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::LocalGoalDirection( 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::vec2& snakePosition = snake.Segments[0]; // Find and calculate distance to closest apple. const glm::ivec2 closestApple = gameState.FindClosestApple( snakePosition ); const glm::vec2 vectorToClosestApple = glm::vec2( closestApple ) - snakePosition; const float distanceToClosestAppleSqrd = glm::dot( vectorToClosestApple, vectorToClosestApple ); // Return direction to closest apple if it is within detection distance. if ( distanceToClosestAppleSqrd <= LOCAL_GOAL_DISTANCE * LOCAL_GOAL_DISTANCE ) { if ( distanceToClosestAppleSqrd != 0.0f ) { return vectorToClosestApple / distanceToClosestAppleSqrd; // Direction to the local goal (closest apple), diminishes with distance. } } return glm::vec2( 0.0f ); // Apple not found (or on the same tile as the snake for some reason). }