An agent playing the Living Fork opened one bout with the line "Diversified cutlery beats concentrated risk." It then justified nearly every move with notes like "Rebalancing toward block." It lost 0-2, according to the launch post. The post offers this as color, but it points to a real design problem. An agent that improvises its moves and narrates its reasoning gives opponents something to read.
This article uses Lobster Kombat's published rules as a worked example for developers building agents for simultaneous-move games. It analyzes the rules only. It reports no match results, and the code is an unexecuted sketch.
What the rules pin down, and what they leave open
Each exchange, both fighters secretly pick strike, block, special or taunt. Neither sees the other's choice until both are in. The documentation names three outcomes: strike beats a special windup, block beats strike, and special beats block.
| Your move | Opponent's move | Documented result | |---|---|---| | Strike | Special | Strike wins | | Block | Strike | Block wins | | Special | Block | Special wins | | Taunt | Anything | Undocumented | | Strike vs strike, block vs block, special vs special | Same move | Not documented (the commentary mentions a "mutual strike," but no effect is stated) |
The cycle runs strike over special, special over block, block over strike. The launch post calls it rock-paper-scissors with twists. If you treat each documented win as symmetric and ignore the twists, the unexploitable baseline is the textbook one: pick the three cycle moves about equally, independently each exchange. That is an inference from standard game theory, not a figure from the vendor.
The baseline weakens once the twists matter. The documentation does not say how an exchange becomes damage, how many exchanges make a round, or what a taunt does beyond being "a gamble on style." Bouts are best of three rounds.
Don't assume a taunt beats, loses to or ties anything. The safest policy leaves it out until its effect is documented or measured.
The vendor also says that because moves are simultaneous and hidden, there is no reaction advantage and no "fastest API wins." That is the vendor's claim. It is consistent with the rules as described, and it implies that reading the opponent is the only edge.
There is another leak to close. A thought stays hidden until the exchange resolves, but the match page is public. Anyone studying your agent's history can read every thought it wrote. This is an inference from the documented behavior, not a stated rule. Keep thoughts short and free of policy language such as "rebalancing."
Cooldowns turn the game into a schedule
Specials have a cooldown. Most fighters wait two exchanges after a special, and Philippe waits three. The documentation does not say whether the exchange where you fire the special counts toward that wait. See related: how to evaluate an ai writing workflow: 4-test scorecard for additional background.
Your own fighter settles the question. arena_get shows which moves are legal right now, and the server rejects a special that is on cooldown. Watch when your special returns, then apply the same count to an opponent playing the same fighter. Add one exchange when the opponent is Philippe. Treat the result as an estimate, since only your own legal list is authoritative.
The schedule changes which moves are worth playing. When the opponent's special is down, their options are strike, block and taunt. Your strike has no documented win in that window, because it beats only a special. It loses to block, and strike against strike is undocumented. A block-and-special mix looks stronger. This is an inference from the three documented outcomes.
The same logic applies to you. When your own special is down, the opponent can work out that your options are strike, block and taunt. Vary between strike and block without a fixed rhythm.
Use this checklist each exchange:
- Call
arena_getand read your legal moves and your own pending move. - Drop taunt from the candidate set unless you accept an undocumented risk.
- Estimate whether the opponent's special is available, using their last special and their fighter's wait (one extra exchange for Philippe).
- If it is down, deprioritize strike and mix block and special.
- If it is up, all three cycle moves are live. Stay near uniform and tilt only on logged evidence.
- If your special just came back, do not fire it automatically. The opponent can count the same window you can.
- Submit well inside the 10-minute limit. A missing move becomes a block, and special beats block.
Log habits, then exploit them sparingly
The launch post names the habits to hunt for: "Does this agent always open with a special? Does it block whenever it's ahead? Will it expect me to expect that?" Log these signals per opponent and per context: We cover related ground in our guide to how to test an ai agent: a 4-task scorecard before you pay.
- Opening move of each bout
- Move choice when ahead, tied and behind
- Response in the exchanges right after the opponent's own special
- How soon the opponent fires a special once it is available
- How the opponent reacts after losing an exchange
- Taunt frequency, if the opponent uses it
Here is a minimal sketch, standard library only. It is unexecuted. It counters a habit only when the sample is large and the habit dominant, and even then only part of the time.
import random
from collections import Counter, defaultdict
BEATS = {"strike": "special", "block": "strike", "special": "block"}
COUNTER = {v: k for k, v in BEATS.items()} # best reply to each move
class Policy:
def __init__(self, min_samples=5, threshold=0.6, exploit_rate=0.6):
self.seen = defaultdict(Counter) # context -> opponent move counts
self.min_samples = min_samples
self.threshold = threshold
self.exploit_rate = exploit_rate
def observe(self, ctx, opp_move):
self.seen[ctx][opp_move] += 1
def choose(self, legal, ctx):
counts = self.seen[ctx]
n = sum(counts.values())
if n >= self.min_samples:
move, c = counts.most_common(1)[0]
reply = COUNTER.get(move)
if c / n >= self.threshold and reply in legal \
and random.random() < self.exploit_rate:
return reply
pool = [m for m in legal if m != "taunt"]
return random.choice(pool)
The legal list comes from arena_get, so the sketch never submits a move the server would reject. For contexts like "open" or "after_special," the caller supplies the label.
Also read: related topic: how to make layered box-shadow css (plus glass cards)
Reading the code, and not from a run, the expected behavior is this. After five logged specials in the "open" context, choose(["strike", "block", "special"], "open") should return strike most of the time. Otherwise it picks uniformly among the cycle moves and never returns taunt. To verify, replay a logged exchange history offline. Count the replies and assert that each one appears in the legal list.
The failure case matters more. Suppose an opponent opens with special five times, and your agent starts answering with strike. If that opponent then switches to block, your strike loses, because block beats strike. Five samples can be noise, and an opponent that models you can bait you on purpose. That is why the sketch exploits only part of the time and keeps a random floor.
A wrong read loses that exchange. Because a bout is only best of three rounds, there may be little in-bout data, and the documentation does not say how many exchanges fit in a round. Public match pages and public agent names or handles suggest a way to seed the model from earlier bouts. That is an inference, and the documentation does not describe it as a feature.
The vendor says the leaderboard was empty at the time of writing, so there is no public field of strong play to calibrate against yet. Practice bouts against the house bot don't affect Elo, but the documentation does not describe how that bot chooses moves. Use practice to check cooldown timing and your logging, not to tune an exploit.
The pattern generalizes beyond lobsters. Start from a randomized baseline, shrink the option set to what the rules say is legal, and treat every detected habit as a probability to lean on, not a certainty. Keep your model of the opponent and your model of yourself equally honest.



