4 "signature": "sha256:374900202f4f7c3f157762c0d012b597cc502efce4de220013e3cc9cd8dfc896"
12 "cell_type": "markdown",
15 "# Hangman 1: set a puzzle\n",
17 "A fairly traditional hangman game. The computer chooses a word, the (human) player has to guess it without making too many wrong guesses. You'll need to find a list of words to chose from and develop a simple UI for the game (e.g. text only: display the target word with underscores and letters, lives left, and maybe incorrect guesses). \n",
19 "## Data structures\n",
21 "* What do we need to track?\n",
22 "* What operations do we need to perform on it?\n",
23 "* How to store it?\n",
25 "## Creating a game\n",
26 "* 'List' of words to choose from\n",
27 " * Pick one at random\n",
32 "<tr valign=\"top\">\n",
34 "<th> </th>\n",
35 "<th>Operations</th>\n",
37 "<tr valign=\"top\">\n",
41 "* Discovered letters\n",
42 " * In order in the word\n",
51 "* Update discovered letters\n",
53 "* Show discovered word\n",
54 "* Detect game end, report\n",
55 "* Detect game win or loss, report\n",
75 "cell_type": "markdown",
79 "Read the words the game setter can choose from. The list contains all sorts of hangman-illegal words, such as proper nouns and abbreviations. We'll remove them by taking the pragmatic approach that if a word contains a non-lowercase letter (capital letters, full stops, apostrophes, etc.), it's illegal. "
86 "# Just use a regex to filter the words. There are other ways to do this, as you know.\n",
87 "WORDS = [w.strip() for w in open('/usr/share/dict/british-english').readlines() \n",
88 " if re.match(r'^[a-z]*$', w.strip())]"
96 "cell_type": "markdown",
99 "A few quick looks at the list of words, to make sure it's sensible."
108 "language": "python",
113 "output_type": "pyout",
128 "language": "python",
133 "output_type": "pyout",
152 "cell_type": "markdown",
155 "## Constants and game states"
159 "cell_type": "markdown",
169 "target = random.choice(WORDS)\n",
172 "language": "python",
177 "output_type": "pyout",
190 "STARTING_LIVES = 10\n",
193 "language": "python",
204 "language": "python",
210 "cell_type": "markdown",
213 "We'll represent the partially-discovered word as a list of characters. Each character is either the letter in the target (if it's been discovered) or an underscore if it's not.\n",
215 "Use a `list` as it's a mutable data structure. That means we can change individual elements of a list, which we couldn't if we represented it as a `string`.\n",
217 "We can also use `discovered` to check of a game win. If `discovered` contains an underscore, the player hasn't won the game."
224 "discovered = list('_' * len(target))\n",
227 "language": "python",
232 "output_type": "pyout",
235 "['_', '_', '_', '_', '_', '_', '_', '_', '_']"
242 "cell_type": "markdown",
245 "We can use `' '.join(discovered)` to display it nicely."
252 "' '.join(discovered)"
254 "language": "python",
259 "output_type": "pyout",
262 "'_ _ _ _ _ _ _ _ _'"
269 "cell_type": "markdown",
272 "## Read a letter\n",
273 "Get rid of fluff and make sure we only get one letter."
280 "letter = input('Enter letter: ').strip().lower()[0]\n",
283 "language": "python",
288 "output_type": "stream",
296 "output_type": "pyout",
306 "cell_type": "markdown",
309 "# Finding a letter in a word\n",
310 "One operation we want to to is to update `discovered` when we get a `letter`. The trick is that we want to update every occurrence of the `letter` in `discovered`. \n",
312 "We'll do it in two phases. In the first phase, we'll find all the occurences of the `letter` in the `target` and return a list of those locations. In the second phase, we'll update all those positions in `discovered` with the `letter`. \n",
314 "There's probaby a clever way of doing this in one pass, but it's complicated (or at least not obvious), so let's just do it the easy way."
321 "# Note that find returns either the first occurrence of a substring, or -1 if it doesn't exist.\n",
322 "def find_all_explicit(string, letter):\n",
325 " location = string.find(letter)\n",
326 " while location > -1:\n",
327 " locations += [location]\n",
328 " starting = location + 1\n",
329 " location = string.find(letter, starting)\n",
332 "language": "python",
341 "# A simpler way using a list comprehension and the built-in enumerate()\n",
342 "def find_all(string, letter):\n",
343 " return [p for p, l in enumerate(string) if l == letter]"
345 "language": "python",
354 "find_all('happy', 'p')"
356 "language": "python",
361 "output_type": "pyout",
371 "cell_type": "markdown",
374 "## Updating the discovered word\n",
375 "Now we can update `discovered`. We'll look through the `locations` list and update that element of `discovered`."
382 "guessed_letter = 'e'\n",
383 "locations = find_all(target, guessed_letter)\n",
384 "for location in locations:\n",
385 " discovered[location] = guessed_letter\n",
388 "language": "python",
393 "output_type": "pyout",
396 "['_', '_', '_', '_', 'e', '_', '_', '_', '_']"
403 "cell_type": "markdown",
406 "## Putting it all together\n",
407 "We've not got quite a few bits and pieces of how the game should work. Now it's time to start putting them together into the game.\n",
411 " initialise lives, target, etc.\n",
412 " finished? = False\n",
414 " while not finished?:\n",
415 " if guessed all letters:\n",
417 " finished? = True\n",
418 " elif run out of lives:\n",
420 " finished? = True\n",
426 "to handle a turn:\n",
427 " print the challenge\n",
429 " if letter in target:\n",
430 " update discovered\n",
432 " update lives, wrong_letters\n",
436 "to update discovered:\n",
437 " find all the locations of letter in target\n",
438 " replace the appropriate underscores in discovered\n",
441 "We pretty much know how to do all these bits, so let's just start at the bottom and off we go!\n",
444 "Note the use of Python's `global` statement, to tell Python that we're updating the global variables in each procedure. We'll fix that later."
451 "def updated_discovered_word(discovered, guessed_letter):\n",
452 " locations = find_all(target, guessed_letter)\n",
453 " for location in locations:\n",
454 " discovered[location] = guessed_letter\n",
457 "language": "python",
466 "def initialise():\n",
467 " global lives, target, discovered, wrong_letters\n",
468 " lives = STARTING_LIVES\n",
469 " target = random.choice(WORDS)\n",
470 " discovered = list('_' * len(target))\n",
471 " wrong_letters = []"
473 "language": "python",
483 " global discovered, lives, wrong_letters\n",
484 " print('Word:', ' '.join(discovered), ' : Lives =', lives, ', wrong guesses:', ' '.join(sorted(wrong_letters)))\n",
485 " guess = input('Enter letter: ').strip().lower()[0]\n",
486 " if guess in target:\n",
487 " updated_discovered_word(discovered, guess)\n",
490 " if guess not in wrong_letters:\n",
491 " wrong_letters += [guess]"
493 "language": "python",
502 "def play_game():\n",
503 " global discovered, lives\n",
505 " game_finished = False\n",
507 " while not game_finished:\n",
508 " if '_' not in discovered:\n",
509 " print('You won! The word was', target)\n",
510 " game_finished = True\n",
511 " elif lives <= 0:\n",
512 " print('You lost. The word was', target)\n",
513 " game_finished = True\n",
517 "language": "python",
523 "cell_type": "markdown",
526 "## Playing the game\n",
527 "That seemed straightforward. Let's see if it works!"
536 "language": "python",
540 "output_type": "stream",
543 "Word: _ _ _ _ _ _ _ : Lives = 10 , wrong guesses: \n"
548 "output_type": "stream",
555 "output_type": "stream",
558 "Word: _ _ _ _ _ _ e : Lives = 10 , wrong guesses: \n"
563 "output_type": "stream",
570 "output_type": "stream",
573 "Word: _ _ _ _ _ _ e : Lives = 9 , wrong guesses: a\n"
578 "output_type": "stream",
585 "output_type": "stream",
588 "Word: _ _ _ _ i _ e : Lives = 9 , wrong guesses: a\n"
593 "output_type": "stream",
600 "output_type": "stream",
603 "Word: _ o _ _ i _ e : Lives = 9 , wrong guesses: a\n"
608 "output_type": "stream",
615 "output_type": "stream",
618 "Word: _ o n _ i _ e : Lives = 9 , wrong guesses: a\n"
623 "output_type": "stream",
630 "output_type": "stream",
633 "Word: _ o n _ i _ e : Lives = 8 , wrong guesses: a v\n"
638 "output_type": "stream",
645 "output_type": "stream",
648 "Word: _ o n _ i _ e : Lives = 7 , wrong guesses: a t v\n"
653 "output_type": "stream",
660 "output_type": "stream",
663 "Word: _ o n _ i _ e : Lives = 6 , wrong guesses: a s t v\n"
668 "output_type": "stream",
675 "output_type": "stream",
678 "Word: _ o n _ i _ e : Lives = 5 , wrong guesses: a h s t v\n"
683 "output_type": "stream",
690 "output_type": "stream",
693 "Word: _ o n f i _ e : Lives = 5 , wrong guesses: a h s t v\n"
698 "output_type": "stream",
705 "output_type": "stream",
708 "Word: b o n f i _ e : Lives = 5 , wrong guesses: a h s t v\n"
713 "output_type": "stream",
720 "output_type": "stream",
723 "You won! The word was bonfire\n"
733 "language": "python",