paint-brush
Cách tạo trò chơi Blackjack với Alpine.js và API bộ bàitừ tác giả@raymondcamden
1,049 lượt đọc
1,049 lượt đọc

Cách tạo trò chơi Blackjack với Alpine.js và API bộ bài

từ tác giả Raymond Camden6m2023/07/20
Read on Terminal Reader

dài quá đọc không nổi

Deck of Cards API xử lý mọi thứ có thể tưởng tượng được liên quan đến làm việc với bộ bài. Nó xử lý việc tạo một bộ bài đã xáo trộn (chứa một hoặc nhiều cỗ bài), chia bài (hoặc nhiều quân bài) và thậm chí là xáo trộn lại. Đó là một API cực kỳ nhiều tính năng và trên hết, nó hoàn toàn miễn phí.
featured image - Cách tạo trò chơi Blackjack với Alpine.js và API bộ bài
Raymond Camden HackerNoon profile picture

Cách đây một thời gian, tôi tình cờ gặp một dịch vụ khá hấp dẫn, API bộ bài . API này xử lý mọi thứ có thể tưởng tượng được liên quan đến làm việc với bộ bài. Nó xử lý việc tạo một bộ bài đã xáo trộn (chứa một hoặc nhiều cỗ bài), chia bài (hoặc nhiều quân bài) và thậm chí là xáo trộn lại.


Thậm chí tốt hơn, nó bao gồm các hình ảnh thẻ mà bạn có thể sử dụng nếu bạn không muốn tìm hình ảnh của riêng mình:


Đó là một API cực kỳ nhiều tính năng và trên hết, nó hoàn toàn miễn phí. Thậm chí không cần chìa khóa. Tôi đã biết về API này một thời gian và đã dự tính xây dựng một trò chơi bài với nó, nhưng nhận ra rằng các trò chơi có thể nhanh chóng đi từ đơn giản đến khá phức tạp.


Trên thực tế, bạn bè của tôi đã thúc giục tôi không nên dành thời gian cho việc này, và thành thật mà nói, họ có thể đúng, nhưng tôi đã có một lịch sử lâu dài về các bản trình diễn mã xây dựng không có ý nghĩa. ;)


Đối với bản demo của tôi, tôi đã thực hiện theo các quy tắc sau:


  • Rõ ràng, các quy tắc cơ bản của Blackjack, cố gắng đến gần 21 nhất có thể mà không vượt qua.


  • Không cá cược, chỉ một tay tại một thời điểm.


  • Không tăng gấp đôi hoặc chia tách.


  • Đại lý có quy tắc "17 mềm". (Tôi gần như chắc chắn rằng mình đã làm đúng.)


  • Trò chơi sử dụng sáu bộ bài (tôi đã đọc ở đâu đó rằng nó là tiêu chuẩn).

Thiết lập trò chơi

Ban đầu, cả người chơi và máy tính đều có một mảng đại diện cho các quân bài của họ.


 playerCards:[], pcCards:[],


Phương thức deal xử lý việc thiết lập các ván bài cho cả hai người chơi:


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


Hai điều cần chỉ ra. Đầu tiên, tôi chia bài cho người chơi, sau đó là PC (hoặc người chia bài, tôi thường nói đi nói lại về tên gọi), rồi quay lại. Tôi cũng sửa đổi đối tượng kết quả bài để có bộ showback sao cho tôi có thể hiển thị mặt sau của quân bài cho người chia bài.


Đây là cách nó được thực hiện trong 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 đơn giản là một hằng số:


 const BACK_CARD = "https://deckofcardsapi.com/static/img/back.png";

Trò chơi logic

Vì vậy, tại thời điểm này, tôi có thể nhấn vào ứng dụng và nhận một ván bài Blackjack:


Bản demo của các thẻ được hiển thị


Ở dưới cùng, tôi đã sử dụng một div để hiển thị trạng thái hiện tại:


Hộp trạng thái màu trắng hỏi người chơi họ muốn làm gì.


Logic của tôi là như vậy:


  • Bắt đầu với người chơi và để họ đánh hoặc đứng.


  • Nếu chúng trúng, hãy thêm một thẻ mới và xem chúng có bị vỡ không.


  • Nếu họ đứng, hãy để người chơi chia bài.


Hãy tập trung vào người chơi trước. Để đánh, chúng tôi chỉ cần thêm một thẻ:


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


Bust kiểm tra là một chút phức tạp. Tôi đã xây dựng một chức năng để lấy 'số' cho ván bài, nhưng trong Blackjack, quân Át có thể là 1 hoặc 11.


Tôi đã tìm ra (và hy vọng là tôi đúng), rằng bạn không bao giờ có thể có hai quân Át 'cao', vì vậy hàm của tôi trả về giá trị lowCounthighCount trong đó đối với phiên bản cao, nếu có quân Át, thì nó được tính là 11, nhưng chỉ một. Đây là logic đó:


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


Nếu người chơi bị hỏng, chúng tôi sẽ kết thúc trò chơi và để người dùng bắt đầu lại. Nếu họ đứng vững, đã đến lúc nhà cái tiếp quản. Logic đó rất đơn giản - đánh khi dưới 17 và phá sản hoặc đứng vững.


Để làm cho trò chơi trở nên thú vị hơn một chút, tôi đã sử dụng một chức năng có thể thay đổi và không đồng bộ, delay , để làm chậm hành động của người chia bài để bạn có thể thấy họ diễn ra trong (gần như) thời gian thực. Đây là logic của đại lý:


 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; } } }


FYI, pcText được sử dụng trong khu vực trạng thái màu trắng như một cách để thiết lập thông báo trò chơi.


Và về cơ bản - đó là nó. Nếu bạn muốn tự mình chơi, hãy xem CodePen bên dưới và thoải mái rẽ nhánh và thêm các cải tiến: