paint-brush
如何使用 Alpine.js 和 Deck of Cards API 创建二十一点游戏by@raymondcamden
1,146
1,146

如何使用 Alpine.js 和 Deck of Cards API 创建二十一点游戏

Raymond Camden6m2023/07/20
Read on Terminal Reader

Deck of Cards API 处理与使用一副纸牌相关的所有内容。它处理创建一组洗牌的牌(包含一副或多副牌)、发一张或多张牌,甚至重新洗牌。它是一个功能丰富的 API,最重要的是,它完全免费。
featured image - 如何使用 Alpine.js 和 Deck of Cards API 创建二十一点游戏
Raymond Camden HackerNoon profile picture

前段时间,我遇到了一个非常有趣的服务,即Deck of Cards API 。该 API 处理与使用一副纸牌相关的所有可以想象到的事情。它处理创建一组洗牌的牌(包含一副或多副牌)、发一张或多张牌,甚至重新洗牌。


更好的是,它包含卡片图像,如果您不想找到自己的卡片图像,您可以使用:


它是一个功能丰富的 API,最重要的是,它完全免费。甚至不需要钥匙。我了解这个 API 一段时间了,并考虑过用它构建一个纸牌游戏,但意识到游戏可以很快从简单变得相当复杂。


事实上,我的朋友强烈建议我不要花时间在这上面,老实说,他们可能是对的,但我长期以来一直在构建没有意义的代码演示。 ;)


对于我的演示,我遵循以下规则:


  • 显然,基本的二十一点规则是,尽量接近 21 点而不超过。


  • 不下注,一次只玩一手牌。


  • 没有加倍或分裂。


  • 庄家有“软17”规则。 (我几乎确信我做对了。)


  • 游戏使用六副牌(我在某处读到这是标准的)。

游戏设置

最初,玩家和计算机都有一个代表他们手牌的数组。


 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()); },


有两点需要指出。首先,我与玩家发牌,然后是电脑(或发牌员,就名字而言我有点来回),然后再返回。我还修改了卡片结果对象以设置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; },


胸围检查有点复杂。我构建了一个函数来获取手牌的“计数”,但在 Blackjack 中,A 可以是 1 或 11。


我发现(希望我是对的),你永远不可能有两个“高”A,所以我的函数返回一个lowCounthighCount值,对于高版本,如果 A 存在,它会被算作 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,并随时对其进行分叉并添加改进: