Assignment #1: Snake AI
Game AI Programming, IS53049A (2018/2019), Alan Zucconi
Overview
The purpose of this assignment is to write an AI that plays the game Snake, using behaviour trees. The assignment must be completed using the Snake simulator and the behaviour tree library available on Learn.Gold.
Snake has been chosen because of several reasons. First of all, it is a very popular game most people are familiar with. Most importantly, is relatively easy to create a decent AI to play it, but is exceptionally challenging to play it optimally.
Deliverables
The submission consists of three deliverables:
Unity Package
A Unity package which contains the original C# scriptyou wrote, and the scriptable objectwhich was used to run the Snake simulator. The correct procedure to export the Unity package is explained in the section ¶ “Submitting your Snake AI”.
Only one Snake AI per student is allowed.
Submissions are tested automatically. If you do not comply with the submission instructions, your AI will not be tested and this will heavily penalise your work.
You will need to:
- Install Unity
- Download the Unity project from Gold
- Create your own Snake AI script and scriptable object (¶ “Setting up your AI”)
- Use a behaviour tree to control the Snake movement (¶ “ Writing your Snake AI”)
- Test the performance of your Snake AI (¶ “ Testing your AI ”)
- Export your files as Unity package (¶ “ Exporting your Snake AI ”)
- Upload the Unity package on Gold
Report
A short report (2-5 pages of text), in PDF format, which explains how your solution works and how it was implemented. This is also your chance to critically discuss when and why your AI fails, and how you could fix that. Do not forget to add references and bibliography, shall you need it.
Optional:you can add a diagram to visualise your behaviour tree, and a screenshot of the AI performance plotgenerated from the Snake simulator (see ¶ “Testing your AI”). Anything else that can improve the report presentation (cover, table of content, bibliography, …) is welcome.
?Grammar is not assessed, but typos will penalise your work: check your spelling.
Video
A short video (1-5 minutes) that shows a few rounds of your AI playing Snake.
You can simply record the Unity editor while running the Snake simulator.
Optional:you can add text or voice over to explain what is happening and how your solution works. You can also explain your code and design decisions.
Spelling and pronunciations are not assessed.
The Game
- Snake is a game played on a grid.
- The snake starts with length 1, which increases every time some food is eaten.
- There is only one piece of food in the world at any time.
- The snake has a direction or movement (North, East, South or West), and continues travelling in that direction if no action is taken.
The Unity package contains a class called SnakeGamewhere the logic of the game is implemented. You do not need to open or read this class, as it uses many advanced features you might not be familiar with. However, you will need to use some of its methods and properties to build you Snake AI (see ¶ “Snake API” for a full list of what you can use).
Addressing Cells
Like every AI agent that performs some sort of decision making, the snake has actions(things it can do) and perceptions(things it can see). The snake has a limited vision, and it can only see what is inside the four cells immediately adjacent to its head.
There are two ways to address the cells around the head: relativeand absolute (table below):
Relative |
Absolute |
- Relative actions and perceptionsare seen from the perspective of the snake, along its direction of movement.
- Absolute actions and perceptionsare relative to the world, and ignore the orientation of the head.
Actions
The only actions the snake can perform are about changing its direction of travelling. The snake can also refuse to take a decision: in that case, it will simply keep travelling along its current direction of movement.
When you are playing Snake with the keyboard, you can use the arrow keys to change its direction in one of the cardinal directions: North, East, South or West. This way of controlling the snake is possible using absolute actions.
Alternatively, you can also rotate the snake using relative actions, such as turn leftor turn right, based on its current direction of movement.
The class SnakeGamecontains several methods to control the movement of the snake in either a relative or absolute fashion (table below).
Relative |
Absolute |
|
Actions |
TurnLeft TurnRight |
GoNorth GoEast GoSouth GoWest |
Perceptions
The snake is able to perceive the four cells adjacent to its head. It can detect if they are free, if there is food or if there is an obstacle.
Relative |
Absolute |
|
Perceptions |
IsFoodAhead IsFoodLeft IsFoodRight |
IsFoodNorth IsFoodEast IsFoodSouth IsFoodWest |
IsFreeAhead IsFreeLeft IsFreeRight |
IsFreeNorth IsFreeEast IsFreeSouth IsFreeWest |
|
IsObstacleAhead IsObstacleLeft IsObstacleRight |
IsObstacleNorth IsObstacleEast IsObstacleSouth IsObstacleWest |
The snake is also aware of the absolute position of its head (HeadPosition) and thanks to its strong sense of smell it knows the absolute position of the food (FoodPosition).
Getting Started
To complete this assignment you will need to have Unity installed. You can download it for free (Personal Edition) from the Unity Store. You need Unity 2018.2; the project is not guaranteed to work with any other version. You get install Unity Hubto manage multiple versions of Unity on the same machine.
Importing the package
- Download the Unity package from Gold
- Open Unity, click on “New” to create a new project.
You should be using Unity 2018.2 - Drag the package into Unity to import it
Testing the project
Once the package is imported, you need to run a test scene to make sure everything works as expected.
- Open the scene called “ Test” in the folder “Assets/Snake”
- Run the scene by pressing the play button
The Snake simulator will start, playing the game with an AI that moves randomly (see below).
If you see something different, or if you receive error messages in the Console window, there might be a problem with your setup or with your version of Unity.
There are other two scenes that you can try: “1. Keyboard” and “2. Example”. The former allows you to play snake with the keyboard. The latter shows a very simple AI that tries to avoid obstacles.
Setting up your AI
Now that the project is working correctly, you need to create your own AI. For this task, you can the scene “3. Create”.
Creating the Script
- From the Project window, open the folder “Snake/AIs/Students”
- Select the script “SnakeAI_yourlogin” and duplicate it by pressing Ctrl+D.
?This operation has to be done from the Unity editor! Ctrl+Don Windows deletes the selected files without asking for confirmation! - Right click on the newly created file and rename it using your student login.
For instance, if your login is “azucc002”, you should rename it “SnakeAI_azucc002” (use your student login instead).
After these steps, you might see an error appearing in the console. That is normal, and is caused by the fact that while we have renamed the file, we have not changed the name of the class it contains (class names in C# must be unique).
Editing the Script
- Open your newly renames script using MonoDevelopor Visual Studio
- Rename the class from “SnakeAI_yourlogin” to “SnakeAI_azucc002” (use your student login instead), as seen below:
// Before
[CreateAssetMenu(fileName = "SnakeAI_yourlogin", menuName = "Snake/SnakeAI_yourlogin")]
public class SnakeAI_yourlogin : SnakeAI
// After
[CreateAssetMenu(fileName = "SnakeAI_azucc002", menuName = "Snake/SnakeAI_azucc002")]
public class SnakeAI_azucc002 : SnakeAI
At this point, there should be no errors in the console.
If you want to experiment with multiple AIs, you can repeat this procedure to create other classes.
While you are encouraged to play with multiple AIs, only the one named with your student login will be valid for submission!
Creating the Scriptable Object
- From the Project window, open the folder “Snake/AIs/Students”
- Right click on it and from the context menu select “Create/Snake/SnakeAI_azucc002” (use your student login instead).
?Make sure the name of your scriptable object is “SnakeAI_azucc002” (use your student login instead).
This will instantiate your script into a scriptable object; loosely speaking, scriptsand scriptable objectsare comparable to classesand objects.
The scriptable object is the asset that you will drag and drop in the Snake simulator.
Using the Snake Simulator
Once you have created a scriptable object for your AI, you can start using the Snake simulator.
- Open the scene “ Create” from the folder “Snake”
- Select the game object “SnakeGame” from the Hierarchy (the list on the left side of the screen).
You will see all the parameters of the Snake simulator on the Inspector (right side of the screen). - Find the scriptable object that you have previously created, and drag it onto the “AI” slot of “SnakeGame” (below).
Once that is done, the Snake simulator will use the chosen AI to control the snake movement. If you have not made any change to your Snake AI, the snake will simply travel in a straight line.
All the settings of the Snake simulator (world size, snake graphics, speed, …) are accessible and can be changed at any time. Please, remember that your Snake AI will be tested with the default ones.
Writing your Snake AI
The AI to control Snake is implemented using a behaviour tree. Each class that derives from SnakeAI(like your SnakeAI_yourlogin) needs to implement a method called CreateBehaviourTree. Such method has to return a Node, which will be the root of your behaviour tree.
When you open the file for the first time, it contains a behaviour tree does nothing:
public override Node CreateBehaviourTree(SnakeGame Snake) { return Action.Nothing; } |
It contains an Actionnode, which does nothing. Since no action is taken, this AI will cause the snake to keep going straight ahead.
For a more interesting example, the following behaviour tree creates an AI that constantly turns left:
public override Node CreateBehaviourTree(SnakeGame Snake) { return new Action( Snake.TurnLeft); } |
The method TurnLeftis the action that we want to perform. To add it to a behaviour tree, it is firstly wrapped in an Actionnode.
When creating Action nodes like this, you want to pass a function, not its results.
This is why we write Snake.TurnLeftand not Snake.TurnLeft(): the former passes to the function as a parameter, the latter executes the function and passes its results.
Conditionals
The following code creates an AI that turns left if the snake sees food on its left:
public override Node CreateBehaviourTree(SnakeGame Snake) { return new Sequence ( new Condition( Snake.IsFoodLeft), // Tries this first... new Action ( Snake.TurnLeft ) // ...then this ); } |
This works because the the Sequencenode tries to execute all nodes in a sequence. The first one is a Conditionnode that checks if the snake sees food on its left. When fails (no food on the left side), the remaining nodes are not executed and the snake does not turn.
This could have also been implemented using the Filternode, which often allows for neater behaviour trees:
public override Node CreateBehaviourTree(SnakeGame Snake) { return new Filter ( Snake.IsFoodLeft, // The condition to test for new Action( Snake.TurnLeft ) // The action to execute ); } |
Note that when you use Filter, you do not need to use Condition. However, you can still use new Condition(Snake.IsFoodLeft).
Random Actions
The following code creates an AI that randomly rotates left or right:
public override Node CreateBehaviourTree(SnakeGame Snake) { return new Selector ( true,// Randomly chooses one action to execute new Action( Snake.TurnLeft ), new Action( Snake.TurnRight ) ); } |
For a more complex example: the following behaviour tree checks if there is an obstacle ahead, and in that case it it randomly goes left or right.
public override Node CreateBehaviourTree(SnakeGame Snake) { return new Filter Snake.IsObstacleAhead, // If there is something ahead… new Selector // ...randomly goes left or right true, new Action( Snake.TurnLeft ), new Action( Snake.TurnRight ) ) ); } |
Custom Conditions
You can can create custom conditions for your Snake AI.
This is possible by passing a boolean function to a Conditionnode.
The following code moves the snake east if the food is on its right:
public override Node CreateBehaviourTree(SnakeGame Snake)
{
return new Filter
(
() => Snake.FoodPosition.x - Snake.HeadPosition.x > 0,
new Action ( Snake.GoEast )
);
}
The notation used in the Filternode is a lambda expression, which allows defining anonymous functionsthat can be used as parametres of other functions.
?This is an advanced feature of C#, which you do not have to use. You can get a decent AI just using the methods that are exposed in the SnakeGameclass.
Common Issues
The Sequencenode evaluates all of its children in a sequence, until one fails. However, the snake can perform only a single action per frame. This means that if you are asking the snake to move twice in a frame, it will not.
For instance: the following AI is not going to make the snake move in a loop.
public override Node CreateBehaviourTree(SnakeGame Snake)
{
return new Sequence
(
new Action ( Snake.GoNorth ),
new Action ( Snake.GoEast ),
new Action ( Snake.GoSouth ),
new Action ( Snake.GoWest )
);
}
While all of the four methods will be executed, they are only telling the snake in which direction to move in the next frame. As such, only the last one will be used and the snake will always go left.
For this reason, Sequencenodes should only have one action inside.
They can also be used as an “and” operator if you want to evaluate a series of conditions.
Submitting your Snake AI
Once you are satisfied with your Snake AI, you can prepare the submission.
All Snake AIs are tested automatically. For this very reason, it is important that you follow these instructions carefully. If you do not, it might be impossible to test your Snake AI and this will heavily penalised your work.
Testing your AI
Before submitting the work, the performance of all Snake AIs must be tested automatically.
- Open the scene “ Test” from the folder “Snake”
- Select the game object “Automation” from the Hierarchy
- From the Inspector, drag the scriptable object of your Snake AI in the “Element 0” slot of the “AIs” list
You can now play the scene. The script will test your Snake AI 100 times, recording its performance directly in the scriptable object. Once done, you will see a message on the console.
Checking the Performance
You can check the performance of your Snake AI by selecting its scriptable object and expanding the “PlotData” field. This will reveal a 2D scatter plot, where each point represents a simulation (below). The x-axis measures time (in game updates); the y-axis measures the score (in pieces of food eaten).
The yellow lines indicate the medianvalue on each axis. The yellow areas highlight the interquartile range, which is a measure of how consistent your results are.
You are encouraged to include a screenshot of the scatter plot in your report.
Exporting your Snake AI
You can export your work after having tested its performance.
- Select both the script and the scriptable object that you have created
- While they are both selected, right click on one of them, and choose “Export package…”
- Uncheck “Include dependencies”, and make sure that only those two files are included (picture below)
- Export the package with the name “SnakeAI_azucc002” (use your student login instead).
The unity package is now ready to be uploaded on Learn.Gold.
For the purpose of the submission, your Unity package should only contain those two files.
If there are other files that you might want to submit (multiple AIs, additional classes or libraries, other test scenes you have used to finetune the parameters of your AIs…) get in touch and we can discuss that.
Make sure to fill all the fields in the Snake AI scriptable objects before submission!
You are also highly encouraged to give silly names to your Snake AI (such as TurboSnake3000).
Snake API
These are the methods of the SnakeGameclass that are allowed to be called.
Relative |
Absolute |
|
Actions |
TurnLeft TurnRight |
GoNorth GoEast GoSouth GoWest |
Perceptions |
IsFoodAhead IsFoodLeft IsFoodRight |
IsFoodNorth IsFoodEast IsFoodSouth IsFoodWest |
IsFreeAhead IsFreeLeft IsFreeRight |
IsFreeNorth IsFreeEast IsFreeSouth IsFreeWest |
|
IsObstacleAhead IsObstacleLeft IsObstacleRight |
IsObstacleNorth IsObstacleEast IsObstacleSouth IsObstacleWest |
The full list is here:
Snake properties (read only)
- Vector2Int Snake.HeadPosition
- Direction Snake.Direction
World properties (read only)
- Vector2Int Snake.FoodPosition
- Vector2Int Snake.Size
Relative perception
- bool Snake.IsFoodAhead ()
- bool Snake.IsFoodLeft ()
- bool Snake.IsFoodRight ()
- bool Snake.IsFreeAhead ()
- bool Snake.IsFreeLeft ()
- bool Snake.IsFreeRight ()
- bool Snake.IsObstacleAhead ()
- bool Snake.IsObstacleLeft ()
- bool Snake.IsObstacleRight ()
Relative movement
- void Snake.TurnLeft ()
- void Snake.TurnRight ()
Absolute perception
- bool Snake.IsFoodNorth ()
- bool Snake.IsFoodEast ()
- bool Snake.IsFoodSouth ()
- bool Snake.IsFoodWest ()
- bool Snake.IsFreeNorth ()
- bool Snake.IsFreeEast ()
- bool Snake.IsFreeSouth ()
- bool Snake.IsFreeWest ()
- bool Snake.IsObstacleNorth ()
- bool Snake.IsObstacleEast ()
- bool Snake.IsObstacleSouth ()
- bool Snake.IsObstacleWest ()
Absolute movement
- void Snake.GoNorth ()
- void Snake.GoEast ()
- void Snake.GoSouth ()
- void Snake.GoWest ()