#include "world.h" #include #include #include /* 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); }