You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
429 lines
8.6 KiB
429 lines
8.6 KiB
#include <avr/io.h>
|
|
#include <avr/interrupt.h>
|
|
#include <avr/pgmspace.h>
|
|
#include <util/delay.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "lib/meta.h"
|
|
#include "lib/arduino_pins.h"
|
|
#include "lib/calc.h"
|
|
#include "lib/adc.h"
|
|
#include "lib/lcd.h"
|
|
#include "lib/debounce.h"
|
|
|
|
// Buttons (to ground)
|
|
#define BTN_LEFT A0
|
|
#define BTN_RIGHT A1
|
|
#define BTN_UP A2
|
|
#define BTN_DOWN A3
|
|
#define BTN_PAUSE A4
|
|
#define BTN_RESTART A5
|
|
|
|
// Debouncer channels for buttons
|
|
// (Must be added in this order to debouncer)
|
|
#define D_LEFT 0
|
|
#define D_RIGHT 1
|
|
#define D_UP 2
|
|
#define D_DOWN 3
|
|
#define D_PAUSE 4
|
|
#define D_RESTART 5
|
|
|
|
// Board size (!!! rows = 2x number of display lines, max 2*4 = 8 !!!)
|
|
#define ROWS 8
|
|
#define COLS 20
|
|
|
|
// Delay between snake steps, in 10 ms
|
|
#define STEP_DELAY 24
|
|
|
|
// proto
|
|
void update();
|
|
void init_cgram();
|
|
void init_gameboard();
|
|
|
|
void init()
|
|
{
|
|
// Randomize RNG
|
|
adc_init();
|
|
srand(adc_read_word(3));
|
|
|
|
// Init LCD
|
|
lcd_init();
|
|
init_cgram(); // load default glyphs
|
|
|
|
// Init game board.
|
|
init_gameboard();
|
|
|
|
// gamepad buttons
|
|
as_input_pu(BTN_LEFT);
|
|
as_input_pu(BTN_RIGHT);
|
|
as_input_pu(BTN_UP);
|
|
as_input_pu(BTN_DOWN);
|
|
as_input_pu(BTN_PAUSE);
|
|
as_input_pu(BTN_RESTART);
|
|
|
|
// add buttons to debouncer
|
|
debo_add_rev(BTN_LEFT);
|
|
debo_add_rev(BTN_RIGHT);
|
|
debo_add_rev(BTN_UP);
|
|
debo_add_rev(BTN_DOWN);
|
|
debo_add_rev(BTN_PAUSE);
|
|
debo_add_rev(BTN_RESTART);
|
|
|
|
// setup timer
|
|
TCCR0A = _BV(WGM01); // CTC
|
|
TCCR0B = _BV(CS02) | _BV(CS00); // prescaler 1024
|
|
OCR0A = 156; // interrupt every 10 ms
|
|
sbi(TIMSK0, OCIE0A);
|
|
sei();
|
|
}
|
|
|
|
|
|
/** timer 0 interrupt vector */
|
|
ISR(TIMER0_COMPA_vect)
|
|
{
|
|
debo_tick(); // poll debouncer
|
|
update(); // update and display
|
|
}
|
|
|
|
|
|
|
|
// sub-glyphs
|
|
#define _HEAD_ 15, 21, 21, 30
|
|
#define _BODY_ 15, 31, 31, 30
|
|
#define _FOOD_ 10, 21, 17, 14
|
|
//14, 17, 17, 14
|
|
#define _NONE_ 0, 0, 0, 0
|
|
|
|
// complete glyphs for loading into memory
|
|
|
|
// Only one food & one head glyph have to be loaded at a time.
|
|
|
|
// Body - Body
|
|
const uint8_t SYMBOL_BB[] PROGMEM = {_BODY_, _BODY_};
|
|
|
|
// Body - None
|
|
const uint8_t SYMBOL_BX[] PROGMEM = {_BODY_, _NONE_};
|
|
const uint8_t SYMBOL_XB[] PROGMEM = {_NONE_, _BODY_};
|
|
|
|
// Head - None
|
|
const uint8_t SYMBOL_HX[] PROGMEM = {_HEAD_, _NONE_};
|
|
const uint8_t SYMBOL_XH[] PROGMEM = {_NONE_, _HEAD_};
|
|
|
|
// Body - Head
|
|
const uint8_t SYMBOL_BH[] PROGMEM = {_BODY_, _HEAD_};
|
|
const uint8_t SYMBOL_HB[] PROGMEM = {_HEAD_, _BODY_};
|
|
|
|
// Head - Food
|
|
const uint8_t SYMBOL_HF[] PROGMEM = {_HEAD_, _FOOD_};
|
|
const uint8_t SYMBOL_FH[] PROGMEM = {_FOOD_, _HEAD_};
|
|
|
|
// Food - None
|
|
const uint8_t SYMBOL_FX[] PROGMEM = {_FOOD_, _NONE_};
|
|
const uint8_t SYMBOL_XF[] PROGMEM = {_NONE_, _FOOD_};
|
|
|
|
// Body - Food
|
|
const uint8_t SYMBOL_BF[] PROGMEM = {_BODY_, _FOOD_};
|
|
const uint8_t SYMBOL_FB[] PROGMEM = {_FOOD_, _BODY_};
|
|
|
|
|
|
// board block (snake, food...)
|
|
typedef enum {
|
|
bEMPTY = 0x00,
|
|
bHEAD = 0x01,
|
|
bFOOD = 0x02,
|
|
bBODY_LEFT = 0x80,
|
|
bBODY_RIGHT = 0x81,
|
|
bBODY_UP = 0x82,
|
|
bBODY_DOWN = 0x83,
|
|
} block_t;
|
|
|
|
// Snake direction
|
|
typedef enum {
|
|
dLEFT = 0x00,
|
|
dRIGHT = 0x01,
|
|
dUP = 0x02,
|
|
dDOWN = 0x03,
|
|
} dir_t;
|
|
|
|
// Coordinate on board
|
|
typedef struct {
|
|
int8_t x;
|
|
int8_t y;
|
|
} coord_t;
|
|
|
|
#define is_body(blk) (((blk) & 0x80) != 0)
|
|
#define mk_body_dir(dir) (0x80 + (dir))
|
|
|
|
// compare two coords
|
|
#define coord_eq(a, b) (((a).x == (b).x) && ((a).y == (b).y))
|
|
|
|
|
|
bool crashed;
|
|
uint8_t snake_len;
|
|
|
|
// y, x indexing
|
|
block_t board[ROWS][COLS];
|
|
|
|
coord_t head_pos;
|
|
coord_t tail_pos;
|
|
dir_t head_dir;
|
|
|
|
const uint8_t CODE_BB = 0;
|
|
const uint8_t CODE_BX = 1;
|
|
const uint8_t CODE_XB = 2;
|
|
const uint8_t CODE_H = 3; // glyph with head, dynamic
|
|
const uint8_t CODE_F = 4; // glyph with food, dynamic
|
|
const uint8_t CODE_XX = 32; // space
|
|
|
|
|
|
// Set a block in board
|
|
#define set_block_xy(x, y, block) do { board[y][x] = (block); } while(0)
|
|
#define get_block_xy(x, y) board[y][x]
|
|
#define get_block(pos) get_block_xy((pos).x, (pos).y)
|
|
#define set_block(pos, block) set_block_xy((pos).x, (pos).y, (block))
|
|
|
|
|
|
void init_cgram()
|
|
{
|
|
// those will be always the same
|
|
lcd_glyph_P(CODE_BB, SYMBOL_BB);
|
|
lcd_glyph_P(CODE_BX, SYMBOL_BX);
|
|
lcd_glyph_P(CODE_XB, SYMBOL_XB);
|
|
lcd_glyph_P(5, SYMBOL_XF);
|
|
}
|
|
|
|
|
|
void place_food()
|
|
{
|
|
while(1) {
|
|
const uint8_t xx = rand() % COLS;
|
|
const uint8_t yy = rand() % ROWS;
|
|
|
|
if (get_block_xy(xx, yy) == bEMPTY) {
|
|
set_block_xy(xx, yy, bFOOD);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void init_gameboard()
|
|
{
|
|
//erase the board
|
|
for (uint8_t x = 0; x < COLS; x++) {
|
|
for (uint8_t y = 0; y < ROWS; y++) {
|
|
set_block_xy(x, y, bEMPTY);
|
|
}
|
|
}
|
|
|
|
lcd_clear();
|
|
|
|
tail_pos = (coord_t) {.x = 0, .y = 0};
|
|
|
|
set_block_xy(0, 0, bBODY_RIGHT);
|
|
set_block_xy(1, 0, bBODY_RIGHT);
|
|
set_block_xy(2, 0, bBODY_RIGHT);
|
|
set_block_xy(3, 0, bHEAD);
|
|
|
|
head_pos = (coord_t) {.x = 3, .y = 0};
|
|
|
|
snake_len = 4; // includes head
|
|
|
|
head_dir = dRIGHT;
|
|
crashed = false;
|
|
|
|
place_food();
|
|
}
|
|
|
|
|
|
uint8_t presc = 0;
|
|
|
|
bool restart_held;
|
|
bool pause_held;
|
|
bool paused;
|
|
void update()
|
|
{
|
|
if (debo_get_pin(D_RESTART)) {
|
|
|
|
if (!restart_held) {
|
|
// restart
|
|
init_gameboard();
|
|
presc = 0;
|
|
restart_held = true;
|
|
}
|
|
|
|
} else {
|
|
restart_held = false;
|
|
}
|
|
|
|
if (debo_get_pin(D_PAUSE)) {
|
|
|
|
if (!pause_held) {
|
|
paused ^= true;
|
|
pause_held = true;
|
|
}
|
|
|
|
} else {
|
|
pause_held = false;
|
|
}
|
|
|
|
if(!crashed && !paused) {
|
|
|
|
// resolve movement direction
|
|
if (debo_get_pin(D_LEFT))
|
|
head_dir = dLEFT;
|
|
else if (debo_get_pin(D_RIGHT))
|
|
head_dir = dRIGHT;
|
|
else if (debo_get_pin(D_UP))
|
|
head_dir = dUP;
|
|
else if (debo_get_pin(D_DOWN))
|
|
head_dir = dDOWN;
|
|
|
|
// time's up for a move
|
|
if (presc++ == STEP_DELAY) {
|
|
presc = 0;
|
|
|
|
// move snake
|
|
const coord_t oldpos = head_pos;
|
|
|
|
switch (head_dir) {
|
|
case dLEFT: head_pos.x--; break;
|
|
case dRIGHT: head_pos.x++; break;
|
|
case dUP: head_pos.y--; break;
|
|
case dDOWN: head_pos.y++; break;
|
|
}
|
|
|
|
bool do_move = false;
|
|
bool do_grow = false;
|
|
|
|
if (head_pos.x < 0 || head_pos.x >= COLS || head_pos.y < 0 || head_pos.y >= ROWS) {
|
|
// ouch, a wall!
|
|
crashed = true;
|
|
} else {
|
|
// check if tile occupied or not
|
|
if (coord_eq(head_pos, tail_pos)) {
|
|
// head moved in previous tail, that's OK.
|
|
do_move = true;
|
|
} else {
|
|
// moved to other tile than tail
|
|
switch (get_block(head_pos)) {
|
|
|
|
case bFOOD:
|
|
do_grow = true; // fall through
|
|
case bEMPTY:
|
|
do_move = true;
|
|
break;
|
|
|
|
default: // includes all BODY_xxx
|
|
crashed = true; // snake crashed into some block
|
|
}
|
|
}
|
|
}
|
|
|
|
if (do_move) {
|
|
// Move tail
|
|
if (do_grow) {
|
|
// let tail as is
|
|
snake_len++; // grow the counter
|
|
} else {
|
|
// tail dir
|
|
dir_t td = get_block(tail_pos) & 0xF;
|
|
|
|
// clean tail
|
|
set_block(tail_pos, bEMPTY);
|
|
|
|
// move tail based on old direction of tail block
|
|
switch (td) {
|
|
case dLEFT: tail_pos.x--; break;
|
|
case dRIGHT: tail_pos.x++; break;
|
|
case dUP: tail_pos.y--; break;
|
|
case dDOWN: tail_pos.y++; break;
|
|
}
|
|
}
|
|
|
|
// Move head
|
|
set_block(head_pos, bHEAD); // place head in new pos
|
|
set_block(oldpos, mk_body_dir(head_dir)); // directional body in old head pos
|
|
|
|
if (do_grow) {
|
|
// food eaten, place new
|
|
place_food();
|
|
}
|
|
}
|
|
}
|
|
} // end !crashed
|
|
|
|
|
|
// Render the board
|
|
for (uint8_t r = 0; r < ROWS / 2; r++) {
|
|
lcd_xy(0, r);
|
|
for (uint8_t c = 0; c < COLS; c++) {
|
|
const block_t t1 = get_block_xy(c, r * 2);
|
|
const block_t t2 = get_block_xy(c, (r * 2) + 1);
|
|
|
|
uint8_t code = '!'; // ! marks fail
|
|
|
|
if ((t1 == bEMPTY) && (t2 == bEMPTY)) {
|
|
code = CODE_XX;
|
|
if (crashed) code = '*';
|
|
} else if (is_body(t1) && is_body(t2))
|
|
code = CODE_BB;
|
|
else if (is_body(t1) && (t2 == bEMPTY))
|
|
code = CODE_BX;
|
|
else if (t1 == bEMPTY && is_body(t2))
|
|
code = CODE_XB;
|
|
else if ((t1 == bFOOD) || (t2 == bFOOD)) {
|
|
// one is food
|
|
|
|
code = CODE_F;
|
|
|
|
if (t1 == bFOOD) {
|
|
if (t2 == bEMPTY)
|
|
lcd_glyph_P(code, SYMBOL_FX);
|
|
else if (t2 == bHEAD)
|
|
lcd_glyph_P(code, SYMBOL_FH);
|
|
else if (is_body(t2))
|
|
lcd_glyph_P(code, SYMBOL_FB);
|
|
} else { // t2 is food
|
|
if (t1 == bEMPTY)
|
|
lcd_glyph_P(code, SYMBOL_XF);
|
|
else if (t1 == bHEAD)
|
|
lcd_glyph_P(code, SYMBOL_HF);
|
|
else if (is_body(t1))
|
|
lcd_glyph_P(code, SYMBOL_BF);
|
|
}
|
|
lcd_xy(c,r);
|
|
|
|
} else if ((t1 == bHEAD )|| (t2 == bHEAD)) {
|
|
// one is head
|
|
|
|
code = CODE_H;
|
|
|
|
if (t1 == bHEAD) {
|
|
if (t2 == bEMPTY)
|
|
lcd_glyph_P(code, SYMBOL_HX);
|
|
else if (is_body(t2))
|
|
lcd_glyph_P(code, SYMBOL_HB);
|
|
} else { // t2 is head
|
|
if (t1 == bEMPTY)
|
|
lcd_glyph_P(code, SYMBOL_XH);
|
|
else if (is_body(t1))
|
|
lcd_glyph_P(code, SYMBOL_BH);
|
|
}
|
|
|
|
lcd_xy(c,r);
|
|
}
|
|
|
|
lcd_putc(code);
|
|
}
|
|
}
|
|
}
|
|
|
|
void main()
|
|
{
|
|
init();
|
|
|
|
while(1); // timer does everything
|
|
}
|
|
|