Exercice pratique : le VIDEO POKER

Utilisez la souris pour jouer.

Autres exercices AS3
Informations

Voici un petit exercice pour réaliser un jeu de Vidéo Poker, à l’attention des débutants. Ne vous attendez pas à un tutorial décrit pas à pas, le but d’un exercice étant que le lecteur fasse l’effort de la compréhension avec toutefois des explications de base.

- Les sources sont disponibles à la fin de l’exercice.
- Retrouvez ce tutoriel sur le Wiki de Mediabox

Partagez
Etude préliminaire

Tout d’abord le VIDEO POKER c’est quoi ? (merci Wikipedia)

Les premiers video poker sont apparus au cours des années 70. Grâce à l’essor de l’électronique et de l’informatique, des concepteurs de machines à sous ont pu combiner un écran de télévision et une unité de traitement dans un même appareil. Certains ont alors eu l’idée de proposer un jeu de poker qui se pratique exclusivement en solitaire.

Durant les années 1980, le video poker est devenu de plus en plus populaire dans les casinos, de nombreux joueurs trouvant ces appareils moins intimidants que les tables de jeu. Aujourd’hui, le video poker s’impose aux côtés des machines à sous, du blackjack et de la roulette comme l’un des jeux les plus populaires des casinos.

Pour gagner au vidéo poker, il faut former une combinaison payante, inspirée des mains au poker, via deux distributions de cartes. Au début d’une partie, le joueur reçoit 5 cartes. Il doit alors choisir celles qu’il désire conserver et celles qu’il veut changer. Il est aussi possible de toutes les garder ou de toutes les changer. Ensuite, le joueur obtient de nouvelles cartes remplaçant celles qu’il ne souhaitait pas conserver. Si sa main forme une combinaison payante, il remporte de l’argent, conformément à ce que prévoit le tableau des paiements. Sinon, il ne gagne rien.

Une main, au poker, est une combinaison de cartes qui est définie par les règles du jeu de poker. Dans le poker classique, sans joker ni carte ajoutée, une main est une combinaison de cinq cartes qu’un joueur constitue et qu’il va comparer avec celles des autres joueurs.

La couleur d’une carte désigne non pas rouge ou noir, mais pique, trèfle, carreau ou cœur. Le rang d’une carte désigne son niveau hiérarchique par rapport aux autres. Aucune couleur n’est privilégiée par rapport à une autre, un 9 de coeur est a priori aussi fort qu’un 9 de trèfle ; de même qu’une quinte flush à pique vaudra autant qu’une quinte flush à carreau (si elles sont de même rang).

Seul le rang compte, et au poker l’ordre des cartes par force croissante est : Deux, Trois, Quatre, Cinq, Six, Sept, Huit, Neuf, Dix, Valet, Dame, Roi, As. L’as peut cependant aussi être utilisé comme carte de valeur 1, si cette convention est acceptée à la table, permettant ainsi de former des suites de type As, 2, 3, 4, 5.

Voici la liste par ordre de force décroissante des combinaisons de 5 cartes au poker, laquelle découle directement des probabilités au poker sur les poker à 52 cartes.

Quinte Flush Royale : cinq cartes de rangs consécutifs à l’As et dont la couleur est identique
Quinte Flush : cinq cartes de rangs consécutifs dont la couleur est identique
Carré : quatre cartes identiques
Full : une paire et un brelan
Couleur : cinq cartes de la même couleur
Quinte : cinq cartes de rangs consécutifs
Quinte blanche : cinq cartes de rangs consécutifs avec l’As en premier
Brelan : trois cartes identiques
Double paire : deux paires
Paire : deux cartes identiques
Carte haute : en cas d’égalité la carte haute gagne la mise

Pour le Video Poker dont nous parlons aujourd’hui cela est bien suffisant, nous plongerons plus avant dans les règles pour le jeu à plusieurs joueurs ou contre l’ordinateur.

Les pré-requis

Je vous recommande fortement d’avoir au moins lu les exercices suivants : DEMINEUR, PONG, SNAKE, TAQUIN, BLACKJACK
Les exercices sont courts mais de nombreuses astuces y sont proposées, je ne les expliquerai pas à chaque nouvel exercice.

Pour ce programme vous devez connaître :

Variables et types : http://help.adobe.com/fr_FR/FlashPl...
Fonctions et paramètres : http://help.adobe.com/fr_FR/FlashPl...
Manipulation de tableaux : http://help.adobe.com/fr_FR/FlashPl...
Ecouteurs d’événements : http://help.adobe.com/fr_FR/FlashPl...

Si vous souhaitez plus de précisions sur ces points, je vous encourage à parcourir le Wiki de Mediabox où vous trouverez de nombreux tutoriaux détaillés.

Le code

Voyons d’abord tout le code d’un coup.

var i:int;
var j:int;
var argent:int;
var mise:int;
var gains:int;
var tour:int;
var paires:int;
var perdu:int;
var couleur:Boolean;
var quinte:Boolean;
var ace:Boolean;
var royale:Boolean;
var main:Array;
var stock:Array;
var paquet:Array;
var stockJetons:Array;
var mainQuinte:Array;
var poseJeton:PoseJeton = new PoseJeton();
var gagneJeton:GagneJeton = new GagneJeton();
var sonBouttons:SonBouttons = new SonBouttons();
var sonErreur:SonErreur = new SonErreur();

var ob:*;
var hud:Hud = new Hud();
for(i=0; i<10;i++){hud["combi"+i].visible = false}
addChild(hud);

var bt1:MovieClip = hud.btRetirer;
var bt2:MovieClip = hud.btDonner;
var bt3:MovieClip = hud.btAjouter;
bt3.addEventListener(MouseEvent.CLICK, ajouter);
bt1.addEventListener(MouseEvent.CLICK, retirer);
bt2.addEventListener(MouseEvent.CLICK, donner);
bt3.addEventListener(MouseEvent.MOUSE_DOWN, effetDown);
bt1.addEventListener(MouseEvent.MOUSE_DOWN, effetDown);
bt3.addEventListener(MouseEvent.MOUSE_UP, effetUp);
bt1.addEventListener(MouseEvent.MOUSE_UP, effetUp);
bt3.buttonMode = true;
bt1.buttonMode = true;
bt2.buttonMode = true;
bt3.mouseChildren = false;
bt1.mouseChildren = false;
bt2.mouseChildren = false;

function effetUp(e:MouseEvent):void{e.target.gotoAndStop(1)};
function effetDown(e:MouseEvent):void{e.target.gotoAndStop(2)};

var autoJetons:Timer = new Timer(10);
autoJetons.addEventListener(TimerEvent.TIMER, accumuleJeton);

var panneaux:Panneaux = new Panneaux();
panneaux.addEventListener(MouseEvent.MOUSE_DOWN, init);
panneaux.buttonMode = true;
addChild(panneaux);

function init(e:MouseEvent):void{
       
        while(numChildren>1) removeChildAt(1);
       
        argent = 100;
        stock = [];
        for (i=0; i<5;i++){
                var c:Cartes = new Cartes();
                c.scaleX = c.scaleY = 1.34;
                c.x = 10+c.width*i;
                c.y = 278;
                addChild(c);
                c.mouseChildren = false
                c.buttonMode = true;
                c.addEventListener(MouseEvent.CLICK, choisir);
                stock.push(c);
        }
        lancePartie();
}

function lancePartie():void{
        supprimeJetons();
        updateInfos(gains,0,-gains);
        tour =                 0;
        perdu =         1;
        paquet =         [];
        main =                 [];
        stockJetons =         [];
        masqueBoutons(1,0,1);
        bt2.gotoAndStop(1);
        for(i=0; i<52;i++) paquet.push(i+1);
        for(i=0; i<10;i++) hud["combi"+i].visible = false;
        for(i=0; i<5;i++){
                stock[i].hold.visible = false;
                stock[i].repere.visible = false;
                stock[i].alpha = 1;
                stock[i].gotoAndStop(1);
        }
}

function choisir(e:MouseEvent):void{
        e.target.hold.visible=!e.target.hold.visible;
        sonBouttons.play();
}

function ajouter(e:MouseEvent):void{
        if(argent>0 && mise<5) {
                mise++
                updateInfos(-1,mise,0);
                ajouteJeton();
                masqueBoutons(1,1,1);
        } else {
                sonErreur.play();
        }
}

function retirer(e:MouseEvent):void{
        if(mise>0) {
                mise--
                updateInfos(1,mise,0);
                i = stockJetons.length-1;
                retireJeton(i);       
                if(mise==0) masqueBoutons(1,0,1);
        } else {
                sonErreur.play();
        }
}

function updateInfos(a:int,m:int,g:int):void{
        argent += a;
        mise = m;
        gains += g;
        hud.caisse.text = argent.toString();
        hud.miseMain.text = mise.toString();
        mise==5 ? i=4000 : i = mise*250;
        hud.paris.text = i+"\n"+mise*50+"\n"+mise*20+"\n"+mise*7+"\n"+mise*6+"\n"+mise*5+"\n"+mise*4+"\n"+mise*3+"\n"+mise*2+"\n"+0;
}

function donner(e:MouseEvent):void{
        if (tour==3) lancePartie();
        if (tour<2 && mise>0) {
                tirage();
                masqueBoutons(0,1,0);
                tour++;
                if (tour==2) calculGains(), tour++;
        }
        sonBouttons.play();
}

function tirage():void{
        for(i=0; i<5; i++) {
                if(!stock[i].hold.visible){
                        j = Math.random()*paquet.length;
                        main[i] = paquet[j];
                        stock[i].gotoAndStop(paquet[j]+1);
                        paquet.splice(j,1);
                }
                stock[i].hold.visible = false;
        }
}

function masqueBoutons(A:int,B:int,C:int):void{
        bt1.visible = Boolean(A);
        bt2.visible = Boolean(B);
        bt3.visible = Boolean(C);
}

function accumuleJeton(e:TimerEvent):void{
        if(gains>0) {
                ob = ajouteJeton();
                if(gains>5){
                        updateInfos(5,mise,-5);
                        ob.gotoAndStop(2);
                } else {
                        updateInfos(1,mise,-1);
                }
        } else {
                autoJetons.stop();
                masqueBoutons(0,1,0);
                bt2.addEventListener(MouseEvent.CLICK, recupereJeton);
        }
}

function recupereJeton(e:MouseEvent):void{
        bt2.removeEventListener(MouseEvent.CLICK, recupereJeton);
        bt2.addEventListener(MouseEvent.CLICK, donner);
        bt2.gotoAndStop(3);
        for each(ob in stockJetons)        ob.addEventListener(Event.ENTER_FRAME, animeJeton);
        updateInfos(0,0,0);
}

function animeJeton(e:Event):void{
        ob =        e.target;
        ob.y += (20+Math.random()*50)*perdu;
        ob.x -= 25*perdu;
        if(ob.y>480 || ob.y<0){
                ob.removeEventListener(Event.ENTER_FRAME, animeJeton);
                removeChild(MovieClip(ob));
                stockJetons.splice(stockJetons.indexOf(ob),1);
                poseJeton.play();
        }
}

function ajouteJeton():MovieClip{
        var j:Jeton = new Jeton();
        j.x = 120+Math.random()*200;
        j.y = 170+Math.random()*40;
        addChild(j);
        stockJetons.push(j);
        poseJeton.play();
        if(stockJetons.length>50){
                removeChild(stockJetons[0]);
                stockJetons.splice(0,1);
        }
        return j;
}

function retireJeton(i):void{
        removeChild(stockJetons[i]);
        stockJetons.splice(i,1);
        poseJeton.play();       
}

function supprimeJetons():void{
        for each(ob in stockJetons){
                removeChild(ob);
                poseJeton.play();
        }
        stockJetons = [];
}

function calculGains():void{
       
        gains = 0;       
        perdu = 1;
        ace = false;
        quinte = true;
        royale = false;
        couleur = true;
        paires = 0;
        mainQuinte = [];
               
        for each (i in main){
                stock[main.indexOf(i)].alpha = 0.5;
                if (int(main[0]/14)!=int(i/14)) couleur = false;
                for each (j in main){
                        if(j!=i && int(j%13)==int(i%13)) {
                                stock[main.indexOf(i)].repere.visible = true;
                                stock[main.indexOf(j)].repere.visible = true;
                                stock[main.indexOf(i)].alpha = 1;
                                stock[main.indexOf(j)].alpha = 1;
                                paires++;
                        }
                }
                j = i%13;
                if(j==0) j = 13;
                if(j==1) ace = true;
                mainQuinte.push(j);
        }

        mainQuinte = mainQuinte.sort(Array.NUMERIC);
        if (ace && mainQuinte[4]==13) mainQuinte[0] = 14;
        mainQuinte = mainQuinte.sort(Array.NUMERIC);
        for (i=1; i<5; i++){
                if (mainQuinte[i]!=mainQuinte[i-1]+1) quinte = false;                       
        }
        if(quinte && mainQuinte[4]==14) royale=true;
       
        if (paires==2){
                for each (i in main){
                        if(stock[main.indexOf(i)].repere.visible == true){
                                if (int(i%13)<10 && int(i%13)!=1 && int(i%13)!=0){
                                        stock[main.indexOf(i)].repere.visible = false;
                                        stock[main.indexOf(i)].alpha = 0.5;
                                        paires=0;
                                }
                        }
                }
        }
       
        if(couleur || quinte){
                for each (i in main){
                        stock[main.indexOf(i)].repere.visible = true;
                        stock[main.indexOf(i)].alpha = 1;
                }
        }
       
        if(couleur && quinte && royale) {
                if(mise==5)        cumulGains(0,4000) else cumulGains(0,mise*250);
        }
        if(couleur && quinte && !royale)         cumulGains(1,mise*50);
        if(paires==12)                                 cumulGains(2,mise*20);
        if(paires==8)                                 cumulGains(3,mise*7);
        if(couleur && !quinte)                        cumulGains(4,mise*6);
        if(!couleur && quinte)                         cumulGains(5,mise*5);
        if(paires==6)                                 cumulGains(6,mise*4);
        if(paires==4)                                 cumulGains(7,mise*3);
        if(paires==2)                                 cumulGains(8,mise*2);
        if(paires==0 && !couleur && !quinte)         cumulGains(9,0), perdu=-1;
       
        if (argent==0 && gains==0) {
                addChild(panneaux);
                panneaux.gotoAndStop(2);
                return;
        }
       
        autoJetons.start();
        masqueBoutons(0,0,0);
        bt2.removeEventListener(MouseEvent.CLICK, donner);
        bt2.gotoAndStop(2);
}

function cumulGains(c:int,g:int):void{
        hud["combi"+c].visible=true;
        gains = g;
        gagneJeton.play();
}

Etude du programme

Comme d’habitude j’utilise la bibliothèque de Flash pour gérer les graphismes, ce qui me permet d’alléger le code et de ne conserver que ce qui est utile pour l’exercice. Cette fois je vais aller un peu plus loin car ce qui nous intéresse ici c’est le moteur du jeu, tout le reste n’est là que pour rendre l’expérience plus agréable, aussi tous les effets et une bonne partie des animations sont réalisés directement avec Flash, que ce soit par des interpolations ou des ajouts d’effets. Je ne vais pas détailler ces points particuliers, car ça prendrait trop de temps, mais je voulais pour une fois vous présenter un jeu un peu construit. Je vous laisse donc le loisir d’aller explorer par vous même les différents clips pour comprendre comment tel ou tel effet spécial à été fait et nous allons nous concentrer sur le moteur proprement dit.

Panneaux : * un clip qui regroupe tous les panneaux d’interface * un panneau différent par frame

Cartes : * un clip qui regroupe toutes les Cartes * une Carte sur chaque frame * la première frames représente le dos des cartes

Hud : * contient toutes les informations pratiques du jeu * contient tous les boutons de l’interface

Jeton : * contient un jeton de casino différent par frame

Le code est assez long, et je vais essayer de vous le découper en plein de petites fonction que l’on pourra travailler indépendamment pour plus de souplesse. Notez également que beaucoup de code aurait pu nous être évité pour l’exercice, notamment en ce qui concerne l’animation des jetons, et beaucoup de petits effets qui sont venus s’ajouter au fil du développement. C’est le contraire de ce que je vous propose habituellement dans les exercices, cette fois on touche aussi au look et aux effets, alors autant en profiter tout de suite pour vous monter aussi comment ça marche.

Allez c’est parti pour l’étude pas à pas :

var i:int;
var j:int;
var argent:int;
var mise:int;
var gains:int;
var tour:int;
var paires:int;
var perdu:int;
var couleur:Boolean;
var quinte:Boolean;
var ace:Boolean;
var royale:Boolean;
var main:Array;
var stock:Array;
var paquet:Array;
var stockJetons:Array;
var mainQuinte:Array;
var poseJeton:PoseJeton = new PoseJeton();
var gagneJeton:GagneJeton = new GagneJeton();
var sonBouttons:SonBouttons = new SonBouttons();
var sonErreur:SonErreur = new SonErreur();

Ici nous avons les variables globales, assez explicites nous les étudierons au fil du programme, notez cependant les deux "pointeurs", "i" et "j" qui servent un peut partout dans les boucles ou les fonctions, je les réutilise régulièrement car il est inutile d’aller créer de nouvelles variables lorsqu’on à juste une info temporaire à stocker.

Nous avons également les différents tableaux qui vont nous servir, leurs noms sont là aussi explicites.

Et les sons utilisés dans le programme.

var ob:*;
var hud:Hud = new Hud();
for(i=0; i<10;i++){hud["combi"+i].visible = false}
addChild(hud);

var bt1:MovieClip = hud.btRetirer;
var bt2:MovieClip = hud.btDonner;
var bt3:MovieClip = hud.btAjouter;
bt3.addEventListener(MouseEvent.CLICK, ajouter);
bt1.addEventListener(MouseEvent.CLICK, retirer);
bt2.addEventListener(MouseEvent.CLICK, donner);
bt3.addEventListener(MouseEvent.MOUSE_DOWN, effetDown);
bt1.addEventListener(MouseEvent.MOUSE_DOWN, effetDown);
bt3.addEventListener(MouseEvent.MOUSE_UP, effetUp);
bt1.addEventListener(MouseEvent.MOUSE_UP, effetUp);
bt3.buttonMode = true;
bt1.buttonMode = true;
bt2.buttonMode = true;
bt3.mouseChildren = false;
bt1.mouseChildren = false;
bt2.mouseChildren = false;

function effetUp(e:MouseEvent):void{e.target.gotoAndStop(1)};
function effetDown(e:MouseEvent):void{e.target.gotoAndStop(2)};

Là nous avons la définition des objets, vous noterez en premier "ob" qui est de type indiférent "*". Cet objet peut prendre différents types, Object, MovieClip, Sprite, ..... c’est pour cela qu’il est typé avec une étoile, il sert à différents endroits dès que j’ai besoin de stocker un objet temporairement quelque part. Le "Hud" contient toute l’interface, j’ai voulu me simplifier la vie et donc je n’ai pas créé l’interface avec le code mais dans un clip conteneur où j’ai posé mes contenus à la main, c’est pourquoi j’ai créé juste en dessous des raccourcis pour accéder aux contenus du clip, ceci me permet d’alléger le programme sans avoir à retaper tout le chemin des boutons à chaque fois que j’en ai besoin. Puisqu’on parle des boutons, vous avez là toute l’interactivité des boutons, c’est quelque chose de connu si vous avez fait les exercices précédents, c’est pour ça que je ne m’étends pas dessus.

var autoJetons:Timer = new Timer(10);
autoJetons.addEventListener(TimerEvent.TIMER, accumuleJeton);

var panneaux:Panneaux = new Panneaux();
panneaux.addEventListener(MouseEvent.MOUSE_DOWN, init);
panneaux.buttonMode = true;
addChild(panneaux);

Et pour finir les déclarations globales, nous avons un Timer qui se charge de distribuer automatiquement des jetons (on y reviendra) et l’affichage du panneau de début de partie qui lance la fonction "init" quand on clique dessus.

Ouf ! Fin des déclarations, cette fois il y en a un bon paquet, rassurez-vous, ce n’est pas parce que ça remplis l’assiette qu’il y en a tant que ça a manger, comme vous allez le constater.

function init(e:MouseEvent):void{
       
        while(numChildren>1) removeChildAt(1);
       
        argent = 100;
        stock = [];
        for (i=0; i<5;i++){
                var c:Cartes = new Cartes();
                c.scaleX = c.scaleY = 1.34;
                c.x = 10+c.width*i;
                c.y = 278;
                addChild(c);
                c.mouseChildren = false
                c.buttonMode = true;
                c.addEventListener(MouseEvent.CLICK, choisir);
                stock.push(c);
        }
        lancePartie();
}

Cette fonction permet d’initialiser le jeu, elle est appellée en début de chaque nouvelle partie, attention il ne faut pas confondre "partie" et "main", la partie se termine lorsque le joueur n’a plus d’argent, alors qu’une main se termine quand l’ensemble des tours de jeu est terminé.

Je commence donc par supprimer tout ce qui est affiché à part l’interface principale, puis j’initialise les valeurs des paramètres principaux (argent, stock, ....), et enfin je crée et positionne les 5 cartes du jeu. Chaque carte dispose d’un écouteur qui permettra de la sélectionner en cours de partie, puis je pose les 5 cartes dans le stock, histoire de pouvoir y faire appel quand je veux. Et pour finir je lance la partie.

Avant de voir comment ça se passe lorsqu’on lance une partie, revenons deux secondes sur l’interactivité des cartes.

function choisir(e:MouseEvent):void{
        e.target.hold.visible=!e.target.hold.visible;
        sonBouttons.play();
}

Chaque carte contient un repère visuel "Hold" qui s’affiche lorsqu’on clique sur une carte et s’efface lorsqu’on reclique sur cette carte. Il va permettre au programme de savoir quelles cartes ont été sélectionnées et doivent être gardées lors du prochain tour.

function lancePartie():void{
        supprimeJetons();
        updateInfos(gains,0,-gains);
        tour =                 0;
        perdu =         1;
        paquet =         [];
        main =                 [];
        stockJetons =         [];
        masqueBoutons(1,0,1);
        bt2.gotoAndStop(1);
        for(i=0; i<52;i++) paquet.push(i+1);
        for(i=0; i<10;i++) hud["combi"+i].visible = false;
        for(i=0; i<5;i++){
                stock[i].hold.visible = false;
                stock[i].repere.visible = false;
                stock[i].alpha = 1;
                stock[i].gotoAndStop(1);
        }
}

Bien, lançons donc une partie, le nom de cette fonction n’est pas idéal, en fait j’aurai du utiliser "lanceMain" car il s’agit en fait de lancer une nouvelle main et non une nouvelle partie, cependant je conserve mon titre d’origine car cette fonction intervient principalement au lancement d’une partie.

Elle commence par supprimer les jetons, pour le cas où il resterai des jetons de la main précédente sur la table. Puis elle mets à jour les infos textuelles et les gains, a chaque nouvelle main la caisse du joueur augmente des gains de la main précédente (nous allons étudier la fonction dédiée juste après). Enfin je mets à jour toutes les infos de la main qui commence, je vide les tableaux et je cache les boutons qui ne doivent pas apparaître (nous allons également revenir là dessus). Je remplis un nouveau paquet de cartes avec 52 cartes allant de 1 à 52. J’efface les résultats de la main précédente, et je réinitialise toutes les cartes affichées.

Voyons de plus près les trois fonction dédiées utilisées ici, notez les biens car nous allons les réutiliser un peu partout dans le programme.

function supprimeJetons():void{
        for each(ob in stockJetons){
                removeChild(ob);
                poseJeton.play();
        }
        stockJetons = [];
}

Cette fonction permet de supprimer tous les jetons affichés sur la scène et de vider le tableau de stockage des jetons.

function updateInfos(a:int,m:int,g:int):void{
        argent += a;
        mise = m;
        gains += g;
        hud.caisse.text = argent.toString();
        hud.miseMain.text = mise.toString();
        mise==5 ? i=4000 : i = mise*250;
        hud.paris.text = i+"\n"+mise*50+"\n"+mise*20+"\n"+mise*7+"\n"+mise*6+"\n"+mise*5+"\n"+mise*4+"\n"+mise*3+"\n"+mise*2+"\n"+0;
}

Cette fonction met à jour les différentes informations du jeu, quelles soient affichées ou non. On distingue notamment l’argent, la mise, les gains, les textes correspondants et les valeurs de gains en fonction de la mise. Je centralise donc la mise à jour des informations par l’intermédiaire d’une seule fonction comportant trois paramètres, "a" pour l’argent à ajouter, "m" pour la valeur de la mise, "g" pour les gains à ajouter.

function masqueBoutons(A:int,B:int,C:int):void{
        bt1.visible = Boolean(A);
        bt2.visible = Boolean(B);
        bt3.visible = Boolean(C);
}

Cette fonction sert à afficher ou masquer les boutons de l’interface, je l’utilise dès que j’ai besoin de manipuler les éléments de l’interface, elle comporte trois paramètre, un pour l’état de chaque boutons. J’ai préféré utiliser un entier "int" pour les paramètres pour simplifier la lecture, je dois donc faire une conversion en "Boolean" si je veux affecter la visibilité de mes boutons. Bien que cela demande une conversion supplémentaire, à ce stade ce n’est pas limitant pour les calculs et permet de rendre la lecture plus claire.

Bon, je viens de me débarrasser de pas mal de choses d’un coup, c’est pas mal, maintenant attaquons nous au coeur du problème. A ce stade, la partie est lancée et le joueur à face à lui 5 cartes vues de dos, et deux boutons affichés pour miser et retirer sa mise, que se passe t’il si il appuie sur un de ces boutons ?

function ajouter(e:MouseEvent):void{
        if(argent>0 && mise<5) {
                mise++
                updateInfos(-1,mise,0);
                ajouteJeton();
                masqueBoutons(1,1,1);
        } else {
                sonErreur.play();
        }
}

function retirer(e:MouseEvent):void{
        if(mise>0) {
                mise--
                updateInfos(1,mise,0);
                i = stockJetons.length-1;
                retireJeton(i);       
                if(mise==0) masqueBoutons(1,0,1);
        } else {
                sonErreur.play();
        }
}

Le joueur ne peut afficher ses cartes que lorsque la mise est supérieure à zéro. Lorsqu’il clique sur le bouton "ajouter", on vérifie si il lui reste de l’argent en caisse et si la mise n’est pas déjà égale à 5 (mise maximum au Video Poker). Si tout va bien on incrémente la mise, on met à jour les infos, on ajoute un jeton à l’affichage et on affiche le bouton qui permet de retourner les cartes (puisque la mise est au moins de 1).

Pour décrémenter sa mise c’est le même principe, on regarde si la mise n’est pas égale à zéro, si c’est le cas on décrémente la mise, on met à jour les infos et on supprime de l’affichage le dernier des jetons qui à été posé (on le retire également du tableau de stockage). Enfin, si la mise est égale à zéro, on masque le bouton qui permet de retourner les cartes.

J’utilise ici deux nouvelles petites fonctions dédiées.

function ajouteJeton():MovieClip{
        var j:Jeton = new Jeton();
        j.x = 120+Math.random()*200;
        j.y = 170+Math.random()*40;
        addChild(j);
        stockJetons.push(j);
        poseJeton.play();
        if(stockJetons.length>50){
                removeChild(stockJetons[0]);
                stockJetons.splice(0,1);
        }
        return j;
}

Lorsque j’ai besoin d’ajouter un jeton, j’utilise cette fonction qui me renvoie le jeton ajouté. Techniquement, elle crée un nouveau jeton et le place sur la scène, l’ajoute au tableau de stockage des jetons, et vérifie si il y a plus de 50 jetons de posés, auquel cas on retire le premier jeton du stockage, ceci permet d’éviter que la machine se mette à ramer dès qu’il faut poser une grande quantité de jetons sur la table (4000 dans le cas d’une quinte flush royale avec une mise de départ à 5).

function retireJeton(i):void{
        removeChild(stockJetons[i]);
        stockJetons.splice(i,1);
        poseJeton.play();       
}

Pour retirer un jeton on prend l’id du jeton en paramètre et on le supprime de l’affichage et du tableau de stockage des jetons.

Bien à ce stage le joueur à misé, le bouton de donne est apparu, il permet d’effectuer le tirage des cartes et de les donner.

function donner(e:MouseEvent):void{
        if (tour==3) lancePartie();
        if (tour<2 && mise>0) {
                tirage();
                masqueBoutons(0,1,0);
                tour++;
                if (tour==2) calculGains(), tour++;
        }
        sonBouttons.play();
}

Selon le tour de jeu les actions ne sont pas les mêmes, cette fonction est un peu torturée, j’en suis désolé mais je n’ai pas trouvé plus simple pour le moment. On commence par vérifier si on est en tour 3 c’est à dire que la main est terminée, dans ce cas on relance une partie lorsqu’on clique sur ce bouton. Puis on va vérifier qu’on ne se trouve pas encore en tour 2 (fin de la main), dans ce cas appuyer sur le bouton lance un tirage des cartes (on va le voir juste après), masque les boutons correspondants, incrémente le tour de jeu, et vérifie juste ensuite si on a atteint le tour 2 (fin de la main), si c’est le cas on calcule les gains et on incrémente le tour pour qu’au prochain clic sur le bouton on soit en tour 3 et que cela relance une partie.

function tirage():void{
        for(i=0; i<5; i++) {
                if(!stock[i].hold.visible){
                        j = Math.random()*paquet.length;
                        main[i] = paquet[j];
                        stock[i].gotoAndStop(paquet[j]+1);
                        paquet.splice(j,1);
                }
                stock[i].hold.visible = false;
        }
}

Cette fonction est appelée fois que l’on veut tirer de nouvelles. Par défaut lors du premier tour aucune carte n’est conservée, au second tour le joueur à sélectionné les cartes qu’il veut garder puis relance un tirage, seules les cartes non conservées seront retirées. C’est pourquoi pour chaque carte je commence par regarder si elle doit être conservée ou non (ici j’utilise la valeur de la visibilité d’un clip, c’est pas top, vous pourriez utiliser des variables si vous souhaitez faire quelque chose de plus clean). Pour chaque carte qui doit être tirée, je choisi une carte aléatoirement dans le paquet, je l’insère dans le tableau de stockage de la main du joueur, j’affiche la carte correspondante du stock, et je retire la carte du paquet, c’est très important pour éviter qu’une carte ne puisse sortir deux fois. Le tirage ayant été effectué, je supprime le marqueur de toutes les cartes qui étaient marquées pour être conservées.

Le moteur en lui même est presque terminé, il reste encore à calculer les gains et à gérer tous les petits effets et animations sur les jetons, mais commençons par calculer les gains.

function calculGains():void{
       
        gains = 0;       
        perdu = 1;
        ace = false;
        quinte = true;
        royale = false;
        couleur = true;
        paires = 0;
        mainQuinte = [];
               
        for each (i in main){
                stock[main.indexOf(i)].alpha = 0.5;
                if (int(main[0]/14)!=int(i/14)) couleur = false;
                for each (j in main){
                        if(j!=i && int(j%13)==int(i%13)) {
                                stock[main.indexOf(i)].repere.visible = true;
                                stock[main.indexOf(j)].repere.visible = true;
                                stock[main.indexOf(i)].alpha = 1;
                                stock[main.indexOf(j)].alpha = 1;
                                paires++;
                        }
                }
                j = i%13;
                if(j==0) j = 13;
                if(j==1) ace = true;
                mainQuinte.push(j);
        }

        mainQuinte = mainQuinte.sort(Array.NUMERIC);
        if (ace && mainQuinte[4]==13) mainQuinte[0] = 14;
        mainQuinte = mainQuinte.sort(Array.NUMERIC);
        for (i=1; i<5; i++){
                if (mainQuinte[i]!=mainQuinte[i-1]+1) quinte = false;                       
        }
        if(quinte && mainQuinte[4]==14) royale=true;
       
        if (paires==2){
                for each (i in main){
                        if(stock[main.indexOf(i)].repere.visible == true){
                                if (int(i%13)<10 && int(i%13)!=1 && int(i%13)!=0){
                                        stock[main.indexOf(i)].repere.visible = false;
                                        stock[main.indexOf(i)].alpha = 0.5;
                                        paires=0;
                                }
                        }
                }
        }
       
        if(couleur || quinte){
                for each (i in main){
                        stock[main.indexOf(i)].repere.visible = true;
                        stock[main.indexOf(i)].alpha = 1;
                }
        }
       
        if(couleur && quinte && royale) {
                if(mise==5)        cumulGains(0,4000) else cumulGains(0,mise*250);
        }
        if(couleur && quinte && !royale)         cumulGains(1,mise*50);
        if(paires==12)                                 cumulGains(2,mise*20);
        if(paires==8)                                 cumulGains(3,mise*7);
        if(couleur && !quinte)                        cumulGains(4,mise*6);
        if(!couleur && quinte)                         cumulGains(5,mise*5);
        if(paires==6)                                 cumulGains(6,mise*4);
        if(paires==4)                                 cumulGains(7,mise*3);
        if(paires==2)                                 cumulGains(8,mise*2);
        if(paires==0 && !couleur && !quinte)         cumulGains(9,0), perdu=-1;
       
        if (argent==0 && gains==0) {
                addChild(panneaux);
                panneaux.gotoAndStop(2);
                return;
        }
       
        autoJetons.start();
        masqueBoutons(0,0,0);
        bt2.removeEventListener(MouseEvent.CLICK, donner);
        bt2.gotoAndStop(2);
}

Pour une fois allons-y pas à pas.

gains = 0;       
perdu = 1;
ace = false;
quinte = true;
royale = false;
couleur = true;
paires = 0;
mainQuinte = [];

Par défaut les gains sont égaux à zéro, "perdu" représente le joueur qui gagne, il s’agit soit du joueur (1) soit de la banque (-1), cette variable nous servira pour savoir à qui donner les jetons au final. Par défaut toujours, il n’y a pas d’as dans le jeu ("ace"), il y a une quinte dans le jeu, elle n’est pas royale, il y a une couleur dans le jeu et il n’y a pas de paires. Vous noterez le petit tableau "mainQuinte", il va nous servir à trier temporairement les cartes dans la main du joueur.

Bien à présent nous allons essayer de regarder le jeu du joueur et vérifier les différentes combinaisons possibles.

for each (i in main){
        stock[main.indexOf(i)].alpha = 0.5;
        if (int(main[0]/14)!=int(i/14)) couleur = false;
        for each (j in main){
                if(j!=i && int(j%13)==int(i%13)) {
                        stock[main.indexOf(i)].repere.visible = true;
                        stock[main.indexOf(j)].repere.visible = true;
                        stock[main.indexOf(i)].alpha = 1;
                        stock[main.indexOf(j)].alpha = 1;
                        paires++;
                }
        }
        j = i%13;
        if(j==0) j = 13;
        if(j==1) ace = true;
        mainQuinte.push(j);
}

Pour chaque carte dans la main du joueur, je la passe en semi invisible, puis, je vais ramener les valeurs des cartes à une couleur en divisant le chiffre correspondant à la carte par 14. En effet, une couleur comporte 13 cartes de l’As au Roi, les valeurs des cartes dans le paquet vont de 1 à 52, en récupérant la valeur entière de la division de la valeur de la carte par 14 j’obtiens la couleur, ce qui nous donne :

Entier de 1 / 14 = 0
Entier de 2 / 14 = 0
Entier de 3 / 14 = 0
Entier de 4 / 14 = 0
Entier de 5 / 14 = 0
Entier de 6 / 14 = 0
Entier de 7 / 14 = 0
Entier de 8 / 14 = 0
Entier de 9 / 14 = 0
Entier de 10 / 14 = 0
Entier de 11 / 14 = 0
Entier de 12 / 14 = 0
Entier de 13 / 14 = 0
Entier de 14 / 14 = 0

Entier de 14 / 14 = 1
Entier de 15 / 14 = 1
Entier de 16 / 14 = 1
Entier de 17 / 14 = 1
Entier de 18 / 14 = 1
Entier de 19 / 14 = 1
Entier de 20 / 14 = 1
Entier de 21 / 14 = 1
Entier de 22 / 14 = 1
Entier de 23 / 14 = 1
Entier de 24 / 14 = 1
Entier de 25 / 14 = 1
Entier de 26 / 14 = 1
Entier de 27 / 14 = 1

Etc....

En récupérant ainsi la couleur je peux comparer la couleur de la carte en cours de test et la couleur par exemple de la première carte de la main, si elles sont différentes alors il n’y a pas de couleur dans la main, sinon c’est qu’il y a bien une couleur dans la main.

Pour chaque carte testée je vais ensuite tester toutes les cartes de la main pour essayer de trouver des cartes identiques. Dans la boucle il faut d’abord s’assurer qu’on ne teste pas la même carte ("i" différente de "j"), puis, comme pour la couleur, on va tester les cartes entre elles, mais cette fois avec un modulo sur 13, ce qui va nous renvoyer non pas la couleur mais la valeur de la carte sur 13 cartes (donc peu importe la couleur). Si on tombe sur deux cartes identiques on allume le marqueur de gain de chacune (là encore j’ai utilisé un repère visuel que vous pouvez modifier en une variable si vous voulez quelque chose de plus clean), et on la repasse en complètement visible, ainsi on repère tout de suite les cartes gagnantes. Et enfin on incrémente la variable "paires". Concrètement voici ce que nous devrions obtenir pour chaque combinaison :

paire = 2 => une paire
paire = 4 => une double paire
paire = 6 => un brelan
paire = 8 => un full
paire = 12 => un carré

Et hop, on a trouvé assez facilement les différentes combinaisons simples telles que les cartes identiques et la couleur, reste à présent à gérer les différentes suites possibles, c’est à dire les quintes, les quintes flush et les quintes flush royales.

A la fin de la boucle vous noterez ceci pour chaque carte :

j = i%13;
if(j==0) j = 13;
if(j==1) ace = true;
mainQuinte.push(j);

Attention, bien que nous utilisions le pointeur "j" juste au dessus, on le réutilise dans un tout autre contexte ici, la boucle utilisant "j" étant terminée on peut considérer que "j" est libre, on peut donc s’en resservir pour tout autre chose. Ici je vais donc enregistrer la valeur de la carte en cours, si elle est égale à zéro il s’agit d’un Roi (13%13 = 0), je lui redonne donc sa vraie valeur. Si il s’agit d’un 1 alors il y a au moins un as dans le jeu, c’est important pour la suite, et enfin je viens ajouter la valeur de la carte dans le tableau temporaire de la main du joueur. Je me retrouve donc avec un nouveau tableau représentant la main du joueur, dénué de couleurs mais contenant la vraie valeur de chaque carte sur 13.

On sort de la boucle car on a fini de regarder les cartes, on sait à présent ce que le joueur à en main, mais il nous reste encore à déterminer si il a une quinte.

mainQuinte = mainQuinte.sort(Array.NUMERIC);
if (ace && mainQuinte[4]==13) mainQuinte[0] = 14;
mainQuinte = mainQuinte.sort(Array.NUMERIC);
for (i=1; i<5; i++){
        if (mainQuinte[i]!=mainQuinte[i-1]+1) quinte = false;                       
}

Une des particularité de l’As c’est que selon certaines règles il peut servir dans les suites (quintes) soit comme un 1 soit comme un 14, donc se placer en début ou en fin de suite. Je trie donc par ordre croissant le tableau "mainQuinte", puis je regarde si il y a un As et si la dernière carte est un Roi, si c’est le cas la seule chance pour qu’il y ait une quinte c’est de placer l’As après le Roi, je change donc la valeur de l’AS. Puis je trie à nouveau mon tableau par ordre croissant afin de faire passer l’As en fin de cette nouvelle liste. Bien, il ne me reste plus qu’à vérifier que toutes les valeurs des cartes de suivent à partir de la première, je fais donc une boucle sur 4 cartes, je ne prend pas en compte la première, et je vérifie si chaque carte testée à la même valeur que la carte précédente à laquelle j’ajoute 1. Si une des cartes n’a pas la bonne valeur il n’y a pas de quinte, sinon elle existe et dans ce cas il faut déterminer si elle est Royale ou pas, donc qu’elle se termine par un As.

if(quinte && mainQuinte[4]==14) royale=true;

Donc c’est très simple, si on vient de déterminer qu’on avait une quinte en main, on regarde si la dernière carte est un As, et on a bien une quinte Royale (mais pas encore flush et c’est elle qui compte).

La règle du Video Poker impose que les paires en dessous d’une valeur de 10 ne sont pas prises en compte dans les gains, cela va nous obliger à patcher le programme pour que finalement il ne prenne pas en compte ce type de cartes.

if (paires==2){
        for each (i in main){
                if(stock[main.indexOf(i)].repere.visible == true){
                        if (int(i%13)<10 && int(i%13)!=1 && int(i%13)!=0){
                                stock[main.indexOf(i)].repere.visible = false;
                                stock[main.indexOf(i)].alpha = 0.5;
                                paires=0;
                        }
                }
        }
}

C’est la même chose que lorsqu’on voulait vérifier les paires dans le jeu, si on a une paire unique on fait une boucle sur toutes les cartes en ne prenant pas en compte les valeurs au dessus de 10, ni les As, ni les Rois. Pour chaque carte ainsi détectée on retire le marqueur de gain et on la rend semi transparente, et on met de décompte de paies à 0.

Youpi on a fini de regarder ce que le joueur à dans les mains, à présent occupons nous de ses gains.

if(couleur || quinte){
        for each (i in main){
                stock[main.indexOf(i)].repere.visible = true;
                stock[main.indexOf(i)].alpha = 1;
        }
}

Au niveau des repères visuels pour marquer les gains, les combinaisons à paire c’est fait mais pas encore pour les couleurs ou les quintes, si le joueur en a une toutes les cartes sont marquées.

if(couleur && quinte && royale) {
        if(mise==5)        cumulGains(0,4000) else cumulGains(0,mise*250);
}
if(couleur && quinte && !royale)         cumulGains(1,mise*50);
if(paires==12)                                 cumulGains(2,mise*20);
if(paires==8)                                 cumulGains(3,mise*7);
if(couleur && !quinte)                        cumulGains(4,mise*6);
if(!couleur && quinte)                         cumulGains(5,mise*5);
if(paires==6)                                 cumulGains(6,mise*4);
if(paires==4)                                 cumulGains(7,mise*3);
if(paires==2)                                 cumulGains(8,mise*2);
if(paires==0 && !couleur && !quinte)         cumulGains(9,0), perdu=-1;

En fonction de la combinaison obtenue, sachant qu’il ne doit pas y avoir deux combinaisons identiques, on cumule les gains en multipliant la mise par la valeur de la combinaison, ça se passe dans un petite fonction qu’on s’étudie tout de suite avant de revenir à nos moutons :

function cumulGains(c:int,g:int):void{
        hud["combi"+c].visible=true;
        gains = g;
        gagneJeton.play();
}

On affiche le repère visuel de la combinaison gagnante et on initialise les gains.

Il nous reste un petit bout de code à voir dans le calcul des gains :

if (argent==0 && gains==0) {
        addChild(panneaux);
        panneaux.gotoAndStop(2);
        return;
}
       
autoJetons.start();
masqueBoutons(0,0,0);
bt2.removeEventListener(MouseEvent.CLICK, donner);
bt2.gotoAndStop(2);

Si le joueur n’a plus d’argent et que sa main ne lui rapporte rien, la partie est terminée. Sinon, on ajoute des jetons pour faire joli, on masque les boutons le temps que l’animation des jetons se fasse et on retire l’événement du bouton (vous allez voir pourquoi). Puisqu’on parle des animations des jetons et qu’on en a fini avec le calcul des gains, voyons un peut ce qu’il se passe niveau animation et effets.

Vous remarquerez qu’à la fin du calcul des gains, si la main à rapporté quelque chose je lance le Timer que j’ai déclaré au début "autoJetons". Il lance à intervalles très court la fonction "accumuleJeton".

function accumuleJeton(e:TimerEvent):void{
        if(gains>0) {
                ob = ajouteJeton();
                if(gains>5){
                        updateInfos(5,mise,-5);
                        ob.gotoAndStop(2);
                } else {
                        updateInfos(1,mise,-1);
                }
        } else {
                autoJetons.stop();
                masqueBoutons(0,1,0);
                bt2.addEventListener(MouseEvent.CLICK, recupereJeton);
        }
}

Tant que les gains sont supérieurs à zéro, on ajoute un jeton que l’on récupère dans une variable "ob", si les gains sont supérieurs à 5, on met à jour les infos en retirant 5 aux gains et en ajoutant 5 à la caisse du joueur, le jeton ajouté est rouge (5) et non plus noir (1), sinon on met juste à jour les infos avec une valeur de 1. Lorsque les gains sont à zéro il est temps de couper le timer et d’afficher le bouton de donne pour passer à l’étape suivante qui sera de récupérer les jetons puisque le nouvel écouteur du bouton de donne le stipule.

function recupereJeton(e:MouseEvent):void{
        bt2.removeEventListener(MouseEvent.CLICK, recupereJeton);
        bt2.addEventListener(MouseEvent.CLICK, donner);
        bt2.gotoAndStop(3);
        for each(ob in stockJetons)        ob.addEventListener(Event.ENTER_FRAME, animeJeton);
        updateInfos(0,0,0);
}

A cet instant le joueur doit récupérer ses jetons posés sur la table, au clic sur le bouton on retire l’écouteur temporaire qu’on avait placé dessus et on lui redonne son écouteur normal, switcher ainsi d’écouteur me permet de changer de fonction au bouton en cours de partie, c’est pratique. Pour chaque bouton qui se trouve dans le tableau de stockage des jetons, j’ajoute un écouteur calé sur ENTER_FRAME qui caste la fonction "animeJeton".

function animeJeton(e:Event):void{
        ob =        e.target;
        ob.y += (20+Math.random()*50)*perdu;
        ob.x -= 25*perdu;
        if(ob.y>480 || ob.y<0){
                ob.removeEventListener(Event.ENTER_FRAME, animeJeton);
                removeChild(MovieClip(ob));
                stockJetons.splice(stockJetons.indexOf(ob),1);
                poseJeton.play();
        }
}

Et pour chaque jeton, je le déplace sur X en fonction du joueur qui remporte la manche (c’est là que sert la variable "perdu"), a gauche c’est le joueur, à droite c’est la banque, je le déplace sur Y à une vitesse plus ou moins aléatoire, et lorsqu’ils ont atteint la limite fixée sur Y (0 pour la banque et 480 pour le joueur) on retire leur écouteur, on les retire de l’affichage et du tableau de stockage des jetons.

Et voilà, nous avons terminé cette première partie d’exercice sur le Poker.

Conclusion

Dans cet exercice j’ai fait des choix discutables (surtout par fénéantise) quand à l’utilisation d’éléments graphiques pour la résolution de certaines actions à mener par le programme, par exemple lorsque j’utilise "hold" des cartes pour savoir lesquelles garder lors d’un tirage. Bien que ça n’impacte pas vraiment ici, essayez d’éviter ce genre de choses, vos calculs ne doivent jamais dépendre de ce qui est affiché, et ce qui est affiché ne doit être que le résultat final des calculs. Outre le fait que c’est risqué car vos calculs dépendent d’un truc qui peut demander un temps variable à s’afficher, se construire ou réagir, c’est aussi une source de complication pour le développeur qui reprendrait votre travail et devrait aller plonger dans les clips pour essayer de retrouver tel ou tel élément avec lequel son programme doit travailler.

En dehors de ça, nous avons à présent une base pour un moteur de jeu de poker, nous avons abordés beaucoup de points intéressants, mais cela reste du Video Poker, un jeu somme toute assez simple dont beaucoup de règle du vrai Poker ont été édulcorées. Puisque nous disposons à présent d’une bonne base, on va pouvoir s’attaquer à un peu plus gros en ajoutant la possibilité de jouer contre un ordinateur (puis pourquoi pas à plusieurs joueurs, on verra), donc créer une Intelligence Artificielle (basique) et tout un tas de nouvelles règles et restrictions qui s’appliquent au jeu à plusieurs.

Sources

Version Flash CS5.5

Zip - 338.2 ko