-
-
Save JeffersGlass/ba9f6cd684c054a2195c3065b72487f9 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "pitches.h" | |
//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 = 5; | |
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 = 4; //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]}; | |
////////////////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}}; | |
//A 1D Array for example: | |
//int numbers[] = {0, 1, 2, 3, 4, 5, 6 ,7, 8, 9}; | |
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; | |
///////Music Data////// | |
#define EN 100 //length of an eighth-note in MS | |
#define GAP 5 //How long a small-gap to leave between identical notes | |
#define SILENCE 30000 //frequency of 'silence' (above hearing range) | |
//C Major Scale | |
//const int toneList[] = {262, EN, 294, EN, 330, EN, 349, EN, 392, EN, 440, EN, 494, EN, 523, EN}; | |
//Tetris | |
const int toneList[] = { | |
NOTE_E4, EN*2, NOTE_B3, EN, NOTE_C4, EN, NOTE_D4, EN*2, NOTE_C4, EN, NOTE_B3, EN, | |
NOTE_A3, EN*2-GAP, SILENCE, GAP, NOTE_A3, EN, NOTE_C4, EN, NOTE_E4, EN*2, NOTE_D4, EN, NOTE_C4, EN, | |
NOTE_B3, EN*3, NOTE_C4, EN, NOTE_D4, EN*2, NOTE_E4, EN*2, | |
NOTE_C4, EN*2, NOTE_A3, EN*2-GAP, SILENCE, GAP, NOTE_A3, EN*2, SILENCE, EN*2, | |
SILENCE, EN, NOTE_D4, EN*2, NOTE_F4, EN, NOTE_A4, EN*2, NOTE_G4, EN, NOTE_F4, EN, | |
NOTE_E4, EN*3, NOTE_C4, EN, NOTE_E4, EN*2, NOTE_D4, EN, NOTE_C4, EN, | |
NOTE_B3, EN*3, NOTE_C4, EN, NOTE_D4, EN*2, NOTE_E4, EN*2, | |
NOTE_C4, EN*2, NOTE_A3, EN*2-GAP, SILENCE, GAP, NOTE_A3, EN*2, SILENCE, EN*2, | |
NOTE_E4, EN*4, NOTE_C4, EN*4, NOTE_D4, EN*4, NOTE_B3, EN*4, | |
NOTE_C4, EN*4, NOTE_A3, EN*4, NOTE_GS3, EN*4, NOTE_B3, EN*3, SILENCE, EN, | |
NOTE_E4, EN*4, NOTE_C4, EN*4, NOTE_D4, EN*4, NOTE_B3, EN*4, | |
NOTE_C4, EN*2, NOTE_E4, EN*2, NOTE_A4, EN*4, NOTE_GS4, EN*4, SILENCE, EN*4 | |
}; | |
const int tonepin = PORTB1; | |
volatile int toneIndex = 0; | |
volatile long timePeriods = 0; | |
int maxTone; | |
void setup() { | |
//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)); | |
//Music Setup | |
maxTone = sizeof(toneList)/sizeof(toneList[1])-1; | |
DDRB |= _BV(tonepin); //Set up tone pin as output | |
//GTCCR |= _BV(PSRASY); | |
TCCR2A = 0; //_BV(COM2A0); //Mode will be normal, WGM12 is in B register | |
TCCR2B = _BV(CS22); //Set up /64 prescaler | |
TIMSK2 = _BV(TOIE2); //Enable overflow interrupt | |
TCCR1A = _BV(COM1A0); //Mode will be CTC; WGM12 is in B register | |
TCCR1B = _BV(WGM12) | _BV(CS11); //Set up x8 prescaler | |
OCR1A = int(1000000/ (long) toneList[0]); | |
TIMSK1 = _BV(OCIE1A); //Enable compare match interrupt | |
interrupts(); //Enable interrupts globally | |
//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(); | |
checkMusicNote(); | |
} | |
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 setNewDirection(int newDir){ | |
dir = newDir; | |
snakeDirection[0] = dirCoords[newDir][0]; | |
snakeDirection[1] = dirCoords[newDir][1]; | |
} | |
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]}; | |
if (isValidPosition(tempPosition[0],tempPosition[1])){ | |
//Check if the snake is intersecting itself: | |
if (board[tempPosition[0]][tempPosition[1]] != 0){ | |
//check that this isn't a false alarm due to food | |
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]){ | |
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(); | |
} | |
//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 = true; | |
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 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(); | |
} | |
//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 (isValidPosition(x, y) && board[x][y] != 0); | |
foodPosition[0] = x; | |
foodPosition[1] = y; | |
} | |
//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; | |
if (x == 4 && y == 4) valid = false; | |
if (x == 4 && y == 5) valid = false; | |
return valid; | |
} | |
//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; | |
} | |
} | |
} | |
//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; | |
} | |
} | |
} | |
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); | |
} | |
//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]; | |
} | |
} | |
} | |
ISR(TIMER1_COMPA_vect){ //Timer1 Comp Interrupt | |
PORTB^= _BV(tonepin); | |
} | |
void checkMusicNote(){ | |
if (timePeriods > (toneList[toneIndex+1] * long(2))){ | |
timePeriods = 0; | |
toneIndex += 2; | |
if (toneIndex > maxTone) toneIndex = 0; | |
OCR1A = int(1000000/ (long) toneList[toneIndex]); //16 MHz / (8x prescaler * 2) | |
} | |
} | |
ISR(TIMER2_OVF_vect){ | |
timePeriods++; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment