Skip to content

Instantly share code, notes, and snippets.

@jchros
Created July 14, 2020 18:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jchros/c842dd6e28c9fa4aac49e196bf99f873 to your computer and use it in GitHub Desktop.
Save jchros/c842dd6e28c9fa4aac49e196bf99f873 to your computer and use it in GitHub Desktop.
The first Swift (4) program I've ever written (August 2018).
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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment