Add server-side ELO ratings
This commit is contained in:
@@ -6,6 +6,7 @@ const CORRECT_ADVANCE_DELAY_SECONDS := 0.6
|
||||
|
||||
var main: Control
|
||||
var choice_buttons: Array[Button] = []
|
||||
var choice_percent_labels: Array[Label] = []
|
||||
var eliminated: Array = []
|
||||
var locked_wrong_choice_ids: Array = []
|
||||
var flagged := false
|
||||
@@ -23,7 +24,9 @@ var timer_label: Label
|
||||
var stem_label: RichTextLabel
|
||||
var lead_label: Label
|
||||
var explanation_label: RichTextLabel
|
||||
var question_elo_label: Label
|
||||
var next_button: Button
|
||||
var skip_button: Button
|
||||
var flag_button: Button
|
||||
var report_button: Button
|
||||
var nav_palette: HBoxContainer
|
||||
@@ -41,6 +44,10 @@ func _ready() -> void:
|
||||
add_theme_constant_override("separation", 10)
|
||||
if not QuestionBankService.choice_percentages_updated.is_connected(_on_choice_percentages_updated):
|
||||
QuestionBankService.choice_percentages_updated.connect(_on_choice_percentages_updated)
|
||||
if not QuestionBankService.question_rating_updated.is_connected(_on_question_rating_updated):
|
||||
QuestionBankService.question_rating_updated.connect(_on_question_rating_updated)
|
||||
if not QuestionBankService.question_skipped.is_connected(_on_question_skipped):
|
||||
QuestionBankService.question_skipped.connect(_on_question_skipped)
|
||||
|
||||
var top := HBoxContainer.new()
|
||||
top.add_theme_constant_override("separation", 12)
|
||||
@@ -125,6 +132,12 @@ func _ready() -> void:
|
||||
Style.set_label(lead_label, 21, Style.TEXT)
|
||||
question_content.add_child(lead_label)
|
||||
|
||||
question_elo_label = Label.new()
|
||||
question_elo_label.visible = false
|
||||
question_elo_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||||
Style.set_label(question_elo_label, 16, Style.AMBER)
|
||||
question_content.add_child(question_elo_label)
|
||||
|
||||
for index in range(5):
|
||||
var button := Button.new()
|
||||
button.custom_minimum_size = Vector2(0, 64)
|
||||
@@ -133,6 +146,19 @@ func _ready() -> void:
|
||||
button.pressed.connect(func(i := index): _select_choice(i))
|
||||
button.gui_input.connect(func(event: InputEvent, i := index): _choice_gui_input(event, i))
|
||||
choice_buttons.append(button)
|
||||
|
||||
var percent_label := Label.new()
|
||||
percent_label.visible = false
|
||||
percent_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
percent_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||||
percent_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
percent_label.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
percent_label.offset_left = 0
|
||||
percent_label.offset_right = -18
|
||||
Style.set_label(percent_label, 18, Style.TEXT)
|
||||
button.add_child(percent_label)
|
||||
choice_percent_labels.append(percent_label)
|
||||
|
||||
question_content.add_child(button)
|
||||
|
||||
explanation_label = RichTextLabel.new()
|
||||
@@ -145,6 +171,13 @@ func _ready() -> void:
|
||||
|
||||
var actions := HBoxContainer.new()
|
||||
actions.add_theme_constant_override("separation", 10)
|
||||
skip_button = Button.new()
|
||||
skip_button.text = "Skip"
|
||||
skip_button.custom_minimum_size = Vector2(120, 48)
|
||||
Style.style_chip(skip_button, Style.AMBER)
|
||||
skip_button.pressed.connect(_skip_current_question)
|
||||
actions.add_child(skip_button)
|
||||
|
||||
next_button = Button.new()
|
||||
next_button.text = "Submit"
|
||||
next_button.custom_minimum_size = Vector2(140, 48)
|
||||
@@ -215,7 +248,12 @@ func _load_question() -> void:
|
||||
flag_button.button_pressed = false
|
||||
_refresh_toolbar_state()
|
||||
explanation_label.visible = false
|
||||
question_elo_label.visible = false
|
||||
question_elo_label.text = ""
|
||||
next_button.text = "Submit"
|
||||
next_button.disabled = false
|
||||
skip_button.disabled = false
|
||||
skip_button.visible = BlockSessionService.session_kind == "core"
|
||||
|
||||
var question := BlockSessionService.get_current_question()
|
||||
if question.is_empty():
|
||||
@@ -255,6 +293,7 @@ func _load_question() -> void:
|
||||
else:
|
||||
_apply_saved_choice_state()
|
||||
next_button.text = "Next"
|
||||
_update_question_elo_label(true)
|
||||
|
||||
_refresh_nav_palette()
|
||||
_refresh_toolbar_state()
|
||||
@@ -276,6 +315,25 @@ func _select_choice(index: int) -> void:
|
||||
_apply_saved_choice_state()
|
||||
|
||||
|
||||
func _skip_current_question() -> void:
|
||||
if pending_correct_advance or not current_answer.is_empty() or BlockSessionService.session_kind != "core":
|
||||
return
|
||||
var question_id := str(BlockSessionService.get_current_question().get("id", ""))
|
||||
if question_id == "":
|
||||
return
|
||||
current_answer = BlockSessionService.skip_core_question(eliminated, flagged)
|
||||
if current_answer.is_empty():
|
||||
return
|
||||
QuestionBankService.skip_question(question_id)
|
||||
selected_choice = ""
|
||||
next_button.disabled = true
|
||||
skip_button.disabled = true
|
||||
pending_correct_advance = true
|
||||
correct_advance_delay = CORRECT_ADVANCE_DELAY_SECONDS
|
||||
_show_explanation()
|
||||
_update_question_elo_label(true)
|
||||
|
||||
|
||||
func _choice_gui_input(event: InputEvent, index: int) -> void:
|
||||
if pending_correct_advance:
|
||||
return
|
||||
@@ -333,6 +391,7 @@ func _submit_or_next() -> void:
|
||||
var answer: Dictionary = result.get("answer", {})
|
||||
if bool(result.get("is_correct", false)):
|
||||
current_answer = answer
|
||||
_update_question_elo_label(true)
|
||||
_trigger_correct_feedback()
|
||||
return
|
||||
_lock_wrong_choice(attempted_choice)
|
||||
@@ -361,6 +420,7 @@ func _show_explanation() -> void:
|
||||
for index in range(question.get("choices", []).size()):
|
||||
var choice: Dictionary = question["choices"][index]
|
||||
var id := str(choice.get("id", ""))
|
||||
_set_choice_button_text(index, choice)
|
||||
if id == correct_id:
|
||||
choice_buttons[index].modulate = Color(0.54, 1.0, 0.58)
|
||||
elif id == selected_choice:
|
||||
@@ -369,6 +429,7 @@ func _show_explanation() -> void:
|
||||
var result_text := "Correct" if current_answer.get("is_correct", false) else "Incorrect"
|
||||
explanation_label.text = "[b]%s[/b]\n%s" % [result_text, question.get("explanation", "")]
|
||||
explanation_label.visible = true
|
||||
_update_question_elo_label(true)
|
||||
_refresh_nav_palette()
|
||||
|
||||
|
||||
@@ -434,6 +495,8 @@ func _trigger_correct_feedback() -> void:
|
||||
red_fog_time = 0.0
|
||||
green_fog_time = FEEDBACK_FADE_SECONDS
|
||||
next_button.disabled = true
|
||||
if skip_button != null:
|
||||
skip_button.disabled = true
|
||||
for button in choice_buttons:
|
||||
button.disabled = true
|
||||
if feedback_fog != null:
|
||||
@@ -462,6 +525,8 @@ func _update_answer_feedback(delta: float) -> void:
|
||||
if correct_advance_delay <= 0.0:
|
||||
pending_correct_advance = false
|
||||
next_button.disabled = false
|
||||
if skip_button != null:
|
||||
skip_button.disabled = false
|
||||
_advance_or_finish()
|
||||
|
||||
|
||||
@@ -474,6 +539,8 @@ func _reset_wrong_feedback() -> void:
|
||||
correct_advance_delay = 0.0
|
||||
pending_correct_advance = false
|
||||
next_button.disabled = false
|
||||
if skip_button != null:
|
||||
skip_button.disabled = false
|
||||
if feedback_fog != null:
|
||||
feedback_fog.color = Color(1.0, 0.0, 0.0, 0.0)
|
||||
|
||||
@@ -596,22 +663,38 @@ func _apply_saved_choice_state() -> void:
|
||||
else:
|
||||
choice_buttons[index].modulate = Color.WHITE
|
||||
|
||||
if pending_correct_advance:
|
||||
_apply_correct_answer_preview()
|
||||
|
||||
|
||||
func _set_choice_button_text(index: int, choice: Dictionary) -> void:
|
||||
var choice_id := str(choice.get("id", ""))
|
||||
var choice_text := str(choice.get("text", ""))
|
||||
_set_choice_percent_label(index, "")
|
||||
if locked_wrong_choice_ids.has(choice_id):
|
||||
var text := "%s. %s" % [choice_id, _strike_text(choice_text)]
|
||||
var percentage := _get_choice_selection_percentage(choice_id)
|
||||
if percentage >= 0:
|
||||
text += "\n %d%%" % percentage
|
||||
choice_buttons[index].text = text
|
||||
_set_choice_percent_label(index, "%d%%" % percentage)
|
||||
choice_buttons[index].text = "%s. %s" % [choice_id, _strike_text(choice_text)]
|
||||
elif _should_show_correct_choice_percentage(choice_id):
|
||||
var correct_percentage := _get_choice_selection_percentage(choice_id)
|
||||
if correct_percentage >= 0:
|
||||
_set_choice_percent_label(index, "%d%%" % correct_percentage)
|
||||
choice_buttons[index].text = "%s. %s" % [choice_id, choice_text]
|
||||
elif eliminated.has(choice_id):
|
||||
choice_buttons[index].text = "%s. [eliminated] %s" % [choice_id, _strike_text(choice_text)]
|
||||
else:
|
||||
choice_buttons[index].text = "%s. %s" % [choice_id, choice_text]
|
||||
|
||||
|
||||
func _set_choice_percent_label(index: int, text: String) -> void:
|
||||
if index < 0 or index >= choice_percent_labels.size():
|
||||
return
|
||||
var label := choice_percent_labels[index]
|
||||
label.text = text
|
||||
label.visible = text != ""
|
||||
|
||||
|
||||
func _get_choice_selection_percentage(choice_id: String) -> int:
|
||||
var question := BlockSessionService.get_current_question()
|
||||
var percentages: Dictionary = question.get("choice_selection_percentages", {})
|
||||
@@ -622,6 +705,21 @@ func _get_choice_selection_percentage(choice_id: String) -> int:
|
||||
return clamp(int(percentages.get(choice_id, -1)), 0, 100)
|
||||
|
||||
|
||||
func _should_show_correct_choice_percentage(choice_id: String) -> bool:
|
||||
var question := BlockSessionService.get_current_question()
|
||||
return choice_id == str(question.get("correct_choice_id", "")) and (pending_correct_advance or not current_answer.is_empty())
|
||||
|
||||
|
||||
func _apply_correct_answer_preview() -> void:
|
||||
var question := BlockSessionService.get_current_question()
|
||||
var correct_id := str(question.get("correct_choice_id", ""))
|
||||
var choices: Array = question.get("choices", [])
|
||||
for index in range(choices.size()):
|
||||
if str(choices[index].get("id", "")) == correct_id:
|
||||
choice_buttons[index].modulate = Color(0.54, 1.0, 0.58)
|
||||
return
|
||||
|
||||
|
||||
func _on_choice_percentages_updated(question_id: String, percentages: Dictionary) -> void:
|
||||
if not visible:
|
||||
return
|
||||
@@ -631,6 +729,37 @@ func _on_choice_percentages_updated(question_id: String, percentages: Dictionary
|
||||
_apply_saved_choice_state()
|
||||
|
||||
|
||||
func _on_question_rating_updated(question_id: String, question_elo: int, _rating: Dictionary) -> void:
|
||||
if str(BlockSessionService.get_current_question().get("id", "")) != question_id:
|
||||
return
|
||||
BlockSessionService.update_question_elo(question_id, question_elo)
|
||||
_update_question_elo_label(not current_answer.is_empty() or pending_correct_advance)
|
||||
_apply_saved_choice_state()
|
||||
|
||||
|
||||
func _on_question_skipped(question_id: String, correct_choice_id: String, question_elo: int, _rating: Dictionary) -> void:
|
||||
if str(BlockSessionService.get_current_question().get("id", "")) != question_id:
|
||||
return
|
||||
var question := BlockSessionService.get_current_question()
|
||||
if correct_choice_id != "":
|
||||
question["correct_choice_id"] = correct_choice_id
|
||||
BlockSessionService.update_question_elo(question_id, question_elo)
|
||||
_update_question_elo_label(true)
|
||||
_apply_saved_choice_state()
|
||||
|
||||
|
||||
func _update_question_elo_label(show_label: bool) -> void:
|
||||
if question_elo_label == null:
|
||||
return
|
||||
if not show_label:
|
||||
question_elo_label.visible = false
|
||||
return
|
||||
var question := BlockSessionService.get_current_question()
|
||||
var question_elo := int(question.get("question_elo", 1000))
|
||||
question_elo_label.text = "Question ELO %d" % question_elo
|
||||
question_elo_label.visible = true
|
||||
|
||||
|
||||
func _strike_text(value: String) -> String:
|
||||
var result := ""
|
||||
for character in value:
|
||||
|
||||
Reference in New Issue
Block a user