import os #os.environ["SDL_VIDEODRIVER"] = "dummy" import pygame import sys import random import numpy as np import math class StateManager: def __init__(self): # height of the gameplay screen self.SCREEN_HEIGHT = 1000 # width of the gameplay screen self.SCREEN_WIDTH = 1000 # fps of the gameplay screen self.FPS = int(30) # the score of the (human) player self.score = int(0) # a boolean variable indicating whether the game has ended or not self.game_over = bool(False) # the x position of the player self.player_position_x = float(100) # the y position of the player self.player_position_y = float(100) # the x and y position of the green circles self.green_circles = list([{"x": 10, "y": 10}, {"x": 10, "y": 10}]) # the x and y position of the red circle self.red_circles = list([{"x": 10, "y": 10}]) # The radius of the player's character (blue circle) self.player_radius = int(20) # The RGB color representation of the player's character (blue circle) self.player_color = tuple(tuple((0, 0, 255))) # The movement speed of the player character along the x-axis self.player_velocity_x = float(55.0) # The movement speed of the player character along the y-axis self.player_velocity_y = float(55.0) # The radius of green circles self.green_circle_radius = int(10) # The radius of red circles self.red_circle_radius = int(10) # The RGB color representation of green circles self.green_circle_color = tuple(tuple((0, 255, 0))) # The RGB color representation of red circles self.red_circle_color = tuple(tuple((255, 0, 0))) # The velocity at which green circles move randomly along the x-axis self.green_circle_velocity_x = float(2.0) # The velocity at which green circles move randomly along the y-axis self.green_circle_velocity_y = float(2.0) # The velocity at which red circles move randomly along the x-axis self.red_circle_velocity_x = float(2.0) # The velocity at which red circles move randomly along the y-axis self.red_circle_velocity_y = float(2.0) # A flag to indicate whether a collision with a green circle has occurred self.green_collision = bool(False) # A flag to indicate whether a collision with a red circle has occurred self.red_collision = bool(False) # Font size for the score display self.score_font_size = int(30) # Font color for the score display self.score_font_color = tuple(tuple((0, 128, 0))) # Initial X position for the score display self.score_pos_x = int(10) # Initial Y position for the score display self.score_pos_y = int(10) # Boolean to keep track if the score text surface needs updating self.score_needs_update = bool(True) # The previous score to compare changes self.previous_score = int(0) # The background color of the score display surface to ensure it's not white self.score_background_color = tuple(tuple((128, 128, 128))) # The text content for the score display self.score_text = str('Score: 0') # A boolean variable indicating whether the restart option has been selected or not self.restart_selected = bool(False) # The color of the game over screen text, ensuring it's not white since the background is white self.game_over_font_color = tuple(tuple((255, 0, 0))) # The size of the game over screen text font self.game_over_font_size = int(50) # The text content for the game over screen self.game_over_text = str('Game Over! Press R to Restart') # X position for the game over screen text self.game_over_pos_x = int(100) # Y position for the game over screen text self.game_over_pos_y = int(150) # A flag to indicate if the game is paused, which happens when a game over condition is met self.game_paused = bool(False) # A boolean flag to indicate whether all green circles have been captured self.all_green_captured = bool(False) # Initial positions of green circles for the restart function self.initial_green_circles_positions = list([{'x': 20, 'y': 30}, {'x': 50, 'y': 70}, {'x': 80, 'y': 90}]) # A boolean flag to indicate if the motion for all objects should be stopped self.stop_all_motion = bool(False) # List containing the initial positions of green circles to reset the green_circles variable on game restart. self.initial_green_circles_positions = list([{'x': 20, 'y': 30}, {'x': 50, 'y': 70}, {'x': 80, 'y': 90}]) # A boolean variable indicating whether the restart option has been selected or not, to initialize game state on restart self.restart_selected = bool(False) # Variable indicating whether game objects should stop moving self.stop_all_motion = bool(False) # Boolean flag to indicate whether the game over screen should be displayed self.display_game_over = bool(False) # Background color of the game over screen to ensure it's not white self.game_over_background_color = tuple(tuple((0, 0, 0))) # Flag to indicate whether to update the game over text surface self.game_over_needs_update = bool(True) # X position for the restart option text. self.restart_option_pos_x = int(100) # Y position for the restart option text. self.restart_option_pos_y = int(200) # RGB color for the restart option text, ensuring visibility over potential background colors. self.restart_option_font_color = tuple(tuple((0, 255, 0))) # Size of the font for the restart option text. self.restart_option_font_size = int(30) # Content for the restart option text. self.restart_option_text = str('Press R to Restart') # The score value to be reset to when the game restarts. self.initial_score = int(0) # The total number of green circles to be captured before the game over condition is met. self.total_green_circles = int(3) # The count of currently captured green circles to track the progress towards the game over condition. self.captured_green_circle_count = int(0) def handle_player_movement(state_manager, event): if event.type == pygame.KEYDOWN: if event.key == pygame.K_LEFT: state_manager.player_position_x = max(0, state_manager.player_position_x - state_manager.player_velocity_x) elif event.key == pygame.K_RIGHT: state_manager.player_position_x = min(state_manager.SCREEN_WIDTH - state_manager.player_radius, state_manager.player_position_x + state_manager.player_velocity_x) elif event.key == pygame.K_UP: state_manager.player_position_y = max(0, state_manager.player_position_y - state_manager.player_velocity_y) elif event.key == pygame.K_DOWN: state_manager.player_position_y = min(state_manager.SCREEN_HEIGHT - state_manager.player_radius, state_manager.player_position_y + state_manager.player_velocity_y) def handle_restart_input(state_manager, event): if state_manager.game_over and event.type == pygame.KEYDOWN and event.key == pygame.K_r: state_manager.score = state_manager.initial_score state_manager.player_position_x = float(100) state_manager.player_position_y = float(100) state_manager.green_circles = state_manager.initial_green_circles_positions.copy() state_manager.red_circles = [{'x': 10, 'y': 10}] # Reset to initial red circle position state_manager.game_over = False state_manager.display_game_over = False state_manager.captured_green_circle_count = 0 state_manager.all_green_captured = False state_manager.restart_selected = False def enforce_screen_bounds(state_manager): if state_manager.player_position_x < 0 + state_manager.player_radius: state_manager.player_position_x = 0 + state_manager.player_radius if state_manager.player_position_x > state_manager.SCREEN_WIDTH - state_manager.player_radius: state_manager.player_position_x = state_manager.SCREEN_WIDTH - state_manager.player_radius if state_manager.player_position_y < 0 + state_manager.player_radius: state_manager.player_position_y = 0 + state_manager.player_radius if state_manager.player_position_y > state_manager.SCREEN_HEIGHT - state_manager.player_radius: state_manager.player_position_y = state_manager.SCREEN_HEIGHT - state_manager.player_radius def update_circle_positions(state_manager): for circle in state_manager.green_circles + state_manager.red_circles: dx = random.choice([-1, 1]) * (state_manager.green_circle_velocity_x if circle in state_manager.green_circles else state_manager.red_circle_velocity_x) dy = random.choice([-1, 1]) * (state_manager.green_circle_velocity_y if circle in state_manager.green_circles else state_manager.red_circle_velocity_y) circle['x'] += dx circle['y'] += dy circle_radius = state_manager.green_circle_radius if circle in state_manager.green_circles else state_manager.red_circle_radius if circle['x'] - circle_radius < 0 or circle['x'] + circle_radius > state_manager.SCREEN_WIDTH: circle['x'] -= dx if circle['y'] - circle_radius < 0 or circle['y'] + circle_radius > state_manager.SCREEN_HEIGHT: circle['y'] -= dy def detect_and_handle_collisions(state_manager): for circle in state_manager.green_circles + state_manager.red_circles: distance = ((circle['x'] - state_manager.player_position_x) ** 2 + (circle['y'] - state_manager.player_position_y) ** 2) ** 0.5 circle_radius = state_manager.green_circle_radius if circle in state_manager.green_circles else state_manager.red_circle_radius if distance < (state_manager.player_radius + circle_radius): if circle in state_manager.green_circles: state_manager.score += 1 state_manager.green_collision = True else: state_manager.score -= 1 state_manager.red_collision = True # Move the circle to a new random position within bounds circle['x'] = random.randint(circle_radius, state_manager.SCREEN_WIDTH - circle_radius) circle['y'] = random.randint(circle_radius, state_manager.SCREEN_HEIGHT - circle_radius) # Randomly decide if it respawns as a green or red circle if random.choice([True, False]): if circle in state_manager.red_circles: state_manager.red_circles.remove(circle) state_manager.green_circles.append(circle) else: if circle in state_manager.green_circles: state_manager.green_circles.remove(circle) state_manager.red_circles.append(circle) def update_score_text(state_manager): if state_manager.previous_score != state_manager.score: state_manager.score_text = 'Score: ' + str(state_manager.score) state_manager.score_needs_update = True state_manager.previous_score = state_manager.score def update_game_over_state(state_manager): if state_manager.captured_green_circle_count >= state_manager.total_green_circles: state_manager.game_over = True state_manager.stop_all_motion = True state_manager.display_game_over = True elif state_manager.restart_selected: state_manager.score = state_manager.initial_score state_manager.player_position_x = 100 state_manager.player_position_y = 100 state_manager.green_circles = state_manager.initial_green_circles_positions.copy() state_manager.captured_green_circle_count = 0 state_manager.game_over = False state_manager.stop_all_motion = False state_manager.display_game_over = False state_manager.restart_selected = False state_manager.game_paused = False def render_player_character(state_manager): pygame.draw.circle(state_manager.screen, state_manager.player_color, (int(state_manager.player_position_x), int(state_manager.player_position_y)), state_manager.player_radius) def render_circles(state_manager): for green_circle in state_manager.green_circles: pygame.draw.circle(state_manager.screen, state_manager.green_circle_color, (int(green_circle['x']), int(green_circle['y'])), state_manager.green_circle_radius) for red_circle in state_manager.red_circles: pygame.draw.circle(state_manager.screen, state_manager.red_circle_color, (int(red_circle['x']), int(red_circle['y'])), state_manager.red_circle_radius) def render_score(state_manager): #if state_manager.score_needs_update or state_manager.previous_score != state_manager.score: state_manager.previous_score = state_manager.score state_manager.score_text = f'Score: {state_manager.score}' #state_manager.screen.fill(state_manager.score_background_color, (state_manager.score_pos_x, state_manager.score_pos_y, 100, state_manager.score_font_size)) font = pygame.font.Font(None, state_manager.score_font_size) text_surface = font.render(state_manager.score_text, True, state_manager.score_font_color, None) state_manager.screen.blit(text_surface, (state_manager.score_pos_x, state_manager.score_pos_y)) state_manager.score_needs_update = False def render_game_over_screen(state_manager): if not state_manager.display_game_over: return if state_manager.game_over_needs_update: state_manager.screen.fill(state_manager.game_over_background_color) game_over_font = pygame.font.Font(None, state_manager.game_over_font_size) game_over_surface = game_over_font.render(state_manager.game_over_text, True, state_manager.game_over_font_color) state_manager.screen.blit(game_over_surface, (state_manager.game_over_pos_x, state_manager.game_over_pos_y)) restart_font = pygame.font.Font(None, state_manager.restart_option_font_size) restart_surface = restart_font.render(state_manager.restart_option_text, True, state_manager.restart_option_font_color) state_manager.screen.blit(restart_surface, (state_manager.restart_option_pos_x, state_manager.restart_option_pos_y)) state_manager.game_over_needs_update = False 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 # This function should detect when the arrow keys are pressed and update the player's x and y position variables accordingly, making sure that the player character does not move off the edges of the screen. The x position should be updated based on left and right arrow key presses, and the y position should be updated based on up and down arrow key presses. handle_player_movement(state_manager, event) # This function should listen for a 'R' key press when the game over condition has occurred (all green circles have been captured) and initiate the game restart process if detected. During the game over state, it should disregard all other input except for the 'R' key press to restart the game. handle_restart_input(state_manager, event) # call all the logics # This function should ensure that any update to the player's position keeps the player within the bounds of the screen. If the state variables indicate the player would move beyond the screen's edge, the state should be adjusted to keep the player's position within the acceptable range. enforce_screen_bounds(state_manager) # This function should go through each green and red circle, then update their x and y positions by a random delta within the velocity limits, while ensuring they do not move outside the game's boundaries. It should randomly choose a direction (positive or negative) for the x-axis and y-axis separately and then apply the circle's respective velocity in that direction, while making sure the circle stays within the edges of the screen by reversing the movement if an edge is reached. update_circle_positions(state_manager) # This function should check for collisions between the player's character (blue circle) and each of the green and red circles. A collision occurs when the distance between the centers of the player's circle and the other circle is less than the sum of their radii. If a collision is detected with a green circle, increase the score by one and signal that a green collision has occurred. If a collision is detected with a red circle, decrease the score by one and signal that a red collision has occurred. After the collision, move the affected green or red circle to a new random position within the bounds of the screen and randomly decide whether it respawns as a green or red circle by updating its corresponding list. detect_and_handle_collisions(state_manager) # This function should check if the current score differs from the previous score. If there is a difference, it updates the score text with the new score, and sets a flag indicating that the score needs to be re-rendered. update_score_text(state_manager) # This function should check for the game over condition by comparing the number of captured green circles with the total number of green circles required for game over. If all are captured, it enables the game over state by setting the 'game_over' flag to True, stops all motion by setting 'stop_all_motion' to True, and displays the game over screen by setting 'display_game_over' to True. If a restart is detected, it should reset all relevant state variables to their initial values, allowing for a new game session to begin. update_game_over_state(state_manager) # Fill the screen with white state_manager.screen.fill((255, 255, 255)) # This function should take the current player position variables and draw the player's character as a blue circle at that position on the screen. render_player_character(state_manager) # This function should iterate over the state variables that store the positions of green and red circles and draw each circle on the screen at their current position. Green circles should be drawn with the defined green circle color and radius, and red circles with their defined color and radius. render_circles(state_manager) # This function should check whether the score needs updating, and if so, renders the current score text in the top-left corner of the screen using the font size, color, and background defined in the state manager. It should clear the previous score text area to ensure the new score is displayed cleanly. render_score(state_manager) # This function renders the game over screen when the game over condition is met. It should display the game over text, and the restart option text while ensuring the game's screen is updated accordingly. If the state indicates that the game over text needs updating (game_over_needs_update), it should render the game over text and the option to restart at the specified positions and with the correct colors and font sizes, ultimately refreshing these parts of the display. render_game_over_screen(state_manager) return not state_manager.game_over if __name__ == "__main__": game = Game() pygame.init() global event running = True while running: event = pygame.event.poll() running = game.run(event) pygame.display.flip() pygame.quit()