2026-10-23 14:29:48 +03:00
|
|
|
// snake.cpp - classic Nokia-style Snake for Bananarchy (Sxmo/dwm, 240x296)
|
|
|
|
|
// Standalone X11 application in C++. Self-contained: uses a built-in 5x7
|
|
|
|
|
// bitmap font so it has no font-file dependency.
|
|
|
|
|
//
|
|
|
|
|
// Controls (Nokia keypad + D-pad):
|
|
|
|
|
// 2 / Up -> turn up 8 / Down -> turn down
|
|
|
|
|
// 4 / Left -> turn left 6 / Right -> turn right
|
|
|
|
|
// 5 / OK / Enter -> pause / restart (after game over)
|
|
|
|
|
// Back / Esc -> quit
|
|
|
|
|
//
|
|
|
|
|
// Build: g++ -O2 snake.cpp -lX11 -o snake
|
|
|
|
|
|
|
|
|
|
#include <X11/Xlib.h>
|
|
|
|
|
#include <cstdio>
|
|
|
|
|
#include <cstdlib>
|
|
|
|
|
#include <cstring>
|
|
|
|
|
#include <ctime>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
#include <sys/select.h>
|
|
|
|
|
#include <sys/time.h>
|
|
|
|
|
#include <X11/Xutil.h>
|
|
|
|
|
#include <X11/keysym.h>
|
|
|
|
|
#include <X11/Xatom.h>
|
|
|
|
|
|
2026-10-23 16:07:35 +03:00
|
|
|
static const int WIN_W = 239;
|
|
|
|
|
static const int WIN_H = 295;
|
2026-10-23 14:29:48 +03:00
|
|
|
static const int COLS = 24;
|
|
|
|
|
static const int ROWS = 28;
|
|
|
|
|
|
|
|
|
|
// ---- 5x7 bitmap font (rows are 5-bit patterns, MSB = leftmost pixel) ----
|
|
|
|
|
struct Glyph { char ch; unsigned char r[7]; };
|
|
|
|
|
static const Glyph GLYPHS[] = {
|
|
|
|
|
{' ', {0,0,0,0,0,0,0}},
|
|
|
|
|
{'!', {0b00100,0b00100,0b00100,0b00100,0b00100,0,0b00100}},
|
|
|
|
|
{'.', {0,0,0,0,0,0b00100,0b00100}},
|
|
|
|
|
{'-', {0,0,0,0b11111,0,0,0}},
|
|
|
|
|
{':', {0,0b00100,0b00100,0,0b00100,0b00100,0}},
|
|
|
|
|
{'?', {0b01110,0b10001,0b00001,0b00110,0b00100,0,0b00100}},
|
|
|
|
|
{'A', {0b01110,0b10001,0b10001,0b11111,0b10001,0b10001,0b10001}},
|
|
|
|
|
{'B', {0b11110,0b10001,0b10001,0b11110,0b10001,0b10001,0b11110}},
|
|
|
|
|
{'C', {0b01110,0b10001,0b10000,0b10000,0b10000,0b10001,0b01110}},
|
|
|
|
|
{'D', {0b11110,0b10001,0b10001,0b10001,0b10001,0b10001,0b11110}},
|
|
|
|
|
{'E', {0b11111,0b10000,0b10000,0b11110,0b10000,0b10000,0b11111}},
|
|
|
|
|
{'F', {0b11111,0b10000,0b10000,0b11110,0b10000,0b10000,0b10000}},
|
|
|
|
|
{'G', {0b01110,0b10001,0b10000,0b10111,0b10001,0b10001,0b01111}},
|
|
|
|
|
{'H', {0b10001,0b10001,0b10001,0b11111,0b10001,0b10001,0b10001}},
|
|
|
|
|
{'I', {0b01110,0b00100,0b00100,0b00100,0b00100,0b00100,0b01110}},
|
|
|
|
|
{'K', {0b10001,0b10010,0b10100,0b11000,0b10100,0b10010,0b10001}},
|
|
|
|
|
{'L', {0b10000,0b10000,0b10000,0b10000,0b10000,0b10000,0b11111}},
|
|
|
|
|
{'M', {0b10001,0b11011,0b10101,0b10101,0b10001,0b10001,0b10001}},
|
|
|
|
|
{'N', {0b10001,0b10001,0b11001,0b10101,0b10011,0b10001,0b10001}},
|
|
|
|
|
{'O', {0b01110,0b10001,0b10001,0b10001,0b10001,0b10001,0b01110}},
|
|
|
|
|
{'P', {0b11110,0b10001,0b10001,0b11110,0b10000,0b10000,0b10000}},
|
|
|
|
|
{'R', {0b11110,0b10001,0b10001,0b11110,0b10100,0b10010,0b10001}},
|
|
|
|
|
{'S', {0b01111,0b10000,0b10000,0b01110,0b00001,0b00001,0b11110}},
|
|
|
|
|
{'T', {0b11111,0b00100,0b00100,0b00100,0b00100,0b00100,0b00100}},
|
|
|
|
|
{'V', {0b10001,0b10001,0b10001,0b10001,0b10001,0b01010,0b00100}},
|
|
|
|
|
{'W', {0b10001,0b10001,0b10001,0b10101,0b10101,0b11011,0b10001}},
|
|
|
|
|
{'Y', {0b10001,0b10001,0b01010,0b00100,0b00100,0b00100,0b00100}},
|
2026-10-23 15:30:29 +03:00
|
|
|
{'J', {0b00010,0b00010,0b00010,0b00010,0b10010,0b10010,0b01100}},
|
|
|
|
|
{'Q', {0b01110,0b10001,0b10001,0b10001,0b10101,0b10010,0b01101}},
|
|
|
|
|
{'U', {0b10001,0b10001,0b10001,0b10001,0b10001,0b10001,0b01110}},
|
|
|
|
|
{'Z', {0b11111,0b00010,0b00100,0b01000,0b10000,0b10000,0b11111}},
|
2026-10-23 14:29:48 +03:00
|
|
|
{'0', {0b01110,0b10011,0b10101,0b10101,0b11001,0b10001,0b01110}},
|
|
|
|
|
{'1', {0b00100,0b01100,0b00100,0b00100,0b00100,0b00100,0b01110}},
|
|
|
|
|
{'2', {0b01110,0b10001,0b00001,0b00110,0b01000,0b10000,0b11111}},
|
|
|
|
|
{'3', {0b11110,0b00001,0b00001,0b01110,0b00001,0b00001,0b11110}},
|
|
|
|
|
{'4', {0b00010,0b00110,0b01010,0b10010,0b11111,0b00010,0b00010}},
|
|
|
|
|
{'5', {0b11111,0b10000,0b11110,0b00001,0b00001,0b10001,0b01110}},
|
|
|
|
|
{'6', {0b00110,0b01000,0b10000,0b11110,0b10001,0b10001,0b01110}},
|
|
|
|
|
{'7', {0b11111,0b00001,0b00010,0b00100,0b01000,0b01000,0b01000}},
|
|
|
|
|
{'8', {0b01110,0b10001,0b10001,0b01110,0b10001,0b10001,0b01110}},
|
|
|
|
|
{'9', {0b01110,0b10001,0b10001,0b01111,0b00001,0b00010,0b01100}},
|
|
|
|
|
};
|
|
|
|
|
static const int NGLYPH = int(sizeof(GLYPHS) / sizeof(GLYPHS[0]));
|
|
|
|
|
|
|
|
|
|
static const unsigned char* glyph(char c) {
|
|
|
|
|
for (int i = 0; i < NGLYPH; i++)
|
|
|
|
|
if (GLYPHS[i].ch == c) return GLYPHS[i].r;
|
|
|
|
|
return GLYPHS[0].r; // space
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---- X11 state ----
|
|
|
|
|
static Display* dpy = nullptr;
|
|
|
|
|
static Window win;
|
|
|
|
|
static GC gc;
|
|
|
|
|
static Pixmap back;
|
|
|
|
|
static int screen;
|
|
|
|
|
static int winW = WIN_W, winH = WIN_H;
|
|
|
|
|
static int cell, gx0, gy0, gw, gh;
|
|
|
|
|
|
|
|
|
|
// ---- colors (Nokia green monochrome) ----
|
|
|
|
|
static unsigned long C_BG, C_FG, C_HEAD, C_FOOD, C_DIM;
|
|
|
|
|
|
|
|
|
|
// ---- game state ----
|
|
|
|
|
enum State { PLAYING, PAUSED, GAMEOVER };
|
|
|
|
|
static State state = PLAYING;
|
|
|
|
|
static int snake[COLS * ROWS][2]; // x,y per segment (0 = tail)
|
|
|
|
|
static int snakelen = 4;
|
|
|
|
|
static int dirx = 1, diry = 0; // current direction
|
|
|
|
|
static int ndirx = 1, ndiry = 0; // buffered next direction
|
|
|
|
|
static int foodx, foody;
|
|
|
|
|
static int score = 0;
|
|
|
|
|
static int best = 0;
|
|
|
|
|
static int speed = 130; // ms per step (speeds up with score)
|
|
|
|
|
static unsigned long nextTick = 0;
|
|
|
|
|
static bool running = true;
|
|
|
|
|
static unsigned long now_ms() {
|
|
|
|
|
struct timeval tv;
|
|
|
|
|
gettimeofday(&tv, nullptr);
|
|
|
|
|
return (unsigned long)(tv.tv_sec * 1000 + tv.tv_usec / 1000);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void compute_grid() {
|
|
|
|
|
int m = 4;
|
2026-10-23 16:07:35 +03:00
|
|
|
int topH = 16; // reserved status strip for SCORE/BEST
|
2026-10-23 14:29:48 +03:00
|
|
|
int aw = winW - 2 * m;
|
2026-10-23 16:07:35 +03:00
|
|
|
int ah = winH - topH - m;
|
2026-10-23 14:29:48 +03:00
|
|
|
cell = aw / COLS;
|
|
|
|
|
if ((ah / ROWS) < cell) cell = ah / ROWS;
|
|
|
|
|
if (cell < 4) cell = 4;
|
|
|
|
|
gw = cell * COLS;
|
|
|
|
|
gh = cell * ROWS;
|
|
|
|
|
gx0 = (winW - gw) / 2;
|
2026-10-23 16:07:35 +03:00
|
|
|
gy0 = topH + (ah - gh) / 2;
|
2026-10-23 14:29:48 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void place_food() {
|
|
|
|
|
bool occ[COLS * ROWS];
|
|
|
|
|
for (int i = 0; i < COLS * ROWS; i++) occ[i] = false;
|
|
|
|
|
for (int i = 0; i < snakelen; i++) occ[snake[i][0] + snake[i][1] * COLS] = true;
|
|
|
|
|
int freecnt = 0;
|
|
|
|
|
for (int i = 0; i < COLS * ROWS; i++) if (!occ[i]) freecnt++;
|
|
|
|
|
if (freecnt == 0) { foodx = -1; foody = -1; return; }
|
|
|
|
|
int pick = (int)(rand() % freecnt);
|
|
|
|
|
int idx = 0;
|
|
|
|
|
for (int i = 0; i < COLS * ROWS; i++) {
|
|
|
|
|
if (!occ[i]) {
|
|
|
|
|
if (idx == pick) { foodx = i % COLS; foody = i / COLS; return; }
|
|
|
|
|
idx++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void reset_game() {
|
|
|
|
|
snakelen = 4;
|
|
|
|
|
int cx = COLS / 2, cy = ROWS / 2;
|
|
|
|
|
for (int i = 0; i < snakelen; i++) {
|
2026-10-23 14:57:59 +03:00
|
|
|
snake[i][0] = cx - (snakelen - 1 - i); // head at cx, tail to the left
|
2026-10-23 14:29:48 +03:00
|
|
|
snake[i][1] = cy;
|
|
|
|
|
}
|
|
|
|
|
dirx = 1; diry = 0;
|
|
|
|
|
ndirx = 1; ndiry = 0;
|
|
|
|
|
score = 0;
|
|
|
|
|
speed = 130;
|
|
|
|
|
place_food();
|
|
|
|
|
state = PLAYING;
|
|
|
|
|
nextTick = now_ms() + speed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void step() {
|
|
|
|
|
// apply buffered direction (no reversing)
|
|
|
|
|
if (!(ndirx == -dirx && ndiry == -diry) && !(ndirx == 0 && ndiry == 0)) {
|
|
|
|
|
dirx = ndirx; diry = ndiry;
|
|
|
|
|
}
|
|
|
|
|
int hx = snake[snakelen - 1][0] + dirx;
|
|
|
|
|
int hy = snake[snakelen - 1][1] + diry;
|
|
|
|
|
if (hx < 0 || hx >= COLS || hy < 0 || hy >= ROWS) { state = GAMEOVER; return; }
|
|
|
|
|
// self collision (ignore current tail cell since it will move, unless growing)
|
|
|
|
|
for (int i = 0; i < snakelen - 1; i++) {
|
|
|
|
|
if (snake[i][0] == hx && snake[i][1] == hy) { state = GAMEOVER; return; }
|
|
|
|
|
}
|
|
|
|
|
bool ate = (hx == foodx && hy == foody);
|
|
|
|
|
if (!ate) {
|
|
|
|
|
// shift body toward tail
|
|
|
|
|
for (int i = 0; i < snakelen - 1; i++) {
|
|
|
|
|
snake[i][0] = snake[i + 1][0];
|
|
|
|
|
snake[i][1] = snake[i + 1][1];
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (snakelen < COLS * ROWS) snakelen++;
|
|
|
|
|
}
|
|
|
|
|
snake[snakelen - 1][0] = hx;
|
|
|
|
|
snake[snakelen - 1][1] = hy;
|
|
|
|
|
if (ate) {
|
|
|
|
|
score += 10;
|
|
|
|
|
if (score > best) best = score;
|
|
|
|
|
int ns = 130 - (score / 50) * 8;
|
|
|
|
|
if (ns < 60) ns = 60;
|
|
|
|
|
speed = ns;
|
|
|
|
|
place_food();
|
|
|
|
|
}
|
|
|
|
|
nextTick = now_ms() + speed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void draw_text(int x, int y, int scale, unsigned long col, const char* s) {
|
|
|
|
|
int cx = x;
|
|
|
|
|
for (const char* p = s; *p; p++) {
|
|
|
|
|
const unsigned char* g = glyph(*p);
|
|
|
|
|
for (int r = 0; r < 7; r++) {
|
|
|
|
|
unsigned char row = g[r];
|
|
|
|
|
for (int b = 0; b < 5; b++) {
|
|
|
|
|
if (row & (1 << (4 - b))) {
|
|
|
|
|
XSetForeground(dpy, gc, col);
|
|
|
|
|
XFillRectangle(dpy, back, gc, cx + b * scale, y + r * scale, scale, scale);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
cx += 6 * scale;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void render() {
|
|
|
|
|
XSetForeground(dpy, gc, C_BG);
|
|
|
|
|
XFillRectangle(dpy, back, gc, 0, 0, winW, winH);
|
|
|
|
|
|
2026-10-23 16:07:35 +03:00
|
|
|
// playfield border (single clean frame around the grid)
|
2026-10-23 14:29:48 +03:00
|
|
|
XSetForeground(dpy, gc, C_DIM);
|
|
|
|
|
XDrawRectangle(dpy, back, gc, gx0 - 1, gy0 - 1, gw + 2, gh + 2);
|
|
|
|
|
|
|
|
|
|
// food
|
|
|
|
|
if (foodx >= 0) {
|
|
|
|
|
XSetForeground(dpy, gc, C_FOOD);
|
|
|
|
|
XFillRectangle(dpy, back, gc, gx0 + foodx * cell, gy0 + foody * cell, cell, cell);
|
|
|
|
|
}
|
|
|
|
|
// snake
|
|
|
|
|
for (int i = 0; i < snakelen; i++) {
|
|
|
|
|
XSetForeground(dpy, gc, (i == snakelen - 1) ? C_HEAD : C_FG);
|
|
|
|
|
XFillRectangle(dpy, back, gc, gx0 + snake[i][0] * cell, gy0 + snake[i][1] * cell, cell, cell);
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-23 16:07:35 +03:00
|
|
|
// score (top status strip, clear of the playfield border)
|
2026-10-23 14:29:48 +03:00
|
|
|
char buf[32];
|
|
|
|
|
snprintf(buf, sizeof(buf), "SCORE %d", score);
|
2026-10-23 16:07:35 +03:00
|
|
|
draw_text(gx0, 4, 1, C_DIM, buf);
|
2026-10-23 14:29:48 +03:00
|
|
|
if (best > 0) {
|
|
|
|
|
snprintf(buf, sizeof(buf), "BEST %d", best);
|
2026-10-23 16:07:35 +03:00
|
|
|
draw_text(gx0 + gw - 11 * 6, 4, 1, C_DIM, buf);
|
2026-10-23 14:29:48 +03:00
|
|
|
}
|
|
|
|
|
|
2026-10-23 15:30:29 +03:00
|
|
|
if (state == PAUSED || state == GAMEOVER) {
|
|
|
|
|
XSetForeground(dpy, gc, C_BG);
|
|
|
|
|
XFillRectangle(dpy, back, gc, 0, 0, winW, winH);
|
|
|
|
|
}
|
2026-10-23 14:29:48 +03:00
|
|
|
if (state == PAUSED) {
|
2026-10-23 15:30:29 +03:00
|
|
|
draw_text(winW / 2 - 5 * 6, winH / 2 - 7 * 2, 2, C_FG, "PAUSE");
|
|
|
|
|
draw_text(winW / 2 - 4 * 6, winH / 2 + 7 * 2, 1, C_DIM, "5 RESUME");
|
2026-10-23 14:29:48 +03:00
|
|
|
} else if (state == GAMEOVER) {
|
2026-10-23 15:30:29 +03:00
|
|
|
draw_text(winW / 2 - 9 * 6, winH / 2 - 14, 2, C_FG, "GAME OVER");
|
2026-10-23 14:29:48 +03:00
|
|
|
snprintf(buf, sizeof(buf), "SCORE %d", score);
|
|
|
|
|
draw_text(winW / 2 - 5 * 6, winH / 2 + 4, 1, C_DIM, buf);
|
2026-10-23 15:30:29 +03:00
|
|
|
draw_text(winW / 2 - 5 * 6, winH / 2 + 4 + 9, 1, C_DIM, "5 NEW GAME");
|
2026-10-23 14:29:48 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
XCopyArea(dpy, back, win, gc, 0, 0, winW, winH, 0, 0);
|
|
|
|
|
XFlush(dpy);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void handle_key(KeySym ks) {
|
|
|
|
|
switch (ks) {
|
|
|
|
|
case XK_Up: case XK_w: case XK_KP_Up: case XK_2:
|
|
|
|
|
ndirx = 0; ndiry = -1; break;
|
|
|
|
|
case XK_Down: case XK_s: case XK_KP_Down: case XK_8: case XK_x:
|
|
|
|
|
ndirx = 0; ndiry = 1; break;
|
|
|
|
|
case XK_Left: case XK_a: case XK_KP_Left: case XK_4:
|
|
|
|
|
ndirx = -1; ndiry = 0; break;
|
|
|
|
|
case XK_Right: case XK_d: case XK_KP_Right: case XK_6:
|
|
|
|
|
ndirx = 1; ndiry = 0; break;
|
|
|
|
|
case XK_5: case XK_Return: case XK_KP_Enter: case XK_space: case XK_p:
|
|
|
|
|
if (state == PLAYING) state = PAUSED;
|
|
|
|
|
else if (state == PAUSED) { state = PLAYING; nextTick = now_ms() + speed; }
|
|
|
|
|
else if (state == GAMEOVER) reset_game();
|
|
|
|
|
break;
|
|
|
|
|
case XK_Escape: case XK_BackSpace: case XK_Delete: case XK_End:
|
|
|
|
|
case XK_Menu:
|
|
|
|
|
// quit is handled by the caller via a flag
|
|
|
|
|
running = false;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
if (state == GAMEOVER) reset_game();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void setup_window() {
|
|
|
|
|
dpy = XOpenDisplay(nullptr);
|
|
|
|
|
if (!dpy) { fprintf(stderr, "snake: cannot open display\n"); exit(1); }
|
|
|
|
|
screen = DefaultScreen(dpy);
|
|
|
|
|
win = XCreateWindow(dpy, RootWindow(dpy, screen), 0, 0, winW, winH, 0,
|
|
|
|
|
CopyFromParent, InputOutput, CopyFromParent, 0, nullptr);
|
|
|
|
|
XStoreName(dpy, win, "Snake");
|
|
|
|
|
XClassHint* ch = XAllocClassHint();
|
|
|
|
|
if (ch) { ch->res_name = (char*)"snake"; ch->res_class = (char*)"snake"; XSetClassHint(dpy, win, ch); XFree(ch); }
|
|
|
|
|
|
|
|
|
|
// float as a dialog so dwm doesn't tile it
|
|
|
|
|
Atom wtype = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False);
|
|
|
|
|
Atom dialog = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DIALOG", False);
|
|
|
|
|
XChangeProperty(dpy, win, wtype, XA_ATOM, 32, PropModeReplace, (unsigned char*)&dialog, 1);
|
|
|
|
|
|
|
|
|
|
// fixed size hints
|
|
|
|
|
XSizeHints sh;
|
|
|
|
|
sh.flags = PMinSize | PMaxSize | PSize;
|
|
|
|
|
sh.min_width = sh.max_width = winW;
|
|
|
|
|
sh.min_height = sh.max_height = winH;
|
|
|
|
|
XSetWMNormalHints(dpy, win, &sh);
|
|
|
|
|
|
|
|
|
|
Atom wm_del = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
|
|
|
|
|
XSetWMProtocols(dpy, win, &wm_del, 1);
|
|
|
|
|
|
|
|
|
|
gc = XCreateGC(dpy, win, 0, nullptr);
|
|
|
|
|
back = XCreatePixmap(dpy, win, winW, winH, DefaultDepth(dpy, screen));
|
|
|
|
|
|
|
|
|
|
C_BG = BlackPixel(dpy, screen);
|
|
|
|
|
// Nokia green monochrome
|
|
|
|
|
C_FG = 0x33ff33;
|
|
|
|
|
C_HEAD = 0x99ff99;
|
|
|
|
|
C_FOOD = 0x66ff66;
|
2026-10-23 14:57:59 +03:00
|
|
|
C_DIM = 0x2f9f2f;
|
2026-10-23 14:29:48 +03:00
|
|
|
|
|
|
|
|
XSelectInput(dpy, win, KeyPressMask | StructureNotifyMask | ExposureMask);
|
|
|
|
|
XMapWindow(dpy, win);
|
|
|
|
|
compute_grid();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void handle_event(XEvent& e) {
|
|
|
|
|
if (e.type == ClientMessage) {
|
|
|
|
|
Atom wm_del = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
|
|
|
|
|
if ((Atom)e.xclient.data.l[0] == wm_del) running = false;
|
|
|
|
|
} else if (e.type == ConfigureNotify) {
|
|
|
|
|
if (e.xconfigure.width != winW || e.xconfigure.height != winH) {
|
|
|
|
|
winW = e.xconfigure.width; winH = e.xconfigure.height;
|
|
|
|
|
compute_grid();
|
|
|
|
|
}
|
|
|
|
|
} else if (e.type == Expose) {
|
|
|
|
|
render();
|
|
|
|
|
} else if (e.type == KeyPress) {
|
|
|
|
|
char buf[8];
|
|
|
|
|
KeySym ks = 0;
|
|
|
|
|
XLookupString(&e.xkey, buf, sizeof(buf), &ks, nullptr);
|
|
|
|
|
if (ks == NoSymbol) ks = XLookupKeysym(&e.xkey, 0);
|
|
|
|
|
if (ks == XK_q) { running = false; return; }
|
|
|
|
|
handle_key(ks);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int main() {
|
|
|
|
|
srand((unsigned)time(nullptr) ^ (unsigned)getpid());
|
|
|
|
|
setup_window();
|
|
|
|
|
reset_game();
|
|
|
|
|
render();
|
|
|
|
|
|
|
|
|
|
int xfd = ConnectionNumber(dpy);
|
|
|
|
|
while (running) {
|
|
|
|
|
long until = (long)nextTick - (long)now_ms();
|
|
|
|
|
if (until < 0) until = 0;
|
|
|
|
|
struct timeval tv;
|
|
|
|
|
tv.tv_sec = until / 1000;
|
|
|
|
|
tv.tv_usec = (until % 1000) * 1000;
|
|
|
|
|
fd_set fds;
|
|
|
|
|
FD_ZERO(&fds);
|
|
|
|
|
FD_SET(xfd, &fds);
|
|
|
|
|
int r = select(xfd + 1, &fds, nullptr, nullptr, &tv);
|
2026-10-23 15:47:30 +03:00
|
|
|
bool dirty = false;
|
2026-10-23 14:29:48 +03:00
|
|
|
if (r > 0) {
|
|
|
|
|
XEvent e;
|
|
|
|
|
while (XPending(dpy)) {
|
|
|
|
|
XNextEvent(dpy, &e);
|
|
|
|
|
handle_event(e);
|
|
|
|
|
if (!running) break;
|
|
|
|
|
}
|
|
|
|
|
if (!running) break;
|
2026-10-23 15:47:30 +03:00
|
|
|
dirty = true;
|
2026-10-23 14:29:48 +03:00
|
|
|
}
|
|
|
|
|
if (running && now_ms() >= nextTick) {
|
|
|
|
|
if (state == PLAYING) step();
|
|
|
|
|
if (state == PLAYING) nextTick = now_ms() + speed;
|
|
|
|
|
else nextTick = now_ms() + 80; // keep loop responsive while paused/over
|
2026-10-23 15:47:30 +03:00
|
|
|
dirty = true;
|
2026-10-23 14:29:48 +03:00
|
|
|
}
|
2026-10-23 15:47:30 +03:00
|
|
|
if (dirty) render();
|
2026-10-23 14:29:48 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
XFreeGC(dpy, gc);
|
|
|
|
|
XFreePixmap(dpy, back);
|
|
|
|
|
XDestroyWindow(dpy, win);
|
|
|
|
|
XCloseDisplay(dpy);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|