import Darwin enum Token : CustomStringConvertible { case cross, circle func matches(tokens: [Token?]) -> Bool { for token in tokens { guard let t = token, t == self else { return false } } return true } func emptyCell(in tokens: [Token?]) -> Int? { if tokens[0] == nil && tokens[1] == self && tokens[2] == self { return 0 } else if tokens[0] == self && tokens[1] == nil && tokens[2] == self { return 1 } else if tokens[0] == self && tokens[1] == self && tokens[2] == nil { return 2 } return nil } var description: String { switch self { case .cross: return "x" case .circle: return "o" } } } struct Board { var cells: [Token?] = [nil, nil, nil, nil, nil, nil, nil, nil, nil] func cells(atCol col: Int) -> [Token?] { return [cells[col], cells[col + 3], cells[col + 6]] } func cells(atRow row: Int) -> [Token?] { return [cells[row * 3], cells[row * 3 + 1], cells[row * 3 + 2]] } func cellsTopLeft() -> [Token?] { return [cells[0], cells[4], cells[8]] } func cellsBottomLeft() -> [Token?] { return [cells[6], cells[4], cells[2]] } func winner() -> Token? { let r0 = cells(atRow: 0) let r1 = cells(atRow: 1) let r2 = cells(atRow: 2) let c0 = cells(atCol: 0) let c1 = cells(atCol: 1) let c2 = cells(atCol: 2) let tl = cellsTopLeft() let bl = cellsBottomLeft() if Token.cross.matches(tokens: r0) || Token.cross.matches(tokens: r1) || Token.cross.matches(tokens: r2) || Token.cross.matches(tokens: c0) || Token.cross.matches(tokens: c1) || Token.cross.matches(tokens: c2) || Token.cross.matches(tokens: tl) || Token.cross.matches(tokens: bl) { return .cross } else if Token.circle.matches(tokens: r0) || Token.circle.matches(tokens: r1) || Token.circle.matches(tokens: r2) || Token.circle.matches(tokens: c0) || Token.circle.matches(tokens: c1) || Token.circle.matches(tokens: c2) || Token.circle.matches(tokens: tl) || Token.circle.matches(tokens: bl) { return .circle } return nil } func atCapacity() -> Bool { return cells.filter { $0 == nil }.count == 0 } mutating func play(token: Token, at location: Int) { cells[location] = token } func findBestLocation(for player: Token) -> Int? { let r0 = cells(atRow: 0) let r1 = cells(atRow: 1) let r2 = cells(atRow: 2) let c0 = cells(atCol: 0) let c1 = cells(atCol: 1) let c2 = cells(atCol: 2) let tl = cellsTopLeft() let bl = cellsBottomLeft() if let cell = player.emptyCell(in: r0) { return cell } else if let cell = player.emptyCell(in: r1) { return cell + 3 } else if let cell = player.emptyCell(in: r2) { return cell + 6 } else if let cell = player.emptyCell(in: c0) { return cell * 3 } else if let cell = player.emptyCell(in: c1) { return cell * 3 + 1 } else if let cell = player.emptyCell(in: c2) { return cell * 3 + 2 } else if let cell = player.emptyCell(in: tl) { return cell == 0 ? 0 : (cell == 1 ? 4 : 8) } else if let cell = player.emptyCell(in: bl) { return cell == 0 ? 6 : (cell == 1 ? 4 : 2) } return nil } func findMove() -> Int { let empties = cells.enumerated().filter { $0.1 == nil } let r = Int(arc4random()) % empties.count return empties[r].0 } } extension Board : CustomStringConvertible { var description: String { var result = "\n---------------\n" for (idx, cell) in cells.enumerated() { if let cell = cell { result += "| \(cell) |" } else { result += "| \(idx) |" } if (idx + 1) % 3 == 0 { result += "\n---------------\n" } } return result } } while true { var board = Board() print("Who do you want to play as ('o' or 'x'): ", separator: "", terminator: "") let answer = readLine()?.characters.first ?? "x" var player: Token = answer == "x" ? .cross : .circle var pc: Token = player == .cross ? .circle : .cross print(board) while true { print("Choose cell to play on: ", separator: "", terminator: "") var pos = Int(readLine() ?? "0") ?? 0 while !board.atCapacity() && board.cells[pos] != nil { print("Invalid move. Choose cell to play on: ", separator: "", terminator: "") pos = Int(readLine() ?? "0") ?? 0 } if board.atCapacity() { print("Draw") break } board.play(token: player, at: pos) print(board) if let winner = board.winner() { print("winner is \(winner)") break } else if board.atCapacity() { print("Draw") break } if let win = board.findBestLocation(for: pc) { board.play(token: pc, at: win) } else if let def = board.findBestLocation(for: player) { board.play(token: pc, at: def) } else { board.play(token: pc, at: board.findMove()) } print(board) if let winner = board.winner() { print("winner is \(winner)") break } } }