- Input buffer rules: enqueue+consume with 180° guard (len>1) - Movement: wrap per config; allow moving into own tail when it vacates - Blocking: head holds, tail shrinks to min 1 - Apples: eat= grow; ensure target apples after tick - Broadcast: send current snakes/apples as delta (placeholder for real deltas)
78 lines
2.4 KiB
Python
78 lines
2.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
|
|
input_buf: Deque[Direction] = field(default_factory=deque)
|
|
blocked: bool = False
|
|
|
|
@property
|
|
def length(self) -> int:
|
|
return len(self.body)
|
|
|
|
def enqueue_direction(self, new_dir: Direction, capacity: int = 3) -> None:
|
|
"""Apply input buffer rules: size<=capacity, replace last on overflow/opposite, drop duplicates."""
|
|
last_dir = self.input_buf[-1] if self.input_buf else self.direction
|
|
# Drop duplicates
|
|
if int(new_dir) == int(last_dir):
|
|
return
|
|
# Opposite of last? replace last
|
|
if (int(new_dir) ^ int(last_dir)) == 2: # 0^2,1^3,2^0,3^1 are opposites
|
|
if self.input_buf:
|
|
self.input_buf[-1] = new_dir
|
|
else:
|
|
# No buffered inputs; just add new_dir (consumption will handle 180° rule)
|
|
self.input_buf.append(new_dir)
|
|
return
|
|
# Normal append with overflow replacement
|
|
if len(self.input_buf) >= capacity:
|
|
self.input_buf[-1] = new_dir
|
|
else:
|
|
self.input_buf.append(new_dir)
|
|
|
|
|
|
@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)
|