TP1 (Super Cartes Infinies)
Le projet
Développer un jeu de cartes (avec un style similaire à Hearthstone). Le projet se fait en équipe de 3 ou 4.
Utilisation de l'IA
Niveau 3. Usage élargi – Collaboration avec l’IAG dans un cadre pédagogique défini
Mais il faut respecter les règles!
Évaluations
- Diagramme de classes (Remise à la Rencontre 3.2)
- Évaluation individuelle de DevOps (Remise à la Rencontre 5.2)
- Évaluation individuelle (Évaluation à la Rencontre 6.1, sauf tâche Hub à la Rencontre 6.2)
- Évaluation de groupe (Évaluation à la Rencontre 8.1)
L'application
Objectifs :
- Analyser un problème et le découper en User Stories et Tasks à l'aide d'Azure DevOps Boards.
- Créer un diagramme de classes de la solution.
- Compléter un application web à l’aide de React, Web API et MVC pour faire un jeu de cartes de style HearthStone.
- Le site React sera utilisé par des membres inscrits en utilisant des tokens pour l'authentification.
- L’administrateur, le game designer et l'artiste pourront configurer le contenu de l’application à l’aide de MVC.
- Il n'est pas nécessaire d'écrire des tests pour le premier TP, mais nous allons en écrire pour les 2 prochains.
Les règles :
- Deux joueurs s’affrontent avec leurs cartes.
- Chaque joueur pige un nombre de cartes configurable dès le départ.
- Chaque joueur pige une carte au début de son tour.
- Chaque joueur reçoit une quantité configurable de mana au début de son tour.
- Chaque carte a une certaine quantité de points d’attaque, une certaine quantité de points de défense et un coût en mana.
- Les joueurs ne pourront pas encore jouer de carte avant le TP2. Nous allons voir les règles du jeu plus en détail à ce moment.
- Un joueur peut terminer son tour. Le tour de l’autre joueur commence alors et il reçoit sa carte et son mana.
- Un joueur peut abandonner la partie et il perd alors automatiquement (l’autre joueur a une victoire).
Clarification :
Le mot « carte » est utilisé très fréquemment, mais il faut comprendre que l’on ne parle pas toujours du modèle de donnée (Card). En effet, lorsqu’on configure le jeu, le mot carte fait référence au « modèle » de la carte. Mais si on mentionne qu’un joueur possède des cartes, on a besoin d’un modèle qui permet d’associer une carte à un joueur. Le premier livrable du projet est un diagramme de classe qui va permettre de clarifier tout ça.
Contraintes
- Le travail doit être effectué en équipes de 3 ou 4.
- Vous devrez utiliser Git/GitHub.
- Vous devrez utiliser Azure DevOps pour la gestion des tâches.
- Vous devrez compléter une application cliente en React.
- Vous devrez compléter une application serveur en ASP.NET MVC et WebAPI.
Les tâches individuelles (une par membre de l'équipe)
- [Obligatoire] SignalR
- [Obligatoire] Cartes
- [Obligatoire] Decks
- Section d'administration (MVC)
Livraisons du TP
Matériel de départ
Diagramme de classe
Voici le diagramme de classe à compléter. Vous devrez compléter le diagramme dans le README.md du repo de votre projet .NET.
Pour vous aider, regardez les endroits qui mentionnent le modèle de données.
Vous pouvez consulter la documentation de Mermaid si vous le désirez : Diagrammes Mermaid.
Un autre outil plus simple pour voir votre diagramme.
Repos GitHub avec classroom50
Groupe A26 1010: Serveur départ Client départ
Groupe A26 1020: Serveur départ Client départ
Une fois que vous avez les 2 repos, il faut créer les branches.
- Serveur:
- Decks
- SignalR
- MVC (Si vous êtes 4 dans votre équipe)
- Client:
- Decks
- Cartes
- SignalR
Chaque étudiant doit travailler dans la bonne branche pour son travail. Une fois que les parties individuelles seront terminées, vous allez pouvoir tout ramener vers la même branche. Nous allons voir comment bien le faire en classe!
Le détail des tâches individuelles
SignalR (Hub)
- Ajouter les events
DrawCardEventetGainManaEventdansPlayerStartTurnEventen passant la quantité de Mana à partir duMatchesService. - Ajouter les events
DrawCardEventdansStartMatchEventen passant le nombre de cartes à partir duMatchesServices. - Pour faire fonctionner SignalR, vous allez devoir modifiez vos CORS dans Program.cs. Il faut ajouter:
policy.AllowCredentials();
- Ajoutez le code nécessaire pour faire fonctionner le [Authorize] avec SignalR et les tokens (Voir SignalR #2)
- Utilisez [Authorize] dans le Hub pour s'assurer que l'utilisateur est bien connecté
- Il faut modifier match-context.tsx pour utiliser la variable myPlayerId pour qu'elle soit celle du joueur connecté (Il y a un TODO)
La logique pour faire fonctionner le jeu est déjà présente, l'objectif c'est vraiment d'utiliser SignalR pour obtenir l'information du match et la faire parvenir au client qui a déjà un match-context.tsx qui contient le nécessaire pour afficher le match!
- Du côté client, il existe déjà un Context qui se nomme fake-signalr-context.tsx. Il utilise des données locales pour afficher un match, mais il faut le transformer (ou en faire une copie pour le remplacer) pour utiliser signalR et les méthodes de notre Hub.
- Ajouter la logique pour se connecter au Hub
- Ajouter la logique pour les méthodes
JoinMatch,StopJoiningMatch,EndTurnetSurrenderqui vont utiliser les méthodes du même nom qui existe dans MatchesService sur le Hub et il faut ensuite les utiliser avec le client. (ATTENTION: Il y a un annexe à la fin de cette page avec plus d'informations à propos de la logique de connexion à un match.) - Une fois la tâche terminée on peut faire une partie avec deux clients, donc:
- démarrer un match et voir que les deux joueurs pigent leurs cartes tour à tour
- annuler le join match
- faire terminer (quand c'est son tour) et voir que l'adversaire pige une carte (et que ça devient son tour)
- abandonner un match pour y mettre fin
- faire à nouveau un join match pour jouer à nouveau
L’affichage du Mana ne devrait pas encore fonctionner, mais tout devrait s’arranger une fois que l’on va faire l’intégration.
Cartes
.NET:
- Il faut terminer l'implémentation de la méthode CreatePlayer de PlayersService.
- Il existe un STUB du CardsService qui retourne les cartes d'un joueur avec GetPlayersCards. Il faut retourner les cartes du joueur (OwnedCards).
Il faut faire des requêtes GET vers GetAllCards et GetPlayersCards de votre serveur WebAPI pour obtenir les listes de cartes à afficher.
Il existe déjà un component SCICard côté React pour afficher les cartes. (Vous pouvez le modifier pour avoir le look que vous voulez!)
- Afficher les cartes existantes avec React (Page Magasin) [Pas encore possible d’acheter ou de vendre des cartes]
- Afficher les cartes du joueur avec React (Page Mes Cartes).
- Pour les deux pages de cartes (Page Magasin et page Mes Cartes), ajouter une option de tri selon les trois champs suivants: "Name", "Attack", "Health" et "ManaCost".
- L'utilisateur doit voir les options en français.
- Il faut également permettre de faire le tri par ordre croissant ou décroissant.
- Il faut créer un component pour faire le tri et afficher les cartes et le réutiliser dans les deux pages. (Il y a donc 1 component par page + 1 component réutilisé pour un total de 3)
- On veut afficher les cartes avec des pages. Il faut 3 options : (3x3, 4x4 ou 5x5), chacune avec un icône qui nous permet de choisir le type d'affichage.
- L'affichage doit prendre toute la largeur, donc les cartes sont plus ou moins grandes en fonctions du nombre de cartes affichées
- Il faut un contrôle pour pouvoir changer de page et on doit désactiver le bouton s'il n'y a pas de page précédente ou de page suivante.
Option 3x3
![]() |
|---|
Option 4x4
![]() |
|---|
Sur la deuxième image, la pagination n'est pas affiché car il y a moins de 16 cartes présentement et donc 1 seule page.
-
Ajouter la logique pour GainManaEvent dans applyEvent de MatchService. (Vous allez maintenant voir le mana qui monte au début du tour)
-
Ajouter un dialogue de défaite et de victoire lorsqu’on a une fin de partie
Ajout des decks
- Un deck a un nom en plus de contenir des cartes (les cartes qu'un joueur possède)
- Une même carte peut faire partie de plusieurs decks. (Les decks sont indépendants les uns des autres)
- Si j'ai une copie (1 entrée OwnedCard) pour une carte, je peux la mettre au maximum une fois dans un deck. Si j'en ai N, je peux en ajouter N.
- Donc quand j'ajoute une carte à un deck, je dois proposer à l'usager SES cartes qui ne sont PAS déjà dans CE deck.
- Mettre en place le modèle de données pour vous permettre de gérer les decks.
- Lors du register, vous devez créer un deck qui se nomme "Depart" avec toutes les cartes du joueur. (C'est le deck courant du joueur)
- Lorsque vous obtenez les decks du joueurs avec un appel au serveur, retournez également l'information de de configuration pour pouvoir l'appliquer dans votre logique:
- Nombre max de decks (MatchConfigurationService.GetNbDecks())
- Nombre max de cartes dans un deck (MatchConfigurationService.GetNbCardsPerDeck())
- Client:
- Afficher la liste des decks d'un joueur dans une section "Mes Decks"
- Pouvoir créer un nouveau deck avec un nom au choix (en respectant la limite de decks de la configuration)
- Pouvoir effacer un deck, si ce n'est pas le deck courant (On n'efface jamais de owned cards ou cards!)
- Pouvoir ajouter et retirer une carte à un deck existant (en respectant la limite de carte de la configuration)
- Assurez-vous de trier les cartes du joueur pour faciliter la sélection
- Pouvoir rendre un deck courant
- Doit etre impossible d'effacer le deck courant (vérification serveur)
- Le code client doit être mis dans un hook que vous devez créer.
- Le code serveur doit être mis dans un contrôleur et un service que vous devez créer.
- Seul les cartes du Deck courant sont disponibles lors d’une partie. (Ce sont les cartes qui vont remplir le CardsPile du match pour ce joueur)
- Changer le démarrage d'un Match pour utiliser les cartes du deck courrant
Section d’administration (MVC)
-
Ajouter 2 rôles et un user pour chacun de ces nouveaux rôles au seed: "Artist" et "Game Deisgner".
-
L'administrateur peut faire toutes les actions (Artiste et Game Designer)
-
Le game designer peut créer, modifier, voir et supprimer les cartes modèles (CRUD), sauf qu'il ne peut pas modifier l'image.
-
Il faut un bouton dans l'index qui permet d'ouvrir une autre vue où il peut changer l'image de la carte.
-
Cartes de départ:
- Mettre en place le modèle de données des cartes de départ.
- Le game designer peut modifier les cartes de départ des nouveaux joueurs. Faire un tri par le nom de la carte dans l’Index.
- Ajouter un seed des cartes de départ. Il doit contenir 3 cartes différentes avec une seule copie et 3 autres cartes avec deux copies chaque.
- Il existe un STUB de StartingCardsService pour les cartes de départ. Il faut retourner les cartes configurées à l'aide des cartes de départ.
-
Configuration:
- Mettre en place le modèle de données de la configuration (GameConfig).
- Ajouter une page de configuration pour le game designer avec une configuration pour:
- le nombre de cartes à piger avant de commencer la partie (nbCardsToDraw)
- la quantité de Mana reçu au début de chaque tour
- le nombre max de decks
- le nombre max de cartes dans un deck
- Ajouter un seed pour la configuration avec une quantité de 4 cartes à piger, 3 manas par tour, un maximum de 3 decks et de 16 cartes par deck.
attentionCe n’est pas un problème que la configuration soit une table avec une seule entrée. Dans une version plus avancée du projet, on pourrait imaginer qu’il y ait plusieurs modes de jeu différents avec des configurations différentes. Vous pouvez faire simplement utiliser First() sur le DbSet.
- Il existe un STUB du MatchConfigurationService qui retourne des données fixes. Il faut retourner les valeurs de la configuration.
-
Ajoutez un lien sur la page home vers les pages suivantes: "Cartes", "Cartes de départ" et "Configuration"
-
Protégez TOUT les contrôleurs (sauf Home) pour que les bons rôles soient nécessaire.
- En résumé, l'artiste peut seulement voir les cartes et modifier leurs images. Il ne devrait pas voir d'option auxquelles il n'a pas accès!
- Le game designer peut faire tout sauf modifier les images
- L'administrateur a tout les droits
-
On n'a pas besoin de pages détails
Le site doit être développé en français, MAIS ce n'est pas nécessaire de traduire tout ce qui est relié à l'authentification avec Identity. Donc simplement utiliser le français pour le contenu que vous ajoutez (Ce qui inclus d'utiliser des DisplayName pour afficher les mots en français).
Intégration (à faire seulement une fois que les fonctionnalités individuelles sont terminées pour préparer à l’évaluation de groupe)
- Ramenez les différentes branches vers la branche Dev.
- Assurez-vous de vous connecter à un vrai match avec le Hub.
- Assurez-vous d’utiliser la bonne implémentation de MatchConfigurationService.
- Assurez-vous d’utiliser la bonne implémentation de StartingCardsService.
- Assurez-vous d’utiliser la bonne implémentation pour le WebAPI pour obtenir les cartes du joueur.
- Dans MatchPlayerData, utiliser les cartes du joueur.
🚧 Il y aura bientôt plus de détails sur cette partie qui doit être remise après la semaine de relâche
Grille de correction
- 12% de la note pour l’évaluation individuelle (voir le document sur la correction individuelle)
- 2% pour l'utilisation d'Azure DevOps Boards
- 10% pour le code et les fonctionnalités
- 8% de la note pour l’évaluation de groupe
- 3% pour le diagramme de classe
- 5% pour l'intégration des fonctionnalités du projet et les dernières fonctionnalités
Référence pour la remise finale en équipe
Une référence pour voir un client et un serveur fonctionnels.
Usernames: admin@sci.com, artist@sci.com et designer@sci.com Le mot de passe: Passw0rd!
Si vous voyez la page suivante, il faut simplement attendre que l'application démarre
Details
Il faut parfois attendre avant de pouvoir accéder à la démo...

Annexes
Comprendre la logique pour rejoindre une partie
Prenez le temps de regarder le code pour comprendre comment les méthodes JoinMatch et StartMatch du service MatchesService fonctionnent. Voici un résumé de la logique pour joindre un nouveau match:
- Description
- Diagramme
- Bob 👨 veut jouer et un appel à l'action
JoinMatchdu Hub est fait.JoinMatchretourne null (n'envoie rien à Bob 👨), mais Bob 👨 est maintenant en attente d'un partenaire. (Le serveur se souvient de Bob 👨) - Alice 👩 veut jouer et un appel à l'action
JoinMatchdu Hub est fait. JoinMatchenvoie unJoiningMatchDataà Alice 👩 dont la propriétéOtherPlayerConnectionIdest celle de la connection de Bob 👨 etIsStartedestfalse- On envoit un message aux deux joueurs avec le
JoiningMatchData(Si vous regardez le client, il y a un objet similaire qui se nommeMatchDataqui est utilisé dansMatchService) - On appel également la méthode
StartMatchdu serviceMatchesServicecar c'est un nouveau match et on envoit ensuite un message aux 2 joueurs pour traiter leStartMatchEvent.
Note: Vous avez donc besoin de définir un message qui permet d'envoyer le JoiningMatchData pour faire le playMatch sur le client ET un message pour envoyer un StartMatchEvent et faire un applyEvents sur le client.
Si on veut rejoindre un match qui était déjà commencé et pas terminé. Par exemple, si je ferme ma fenêtre et j'en ouvre une nouvelle et je fais "joindre une partie", je veux retourner dans la même partie dans laquelle j'étais.
- Description
- Diagramme
- Bob 👨 et Alice 👩 étaient dans un match, mais le navigateur de Bob 👨 a crashé. 💥
- Bob 👨 veut retourner sur sa partie. Un appel à
JoinMatchest fait. - On envoit un message à Bob 👨 avec le
JoiningMatchData(L'autre joueur a probablement encore sa fenêtre ouverte, on a pas besoin de rien lui envoyer!)

