{ "metadata": { "name": "", "signature": "sha256:374900202f4f7c3f157762c0d012b597cc502efce4de220013e3cc9cd8dfc896" }, "nbformat": 3, "nbformat_minor": 0, "worksheets": [ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Hangman 1: set a puzzle\n", "\n", "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", "\n", "## Data structures\n", "\n", "* What do we need to track?\n", "* What operations do we need to perform on it?\n", "* How to store it?\n", "\n", "## Creating a game\n", "* 'List' of words to choose from\n", " * Pick one at random\n", "\n", "## Game state\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
Data         Operations
\n", "\n", "* Target word\n", "* Discovered letters\n", " * In order in the word\n", "* Lives left\n", "* Wrong letters?\n", "\n", "\n", "\n", "* Get a guess\n", "* Update discovered letters\n", "* Update lives\n", "* Show discovered word\n", "* Detect game end, report\n", "* Detect game win or loss, report\n", "\n", "
" ] }, { "cell_type": "code", "collapsed": false, "input": [ "import re\n", "import random" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 3 }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Get the words\n", "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. " ] }, { "cell_type": "code", "collapsed": false, "input": [ "# Just use a regex to filter the words. There are other ways to do this, as you know.\n", "WORDS = [w.strip() for w in open('/usr/share/dict/british-english').readlines() \n", " if re.match(r'^[a-z]*$', w.strip())]" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 4 }, { "cell_type": "markdown", "metadata": {}, "source": [ "A few quick looks at the list of words, to make sure it's sensible." ] }, { "cell_type": "code", "collapsed": false, "input": [ "len(WORDS)" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 5, "text": [ "62856" ] } ], "prompt_number": 5 }, { "cell_type": "code", "collapsed": false, "input": [ "WORDS[30000:30010]" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 6, "text": [ "['jotted',\n", " 'jotting',\n", " 'jottings',\n", " 'joule',\n", " 'joules',\n", " 'jounce',\n", " 'jounced',\n", " 'jounces',\n", " 'jouncing',\n", " 'journal']" ] } ], "prompt_number": 6 }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Constants and game states" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## The target word" ] }, { "cell_type": "code", "collapsed": false, "input": [ "target = random.choice(WORDS)\n", "target" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 7, "text": [ "'rocketing'" ] } ], "prompt_number": 7 }, { "cell_type": "code", "collapsed": false, "input": [ "STARTING_LIVES = 10\n", "lives = 0" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 8 }, { "cell_type": "code", "collapsed": false, "input": [ "wrong_letters = []" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 9 }, { "cell_type": "markdown", "metadata": {}, "source": [ "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", "\n", "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", "\n", "We can also use `discovered` to check of a game win. If `discovered` contains an underscore, the player hasn't won the game." ] }, { "cell_type": "code", "collapsed": false, "input": [ "discovered = list('_' * len(target))\n", "discovered" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 10, "text": [ "['_', '_', '_', '_', '_', '_', '_', '_', '_']" ] } ], "prompt_number": 10 }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can use `' '.join(discovered)` to display it nicely." ] }, { "cell_type": "code", "collapsed": false, "input": [ "' '.join(discovered)" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 11, "text": [ "'_ _ _ _ _ _ _ _ _'" ] } ], "prompt_number": 11 }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Read a letter\n", "Get rid of fluff and make sure we only get one letter." ] }, { "cell_type": "code", "collapsed": false, "input": [ "letter = input('Enter letter: ').strip().lower()[0]\n", "letter" ], "language": "python", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "stream": "stdout", "text": [ "Enter letter: r\n" ] }, { "metadata": {}, "output_type": "pyout", "prompt_number": 9, "text": [ "'r'" ] } ], "prompt_number": 9 }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Finding a letter in a word\n", "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", "\n", "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", "\n", "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." ] }, { "cell_type": "code", "collapsed": false, "input": [ "# Note that find returns either the first occurrence of a substring, or -1 if it doesn't exist.\n", "def find_all_explicit(string, letter):\n", " locations = []\n", " starting=0\n", " location = string.find(letter)\n", " while location > -1:\n", " locations += [location]\n", " starting = location + 1\n", " location = string.find(letter, starting)\n", " return locations" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 25 }, { "cell_type": "code", "collapsed": false, "input": [ "# A simpler way using a list comprehension and the built-in enumerate()\n", "def find_all(string, letter):\n", " return [p for p, l in enumerate(string) if l == letter]" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 12 }, { "cell_type": "code", "collapsed": false, "input": [ "find_all('happy', 'p')" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 13, "text": [ "[2, 3]" ] } ], "prompt_number": 13 }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Updating the discovered word\n", "Now we can update `discovered`. We'll look through the `locations` list and update that element of `discovered`." ] }, { "cell_type": "code", "collapsed": false, "input": [ "guessed_letter = 'e'\n", "locations = find_all(target, guessed_letter)\n", "for location in locations:\n", " discovered[location] = guessed_letter\n", "discovered" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 15, "text": [ "['_', '_', '_', '_', 'e', '_', '_', '_', '_']" ] } ], "prompt_number": 15 }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Putting it all together\n", "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", "\n", "```\n", "to play a game:\n", " initialise lives, target, etc.\n", " finished? = False\n", " handle a turn\n", " while not finished?:\n", " if guessed all letters:\n", " report success\n", " finished? = True\n", " elif run out of lives:\n", " report failure\n", " finished? = True\n", " else:\n", " handle a turn\n", "```\n", "\n", "```\n", "to handle a turn:\n", " print the challenge\n", " get the letter\n", " if letter in target:\n", " update discovered\n", " else:\n", " update lives, wrong_letters\n", "```\n", "\n", "```\n", "to update discovered:\n", " find all the locations of letter in target\n", " replace the appropriate underscores in discovered\n", "```\n", "\n", "We pretty much know how to do all these bits, so let's just start at the bottom and off we go!\n", "\n", "###Syntax note\n", "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." ] }, { "cell_type": "code", "collapsed": false, "input": [ "def updated_discovered_word(discovered, guessed_letter):\n", " locations = find_all(target, guessed_letter)\n", " for location in locations:\n", " discovered[location] = guessed_letter\n", " return discovered" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 16 }, { "cell_type": "code", "collapsed": false, "input": [ "def initialise():\n", " global lives, target, discovered, wrong_letters\n", " lives = STARTING_LIVES\n", " target = random.choice(WORDS)\n", " discovered = list('_' * len(target))\n", " wrong_letters = []" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 17 }, { "cell_type": "code", "collapsed": false, "input": [ "def do_turn():\n", " global discovered, lives, wrong_letters\n", " print('Word:', ' '.join(discovered), ' : Lives =', lives, ', wrong guesses:', ' '.join(sorted(wrong_letters)))\n", " guess = input('Enter letter: ').strip().lower()[0]\n", " if guess in target:\n", " updated_discovered_word(discovered, guess)\n", " else:\n", " lives -= 1\n", " if guess not in wrong_letters:\n", " wrong_letters += [guess]" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 18 }, { "cell_type": "code", "collapsed": false, "input": [ "def play_game():\n", " global discovered, lives\n", " initialise()\n", " game_finished = False\n", " do_turn()\n", " while not game_finished:\n", " if '_' not in discovered:\n", " print('You won! The word was', target)\n", " game_finished = True\n", " elif lives <= 0:\n", " print('You lost. The word was', target)\n", " game_finished = True\n", " else:\n", " do_turn()" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 19 }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Playing the game\n", "That seemed straightforward. Let's see if it works!" ] }, { "cell_type": "code", "collapsed": false, "input": [ "play_game()" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Word: _ _ _ _ _ _ _ : Lives = 10 , wrong guesses: \n" ] }, { "name": "stdout", "output_type": "stream", "stream": "stdout", "text": [ "Enter letter: e\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "Word: _ _ _ _ _ _ e : Lives = 10 , wrong guesses: \n" ] }, { "name": "stdout", "output_type": "stream", "stream": "stdout", "text": [ "Enter letter: a\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "Word: _ _ _ _ _ _ e : Lives = 9 , wrong guesses: a\n" ] }, { "name": "stdout", "output_type": "stream", "stream": "stdout", "text": [ "Enter letter: i\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "Word: _ _ _ _ i _ e : Lives = 9 , wrong guesses: a\n" ] }, { "name": "stdout", "output_type": "stream", "stream": "stdout", "text": [ "Enter letter: o\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "Word: _ o _ _ i _ e : Lives = 9 , wrong guesses: a\n" ] }, { "name": "stdout", "output_type": "stream", "stream": "stdout", "text": [ "Enter letter: n\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "Word: _ o n _ i _ e : Lives = 9 , wrong guesses: a\n" ] }, { "name": "stdout", "output_type": "stream", "stream": "stdout", "text": [ "Enter letter: v\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "Word: _ o n _ i _ e : Lives = 8 , wrong guesses: a v\n" ] }, { "name": "stdout", "output_type": "stream", "stream": "stdout", "text": [ "Enter letter: t\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "Word: _ o n _ i _ e : Lives = 7 , wrong guesses: a t v\n" ] }, { "name": "stdout", "output_type": "stream", "stream": "stdout", "text": [ "Enter letter: s\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "Word: _ o n _ i _ e : Lives = 6 , wrong guesses: a s t v\n" ] }, { "name": "stdout", "output_type": "stream", "stream": "stdout", "text": [ "Enter letter: h\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "Word: _ o n _ i _ e : Lives = 5 , wrong guesses: a h s t v\n" ] }, { "name": "stdout", "output_type": "stream", "stream": "stdout", "text": [ "Enter letter: f\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "Word: _ o n f i _ e : Lives = 5 , wrong guesses: a h s t v\n" ] }, { "name": "stdout", "output_type": "stream", "stream": "stdout", "text": [ "Enter letter: b\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "Word: b o n f i _ e : Lives = 5 , wrong guesses: a h s t v\n" ] }, { "name": "stdout", "output_type": "stream", "stream": "stdout", "text": [ "Enter letter: r\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "You won! The word was bonfire\n" ] } ], "prompt_number": 22 }, { "cell_type": "code", "collapsed": false, "input": [], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 17 } ], "metadata": {} } ] }