working game of snake

multi-mode
Ondřej Hruška 9 years ago
parent a4400c25f5
commit 736567af8c
  1. 336
      project/mode_snake.c

@ -24,7 +24,8 @@ typedef enum {
} CellType; } CellType;
/** Board cell */ /** Board cell */
typedef struct __attribute__((packed)) { typedef struct __attribute__((packed))
{
CellType type : 2; // Cell type CellType type : 2; // Cell type
Direction dir : 2; // direction the head moved to from this tile, if body = 1 Direction dir : 2; // direction the head moved to from this tile, if body = 1
} Cell; } Cell;
@ -47,8 +48,9 @@ static Direction head_dir;
/** blinking to visually change 'color' */ /** blinking to visually change 'color' */
typedef struct { typedef struct __attribute__((packed)) {
bool pwm_bit; bool pwm_bit : 1;
bool offtask_pending : 1;
task_pid_t on_task; // periodic task_pid_t on_task; // periodic
task_pid_t off_task; // scheduled task_pid_t off_task; // scheduled
ms_time_t interval_ms; ms_time_t interval_ms;
@ -56,6 +58,7 @@ typedef struct {
} snake_pwm; } snake_pwm;
static bool alive = false;
static bool moving = false; static bool moving = false;
static task_pid_t task_snake_move; static task_pid_t task_snake_move;
@ -71,13 +74,117 @@ static snake_pwm head_pwm = {
.offtime_ms = 90 .offtime_ms = 90
}; };
static ms_time_t move_interval = 500; #define MIN_MOVE_INTERVAL 64
#define POINTS_TO_LEVEL_UP 5
#define PIECES_REMOVED_ON_LEVEL_UP 3
#define PREDEFINED_LEVELS 10
static const ms_time_t speeds_for_levels[PREDEFINED_LEVELS] = {
320, 245, 190, 150, 120, 100, 80, 60, 50, 40
};
static ms_time_t move_interval;
static uint32_t score;
static uint32_t level_up_score; // counter
static uint32_t level;
static Cell *cell_at_xy(int x, int y);
// ---
static void show_board(void)
{
if (!snake_active) return;
dmtx_clear(dmtx);
for (int x = 0; x < BOARD_W; x++) {
for (int y = 0; y < BOARD_H; y++) {
Cell *cell = cell_at_xy(x, y);
bool set = 0;
switch (cell->type) {
case CELL_EMPTY: set = 0; break;
case CELL_BODY: set = 1; break;
case CELL_FOOD:
set = food_pwm.pwm_bit;
break;
case CELL_WALL: set = 1; break;
}
if (x == head.x && y == head.y) set = head_pwm.pwm_bit;
dmtx_set(dmtx, x, y, set);
}
}
dmtx_show(dmtx);
}
// --- Snake PWM ---
/** Turn off a PWM bit (scheduled callback) */
static void task_pwm_off_cb(void *ptr)
{
if (!snake_active) return;
snake_pwm* pwm = ptr;
if (!pwm->offtask_pending) return;
pwm->offtask_pending = false;
pwm->pwm_bit = 0;
show_board();
}
/** Turn on a PWM bit and schedule the turn-off task (periodic callback) */
static void task_pwm_on_cb(void *ptr)
{
if (!snake_active) return;
snake_pwm* pwm = ptr;
pwm->pwm_bit = 1;
show_board();
pwm->offtask_pending = true;
schedule_task(task_pwm_off_cb, ptr, pwm->offtime_ms, true);
}
/** Initialize a snake PWM channel */
static void snake_pwm_init(snake_pwm *pwm)
{
pwm->on_task = add_periodic_task(task_pwm_on_cb, pwm, pwm->interval_ms, true);
enable_periodic_task(pwm->on_task, false);
}
/** Clear & start a snake PWM channel */
static void snake_pwm_start(snake_pwm *pwm)
{
pwm->pwm_bit = 1;
pwm->offtask_pending = false;
reset_periodic_task(pwm->on_task);
enable_periodic_task(pwm->on_task, true);
}
/** Stop a snake PWM channel */
static void snake_pwm_stop(snake_pwm *pwm)
{
pwm->offtask_pending = false;
enable_periodic_task(pwm->on_task, false);
abort_scheduled_task(pwm->off_task);
}
/** Get board cell at coord (x,y) */ /** Get board cell at coord (x,y) */
static Cell *cell_at_xy(int x, int y) static Cell *cell_at_xy(int x, int y)
{ {
if (x < 0 || x >= BOARD_W || y < 0 || y >= BOARD_H ) { if (x < 0 || x >= BOARD_W || y < 0 || y >= BOARD_H) {
return &WALL_TILE; // discards const - OK return &WALL_TILE; // discards const - OK
} }
@ -104,15 +211,33 @@ static void place_food(void)
if (cell->type == CELL_EMPTY) { if (cell->type == CELL_EMPTY) {
cell->type = CELL_FOOD; cell->type = CELL_FOOD;
dbg("Food at [%d, %d]", food.x, food.y); //dbg("Food at [%d, %d]", food.x, food.y);
break; break;
} }
} }
// avoid "doubleblink" glitch
reset_periodic_task(food_pwm.on_task);
food_pwm.pwm_bit = 1;
food_pwm.offtask_pending = 0;
} }
/** Clear the board and prepare for a new game */ /** Clear the board and prepare for a new game */
static void new_game(void) static void new_game(void)
{ {
// Stop snake (make sure it's stopped)
reset_periodic_task(task_snake_move);
enable_periodic_task(task_snake_move, false);
moving = false;
alive = true;
level = 0;
score = 0;
move_interval = speeds_for_levels[level];
level_up_score = 0;
set_periodic_task_interval(task_snake_move, move_interval);
// randomize (we can assume it takes random time before the user switches to the Snake mode) // randomize (we can assume it takes random time before the user switches to the Snake mode)
srand(SysTick->VAL); srand(SysTick->VAL);
@ -121,8 +246,8 @@ static void new_game(void)
// Place initial snake // Place initial snake
tail.x = BOARD_W/2-5; tail.x = BOARD_W / 2 - 5;
tail.y = BOARD_H/2; tail.y = BOARD_H / 2;
head.x = tail.x + 4; head.x = tail.x + 4;
head.y = tail.y; head.y = tail.y;
@ -131,93 +256,129 @@ static void new_game(void)
for (int x = tail.x; x < head.x; x++) { for (int x = tail.x; x < head.x; x++) {
board[tail.y][x].type = CELL_BODY; board[tail.y][x].type = CELL_BODY;
board[tail.y][x].dir = EAST;
} }
place_food(); place_food();
}
static void show_board(void) snake_pwm_start(&head_pwm);
{ snake_pwm_start(&food_pwm);
if (!snake_active) return;
dmtx_clear(dmtx);
for (int x = 0; x < BOARD_W; x++) { show_board();
for (int y = 0; y < BOARD_H; y++) { }
Cell *cell = cell_at_xy(x, y);
bool set = 0; static void snake_died(void)
{
dbg("R.I.P. Snake");
switch (cell->type) { moving = false;
case CELL_EMPTY: set = 0; break; alive = false;
case CELL_BODY: set = 1; break; enable_periodic_task(task_snake_move, false);
case CELL_FOOD:
set = food_pwm.pwm_bit;
break;
case CELL_WALL: set = 1; break; // stop blinky animation of the snake head.
} snake_pwm_stop(&head_pwm);
if (x == head.x && y == head.y) set = head_pwm.pwm_bit; // TODO death animation
// TODO show score
}
dmtx_set(dmtx, x, y, set); static void move_coord_by_dir(Coord *coord, Direction dir)
} {
switch (dir) {
case NORTH: coord->y++; break;
case SOUTH: coord->y--; break;
case EAST: coord->x++; break;
case WEST: coord->x--; break;
} }
dmtx_show(dmtx); // Wrap-around
if (coord->x < 0) coord->x = BOARD_W - 1;
if (coord->y < 0) coord->y = BOARD_H - 1;
if (coord->x >= BOARD_W) coord->x = 0;
if (coord->y >= BOARD_H) coord->y = 0;
}
static void move_tail(void)
{
Cell *tail_cell = cell_at(&tail);
tail_cell->type = CELL_EMPTY;
move_coord_by_dir(&tail, tail_cell->dir);
} }
static void snake_move_cb(void *unused) static void snake_move_cb(void *unused)
{ {
(void)unused; (void)unused;
dbg("Move."); bool want_new_food = false;
}
// --- Snake PWM --- Coord shadowhead = head;
/** Turn off a PWM bit (scheduled callback) */ move_coord_by_dir(&shadowhead, head_dir);
static void task_pwm_off_cb(void *ptr)
{
if (!snake_active) return;
((snake_pwm*)ptr)->pwm_bit = 0; Cell *head_cell = cell_at(&head);
show_board(); Cell *future_head_cell = cell_at(&shadowhead);
}
/** Turn on a PWM bit and schedule the turn-off task (periodic callback) */ switch (future_head_cell->type) {
static void task_pwm_on_cb(void *ptr) case CELL_BODY:
{ case CELL_WALL:
if (!snake_active) return; // R.I.P.
snake_died();
return;
case CELL_FOOD:
// grow - no head move
score++;
want_new_food = 1;
level_up_score++;
break;
((snake_pwm*)ptr)->pwm_bit = 1; case CELL_EMPTY:
// move tail
move_tail();
break;
}
// Move head
// (if died, already returned)
head_cell->dir = head_dir;
head_cell->type = CELL_BODY;
future_head_cell->type = CELL_BODY;
// apply movement
head = shadowhead;
if (want_new_food) {
place_food();
}
// render
show_board(); show_board();
schedule_task(task_pwm_off_cb, ptr, ((snake_pwm*)ptr)->offtime_ms, true); // level up
} if (level_up_score == POINTS_TO_LEVEL_UP) {
enable_periodic_task(task_snake_move, false);
/** Initialize a snake PWM channel */ // remove some pieces
static void snake_pwm_init(snake_pwm *ptr) for (int i = 0; i < PIECES_REMOVED_ON_LEVEL_UP; i++) {
{ move_tail();
ptr->on_task = add_periodic_task(task_pwm_on_cb, ptr, ptr->interval_ms, true); until_timeout(move_interval/2) {
enable_periodic_task(ptr->on_task, false); tq_poll(); // take care of periodic tasks (anim)
} }
}
/** Clear & start a snake PWM channel */ level++;
static void snake_pwm_start(snake_pwm *ptr) level_up_score = 0;
{
ptr->pwm_bit = 1;
reset_periodic_task(ptr->on_task);
enable_periodic_task(ptr->on_task, true);
}
/** Stop a snake PWM channel */ // go faster if not too fast yet
static void snake_pwm_stop(snake_pwm *ptr) if (level < PREDEFINED_LEVELS) {
{ move_interval = speeds_for_levels[level];
enable_periodic_task(ptr->on_task, false); }
abort_scheduled_task(ptr->off_task);
}
set_periodic_task_interval(task_snake_move, move_interval);
enable_periodic_task(task_snake_move, true);
}
}
/** INIT snake */ /** INIT snake */
@ -238,10 +399,6 @@ void mode_snake_start(void)
snake_pwm_start(&head_pwm); snake_pwm_start(&head_pwm);
snake_pwm_start(&food_pwm); snake_pwm_start(&food_pwm);
// Stop snake (make sure it's stopped)
enable_periodic_task(task_snake_move, false);
moving = false;
new_game(); new_game();
} }
@ -256,30 +413,51 @@ void mode_snake_stop(void)
enable_periodic_task(task_snake_move, false); enable_periodic_task(task_snake_move, false);
} }
/** Change dir (safely) */
static void change_direction(Direction dir)
{
// This is a compensation for shitty gamepads
// with accidental arrow hits
Coord shadowhead = head;
move_coord_by_dir(&shadowhead, dir);
Cell *target = cell_at(&shadowhead);
if (target->type == CELL_BODY) return;
head_dir = dir;
}
/** User button */ /** User button */
void mode_snake_btn(char key) void mode_snake_btn(char key)
{ {
switch (key) { switch (key) {
case 'U': head_dir = NORTH; break; case 'U': change_direction(NORTH); break;
case 'D': head_dir = SOUTH; break; case 'D': change_direction(SOUTH); break;
case 'L': head_dir = WEST; break; case 'L': change_direction(WEST); break;
case 'R': head_dir = EAST; break; case 'R': change_direction(EAST); break;
case 'J':
if (alive) break;
// dead - reset by pressing 'start'
case 'K': // clear case 'K': // clear
// TODO reset animation // TODO reset animation
mode_snake_stop(); new_game();
mode_snake_start(); return;
break;
} }
if (!moving && (key == 'U' || key == 'D' || key == 'L' || key == 'R' || key == 'J')) { if (!moving && alive && (key == 'U' || key == 'D' || key == 'L' || key == 'R' || key == 'J')) {
// start moving // start moving
moving = true;
reset_periodic_task(task_snake_move); reset_periodic_task(task_snake_move);
enable_periodic_task(task_snake_move, true); enable_periodic_task(task_snake_move, true);
} }
// running + start -> 'pause' // running + start -> 'pause'
if (moving && key == 'J') { if (alive && moving && key == 'J') {
moving = false;
enable_periodic_task(task_snake_move, false); enable_periodic_task(task_snake_move, false);
} }
} }

Loading…
Cancel
Save