void Boids::MakeMoves( const GameState currentState, size_t teamIndex, std::vector<Move>& outMoves ) { const Team& team = currentState.Teams[teamIndex]; const glm::vec2 teamAvaragePosition = CalculateAvaragePosition( currentState, teamIndex ); // Choose a new apple for the team as its goal if the previous goal-apple was taken. if ( !currentState.IsTileWalkable( m_GoalTile ) || currentState.Board[m_GoalTile.y][m_GoalTile.x] != Tile::Apple ) { m_GoalTile = currentState.FindClosestApple( teamAvaragePosition ); // TODO: Figure out another goal if there are no apples. } // Decide for each snake which direction it should move. for ( size_t snakeIndex = 0; snakeIndex < team.Snakes.size(); ++snakeIndex ) { const Snake& snake = team.Snakes[snakeIndex]; const glm::ivec2& snakeTile = snake.Segments[0]; const glm::vec2 snakePosition = glm::vec2( snakeTile ); // Calculate which moves the snake can make without dying this turn. // TODO: Take into account that tails move. std::vector<Move> safeMoves; if ( currentState.IsTileWalkable( snakeTile + glm::ivec2( 0, -1 ) ) ) { safeMoves.push_back( Move::Up ); } if ( currentState.IsTileWalkable( snakeTile + glm::ivec2( 0, 1 ) ) ) { safeMoves.push_back( Move::Down ); } if ( currentState.IsTileWalkable( snakeTile + glm::ivec2( -1, 0 ) ) ) { safeMoves.push_back( Move::Left ); } if ( currentState.IsTileWalkable( snakeTile + glm::ivec2( 1, 0 ) ) ) { safeMoves.push_back( Move::Right ); } // If there is only one safe move it is chosen, and we continue to the next snake instead. if ( safeMoves.size() == 1 ) { outMoves[snakeIndex] = safeMoves.front(); continue; } // Calculate and accumulate the effect of each rule that makes up boids (plus some extra ones specialized for the application snake/nibbles). glm::vec2 newSnakeDirection = glm::vec2( 0.0f ); newSnakeDirection += RULE_FACTOR_GOAL * GoalDirection( currentState, teamIndex, snakeIndex ); newSnakeDirection += RULE_FACTOR_LOCAL_GOAL * LocalGoalDirection( currentState, teamIndex, snakeIndex ); newSnakeDirection += RULE_FACTOR_SEPERATION * SeperationDirection( currentState, teamIndex, snakeIndex ); if ( team.Snakes.size() > 1 ) { // Only apply team-rules if the snake is not alone in the team. newSnakeDirection += RULE_FACTOR_COHESION * CohesionDirection( currentState, teamIndex, snakeIndex ); newSnakeDirection += RULE_FACTOR_ALIGNMENT * AlignmentDirection( currentState, teamIndex, snakeIndex ); newSnakeDirection += RULE_FACTOR_TEAM_SEPERATION * TeamSeperationDirection( currentState, teamIndex, snakeIndex ); } outMoves[snakeIndex] = ChooseSafeMove( newSnakeDirection, outMoves[snakeIndex], safeMoves ); } }
glm::vec2 Boids::SeperationDirection( const GameState& gameState, const size_t teamIndex, const size_t snakeIndex ) const { const Team& team = gameState.Teams[teamIndex]; const Snake& snake = team.Snakes[snakeIndex]; const glm::ivec2& snakeTile = snake.Segments[0]; // Accumulate repelling forces that keeps the snake away from blocked tiles within the avoidance distance. glm::vec2 avoidDirection = glm::vec2( 0.0f ); for ( int dy = -AVOIDANCE_DISTANCE; dy <= AVOIDANCE_DISTANCE; ++dy ) { for ( int dx = -AVOIDANCE_DISTANCE; dx <= AVOIDANCE_DISTANCE; ++dx ) { const glm::ivec2 tile = glm::ivec2( snakeTile.x + dx, snakeTile.y + dy ); // Tiles that are walkable are skipped, since they are safe for the snake to traverse. if ( gameState.IsTileWalkable( tile ) ) { // TODO: Take into account that tails move. continue; } // Skip tile if it is one of the 4 first segments in the snake, since the head cannot collide with any of those segments. bool skipTile = false; for ( size_t segmentIndex = 0; segmentIndex < 4 && segmentIndex < snake.Segments.size(); ++segmentIndex ) { if ( tile == snake.Segments[segmentIndex] ) { skipTile = true; break; } } if ( skipTile ) { continue; } // Add repelling force that keeps the snake away from the tile const glm::vec2 vectorFromTile = glm::vec2( snakeTile - tile ); const float distanceFromTile = glm::length( vectorFromTile ); avoidDirection += vectorFromTile / ( glm::pow( distanceFromTile, 3 ) ); // Force diminishes with distance. } } return avoidDirection; }