module tetris; import raylib; /** * raylib - classic game: tetris * * Sample game developed by Marc Palau and Ramon Santamaria, * converted to C3 by Christoffer Lerno. * * This game has been created using raylib v1.3 (www.raylib.com) * * Copyright (c) 2015 Ramon Santamaria (@raysan5) */ //---------------------------------------------------------------------------------- // Some Defines //---------------------------------------------------------------------------------- const SQUARE_SIZE = 20; const GRID_HORIZONTAL_SIZE = 12; const GRID_VERTICAL_SIZE = 20; const LATERAL_SPEED = 10; const TURNING_SPEED = 12; const FAST_FALL_AWAIT_COUNTER = 30; const FADING_TIME = 33; //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- enum GridSquare { EMPTY, MOVING, FULL, BLOCK, FADING } //------------------------------------------------------------------------------------ // Global Variables Declaration //------------------------------------------------------------------------------------ const int SCREEN_WIDTH = 800; const int SCREEN_HEIGHT = 450; bool game_over = false; bool pause = false; // Matrices GridSquare[GRID_VERTICAL_SIZE][GRID_HORIZONTAL_SIZE] grid; GridSquare[4][4] piece; GridSquare[4][4] incoming_piece; struct IntVec { int x; int y; } // These variables keep track of the active piece position int piece_position_x = 0; int piece_position_y = 0; // Game parameters Color fading_color; //int fallingSpeed; // In frames bool begin_play = true; // This var is only true at the begining of the game, used for the first matrix creations bool piece_active = false; bool detection = false; bool line_to_delete = false; // Statistics int level = 1; int lines = 0; // Counters int gravity_movement_counter = 0; int lateral_movement_counter = 0; int turn_movement_counter = 0; int fast_fall_movement_counter = 0; int fade_line_counter = 0; // Based on level int gravity_speed = 30; //------------------------------------------------------------------------------------ // Program main entry point //------------------------------------------------------------------------------------ fn void main() { // Initialization (Note windowTitle is unused on Android) //--------------------------------------------------------- raylib::init_window(SCREEN_WIDTH, SCREEN_HEIGHT, "classic game: tetris"); init_game(); raylib::set_target_fps(60); //-------------------------------------------------------------------------------------- // Main game loop while (!raylib::window_should_close()) // Detect window close button or ESC key { // Update and Draw //---------------------------------------------------------------------------------- update_draw_frame(); //---------------------------------------------------------------------------------- } // De-Initialization //-------------------------------------------------------------------------------------- unload_game(); // Unload loaded data (textures, sounds, models...) raylib::close_window(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- } //-------------------------------------------------------------------------------------- // Game Module Functions Definition //-------------------------------------------------------------------------------------- // Initialize game variables fn void init_game() { // Initialize game statistics level = 1; lines = 0; fading_color = raylib::GRAY; piece_position_x = 0; piece_position_y = 0; pause = false; begin_play = true; piece_active = false; detection = false; line_to_delete = false; // Counters gravity_movement_counter = 0; lateral_movement_counter = 0; turn_movement_counter = 0; fast_fall_movement_counter = 0; fade_line_counter = 0; gravity_speed = 30; // Initialize grid matrices for (int i = 0; i < GRID_HORIZONTAL_SIZE; i++) { for (int j = 0; j < GRID_VERTICAL_SIZE; j++) { if ((j == GRID_VERTICAL_SIZE - 1) || (i == 0) || (i == GRID_HORIZONTAL_SIZE - 1)) { grid[i][j] = BLOCK; } else { grid[i][j] = EMPTY; } } } // Initialize incoming piece matrices for (int i = 0; i < 4; i++) { for (int j = 0; j< 4; j++) { incoming_piece[i][j] = EMPTY; } } } // Update game (one frame) fn void update_game() { if (game_over) { if (raylib::is_key_pressed(keyboard::ENTER)) { init_game(); game_over = false; } return; } if (raylib::is_key_pressed((KeyboardKey)'P')) pause = !pause; if (pause) return; if (line_to_delete) { // Animation when deleting lines fade_line_counter++; fading_color = fade_line_counter % 8 < 4 ? raylib::MAROON : raylib::GRAY; if (fade_line_counter >= FADING_TIME) { lines += delete_complete_lines(); fade_line_counter = 0; line_to_delete = false; } return; } if (!piece_active) { // Get another piece piece_active = create_piece(); // We leave a little time before starting the fast falling down fast_fall_movement_counter = 0; } else // Piece falling { // Counters update fast_fall_movement_counter++; gravity_movement_counter++; lateral_movement_counter++; turn_movement_counter++; // We make sure to move if we've pressed the key this frame if (raylib::is_key_pressed(keyboard::LEFT) || raylib::is_key_pressed(keyboard::RIGHT)) lateral_movement_counter = LATERAL_SPEED; if (raylib::is_key_pressed(keyboard::UP)) turn_movement_counter = TURNING_SPEED; // Fall down if (raylib::is_key_down(keyboard::DOWN) && (fast_fall_movement_counter >= FAST_FALL_AWAIT_COUNTER)) { // We make sure the piece is going to fall this frame gravity_movement_counter += gravity_speed; } if (gravity_movement_counter >= gravity_speed) { // Basic falling movement check_detection(&detection); // Check if the piece has collided with another piece or with the boundings resolve_falling_movement(&detection, &piece_active); // Check if we fullfilled a line and if so, erase the line and pull down the the lines above check_completion(&line_to_delete); gravity_movement_counter = 0; } // Move laterally at player's will if (lateral_movement_counter >= LATERAL_SPEED) { // Update the lateral movement and if success, reset the lateral counter if (!resolve_lateral_movement()) lateral_movement_counter = 0; } // Turn the piece at player's will if (turn_movement_counter >= TURNING_SPEED) { // Update the turning movement and reset the turning counter if (resolve_turn_movement()) turn_movement_counter = 0; } } // Game over logic for (int j = 0; j < 2; j++) { for (int i = 1; i < GRID_HORIZONTAL_SIZE - 1; i++) { if (grid[i][j] == GridSquare.FULL) { game_over = true; } } } } // Draw game (one frame) fn void draw_game() { raylib::begin_drawing(); raylib::clear_background(raylib::RAYWHITE); if (game_over) { raylib::draw_text("PRESS [ENTER] TO PLAY AGAIN", raylib::get_screen_width() / 2 - raylib::measure_text("PRESS [ENTER] TO PLAY AGAIN", 20) / 2, raylib::get_screen_height() / 2 - 50, 20, raylib::GRAY); raylib::end_drawing(); return; } // Draw gameplay area IntVec offset = { SCREEN_WIDTH / 2 - (GRID_HORIZONTAL_SIZE * SQUARE_SIZE / 2) - 50, SCREEN_HEIGHT / 2 - ((GRID_VERTICAL_SIZE - 1) * SQUARE_SIZE / 2) + SQUARE_SIZE * 2 }; offset.y -= 50; // NOTE: Harcoded position! int controller = offset.x; for (int j = 0; j < GRID_VERTICAL_SIZE; j++) { for (int i = 0; i < GRID_HORIZONTAL_SIZE; i++) { // Draw each square of the grid switch (grid[i][j]) { case EMPTY: raylib::draw_line(offset.x, offset.y, offset.x + SQUARE_SIZE, offset.y, raylib::LIGHTGRAY ); raylib::draw_line(offset.x, offset.y, offset.x, offset.y + SQUARE_SIZE, raylib::LIGHTGRAY ); raylib::draw_line(offset.x + SQUARE_SIZE, offset.y, offset.x + SQUARE_SIZE, offset.y + SQUARE_SIZE, raylib::LIGHTGRAY ); raylib::draw_line(offset.x, offset.y + SQUARE_SIZE, offset.x + SQUARE_SIZE, offset.y + SQUARE_SIZE, raylib::LIGHTGRAY ); offset.x += SQUARE_SIZE; case FULL: raylib::draw_rectangle(offset.x, offset.y, SQUARE_SIZE, SQUARE_SIZE, raylib::GRAY); offset.x += SQUARE_SIZE; case MOVING: raylib::draw_rectangle(offset.x, offset.y, SQUARE_SIZE, SQUARE_SIZE, raylib::DARKGRAY); offset.x += SQUARE_SIZE; case BLOCK: raylib::draw_rectangle(offset.x, offset.y, SQUARE_SIZE, SQUARE_SIZE, raylib::LIGHTGRAY); offset.x += SQUARE_SIZE; case FADING: raylib::draw_rectangle(offset.x, offset.y, SQUARE_SIZE, SQUARE_SIZE, fading_color); offset.x += SQUARE_SIZE; default: } } offset.x = controller; offset.y += SQUARE_SIZE; } // Draw incoming piece (hardcoded) offset.x = 500; offset.y = 45; controller = offset.x; for (int j = 0; j < 4; j++) { for (int i = 0; i < 4; i++) { switch (incoming_piece[i][j]) { case EMPTY: raylib::draw_line(offset.x, offset.y, offset.x + SQUARE_SIZE, offset.y, raylib::LIGHTGRAY); raylib::draw_line(offset.x, offset.y, offset.x, offset.y + SQUARE_SIZE, raylib::LIGHTGRAY); raylib::draw_line(offset.x + SQUARE_SIZE, offset.y, offset.x + SQUARE_SIZE, offset.y + SQUARE_SIZE, raylib::LIGHTGRAY); raylib::draw_line(offset.x, offset.y + SQUARE_SIZE, offset.x + SQUARE_SIZE, offset.y + SQUARE_SIZE, raylib::LIGHTGRAY); offset.x += SQUARE_SIZE; case MOVING: raylib::draw_rectangle(offset.x, offset.y, SQUARE_SIZE, SQUARE_SIZE, raylib::GRAY); offset.x += SQUARE_SIZE; default: break; } } offset.x = controller; offset.y += SQUARE_SIZE; } raylib::draw_text("INCOMING:", offset.x, offset.y - 100, 10, raylib::GRAY); raylib::draw_text(raylib::text_format("LINES: %04i", lines), offset.x, offset.y + 20, 10, raylib::GRAY); if (pause) { raylib::draw_text("GAME PAUSED", SCREEN_WIDTH / 2 - raylib::measure_text("GAME PAUSED", 40)/2, SCREEN_HEIGHT/2 - 40, 40, raylib::GRAY); } raylib::end_drawing(); } // Unload game variables fn void unload_game() { // TODO: Unload all dynamic loaded data (textures, sounds, models...) } // Update and Draw (one frame) fn void update_draw_frame() { update_game(); draw_game(); } //-------------------------------------------------------------------------------------- // Additional module functions //-------------------------------------------------------------------------------------- fn bool create_piece() { piece_position_x = (int)((GRID_HORIZONTAL_SIZE - 4)/2); piece_position_y = 0; // If the game is starting and you are going to create the first piece, we create an extra one if (begin_play) { get_random_piece(); begin_play = false; } // We assign the incoming piece to the actual piece for (int i = 0; i < 4; i++) { for (int j = 0; j< 4; j++) { piece[i][j] = incoming_piece[i][j]; } } // We assign a random piece to the incoming one get_random_piece(); // Assign the piece to the grid for (int i = piece_position_x; i < piece_position_x + 4; i++) { for (int j = 0; j < 4; j++) { if (piece[i - (int)piece_position_x][j] == GridSquare.MOVING) grid[i][j] = MOVING; } } return true; } fn void get_random_piece() { int random = raylib::get_random_value(0, 6); for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { incoming_piece[i][j] = EMPTY; } } switch (random) { case 0: incoming_piece[1][1] = MOVING; incoming_piece[2][1] = MOVING; incoming_piece[1][2] = MOVING; incoming_piece[2][2] = MOVING; //Cube case 1: incoming_piece[1][0] = MOVING; incoming_piece[1][1] = MOVING; incoming_piece[1][2] = MOVING; incoming_piece[2][2] = MOVING; //L case 2: incoming_piece[1][2] = MOVING; incoming_piece[2][0] = MOVING; incoming_piece[2][1] = MOVING; incoming_piece[2][2] = MOVING; //L inversa case 3: incoming_piece[0][1] = MOVING; incoming_piece[1][1] = MOVING; incoming_piece[2][1] = MOVING; incoming_piece[3][1] = MOVING; //Recta case 4: incoming_piece[1][0] = MOVING; incoming_piece[1][1] = MOVING; incoming_piece[1][2] = MOVING; incoming_piece[2][1] = MOVING; //Creu tallada case 5: incoming_piece[1][1] = MOVING; incoming_piece[2][1] = MOVING; incoming_piece[2][2] = MOVING; incoming_piece[3][2] = MOVING; //S case 6: incoming_piece[1][2] = MOVING; incoming_piece[2][2] = MOVING; incoming_piece[2][1] = MOVING; incoming_piece[3][1] = MOVING; //S inversa default: unreachable(); } } fn void resolve_falling_movement(bool* detection_ref, bool* piece_active_ref) { // If we finished moving this piece, we stop it if (*detection_ref) { for (int j = GRID_VERTICAL_SIZE - 2; j >= 0; j--) { for (int i = 1; i < GRID_HORIZONTAL_SIZE - 1; i++) { if (grid[i][j] == GridSquare.MOVING) { grid[i][j] = FULL; *detection_ref = false; *piece_active_ref = false; } } } } else // We move down the piece { for (int j = GRID_VERTICAL_SIZE - 2; j >= 0; j--) { for (int i = 1; i < GRID_HORIZONTAL_SIZE - 1; i++) { if (grid[i][j] == GridSquare.MOVING) { grid[i][j+1] = MOVING; grid[i][j] = EMPTY; } } } piece_position_y++; } } fn bool resolve_lateral_movement() { bool collision = false; // Piece movement if (raylib::is_key_down(keyboard::LEFT)) // Move left { // Check if is possible to move to left for (int j = GRID_VERTICAL_SIZE - 2; j >= 0; j--) { for (int i = 1; i < GRID_HORIZONTAL_SIZE - 1; i++) { if (grid[i][j] == GridSquare.MOVING) { // Check if we are touching the left wall or we have a full square at the left if ((i-1 == 0) || (grid[i-1][j] == GridSquare.FULL)) collision = true; } } } // If able, move left if (!collision) { for (int j = GRID_VERTICAL_SIZE - 2; j >= 0; j--) { for (int i = 1; i < GRID_HORIZONTAL_SIZE - 1; i++) // We check the matrix from left to right { // Move everything to the left if (grid[i][j] == GridSquare.MOVING) { grid[i-1][j] = MOVING; grid[i][j] = EMPTY; } } } piece_position_x--; } } else if (raylib::is_key_down(keyboard::RIGHT)) // Move right { // Check if is possible to move to right for (int j = GRID_VERTICAL_SIZE - 2; j >= 0; j--) { for (int i = 1; i < GRID_HORIZONTAL_SIZE - 1; i++) { if (grid[i][j] == GridSquare.MOVING) { // Check if we are touching the right wall or we have a full square at the right if ((i+1 == GRID_HORIZONTAL_SIZE - 1) || (grid[i+1][j] == GridSquare.FULL)) { collision = true; } } } } // If able move right if (!collision) { for (int j = GRID_VERTICAL_SIZE - 2; j >= 0; j--) { for (int i = GRID_HORIZONTAL_SIZE - 1; i >= 1; i--) // We check the matrix from right to left { // Move everything to the right if (grid[i][j] == GridSquare.MOVING) { grid[i+1][j] = MOVING; grid[i][j] = EMPTY; } } } piece_position_x++; } } return collision; } fn bool resolve_turn_movement() { // Input for turning the piece if (raylib::is_key_down(keyboard::UP)) { GridSquare aux; bool checker = false; // Check all turning possibilities if ((grid[piece_position_x + 3][piece_position_y] == GridSquare.MOVING) && (grid[piece_position_x][piece_position_y] != GridSquare.EMPTY) && (grid[piece_position_x][piece_position_y] != GridSquare.MOVING)) checker = true; if ((grid[piece_position_x + 3][piece_position_y + 3] == GridSquare.MOVING) && (grid[piece_position_x + 3][piece_position_y] != GridSquare.EMPTY) && (grid[piece_position_x + 3][piece_position_y] != GridSquare.MOVING)) checker = true; if ((grid[piece_position_x][piece_position_y + 3] == GridSquare.MOVING) && (grid[piece_position_x + 3][piece_position_y + 3] != GridSquare.EMPTY) && (grid[piece_position_x + 3][piece_position_y + 3] != GridSquare.MOVING)) checker = true; if ((grid[piece_position_x][piece_position_y] == GridSquare.MOVING) && (grid[piece_position_x][piece_position_y + 3] != GridSquare.EMPTY) && (grid[piece_position_x][piece_position_y + 3] != GridSquare.MOVING)) checker = true; if ((grid[piece_position_x + 1][piece_position_y] == GridSquare.MOVING) && (grid[piece_position_x][piece_position_y + 2] != GridSquare.EMPTY) && (grid[piece_position_x][piece_position_y + 2] != GridSquare.MOVING)) checker = true; if ((grid[piece_position_x + 3][piece_position_y + 1] == GridSquare.MOVING) && (grid[piece_position_x + 1][piece_position_y] != GridSquare.EMPTY) && (grid[piece_position_x + 1][piece_position_y] != GridSquare.MOVING)) checker = true; if ((grid[piece_position_x + 2][piece_position_y + 3] == GridSquare.MOVING) && (grid[piece_position_x + 3][piece_position_y + 1] != GridSquare.EMPTY) && (grid[piece_position_x + 3][piece_position_y + 1] != GridSquare.MOVING)) checker = true; if ((grid[piece_position_x][piece_position_y + 2] == GridSquare.MOVING) && (grid[piece_position_x + 2][piece_position_y + 3] != GridSquare.EMPTY) && (grid[piece_position_x + 2][piece_position_y + 3] != GridSquare.MOVING)) checker = true; if ((grid[piece_position_x + 2][piece_position_y] == GridSquare.MOVING) && (grid[piece_position_x][piece_position_y + 1] != GridSquare.EMPTY) && (grid[piece_position_x][piece_position_y + 1] != GridSquare.MOVING)) checker = true; if ((grid[piece_position_x + 3][piece_position_y + 2] == GridSquare.MOVING) && (grid[piece_position_x + 2][piece_position_y] != GridSquare.EMPTY) && (grid[piece_position_x + 2][piece_position_y] != GridSquare.MOVING)) checker = true; if ((grid[piece_position_x + 1][piece_position_y + 3] == GridSquare.MOVING) && (grid[piece_position_x + 3][piece_position_y + 2] != GridSquare.EMPTY) && (grid[piece_position_x + 3][piece_position_y + 2] != GridSquare.MOVING)) checker = true; if ((grid[piece_position_x][piece_position_y + 1] == GridSquare.MOVING) && (grid[piece_position_x + 1][piece_position_y + 3] != GridSquare.EMPTY) && (grid[piece_position_x + 1][piece_position_y + 3] != GridSquare.MOVING)) checker = true; if ((grid[piece_position_x + 1][piece_position_y + 1] == GridSquare.MOVING) && (grid[piece_position_x + 1][piece_position_y + 2] != GridSquare.EMPTY) && (grid[piece_position_x + 1][piece_position_y + 2] != GridSquare.MOVING)) checker = true; if ((grid[piece_position_x + 2][piece_position_y + 1] == GridSquare.MOVING) && (grid[piece_position_x + 1][piece_position_y + 1] != GridSquare.EMPTY) && (grid[piece_position_x + 1][piece_position_y + 1] != GridSquare.MOVING)) checker = true; if ((grid[piece_position_x + 2][piece_position_y + 2] == GridSquare.MOVING) && (grid[piece_position_x + 2][piece_position_y + 1] != GridSquare.EMPTY) && (grid[piece_position_x + 2][piece_position_y + 1] != GridSquare.MOVING)) checker = true; if ((grid[piece_position_x + 1][piece_position_y + 2] == GridSquare.MOVING) && (grid[piece_position_x + 2][piece_position_y + 2] != GridSquare.EMPTY) && (grid[piece_position_x + 2][piece_position_y + 2] != GridSquare.MOVING)) checker = true; if (!checker) { aux = piece[0][0]; piece[0][0] = piece[3][0]; piece[3][0] = piece[3][3]; piece[3][3] = piece[0][3]; piece[0][3] = aux; aux = piece[1][0]; piece[1][0] = piece[3][1]; piece[3][1] = piece[2][3]; piece[2][3] = piece[0][2]; piece[0][2] = aux; aux = piece[2][0]; piece[2][0] = piece[3][2]; piece[3][2] = piece[1][3]; piece[1][3] = piece[0][1]; piece[0][1] = aux; aux = piece[1][1]; piece[1][1] = piece[2][1]; piece[2][1] = piece[2][2]; piece[2][2] = piece[1][2]; piece[1][2] = aux; } for (int j = GRID_VERTICAL_SIZE - 2; j >= 0; j--) { for (int i = 1; i < GRID_HORIZONTAL_SIZE - 1; i++) { if (grid[i][j] == GridSquare.MOVING) { grid[i][j] = EMPTY; } } } for (int i = piece_position_x; i < piece_position_x + 4; i++) { for (int j = piece_position_y; j < piece_position_y + 4; j++) { if (piece[i - piece_position_x][j - piece_position_y] == GridSquare.MOVING) { grid[i][j] = MOVING; } } } return true; } return false; } fn void check_detection(bool *detection_ref) { for (int j = GRID_VERTICAL_SIZE - 2; j >= 0; j--) { for (int i = 1; i < GRID_HORIZONTAL_SIZE - 1; i++) { if ((grid[i][j] == GridSquare.MOVING) && ((grid[i][j+1] == GridSquare.FULL) || (grid[i][j+1] == GridSquare.BLOCK))) *detection_ref = true; } } } fn void check_completion(bool *line_to_delete_ref) { int calculator = 0; for (int j = GRID_VERTICAL_SIZE - 2; j >= 0; j--) { calculator = 0; for (int i = 1; i < GRID_HORIZONTAL_SIZE - 1; i++) { // Count each square of the line if (grid[i][j] == GridSquare.FULL) { calculator++; } // Check if we completed the whole line if (calculator == GRID_HORIZONTAL_SIZE - 2) { *line_to_delete_ref = true; calculator = 0; // points++; // Mark the completed line for (int z = 1; z < GRID_HORIZONTAL_SIZE - 1; z++) { grid[z][j] = FADING; } } } } } fn int delete_complete_lines() { int lines_to_erase = 0; // Erase the completed line for (int j = GRID_VERTICAL_SIZE - 2; j >= 0; j--) { while (grid[1][j] == GridSquare.FADING) { lines_to_erase++; for (int i = 1; i < GRID_HORIZONTAL_SIZE - 1; i++) { grid[i][j] = GridSquare.EMPTY; } for (int j2 = j-1; j2 >= 0; j2--) { for (int i2 = 1; i2 < GRID_HORIZONTAL_SIZE - 1; i2++) { switch (grid[i2][j2]) { case FULL: grid[i2][j2+1] = GridSquare.FULL; grid[i2][j2] = GridSquare.EMPTY; case FADING: grid[i2][j2+1] = GridSquare.FADING; grid[i2][j2] = GridSquare.EMPTY; default: } } } } } return lines_to_erase; }