HiveBrain v1.2.0
Get Started
← Back to all entries
patternswiftMinor

Second attempt at Hangman in Swift

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
swifthangmansecondattempt

Problem

I recently posted my Hangman game, and got some amazing feedback! The main thing suggested was to create a word generator that could read JSON files so that my hangman brain class didn't have to generate the word used on top of handling gameplay. To do this, I created the HangmanWordGenerator class that parses JSON and generates a word for the HangmanBrain class. Before, the HangmanBrain used a set "database" in the form of three arrays (one for each difficulty) and picked a word itself. I am wondering how I did in implementing these improvements.

Hangman Brain

```
import Foundation
import Darwin

//MARK: - Global Enum

enum DifficultyLevel: String {
case Easy = "Easy"
case Medium = "Medium"
case Hard = "Hard"
}

class HangmanBrain {

init() {
//not sure what to do here? without an empty init xcode says I have to use the one below
}

init(word: String) {
self.word = word //when the user inputs a word
}

//MARK: - Properties

var level: DifficultyLevel? {
didSet {
wordGenerator = HangmanWordGenerator(level: level!)
word = wordGenerator!.generateWord()
setNumberOfGuesses(level: level!)
}
}

var wordGenerator : HangmanWordGenerator? = nil

var guesses: Int? = nil

var word: String? = nil {
didSet {
makeNewGameWord()
}
}

var gameWord = ""

//MARK: - Gameplay Functions

private func makeNewGameWord() {
gameWord = ""
for _ in word!.characters {
createGameWord(character: "_")
}
}

private func setNumberOfGuesses(level level: DifficultyLevel) {
switch level.rawValue {
case "Easy": guesses = 10
case "Medium": guesses = 8
case "Hard": guesses = 6
default: break
}
}

func checkGuessAndUpdateGameWordAndGuesses(character character: String) {
var guessIsCorrect = false
let answer = word!
let currentWord = gameWord as String
gameWord = ""
let currentWordTrimmed = currentWord.stringByReplacingOccurrencesOfString(" ", withString: "")
let numberOfLetter

Solution

This is a better approach than your previous question, but let's make it even better, shall we? Let's take a protocol-oriented approach to this problem.

What methods does the HangmanWordGenerator implement that the HangmanBrain actually need? Realistically, only generateWord, right?

So let's make a protocol and have the HangmanBrain have it's generator's type be this protocol, shall we?

protocol HangmanWordGenerator {
    func nextWord() -> String?
}


Now, in our HangmanBrain, this is our type:

var wordGenerator: HangmanWordGenerator?


So now, we can just make classes or structs that implement this protocol to use as our generator. So perhaps we rename your current generator class as something marking at it as a JSON word generator?

class JSONWordGenerator : HangmanWordGenerator { // etc...


Now later, I could extend your game by creating my own class that perhaps grabs words from some online dictionary...

class OnlineDictionaryWordGenerator : HangmanWordGenerator { // etc...


All your HangmanBrain class needs is a means for getting a word to play (by way of nextWord() that the protocol requires). By using a protocol rather than a class, you leave 100% of the implementation details up to whomever is extending the game with this new means of generating a word.

They don't have to inherit any of the code you've written.

init() {
    //not sure what to do here? without an empty init xcode says I have to use the one below
}

init(word: String) {
    self.word = word //when the user inputs a word
}


These constructors don't really make sense.

When we're constructing a brain, we don't immediately need a word. What we need is a generator. We'll only ask our generator for a word whenever it's time to start a game.

It also probably makes sense to grab a difficulty level during the constructor. So we want something like this:

init(generator: HangmanWordGenerator, difficulty: DifficultyLevel)


switch level.rawValue {
case "Easy": guesses = 10
case "Medium": guesses = 8
case "Hard": guesses = 6
default: break
}


It's not even entirely necessary for our enum to be String backed. But the place to use the backing data isn't in our switches. This is entirely inefficient and make matching more difficult. Instead, just switch on the value without expanding it to its raw value.

switch level {
case .Easy: guesses = 10
case .Medium: guesses = 8
case .Hard: guesses = 6
}


Doing it this way also means we're not required to have a default case.

Code Snippets

protocol HangmanWordGenerator {
    func nextWord() -> String?
}
var wordGenerator: HangmanWordGenerator?
class JSONWordGenerator : HangmanWordGenerator { // etc...
class OnlineDictionaryWordGenerator : HangmanWordGenerator { // etc...
init() {
    //not sure what to do here? without an empty init xcode says I have to use the one below
}

init(word: String) {
    self.word = word //when the user inputs a word
}

Context

StackExchange Code Review Q#97768, answer score: 6

Revisions (0)

No revisions yet.