Compare commits

...

10 commits

15 changed files with 358 additions and 121 deletions

View file

@ -1,5 +1,5 @@
# Gordle
Exactly what the title implies - A [Godot engine](https://godotengine.org/) 4.2.1 clone of the trendy game [Wordle](https://www.nytimes.com/games/wordle/index.html). Don't get your expections high.
Exactly what the title implies - A [Godot engine](https://godotengine.org/) 4.3 clone of the trendy game [Wordle](https://www.nytimes.com/games/wordle/index.html). Don't get your expections high.
Play it at https://thearst3rd.com/games/gordle or https://thearst3rd.itch.io/gordle

View file

@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/Louis George Cafe Bold Italic.ttf-f7950aa2883
Rendering=null
antialiasing=1
generate_mipmaps=false
disable_embedded_bitmaps=true
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48

View file

@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/Louis George Cafe Bold.ttf-b0636941e64913a7c7
Rendering=null
antialiasing=1
generate_mipmaps=false
disable_embedded_bitmaps=true
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48

View file

@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/Louis George Cafe Italic.ttf-7c4cebb6b89daf9a
Rendering=null
antialiasing=1
generate_mipmaps=false
disable_embedded_bitmaps=true
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48

View file

@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/Louis George Cafe Light Italic.ttf-3bc639f66f
Rendering=null
antialiasing=1
generate_mipmaps=false
disable_embedded_bitmaps=true
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48

View file

@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/Louis George Cafe Light.ttf-af51ede3c574e1f3e
Rendering=null
antialiasing=1
generate_mipmaps=false
disable_embedded_bitmaps=true
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48

View file

@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/Louis George Cafe.ttf-7069fa0ad08fa81628e2f55
Rendering=null
antialiasing=1
generate_mipmaps=false
disable_embedded_bitmaps=true
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48

View file

@ -12,7 +12,7 @@ config_version=5
config/name="gordle"
run/main_scene="res://src/menu.tscn"
config/features=PackedStringArray("4.2")
config/features=PackedStringArray("4.3")
config/icon="res://icon.png"
[autoload]

View file

@ -136,20 +136,21 @@ layout_mode = 2
[node name="WordLabel" type="Label" parent="C/V/Help/Options"]
layout_mode = 2
text = "A five letter word, e.g. \"APPLE\""
text = "A 3-8 letter word, e.g. \"APPLE\""
label_settings = SubResource("LabelSettings_6at68")
horizontal_alignment = 1
[node name="CodeLabel" type="Label" parent="C/V/Help/Options"]
layout_mode = 2
text = "An encoded word, e.g. \"a00d57a\""
text = "An encoded word beginning with \"$\",
e.g. \"$bco92\""
label_settings = SubResource("LabelSettings_6at68")
horizontal_alignment = 1
[node name="DateLabel" type="Label" parent="C/V/Help/Options"]
layout_mode = 2
text = "A date in YYYY-MM-DD format, e.g.
\"2024-01-23\""
text = "A date in YYYY-MM-DD format,
e.g. \"2024-01-23\""
label_settings = SubResource("LabelSettings_6at68")
horizontal_alignment = 1

View file

@ -7,27 +7,36 @@ enum GameMode {
CUSTOM,
}
const MIN_LENGTH := 5
const MAX_LENGTH := 5
const MIN_WORD_LENGTH := 3
const MAX_WORD_LENGTH := 8
const DEFAULT_WORD_LENGTH := 5
const GENERABLE_WORDS_FILENAME = "res://words/popular-filtered.txt"
const ALL_WORDS_FILENAME := "res://words/enable1.txt"
# !$'()*+,-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz <- All URL-encodable characters
const LARGE_BASE_CHARS := "!'()*+,-.123456789ABCDEFGHJKLMNPQRSTUVWXYZ_abcdefghijkmnopqrstuvwxyz" # <- Without some similar looking characters
const LARGE_BASE := 68 # Number of characters in above array
const ENCODING_INDICATOR := "$"
var date_regex = RegEx.create_from_string("^[0-9]+-[0-9]+-[0-9]+$")
var game_mode := GameMode.DAILY
var custom_word := ""
var custom_date_str := ""
var custom_word_length := -1
var generable_words: Dictionary
var all_words: Dictionary
var allow_future := false
var strict_mode := false
func _ready() -> void:
randomize()
generable_words = parse_words(GENERABLE_WORDS_FILENAME, MIN_LENGTH, MAX_LENGTH)
all_words = parse_words(ALL_WORDS_FILENAME, MIN_LENGTH, MAX_LENGTH)
generable_words = parse_words(GENERABLE_WORDS_FILENAME, MIN_WORD_LENGTH, MAX_WORD_LENGTH)
all_words = parse_words(ALL_WORDS_FILENAME, MIN_WORD_LENGTH, MAX_WORD_LENGTH)
detect_params()
@ -66,7 +75,7 @@ func generate_word(length: int, random_seed = null) -> String:
func is_valid_word(word: String) -> bool:
var length := word.length()
if length < MIN_LENGTH or length > MAX_LENGTH:
if length < MIN_WORD_LENGTH or length > MAX_WORD_LENGTH:
return false
var words_list := all_words[length] as Array
@ -112,19 +121,29 @@ func detect_params() -> void:
## Checks if the given string is:
##
## * A normal five letter word, or
## * A valid encoded word, or
## * A normal alphabetical word, or
## * A valid date in YYYY-MM-DD encoding
## * Will only allow this date to be in the present or past, unless `allow_future` is set to true
##
## Returns true if successful, and will set `custom_word` and possibly `custom_date_str` values accordingly. Returns
## false if unsuccessful and will erase `custom_word` and `custom_date_str`.
func parse_custom(value: String) -> bool:
if value.length() == 5:
custom_word = ""
custom_date_str = ""
var decoded := decode_word(value)
var decoded_len := decoded.length()
if decoded_len >= MIN_WORD_LENGTH and decoded_len <= MAX_WORD_LENGTH:
custom_word = decoded
return true
var value_len := value.length()
if value_len >= MIN_WORD_LENGTH and value_len <= MAX_WORD_LENGTH:
# ...is there an is_alpha function that I missed?
var word := value.to_upper()
var valid := true
for i in range(5):
for i in range(value_len):
var code := word.unicode_at(i)
if code < 65 or code > 90: # 65 = ascii "A", 90 = ascii "Z"
valid = false
@ -133,11 +152,6 @@ func parse_custom(value: String) -> bool:
custom_word = word
return true
var decoded := decode_word(value)
if not decoded.is_empty():
custom_word = decoded
return true
if date_regex.search(value):
var parsed_date := Time.get_datetime_dict_from_datetime_string(value, false)
parsed_date["hour"] = 0
@ -148,16 +162,14 @@ func parse_custom(value: String) -> bool:
if new_time != 0 or (parsed_date["year"] == 1970 and parsed_date["month"] == 1 and parsed_date["day"] == 1):
valid = true
if not allow_future:
var current_time := Time.get_unix_time_from_datetime_string(Time.get_date_string_from_system(true))
var current_time := Time.get_unix_time_from_datetime_string(Time.get_date_string_from_system(false))
if new_time > current_time:
valid = false
if valid:
custom_word = generate_word(5, new_time)
custom_word = generate_word(DEFAULT_WORD_LENGTH, new_time)
custom_date_str = Time.get_date_string_from_unix_time(new_time)
return true
custom_word = ""
custom_date_str = ""
return false
@ -178,60 +190,81 @@ func popcnt(num: int) -> int:
## Encodes a word using a very basic "encryption" method. Returns an empty string if invalid word
func encode_word(word: String) -> String:
if word.length() != 5:
return ""
word = word.to_upper()
# Encode word in base 26
var num := 0
var num := 1 # Start with a single 1 bit
for i in range(word.length()):
var letter_ind := word[i].unicode_at(0) - 65 # 65 = ascii "A"
num *= 26
var letter_ind := word.unicode_at(i) - 65 # 65 = ascii "A"
if letter_ind < 0 or letter_ind >= 26:
return ""
num += letter_ind * (26 ** i)
# Add an offset so that "aaaaa" isn't just zeroes
num += 123456
num += letter_ind
# Shuffle bits around
# uvwxyz -> vuxwzy -> zyvuxw
num = ((num & 0xf0f0f0) >> 4) | ((num & 0x0f0f0f) << 4)
num = ((num & 0xffff00) >> 8) | ((num & 0x0000ff) << 16)
# Count bits and add as checksum
var bits := popcnt(num)
num = num | (bits << 24)
# Bit count for redundancy
var count := popcnt(num)
num = (num << 6) + count
return String.num_int64(num, 16)
return ENCODING_INDICATOR + large_encode(num)
## Decodes an encoded word. Returns an empty string if invalid
func decode_word(encoded_word: String) -> String:
if not encoded_word.is_valid_hex_number():
return ""
var encoded_word_num := encoded_word.hex_to_int()
var bits := encoded_word_num >> 24
var num := encoded_word_num & 0xffffff
# Verify bit count
if popcnt(num) != bits:
if not encoded_word.begins_with(ENCODING_INDICATOR):
return ""
# Unshuffle bits
num = ((num << 8) & 0xffff00) | ((num >> 16) & 0x0000ff)
num = ((num << 4) & 0xf0f0f0) | ((num >> 4) & 0x0f0f0f)
var large_base_str := encoded_word.substr(1)
var full_num := large_decode(large_base_str)
num -= 123456
# Check the bit count
var count := full_num & 0x3F # 6 bits
var num := full_num >> 6
if popcnt(num) != count:
return ""
if num < 0 or num >= (26 ** 5): # (26 ** 5) - 1 = "ZZZZZ"
# Someone is trying to be sneaky! Or more likely I just messed up.
if num <= 0:
return ""
# Decode base 26 word
var word := ""
for i in range(5):
while num > 1:
var letter_ind := num % 26
word += char(letter_ind + 65) # 65 = ascii "A"
word = char(letter_ind + 65) + word # 65 = ascii "A"
@warning_ignore("integer_division")
num = num / 26
if num != 1:
return ""
return word
## Encodes an integer as a large-base string
func large_encode(num: int) -> String:
if num <= 0: # Negative numbers not supported since they are not needed
return LARGE_BASE_CHARS[0]
var lint := ""
while num > 0:
lint = LARGE_BASE_CHARS[num % LARGE_BASE] + lint
@warning_ignore("integer_division")
num = num / LARGE_BASE
return lint
## Checks if the given string is a valid large-base integer. (Sorta, it only checks if the character composition is good)
func is_valid_large_base_int(lint: String) -> bool:
for chr in lint:
if chr not in LARGE_BASE_CHARS:
return false
return true
## Converts the given large-base integer back into a normal int. Returns -1 if invalid
func large_decode(lint: String) -> int:
var num := 0
for chr in lint:
var index := LARGE_BASE_CHARS.find(chr)
if index < 0:
return -1
num = (num * LARGE_BASE) + index
return num

View file

@ -4,6 +4,9 @@
[sub_resource type="FontFile" id="1"]
fallbacks = Array[Font]([ExtResource("1")])
subpixel_positioning = 0
msdf_pixel_range = 14
msdf_size = 128
cache/0/52/0/ascent = 0.0
cache/0/52/0/descent = 0.0
cache/0/52/0/underline_position = 0.0

View file

@ -1,7 +1,6 @@
extends Control
const letter_count := 5
const guess_count := 6
const Letter := preload("res://src/letter.tscn")
@ -13,6 +12,7 @@ const Letter := preload("res://src/letter.tscn")
# An array of arrays of the letter nodes
var letters := []
var keyboard_buttons := {}
var letter_count := Global.DEFAULT_WORD_LENGTH
var target_word: String
var current_guess: int
@ -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,20 +45,12 @@ 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
func _ready() -> void:
letter_grid.columns = letter_count
for _i in range(guess_count):
var letter_array := []
for _j in range(letter_count):
var letter := Letter.instantiate()
letter_array.append(letter)
letter_grid.add_child(letter)
letters.append(letter_array)
if Global.game_mode != Global.GameMode.CUSTOM:
Global.custom_word = ""
Global.custom_date_str = ""
@ -62,7 +66,7 @@ func _ready() -> void:
else:
var random_seed = null
if Global.game_mode == Global.GameMode.DAILY:
var current_time := Time.get_date_string_from_system(true)
var current_time := Time.get_date_string_from_system(false)
random_seed = Time.get_unix_time_from_datetime_string(current_time)
title.text = "Daily " + title.text
@ -70,11 +74,31 @@ func _ready() -> void:
elif Global.game_mode == Global.GameMode.RANDOM:
title.text = "Random " + title.text
if Global.custom_word_length > 0:
letter_count = Global.custom_word_length
target_word = Global.generate_word(letter_count, random_seed)
if Global.game_mode != Global.GameMode.DAILY:
subtitle.text = Global.encode_word(target_word)
letter_count = target_word.length()
letter_grid.columns = letter_count
for _i in range(guess_count):
var letter_array := []
for _j in range(letter_count):
var letter := Letter.instantiate()
letter_array.append(letter)
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
@ -88,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:
@ -132,18 +158,73 @@ func guess_entered() -> void:
return
input_guess = input_guess.to_upper()
if input_guess.length() != letter_count:
show_error("Word must be five characters.")
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]
@ -152,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?)
@ -175,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
@ -236,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):
@ -260,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

View file

@ -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
@ -233,63 +235,73 @@ layout_mode = 2
size_flags_horizontal = 4
[node name="ButtonQ" type="Button" parent="C/V/KeyboardVBox/HRow1"]
custom_minimum_size = Vector2(50, 0)
custom_minimum_size = Vector2(58, 58)
layout_mode = 2
focus_mode = 0
theme_override_font_sizes/font_size = 44
text = "Q"
[node name="ButtonW" type="Button" parent="C/V/KeyboardVBox/HRow1"]
custom_minimum_size = Vector2(50, 0)
custom_minimum_size = Vector2(58, 58)
layout_mode = 2
focus_mode = 0
theme_override_font_sizes/font_size = 44
text = "W"
[node name="ButtonE" type="Button" parent="C/V/KeyboardVBox/HRow1"]
custom_minimum_size = Vector2(50, 0)
custom_minimum_size = Vector2(58, 58)
layout_mode = 2
focus_mode = 0
theme_override_font_sizes/font_size = 44
text = "E"
[node name="ButtonR" type="Button" parent="C/V/KeyboardVBox/HRow1"]
custom_minimum_size = Vector2(50, 0)
custom_minimum_size = Vector2(58, 58)
layout_mode = 2
focus_mode = 0
theme_override_font_sizes/font_size = 44
text = "R"
[node name="ButtonT" type="Button" parent="C/V/KeyboardVBox/HRow1"]
custom_minimum_size = Vector2(50, 0)
custom_minimum_size = Vector2(58, 58)
layout_mode = 2
focus_mode = 0
theme_override_font_sizes/font_size = 44
text = "T"
[node name="ButtonY" type="Button" parent="C/V/KeyboardVBox/HRow1"]
custom_minimum_size = Vector2(50, 0)
custom_minimum_size = Vector2(58, 58)
layout_mode = 2
focus_mode = 0
theme_override_font_sizes/font_size = 44
text = "Y"
[node name="ButtonU" type="Button" parent="C/V/KeyboardVBox/HRow1"]
custom_minimum_size = Vector2(50, 0)
custom_minimum_size = Vector2(58, 58)
layout_mode = 2
focus_mode = 0
theme_override_font_sizes/font_size = 44
text = "U"
[node name="ButtonI" type="Button" parent="C/V/KeyboardVBox/HRow1"]
custom_minimum_size = Vector2(50, 0)
custom_minimum_size = Vector2(58, 58)
layout_mode = 2
focus_mode = 0
theme_override_font_sizes/font_size = 44
text = "I"
[node name="ButtonO" type="Button" parent="C/V/KeyboardVBox/HRow1"]
custom_minimum_size = Vector2(50, 0)
custom_minimum_size = Vector2(58, 58)
layout_mode = 2
focus_mode = 0
theme_override_font_sizes/font_size = 44
text = "O"
[node name="ButtonP" type="Button" parent="C/V/KeyboardVBox/HRow1"]
custom_minimum_size = Vector2(50, 0)
custom_minimum_size = Vector2(58, 58)
layout_mode = 2
focus_mode = 0
theme_override_font_sizes/font_size = 44
text = "P"
[node name="HRow2" type="HBoxContainer" parent="C/V/KeyboardVBox"]
@ -297,57 +309,66 @@ layout_mode = 2
size_flags_horizontal = 4
[node name="ButtonA" type="Button" parent="C/V/KeyboardVBox/HRow2"]
custom_minimum_size = Vector2(50, 0)
custom_minimum_size = Vector2(58, 58)
layout_mode = 2
focus_mode = 0
theme_override_font_sizes/font_size = 44
text = "A"
[node name="ButtonS" type="Button" parent="C/V/KeyboardVBox/HRow2"]
custom_minimum_size = Vector2(50, 0)
custom_minimum_size = Vector2(58, 58)
layout_mode = 2
focus_mode = 0
theme_override_font_sizes/font_size = 44
text = "S"
[node name="ButtonD" type="Button" parent="C/V/KeyboardVBox/HRow2"]
custom_minimum_size = Vector2(50, 0)
custom_minimum_size = Vector2(58, 58)
layout_mode = 2
focus_mode = 0
theme_override_font_sizes/font_size = 44
text = "D"
[node name="ButtonF" type="Button" parent="C/V/KeyboardVBox/HRow2"]
custom_minimum_size = Vector2(50, 0)
custom_minimum_size = Vector2(58, 58)
layout_mode = 2
focus_mode = 0
theme_override_font_sizes/font_size = 44
text = "F"
[node name="ButtonG" type="Button" parent="C/V/KeyboardVBox/HRow2"]
custom_minimum_size = Vector2(50, 0)
custom_minimum_size = Vector2(58, 58)
layout_mode = 2
focus_mode = 0
theme_override_font_sizes/font_size = 44
text = "G"
[node name="ButtonH" type="Button" parent="C/V/KeyboardVBox/HRow2"]
custom_minimum_size = Vector2(50, 0)
custom_minimum_size = Vector2(58, 58)
layout_mode = 2
focus_mode = 0
theme_override_font_sizes/font_size = 44
text = "H"
[node name="ButtonJ" type="Button" parent="C/V/KeyboardVBox/HRow2"]
custom_minimum_size = Vector2(50, 0)
custom_minimum_size = Vector2(58, 58)
layout_mode = 2
focus_mode = 0
theme_override_font_sizes/font_size = 44
text = "J"
[node name="ButtonK" type="Button" parent="C/V/KeyboardVBox/HRow2"]
custom_minimum_size = Vector2(50, 0)
custom_minimum_size = Vector2(58, 58)
layout_mode = 2
focus_mode = 0
theme_override_font_sizes/font_size = 44
text = "K"
[node name="ButtonL" type="Button" parent="C/V/KeyboardVBox/HRow2"]
custom_minimum_size = Vector2(50, 0)
custom_minimum_size = Vector2(58, 58)
layout_mode = 2
focus_mode = 0
theme_override_font_sizes/font_size = 44
text = "L"
[node name="HRow3" type="HBoxContainer" parent="C/V/KeyboardVBox"]
@ -355,56 +376,64 @@ layout_mode = 2
size_flags_horizontal = 4
[node name="ButtonZ" type="Button" parent="C/V/KeyboardVBox/HRow3"]
custom_minimum_size = Vector2(50, 0)
custom_minimum_size = Vector2(58, 58)
layout_mode = 2
focus_mode = 0
theme_override_font_sizes/font_size = 44
text = "Z"
[node name="ButtonX" type="Button" parent="C/V/KeyboardVBox/HRow3"]
custom_minimum_size = Vector2(50, 0)
custom_minimum_size = Vector2(58, 58)
layout_mode = 2
focus_mode = 0
theme_override_font_sizes/font_size = 44
text = "X"
[node name="ButtonC" type="Button" parent="C/V/KeyboardVBox/HRow3"]
custom_minimum_size = Vector2(50, 0)
custom_minimum_size = Vector2(58, 58)
layout_mode = 2
focus_mode = 0
theme_override_font_sizes/font_size = 44
text = "C"
[node name="ButtonV" type="Button" parent="C/V/KeyboardVBox/HRow3"]
custom_minimum_size = Vector2(50, 0)
custom_minimum_size = Vector2(58, 58)
layout_mode = 2
focus_mode = 0
theme_override_font_sizes/font_size = 44
text = "V"
[node name="ButtonB" type="Button" parent="C/V/KeyboardVBox/HRow3"]
custom_minimum_size = Vector2(50, 0)
custom_minimum_size = Vector2(58, 58)
layout_mode = 2
focus_mode = 0
theme_override_font_sizes/font_size = 44
text = "B"
[node name="ButtonN" type="Button" parent="C/V/KeyboardVBox/HRow3"]
custom_minimum_size = Vector2(50, 0)
custom_minimum_size = Vector2(58, 58)
layout_mode = 2
focus_mode = 0
theme_override_font_sizes/font_size = 44
text = "N"
[node name="ButtonM" type="Button" parent="C/V/KeyboardVBox/HRow3"]
custom_minimum_size = Vector2(50, 0)
custom_minimum_size = Vector2(58, 58)
layout_mode = 2
focus_mode = 0
theme_override_font_sizes/font_size = 44
text = "M"
[node name="ButtonBksp" type="Button" parent="C/V/KeyboardVBox/HRow3"]
unique_name_in_owner = true
custom_minimum_size = Vector2(50, 0)
custom_minimum_size = Vector2(86, 58)
layout_mode = 2
focus_mode = 0
text = "bksp"
[node name="GuessButton" type="Button" parent="C/V/KeyboardVBox"]
unique_name_in_owner = true
custom_minimum_size = Vector2(120, 0)
layout_mode = 2
size_flags_horizontal = 4
focus_mode = 0
@ -413,6 +442,7 @@ text = "Guess"
[node name="ShareButton" type="Button" parent="C/V/KeyboardVBox/GuessButton"]
unique_name_in_owner = true
custom_minimum_size = Vector2(120, 0)
layout_mode = 0
offset_left = 180.0
offset_right = 283.0
@ -422,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
@ -452,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"]

View file

@ -4,22 +4,33 @@ extends Control
@onready var quit_button: Button = %QuitButton
@onready var credits: ColorRect = %Credits
@onready var random_buttons = [%Random3, %Random4, %Random6, %Random7, %Random8]
func _ready() -> void:
if OS.has_feature("web"):
quit_button.hide()
for button in random_buttons:
button.pressed.connect(start_random_game.bind(int(button.text[0])))
func start_random_game(length := -1) -> void:
Global.game_mode = Global.GameMode.RANDOM
Global.custom_word_length = length
var error := get_tree().change_scene_to_file("res://src/main.tscn")
assert(not error)
func _on_DailyButton_pressed() -> void:
Global.game_mode = Global.GameMode.DAILY
Global.custom_word_length = -1
var error := get_tree().change_scene_to_file("res://src/main.tscn")
assert(not error)
func _on_RandomButton_pressed() -> void:
Global.game_mode = Global.GameMode.RANDOM
var error := get_tree().change_scene_to_file("res://src/main.tscn")
assert(not error)
start_random_game()
func _on_custom_button_pressed() -> void:

View file

@ -92,20 +92,22 @@ horizontal_alignment = 1
[node name="V2" type="VBoxContainer" parent="C/V"]
layout_mode = 2
size_flags_horizontal = 4
theme_override_constants/separation = 30
[node name="V" type="VBoxContainer" parent="C/V/V2"]
layout_mode = 2
[node name="DailyButton" type="Button" parent="C/V/V2/V"]
custom_minimum_size = Vector2(280, 0)
layout_mode = 2
size_flags_horizontal = 4
text = "Daily Gordle"
[node name="Label" type="Label" parent="C/V/V2/V"]
layout_mode = 2
theme_override_fonts/font = SubResource("2")
text = "Changes every day
at midnight GMT"
text = "Changes at midnight local time"
label_settings = SubResource("LabelSettings_17upj")
horizontal_alignment = 1
@ -114,9 +116,49 @@ custom_minimum_size = Vector2(310, 0)
layout_mode = 2
[node name="RandomButton" type="Button" parent="C/V/V2/V2"]
custom_minimum_size = Vector2(280, 0)
layout_mode = 2
size_flags_horizontal = 4
text = "Random Gordle"
[node name="H2" type="HBoxContainer" parent="C/V/V2/V2"]
layout_mode = 2
alignment = 1
[node name="Random3" type="Button" parent="C/V/V2/V2/H2"]
unique_name_in_owner = true
layout_mode = 2
theme_override_font_sizes/font_size = 24
text = "3dle"
[node name="Random4" type="Button" parent="C/V/V2/V2/H2"]
unique_name_in_owner = true
layout_mode = 2
theme_override_font_sizes/font_size = 24
text = "4dle"
[node name="Spacer" type="Control" parent="C/V/V2/V2/H2"]
custom_minimum_size = Vector2(20, 0)
layout_mode = 2
[node name="Random6" type="Button" parent="C/V/V2/V2/H2"]
unique_name_in_owner = true
layout_mode = 2
theme_override_font_sizes/font_size = 24
text = "6dle"
[node name="Random7" type="Button" parent="C/V/V2/V2/H2"]
unique_name_in_owner = true
layout_mode = 2
theme_override_font_sizes/font_size = 24
text = "7dle"
[node name="Random8" type="Button" parent="C/V/V2/V2/H2"]
unique_name_in_owner = true
layout_mode = 2
theme_override_font_sizes/font_size = 24
text = "8dle"
[node name="Label" type="Label" parent="C/V/V2/V2"]
layout_mode = 2
theme_override_fonts/font = SubResource("2")
@ -129,7 +171,9 @@ custom_minimum_size = Vector2(310, 0)
layout_mode = 2
[node name="CustomButton" type="Button" parent="C/V/V2/V3"]
custom_minimum_size = Vector2(280, 0)
layout_mode = 2
size_flags_horizontal = 4
text = "Custom Gordle"
[node name="Label" type="Label" parent="C/V/V2/V3"]
@ -140,12 +184,16 @@ label_settings = SubResource("LabelSettings_cdviw")
horizontal_alignment = 1
[node name="CreditsButton" type="Button" parent="C/V/V2"]
custom_minimum_size = Vector2(280, 0)
layout_mode = 2
size_flags_horizontal = 4
text = "Licenses"
[node name="QuitButton" type="Button" parent="C/V/V2"]
unique_name_in_owner = true
custom_minimum_size = Vector2(280, 0)
layout_mode = 2
size_flags_horizontal = 4
text = "Quit"
[node name="Credits" type="ColorRect" parent="."]