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 snake[COLS * ROWS][2]; // x,y per segment (0 = tail)
|
||||||
static int snakelen = 4;
|
static int snakelen = 4;
|
||||||
static int dirx = 1, diry = 0; // current direction
|
static int dirx = 1, diry = 0; // current direction
|
||||||
static int tqx[4], tqy[4]; // queued pending turns (rapid presses not lost)
|
static int ndirx = 1, ndiry = 0; // buffered next direction (latest keypress wins)
|
||||||
static int tqh = 0, tqt = 0, tqn = 0;
|
|
||||||
static int foodx, foody;
|
static int foodx, foody;
|
||||||
static int score = 0;
|
static int score = 0;
|
||||||
static int best = 0;
|
static int best = 0;
|
||||||
static int speed = 90; // ms per step (derived from pps below)
|
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 unsigned long nextTick = 0;
|
||||||
static bool running = true;
|
static bool running = true;
|
||||||
static unsigned long now_ms() {
|
static unsigned long now_ms() {
|
||||||
@@ -164,33 +163,19 @@ static void reset_game() {
|
|||||||
snake[i][1] = cy;
|
snake[i][1] = cy;
|
||||||
}
|
}
|
||||||
dirx = 1; diry = 0;
|
dirx = 1; diry = 0;
|
||||||
tqh = tqt = tqn = 0;
|
ndirx = 1; ndiry = 0;
|
||||||
score = 0;
|
score = 0;
|
||||||
pps = 130;
|
pps = 100;
|
||||||
update_speed();
|
update_speed();
|
||||||
place_food();
|
place_food();
|
||||||
state = PLAYING;
|
state = PLAYING;
|
||||||
nextTick = now_ms() + speed;
|
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() {
|
static void step() {
|
||||||
// apply the oldest queued turn (no reversing); rapid presses are not lost
|
// apply buffered direction (no reversing); latest keypress wins
|
||||||
int tdx = 0, tdy = 0;
|
if (!(ndirx == -dirx && ndiry == -diry) && !(ndirx == 0 && ndiry == 0)) {
|
||||||
if (tqn > 0) {
|
dirx = ndirx; diry = ndiry;
|
||||||
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 hx = snake[snakelen - 1][0] + dirx;
|
||||||
int hy = snake[snakelen - 1][1] + diry;
|
int hy = snake[snakelen - 1][1] + diry;
|
||||||
@@ -217,7 +202,7 @@ static void step() {
|
|||||||
score += 10;
|
score += 10;
|
||||||
if (score > best) best = score;
|
if (score > best) best = score;
|
||||||
int food = score / 10; // food items eaten so far
|
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();
|
update_speed();
|
||||||
place_food();
|
place_food();
|
||||||
}
|
}
|
||||||
@@ -288,13 +273,13 @@ static void render() {
|
|||||||
static void handle_key(KeySym ks) {
|
static void handle_key(KeySym ks) {
|
||||||
switch (ks) {
|
switch (ks) {
|
||||||
case XK_Up: case XK_w: case XK_KP_Up: case XK_2:
|
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:
|
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:
|
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:
|
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:
|
case XK_5: case XK_Return: case XK_KP_Enter: case XK_space: case XK_p:
|
||||||
if (state == PLAYING) state = PAUSED;
|
if (state == PLAYING) state = PAUSED;
|
||||||
else if (state == PAUSED) { state = PLAYING; nextTick = now_ms() + speed; }
|
else if (state == PAUSED) { state = PLAYING; nextTick = now_ms() + speed; }
|
||||||
@@ -390,12 +375,19 @@ int main() {
|
|||||||
render();
|
render();
|
||||||
|
|
||||||
int xfd = ConnectionNumber(dpy);
|
int xfd = ConnectionNumber(dpy);
|
||||||
|
const unsigned long FRAME_MS = 1000 / 60; // 60 fps rendering
|
||||||
|
unsigned long lastFrame = now_ms();
|
||||||
|
|
||||||
while (running) {
|
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;
|
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;
|
struct timeval tv;
|
||||||
tv.tv_sec = until / 1000;
|
tv.tv_sec = wait / 1000;
|
||||||
tv.tv_usec = (until % 1000) * 1000;
|
tv.tv_usec = (wait % 1000) * 1000;
|
||||||
fd_set fds;
|
fd_set fds;
|
||||||
FD_ZERO(&fds);
|
FD_ZERO(&fds);
|
||||||
FD_SET(xfd, &fds);
|
FD_SET(xfd, &fds);
|
||||||
@@ -407,17 +399,22 @@ int main() {
|
|||||||
XNextEvent(dpy, &e);
|
XNextEvent(dpy, &e);
|
||||||
handle_event(e);
|
handle_event(e);
|
||||||
if (!running) break;
|
if (!running) break;
|
||||||
|
dirty = true;
|
||||||
}
|
}
|
||||||
if (!running) break;
|
if (!running) break;
|
||||||
dirty = true;
|
|
||||||
}
|
}
|
||||||
if (running && now_ms() >= nextTick) {
|
now = now_ms();
|
||||||
|
if (running && now >= nextTick) {
|
||||||
if (state == PLAYING) step();
|
if (state == PLAYING) step();
|
||||||
if (state == PLAYING) nextTick = now_ms() + speed;
|
if (state == PLAYING) nextTick = now + speed;
|
||||||
else nextTick = now_ms() + 80; // keep loop responsive while paused/over
|
else nextTick = now + 80; // keep loop responsive while paused/over
|
||||||
dirty = true;
|
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);
|
XFreeGC(dpy, gc);
|
||||||
|
|||||||
+20
-8
@@ -499,12 +499,19 @@ int main() {
|
|||||||
render();
|
render();
|
||||||
|
|
||||||
int xfd = ConnectionNumber(dpy);
|
int xfd = ConnectionNumber(dpy);
|
||||||
|
const unsigned long FRAME_MS = 1000 / 60; // 60 fps rendering
|
||||||
|
unsigned long lastFrame = now_ms();
|
||||||
|
|
||||||
while (running) {
|
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;
|
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;
|
struct timeval tv;
|
||||||
tv.tv_sec = until / 1000;
|
tv.tv_sec = wait / 1000;
|
||||||
tv.tv_usec = (until % 1000) * 1000;
|
tv.tv_usec = (wait % 1000) * 1000;
|
||||||
fd_set fds;
|
fd_set fds;
|
||||||
FD_ZERO(&fds);
|
FD_ZERO(&fds);
|
||||||
FD_SET(xfd, &fds);
|
FD_SET(xfd, &fds);
|
||||||
@@ -516,17 +523,22 @@ int main() {
|
|||||||
XNextEvent(dpy, &e);
|
XNextEvent(dpy, &e);
|
||||||
handle_event(e);
|
handle_event(e);
|
||||||
if (!running) break;
|
if (!running) break;
|
||||||
|
dirty = true;
|
||||||
}
|
}
|
||||||
if (!running) break;
|
if (!running) break;
|
||||||
dirty = true;
|
|
||||||
}
|
}
|
||||||
if (running && now_ms() >= nextTick) {
|
now = now_ms();
|
||||||
|
if (running && now >= nextTick) {
|
||||||
if (state == PLAYING) step();
|
if (state == PLAYING) step();
|
||||||
if (state == PLAYING) nextTick = now_ms() + speed;
|
if (state == PLAYING) nextTick = now + speed;
|
||||||
else nextTick = now_ms() + 80;
|
else nextTick = now + 80;
|
||||||
dirty = true;
|
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);
|
XFreeGC(dpy, gc);
|
||||||
|
|||||||
Reference in New Issue
Block a user