feature/avoid-repeating-places-in-game #38
| @ -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; | ||||
| @ -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); | ||||
|         } | ||||
| 
 | ||||
|  | ||||
| @ -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 | ||||
| @ -145,9 +150,32 @@ class GameFlowController | ||||
| 
 | ||||
|         $session->set('state', $state); | ||||
| 
 | ||||
|         $this->saveVisit($last); | ||||
| 
 | ||||
|         return new JsonContent($response); | ||||
|     } | ||||
| 
 | ||||
|     // 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']; | ||||
|             $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); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function multiGuess(): IContent | ||||
|     { | ||||
|         $roomId = $this->request->query('roomId'); | ||||
| @ -224,7 +252,10 @@ class GameFlowController | ||||
| 
 | ||||
|     private function startNewGame(array &$state, int $mapId): void | ||||
|     { | ||||
|         $places = $this->placeRepository->getRandomNForMapWithValidPano($mapId, static::NUMBER_OF_ROUNDS); | ||||
|         $session = $this->request->session(); | ||||
|         $userId = $session->get('userId'); | ||||
| 
 | ||||
|         $places = $this->placeRepository->getRandomNPlaces($mapId, static::NUMBER_OF_ROUNDS, $userId); | ||||
| 
 | ||||
|         $state['rounds'] = []; | ||||
|         $state['currentRound'] = 0; | ||||
|  | ||||
| @ -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(); | ||||
|  | ||||
| @ -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); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -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(); | ||||
|  | ||||
| @ -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($tableQuery, $tableParams) = $this->generateTable($this->table, true); | ||||
|         $queryString = 'SELECT ' . $this->generateColumns() . ' FROM ' . $tableQuery; | ||||
| 
 | ||||
|         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($tableParams, $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,19 @@ class Select | ||||
|         return implode(',', $columns); | ||||
|     } | ||||
| 
 | ||||
|     private function generateJoins(): string | ||||
|     private function generateJoins(): array | ||||
|     { | ||||
|         $joins = $this->joins; | ||||
| 
 | ||||
|         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]); | ||||
|         }); | ||||
|         $joinQueries = []; | ||||
|         $params = []; | ||||
| 
 | ||||
|         return implode(' ', $joins); | ||||
|         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); | ||||
| 
				
					
						bence
						commented  Better to use  Better to use `$array[] =` to add a new element to an array because it is more efficient according the the [PHP docs](https://www.php.net/manual/en/function.array-push.php):
```php
$joinQueries[] = $value[0] . ' JOIN ' . $joinQueryFragment . ' ON ' . $this->generateColumn($value[2]) . ' ' . $value[3] . ' ' . $this->generateColumn($value[4]));
``` | ||||
|         } | ||||
| 
 | ||||
|         return [implode(' ', $joinQueries), $params]; | ||||
|     } | ||||
| 
 | ||||
|     private function generateConditions(int $type): array | ||||
|  | ||||
							
								
								
									
										99
									
								
								src/PersistentData/Model/UserPlayedPlace.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								src/PersistentData/Model/UserPlayedPlace.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,99 @@ | ||||
| <?php namespace MapGuesser\PersistentData\Model; | ||||
| 
 | ||||
| use DateTime; | ||||
| 
 | ||||
| class UserPlayedPlace extends Model | ||||
| { | ||||
|     protected static string $table = 'user_played_place'; | ||||
| 
 | ||||
|     protected static array $fields = ['user_id', 'place_id', 'last_time', 'occurrences']; | ||||
| 
 | ||||
|     protected static array $relations = ['user' => 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; | ||||
|     } | ||||
| } | ||||
| @ -28,13 +28,35 @@ 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): array | ||||
|     { | ||||
|         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) { | ||||
| 
				
					
						bence
						commented  I would check if  I would check if `count($unvisitedPlaces) == $n` before calling this function so we could save a function call and a DB query in a lot of cases.
```php
if (count($unvisitedPlaces) == $n) {
    return $unvisitedPlaces;
}
``` | ||||
|                 return $unvisitedPlaces; | ||||
|             } | ||||
| 
 | ||||
|             $oldPlaces = $this->getRandomOldNForMapWithValidPano($mapId, $n - count($unvisitedPlaces), $userId); | ||||
|              | ||||
|             return array_merge($unvisitedPlaces, $oldPlaces); | ||||
| 
				
					
						bence
						commented  This can be  This can be `private` now. And I don't remember why it has a parameter `$exclude` but I would remove that for a better understaning and create a local variable instead. | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     //TODO: use Map instead of id
 | ||||
|     public function getRandomNForMapWithValidPano(int $mapId, int $n, array $exclude = []): array | ||||
|     private function getRandomNForMapWithValidPano(int $mapId, int $n): array | ||||
| 
				
					
						bence
						commented  This can be removed too. This can be removed too. | ||||
|     { | ||||
|         $places = []; | ||||
| 
 | ||||
|         $select = new Select(\Container::$dbConnection, 'places'); | ||||
|         $select->where('map_id', '=', $mapId); | ||||
|         $numberOfPlaces = $select->count(); | ||||
| 
 | ||||
|         $exclude = []; | ||||
|         for ($i = 1; $i <= $n; ++$i) { | ||||
|             $place = $this->getRandomForMapWithValidPano($mapId, $exclude); | ||||
|             $place = $this->getRandomForMapWithValidPano($numberOfPlaces, $select, $exclude); | ||||
| 
 | ||||
|             $places[] = $place; | ||||
|             $exclude[] = $place->getId(); | ||||
| @ -43,11 +65,15 @@ class PlaceRepository | ||||
|         return $places; | ||||
|     } | ||||
| 
 | ||||
|     //TODO: use Map instead of id
 | ||||
|     public function getRandomForMapWithValidPano(int $mapId, array $exclude = []): Place | ||||
|     private function getRandomForMapWithValidPano(int $numberOfPlaces, Select $select, array &$exclude, ?callable $pickRandomInt = null): ?Place | ||||
|     { | ||||
|         do { | ||||
|             $place = $this->selectRandomFromDbForMap($mapId, $exclude); | ||||
|             $numberOfPlacesLeft = $numberOfPlaces - count($exclude); | ||||
|             $place = $this->selectRandomFromDbForMap($numberOfPlacesLeft, $select, $exclude, $pickRandomInt); | ||||
|             if ($place === null) { | ||||
|                 // there is no more never visited place left
 | ||||
| 
				
					
						bence
						commented  I wouldn't reformat this. There is no official coding guideline but I usually put a space after if, while, for, etc. I wouldn't reformat this. There is no official coding guideline but I usually put a space after if, while, for, etc. | ||||
|                 return null; | ||||
|             } | ||||
| 
 | ||||
|             $panoId = $place->getFreshPanoId(); | ||||
|             if ($panoId === null) { | ||||
| @ -58,25 +84,97 @@ class PlaceRepository | ||||
|         return $place; | ||||
|     } | ||||
| 
 | ||||
|     private function selectRandomFromDbForMap(int $mapId, array $exclude): Place | ||||
|     private function selectRandomFromDbForMap(int $numberOfPlacesLeft, Select $select, array $exclude, ?callable $pickRandomInt): ?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($pickRandomInt)) { | ||||
|             $randomOffset = random_int(0, $numberOfPlacesLeft - 1); | ||||
|         } else { | ||||
|             $randomOffset = $pickRandomInt($numberOfPlacesLeft); | ||||
|         } | ||||
| 
 | ||||
|         $randomOffset = random_int(0, $numberOfPlaces - 1); | ||||
| 
 | ||||
|         $select->orderBy('id'); | ||||
|         $select->where('id', 'NOT IN', $exclude); | ||||
|         $select->limit(1, $randomOffset); | ||||
| 
 | ||||
|         return $this->pdm->selectFromDb($select, Place::class); | ||||
|     } | ||||
| 
 | ||||
|     // Never visited places
 | ||||
|     private function getRandomUnvisitedNForMapWithValidPano(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 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->getRandomForMapWithValidPano($numberOfUnvisitedPlaces, $selectUnvisited, $exclude); | ||||
|             if (isset($place)) { | ||||
|                 $places[] = $place; | ||||
|                 $exclude[] = $place->getId(); | ||||
|             } | ||||
|         } while (count($places) < $n && isset($place)); | ||||
| 
 | ||||
|         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->innerJoin($selectPlacesByCurrentUser, ['places', 'id'], '=', ['places_by_current_user', 'place_id']); | ||||
|         $selectOldPlaces->where('map_id', '=', $mapId); | ||||
|         $numberOfOldPlaces = $selectOldPlaces->count(); | ||||
| 
 | ||||
|         // set order by datetime, oldest first
 | ||||
|         $selectOldPlaces->orderBy('last_time'); | ||||
| 
 | ||||
|         // selection algorithm with preference (weighting) for older places using Box-Muller transform
 | ||||
|         $pickGaussianRandomInt = 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->getRandomForMapWithValidPano($numberOfOldPlaces, $selectOldPlaces, $exclude, $pickGaussianRandomInt); | ||||
|             if (isset($place)) | ||||
|             { | ||||
|                 $places[] = $place; | ||||
|                 $exclude[] = $place->getId(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $places; | ||||
|     } | ||||
|    | ||||
| } | ||||
|  | ||||
							
								
								
									
										52
									
								
								src/Repository/UserPlayedPlaceRepository.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/Repository/UserPlayedPlaceRepository.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,52 @@ | ||||
| <?php namespace MapGuesser\Repository; | ||||
| 
 | ||||
| use DateTime; | ||||
| use Generator; | ||||
| use MapGuesser\Database\Query\Select; | ||||
| use MapGuesser\PersistentData\Model\User; | ||||
| use MapGuesser\PersistentData\Model\Place; | ||||
| use MapGuesser\PersistentData\Model\UserPlayedPlace; | ||||
| use MapGuesser\PersistentData\PersistentDataManager; | ||||
| 
 | ||||
| class UserPlayedPlaceRepository | ||||
| { | ||||
|     private PersistentDataManager $pdm; | ||||
| 
 | ||||
|     public function __construct() | ||||
|     { | ||||
|         $this->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 getAllByPlace(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); | ||||
|     } | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	
If it is refactored anyway I would directly use
$this->joinsand local variable$joins(line 331) becomes unnecessary.And calling the loop variable
$joininstead of$valuewould be nicer.