Last active
August 11, 2016 15:42
-
-
Save marciok/b554e83fa0767dc8ef2d69c3d591c6e3 to your computer and use it in GitHub Desktop.
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
| // | |
| // A Tic-Tac-Toe game written in Swift using a functional programming approach. | |
| // | |
| // | |
| // Created by Marcio Klepacz on 8/10/16. | |
| // | |
| // | |
| import Foundation | |
| struct Board { | |
| /// The empty space of the board. | |
| let emptySpace = " " | |
| /// The board field. | |
| let field: [[String]] | |
| /// The current state of the board. | |
| let currentState: State | |
| /// The winner of the game, since Game doesn't have any state, the board has to know. | |
| let winner: Piece? | |
| /** | |
| Initializes a new board with the provided field and state | |
| - Paramerters: | |
| - field: The board field | |
| - currentState: The current state for the new board | |
| - winner: The winner, if there's any | |
| */ | |
| init(field: [[String]], currentState: State, winner: Piece? = nil) { | |
| self.currentState = currentState | |
| self.field = field | |
| self.winner = winner | |
| } | |
| /** | |
| Initializes a new board with X play turn | |
| */ | |
| init() { | |
| self.currentState = .Xplay | |
| let row = [emptySpace, emptySpace, emptySpace] | |
| self.field = [row, row, row] | |
| self.winner = nil | |
| } | |
| /** | |
| Piece to be placed on the board | |
| - X: X player | |
| - O: O player | |
| */ | |
| enum Piece: String { | |
| case X | |
| case O | |
| } | |
| /** | |
| State to track the game | |
| - Xplay: X turn to play | |
| - Oplay: O turn to play | |
| - Finished: Game is over | |
| - InvalidMove: When there is already a piece on the requested position | |
| */ | |
| enum State { | |
| case Xplay | |
| case Oplay | |
| case Finished | |
| case InvalidMove | |
| } | |
| enum Row: Int { | |
| case Top | |
| case Mid | |
| case Bottom | |
| } | |
| enum Column: Int { | |
| case Left | |
| case Mid | |
| case Right | |
| } | |
| } | |
| struct Game { | |
| private static let winnerSequenceSize = 3 | |
| /** | |
| Make a move on the game | |
| - Parameter: | |
| - board: The board game that you wish to play | |
| - row: the row where your piece will be placed | |
| - column: the column where your piece will be placed | |
| */ | |
| internal static func play(board: Board, row: Board.Row, column: Board.Column) -> Board { | |
| if board.currentState == .Finished { return board } | |
| return checkForEmptySpaces(checkForWinner(togglePlayer(addMove(board, row: row, column: column)))) | |
| } | |
| internal static func botPlay(board: Board) -> Board { | |
| // 1. get the empty spaces | |
| var emptySpaces: [(Int,Int)] = [] | |
| for rowIndex in 0..<board.field.count { | |
| let col = board.field[rowIndex] | |
| for colIndex in 0..<col.count { | |
| if col[colIndex] == board.emptySpace { | |
| // 2. Put inside an array | |
| emptySpaces.append((rowIndex, colIndex)) | |
| } | |
| } | |
| } | |
| // 4. Create a position | |
| let randNumber = Int(arc4random_uniform(UInt32(emptySpaces.count))) | |
| let randPair = emptySpaces[randNumber] | |
| let row = Board.Row(rawValue: randPair.0) | |
| let col = Board.Column(rawValue: randPair.1) | |
| return self.play(board, row: row!, column: col!) | |
| } | |
| private static func checkForEmptySpaces(board: Board) -> Board { | |
| var hasSpace = false | |
| for row in board.field { | |
| hasSpace = row.filter { $0 == board.emptySpace }.count > 0 | |
| } | |
| if !hasSpace { | |
| return changeState(board, newState: .Finished) | |
| } | |
| return board | |
| } | |
| private static func checkForWinner(board: Board) -> Board { | |
| let sequencesInRow = flattenRow(board.field) | |
| if hasHorizontalWinnerSequence(sequencesInRow) { | |
| return winner(board, sequence: sequencesInRow) | |
| } | |
| var winnerSequenceBuffer: [String] = [] | |
| let winnerSequence = winnerTuples(board.field).flatMap { (x,y) -> [String] in | |
| winnerSequenceBuffer += [board.field[x][y]] | |
| if winnerSequenceBuffer.count == winnerSequenceSize { | |
| if hasUniquePiece(winnerSequenceBuffer) { | |
| return winnerSequenceBuffer | |
| } else { | |
| winnerSequenceBuffer = [] | |
| } | |
| } | |
| return [] | |
| } | |
| return winner(board, sequence: winnerSequence) | |
| } | |
| /** | |
| Changes the game state | |
| - Parameter: | |
| - board: The board game that you want change the state | |
| */ | |
| static func changeState(board: Board, newState: Board.State) -> Board { | |
| return Board(field: board.field, currentState: newState) | |
| } | |
| private static func changeField(board: Board, newField: [[String]]) -> Board { | |
| return Board(field: newField, currentState: board.currentState) | |
| } | |
| private static func finishGame(board: Board, winner: Board.Piece) -> Board { | |
| return Board(field: board.field, currentState: .Finished, winner: winner) | |
| } | |
| private static func addMove(board: Board, row: Board.Row, column: Board.Column) -> Board { | |
| var field = board.field | |
| let x = row.rawValue | |
| let y = column.rawValue | |
| if field[x][y] != board.emptySpace { | |
| return changeState(board, newState: .InvalidMove) | |
| } | |
| if board.currentState == .Xplay { | |
| field[x][y] = Board.Piece.X.rawValue | |
| } else if board.currentState == .Oplay { | |
| field[x][y] = Board.Piece.O.rawValue | |
| } | |
| return changeField(board, newField: field) | |
| } | |
| private static func togglePlayer(board: Board) -> Board { | |
| if board.currentState == .Oplay { | |
| return changeState(board, newState: .Xplay) | |
| } else if board.currentState == .Xplay { | |
| return changeState(board, newState: .Oplay) | |
| } | |
| return board | |
| } | |
| private static func winner(board: Board, sequence: [String]) -> Board { | |
| guard let firstElement = sequence.first else { return board } | |
| return finishGame(board, winner: Board.Piece(rawValue: firstElement)!) | |
| } | |
| // MARK: Helpers | |
| private static func flattenRow(field: [[String]]) -> [String] { | |
| return field.filter { return hasUniquePiece($0) }.flatMap { $0 } | |
| } | |
| private static func hasHorizontalWinnerSequence(sequence: [String]) -> Bool { | |
| return sequence.count == winnerSequenceSize | |
| } | |
| private static func winnerTuples(filed: [[String]]) -> [(Int, Int)] { | |
| var diagonalTuples: [(Int, Int)] = Array() | |
| var verticalTuples: [(Int, Int)] = Array() | |
| for rowIndex in 0..<board.field.count { | |
| diagonalTuples += [(rowIndex, rowIndex)] | |
| for columnIndex in 0..<board.field.first!.count { | |
| verticalTuples += [(columnIndex, rowIndex)] | |
| } | |
| } | |
| return verticalTuples + diagonalTuples + diagonalTuples.reverse() | |
| } | |
| private static func hasUniquePiece(row: [String]) -> Bool { | |
| let uniqueElements = Set(row) | |
| return uniqueElements.count == 1 && uniqueElements.first != board.emptySpace | |
| } | |
| } | |
| /** | |
| Print a board and its current status | |
| - Parameter board: board to be printed | |
| */ | |
| func print(board: Board) { | |
| if let winner = board.winner { | |
| print("The winner is: \(winner) 🎉") | |
| } | |
| for row in board.field { | |
| print(row) | |
| } | |
| print("---------------") | |
| } | |
| // MARK: UI | |
| var board = Board() | |
| var previousState: Board.State = board.currentState | |
| repeat { | |
| switch board.currentState { | |
| case .InvalidMove: | |
| print("⚠️ Ops... Invalid move, try another a different position") | |
| board = Game.changeState(board, newState: previousState) | |
| continue | |
| case .Xplay: | |
| print("\(Board.Piece.X.rawValue) turn") | |
| case .Oplay: | |
| board = Game.botPlay(board) | |
| print("Behold 🤖 playing") | |
| print(board) | |
| continue | |
| default: | |
| break | |
| } | |
| print("Enter row") | |
| guard let rowInput = readLine(stripNewline: true) else { continue } | |
| guard let rowNumber = Int(rowInput) else { continue } | |
| guard let row = Board.Row(rawValue: rowNumber) else { continue } | |
| print("Enter column") | |
| guard let columnInput = readLine(stripNewline: true) else { continue } | |
| guard let columnNumber = Int(columnInput) else { continue } | |
| guard let column = Board.Column(rawValue: columnNumber) else { continue } | |
| previousState = board.currentState | |
| board = Game.play(board, row: row, column: column) | |
| print(board) | |
| } while board.currentState != .Finished |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment