patternswiftMinor
Second attempt at Hangman in Swift
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
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
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
So let's make a protocol and have the
Now, in our
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?
Now later, I could extend your game by creating my own class that perhaps grabs words from some online dictionary...
All your
They don't have to inherit any of the code you've written.
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:
It's not even entirely necessary for our
Doing it this way also means we're not required to have a default case.
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.