226 lines
7.3 KiB
GDScript
226 lines
7.3 KiB
GDScript
class_name WordsMinigame
|
|
extends Minigame
|
|
|
|
# Define the maximum number of GP inside each word
|
|
@export var max_number_of_gps: int = 6
|
|
# Define the size of the distractor queue for each GP
|
|
@export var distractors_queue_size: int = 6
|
|
# Define the time of visibility of a found word between rounds
|
|
@export var time_between_words: float = 3.
|
|
|
|
var current_word_progression: int = 0: set = _set_current_word_progression
|
|
var max_word_progression: int = 0
|
|
var current_gp_distractors_queue: Array[Dictionary] = []
|
|
var current_word_has_errors: bool = false
|
|
|
|
|
|
# Find the stimuli and distractions of the minigame.
|
|
func _find_stimuli_and_distractions() -> void:
|
|
# Get the currently known words list
|
|
var words_list: Array[Dictionary] = Database.get_words_for_lesson(lesson_nb, false, 2, max_number_of_gps)
|
|
if words_list.is_empty():
|
|
return
|
|
|
|
var current_lesson_words: Array[Dictionary] = []
|
|
var previous_lesson_words: Array[Dictionary] = []
|
|
|
|
for word: Dictionary in words_list:
|
|
if not FileAccess.file_exists(Database.get_word_sound_path(word)):
|
|
continue
|
|
|
|
if word.LessonNb == lesson_nb:
|
|
current_lesson_words.append(word)
|
|
else:
|
|
previous_lesson_words.append(word)
|
|
|
|
# Shuffle everything
|
|
current_lesson_words.shuffle()
|
|
previous_lesson_words.shuffle()
|
|
|
|
# Sort for remediation based on GP
|
|
current_lesson_words.sort_custom(_sort_scoring)
|
|
previous_lesson_words.sort_custom(_sort_scoring)
|
|
|
|
# If there is no previous stimuli, only adds from current lesson
|
|
if previous_lesson_words.is_empty():
|
|
while stimuli.size() < max_progression:
|
|
stimuli.append(current_lesson_words.pick_random())
|
|
else:
|
|
if not current_lesson_words.is_empty():
|
|
# If there are more stimuli in current lesson than needed
|
|
if current_lesson_words.size() >= current_lesson_stimuli_number:
|
|
for index: int in range(current_lesson_stimuli_number):
|
|
stimuli.append(current_lesson_words[index])
|
|
else:
|
|
stimuli.append_array(current_lesson_words)
|
|
|
|
# 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_words.pick_random())
|
|
|
|
# Gets other stimuli from previous errors or lessons
|
|
var spaces_left: int = max_progression - stimuli.size()
|
|
if previous_lesson_words.size() >= spaces_left:
|
|
for index: int in range(spaces_left):
|
|
stimuli.append(previous_lesson_words[index])
|
|
else:
|
|
stimuli.append_array(previous_lesson_words)
|
|
|
|
# If there are not enough stimuli, fill the rest with current lesson or previous lesson
|
|
if current_lesson_words:
|
|
while stimuli.size() < max_progression:
|
|
if current_lesson_words:
|
|
stimuli.append(current_lesson_words.pick_random())
|
|
else:
|
|
stimuli.append(previous_lesson_words.pick_random())
|
|
|
|
# Shuffle the stimuli
|
|
stimuli.shuffle()
|
|
|
|
# Find the GPs and distractors for each word
|
|
for stimulus: Dictionary in stimuli:
|
|
stimulus.GPs = Database.get_gp_from_word(stimulus.ID as int)
|
|
var grapheme_distractions: Array = []
|
|
for gp: Dictionary in stimulus.GPs:
|
|
grapheme_distractions.append(Database.get_distractors_for_grapheme(gp.ID as int, lesson_nb))
|
|
distractions.append(grapheme_distractions)
|
|
|
|
|
|
# Launch the minigame
|
|
func _start() -> void:
|
|
super()
|
|
if stimuli.is_empty():
|
|
Log.error("WordsMinigame: Cannot start game because stimuli is empty")
|
|
_win()
|
|
return
|
|
_setup_word_progression()
|
|
|
|
|
|
# Find and set the parameters of the minigame, like the number of lives or the victory conditions.
|
|
func _setup_minigame() -> void:
|
|
Log.trace("WordsMinigame: SetupMinigame")
|
|
super()
|
|
|
|
|
|
# Sets up the word progression for the current progression
|
|
func _setup_word_progression() -> void:
|
|
var stimulus: Dictionary = _get_current_stimulus()
|
|
var gps: Array = stimulus.GPs as Array
|
|
max_word_progression = gps.size()
|
|
current_word_progression = 0
|
|
|
|
_play_stimulus()
|
|
|
|
|
|
func _set_current_word_progression(p_current_word_progression: int) -> void:
|
|
current_word_progression = p_current_word_progression
|
|
|
|
consecutive_errors = 0
|
|
is_highlighting = false
|
|
|
|
if current_word_progression == max_word_progression:
|
|
current_progression += 1
|
|
else:
|
|
@warning_ignore("redundant_await")
|
|
await _on_current_word_progression_changed()
|
|
|
|
|
|
func _reset_distractors_queue() -> void:
|
|
var current_distractors: Array = distractions[current_progression][current_word_progression] as Array
|
|
|
|
current_gp_distractors_queue = current_distractors.duplicate()
|
|
current_gp_distractors_queue.shuffle()
|
|
while current_gp_distractors_queue.size() > distractors_queue_size:
|
|
current_gp_distractors_queue.pop_front()
|
|
|
|
|
|
# Gets the previous stimulus which is already found
|
|
func _get_previous_stimulus() -> Dictionary:
|
|
if stimuli.size() == 0 or current_progression == 0:
|
|
return {}
|
|
return stimuli[(current_progression-1) % stimuli.size()]
|
|
|
|
|
|
# Get the current stimulus which needs to be found to increase progression
|
|
func _get_current_stimulus() -> Dictionary:
|
|
if stimuli.size() == 0:
|
|
return {}
|
|
return stimuli[current_progression % stimuli.size()]
|
|
|
|
|
|
# Get the distractors for current word
|
|
func _get_current_distractors() -> Array:
|
|
return distractions[current_progression]
|
|
|
|
|
|
# Get the current GP to find
|
|
func _get_gp() -> Dictionary:
|
|
var stimulus: Dictionary = _get_current_stimulus()
|
|
if not stimulus or not stimulus.has("GPs") or current_word_progression >= (stimulus.GPs as Array).size():
|
|
return {}
|
|
return stimulus.GPs[current_word_progression]
|
|
|
|
|
|
# Get a random distractor for the current GP
|
|
func _get_distractor() -> Dictionary:
|
|
if current_gp_distractors_queue.is_empty():
|
|
_reset_distractors_queue()
|
|
|
|
if current_gp_distractors_queue:
|
|
return current_gp_distractors_queue.pop_front()
|
|
|
|
return {}
|
|
|
|
|
|
# Check if the provided GP is the expected answer
|
|
func _is_gp_right(gp: Dictionary) -> bool:
|
|
return gp == _get_gp()
|
|
|
|
|
|
# Log the response and score
|
|
func _log_new_response_and_score(gp: Dictionary) -> void:
|
|
# Logs the answer
|
|
_log_new_response(gp, self._get_gp())
|
|
|
|
if gp.has("ID"): # GP can be an empty dictionary (empty word)
|
|
_update_confusion_matrix_gp_score(self._get_gp().ID as int, gp.ID as int)
|
|
else:
|
|
Log.trace("WordsMinigame: Confusion matrix cannot be updated because word is empty") # Empty word is normal, it just does not update the confusion matrix
|
|
|
|
# Handles Remediation GP scoring
|
|
if self._is_gp_right(gp):
|
|
if not is_highlighting:
|
|
_update_remediation_gp_score(gp.ID as int, 1)
|
|
else:
|
|
if gp:
|
|
_update_remediation_gp_score(gp.ID as int, -1)
|
|
current_word_has_errors = true
|
|
_update_remediation_gp_score(self._get_gp().ID as int, -1)
|
|
|
|
# ------------- UI Callbacks ------------- #
|
|
|
|
func _play_stimulus() -> void:
|
|
await audio_player.play_word(_get_current_stimulus().Word as String)
|
|
|
|
# -------------- CONNECTIONS -------------- #
|
|
|
|
func _on_current_word_progression_changed() -> void:
|
|
_reset_distractors_queue()
|
|
|
|
|
|
func _on_current_progression_changed() -> void:
|
|
var finished_stimulus: Dictionary = _get_previous_stimulus()
|
|
if finished_stimulus.is_empty():
|
|
Log.error("WordsMinigame: Finished stimulus is empty")
|
|
return
|
|
if current_word_has_errors:
|
|
_update_remediation_word_score(finished_stimulus.ID as int, -1)
|
|
current_word_has_errors = false
|
|
else:
|
|
_update_remediation_word_score(finished_stimulus.ID as int, 1)
|
|
if current_progression >= max_progression:
|
|
return
|
|
_setup_word_progression()
|