can you fix any errors you see with this code 


import pygame
import random

# Initialize Pygame
pygame.init()

# Set up display
WIDTH, HEIGHT = 800, 600
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("2D AI Cube Tag")

# Define Colors
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)  # Power-up color
BLACK = (0, 0, 0)
GRAY = (128, 128, 128)

# Load images
runner_image = pygame.image.load('runner.png')
chaser_image = pygame.image.load('chaser.png')

# Resize images
runner_image = pygame.transform.scale(runner_image, (50, 50))
chaser_image = pygame.transform.scale(chaser_image, (50, 50))

# Game variables
cube_speed = 5
boosted_speed = 10
boost_duration = 6000  # 6000 frames = 100 seconds at 60 FPS
alpha = 0.3  # Increased learning rate
gamma = 0.9  # Discount factor
epsilon = 0.1  # Reduced exploration rate
speed_up_duration = 15  # 15 frames = 0.25 seconds

# Q-learning setup (global for knowledge retention)
Q_table_chaser = {}  # Q-table for the chaser
Q_table_runner = {}  # Q-table for the runner

# Cube class
class Cube:
    def __init__(self, x, y, image):
        self.x = x
        self.y = y
        self.width = 50
        self.height = 50
        self.image = image
        self.speed = cube_speed
        self.boost_counter = 0

    def draw(self, screen):
        screen.blit(self.image, (self.x, self.y))

    def move(self, dx, dy):
        self.x += dx
        self.y += dy

        # Keep the cube within the screen bounds
        if self.x < 0:
            self.x = 0
        elif self.x + self.width > WIDTH:
            self.x = WIDTH - self.width

        if self.y < 0:
            self.y = 0
        elif self.y + self.height > HEIGHT:
            self.y = HEIGHT - self.height

    def apply_power_up(self):
        self.speed = boosted_speed
        self.boost_counter = boost_duration

    def update_speed(self):
        # Reset speed if boost has worn off
        if self.boost_counter > 0:
            self.boost_counter -= 1
            if self.boost_counter == 0:
                self.speed = cube_speed

# AI Cube with Q-learning
class AICube(Cube):
    def __init__(self, x, y, image, role):
        super().__init__(x, y, image)
        self.role = role  # 'chaser' or 'runner'

    def get_state(self, other_cube, power_up):
        # Simplified state: relative position of the two cubes and power-up
        dx = int((self.x - other_cube.x) / 50)  # Reduce precision to make learning faster
        dy = int((self.y - other_cube.y) / 50)
        power_dx = int((self.x - power_up.x) / 50)
        power_dy = int((self.y - power_up.y) / 50)
        return (dx, dy, power_dx, power_dy)

    def choose_action(self, state, Q_table):
        if random.random() < epsilon:
            return random.choice(["left", "right", "up", "down"])
        else:
            if state not in Q_table:
                return random.choice(["left", "right", "up", "down"])
            return max(Q_table[state], key=Q_table[state].get)

    def update_q_value(self, Q_table, state, action, reward, next_state):
        old_value = Q_table.get(state, {}).get(action, 0)
        future_value = max(Q_table.get(next_state, {}).values(), default=0)
        Q_table.setdefault(state, {})[action] = old_value + alpha * (reward + gamma * future_value - old_value)

    def update(self, other_cube, power_up, Q_table, reward):
        state = self.get_state(other_cube, power_up)
        action = self.choose_action(state, Q_table)

        # Perform the action
        if action == "left":
            self.move(-self.speed, 0)
        elif action == "right":
            self.move(self.speed, 0)
        elif action == "up":
            self.move(0, -self.speed)
        elif action == "down":
            self.move(0, self.speed)

        next_state = self.get_state(other_cube, power_up)

        # Update the Q-value for this state-action pair
        self.update_q_value(Q_table, state, action, reward, next_state)

# Power-up class
class PowerUp:
    def __init__(self):
        self.x = random.randint(0, WIDTH - 30)
        self.y = random.randint(0, HEIGHT - 30)
        self.width = 30
        self.height = 30

    def draw(self, screen):
        pygame.draw.rect(screen, GREEN, (self.x, self.y, self.width, self.height))

    def is_collected(self, cube):
        return cube.x < self.x + self.width and cube.x + cube.width > self.x \
               and cube.y < self.y + self.height and cube.y + cube.height > self.y

# Game loop
def game_loop():
    clock = pygame.time.Clock()

    # Create AI cubes
    chaser = AICube(100, 100, chaser_image, 'chaser')
    runner = AICube(400, 400, runner_image, 'runner')

    power_up = PowerUp()  # Generate first power-up

    # Timer for the tag game
    tag_timer = 60  # 60 seconds
    game_over = False
    game_speed = 1  # Default game speed
    speed_toggled = False  # Track whether speed is toggled
    speed_up_counter = 0  # Counter for speed-up duration

    while True:
        screen.fill(WHITE)

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                return
            if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
                speed_toggled = not speed_toggled
                game_speed = 100 if speed_toggled else 1
                speed_up_counter = speed_up_duration if speed_toggled else 0

        # Check for power-up collection
        if power_up.is_collected(chaser):
            chaser.apply_power_up()
            power_up = PowerUp()  # Respawn power-up
        if power_up.is_collected(runner):
            runner.apply_power_up()
            power_up = PowerUp()  # Respawn power-up

        # Update cube speeds if boosted
        chaser.update_speed()
        runner.update_speed()

        # Calculate rewards
        if chaser.x < runner.x + runner.width and chaser.x + chaser.width > runner.x \
           and chaser.y < runner.y + runner.height and chaser.y + chaser.height > runner.y:
            print("Chaser wins! Tag successful.")
            reward_chaser = 100
            reward_runner = -100
            game_over = True
        else:
            reward_chaser = -1
            reward_runner = 1

        # Update AI cubes
        chaser.update(runner, power_up, Q_table_chaser, reward_chaser)
        runner.update(chaser, power_up, Q_table_runner, reward_runner)

        # Draw cubes and power-up
        chaser.draw(screen)
        runner.draw(screen)
        power_up.draw(screen)

        # Draw AI's brain
        draw_brain(Q_table_chaser, Q_table_runner)

        # Speed up the game for a short duration if toggled
        if speed_up_counter > 0:
            speed_up_counter -= 1
            if speed_up_counter == 0:
                speed_toggled = False
                game_speed = 1

        # Update the display
        pygame.display.flip()
        clock.tick(60 * game_speed)

        # Reduce the timer
        tag_timer -= 1 / 60
        if tag_timer <= 0:
            print("Time's up! Runner wins.")
            game_over = True

        if game_over:
            # Reset game, but keep learned Q-tables
            chaser.x, chaser.y = 100, 100
            runner.x, runner.y = 400, 400
            power_up = PowerUp()  # Respawn power-up
            tag_timer = 60
            game_over = False

def draw_brain(Q_table_chaser, Q_table_runner):
    # Draw a box in the corner showing the Q-values
    font = pygame.font.SysFont(None, 24)
    pygame.draw.rect(screen, BLACK, (10, 10, 250, 180))
    pygame.draw.rect(screen, WHITE, (12, 12, 246, 176))
    pygame.draw.rect(screen, GRAY, (10, 10, 250, 180), 2)

    y_offset = 20
    for state, actions in Q_table_chaser.items():
        text = font.render(f"Chaser State {state}: {actions}", True, BLACK)
        screen.blit(text, (20, y_offset))
        y_offset += 20
        if y_offset > 180:
            break

    y_offset += 20
    for state, actions in Q_table_runner.items():
        text = font.render(f"Runner State {state}: {actions}", True, BLACK)
        screen.blit(text, (20, y_offset))
        y_offset += 20
        if y_offset > 180:
            break

game_loop()