Files
codexPySnake/server/model.py
Vladyslav Doloman 991b8f3660 Server: per-tick simulation skeleton (inputs, movement, blocking/shrink, apple growth, wrap behavior) and basic state delta broadcast
- 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)
2025-10-07 20:27:43 +03:00

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)