PragmaticPhil
Published © GPL3+

Making card games on micro:bit, Part 3

Use the card game engine developed in Parts 1 & 2 to implement a 1 player blackjack game

AdvancedFull instructions provided1.5 hours771
Making card games on micro:bit, Part 3

Things used in this project

Hardware components

BBC micro:bit board
BBC micro:bit board
Any SBC that you can code in MakeCode will do - check out the excellent Maker.MakeCode IDE and see the broad range of boards supported :)
×1
OD01
XinaBox OD01
We use an SSD1306 as the display. You can use a different type of screen or a differently mounted SSD1306 - the code should be easy to adapt
×1
XinaBox IM01 micro:bit bridge
We use persistent memory to ensure our graphics are visually appealing. 17 small (1kb) bitmaps are saved on microSD card and referenced in runtime. This works around limitations in micro:bit memory,
×1
XC10
XinaBox XC10
You only need 1 of these connectorss
×1

Story

Read more

Schematics

Lookup table

Each card in a standard deck is represented by an integer from 0 to 51 (called IntID). But convention: 0, 13, 26, 39 = Ace. +1 = two, +2 = three etc. 0-12 = hearts, 13-25 = Clubs etc. A lot of info is encoded into IntID - a lot of that info is shown in this lookup table

config file

A premade config file you can use for testing.
Contains 1 string, 42 chars, each char numeric.
Each chunk of 3 chars is a value as defined in configHelp spreadsheet

Create Config Files

A spreadsheet to help you easily create a config file

Code

Javascript and link to MakeCode project for blocks

JavaScript
The code below works fine so you can ignore the warnings. Alternately go directly to the MakeCode project using this link: https://makecode.microbit.org/_8zF9rf9cpX5e
function showStats () {
    OD01.horizontalLine(2, 18, 120, 1)
    OD01.showString("Hands won:", 12, 3, 1)
    OD01.showNumber(handsWon, 88, 3, 1)
    OD01.showString("Hands lost:", 6, 4, 1)
    OD01.showNumber(handsPlayed - handsWon, 88, 4, 1)
    OD01.showString("Winnings:", 18, 5, 1)
    OD01.showNumber(cumulativeWinTotal, 88, 5, 1)
}
function showPlayerHandHeader () {
    OD01.showString("Total:", 0, playerID, 1)
    showHandTotal(playerID)
    OD01.showString("Bet:", 72, playerID, 1)
    OD01.showNumber(getBetAmount(), 100, playerID, 1)
}
function getHandTotal (playerDealerID: number) {
    return getHandValue(rawHandValues[playerDealerID], acesInHand[playerDealerID])
}
function loadConfig () {
    betAmounts = [13]
    configString = IM01.readFile("config.txt")
    for (let index = 0; index <= 12; index++) {
        utilityVariable = parseFloat(configString.substr(index * 3, 3))
        betAmounts[index] = utilityVariable
    }
    dealerStickAmount = parseFloat(configString.substr(39, 3))
}
function drawCardFace (lateralPosition: number, faceID: number) {
    drawCardAsset(lateralPosition + 1, 40, IM01.readFile("face" + convertToText(faceID) + ".txt"))
}
function processBlackjack (handWinner: number) {
    OD01.clear()
    OD01.showString("BLACKJACK!", 32, 0, 1)
    processHandOutcome(handWinner)
    showHandSummary(handWinner)
    basic.pause(2000)
    gamePhase = phase1_NewHand
}
function showHandTotal (playerDealerID: number) {
    OD01.showString("  ", 44, playerDealerID, 1)
    OD01.showNumber(getHandTotal(playerDealerID), 44, playerDealerID, 1)
}
function checkForBlackjack () {
    if (isBlackjack(dealerID, dealerHand[0], dealerHand[1])) {
        processBlackjack(dealerID)
        return true
    } else {
        if (isBlackjack(playerID, playerHand[0], playerHand[1])) {
            processBlackjack(playerID)
            return true
        }
    }
    return false
}
function dealCard (playerDealerID: number) {
    utilityVariable = getDealtCard()
    activeCard_PackPosition += 1
    if (playerDealerID == playerID) {
        playerHand[cardsInHand[playerDealerID]] = utilityVariable
    } else {
        dealerHand[cardsInHand[playerDealerID]] = utilityVariable
    }
    drawCard(getLateralDrawPosition(cardsInHand[playerDealerID]), utilityVariable)
    cardsInHand[playerDealerID] = cardsInHand[playerDealerID] + 1
    utilityVariable = getCardValue(getCardFaceID(utilityVariable))
    if (utilityVariable == 11) {
        acesInHand[playerDealerID] = acesInHand[playerDealerID] + 1
    }
    rawHandValues[playerDealerID] = rawHandValues[playerDealerID] + utilityVariable
}
function drawAsset (lateralPosition: number, height: number, bitmapString: string, imageWidth: number, imageHeight: number) {
    countColumns = 0
    countRows = 0
    for (let index = 0; index <= imageWidth * imageHeight - 1; index++) {
        if (bitmapString.charAt(index + 1) == "1") {
            OD01.pixel(lateralPosition + countColumns, height + countRows, 1)
        }
        countColumns += 1
        if (countColumns >= imageWidth) {
            countColumns = 0
            countRows += 1
        }
    }
}
function getBetAmount () {
    return betAmounts[getCardFaceID(playerHand[0])]
}
input.onButtonPressed(Button.A, function () {
    if (gamePhase == phase2_PlayerTurn) {
        twist(playerID)
    }
})
function setHandArrays () {
    playerHand = [handMaxSize]
    dealerHand = [handMaxSize]
    cardsInHand = [2]
    acesInHand = [2]
    rawHandValues = [2]
}
function shufflePack () {
    resetPack()
    for (let loopX = 0; loopX <= packSize - 1; loopX++) {
        cardChosen = randint(0, originPack.length - 1)
        deckOfCards.insertAt(loopX, originPack.removeAt(cardChosen))
    }
    activeCard_PackPosition = 0
    numberPackShuffles += 1
}
function getLateralDrawPosition (drawCardNumber: number) {
    if (drawCardNumber * 26 + 24 >= 128) {
        return drawCardNumber * 26 - 1
    }
    return drawCardNumber * 26
}
function getCardSuit (cardID: number) {
    return (cardID - getCardFaceID(cardID)) / 13
}
function isBlackjack (playerDealerID: number, card1: number, card2: number) {
    if (cardsInHand[playerDealerID] > 2) {
        return false
    }
    if (getCardFaceID(card1) == 0 || getCardFaceID(card2) == 0) {
        if (getCardFaceID(card1) >= 10 || getCardFaceID(card2) >= 10) {
            return true
        }
    }
    return false
}
function drawBorder (lateralPosition: number) {
    OD01.rectangle(lateralPosition, 16, lateralPosition + 24, 63, 1)
}
function drawCard (lateralPosition: number, cardID: number) {
    drawCardFace(lateralPosition, getCardFaceID(cardID))
    drawSuit(lateralPosition, getCardSuit(cardID))
    drawBorder(lateralPosition)
}
function drawSuit (lateralPosition: number, suitID: number) {
    drawCardAsset(lateralPosition + 1, 17, IM01.readFile("suit" + convertToText(suitID) + ".txt"))
}
function getDealtCard () {
    if (activeCard_PackPosition >= 52) {
        shufflePack()
    }
    return deckOfCards[activeCard_PackPosition]
}
function resetPack () {
    originPack = [packSize]
    for (let loopX2 = 0; loopX2 <= packSize - 1; loopX2++) {
        originPack[loopX2] = loopX2
    }
}
function getHandValue (rawTotal: number, numberOfAces: number) {
    if (rawTotal <= 21 || numberOfAces == 0) {
        return rawTotal
    }
    newTotal = rawTotal
    for (let index3 = 0; index3 <= numberOfAces - 1; index3++) {
        newTotal += -10
        if (newTotal <= 21) {
            return newTotal
        }
    }
    return newTotal
}
function checkForBust (playerDealerID: number) {
    if (getHandTotal(playerDealerID) > 21) {
        gamePhase = 4 + playerDealerID
    }
}
function showHandSummary (handWinner: number) {
    if (handWinner == playerID) {
        OD01.showString("Player", 0, 1, 1)
    } else {
        OD01.showString("Dealer", 0, 1, 1)
    }
    OD01.showString("wins the hand", 42, 1, 1)
    showStats()
    OD01.showString("New hand starts in", 0, 7, 1)
    for (let index = 0; index <= 3; index++) {
        OD01.showNumber(4 - index, 112, 7, 1)
        basic.pause(1000)
    }
}
function getCardValue (cardFaceID: number) {
    if (cardFaceID == 0) {
        return 11
    }
    if (cardFaceID >= 10 && cardFaceID <= 12) {
        return 10
    }
    return cardFaceID + 1
}
function processBothStuck () {
    OD01.clear()
    showHandSummary(getHandWinner())
    basic.pause(2000)
    gamePhase = phase1_NewHand
}
function processGoneBust (winnerID: number) {
    OD01.clear()
    OD01.showString("BUSTED!", 44, 0, 1)
    processHandOutcome(winnerID)
    showHandSummary(winnerID)
    gamePhase = phase1_NewHand
}
function twist (playerDealerID: number) {
    if (cardsInHand[playerDealerID] < 5) {
        dealCard(playerDealerID)
        showHandTotal(playerDealerID)
        checkForBust(playerDealerID)
        basic.pause(1000)
    } else {
        if (playerDealerID == playerID) {
            gamePhase = phase3_DealerTurn
        } else {
            gamePhase = phase6_BothStuck
        }
    }
}
function processHandOutcome (handWinner: number) {
    if (handWinner == playerID) {
        handsWon += 1
        cumulativeWinTotal += getBetAmount()
    } else {
        cumulativeWinTotal += -1 * getBetAmount()
    }
}
function getHandWinner () {
    if (getHandTotal(dealerID) >= getHandTotal(playerID)) {
        processHandOutcome(dealerID)
        return dealerID
    }
    processHandOutcome(playerID)
    return playerID
}
input.onButtonPressed(Button.B, function () {
    if (gamePhase == phase2_PlayerTurn) {
        gamePhase = phase3_DealerTurn
    }
})
function drawCardAsset (lateralPosition: number, height: number, bitmapString: string) {
    drawAsset(lateralPosition, height, bitmapString, 22, 22)
}
function startNewHand () {
    resetHandParamaters()
    handsPlayed += 1
    dealFirst2Cards(playerID)
}
function getCardFaceID (cardID: number) {
    return cardID % 13
}
function resetHandParamaters () {
    cardsInHand[playerID] = 0
    cardsInHand[dealerID] = 0
    acesInHand[playerID] = 0
    acesInHand[dealerID] = 0
    rawHandValues[playerID] = 0
    rawHandValues[dealerID] = 0
}
function setConstants () {
    playerID = 0
    dealerID = 1
    packSize = 52
    handMaxSize = 5
    phase1_NewHand = 1
    phase2_PlayerTurn = 2
    phase3_DealerTurn = 3
    phase4_PlayerBust = 4
    phase5_DealerBust = 5
    phase6_BothStuck = 6
}
function dealToDealer () {
    dealFirst2Cards(dealerID)
    if (!(checkForBlackjack())) {
        gamePhase = phase6_BothStuck
        while (getHandTotal(dealerID) <= dealerStickAmount) {
            twist(dealerID)
        }
    }
}
function dealFirst2Cards (playerDealerID: number) {
    OD01.clear()
    dealCard(playerDealerID)
    showPlayerHandHeader()
    if (playerDealerID == playerID) {
        OD01.showString("A: Twist, B: Stick", 0, 1, 1)
    } else {
        OD01.showString("Total:", 0, dealerID, 1)
    }
    dealCard(playerDealerID)
    showHandTotal(playerDealerID)
}
let phase5_DealerBust = 0
let phase4_PlayerBust = 0
let phase6_BothStuck = 0
let phase3_DealerTurn = 0
let newTotal = 0
let numberPackShuffles = 0
let originPack: number[] = []
let cardChosen = 0
let handMaxSize = 0
let phase2_PlayerTurn = 0
let countRows = 0
let countColumns = 0
let cardsInHand: number[] = []
let activeCard_PackPosition = 0
let playerHand: number[] = []
let dealerHand: number[] = []
let dealerID = 0
let phase1_NewHand = 0
let dealerStickAmount = 0
let configString = ""
let betAmounts: number[] = []
let acesInHand: number[] = []
let rawHandValues: number[] = []
let playerID = 0
let packSize = 0
let deckOfCards: number[] = []
let handsPlayed = 0
let cumulativeWinTotal = 0
let handsWon = 0
let utilityVariable = 0
let gamePhase = 0
setConstants()
gamePhase = 0
utilityVariable = 0
handsWon = 0
cumulativeWinTotal = 0
handsPlayed = 0
deckOfCards = [packSize]
loadConfig()
setHandArrays()
shufflePack()
gamePhase = 1
basic.forever(function () {
    if (gamePhase == phase1_NewHand) {
        startNewHand()
        gamePhase = 2
    }
    if (gamePhase == phase3_DealerTurn) {
        dealToDealer()
        basic.pause(1000)
    }
    if (gamePhase == phase4_PlayerBust) {
        processGoneBust(dealerID)
    }
    if (gamePhase == phase5_DealerBust) {
        processGoneBust(playerID)
    }
    if (gamePhase == phase6_BothStuck) {
        processBothStuck()
    }
})

Credits

PragmaticPhil

PragmaticPhil

17 projects • 17 followers
Pragmatic hobbyist

Comments