Go Fish: Three Bots, Three Genuinely Different Ways to Think
Easy guesses honestly, Medium plays the odds under uncertainty, and Hard remembers everything public — never anything it shouldn't see.
Classic Go Fish, playable solo against up to three computer opponents, each independently dialed to Easy, Medium, or Hard. The card game itself is trivially simple to implement — the actual design work here went entirely into making three difficulty tiers of bot feel like genuinely different opponents rather than the same logic with a coin-flip chance of making a mistake.
Easy had to feel naive, not just weaker
The simplest way to build a weak bot is having it ask for ranks at random with some chance of just playing suboptimally. I wanted Easy to feel like a genuinely naive player rather than a handicapped version of a smart one, which meant its logic actually asks at random rather than faking randomness on top of hidden knowledge it's deliberately not using. That distinction matters for how it feels to play against — a bot that's secretly capable but deliberately throttled can feel patronizing once a player notices the pattern, while a bot that's honestly simple just feels like an approachable, low-pressure opponent to practice against.
Medium had to play the odds without full information
Medium plays the odds — reasoning probabilistically about which ranks are likely to be held by which opponent based on how many cards they're holding and what's already been asked for, without perfect memory of every single card that's changed hands. That's a genuinely different algorithm from Easy's randomness, one that has to weigh partial, probabilistic information rather than either guessing blindly or knowing everything. Building a bot that reasons under uncertainty, rather than one that either knows nothing or knows everything, was the actual interesting problem in this whole project — most simple AI opponents skip straight from "random" to "cheats by seeing your hand," and I wanted a real middle tier that doesn't do either.
Hard had to remember, not cheat
The Hard bot tracks every card that's changed hands across the entire game and hunts specific ranks down with real card-counting instincts — which sounds like it's playing with hidden information, and it isn't. Everything Hard knows is information any attentive human player could also track by paying close attention to every ask and every "go fish" throughout the game. The bot doesn't see your hand; it remembers the public history perfectly, the way a genuinely sharp human opponent theoretically could. That distinction — perfect memory of public information, not access to private information — is what keeps Hard feeling like a legitimately skilled opponent to lose to, rather than one that's just cheating in a way dressed up to look like skill.
Setup had to make three independent difficulty dials feel approachable, not intimidating
Letting someone configure one to three opponents, each independently set to Easy, Medium, or Hard, is a lot of configuration surface for a game this otherwise simple, and I was wary of the setup screen itself feeling like more work than the game that follows it. Sensible defaults — landing on a single Medium opponent rather than an empty, unconfigured table — meant anyone who just wants to start playing immediately can ignore the configuration entirely, while the deeper customization stays available for anyone who specifically wants to stack three Hard bots against themselves or ease a newer player in against an all-Easy table.
Multiple opponents at independent difficulties multiplied the testing surface
Supporting one to three opponents, each independently set to any of the three difficulty tiers, means the actual number of distinct game configurations to verify is much larger than three. A table with one Easy and one Hard bot creates different dynamics than a table with three Mediums, since the bots' decisions affect each other's available information as cards move around the table. I had to test enough of these combinations to be confident the difficulty tiers hold up consistently regardless of table composition, not just in the specific single-opponent matchups I happened to test most during development.
Banking a Book had to feel decisive, not like a passive side effect
Collecting all four cards of a rank banks a Book automatically, and I wanted that moment to land as a real, celebrated milestone rather than quietly happening in the background of an otherwise ongoing hand. The game explicitly calls it out the instant it happens rather than folding it silently into the next turn, since a Book is the actual scoring unit the entire game revolves around — burying that moment inside the normal flow of turns would undersell the one event that most directly determines who's actually winning at any given point in the game.
Why the card animations mattered more than they would in a solitary game
Go Fish is fundamentally a social, table-read game even against computer opponents, which meant hand-drawn portraits and flying-card catch animations weren't just visual polish — they're doing real work making the table feel populated and alive rather than like a bare data-driven card exchange. A card game reduced to plain numbers moving between hidden piles would technically implement the same rules while losing almost everything that makes Go Fish specifically feel like Go Fish. The theatricality of watching a card actually fly from one hand to another is part of the game, not decoration layered on top of it.
Climbing the global Wins leaderboard gives every match a stake beyond the individual hand, which matters more for a game this quick and low-stakes on its own than it would for something with a longer natural arc already built in. A single round of Go Fish is short enough that without something tracking wins across sessions, there'd be very little pulling anyone back for a second match beyond simply wanting to play cards again, which is a fine reason on its own but not one I wanted to rely on entirely.
More from the Devlog