From 60699af63f92f43cc4b4b9e3050fcdd2a8468281 Mon Sep 17 00:00:00 2001 From: Andrew Opalach Date: Wed, 10 Apr 2019 15:14:00 -0400 Subject: add opengl fronend, remove sdl frontend, refactor build system --- core/src/cetris.c | 262 ++++++++++++++++++++++++++++++++++++++++++++++++++++ core/src/input.c | 51 +++++++++++ core/src/matrix.c | 269 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ core/src/test.c | 72 +++++++++++++++ 4 files changed, 654 insertions(+) create mode 100644 core/src/cetris.c create mode 100644 core/src/input.c create mode 100644 core/src/matrix.c create mode 100644 core/src/test.c (limited to 'core/src') diff --git a/core/src/cetris.c b/core/src/cetris.c new file mode 100644 index 0000000..e6bbc8d --- /dev/null +++ b/core/src/cetris.c @@ -0,0 +1,262 @@ +#include +#include +#include +#include +#include +#include + +#include "cetris.h" +#include "types.h" +#include "matrix.h" +#include "input.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 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; + + memset(g->held_moves, 0, sizeof(uint8_t) * 7); + g->das_repeat = 0; + g->prev_das_move = 0; + g->das_move_tick = 0; + g->down_move_tick = 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_peice(struct cetris_game* g, input_t move) { + if (!g->held_moves[move]) { + switch (move) { + case LEFT: + case RIGHT: + case DOWN: + move_current(g, basic_movements[move]); + break; + case HARD_DROP: + hard_drop(g); + break; + case ROTATE_CW: + rotate_matrix(g, 1); + break; + case ROTATE_CCW: + rotate_matrix(g, 0); + break; + } + } + g->held_moves[move] = 1; +} + +void stop_holding(struct cetris_game* g, input_t move) { + g->held_moves[move] = 0; + if (move == RIGHT || move == LEFT) { + g->das_move_tick = 0; + g->das_repeat = 0; + } else if (move == DOWN) g->down_move_tick = 0; +} diff --git a/core/src/input.c b/core/src/input.c new file mode 100644 index 0000000..9fd9142 --- /dev/null +++ b/core/src/input.c @@ -0,0 +1,51 @@ +#include + +#include "input.h" + +#include "types.h" +#include "matrix.h" +#include "cetris.h" + +uint8_t handle_inputs(struct cetris_game* g) { + if ((g->held_moves[RIGHT] || g->held_moves[LEFT]) && !g->das_move_tick) { + if (g->das_repeat == 0) { + g->das_move_tick = g->tick + CETRIS_DAS_DELAY; + } else if (g->das_repeat > 0) { + g->das_move_tick = g->tick + CETRIS_DAS_PERIOD; + } + } + + if (g->held_moves[DOWN] && !g->down_move_tick) { + g->down_move_tick = g->tick + CETRIS_DROP_PERIOD; + } + + uint8_t did_move = 0; + if (g->das_move_tick && g->tick >= g->das_move_tick) { + if (g->held_moves[RIGHT]) { + if (g->prev_das_move == RIGHT || g->das_repeat == 0) g->das_repeat++; + else g->das_repeat = 0; + move_current(g, basic_movements[RIGHT]); + g->prev_das_move = RIGHT; + } + + if (g->held_moves[LEFT]) { + if (g->prev_das_move == LEFT || g->das_repeat == 0) g->das_repeat++; + else g->das_repeat = 0; + move_current(g, basic_movements[LEFT]); + g->prev_das_move = LEFT; + } + + g->das_move_tick = 0; + did_move = 1; + } + + if (g->down_move_tick && g->tick >= g->down_move_tick) { + if (g->held_moves[DOWN]) { + move_current(g, basic_movements[DOWN]); + } + + g->down_move_tick = 0; + did_move = 1; + } + return did_move; +} diff --git a/core/src/matrix.c b/core/src/matrix.c new file mode 100644 index 0000000..5ee971b --- /dev/null +++ b/core/src/matrix.c @@ -0,0 +1,269 @@ +#include + +#include "matrix.h" +#include "types.h" +#include "cetris.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) { + if (g->game_over) return; + + g->current.pos.y += offset.y; + g->current.pos.x += offset.x; + + int8_t check = check_new_matrix(g, g->current.m); + 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.m[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.m); + 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->game_over) return; + 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.m[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; + } + } + } + + 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.m, m, sizeof(piece_matrix)); + wipe_board(g); + } +} + +int8_t check_new_matrix(struct cetris_game* g, piece_matrix m) { + for (uint8_t x = 0; x < 4; x++) { + for (uint8_t y = 0; y < 4; y++) { + vec2 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/core/src/test.c b/core/src/test.c new file mode 100644 index 0000000..33badbd --- /dev/null +++ b/core/src/test.c @@ -0,0 +1,72 @@ +#include "test.h" +#include "cetris.h" + +int tspin_board[20][10] = { + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 1, 1, 1, 1, 0, 0, 1, 1, 1, 1 }, + { 1, 1, 1, 0, 0, 0, 1, 1, 1, 1 }, + { 1, 1, 1, 1, 0, 1, 1, 1, 1, 1 }, + { 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 } +}; + +int tspin_no_lines_board[20][10] = { + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 1, 1, 1, 1, 0, 0, 1, 1, 1, 0 }, + { 1, 1, 1, 0, 0, 0, 1, 1, 1, 0 }, + { 1, 1, 1, 1, 0, 1, 1, 1, 1, 0 }, + { 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 } +}; + +void apply_test_board(struct cetris_game* g, enum tests t) { + int (*board)[20][10]; + switch (t) { + case TSPIN: + board = &tspin_board; + break; + case TSPIN_NO_LINES: + board = &tspin_no_lines_board; + break; + default: + return; + } + for (int y = CETRIS_BOARD_VISABLE; y < CETRIS_BOARD_Y; y++) { + for (int x = 0; x < 10; x++) { + if ((*board)[y - CETRIS_BOARD_VISABLE][x]) { + g->board[x][y].occupied = 1; + g->board[x][y].constant = 1; + g->board[x][y].c = COLOR_I; + } + } + } +} + -- cgit v1.2.3-101-g0448