mirror of
https://github.com/mr0xb/godot-flashcards.git
synced 2026-08-27 20:44:56 -04:00
initial commit
This commit is contained in:
commit
94847a50a2
42 changed files with 2063 additions and 0 deletions
48
scenes/ui/quiz_mode/flashcard_display.gd
Normal file
48
scenes/ui/quiz_mode/flashcard_display.gd
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
extends PanelContainer
|
||||
|
||||
signal answer_submitted(is_correct: bool)
|
||||
|
||||
@onready var question_label = $MarginContainer/VBoxContainer/QuestionPanel/MarginContainer/QuestionLabel
|
||||
@onready var answer_panel = $MarginContainer/VBoxContainer/AnswerPanel
|
||||
@onready var answer_label = $MarginContainer/VBoxContainer/AnswerPanel/MarginContainer/AnswerLabel
|
||||
@onready var show_answer_btn = $MarginContainer/VBoxContainer/Controls/ShowAnswerButton
|
||||
@onready var rating_buttons = $MarginContainer/VBoxContainer/Controls/RatingButtons
|
||||
@onready var incorrect_btn = $MarginContainer/VBoxContainer/Controls/RatingButtons/IncorrectButton
|
||||
@onready var correct_btn = $MarginContainer/VBoxContainer/Controls/RatingButtons/CorrectButton
|
||||
|
||||
var current_card: Flashcard
|
||||
var answer_revealed: bool = false
|
||||
|
||||
func _ready() -> void:
|
||||
show_answer_btn.pressed.connect(_on_show_answer_pressed)
|
||||
incorrect_btn.pressed.connect(_on_incorrect_pressed)
|
||||
correct_btn.pressed.connect(_on_correct_pressed)
|
||||
|
||||
answer_panel.hide()
|
||||
rating_buttons.hide()
|
||||
|
||||
func set_card(card: Flashcard) -> void:
|
||||
current_card = card
|
||||
answer_revealed = false
|
||||
|
||||
question_label.text = card.question_text
|
||||
question_label.modulate = card.question_color
|
||||
|
||||
answer_label.text = card.answer_text
|
||||
answer_label.modulate = card.answer_color
|
||||
|
||||
answer_panel.hide()
|
||||
rating_buttons.hide()
|
||||
show_answer_btn.show()
|
||||
|
||||
func _on_show_answer_pressed() -> void:
|
||||
answer_revealed = true
|
||||
answer_panel.show()
|
||||
show_answer_btn.hide()
|
||||
rating_buttons.show()
|
||||
|
||||
func _on_incorrect_pressed() -> void:
|
||||
answer_submitted.emit(false)
|
||||
|
||||
func _on_correct_pressed() -> void:
|
||||
answer_submitted.emit(true)
|
||||
1
scenes/ui/quiz_mode/flashcard_display.gd.uid
Normal file
1
scenes/ui/quiz_mode/flashcard_display.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://lobty8kshx4c
|
||||
83
scenes/ui/quiz_mode/flashcard_display.tscn
Normal file
83
scenes/ui/quiz_mode/flashcard_display.tscn
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
[gd_scene load_steps=2 format=3 uid="uid://b4nnkfplcdxkp"]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/ui/quiz_mode/flashcard_display.gd" id="1_8xp2n"]
|
||||
|
||||
[node name="FlashcardDisplay" type="PanelContainer"]
|
||||
custom_minimum_size = Vector2(600, 400)
|
||||
script = ExtResource("1_8xp2n")
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="."]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 30
|
||||
theme_override_constants/margin_top = 30
|
||||
theme_override_constants/margin_right = 30
|
||||
theme_override_constants/margin_bottom = 30
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 20
|
||||
|
||||
[node name="QuestionPanel" type="PanelContainer" parent="MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/QuestionPanel"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 20
|
||||
theme_override_constants/margin_top = 20
|
||||
theme_override_constants/margin_right = 20
|
||||
theme_override_constants/margin_bottom = 20
|
||||
|
||||
[node name="QuestionLabel" type="Label" parent="MarginContainer/VBoxContainer/QuestionPanel/MarginContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 24
|
||||
text = "Question goes here"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
autowrap_mode = 3
|
||||
|
||||
[node name="AnswerPanel" type="PanelContainer" parent="MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/AnswerPanel"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 20
|
||||
theme_override_constants/margin_top = 20
|
||||
theme_override_constants/margin_right = 20
|
||||
theme_override_constants/margin_bottom = 20
|
||||
|
||||
[node name="AnswerLabel" type="Label" parent="MarginContainer/VBoxContainer/AnswerPanel/MarginContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 24
|
||||
text = "Answer goes here"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
autowrap_mode = 3
|
||||
|
||||
[node name="Controls" type="VBoxContainer" parent="MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 10
|
||||
|
||||
[node name="ShowAnswerButton" type="Button" parent="MarginContainer/VBoxContainer/Controls"]
|
||||
custom_minimum_size = Vector2(0, 50)
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 20
|
||||
text = "Show Answer"
|
||||
|
||||
[node name="RatingButtons" type="HBoxContainer" parent="MarginContainer/VBoxContainer/Controls"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 20
|
||||
alignment = 1
|
||||
|
||||
[node name="IncorrectButton" type="Button" parent="MarginContainer/VBoxContainer/Controls/RatingButtons"]
|
||||
custom_minimum_size = Vector2(200, 50)
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 18
|
||||
text = "❌ Incorrect"
|
||||
|
||||
[node name="CorrectButton" type="Button" parent="MarginContainer/VBoxContainer/Controls/RatingButtons"]
|
||||
custom_minimum_size = Vector2(200, 50)
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 18
|
||||
text = "✓ Correct"
|
||||
67
scenes/ui/quiz_mode/multiple_choice_display.gd
Normal file
67
scenes/ui/quiz_mode/multiple_choice_display.gd
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
extends PanelContainer
|
||||
|
||||
signal answer_submitted(is_correct: bool)
|
||||
|
||||
@onready var question_label = $MarginContainer/VBoxContainer/QuestionPanel/MarginContainer/QuestionLabel
|
||||
@onready var choices_container = $MarginContainer/VBoxContainer/ChoicesContainer
|
||||
@onready var feedback_panel = $MarginContainer/VBoxContainer/FeedbackPanel
|
||||
@onready var feedback_label = $MarginContainer/VBoxContainer/FeedbackPanel/MarginContainer/FeedbackLabel
|
||||
|
||||
var current_card: Flashcard
|
||||
var choice_buttons: Array[Button] = []
|
||||
var answered: bool = false
|
||||
|
||||
func _ready() -> void:
|
||||
feedback_panel.hide()
|
||||
|
||||
func set_card(card: Flashcard) -> void:
|
||||
current_card = card
|
||||
answered = false
|
||||
|
||||
question_label.text = card.question_text
|
||||
question_label.modulate = card.question_color
|
||||
|
||||
for button in choice_buttons:
|
||||
button.queue_free()
|
||||
choice_buttons.clear()
|
||||
|
||||
for i in range(card.choices.size()):
|
||||
var choice = card.choices[i]
|
||||
var button = Button.new()
|
||||
button.text = choice.text
|
||||
button.custom_minimum_size = Vector2(0, 50)
|
||||
button.add_theme_font_size_override("font_size", 18)
|
||||
button.pressed.connect(_on_choice_selected.bind(i))
|
||||
choices_container.add_child(button)
|
||||
choice_buttons.append(button)
|
||||
|
||||
feedback_panel.hide()
|
||||
|
||||
func _on_choice_selected(choice_index: int) -> void:
|
||||
if answered:
|
||||
return
|
||||
|
||||
answered = true
|
||||
var is_correct = current_card.choices[choice_index].is_correct
|
||||
|
||||
for button in choice_buttons:
|
||||
button.disabled = true
|
||||
|
||||
if is_correct:
|
||||
choice_buttons[choice_index].modulate = Color.GREEN
|
||||
feedback_label.text = "✓ Correct! " + current_card.answer_text
|
||||
feedback_panel.self_modulate = Color(0.2, 0.6, 0.2, 1.0)
|
||||
else:
|
||||
choice_buttons[choice_index].modulate = Color.RED
|
||||
|
||||
var correct_index = current_card.get_correct_choice_index()
|
||||
if correct_index >= 0:
|
||||
choice_buttons[correct_index].modulate = Color.GREEN
|
||||
|
||||
feedback_label.text = "❌ Incorrect. The answer is: " + current_card.answer_text
|
||||
feedback_panel.self_modulate = Color(0.6, 0.2, 0.2, 1.0)
|
||||
|
||||
feedback_panel.show()
|
||||
|
||||
await get_tree().create_timer(2.0).timeout
|
||||
answer_submitted.emit(is_correct)
|
||||
1
scenes/ui/quiz_mode/multiple_choice_display.gd.uid
Normal file
1
scenes/ui/quiz_mode/multiple_choice_display.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://2cccr5qtjk2c
|
||||
58
scenes/ui/quiz_mode/multiple_choice_display.tscn
Normal file
58
scenes/ui/quiz_mode/multiple_choice_display.tscn
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
[gd_scene load_steps=2 format=3 uid="uid://b0qexkdgxksek"]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/ui/quiz_mode/multiple_choice_display.gd" id="1_a2ghr"]
|
||||
|
||||
[node name="MultipleChoiceDisplay" type="PanelContainer"]
|
||||
custom_minimum_size = Vector2(600, 400)
|
||||
script = ExtResource("1_a2ghr")
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="."]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 30
|
||||
theme_override_constants/margin_top = 30
|
||||
theme_override_constants/margin_right = 30
|
||||
theme_override_constants/margin_bottom = 30
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 20
|
||||
|
||||
[node name="QuestionPanel" type="PanelContainer" parent="MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/QuestionPanel"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 20
|
||||
theme_override_constants/margin_top = 20
|
||||
theme_override_constants/margin_right = 20
|
||||
theme_override_constants/margin_bottom = 20
|
||||
|
||||
[node name="QuestionLabel" type="Label" parent="MarginContainer/VBoxContainer/QuestionPanel/MarginContainer"]
|
||||
custom_minimum_size = Vector2(0, 80)
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 24
|
||||
text = "Question goes here"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
autowrap_mode = 3
|
||||
|
||||
[node name="ChoicesContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 10
|
||||
|
||||
[node name="FeedbackPanel" type="PanelContainer" parent="MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/FeedbackPanel"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 20
|
||||
theme_override_constants/margin_top = 15
|
||||
theme_override_constants/margin_right = 20
|
||||
theme_override_constants/margin_bottom = 15
|
||||
|
||||
[node name="FeedbackLabel" type="Label" parent="MarginContainer/VBoxContainer/FeedbackPanel/MarginContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 18
|
||||
text = "Feedback"
|
||||
horizontal_alignment = 1
|
||||
autowrap_mode = 3
|
||||
95
scenes/ui/quiz_mode/quiz_scene.gd
Normal file
95
scenes/ui/quiz_mode/quiz_scene.gd
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
extends Control
|
||||
|
||||
@onready var exit_btn = $MarginContainer/VBoxContainer/Header/ExitButton
|
||||
@onready var progress_label = $MarginContainer/VBoxContainer/Header/ProgressLabel
|
||||
@onready var score_label = $MarginContainer/VBoxContainer/ScorePanel/ScoreLabel
|
||||
@onready var card_container = $MarginContainer/VBoxContainer/CardContainer
|
||||
|
||||
var quiz_session: QuizSession
|
||||
var current_card_display: Node
|
||||
|
||||
const FlashcardDisplayScene = preload("res://scenes/ui/quiz_mode/flashcard_display.tscn")
|
||||
const MultipleChoiceDisplayScene = preload("res://scenes/ui/quiz_mode/multiple_choice_display.tscn")
|
||||
|
||||
func _ready() -> void:
|
||||
exit_btn.pressed.connect(_on_exit_pressed)
|
||||
|
||||
func start_quiz(deck: Deck) -> void:
|
||||
if not deck or deck.cards.is_empty():
|
||||
push_error("Cannot start quiz with empty or null deck")
|
||||
return
|
||||
|
||||
quiz_session = QuizSession.new()
|
||||
quiz_session.question_answered.connect(_on_question_answered)
|
||||
quiz_session.session_completed.connect(_on_session_completed)
|
||||
quiz_session.progress_updated.connect(_on_progress_updated)
|
||||
quiz_session.start_session(deck, true)
|
||||
|
||||
var progress = DataManager.get_or_create_progress(deck.id)
|
||||
progress.start_session()
|
||||
DataManager.save_progress()
|
||||
|
||||
_show_next_card()
|
||||
_update_ui()
|
||||
|
||||
func _show_next_card() -> void:
|
||||
var card = quiz_session.get_current_card()
|
||||
|
||||
if not card:
|
||||
return
|
||||
|
||||
if current_card_display:
|
||||
current_card_display.queue_free()
|
||||
current_card_display = null
|
||||
|
||||
if card.card_type == Flashcard.QuestionType.BASIC:
|
||||
current_card_display = FlashcardDisplayScene.instantiate()
|
||||
else:
|
||||
current_card_display = MultipleChoiceDisplayScene.instantiate()
|
||||
|
||||
card_container.add_child(current_card_display)
|
||||
current_card_display.set_card(card)
|
||||
current_card_display.answer_submitted.connect(_on_answer_submitted)
|
||||
|
||||
func _on_answer_submitted(is_correct: bool) -> void:
|
||||
var card = quiz_session.get_current_card()
|
||||
|
||||
if quiz_session.deck:
|
||||
DataManager.record_quiz_result(quiz_session.deck.id, card.id, is_correct)
|
||||
|
||||
quiz_session.answer_question(is_correct)
|
||||
|
||||
func _on_question_answered(is_correct: bool, card: Flashcard) -> void:
|
||||
await get_tree().create_timer(0.5).timeout
|
||||
_update_ui()
|
||||
_show_next_card()
|
||||
|
||||
func _on_progress_updated(current: int, total: int) -> void:
|
||||
_update_ui()
|
||||
|
||||
func _on_session_completed(results: Dictionary) -> void:
|
||||
print("Quiz completed! Results:")
|
||||
print(" Total: %d" % results.total_questions)
|
||||
print(" Correct: %d" % results.correct)
|
||||
print(" Incorrect: %d" % results.incorrect)
|
||||
print(" Accuracy: %.1f%%" % results.accuracy)
|
||||
print(" Duration: %.1f seconds" % results.duration_seconds)
|
||||
|
||||
await get_tree().create_timer(1.0).timeout
|
||||
GameManager.return_to_main_menu()
|
||||
|
||||
func _update_ui() -> void:
|
||||
if quiz_session:
|
||||
progress_label.text = "Card %d / %d" % [
|
||||
quiz_session.current_index + 1,
|
||||
quiz_session.shuffled_cards.size()
|
||||
]
|
||||
|
||||
score_label.text = "Score: %d correct, %d incorrect (%.1f%%)" % [
|
||||
quiz_session.session_correct,
|
||||
quiz_session.session_incorrect,
|
||||
quiz_session.get_session_accuracy()
|
||||
]
|
||||
|
||||
func _on_exit_pressed() -> void:
|
||||
GameManager.return_to_main_menu()
|
||||
1
scenes/ui/quiz_mode/quiz_scene.gd.uid
Normal file
1
scenes/ui/quiz_mode/quiz_scene.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://sfunbafcelku
|
||||
57
scenes/ui/quiz_mode/quiz_scene.tscn
Normal file
57
scenes/ui/quiz_mode/quiz_scene.tscn
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
[gd_scene load_steps=2 format=3 uid="uid://bjldqy3e3m3yj"]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/ui/quiz_mode/quiz_scene.gd" id="1_vu2ha"]
|
||||
|
||||
[node name="QuizScene" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_vu2ha")
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_constants/margin_left = 40
|
||||
theme_override_constants/margin_top = 40
|
||||
theme_override_constants/margin_right = 40
|
||||
theme_override_constants/margin_bottom = 40
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 20
|
||||
|
||||
[node name="Header" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ExitButton" type="Button" parent="MarginContainer/VBoxContainer/Header"]
|
||||
custom_minimum_size = Vector2(100, 50)
|
||||
layout_mode = 2
|
||||
text = "Exit Quiz"
|
||||
|
||||
[node name="ProgressLabel" type="Label" parent="MarginContainer/VBoxContainer/Header"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_font_sizes/font_size = 24
|
||||
text = "Card 1 / 10"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="ScorePanel" type="PanelContainer" parent="MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ScoreLabel" type="Label" parent="MarginContainer/VBoxContainer/ScorePanel"]
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 18
|
||||
text = "Score: 0 correct, 0 incorrect (0.0%)"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="CardContainer" type="CenterContainer" parent="MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
Loading…
Reference in a new issue