fix(games): correct Tetris rotation, fix input/render lag, visible window frame

- Tetris: replace pivot-based rotation with explicit 4-state SHAPES
  table (classic SRS-style) so rotation is correct and the O piece no
  longer drifts on rotate; keep simple wall-kick offsets.
- Both: render immediately after processing input (not only on gravity
  steps) so rotation/movement is no longer laggy.
- Both: draw the window frame inset by 2-3px (double line) so the right
  and bottom edges are actually visible instead of clipped at the edge.
This commit is contained in:
Luxferre
2026-10-23 15:47:30 +03:00
parent 6bf4139263
commit 1dd9fac313
6 changed files with 38 additions and 28 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+6 -2
View File
@@ -219,7 +219,8 @@ static void render() {
// frame the whole window (right/bottom edges too)
XSetForeground(dpy, gc, C_DIM);
XDrawRectangle(dpy, back, gc, 0, 0, winW - 1, winH - 1);
XDrawRectangle(dpy, back, gc, 2, 2, winW - 4, winH - 4);
XDrawRectangle(dpy, back, gc, 3, 3, winW - 6, winH - 6);
// food
if (foodx >= 0) {
@@ -363,6 +364,7 @@ int main() {
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)) {
@@ -371,13 +373,15 @@ int main() {
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
render();
dirty = true;
}
if (dirty) render();
}
XFreeGC(dpy, gc);
+32 -26
View File
@@ -92,14 +92,16 @@ static unsigned long C_BG, C_FG, C_HEAD, C_FOOD, C_DIM;
// ---- tetromino definitions (state 0 within a 4x4 box) ----
// type 0..6 -> I,O,T,S,Z,J,L
static const int BASE[7][4][2] = {
{{0,1},{1,1},{2,1},{3,1}}, // I
{{1,0},{2,0},{1,1},{2,1}}, // O
{{1,0},{0,1},{1,1},{2,1}}, // T
{{1,0},{2,0},{0,1},{1,1}}, // S
{{0,0},{1,0},{1,1},{2,1}}, // Z
{{0,0},{0,1},{1,1},{2,1}}, // J
{{2,0},{0,1},{1,1},{2,1}}, // L
// 7 tetrominoes, each with 4 explicit rotation states (classic SRS-style).
// type 0..6 -> I,O,T,S,Z,J,L. Coordinates are within a 4x4 box [x,y].
static const int SHAPES[7][4][4][2] = {
{ {{0,1},{1,1},{2,1},{3,1}}, {{2,0},{2,1},{2,2},{2,3}}, {{0,2},{1,2},{2,2},{3,2}}, {{1,0},{1,1},{1,2},{1,3}} }, // I
{ {{1,0},{2,0},{1,1},{2,1}}, {{1,0},{2,0},{1,1},{2,1}}, {{1,0},{2,0},{1,1},{2,1}}, {{1,0},{2,0},{1,1},{2,1}} }, // O
{ {{1,0},{0,1},{1,1},{2,1}}, {{1,0},{1,1},{2,1},{1,2}}, {{0,1},{1,1},{2,1},{1,2}}, {{1,0},{0,1},{1,1},{1,2}} }, // T
{ {{1,0},{2,0},{0,1},{1,1}}, {{1,0},{1,1},{2,1},{2,2}}, {{1,1},{2,1},{0,2},{1,2}}, {{0,0},{0,1},{1,1},{1,2}} }, // S
{ {{0,0},{1,0},{1,1},{2,1}}, {{2,0},{1,1},{2,1},{1,2}}, {{0,1},{1,1},{1,2},{2,2}}, {{1,0},{0,1},{1,1},{0,2}} }, // Z
{ {{0,0},{0,1},{1,1},{2,1}}, {{1,0},{2,0},{1,1},{1,2}}, {{0,1},{1,1},{2,1},{2,2}}, {{1,0},{1,1},{0,2},{1,2}} }, // J
{ {{2,0},{0,1},{1,1},{2,1}}, {{1,0},{1,1},{1,2},{2,2}}, {{0,1},{1,1},{2,1},{0,2}}, {{0,0},{1,0},{1,1},{1,2}} }, // L
};
enum State { PLAYING, PAUSED, GAMEOVER };
@@ -112,16 +114,12 @@ static int speed = 1000; // ms per gravity step
static unsigned long nextTick = 0;
static bool running = true;
static void rotate_cells(const int src[4][2], int dst[4][2], int times) {
for (int i = 0; i < 4; i++) {
int x = src[i][0], y = src[i][1];
for (int t = 0; t < times; t++) { int nx = 3 - y; int ny = x; x = nx; y = ny; }
dst[i][0] = x; dst[i][1] = y;
}
}
static void piece_cells(int type, int rot, int out[4][2]) {
rotate_cells(BASE[type], out, ((rot % 4) + 4) % 4);
int r = ((rot % 4) + 4) % 4;
for (int i = 0; i < 4; i++) {
out[i][0] = SHAPES[type][r][i][0];
out[i][1] = SHAPES[type][r][i][1];
}
}
static bool collide(int px, int py, int cells[4][2]) {
@@ -214,14 +212,18 @@ static void try_move(int dx) {
}
static void try_rotate() {
int nr = (curRot + 1) % 4;
int cells[4][2];
piece_cells(curType, (curRot + 1) % 4, cells);
// simple wall kick: try shift left/right if blocked
if (!collide(curX, curY, cells)) { curRot = (curRot + 1) % 4; return; }
if (!collide(curX - 1, curY, cells)) { curX--; curRot = (curRot + 1) % 4; return; }
if (!collide(curX + 1, curY, cells)) { curX++; curRot = (curRot + 1) % 4; return; }
if (!collide(curX - 2, curY, cells)) { curX -= 2; curRot = (curRot + 1) % 4; return; }
if (!collide(curX + 2, curY, cells)) { curX += 2; curRot = (curRot + 1) % 4; return; }
piece_cells(curType, nr, cells);
// wall kicks: try in place, then small horizontal shifts
int kicks[5] = {0, -1, 1, -2, 2};
for (int k = 0; k < 5; k++) {
if (!collide(curX + kicks[k], curY, cells)) {
curX += kicks[k];
curRot = nr;
return;
}
}
}
static void reset_game() {
@@ -259,7 +261,8 @@ static void render() {
// frame the whole window (right/bottom edges too)
XSetForeground(dpy, gc, C_DIM);
XDrawRectangle(dpy, back, gc, 0, 0, winW - 1, winH - 1);
XDrawRectangle(dpy, back, gc, 2, 2, winW - 4, winH - 4);
XDrawRectangle(dpy, back, gc, 3, 3, winW - 6, winH - 6);
// settled blocks
for (int x = 0; x < PCOLS; x++)
@@ -445,6 +448,7 @@ int main() {
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)) {
@@ -453,13 +457,15 @@ int main() {
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;
render();
dirty = true;
}
if (dirty) render();
}
XFreeGC(dpy, gc);