Last active
December 21, 2015 21:49
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
''' | |
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