1
0
Fork 0
tetris-c/src/game.c
2026-05-10 00:20:28 +02:00

168 lines
3.5 KiB
C

#include "game.h"
#include "debug.h"
#include "grid.h"
#include "render.h"
#include "shape.h"
#include <SDL3/SDL_events.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
// coordinates for the current shape (the one that is moving)
static uint8_t current_x;
static uint8_t current_y;
// current shape and next shape.
static shape_t shape;
static shape_t next_shape;
static uint32_t score;
static uint8_t game_over;
static grid_t grid;
void game_init() {
game_over = 0;
score = 0;
grid_init(grid);
// Create next shape first. So game_spawn_shape can use it.
shape_create(&next_shape, rand() % NUM_SHAPES);
game_spawn_shape();
}
void game_place_shape() {
uint8_t x = current_x;
uint8_t y = current_y;
tile_t color = shape_color(&shape);
grid_cell(grid, x, y) = color;
for (int i=0; i < 6; i += 2) {
int8_t offset_x = shape.coordinates[i];
int8_t offset_y = shape.coordinates[i+1];
grid_cell(grid, x + offset_x, y + offset_y) = color;
}
debug_printf("shape placed\n");
}
void game_spawn_shape() {
current_x = GRID_WIDTH / 2;
current_y = 0;
// Copy the next shape and generate a new next_shape.
shape_copy(&shape, &next_shape);
shape_create(&next_shape, rand() % NUM_SHAPES);
}
void game_move_shape(int direction) {
int8_t new_x = current_x + direction;
// Reject immediately if the shape origin would leave board bounds.
if (grid_check_collision(grid, new_x, current_y)) {
return;
}
// Then validate every block offset around that origin.
for (int i=0; i < 6; i += 2) {
uint8_t x = shape.coordinates[i] + new_x;
uint8_t y = shape.coordinates[i+1] + current_y;
if (grid_check_collision(grid, x, y)) {
return;
}
}
current_x = new_x;
}
void game_rotate_shape() {
// Temporary array to store new coordinates in.
int8_t tmp[6];
// O-Shapes are the same when rotated.
if (shape.type == SHAPE_O) {
return;
}
// Rotate the shape clockwise and check collision.
// Coordinates are stored relative to the shape origin.
// 90-degree clockwise rotation: (x, y) -> (-y, x).
for(int i = 0; i < 6; i += 2) {
int x = -1 * shape.coordinates[i+1];
int y = shape.coordinates[i];
if (grid_check_collision(grid, current_x + x, current_y + y)) {
debug_printf("collide(rotate)\n");
return;
}
// Set the new coordiate.
tmp[i] = x;
tmp[i+1] = y;
}
// shape is rotated and collision passed.
// Update the shape coordinates with the rotated ones.
memcpy(shape.coordinates, tmp, sizeof(tmp[0]) * 6);
}
int game_move_shape_down() {
uint8_t new_y = current_y + 1;
// Check origin first
if (grid_check_collision(grid, current_x, new_y)) {
debug_printf("collide(1): %d, %d\n", current_x, new_y);
game_reset_shape();
return 1;
}
// Then each offset cell.
for (int i = 0; i < 6; i += 2) {
uint8_t x = shape.coordinates[i] + current_x;
uint8_t y = shape.coordinates[i+1] + new_y;
if (grid_check_collision(grid, x, y)) {
debug_printf("collide(2): %d, %d\n", x, y);
game_reset_shape();
return 1;
}
}
current_y = new_y;
return 0;
}
void game_render() {
render_score(score);
render_board_border();
render_board(grid);
render_shape(&shape, current_x, current_y);
render_next_shape(&next_shape);
}
// places the current shape on the board.
// clears any full rows.
// spawn a new shape.
void game_reset_shape() {
game_place_shape();
score += grid_clear_full_rows(grid) * 100;
if (!game_is_done()) {
game_spawn_shape();
}
}
int game_is_done() {
if (game_over) {
return 1;
}
for(int x = 0; x < GRID_WIDTH; x++) {
if (grid_cell(grid, x, 0)) {
game_over = 1;
return 1;
}
}
return 0;
}