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 catcher self.catcher_position_x = int(100) # the y position of the catcher self.catcher_position_y = int(950) # the number of lives the player has self.lives = int(3) # a list of dictionaries representing the x and y positions of the balls self.balls = list([{"x": 500, "y": 100}]) # 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) # Width of the catcher character self.catcher_width = int(70) # Height of the catcher character self.catcher_height = int(20) # Color of the catcher character in RGB self.catcher_color = tuple(tuple((255, 0, 0))) # The speed of the catcher's horizontal movement self.catcher_speed_x = int(5) # Variable to track the current state of the left arrow key (pressed or not). self.left_key_down = bool(False) # Variable to track the current state of the right arrow key (pressed or not). self.right_key_down = bool(False) # Time interval in seconds between ball spawns self.ball_spawn_interval = float(2.0) # Counter to track time elapsed for the ball spawn self.ball_spawn_timer = float(0.0) # The height at which new balls appear (top of the screen) self.ball_spawn_height = int(0) # Default width of the ball character self.ball_width = int(25) # Default height of the ball character self.ball_height = int(25) # Color of the ball character in RGB, not white to be distinct on the background self.ball_color = tuple(tuple((0, 255, 0))) # Minimum x-coordinate for random ball spawn self.ball_spawn_min_x = int(0) # Maximum x-coordinate for random ball spawn, should be width of screen minus width of ball self.ball_spawn_max_x = int(975) # Boolean variable to indicate whether the ball speed increases. self.increase_ball_speed = bool(False) # The increment value by which the ball's downward speed increases over time, if enabled. self.ball_speed_increment = float(0.1) # The interval in seconds to increase the ball's speed, if enabled. self.ball_speed_increase_interval = float(30.0) # Counter to track time elapsed for the next ball speed increment, if enabled. self.ball_speed_increase_timer = float(0.0) # The maximum downward speed the balls can have, to ensure the game remains playable. self.ball_speed_y_max = int(10) # Current downward speed of the balls, which may increase during the game. self.ball_speed_y_current = int(3) # Font type for score display. self.score_font_type = str('arial') # Size of the font for the score. self.score_font_size = int(30) # Color of the score text in RGB, distinct from background and catcher colors. self.score_color = tuple(tuple((0, 0, 0))) # Position on screen to display score (x, y). self.score_position = tuple(tuple((10, 10))) # A variable to keep track of the bottom limit for balls before losing a life self.ball_loss_y_threshold = int(975) # A string representing the 'Game Over!' message to display when lives reach zero. self.game_over_message = str("""Game Over!""") # Position on screen to display 'Game Over!' message (x, y). self.game_over_position = tuple(tuple((400, 500))) # Size of the font for the 'Game Over!' message. self.game_over_font_size = int(74) # Color of the 'Game Over!' text in RGB, distinct from background and other elements. self.game_over_color = tuple(tuple((255, 0, 0))) # A boolean indicator for temporarily halting gameplay without terminating the game. self.halt_gameplay = bool(False) # Boolean to track if a mouse click has occurred to restart the game self.restart_game_click = bool(False) # Interval in seconds to trigger the catcher size reduction, if this feature is enabled. self.catcher_shrink_interval = float(30.0) # The decrement value by which the catcher's width decreases over time, if enabled. self.catcher_width_decrement = int(1) # Minimum size of catcher width to maintain gameplay feasibility, ensures the catcher does not shrink to zero. self.catcher_width_min = int(20) def handle_catcher_movement(state_manager, event): if event.type == pygame.KEYDOWN: if event.key == pygame.K_LEFT: state_manager.catcher_position_x = max(0, state_manager.catcher_position_x - state_manager.catcher_speed_x) elif event.key == pygame.K_RIGHT: state_manager.catcher_position_x = min(state_manager.SCREEN_WIDTH - state_manager.catcher_width, state_manager.catcher_position_x + state_manager.catcher_speed_x) def handle_arrow_keys(state_manager, event): if event.type == pygame.KEYDOWN: if event.key == pygame.K_LEFT: state_manager.left_key_down = True elif event.key == pygame.K_RIGHT: state_manager.right_key_down = True elif event.type == pygame.KEYUP: if event.key == pygame.K_LEFT: state_manager.left_key_down = False elif event.key == pygame.K_RIGHT: state_manager.right_key_down = False def handle_game_restart(state_manager, event): if state_manager.game_over and event.type == pygame.KEYDOWN and event.key == pygame.K_r: state_manager.catcher_position_x = 100 state_manager.catcher_position_y = 950 state_manager.lives = 3 state_manager.balls = [{'x': 500, 'y': 100}] state_manager.score = 0 state_manager.game_over = False state_manager.halt_gameplay = False def handle_mouse_click_for_restart(state_manager, event): if state_manager.game_over and event.type == pygame.MOUSEBUTTONDOWN: state_manager.restart_game_click = True def update_catcher_position(state_manager): keys = pygame.key.get_pressed() if keys[pygame.K_LEFT]: state_manager.catcher_position_x -= state_manager.catcher_speed_x if keys[pygame.K_RIGHT]: state_manager.catcher_position_x += state_manager.catcher_speed_x # Ensure catcher stays within the screen boundaries. state_manager.catcher_position_x = max(0, state_manager.catcher_position_x) state_manager.catcher_position_x = min(state_manager.SCREEN_WIDTH - state_manager.catcher_width, state_manager.catcher_position_x) def update_catcher_position_with_key_state(state_manager): if state_manager.left_key_down: state_manager.catcher_position_x = max(0, state_manager.catcher_position_x - state_manager.catcher_speed_x) if state_manager.right_key_down: state_manager.catcher_position_x = min(state_manager.SCREEN_WIDTH - state_manager.catcher_width, state_manager.catcher_position_x + state_manager.catcher_speed_x) def spawn_new_ball_if_needed(state_manager): state_manager.ball_spawn_timer += 1 / state_manager.FPS if state_manager.ball_spawn_timer >= state_manager.ball_spawn_interval: new_ball_x = random.randint(state_manager.ball_spawn_min_x, state_manager.ball_spawn_max_x) new_ball = {'x': new_ball_x, 'y': state_manager.ball_spawn_height} state_manager.balls.append(new_ball) state_manager.ball_spawn_timer = 0.0 def update_ball_positions_and_speed(state_manager): for ball in state_manager.balls: ball['y'] += state_manager.ball_speed_y_current if state_manager.increase_ball_speed: state_manager.ball_speed_increase_timer += 1.0 / state_manager.FPS if state_manager.ball_speed_increase_timer >= state_manager.ball_speed_increase_interval: state_manager.ball_speed_increase_timer = 0.0 state_manager.ball_speed_y_current = min(state_manager.ball_speed_y_current + state_manager.ball_speed_increment, state_manager.ball_speed_y_max) def detect_collision_and_update_score(state_manager): score_increment = 10 catcher_rect = pygame.Rect(state_manager.catcher_position_x, state_manager.catcher_position_y, state_manager.catcher_width, state_manager.catcher_height) for ball in state_manager.balls[:]: ball_rect = pygame.Rect(ball['x'], ball['y'], 10, 10) # Assumes a fixed size for balls if catcher_rect.colliderect(ball_rect): state_manager.balls.remove(ball) state_manager.score += score_increment def update_lives_and_game_over_status(state_manager): for ball in state_manager.balls[:]: if ball['y'] >= state_manager.ball_loss_y_threshold: state_manager.balls.remove(ball) state_manager.lives -= 1 if state_manager.lives <= 0: state_manager.game_over = True break def halt_gameplay_if_over(state_manager): if state_manager.game_over: state_manager.halt_gameplay = True # You can include any additional halting logic here if needed def reset_game_state_on_click(state_manager): if state_manager.restart_game_click and state_manager.game_over: state_manager.catcher_position_x = 100 state_manager.catcher_position_y = 950 state_manager.lives = 3 state_manager.balls = [{'x': 500, 'y': 100}] state_manager.score = 0 state_manager.game_over = False state_manager.restart_game_click = False # The following variables are reset to ensure the new game starts normally state_manager.halt_gameplay = False state_manager.ball_spawn_timer = 0.0 state_manager.ball_speed_y_current = 3 def maintain_ball_availability_and_adjust_difficulty(state_manager): # Ensure there is always at least one ball in play if len(state_manager.balls) == 0: random_x = random.randint(state_manager.ball_spawn_min_x, state_manager.ball_spawn_max_x) state_manager.balls.append({'x': random_x, 'y': state_manager.ball_spawn_height}) # Adjust difficulty based on score if state_manager.score > 0 and state_manager.increase_ball_speed: if (state_manager.score % state_manager.ball_speed_increase_interval) == 0: state_manager.ball_speed_y_current = min(state_manager.ball_speed_y_current + state_manager.ball_speed_increment, state_manager.ball_speed_y_max) if state_manager.score > 0 and (state_manager.score % state_manager.catcher_shrink_interval) == 0: if state_manager.catcher_width > state_manager.catcher_width_min: state_manager.catcher_width = max(state_manager.catcher_width - state_manager.catcher_width_decrement, state_manager.catcher_width_min) def render_catcher(state_manager): pygame.draw.rect(state_manager.screen, state_manager.catcher_color, pygame.Rect(state_manager.catcher_position_x, state_manager.catcher_position_y, state_manager.catcher_width, state_manager.catcher_height)) def render_balls(state_manager): for ball in state_manager.balls: pygame.draw.ellipse(state_manager.screen, state_manager.ball_color, pygame.Rect(ball['x'], ball['y'], state_manager.ball_width, state_manager.ball_height)) def render_score(state_manager): font = pygame.font.SysFont(state_manager.score_font_type, state_manager.score_font_size) score_text = font.render('Score: ' + str(state_manager.score), True, state_manager.score_color) state_manager.screen.blit(score_text, state_manager.score_position) def render_lives(state_manager): lives_font = pygame.font.SysFont('Arial', 30) lives_text = lives_font.render('Lives: {}'.format(state_manager.lives), True, (0, 0, 0)) state_manager.screen.blit(lives_text, (state_manager.SCREEN_WIDTH - 200, 10)) def render_game_over_message(state_manager): if state_manager.game_over: game_over_font = pygame.font.SysFont(None, state_manager.game_over_font_size) game_over_text = game_over_font.render(state_manager.game_over_message, True, state_manager.game_over_color) game_over_rect = game_over_text.get_rect(center=state_manager.game_over_position) state_manager.screen.blit(game_over_text, game_over_rect) 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 # Handle keyboard events to move the catcher character horizontally based on user input. The catcher should move left when the left arrow key is pressed and move right when the right arrow key is pressed. handle_catcher_movement(state_manager, event) # The function should listen to KEYDOWN events for left and right arrow keys. When the left arrow key is detected as pressed, it updates the 'left_key_down' state variable to True, and to False when it is released. Similarly, it should set the 'right_key_down' variable to True when the right arrow key is pressed and to False when released. handle_arrow_keys(state_manager, event) # This function should check for a keyboard event that allows the user to restart the game once the 'Game Over!' message is displayed. It should listen for a specific key press (e.g., the 'R' key) and when detected, it should reset the relevant game state variables to restart the game. handle_game_restart(state_manager, event) # This function should check for a mouse click event and determine if it has occurred anywhere on the screen after the game is over. If a click is detected, it should update the 'restart_game_click' state variable to True. handle_mouse_click_for_restart(state_manager, event) # call all the logics # Update the catcher's position based on the current velocity and handle the boundaries of the gameplay area to ensure the catcher doesn't move off-screen. update_catcher_position(state_manager) # Based on the 'left_key_down' and 'right_key_down' state variables, this function should determine the direction of the catcher's movement and update the 'catcher_position_x' accordingly. If 'left_key_down' is True, decrease 'catcher_position_x' by 'catcher_speed_x', and if 'right_key_down' is True increase 'catcher_position_x' by 'catcher_speed_x'. Ensure the catcher does not move off-screen by keeping 'catcher_position_x' within the boundaries of the gameplay area. update_catcher_position_with_key_state(state_manager) # Periodically check if the timer has exceeded the ball spawn interval. If so, reset the timer and add a new ball with a random x-coordinate (between a predefined min and max range) and fixed y-coordinate (spawn height) to the balls list. Ensure the balls list always has at least one ball, which will be addressed by controlling the ball removal logic elsewhere. spawn_new_ball_if_needed(state_manager) # This function should loop through each ball in the 'balls' list, and increment its 'y' position by 'ball_speed_y_current'. The function should also handle the logic to gradually increase the ball's downward speed if 'increase_ball_speed' is True, by using the 'ball_speed_increase_timer' and 'ball_speed_increment', without exceeding 'ball_speed_y_max'. update_ball_positions_and_speed(state_manager) # Detect when a ball's position intersects with the catcher's position. If a collision is detected, remove the ball from the balls list and increment the player's score by a predefined value. detect_collision_and_update_score(state_manager) # Check each ball to see if it has reached the 'ball_loss_y_threshold'. If it has, remove the ball from the 'balls' list and reduce the player's 'lives' by one. If the 'lives' reach zero, set 'game_over' to True to indicate the end of the game. update_lives_and_game_over_status(state_manager) # After the game is over, this function should be responsible for halting any state updates that would normally occur during active gameplay, such as moving balls, catching actions, or score updates. It should check the 'game_over' state variable, and if true, it should halt any game state updates that do not pertain to the 'game_over' display or restarting the game. halt_gameplay_if_over(state_manager) # This function should check if the 'restart_game_click' state variable is True and if the 'game_over' state is also True, then reset the game state variables including 'catcher_position_x', 'catcher_position_y', 'lives', 'balls', 'score', and 'game_over' to their initial values and set 'restart_game_click' to False to prepare for a new game session. reset_game_state_on_click(state_manager) # Check if there are no balls in the 'balls' list and spawn a new ball at a random x-coordinate with the initial y-coordinate. This function maintains continuous gameplay by ensuring there is always at least one ball in play. Additionally, increase the difficulty by gradually speeding up the ball's downward motion or shrinking the catcher's size as the player's score increases. This could be based on the player's current score reaching certain thresholds. maintain_ball_availability_and_adjust_difficulty(state_manager) # Fill the screen with white state_manager.screen.fill((255, 255, 255)) # Draw the catcher rectangle at its current position at the bottom of the screen with the specified width, height, and color. render_catcher(state_manager) # Iterate over the balls list and for each ball draw a circle or other suitable shape on the screen at the ball's x and y position with the designated ball color to make it visually distinct. render_balls(state_manager) # Draw the updated score value on the screen at the predetermined location (top-left corner). The score should be rendered with the specified font type, size, and color. render_score(state_manager) # Draw the remaining number of lives on the screen, showing it in a designated area, typically at the top-right corner. The lives should be represented with a predefined symbol or text, using the specified font type, size, and color. render_lives(state_manager) # This function should display the 'Game Over!' message in the center of the screen or at the predefined 'game_over_position' when the 'game_over' state variable is set to true. It should render the message using the 'game_over_font_size', 'game_over_color', and ensure it remains on the screen until gameplay is restarted. render_game_over_message(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.00) pygame.quit() asyncio.run(main())