games: progressive difficulty + Tetris controls panel

Snake: speed derived from pixels/second; +1 px/sec per 5 food eaten
(pps = 70 + food/5, step = cell*1000/pps, floored at 40ms).

Tetris: speed derived from pixels/second; +1 px/sec per 10 lines cleared
(pps = 23 + lines/10, step = CELL*1000/pps, floored at 40ms), replacing
the old level-driven formula. Level still drives the score multiplier.

Tetris: list all controls concisely in the free space of the right panel
(KEYS, 4/6 MOVE, 2 ROTATE, 8 SOFT, 0 DROP, # PAUSE, 5 OK).
This commit is contained in:
Luxferre
2026-10-23 20:17:15 +03:00
parent 10b67c0f30
commit fdcdb89684
6 changed files with 40 additions and 10 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+15 -5
View File
@@ -101,7 +101,8 @@ 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 int speed = 130; // ms per step (derived from pps below)
static int pps = 70; // 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() {
@@ -110,6 +111,14 @@ static unsigned long now_ms() {
return (unsigned long)(tv.tv_sec * 1000 + tv.tv_usec / 1000);
}
// Derive the per-step delay (ms) from the target pixels/second. The snake
// advances one cell (cell pixels) per step, so step time = cell / pps.
static void update_speed() {
int ms = (cell * 1000) / pps;
if (ms < 40) ms = 40; // cap so the snake never becomes uncontrollable
speed = ms;
}
static void compute_grid() {
int m = 2;
int topH = 14; // reserved status strip for SCORE/BEST
@@ -151,7 +160,8 @@ static void reset_game() {
dirx = 1; diry = 0;
ndirx = 1; ndiry = 0;
score = 0;
speed = 130;
pps = 70;
update_speed();
place_food();
state = PLAYING;
nextTick = now_ms() + speed;
@@ -184,9 +194,9 @@ static void step() {
if (ate) {
score += 10;
if (score > best) best = score;
int ns = 130 - (score / 50) * 8;
if (ns < 60) ns = 60;
speed = ns;
int food = score / 10; // food items eaten so far
pps = 70 + food / 5; // +1 px/sec per 5 food eaten (progressive difficulty)
update_speed();
place_food();
}
nextTick = now_ms() + speed;
+25 -5
View File
@@ -109,7 +109,8 @@ static int board[PCOLS][PROWS]; // 0 empty, else type+1
static int curType = 0, curRot = 0, curX = 3, curY = 0;
static int nextType = 0;
static int score = 0, best = 0, lines = 0, level = 1;
static int speed = 1000; // ms per gravity step
static int speed = 600; // ms per gravity step (derived from pps below)
static int pps = 23; // base speed px/sec; +1 px/sec per 10 lines cleared
static unsigned long nextTick = 0;
static bool running = true;
@@ -137,6 +138,15 @@ static unsigned long now_ms() {
return (unsigned long)(tv.tv_sec * 1000 + tv.tv_usec / 1000);
}
// Derive the gravity delay (ms) from the target pixels/second. A piece drops
// one cell (CELL pixels) per gravity step, so step time = CELL / pps.
static void update_speed() {
pps = 23 + lines / 10; // +1 px/sec per 10 cleared lines (progressive difficulty)
int ms = (CELL * 1000) / pps;
if (ms < 40) ms = 40; // floor on gravity so it stays playable
speed = ms;
}
static void spawn() {
curType = nextType;
nextType = rand() % 7;
@@ -176,11 +186,11 @@ static void lock_piece() {
}
if (cleared > 0) {
lines += cleared;
level = 1 + lines / 10;
int pts[5] = {0, 100, 300, 500, 800};
score += pts[cleared] * level;
if (score > best) best = score;
int nl = 1 + lines / 10;
if (nl != level) { level = nl; speed = 1000 - (level - 1) * 40; if (speed < 300) speed = 300; }
update_speed(); // +1 px/sec per 10 cleared lines
}
spawn();
}
@@ -227,7 +237,7 @@ static void try_rotate() {
static void reset_game() {
for (int x = 0; x < PCOLS; x++) for (int y = 0; y < PROWS; y++) board[x][y] = 0;
score = 0; lines = 0; level = 1; speed = 600;
score = 0; lines = 0; level = 1; update_speed();
nextType = rand() % 7;
spawn();
state = PLAYING;
@@ -319,6 +329,16 @@ static void render() {
snprintf(buf, sizeof(buf), "LEVEL %d", level); draw_text(sx, ty + 18, 1, C_DIM, buf);
if (best > 0) { snprintf(buf, sizeof(buf), "BEST %d", best); draw_text(sx, ty + 27, 1, C_DIM, buf); }
// concise controls in the free space below the stats
int ky = ty + 27 + 12;
draw_text(sx, ky, 1, C_DIM, "KEYS");
draw_text(sx, ky + 9, 1, C_DIM, "4/6 MOVE");
draw_text(sx, ky + 18, 1, C_DIM, "2 ROTATE");
draw_text(sx, ky + 27, 1, C_DIM, "8 SOFT");
draw_text(sx, ky + 36, 1, C_DIM, "0 DROP");
draw_text(sx, ky + 45, 1, C_DIM, "# PAUSE");
draw_text(sx, ky + 54, 1, C_DIM, "5 OK");
if (state == PAUSED || state == GAMEOVER) {
XSetForeground(dpy, gc, C_BG);
XFillRectangle(dpy, back, gc, 0, 0, winW, winH);
@@ -352,7 +372,7 @@ static void setup_window() {
if (winW < 96) winW = 96;
if (winH < 96) winH = 96;
win = XCreateWindow(dpy, RootWindow(dpy, screen), bw, TOPBAR + bw, winW, winH, 0,
CopyFromParent, InputOutput, CopyFromParent, 0, nullptr);
CopyFromParent, InputOutput, CopyFromParent, 0, nullptr);
XStoreName(dpy, win, "Tetris");
XClassHint* ch = XAllocClassHint();
if (ch) { ch->res_name = (char*)"tetris"; ch->res_class = (char*)"tetris"; XSetClassHint(dpy, win, ch); XFree(ch); }