From e9226e4a39a8e9df0673a169308eee7f42eb9867 Mon Sep 17 00:00:00 2001 From: Quinn Date: Tue, 9 Sep 2025 19:29:42 +0200 Subject: [PATCH] rework shape handelling, placement and play field management --- src/game/tetromino/placing.c | 95 ------------------------------------ src/game/tetromino/placing.h | 10 ---- src/game/tetromino/shapes.c | 83 ------------------------------- src/game/tetromino/shapes.h | 28 ----------- src/tetris.c | 76 +++++++++++++++++++++++++++++ src/tetris.h | 51 +++++++++++++++++++ 6 files changed, 127 insertions(+), 216 deletions(-) delete mode 100644 src/game/tetromino/placing.c delete mode 100644 src/game/tetromino/placing.h delete mode 100644 src/game/tetromino/shapes.c delete mode 100644 src/game/tetromino/shapes.h create mode 100644 src/tetris.c create mode 100644 src/tetris.h diff --git a/src/game/tetromino/placing.c b/src/game/tetromino/placing.c deleted file mode 100644 index 9d35666..0000000 --- a/src/game/tetromino/placing.c +++ /dev/null @@ -1,95 +0,0 @@ -#include "placing.h" - -#include -#include -#include - -#include "../../io/audio.h" -#include "../../io/input.h" -#include "../../util/types.h" -#include "../../util/vec.h" -#include "../game.h" -#include "shapes.h" - - -static int clear_rows(u8 *restrict *restrict rows) { - int count = 0; - u8 *cache[4]; /* the maximum amount of rows the user can clear at once is four */ - - for (int y = ROWS - 1; y >= 0; y--) { - int x = 0; - while (x < COLUMNS && rows[y][x] && count < 4) x++; - if (x >= COLUMNS) cache[count++] = rows[y]; - else rows[y + count] = rows[y]; - } - - if (count) { - for (int i = 0; i < count; i++) { - memset(cache[i], 0, COLUMNS); - rows[i] = cache[i]; - } - } - return count; -} - -/* writes a shape to the screen */ -static void plcmnt_place(u8 *restrict const *restrict row, u8 id, i8vec2 pos) { - u8 colour = colour_from_id(id); - - i8vec2 bpos[4]; - shape_getblocks(id, bpos); - bpos[0] += pos; - bpos[1] += pos; - bpos[2] += pos; - bpos[3] += pos; - - row[bpos[0][VY]][bpos[0][VX]] = colour; - row[bpos[1][VY]][bpos[1][VX]] = colour; - row[bpos[2][VY]][bpos[2][VX]] = colour; - row[bpos[3][VY]][bpos[3][VX]] = colour; -} - -static int plcmnt_valid(u8 *restrict const *restrict const rows, i8vec2 pos) { - return pos[VX] >= 0 && pos[VX] < COLUMNS && - pos[VY] >= 0 && pos[VY] < ROWS && - !rows[pos[VY]][pos[VX]]; -} - -static int plcmnt_intersect(u8 *restrict const *restrict const rows, const u8 id, i8vec2 pos) { - i8vec2 bpos[4]; - shape_getblocks(id, bpos); - return !(plcmnt_valid(rows, pos + bpos[0]) && - plcmnt_valid(rows, pos + bpos[1]) && - plcmnt_valid(rows, pos + bpos[2]) && - plcmnt_valid(rows, pos + bpos[3])); -} - -int place_update(struct gamedata *gdat, int movdat) { - // store the current index and ID, only changes when placed (which yields no movement) and rotation (which occurs last) - int tmp; - u8 id = gdat->pdat.cur; - - // update Y axis - tmp = !!(movdat & MOVD); - gdat->pdat.sel[VY] += tmp; - tmp = tmp && plcmnt_intersect(gdat->rows, id, gdat->pdat.sel); - if (tmp) { - gdat->pdat.sel[VY]--; - plcmnt_place(gdat->rows, id, gdat->pdat.sel); - gdat->pnts += clear_rows(gdat->rows) << 4; // clear the rows that have been completed - next_shape(); - audio_play(AUDIO_ID_PLACE); - - if (plcmnt_intersect(gdat->rows, gdat->pdat.cur, gdat->pdat.sel)) - return 1; - } - - // update X axis - tmp = !!(movdat & MOVR) - !!(movdat & MOVL); - gdat->pdat.sel[VX] += (tmp && !plcmnt_intersect(gdat->rows, id, (i8vec2){gdat->pdat.sel[VX] + tmp, gdat->pdat.sel[VY]})) * tmp; - - // update roll - tmp = id ^ (((!!(movdat & MOVRR) - !!(movdat & MOVRL)) * 8 + id) & 31); - gdat->pdat.cur ^= (tmp && !plcmnt_intersect(gdat->rows, id ^ tmp, gdat->pdat.sel)) * tmp; - return 0; -} diff --git a/src/game/tetromino/placing.h b/src/game/tetromino/placing.h deleted file mode 100644 index f8b5409..0000000 --- a/src/game/tetromino/placing.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include -#include - -#include "../game.h" - -/* updates the movement of the `pdat` structure, updating the rows when colliding downwards. - * returns `0` if we successfully updated. Returns 1 if we couldn't update. (e.g. when a next block immediately collides) */ -int place_update(struct gamedata *gdat, int movdat); diff --git a/src/game/tetromino/shapes.c b/src/game/tetromino/shapes.c deleted file mode 100644 index 3aa0b9e..0000000 --- a/src/game/tetromino/shapes.c +++ /dev/null @@ -1,83 +0,0 @@ -#include "shapes.h" - -#include "../../io/colour/colour8.h" -#include "../../util/types.h" -#include "../../util/vec.h" -#include "../../error.h" - -void shape_getblocks(u8 id, i8vec2 *restrict out) { - struct blockdat { - u8 ax : 2, ay : 2; - u8 bx : 2, by : 2; - u8 cx : 2, cy : 2; - u8 dx : 2, dy : 2; - } dat; - - switch (id) { - // O tetromino - case TET_O | TET_R0: - case TET_O | TET_R90: - case TET_O | TET_R180: - case TET_O | TET_R270: dat = (struct blockdat){1, 1, 2, 1, 1, 2, 2, 2}; break; - - // I tetromino - case TET_I | TET_R0: dat = (struct blockdat){0, 1, 1, 1, 2, 1, 3, 1}; break; - case TET_I | TET_R90: dat = (struct blockdat){2, 0, 2, 1, 2, 2, 2, 3}; break; - case TET_I | TET_R180: dat = (struct blockdat){0, 2, 1, 2, 2, 2, 3, 2}; break; - case TET_I | TET_R270: dat = (struct blockdat){1, 0, 1, 1, 1, 2, 1, 3}; break; - - // S tetromino - case TET_S | TET_R0: dat = (struct blockdat){1, 0, 2, 0, 0, 1, 1, 1}; break; - case TET_S | TET_R90: dat = (struct blockdat){1, 0, 1, 1, 2, 1, 2, 2}; break; - case TET_S | TET_R180: dat = (struct blockdat){1, 1, 2, 1, 0, 2, 1, 2}; break; - case TET_S | TET_R270: dat = (struct blockdat){0, 0, 0, 1, 1, 1, 1, 2}; break; - - // Z tetromino - case TET_Z | TET_R0: dat = (struct blockdat){0, 0, 1, 0, 1, 1, 2, 1}; break; - case TET_Z | TET_R90: dat = (struct blockdat){2, 0, 1, 1, 2, 1, 1, 2}; break; - case TET_Z | TET_R180: dat = (struct blockdat){0, 1, 1, 1, 1, 2, 2, 2}; break; - case TET_Z | TET_R270: dat = (struct blockdat){1, 0, 0, 1, 1, 1, 0, 2}; break; - - // T tetromino - case TET_T | TET_R0: dat = (struct blockdat){0, 1, 1, 1, 2, 1, 1, 2}; break; - case TET_T | TET_R90: dat = (struct blockdat){1, 0, 0, 1, 1, 1, 1, 2}; break; - case TET_T | TET_R180: dat = (struct blockdat){1, 0, 0, 1, 1, 1, 2, 1}; break; - case TET_T | TET_R270: dat = (struct blockdat){1, 0, 1, 1, 2, 1, 1, 2}; break; - - // L tetromino - case TET_L | TET_R0: dat = (struct blockdat){1, 0, 1, 1, 1, 2, 2, 2}; break; - case TET_L | TET_R90: dat = (struct blockdat){0, 1, 1, 1, 2, 1, 0, 2}; break; - case TET_L | TET_R180: dat = (struct blockdat){0, 0, 1, 0, 1, 1, 1, 2}; break; - case TET_L | TET_R270: dat = (struct blockdat){2, 0, 0, 1, 1, 1, 2, 1}; break; - - // J tetromino - case TET_J | TET_R0: dat = (struct blockdat){1, 0, 1, 1, 0, 2, 1, 2}; break; - case TET_J | TET_R90: dat = (struct blockdat){0, 0, 0, 1, 1, 1, 2, 1}; break; - case TET_J | TET_R180: dat = (struct blockdat){1, 0, 2, 0, 1, 1, 1, 2}; break; - case TET_J | TET_R270: dat = (struct blockdat){0, 1, 1, 1, 2, 1, 2, 2}; break; - - default: -#ifndef NDEBUG - fatal(1, "something went wrong; couldn't reconise the ID.", ); -#endif - break; - } - - out[0] = (i8vec2){dat.ax, dat.ay}; - out[1] = (i8vec2){dat.bx, dat.by}; - out[2] = (i8vec2){dat.cx, dat.cy}; - out[3] = (i8vec2){dat.dx, dat.dy}; -} - -colour8 colour_from_id(u8 id) { - switch (id & 7) { - case TET_O: return COLOUR8_YELLOW; - case TET_I: return COLOUR8_CYAN; - case TET_S: return COLOUR8_GREEN; - case TET_Z: return COLOUR8_RED; - case TET_T: return COLOUR8_MAGENTA; - case TET_L: return COLOUR8_ORANGE; - case TET_J: return COLOUR8_BLUE; - default: return COLOUR8_BLACK; - } -} diff --git a/src/game/tetromino/shapes.h b/src/game/tetromino/shapes.h deleted file mode 100644 index f988cba..0000000 --- a/src/game/tetromino/shapes.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once -#include -#include - -#include "../../io/colour/colour8.h" -#include "../../util/types.h" -#include "../../util/vec.h" - -enum tetromino { - TET_O = 0, - TET_I = 1, - TET_S = 2, - TET_Z = 3, - TET_T = 4, - TET_L = 5, - TET_J = 6, - TET_R0 = 0, - TET_R90 = 8, - TET_R180 = 16, - TET_R270 = 24, -}; - -#define SHAPE_WIDTH 4 -#define SHAPE_HEIGHT 4 -#define TETC 7 - -void shape_getblocks(u8 id, i8vec2 *out); -colour8 colour_from_id(u8 id); diff --git a/src/tetris.c b/src/tetris.c new file mode 100644 index 0000000..79e72e7 --- /dev/null +++ b/src/tetris.c @@ -0,0 +1,76 @@ +#include "tetris.h" + +#include +#include "util/intdef.h" + +/* Board co-ordinates: + * 0 1 2 3 + * 4 5 6 7 + * 8 9 A B + * C D E F */ +const u16 tetromino_shapes[7][4] = { + /* NONE CW_90 CW_180 CW_270 */ + [TET_I] = {0x159D, 0x89AB, 0x26AE, 0x4567}, + [TET_O] = {0x569A, 0x569A, 0x569A, 0x569A}, + [TET_T] = {0x1456, 0x1569, 0x4569, 0x1459}, + [TET_J] = {0x0456, 0x1259, 0x456A, 0x1589}, + [TET_L] = {0x4536, 0x159A, 0x4568, 0x0159}, + [TET_S] = {0x1245, 0x156A, 0x5689, 0x0459}, + [TET_Z] = {0x0156, 0x2569, 0x459A, 0x1458}, +}; + +void tetris_get_blocks(u8 tetromino, uint *restrict positions) { + uint shape = tetromino_shapes[tetromino & 7][tetromino >> 3]; + uint *end = positions + 8; + while (positions < end) { + *positions += shape & 3; + shape >>= 2; + positions++; + } +} + +void tetris_init_rows(const u8 *restrict data, const u8 *restrict *restrict out) { + const u8 *restrict *end = out + TET_HEIGHT; + while (out < end) { + *out = data; + data += TET_WIDTH; + out++; + } +} + +int tetris_clear_rows(u8 *restrict *restrict rows) { + int count = 0; + u8 *cache[4]; /* the maximum amount of rows the user can clear at once is four */ + + for (int y = 0; y < TET_HEIGHT; y++) { + int x = 0; + while (x < TET_WIDTH && rows[y][x] && count < 4) x++; + if (x >= TET_WIDTH) cache[count++] = rows[y]; + else rows[y - count] = rows[y]; + } + + if (count) { + for (int i = 0; i < count; i++) { + memset(cache[i], 0, TET_WIDTH); + rows[TET_HEIGHT - 1 - i] = cache[i]; + } + } + return count; +} + +int tetris_intersect(const u8 *restrict const *restrict rows, u8 tetromino, uint x, uint y) { + uint *pos = (uint[8]){x, y, x, y, x, y, x, y}; + uint *end = pos + 8; + tetris_get_blocks(tetromino, pos); + while (pos < end && !rows[*(pos + 1)][*pos]) pos += 2; + return pos < end; +} + +void tetris_place(u8 *restrict *restrict rows, u8 tetromino, uint x, uint y) { + uint *pos = (uint[8]){x, y, x, y, x, y, x, y}; + uint *end = pos + 8; + tetris_get_blocks(tetromino, pos); + tetromino &= 7; /* strip rotation data */ + for (; pos < end; pos += 2) + rows[*(pos + 1)][*pos] = tetromino; +} diff --git a/src/tetris.h b/src/tetris.h new file mode 100644 index 0000000..87be573 --- /dev/null +++ b/src/tetris.h @@ -0,0 +1,51 @@ +#ifndef TETRIS_H +#define TETRIS_H 1 +#include "util/intdef.h" +#include "util/atrb.h" + +#define TET_WIDTH 10 +#define TET_HEIGHT 24 /* height may be 16—24 */ + +/* Defines tetromino id. + * The `TET_R*` definitions specify various rotations. + * This is designed to be OR'd with the tetromino shape index. */ +enum tetromino { + TET_I = 0x00, + TET_O = 0x01, + TET_T = 0x02, + TET_J = 0x03, + TET_L = 0x04, + TET_S = 0x05, + TET_Z = 0x06, + TET_R0 = 0x08, + TET_R90 = 0x10, + TET_R180 = 0x20, + TET_R270 = 0x40, +}; + +/* Stores the co-ordinates of the four blocks in a 4x4 plane. + * Each co-ordinate is 4 bits wide in total. (2 bits for each axis). + * This is done over encoding the shape directly within the 16 bits (4²), + * since this is more easily parsed in practice. */ +extern const u16 tetromino_shapes[7][4]; + +/* Adds the block position data to the numbers in `positions`. + * it is assumed `positions` has at least eight members and is initialised. `(X,Y)*4 = 8` */ +void tetris_get_blocks(u8 tetromino, uint *restrict positions) NONNULL((2)); + +/* Initialises the rows of the Tetris play field. + * `data` is assumed to point to data of at least `TET_WIDTH * TET_HEIGHT` members. + * `out` is assumed to point to data of at least `TET_HEIGHT` members; the row pointers shall be outputted here. */ +void tetris_init_rows(const u8 *restrict data, const u8 *restrict *restrict out) NONNULL((1, 2)); + +/* Finds up to 4 filled rows in `rows`, moving succeeding rows down and adding the cleared row on top. + * It is assumed `rows[0]` is the bottom row and `rows[TET_WIDTH-1]` is the top row. + * Returns the amount of rows cleared. */ +int tetris_clear_rows(u8 *restrict *restrict rows) NONNULL((1)); + +/* Checks if `tetromino` intersects at (`x`, `y`) in `rows`, returns `1` if so, `0` if not. */ +int tetris_intersect(const u8 *restrict const *restrict rows, u8 tetromino, uint x, uint y) NONNULL((1)); + +/* Writes the blocks of `tetromino` to `rows` at position `x` and `y`. */ +void tetris_place(u8 *restrict *restrict rows, u8 tetromino, uint x, uint y) NONNULL((1)); +#endif