From 807b4d024f5937d8a57febf78777509c2691bf18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Vigh?= Date: Wed, 5 May 2021 19:16:54 +0200 Subject: [PATCH 01/14] extended database with UserPlayedPlace table for tracking when and what place a User played in game --- .../20210503_1040_user_played_place.sql | 12 +++ src/PersistentData/Model/UserPlayedPlace.php | 99 +++++++++++++++++++ src/Repository/UserPlayedPlaceRepository.php | 52 ++++++++++ 3 files changed, 163 insertions(+) create mode 100644 database/migrations/structure/20210503_1040_user_played_place.sql create mode 100644 src/PersistentData/Model/UserPlayedPlace.php create mode 100644 src/Repository/UserPlayedPlaceRepository.php diff --git a/database/migrations/structure/20210503_1040_user_played_place.sql b/database/migrations/structure/20210503_1040_user_played_place.sql new file mode 100644 index 0000000..afe60b3 --- /dev/null +++ b/database/migrations/structure/20210503_1040_user_played_place.sql @@ -0,0 +1,12 @@ +CREATE TABLE `user_played_place` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `user_id` int(10) unsigned NOT NULL, + `place_id` int(10) unsigned NOT NULL, + `last_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `occurrences` int(10) NOT NULL DEFAULT 1, + PRIMARY KEY(`id`), + KEY `user_id` (`user_id`), + KEY `place_id` (`place_id`), + CONSTRAINT `user_played_place_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`), + CONSTRAINT `user_played_place_place_id` FOREIGN KEY (`place_id`) REFERENCES `places` (`id`) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; diff --git a/src/PersistentData/Model/UserPlayedPlace.php b/src/PersistentData/Model/UserPlayedPlace.php new file mode 100644 index 0000000..d612476 --- /dev/null +++ b/src/PersistentData/Model/UserPlayedPlace.php @@ -0,0 +1,99 @@ + User::class, 'place' => Place::class]; + + private ?User $user = null; + + private ?int $userId = null; + + private ?Place $place = null; + + private ?int $placeId = null; + + private DateTime $lastTime; + + private int $occurrences = 1; + + public function setUser(User $user): void + { + $this->user = $user; + } + + public function setUserId(int $userId): void + { + $this->userId = $userId; + } + + public function setPlace(Place $place): void + { + $this->place = $place; + } + + public function setPlaceId(int $placeId): void + { + $this->placeId = $placeId; + } + + public function setLastTimeDate(DateTime $lastTime): void + { + $this->lastTime = $lastTime; + } + + public function setLastTime(string $lastTime): void + { + $this->lastTime = new DateTime($lastTime); + } + + public function setOccurrences(int $occurrences): void + { + $this->occurrences = $occurrences; + } + + public function incrementOccurrences(): void + { + $this->occurrences++; + } + + public function getUser(): ?User + { + return $this->user; + } + + public function getUserId(): ?int + { + return $this->userId; + } + + public function getPlace(): ?Place + { + return $this->place; + } + + public function getPlaceId(): ?int + { + return $this->placeId; + } + + public function getLastTimeDate(): DateTime + { + return $this->lastTime; + } + + public function getLastTime(): string + { + return $this->lastTime->format('Y-m-d H:i:s'); + } + + public function getOccurrences(): int + { + return $this->occurrences; + } +} diff --git a/src/Repository/UserPlayedPlaceRepository.php b/src/Repository/UserPlayedPlaceRepository.php new file mode 100644 index 0000000..0e497b7 --- /dev/null +++ b/src/Repository/UserPlayedPlaceRepository.php @@ -0,0 +1,52 @@ +pdm = new PersistentDataManager(); + } + + public function getByUser(User $user): Generator + { + $select = new Select(\Container::$dbConnection); + $select->where('user_id', '=', $user->getId()); + + yield from $this->pdm->selectMultipleFromDb($select, UserPlayedPlace::class); + } + + public function getByPlace(Place $place): Generator + { + $select = new Select(\Container::$dbConnection); + $select->where('place_id', '=', $place->getId()); + + yield from $this->pdm->selectMultipleFromDb($select, UserPlayedPlace::class); + } + + public function getAllByUser(User $user) : Generator + { + $select = new Select(\Container::$dbConnection); + $select->where('user_id', '=', $user->getId()); + + yield from $this->pdm->selectMultipleFromDb($select, UserPlayedPlace::class, true); + } + + public function getByUserIdAndPlaceId(int $userId, int $placeId) : ?UserPlayedPlace + { + $select = new Select(\Container::$dbConnection); + $select->where('user_id', '=', $userId); + $select->where('place_id', '=', $placeId); + + return $this->pdm->selectFromDb($select, UserPlayedPlace::class); + } +} -- 2.45.2 From 66c871fbc2db3c3ab0b8ddec37df5fb61a42504a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Vigh?= Date: Wed, 5 May 2021 19:18:07 +0200 Subject: [PATCH 02/14] Extended Select functionality with handling of derived tables --- src/Database/Query/Select.php | 60 +++++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/src/Database/Query/Select.php b/src/Database/Query/Select.php index 08a1049..ab8ee74 100644 --- a/src/Database/Query/Select.php +++ b/src/Database/Query/Select.php @@ -12,6 +12,8 @@ class Select const CONDITION_HAVING = 1; + const DERIVED_TABLE_KEY = 'DERIVED'; + private IConnection $connection; private string $table; @@ -55,6 +57,11 @@ class Select return $this; } + public function setDerivedTableAlias(string $tableAlias): Select + { + return $this->setTableAliases([Select::DERIVED_TABLE_KEY => $tableAlias]); + } + public function from(string $table): Select { $this->table = $table; @@ -194,6 +201,11 @@ class Select } } + private function isDerivedTable(): bool + { + return array_key_exists(Select::DERIVED_TABLE_KEY, $this->tableAliases); + } + private function addJoin(string $type, $table, $column1, string $relation, $column2): void { $this->joins[] = [$type, $table, $column1, $relation, $column2]; @@ -211,10 +223,14 @@ class Select private function generateQuery(): array { - $queryString = 'SELECT ' . $this->generateColumns() . ' FROM ' . $this->generateTable($this->table, true); + list($innerQuery, $innerParams) = $this->generateTable($this->table, true); + $queryString = 'SELECT ' . $this->generateColumns() . ' FROM ' . $innerQuery; if (count($this->joins) > 0) { - $queryString .= ' ' . $this->generateJoins(); + list($joinQuery, $joinParams) = $this->generateJoins(); + $queryString .= ' ' . $joinQuery; + } else { + $joinParams = []; } if (count($this->conditions[self::CONDITION_WHERE]) > 0) { @@ -245,20 +261,32 @@ class Select $queryString .= ' LIMIT ' . $this->limit[1] . ', ' . $this->limit[0]; } - return [$queryString, array_merge($whereParams, $havingParams)]; + if($this->isDerivedTable()) { + $queryString = '(' . $queryString . ') AS ' . $this->tableAliases[Select::DERIVED_TABLE_KEY]; + } + + return [$queryString, array_merge($innerParams, $joinParams, $whereParams, $havingParams)]; } - private function generateTable($table, bool $defineAlias = false): string + private function generateTable($table, bool $defineAlias = false): array { + $params = []; + if ($table instanceof RawExpression) { - return (string) $table; + return [(string) $table, $params]; + } + + if($table instanceof Select) + { + return $table->generateQuery(); } if (isset($this->tableAliases[$table])) { - return ($defineAlias ? Utils::backtick($this->tableAliases[$table]) . ' ' . Utils::backtick($table) : Utils::backtick($table)); + $queryString = ($defineAlias ? Utils::backtick($this->tableAliases[$table]) . ' ' . Utils::backtick($table) : Utils::backtick($table)); + return [$queryString, $params]; } - return Utils::backtick($table); + return [Utils::backtick($table), $params]; } private function generateColumn($column): string @@ -271,7 +299,8 @@ class Select $out = ''; if ($column[0]) { - $out .= $this->generateTable($column[0]) . '.'; + list($tableName, $params) = $this->generateTable($column[0]); + $out .= $tableName . '.'; } $out .= Utils::backtick($column[1]); @@ -297,15 +326,20 @@ class Select return implode(',', $columns); } - private function generateJoins(): string + private function generateJoins(): array { $joins = $this->joins; + + $joinQueries = []; + $params = []; - array_walk($joins, function (&$value, $key) { - $value = $value[0] . ' JOIN ' . $this->generateTable($value[1], true) . ' ON ' . $this->generateColumn($value[2]) . ' ' . $value[3] . ' ' . $this->generateColumn($value[4]); - }); + foreach($joins as $value) { + list($joinQueryFragment, $paramsFragment) = $this->generateTable($value[1], true); + array_push($joinQueries, $value[0] . ' JOIN ' . $joinQueryFragment . ' ON ' . $this->generateColumn($value[2]) . ' ' . $value[3] . ' ' . $this->generateColumn($value[4])); + $params = array_merge($params, $paramsFragment); + } - return implode(' ', $joins); + return [implode(' ', $joinQueries), $params]; } private function generateConditions(int $type): array -- 2.45.2 From 01f3c7c8cf7a093f0a20ee4f86276b04636769f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Vigh?= Date: Wed, 5 May 2021 19:19:38 +0200 Subject: [PATCH 03/14] added preference for places not seen by user in the selection of places for the game --- src/Controller/GameFlowController.php | 29 +++++- src/Repository/PlaceRepository.php | 125 ++++++++++++++++++++++++++ 2 files changed, 151 insertions(+), 3 deletions(-) diff --git a/src/Controller/GameFlowController.php b/src/Controller/GameFlowController.php index cca90c1..0cddef7 100644 --- a/src/Controller/GameFlowController.php +++ b/src/Controller/GameFlowController.php @@ -7,8 +7,10 @@ use MapGuesser\Response\JsonContent; use MapGuesser\Interfaces\Response\IContent; use MapGuesser\Multi\MultiConnector; use MapGuesser\PersistentData\PersistentDataManager; +use MapGuesser\PersistentData\Model\UserPlayedPlace; use MapGuesser\Repository\MultiRoomRepository; use MapGuesser\Repository\PlaceRepository; +use MapGuesser\Repository\UserPlayedPlaceRepository; class GameFlowController { @@ -25,6 +27,8 @@ class GameFlowController private PlaceRepository $placeRepository; + private UserPlayedPlaceRepository $userPlayedPlaceRepository; + public function __construct(IRequest $request) { $this->request = $request; @@ -32,6 +36,7 @@ class GameFlowController $this->multiConnector = new MultiConnector(); $this->multiRoomRepository = new MultiRoomRepository(); $this->placeRepository = new PlaceRepository(); + $this->userPlayedPlaceRepository = new UserPlayedPlaceRepository(); } public function initialData(): IContent @@ -43,8 +48,10 @@ class GameFlowController return new JsonContent(['error' => 'no_session_found']); } + $userId = $session->get('userId'); + if (!isset($state['currentRound']) || $state['currentRound'] == -1 || $state['currentRound'] >= static::NUMBER_OF_ROUNDS) { - $this->startNewGame($state, $mapId); + $this->startNewGame($state, $mapId, $userId); $session->set('state', $state); } @@ -145,6 +152,22 @@ class GameFlowController $session->set('state', $state); + // save the selected place for the round in UserPlayedPlace + $userId = $session->get('userId'); + if(isset($userId)) { + $placeId = $last['placeId']; + $userPlayedPlace = $this->userPlayedPlaceRepository->getByUserIdAndPlaceId($userId, $placeId); + if(!$userPlayedPlace) { + $userPlayedPlace = new UserPlayedPlace(); + $userPlayedPlace->setUserId($userId); + $userPlayedPlace->setPlaceId($placeId); + } else { + $userPlayedPlace->incrementOccurrences(); + } + $userPlayedPlace->setLastTimeDate(new DateTime()); + $this->pdm->saveToDb($userPlayedPlace); + } + return new JsonContent($response); } @@ -222,9 +245,9 @@ class GameFlowController return ['distance' => $distance, 'score' => $score]; } - private function startNewGame(array &$state, int $mapId): void + private function startNewGame(array &$state, int $mapId, $userId = null): void { - $places = $this->placeRepository->getRandomNForMapWithValidPano($mapId, static::NUMBER_OF_ROUNDS); + $places = $this->placeRepository->getRandomNPlaces($mapId, static::NUMBER_OF_ROUNDS, $userId); $state['rounds'] = []; $state['currentRound'] = 0; diff --git a/src/Repository/PlaceRepository.php b/src/Repository/PlaceRepository.php index 84c90cf..a56ad7c 100644 --- a/src/Repository/PlaceRepository.php +++ b/src/Repository/PlaceRepository.php @@ -28,6 +28,16 @@ class PlaceRepository yield from $this->pdm->selectMultipleFromDb($select, Place::class); } + //TODO: use Map and User instead of id + public function getRandomNPlaces(int $mapId, int $n, int $userId = null): array + { + if(!isset($userId)) { + return $this->getRandomNForMapWithValidPano($mapId, $n); + } else { + return $this->getRandomNewNForMapWithValidPano($mapId, $n, $userId); + } + } + //TODO: use Map instead of id public function getRandomNForMapWithValidPano(int $mapId, int $n, array $exclude = []): array { @@ -79,4 +89,119 @@ class PlaceRepository return $this->pdm->selectFromDb($select, Place::class); } + + private function getRandomNewNForMapWithValidPano(int $mapId, int $n, int $userId, array $exclude = []): array + { + $places = []; + + // Never visited places + do { + $place = $this->getRandomNeverVisitedForMapWithValidPano($mapId, $userId, $exclude); + if(isset($place)) { + $places[] = $place; + $exclude[] = $place->getId(); + } + } while(count($places) < $n && isset($place)); + + // Places visited in the longest time + while(count($places) < $n) + { + $place = $this->getRandomOldPlaceForMapWithValidPano($mapId, $userId, $exclude); + if(isset($place)) + { + $places[] = $place; + $exclude[] = $place->getId(); + } + } + + return $places; + } + + private function getRandomNeverVisitedForMapWithValidPano(int $mapId, $userId, array $exclude = []): ?Place + { + do { + $place = $this->selectRandomNeverVisitedFromDbForMap($mapId, $userId, $exclude); + if($place === null) { + // there is no more never visited place left + return null; + } + + $panoId = $place->getFreshPanoId(); + if($panoId === null) { + $exclude[] = $place->getId(); + } + } while($panoId === null); + + return $place; + } + + private function selectRandomNeverVisitedFromDbForMap(int $mapId, $userId, array $exclude): ?Place + { + $select_places_by_current_user = new Select(\Container::$dbConnection, 'user_played_place'); + $select_places_by_current_user->columns(['place_id', 'last_time']); + $select_places_by_current_user->where('user_id', '=', $userId); + $select_places_by_current_user->setDerivedTableAlias('places_by_current_user'); + + $select_unvisited = new Select(\Container::$dbConnection, 'places'); + $select_unvisited->leftJoin($select_places_by_current_user, ['places', 'id'], '=', ['places_by_current_user', 'place_id']); + $select_unvisited->where('map_id', '=', $mapId); + $select_unvisited->where('last_time', '=', null); + $select_unvisited->where('id', 'NOT IN', $exclude); + $numberOfUnvisitedPlaces = $select_unvisited->count(); + + if($numberOfUnvisitedPlaces == 0) { + return null; + } + + $randomOffset = random_int(0, $numberOfUnvisitedPlaces - 1); + + // $select_unvisited->orderBy('last_time'); + $select_unvisited->limit(1, $randomOffset); + + return $this->pdm->selectFromDb($select_unvisited, Place::class); + } + + private function getRandomOldPlaceForMapWithValidPano(int $mapId, $userId, array $exclude = []): ?Place + { + do { + $place = $this->selectRandomOldPlaceFromDbForMap($mapId, $userId, $exclude); + if($place === null) { + // there is no more never visited place left + return null; + } + + $panoId = $place->getFreshPanoId(); + if($panoId === null) { + $exclude[] = $place->getId(); + } + } while($panoId === null); + + return $place; + } + + private function selectRandomOldPlaceFromDbForMap(int $mapId, $userId, array $exclude): ?Place + { + $select_places_by_current_user = new Select(\Container::$dbConnection, 'user_played_place'); + $select_places_by_current_user->columns(['place_id', 'last_time']); + $select_places_by_current_user->where('user_id', '=', $userId); + $select_places_by_current_user->setDerivedTableAlias('places_by_current_user'); + + $select_old_place = new Select(\Container::$dbConnection, 'places'); + $select_old_place->leftJoin($select_places_by_current_user, ['places', 'id'], '=', ['places_by_current_user', 'place_id']); + $select_old_place->where('map_id', '=', $mapId); + $select_old_place->where('last_time', 'IS NOT', null); + $select_old_place->where('id', 'NOT IN', $exclude); + $numberOfOldPlaces = $select_old_place->count(); + + if($numberOfOldPlaces == 0) { + return null; + } + + $randomOffset = random_int(0, $numberOfOldPlaces - 1); + + // $select_unvisited->orderBy('last_time'); + $select_old_place->limit(1, $randomOffset); + + return $this->pdm->selectFromDb($select_old_place, Place::class); + } } -- 2.45.2 From 3c6a7a3c5f343f10ce7315bf8711a77d6280a8c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Vigh?= Date: Wed, 5 May 2021 21:50:23 +0200 Subject: [PATCH 04/14] selection with preference for older places; refactored PlaceRepository --- src/Repository/PlaceRepository.php | 195 ++++++++++++----------------- 1 file changed, 83 insertions(+), 112 deletions(-) diff --git a/src/Repository/PlaceRepository.php b/src/Repository/PlaceRepository.php index a56ad7c..5119666 100644 --- a/src/Repository/PlaceRepository.php +++ b/src/Repository/PlaceRepository.php @@ -34,7 +34,9 @@ class PlaceRepository if(!isset($userId)) { return $this->getRandomNForMapWithValidPano($mapId, $n); } else { - return $this->getRandomNewNForMapWithValidPano($mapId, $n, $userId); + $unvisitedPlaces = $this->getRandomUnvisitedNForMapWithValidPano($mapId, $n, $userId); + $oldPlaces = $this->getRandomOldNForMapWithValidPano($mapId, $n - count($unvisitedPlaces), $userId); + return array_merge($unvisitedPlaces, $oldPlaces); } } @@ -43,8 +45,13 @@ class PlaceRepository { $places = []; + $select = new Select(\Container::$dbConnection, 'places'); + $select->where('id', 'NOT IN', $exclude); + $select->where('map_id', '=', $mapId); + $numberOfPlaces = $select->count(); + for ($i = 1; $i <= $n; ++$i) { - $place = $this->getRandomForMapWithValidPano($mapId, $exclude); + $place = $this->getRandomForMapWithValidPano($numberOfPlaces, $select, $exclude); $places[] = $place; $exclude[] = $place->getId(); @@ -53,60 +60,110 @@ class PlaceRepository return $places; } - //TODO: use Map instead of id - public function getRandomForMapWithValidPano(int $mapId, array $exclude = []): Place + private function getRandomForMapWithValidPano($numberOfPlaces, $select, array &$exclude, $randomSelection = null): ?Place { do { - $place = $this->selectRandomFromDbForMap($mapId, $exclude); + $numberOfPlacesLeft = $numberOfPlaces - count($exclude); + $place = $this->selectRandomFromDbForMap($numberOfPlacesLeft, $select, $exclude, $randomSelection); + if($place === null) { + // there is no more never visited place left + return null; + } $panoId = $place->getFreshPanoId(); - if ($panoId === null) { + if($panoId === null) { $exclude[] = $place->getId(); } - } while ($panoId === null); + } while($panoId === null); return $place; } - private function selectRandomFromDbForMap(int $mapId, array $exclude): Place + private function selectRandomFromDbForMap($numberOfPlacesLeft, $select, array $exclude, $randomSelection): ?Place { - //TODO: omit table name here - $select = new Select(\Container::$dbConnection, 'places'); - $select->where('id', 'NOT IN', $exclude); - $select->where('map_id', '=', $mapId); + if($numberOfPlacesLeft <= 0) + return null; - $numberOfPlaces = $select->count(); - - //TODO: prevent this - if ($numberOfPlaces === 0) { - throw new \Exception('There is no selectable place (count was 0).'); + if(!isset($randomSelection)) { + $randomOffset = random_int(0, $numberOfPlacesLeft - 1); + } else { + $randomOffset = $randomSelection($numberOfPlacesLeft); } - $randomOffset = random_int(0, $numberOfPlaces - 1); - - $select->orderBy('id'); + // $select_unvisited->orderBy('last_time'); + $select->where('id', 'NOT IN', $exclude); $select->limit(1, $randomOffset); return $this->pdm->selectFromDb($select, Place::class); } - private function getRandomNewNForMapWithValidPano(int $mapId, int $n, int $userId, array $exclude = []): array + // Never visited places + private function getRandomUnvisitedNForMapWithValidPano(int $mapId, int $n, int $userId): array { $places = []; + $exclude = []; - // Never visited places + // list of places visited by user + $selectPlacesByCurrentUser = new Select(\Container::$dbConnection, 'user_played_place'); + $selectPlacesByCurrentUser->columns(['place_id', 'last_time']); + $selectPlacesByCurrentUser->where('user_id', '=', $userId); + $selectPlacesByCurrentUser->setDerivedTableAlias('places_by_current_user'); + + // count the places never visited + $selectUnvisited = new Select(\Container::$dbConnection, 'places'); + $selectUnvisited->leftJoin($selectPlacesByCurrentUser, ['places', 'id'], '=', ['places_by_current_user', 'place_id']); + $selectUnvisited->where('map_id', '=', $mapId); + $selectUnvisited->where('last_time', '=', null); + $numberOfUnvisitedPlaces = $selectUnvisited->count(); + + // look for as many new places as possible but maximum $n do { - $place = $this->getRandomNeverVisitedForMapWithValidPano($mapId, $userId, $exclude); + $place = $this->getRandomForMapWithValidPano($numberOfUnvisitedPlaces, $selectUnvisited, $exclude); if(isset($place)) { $places[] = $place; $exclude[] = $place->getId(); } } while(count($places) < $n && isset($place)); - // Places visited in the longest time + return $places; + } + + // Places visited in the longest time + private function getRandomOldNForMapWithValidPano(int $mapId, int $n, int $userId): array + { + $places = []; + $exclude = []; + + // list of places visited by user + $selectPlacesByCurrentUser = new Select(\Container::$dbConnection, 'user_played_place'); + $selectPlacesByCurrentUser->columns(['place_id', 'last_time']); + $selectPlacesByCurrentUser->where('user_id', '=', $userId); + $selectPlacesByCurrentUser->setDerivedTableAlias('places_by_current_user'); + + // count places that were visited at least once + $selectOldPlaces = new Select(\Container::$dbConnection, 'places'); + $selectOldPlaces->leftJoin($selectPlacesByCurrentUser, ['places', 'id'], '=', ['places_by_current_user', 'place_id']); + $selectOldPlaces->where('map_id', '=', $mapId); + $selectOldPlaces->where('last_time', 'IS NOT', null); + $numberOfOldPlaces = $selectOldPlaces->count(); + + // set order by datetime, oldest first + $selectOldPlaces->orderBy('last_time'); + + // selection algorithm with preference (weighting) for older places + $gaussianRandomSelection = function($numberOfPlaces) { + $stdev = 0.2; + $avg = 0.0; + $x = mt_rand() / mt_getrandmax(); + $y = mt_rand() / mt_getrandmax(); + $randomNum = abs(sqrt(-2 * log($x)) * cos(2 * pi() * $y) * $stdev + $avg); + return (int) min($randomNum * $numberOfPlaces, $numberOfPlaces - 1); + }; + + // look for n - numberOfUnvisitedPlaces places while(count($places) < $n) { - $place = $this->getRandomOldPlaceForMapWithValidPano($mapId, $userId, $exclude); + $place = $this->getRandomForMapWithValidPano($numberOfOldPlaces, $selectOldPlaces, $exclude, $gaussianRandomSelection); if(isset($place)) { $places[] = $place; @@ -117,91 +174,5 @@ class PlaceRepository return $places; } - private function getRandomNeverVisitedForMapWithValidPano(int $mapId, $userId, array $exclude = []): ?Place - { - do { - $place = $this->selectRandomNeverVisitedFromDbForMap($mapId, $userId, $exclude); - if($place === null) { - // there is no more never visited place left - return null; - } - - $panoId = $place->getFreshPanoId(); - if($panoId === null) { - $exclude[] = $place->getId(); - } - } while($panoId === null); - - return $place; - } - - private function selectRandomNeverVisitedFromDbForMap(int $mapId, $userId, array $exclude): ?Place - { - $select_places_by_current_user = new Select(\Container::$dbConnection, 'user_played_place'); - $select_places_by_current_user->columns(['place_id', 'last_time']); - $select_places_by_current_user->where('user_id', '=', $userId); - $select_places_by_current_user->setDerivedTableAlias('places_by_current_user'); - - $select_unvisited = new Select(\Container::$dbConnection, 'places'); - $select_unvisited->leftJoin($select_places_by_current_user, ['places', 'id'], '=', ['places_by_current_user', 'place_id']); - $select_unvisited->where('map_id', '=', $mapId); - $select_unvisited->where('last_time', '=', null); - $select_unvisited->where('id', 'NOT IN', $exclude); - $numberOfUnvisitedPlaces = $select_unvisited->count(); - - if($numberOfUnvisitedPlaces == 0) { - return null; - } - - $randomOffset = random_int(0, $numberOfUnvisitedPlaces - 1); - - // $select_unvisited->orderBy('last_time'); - $select_unvisited->limit(1, $randomOffset); - - return $this->pdm->selectFromDb($select_unvisited, Place::class); - } - - private function getRandomOldPlaceForMapWithValidPano(int $mapId, $userId, array $exclude = []): ?Place - { - do { - $place = $this->selectRandomOldPlaceFromDbForMap($mapId, $userId, $exclude); - if($place === null) { - // there is no more never visited place left - return null; - } - - $panoId = $place->getFreshPanoId(); - if($panoId === null) { - $exclude[] = $place->getId(); - } - } while($panoId === null); - - return $place; - } - - private function selectRandomOldPlaceFromDbForMap(int $mapId, $userId, array $exclude): ?Place - { - $select_places_by_current_user = new Select(\Container::$dbConnection, 'user_played_place'); - $select_places_by_current_user->columns(['place_id', 'last_time']); - $select_places_by_current_user->where('user_id', '=', $userId); - $select_places_by_current_user->setDerivedTableAlias('places_by_current_user'); - - $select_old_place = new Select(\Container::$dbConnection, 'places'); - $select_old_place->leftJoin($select_places_by_current_user, ['places', 'id'], '=', ['places_by_current_user', 'place_id']); - $select_old_place->where('map_id', '=', $mapId); - $select_old_place->where('last_time', 'IS NOT', null); - $select_old_place->where('id', 'NOT IN', $exclude); - $numberOfOldPlaces = $select_old_place->count(); - - if($numberOfOldPlaces == 0) { - return null; - } - - $randomOffset = random_int(0, $numberOfOldPlaces - 1); - - // $select_unvisited->orderBy('last_time'); - $select_old_place->limit(1, $randomOffset); - - return $this->pdm->selectFromDb($select_old_place, Place::class); - } + } -- 2.45.2 From 8136273b337a6bfab4d76fdbbe6d10cef67d986c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Vigh?= Date: Wed, 5 May 2021 21:58:19 +0200 Subject: [PATCH 05/14] replaced left join with inner join for older places search --- src/Repository/PlaceRepository.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Repository/PlaceRepository.php b/src/Repository/PlaceRepository.php index 5119666..bbc80f3 100644 --- a/src/Repository/PlaceRepository.php +++ b/src/Repository/PlaceRepository.php @@ -142,9 +142,8 @@ class PlaceRepository // count places that were visited at least once $selectOldPlaces = new Select(\Container::$dbConnection, 'places'); - $selectOldPlaces->leftJoin($selectPlacesByCurrentUser, ['places', 'id'], '=', ['places_by_current_user', 'place_id']); + $selectOldPlaces->innerJoin($selectPlacesByCurrentUser, ['places', 'id'], '=', ['places_by_current_user', 'place_id']); $selectOldPlaces->where('map_id', '=', $mapId); - $selectOldPlaces->where('last_time', 'IS NOT', null); $numberOfOldPlaces = $selectOldPlaces->count(); // set order by datetime, oldest first -- 2.45.2 From fb7c0e7a5c662b7be48faadc8cac9a4ce8cb13fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Vigh?= Date: Thu, 6 May 2021 09:36:49 +0200 Subject: [PATCH 06/14] renamed the random int generator function --- src/Repository/PlaceRepository.php | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/Repository/PlaceRepository.php b/src/Repository/PlaceRepository.php index bbc80f3..7f41671 100644 --- a/src/Repository/PlaceRepository.php +++ b/src/Repository/PlaceRepository.php @@ -60,11 +60,11 @@ class PlaceRepository return $places; } - private function getRandomForMapWithValidPano($numberOfPlaces, $select, array &$exclude, $randomSelection = null): ?Place + private function getRandomForMapWithValidPano($numberOfPlaces, $select, array &$exclude, $pickRandomInt = null): ?Place { do { $numberOfPlacesLeft = $numberOfPlaces - count($exclude); - $place = $this->selectRandomFromDbForMap($numberOfPlacesLeft, $select, $exclude, $randomSelection); + $place = $this->selectRandomFromDbForMap($numberOfPlacesLeft, $select, $exclude, $pickRandomInt); if($place === null) { // there is no more never visited place left return null; @@ -79,15 +79,15 @@ class PlaceRepository return $place; } - private function selectRandomFromDbForMap($numberOfPlacesLeft, $select, array $exclude, $randomSelection): ?Place + private function selectRandomFromDbForMap($numberOfPlacesLeft, $select, array $exclude, $pickRandomInt): ?Place { if($numberOfPlacesLeft <= 0) return null; - if(!isset($randomSelection)) { + if(!isset($pickRandomInt)) { $randomOffset = random_int(0, $numberOfPlacesLeft - 1); } else { - $randomOffset = $randomSelection($numberOfPlacesLeft); + $randomOffset = $pickRandomInt($numberOfPlacesLeft); } // $select_unvisited->orderBy('last_time'); @@ -150,7 +150,7 @@ class PlaceRepository $selectOldPlaces->orderBy('last_time'); // selection algorithm with preference (weighting) for older places - $gaussianRandomSelection = function($numberOfPlaces) { + $pickGaussianRandomInt = function($numberOfPlaces) { $stdev = 0.2; $avg = 0.0; $x = mt_rand() / mt_getrandmax(); @@ -162,7 +162,7 @@ class PlaceRepository // look for n - numberOfUnvisitedPlaces places while(count($places) < $n) { - $place = $this->getRandomForMapWithValidPano($numberOfOldPlaces, $selectOldPlaces, $exclude, $gaussianRandomSelection); + $place = $this->getRandomForMapWithValidPano($numberOfOldPlaces, $selectOldPlaces, $exclude, $pickGaussianRandomInt); if(isset($place)) { $places[] = $place; @@ -172,6 +172,5 @@ class PlaceRepository return $places; } - - + } -- 2.45.2 From 7143c7ec632e5814642ba2dd9bb21face378a118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Vigh?= Date: Thu, 6 May 2021 10:05:23 +0200 Subject: [PATCH 07/14] moved saving to UserPlayedPlace to seperate function --- src/Controller/GameFlowController.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Controller/GameFlowController.php b/src/Controller/GameFlowController.php index 0cddef7..f7ba957 100644 --- a/src/Controller/GameFlowController.php +++ b/src/Controller/GameFlowController.php @@ -152,7 +152,16 @@ class GameFlowController $session->set('state', $state); - // save the selected place for the round in UserPlayedPlace + $this->saveVisit($last); + + return new JsonContent($response, $last); + } + + // save the selected place for the round in UserPlayedPlace + private function saveVisit($last): void + { + $session = $this->request->session(); + $userId = $session->get('userId'); if(isset($userId)) { $placeId = $last['placeId']; @@ -167,8 +176,6 @@ class GameFlowController $userPlayedPlace->setLastTimeDate(new DateTime()); $this->pdm->saveToDb($userPlayedPlace); } - - return new JsonContent($response); } public function multiGuess(): IContent -- 2.45.2 From 886bd02f8859e3b7dc982ae4d5ee51996ac7f305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Vigh?= Date: Thu, 6 May 2021 10:09:28 +0200 Subject: [PATCH 08/14] removed mistakenly added parameter --- src/Controller/GameFlowController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Controller/GameFlowController.php b/src/Controller/GameFlowController.php index f7ba957..cd0eda8 100644 --- a/src/Controller/GameFlowController.php +++ b/src/Controller/GameFlowController.php @@ -154,7 +154,7 @@ class GameFlowController $this->saveVisit($last); - return new JsonContent($response, $last); + return new JsonContent($response); } // save the selected place for the round in UserPlayedPlace -- 2.45.2 From b2535ad78a4f63d2f6eb2c0a267e786ff95ec861 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Vigh?= Date: Thu, 6 May 2021 17:12:18 +0200 Subject: [PATCH 09/14] refactored function and variable names, and replaced variables in inner scope --- src/Controller/GameFlowController.php | 11 ++++++----- src/Database/Query/Select.php | 6 +++--- src/Repository/PlaceRepository.php | 8 ++++---- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/Controller/GameFlowController.php b/src/Controller/GameFlowController.php index cd0eda8..bb5bffc 100644 --- a/src/Controller/GameFlowController.php +++ b/src/Controller/GameFlowController.php @@ -48,10 +48,8 @@ class GameFlowController return new JsonContent(['error' => 'no_session_found']); } - $userId = $session->get('userId'); - if (!isset($state['currentRound']) || $state['currentRound'] == -1 || $state['currentRound'] >= static::NUMBER_OF_ROUNDS) { - $this->startNewGame($state, $mapId, $userId); + $this->startNewGame($state, $mapId); $session->set('state', $state); } @@ -161,8 +159,8 @@ class GameFlowController private function saveVisit($last): void { $session = $this->request->session(); - $userId = $session->get('userId'); + if(isset($userId)) { $placeId = $last['placeId']; $userPlayedPlace = $this->userPlayedPlaceRepository->getByUserIdAndPlaceId($userId, $placeId); @@ -252,8 +250,11 @@ class GameFlowController return ['distance' => $distance, 'score' => $score]; } - private function startNewGame(array &$state, int $mapId, $userId = null): void + private function startNewGame(array &$state, int $mapId): void { + $session = $this->request->session(); + $userId = $session->get('userId'); // in case of multiplayer it will be null and handled the same way as for anonymous players + $places = $this->placeRepository->getRandomNPlaces($mapId, static::NUMBER_OF_ROUNDS, $userId); $state['rounds'] = []; diff --git a/src/Database/Query/Select.php b/src/Database/Query/Select.php index ab8ee74..8dc3a90 100644 --- a/src/Database/Query/Select.php +++ b/src/Database/Query/Select.php @@ -223,8 +223,8 @@ class Select private function generateQuery(): array { - list($innerQuery, $innerParams) = $this->generateTable($this->table, true); - $queryString = 'SELECT ' . $this->generateColumns() . ' FROM ' . $innerQuery; + list($tableQuery, $tableParams) = $this->generateTable($this->table, true); + $queryString = 'SELECT ' . $this->generateColumns() . ' FROM ' . $tableQuery; if (count($this->joins) > 0) { list($joinQuery, $joinParams) = $this->generateJoins(); @@ -265,7 +265,7 @@ class Select $queryString = '(' . $queryString . ') AS ' . $this->tableAliases[Select::DERIVED_TABLE_KEY]; } - return [$queryString, array_merge($innerParams, $joinParams, $whereParams, $havingParams)]; + return [$queryString, array_merge($tableParams, $joinParams, $whereParams, $havingParams)]; } private function generateTable($table, bool $defineAlias = false): array diff --git a/src/Repository/PlaceRepository.php b/src/Repository/PlaceRepository.php index 7f41671..e9c1238 100644 --- a/src/Repository/PlaceRepository.php +++ b/src/Repository/PlaceRepository.php @@ -29,11 +29,11 @@ class PlaceRepository } //TODO: use Map and User instead of id - public function getRandomNPlaces(int $mapId, int $n, int $userId = null): array + public function getRandomNPlaces(int $mapId, int $n, ?int $userId): array { - if(!isset($userId)) { + if(!isset($userId)) { // anonymous single player or multiplayer game return $this->getRandomNForMapWithValidPano($mapId, $n); - } else { + } else { // authorized user $unvisitedPlaces = $this->getRandomUnvisitedNForMapWithValidPano($mapId, $n, $userId); $oldPlaces = $this->getRandomOldNForMapWithValidPano($mapId, $n - count($unvisitedPlaces), $userId); return array_merge($unvisitedPlaces, $oldPlaces); @@ -149,7 +149,7 @@ class PlaceRepository // set order by datetime, oldest first $selectOldPlaces->orderBy('last_time'); - // selection algorithm with preference (weighting) for older places + // selection algorithm with preference (weighting) for older places using Box-Muller transform $pickGaussianRandomInt = function($numberOfPlaces) { $stdev = 0.2; $avg = 0.0; -- 2.45.2 From 8b3c95bdc77c9ba250d1145dd0215dd2f1a5d25c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Vigh?= Date: Thu, 6 May 2021 20:09:05 +0200 Subject: [PATCH 10/14] handling of deleting places or user updated --- src/Cli/MaintainDatabaseCommand.php | 8 ++++++++ src/Controller/LoginController.php | 8 ++++++++ src/Controller/MapAdminController.php | 17 +++++++++++++++-- src/Controller/UserController.php | 8 ++++++++ src/Repository/UserPlayedPlaceRepository.php | 2 +- 5 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/Cli/MaintainDatabaseCommand.php b/src/Cli/MaintainDatabaseCommand.php index b622204..e893c71 100644 --- a/src/Cli/MaintainDatabaseCommand.php +++ b/src/Cli/MaintainDatabaseCommand.php @@ -8,6 +8,7 @@ use MapGuesser\PersistentData\PersistentDataManager; use MapGuesser\Repository\MultiRoomRepository; use MapGuesser\Repository\UserConfirmationRepository; use MapGuesser\Repository\UserPasswordResetterRepository; +use MapGuesser\Repository\UserPlayedPlaceRepository; use MapGuesser\Repository\UserRepository; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; @@ -25,6 +26,8 @@ class MaintainDatabaseCommand extends Command private MultiRoomRepository $multiRoomRepository; + private UserPlayedPlaceRepository $userPlayedPlaceRepository; + public function __construct() { parent::__construct(); @@ -34,6 +37,7 @@ class MaintainDatabaseCommand extends Command $this->userConfirmationRepository = new UserConfirmationRepository(); $this->userPasswordResetterRepository = new UserPasswordResetterRepository(); $this->multiRoomRepository = new MultiRoomRepository(); + $this->userPlayedPlaceRepository = new UserPlayedPlaceRepository(); } public function configure(): void @@ -81,6 +85,10 @@ class MaintainDatabaseCommand extends Command $this->pdm->deleteFromDb($userPasswordResetter); } + foreach ($this->userPlayedPlaceRepository->getAllByUser($user) as $userPlayedPlace) { + $this->pdm->deleteFromDb($userPlayedPlace); + } + $this->pdm->deleteFromDb($user); } diff --git a/src/Controller/LoginController.php b/src/Controller/LoginController.php index e54d44a..4f85983 100644 --- a/src/Controller/LoginController.php +++ b/src/Controller/LoginController.php @@ -14,6 +14,7 @@ use MapGuesser\PersistentData\Model\UserPasswordResetter; use MapGuesser\PersistentData\PersistentDataManager; use MapGuesser\Repository\UserConfirmationRepository; use MapGuesser\Repository\UserPasswordResetterRepository; +use MapGuesser\Repository\UserPlayedPlaceRepository; use MapGuesser\Repository\UserRepository; use MapGuesser\Response\HtmlContent; use MapGuesser\Response\JsonContent; @@ -32,6 +33,8 @@ class LoginController private UserPasswordResetterRepository $userPasswordResetterRepository; + private UserPlayedPlaceRepository $userPlayedPlaceRepository; + public function __construct(IRequest $request) { $this->request = $request; @@ -39,6 +42,7 @@ class LoginController $this->userRepository = new UserRepository(); $this->userConfirmationRepository = new UserConfirmationRepository(); $this->userPasswordResetterRepository = new UserPasswordResetterRepository(); + $this->userPlayedPlaceRepository = new UserPlayedPlaceRepository(); } public function getLoginForm() @@ -430,6 +434,10 @@ class LoginController $user = $this->userRepository->getById($confirmation->getUserId()); + foreach ($this->userPlayedPlaceRepository->getAllByUser($user) as $userPlayedPlace) { + $this->pdm->deleteFromDb($userPlayedPlace); + } + $this->pdm->deleteFromDb($user); \Container::$dbConnection->commit(); diff --git a/src/Controller/MapAdminController.php b/src/Controller/MapAdminController.php index add5232..12b260a 100644 --- a/src/Controller/MapAdminController.php +++ b/src/Controller/MapAdminController.php @@ -10,6 +10,7 @@ use MapGuesser\PersistentData\Model\Place; use MapGuesser\PersistentData\PersistentDataManager; use MapGuesser\Repository\MapRepository; use MapGuesser\Repository\PlaceRepository; +use MapGuesser\Repository\UserPlayedPlaceRepository; use MapGuesser\Response\HtmlContent; use MapGuesser\Response\JsonContent; use MapGuesser\Util\Geo\Bounds; @@ -27,12 +28,15 @@ class MapAdminController implements ISecured private PlaceRepository $placeRepository; + private UserPlayedPlaceRepository $userPlayedPlaceRepository; + public function __construct(IRequest $request) { $this->request = $request; $this->pdm = new PersistentDataManager(); $this->mapRepository = new MapRepository(); $this->placeRepository = new PlaceRepository(); + $this->userPlayedPlaceRepository = new UserPlayedPlaceRepository(); } public function authorize(): bool @@ -138,7 +142,7 @@ class MapAdminController implements ISecured $place = $this->placeRepository->getById((int) $placeRaw['id']); - $this->pdm->deleteFromDb($place); + $this->deletePlace($place); } } @@ -178,10 +182,19 @@ class MapAdminController implements ISecured return new JsonContent(['success' => true]); } + private function deletePlace(Place $place): void + { + foreach ($this->userPlayedPlaceRepository->getAllByPlace($place) as $userPlayedPlace) { + $this->pdm->deleteFromDb($userPlayedPlace); + } + + $this->pdm->deleteFromDb($place); + } + private function deletePlaces(Map $map): void { foreach ($this->placeRepository->getAllForMap($map) as $place) { - $this->pdm->deleteFromDb($place); + $this->deletePlace($place); } } diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index 805b752..3b0df05 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -11,6 +11,7 @@ use MapGuesser\PersistentData\PersistentDataManager; use MapGuesser\PersistentData\Model\User; use MapGuesser\Repository\UserConfirmationRepository; use MapGuesser\Repository\UserPasswordResetterRepository; +use MapGuesser\Repository\UserPlayedPlaceRepository; use MapGuesser\Response\HtmlContent; use MapGuesser\Response\JsonContent; use MapGuesser\Response\Redirect; @@ -26,12 +27,15 @@ class UserController implements ISecured private UserPasswordResetterRepository $userPasswordResetterRepository; + private UserPlayedPlaceRepository $userPlayedPlaceRepository; + public function __construct(IRequest $request) { $this->request = $request; $this->pdm = new PersistentDataManager(); $this->userConfirmationRepository = new UserConfirmationRepository(); $this->userPasswordResetterRepository = new UserPasswordResetterRepository(); + $this->userPlayedPlaceRepository = new UserPlayedPlaceRepository(); } public function authorize(): bool @@ -201,6 +205,10 @@ class UserController implements ISecured $this->pdm->deleteFromDb($userPasswordResetter); } + foreach ($this->userPlayedPlaceRepository->getAllByUser($user) as $userPlayedPlace) { + $this->pdm->deleteFromDb($userPlayedPlace); + } + $this->pdm->deleteFromDb($user); \Container::$dbConnection->commit(); diff --git a/src/Repository/UserPlayedPlaceRepository.php b/src/Repository/UserPlayedPlaceRepository.php index 0e497b7..5daaaaa 100644 --- a/src/Repository/UserPlayedPlaceRepository.php +++ b/src/Repository/UserPlayedPlaceRepository.php @@ -25,7 +25,7 @@ class UserPlayedPlaceRepository yield from $this->pdm->selectMultipleFromDb($select, UserPlayedPlace::class); } - public function getByPlace(Place $place): Generator + public function getAllByPlace(Place $place): Generator { $select = new Select(\Container::$dbConnection); $select->where('place_id', '=', $place->getId()); -- 2.45.2 From 8d8074977b81705ae771105da48d7ecc3e046b4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Vigh?= Date: Thu, 6 May 2021 20:25:48 +0200 Subject: [PATCH 11/14] fixing comments --- src/Controller/GameFlowController.php | 2 +- src/Repository/PlaceRepository.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Controller/GameFlowController.php b/src/Controller/GameFlowController.php index bb5bffc..eba2a48 100644 --- a/src/Controller/GameFlowController.php +++ b/src/Controller/GameFlowController.php @@ -253,7 +253,7 @@ class GameFlowController private function startNewGame(array &$state, int $mapId): void { $session = $this->request->session(); - $userId = $session->get('userId'); // in case of multiplayer it will be null and handled the same way as for anonymous players + $userId = $session->get('userId'); $places = $this->placeRepository->getRandomNPlaces($mapId, static::NUMBER_OF_ROUNDS, $userId); diff --git a/src/Repository/PlaceRepository.php b/src/Repository/PlaceRepository.php index e9c1238..c621ff1 100644 --- a/src/Repository/PlaceRepository.php +++ b/src/Repository/PlaceRepository.php @@ -31,9 +31,9 @@ class PlaceRepository //TODO: use Map and User instead of id public function getRandomNPlaces(int $mapId, int $n, ?int $userId): array { - if(!isset($userId)) { // anonymous single player or multiplayer game + if(!isset($userId)) { // anonymous single player return $this->getRandomNForMapWithValidPano($mapId, $n); - } else { // authorized user + } else { // authorized user or multiplayer game with selection based on what the host played before $unvisitedPlaces = $this->getRandomUnvisitedNForMapWithValidPano($mapId, $n, $userId); $oldPlaces = $this->getRandomOldNForMapWithValidPano($mapId, $n - count($unvisitedPlaces), $userId); return array_merge($unvisitedPlaces, $oldPlaces); -- 2.45.2 From c626e36bbb9061b62b4c6afd48b8d2666d2e19ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Vigh?= Date: Thu, 6 May 2021 20:39:00 +0200 Subject: [PATCH 12/14] specified explicit type in function parameter --- src/Repository/PlaceRepository.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Repository/PlaceRepository.php b/src/Repository/PlaceRepository.php index c621ff1..f13f9a4 100644 --- a/src/Repository/PlaceRepository.php +++ b/src/Repository/PlaceRepository.php @@ -60,7 +60,7 @@ class PlaceRepository return $places; } - private function getRandomForMapWithValidPano($numberOfPlaces, $select, array &$exclude, $pickRandomInt = null): ?Place + private function getRandomForMapWithValidPano($numberOfPlaces, $select, array &$exclude, ?callable $pickRandomInt = null): ?Place { do { $numberOfPlacesLeft = $numberOfPlaces - count($exclude); @@ -79,7 +79,7 @@ class PlaceRepository return $place; } - private function selectRandomFromDbForMap($numberOfPlacesLeft, $select, array $exclude, $pickRandomInt): ?Place + private function selectRandomFromDbForMap($numberOfPlacesLeft, $select, array $exclude, ?callable $pickRandomInt): ?Place { if($numberOfPlacesLeft <= 0) return null; -- 2.45.2 From 899817a8537627dc5d3430ccd6a81af43e10f58c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Vigh?= Date: Thu, 6 May 2021 20:41:23 +0200 Subject: [PATCH 13/14] removed unnecessary comment --- src/Repository/PlaceRepository.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Repository/PlaceRepository.php b/src/Repository/PlaceRepository.php index f13f9a4..aa6fac8 100644 --- a/src/Repository/PlaceRepository.php +++ b/src/Repository/PlaceRepository.php @@ -90,7 +90,6 @@ class PlaceRepository $randomOffset = $pickRandomInt($numberOfPlacesLeft); } - // $select_unvisited->orderBy('last_time'); $select->where('id', 'NOT IN', $exclude); $select->limit(1, $randomOffset); -- 2.45.2 From 3f8311d708238200a5c2cfc8daea545df15b8149 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Vigh?= Date: Sat, 8 May 2021 23:37:36 +0200 Subject: [PATCH 14/14] implemented review findings --- src/Database/Query/Select.php | 9 ++++---- src/Repository/PlaceRepository.php | 33 +++++++++++++++++------------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/Database/Query/Select.php b/src/Database/Query/Select.php index 8dc3a90..d62bd84 100644 --- a/src/Database/Query/Select.php +++ b/src/Database/Query/Select.php @@ -328,14 +328,13 @@ class Select private function generateJoins(): array { - $joins = $this->joins; - + $joinQueries = []; $params = []; - foreach($joins as $value) { - list($joinQueryFragment, $paramsFragment) = $this->generateTable($value[1], true); - array_push($joinQueries, $value[0] . ' JOIN ' . $joinQueryFragment . ' ON ' . $this->generateColumn($value[2]) . ' ' . $value[3] . ' ' . $this->generateColumn($value[4])); + foreach($this->joins as $join) { + list($joinQueryFragment, $paramsFragment) = $this->generateTable($join[1], true); + $joinQueries[] = $join[0] . ' JOIN ' . $joinQueryFragment . ' ON ' . $this->generateColumn($join[2]) . ' ' . $join[3] . ' ' . $this->generateColumn($join[4]); $params = array_merge($params, $paramsFragment); } diff --git a/src/Repository/PlaceRepository.php b/src/Repository/PlaceRepository.php index aa6fac8..4c36edc 100644 --- a/src/Repository/PlaceRepository.php +++ b/src/Repository/PlaceRepository.php @@ -31,25 +31,30 @@ class PlaceRepository //TODO: use Map and User instead of id public function getRandomNPlaces(int $mapId, int $n, ?int $userId): array { - if(!isset($userId)) { // anonymous single player + if (!isset($userId)) { // anonymous single player return $this->getRandomNForMapWithValidPano($mapId, $n); } else { // authorized user or multiplayer game with selection based on what the host played before $unvisitedPlaces = $this->getRandomUnvisitedNForMapWithValidPano($mapId, $n, $userId); + if (count($unvisitedPlaces) == $n) { + return $unvisitedPlaces; + } + $oldPlaces = $this->getRandomOldNForMapWithValidPano($mapId, $n - count($unvisitedPlaces), $userId); + return array_merge($unvisitedPlaces, $oldPlaces); } } //TODO: use Map instead of id - public function getRandomNForMapWithValidPano(int $mapId, int $n, array $exclude = []): array + private function getRandomNForMapWithValidPano(int $mapId, int $n): array { $places = []; $select = new Select(\Container::$dbConnection, 'places'); - $select->where('id', 'NOT IN', $exclude); $select->where('map_id', '=', $mapId); $numberOfPlaces = $select->count(); + $exclude = []; for ($i = 1; $i <= $n; ++$i) { $place = $this->getRandomForMapWithValidPano($numberOfPlaces, $select, $exclude); @@ -60,31 +65,31 @@ class PlaceRepository return $places; } - private function getRandomForMapWithValidPano($numberOfPlaces, $select, array &$exclude, ?callable $pickRandomInt = null): ?Place + private function getRandomForMapWithValidPano(int $numberOfPlaces, Select $select, array &$exclude, ?callable $pickRandomInt = null): ?Place { do { $numberOfPlacesLeft = $numberOfPlaces - count($exclude); $place = $this->selectRandomFromDbForMap($numberOfPlacesLeft, $select, $exclude, $pickRandomInt); - if($place === null) { + if ($place === null) { // there is no more never visited place left return null; } $panoId = $place->getFreshPanoId(); - if($panoId === null) { + if ($panoId === null) { $exclude[] = $place->getId(); } - } while($panoId === null); + } while ($panoId === null); return $place; } - private function selectRandomFromDbForMap($numberOfPlacesLeft, $select, array $exclude, ?callable $pickRandomInt): ?Place + private function selectRandomFromDbForMap(int $numberOfPlacesLeft, Select $select, array $exclude, ?callable $pickRandomInt): ?Place { - if($numberOfPlacesLeft <= 0) + if ($numberOfPlacesLeft <= 0) return null; - if(!isset($pickRandomInt)) { + if (!isset($pickRandomInt)) { $randomOffset = random_int(0, $numberOfPlacesLeft - 1); } else { $randomOffset = $pickRandomInt($numberOfPlacesLeft); @@ -118,11 +123,11 @@ class PlaceRepository // look for as many new places as possible but maximum $n do { $place = $this->getRandomForMapWithValidPano($numberOfUnvisitedPlaces, $selectUnvisited, $exclude); - if(isset($place)) { + if (isset($place)) { $places[] = $place; $exclude[] = $place->getId(); } - } while(count($places) < $n && isset($place)); + } while (count($places) < $n && isset($place)); return $places; } @@ -159,10 +164,10 @@ class PlaceRepository }; // look for n - numberOfUnvisitedPlaces places - while(count($places) < $n) + while (count($places) < $n) { $place = $this->getRandomForMapWithValidPano($numberOfOldPlaces, $selectOldPlaces, $exclude, $pickGaussianRandomInt); - if(isset($place)) + if (isset($place)) { $places[] = $place; $exclude[] = $place->getId(); -- 2.45.2