games: 60 fps rendering loop + smoother Snake controls
Both games now render at 60 fps independent of the game-logic step rate: the main loop caps select() at one frame (16 ms) so the screen updates smoothly even when gravity/snake steps are slow, and renders every frame or whenever state changed. Snake: replaced the turn queue with a single buffered direction (latest keypress wins) so rapid key presses don't queue up and misfire. Base speed set to 100 px/sec (~90 ms per step) for controllable, responsive play. Progressive difficulty (+1 px/sec per 5 food) and edge wrap are unchanged.
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+32
-35
@@ -102,13 +102,12 @@ 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 tqx[4], tqy[4]; // queued pending turns (rapid presses not lost)
|
||||
static int tqh = 0, tqt = 0, tqn = 0;
|
||||
static int ndirx = 1, ndiry = 0; // buffered next direction (latest keypress wins)
|
||||
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 = 130; // base speed px/sec; +1 px/sec per 5 food eaten
|
||||
static int pps = 100; // 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() {
|
||||
@@ -164,33 +163,19 @@ static void reset_game() {
|
||||
snake[i][1] = cy;
|
||||
}
|
||||
dirx = 1; diry = 0;
|
||||
tqh = tqt = tqn = 0;
|
||||
ndirx = 1; ndiry = 0;
|
||||
score = 0;
|
||||
pps = 130;
|
||||
pps = 100;
|
||||
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 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;
|
||||
// apply buffered direction (no reversing); latest keypress wins
|
||||
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;
|
||||
@@ -217,7 +202,7 @@ static void step() {
|
||||
score += 10;
|
||||
if (score > best) best = score;
|
||||
int food = score / 10; // food items eaten so far
|
||||
pps = 130 + food / 5; // +1 px/sec per 5 food eaten (progressive difficulty)
|
||||
pps = 100 + food / 5; // +1 px/sec per 5 food eaten (progressive difficulty)
|
||||
update_speed();
|
||||
place_food();
|
||||
}
|
||||
@@ -288,13 +273,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:
|
||||
tq_push(0, -1); break;
|
||||
ndirx = 0; ndiry = -1; break;
|
||||
case XK_Down: case XK_s: case XK_KP_Down: case XK_8: case XK_x:
|
||||
tq_push(0, 1); break;
|
||||
ndirx = 0; ndiry = 1; break;
|
||||
case XK_Left: case XK_a: case XK_KP_Left: case XK_4:
|
||||
tq_push(-1, 0); break;
|
||||
ndirx = -1; ndiry = 0; break;
|
||||
case XK_Right: case XK_d: case XK_KP_Right: case XK_6:
|
||||
tq_push(1, 0); break;
|
||||
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; }
|
||||
@@ -390,12 +375,19 @@ int main() {
|
||||
render();
|
||||
|
||||
int xfd = ConnectionNumber(dpy);
|
||||
const unsigned long FRAME_MS = 1000 / 60; // 60 fps rendering
|
||||
unsigned long lastFrame = now_ms();
|
||||
|
||||
while (running) {
|
||||
long until = (long)nextTick - (long)now_ms();
|
||||
unsigned long now = now_ms();
|
||||
long until = (long)nextTick - (long)now;
|
||||
if (until < 0) until = 0;
|
||||
// Never block select longer than one frame so the screen stays smooth
|
||||
long wait = (until < (long)FRAME_MS) ? until : (long)FRAME_MS;
|
||||
|
||||
struct timeval tv;
|
||||
tv.tv_sec = until / 1000;
|
||||
tv.tv_usec = (until % 1000) * 1000;
|
||||
tv.tv_sec = wait / 1000;
|
||||
tv.tv_usec = (wait % 1000) * 1000;
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(xfd, &fds);
|
||||
@@ -407,17 +399,22 @@ int main() {
|
||||
XNextEvent(dpy, &e);
|
||||
handle_event(e);
|
||||
if (!running) break;
|
||||
dirty = true;
|
||||
}
|
||||
if (!running) break;
|
||||
dirty = true;
|
||||
}
|
||||
if (running && now_ms() >= nextTick) {
|
||||
now = now_ms();
|
||||
if (running && now >= nextTick) {
|
||||
if (state == PLAYING) step();
|
||||
if (state == PLAYING) nextTick = now_ms() + speed;
|
||||
else nextTick = now_ms() + 80; // keep loop responsive while paused/over
|
||||
if (state == PLAYING) nextTick = now + speed;
|
||||
else nextTick = now + 80; // keep loop responsive while paused/over
|
||||
dirty = true;
|
||||
}
|
||||
if (dirty) render();
|
||||
// Render at 60 fps or whenever state changed
|
||||
if (dirty || now - lastFrame >= FRAME_MS) {
|
||||
render();
|
||||
lastFrame = now;
|
||||
}
|
||||
}
|
||||
|
||||
XFreeGC(dpy, gc);
|
||||
|
||||
+20
-8
@@ -499,12 +499,19 @@ int main() {
|
||||
render();
|
||||
|
||||
int xfd = ConnectionNumber(dpy);
|
||||
const unsigned long FRAME_MS = 1000 / 60; // 60 fps rendering
|
||||
unsigned long lastFrame = now_ms();
|
||||
|
||||
while (running) {
|
||||
long until = (long)nextTick - (long)now_ms();
|
||||
unsigned long now = now_ms();
|
||||
long until = (long)nextTick - (long)now;
|
||||
if (until < 0) until = 0;
|
||||
// Never block select longer than one frame so the screen stays smooth
|
||||
long wait = (until < (long)FRAME_MS) ? until : (long)FRAME_MS;
|
||||
|
||||
struct timeval tv;
|
||||
tv.tv_sec = until / 1000;
|
||||
tv.tv_usec = (until % 1000) * 1000;
|
||||
tv.tv_sec = wait / 1000;
|
||||
tv.tv_usec = (wait % 1000) * 1000;
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(xfd, &fds);
|
||||
@@ -516,17 +523,22 @@ int main() {
|
||||
XNextEvent(dpy, &e);
|
||||
handle_event(e);
|
||||
if (!running) break;
|
||||
dirty = true;
|
||||
}
|
||||
if (!running) break;
|
||||
dirty = true;
|
||||
}
|
||||
if (running && now_ms() >= nextTick) {
|
||||
now = now_ms();
|
||||
if (running && now >= nextTick) {
|
||||
if (state == PLAYING) step();
|
||||
if (state == PLAYING) nextTick = now_ms() + speed;
|
||||
else nextTick = now_ms() + 80;
|
||||
if (state == PLAYING) nextTick = now + speed;
|
||||
else nextTick = now + 80;
|
||||
dirty = true;
|
||||
}
|
||||
if (dirty) render();
|
||||
// Render at 60 fps or whenever state changed
|
||||
if (dirty || now - lastFrame >= FRAME_MS) {
|
||||
render();
|
||||
lastFrame = now;
|
||||
}
|
||||
}
|
||||
|
||||
XFreeGC(dpy, gc);
|
||||
|
||||
Reference in New Issue
Block a user