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);
 | 
				
			||||||
 | 
					        $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,60 +60,110 @@ class PlaceRepository
 | 
				
			|||||||
        return $places;
 | 
					        return $places;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    //TODO: use Map instead of id
 | 
					    private function getRandomForMapWithValidPano($numberOfPlaces, $select, array &$exclude, $randomSelection = 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, $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) {
 | 
				
			||||||
| 
							
							
								
									
	
	
	
	
	
	
	
	 | 
					|||||||
                $exclude[] = $place->getId();
 | 
					                $exclude[] = $place->getId();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } while ($panoId === null);
 | 
					        } while($panoId === null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	
I wouldn't reformat this. There is no official coding guideline but I usually put a space after if, while, for, etc.