Skip to content

· 15 min read

Soccer Deal: a football manager where the AI talks and the rules decide

From Elifoot and Brasfoot to Soccer Deal: how I built a football manager that runs in the browser, the tools, the game mechanics, the match decision engine and the AI behind the negotiations, with the architecture explained in C4.

From Elifoot to Brasfoot

Many people of my generation learned what a computer was by picking a football team. Elifoot was born in Portugal in 1987, created by André Elias while he was still a teenager, and it crossed the ocean: the 1990s versions, like Elifoot 98, ran on many school, internet café and office PCs in Brazil. Simple screens, renamed clubs, and hours and hours trying to take a small team to the top division.

In the early 2000s came Brasfoot, with the feel of Brazilian football: state championships, the Copa do Brasil, the national league divisions, wages, ticket sales, the transfer market. It was the game of a whole generation, and many friendships went through arguments about the best formation for your favorite club.

I wanted to study more and try new things, and a game like that was the perfect project for it. On top of that, there was something about those classics that always bothered me: signing a player was a form. You typed in a fee, the club accepted or refused, and that was it.

In real football, signing a player is a conversation. The other club's chairman haggles, bluffs, makes a scene, remembers that you let him down last time. That is where Soccer Deal came from: a football manager where every signing is a negotiation, a tribute to those games with a tool they didn't have.

A lab for AI

Soccer Deal was also my lab for a question that interests me as a technology leader: where does generative AI really improve a product, and where does it get in the way? The answer I found became the project's golden rule:

The AI talks, the rules decide.

In this article I explain how the game was built: the tools, the mechanics, the engine that decides the matches and the AI behind the conversations. For the architecture, I use the C4 model, from context down to code, always with diagrams.

The tools

Just like this site and amardoar, Soccer Deal was built in sessions with Claude Code, Anthropic's coding agent, working directly in the repository. I described the intent and reviewed every delivery; the AI proposed, implemented, tested in the browser and opened the pull request. That came to more than 50 pull requests in a little over a week, each one small and reviewed before merging.

The stack is deliberately simple:

  • Eleventy 3 builds the static site. A step after the build gives every stylesheet and script an address that changes when its content changes, so the cache never serves a stale file.
  • Plain JavaScript, with modules and no framework. About 4,300 lines split into screens, rules and the match engine.
  • PHP 8.2 and MySQL on Hostinger for the small part that needs a server: the AI premium, the account and the cloud save, with the official Anthropic SDK and PHPMailer.
  • Claude, through the Anthropic API, for the lines of the chairmen, the press and the board. The model can be changed without a new deploy; I started with Claude Opus at low effort.
  • ElevenLabs for every generated sound and image: stadium effects, the opening music, the narrator with emotion tags, the opening video and the match illustrations.
  • ffmpeg and cwebp to process those files: measure the loudness of each sound, find the peak of the music to sync the crest, compress the video from 12.9 MB to 1.1 MB and crop the illustrations.
  • Automated API tests, running the whole account, simulated AI and limits flow on a local server, without touching production.

Architecture: the context

At the highest C4 level, Soccer Deal has one person and few neighbors. The player uses the game in the browser. The game talks to the Anthropic API, sends the sign-in email through Hostinger's SMTP and measures visits on Google Analytics, only with consent.

C4 level 1, context: the player uses Soccer Deal, which talks to the Anthropic API, sends email through Hostinger SMTP and measures visits on Google Analytics with consent. ElevenLabs generates the sounds, music, videos and illustrations beforehand, outside the game. Player [Person] browser or phone Soccer Deal [Software system] football manager where every signing is a conversation plays Anthropic API [External system] Claude models Hostinger SMTP [External system] sign-in link email Google Analytics [External system] only with consent character dialogue sends link visits ElevenLabs [Tool, outside the game] sounds, music, video, images files generated once
Figure 1. C4, level 1 (context): who uses Soccer Deal and which systems it talks to. ElevenLabs is dashed because it works beforehand, generating files, not during the game.

ElevenLabs is dashed on purpose. It doesn't take part in the game: it works beforehand, generating files that become part of the site. No sound is generated while you play, which keeps the ElevenLabs cost fixed instead of growing with the number of players.

Architecture: the containers

The most important decision of the project is at the second level: the whole game runs in the browser. Calendar, squad, finances, market, match engine and saved game live in the browser itself. You can play an entire season without a single call to the server.

C4 level 2, containers: the static site delivers the app that runs in the browser; the app keeps the game in local storage and calls the PHP API only for AI, accounts and saved games; the API uses MySQL, the Anthropic API and SMTP. Soccer Deal [Software system] Browser app [JavaScript, ES modules] screens, rules, match engine and the save in local storage no game server Static site [Eleventy, files on Hostinger] HTML, CSS, hashed JS, sounds, videos, illustrations API [PHP 8.2, official Anthropic SDK] AI premium, limits, account and cloud save JSON over HTTPS Database [MySQL; SQLite in tests] accounts (hashed email), sessions, saves, AI usage Anthropic API [External system] Hostinger SMTP [External system] Messages API, JSON Schema output sign-in link Player [Person] plays in the browser
Figure 2. C4, level 2 (containers): the whole game runs in the browser. The server only steps in for the AI premium, the account and the cloud save.

The server only shows up in three cases: when the AI needs to talk, when you sign in and when your career is uploaded to the cloud. That has three consequences I wanted:

  1. Cost close to zero. Hosting static files is cheap, and the server spends nothing on people who play without an account or premium.
  2. Speed. Every click responds instantly, because it doesn't wait for the network.
  3. AI is optional. If the Anthropic API goes down, the game goes on, with ready-made lines.

The game mechanics

You pick a Série A club, receive the board's target and play the season: the national league with 38 rounds, Série B with promotion and relegation, your club's state championship and the Copa do Brasil knockout cup, with penalty shootouts. Squads start from public data; clubs use alternative names and the chairmen are fictional characters, as in the classics that inspired the game.

Under the hood, four systems feed each other:

  • Squad: every player has strength, position, fitness, morale and recent form.
  • Money: ticket sales per match, TV rights, wages paid every month, prize money and stadium works.
  • Confidence: four gauges from 0 to 100, for the board, the fans, the squad and the press.
  • Market: transfer windows, with every chairman having one of six personalities (old fox, pragmatic, vain, penny-pincher, in debt and emotional) and a memory of how you negotiated with him.

The match decision engine

The match is decided by a simple, explainable engine that follows five steps.

The match engine in five steps: each player's output, the team's strength by sector, the expected goals on each side, the draw of events per half and the live match, which acts out that plan. 1. Player strength × fitness × form × morale tired: up to −20% out of position: −10% a −20% 2. Team three strengths: attack, midfield and defense the formation sets who adds to each 3. Expected goals attack against the rival defense, cubed home advantage based on the crowd and the stance 4. Draw Poisson, per half goals, chances, corners and fouls each event gets its own minute 5. Live the match acts out the plan changed stance or player? the rest is replanned changes during the match
Figure 3. The match engine: from each player's output to the event on the screen. The same calculation simulates the other clubs' matches in milliseconds.

First, each player becomes an output number: strength adjusted by fitness, recent form and morale. An exhausted player performs up to 20% worse; morale moves it up to 3%, and form up to 6%. Out of position, he loses 10% to 20%.

Then the team becomes three strengths: attack, midfield and defense. Attack adds up forwards and midfielders; defense adds up defenders, goalkeeper and midfielders. By comparing one side's attack with the other's defense, the engine works out how many goals each team should score on average. That comparison is cubed, so the gap in quality shows up on the scoreboard. Home advantage isn't fixed: it depends on the expected crowd and how full the stadium is, and the crowd boost, which you can trigger up to three times per match, pushes the home side for eight minutes. An attacking stance increases goals on both sides.

With the average goals in hand, the engine draws the events of each half with a Poisson distribution, the same one used to estimate scores in real football. It is what makes the favorite win more often, without making upsets impossible:

Chance of each score for two teams: the stronger home side, averaging 1.6 goals, scores 1 goal in 32% of matches and 2 in 26%; the weaker away side, averaging 0.9 goals, is held scoreless in 41% of matches but scores at least once in almost 6 out of 10. Strong home side (avg. 1.6 goals) Weaker away side (avg. 0.9) team goals in the match 20% 41% 0 32% 37% 1 26% 16% 2 14% 5% 3 6% 1% 4 2% 0% 5
Figure 4. Why upsets happen: given each side's average goals, the Poisson draw gives the chance of each score. The weaker team is held scoreless in 41% of matches, but scores at least once in almost 6 out of 10.

The live match you watch is the acting out of that plan: goals, chances, corners and fouls, each in its own minute. When you change the stance, the formation or a player, the engine replans the remaining minutes.

This approach has an advantage I value a lot: any result can be explained. The same calculation simulates the other clubs' matches in milliseconds, which keeps the whole table consistent.

Confidence, the board and getting fired

Confidence is what turns results into a story. Each gauge slowly returns to its balance point, and results push it around. The board looks at your position against the target and at the cash; the fans feel a home defeat more; the press reacts to the tone of your answers at the press conference.

When the board's confidence reaches 10 or less, after at least six rounds, the chairman calls you into a meeting. You defend yourself and may get five more rounds, or leave fired. Once fired, offers arrive from other clubs, and your career continues with its history.

In premium mode, you write your defense in your own words. But the AI doesn't decide whether you stay. It only classifies the argument, and the rules do the rest:

Weight of each kind of argument on the chance of getting more time in the board meeting: strong adds 30 percentage points, reasonable adds 12, weak takes 8 and offensive takes 40. chance of getting more time (percentage points) Strong argument +30 Reasonable +12 Weak −8 Offensive −40
Figure 5. In the board meeting, the AI only picks which category your argument fits. The weight of each category on the chance of getting more time is a game rule.

The components

At the third C4 level, the browser app is organized into parts with clear responsibilities. The rules are kept apart from the screens, and everything that uses AI goes through a single client.

C4 level 3, components of the browser app: screens and state; match engine; confidence; board; negotiation with guardrails; premium client; account and cloud; sound and opening. Negotiation, press conference and board meeting go through the premium client to the API. Browser app [Container: JavaScript] Screens and state JavaScript, modules squad, market, table, finances Match engine rules, no AI strength, expected goals, Poisson Confidence rules, no AI board, fans, squad, press Negotiation + guardrails rules + optional AI secret floor and price range Press and board talks optional AI effect decided by rules Board rules, no AI meeting, deadline, firing Premium client HTTPS + JSON short context, daily balance Account and cloud HTTPS + gzip gzip, version, conflict Sound and image Web Audio, video sounds, opening, scenes 10 or below PHP API (Figure 2): AI, account and saved game
Figure 6. C4, level 3 (app components): the rules live in their own modules. Everything that uses AI goes through a single client, which sends a short context and gets back an answer in a fixed format.

This separation was built step by step. The game started as a single file and was split as it grew: first the state, then the tabs, then the match engine and the confidence and board rules. Each split was its own pull request, with the game working before and after.

The AI behind the negotiations

This is the heart of the project. When you make an offer, the sequence is always the same, and the AI takes up only one step of it.

C4 level 4, negotiation code: the offer comes in; the rules compute the secret floor and the range; the AI returns dialogue and decision in JSON; the guardrails fix decisions outside the rules; the game state is updated. Without AI, a ready-made line is used. 1. Offer cash, swap and the manager's free text 2. Rules secret floor may / must accept counteroffer range, patience 3. AI talks chairman's line decision, new ask, persuasion, memory JSON Schema 4. Rules decide accept when not allowed? becomes a counteroffer price out of range? gets clamped 5. State cash, squad, relationship and chairman's memory Ready-made line no premium, AI down or daily limit reached persuasion: ±4% on the floor per message, up to ±12%
Figure 7. C4, level 4 (code): the path of an offer in a negotiation. The AI writes the dialogue; the rules decide the price and the deal.

First, the rules compute the limits. Every negotiation has an asking price and a secret floor, which depend on the player's value, whether he is a starter, the chairman's personality and your relationship. From the offer, the game knows whether the chairman may accept, whether he must accept and the allowed range for a counteroffer.

Then, the AI talks. Claude receives the character, the situation, the secret rules and the latest messages. The answer must come back in a fixed format, validated against a schema: the chairman's line, the decision (accept, counteroffer or walk away), the new asking price, a persuasion score (−1, 0 or 1) and a memory sentence. There is no loose text and no answer outside the format.

Finally, the rules decide. Before applying anything, the game checks the answer. If the AI said "accept" and the offer is below the floor, it becomes a counteroffer. If the new asking price falls outside the allowed range, it gets clamped. And persuasion has a limit: a good argument lowers the floor by 4%, up to 12% in total; rudeness does the opposite, costs patience and hurts the relationship.

The AI gets real power, but limited. A compliment that lands or a creative offer lowers the price. And the memory sentence the chairman writes when closing or ending the deal comes back in your next conversations with him. That is what makes the negotiation feel alive without leaving the game at the mercy of an unexpected answer.

The press conference works the same way: the AI writes the questions and rates the tone of your answer in a category; the effect on the press's confidence is a game rule.

AI cost and safety

Putting a paid AI on an open website takes guardrails. All of them live on the server:

  • Validated, trimmed context. The browser sends only bounded numbers and short texts; the API builds the model's instructions on its side. The player can't send instructions of their own.
  • Reservation before the call. Before calling Claude, the API records the highest possible cost of the message. If the connection drops halfway, the reservation keeps that cost; if the API answers with an error, the reservation is canceled. The spending cap never gets loose.
  • Layered limits: messages per day per invite, messages per network address, blocking of code guessing and a daily spending cap for the whole site. When the cap is hit, the game falls back to ready-made lines until the next day.
  • Simulated mode for tests, which follows the same rules without spending anything.

By my numbers, a whole negotiation costs a few cents of a dollar. If usage grows, switching to a smaller model is a configuration change.

Passwordless account and cloud save

The account is optional and has no password. You type your email and receive a link that is valid for 15 minutes and works only once. The database stores the email only as a scrambled code, as it does with links, sessions and premium codes.

The saved game is uploaded compressed a few seconds after each change. To handle two devices, each upload says which cloud version it started from. If the cloud is already newer, because you played on your phone, the server refuses the write and the game asks which career you want to keep, instead of overwriting it silently.

Sound and image with ElevenLabs

Sound was the most fun part. Each effect is born from a description: whistles, crowd, boos, the "ooh" of a missed chance. The crowd reacts to the score and to who is playing at home. The narrator speaks Portuguese with emotion, and the opening music peaks around the 9-second mark, where the crest appears.

The opening is a video generated on ElevenLabs, compressed and synced with the music. The match events got a faceless character, the same in every scene: the referee showing a card or the substitution board, the player celebrating a goal. I asked for the images with the kit in pure green and the background in pure magenta. That way, the processing can remove the background and turn the kit into a mask, which the game paints in each club's color:

The same illustration of the player celebrating a goal, in four versions: with the kit in shades of gray, as it comes out of processing, and painted red, white and blue, with the shading preserved. Before color kit in shades of gray Red White Blue
Figure 8. One illustration for all 40 clubs: the kit becomes a mask, and the game paints it in each club's color, keeping the shading.

What I learned

AI works better as an interface than as a referee. It is great at understanding free text and answering with personality. To decide prices, results or firings, explicit rules are fairer, cheaper and easier to explain.

Design for failure before success. Every use of AI has a ready-made answer behind it. AI is a layer that enriches the game, never a dependency of it.

Cost is an architecture requirement. Running the game in the browser, generating the sounds only once and reserving the cost before calling the API were architecture decisions, not optimizations made later.

Small and reviewable beats big and fast. More than 50 small pull requests, each with the game working, left a history that tells how the project evolved and made any mistake easy to undo.

Soccer Deal is at version 1.0.0 and you can play it now, for free, in your browser: fghisi.com.br/soccer-deal. It is my tribute to Elifoot and Brasfoot. Enjoy the game, coach.