forked from iSchool-597DS/2022Spring_Projects
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathmain.py
364 lines (308 loc) · 10.7 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
"""
Source: https://www.youtube.com/watch?v=TsnovVfkafI
"""
import random
from collections import Counter
import pygame
import pandas as pd
from services2 import validate2
import numpy as np
import threading
# AI True/False
AI = True
# FORCE FIRST WORD
FORCE = True
words = []
with open('data/allowed_words.txt') as f:
for line in f:
words.append(line.strip())
possible_answers = []
with open('data/words.txt') as f:
for line in f:
possible_answers.append(line.strip())
ANSWER = random.choice(possible_answers)
# print(ANSWER)
def prepareWordDF(words):
"""
Converts words to a pandas dataframe containing 5 columns for each letter.
:param words: list of words
:return: Word DataFrame
"""
cols = []
for col in np.arange(5):
cols.append(f"c{col}")
df = pd.DataFrame(columns = cols)
for idx, word in enumerate(words):
print(f"{idx+1}/{len(words)}", end="\r")
df = pd.concat([df, pd.DataFrame([list(word.strip())], columns=cols)], ignore_index = True, axis = 0)
return df
wordDF = prepareWordDF(possible_answers)
searchSpace = wordDF.copy()
xGBoard = pd.read_csv("xGTable.csv")
AI_GUESS = ""
WIDTH = 600
HEIGHT = 800
MARGIN = 10
T_MARGIN = 65
B_MARGIN = 100
LR_MARGIN = 100
WHITE = (255, 255, 255)
GREY = (58, 58, 60)
LIGHT_GREY = (129, 131, 132)
GREEN = (83, 141, 78)
YELLOW = (181, 159, 59)
INPUT = ""
GUESSES = []
COLORS = []
TRINARY_COLORS = []
KEYBOARD = ["QWERTYUIOP", "ASDFGHJKL", "ZXCVBNM"]
ALPHABET_DICT = {}
GAME_OVER = False
pygame.init()
pygame.font.init()
pygame.display.set_caption("Wordle")
SQ_SIZE = (WIDTH - 4 * MARGIN - 2 * LR_MARGIN) // 5
FONT = pygame.font.SysFont("free sans bold", SQ_SIZE)
FONT_SMALL = pygame.font.SysFont("free sans bold", SQ_SIZE // 2)
def validate(guess):
"""
Validates the current word with colors
:param guess: The current guess
:return: list of colors for each character
"""
colors = [GREY, GREY, GREY, GREY, GREY]
character_matches = dict(Counter(list(ANSWER)) & Counter(list(guess)))
for i in range(5):
if ANSWER[i] == guess[i]:
colors[i] = GREEN
character_matches[guess[i]] -= 1
if guess[i] in character_matches.keys() and colors[i] == GREY:
if character_matches[guess[i]] > 0:
colors[i] = YELLOW
character_matches[guess[i]] -= 1
return colors
def color2trinary(colors):
"""
Converts color list to trinary string
:param colors: Color list
:return: Trinary string
"""
trinary_colors = [0, 0, 0, 0, 0]
for i in range(len(colors)):
if colors[i] == GREY:
trinary_colors[i] = 0
elif colors[i] == GREEN:
trinary_colors[i] = 1
elif colors[i] == YELLOW:
trinary_colors[i] = 2
return "".join(str(e) for e in trinary_colors)
def filteredSearch(df, constraints, guess):
"""
Filters the search space based on previous feedback
:param df: Word DataFrame
:param constraints: Previous Feedback
:param guess: Current guess
:return: Filtered search space
"""
cols = []
for col in np.arange(5):
cols.append(f"c{col}")
filtered = df
searchList = {}
for idx, constrain in enumerate(list(constraints)):
if constrain == "2":
filtered = filtered[filtered.eq(guess[idx]).any(1)]
filtered = filtered[filtered[f"c{idx}"] != guess[idx]]
if constrain == "1":
filtered = filtered[filtered[f"c{idx}"] == guess[idx]]
if constrain == "0":
if guess[idx] not in searchList:
filtered = filtered[filtered.eq(guess[idx]).any(1) == False]
else:
filtered = filtered[filtered[f"c{idx}"] != guess[idx]]
searchList[guess[idx]] = guess[idx]
return filtered.reset_index()[cols]
def xMap(guess, df):
"""
Creates a map of most frequent letters used in 5 letter words
:param guess: Current guess
:param df: Word DF
:return: Frequency Map
"""
fbMap = {}
for _, row in df.iterrows():
word = "".join(row)
_, fb = validate2(word, guess)
if fb in fbMap:
fbMap[fb] += 1
else:
fbMap[fb] = 1
return fbMap
def getxG(guess, df):
"""
Implementing the expectation equation
:param guess: Current guess
:param df: Word DF
:return: Expected Information - (sums of the probability of encountering a particular response
times the the negative log of this probability)
"""
fbMap = xMap(guess, df)
x = 0
for info in fbMap:
p = fbMap[info] / df.shape[0]
x += p * - np.log2(p)
return x
def getBestWords(df):
"""
Getting best guess from expected Information
:param df: Word DF
:return: List of words sorted by expectation
"""
xGBoard = pd.DataFrame(columns=["word", "xG"])
for idx, row in df.iterrows():
word = "".join(row)
xG = getxG(word, df)
xGBoard = xGBoard.append({"word": word, "xG": xG}, ignore_index = True)
return xGBoard
def infoTheorySolver(df, word):
"""
Helper function to call all functions.
:param df: Word DF
:param word: First word
:return: List of possible guesses
"""
xGBoard = pd.read_csv("xGTable.csv")
guesses = 0
isDone = False
while not isDone:
guess = xGBoard.sort_values(by="xG", ascending=False).iloc[0]["word"]
isDone, fb = validate(word, guess)
df = filteredSearch(df, fb, guess)
xGBoard = getBestWords(df)
guesses += 1
return guesses
def getGuess(idx):
"""
Returns guess from list of guesses and resets everything after a guess
:param idx: Index of current guess out of 6
"""
global searchSpace
global xGBoard
global AI_GUESS
print(idx)
if idx == 0:
if FORCE:
AI_GUESS = xGBoard.sort_values(by="xG", ascending=False).iloc[0]["word"]
else:
AI_GUESS = "raise"
else:
print(searchSpace.shape)
print(TRINARY_COLORS[-1])
print(GUESSES[-1])
print(GUESSES)
print(TRINARY_COLORS)
searchSpace = filteredSearch(searchSpace, str(TRINARY_COLORS[-1]), GUESSES[-1].lower())
# print(searchSpace.shape)
xGBoard = getBestWords(searchSpace)
AI_GUESS = xGBoard.sort_values(by="xG", ascending=False).iloc[0]["word"]
# create screen
screen = pygame.display.set_mode((WIDTH, HEIGHT))
# animation loop
animating = True
while animating:
# background
screen.fill("black")
letters = pygame.font.SysFont("cambria", SQ_SIZE // 2, bold=True).render("Wordle", False, WHITE)
surface = letters.get_rect(center=(WIDTH // 2, 25))
screen.blit(letters, surface)
pygame.draw.line(screen, LIGHT_GREY, (0, 50), (WIDTH, 50))
y = T_MARGIN
for i in range(6):
x = LR_MARGIN
for j in range(5):
square = pygame.Rect(x, y, SQ_SIZE, SQ_SIZE)
pygame.draw.rect(screen, GREY, square, width=2)
if i < len(GUESSES):
color = COLORS[i][j]
pygame.draw.rect(screen, color, square, border_radius=3)
letter = FONT.render(GUESSES[i][j], False, (255, 255, 255))
surface = letter.get_rect(center=(x + SQ_SIZE // 2, y + SQ_SIZE // 2))
screen.blit(letter, surface)
# user text input next guess
if i == len(GUESSES) and j < len(INPUT):
letter = FONT.render(INPUT[j], False, WHITE)
surface = letter.get_rect(center=(x + SQ_SIZE // 2, y + SQ_SIZE // 2))
screen.blit(letter, surface)
x += SQ_SIZE + MARGIN
y += SQ_SIZE + MARGIN
# show the correct ANSWER after a game over
if len(GUESSES) == 6 and GUESSES[5] != ANSWER:
GAME_OVER = True
letters = pygame.font.SysFont("cambria", SQ_SIZE // 3, bold=True).render(ANSWER.upper(), False, WHITE)
surface = letters.get_rect(center=(WIDTH // 2, y + SQ_SIZE // 6))
screen.blit(letters, surface)
y = y + SQ_SIZE // 2
for keys in KEYBOARD:
x = 250 + ((LR_MARGIN - (len(keys) * 50)) // 2)
for char in keys:
color = LIGHT_GREY
if char in ALPHABET_DICT.keys():
color = ALPHABET_DICT[char]
pygame.draw.rect(screen, color, pygame.Rect(x, y, 45, 55), border_radius=3)
letter = FONT_SMALL.render(char, False, (255, 255, 255))
surface = letter.get_rect(center=(x + 45 / 2, y + 55 / 2))
screen.blit(letter, surface)
x += 50
y += 60
# update the screen
pygame.display.flip()
for event in pygame.event.get():
# closing the window stops the animation
if event.type == pygame.QUIT:
animating = False
elif event.type == pygame.KEYDOWN:
if AI and not GAME_OVER:
# Integrate AI HERE
INPUT = random.choice(words).upper()
print("Shape in here")
print(searchSpace.shape)
print("Done")
x = threading.Thread(target=getGuess, args=(len(GUESSES),))
x.start()
pygame.display.flip()
x.join()
INPUT = AI_GUESS.upper()
# escape key to quit animation
if event.type == pygame.K_ESCAPE:
animating = False
# backspace to correct user input
if event.key == pygame.K_BACKSPACE:
if len(INPUT) > 0:
INPUT = INPUT[:len(INPUT) - 1]
# return key to submit a guess
elif event.key == pygame.K_RETURN:
if len(INPUT) == 5 and INPUT.lower() in words:
GUESSES.append(INPUT)
COLORS.append(validate(INPUT.lower()))
TRINARY_COLORS.append(color2trinary(validate(INPUT.lower())))
for g, c in zip(GUESSES, COLORS):
for letter, rgb in zip(g, c):
if letter not in ALPHABET_DICT.keys():
ALPHABET_DICT[letter] = rgb
#
GAME_OVER = True if INPUT == ANSWER.upper() else False
INPUT = ""
# space bar to restart
elif event.key == pygame.K_SPACE:
GAME_OVER = False
ANSWER = random.choice(possible_answers)
print(ANSWER)
GUESSES = []
COLORS = []
TRINARY_COLORS = []
ALPHABET_DICT = {}
INPUT = ""
searchSpace = wordDF.copy()
# regular text input
elif len(INPUT) < 5 and not GAME_OVER:
INPUT = INPUT + event.unicode.upper()