- GameMgr OO implementation

This commit is contained in:
Wiesner András 2024-09-17 16:47:47 +02:00
parent ab07345195
commit b01c5011ed
3 changed files with 446 additions and 34 deletions

View File

@ -1,13 +1,407 @@
<?php
class Game {
class Game
{
public const DEFAULT_GAME_PROPERTIES = [
"forward_only" => false, // player may traverse back and forth between challenges
"time_limit" => 0, // no time limit; otherwise, this field indicates time limit in seconds
"repeatable" => false // this test can be taken multiple times
];
public const CURRENT_GAME_VERSION = 2; // MUST BE INCREMENTED!!
// --------
private int $_id; // Game's ID
private string $name; // Game's name
private string $owner; // Game's owner
private array $contributors; // Contributors to the game
private string $description; // Game's description
private bool $gameFileIsPresent; // indicates if game CSV is in place
private bool $gameFileIsPresent; // Indicates if game CSV is in place
private array $properties; // Collection of several game properties
private bool $public; // Is this game publicly available?
private string $publicId; // Public-accessible ID
private int $VERSION; // Game representation version (used during updates)
private GameMgr $gameMgr; // Game manager managing this instance
private array $challenges; // Challenges
// -------
static private function patchUpGameDate(array &$a) : void
{
$version = $a["version"] ?? 0;
if ($version < 2) { // update to game version 2
if (!key_exists("public_id", $a)) {
$a["public"] = false;
$a["public_id"] = generate_public_id();
}
$a["version"] = 2;
}
}
// Store modifications.
function storeMods(): void
{
$this->patchUpGameDate();
$this->gameMgr->updateGame($this);
}
// Load game challenges.
public function loadChallenges(): void
{
if ($this->isGameFileIsPresent()) { // load if file is present
$this->challenges = json_decode(file_get_contents($this->getGameFile()), true);
}
}
// Save challenges.
public function saveChallenges(): void
{
file_put_contents($this->getGameFile(), json_encode($this->challenges)); // store challenges in JSON-format
}
// -------
function __construct(GameMgr &$gameMgr, string $name, string $description = "", int $id = -1, string $owner = "",
array $contributors = [], bool $gameFileIsPresent = false, array $properties = [],
bool $public = false, string $publicId = "", int $version = 2)
{
$this->gameMgr = $gameMgr;
$this->id = $id;
$this->name = $name;
$this->description = $description;
$this->owner = $owner;
$this->contributors = $contributors;
$this->gameFileIsPresent = $gameFileIsPresent;
$this->properties = $properties;
$this->public = $public;
$this->publicId = $publicId;
$this->VERSION = $version;
$this->challenges = [];
}
// Create game from array representation.
static function fromArray(GameMgr &$gameMgr, array $a): Game
{
$id = $a["id"] ?? -1;
self::patchUpGameDate($a);
return new Game($gameMgr, $a["name"], $a["description"], $id, $a["owner"], $a["contributors"],
$a["gameFileIsPresent"], $a["properties"], $a["public"], $a["publicId"], $a["version"]);
}
// Convert game to array representation.
function toArray(array $omit = []): array
{
$a = [
"_id" => $this->_id,
"name" => $this->name,
"description" => $this->description,
"owner" => $this->owner,
"contributors" => $this->contributors,
"game_file_present" => $this->gameFileIsPresent,
"properties" => $this->properties,
"public" => $this->public,
"public_id" => $this->publicId,
"version" => $this->VERSION,
];
foreach ($omit as $field) {
unset($a[$field]);
}
return $a;
}
// Export challenges to a CSV file.
function exportChallengesToCSV(&$f): void
{
// load challenges
$this->loadChallenges();
// populate CSV file
foreach ($this->challenges as $ch) {
$csvline = [
$ch["question"],
$ch["image_url"],
];
$csvline = array_merge($csvline, $ch["answers"]);
fputcsv($f, $csvline);
}
}
// Get game directory NAME with path. Does not check if the game directory exists or not.
function getGameDir(): string
{
return GAMEMEDIA_DIR . DIRECTORY_SEPARATOR . $this->getId();
}
// Get game file NAME with path. Does not check whether the game file is in place or not.
function getGameFile(): string
{
return GAMEMEDIA_DIR . DIRECTORY_SEPARATOR . $this->getId() . DIRECTORY_SEPARATOR . GAME_FILE;
}
// Is the given user the owner of the game?
function isUserOwner(string $nickname): bool
{
return $this->owner === $nickname;
}
// Is the given user a contributor of the game?
function isUserContributor(string $nickname): bool
{
return in_array($nickname, $this->contributors);
}
// Is user contributor or owner?
function isUserContributorOrOwner(string $nickname): bool
{
return $this->isUserContributor($nickname) || $this->isUserOwner($nickname);
}
const CSV_ENCODINGS = ["UTF-8", "Windows-1252"];
// Import challenges from a CSV table.
function importChallengesFromCSV(string $csv_path): array
{
// convert text encoding into UTF-8
$data = file_get_contents($csv_path);
$encoding = "UNKNOWN";
foreach (self::CSV_ENCODINGS as $enc) { // detect encoding
if (mb_check_encoding($data, $enc)) {
$encoding = $enc;
break;
}
}
if ($encoding !== "UNKNOWN") { // if encoding has been detected successfully
$data = mb_convert_encoding($data, "UTF-8", $encoding);
file_put_contents($csv_path, $data);
}
// clear challenges
$this->challenges = [];
// load filled CSV file
$f = fopen($csv_path, "r");
if (!$f) { // failed to open file
return ["n" => 0, "encoding" => $encoding];
}
while ($csvline = fgetcsv($f)) {
// skip empty lines
if (trim(implode("", $csvline)) === "") {
continue;
}
if (count($csvline) >= 3) {
// construct challenge record
$ch = [
"question" => trim($csvline[0]),
"image_url" => trim($csvline[1]),
"correct_answer" => trim($csvline[2]),
"answers" => array_filter(array_slice($csvline, 2), function ($v) {
return trim($v) !== "";
})
];
// if image is attached to the challenge, then give a random name to the image
if ($ch["image_url"] !== "") {
$old_img_name = $ch["image_url"];
$ext = pathinfo($old_img_name, PATHINFO_EXTENSION);
$ext = ($ext !== "") ? ("." . $ext) : $ext;
$new_img_name = uniqid("img_", true) . $ext;
$ch["image_url"] = $new_img_name;
// rename the actual file
$old_img_path = $this->getGameDir() . DIRECTORY_SEPARATOR . $old_img_name;
$new_img_path = $this->getGameDir() . DIRECTORY_SEPARATOR . $new_img_name;
rename($old_img_path, $new_img_path);
}
// store the challenge
$this->challenges[] = $ch;
}
}
fclose($f);
// save challenges
$this->saveChallenges();
// update game with game file present
$this->gameFileIsPresent = true;
// store modifications
$this->storeMods();
return ["n" => count($this->challenges), "encoding" => $encoding];
}
// ---------
public function getName(): string
{
return $this->name;
}
public function setName(string $name): void
{
$this->name = $name;
}
public function getOwner(): string
{
return $this->owner;
}
public function setOwner(string $owner): void
{
$this->owner = $owner;
}
public function getContributors(): array
{
return $this->contributors;
}
public function setContributors(array $contributors): void
{
$this->contributors = $contributors;
}
public function getDescription(): string
{
return $this->description;
}
public function setDescription(string $description): void
{
$this->description = $description;
}
public function getId(): int
{
return $this->_id;
}
public function isGameFileIsPresent(): bool
{
return $this->gameFileIsPresent;
}
public function& getProperties(): array
{
return $this->properties;
}
public function isPublic(): bool
{
return $this->public;
}
public function getPublicId(): string
{
return $this->publicId;
}
public function getChallenges(): array
{
return $this->challenges;
}
}
class GameMgr
{
private \SleekDB\Store $db; // game database
// --------
static private function genPublicId(): string
{
return uniqid("p");
}
// --------
function __construct()
{
$this->db = new \SleekDB\Store(GAMEDB, DATADIR, ["timeout" => false]);
}
// Get game by ID.
function getGame(string $gameid): Game|null
{
$game_data_array = $this->db->findById($gameid);
return count($game_data_array) != 0 ? Game::fromArray($this, $game_data_array[0]) : null;
}
// Get public game. FIXME!!!
function getPublicGame(string $public_id): Game|null
{
$game_data_array = $this->db->findBy([["public", "=", "true"], "AND", ["public_id", "=", $public_id]]);
return count($game_data_array) != 0 ? Game::fromArray($this, $game_data_array[0]) : null;
}
// Update game.
function updateGame(Game $game): void
{
$a = $game->toArray();
$this->db->update($a);
}
function addGame(string $name, string $owner, string $description, array $properties = Game::DEFAULT_GAME_PROPERTIES,
array $contributors = [], array $challenges = []): bool
{
$game_data = [
"name" => $name,
"owner" => $owner,
"contributors" => $contributors,
"description" => $description,
"game_file_present" => false,
"properties" => $properties,
"groups" => [],
"public" => false,
"public_id" => self::genPublicId(),
"version" => Game::CURRENT_GAME_VERSION
];
$game_data = $this->db->insert($game_data);
// prepare game context
$game = Game::fromArray($this, $game_data);
$current_game_media_dir = $game->getGameDir();
mkdir($current_game_media_dir);
$game->saveChallenges();
return true;
}
// Delete game by ID.
function deleteGame(string $gameid): void
{
$this->db->deleteById($gameid);
}
// Get all game data by contributor nickname.
function getAllGameDataByContributor(string $nickname): array {
$games = [];
if ($nickname !== "*") {
$game_data_array = $this->db->findBy([["owner", "=", $nickname], "OR", ["contributors", "CONTAINS", $nickname]]);
} else {
$game_data_array = $this->db->findAll();
}
foreach ($game_data_array as $game_data) {
$games[] = Game::fromArray($this, $game_data);
}
return $games;
}
// Get all games.
function getAllGames() : array {
$gamesa = $this->db->findAll();
$games = [];
foreach ($gamesa as $a) {
$games[] = new Game($this, $a);
}
return $games;
}
// -------
}

View File

@ -63,79 +63,91 @@ class Group
}
// Get group's ID.
function getID() : int
function getID(): int
{
return $this->_id;
}
// Get group's name.
function getName() : string
function getName(): string
{
return $this->name;
}
// Set group's name.
function setName(string $name) : void {
function setName(string $name): void
{
$this->name = $name;
$this->storeMods();
}
// Tell if group is unique
function isUnique() : bool {
function isUnique(): bool
{
return $this->unique;
}
// Get group's description.
function getDescription() : string {
function getDescription(): string
{
return $this->description;
}
// Set group's description.
function setDescription(string $description) : void {
function setDescription(string $description): void
{
$this->description = $description;
$this->storeMods();
}
// Get group's owner.
function getOwner() : string {
function getOwner(): string
{
return $this->owner;
}
// Set group's owner.
function setOwner(string $owner) : void {
function setOwner(string $owner): void
{
$this->owner = $owner;
$this->storeMods();
}
// Get list of editors.
function getEditors() : array {
function getEditors(): array
{
return $this->editors;
}
// Set editors.
function setEditors(array $editors) : void {
function setEditors(array $editors): void
{
$this->editors = $editors;
$this->storeMods();
}
// Get group members.
function getMembers() : array {
function getMembers(): array
{
return $this->members;
}
// Set group members.
function setMembers(array $members) : void {
function setMembers(array $members): void
{
$this->members = $members;
$this->storeMods();
}
// Get games.
function getGames() : array {
function getGames(): array
{
return $this->games;
}
// Set games.
function setGames(array $games) : void {
function setGames(array $games): void
{
$this->games = $games;
$this->storeMods();
}
@ -168,12 +180,14 @@ class Group
}
// Returns whether the user is an editor of this group.
function isUserEditor(string $nickname): bool {
function isUserEditor(string $nickname): bool
{
return in_array($nickname, $this->editors);
}
// Returns whether the user is an editor or the owner of the group.
function isUserContributor(string $nickname): bool {
function isUserContributor(string $nickname): bool
{
return $this->isUserEditor($nickname) || ($this->owner === $nickname);
}
@ -190,7 +204,7 @@ class Group
}
// Get groups unique name.
function getUniqueName() : string
function getUniqueName(): string
{
return $this->name . ($this->unique ? "" : ("#" . $this->_id));
}
@ -202,7 +216,8 @@ class GroupMgr
// -------------------------
private function manageTwins(int $current_group_id, string $groupname): bool {
private function manageTwins(int $current_group_id, string $groupname): bool
{
// make test on name uniqueness
$twins = $this->db->findBy([["groupname", "=", "$groupname"], "AND", ["_id", "!=", $current_group_id]]);
$unique = count($twins) == 0;
@ -235,7 +250,8 @@ class GroupMgr
}
// Add a new group.
function addGroup(string $groupname, string $owner, string $description = ""): bool {
function addGroup(string $groupname, string $owner, string $description = ""): bool
{
// test name uniqueness
$unique = $this->manageTwins(0, $groupname);
@ -259,10 +275,10 @@ class GroupMgr
// Delete group.
function deleteGroup(string $groupid): void
{
$group = $this->getGroup($groupid);
if ($group != null) {
$this->db->deleteById($groupid);
}
//$group = $this->getGroup($groupid);
//if ($group != null) {
$this->db->deleteById($groupid);
//}
}
// Get all groups.
@ -336,7 +352,8 @@ class GroupMgr
private array $groupid_cache = [];
// Convert group IDs into unique group names IN PLACE!
function resolveGroupIds(array &$groupids): void {
function resolveGroupIds(array &$groupids): void
{
foreach ($groupids as &$groupid) {
if (array_key_exists($groupid, $this->groupid_cache)) {
$group = $this->getGroup($groupid); // fetch group

View File

@ -125,7 +125,8 @@ class User
}
// Has the user quizmaster privileges?
function hasQuizmasterPrivilege(): bool {
function hasQuizmasterPrivilege(): bool
{
return $this->privilege == PRIVILEGE_QUIZMASTER;
}
}
@ -196,13 +197,13 @@ class UserMgr
return;
}
$user = $this->getUser($nickname);
if ($user !== null) {
foreach ($user->getGroups() as $groupid) {
change_group_user_assignments($groupid, null, $nickname);
}
$this->db->deleteBy(["nickname", "=", $nickname]);
}
// $user = $this->getUser($nickname);
// if ($user !== null) {
// foreach ($user->getGroups() as $groupid) {
// change_group_user_assignments($groupid, null, $nickname);
// }
$this->db->deleteBy(["nickname", "=", $nickname]);
//}
}
// Dump all users. Users come wrapped in User objects.