- Protocol: join parser, join_ack/deny builders, input parser, state_full builder - Server: on_datagram dispatch, spawn per rules (prefer length 3 else 1), join deny if no cell, immediate input_broadcast relay - Model: occupancy map and helpers - Transport: deliver to specified peer in in-memory mode
56 lines
1.4 KiB
Python
56 lines
1.4 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from typing import Deque, Dict, List, Optional, Tuple
|
|
from collections import deque
|
|
|
|
from .protocol import Direction
|
|
|
|
|
|
Coord = Tuple[int, int]
|
|
|
|
|
|
@dataclass
|
|
class Snake:
|
|
snake_id: int
|
|
head: Coord
|
|
direction: Direction
|
|
body: Deque[Coord] = field(default_factory=deque) # includes head at index 0
|
|
|
|
@property
|
|
def length(self) -> int:
|
|
return len(self.body)
|
|
|
|
|
|
@dataclass
|
|
class PlayerSession:
|
|
player_id: int
|
|
name: str
|
|
color_id: int
|
|
peer: object # transport-specific handle
|
|
input_seq: int = 0
|
|
|
|
|
|
@dataclass
|
|
class GameState:
|
|
width: int
|
|
height: int
|
|
snakes: Dict[int, Snake] = field(default_factory=dict)
|
|
apples: List[Coord] = field(default_factory=list)
|
|
tick: int = 0
|
|
occupancy: Dict[Coord, Tuple[int, int]] = field(default_factory=dict) # cell -> (snake_id, index)
|
|
|
|
def in_bounds(self, x: int, y: int) -> bool:
|
|
return 0 <= x < self.width and 0 <= y < self.height
|
|
|
|
def cell_free(self, x: int, y: int) -> bool:
|
|
return (x, y) not in self.occupancy
|
|
|
|
def occupy_snake(self, snake: Snake) -> None:
|
|
for idx, (cx, cy) in enumerate(snake.body):
|
|
self.occupancy[(cx, cy)] = (snake.snake_id, idx)
|
|
|
|
def clear_snake(self, snake: Snake) -> None:
|
|
for (cx, cy) in list(snake.body):
|
|
self.occupancy.pop((cx, cy), None)
|