Change custom word format and allow different length words

This commit is contained in:
Terry Hearst 2024-03-19 21:39:11 -04:00
parent 66df0a9b44
commit cde8565f5d
3 changed files with 53 additions and 51 deletions

View file

@ -136,14 +136,14 @@ layout_mode = 2
[node name="WordLabel" type="Label" parent="C/V/Help/Options"] [node name="WordLabel" type="Label" parent="C/V/Help/Options"]
layout_mode = 2 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") label_settings = SubResource("LabelSettings_6at68")
horizontal_alignment = 1 horizontal_alignment = 1
[node name="CodeLabel" type="Label" parent="C/V/Help/Options"] [node name="CodeLabel" type="Label" parent="C/V/Help/Options"]
layout_mode = 2 layout_mode = 2
text = "An encoded word beginning with \"5$\", text = "An encoded word beginning with \"$\",
e.g. \"5$BTEo\"" e.g. \"$bco92\""
label_settings = SubResource("LabelSettings_6at68") label_settings = SubResource("LabelSettings_6at68")
horizontal_alignment = 1 horizontal_alignment = 1

View file

@ -7,8 +7,8 @@ enum GameMode {
CUSTOM, CUSTOM,
} }
const MIN_LENGTH := 5 const MIN_WORD_LENGTH := 3
const MAX_LENGTH := 5 const MAX_WORD_LENGTH := 8
const GENERABLE_WORDS_FILENAME = "res://words/popular-filtered.txt" const GENERABLE_WORDS_FILENAME = "res://words/popular-filtered.txt"
const ALL_WORDS_FILENAME := "res://words/enable1.txt" const ALL_WORDS_FILENAME := "res://words/enable1.txt"
@ -31,8 +31,8 @@ var allow_future := false
func _ready() -> void: func _ready() -> void:
randomize() randomize()
generable_words = parse_words(GENERABLE_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_LENGTH, MAX_LENGTH) all_words = parse_words(ALL_WORDS_FILENAME, MIN_WORD_LENGTH, MAX_WORD_LENGTH)
detect_params() detect_params()
@ -71,7 +71,7 @@ func generate_word(length: int, random_seed = null) -> String:
func is_valid_word(word: String) -> bool: func is_valid_word(word: String) -> bool:
var length := word.length() var length := word.length()
if length < MIN_LENGTH or length > MAX_LENGTH: if length < MIN_WORD_LENGTH or length > MAX_WORD_LENGTH:
return false return false
var words_list := all_words[length] as Array var words_list := all_words[length] as Array
@ -117,8 +117,8 @@ func detect_params() -> void:
## Checks if the given string is: ## Checks if the given string is:
## ##
## * A normal five letter word, or
## * A valid encoded word, or ## * A valid encoded word, or
## * A normal alphabetical word, or
## * A valid date in YYYY-MM-DD encoding ## * 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 ## * Will only allow this date to be in the present or past, unless `allow_future` is set to true
## ##
@ -128,11 +128,18 @@ func parse_custom(value: String) -> bool:
custom_word = "" custom_word = ""
custom_date_str = "" custom_date_str = ""
if value.length() == 5: 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? # ...is there an is_alpha function that I missed?
var word := value.to_upper() var word := value.to_upper()
var valid := true var valid := true
for i in range(5): for i in range(value_len):
var code := word.unicode_at(i) var code := word.unicode_at(i)
if code < 65 or code > 90: # 65 = ascii "A", 90 = ascii "Z" if code < 65 or code > 90: # 65 = ascii "A", 90 = ascii "Z"
valid = false valid = false
@ -141,11 +148,6 @@ func parse_custom(value: String) -> bool:
custom_word = word custom_word = word
return true return true
var decoded := decode_word(value)
if not decoded.is_empty() and decoded.length() == 5:
custom_word = decoded
return true
if date_regex.search(value): if date_regex.search(value):
var parsed_date := Time.get_datetime_dict_from_datetime_string(value, false) var parsed_date := Time.get_datetime_dict_from_datetime_string(value, false)
parsed_date["hour"] = 0 parsed_date["hour"] = 0
@ -187,49 +189,49 @@ func encode_word(word: String) -> String:
word = word.to_upper() word = word.to_upper()
# Encode word in base 26 # Encode word in base 26
var num := 0 var num := 1 # Start with a single 1 bit
for i in range(word.length()): 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: if letter_ind < 0 or letter_ind >= 26:
return "" return ""
num += letter_ind * (26 ** i) num += letter_ind
# Add an offset so that "aaaaa" isn't just zeroes
num += 123456
return str(word.length()) + ENCODING_INDICATOR + large_encode(num) # Bit count for redundancy
var count := popcnt(num)
num = (num << 6) + count
return ENCODING_INDICATOR + large_encode(num)
## Decodes an encoded word. Returns an empty string if invalid ## Decodes an encoded word. Returns an empty string if invalid
func decode_word(encoded_word: String) -> String: func decode_word(encoded_word: String) -> String:
var index := encoded_word.find(ENCODING_INDICATOR) if not encoded_word.begins_with(ENCODING_INDICATOR):
if index < 1:
return ""
var len_str := encoded_word.substr(0, index)
if not len_str.is_valid_int():
return ""
var word_len := len_str.to_int()
if word_len < 1:
return "" return ""
var large_base_str := encoded_word.substr(index + 1) var large_base_str := encoded_word.substr(1)
var num := large_decode(large_base_str) var full_num := large_decode(large_base_str)
if num < 0:
# Check the bit count
var count := full_num & 0x3F # 6 bits
var num := full_num >> 6
if popcnt(num) != count:
return "" return ""
num -= 123456 if num <= 0:
if num < 0 or num >= (26 ** word_len): # (26 ** 5) - 1 = "ZZZZZ"
# Someone is trying to be sneaky! Or more likely I just messed up.
return "" return ""
# Decode base 26 word # Decode base 26 word
var word := "" var word := ""
for i in range(word_len): while num > 1:
var letter_ind := num % 26 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") @warning_ignore("integer_division")
num = num / 26 num = num / 26
if num != 1:
return ""
return word return word

View file

@ -1,7 +1,6 @@
extends Control extends Control
const letter_count := 5
const guess_count := 6 const guess_count := 6
const Letter := preload("res://src/letter.tscn") 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 # An array of arrays of the letter nodes
var letters := [] var letters := []
var keyboard_buttons := {} var keyboard_buttons := {}
var letter_count := 5
var target_word: String var target_word: String
var current_guess: int var current_guess: int
@ -37,16 +37,6 @@ var input_guess: String
func _ready() -> void: 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: if Global.game_mode != Global.GameMode.CUSTOM:
Global.custom_word = "" Global.custom_word = ""
Global.custom_date_str = "" Global.custom_date_str = ""
@ -75,6 +65,16 @@ func _ready() -> void:
if Global.game_mode != Global.GameMode.DAILY: if Global.game_mode != Global.GameMode.DAILY:
subtitle.text = Global.encode_word(target_word) 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)
current_guess = 0 current_guess = 0
ended = false ended = false
won = false won = false
@ -132,7 +132,7 @@ func guess_entered() -> void:
return return
input_guess = input_guess.to_upper() input_guess = input_guess.to_upper()
if input_guess.length() != letter_count: if input_guess.length() != letter_count:
show_error("Word must be five characters.") show_error("Word must be %d characters." % [letter_count])
return return
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.")