I've starting writing the card game Uno the past couple of days and was wondering if you can review my code please. The initialization is done I think, a couple of game functions already exist, please have look.
Please give me feedback concerning, structure, logic, data structures, naming conventions, anything you're noticing!
I want to make sure everything is set until I move on to the View and Controller.
import UIKit
import Foundation
// Represents all jokers of the game.
enum Joker: String, CaseIterable {
// Only four copies of each will be created
case joker, drawFour, swapCircle
func points() -> Int {
switch self {
case .joker, .drawFour, .swapCircle: return 50
} // returns points of each card
}
}
// Represents all "normal" cards.
enum Value: String, CaseIterable {
// A cartesian product with all colors will be built.
case one, two, three, four, five, six, seven, eight, nine, drawTwo, skip, turn
// returns points of each card
func points() -> Int {
switch self {
// THIS DOES NOT LOOK VERY NICE. IS THERE A SMOOTHER WAY?
case .one: return 1
case .two: return 2
case .three: return 3
case .four: return 4
case .five: return 5
case .six: return 6
case .seven: return 7
case .eight: return 8
case .nine: return 9
case .drawTwo, .skip, .turn: return 20
}
}
}
// Represents all colors.
enum Color: String, CaseIterable {
case red, blue, yellow, green
}
// Represents the structure of a card.
struct Card {
let value: Value? // is nil when card is a joker
let color: Color? // is nil when card is a joker
let joker: Joker? // is nil when card has a value and color
}
// Represents the two stacks of the game. Game stack as well as draw stack will be instances of stack, depending on which constructor is called the correct instance will be created.
class Stack {
public var stack: [Card]
// Creates the draw when the game is first initalized.
private static func createDrawStack(with amountOfCards: Int) -> [Card] {
var cards = [Card]()
// add 2 cards of each value
for color in Color.allCases {
for value in Value.allCases {
for _ in 0..<amountOfCards { // add two copies
cards += [Card(value: value, color: color, joker: nil)]
}
}
}
for _ in 0..<(amountOfCards * 2) {
// add 1 swap-circle
cards += [Card(value: nil, color: nil, joker: .swapCircle)]
// add 1 draw-four
cards += [Card(value: nil, color: nil, joker: .drawFour)]
// add 1 joker
cards += [Card(value: nil, color: nil, joker: .joker)]
}
return cards.shuffled() // TODO: In-place shuffling wouldn't work, maybe not shuffling in here is smoother
}
// Creates the game stack when the game is first initalized.
private static func createGameStack(with drawStack: Stack) -> [Card] {
return [drawStack.stack.popLast()!]
}
init(createDrawStackWith amount: Int) {
self.stack = Stack.createDrawStack(with: amount)
}
init(createGameStackWith drawStack: Stack) {
self.stack = Stack.createGameStack(with: drawStack)
}
}
// Represents a player and the actions a player can do.
class Player {
let id: Int
var hand = [Card?]()
var didDraw = false
// Receives index of button pressed and returns the card selected, so that cardsMatch() can check if it's a valid turn. If so, playCard() will be called.
public func wantsToPlayCard(at index: Int) -> Card {
return hand[index]!
}
// Moves card requested from user hand to playStack
public func playCard(at index: Int, onto playStack: Stack) {
if let card = self.hand.remove(at: index) {
playStack.stack.append(card)
}
}
// Moves a card from drawStack to user hand
public func drawCard(from deck: Stack) {
self.hand.append(deck.stack.popLast()!)
}
init(withID id: Int) {
self.id = id
}
}
// Represents the whole game. This is the core of the game.
class Uno {
public var drawStack: Stack // creates a stack to draw from with 2 copies of each color
public var playStack: Stack // creates a stack where the game will be played
public var players: [Player] // will be initialized dependent of amount
// Moves play stack back into draw stack.
public func resetDrawStack() {
let topCard = playStack.stack.popLast()! // save top card
drawStack.stack = playStack.stack // move rest of playStack back into drawStack
drawStack.stack.shuffle()
playStack.stack.removeAll()
playStack.stack += [topCard]
}
// Checks if a requested card can be played or not.
public func cardsMatch(compare topCard: Card, with handCard: Card) -> Bool {
// if colors match
if let colorTopCard = topCard.color, let colorHandCard = handCard.color {
if colorTopCard == colorHandCard {
return true
}
}
// if values match
if let valueTopCard = topCard.value, let valueHandCard = handCard.value {
if valueTopCard == valueHandCard {
return true
}
}
// if its a joker
if let _ = handCard.joker {
return true
}
// else
return false
}
// Deals cards to players at the start of round.
private func dealCards(with amountOfCards: Int) {
for id in players.indices {
for _ in 0..<amountOfCards {
players[id].drawCard(from: drawStack)
}
}
}
// Creates the players.
private static func createPlayers(with amountOfPlayers: Int) -> [Player] {
var players = [Player]()
for id in 0..<amountOfPlayers {
players += [Player(withID: id)]
}
return players
}
// Constructs game dependent on settings.
init(amountOfPlayers: Int, amountOfCopies: Int, amountOfCards: Int) {
self.drawStack = Stack(createDrawStackWith: amountOfCopies)
print(self.drawStack.stack)
self.playStack = Stack(createGameStackWith: self.drawStack)
self.players = Uno.createPlayers(with: amountOfPlayers) // creates 2 players
self.dealCards(with: amountOfCards)
}
}
var game = Uno(amountOfPlayers: 2, amountOfCopies: 2, amountOfCards: 7)