Skip to content

Commit

Permalink
Add new algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
michalnowak061 committed Jul 21, 2021
1 parent 915684d commit a82d199
Show file tree
Hide file tree
Showing 26 changed files with 1,356 additions and 0 deletions.
72 changes: 72 additions & 0 deletions algorithms/AI/minimax/README.md
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)
Binary file added algorithms/AI/minimax/Resources/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added algorithms/AI/minimax/Resources/image1.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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
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
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
public typealias Position = (row: Int, column: Int)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
public enum BoardStatus {

case continues

case win

case draw
}
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
}
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()
}
}
}
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
}
Loading

0 comments on commit a82d199

Please sign in to comment.