前段时间,我遇到了一个非常有趣的服务,即Deck of Cards API 。该 API 处理与使用一副纸牌相关的所有可以想象到的事情。它处理创建一组洗牌的牌(包含一副或多副牌)、发一张或多张牌,甚至重新洗牌。
更好的是,它包含卡片图像,如果您不想找到自己的卡片图像,您可以使用:
它是一个功能丰富的 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()); },
有两点需要指出。首先,我与玩家发牌,然后是电脑(或发牌员,就名字而言我有点来回),然后再返回。我还修改了卡片结果对象以设置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,所以我的函数返回一个lowCount
和highCount
值,对于高版本,如果 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,并随时对其进行分叉并添加改进: