Vor einiger Zeit bin ich auf einen ziemlich faszinierenden Dienst gestoßen, die Deck of Cards API . Diese API verarbeitet alles, was man sich im Zusammenhang mit der Arbeit mit Kartenspielen vorstellen kann. Es übernimmt das Erstellen eines gemischten Kartensatzes (der einen oder mehrere Stapel enthält), das Austeilen einer Karte (oder Karten) und sogar das Neumischen.
Noch besser: Es enthält Kartenbilder, die Sie verwenden können, wenn Sie keine eigenen finden möchten:
Es handelt sich um eine unglaublich funktionsreiche API, und das Beste ist, dass sie völlig kostenlos ist. Es ist nicht einmal ein Schlüssel erforderlich. Ich kenne diese API schon seit einiger Zeit und habe darüber nachgedacht, damit ein Kartenspiel zu erstellen, habe aber festgestellt, dass Spiele schnell von einfach zu ziemlich komplex werden können.
Tatsächlich haben mir meine Freunde dringend geraten, keine Zeit damit zu verschwenden, und ehrlich gesagt hatten sie wahrscheinlich Recht, aber ich habe eine lange Geschichte mit der Erstellung von Code-Demos, die keinen Sinn ergeben. ;)
Für meine Demo habe ich mich an die folgenden Regeln gehalten:
Zunächst verfügen sowohl der Spieler als auch der Computer über ein Array, das ihre Hände darstellt.
playerCards:[], pcCards:[],
Die deal
Methode übernimmt das Einrichten der Hände für beide Spieler:
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()); },
Zwei Dinge sind hervorzuheben. Zuerst teile ich es dem Spieler aus, dann dem PC (oder dem Dealer, was den Namen angeht, gehe ich hin und her) und dann wieder zurück. Außerdem ändere ich das Kartenergebnisobjekt so, dass es showback
Einstellung hat, sodass ich die Rückseite der Karte für den Dealer rendern kann.
So geht das in 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
ist einfach eine Konstante:
const BACK_CARD = "https://deckofcardsapi.com/static/img/back.png";
An diesem Punkt könnte ich also die App aufrufen und eine Blackjack-Hand erhalten:
Unten habe ich ein div verwendet, um den aktuellen Status anzuzeigen:
Meine Logik war so:
Konzentrieren wir uns zunächst auf den Spieler. Zum Schlagen fügen wir einfach eine Karte hinzu:
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; },
Die Brustprüfung war etwas komplex. Ich habe eine Funktion erstellt, um die „Anzahl“ für die Hand zu ermitteln, aber beim Blackjack können Asse 1 oder 11 sein.
Ich habe herausgefunden (und hoffe, dass ich recht habe), dass man niemals zwei „hohe“ Asse haben kann, also gibt meine Funktion einen lowCount
und highCount
Wert zurück, wobei für die hohe Version ein Ass, wenn es existiert, als 11 gezählt wird, aber nur eins. Hier ist diese Logik:
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 }; },
Wenn der Spieler ausscheidet, beenden wir das Spiel und lassen den Benutzer von vorne beginnen. Wenn sie bestehen bleiben, ist es Zeit für den Händler, zu übernehmen. Diese Logik war einfach: einen Treffer erzielen, während man unter 17 ist, und entweder Pleit oder Stand.
Um es etwas spannender zu machen, habe ich eine variable und asynchrone Funktion namens delay
verwendet, um die Aktionen des Dealers zu verlangsamen, sodass Sie sie (irgendwie) in Echtzeit sehen können. Hier ist die Logik des Händlers:
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; } } }
Zu Ihrer Information: pcText
wird im weißen Statusbereich verwendet, um Spielnachrichten festzulegen.
Und im Grunde – das ist es. Wenn Sie es selbst spielen möchten, schauen Sie sich den CodePen unten an und teilen Sie ihn gerne mit einem Fork und fügen Sie Verbesserungen hinzu: