Spaces:
Runtime error
Runtime error
Ali Abid
commited on
Commit
•
2a68adc
1
Parent(s):
8d1c4df
first commit
Browse files- .gitignore +1 -0
- README.md +2 -3
- formatters.py +44 -0
- game_manager.py +259 -0
- prompt.txt +10 -0
- requirements.txt +1 -0
- run.py +102 -0
- style.css +101 -0
- test.py +21 -0
- wordmaker.py +48 -0
- 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:
|
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 += " "
|
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
|
|