pvjc26/final/main.c
2026-05-13 13:27:09 +02:00

551 lines
18 KiB
C

#include "world.h"
#include <stdlib.h>
#include <string.h>
#include <time.h>
/* Tetris game dimensions */
#define BOARD_WIDTH 10
#define BOARD_HEIGHT 20
#define CELL_WIDTH 2 /* Double-width cells for square appearance */
#define BOARD_RENDER_X 2 /* Left panel position */
#define BOARD_RENDER_Y 2
#define STATS_X 26 /* Right panel position */
#define STATS_Y 2
/* Tetromino definitions - 7 standard tetrominoes */
enum PieceType {
PIECE_I, PIECE_O, PIECE_T, PIECE_S, PIECE_Z, PIECE_J, PIECE_L, NUM_PIECES
};
/* Piece colors and characters - use different characters for different piece types */
static const int PIECE_COLORS[NUM_PIECES] = {
COLOR_CYAN, /* I */
COLOR_YELLOW, /* O */
COLOR_MAGENTA, /* T */
COLOR_GREEN, /* S */
COLOR_RED, /* Z */
COLOR_BLUE, /* J */
COLOR_WHITE /* L */
};
static const char PIECE_CHARS[NUM_PIECES] = {
'#', /* I */
'O', /* O */
'T', /* T */
'S', /* S */
'Z', /* Z */
'J', /* J */
'L' /* L */
};
/* Rotation states for each tetromino (4 rotations, 4 blocks each) */
/* Format: (x, y) offset from piece center */
static const int PIECE_SHAPES[NUM_PIECES][4][4][2] = {
/* I piece */
{
{{-1, 0}, {0, 0}, {1, 0}, {2, 0}}, /* Rotation 0 */
{{0, -1}, {0, 0}, {0, 1}, {0, 2}}, /* Rotation 1 */
{{-1, 0}, {0, 0}, {1, 0}, {2, 0}}, /* Rotation 2 */
{{0, -1}, {0, 0}, {0, 1}, {0, 2}} /* Rotation 3 */
},
/* O piece (square - no rotation needed) */
{
{{0, 0}, {1, 0}, {0, 1}, {1, 1}},
{{0, 0}, {1, 0}, {0, 1}, {1, 1}},
{{0, 0}, {1, 0}, {0, 1}, {1, 1}},
{{0, 0}, {1, 0}, {0, 1}, {1, 1}}
},
/* T piece */
{
{{-1, 0}, {0, 0}, {1, 0}, {0, 1}},
{{0, -1}, {0, 0}, {0, 1}, {1, 0}},
{{-1, 0}, {0, 0}, {1, 0}, {0, -1}},
{{0, -1}, {0, 0}, {0, 1}, {-1, 0}}
},
/* S piece */
{
{{0, 0}, {1, 0}, {-1, 1}, {0, 1}},
{{0, -1}, {0, 0}, {1, 0}, {1, 1}},
{{0, 0}, {1, 0}, {-1, 1}, {0, 1}},
{{0, -1}, {0, 0}, {1, 0}, {1, 1}}
},
/* Z piece */
{
{{-1, 0}, {0, 0}, {0, 1}, {1, 1}},
{{1, -1}, {0, 0}, {1, 0}, {0, 1}},
{{-1, 0}, {0, 0}, {0, 1}, {1, 1}},
{{1, -1}, {0, 0}, {1, 0}, {0, 1}}
},
/* J piece */
{
{{-1, 0}, {0, 0}, {1, 0}, {1, 1}},
{{0, -1}, {0, 0}, {0, 1}, {1, -1}},
{{-1, -1}, {-1, 0}, {0, 0}, {1, 0}},
{{0, -1}, {0, 0}, {0, 1}, {-1, 1}}
},
/* L piece */
{
{{-1, 0}, {0, 0}, {1, 0}, {-1, 1}},
{{0, -1}, {0, 0}, {0, 1}, {1, 1}},
{{-1, -1}, {-1, 0}, {0, 0}, {1, 0}},
{{0, -1}, {0, 0}, {0, 1}, {-1, -1}}
}
};
/* Game state structure */
typedef struct {
/* Board: 1 = filled, 0 = empty */
int board[BOARD_HEIGHT][BOARD_WIDTH];
/* Current piece state */
int piece_type;
int piece_x, piece_y;
int piece_rotation;
/* Next piece preview */
int next_piece_type;
/* Game statistics */
int score;
int level;
int lines_cleared;
int total_lines;
/* Game state */
int game_over;
int paused;
/* Timing */
int gravity_counter;
int gravity_speed;
int frames_since_last_gravity;
/* Display dimensions */
int screen_width;
int screen_height;
} GameState;
/* Initialize game state */
void* init_game() {
GameState* state = malloc(sizeof(GameState));
memset(state, 0, sizeof(GameState));
/* Initialize board */
for (int y = 0; y < BOARD_HEIGHT; y++) {
for (int x = 0; x < BOARD_WIDTH; x++) {
state->board[y][x] = 0;
}
}
state->level = 1;
state->gravity_speed = 25 - (state->level - 1) * 2; /* Speeds up with level */
if (state->gravity_speed < 3) state->gravity_speed = 3;
state->next_piece_type = rand() % NUM_PIECES;
return state;
}
/* Destroy game state */
void destroy_game(void* game) {
free(game);
}
/* Check if a piece can be placed at given position and rotation */
int can_place_piece(GameState* state, int type, int x, int y, int rotation) {
const int (*shape)[4][2] = &PIECE_SHAPES[type][rotation];
for (int i = 0; i < 4; i++) {
int bx = x + (*shape)[i][0];
int by = y + (*shape)[i][1];
/* Check bounds */
if (bx < 0 || bx >= BOARD_WIDTH || by < 0 || by >= BOARD_HEIGHT) {
return 0;
}
/* Check collision */
if (state->board[by][bx] != 0) {
return 0;
}
}
return 1;
}
/* Spawn a new piece */
void spawn_piece(GameState* state) {
state->piece_type = state->next_piece_type;
state->next_piece_type = rand() % NUM_PIECES;
state->piece_x = BOARD_WIDTH / 2;
state->piece_y = 0;
state->piece_rotation = 0;
/* Check if game is over (piece can't spawn) */
if (!can_place_piece(state, state->piece_type, state->piece_x, state->piece_y, 0)) {
state->game_over = 1;
}
}
/* Move piece left */
void move_piece_left(GameState* state) {
if (can_place_piece(state, state->piece_type, state->piece_x - 1, state->piece_y, state->piece_rotation)) {
state->piece_x--;
}
}
/* Move piece right */
void move_piece_right(GameState* state) {
if (can_place_piece(state, state->piece_type, state->piece_x + 1, state->piece_y, state->piece_rotation)) {
state->piece_x++;
}
}
/* Soft drop (move piece down) */
int move_piece_down(GameState* state) {
if (can_place_piece(state, state->piece_type, state->piece_x, state->piece_y + 1, state->piece_rotation)) {
state->piece_y++;
return 1;
}
return 0;
}
/* Hard drop (instant drop) */
void hard_drop_piece(GameState* state) {
while (move_piece_down(state));
}
/* Rotate piece clockwise */
void rotate_piece(GameState* state) {
int new_rotation = (state->piece_rotation + 1) % 4;
if (can_place_piece(state, state->piece_type, state->piece_x, state->piece_y, new_rotation)) {
state->piece_rotation = new_rotation;
}
}
/* Lock piece into board and spawn next piece */
void lock_piece(GameState* state) {
const int (*shape)[4][2] = &PIECE_SHAPES[state->piece_type][state->piece_rotation];
/* Place piece blocks on board */
for (int i = 0; i < 4; i++) {
int bx = state->piece_x + (*shape)[i][0];
int by = state->piece_y + (*shape)[i][1];
if (bx >= 0 && bx < BOARD_WIDTH && by >= 0 && by < BOARD_HEIGHT) {
state->board[by][bx] = state->piece_type + 1; /* +1 so 0 means empty */
}
}
spawn_piece(state);
}
/* Check and clear completed lines */
void check_and_clear_lines(GameState* state) {
int lines_to_clear = 0;
int new_board[BOARD_HEIGHT][BOARD_WIDTH];
/* Identify complete lines */
int write_row = BOARD_HEIGHT - 1;
for (int y = BOARD_HEIGHT - 1; y >= 0; y--) {
int is_complete = 1;
for (int x = 0; x < BOARD_WIDTH; x++) {
if (state->board[y][x] == 0) {
is_complete = 0;
break;
}
}
if (!is_complete) {
memcpy(new_board[write_row], state->board[y], sizeof(state->board[y]));
write_row--;
} else {
lines_to_clear++;
}
}
/* Clear top rows */
for (int y = 0; y <= write_row; y++) {
memset(new_board[y], 0, sizeof(new_board[y]));
}
if (lines_to_clear > 0) {
memcpy(state->board, new_board, sizeof(state->board));
state->total_lines += lines_to_clear;
state->lines_cleared = lines_to_clear;
/* Update score: higher points for clearing multiple lines */
int points = 0;
switch (lines_to_clear) {
case 1: points = 100; break;
case 2: points = 300; break;
case 3: points = 500; break;
case 4: points = 800; break;
}
state->score += points * state->level;
/* Update level every 10 lines */
int new_level = state->total_lines / 10 + 1;
if (new_level != state->level) {
state->level = new_level;
state->gravity_speed = 25 - (state->level - 1) * 2;
if (state->gravity_speed < 3) state->gravity_speed = 3;
}
}
}
/* Apply gravity tick - move piece down automatically */
void apply_gravity(GameState* state) {
state->frames_since_last_gravity++;
if (state->frames_since_last_gravity >= state->gravity_speed) {
state->frames_since_last_gravity = 0;
if (!move_piece_down(state)) {
/* Piece can't move down, lock it */
lock_piece(state);
check_and_clear_lines(state);
}
}
}
/* Render the game */
void render_game(GameState* state) {
/* Clear screen */
clear_screen();
/* Draw board border (left panel) */
int board_width = BOARD_WIDTH * CELL_WIDTH;
int board_height = BOARD_HEIGHT;
int board_x = BOARD_RENDER_X;
int board_y = BOARD_RENDER_Y;
/* Top border */
set_color_cell(0x250C, board_x, board_y, COLOR_WHITE, COLOR_BLACK); /* ┌ */
for (int x = 1; x < board_width + 1; x++) {
set_color_cell(0x2500, board_x + x, board_y, COLOR_WHITE, COLOR_BLACK); /* ─ */
}
set_color_cell(0x2510, board_x + board_width + 1, board_y, COLOR_WHITE, COLOR_BLACK); /* ┐ */
/* Left border */
for (int y = 1; y <= board_height; y++) {
set_color_cell(0x2502, board_x, board_y + y, COLOR_WHITE, COLOR_BLACK); /* │ */
}
/* Right border */
for (int y = 1; y <= board_height; y++) {
set_color_cell(0x2502, board_x + board_width + 1, board_y + y, COLOR_WHITE, COLOR_BLACK); /* │ */
}
/* Bottom border */
set_color_cell(0x2514, board_x, board_y + board_height + 1, COLOR_WHITE, COLOR_BLACK); /* └ */
for (int x = 1; x < board_width + 1; x++) {
set_color_cell(0x2500, board_x + x, board_y + board_height + 1, COLOR_WHITE, COLOR_BLACK); /* ─ */
}
set_color_cell(0x2518, board_x + board_width + 1, board_y + board_height + 1, COLOR_WHITE, COLOR_BLACK); /* ┘ */
/* Draw board cells */
for (int y = 0; y < BOARD_HEIGHT; y++) {
for (int x = 0; x < BOARD_WIDTH; x++) {
int screen_x = board_x + 1 + x * CELL_WIDTH;
int screen_y = board_y + 1 + y;
if (state->board[y][x] != 0) {
int piece_type = state->board[y][x] - 1;
/* Ensure piece_type is valid */
if (piece_type < 0 || piece_type >= NUM_PIECES) {
piece_type = 0; /* Default to I piece if invalid */
}
set_color_cell(PIECE_CHARS[piece_type], screen_x, screen_y, PIECE_COLORS[piece_type], COLOR_BLACK);
set_color_cell(PIECE_CHARS[piece_type], screen_x + 1, screen_y, PIECE_COLORS[piece_type], COLOR_BLACK);
} else {
set_color_cell(' ', screen_x, screen_y, COLOR_WHITE, COLOR_BLACK);
set_color_cell(' ', screen_x + 1, screen_y, COLOR_WHITE, COLOR_BLACK);
}
}
}
/* Draw current piece (ghost preview at landing position) */
const int (*shape)[4][2] = &PIECE_SHAPES[state->piece_type][state->piece_rotation];
/* Draw ghost piece */
int ghost_y = state->piece_y;
while (can_place_piece(state, state->piece_type, state->piece_x, ghost_y + 1, state->piece_rotation)) {
ghost_y++;
}
for (int i = 0; i < 4; i++) {
int bx = state->piece_x + (*shape)[i][0];
int by = ghost_y + (*shape)[i][1];
if (bx >= 0 && bx < BOARD_WIDTH && by >= 0 && by < BOARD_HEIGHT) {
int screen_x = board_x + 1 + bx * CELL_WIDTH;
int screen_y = board_y + 1 + by;
set_color_cell('.', screen_x, screen_y, COLOR_GREEN, COLOR_BLACK);
set_color_cell('.', screen_x + 1, screen_y, COLOR_GREEN, COLOR_BLACK);
}
}
/* Draw current piece */
for (int i = 0; i < 4; i++) {
int bx = state->piece_x + (*shape)[i][0];
int by = state->piece_y + (*shape)[i][1];
if (bx >= 0 && bx < BOARD_WIDTH && by >= 0 && by < BOARD_HEIGHT) {
int screen_x = board_x + 1 + bx * CELL_WIDTH;
int screen_y = board_y + 1 + by;
set_color_cell(PIECE_CHARS[state->piece_type], screen_x, screen_y, PIECE_COLORS[state->piece_type], COLOR_BLACK);
set_color_cell(PIECE_CHARS[state->piece_type], screen_x + 1, screen_y, PIECE_COLORS[state->piece_type], COLOR_BLACK);
}
}
/* Draw stats panel (right side) */
int stats_x = STATS_X;
int stats_y = STATS_Y;
/* Stats border */
set_color_cell(0x250C, stats_x, stats_y, COLOR_CYAN, COLOR_BLACK); /* ┌ */
for (int x = 1; x < 18; x++) {
set_color_cell(0x2500, stats_x + x, stats_y, COLOR_CYAN, COLOR_BLACK); /* ─ */
}
set_color_cell(0x2510, stats_x + 18, stats_y, COLOR_CYAN, COLOR_BLACK); /* ┐ */
for (int y = 1; y <= 8; y++) {
set_color_cell(0x2502, stats_x, stats_y + y, COLOR_CYAN, COLOR_BLACK); /* │ */
set_color_cell(0x2502, stats_x + 18, stats_y + y, COLOR_CYAN, COLOR_BLACK);
}
set_color_cell(0x2514, stats_x, stats_y + 9, COLOR_CYAN, COLOR_BLACK); /* └ */
for (int x = 1; x < 18; x++) {
set_color_cell(0x2500, stats_x + x, stats_y + 9, COLOR_CYAN, COLOR_BLACK); /* ─ */
}
set_color_cell(0x2518, stats_x + 18, stats_y + 9, COLOR_CYAN, COLOR_BLACK); /* ┘ */
/* Draw stats text */
char buffer[32];
snprintf(buffer, sizeof(buffer), "SCORE:%d", state->score);
set_message(buffer, stats_x + 2, stats_y + 1);
snprintf(buffer, sizeof(buffer), "LEVEL:%d", state->level);
set_message(buffer, stats_x + 2, stats_y + 2);
snprintf(buffer, sizeof(buffer), "LINES:%d", state->total_lines);
set_message(buffer, stats_x + 2, stats_y + 3);
snprintf(buffer, sizeof(buffer), "NEXT:");
set_message(buffer, stats_x + 2, stats_y + 5);
/* Draw next piece preview */
const int (*next_shape)[4][2] = &PIECE_SHAPES[state->next_piece_type][0];
for (int i = 0; i < 4; i++) {
int dx = next_shape[0][i][0];
int dy = next_shape[0][i][1];
int px = stats_x + 3 + dx;
int py = stats_y + 6 + dy;
if (px > stats_x && px < stats_x + 18 && py > stats_y && py < stats_y + 9) {
set_color_cell(PIECE_CHARS[state->next_piece_type], px, py, PIECE_COLORS[state->next_piece_type], COLOR_BLACK);
}
}
/* Draw status bar at bottom */
int status_y = 23;
char status_msg[80];
if (state->game_over) {
snprintf(status_msg, sizeof(status_msg), "GAME OVER - Press 'q' to quit or 'r' to restart");
} else if (state->paused) {
snprintf(status_msg, sizeof(status_msg), "PAUSED - Press 'p' to resume or 'q' to quit");
} else {
snprintf(status_msg, sizeof(status_msg), "Arrows: move | Z: rotate | Space: drop | P: pause | Q: quit");
}
set_message(status_msg, 0, status_y);
}
/* Game event handler */
int world_event(struct event* event, void* game) {
GameState* state = (GameState*)game;
state->screen_width = event->width;
state->screen_height = event->height;
if (event->type == EVENT_START) {
spawn_piece(state);
} else if (event->type == EVENT_KEY) {
if (!state->game_over && !state->paused) {
switch (event->key) {
case KEY_LEFT:
move_piece_left(state);
break;
case KEY_RIGHT:
move_piece_right(state);
break;
case KEY_DOWN:
move_piece_down(state);
break;
case ' ': /* Hard drop */
hard_drop_piece(state);
lock_piece(state);
check_and_clear_lines(state);
break;
case 'z':
case 'Z':
rotate_piece(state);
break;
case 'p':
case 'P':
state->paused = 1;
break;
case 'q':
case 'Q':
destroy_game(state);
return 1; /* Exit game */
}
} else if (state->paused) {
switch (event->key) {
case 'p':
case 'P':
state->paused = 0;
break;
case 'q':
case 'Q':
destroy_game(state);
return 1; /* Exit game */
}
} else if (state->game_over) {
switch (event->key) {
case 'r':
case 'R':
/* Restart game */
memset(state->board, 0, sizeof(state->board));
state->score = 0;
state->level = 1;
state->lines_cleared = 0;
state->total_lines = 0;
state->game_over = 0;
state->gravity_speed = 25 - (state->level - 1) * 2;
if (state->gravity_speed < 3) state->gravity_speed = 3;
spawn_piece(state);
break;
case 'q':
case 'Q':
destroy_game(state);
return 1; /* Exit game */
}
}
} else if (event->type == EVENT_TIMEOUT) {
if (!state->paused && !state->game_over) {
apply_gravity(state);
}
}
render_game(state);
return 0; /* Continue game */
}
int main() {
game_speed(50); /* 50ms per frame = 20 FPS for faster gameplay */
return start_world(init_game, world_event, destroy_game);
}