Back
A* Snake
  • Pathfinding
  • AI
  • Unity
Info

Role: Programmer

Category: AI

Engine: Unity

Team Size: Solo

Time: 2 weeks

About

Implementation of Snake's AI with A* pathfinding and weighed pickups. Simulation can be run with or without user having a controlled snake.

Highlights
  • Working and efficient A* pathfinding algorithm with weighed pickups.
  • Implementation of a linked list suited to the project's needs.
  • Variables and constraints that are easy to change and tweak for simulation purposes.
List of Features
Feature: Description:
Grid Generation Grid map generator with neighbour checking
Screen Wrapping Screen wrapping on map's edges
Linked List Implementation of linked list used in snakes
A* Pathfinding A* pathfinding for snake's AI with weighed pickups
Snake Slicing Break snake's linked lists in runtime
Grid Generation
Neighbours

Finding the neighbours of each tile is essential for true grid movement which was my goal for this project. In this case, only 4 positions need to be checked around the tile since there's no diagonal movement.

Code - Neighbours Around Tile
    // Check all neighbours around singular tile
    private void NeighboursAroundTile(Tile tile)
    {
        // Defining positions around tile to check
        Vector2Int position = tile.gridPosition;
        
        // Four possible neighbour positions around tile
        Vector2Int[] positions = new[]
        {
            new Vector2Int(position.x - 1, position.y), // left
            new Vector2Int(position.x + 1, position.y), // right
            new Vector2Int(position.x, position.y - 1), // down
            new Vector2Int(position.x, position.y + 1)  // up
        };
 
        // Check validity of all positions
        for (int i = 0; i < positions.Length; i++)
        {
            if (!IsEdgeTile(tile, positions[i]))
            {
                tile.neighbourTiles[i] = tileGrid[positions[i].x, positions[i].y];
            }
        }
    }
Screen Wrapping

Since the movement is already constraining that a snake's next move can only be to a neighbour tile, setting up the neighbours on the edges to be on the opposite edge is enough for the screen wrapping to work.

Code - Set edge's neighbours
    // Check if neighbour is valid and assigns it
    private bool SetEdgeTile(Tile tile, Vector2Int positionToCheck)
    {
        // Right edge
        if (positionToCheck.x >= size.x)
        {
            tile.neighbourTiles[(int) Direction.Right] = tileGrid[0, positionToCheck.y];
            return true;
        }
        // Left edge
        if (positionToCheck.x < 0)
        {
            tile.neighbourTiles[(int) Direction.Left] = tileGrid[size.x - 1, positionToCheck.y];
            return true;
        }
        // Up edge
        if (positionToCheck.y >= size.y)
        {
            tile.neighbourTiles[(int) Direction.Up] = tileGrid[positionToCheck.x, 0];
            return true;
        }
        // Down edge
        if (positionToCheck.y < 0)
        {
            tile.neighbourTiles[(int) Direction.Down] = tileGrid[positionToCheck.x, size.y - 1];
            return true;
        }
        return false;
    }
A* Pathfinding

Pickups

There's three types of pickups: Snake's dead body, fruit and a blue fruit. These pickups have different weights that will affect the snake's decisions when deciding on a path. This is done by adding this weight to the cost of the tile where the pickup is placed.

Code - Finding lowest cost object
    // Object with the lowest cost of distance + weight
    public GridObject LowestCostObject()
    {
        if (_spawner.spawnedObjects == null || _spawner.spawnedObjects.Count == 0) { return null; }

        int index = 0;
        int max = 99999;
        for (int i = 0; i < _spawner.spawnedObjects.Count; i++)
        {
            if (_spawner.spawnedObjects[i] == null) { continue; }
            
            GridObject gridObject = _spawner.spawnedObjects[i];
            
            int distanceCost = _pathfinding.TileDistanceCost(gridObject.currentTile, aiController.headBody.gridObject.currentTile);
            int totalCost = distanceCost + gridObject.GetWeight();
            if (totalCost < max)
            {
                max = totalCost;
                index = i;
            }
        }
        return _spawner.spawnedObjects[index];
    }
Linked List
Movement
AIController.cs
Snake Slicing
Snake.cs
© 2022 João Freire. All rights reserved.