Skip to content

Commit

Permalink
Rewriting logic
Browse files Browse the repository at this point in the history
  • Loading branch information
FredericXS committed Mar 18, 2023
1 parent 9d6a9ae commit ff47367
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 38 deletions.
8 changes: 6 additions & 2 deletions TicTacToe.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
5F27EC4829BE819E00A3E8CC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5F27EC4729BE819E00A3E8CC /* Assets.xcassets */; };
5F27EC4B29BE819E00A3E8CC /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5F27EC4A29BE819E00A3E8CC /* Preview Assets.xcassets */; };
5F970CDE29BE9BD0002D3758 /* AlertsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F970CDD29BE9BCF002D3758 /* AlertsView.swift */; };
5FBDEC0329C62E5E001DE90E /* GameLogicModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FBDEC0229C62E5E001DE90E /* GameLogicModel.swift */; };
5FE2A60C29BEB4E8007FED0A /* VersusAIViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FE2A60B29BEB4E8007FED0A /* VersusAIViewModel.swift */; };
5FE2A60E29BEB8E6007FED0A /* VersusAIModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FE2A60D29BEB8E6007FED0A /* VersusAIModel.swift */; };
5FE2A61129BEBF1D007FED0A /* DesignView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FE2A61029BEBF1D007FED0A /* DesignView.swift */; };
Expand All @@ -27,6 +28,7 @@
5F27EC4729BE819E00A3E8CC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
5F27EC4A29BE819E00A3E8CC /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
5F970CDD29BE9BCF002D3758 /* AlertsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertsView.swift; sourceTree = "<group>"; };
5FBDEC0229C62E5E001DE90E /* GameLogicModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameLogicModel.swift; sourceTree = "<group>"; };
5FE2A60B29BEB4E8007FED0A /* VersusAIViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersusAIViewModel.swift; sourceTree = "<group>"; };
5FE2A60D29BEB8E6007FED0A /* VersusAIModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersusAIModel.swift; sourceTree = "<group>"; };
5FE2A61029BEBF1D007FED0A /* DesignView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesignView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -108,6 +110,7 @@
children = (
5FE2A60D29BEB8E6007FED0A /* VersusAIModel.swift */,
5FE2A61629BEC39B007FED0A /* VersusFriendModel.swift */,
5FBDEC0229C62E5E001DE90E /* GameLogicModel.swift */,
);
path = Model;
sourceTree = "<group>";
Expand Down Expand Up @@ -186,6 +189,7 @@
5FE2A60C29BEB4E8007FED0A /* VersusAIViewModel.swift in Sources */,
5FE2A61929BEC402007FED0A /* VersusFriendViewModel.swift in Sources */,
5FE2A61129BEBF1D007FED0A /* DesignView.swift in Sources */,
5FBDEC0329C62E5E001DE90E /* GameLogicModel.swift in Sources */,
5FE2A61729BEC39B007FED0A /* VersusFriendModel.swift in Sources */,
5FE2A61329BEC1EA007FED0A /* GameVersusFriend.swift in Sources */,
5F27EC4629BE818200A3E8CC /* GameVersusAIView.swift in Sources */,
Expand Down Expand Up @@ -334,7 +338,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.1;
MARKETING_VERSION = 1.2;
PRODUCT_BUNDLE_IDENTIFIER = com.ashborn.TicTacToe;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
Expand Down Expand Up @@ -366,7 +370,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.1;
MARKETING_VERSION = 1.2;
PRODUCT_BUNDLE_IDENTIFIER = com.ashborn.TicTacToe;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
Expand Down
87 changes: 87 additions & 0 deletions TicTacToe/Model/GameLogicModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//
// GameLogicModel.swift
// TicTacToe
//
// Created by Ashborn on 18/03/23.
//

import Foundation

struct GameLogicModel {
private(set) var moves: [VersusAIModel.Move?]
private var aiVM: VersusAIViewModel
private var humanMoves: [VersusAIModel.Move]
private var computerMoves: [VersusAIModel.Move]

init(currentMove: [VersusAIModel.Move?]) {
moves = currentMove
aiVM = VersusAIViewModel()

// Get human positions to block
humanMoves = moves.compactMap { $0 }.filter { $0.player == .human }

// Get computer positions to win
computerMoves = moves.compactMap { $0 }.filter { $0.player == .computer }
}

func pickRandomSquare() -> Int {
var randomPosition = Int.random(in: 0..<9)

// If square is occupied, pick any other
while aiVM.isSquareOccupied(in: moves, forIndex: randomPosition) {
randomPosition = Int.random(in: 0..<9)
}

return randomPosition
}

func pickMiddleSquare() -> Int {
let centerSquare = 4
if !aiVM.isSquareOccupied(in: moves, forIndex: centerSquare) {
return centerSquare
}

return -1
}

func pickCornerSquare() -> Int {
let corners: [Int] = [0, 2, 6, 8]
for corner in corners {
if !aiVM.isSquareOccupied(in: moves, forIndex: corner) {
return corner
}
}

return -1
}

func blockWin(patterns: Set<Set<Int>>) -> Int {
let humanPositions = Set(humanMoves.map { $0.boardIndex })

for pattern in patterns {
let blockPositions = pattern.subtracting(humanPositions)

if blockPositions.count == 1 {
let isBlockable = !aiVM.isSquareOccupied(in: moves, forIndex: blockPositions.first!)
if isBlockable { return blockPositions.first! }
}
}

return -1
}

func winGame(patterns: Set<Set<Int>>) -> Int {
let computerPositions = Set(computerMoves.map { $0.boardIndex })

for pattern in patterns {
let winPositions = pattern.subtracting(computerPositions)

if winPositions.count == 1 {
let isAvailable = !aiVM.isSquareOccupied(in: moves, forIndex: winPositions.first!)
if isAvailable { return winPositions.first! }
}
}

return -1
}
}
76 changes: 40 additions & 36 deletions TicTacToe/ViewModel/VersusAIViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import SwiftUI

final class VersusAIViewModel: ObservableObject {
let columns: [GridItem] = [GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())]
let levels: [String] = ["Easy", "Medium", "Hard"]
let levels: [String] = ["Easy", "Medium", "Hard", "Impossible"]

@Published var moves: [VersusAIModel.Move?] = Array(repeating: nil, count: 9)
@Published var isGameboardDisabled = false
Expand Down Expand Up @@ -56,59 +56,61 @@ final class VersusAIViewModel: ObservableObject {
}

func determineComputerMovePosistion(in moves: [VersusAIModel.Move?]) -> Int {
// Setting Win conditions
let logicModel = GameLogicModel(currentMove: moves)
let winPatterns: Set<Set<Int>> = [[0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6]]

var movePosition: Int = 0

func easyMode() -> Int {
// Pick a random square
var movePosition = Int.random(in: 0..<9)

while isSquareOccupied(in: moves, forIndex: movePosition) {
movePosition = Int.random(in: 0..<9)
}

return movePosition
logicModel.pickRandomSquare()
}

func mediumMode() -> Int {
// Try pick the middle square
let centerSquare = 4
if !isSquareOccupied(in: moves, forIndex: centerSquare) {
return centerSquare
}
// Try block possible player win
let blockPossibleWin = logicModel.blockWin(patterns: winPatterns)
if (blockPossibleWin != -1) { return blockPossibleWin }

// If there's no win to be blocked, then pick the middle square
let middleSquare = logicModel.pickMiddleSquare()
if (middleSquare != -1) { return middleSquare }

// If AI can't pick middle square, then pick a random square
return easyMode()
}

func hardMode() -> Int {
// Win variables
let computerMoves = moves.compactMap { $0 }.filter { $0.player == .computer }
let computerPositions = Set(computerMoves.map { $0.boardIndex })
// If AI can win, then win the game
let winGame = logicModel.winGame(patterns: winPatterns)
if (winGame != -1) { return winGame }

// Block variables
let humanMoves = moves.compactMap { $0 }.filter { $0.player == .human }
let humanPositions = Set(humanMoves.map { $0.boardIndex })
// If AI can't win the game, then block a possible win
return mediumMode()
}

func impossibleMode() -> Int {
// If AI can win, then win the game
let winGame = logicModel.winGame(patterns: winPatterns)
if (winGame != -1) { return winGame }

for pattern in winPatterns {
let winPositions = pattern.subtracting(computerPositions)
let blockPositions = pattern.subtracting(humanPositions)

if winPositions.count == 1 {
// If AI can win, then win
let isAvailable = !isSquareOccupied(in: moves, forIndex: winPositions.first!)
if isAvailable { return winPositions.first! }
} else if blockPositions.count == 1 {
// If AI can't win, then block
let isBlockable = !isSquareOccupied(in: moves, forIndex: blockPositions.first!)
if isBlockable { return blockPositions.first! }
}
}
// If AI can't win the game, then block a possible win
let blockPossibleWin = logicModel.blockWin(patterns: winPatterns)
if (blockPossibleWin != -1) { return blockPossibleWin }

// If AI can't win and can't block, then try pick middle square
return mediumMode()
// If there's no win to be blocked, then pick the middle square
let middleSquare = logicModel.pickMiddleSquare()
if (middleSquare != -1) { return middleSquare }

// If AI can't pick middle square because is yours, then pick a square above the middle
let secondSquare = 1 // 1 is the second square in array [0, 1, 2...]
if moves.contains(where: { $0?.boardIndex == 4 && $0?.player == .computer }) { return secondSquare }

// If AI can't pick middle square because is not yours, then pick a corner square
let cornerSquare = logicModel.pickCornerSquare()
if (cornerSquare != -1) { return cornerSquare }

// If AI can't pick corner square, then pick a random square
return easyMode()
}

switch(selectedLevelIndex) {
Expand All @@ -118,6 +120,8 @@ final class VersusAIViewModel: ObservableObject {
movePosition = mediumMode()
case 2:
movePosition = hardMode()
case 3:
movePosition = impossibleMode()
default:
print("Something is wrong")
}
Expand Down

0 comments on commit ff47367

Please sign in to comment.