Algum tempo atrás, encontrei um serviço bastante fascinante, a API Deck of Cards . Essa API lida com tudo o que se possa imaginar relacionado ao trabalho com baralhos de cartas. Ele lida com a criação de um conjunto de cartas embaralhadas (contendo um ou mais baralhos), distribuindo uma carta (ou cartas) e até mesmo reembaralhando.
Melhor ainda, inclui imagens de cartões que você pode usar se não quiser encontrar as suas próprias:
É uma API incrivelmente repleta de recursos e, o melhor de tudo, é totalmente gratuita. Não há necessidade nem mesmo de uma chave. Conheço essa API há algum tempo e pensei em criar um jogo de cartas com ela, mas percebi que os jogos podem passar rapidamente de simples a razoavelmente complexos.
Na verdade, meus amigos insistiram muito para que eu não perdesse tempo com isso e, honestamente, eles provavelmente estavam certos, mas tenho uma longa história de criação de demos de código que não fazem sentido. ;)
Para minha demonstração, segui as seguintes regras:
Inicialmente, o jogador e o computador têm uma matriz representando suas mãos.
playerCards:[], pcCards:[],
O método deal
lida com a configuração das mãos para ambos os jogadores:
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()); },
Duas coisas a apontar. Primeiro, negocio com o jogador, depois com o PC (ou dealer, pelo nome, eu meio que vou e volto) e depois volto novamente. Também modifico o objeto de resultado da carta para que showback
seja definido de forma que eu possa renderizar o verso da carta para o dealer.
Veja como isso é feito em 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
é simplesmente uma constante:
const BACK_CARD = "https://deckofcardsapi.com/static/img/back.png";
Então, neste ponto, eu poderia acessar o aplicativo e obter uma mão de Blackjack:
Na parte inferior, usei um div para exibir o status atual:
Minha lógica era assim:
Vamos nos concentrar no jogador primeiro. Para acertar, basta adicionar um cartão:
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; },
A verificação do busto foi um pouco complexa. Criei uma função para obter a 'contagem' da mão, mas no Blackjack, os ases podem ser 1 ou 11.
Eu descobri (e espero estar certo), que você nunca pode ter dois ases 'altos', então minha função retorna um valor lowCount
e highCount
onde para a versão alta, se um Ace existe, é contado como 11, mas apenas um. Aqui está essa lógica:
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 }; },
Se o jogador estourar, encerramos o jogo e deixamos o usuário recomeçar. Se eles permanecerem, é hora de o revendedor assumir. Essa lógica era simples - acertar enquanto estava abaixo de 17 e rebentar ou ficar de pé.
Para torná-lo um pouco mais emocionante, usei uma função variável e assíncrona, delay
, para retardar as ações do dealer para que você possa vê-las acontecer em (meio) tempo real. Aqui está a lógica do dealer:
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; } } }
Para sua informação, pcText
é usado na área de status branca como uma forma de definir as mensagens do jogo.
E basicamente - é isso. Se você quiser jogar sozinho, confira o CodePen abaixo e sinta-se à vontade para bifurcá-lo e adicionar melhorias: