Skip to content

Instantly share code, notes, and snippets.

@twneale
Last active December 21, 2015 21:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save twneale/6371143 to your computer and use it in GitHub Desktop.
Save twneale/6371143 to your computer and use it in GitHub Desktop.
My script for cheating at Boggle. words.json is just the scrabble dictionary as a big list.
'''
A boggle board is a mapping of coordinates to letters.
'''
from os.path import dirname, abspath, join
import pdb
import json
import random
from itertools import chain, product
from functools import partial
from operator import add, itemgetter
from collections import namedtuple
# ----------------------------------------------------------------------------
# Helpers for processing words.
# ----------------------------------------------------------------------------
def words():
'''Return a list of all words in the Official Scrabble Players'
Dictionary.'''
with open(join(dirname(abspath(__file__)), 'words.json')) as f:
return json.load(f)
def trie():
'''Build an ultra simple retrieval trie from the words.'''
_trie = {}
for w in words():
_d = _trie
for char in w:
try:
_d = _d[char]
except KeyError:
_d[char] = {}
_d = _d[char]
# The key 0 signals a complete word has been found.
_d[0] = None
return _trie
# ----------------------------------------------------------------------------
# Helpers for processing boards.
# ----------------------------------------------------------------------------
# The dimension of the board.
DIM = 4
# Functions to calculate coordinates of adjacent squares.
_shifters = [partial(add, x) for x in (-1, 0, 1)]
_shifters = set(product(_shifters, _shifters))
# All valid coordinates.
_coordinates = set([(x, y) for x in range(DIM) for y in range(DIM)])
def adjacents(xy, valid=_coordinates, _shifters=_shifters):
'''Get a set of all squares adjacent to the given square,
including diagonals.
'''
x, y = xy
points = set((f(x), g(y)) for (f, g) in _shifters)
return points & valid - set([xy])
def scan(board, xy, node=None, buf=None,
adjacents=adjacents, trie=trie()):
'''
Given a boggle board, a square, and a list of characters,
determine whether any adjacent squares are part of potentially valid
words relative to the given square.
'''
node = (node or trie)
buf = (buf or [])
char = board[xy]
# If the trie lookup fails, no word is possible; bail.
try:
node = node[char]
except KeyError:
return
# Buf has to be a new list.
buf = buf + [(xy, char)]
# 0 signals a full word has been found; yield it.
if 0 in node:
yield tuple(buf)
# If no word has been found, repeat the process for each new adjacent.
for xy in adjacents(xy):
for _buf in scan(board, xy, node, buf=buf):
yield _buf
def allplays(board, scan=scan, second=itemgetter(1)):
'''A generator of all valid plays for this board.'''
res = set()
for xy in board:
for w in scan(board, xy):
wlen = len(w)
# Valid words must be at least 3 letters long...
condition1 = (2 < wlen)
# ...and can't reuse any square more than once.
condition2 = (wlen == len(set(w)))
if condition1 and condition2:
yield w
#yield ''.join(map(second, w))
def pprint(board, n=DIM, first=itemgetter(0)):
'''Pretty-print the boggle board.'''
i = 0
for t in sorted(board):
print board[t],
i += 1
if i == n:
print '\n',
i = 0
LETTERS = (
(12, 'e'),
(9, 'ai'),
(8, 'o'),
(6, 'nrt'),
(4, 'dlsu'),
(3, 'g'),
(2, 'bcfhmpvwy'),
(1, 'jkzxq'),
)
LETTERS = list(chain.from_iterable(x * y for (x, y) in LETTERS))
letter = partial(random.choice, LETTERS)
def letters(f=letter):
while True:
yield f()
def generate_board(dim=DIM, _letters=None):
'''
Given a single dimension, generates a "random" boggle board using the
letter frequencies from scrabble.
'''
if _letters is None:
_letters = letters()
board = dict(zip(_coordinates, _letters))
return board
def data(board, Play=namedtuple('Play', 'word coords'), second=itemgetter(1)):
'''
Return all plays, each a list consisting of the word as text and a
list correlating jquery selectors to characters.
'''
res = []
for coords in allplays(board):
res.append(
(''.join(map(second, coords)),
[('-'.join(map(str, xy)), c) for (xy, c) in coords]))
return res
# ----------------------------------------------------------------------------
# Helpers for scoring plays.
# ----------------------------------------------------------------------------
def scoreword(w):
wlen = len(w)
if wlen < 3:
return 0
if wlen in (3, 4):
return 1
else:
return 2 + wlen - 4
def highscore(plays):
return sum(map(scoreword, plays))
# if __name__ == "__main__":
# bb = generate()
# dd = data(bb)
if __name__ == "__main__":
# Create a random boggle board.
_input = raw_input("Input the board? Y, else N to generate random board: ")
if _input.lower() == 'y':
letters = raw_input(('\nType the letters from top to bottom, '
'left to right (no whitespace):\n'))
game = dict(zip(sorted(_coordinates), letters))
else:
# Generate a random boggle board, using the letter frequencies from scrabble.
LETTERS = (
(12, 'e'),
(9, 'ai'),
(8, 'o'),
(6, 'nrt'),
(4, 'dlsu'),
(3, 'g'),
(2, 'bcfhmpvwy'),
(1, 'jkzxq'),
)
LETTERS = list(chain.from_iterable(x * y for (x, y) in LETTERS))
random.shuffle(LETTERS)
game = dict(zip(_coordinates, LETTERS))
# Display it.
pprint(game)
plays = set(allplays(game))
print 'Highest score for this board: ', highscore(plays)
raw_input('Press enter to view all words for this board:')
def play_str(play, second=itemgetter(1)):
return '%r -> %r: %r' % (play[0][0], play[-1][0], ''.join(map(second, play)))
# Display all plays alphabetically with a score.
plays = ((scoreword(p), play_str(p)) for p in plays)
for score, p in sorted(plays, reverse=True):
print score, p
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment