|
|
|
#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 16
|
|
|
|
#define BOARD_H 16
|
|
|
|
|
|
|
|
#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 100 // mouse move
|
|
|
|
#define MOVE_START_TIME 700
|
|
|
|
|
|
|
|
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(int x, int y)
|
|
|
|
{
|
|
|
|
// wrap
|
|
|
|
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;
|
|
|
|
|
|
|
|
return (board[y] >> (BOARD_W - x - 1)) & 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool board_prev_get(int x, int y)
|
|
|
|
{
|
|
|
|
// wrap
|
|
|
|
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;
|
|
|
|
|
|
|
|
return (board_prev[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_prev_get(i - 1, j - 1);
|
|
|
|
count += board_prev_get(i, j - 1);
|
|
|
|
count += board_prev_get(i + 1, j - 1);
|
|
|
|
|
|
|
|
// Sides
|
|
|
|
count += board_prev_get(i - 1, j);
|
|
|
|
count += board_prev_get(i + 1, j);
|
|
|
|
|
|
|
|
// Below
|
|
|
|
count += board_prev_get(i - 1, j + 1);
|
|
|
|
count += board_prev_get(i, j + 1);
|
|
|
|
count += board_prev_get(i + 1, j + 1);
|
|
|
|
|
|
|
|
bool at = board_prev_get(i, j);
|
|
|
|
|
|
|
|
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(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 (modA_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 'X': // slower
|
|
|
|
if (step_time < 1000) {
|
|
|
|
step_time += 50;
|
|
|
|
set_periodic_task_interval(task_gametick, step_time);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'Y': // 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 'Y': painting_1 = true; break;
|
|
|
|
case 'y': painting_1 = false; break;
|
|
|
|
case 'X': painting_0 = true; break;
|
|
|
|
case 'x': 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|