import asyncio import pygame import sys import random import numpy as np import math class State: def __init__(self, name, value, variable_type, description): self.name = name self.value = value self.variable_type = variable_type self.description = description class StateManager: def __init__(self): # height of the gameplay screen self.SCREEN_HEIGHT = int(1000) # width of the gameplay screen self.SCREEN_WIDTH = int(1000) # fps of the gameplay screen self.FPS = int(60) # the x position of the bird self.bird_position_x = int(100) # the y position of the bird self.bird_position_y = int(100) # a list of dictionaries representing the x positions and the height of the bottom pipes self.pipe_positions = list([{"x": 500, "height": 100}]) # the gap of the pipes self.PIPE_GAP = int(150) # the width of the pipes self.PIPE_WIDTH = int(50) # the score of the player self.score = int(0) # a boolean variable indicating whether the game has ended or not self.game_over = bool(False) # The vertical velocity of the bird self.bird_velocity_y = int(0) # The color of the bird character, but not white self.bird_color = tuple(tuple((255, 200, 0))) # The dimensions of the bird character as a rectangle self.bird_size = tuple(tuple((30, 30))) # Boolean variable to keep track whether the up or down arrow key is pressed self.is_arrow_key_pressed = bool(False) # The acceleration due to gravity affecting the bird's vertical movement self.gravity = float(0.5) # The impulse given to the bird when the player inputs a flap (upward force) self.flap_impulse = float(-10.5) # The maximum downward speed the bird can reach due to gravity self.terminal_velocity = float(10) # Tracks if the mouse button is currently pressed or a jump key is pressed (can be mapped to jump action) self.is_jump_pressed = bool(False) # The horizontal distance between sequential pairs of pipes self.pipe_spacing = int(300) # The minimum height of a pipe so it is still visible on the screen self.min_pipe_height = int(50) # The maximum height of a pipe so it does not overlap with the upper part of the screen self.max_pipe_height = int(400) # The speed at which the pipes move to the left self.pipe_speed = int(5) # The color of the pipe, specified as an RGB tuple self.pipe_color = tuple(tuple((0, 255, 0))) # The height of the screen used to define the Y position of the bottom pipe self.SCREEN_HEIGHT = int(1000) # Text to be displayed when the game ends self.game_over_text = str("""Game Over!""") # Color of the game over text self.game_over_text_color = tuple(tuple((255, 0, 0))) # Font size of the game over text self.game_over_text_size = int(50) # Position of the game over text on the x-axis self.game_over_text_x = int(400) # Position of the game over text on the y-axis self.game_over_text_y = int(500) # Boolean to check if we should show the game over text self.display_game_over = bool(False) # This boolean variable shows whether a pipe has been already passed successfully by the bird, so it's not counted again for points. self.pipe_passed = bool(False) # The number of points to award player for successful pass. Not visually rendered but is necessary for scoring logic. self.points_per_pass = int(1) # Font size for score display self.score_font_size = int(30) # Score display position on the x-axis self.score_pos_x = int(10) # Score display position on the y-axis self.score_pos_y = int(10) # Color of the score text, not white for visibility self.score_color = tuple(tuple((255, 165, 0))) # Text to be displayed before the score count self.score_text_prefix = str("Score: ") # Tracks the moment when a new pipe needs to be generated self.next_pipe_generation_x = int(800) # The score amount where the difficulty of the game might increase (can be number of pipes passed) self.next_difficulty_threshold = int(10) def handle_bird_movement_input(state_manager, event): if event.type == pygame.KEYDOWN: if event.key == pygame.K_UP: state_manager.bird_velocity_y = -10 # negative value for upward state_manager.is_arrow_key_pressed = True elif event.key == pygame.K_DOWN: state_manager.bird_velocity_y = 10 # positive value for downward state_manager.is_arrow_key_pressed = True elif event.type == pygame.KEYUP: if event.key in (pygame.K_UP, pygame.K_DOWN): state_manager.bird_velocity_y = 0 state_manager.is_arrow_key_pressed = False def handle_jump_input(state_manager, event): if event.type == pygame.MOUSEBUTTONDOWN or (event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE): state_manager.is_jump_pressed = True elif event.type == pygame.MOUSEBUTTONUP or (event.type == pygame.KEYUP and event.key == pygame.K_SPACE): state_manager.is_jump_pressed = False def update_bird_position(state_manager): # Update the bird's position state_manager.bird_position_y += state_manager.bird_velocity_y # Clamp the bird's position within the screen's bounds if state_manager.bird_position_y < 0: state_manager.bird_position_y = 0 state_manager.bird_velocity_y = 0 elif state_manager.bird_position_y > state_manager.SCREEN_HEIGHT - state_manager.bird_size[1]: state_manager.bird_position_y = state_manager.SCREEN_HEIGHT - state_manager.bird_size[1] state_manager.bird_velocity_y = 0 def apply_gravity_and_update_position(state_manager): # Check for the flap impulse if state_manager.is_arrow_key_pressed: state_manager.bird_velocity_y += state_manager.flap_impulse state_manager.is_arrow_key_pressed = False # Update velocity with gravity state_manager.bird_velocity_y += state_manager.gravity # Cap the velocity to the terminal velocity if state_manager.bird_velocity_y > state_manager.terminal_velocity: state_manager.bird_velocity_y = state_manager.terminal_velocity # Update bird position state_manager.bird_position_y += state_manager.bird_velocity_y # Keep bird within the screen bounds if state_manager.bird_position_y > state_manager.SCREEN_HEIGHT: state_manager.bird_position_y = state_manager.SCREEN_HEIGHT elif state_manager.bird_position_y < 0: state_manager.bird_position_y = 0 def apply_jump(state_manager): if state_manager.is_jump_pressed: state_manager.bird_velocity_y = state_manager.flap_impulse state_manager.is_jump_pressed = False def update_pipe_positions_and_generate_new_pipes(state_manager): for pipe in state_manager.pipe_positions: pipe['x'] -= state_manager.pipe_speed if state_manager.pipe_positions: last_pipe_x = state_manager.pipe_positions[-1]['x'] if last_pipe_x < state_manager.SCREEN_WIDTH - state_manager.pipe_spacing: new_pipe_height = random.randint(state_manager.min_pipe_height, state_manager.max_pipe_height) state_manager.pipe_positions.append({'x': state_manager.SCREEN_WIDTH, 'height': new_pipe_height}) state_manager.pipe_positions = [pipe for pipe in state_manager.pipe_positions if pipe['x'] + state_manager.PIPE_WIDTH > 0] def detect_collision_and_update_game_over(state_manager): bird_rect = pygame.Rect(state_manager.bird_position_x, state_manager.bird_position_y, state_manager.bird_size[0], state_manager.bird_size[1]) for pipe in state_manager.pipe_positions: bottom_pipe_rect = pygame.Rect(pipe['x'], state_manager.SCREEN_HEIGHT - pipe['height'], state_manager.PIPE_WIDTH, pipe['height']) top_pipe_rect = pygame.Rect(pipe['x'], 0, state_manager.PIPE_WIDTH, state_manager.SCREEN_HEIGHT - pipe['height'] - state_manager.PIPE_GAP) if bird_rect.colliderect(bottom_pipe_rect) or bird_rect.colliderect(top_pipe_rect): state_manager.game_over = True return if state_manager.bird_position_y + state_manager.bird_size[1] >= state_manager.SCREEN_HEIGHT: state_manager.game_over = True def stop_game_on_game_over(state_manager): if state_manager.game_over: state_manager.bird_velocity_y = 0 state_manager.pipe_speed = 0 state_manager.display_game_over = True def update_score_for_passing_pipes(state_manager): for pipe in state_manager.pipe_positions: # Check if the bird has passed the pipe and the pass is not already counted if not state_manager.pipe_passed and state_manager.bird_position_x > (pipe['x'] + state_manager.PIPE_WIDTH): state_manager.score += state_manager.points_per_pass state_manager.pipe_passed = True # Reset the pipe_passed flag if the bird has not passed the current pipe elif state_manager.bird_position_x <= (pipe['x'] + state_manager.PIPE_WIDTH): state_manager.pipe_passed = False def adjust_difficulty_based_on_score(state_manager): score = state_manager.score if score >= state_manager.next_difficulty_threshold: # Increment difficulty settings slightly state_manager.pipe_speed += 1 state_manager.PIPE_GAP -= 2 state_manager.next_pipe_generation_x += 50 # Ensure values remain within reasonable limits state_manager.PIPE_GAP = max(10, state_manager.PIPE_GAP) # Update the threshold for the next difficulty increase state_manager.next_difficulty_threshold += 10 # Ensure continuous pipe generation if state_manager.pipe_positions[-1]['x'] < state_manager.next_pipe_generation_x: new_pipe_height = random.randint(state_manager.min_pipe_height, state_manager.max_pipe_height) state_manager.pipe_positions.append({'x': state_manager.SCREEN_WIDTH, 'height': new_pipe_height}) state_manager.next_pipe_generation_x = state_manager.pipe_positions[-1]['x'] - state_manager.pipe_spacing def render_sprite(state_manager, sprite_image_path, x, y, width, height): image = pygame.image.load(sprite_image_path) image = pygame.transform.scale(image, (width, height)) # Get the rectangle object from the image for positioning rect = image.get_rect(topleft=(x, y)) # Blit the image onto the screen state_manager.screen.blit(image, rect) def render_bird(state_manager): state_manager.bird_image_path = "bird.png" render_sprite(state_manager, state_manager.bird_image_path, state_manager.bird_position_x, state_manager.bird_position_y, state_manager.bird_size[0], state_manager.bird_size[1]) def render_pipes(state_manager): for pipe in state_manager.pipe_positions: # Render the bottom pipe bottom_pipe_height = pipe['height'] #bottom_pipe_rect = pygame.Rect(pipe['x'], state_manager.SCREEN_HEIGHT - bottom_pipe_height, # state_manager.PIPE_WIDTH, bottom_pipe_height) #pygame.draw.rect(state_manager.screen, state_manager.pipe_color, bottom_pipe_rect) render_sprite(state_manager, "pipe_top.png", pipe['x'], state_manager.SCREEN_HEIGHT - bottom_pipe_height, state_manager.PIPE_WIDTH, 20) render_sprite(state_manager, "pipe_body.png", pipe['x'], state_manager.SCREEN_HEIGHT - bottom_pipe_height+20, state_manager.PIPE_WIDTH, bottom_pipe_height-40) render_sprite(state_manager, "pipe_bottom.png", pipe['x'], state_manager.SCREEN_HEIGHT - bottom_pipe_height+bottom_pipe_height-20, state_manager.PIPE_WIDTH, 20) # Render the top pipe top_pipe_height = state_manager.SCREEN_HEIGHT - (state_manager.PIPE_GAP + bottom_pipe_height) #top_pipe_rect = pygame.Rect(pipe['x'], 0, state_manager.PIPE_WIDTH, top_pipe_height) #pygame.draw.rect(state_manager.screen, state_manager.pipe_color, top_pipe_rect) image_path = "pipe.png" render_sprite(state_manager, "pipe_top.png", pipe['x'], 0, state_manager.PIPE_WIDTH, 20) render_sprite(state_manager, "pipe_body.png", pipe['x'], 20, state_manager.PIPE_WIDTH, top_pipe_height-40) render_sprite(state_manager, "pipe_bottom.png", pipe['x'], top_pipe_height-20, state_manager.PIPE_WIDTH, 20) def render_game_over_message(state_manager): if state_manager.display_game_over: font = pygame.font.Font(None, state_manager.game_over_text_size) text_surface = font.render(state_manager.game_over_text, True, state_manager.game_over_text_color) state_manager.screen.blit(text_surface, (state_manager.game_over_text_x, state_manager.game_over_text_y)) def render_score(state_manager): score_font = pygame.font.Font(None, 74) score_surface = score_font.render(str(state_manager.score), True, (0, 0, 0)) score_x = state_manager.SCREEN_WIDTH // 2 - score_surface.get_width() // 2 score_y = 50 state_manager.screen.blit(score_surface, (score_x, score_y)) def render_score_top_left(state_manager): score_font = pygame.font.Font(None, state_manager.score_font_size) score_text = state_manager.score_text_prefix + str(state_manager.score) score_surface = score_font.render(score_text, True, state_manager.score_color) state_manager.screen.blit(score_surface, (state_manager.score_pos_x, state_manager.score_pos_y)) class Game(): def __init__(self): self.state_manager = StateManager() self.state_manager.screen = pygame.display.set_mode((self.state_manager.SCREEN_WIDTH, self.state_manager.SCREEN_HEIGHT)) def run(self, event): state_manager = self.state_manager if event.type == pygame.QUIT: return False # Detect when the up or down arrow key is pressed and update the 'is_arrow_key_pressed' state variable to True. If the up arrow key is pressed, set 'bird_velocity_y' to a negative value to indicate upward movement, and if the down arrow key is pressed, set 'bird_velocity_y' to a positive value to indicate downward movement. Upon key release, set the 'is_arrow_key_pressed' state variable back to False and 'bird_velocity_y' to zero to stop the bird's vertical motion. handle_bird_movement_input(state_manager, event) # Detect mouse button click or space bar key press and update the 'is_jump_pressed' state variable to True. Upon the release of the mouse button or space bar key, set the 'is_jump_pressed' variable back to False. handle_jump_input(state_manager, event) # call all the logics # Update the 'bird_position_y' state variable by adding the current 'bird_velocity_y' to it. Ensure the bird stays within the bounds of the game window by clamping its 'bird_position_y' between 0 and 'SCREEN_HEIGHT' minus the bird's height. If the bird's position tries to exceed these bounds, set 'bird_position_y' to the boundary value and 'bird_velocity_y' to zero to prevent further movement in that direction. update_bird_position(state_manager) # Modify the 'update_bird_position' function or create a new function that updates the 'bird_velocity_y' by adding the 'gravity' value each frame, simulating the effect of gravity. Then, update the bird's 'bird_position_y' by adding this velocity to it, ensuring that the velocity does not exceed the 'terminal_velocity'. If the 'is_arrow_key_pressed' is true and the up arrow key was the last one pressed, add the 'flap_impulse' to the velocity, simulating a flap to counteract gravity. apply_gravity_and_update_position(state_manager) # Check the 'is_jump_pressed' state variable, and if it is True, apply a jump by updating the 'bird_velocity_y' with the negative value of 'flap_impulse'. After applying the impulse, set 'is_jump_pressed' back to False to ensure the impulse is only applied once per button press. apply_jump(state_manager) # Create a function that checks if a new pair of pipes should be generated based on the horizontal distance from the last pair and increments the x positions of existing pipes to the left based on the pipe_speed. When generating a new pair, it should add a dictionary with the 'x' coordinate set to the right edge of the screen and 'height' set to a random value within the given range for bottom pipe's height. Lastly, it should handle the removal of pipes that have moved off the left side of the screen to ensure the pipe list does not endlessly grow. update_pipe_positions_and_generate_new_pipes(state_manager) # Check if the bird's rectangular area intersects with any of the pipe's rectangular areas or if the bird's y position is at the bottom of the game window. If a collision is detected, set the game_over state variable to True. detect_collision_and_update_game_over(state_manager) # This function should halt the game's actions by stopping bird movement, pipe movement, and any other animations or updates to the game world when the 'game_over' flag is set to True. It should check the state of 'game_over', and if it is True, ensure the velocity of the bird and the movement speed of the pipes are set to zero. The function name 'stop_game_on_game_over' indicates the purpose of the function which is to stop the game when the game is over. stop_game_on_game_over(state_manager) # This function should identify when the bird has successfully passed a pair of pipes and increment the 'score' state variable by the 'points_per_pass' value. It must check the bird's position relative to the pipe pairs and increase the score only when the bird fully clears the pipes, ensuring each pair can only contribute once to the score (wherein the 'pipe_passed' flag can be utilized). update_score_for_passing_pipes(state_manager) # Adjust difficulty settings such as 'pipe_speed', 'PIPE_GAP', and 'next_pipe_generation_x' incrementally based on the player's current score, maintaining the same difficulty level and ensuring new pipes are generated continuously without introducing sudden jumps in difficulty. This may involve creating a function that periodically checks the 'score' and, once it reaches certain predefined thresholds, slightly modifies these state variables within constraints to keep the challenge consistent and achievable. adjust_difficulty_based_on_score(state_manager) # Fill the screen with white state_manager.screen.fill((255, 255, 255)) # Draw a rectangle at the bird's current position ('bird_position_x', 'bird_position_y') using the bird's dimensions ('bird_size') and color ('bird_color'). render_bird(state_manager) # Create a function that iterates through the list of pipe positions and renders two pipes for each entry: one at the 'x' position with the 'height' specified, and another above it, with the gap between them defined by 'PIPE_GAP'. Both pipes are inverted in relation to each other to create a path for the bird. render_pipes(state_manager) # This function should render the 'Game Over!' text message on the screen when 'display_game_over' is True. The message should be displayed using the 'game_over_text_color', 'game_over_text_size', and positioned at 'game_over_text_x' and 'game_over_text_y'. The function name 'render_game_over_message' clearly describes the action of rendering the message on the game screen. render_game_over_message(state_manager) # This function should render the current score on the game screen, such as by drawing a text representation of the 'score' state variable in a designated area on the screen. It should ensure that the score is easily readable and appears in a consistent position on the screen, perhaps at the top center. render_score(state_manager) # This function should render the player's current score at the top-left corner of the screen using the 'score_font_size', 'score_pos_x', 'score_pos_y', and 'score_color'. The score should be represented as text prefixed by 'score_text_prefix' followed by the current 'score' value. This display should update in real-time as the player's score changes. #render_score_top_left(state_manager) return True async def tmp(): game = Game() pygame.init() global event running = True while running: event = pygame.event.poll() running = game.run(event) pygame.display.flip() pygame.quit() async def main(): game = Game() pygame.init() global event running = True while running: event = pygame.event.poll() running = game.run(event) pygame.display.flip() await asyncio.sleep(0.05) pygame.quit() asyncio.run(main())