Skip to content

Instantly share code, notes, and snippets.

@jchros
Created July 14, 2020 18:49
Show Gist options
  • Select an option

  • Save jchros/c842dd6e28c9fa4aac49e196bf99f873 to your computer and use it in GitHub Desktop.

Select an option

Save jchros/c842dd6e28c9fa4aac49e196bf99f873 to your computer and use it in GitHub Desktop.

Revisions

  1. jchros created this gist Jul 14, 2020.
    677 changes: 677 additions & 0 deletions blackjack.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,677 @@

    import Foundation

    //MARK: Enumerations and Classes

    enum Suit {
    case hearts, diamonds, clubs, spades
    }

    enum Language {
    case eng, fre
    }

    enum Currency {
    case Dollar, Euro, Pound
    }


    class Card {
    let value: Int // 11: Jack, 12: Queen, 13: King
    let suit: Suit

    init(value: Int, suit: Suit) {
    self.value = value
    self.suit = suit
    }

    }

    class Player {
    var hand: Array<Card>
    var score: Int
    let dealer: Bool

    init(hand: Array<Card>, dealer: Bool, score: Int = 0) {
    self.hand = hand
    self.dealer = dealer
    self.score = score
    }

    func calculateScore(_ hand: Array<Card>) {
    // This functions updates the player's
    // score based on the cards of the player.
    var score = 0
    // AceCount explained below
    var AceCount = 0

    // Sorting the cards in ascending order
    let sortedCards = hand.sorted(by: { $0.value > $1.value})

    // Calculate the score
    for card in sortedCards {
    score += card.value < 10 ? card.value : 10
    if card.value == 1 { AceCount += 1 }
    }

    // Taking soft hands into account
    if AceCount >= 1 && score + 10 < 22 { score += 10 }
    self.score = score
    }
    }

    //MARK: - Functions

    func drawAndShuffle() -> Array<Card> {
    // This functions first creates cards of each possible value in an array
    // numberOfDecks amount of times, and then puts them in a random order.
    var cardList = [Card]()
    let numberOfDecks = 8

    // Creating the cards
    for _ in 1...numberOfDecks {
    for i in 1...13 {
    for j in 0...3 {
    var newCard: Card
    switch j {
    case 0:
    newCard = Card(value: i, suit: .hearts)
    case 1:
    newCard = Card(value: i, suit: .clubs)
    case 2:
    newCard = Card(value: i, suit: .diamonds)
    default:
    newCard = Card(value: i, suit: .spades)
    }
    cardList.append(newCard)
    }
    }
    }

    // Shuffling the cards
    cardList.shuffle()
    return cardList
    }

    func cardToString(_ card: Card, language: Language = .eng) -> String {
    // This functions reads a card and turns them into a human-readable string
    // in any language.
    var string = ""
    switch language {
    case .fre:
    switch card.value {
    case 1:
    string += "As de "
    case 11:
    string += "Valet de "
    case 12:
    string += "Dame de "
    case 13:
    string += "Roi de "
    default:
    string += "\(card.value) de "
    }

    switch card.suit {
    case .clubs:
    string += "trèfle"
    case .diamonds:
    string += "carreau"
    case .hearts:
    string += "cœur"
    case .spades:
    string += "pique"
    }

    default:
    switch card.value {
    case 1:
    string += "Ace of "
    case 11:
    string += "Jack of "
    case 12:
    string += "Queen of "
    case 13:
    string += "King of "
    default:
    string += "\(card.value) of "
    }

    switch card.suit {
    case .clubs:
    string += "clubs"
    case .diamonds:
    string += "diamonds"
    case .hearts:
    string += "hearts"
    case .spades:
    string += "spades"
    }
    }
    return string
    }


    func plainLanguageArrayElementSeparator(index: Int, count: Int,
    language: Language = .eng)
    -> String {
    /* This function is used to separate elements of an array when using
    * human language (example case: an orange, a banana and an apple.)
    * In this example, the function would be used after each item of the list
    * and returns respectively ", " | " and " | "."
    */
    if index >= count - 2 {
    switch language {
    case .eng:
    return index == count - 2 ? " and " : "."
    case .fre:
    return index == count - 2 ? " et " : "."
    }
    } else { return ", "}
    }


    func plaes(index: Int, count: Int, language: Language = .eng) -> String {
    return plainLanguageArrayElementSeparator(index: index, count: count,
    language: language)
    }


    func announceCards(dealer: Bool, hand: Array<Card>, language: Language,
    hideLastCard: Bool = false) {
    // This function displays the player's cards or the dealer's cards.
    var string = ""
    switch language {
    case .eng:
    string += dealer ? "The dealer's cards: " : "Your cards: "
    case .fre:
    string += dealer ? "Les cartes du croupier: " : "Vos cartes: "
    }
    // We need to hide the dealer's second card during the player's turn
    // according to the rules of blackjack.
    if hideLastCard {
    switch language {
    case .eng:
    string += dealer ? "The dealer's cards: " : "Your cards: "
    string += "\(cardToString(hand[0])) and a hidden card."
    case .fre:
    string += dealer ? "Les cartes du dealer: " : "Vos cartes: "
    string += cardToString(hand[0], language: .fre)
    string += " et une carte cachée."
    }
    } else {
    for cardIndex in 0..<hand.count {
    string += cardToString(hand[cardIndex], language: language)
    string += plaes(index: cardIndex, count: hand.count,
    language: language)
    }
    }
    print(string)
    }


    func readBetInput(_ input: String) -> Double {
    /* This function removes any currency symbol at the beginning or at the end
    * of a string, interprets the rest, then returns the relevant Double if
    * the string is a number; 0 otherwise.
    */
    let listOfAcceptableCurrencyInputs = [Currency.Dollar : "$",
    Currency.Euro : "",
    Currency.Pound : "£"]
    var userInput = input
    while true {
    var removedSymbol = false
    for symbol in listOfAcceptableCurrencyInputs.values {
    if userInput.suffix(1) == symbol {
    userInput = String(userInput.dropLast())
    removedSymbol = true
    break
    }
    }
    if !removedSymbol { break }
    }
    while true {
    var removedSymbol = false
    for symbol in listOfAcceptableCurrencyInputs.values {
    if userInput.suffix(1) == symbol {
    userInput = String(userInput.dropLast())
    removedSymbol = true
    break
    }
    }
    if !removedSymbol { break }
    }
    let bet = Double(userInput) ?? 0
    return bet
    }


    func playerMakesABet(moneyLeft: Double, language: Language) -> Double {
    /* This functions asks the user to make a bet, checks if
    * the player has enough money to do so, asks again as long
    * as the input is invalid and returns 0 if the user has
    * no money (this ends the execution of the program).
    */
    if moneyLeft == 0 { return 0 }
    var bet: Double
    var userInput: String
    repeat {
    switch language {
    case .eng:
    print("Enter an amount of money to bet, ",
    "or press 't' to go all-in.")
    case .fre:
    print("Entrez une somme à parier ou",
    "tapez 't' pour parier l'intégralité de votre argent.")
    }
    userInput = readLine()!
    if userInput.lowercased() == "t" { return moneyLeft }
    bet = readBetInput(userInput)
    if bet > moneyLeft {
    bet = 0
    switch language {
    case .eng:
    print("You don't have enough money.")
    case .fre:
    print("Vous n'avez pas assez d'argent.")
    }
    }
    } while bet <= 0
    return bet
    }

    // TODO: Divide the hitOrStand function in two parts
    func hitOrStand(hand: Array<Card>, deck: inout Array<Card>, player: Bool,
    language: Language = .eng, score: Int = 0) -> Array<Card> {
    /* This function asks the player if they prefer to hit or to stand (to hit
    * means drawing a new card and standing means ending your turn), with a
    * prompt in the user's language, then removes a card from the deck and
    * gives it to the player if he chooses to hit.
    * It also mimics the same function for the dealer (i.e. the computer)
    * with a preset behavior (hits on 16s, stands on 17s) and without the
    * prompts.
    */
    var shortcuts = [Bool: String]()
    var userInput = ""
    var hit = true
    let standLimit = 17
    if player {
    var validInput = false
    while !validInput {
    switch language {
    case .eng:
    print("Press 'h' to hit or 's' to stand.")
    shortcuts[true] = "h"
    shortcuts[false] = "s"
    case .fre:
    print("Appuyez sur 'c' pour tirer une carte ou sur",
    "'r' pour rester.")
    shortcuts[true] = "c"
    shortcuts[false] = "r"
    }
    userInput = readLine()!.lowercased()
    if userInput == shortcuts[true] {
    validInput = true
    hit = true
    }
    else if userInput == shortcuts[false] {
    validInput = true
    hit = false
    }
    }
    } else { hit = score < standLimit }
    var hand = hand
    if hit {
    let newCard = deck.popLast()!
    hand.append(newCard)
    if player {
    switch language {
    case .eng:
    print("You've drawn a \(cardToString(newCard)).")
    case .fre:
    var string = "Vous avez tiré un"
    string += newCard.value == 12 ? "e " : " "
    string += cardToString(newCard, language: .fre)
    print(string)
    }
    } else {
    switch language {
    case .eng:
    print("The dealer has drawn a \(cardToString(newCard)).")
    case .fre:
    var string = "Le croupier a tiré un"
    string += newCard.value == 12 ? "e" : " "
    string += cardToString(newCard, language: .fre)
    }
    }
    }
    return hand
    }


    func checkIfPlayerTurnIsOver(score: Int, hit: Bool) -> Bool {
    if hit {
    return score >= 21
    } else {
    return true
    }
    }

    func moneyString(money: Double,
    currency: Currency,
    language: Language) -> String {
    var outputString: String
    switch language {
    case .fre:
    outputString = "\(money)"
    default:
    outputString = ""
    }
    switch currency {
    case .Dollar:
    outputString += "$"
    case .Euro:
    outputString += ""
    case .Pound:
    outputString += "£"
    }
    switch language {
    case .eng:
    outputString += "\(money)"
    default:
    return outputString
    }
    return outputString
    }


    func dealerDelay(_ language: Language) {
    var userInput: String
    repeat {
    switch language {
    case .eng:
    print("Press enter to continue.")
    case .fre:
    print("Appuyez sur entrée pour continuer.")
    }
    userInput = readLine()!
    } while userInput != ""
    }


    func announceCardsAndScore(player: Player, dealer: Player,
    playerTurn: Bool, language: Language) {
    if playerTurn {
    announceCards(dealer: true, hand: dealer.hand,
    language: language, hideLastCard: true)
    announceCards(dealer: false, hand: player.hand, language: language)
    switch language {
    case .eng:
    print("The dealer's score: \(dealer.score)")
    print("Your score: \(player.score)")
    case .fre:
    print("Le score du croupier: \(dealer.score)")
    print("Votre score: \(player.score)")
    }
    } else {
    announceCards(dealer: false, hand: player.hand, language: language)
    announceCards(dealer: true, hand: dealer.hand, language: language)
    switch language {
    case .eng:
    print("Your score: \(player.score)")
    print("The dealer's score: \(dealer.score)")
    case .fre:
    print("Votre score: \(player.score)")
    print("Le score du croupier: \(dealer.score)")
    }
    }
    }


    //MARK: - Welcome prompts and assignments

    // Language selection
    var userInput: String
    let listOfAcceptableLanguageInputs = [Language.eng : "e",
    Language.fre : "f"]
    var optionalLanguageSelection: Language?
    while optionalLanguageSelection == nil {
    print("Press 'e' to play Blackjack in English.")
    print("Appuyez sur 'f' pour jouer à Blackjack en Français.")
    userInput = readLine()!
    for (language, shortcut) in listOfAcceptableLanguageInputs {
    if userInput.lowercased() == shortcut {
    optionalLanguageSelection = language
    break
    }
    }
    }
    let languageSelected = optionalLanguageSelection!

    // Welcome prompt
    var firstTry = true
    repeat {
    switch languageSelected {
    case .fre:
    if firstTry {
    print("Blackjack! Appuyez sur entrée pour jouer!",
    "(Note: La banque tire à 16, reste à 17.)")
    } else {
    print("Appuyez sur entrée pour jouer.")
    }
    case .eng:
    if firstTry {
    print("Blackjack! Press enter to play!",
    "(Note: Dealer must draw to 16, and stand on all 17's.)")
    } else {
    print("Press enter to play.")
    }
    }
    userInput = readLine()!
    firstTry = false
    } while userInput != ""

    // Currency selection (has no significant effect in the game)
    let listOfAcceptableCurrencyInputs = [Currency.Dollar : "d",
    Currency.Euro : "e",
    Currency.Pound : "p"]
    var optionalCurrencySelection: Currency?
    while optionalCurrencySelection == nil {
    switch languageSelected {
    case .fre:
    print("Appuyez sur 'd' pour utiliser le dollar,",
    "'e' pour utiliser l'euro ou",
    "'p' pour utiliser la livre sterling.")
    case .eng:
    print("Press 'd' to use dollars, 'e' to use euros or 'p' to use pounds."
    )
    }
    userInput = readLine()!
    for (currency, shortcut) in listOfAcceptableCurrencyInputs {
    if userInput.lowercased() == shortcut {
    optionalCurrencySelection = currency
    break
    }
    }
    }
    let currencySelected = optionalCurrencySelection!

    // The playerMoney algorithm uses the Pareto distribution to generate a random
    // amount of money for each game — with a shape of log4(5).
    let randomFloat = 1 - Float.random(in: 0..<1)
    let playerMoneyMean = 500.0
    let dividend = playerMoneyMean * log(1.25)
    let divisor = Double(log(5) * pow(randomFloat, log(4) / log(5)))
    var playerMoney = Double(round(dividend / divisor))

    var cardList = drawAndShuffle()
    var player = Player(hand: [], dealer: false)
    var dealer = Player(hand: [], dealer: true)
    var dealerLastCardHidden = true
    var playerTurnIsOver = false
    var gameOver = false
    var newCard: Card
    var bet: Double = 0
    let standLimit = 17

    var amountOfMoney = moneyString(money: playerMoney, currency: currencySelected,
    language: languageSelected)
    // Telling the player how much money they have.
    switch languageSelected {
    case .fre:
    print("Vous commencez le jeu avec \(amountOfMoney).")
    case .eng:
    print("You start the game with \(amountOfMoney).")
    }

    //MARK: Game
    repeat {
    let bet = playerMakesABet(moneyLeft: playerMoney,
    language: languageSelected)
    if bet == 0 {
    switch languageSelected {
    case .eng:
    print("The house always wins! You are ruined!😈")
    case .fre:
    print("La maison sort toujours gagnante! Vous êtes ruiné.😈")
    }
    break
    }
    playerMoney -= bet

    // Dealing cards and calculating first score
    for i in 0...3 {
    newCard = cardList.popLast()!
    if i % 2 == 0 { dealer.hand.append(newCard) }
    else { player.hand.append(newCard) }
    }
    player.calculateScore(player.hand)
    dealer.calculateScore([dealer.hand[0]])
    let blackjack = player.score == 21
    if blackjack {
    playerTurnIsOver = true
    gameOver = true
    switch languageSelected {
    case .eng:
    print("Blackjack! You have won!😊")
    case .fre:
    print("Blackjack! Vous avez gagné!😊")
    }
    let blackjackCoefficient = 2.25
    playerMoney += bet * blackjackCoefficient
    }
    while !playerTurnIsOver {
    announceCardsAndScore(player: player, dealer: dealer,
    playerTurn: true, language: languageSelected)
    let updatedHand = hitOrStand(hand: player.hand, deck: &cardList,
    player: true, language: languageSelected)
    let hit = updatedHand.count != player.hand.count
    player.hand = updatedHand
    player.calculateScore(player.hand)
    playerTurnIsOver = checkIfPlayerTurnIsOver(score: player.score,
    hit: hit)
    }
    if player.score > 21 {
    gameOver = true
    switch languageSelected {
    case .eng:
    print("You have busted. You have lost.☹️")
    case .fre:
    print("Vous avez dépassé 21. Vous avez perdu.☹️")
    }
    }
    while !gameOver {
    dealer.calculateScore(dealer.hand)
    announceCardsAndScore(player: player, dealer: dealer,
    playerTurn: false, language: languageSelected)
    if dealer.score == 21 {
    switch languageSelected {
    case .eng:
    print("The dealer has a blackjack! You have lost.☹️")
    case .fre:
    print("Le croupier a fait un blackjack! Vous avez perdu.☹️")
    }
    }
    dealerDelay(languageSelected)
    while dealer.score < standLimit {
    dealer.hand = hitOrStand(hand: dealer.hand, deck: &cardList,
    player: false)
    dealer.calculateScore(dealer.hand)
    dealerDelay(languageSelected)
    announceCardsAndScore(player: player, dealer: dealer,
    playerTurn: false, language: languageSelected)
    }
    }
    if !gameOver {
    switch languageSelected {
    case .eng:
    switch dealer.score {
    case 0..<player.score:
    print("You have reached a score higher than the dealer's.",
    "You have won! 😊")
    playerMoney += bet * 2
    case player.score:
    print("Draw. You've recovered your bet.😐")
    playerMoney += bet
    case ...21:
    print("The dealer has reached a score higher than yours.",
    "You have lost.☹️")
    default:
    print("The dealer has busted. You have won!😊")
    playerMoney += bet * 2
    }
    case .fre:
    switch dealer.score {
    case 0..<player.score:
    print("Vous avez atteint un score supérieur à celui du ",
    "croupier. Vous avez gagné! 😊")
    playerMoney += bet * 2
    case player.score:
    print("Égalité. Vous récupéreez votre mise.😐")
    playerMoney += bet
    case player.score...21:
    print("Le croupier a atteint un score supérieur au vôtre.",
    "Vous avez perdu.☹️")
    default:
    print("Le croupier a dépassé 21. Vous avez gagné!😊")
    playerMoney += bet * 2
    }
    }
    }
    amountOfMoney = moneyString(money: playerMoney, currency: currencySelected,
    language: languageSelected)
    var replayString: String
    switch languageSelected {
    case .eng:
    print("You have \(amountOfMoney).")
    replayString = "Press enter to keep playing or press any character "
    replayString += "then enter to stop playing."
    case .fre:
    print("Vous avez \(amountOfMoney).")
    replayString = "Appuyez sur entrée pour continuer à jouer ou "
    replayString += "appuyez sur n'importe quel caractère puis sur entrée "
    replayString += "pour arrêter de jouer."
    }
    print(replayString)
    userInput = readLine()!
    gameOver = userInput == ""
    if !gameOver {
    player.hand = [] ; dealer.hand = []
    player.score = 0 ; dealer.score = 0
    playerTurnIsOver = false
    }
    } while !gameOver
    var exitPrompt = ""
    switch languageSelected {
    case .eng:
    if playerMoney != 0 {
    exitPrompt += "You have exited the game with \(amountOfMoney)"
    }
    exitPrompt += "Goodbye!"
    case .fre:
    if playerMoney != 0 {
    exitPrompt += "Vous avez quitté le jeu avec \(amountOfMoney)."
    }
    exitPrompt += "Au revoir!"
    }
    print(exitPrompt)