Merge pull request 'feature/avoid-repeating-places-in-game' (#38) from feature/avoid-repeating-places-in-game into develop
All checks were successful
default-pipeline default-pipeline #189
All checks were successful
default-pipeline default-pipeline #189
Reviewed-on: https://gitea.e5tv.hu/esoko/mapguesser/pulls/38 Reviewed-by: Pőcze Bence <bence@pocze.ch>
This commit is contained in:
commit
216d30329f
@ -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);
|
||||
}
|
||||
|
||||
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) {
|
||||
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('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
|
||||
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…
Reference in New Issue
Block a user