Snake works! Scoreboard is fucky.

This commit is contained in:
2026-06-16 22:12:30 -04:00
parent 8303ea9d10
commit 414f0684ab
15 changed files with 648 additions and 325 deletions

View File

@@ -1,74 +1,348 @@
#include <Arduino.h>
#include "controls.h"
#include "error.h"
/*
Need 11 GPIO pins
- 74HC95 for the 8 segment
- 74HC595 for the rows of the 8x8
- 74HC9 for the cols of the 8x8
- 1x for the Joystick button
- 1x for the buzzer
Need 3 ADC pins
- Joy X
- Joy Y
- Difficulty potentiometer
*/
// Pins 1 and 2 are on ADC channel 0 and 1.
#define PIN_JOY_X 1
#define PIN_JOY_Y 2
// Pin 47 is GPIO only
#define PIN_JOY_Z 47
// We save 4 pins by updating all 3 shift registers at once
// with one clock and one update signal. Each shift register
// only needs its own data line.
#define PIN_74HC595_CLOCK 11
#define PIN_74HC595_UPDATE 12
#define PIN_74HC595_LEDROWS_DATA 18
#define PIN_74HC595_LEDCOLS_DATA 13
#define PIN_74HC595_SCOREBOARD_DATA 14
#include "IC74HC595.h"
#include "7Segment.h"
#include "matrix.h"
#define PIN_BUZZER 21
#define PIN_DIFFICULTY 9
#define PWM_FREQUENCY 1000
#define PWM_BITWIDTH 12
#define PWM_BUZZER_CHANNEL 0
Joystick js;
#define ENTITY_ALIVE 1 << 0
#define ENTITY_FOOD 1 << 1
#define ENTITY_SNAKE 1 << 2
#define ENTITY_HIGH_MASK 240 // 11110000
#define ENTITY_LOW_MASK 15 // 00001111
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)
// | | `-------- Reserved
// `-----+---------- Lifetime. How many game cycles should this entity remain for? 0-128.
// The lifetime gets counted down each cycle!
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};
void initSerial()
{
Serial.begin(115200);
Serial.printf("\nSERIAL READY\n");
}
void setup() {
memset(&js, 0x00, sizeof(Joystick));
void initBuzzer() {
ledcAttachChannel(PIN_BUZZER, 1000, 10, PWM_BUZZER_CHANNEL);
ledcWriteTone(PIN_BUZZER, 2000);
delay(1000);
ledcWriteTone(PIN_BUZZER, 0);
}
initSerial();
if ( initJoystick(&js, PIN_JOY_X, PIN_JOY_Y, PIN_JOY_Z) != ERRNO_SUCCESS ) {
Serial.printf("Failed to initialized Joystick datastructure : %d\n", errno);
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 ) {
Serial.printf("Hit entity %d (%d) and died!", (player_x * 8) + player_y, entity);
ledcWriteTone(PIN_BUZZER, 2000);
delay(1000);
init_game();
} else if ( (entity & ENTITY_FOOD) == ENTITY_FOOD ) {
Serial.printf("Found food!\n");
found_food = 1;
}
}
}
void draw_entities()
{
uint8_t entity;
resetMatrix();
// 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) >> 4;
Serial.printf("Entity %d (%d) has lifetime %d\n", i, entity, elife);
if ( found_food == 0 ) {
// Count down their lifetime
if ( elife == 0 ) {
Serial.printf("Entity %d died\n", i);
entity = 0;
} else {
elife -= 1;
entity = (elife << 4) | (entity & ENTITY_LOW_MASK);
}
entities[i] = entity;
} /*else {
elife += 1;
entity = (elife << 4) | (entity & ENTITY_LOW_MASK);
entities[i] = entity;
}*/
}
}
}
}
void wingame()
{
Serial.printf("The player won the game!\n");
}
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) {
Serial.printf("Entity %d is available (%d)\n", i, entity);
open_positions += 1;
open_indexes[open_positions] = i;
} else {
Serial.printf("Entity %d is alive (%d)\n", i, entity);
}
}
Serial.printf("There are %d positions open on the board\n", open_positions);
if ( open_positions == 0 ) {
// This is a win condition!
wingame();
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);
}
Serial.printf("Placed entity at %d (%d)\n", (index / 8) + (index % 8), entities[index]);
Serial.printf("Done\n");
}
void init_game()
{
tail_length = 2;
memset((void *)&entities, 0x00, 64);
gamespeed = 1000;
lastupdate = millis();
place_random_entity(ENTITY_HIGH_MASK | ENTITY_ALIVE | ENTITY_FOOD, 0);
// Place the player away from the food
Serial.printf("Placing player\n");
place_random_entity(((tail_length << 4) | 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;
}
// 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 ( difficulty > 0 ) {
gamespeed = 1000 / difficulty;
} else {
gamespeed = 1000;
}
score_cur = (score % 10);
select7SegDisplay(PIN_7SEGMENT_DIGIT1);
rc = writeIC74HC595Char(&scoreboard, integerValues[score_cur], 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);
if ( rc != ERRNO_SUCCESS ) {
Serial.printf("Failed to write scoreboard: %d", errno);
return;
}
select7SegDisplay(PIN_7SEGMENT_DIGIT3);
rc = writeIC74HC595Char(&scoreboard, 0x00, MSBFIRST);
if ( rc != ERRNO_SUCCESS ) {
Serial.printf("Failed to write scoreboard: %d", errno);
return;
}
select7SegDisplay(PIN_7SEGMENT_DIGIT4);
rc = writeIC74HC595Char(&scoreboard, integerValues[difficulty], MSBFIRST);
if ( rc != ERRNO_SUCCESS ) {
Serial.printf("Failed to write scoreboard: %d", errno);
return;
}
}
void gamelogic()
{
uint32_t updatetime = millis();
found_food = 0;
if ( (updatetime - lastupdate) > gamespeed ) {
Serial.printf("Updating game logic. Difficulty %d (%d elapsed / gamespeed %d). Player <%d, %d>.\n", difficulty, (updatetime - lastupdate), gamespeed, player_x, player_y);
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 << 4) | ENTITY_ALIVE | ENTITY_SNAKE );
}
update_matrix_entities();
found_food = 0;
}
}
void setup() {
initSerial();
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();
Serial.printf("\nSETUP READY\n");
}
void loop() {
if ( readButton(&js.button) != ERRNO_SUCCESS ) Serial.printf("Failed to read calibration button : %d\n", errno);
if ( js.button.state == BUTTON_STATE_DOWN && js.state != JOYSTICK_STATE_CALIBRATING ) {
js.button.state = BUTTON_STATE_HELD;
js.state = JOYSTICK_STATE_CALIBRATING;
}
if ( js.state == JOYSTICK_STATE_CALIBRATING ) {
if ( calibrateJoystick(&js) != ERRNO_SUCCESS ) {
Serial.printf("Failed to calibrate joystick : %d\n", errno);
}
}
if ( ( js.state & JOYSTICK_STATE_CALIBRATED) == JOYSTICK_STATE_CALIBRATED ) {
if ( readJoystick(&js) != ERRNO_SUCCESS ) {
Serial.printf("Failed to read joystick state : %d\n", errno);
}
}
int rc = 0;
read_controls();
display_score();
gamelogic();
draw_entities();
}