paint-brush
Comment créer un jeu de blackjack avec Alpine.js et l'API Deck of Cardsby@raymondcamden
1,146
1,146

Comment créer un jeu de blackjack avec Alpine.js et l'API Deck of Cards

Raymond Camden6m2023/07/20
Read on Terminal Reader

L'API Deck of Cards gère tout ce qui est imaginable lié au travail avec des jeux de cartes. Il gère la création d'un jeu de cartes mélangées (contenant un ou plusieurs jeux), la distribution d'une carte (ou de cartes) et même le remaniement. C'est une API incroyablement riche en fonctionnalités, et surtout, elle est entièrement gratuite.
featured image - Comment créer un jeu de blackjack avec Alpine.js et l'API Deck of Cards
Raymond Camden HackerNoon profile picture

Il y a quelque temps, je suis tombé sur un service assez fascinant, l' API Deck of Cards . Cette API gère tout ce qui est imaginable lié au travail avec des jeux de cartes. Il gère la création d'un jeu de cartes mélangées (contenant un ou plusieurs jeux), la distribution d'une carte (ou de cartes) et même le remaniement.


Encore mieux, il inclut des images de carte que vous pouvez utiliser si vous ne voulez pas trouver la vôtre :


C'est une API incroyablement riche en fonctionnalités, et surtout, elle est entièrement gratuite. Même pas besoin de clé. Je connais cette API depuis un certain temps et j'ai envisagé de créer un jeu de cartes avec, mais j'ai réalisé que les jeux peuvent rapidement passer de simples à assez complexes.


En fait, mes amis m'ont fortement conseillé de ne pas passer de temps là-dessus, et honnêtement, ils avaient probablement raison, mais j'ai une longue histoire de démos de code du bâtiment qui n'ont pas de sens. ;)


Pour ma démo, je suis allé avec les règles suivantes:


  • Évidemment, les règles de base du Blackjack, essayez de vous rapprocher le plus possible de 21 sans le dépasser.


  • Pas de pari, juste une main à la fois.


  • Pas de doubler ou de diviser.


  • Le concessionnaire a une règle "soft 17". (Je suis surtout sûr d'avoir bien fait.)


  • Le jeu utilise six decks (j'ai lu quelque part que c'était standard).

Configuration du jeu

Initialement, le joueur et l'ordinateur ont tous deux un tableau représentant leurs mains.


 playerCards:[], pcCards:[],


La méthode deal gère la mise en place des mains pour les deux joueurs :


 async deal() { // first to player, then PC, then player, then PC this.playerCards.push(await this.drawCard()); // for the dealer, the first card is turned over let newcard = await this.drawCard(); newcard.showback = true; this.pcCards.push(newcard); this.playerCards.push(await this.drawCard()); this.pcCards.push(await this.drawCard()); },


Deux choses à souligner. D'abord, je distribue au joueur, puis au PC (ou au croupier, en ce qui concerne le nom, je fais des allers-retours), puis à nouveau. Je modifie également l'objet de résultat de la carte pour que showback soit défini de sorte que je puisse rendre le dos de la carte pour le croupier.


Voici comment cela se passe en HTML :


 <div id="pcArea" class="cardArea"> <h3>Dealer</h3> <template x-for="card in pcCards"> <!-- todo: don't like the logic in template --> <img :src="card.showback?BACK_CARD:card.image" :title="card.showback?'':card.title"> </template> </div> <div id="playerArea" class="cardArea"> <h3>Player</h3> <template x-for="card in playerCards"> <img :src="card.image" :title="card.title"> </template> </div>


BACK_CARD est simplement une constante :


 const BACK_CARD = "https://deckofcardsapi.com/static/img/back.png";

Logique du jeu

Donc, à ce stade, je pourrais cliquer sur l'application et obtenir une main de Blackjack :


Démo des cartes affichées


En bas, j'ai utilisé un div pour afficher l'état actuel :


La case d'état blanche demande au joueur ce qu'il veut faire.


Ma logique était la suivante :


  • Commencez par le joueur et laissez-le frapper ou rester debout.


  • S'ils touchent, ajoutez une nouvelle carte et voyez s'ils ont éclaté.


  • S'ils se tiennent, laissez le joueur croupier.


Concentrons-nous d'abord sur le joueur. Pour frapper, on ajoute simplement une carte :


 async hitMe() { this.hitMeDisabled = true; this.playerCards.push(await this.drawCard()); let count = this.getCount(this.playerCards); if(count.lowCount >= 22) { this.playerTurn = false; this.playerBusted = true; } this.hitMeDisabled = false; },


La vérification du buste était un peu complexe. J'ai construit une fonction pour obtenir le "compte" de la main, mais au Blackjack, les As peuvent être 1 ou 11.


J'ai compris (et j'espère que j'ai raison), que vous ne pouvez jamais avoir deux as "élevés", donc ma fonction renvoie une valeur lowCount et highCount où pour la version haute, si un As existe, il est compté comme 11, mais seulement un. Voici cette logique :


 getCount(hand) { /* For a hand, I return 2 values, a low value, where aces are considered 1s, and a high value, where aces are 11. Note that this fails to properly handle a case where I have 3 aces and could have a mix... although thinking about it, you can only have ONE ace at 11, so maybe the logic is: low == all aces at 1. high = ONE ace at 11. fixed! */ let result = {}; // first we will do low, all 1s let lowCount = 0; for(card of hand) { if(card.value === 'JACK' || card.value === 'KING' || card.value === 'QUEEN') lowCount+=10; else if(card.value === 'ACE') lowCount += 1; else lowCount += Number(card.value); //console.log(card); } //console.log('lowCount', lowCount); let highCount = 0; let oneAce = false; for(card of hand) { if(card.value === 'JACK' || card.value === 'KING' || card.value === 'QUEEN') highCount+=10; else if(card.value === 'ACE') { if(oneAce) highCount += 1; else { highCount += 10; oneAce = true; } } else highCount += Number(card.value); } //console.log('highCount', highCount); return { lowCount, highCount }; },


Si le joueur saute, nous terminons le jeu et laissons l'utilisateur recommencer. S'ils restent debout, il est temps pour le croupier de prendre le relais. Cette logique était simple - frapper en dessous de 17 ans et soit buste, soit debout.


Afin de le rendre un peu plus excitant, j'ai utilisé une fonction variable et asynchrone, delay , pour ralentir les actions du croupier afin que vous puissiez les voir se dérouler en (un peu) temps réel. Voici la logique du concessionnaire :


 async startDealer() { /* Idea is - I take a card everytime I'm < 17. so i check my hand, and do it, see if im going to stay or hit. if hit, i do a delay though so the game isn't instant. */ // really first, initial text this.pcText = 'The dealer begins their turn...'; await delay(DEALER_PAUSE); // first, a pause while we talk this.pcText = 'Let me show my hand...'; await delay(DEALER_PAUSE); // reveal my second card this.pcCards[0].showback = false; // what does the player have, we need the best under 22 let playerCount = this.getCount(this.playerCards); let playerScore = playerCount.lowCount; if(playerCount.highCount < 22) playerScore = playerCount.highCount; //console.log('dealer needs to beat', playerScore); // ok, now we're going to loop until i bust/win let dealerLoop = true; while(dealerLoop) { let count = this.getCount(this.pcCards); /* We are NOT doing 'soft 17', so 1 ace always count as 11 */ if(count.highCount <= 16) { this.pcText = 'Dealer draws a card...'; await delay(DEALER_PAUSE); this.pcCards.push(await this.drawCard()); } else if(count.highCount <= 21) { this.pcText = 'Dealer stays...'; await delay(DEALER_PAUSE); dealerLoop = false; this.pcTurn = false; if(count.highCount >= playerScore) this.pcWon = true; else this.playerWon = true; } else { dealerLoop = false; this.pcTurn = false; this.pcBusted = true; } } }


Pour votre information, pcText est utilisé dans la zone d'état blanche comme moyen de définir des messages de jeu.


Et fondamentalement - c'est tout. Si vous voulez y jouer vous-même, consultez le CodePen ci-dessous, et n'hésitez pas à le bifurquer et à ajouter des améliorations :