少し前に、 Deck of Cards APIという非常に魅力的なサービスに出会いました。この API は、トランプの操作に関連するあらゆるものを処理します。シャッフルされたカードのセット (1 つ以上のデッキを含む) の作成、カード (複数可) の配り、さらには再シャッフルを処理します。
さらに良いことに、独自のカード画像を見つけたくない場合に使用できるカード画像も含まれています。
これは信じられないほど機能が満載の API であり、何よりも完全に無料です。鍵さえ必要ありません。私はこの API については以前から知っており、それを使用してカード ゲームを構築することを検討していましたが、ゲームは簡単なものからかなり複雑なものまですぐに変化する可能性があることに気づきました。
実際、友人たちはこれに時間を費やさないように私に強く勧めました。正直に言うと、おそらく彼らは正しかったのですが、私には意味のないコードデモを構築してきた長い歴史があります。 ;)
私のデモでは、次のルールを使用しました。
最初は、プレーヤーとコンピューターの両方が、手を表す配列を持っています。
playerCards:[], pcCards:[],
deal
メソッドは、両方のプレイヤーのハンドのセットアップを処理します。
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()); },
指摘すべきことが 2 つあります。最初にプレーヤーに取引し、次に PC (またはディーラー、名前的には行ったり来たりする感じです)、そしてまた元に戻ります。また、ディーラー用にカードの裏面をレンダリングできるように、カード結果オブジェクトを変更してshowback
を設定します。
これを 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
は単なる定数です。
const BACK_CARD = "https://deckofcardsapi.com/static/img/back.png";
したがって、この時点で、アプリにアクセスしてブラックジャックのハンドを得ることができました。
下部では、div を使用して現在のステータスを表示しています。
私のロジックは次のようなものでした。
まずはプレイヤーに注目してみましょう。ヒットするには、カードを追加するだけです。
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; },
バストチェックは少し複雑でした。ハンドの「カウント」を取得する関数を作成しましたが、ブラックジャックではエースは 1 または 11 になります。
私は、「上位」エースを 2 つ持つことはできないことを理解しました (そして私が正しいことを願っています)。そのため、私の関数はlowCount
とhighCount
値を返します。上位バージョンでは、エースが存在する場合、11 としてカウントされますが、一。そのロジックは次のとおりです。
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 }; },
プレイヤーがバストした場合、ゲームを終了し、ユーザーが最初からやり直せるようにします。彼らが立っている場合は、ディーラーが引き継ぐ時が来ています。そのロジックは単純で、17 歳未満でヒットし、バストするかスタンドするかのどちらかです。
もう少しエキサイティングなものにするために、変数と非同期関数のdelay
使用してディーラーのアクションを遅くし、(一種の) リアルタイムでのプレイを確認できるようにしました。ディーラーのロジックは次のとおりです。
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; } } }
参考までに、 pcText
ゲーム メッセージを設定する方法として白いステータス領域で使用されます。
そして基本的にはそれだけです。自分でプレイしてみたい場合は、以下の CodePen をチェックして、自由にフォークして改善を追加してください。