Skip to content

Commit

Permalink
rename utils.py, add solve_puzzles.py, small refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
Philip Shemella committed May 13, 2019
1 parent dc67143 commit 39d95a8
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 88 deletions.
18 changes: 13 additions & 5 deletions README.md
Expand Up @@ -19,9 +19,19 @@ To play a random game:

To play a non-random game:

python3 play_puzzles.py AGFEDCB
python3 play_puzzles.py RDGHNOU

where A is the center letter. If the puzzle `AGFEDCB` does not exist, it will be created and saved to `data/ABCDEFG.json` (the file names are the first letter and the alphabetically sorted remaining letters).
where A is the center letter. If the puzzle `RDGHNOU` does not exist, it will be created and saved to `data/RDGHNOU.json` (the file names are the first letter and the alphabetically sorted remaining letters).

⚠️ The word list used is from Scrabble: [TWL06](https://www.wordgamedictionary.com/twl06/), and at roungly 180k words, it's larger than what the NY Times uses. To reach "genius" level, you'll need to solve 50% of the words.

To solve a game (aka cheat-mode):

python3 solve_puzzles.py RDGHNOU

If the game does not exist, it will be created and saved to the `data/` folder.

For a list of the previous NY Times letter selections, see [William Shunn's page](https://www.shunn.net/bee/?past=1).

## to generate new puzzles

Expand All @@ -41,11 +51,10 @@ To generate a certain letter combination, use:

which will then be saved to `data/ABCDEFG.json`.

For a list of the previous NY Times letter selections, see [William Shunn's page](https://www.shunn.net/bee/?past=1).

---

# Game Play

To play, build words with a minimum of 4 letters, using the letters provided.

Each word must include the center letter at least once.
Expand All @@ -56,7 +65,6 @@ Scoring: 1 point for a 4 letter word, and 1 more point for each additional lette

Each puzzle has 1 "pangram" that uses each of the 7 letters at least once. The pangram is worth 7 extra points.

Dictionary: Scrabble word list [TWL06](https://www.wordgamedictionary.com/twl06/)


## example play
Expand Down
24 changes: 0 additions & 24 deletions functions.py

This file was deleted.

2 changes: 1 addition & 1 deletion generate_puzzles.py
Expand Up @@ -3,7 +3,7 @@
''' generate puzzles based on criteria in params.py '''

import params
import functions
import utils

import os
import sys
Expand Down
66 changes: 8 additions & 58 deletions play_puzzle.py
Expand Up @@ -3,55 +3,14 @@
''' play puzzles based on params.py PUZZLE_PATH_READ value'''

import params
import generate_puzzles
import functions
import utils

import os
import sys
import json
import random
import glob

def main():
pass

def select_puzzle(puzl_idx=None):

puzzles = glob.glob(params.PUZZLE_DATA_PATH + os.sep + '*.json')
puzl_idx_list = [x.split(os.sep)[-1].split('.')[0] for x in puzzles]

# scenario 1: no selection made, so return a random puzzle
if puzl_idx is None:
# return a random puzzle
puzl_path = random.choice(puzzles)
print ('You selected a random puzzle:',puzl_path)
return puzl_path

if len(puzl_idx) != params.TOTAL_LETTER_COUNT:
print ('Puzzles must be ',str(params.TOTAL_LETTER_COUNT),'letters long. Please try again.')
exit(0)

# scenario 2: specific puzzle requested but not already available
if puzl_idx in puzl_idx_list:
print('Existing puzzle will be played:',puzl_idx)
puzl_path = params.PUZZLE_DATA_PATH + os.sep + puzl_idx + '.json'
# scenario 3: create a new puzzle because an existing one was not found
else:
puzl_idx = generate_puzzles.main(puzl_idx)
print ('You created a new puzzle:',puzl_idx)
puzl_path = params.PUZZLE_DATA_PATH + os.sep + puzl_idx + '.json'

return puzl_path

def read_puzzle(puzl_path):

with open(puzl_path,'r') as fp:
puzzles = json.load(fp)

#print(len(puzzles.get('letters'),'total puzzle(s) were loaded')

return puzzles

def play(puzl):
print('Type !help or !h for help')

Expand Down Expand Up @@ -146,7 +105,8 @@ def play(puzl):
print_list[0] += ' ***'

# print success and running stats
print_table(print_list, len(print_list), 22)
utils.print_table(print_list, len(print_list), 22)
print()

# add good guess to the list, so it can't be reused
guess_list.append(guess)
Expand All @@ -155,16 +115,6 @@ def play(puzl):
if player_words == word_count:
print ('Congratulations. You found them all!','\n')

def print_table(data, cols, wide):
'''Prints formatted data on columns of given width.'''
# https://stackoverflow.com/a/50215584/2327328
n, r = divmod(len(data), cols)
pat = '{{:{}}}'.format(wide)
line = '\n'.join(pat * cols for _ in range(n))
last_line = pat * r
print(line.format(*data))
print(last_line.format(*data[n*cols:]))

def shuffle_letters(letters):
# shuffles letters, excluding the center letter
# random.shuffle() only works in place
Expand Down Expand Up @@ -229,7 +179,7 @@ def help(msg, letters, guess_list, player_score, player_words, player_pangram, t
WORDS - 2 points
SPELLING - 5 points
Each puzzle has ''' + str(params.COUNT_PANGRAMS) + ''' pangram(s) that uses each of the ''' + str(params.MIN_WORD_LENGTH) + ''' letters.
Each puzzle has ''' + str(params.COUNT_PANGRAMS) + ''' pangram(s) that uses each of the ''' + str(params.TOTAL_LETTER_COUNT) + ''' letters.
The pangram is worth 7 extra points.
Have fun!
Expand Down Expand Up @@ -260,14 +210,14 @@ def main():
if puzzle_idx is not None:

# check validity of letters
functions.check_letters(puzzle_idx)
utils.check_letters(puzzle_idx)

# choose standard sorting for all puzzle file names
puzzle_idx = functions.sort_letters(puzzle_idx)
puzzle_idx = utils.sort_letters(puzzle_idx)

puzl_path = select_puzzle(puzzle_idx)
puzl_path = utils.select_puzzle(puzzle_idx)

puzl = read_puzzle(puzl_path)
puzl = utils.read_puzzle(puzl_path)

play(puzl)

Expand Down
58 changes: 58 additions & 0 deletions solve_puzzle.py
@@ -0,0 +1,58 @@
#!/usr/bin/env python

''' solve (cheat) puzzles '''

import params
import utils

import os
import sys

def solve(p):

print ('letters:', p.get('letters',None))
print ('total_score:', p.get('total_score',''))
print ('word_count:', p.get('word_count',''))
print ('pangram(s):', ', '.join(p.get('pangram_list',[])))
print ()

# print all answers
for x in p.get('word_list',[]):
score = x.get('score')

# add 7 points if word is pangram
if x.get('word') in p.get('pangram_list',[]):
score += + 7
utils.print_table((x.get('word'),score), 2, 10)

return

def main():

# try to read an existing or new puzzle from command line (not required)
try:
puzzle_idx = sys.argv[1].strip().upper()
except:
puzzle_idx = None

if puzzle_idx is not None:

# check validity of letters
utils.check_letters(puzzle_idx)

# choose standard sorting for all puzzle file names
puzzle_idx = utils.sort_letters(puzzle_idx)

# select puzzle, generate it if it doesn't exist
puzl_path = utils.select_puzzle(puzzle_idx)

# load json puzzle data
puzl = utils.read_puzzle(puzl_path)

# solve puzzle (cheat mode)
solve(puzl)

if __name__ == "__main__":

main()

78 changes: 78 additions & 0 deletions utils.py
@@ -0,0 +1,78 @@
#!/usr/bin/env python

''' helper functions that are used between different parts of the code '''

import params
import generate_puzzles

import os
import random
import json
import glob

# check validity of provided letters
def check_letters(pzl):

if len(pzl) != len(list(set(pzl))):
print('Invalid count of letters requested.')
print('Exiting...')
exit(0)

elif len(pzl) != params.TOTAL_LETTER_COUNT:
print('Invalid count of letters requested.')
print('Exiting...')
exit(0)
else:
return

# smart sorting of letters, keeping first letter first
def sort_letters(pzl):
return pzl[0] + ''.join(sorted(pzl[1:]))


def select_puzzle(puzl_idx=None):

puzzles = glob.glob(params.PUZZLE_DATA_PATH + os.sep + '*.json')
puzl_idx_list = [x.split(os.sep)[-1].split('.')[0] for x in puzzles]

# scenario 1: no selection made, so return a random puzzle
if puzl_idx is None:
# return a random puzzle
puzl_path = random.choice(puzzles)
print ('You selected a random puzzle:',puzl_path)
return puzl_path

if len(puzl_idx) != params.TOTAL_LETTER_COUNT:
print ('Puzzles must be ',str(params.TOTAL_LETTER_COUNT),'letters long. Please try again.')
exit(0)

# scenario 2: specific puzzle requested but not already available
if puzl_idx in puzl_idx_list:
print('Existing puzzle will be played:',puzl_idx)
puzl_path = params.PUZZLE_DATA_PATH + os.sep + puzl_idx + '.json'
# scenario 3: create a new puzzle because an existing one was not found
else:
puzl_idx = generate_puzzles.main(puzl_idx)
print ('You created a new puzzle:',puzl_idx)
puzl_path = params.PUZZLE_DATA_PATH + os.sep + puzl_idx + '.json'

return puzl_path

def read_puzzle(puzl_path):

with open(puzl_path,'r') as fp:
puzzles = json.load(fp)

#print(len(puzzles.get('letters'),'total puzzle(s) were loaded')

return puzzles

def print_table(data, cols, wide):
'''Prints formatted data on columns of given width.'''
# https://stackoverflow.com/a/50215584/2327328
n, r = divmod(len(data), cols)
pat = '{{:{}}}'.format(wide)
line = '\n'.join(pat * cols for _ in range(n))
last_line = pat * r
print(line.format(*data))
#print(last_line.format(*data[n*cols:]))

0 comments on commit 39d95a8

Please sign in to comment.