0

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.

enter image description here

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)
    }
}
user1960279
  • 494
  • 1
  • 8
  • 24
  • Please provide more context. – Tushar Sharma Feb 26 '22 at 20:29
  • I have edited my post. – user1960279 Feb 26 '22 at 20:46
  • You haven't shown your view model and `updateButtonView` seems to be doing more than just resetting the board. To reset the board simply iterate over your `buttons` array and set each image to `nil` Presumably you would want to do this by resetting your `moves` array. Show your view model code for resetting the game – Paulw11 Feb 26 '22 at 20:55
  • updateButtonView is used to reset the view only. I am adding view model class too. – user1960279 Feb 26 '22 at 21:08
  • @user1960279 - you didn't include your code showing what `Move` is, or how it is setting / returning `.indicator`. I made a few assumptions, and got your code working fine. If you post the other (necessary) parts of your code, we can probably find the issue. Otherwise, you can take a look at what I did with your code here: https://pastebin.com/cpW5qeCr (note: the UI is created via code, so no `@IBOutlet` or `@IBAction` connections needed) – DonMag Feb 27 '22 at 16:55

0 Answers0