Add files via upload
This commit is contained in:
parent
1438c333bd
commit
c882c9d71b
640
Ghost.ipynb
640
Ghost.ipynb
@ -240,7 +240,7 @@
|
||||
"\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",
|
||||
"- 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 made something that is not the beginning of an actual word, and lost).\n",
|
||||
"- The fragment is not a legal fragment (meaning the other player formed a non-prefix, and lost).\n",
|
||||
"- At least one of the legal plays puts the opponent in a position from which they *cannot* win.\n",
|
||||
"\n",
|
||||
"The function `win(vocab, fragment)` implements this idea, returning `True` if the current player can force a win."
|
||||
@ -395,7 +395,7 @@
|
||||
"**No.** The game is a win for the second player, not the first.\n",
|
||||
"This agrees with [xkcd](https://xkcd.com/)'s Randall Monroe, who [says](https://blog.xkcd.com/2007/12/31/ghost/) *\"I hear if you use the Scrabble wordlist, it’s always a win for the second player.\"*\n",
|
||||
"\n",
|
||||
"But ... Wikipedia says that the minimum word length can be \"three or four letter.\" In `enable1` we included three-letter words, which is a disadvantage for the first player, who has to avoid all those three letter possibilities. What if we eliminate the three-letter words?"
|
||||
"But ... Wikipedia says that the minimum word length can be \"three or four letters.\" In `enable1` the limit was three; let's try agian with a limit of four:"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -426,7 +426,7 @@
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"**Yes.** The first player can win in this case. 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 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).\""
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -435,7 +435,7 @@
|
||||
"source": [
|
||||
"# Playing the Game: Strategies\n",
|
||||
"\n",
|
||||
"We define a *strategy* as a function that is given a vocabulary and a fragment as arguments, and returns a legal play. Below we define `rational` (a strategy that wins whenever it is possible to do so) and `ask` (a strategy factory that returns a strategy that, when called, will ask the named person to input a fragment)."
|
||||
"We define a *strategy* as a function that is given a vocabulary and a fragment as arguments, and returns a legal play. Below we define `rational`, a strategy that wins whenever it is possible to do so and plays randomly otherwise, and `ask` (a strategy factory that returns a strategy that, when called, will ask the named person to input a fragment)."
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -449,14 +449,17 @@
|
||||
"import random\n",
|
||||
"\n",
|
||||
"def rational(vocab, fragment): \n",
|
||||
" \"Select a play that makes opponent not win (if there is one), 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 next((play for play in plays if not win(vocab, play)),\n",
|
||||
" random.choice(plays))\n",
|
||||
" return first(p for p in plays if not win(vocab, p)) or random.choice(plays)\n",
|
||||
"\n",
|
||||
"def ask(name):\n",
|
||||
" \"Return a strategy that asks for the next letter.\"\n",
|
||||
" return (lambda _, fragment: input('Player {}, given \"{}\", plays? '.format(name, fragment)))"
|
||||
" return (lambda _, fragment: input('Player {}, given \"{}\", plays? '.format(name, fragment)))\n",
|
||||
"\n",
|
||||
"def first(iterable, default=None):\n",
|
||||
" \"Return the first element of iterable, or default if iterable is empty.\"\n",
|
||||
" return next(iter(iterable), default)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -476,7 +479,7 @@
|
||||
"source": [
|
||||
"from itertools import cycle\n",
|
||||
"\n",
|
||||
"def play(vocab, *strategies, verbose=True):\n",
|
||||
"def play(vocab, *strategies, verbose=False):\n",
|
||||
" \"Return (winner, final_fragment) for a game of Ghost between these strategies.\"\n",
|
||||
" fragment = ''\n",
|
||||
" for (p, strategy) in cycle(enumerate(strategies)): # p is the player number\n",
|
||||
@ -498,19 +501,10 @@
|
||||
"collapsed": false
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Player 0, given \"\", plays \"w\".\n",
|
||||
"Player 1, given \"w\", plays \"wr\".\n",
|
||||
"Player 0, given \"wr\", plays \"wry\".\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"(1, 'wry')"
|
||||
"(1, 'ycleped')"
|
||||
]
|
||||
},
|
||||
"execution_count": 19,
|
||||
@ -529,20 +523,19 @@
|
||||
"collapsed": false
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Player 0, given \"\", plays \"n\".\n",
|
||||
"Player 1, given \"n\", plays \"ni\".\n",
|
||||
"Player 0, given \"ni\", plays \"nig\".\n",
|
||||
"Player 1, given \"nig\", plays \"nigh\".\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"(0, 'nigh')"
|
||||
"[(1, 'bwana'),\n",
|
||||
" (1, 'bwana'),\n",
|
||||
" (1, 'bwana'),\n",
|
||||
" (1, 'bwana'),\n",
|
||||
" (1, 'bwana'),\n",
|
||||
" (1, 'dwarf'),\n",
|
||||
" (1, 'hmm'),\n",
|
||||
" (1, 'llano'),\n",
|
||||
" (1, 'oquassa'),\n",
|
||||
" (1, 'whiff')]"
|
||||
]
|
||||
},
|
||||
"execution_count": 20,
|
||||
@ -551,7 +544,9 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"play(enable1_4, rational, rational)"
|
||||
"# Does player 1 win every time?\n",
|
||||
"\n",
|
||||
"sorted(play(enable1, rational, rational, verbose=False) for _ in range(10))"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -560,22 +555,37 @@
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"(0, 'null')"
|
||||
]
|
||||
},
|
||||
"execution_count": 21,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"play(enable1_4, rational, rational)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 22,
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Player 0, given \"\", plays? d\n",
|
||||
"Player 0, given \"\", plays \"d\".\n",
|
||||
"Player 1, given \"d\", plays \"dw\".\n",
|
||||
"Player 0, given \"dw\", plays? dwa\n",
|
||||
"Player 0, given \"dw\", plays \"dwa\".\n",
|
||||
"Player 1, given \"dwa\", plays \"dwar\".\n",
|
||||
"Player 0, given \"dwar\", plays? dwarv\n",
|
||||
"Player 0, given \"dwar\", plays \"dwarv\".\n",
|
||||
"Player 1, given \"dwarv\", plays \"dwarve\".\n",
|
||||
"Player 0, given \"dwarve\", plays? dwarves\n",
|
||||
"Player 0, given \"dwarve\", plays \"dwarves\".\n"
|
||||
"Player 0, given \"dwarve\", plays? dwarves\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -584,7 +594,7 @@
|
||||
"(1, 'dwarves')"
|
||||
]
|
||||
},
|
||||
"execution_count": 21,
|
||||
"execution_count": 22,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
@ -600,7 +610,7 @@
|
||||
"# Minimizing Possible Outcomes\n",
|
||||
"\n",
|
||||
"Now we know how to play perfectly, if we have a computer handy to execute the strategy.\n",
|
||||
"But can we summarize the strategy into a form that is small enough that a human can memorize it? I will define the function `outcomes(vocab, fragment, player)` to return a set of words that are the possible outcomes of a game, where the opponent can use any strategy whatsoever, but `player` uses a strategy that is:\n",
|
||||
"But can we summarize the strategy into a form that is small enough that a human can memorize it? I will define the function `outcomes(vocab, fragment, player)` to return a set of all the words that are possible outcomes of a game, where the opponent can use any strategy whatsoever, but `player` uses a strategy that is:\n",
|
||||
"\n",
|
||||
"- *Rational*: plays towards a forced win whenever there is one.\n",
|
||||
"- *Exploitive*: otherwise tries to give the opponent an opportunity to make a mistake that can be exploited.\n",
|
||||
@ -609,12 +619,14 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 22,
|
||||
"execution_count": 23,
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import textwrap \n",
|
||||
"\n",
|
||||
"def outcomes(vocab, fragment, player):\n",
|
||||
" \"The smallest set of outcome words, if player tries to win, and make the set small.\"\n",
|
||||
" if fragment in vocab.words:\n",
|
||||
@ -633,7 +645,19 @@
|
||||
" lossiness = (0 if all(winner(word) == player for word in words) else\n",
|
||||
" 1 if any(winner(word) == player for word in words) else\n",
|
||||
" 2) \n",
|
||||
" return (lossiness, len(words), sum(map(len, words)))"
|
||||
" return (lossiness, len(words), sum(map(len, words)))\n",
|
||||
"\n",
|
||||
"def report_outcomes(vocab, player):\n",
|
||||
" \"Find minimal outcomes for player; print info.\"\n",
|
||||
" oc = outcomes(vocab, '', player)\n",
|
||||
" winners = {w for w in oc if winner(w) == player}\n",
|
||||
" def fill(label, words): \n",
|
||||
" if words:\n",
|
||||
" text = '{} ({}): {}'.format(label, len(words), ' '.join(sorted(words)))\n",
|
||||
" print(textwrap.fill(text))\n",
|
||||
" print('Outcomes ({}) for player {}:'.format(len(oc), player))\n",
|
||||
" fill('Losers', oc - winners)\n",
|
||||
" fill('Winners', winners)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -647,24 +671,23 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 23,
|
||||
"execution_count": 24,
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'qaid', 'qiviut', 'qoph', 'qursh', 'qurush', 'qwerty'}"
|
||||
]
|
||||
},
|
||||
"execution_count": 23,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Outcomes (6) for player 0:\n",
|
||||
"Losers (1): qursh\n",
|
||||
"Winners (5): qaid qiviut qoph qurush qwerty\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"outcomes(enable1, '', 0)"
|
||||
"report_outcomes(enable1, 0)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -676,24 +699,22 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 24,
|
||||
"execution_count": 25,
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'nays', 'nene', 'ngultrum', 'nirvanic', 'nolo', 'null', 'nyctalopia'}"
|
||||
]
|
||||
},
|
||||
"execution_count": 24,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Outcomes (7) for player 0:\n",
|
||||
"Winners (7): naan nene ngultrum nirvanic nolo null nyctalopia\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"outcomes(enable1_4, '', 0)"
|
||||
"report_outcomes(enable1_4, 0)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -709,82 +730,6 @@
|
||||
"With `enable1` we already know that player 1 can force a win, so all the words in the `outcomes` set will have odd length:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 25,
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'aah',\n",
|
||||
" 'aal',\n",
|
||||
" 'aargh',\n",
|
||||
" 'aas',\n",
|
||||
" 'bwana',\n",
|
||||
" 'cwm',\n",
|
||||
" 'drank',\n",
|
||||
" 'dreck',\n",
|
||||
" 'drift',\n",
|
||||
" 'droit',\n",
|
||||
" 'druid',\n",
|
||||
" 'dry',\n",
|
||||
" 'ewe',\n",
|
||||
" 'fjeld',\n",
|
||||
" 'fjord',\n",
|
||||
" 'gjetost',\n",
|
||||
" 'hmm',\n",
|
||||
" 'ihram',\n",
|
||||
" 'jnana',\n",
|
||||
" 'kwashiorkor',\n",
|
||||
" 'llano',\n",
|
||||
" 'mho',\n",
|
||||
" 'nth',\n",
|
||||
" 'oquassa',\n",
|
||||
" 'prahu',\n",
|
||||
" 'prequel',\n",
|
||||
" 'prill',\n",
|
||||
" 'pro',\n",
|
||||
" 'prurigo',\n",
|
||||
" 'pry',\n",
|
||||
" 'qua',\n",
|
||||
" 'quell',\n",
|
||||
" 'quiff',\n",
|
||||
" 'quondam',\n",
|
||||
" 'qursh',\n",
|
||||
" 'rhamnus',\n",
|
||||
" 'rheum',\n",
|
||||
" 'rhizoid',\n",
|
||||
" 'rho',\n",
|
||||
" 'rhumb',\n",
|
||||
" 'rhyolitic',\n",
|
||||
" 'sjambok',\n",
|
||||
" 'tchotchke',\n",
|
||||
" 'uhlan',\n",
|
||||
" 'vying',\n",
|
||||
" 'wrang',\n",
|
||||
" 'wreck',\n",
|
||||
" 'wrist',\n",
|
||||
" 'wrong',\n",
|
||||
" 'wrung',\n",
|
||||
" 'wry',\n",
|
||||
" 'xanthic',\n",
|
||||
" 'xanthin',\n",
|
||||
" 'yttrium',\n",
|
||||
" 'zucchetto'}"
|
||||
]
|
||||
},
|
||||
"execution_count": 25,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"outcomes(enable1, '', 1)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 26,
|
||||
@ -793,25 +738,28 @@
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"(55, [])"
|
||||
]
|
||||
},
|
||||
"execution_count": 26,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Outcomes (55) for player 1:\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",
|
||||
"mho nth oquassa praam prequel prill pro prurigo pry qua quell quiff\n",
|
||||
"quomodo qursh rhamnus rheum rhizoid rho rhumb rhyolitic sjambok\n",
|
||||
"tchotchke uhlan vying wrack wreck wrist wrong wrung wry xanthic\n",
|
||||
"xanthin ycleped zucchetto\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"len(_), [w for w in _ if winner(w) == 0]"
|
||||
"report_outcomes(enable1, 1)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This says that player 1 can force the game towards one of these 55 words (none of which are losses). Memorize this list and you will never lose as player 1.\n",
|
||||
"Memorize this list and you will never lose as player 1.\n",
|
||||
"\n",
|
||||
"How about with the other vocabulary?"
|
||||
]
|
||||
@ -824,124 +772,26 @@
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'aquiculture',\n",
|
||||
" 'aquifer',\n",
|
||||
" 'aquilegia',\n",
|
||||
" 'aquiver',\n",
|
||||
" 'bwana',\n",
|
||||
" 'cnidarian',\n",
|
||||
" 'drank',\n",
|
||||
" 'dreck',\n",
|
||||
" 'drift',\n",
|
||||
" 'droit',\n",
|
||||
" 'druid',\n",
|
||||
" 'dryness',\n",
|
||||
" 'eschatologies',\n",
|
||||
" 'eschatology',\n",
|
||||
" 'escheat',\n",
|
||||
" 'eserine',\n",
|
||||
" 'eskar',\n",
|
||||
" 'esophagus',\n",
|
||||
" 'esplanade',\n",
|
||||
" 'esquire',\n",
|
||||
" 'esquiring',\n",
|
||||
" 'essay',\n",
|
||||
" 'estuarial',\n",
|
||||
" 'estuary',\n",
|
||||
" 'esurience',\n",
|
||||
" 'fjeld',\n",
|
||||
" 'fjord',\n",
|
||||
" 'gjetost',\n",
|
||||
" 'hyaenic',\n",
|
||||
" 'hybris',\n",
|
||||
" 'hydatid',\n",
|
||||
" 'hyena',\n",
|
||||
" 'hyenine',\n",
|
||||
" 'hyenoid',\n",
|
||||
" 'hygeist',\n",
|
||||
" 'hying',\n",
|
||||
" 'hylozoism',\n",
|
||||
" 'hylozoist',\n",
|
||||
" 'hymen',\n",
|
||||
" 'hyoid',\n",
|
||||
" 'hypha',\n",
|
||||
" 'hyphen',\n",
|
||||
" 'hyraces',\n",
|
||||
" 'hyrax',\n",
|
||||
" 'hyson',\n",
|
||||
" 'hyte',\n",
|
||||
" 'ihram',\n",
|
||||
" 'jnana',\n",
|
||||
" 'kwashiorkor',\n",
|
||||
" 'llano',\n",
|
||||
" 'mbira',\n",
|
||||
" 'ngultrum',\n",
|
||||
" 'ngwee',\n",
|
||||
" 'oquassa',\n",
|
||||
" 'plaza',\n",
|
||||
" 'plethoric',\n",
|
||||
" 'plica',\n",
|
||||
" 'plonk',\n",
|
||||
" 'pluck',\n",
|
||||
" 'plyer',\n",
|
||||
" 'quack',\n",
|
||||
" 'quell',\n",
|
||||
" 'quiff',\n",
|
||||
" 'quondam',\n",
|
||||
" 'qursh',\n",
|
||||
" 'rhamnus',\n",
|
||||
" 'rheum',\n",
|
||||
" 'rhizoid',\n",
|
||||
" 'rhomb',\n",
|
||||
" 'rhumb',\n",
|
||||
" 'rhyolitic',\n",
|
||||
" 'sjambok',\n",
|
||||
" 'tchotchke',\n",
|
||||
" 'uhlan',\n",
|
||||
" 'vying',\n",
|
||||
" 'wrang',\n",
|
||||
" 'wreck',\n",
|
||||
" 'wrist',\n",
|
||||
" 'wrong',\n",
|
||||
" 'wrung',\n",
|
||||
" 'wryly',\n",
|
||||
" 'xanthic',\n",
|
||||
" 'xanthin',\n",
|
||||
" 'yttrium',\n",
|
||||
" 'zucchetto'}"
|
||||
]
|
||||
},
|
||||
"execution_count": 27,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Outcomes (85) for player 1:\n",
|
||||
"Losers (4): hybrid hyphen hyte ngultrum\n",
|
||||
"Winners (81): aquiculture aquifer aquilegia aquiver bwana cnidarian\n",
|
||||
"drake dreck drift droit drunk drywall eschatologies eschatology\n",
|
||||
"escheat eserine eskar esophagus esplanade esquire esquiring essay\n",
|
||||
"estuarine estuary esurience fjeld fjord gjetost hyaenic hydatid hyena\n",
|
||||
"hyenine hyenoid hygeist hying hylozoism hylozoist hymen hyoid hypha\n",
|
||||
"hyraces hyrax hyson ihram jnana kwashiorkor llano mbira ngwee oquassa\n",
|
||||
"praam prequel prill proof prurigo pryer quaff quell quiff quomodo\n",
|
||||
"qursh rhamnus rheum rhizoid rhomb rhumb rhyolitic sjambok tchotchke\n",
|
||||
"uhlan vying wrack wreck wrist wrong wrung wryly xanthic xanthin\n",
|
||||
"ycleped zucchetto\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"outcomes(enable1_4, '', 1)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 28,
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"(85, ['ngultrum', 'hyte', 'hyphen', 'hybris'])"
|
||||
]
|
||||
},
|
||||
"execution_count": 28,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"len(_), [w for w in _ if winner(w) == 0]"
|
||||
"report_outcomes(enable1_4, 1)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -962,7 +812,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 29,
|
||||
"execution_count": 28,
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
@ -978,7 +828,7 @@
|
||||
" \n",
|
||||
" def legal_plays(self, fragment):\n",
|
||||
" \"All plays (adding a letter to fragment) that form a valid infix.\"\n",
|
||||
" return {play for L in alphabet for play in (fragment + L, L + fragment)} & self.fragments"
|
||||
" return {p for L in alphabet for p in (fragment + L, L + fragment)} & self.fragments"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -990,7 +840,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 30,
|
||||
"execution_count": 29,
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
@ -1000,6 +850,46 @@
|
||||
"enable1_4s = SuperVocabulary(enable1.words, 4)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 30,
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'acrop',\n",
|
||||
" 'cropa',\n",
|
||||
" 'crope',\n",
|
||||
" 'croph',\n",
|
||||
" 'cropi',\n",
|
||||
" 'cropl',\n",
|
||||
" 'cropo',\n",
|
||||
" 'cropp',\n",
|
||||
" 'cropr',\n",
|
||||
" 'crops',\n",
|
||||
" 'cropt',\n",
|
||||
" 'cropu',\n",
|
||||
" 'cropy',\n",
|
||||
" 'ecrop',\n",
|
||||
" 'icrop',\n",
|
||||
" 'ncrop',\n",
|
||||
" 'rcrop',\n",
|
||||
" 'tcrop'}"
|
||||
]
|
||||
},
|
||||
"execution_count": 30,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Example legal plays\n",
|
||||
"enable1s.legal_plays('crop')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 31,
|
||||
@ -1010,19 +900,7 @@
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'atresse',\n",
|
||||
" 'ctresse',\n",
|
||||
" 'etresse',\n",
|
||||
" 'itresse',\n",
|
||||
" 'ntresse',\n",
|
||||
" 'otresse',\n",
|
||||
" 'ptresse',\n",
|
||||
" 'rtresse',\n",
|
||||
" 'stresse',\n",
|
||||
" 'tressed',\n",
|
||||
" 'tressel',\n",
|
||||
" 'tresses',\n",
|
||||
" 'ttresse'}"
|
||||
"True"
|
||||
]
|
||||
},
|
||||
"execution_count": 31,
|
||||
@ -1031,8 +909,9 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Example legal plays\n",
|
||||
"enable1s.legal_plays('tresse')"
|
||||
"# Can the first player win in SuperGhost with 3-letter words?\n",
|
||||
"\n",
|
||||
"win(enable1s)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -1053,30 +932,6 @@
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Can the first player win in SuperGhost with 3-letter words?\n",
|
||||
"\n",
|
||||
"win(enable1s)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 33,
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"True"
|
||||
]
|
||||
},
|
||||
"execution_count": 33,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# How about with a 4-letter limit?\n",
|
||||
"\n",
|
||||
@ -1092,7 +947,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 34,
|
||||
"execution_count": 33,
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
@ -1102,28 +957,18 @@
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Player 0, given \"\", plays? q\n",
|
||||
"Player 0, given \"\", plays \"q\".\n",
|
||||
"Player 1, given \"q\", plays \"zq\".\n",
|
||||
"Player 0, given \"zq\", plays? zqu\n",
|
||||
"Player 0, given \"zq\", plays \"zqu\".\n",
|
||||
"Player 1, given \"zqu\", plays \"zqui\".\n",
|
||||
"Player 0, given \"zqui\", plays? zquit\n",
|
||||
"Player 0, given \"zqui\", plays \"zquit\".\n",
|
||||
"Player 1, given \"zquit\", plays \"zquite\".\n",
|
||||
"Player 0, given \"zquite\", plays? ezquite\n",
|
||||
"Player 0, given \"zquite\", plays \"ezquite\".\n",
|
||||
"Player 1, given \"ezquite\", plays \"ezquites\".\n",
|
||||
"Player 0, given \"ezquites\", plays? mezquites\n",
|
||||
"Player 0, given \"ezquites\", plays \"mezquites\".\n"
|
||||
"Player 0, given \"mq\", plays? mqu\n",
|
||||
"Player 0, given \"umqu\", plays? umqua\n",
|
||||
"Player 0, given \"cumqua\", plays? cumquat\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"(1, 'mezquites')"
|
||||
"(1, 'cumquat')"
|
||||
]
|
||||
},
|
||||
"execution_count": 34,
|
||||
"execution_count": 33,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
@ -1146,7 +991,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 35,
|
||||
"execution_count": 34,
|
||||
"metadata": {
|
||||
"collapsed": true
|
||||
},
|
||||
@ -1157,14 +1002,14 @@
|
||||
" \n",
|
||||
" def legal_plays(self, fragment):\n",
|
||||
" \"All plays that form a valid infix; optionally reverse fragment first.\"\n",
|
||||
" return {play for L in alphabet \n",
|
||||
" for play in (fragment + L, fragment[::-1] + L,\n",
|
||||
" L + fragment, L + fragment[::-1])} & self.fragments"
|
||||
" def all4(frag, L): \n",
|
||||
" return (frag + L, frag[::-1] + L, L + frag, L + frag[::-1])\n",
|
||||
" return {p for L in alphabet for p in all4(fragment, L)} & self.fragments"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 36,
|
||||
"execution_count": 35,
|
||||
"metadata": {
|
||||
"collapsed": true
|
||||
},
|
||||
@ -1176,7 +1021,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 37,
|
||||
"execution_count": 36,
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
@ -1184,43 +1029,52 @@
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'atresse',\n",
|
||||
" 'ctresse',\n",
|
||||
" 'dessert',\n",
|
||||
" 'esserts',\n",
|
||||
" 'etresse',\n",
|
||||
" 'itresse',\n",
|
||||
" 'ntresse',\n",
|
||||
" 'otresse',\n",
|
||||
" 'ptresse',\n",
|
||||
" 'rtresse',\n",
|
||||
" 'stresse',\n",
|
||||
" 'tressed',\n",
|
||||
" 'tressel',\n",
|
||||
" 'tresses',\n",
|
||||
" 'ttresse'}"
|
||||
"{'acrop',\n",
|
||||
" 'cropa',\n",
|
||||
" 'crope',\n",
|
||||
" 'croph',\n",
|
||||
" 'cropi',\n",
|
||||
" 'cropl',\n",
|
||||
" 'cropo',\n",
|
||||
" 'cropp',\n",
|
||||
" 'cropr',\n",
|
||||
" 'crops',\n",
|
||||
" 'cropt',\n",
|
||||
" 'cropu',\n",
|
||||
" 'cropy',\n",
|
||||
" 'ecrop',\n",
|
||||
" 'icrop',\n",
|
||||
" 'iporc',\n",
|
||||
" 'ncrop',\n",
|
||||
" 'nporc',\n",
|
||||
" 'porce',\n",
|
||||
" 'porch',\n",
|
||||
" 'porci',\n",
|
||||
" 'porcu',\n",
|
||||
" 'rcrop',\n",
|
||||
" 'tcrop'}"
|
||||
]
|
||||
},
|
||||
"execution_count": 37,
|
||||
"execution_count": 36,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Example legal plays\n",
|
||||
"enable1sd.legal_plays('tresse')"
|
||||
"enable1sd.legal_plays('crop')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now we should check who wins. But when I tried `win(enable1sd)`, I didn't get an answer within the first minute; there are just too many paths to get to the same fragment, so we are repeating a lot of work. The standard response to this problem is a `lru_cache`, which brings the time down to 2 seconds."
|
||||
"Now we should check who wins. But I'm impateint: I tried `win(enable1sd)`, waited a minute, and it still hadn't returned, so I interrupted that computation and threw in a `lru_cache` decorator; which stops `win` from wasting time repeating the same computation over and over. This brings the time down to about 2 seconds."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 38,
|
||||
"execution_count": 37,
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
@ -1237,6 +1091,28 @@
|
||||
" for play in vocab.legal_plays(fragment)))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 38,
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"True"
|
||||
]
|
||||
},
|
||||
"execution_count": 38,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"win(enable1sd)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 39,
|
||||
@ -1255,28 +1131,6 @@
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"win(enable1sd)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 40,
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"True"
|
||||
]
|
||||
},
|
||||
"execution_count": 40,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"win(enable1_4sd)"
|
||||
]
|
||||
@ -1290,7 +1144,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 41,
|
||||
"execution_count": 40,
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
@ -1299,29 +1153,29 @@
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Player 0, given \"\", plays \"b\".\n",
|
||||
"Player 1, given \"b\", plays \"lb\".\n",
|
||||
"Player 0, given \"lb\", plays \"hlb\".\n",
|
||||
"Player 1, given \"hlb\", plays \"ahlb\".\n",
|
||||
"Player 0, given \"ahlb\", plays \"fahlb\".\n",
|
||||
"Player 1, given \"fahlb\", plays \"fahlba\".\n",
|
||||
"Player 0, given \"fahlba\", plays \"fahlban\".\n",
|
||||
"Player 1, given \"fahlban\", plays \"fahlband\".\n"
|
||||
"Player 0, given \"\", plays \"d\".\n",
|
||||
"Player 1, given \"d\", plays \"db\".\n",
|
||||
"Player 0, given \"db\", plays \"udb\".\n",
|
||||
"Player 1, given \"udb\", plays \"bdui\".\n",
|
||||
"Player 0, given \"bdui\", plays \"bduin\".\n",
|
||||
"Player 1, given \"bduin\", plays \"ubduin\".\n",
|
||||
"Player 0, given \"ubduin\", plays \"subduin\".\n",
|
||||
"Player 1, given \"subduin\", plays \"subduing\".\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"(0, 'fahlband')"
|
||||
"(0, 'subduing')"
|
||||
]
|
||||
},
|
||||
"execution_count": 41,
|
||||
"execution_count": 40,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"play(enable1sd, rational, rational)"
|
||||
"play(enable1sd, rational, rational, verbose=True)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -1333,7 +1187,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 42,
|
||||
"execution_count": 41,
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
@ -1344,13 +1198,14 @@
|
||||
"[387878, 387844, 1076434, 1076431, 1076434, 1076431]"
|
||||
]
|
||||
},
|
||||
"execution_count": 42,
|
||||
"execution_count": 41,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"[len(v.fragments) for v in [enable1, enable1_4, enable1s, enable1_4s, enable1sd, enable1_4sd]]"
|
||||
"[len(v.fragments) \n",
|
||||
" for v in [enable1, enable1_4, enable1s, enable1_4s, enable1sd, enable1_4sd]]"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -1376,12 +1231,21 @@
|
||||
"\n",
|
||||
"- **Exploitation:** What are some good strategies when you are not guaranteed to win, to exploit an imperfect human opponent? Can you steer the game so that you win if the opponent is unfamiliar with some obscure word(s)? You might need a file of [word frequencies](http://norvig.com/ngrams/count_1w.txt).\n",
|
||||
"- **Security:** A strategy function could *cheat*, and modify `vocab.words`, inserting or deleting some crucial words to ensure victory. Can you harden `play` (and/or change `Vocabulary`) to protect against that?\n",
|
||||
"- **Saving Space:** Currently `Vocabulary` saves words and fragments that could never be reached in a game. For example, because `'the'` is a word that ends the game, we could never reach `'them'` or `'theme'` or `'thermoluminescences'`. Can you eliminate these redundant words/fragments?\n",
|
||||
"- **Pruning:** Currently `Vocabulary` saves words and fragments that could never be reached in a game. For example, because `'the'` is a word that ends the game, we could never reach `'them'` or `'theme'` or `'thermoluminescences'`. Can you prune away these redundant words/fragments?\n",
|
||||
"- **Multi-player:** `play(enable1, ask('A'), ask('B'), ask('C'))` will play a three-player game. But `rational` (along with `win` and `winner`) would no longer work, since they assume there are exactly two players. Can you alter them to allow *n* players?\n",
|
||||
"- **SuperGhost Summary:** Can you summarize a SuperGhost strategy in a way that a human can memorize?\n",
|
||||
"- **Xghost:** In *Xghost*, a letter can be added anywhere, so from the fragment `'ab'` you could play `'arb'`.\n",
|
||||
"- **Spook:** In *Spook*, letters can be rearranged before adding one, so from the fragment `'ab'` you could play `'bxa'`."
|
||||
"- **SuperGhost Summary:** Can you summarize a SuperGhost or SuperDuperGhost strategy in a way that a human can memorize?\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'`."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"collapsed": true
|
||||
},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
Loading…
Reference in New Issue
Block a user