Projet
Introduction
Le Monopoly est un jeu de société très connu. Il n'est pas très fun à jouer, mais il a un intérêt sur le plan pédagogique, car ses règles sont plutôt bien connues et intéressantes à implémenter sous forme de logiciel. Il nous permet aussi d'intégrer facilement les sujets qui nous occupent, dans ce module comme le threading et le réseau.
Pour la petite histoire, le Monopoly a été inventé au début du 20e siècle par Lizzie Magie pour montrer le problème de l'accumulation de la propriété foncière. Je recommande cette vidéo sur le sujet de l'origine du Monopoly si cela vous intéresse.
Objectif
L'objectif du projet est, dans un premier temps, d'implémenter une simulation du jeu de société, le Monopoly. Une fois cette simulation mise en place, elle pourra être utilisée pour jouer au jeu entre plusieurs joueurs via le réseau, depuis une interface en ligne de commande d'abord, puis, si le temps le permet avec des éléments d'interface graphique.
Notation
La notation du projet est conçue pour ne pas être trop punitive pour ceux qui sont en difficulté et récompenser ceux qui s'investissent beaucoup. Je n'hésiterai cependant pas à sanctionner le manque de travail évident. Je n'aurai pas de problème à mettre 0 à quelqu'un qui n'a pas travaillé du tout, tout comme je n'hésiterai pas à mettre 20 à quelqu'un qui fourni un travail complet et de qualité.
Le barème est calculé par livrable, chaque livrable rapporte 4 points. Pour chaque livrable :
2 points pour la complétion des fonctionnalités du projet. Si votre code passe tous les tests d'intégration fournis, et que je ne repère pas de dysfonctionnement dans votre code à la revue, vous avez 2/2 sur cette partie.
2 points de qualité du code :
Implémentation de tests unitaires en plus des tests fournis
Application des pratiques de qualité logicielle vus en cours
Utilisation des fonctionnalités appropriées du langage selon le contexte
La réalisation du livrable bonus donnera un coup de pouce de +2 points sur la note à l'examen.
Démarrage du projet
Compte GitLab
Le projet se déroulera sur GitLab, vous aurez donc besoin de créer un compte dessus.
Binômes
Ce projet est à faire par binôme. Une fois que vous avez choisi votre binôme, renseignez le sur le document Excel présent dans l'équipe Teams, pour me communiquer votre binôme, votre nom d'utilisateur GitLab ainsi que celui de votre binôme.
Forker le projet
Le projet est fourni sous forme de template dans ce dépôt GitLab, vous devez forker ce projet pour créer votre propre dépôt sur lequel vous allez travailler.
N'oubliez pas de créer votre projet en privé !
Ajouter le prof & votre binôme
Ajoutez-moi ensuite sur votre projet (mon nom d'utilisateur est Ombrelin
) avec le rôle "Maintainer".
Cloner le projet
Avec de cloner (avec la commande git clone
) le projet n'oubliez pas de configurer votre compte git en local, en utilisant comme username votre username Gitlab, comme décrit la section du cours à ce sujet, via la command git config
.
Vous pouvez ensuite ouvrir le dossier du dépôt que vous venez de clôner avec IntelliJ.
Description du dossier du projet
Le projet est un projet Gradle voir la section du cours à ce sujet. Il contient un module appelé core
qui va contenir du code que je fournis :
Des tests d'intégration, qui vérifient de façon automatique que votre code implémente bien les spécifications requises.
Des abstractions (interfaces) qui permettent à mes tests de s'intégrer avec votre code
Créer votre module
Avant de commencer, créer et positionnez vous sur une nouvelle branche.
Pour commencer à travailler sur le projet il vous faut créer votre module, qui contiendra votre code de simulation :
Créer un nouveau module Gradle nommé
simulation
avec l'aide d'IntelliJ. En tant que "GroupId", saisissezfr.<votre nom><nom binome>.efrei.monopoly
:
Mettre à jour la configuration Gradle de votre module (le module
simulation
, vous ne devez jamais modifier le modulecore
par vous même) :
Créer votre paquet racine dans votre projet. Suggestion de nommage (dans
main/java
ettest/java
) :fr.<votre nom><nom binome>.efrei.monopoly.simulation
.Créer un paquet
fr.<votre nom><nom binome>.efrei.monopoly.simulation.integration
dans votre dossier de test (simulation/src/test/java
), et créer une classeMonopolyTests
qui étend ma classe de testBaseMonopolyTests
. Ainsi, vous pourrez pour le 1er livrable implémenter la méthodecreateMonopoly
pour fournir votre propre implémentation deMonopoly
afin de pouvoir exécuter mes tests avec.Vous avez terminé le setup pour le projet. Vous pouvez pousser votre branche pour que votre binôme puisse la récupérer de son côté.
Note sur l'exécution des tests d'intégration fournis dans le projet
La technique utilisée pour permettre de vous fournir des tests que vous pourrez plugger directement à votre code cause un petit souci avec le système d'intégration des tests Gradle d'IntelliJ. Heureusement, il y a une solution de contournement simple.
Le problème est le suivant : si vous exécutez vos tests d'intégration en passant par l'icône dans la marge de la classe et que vous exécutez tous les tests, ou bien que vous exécutez la commande gradle test
tout va bien, les tests s'exécutent :
Cependant, si vous exécutez un test individuellement via la vue de test (par exemple parce que vous voulez débugger un test spécifique), une erreur apparait :
C'est parce que le système utilise le mauvais nom de classe et de module pour exécuter le test, il les prend de la classe de tests abstraite. Pour résoudre le problème :
Ouvrez la configuration d'exécution qui a été créée par l'essai que vous venez de faire :
Remplacez dans la commande du test :
Le nom du module (
core
parsimulation
)Le nom (complet) de la classe
fr.arsenelapostolet.efrei.monopoly.BaseMonopolyTests
par le nom (complet) de votre classe :fr.<votre nom><nom binome>.efrei.monopoly.simulation.integration.MonopolyTest
. Pour être sûr d'avoir le bon, vous pouvez le copier-coller de la commande de la config de la classe entière. Faîtes bien attention à laisser le nom de la méthode de test à la fin de la commande.
Le test devrait d'exécuter sans problème avec cette configuration, vous pouvez même la lancer en mode debug pour faire du pas à pas.
Processus de livraison
Pour un livrable donné x:
Sur votre dépôt, créer une nouvelle branche à partir de la branche master appelée dev/livrable-x en remplaçant x par le nom du livrable concerné.
Fusionner la branche template/livrable-x (où x est le numéro du livrable) de mon dépôt dans la branchedev/livrable-x en question de votre dépôt, afin d'avoir les tests d'intégration correspondant au livrable (vous pouvez le faire via l'interface de GitLab en utilisant une merge request que vous validerez vous-même).
Commiter sur cette branche les changements permettant de satisfaire les tests d'intégration du livrable
Créer une pull request de votre branche dev/livrable-x, vers votre branche master, en me mettant dans le champ assignee de la pull request.
Je vais ensuite être notifié de la demande de revue, et vais procéder à une relecture de votre code, et éventuellement faire des commentaires, des recommendations d'amélioration. Une fois ces améliorations implémentée ou votre choix spécifique argumenté, je ferai une évaluation du code en l'état, qui servira pour la partie "qualité" de la notation. Je vais enfin procéder à la fusion de votre branche de livraison sur votre master, vous pouvez ainsi reprendre le processus du début, pour le prochain livrable.
Planning du projet
Date | Sujet |
---|---|
Vendredi 19 Janvier 2024 | Démarrage du projet |
Dimanche 4 Février 2024 | Date limite de rendu du livrable 1 |
Dimanche 18 Février 2024 | Date limite de rendu du livrable 2 |
Dimanche 3 Mars 2024 | Date limite de rendu du livrable 3 |
Dimanche 17 Mars 2024 | Date limite de rendu du livrable 4 |
Dimanche 31 Mars 2024 | Date limite de rendu du livrable 5 |
Dimanche 14 Avril 2024 | Date limite de rendu du livrable 6 (bonus) |
L'heure limite pour le rendu de chaque livrable est minuit. Si votre travail est prêt avant la date limite, n'attendez pas la dernière minute pour faire votre pull request de rendu. Plus vite vous avez une revue de votre code, plus vite vous pouvez passer à la suite et éventuellement prendre de l'avance.
Interface publique de la simulation
Le jeu est modélisé par une interface Simulation
fourni, qui est utilisée dans les tests d'intégration fournis. Votre simulation doit implémenter cette interface.
L'élément central de l'interface est la méthode submitOrder
, elle permet à un joueur d'effectuer une action, ou de passer son tour (ordre IDLE
). Cette méthode résout aussi la situation avant l'action à faire par le joueur suivant.
En résumé submitOrder
c'est :
Résolution de l'action du joueur
Jet de dé pour le déplacement du joueur suivant
Résolution du déplacement du joueur suivant et des conséquences de ce déplacement
D'autres méthodes sur l'interface permettent de lire les informations sur la situation courante du jeu.
Livrables
Livrable 1 : Jets de dés, plateau, déplacement
Les joueurs peuvent se déplacer sur le plateau. Pour l'instant les joueurs ne peuvent rien faire, le jeu progresse quand ils donnent l'ordre IDLE
(ne rien faire), ce qui passe le tour, et déclenche le jeté les dés et le déplacement pour le joueur suivant. Les ordres incohérents avec la situation courante du jeu sont ignorés. Un ordre invalide peut être un ordre d'un joueur pour qui ce n'est pas le tour de jouer, ou alors un ordre qui n'est pas cohérent avec la situation courante du tour.
La composition du plateau est la suivante, elle est décrite dans le fichier de ressources monopoly.csv
(sous core/main/resources
dans le projet). Ce fichier est à parser pour créer une représentation en mémoire du plateau.
Voici les règles concernant les dés : on simule le jet de deux dés à six face, le score de déplacement est la somme des deux dés ; il faut donc faire deux générations aléatoires pour un lancer de dés. Les dés ne sont pas testés par mes tests, ils sont remplacés par une pseudo entité, mais je corrigerai la validité de votre implémentation ainsi que comment vous l'avez testée.
En résumé, 3 tâches à faire pour ce livrable :
Parser le plateau depuis le fichier CSV et en faire une modélisation
Implémenter les dés
Combiner la modélisation du plateau dans votre simulation de Monopoly afin d'avoir la fonctionnalité de déplacement spécifiée par les tests fournis.
Livrable 2 : Argent, Achat, Loyers terrain nu
Dans ce livrable, on va implémenter :
Un système de gestion de l'argent des joueurs, et des transactions. Chaque joueur commence avec une somme de départ de 1500€.
Possibilité pour les joueurs d'acheter des propriétés, gares et companies, en donnant un ordre de type
BUY
à son tour lorsqu'on est sur une localisation de ce typeLes joueurs doivent régler un loyer lorsqu'ils arrivent sur une propriété, gare ou compagnie, déjà possédée par un autre joueur
Les joueurs ne peuvent pas acheter une propriété, gare ou compagnie qui appartient déjà à quelqu'un d'autre
Les loyers terrain nu sont les suivants :
Propriété | Loyer terrain nu |
---|---|
Rue Raspail | 2 |
Rue Victor Hugo | 4 |
Rue Jean Jaurès | 6 |
Boulevard Maxime Gorki | 6 |
Rue Youri Gagarine | 8 |
Avenue Louis Aragon | 10 |
Avenue de la République | 10 |
Avenue de Stalingrad | 12 |
Allée Berlioz | 14 |
Rue du Moulin de Saquet | 14 |
Sentier de la Commune | 16 |
Rue Pascal | 18 |
Rue Blanqui | 18 |
Rue Rosa Luxembourg | 20 |
Rue de Bretagne | 22 |
Rue René Hamon | 22 |
Rue Guy Môquet | 24 |
Rue Henri Barbusse | 26 |
Rue Ambroise Croizat | 26 |
Rue de Verdun | 28 |
Avenue de Paris | 35 |
Avenue Paul Vaillant Couturier | 50 |
Pour l'instant le loyer des gares est un prix fixe de 25.
Le loyer des compagnies se calcule selon la logique suivante :
Si le propriétaire de la compagnie possède une seule des deux compagnies, alors le prix est le score du joueur locataire multiplié par quatre
Si le propriétaire de la compagnie possède les deux compagnies, alors le prix est le score du joueur locataire multiplié par dix
Si un joueur est confronté à un loyer qu'il ne peut pas payer, il est déclaré en banqueroute, il perd, et est supprimé des joueurs de la partie. Le joueur propriétaire percevant le loyer qui déclenche la banqueroute perçoit l'argent du joueur en banqueroute comme loyer, pas plus. Si la partie contient moins de 2 joueurs, elle s'arrête, c'est-à-dire que tout appel ultérieur à submitOrder
jette une exception de type GameFinishedException
.
Livrable 3 : Prison, case départ, gares
Prison
Si un joueur tombe sur la case "Aller en prison", il va en prison, et est déplacé sur la case "En prison".
Lors de son prochain tour, il peut :
émettre un ordre de
PAY_PRISON
, ce qui lui coûte 50. S'il fait ça, il avance de son score, il n'est plus en prison.émettre un ordre
IDLE
, il est toujours en prison.
Ces choix s'offrent à lui pour les deux prochains tours. Au 3e tour, il est obligé de payer, et avance en fonction de son dernier jet de dés.
Case départ
Quand un joueur passe par la case départ, il gagne 200.
Gares
Le loyer d'une gare est calculé en fonction du nombre de gares possédées par le joueur qui possède la gare :
Nombre de gares possédées | Loyer |
---|---|
1 | 25 |
2 | 50 |
3 | 100 |
4 | 200 |
Livrable 4 : Construction, loyers adéquats et taxes
Les joueurs peuvent construire des maisons pour leurs propriétés en émettant un ordre BUILD
à leur tour. Cet ordre a un paramètre propertyName
: le nom de la propriété sur laquelle construire.
Pour construire, un joueur de posséder toutes les propriétés d'un groupe de couleur.
Une propriété a cinq niveaux de construction. Voici la spécification des loyer et prix de construction en fonction des propriétés :
Propriété | Cout de construction | Loyer "1 Maison" | Loyer "2 Maisons" | Loyer "3 Maisons" | Loyer "4 Maisons" | Loyer "Hotel" |
---|---|---|---|---|---|---|
Rue Raspail | 50 | 10 | 30 | 90 | 160 | 250 |
Rue Victor Hugo | 50 | 20 | 60 | 180 | 320 | 450 |
Rue Jean Jaurès | 50 | 30 | 90 | 270 | 400 | 550 |
Boulevard Maxime Gorki | 50 | 30 | 90 | 270 | 400 | 550 |
Rue Youri Gagarine | 50 | 40 | 100 | 300 | 450 | 600 |
Avenue Louis Aragon | 100 | 50 | 150 | 450 | 625 | 750 |
Avenue de la République | 100 | 50 | 150 | 450 | 625 | 750 |
Avenue de Stalingrad | 100 | 60 | 180 | 500 | 700 | 900 |
Allée Berlioz | 100 | 70 | 200 | 550 | 750 | 950 |
Rue du Moulin de Saquet | 100 | 70 | 200 | 550 | 750 | 950 |
Sentier de la Commune | 100 | 80 | 220 | 600 | 800 | 1000 |
Rue Pascal | 150 | 90 | 250 | 700 | 875 | 1050 |
Rue Blanqui | 150 | 90 | 250 | 700 | 875 | 1050 |
Rue Rosa Luxembourg | 150 | 100 | 300 | 750 | 900 | 1100 |
Rue de Bretagne | 150 | 110 | 330 | 800 | 975 | 1150 |
Rue René Hamon | 150 | 110 | 330 | 800 | 975 | 1150 |
Rue Guy Môquet | 150 | 120 | 360 | 850 | 1025 | 1200 |
Rue Henri Barbusse | 200 | 130 | 390 | 900 | 1100 | 1275 |
Rue Ambroise Croizat | 200 | 130 | 390 | 900 | 1100 | 1275 |
Rue de Verdun | 200 | 150 | 450 | 1000 | 1200 | 1400 |
Avenue de Paris | 200 | 175 | 500 | 1100 | 1300 | 1500 |
Avenue Paul Vaillant Couturier | 200 | 200 | 600 | 1400 | 1700 | 2000 |
Les classes de taxes doivent débiter les joueurs qui tombent dessus du montant correspondant, qui peut être retrouvé dans la colonne price
pour les location de type tax
dans monopoly.csv
.
Livrable 5 : Jeu en réseau en mode client-serveur
Nous voulons maintenant utiliser notre simulation de Monopoly afin de jouer en réseau. Nous allons donc devoir créer deux nouveaux modules dans l'application, selon le modèle client-serveur :
client
: application en ligne de commande permettant aux joueurs de jouer au jeu en leur permettant d'envoyer leurs ordres à la simulation, mais aussi de lire des informations sur la situation courante du jeu.server
: application qui fait tourner la simulation en mémoire, et intéragit via le réseau avec les clients pour le permettre de jouer ensemble.
Le client et le serveur devront implémenter un main
qui permettra de jouer en condition réelles, via une interface en ligne de commandes.
Manipulations préparatoires
Créer deux nouveaux modules gradle :
client
etserveur
Ajouter le plugin Gradle
application
au script de build de ces modulesCréer une classe
App
contenant une méthodemain
dans chacun des modulesAjouter la configuration du plugin application pour dire à Gradle quelle est la classe principale au script de build de ces modules :
Ajouter la configuration run plugin application pour correctement câbler l'entrée standard au script de build de ces modules :
Ajouter les dépendances et références de projet requises au script de build de ces modules
Dans le module
client
, créez une classe de test qui étendBaseMultiplayerMonopolyGameTests
et implémente les méthodes abstraites avec vos propres classes.
Protocole
Le protocole de jeu est le suivant :
Le serveur démarre avec en paramètre un certain nombre de joueurs attendus pour la partie. Quand un joueur se connecte, il envoie son pseudo.
Une fois le nombre de joueurs attendus connectés, le serveur crée une nouvelle simulation la partie démarre. Il envoie l'état initial de la simulation après création à tous les joueurs.
Le serveur entre en attente des envois d'ordre des joueurs. Les joueurs jouent chacun leur tour. Après chaque gestion d'ordre sur la simulation, le serveur envoie aux clients l'état courant de la partie.
L'envoie de l'état de la partie se fait sous la forme d'un format de sérialisation des informations. On envoie une ligne qui contient :
L'ordre exécuté et le joueur qui l'exécute au format
joueur:ordre
(et éventuellement un:propriété
en cas deBUILD
)La localisation des joueurs au format
joueur:location
, séparés par des virgulesLa balance des joueurs au format
joueur:balance
, séparés par des virgulesL'état de propriété de toutes les cases du plateau au format :
case:propriétaire
, séparés par des virgules
Chaque partie est séparés par des |
.
Exemple :
Tests
Les interfaces GameServer
et GameClient
feront le lien entre votre client/serveur et mes tests d'intégration, de la même manière que l'interface Monopoly
pour la simulation. Le module client doit contenir une implémentation du test d'intégration fourni qui valide le fonctionnement du système client-serveur.
Je ferai également quelques tests manuels pour vérifier que les mains de vos applications fonctionnent correctement et permettent de jouer.
Livrable 6 (Bonus) : Interface graphique pour le client
Ce livrable est beaucoup plus libre, l'idée est que le client offre une interface graphique qui montre :
L'état de la partie en temps réel
Des boutons permettant au joueur d'envoyer ses ordres
Je testerai manuellement cette interface graphique et validerai ou non le bonus en fonction de l'aboutissement du livrable.
Livrable 6-bis (Bonus) : Créer son propre conteneur d'injection de dépendance
Ce livrable est beaucoup plus libre, l'idée est de développer un conteneur d'injection de dépendances et de l'utiliser dans les applications du livrable 5.
Créer un nouveau module Gradle de type "library" dans le projet qui implémente ce conteneur d'injection de dépendances.
L'interface du conteneur sera la suivante :