games: snappier Snake turns + Gameboy-style Tetris blocks
Snake: add a turn queue (up to 4 pending direction changes) so rapid key presses are never lost, and raise base speed from 70->130 px/sec (~128ms -> ~69ms per step) for snappier, more responsive play. Progressive difficulty (+1 px/sec per 5 food) is unchanged. Tetris: replace solid-green blocks with Gameboy-style 4-shade beveled cells (pale top/left highlight, medium body, dark bottom/right shadow). Settled blocks use C_GB_MED, the active piece uses C_GB_LIGHT, and the next-piece preview is beveled too.
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+27
-12
@@ -102,12 +102,13 @@ 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 tqx[4], tqy[4]; // queued pending turns (rapid presses not lost)
|
||||
static int tqh = 0, tqt = 0, tqn = 0;
|
||||
static int foodx, foody;
|
||||
static int score = 0;
|
||||
static int best = 0;
|
||||
static int speed = 90; // ms per step (derived from pps below)
|
||||
static int pps = 100; // base speed px/sec; +1 px/sec per 5 food eaten
|
||||
static int pps = 130; // base speed px/sec; +1 px/sec per 5 food eaten
|
||||
static unsigned long nextTick = 0;
|
||||
static bool running = true;
|
||||
static unsigned long now_ms() {
|
||||
@@ -163,19 +164,33 @@ static void reset_game() {
|
||||
snake[i][1] = cy;
|
||||
}
|
||||
dirx = 1; diry = 0;
|
||||
ndirx = 1; ndiry = 0;
|
||||
tqh = tqt = tqn = 0;
|
||||
score = 0;
|
||||
pps = 100;
|
||||
pps = 130;
|
||||
update_speed();
|
||||
place_food();
|
||||
state = PLAYING;
|
||||
nextTick = now_ms() + speed;
|
||||
}
|
||||
|
||||
// Queue a pending direction change; rapid key presses are stored so none are lost.
|
||||
static void tq_push(int dx, int dy) {
|
||||
if (tqn >= 4) return; // queue full: drop the new turn
|
||||
tqx[(tqh + tqn) % 4] = dx;
|
||||
tqy[(tqh + tqn) % 4] = dy;
|
||||
tqn++;
|
||||
}
|
||||
|
||||
static void step() {
|
||||
// apply buffered direction (no reversing)
|
||||
if (!(ndirx == -dirx && ndiry == -diry) && !(ndirx == 0 && ndiry == 0)) {
|
||||
dirx = ndirx; diry = ndiry;
|
||||
// apply the oldest queued turn (no reversing); rapid presses are not lost
|
||||
int tdx = 0, tdy = 0;
|
||||
if (tqn > 0) {
|
||||
tdx = tqx[tqt]; tdy = tqy[tqt];
|
||||
tqt = (tqt + 1) % 4;
|
||||
tqn--;
|
||||
}
|
||||
if (!(tdx == -dirx && tdy == -diry) && !(tdx == 0 && tdy == 0)) {
|
||||
dirx = tdx; diry = tdy;
|
||||
}
|
||||
int hx = snake[snakelen - 1][0] + dirx;
|
||||
int hy = snake[snakelen - 1][1] + diry;
|
||||
@@ -202,7 +217,7 @@ static void step() {
|
||||
score += 10;
|
||||
if (score > best) best = score;
|
||||
int food = score / 10; // food items eaten so far
|
||||
pps = 70 + food / 5; // +1 px/sec per 5 food eaten (progressive difficulty)
|
||||
pps = 130 + food / 5; // +1 px/sec per 5 food eaten (progressive difficulty)
|
||||
update_speed();
|
||||
place_food();
|
||||
}
|
||||
@@ -273,13 +288,13 @@ static void render() {
|
||||
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;
|
||||
tq_push(0, -1); break;
|
||||
case XK_Down: case XK_s: case XK_KP_Down: case XK_8: case XK_x:
|
||||
ndirx = 0; ndiry = 1; break;
|
||||
tq_push(0, 1); break;
|
||||
case XK_Left: case XK_a: case XK_KP_Left: case XK_4:
|
||||
ndirx = -1; ndiry = 0; break;
|
||||
tq_push(-1, 0); break;
|
||||
case XK_Right: case XK_d: case XK_KP_Right: case XK_6:
|
||||
ndirx = 1; ndiry = 0; break;
|
||||
tq_push(1, 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; }
|
||||
|
||||
+23
-8
@@ -93,6 +93,7 @@ static Pixmap back;
|
||||
static int screen;
|
||||
static int winW = 240, winH = 296; // overridden at runtime from the actual screen
|
||||
static unsigned long C_BG, C_FG, C_HEAD, C_FOOD, C_DIM;
|
||||
static unsigned long C_GB_DARK, C_GB_MED, C_GB_LIGHT, C_GB_PALE;
|
||||
|
||||
// ---- tetromino definitions (state 0 within a 4x4 box) ----
|
||||
// type 0..6 -> I,O,T,S,Z,J,L
|
||||
@@ -263,6 +264,19 @@ static void draw_text(int x, int y, int scale, unsigned long col, const char* s)
|
||||
}
|
||||
}
|
||||
|
||||
// Draw a Gameboy-style beveled cell: medium body, pale top/left highlight,
|
||||
// dark bottom/right shadow. bodycol is the fill shade (settled vs active).
|
||||
static void draw_gb_cell(int x, int y, int size, unsigned long bodycol) {
|
||||
XSetForeground(dpy, gc, bodycol);
|
||||
XFillRectangle(dpy, back, gc, x + 1, y + 1, size - 2, size - 2);
|
||||
XSetForeground(dpy, gc, C_GB_PALE);
|
||||
XFillRectangle(dpy, back, gc, x + 1, y, size - 2, 1);
|
||||
XFillRectangle(dpy, back, gc, x, y + 1, 1, size - 2);
|
||||
XSetForeground(dpy, gc, C_GB_DARK);
|
||||
XFillRectangle(dpy, back, gc, x + 1, y + size - 1, size - 2, 1);
|
||||
XFillRectangle(dpy, back, gc, x + size - 1, y + 1, 1, size - 2);
|
||||
}
|
||||
|
||||
static void render() {
|
||||
int px0 = 8, py0 = 8;
|
||||
XSetForeground(dpy, gc, C_BG);
|
||||
@@ -275,21 +289,18 @@ static void render() {
|
||||
// settled blocks
|
||||
for (int x = 0; x < PCOLS; x++)
|
||||
for (int y = 0; y < PROWS; y++)
|
||||
if (board[x][y]) {
|
||||
XSetForeground(dpy, gc, C_FG);
|
||||
XFillRectangle(dpy, back, gc, px0 + x * CELL, py0 + y * CELL, CELL, CELL);
|
||||
}
|
||||
if (board[x][y])
|
||||
draw_gb_cell(px0 + x * CELL, py0 + y * CELL, CELL, C_GB_MED);
|
||||
|
||||
// active piece
|
||||
if (state == PLAYING) {
|
||||
int cells[4][2];
|
||||
piece_cells(curType, curRot, cells);
|
||||
XSetForeground(dpy, gc, C_HEAD);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int cx = curX + cells[i][0];
|
||||
int cy = curY + cells[i][1];
|
||||
if (cy >= 0)
|
||||
XFillRectangle(dpy, back, gc, px0 + cx * CELL, py0 + cy * CELL, CELL, CELL);
|
||||
draw_gb_cell(px0 + cx * CELL, py0 + cy * CELL, CELL, C_GB_LIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,11 +331,10 @@ static void render() {
|
||||
int prows = maxy - miny + 1;
|
||||
int nx0 = sx + ((winW - sx - 4) - pcols * pcell) / 2;
|
||||
int ny0 = boxY + (boxH - prows * pcell) / 2 + 12; // sit lower in the frame
|
||||
XSetForeground(dpy, gc, C_HEAD);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int cx = nx0 + (nc[i][0] - minx) * pcell;
|
||||
int cy = ny0 + (nc[i][1] - miny) * pcell;
|
||||
XFillRectangle(dpy, back, gc, cx, cy, pcell, pcell);
|
||||
draw_gb_cell(cx, cy, pcell, C_HEAD);
|
||||
}
|
||||
int ty = boxY + boxH + 6;
|
||||
char buf[32];
|
||||
@@ -402,6 +412,11 @@ static void setup_window() {
|
||||
C_HEAD = 0x99ff99;
|
||||
C_FOOD = 0x66ff66;
|
||||
C_DIM = 0x2f9f2f;
|
||||
// Gameboy-style four-shade green palette
|
||||
C_GB_DARK = 0x1f7f1f; // bottom/right shadow
|
||||
C_GB_MED = 0x3faf3f; // settled block body
|
||||
C_GB_LIGHT = 0x7fdf7f; // active piece body
|
||||
C_GB_PALE = 0xbfffbf; // top/left highlight
|
||||
|
||||
XSelectInput(dpy, win, KeyPressMask | StructureNotifyMask | ExposureMask);
|
||||
XMapWindow(dpy, win);
|
||||
|
||||
Reference in New Issue
Block a user