#include "raylib.h" #include #include #include #define SCREEN_WIDTH 800 #define SCREEN_HEIGHT 600 #define MATRIX_WIDTH 10 #define MATRIX_HEIGHT 40 #define BLOCK_SIZE 25 #define FALL_TICK 0.3 #define SOFT_DROP_MULT 4.0 #define LOCK_TICK 0.5 #define DAS 0.3 enum InputResult { NONE = 0, MOVED, HARD_DROPPED }; #include "tetromino.h" struct { enum { GENERATION, FALL, LOCK, PATTERN, //MARK, //ITERATE, //ANIMATE, //ELIMINATE, //COMPLETE, GAME_OVER, } phase; struct Color matrix[MATRIX_WIDTH][MATRIX_HEIGHT]; struct { int pos_x; int pos_y; int rotation; int type; } current_piece; int piece_queue[TETROMINO_COUNT*2]; int current_piece_index; int hold_piece; int held; double phase_time; double fall_tick; double das_timer; } game_state; void draw_block(int x, int y, int width, int height, struct Color color) { DrawRectangle(x, -y + SCREEN_HEIGHT - height, width, height, color); } int is_overlap(int x, int y) { int piece = game_state.current_piece.type; int p_x = game_state.current_piece.pos_x; int p_y = game_state.current_piece.pos_y; int rot = game_state.current_piece.rotation; int c_x; int c_y; int i; int j; for (i = 0; i < tetrominos[piece].size; i++) { for (j = 0; j < tetrominos[piece].size; j++) { if (tetrominos[piece].directions[rot][j][i] == 1) { c_x = p_x + i - 1 + x; c_y = p_y + j - 1 + y; if (p_x + i - 1 + x >= MATRIX_WIDTH || p_x + i - 1 + x < 0 || p_y + j - 1 + y < 0 || (!ColorIsEqual(BLACK, game_state.matrix[c_x][c_y]) && !ColorIsEqual(WHITE, game_state.matrix[c_x][c_y]))) { return 1; } } } } return 0; } void render_matrix(void) { int matrix_origin_x = ((SCREEN_WIDTH/2 - (MATRIX_WIDTH * BLOCK_SIZE))/2) + (BLOCK_SIZE * 5); int matrix_origin_y = (SCREEN_HEIGHT - MATRIX_HEIGHT/2 * BLOCK_SIZE)/2; /* draw matrix background */ draw_block( matrix_origin_x - 1 ,matrix_origin_y - 1 ,MATRIX_WIDTH * BLOCK_SIZE + 2 ,MATRIX_HEIGHT/2 * BLOCK_SIZE + 2 ,GRAY ); /* draw matrix */ for (int i = 0; i < MATRIX_WIDTH; i++) { for (int j = 0; j < MATRIX_HEIGHT; j++) { draw_block( matrix_origin_x + i * BLOCK_SIZE + 1 ,matrix_origin_y + j * BLOCK_SIZE + 1 ,BLOCK_SIZE - 2 ,BLOCK_SIZE - 2 ,game_state.matrix[i][j] ); } } int ghost_y = 0; if (game_state.phase == FALL || game_state.phase == LOCK) { /* draw ghost piece */ while(!is_overlap(0, ghost_y)) { ghost_y--; } for (int i = 0; i < tetrominos[game_state.current_piece.type].size; i++) { for (int j = 0; j < tetrominos[game_state.current_piece.type].size; j++) { if (tetrominos[game_state.current_piece.type].directions[game_state.current_piece.rotation][j][i] == 1) { draw_block( (matrix_origin_x + ((game_state.current_piece.pos_x + i - 1) * BLOCK_SIZE)) + 3 ,(matrix_origin_y + ((game_state.current_piece.pos_y + j + ghost_y) * BLOCK_SIZE)) + 3 ,BLOCK_SIZE - 6 ,BLOCK_SIZE - 6 ,tetrominos[game_state.current_piece.type].color ); draw_block( (matrix_origin_x + ((game_state.current_piece.pos_x + i - 1) * BLOCK_SIZE)) + 4 ,(matrix_origin_y + ((game_state.current_piece.pos_y + j + ghost_y) * BLOCK_SIZE)) + 4 ,BLOCK_SIZE - 8 ,BLOCK_SIZE - 8 ,BLACK ); } } } /* draw current piece */ for (int i = 0; i < tetrominos[game_state.current_piece.type].size; i++) { for (int j = 0; j < tetrominos[game_state.current_piece.type].size; j++) { if (tetrominos[game_state.current_piece.type].directions[game_state.current_piece.rotation][j][i] == 1) { draw_block( (matrix_origin_x + ((game_state.current_piece.pos_x + i - 1) * BLOCK_SIZE)) + 1 ,(matrix_origin_y + ((game_state.current_piece.pos_y + j - 1) * BLOCK_SIZE)) + 1 ,BLOCK_SIZE - 2 ,BLOCK_SIZE - 2 ,tetrominos[game_state.current_piece.type].color ); } } } } /* draw piece hold */ draw_block( matrix_origin_x - BLOCK_SIZE * 5, matrix_origin_y + MATRIX_HEIGHT/2 * BLOCK_SIZE - 4 * BLOCK_SIZE, 4*BLOCK_SIZE, 4*BLOCK_SIZE, BLACK ); if (game_state.hold_piece != 7) { for (int i = 0; i < tetrominos[game_state.hold_piece].size; i++) { for (int j = 0; j < tetrominos[game_state.hold_piece].size; j++) { if (tetrominos[game_state.hold_piece].directions[NORTH][j][i] == 1) { draw_block( ((matrix_origin_x - BLOCK_SIZE * 5) + (i * BLOCK_SIZE)) + 1 + ((game_state.hold_piece != I && game_state.hold_piece != O)?(BLOCK_SIZE/2):0) ,matrix_origin_y + (MATRIX_HEIGHT/2 * BLOCK_SIZE) + ((j-4) * BLOCK_SIZE) - ((game_state.hold_piece == I)?BLOCK_SIZE/2:0) ,BLOCK_SIZE - 2 ,BLOCK_SIZE - 2 ,tetrominos[game_state.hold_piece].color ); } } } } /* draw piece queue */ draw_block( (SCREEN_WIDTH/2 + 1 - (BLOCK_SIZE)) + (BLOCK_SIZE * 4), matrix_origin_y + MATRIX_HEIGHT/2 * BLOCK_SIZE - 20 * BLOCK_SIZE, 4*BLOCK_SIZE, (4*BLOCK_SIZE) * 5, BLACK ); for (int y = 0; y < 5; y++) { for (int i = 0; i < tetrominos[game_state.piece_queue[(y+game_state.current_piece_index)%14]].size; i++) { for (int j = 0; j < tetrominos[game_state.piece_queue[(y+game_state.current_piece_index)%14]].size; j++) { if (tetrominos[game_state.piece_queue[(y+game_state.current_piece_index)%14]].directions[NORTH][j][i] == 1) { draw_block( (((SCREEN_WIDTH/2 + 1 - (BLOCK_SIZE)) + (i * BLOCK_SIZE)) + 1) + (BLOCK_SIZE * 4) + ((game_state.piece_queue[(y+game_state.current_piece_index)%14] != I && game_state.piece_queue[(y+game_state.current_piece_index)%14] != O)?(BLOCK_SIZE/2):0) ,((matrix_origin_y + MATRIX_HEIGHT/2 * BLOCK_SIZE - 15 * BLOCK_SIZE) + ((j - 1) * BLOCK_SIZE)) + 1 + ((3 - y) * BLOCK_SIZE * 4) - ((game_state.piece_queue[(y+game_state.current_piece_index)%14] == I)?BLOCK_SIZE/2:0) ,BLOCK_SIZE - 2 ,BLOCK_SIZE - 2 ,tetrominos[game_state.piece_queue[(y+game_state.current_piece_index)%14]].color ); } } } } } void shuffle_bag(int *bag) { int i; int r; int t; for (i = 0; i < TETROMINO_COUNT; i++) { bag[i] = i; } for (i = 0; i < TETROMINO_COUNT; i++) { r = i + rand() / (RAND_MAX / (TETROMINO_COUNT - i) + 1); t = bag[r]; bag[r] = bag[i]; bag[i] = t; } } void generation_phase(void) { if (game_state.phase_time < 0.2) { return; } // bag generation if (game_state.current_piece_index == 14) { shuffle_bag(&(game_state.piece_queue[7])); game_state.current_piece_index = 0; } if (game_state.current_piece_index == 7) { shuffle_bag(game_state.piece_queue); } // set current piece game_state.current_piece.type = game_state.piece_queue[game_state.current_piece_index++]; game_state.current_piece.rotation = NORTH; game_state.current_piece.pos_x = 4; game_state.current_piece.pos_y = 20; game_state.held = 0; game_state.phase_time = 0.0; // blockout lose condition if (is_overlap(0, 0)) { game_state.phase = GAME_OVER; } else { game_state.phase = FALL; } } int rotate_piece(int dir) { int *kick_table; int i; int can_rotate = 0; int piece = game_state.current_piece.type; int rot = game_state.current_piece.rotation; int init_rot = game_state.current_piece.rotation; if (dir == 1) { kick_table = tetrominos[piece].cw_kick_table[rot]; } else if (dir == -1) { kick_table = tetrominos[piece].ccw_kick_table[rot]; } rot += dir; if (rot < 0) { rot = 3; } else { rot = rot % 4; } game_state.current_piece.rotation = rot; for (i = 0; i < 10 && !can_rotate; i += 2) { if (!is_overlap(kick_table[i], kick_table[i+1])) { game_state.current_piece.pos_x += kick_table[i]; game_state.current_piece.pos_y += kick_table[i+1]; can_rotate = 1; } } if (!can_rotate) { game_state.current_piece.rotation = init_rot; } return can_rotate; } int move_piece(int dir) { if (is_overlap(dir, 0)) { return 0; } game_state.current_piece.pos_x += dir; return 1; } void move_piece_to_matrix(void) { int piece = game_state.current_piece.type; int rot = game_state.current_piece.rotation; int x = game_state.current_piece.pos_x; int y = game_state.current_piece.pos_y; int i; int j; for (i = 0; i < tetrominos[piece].size; i++) { for (j = 0; j < tetrominos[piece].size; j++) { if (tetrominos[piece].directions[rot][j][i] == 1) { game_state.matrix[x + i - 1][y + j - 1] = tetrominos[piece].color; } } } } void hold_piece(void) { int temp; if (!game_state.held) { if (game_state.hold_piece == 7) { game_state.hold_piece = game_state.current_piece.type; game_state.current_piece.type = game_state.piece_queue[game_state.current_piece_index]; game_state.current_piece_index++; game_state.current_piece_index = game_state.current_piece_index % 14; } else { temp = game_state.current_piece.type; game_state.current_piece.type = game_state.hold_piece; game_state.hold_piece = temp; } game_state.phase_time = 0.0; game_state.phase = FALL; game_state.current_piece.rotation = NORTH; game_state.current_piece.pos_x = 4; game_state.current_piece.pos_y = 20; if (is_overlap(0,0)) { CloseWindow(); } game_state.held = 1; } } int handle_input(void) { enum InputResult action = NONE; if (IsKeyPressed(KEY_A)) { move_piece(-1); game_state.das_timer = 0.0; action = MOVED; } if (IsKeyPressed(KEY_D)) { move_piece(+1); game_state.das_timer = 0.0; action = MOVED; } if (IsKeyDown(KEY_A) || IsKeyDown(KEY_D)) { game_state.das_timer += GetFrameTime(); } else { game_state.das_timer = 0.0; } if (IsKeyDown(KEY_A) && game_state.das_timer > DAS) { move_piece(-1); action = MOVED; } if (IsKeyDown(KEY_D) && game_state.das_timer > DAS) { move_piece(+1); action = MOVED; } if (IsKeyPressed(KEY_L)) { rotate_piece(+1); action = MOVED; } if (IsKeyPressed(KEY_J)) { rotate_piece(-1); action = MOVED; } if (IsKeyPressed(KEY_W)) { while (!is_overlap(0, -1)) { game_state.current_piece.pos_y--; } action = HARD_DROPPED; } if (IsKeyDown(KEY_S)) { game_state.fall_tick = FALL_TICK / SOFT_DROP_MULT; } else { game_state.fall_tick = FALL_TICK; } if (IsKeyPressed(KEY_SPACE)) { hold_piece(); } return action; } void lock_phase(void) { enum InputResult action = handle_input(); if (action == MOVED) { game_state.phase_time = 0.0; } if (!is_overlap(0, -1)) { game_state.phase_time = 0.0; game_state.phase = FALL; } else if (game_state.phase_time >= LOCK_TICK || action == HARD_DROPPED) { move_piece_to_matrix(); game_state.phase_time = 0.0; game_state.phase = PATTERN; } } void fall_phase(void) { enum InputResult action = handle_input(); if (action == HARD_DROPPED) { game_state.phase_time = 0.0; game_state.phase = PATTERN; } else { if (is_overlap(0, -1)) { game_state.phase_time = 0.0; game_state.phase = LOCK; } else if (game_state.phase_time >= game_state.fall_tick || action == HARD_DROPPED) { game_state.phase_time = 0.0; game_state.current_piece.pos_y--; } } } void clear_line(int line) { int y; int x; for (y = line; y < MATRIX_HEIGHT - 1; y++) { for (x = 0; x < MATRIX_WIDTH; x++) { game_state.matrix[x][y] = game_state.matrix[x][y+1]; if (ColorIsEqual(game_state.matrix[x][y], WHITE) && y < 20) { game_state.matrix[x][y] = BLACK; } } } } int check_lockout(void) { int piece = game_state.current_piece.type; int rot = game_state.current_piece.rotation; int y = game_state.current_piece.pos_y; int fully_above = 1; int i; int j; for (i = 0; i < tetrominos[piece].size; i++) { for (j = 0; j < tetrominos[piece].size; j++) { if (tetrominos[piece].directions[rot][j][i] == 1) { if (y + j - 1 < 20) fully_above = 0; } } } return fully_above; } void pattern_phase(void) { int y; int x; int line_full; if (check_lockout()) { game_state.phase_time = 0.0; game_state.phase = GAME_OVER; return; } move_piece_to_matrix(); for (y = 0; y < MATRIX_HEIGHT; y++) { line_full = 1; for (x = 0; x < MATRIX_WIDTH; x++) { if (ColorIsEqual(game_state.matrix[x][y], BLACK) || ColorIsEqual(game_state.matrix[x][y], WHITE)) { line_full = 0; } } if (line_full) { clear_line(y--); } } game_state.phase_time = 0.0; game_state.phase = GENERATION; } void game_logic(void) { game_state.phase_time += GetFrameTime(); switch (game_state.phase) { case GENERATION: generation_phase(); break; case FALL: fall_phase(); break; case LOCK: lock_phase(); break; case PATTERN: pattern_phase(); break; case GAME_OVER: break; } } void init(void) { int y; int x; // set default block colors for (y = 0; y < 40; y++) { for (x = 0; x < 10; x++) { if (y < 20) { game_state.matrix[x][y] = BLACK; } else { game_state.matrix[x][y] = WHITE; } } } shuffle_bag(game_state.piece_queue); shuffle_bag(&(game_state.piece_queue[7])); game_state.phase_time = 0.0; game_state.phase = GENERATION; game_state.hold_piece = 7; game_state.current_piece_index = 0; game_state.held = 0; game_state.fall_tick = FALL_TICK; } int main(void) { srand(time(NULL)); InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "~turnipGod's Tetris"); SetTargetFPS(60); init(); while (!WindowShouldClose() && game_state.phase != GAME_OVER) { game_logic(); BeginDrawing(); ClearBackground(WHITE); render_matrix(); DrawFPS(0, 0); EndDrawing(); } CloseWindow(); return 0; }