#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.2 #define SOFT_DROP_MULT 4 #define LOCK_TICK 0.5 enum Phases { GENERATION, FALL, LOCK, PATTERN, MARK, ITERATE, ANIMATE, ELIMINATE, COMPLETE } game_phase; #include "tetromino.h" struct { int pos_x; int pos_y; int rotation; int type; } current_piece; struct Color matrix[MATRIX_WIDTH][MATRIX_HEIGHT]; int piece_queue[TETROMINO_COUNT*2]; int current_bag = 0; int current_piece_index = 0; int hold = 7; int held = 0; int hard_drop = 0; double phase_time = 0.0; 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) { for (int i = 0; i < tetrominos[current_piece.type].size; i++) { for (int j = 0; j < tetrominos[current_piece.type].size; j++) { if (tetrominos[current_piece.type].directions[current_piece.rotation][j][i] == 1) { if (current_piece.pos_x + i - 1 + x >= MATRIX_WIDTH || current_piece.pos_x + i - 1 + x < 0 || current_piece.pos_y + j - 1 + y < 0 || (!ColorIsEqual(BLACK, matrix[current_piece.pos_x + i - 1 + x][current_piece.pos_y + j - 1 + y]) && !ColorIsEqual(WHITE, matrix[current_piece.pos_x + i - 1 + x][current_piece.pos_y + j - 1 + y]))) { return 1; } } } } return 0; } void render_matrix(void) { int matrix_origin_x = (SCREEN_WIDTH/2 - (MATRIX_WIDTH * BLOCK_SIZE))/2; 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 ,matrix[i][j] ); } } /* draw ghost piece */ int ghost_y = 0; while(!is_overlap(0, ghost_y)) { ghost_y--; } for (int i = 0; i < tetrominos[current_piece.type].size; i++) { for (int j = 0; j < tetrominos[current_piece.type].size; j++) { if (tetrominos[current_piece.type].directions[current_piece.rotation][j][i] == 1) { draw_block( (matrix_origin_x + ((current_piece.pos_x + i - 1) * BLOCK_SIZE)) + 3 ,(matrix_origin_y + ((current_piece.pos_y + j + ghost_y) * BLOCK_SIZE)) + 3 ,BLOCK_SIZE - 6 ,BLOCK_SIZE - 6 ,tetrominos[current_piece.type].color ); draw_block( (matrix_origin_x + ((current_piece.pos_x + i - 1) * BLOCK_SIZE)) + 4 ,(matrix_origin_y + ((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[current_piece.type].size; i++) { for (int j = 0; j < tetrominos[current_piece.type].size; j++) { if (tetrominos[current_piece.type].directions[current_piece.rotation][j][i] == 1) { draw_block( (matrix_origin_x + ((current_piece.pos_x + i - 1) * BLOCK_SIZE)) + 1 ,(matrix_origin_y + ((current_piece.pos_y + j - 1) * BLOCK_SIZE)) + 1 ,BLOCK_SIZE - 2 ,BLOCK_SIZE - 2 ,tetrominos[current_piece.type].color ); } } } /* draw piece hold */ draw_block( SCREEN_WIDTH/2 + 1 - (BLOCK_SIZE), matrix_origin_y + MATRIX_HEIGHT/2 * BLOCK_SIZE - 3 * BLOCK_SIZE, 4*BLOCK_SIZE, 4*BLOCK_SIZE, BLACK ); if (hold != 7) { for (int i = 0; i < tetrominos[hold].size; i++) { for (int j = 0; j < tetrominos[hold].size; j++) { if (tetrominos[hold].directions[NORTH][j][i] == 1) { draw_block( ((SCREEN_WIDTH/2 + 1 - (BLOCK_SIZE)) + (i * BLOCK_SIZE)) + 1 + ((hold != I && hold != O)?(BLOCK_SIZE/2):0) ,matrix_origin_y + (MATRIX_HEIGHT/2 * BLOCK_SIZE) + ((j-3) * BLOCK_SIZE) ,BLOCK_SIZE - 2 ,BLOCK_SIZE - 2 ,tetrominos[hold].color ); } } } } /* draw piece queue */ draw_block( SCREEN_WIDTH/2 + 1 - (BLOCK_SIZE), matrix_origin_y + MATRIX_HEIGHT/2 * BLOCK_SIZE - 20 * BLOCK_SIZE, 4*BLOCK_SIZE, (4*BLOCK_SIZE) * 4, BLACK ); for (int y = 0; y < 4; y++) { for (int i = 0; i < tetrominos[piece_queue[(y+current_piece_index)%14]].size; i++) { for (int j = 0; j < tetrominos[piece_queue[(y+current_piece_index)%14]].size; j++) { if (tetrominos[piece_queue[(y+current_piece_index)%14]].directions[NORTH][j][i] == 1) { draw_block( ((SCREEN_WIDTH/2 + 1 - (BLOCK_SIZE)) + (i * BLOCK_SIZE)) + 1 + ((piece_queue[(y+current_piece_index)%14] != I && piece_queue[(y+current_piece_index)%14] != O)?(BLOCK_SIZE/2):0) ,((matrix_origin_y + MATRIX_HEIGHT/2 * BLOCK_SIZE - 20 * BLOCK_SIZE) + ((j - 1) * BLOCK_SIZE)) + 1 + ((3 - y) * BLOCK_SIZE * 4) + BLOCK_SIZE/2 ,BLOCK_SIZE - 2 ,BLOCK_SIZE - 2 ,tetrominos[piece_queue[(y+current_piece_index)%14]].color ); } } } } } void shuffle_bag(int *bag) { for (int i = 0; i < TETROMINO_COUNT; i++) { bag[i] = i; } for (int i = 0; i < TETROMINO_COUNT; i++) { int r = i + rand() / (RAND_MAX / (TETROMINO_COUNT - i) + 1); int t = bag[r]; bag[r] = bag[i]; bag[i] = t; } } void generation_phase(void) { // bag generation if (current_piece_index == 14) { shuffle_bag(&(piece_queue[7])); current_piece_index = 0; } if (current_piece_index == 7) { shuffle_bag(piece_queue); } // set current piece current_piece.type = piece_queue[current_piece_index++]; current_piece.rotation = NORTH; current_piece.pos_x = 4; current_piece.pos_y = 20; if (is_overlap(0,0)) { CloseWindow(); } } void handle_hold(void) { if (IsKeyDown(KEY_SPACE) && !held) { if (hold == 7) { hold = current_piece.type; current_piece.type = piece_queue[current_piece_index]; current_piece_index++; current_piece_index = current_piece_index % 14; } else { int temp = current_piece.type; current_piece.type = hold; hold = temp; } game_phase = FALL; current_piece.rotation = NORTH; current_piece.pos_x = 4; current_piece.pos_y = 20; if (is_overlap(0,0)) { CloseWindow(); } held = 1; } } void handle_rotate(void) { static int rotated_cw = 0; static int rotated_ccw = 0; if (game_phase == FALL || game_phase == LOCK) { int *kick_table; int can_rotate = 0; int i; if (IsKeyDown(KEY_L) && !rotated_cw) { kick_table = tetrominos[current_piece.type].cw_kick_table[current_piece.rotation]; current_piece.rotation++; current_piece.rotation = current_piece.rotation % 4; for (i = 0; i < 10 && !can_rotate; i += 2) { if (!is_overlap(kick_table[i], kick_table[i+1])) { current_piece.pos_x += kick_table[i]; current_piece.pos_y += kick_table[i+1]; can_rotate = 1; if (game_phase == LOCK) { phase_time = 0.0; } } } if (!can_rotate) { current_piece.rotation--; if (current_piece.rotation < 0) current_piece.rotation = 3; } rotated_cw = 1; } if (IsKeyUp(KEY_L)) { rotated_cw = 0; } if (IsKeyDown(KEY_J) && !rotated_ccw) { kick_table = tetrominos[current_piece.type].ccw_kick_table[current_piece.rotation]; current_piece.rotation--; if (current_piece.rotation < 0) current_piece.rotation = 3; for (i = 0; i < 10 && !can_rotate; i += 2) { if (!is_overlap(kick_table[i], kick_table[i+1])) { current_piece.pos_x += kick_table[i]; current_piece.pos_y += kick_table[i+1]; can_rotate = 1; if (game_phase == LOCK) { phase_time = 0.0; } } } if (!can_rotate) { current_piece.rotation++; current_piece.rotation = current_piece.rotation % 4; } rotated_ccw = 1; } if (IsKeyUp(KEY_J)) { rotated_ccw = 0; } } } void move_right(void) { if (is_overlap(1, 0)) { return; } if (game_phase == LOCK) { phase_time = 0.0; } current_piece.pos_x++; } void move_left(void) { if (is_overlap(-1, 0)) { return; } if (game_phase == LOCK) { phase_time = 0.0; } current_piece.pos_x--; } void handle_lr(void) { static int moved_right = 0; static int moved_left = 0; if (game_phase == FALL || game_phase == LOCK) { if (IsKeyDown(KEY_D) && !moved_right) { move_right(); moved_right = 1; } if (IsKeyUp(KEY_D)) { moved_right = 0; } if (IsKeyDown(KEY_A) && !moved_left) { move_left(); moved_left = 1; } if (IsKeyUp(KEY_A)) { moved_left = 0; } } } void move_piece_to_matrix(void) { for (int i = 0; i < tetrominos[current_piece.type].size; i++) { for (int j = 0; j < tetrominos[current_piece.type].size; j++) { if (tetrominos[current_piece.type].directions[current_piece.rotation][j][i] == 1) { matrix[current_piece.pos_x + i - 1][current_piece.pos_y + j - 1] = tetrominos[current_piece.type].color; } } } } void handle_fall(void) { phase_time += GetFrameTime(); if (game_phase == FALL) { if (!hard_drop && ((!IsKeyDown(KEY_S) && phase_time < FALL_TICK) || phase_time < FALL_TICK / SOFT_DROP_MULT)) { return; } else { phase_time = 0.0; } } if (is_overlap(0, -1)) { game_phase = LOCK; } else { game_phase = FALL; current_piece.pos_y--; } if (is_overlap(0, -1) && (phase_time >= LOCK_TICK || hard_drop)) { move_piece_to_matrix(); generation_phase(); held = 0; game_phase = FALL; phase_time = 0.0; hard_drop = 0; return; } } void handle_hard_drop(void) { static int dropped = 0; if (game_phase == FALL || game_phase == LOCK) { if (IsKeyDown(KEY_W) && !dropped) { dropped = 1; hard_drop = 1; while(!is_overlap(0, -1)) { current_piece.pos_y--; } } if (IsKeyUp(KEY_W)) { dropped = 0; } } } void clear_line(int line) { for (int y = line; y < MATRIX_HEIGHT - 1; y++) { for (int x = 0; x < MATRIX_WIDTH; x++) { matrix[x][y] = matrix[x][y+1]; if (ColorIsEqual(matrix[x][y], WHITE) && y < 20) { matrix[x][y] = BLACK; } } } } void handle_line_clears(void) { for (int y = 0; y < MATRIX_HEIGHT; y++) { int line_full = 1; for (int x = 0; x < MATRIX_WIDTH; x++) { if (ColorIsEqual(matrix[x][y], BLACK) || ColorIsEqual(matrix[x][y], WHITE)) { line_full = 0; } } if (line_full) { clear_line(y); } } } int main(void) { srand(time(NULL)); InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "~turnipGod's Tetris"); // set all blocks to white for (int y = 0; y < 40; y++) { for (int x = 0; x < 10; x++) { matrix[x][y] = WHITE; } } for (int y = 0; y < 20; y++) { for (int x = 0; x < 10; x++) { matrix[x][y] = BLACK; } } game_phase = FALL; shuffle_bag(piece_queue); shuffle_bag(&(piece_queue[7])); generation_phase(); while (!WindowShouldClose()) { handle_lr(); handle_rotate(); handle_hold(); handle_hard_drop(); handle_fall(); handle_line_clears(); BeginDrawing(); ClearBackground(WHITE); render_matrix(); EndDrawing(); } CloseWindow(); return 0; }