Search code examples
swiftxcodeuiviewtextlabel

Game of Fifteen (Swift): extra touch to confirm all the buttons are arranged


Could anyone tell me what is wrong with the logic?
I make a game of Fifteen and faced a problem. I need to be sure all the fifteen buttons are arranged in a proper way:

logic:
Every time a button is touched
1. function makeMove() changes the position of a button .
2. function checkGameOver() checks whether all the button are arranged properly, if yes, then function showAlert() make a pop-up window appear.

problem:
when all the buttons are placed, showAlert() does not fire and I need to touch again any button to get the pop-up window

Thank you.

func makeMove(button: UIButton) {
        var currentButtonNumber = button.tag - 1
        if ( currentButtonNumber >= 0 && currentButtonNumber <= 15 ) && button.tag != 4 && button.tag != 8 && button.tag != 12 {
            guard buttons[button.tag - 1].backgroundColor != .none else {
                buttons[button.tag - 1].backgroundColor = .yellow
                buttons[button.tag - 1].setTitle(button.titleLabel?.text, for: .normal)
                buttons[button.tag].backgroundColor = .none
                button.setTitle("", for: .normal)
                return
            }
        }

        currentButtonNumber = button.tag + 1
        if ( currentButtonNumber >= 0 && currentButtonNumber <= 15 ) && button.tag != 3 && button.tag != 7 && button.tag != 11 {
            guard buttons[button.tag + 1].backgroundColor != .none else {
                buttons[button.tag + 1].backgroundColor = .yellow
                buttons[button.tag + 1].setTitle(button.titleLabel?.text, for: .normal)
                buttons[button.tag].backgroundColor = .none
                button.setTitle("", for: .normal)
                return
            }
        }

        currentButtonNumber = button.tag - 4
        if currentButtonNumber >= 0 && currentButtonNumber <= 15 {
            guard buttons[button.tag - 4].backgroundColor != .none else {
                buttons[button.tag - 4].backgroundColor = .yellow
                buttons[button.tag - 4].setTitle(button.titleLabel?.text, for: .normal)
                buttons[button.tag].backgroundColor = .none
                button.setTitle("", for: .normal)
                return
            }
        }

        currentButtonNumber = button.tag + 4
        if currentButtonNumber >= 0 && currentButtonNumber <= 15 {
            guard buttons[button.tag + 4].backgroundColor != .none else {
                buttons[button.tag + 4].backgroundColor = .yellow
                buttons[button.tag + 4].setTitle(button.titleLabel?.text, for: .normal)
                buttons[button.tag].backgroundColor = .none
                button.setTitle("", for: .normal)
                return
            }
        }

    }
    func showAlert() {
        var minutes = 0
        var seconds = 0

        if timerCounter < 60 {
            seconds = timerCounter
        } else if timerCounter == 60 {
            minutes = 1
            seconds = 0
        } else {
            seconds = timerCounter % 60
            minutes = (timerCounter - seconds) / 60
        }

        let alert = UIAlertController(title: "Congratulations!",
                                      message: "You spent \(minutes) minutes and \(seconds) seconds", preferredStyle: .alert)

        let action = UIAlertAction(title: "OK",
                                   style: .default, handler: {
                                    action in
                                    self.setNewGame() 
        })

        alert.addAction(action)

        present(alert, animated: true, completion: nil)
    }

    func checkGameOver() -> Bool {
        var isGameOver = false
        var rightOrderCounter = 0

        for number in 0...14 {
            if (buttons[number].titleLabel?.text == String(number + 1)) {
                rightOrderCounter += 1
            } else {
                rightOrderCounter = 0
                break
            }
        }

        if rightOrderCounter == 15 {
            isGameOver = true
        }

        return isGameOver

    }

    @IBAction func moveButton(button: UIButton) {

        makeMove(button: button)

        if self.checkGameOver() {
            self.stopTimer()
            self.showAlert()
        }

    }


Solution

  • Your mistake is using the button's titles as your model. The problem is that setting a button's title with setTitle(:for:) doesn't necessarily happen until after you leave the main thread. So when you check the current titleLabel, it hasn't been updated yet and reflects the previous state.

    A better approach is to use an array of Int to model your puzzle, and update the button's titles using this array. Your checkGameOver() method should check the order of this array instead of the button's titles.

    The general rule of thumb is: Never use UI elements to store your state