TicTacToe

master

TicTacToe written in pure Swift, playable in your favourite shell! (ascii graphics)
Sajjon/TicTacToe

TicTacToe

Play

swift run TicTacToe

Which starts the game, presenting this beautiful ascii graphics:

-------------
| 1 | 2 | 3 |
-------------
| 4 | 5 | 6 |
-------------
| 7 | 8 | 9 |
-------------

playerX, which square:

And since this is the most pointless game in the history of games, get used to seeing the draw result.

⚖️ Game ended with a draw
-------------
||||
-------------
||||
-------------
||||
-------------

And in some rare cases where your partner had one to many 🍺 you might see this:

playerX, which square:
7

🎉  playerX won!
-------------
||||
-------------
||||
-------------
|| 8 | 9 |
-------------

Implementation

main.swift

func run() throws {
    var game = TicTacToe(matchUp: .humanVersusHuman)
    
    var result: TicTacToe.Result!
    repeat {
        result = try game.play()
    } while result == nil
    
    print("\n\(result!)")
    game.printBoard()
}

TicTacToe.swift

mutating func play() throws -> Result? {
    defer { activePlayer.toggle() }
    
    let index = repeatedReadSquare(
        prompt: "\(activePlayer), which square:",
        ifBadNumber: "☣️  Bad input",
        ifSquareTaken: "⚠️  Square not free",
        tipOnHowToExitProgram: "💡 You can quit this program by pressing: `CTRL + c`"
    ) { board.is(square: $0, .free) }
        
    try board.play(index: index, by: activePlayer)
    
    if board.isFull() {
        return .draw
    } else {
        return board.winner().map { .win(by: $0) }
    }
}

Board

public extension TicTacToe {
    struct Board: Equatable, CustomStringConvertible {
        internal var fillAtSquare: [Square: Fill] = [:]
    }
}

extension TicTacToe.Board {
    /// Squares 0 through 8
    enum Square: UInt8, ExpressibleByIntegerLiteral, CaseIterable, Hashable {
        case zero, one, two, three, four, five, six, seven, eight
    }
}

Got smarter ASCII print? PR!

public extension TicTacToe.Board {
    func ascii() -> String {
        let rowSeparator: String = .init(repeating: "-", count: 13) + "\n"
        var output = rowSeparator
        for row in Self.rows {
            defer { output += "\n" + rowSeparator }
            output += row.map({ "| \(ascii(square: $0)) "}).joined() + "|"
        }
        return output
    }
}

private extension TicTacToe.Board {
    func ascii(square: Square) -> String {
        self[square].map({ $0.ascii }) ?? square.ascii
    }
}

private extension TicTacToe.Board.Fill {
    var ascii: String { rawValue }
}

private extension TicTacToe.Board.Square {
    var ascii: String { "\(rawValue + 1)" }
}

Got smarter win condition check? PR!

public extension TicTacToe.Board {
    func winner() -> Player? {
        func hasPlayerWon(_ player: Player) -> Bool {
            func check(_ squareMatrix: [[Square]]) -> Bool {
                squareMatrix.reduce(false, { hasWon, squareList in
                    hasWon || squareList.allSatisfy({ hasPlayer(player, filledSquare: $0) })
                })
            }
            
            return Self.winConditions.reduce(false, { hasWon, squareMatrix in
                hasWon || check(squareMatrix)
            })
        }
        
        return Player.allCases.first(where: { hasPlayerWon($0) })
    }
}


extension TicTacToe.Board {
    
    typealias Row       = [Square]
    typealias Column    = [Square]
    typealias Diagonal  = [Square]
    
    // MARK: Rows
    static let firstRow:        Row = [0, 1, 2]
    static let secondRow:       Row = [3, 4, 5]
    static let thirdRow:        Row = [6, 7, 8]
    static let rows:            [Row] = [firstRow, secondRow, thirdRow]
    
    // MARK: Columns
    static let firstColumn:     Column = [0, 3, 6]
    static let secondColumn:    Column = [1, 4, 7]
    static let thirdColumn:     Column = [2, 5, 8]
    static let columns:         [Column] = [firstColumn, secondColumn, thirdColumn]
    
    // MARK: Diagonals
    
    /// Main, Major, Principal, Primary; diagonal: ╲
    /// from top left, to bottom right
    static let mainDiagonal:    Diagonal = [2, 4, 6]
    
    /// Anti-, Minor, Counter, Secondary; diagonal: ╱
    /// from bottom left, to top right
    static let antiDiagonal:    Diagonal = [0, 4, 8]
    static let diagonals:       [Diagonal] = [mainDiagonal, antiDiagonal]
    
    static let winConditions: [[[Square]]] = [rows, columns, diagonals]
}

Description

  • Swift Tools 5.2.0
View More Packages from this Author

Dependencies

Last updated: Wed Jun 28 2023 12:03:33 GMT-0900 (Hawaii-Aleutian Daylight Time)