feature/avoid-repeating-places-in-game #38
@ -34,7 +34,9 @@ class PlaceRepository
|
|||||||
if(!isset($userId)) {
|
if(!isset($userId)) {
|
||||||
return $this->getRandomNForMapWithValidPano($mapId, $n);
|
return $this->getRandomNForMapWithValidPano($mapId, $n);
|
||||||
} else {
|
} else {
|
||||||
return $this->getRandomNewNForMapWithValidPano($mapId, $n, $userId);
|
$unvisitedPlaces = $this->getRandomUnvisitedNForMapWithValidPano($mapId, $n, $userId);
|
||||||
|
$oldPlaces = $this->getRandomOldNForMapWithValidPano($mapId, $n - count($unvisitedPlaces), $userId);
|
||||||
|
|||||||
|
return array_merge($unvisitedPlaces, $oldPlaces);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,8 +45,13 @@ class PlaceRepository
|
|||||||
{
|
{
|
||||||
$places = [];
|
$places = [];
|
||||||
|
|
||||||
|
$select = new Select(\Container::$dbConnection, 'places');
|
||||||
|
$select->where('id', 'NOT IN', $exclude);
|
||||||
bence
commented
This can be removed too. This can be removed too.
|
|||||||
|
$select->where('map_id', '=', $mapId);
|
||||||
|
$numberOfPlaces = $select->count();
|
||||||
|
|
||||||
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();
|
||||||
@ -53,11 +60,15 @@ class PlaceRepository
|
|||||||
return $places;
|
return $places;
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: use Map instead of id
|
private function getRandomForMapWithValidPano($numberOfPlaces, $select, array &$exclude, $randomSelection = null): ?Place
|
||||||
bence
commented
```php
int $numberOfPlaces, Select $select
```
|
|||||||
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, $randomSelection);
|
||||||
|
if($place === null) {
|
||||||
|
// there is no more never visited place left
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
$panoId = $place->getFreshPanoId();
|
$panoId = $place->getFreshPanoId();
|
||||||
if($panoId === null) {
|
if($panoId === null) {
|
||||||
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.
|
|||||||
@ -68,45 +79,91 @@ class PlaceRepository
|
|||||||
return $place;
|
return $place;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function selectRandomFromDbForMap(int $mapId, array $exclude): Place
|
private function selectRandomFromDbForMap($numberOfPlacesLeft, $select, array $exclude, $randomSelection): ?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($randomSelection)) {
|
||||||
|
$randomOffset = random_int(0, $numberOfPlacesLeft - 1);
|
||||||
//TODO: prevent this
|
} else {
|
||||||
if ($numberOfPlaces === 0) {
|
$randomOffset = $randomSelection($numberOfPlacesLeft);
|
||||||
throw new \Exception('There is no selectable place (count was 0).');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$randomOffset = random_int(0, $numberOfPlaces - 1);
|
// $select_unvisited->orderBy('last_time');
|
||||||
|
$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);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getRandomNewNForMapWithValidPano(int $mapId, int $n, int $userId, array $exclude = []): array
|
// Never visited places
|
||||||
|
private function getRandomUnvisitedNForMapWithValidPano(int $mapId, int $n, int $userId): array
|
||||||
{
|
{
|
||||||
$places = [];
|
$places = [];
|
||||||
|
$exclude = [];
|
||||||
|
|
||||||
// Never visited places
|
// list of places visited by user
|
||||||
|
$selectPlacesByCurrentUser = new Select(\Container::$dbConnection, 'user_played_place');
|
||||||
|
$selectPlacesByCurrentUser->columns(['place_id', 'last_time']);
|
||||||
|
$selectPlacesByCurrentUser->where('user_id', '=', $userId);
|
||||||
|
$selectPlacesByCurrentUser->setDerivedTableAlias('places_by_current_user');
|
||||||
|
|
||||||
|
// count the places never visited
|
||||||
|
$selectUnvisited = new Select(\Container::$dbConnection, 'places');
|
||||||
|
$selectUnvisited->leftJoin($selectPlacesByCurrentUser, ['places', 'id'], '=', ['places_by_current_user', 'place_id']);
|
||||||
|
$selectUnvisited->where('map_id', '=', $mapId);
|
||||||
|
$selectUnvisited->where('last_time', '=', null);
|
||||||
|
$numberOfUnvisitedPlaces = $selectUnvisited->count();
|
||||||
|
|
||||||
|
// look for as many new places as possible but maximum $n
|
||||||
do {
|
do {
|
||||||
$place = $this->getRandomNeverVisitedForMapWithValidPano($mapId, $userId, $exclude);
|
$place = $this->getRandomForMapWithValidPano($numberOfUnvisitedPlaces, $selectUnvisited, $exclude);
|
||||||
if(isset($place)) {
|
if(isset($place)) {
|
||||||
$places[] = $place;
|
$places[] = $place;
|
||||||
$exclude[] = $place->getId();
|
$exclude[] = $place->getId();
|
||||||
}
|
}
|
||||||
} while(count($places) < $n && isset($place));
|
} while(count($places) < $n && isset($place));
|
||||||
|
|
||||||
|
return $places;
|
||||||
|
}
|
||||||
|
|
||||||
// Places visited in the longest time
|
// Places visited in the longest time
|
||||||
|
private function getRandomOldNForMapWithValidPano(int $mapId, int $n, int $userId): array
|
||||||
|
{
|
||||||
|
$places = [];
|
||||||
|
$exclude = [];
|
||||||
|
|
||||||
|
// list of places visited by user
|
||||||
|
$selectPlacesByCurrentUser = new Select(\Container::$dbConnection, 'user_played_place');
|
||||||
|
$selectPlacesByCurrentUser->columns(['place_id', 'last_time']);
|
||||||
|
$selectPlacesByCurrentUser->where('user_id', '=', $userId);
|
||||||
|
$selectPlacesByCurrentUser->setDerivedTableAlias('places_by_current_user');
|
||||||
|
|
||||||
|
// count places that were visited at least once
|
||||||
|
$selectOldPlaces = new Select(\Container::$dbConnection, 'places');
|
||||||
|
$selectOldPlaces->leftJoin($selectPlacesByCurrentUser, ['places', 'id'], '=', ['places_by_current_user', 'place_id']);
|
||||||
|
$selectOldPlaces->where('map_id', '=', $mapId);
|
||||||
|
$selectOldPlaces->where('last_time', 'IS NOT', null);
|
||||||
|
$numberOfOldPlaces = $selectOldPlaces->count();
|
||||||
|
|
||||||
|
// set order by datetime, oldest first
|
||||||
|
$selectOldPlaces->orderBy('last_time');
|
||||||
|
|
||||||
|
// selection algorithm with preference (weighting) for older places
|
||||||
|
$gaussianRandomSelection = function($numberOfPlaces) {
|
||||||
|
$stdev = 0.2;
|
||||||
|
$avg = 0.0;
|
||||||
|
$x = mt_rand() / mt_getrandmax();
|
||||||
|
$y = mt_rand() / mt_getrandmax();
|
||||||
|
$randomNum = abs(sqrt(-2 * log($x)) * cos(2 * pi() * $y) * $stdev + $avg);
|
||||||
|
return (int) min($randomNum * $numberOfPlaces, $numberOfPlaces - 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// look for n - numberOfUnvisitedPlaces places
|
||||||
while(count($places) < $n)
|
while(count($places) < $n)
|
||||||
{
|
{
|
||||||
$place = $this->getRandomOldPlaceForMapWithValidPano($mapId, $userId, $exclude);
|
$place = $this->getRandomForMapWithValidPano($numberOfOldPlaces, $selectOldPlaces, $exclude, $gaussianRandomSelection);
|
||||||
if(isset($place))
|
if(isset($place))
|
||||||
{
|
{
|
||||||
$places[] = $place;
|
$places[] = $place;
|
||||||
@ -117,91 +174,5 @@ class PlaceRepository
|
|||||||
return $places;
|
return $places;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getRandomNeverVisitedForMapWithValidPano(int $mapId, $userId, array $exclude = []): ?Place
|
|
||||||
{
|
|
||||||
do {
|
|
||||||
$place = $this->selectRandomNeverVisitedFromDbForMap($mapId, $userId, $exclude);
|
|
||||||
if($place === null) {
|
|
||||||
// there is no more never visited place left
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$panoId = $place->getFreshPanoId();
|
|
||||||
if($panoId === null) {
|
|
||||||
$exclude[] = $place->getId();
|
|
||||||
}
|
|
||||||
} while($panoId === null);
|
|
||||||
|
|
||||||
return $place;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function selectRandomNeverVisitedFromDbForMap(int $mapId, $userId, array $exclude): ?Place
|
|
||||||
{
|
|
||||||
$select_places_by_current_user = new Select(\Container::$dbConnection, 'user_played_place');
|
|
||||||
$select_places_by_current_user->columns(['place_id', 'last_time']);
|
|
||||||
$select_places_by_current_user->where('user_id', '=', $userId);
|
|
||||||
$select_places_by_current_user->setDerivedTableAlias('places_by_current_user');
|
|
||||||
|
|
||||||
$select_unvisited = new Select(\Container::$dbConnection, 'places');
|
|
||||||
$select_unvisited->leftJoin($select_places_by_current_user, ['places', 'id'], '=', ['places_by_current_user', 'place_id']);
|
|
||||||
$select_unvisited->where('map_id', '=', $mapId);
|
|
||||||
$select_unvisited->where('last_time', '=', null);
|
|
||||||
$select_unvisited->where('id', 'NOT IN', $exclude);
|
|
||||||
$numberOfUnvisitedPlaces = $select_unvisited->count();
|
|
||||||
|
|
||||||
if($numberOfUnvisitedPlaces == 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$randomOffset = random_int(0, $numberOfUnvisitedPlaces - 1);
|
|
||||||
|
|
||||||
// $select_unvisited->orderBy('last_time');
|
|
||||||
$select_unvisited->limit(1, $randomOffset);
|
|
||||||
|
|
||||||
return $this->pdm->selectFromDb($select_unvisited, Place::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getRandomOldPlaceForMapWithValidPano(int $mapId, $userId, array $exclude = []): ?Place
|
|
||||||
{
|
|
||||||
do {
|
|
||||||
$place = $this->selectRandomOldPlaceFromDbForMap($mapId, $userId, $exclude);
|
|
||||||
if($place === null) {
|
|
||||||
// there is no more never visited place left
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$panoId = $place->getFreshPanoId();
|
|
||||||
if($panoId === null) {
|
|
||||||
$exclude[] = $place->getId();
|
|
||||||
}
|
|
||||||
} while($panoId === null);
|
|
||||||
|
|
||||||
return $place;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function selectRandomOldPlaceFromDbForMap(int $mapId, $userId, array $exclude): ?Place
|
|
||||||
{
|
|
||||||
$select_places_by_current_user = new Select(\Container::$dbConnection, 'user_played_place');
|
|
||||||
$select_places_by_current_user->columns(['place_id', 'last_time']);
|
|
||||||
$select_places_by_current_user->where('user_id', '=', $userId);
|
|
||||||
$select_places_by_current_user->setDerivedTableAlias('places_by_current_user');
|
|
||||||
|
|
||||||
$select_old_place = new Select(\Container::$dbConnection, 'places');
|
|
||||||
$select_old_place->leftJoin($select_places_by_current_user, ['places', 'id'], '=', ['places_by_current_user', 'place_id']);
|
|
||||||
$select_old_place->where('map_id', '=', $mapId);
|
|
||||||
$select_old_place->where('last_time', 'IS NOT', null);
|
|
||||||
$select_old_place->where('id', 'NOT IN', $exclude);
|
|
||||||
$numberOfOldPlaces = $select_old_place->count();
|
|
||||||
|
|
||||||
if($numberOfOldPlaces == 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$randomOffset = random_int(0, $numberOfOldPlaces - 1);
|
|
||||||
|
|
||||||
// $select_unvisited->orderBy('last_time');
|
|
||||||
$select_old_place->limit(1, $randomOffset);
|
|
||||||
|
|
||||||
return $this->pdm->selectFromDb($select_old_place, Place::class);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user
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.