I have a situation where 9 buttons are placed on viewController and making tic-tac-toe function on ios. whenever I am trying to reset button images once the game is finished, button images are not getting reset.
class TicTacToeViewController: UIViewController {
@IBOutlet weak var gameBoardView: UIView!
@IBOutlet var buttons: [UIButton]!
var viewModel = TicTacToeViewModel()
override func viewDidLoad() {
super.viewDidLoad()
configureUI()
}
func configureUI() {
viewModel.delegate = self
}
@IBAction func buttonClicked(_ sender: UIButton) {
viewModel.processPlayerMove(for: sender.tag)
}
/// This **function is to reset the view means remove close and circle image once player wants to restart the game. But this code is not working**
func updateButtonView() {
DispatchQueue.main.async {
for (index, moves) in self.viewModel.moves.enumerated() {
let button = self.buttons[index]
if let moves = moves {
let image = UIImage(named: moves.indicator)
button.setImage(image, for: .normal)
} else {
button.setImage(nil, for: .normal)
}
}
}
}
}
/// Delegates are called from viewmodel extension TicTacToeViewController: TicTacToeViewModelProtocol {
func updatePlayerInfo(index: Int) {
let systemImageName = viewModel.moves[index - 1]?.indicator ?? ""
let image = UIImage(named: systemImageName)
if let arrayButtons = buttons {
let button = arrayButtons[index - 1]
button.setImage(image, for: .normal)
}
}
func displayAlert(alertData: AlertItem) {
let alert = UIAlertController(title: alertData.title, message: alertData.message, preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: alertData.buttonTitle, style: UIAlertAction.Style.default, handler: { [weak self] _ in
self?.viewModel.resetGame()
self?.updateButtonView()
}))
self.present(alert, animated: true, completion: nil)
}
func gameBoardDisabled(isGameBoardDisable: Bool) {
gameBoardView.isUserInteractionEnabled = !isGameBoardDisable
}
}
Please let me know of any confusion. Thanks in advance for your help.
This is view model class import Foundation
protocol TicTacToeViewModelProtocol: AnyObject {
func updatePlayerInfo(index: Int)
func displayAlert(alertData: AlertItem)
func gameBoardDisabled(isGameBoardDisable: Bool)
}
enum Player {
case human
case computer
}
class TicTacToeViewModel {
var currentMovePosition: Int?
var moves: [Move?] = Array(repeating: nil, count: 9) {
didSet {
if let delegate = delegate, let position = self.currentMovePosition {
delegate.updatePlayerInfo(index: position)
}
}
}
var isGameBoardDisable = false {
didSet {
if let delegate = delegate {
delegate.gameBoardDisabled(isGameBoardDisable: isGameBoardDisable)
}
}
}
var alertItem: AlertItem? {
didSet {
if let delegate = delegate, let alertItem = alertItem {
delegate.displayAlert(alertData: alertItem)
}
}
}
let winPatterns: Set<Set<Int>> = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [1, 4, 7], [2, 5, 8], [3, 6, 9], [1, 5, 9], [3, 5, 7]]
weak var delegate: TicTacToeViewModelProtocol!
func processPlayerMove(for position: Int) {
self.currentMovePosition = position
if isSquareOccupied(in: moves, forIndex: position) { return }
moves[position - 1] = Move(player: .human, boardIndex: position)
// logic for win, draw or lose
if checkWinCondition(for: .human, in: moves) {
alertItem = AlertContext.humanWin
return
}
if checkForDraw(in: moves) {
alertItem = AlertContext.draw
return
}
isGameBoardDisable = true
processComputerMove()
}
func processComputerMove() {
let computerPosition = determinComputerMovePosition(in: moves)
self.currentMovePosition = computerPosition
moves[computerPosition - 1] = Move(player: .computer, boardIndex: computerPosition)
isGameBoardDisable = false
if checkWinCondition(for: .computer, in: moves) {
alertItem = AlertContext.computerWin
return
}
if checkForDraw(in: moves) {
alertItem = AlertContext.draw
return
}
}
func isSquareOccupied(in moves:[Move?], forIndex index: Int) -> Bool {
return moves.contains(where: { $0?.boardIndex == index })
}
func determinComputerMovePosition(in moves: [Move?]) -> Int {
let computerMoves = moves.compactMap { $0 }.filter { $0.player == .computer }
let computerPositions = Set(computerMoves.map { $0.boardIndex })
for pattern in winPatterns {
let winPositions = pattern.subtracting(computerPositions)
if winPositions.count == 1 {
let isAvailable = !isSquareOccupied(in: moves, forIndex: winPositions.first!)
if isAvailable { return winPositions.first! }
}
}
// if the AI can't finish the game it will block
let humanMoves = moves.compactMap { $0 }.filter { $0.player == .human }
let humanPositions = Set(humanMoves.map { $0.boardIndex })
for pattern in winPatterns {
let winPositions = pattern.subtracting(humanPositions)
if winPositions.count == 1 {
let isAvailable = !isSquareOccupied(in: moves, forIndex: winPositions.first!)
if isAvailable { return winPositions.first! }
}
}
// always take the middle block
let middleSquare = 5
if !isSquareOccupied(in: moves, forIndex: middleSquare) {
return middleSquare
}
// if the AI can't get position middle block it will get a random position
var movePosition = Int.random(in: 1..<10)
while isSquareOccupied(in: moves, forIndex: movePosition) {
movePosition = Int.random(in: 1..<10)
}
return movePosition
}
func checkWinCondition(for player: Player, in moves:[Move?]) -> Bool {
let playerMoves = moves.compactMap({ $0 }).filter { $0.player == player }
let playerPositions = Set(playerMoves.map { $0.boardIndex })
for pattern in winPatterns where pattern.isSubset(of: playerPositions) {return true}
return false
}
func checkForDraw(in moves: [Move?]) -> Bool {
return moves.compactMap { $0 }.count == 9
}
func resetGame() {
moves = Array(repeating: nil, count: 9)
}
}