Updated Bots and map system
This commit is contained in:
parent
9b6defebe8
commit
01377eada9
167
client.py
167
client.py
@ -8,16 +8,12 @@ import math
|
||||
|
||||
HOST = '127.0.0.1'
|
||||
PORT = 65432
|
||||
|
||||
# Racing physics constants.
|
||||
ACCELERATION = 300.0 # pixels per second^2
|
||||
BRAKE_DECELERATION = 400.0 # pixels per second^2
|
||||
FRICTION = 200.0 # pixels per second^2
|
||||
TURN_SPEED = math.radians(120) # radians per second (e.g., 120° per second)
|
||||
MAX_SPEED = 400.0 # maximum forward speed
|
||||
MIN_SPEED = -200.0 # maximum reverse speed
|
||||
|
||||
# Dimensions for the car.
|
||||
ACCELERATION = 300.0
|
||||
BRAKE_DECELERATION = 400.0
|
||||
FRICTION = 200.0
|
||||
TURN_SPEED = math.radians(120)
|
||||
MAX_SPEED = 400.0
|
||||
MIN_SPEED = -200.0
|
||||
CAR_WIDTH = 40
|
||||
CAR_HEIGHT = 20
|
||||
|
||||
@ -48,23 +44,25 @@ def recv_event(sock):
|
||||
data = recvall(sock, msg_len)
|
||||
return pickle.loads(data)
|
||||
|
||||
# Global state.
|
||||
players = {} # client_id -> {"username", "x", "y", "angle", "speed"}
|
||||
my_client_id = None # Our unique client id from the server.
|
||||
# Predicted state for our own car.
|
||||
players = {} # client_id -> state from server
|
||||
my_client_id = None
|
||||
predicted_state = {
|
||||
"x": 100, "y": 100,
|
||||
"angle": 0, # Facing right initially.
|
||||
"x": 100,
|
||||
"y": 100,
|
||||
"angle": 0,
|
||||
"speed": 0,
|
||||
"keys": set(),
|
||||
"username": "Guest"
|
||||
"username": "Guest",
|
||||
"checkpoint_index": 0,
|
||||
"finished": False
|
||||
}
|
||||
ping = 0 # measured ping in seconds
|
||||
server_details = {} # Updated from server_info and state_update events.
|
||||
map_obstacles = [] # Map obstacles from server info.
|
||||
server_details = {}
|
||||
map_data = {} # Will hold the map (roads, obstacles, checkpoints, finish lines)
|
||||
ping = 0
|
||||
local_keys = set()
|
||||
|
||||
def listen_to_server(sock):
|
||||
global my_client_id, players, predicted_state, ping, server_details, map_obstacles
|
||||
global my_client_id, players, predicted_state, ping, server_details, map_data
|
||||
while True:
|
||||
try:
|
||||
event = recv_event(sock)
|
||||
@ -76,40 +74,21 @@ def listen_to_server(sock):
|
||||
print(f"[INFO] My client ID: {my_client_id}")
|
||||
elif event.name == "server_info":
|
||||
server_details = event.payload
|
||||
map_obstacles = server_details.get("map", [])
|
||||
map_data = server_details.get("map", {})
|
||||
print(f"[INFO] Received server info: {server_details}")
|
||||
elif event.name == "client_connect":
|
||||
print(f"[INFO] Client connected: {event.payload.get('client_id')}")
|
||||
elif event.name == "client_disconnect":
|
||||
cid = event.payload.get("client_id")
|
||||
print(f"[INFO] Client disconnected: {cid}")
|
||||
if cid in players:
|
||||
del players[cid]
|
||||
elif event.name == "state_update":
|
||||
# Expect players to have "x", "y", "angle", "speed", "username".
|
||||
players = event.payload.get("players", {})
|
||||
# Update dynamic server details.
|
||||
if "total_users" in event.payload:
|
||||
server_details["total_users"] = event.payload["total_users"]
|
||||
if "max_users" in event.payload:
|
||||
server_details["max_users"] = event.payload["max_users"]
|
||||
if my_client_id and my_client_id in players:
|
||||
server_x = players[my_client_id].get("x", 100)
|
||||
server_y = players[my_client_id].get("y", 100)
|
||||
server_angle = players[my_client_id].get("angle", 0)
|
||||
server_speed = players[my_client_id].get("speed", 0)
|
||||
tickrate = server_details.get("tickrate", 64)
|
||||
correction_factor = 0.1 * (64 / tickrate)
|
||||
if not predicted_state["keys"]:
|
||||
predicted_state["x"] = server_x
|
||||
predicted_state["y"] = server_y
|
||||
predicted_state["angle"] = server_angle
|
||||
predicted_state["speed"] = server_speed
|
||||
else:
|
||||
predicted_state["x"] += (server_x - predicted_state["x"]) * correction_factor
|
||||
predicted_state["y"] += (server_y - predicted_state["y"]) * correction_factor
|
||||
predicted_state["angle"] += (server_angle - predicted_state["angle"]) * correction_factor
|
||||
predicted_state["speed"] += (server_speed - predicted_state["speed"]) * correction_factor
|
||||
elif event.name == "race_reset":
|
||||
predicted_state.update({
|
||||
"x": 100,
|
||||
"y": 100,
|
||||
"angle": 0,
|
||||
"speed": 0,
|
||||
"keys": set(),
|
||||
"checkpoint_index": 0,
|
||||
"finished": False
|
||||
})
|
||||
print("[RACE] Race reset by server.")
|
||||
elif event.name == "pong":
|
||||
sent_time = event.payload.get("timestamp")
|
||||
if sent_time:
|
||||
@ -120,26 +99,16 @@ def listen_to_server(sock):
|
||||
print(f"[ERROR] {e}")
|
||||
break
|
||||
|
||||
def send_message(sock, event):
|
||||
send_event(sock, event)
|
||||
|
||||
def ping_loop(sock):
|
||||
while True:
|
||||
ts = time.time()
|
||||
send_message(sock, Event("ping", {"timestamp": ts}))
|
||||
send_event(sock, Event("ping", {"timestamp": ts}))
|
||||
time.sleep(1)
|
||||
|
||||
def draw_car(surface, x, y, angle, color):
|
||||
# Define a simple rectangle for the car.
|
||||
half_w = CAR_WIDTH / 2
|
||||
half_h = CAR_HEIGHT / 2
|
||||
# Corners of the rectangle before rotation.
|
||||
corners = [
|
||||
(-half_w, -half_h),
|
||||
(half_w, -half_h),
|
||||
(half_w, half_h),
|
||||
(-half_w, half_h)
|
||||
]
|
||||
corners = [(-half_w, -half_h), (half_w, -half_h), (half_w, half_h), (-half_w, half_h)]
|
||||
rotated = []
|
||||
cos_a = math.cos(angle)
|
||||
sin_a = math.sin(angle)
|
||||
@ -160,22 +129,19 @@ def main():
|
||||
threading.Thread(target=listen_to_server, args=(sock,), daemon=True).start()
|
||||
threading.Thread(target=ping_loop, args=(sock,), daemon=True).start()
|
||||
|
||||
# Register for events.
|
||||
for event_name in ["client_connect", "client_disconnect", "state_update"]:
|
||||
send_message(sock, Event("register_event", {"event": event_name}))
|
||||
for event_name in ["client_connect", "client_disconnect", "state_update", "race_reset"]:
|
||||
send_event(sock, Event("register_event", {"event": event_name}))
|
||||
print(f"[INFO] Registered for event: {event_name}")
|
||||
|
||||
send_message(sock, Event("set_username", {"username": username}))
|
||||
send_event(sock, Event("set_username", {"username": username}))
|
||||
|
||||
pygame.init()
|
||||
screen = pygame.display.set_mode((800, 600))
|
||||
pygame.display.set_caption("Top Down Racing Multiplayer")
|
||||
clock = pygame.time.Clock()
|
||||
|
||||
local_keys = set()
|
||||
|
||||
while True:
|
||||
dt = clock.tick(60) / 1000.0 # Delta time in seconds
|
||||
dt = clock.tick(60) / 1000.0
|
||||
|
||||
for ev in pygame.event.get():
|
||||
if ev.type == pygame.QUIT:
|
||||
@ -194,7 +160,7 @@ def main():
|
||||
if key_name and key_name not in local_keys:
|
||||
local_keys.add(key_name)
|
||||
predicted_state["keys"].add(key_name)
|
||||
send_message(sock, Event("keydown", {"key": key_name}))
|
||||
send_event(sock, Event("keydown", {"key": key_name}))
|
||||
elif ev.type == pygame.KEYUP:
|
||||
key_name = None
|
||||
if ev.key == pygame.K_LEFT:
|
||||
@ -209,16 +175,13 @@ def main():
|
||||
local_keys.remove(key_name)
|
||||
if key_name in predicted_state["keys"]:
|
||||
predicted_state["keys"].remove(key_name)
|
||||
send_message(sock, Event("keyup", {"key": key_name}))
|
||||
send_event(sock, Event("keyup", {"key": key_name}))
|
||||
|
||||
# Racing physics update.
|
||||
# Acceleration/Braking.
|
||||
if "up" in predicted_state["keys"]:
|
||||
predicted_state["speed"] += ACCELERATION * dt
|
||||
elif "down" in predicted_state["keys"]:
|
||||
predicted_state["speed"] -= BRAKE_DECELERATION * dt
|
||||
else:
|
||||
# Apply friction to gradually slow the car.
|
||||
if predicted_state["speed"] > 0:
|
||||
predicted_state["speed"] -= FRICTION * dt
|
||||
if predicted_state["speed"] < 0:
|
||||
@ -228,76 +191,74 @@ def main():
|
||||
if predicted_state["speed"] > 0:
|
||||
predicted_state["speed"] = 0
|
||||
|
||||
# Clamp speed.
|
||||
if predicted_state["speed"] > MAX_SPEED:
|
||||
predicted_state["speed"] = MAX_SPEED
|
||||
if predicted_state["speed"] < MIN_SPEED:
|
||||
predicted_state["speed"] = MIN_SPEED
|
||||
|
||||
# Steering.
|
||||
if "left" in predicted_state["keys"]:
|
||||
predicted_state["angle"] -= TURN_SPEED * dt
|
||||
if "right" in predicted_state["keys"]:
|
||||
predicted_state["angle"] += TURN_SPEED * dt
|
||||
|
||||
# Update position based on current speed and angle.
|
||||
predicted_state["x"] += predicted_state["speed"] * math.cos(predicted_state["angle"]) * dt
|
||||
predicted_state["y"] += predicted_state["speed"] * math.sin(predicted_state["angle"]) * dt
|
||||
|
||||
# Camera follows the local car.
|
||||
local_x = predicted_state["x"]
|
||||
local_y = predicted_state["y"]
|
||||
screen_width, screen_height = 800, 600
|
||||
camera_offset = (local_x - screen_width // 2, local_y - screen_height // 2)
|
||||
|
||||
# Drawing.
|
||||
screen.fill((30, 30, 30)) # Background.
|
||||
screen.fill((30, 30, 30))
|
||||
if server_details.get("map"):
|
||||
mdata = server_details["map"]
|
||||
if "road" in mdata:
|
||||
for road in mdata["road"]:
|
||||
rect = pygame.Rect(road["x"] - camera_offset[0], road["y"] - camera_offset[1], road["width"], road["height"])
|
||||
pygame.draw.rect(screen, (80,80,80), rect)
|
||||
if "obstacle" in mdata:
|
||||
for obs in mdata["obstacle"]:
|
||||
rect = pygame.Rect(obs["x"] - camera_offset[0], obs["y"] - camera_offset[1], obs["width"], obs["height"])
|
||||
pygame.draw.rect(screen, (150,50,50), rect)
|
||||
if "checkpoint" in mdata:
|
||||
for cp in mdata["checkpoint"]:
|
||||
rect = pygame.Rect(cp["x"] - camera_offset[0], cp["y"] - camera_offset[1], cp["width"], cp["height"])
|
||||
pygame.draw.rect(screen, (200,200,0), rect, 2)
|
||||
if "finish_line" in mdata:
|
||||
for fl in mdata["finish_line"]:
|
||||
rect = pygame.Rect(fl["x"] - camera_offset[0], fl["y"] - camera_offset[1], fl["width"], fl["height"])
|
||||
pygame.draw.rect(screen, (0,0,255), rect, 2)
|
||||
|
||||
# Draw map obstacles (if any).
|
||||
for obs in map_obstacles:
|
||||
rect = pygame.Rect(obs["x"] - camera_offset[0], obs["y"] - camera_offset[1],
|
||||
obs["width"], obs["height"])
|
||||
pygame.draw.rect(screen, (100, 100, 100), rect)
|
||||
|
||||
# Draw players as cars.
|
||||
for cid, data in players.items():
|
||||
# Get car position and orientation.
|
||||
px = data.get("x", 100)
|
||||
py = data.get("y", 100)
|
||||
angle = data.get("angle", 0)
|
||||
uname = data.get("username", "Guest")
|
||||
# For our own car, use the predicted state.
|
||||
if cid == my_client_id:
|
||||
px = predicted_state["x"]
|
||||
py = predicted_state["y"]
|
||||
angle = predicted_state["angle"]
|
||||
color = (0, 0, 255) # Blue for local player.
|
||||
color = (0, 0, 255)
|
||||
else:
|
||||
color = (255, 0, 0) # Red for others.
|
||||
draw_x = px - camera_offset[0]
|
||||
draw_y = py - camera_offset[1]
|
||||
draw_car(screen, draw_x, draw_y, angle, color)
|
||||
# Draw username above the car.
|
||||
color = (255, 0, 0)
|
||||
draw_car(screen, px - camera_offset[0], py - camera_offset[1], angle, color)
|
||||
font = pygame.font.SysFont(None, 20)
|
||||
name_surface = font.render(uname, True, (255, 255, 255))
|
||||
screen.blit(name_surface, (draw_x - name_surface.get_width() // 2, draw_y - 30))
|
||||
screen.blit(name_surface, (px - camera_offset[0] - name_surface.get_width()//2, py - camera_offset[1] - 30))
|
||||
|
||||
# Display ping and HUD info.
|
||||
ping_font = pygame.font.SysFont(None, 24)
|
||||
ping_surface = ping_font.render(f"Ping: {int(ping * 1000)} ms", True, (255, 255, 0))
|
||||
ping_surface = ping_font.render(f"Ping: {int(ping*1000)} ms", True, (255, 255, 0))
|
||||
screen.blit(ping_surface, (10, 10))
|
||||
|
||||
hud_font = pygame.font.SysFont(None, 24)
|
||||
total_users = server_details.get("total_users", len(players))
|
||||
max_users = server_details.get("max_users", "?")
|
||||
tickrate = server_details.get("tickrate", 64)
|
||||
hud_text = hud_font.render(
|
||||
f"Speed: {int(predicted_state['speed'])} px/s | Users: {total_users}/{max_users} | Client ID: {my_client_id or 'N/A'} | Tickrate: {tickrate} tps",
|
||||
f"Users: {total_users}/{max_users} | Client ID: {my_client_id or 'N/A'} | Tickrate: {tickrate} tps",
|
||||
True,
|
||||
(255, 255, 255)
|
||||
)
|
||||
screen.blit(hud_text, (10, 40))
|
||||
|
||||
pygame.display.flip()
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
172
map.json
Normal file
172
map.json
Normal file
@ -0,0 +1,172 @@
|
||||
{
|
||||
"obstacle": [
|
||||
{
|
||||
"x": -85,
|
||||
"y": 4,
|
||||
"width": 93,
|
||||
"height": 856
|
||||
},
|
||||
{
|
||||
"x": 4,
|
||||
"y": 792,
|
||||
"width": 1012,
|
||||
"height": 21
|
||||
},
|
||||
{
|
||||
"x": 982,
|
||||
"y": -8,
|
||||
"width": 48,
|
||||
"height": 804
|
||||
},
|
||||
{
|
||||
"x": -78,
|
||||
"y": -82,
|
||||
"width": 1068,
|
||||
"height": 94
|
||||
},
|
||||
{
|
||||
"x": 240,
|
||||
"y": 12,
|
||||
"width": 10,
|
||||
"height": 687
|
||||
},
|
||||
{
|
||||
"x": 427,
|
||||
"y": 82,
|
||||
"width": 9,
|
||||
"height": 711
|
||||
},
|
||||
{
|
||||
"x": 622,
|
||||
"y": 10,
|
||||
"width": 10,
|
||||
"height": 709
|
||||
},
|
||||
{
|
||||
"x": 791,
|
||||
"y": 91,
|
||||
"width": 20,
|
||||
"height": 706
|
||||
}
|
||||
],
|
||||
"road": [
|
||||
{
|
||||
"x": 858,
|
||||
"y": 75,
|
||||
"width": 69,
|
||||
"height": 585
|
||||
},
|
||||
{
|
||||
"x": 655,
|
||||
"y": 21,
|
||||
"width": 266,
|
||||
"height": 48
|
||||
},
|
||||
{
|
||||
"x": 658,
|
||||
"y": 45,
|
||||
"width": 88,
|
||||
"height": 734
|
||||
},
|
||||
{
|
||||
"x": 465,
|
||||
"y": 730,
|
||||
"width": 281,
|
||||
"height": 49
|
||||
},
|
||||
{
|
||||
"x": 464,
|
||||
"y": 47,
|
||||
"width": 117,
|
||||
"height": 717
|
||||
},
|
||||
{
|
||||
"x": 271,
|
||||
"y": 23,
|
||||
"width": 309,
|
||||
"height": 45
|
||||
},
|
||||
{
|
||||
"x": 273,
|
||||
"y": 56,
|
||||
"width": 111,
|
||||
"height": 727
|
||||
},
|
||||
{
|
||||
"x": 38,
|
||||
"y": 707,
|
||||
"width": 346,
|
||||
"height": 76
|
||||
},
|
||||
{
|
||||
"x": 41,
|
||||
"y": 71,
|
||||
"width": 144,
|
||||
"height": 705
|
||||
},
|
||||
{
|
||||
"x": 858,
|
||||
"y": 43,
|
||||
"width": 64,
|
||||
"height": 59
|
||||
}
|
||||
],
|
||||
"checkpoint": [
|
||||
{
|
||||
"x": 32,
|
||||
"y": 371,
|
||||
"width": 160,
|
||||
"height": 10
|
||||
},
|
||||
{
|
||||
"x": 263,
|
||||
"y": 370,
|
||||
"width": 130,
|
||||
"height": 11
|
||||
},
|
||||
{
|
||||
"x": 462,
|
||||
"y": 17,
|
||||
"width": 47,
|
||||
"height": 53
|
||||
},
|
||||
{
|
||||
"x": 462,
|
||||
"y": 367,
|
||||
"width": 120,
|
||||
"height": 18
|
||||
},
|
||||
{
|
||||
"x": 617,
|
||||
"y": 727,
|
||||
"width": 21,
|
||||
"height": 53
|
||||
},
|
||||
{
|
||||
"x": 666,
|
||||
"y": 355,
|
||||
"width": 88,
|
||||
"height": 11
|
||||
},
|
||||
{
|
||||
"x": 796,
|
||||
"y": 21,
|
||||
"width": 6,
|
||||
"height": 51
|
||||
},
|
||||
{
|
||||
"x": 855,
|
||||
"y": 363,
|
||||
"width": 75,
|
||||
"height": 15
|
||||
}
|
||||
],
|
||||
"finish_line": [
|
||||
{
|
||||
"x": 844,
|
||||
"y": 667,
|
||||
"width": 108,
|
||||
"height": 106
|
||||
}
|
||||
]
|
||||
}
|
119
map.py
Normal file
119
map.py
Normal file
@ -0,0 +1,119 @@
|
||||
import pygame
|
||||
import json
|
||||
import sys
|
||||
|
||||
# Editor settings.
|
||||
SCREEN_WIDTH, SCREEN_HEIGHT = 1000, 800
|
||||
BACKGROUND_COLOR = (30, 30, 30)
|
||||
FPS = 60
|
||||
|
||||
# Colors for different element types.
|
||||
COLORS = {
|
||||
"obstacle": (150, 50, 50),
|
||||
"road": (100, 100, 100),
|
||||
"checkpoint": (200, 200, 0),
|
||||
"finish_line": (0, 0, 255)
|
||||
}
|
||||
|
||||
# Element types.
|
||||
ELEMENT_TYPES = ["obstacle", "road", "checkpoint", "finish_line"]
|
||||
|
||||
def main():
|
||||
pygame.init()
|
||||
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
|
||||
pygame.display.set_caption("Advanced Map Editor: Roads, Checkpoints & Finish Line")
|
||||
clock = pygame.time.Clock()
|
||||
font = pygame.font.SysFont(None, 24)
|
||||
|
||||
# A list to store drawn elements as tuples: (type, rect dict)
|
||||
elements = []
|
||||
current_type = "obstacle" # Default drawing type.
|
||||
|
||||
drawing = False
|
||||
start_pos = (0, 0)
|
||||
current_rect = None
|
||||
|
||||
instructions = [
|
||||
"Press O: Obstacle, R: Road, C: Checkpoint, F: Finish Line",
|
||||
"Click & drag to draw. Press S to save, U to undo last element, ESC to exit."
|
||||
]
|
||||
|
||||
while True:
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
pygame.quit()
|
||||
sys.exit()
|
||||
elif event.type == pygame.KEYDOWN:
|
||||
if event.key == pygame.K_ESCAPE:
|
||||
pygame.quit()
|
||||
sys.exit()
|
||||
elif event.key == pygame.K_o:
|
||||
current_type = "obstacle"
|
||||
elif event.key == pygame.K_r:
|
||||
current_type = "road"
|
||||
elif event.key == pygame.K_c:
|
||||
current_type = "checkpoint"
|
||||
elif event.key == pygame.K_f:
|
||||
current_type = "finish_line"
|
||||
elif event.key == pygame.K_s:
|
||||
# Organize elements by type.
|
||||
map_data = {etype: [] for etype in ELEMENT_TYPES}
|
||||
for etype, rect in elements:
|
||||
map_data[etype].append(rect)
|
||||
with open('map.json', 'w') as f:
|
||||
json.dump(map_data, f, indent=4)
|
||||
print("Map saved to map.json")
|
||||
elif event.key == pygame.K_u:
|
||||
if elements:
|
||||
removed = elements.pop()
|
||||
print(f"Removed last element of type {removed[0]}")
|
||||
elif event.type == pygame.MOUSEBUTTONDOWN:
|
||||
if event.button == 1: # Left click starts drawing.
|
||||
drawing = True
|
||||
start_pos = event.pos
|
||||
elif event.type == pygame.MOUSEBUTTONUP:
|
||||
if event.button == 1 and drawing:
|
||||
drawing = False
|
||||
end_pos = event.pos
|
||||
x = min(start_pos[0], end_pos[0])
|
||||
y = min(start_pos[1], end_pos[1])
|
||||
width = abs(end_pos[0] - start_pos[0])
|
||||
height = abs(end_pos[1] - start_pos[1])
|
||||
if width > 5 and height > 5:
|
||||
rect_dict = {"x": x, "y": y, "width": width, "height": height}
|
||||
elements.append((current_type, rect_dict))
|
||||
current_rect = None
|
||||
elif event.type == pygame.MOUSEMOTION:
|
||||
if drawing:
|
||||
end_pos = event.pos
|
||||
x = min(start_pos[0], end_pos[0])
|
||||
y = min(start_pos[1], end_pos[1])
|
||||
width = abs(end_pos[0] - start_pos[0])
|
||||
height = abs(end_pos[1] - start_pos[1])
|
||||
current_rect = pygame.Rect(x, y, width, height)
|
||||
|
||||
screen.fill(BACKGROUND_COLOR)
|
||||
|
||||
# Draw saved elements.
|
||||
for etype, rect_dict in elements:
|
||||
rect = pygame.Rect(rect_dict["x"], rect_dict["y"], rect_dict["width"], rect_dict["height"])
|
||||
pygame.draw.rect(screen, COLORS.get(etype, (255,255,255)), rect)
|
||||
|
||||
# Draw current preview rectangle.
|
||||
if current_rect:
|
||||
pygame.draw.rect(screen, COLORS.get(current_type, (255,255,255)), current_rect, 2)
|
||||
|
||||
# Display instructions.
|
||||
y_offset = 10
|
||||
for line in instructions:
|
||||
surf = font.render(line, True, (255,255,255))
|
||||
screen.blit(surf, (10, y_offset))
|
||||
y_offset += 24
|
||||
type_text = font.render(f"Current Element Type: {current_type.upper()}", True, (255,255,255))
|
||||
screen.blit(type_text, (10, y_offset))
|
||||
|
||||
pygame.display.flip()
|
||||
clock.tick(FPS)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
433
server.py
433
server.py
@ -4,30 +4,50 @@ import pickle
|
||||
import time
|
||||
import uuid
|
||||
import math
|
||||
import json
|
||||
import pygame # For the dashboard
|
||||
|
||||
# --- Configuration ---
|
||||
HOST = '127.0.0.1'
|
||||
PORT = 65432
|
||||
TICKRATE = 128 # ticks per second (sub-tick simulation)
|
||||
TICKRATE = 128 # ticks per second
|
||||
MAX_USERS = 100
|
||||
|
||||
# Racing physics constants.
|
||||
ACCELERATION = 300.0 # pixels per second^2 (forward)
|
||||
BRAKE_DECELERATION = 400.0 # pixels per second^2 (when braking)
|
||||
FRICTION = 200.0 # pixels per second^2 (natural deceleration)
|
||||
TURN_SPEED = math.radians(120) # radians per second (steering)
|
||||
MAX_SPEED = 400.0 # maximum forward speed (pixels/sec)
|
||||
MIN_SPEED = -200.0 # maximum reverse speed (pixels/sec)
|
||||
ACCELERATION = 300.0 # pixels per second^2
|
||||
BRAKE_DECELERATION = 400.0 # pixels per second^2
|
||||
FRICTION = 200.0 # pixels per second^2
|
||||
TURN_SPEED = math.radians(240) # radians per second
|
||||
MAX_SPEED = 400.0 # pixels per second
|
||||
MIN_SPEED = -200.0 # pixels per second
|
||||
|
||||
# Player appearance (for collision).
|
||||
PLAYER_RADIUS = 20
|
||||
# Collision / rendering radius.
|
||||
PLAYER_RADIUS = 10
|
||||
|
||||
# Define map obstacles.
|
||||
map_obstacles = [
|
||||
{"x": 200, "y": 150, "width": 100, "height": 300},
|
||||
{"x": 500, "y": 100, "width": 50, "height": 400},
|
||||
{"x": 100, "y": 500, "width": 600, "height": 50},
|
||||
]
|
||||
# --- Sensor Configuration for Bots ---
|
||||
BOT_SENSOR_RAY_COUNT = 90
|
||||
BOT_SENSOR_FOV = math.radians(180) # Field of view of 90 degrees
|
||||
SENSOR_MAX_DISTANCE = 150 # Maximum sensor range
|
||||
|
||||
# --- Load Map from JSON ---
|
||||
try:
|
||||
with open('map.json', 'r') as f:
|
||||
map_data = json.load(f)
|
||||
print("[INFO] Loaded map.json successfully.")
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Could not load map.json: {e}")
|
||||
map_data = {
|
||||
"obstacle": [],
|
||||
"road": [],
|
||||
"checkpoint": [],
|
||||
"finish_line": []
|
||||
}
|
||||
obstacles = map_data.get("obstacle", [])
|
||||
roads = map_data.get("road", [])
|
||||
checkpoints = map_data.get("checkpoint", [])
|
||||
finish_lines = map_data.get("finish_line", [])
|
||||
|
||||
# --- Collision Helpers ---
|
||||
def circle_rect_collision(cx, cy, radius, rect):
|
||||
closest_x = max(rect["x"], min(cx, rect["x"] + rect["width"]))
|
||||
closest_y = max(rect["y"], min(cy, rect["y"] + rect["height"]))
|
||||
@ -35,6 +55,23 @@ def circle_rect_collision(cx, cy, radius, rect):
|
||||
dy = cy - closest_y
|
||||
return (dx * dx + dy * dy) < (radius * radius)
|
||||
|
||||
def rect_collision(cx, cy, rect):
|
||||
return (rect["x"] <= cx <= rect["x"] + rect["width"] and
|
||||
rect["y"] <= cy <= rect["y"] + rect["height"])
|
||||
|
||||
# --- Ray Casting for Sensors ---
|
||||
def ray_cast(x, y, angle, max_distance):
|
||||
step = 5
|
||||
for d in range(0, int(max_distance), step):
|
||||
test_x = x + d * math.cos(angle)
|
||||
test_y = y + d * math.sin(angle)
|
||||
for obs in obstacles:
|
||||
if (obs["x"] <= test_x <= obs["x"] + obs["width"] and
|
||||
obs["y"] <= test_y <= obs["y"] + obs["height"]):
|
||||
return d
|
||||
return max_distance
|
||||
|
||||
# --- Network Event Class & Helpers ---
|
||||
class Event:
|
||||
def __init__(self, name, payload):
|
||||
self.name = name
|
||||
@ -43,16 +80,22 @@ class Event:
|
||||
def recvall(sock, n):
|
||||
data = b''
|
||||
while len(data) < n:
|
||||
packet = sock.recv(n - len(data))
|
||||
try:
|
||||
packet = sock.recv(n - len(data))
|
||||
except Exception as e:
|
||||
return None
|
||||
if not packet:
|
||||
return None
|
||||
data += packet
|
||||
return data
|
||||
|
||||
def send_event(sock, event):
|
||||
data = pickle.dumps(event)
|
||||
length = len(data)
|
||||
sock.sendall(length.to_bytes(4, byteorder='big') + data)
|
||||
try:
|
||||
data = pickle.dumps(event)
|
||||
length = len(data)
|
||||
sock.sendall(length.to_bytes(4, byteorder='big') + data)
|
||||
except Exception as e:
|
||||
print(f"[ERROR] send_event: {e}")
|
||||
|
||||
def recv_event(sock):
|
||||
raw_len = recvall(sock, 4)
|
||||
@ -60,15 +103,189 @@ def recv_event(sock):
|
||||
return None
|
||||
msg_len = int.from_bytes(raw_len, byteorder='big')
|
||||
data = recvall(sock, msg_len)
|
||||
if data is None:
|
||||
return None
|
||||
return pickle.loads(data)
|
||||
|
||||
# Global dictionaries.
|
||||
# --- Global State ---
|
||||
event_subscriptions = {} # event name -> list of client sockets
|
||||
client_ids = {} # conn -> client_id
|
||||
# Each client state now stores position, angle, speed, and keys.
|
||||
client_states = {} # conn -> {"client_id", "username", "x", "y", "angle", "speed", "keys"}
|
||||
client_states = {} # conn -> state dict (for human players)
|
||||
bot_states = {} # bot_id -> state dict (with "bot": True)
|
||||
lock = threading.Lock()
|
||||
reset_in_progress = False # Global flag to avoid repeated resets
|
||||
|
||||
# --- Bot AI (Smart with Configurable Sensors) ---
|
||||
def update_bot(state, dt):
|
||||
# Determine target based on checkpoints or finish line.
|
||||
if checkpoints and state["checkpoint_index"] < len(checkpoints):
|
||||
cp = checkpoints[state["checkpoint_index"]]
|
||||
target_x = cp["x"] + cp["width"] / 2
|
||||
target_y = cp["y"] + cp["height"] / 2
|
||||
elif finish_lines:
|
||||
fl = finish_lines[0] # assume one finish line
|
||||
target_x = fl["x"] + fl["width"] / 2
|
||||
target_y = fl["y"] + fl["height"] / 2
|
||||
else:
|
||||
target_x = state["x"] + math.cos(state["angle"]) * 100
|
||||
target_y = state["y"] + math.sin(state["angle"]) * 100
|
||||
|
||||
# Incentivize staying on roads.
|
||||
def is_on_road(x, y):
|
||||
for road in roads:
|
||||
if rect_collision(x, y, road):
|
||||
return True
|
||||
return False
|
||||
if not is_on_road(state["x"], state["y"]) and roads:
|
||||
best_distance = float('inf')
|
||||
best_center = None
|
||||
for road in roads:
|
||||
center_x = road["x"] + road["width"] / 2
|
||||
center_y = road["y"] + road["height"] / 2
|
||||
d = math.hypot(center_x - state["x"], center_y - state["y"])
|
||||
if d < best_distance:
|
||||
best_distance = d
|
||||
best_center = (center_x, center_y)
|
||||
if best_center is not None:
|
||||
# Blend target: 70% checkpoint/finish and 30% road center.
|
||||
target_x = 0.7 * target_x + 0.3 * best_center[0]
|
||||
target_y = 0.7 * target_y + 0.3 * best_center[1]
|
||||
|
||||
state["target"] = (target_x, target_y) # For dashboard drawing
|
||||
|
||||
# Compute desired angle toward target.
|
||||
desired_angle_target = math.atan2(target_y - state["y"], target_x - state["x"])
|
||||
|
||||
# --- Sensor Readings ---
|
||||
sensor_readings = []
|
||||
for i in range(BOT_SENSOR_RAY_COUNT):
|
||||
if BOT_SENSOR_RAY_COUNT > 1:
|
||||
offset = -BOT_SENSOR_FOV/2 + i * (BOT_SENSOR_FOV/(BOT_SENSOR_RAY_COUNT - 1))
|
||||
else:
|
||||
offset = 0
|
||||
sensor_angle = state["angle"] + offset
|
||||
distance = ray_cast(state["x"], state["y"], sensor_angle, SENSOR_MAX_DISTANCE)
|
||||
sensor_readings.append((offset, distance))
|
||||
state["sensors"] = sensor_readings
|
||||
|
||||
# --- Compute Obstacle Avoidance Vector ---
|
||||
avoidance_x = 0
|
||||
avoidance_y = 0
|
||||
threshold = SENSOR_MAX_DISTANCE * 0.6 # Activate avoidance if obstacle is closer than 60% of max.
|
||||
for offset, distance in sensor_readings:
|
||||
if distance < threshold:
|
||||
repulsion = (threshold - distance) / threshold
|
||||
avoid_angle = state["angle"] + offset
|
||||
avoidance_x -= repulsion * math.cos(avoid_angle)
|
||||
avoidance_y -= repulsion * math.sin(avoid_angle)
|
||||
avoidance_angle = 0
|
||||
avoidance_weight = 0
|
||||
if avoidance_x != 0 or avoidance_y != 0:
|
||||
avoidance_angle = math.atan2(avoidance_y, avoidance_x)
|
||||
avoidance_weight = math.hypot(avoidance_x, avoidance_y)
|
||||
|
||||
# --- Combine Target and Avoidance ---
|
||||
w_target = 1.0
|
||||
w_avoid = avoidance_weight
|
||||
target_vec = (math.cos(desired_angle_target), math.sin(desired_angle_target))
|
||||
avoid_vec = (math.cos(avoidance_angle), math.sin(avoidance_angle)) if w_avoid > 0 else (0, 0)
|
||||
combined_x = w_target * target_vec[0] + w_avoid * avoid_vec[0]
|
||||
combined_y = w_target * target_vec[1] + w_avoid * avoid_vec[1]
|
||||
desired_angle = math.atan2(combined_y, combined_x)
|
||||
|
||||
# --- Determine Steering Keys ---
|
||||
def normalize_angle(a):
|
||||
while a > math.pi:
|
||||
a -= 2*math.pi
|
||||
while a < -math.pi:
|
||||
a += 2*math.pi
|
||||
return a
|
||||
angle_diff = normalize_angle(desired_angle - state["angle"])
|
||||
desired_keys = {"up"} # Always accelerate.
|
||||
if angle_diff > 0.1:
|
||||
desired_keys.add("right")
|
||||
elif angle_diff < -0.1:
|
||||
desired_keys.add("left")
|
||||
state["keys"] = desired_keys
|
||||
|
||||
# --- Physics Update for All Entities ---
|
||||
def update_physics(state, dt):
|
||||
if 'up' in state['keys']:
|
||||
state['speed'] += ACCELERATION * dt
|
||||
elif 'down' in state['keys']:
|
||||
state['speed'] -= BRAKE_DECELERATION * dt
|
||||
else:
|
||||
if state['speed'] > 0:
|
||||
state['speed'] -= FRICTION * dt
|
||||
if state['speed'] < 0:
|
||||
state['speed'] = 0
|
||||
elif state['speed'] < 0:
|
||||
state['speed'] += FRICTION * dt
|
||||
if state['speed'] > 0:
|
||||
state['speed'] = 0
|
||||
|
||||
if state['speed'] > MAX_SPEED:
|
||||
state['speed'] = MAX_SPEED
|
||||
if state['speed'] < MIN_SPEED:
|
||||
state['speed'] = MIN_SPEED
|
||||
|
||||
if 'left' in state['keys']:
|
||||
state['angle'] -= TURN_SPEED * dt
|
||||
if 'right' in state['keys']:
|
||||
state['angle'] += TURN_SPEED * dt
|
||||
|
||||
new_x = state['x'] + state['speed'] * math.cos(state['angle']) * dt
|
||||
new_y = state['y'] + state['speed'] * math.sin(state['angle']) * dt
|
||||
|
||||
collision = False
|
||||
for obs in obstacles:
|
||||
if circle_rect_collision(new_x, new_y, PLAYER_RADIUS, obs):
|
||||
collision = True
|
||||
break
|
||||
if not collision:
|
||||
state['x'] = new_x
|
||||
state['y'] = new_y
|
||||
else:
|
||||
state['speed'] = 0
|
||||
|
||||
# --- Checkpoints and Finish Line ---
|
||||
if "checkpoint_index" not in state:
|
||||
state["checkpoint_index"] = 0
|
||||
if "finished" not in state:
|
||||
state["finished"] = False
|
||||
|
||||
if checkpoints and not state["finished"]:
|
||||
idx = state["checkpoint_index"]
|
||||
if idx < len(checkpoints):
|
||||
cp = checkpoints[idx]
|
||||
if rect_collision(state["x"], state["y"], cp):
|
||||
state["checkpoint_index"] += 1
|
||||
print(f"[RACE] {state['username']} passed checkpoint {idx+1}")
|
||||
for fl in finish_lines:
|
||||
if rect_collision(state["x"], state["y"], fl) and not state["finished"]:
|
||||
if (checkpoints and state["checkpoint_index"] >= len(checkpoints)) or not checkpoints:
|
||||
state["finished"] = True
|
||||
print(f"[RACE] {state['username']} has finished the race!")
|
||||
|
||||
# --- Global Reset Function ---
|
||||
def reset_race():
|
||||
with lock:
|
||||
for state in list(client_states.values()) + list(bot_states.values()):
|
||||
state["x"] = 100
|
||||
state["y"] = 100
|
||||
state["angle"] = 0
|
||||
state["speed"] = 0
|
||||
state["keys"] = set()
|
||||
state["checkpoint_index"] = 0
|
||||
state["finished"] = False
|
||||
print("[RACE] Race finished. Resetting all players.")
|
||||
|
||||
def clear_reset_flag():
|
||||
global reset_in_progress
|
||||
with lock:
|
||||
reset_in_progress = False
|
||||
|
||||
# --- Network Client Handler ---
|
||||
def handle_client(conn, addr):
|
||||
client_id = str(uuid.uuid4())
|
||||
with lock:
|
||||
@ -78,9 +295,11 @@ def handle_client(conn, addr):
|
||||
"username": "Guest",
|
||||
"x": 100,
|
||||
"y": 100,
|
||||
"angle": 0, # Facing right initially.
|
||||
"angle": 0,
|
||||
"speed": 0,
|
||||
"keys": set()
|
||||
"keys": set(),
|
||||
"finished": False,
|
||||
"checkpoint_index": 0
|
||||
}
|
||||
print(f"[NEW CONNECTION] {addr} connected as {client_id}")
|
||||
|
||||
@ -88,12 +307,12 @@ def handle_client(conn, addr):
|
||||
send_event(conn, Event("self_id", {"client_id": client_id}))
|
||||
server_info = {
|
||||
"tickrate": TICKRATE,
|
||||
"total_users": len(client_states),
|
||||
"total_users": len(client_states) + len(bot_states),
|
||||
"max_users": MAX_USERS,
|
||||
"server_ip": HOST,
|
||||
"server_port": PORT,
|
||||
"server_name": "My Top Down Racing Server",
|
||||
"map": map_obstacles
|
||||
"server_name": "Advanced Top Down Racing Server",
|
||||
"map": map_data
|
||||
}
|
||||
send_event(conn, Event("server_info", server_info))
|
||||
except Exception as e:
|
||||
@ -132,7 +351,6 @@ def handle_client(conn, addr):
|
||||
elif event.name == "ping":
|
||||
timestamp = event.payload.get('timestamp')
|
||||
send_event(conn, Event("pong", {"timestamp": timestamp}))
|
||||
# Any other events (e.g., mouse_move, shoot) are ignored for racing.
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Exception with {client_ids.get(conn, 'unknown')}: {e}")
|
||||
finally:
|
||||
@ -157,59 +375,41 @@ def broadcast_event(event_name, payload):
|
||||
print(f"[ERROR] Failed to send to client, removing: {e}")
|
||||
receivers.remove(client)
|
||||
|
||||
# --- Bot Spawning ---
|
||||
def spawn_bot():
|
||||
bot_id = str(uuid.uuid4())
|
||||
bot_state = {
|
||||
"client_id": bot_id,
|
||||
"username": "Bot_" + bot_id[:4],
|
||||
"x": 150,
|
||||
"y": 150,
|
||||
"angle": 0,
|
||||
"speed": 0,
|
||||
"keys": set(),
|
||||
"finished": False,
|
||||
"checkpoint_index": 0,
|
||||
"bot": True
|
||||
}
|
||||
with lock:
|
||||
bot_states[bot_id] = bot_state
|
||||
print(f"[BOT] Spawned bot {bot_state['username']}")
|
||||
|
||||
# --- Game Loop ---
|
||||
def game_loop():
|
||||
global reset_in_progress
|
||||
dt = 1 / TICKRATE
|
||||
while True:
|
||||
time.sleep(dt)
|
||||
with lock:
|
||||
# Update each player's state using racing physics.
|
||||
for state in client_states.values():
|
||||
# Acceleration or braking.
|
||||
if 'up' in state['keys']:
|
||||
state['speed'] += ACCELERATION * dt
|
||||
elif 'down' in state['keys']:
|
||||
state['speed'] -= BRAKE_DECELERATION * dt
|
||||
else:
|
||||
# Apply friction when no acceleration is active.
|
||||
if state['speed'] > 0:
|
||||
state['speed'] -= FRICTION * dt
|
||||
if state['speed'] < 0:
|
||||
state['speed'] = 0
|
||||
elif state['speed'] < 0:
|
||||
state['speed'] += FRICTION * dt
|
||||
if state['speed'] > 0:
|
||||
state['speed'] = 0
|
||||
all_states = list(client_states.values()) + list(bot_states.values())
|
||||
for state in all_states:
|
||||
if state.get("bot", False):
|
||||
update_bot(state, dt)
|
||||
update_physics(state, dt)
|
||||
|
||||
|
||||
|
||||
# Clamp speed.
|
||||
if state['speed'] > MAX_SPEED:
|
||||
state['speed'] = MAX_SPEED
|
||||
if state['speed'] < MIN_SPEED:
|
||||
state['speed'] = MIN_SPEED
|
||||
|
||||
# Steering.
|
||||
if 'left' in state['keys']:
|
||||
state['angle'] -= TURN_SPEED * dt
|
||||
if 'right' in state['keys']:
|
||||
state['angle'] += TURN_SPEED * dt
|
||||
|
||||
# Compute new position.
|
||||
new_x = state['x'] + state['speed'] * math.cos(state['angle']) * dt
|
||||
new_y = state['y'] + state['speed'] * math.sin(state['angle']) * dt
|
||||
|
||||
# Check collision with obstacles.
|
||||
collision = False
|
||||
for obs in map_obstacles:
|
||||
if circle_rect_collision(new_x, new_y, PLAYER_RADIUS, obs):
|
||||
collision = True
|
||||
break
|
||||
if not collision:
|
||||
state['x'] = new_x
|
||||
state['y'] = new_y
|
||||
else:
|
||||
# On collision, stop the car.
|
||||
state['speed'] = 0
|
||||
|
||||
# Build payload to send to all clients.
|
||||
players_payload = {}
|
||||
for state in client_states.values():
|
||||
players_payload[state['client_id']] = {
|
||||
@ -217,9 +417,21 @@ def game_loop():
|
||||
"x": state['x'],
|
||||
"y": state['y'],
|
||||
"angle": state['angle'],
|
||||
"speed": state['speed']
|
||||
"speed": state['speed'],
|
||||
"finished": state.get("finished", False)
|
||||
}
|
||||
total_users = len(client_states)
|
||||
for state in bot_states.values():
|
||||
players_payload[state['client_id']] = {
|
||||
"username": state['username'],
|
||||
"x": state['x'],
|
||||
"y": state['y'],
|
||||
"angle": state['angle'],
|
||||
"speed": state['speed'],
|
||||
"finished": state.get("finished", False),
|
||||
"sensors": state.get("sensors", []),
|
||||
"target": state.get("target", None)
|
||||
}
|
||||
total_users = len(client_states) + len(bot_states)
|
||||
payload = {
|
||||
"players": players_payload,
|
||||
"total_users": total_users,
|
||||
@ -227,9 +439,78 @@ def game_loop():
|
||||
}
|
||||
broadcast_event("state_update", payload)
|
||||
|
||||
# --- Server Dashboard (Visualization) ---
|
||||
def server_dashboard():
|
||||
pygame.init()
|
||||
DASH_WIDTH, DASH_HEIGHT = 1200, 900
|
||||
screen = pygame.display.set_mode((DASH_WIDTH, DASH_HEIGHT))
|
||||
pygame.display.set_caption("Server Dashboard: Full Race Course View")
|
||||
clock = pygame.time.Clock()
|
||||
font = pygame.font.SysFont(None, 20)
|
||||
|
||||
while True:
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
pygame.quit()
|
||||
return
|
||||
|
||||
screen.fill((20, 20, 20))
|
||||
|
||||
# Draw Roads.
|
||||
for road in roads:
|
||||
rect = pygame.Rect(road["x"], road["y"], road["width"], road["height"])
|
||||
pygame.draw.rect(screen, (80, 80, 80), rect)
|
||||
|
||||
# Draw Obstacles.
|
||||
for obs in obstacles:
|
||||
rect = pygame.Rect(obs["x"], obs["y"], obs["width"], obs["height"])
|
||||
pygame.draw.rect(screen, (150, 50, 50), rect)
|
||||
|
||||
# Draw Checkpoints.
|
||||
for cp in checkpoints:
|
||||
rect = pygame.Rect(cp["x"], cp["y"], cp["width"], cp["height"])
|
||||
pygame.draw.rect(screen, (200, 200, 0), rect, 2)
|
||||
|
||||
# Draw Finish Lines.
|
||||
for fl in finish_lines:
|
||||
rect = pygame.Rect(fl["x"], fl["y"], fl["width"], fl["height"])
|
||||
pygame.draw.rect(screen, (0, 0, 255), rect, 2)
|
||||
|
||||
# Draw Players.
|
||||
with lock:
|
||||
all_states = list(client_states.values()) + list(bot_states.values())
|
||||
for state in all_states:
|
||||
x = int(state["x"])
|
||||
y = int(state["y"])
|
||||
color = (0, 255, 0) if not state.get("bot", False) else (255, 165, 0)
|
||||
pygame.draw.circle(screen, color, (x, y), PLAYER_RADIUS)
|
||||
name = state["username"]
|
||||
label = font.render(name, True, (255,255,255))
|
||||
screen.blit(label, (x - label.get_width() // 2, y - PLAYER_RADIUS - 15))
|
||||
|
||||
# For bots, draw sensor rays and target line.
|
||||
if state.get("bot", False):
|
||||
for offset, distance in state.get("sensors", []):
|
||||
ray_angle = state["angle"] + offset
|
||||
end_x = int(x + distance * math.cos(ray_angle))
|
||||
end_y = int(y + distance * math.sin(ray_angle))
|
||||
ray_color = (255, 0, 0) if distance < SENSOR_MAX_DISTANCE else (0, 255, 0)
|
||||
pygame.draw.line(screen, ray_color, (x, y), (end_x, end_y), 2)
|
||||
if "target" in state and state["target"]:
|
||||
tx, ty = state["target"]
|
||||
pygame.draw.line(screen, (255,255,255), (x, y), (int(tx), int(ty)), 2)
|
||||
|
||||
info_text = font.render("Server Dashboard - Full Race Course View", True, (255,255,255))
|
||||
screen.blit(info_text, (10, 10))
|
||||
pygame.display.flip()
|
||||
clock.tick(30)
|
||||
|
||||
# --- Server Startup ---
|
||||
def start_server():
|
||||
print("[STARTING] Server is starting...")
|
||||
threading.Thread(target=game_loop, daemon=True).start()
|
||||
threading.Thread(target=server_dashboard, daemon=True).start()
|
||||
spawn_bot() # Spawn one bot (add more if desired)
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.bind((HOST, PORT))
|
||||
s.listen()
|
||||
|
Loading…
Reference in New Issue
Block a user