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_life.c

465 lines
9.2 KiB

#include "mode_life.h"
#include "utils/timebase.h"
#include "com/debug.h"
#include "dotmatrix.h"
static bool life_enabled = false;
static void show_board(bool do_show);
static void apply_paint(void);
#define BOARD_W SCREEN_W
#define BOARD_H SCREEN_H
#define SIZEOF_BOARD (BOARD_H*sizeof(uint32_t))
// 32-bit for easy blit
static bool is_orig_board = true; // marks that currently displayed board is the orig board to reset back to.
static uint32_t board_orig[BOARD_H];
static uint32_t board_prev[BOARD_H];
static uint32_t board[BOARD_H];
static task_pid_t task_gametick; // game update tick
static task_pid_t task_movetick; // repeated move
static task_pid_t task_start_moveticks; // scheduled task to start moveticks after a delay
static task_pid_t task_movepress; // move on press (delayed to allow for diagonals)
static task_pid_t task_blink; // periodic blink task
static task_pid_t task_blink2; // scheduled 'unblink' task
static ms_time_t step_time = 200; // game step time
#define BLINKLEN 350 // blink on time (total)
#define BLINKLEN_ONE 300 // blink on time for '1'
#define BLINKLEN_ZERO 10 // blink on time for '0'
#define MOVE_TIME 80 // mouse move
#define MOVE_START_TIME 600
#define WRAPPING 0
static int cursorX = BOARD_W / 2 - 1;
static int cursorY = BOARD_H / 2 - 1;
static bool running = false;
static bool painting_1 = false;
static bool painting_0 = false;
static bool modA_down = false;
static bool modB_down = false;
// X+,X-,Y+,Y-
typedef enum {
DIR_NONE = 0b0000,
// straight
DIR_N = 0b0010,
DIR_S = 0b0001,
DIR_E = 0b1000,
DIR_W = 0b0100,
// diagonals
DIR_NE = DIR_N | DIR_E,
DIR_NW = DIR_N | DIR_W,
DIR_SE = DIR_S | DIR_E,
DIR_SW = DIR_S | DIR_W,
} MoveDir;
static MoveDir active_dir = DIR_NONE;
// ---
static void board_set(int x, int y, bool color)
{
if (x < 0 || y < 0 || x >= BOARD_W || y >= BOARD_H) return;
uint32_t mask = 1 << (BOARD_W - x - 1);
if (color) {
board[y] |= mask;
} else {
board[y] &= ~mask;
}
}
static bool board_get(uint32_t *brd, int x, int y)
{
#if WRAPPING
while (x < 0) x += BOARD_W;
while (y < 0) y += BOARD_H;
while (x >= BOARD_W) x -= BOARD_W;
while (y >= BOARD_H) y -= BOARD_H;
#else
if (x < 0 || y < 0 || x >= BOARD_W || y >= BOARD_H) return 0;
#endif
return (brd[y] >> (BOARD_W - x - 1)) & 1;
}
static void move_cursor(void)
{
if (active_dir & DIR_N) {
cursorY++;
} else if (active_dir & DIR_S) {
cursorY--;
}
if (active_dir & DIR_E) {
cursorX++;
} else if (active_dir & DIR_W) {
cursorX--;
}
// clamp
if (cursorX >= BOARD_W) cursorX = BOARD_W - 1;
if (cursorX < 0) cursorX = 0;
if (cursorY >= BOARD_H) cursorY = BOARD_H - 1;
if (cursorY < 0) cursorY = 0;
// apply paint if active
apply_paint();
show_board(true);
dmtx_set(dmtx, cursorX, cursorY, 1);
abort_scheduled_task(task_blink2); // abort unblink task FIXME ???
}
static void apply_paint(void)
{
if (painting_0) {
board_set(cursorX, cursorY, 0);
}
if (painting_1) {
board_set(cursorX, cursorY, 1);
}
}
/** start periodic move */
static void start_moveticks_cb(void *unused)
{
(void)unused;
if (!life_enabled) return;
task_start_moveticks = 0;
enable_periodic_task(task_movetick, true);
}
/** Perform one game step */
static void gametick_cb(void *unused)
{
(void)unused;
if (!life_enabled) return;
dbg("Game tick!");
memcpy(board_prev, board, SIZEOF_BOARD);
for (int i = 0; i < BOARD_W; i++) {
for (int j = 0; j < BOARD_H; j++) {
int count = 0;
// Above
count += board_get(board_prev, i - 1, j - 1);
count += board_get(board_prev, i, j - 1);
count += board_get(board_prev, i + 1, j - 1);
// Sides
count += board_get(board_prev, i - 1, j);
count += board_get(board_prev, i + 1, j);
// Below
count += board_get(board_prev, i - 1, j + 1);
count += board_get(board_prev, i, j + 1);
count += board_get(board_prev, i + 1, j + 1);
bool at = board_get(board_prev, i, j);
//board_set(i, j, count == 2 || count == 3);
if (at) {
// live cell
board_set(i, j, count == 2 || count == 3);
} else {
board_set(i, j, count == 3);
}
}
}
show_board(true);
}
/** Move cursor one step in active direction */
static void movetick_cb(void *unused)
{
(void)unused;
if (!life_enabled) return;
move_cursor();
}
static void blink_cb(void *arg)
{
if (!life_enabled) return; // pending leftover...
uint32_t which = (uint32_t)arg;
if (which == 0) {
// first
show_board(false);
bool at = board_get(board, cursorX, cursorY);
dmtx_set(dmtx, cursorX, cursorY, 1);
dmtx_show(dmtx);
// schedule unblink
schedule_task(blink_cb, (void*)1, at ? BLINKLEN_ONE : BLINKLEN_ZERO, true);
} else {
show_board(false);
dmtx_set(dmtx, cursorX, cursorY, 0);
dmtx_show(dmtx);
}
}
// ---
static void show_board(bool do_show)
{
dmtx_set_block(dmtx, 0, 0, board, 16, 16);
if (do_show) dmtx_show(dmtx);
}
// ---
void mode_life_init(void)
{
// prepare tasks
task_gametick = add_periodic_task(gametick_cb, NULL, step_time, true);
task_movetick = add_periodic_task(movetick_cb, NULL, MOVE_TIME, true);
task_blink = add_periodic_task(blink_cb, 0, BLINKLEN, true);
// stop all - we may not be starting this mode just yet
enable_periodic_task(task_gametick, false);
enable_periodic_task(task_movetick, false);
enable_periodic_task(task_blink, false);
}
void mode_life_start(void)
{
life_enabled = true;
enable_periodic_task(task_blink, true);
// if (running) {
// enable_periodic_task(task_gametick, true);
// } else {
// enable_periodic_task(task_blink, true);
// }
}
void mode_life_stop(void)
{
enable_periodic_task(task_gametick, false);
enable_periodic_task(task_movetick, false);
enable_periodic_task(task_blink, false);
abort_scheduled_task(task_blink2);
abort_scheduled_task(task_movepress);
abort_scheduled_task(task_start_moveticks);
painting_1 = painting_0 = false;
modA_down = modB_down = false;
running = false; // stop
life_enabled = false;
}
static void toggle_game(bool run)
{
if (run) {
running = true;
enable_periodic_task(task_movetick, false);
enable_periodic_task(task_blink, false);
abort_scheduled_task(task_movepress);
abort_scheduled_task(task_start_moveticks);
// Go!!
enable_periodic_task(task_gametick, true);
} else {
running = false;
enable_periodic_task(task_gametick, false);
enable_periodic_task(task_blink, true);
}
show_board(true);
}
/** do move (had ample time to press both arrows for diagonal) */
static void movepress_cb(void *unused)
{
(void)unused;
if (!life_enabled) return;
move_cursor();
task_movepress = 0;
task_start_moveticks = schedule_task(start_moveticks_cb, NULL, MOVE_START_TIME, true);
}
void mode_life_btn(char key)
{
// Common for run / stop mode
switch (key) {
case 'A': modA_down = true; break;
case 'a': modA_down = false; break;
case 'B': modB_down = true; break;
case 'b': modB_down = false; break;
case 'K':
// Reset btn
if (modB_down) {
// total reset
dbg("Reset to blank");
memset(board_orig, 0, SIZEOF_BOARD);
memset(board, 0, SIZEOF_BOARD);
toggle_game(false);
is_orig_board = true;
} else {
// reset to orig
dbg("Reset to original");
memcpy(board, board_orig, SIZEOF_BOARD);
toggle_game(false);
is_orig_board = true;
}
break;
}
if (running) {
switch (key) {
case 'Y': // slower
if (step_time < 1000) {
step_time += 50;
set_periodic_task_interval(task_gametick, step_time);
}
break;
case 'X': // faster
if (step_time > 50) {
step_time -= 50;
set_periodic_task_interval(task_gametick, step_time);
}
break;
case 'J': // stop
dbg("STOP game!");
toggle_game(false);
break;
}
} else {
bool want_move = false;
bool clear_moveticks = false;
bool want_apply_paint = false;
// Groups
switch (key) {
case 'U':
case 'D':
case 'R':
case 'L':
want_move = true;
break;
case 'u':
case 'd':
case 'r':
case 'l':
clear_moveticks = true;
break;
case 'Y': // AA
case 'X': // BB
want_apply_paint = true;
break;
}
// Individual effects
switch (key) {
// --- ARROW DOWN ---
case 'U':
active_dir &= ~DIR_S;
active_dir |= DIR_N;
break;
case 'D':
active_dir &= ~DIR_N;
active_dir |= DIR_S;
break;
case 'R':
active_dir &= ~DIR_W;
active_dir |= DIR_E;
break;
case 'L':
active_dir &= ~DIR_E;
active_dir |= DIR_W;
break;
// --- ARROW UP ---
case 'u': active_dir &= ~DIR_N; break;
case 'd': active_dir &= ~DIR_S; break;
case 'r': active_dir &= ~DIR_E; break;
case 'l': active_dir &= ~DIR_W; break;
// --- Paint BTN ---
case 'X': painting_1 = true; break;
case 'x': painting_1 = false; break;
case 'Y': painting_0 = true; break;
case 'y': painting_0 = false; break;
// --- Control ---
case 'J':
dbg("Start game!");
// starting
if (is_orig_board) {
// save
memcpy(board_orig, board, SIZEOF_BOARD);
}
is_orig_board = false;
toggle_game(true);
break;
}
if (clear_moveticks) {
enable_periodic_task(task_movetick, false);
abort_scheduled_task(task_start_moveticks);
}
if (want_apply_paint) {
apply_paint();
}
if (want_move) {
if (task_movepress == 0) {
task_movepress = schedule_task(movepress_cb, NULL, 10, true);
}
}
}
}