Implementing Players

Your interaction with the player so far has been represented by a single function, next-guess. It’s time to fill out that idea a bit more. There are many potential ways to implement either a human or computer player. Any time you determine a function is open for extension, you should strongly consider using a multimethod or protocol to make that possible.

In this case, you only have a single function, so either choice is viable. There might be a need for players to keep state (remembering what they’ve guessed), so protocols are a bit easier to use, by encapsulating that state in a record which extends the protocol.

So replace that function with a protocol:

 (defprotocol Player
  (next-guess [player progress]))

Given this protocol, consider a first player that just makes random guesses without regard to previous guesses.

To start with the random player, you’ll need a pool of letters to draw from. All characters have an integer mapping, and you can leverage this, along with standard core functions like range, to build a vector of all legal letters:

 (defonce letters (mapv char (range (int ​a​) (inc (int ​z​)))))

You can then build a function that generates a random letter by using ‘rand-nth‘:

 (​defn​ rand-letter []
  (rand-nth letters))

And finally, you have everything you need to build your first player. Because you don’t need any state for a random player, you can just use reify to create an anonymous implementation of the random player:

 (​def​ random-player
  (reify Player
  (next-guess [_ progress] (rand-letter))))

Now that you have a player, you can actually run the game and see how the random player does.

 (game random-player ​"hello"​)
 -> 92

Not so good. Given only 26 letters in the alphabet, any score over 26 is pretty bad. If you can give your player some memory, it could at least avoid guessing the same (bad) letters over and over again.

In fact, keeping in mind that the maximum number of guesses you should need to make is 26, it’s reasonable to create a player that is simply given the choices to make in order.

Memory requires state, so you’ll need to use one of the stateful Clojure constructs to store that memory. Since each player will only be used in a single thread and does not require state coordination or asynchronous updating, an atom is sufficient.

The choices-player will use an atom containing the sequence of choices to make. The next-guess implementation then simply takes the first choice and updates the atom to retain the rest of the choices for the next call.

The player could be implemented by closing over the atom and using reify as you did with random-player, but instead use a record to hold the state and extend it to the protocol:

 (defrecord ChoicesPlayer [choices]
  Player
  (next-guess [_ progress]
  (​let​ [guess (first @choices)]
  (swap! choices rest)
  guess)))
 
 (​defn​ choices-player [choices]
  (->ChoicesPlayer (atom choices)))

You can then create a shuffled-player that guesses each letter in a random order by just shuffling the letters vector:

 (​defn​ shuffled-player []
  (choices-player (shuffle letters)))

Run a few games and try it:

 (game (shuffled-player) ​"hello"​)
 -> 24
 
 (game (shuffled-player) ​"hello"​)
 -> 19
 
 (game (shuffled-player) ​"hello"​)
 -> 21

That’s certainly better than 92, but it still doesn’t seem very good. You could instead implement a player that picks the letters in alphabetical order instead of shuffled order by just not shuffling:

 (​defn​ alpha-player []
  (choices-player letters))

You only need to run this test once as there’s no random element:

 (game (alpha-player) ​"hello"​)
 -> 15

That’s a better score for this word, but it would be worse for others—you can actually predict the score, as it will be the index of the latest letter in the alphabet (here “o” which is 15th).

Over a wide range of words, you would expect the frequency of letters in English words[44] to be a good ordering. You can just hard-code that into a freq-player:

 (​defn​ freq-player []
  (choices-player (seq ​"etaoinshrdlcumwfgypbvkjxqz"​)))

Give it a try:

 (game (freq-player) ​"hello"​)
 -> 11

Just letting the computer play isn’t very fun though. Next let’s add an interactive player so you can play, too.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.12.136.63