Added split-candidate player
authorNeil Smith <neil.git@njae.me.uk>
Sun, 11 Jan 2015 12:05:22 +0000 (12:05 +0000)
committerNeil Smith <neil.git@njae.me.uk>
Sun, 11 Jan 2015 12:05:22 +0000 (12:05 +0000)
SIGNED.md
hangman/hangman-split-player.ipynb [new file with mode: 0644]
hangman/hangman.py [new file with mode: 0644]

index e609c44073ea0cac76784f99cf4e576d730f2f52..fb70304a10dcb4080aeaa43422b756a4aa1ef3dd 100644 (file)
--- a/SIGNED.md
+++ b/SIGNED.md
@@ -3,19 +3,19 @@
 -----BEGIN PGP SIGNATURE-----
 Version: GnuPG v2
 
-iQIcBAABAgAGBQJUj2cdAAoJEJPB2e07PgbqZdUP/R4/UJ4CU3o/my4s2frGUfjU
-txc034TrbVZsjEHj7NuNtRifzVaIDx4nbTAlA6gGKsh9P4Kk74UM4N7rcTVD65YP
-EcTKjwNgiUXBgVJ59f901zYR5QUA4yt7DHmxxuUrO7aFYXd5Du4op1ZcpMgmZwwG
-MpWqr5Db0+Tkz4Od43C1EhafiVAucsczNWhxCblkX00dQH8gZlZk3QvR7pRuzOup
-FyGHm531Or9gn1QhO9YTFE5el/X0XdDFFjKjtGFQ2+kBPk+W0dqlAnLPTc5K7FgP
-/6OnLRVAI/aefr1ZICIqMeJqSO7MnoKYfqf3UUvoeB7MFpOoNN9Qhu6LIiu9R3pN
-5Z9bkODE80u6+pycg4lS6kebIZz1K9VNA4Z7nV2ZaJBdPai13lR4JRsCb1iALncf
-l0OduJ7lNSkg7PSQ8YBW4olIBMuW1yAVMgQpKM8ecRC/OAPu15N9YW+eQLW3q0cE
-9n+1o2hKSwqEMVe4GcrUQYh3tQ+IPtNqgxH2+NlLkUkTX1qg0USgkv8AcVCWiRK0
-6Ek0vrNv+h9FA9SoIZVupiRP6G1yx6/aXW2dGJGEgxIbfotVdI+/XKoiZTqCmhez
-MLrtx+mpCGFbNYRePbfFxozoOOPrqcND6iWvEbAL2esWeYm+FJ/ksZ4GzgS3P9MO
-0WAw+3p/dXm/NCF5o74X
-=TAdh
+iQIcBAABAgAGBQJUsmceAAoJEJPB2e07PgbqA+UQAJ/0nqhYyF9IVNIQbO3vUV9Q
+XrtNiQ5OmCsDpzq1DQ8CL767tR6HLInHjDcJ6sr9ti3Nl5nPGwww9mDBD4kzGsYU
+wilJ4UUvgEO2+hzqH1v84gHeha2R0xWdQ+M3uQBNMD7XCgmrotLIlR8R0N+GDxEH
+BwaypN5BBAdHv/DhZRyqDAglgmC4mZBwqyMJxETnh8XSW7bZXgGD234JGo9X3iws
+bys42qyMch1oUiOasMwY1/UaT8pbSjhwSEwqU7XaPG+70gfPbWUfVuCj6pr5WVH2
+g82L14dMG079MQf8CTZdWulSXj9733oL8tBRS6NBuFmda8ip20R8CDfabHMgYdqg
+bBhGjVIftSjFd4dgCxiZ1kFlkD1B24hdLei5pbbOET44h/QZfvk59/hqRNL1DwYj
+gcUV/t8FhWJh1zjO8yddRAwna4cnge6C8zziaWTZ5UZIgkgnDnflFekcgaMmJ9yf
+I8b91H8hbGdiNxaZBxQPaadbG49gHmkPyhc1y8T68NViRgb32ryNcvRpY7PCXXBk
+rgTZXc0nHCikAJOb4MZrgDCUp2Y5IGhghG1Z6QwbY/g/JtlNeqtQlmdwUUDcOvbs
+ALbUQsR3bmbaaSThUHZ69WhtkYseLq9I6i3DNigVLDw2kDz+7tkRoiqR6paQp8Cs
+eCTbq7UZA4jo5t6kkUhG
+=0kaS
 -----END PGP SIGNATURE-----
 
 ```
@@ -38,17 +38,6 @@ size    exec  file                                 contents
 27510           big-o-chart-2.png                  a9f40d4499bd43d07bad2b9674b3055a9a3f319349a1256d88a9419f206287d7|56acfe8154f080f9d3186170980a44efd640d4c1748c37c7031f1f20d2f3342e
 11222           big-o-chart-smaller.png            5b453ac13c187ea797dbdcf03c2f1941c730a952e285d2afbfde83cf231f0bc3|ba81f54c5daae7c199d9eb66f58db952e52e6b15cf9c69ed34245c4400ffc318
 31971           big-o-chart.png                    82cea33b61f5af16f928d7037877b31526e532ea62a7cf99ca3761bc4313e4f1|0c43ead93dc010d2fee9724cfda23f2d922cf58931d6258f1da9977fd5bb8cd1
-                controlled-assessment/                                                                                                                                              
-21479             arithmetic-quiz-1.ipynb          e697dd456bed3eda09fdca4c6974842ac5caa2cabc395a5e8a915566dd1ffb00                                                                 
-27329             arithmetic-quiz-2.ipynb          f88667746026264a7cb7fe0c9160b0911ef8fd20b9a89dda220d5807671be26d                                                                 
-64858             arithmetic-quiz-3.ipynb          7659839bbac84d035d42bd017903898063148833fa79768d78b1b1902eb54b88                                                                 
-6713              arithmetic-quiz-analysis.py      54d3436f9c09a5a90af593475a049a06bc2ee12ea818b969b7a9699da6de5747                                                                 
-3995              arithmetic-quiz.py               433575176f8abbc189a269ffbab66ea103c5203fe594fd295d55032029b37b98                                                                 
-4064              class1.results                   cf32c1772a9b0252b946645c1a28fce0e1e1baae1214f318aa24974b58a7f045                                                                 
-4058              class2.results                   9bd729d73541984a846fda0f6151f6d413c6e45ca4d136ae22ba2144a3cbb82c                                                                 
-3989              class3.results                   1afdc258a0f93fb039cb1c7d0f8c4371a20c4db9bc6a8d8b8f78fc49becaaa9b                                                                 
-260               classes.ini                      87126cdcec2366f6f3dddd61869d6c1aff498ea23dca47849a45253439d41faa                                                                 
-23076             names.txt                        3731fb885e6eee63d19f0915b05ae9b5529a6bb201056f2f317a3216fafe6433                                                                 
 21947           euler-11.ipynb                     81780dca17496751894f582e8691935ee6f94949017fb3135087056489f8ad88                                                                 
 24903           euler-14.ipynb                     3652a2be86ec270420431814efa453f8679ad054ae28c015d34232c48dac16bd                                                                 
                 hangman/                                                                                                                                                            
@@ -65,6 +54,8 @@ size    exec  file                                 contents
 9419              hangman-guesser.ipynb            d867d7f57b1b59b9cf74f4594fe4b6fd5761e93799292abfb65dcf35381f25e1                                                                 
 181279            hangman-logging.ipynb            fe153cdccb5fbdc5dfbd5bd828c17bfde8b479291410a817d6b73a80222acc17                                                                 
 12247             hangman-setter.ipynb             bc8d8d7f0bddbe128d273ccd174bbf4ad338771c9159b5b07b262a3e5fede892                                                                 
+44670             hangman-split-player.ipynb       b72ff88817c6cce83d26a701e7e3b17d4332f10fa6cf97520dd7dd6ee6e00464                                                                 
+10061             hangman.py                       f800a3e421c73b4174a5127b709ffbc973d4cfc41216aee0718e67e72ca2470c                                                                 
 10890             word_filter_comparison.ipynb     9b80fff718b9ff5620d9441123ebbf60073ea337d14e1e4825ef7860f4e1346b                                                                 
 5286            hangman.html                       b9a4e8d3f96c57d64dd678fecb1dbd453ee6b415a0bb5a1c232e4e224310eb88                                                                 
 4087            hangman2.html                      18e1678e58a1d89f5d2e9fd68de94152f9f34bb2ad93023b6fb69b970bf53a4a                                                                 
diff --git a/hangman/hangman-split-player.ipynb b/hangman/hangman-split-player.ipynb
new file mode 100644 (file)
index 0000000..a4a9fc8
--- /dev/null
@@ -0,0 +1,1841 @@
+{
+ "metadata": {
+  "name": "",
+  "signature": "sha256:5a2b414ce60f525ddfcbb85f2b57ef6ecf5d08662151337bdc833cd40f541f71"
+ },
+ "nbformat": 3,
+ "nbformat_minor": 0,
+ "worksheets": [
+  {
+   "cells": [
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "import re\n",
+      "import random\n",
+      "import string\n",
+      "import collections"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [],
+     "prompt_number": 4
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "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": 5
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "LETTER_COUNTS = collections.Counter(l.lower() for l in open('../sherlock-holmes.txt').read() if l in string.ascii_letters)\n",
+      "LETTERS_IN_ORDER = [p[0] for p in LETTER_COUNTS.most_common()]"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [],
+     "prompt_number": 6
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "STARTING_LIVES = 10"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [],
+     "prompt_number": 7
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "class Game:\n",
+      "    def __init__(self, target, player=None, lives=STARTING_LIVES):\n",
+      "        self.lives = lives\n",
+      "        self.player = player\n",
+      "        self.target = target\n",
+      "        self.discovered = list('_' * len(target))\n",
+      "        self.wrong_letters = []\n",
+      "        self.game_finished = False\n",
+      "        self.game_won = False\n",
+      "        self.game_lost = False\n",
+      "    \n",
+      "    def find_all(self, letter):\n",
+      "        return [p for p, l in enumerate(self.target) if l == letter]\n",
+      "    \n",
+      "    def update_discovered_word(self, guessed_letter):\n",
+      "        locations = self.find_all(guessed_letter)\n",
+      "        for location in locations:\n",
+      "            self.discovered[location] = guessed_letter\n",
+      "        return self.discovered\n",
+      "    \n",
+      "    def do_turn(self):\n",
+      "        if self.player:\n",
+      "            guess = self.player.guess(self.discovered, self.wrong_letters, self.lives)\n",
+      "        else:\n",
+      "            guess = self.ask_for_guess()\n",
+      "        if guess in self.target:\n",
+      "            self.update_discovered_word(guess)\n",
+      "        else:\n",
+      "            self.lives -= 1\n",
+      "            if guess not in self.wrong_letters:\n",
+      "                self.wrong_letters += [guess]\n",
+      "        if self.lives == 0:\n",
+      "            self.game_finished = True\n",
+      "            self.game_lost = True\n",
+      "        if '_' not in self.discovered:\n",
+      "            self.game_finished = True\n",
+      "            self.game_won = True\n",
+      "            \n",
+      "    def ask_for_guess(self):\n",
+      "        print('Word:', ' '.join(self.discovered), \n",
+      "              ' : Lives =', self.lives, \n",
+      "              ', wrong guesses:', ' '.join(sorted(self.wrong_letters)))\n",
+      "        guess = input('Enter letter: ').strip().lower()[0]\n",
+      "        return guess\n",
+      "    \n",
+      "    def play_game(self):\n",
+      "        while not self.game_finished:\n",
+      "            self.do_turn()\n",
+      "        if not self.player:\n",
+      "            self.report_on_game()\n",
+      "        return self.game_won\n",
+      "    \n",
+      "    def report_on_game(self):\n",
+      "        if self.game_won:\n",
+      "            print('You won! The word was', self.target)\n",
+      "        else:\n",
+      "            print('You lost. The word was', self.target)\n",
+      "        return self.game_won"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [],
+     "prompt_number": 8
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "DICT_COUNTS = collections.Counter(l.lower() for l in open('/usr/share/dict/british-english').read() if l in string.ascii_letters)\n",
+      "DICT_LETTERS_IN_ORDER = [p[0] for p in DICT_COUNTS.most_common()]"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [],
+     "prompt_number": 9
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "class PlayerAdaptiveNoRegex:\n",
+      "    def __init__(self, words):\n",
+      "        self.candidate_words = words\n",
+      "        \n",
+      "    def guess(self, discovered, missed, lives):\n",
+      "        self.filter_candidate_words(discovered, missed)\n",
+      "        self.set_ordered_letters()\n",
+      "        guessed_letters = [l.lower() for l in discovered + missed if l in string.ascii_letters]\n",
+      "        return [l for l in self.ordered_letters if l not in guessed_letters][0]\n",
+      "    \n",
+      "    def filter_candidate_words(self, discovered, missed):\n",
+      "        pass\n",
+      "        \n",
+      "    def set_ordered_letters(self):\n",
+      "        counts = collections.Counter(l.lower() \n",
+      "                                     for l in ''.join(self.candidate_words) + string.ascii_lowercase \n",
+      "                                     if l in string.ascii_letters)\n",
+      "        self.ordered_letters = [p[0] for p in counts.most_common()]\n",
+      "\n",
+      "    def match(self, pattern, target, excluded=None):\n",
+      "        if not excluded:\n",
+      "            excluded = ''\n",
+      "        if len(pattern) != len(target):\n",
+      "            return False\n",
+      "        for m, c in zip(pattern, target):\n",
+      "            if m == '_' and c not in excluded:\n",
+      "                # true\n",
+      "                pass\n",
+      "            elif m != '_' and m == c:\n",
+      "                # true\n",
+      "                pass\n",
+      "            else:\n",
+      "                return False\n",
+      "        return True        "
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [],
+     "prompt_number": 10
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "class PlayerAdaptiveLengthNoRegex(PlayerAdaptiveNoRegex):\n",
+      "    def __init__(self, words):\n",
+      "        super().__init__(words)\n",
+      "        self.word_len = None\n",
+      "        self.ordered_letters = None\n",
+      "        \n",
+      "    def filter_candidate_words(self, discovered, missed):\n",
+      "        if not self.word_len:\n",
+      "            self.word_len = len(discovered)\n",
+      "            self.candidate_words = [w for w in self.candidate_words if len(w) == self.word_len]\n",
+      "    \n",
+      "    def set_ordered_letters(self):\n",
+      "        if not self.ordered_letters:\n",
+      "            super().set_ordered_letters()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [],
+     "prompt_number": 11
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "class PlayerAdaptiveIncludedLettersNoRegex(PlayerAdaptiveNoRegex):\n",
+      "    def filter_candidate_words(self, discovered, missed):\n",
+      "        self.candidate_words = [w for w in self.candidate_words if self.match(discovered, w)]"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [],
+     "prompt_number": 12
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "class PlayerAdaptiveExcludedLettersNoRegex(PlayerAdaptiveNoRegex):\n",
+      "    def filter_candidate_words(self, discovered, missed):\n",
+      "        if missed:\n",
+      "            empty_target = '_' * len(discovered)\n",
+      "            self.candidate_words = [w for w in self.candidate_words if self.match(empty_target, w, missed)]        "
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [],
+     "prompt_number": 13
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "class PlayerAdaptivePatternNoRegex(PlayerAdaptiveNoRegex):\n",
+      "    def filter_candidate_words(self, discovered, missed):\n",
+      "        attempted_letters = [l for l in discovered if l != '_'] + missed\n",
+      "        self.candidate_words = [w for w in self.candidate_words if self.match(discovered, w, attempted_letters)]"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [],
+     "prompt_number": 14
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "%%timeit\n",
+      "\n",
+      "wins = 0\n",
+      "for _ in range(1000):\n",
+      "    g = Game(random.choice(WORDS), player=PlayerAdaptivePatternNoRegex(WORDS))\n",
+      "    g.play_game()\n",
+      "    if g.game_won:\n",
+      "        wins += 1\n",
+      "print(wins)"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        "993\n",
+        "992"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        "\n",
+        "994"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        "\n",
+        "991"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        "\n",
+        "1 loops, best of 3: 48.2 s per loop\n"
+       ]
+      }
+     ],
+     "prompt_number": 15
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "len([w for w in WORDS if 'r' in w])"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [
+      {
+       "metadata": {},
+       "output_type": "pyout",
+       "prompt_number": 21,
+       "text": [
+        "31398"
+       ]
+      }
+     ],
+     "prompt_number": 21
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "len([w for w in WORDS if 'r' not in w])"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [
+      {
+       "metadata": {},
+       "output_type": "pyout",
+       "prompt_number": 22,
+       "text": [
+        "31458"
+       ]
+      }
+     ],
+     "prompt_number": 22
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "letter_diffs = []\n",
+      "for l in string.ascii_lowercase:\n",
+      "    n = 0\n",
+      "    for w in WORDS:\n",
+      "        if l in w:\n",
+      "            n += 1\n",
+      "        else:\n",
+      "            n -=1\n",
+      "    letter_diffs += [(l, abs(n))]\n",
+      "sorted(letter_diffs, key=lambda p: p[1])"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [
+      {
+       "metadata": {},
+       "output_type": "pyout",
+       "prompt_number": 19,
+       "text": [
+        "[('r', 60),\n",
+        " ('a', 98),\n",
+        " ('n', 3720),\n",
+        " ('t', 4728),\n",
+        " ('i', 6136),\n",
+        " ('s', 8662),\n",
+        " ('o', 12788),\n",
+        " ('l', 17878),\n",
+        " ('e', 22936),\n",
+        " ('c', 26102),\n",
+        " ('d', 26368),\n",
+        " ('u', 30282),\n",
+        " ('g', 33260),\n",
+        " ('p', 35960),\n",
+        " ('m', 37904),\n",
+        " ('h', 41134),\n",
+        " ('b', 44784),\n",
+        " ('y', 47462),\n",
+        " ('f', 49626),\n",
+        " ('v', 52502),\n",
+        " ('k', 53616),\n",
+        " ('w', 53688),\n",
+        " ('x', 60010),\n",
+        " ('q', 60816),\n",
+        " ('j', 60938),\n",
+        " ('z', 61244)]"
+       ]
+      }
+     ],
+     "prompt_number": 19
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "def letter_diff(l):\n",
+      "    return abs(sum(1 if l in w else -1 for w in WORDS))\n",
+      "\n",
+      "letter_diffs = [(l, letter_diff(l)) \n",
+      "                for l in string.ascii_lowercase]\n",
+      "sorted(letter_diffs, key=lambda p: p[1])"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [
+      {
+       "metadata": {},
+       "output_type": "pyout",
+       "prompt_number": 23,
+       "text": [
+        "[('r', 60),\n",
+        " ('a', 98),\n",
+        " ('n', 3720),\n",
+        " ('t', 4728),\n",
+        " ('i', 6136),\n",
+        " ('s', 8662),\n",
+        " ('o', 12788),\n",
+        " ('l', 17878),\n",
+        " ('e', 22936),\n",
+        " ('c', 26102),\n",
+        " ('d', 26368),\n",
+        " ('u', 30282),\n",
+        " ('g', 33260),\n",
+        " ('p', 35960),\n",
+        " ('m', 37904),\n",
+        " ('h', 41134),\n",
+        " ('b', 44784),\n",
+        " ('y', 47462),\n",
+        " ('f', 49626),\n",
+        " ('v', 52502),\n",
+        " ('k', 53616),\n",
+        " ('w', 53688),\n",
+        " ('x', 60010),\n",
+        " ('q', 60816),\n",
+        " ('j', 60938),\n",
+        " ('z', 61244)]"
+       ]
+      }
+     ],
+     "prompt_number": 23
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "class PlayerAdaptiveSplit(PlayerAdaptivePatternNoRegex):\n",
+      "    def set_ordered_letters(self):\n",
+      "        def letter_diff(l):\n",
+      "            return abs(sum(1 if l in w else -1 for w in self.candidate_words))\n",
+      "        possible_letters = set(''.join(self.candidate_words))\n",
+      "        # if len(self.candidate_words) > 1:\n",
+      "        letter_diffs = [(l, letter_diff(l)) for l in possible_letters]\n",
+      "        self.ordered_letters = [p[0] for p in sorted(letter_diffs, key=lambda p: p[1])]\n",
+      "        # else:\n",
+      "        #    self.ordered_letters = list(self.candidate_words[0])"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [],
+     "prompt_number": 71
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "g = Game(random.choice(WORDS), player=PlayerAdaptiveSplit(WORDS))\n",
+      "g.play_game()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [
+      {
+       "metadata": {},
+       "output_type": "pyout",
+       "prompt_number": 72,
+       "text": [
+        "True"
+       ]
+      }
+     ],
+     "prompt_number": 72
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "g.target"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [
+      {
+       "metadata": {},
+       "output_type": "pyout",
+       "prompt_number": 73,
+       "text": [
+        "'baste'"
+       ]
+      }
+     ],
+     "prompt_number": 73
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "g.discovered"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [
+      {
+       "metadata": {},
+       "output_type": "pyout",
+       "prompt_number": 74,
+       "text": [
+        "['b', 'a', 's', 't', 'e']"
+       ]
+      }
+     ],
+     "prompt_number": 74
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "g.wrong_letters"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [
+      {
+       "metadata": {},
+       "output_type": "pyout",
+       "prompt_number": 75,
+       "text": [
+        "['c', 'h']"
+       ]
+      }
+     ],
+     "prompt_number": 75
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "%%timeit\n",
+      "\n",
+      "wins = 0\n",
+      "for _ in range(1000):\n",
+      "    g = Game(random.choice(WORDS), player=PlayerAdaptiveSplit(WORDS))\n",
+      "    g.play_game()\n",
+      "    if g.game_won:\n",
+      "        wins += 1\n",
+      "print(wins)"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        "983\n",
+        "982"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        "\n",
+        "981"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        "\n",
+        "987"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        "\n",
+        "1 loops, best of 3: 55.6 s per loop\n"
+       ]
+      }
+     ],
+     "prompt_number": 78
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "p=PlayerAdaptiveSplit(WORDS)"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [],
+     "prompt_number": 54
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "dsc = ['_'] * len('recognition')\n",
+      "dsc"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [
+      {
+       "metadata": {},
+       "output_type": "pyout",
+       "prompt_number": 55,
+       "text": [
+        "['_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_']"
+       ]
+      }
+     ],
+     "prompt_number": 55
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "p.guess(dsc, [], 10)"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [
+      {
+       "metadata": {},
+       "output_type": "pyout",
+       "prompt_number": 56,
+       "text": [
+        "'o'"
+       ]
+      }
+     ],
+     "prompt_number": 56
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "len(p.candidate_words)"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [
+      {
+       "metadata": {},
+       "output_type": "pyout",
+       "prompt_number": 57,
+       "text": [
+        "5027"
+       ]
+      }
+     ],
+     "prompt_number": 57
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "p.guess(['_', '_', '_', 'o', '_', '_', '_', '_', '_', 'o', '_'], [], 10)"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [
+      {
+       "metadata": {},
+       "output_type": "pyout",
+       "prompt_number": 58,
+       "text": [
+        "'c'"
+       ]
+      }
+     ],
+     "prompt_number": 58
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "p.candidate_words"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [
+      {
+       "metadata": {},
+       "output_type": "pyout",
+       "prompt_number": 59,
+       "text": [
+        "['association',\n",
+        " 'defoliation',\n",
+        " 'deformation',\n",
+        " 'denominator',\n",
+        " 'deportation',\n",
+        " 'excoriation',\n",
+        " 'exhortation',\n",
+        " 'exportation',\n",
+        " 'importation',\n",
+        " 'information',\n",
+        " 'liposuction',\n",
+        " 'negotiation',\n",
+        " 'recognition',\n",
+        " 'recondition',\n",
+        " 'reformation',\n",
+        " 'subornation']"
+       ]
+      }
+     ],
+     "prompt_number": 59
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "p.guess(['_', '_', 'c', 'o', '_', '_', '_', '_', '_', 'o', '_'], [], 10)"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [
+      {
+       "metadata": {},
+       "output_type": "pyout",
+       "prompt_number": 60,
+       "text": [
+        "'a'"
+       ]
+      }
+     ],
+     "prompt_number": 60
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "p.candidate_words"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [
+      {
+       "metadata": {},
+       "output_type": "pyout",
+       "prompt_number": 61,
+       "text": [
+        "['excoriation', 'recognition', 'recondition']"
+       ]
+      }
+     ],
+     "prompt_number": 61
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "p.guess(['_', '_', 'c', 'o', '_', '_', '_', '_', '_', 'o', '_'], ['a'], 9)"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [
+      {
+       "metadata": {},
+       "output_type": "pyout",
+       "prompt_number": 62,
+       "text": [
+        "'d'"
+       ]
+      }
+     ],
+     "prompt_number": 62
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "p.candidate_words"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [
+      {
+       "metadata": {},
+       "output_type": "pyout",
+       "prompt_number": 63,
+       "text": [
+        "['recognition', 'recondition']"
+       ]
+      }
+     ],
+     "prompt_number": 63
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "p.guess(['_', '_', 'c', 'o', '_', '_', '_', '_', '_', 'o', '_'], ['a', 'd'], 8)"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [
+      {
+       "metadata": {},
+       "output_type": "pyout",
+       "prompt_number": 64,
+       "text": [
+        "'r'"
+       ]
+      }
+     ],
+     "prompt_number": 64
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "p.candidate_words"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [
+      {
+       "metadata": {},
+       "output_type": "pyout",
+       "prompt_number": 65,
+       "text": [
+        "['recognition']"
+       ]
+      }
+     ],
+     "prompt_number": 65
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "g = Game('recognition', player=PlayerAdaptiveSplit(WORDS))\n",
+      "g.play_game()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [
+      {
+       "metadata": {},
+       "output_type": "pyout",
+       "prompt_number": 67,
+       "text": [
+        "True"
+       ]
+      }
+     ],
+     "prompt_number": 67
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "g.discovered"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [
+      {
+       "metadata": {},
+       "output_type": "pyout",
+       "prompt_number": 68,
+       "text": [
+        "['r', 'e', 'c', 'o', 'g', 'n', 'i', 't', 'i', 'o', 'n']"
+       ]
+      }
+     ],
+     "prompt_number": 68
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "g.lives"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [
+      {
+       "metadata": {},
+       "output_type": "pyout",
+       "prompt_number": 69,
+       "text": [
+        "8"
+       ]
+      }
+     ],
+     "prompt_number": 69
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "%%timeit\n",
+      "\n",
+      "wins = 0\n",
+      "for _ in range(10000):\n",
+      "    g = Game(random.choice(WORDS), player=PlayerAdaptiveSplit(WORDS))\n",
+      "    g.play_game()\n",
+      "    if g.game_won:\n",
+      "        wins += 1\n",
+      "print(wins)"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        "9857\n",
+        "9862"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        "\n",
+        "9844"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        "\n",
+        "9860"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        "\n",
+        "1 loops, best of 3: 9min 15s per loop\n"
+       ]
+      }
+     ],
+     "prompt_number": 79
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "%%timeit\n",
+      "\n",
+      "wins = 0\n",
+      "for _ in range(10000):\n",
+      "    g = Game(random.choice(WORDS), player=PlayerAdaptivePatternNoRegex(WORDS))\n",
+      "    g.play_game()\n",
+      "    if g.game_won:\n",
+      "        wins += 1\n",
+      "print(wins)"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        "9934\n",
+        "9916"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        "\n",
+        "9921"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        "\n",
+        "9932"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        "\n",
+        "1 loops, best of 3: 8min 12s per loop\n"
+       ]
+      }
+     ],
+     "prompt_number": 81
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "for w in random.sample(WORDS, 5000):\n",
+      "    gp = Game(w, player=PlayerAdaptivePatternNoRegex(WORDS))\n",
+      "    gp.play_game()\n",
+      "    gs = Game(w, player=PlayerAdaptiveSplit(WORDS))\n",
+      "    gs.play_game()\n",
+      "    if not gp.game_won and not gs.game_won:\n",
+      "        print('Both:::::', gp.target, 'Pattern:', '[' + ' '.join(gp.discovered) + ']', ''.join(gp.wrong_letters), \n",
+      "              ':: Split:', '[' + ' '.join(gs.discovered) + ']', ''.join(gs.wrong_letters))\n",
+      "    if not gp.game_won and gs.game_won:\n",
+      "        print('Pattern::', gp.target, '[' + ' '.join(gp.discovered) + ']', ''.join(gp.wrong_letters))\n",
+      "    if gp.game_won and not gs.game_won:\n",
+      "        print('Split::::', gs.target, '[' + ' '.join(gs.discovered) + ']', ''.join(gs.wrong_letters))"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        "Split:::: cut [_ _ t] aoeiybgnpj\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " reviewers [_ _ _ _ _ _ _ _ _] taoldgnupc\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " piped [_ i _ e d] saolrnkvbm\n",
+        "Both:::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " joying Pattern: [_ o _ i n g] esapwdtrkm :: Split: [_ o _ _ n _] srdatpwhvk\n",
+        "Both:::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " duck Pattern: [_ u _ _] esoailfrnm :: Split: [_ u _ _] esaorlnfmt\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " bum [b _ _] aoeiyrtgns\n",
+        "Pattern::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " jibing [_ i _ i n g] esrldmpkvt\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " begged [_ _ _ _ _ d] srlnoauipt\n",
+        "Pattern::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " bucked [b u _ _ e d] aoilstfgpr\n",
+        "Pattern::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " bunk [_ u n k] esoailfrgp\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " dumping [_ u _ p _ _ g] srlaoecjhb\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " meekest [_ _ _ _ _ s _] iaournlpdw\n",
+        "Pattern::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " wove [_ o v e] arnldptkmc\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " finking [_ _ _ _ _ _ g] srlaoeutpd\n",
+        "Both:::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " fan Pattern: [_ a _] tpgwdmrbys :: Split: [_ a n] tpgwmbyrdv\n",
+        "Pattern::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " hug [_ u g] aoeibpdmtj\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " feminism [_ _ _ _ _ _ _ _] ratlogdcuh\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " effect [_ _ _ _ c t] srdnaioulp\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " puff [_ u f f] esaorlnmgc\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " skivvies [_ _ _ _ _ _ _ _] ratlogdcuh\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " miffing [_ _ _ _ _ _ g] srlaoeutpd\n",
+        "Both:::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " dung Pattern: [_ u n _] esoailfrkt :: Split: [_ u n g] esaorlkthb\n",
+        "Pattern::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " gibed [_ i _ e d] saomlprkvn\n",
+        "Pattern::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " rubs [_ u b s] eaoicpdtnh\n",
+        "Pattern::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " bat [_ a t] ecpsofmvhr\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " boobs [_ o o _ s] eatdklmpfn\n",
+        "Both:::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " yon Pattern: [_ o n] atbdwpiecs :: Split: [_ o n] atbdwpiesc\n",
+        "Both:::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " firs Pattern: [f i _ s] eaoptgbdmn :: Split: [_ i _ s] eaotpgbdmn\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " quipping [_ _ _ _ _ _ _ g] ratloscedh\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " ragging [r a _ _ _ n _] seotzclpdm\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " fuddled [_ u _ _ l _ _] sraoibzmgc\n",
+        "Both:::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " yack Pattern: [_ a c k] esolrntmpj :: Split: [_ a c k] esrlntmjhb\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " vanning [_ a _ _ _ _ g] srltpbwmck\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " fizziest [_ _ _ _ _ _ _ t] rauonlphdk\n",
+        "Pattern::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " rug [_ u g] aoeibpdmtj\n",
+        "Both:::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " fixed Pattern: [_ i _ e d] saomlprkvn :: Split: [_ i _ e d] saolrnkvbm\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " mop [_ o p] atbdwchlsf\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " separatists [_ _ p _ _ _ _ _ _ _ _] oldcgnymbx\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " miff [_ _ _ _] esaoutnlrc\n",
+        "Pattern::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " putty [p u _ _ y] seaoimlnrf\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " mice [_ i _ e] aorlnvtdkw\n",
+        "Both:::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " fixing Pattern: [_ i _ i n g] esrldmpkvt :: Split: [_ _ _ _ n _] srdaoeulbp\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " gaged [_ a _ e d] srwctpmzbh\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " fag [_ a g] tpjnhrlbsw\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " woeful [_ o _ _ _ _] srdnaictyb\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " pauper [_ a _ _ _ r] sntlmidkvj\n",
+        "Both:::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " faxing Pattern: [f a _ i n g] esrwtlcdkz :: Split: [_ a _ _ n _] srdtwmpczh\n",
+        "Both:::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " yoking Pattern: [_ o k i n g] esapwdtrcj :: Split: [_ o k _ n _] srdatpwhvj\n",
+        "Both:::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " cox Pattern: [_ o _] atbdwpnsge :: Split: [_ o _] atbdwpngse\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " doff [_ o _ _] esalrnctyb\n",
+        "Both:::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " puck Pattern: [_ u _ _] esoailfrnm :: Split: [_ u _ _] esaorlnfmt\n",
+        "Pattern::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " tuck [_ u _ _] esoailfrnm\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " kerchiefs [_ _ _ c _ _ _ _ _] taoldgnupv\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " members [_ _ _ _ _ r s] aoiutdpcgv\n",
+        "Both:::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " foxing Pattern: [_ o _ i n g] esapwdtrkm :: Split: [_ o _ _ n _] srdatpwhvk\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " anaesthetises [_ _ _ _ _ _ _ _ _ _ _ _ _] clgmpdvuro\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " versifies [_ _ _ _ _ _ _ _ _] taoldgnupc\n",
+        "Both:::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " babes Pattern: [_ a _ e s] rltngcdkzv :: Split: [_ a _ e s] rltngcdkzv\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " huffing [_ u f f _ _ g] srlaoecpbm\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " weeper [_ _ _ p _ r] saioutdhbk\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " buffering [_ u _ _ _ _ _ _ g] taoldsphmv\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " dills [_ _ l l s] eaountghrb\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " wigging [_ _ g g _ _ g] srlaouejpd\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " indenting [_ _ d _ _ t _ _ _] aoscrlbvhf\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " morn [_ o r n] esaltchbpw\n",
+        "Both:::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " buff Pattern: [_ u f f] esoailgcpd :: Split: [_ u f f] esaorlnmgc\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " zero [_ e r _] satlbnhpyg\n",
+        "Both:::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " jazz Pattern: [_ a _ _] esolrntmky :: Split: [_ a _ _] esrlntmkdw\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " k [_] geopdtuyiz\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " find [_ _ n d] esaoutkhrb\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " happy [_ a _ _ y] sentdrglcz\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " fibbing [_ _ _ _ _ _ g] srlaoeutpd\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " wifeliest [_ _ _ _ _ _ _ _ t] nroacmhpgu\n",
+        "Both:::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " r Pattern: [_] giecposdwk :: Split: [_] geopdtuyiz\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " businesses [_ _ _ _ n _ _ _ _ _] olatdgcphr\n",
+        "Both:::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " fife Pattern: [_ i _ e] aorlndvtpm :: Split: [_ i _ e] aorlnvtdkw\n",
+        "Both:::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " fix Pattern: [_ i _] aoeptdngsb :: Split: [_ i _] aoetndpgbs\n",
+        "Both:::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " mil Pattern: [_ i _] aoeptdngsb :: Split: [_ i _] aoetndpgbs\n",
+        "Split::::"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " wetted [_ _ t t _ d] srlnoauipj\n"
+       ]
+      }
+     ],
+     "prompt_number": 90
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "gs = Game('businesses', player=PlayerAdaptiveSplit(WORDS))"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [],
+     "prompt_number": 91
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "g = Game('feminism', player=PlayerAdaptiveSplit(WORDS))\n",
+      "while not g.game_finished:\n",
+      "    guess = g.player.guess(g.discovered, g.wrong_letters, g.lives)\n",
+      "    print(g.target, '(' + str(g.lives) + ')', \n",
+      "          '[' + ' '.join(g.discovered) + ']', ''.join(g.wrong_letters), \n",
+      "          ';', len(g.player.candidate_words), 'candidate words')\n",
+      "    print('Guess = ', guess)\n",
+      "    g.do_turn()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        "feminism (10) [_ _ _ _ _ _ _ _]  ; 10328 candidate words\n",
+        "Guess =  r\n",
+        "feminism"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " (9) [_ _ _ _ _ _ _ _] r ; 5102 candidate words\n",
+        "Guess =  a\n",
+        "feminism (8) [_ _ _ _ _ _ _ _] ra ; 2673 candidate words\n",
+        "Guess =  t\n",
+        "feminism"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " (7) [_ _ _ _ _ _ _ _] rat ; 1466 candidate words\n",
+        "Guess =  l\n",
+        "feminism (6) [_ _ _ _ _ _ _ _] ratl ; 704 candidate words\n",
+        "Guess =  o\n",
+        "feminism (5) [_ _ _ _ _ _ _ _] ratlo ; 359 candidate words\n",
+        "Guess =  g\n",
+        "feminism (4) [_ _ _ _ _ _ _ _] ratlog ; 189 candidate words\n",
+        "Guess =  d\n",
+        "feminism (3) [_ _ _ _ _ _ _ _] ratlogd ; 94 candidate words\n",
+        "Guess =  c\n",
+        "feminism (2) [_ _ _ _ _ _ _ _] ratlogdc ; 50 candidate words\n",
+        "Guess =  u\n",
+        "feminism (1) [_ _ _ _ _ _ _ _] ratlogdcu ; 31 candidate words\n",
+        "Guess =  h\n"
+       ]
+      }
+     ],
+     "prompt_number": 170
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "g = Game('feminism', player=PlayerAdaptivePatternNoRegex(WORDS))\n",
+      "while not g.game_finished:\n",
+      "    guess = g.player.guess(g.discovered, g.wrong_letters, g.lives)\n",
+      "    print(g.target, '(' + str(g.lives) + ')', \n",
+      "          '[' + ' '.join(g.discovered) + ']', ''.join(g.wrong_letters), \n",
+      "          ';', len(g.player.candidate_words), 'candidate words')\n",
+      "    print('Guess = ', guess)\n",
+      "    g.do_turn()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        "feminism (10) [_ _ _ _ _ _ _ _]  ; 10328 candidate words\n",
+        "Guess =  e\n",
+        "feminism"
+       ]
+      },
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        " (10) [_ e _ _ _ _ _ _]  ; 540 candidate words\n",
+        "Guess =  i\n",
+        "feminism (10) [_ e _ i _ i _ _]  ; 42 candidate words\n",
+        "Guess =  n\n",
+        "feminism (10) [_ e _ i n i _ _]  ; 3 candidate words\n",
+        "Guess =  s\n",
+        "feminism (10) [_ e _ i n i s _]  ; 3 candidate words\n",
+        "Guess =  f\n",
+        "feminism (10) [f e _ i n i s _]  ; 2 candidate words\n",
+        "Guess =  m\n"
+       ]
+      }
+     ],
+     "prompt_number": 172
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "g.player.candidate_words"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [
+      {
+       "metadata": {},
+       "output_type": "pyout",
+       "prompt_number": 171,
+       "text": [
+        "['beehives',\n",
+        " 'enmeshes',\n",
+        " 'evenness',\n",
+        " 'expenses',\n",
+        " 'feminine',\n",
+        " 'feminism',\n",
+        " 'fineness',\n",
+        " 'finesses',\n",
+        " 'finishes',\n",
+        " 'fishwife',\n",
+        " 'inkiness',\n",
+        " 'keenness',\n",
+        " 'meekness',\n",
+        " 'minibike',\n",
+        " 'minimise',\n",
+        " 'missives',\n",
+        " 'ninepins',\n",
+        " 'penknife',\n",
+        " 'sexiness',\n",
+        " 'sheepish',\n",
+        " 'shimmies',\n",
+        " 'shinnies',\n",
+        " 'skivvies',\n",
+        " 'sphinxes',\n",
+        " 'vivifies',\n",
+        " 'vixenish',\n",
+        " 'whimseys',\n",
+        " 'whimsies',\n",
+        " 'whinnies',\n",
+        " 'whiskeys',\n",
+        " 'whiskies']"
+       ]
+      }
+     ],
+     "prompt_number": 171
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    }
+   ],
+   "metadata": {}
+  }
+ ]
+}
\ No newline at end of file
diff --git a/hangman/hangman.py b/hangman/hangman.py
new file mode 100644 (file)
index 0000000..4725c9d
--- /dev/null
@@ -0,0 +1,396 @@
+
+# coding: utf-8
+
+# In[4]:
+
+import re
+import random
+import string
+import collections
+
+
+# In[5]:
+
+WORDS = [w.strip() for w in open('/usr/share/dict/british-english').readlines() 
+         if re.match(r'^[a-z]*$', w.strip())]
+
+
+# In[6]:
+
+LETTER_COUNTS = collections.Counter(l.lower() for l in open('../sherlock-holmes.txt').read() if l in string.ascii_letters)
+LETTERS_IN_ORDER = [p[0] for p in LETTER_COUNTS.most_common()]
+
+
+# In[7]:
+
+STARTING_LIVES = 10
+
+
+# In[8]:
+
+class Game:
+    def __init__(self, target, player=None, lives=STARTING_LIVES):
+        self.lives = lives
+        self.player = player
+        self.target = target
+        self.discovered = list('_' * len(target))
+        self.wrong_letters = []
+        self.game_finished = False
+        self.game_won = False
+        self.game_lost = False
+    
+    def find_all(self, letter):
+        return [p for p, l in enumerate(self.target) if l == letter]
+    
+    def update_discovered_word(self, guessed_letter):
+        locations = self.find_all(guessed_letter)
+        for location in locations:
+            self.discovered[location] = guessed_letter
+        return self.discovered
+    
+    def do_turn(self):
+        if self.player:
+            guess = self.player.guess(self.discovered, self.wrong_letters, self.lives)
+        else:
+            guess = self.ask_for_guess()
+        if guess in self.target:
+            self.update_discovered_word(guess)
+        else:
+            self.lives -= 1
+            if guess not in self.wrong_letters:
+                self.wrong_letters += [guess]
+        if self.lives == 0:
+            self.game_finished = True
+            self.game_lost = True
+        if '_' not in self.discovered:
+            self.game_finished = True
+            self.game_won = True
+            
+    def ask_for_guess(self):
+        print('Word:', ' '.join(self.discovered), 
+              ' : Lives =', self.lives, 
+              ', wrong guesses:', ' '.join(sorted(self.wrong_letters)))
+        guess = input('Enter letter: ').strip().lower()[0]
+        return guess
+    
+    def play_game(self):
+        while not self.game_finished:
+            self.do_turn()
+        if not self.player:
+            self.report_on_game()
+        return self.game_won
+    
+    def report_on_game(self):
+        if self.game_won:
+            print('You won! The word was', self.target)
+        else:
+            print('You lost. The word was', self.target)
+        return self.game_won
+
+
+# In[9]:
+
+DICT_COUNTS = collections.Counter(l.lower() for l in open('/usr/share/dict/british-english').read() if l in string.ascii_letters)
+DICT_LETTERS_IN_ORDER = [p[0] for p in DICT_COUNTS.most_common()]
+
+
+# In[10]:
+
+class PlayerAdaptiveNoRegex:
+    def __init__(self, words):
+        self.candidate_words = words
+        
+    def guess(self, discovered, missed, lives):
+        self.filter_candidate_words(discovered, missed)
+        self.set_ordered_letters()
+        guessed_letters = [l.lower() for l in discovered + missed if l in string.ascii_letters]
+        return [l for l in self.ordered_letters if l not in guessed_letters][0]
+    
+    def filter_candidate_words(self, discovered, missed):
+        pass
+        
+    def set_ordered_letters(self):
+        counts = collections.Counter(l.lower() 
+                                     for l in ''.join(self.candidate_words) + string.ascii_lowercase 
+                                     if l in string.ascii_letters)
+        self.ordered_letters = [p[0] for p in counts.most_common()]
+
+    def match(self, pattern, target, excluded=None):
+        if not excluded:
+            excluded = ''
+        if len(pattern) != len(target):
+            return False
+        for m, c in zip(pattern, target):
+            if m == '_' and c not in excluded:
+                # true
+                pass
+            elif m != '_' and m == c:
+                # true
+                pass
+            else:
+                return False
+        return True        
+
+
+# In[11]:
+
+class PlayerAdaptiveLengthNoRegex(PlayerAdaptiveNoRegex):
+    def __init__(self, words):
+        super().__init__(words)
+        self.word_len = None
+        self.ordered_letters = None
+        
+    def filter_candidate_words(self, discovered, missed):
+        if not self.word_len:
+            self.word_len = len(discovered)
+            self.candidate_words = [w for w in self.candidate_words if len(w) == self.word_len]
+    
+    def set_ordered_letters(self):
+        if not self.ordered_letters:
+            super().set_ordered_letters()
+
+
+# In[12]:
+
+class PlayerAdaptiveIncludedLettersNoRegex(PlayerAdaptiveNoRegex):
+    def filter_candidate_words(self, discovered, missed):
+        self.candidate_words = [w for w in self.candidate_words if self.match(discovered, w)]
+
+
+# In[13]:
+
+class PlayerAdaptiveExcludedLettersNoRegex(PlayerAdaptiveNoRegex):
+    def filter_candidate_words(self, discovered, missed):
+        if missed:
+            empty_target = '_' * len(discovered)
+            self.candidate_words = [w for w in self.candidate_words if self.match(empty_target, w, missed)]        
+
+
+# In[14]:
+
+class PlayerAdaptivePatternNoRegex(PlayerAdaptiveNoRegex):
+    def filter_candidate_words(self, discovered, missed):
+        attempted_letters = [l for l in discovered if l != '_'] + missed
+        self.candidate_words = [w for w in self.candidate_words if self.match(discovered, w, attempted_letters)]
+
+
+# In[15]:
+
+get_ipython().run_cell_magic('timeit', '', '\nwins = 0\nfor _ in range(1000):\n    g = Game(random.choice(WORDS), player=PlayerAdaptivePatternNoRegex(WORDS))\n    g.play_game()\n    if g.game_won:\n        wins += 1\nprint(wins)')
+
+
+# In[21]:
+
+len([w for w in WORDS if 'r' in w])
+
+
+# In[22]:
+
+len([w for w in WORDS if 'r' not in w])
+
+
+# In[19]:
+
+letter_diffs = []
+for l in string.ascii_lowercase:
+    n = 0
+    for w in WORDS:
+        if l in w:
+            n += 1
+        else:
+            n -=1
+    letter_diffs += [(l, abs(n))]
+sorted(letter_diffs, key=lambda p: p[1])
+
+
+# In[23]:
+
+def letter_diff(l):
+    return abs(sum(1 if l in w else -1 for w in WORDS))
+
+letter_diffs = [(l, letter_diff(l)) 
+                for l in string.ascii_lowercase]
+sorted(letter_diffs, key=lambda p: p[1])
+
+
+# In[71]:
+
+class PlayerAdaptiveSplit(PlayerAdaptivePatternNoRegex):
+    def set_ordered_letters(self):
+        def letter_diff(l):
+            return abs(sum(1 if l in w else -1 for w in self.candidate_words))
+        possible_letters = set(''.join(self.candidate_words))
+        # if len(self.candidate_words) > 1:
+        letter_diffs = [(l, letter_diff(l)) for l in possible_letters]
+        self.ordered_letters = [p[0] for p in sorted(letter_diffs, key=lambda p: p[1])]
+        # else:
+        #    self.ordered_letters = list(self.candidate_words[0])
+
+
+# In[72]:
+
+g = Game(random.choice(WORDS), player=PlayerAdaptiveSplit(WORDS))
+g.play_game()
+
+
+# In[73]:
+
+g.target
+
+
+# In[74]:
+
+g.discovered
+
+
+# In[75]:
+
+g.wrong_letters
+
+
+# In[78]:
+
+get_ipython().run_cell_magic('timeit', '', '\nwins = 0\nfor _ in range(1000):\n    g = Game(random.choice(WORDS), player=PlayerAdaptiveSplit(WORDS))\n    g.play_game()\n    if g.game_won:\n        wins += 1\nprint(wins)')
+
+
+# In[54]:
+
+p=PlayerAdaptiveSplit(WORDS)
+
+
+# In[55]:
+
+dsc = ['_'] * len('recognition')
+dsc
+
+
+# In[56]:
+
+p.guess(dsc, [], 10)
+
+
+# In[57]:
+
+len(p.candidate_words)
+
+
+# In[58]:
+
+p.guess(['_', '_', '_', 'o', '_', '_', '_', '_', '_', 'o', '_'], [], 10)
+
+
+# In[59]:
+
+p.candidate_words
+
+
+# In[60]:
+
+p.guess(['_', '_', 'c', 'o', '_', '_', '_', '_', '_', 'o', '_'], [], 10)
+
+
+# In[61]:
+
+p.candidate_words
+
+
+# In[62]:
+
+p.guess(['_', '_', 'c', 'o', '_', '_', '_', '_', '_', 'o', '_'], ['a'], 9)
+
+
+# In[63]:
+
+p.candidate_words
+
+
+# In[64]:
+
+p.guess(['_', '_', 'c', 'o', '_', '_', '_', '_', '_', 'o', '_'], ['a', 'd'], 8)
+
+
+# In[65]:
+
+p.candidate_words
+
+
+# In[67]:
+
+g = Game('recognition', player=PlayerAdaptiveSplit(WORDS))
+g.play_game()
+
+
+# In[68]:
+
+g.discovered
+
+
+# In[69]:
+
+g.lives
+
+
+# In[79]:
+
+get_ipython().run_cell_magic('timeit', '', '\nwins = 0\nfor _ in range(10000):\n    g = Game(random.choice(WORDS), player=PlayerAdaptiveSplit(WORDS))\n    g.play_game()\n    if g.game_won:\n        wins += 1\nprint(wins)')
+
+
+# In[81]:
+
+get_ipython().run_cell_magic('timeit', '', '\nwins = 0\nfor _ in range(10000):\n    g = Game(random.choice(WORDS), player=PlayerAdaptivePatternNoRegex(WORDS))\n    g.play_game()\n    if g.game_won:\n        wins += 1\nprint(wins)')
+
+
+# In[90]:
+
+for w in random.sample(WORDS, 5000):
+    gp = Game(w, player=PlayerAdaptivePatternNoRegex(WORDS))
+    gp.play_game()
+    gs = Game(w, player=PlayerAdaptiveSplit(WORDS))
+    gs.play_game()
+    if not gp.game_won and not gs.game_won:
+        print('Both:::::', gp.target, 'Pattern:', '[' + ' '.join(gp.discovered) + ']', ''.join(gp.wrong_letters), 
+              ':: Split:', '[' + ' '.join(gs.discovered) + ']', ''.join(gs.wrong_letters))
+    if not gp.game_won and gs.game_won:
+        print('Pattern::', gp.target, '[' + ' '.join(gp.discovered) + ']', ''.join(gp.wrong_letters))
+    if gp.game_won and not gs.game_won:
+        print('Split::::', gs.target, '[' + ' '.join(gs.discovered) + ']', ''.join(gs.wrong_letters))
+
+
+# In[91]:
+
+gs = Game('businesses', player=PlayerAdaptiveSplit(WORDS))
+
+
+# In[170]:
+
+g = Game('feminism', player=PlayerAdaptiveSplit(WORDS))
+while not g.game_finished:
+    guess = g.player.guess(g.discovered, g.wrong_letters, g.lives)
+    print(g.target, '(' + str(g.lives) + ')', 
+          '[' + ' '.join(g.discovered) + ']', ''.join(g.wrong_letters), 
+          ';', len(g.player.candidate_words), 'candidate words')
+    print('Guess = ', guess)
+    g.do_turn()
+
+
+# In[172]:
+
+g = Game('feminism', player=PlayerAdaptivePatternNoRegex(WORDS))
+while not g.game_finished:
+    guess = g.player.guess(g.discovered, g.wrong_letters, g.lives)
+    print(g.target, '(' + str(g.lives) + ')', 
+          '[' + ' '.join(g.discovered) + ']', ''.join(g.wrong_letters), 
+          ';', len(g.player.candidate_words), 'candidate words')
+    print('Guess = ', guess)
+    g.do_turn()
+
+
+# In[171]:
+
+g.player.candidate_words
+
+
+# In[ ]:
+
+
+