WWDC22 SALE: Save 50% on all my Swift books and bundles! >>

milestone 7-9

Forums > 100 Days of Swift

Did anyone completed the Hangman game in milestone 7-9. Please share

   

Here is my solution. A bit verbose though as I created all views in code. I added all the comments to understand what is going on in there. So hope it is clear :) All is based on things that were taught in previous days. More beautified version looks like that Hangman


  var scoreLabel: UILabel!
  var levelLabel: UILabel!
  var promptLabel: UILabel!
  var letterButtons = [UIButton]()
  var spacer: UILabel!

  var level = 0 {
    didSet {
      levelLabel.text = "Level: \(level + 1)"
    }
  }
  var score = 0 {
    didSet {
      scoreLabel.text = "Score: \(score)"
    }
  }
  var wrongAnswer = 0

  var word = ""
  var wordLetterArray = [String]()
  var maskedWordString = ""
  var maskedWordArray = [String]()

  override func loadView() {
    view = UIView()
    view.backgroundColor = .white

    // create score label
    scoreLabel = UILabel()
    scoreLabel.translatesAutoresizingMaskIntoConstraints = false
    scoreLabel.textAlignment = .right
    scoreLabel.text = "Score: 0"
    scoreLabel.textColor = .black
    scoreLabel.font = UIFont.systemFont(ofSize: 20)
    view.addSubview(scoreLabel)

    // create level label
    levelLabel = UILabel()
    levelLabel.translatesAutoresizingMaskIntoConstraints = false
    levelLabel.textAlignment = .left
    levelLabel.text = "Level: 1"
    levelLabel.textColor = .black
    levelLabel.font = UIFont.systemFont(ofSize: 20)
    view.addSubview(levelLabel)

    // create prompt label i.e. where our word will appear
    promptLabel = UILabel()
    promptLabel.translatesAutoresizingMaskIntoConstraints = false
    promptLabel.textAlignment = .center
    promptLabel.textColor = .black
    promptLabel.font = UIFont.systemFont(ofSize: 80)
    view.addSubview(promptLabel)

    // create space as it stretches depending on the size of the screen see layout priority set to 1
    spacer = UILabel()
    spacer.translatesAutoresizingMaskIntoConstraints = false
    spacer.setContentHuggingPriority(UILayoutPriority(1), for: .vertical)
    view.addSubview(spacer)

    // create a view that holds all buttons
    let buttonsView = UIView()
    buttonsView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(buttonsView)

    // setting constrains to our views on the screen
    NSLayoutConstraint.activate([
      scoreLabel.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
      scoreLabel.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),

      levelLabel.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
      levelLabel.trailingAnchor.constraint(equalTo: scoreLabel.leadingAnchor, constant: -20),

      spacer.topAnchor.constraint(equalTo: scoreLabel.bottomAnchor),
      spacer.centerXAnchor.constraint(equalTo: view.centerXAnchor),

      promptLabel.topAnchor.constraint(equalTo: spacer.bottomAnchor, constant: 20),
      promptLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
      promptLabel.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.8),

      buttonsView.widthAnchor.constraint(equalToConstant: 735),
      buttonsView.heightAnchor.constraint(equalToConstant: 232),
      buttonsView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
      buttonsView.topAnchor.constraint(equalTo: promptLabel.bottomAnchor, constant: 50),
      buttonsView.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor, constant: -50)
    ])

    // create buttons sizes
    let height = 64
    let width = 64

    // as english alphabet contains 26 letters we will place them on
    // 3 rows and last row will be place in center relative to other buttons
    for row in 0..<3 {
      for column in 0..<9 {
        // create the view of a button
        let letterButton = UIButton(type: .system)
        letterButton.titleLabel?.font = UIFont.systemFont(ofSize: 36)
        letterButton.setTitle("A", for: .normal)
        letterButton.layer.cornerRadius = 10
        letterButton.layer.borderColor = UIColor.gray.cgColor
        letterButton.layer.borderWidth = 1

        // place buttons inside button view with 20 points between them
        let frame = CGRect(x: (column * 20) + (column * width), y: (row * 20) + (row * height), width: width, height: height)
        letterButton.frame = frame

        // place last row in center -> 32 points what makes additional space from left
        // and place the row in center
        if row == 2 && column < 8 {
          letterButton.frame = CGRect(x: 32 + (column * 20) + (column * width),  y: (row * 20) + (row * height), width: width, height: height)
        }

        // remove the last button (i.e. 27th letter) from last row
        if row == 2 && column == 8 {
          continue
        }

        // add buttons to button view so that they become visible on our screen
        buttonsView.addSubview(letterButton)

        // add buttons to letter buttons array
        letterButtons.append(letterButton)

        // create action for buttons
        letterButton.addTarget(self, action: #selector(letterTapped), for: .touchUpInside)

      }
    }
  }

  override func viewDidLoad() {
    super.viewDidLoad()

    loadLevel()

    // create alphabet array
    let alphabet = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
                      "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]

    // set title for all the letters
    for i in 0..<letterButtons.count {
      letterButtons[i].setTitle(alphabet[i], for: .normal)
    }
  }

  func loadLevel() {

    // load words file (so depending on your file some info may be different)
    if let wordsFileURL = Bundle.main.url(forResource: "words", withExtension: "txt") {
      if let wordsContents = try? String(contentsOf: wordsFileURL) {
        var listOfWords = wordsContents.components(separatedBy: "\n")

        listOfWords.shuffle()

        // removing line breaks
        //(in my case words list contains additional "\" so i had to put it as "\\")
        word = listOfWords[level].replacingOccurrences(of: "\\", with: "").trimmingCharacters(in: .whitespacesAndNewlines)

        // loop through the word and adding letters as strings to word letter array
        for letter in word {
          wordLetterArray.append(String(letter))
        }

        print(wordLetterArray)
        print(word)

        // loop through word letter array created in previous step
        // and add "*" in masked word string as well as append masked word array.
        for _ in 0..<wordLetterArray.count {
          maskedWordString += "*"
          maskedWordArray.append("*")
        }

        // display word as "*****" in prompt label
        promptLabel.text = maskedWordString
      }
    }
  }

  @objc func letterTapped(_ sender: UIButton) {
    var usedLetters = [String]()

    // retrieve the letter from title label
    guard let letterChosen = sender.titleLabel?.text else { return }

    // hide pressed button
    sender.isHidden = true

    // add letter to used letters array
    usedLetters.append(letterChosen)

    // if world letter array contains the letter you pressed
    if wordLetterArray.contains(letterChosen) {

      // loop through the word letter array and assign index to that letter
      for (index, letter) in wordLetterArray.enumerated() {
        // if the letter we chose == letter in word letter array
        // assign the letter to our masked word array using its index
        // i.e. if index is 2 and letter is "O" now masked word array looks like ["*", "*", "O"]
        if letterChosen == letter {
          maskedWordArray[index] = letter
          // add point to score
          score += 1
        }
      }
      // convert masked word array back to string
      maskedWordString = maskedWordArray.joined()
    } else {
      // if no letter is found deduct from score and add to wrong answer integer
      score -= 1
      wrongAnswer += 1
    }

    // assign masked word string to display the word i.e. now it is **O
    promptLabel.text = maskedWordString

    // check if the word is complete
    checkIfCompleted()
  }

  func checkIfCompleted() {

    // if wrong answers == 7 show the alert and restart the game
    if wrongAnswer == 7 {

      let ac = UIAlertController(title: "You lost!\n Correct word was \(word)",  message: "Try one more time!", preferredStyle: .alert)
      ac.addAction(UIAlertAction(title: "Restart", style: .default, handler: restartGame))
      present(ac, animated: true, completion: nil)
    }

    // if word that was loaded == masked word string after all letters
    // become unmasked then show the alert and go to next level
    if word == maskedWordString {
      let ac = UIAlertController(title: "Well done!", message: "Let's try another word!", preferredStyle: .alert)
      ac.addAction(UIAlertAction(title: "Let's go", style: .default, handler: levelUp))
      present(ac, animated: true, completion: nil)
    }
  }

  // the rest is self-explanatory
    func levelUp(action: UIAlertAction) {
    level += 1
    updateUI()
    loadLevel()
  }

  func restartGame(action: UIAlertAction) {
    level = 0
    score = 0
    updateUI()
    loadLevel()
  }

  func updateUI() {
    wrongAnswer = 0
    wordLetterArray.removeAll()
    maskedWordArray.removeAll()
    maskedWordString = ""
    for btn in letterButtons {
      btn.isHidden = false
    }
  }

   

Hacking with Swift is sponsored by Emerge

SPONSORED Optimize your app’s startup time, binary size, and overall performance using Emerge’s advanced app optimization and monitoring tools. Reliably measure app size, speed up your app's startup time with Emerge's Launch Booster, and much more. Emerge is actively used by many of the top mobile development teams in the world.

Find out more

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.