diff --git a/src/global.gd b/src/global.gd index 96805f3..6c42c09 100644 --- a/src/global.gd +++ b/src/global.gd @@ -30,6 +30,8 @@ var all_words: Dictionary var allow_future := false +var strict_mode := false + func _ready() -> void: randomize() diff --git a/src/main.gd b/src/main.gd index 754f798..62a9aca 100644 --- a/src/main.gd +++ b/src/main.gd @@ -20,6 +20,18 @@ var ended: bool var won: bool 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 subtitle: Label = %Subtitle @onready var letter_grid: GridContainer = %LetterGrid @@ -33,6 +45,8 @@ var input_guess: String @onready var info_text_animation: AnimationPlayer = %InfoTextAnimation @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 @@ -77,6 +91,14 @@ func _ready() -> void: letter_grid.add_child(letter) 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 ended = false won = false @@ -90,6 +112,8 @@ func _ready() -> void: share_button.hide() + strict_button.button_pressed = Global.strict_mode + func type_letter(letter: String) -> void: if ended or input_guess.length() >= letter_count: @@ -136,16 +160,71 @@ func guess_entered() -> void: if input_guess.length() != letter_count: show_error("Word must be %d characters." % [letter_count]) 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: show_error("Not a recognized word.") return + # This is a valid guess. Strict mode can no longer be changed! + strict_button.disabled = true + var letters_remaining = [] for letter in target_word: letters_remaining.append(letter) 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 for i in range(letter_count): var guess_letter := input_guess[i] @@ -154,6 +233,8 @@ func guess_entered() -> void: var letter_instance := letters[current_guess][i] as ColorRect letter_instance.get_node("Label").text = guess_letter if guess_letter == target_letter: + if Global.strict_mode: + strict_green_requirements[i] = guess_letter letter_instance.color = color_correct keyboard_buttons[target_letter].modulate = color_correct # Remove that one letter from remaining letters (is there a function for this?) @@ -177,9 +258,13 @@ func guess_entered() -> void: break if found: 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: keyboard_buttons[guess_letter].modulate = color_misplaced else: + if Global.strict_mode: + strict_gray_restrictions.push_back(guess_letter) letter_instance.color = color_incorrect if keyboard_buttons[guess_letter].modulate not in [color_correct, color_misplaced]: keyboard_buttons[guess_letter].modulate = color_incorrect @@ -238,14 +323,17 @@ func _on_ButtonBksp_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: - text += "%d/%d\n\n" % [current_guess, guess_count] + text += "%d/%d" % [current_guess, guess_count] elif ended: - text += "X/%d\n\n" % [guess_count] + text += "X/%d" % [guess_count] else: # 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 j in range(letter_count): @@ -262,3 +350,7 @@ func _on_share_button_pressed() -> void: DisplayServer.clipboard_set(text) copied_text_animation.stop() copied_text_animation.play("Copied") + + +func _on_strict_button_toggled(toggled_on: bool) -> void: + Global.strict_mode = toggled_on diff --git a/src/main.tscn b/src/main.tscn index 6d84ce0..8f8c3c8 100644 --- a/src/main.tscn +++ b/src/main.tscn @@ -102,23 +102,23 @@ fallbacks = Array[Font]([ExtResource("1")]) subpixel_positioning = 0 msdf_pixel_range = 14 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/descent = 0.0 cache/0/16/0/underline_position = 0.0 cache/0/16/0/underline_thickness = 0.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/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"] -font_size = 24 +font_size = 18 font_color = Color(0.968627, 1, 0, 1) [sub_resource type="Animation" id="Animation_xl3cv"] @@ -146,7 +146,7 @@ tracks/1/keys = { "times": PackedFloat32Array(0, 0.3, 4), "transitions": PackedFloat32Array(1, 1, 1), "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"] @@ -181,11 +181,13 @@ theme_override_constants/separation = 0 [node name="Title" type="Label" parent="C/V/Header"] unique_name_in_owner = true +custom_minimum_size = Vector2(0, 112) layout_mode = 2 theme_override_fonts/font = SubResource("1") text = "Gordle" label_settings = SubResource("LabelSettings_h2dux") horizontal_alignment = 1 +vertical_alignment = 2 [node name="Subtitle" type="Label" parent="C/V/Header"] unique_name_in_owner = true @@ -450,16 +452,15 @@ text = "Share" [node name="CopiedText" type="Label" parent="C/V/KeyboardVBox/GuessButton"] modulate = Color(1, 1, 1, 0) -layout_mode = 0 -offset_left = 126.0 -offset_top = 31.0 -offset_right = 347.0 -offset_bottom = 59.0 +layout_mode = 1 +offset_left = 266.0 +offset_top = 14.0 +offset_right = 331.0 +offset_bottom = 35.0 theme_override_colors/font_color = Color(1, 0.309804, 0.309804, 1) theme_override_fonts/font = SubResource("FontFile_6m14o") -text = "Copied to clipboard!" +text = "Copied!" label_settings = SubResource("LabelSettings_vxdpn") -horizontal_alignment = 1 [node name="CopiedTextAnimation" type="AnimationPlayer" parent="C/V/KeyboardVBox/GuessButton"] unique_name_in_owner = true @@ -480,7 +481,19 @@ grow_horizontal = 0 theme_override_font_sizes/font_size = 28 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/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="MenuButton" to="." method="_on_MenuButton_pressed"] +[connection signal="toggled" from="StrictButton" to="." method="_on_strict_button_toggled"]