What would be the best way to organize BlackJack in JavaScript and maybe start with blank slate?
Specific areas:
- Updating UI
- Incorporating the dealer hand into the
Handclass to reduce the repetitiveness - Possibly reducing the nested
IFs - Incorporating the Split
I wasn't worried about the betting amounts/payouts/chip counts for this prototype, but of course will add that.
Code:
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>BlackJack Early Prototype</title>
<style media="screen">
.flex-row{
display: flex;
flex-flow: row wrap;
justify-content: space-around;
}
</style>
</head>
<body>
<button id='deal'>deal</button>
<div class="flex-row">
<div id="dealer">
</div>
<div id="hand1">
</div>
<div id="hand2">
</div>
<div id="hand3">
</div>
</div>
<script type="text/javascript">
const suits = [
{
digit: 'H',
word: 'hearts'
},
{
digit: 'C',
word: 'clubs'
},
{
digit: 'D',
word: 'diamonds'
},
{
digit: 'S',
word: 'spades'
}
]
const cardsWithoutSuits = [
{
numeric: 11,
word: 'ace',
digit: 'A'
},
{
numeric: 2,
word: 'two',
},
{
numeric: 3,
word: 'three',
},
{
numeric: 4,
word: 'four',
},
{
numeric: 5,
word: 'five',
},
{
numeric: 6,
word: 'six',
},
{
numeric: 7,
word: 'seven',
},
{
numeric: 8,
word: 'eight',
},
{
numeric: 9,
word: 'nine',
},
{
numeric: 10,
word: 'ten',
},
{
numeric: 10,
word: 'jack',
digit: 'J'
},
{
numeric: 10,
word: 'queen',
digit: 'Q'
},
{
numeric: 10,
word: 'king',
digit: 'K'
}
]
class Hand{
constructor(bet){
// assigning the bet value
this.bet = bet;
// default values, no cards dealt yet
this.cards = [];
this.value = 0;
this.blackjack = false;
this.soft = false;
this.bust = false;
this.winner = false;
this.aceQuantity = 0;
this.canHit = false;
this.canStay = false;
this.canSplit = false;
this.canDouble = false;
this.finished = false;
this.textResult = '';
this.payout = 0;
};
evaluate(){
this.aceQuantity = this.cards.filter(x => x.word === 'ace').length;
this.value = this.cards.filter(x => x.word !== 'ace').reduce((total, x) => +total + x.numeric, 0);
this.soft = false;
for (var i = 0; i < this.aceQuantity; i++) {
if (this.value + 11 > 21) this.value += 1;
else {
this.value +=11;
if (this.value !== 21) this.soft = true;
}
}
if (this.cards.length === 2) {
this.canDouble = true;
this.canSplit = this.cards[0].word === this.cards[1].word;
} else {
this.canSplit = false;
this.canDouble = false;
}
if (this.value > 21){
this.bust = true;
this.finished = true;
this.canHit = false;
this.canStay = false;
}
if(this.value === 21){
this.finished = true;
this.canHit = false;
if (this.cards.length === 2) {
this.blackjack = true;
}
}
if (this.bust) {
this.textResult = `Busted!`;
this.payout = 0;
} else if (this.blackjack) {
this.textResult = `BlackJack!`;
} else if (game.dealerFinished) {
if (this.finished) {
if (game.dealerValue > 21) {
this.textResult = `Winner!`;
} else if (this.value > game.dealerValue) {
this.textResult = `Winner!`;
} else if (this.value === game.dealerValue) {
this.textResult = `Push`;
} else if (this.value < game.dealerValue) {
this.textResult = `Loser`;
}
}
} else {
this.textResult = `Standing on ${this.value}`;
}
}
}
function createDeck(decks = 1){
let deck = [];
for (let i = 0; i < decks; i++) {
suits.forEach( x => {
cardsWithoutSuits.forEach( y => {
deck.push({
numeric: y.numeric,
word: y.word,
suit: x.word,
phrase: `${y.word} of ${x.word}`,
abbr: `${y.hasOwnProperty('digit') ? y.digit : y.numeric}${x.digit}`
})
})
})
}
return deck;
}
function shuffle(array){
let array2 = [];
while (array.length){
let index = Math.floor(Math.random() * array.length);
let card = array.splice(index, 1);
array2.push(card[0]);
}
return array2;
}
let game = {
state: 'start',
deck: [],
dealerCards: [],
dealerFinished: false,
dealerValue: 0,
hands: [],
shuffle: function(){
this.deck = shuffle(createDeck(4));
},
deal: function(){
this.hands.forEach(x => {
x.cards.push(this.deck.shift());
x.evaluate();
});
this.dealerCards.push(this.deck.shift());
this.hands.forEach(x => {
x.cards.push(this.deck.shift());
x.evaluate();
});
this.dealerCards.push(this.deck.shift());
},
tempCreateTestHands: function(){
this.hands.push(new Hand(5));
this.hands.push(new Hand(10));
this.hands.push(new Hand(25));
},
dealCard: function(hand){
if (hand === -1) {
this.dealerCards.push(this.deck.shift());
updateUI();
} else {
this.hands[hand].cards.push(this.deck.shift());
this.hands[hand].evaluate();
}
},
tempStart: function(){
game.dealerFinished = false;
game.hands = [];
game.dealerCards = [];
if (game.deck.length < 30) game.shuffle();
game.tempCreateTestHands();
game.deal();
updateUI();
},
dealerTurn: function(){
let aceQuantity = this.dealerCards.filter(x => x.word === 'ace').length;
let value = this.dealerCards.filter(x => x.word !== 'ace').reduce((total, x) => +total + x.numeric, 0);
for (var i = 0; i < aceQuantity; i++) {
if (value + 11 > 21) value += 1;
else {
value +=11;
}
}
this.dealerValue = value;
if (value < 17) {
this.dealCard(-1);
} else {
this.dealerFinished = true;
game.hands.forEach(x => x.evaluate());
updateUI();
}
}
}
game.tempStart();
function updateUI(){
const playersUI = [document.getElementById('hand1'), document.getElementById('hand2'), document.getElementById('hand3')];
const dealer = document.getElementById('dealer');
const handsRemaining = game.hands.filter(x => !x.finished).length;
if (handsRemaining) dealer.innerHTML = `<h2>Dealer</h2><p>Card Hidden</p><p>${game.dealerCards[1].phrase}</p><p>Showing ${game.dealerCards[1].numeric}</p>`;
else dealer.innerHTML = `<h2>Dealer</h2>` + game.dealerCards.map(x => `<p>${x.phrase}</p>`).join('') + `<p>Total: ${game.dealerValue}</p>`;
if (!handsRemaining && !game.dealerFinished) {
game.dealerTurn();
}
for (var i = 0; i < game.hands.length; i++) {
let buttons = '';
if (game.hands[i].finished){
buttons += `<div>`;
if (game.hands[i].busted) buttons +=`BUSTED`;
else {
buttons += game.hands[i].textResult;
}
buttons += `</div>`
} else {
buttons += `<div>
<button class='hit' onclick="buttonHandler(${i}, 'hit')">HIT</button>
<button class='stay' onclick="buttonHandler(${i}, 'stay')">STAY</button>
`;
if (game.hands[i].canDouble) buttons +=`<button class='double' onclick="buttonHandler(${i}, 'double')">DOUBLE</button>`;
if (game.hands[i].canSplit) buttons +=`<button class='split' onclick="buttonHandler(${i}, 'split')">SPLIT</button>`;
buttons += `</div>`;
}
playersUI[i].innerHTML = `<h2>Hand ${i + 1}</h2>` + game.hands[i].cards.map(x => `<p>${x.phrase}</p>`).join('') + `<p>Total: ${game.hands[i].soft ? 'Soft' : ''} ${game.hands[i].value}</p>${buttons}`;
}
}
function buttonHandler(playerHand, action){
switch (action) {
case 'stay':
game.hands[playerHand].finished = true;
break;
case 'hit':
game.dealCard(playerHand);
break;
case 'double':
game.dealCard(playerHand);
game.hands[playerHand].finished = true;
game.hands[playerHand].bet = game.hands[playerHand].bet * 2;
break;
case 'split':
console.log(`split function not setup yet...`);
break;
default:
console.log(`error, cannot find ${action} in the switch statement.`);
}
updateUI();
}
document.getElementById("deal").onclick = function() {
// alert("hello");
game.tempStart();
}
</script>
</body>
</html>