Compare commits

...

11 commits
0.1.0 ... main

13 changed files with 161 additions and 82 deletions

3
.gitignore vendored
View file

@ -1,3 +1,6 @@
# Godot 4+ specific ignores # Godot 4+ specific ignores
.godot/ .godot/
/android/ /android/
/keystore/
/dist/

View file

@ -9,7 +9,7 @@ custom_features=""
export_filter="all_resources" export_filter="all_resources"
include_filter="" include_filter=""
exclude_filter="" exclude_filter=""
export_path="./Repeat After Me.apk" export_path="dist/Repeat-After-Me-1.0.1.apk"
patches=PackedStringArray() patches=PackedStringArray()
encryption_include_filters="" encryption_include_filters=""
encryption_exclude_filters="" encryption_exclude_filters=""

View file

@ -11,7 +11,7 @@ config_version=5
[application] [application]
config/name="Repeat After Me" config/name="Repeat After Me"
config/version="0.1.0" config/version="1.0.1"
run/main_scene="uid://cv38ubwerjlpx" run/main_scene="uid://cv38ubwerjlpx"
config/features=PackedStringArray("4.4", "Mobile") config/features=PackedStringArray("4.4", "Mobile")
run/low_processor_mode=true run/low_processor_mode=true

View file

@ -179,17 +179,10 @@ offset_right = -40.0
offset_bottom = 320.0 offset_bottom = 320.0
alignment = 1 alignment = 1
keep_editing_on_text_submit = true keep_editing_on_text_submit = true
context_menu_enabled = false
[node name="SubmitButton" type="Button" parent="."] middle_mouse_paste_enabled = false
unique_name_in_owner = true selecting_enabled = false
layout_mode = 2 drag_and_drop_selection_enabled = false
anchor_left = 0.5
anchor_right = 0.5
offset_left = -40.0
offset_top = 344.0
offset_right = 40.0
offset_bottom = 376.0
text = "Submit"
[node name="SettingsMenu" parent="." instance=ExtResource("8_85g3d")] [node name="SettingsMenu" parent="." instance=ExtResource("8_85g3d")]
unique_name_in_owner = true unique_name_in_owner = true
@ -214,6 +207,7 @@ offset_right = 32.0
offset_bottom = -33.7 offset_bottom = -33.7
grow_horizontal = 2 grow_horizontal = 2
grow_vertical = 0 grow_vertical = 0
focus_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_ya4ey") theme_override_styles/panel = SubResource("StyleBoxFlat_ya4ey")
script = ExtResource("11_ya4ey") script = ExtResource("11_ya4ey")
@ -228,4 +222,3 @@ vertical_alignment = 1
[connection signal="pressed" from="ShowSettingsButton" to="." method="_on_settings_button_pressed"] [connection signal="pressed" from="ShowSettingsButton" to="." method="_on_settings_button_pressed"]
[connection signal="pressed" from="ShowPhrasesButton" to="." method="_on_show_phrases_button_pressed"] [connection signal="pressed" from="ShowPhrasesButton" to="." method="_on_show_phrases_button_pressed"]
[connection signal="text_changed" from="AnswerInput" to="." method="_on_answer_input_text_changed"] [connection signal="text_changed" from="AnswerInput" to="." method="_on_answer_input_text_changed"]
[connection signal="pressed" from="SubmitButton" to="." method="_on_submit_button_pressed"]

View file

@ -57,6 +57,10 @@ offset_top = 56.0
offset_right = 184.0 offset_right = 184.0
offset_bottom = 75.0 offset_bottom = 75.0
keep_editing_on_text_submit = true keep_editing_on_text_submit = true
context_menu_enabled = false
middle_mouse_paste_enabled = false
selecting_enabled = false
drag_and_drop_selection_enabled = false
[node name="AddPhraseButton" type="Button" parent="."] [node name="AddPhraseButton" type="Button" parent="."]
layout_mode = 1 layout_mode = 1

View file

@ -22,6 +22,13 @@ offset_bottom = 16.0
clip_text = true clip_text = true
text_overrun_behavior = 3 text_overrun_behavior = 3
[node name="LabelButton" type="TextureButton" parent="."]
layout_mode = 1
anchors_preset = -1
anchor_right = 1.143
offset_right = -48.032
offset_bottom = 16.0
[node name="RemoveButton" type="TextureButton" parent="."] [node name="RemoveButton" type="TextureButton" parent="."]
layout_mode = 2 layout_mode = 2
anchor_left = 1.036 anchor_left = 1.036
@ -32,4 +39,5 @@ offset_bottom = 16.0
texture_normal = ExtResource("2_cv30m") texture_normal = ExtResource("2_cv30m")
stretch_mode = 0 stretch_mode = 0
[connection signal="pressed" from="LabelButton" to="." method="_on_label_button_pressed"]
[connection signal="pressed" from="RemoveButton" to="." method="_on_remove_button_pressed"] [connection signal="pressed" from="RemoveButton" to="." method="_on_remove_button_pressed"]

View file

@ -32,6 +32,15 @@ theme = ExtResource("2_lwwgp")
theme_override_styles/panel = SubResource("StyleBoxTexture_choun") theme_override_styles/panel = SubResource("StyleBoxTexture_choun")
script = ExtResource("2_labj1") script = ExtResource("2_labj1")
[node name="MenuLabel" type="Label" parent="."]
layout_mode = 0
offset_left = 16.0
offset_top = 16.0
offset_right = 104.0
offset_bottom = 39.0
text = "Settings"
label_settings = SubResource("LabelSettings_labj1")
[node name="CloseSettingsButton" type="TextureButton" parent="."] [node name="CloseSettingsButton" type="TextureButton" parent="."]
layout_mode = 1 layout_mode = 1
anchors_preset = 1 anchors_preset = 1
@ -45,22 +54,44 @@ grow_horizontal = 0
texture_normal = ExtResource("2_wswnh") texture_normal = ExtResource("2_wswnh")
stretch_mode = 0 stretch_mode = 0
[node name="Label" type="Label" parent="."] [node name="ImportExportLabel" type="Label" parent="."]
layout_mode = 0 layout_mode = 0
offset_left = 16.0 offset_left = 16.0
offset_top = 16.0 offset_top = 80.0
offset_right = 104.0 offset_right = 144.0
offset_bottom = 39.0 offset_bottom = 114.8
text = "Settings" text = "Import | Export
label_settings = SubResource("LabelSettings_labj1") to/from Clipboard"
[node name="ResetXPButton" type="Button" parent="."] [node name="ImportButton" type="Button" parent="ImportExportLabel"]
layout_mode = 0
offset_top = 48.0
offset_right = 61.0
offset_bottom = 72.0
text = "Import"
[node name="ExportButton" type="Button" parent="ImportExportLabel"]
layout_mode = 0
offset_left = 75.0
offset_top = 48.0
offset_right = 136.0
offset_bottom = 72.0
text = "Export"
[node name="ResetLabel" type="Label" parent="."]
layout_mode = 0 layout_mode = 0
offset_left = 16.0 offset_left = 16.0
offset_top = 72.0 offset_top = 192.0
offset_right = 92.0 offset_right = 91.0
offset_bottom = 96.0 offset_bottom = 207.9
text = "Reset XP" text = "Reset Data"
[node name="ResetXpAndStatsButton" type="Button" parent="ResetLabel"]
layout_mode = 0
offset_top = 24.0
offset_right = 76.0
offset_bottom = 48.0
text = "Reset XP & Stats"
[node name="Info Footer" type="Label" parent="."] [node name="Info Footer" type="Label" parent="."]
layout_mode = 1 layout_mode = 1
@ -84,4 +115,6 @@ text_overrun_behavior = 3
script = ExtResource("5_lwwgp") script = ExtResource("5_lwwgp")
[connection signal="pressed" from="CloseSettingsButton" to="." method="_on_close_settings_button_pressed"] [connection signal="pressed" from="CloseSettingsButton" to="." method="_on_close_settings_button_pressed"]
[connection signal="pressed" from="ResetXPButton" to="." method="_on_reset_xp_button_pressed"] [connection signal="pressed" from="ImportExportLabel/ImportButton" to="." method="_on_import_button_pressed"]
[connection signal="pressed" from="ImportExportLabel/ExportButton" to="." method="_on_export_button_pressed"]
[connection signal="pressed" from="ResetLabel/ResetXpAndStatsButton" to="." method="_on_reset_xp_and_stats_button_pressed"]

View file

@ -14,15 +14,13 @@ var _last_autocleanup: int = 0 # ms since engine start
var current_phrase: String = "" var current_phrase: String = ""
var current_status: String = "" var current_status: String = ""
var last_played_phrases: Dictionary = {} # p: [timestamp1, timestamp2, ...] # represents the order of last played phrases
var last_played_phrases: Array = []
func register_current_phrase_played(): func register_current_phrase_played():
var t = Time.get_unix_time_from_system()
if current_phrase in last_played_phrases: if current_phrase in last_played_phrases:
last_played_phrases[current_phrase].append(t) last_played_phrases.erase(current_phrase)
else: last_played_phrases.append(current_phrase)
last_played_phrases[current_phrase] = [t]
SaveManager.save_game() SaveManager.save_game()
func answer(p_in: String) -> bool: func answer(p_in: String) -> bool:
@ -38,23 +36,33 @@ func next_phrase() -> void:
current_phrase = "" current_phrase = ""
current_status = STATUS_NONE_AVAILABLE current_status = STATUS_NONE_AVAILABLE
return return
# search for a non played phrase # pick a random non-played phrase, if possible
var phrases_not_played = []
for p in PhrasesManager.phrases: for p in PhrasesManager.phrases:
if not p in last_played_phrases: if not p in last_played_phrases:
current_phrase = p phrases_not_played.append(p)
current_status = STATUS_PLEASE_REPEAT if len(phrases_not_played) > 0:
return current_phrase = phrases_not_played.pick_random()
# find the phrase that was played the longest ago current_status = STATUS_PLEASE_REPEAT
var phrases_last_ts: Dictionary = {} # timestamp: phrase return
var phrases_last_ts_keys: Array[float] = [] # find the half of phrases that were repeated longest ago
for p in last_played_phrases: var i_max = max(1, roundi(float(len(last_played_phrases)) / 2))
if p in PhrasesManager.phrases: var phrases_played_longest_ago = last_played_phrases.slice(0, i_max)
var t_max = last_played_phrases[p].max() # pick random phrase
phrases_last_ts[t_max] = p var phrase = phrases_played_longest_ago.pick_random()
phrases_last_ts_keys.append(t_max) if phrase == null: # this shouldn't happen!
# return the phrase with the smallest timestamp (-> longest ago) current_phrase = ""
current_phrase = phrases_last_ts[phrases_last_ts_keys.min()] current_status = STATUS_NONE_AVAILABLE
current_status = STATUS_PLEASE_REPEAT else:
current_phrase = phrase
current_status = STATUS_PLEASE_REPEAT
func try_overwrite_next_phrase(p: String):
if p in PhrasesManager.phrases:
current_phrase = p
current_status = STATUS_PLEASE_REPEAT
else: # if this didn't work, choose one automatically
next_phrase()
func cleanup_last_played_phrases(): func cleanup_last_played_phrases():
var changed = false var changed = false

View file

@ -5,13 +5,33 @@ const SAVEFILE = "user://save.dat"
func _ready() -> void: func _ready() -> void:
load_game() load_game()
func save_game(): func _to_dict() -> Dictionary:
var data: Dictionary = { return {
"player_xp": XpLevelManager.player_xp, "player_xp": XpLevelManager.player_xp,
"phrases": PhrasesManager.phrases, "phrases": PhrasesManager.phrases,
"last_played_phrases": CoreGameplayManager.last_played_phrases "last_played_phrases": CoreGameplayManager.last_played_phrases
} }
var data_json = JSON.stringify(data)
func _from_dict(data: Dictionary) -> int:
var successfully_set = 0
# set variables
if "player_xp" in data:
XpLevelManager.loading = true
XpLevelManager.player_xp = data["player_xp"]
XpLevelManager.loading = false
successfully_set += 1 #!
if "phrases" in data and data["phrases"] is Array:
PhrasesManager.phrases = []
for p in data["phrases"]:
PhrasesManager.phrases.append(p)
successfully_set += 1 #!
if "last_played_phrases" in data and data["last_played_phrases"] is Array:
CoreGameplayManager.last_played_phrases = data["last_played_phrases"]
successfully_set += 1 #!
return successfully_set
func save_game():
var data_json = JSON.stringify(_to_dict())
var f = FileAccess.open(SAVEFILE, FileAccess.WRITE) var f = FileAccess.open(SAVEFILE, FileAccess.WRITE)
f.store_string(data_json) f.store_string(data_json)
f.close() f.close()
@ -20,14 +40,19 @@ func load_game():
if FileAccess.file_exists(SAVEFILE): if FileAccess.file_exists(SAVEFILE):
var data_json = FileAccess.get_file_as_string(SAVEFILE) var data_json = FileAccess.get_file_as_string(SAVEFILE)
var data = JSON.parse_string(data_json) var data = JSON.parse_string(data_json)
# set variables _from_dict(data)
if "player_xp" in data:
XpLevelManager.loading = true func export_to_base64() -> String:
XpLevelManager.player_xp = data["player_xp"] var data_json = JSON.stringify(_to_dict())
XpLevelManager.loading = false return Marshalls.utf8_to_base64(data_json)
if "phrases" in data and data["phrases"] is Array:
PhrasesManager.phrases = [] func import_from_base64(encoded_data: String) -> bool:
for p in data["phrases"]: var data_json = Marshalls.base64_to_utf8(encoded_data)
PhrasesManager.phrases.append(p) var data_dict = JSON.parse_string(data_json)
if "last_played_phrases" in data and data["last_played_phrases"] is Dictionary: if data_dict == null:
CoreGameplayManager.last_played_phrases = data["last_played_phrases"] return false
var n_successful: int = _from_dict(data_dict) > 0
if n_successful > 0:
save_game()
return true
else: return false

View file

@ -2,7 +2,6 @@ extends Control
func _ready() -> void: func _ready() -> void:
CoreGameplayManager.next_phrase() CoreGameplayManager.next_phrase()
%SubmitButton.hide()
%AnswerInput.hide() %AnswerInput.hide()
func _on_settings_button_pressed() -> void: func _on_settings_button_pressed() -> void:
@ -24,17 +23,10 @@ func _process(_delta: float) -> void:
else: else:
%CurrentPhrase.text = "" %CurrentPhrase.text = ""
%AnswerInput.hide() %AnswerInput.hide()
%SubmitButton.hide()
if last_known_status != CoreGameplayManager.current_status: if last_known_status != CoreGameplayManager.current_status:
last_known_status = CoreGameplayManager.current_status last_known_status = CoreGameplayManager.current_status
%CurrentStatus.text = last_known_status %CurrentStatus.text = last_known_status
func _on_submit_button_pressed() -> void:
if CoreGameplayManager.answer(%AnswerInput.text):
%AnswerInput.clear()
func _on_answer_input_text_changed(new_text: String) -> void: func _on_answer_input_text_changed(new_text: String) -> void:
if new_text.to_lower() == CoreGameplayManager.current_phrase.to_lower(): if CoreGameplayManager.answer(new_text):
%SubmitButton.show() %AnswerInput.clear()
else:
%SubmitButton.hide()

View file

@ -1,6 +1,5 @@
extends PanelContainer extends PanelContainer
var showing_notification: bool = false
var notification_started: int = 0 # in ms var notification_started: int = 0 # in ms
var current_notification_timeout: int = 0 # in ms var current_notification_timeout: int = 0 # in ms
@ -9,18 +8,14 @@ func _ready() -> void:
func _process(_delta: float) -> void: func _process(_delta: float) -> void:
var t = Time.get_ticks_msec() var t = Time.get_ticks_msec()
if showing_notification: if visible:
if t - notification_started > current_notification_timeout: if t - notification_started > current_notification_timeout:
showing_notification = false hide()
else: else:
var n = NotificationQueue.get_next() # [text, timeout] or null var n = NotificationQueue.get_next() # [text, timeout] or null
if n != null: if n != null:
showing_notification = true
notification_started = t notification_started = t
$Label.text = n[0] $Label.text = n[0]
current_notification_timeout = n[1] current_notification_timeout = n[1]
# show()
if not showing_notification and visible: grab_focus()
hide()
elif showing_notification and not visible:
show()

View file

@ -8,3 +8,6 @@ var text: String:
func _on_remove_button_pressed() -> void: func _on_remove_button_pressed() -> void:
PhrasesManager.remove_phrase(text) PhrasesManager.remove_phrase(text)
func _on_label_button_pressed() -> void:
CoreGameplayManager.try_overwrite_next_phrase(text)

View file

@ -6,7 +6,22 @@ func _ready() -> void:
func _on_close_settings_button_pressed() -> void: func _on_close_settings_button_pressed() -> void:
hide() hide()
func _on_reset_xp_button_pressed() -> void: func _on_reset_xp_and_stats_button_pressed() -> void:
XpLevelManager.player_xp = 0 XpLevelManager.player_xp = 0
CoreGameplayManager.last_played_phrases = []
SaveManager.save_game() SaveManager.save_game()
NotificationQueue.add("Reset XP.") CoreGameplayManager.next_phrase()
NotificationQueue.add("Reset XP & Stats.")
func _on_import_button_pressed() -> void:
var data = DisplayServer.clipboard_get()
if SaveManager.import_from_base64(data):
NotificationQueue.add("Import successful", 4000)
CoreGameplayManager.next_phrase()
else:
NotificationQueue.add("Import failed", 4000)
func _on_export_button_pressed() -> void:
var data = SaveManager.export_to_base64()
DisplayServer.clipboard_set(data)
NotificationQueue.add("Exported to clipboard", 4000)