Underdog Coach

Inspiration

Coaching skill is talked about constantly in football, but almost nothing lets an ordinary fan or grassroots coach actually practice it. You can read about marking schemes and defensive lines all day, but you never get graded on a real decision. We wanted a tactics trainer that plays — a fixed board, deterministic moves, an opponent that actually punishes your mistakes — with an AI coach standing next to you explaining exactly why a decision worked or didn't, by player name and by trait, not with generic "your defense is weak" platitudes.

What it does

Underdog Coach is a turn-based football tactics trainer. You set your formation on a fixed slot grid; an AI opponent manager commits its own counter-formation and specifically targets a real weakness on your roster (a pace mismatch, a fullback who doesn't track back, a keeper with poor distribution); a coach agent grades the turn — SOLVED, PARTIAL, or EXPOSED — against a real rules engine (offside, marking, defensive-line shape, pressing traps) and explains the grade in plain language, then a deeper technical breakdown on request. Every turn is one of three actions: swap a pawn with an adjacent teammate, change formation (rare, costly, on a cooldown), or bring on a substitute from the bench. A scoreboard tracks the match toward a target score across a bounded number of turns, and a roster manager lets you add, edit, and delete players — complete with stat blocks, tagged strengths and weaknesses, and a randomly assigned photo — on either team.

How we built it

The backbone is four Strands agents on Amazon Bedrock: a Match Director that designs the scenario for a new match (delegating the actual scenario prose to a Scenario agent — a genuine agent-to-agent call, not backend code chaining two requests), an Opponent Manager that commits a counter-formation and narrates why it's targeting a specific player, and a Coach that explains the graded outcome of every turn. A fifth, Progress, is pure session memory — it watches for a recurring weakness across rounds and feeds it back into the next match.

The one rule we never broke: ground truth is computed in code and handed to the LLM, never the other way around. Pawn movement lives on a fixed, deterministic slot grid (grid_movement.py) so a "move" is always one legal step, never a corner-to-corner jump. Whether a turn is SOLVED or EXPOSED is decided by a real rules engine reading actual pawn coordinates, not by a model grading its own homework. The agents only ever narrate and explain — they never place their own pieces or decide their own grade. Match, turn, and player state persist in Postgres via Docker Compose, and the frontend is a Next.js app styled like a FIFA career-mode menu: a stadium backdrop with sweeping floodlights and drifting cloud cover, animated pawns with real player photos, directional arrows showing the opponent's plan, and an end-of-match summary modal.

Challenges we ran into

The very first version let players drag pawns freely, which meant a fullback could "move" clean across the entire pitch in one turn — not remotely realistic, and it made the AI's counter-play feel arbitrary rather than earned. We rebuilt movement entirely around a fixed grid with king-move adjacency, which meant solving a much harder problem: a starting XI fills every slot on the grid, so almost every move is actually a mutual swap between two players, not a move into empty space — the deterministic movement and opponent-planning logic had to be redesigned around that from scratch.

Grading was its own rabbit hole. Early on, the same two players kept getting flagged as the exploitable matchup every single round, which made the "coach" feel broken rather than perceptive — we had to add a rotation and a windowed memory so recurring-weakness detection didn't lock onto the first pair it found. Later, once formations were changed to spread across the whole pitch (realistic width, not confined to half the field), the SOLVED verdict became almost unreachable, because the geometry that made a defender "covered" only worked in one narrow formation. We ended up tracing the actual marking radius math match by match until the grading felt fair across every formation, not just a lucky one.

Accomplishments that we're proud of

Getting movement, scoring, and verdicts to be fully deterministic and reproducible — while still having four separate Bedrock agents doing real narration, delegation, and tool-calling around that deterministic core — is the thing we're most proud of. The system never lets an LLM invent an illegal move, grade its own performance, or contradict the numbers it's citing, and it still degrades gracefully to a scripted fallback if Bedrock is ever unreachable, so a live demo never hard-fails on stage.

What we learned

The biggest lesson was how much a genuinely agentic system depends on not trusting the model with anything that has a correct answer. Every time we let an agent freely decide something with real consequences — a grid position, a verdict, which defender is exposed — it eventually drifted from what the UI or the score actually showed. Moving that decision into code and only asking the model to explain it fixed the bug and made the coaching feel more trustworthy at the same time.

What's next

Real authentication in place of the current fake login gate, moving the turn-based match system onto the AWS deploy path (it's currently local Docker/Postgres only), and letting a coach build and save their own club's roster from scratch instead of starting from the two demo teams.

Built With

Share this project:

Updates

Submission history