Implement strict mode

This commit is contained in:
Terry Hearst 2024-07-08 23:49:08 -04:00
parent 3107c40c75
commit ec2d9d2af5
3 changed files with 128 additions and 21 deletions

View file

@ -30,6 +30,8 @@ var all_words: Dictionary
var allow_future := false var allow_future := false
var strict_mode := false
func _ready() -> void: func _ready() -> void:
randomize() randomize()

View file

@ -20,6 +20,18 @@ var ended: bool
var won: bool var won: bool
var input_guess: String var input_guess: String
# Array from position -> 1 character string, if the letter is green, else null if not green yet. In strict mode, once a
# green letter is found, it has to be used in all subsequent guesses.
var strict_green_requirements: Array = [] # Array[String or null]
# Array of guessed letters which were green or yellow. In strict mode, these letters must be used in subsequent guesses.
var strict_letter_requirements: Array[String] = []
# Array from position index -> Array of letters which have been found yellow at that index. In strict mode, these yellow
# must not be used at this position in subsequent guesses.
var strict_yellow_restrictions: Array[Array] = [] # Array[Array[String]]
# Array of guessed letters which aren't in the target word. In strict mode, these letters cannot be used in subsequent
# guesses more times than they have been seen as green or yellow letters.
var strict_gray_restrictions: Array[String] = []
@onready var title: Label = %Title @onready var title: Label = %Title
@onready var subtitle: Label = %Subtitle @onready var subtitle: Label = %Subtitle
@onready var letter_grid: GridContainer = %LetterGrid @onready var letter_grid: GridContainer = %LetterGrid
@ -33,6 +45,8 @@ var input_guess: String
@onready var info_text_animation: AnimationPlayer = %InfoTextAnimation @onready var info_text_animation: AnimationPlayer = %InfoTextAnimation
@onready var copied_text_animation: AnimationPlayer = %CopiedTextAnimation @onready var copied_text_animation: AnimationPlayer = %CopiedTextAnimation
@onready var strict_button: CheckButton = %StrictButton
@onready var error_text_color_default := info_text.label_settings.font_color @onready var error_text_color_default := info_text.label_settings.font_color
@ -77,6 +91,14 @@ func _ready() -> void:
letter_grid.add_child(letter) letter_grid.add_child(letter)
letters.append(letter_array) letters.append(letter_array)
strict_green_requirements = []
strict_green_requirements.resize(letter_count)
strict_letter_requirements = []
strict_yellow_restrictions = []
for _i in range(letter_count):
strict_yellow_restrictions.push_back([])
strict_gray_restrictions = []
current_guess = 0 current_guess = 0
ended = false ended = false
won = false won = false
@ -90,6 +112,8 @@ func _ready() -> void:
share_button.hide() share_button.hide()
strict_button.button_pressed = Global.strict_mode
func type_letter(letter: String) -> void: func type_letter(letter: String) -> void:
if ended or input_guess.length() >= letter_count: if ended or input_guess.length() >= letter_count:
@ -136,16 +160,71 @@ func guess_entered() -> void:
if input_guess.length() != letter_count: if input_guess.length() != letter_count:
show_error("Word must be %d characters." % [letter_count]) show_error("Word must be %d characters." % [letter_count])
return return
if Global.strict_mode:
# Strict mode requirements:
#
# * Letters that have been previously green must be used again
# * Letters that have been previously yellow cannot be used again in the same position
# * Every letter that has been previously yellow must be used somewhere
# * Every letter that has been gray cannot be used, except if it has been found to be green or yellow, in which
# case it must be used exactly the correct number of times
#
# It is possible to rearrange this block to reduce the number of conditions, but doing it this way makes the
# error messages more intuitive in my opinion
for i in range(letter_count):
var letter := input_guess[i]
var green = strict_green_requirements[i] # String | null
if green and (letter != green):
show_error("Letter %d must be %s." % [i + 1, green])
return
var yellows: Array[String] = []
yellows.assign(strict_yellow_restrictions[i])
if letter in yellows:
show_error("Letter %d cannot be %s." % [i + 1, letter])
return
var input_letters := Array(input_guess.split(""))
var used_requirements: Array[String] = []
for present_letter in strict_letter_requirements:
if present_letter not in input_letters:
var count := strict_letter_requirements.count(present_letter)
var count_str := (" %dx" % count) if count > 1 else ""
show_error("Letter %s must be used%s." % [present_letter, count_str])
return
used_requirements.push_back(present_letter)
input_letters.erase(present_letter)
for i in letter_count:
var letter := input_guess[i]
if letter in strict_gray_restrictions:
if letter not in used_requirements:
var count := strict_letter_requirements.count(letter)
# We love nested ternaries
var count_str = (" more than %d time%s" % [count, "" if count == 1 else "s"]) if count > 0 else ""
show_error("Letter %s cannot be used%s." % [letter, count_str])
return
used_requirements.erase(letter)
if not Global.is_valid_word(input_guess) and input_guess != target_word: if not Global.is_valid_word(input_guess) and input_guess != target_word:
show_error("Not a recognized word.") show_error("Not a recognized word.")
return return
# This is a valid guess. Strict mode can no longer be changed!
strict_button.disabled = true
var letters_remaining = [] var letters_remaining = []
for letter in target_word: for letter in target_word:
letters_remaining.append(letter) letters_remaining.append(letter)
hide_info() hide_info()
if Global.strict_mode:
strict_letter_requirements = []
var target_letters := Array(target_word.split(""))
for letter in input_guess:
if letter in target_letters:
strict_letter_requirements.push_back(letter)
target_letters.erase(letter)
# Mark all greens # Mark all greens
for i in range(letter_count): for i in range(letter_count):
var guess_letter := input_guess[i] var guess_letter := input_guess[i]
@ -154,6 +233,8 @@ func guess_entered() -> void:
var letter_instance := letters[current_guess][i] as ColorRect var letter_instance := letters[current_guess][i] as ColorRect
letter_instance.get_node("Label").text = guess_letter letter_instance.get_node("Label").text = guess_letter
if guess_letter == target_letter: if guess_letter == target_letter:
if Global.strict_mode:
strict_green_requirements[i] = guess_letter
letter_instance.color = color_correct letter_instance.color = color_correct
keyboard_buttons[target_letter].modulate = color_correct keyboard_buttons[target_letter].modulate = color_correct
# Remove that one letter from remaining letters (is there a function for this?) # Remove that one letter from remaining letters (is there a function for this?)
@ -177,9 +258,13 @@ func guess_entered() -> void:
break break
if found: if found:
letter_instance.color = color_misplaced letter_instance.color = color_misplaced
if Global.strict_mode:
strict_yellow_restrictions[i].push_back(guess_letter)
if keyboard_buttons[guess_letter].modulate != color_correct: if keyboard_buttons[guess_letter].modulate != color_correct:
keyboard_buttons[guess_letter].modulate = color_misplaced keyboard_buttons[guess_letter].modulate = color_misplaced
else: else:
if Global.strict_mode:
strict_gray_restrictions.push_back(guess_letter)
letter_instance.color = color_incorrect letter_instance.color = color_incorrect
if keyboard_buttons[guess_letter].modulate not in [color_correct, color_misplaced]: if keyboard_buttons[guess_letter].modulate not in [color_correct, color_misplaced]:
keyboard_buttons[guess_letter].modulate = color_incorrect keyboard_buttons[guess_letter].modulate = color_incorrect
@ -238,14 +323,17 @@ func _on_ButtonBksp_pressed() -> void:
func _on_share_button_pressed() -> void: func _on_share_button_pressed() -> void:
var text := "%s %s\n" % [title.text, subtitle.text] var text := "%s %s " % [title.text, subtitle.text]
if won: if won:
text += "%d/%d\n\n" % [current_guess, guess_count] text += "%d/%d" % [current_guess, guess_count]
elif ended: elif ended:
text += "X/%d\n\n" % [guess_count] text += "X/%d" % [guess_count]
else: else:
# Can probably bail early but I might as allow it # Can probably bail early but I might as allow it
text += "?/%d\n\n" % [guess_count] text += "?/%d" % [guess_count]
if strict_button.button_pressed:
text += "*"
text += "\n\n"
for i in range(current_guess): for i in range(current_guess):
for j in range(letter_count): for j in range(letter_count):
@ -262,3 +350,7 @@ func _on_share_button_pressed() -> void:
DisplayServer.clipboard_set(text) DisplayServer.clipboard_set(text)
copied_text_animation.stop() copied_text_animation.stop()
copied_text_animation.play("Copied") copied_text_animation.play("Copied")
func _on_strict_button_toggled(toggled_on: bool) -> void:
Global.strict_mode = toggled_on

View file

@ -102,23 +102,23 @@ fallbacks = Array[Font]([ExtResource("1")])
subpixel_positioning = 0 subpixel_positioning = 0
msdf_pixel_range = 14 msdf_pixel_range = 14
msdf_size = 128 msdf_size = 128
cache/0/18/0/ascent = 0.0
cache/0/18/0/descent = 0.0
cache/0/18/0/underline_position = 0.0
cache/0/18/0/underline_thickness = 0.0
cache/0/18/0/scale = 1.0
cache/0/18/0/kerning_overrides/18/0 = Vector2(0, 0)
cache/0/18/0/kerning_overrides/16/0 = Vector2(0, 0)
cache/0/16/0/ascent = 0.0 cache/0/16/0/ascent = 0.0
cache/0/16/0/descent = 0.0 cache/0/16/0/descent = 0.0
cache/0/16/0/underline_position = 0.0 cache/0/16/0/underline_position = 0.0
cache/0/16/0/underline_thickness = 0.0 cache/0/16/0/underline_thickness = 0.0
cache/0/16/0/scale = 1.0 cache/0/16/0/scale = 1.0
cache/0/16/0/kerning_overrides/18/0 = Vector2(0, 0)
cache/0/16/0/kerning_overrides/16/0 = Vector2(0, 0) cache/0/16/0/kerning_overrides/16/0 = Vector2(0, 0)
cache/0/16/0/kerning_overrides/24/0 = Vector2(0, 0)
cache/0/24/0/ascent = 0.0
cache/0/24/0/descent = 0.0
cache/0/24/0/underline_position = 0.0
cache/0/24/0/underline_thickness = 0.0
cache/0/24/0/scale = 1.0
cache/0/24/0/kerning_overrides/16/0 = Vector2(0, 0)
cache/0/24/0/kerning_overrides/24/0 = Vector2(0, 0)
[sub_resource type="LabelSettings" id="LabelSettings_vxdpn"] [sub_resource type="LabelSettings" id="LabelSettings_vxdpn"]
font_size = 24 font_size = 18
font_color = Color(0.968627, 1, 0, 1) font_color = Color(0.968627, 1, 0, 1)
[sub_resource type="Animation" id="Animation_xl3cv"] [sub_resource type="Animation" id="Animation_xl3cv"]
@ -146,7 +146,7 @@ tracks/1/keys = {
"times": PackedFloat32Array(0, 0.3, 4), "times": PackedFloat32Array(0, 0.3, 4),
"transitions": PackedFloat32Array(1, 1, 1), "transitions": PackedFloat32Array(1, 1, 1),
"update": 0, "update": 0,
"values": [Vector2(126, 31), Vector2(126, 51), Vector2(126, 51)] "values": [Vector2(266, 14), Vector2(308, 14), Vector2(308, 14)]
} }
[sub_resource type="AnimationLibrary" id="AnimationLibrary_sgitb"] [sub_resource type="AnimationLibrary" id="AnimationLibrary_sgitb"]
@ -181,11 +181,13 @@ theme_override_constants/separation = 0
[node name="Title" type="Label" parent="C/V/Header"] [node name="Title" type="Label" parent="C/V/Header"]
unique_name_in_owner = true unique_name_in_owner = true
custom_minimum_size = Vector2(0, 112)
layout_mode = 2 layout_mode = 2
theme_override_fonts/font = SubResource("1") theme_override_fonts/font = SubResource("1")
text = "Gordle" text = "Gordle"
label_settings = SubResource("LabelSettings_h2dux") label_settings = SubResource("LabelSettings_h2dux")
horizontal_alignment = 1 horizontal_alignment = 1
vertical_alignment = 2
[node name="Subtitle" type="Label" parent="C/V/Header"] [node name="Subtitle" type="Label" parent="C/V/Header"]
unique_name_in_owner = true unique_name_in_owner = true
@ -450,16 +452,15 @@ text = "Share"
[node name="CopiedText" type="Label" parent="C/V/KeyboardVBox/GuessButton"] [node name="CopiedText" type="Label" parent="C/V/KeyboardVBox/GuessButton"]
modulate = Color(1, 1, 1, 0) modulate = Color(1, 1, 1, 0)
layout_mode = 0 layout_mode = 1
offset_left = 126.0 offset_left = 266.0
offset_top = 31.0 offset_top = 14.0
offset_right = 347.0 offset_right = 331.0
offset_bottom = 59.0 offset_bottom = 35.0
theme_override_colors/font_color = Color(1, 0.309804, 0.309804, 1) theme_override_colors/font_color = Color(1, 0.309804, 0.309804, 1)
theme_override_fonts/font = SubResource("FontFile_6m14o") theme_override_fonts/font = SubResource("FontFile_6m14o")
text = "Copied to clipboard!" text = "Copied!"
label_settings = SubResource("LabelSettings_vxdpn") label_settings = SubResource("LabelSettings_vxdpn")
horizontal_alignment = 1
[node name="CopiedTextAnimation" type="AnimationPlayer" parent="C/V/KeyboardVBox/GuessButton"] [node name="CopiedTextAnimation" type="AnimationPlayer" parent="C/V/KeyboardVBox/GuessButton"]
unique_name_in_owner = true unique_name_in_owner = true
@ -480,7 +481,19 @@ grow_horizontal = 0
theme_override_font_sizes/font_size = 28 theme_override_font_sizes/font_size = 28
text = "Menu" text = "Menu"
[node name="StrictButton" type="CheckButton" parent="."]
unique_name_in_owner = true
layout_mode = 0
offset_left = 10.0
offset_top = 10.0
offset_right = 206.0
offset_bottom = 50.0
focus_mode = 0
theme_override_font_sizes/font_size = 28
text = "Strict Mode"
[connection signal="pressed" from="C/V/KeyboardVBox/HRow3/ButtonBksp" to="." method="_on_ButtonBksp_pressed"] [connection signal="pressed" from="C/V/KeyboardVBox/HRow3/ButtonBksp" to="." method="_on_ButtonBksp_pressed"]
[connection signal="pressed" from="C/V/KeyboardVBox/GuessButton" to="." method="_on_GuessButton_pressed"] [connection signal="pressed" from="C/V/KeyboardVBox/GuessButton" to="." method="_on_GuessButton_pressed"]
[connection signal="pressed" from="C/V/KeyboardVBox/GuessButton/ShareButton" to="." method="_on_share_button_pressed"] [connection signal="pressed" from="C/V/KeyboardVBox/GuessButton/ShareButton" to="." method="_on_share_button_pressed"]
[connection signal="pressed" from="MenuButton" to="." method="_on_MenuButton_pressed"] [connection signal="pressed" from="MenuButton" to="." method="_on_MenuButton_pressed"]
[connection signal="toggled" from="StrictButton" to="." method="_on_strict_button_toggled"]