#include #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 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 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 ) { 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() { int rc = 0; read_controls(); display_score(); gamelogic(); draw_entities(); }