#define _POSIX_C_SOURCE 202511L #include #include #include #include #include "wrmr.h" #include "vui.h" int alive, paused; int flash = 0; u32 frame, score; /* input buffer */ int inbuf[32] = { 0 }; #define INBUF_MASK 0x1f u32 inbuf_start = 0, inbuf_end = 0; int pop_input(void) { /* int k = inbuf[inbuf_start]; inbuf[inbuf_start] = 0; if (k) inbuf_start = (inbuf_start + 1) & INBUF_MASK; return k; */ if (inbuf_start == inbuf_end) return 0; int k = inbuf[inbuf_start++]; inbuf_start &= INBUF_MASK; return k; } void push_input(int i) { inbuf[inbuf_end++] = i; inbuf_end &= INBUF_MASK; } u32 poll_input_iter = 0; int poll_input(u32 ms) { struct timespec start; clock_gettime(CLOCK_MONOTONIC, &start); u64 n = start.tv_nsec + (ms * 1000000); struct timespec stop = { .tv_sec = start.tv_sec + n / 1000000000, .tv_nsec = n % 1000000000 }; poll_input_iter = 0; for (;;) { poll_input_iter++; struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); i64 ns_rem = (stop.tv_sec - now.tv_sec) * 1000000000 + (i64)stop.tv_nsec - (i64)now.tv_nsec; i64 ms_rem = ns_rem / 1000000; if (ns_rem < 1) break; if (vui_wait_for_input(ms_rem)) push_input(vui_key()); else break; } return 0; } /* program */ #define WIDTH 20 #define HEIGHT 20 typedef enum { TILE_FLOOR, TILE_SNK_ODD, TILE_SNK_EVEN, TILE_APPLE, TILE_MAX } Tile; const VuiAttr tile_attr[TILE_MAX][2] = { { FG_BLUE | BG_BLACK, FG_BLUE | BG_BLACK }, { FG_YELLOW | BG_BLACK, FG_BYELLOW | BG_BLACK }, { FG_GREEN | BG_BLACK, FG_BGREEN | BG_BLACK }, { FG_BGREEN | BG_BLACK, FG_BRED | BG_BLACK }, }; const VuiChar tile_chr[TILE_MAX][2] = { { ' ', '.' }, { '[', ']' }, { '[', ']' }, { '`', 'o' } }; Tile board[HEIGHT][WIDTH]; typedef enum { DIR_LEFT, DIR_UP, DIR_RIGHT, DIR_DOWN } Dir; #define DIR_IS_Y(d) ((d)&1) #define DIR_IS_X(d) (~(d)&1) #define DIR_SIGN(d) ((d)&2 ? 1 : -1) #define DIR_FLIP(d) (((d) + 2) & 3) #define DIR_DX(d) (DIR_IS_X(d) * DIR_SIGN(d)) #define DIR_DY(d) (DIR_IS_Y(d) * DIR_SIGN(d)) typedef struct { i16 x, y; } SnkSeg; #define SNK_SEG_MAX (WIDTH * HEIGHT) typedef struct { Dir dir; u32 len; SnkSeg seg[SNK_SEG_MAX]; } Snake; Snake snk = { 0 }; typedef struct { i16 x, y; } Apple; #define APPLE_MAX ((WIDTH * HEIGHT) / 100 + 1) u32 napple = APPLE_MAX; Apple apple[APPLE_MAX]; typedef struct { u64 len; u64 data[(WIDTH * HEIGHT + 63) / 64]; } BitBoard; static inline int bitb_has(BitBoard *b, i16 x, i16 y) { u32 i = y * WIDTH + x; return !!(b->data[i / 64] & (1ULL << (i & 63))); } static inline void bitb_del(BitBoard *b, i16 x, i16 y) { if (bitb_has(b, x, y)) { u32 i = y * WIDTH + x; b->data[i / 64] &= ~(1ULL << (i & 63)); b->len--; } } static inline void bitb_add(BitBoard *b, i16 x, i16 y) { if (!bitb_has(b, x, y)) { u32 i = y * WIDTH + x; b->data[i / 64] |= 1ULL << (i & 63); b->len++; } } static inline int bitb_nth(BitBoard *b, u32 nth, i16 *x, i16 *y) { for (u32 i = 0; i < sizeof b->data * sizeof(u64); i++) { u64 d = b->data[i]; u32 j = i * 64; while (d) { u32 z = stdc_trailing_zeros(d); j += z; if (!nth--) { *x = j % WIDTH; *y = j / WIDTH; return 1; } d >>= z + 1; j += 1; } } /* while (i < WIDTH * HEIGHT) { if (b->data[i / 64] & (1ULL << (i & 63))) { nth--; if (!nth) { *x = i % WIDTH; *y = i / WIDTH; return 1; } } i++; } */ return 0; } static inline void bitb_fill(BitBoard *b, int v) { u64 val = v ? (u64)-1 : 0; for (u32 i = 0; i < sizeof b->data / sizeof *b->data; i++) { b->data[i] = val; } b->len = v ? WIDTH * HEIGHT : 0; } #include void apple_bitboard(BitBoard *b) { bitb_fill(b, 1); for (u32 j = 0; j < snk.len; j++) { bitb_del(b, snk.seg[j].x, snk.seg[j].y); } for (u32 j = 0; j < napple; j++) { bitb_del(b, apple[j].x, apple[j].y); } } u32 last_apple_bb_len = 0; u32 last_apple_pos = 0; void replace_apple(u32 i) { BitBoard bb; //printf("%u after apple removal\n\r", bb.len); //assert(bb.len < WIDTH * HEIGHT); //bitb_del(&bb, WIDTH - 1, HEIGHT - 1); apple_bitboard(&bb); last_apple_bb_len = bb.len; if (bb.len > 0) { Apple *a = &apple[i]; bitb_nth(&bb, rand() % bb.len, &a->x, &a->y); for (u32 j = 0; j < napple; j++) { if (j != i) { //assert(!(apple[j].x == a->x && apple[j].y == a->y)); } } last_apple_pos = a->y * WIDTH + a->x; } else { if (i + 1 < napple) { apple[i] = apple[napple - 1]; } napple--; } } void place_apple(void) { for (u32 i = 0; i < napple; i++) { replace_apple(i); } } void brd_clear(void) { for (u32 y = 0; y < HEIGHT; y++) { for (u32 x = 0; x < WIDTH; x++) { board[y][x] = TILE_FLOOR; } } } void brd_draw(void) { u32 left = COLS / 2 - WIDTH; u32 top = LINES / 2 - HEIGHT / 2; /* u32 left = COLS / 2 - snk.seg->x * 2; u32 top = LINES / 2 - snk.seg->y; */ u32 right = left + WIDTH * 2; u32 bottom = top + HEIGHT; u32 lvl = score / 10; VuiAttr bordatr = A_BOLD | ((FG_BLUE + lvl) & 7); for (u32 y = 0; y < HEIGHT; y++) { vui_chra(left - 1, top + y, u'║', bordatr); vui_chra(right + 1, top + y, u'║', bordatr); for (u32 x = 0; x < WIDTH; x++) { Tile t = board[y][x]; VuiAttr a0 = tile_attr[t][0]; VuiAttr a1 = tile_attr[t][1]; a1 = ((a1 + lvl) & 7) | (a1 & 8); a0 = ((a0 + lvl) & 7) | (a0 & 8); if (t == TILE_FLOOR) { if ((x + y + frame) & 8) a1 |= A_BOLD; } if (flash) a0 = (a1 = FG_BWHITE | BG_BLACK); vui_chra(left + 2 * x, top + y, tile_chr[t][0], a0); vui_chra(left + 2 * x + 1, top + y, tile_chr[t][1], a1); vui_putsa(left + 2 * x, top - 1, "══", bordatr); vui_putsa(left + 2 * x, bottom, "══", bordatr); } } vui_chra(left - 1, top - 1, u'╔', bordatr); vui_chra(right + 1, top - 1, u'╗', bordatr); vui_chra(left - 1, bottom, u'╚', bordatr); vui_chra(right + 1, bottom, u'╝', bordatr); vui_chra(right, bottom, u'═', bordatr); vui_chra(right, top - 1, u'═', bordatr); /* u32 x = 0; u32 y = 1; for (u32 i = 0; i < sizeof inbuf / sizeof *inbuf; i++) { int within = (i >= inbuf_start && i < inbuf_end); if (inbuf_end < inbuf_start) { within = i >= inbuf_start || i < inbuf_end; } VuiAttr a = within ? FG_BCYAN : FG_BBLACK; if (i == inbuf_start) vui_chra(x, bottom + y - 1, 'v', FG_BCYAN); if (i == inbuf_end) vui_chra(x + 1, bottom + y - 1, 'v', FG_BMAGENTA); x += vui_aprintf(x, bottom + y, a, "%6X ", inbuf[i]); if (x >= COLS - 24) { x = 0; y += 2; } } */ flash = 0; } void snk_draw(Snake *s) { for (u32 i = 0; i < s->len; i++) { board[s->seg[i].y][s->seg[i].x] = (i / 4) & 1 ? TILE_SNK_ODD : TILE_SNK_EVEN; } } void apple_draw(void) { for (u32 i = 0; i < napple; i++) { board[apple[i].y][apple[i].x] = TILE_APPLE; } } void draw(void *ctx) { (void)ctx; vui_clear(); brd_clear(); apple_draw(); snk_draw(&snk); brd_draw(); if (!alive) { vui_puts(COLS/2 - 4, LINES/2, "YOU DIED!"); } vui_printf(0, 0, "input polling iterations: %u", poll_input_iter); vui_printf(0, 1, "apples: %u", napple); vui_printf(0, 2, "last apple bitboard len: %u", last_apple_bb_len); vui_printf(0, 3, "last apple position: %u, %u", last_apple_pos % WIDTH, last_apple_pos / WIDTH); vui_printf(-1, 0, "snake pos: %hd, %hd", snk.seg->x, snk.seg->y); BitBoard bb; apple_bitboard(&bb); i16 x, y; int r = bitb_nth(&bb, 0, &x, &y); vui_printf(0, 4, "0th bitboard free space (%d): %hd, %hd / %u", r, x, y, bb.len); for (u32 i = 0; i < sizeof bb.data / sizeof *bb.data; i++) { vui_printf(0, 6 + i, "%016lX", bb.data[i]); } } void update(void) { for (u32 i = 0; i < napple; i++) { if (snk.seg[0].x == apple[i].x && snk.seg[0].y == apple[i].y) { flash = 1; replace_apple(i); snk.len++; score++; break; } } memmove(snk.seg + 1, snk.seg, (SNK_SEG_MAX - 1) * sizeof(SnkSeg)); i16 x = snk.seg[0].x + DIR_DX(snk.dir); i16 y = snk.seg[0].y + DIR_DY(snk.dir); if (x < 0 || y < 0 || x >= WIDTH || y >= HEIGHT) { alive = 0; return; } for (u32 i = 1; i < snk.len; i++) { if (snk.seg[i].x == x && snk.seg[i].y == y) { alive = 0; return; } } snk.seg[0].x = x; snk.seg[0].y = y; //vui_scroll(-DIR_DX(snk.dir) * 2, -DIR_DY(snk.dir)); } void snk_chg_dir(Snake *s, Dir d) { if (paused) return; if (s->len < 2 || d != DIR_FLIP(s->dir)) { s->dir = d; } } /* game */ int main(void) { srand(time(NULL)); vui_init(); vui_redraw_fn(draw); snk.dir = DIR_RIGHT; snk.len = 0; snk.seg[snk.len++] = (SnkSeg) { 0, 0 }; alive = 1; paused = 0; napple = APPLE_MAX; place_apple(); frame = 0; score = 0; while (alive) { frame++; poll_input(33); draw(NULL); vui_blit(); switch (pop_input()) { case 0: break; case KEY_ESC: goto done; case KEY_LEFT : case 'j': snk_chg_dir(&snk, DIR_LEFT); break; case KEY_UP : case 'i': snk_chg_dir(&snk, DIR_UP); break; case KEY_RIGHT: case 'l': snk_chg_dir(&snk, DIR_RIGHT); break; case KEY_DOWN : case 'k': snk_chg_dir(&snk, DIR_DOWN); break; case ' ': paused = !paused; break; } if (!paused) update(); } draw(NULL); vui_blit(); (void)vui_key(); done: vui_fini(); return 0; }