Initial commit

This commit is contained in:
Luke Hubmayer-Werner 2022-02-10 19:38:18 +10:30
commit a36b57ed2e
16 changed files with 16016 additions and 0 deletions

560
Game.gd Normal file
View File

@ -0,0 +1,560 @@
extends Node
const THEME_EMPTY := preload('res://letter_empty.tres')
const THEME_ABSENT := preload('res://letter_absent.tres')
const THEME_PRESENT := preload('res://letter_present.tres')
const THEME_CORRECT := preload('res://letter_correct.tres')
const WORD_LENGTH := 5
const MAX_TURNS := 6
var required_charmask := 0
var absent_charmask := 0
var charmask_bans := []
onready var GuessesPanel := $'/root/Control/VBoxContainer/GuessesPanel'
onready var KeyboardPanel := $'/root/Control/VBoxContainer/KeyboardPanel'
onready var PlayGameDialog := $'/root/Control/PlayGameDialog'
onready var NewGameButton := $'/root/Control/PlayGameDialog/NewGameButton'
var WordContainers: Array # Nodes
var WordStrings: Array = [] # Strings
var WordMasks: Array = [] # Mask form
var WordChars: Array = [] # Mask form
var Words: Array = [] # Entered words, string form
var WordsMaskLookup: Dictionary = {}
var AllValidWords: Array = []
var ValidWords: Array = []
var BotOpeners: Array = []
var botopener: PoolStringArray
var working_word = ''
var game_is_over = false
func _generate_WordsMaskLookup_recursive(key:=0, next_key:=0, depth:=5): # This is many orders of magnitude slower than Python, let alone Rust :/
for k in range(next_key, 26):
var key2 = key | (1<<k)
var wordslist = []
for w in WordsMaskLookup[key]:
if WordMasks[w] & key2 == key2:
wordslist.push_back(w)
if len(wordslist) > 0:
WordsMaskLookup[key2] = PoolIntArray(wordslist)
if depth > 0:
_generate_WordsMaskLookup_recursive(key2, next_key+1, depth-1)
func _generate_WordsMaskLookup(): # This seems fine perf-wise
print('Generating WordsMask cache')
for k1 in range(0, 26):
var key1 = (1<<k1)
var wl1 = []
for w in WordsMaskLookup[0]:
if WordMasks[w] & key1 == key1:
wl1.push_back(w)
if len(wl1) > 0:
WordsMaskLookup[key1] = wl1
for k2 in range(k1+1, 26):
var key2 = key1 | (1<<k2)
var wl2 = []
for w in WordsMaskLookup[key1]:
if WordMasks[w] & key2 == key2:
wl2.push_back(w)
if len(wl2) > 0:
WordsMaskLookup[key2] = wl2
for k3 in range(k2+1, 26):
var key3 = key2 | (1<<k3)
var wl3 = []
for w in WordsMaskLookup[key2]:
if WordMasks[w] & key3 == key3:
wl3.push_back(w)
if len(wl3) > 0:
WordsMaskLookup[key3] = wl3
for k4 in range(k3+1, 26):
var key4 = key3 | (1<<k4)
var wl4 = []
for w in WordsMaskLookup[key3]:
if WordMasks[w] & key4 == key4:
wl4.push_back(w)
if len(wl4) > 0:
WordsMaskLookup[key4] = wl4
for k5 in range(k4+1, 26):
var key5 = key4 | (1<<k5)
var wl5 = []
for w in WordsMaskLookup[key4]:
if WordMasks[w] & key5 == key5:
wl5.push_back(w)
if len(wl5) > 0:
WordsMaskLookup[key5] = wl5
func _setup_globals() -> void:
print('Loading dictionary')
var file = File.new()
match file.open('res://WORDLE', File.READ):
OK:
WordStrings = file.get_as_text().split('\n', false)
var err:
get_tree().quit(err)
print('Loading openers')
match file.open('res://opening_words.txt', File.READ):
OK:
for line in file.get_as_text().split('\n', false):
BotOpeners.push_back(PoolStringArray(line.split(',')))
var err:
get_tree().quit(err)
for s in WordStrings:
var w = str2Word(s)
AllValidWords.push_back(w)
WordMasks.push_back(w[1])
WordChars.push_back(w[2])
var w_idxs = []
for i in len(AllValidWords):
w_idxs.push_back(i)
WordsMaskLookup[0] = w_idxs
_generate_WordsMaskLookup()
func new_game() -> void:
PlayGameDialog.hide()
print('Preparing new game')
ValidWords = AllValidWords.slice(0, len(AllValidWords)-1)
charmask_bans = []
for i in WORD_LENGTH:
charmask_bans.push_back(0)
required_charmask = 0
absent_charmask = 0
botopener = BotOpeners[randi()%(len(BotOpeners))]
Words = []
for guess in WordContainers:
for button in guess.letter_btns:
button.text = ''
button.theme = THEME_EMPTY
for child in button.get_children():
child.queue_free()
game_is_over = false
update_keyboard()
bot_guess()
func _ready() -> void:
WordContainers = GuessesPanel.get_children().slice(2, -1)
assert(len(WordContainers)%2 == 0)
_setup_globals()
print('Cache completed')
randomize()
print('RNG seeded')
# yield(KeyboardPanel, 'ready')
print('Skinning keyboard')
update_keyboard()
NewGameButton.connect('pressed', self, 'new_game')
print('Ready to play')
func game_over() -> void:
game_is_over = true
var guesses := int(len(Words)/2)
var ws := len(ValidWords)
var endstr := ''
if guesses <= 1:
endstr = 'DEFEAT:\nThe goal is to pick different words...'
elif guesses < 6:
endstr = 'DEFEAT:\nYou were caught by the bot in %d guesses!'%guesses
if ws > 1:
endstr = endstr + '\nYou did have %d other words though...'%ws
else:
endstr = 'VICTORY:\nYou escaped the bot with %d words left to hide behind!'%ws
PlayGameDialog.get_child(0).text = endstr
PlayGameDialog.show()
func _exhaustive_search_best() -> String:
print('Doing a full exhaustive search')
var best := 0
var best_remainder := 1<<20
for idx in len(WordMasks):
var guessmask: int = WordMasks[idx]
var guesschars: Array = WordChars[idx]
var worst_remainder := 0
for soli in len(ValidWords):
var solw: Array = ValidWords[soli]
var remainder := 0
var reqmask: int = required_charmask | (guessmask & solw[1])
var banmask: int = absent_charmask | (guessmask & ~solw[1])
var charmasks := []
var solc: Array = solw[2]
for c in WORD_LENGTH:
if solc[c] == guesschars[c]:
charmasks.push_back(~solc[c])
else:
charmasks.push_back(charmask_bans[c] | guesschars[c])
for w in ValidWords:
if (w[1] & reqmask == reqmask) and (w[1] & banmask == 0):
var remains := true
var wc = w[2]
for c in WORD_LENGTH:
if wc[c] & charmasks[c] != 0:
remains = false
break
if remains:
remainder += 1
worst_remainder = max(worst_remainder, remainder)
if worst_remainder < best_remainder:
best = idx
best_remainder = worst_remainder
if best_remainder == 1:
break
return WordStrings[best]
func _exhaustive_search_fast() -> String:
print('Doing a fast exhaustive search')
var best := 0
var best_remainder := 1<<30
for idx in len(WordMasks):
var guessmask: int = WordMasks[idx]
var worst_remainder := 0
for soli in len(ValidWords):
var solw: Array = ValidWords[soli]
var remainder := 0
var reqmask: Array = required_charmask | (guessmask & solw[1])
var banmask: Array = absent_charmask | (guessmask & ~solw[1])
for w in ValidWords:
if (w[1] & reqmask == reqmask) and (w[1] & banmask == 0):
remainder += 1
worst_remainder = max(worst_remainder, remainder)
if worst_remainder < best_remainder:
best = idx
best_remainder = worst_remainder
if best_remainder == 1:
break
return WordStrings[best]
func _eliminative_search() -> String:
print('Doing an eliminative search')
var best := 0
var untested_mask: int = ~(required_charmask | absent_charmask) & (1<<26 - 1)
var highest_key = 0
for key in WordsMaskLookup.keys():
if key & untested_mask > highest_key:
highest_key = key
var indices = WordsMaskLookup[highest_key]
best = indices[randi()%len(indices)]
return WordStrings[best]
func bot_guess() -> void:
assert(len(Words)%2 == 0)
if game_is_over:
return
var words_left = len(ValidWords)
var lw = len(Words)
var guess = botopener[0]
if lw == 0:
guess = botopener[0]
elif lw == 2 and words_left > 20:
guess = botopener[1]
elif words_left <= 2: # 50:50
match randi()%2:
0: guess = Words[-1] # Make a cool effect for when it peeks answer to pressure you?
_: guess = ValidWords[randi()%words_left][0] # This is fine
elif words_left > 100: # Favor mass elimination
match randi()%100:
0: guess = Words[-1] # Make a cool effect for when it peeks answer to pressure you?
1,2,3: guess = ValidWords[randi()%words_left][0] # Maybe adjust this?
_: guess = _eliminative_search()
elif words_left > 30: # Maybe some reasonably fast checking?
match randi()%80:
0: guess = Words[-1] # Make a cool effect for when it peeks answer to pressure you?
1,2,3: guess = ValidWords[randi()%words_left][0] # Maybe adjust this?
_: guess = _exhaustive_search_fast()
else: # Go exhaustive bestcase
match randi()%50:
0: guess = Words[-1] # Make a cool effect for when it peeks answer to pressure you?
1,2,3: guess = ValidWords[randi()%words_left][0] # Losing move tbh
_: guess = _exhaustive_search_best()
push_word(guess)
func _exhaustive_answer_judgement(solution: String) -> String:
print('Doing a full exhaustive answer grade')
assert(len(Words)%2 == 1)
var guess: Array = str2Word(Words[-1])
var guessmask: int = guess[1]
var guesschars: Array = guess[2]
var results := []
var chosen_result := 0
for ans in ValidWords:
var remainder := 0
var reqmask: int = required_charmask | (guessmask & ans[1])
var banmask: int = absent_charmask | (guessmask & ~ans[1])
var charmasks := []
var ansc: Array = ans[2]
for c in WORD_LENGTH:
if ansc[c] == guesschars[c]:
charmasks.push_back(~ansc[c])
else:
charmasks.push_back(charmask_bans[c] | guesschars[c])
for w in ValidWords:
if (w[1] & reqmask == reqmask) and (w[1] & banmask == 0):
var remains := true
var wc = w[2]
for c in WORD_LENGTH:
if wc[c] & charmasks[c] != 0:
remains = false
break
if remains:
remainder += 1
results.push_back(remainder)
if ans[0] == solution:
chosen_result = remainder
# Note: we don't care about returning which word was best, just the possible scores
results.sort()
var sol_position = float(results.rfind(chosen_result))/len(results)
if sol_position > 0.97 and len(results) > 200:
return '!!'
if sol_position > 0.9:
return '!'
elif sol_position > 0.5:
return ''
elif sol_position > 0.4:
return '?!'
elif sol_position > 0.2:
return '?'
else:
return '??'
func _fast_answer_judgement(solution: String) -> String:
print('Doing a fast exhaustive answer grade')
assert(len(Words)%2 == 1)
var guess: Array = str2Word(Words[-1])
var guessmask: int = guess[1]
var results := []
var chosen_result := 0
for ans in ValidWords:
var remainder := 0
var reqmask: int = required_charmask | (guessmask & ans[1])
var banmask: int = absent_charmask | (guessmask & ~ans[1])
for w in ValidWords:
if (w[1] & reqmask == reqmask) and (w[1] & banmask == 0):
remainder += 1
results.push_back(remainder)
if ans[0] == solution:
chosen_result = remainder
# Note: we don't care about returning which word was best, just the possible scores
results.sort()
var sol_position = float(results.rfind(chosen_result))/len(results)
if sol_position > 0.9:
return '!'
elif sol_position > 0.4:
return ''
elif sol_position > 0.25:
return '?!'
elif sol_position > 0.1:
return '?'
else:
return '??'
func _estimated_answer_judgement(solution: String) -> String:
print('Estimating answer grade')
assert(len(Words)%2 == 1)
var guess: Array = str2Word(Words[-1])
var guessmask: int = guess[1]
var guesschars: Array = guess[2]
var ans = str2Word(solution)
var remainder := 0
var reqmask: int = required_charmask | (guessmask & ans[1])
var banmask: int = absent_charmask | (guessmask & ~ans[1])
for w in ValidWords:
if (w[1] & reqmask == reqmask) and (w[1] & banmask == 0):
remainder += 1
var sol_position = float(remainder)/len(ValidWords)
if sol_position > 0.75:
return '!!'
elif sol_position > 0.5:
return '!'
elif sol_position > 0.09:
return ''
elif sol_position > 0.025:
return '?!'
elif sol_position > 0.01:
return '?'
else:
return '??'
func _answer_judgement(solution: String) -> String:
if len(ValidWords) < 2:
return '#'
elif len(ValidWords) > 2400:
return _estimated_answer_judgement(solution)
elif len(ValidWords) > 300:
return _fast_answer_judgement(solution)
else:
return _exhaustive_answer_judgement(solution)
func is_word_valid(word: Array) -> bool:
if word[1] & required_charmask != required_charmask:
return false
for i in WORD_LENGTH:
if word[2][i] & charmask_bans[i] != 0:
return false
return true
func push_word(word: String) -> void:
update_word(word)
Words.push_back(word)
if len(Words)%2 == 0: # Just entered a solution
var guess: String = Words[-2]
var solution: String = Words[-1]
if guess == solution:
game_over()
return
for i in WORD_LENGTH:
var gcm = ascii2cm(guess[i])
if guess[i] == solution[i]:
charmask_bans[i] = ~gcm
required_charmask |= gcm
elif guess[i] in solution:
charmask_bans[i] |= gcm
required_charmask |= gcm
else:
absent_charmask |= gcm
for j in WORD_LENGTH:
charmask_bans[j] |= gcm
for i in range(len(ValidWords)-1, -1, -1):
if not is_word_valid(ValidWords[i]):
ValidWords.remove(i)
update_keyboard()
func update_keyboard() -> void:
for button in KeyboardPanel.buttons:
var cm = ascii2cm(button.text)
if cm & absent_charmask != 0:
button.theme = THEME_ABSENT
elif cm & required_charmask != 0:
button.theme = THEME_PRESENT
else:
button.theme = THEME_EMPTY
func update_word(word: String) -> void:
WordContainers[len(Words)].set_word(word)
if len(Words)%2 == 1: # Solution entry
WordContainers[len(Words)-1].grade_word(word)
WordContainers[len(Words)].grade_solution(Words[-1])
func _input(event: InputEvent) -> void:
if event is InputEventKey and not event.pressed:
print('Processing InputEvent', event)
for i in event.unicode:
randi()
var c = char(event.unicode).capitalize()
var cm = ascii2cm(c)
if cm > 0:
if len(working_word) < WORD_LENGTH:
if cm & charmask_bans[len(working_word)] == 0:
working_word += c
else:
if cm & charmask_bans[-1] == 0:
working_word[-1] = c
update_word(working_word)
else:
match event.scancode:
KEY_BACKSPACE:
working_word.erase(len(working_word)-1, 1)
update_word(working_word)
KEY_ENTER:
if (len(working_word) == WORD_LENGTH) and working_word in WordStrings:
var judgement = _answer_judgement(working_word)
push_word(working_word)
if judgement != '':
var lbl := Label.new()
lbl.text = judgement
lbl.align = Label.ALIGN_CENTER
lbl.anchor_left = 1.0
lbl.grow_horizontal = Control.GROW_DIRECTION_BOTH
lbl.margin_top = 1
lbl.add_color_override("font_color_shadow", Color(0, 0, 0))
lbl.add_constant_override("shadow_offset_x", 1)
lbl.add_constant_override("shadow_offset_y", 1)
lbl.add_constant_override("shadow_as_outline", 1)
match lbl.text:
'?!': lbl.add_color_override("font_color", Color(1, 0.5, 0.5))
'?': lbl.add_color_override("font_color", Color(1, 0.25, 0.25))
'??': lbl.add_color_override("font_color", Color(1, 0, 0))
WordContainers[len(Words)-1].letter_btns[-1].add_child(lbl)
working_word = ''
if len(Words) < 12:
bot_guess()
else:
game_over()
_:
pass
func _osk_pressed(c):
c = c[0]
if c == '':
working_word.erase(len(working_word)-1, 1)
update_word(working_word)
else:
var cm = ascii2cm(c)
if len(working_word) < WORD_LENGTH:
if cm & charmask_bans[len(working_word)] == 0:
working_word += c
else:
if cm & charmask_bans[-1] == 0:
working_word[-1] = c
update_word(working_word)
func ascii2cm(c: String) -> int:
match c:
'S': return 1<<25
'E': return 1<<24
'A': return 1<<23
'O': return 1<<22
'R': return 1<<21
'I': return 1<<20
'L': return 1<<19
'T': return 1<<18
'N': return 1<<17
'U': return 1<<16
'D': return 1<<15
'Y': return 1<<14
'C': return 1<<13
'P': return 1<<12
'M': return 1<<11
'H': return 1<<10
'G': return 1<<9
'B': return 1<<8
'K': return 1<<7
'W': return 1<<6
'F': return 1<<5
'V': return 1<<4
'Z': return 1<<3
'J': return 1<<2
'X': return 1<<1
'Q': return 1<<0
_: return 0
func popcnt(x: int) -> int:
var c = 0
while x != 0:
x &= x-1
c += 1
return c
func str2Word(s: String) -> Array:
var chars = []
var mask = 0
for i in WORD_LENGTH:
var cm = ascii2cm(s[i])
mask |= cm
chars.push_back(cm)
return [s, mask, chars]

45
KeyboardPanel.gd Normal file
View File

@ -0,0 +1,45 @@
tool
extends PanelContainer
const BACKSPACE = ''
const QWERTY := ['QWERTYUIOP', 'ASDFGHJKL', 'ZXCVBNM'+BACKSPACE]
const ALPHABETICAL := ['ABCDEFGHI', 'JKLMNOPQR', 'STUVWXYZ']
const BY_FREQUENCY := ['SEAORILTN', 'UDYCPMHGB', 'KFWVZJXQ']
enum KeyboardLayout {
QWERTY,
ALPHABETICAL,
BY_FREQUENCY
}
var rows := []
var buttons := []
var vcont
func make_buttons() -> void:
# delete children and then
vcont = VBoxContainer.new()
for r in QWERTY:
var row = HBoxContainer.new()
row.alignment = BoxContainer.ALIGN_CENTER
for c in r:
var button = Button.new()
button.text = c
if c == BACKSPACE:
button.text = '<-'
button.connect('pressed', Game, '_osk_pressed', [c])
button.rect_min_size = Vector2(32, 32)
buttons.append(button)
row.add_child(button)
rows.push_back(row)
vcont.add_child(row)
add_child(vcont)
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
make_buttons()
# Called every frame. 'delta' is the elapsed time since the previous frame.
#func _process(delta: float) -> void:
# pass

137
Main.tscn Normal file
View File

@ -0,0 +1,137 @@
[gd_scene load_steps=3 format=2]
[ext_resource path="res://KeyboardPanel.gd" type="Script" id=1]
[ext_resource path="res://WordContainer.tscn" type="PackedScene" id=2]
[node name="Control" type="Control"]
anchor_right = 1.0
anchor_bottom = 1.0
__meta__ = {
"_edit_use_anchors_": false
}
[node name="VBoxContainer" type="VBoxContainer" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
__meta__ = {
"_edit_use_anchors_": false
}
[node name="GuessesPanel" type="GridContainer" parent="VBoxContainer"]
margin_right = 420.0
margin_bottom = 478.0
size_flags_horizontal = 3
size_flags_vertical = 3
columns = 2
[node name="lbl_guess" type="Label" parent="VBoxContainer/GuessesPanel"]
margin_right = 208.0
margin_bottom = 14.0
size_flags_horizontal = 3
text = "Computer's Guess"
align = 1
[node name="lbl_solution" type="Label" parent="VBoxContainer/GuessesPanel"]
margin_left = 212.0
margin_right = 420.0
margin_bottom = 14.0
size_flags_horizontal = 3
text = "Your Solution"
align = 1
[node name="WordContainer" parent="VBoxContainer/GuessesPanel" instance=ExtResource( 2 )]
margin_right = 208.0
[node name="WordContainer2" parent="VBoxContainer/GuessesPanel" instance=ExtResource( 2 )]
margin_left = 212.0
margin_right = 420.0
[node name="WordContainer3" parent="VBoxContainer/GuessesPanel" instance=ExtResource( 2 )]
margin_top = 54.0
margin_right = 208.0
margin_bottom = 86.0
[node name="WordContainer4" parent="VBoxContainer/GuessesPanel" instance=ExtResource( 2 )]
margin_left = 212.0
margin_top = 54.0
margin_right = 420.0
margin_bottom = 86.0
[node name="WordContainer5" parent="VBoxContainer/GuessesPanel" instance=ExtResource( 2 )]
margin_top = 90.0
margin_right = 208.0
margin_bottom = 122.0
[node name="WordContainer6" parent="VBoxContainer/GuessesPanel" instance=ExtResource( 2 )]
margin_left = 212.0
margin_top = 90.0
margin_right = 420.0
margin_bottom = 122.0
[node name="WordContainer7" parent="VBoxContainer/GuessesPanel" instance=ExtResource( 2 )]
margin_top = 126.0
margin_right = 208.0
margin_bottom = 158.0
[node name="WordContainer8" parent="VBoxContainer/GuessesPanel" instance=ExtResource( 2 )]
margin_left = 212.0
margin_top = 126.0
margin_right = 420.0
margin_bottom = 158.0
[node name="WordContainer9" parent="VBoxContainer/GuessesPanel" instance=ExtResource( 2 )]
margin_top = 162.0
margin_right = 208.0
margin_bottom = 194.0
[node name="WordContainer10" parent="VBoxContainer/GuessesPanel" instance=ExtResource( 2 )]
margin_left = 212.0
margin_top = 162.0
margin_right = 420.0
margin_bottom = 194.0
[node name="WordContainer11" parent="VBoxContainer/GuessesPanel" instance=ExtResource( 2 )]
margin_top = 198.0
margin_right = 208.0
margin_bottom = 230.0
[node name="WordContainer12" parent="VBoxContainer/GuessesPanel" instance=ExtResource( 2 )]
margin_left = 212.0
margin_top = 198.0
margin_right = 420.0
margin_bottom = 230.0
[node name="KeyboardPanel" type="PanelContainer" parent="VBoxContainer"]
margin_top = 482.0
margin_right = 420.0
margin_bottom = 600.0
size_flags_horizontal = 3
script = ExtResource( 1 )
[node name="PlayGameDialog" type="ColorRect" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
color = Color( 0, 0, 0, 0.705882 )
[node name="Label" type="Label" parent="PlayGameDialog"]
anchor_right = 1.0
anchor_bottom = 1.0
margin_bottom = -300.0
text = "A bot is trying to hunt you down!
When it guesses a word, choose a word to hide behind.
You can change words each turn,
but you can't contradict your previous clues!
Try to last 6 turns!"
align = 1
valign = 1
[node name="NewGameButton" type="Button" parent="PlayGameDialog"]
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
margin_top = -300.0
text = "Play New Game"
__meta__ = {
"_edit_use_anchors_": false
}

12972
WORDLE Normal file

File diff suppressed because it is too large Load Diff

51
WordContainer.gd Normal file
View File

@ -0,0 +1,51 @@
extends HBoxContainer
const WORD_LENGTH := 5
const THEME_EMPTY := preload('res://letter_empty.tres')
const THEME_ABSENT := preload('res://letter_absent.tres')
const THEME_PRESENT := preload('res://letter_present.tres')
const THEME_CORRECT := preload('res://letter_correct.tres')
var word := ''
var solution := ''
var guess := ''
onready var letter_btns := get_children()
func set_word(word: String):
self.word = word
for i in WORD_LENGTH:
letter_btns[i].text = word[i] if len(word) > i else ''
func grade_word(solution: String):
assert(self.word.length() == WORD_LENGTH)
if solution.length() < WORD_LENGTH:
self.solution = solution + ' '.repeat(WORD_LENGTH - len(solution))
elif solution.length() == WORD_LENGTH:
self.solution = solution
else:
self.solution = solution.left(WORD_LENGTH)
for i in WORD_LENGTH:
if self.word[i] == self.solution[i]:
self.letter_btns[i].theme = THEME_CORRECT
elif self.word[i] in self.solution:
self.letter_btns[i].theme = THEME_PRESENT
else:
self.letter_btns[i].theme = THEME_ABSENT
func grade_solution(guess: String):
if guess.length() < WORD_LENGTH:
self.guess = guess + ' '.repeat(WORD_LENGTH - len(guess))
elif guess.length() == WORD_LENGTH:
self.guess = guess
else:
self.guess = guess.left(WORD_LENGTH)
for i in WORD_LENGTH:
if len(self.word) > i:
if self.word[i] == self.guess[i]:
self.letter_btns[i].theme = THEME_CORRECT
elif self.word[i] in self.guess:
self.letter_btns[i].theme = THEME_PRESENT
else:
self.letter_btns[i].theme = THEME_EMPTY
else:
self.letter_btns[i].theme = THEME_EMPTY

45
WordContainer.tscn Normal file
View File

@ -0,0 +1,45 @@
[gd_scene load_steps=3 format=2]
[ext_resource path="res://letter_empty.tres" type="Theme" id=1]
[ext_resource path="res://WordContainer.gd" type="Script" id=2]
[node name="WordContainer" type="HBoxContainer"]
margin_top = 18.0
margin_right = 198.0
margin_bottom = 50.0
theme = ExtResource( 1 )
alignment = 1
script = ExtResource( 2 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="WordChar1" type="Button" parent="."]
margin_left = 11.0
margin_right = 43.0
margin_bottom = 32.0
rect_min_size = Vector2( 32, 32 )
[node name="WordChar2" type="Button" parent="."]
margin_left = 47.0
margin_right = 79.0
margin_bottom = 32.0
rect_min_size = Vector2( 32, 32 )
[node name="WordChar3" type="Button" parent="."]
margin_left = 83.0
margin_right = 115.0
margin_bottom = 32.0
rect_min_size = Vector2( 32, 32 )
[node name="WordChar4" type="Button" parent="."]
margin_left = 119.0
margin_right = 151.0
margin_bottom = 32.0
rect_min_size = Vector2( 32, 32 )
[node name="WordChar5" type="Button" parent="."]
margin_left = 155.0
margin_right = 187.0
margin_bottom = 32.0
rect_min_size = Vector2( 32, 32 )

29
letter_absent.tres Normal file
View File

@ -0,0 +1,29 @@
[gd_resource type="Theme" load_steps=3 format=2]
[ext_resource path="res://stylebox_letter_normal_absent.tres" type="StyleBox" id=1]
[sub_resource type="StyleBoxFlat" id=1]
content_margin_left = 6.0
content_margin_right = 6.0
content_margin_top = 4.0
content_margin_bottom = 4.0
bg_color = Color( 0.18, 0.207, 0.279, 1 )
border_width_left = 1
border_width_top = 1
border_width_right = 1
border_width_bottom = 1
border_color = Color( 0.14, 0.161, 0.217, 1 )
[resource]
Button/colors/font_color = Color( 0.8, 0.8075, 0.8275, 1 )
Button/colors/font_color_disabled = Color( 1, 1, 1, 0.3 )
Button/colors/font_color_focus = Color( 0.88, 0.8845, 0.8965, 1 )
Button/colors/font_color_hover = Color( 0.88, 0.8845, 0.8965, 1 )
Button/colors/font_color_pressed = Color( 0.41, 0.61, 0.91, 1 )
Button/colors/icon_color_hover = Color( 1.15, 1.15, 1.15, 1 )
Button/colors/icon_color_pressed = Color( 0.4715, 0.7015, 1.0465, 1 )
Button/styles/disabled = SubResource( 1 )
Button/styles/focus = ExtResource( 1 )
Button/styles/hover = ExtResource( 1 )
Button/styles/normal = ExtResource( 1 )
Button/styles/pressed = ExtResource( 1 )

29
letter_correct.tres Normal file
View File

@ -0,0 +1,29 @@
[gd_resource type="Theme" load_steps=3 format=2]
[ext_resource path="res://stylebox_letter_normal_correct.tres" type="StyleBox" id=1]
[sub_resource type="StyleBoxFlat" id=1]
content_margin_left = 6.0
content_margin_right = 6.0
content_margin_top = 4.0
content_margin_bottom = 4.0
bg_color = Color( 0.18, 0.207, 0.279, 1 )
border_width_left = 1
border_width_top = 1
border_width_right = 1
border_width_bottom = 1
border_color = Color( 0.14, 0.161, 0.217, 1 )
[resource]
Button/colors/font_color = Color( 0.8, 0.8075, 0.8275, 1 )
Button/colors/font_color_disabled = Color( 1, 1, 1, 0.3 )
Button/colors/font_color_focus = Color( 0.88, 0.8845, 0.8965, 1 )
Button/colors/font_color_hover = Color( 0.88, 0.8845, 0.8965, 1 )
Button/colors/font_color_pressed = Color( 0.41, 0.61, 0.91, 1 )
Button/colors/icon_color_hover = Color( 1.15, 1.15, 1.15, 1 )
Button/colors/icon_color_pressed = Color( 0.4715, 0.7015, 1.0465, 1 )
Button/styles/disabled = SubResource( 1 )
Button/styles/focus = ExtResource( 1 )
Button/styles/hover = ExtResource( 1 )
Button/styles/normal = ExtResource( 1 )
Button/styles/pressed = ExtResource( 1 )

29
letter_empty.tres Normal file
View File

@ -0,0 +1,29 @@
[gd_resource type="Theme" load_steps=3 format=2]
[ext_resource path="res://stylebox_letter_normal_empty.tres" type="StyleBox" id=1]
[sub_resource type="StyleBoxFlat" id=1]
content_margin_left = 6.0
content_margin_right = 6.0
content_margin_top = 4.0
content_margin_bottom = 4.0
bg_color = Color( 0.18, 0.207, 0.279, 1 )
border_width_left = 1
border_width_top = 1
border_width_right = 1
border_width_bottom = 1
border_color = Color( 0.14, 0.161, 0.217, 1 )
[resource]
Button/colors/font_color = Color( 0.8, 0.8075, 0.8275, 1 )
Button/colors/font_color_disabled = Color( 1, 1, 1, 0.3 )
Button/colors/font_color_focus = Color( 0.88, 0.8845, 0.8965, 1 )
Button/colors/font_color_hover = Color( 0.88, 0.8845, 0.8965, 1 )
Button/colors/font_color_pressed = Color( 0.41, 0.61, 0.91, 1 )
Button/colors/icon_color_hover = Color( 1.15, 1.15, 1.15, 1 )
Button/colors/icon_color_pressed = Color( 0.4715, 0.7015, 1.0465, 1 )
Button/styles/disabled = SubResource( 1 )
Button/styles/focus = ExtResource( 1 )
Button/styles/hover = ExtResource( 1 )
Button/styles/normal = ExtResource( 1 )
Button/styles/pressed = ExtResource( 1 )

29
letter_present.tres Normal file
View File

@ -0,0 +1,29 @@
[gd_resource type="Theme" load_steps=3 format=2]
[ext_resource path="res://stylebox_letter_normal_present.tres" type="StyleBox" id=1]
[sub_resource type="StyleBoxFlat" id=1]
content_margin_left = 6.0
content_margin_right = 6.0
content_margin_top = 4.0
content_margin_bottom = 4.0
bg_color = Color( 0.18, 0.207, 0.279, 1 )
border_width_left = 1
border_width_top = 1
border_width_right = 1
border_width_bottom = 1
border_color = Color( 0.14, 0.161, 0.217, 1 )
[resource]
Button/colors/font_color = Color( 0.8, 0.8075, 0.8275, 1 )
Button/colors/font_color_disabled = Color( 1, 1, 1, 0.3 )
Button/colors/font_color_focus = Color( 0.88, 0.8845, 0.8965, 1 )
Button/colors/font_color_hover = Color( 0.88, 0.8845, 0.8965, 1 )
Button/colors/font_color_pressed = Color( 0.41, 0.61, 0.91, 1 )
Button/colors/icon_color_hover = Color( 1.15, 1.15, 1.15, 1 )
Button/colors/icon_color_pressed = Color( 0.4715, 0.7015, 1.0465, 1 )
Button/styles/disabled = SubResource( 1 )
Button/styles/focus = ExtResource( 1 )
Button/styles/hover = ExtResource( 1 )
Button/styles/normal = ExtResource( 1 )
Button/styles/pressed = ExtResource( 1 )

2000
opening_words.txt Executable file

File diff suppressed because it is too large Load Diff

38
project.godot Normal file
View File

@ -0,0 +1,38 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=4
_global_script_classes=[ ]
_global_script_class_icons={
}
[application]
config/name="Word Hunt"
run/main_scene="res://Main.tscn"
config/icon="res://icon.png"
[autoload]
Game="*res://Game.gd"
[display]
window/size/width=420
window/dpi/allow_hidpi=true
window/handheld/orientation="portrait"
[physics]
common/enable_pause_aware_picking=true
[rendering]
quality/driver/driver_name="GLES2"
environment/default_environment="res://default_env.tres"

View File

@ -0,0 +1,13 @@
[gd_resource type="StyleBoxFlat" format=2]
[resource]
content_margin_left = 6.0
content_margin_right = 6.0
content_margin_top = 4.0
content_margin_bottom = 4.0
bg_color = Color( 0.109804, 0.109804, 0.109804, 1 )
border_width_left = 1
border_width_top = 1
border_width_right = 1
border_width_bottom = 1
border_color = Color( 0.125, 0.14375, 0.19375, 1 )

View File

@ -0,0 +1,13 @@
[gd_resource type="StyleBoxFlat" format=2]
[resource]
content_margin_left = 6.0
content_margin_right = 6.0
content_margin_top = 4.0
content_margin_bottom = 4.0
bg_color = Color( 0.447059, 0.439216, 0.631373, 1 )
border_width_left = 1
border_width_top = 1
border_width_right = 1
border_width_bottom = 1
border_color = Color( 0.125, 0.14375, 0.19375, 1 )

View File

@ -0,0 +1,13 @@
[gd_resource type="StyleBoxFlat" format=2]
[resource]
content_margin_left = 6.0
content_margin_right = 6.0
content_margin_top = 4.0
content_margin_bottom = 4.0
bg_color = Color( 0.160784, 0.156863, 0.219608, 1 )
border_width_left = 1
border_width_top = 1
border_width_right = 1
border_width_bottom = 1
border_color = Color( 0.125, 0.14375, 0.19375, 1 )

View File

@ -0,0 +1,13 @@
[gd_resource type="StyleBoxFlat" format=2]
[resource]
content_margin_left = 6.0
content_margin_right = 6.0
content_margin_top = 4.0
content_margin_bottom = 4.0
bg_color = Color( 0.631373, 0.439216, 0.439216, 1 )
border_width_left = 1
border_width_top = 1
border_width_right = 1
border_width_bottom = 1
border_color = Color( 0.125, 0.14375, 0.19375, 1 )