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\MultiRoomRepository; | ||||||
| use MapGuesser\Repository\UserConfirmationRepository; | use MapGuesser\Repository\UserConfirmationRepository; | ||||||
| use MapGuesser\Repository\UserPasswordResetterRepository; | use MapGuesser\Repository\UserPasswordResetterRepository; | ||||||
|  | use MapGuesser\Repository\UserPlayedPlaceRepository; | ||||||
| use MapGuesser\Repository\UserRepository; | use MapGuesser\Repository\UserRepository; | ||||||
| use Symfony\Component\Console\Command\Command; | use Symfony\Component\Console\Command\Command; | ||||||
| use Symfony\Component\Console\Input\InputInterface; | use Symfony\Component\Console\Input\InputInterface; | ||||||
| @ -25,6 +26,8 @@ class MaintainDatabaseCommand extends Command | |||||||
| 
 | 
 | ||||||
|     private MultiRoomRepository $multiRoomRepository; |     private MultiRoomRepository $multiRoomRepository; | ||||||
| 
 | 
 | ||||||
|  |     private UserPlayedPlaceRepository $userPlayedPlaceRepository; | ||||||
|  | 
 | ||||||
|     public function __construct() |     public function __construct() | ||||||
|     { |     { | ||||||
|         parent::__construct(); |         parent::__construct(); | ||||||
| @ -34,6 +37,7 @@ class MaintainDatabaseCommand extends Command | |||||||
|         $this->userConfirmationRepository = new UserConfirmationRepository(); |         $this->userConfirmationRepository = new UserConfirmationRepository(); | ||||||
|         $this->userPasswordResetterRepository = new UserPasswordResetterRepository(); |         $this->userPasswordResetterRepository = new UserPasswordResetterRepository(); | ||||||
|         $this->multiRoomRepository = new MultiRoomRepository(); |         $this->multiRoomRepository = new MultiRoomRepository(); | ||||||
|  |         $this->userPlayedPlaceRepository = new UserPlayedPlaceRepository(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function configure(): void |     public function configure(): void | ||||||
| @ -81,6 +85,10 @@ class MaintainDatabaseCommand extends Command | |||||||
|                 $this->pdm->deleteFromDb($userPasswordResetter); |                 $this->pdm->deleteFromDb($userPasswordResetter); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  |             foreach ($this->userPlayedPlaceRepository->getAllByUser($user) as $userPlayedPlace) { | ||||||
|  |                 $this->pdm->deleteFromDb($userPlayedPlace); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|             $this->pdm->deleteFromDb($user); |             $this->pdm->deleteFromDb($user); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -7,8 +7,10 @@ use MapGuesser\Response\JsonContent; | |||||||
| use MapGuesser\Interfaces\Response\IContent; | use MapGuesser\Interfaces\Response\IContent; | ||||||
| use MapGuesser\Multi\MultiConnector; | use MapGuesser\Multi\MultiConnector; | ||||||
| use MapGuesser\PersistentData\PersistentDataManager; | use MapGuesser\PersistentData\PersistentDataManager; | ||||||
|  | use MapGuesser\PersistentData\Model\UserPlayedPlace; | ||||||
| use MapGuesser\Repository\MultiRoomRepository; | use MapGuesser\Repository\MultiRoomRepository; | ||||||
| use MapGuesser\Repository\PlaceRepository; | use MapGuesser\Repository\PlaceRepository; | ||||||
|  | use MapGuesser\Repository\UserPlayedPlaceRepository; | ||||||
| 
 | 
 | ||||||
| class GameFlowController | class GameFlowController | ||||||
| { | { | ||||||
| @ -25,6 +27,8 @@ class GameFlowController | |||||||
| 
 | 
 | ||||||
|     private PlaceRepository $placeRepository; |     private PlaceRepository $placeRepository; | ||||||
| 
 | 
 | ||||||
|  |     private UserPlayedPlaceRepository $userPlayedPlaceRepository; | ||||||
|  | 
 | ||||||
|     public function __construct(IRequest $request) |     public function __construct(IRequest $request) | ||||||
|     { |     { | ||||||
|         $this->request = $request; |         $this->request = $request; | ||||||
| @ -32,6 +36,7 @@ class GameFlowController | |||||||
|         $this->multiConnector = new MultiConnector(); |         $this->multiConnector = new MultiConnector(); | ||||||
|         $this->multiRoomRepository = new MultiRoomRepository(); |         $this->multiRoomRepository = new MultiRoomRepository(); | ||||||
|         $this->placeRepository = new PlaceRepository(); |         $this->placeRepository = new PlaceRepository(); | ||||||
|  |         $this->userPlayedPlaceRepository = new UserPlayedPlaceRepository(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function initialData(): IContent |     public function initialData(): IContent | ||||||
| @ -145,9 +150,32 @@ class GameFlowController | |||||||
| 
 | 
 | ||||||
|         $session->set('state', $state); |         $session->set('state', $state); | ||||||
| 
 | 
 | ||||||
|  |         $this->saveVisit($last); | ||||||
|  | 
 | ||||||
|         return new JsonContent($response); |         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 |     public function multiGuess(): IContent | ||||||
|     { |     { | ||||||
|         $roomId = $this->request->query('roomId'); |         $roomId = $this->request->query('roomId'); | ||||||
| @ -224,7 +252,10 @@ class GameFlowController | |||||||
| 
 | 
 | ||||||
|     private function startNewGame(array &$state, int $mapId): void |     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['rounds'] = []; | ||||||
|         $state['currentRound'] = 0; |         $state['currentRound'] = 0; | ||||||
|  | |||||||
| @ -14,6 +14,7 @@ use MapGuesser\PersistentData\Model\UserPasswordResetter; | |||||||
| use MapGuesser\PersistentData\PersistentDataManager; | use MapGuesser\PersistentData\PersistentDataManager; | ||||||
| use MapGuesser\Repository\UserConfirmationRepository; | use MapGuesser\Repository\UserConfirmationRepository; | ||||||
| use MapGuesser\Repository\UserPasswordResetterRepository; | use MapGuesser\Repository\UserPasswordResetterRepository; | ||||||
|  | use MapGuesser\Repository\UserPlayedPlaceRepository; | ||||||
| use MapGuesser\Repository\UserRepository; | use MapGuesser\Repository\UserRepository; | ||||||
| use MapGuesser\Response\HtmlContent; | use MapGuesser\Response\HtmlContent; | ||||||
| use MapGuesser\Response\JsonContent; | use MapGuesser\Response\JsonContent; | ||||||
| @ -32,6 +33,8 @@ class LoginController | |||||||
| 
 | 
 | ||||||
|     private UserPasswordResetterRepository $userPasswordResetterRepository; |     private UserPasswordResetterRepository $userPasswordResetterRepository; | ||||||
| 
 | 
 | ||||||
|  |     private UserPlayedPlaceRepository $userPlayedPlaceRepository; | ||||||
|  | 
 | ||||||
|     public function __construct(IRequest $request) |     public function __construct(IRequest $request) | ||||||
|     { |     { | ||||||
|         $this->request = $request; |         $this->request = $request; | ||||||
| @ -39,6 +42,7 @@ class LoginController | |||||||
|         $this->userRepository = new UserRepository(); |         $this->userRepository = new UserRepository(); | ||||||
|         $this->userConfirmationRepository = new UserConfirmationRepository(); |         $this->userConfirmationRepository = new UserConfirmationRepository(); | ||||||
|         $this->userPasswordResetterRepository = new UserPasswordResetterRepository(); |         $this->userPasswordResetterRepository = new UserPasswordResetterRepository(); | ||||||
|  |         $this->userPlayedPlaceRepository = new UserPlayedPlaceRepository(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function getLoginForm() |     public function getLoginForm() | ||||||
| @ -430,6 +434,10 @@ class LoginController | |||||||
| 
 | 
 | ||||||
|         $user = $this->userRepository->getById($confirmation->getUserId()); |         $user = $this->userRepository->getById($confirmation->getUserId()); | ||||||
| 
 | 
 | ||||||
|  |         foreach ($this->userPlayedPlaceRepository->getAllByUser($user) as $userPlayedPlace) { | ||||||
|  |             $this->pdm->deleteFromDb($userPlayedPlace); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         $this->pdm->deleteFromDb($user); |         $this->pdm->deleteFromDb($user); | ||||||
| 
 | 
 | ||||||
|         \Container::$dbConnection->commit(); |         \Container::$dbConnection->commit(); | ||||||
|  | |||||||
| @ -10,6 +10,7 @@ use MapGuesser\PersistentData\Model\Place; | |||||||
| use MapGuesser\PersistentData\PersistentDataManager; | use MapGuesser\PersistentData\PersistentDataManager; | ||||||
| use MapGuesser\Repository\MapRepository; | use MapGuesser\Repository\MapRepository; | ||||||
| use MapGuesser\Repository\PlaceRepository; | use MapGuesser\Repository\PlaceRepository; | ||||||
|  | use MapGuesser\Repository\UserPlayedPlaceRepository; | ||||||
| use MapGuesser\Response\HtmlContent; | use MapGuesser\Response\HtmlContent; | ||||||
| use MapGuesser\Response\JsonContent; | use MapGuesser\Response\JsonContent; | ||||||
| use MapGuesser\Util\Geo\Bounds; | use MapGuesser\Util\Geo\Bounds; | ||||||
| @ -27,12 +28,15 @@ class MapAdminController implements ISecured | |||||||
| 
 | 
 | ||||||
|     private PlaceRepository $placeRepository; |     private PlaceRepository $placeRepository; | ||||||
| 
 | 
 | ||||||
|  |     private UserPlayedPlaceRepository $userPlayedPlaceRepository; | ||||||
|  | 
 | ||||||
|     public function __construct(IRequest $request) |     public function __construct(IRequest $request) | ||||||
|     { |     { | ||||||
|         $this->request = $request; |         $this->request = $request; | ||||||
|         $this->pdm = new PersistentDataManager(); |         $this->pdm = new PersistentDataManager(); | ||||||
|         $this->mapRepository = new MapRepository(); |         $this->mapRepository = new MapRepository(); | ||||||
|         $this->placeRepository = new PlaceRepository(); |         $this->placeRepository = new PlaceRepository(); | ||||||
|  |         $this->userPlayedPlaceRepository = new UserPlayedPlaceRepository(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function authorize(): bool |     public function authorize(): bool | ||||||
| @ -138,7 +142,7 @@ class MapAdminController implements ISecured | |||||||
| 
 | 
 | ||||||
|                 $place = $this->placeRepository->getById((int) $placeRaw['id']); |                 $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]); |         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 |     private function deletePlaces(Map $map): void | ||||||
|     { |     { | ||||||
|         foreach ($this->placeRepository->getAllForMap($map) as $place) { |         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\PersistentData\Model\User; | ||||||
| use MapGuesser\Repository\UserConfirmationRepository; | use MapGuesser\Repository\UserConfirmationRepository; | ||||||
| use MapGuesser\Repository\UserPasswordResetterRepository; | use MapGuesser\Repository\UserPasswordResetterRepository; | ||||||
|  | use MapGuesser\Repository\UserPlayedPlaceRepository; | ||||||
| use MapGuesser\Response\HtmlContent; | use MapGuesser\Response\HtmlContent; | ||||||
| use MapGuesser\Response\JsonContent; | use MapGuesser\Response\JsonContent; | ||||||
| use MapGuesser\Response\Redirect; | use MapGuesser\Response\Redirect; | ||||||
| @ -26,12 +27,15 @@ class UserController implements ISecured | |||||||
| 
 | 
 | ||||||
|     private UserPasswordResetterRepository $userPasswordResetterRepository; |     private UserPasswordResetterRepository $userPasswordResetterRepository; | ||||||
| 
 | 
 | ||||||
|  |     private UserPlayedPlaceRepository $userPlayedPlaceRepository; | ||||||
|  | 
 | ||||||
|     public function __construct(IRequest $request) |     public function __construct(IRequest $request) | ||||||
|     { |     { | ||||||
|         $this->request = $request; |         $this->request = $request; | ||||||
|         $this->pdm = new PersistentDataManager(); |         $this->pdm = new PersistentDataManager(); | ||||||
|         $this->userConfirmationRepository = new UserConfirmationRepository(); |         $this->userConfirmationRepository = new UserConfirmationRepository(); | ||||||
|         $this->userPasswordResetterRepository = new UserPasswordResetterRepository(); |         $this->userPasswordResetterRepository = new UserPasswordResetterRepository(); | ||||||
|  |         $this->userPlayedPlaceRepository = new UserPlayedPlaceRepository(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function authorize(): bool |     public function authorize(): bool | ||||||
| @ -201,6 +205,10 @@ class UserController implements ISecured | |||||||
|             $this->pdm->deleteFromDb($userPasswordResetter); |             $this->pdm->deleteFromDb($userPasswordResetter); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         foreach ($this->userPlayedPlaceRepository->getAllByUser($user) as $userPlayedPlace) { | ||||||
|  |             $this->pdm->deleteFromDb($userPlayedPlace); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         $this->pdm->deleteFromDb($user); |         $this->pdm->deleteFromDb($user); | ||||||
| 
 | 
 | ||||||
|         \Container::$dbConnection->commit(); |         \Container::$dbConnection->commit(); | ||||||
|  | |||||||
| @ -12,6 +12,8 @@ class Select | |||||||
| 
 | 
 | ||||||
|     const CONDITION_HAVING = 1; |     const CONDITION_HAVING = 1; | ||||||
| 
 | 
 | ||||||
|  |     const DERIVED_TABLE_KEY = 'DERIVED'; | ||||||
|  | 
 | ||||||
|     private IConnection $connection; |     private IConnection $connection; | ||||||
| 
 | 
 | ||||||
|     private string $table; |     private string $table; | ||||||
| @ -55,6 +57,11 @@ class Select | |||||||
|         return $this; |         return $this; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function setDerivedTableAlias(string $tableAlias): Select | ||||||
|  |     { | ||||||
|  |         return $this->setTableAliases([Select::DERIVED_TABLE_KEY => $tableAlias]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function from(string $table): Select |     public function from(string $table): Select | ||||||
|     { |     { | ||||||
|         $this->table = $table; |         $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 |     private function addJoin(string $type, $table, $column1, string $relation, $column2): void | ||||||
|     { |     { | ||||||
|         $this->joins[] = [$type, $table, $column1, $relation, $column2]; |         $this->joins[] = [$type, $table, $column1, $relation, $column2]; | ||||||
| @ -211,10 +223,14 @@ class Select | |||||||
| 
 | 
 | ||||||
|     private function generateQuery(): array |     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) { |         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) { |         if (count($this->conditions[self::CONDITION_WHERE]) > 0) { | ||||||
| @ -245,20 +261,32 @@ class Select | |||||||
|             $queryString .= ' LIMIT ' . $this->limit[1] . ', ' . $this->limit[0]; |             $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]; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|     private function generateTable($table, bool $defineAlias = false): string |         return [$queryString, array_merge($tableParams, $joinParams, $whereParams, $havingParams)]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private function generateTable($table, bool $defineAlias = false): array | ||||||
|     { |     { | ||||||
|  |         $params = []; | ||||||
|  | 
 | ||||||
|         if ($table instanceof RawExpression) { |         if ($table instanceof RawExpression) { | ||||||
|             return (string) $table; |             return [(string) $table, $params]; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if($table instanceof Select) | ||||||
|  |         { | ||||||
|  |             return $table->generateQuery(); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (isset($this->tableAliases[$table])) { |         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 |     private function generateColumn($column): string | ||||||
| @ -271,7 +299,8 @@ class Select | |||||||
|             $out = ''; |             $out = ''; | ||||||
| 
 | 
 | ||||||
|             if ($column[0]) { |             if ($column[0]) { | ||||||
|                 $out .= $this->generateTable($column[0]) . '.'; |                 list($tableName, $params) = $this->generateTable($column[0]); | ||||||
|  |                 $out .= $tableName . '.'; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             $out .= Utils::backtick($column[1]); |             $out .= Utils::backtick($column[1]); | ||||||
| @ -297,15 +326,19 @@ class Select | |||||||
|         return implode(',', $columns); |         return implode(',', $columns); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private function generateJoins(): string |     private function generateJoins(): array | ||||||
|     { |     { | ||||||
|         $joins = $this->joins; |  | ||||||
| 
 | 
 | ||||||
|         array_walk($joins, function (&$value, $key) { |         $joinQueries = []; | ||||||
|             $value = $value[0] . ' JOIN ' . $this->generateTable($value[1], true) . ' ON ' . $this->generateColumn($value[2]) . ' ' . $value[3] . ' ' . $this->generateColumn($value[4]); |         $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); | ||||||
|  | |||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return [implode(' ', $joinQueries), $params]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private function generateConditions(int $type): array |     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); |         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) { | ||||||
|  |                 return $unvisitedPlaces; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             $oldPlaces = $this->getRandomOldNForMapWithValidPano($mapId, $n - count($unvisitedPlaces), $userId); | ||||||
|  |              | ||||||
|  |             return array_merge($unvisitedPlaces, $oldPlaces); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     //TODO: use Map instead of id
 |     //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 = []; |         $places = []; | ||||||
| 
 | 
 | ||||||
|  |         $select = new Select(\Container::$dbConnection, 'places'); | ||||||
|  |         $select->where('map_id', '=', $mapId); | ||||||
|  |         $numberOfPlaces = $select->count(); | ||||||
|  | 
 | ||||||
|  |         $exclude = []; | ||||||
|         for ($i = 1; $i <= $n; ++$i) { |         for ($i = 1; $i <= $n; ++$i) { | ||||||
|             $place = $this->getRandomForMapWithValidPano($mapId, $exclude); |             $place = $this->getRandomForMapWithValidPano($numberOfPlaces, $select, $exclude); | ||||||
| 
 | 
 | ||||||
|             $places[] = $place; |             $places[] = $place; | ||||||
|             $exclude[] = $place->getId(); |             $exclude[] = $place->getId(); | ||||||
| @ -43,11 +65,15 @@ class PlaceRepository | |||||||
|         return $places; |         return $places; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     //TODO: use Map instead of id
 |     private function getRandomForMapWithValidPano(int $numberOfPlaces, Select $select, array &$exclude, ?callable $pickRandomInt = null): ?Place | ||||||
|     public function getRandomForMapWithValidPano(int $mapId, array $exclude = []): Place |  | ||||||
|     { |     { | ||||||
|         do { |         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(); |             $panoId = $place->getFreshPanoId(); | ||||||
|             if ($panoId === null) { |             if ($panoId === null) { | ||||||
| @ -58,25 +84,97 @@ class PlaceRepository | |||||||
|         return $place; |         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
 |         if ($numberOfPlacesLeft <= 0) | ||||||
|         $select = new Select(\Container::$dbConnection, 'places'); |             return null; | ||||||
|         $select->where('id', 'NOT IN', $exclude); |  | ||||||
|         $select->where('map_id', '=', $mapId); |  | ||||||
| 
 | 
 | ||||||
|         $numberOfPlaces = $select->count(); |         if (!isset($pickRandomInt)) { | ||||||
| 
 |             $randomOffset = random_int(0, $numberOfPlacesLeft - 1); | ||||||
|         //TODO: prevent this
 |         } else { | ||||||
|         if ($numberOfPlaces === 0) { |             $randomOffset = $pickRandomInt($numberOfPlacesLeft); | ||||||
|             throw new \Exception('There is no selectable place (count was 0).'); |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         $randomOffset = random_int(0, $numberOfPlaces - 1); |         $select->where('id', 'NOT IN', $exclude); | ||||||
| 
 |  | ||||||
|         $select->orderBy('id'); |  | ||||||
|         $select->limit(1, $randomOffset); |         $select->limit(1, $randomOffset); | ||||||
| 
 | 
 | ||||||
|         return $this->pdm->selectFromDb($select, Place::class); |         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
	
Better to use
$array[] =to add a new element to an array because it is more efficient according the the PHP docs: