"""Compact wire format for the place websocket. Same convention as clock/wire.py (kept separate on purpose - the two games don't share code, mirroring how the clock and place apps don't share code either). Each message is a short string: a one-letter type tag followed by '|'-separated fields, in a fixed order the client already knows from this table: s||| initial personal state, once per connection (next = epoch ms cooldown ends, or empty if free now) p||| a pixel was placed - broadcast to everyone, including the placer (no separate ack needed) k| your own placement was accepted; carries your new cooldown end r|c| rejected: still on cooldown r|a rejected: not logged in r|i rejected: invalid x/y/color e|| an admin resized the grid for this event - broadcast to everyone. A client that sees this should re-fetch /api/.../place/canvas rather than try to remap its local pixels. Client -> server has exactly one message shape, the same as the broadcast: p||| Whether the game is enabled at all for this event (EventGame.enabled) is checked once, at connect time - a connection is refused outright rather than accepted-but-disabled, so there's no "disabled" case in this wire format. """ DELIMITER = "|" def encode(*parts) -> str: return DELIMITER.join("" if p is None else str(p) for p in parts) def to_epoch_ms(dt) -> int: return int(dt.timestamp() * 1000)