Add the 74HC595 Snake game project
This commit is contained in:
380
08-74HC595-Snake/08-74HC595-Snake.ino
Normal file
380
08-74HC595-Snake/08-74HC595-Snake.ino
Normal file
@@ -0,0 +1,380 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "controls.h"
|
||||
#include "error.h"
|
||||
#include "IC74HC595.h"
|
||||
#include "7Segment.h"
|
||||
#include "matrix.h"
|
||||
|
||||
#define PIN_BUZZER 21
|
||||
|
||||
#define PWM_FREQUENCY 1000
|
||||
#define PWM_BITWIDTH 12
|
||||
#define PWM_BUZZER_CHANNEL 0
|
||||
|
||||
#define ENTITY_ALIVE 1 << 0
|
||||
#define ENTITY_FOOD 1 << 1
|
||||
#define ENTITY_SNAKE 1 << 2
|
||||
|
||||
#define ENTITY_HIGH_MASK 248 // 11111000
|
||||
#define ENTITY_LOW_MASK 7 // 00000111
|
||||
|
||||
#define ENTITY_SHIFT_LIFETIME 3
|
||||
|
||||
int8_t player_x;
|
||||
int8_t player_y;
|
||||
int8_t delta_x;
|
||||
int8_t delta_y;
|
||||
|
||||
// Entities represent a lit pixel on the LED matrix. Each entity is an 8 bit mask that consists of:
|
||||
//
|
||||
// 8 0
|
||||
// 0 0 0 0 0 0 0 0
|
||||
// ^ ^ ^ ^ ^
|
||||
// | | | | `-- Alive or dead. When 1, display this entity.
|
||||
// | | | `---- Food (1)
|
||||
// | | `------ Snake (1)
|
||||
// | |
|
||||
// `-------+-------- Lifetime. How many game cycles should this entity remain for? 0-248.
|
||||
// The lifetime gets counted down each cycle!
|
||||
|
||||
#define GAMESTATE_SCREENCAL 1
|
||||
#define GAMESTATE_PLAY 2
|
||||
#define GAMESTATE_LOSE 3
|
||||
#define GAMESTATE_WIN 4
|
||||
|
||||
uint8_t entities[64] = {0};
|
||||
int found_food = 0;
|
||||
int8_t tail_length = 2;
|
||||
uint32_t gamespeed;
|
||||
uint32_t lastupdate;
|
||||
uint32_t difficulty = 0;
|
||||
uint8_t score = 0;
|
||||
uint8_t open_indexes[64] = {0};
|
||||
|
||||
Button btn_left = {PIN_BUTTON_LEFT, 0, LOW, BUTTON_STABLETIME, 0};
|
||||
Button btn_up = {PIN_BUTTON_UP, 0, LOW, BUTTON_STABLETIME, 0};
|
||||
Button btn_down = {PIN_BUTTON_DOWN, 0, LOW, BUTTON_STABLETIME, 0};
|
||||
Button btn_right = {PIN_BUTTON_RIGHT, 0, LOW, BUTTON_STABLETIME, 0};
|
||||
|
||||
uint8_t gamestate = 0;
|
||||
|
||||
void initSerial()
|
||||
{
|
||||
Serial.begin(115200);
|
||||
Serial.printf("\nSERIAL READY\n");
|
||||
}
|
||||
|
||||
void initBuzzer() {
|
||||
ledcAttachChannel(PIN_BUZZER, 1000, 10, PWM_BUZZER_CHANNEL);
|
||||
ledcWriteTone(PIN_BUZZER, 2000);
|
||||
delay(1000);
|
||||
ledcWriteTone(PIN_BUZZER, 0);
|
||||
}
|
||||
|
||||
void move_player()
|
||||
{
|
||||
player_x += delta_x;
|
||||
player_y += delta_y;
|
||||
if ( player_x > 7) {
|
||||
player_x = 0;
|
||||
} else if ( player_x < 0 ) {
|
||||
player_x = 7;
|
||||
}
|
||||
if ( player_y > 7 ) {
|
||||
player_y = 0;
|
||||
} else if ( player_y < 0 ) {
|
||||
player_y = 7;
|
||||
}
|
||||
}
|
||||
|
||||
void collide_player()
|
||||
{
|
||||
uint8_t entity;
|
||||
entity = entities[(player_x * 8) + player_y];
|
||||
// Is there an entity where we're going?
|
||||
if ( entity > 0 ) {
|
||||
// We die if we hit our own body
|
||||
if ( (entity & ENTITY_SNAKE) == ENTITY_SNAKE ) {
|
||||
ledcWriteTone(PIN_BUZZER, 2000);
|
||||
delay(1000);
|
||||
ledcWriteTone(PIN_BUZZER, 0);
|
||||
init_game();
|
||||
} else if ( (entity & ENTITY_FOOD) == ENTITY_FOOD ) {
|
||||
found_food = 1;
|
||||
score += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void draw_entities()
|
||||
{
|
||||
uint8_t entity;
|
||||
resetMatrix();
|
||||
if ( gamestate == GAMESTATE_SCREENCAL ) {
|
||||
for ( int i = 0 ; i < 64 ; i++ ) {
|
||||
if ( pokeMatrixPixel((i / 8), (i % 8)) != ERRNO_SUCCESS ) {
|
||||
Serial.printf("Failed to poke pixel : %d\n", i, errno);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Poke matrix pixels for every living entity
|
||||
for ( int i = 0 ; i < 64; i++ ) {
|
||||
entity = entities[i];
|
||||
if ( ( entity & ENTITY_ALIVE ) == ENTITY_ALIVE ) {
|
||||
if ( pokeMatrixPixel((i / 8), (i % 8)) != ERRNO_SUCCESS ) {
|
||||
Serial.printf("Failed to poke pixel for entity %d : %d\n", i, errno);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
displayMatrix();
|
||||
}
|
||||
|
||||
void update_matrix_entities()
|
||||
{
|
||||
uint8_t entity;
|
||||
int8_t elife = 0;
|
||||
// Poke matrix pixels for every living entity
|
||||
for ( int i = 0 ; i < 64; i++ ) {
|
||||
entity = entities[i];
|
||||
if ( (entity & ENTITY_ALIVE) == ENTITY_ALIVE && i != ((player_x * 8) + player_y) ) {
|
||||
// Snake parts age (except the head)
|
||||
if ( (entity & ENTITY_SNAKE) == ENTITY_SNAKE ) {
|
||||
elife = (entity & ENTITY_HIGH_MASK) >> ENTITY_SHIFT_LIFETIME;
|
||||
if ( found_food == 0 ) {
|
||||
// Count down their lifetime
|
||||
if ( elife == 0 ) {
|
||||
entity = 0;
|
||||
} else {
|
||||
elife -= 1;
|
||||
entity = (elife << ENTITY_SHIFT_LIFETIME) | (entity & ENTITY_LOW_MASK);
|
||||
}
|
||||
entities[i] = entity;
|
||||
} /*else {
|
||||
elife += 1;
|
||||
entity = (elife << ENTITY_SHIFT_LIFETIME) | (entity & ENTITY_LOW_MASK);
|
||||
entities[i] = entity;
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void place_random_entity(int8_t eflags, int8_t set_player)
|
||||
{
|
||||
uint8_t index = 0;
|
||||
uint8_t entity = 0;
|
||||
uint8_t open_positions = 0;
|
||||
|
||||
memset((void *)&open_indexes, 0x00, 64);
|
||||
|
||||
// Count how many positions are open on the board
|
||||
for ( int i = 0 ; i < 64; i++ ) {
|
||||
entity = entities[i];
|
||||
if ( entity == 0) {
|
||||
open_indexes[open_positions] = i;
|
||||
open_positions += 1;
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
if ( open_positions == 0 ) {
|
||||
// This is a win condition!
|
||||
gamestate = GAMESTATE_WIN;
|
||||
delta_x = 0;
|
||||
delta_y = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
index = random(0, open_positions-1);
|
||||
if ( index > 63 ) {
|
||||
Serial.printf("Index out of bounds!\n");
|
||||
return;
|
||||
}
|
||||
index = open_indexes[index];
|
||||
|
||||
// Place a random piece of food on the board
|
||||
entities[index] = (eflags);
|
||||
|
||||
if ( set_player == 1 ) {
|
||||
player_x = (index / 8);
|
||||
player_y = (index % 8);
|
||||
}
|
||||
}
|
||||
|
||||
void init_game()
|
||||
{
|
||||
tail_length = 2;
|
||||
memset((void *)&entities, 0x00, 64);
|
||||
gamespeed = 1000;
|
||||
lastupdate = millis();
|
||||
score = 0;
|
||||
delta_x = 0;
|
||||
delta_y = 0;
|
||||
|
||||
place_random_entity(ENTITY_HIGH_MASK | ENTITY_ALIVE | ENTITY_FOOD, 0);
|
||||
|
||||
// Place the player away from the food
|
||||
place_random_entity(((tail_length << ENTITY_SHIFT_LIFETIME) | ENTITY_ALIVE | ENTITY_SNAKE), 1);
|
||||
}
|
||||
|
||||
void read_controls()
|
||||
{
|
||||
difficulty = readDifficulty();
|
||||
if ( readButton(&btn_up) != ERRNO_SUCCESS ||
|
||||
readButton(&btn_left) != ERRNO_SUCCESS ||
|
||||
readButton(&btn_right) != ERRNO_SUCCESS ||
|
||||
readButton(&btn_down) != ERRNO_SUCCESS ) {
|
||||
Serial.printf("Failed to read buttons\n");
|
||||
return;
|
||||
}
|
||||
if ( gamestate == GAMESTATE_SCREENCAL ) {
|
||||
if ( btn_up.state + btn_down.state + btn_left.state + btn_right.state != 0 ) {
|
||||
init_game();
|
||||
gamestate = GAMESTATE_PLAY;
|
||||
}
|
||||
} else {
|
||||
// We don't let the player reverse direction back on themselves.
|
||||
if ( (delta_x == 0) && (btn_up.state & BUTTON_STATE_DOWN) == BUTTON_STATE_DOWN ) {
|
||||
delta_x = 1;
|
||||
delta_y = 0;
|
||||
}
|
||||
if ( (delta_x == 0) && (btn_down.state & BUTTON_STATE_DOWN) == BUTTON_STATE_DOWN ) {
|
||||
delta_x = -1;
|
||||
delta_y = 0;
|
||||
}
|
||||
if ( (delta_y == 0) && (btn_right.state & BUTTON_STATE_DOWN) == BUTTON_STATE_DOWN ) {
|
||||
delta_y = -1;
|
||||
delta_x = 0;
|
||||
}
|
||||
if ( (delta_y == 0) && (btn_left.state & BUTTON_STATE_DOWN) == BUTTON_STATE_DOWN ) {
|
||||
delta_y = 1;
|
||||
delta_x = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void display_score()
|
||||
{
|
||||
uint8_t integerValues[] = {
|
||||
CHAR_7SEG_0,
|
||||
CHAR_7SEG_1,
|
||||
CHAR_7SEG_2,
|
||||
CHAR_7SEG_3,
|
||||
CHAR_7SEG_4,
|
||||
CHAR_7SEG_5,
|
||||
CHAR_7SEG_6,
|
||||
CHAR_7SEG_7,
|
||||
CHAR_7SEG_8,
|
||||
CHAR_7SEG_9,
|
||||
};
|
||||
uint8_t score_cur = 0;
|
||||
int rc = 0;
|
||||
|
||||
if ( gamestate == GAMESTATE_SCREENCAL ) {
|
||||
select7SegDisplay(PIN_7SEGMENT_DIGIT4);
|
||||
rc = writeIC74HC595Char(&scoreboard, CHAR_7SEG_C, MSBFIRST);
|
||||
rc += writeIC74HC595Char(&scoreboard, CHAR_7SEG_OFF, MSBFIRST);
|
||||
select7SegDisplay(PIN_7SEGMENT_DIGIT3);
|
||||
rc += writeIC74HC595Char(&scoreboard, CHAR_7SEG_A, MSBFIRST);
|
||||
rc += writeIC74HC595Char(&scoreboard, CHAR_7SEG_OFF, MSBFIRST);
|
||||
select7SegDisplay(PIN_7SEGMENT_DIGIT2);
|
||||
rc += writeIC74HC595Char(&scoreboard, CHAR_7SEG_L, MSBFIRST);
|
||||
rc += writeIC74HC595Char(&scoreboard, CHAR_7SEG_OFF, MSBFIRST);
|
||||
if ( rc != ERRNO_SUCCESS ) {
|
||||
Serial.printf("Failed to write scoreboard: %d", errno);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ( difficulty > 0 ) {
|
||||
gamespeed = 1000 / difficulty;
|
||||
} else {
|
||||
gamespeed = 1000;
|
||||
}
|
||||
score_cur = (score % 10);
|
||||
select7SegDisplay(PIN_7SEGMENT_DIGIT1);
|
||||
rc = writeIC74HC595Char(&scoreboard, integerValues[score_cur], MSBFIRST);
|
||||
rc += writeIC74HC595Char(&scoreboard, CHAR_7SEG_OFF, MSBFIRST);
|
||||
if ( rc != ERRNO_SUCCESS ) {
|
||||
Serial.printf("Failed to write scoreboard: %d", errno);
|
||||
return;
|
||||
}
|
||||
|
||||
score_cur = (score / 10);
|
||||
select7SegDisplay(PIN_7SEGMENT_DIGIT2);
|
||||
rc = writeIC74HC595Char(&scoreboard, integerValues[score_cur], MSBFIRST);
|
||||
rc += writeIC74HC595Char(&scoreboard, CHAR_7SEG_OFF, MSBFIRST);
|
||||
if ( rc != ERRNO_SUCCESS ) {
|
||||
Serial.printf("Failed to write scoreboard: %d", errno);
|
||||
return;
|
||||
}
|
||||
|
||||
select7SegDisplay(PIN_7SEGMENT_DIGIT4);
|
||||
rc = writeIC74HC595Char(&scoreboard, integerValues[difficulty], MSBFIRST);
|
||||
rc += writeIC74HC595Char(&scoreboard, CHAR_7SEG_OFF, MSBFIRST);
|
||||
if ( rc != ERRNO_SUCCESS ) {
|
||||
Serial.printf("Failed to write scoreboard: %d", errno);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void gamelogic()
|
||||
{
|
||||
uint32_t updatetime = millis();
|
||||
found_food = 0;
|
||||
Serial.printf("Score is %d\n", score);
|
||||
if ( (updatetime - lastupdate) > gamespeed ) {
|
||||
lastupdate = updatetime;
|
||||
// Skip all of this if we have no delta (the player isn't playing yet)
|
||||
if ( (delta_x + delta_y) != 0 ) {
|
||||
move_player();
|
||||
collide_player();
|
||||
// Add a snake entity to the new location
|
||||
if ( found_food == 1 ) {
|
||||
tail_length += 1;
|
||||
place_random_entity(ENTITY_HIGH_MASK | ENTITY_ALIVE | ENTITY_FOOD, 0);
|
||||
}
|
||||
entities[(player_x * 8) + player_y] = ((tail_length << ENTITY_SHIFT_LIFETIME) | ENTITY_ALIVE | ENTITY_SNAKE );
|
||||
}
|
||||
update_matrix_entities();
|
||||
found_food = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void setup() {
|
||||
initSerial();
|
||||
score = 0;
|
||||
if ( init74HC595(&matrixcols) != ERRNO_SUCCESS ) {
|
||||
Serial.printf("failed to initialize matrix series 74HC595");
|
||||
}
|
||||
if ( initButton(&btn_up) != ERRNO_SUCCESS ||
|
||||
initButton(&btn_left) != ERRNO_SUCCESS ||
|
||||
initButton(&btn_right) != ERRNO_SUCCESS ||
|
||||
initButton(&btn_down) != ERRNO_SUCCESS ) {
|
||||
Serial.printf("Failed to initialize buttons\n");
|
||||
}
|
||||
if ( init74HC595(&scoreboard) != ERRNO_SUCCESS ) {
|
||||
Serial.printf("Failed to initialize scoreboard 74HC595");
|
||||
}
|
||||
if ( init7Segment() != ERRNO_SUCCESS ) {
|
||||
Serial.printf("Failed to initialize 7 segment display");
|
||||
}
|
||||
initBuzzer();
|
||||
init_game();
|
||||
gamestate = GAMESTATE_SCREENCAL;
|
||||
|
||||
Serial.printf("\nSETUP READY\n");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
int rc = 0;
|
||||
read_controls();
|
||||
display_score();
|
||||
gamelogic();
|
||||
draw_entities();
|
||||
}
|
||||
Reference in New Issue
Block a user