Part 2 -🚀 Creating a Realistic Soccer Match Simulator Using AI and Prompt Engineering — Integrating AI-Generated Match Narratives into our Symfony-Based Soccer Manager Simulator— Symfony [PHP]
Hey again, friends! đź‘‹
Remember my last post about that soccer simulation project I’ve been working on?
Yep, the one where I was dabbling with PHP and then decided to throw in a dash of AI magic. Well, I’m back with more updates and adventures from this coding journey!
RECAP
In the first part, I shared how I started with a basic PHP-based soccer simulator — it was decent, and functional, but nothing too fancy. But then, curiosity got the better of me, and I wondered, “What if AI could join the party?” So, I started experimenting with integrating AI into the simulation to see if it could add a new layer of depth and realism.
The challenge was exciting: I crafted prompts that would guide the AI to create a narrative that captures the essence of a soccer match — the drama, the strategies, and the unexpected twists. The idea was to make the AI generate a match story in a structured JSON format, which would be easy to integrate back into the PHP framework. It wasn’t just about coding; it felt like being a chef mixing ingredients in a tech kitchen, balancing creativity with technical expertise.
And guess what? The result was a fresh, AI-generated soccer match narrative, complete with a connected timeline of events, player performance changes, and all the little details that make soccer so thrilling. It was a JSON-formatted story, ready to be woven into the PHP simulator.
Mixing AI with PHP
Okay, so you know how I was playing around with that AI thing to create soccer match stories? I finally figured out how to make it work with my PHP soccer simulator! It’s like teaching two different languages to talk to each other.
Hey, so let me tell you about this cool thing I built for our Soccer Manager Simulator. It’s like crafting a mini-story every time a match happens in the game.
The Recipe for Soccer Stories
First up, I created this little service in the simulator. Think of it as a storyteller. What it does is pretty neat — it takes the teams and their players and cooks up a prompt. This prompt is like a story starter, telling the AI, “Hey, we’ve got these teams with these players. Now, give us a thrilling soccer match tale.”
<?php
namespace App\Service;
use App\Entity\MatchGame;
use App\Repository\PlayerRepository;
use App\Service\ChatGPT;
class SimulateMatchByAi
{
private $chatGPT;
private $playerRepository;
public function __construct(ChatGPT $chatGPT,PlayerRepository $playerRepository)
{
$this->chatGPT = $chatGPT;
$this->playerRepository = $playerRepository;
}
public function generateSoccerMatchSimulationPrompt(MatchGame $match){
$homeTeamPlayers = $this->playerRepository->findBestTeamFor442Formation($match->getHomeTeam()->getId());
$awayTeamPlayers = $this->playerRepository->findBestTeamFor442Formation($match->getAwayTeam()->getId());
$prompt =
"Vars:
[HomeTeam]: ".$match->getHomeTeam()->getName().".
[AwayTeam]: ".$match->getAwayTeam()->getName().".
[HomeTeamPlayers] = [ ";
foreach($homeTeamPlayers as $player){
$prompt = $prompt . '[Name='.$player->getName().',';
$prompt = $prompt . 'Position='.$player->getPosition().',';
$prompt = $prompt . 'Ability='.$player->getAbility().']';
}
$prompt = $prompt . ']';
$prompt = $prompt . '[AwayTeamPlayers] = [';
foreach($awayTeamPlayers as $player){
$prompt = $prompt . '[Name='.$player->getName().',';
$prompt = $prompt . 'Position='.$player->getPosition().',';
$prompt = $prompt . 'Ability='.$player->getAbility().']';
}
$prompt = $prompt .'] ';
$prompt = $prompt .
'TASK:Create a realistic timeline between '. $match->getHomeTeam()->getName().' and '. $match->getAwayTeam()->getName().' ,
JSON: {
timeline = {time, action, team, description } Always 10 events always complete the 10 events,
homeTeamScore = Calculation based on the timeline,
awayTeamScore = Calculation based on the timeline
}';
return $prompt;
}
public function simulateMatch(MatchGame $match){
$prompt = $this->generateSoccerMatchSimulationPrompt($match);
$response = $this->chatGPT->sendChatMessage($prompt);
// Decode JSON string to PHP array
$decodedArray = json_decode($response, true);
// Check for errors in decoding
if (json_last_error() !== JSON_ERROR_NONE) {
throw new \Exception(json_last_error() );
}
return $decodedArray;
}
}
Chatting with the AI
Next, we send this prompt over to OpenAI — yes, that’s where the AI brains live. It’s like sending a text message to a super-smart buddy who knows a ton about soccer. We’re like, “Here’s our match setup. Can you tell us a story?”
<?php
namespace App\Service;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Gioni06\Gpt3Tokenizer\Gpt3TokenizerConfig;
use Gioni06\Gpt3Tokenizer\Gpt3Tokenizer;
class ChatGPT
{
private string $botEndpoint;
private Client $httpClient;
private string $apiKey;
private $systemContent;
public function __construct( ) {
$this->botEndpoint = $_ENV['CHAT_GPT_ENDPOINT'];
$this->apiKey = $_ENV['CHAT_GPT_SECRET_KEY'];
$this->httpClient = new Client();
$this->systemContent =
'Role:Manolo Lama-style Commentary, emulate the enthusiastic and detailed style of Manolo Lamas sports commentary.
Task:Simulate a detailed, interconnected soccer match between two specified teams.
';
}
public function sendChatMessage( $prompt)
{
set_time_limit(-1);
$promptTokens = $this->getPromptTokens($prompt);
$maxTokens = 4096;
$tokens = $maxTokens - $promptTokens;
$messages = [
['role' => 'system', 'content' => $this->systemContent],
['role' => 'user', 'content' => $prompt],
];
$headers = [
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . $this->apiKey,
];
$body = json_encode([
'model' => 'gpt-3.5-turbo-1106', // Specify the model
"response_format"=> ["type"=> "json_object" ],
'messages' => $messages,
'temperature' => 0.7,
'max_tokens' => $tokens,
'top_p' => 1,
'frequency_penalty' => 0.5,
'presence_penalty' => 0.5,
]);
try {
$response = $this->httpClient->request('POST', $this->botEndpoint, [
'headers' => $headers,
'body' => $body,
]);
$responseData = json_decode($response->getBody()->getContents(), true);
$answer = $responseData['choices'][0]['message']['content'] ?? null;
return $answer;
}catch (GuzzleException $e) {
// Handle the exception
throw new \Exception('Request failed: ' . $e->getMessage());
}
}
private function getPromptTokens($text){
$tokenizer = new Gpt3Tokenizer(new Gpt3TokenizerConfig());
$numberOfTokens = $tokenizer->count($text);
return $numberOfTokens;
}
}
Magic from the Machine
Then, we wait for a bit, and voila! The AI comes back with this awesome narrative. It’s like getting a play-by-play of a soccer match, but it’s all AI-generated. It tells us what’s happening in the game, the exciting moments, the goals, the near-misses — the whole shebang.
[
0 => [
"time" => 5
"action" => "goal"
"team" => "home"
"description" => "Leighton Ellis scores a brilliant goal with a powerful strike from outside the box, giving Monaco Crew an early lead!"
]
1 => [
"time" => 20
"action" => "yellow card"
"team" => "away"
"description" => "Seamus McCoy receives a yellow card for a reckless challenge on Colson Stevenson."
]
2 => [
"time" => 35
"action" => "substitution"
"team" => "home"
"description" => "[In]: Alexei Vasquez [Out]: Torin Phelps. Monaco Crew's manager looks to solidify the midfield."
]
3 => [
"time" => 40
"action" => "goal"
"team" => "away"
"description" => "Callahan Davis finds the back of the net with a clinical finish after a well-worked team play, equalizing for Fenerbahce Hull!"
]
4 => [
"time" => 55
"action" => "substitution"
"team" => "away"
"description" => "[In]:Gareth Haynes [Out]:Tobin Guerra. Fenerbahce Hull's manager makes an attacking change."
]
5 => [
"time" => 62
"action" => "red card"
"team" => "home"
"description" => "Luke Ellis is shown a straight red card for a dangerous tackle on Dylan Maynard. Monaco Crew down to ten men!"
]
6 => [
"time" => 70
"action" => "goal"
"team" => "away"
"description" => "Joseph Clark capitalizes on the numerical advantage with a precise shot into the bottom corner, giving Fenerbahce Hull the lead!"
]
7 => array: [
"time" => 80
"action" => "substitution"
"team" => "home"
"description" => "[In]:Rafael Diaz [Out]:Micah Fox.Monaco Crew make their final substitution in search of an equalizer."
]
8 => array: [
"time" => 85
"action" => "yellow card"
"team" => "home"
" description " => "Seamus Ward receives a caution for dissent as frustration grows in Monaco Crew's camp."
]
9 => array: [
" time " => 90
" action " => " goal "
" team " => " home "
" description " => " Colson Stevenson rises highest to head in John Harris' pinpoint cross, rescuing a point for Monaco Crew in stoppage time!"
]
"homeTeamScore" => 2
"awayTeamScore" => 1
]
#[Route('/match/simulate/{id}', name: 'app_game_match_simulate', methods: ['POST'])]
# @Security("is_granted('ROLE_ADMIN') or is_granted('ROLE_USER')")
function simulateMatch(Game $game,SimulateMatchByAi $simulateMatchByAi, MatchGameRepository $matchGameRepository, EntityManagerInterface $entityManager): Response
{
$match = $matchGameRepository->findNextMatchForTeam($game->getTeam()->getId(),$game->getId());
$result = $simulateMatchByAi->simulateMatch($match);
$homeTeamScore = (int)$result['homeTeamScore'];
$awayTeamScore = (int)$result['awayTeamScore'];
$timeline = $result['timeline'];
$match->setHomeTeamScore($homeTeamScore);
$match->setAwayTeamScore($awayTeamScore);
$match->setEvents($timeline);
$entityManager->persist($match);
$entityManager->flush();
return $this->redirectToRoute('app_game_match_play', ['id'=>$game->getId()]);
}
I know, I know, it looks a bit geeky, but it’s actually pretty straightforward once you get the hang of it!
Why I’m Sticking with ChatGPT-3
Alright, so there’s a newer, fancier AI version out there (ChatGPT-4), but I’m sticking with ChatGPT-3. Why? It’s all about keeping the costs down while still getting the job done. Sure, it’s like opting for a reliable sedan over a flashy sports car — not as sleek, but gets you where you need to go!
It Ain’t Perfect, But It’s Ours
It’s not all roses, though. Sometimes the AI gets a bit wonky and spits out weird JSON stuff or half-baked match events. But that’s just part of the charm!
And that’s a wrap on today’s update!
It really gets you thinking, doesn’t it? Could we be the first to mesh an AI-simulated soccer match with a PHP framework like Symfony? It’s kind of stepping into new territory — a bit exciting and a bit nerve-wracking!
Right now, our AI isn’t perfect — it’s like a new player who’s still learning the ropes. But the potential is huge. Think of AI as a teammate that never stops improving.
Each day, it gets smarter and better at what it does. So, while today’s AI-generated soccer matches might have a few rough spots, they’re quickly evolving.
We’re heading towards creating matches that are more lifelike, more unpredictable, and way more thrilling than traditional, manually-created simulations. Imagine games that are so realistic and gripping, you can’t help but be totally absorbed!
I’d love to hear your ideas. What features would make our Soccer Manager Simulator a must-play for you? Got any cool suggestions or wild dreams for it? Drop them in the comments. Your thoughts are like gold dust — they really help give this project direction and life.