There's a problem in my game. Player 1 isn't being drawn for the Player 2 (Clientside). Please correct this and output the entire code.



import pygame
import sys
import threading
import socket
import json
import random
from pygame import mixer

# Increase the recursion limit; use with caution
sys.setrecursionlimit(10000)

# Initialize Pygame
pygame.init()
mixer.init()

# Screen dimensions
WIDTH, HEIGHT = 800, 600
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Maze Explorers Online")

# Colors
WHITE = (255, 255, 255)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
BLACK = (0, 0, 0)
GOLDEN = (255, 215, 0)

# Player settings
player_size = 40
player_speed = 5

# Tile settings
TILE_SIZE = 40  # Size of each tile in pixels
goal_size = 40  # Size of the goal square

# Font settings
font_size = 30
font_name = pygame.font.match_font('arial')  # Change 'arial' to any installed font
font = pygame.font.Font(font_name, font_size)

# Sound settings
# Load the sound (ensure these files exist in your working directory)
try:
    goal_sound = mixer.Sound('goal.wav')
    fireworks_sound = mixer.Sound('fireworks.mp3')
    title_music = 'title_screen.mp3'
    in_game_music = 'in_game.mp3'
    game_over_music = 'game_over.mp3'
except Exception as e:
    print("Error loading sound files:", e)
    goal_sound = None
    fireworks_sound = None
    title_music = None
    in_game_music = None
    game_over_music = None

# Networking settings
class NetworkThread(threading.Thread):
    def __init__(self, is_server, host, port, game_state, level_event, lock):
        threading.Thread.__init__(self)
        self.is_server = is_server
        self.host = host
        self.port = port
        self.game_state = game_state  # Shared game state
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.connection = None
        self.running = True
        self.connected = threading.Event()  # Event to signal that the connection is established
        self.level_event = level_event  # Event to signal level advancement
        self.lock = lock  # Shared lock for thread-safe operations

    def run(self):
        buffer = ''
        try:
            if self.is_server:
                self.sock.bind((self.host, self.port))
                self.sock.listen(1)
                print(f"[Server] Listening on {self.host}:{self.port}")
                self.connection, addr = self.sock.accept()
                print(f"[Server] Client connected from {addr}")
                self.connected.set()  # Signal that the connection is established
            else:
                while self.running:
                    try:
                        self.sock.connect((self.host, self.port))
                        self.connection = self.sock
                        print(f"[Client] Connected to server at {self.host}:{self.port}")
                        self.connected.set()  # Signal that the connection is established
                        break
                    except socket.error as e:
                        print(f"[Client] Connection failed: {e}. Retrying in 1 second...")
                        pygame.time.wait(1000)  # Wait 1 second before retrying

            while self.running:
                try:
                    data = self.connection.recv(4096).decode()
                    if data:
                        buffer += data
                        while '\n' in buffer:
                            line, buffer = buffer.split('\n', 1)
                            if line:
                                try:
                                    remote_game_state = json.loads(line)
                                    print(f"[NetworkThread] Received data: {remote_game_state}")
                                    with self.lock:
                                        if self.is_server:
                                            # Server receives data from client
                                            self.game_state['opponent_pos'] = remote_game_state.get('player_pos', self.game_state.get('opponent_pos', [0, 0]))
                                            self.game_state['opponent_score'] = remote_game_state.get('player_score', self.game_state.get('opponent_score', 0))
                                            
                                            # Handle level advancement request
                                            if remote_game_state.get('request_level_advance'):
                                                self.game_state['level_advance_requested'] = True
                                                print("[NetworkThread] Level advancement requested by client.")
                                        else:
                                            # Client receives game state updates from server
                                            self.game_state.update(remote_game_state)
                                except json.JSONDecodeError as e:
                                    print(f"[NetworkThread] JSON decode error: {e} in line: {line}")
                except Exception as e:
                    print(f"[NetworkThread] Networking error: {e}")
                    self.running = False
        finally:
            if self.connection:
                self.connection.close()
            self.sock.close()
            print("[NetworkThread] Socket closed.")

    def send_data(self, data):
        try:
            if self.connection and self.running:
                message = json.dumps(data) + '\n'
                self.connection.sendall(message.encode())
                print(f"[NetworkThread] Sent data: {data}")
        except Exception as e:
            print(f"[NetworkThread] Error sending data: {e}")
            self.running = False

    def stop(self):
        self.running = False
        if self.connection:
            self.connection.close()
        self.sock.close()

# Function to render text with outline
def draw_text_with_outline(surface, text, text_color, outline_color, x, y):
    # Create the base text surface
    text_surface = font.render(text, True, text_color)
    text_rect = text_surface.get_rect()
    text_rect.topleft = (x, y)

    # Offsets for the outline
    outline_offset = 2

    # Draw the outline by rendering the text in the outline color with slight offsets
    for dx in [-outline_offset, 0, outline_offset]:
        for dy in [-outline_offset, 0, outline_offset]:
            if dx != 0 or dy != 0:  # Avoid redrawing the base text here
                outline_rect = text_surface.get_rect()
                outline_rect.topleft = (x + dx, y + dy)
                surface.blit(font.render(text, True, outline_color), outline_rect)

    # Draw the base text
    surface.blit(text_surface, text_rect)

# Function to fade in/out a surface
def fade_surface(surface, fade_duration, fade_in=True, fade_color=(0, 0, 0)):
    alpha_start = 0 if fade_in else 255
    alpha_end = 255 if fade_in else 0
    step = 5  # Adjust the step for faster or slower fade

    fade_overlay = pygame.Surface((WIDTH, HEIGHT))
    fade_overlay.fill(fade_color)

    if fade_in:
        alpha_range = range(alpha_start, alpha_end, step)
    else:
        alpha_range = range(alpha_start, alpha_end, -step)

    for alpha in alpha_range:
        fade_overlay.set_alpha(alpha)
        surface.blit(fade_overlay, (0, 0))
        pygame.display.flip()
        pygame.time.delay(int(fade_duration / (abs(alpha_end - alpha_start) / step)))

def splash_and_title_screen(surface):
    # Load and play the background music
    if title_music:
        mixer.music.load(title_music)
        mixer.music.set_volume(0.25)
        mixer.music.play(-1)  # The '-1' makes the music play in a loop

    # Splash screen
    surface.fill(BLACK)
    splash_font = pygame.font.Font(font_name, 50)
    text = splash_font.render("Exploratory Studios Presents", True, WHITE)
    text_rect = text.get_rect(center=(WIDTH // 2, HEIGHT // 2))
    surface.blit(text, text_rect)
    pygame.display.flip()
    pygame.time.wait(2000)

    fade_surface(surface, fade_duration=1000, fade_in=True)  # Fade in
    fade_surface(surface, fade_duration=1500, fade_in=True, fade_color=WHITE)  # Fade in

    # Title screen
    running = True

    # Load background maze for the title screen
    maze_width, maze_height = 20, 20  # Adjust these values as needed
    maze = generate_maze(maze_width, maze_height)
    maze_offset_x, maze_offset_y = 0, 0
    scroll_speed = 1.4  # Speed of the maze scrolling

    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_RETURN:
                    mixer.music.stop()
                    running = False

        surface.fill(WHITE)

        # Draw the scrolling maze
        for y, row in enumerate(maze):
            for x, tile in enumerate(row):
                if tile == 1:
                    tile_x = x * TILE_SIZE + maze_offset_x
                    tile_y = y * TILE_SIZE
                    pygame.draw.rect(surface, BLACK, (tile_x % WIDTH, tile_y % HEIGHT, TILE_SIZE, TILE_SIZE))

        maze_offset_x -= scroll_speed
        if maze_offset_x <= -maze_width * TILE_SIZE:
            maze_offset_x = 0

        # Title text
        title_font = pygame.font.Font(font_name, 70)
        title_text = "Maze Explorers Online"
        title_render = title_font.render(title_text, True, GOLDEN)
        title_rect = title_render.get_rect(center=(WIDTH // 2, HEIGHT // 3))
        draw_text_with_outline(surface, title_text, GOLDEN, BLACK, title_rect.centerx - title_font.size(title_text)[0]//2, title_rect.centery - title_font.size(title_text)[1]//2)

        # Press Enter to Start
        start_font = pygame.font.Font(font_name, 30)
        start_text = "Press Enter"
        start_render = start_font.render(start_text, True, GOLDEN)
        start_rect = start_render.get_rect(center=(WIDTH // 2, HEIGHT // 2))
        draw_text_with_outline(surface, start_text, GOLDEN, BLACK, start_rect.centerx - start_font.size(start_text)[0]//2, start_rect.centery - start_font.size(start_text)[1]//2)

        pygame.display.flip()
        pygame.time.Clock().tick(30)

def display_game_over_screen(surface, winner, winner_score):
    # Load and play game over music
    if game_over_music:
        mixer.music.load(game_over_music)
        mixer.music.set_volume(0.25)
        mixer.music.play(-1)

    # Game over screen settings
    running = True
    clock = pygame.time.Clock()

    # Fade in effect
    fade_surface(surface, fade_duration=2000, fade_in=True, fade_color=WHITE)

    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
                pygame.quit()
                sys.exit()
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_RETURN:
                    mixer.music.stop()
                    running = False
                    main()

        surface.fill(WHITE)

        # Display Game Over text
        game_over_font = pygame.font.Font(font_name, 60)
        game_over_text = "Game Over"
        game_over_rect = game_over_font.render(game_over_text, True, GOLDEN).get_rect(center=(WIDTH // 2, HEIGHT // 3))
        draw_text_with_outline(surface, game_over_text, GOLDEN, BLACK, game_over_rect.centerx - game_over_font.size(game_over_text)[0]//2, game_over_rect.centery - game_over_font.size(game_over_text)[1]//2)

        # Display Winner
        winner_font = pygame.font.Font(font_name, 40)
        winner_text = f"Winner: Player {winner} with {winner_score} points"
        winner_rect = winner_font.render(winner_text, True, BLACK).get_rect(center=(WIDTH // 2, HEIGHT // 2))
        draw_text_with_outline(surface, winner_text, BLACK, WHITE, winner_rect.centerx - winner_font.size(winner_text)[0]//2, winner_rect.centery - winner_font.size(winner_text)[1]//2)

        # Press Enter to Restart
        restart_font = pygame.font.Font(font_name, 30)
        restart_text = "Press Enter to Restart"
        restart_rect = restart_font.render(restart_text, True, BLACK).get_rect(center=(WIDTH // 2, HEIGHT // 2 + 50))
        draw_text_with_outline(surface, restart_text, BLACK, WHITE, restart_rect.centerx - restart_font.size(restart_text)[0]//2, restart_rect.centery - restart_font.size(restart_text)[1]//2)

        pygame.display.flip()
        clock.tick(30)

def generate_maze(width, height):
    # Initialize all cells as walls
    maze = [[1 for _ in range(width)] for _ in range(height)]

    # Starting position
    start_x, start_y = 1, 1
    maze[start_y][start_x] = 0

    # Function to carve out the maze
    def carve(x, y):
        directions = [(1, 0), (-1, 0), (0, 1), (0, -1)]
        random.shuffle(directions)

        for dx, dy in directions:
            new_x, new_y = x + dx * 2, y + dy * 2
            if 0 <= new_x < width and 0 <= new_y < height and maze[new_y][new_x] == 1:
                maze[new_y][new_x] = 0
                maze[new_y - dy][new_x - dx] = 0
                carve(new_x, new_y)

    carve(start_x, start_y)

    # Add walls on the bottom and right edges
    for x in range(width):
        maze[height - 1][x] = 1
    for y in range(height):
        maze[y][width - 1] = 1

    return maze

def find_center_open_space():
    center_x = NUM_TILES_WIDTH // 2
    center_y = NUM_TILES_HEIGHT // 2

    radius = 0
    max_radius = max(NUM_TILES_WIDTH, NUM_TILES_HEIGHT)  # Maximum possible radius

    while radius <= max_radius:
        for dx in range(-radius, radius + 1):
            for dy in range(-radius, radius + 1):
                x = center_x + dx
                y = center_y + dy
                if 0 <= x < NUM_TILES_WIDTH and 0 <= y < NUM_TILES_HEIGHT:
                    if environment[y][x] == 0:
                        return x * TILE_SIZE, y * TILE_SIZE
        radius += 1

    # If no open space is found, default to starting position
    return center_x * TILE_SIZE, center_y * TILE_SIZE

# Function to find the next open space in the maze
def find_open_space_near(x, y):
    if 0 <= y < len(environment) and 0 <= x < len(environment[0]) and environment[y][x] == 0:
        return [x * TILE_SIZE, y * TILE_SIZE]
    else:
        radius = 1
        max_radius = max(NUM_TILES_WIDTH, NUM_TILES_HEIGHT)
        while radius <= max_radius:
            for dx in range(-radius, radius + 1):
                for dy in range(-radius, radius + 1):
                    nx = x + dx
                    ny = y + dy
                    if 0 <= ny < len(environment) and 0 <= nx < len(environment[0]):
                        if environment[ny][nx] == 0:
                            return [nx * TILE_SIZE, ny * TILE_SIZE]
            radius += 1
    # Default to the original position if no open space is found
    return [x * TILE_SIZE, y * TILE_SIZE]

def freeze_and_fade_out(surface, freeze_duration, fade_duration, fade_color):
    pygame.time.wait(freeze_duration)  # Freeze the game for a set duration
    fade_surface(surface, fade_duration, fade_in=True, fade_color=fade_color)  # Fade to the specified color

# Declare the global variable for goal_collected
goal_collected = False

def increase_level():
    global current_level, NUM_TILES_WIDTH, NUM_TILES_HEIGHT, environment, player_pos, opponent_pos, center_pos, level_start_time, game_state, goal_collected

    current_level += 1
    goal_collected = False  # Reset the flag

    # Check if maximum level is exceeded
    if current_level > max_level:
        if fireworks_sound:
            fireworks_sound.play()
        freeze_and_fade_out(screen, 800, 1000, WHITE)
        display_game_over_screen(screen, player_number if player_score > opponent_score else (2 if player_number == 1 else 1), max(player_score, opponent_score))
        pygame.time.wait(5000)  # Display game over screen for 5 seconds
        pygame.quit()
        sys.exit()
        return

    # Freeze and fade out before changing levels
    freeze_and_fade_out(screen, 800, 1000, WHITE)

    # Regenerate the maze for the next level
    NUM_TILES_WIDTH += level_increment
    NUM_TILES_HEIGHT += level_increment

    # Generate the maze on the server
    environment = generate_maze(NUM_TILES_WIDTH, NUM_TILES_HEIGHT)
    print(f"[increase_level] Generated new maze with size {NUM_TILES_WIDTH}x{NUM_TILES_HEIGHT}")

    # Update the location of the gold square
    center_pos = find_center_open_space()
    print(f"[increase_level] New center position: {center_pos}")

    # Reset player positions
    if player_number == 1:
        player_pos = find_open_space_near(1, 1)
        opponent_pos = find_open_space_near(NUM_TILES_WIDTH - 2, NUM_TILES_HEIGHT - 2)
    else:
        player_pos = find_open_space_near(NUM_TILES_WIDTH - 2, NUM_TILES_HEIGHT - 2)
        opponent_pos = find_open_space_near(1, 1)

    print(f"[increase_level] Player position: {player_pos}")
    print(f"[increase_level] Opponent position: {opponent_pos}")

    # Update game state
    game_state['environment'] = environment
    game_state['center_pos'] = center_pos
    game_state['level'] = current_level
    game_state['player_pos'] = player_pos
    game_state['player_score'] = player_score
    game_state['goal_collected'] = goal_collected  # Update the flag

    # Signal to the client that the level has advanced
    game_state['level_advanced'] = True

    # Reset level start time for the new level
    level_start_time = pygame.time.get_ticks()

    # Fade in for the new level
    fade_surface(screen, 1000, fade_in=True, fade_color=WHITE)
    print(f"[increase_level] Level {current_level} started.")

# Function to calculate Euclidean distance
def calculate_distance(pos1, pos2):
    return ((pos1[0] - pos2[0]) ** 2 + (pos1[1] - pos2[1]) ** 2) ** 0.5

def check_gold_collision(player_pos, player_number):
    global player_score, opponent_score, goal_collected  # Added goal_collected

    if goal_collected:
        return False  # Goal already collected in this level

    player_rect = pygame.Rect(player_pos[0], player_pos[1], player_size, player_size)
    gold_rect = pygame.Rect(center_pos[0], center_pos[1], goal_size, goal_size)
    if player_rect.colliderect(gold_rect):
        goal_collected = True  # Set the flag
        if goal_sound:
            goal_sound.play()
        current_time = pygame.time.get_ticks()
        time_taken = current_time - level_start_time
        score = max(1000 - time_taken // 100, 0)  # Example scoring formula

        if player_number == 1:
            player_score += score
            # Calculate score for opponent based on distance
            distance = calculate_distance(opponent_pos, center_pos)
            opponent_score += int(max(300 - distance // TILE_SIZE, 0))  # Example proximity scoring
            print(f"[check_gold_collision] Player 1 scored {score}. Opponent score updated by {int(max(300 - distance // TILE_SIZE, 0))}")
        else:
            player_score += score
            # Calculate score for opponent based on distance
            distance = calculate_distance(opponent_pos, center_pos)
            opponent_score += int(max(300 - distance // TILE_SIZE, 0))  # Example proximity scoring
            print(f"[check_gold_collision] Player 2 scored {score}. Opponent score updated by {int(max(300 - distance // TILE_SIZE, 0))}")

        return True
    return False

def is_collision_with_wall(x, y):
    # Convert the position to tile coordinates
    tile_x = int(x // TILE_SIZE)
    tile_y = int(y // TILE_SIZE)

    # Check for out-of-bounds
    if tile_x < 0 or tile_y < 0 or tile_x >= NUM_TILES_WIDTH or tile_y >= NUM_TILES_HEIGHT:
        return True

    # Check for collision with a wall tile
    return environment[tile_y][tile_x] == 1

def draw_environment(surface, player_pos, opponent_pos, player_color, opponent_color):
    # Draw the maze environment relative to the player's position (camera)
    for y, row in enumerate(environment):
        for x, tile in enumerate(row):
            if tile == 1:
                tile_x = x * TILE_SIZE - player_pos[0] + WIDTH // 2
                tile_y = y * TILE_SIZE - player_pos[1] + HEIGHT // 2
                pygame.draw.rect(surface, BLACK, (tile_x, tile_y, TILE_SIZE, TILE_SIZE))

    # Draw the golden square (goal) relative to the player's position
    gold_x = center_pos[0] - player_pos[0] + WIDTH // 2
    gold_y = center_pos[1] - player_pos[1] + HEIGHT // 2
    if not goal_collected:
        pygame.draw.rect(surface, GOLDEN, (gold_x, gold_y, goal_size, goal_size))

    # Calculate opponent's position relative to the player's position (camera)
    opp_x = opponent_pos[0] - player_pos[0] + WIDTH // 2
    opp_y = opponent_pos[1] - player_pos[1] + HEIGHT // 2

    # Debug: Print opponent's relative position
    print(f"[Draw] Opponent relative position: ({opp_x}, {opp_y})")

    # Draw the opponent if within the visible screen
    if 0 <= opp_x <= WIDTH - player_size and 0 <= opp_y <= HEIGHT - player_size:
        pygame.draw.rect(surface, opponent_color, (opp_x, opp_y, player_size, player_size))
    else:
        print(f"[Draw] Opponent is out of bounds: ({opp_x}, {opp_y})")

    # Draw the local player at the center
    pygame.draw.rect(surface, player_color, (WIDTH // 2, HEIGHT // 2, player_size, player_size))

def outline_text(surface, text, font, color, outline_color, x, y, alpha=255):
    # Create a text surface with the primary color
    text_render = font.render(text, True, color)
    text_render.set_alpha(alpha)
    text_rect = text_render.get_rect(centerx=x, centery=y)

    # Create an outline
    outline = font.render(text, True, outline_color)
    outline.set_alpha(alpha)
    outline_rect = outline.get_rect(centerx=x, centery=y)

    # Blit the outline first
    for dx, dy in [(-2, 0), (2, 0), (0, -2), (0, 2)]:
        surface.blit(outline, outline_rect.move(dx, dy))

    # Then blit the text on top of the outline
    surface.blit(text_render, text_rect)

def main():
    global player_pos, opponent_pos, player_score, opponent_score, center_pos, current_level, level_start_time, NUM_TILES_WIDTH, NUM_TILES_HEIGHT, environment, player_number, is_server, game_state, goal_collected

    # Initialize level settings
    global max_level, level_increment
    max_level = 5  # Maximum level
    level_increment = 4  # Increase in map size per level
    level_start_time = pygame.time.get_ticks()

    # Initialize threading lock
    game_state_lock = threading.Lock()

    # Networking setup
    is_server = False
    host = 'localhost'
    port = 4096  # Default port

    choice = input("Do you want to host the game? (y/n): ").lower()
    if choice == 'y':
        is_server = True
        host = '0.0.0.0'  # Listen on all interfaces
        port_input = input("Enter the port number to host the game (default 4096): ")
        try:
            port = int(port_input)
            if not (0 < port < 65536):
                raise ValueError
        except ValueError:
            print("Invalid port number. Using default port 4096.")
            port = 4096
    else:
        host = input("Enter the server's IP address: ")
        port_input = input("Enter the server's port number (default 4096): ")
        try:
            port = int(port_input)
            if not (0 < port < 65536):
                raise ValueError
        except ValueError:
            print("Invalid port number. Using default port 4096.")
            port = 4096

    # Initialize the game state
    current_level = 1
    NUM_TILES_WIDTH = 15
    NUM_TILES_HEIGHT = 15

    level_event = threading.Event()  # Event to signal level advancement

    goal_collected = False  # Initialize the flag

    if is_server:
        # Generate the maze on the server
        environment = generate_maze(NUM_TILES_WIDTH, NUM_TILES_HEIGHT)
        print(f"[Server] Generated maze with size {NUM_TILES_WIDTH}x{NUM_TILES_HEIGHT}")

        # Set the center position
        center_pos = find_center_open_space()
        print(f"[Server] Center position: {center_pos}")

        # Ensure center_pos is not None
        if center_pos is None:
            center_pos = (NUM_TILES_WIDTH // 2 * TILE_SIZE, NUM_TILES_HEIGHT // 2 * TILE_SIZE)
            print(f"[Server] Center position was None, defaulted to: {center_pos}")

        # Determine player number and starting positions
        player_number = 1
        player_pos = find_open_space_near(1, 1)  # Player 1 near top left corner
        opponent_pos = find_open_space_near(NUM_TILES_WIDTH - 2, NUM_TILES_HEIGHT - 2)
        player_color = RED
        opponent_color = BLUE

        # Initialize scores
        player_score = 0
        opponent_score = 0

        # Initialize the game state
        game_state = {
            'level': current_level,
            'center_pos': center_pos,
            'environment': environment,
            'player_pos': player_pos,
            'player_score': player_score,
            'level_advanced': False,
            'level_advance_requested': False,
            'goal_collected': goal_collected
        }
    else:
        # Client will receive the maze from the server
        environment = None
        center_pos = None
        # Determine player number and starting positions
        player_number = 2
        player_pos = None
        opponent_pos = None
        player_color = BLUE
        opponent_color = RED
        # Initialize scores
        player_score = 0
        opponent_score = 0
        # Initialize the game state
        game_state = {
            'level': None,
            'center_pos': None,
            'environment': None,
            'player_pos': None,
            'player_score': player_score,
            'level_advanced': False,
            'level_advance_requested': False,
            'goal_collected': goal_collected
        }

    network_thread = NetworkThread(is_server, host, port, game_state, level_event, game_state_lock)
    network_thread.start()

    # Wait until the network connection is established
    print("Waiting for network connection...")
    network_thread.connected.wait()
    print("Network connection established.")

    if is_server:
        # Send initial game state to client
        initial_game_state = {
            'level': current_level,
            'center_pos': center_pos,
            'environment': environment,
            'player_pos': player_pos,  # Send server's player position
            'player_score': player_score,
            'goal_collected': goal_collected
        }
        network_thread.send_data(initial_game_state)
        print(f"[Server] Sent initial game state: {initial_game_state}")
    else:
        # Wait for initial game state from the server
        print("Waiting for initial game state from the server...")
        while True:
            with game_state_lock:
                if game_state['environment'] is not None and game_state['center_pos'] is not None and game_state['player_pos'] is not None:
                    break
            pygame.time.wait(100)
        print("Initial game state received.")

        # Debug: Print the received game state
        print(f"[Client] Received game state: {game_state}")

        # Set up the game state based on received data
        environment = game_state['environment']
        center_pos_received = game_state['center_pos']
        if center_pos_received is not None:
            center_pos = tuple(center_pos_received)
        else:
            center_pos = (NUM_TILES_WIDTH // 2 * TILE_SIZE, NUM_TILES_HEIGHT // 2 * TILE_SIZE)
        current_level = game_state['level']
        NUM_TILES_WIDTH = len(environment[0])
        NUM_TILES_HEIGHT = len(environment)
        level_start_time = pygame.time.get_ticks()
        goal_collected = game_state['goal_collected']

        # Set player and opponent positions
        player_pos = find_open_space_near(NUM_TILES_WIDTH - 2, NUM_TILES_HEIGHT - 2)
        opponent_pos = game_state['player_pos']  # Server's player position

        print(f"[Client] Player position: {player_pos}")
        print(f"[Client] Opponent position: {opponent_pos}")

        # Send initial player position to server
        initial_player_data = {
            'player_pos': player_pos,
            'player_score': player_score
            # Do NOT send 'goal_collected' from client
        }
        network_thread.send_data(initial_player_data)
        print(f"[Client] Sent initial player data: {initial_player_data}")

    # Now both server and client have their own player_pos and opponent_pos

    # Fade in at the start of a new game
    fade_surface(screen, 1000, fade_in=True, fade_color=WHITE)  # Adjust fade duration and color as needed

    if in_game_music:
        mixer.music.load(in_game_music)
        mixer.music.set_volume(0.5)
        mixer.music.play(-1)  # The '-1' makes the music play in a loop

    # Game loop
    running = True
    clock = pygame.time.Clock()
    while running:
        if not network_thread.running:
            print("Network connection lost.")
            running = False
            break

        # Event handling
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
                network_thread.stop()
                pygame.quit()
                sys.exit()

        # Player movement
        keys = pygame.key.get_pressed()

        # Local Player Movement (Arrow Keys)
        if keys[pygame.K_UP]:
            new_y = player_pos[1] - player_speed
            if not is_collision_with_wall(player_pos[0], new_y) and not is_collision_with_wall(player_pos[0] + player_size - 1, new_y):
                player_pos[1] = new_y
        if keys[pygame.K_DOWN]:
            new_y = player_pos[1] + player_speed
            if not is_collision_with_wall(player_pos[0], new_y + player_size - 1) and not is_collision_with_wall(player_pos[0] + player_size - 1, new_y + player_size - 1):
                player_pos[1] = new_y
        if keys[pygame.K_LEFT]:
            new_x = player_pos[0] - player_speed
            if not is_collision_with_wall(new_x, player_pos[1]) and not is_collision_with_wall(new_x, player_pos[1] + player_size - 1):
                player_pos[0] = new_x
        if keys[pygame.K_RIGHT]:
            new_x = player_pos[0] + player_speed
            if not is_collision_with_wall(new_x + player_size - 1, player_pos[1]) and not is_collision_with_wall(new_x + player_size - 1, player_pos[1] + player_size - 1):
                player_pos[0] = new_x

        # Check for collision with gold square
        if check_gold_collision(player_pos, player_number):
            if is_server:
                # Server handles level advancement
                increase_level()
                level_start_time = pygame.time.get_ticks()  # Reset the level start time
                # Update game state
                with game_state_lock:
                    game_state['environment'] = environment
                    game_state['center_pos'] = center_pos
                    game_state['level'] = current_level
                    game_state['goal_collected'] = goal_collected
                    game_state['level_advanced'] = True

                # Send updated game state to client
                network_thread.send_data(game_state)
                print(f"[Server] Level advanced to {current_level}, sent updated game state.")
            else:
                # Client requests level advancement from server
                network_thread.send_data({'request_level_advance': True})
                print("[Client] Requested level advancement.")

        # Update game state
        if is_server:
            with game_state_lock:
                game_state['player_pos'] = player_pos
                game_state['player_score'] = player_score
        else:
            with game_state_lock:
                game_state['player_pos'] = player_pos
                game_state['player_score'] = player_score

        # Send local player data (excluding 'goal_collected')
        if not is_server:
            # Client sends its own player data
            local_player_data = {
                'player_pos': player_pos,
                'player_score': player_score
            }
            network_thread.send_data(local_player_data)
            print(f"[Client] Sent player data: {local_player_data}")
        else:
            # Server sends the entire game_state including its own player_pos
            with game_state_lock:
                network_thread.send_data(game_state)
            print(f"[Server] Sent game state: {game_state}")

        # Update opponent's data from game state
        with game_state_lock:
            if is_server:
                opponent_pos = game_state.get('opponent_pos', opponent_pos)
                opponent_score = game_state.get('opponent_score', opponent_score)
                print(f"[Server] Updated opponent_pos: {opponent_pos}, opponent_score: {opponent_score}")
            else:
                # On the client side, 'player_pos' in game_state represents the server's position
                opponent_pos = game_state.get('player_pos', opponent_pos)
                opponent_score = game_state.get('player_score', opponent_score)
                print(f"[Client] Updated opponent_pos: {opponent_pos}, opponent_score: {opponent_score}")

            # Server handles level advancement requests from client
            if is_server and game_state.get('level_advance_requested'):
                print("[Main] Level advancement requested by client.")
                increase_level()
                level_start_time = pygame.time.get_ticks()  # Reset the level start time
                # Update game state
                game_state['environment'] = environment
                game_state['center_pos'] = center_pos
                game_state['level'] = current_level
                game_state['goal_collected'] = goal_collected
                game_state['level_advanced'] = True
                game_state['level_advance_requested'] = False  # Reset the request flag

                # Send updated game state to client
                network_thread.send_data(game_state)
                print(f"[Server] Level advanced to {current_level} due to client request, sent updated game state.")

            # Check if level advanced
            if game_state.get('level_advanced'):
                # Level has advanced, update game state
                current_level = game_state['level']
                environment = game_state['environment']
                center_pos = tuple(game_state['center_pos'])
                NUM_TILES_WIDTH = len(environment[0])
                NUM_TILES_HEIGHT = len(environment)
                goal_collected = False  # Reset the flag

                # Reset player positions
                if player_number == 1:
                    player_pos = find_open_space_near(1, 1)
                    opponent_pos = find_open_space_near(NUM_TILES_WIDTH - 2, NUM_TILES_HEIGHT - 2)
                else:
                    player_pos = find_open_space_near(NUM_TILES_WIDTH - 2, NUM_TILES_HEIGHT - 2)
                    opponent_pos = find_open_space_near(1, 1)

                level_start_time = pygame.time.get_ticks()

                # Reset level advanced flag
                game_state['level_advanced'] = False

                # Fade in for the new level
                fade_surface(screen, 1000, fade_in=True, fade_color=WHITE)
                print(f"[Game] Level {current_level} started.")

        # Drawing
        screen.fill(WHITE)

        # Draw the environment
        draw_environment(screen, player_pos, opponent_pos, player_color, opponent_color)

        # Display scores
        draw_text_with_outline(screen, f"Your Score: {player_score}", BLACK, WHITE, 5, 5)
        draw_text_with_outline(screen, f"Opponent Score: {opponent_score}", BLACK, WHITE, 5, 35)
        draw_text_with_outline(screen, f"Level: {current_level}", BLACK, WHITE, WIDTH - 150, 5)

        # Update the display
        pygame.display.flip()

        # Frame rate
        clock.tick(30)

        # End the game
        if current_level is not None and current_level > max_level:  # If maximum level is exceeded
            # Determine the winner and their score
            winner = player_number if player_score > opponent_score else (2 if player_number == 1 else 1)
            winner_score = player_score if winner == player_number else opponent_score
            mixer.music.stop()
            display_game_over_screen(screen, winner, winner_score)

            # Keep the game over screen for a set amount of time
            pygame.time.wait(5000)  # 5 seconds

            # Wait for user input after the game over screen
            waiting_for_input = True
            while waiting_for_input:
                for event in pygame.event.get():
                    if event.type == pygame.QUIT:
                        waiting_for_input = False
                        pygame.quit()
                        sys.exit()
                    if event.type == pygame.KEYDOWN:
                        if event.key == pygame.K_ESCAPE:  # Close the game when ESC is pressed
                            waiting_for_input = False

            break  # Break out of the game loop

    # Cleanup
    network_thread.stop()
    pygame.quit()
    sys.exit()

# Run the splash and title screen
splash_and_title_screen(screen)

# Start the main function
main()

# Quit Pygame
pygame.quit()
sys.exit()