Last active
January 20, 2025 21:07
-
-
Save Hekzagonal-git/074f7617da060ab8905a30b13163d676 to your computer and use it in GitHub Desktop.
Fully* Game-Accurate Tetris clone made with PyGame
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # CS20 Final Assignment | |
| # My Full Name Goes Here | |
| # January 13, 2025 | |
| import pygame | |
| import random as rand | |
| # Stores TETROMINO_COLORS used for each of the 7 Tetrominoes | |
| TETROMINO_COLORS = [ | |
| (0, 255, 255), | |
| (0, 0, 255), | |
| (255, 127, 0), | |
| (255, 255, 0), | |
| (0, 255, 0), | |
| (128, 0, 128), | |
| (255, 0, 0) | |
| ] | |
| # All non-Tetromino colors | |
| BLACK = (0, 0, 0) | |
| WHITE = (255, 255, 255) | |
| GRID_GREY = (170, 170, 170) | |
| DEAD_SQUARE_GREY = (120, 120, 120) | |
| YELLOW = (200, 200, 0) | |
| PIECE_LOCK = pygame.USEREVENT + 1 | |
| DAS_LEFT = pygame.USEREVENT + 2 | |
| DAS_RIGHT = pygame.USEREVENT + 3 | |
| class Tetromino: | |
| '''A Tetris piece.''' | |
| # Each row is a different piece, each column is a different rotation. | |
| # The numbers represent the spaces each figure occupies on a 5x5 grid (I piece) | |
| # or a 3x3 grid (every other piece) | |
| FIGURES = [ | |
| [[11, 12, 13, 14], [7, 12, 17, 22], [10, 11, 12, 13], [2, 7, 12, 17]], | |
| [[0, 3, 4, 5], [1, 2, 4, 7], [3, 4, 5, 8], [1, 4, 6, 7]], | |
| [[2, 3, 4, 5], [1, 4, 7, 8], [3, 4, 5, 6], [0, 1, 4, 7]], | |
| [[1, 2, 4, 5], [4, 5, 7, 8], [3, 4, 6, 7], [0, 1, 3, 4]], | |
| [[1, 2, 3, 4], [1, 4, 5, 8], [4, 5, 6, 7], [0, 3, 4, 7]], | |
| [[1, 3, 4, 5], [1, 4, 5, 7], [3, 4, 5, 7], [1, 3, 4, 7]], | |
| [[0, 1, 4, 5], [2, 4, 5, 7], [3, 4, 7, 8], [1, 3, 4, 6]] | |
| ] | |
| # SRS Offset Data table | |
| JLSTZ_OFFSET_DATA = [ | |
| [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]], | |
| [[0, 0], [1, 0], [1, -1], [0, 2], [1, 2]], | |
| [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]], | |
| [[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]] | |
| ] | |
| # SRS Offset Data table | |
| I_OFFSET_DATA = [ | |
| [[0, 0], [-1, 0], [2, 0], [-1, 0], [2, 0]], | |
| [[-1, 0], [0, 0], [0, 0], [0, 1], [0, -2]], | |
| [[-1, 1], [1, 1], [-2, 1], [1, 0], [-2, 0]], | |
| [[0, 1], [0, 1], [0, 1], [0, -1], [0, 2]] | |
| ] | |
| # SRS Offset Data table | |
| O_OFFSET_DATA = [ | |
| [0, 0], | |
| [0, -1], | |
| [-1, -1], | |
| [-1, 0], | |
| ] | |
| def __init__(self, source): | |
| '''Spawns a Tetromino. "source" is either "Hold" or "Queue", which represent where the Tetromino is coming from.''' | |
| if source == "Queue": | |
| self.type = game.advance_piece_queue() | |
| elif source == "Hold": | |
| if game.piece_held == None: | |
| self.type = game.advance_piece_queue() | |
| else: | |
| self.type = game.piece_held | |
| self.x = 3 | |
| self.y = 1 | |
| self.rotation = 0 | |
| if self.type == 0: | |
| self.x -= 1 | |
| self.y -= 1 | |
| pygame.time.set_timer(PIECE_LOCK, 0) | |
| self.move_reset_counter = 0 | |
| game.lock_delay = False | |
| if debug: | |
| print("initialized new Tetromino") | |
| def image(self, rotation_difference = 0): | |
| '''Returns the piece's shape and orientation. Allows for images to be made with non-true rotations for SRS.''' | |
| return Tetromino.FIGURES[self.type][(self.rotation + rotation_difference) % 4] | |
| def rotate(self, rotation_distance): | |
| '''Rotates the current active piece using the Super Rotation System.''' | |
| if self.type == 0: | |
| self.new_rotation = (self.rotation + rotation_distance) % 4 | |
| for i in range(5): | |
| (self.previous_offset_x, self.previous_offset_y) = Tetromino.I_OFFSET_DATA[self.rotation][i] | |
| self.new_offset_x, self.new_offset_y = Tetromino.I_OFFSET_DATA[self.new_rotation][i] | |
| self.x_difference = self.previous_offset_x - self.new_offset_x | |
| self.y_difference = self.previous_offset_y - self.new_offset_y | |
| if not game.intersects(self.x_difference, self.y_difference, rotation_distance): | |
| self.rotation = self.new_rotation | |
| self.x += self.x_difference | |
| self.y -= self.y_difference | |
| break | |
| elif self.type == 3: | |
| (self.previous_offset_x, self.previous_offset_y) = Tetromino.O_OFFSET_DATA[self.rotation] | |
| self.new_rotation = (self.rotation + rotation_distance) % 4 | |
| (self.new_offset_x, self.new_offset_y) = Tetromino.O_OFFSET_DATA[self.new_rotation] | |
| self.x_difference = self.previous_offset_x - self.new_offset_x | |
| self.y_difference = self.previous_offset_y - self.new_offset_y | |
| if not game.intersects(self.x_difference, self.y_difference, rotation_distance): | |
| self.rotation = self.new_rotation | |
| self.x += self.x_difference | |
| self.y -= self.y_difference | |
| else: | |
| self.new_rotation = (self.rotation + rotation_distance) % 4 | |
| for i in range(5): | |
| (self.previous_offset_x, self.previous_offset_y) = Tetromino.JLSTZ_OFFSET_DATA[self.rotation][i] | |
| self.new_offset_x, self.new_offset_y = Tetromino.JLSTZ_OFFSET_DATA[self.new_rotation][i] | |
| self.x_difference = self.previous_offset_x - self.new_offset_x | |
| self.y_difference = self.previous_offset_y - self.new_offset_y | |
| if not game.intersects(self.x_difference, self.y_difference, rotation_distance): | |
| self.rotation = self.new_rotation | |
| self.x += self.x_difference | |
| self.y -= self.y_difference | |
| break | |
| if game.lock_delay == True: | |
| self.move_reset_counter += 1 | |
| pygame.time.set_timer(PIECE_LOCK, 500) | |
| class Tetris: | |
| '''A game of Tetris.''' | |
| MATRIX_WIDTH = 10 | |
| MATRIX_HEIGHT = 20 | |
| MATRIX_X_OFFSET = 5 | |
| MATRIX_Y_OFFSET = 1.5 | |
| GAME_ZOOM = 20 | |
| # The gravity values used. Left column is the amount of frames it takes a piece to fall, | |
| # right column is the distance it falls. Each row represents a different level. | |
| GRAVITIES = [ | |
| [60, 1], | |
| [48, 1], | |
| [37, 1], | |
| [28, 1], | |
| [21, 1], | |
| [16, 1], | |
| [11, 1], | |
| [8, 1], | |
| [6, 1], | |
| [4, 1], | |
| [3, 1], | |
| [2, 1], | |
| [1, 1], | |
| [0.5, 2], | |
| ] | |
| def __init__(self): | |
| '''Initializes the game.''' | |
| # Game info - Stats | |
| self.score = 0 | |
| self.level = 1 | |
| self.total_lines_cleared = 0 | |
| # Game info - Other | |
| self.active_piece = None | |
| self.piece_held = None | |
| self.hold_used = False | |
| self.lock_delay = False | |
| # User input info | |
| self.hard_dropping = False | |
| self.soft_dropping = False | |
| self.do_das_left = False | |
| self.do_das_right = False | |
| # Create matrix and set game state to active | |
| self.field = [[0 for j in range(Tetris.MATRIX_WIDTH)] for i in range(Tetris.MATRIX_HEIGHT)] | |
| self.queue = [] | |
| self.game_active = True | |
| def call_rotation(self, rotation_distance): | |
| '''Ensures there is an active piece to rotate before calling the self.active_piece.rotate() function.''' | |
| try: | |
| self.active_piece.rotate(rotation_distance) | |
| except: | |
| pass | |
| def __str__(self): | |
| '''Returns the matrix as a string for testing purposes.''' | |
| stringed_field = '' | |
| for i in range(Tetris.MATRIX_HEIGHT): | |
| for j in range(Tetris.MATRIX_WIDTH): | |
| stringed_field += str(self.field[i][j]) | |
| stringed_field += "\n" | |
| return(f"{stringed_field}") | |
| def advance_piece_queue(self): | |
| '''Refills queue if needed, then removes the first item in the queue and returns it.''' | |
| if len(self.queue) < 6: | |
| bag = [0, 1, 2, 3, 4, 5, 6] | |
| rand.shuffle(bag) | |
| self.queue += bag | |
| new_piece = self.queue[0] | |
| self.queue.pop(0) | |
| return(new_piece) | |
| def clear_lines(self): | |
| '''Clears any row where all spaces are non-empty. Shifts contents of all rows above cleared line down one row.''' | |
| if debug: | |
| print("Tetris.clear_lines() called") | |
| lines = 0 | |
| for i in range(Tetris.MATRIX_HEIGHT): | |
| if 0 not in self.field[i]: | |
| lines += 1 | |
| self.total_lines_cleared += 1 | |
| for i1 in range(i, 1, -1): | |
| for j in range(Tetris.MATRIX_WIDTH): | |
| self.field[i1][j] = self.field[i1 - 1][j] | |
| self.field[0] = [0 for i in range(Tetris.MATRIX_WIDTH)] | |
| # Adds to the score based on how many lines were cleared. | |
| match lines: | |
| case 1: | |
| self.score += 100 * self.level | |
| case 2: | |
| self.score += 300 * self.level | |
| case 3: | |
| self.score += 500 * self.level | |
| case 4: | |
| self.score += 800 * self.level | |
| case _: | |
| pass | |
| def hold_piece(self): | |
| '''Swaps the active piece with the piece in the hold space.''' | |
| if self.active_piece != None and not self.hold_used: | |
| if debug: | |
| print("Tetris.hold_piece() called") | |
| self.active_piece, self.piece_held = Tetromino("Hold"), self.active_piece.type | |
| self.hold_used = True | |
| self.lock_delay = False | |
| pygame.time.set_timer(PIECE_LOCK, 0) | |
| def place_piece(self): | |
| '''Locks a Tetromino into place.''' | |
| if debug: | |
| print("Tetris.place_piece() called") | |
| # Check to make sure the piece should be placed | |
| if self.intersects(0, -1, 0) and self.game_active: | |
| # Places each cell of the piece | |
| self.piece_placement_scan_dimension = self.get_scan_dimension() | |
| for i in range(self.piece_placement_scan_dimension): | |
| for j in range(self.piece_placement_scan_dimension): | |
| if i * self.piece_placement_scan_dimension + j in self.active_piece.image(): | |
| self.field[i + self.active_piece.y][j + self.active_piece.x] = self.active_piece.type + 1 | |
| self.clear_lines() | |
| pygame.time.set_timer(PIECE_LOCK, 0) | |
| self.lock_delay = False | |
| self.hold_used = False | |
| self.active_piece = None | |
| def get_scan_dimension(self, piece_state = None, piece_type = None): | |
| '''Checks if the piece's figure sits inside a 3x3 or a 5x5. Returns the resulting side length.''' | |
| if piece_state == "Active" or piece_state == None: | |
| if self.active_piece == None: | |
| return(0) | |
| elif self.active_piece.type == 0: | |
| return(5) | |
| else: | |
| return(3) | |
| if piece_state == "Queue" or piece_state == "Hold": | |
| if piece_type == 0: | |
| return(5) | |
| if piece_type in [1, 2, 3, 4, 5, 6]: | |
| return(3) | |
| def move_piece_h(self, dx): | |
| '''Moves active piece along the X axis.''' | |
| if self.active_piece != None: | |
| self.active_piece.x += dx | |
| if self.intersects(): | |
| self.active_piece.x -= dx | |
| if self.lock_delay == True: | |
| self.active_piece.move_reset_counter += 1 | |
| pygame.time.set_timer(PIECE_LOCK, 500) | |
| def move_piece_down(self): | |
| '''Moves active piece down 1 space on the Y axis.''' | |
| if self.active_piece != None: | |
| self.active_piece.y += 1 | |
| if self.intersects(): | |
| self.active_piece.y -= 1 | |
| # If the player has evaded piece lock 15 times, places the piece. | |
| if self.active_piece.move_reset_counter >= 15: | |
| self.place_piece() | |
| elif game.hard_dropping: | |
| self.place_piece() | |
| self.hard_dropping = False | |
| # Starts a timer for the piece to lock in place (piece lock) on its own. | |
| else: | |
| if not self.lock_delay: | |
| self.lock_delay = True | |
| self.active_piece.move_reset_counter = 0 | |
| pygame.time.set_timer(PIECE_LOCK, 500) | |
| # Deactivates the piece lock timer if the piece moves down successfully before the player maxes out their move reset. | |
| elif self.active_piece.move_reset_counter < 15: | |
| pygame.time.set_timer(PIECE_LOCK, 0) | |
| self.lock_delay = False | |
| def hard_drop(self): | |
| '''Drops active piece down as far as it can go.''' | |
| if debug: | |
| print("Tetris.hard_drop() called") | |
| self.hard_dropping = True | |
| self.soft_dropping = False | |
| while self.hard_dropping: | |
| self.move_piece_down() | |
| def intersects(self, x_difference = 0, y_difference = 0, rotation_difference = 0): | |
| '''Checks if a piece is outside of the matrix and returns the result. Allows for checks using altered coordinates and rotations.''' | |
| self.intersection_scan_dimension = self.get_scan_dimension() | |
| intersection = False | |
| for i in range(self.intersection_scan_dimension): | |
| for j in range(self.intersection_scan_dimension): | |
| if i * self.intersection_scan_dimension + j in self.active_piece.image(rotation_difference): | |
| if i + self.active_piece.y - y_difference > Tetris.MATRIX_HEIGHT - 1 or \ | |
| i + self.active_piece.y - y_difference < 0 or \ | |
| j + self.active_piece.x + x_difference < 0 or \ | |
| j + self.active_piece.x + x_difference > Tetris.MATRIX_WIDTH - 1 or \ | |
| self.field[i + self.active_piece.y - y_difference][j + self.active_piece.x + x_difference] != 0: | |
| intersection = True | |
| return(intersection) | |
| # Initialize game engine | |
| pygame.init() | |
| # Create fonts for in-game text | |
| VARIABLE_DISPLAY_FONT = pygame.font.SysFont("verdana", 12) | |
| GAME_OVER_FONT = pygame.font.SysFont("consolas", 24) | |
| # Create window | |
| SCREEN_SIZE = (20 * Tetris.GAME_ZOOM, 25 * Tetris.GAME_ZOOM) | |
| screen = pygame.display.set_mode(SCREEN_SIZE) | |
| pygame.display.set_caption("Tetris but Awesome") | |
| # Initialize game | |
| clock = pygame.time.Clock() | |
| FPS = 60 | |
| current_frame = 0 | |
| game = Tetris() | |
| running = True | |
| debug = False | |
| while running: | |
| # Stores the current frame and level. | |
| current_frame = (current_frame + 1) % FPS | |
| game.level = 1 + game.total_lines_cleared // 10 | |
| if game.active_piece == None: | |
| game.active_piece = Tetromino("Queue") | |
| try: | |
| if current_frame % Tetris.GRAVITIES[game.level - 1][0] == 0: | |
| for i in range(Tetris.GRAVITIES[game.level][1]): | |
| game.move_piece_down() | |
| except IndexError: | |
| if current_frame % Tetris.GRAVITIES[13][0] == 0: | |
| for i in range(Tetris.GRAVITIES[13][1]): | |
| game.move_piece_down() | |
| for event in pygame.event.get(): | |
| if event.type == pygame.QUIT: | |
| running = False | |
| pygame.quit() | |
| # Checks for key inputs that only matter when the game of Tetris hasn't been lost yet | |
| if event.type == pygame.KEYDOWN and game.game_active == True: | |
| if event.key == pygame.K_LSHIFT: | |
| game.soft_dropping = True | |
| # Left and right movement | |
| if event.key == pygame.K_a: | |
| game.move_piece_h(-1) | |
| pygame.time.set_timer(DAS_LEFT, 133) | |
| game.do_das_right = False | |
| if event.key == pygame.K_d: | |
| game.move_piece_h(1) | |
| pygame.time.set_timer(DAS_RIGHT, 133) | |
| game.do_das_left = False | |
| # Inputs for piece rotation | |
| if event.key == pygame.K_j: | |
| game.call_rotation(-1) | |
| if event.key == pygame.K_k: | |
| game.call_rotation(2) | |
| if event.key == pygame.K_l: | |
| game.call_rotation(1) | |
| if event.key == pygame.K_SLASH: | |
| game.hold_piece() | |
| if event.key == pygame.K_s: | |
| game.hard_drop() | |
| if event.key == pygame.K_F1: | |
| debug = not debug | |
| # Checks for key inputs that matter regardless of game state. Currently, this is only the reset key. | |
| if event.type == pygame.KEYDOWN: | |
| if event.key == pygame.K_c: | |
| game = Tetris() | |
| # Checks for a key non-input so the game knows when to stop soft-dropping / DASing. | |
| if event.type == pygame.KEYUP: | |
| if event.key == pygame.K_LSHIFT: | |
| game.soft_dropping = False | |
| if event.key == pygame.K_a: | |
| pygame.time.set_timer(DAS_LEFT, 0) | |
| game.do_das_left = False | |
| if event.key == pygame.K_d: | |
| pygame.time.set_timer(DAS_RIGHT, 0) | |
| game.do_das_right = False | |
| # Places a piece if the Piece Lock timer runs out. | |
| if event.type == PIECE_LOCK and game.game_active: | |
| if debug: | |
| print("PIECE_LOCK pushed") | |
| game.place_piece() | |
| # If left or right movement keys have been held without interruption for long enough, enable repeated left/right movement | |
| if event.type == DAS_LEFT: | |
| game.do_das_left = True | |
| pygame.time.set_timer(DAS_RIGHT, 0) | |
| if event.type == DAS_RIGHT: | |
| game.do_das_right = True | |
| pygame.time.set_timer(DAS_LEFT, 0) | |
| if game.soft_dropping == True: | |
| game.move_piece_down() | |
| if game.do_das_right and current_frame % 2 == 0: | |
| game.move_piece_h(1) | |
| if game.do_das_left and current_frame % 2 == 0: | |
| game.move_piece_h(-1) | |
| # Checks for intersection after everything else. Attempts to rotate as a failsave, then | |
| if game.intersects(): | |
| if debug: | |
| print("game over") | |
| game.game_active = False | |
| screen.fill(BLACK) | |
| if game.game_active: | |
| square_colors = TETROMINO_COLORS | |
| else: | |
| square_colors = [DEAD_SQUARE_GREY for i in range(len(TETROMINO_COLORS))] | |
| # For each square in the matrix, | |
| for i in range(Tetris.MATRIX_HEIGHT): | |
| for j in range(Tetris.MATRIX_WIDTH): | |
| # If square in matrix is empty... | |
| if game.field[i][j] == 0: | |
| # ...Draw the grid for the matrix. | |
| pygame.draw.rect(screen, GRID_GREY, [Tetris.GAME_ZOOM * (Tetris.MATRIX_X_OFFSET + j), Tetris.GAME_ZOOM * (Tetris.MATRIX_Y_OFFSET + i), Tetris.GAME_ZOOM, Tetris.GAME_ZOOM], 1) | |
| # If the game is active, draw all placed Tetrominoes in color. | |
| elif game.game_active: | |
| pygame.draw.rect(screen, square_colors[game.field[i][j] - 1], [Tetris.GAME_ZOOM * (Tetris.MATRIX_X_OFFSET + j), Tetris.GAME_ZOOM * (Tetris.MATRIX_Y_OFFSET + i), Tetris.GAME_ZOOM - 0, Tetris.GAME_ZOOM - 0]) | |
| # Otherwise, grey them out. | |
| else: | |
| pygame.draw.rect(screen, DEAD_SQUARE_GREY, [Tetris.GAME_ZOOM * (Tetris.MATRIX_X_OFFSET + j), Tetris.GAME_ZOOM * (Tetris.MATRIX_Y_OFFSET + i), Tetris.GAME_ZOOM - 0, Tetris.GAME_ZOOM - 0]) | |
| # Find the position of the ghost piece | |
| for i in range(Tetris.MATRIX_HEIGHT): | |
| if game.intersects(0, -i, 0): | |
| ghost_piece_y_difference = i - 1 | |
| break | |
| # Draw the ghost piece | |
| rendering_scan_dimension = game.get_scan_dimension() | |
| for i in range(rendering_scan_dimension): | |
| for j in range(rendering_scan_dimension): | |
| if i * rendering_scan_dimension + j in game.active_piece.image(): | |
| pygame.draw.rect(screen, DEAD_SQUARE_GREY, | |
| [ | |
| Tetris.GAME_ZOOM * (Tetris.MATRIX_X_OFFSET + game.active_piece.x + j), | |
| Tetris.GAME_ZOOM * (Tetris.MATRIX_Y_OFFSET + game.active_piece.y + i + ghost_piece_y_difference), | |
| Tetris.GAME_ZOOM, | |
| Tetris.GAME_ZOOM | |
| ] | |
| ) | |
| # Draw the active piece | |
| rendering_scan_dimension = game.get_scan_dimension() | |
| for i in range(rendering_scan_dimension): | |
| for j in range(rendering_scan_dimension): | |
| if i * rendering_scan_dimension + j in game.active_piece.image(): | |
| pygame.draw.rect(screen, square_colors[game.active_piece.type], | |
| [ | |
| Tetris.GAME_ZOOM * (Tetris.MATRIX_X_OFFSET + game.active_piece.x + j), | |
| Tetris.GAME_ZOOM * (Tetris.MATRIX_Y_OFFSET + game.active_piece.y + i), | |
| Tetris.GAME_ZOOM, | |
| Tetris.GAME_ZOOM | |
| ] | |
| ) | |
| # Draw the earliest 5 items in the queue. | |
| for i in range(5): | |
| try: | |
| queue_rendering_scan_dimension = game.get_scan_dimension("Queue", game.queue[i]) | |
| except IndexError: | |
| break | |
| for i1 in range(queue_rendering_scan_dimension): | |
| for j in range(queue_rendering_scan_dimension): | |
| if i1 * queue_rendering_scan_dimension + j in Tetromino.FIGURES[game.queue[i]][0]: | |
| if game.queue[i] == 0: | |
| figure_render_offset_x = -1 | |
| figure_render_offset_y = -2 | |
| else: | |
| figure_render_offset_x = 0 | |
| figure_render_offset_y = 0 | |
| pygame.draw.rect(screen, TETROMINO_COLORS[game.queue[i]], | |
| [ | |
| Tetris.GAME_ZOOM * (Tetris.MATRIX_X_OFFSET + Tetris.MATRIX_WIDTH + 0.5 + j + figure_render_offset_x), | |
| Tetris.GAME_ZOOM * (Tetris.MATRIX_Y_OFFSET + 1 + i1 + figure_render_offset_y + i * 3), | |
| Tetris.GAME_ZOOM, | |
| Tetris.GAME_ZOOM | |
| ] | |
| ) | |
| # Draw the held piece, if there is one. | |
| if game.piece_held != None: | |
| hold_rendering_scan_dimension = game.get_scan_dimension("Hold", game.piece_held) | |
| for i in range(hold_rendering_scan_dimension): | |
| for j in range(hold_rendering_scan_dimension): | |
| if i * hold_rendering_scan_dimension + j in Tetromino.FIGURES[game.piece_held][0]: | |
| if game.piece_held == 0: | |
| figure_render_offset_x = -1 | |
| figure_render_offset_y = -2 | |
| else: | |
| figure_render_offset_x = 0 | |
| figure_render_offset_y = 0 | |
| pygame.draw.rect(screen, TETROMINO_COLORS[game.piece_held], | |
| [ | |
| Tetris.GAME_ZOOM * (Tetris.MATRIX_X_OFFSET - 4.5 + j + figure_render_offset_x), | |
| Tetris.GAME_ZOOM * (Tetris.MATRIX_Y_OFFSET + 1 + i + figure_render_offset_y), | |
| Tetris.GAME_ZOOM, | |
| Tetris.GAME_ZOOM | |
| ] | |
| ) | |
| # Create game info display text | |
| score_display_text = VARIABLE_DISPLAY_FONT.render(f"Score: {game.score}", True, WHITE) | |
| level_display_text = VARIABLE_DISPLAY_FONT.render(f"Level: {game.level}", True, WHITE) | |
| lines_display_text = VARIABLE_DISPLAY_FONT.render(f"Lines: {game.total_lines_cleared}", True, WHITE) | |
| # Create game over text | |
| game_over_text_1 = GAME_OVER_FONT.render("Game Over!", True, BLACK) | |
| game_over_text_2 = GAME_OVER_FONT.render("Press C.", True, BLACK) | |
| # Blit display text to screen | |
| display_text_x = 15 | |
| display_text_y = 100 | |
| for i in [score_display_text, level_display_text, lines_display_text]: | |
| screen.blit(i, [display_text_x, display_text_y]) | |
| display_text_y += 30 | |
| # Blits game over text to screen if game has been lost. | |
| if game.game_active == False: | |
| pygame.draw.rect(screen, YELLOW, [Tetris.GAME_ZOOM * Tetris.MATRIX_X_OFFSET, Tetris.GAME_ZOOM * 7 + Tetris.GAME_ZOOM * Tetris.MATRIX_Y_OFFSET, Tetris.GAME_ZOOM * Tetris.MATRIX_WIDTH, Tetris.GAME_ZOOM * 3]) | |
| screen.blit(game_over_text_1, (Tetris.GAME_ZOOM * (Tetris.MATRIX_X_OFFSET + Tetris.MATRIX_WIDTH) // 2, Tetris.GAME_ZOOM * 7.5 + Tetris.GAME_ZOOM * Tetris.MATRIX_Y_OFFSET)) | |
| screen.blit(game_over_text_2, (Tetris.GAME_ZOOM * (Tetris.MATRIX_X_OFFSET + Tetris.MATRIX_WIDTH) // 2, Tetris.GAME_ZOOM * 8.5 + Tetris.GAME_ZOOM * Tetris.MATRIX_Y_OFFSET)) | |
| pygame.display.flip() | |
| clock.tick(FPS) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment