#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 enum Phases { GENERATION, FALL, LOCK, PATTERN, //MARK, //ITERATE, //ANIMATE, //ELIMINATE, //COMPLETE } game_phase; enum InputResult { NONE = 0, MOVED, HARD_DROPPED }; #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; double phase_time; double fall_tick; 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] ); } } int ghost_y = 0; if (game_phase == FALL || game_phase == LOCK) { /* draw ghost piece */ 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) { if (phase_time < 0.2) { return; } // 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; held = 0; phase_time = 0.0; game_phase = FALL; } int rotate_piece(int dir) { int *kick_table; int i; int can_rotate = 0; if (dir == 1) { kick_table = tetrominos[current_piece.type].cw_kick_table[current_piece.rotation]; } else if (dir == -1) { kick_table = tetrominos[current_piece.type].ccw_kick_table[current_piece.rotation]; } current_piece.rotation += dir; if (current_piece.rotation < 0) { current_piece.rotation = 3; } else { 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 (!can_rotate) { current_piece.rotation -= dir; if (current_piece.rotation < 0) { current_piece.rotation = 3; } else { current_piece.rotation = current_piece.rotation % 4; } } return can_rotate; } int move_piece(int dir) { if (is_overlap(dir, 0)) { return 0; } current_piece.pos_x += dir; return 1; } 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 hold_piece(void) { if (!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; } phase_time = 0.0; 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; } } int handle_input(void) { static int moved_l = 0; static int moved_r = 0; static int moved_cw = 0; static int moved_ccw = 0; static int hard_dropped = 0; if (IsKeyDown(KEY_A)) { if (!moved_l) { moved_l = move_piece(-1); return MOVED; } } else { moved_l = 0; } if (IsKeyDown(KEY_D)) { if (!moved_r) { moved_r = move_piece(+1); return MOVED; } } else { moved_r = 0; } if (IsKeyDown(KEY_L)) { if (!moved_cw) { moved_cw = rotate_piece(+1); return MOVED; } } else { moved_cw = 0; } if (IsKeyDown(KEY_J)) { if (!moved_ccw) { moved_ccw = rotate_piece(-1); return MOVED; } } else { moved_ccw = 0; } if (IsKeyDown(KEY_W)) { if (!hard_dropped) { hard_dropped = 1; while (!is_overlap(0, -1)) { current_piece.pos_y--; } return HARD_DROPPED; } } else { hard_dropped = 0; } if (IsKeyDown(KEY_S)) { fall_tick = FALL_TICK / SOFT_DROP_MULT; } else { fall_tick = FALL_TICK; } if (IsKeyDown(KEY_SPACE)) { hold_piece(); } return NONE; } void lock_phase(void) { enum InputResult action = handle_input(); if (action == MOVED) { phase_time = 0.0; } if (!is_overlap(0, -1)) { phase_time = 0.0; game_phase = FALL; } else if (phase_time >= LOCK_TICK || action == HARD_DROPPED) { move_piece_to_matrix(); phase_time = 0.0; game_phase = PATTERN; } } void fall_phase(void) { enum InputResult action = handle_input(); if (action == HARD_DROPPED) { phase_time = 0.0; game_phase = PATTERN; } else { if (is_overlap(0, -1)) { phase_time = 0.0; game_phase = LOCK; } else if (phase_time >= fall_tick || action == HARD_DROPPED) { phase_time = 0.0; current_piece.pos_y--; } } } 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 pattern_phase(void) { move_piece_to_matrix(); 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--); } } phase_time = 0.0; game_phase = GENERATION; } void game_logic(void) { phase_time += GetFrameTime(); switch (game_phase) { case GENERATION: generation_phase(); break; case FALL: fall_phase(); break; case LOCK: lock_phase(); break; case PATTERN: pattern_phase(); break; } } int main(void) { srand(time(NULL)); InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "~turnipGod's Tetris"); // set default block colors for (int y = 0; y < 40; y++) { for (int x = 0; x < 10; x++) { if (y < 20) { matrix[x][y] = BLACK; } else { matrix[x][y] = WHITE; } } } shuffle_bag(piece_queue); shuffle_bag(&(piece_queue[7])); phase_time = 0.0; game_phase = GENERATION; while (!WindowShouldClose()) { game_logic(); BeginDrawing(); ClearBackground(WHITE); render_matrix(); EndDrawing(); } CloseWindow(); return 0; }