# Class 6

#### This class discusses the snake game implementation.

Now that all the hardware and firmware is set up, it’s time to get to the software. At this point we have a `Cube` class which can enable an LED based on a coordinate. We need to now create software which uses this low level firmware. We will use multiple files to achieve this, as is the goal of object-oriented programming. One class should be dedicated to fulfilling one and only one task. They can communicate, but should know nothing about each other. For the Snake game, we will need a couple of things:

• The snake logic
• A way to handle input
• A sense of direction

We want to decouple these things, as they should perform independently. However they will all work together to pull the game together.

###### Modifying the Cube Class

I’m going to add two functions to the `Cube` class, `bufferFromMatrix` and `toggleLED` to the `Cube` class to make programming the game a little easier, and to add some more functionality. Here is the completed `Cube.h`.

``````/*
* File:     Cube.h
* Author:   Jeff Longo
* Comments: LED cube controller class
*/

// Header guard, prevents including this file more than once
#ifndef CUBE_H
#define CUBE_H

#include "Arduino.h"

// Begin pin definitions
#define SERIAL_IN  2
#define LATCH_CLK  3
#define INPUT_CLK  4
#define UPDATE_CLK 5
#define RESET      6
#define SET        7
// End pin definitions

class Cube
{
private:
const static byte size = 4;
const static int period = 100;
unsigned short buffer[size];

public:
// Initializes the cube
Cube();

// Adds an LED to the buffer at position (x, y, z) with range (0, size - 1)
void bufferLED(byte x, byte y, byte z);

// Buffer LEDs from a 3D boolean matrix
void bufferFromMatrix(bool m[size][size][size]);

// Switches the state of an LED at position (x, y, z) with range (0, size - 1)
void toggleLED(byte x, byte y, byte z);

// Parses the data from the buffer, displays the frame, then clears the buffer
void display();

// Emptys the contents of the buffer
void clearBuffer();

// Resets the system
void reset();
};

#endif // CUBE_H
``````

And the completed `Cube.cpp`

``````#include "Cube.h"

Cube::Cube()
{
// Configure the necessary pins as output
pinMode(SERIAL_IN, OUTPUT);
pinMode(INPUT_CLK, OUTPUT);
pinMode(LATCH_CLK, OUTPUT);
pinMode(UPDATE_CLK, OUTPUT);
pinMode(RESET, OUTPUT);
pinMode(SET, OUTPUT);
}

void Cube::bufferLED(byte x, byte y, byte z)
{
// Ensure the specified LED is within the bounds of the cube
if (x > (size - 1) || y > (size - 1) || z > (size - 1)) return;

// Convert the x,y position to a mapping of the format used for the buffer
/*
*  (0, 0) --> 000000000000001   -> 1
*  (1, 0) --> 000000000000010   -> 2
*  (2, 0) --> 000000000000100   -> 4
*  ...
*  (3, 3) --> 1000000000000000  -> 32768
*/
unsigned short mapped = pow(2, (x + size*y)) + 0.5;

// Add the LED to the buffer
buffer[z] = buffer[z] | mapped;
}

void Cube::bufferFromMatrix(bool m[size][size][size])
{
for (byte x = 0; x < 4; x++)
{
for (byte y = 0; y < 4; y++)
{
for (byte z = 0; z < 4; z++)
{
if (m[x][y][z])
{
bufferLED(x, y, z);
}
}
}
}
}

void Cube::toggleLED(byte x, byte y, byte z)
{
buffer[z] ^= 1 << (x + size*y);
}

void Cube::clearBuffer()
{
for (byte i = 0; i < size; i++) buffer[i] = 0;
}

void Cube::reset()
{
clearBuffer();
// Remove any active LEDs
display();
// Clear the layer shift register
digitalWrite(RESET, LOW);
delayMicroseconds(period);
digitalWrite(RESET, HIGH);
delayMicroseconds(period);
// Start the layer shift register
digitalWrite(SET, HIGH);
delayMicroseconds(period);
digitalWrite(UPDATE_CLK, HIGH);
delayMicroseconds(period);
digitalWrite(UPDATE_CLK, LOW);
delayMicroseconds(period); // probably not necessary
digitalWrite(SET, LOW);
}

void Cube::display()
{
// Update each layer (z-axis)
for (byte i = 0; i < size; i++)
{
// Update x-y plane
for (byte j = 0; j < size*size; j++)
{
// Retrieve the state of bit j on the current layer
byte curr = (buffer[i] >> (size*size - 1 - j)) &  1;
digitalWrite(SERIAL_IN, curr);
// Toggle the input clock to push the bit through
digitalWrite(INPUT_CLK, HIGH);
// Provide time for the input to update
delayMicroseconds(period);
// Toggle the input clock to wait for the next bit
digitalWrite(INPUT_CLK, LOW);
// Provide time to wait for the next bit
delayMicroseconds(period);
}
// Toggle the latch clock to save the output
digitalWrite(LATCH_CLK, HIGH);
// Provide time for the output to latch
delayMicroseconds(period);
digitalWrite(LATCH_CLK, LOW);
// Provide time before changing the layer
delayMicroseconds(period);
// Toggle the update clock to switch to the next layer
digitalWrite(UPDATE_CLK, HIGH);
// Provide time to switch layers
delayMicroseconds(period);
// Toggle the update clock to work with the selected layer
digitalWrite(UPDATE_CLK, LOW);
}
}
``````

`bufferFromMatrix` simply allows you to pass a 3D matrix of booleans (1 or 0) and it will buffer the LEDs which are 1. This will be useful when we need to turn many LEDs on at once.

`toggleLED` checks the status of the LED at the given coordinate and uses the XOR operator to switch its status. In other words 1->0 or 0->1. This will be useful for blinking an LED, so we don’t have to keep a variable to hold the current state of an LED.

###### Direction and Enums

Next we are going to create whats called an `enum` to store direction. We could use an `int` to store direction (0 = up, 1 = down, ect) but this is not very readable. Instead, we use an `enum`. An `enum` allows you to define your own data type. For example, you might define an enum called Color and have a set of colors: Red, Blue, Green. Then we can use code such as the following:

``````enum Color
{
Red,
Blue,
Green
};

Color c;

if (c == Red)
{
// Do something
}
``````

We will do the same to store direction. We create a header file called `Direction.h`.

``````/*
* File:     Direction.h
* Author:   Jeff Longo
* Comments: Small enum for storing direction
*/

// Header guard, prevents including this file more than once
#ifndef DIRECTION_H
#define DIRECTION_H

enum Direction
{
xpos, // +x
xneg, // -x
ypos, // +y
yneg, // -y
zpos, // +z
zneg  // -z
};

#endif // DIRECTION_H
``````

We use the header guard, like normal, and create an enum called `Direction`. Any file that includes `Direction.h` can use a `Direction` type variable. We create a direction for every possible movement in 3D space.

###### Handling Input

Next we will make a class to handle input. The desired effect is that our `Snake` game class creates an instance of the `InputHandler` class. The `Snake` can query the `InputHandler` for the current state of input. The `Snake` performs logic based on the current input. This way, the `InputHandler` only knows about what inputs the user makes and the `Snake` only needs to know what input is given to it by the `InputHandler` and performs all the game logic. Here is our `InputHandler.h`.

``````/*
* File:     InputHandler.h
* Author:   Jeff Longo
* Comments: Handles inputs from the pushbutton switches
*/

// Header guard, prevents including this file more than once
#ifndef INPUT_HANDLER_H
#define INPUT_HANDLER_H

#include "Arduino.h"
#include "Direction.h"

#define START 8
#define UP    9
#define DOWN  10
#define LEFT  11
#define RIGHT 12

class InputHandler
{
public:
// Initializes the input handler
InputHandler();

// Initializes runtime variables
void init();

// Checks for a start button press
bool startPressed();

// Polls for input and handles the snake's direction
void update();

// Updates prevDir for the next frame
void saveDir() { prevDir = dir; }

// Set the current direction
void setDir(Direction dir) { this->dir = dir; }

// Get the current direction
Direction getDir(){ return dir; }

private:
Direction dir, prevDir, lastButtonPressed;
bool released;
unsigned long timeOfUpRelease, timeOfDownRelease;

};

#endif // INPUT_HANDLER_H
``````

We have a lot of functions are variables here. We create the constructor, `InputHandler` which will run only when the object is initialized. We also create `init` which is necessary because if the game resets, we do not get to run the constructor again, so we need a way to set up again. We create `startPressed` to check specifically for the start button being pressed, which the game needs to wait for to start. `update` is perhaps the most important function, and it is where we will determine what direction to send to the `Snake` game. Lastly, we create three more functions to manipulate direction, since the snake logic will modify direction. These three functions are written inline because they are so simple.

Here is `InputHandler.cpp`

``````#include "InputHandler.h"

InputHandler::InputHandler()
{
pinMode(START, INPUT);
pinMode(UP, INPUT);
pinMode(DOWN, INPUT);
pinMode(LEFT, INPUT);
pinMode(RIGHT, INPUT);
}

void InputHandler::init()
{
dir = prevDir = lastButtonPressed = xpos;
timeOfUpRelease = timeOfDownRelease = 0;
released = true;
}

bool InputHandler::startPressed()
{
return digitalRead(START) ? true : false;
}

void InputHandler::update()
{
{
// Detect double up press
if (prevDir != zneg && lastButtonPressed == ypos && (millis() - timeOfUpRelease < 150))
{
dir = zpos;
}
// Prevent direction switch if a double up press just occurred
else if (prevDir != yneg && (dir != zpos || (millis() - timeOfUpRelease > 400)))
{
dir = ypos;
}
lastButtonPressed = ypos;
released = false;
}
{
// Detect double down press
if (prevDir != zpos && lastButtonPressed == yneg && (millis() - timeOfDownRelease < 150))
{
dir = zneg;
}
// Prevent direction switch if a double down press just occurred
else if (prevDir != ypos && (dir != zneg || (millis() - timeOfDownRelease > 400)))
{
dir = yneg;
}
lastButtonPressed = yneg;
released = false;
}
{
if (prevDir != xneg)
{
dir = xpos;
}
lastButtonPressed = xpos;
released = false;
}
{
if (prevDir != xpos)
{
dir = xneg;
}
lastButtonPressed = xneg;
released = false;
}
// Detect up or down button releases
else if (!released)
{
if (lastButtonPressed == ypos)
{
timeOfUpRelease = millis();
}
else if (lastButtonPressed == yneg)
{
timeOfDownRelease = millis();
}
released = true;
}
}
``````

Similar to the `Cube` class, we define the pins used for the switches on the board in the constructor. However this time we are using the pins as inputs instead of outputs.

`startPressed` simply performs a read on the Arduino pin corresponding with the start button and returns whether it is pushed or not.

`update` is where everything happens. We first create a few if/else if statements. We perform reads on the button pins and check if any of them are pushed. If a button is pushed, we change the `Direction` variable to correspond with the direction we should go. For example, pushing the right button should change the direction to moving in the positive x-axis. Pushing left should correspond to moving in the negative x-axis. However, we must ensure that we are not allowed to move in the direction opposite to where we are already moving, or else the player would instantly die. To handle this, we perform a check to see if the current direction is not the opposite direction of the button we pushed before changing the direction. We also record our previous direction, and change the state of a variable which holds whether or not a button is pushed or not to false. This will become clear later.

The UP button and DOWN button are difficult to handle. This is because we use these to determine both a change in the y-axis, or a change in the z-axis, depending on how you push the buttons. My implementation of the game records a double-tap of the UP button as movement in the positive z-axis, and a double-tap of the DOWN button as movement in the negative z-axis.

To differentiate between a regular button push a double press, we do a few things. Take a look at the code for handling an UP press.

``````if (digitalRead(UP))
{
// Detect double up press
if (prevDir != zneg && lastButtonPressed == ypos && (millis() - timeOfUpRelease < 150))
{
dir = zpos;
}
// Prevent direction switch if a double up press just occurred
else if (prevDir != yneg && (dir != zpos || (millis() - timeOfUpRelease > 400)))
{
dir = ypos;
}
lastButtonPressed = ypos;
released = false;
}
``````

The first if statement

``````if (prevDir != zneg && lastButtonPressed == ypos && (millis() - timeOfUpRelease < 150))
``````

First checks that you aren’t moving in the opposite direction as the double press, and checks that the last button that was pushed was also up (so that it is a indeed a double press), and also checks the difference in time since you last released the up button and sees if it is less than 150ms. We want to ensure that only double presses that happened fast are recorded as double presses.

The second if statement

``````else if (prevDir != yneg && (dir != zpos || (millis() - timeOfUpRelease > 400)))
``````

is for a single press. It checks that you aren’t moving in the opposite direction as the single press, then checks that either you didn’t just input a double press up, or if enough time has elapsed to where the last up press was not recent. We do this because when the next game-frame comes and checks for inputs, if you are still holding the up button from the last game frame, it will detect this as a double press, and move you on the z-axis instead of the y-axis.

If no buttons are pressed, we can safely record the time that a button was released.

###### The Snake Game

Lastly is the implementation of the snake game itself. I will provide most of the code, but I will leave the logic up to you. Here is `Snake.h`

``````/*
* File:     Snake.h
* Author:   Jeff Longo
*/

// Header guard, prevents including this file more than once
#ifndef SNAKE_H
#define SNAKE_H

#include <ArduinoSTL.h>
#include <deque>
#include "Cube.h"
#include "InputHandler.h"
#include "Direction.h"

class Snake
{
public:
// Initializes the snake
Snake();

// Polls for a start button press then initializes runtime variables
void start();

// Executes one game tick
void update();

// Resets the game state
void reset();

// Checks if the snake is alive

private:
// Structure to hold a 3D coordinate point
struct Coord { byte x, y, z; };

// Initializes runtime variables
void init();

// Executes game logic
void logic();

// Generates a new food piece
void generateFood();

Cube cube;
bool m;
std::deque<Coord> snake;
Coord food;
InputHandler io;
int frameTime;
};

#endif // SNAKE_H
``````

Taking a look at our functions, we create a `start` function, an `update` function, a `reset` function, and a `isDead` function. These are all the public functions needed to run the game. When we’re ready to start, we run `start`. The game’s main loop will just continuously run `update` until `isDead` returns true. When that happens you run `reset` and start again. This is what will happen in the `loop` function in the `.ino` file.

In the private section, we create variables and functions only the `Snake` can use. We define a `struct` called Coord. A `struct` is similar to a `class` aside from the fact that you do not put functions in a `struct`, only variables. Therefore, we can create an instance of `Coord` to create a 3D point. Similar to the `InputHandler`, we create an `init` function because we can only run the constructor once, and we need a way to reset. `logic` is where the game will determine what to do next, while `update` manages the framerate and polls for input. `generateFood` is a simple function which should place a piece of food in a random location on the map.

For our variables, we create the `Cube` object here since the `Snake` will be drawing to the cube. We create a 3D array of size 4x4x4 of booleans to determine which LEDs should be active and which LEDs should not be active at any time. This is why we added `bufferFromMatrix` to `Cube.cpp`. The Snake itself will be stored in a data structure called a `deque` (note: this is for my implementation only, there’s no restriction to implement the game the way I did). A `deque` is a Double-Ended Queue. It allows you to push or pop (add or remove) from the front of the data structure, or the back of the data structure. It can also be a variable size. Think of it like a variable size array in which you can only add, remove, or access from the first or last element of the array. If you want to use this implementation, you will need to download and include the library `ArduinoSTL` (you can do this in the Arduino IDE). More on this when we talk about the game logic.

We create an instance of `Coord` to hold the position of the food. We also create an instance of `InputHandler` which we will use to poll for inputs. We create a boolean called `dead` to store the state of whether the player is alive or dead, and an int `frameTime` to fix the framerate of the game.

Here is the unfinished `Snake.cpp`. I will discuss the important functions, and what you need to implement yourself.

``````#include "Snake.h"

Snake::Snake()
{
// Start from a known state
reset();
}

void Snake::start()
{
while (!io.startPressed())
{

}
init();
}

void Snake::update()
{
io.saveDir();
// Repeat the display and input polling for the duration of one frame
unsigned long startTime = millis();
unsigned long elapsed = 0;
while (elapsed < frameTime)
{
if (blinkCounter++ % 10 == 0)
{
cube.toggleLED(food.x, food.y, food.z);
}
cube.display();
io.update();
elapsed = millis() - startTime;
}
// Reset the buffer and prepare data for the next frame
cube.clearBuffer();
logic();

// If the snake dies, pause the display before ending the game
{
cube.bufferFromMatrix(m);
cube.bufferLED(food.x, food.y, food.z);
startTime = millis();
elapsed = 0;
while (elapsed < 1000)
{
cube.display();
elapsed = millis() - startTime;
}
}
}

void Snake::reset()
{
snake.clear();
cube.reset();

// Reset the map matrix
for (byte x = 0; x < 4; x++)
{
for (byte y = 0; y < 4; y++)
{
for (byte z = 0; z < 4; z++)
{
m[x][y][z] = 0;
}
}
}
}

void Snake::init()
{
// Disable any active LEDs
cube.reset();

// Init runtime variables
io.init();
frameTime = 500;

// Init snake location
snake.push_front({0, 0, 0});
m[snake.front().x][snake.front().y][snake.front().z] = 1;

// Spawn a food
generateFood();

// Illuminate the snake and food LEDs
cube.bufferFromMatrix(m);
}

void Snake::logic()
{
/*
* Write this function!
*/
}

void Snake::generateFood()
{
// Find an available slot for the food
/*
* Write this function!
*/
}

``````

The constructor simply resets the systems so that it starts from a known state.

`start` runs an infinite loop until the `InputHandler` detects a start button press. In the infinite loop, you can display a cool pattern while you are waiting for a start press.

`update` is the most important function. We create a loop which runs until the elapsed time since the function update function began is equal to the frametime. This is one game tick. For example, say we want to run the cube at 2 frames per second. 2 frames per second corresponds to half a second per game tick. Thus, we run the loop inside `update` until 500ms have elapsed. The loop inside `update` will continuously display to the cube poll for input as fast as possible until the frame is over. This way, the updates to the cube and polls for input are not limited to 2 per second. I’ve included code which allows the the food LED to blink using the `toggleLED` function.

At the end of the frame, the cube is cleared and the game logic happens. This does not go in the loop because game logic should only happen one time a frame. I have also added code so that if the player dies, it will display where the player died for a brief time before moving to the next frame.

`reset` simply clears the matrix of LEDs and the deque for the Snake.

`generateFood` you will need to write yourself. You can make use of Arduino’s `random` function to generate a random position. Be sure that is within bounds and that it is not spawned on top of the snake!

`logic` you will also write yourself and is where the most thought will be required. You will need to determine based on the direction you request from the `InputHandler` how the snake moves, if it will live or die, and how to update the snake data structure. Notice that instead of moving every LED forward by one based on direction, you only need to move the head forward by one and delete the tail!

After this is done, all the code you need to run the game in the `.ino` file should be the following:

``````void loop()
{
snake.start();