forked from TheAlgorithms/Swift
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
915684d
commit a82d199
Showing
26 changed files
with
1,356 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
# Minimax algorithm | ||
|
||
<p align="center"> <img src="Resources/image1.jpg" {:height="50%" width="50%"} /> </p> | ||
|
||
## Runtime environment | ||
<img src="https://img.shields.io/badge/Swift-5.3-orange.svg?style=flat" /> | ||
<img src="https://img.shields.io/badge/Xcode-12.4-blue.svg?style=flat" /> | ||
<img src="https://img.shields.io/badge/MacOS-11.2.3-blue.svg?style=flat" /> | ||
|
||
## 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 | ||
|
||
<p align="center"> <img src="Resources/demo.gif" {:height="100%" width="100%"} /> </p> | ||
|
||
## 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) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions
8
algorithms/AI/minimax/Sources/Minimax.playground/Contents.swift
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
154 changes: 154 additions & 0 deletions
154
algorithms/AI/minimax/Sources/Minimax.playground/Sources/Model/Board/Board.swift
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
} |
1 change: 1 addition & 0 deletions
1
algorithms/AI/minimax/Sources/Minimax.playground/Sources/Model/Board/BoardPosition.swift
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
public typealias Position = (row: Int, column: Int) |
8 changes: 8 additions & 0 deletions
8
algorithms/AI/minimax/Sources/Minimax.playground/Sources/Model/Board/BoardStatus.swift
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
public enum BoardStatus { | ||
|
||
case continues | ||
|
||
case win | ||
|
||
case draw | ||
} |
8 changes: 8 additions & 0 deletions
8
...rithms/AI/minimax/Sources/Minimax.playground/Sources/Model/GameModel/DifficultLevel.swift
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
public enum DifficultLevel: Int { | ||
|
||
case easy = 2 | ||
|
||
case medium = 3 | ||
|
||
case hard = 5 | ||
} |
95 changes: 95 additions & 0 deletions
95
algorithms/AI/minimax/Sources/Minimax.playground/Sources/Model/GameModel/GameModel.swift
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() | ||
} | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
algorithms/AI/minimax/Sources/Minimax.playground/Sources/Model/Minimax/GameStateValue.swift
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
public enum GameStateValue: Int { | ||
|
||
case min = -1 | ||
|
||
case null = 0 | ||
|
||
case max = 1 | ||
} |
Oops, something went wrong.