Files
Kalulu/sources/minigames/ants/ants_minigame.gd
T
2025-09-05 15:55:48 +02:00

274 lines
8.7 KiB
GDScript

@tool
extends Minigame
const BLANK_SCENE: PackedScene = preload("res://sources/minigames/ants/blank.tscn")
const ANT_SCENE: PackedScene = preload("res://sources/minigames/ants/ant.tscn")
const WORD_SCENE: PackedScene = preload("res://sources/minigames/ants/word.tscn")
const LABEL_SETTINGS: LabelSettings = preload("res://resources/themes/minigames_label_settings.tres")
var current_sentence: Dictionary = {}
var answer_input_done: Array[bool] = []
var answers: Dictionary[String, String] # Expected, current
@onready var sentence_container: HFlowContainer = %Sentence
@onready var ants_spawn: Node2D = %AntsSpawn
@onready var ants_start: Node2D = %AntsStart
@onready var ants_end: Node2D = %AntsEnd
@onready var ants_despawn: Node2D = %AntsDespawn
@onready var ants: Node2D = %Ants
@onready var words: Node2D = %Words
func _find_stimuli_and_distractions() -> void:
var sentences_list: Array = Database.get_sentences_for_lesson(lesson_nb, difficulty + 2, 50)
if sentences_list.is_empty():
return
var current_lesson_sentences: Array[Dictionary] = []
var previous_lesson_sentences: Array[Dictionary] = []
for sentence_in_list: Dictionary in sentences_list:
if sentence_in_list.LessonNb == lesson_nb:
current_lesson_sentences.append(sentence_in_list)
else:
previous_lesson_sentences.append(sentence_in_list)
# Shuffle everything
current_lesson_sentences.shuffle()
previous_lesson_sentences.shuffle()
# If there is no previous stimuli, only adds from current lesson
if not previous_lesson_sentences:
if current_lesson_sentences.size() >= max_progression:
for index: int in range(max_progression):
stimuli.append(current_lesson_sentences[index])
else:
while stimuli.size() < max_progression:
stimuli.append(current_lesson_sentences.pick_random())
else:
if current_lesson_sentences:
# If there are more stimuli in current lesson than needed
if current_lesson_sentences.size() >= current_lesson_stimuli_number:
for index: int in range(current_lesson_stimuli_number):
stimuli.append(current_lesson_sentences[index])
else:
stimuli.append_array(current_lesson_sentences)
# If there are not enough stimuli from current lesson, we want at least half the target number of stimuli
var minimal_stimuli: int = floori(current_lesson_stimuli_number/2.0)
if stimuli.size() < minimal_stimuli:
while stimuli.size() < minimal_stimuli:
stimuli.append(current_lesson_sentences.pick_random())
# Gets other stimuli from previous errors or lessons
var spaces_left: int = max_progression - stimuli.size()
if previous_lesson_sentences.size() >= spaces_left:
for index: int in range(spaces_left):
stimuli.append(previous_lesson_sentences[index])
else:
stimuli.append_array(previous_lesson_sentences)
# If there are not enough stimuli, fill the rest with current lesson or previous lesson
if current_lesson_sentences:
while stimuli.size() < max_progression:
if current_lesson_sentences:
stimuli.append(current_lesson_sentences.pick_random())
else:
stimuli.append(previous_lesson_sentences.pick_random())
# Shuffle the stimuli
stimuli.shuffle()
Logger.trace("AntsMinigame: stimuli = " + str(stimuli))
func _start() -> void:
super()
_on_current_progression_changed()
func _get_new_sentence() -> void:
if audio_player.playing:
await audio_player.finished
await _next_sentence()
await _start_ants()
func _next_sentence() -> void:
var nodes: Array[Node] = []
nodes.append_array(sentence_container.get_children())
nodes.append_array(words.get_children())
for node: Node in nodes:
node.queue_free()
for ant: Ant in ants.get_children():
ant.walk()
var tween: Tween = create_tween()
tween.tween_property(ant, "global_position", ants_despawn.global_position, 1.0)
await tween.finished
ant.queue_free()
await ant.tree_exited
await get_tree().process_frame
current_sentence = stimuli.pop_front()
var current_words: PackedStringArray = (current_sentence.Sentence as String).replace("'", " ' ").replace("-", " - ").split(" ")
var inds_to_remove: Array[int] = []
for index: int in range(1, current_words.size()):
var word: String = current_words[index]
if word in ["?", "!", ":"]:
current_words[index - 1] += " " + word
inds_to_remove.append(index)
if word in ["'", "-", "¿", "¡"]:
current_words[index - 1] += word
inds_to_remove.append(index)
inds_to_remove.reverse()
for index: int in inds_to_remove:
current_words.remove_at(index)
var number_of_blanks: int = maxi(2, mini(difficulty, current_words.size()))
var blanks: Array = range(current_words.size())
blanks.shuffle()
while blanks.size() > number_of_blanks:
blanks.pop_back()
answers.clear()
answer_input_done = []
for index: int in range(current_words.size()):
var current_word: String = current_words[index]
if index in blanks:
var blank: Blank = BLANK_SCENE.instantiate()
blank.stimulus = current_word
sentence_container.add_child(blank)
var ant: Node2D = ANT_SCENE.instantiate()
ants.add_child(ant)
ant.global_position = ants_spawn.global_position
var word: Word = WORD_SCENE.instantiate()
words.add_child(word)
word.stimulus = current_word
word.current_anchor = ant
@warning_ignore("UNSAFE_METHOD_ACCESS")
word.current_anchor.set_monitorable(false)
answer_input_done.append(false)
answers[current_word] = ""
word.answer.connect(_on_word_answer.bind(word))
word.no_answer.connect(_on_word_no_answer.bind(word))
else:
var label: Label = Label.new()
sentence_container.add_child(label)
label.text = current_word + " "
label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
label.label_settings = LABEL_SETTINGS
func _start_ants() -> void:
var total_ants: int = ants.get_child_count()
# Animate each ant, one after another
for ant_index: int in range(total_ants):
var ant: Ant = ants.get_child(ant_index)
# Start walking animation or logic
ant.walk()
# Create a tween to move the ant from start to end point
var tween: Tween = create_tween()
# Compute interpolation factor (0.0 to 1.0) based on position in the list
var position_ratio: float = float(ant_index) / float(total_ants - 1)
# Interpolate position from ants_start to ants_end using the ratio
var start_position: Vector2 = ants_start.global_position
var end_position: Vector2 = ants_end.global_position
var target_position: Vector2 = lerp(start_position, end_position, position_ratio)
# Animate the movement over 1 second
tween.tween_property(ant, "global_position", target_position, 1.0)
await tween.finished
# Switch to idle state once movement is complete
ant.idle()
# Reactivate all words once ants have reached their positions
for word: Word in words.get_children():
word.disabled = false
func _on_current_progression_changed() -> void:
if stimuli.size() != 0:
await _get_new_sentence()
else:
_win()
func _on_word_answer(stimulus: String, expected_stimulus: String, word: TextureButton) -> void:
_log_new_response({"Word": stimulus}, {"Word": expected_stimulus})
answers[expected_stimulus] = stimulus
answer_input_done[word.get_index()] = true
var all_answered: bool = true
for answer: bool in answer_input_done:
if not answer:
all_answered = false
break
if all_answered:
var is_right: bool = true
var word_id: int = -1
for key: String in answers.keys():
word_id = Database.get_word_id_from_text(key)
if key != answers[key]:
is_right = false
if word_id != -1:
_update_remediation_word_score(word_id, -1)
word_id = Database.get_word_id_from_text(answers[key])
if word_id != -1:
_update_remediation_word_score(word_id, -1)
else:
if word_id != -1:
_update_remediation_word_score(word_id, 1)
for word_i: Word in words.get_children():
@warning_ignore("unsafe_method_access")
word_i.current_anchor.set_monitorable(true)
word_i.disabled = true
if is_right:
for index: int in range(words.get_child_count() - 1):
(words.get_child(index) as Word).right()
await (words.get_child(words.get_child_count() - 1) as Word).right()
current_progression += 1
else:
for index: int in range(words.get_child_count() - 1):
(words.get_child(index) as Word).wrong()
await (words.get_child(words.get_child_count() - 1) as Word).wrong()
current_lives -= 1
for index: int in range(ants.get_child_count()):
answer_input_done[index] = false
@warning_ignore("UNSAFE_PROPERTY_ACCESS")
words.get_child(index).current_anchor = ants.get_child(index)
@warning_ignore("UNSAFE_METHOD_ACCESS")
ants.get_child(index).set_monitorable(false)
for word_i: Word in words.get_children():
word_i.disabled = false
func _on_word_no_answer(word: TextureButton) -> void:
answer_input_done[word.get_index()] = false