paint-brush
Alpine.js と Deck of Cards API を使用してブラックジャック ゲームを作成する方法@raymondcamden
1,176 測定値
1,176 測定値

Alpine.js と Deck of Cards API を使用してブラックジャック ゲームを作成する方法

Raymond Camden6m2023/07/20
Read on Terminal Reader

長すぎる; 読むには

Deck of Cards API は、トランプの操作に関連するあらゆるものを処理します。シャッフルされたカードのセット (1 つ以上のデッキを含む) の作成、カード (複数可) の配り、さらには再シャッフルを処理します。これは信じられないほど機能が満載の API であり、何よりも完全に無料です。
featured image - Alpine.js と Deck of Cards API を使用してブラックジャック ゲームを作成する方法
Raymond Camden HackerNoon profile picture

少し前に、 Deck of Cards APIという非常に魅力的なサービスに出会いました。この API は、トランプの操作に関連するあらゆるものを処理します。シャッフルされたカードのセット (1 つ以上のデッキを含む) の作成、カード (複数可) の配り、さらには再シャッフルを処理します。


さらに良いことに、独自のカード画像を見つけたくない場合に使用できるカード画像も含まれています。


これは信じられないほど機能が満載の API であり、何よりも完全に無料です。鍵さえ必要ありません。私はこの API については以前から知っており、それを使用してカード ゲームを構築することを検討していましたが、ゲームは簡単なものからかなり複雑なものまですぐに変化する可能性があることに気づきました。


実際、友人たちはこれに時間を費やさないように私に強く勧めました。正直に言うと、おそらく彼らは正しかったのですが、私には意味のないコードデモを構築してきた長い歴史があります。 ;)


私のデモでは、次のルールを使用しました。


  • 明らかに、基本的なブラックジャックのルールでは、超えずにできるだけ 21 に近づけるように努めます。


  • 賭けはせず、一度に片手だけです。


  • 倍増や分割はありません。


  • ディーラーには「ソフト17」ルールがあります。 (私はそれが正しかったとほぼ確信しています。)


  • このゲームでは 6 つのデッキが使用されます (それが標準であるとどこかで読みました)。

ゲームのセットアップ

最初は、プレーヤーとコンピューターの両方が、手を表す配列を持っています。


 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 つ持つことはできないことを理解しました (そして私が正しいことを願っています)。そのため、私の関数はlowCounthighCount値を返します。上位バージョンでは、エースが存在する場合、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 をチェックして、自由にフォークして改善を追加してください。