Skip to content

Instantly share code, notes, and snippets.

@JeffersGlass
Created April 26, 2020 17:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JeffersGlass/747dac815e41b2adedc32973bf979031 to your computer and use it in GitHub Desktop.
Save JeffersGlass/747dac815e41b2adedc32973bf979031 to your computer and use it in GitHub Desktop.
////////////// Input/Output Data/////////
//If this is true, we'll write some debug values to the Serial port
bool PRINT = true;
//The three pins connected to their respective ports
//on the shift registers
int CLK_PIN = 10;
int DATA_PIN = 11;
int LATCH_PIN = 12;
//Button order is up, right, down, left
int BUTTON_UP_PIN = 9;
int BUTTON_RIGHT_PIN = 8;
int BUTTON_DOWN_PIN = 7;
int BUTTON_LEFT_PIN = 6;
int numButtons = 4;
int buttons[] = {BUTTON_UP_PIN, BUTTON_RIGHT_PIN, BUTTON_DOWN_PIN, BUTTON_LEFT_PIN};
///////////////////////Game Data/////////
long gameUpdatePeriod = 400000; //Microseconds between updating lines of display
long lastGameUpdateTime = 0; //Stores the last time we updated the display
int snakeLength = 3; //Starting length of the snake
int snakeHeadPosition[] = {0,0}; //Stores where the snake's "head" is, (x,y)
int foodPosition[] = {0,0}; //Stores where the "food" is, (x,y)
long foodUpdatePeriod = 100000; //Microseconds between flashing the food on and off
long lastFoodUpdateTime = 0; //Stores the last time we updated the flashing food
int foodState = 0; //Should the food dot be on or off? 0 = off, 1 = on
//dir in the index of the direction that the snake is currently travelling
//0 = UP, 1 = RIGHT, 2 = DOWN, 3 = LEFT
//By store this as a numerical value with the directions offset from their
//opposite by 2, we can do some clever numerical tricks later
int dir = 0;
//For each direction that we're travelling, how much should the x and y
//values change for each game step?
int dirCoords[4][2] = {{0,-1}, {1,0}, {0,1}, {-1,0}};
//The current change in the snake's head's X and Y coordinates with
//each game step
int snakeDirection[2] = {dirCoords[0][0], dirCoords[0][1]};
//How much food have we eaten?
int score = 0;
////////////////GameState Data////////////
//We'll use this to tell the game to display a frowny face when we lose
bool gameOver = false;
///////////////////////Display Data//////////
//If a given row is to be "turned on" (in our case, pulled to ground),
//which bit in the shift registers does that correspond to?
int ROW0 = 9;
int ROW1 = 14;
int ROW2 = 8;
int ROW3 = 12;
int ROW4 = 1;
int ROW5 = 7;
int ROW6 = 2;
int ROW7 = 5;
const int NUMROWS = 8;
int rows[] = {ROW0, ROW1, ROW2, ROW3, ROW4, ROW5, ROW6, ROW7};
//If a given column is to be "turned on" (in our case, pulled high
//through a resistor), hich bit in the shift registers
//does that correspond to?
int COL0 = 13;
int COL1 = 3;
int COL2 = 4;
int COL3 = 10;
int COL4 = 6;
int COL5 = 11;
int COL6 = 15;
int COL7 = 16;
const int NUMCOLS = 8;
int cols[] = {COL0, COL1, COL2, COL3, COL4, COL5, COL6, COL7};
//If all of the rows and columns were off, what would the bits in the shift
//registers look like? We could build this data from the row/column data
//above; I have built it here manually
int allOff = (B11001011 * 256) + B10010100;
//The 2D array that will hold our board data. Starts as all 0's.
int board[NUMROWS][NUMCOLS] = { {0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0}};
long displayUpdatePeriod = 500; //Microseconds between updating lines of display
long lastDisplayUpdateTime = 0; //Last time (from micros()) that we updated the display
//We'll step through the display multiplexing row-by-row - this is the row we're currently on
int currentRow = 0;
void setup() {
//If we need to debug things via the Serial console, start it up here.
if (PRINT) Serial.begin(115200);
//Configure out shift-register outputs
pinMode(CLK_PIN, OUTPUT);
pinMode(DATA_PIN, OUTPUT);
pinMode(LATCH_PIN, OUTPUT);;
//Configure our button inputs
for (int i = 0; i < numButtons; i++){
pinMode(buttons[i], INPUT_PULLUP);
}
//Make sure that random really is random
randomSeed(analogRead(A0));
//Set up the starting position for the snake and food:
initializeGameplay();
}
void loop() {
//Check input - have any buttons been pressed? Update variables to account for what buttons have been pressed.
//check gamestate - has enough time passed for us to do a "tick" of gamestate? If so update the appropriate variables
// and the board display
//check displayUpdate - has enough time passed to need to update the display? If so, take the appropriate actions
// to update the state of the board
checkInput();
checkGamestate();
displayUpdate();
}
void checkInput(){
//length should match
bool buttonState[] = {false, false, false, false};
for (int i = 0; i < 4; i++){ //read direction buttons
if (digitalRead(buttons[i]) == LOW) buttonState[i] = true; //is any individual button being pressed?
}
//After we check the button states, if tempDir hasn't changed, we'll know no button was pressed
int tempDir = -1;
if (dir % 2 == 0){ //travelling up or down
if (buttonState[1]) tempDir = 1;
else if (buttonState[3]) tempDir = 3;
}
else{
if (buttonState[0]) tempDir = 0;
else if (buttonState[2]) tempDir = 2;
}
//If tempDir has changed, we'll need to handle the hange in direction.
if (tempDir != -1){
//Create the potential new position:
int tempPosition[] = {snakeHeadPosition[0] + dirCoords[tempDir][0], snakeHeadPosition[1] + dirCoords[tempDir][1]};
//If the next position is within the board, set the new direction to be the one the user selected
if (isValidPosition(tempPosition[0], tempPosition[1])) setNewDirection(tempDir);
}
}
void checkGamestate(){
if (!gameOver){
if (micros() > lastGameUpdateTime + gameUpdatePeriod){
//Create a temporary position for where the snake would be next:
int tempPosition[] = {snakeHeadPosition[0] + snakeDirection[0], snakeHeadPosition[1] + snakeDirection[1]};
//don't let the snake move off the screen
if (isValidPosition(tempPosition[0],tempPosition[1])){
//Check if the snake is intersecting itself:
if (board[tempPosition[0]][tempPosition[1]] != 0){
if (!(tempPosition[0] == foodPosition[0] && tempPosition[1] == foodPosition[1])){
gameOver = true;
return;
}
}
//Move the snake's using the snakeDirection vector
snakeHeadPosition[0] += snakeDirection[0];
snakeHeadPosition[1] += snakeDirection[1];
//Subtract 1 from every board position. This mean's the snake's "tail" will go from 1 to zero
//and will dissapear.
subtractBoard();
//Set the value of underneath the snake's head to the current length of the snake
board[snakeHeadPosition[0]][snakeHeadPosition[1]] = snakeLength;
//Check if the snake's head is over the food
if (snakeHeadPosition[0] == foodPosition[0] && snakeHeadPosition[1] == foodPosition[1]){
score++;
snakeLength++;
//Add one to every snake segment on the board. This will make them last one
//game-tick longer and have the effect of stretching the snake
addBoard();
makeFood();
}
}
else{
//Should we do something if the snake tries to go off the board??
}
lastGameUpdateTime = micros();
printBoard();
}
//Do we need to update the food-flicker effect?
if (micros() > lastFoodUpdateTime + foodUpdatePeriod){
foodState = 1-foodState; //0 becomes 1, 1 becomes 0
board[foodPosition[0]][foodPosition[1]] = foodState;
lastFoodUpdateTime = micros();
}
}
else { //gameOver
showGameOver();
}
}
void displayUpdate(){
//Check whether we need to update the display
if (micros() > lastDisplayUpdateTime + displayUpdatePeriod){
currentRow = (currentRow + 1) % NUMROWS; //Advance to the next row
displayRow(currentRow); //Display the current row
lastDisplayUpdateTime = micros();
}
}
void displayRow(int rowNum){
//Start with everything turned off
int value = allOff;
//Turn on the current row (in our case, by sending it low/0, so we need to clear a bit)
bitClear(value, 15-(rows[rowNum] - 1));
//For each dot in this row, if the associated row-value is non-zero,
//Turn the associated column-bit on(in our case, by sending it high/1, so we used bitSet)
for (int i = 0; i < NUMCOLS; i++){
if (board[rowNum][i] > 0){
bitSet(value, 15-(cols[i] - 1));
}
}
//output the generated data to the shiftRegisters
sendIntToDisplay(value);
}
void sendIntToDisplay(int input){
//Make sure clock and latch are low before we start, since they
//Trigger on a rising-edge
digitalWrite(CLK_PIN, LOW);
digitalWrite(LATCH_PIN, LOW);
//Clock out the data
shiftOut(DATA_PIN, CLK_PIN, LSBFIRST, lowByte(input));
shiftOut(DATA_PIN, CLK_PIN, LSBFIRST, highByte(input));
//Latch the data in
digitalWrite(LATCH_PIN, HIGH);
}
void initializeGameplay(){
resetBoard(); //sets board to all zeros
//Tracks whether we've encountered any issues with the board
//we've generated
bool isProblem;
do{
isProblem = false;
//The snake's head will start in a random place
snakeHeadPosition[0] = random(0, NUMROWS);
snakeHeadPosition[1] = random(0, NUMCOLS);
board[snakeHeadPosition[0]][snakeHeadPosition[1]] = snakeLength;
//Snake will start moving in a random direction
//{0, -1}, {1, 0}, {0, 1}, {-1, 0} from dirCoords array above
dir = random(0,4);
snakeDirection[0] = dirCoords[dir][0];
snakeDirection[1] = dirCoords[dir][1];
//create a dummy position to experiment with
int tempPosition[] = {snakeHeadPosition[0], snakeHeadPosition[1]};
//We don't want the snake to start with its head pressed against a wall,
//So if moving one step takes us off the board, we declare this board a problem
if (!isValidPosition(tempPosition[0]+snakeDirection[0], tempPosition[1]+snakeDirection[1])){
isProblem = true;
resetBoard();
}
//For however long the starting snake is, take that many steps
//in the directioon **opposite** the snake's movement
//If we're outside of the boundaries of the board, we have a problem
//and need to try again. Otherwise, write reduce the starting snake-value
//by one and repeat until we've either failed or written the
//whole snake
for (int i = snakeLength - 1; i > 0; i--){
tempPosition[0] -= snakeDirection[0];
tempPosition[1] -= snakeDirection[1];
if (!isValidPosition(tempPosition[0], tempPosition[1])){
isProblem = true;
resetBoard();
}
else{
board[tempPosition[0]][tempPosition[1]] = i;
}
}
}
while(isProblem);
//Pick a starting spot for the food
makeFood();
}
//Updates the direction index, as well as the x and y values
//of the move vetor
void setNewDirection(int newDir){
dir = newDir;
snakeDirection[0] = dirCoords[newDir][0];
snakeDirection[1] = dirCoords[newDir][1];
}
//Makes the board entirely empty
void resetBoard(){
for (int i = 0; i < NUMROWS; i++){
for (int j = 0; j < NUMCOLS; j++){
board[i][j] = 0;
}
}
}
//Subtract 1 from every non-zero, non-food position on the board
//Used to move the snake
void subtractBoard(){
for (int i = 0; i < NUMROWS; i++){
for (int j = 0; j < NUMCOLS; j++){
if (board[i][j] > 0) board[i][j] -= 1; // && !(i == foodPosition[0] && j == foodPosition[1])) board[i][j] -= 1;
}
}
}
//Adds 1 to every non-zero, non-food position on the board
//Used the lengthen the snake
void addBoard(){
for (int i = 0; i < NUMROWS; i++){
for (int j = 0; j < NUMCOLS; j++){
if (board[i][j] > 0) board[i][j] += 1; // && !(i == foodPosition[0] && j == foodPosition[1])) board[i][j] -= 1;
}
}
}
//Checks whether the given x and y position is within the boundaries of the game board
bool isValidPosition(int x, int y){
bool valid = true;
if (x < 0 || x >= NUMROWS) valid = false;
if (y < 0 || y >= NUMROWS) valid = false;
return valid;
}
//Picks a new empty location on the board for the food to be
void makeFood(){
int x;
int y;
do{
x = random(0, NUMROWS);
y = random(0, NUMCOLS);
} while (board[x][y] != 0);
foodPosition[0] = x;
foodPosition[1] = y;
}
//For debugging purposes. Prints out the index of the direction,
//the X and Y directions that the snake will move in,
//And the board itself
void printBoard(){
if (PRINT){
Serial.print("Dir: ");
Serial.print(dir);
Serial.print("\tDirX: ");
Serial.print(snakeDirection[0]);
Serial.print("\tDirY: ");
Serial.println(snakeDirection[1]);
for (int i = 0; i < NUMROWS; i++){
for (int j = 0; j < NUMCOLS; j++){
Serial.print(board[i][j]);
}
Serial.println("");
}
Serial.println("");
}
}
//This is the board that will show when the player looses
int gameOverBoard[NUMROWS][NUMCOLS] = { {0,0,1,0,0,1,0,0},
{0,0,1,0,0,1,0,0},
{0,0,1,0,0,1,0,0},
{0,0,1,0,0,1,0,0},
{0,0,0,0,0,0,0,0},
{0,0,1,1,1,1,0,0},
{0,1,0,0,0,0,1,0},
{0,1,0,0,0,0,1,0}};
void showGameOver(){
for (int i = 0; i < NUMROWS; i++){
for (int j = 0; j < NUMCOLS; j++){
//Just copy the image above into the board data
board[i][j] = gameOverBoard[i][j];
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment