diff options
| -rw-r--r-- | cetris.c | 576 | ||||
| -rw-r--r-- | frontend/ncurses/ncurses_ui.c | 20 | ||||
| -rw-r--r-- | frontend/sdl/.border.jpeg-autosave.kra | bin | 71027 -> 0 bytes | |||
| -rw-r--r-- | frontend/sdl/sdl_ui.c | 10 | ||||
| -rw-r--r-- | meson.build | 16 | ||||
| -rw-r--r-- | src/cetris.c | 275 | ||||
| -rw-r--r-- | src/cetris.h (renamed from cetris.h) | 42 | ||||
| -rw-r--r-- | src/input.c | 44 | ||||
| -rw-r--r-- | src/input.h | 25 | ||||
| -rw-r--r-- | src/matrix.c | 282 | ||||
| -rw-r--r-- | src/matrix.h | 16 | ||||
| -rw-r--r-- | src/test.c (renamed from test.c) | 0 | ||||
| -rw-r--r-- | src/test.h (renamed from test.h) | 0 |
13 files changed, 680 insertions, 626 deletions
diff --git a/cetris.c b/cetris.c deleted file mode 100644 index 26da86a..0000000 --- a/cetris.c +++ /dev/null @@ -1,576 +0,0 @@ -#include <stdio.h>
-#include <locale.h>
-#include <time.h>
-#include <string.h>
-#include <stdlib.h>
-#include <assert.h>
-
-#include "cetris.h"
-
-#ifdef BUILD_TESTS
-#include "test.h"
-#endif
-
-/* FUNCTION PROTOTYPES */
-
-static void init_piece_queue(struct cetris_game* g);
-static void shuffle_queue(struct cetris_game* g);
-static void next_piece(struct cetris_game* g);
-static void move_current(struct cetris_game* g, vec2 offset);
-static int8_t check_new_matrix(struct cetris_game* g, piece_matrix m);
-static void wipe_board(struct cetris_game* g);
-static void set_constants(struct cetris_game* g);
-static void rotate_matrix(struct cetris_game* g, int clockwise);
-static void overlay_current_matrix(struct cetris_game* g);
-static uint8_t handle_inputs(struct cetris_game* g);
-
-/* DEFAULT MATRIX FOR EACH POSSIBLE TETRIMINO */
-
-static const piece_matrix default_matrices[7] = {
- {
- { 0, 0, 0, 0 },
- { 0, 1, 1, 0 },
- { 0, 1, 1, 0 },
- { 0, 0, 0, 0 }
- },
-
- {
- { 0, 0, 0, 0 },
- { 1, 1, 1, 1 },
- { 0, 0, 0, 0 },
- { 0, 0, 0, 0 }
- },
-
- {
- { 0, 0, 0, 0 },
- { 0, 1, 1, 0 },
- { 1, 1, 0, 0 },
- { 0, 0, 0, 0 }
- },
-
- {
- { 0, 0, 0, 0 },
- { 1, 1, 0, 0 },
- { 0, 1, 1, 0 },
- { 0, 0, 0, 0 }
- },
-
- {
- { 0, 0, 0, 0 },
- { 0, 0, 1, 0 },
- { 1, 1, 1, 0 },
- { 0, 0, 0, 0 }
- },
-
- {
- { 0, 0, 0, 0 },
- { 1, 0, 0, 0 },
- { 1, 1, 1, 0 },
- { 0, 0, 0, 0 }
- },
-
- {
- { 0, 0, 0, 0 },
- { 0, 1, 0, 0 },
- { 1, 1, 1, 0 },
- { 0, 0, 0, 0 }
- }
-};
-
-/* LEVEL DROP SPEED VALUES */
-static const uint8_t level_drop_delay[20] = {
- 48, 43, 38, 33, 28, 23, 18, 13, 8, 6, 5, 5, 5, 4, 4, 4, 3, 3, 3, 3
-};
-
-/* SRS WALL KICK VALUES */
-
-// https://tetris.wiki/SRS
-static const vec2 srs_wall_kicks[8][5] = {
- { {0, 0}, {-1, 0}, {-1,1}, {0,-2}, {-1,-2} }, // 0->R
- { {0, 0}, {1, 0}, {1,-1}, {0,2}, {1,2} }, // R->0
- { {0, 0}, {1, 0}, {1,-1}, {0,2}, {1,2} }, // R->2
- { {0, 0}, {-1, 0}, {-1,1}, {0,-2}, {-1,-2} }, // 2->R
- { {0, 0}, {1, 0}, {1,1}, {0,-2}, {1,-2} }, // 2->L
- { {0, 0}, {-1, 0}, {-1,-1}, {0,2}, {-1,2} }, // L->2
- { {0, 0}, {-1, 0}, {-1,-1}, {0,2}, {-1,2} }, // L->0
- { {0, 0}, {1, 0}, {1,1}, {0,-2}, {1,-2} } // 0->L
-};
-
-static const vec2 srs_wall_kicks_i[8][5] = {
- { {0, 0}, {-2, 0}, {1, 0}, {-2,-1}, {1,2} }, // 0->R
- { {0, 0}, {2, 0}, {-1, 0}, {2,1}, {-1,-2} }, // R->0
- { {0, 0}, {-1, 0}, {2, 0}, {-1,2}, {2,-1} }, // R->2
- { {0, 0}, {1, 0}, {-2, 0}, {1,-2}, {-2,1} }, // 2->R
- { {0, 0}, {2, 0}, {-1, 0}, {2,1}, {-1,-2} }, // 2->L
- { {0, 0}, {-2, 0}, {1, 0}, {-2,-1}, {1,2} }, // L->2
- { {0, 0}, {1, 0}, {-2, 0}, {1,-2}, {-2,1} }, // L->0
- { {0, 0}, {-1, 0}, {2, 0}, {-1,2}, {2,-1} } // 0->L
-};
-
-/* MATRIX MODIFICATION */
-
-static const vec2 basic_movements[4] = {
- {0, 0}, {0, 1}, {1, 0}, {-1, 0} // NONE, DOWN, RIGHT, LEFT
-};
-
-void move_current(struct cetris_game* g, vec2 offset) {
- g->current.pos.y += offset.y;
- g->current.pos.x += offset.x;
-
- int8_t check = check_new_matrix(g, g->current.mat);
- if (check <= 0) {
- g->current.pos.y -= offset.y;
- g->current.pos.x -= offset.x;
-
- if (check == -1 && g->current.lock_tick == 0) {
- g->current.lock_tick = g->tick + 30;
- }
- }
-
- wipe_board(g);
-}
-
-void hard_drop(struct cetris_game* g) {
- if (g->game_over) return;
-
- uint8_t drop = 0;
- uint8_t drop_count = 0;
- while (!drop) {
- g->current.pos.y++;
- drop_count++;
- int8_t check = check_new_matrix(g, g->current.mat);
- if (check <= 0) {
- g->current.pos.y--;
- drop_count--;
- drop = 1;
- }
- }
-
- g->score += 2 * drop_count; // 2 score for each harddrop cell
-
- wipe_board(g);
- next_piece(g);
-}
-
-void move_hard_drop(struct cetris_game* g) {
- if (g->input.held_move != HARD_DROP) {
- hard_drop(g);
- }
- g->input.held_move = HARD_DROP;
-}
-
-void move_down(struct cetris_game* g) {
- if (g->input.held_move != DOWN) {
- move_current(g, basic_movements[DOWN]);
- }
- g->input.held_move = DOWN;
-}
-
-void move_right(struct cetris_game* g) {
- if (g->input.held_move != RIGHT) {
- move_current(g, basic_movements[RIGHT]);
- }
- g->input.held_move = RIGHT;
-}
-
-void move_left(struct cetris_game* g) {
- if (g->input.held_move != LEFT) {
- move_current(g, basic_movements[LEFT]);
- }
- g->input.held_move = LEFT;
-}
-
-void rotate_matrix(struct cetris_game* g, int clockwise) {
- if (g->current.t == O) return;
-
- rstate next;
- switch (g->current.r) {
- case INIT:
- next = (clockwise) ? RRIGHT : RLEFT;
- break;
- case RRIGHT:
- next = (clockwise) ? TWO : INIT;
- break;
- case RLEFT:
- next = (clockwise) ? INIT : TWO;
- break;
- case TWO:
- next = (clockwise) ? RLEFT : RRIGHT;
- break;
- }
-
- piece_matrix m;
- memset(m, 0, sizeof(piece_matrix));
-
- for (uint8_t x = 0; x < 4; x++) {
- for (uint8_t y = 0; y < 4; y++) {
- if (g->current.mat[y][x]) {
- uint8_t new_x = (clockwise) ? 1 - (y - 2) : 1 + (y - 2);
- uint8_t new_y = (clockwise) ? 2 + (x - 1) : 2 - (x - 1);
-
- if (g->current.t == I) {
- if (clockwise) new_y--;
- else new_x++;
- }
-
- m[new_y][new_x] = 1;
- }
- }
- }
-
- uint8_t wall_kick;
- switch (g->current.r) {
- case INIT:
- wall_kick = (next == RRIGHT) ? 0 : 7;
- break;
- case RRIGHT:
- wall_kick = (next == INIT) ? 1 : 2;
- break;
- case RLEFT:
- wall_kick = (next == INIT) ? 6 : 5;
- break;
- case TWO:
- wall_kick = (next == RRIGHT) ? 3 : 4;
- break;
- default: // check for invalid rotations
- assert(0);
- }
-
- vec2 kick;
- uint8_t set_current = 0;
- uint8_t did_kick = 0;
- for (uint8_t i = 0; i < 5; i++) {
- if (g->current.t == I) {
- kick = srs_wall_kicks_i[wall_kick][i];
- } else {
- kick = srs_wall_kicks[wall_kick][i];
- }
- g->current.pos.x += kick.x;
- g->current.pos.y += kick.y;
- if (check_new_matrix(g, m) > 0) {
- set_current = 1;
- if (i > 0) did_kick = 1;
- break;
- } else {
- g->current.pos.x -= kick.x;
- g->current.pos.y -= kick.y;
- }
- }
-
- if (set_current) {
- /* check for tspin */
- if (g->current.t == T) {
- uint8_t did_tspin = 1;
- for (uint8_t i = 0; i < 4; i++) {
- g->current.pos.x += basic_movements[i].x;
- g->current.pos.y += basic_movements[i].y;
- if (check_new_matrix(g, m) == 1) {
- did_tspin = 0;
- }
- g->current.pos.x -= basic_movements[i].x;
- g->current.pos.y -= basic_movements[i].y;
- }
- if (did_tspin) {
- if (did_kick) g->mini_tspin = 1;
- else g->tspin = 1;
- }
- }
-
- g->current.r = next;
- memcpy(g->current.mat, m, sizeof(piece_matrix));
- wipe_board(g);
- }
-}
-
-void rotate_clockwise(struct cetris_game* g) {
- if (g->input.held_move != ROTATE_CW) {
- rotate_matrix(g, 1);
- }
- g->input.held_move = ROTATE_CW;
-}
-
-void rotate_counterclockwise(struct cetris_game* g) {
- if (g->input.held_move != ROTATE_CCW) {
- rotate_matrix(g, 0);
- }
- g->input.held_move = ROTATE_CCW;
-}
-
-int8_t check_new_matrix(struct cetris_game* g, piece_matrix m) {
- vec2 r;
- for (uint8_t x = 0; x < 4; x++) {
- for (uint8_t y = 0; y < 4; y++) {
- r = (vec2){g->current.pos.x + x, g->current.pos.y + y};
- if (m[y][x]) {
- if (r.x > BOARD_X - 1 || r.x < 0) return 0;
-
- if (r.y > BOARD_Y - 1 || r.y < 0) return -1;
-
- if (g->board[r.x][r.y].occupied &&
- g->board[r.x][r.y].constant) return -1;
- }
- }
- }
- return 1;
-}
-
-/* GAME FUNCTIONS */
-
-void init_game(struct cetris_game* g) {
- srand(time(NULL));
-
- memset(g->board, 0, sizeof(slot) * BOARD_X * BOARD_Y);
-
-#ifdef BUILD_TESTS
- apply_test_board(g, TSPIN_NO_LINES);
-#endif
-
- g->tick = 0;
- g->next_drop_tick = level_drop_delay[0];
-
- g->current_index = 0;
-
- g->input.held_move = 0;
- g->input.prev_move = 0;
- g->input.next_move_tick = 0;
- g->input.can_rotate = 0;
- g->input.can_hard_drop = 0;
-
- g->lines = 0;
- g->level = 1;
- g->game_over = 0;
-
- g->tspin = 0;
- g->mini_tspin = 0;
-
- g->score = 0;
-
- init_piece_queue(g);
- shuffle_queue(g);
-
- next_piece(g);
-}
-
-void init_piece_queue(struct cetris_game* g) {
- for (uint8_t i = 0; i < 7; i++) {
- switch (i) {
- case 0:
- g->piece_queue[i].t = O;
- g->piece_queue[i].c = COLOR_O;
- break;
- case 1:
- g->piece_queue[i].t = I;
- g->piece_queue[i].c = COLOR_I;
- break;
- case 2:
- g->piece_queue[i].t = S;
- g->piece_queue[i].c = COLOR_S;
- break;
- case 3:
- g->piece_queue[i].t = Z;
- g->piece_queue[i].c = COLOR_Z;
- break;
- case 4:
- g->piece_queue[i].t = L;
- g->piece_queue[i].c = COLOR_L;
- break;
- case 5:
- g->piece_queue[i].t = J;
- g->piece_queue[i].c = COLOR_J;
- break;
- case 6:
- g->piece_queue[i].t = T;
- g->piece_queue[i].c = COLOR_T;
- break;
- }
- memcpy(g->piece_queue[i].mat, default_matrices[i], sizeof(piece_matrix));
- g->piece_queue[i].r = INIT;
- g->piece_queue[i].lock_tick = 0;
- g->piece_queue[i].pos = (vec2){3, 20}; // y = 22 - 1 for matrix
- }
-}
-
-void shuffle_queue(struct cetris_game* g) {
- for (uint8_t i = 0; i < 7; i++) {
- struct tetrimino t = g->piece_queue[i];
- uint8_t rand_index = rand() % 7;
- g->piece_queue[i] = g->piece_queue[rand_index];
- g->piece_queue[rand_index] = t;
- }
-}
-
-uint8_t handle_inputs(struct cetris_game* g) {
- uint8_t did_move = 0;
- if (g->input.held_move && !g->input.next_move_tick) {
- if (g->input.held_move == RIGHT || g->input.held_move == LEFT) {
- if (g->input.prev_move == g->input.held_move) {
- g->input.next_move_tick = g->tick + CETRIS_DAS_PERIOD;
- } else {
- g->input.next_move_tick = g->tick + CETRIS_DAS_DELAY;
- }
- } else {
- g->input.next_move_tick = g->tick + CETRIS_DROP_PERIOD;
- }
- }
-
- if (g->input.next_move_tick && g->tick >= g->input.next_move_tick) {
- switch (g->input.held_move) {
- case DOWN:
- g->score++;
- move_current(g, basic_movements[DOWN]);
- break;
- case LEFT:
- move_current(g, basic_movements[LEFT]);
- break;
- case RIGHT:
- move_current(g, basic_movements[RIGHT]);
- break;
- }
- did_move = 1;
- }
-
- if (did_move) {
- g->input.next_move_tick = 0;
- g->input.prev_move = g->input.held_move;
- }
- return did_move;
-}
-
-void clear_held_key(struct input_manager* input) {
- input->prev_move = 0;
- input->held_move = 0;
- input->next_move_tick = 0;
-}
-
-void update_game_tick(struct cetris_game* g) {
- if (g->game_over) return;
-
- uint8_t did_move = 0;
- if (g->tick == g->next_drop_tick) {
- move_current(g, basic_movements[DOWN]);
- did_move = 1;
- g->next_drop_tick = g->tick + level_drop_delay[g->level - 1];
- }
-
- /* lock piece if it was hovering for CETRIS_LOCK_DELAY */
- if (g->current.lock_tick && g->current.lock_tick <= g->tick) {
- g->current.pos.y++;
- if (check_new_matrix(g, g->current.mat) <= 0) {
- next_piece(g);
- }
- g->current.pos.y--;
- g->current.lock_tick = 0;
- }
-
- if (handle_inputs(g)) {
- did_move = 1;
- }
-
- if (did_move) {
- wipe_board(g);
- }
-
- g->tick++;
-}
-
-void next_piece(struct cetris_game* g) {
- set_constants(g);
-
- g->current = g->piece_queue[g->current_index];
- if (check_new_matrix(g, g->current.mat) <= 0) {
- g->game_over = 1;
- }
- g->current_index++;
-
- if (g->current_index >= 7) {
- g->current_index = 0;
- shuffle_queue(g);
- }
-}
-
-void set_constants(struct cetris_game* g) {
- for (uint8_t x = 0; x < BOARD_X; x++) {
- for (uint8_t y = 0; y < BOARD_Y; y++) {
- if (g->board[x][y].occupied) g->board[x][y].constant = 1;
- }
- }
-}
-
-void add_score(struct cetris_game* g, int lines) {
- if (!g->tspin && !g->mini_tspin) {
- switch (lines) {
- case 1: g->score += 100 * g->level; break;
- case 2: g->score += 300 * g->level; break;
- case 3: g->score += 500 * g->level; break;
- case 4: g->score += 800 * g->level; break;
- }
- } else if (g->tspin) {
- switch (lines) {
- case 0: g->score += 400 * g->level; break;
- case 1: g->score += 800 * g->level; break;
- case 2: g->score += 1200 * g->level; break;
- case 3: g->score += 1600 * g->level; break;
- }
- g->tspin = 0;
- } else if (g->mini_tspin) {
- switch (lines) {
- case 0: g->score += 100 * g->level; break;
- case 1: g->score += 200 * g->level; break;
- case 2: g->score += 400 * g->level; break;
- }
- g->mini_tspin = 0;
- }
-}
-
-void overlay_current_matrix(struct cetris_game* g) {
- vec2 r;
- for (uint8_t y = 0; y < 4; y++) {
- for (uint8_t x = 0; x < 4; x++) {
- r = (vec2){x + g->current.pos.x, y + g->current.pos.y};
- if (g->current.mat[y][x]) {
- g->board[r.x][r.y].occupied = 1;
- g->board[r.x][r.y].c = g->current.c;
- }
- }
- }
-}
-
-void wipe_board(struct cetris_game* g) {
- uint8_t lines_cleared = 0;
- for (uint8_t y = 0; y < BOARD_Y; y++) {
- uint8_t clear_line = 1;
- for (uint8_t x = 0; x < BOARD_X; x++) {
- if (!g->board[x][y].constant) {
- memset(&g->board[x][y], 0, sizeof(slot));
- }
-
- if (g->board[x][y].remove_tick && g->board[x][y].remove_tick <= g->tick) {
- memset(&g->board[x][y], 0, sizeof(slot));
- for (int8_t s = y - 1; s >= 0; s--) {
- g->board[x][s + 1] = g->board[x][s];
- }
- }
-
- if (!g->board[x][y].occupied || g->board[x][y].remove_tick > 0) {
- clear_line = 0;
- }
- }
- if (clear_line) {
- lines_cleared++;
- for (uint8_t x = 0; x < BOARD_X; x++) {
- g->board[x][y].remove_tick = g->tick + CETRIS_LINE_CLEAR_DELAY;
- }
- }
- }
-
- overlay_current_matrix(g);
-
- assert(lines_cleared <= 4);
- if (lines_cleared > 0) {
- add_score(g, lines_cleared);
- if (lines_cleared > 0) {
- g->lines += lines_cleared;
- if (g->lines >= (g->level * 10) && g->level <= 20) g->level++;
- }
- }
-}
diff --git a/frontend/ncurses/ncurses_ui.c b/frontend/ncurses/ncurses_ui.c index 7a92c0f..98f2b4b 100644 --- a/frontend/ncurses/ncurses_ui.c +++ b/frontend/ncurses/ncurses_ui.c @@ -35,17 +35,17 @@ " \\--------------------/"
#else
#define BLOCK "[]"
-#define PLAY_FIELD_STR " ┏━━━━━━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━┓ \n"\
+#define PLAY_FIELD_STR " ┏━━━━━━━━━━━┓ ┏━━━━━━━━━\n"\
" ┃ ┃ ┃ ┃ \n"\
- " ┃ ┃ ┗━━━━━━━━━━━━━━━┛ \n"\
+ " ┃ ┃ ━━━━━━━━━┛ \n"\
" ┃ ┃ \n"\
" ┃ ┃ \n"\
- " ┃ ┃ ┏━━━━━━━━━━━┓ \n"\
+ " ┃ ┃ ┏━━━━━━┓ \n"\
" ┃ ┃ ┃ ┃ \n"\
" ┃ ┃ ┃ ┃ \n"\
" ┃ ┃ ┃ ┃ \n"\
" ┃ ┃ ┃ ┃ \n"\
- " ┃ ┃ ┗━━━━━━━━━━━┛ \n"\
+ " ┃ ┃ ┗━━━━━━┛ \n"\
" ┃ ┃ \n"\
" ┃ ┃ \n"\
" ┃ ┃ \n"\
@@ -56,7 +56,7 @@ " ┃ ┃ \n"\
" ┃ ┃ \n"\
" ┃ ┃ \n"\
- " ┗━━━━━━━━━━━━━━━━━━━━┛ "
+ " ┗━━━━━━━━━━━┛ "
#endif
#define X_OFFSET 8
@@ -86,16 +86,16 @@ void curses_init() { void draw_board() {
mvaddstr(0, 0, PLAY_FIELD_STR);
- for (int x = 0; x < BOARD_X; x++) {
- for (int y = BOARD_VISABLE; y < BOARD_Y; y++) {
+ for (int x = 0; x < CETRIS_BOARD_X; x++) {
+ for (int y = CETRIS_BOARD_VISABLE; y < CETRIS_BOARD_Y; y++) {
if (game.board[x][y].occupied) {
attron(COLOR_PAIR(game.board[x][y].c));
if (game.board[x][y].remove_tick > 0) {
if (game.tick % 2 == 0) {
- mvaddstr((y - BOARD_VISABLE) + 1, x * 2 + X_OFFSET, BLOCK);
+ mvaddstr((y - CETRIS_BOARD_VISABLE) + 1, x * 2 + X_OFFSET, BLOCK);
}
} else {
- mvaddstr((y - BOARD_VISABLE) + 1, x * 2 + X_OFFSET, BLOCK);
+ mvaddstr((y - CETRIS_BOARD_VISABLE) + 1, x * 2 + X_OFFSET, BLOCK);
}
attroff(COLOR_PAIR(game.board[x][y].c));
}
@@ -161,6 +161,8 @@ int main(void) { init_game(&game);
}
break;
+ default:
+ clear_held_key(&game.input);
}
update_game_tick(&game);
erase();
diff --git a/frontend/sdl/.border.jpeg-autosave.kra b/frontend/sdl/.border.jpeg-autosave.kra Binary files differdeleted file mode 100644 index 69f596a..0000000 --- a/frontend/sdl/.border.jpeg-autosave.kra +++ /dev/null diff --git a/frontend/sdl/sdl_ui.c b/frontend/sdl/sdl_ui.c index 8eaecab..c5c9917 100644 --- a/frontend/sdl/sdl_ui.c +++ b/frontend/sdl/sdl_ui.c @@ -141,11 +141,11 @@ void draw_stuff() { free(lines); SDL_SetRenderDrawColor(render, 255, 255, 255, 124); - for (int x = 0; x < BOARD_X + 1; x++) { + for (int x = 0; x < CETRIS_BOARD_X + 1; x++) { int rx = BOARD_OFFSET_X + 1 + (x * 25); SDL_RenderDrawLine(render, rx, BOARD_OFFSET_Y + 1, rx, BOARD_OFFSET_Y + 500); } - for (int y = 0; y < BOARD_Y - BOARD_VISABLE + 1; y++) { + for (int y = 0; y < CETRIS_BOARD_Y - CETRIS_BOARD_VISABLE + 1; y++) { int ry = BOARD_OFFSET_Y + (y * 25); SDL_RenderDrawLine(render, BOARD_OFFSET_X + 1, ry, BOARD_OFFSET_X + 250, ry); } @@ -153,13 +153,13 @@ void draw_stuff() { sdl_color c; SDL_Rect r = {0, 0, 25, 25}; - for (int x = 0; x < BOARD_X; x++) { - for (int y = BOARD_VISABLE; y < BOARD_Y; y++) { + for (int x = 0; x < CETRIS_BOARD_X; x++) { + for (int y = CETRIS_BOARD_VISABLE; y < CETRIS_BOARD_Y; y++) { if (g.board[x][y].occupied) { c = colors[g.board[x][y].c]; SDL_SetTextureColorMod(block, c.r, c.g, c.b); r.x = BOARD_OFFSET_X + x * 25; - r.y = BOARD_OFFSET_Y + (y - BOARD_VISABLE) * 25; + r.y = BOARD_OFFSET_Y + (y - CETRIS_BOARD_VISABLE) * 25; SDL_RenderCopy(render, block, NULL, &r); } } diff --git a/meson.build b/meson.build index 5d5d1af..9fe6a8c 100644 --- a/meson.build +++ b/meson.build @@ -4,7 +4,8 @@ project('cetris', 'c', compiler = meson.get_compiler('c')
-src = ['cetris.c']
+src = ['src/input.c', 'src/matrix.c', 'src/cetris.c']
+inc = include_directories('src')
if host_machine.system() != 'windows'
if get_option('debug') == true
@@ -12,7 +13,7 @@ if host_machine.system() != 'windows' endif
endif
-src += ['test.c']
+src += ['src/test.c']
sdlinc = []
sdlttfinc = []
@@ -28,11 +29,12 @@ if get_option('build_frontends') == true sdl2 = dependency('sdl2')
sdl2_ttf = meson.get_compiler('c').find_library('SDL2_ttf')
endif
-# executable('cetris_curses', [src, 'frontend/ncurses/ncurses_ui.c'],
-# dependencies: curses,
-# install : false)
+ executable('cetris_curses', [src, 'frontend/ncurses/ncurses_ui.c'],
+ dependencies: curses,
+ include_directories: inc,
+ install : false)
executable('cetris_sdl', [src, 'frontend/sdl/sdl_ui.c'],
- include_directories: [sdlttfinc, sdlinc],
+ include_directories: [inc, sdlttfinc, sdlinc],
dependencies: [sdl2, sdl2_ttf],
- install : false)
+ install : false)
endif
diff --git a/src/cetris.c b/src/cetris.c new file mode 100644 index 0000000..7adf5b1 --- /dev/null +++ b/src/cetris.c @@ -0,0 +1,275 @@ +#include <stdio.h>
+#include <locale.h>
+#include <time.h>
+#include <string.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "cetris.h"
+
+#ifdef BUILD_TESTS
+#include "test.h"
+#endif
+
+/* FUNCTION PROTOTYPES */
+
+static void init_piece_queue(struct cetris_game* g);
+static void shuffle_queue(struct cetris_game* g);
+static void next_piece(struct cetris_game* g);
+static void wipe_board(struct cetris_game* g);
+static void set_constants(struct cetris_game* g);
+
+/* LEVEL DROP SPEED VALUES */
+
+static const uint8_t level_drop_delay[20] = {
+ 48, 43, 38, 33, 28, 23, 18, 13, 8, 6, 5, 5, 5, 4, 4, 4, 3, 3, 3, 3
+};
+
+/* GAME FUNCTIONS */
+
+void init_game(struct cetris_game* g) {
+ srand(time(NULL));
+
+ memset(g->board, 0, sizeof(slot) * CETRIS_BOARD_X * CETRIS_BOARD_Y);
+
+#ifdef BUILD_TESTS
+ apply_test_board(g, TSPIN_NO_LINES);
+#endif
+
+ g->tick = 0;
+ g->next_drop_tick = level_drop_delay[0];
+
+ g->current_index = 0;
+
+ g->input.held_move = 0;
+ g->input.prev_move = 0;
+ g->input.next_move_tick = 0;
+ g->input.can_rotate = 0;
+ g->input.can_hard_drop = 0;
+
+ g->lines = 0;
+ g->level = 1;
+ g->game_over = 0;
+
+ g->tspin = 0;
+ g->mini_tspin = 0;
+
+ g->score = 0;
+
+ init_piece_queue(g);
+ shuffle_queue(g);
+
+ next_piece(g);
+}
+
+void init_piece_queue(struct cetris_game* g) {
+ for (uint8_t i = 0; i < 7; i++) {
+ switch (i) {
+ case 0:
+ g->piece_queue[i].t = O;
+ g->piece_queue[i].c = COLOR_O;
+ break;
+ case 1:
+ g->piece_queue[i].t = I;
+ g->piece_queue[i].c = COLOR_I;
+ break;
+ case 2:
+ g->piece_queue[i].t = S;
+ g->piece_queue[i].c = COLOR_S;
+ break;
+ case 3:
+ g->piece_queue[i].t = Z;
+ g->piece_queue[i].c = COLOR_Z;
+ break;
+ case 4:
+ g->piece_queue[i].t = L;
+ g->piece_queue[i].c = COLOR_L;
+ break;
+ case 5:
+ g->piece_queue[i].t = J;
+ g->piece_queue[i].c = COLOR_J;
+ break;
+ case 6:
+ g->piece_queue[i].t = T;
+ g->piece_queue[i].c = COLOR_T;
+ break;
+ }
+ memcpy(g->piece_queue[i].m, default_matrices[i], sizeof(piece_matrix));
+ g->piece_queue[i].r = INIT;
+ g->piece_queue[i].lock_tick = 0;
+ g->piece_queue[i].pos = (vec2){3, 20}; // y = 22 - 1 for matrix
+ }
+}
+
+void shuffle_queue(struct cetris_game* g) {
+ for (uint8_t i = 0; i < 7; i++) {
+ struct tetrimino t = g->piece_queue[i];
+ uint8_t rand_index = rand() % 7;
+ g->piece_queue[i] = g->piece_queue[rand_index];
+ g->piece_queue[rand_index] = t;
+ }
+}
+
+void update_game_tick(struct cetris_game* g) {
+ if (g->game_over) return;
+
+ uint8_t did_move = 0;
+ if (g->tick == g->next_drop_tick) {
+ move_current(g, basic_movements[DOWN]);
+ did_move = 1;
+ g->next_drop_tick = g->tick + level_drop_delay[g->level - 1];
+ }
+
+ /* lock piece if it was hovering for CETRIS_LOCK_DELAY */
+ if (g->current.lock_tick && g->current.lock_tick <= g->tick) {
+ g->current.pos.y++;
+ if (check_new_matrix(g, g->current.m) <= 0) {
+ next_piece(g);
+ }
+ g->current.pos.y--;
+ g->current.lock_tick = 0;
+ }
+
+ if (handle_inputs(g)) {
+ did_move = 1;
+ }
+
+ if (did_move) {
+ wipe_board(g);
+ }
+
+ g->tick++;
+}
+
+void next_piece(struct cetris_game* g) {
+ set_constants(g);
+
+ g->current = g->piece_queue[g->current_index];
+ if (check_new_matrix(g, g->current.m) <= 0) {
+ g->game_over = 1;
+ }
+ g->current_index++;
+
+ if (g->current_index >= 7) {
+ g->current_index = 0;
+ shuffle_queue(g);
+ }
+}
+
+void set_constants(struct cetris_game* g) {
+ for (uint8_t x = 0; x < CETRIS_BOARD_X; x++) {
+ for (uint8_t y = 0; y < CETRIS_BOARD_Y; y++) {
+ if (g->board[x][y].occupied) g->board[x][y].constant = 1;
+ }
+ }
+}
+
+void add_score(struct cetris_game* g, int lines) {
+ if (!g->tspin && !g->mini_tspin) {
+ switch (lines) {
+ case 1: g->score += 100 * g->level; break;
+ case 2: g->score += 300 * g->level; break;
+ case 3: g->score += 500 * g->level; break;
+ case 4: g->score += 800 * g->level; break;
+ }
+ } else if (g->tspin) {
+ switch (lines) {
+ case 0: g->score += 400 * g->level; break;
+ case 1: g->score += 800 * g->level; break;
+ case 2: g->score += 1200 * g->level; break;
+ case 3: g->score += 1600 * g->level; break;
+ }
+ g->tspin = 0;
+ } else if (g->mini_tspin) {
+ switch (lines) {
+ case 0: g->score += 100 * g->level; break;
+ case 1: g->score += 200 * g->level; break;
+ case 2: g->score += 400 * g->level; break;
+ }
+ g->mini_tspin = 0;
+ }
+}
+
+void wipe_board(struct cetris_game* g) {
+ uint8_t lines_cleared = 0;
+ for (uint8_t y = 0; y < CETRIS_BOARD_Y; y++) {
+ uint8_t clear_line = 1;
+ for (uint8_t x = 0; x < CETRIS_BOARD_X; x++) {
+ if (!g->board[x][y].constant) {
+ memset(&g->board[x][y], 0, sizeof(slot));
+ }
+
+ if (g->board[x][y].remove_tick && g->board[x][y].remove_tick <= g->tick) {
+ memset(&g->board[x][y], 0, sizeof(slot));
+ for (int8_t s = y - 1; s >= 0; s--) {
+ g->board[x][s + 1] = g->board[x][s];
+ }
+ }
+
+ if (!g->board[x][y].occupied || g->board[x][y].remove_tick > 0) {
+ clear_line = 0;
+ }
+ }
+ if (clear_line) {
+ lines_cleared++;
+ for (uint8_t x = 0; x < CETRIS_BOARD_X; x++) {
+ g->board[x][y].remove_tick = g->tick + CETRIS_LINE_CLEAR_DELAY;
+ }
+ }
+ }
+
+ overlay_current_matrix(g);
+
+ assert(lines_cleared <= 4);
+ if (lines_cleared > 0) {
+ add_score(g, lines_cleared);
+ if (lines_cleared > 0) {
+ g->lines += lines_cleared;
+ if (g->lines >= (g->level * 10) && g->level <= 20) g->level++;
+ }
+ }
+}
+
+/* MOVEMENT FUNCTIONS */
+
+void move_down(struct cetris_game* g) {
+ if (g->input.held_move != DOWN) {
+ move_current(g, basic_movements[DOWN]);
+ }
+ g->input.held_move = DOWN;
+}
+
+void move_right(struct cetris_game* g) {
+ if (g->input.held_move != RIGHT) {
+ move_current(g, basic_movements[RIGHT]);
+ }
+ g->input.held_move = RIGHT;
+}
+
+void move_left(struct cetris_game* g) {
+ if (g->input.held_move != LEFT) {
+ move_current(g, basic_movements[LEFT]);
+ }
+ g->input.held_move = LEFT;
+}
+
+void move_hard_drop(struct cetris_game* g) {
+ if (g->input.held_move != HARD_DROP) {
+ hard_drop(g);
+ }
+ g->input.held_move = HARD_DROP;
+}
+
+void rotate_clockwise(struct cetris_game* g) {
+ if (g->input.held_move != ROTATE_CW) {
+ rotate_matrix(g, 1);
+ }
+ g->input.held_move = ROTATE_CW;
+}
+
+void rotate_counterclockwise(struct cetris_game* g) {
+ if (g->input.held_move != ROTATE_CCW) {
+ rotate_matrix(g, 0);
+ }
+ g->input.held_move = ROTATE_CCW;
+}
@@ -2,13 +2,16 @@ #include <stdint.h>
-#define BOARD_X 10
-#define BOARD_Y 43
-#define BOARD_VISABLE 23
+#include "input.h"
+#include "matrix.h"
+
+#define CETRIS_BOARD_X 10
+#define CETRIS_BOARD_Y 43
+#define CETRIS_BOARD_VISABLE 23
#define CETRIS_HZ 60
#define CETRIS_DAS_DELAY 11
-#define CETRIS_DAS_PERIOD 5
+#define CETRIS_DAS_PERIOD 3
#define CETRIS_DROP_PERIOD 2
#define CETRIS_LINE_CLEAR_DELAY 40
#define CETRIS_WAIT_ON_CLEAR 0
@@ -18,6 +21,7 @@ typedef struct { int8_t y;
} vec2;
+
typedef enum {
O, I, S, Z, L, J, T
} type;
@@ -35,18 +39,16 @@ typedef enum { typedef enum {
INIT,
- RRIGHT,
- RLEFT,
- TWO
+ ONCE_RIGHT,
+ ONCE_LEFT,
+ TWICE
} rstate;
-typedef uint8_t piece_matrix[4][4];
-
struct tetrimino {
type t;
rstate r;
color c;
- piece_matrix mat;
+ piece_matrix m;
vec2 pos;
int lock_tick;
};
@@ -58,26 +60,9 @@ typedef struct { color c;
} slot;
-typedef enum {
- DOWN = 1,
- RIGHT = 2,
- LEFT = 3,
- ROTATE_CCW = 4,
- ROTATE_CW = 5,
- HARD_DROP = 6
-} move;
-
-struct input_manager {
- move held_move;
- move prev_move;
- int next_move_tick;
- uint8_t can_rotate;
- uint8_t can_hard_drop;
-};
-
struct cetris_game {
/* playfield represented by a 2d array */
- slot board[BOARD_X][BOARD_Y];
+ slot board[CETRIS_BOARD_X][CETRIS_BOARD_Y];
/* constant queue of all 7 possible tetrimino */
struct tetrimino piece_queue[7];
@@ -115,4 +100,3 @@ void move_right(struct cetris_game* g); void move_hard_drop(struct cetris_game* g);
void rotate_clockwise(struct cetris_game* g);
void rotate_counterclockwise(struct cetris_game* g);
-void clear_held_key(struct input_manager* input);
diff --git a/src/input.c b/src/input.c new file mode 100644 index 0000000..a77f4df --- /dev/null +++ b/src/input.c @@ -0,0 +1,44 @@ +#include "input.h" + +uint8_t handle_inputs(struct cetris_game* g) { + uint8_t did_move = 0; + if (g->input.held_move && !g->input.next_move_tick) { + if (g->input.held_move == RIGHT || g->input.held_move == LEFT) { + if (g->input.prev_move == g->input.held_move) { + g->input.next_move_tick = g->tick + CETRIS_DAS_PERIOD; + } else { + g->input.next_move_tick = g->tick + CETRIS_DAS_DELAY; + } + } else { + g->input.next_move_tick = g->tick + CETRIS_DROP_PERIOD; + } + } + + if (g->input.next_move_tick && g->tick >= g->input.next_move_tick) { + switch (g->input.held_move) { + case DOWN: + g->score++; + move_current(g, basic_movements[DOWN]); + break; + case LEFT: + move_current(g, basic_movements[LEFT]); + break; + case RIGHT: + move_current(g, basic_movements[RIGHT]); + break; + } + did_move = 1; + } + + if (did_move) { + g->input.next_move_tick = 0; + g->input.prev_move = g->input.held_move; + } + return did_move; +} + +void clear_held_key(struct input_manager* input) { + input->prev_move = 0; + input->held_move = 0; + input->next_move_tick = 0; +} diff --git a/src/input.h b/src/input.h new file mode 100644 index 0000000..188ca71 --- /dev/null +++ b/src/input.h @@ -0,0 +1,25 @@ +#pragma once + +#include <stdint.h> + +#include "cetris.h" + +typedef enum { + DOWN = 1, + RIGHT = 2, + LEFT = 3, + ROTATE_CCW = 4, + ROTATE_CW = 5, + HARD_DROP = 6 +} input_t; + +struct input_manager { + input_t held_move; + input_t prev_move; + int next_move_tick; + uint8_t can_rotate; + uint8_t can_hard_drop; +}; + +uint8_t handle_inputs(struct cetris_game* g); +void clear_held_key(struct input_manager* input); diff --git a/src/matrix.c b/src/matrix.c new file mode 100644 index 0000000..796b4a8 --- /dev/null +++ b/src/matrix.c @@ -0,0 +1,282 @@ +#include <string.h> + +#include "matrix.h" + +/* SRS WALL KICK VALUES */ + +// https://tetris.wiki/SRS +static const vec2 srs_wall_kicks[8][5] = { + { {0, 0}, {-1, 0}, {-1,1}, {0,-2}, {-1,-2} }, // 0->R + { {0, 0}, {1, 0}, {1,-1}, {0,2}, {1,2} }, // R->0 + { {0, 0}, {1, 0}, {1,-1}, {0,2}, {1,2} }, // R->2 + { {0, 0}, {-1, 0}, {-1,1}, {0,-2}, {-1,-2} }, // 2->R + { {0, 0}, {1, 0}, {1,1}, {0,-2}, {1,-2} }, // 2->L + { {0, 0}, {-1, 0}, {-1,-1}, {0,2}, {-1,2} }, // L->2 + { {0, 0}, {-1, 0}, {-1,-1}, {0,2}, {-1,2} }, // L->0 + { {0, 0}, {1, 0}, {1,1}, {0,-2}, {1,-2} } // 0->L +}; + +static const vec2 srs_wall_kicks_i[8][5] = { + { {0, 0}, {-2, 0}, {1, 0}, {-2,-1}, {1,2} }, // 0->R + { {0, 0}, {2, 0}, {-1, 0}, {2,1}, {-1,-2} }, // R->0 + { {0, 0}, {-1, 0}, {2, 0}, {-1,2}, {2,-1} }, // R->2 + { {0, 0}, {1, 0}, {-2, 0}, {1,-2}, {-2,1} }, // 2->R + { {0, 0}, {2, 0}, {-1, 0}, {2,1}, {-1,-2} }, // 2->L + { {0, 0}, {-2, 0}, {1, 0}, {-2,-1}, {1,2} }, // L->2 + { {0, 0}, {1, 0}, {-2, 0}, {1,-2}, {-2,1} }, // L->0 + { {0, 0}, {-1, 0}, {2, 0}, {-1,2}, {2,-1} } // 0->L +}; + +/* DEFAULT MATRIX FOR EACH POSSIBLE TETRIMINO */ + +const piece_matrix default_matrices[7] = { + { + { 0, 0, 0, 0 }, + { 0, 1, 1, 0 }, + { 0, 1, 1, 0 }, + { 0, 0, 0, 0 } + }, + + { + { 0, 0, 0, 0 }, + { 1, 1, 1, 1 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 } + }, + + { + { 0, 0, 0, 0 }, + { 0, 1, 1, 0 }, + { 1, 1, 0, 0 }, + { 0, 0, 0, 0 } + }, + + { + { 0, 0, 0, 0 }, + { 1, 1, 0, 0 }, + { 0, 1, 1, 0 }, + { 0, 0, 0, 0 } + }, + + { + { 0, 0, 0, 0 }, + { 0, 0, 1, 0 }, + { 1, 1, 1, 0 }, + { 0, 0, 0, 0 } + }, + + { + { 0, 0, 0, 0 }, + { 1, 0, 0, 0 }, + { 1, 1, 1, 0 }, + { 0, 0, 0, 0 } + }, + + { + { 0, 0, 0, 0 }, + { 0, 1, 0, 0 }, + { 1, 1, 1, 0 }, + { 0, 0, 0, 0 } + } +}; + +/* MATRIX MODIFICATION */ + +const vec2 basic_movements[4] = { + {0, 0}, {0, 1}, {1, 0}, {-1, 0} // NONE, DOWN, RIGHT, LEFT +}; + +void move_current(struct cetris_game* g, vec2 offset) { + g->current.pos.y += offset.y; + g->current.pos.x += offset.x; + + int8_t check = check_new_matrix(g, g->current.mat); + if (check <= 0) { + g->current.pos.y -= offset.y; + g->current.pos.x -= offset.x; + + if (check == -1 && g->current.lock_tick == 0) { + g->current.lock_tick = g->tick + 30; + } + } + + wipe_board(g); +} + +void overlay_current_matrix(struct cetris_game* g) { + for (uint8_t y = 0; y < 4; y++) { + for (uint8_t x = 0; x < 4; x++) { + vec2 r = (vec2){x + g->current.pos.x, y + g->current.pos.y}; + if (g->current.mat[y][x]) { + g->board[r.x][r.y].occupied = 1; + g->board[r.x][r.y].c = g->current.c; + } + } + } +} + +void hard_drop(struct cetris_game* g) { + if (g->game_over) return; + + uint8_t drop = 0; + uint8_t drop_count = 0; + while (!drop) { + g->current.pos.y++; + drop_count++; + int8_t check = check_new_matrix(g, g->current.mat); + if (check <= 0) { + g->current.pos.y--; + drop_count--; + drop = 1; + } + } + + g->score += 2 * drop_count; // 2 score for each harddrop cell + + wipe_board(g); + next_piece(g); +} + +void rotate_matrix(struct cetris_game* g, int clockwise) { + if (g->current.t == O) return; + + rstate next; int8_t wall_kick; + switch (g->current.r) { + case INIT: + if (clockwise) { + next = ONCE_RIGHT; + wall_kick = 0; + } else { + next = ONCE_LEFT; + wall_kick = 7; + } break; + case ONCE_RIGHT: + if (clockwise) { + next = TWICE; + wall_kick = 2; + } else { + next = INIT; + wall_kick = 1; + } break; + case ONCE_LEFT: + if (clockwise) { + next = INIT; + wall_kick = 6; + } else { + next = TWICE; + wall_kick = 5; + } break; + case TWICE: + if (clockwise) { + next = ONCE_LEFT; + wall_kick = 4; + } else { + next = ONCE_RIGHT; + wall_kick = 3; + } break; + } + + piece_matrix m; + memset(m, 0, sizeof(piece_matrix)); + + for (uint8_t x = 0; x < 4; x++) { + for (uint8_t y = 0; y < 4; y++) { + if (g->current.mat[y][x]) { + uint8_t new_x = (clockwise) ? 1 - (y - 2) : 1 + (y - 2); + uint8_t new_y = (clockwise) ? 2 + (x - 1) : 2 - (x - 1); + + if (g->current.t == I) { + if (clockwise) new_y--; + else new_x++; + } + + m[new_y][new_x] = 1; + } + } + } + + /* + uint8_t wall_kick; + switch (g->current.r) { + case INIT: + wall_kick = (next == RRIGHT) ? 0 : 7; + break; + case ONCE_RIGHT: + wall_kick = (next == INIT) ? 1 : 2; + break; + case ONCE_LEFT: + wall_kick = (next == INIT) ? 6 : 5; + break; + case TWICE: + wall_kick = (next == RRIGHT) ? 3 : 4; + break; + default: // check for invalid rotations + assert(0); + } + */ + + vec2 kick; + uint8_t set_current = 0; + uint8_t did_kick = 0; + for (uint8_t i = 0; i < 5; i++) { + if (g->current.t == I) { + kick = srs_wall_kicks_i[wall_kick][i]; + } else { + kick = srs_wall_kicks[wall_kick][i]; + } + g->current.pos.x += kick.x; + g->current.pos.y += kick.y; + if (check_new_matrix(g, m) > 0) { + set_current = 1; + if (i > 0) did_kick = 1; + break; + } else { + g->current.pos.x -= kick.x; + g->current.pos.y -= kick.y; + } + } + + if (set_current) { + /* check for tspin */ + if (g->current.t == T) { + uint8_t did_tspin = 1; + for (uint8_t i = 0; i < 4; i++) { + g->current.pos.x += basic_movements[i].x; + g->current.pos.y += basic_movements[i].y; + if (check_new_matrix(g, m) == 1) { + did_tspin = 0; + } + g->current.pos.x -= basic_movements[i].x; + g->current.pos.y -= basic_movements[i].y; + } + if (did_tspin) { + if (did_kick) g->mini_tspin = 1; + else g->tspin = 1; + } + } + + g->current.r = next; + memcpy(g->current.mat, m, sizeof(piece_matrix)); + wipe_board(g); + } +} + +int8_t check_new_matrix(struct cetris_game* g, piece_matrix m) { + vec2 r; + for (uint8_t x = 0; x < 4; x++) { + for (uint8_t y = 0; y < 4; y++) { + r = (vec2){g->current.pos.x + x, g->current.pos.y + y}; + if (m[y][x]) { + if (r.x > CETRIS_BOARD_X - 1 || r.x < 0) + return 0; + + if (r.y > CETRIS_BOARD_Y - 1 || r.y < 0) + return -1; + + if (g->board[r.x][r.y].occupied && g->board[r.x][r.y].constant) + return -1; + } + } + } + return 1; +} diff --git a/src/matrix.h b/src/matrix.h new file mode 100644 index 0000000..57166cc --- /dev/null +++ b/src/matrix.h @@ -0,0 +1,16 @@ +#pragma once + +#include <stdint.h> + +#include "cetris.h" + +typedef uint8_t piece_matrix[4][4]; + +const piece_matrix default_matrices[7]; +const vec2 basic_movements[4]; + +void move_current(struct cetris_game* g, vec2 offset); +void overlay_current_matrix(struct cetris_game* g); +void hard_drop(struct cetris_game* g); +void rotate_matrix(struct cetris_game* g, int clockwise); +int8_t check_new_matrix(struct cetris_game* g, piece_matrix m); |