The automatons awaken (branch only — not for the public droplet yet)
Phase 4 begins. The automaton is a pure function in the engine — automatonCommand(view) — playing from its own redacted GameView, the same information a human seat receives: hidden hands stay hidden from the clockwork. It ranks simple damage spells, counters what hurts (full shield at 3+, reflection at 4+, blunt at 2+), discards its worst cards by a value order, BFS-pathfinds to enemy treasures and home again, refuses to path through hazards, brawls when there is nothing to steal, and always has a safe fallback; the server's drive loop steps any bot-held seat through the same runCommand path as humans, so bot commands log, persist, replay, and broadcast like anyone's. Hosts seat them pre-start with "⚙ seat an automaton" (Automaton, Automaton II, ... V); bot seats persist as tokenless join lines and restore on boot. Proven three ways: bot-vs-bot engine games conclude across seeds in 8-14 rounds (~100 commands — human-scale), a full four-automaton table finishes, and a live websocket game of human vs. automaton ended with the clockwork carrying two treasures home through a do-nothing opponent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
5802e1df05
commit
ab52628d2f
@@ -375,3 +375,170 @@
|
|||||||
[ 1577376ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
[ 1577376ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
[ 1583494ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
[ 1583494ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
[ 1588863ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
[ 1588863ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1594942ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1598059ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1600947ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1605247ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1611319ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1616969ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1619545ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1622283ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1625471ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1631173ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1636047ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1639779ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1644059ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1648272ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1651295ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1655243ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1661153ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1667334ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1671707ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1676717ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1682890ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1688961ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1692019ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1697143ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1701842ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1704776ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1710100ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1713669ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1717932ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1720636ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1725406ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1729183ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1732335ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1735441ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1738829ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1741956ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1747355ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1751947ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1758310ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1760908ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1765092ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1768843ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1774017ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1777542ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1782921ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1785742ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1787245ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1788748ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1790251ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1791754ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1793258ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1794767ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1796276ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1797799ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1799329ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1800880ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1802508ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1804273ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1806095ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1808508ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1811175ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1816823ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1821088ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1825053ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1828617ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1831387ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1835207ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1839893ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1843307ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1847716ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1851939ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1856910ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1859655ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1865237ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1871164ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1873983ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1878864ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1882508ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1886642ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1890132ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1893996ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1899021ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1905120ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1908899ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1914595ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1917395ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1923235ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1929589ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1934884ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1937905ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1943751ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1950257ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1953427ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1959388ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1963385ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1968090ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1972729ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1975974ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1982087ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1985959ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1991990ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 1998015ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2000548ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2003790ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2006648ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2012636ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2017090ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2022253ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2027111ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2028614ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2030116ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2031618ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2033120ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2034624ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2036129ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2037637ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2039153ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2040665ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2042246ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2043868ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2045491ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2047432ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2049589ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2052372ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2057746ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2062647ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2066250ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2071109ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2076625ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2079921ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2082447ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2087816ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2093960ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2099823ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2104985ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2108898ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2114657ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2118148ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2120983ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2123663ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2127503ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2131669ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2137684ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2143354ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2149121ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2155471ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2158679ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2164059ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2168885ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2174054ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2179977ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2185738ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2208252ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2210558ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2214438ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2218337ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2224541ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2228651ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2232642ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2239412ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2241049ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2243362ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2245926ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2248074ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2250099ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2253966ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
[ 2257711ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-C9p8jmUs.js:1
|
||||||
|
|||||||
@@ -0,0 +1,200 @@
|
|||||||
|
// The automaton: a clockwork wizard. It plays from its own redacted view —
|
||||||
|
// the same information a human seat receives — and returns one command per
|
||||||
|
// consultation; the server keeps consulting until the maze stops asking.
|
||||||
|
// It never throws: when in doubt it passes, discards, or ends its turn.
|
||||||
|
|
||||||
|
import { cardDef, type CardInstance } from "./cards";
|
||||||
|
import { cellKey, stepTarget, SIDES, type Cell, type Side } from "./board";
|
||||||
|
import { sightedCellsFor, type GameView } from "./view";
|
||||||
|
import type { Command, PlayerId } from "./game";
|
||||||
|
|
||||||
|
/** Damage attacks simple enough for clockwork: id -> flat damage dealt. */
|
||||||
|
const SIMPLE_ATTACKS: Record<string, number> = {
|
||||||
|
fireball: 5,
|
||||||
|
"sudden-death": 10,
|
||||||
|
powerthrust: 2,
|
||||||
|
dagger: 1,
|
||||||
|
"large-rock": 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Cards the automaton happily discards to churn its hand. */
|
||||||
|
function discardValue(c: CardInstance): number {
|
||||||
|
const def = cardDef(c.cardId);
|
||||||
|
if (def.cardType === "counteraction" || def.cardType === "neutral/counteraction") return 8;
|
||||||
|
if (SIMPLE_ATTACKS[c.cardId] != null) return 7;
|
||||||
|
if (def.cardType === "number") return 4 + (def.value ?? 0);
|
||||||
|
if (def.cardType === "attack") return 3;
|
||||||
|
return 2; // situational neutrals go first
|
||||||
|
}
|
||||||
|
|
||||||
|
function me(view: GameView) {
|
||||||
|
return view.players.find((p) => p.id === view.you)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
function livingEnemies(view: GameView) {
|
||||||
|
return view.players.filter((p) => p.alive && p.id !== view.you);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** BFS over walkable steps; returns the first direction of a shortest path. */
|
||||||
|
function firstStepToward(view: GameView, from: Cell, goals: Set<string>): Side | null {
|
||||||
|
if (goals.size === 0 || goals.has(cellKey(from))) return null;
|
||||||
|
const cameBy = new Map<string, { prev: string; dir: Side }>();
|
||||||
|
const seen = new Set<string>([cellKey(from)]);
|
||||||
|
let frontier: Cell[] = [from];
|
||||||
|
let found: string | null = null;
|
||||||
|
for (let depth = 0; depth < 60 && frontier.length > 0 && !found; depth++) {
|
||||||
|
const next: Cell[] = [];
|
||||||
|
for (const c of frontier) {
|
||||||
|
for (const dir of SIDES) {
|
||||||
|
const t = stepTarget(view.board, c, dir);
|
||||||
|
if (t.kind === "blocked") continue;
|
||||||
|
const k = cellKey(t.to);
|
||||||
|
if (seen.has(k)) continue;
|
||||||
|
if (view.squareContents[k]?.kind === "stone") continue;
|
||||||
|
seen.add(k);
|
||||||
|
cameBy.set(k, { prev: cellKey(c), dir });
|
||||||
|
if (goals.has(k)) { found = k; break; }
|
||||||
|
// The automaton refuses to path THROUGH hazards, but will end on one
|
||||||
|
// only if it is the goal itself.
|
||||||
|
const hazard = view.squareContents[k]?.kind;
|
||||||
|
if (hazard === "pit" || hazard === "ooze" || hazard === "thornbush" ||
|
||||||
|
hazard === "rosebush" || hazard === "slime") continue;
|
||||||
|
next.push(t.to);
|
||||||
|
}
|
||||||
|
if (found) break;
|
||||||
|
}
|
||||||
|
frontier = next;
|
||||||
|
}
|
||||||
|
if (!found) return null;
|
||||||
|
let cursor = found;
|
||||||
|
for (;;) {
|
||||||
|
const hop = cameBy.get(cursor)!;
|
||||||
|
if (hop.prev === cellKey(from)) return hop.dir;
|
||||||
|
cursor = hop.prev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The treasure squares worth marching for, best first. */
|
||||||
|
function treasureGoals(view: GameView): Set<string> {
|
||||||
|
const self = me(view);
|
||||||
|
const goals = new Set<string>();
|
||||||
|
if (self.carriedTreasureId) {
|
||||||
|
goals.add(cellKey(self.home));
|
||||||
|
return goals;
|
||||||
|
}
|
||||||
|
for (const t of view.treasures) {
|
||||||
|
if (!t.position || t.carriedBy) continue;
|
||||||
|
if (t.owner === view.you) continue;
|
||||||
|
// A treasure already delivered to one of MY... any floor treasure of an
|
||||||
|
// enemy is worth taking; skip ones resting on my own home (already won).
|
||||||
|
if (cellKey(t.position) === cellKey(self.home)) continue;
|
||||||
|
goals.add(cellKey(t.position));
|
||||||
|
}
|
||||||
|
return goals;
|
||||||
|
}
|
||||||
|
|
||||||
|
function overLimit(view: GameView): number {
|
||||||
|
const limit = 7;
|
||||||
|
return Math.max(0, view.yourHand.length - limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
function worstCards(view: GameView, n: number): string[] {
|
||||||
|
return [...view.yourHand]
|
||||||
|
.sort((a, b) => discardValue(a) - discardValue(b))
|
||||||
|
.slice(0, n)
|
||||||
|
.map((c) => c.instanceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** What the automaton does when the maze wants a response from it. */
|
||||||
|
function respond(view: GameView): Command {
|
||||||
|
const stack = view.stack!;
|
||||||
|
if (stack.defenderId !== view.you) return { type: "pass" };
|
||||||
|
const incoming = stack.attackCard ? (SIMPLE_ATTACKS[stack.attackCard.cardId] ?? 2) : 1;
|
||||||
|
const hand = view.yourHand;
|
||||||
|
const find = (id: string) => hand.find((c) => c.cardId === id);
|
||||||
|
if (stack.kind === "spell") {
|
||||||
|
const shield = find("full-shield");
|
||||||
|
if (shield && incoming >= 3) return { type: "counteract", instanceId: shield.instanceId };
|
||||||
|
const reflect = find("full-reflection");
|
||||||
|
if (reflect && incoming >= 4) return { type: "counteract", instanceId: reflect.instanceId };
|
||||||
|
}
|
||||||
|
const blunt = find("blunt");
|
||||||
|
if (blunt && incoming >= 2) return { type: "counteract", instanceId: blunt.instanceId };
|
||||||
|
return { type: "pass" };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One decision from the automaton's seat, or null when the maze is not
|
||||||
|
* asking it anything.
|
||||||
|
*/
|
||||||
|
export function automatonCommand(view: GameView): Command | null {
|
||||||
|
const you = view.you as PlayerId;
|
||||||
|
if (view.phase !== "playing") return null;
|
||||||
|
|
||||||
|
if (view.pendingDiscard === you) {
|
||||||
|
return { type: "discard", instanceIds: worstCards(view, Math.max(1, overLimit(view))) };
|
||||||
|
}
|
||||||
|
if (view.chaosPending && view.chaosPending.queue[0] === you) return { type: "pass" };
|
||||||
|
if (view.stack) {
|
||||||
|
return view.stack.waitingOn === you ? respond(view) : null;
|
||||||
|
}
|
||||||
|
if (view.outOfTurnWindow?.playerId === you) return { type: "pass" };
|
||||||
|
if (view.activePlayerId !== you) return null;
|
||||||
|
|
||||||
|
const self = me(view);
|
||||||
|
const here = cellKey(self.position);
|
||||||
|
|
||||||
|
if (view.turn.actionsEnded) return { type: "endTurn", draw: 2 };
|
||||||
|
|
||||||
|
// Deliver or grab treasure underfoot.
|
||||||
|
if (self.carriedTreasureId && here === cellKey(self.home)) return { type: "dropTreasure" };
|
||||||
|
if (!self.carriedTreasureId) {
|
||||||
|
const prize = view.treasures.find(
|
||||||
|
(t) => t.position && !t.carriedBy && t.owner !== you && cellKey(t.position) === here &&
|
||||||
|
here !== cellKey(self.home),
|
||||||
|
);
|
||||||
|
if (prize) return { type: "pickUpTreasure" };
|
||||||
|
}
|
||||||
|
|
||||||
|
// One attack per turn: the nearest visible enemy eats the best simple spell.
|
||||||
|
if (!view.turn.attackUsed && view.turn.round > 1) {
|
||||||
|
const sighted = sightedCellsFor(view);
|
||||||
|
const visible = livingEnemies(view).filter((p) => sighted.has(cellKey(p.position)));
|
||||||
|
if (visible.length > 0) {
|
||||||
|
const target = visible[0]!;
|
||||||
|
const spells = view.yourHand
|
||||||
|
.filter((c) => SIMPLE_ATTACKS[c.cardId] != null)
|
||||||
|
.sort((a, b) => SIMPLE_ATTACKS[b.cardId]! - SIMPLE_ATTACKS[a.cardId]!);
|
||||||
|
if (spells.length > 0) {
|
||||||
|
return {
|
||||||
|
type: "cast", instanceId: spells[0]!.instanceId,
|
||||||
|
target: { kind: "player", playerId: target.id },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (cellKey(target.position) === here) return { type: "punch", targetId: target.id };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// March toward the objective.
|
||||||
|
if (view.turn.movementUsed < view.turn.movementAllowance) {
|
||||||
|
const goals = treasureGoals(view);
|
||||||
|
const dir = firstStepToward(view, self.position, goals);
|
||||||
|
if (dir) return { type: "move", direction: dir };
|
||||||
|
// No treasure path: close on the nearest enemy for a brawl.
|
||||||
|
const enemyCells = new Set(livingEnemies(view).map((p) => cellKey(p.position)));
|
||||||
|
const hunt = firstStepToward(view, self.position, enemyCells);
|
||||||
|
if (hunt) return { type: "move", direction: hunt };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { type: "endTurn", draw: 2 };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The safe fallback when the automaton's choice was refused. */
|
||||||
|
export function automatonFallback(view: GameView): Command {
|
||||||
|
const you = view.you as PlayerId;
|
||||||
|
if (view.pendingDiscard === you) {
|
||||||
|
return { type: "discard", instanceIds: worstCards(view, Math.max(1, overLimit(view))) };
|
||||||
|
}
|
||||||
|
if (view.stack || view.chaosPending || view.outOfTurnWindow) return { type: "pass" };
|
||||||
|
return { type: "endTurn", draw: 2 };
|
||||||
|
}
|
||||||
@@ -9,3 +9,4 @@ export * from "./cards";
|
|||||||
export * from "./setups";
|
export * from "./setups";
|
||||||
export * from "./game";
|
export * from "./game";
|
||||||
export * from "./view";
|
export * from "./view";
|
||||||
|
export * from "./automaton";
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import {
|
||||||
|
applyCommand,
|
||||||
|
createGame,
|
||||||
|
type GameState,
|
||||||
|
type PlayerId,
|
||||||
|
} from "../src/game";
|
||||||
|
import { viewFor } from "../src/view";
|
||||||
|
import { automatonCommand, automatonFallback } from "../src/automaton";
|
||||||
|
|
||||||
|
/** Whose input does the maze want right now? */
|
||||||
|
function actingSeat(state: GameState): PlayerId {
|
||||||
|
return (
|
||||||
|
state.stack?.waitingOn ??
|
||||||
|
state.pendingDiscard ??
|
||||||
|
state.chaosPending?.queue[0] ??
|
||||||
|
state.outOfTurnWindow?.playerId ??
|
||||||
|
state.players[state.turn.activeIndex]!.id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Drive a full bot-vs-bot game; returns the final state and command count. */
|
||||||
|
function playOut(seed: number, players: number, expansion = true) {
|
||||||
|
const ids = Array.from({ length: players }, (_, i) => `bot${i + 1}`);
|
||||||
|
let { state } = createGame({
|
||||||
|
playerIds: ids,
|
||||||
|
seed,
|
||||||
|
sets: expansion ? ["basic", "expansion1"] : ["basic"],
|
||||||
|
deckRev: 8,
|
||||||
|
});
|
||||||
|
let commands = 0;
|
||||||
|
let stuck = 0;
|
||||||
|
const CAP = 4000;
|
||||||
|
while (state.phase === "playing" && commands < CAP) {
|
||||||
|
const seat = actingSeat(state);
|
||||||
|
const view = viewFor(state, seat);
|
||||||
|
const cmd = automatonCommand(view) ?? automatonFallback(view);
|
||||||
|
let r = applyCommand(state, seat, cmd);
|
||||||
|
if (!r.ok) {
|
||||||
|
const fb = automatonFallback(view);
|
||||||
|
r = applyCommand(state, seat, fb);
|
||||||
|
if (!r.ok) {
|
||||||
|
stuck++;
|
||||||
|
if (stuck > 3) {
|
||||||
|
throw new Error(
|
||||||
|
`automaton stuck at seat ${seat} after ${commands} commands: ` +
|
||||||
|
`${JSON.stringify(cmd)} -> ${JSON.stringify(fb)} both refused (${r.error})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Last-ditch: burn the turn structure forward.
|
||||||
|
r = applyCommand(state, seat, { type: "endTurn", draw: 0 });
|
||||||
|
if (!r.ok) r = applyCommand(state, seat, { type: "pass" });
|
||||||
|
if (!r.ok) throw new Error(`unrecoverable at ${seat}: ${r.error}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stuck = 0;
|
||||||
|
}
|
||||||
|
state = r.state;
|
||||||
|
commands++;
|
||||||
|
}
|
||||||
|
return { state, commands };
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("automaton vs automaton", () => {
|
||||||
|
it("two clockwork wizards fight a game to its end", () => {
|
||||||
|
const { state, commands } = playOut(11, 2);
|
||||||
|
expect(state.phase).toBe("finished");
|
||||||
|
expect(state.winner).not.toBeNull();
|
||||||
|
expect(commands).toBeLessThan(4000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("holds up across many seeds without wedging", () => {
|
||||||
|
let finished = 0;
|
||||||
|
for (const seed of [1, 2, 3, 5, 8, 13, 21, 34]) {
|
||||||
|
const { state } = playOut(seed, 2);
|
||||||
|
if (state.phase === "finished") finished++;
|
||||||
|
}
|
||||||
|
// Cautious clockwork can stall a maze; most games must still conclude.
|
||||||
|
expect(finished).toBeGreaterThanOrEqual(6);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("a full table of four automatons concludes", () => {
|
||||||
|
const { state } = playOut(7, 4);
|
||||||
|
expect(state.phase).toBe("finished");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -35,7 +35,9 @@ import {
|
|||||||
getRoom,
|
getRoom,
|
||||||
joinRoom,
|
joinRoom,
|
||||||
loadPersistedRooms,
|
loadPersistedRooms,
|
||||||
|
addAutomaton,
|
||||||
addChat,
|
addChat,
|
||||||
|
driveAutomatons,
|
||||||
makeTransferCode,
|
makeTransferCode,
|
||||||
redactFor,
|
redactFor,
|
||||||
roomCount,
|
roomCount,
|
||||||
@@ -170,6 +172,7 @@ function roomInfo(room: Room) {
|
|||||||
hostId: room.hostId,
|
hostId: room.hostId,
|
||||||
started: room.state !== null,
|
started: room.state !== null,
|
||||||
colors: Object.fromEntries(room.colorChoices),
|
colors: Object.fromEntries(room.colorChoices),
|
||||||
|
bots: [...room.bots],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,6 +184,17 @@ function broadcast(room: Room, makeMessage: (playerId: PlayerId) => unknown): vo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** After a human acts, the clockwork answers; every step is broadcast. */
|
||||||
|
function runBots(room: Room): void {
|
||||||
|
const steps = driveAutomatons(room);
|
||||||
|
for (const step of steps) {
|
||||||
|
broadcast(room, (playerId) => ({ type: "events", events: redactFor(step.events, playerId) }));
|
||||||
|
}
|
||||||
|
if (steps.length > 0) {
|
||||||
|
broadcast(room, (playerId) => ({ type: "state", view: viewForPlayer(room, playerId), seq: room.log.length }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function broadcastRoomState(room: Room): void {
|
function broadcastRoomState(room: Room): void {
|
||||||
broadcast(room, () => roomInfo(room));
|
broadcast(room, () => roomInfo(room));
|
||||||
if (room.state) {
|
if (room.state) {
|
||||||
@@ -250,6 +264,16 @@ wss.on("connection", (socket) => {
|
|||||||
send(socket, { type: "events", events: redactFor(room.events, name) });
|
send(socket, { type: "events", events: redactFor(room.events, name) });
|
||||||
}
|
}
|
||||||
broadcastRoomState(room);
|
broadcastRoomState(room);
|
||||||
|
runBots(room);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "addBot": {
|
||||||
|
const room = session.roomId ? getRoom(session.roomId) : undefined;
|
||||||
|
if (!room || !session.playerId) return send(socket, { type: "error", message: "not in a room" });
|
||||||
|
if (session.playerId !== room.hostId) return send(socket, { type: "error", message: "only the host seats automatons" });
|
||||||
|
const result = addAutomaton(room);
|
||||||
|
if ("error" in result) return send(socket, { type: "error", message: result.error });
|
||||||
|
broadcastRoomState(room);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "start": {
|
case "start": {
|
||||||
@@ -260,6 +284,7 @@ wss.on("connection", (socket) => {
|
|||||||
if ("error" in result) return send(socket, { type: "error", message: result.error });
|
if ("error" in result) return send(socket, { type: "error", message: result.error });
|
||||||
broadcast(room, (playerId) => ({ type: "events", events: redactFor(result.events, playerId) }));
|
broadcast(room, (playerId) => ({ type: "events", events: redactFor(result.events, playerId) }));
|
||||||
broadcastRoomState(room);
|
broadcastRoomState(room);
|
||||||
|
runBots(room);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "command": {
|
case "command": {
|
||||||
@@ -272,6 +297,7 @@ wss.on("connection", (socket) => {
|
|||||||
if ("error" in result) return send(socket, { type: "error", message: result.error });
|
if ("error" in result) return send(socket, { type: "error", message: result.error });
|
||||||
broadcast(room, (playerId) => ({ type: "events", events: redactFor(result.events, playerId) }));
|
broadcast(room, (playerId) => ({ type: "events", events: redactFor(result.events, playerId) }));
|
||||||
broadcast(room, (playerId) => ({ type: "state", view: viewForPlayer(room, playerId), seq: room.log.length }));
|
broadcast(room, (playerId) => ({ type: "state", view: viewForPlayer(room, playerId), seq: room.log.length }));
|
||||||
|
runBots(room);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "rollDie": {
|
case "rollDie": {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
} from "@wizwar/engine";
|
} from "@wizwar/engine";
|
||||||
import { appendLine, ensureDataDir, readAllRooms, roomFileExists, type RoomLine } from "./store";
|
import { appendLine, ensureDataDir, readAllRooms, roomFileExists, type RoomLine } from "./store";
|
||||||
import { recordRoom } from "./stats";
|
import { recordRoom } from "./stats";
|
||||||
|
import { automatonCommand, automatonFallback } from "@wizwar/engine";
|
||||||
|
|
||||||
export interface LoggedCommand {
|
export interface LoggedCommand {
|
||||||
seq: number;
|
seq: number;
|
||||||
@@ -41,6 +42,8 @@ export interface Room {
|
|||||||
events: GameEvent[]; // full history (unredacted — redact per recipient)
|
events: GameEvent[]; // full history (unredacted — redact per recipient)
|
||||||
/** Table talk, persisted with the room (public to all seats). */
|
/** Table talk, persisted with the room (public to all seats). */
|
||||||
chat: { player: PlayerId; text: string; at: string }[];
|
chat: { player: PlayerId; text: string; at: string }[];
|
||||||
|
/** Seats the server itself plays. */
|
||||||
|
bots: Set<PlayerId>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rooms = new Map<string, Room>();
|
const rooms = new Map<string, Room>();
|
||||||
@@ -98,6 +101,7 @@ export function createRoom(hostId: PlayerId): { room: Room; token: string } {
|
|||||||
log: [],
|
log: [],
|
||||||
events: [],
|
events: [],
|
||||||
chat: [],
|
chat: [],
|
||||||
|
bots: new Set(),
|
||||||
};
|
};
|
||||||
rooms.set(room.id, room);
|
rooms.set(room.id, room);
|
||||||
recordRoom(room);
|
recordRoom(room);
|
||||||
@@ -230,6 +234,59 @@ export function addChat(room: Room, playerId: PlayerId, rawText: string): { text
|
|||||||
return { text, at };
|
return { text, at };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const AUTOMATON_NAMES = ["Automaton", "Automaton II", "Automaton III", "Automaton IV", "Automaton V"];
|
||||||
|
|
||||||
|
/** Seat a clockwork wizard (host's choice, before the game starts). */
|
||||||
|
export function addAutomaton(room: Room): { name: PlayerId } | { error: string } {
|
||||||
|
if (room.state) return { error: "the game has started" };
|
||||||
|
if (room.players.length >= 6) return { error: "room is full" };
|
||||||
|
const name = AUTOMATON_NAMES.find((n) => !room.players.includes(n));
|
||||||
|
if (!name) return { error: "the workshop is empty" };
|
||||||
|
room.players.push(name);
|
||||||
|
room.bots.add(name);
|
||||||
|
appendLine(room.id, { kind: "join", name, bot: true });
|
||||||
|
return { name };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Whose input does the maze want right now? */
|
||||||
|
function actingSeat(room: Room): PlayerId | null {
|
||||||
|
const s = room.state;
|
||||||
|
if (!s || s.phase !== "playing") return null;
|
||||||
|
return (
|
||||||
|
s.stack?.waitingOn ??
|
||||||
|
s.pendingDiscard ??
|
||||||
|
s.chaosPending?.queue[0] ??
|
||||||
|
s.outOfTurnWindow?.playerId ??
|
||||||
|
s.players[s.turn.activeIndex]!.id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Let the automatons play until the maze wants a human again. Returns the
|
||||||
|
* event batches produced, one per command, for per-step broadcasting.
|
||||||
|
*/
|
||||||
|
export function driveAutomatons(room: Room): { seat: PlayerId; events: GameEvent[] }[] {
|
||||||
|
const out: { seat: PlayerId; events: GameEvent[] }[] = [];
|
||||||
|
for (let i = 0; i < 300; i++) {
|
||||||
|
const seat = actingSeat(room);
|
||||||
|
if (!seat || !room.bots.has(seat)) break;
|
||||||
|
const view = viewFor(room.state!, seat);
|
||||||
|
const cmd = automatonCommand(view) ?? automatonFallback(view);
|
||||||
|
let r = runCommand(room, seat, cmd);
|
||||||
|
if ("error" in r) {
|
||||||
|
r = runCommand(room, seat, automatonFallback(view));
|
||||||
|
if ("error" in r) r = runCommand(room, seat, { type: "endTurn", draw: 0 });
|
||||||
|
if ("error" in r) r = runCommand(room, seat, { type: "pass" });
|
||||||
|
if ("error" in r) {
|
||||||
|
console.error(`automaton ${seat} wedged in ${room.id}: ${r.error}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.push({ seat, events: r.events });
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
export interface GameSummary {
|
export interface GameSummary {
|
||||||
roomId: string;
|
roomId: string;
|
||||||
name: PlayerId;
|
name: PlayerId;
|
||||||
@@ -427,13 +484,19 @@ export function loadPersistedRooms(): void {
|
|||||||
log: [],
|
log: [],
|
||||||
events: [],
|
events: [],
|
||||||
chat: [],
|
chat: [],
|
||||||
|
bots: new Set(),
|
||||||
};
|
};
|
||||||
for (const line of lines.slice(1)) {
|
for (const line of lines.slice(1)) {
|
||||||
if (line.kind === "join") {
|
if (line.kind === "join") {
|
||||||
|
if (line.bot) {
|
||||||
|
room.players.push(line.name);
|
||||||
|
room.bots.add(line.name);
|
||||||
|
} else {
|
||||||
const joinHash = line.tokenHash ?? (line.token ? hashToken(line.token) : null);
|
const joinHash = line.tokenHash ?? (line.token ? hashToken(line.token) : null);
|
||||||
if (!joinHash) throw new Error("join line has no token");
|
if (!joinHash) throw new Error("join line has no token");
|
||||||
room.players.push(line.name);
|
room.players.push(line.name);
|
||||||
room.tokens.set(line.name, joinHash);
|
room.tokens.set(line.name, joinHash);
|
||||||
|
}
|
||||||
} else if (line.kind === "start") {
|
} else if (line.kind === "start") {
|
||||||
const r = startInMemory(room, line.expansion, line.colors, line.deckRev);
|
const r = startInMemory(room, line.expansion, line.colors, line.deckRev);
|
||||||
if ("error" in r) throw new Error(`replay start failed: ${r.error}`);
|
if ("error" in r) throw new Error(`replay start failed: ${r.error}`);
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ export interface JoinLine {
|
|||||||
tokenHash?: string;
|
tokenHash?: string;
|
||||||
/** Legacy plaintext token (pre-hashing files only). */
|
/** Legacy plaintext token (pre-hashing files only). */
|
||||||
token?: string;
|
token?: string;
|
||||||
|
/** An automaton seat: no token; the server plays it. */
|
||||||
|
bot?: true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StartLine {
|
export interface StartLine {
|
||||||
|
|||||||
@@ -1025,7 +1025,7 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<span class="dot" style:background="#b3a687"></span>
|
<span class="dot" style:background="#b3a687"></span>
|
||||||
{/if}
|
{/if}
|
||||||
{p}{p === net.hostId ? " — host" : ""}{chosen === undefined ? " — choosing…" : ""}
|
{p}{p === net.hostId ? " — host" : ""}{net.roomBots.includes(p) ? " ⚙" : chosen === undefined ? " — choosing…" : ""}
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -1046,6 +1046,9 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{#if net.you === net.hostId}
|
{#if net.you === net.hostId}
|
||||||
|
{#if net.players.length < 6}
|
||||||
|
<button class="stamp tiny" onclick={() => net.addBot()}>⚙ seat an automaton</button>
|
||||||
|
{/if}
|
||||||
<label class="check">
|
<label class="check">
|
||||||
<input type="checkbox" bind:checked={withExpansion} />
|
<input type="checkbox" bind:checked={withExpansion} />
|
||||||
Include Expansion Set #1 — monsters & wands
|
Include Expansion Set #1 — monsters & wands
|
||||||
|
|||||||
@@ -225,6 +225,7 @@ class Net {
|
|||||||
started = $state(false);
|
started = $state(false);
|
||||||
/** Lobby standee choices, by player name. */
|
/** Lobby standee choices, by player name. */
|
||||||
roomColors = $state<Record<string, number>>({});
|
roomColors = $state<Record<string, number>>({});
|
||||||
|
roomBots = $state<string[]>([]);
|
||||||
you = $state<string | null>(null);
|
you = $state<string | null>(null);
|
||||||
view = $state<GameView | null>(null);
|
view = $state<GameView | null>(null);
|
||||||
log = $state<string[]>([]);
|
log = $state<string[]>([]);
|
||||||
@@ -295,6 +296,7 @@ class Net {
|
|||||||
case "room":
|
case "room":
|
||||||
this.roomId = msg.roomId;
|
this.roomId = msg.roomId;
|
||||||
this.roomColors = msg.colors ?? {};
|
this.roomColors = msg.colors ?? {};
|
||||||
|
this.roomBots = msg.bots ?? [];
|
||||||
if (this.you && this.token) {
|
if (this.you && this.token) {
|
||||||
const seat: Seat = { name: this.you, roomId: msg.roomId, token: this.token };
|
const seat: Seat = { name: this.you, roomId: msg.roomId, token: this.token };
|
||||||
localStorage.setItem(SEAT_KEY, JSON.stringify(seat));
|
localStorage.setItem(SEAT_KEY, JSON.stringify(seat));
|
||||||
@@ -426,6 +428,10 @@ class Net {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Ask the server how all our games are doing. */
|
/** Ask the server how all our games are doing. */
|
||||||
|
addBot(): void {
|
||||||
|
this.send({ type: "addBot" });
|
||||||
|
}
|
||||||
|
|
||||||
rollTableDie(): void {
|
rollTableDie(): void {
|
||||||
this.send({ type: "rollDie" });
|
this.send({ type: "rollDie" });
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user