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
Read this story w/o Javascript

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" 규칙이 있습니다. (대체로 나는 그 일을 제대로 했다고 확신합니다.)


  • 이 게임은 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()); },


지적할 점은 두 가지입니다. 먼저 플레이어에게 거래를 한 다음 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이 될 수 있습니다.


나는 두 개의 '하이' 에이스를 가질 수 없다는 것을 알아냈습니다(그리고 내 말이 맞기를 바랍니다). 따라서 내 함수는 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을 확인하고 자유롭게 포크하여 개선 사항을 추가하세요.