Add files via upload

This commit is contained in:
Peter Norvig
2017-09-18 13:55:00 -07:00
committed by GitHub
parent 1727294cc0
commit 6400be4483

View File

@@ -47,7 +47,8 @@
" self.words = {word for word in words if len(word) >= minlength}\n", " self.words = {word for word in words if len(word) >= minlength}\n",
" self.fragments = {word[:i] for word in self.words for i in range(len(word) + 1)}\n", " self.fragments = {word[:i] for word in self.words for i in range(len(word) + 1)}\n",
" \n", " \n",
" def legal_plays(self, fragment): return {fragment + L for L in alphabet} & self.fragments \n", " def legal_plays(self, fragment): \n",
" return {fragment + L for L in alphabet} & self.fragments \n",
" \n", " \n",
"alphabet = 'abcdefghijklmnopqrstuvwxyz'\n", "alphabet = 'abcdefghijklmnopqrstuvwxyz'\n",
" \n", " \n",
@@ -58,7 +59,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Here is a small example:" "Here is a small example with a vocabulary of just three words:"
] ]
}, },
{ {
@@ -71,8 +72,7 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"({'game', 'ghost', 'ghoul'},\n", "{'game', 'ghost', 'ghoul'}"
" {'', 'g', 'ga', 'gam', 'game', 'gh', 'gho', 'ghos', 'ghost', 'ghou', 'ghoul'})"
] ]
}, },
"execution_count": 2, "execution_count": 2,
@@ -83,7 +83,29 @@
"source": [ "source": [
"v = Vocabulary(words('game ghost ghoul'))\n", "v = Vocabulary(words('game ghost ghoul'))\n",
"\n", "\n",
"v.words, v.fragments" "v.words"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"{'', 'g', 'ga', 'gam', 'game', 'gh', 'gho', 'ghos', 'ghost', 'ghou', 'ghoul'}"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"v.fragments"
] ]
}, },
{ {
@@ -95,7 +117,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 3, "execution_count": 4,
"metadata": { "metadata": {
"collapsed": true "collapsed": true
}, },
@@ -106,7 +128,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 4, "execution_count": 5,
"metadata": { "metadata": {
"collapsed": false "collapsed": false
}, },
@@ -124,7 +146,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 5, "execution_count": 6,
"metadata": { "metadata": {
"collapsed": false "collapsed": false
}, },
@@ -135,7 +157,7 @@
"(172724, 387878, 'ethylenediaminetetraacetates')" "(172724, 387878, 'ethylenediaminetetraacetates')"
] ]
}, },
"execution_count": 5, "execution_count": 6,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@@ -146,7 +168,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 6, "execution_count": 7,
"metadata": { "metadata": {
"collapsed": false "collapsed": false
}, },
@@ -157,7 +179,7 @@
"{'ghos', 'ghou'}" "{'ghos', 'ghou'}"
] ]
}, },
"execution_count": 6, "execution_count": 7,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@@ -168,7 +190,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 7, "execution_count": 8,
"metadata": { "metadata": {
"collapsed": false "collapsed": false
}, },
@@ -179,7 +201,7 @@
"{'ewe'}" "{'ewe'}"
] ]
}, },
"execution_count": 7, "execution_count": 8,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@@ -190,7 +212,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 8, "execution_count": 9,
"metadata": { "metadata": {
"collapsed": false "collapsed": false
}, },
@@ -201,7 +223,7 @@
"{'tha', 'the', 'thi', 'tho', 'thr', 'thu', 'thw', 'thy'}" "{'tha', 'the', 'thi', 'tho', 'thr', 'thu', 'thw', 'thy'}"
] ]
}, },
"execution_count": 8, "execution_count": 9,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@@ -223,7 +245,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 9, "execution_count": 10,
"metadata": { "metadata": {
"collapsed": true "collapsed": true
}, },
@@ -238,28 +260,35 @@
"source": [ "source": [
"# Who Wins?\n", "# Who Wins?\n",
"\n", "\n",
"Who wins a game if both players are rational? Given the current fragment, the player whose turn it is will win if either:\n", "Who wins a game if both players play rationally? To answer that, consider the general situation at some point\n",
"suring the game where\n",
"a player is presented with a fragment. That player can win if either:\n",
"- The fragment is a word (meaning the other player formed the word, and lost).\n", "- The fragment is a word (meaning the other player formed the word, and lost).\n",
"- The fragment is not a legal fragment (meaning the other player formed a non-prefix, and lost).\n", "- The fragment is not a legal fragment (meaning the other player made a mistake, and lost).\n",
"- At least one of the legal plays puts the opponent in a position from which they *cannot* win.\n", "- At least one of the legal plays puts the opponent in a position from which they *cannot* win.\n",
"\n", "\n",
"The function `win(vocab, fragment)` implements this idea, returning `True` if the current player can force a win." "The function `win(vocab, fragment)` implements this idea. It returns a winning fragment if there is one, otherwise `None`. In particular, it returns `fragment` if the current player has already won because `fragment` forms\n",
"a word or illegal fragment, and it returns one of the legal plays if the play leads to a position from\n",
"which the opponent *cannot* `win`."
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 10, "execution_count": 11,
"metadata": { "metadata": {
"collapsed": false "collapsed": false
}, },
"outputs": [], "outputs": [],
"source": [ "source": [
"def win(vocab, fragment=''):\n", "def win(vocab, fragment=''):\n",
" \"Does the player whose turn it is have a forced win?\"\n", " \"\"\"Does the current player have a forced win? \n",
" return (fragment in vocab.words or \n", " Return fragment if the player has already won, or return a play that forces a win.\"\"\"\n",
" fragment not in vocab.fragments or\n", " if fragment in vocab.words or fragment not in vocab.fragments:\n",
" any(not win(vocab, play) \n", " return fragment\n",
" for play in vocab.legal_plays(fragment)))" " for play in vocab.legal_plays(fragment):\n",
" if not win(vocab, play):\n",
" return play \n",
" return False"
] ]
}, },
{ {
@@ -271,7 +300,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 11, "execution_count": 12,
"metadata": { "metadata": {
"collapsed": false "collapsed": false
}, },
@@ -282,35 +311,14 @@
"False" "False"
] ]
}, },
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"win(Vocabulary(words('cat camel'))) # All words have odd number of letters; first player loses"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 12, "execution_count": 12,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
], ],
"source": [ "source": [
"win(Vocabulary(words('cat camel goat'))) # First player plays 'g', leading to a win with 'goat'" "# No winning play because all words have odd number of letters.\n",
"win(Vocabulary(words('cat camel gecko'))) "
] ]
}, },
{ {
@@ -323,7 +331,7 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"False" "'g'"
] ]
}, },
"execution_count": 13, "execution_count": 13,
@@ -332,7 +340,8 @@
} }
], ],
"source": [ "source": [
"win(Vocabulary(words('cat camel goat gar'))) # Second player can avoid 'goat' with 'ga'" "# 'g' is a winning play (but 'c' wuld be a loser)\n",
"win(Vocabulary(words('cat camel goat gerbil'))) "
] ]
}, },
{ {
@@ -345,7 +354,7 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"True" "False"
] ]
}, },
"execution_count": 14, "execution_count": 14,
@@ -354,7 +363,31 @@
} }
], ],
"source": [ "source": [
"win(Vocabulary(words('cat camel goat gar gannet'))) # First player plays 'gan' after 'ga' to win" "# No winning play; dommed to 'camel' or 'gar'\n",
"win(Vocabulary(words('cat camel goat gecko gerbil gar'))) "
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"'g'"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# 'g' wins because 'ga' can be answered with 'gan'\n",
"win(Vocabulary(words('cat camel goat gecko gerbil gar gannet'))) "
] ]
}, },
{ {
@@ -368,7 +401,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 15, "execution_count": 16,
"metadata": { "metadata": {
"collapsed": false "collapsed": false
}, },
@@ -379,7 +412,7 @@
"False" "False"
] ]
}, },
"execution_count": 15, "execution_count": 16,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@@ -400,18 +433,26 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 16, "execution_count": 17,
"metadata": { "metadata": {
"collapsed": false "collapsed": false
}, },
"outputs": [ "outputs": [
{ {
"data": { "name": "stdout",
"text/plain": [ "output_type": "stream",
"True" "text": [
"CPU times: user 6.35 ms, sys: 179 µs, total: 6.53 ms\n",
"Wall time: 7.14 ms\n"
] ]
}, },
"execution_count": 16, {
"data": {
"text/plain": [
"'h'"
]
},
"execution_count": 17,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@@ -419,14 +460,14 @@
"source": [ "source": [
"enable1_4 = Vocabulary(enable1.words, 4)\n", "enable1_4 = Vocabulary(enable1.words, 4)\n",
"\n", "\n",
"win(enable1_4)" "%time win(enable1_4)"
] ]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"**Yes.** The first player can win in this case. (It is easier for the first player to win, because now it is the second player who has the first chance to lose (on the fourth play).) So here's a good meta-strategy: Say \"Hey, let's play a game of Ghost. We can use the `enable1` word list. Would you like the limit to be 3 or 4 letters?\" Then if your opponent says three (or four) you can say \"OK, since you decided that, I'll decide to go second (or first).\"" "**Yes.** The first player can win with this vocabulary, by playing `'h'` first (and there might be other first plays that also force a win). It makes sense that it is easier for the first player to win, because we've eliminated a bunch of three-letter words from the vocabulary, all of which are losers for the first player. So here's a good meta-strategy: Say \"*Hey, let's play a game of Ghost. We can use the `enable1` word list. Would you like the limit to be 3 or 4 letters?*\" Then if your opponent says three (or four) you can say \"*OK, since you decided that, I'll decide to go second (or first).*\""
] ]
}, },
{ {
@@ -440,7 +481,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 17, "execution_count": 18,
"metadata": { "metadata": {
"collapsed": false "collapsed": false
}, },
@@ -450,8 +491,7 @@
"\n", "\n",
"def rational(vocab, fragment): \n", "def rational(vocab, fragment): \n",
" \"Select a play that makes opponent not win (if possible), otherwise a random play.\"\n", " \"Select a play that makes opponent not win (if possible), otherwise a random play.\"\n",
" plays = list(vocab.legal_plays(fragment))\n", " return win(vocab, fragment) or random.choice(list(vocab.legal_plays(fragment)))\n",
" return first(p for p in plays if not win(vocab, p)) or random.choice(plays)\n",
"\n", "\n",
"def ask(name):\n", "def ask(name):\n",
" \"Return a strategy that asks for the next letter.\"\n", " \"Return a strategy that asks for the next letter.\"\n",
@@ -471,7 +511,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 18, "execution_count": 19,
"metadata": { "metadata": {
"collapsed": false "collapsed": false
}, },
@@ -496,7 +536,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 19, "execution_count": 20,
"metadata": { "metadata": {
"collapsed": false "collapsed": false
}, },
@@ -504,10 +544,10 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"(1, 'ycleped')" "(1, 'llama')"
] ]
}, },
"execution_count": 19, "execution_count": 20,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@@ -518,7 +558,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 20, "execution_count": 21,
"metadata": { "metadata": {
"collapsed": false "collapsed": false
}, },
@@ -526,19 +566,29 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"[(1, 'bwana'),\n", "[(1, 'dreck'),\n",
" (1, 'mho'),\n",
" (1, 'bwana'),\n", " (1, 'bwana'),\n",
" (1, 'bwana'),\n", " (1, 'aal'),\n",
" (1, 'bwana'),\n", " (1, 'gjetost'),\n",
" (1, 'bwana'),\n",
" (1, 'dwarf'),\n",
" (1, 'hmm'),\n",
" (1, 'llano'),\n",
" (1, 'oquassa'),\n", " (1, 'oquassa'),\n",
" (1, 'whiff')]" " (1, 'oquassa'),\n",
" (1, 'yperite'),\n",
" (1, 'yperite'),\n",
" (1, 'plica'),\n",
" (1, 'wrest'),\n",
" (1, 'qursh'),\n",
" (1, 'aal'),\n",
" (1, 'aas'),\n",
" (1, 'tweak'),\n",
" (1, 'uintahite'),\n",
" (1, 'uintahite'),\n",
" (1, 'hmm'),\n",
" (1, 'uintahite'),\n",
" (1, 'jnana')]"
] ]
}, },
"execution_count": 20, "execution_count": 21,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@@ -546,12 +596,12 @@
"source": [ "source": [
"# Does player 1 win every time?\n", "# Does player 1 win every time?\n",
"\n", "\n",
"sorted(play(enable1, rational, rational, verbose=False) for _ in range(10))" "[play(enable1, rational, rational, verbose=False) for _ in range(20)]"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 21, "execution_count": 22,
"metadata": { "metadata": {
"collapsed": false "collapsed": false
}, },
@@ -559,10 +609,10 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"(0, 'null')" "(0, 'hugs')"
] ]
}, },
"execution_count": 21, "execution_count": 22,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@@ -573,7 +623,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 22, "execution_count": 23,
"metadata": { "metadata": {
"collapsed": false "collapsed": false
}, },
@@ -583,18 +633,17 @@
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"Player 0, given \"\", plays? d\n", "Player 0, given \"\", plays? d\n",
"Player 0, given \"dw\", plays? dwa\n", "Player 0, given \"dr\", plays? dri\n",
"Player 0, given \"dwar\", plays? dwarv\n", "Player 0, given \"drif\", plays? drift\n"
"Player 0, given \"dwarve\", plays? dwarves\n"
] ]
}, },
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"(1, 'dwarves')" "(1, 'drift')"
] ]
}, },
"execution_count": 22, "execution_count": 23,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@@ -619,7 +668,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 23, "execution_count": 24,
"metadata": { "metadata": {
"collapsed": false "collapsed": false
}, },
@@ -671,7 +720,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 24, "execution_count": 25,
"metadata": { "metadata": {
"collapsed": false "collapsed": false
}, },
@@ -699,7 +748,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 25, "execution_count": 26,
"metadata": { "metadata": {
"collapsed": false "collapsed": false
}, },
@@ -709,7 +758,7 @@
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"Outcomes (7) for player 0:\n", "Outcomes (7) for player 0:\n",
"Winners (7): naan nene ngultrum nirvanic nolo null nyctalopia\n" "Winners (7): nays nene ngultrum nirvanic nolo null nyctalopia\n"
] ]
} }
], ],
@@ -732,7 +781,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 26, "execution_count": 27,
"metadata": { "metadata": {
"collapsed": false "collapsed": false
}, },
@@ -743,11 +792,11 @@
"text": [ "text": [
"Outcomes (55) for player 1:\n", "Outcomes (55) for player 1:\n",
"Winners (55): aah aal aargh aas bwana cwm drake dreck drift droit\n", "Winners (55): aah aal aargh aas bwana cwm drake dreck drift droit\n",
"drunk dry ewe fjeld fjord gjetost hmm ihram jnana kwashiorkor llano\n", "druse dry ewe fjeld fjord gjetost hmm ihram jnana kwashiorkor llama\n",
"mho nth oquassa praam prequel prill pro prurigo pry qua quell quiff\n", "mho nth oquassa praam prequel prill pro prurigo pry qua quell quiff\n",
"quomodo qursh rhamnus rheum rhizoid rho rhumb rhyolitic sjambok\n", "quondam qursh rhamnus rheum rhizoid rho rhumb rhyolitic sjambok\n",
"tchotchke uhlan vying wrack wreck wrist wrong wrung wry xanthic\n", "tchotchke uhlan vroom wrang wrest wrick wrong wrung wry xanthic\n",
"xanthin ycleped zucchetto\n" "xanthin yperite zucchetto\n"
] ]
} }
], ],
@@ -766,7 +815,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 27, "execution_count": 28,
"metadata": { "metadata": {
"collapsed": false "collapsed": false
}, },
@@ -778,15 +827,15 @@
"Outcomes (85) for player 1:\n", "Outcomes (85) for player 1:\n",
"Losers (4): hybrid hyphen hyte ngultrum\n", "Losers (4): hybrid hyphen hyte ngultrum\n",
"Winners (81): aquiculture aquifer aquilegia aquiver bwana cnidarian\n", "Winners (81): aquiculture aquifer aquilegia aquiver bwana cnidarian\n",
"drake dreck drift droit drunk drywall eschatologies eschatology\n", "drake dreck drift droit druse drywall eschatologies eschatology\n",
"escheat eserine eskar esophagus esplanade esquire esquiring essay\n", "escheat eserine eskar esophagus esplanade esquire esquiring essay\n",
"estuarine estuary esurience fjeld fjord gjetost hyaenic hydatid hyena\n", "estuarine estuary esurience fjeld fjord gjetost hyaenic hydatid hyena\n",
"hyenine hyenoid hygeist hying hylozoism hylozoist hymen hyoid hypha\n", "hyenine hyenoid hygeist hying hylozoism hylozoist hymen hyoid hypha\n",
"hyraces hyrax hyson ihram jnana kwashiorkor llano mbira ngwee oquassa\n", "hyraces hyrax hyson ihram jnana kwashiorkor llama mbira ngwee oquassa\n",
"praam prequel prill proof prurigo pryer quaff quell quiff quomodo\n", "plaza plethoric plica plonk pluck plyer quaff quell quiff quondam\n",
"qursh rhamnus rheum rhizoid rhomb rhumb rhyolitic sjambok tchotchke\n", "qursh rhamnus rheum rhizoid rhomb rhumb rhyolitic sjambok tchotchke\n",
"uhlan vying wrack wreck wrist wrong wrung wryly xanthic xanthin\n", "uhlan vroom wrang wrest wrick wrong wrung wryly xanthic xanthin\n",
"ycleped zucchetto\n" "yperite zucchetto\n"
] ]
} }
], ],
@@ -812,7 +861,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 28, "execution_count": 29,
"metadata": { "metadata": {
"collapsed": false "collapsed": false
}, },
@@ -840,19 +889,19 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 29, "execution_count": 30,
"metadata": { "metadata": {
"collapsed": false "collapsed": false
}, },
"outputs": [], "outputs": [],
"source": [ "source": [
"enable1s = SuperVocabulary(enable1.words)\n", "enable1s = SuperVocabulary(enable1.words, 3)\n",
"enable1_4s = SuperVocabulary(enable1.words, 4)" "enable1_4s = SuperVocabulary(enable1.words, 4)"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 30, "execution_count": 31,
"metadata": { "metadata": {
"collapsed": false "collapsed": false
}, },
@@ -880,7 +929,7 @@
" 'tcrop'}" " 'tcrop'}"
] ]
}, },
"execution_count": 30, "execution_count": 31,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@@ -892,7 +941,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 31, "execution_count": 32,
"metadata": { "metadata": {
"collapsed": false "collapsed": false
}, },
@@ -900,10 +949,10 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"True" "'y'"
] ]
}, },
"execution_count": 31, "execution_count": 32,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@@ -916,7 +965,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 32, "execution_count": 33,
"metadata": { "metadata": {
"collapsed": false "collapsed": false
}, },
@@ -924,10 +973,10 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"True" "'y'"
] ]
}, },
"execution_count": 32, "execution_count": 33,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@@ -947,7 +996,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 33, "execution_count": 34,
"metadata": { "metadata": {
"collapsed": false "collapsed": false
}, },
@@ -968,7 +1017,7 @@
"(1, 'cumquat')" "(1, 'cumquat')"
] ]
}, },
"execution_count": 33, "execution_count": 34,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@@ -991,7 +1040,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 34, "execution_count": 35,
"metadata": { "metadata": {
"collapsed": true "collapsed": true
}, },
@@ -1003,13 +1052,14 @@
" def legal_plays(self, fragment):\n", " def legal_plays(self, fragment):\n",
" \"All plays that form a valid infix; optionally reverse fragment first.\"\n", " \"All plays that form a valid infix; optionally reverse fragment first.\"\n",
" def all4(frag, L): \n", " def all4(frag, L): \n",
" return (frag + L, frag[::-1] + L, L + frag, L + frag[::-1])\n", " return (frag + L, frag[::-1] + L, \n",
" L + frag, L + frag[::-1])\n",
" return {p for L in alphabet for p in all4(fragment, L)} & self.fragments" " return {p for L in alphabet for p in all4(fragment, L)} & self.fragments"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 35, "execution_count": 36,
"metadata": { "metadata": {
"collapsed": true "collapsed": true
}, },
@@ -1021,7 +1071,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 36, "execution_count": 37,
"metadata": { "metadata": {
"collapsed": false "collapsed": false
}, },
@@ -1055,7 +1105,7 @@
" 'tcrop'}" " 'tcrop'}"
] ]
}, },
"execution_count": 36, "execution_count": 37,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@@ -1074,7 +1124,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 37, "execution_count": 38,
"metadata": { "metadata": {
"collapsed": false "collapsed": false
}, },
@@ -1084,16 +1134,19 @@
"\n", "\n",
"@lru_cache(None)\n", "@lru_cache(None)\n",
"def win(vocab, fragment=''):\n", "def win(vocab, fragment=''):\n",
" \"Does the player whose turn it is have a forced win?\"\n", " \"\"\"Does the current player have a forced win? \n",
" return (fragment in vocab.words or \n", " Return fragment if the player has already won, or return a play that forces a win.\"\"\"\n",
" fragment not in vocab.fragments or\n", " if fragment in vocab.words or fragment not in vocab.fragments:\n",
" any(not win(vocab, play) \n", " return fragment\n",
" for play in vocab.legal_plays(fragment)))" " for play in vocab.legal_plays(fragment):\n",
" if not win(vocab, play):\n",
" return play \n",
" return False"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 38, "execution_count": 39,
"metadata": { "metadata": {
"collapsed": false "collapsed": false
}, },
@@ -1101,10 +1154,10 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"True" "'y'"
] ]
}, },
"execution_count": 38, "execution_count": 39,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@@ -1115,7 +1168,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 39, "execution_count": 40,
"metadata": { "metadata": {
"collapsed": false "collapsed": false
}, },
@@ -1123,10 +1176,10 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"True" "'y'"
] ]
}, },
"execution_count": 39, "execution_count": 40,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@@ -1144,7 +1197,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 40, "execution_count": 41,
"metadata": { "metadata": {
"collapsed": false "collapsed": false
}, },
@@ -1153,23 +1206,25 @@
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"Player 0, given \"\", plays \"d\".\n", "Player 0, given \"\", plays \"y\".\n",
"Player 1, given \"d\", plays \"db\".\n", "Player 1, given \"y\", plays \"zy\".\n",
"Player 0, given \"db\", plays \"udb\".\n", "Player 0, given \"zy\", plays \"izy\".\n",
"Player 1, given \"udb\", plays \"bdui\".\n", "Player 1, given \"izy\", plays \"mizy\".\n",
"Player 0, given \"bdui\", plays \"bduin\".\n", "Player 0, given \"mizy\", plays \"mizyg\".\n",
"Player 1, given \"bduin\", plays \"ubduin\".\n", "Player 1, given \"mizyg\", plays \"emizyg\".\n",
"Player 0, given \"ubduin\", plays \"subduin\".\n", "Player 0, given \"emizyg\", plays \"emizygo\".\n",
"Player 1, given \"subduin\", plays \"subduing\".\n" "Player 1, given \"emizygo\", plays \"emizygou\".\n",
"Player 0, given \"emizygou\", plays \"hemizygou\".\n",
"Player 1, given \"hemizygou\", plays \"hemizygous\".\n"
] ]
}, },
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"(0, 'subduing')" "(0, 'hemizygous')"
] ]
}, },
"execution_count": 40, "execution_count": 41,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@@ -1187,7 +1242,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 41, "execution_count": 42,
"metadata": { "metadata": {
"collapsed": false "collapsed": false
}, },
@@ -1198,7 +1253,7 @@
"[387878, 387844, 1076434, 1076431, 1076434, 1076431]" "[387878, 387844, 1076434, 1076431, 1076434, 1076431]"
] ]
}, },
"execution_count": 41, "execution_count": 42,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@@ -1216,7 +1271,7 @@
"\n", "\n",
"Here's a summary of what we have learned. (*Note:* the bold **qursh** means it is a losing word):\n", "Here's a summary of what we have learned. (*Note:* the bold **qursh** means it is a losing word):\n",
"\n", "\n",
"| Variant \t| Shortest \t| Winner \t| First Player Outcomes | 2nd Outcomes | Fragments\n", "| Variant \t| Shortest \t| Winner \t| First Player Forced Outcomes | 2nd Outcomes | Fragments\n",
"|----\t|---:\t |---\t |--- |--- |---:\n", "|----\t|---:\t |---\t |--- |--- |---:\n",
"| Ghost | 3 \t | Second \t| qaid qiviut qoph **qursh** qurush qwerty | 55 words | 387,878\n", "| Ghost | 3 \t | Second \t| qaid qiviut qoph **qursh** qurush qwerty | 55 words | 387,878\n",
"| Ghost | 4 \t | First \t| naan nene ngultrum nirvanic nolo null nyctalopia | 85 words | 387,844\n", "| Ghost | 4 \t | First \t| naan nene ngultrum nirvanic nolo null nyctalopia | 85 words | 387,844\n",
@@ -1237,15 +1292,6 @@
"- **Xghost:** In *Xghost*, a letter can be added anywhere, so from the fragment `'era'` you could play `'erba'`.\n", "- **Xghost:** In *Xghost*, a letter can be added anywhere, so from the fragment `'era'` you could play `'erba'`.\n",
"- **Spook:** In *Spook*, letters can be rearranged before adding one, so from the fragment `'era'` you could play `'bear'`." "- **Spook:** In *Spook*, letters can be rearranged before adding one, so from the fragment `'era'` you could play `'bear'`."
] ]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": []
} }
], ],
"metadata": { "metadata": {