nicer gameplay experience

multi-mode
Ondřej Hruška 9 years ago
parent 43ab7abce7
commit c0d2b618da
  1. 2
      project/dotmatrix.c
  2. 114
      project/mode_snake.c
  3. 6
      project/scrolltext.c
  4. 2
      project/scrolltext.h

@ -52,7 +52,7 @@ void dmtx_intensity(DotMatrix_Cfg* disp, uint8_t intensity)
void dmtx_blank(DotMatrix_Cfg* disp, bool blank) void dmtx_blank(DotMatrix_Cfg* disp, bool blank)
{ {
max2719_cmd_all(&disp->drv, MAX2719_CMD_SHUTDOWN, blank & 0x01); max2719_cmd_all(&disp->drv, MAX2719_CMD_SHUTDOWN, (!blank) & 0x01);
} }
/** /**

@ -3,6 +3,8 @@
#include "dotmatrix.h" #include "dotmatrix.h"
#include "utils/timebase.h" #include "utils/timebase.h"
#include "scrolltext.h"
#define BOARD_W SCREEN_W #define BOARD_W SCREEN_W
#define BOARD_H SCREEN_H #define BOARD_H SCREEN_H
@ -31,7 +33,7 @@ typedef struct __attribute__((packed))
} Cell; } Cell;
/** Wall tile, invariant, used for out-of-screen coords */ /** Wall tile, invariant, used for out-of-screen coords */
static const Cell WALL_TILE = {CELL_WALL, 0}; static Cell WALL_TILE = {CELL_WALL, 0};
/** Game board */ /** Game board */
static Cell board[BOARD_H][BOARD_W]; static Cell board[BOARD_H][BOARD_W];
@ -46,9 +48,13 @@ static Coord head;
static Coord tail; static Coord tail;
static Direction head_dir; static Direction head_dir;
/** Limit dir changes to 1 per move */
static bool changed_dir_this_tick = false;
/** blinking to visually change 'color' */ /** blinking to visually change 'color' */
typedef struct __attribute__((packed)) { typedef struct __attribute__((packed))
{
bool pwm_bit : 1; bool pwm_bit : 1;
bool offtask_pending : 1; bool offtask_pending : 1;
task_pid_t on_task; // periodic task_pid_t on_task; // periodic
@ -76,11 +82,11 @@ static snake_pwm head_pwm = {
#define MIN_MOVE_INTERVAL 64 #define MIN_MOVE_INTERVAL 64
#define POINTS_TO_LEVEL_UP 5 #define POINTS_TO_LEVEL_UP 5
#define PIECES_REMOVED_ON_LEVEL_UP 3 #define PIECES_REMOVED_ON_LEVEL_UP 4
#define PREDEFINED_LEVELS 10 #define PREDEFINED_LEVELS 10
static const ms_time_t speeds_for_levels[PREDEFINED_LEVELS] = { static const ms_time_t speeds_for_levels[PREDEFINED_LEVELS] = {
320, 240, 180, 140, 95, 80, 65, 55, 40, 30 260, 200, 160, 120, 95, 80, 65, 55, 50, 45
}; };
static ms_time_t move_interval; static ms_time_t move_interval;
@ -89,6 +95,9 @@ static uint32_t score;
static uint32_t level_up_score; // counter static uint32_t level_up_score; // counter
static uint32_t level; static uint32_t level;
// used to reduce length seamlessly after a level-up
static uint32_t double_tail_move_cnt = 0;
static Cell *cell_at_xy(int x, int y); static Cell *cell_at_xy(int x, int y);
@ -152,7 +161,7 @@ static void task_pwm_on_cb(void *ptr)
show_board(); show_board();
pwm->offtask_pending = true; pwm->offtask_pending = true;
schedule_task(task_pwm_off_cb, ptr, pwm->offtime_ms, true); pwm->off_task = schedule_task(task_pwm_off_cb, ptr, pwm->offtime_ms, true);
} }
/** Initialize a snake PWM channel */ /** Initialize a snake PWM channel */
@ -179,13 +188,11 @@ static void snake_pwm_stop(snake_pwm *pwm)
abort_scheduled_task(pwm->off_task); 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;
} }
return &board[y][x]; return &board[y][x];
@ -211,20 +218,22 @@ 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("Placed food at [%d, %d]", food.x, food.y);
break; break;
} }
} }
// avoid "doubleblink" glitch // avoid "doubleblink" glitch
reset_periodic_task(food_pwm.on_task); reset_periodic_task(food_pwm.on_task);
food_pwm.pwm_bit = 1; abort_scheduled_task(food_pwm.off_task);
food_pwm.offtask_pending = 0; task_pwm_on_cb(&food_pwm);
} }
/** 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)
{ {
dbg("Snake NEW GAME");
// Stop snake (make sure it's stopped) // Stop snake (make sure it's stopped)
reset_periodic_task(task_snake_move); reset_periodic_task(task_snake_move);
enable_periodic_task(task_snake_move, false); enable_periodic_task(task_snake_move, false);
@ -232,6 +241,9 @@ static void new_game(void)
moving = false; moving = false;
alive = true; alive = true;
changed_dir_this_tick = false;
double_tail_move_cnt = 0;
level = 0; level = 0;
score = 0; score = 0;
move_interval = speeds_for_levels[level]; move_interval = speeds_for_levels[level];
@ -265,11 +277,16 @@ static void new_game(void)
snake_pwm_start(&food_pwm); snake_pwm_start(&food_pwm);
show_board(); show_board();
// Discard keys pressed during the death animation
uint8_t x;
while(com_rx(gamepad_iface, &x));
} }
static void snake_died(void) static void snake_died(void)
{ {
dbg("R.I.P. Snake"); dbg("R.I.P. Snake");
dbg("Total Score %d, Level %d", score, level);
moving = false; moving = false;
alive = false; alive = false;
@ -277,9 +294,41 @@ static void snake_died(void)
// stop blinky animation of the snake head. // stop blinky animation of the snake head.
snake_pwm_stop(&head_pwm); snake_pwm_stop(&head_pwm);
head_pwm.pwm_bit = 1;
// Let it sink...
until_timeout(600) {
tq_poll(); // take care of periodic tasks (anim)
}
snake_pwm_stop(&food_pwm);
show_board();
snake_active = false; // suppress pending callbacks
dmtx_clear(dmtx);
dmtx_blank(dmtx, true); // Screen off
char txt[6];
sprintf(txt, "%d", score);
size_t len = strlen(txt);
printtext(txt, (int)(SCREEN_W / 2 - (len * 5) / 2) - 1, SCREEN_H / 2 - 4);
delay_ms(400);
dmtx_blank(dmtx, false); // unblank
delay_ms(2000);
dmtx_blank(dmtx, true); // blank
delay_ms(400);
// TODO death animation dmtx_clear(dmtx);
// TODO show score dmtx_show(dmtx);
dmtx_blank(dmtx, false); // unblank
snake_active = true; // resume snake
new_game();
} }
static void move_coord_by_dir(Coord *coord, Direction dir) static void move_coord_by_dir(Coord *coord, Direction dir)
@ -309,6 +358,7 @@ static void snake_move_cb(void *unused)
{ {
(void)unused; (void)unused;
changed_dir_this_tick = false; // allow dir change next move
bool want_new_food = false; bool want_new_food = false;
Coord shadowhead = head; Coord shadowhead = head;
@ -335,6 +385,11 @@ static void snake_move_cb(void *unused)
case CELL_EMPTY: case CELL_EMPTY:
// move tail // move tail
move_tail(); move_tail();
if (double_tail_move_cnt > 0) {
move_tail(); // 2x
double_tail_move_cnt--;
}
break; break;
} }
@ -357,15 +412,9 @@ static void snake_move_cb(void *unused)
// level up // level up
if (level_up_score == POINTS_TO_LEVEL_UP) { if (level_up_score == POINTS_TO_LEVEL_UP) {
enable_periodic_task(task_snake_move, false); info("level up");
// remove some pieces double_tail_move_cnt += PIECES_REMOVED_ON_LEVEL_UP;
for (int i = 0; i < PIECES_REMOVED_ON_LEVEL_UP; i++) {
move_tail();
until_timeout(move_interval/2) {
tq_poll(); // take care of periodic tasks (anim)
}
}
level++; level++;
level_up_score = 0; level_up_score = 0;
@ -376,7 +425,6 @@ static void snake_move_cb(void *unused)
} }
set_periodic_task_interval(task_snake_move, move_interval); set_periodic_task_interval(task_snake_move, move_interval);
enable_periodic_task(task_snake_move, true);
} }
} }
@ -396,9 +444,6 @@ void mode_snake_start(void)
{ {
snake_active = true; snake_active = true;
snake_pwm_start(&head_pwm);
snake_pwm_start(&food_pwm);
new_game(); new_game();
} }
@ -416,17 +461,23 @@ void mode_snake_stop(void)
/** Change dir (safely) */ /** Change dir (safely) */
static void change_direction(Direction dir) static void change_direction(Direction dir)
{ {
if (changed_dir_this_tick) return;
// This is a compensation for shitty gamepads // This is a compensation for shitty gamepads
// with accidental arrow hits // with accidental arrow hits
Coord shadowhead = head; Coord shadowhead = head;
move_coord_by_dir(&shadowhead, dir); move_coord_by_dir(&shadowhead, dir);
Cell *target = cell_at(&shadowhead); // Target cell
Cell *target = cell_at(&shadowhead); if (target->type == CELL_BODY) {
move_coord_by_dir(&shadowhead, target->dir);
if (target->type == CELL_BODY) return; if (shadowhead.x == head.x && shadowhead.y == head.y) {
warn("Ignoring suicidal dir change");
return; // Would crash into own neck
}
}
head_dir = dir; head_dir = dir;
changed_dir_this_tick = true;
} }
/** User button */ /** User button */
@ -438,7 +489,7 @@ void mode_snake_btn(char key)
case 'L': change_direction(WEST); break; case 'L': change_direction(WEST); break;
case 'R': change_direction(EAST); break; case 'R': change_direction(EAST); break;
case 'J': case 'J': // Start
if (alive) break; if (alive) break;
// dead - reset by pressing 'start' // dead - reset by pressing 'start'
@ -453,6 +504,7 @@ void mode_snake_btn(char key)
moving = true; 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);
return;
} }
// running + start -> 'pause' // running + start -> 'pause'

@ -4,7 +4,7 @@
#include "com/debug.h" #include "com/debug.h"
static void printtext(const char *text, int x, int y) void printtext(const char *text, int x, int y)
{ {
dmtx_clear(dmtx); dmtx_clear(dmtx);
dmtx_show(dmtx); dmtx_show(dmtx);
@ -52,9 +52,9 @@ void scrolltext(const char *text, ms_time_t step)
{ {
(void)step; (void)step;
for (int i = 0; i < (int)strlen(text)*(FONT_WIDTH+1) + 15; i++) { for (int i = 0; i < (int)strlen(text)*(FONT_WIDTH+1) + SCREEN_W-1; i++) {
if (i > 0) delay_ms(step); if (i > 0) delay_ms(step);
printtext(text, 15-i, 4); printtext(text, (SCREEN_W-1)-i, SCREEN_H/2-4);
} }
} }

@ -5,6 +5,8 @@
#include "dotmatrix.h" #include "dotmatrix.h"
#include "utils/timebase.h" #include "utils/timebase.h"
void printtext(const char *text, int x, int y);
void scrolltext(const char *text, ms_time_t step); void scrolltext(const char *text, ms_time_t step);
#endif // SCROLLTEXT_H #endif // SCROLLTEXT_H

Loading…
Cancel
Save