add bullet limt system and add bullet delay to disable counties firing
give me new full code
game.py
import time
import pygame
import socket
import pickle
import threading
import struct
import sys
import json

# Initialize Pygame
pygame.init()

# Set up the screen
screen = pygame.display.set_mode((1280, 720))
pygame.display.set_caption('2dFight Game')

# Constants
tile_size = 72  # Tile size after scaling
GRAVITY = 1
JUMP_STRENGTH = 15
SPEED = 11
FRICTION = 0.0001
FONT = pygame.font.Font(None, 48)
SMALL_FONT = pygame.font.Font(None, 36)
WHITE = (255, 255, 255)
BLACK = (46, 46, 46)
BLUE = (135, 206, 235)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
# Input boxes
player_name_input_box = pygame.Rect(400, 200, 400, 50)
server_ip_input_box = pygame.Rect(400, 300, 400, 50)

# Text variables
player_name = 'Player'
server_ip = 'localhost'
input_active = None

# Connecting flag
connecting = False

# Function to display text
def draw_text(text, font, color, surface, x, y):
    textobj = font.render(text, True, color)
    textrect = textobj.get_rect(center=(x, y))
    surface.blit(textobj, textrect)

# Connecting screen
def show_connecting_screen():
    screen.fill(BLUE)
    draw_text("Connecting...", FONT, BLACK, screen, 640, 360)
    pygame.display.flip()

# Function to handle user input for name and server IP in Pygame
def get_user_input_pygame():
    global player_name, server_ip, input_active, connecting
    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            if event.type == pygame.MOUSEBUTTONDOWN:
                if player_name_input_box.collidepoint(event.pos):
                    input_active = 'name'
                elif server_ip_input_box.collidepoint(event.pos):
                    input_active = 'server_ip'
                else:
                    input_active = None
            if event.type == pygame.KEYDOWN:
                if input_active == 'name':
                    if event.key == pygame.K_RETURN:
                        input_active = 'server_ip'
                    elif event.key == pygame.K_BACKSPACE:
                        player_name = player_name[:-1]
                    else:
                        player_name += event.unicode
                elif input_active == 'server_ip':
                    if event.key == pygame.K_RETURN:
                        connecting = True
                        show_connecting_screen()
                        return player_name, server_ip
                    elif event.key == pygame.K_BACKSPACE:
                        server_ip = server_ip[:-1]
                    else:
                        server_ip += event.unicode

        screen.fill(BLUE)
        draw_text("Enter Player Name:", FONT, BLACK, screen, 640, 160)
        draw_text("Enter Server IP:", FONT, BLACK, screen, 640, 260)

        pygame.draw.rect(screen, WHITE, player_name_input_box, 2)
        pygame.draw.rect(screen, WHITE, server_ip_input_box, 2)

        draw_text(player_name, SMALL_FONT, BLACK, screen, 640, 225)
        draw_text(server_ip, SMALL_FONT, BLACK, screen, 640, 325)

        pygame.display.flip()

# Function to send data
def send_data(data):
    serialized_data = pickle.dumps(data)
    length = len(serialized_data)
    client.sendall(struct.pack('>I', length) + serialized_data)

# Function to receive messages
def receive_message(conn):
    length_data = conn.recv(4)
    if len(length_data) < 4:
        raise Exception("Received incomplete length data")

    data_length = struct.unpack('>I', length_data)[0]
    data = b''
    while len(data) < data_length:
        packet = conn.recv(data_length - len(data))
        if not packet:
            raise Exception("Received incomplete data")
        data += packet
    return pickle.loads(data)

# Set up the character
character = pygame.Rect(375, 375, 50, 50)  # Initial position
character_velocity_x = 0
character_velocity_y = 0
is_jumping = False

# Initialize player data and map layout
players = {}
map_layout = []

# Load tile images
tile_images = {
    0: pygame.Surface((tile_size, tile_size)),  # Empty space
    1: pygame.image.load('./assets/grass.png').convert_alpha(),  # Grass tile (solid)
    2: pygame.image.load('./assets/grass.png').convert_alpha(),  # Grass tile (jump-through)
}

# Scale the grass tile image
tile_images[1] = pygame.transform.scale(tile_images[1], (tile_size, tile_size))
tile_images[2] = pygame.transform.scale(tile_images[2], (tile_size, tile_size))

# Set color for transparent tile (matching the screen background)
tile_images[0].fill(BLUE)  # Light blue to blend with background

# Function to check if the character is on the ground (on a grass tile)
def is_on_ground(rect):
    rect.bottom += 1  # Temporarily move the rect down by 1 pixel
    for y in range(max(0, rect.bottom // tile_size), min(len(map_layout), (rect.bottom // tile_size) + 1)):
        for x in range(max(0, rect.left // tile_size), min(len(map_layout[y]), (rect.right // tile_size) + 1)):
            if map_layout[y][x] == 1 or map_layout[y][x] == 2:
                tile_rect = pygame.Rect(x * tile_size, y * tile_size, tile_size, tile_size)
                if rect.colliderect(tile_rect):
                    rect.bottom -= 1  # Move the rect back up
                    return True
    rect.bottom -= 1  # Move the rect back up
    return False

# Function to check and handle general collision with tiles
def handle_tile_collisions(rect):
    global character_velocity_y, character_velocity_x, is_jumping

    # Check vertical collisions first (falling or jumping)
    for y in range(max(0, rect.top // tile_size), min(len(map_layout), (rect.bottom // tile_size) + 1)):
        for x in range(max(0, rect.left // tile_size), min(len(map_layout[y]), (rect.right // tile_size) + 1)):
            tile_id = map_layout[y][x]
            tile_rect = pygame.Rect(x * tile_size, y * tile_size, tile_size, tile_size)

            if rect.colliderect(tile_rect):
                if tile_id == 1:  # Solid tile
                    # Handle vertical collisions
                    if character_velocity_y > 0:  # Falling down
                        if rect.bottom > tile_rect.top and rect.bottom <= tile_rect.bottom:
                            rect.bottom = tile_rect.top
                            character_velocity_y = 0
                            is_jumping = False
                    elif character_velocity_y < 0:  # Jumping up
                        if rect.top < tile_rect.bottom and rect.top >= tile_rect.top:
                            rect.top = tile_rect.bottom
                            character_velocity_y = 0

                elif tile_id == 2:  # Jump-through tile
                    # Handle collision if falling onto the tile
                    if character_velocity_y > 0 and rect.bottom > tile_rect.top and rect.bottom <= tile_rect.bottom:
                        rect.bottom = tile_rect.top
                        character_velocity_y = 0
                        is_jumping = False

    # Check horizontal collisions after vertical collisions are resolved
    for y in range(max(0, rect.top // tile_size), min(len(map_layout), (rect.bottom // tile_size) + 1)):
        for x in range(max(0, rect.left // tile_size), min(len(map_layout[y]), (rect.right // tile_size) + 1)):
            tile_id = map_layout[y][x]
            tile_rect = pygame.Rect(x * tile_size, y * tile_size, tile_size, tile_size)

            if rect.colliderect(tile_rect):
                if tile_id == 1:  # Solid tile
                    # Handle horizontal collisions
                    if character_velocity_x > 0:  # Moving right
                        if rect.right > tile_rect.left and rect.right <= tile_rect.right:
                            rect.right = tile_rect.left
                            character_velocity_x = 0
                    elif character_velocity_x < 0:  # Moving left
                        if rect.left < tile_rect.right and rect.left >= tile_rect.left:
                            rect.left = tile_rect.right

# Networking setup
player_name, server_ip = get_user_input_pygame()

try:
    HOST, PORT = server_ip.split(':')
    PORT = int(PORT) if PORT else 25566
except ValueError:
    HOST = server_ip
    PORT = 25566
except TypeError:
    HOST = 'localhost'
    PORT = 25566
except Exception as e:
    print(f"An error occurred: {e}")
    HOST = 'localhost'
    PORT = 25566

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
    client.connect((HOST, PORT))
    # Send initial player data to the server
    send_data({'name': player_name, 'position': (character.x, character.y)})
except socket.error as e:
    print(f"Unable to connect to the server: {e}")
    sys.exit()

def receive_data():
    global players, map_layout, running
    while running:
        try:
            data = receive_message(client)
            players = data.get('players', {})
            map_layout = data.get('map_layout', [])
        except Exception as e:
            print(f"Disconnected from server: {e}")
            running = False
            break

# Start a thread to receive data from the server
running = True
threading.Thread(target=receive_data, daemon=True).start()

# Bullet class to manage bullets
class Bullet:
    def __init__(self, owner, position, direction):
        self.owner = owner  # Tuple (IP, PORT)
        self.x, self.y = position
        self.direction = direction  # 1 for right, -1 for left
        self.speed = 15
        self.rect = pygame.Rect(self.x, self.y, 10, 5)

    def move(self):
        self.x += self.speed * self.direction
        self.rect.x = self.x

    def draw(self, surface):
        pygame.draw.rect(surface, RED, self.rect)

# List to store bullets
bullets = []

# Main game loop
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    # Get keys pressed
    keys = pygame.key.get_pressed()

    # Handle horizontal movement
    if keys[pygame.K_LEFT]:
        character_velocity_x = -SPEED
    elif keys[pygame.K_RIGHT]:
        character_velocity_x = SPEED
    else:
        character_velocity_x *= FRICTION  # Apply friction when no key is pressed

    # Handle jumping
    if (keys[pygame.K_SPACE] or keys[pygame.K_UP] or keys[pygame.K_w]) and not is_jumping and is_on_ground(character):
        character_velocity_y = -JUMP_STRENGTH
        is_jumping = True

    # Handle shooting
    if keys[pygame.K_f]:
        # Determine shooting direction based on movement
        if character_velocity_x >= 0:
            direction = 1  # Right
        else:
            direction = -1  # Left
        # Create a bullet at the front of the character
        bullet_x = character.x + character.width if direction == 1 else character.x - 10
        bullet_y = character.y + character.height // 2
        bullets.append(Bullet((client.getsockname()[0], client.getsockname()[1]), (bullet_x, bullet_y), direction))
        # Prevent continuous firing by adding a short delay
        pygame.time.delay(10)

    # Apply gravity
    character_velocity_y += GRAVITY
    character.x += character_velocity_x
    character.y += character_velocity_y

    # Check and handle collisions with tiles
    handle_tile_collisions(character)

    # Ensure character stays within screen bounds
    if character.left < 0:
        character.left = 0
    if character.right > 1280:
        character.right = 1280
    if character.top < 0:
        character.top = 0
    if character.bottom > 720:
        character.bottom = 720

    # Update bullets
    for bullet in bullets[:]:
        bullet.move()
        # Remove bullets that are off-screen
        if bullet.x < 0 or bullet.x > 1280:
            bullets.remove(bullet)

    # Send character position and bullets to server
    try:
        # Serialize bullet data
        bullet_data = [{'x': b.x, 'y': b.y, 'direction': b.direction} for b in bullets]
        send_data({'name': player_name, 'position': (character.x, character.y), 'bullets': bullet_data})
    except socket.error as e:
        print(f"Failed to send data to server: {e}")
        running = False

    # Draw everything
    screen.fill(BLUE)

    # Draw the map layout
    for y, row in enumerate(map_layout):
        for x, tile_id in enumerate(row):
            tile_image = tile_images.get(tile_id)
            if tile_image:
                screen.blit(tile_image, (x * tile_size, y * tile_size))

    # Draw all players
    for addr, info in players.items():
        pos = info['position']
        color = info['color']
        health = info.get('health', 100)
        player_rect = pygame.Rect(pos[0], pos[1], 50, 50)
        pygame.draw.rect(screen, color, player_rect)
        font = pygame.font.Font(None, 24)
        text_surface = font.render(info['name'], True, BLACK)
        screen.blit(text_surface, (pos[0], pos[1] - 20))  # Display name above player

        # Draw health bar
        health_bar_width = 50
        health_bar_height = 5
        health_ratio = max(health / 100, 0)
        pygame.draw.rect(screen, RED, (pos[0], pos[1] - 10, health_bar_width, health_bar_height))
        pygame.draw.rect(screen, GREEN, (pos[0], pos[1] - 10, health_bar_width * health_ratio, health_bar_height))

    # Draw all bullets
    server_bullets = players.get((client.getsockname()[0], client.getsockname()[1]), {}).get('bullets', [])
    for addr, info in players.items():
        for b in info.get('bullets', []):
            bullet_rect = pygame.Rect(b['x'], b['y'], 10, 5)
            pygame.draw.rect(screen, RED, bullet_rect)

    # Draw local bullets
    for bullet in bullets:
        bullet.draw(screen)

    # Update the display
    pygame.display.flip()

    # Cap the frame rate
    pygame.time.Clock().tick(60)

pygame.quit()


server.py

import random
import socket
import threading
import pickle
import struct
import time

import pygame

# Server settings
HOST = '0.0.0.0'  # Listen on all interfaces
PORT = 25566

# Store player positions, colors, health, and bullets
players = {}

# List to keep track of connections
connections = []

# Tile size
tile_size = 72  # Updated to match client

# Define the map layouts
map_layouts = [
    [
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 1, 1, 1, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 1, 1, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]  # Ground layer
    ],
    # Add additional predefined map layouts here
    [
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 1, 1, 0, 1, 0, 0, 0, 0, 2, 0, 2, 0, 1, 1, 1, 0, 0, 1, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]  # Ground layer
    ]
]

current_map_layout = map_layouts[0]

# Networking functions
def send_data(conn, data):
    """Serialize and send data to a client."""
    try:
        serialized_data = pickle.dumps(data)
        length = len(serialized_data)
        conn.sendall(struct.pack('>I', length) + serialized_data)
    except Exception as e:
        print(f"Error sending data: {e}")
        close_connection(conn)

def receive_message(conn):
    """Receive and deserialize a message from a client."""
    try:
        length_data = conn.recv(4)
        if len(length_data) < 4:
            raise Exception("Received incomplete length data")

        data_length = struct.unpack('>I', length_data)[0]
        data = b''
        while len(data) < data_length:
            packet = conn.recv(data_length - len(data))
            if not packet:
                raise Exception("Received incomplete data")
            data += packet
        return pickle.loads(data)
    except Exception as e:
        print(f"Error receiving message: {e}")
        close_connection(conn)
        raise

def handle_client(conn, addr):
    print(f"Connected by {addr}")
    try:
        initial_data = receive_message(conn)
        player_name = initial_data.get('name', 'Unknown')
        spawn_position = get_random_spawn_position()
        players[addr] = {
            'name': player_name, 
            'position': spawn_position, 
            'color': generate_color(),
            'health': 100,
            'bullets': []
        }

        # Send initial data to the new client
        send_data(conn, {'players': players, 'map_layout': current_map_layout})

        # Broadcast to all clients about the new player
        broadcast({'players': players, 'map_layout': current_map_layout}, exclude=conn)

        while True:
            data = receive_message(conn)
            if not data:
                break
            # Update player position
            players[addr]['position'] = data['position']

            # Handle bullets from this player
            incoming_bullets = data.get('bullets', [])
            for b in incoming_bullets:
                bullet = {
                    'owner': addr,
                    'x': b['x'],
                    'y': b['y'],
                    'direction': b['direction']
                }
                players[addr]['bullets'].append(bullet)

            # Update bullets positions and handle collisions
            update_bullets()

            # Remove bullets that have hit or are out of bounds
            for player_addr in players:
                bullets_to_remove = []
                for bullet in players[player_addr]['bullets']:
                    # Move bullet
                    bullet['x'] += bullet['direction'] * 15
                    # Check if bullet is out of bounds
                    if bullet['x'] < 0 or bullet['x'] > 1280:
                        bullets_to_remove.append(bullet)
                        continue
                    # Check collision with other players
                    for other_addr, info in players.items():
                        if other_addr != player_addr:
                            other_rect = pygame.Rect(info['position'][0], info['position'][1], 50, 50)
                            bullet_rect = pygame.Rect(bullet['x'], bullet['y'], 10, 5)
                            if bullet_rect.colliderect(other_rect):
                                # Decrease health
                                info['health'] -= 10
                                print(f"{info['name']} was hit by {players[player_addr]['name']}! Health: {info['health']}")
                                # Remove the bullet
                                bullets_to_remove.append(bullet)
                                # Check if player is dead
                                if info['health'] <= 0:
                                    print(f"{info['name']} has been eliminated!")
                                    # Reset player position and health
                                    info['position'] = get_random_spawn_position()
                                    info['health'] = 100
                                break
                # Remove bullets
                for bullet in bullets_to_remove:
                    players[player_addr]['bullets'].remove(bullet)

            # Update all clients
            broadcast({'players': players, 'map_layout': current_map_layout}, exclude=None)

    except Exception as e:
        print(f"Exception in handle_client: {e}")
    finally:
        disconnect_client(conn, addr)

def broadcast(data, exclude=None):
    """Send data to all clients except the specified one."""
    for player_conn in connections:
        if player_conn != exclude:
            try:
                send_data(player_conn, data)
            except Exception as e:
                print(f"Error broadcasting data: {e}")
                close_connection(player_conn)

def get_random_spawn_position():
    x = random.randint(0, len(current_map_layout[0]) - 1)
    y = random.randint(0, len(current_map_layout) - 1)
    return x * tile_size, y * tile_size

def generate_color():
    return (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))

def change_map_layout_periodically():
    """Change the map layout every 60 seconds."""
    global current_map_layout
    map_index = 0
    while True:
        time.sleep(60)
        current_map_layout = map_layouts[map_index]
        map_index = (map_index + 1) % len(map_layouts)
        print("Map layout changed.")
        broadcast({'players': players, 'map_layout': current_map_layout})

# Connection handling
def close_connection(conn):
    """Close the connection and remove it from the active list."""
    if conn in connections:
        connections.remove(conn)
    conn.close()

def disconnect_client(conn, addr):
    """Remove a client from the player list and notify others."""
    print(f"Disconnected by {addr}")
    if addr in players:
        del players[addr]
    close_connection(conn)
    broadcast({'players': players, 'map_layout': current_map_layout})

# Core game functions
def update_bullets():
    """Update bullet positions and handle collisions (optional if handled differently)."""
    pass  # Already handled in handle_client

# Server setup
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((HOST, PORT))
server.listen()
print("Server started, waiting for connections...")

# Start the map layout change thread
threading.Thread(target=change_map_layout_periodically, daemon=True).start()

while True:
    conn, addr = server.accept()
    connections.append(conn)
    threading.Thread(target=handle_client, args=(conn, addr), daemon=True).start()