xkcd-Name-Dominoes.ipynb

This commit is contained in:
Peter Norvig authored and GitHub committed 2018-03-27 00:03:13 -07:00
1 parent ada7fdf3c9
commit a912843c34
1 file changed
+153 -153
+153 -153
View File
@@ -9,10 +9,17 @@
"# `xkcd` Name Dominoes\n",
"\n",
"The March 21, 2018 `xkcd` comic (#1970) was [Name Dominoes](https://xkcd.com/1970/): domino tiles laid out as if in a game, but the tiles contain names of famous poeple rather than numbers. \n",
"In [dominoes](https://en.wikipedia.org/wiki/Dominoes) each tile has two halves, and a tile can be legally placed only if one half is adjacent to another tile with the same number/name, and neither half is adjacent to any tile with a different number/name. (The very first tile played in a game has no adjacent tiles, so it can be placed anywhere.) I will write a function to lay out tiles in a random, legal array. I'll start with two key data structures:\n",
"In [dominoes](https://en.wikipedia.org/wiki/Dominoes) each tile has two halves, and a tile can be legally placed only if one half is adjacent to another tile half with the same number/name, and neither half is adjacent to a tile half with a different number/name. (Exception: the very first tile can be placed anywhere.) \n",
"\n",
"- **`Tile`**: a tile is a 2-element tuple, like `('TIM', 'COOK')`, indicating the two halves.\n",
"- **`Board(w, h)`**: a [w × h] 2-dimensional array of locations; each location holds as a value one half of a tile, or it can be `empty`, or it can be a `border`, meaning nothing can be placed there."
"I will write a function to lay out tiles in a random, legal array. First, the key data structures:\n",
"\n",
"- **`Tile`**: a tile is a 2-element tuple of names, like `('TIM', 'COOK')`.\n",
"The tile `('FRANK LLOYD', 'WRIGHT')` has a space in the first name.\n",
"- **`Name`**: a name (first name or last name) is a string.\n",
"- **`Board(width, height)`**: a mapping of locations to names.\n",
"Initially, all width × height squares on the board are `empty`, but when we put a tile on the board,\n",
"the first name covers one location and the last name covers an adjacent location, e.g. `board[0, 0], board[0, 1] = ('TIM', 'COOK')`.\n",
"- **`Location`**: a location is an `(x, y)` pair of coordinates for a square on the board."
]
},
{
@@ -21,21 +28,27 @@
"metadata": {},
"outputs": [],
"source": [
"empty = ' '\n",
"border = '--'\n",
"empty = None # An empty square\n",
"\n",
"class Board(list):\n",
" \"A board is a 2d array of values.\"\n",
" \n",
" def __init__(self, width=16, height=24): \n",
" \"Initialize a [width × height] array of `empty`, surrounded by `border`\"\n",
" def rows (r, val): \n",
" return [[border] + width * [val] + [border] for _ in range(r)]\n",
" self[:] = rows(1, border) + rows(height, empty) + rows(1, border)\n",
" \n",
" def get(self, loc): return self[loc[1]][loc[0]]\n",
" \n",
" def put(self, loc, value): self[loc[1]][loc[0]] = value"
"class Board(dict):\n",
" \"A mapping from location to name.\"\n",
" def __init__(self, width=16, height=24): self.width, self.height = width, height\n",
" def __missing__(self, loc): return empty"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now I need a strategy to fill the board with tiles. I will randomly place tiles one at a time, and to make things easier I will *not* consider removing a tile from the board and backtracking. Some more concepts and functions:\n",
"\n",
"- **`frontier`**: I'll maintain a *frontier*, a set of locations that are adjacent to tiles on the board, and thus are candidates for placing new tiles.\n",
"- **`dominoes(tiles)`**: makes a board and places tiles on it, one at a time, until no more can be placed. Chooses a random tile for the first tile, then repeatedly calls `try_one` to legally place an additional tile, stopping when either there is no `frontier` left (meaning no place to legally place a tile) or no `tiles` left to place.\n",
"- **`try_one(tiles, board, frontier)`**: pop a location off the frontier, and try to find some tile that can legally put one of its halves there; when found, `put` the tile there, and remove it from `tiles`.\n",
"- **`legal(name, loc, board)`**: a name can be placed if the location is empty, and there are no conflicts with any neighboring location.\n",
"- **`neighbors(loc, board)`**: returns the (up to 4) neighbors of a location that are on the board.\n",
"- **`put_tile(board, loc0, loc1, tile, frontier)`**: places a tile on the board across `loc0` and `loc`; update the `frontier` to say that the just-covered locations are no longer in the frontier, but the empty neighbors of the tile are.\n",
"- **`shuffle(items)`**: used to randomize lists; calls `random.shuffle` and returns the result."
]
},
{
@@ -43,106 +56,91 @@
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"Board(2, 3)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now I need a strategy to fill the board with tiles. I will randomly place tiles one at a time, and to make things easier I will *not* consider removing a tile from the board and backtracking. Some more concepts:\n",
"\n",
"- **`frontier`**: I'll maintain a *frontier*, a set of locations that are adjacent to tiles on the board, and thus are candidates for placing new tiles.\n",
"- **`dominoes(tiles)`**: makes a board and places tiles on it, one at a time, until no more can be placed. Chooses a random tile for the first tile, then repeatedly calls `try_one` to legally place an additional tile, stopping when either there is no `frontier` left (meaning no place to put a tile) or no `tiles` left to place.\n",
"- **`try_one(tiles, board, frontier)`**: pop a location off the frontier, and try to find some tile that can legally put one of its halves there and the other half on an adjacent location; when found, `put` the tile there, and remove it from `tiles`.\n",
"- **`legal(value, loc, board)`**: a value can be placed if the location is empty, and the value matches all the neighboring location values.\n",
"- **`neighbors(loc)`**: returns the four neighbors of a location.\n",
"- **`put(board, loc0, loc1, tile, frontier)`**: places a tile on the board; it accomplishes this by making two calls to the board's `put` method, one for each half of the tile. The `put` function also updates the `frontier` to say that the just-covered locations are no longer in the frontier, but the empty neighbors of the tile are.\n",
"- **`shuffle(items)`**: used to randomize lists; calls `random.shuffle` and returns the result."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"import random\n",
"\n",
"def dominoes(tiles, width=16, height=24):\n",
" \"Place as many tiles on board as possible, legally and randomly.\"\n",
" \"Place as many tiles on board as possible (legally and randomly).\"\n",
" tiles = shuffle(list(tiles))\n",
" board = Board(width, height)\n",
" frontier = set()\n",
" m = min(width, height) // 2\n",
" put(board, (m, m), (m, m + 1), tiles.pop(), frontier) # Place first tile\n",
" put_tile(board, (m, m), (m, m + 1), tiles.pop(), frontier) # Place first tile\n",
" while tiles and frontier:\n",
" try_one(tiles, board, frontier)\n",
" return board\n",
" \n",
"def try_one(tiles, board, frontier):\n",
" \"Pop a frontier location, and try to place a random tile on that location.\"\n",
" \"Pop a frontier location, and try to place a tile on that location.\"\n",
" loc0 = frontier.pop()\n",
" for tile in shuffle(tiles):\n",
" for (v, w) in [tile, tile[::-1]]:\n",
" if legal(v, loc0, board):\n",
" for loc1 in shuffle(neighbors(loc0)):\n",
" if legal(w, loc1, board):\n",
" put(board, loc0, loc1, [v, w], frontier)\n",
" for (name0, name1) in [tile, tile[::-1]]:\n",
" if legal(name0, loc0, board):\n",
" for loc1 in shuffle(neighbors(loc0, board)):\n",
" if legal(name1, loc1, board):\n",
" put_tile(board, loc0, loc1, (name0, name1), frontier)\n",
" tiles.remove(tile)\n",
" return tile\n",
" \n",
"def legal(value, loc, board):\n",
" \"Is it legal to place this value on this location on board?\"\n",
" return (board.get(loc) is empty and\n",
" all(board.get(nbr) in (empty, border, value)\n",
" for nbr in neighbors(loc)))\n",
" \n",
"def neighbors(loc):\n",
" \"Neighbors of this location.\"\n",
" x, y = loc\n",
" return [(x, y+1), (x, y-1), (x+1, y), (x-1, y)]\n",
"def legal(name, loc, board):\n",
" \"Is it legal to place this name on this location on board?\"\n",
" return (board[loc] is empty and\n",
" all(board[nbr] is empty or board[nbr] == name\n",
" for nbr in neighbors(loc, board)))\n",
"\n",
"def put(board, loc0, loc1, tile, frontier): \n",
"def neighbors(loc, board):\n",
" \"Neighbors of this location on the board.\"\n",
" x, y = loc\n",
" nbrs = [(x, y+1), (x, y-1), (x+1, y), (x-1, y)]\n",
" return [(X, Y) for (X, Y) in nbrs\n",
" if 0 <= X < board.width and 0 <= Y < board.height]\n",
"\n",
"def put_tile(board, loc0, loc1, tile, frontier): \n",
" \"Place the tile across the two locations, and update frontier.\"\n",
" board.put(loc0, tile[0])\n",
" board.put(loc1, tile[1])\n",
" board[loc0], board[loc1] = tile\n",
" frontier -= {loc0, loc1}\n",
" frontier |= {loc for loc in neighbors(loc0) + neighbors(loc1)\n",
" if board.get(loc) is empty}\n",
" frontier |= {loc for loc in neighbors(loc0, board) + neighbors(loc1, board)\n",
" if board[loc] is empty}\n",
" \n",
"def shuffle(items): random.shuffle(items); return items"
]
},
{
"cell_type": "code",
"execution_count": 4,
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[['--', '--', '--', '--', '--', '--', '--', '--'],\n",
" ['--', ' ', ' ', 'JA', 'BO', ' ', ' ', '--'],\n",
" ['--', ' ', 'RY', 'JA', ' ', ' ', ' ', '--'],\n",
" ['--', ' ', ' ', 'JA', ' ', ' ', ' ', '--'],\n",
" ['--', 'GR', 'KE', 'KE', 'KE', 'RY', ' ', '--'],\n",
" ['--', ' ', ' ', 'KE', ' ', ' ', ' ', '--'],\n",
" ['--', 'GR', 'JO', 'JO', ' ', ' ', ' ', '--'],\n",
" ['--', '--', '--', '--', '--', '--', '--', '--']]"
"{(0, 2): 'GR',\n",
" (0, 3): 'JO',\n",
" (1, 1): 'BO',\n",
" (1, 3): 'JO',\n",
" (1, 4): 'KE',\n",
" (2, 1): 'JA',\n",
" (2, 4): 'KE',\n",
" (2, 5): 'GR',\n",
" (3, 0): 'JA',\n",
" (3, 1): 'JA',\n",
" (3, 2): 'RY',\n",
" (3, 3): 'RY',\n",
" (3, 4): 'KE',\n",
" (4, 0): 'PO',\n",
" (4, 4): 'KE',\n",
" (5, 4): 'JA'}"
]
},
"execution_count": 4,
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"tiles1 = [('BO', 'JA'), ('JA', 'PO'), ('JA', 'RY'), ('RY', 'KE'), \n",
"tiles8 = [('BO', 'JA'), ('JA', 'PO'), ('JA', 'RY'), ('RY', 'KE'), \n",
" ('GR', 'KE'), ('GR', 'JO'), ('JA', 'KE'), ('KE', 'JO')]\n",
"\n",
"dominoes(tiles1, 6, 6)"
"dominoes(tiles8, 6, 6)"
]
},
{
@@ -151,70 +149,68 @@
"source": [
"# Pretty Output\n",
"\n",
"There are two problems with this output. One, it is ugly. Two, I can't easily tell where each tile is: when three names come together, which of the outside names goes with the middle name? To fix those two problems I will:\n",
"There are two problems with this output. One, it is not visual. Two, it doesn't say where each tile is: when three names come together, which of the outside names goes with the middle name? To fix those two problems I will:\n",
"\n",
"- Use `matplotlib` to plot a less-ugly display; encapsulate this in the function `plot_board(board)`.\n",
"- Modify the `Board` class and the `put` function so that the board maintains a list of `boxes` that surround each two-location rectangle that a tile occupies."
"- Modify the `Board` class and the `put` function so that the board maintains a list of `boxes` that surround each two-location rectangle that a tile occupies. A box is a 4-tuple, `(x0, y0, x1, y1)`, holding the `x, y` coordinates\n",
"of the upper-left and lower-right corners of a box. The function `plot_box(box)` plots the rectangle. Note that in the new last line of `put_tile`, I add just `0.94`, not `1.0` to the size of the box; this way two adjacent tiles won't quite touch (as in the xkcd comic)."
]
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"%matplotlib inline\n",
"import matplotlib.pyplot as plt\n",
"\n",
"ϵ = 0.06 # A small amount; the space between adjacent lines\n",
"\n",
"def plot_board(board, figsize=(16, 24)):\n",
" \"Plot the box and name for every tile, plus a big box around the board.\"\n",
" plt.figure(figsize=figsize)\n",
" plt.axis('off') \n",
" plt.axis('equal')\n",
" plt.tight_layout()\n",
" for (x0, y0, x1, y1) in board.boxes:\n",
" plt.plot([x0, x1, x1, x0, x0], \n",
" [y0, y0, y1, y1, y0], 'k-')\n",
" for (y, row) in enumerate(board):\n",
" for (x, val) in enumerate(row):\n",
" if val is not border:\n",
" plt.text(x + 0.5, y + 0.3, val, ha='center', fontsize=8)\n",
" for box in board.boxes:\n",
" plot_box(box)\n",
" for (x, y) in board:\n",
" plt.text(x + 0.5, y + 0.3, board[x, y], ha='center', fontsize=8)\n",
" plot_box((-2*ϵ, -2*ϵ, figsize[0] + ϵ, figsize[1] + ϵ))\n",
"\n",
"def plot_box(box):\n",
" \"Plot a box.\"\n",
" x0, y0, x1, y1 = box\n",
" plt.plot([x0, x1, x1, x0, x0], \n",
" [y0, y0, y1, y1, y0], 'k-')\n",
" \n",
"class Board(list):\n",
" \"A board is a 2d array of values.\"\n",
"class Board(dict):\n",
" \"A mapping from location to name.\"\n",
" def __init__(self, width=16, height=24): \n",
" self.width, self.height, self.boxes = width, height, []\n",
" def __missing__(self, loc): return empty\n",
" \n",
" def __init__(self, width=20, height=36): \n",
" \"Initialize a [width × height] array of `empty`, surrounded by `border`\"\n",
" def rows (r, val): \n",
" return [[border] + width * [val] + [border] for _ in range(r)]\n",
" self[:] = rows(1, border) + rows(height, empty) + rows(1, border)\n",
" self.boxes = []\n",
" \n",
" def get(self, loc): return self[loc[1]][loc[0]]\n",
" \n",
" def put(self, loc, value): self[loc[1]][loc[0]] = value\n",
" \n",
" \n",
"def put(board, loc0, loc1, tile, frontier): \n",
" \"Place the tile across the two locations, and update frontier.\"\n",
" board.put(loc0, tile[0])\n",
" board.put(loc1, tile[1])\n",
"def put_tile(board, loc0, loc1, tile, frontier): \n",
" \"Place the tile across the two locations, and update frontier and boxes.\"\n",
" board[loc0], board[loc1] = tile\n",
" frontier -= {loc0, loc1}\n",
" frontier |= {loc for loc in neighbors(loc0) + neighbors(loc1)\n",
" if board.get(loc) is empty}\n",
" frontier |= {loc for loc in neighbors(loc0, board) + neighbors(loc1, board)\n",
" if board[loc] is empty}\n",
" (x0, y0), (x1, y1) = loc0, loc1\n",
" board.boxes.append((min(x0, x1), min(y0, y1), max(x0, x1) + 0.94, max(y0, y1) + 0.94))"
" board.boxes.append((min(x0, x1), min(y0, y1), \n",
" max(x0, x1) + 1 - ϵ, max(y0, y1) + 1 - ϵ)) "
]
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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 truncated
"image/png": "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\n",
"text/plain": [
"<matplotlib.figure.Figure at 0x1104bf208>"
"<matplotlib.figure.Figure at 0x1068ccd30>"
]
},
"metadata": {},
@@ -222,7 +218,7 @@
}
],
"source": [
"plot_board(dominoes(tiles1, 6, 6), (6, 6))"
"plot_board(dominoes(tiles8, 6, 6), (6, 6))"
]
},
{
@@ -237,7 +233,7 @@
},
{
"cell_type": "code",
"execution_count": 7,
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
@@ -256,8 +252,8 @@
" James Watt, James Watt, Kevin Costner, Kevin Bacon, Kevin Love, Lisa Frank, Frank Drake, Drake, \n",
" Oscar de la Renta, Oscar de la Hoya, Sean Hayes, Wallace Shawn, Wayne Howard, Wayne Brady, \n",
" James Brady, Tom Brady, Helen Thomas, Tom Hanks, Hank Aaron, Aaron Carter, Stephen James, \n",
" Will Smith, Kevin Smith, Kein James, Garfield, James Garfield, Warren Buffett, Jimmy Buffett, \n",
" Warren Beatty, Elizabeth Warren, Earl Warren, Eliabeth Kolbert, Stephen Colbert, George Wallace,\n",
" Will Smith, Kevin Smith, Kevin James, Garfield, James Garfield, Warren Buffett, Jimmy Buffett, \n",
" Warren Beatty, Elizabeth Warren, Earl Warren, Elizabeth Kolbert, Stephen Colbert, George Wallace,\n",
" Charles Wallace, James Monroe, Marilyn Monroe, Hank Williams, William C. Williams, Steve Harvey,\n",
" Domino Harvey, Harvey Milk, James Saint James, Etta James, Jim Jones, James Earl Jones, \n",
" Charlie Parker, Ray Parker, Ray Charles, Charles Manson, Marilyn Manson, Robin Williams, \n",
@@ -266,7 +262,7 @@
" Robin Wright, Wilbur Wright, Fatty Arbuckle, Fat Joe, George Clinton, John Kerry, \n",
" Kerry Washington, John Irving, John Quincy Adams, John Adams, Amy Adams, Aimee Mann, Super Man, \n",
" Bat Man, Ayn Rand, Lily Allen, Paul Allen, Ron Howard, Howard Hughes, Joe Kennedy, George Bush, \n",
" George Wasington, Wasington Irving, Martha Wasington, Ma Rainey, Jack Ma, Super Grover, \n",
" George Washington, Washington Irving, Martha Washington, Ma Rainey, Jack Ma, Super Grover, \n",
" Jack Black, Rand Paul, Paul Ryan, Paul Simon, Ron Paul, John Hughes, Langston Hughes, \n",
" John F. Kennedy, Little Richard, Rich Little, Martha Stewart, Yo Yo Ma, Ma Bell, \n",
" Grover Cleveland Alexander, Grover Cleveland, Jack White, Jack Ryan, Debby Ryan, Carly Simon, \n",
@@ -275,16 +271,16 @@
" Lloyd Alexander, Meg White, Meg Ryan, Debbie Reynolds, John Reynolds, Carly Fiorina, \n",
" Grace Lee Boggs, Wade Boggs, William Safire, Prince William, Little Prince, Harry Potter, \n",
" James Potter, James Hook, James Dean, Aretha Franklin, Frank Lloyd Wright, Barry White, \n",
" Walter White, Walt Whitman, John Kelly, Grace Lee, Nancy Grace, Garnet, Prince, Prince Felder, \n",
" Prince Harry, Harry Styles, John Dean, Benjamin Franklin, Harrold Lloyd, Harrold Ford, \n",
" Walter White, Walt Whitman, John Kelly, Grace Lee, Nancy Grace, Garnet, Prince, Prince Fielder, \n",
" Prince Harry, Harry Styles, John Dean, Benjamin Franklin, Harold Lloyd, Harold Ford, \n",
" Betty White, Meg Whitman, Christine Todd Whitman, Megyn Kelly, Grace Kelly, Grace Jones, \n",
" Jack Nicholson, Jack Ruby, Jack Russel, Harry Fielder, Harry Truman, Harry Jon Benjamin, \n",
" Jack Nicholson, Jack Ruby, Jack Russell, Harry Fielder, Harry Truman, Harry Jon Benjamin, \n",
" John Edward, Benjamin Harrison, Harrison Ford, Henry Ford, Betty Ford, Betty Friedan, \n",
" Chris Christie, Chris Pratt, Maggie Grace, Grace Hopper, Russel Crowe, Russ Smith, John Smith, \n",
" Chris Christie, Chris Pratt, Maggie Grace, Grace Hopper, Russell Crowe, Russ Smith, John Smith, \n",
" Justin Long, John Bel Edwards, John Candy, John Henry, Henry James, Bill James, Chris Cooper, \n",
" Chris Hemsworth, Chris Evans, Topher Grace, Van Morrison, Sheryl Crow, Sheryl Sandberg, \n",
" Cameron Crow, Long John Silver, Olivia Newton John, Huey Long, John Edwards, Candy Crowley, \n",
" Aleister Crowley, James Fenimore Cooper, James Cook, Robert Frost, Bob Evans, Evan Tayler Jones,\n",
" Cameron Crowe, Long John Silver, Olivia Newton John, Huey Long, John Edwards, Candy Crowley, \n",
" Aleister Crowley, James Fenimore Cooper, James Cook, Robert Frost, Bob Evans, Evan Taylor Jones,\n",
" James Cameron, Cam Newton, Cameron Diaz, Huey Newton, Huey Lewis, John Lewis, Jenny Lewis, \n",
" Ryan Lewis, Burt Reynolds, Alistair Cooke, Alistair Cookie, Cokie Roberts, John Roberts, \n",
" Robert Johnson, Robert E. Lee, Tommy Lee, Tommy Lee Jones, Etta James, John Oliver, \n",
@@ -293,7 +289,7 @@
},
{
"cell_type": "code",
"execution_count": 8,
"execution_count": 7,
"metadata": {},
"outputs": [
{
@@ -302,7 +298,7 @@
"270"
]
},
"execution_count": 8,
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
@@ -313,24 +309,24 @@
},
{
"cell_type": "code",
"execution_count": 9,
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[('RAY', 'ALLEN'),\n",
" ('HARVEY', 'MILK'),\n",
" ('LONG JOHN', 'SILVER'),\n",
" ('MARILYN', 'MONROE'),\n",
" ('', 'PRINCE'),\n",
" ('JON', 'BROWN'),\n",
" ('WAYNE', 'NEWTON'),\n",
" ('JOHN', 'CANDY'),\n",
" ('HENRY', 'JAMES')]"
"[('VAN', 'MORRISON'),\n",
" ('HARRY JON', 'BENJAMIN'),\n",
" ('AIMEE', 'MANN'),\n",
" ('JOE', 'MCCARTHY'),\n",
" ('JOHN', 'ADAMS'),\n",
" ('CHRIS', 'HEMSWORTH'),\n",
" ('EUGENE', 'MCCARTHY'),\n",
" ('OSCAR THE', 'GROUCH'),\n",
" ('SUPER', 'GROVER')]"
]
},
"execution_count": 9,
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
@@ -355,7 +351,7 @@
},
{
"cell_type": "code",
"execution_count": 10,
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
@@ -381,7 +377,7 @@
" COLUMBO=COLUMBUS, SAFIRE=SAPPHIRE=GARNET, GARNET=RUBY, CHARLIE=CHARLES, SEAN=SHAWN,\n",
" JIMMY=JAMES, MAN=MANN, JACK=JOHN, TOM=TOMMY, WILL=WILLIAM=WILLIAMS, ROBERT=ROBERTS=BOB, \n",
" CAM=CAMERON, OLIVER=OLIVIA, EDWARD=EDWARDS, RICH=RICHARD, CHRIS=CHRISTOPHER=TOPHER, \n",
" FAT=FATS=FATTY, WALT=WALTER, HANK=HANKS\"\"\", xkcdtiles)"
" FAT=FATS=FATTY, WALT=WALTER, HANK=HANKS, CROW=CROWE\"\"\", xkcdtiles)"
]
},
{
@@ -390,25 +386,25 @@
"collapsed": true
},
"source": [
"To make this work, I update `legal` to call the new function `match_neighbor` for each neighbor; this checks for empties, borders, and exact matches just like before, but it also consults the `synsets` global variable for an approximate or partial match, and it disallows a match between two empty values, so you can't match the empty first name of \"PRINCE\" with the empty first name of \"DRAKE\"."
"To make this work, I update `legal` to call the new function `match_neighbor` for each neighbor; this checks for empties and exact matches just like before, but it also consults the `synsets` global variable for an approximate or partial match, and it disallows a match between the empty first name of \"PRINCE\" with the empty first name of \"DRAKE\"."
]
},
{
"cell_type": "code",
"execution_count": 11,
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"def legal(value, loc, board):\n",
"def legal(name, loc, board):\n",
" \"Is it legal to place this value on this location on board?\"\n",
" return (board.get(loc) is empty and\n",
" all(match_neighbor(value, board.get(nbr)) for nbr in neighbors(loc)))\n",
" return (board[loc] is empty and\n",
" all(match_neighbor(name, board[nbr]) for nbr in neighbors(loc, board)))\n",
"\n",
"def match_neighbor(value, nbrval):\n",
" \"Does this value match the neighbor's value?\"\n",
" return (nbrval in (empty, border) or \n",
" nbrval == value != '' or\n",
" nbrval in synsets[value])"
"def match_neighbor(name, nbrval):\n",
" \"Does this name match the neighbor's value?\"\n",
" return (nbrval is empty or \n",
" nbrval == name != '' or\n",
" nbrval in synsets[name])"
]
},
{
@@ -422,7 +418,7 @@
},
{
"cell_type": "code",
"execution_count": 12,
"execution_count": 11,
"metadata": {},
"outputs": [],
"source": [
@@ -437,19 +433,19 @@
"source": [
"# Final Results\n",
"\n",
"Let's see what we can generate, and compare it to the original comic. *Note:* By default, I'm using a 16×24 square board; the xkcd comic uses 27×35, but if I try to fit that many then each tile will be smaller, and the names overflow the sides of the dominoes too much. (As it is, only a few names, like \"OLIVIA NEWTON JOHN\" overflow.)"
"Let's see what we can generate, and compare it to the original comic. *Note:* By default, I'm using a 16×24 square board; the xkcd comic uses 27×35, but if I try to fit that many then each tile will be smaller, and the names overflow the sides of the dominoes too much. (As it is, only a few names overflow.)"
]
},
{
"cell_type": "code",
"execution_count": 13,
"execution_count": 12,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAABGEAAAazCAYAAABK+TQ/AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAIABJREFUeJzs3Xu4bWVdN/zvT1FQUwHPh8THDmolEgkGKYeNiuIhJU9liFpWVmqa2PNqaWWWYb6VZic1bKv5Vh4qFQVibwTFBFTAJx/jqjTTVDQ5mAcQud8/xj3ZY8+91j6x1lh7r/n5XNe61pj3GHOscc8x7nuO8R2HVa21AAAAALC6brLWCwAAAACwCIQwAAAAABMQwgAAAABMQAgDAAAAMAEhDAAAAMAEhDAAAAAAExDCAAAAAExACAMAAAAwASEMAAAAwASEMAAAAAATEMIAAAAATEAIAwAAADABIQwAAADABIQwAAAAABMQwgAAAABMQAgDAAAAMAEhDAAAAMAEhDAAAAAAExDCAAAAAExACAMAAAAwASEMAAAAwASEMAAAAAATEMIAAAAATEAIAwAAADABIQwAAADABIQwAAAAABMQwgAAAABMQAgDAAAAMAEhDAAAAMAEhDAAAAAAExDCAAAAAExACAMAAAAwASEMAAAAwASEMAAAAAATEMIAAAAATEAIAwAAADABIQwAAADABIQwAAAAABMQwgAAAABMQAgDAAAAMAEhDAAAAMAEhDAAAAAAExDCAAAAAExACAMAAAAwASEMAAAAwASEMAAAAAATEMIAAAAATEAIAwAAADABIQwAAADABIQwAAAAABMQwgAAAABMQAgDAAAAMAEhDAAAAMAEhDAAAAAAExDCAAAAAExACAMAAAAwASEMAAAAwASEMAAAAAATEMIAAAAATEAIAwAAADABIQwAAADABIQwAAAAABMQwgAAAABMQAgDAAAAMAEhDAAAAMAEhDAAAAAAExDCAAAAAExACAMAAAAwASEMAAAAwASEMAAAAAATEMIAAAAATEAIAwAAADABIQwAAADABIQwAAAAABMQwgAAAABMQAgDAAAAMAEhDAAAAMAEhDAAAAAAExDCAAAAAExACAMAAAAwASEMAAAAwASEMAAAAAATEMIAAAAATEAIAwAAADABIQwAAADABIQwAAAAABMQwgAAAABMQAgDAAAAMAEhDAAAAMAEhDAAAAAAExDCAAAAAExACAMAAAAwASEMAAAAwASEMAAAAAATEMIAAAAATEAIAwAA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 truncated
"image/png": "iVBORw0KGgoAAAANSUhEUgAAA50AAAVcCAYAAAC/SP5hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAIABJREFUeJzs3Hm4JVV97//3h6CAODGJOJE44BSNmqiIV5AGJ4IajJogokkUpzhEryT3RgNRNPFK1IhKHAIYFP0ZURwQBaSbQVHBkdwQNIpcVEBQUBQEBb6/P2rt7urde3efhlN9zj7n/Xqe85zaq4b93WuvWlXfqlU7VYUkSZIkSUPYbKEDkCRJkiQtXSadkiRJkqTBmHRKkiRJkgZj0ilJkiRJGoxJpyRJkiRpMCadkiRJkqTBmHRKkiRJkgZj0ilJkiRJGoxJpyRJkiRpMCadkiRJkqTBmHRKkiRJkgZj0ilJkiRJGoxJpyRJkiRpMCadkiRJkqTBmHRKkiRJkgZj0ilJkiRJGoxJpyRJkiRpMCadkiRJkqTBmHRKkiRJkgZj0ilJkiRJGoxJpyRJkiRpMCadkiRJkqTBmHRKkiRJkgZj0ilJkiRJGoxJpyRJkiRpMCadkiRJkqTBmHRKkiRJkgZj0ilJkiRJGoxJpyRJkiRpMCadkiRJkqTBmHRKkiRJkgZj0ilJkiRJGoxJpyRJkiRpMCadkiRJkqTBmHRKkiRJkgZj0ilJkiRJGoxJpyRJkiRpMCadkiRJkqTBmHRKkiRJkgZj0ilJkiRJGoxJpyRJkiRpMCadkiRJkqTBmHRKkiRJkgZj0ilJkiRJGoxJpyRJkiRpMCadkiRJkqTBmHRKkiRJkgZj0ilJkiRJGoxJpyRJkiRpMCadkiRJkqTBmHRKkiRJkgZj0ilJkiRJGoxJpyRJkiRpMJsvdABaXJJcCWyz0HFIkiRJM+aqqtp2oYNYjFJVCx2DFpEkVVVZ6DgkSZKkWeJ59HQOr5UkSZIkDcakU5IkSZI0GJNOSZIkSdJg/CEhzckM/cDQVVW17QzFO4us4+HN7A8R2C4GtbpdWM+Dmdl9T5rG/mIw9hcbwR8S0lqmPQA9Kw9Gj+KclXhnkXU8vFmu21mOfbHr1631PAzrVUuR7XoYk+rVup7O4bWSJEmSpMGYdEqSJEmSBmPSKUmSJEkajEmnJEmSJGkwJp2SJEmSpMGYdEqSJEmSBmPSKUmSJEkajEmnJEmSJGkwJp2SJEmSpMGYdEqSJEmSBmPSKUmSJEkajEmnJEmSJGkwJp2SJEmSpMGYdEqSJEmSBmPSKUmSJEkajEmnJEmSJGkwJp2SJEmSpMGYdEqSJEmSBmPSKUmSJEkajEmnJEmSJGkwJp2aN0l2T3JaklVJTk2yW5KftbIvJHl4b9m7J/lNkjv3yn47yaeTnN7W+f0kz01yQZKV7e8PllvMSW7f4lvVYluZ5Kgkf5PkjPb3nLbszkkua8uckmSHFs9f9La3KslmSQ5N8s32emWSu42VvfeWxD3hc+zVtnt6ko8n+VSSe/bmn9WLb1WSLyZ5fivbI8lhbfqtSc7qz59v7f0uavWyKslXk9y+N/9tSR7Tpg9Mcv7YulePlk9yTJJ7tvbz+ba9LyS56xCxL2br2d9G+8pr23K/k+SkVvbRJNu18mOSfDnJmUkO7W33d5N8tte+9m3lo/1wVZJ3zjHG9ye5X5t+V5K/b9N7Jjk8yRbt+31Eb52zxrZxaJIVE7Z9apK/7r3eLMk/tpjPSnJwK1+nTpZazL3tzalfaNOT9rWLWps6Ock2rXxVks16y63TJ7s/arlIsseEsu2THN/a/2eS/HYrP3RsnxsdlzdL8rq2/5ye5N+SbDm2zf6yb2nbPiPJ40dxpB3H2+vRsfG5Sb6fJK18rf13PZ/rdklObMufne786wP9WHrLfWFs3S8k2XJSv5Up54C65TZf6AC0NKQ7KXwdsG9VXZNka+A+wHlVtVc7oP8z8Iy2yn7A0cBTgfe0sn8FXlRV323r79LK31xVRy/XmKvqamDPFvOZVbUiyROBp1bVHkl+C/h4knOAXwGnVNVzkjwDeAHwg/FN9qZfVVUrRy9an//KqlqV5H1JHlBV53MLJdke+Dtgn6q6Nsl9gCPWE9eKqqokX6SrY4BK8kBg+6oaJXx3uKWxrcexVXVIe59XA08GjmvzHg28qk3vA3wlyX2r6tut7GLg+cBbWfO5Xg68sdXtrQeMe1HawP42nuy8D3hBVV2YZDe6tnJAm7d/VX2/JRlbA78G3gU8o6ouT7I58LC27OUTtr0h5wAPBy4Abg9s1cof3uY9AfgwXX9wTptXbECSbYErgd2BN7fiFwBXV9Vj2zK7t/JJdbLUYr45/cKkfe3YqjokyQHA/sCRUz7bWn1yksNZxvujlr0jgLdU1ZeS3Juuz31cm7dNkodV1ddZsy89H7h+tI8neTDr5hCjZV9A1/fumeQ2wGeTfHVsmfHpa+j6p48zh76peQ7wsao6piWpD+itu3obVfWLJFcluUtVXdIS7Eur6rokk/qtaeeAuoW806n5sg/wgaq6BqCqrqmqbwJp87dh7Y7kscCrgdEVsHsAl1TVd3vrf6MtG4YxizGPPBP4p/a+NwJvZ92O8VvA3SbEM216ZNQvzGdCtw/dyeG1AFX138BlU94fYLMkWwFbjJX/Ctglyb3adn4+jzGO68d2At3FBpI8FPhmS4q3oquv9wFP6y3/KeDJ7UA42s61wJ5JbldVv66qXw8Y+2K0of0NWL1fXVpVF7blzgZ27F35TrvQchu6ut8VOKOqLm/L31BVo8Tq5uyH5wKPSHIruoR29L6jBO6pdInSgzZyu08B/h34UZKdWtnTgLeMFqiqM29m3LMYM2xEv7CefW207B0nlLGesmuBxy7j/VHLVOtLd6qqLwG0c5hLkty9LfIO4BVjqz2dds7R1jmvqn455S2eRpes0fbt9wN/OHr7fii96WOAgyaUr8+1wKOSbFdVNwG/mLJt6I7hf9Sm92uvp73XtHNA3UImnZovdwEuBUiyf7phV4cDD0pyJnA68MY2f3vgp1X1C+AX6YYh7jRaf4KDe8Mf7jZlmeUS88hOwCW91z8ERsN+Rx3m7sC32+tRPKuA3+utNxoCs7LdIRqVfRv4zXzc5ezFO6muPtiLq+804CLW3FEGoCUibweOSXJekl3nKb5JDhx9h8C2wF3aHZH+AeuJwEktMXpEb90bgU/THXxHB6030yVK5yb5SDuJXk7Wt7+N9pXns27bBvgxsF2b/hBwIXBm2x/7292ztecPtWV3GB86NQffBB4CPJjuws3FSXYGdqbbz+7YEtzzktx/Iz7/E4DPAh+ja0MAW05JdsbrZCnGDBvXL0zb1w5Mci7wYuADrWzSieJ4n/xmYGuW7/6o5WsH4PKxsh/RnUMU8F1g696FJujt9+mG1n4jveH6Y7asqusnbBvWHFdX0fUvIz8DvpPucaS5JnofoBvJtSrJKb33mORTdKOVAPYFTmzTk/qtByU5g945oOaHw2s1Xy4F7gpQVR9ONyzydXTDFHZP90zhbnQnRE8FHpLks3Sd377AF0brTzDI8NoZjXnkErqT7e+113djzcnb45KcRtfRv5juCuXqeFoSNTJpeO2rgC8Bn0hy63m6A7C6rsccMLqj1RL9kT3phir/Hd2djdWq6sPAh9tJ9THAxg6fnKvVw2tbfKfSDT/aCxg9l/IU4O5J/pTuDuzoMxbdsOCP0n0PtDt8rwZene4ZuQOBeX1udpFb3/62+jtsdzrH28qOwE/a9P7AL+m++9F2H9S2u4ruBGTUpjd6eG1V/abtB7vR3UHcge6O3I/pRjvcN8lJwO1aHP+1oW2mGwa8K3A83XH3erphoL+aso9t1FDVWYy52Zh+Ydq+NhpeezRwD+A/p7zXpD55Oe+PWr6uoOtT+0bnEKE7fh0JvLQ3f/V+X1XPTXIIsCWTXZdki17i2T8/6T+20t8fi27I79/P9UNU1Q10x+LDkvwJ8FfAb6Ys+5Mkv5VkF7phwle3WdOG1+4xdg6oeeCdTs2Xk4BnZ82PrdxqbP776a5wbUY3zOLRVfUkYA/gKVV1Md0Qul2gO+FJ8pC27lBDVWcx5tF2PwaMfsBjc7rO9uNt3ilVtVdVPWc0lHHKNqbGWVW/ojvZ/PN5ibqr6wOS3LbFfG+6uxzTZPTMVtoPpLTpOyYZDaP7GXDTPMU3MYax1yfQnaRe3E7yNwe2raq9W7t4MWvuBo2exf028MgW+z172/oJy6//3dD+BkDbr3ZqbYQkj6ZLHkdXv1NVVwDfSvIE4MvAHqOr8r079nDz98NvAX8GfAP4Ot13ey7dnesnV9U+1T1X/Kj1vE+/bB/g9W29xwNXtnb8cbo2RYv9Mbcg7lmMeU79QvtOt5u2rzVvAl7Ti2U8nvFh3Mt9f9Qy1YaiXprueXnSPUt9l6r6YW+ZlXQJ12gEwAnAX/c2M7H/bj4BvLJte2vguXQjJsattU9W1ffoRh/M6Ue9ktyj199fwYb34RPpRk99cloMY2XvB56TdkVPt5x3OjUv2lWkvwc+meRG4Aa6k4DXt/k3teEPBwC3HV0lr+4HRbZLsgXdeP53tBOQYs2JzavT/UgEdD/8cNpyjbm9B1V1UpLf690FOKqqzm93/+a0jeafkvyslb1sbN6Hgc8xNsT1ZgXd1fVhwImt/76S7s7JhuI7CngRaxLq7YCjkxTdAeYQhvPslvBAV7/HtbsrR7ayFXTDGke+SHeX+Lxe2RHAS9r03u3K6TV0z54cwDIytr/dRHdF+k3ACb07k1+rqoPpfojiXW0fuxJ44WgzvU0eRfdDGCcneSnwb+me9byJ7oeFALZv2w7ww6o6cI7hngP8j6q6Dvhhkh3oErhDqur7veWubndmt2t9BW2564H/k+TK9t6/Zu27Bl+kG+r1XuAfkpwO/BbdELCzaEO+xupkycW8Ef3CnnTJdD+Wtfa1qvpOul/kvBNdO/lc6ycuBs5krE8G7rWc90ctP739/Q10x/v3pvuxsF+x5nnLine truncated
"text/plain": [
"<matplotlib.figure.Figure at 0x11868c828>"
"<matplotlib.figure.Figure at 0x105886438>"
]
},
"metadata": {},
@@ -466,9 +462,13 @@
"source": [
"![](https://imgs.xkcd.com/comics/name_dominoes_2x.png)\n",
"\n",
"# What's Next?\n",
"\n",
"I'm happy with the results, but here are some ideas for improvements, if you want something to work on:\n",
"- Allow tiles that are 1 or 3 squares wide, like `('PRINCE',)` or `('FRANK', 'LLOYD', 'WRIGHT')`.\n",
"- Print names vertically in tiles that are placed vertically, and upside down for tiles that are placed horizontally, but with the first name on the right.\n"
"- Print names vertically in tiles that are placed vertically, and upside down for tiles that are placed horizontally, but with the first name on the right.\n",
"- Print shorter names with a bigger font and longer names with a smaller font.\n",
"- Download a bunch of names from Wikipedia and fill a 200 × 300 board.\n"
]
}
],