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 #int(np.random.randint(300, 1000)) # width of the gameplay screen self.SCREEN_WIDTH = 1000 #int(np.random.randint(300, 1000)) # fps of the gameplay screen self.FPS = int(30) # the score of the (human) player self.score = int(0) self.cpu_score = int(0) # a boolean variable indicating whether the game has ended or not self.game_over = bool(False) # x and y position of the ball, and the speed in the x and y directions self.ball = {"x": self.SCREEN_WIDTH/2, "y": self.SCREEN_HEIGHT/2} # how much the ball moves along the x-axis per frame self.ball_speed_x = 10*float(np.random.random()) # how much the ball moves along the y-axis per frame self.ball_speed_y = float(np.random.random()) # the x and y position of the player's paddle self.player_paddle = {"x": 50, "y": float(np.random.randint(0, self.SCREEN_HEIGHT))} # how much the player's paddle moves per key press self.player_paddle_speed = 5 # the x and y position of the cpu player's paddle self.cpu_paddle = {"x": self.SCREEN_WIDTH-50, "y": float(np.random.randint(0, self.SCREEN_HEIGHT))} # how much the cpu player's paddle moves per frame self.cpu_paddle_speed = 5 # The RGB color value of the player's paddle self.player_paddle_color = tuple(tuple((0, 128, 255))) # The rectangle's width self.paddle_width = int(10) # The rectangle's height self.paddle_height = int(100) # The RGB color value of the CPU's paddle to ensure it is not white self.cpu_paddle_color = tuple(tuple((255, 128, 0))) # The decision threshold for the CPU paddle to start moving, prevents jitter self.cpu_decision_threshold = float(0.5) # Determines the diameter of the ball. self.ball_diameter = int(20) # The RGB color value of the ball to ensure it is not white. self.ball_color = tuple(tuple((0, 255, 128))) # A variable that tracks if the ball has hit the left or right wall. self.ball_out_of_bounds = bool(False) # Tracks the current y-direction 'upward' or 'downward' to determine if the ball should bounce in the reverse y-direction when colliding with the top or bottom wall. self.ball_y_direction = str("""upward""") # Counts the number of collisions between ball and paddles self.collision_count = int(0) # Indicates the last paddle that the ball collided with; it helps update the score correctly self.last_paddle_hit = str("""none""") # Font color for displaying the score self.score_font_color = tuple(tuple((0, 0, 0))) # FontSize to display the score self.score_font_size = int(30) # Position to render the score on the screen self.score_position = tuple(tuple((10, 10))) # Points to add to the score when the player's paddle hits the ball self.score_points_hit = int(10) # Points to deduct from the score when the ball passes the player's paddle self.score_points_miss = int(-1) # X-position at which to place the 'Game Over!' message self.game_over_message_x = int(150) # Y-position at which to place the 'Game Over!' message self.game_over_message_y = int(150) # Font size for the 'Game Over!' message self.game_over_font_size = int(50) # Color for the 'Game Over!' message self.game_over_font_color = tuple(tuple((255, 0, 0))) # Message to be displayed when the game is over self.game_over_message = str('Game Over!') # X-position at which to place the restart button or instruction self.restart_button_x = int(150) # Y-position at which to place the restart button or instruction self.restart_button_y = int(250) # Font size for the restart game instruction self.restart_instruction_font_size = int(20) # Color for the restart game instruction self.restart_instruction_font_color = tuple(tuple((50, 205, 50))) # Instruction message to restart the game self.restart_instruction_message = str('Press R to restart') # A boolean variable indicating whether the restart game input has been received self.restart_game_input_received = bool(False) def handle_player_input(state_manager, event): if event.type == pygame.KEYDOWN: if event.key == pygame.K_UP: state_manager.player_paddle['y'] = max(0, state_manager.player_paddle['y'] - state_manager.player_paddle_speed) elif event.key == pygame.K_DOWN: state_manager.player_paddle['y'] = min(state_manager.SCREEN_HEIGHT - state_manager.paddle_height, state_manager.player_paddle['y'] + state_manager.player_paddle_speed) 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.restart_game_input_received = True def update_player_paddle_position(state_manager): keys = pygame.key.get_pressed() if keys[pygame.K_DOWN]: state_manager.player_paddle['y'] += state_manager.player_paddle_speed if state_manager.player_paddle['y'] > state_manager.SCREEN_HEIGHT - state_manager.paddle_height: state_manager.player_paddle['y'] = state_manager.SCREEN_HEIGHT - state_manager.paddle_height if keys[pygame.K_UP]: state_manager.player_paddle['y'] -= state_manager.player_paddle_speed if state_manager.player_paddle['y'] < 0: state_manager.player_paddle['y'] = 0 def update_cpu_paddle_position(state_manager): ball_center = state_manager.ball['y'] paddle_center = state_manager.cpu_paddle['y'] + state_manager.paddle_height / 2 if abs(ball_center - paddle_center) > state_manager.cpu_decision_threshold: if ball_center > paddle_center: state_manager.cpu_paddle['y'] += min(state_manager.cpu_paddle_speed, ball_center - paddle_center) elif ball_center < paddle_center: state_manager.cpu_paddle['y'] -= min(state_manager.cpu_paddle_speed, paddle_center - ball_center) state_manager.cpu_paddle['y'] = max(0, min(state_manager.SCREEN_HEIGHT - state_manager.paddle_height, state_manager.cpu_paddle['y'])) def update_ball_position(state_manager): # Update the ball's position state_manager.ball['x'] += state_manager.ball_speed_x state_manager.ball['y'] += state_manager.ball_speed_y # Check for wall collisions if state_manager.ball['y'] <= 0 or state_manager.ball['y'] >= state_manager.SCREEN_HEIGHT: state_manager.ball_speed_y *= -1 state_manager.ball_y_direction = 'upward' if state_manager.ball_speed_y < 0 else 'downward' # Check for paddle collisions # Logic for collisions with the player's paddle # Logic for collisions with the CPU's paddle # Check if the ball goes out of bounds if state_manager.ball['x'] <= 0 or state_manager.ball['x'] >= state_manager.SCREEN_WIDTH: state_manager.ball_out_of_bounds = True # Reset ball's position # Update score # Set game_over if conditions met # Update ball's speed # Apply speed multipliers if collisions detected def detect_and_handle_collisions(state_manager): ball_rect = pygame.Rect(state_manager.ball['x'] - state_manager.ball_diameter / 2, state_manager.ball['y'] - state_manager.ball_diameter / 2, state_manager.ball_diameter, state_manager.ball_diameter) player_paddle_rect = pygame.Rect(state_manager.player_paddle['x'], state_manager.player_paddle['y'], state_manager.paddle_width, state_manager.paddle_height) cpu_paddle_rect = pygame.Rect(state_manager.cpu_paddle['x'], state_manager.cpu_paddle['y'], state_manager.paddle_width, state_manager.paddle_height) # Collision detection and handling if ball_rect.colliderect(player_paddle_rect): state_manager.ball_speed_x *= -1 state_manager.collision_count += 1 state_manager.last_paddle_hit = 'player' #state_manager.score += state_manager.score_points_hit # Play sound effect elif ball_rect.colliderect(cpu_paddle_rect): state_manager.ball_speed_x *= -1 state_manager.collision_count += 1 state_manager.last_paddle_hit = 'cpu' else: # Check if the ball has passed the left or right screen boundary if state_manager.ball['x'] <= 0 or state_manager.ball['x'] >= state_manager.SCREEN_WIDTH: state_manager.score += state_manager.score_points_miss if state_manager.ball['x'] <= 0 else 0 #state_manager.game_over = True if state_manager.ball['x'] <= 0 else state_manager.game_over if state_manager.ball["x"] <= 0: state_manager.cpu_score += 1 state_manager.game_over = state_manager.score >= 5 or state_manager.cpu_score >= 5 state_manager.ball['x'] = state_manager.SCREEN_WIDTH / 2 state_manager.ball['y'] = state_manager.SCREEN_HEIGHT / 2 # Reset ball speeds state_manager.ball_speed_x = float(np.random.random()) state_manager.ball_speed_y = float(np.random.random()) # Play sound effect # Move the ball state_manager.ball['x'] += state_manager.ball_speed_x state_manager.ball['y'] += state_manager.ball_speed_y def restart_game(state_manager): if state_manager.restart_game_input_received: state_manager.score = 0 state_manager.ball['x'] = state_manager.SCREEN_WIDTH / 2 state_manager.ball['y'] = state_manager.SCREEN_HEIGHT / 2 state_manager.ball_speed_x = float(np.random.random()) state_manager.ball_speed_y = float(np.random.random()) state_manager.player_paddle['y'] = float(np.random.randint(0, state_manager.SCREEN_HEIGHT)) state_manager.cpu_paddle['y'] = float(np.random.randint(0, state_manager.SCREEN_HEIGHT)) state_manager.game_over = False state_manager.restart_game_input_received = False def render_player_paddle(state_manager): pygame.draw.rect(state_manager.screen, state_manager.player_paddle_color, pygame.Rect(state_manager.player_paddle['x'], state_manager.player_paddle['y'], state_manager.paddle_width, state_manager.paddle_height)) def render_cpu_paddle(state_manager): pygame.draw.rect(state_manager.screen, state_manager.cpu_paddle_color, pygame.Rect(state_manager.cpu_paddle['x'], state_manager.cpu_paddle['y'], state_manager.paddle_width, state_manager.paddle_height)) def render_ball(state_manager): pygame.draw.circle(state_manager.screen, state_manager.ball_color, (int(state_manager.ball['x']), int(state_manager.ball['y'])), state_manager.ball_diameter // 2) def render_score(state_manager): font = pygame.font.Font(None, state_manager.score_font_size) text = font.render("Score: " + str(state_manager.score), True, state_manager.score_font_color) state_manager.screen.blit(text, state_manager.score_position) def render_game_over_screen(state_manager): if state_manager.game_over: font = pygame.font.Font(None, state_manager.game_over_font_size) message = font.render(state_manager.game_over_message, True, state_manager.game_over_font_color) state_manager.screen.blit(message, (state_manager.game_over_message_x, state_manager.game_over_message_y)) font = pygame.font.Font(None, state_manager.restart_instruction_font_size) message = font.render(state_manager.restart_instruction_message, True, state_manager.restart_instruction_font_color) state_manager.screen.blit(message, (state_manager.restart_button_x, state_manager.restart_button_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 # A function to detect when the player presses the up or down arrow keys and set a flag or directly call the state transition function to move the player's paddle up or down. handle_player_input(state_manager, event) # Listen for the 'R' key press when the game is over. If the 'R' key is pressed, set the restart_game_input_received state variable to True. handle_restart_input(state_manager, event) # call all the logics # A function to update the vertical position of the player's paddle based on input events. The paddle's vertical position should increase when the down arrow key is pressed and decrease when the up arrow key is pressed, within the constraints of the screen dimensions. update_player_paddle_position(state_manager) # An AI logic function that controls the CPU paddle's y position to autonomously track and align with the ball's y position within a decision threshold to make it more human-like instead of instantly matching the ball's position. update_cpu_paddle_position(state_manager) # Update the ball's position based on its current speed and direction. Check for collisions with paddles and walls, and change direction and speed accordingly. Reset the ball's position if it goes out of bounds on the left or right side, updating the score and game over state if necessary. update_ball_position(state_manager) # Detect collisions between the ball and the paddles, reverse the ball's direction upon collision, update the collision count, determine which paddle was hit last, and adjust the score accordingly. If the ball passes the left or right edge of the screen, reset the ball's position, update the score, potentially end the game, and play the score sound effect. detect_and_handle_collisions(state_manager) # Check if the restart_game_input_received flag is True. If it is, reset relevant state variables including the score, ball position, paddle positions, game_over flag, and any other necessary variables to their initial states. restart_game(state_manager) # Fill the screen with white state_manager.screen.fill((255, 255, 255)) # A function that takes the current state of the player's paddle and renders it on the screen as a rectangle on the left side. render_player_paddle(state_manager) # A rendering function that draws the CPU paddle as a rectangle on the screen using its current state variables, specifically on the right side to represent the CPU-controlled player. render_cpu_paddle(state_manager) # Render the ball on the screen using its current position and the predefined color and diameter. render_ball(state_manager) # Render the current score of the game at the top of the screen using the score_font_size, score_position, and score_font_color properties from the state manager. render_score(state_manager) # When the game_over flag is set, render the 'Game Over!' message using the game_over_message, game_over_message_x, game_over_message_y, game_over_font_size, and game_over_font_color state variables. Also, render the restart instruction message using the restart_instruction_message, restart_button_x, restart_button_y, restart_instruction_font_size, and restart_instruction_font_color variables. render_game_over_screen(state_manager) #return not state_manager.game_over return True 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()