Created
July 14, 2020 18:49
-
-
Save jchros/c842dd6e28c9fa4aac49e196bf99f873 to your computer and use it in GitHub Desktop.
The first Swift (4) program I've ever written (August 2018).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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