[TP] Le craps

Bien, jusque là je vous ai guidé lors des différents chapitres. Je vous ai même fourni les solutions des exercices (qui a dit «pas toujours» ? :colere2: ). Après ces quelques leçons de programmation en Ada, il est temps pour vous de réaliser un projet un peu plus palpitant que de dessiner des rectangles avec des dièses (#). Voila pourquoi je vous propose de vous lancer dans la réalisation d'un jeu de dé :

Le craps !

Il ne s'agira pas d'un exercice comme les précédents, car celui-ci fera appel à de nombreuses connaissances (toutes vos connaissances, pour être franc). Il sera également plus long et devra être bien plus complet que les précédents. Songez seulement à tous les cas qu'il a fallu envisager lorsque nous avons créé notre programme lors du chapitre sur les conditions. Eh bien il faudra être aussi exhaustif, si ce n'est davantage.

Rassurez-vous, vous ne serez pas lâchez en pleine nature : je vais vous guider. Tout d'abord, nous allons établir les règles du jeu et le cahier des charges de notre programme. Puis nous règlerons ensemble certains problèmes techniques que vous ne pouvez résoudre seuls (le problème du hasard notamment). Pour ceux qui auront des difficultés à structurer leur code et leur pensée, je vous proposerai un plan type. Et finalement, je vous proposerai une solution possible et quelques idées pour améliorer notre jeu.

Les règles du craps

Bon, avant de nous lancer tête baissée, il serait bon de définir ce dont on parle. Le craps se joue avec 2 dés et nous ne nous intéressons pas aux faces des dés mais à leur somme ! Si vos dés indiquent un 5 et un 3 alors vous avez obtenu 5+3=8 !Le joueur mise une certaine somme, puis il lance les dés. Au premier lancer, on dit que le point est à off.

  • Si le joueur obtient 7 (somme la plus probable) ou 11 (obtenu seulement avec 6 et 5), il remporte sa mise. (S'il avait misé 10\$, alors il gagne 10\$)
  • S'il obtient des craps (2, 3 ou 12) alors il perd sa mise.
  • S'il obtient 4, 5, 6, 8, 9 ou 10, alors le point passe à on et les règles vont changer.

Une fois que le point est à on, le joueur doit parvenir à refaire le nombre obtenu (4, 5, 6, 8, 9 ou 10). Pour cela, il peut lancer le dé autant de fois qu'il le souhaite, mais il ne doit pas obtenir de 7 !

  • S'il parvient à refaire le nombre obtenu, le joueur remporte sa mise.
  • S'il obtient 7 avant d'avoir refait son nombre, le joueur perd sa mise. On peut alors hurler : SEVEN OUT ! Hum… désolé.

Ce sont là des règles simplifiées car il est possible de parier sur les issues possibles (le prochain lancer est un 11, le 6 sortira avant le 7, le prochain lancer sera un craps…) permettant de remporter jusqu'à 15 fois votre mise. Mais nous nous tiendrons pour l'instant aux règles citées au-dessus.

Cahier des charges

Un programmeur ne part pas à l'aventure sans avoir pris connaissances des exigences de son client, car le client est roi (et ici, le client c'est moi :diable: ). Vous devez avoir en tête les différentes contraintes qui vous sont imposées avant de vous lancer :

  • Tout d'abord, les mises se feront en \$ et nous n'accepterons qu'un nombre entiers de dollars (pas de 7\$23, on n'est pas à la boulangerie !)
  • Le joueur doit pouvoir continuer à jouer tant qu'il n'a pas atteint les 0\$.
  • Un joueur ne peut pas avoir - 8\$ ! ! ! Scores négatifs interdits. Et pour cela, il serait bon que le croupier vérifie que personne ne mise plus qu'il ne peut perdre (autrement dit, pas de mise sans vérification).
  • Le joueur ne peut pas quitter le jeu avant que sa mise ait été soit perdue soit gagnée. En revanche, il peut quitter la partie en misant 0\$.
  • L'affichage des résultats devra être lisible. Pas de :
1
+5

mais plutôt :

1
Vous avez gagné 5$. Vous avez maintenant 124$.
  • Le programme devra indiquer clairement quel est le point (off ou on, et dans ce cas, quel est le nombre ?), si vous avez fait un CRAPS ou un SEVEN OUT, si vous avez gagné, quelle somme vous avez gagné/perdu, ce qu'il vous reste, les faces des dés et leur somme… Bref, le joueur ne doit pas être dans l'inconnu. Exemple :
1
2
3
Vous avez encore 51$. Combien misez vous ? 50
   Le premier dé donne 1 et le second donne 2. Ce qui nous fait 3.
   C'est un CRAPS. Vous avez perdu 50$, il ne vous reste plus que 1$.
  • Votre procédure principale devra être la plus simple possible, pensez pour cela à utiliser des fonctions et des sous-procédures. Vous êtes autorisés pour l'occasion à utiliser des variables en mode OUT ou IN OUT. ^^
  • Votre code devra comporter le moins de tests possibles : chaque fois que vous testez une égalité, vous prenez du temps processeur et de la mémoire, donc soyez économes.

Compris ? Alors il nous reste seulement un petit détail technique à régler : comment simuler un lancer de dé ? (Oui, je sais, ce n'est pas vraiment un petit détail).

Simuler le hasard (ou presque)

Je vous transmets ci-dessous une portion de code qui vous permettra de générer un nombre entre 1 et 6, choisi au hasard (on dit aléatoirement).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
WITH Ada.Numerics.Discrete_Random ;

PROCEDURE Craps IS

   SUBTYPE Intervalle IS Integer RANGE 1..6 ;
   PACKAGE Aleatoire IS NEW Ada.Numerics.Discrete_Random(Intervalle);
   USE Aleatoire;

   Hasard  : Generator;

   ...

Comme il est écrit ci-dessus, vous aurez besoin du package Ada.Numerics.Discrete_Random mais vous ne devrez pas écrire l'instruction USE Ada.Numerics.Discrete_Random !

Bah, pourquoi ?

Malheureusement, je ne peux pas vous expliquer ce code pour l'instant car il fait appel à des notions sur les packages, l'héritage, la généricité… que nous verrons bien plus tard. Vous n'aurez donc qu'à effectuer un copier-coller. En revanche, je peux vous expliquer la toute dernière ligne : pour générer un nombre «aléatoire», l'ordinateur a besoin d'une variable appelée générateur ou germe. C'est à partir de ce générateur que le programme créera de nouveaux nombres. C'est là le sens de cette variable Hasard (de type generator).

Mais vous devrez initialiser ce générateur au début de votre procédure principale une et une seule fois ! Pour cela, il vous suffira d'écrire :

1
2
3
BEGIN
   reset(Hasard) ; 
   ...

Par la suite, pour obtenir un nombre compris entre 1 et 6 aléatoirement, vous n'aurez à écrire :

1
MaVariable := random(Hasard) ;

Et pourquoi pas une fonction qui renverrait directement un nombre entre 2 et 12 ?

Autant, vous avez les même chances d'obtenir 1, 2, 3, 4, 5 ou 6 en lançant un dé (on dit qu'il y équiprobabilité) autant vous n'avez pas la même chance de faire 7 et 2 avec deux dés. En effet, pour avoir 7, vous pouvez faire 1/6, 2/5, 3/4, 4/3, 5/2 ou 6/1 tandis que pour avoir 2, vous ne pouvez faire que 1/1 ! Or nous voulons que notre jeu soit aléatoire (ou presque, je ne rentre pas dans les détails mathématiques), donc nous devrons simuler deux dés et en faire la somme.

Un plan de bataille

Comme promis, pour ceux qui ne sauraient pas comment partir ou qui seraient découragés, voici un plan de ce que pourrait être votre programme.

Vous devez réfléchir aux fonctions essentielles : que fait un joueur de CRAPS ? Il joue tant qu' il a de l'argent. Il commence par parier une mise (s'il mise 0, c'est qu'il arrête et qu'il part avec son argent) puis il attaque la première manche (la seconde ne commençant que sous certaines conditions, on peut considérer pour l'instant qu'il ne fait que jouer à la première manche). Voilà donc un code sommaire de notre procédure principale :

1
2
3
4
5
6
7
8
9
RESET(hasard) ;

TANT QUE argent /= 0
|   PARIER
|   SI mise = 0 
|   |   ALORS STOP
|   FIN DU SI
|   JOUER LA PREMIÈRE MANCHE
FIN DE BOUCLE

À vous de le traduire en Ada. Vous devriez avoir identifier une boucle WHILE, un IF et un EXIT. Vous devriez également avoir compris que nous aurons besoin de deux fonctions/procédures PARIER et JOUER LA PREMIÈRE MANCHE (nom à revoir bien entendu). La procédure/fonction PARIER se chargera de demander la mise désirée et de vérifier que le joueur peut effectivement parier une telle somme :

1
2
3
TANT QUE mise trop importante
|   DEMANDER mise
FIN DE BOUCLE

La fonction/procédure JOUER LA PREMIÈRE MANCHE se contentera de lancer les dés, de regarder les résultats et d'en tirer les conclusions.

1
2
3
4
5
6
7
LANCER dés
SI les dés valent
|   7 ou 11     => on gagne
|   2,3 ou 12   => on perd
|   autre chose => point := on
|                  MANCHE2 avec valeur du point
FIN DE SI

Il est possible de réaliser une fonction/procédure LANCER qui «jettera les dés» et affichera les scores ou bien on pourra écrire le code nécessaire dans la fonction/procédure JOUER LA PREMIÈRE MANCHE. Intéressons-nous à la fonction/procédure JOUER LA DEUXIEME MANCHE :

1
2
3
4
5
6
TANT QUE point = on
|   SI les dés valent
|   |   7     => on perd
|   |   point => on gagne
|   FIN DE SI
FIN DE BOUCLE

Voilà décomposé la structure des différentes fonctions/procédures que vous devrez rédiger. Bien sûr, je ne vous indique pas si vous devez choisir entre fonction ou procédure, si vous devez transmettre des paramètres, afficher du texte, choisir entre IF et CASE … Je ne vais pas non plus tout faire.

Une solution

Je vous fournis ci-dessous le code d'une solution possible à ce TP, ce n'est pas la seule bien sûr :

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
WITH Ada.Text_IO,Ada.Integer_Text_IO,Ada.Numerics.Discrete_Random ;
USE Ada.Text_IO,Ada.Integer_Text_IO ;

PROCEDURE Craps IS

   ------------------------
   --PACKAGES NÉCESSAIRES--
   ------------------------

   SUBTYPE Intervalle IS Integer RANGE 1..6 ;
   PACKAGE Aleatoire IS NEW Ada.Numerics.Discrete_Random(Intervalle);
   USE Aleatoire;

   ---------------------------
   --FONCTIONS ET PROCÉDURES--
   ---------------------------

   PROCEDURE Lancer (
         D1        OUT Natural;
         D2        OUT Natural;
         Hasard        Generator) IS
   BEGIN
      D1 := Random(Hasard) ;
      D2 := Random(Hasard) ;
      Put("   Vous obtenez un") ; Put(D1,2) ;
      Put(" et un") ; Put(D2,2) ; 
      put(". Soit un total de ") ; put(D1+D2,4) ; put_line(" !") ; 
   END Lancer ;



   function parier(argent natural) return natural is
      mise : natural := 0 ; 
   begin
      Put("   Vous disposez de") ; Put(argent) ; put_line(" $.") ; 
      while mise = 0 loop
         Put("   Combien souhaitez-vous miser ? ") ; 
         Get(mise) ; skip_line ; 
         if mise > argent
            then Put_line("   La maison ne fait pas credit, Monsieur.") ; mise := 0 ;
            else exit ; 
         end if ; 
      end loop ; 
      return mise ; 
   end parier ; 
   


   procedure Manche2(Hasard generator ; Mise in out natural ; Argent in out natural ; Point in out natural) is
      D1,D2 : Natural;             --D1 et D2 indiquent les faces du dé 1 et du dé 2
   begin
      while point>0 loop 
         Lancer(D1,D2,Hasard) ; 
         if D1 + D2 = Point
            then Put("      Vous empochez ") ; Put(Mise,1) ; put(" $. Ce qui vous fait ") ; 
                 Argent := Argent + Mise ; point := 0 ; mise := 0 ; 
                 Put(Argent,1) ; Put_line(" $. Bravo.") ; new_line ; 
         elsif D1 + D2 = 7
            then Put("      Seven Out ! Vous perdez ") ; Put(Mise,1) ; put(" $. Plus que ") ; 
                 Argent := Argent - Mise ; point := 0 ; mise := 0 ; 
                 Put(Argent,1) ; Put_line(" $. Dommage.") ; new_line ; 
         end if ;
      end loop ; 
   end Manche2 ; 



   procedure Manche1(Hasard generator ; Mise in out natural ; Argent in out natural) is
      D1,D2 : Natural;             --D1 et D2 indiquent les faces du dé 1 et du dé 2
      Point   : Natural   := 0;    --0 pour off, les autres nombres indiquant que le point est on
   begin
      Lancer(D1,D2,Hasard) ; 
      CASE D1 + D2 IS
         WHEN 7 | 11     => Put("      Vous empochez ") ; Put(Mise,1) ; put(" $. Ce qui vous fait ") ; 
                            Argent := Argent + Mise ; Mise := 0 ; 
                            Put(Argent,1) ; Put_line(" $. Bravo.") ; new_line ; 
         WHEN 2 | 3 | 12 => Put("      CRAPS ! Vous perdez ") ; Put(Mise,1) ; put(" $. Plus que ") ; 
                            Argent := Argent - Mise ; Mise := 0 ; 
                            Put(Argent,1) ; Put_line(" $. Dommage.") ; new_line ; 
         WHEN OTHERS     => Point := D1 + D2 ;
                            Put("      Le point est etabli a ") ; Put(point,5) ; New_line ; 
                            Manche2(Hasard,Mise,Argent,Point) ;
      END CASE ;
   end Manche1 ; 
   
   -------------
   --VARIABLES--
   -------------

   Hasard  : Generator;         --Ce generator nous servira à simuler le hasard
   Argent   : Natural  := 100; --On commence avec 100$
   Mise    : Natural   := 0;    --montant misé, on l'initialise à 0 pour débuter.

   ------------------------
   --PROCÉDURE PRINCIPALE--
   ------------------------
BEGIN

   Reset(Hasard) ;
   Put_Line("          Bienvenue au CRAPS ! ! !") ; New_Line ;

   while argent > 0 loop
      Mise := Parier(argent) ; 
      exit when Mise = 0 ; 
      Put_line("         Les jeux sont faits !") ; 
      Manche1(Hasard,Mise,Argent) ;
      new_line ; 
   END LOOP ;

   Put("Vous repartez avec ") ; Put(Argent,5) ; put("$ !") ; 

END Craps ;

Voici quelques pistes d'amélioration, certaines n'étant réalisables qu'avec davantage de connaissances. À vous de trouver vos idées, de bidouiller, de tester… c'est comme ça que vous progresserez. N'ayez pas peur de modifier ce que vous avez fait !

Pistes d'amélioration :

  • Prendre en compte tous les types de paris. Pour davantage de détails, aller voir les règles sur ce site.
  • Prendre en compte un parieur extérieur, un second joueur.
  • Enregistrer les meilleurs scores dans un fichier (pour plus tard)
  • Proposer un système de «médaille» : celui qui quitte la partie avec moins de 100\$ aura la médaille de «rookie», plus de 1000\$ la médaille du «Dieu des dés»… à vous d'inventer différents sobriquets.
  • Et que pourrait faire le joueur de tout cet argent gagné ? S'acheter une bière, une moto, un cigare ? On peut imaginer un système de récompense.