demo of driving MAX2719 dot matrix displays with STM32F103 Bluepill
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.
 
 
 
 
f103-dotmatrix/project/mode_snake.c

285 lines
5.3 KiB

#include "mode_snake.h"
#include "com/debug.h"
#include "dotmatrix.h"
#include "utils/timebase.h"
#define BOARD_W SCREEN_W
#define BOARD_H SCREEN_H
static bool snake_active = false;
/** Snake movement direction */
typedef enum {
NORTH,
SOUTH,
EAST,
WEST
} Direction;
typedef enum {
CELL_EMPTY,
CELL_BODY,
CELL_FOOD,
CELL_WALL
} CellType;
/** Board cell */
typedef struct __attribute__((packed)) {
CellType type : 2; // Cell type
Direction dir : 2; // direction the head moved to from this tile, if body = 1
} Cell;
/** Wall tile, invariant, used for out-of-screen coords */
static const Cell WALL_TILE = {CELL_WALL, 0};
/** Game board */
static Cell board[BOARD_H][BOARD_W];
typedef struct {
int x;
int y;
} Coord;
/** Snake coords */
static Coord head;
static Coord tail;
static Direction head_dir;
/** blinking to visually change 'color' */
typedef struct {
bool pwm_bit;
task_pid_t on_task; // periodic
task_pid_t off_task; // scheduled
ms_time_t interval_ms;
ms_time_t offtime_ms;
} snake_pwm;
static bool moving = false;
static task_pid_t task_snake_move;
static snake_pwm food_pwm = {
.interval_ms = 400,
.offtime_ms = 330
};
static snake_pwm head_pwm = {
.interval_ms = 100,
.offtime_ms = 90
};
static ms_time_t move_interval = 500;
/** Get board cell at coord (x,y) */
static Cell *cell_at_xy(int x, int y)
{
if (x < 0 || x >= BOARD_W || y < 0 || y >= BOARD_H ) {
return &WALL_TILE; // discards const - OK
}
return &board[y][x];
}
/** Get board cell at coord */
static Cell *cell_at(Coord *coord)
{
return cell_at_xy(coord->x, coord->y);
}
/** Place food in a random empty tile */
static void place_food(void)
{
Coord food;
// 1000 tries - timeout in case board is full of snake (?)
for (int i = 0; i < 1000; i++) {
food.x = rand() % BOARD_W;
food.y = rand() % BOARD_H;
Cell *cell = cell_at(&food);
if (cell->type == CELL_EMPTY) {
cell->type = CELL_FOOD;
dbg("Food at [%d, %d]", food.x, food.y);
break;
}
}
}
/** Clear the board and prepare for a new game */
static void new_game(void)
{
// randomize (we can assume it takes random time before the user switches to the Snake mode)
srand(SysTick->VAL);
// Erase the board
memset(board, 0, sizeof(board));
// Place initial snake
tail.x = BOARD_W/2-5;
tail.y = BOARD_H/2;
head.x = tail.x + 4;
head.y = tail.y;
head_dir = EAST;
for (int x = tail.x; x < head.x; x++) {
board[tail.y][x].type = CELL_BODY;
}
place_food();
}
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);
}
static void snake_move_cb(void *unused)
{
(void)unused;
dbg("Move.");
}
// --- Snake PWM ---
/** Turn off a PWM bit (scheduled callback) */
static void task_pwm_off_cb(void *ptr)
{
if (!snake_active) return;
((snake_pwm*)ptr)->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*)ptr)->pwm_bit = 1;
show_board();
schedule_task(task_pwm_off_cb, ptr, ((snake_pwm*)ptr)->offtime_ms, true);
}
/** Initialize a snake PWM channel */
static void snake_pwm_init(snake_pwm *ptr)
{
ptr->on_task = add_periodic_task(task_pwm_on_cb, ptr, ptr->interval_ms, true);
enable_periodic_task(ptr->on_task, false);
}
/** Clear & start a snake PWM channel */
static void snake_pwm_start(snake_pwm *ptr)
{
ptr->pwm_bit = 1;
reset_periodic_task(ptr->on_task);
enable_periodic_task(ptr->on_task, true);
}
/** Stop a snake PWM channel */
static void snake_pwm_stop(snake_pwm *ptr)
{
enable_periodic_task(ptr->on_task, false);
abort_scheduled_task(ptr->off_task);
}
/** INIT snake */
void mode_snake_init(void)
{
snake_pwm_init(&head_pwm);
snake_pwm_init(&food_pwm);
task_snake_move = add_periodic_task(snake_move_cb, NULL, move_interval, true);
enable_periodic_task(task_snake_move, false);
}
/** START playing */
void mode_snake_start(void)
{
snake_active = true;
snake_pwm_start(&head_pwm);
snake_pwm_start(&food_pwm);
// Stop snake (make sure it's stopped)
enable_periodic_task(task_snake_move, false);
moving = false;
new_game();
}
/** STOP playing */
void mode_snake_stop(void)
{
snake_active = false;
snake_pwm_stop(&head_pwm);
snake_pwm_stop(&food_pwm);
enable_periodic_task(task_snake_move, false);
}
/** User button */
void mode_snake_btn(char key)
{
switch (key) {
case 'U': head_dir = NORTH; break;
case 'D': head_dir = SOUTH; break;
case 'L': head_dir = WEST; break;
case 'R': head_dir = EAST; break;
case 'K': // clear
// TODO reset animation
mode_snake_stop();
mode_snake_start();
break;
}
if (!moving && (key == 'U' || key == 'D' || key == 'L' || key == 'R' || key == 'J')) {
// start moving
reset_periodic_task(task_snake_move);
enable_periodic_task(task_snake_move, true);
}
// running + start -> 'pause'
if (moving && key == 'J') {
enable_periodic_task(task_snake_move, false);
}
}