Server: implement JOIN/JOIN_ACK/JOIN_DENY handling, input parsing/relay, spawn logic, apples maintenance; fix InMemoryTransport to address specific peer; add state_full encoder
- 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
This commit is contained in:
@@ -114,7 +114,7 @@ def unpack_header(buf: bytes, expect_tick: bool) -> Tuple[int, PacketType, int,
|
||||
return ver, ptype, flags, seq, tick, off
|
||||
|
||||
|
||||
# Message builders (subset/skeleton)
|
||||
# Message builders/parsers (subset)
|
||||
|
||||
def build_config_update(
|
||||
*,
|
||||
@@ -191,3 +191,109 @@ def bitpack_2bit_directions(directions: Iterable[Direction]) -> bytes:
|
||||
out.append(acc & 0xFF) # zero-padded high bits
|
||||
return bytes(out)
|
||||
|
||||
|
||||
# Join / Ack / Deny
|
||||
|
||||
def build_join(name_utf8: bytes, preferred_color_id: int | None = None) -> bytes:
|
||||
# Client-side helper (not used in server)
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def parse_join(buf: bytes, offset: int) -> Tuple[str, int | None, int]:
|
||||
name_len, off = quic_varint_decode(buf, offset)
|
||||
name_b = buf[off : off + name_len]
|
||||
off += name_len
|
||||
preferred = None
|
||||
if off < len(buf):
|
||||
preferred = buf[off]
|
||||
off += 1
|
||||
name = name_b.decode("utf-8", errors="ignore")
|
||||
return name, preferred, off
|
||||
|
||||
|
||||
def build_join_ack(
|
||||
*,
|
||||
version: int,
|
||||
seq: int,
|
||||
player_id: int,
|
||||
color_id: int,
|
||||
width: int,
|
||||
height: int,
|
||||
tick_rate: int,
|
||||
wrap_edges: bool,
|
||||
apples_per_snake: int,
|
||||
apples_cap: int,
|
||||
compression_mode: int,
|
||||
) -> bytes:
|
||||
header = pack_header(version, PacketType.JOIN_ACK, 0, seq, None)
|
||||
body = bytearray()
|
||||
body.append(player_id & 0xFF)
|
||||
body.append(color_id & 0xFF)
|
||||
body.append(width & 0xFF)
|
||||
body.append(height & 0xFF)
|
||||
body.append(tick_rate & 0xFF)
|
||||
body.append(1 if wrap_edges else 0)
|
||||
body.append(apples_per_snake & 0xFF)
|
||||
body.append(apples_cap & 0xFF)
|
||||
body.append(compression_mode & 0xFF)
|
||||
return header + bytes(body)
|
||||
|
||||
|
||||
def build_join_deny(*, version: int, seq: int, reason: str) -> bytes:
|
||||
header = pack_header(version, PacketType.JOIN_DENY, 0, seq, None)
|
||||
rb = reason.encode("utf-8")[:64]
|
||||
return header + quic_varint_encode(len(rb)) + rb
|
||||
|
||||
|
||||
# Input (client -> server)
|
||||
|
||||
def parse_input(buf: bytes, offset: int) -> Tuple[int, int, int, List[InputEvent], int]:
|
||||
ack_seq = int.from_bytes(buf[offset : offset + 2], "big")
|
||||
offset += 2
|
||||
input_seq = int.from_bytes(buf[offset : offset + 2], "big")
|
||||
offset += 2
|
||||
base_tick = int.from_bytes(buf[offset : offset + 2], "big")
|
||||
offset += 2
|
||||
n_ev, offset = quic_varint_decode(buf, offset)
|
||||
events: List[InputEvent] = []
|
||||
for _ in range(n_ev):
|
||||
rel, offset = quic_varint_decode(buf, offset)
|
||||
d = Direction(buf[offset] & 0x03)
|
||||
offset += 1
|
||||
events.append(InputEvent(rel_tick_offset=int(rel), direction=d))
|
||||
return ack_seq, input_seq, base_tick, events, offset
|
||||
|
||||
|
||||
# State snapshot (server -> client)
|
||||
|
||||
def build_state_full(
|
||||
*,
|
||||
version: int,
|
||||
seq: int,
|
||||
tick: int,
|
||||
snakes: Sequence[Tuple[int, int, int, int, Sequence[Direction]]],
|
||||
apples: Sequence[Tuple[int, int]],
|
||||
) -> bytes:
|
||||
"""Build a minimal state_full: per-snake header + BODY_2BIT TLV; apples list.
|
||||
|
||||
snakes: sequence of (snake_id, len, head_x, head_y, body_dirs_from_head)
|
||||
apples: sequence of (x, y)
|
||||
"""
|
||||
header = pack_header(version, PacketType.STATE_FULL, 0, seq, tick & 0xFFFF)
|
||||
body = bytearray()
|
||||
# snakes count
|
||||
body.extend(quic_varint_encode(len(snakes)))
|
||||
for sid, slen, hx, hy, dirs in snakes:
|
||||
body.append(sid & 0xFF)
|
||||
body.extend(int(slen & 0xFFFF).to_bytes(2, "big"))
|
||||
body.append(hx & 0xFF)
|
||||
body.append(hy & 0xFF)
|
||||
payload = bitpack_2bit_directions(dirs)
|
||||
tlv = pack_body_tlv(BodyTLV.BODY_2BIT, payload)
|
||||
body.extend(tlv)
|
||||
# apples
|
||||
body.extend(quic_varint_encode(len(apples)))
|
||||
for ax, ay in apples:
|
||||
body.append(ax & 0xFF)
|
||||
body.append(ay & 0xFF)
|
||||
return header + bytes(body)
|
||||
|
||||
Reference in New Issue
Block a user