parent
d4e386daa9
commit
7fe3863f1b
@ -1,21 +1,469 @@ |
|||||||
#include "mode_life.h" |
#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) |
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) |
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) |
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) |
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); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
} |
} |
||||||
|
Loading…
Reference in new issue