BLOG · .NET · IA

Un mini-jeu Pokémon créé en 20 minutes avec l'IA ? C'est possible !

Illustration : une Kernel Function et une Pokéball, l'IA choisit seule la fonction à exécuter

L'autre soir, j'ai voulu relever un défi : créer un mini-jeu Pokémon en .NET, sans base de données ni logique métier complexe, en moins de 30 minutes. Le secret ? Semantic Kernel et ses Kernel Functions, qui permettent de déléguer toute la logique à l'IA.

La démo en vidéo

Le jeu en action : l'IA gère l'équipe, les combats et les soins toute seule.

Comment ça marche ?

C'est simple : chaque action du jeu (choix du Pokémon, combat, soin...) est gérée par une Kernel Function, un plugin IA qui prend ses décisions de manière autonome. Plus besoin d'écrire des centaines de lignes de conditions ni de gérer une base de données : tout est géré dans vos Kernel Functions et grâce aux modèles de données que vous avez définis.

Les fameuses Kernel Functions sont dans un plugin que j'ai créé et appelé directement dans mon service :

[KernelFunction("get_team")]
[Description("Affiche les Pokémon de l'équipe du joueur avec leurs statistiques.")]
public async Task<TeamResponseModel> GetTeamAsync()
{
    return new TeamResponseModel
    {
        Team = _team,
        MainPokemon = _mainPokemon?.Name ?? "Aucun Pokémon principal n'est défini."
    };
}

[KernelFunction("heal_team")]
[Description("Soigne tous les Pokémon de l'équipe en restaurant leur santé maximale.")]
public async Task<string> HealTeamAsync()
{
    _team.ForEach(p => p.Health = p.MaxHealth);
    return "Pokémon soignés.";
}

[KernelFunction("get_main_pokemon")]
[Description("Affiche le Pokémon principal actuel.")]
public async Task<string> GetMainPokemonAsync()
{
    if (_mainPokemon == null)
    {
        return "Non défini";
    }

    return $"Pokémon principal : {_mainPokemon.Name}.";
}

Avec juste ces quelques fonctions bien nommées et des descriptions claires (start_game, get_team, heal_team, set_main_pokemon, fight_wild_pokemon, catch_random_pokemon...), l'IA comprend seule quelle fonction appeler en fonction de la question posée.

Une seule route pour discuter avec le jeu

Côté API, j'utilise une seule route. Peu importe l'action que je veux faire (démarrer une partie, soigner l'équipe, afficher les Pokémon...), j'appelle toujours la même route en échangeant naturellement, comme si je parlais à un ami :

[HttpPost("play")]
public async Task<ActionResult<string>> PlayGame([FromBody] AskQuestionWithSessionCommand command)
{
    if (string.IsNullOrWhiteSpace(command.Question))
    {
        return BadRequest(new { Error = "La question ne peut pas être vide." });
    }

    var result = await _gameService.PlayGameAsync(command);
    return Ok(result);
}

Le service : là où la magie opère

Le service branche le plugin, laisse l'IA choisir elle-même ses fonctions avec FunctionChoiceBehavior.Auto(), et conserve l'historique de conversation dans un cache : l'IA garde le contexte du jeu entre chaque appel et peut jouer une même partie en continu.

public async Task<string> PlayGameAsync(AskQuestionWithSessionCommand command)
{
    // Lier notre méthode au plugin qui contient nos kernel functions
    _kernel.Plugins.AddFromType<GamePlugin>("Game");

    // Récupérer le service de chat IA avec lequel on travaille
    var chatService = _kernel.GetRequiredService<IChatCompletionService>();

    // Définir les paramètres d'exécution du prompt
    OpenAIPromptExecutionSettings settings = new()
    {
        // L'IA choisit elle-même quelle Kernel Function utiliser
        FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(),
    };

    // Récupérer ou créer un historique de chat pour la session actuelle
    var chatHistory = _cache.GetOrCreate(command.SessionId, entry =>
    {
        entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10);
        var newHistory = new ChatHistory();

        // Message système pour définir le rôle de l'IA dans le jeu
        newHistory.AddSystemMessage(
            "Tu es un maître du jeu Pokémon et tu t'appelles El Profesor. " +
            "Tu dois aider le joueur à :\n" +
            "- Gérer son équipe\n" +
            "- Se battre contre des Pokémon sauvages\n" +
            "- Capturer de nouveaux Pokémon."
        );

        return newHistory;
    });

    // Ajouter la question de l'utilisateur dans l'historique
    chatHistory.AddUserMessage(command.Question);

    // Générer une réponse en fonction du contexte
    var result = await chatService.GetChatMessageContentAsync(
        chatHistory,
        executionSettings: settings,
        kernel: _kernel
    );

    // Ajouter la réponse de l'IA dans l'historique
    chatHistory.AddAssistantMessage(result.ToString());

    return result.ToString();
}

Notez le message système : c'est lui qui donne son rôle à l'IA (« El Profesor », maître du jeu Pokémon).

Les modèles de données

Le plugin déclare l'état du jeu et le pool de Pokémon, entièrement en mémoire :

public class GamePlugin
{
    private static List<PokemonModel> _team = new();
    private static PokemonModel? _mainPokemon = null;
    private static readonly List<PokemonModel> _pokemonPool = new()
    {
        new PokemonModel { Name = "Pikachu", MaxHealth = 50, Attack = 25, Defense = 12, Type = "électrique" },
        new PokemonModel { Name = "Bulbizarre", MaxHealth = 50, Attack = 20, Defense = 15, Type = "plante" },
        new PokemonModel { Name = "Salamèche", MaxHealth = 60, Attack = 22, Defense = 10, Type = "feu" },
        new PokemonModel { Name = "Carapuce", MaxHealth = 50, Attack = 18, Defense = 18, Type = "eau" },
        new PokemonModel { Name = "Dracolosse", MaxHealth = 120, Attack = 40, Defense = 20, Type = "dragon" },
        // ... le reste du pool
    };

    // kernel functions ...
}

public class PokemonModel
{
    public string Name { get; set; }
    public int Health { get; set; }
    public int MaxHealth { get; set; }
    public int Attack { get; set; }
    public int Defense { get; set; }
    public string Type { get; set; }
}

Et la Kernel Function qui démarre une partie :

[KernelFunction("start_game")]
[Description("Démarre une nouvelle partie en attribuant 3 Pokémon aléatoires et définit un Pokémon principal.")]
public async Task<string> StartGameAsync()
{
    var random = new Random();
    _team = _pokemonPool.OrderBy(x => random.Next()).Take(3).ToList();
    _team.ForEach(p => p.Health = p.MaxHealth);

    _mainPokemon = _team.First();

    return $"Equipe: {string.Join(", ", _team.Select(p => p.Name))} et pokemon principal: {_mainPokemon.Name}";
}

Le plus intéressant ?

Et elle ne se limite pas à exécuter bêtement une fonction. Elle enrichit chaque réponse de manière intelligente et surprenante : elle sait quelle fonction appeler ET comment enrichir sa réponse, sans que je le lui aie appris explicitement. Elle propose parfois des actions qu'on ne lui a même pas demandées, comme suggérer ce que le joueur pourrait faire ensuite.

Mon rôle ? Trois choses seulement

Résultat : l'IA peut gérer un jeu entier toute seule, en répondant toujours de manière cohérente au joueur.

Les points forts de l'approche

Développement ultra rapide : une Kernel Function = quelques lignes de code + un prompt en langage naturel. L'IA s'occupe du reste.

Architecture légère et flexible : pas de base de données, pas de backend complexe.

Plugins réutilisables : les Kernel Functions sont comme des Lego. Créez-en une fois, réutilisez-la partout.

Contrôle total sur l'IA : chaque Kernel Function peut être paramétrée pour affiner son comportement.

Et on peut aller bien plus loin

En 20 minutes, j'ai créé un jeu fonctionnel, mais ce n'est que le début. Avec Semantic Kernel, vous pouvez aussi créer des embeddings pour la recherche intelligente ou automatiser des tâches complexes. C'est exactement ce type de briques que j'ai ensuite mis en production sur un vrai projet : un bot RAG qui interroge toute la base documentaire d'une entreprise.

Semantic Kernel prend en charge C#, Python et Java.

À retenir : avec quelques noms de fonctions bien choisis, un peu de code et des modèles de données simples, on obtient une IA capable de comprendre toute seule comment agir. Les possibilités sont infinies.

Cet article est tiré d'une de mes publications LinkedIn : rejoignez la discussion ↗