Ali Abid commited on
Commit
2a68adc
1 Parent(s): 8d1c4df

first commit

Browse files
Files changed (11) hide show
  1. .gitignore +1 -0
  2. README.md +2 -3
  3. formatters.py +44 -0
  4. game_manager.py +259 -0
  5. prompt.txt +10 -0
  6. requirements.txt +1 -0
  7. run.py +102 -0
  8. style.css +101 -0
  9. test.py +21 -0
  10. wordmaker.py +48 -0
  11. words.json +0 -0
.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ jeopardy.json
README.md CHANGED
@@ -1,13 +1,12 @@
1
  ---
2
  title: Crossword
3
- emoji: 💩
4
  colorFrom: indigo
5
  colorTo: gray
6
  sdk: gradio
7
  sdk_version: 3.16.1
8
- app_file: app.py
9
  pinned: false
10
  license: mit
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
  title: Crossword
3
+ emoji: 🧩
4
  colorFrom: indigo
5
  colorTo: gray
6
  sdk: gradio
7
  sdk_version: 3.16.1
8
+ app_file: run.py
9
  pinned: false
10
  license: mit
11
  ---
12
 
 
formatters.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from game_manager import Clue
2
+ from typing import List, Optional
3
+ import time
4
+ from game_manager import GUESS_TIMEOUT
5
+
6
+
7
+ def crossword(grid: List[List[Optional[str]]], clues: List[Clue]):
8
+ clue_grid = [[[] for _ in range(len(grid[0]))] for _ in range(len(grid))]
9
+ for clue_id, clue in enumerate(clues):
10
+ if clue is None:
11
+ continue
12
+ if clue.across:
13
+ for i, _ in enumerate(clue.answer):
14
+ clue_grid[clue.location[0]][clue.location[1] + i].append(clue_id)
15
+ else:
16
+ for i, _ in enumerate(clue.answer):
17
+ clue_grid[clue.location[0] + i][clue.location[1]].append(clue_id)
18
+ output = "<div class='crossword'>"
19
+ for i, row in enumerate(grid):
20
+ output += "<div class='c-row'>"
21
+ for j, cell in enumerate(row):
22
+ clue_ids = clue_grid[i][j]
23
+ output += f"""<div class='cell {"" if cell is None else "filled"} {"" if len(clue_ids) == 0 else f"clue {' '.join('clue-' + str(clue_id+1) for clue_id in clue_ids)}"}'>"""
24
+ if cell == None:
25
+ output += "&nbsp;"
26
+ else:
27
+ output += cell
28
+ output += "</div>"
29
+ output += "</div>"
30
+ output += "</div>"
31
+ return output
32
+
33
+
34
+ def clue_riddle(clue):
35
+ if clue is None:
36
+ return "..."
37
+
38
+ time_remaining = GUESS_TIMEOUT - (time.time() - clue.create_time)
39
+ return (
40
+ "\n".join(line for line in clue.riddle.splitlines() if line.strip() != "")
41
+ + " - "
42
+ + str(round(max(0, time_remaining)))
43
+ + "s"
44
+ )
game_manager.py ADDED
@@ -0,0 +1,259 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import threading
2
+ import random
3
+ from typing import Tuple
4
+ import re
5
+ import copy
6
+ import os
7
+ import json
8
+ import openai
9
+ import time
10
+
11
+ openai.api_key = os.environ["OPENAI_KEY"]
12
+
13
+ SIZE = 15
14
+ RIDDLE_COUNT = 3
15
+ MIN_WORD_SIZE = 4
16
+ MAX_WORD_SIZE = 10
17
+ WORD_LIMIT = 7500
18
+ COMPLETE_GAME_TIMEOUT = 60 * 60
19
+ INCOMPLETE_GAME_TIMEOUT = 60 * 60 * 24
20
+ GUESS_TIMEOUT = 20
21
+
22
+
23
+ with open("words.json", "r") as f:
24
+ words = json.loads(f.read())
25
+
26
+ with open("prompt.txt", "r") as f:
27
+ RIDDLE_PROMPT = f.read()
28
+
29
+ search_text = "\n".join(words)
30
+ games = {}
31
+ all_games_lock = threading.Lock()
32
+
33
+
34
+ def get_riddle(answer):
35
+ prompt = RIDDLE_PROMPT.format(
36
+ answer,
37
+ )
38
+ while True:
39
+ try:
40
+ completions = openai.Completion.create(
41
+ engine="text-davinci-003", prompt=prompt, max_tokens=200, n=1, temperature=0.5
42
+ )
43
+ riddle = completions["choices"][0]["text"]
44
+ return riddle
45
+ except Exception as e:
46
+ print("OpenAI Error", e, "Retrying...")
47
+ time.sleep(0.5)
48
+
49
+
50
+ class Clue:
51
+ def __init__(self, answer, location, across, solved):
52
+ self.answer: str = answer
53
+ self.location: Tuple[int, int] = location
54
+ self.across: bool = across
55
+ self.solved: bool = solved
56
+ self.riddle: str = ""
57
+ self.create_time: int
58
+
59
+ def __repr__(self):
60
+ return f"{self.answer}: {self.location}, {'Across' if self.across else 'Down'}, {'Solved' if self.solved else 'Unsolved'}"
61
+
62
+
63
+ class Game:
64
+ def __init__(self, room_name, competitive, init_word):
65
+ self.room_name = room_name
66
+ self.competitive = competitive
67
+ self.player_scores = {}
68
+ self.grid = [[None for i in range(SIZE)] for j in range(SIZE)]
69
+ self.grid = place_on_grid(
70
+ self.grid, init_word, (SIZE // 2, SIZE // 2 - len(init_word) // 2), True
71
+ )
72
+ self.clues = [None] * RIDDLE_COUNT
73
+ self.previous_clues = []
74
+ self.lock = threading.Lock()
75
+ self.complete = False
76
+ self.last_update_index = 0
77
+ self.last_update_time = time.time()
78
+ self.last_riddle_update_time = None
79
+ self.pending_request = False
80
+
81
+ games[room_name] = self
82
+
83
+ def update(self):
84
+ self.last_update_index += 1
85
+ self.last_update_time = time.time()
86
+
87
+ def replace_clue(self, index):
88
+ clue_grid = copy.deepcopy(self.grid)
89
+ for j, clue in enumerate(self.clues):
90
+ if clue and index != j:
91
+ clue_grid = place_on_grid(clue_grid, clue.answer, clue.location, clue.across)
92
+
93
+ if self.clues[index]:
94
+ self.previous_clues.append(self.clues[index])
95
+ clue = find_clue(clue_grid)
96
+ if clue is None:
97
+ self.complete = True
98
+ return
99
+ clue.create_time = time.time()
100
+ self.pending_request = True
101
+ clue.riddle = get_riddle(clue.answer)
102
+ self.pending_request = False
103
+ self.last_riddle_update_time = time.time()
104
+ self.clues[index] = clue
105
+
106
+ def add_player(self, player_name):
107
+ with self.lock:
108
+ self.update()
109
+ if player_name not in self.player_scores:
110
+ self.player_scores[player_name] = 0
111
+
112
+ def player_guess(self, player_name, guess):
113
+ guess = guess.lower()
114
+ if self.pending_request:
115
+ return
116
+ with self.lock:
117
+ self.update()
118
+ matched_clues = [
119
+ clue for clue in self.clues if not clue.solved and clue.answer == guess
120
+ ]
121
+ if len(matched_clues) == 0:
122
+ return False
123
+ for clue in matched_clues:
124
+ clue.solved = True
125
+ place_on_grid(self.grid, clue.answer, clue.location, clue.across)
126
+ self.player_scores[player_name] += 1
127
+
128
+ def time_left(self):
129
+ return INCOMPLETE_GAME_TIMEOUT - (time.time() - self.last_update_time)
130
+
131
+
132
+ def place_on_grid(grid, word, location, across):
133
+ x, y = location
134
+ if across:
135
+ grid[x][y : y + len(word)] = word
136
+ else:
137
+ for i, letter in enumerate(word):
138
+ grid[x + i][y] = letter
139
+ return grid
140
+
141
+
142
+ def find_clue(grid) -> Clue:
143
+ all_coordinate_pairs = [
144
+ (i, j) for i in range(SIZE) for j in range(SIZE) if grid[i][j] is not None
145
+ ]
146
+ random.shuffle(all_coordinate_pairs)
147
+ for i, j in all_coordinate_pairs:
148
+ regexes = []
149
+ if (j == 0 or grid[i][j - 1] is None) and (
150
+ j + 1 == SIZE or grid[i][j + 1] is None
151
+ ):
152
+ running_regex = ""
153
+ possible_across_regexes = []
154
+ for k in range(j, -1, -1):
155
+ if (
156
+ (i != 0 and grid[i - 1][k] is not None)
157
+ or (i != SIZE - 1 and grid[i + 1][k] is not None)
158
+ ) and grid[i][k] is None:
159
+ break
160
+ valid = k == 0 or grid[i][k - 1] is None
161
+ running_regex = (grid[i][k] or ".") + running_regex
162
+ possible_across_regexes.append(((i, k), running_regex, valid))
163
+ possible_across_regexes = [p for p in possible_across_regexes if p[2]]
164
+ for k in range(j + 1, SIZE):
165
+ if (
166
+ (i != 0 and grid[i - 1][k] is not None)
167
+ or (i != SIZE - 1 and grid[i + 1][k] is not None)
168
+ ) and grid[i][k] is None:
169
+ break
170
+ valid = k == SIZE - 1 or grid[i][k + 1] is None
171
+ for start, possible_across_regex, _ in possible_across_regexes[:]:
172
+ if start[1] + len(possible_across_regex) == k:
173
+ possible_across_regexes.append(
174
+ (start, possible_across_regex + (grid[i][k] or "."), valid)
175
+ )
176
+ possible_across_regexes = [
177
+ (loc, regex, True)
178
+ for loc, regex, valid in possible_across_regexes
179
+ if len(regex) >= MIN_WORD_SIZE and valid
180
+ ]
181
+ regexes.extend(possible_across_regexes)
182
+ elif (i == 0 or grid[i - 1][j] is None) and (
183
+ i + 1 == SIZE or grid[i + 1][j] is None
184
+ ):
185
+ running_regex = ""
186
+ possible_down_regexes = []
187
+ for k in range(i, -1, -1):
188
+ if (
189
+ (j != 0 and grid[k][j - 1] is not None)
190
+ or (j != SIZE - 1 and grid[k][j + 1] is not None)
191
+ ) and grid[k][j] is None:
192
+ break
193
+ valid = k == 0 or grid[k - 1][j] is None
194
+ running_regex = (grid[k][j] or ".") + running_regex
195
+ possible_down_regexes.append(((k, j), running_regex, valid))
196
+ possible_down_regexes = [p for p in possible_down_regexes if p[2]]
197
+ for k in range(i + 1, SIZE):
198
+ if (
199
+ (j != 0 and grid[k][j - 1] is not None)
200
+ or (j != SIZE - 1 and grid[k][j + 1] is not None)
201
+ ) and grid[k][j] is None:
202
+ break
203
+ valid = k == SIZE - 1 or grid[k + 1][j] is None
204
+ for start, possible_down_regex, _ in possible_down_regexes[:]:
205
+ if start[0] + len(possible_down_regex) == k:
206
+ possible_down_regexes.append(
207
+ (start, possible_down_regex + (grid[k][j] or "."), valid)
208
+ )
209
+ possible_down_regexes = [
210
+ (loc, regex, False)
211
+ for loc, regex, valid in possible_down_regexes
212
+ if len(regex) >= MIN_WORD_SIZE and valid
213
+ ]
214
+ regexes.extend(possible_down_regexes)
215
+
216
+ random.shuffle(regexes)
217
+ for loc, regex, across in regexes:
218
+ matches = re.findall("^" + regex + "$", search_text, re.MULTILINE)
219
+ if len(matches) > 1:
220
+ random.shuffle(matches)
221
+ answer = matches[0]
222
+ clue = Clue(answer, loc, across, False)
223
+ return clue
224
+ return None
225
+
226
+
227
+ def new_game(room_name):
228
+ competitive = room_name != ""
229
+ with all_games_lock:
230
+ if room_name in games:
231
+ return games[room_name]
232
+ if not competitive:
233
+ while room_name == "" or room_name in games:
234
+ room_name = str(random.randint(0, 999999))
235
+ init_word = random.choice(words)
236
+ else:
237
+ init_word = room_name
238
+ return Game(room_name, competitive, init_word)
239
+
240
+
241
+ def game_thread():
242
+ while True:
243
+ now = time.time()
244
+ for room_name, game in games.items():
245
+ idle_time = now - game.last_update_time
246
+ if (game.complete and idle_time > COMPLETE_GAME_TIMEOUT) or (
247
+ idle_time > INCOMPLETE_GAME_TIMEOUT
248
+ ):
249
+ del games[room_name]
250
+ continue
251
+ for i, clue in enumerate(game.clues):
252
+ if clue is None or clue.solved or now - clue.create_time > GUESS_TIMEOUT:
253
+ game.replace_clue(i)
254
+
255
+ time.sleep(0.1)
256
+
257
+ thread = threading.Thread(target=game_thread)
258
+ thread.daemon = True
259
+ thread.start()
prompt.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ I will give you a word. Write a rhyming poem of 4 lines that gives clues to the word. Do not use the word itself in the riddle. For example:
2
+
3
+ WORD: tick
4
+
5
+ This is the sound
6
+ of time marching on
7
+ Or a type of bug
8
+ you surely will want gone!
9
+
10
+ WORD: {}
requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ openai
run.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import formatters
3
+ from game_manager import games, new_game
4
+
5
+ with gr.Blocks(css="style.css") as app:
6
+ started = gr.Variable(False)
7
+ player = gr.Variable()
8
+ last_update = gr.Variable(0)
9
+
10
+ with gr.Column() as opening:
11
+ gr.Markdown("# Crossword GPT")
12
+ gr.Markdown(
13
+ """
14
+ Welcome to Crossword GPT, a game that dynamically creates a crossword and uses GPT to create clues.
15
+
16
+ - At the start of the game, a crossword puzzle will be created, with single word already solved.
17
+
18
+ - At any time, a riddle with three clues will be shown, corresponding to three words that branch off the solved part of the puzzle. Riddles are regenerated every 20 seconds, with unsolved words removed.
19
+
20
+ - You can play against friends, in which case enter a shared room name below. To play alone, leave the fields blank and start the game.
21
+
22
+ - Game ends when there is no more space to add words. Winner in competitive mode is the player with the most words.
23
+ """
24
+ )
25
+
26
+ room_name = gr.Text(label="Room Name")
27
+ player_name = gr.Text(label="Player Name")
28
+ start_btn = gr.Button("Let's Go!")
29
+
30
+ with gr.Column(visible=False) as game_col:
31
+ with gr.Row():
32
+ with gr.Column(min_width=500):
33
+ grid = gr.HTML()
34
+ score_table = gr.DataFrame(headers=["team", "score"], label="Scores")
35
+
36
+ with gr.Column():
37
+ clue1 = gr.Textbox(label="Clue 1", elem_id="clue-1")
38
+ clue2 = gr.Textbox(label="Clue 2", elem_id="clue-2")
39
+ clue3 = gr.Textbox(label="Clue 3", elem_id="clue-3")
40
+ guess = gr.Textbox(
41
+ label="Guess",
42
+ placeholder="Answer any clue here...",
43
+ elem_id="guess",
44
+ )
45
+ prev_answers = gr.DataFrame(
46
+ headers=None, row_count=1, col_count=3, label="Previous Answers"
47
+ )
48
+
49
+ def start_game(data):
50
+ game = new_game(data[room_name])
51
+ game.add_player(data[player_name])
52
+
53
+ return {
54
+ game_col: gr.update(visible=True),
55
+ opening: gr.update(visible=False),
56
+ room_name: game.room_name,
57
+ player: data[player_name],
58
+ }
59
+
60
+ start_btn.click(
61
+ start_game,
62
+ {room_name, player_name},
63
+ [game_col, opening, player, room_name],
64
+ )
65
+
66
+ def submit_guess(data):
67
+ game = games[data[room_name]]
68
+ game.player_guess(data[player], data[guess])
69
+
70
+ guess.submit(submit_guess, {room_name, player, guess}, None)
71
+ guess.submit(
72
+ None,
73
+ None,
74
+ None,
75
+ _js="""() => {document.querySelector("gradio-app").shadowRoot.querySelector("#guess textarea").setSelectionRange(0, 9999)}""",
76
+ status_tracker=None,
77
+ )
78
+
79
+ def update_game(data):
80
+ if data[room_name] is None or data[room_name] not in games:
81
+ return {grid: gr.skip()}
82
+ game = games[data[room_name]]
83
+ no_up = data[last_update] == game.last_update_index
84
+ return {
85
+ grid: gr.skip() if no_up else formatters.crossword(game.grid, game.clues),
86
+ prev_answers: gr.skip() if no_up else [[clue.answer for clue in game.previous_clues[-3:]]],
87
+ score_table: [[k, v] for k, v in game.player_scores.items()],
88
+ clue1: formatters.clue_riddle(game.clues[0]),
89
+ clue2: formatters.clue_riddle(game.clues[1]),
90
+ clue3: formatters.clue_riddle(game.clues[2]),
91
+
92
+ }
93
+
94
+ start_btn.click(
95
+ update_game,
96
+ {room_name, last_update},
97
+ [grid, prev_answers, clue1, clue2, clue3, score_table],
98
+ every=1,
99
+ )
100
+
101
+
102
+ app.queue().launch()
style.css ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .crossword {
2
+ font-family: monospace;
3
+ font-weight: bold;
4
+ display: flex;
5
+ flex-direction: column;
6
+ overflow-x: auto;
7
+ }
8
+ .crossword .c-row {
9
+ display: flex;
10
+ }
11
+ .cell {
12
+ border: 1px solid black;
13
+ background-color: white;
14
+ color: black;
15
+ display: inline-flex;
16
+ justify-content: center;
17
+ align-items: center;
18
+ padding: 0.25em 0.75em;
19
+ }
20
+ .dark .cell {
21
+ border: 1px solid gray;
22
+ background-color: black;
23
+ color: white;
24
+ }
25
+ .crossword .cell.filled :not([clue]) {
26
+ background-color: gainsboro;
27
+ }
28
+ .dark .crossword .cell.filled :not([clue]) {
29
+ background-color: dimgray;
30
+ }
31
+ .clue-1, #clue-1 textarea {
32
+ background-color: lightpink;
33
+ }
34
+ .clue-2, #clue-2 textarea {
35
+ background-color: lightgreen;
36
+ }
37
+ .clue-3, #clue-3 textarea {
38
+ background-color: lightblue;
39
+ }
40
+ .clue-1.clue-2 {
41
+ background: linear-gradient(45deg, lightpink 50%, lightgreen 50%);
42
+ }
43
+ .clue-1.clue-3 {
44
+ background: linear-gradient(45deg, lightpink 50%, lightblue 50%);
45
+ }
46
+ .clue-2.clue-3 {
47
+ background: linear-gradient(45deg, lightgreen 50%, lightblue 50%);
48
+ }
49
+
50
+ .dark .clue-1, .dark #clue-1 textarea {
51
+ background-color: darkred;
52
+ }
53
+ .dark .clue-2, .dark #clue-2 textarea {
54
+ background-color: darkgreen;
55
+ }
56
+ .dark .clue-3, .dark #clue-3 textarea {
57
+ background-color: darkblue;
58
+ }
59
+ #clue-1 textarea, #clue-2 textarea, #clue-3 textarea {
60
+ font-size: 1.4em;
61
+ }
62
+ .dark .clue-1.clue-2 {
63
+ background: linear-gradient(45deg, darkred 50%, darkgreen 50%);
64
+ }
65
+ .dark .clue-1.clue-3 {
66
+ background: linear-gradient(45deg, darkred 50%, darkblue 50%);
67
+ }
68
+ .dark .clue-2.clue-3 {
69
+ background: linear-gradient(45deg, darkgreen 50%, darkblue 50%);
70
+ }
71
+
72
+
73
+ .cell.clue {
74
+ -webkit-animation: glow 1s ease-in-out infinite alternate;
75
+ -moz-animation: glow 1s ease-in-out infinite alternate;
76
+ animation: glow 1s ease-in-out infinite alternate;
77
+ }
78
+ @-webkit-keyframes glow {
79
+ from {
80
+ opacity: 1;
81
+ }
82
+ to {
83
+ opacity: 0.75;
84
+ }
85
+ }
86
+ #clue1 textarea, #clue2 textarea, #clue3 textarea {
87
+ font-weight: bold;
88
+ font-size: 1.4em;
89
+ }
90
+
91
+ .textspan {
92
+ display: block !important;
93
+ margin: 4px 0 0 0 !important;
94
+ font-size: 1.25em;
95
+ }
96
+ .textspan .label {
97
+ display: none;
98
+ }
99
+ .generating {
100
+ display: none !important;
101
+ }
test.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import game_manager
2
+ import random
3
+ random.seed(2)
4
+
5
+ game_manager.SIZE = 8
6
+
7
+ XXX = None
8
+ grid = [
9
+ [XXX, "s", XXX, XXX, XXX, XXX, XXX, XXX],
10
+ [XXX, "h", XXX, XXX, XXX, "t", XXX, XXX],
11
+ ["l", "o", "n", "e", "l", "y", XXX, XXX],
12
+ [XXX, "n", XXX, XXX, XXX, "l", XXX, XXX],
13
+ [XXX, "e", XXX, XXX, XXX, "e", "n", "d"],
14
+ [XXX, XXX, XXX, XXX, XXX, "r", XXX, XXX],
15
+ [XXX, XXX, XXX, XXX, XXX, XXX, XXX, XXX],
16
+ [XXX, XXX, XXX, XXX, XXX, XXX, XXX, XXX],
17
+ ]
18
+
19
+ clues = game_manager.find_clues(grid, 7)
20
+ for c in clues:
21
+ print(c)
wordmaker.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from nltk import FreqDist
2
+ from nltk.corpus import brown
3
+ from nltk.stem.lancaster import LancasterStemmer
4
+ import json
5
+
6
+ WORD_LIMIT = 10000
7
+ MIN_WORD_SIZE, MAX_WORD_SIZE = 4, 10
8
+
9
+ # stem = LancasterStemmer()
10
+ # frequency_list = FreqDist(i.lower() for i in brown.words())
11
+ # words = [
12
+ # w.lower()
13
+ # for w, _ in frequency_list.most_common()[:WORD_LIMIT]
14
+ # if w.isalpha() and len(w) >= MIN_WORD_SIZE and len(w) <= MAX_WORD_SIZE
15
+ # ]
16
+ # stem_to_words = {}
17
+ # for word in words:
18
+ # stemmed = stem.stem(word)
19
+ # if stemmed not in stem_to_words:
20
+ # stem_to_words[stemmed] = set()
21
+ # stem_to_words[stemmed].add(word)
22
+
23
+ # final_words = []
24
+ # for stem, words in stem_to_words.items():
25
+ # shortest = min(words, key=len)
26
+ # final_words.append(shortest)
27
+
28
+ # with open("words.json", "w") as f:
29
+ # f.write(json.dumps(final_words))
30
+
31
+ with open("jeopardy.json", "r") as f:
32
+ jeopardy = json.loads(f.read())
33
+
34
+ answers = set()
35
+ for row in jeopardy:
36
+ answer = row["answer"].lower()
37
+ if not answer.isalpha():
38
+ continue
39
+ if answer.startswith("the "):
40
+ answer = answer[4:]
41
+ elif answer.startswith("a "):
42
+ answer = answer[2:]
43
+ if len(answer) < MIN_WORD_SIZE or len(answer) > MAX_WORD_SIZE:
44
+ continue
45
+ answers.add(answer)
46
+
47
+ with open("words.json", "w") as f:
48
+ f.write(json.dumps(list(answers)))
words.json ADDED
The diff for this file is too large to render. See raw diff