diff --git a/Minimax/README.md b/Minimax/README.md
new file mode 100644
index 000000000..a4aae2c4b
--- /dev/null
+++ b/Minimax/README.md
@@ -0,0 +1,70 @@
+# Minimax algorithm
+
+
+
+## Runtime environment
+
+
+## Table of contents
+* [General info](#general-info)
+* [Functionality](#functionality)
+* [Pseudocode](#pseudocode)
+* [Demo](#demo)
+* [Sources](#sources)
+
+## General info
+It is an example of implementation and use ``minimax algorithm`` in ``Tic Tac Toe`` game. Minimax is an algorithm that searches deeply into all possible states in the game. There are two types of players in the algorithm. One that wants to maximize the state of the game and one that wants to minimaze the state of the game. There are three states in the tic-tac-toe game:
+- `` -1 `` - if the minimizing player wins
+- `` 0 `` - in case of a tie
+- `` 1 `` - if the maximizing player wins
+
+``Alpha-beta prunning`` this is a method that interrupts the search of those branches that do not lead to win. Alpha for maximizing player and beta for minimizing player. Alpha-beta prunnings reduce the time complexity of the algorithm.
+
+Parameters:
+- ``searching depth`` - how many moves in depth is to be calculated by the algorithm
+
+Input:
+- ``actual board state`` - in the form of an array for players symbols (cross/circle)
+- ``two player symbols`` - cross / circle
+
+Output:
+- ``the best move for autonomus(AI) player`` - Position(row: Int, column: Int)
+
+## Functionality
+- example of use in Swift Playground with interactive UIView
+- unit tests in XCode
+
+## Pseudocode
+
+```
+function alphabeta(node, depth, α, β, maximizingPlayer) is
+ if depth = 0 or node is a terminal node then
+ return the heuristic value of node
+ if maximizingPlayer then
+ value := −∞
+ for each child of node do
+ value := max(value, alphabeta(child, depth − 1, α, β, FALSE))
+ if value ≥ β then
+ break (* β cutoff *)
+ α := max(α, value)
+ return value
+ else
+ value := +∞
+ for each child of node do
+ value := min(value, alphabeta(child, depth − 1, α, β, TRUE))
+ if value ≤ α then
+ break (* α cutoff *)
+ β := min(β, value)
+ return value
+```
+
+## Demo
+
+
+
+## Sources
+* Minimax algorithm: https://en.wikipedia.org/wiki/Minimax
+* Alpha-beta prunning: https://en.wikipedia.org/wiki/Alpha–beta_pruning
+
+## Author
+Written by Michał Nowak(mnowak061)
diff --git a/Minimax/Resources/demo.gif b/Minimax/Resources/demo.gif
new file mode 100644
index 000000000..4c1906660
Binary files /dev/null and b/Minimax/Resources/demo.gif differ
diff --git a/Minimax/Resources/image1.jpg b/Minimax/Resources/image1.jpg
new file mode 100644
index 000000000..6e9388bef
Binary files /dev/null and b/Minimax/Resources/image1.jpg differ
diff --git a/Minimax/Sources/Minimax.playground/Contents.swift b/Minimax/Sources/Minimax.playground/Contents.swift
new file mode 100644
index 000000000..e6508923c
--- /dev/null
+++ b/Minimax/Sources/Minimax.playground/Contents.swift
@@ -0,0 +1,8 @@
+import UIKit
+import PlaygroundSupport
+
+let boardSize = CGSize(width: 500, height: 550)
+let boardView = BoardView(frame: CGRect(origin: .zero, size: boardSize))
+
+PlaygroundPage.current.needsIndefiniteExecution = true
+PlaygroundPage.current.liveView = boardView
diff --git a/Minimax/Sources/Minimax.playground/Sources/Model/Board/Board.swift b/Minimax/Sources/Minimax.playground/Sources/Model/Board/Board.swift
new file mode 100644
index 000000000..3b637df85
--- /dev/null
+++ b/Minimax/Sources/Minimax.playground/Sources/Model/Board/Board.swift
@@ -0,0 +1,154 @@
+public struct Board {
+ // MARK: -- Public variable's
+ public var size: Int
+
+ // MARK: -- Private variable's
+ private var table: [ [PlayerSymbol?] ]
+
+ // MARK: -- Public function's
+ public init(size: Int) {
+ self.size = size
+ self.table = []
+ self.clear()
+ }
+
+ public mutating func clear() {
+ self.table = Array(repeating: Array(repeating: PlayerSymbol.empty, count: size), count: size)
+ }
+
+ public func hasEmptyField() -> Bool {
+ for i in 0 ..< self.size {
+ for j in 0 ..< self.size {
+ if self.table[i][j] == PlayerSymbol.empty {
+ return true
+ }
+ }
+ }
+ return false
+ }
+
+ public func symbol(forPosition position: Position) -> PlayerSymbol? {
+ guard position.row < self.size, position.column < size else { return nil }
+ return self.table[position.row][position.column]
+ }
+
+ public mutating func makeMove(player: Player, position: Position) {
+ guard self.symbol(forPosition: position) == PlayerSymbol.empty else { return }
+ guard self.symbol(forPosition: position) != player.symbol else { return }
+
+ self.table[position.row][position.column] = player.symbol
+ }
+
+ public func check(player: Player) -> BoardStatus {
+ let playerSymbol: PlayerSymbol = player.symbol
+
+ if self.foundWinInRows(playerSymbol) { return BoardStatus.win }
+ if self.foundWinInColumns(playerSymbol) { return BoardStatus.win }
+ if self.foundWinInSlants(playerSymbol) { return BoardStatus.win }
+
+ if self.hasEmptyField() { return BoardStatus.continues } else { return BoardStatus.draw }
+ }
+
+ // MARK: -- Private function's
+ private func foundWinInRows(_ playerSymbol: PlayerSymbol) -> Bool {
+ for i in 0 ..< self.size {
+ var theSameSymbolsInRowCount = 0
+
+ for j in 0 ..< self.size - 1 {
+ if self.table[i][j] == self.table[i][j+1] && (self.table[i][j] == playerSymbol) {
+ theSameSymbolsInRowCount += 1
+ } else {
+ theSameSymbolsInRowCount = 0
+ }
+ }
+
+ if theSameSymbolsInRowCount == self.size - 1 {
+ return true
+ }
+ }
+
+ return false
+ }
+
+ private func foundWinInColumns(_ playerSymbol: PlayerSymbol) -> Bool {
+ for j in 0 ..< self.size {
+ var theSameSymbolsInColumnCount = 0
+
+ for i in 0 ..< self.size - 1 {
+ if self.table[i][j] == self.table[i+1][j] && (self.table[i][j] == playerSymbol) {
+ theSameSymbolsInColumnCount += 1
+ } else {
+ theSameSymbolsInColumnCount = 0
+ }
+ }
+
+ if theSameSymbolsInColumnCount == self.size - 1 {
+ return true
+ }
+ }
+
+ return false
+ }
+
+ private func foundWinInSlants(_ playerSymbol: PlayerSymbol) -> Bool {
+ var theSameSymbolsInSlantCount = 0
+
+ for i in 0 ..< self.size {
+ for j in -(self.size - 1) ... 0 {
+ if(self.table[-j][i] == playerSymbol) {
+ var k: Int = -j
+ var l: Int = i
+ theSameSymbolsInSlantCount = 0
+
+ while l < self.size && k >= 0 {
+ if self.table[k][l] == playerSymbol {
+ theSameSymbolsInSlantCount += 1
+ } else {
+ theSameSymbolsInSlantCount = 0
+ }
+ k -= 1
+ l += 1
+
+ if theSameSymbolsInSlantCount == self.size {
+ return true
+ }
+ }
+ theSameSymbolsInSlantCount = 0
+ }
+ theSameSymbolsInSlantCount = 0
+ }
+ theSameSymbolsInSlantCount = 0
+ }
+
+ theSameSymbolsInSlantCount = 0
+
+ for i in 0 ..< self.size {
+ for j in 0 ..< self.size {
+ if(self.table[j][i] == playerSymbol) {
+ var k: Int = j
+ var l: Int = i
+ theSameSymbolsInSlantCount = 0
+
+ while l < self.size && k < self.size {
+ if self.table[k][l] == playerSymbol {
+ theSameSymbolsInSlantCount += 1
+ } else {
+ theSameSymbolsInSlantCount = 0
+ }
+ k += 1
+ l += 1
+
+ if theSameSymbolsInSlantCount == self.size {
+ return true
+ }
+ }
+ theSameSymbolsInSlantCount = 0
+ }
+ theSameSymbolsInSlantCount = 0
+ }
+ theSameSymbolsInSlantCount = 0
+ }
+
+ return false
+ }
+}
diff --git a/Minimax/Sources/Minimax.playground/Sources/Model/Board/BoardPosition.swift b/Minimax/Sources/Minimax.playground/Sources/Model/Board/BoardPosition.swift
new file mode 100644
index 000000000..4599da3af
--- /dev/null
+++ b/Minimax/Sources/Minimax.playground/Sources/Model/Board/BoardPosition.swift
@@ -0,0 +1 @@
+public typealias Position = (row: Int, column: Int)
diff --git a/Minimax/Sources/Minimax.playground/Sources/Model/Board/BoardStatus.swift b/Minimax/Sources/Minimax.playground/Sources/Model/Board/BoardStatus.swift
new file mode 100644
index 000000000..d09fc7840
--- /dev/null
+++ b/Minimax/Sources/Minimax.playground/Sources/Model/Board/BoardStatus.swift
@@ -0,0 +1,8 @@
+public enum BoardStatus {
+
+ case continues
+
+ case win
+
+ case draw
+}
diff --git a/Minimax/Sources/Minimax.playground/Sources/Model/GameModel/DifficultLevel.swift b/Minimax/Sources/Minimax.playground/Sources/Model/GameModel/DifficultLevel.swift
new file mode 100644
index 000000000..e246d7f61
--- /dev/null
+++ b/Minimax/Sources/Minimax.playground/Sources/Model/GameModel/DifficultLevel.swift
@@ -0,0 +1,8 @@
+public enum DifficultLevel: Int {
+
+ case easy = 2
+
+ case medium = 3
+
+ case hard = 5
+}
diff --git a/Minimax/Sources/Minimax.playground/Sources/Model/GameModel/GameModel.swift b/Minimax/Sources/Minimax.playground/Sources/Model/GameModel/GameModel.swift
new file mode 100644
index 000000000..bacd3c899
--- /dev/null
+++ b/Minimax/Sources/Minimax.playground/Sources/Model/GameModel/GameModel.swift
@@ -0,0 +1,95 @@
+import Foundation
+
+public class GameModel {
+ // MARK: -- Public variable's
+ public var board: Board!
+
+ public var gameStatus: BoardStatus
+
+ // MARK: -- Private variable's
+ private var playersList: [Player]!
+
+ private var movementsSequence: [Int]!
+
+ private var actualPlayerIndex: Int!
+
+ private var actualPlayer: Player {
+ get {
+ return playersList[actualPlayerIndex]
+ }
+ }
+
+ private var difficultLevel: DifficultLevel = DifficultLevel.hard
+
+ // MARK: -- Public function's
+ public init(boardSize: Int, playersList: [Player], difficultLevel: DifficultLevel) {
+ self.board = Board.init(size: boardSize)
+ self.playersList = playersList
+ self.difficultLevel = difficultLevel
+ self.gameStatus = BoardStatus.continues
+
+ self.generateMovementsSequence()
+ self.changeActualPlayer()
+ }
+
+ public func update() {
+ self.gameStatus = board.check(player: actualPlayer)
+
+ switch self.gameStatus {
+ case BoardStatus.continues:
+ changeActualPlayer()
+ case BoardStatus.draw:
+ changeActualPlayer()
+ default: break
+ }
+ }
+
+ public func playerMakeMove(selectedPosition: (row: Int, column: Int)) {
+ guard board.symbol(forPosition: selectedPosition) == PlayerSymbol.empty else { return }
+ guard board.hasEmptyField() == true else { return }
+
+ board.makeMove(player: actualPlayer, position: selectedPosition)
+ update()
+ }
+
+ public func makeMinimaxMove() {
+ guard actualPlayer.type == PlayerType.computer else { return }
+ guard board.hasEmptyField() == true else { return }
+
+ sleep(1)
+
+ let selectedPosition: Position = minimaxMove(board: board, player: playersList[0], opponent: playersList[1], depth: self.difficultLevel.rawValue)
+ board.makeMove(player: actualPlayer, position: selectedPosition)
+ update()
+ }
+
+ public func newRound() {
+ board.clear()
+ gameStatus = BoardStatus.continues
+ generateMovementsSequence()
+ changeActualPlayer()
+ }
+
+ // MARK: -- Private function's
+ private func generateMovementsSequence() {
+ self.movementsSequence = []
+
+ let playersCount = playersList.count
+ let movesCount = (board.size * board.size)
+
+ var move = Int.random(in: 0 ..< playersCount)
+ movementsSequence.append(move)
+
+ for _ in 0 ..< movesCount - 1 {
+ move += 1
+ movementsSequence.append(move % playersCount)
+ }
+ }
+
+ private func changeActualPlayer() {
+ if !movementsSequence.isEmpty {
+ actualPlayerIndex = movementsSequence.first!
+ movementsSequence.removeFirst()
+ }
+ }
+}
diff --git a/Minimax/Sources/Minimax.playground/Sources/Model/Minimax/GameStateValue.swift b/Minimax/Sources/Minimax.playground/Sources/Model/Minimax/GameStateValue.swift
new file mode 100644
index 000000000..6924d0c1c
--- /dev/null
+++ b/Minimax/Sources/Minimax.playground/Sources/Model/Minimax/GameStateValue.swift
@@ -0,0 +1,8 @@
+public enum GameStateValue: Int {
+
+ case min = -1
+
+ case null = 0
+
+ case max = 1
+}
diff --git a/Minimax/Sources/Minimax.playground/Sources/Model/Minimax/Minimax.swift b/Minimax/Sources/Minimax.playground/Sources/Model/Minimax/Minimax.swift
new file mode 100644
index 000000000..6a597e774
--- /dev/null
+++ b/Minimax/Sources/Minimax.playground/Sources/Model/Minimax/Minimax.swift
@@ -0,0 +1,98 @@
+public func minimaxMove(board: Board, player: Player, opponent: Player, depth: Int) -> Position {
+ var bestVal = GameStateValue.min
+ var bestMoves: [Position] = []
+
+ for i in 0 ..< board.size {
+ for j in 0 ..< board.size {
+ if board.symbol(forPosition: Position(i, j)) == PlayerSymbol.empty {
+ var tempBoard = board
+ let move = Position(i, j)
+
+ tempBoard.makeMove(player: opponent, position: (i, j))
+ let moveVal = minMax(board: tempBoard, player: opponent, opponent: player,
+ depth: depth,
+ alpha: GameStateValue.min.rawValue, beta: GameStateValue.max.rawValue,
+ maximizingPlayer: false)
+
+ if moveVal.rawValue > bestVal.rawValue {
+ bestVal = moveVal
+ bestMoves = []
+ bestMoves.append(move)
+ } else if moveVal == bestVal {
+ bestMoves.append(move)
+ }
+ }
+ }
+ }
+
+ return bestMoves[Int.random(in: 0 ..< bestMoves.count)]
+}
+
+public func minMax(board: Board, player: Player, opponent: Player, depth: Int, alpha: Int, beta: Int, maximizingPlayer: Bool) -> GameStateValue {
+ var alpha = alpha
+ var beta = beta
+
+ if let gameResult = evaluateGameState(board: board, player: player, opponent: opponent) {
+ guard depth != 0 && gameResult != GameStateValue.min && gameResult != GameStateValue.max && gameResult != GameStateValue.null else {
+ return gameResult
+ }
+ }
+
+ if maximizingPlayer {
+ var maxEval = GameStateValue.min
+
+ for i in 0 ..< board.size {
+ for j in 0 ..< board.size {
+ if board.symbol(forPosition: Position(i, j)) == PlayerSymbol.empty {
+ var tempBoard = board
+ tempBoard.makeMove(player: player, position: Position(i, j))
+
+ let eval = minMax(board: tempBoard, player: player, opponent: opponent, depth: depth - 1,
+ alpha: alpha, beta: beta,
+ maximizingPlayer: !maximizingPlayer)
+
+ maxEval = GameStateValue(rawValue: max(maxEval.rawValue, eval.rawValue))!
+ alpha = max(alpha, eval.rawValue)
+
+ if beta <= alpha { break }
+ }
+ }
+ }
+
+ return maxEval
+ } else {
+ var minEval = GameStateValue.max
+
+ for i in 0 ..< board.size {
+ for j in 0 ..< board.size {
+ if board.symbol(forPosition: Position(i, j)) == PlayerSymbol.empty {
+ var tempBoard = board
+ tempBoard.makeMove(player: opponent, position: (i, j))
+
+ let eval = minMax(board: tempBoard, player: player, opponent: opponent, depth: depth - 1,
+ alpha: alpha, beta: beta,
+ maximizingPlayer: !maximizingPlayer)
+
+ minEval = GameStateValue(rawValue: min(minEval.rawValue, eval.rawValue))!
+ beta = min(beta, eval.rawValue)
+
+ if beta <= alpha { break }
+ }
+ }
+ }
+
+ return minEval
+ }
+}
+
+public func evaluateGameState(board: Board, player: Player, opponent: Player) -> GameStateValue? {
+ if board.check(player: player) == BoardStatus.win {
+ return GameStateValue.max
+ } else if board.check(player: opponent) == BoardStatus.win {
+ return GameStateValue.min
+ } else if board.check(player: player) == BoardStatus.draw || board.check(player: opponent) == BoardStatus.draw {
+ return GameStateValue.null
+ }
+
+ return nil
+}
diff --git a/Minimax/Sources/Minimax.playground/Sources/Model/Player/Player.swift b/Minimax/Sources/Minimax.playground/Sources/Model/Player/Player.swift
new file mode 100644
index 000000000..4236b7b0b
--- /dev/null
+++ b/Minimax/Sources/Minimax.playground/Sources/Model/Player/Player.swift
@@ -0,0 +1,12 @@
+public struct Player {
+ // MARK: -- Public variable's
+ public var type: PlayerType
+
+ public var symbol: PlayerSymbol
+
+ // MARK: -- Public function's
+ public init(type: PlayerType, symbol: PlayerSymbol) {
+ self.type = type
+ self.symbol = symbol
+ }
+}
diff --git a/Minimax/Sources/Minimax.playground/Sources/Model/Player/PlayerSymbol.swift b/Minimax/Sources/Minimax.playground/Sources/Model/Player/PlayerSymbol.swift
new file mode 100644
index 000000000..3f858db15
--- /dev/null
+++ b/Minimax/Sources/Minimax.playground/Sources/Model/Player/PlayerSymbol.swift
@@ -0,0 +1,8 @@
+public enum PlayerSymbol: String {
+
+ case empty = ""
+
+ case circle = "⭕️"
+
+ case cross = "❌"
+}
diff --git a/Minimax/Sources/Minimax.playground/Sources/Model/Player/PlayerType.swift b/Minimax/Sources/Minimax.playground/Sources/Model/Player/PlayerType.swift
new file mode 100644
index 000000000..01a65094d
--- /dev/null
+++ b/Minimax/Sources/Minimax.playground/Sources/Model/Player/PlayerType.swift
@@ -0,0 +1,6 @@
+public enum PlayerType {
+
+ case computer
+
+ case human
+}
diff --git a/Minimax/Sources/Minimax.playground/Sources/View/BoardView.swift b/Minimax/Sources/Minimax.playground/Sources/View/BoardView.swift
new file mode 100644
index 000000000..68e86409f
--- /dev/null
+++ b/Minimax/Sources/Minimax.playground/Sources/View/BoardView.swift
@@ -0,0 +1,238 @@
+import UIKit
+
+public class BoardView: UIView {
+ // MARK: -- Public
+ public var gameModel: GameModel!
+
+ public var players = [Player(type: .human, symbol: .circle),
+ Player(type: .computer, symbol: .cross)]
+
+ // MARK: -- Override's
+ public override init(frame: CGRect) {
+ super.init(frame: frame)
+
+ self.setupBoard()
+ self.setupResetButton()
+ self.setupIndicator()
+ self.startGame()
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ }
+
+ // MARK: -- Private
+ private var buttons: [UIButton] = []
+
+ private var stackView: UIStackView!
+
+ private var resetButton: UIButton!
+
+ private var indicator: UIActivityIndicatorView!
+
+ private func startGame() {
+ self.gameModel = GameModel.init(boardSize: 3, playersList: self.players, difficultLevel: .hard)
+
+ DispatchQueue.global(qos: .userInteractive).async {
+
+ self.blockViewForUser()
+
+ self.gameModel.makeMinimaxMove()
+
+ self.unblockViewForUser()
+ }
+ }
+
+ private func updateUI() {
+ if gameModel.gameStatus != BoardStatus.continues {
+ self.resetButton.setTitle("New game", for: .normal)
+ blockButtons()
+ } else {
+ self.resetButton.setTitle("Reset", for: .normal)
+ }
+ boardToButtons()
+ }
+
+ private func boardToButtons() {
+ var buttonIndex = 0
+
+ for row in 0 ..< 3 {
+ for column in 0 ..< 3 {
+ let symbol = gameModel.board.symbol(forPosition: Position(row, column))
+ if symbol != PlayerSymbol.empty {
+ self.buttons[buttonIndex].setTitle(symbol?.rawValue, for: .normal)
+ self.buttons[buttonIndex].isUserInteractionEnabled = false
+ }
+ buttonIndex += 1
+ }
+ }
+ }
+
+ private func setupBoard() {
+ self.stackView = UIStackView()
+ self.stackView.translatesAutoresizingMaskIntoConstraints = false
+ self.stackView.axis = .vertical
+ self.stackView.alignment = .fill
+ self.stackView.distribution = .fillEqually
+ self.stackView.spacing = 10
+
+ self.addSubview(self.stackView)
+
+ for index in 1 ... 3 {
+ let boardRow = self.createBoardRow(rowNumber: index)
+ self.stackView.addArrangedSubview(boardRow)
+ }
+
+ // constraints
+ let constraints = [
+ self.stackView.topAnchor.constraint(equalTo: self.topAnchor, constant: 10),
+ self.stackView.centerXAnchor.constraint(equalTo: self.centerXAnchor),
+ self.stackView.widthAnchor.constraint(equalTo: self.widthAnchor, constant: -20),
+ self.stackView.heightAnchor.constraint(equalTo: self.stackView.widthAnchor)
+ ]
+ NSLayoutConstraint.activate(constraints)
+ }
+
+ private func createBoardRow(rowNumber: Int) -> UIStackView {
+ let stackView = UIStackView()
+ stackView.translatesAutoresizingMaskIntoConstraints = false
+ stackView.axis = .horizontal
+ stackView.alignment = .fill
+ stackView.distribution = .fillEqually
+ stackView.spacing = 10
+
+ for index in 1 ... 3 {
+ let button = UIButton()
+ let id = String(index + ( (rowNumber - 1) * 3 ) )
+ button.restorationIdentifier = id
+ button.backgroundColor = .lightGray
+ button.titleLabel?.font = UIFont(name: "Helvetica", size: 50)
+ button.addTarget(self, action: #selector(buttonPressed(_:)), for: .touchUpInside)
+
+ self.buttons.append(button)
+ stackView.addArrangedSubview(button)
+ }
+
+ return stackView
+ }
+
+ private func blockViewForUser() {
+ DispatchQueue.main.async {
+ self.resetButton.isHidden = true
+ self.indicator.isHidden = false
+ self.indicator.startAnimating()
+
+ self.blockButtons()
+ self.updateUI()
+ }
+ }
+
+ private func unblockViewForUser() {
+ DispatchQueue.main.async {
+ self.unblockButtons()
+ self.updateUI()
+
+ self.resetButton.isHidden = false
+ self.indicator.isHidden = true
+ self.indicator.stopAnimating()
+ }
+ }
+
+ @objc private func buttonPressed(_ sender: UIButton) {
+ let position = buttonIDtoPosition(id: sender.restorationIdentifier!)
+
+ DispatchQueue.global(qos: .userInteractive).async {
+ self.gameModel.playerMakeMove(selectedPosition: position)
+
+ self.blockViewForUser()
+
+ self.gameModel.makeMinimaxMove()
+
+ self.unblockViewForUser()
+ }
+ }
+
+ private func setupResetButton() {
+ self.resetButton = UIButton(type: .system)
+ self.resetButton.translatesAutoresizingMaskIntoConstraints = false
+ self.resetButton.setTitle("Reset", for: .normal)
+ self.resetButton.backgroundColor = .lightGray
+ self.resetButton.addTarget(self, action: #selector(resetButtonPressed(_:)), for: .touchUpInside)
+
+ self.addSubview(self.resetButton)
+
+ // constraints
+ let constraints = [
+ self.resetButton.topAnchor.constraint(equalTo: self.stackView.bottomAnchor, constant: 10),
+ self.resetButton.bottomAnchor.constraint(equalTo: self.bottomAnchor),
+ self.resetButton.widthAnchor.constraint(equalTo: self.widthAnchor)
+ ]
+ NSLayoutConstraint.activate(constraints)
+ }
+
+ @objc private func resetButtonPressed(_ sender: UIButton) {
+ self.gameModel.newRound()
+ self.clearButtons()
+ self.startGame()
+ }
+
+ private func setupIndicator() {
+ self.indicator = UIActivityIndicatorView()
+ self.indicator.translatesAutoresizingMaskIntoConstraints = false
+ self.indicator.backgroundColor = .lightGray
+
+ self.addSubview(self.indicator)
+
+ // constraints
+ let constraints = [
+ self.indicator.topAnchor.constraint(equalTo: self.stackView.bottomAnchor, constant: 10),
+ self.indicator.bottomAnchor.constraint(equalTo: self.bottomAnchor),
+ self.indicator.widthAnchor.constraint(equalTo: self.widthAnchor)
+ ]
+ NSLayoutConstraint.activate(constraints)
+ }
+
+ private func buttonIDtoPosition(id: String) -> Position {
+ switch id {
+ case "1":
+ return Position(0, 0)
+ case "2":
+ return Position(0, 1)
+ case "3":
+ return Position(0, 2)
+ case "4":
+ return Position(1, 0)
+ case "5":
+ return Position(1, 1)
+ case "6":
+ return Position(1, 2)
+ case "7":
+ return Position(2, 0)
+ case "8":
+ return Position(2, 1)
+ case "9":
+ return Position(2, 2)
+ default:
+ return Position(0, 0)
+ }
+ }
+
+ private func clearButtons() {
+ for button in self.buttons {
+ button.setTitle("", for: .normal)
+ button.isUserInteractionEnabled = true
+ }
+ }
+
+ private func unblockButtons() {
+ for button in self.buttons {
+ button.isUserInteractionEnabled = true
+ }
+ }
+
+ private func blockButtons() {
+ for button in self.buttons {
+ button.isUserInteractionEnabled = false
+ }
+ }
+}
diff --git a/Minimax/Sources/Minimax.playground/contents.xcplayground b/Minimax/Sources/Minimax.playground/contents.xcplayground
new file mode 100644
index 000000000..cf026f228
--- /dev/null
+++ b/Minimax/Sources/Minimax.playground/contents.xcplayground
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/Minimax/Sources/Minimax.playground/playground.xcworkspace/contents.xcworkspacedata b/Minimax/Sources/Minimax.playground/playground.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 000000000..ca3329e1a
--- /dev/null
+++ b/Minimax/Sources/Minimax.playground/playground.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/Minimax/Sources/Minimax.playground/playground.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/Minimax/Sources/Minimax.playground/playground.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 000000000..08de0be8d
--- /dev/null
+++ b/Minimax/Sources/Minimax.playground/playground.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded
+
+
+
diff --git a/Minimax/Sources/Tests/Tests.xcodeproj/project.pbxproj b/Minimax/Sources/Tests/Tests.xcodeproj/project.pbxproj
new file mode 100644
index 000000000..2ee08e4e0
--- /dev/null
+++ b/Minimax/Sources/Tests/Tests.xcodeproj/project.pbxproj
@@ -0,0 +1,350 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 50;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 9D029435268265690015843C /* BoardTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D029434268265690015843C /* BoardTests.swift */; };
+ 9D02946D268285E20015843C /* PlayerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D02946C268285E20015843C /* PlayerTests.swift */; };
+ 9D0294852682B1850015843C /* Minimax.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D02947A2682B1840015843C /* Minimax.swift */; };
+ 9D0294862682B1850015843C /* PlayerSymbol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D02947B2682B1840015843C /* PlayerSymbol.swift */; };
+ 9D0294872682B1850015843C /* Board.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D02947D2682B1840015843C /* Board.swift */; };
+ 9D0294882682B1850015843C /* Player.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D02947E2682B1840015843C /* Player.swift */; };
+ 9D0294892682B1850015843C /* BoardPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D02947F2682B1840015843C /* BoardPosition.swift */; };
+ 9D02948A2682B1850015843C /* PlayerType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D0294802682B1840015843C /* PlayerType.swift */; };
+ 9D02948B2682B1850015843C /* GameModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D0294812682B1840015843C /* GameModel.swift */; };
+ 9D02948C2682B1850015843C /* DifficultLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D0294822682B1840015843C /* DifficultLevel.swift */; };
+ 9D02948D2682B1850015843C /* BoardStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D0294832682B1840015843C /* BoardStatus.swift */; };
+ 9D02948E2682B1850015843C /* GameStateValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D0294842682B1850015843C /* GameStateValue.swift */; };
+ 9DB8564E268129FE0046878A /* MinimaxTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DB8564D268129FE0046878A /* MinimaxTests.swift */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+ 9D029434268265690015843C /* BoardTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoardTests.swift; sourceTree = ""; };
+ 9D02946C268285E20015843C /* PlayerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerTests.swift; sourceTree = ""; };
+ 9D02947A2682B1840015843C /* Minimax.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Minimax.swift; path = ../Minimax.playground/Sources/Model/Minimax/Minimax.swift; sourceTree = ""; };
+ 9D02947B2682B1840015843C /* PlayerSymbol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PlayerSymbol.swift; path = ../Minimax.playground/Sources/Model/Player/PlayerSymbol.swift; sourceTree = ""; };
+ 9D02947D2682B1840015843C /* Board.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Board.swift; path = ../Minimax.playground/Sources/Model/Board/Board.swift; sourceTree = ""; };
+ 9D02947E2682B1840015843C /* Player.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Player.swift; path = ../Minimax.playground/Sources/Model/Player/Player.swift; sourceTree = ""; };
+ 9D02947F2682B1840015843C /* BoardPosition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BoardPosition.swift; path = ../Minimax.playground/Sources/Model/Board/BoardPosition.swift; sourceTree = ""; };
+ 9D0294802682B1840015843C /* PlayerType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PlayerType.swift; path = ../Minimax.playground/Sources/Model/Player/PlayerType.swift; sourceTree = ""; };
+ 9D0294812682B1840015843C /* GameModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GameModel.swift; path = ../Minimax.playground/Sources/Model/GameModel/GameModel.swift; sourceTree = ""; };
+ 9D0294822682B1840015843C /* DifficultLevel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DifficultLevel.swift; path = ../Minimax.playground/Sources/Model/GameModel/DifficultLevel.swift; sourceTree = ""; };
+ 9D0294832682B1840015843C /* BoardStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BoardStatus.swift; path = ../Minimax.playground/Sources/Model/Board/BoardStatus.swift; sourceTree = ""; };
+ 9D0294842682B1850015843C /* GameStateValue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GameStateValue.swift; path = ../Minimax.playground/Sources/Model/Minimax/GameStateValue.swift; sourceTree = ""; };
+ 9DB8564A268129FE0046878A /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 9DB8564D268129FE0046878A /* MinimaxTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MinimaxTests.swift; sourceTree = ""; };
+ 9DB8564F268129FE0046878A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 9DB85647268129FE0046878A /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 9DB8563F268129EF0046878A = {
+ isa = PBXGroup;
+ children = (
+ 9D02947D2682B1840015843C /* Board.swift */,
+ 9D02947F2682B1840015843C /* BoardPosition.swift */,
+ 9D0294832682B1840015843C /* BoardStatus.swift */,
+ 9D0294822682B1840015843C /* DifficultLevel.swift */,
+ 9D0294812682B1840015843C /* GameModel.swift */,
+ 9D0294842682B1850015843C /* GameStateValue.swift */,
+ 9D02947A2682B1840015843C /* Minimax.swift */,
+ 9D02947E2682B1840015843C /* Player.swift */,
+ 9D02947B2682B1840015843C /* PlayerSymbol.swift */,
+ 9D0294802682B1840015843C /* PlayerType.swift */,
+ 9DB8564C268129FE0046878A /* Tests */,
+ 9DB8564B268129FE0046878A /* Products */,
+ );
+ sourceTree = "";
+ };
+ 9DB8564B268129FE0046878A /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 9DB8564A268129FE0046878A /* Tests.xctest */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 9DB8564C268129FE0046878A /* Tests */ = {
+ isa = PBXGroup;
+ children = (
+ 9DB8564F268129FE0046878A /* Info.plist */,
+ 9D02946C268285E20015843C /* PlayerTests.swift */,
+ 9D029434268265690015843C /* BoardTests.swift */,
+ 9DB8564D268129FE0046878A /* MinimaxTests.swift */,
+ );
+ path = Tests;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 9DB85649268129FE0046878A /* Tests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 9DB85650268129FE0046878A /* Build configuration list for PBXNativeTarget "Tests" */;
+ buildPhases = (
+ 9DB85646268129FE0046878A /* Sources */,
+ 9DB85647268129FE0046878A /* Frameworks */,
+ 9DB85648268129FE0046878A /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = Tests;
+ productName = Tests;
+ productReference = 9DB8564A268129FE0046878A /* Tests.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 9DB85640268129EF0046878A /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastSwiftUpdateCheck = 1250;
+ LastUpgradeCheck = 1250;
+ TargetAttributes = {
+ 9DB85649268129FE0046878A = {
+ CreatedOnToolsVersion = 12.5;
+ };
+ };
+ };
+ buildConfigurationList = 9DB85643268129EF0046878A /* Build configuration list for PBXProject "Tests" */;
+ compatibilityVersion = "Xcode 9.3";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 9DB8563F268129EF0046878A;
+ productRefGroup = 9DB8564B268129FE0046878A /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 9DB85649268129FE0046878A /* Tests */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 9DB85648268129FE0046878A /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 9DB85646268129FE0046878A /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9D029435268265690015843C /* BoardTests.swift in Sources */,
+ 9D02948E2682B1850015843C /* GameStateValue.swift in Sources */,
+ 9DB8564E268129FE0046878A /* MinimaxTests.swift in Sources */,
+ 9D0294872682B1850015843C /* Board.swift in Sources */,
+ 9D0294892682B1850015843C /* BoardPosition.swift in Sources */,
+ 9D02948A2682B1850015843C /* PlayerType.swift in Sources */,
+ 9D02948B2682B1850015843C /* GameModel.swift in Sources */,
+ 9D02948C2682B1850015843C /* DifficultLevel.swift in Sources */,
+ 9D0294862682B1850015843C /* PlayerSymbol.swift in Sources */,
+ 9D02948D2682B1850015843C /* BoardStatus.swift in Sources */,
+ 9D0294882682B1850015843C /* Player.swift in Sources */,
+ 9D02946D268285E20015843C /* PlayerTests.swift in Sources */,
+ 9D0294852682B1850015843C /* Minimax.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+ 9DB85644268129EF0046878A /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ };
+ name = Debug;
+ };
+ 9DB85645268129EF0046878A /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ };
+ name = Release;
+ };
+ 9DB85651268129FE0046878A /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ INFOPLIST_FILE = Tests/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ "@loader_path/../Frameworks",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 11.3;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.Tests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = macosx;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ };
+ name = Debug;
+ };
+ 9DB85652268129FE0046878A /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ INFOPLIST_FILE = Tests/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ "@loader_path/../Frameworks",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 11.3;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.Tests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = macosx;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
+ SWIFT_VERSION = 5.0;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 9DB85643268129EF0046878A /* Build configuration list for PBXProject "Tests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 9DB85644268129EF0046878A /* Debug */,
+ 9DB85645268129EF0046878A /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 9DB85650268129FE0046878A /* Build configuration list for PBXNativeTarget "Tests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 9DB85651268129FE0046878A /* Debug */,
+ 9DB85652268129FE0046878A /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 9DB85640268129EF0046878A /* Project object */;
+}
diff --git a/Minimax/Sources/Tests/Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Minimax/Sources/Tests/Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 000000000..919434a62
--- /dev/null
+++ b/Minimax/Sources/Tests/Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/Minimax/Sources/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Minimax/Sources/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 000000000..18d981003
--- /dev/null
+++ b/Minimax/Sources/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/Minimax/Sources/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/Minimax/Sources/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 000000000..08de0be8d
--- /dev/null
+++ b/Minimax/Sources/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded
+
+
+
diff --git a/Minimax/Sources/Tests/Tests/BoardTests.swift b/Minimax/Sources/Tests/Tests/BoardTests.swift
new file mode 100644
index 000000000..e07f35d07
--- /dev/null
+++ b/Minimax/Sources/Tests/Tests/BoardTests.swift
@@ -0,0 +1,145 @@
+import XCTest
+
+class BoardTests: XCTestCase {
+
+ private var sut: Board!
+
+ private var boardSize = 3
+
+ override func setUp() {
+ super.setUp()
+ sut = Board(size: boardSize)
+ }
+
+ override func tearDown() {
+ sut = nil
+ super.tearDown()
+ }
+
+ func testInit() {
+ XCTAssertEqual(sut.size, boardSize)
+ XCTAssertEqual(allFieldsAreEmpty(), true)
+ }
+
+ func testSymbolForPosition() {
+ let player = Player(type: .human, symbol: .circle)
+ let position = Position(0, 0)
+
+ sut.clear()
+ XCTAssertEqual(sut.symbol(forPosition: position), PlayerSymbol.empty)
+
+ sut.makeMove(player: player, position: position)
+ XCTAssertEqual(sut.symbol(forPosition: position), player.symbol)
+ }
+
+ func testClear() {
+ let player = Player(type: .computer, symbol: .circle)
+ let position = Position(0, 0)
+
+ sut.makeMove(player: player, position: position)
+
+ XCTAssertEqual(allFieldsAreEmpty(), false)
+
+ sut.clear()
+
+ XCTAssertEqual(allFieldsAreEmpty(), true)
+ }
+
+ func testHasEmptyField() {
+ let player = Player(type: .computer, symbol: .circle)
+
+ sut.clear()
+
+ XCTAssertEqual(sut.hasEmptyField(), true)
+
+ sut.makeMove(player: player, position: Position(0, 0))
+ sut.makeMove(player: player, position: Position(0, 1))
+ sut.makeMove(player: player, position: Position(0, 2))
+
+ sut.makeMove(player: player, position: Position(1, 0))
+ sut.makeMove(player: player, position: Position(1, 1))
+ sut.makeMove(player: player, position: Position(1, 2))
+
+ sut.makeMove(player: player, position: Position(2, 0))
+ sut.makeMove(player: player, position: Position(2, 1))
+ sut.makeMove(player: player, position: Position(2, 2))
+
+ XCTAssertEqual(sut.hasEmptyField(), false)
+ }
+
+ func testMakeMove() {
+ let firstPlayer = Player(type: .human, symbol: .circle)
+ let secondPlayer = Player(type: .human, symbol: .cross)
+ let position = Position(0, 0)
+
+ sut.clear()
+ sut.makeMove(player: firstPlayer, position: position)
+ sut.makeMove(player: secondPlayer, position: position)
+
+ XCTAssertEqual(sut.symbol(forPosition: position), firstPlayer.symbol)
+ }
+
+ func testCheck() {
+ let firstPlayer = Player(type: .computer, symbol: .circle)
+ let secondPlayer = Player(type: .computer, symbol: .cross)
+
+ sut.clear()
+
+ XCTAssertEqual(sut.check(player: firstPlayer), BoardStatus.continues)
+ XCTAssertEqual(sut.check(player: secondPlayer), BoardStatus.continues)
+
+ sut.clear()
+ sut.makeMove(player: firstPlayer, position: Position(0, 0))
+ sut.makeMove(player: firstPlayer, position: Position(0, 1))
+ sut.makeMove(player: firstPlayer, position: Position(0, 2))
+
+ XCTAssertEqual(sut.check(player: firstPlayer), BoardStatus.win)
+ XCTAssertEqual(sut.check(player: secondPlayer), BoardStatus.continues)
+
+ sut.clear()
+ sut.makeMove(player: firstPlayer, position: Position(0, 0))
+ sut.makeMove(player: firstPlayer, position: Position(1, 0))
+ sut.makeMove(player: firstPlayer, position: Position(2, 0))
+
+ XCTAssertEqual(sut.check(player: firstPlayer), BoardStatus.win)
+ XCTAssertEqual(sut.check(player: secondPlayer), BoardStatus.continues)
+
+ sut.clear()
+ sut.makeMove(player: firstPlayer, position: Position(0, 0))
+ sut.makeMove(player: firstPlayer, position: Position(1, 1))
+ sut.makeMove(player: firstPlayer, position: Position(2, 2))
+
+ XCTAssertEqual(sut.check(player: firstPlayer), BoardStatus.win)
+ XCTAssertEqual(sut.check(player: secondPlayer), BoardStatus.continues)
+
+ sut.clear()
+ sut.makeMove(player: firstPlayer, position: Position(0, 0))
+ sut.makeMove(player: secondPlayer, position: Position(0, 1))
+ sut.makeMove(player: secondPlayer, position: Position(0, 2))
+
+ sut.makeMove(player: secondPlayer, position: Position(1, 0))
+ sut.makeMove(player: firstPlayer, position: Position(1, 1))
+ sut.makeMove(player: firstPlayer, position: Position(1, 2))
+
+ sut.makeMove(player: secondPlayer, position: Position(2, 0))
+ sut.makeMove(player: firstPlayer, position: Position(2, 1))
+ sut.makeMove(player: secondPlayer, position: Position(2, 2))
+
+ XCTAssertEqual(sut.check(player: firstPlayer), BoardStatus.draw)
+ XCTAssertEqual(sut.check(player: secondPlayer), BoardStatus.draw)
+ }
+
+ private func allFieldsAreEmpty() -> Bool {
+ var allFieldAreEmpty = true
+
+ for row in 0 ..< sut.size {
+ for column in 0 ..< sut.size {
+ if sut.symbol(forPosition: Position(row, column)) != PlayerSymbol.empty {
+ allFieldAreEmpty = false
+ }
+ }
+ }
+
+ return allFieldAreEmpty
+ }
+}
diff --git a/Minimax/Sources/Tests/Tests/Info.plist b/Minimax/Sources/Tests/Tests/Info.plist
new file mode 100644
index 000000000..64d65ca49
--- /dev/null
+++ b/Minimax/Sources/Tests/Tests/Info.plist
@@ -0,0 +1,22 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+
+
diff --git a/Minimax/Sources/Tests/Tests/MinimaxTests.swift b/Minimax/Sources/Tests/Tests/MinimaxTests.swift
new file mode 100644
index 000000000..0ace9f133
--- /dev/null
+++ b/Minimax/Sources/Tests/Tests/MinimaxTests.swift
@@ -0,0 +1,56 @@
+import XCTest
+
+class MinimaxTests: XCTestCase {
+ override func setUp() {
+ super.setUp()
+ }
+
+ override func tearDown() {
+ super.tearDown()
+ }
+
+ func testEvaluateGameState() {
+ var board = Board(size: 3)
+ let firstPlayer = Player(type: .human, symbol: .cross)
+ let secondPlayer = Player(type: .human, symbol: .circle)
+
+ board.clear()
+
+ XCTAssertEqual(evaluateGameState(board: board, player: firstPlayer, opponent: secondPlayer), nil)
+
+ board.makeMove(player: firstPlayer, position: Position(0, 0))
+
+ XCTAssertEqual(evaluateGameState(board: board, player: firstPlayer, opponent: secondPlayer), nil)
+
+ board.makeMove(player: firstPlayer, position: Position(0, 1))
+ board.makeMove(player: firstPlayer, position: Position(0, 2))
+
+ XCTAssertEqual(evaluateGameState(board: board, player: firstPlayer, opponent: secondPlayer), .max)
+ XCTAssertEqual(evaluateGameState(board: board, player: secondPlayer, opponent: firstPlayer), .min)
+
+ board.clear()
+ board.makeMove(player: secondPlayer, position: Position(0, 0))
+ board.makeMove(player: secondPlayer, position: Position(0, 1))
+ board.makeMove(player: secondPlayer, position: Position(0, 2))
+ board.makeMove(player: firstPlayer, position: Position(1, 0))
+
+ XCTAssertEqual(evaluateGameState(board: board, player: firstPlayer, opponent: secondPlayer), .min)
+ XCTAssertEqual(evaluateGameState(board: board, player: secondPlayer, opponent: firstPlayer), .max)
+
+ board.clear()
+ board.makeMove(player: firstPlayer, position: Position(0, 0))
+ board.makeMove(player: secondPlayer, position: Position(0, 1))
+ board.makeMove(player: secondPlayer, position: Position(0, 2))
+
+ board.makeMove(player: secondPlayer, position: Position(1, 0))
+ board.makeMove(player: firstPlayer, position: Position(1, 1))
+ board.makeMove(player: firstPlayer, position: Position(1, 2))
+
+ board.makeMove(player: secondPlayer, position: Position(2, 0))
+ board.makeMove(player: firstPlayer, position: Position(2, 1))
+ board.makeMove(player: secondPlayer, position: Position(2, 2))
+
+ XCTAssertEqual(evaluateGameState(board: board, player: firstPlayer, opponent: secondPlayer), .null)
+ XCTAssertEqual(evaluateGameState(board: board, player: secondPlayer, opponent: firstPlayer), .null)
+ }
+}
diff --git a/Minimax/Sources/Tests/Tests/PlayerTests.swift b/Minimax/Sources/Tests/Tests/PlayerTests.swift
new file mode 100644
index 000000000..801b30d58
--- /dev/null
+++ b/Minimax/Sources/Tests/Tests/PlayerTests.swift
@@ -0,0 +1,25 @@
+import XCTest
+
+class PlayerTests: XCTestCase {
+
+ private var sut: Player!
+
+ private var playerType: PlayerType = .human
+
+ private var playerSymbol: PlayerSymbol = .circle
+
+ override func setUp() {
+ super.setUp()
+ sut = Player(type: playerType, symbol: playerSymbol)
+ }
+
+ override func tearDown() {
+ sut = nil
+ super.tearDown()
+ }
+
+ func testInit() {
+ XCTAssertEqual(sut.type, playerType)
+ XCTAssertEqual(sut.symbol, playerSymbol)
+ }
+}