// 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 #include #include #include #include #include #include #include #include #include #include static const int WIN_W = 239; static const int WIN_H = 295; 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}}, {'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}}, {'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; int topH = 16; // reserved status strip for SCORE/BEST int aw = winW - 2 * m; int ah = winH - topH - m; 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; gy0 = topH + (ah - gh) / 2; } 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++) { snake[i][0] = cx - (snakelen - 1 - i); // head at cx, tail to the left 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); // playfield border (single clean frame around the grid) 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); } // score (top status strip, clear of the playfield border) char buf[32]; snprintf(buf, sizeof(buf), "SCORE %d", score); draw_text(gx0, 4, 1, C_DIM, buf); if (best > 0) { snprintf(buf, sizeof(buf), "BEST %d", best); draw_text(gx0 + gw - 11 * 6, 4, 1, C_DIM, buf); } if (state == PAUSED || state == GAMEOVER) { XSetForeground(dpy, gc, C_BG); XFillRectangle(dpy, back, gc, 0, 0, winW, winH); } if (state == PAUSED) { 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"); } else if (state == GAMEOVER) { draw_text(winW / 2 - 9 * 6, winH / 2 - 14, 2, C_FG, "GAME OVER"); snprintf(buf, sizeof(buf), "SCORE %d", score); draw_text(winW / 2 - 5 * 6, winH / 2 + 4, 1, C_DIM, buf); draw_text(winW / 2 - 5 * 6, winH / 2 + 4 + 9, 1, C_DIM, "5 NEW GAME"); } 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; C_DIM = 0x2f9f2f; 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); bool dirty = false; if (r > 0) { XEvent e; while (XPending(dpy)) { XNextEvent(dpy, &e); handle_event(e); if (!running) break; } if (!running) break; dirty = true; } 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 dirty = true; } if (dirty) render(); } XFreeGC(dpy, gc); XFreePixmap(dpy, back); XDestroyWindow(dpy, win); XCloseDisplay(dpy); return 0; }