Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 45 additions & 8 deletions Scenes/message_display.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,50 @@ grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_y0tj3")

[node name="Label" type="Label" parent="."]
layout_mode = 0
offset_right = 1153.0
offset_bottom = 649.0
[node name="Background" type="ColorRect" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
color = Color(0, 0, 0, 0.7)

[node name="CenterContainer" type="CenterContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2

[node name="Panel" type="Panel" parent="CenterContainer"]
custom_minimum_size = Vector2(900, 500)
layout_mode = 2

[node name="MarginContainer" type="MarginContainer" parent="CenterContainer/Panel"]
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="ScrollContainer" type="ScrollContainer" parent="CenterContainer/Panel/MarginContainer"]
layout_mode = 2

[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer/Panel/MarginContainer/ScrollContainer"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 20

[node name="Label" type="Label" parent="CenterContainer/Panel/MarginContainer/ScrollContainer/VBoxContainer"]
layout_mode = 2
theme_override_colors/font_color = Color(0, 0.286275, 0.556863, 1)
theme_override_colors/font_shadow_color = Color(1, 0.94902, 0.870588, 1)
theme_override_font_sizes/font_size = 100
theme_override_font_sizes/font_size = 28
horizontal_alignment = 1
vertical_alignment = 1
autowrap_mode = 2
autowrap_mode = 3
7 changes: 7 additions & 0 deletions Scenes/sink.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ script = ExtResource("2_16xt0")
position = Vector2(0, -5.83331)
shape = SubResource("RectangleShape2D_7l3ci")

[node name="IndicatorLight" type="ColorRect" parent="."]
offset_left = -10.0
offset_top = -220.0
offset_right = 10.0
offset_bottom = -200.0
color = Color(0, 1, 0, 1)
z_index = 1
[node name="hoverlabel" type="RichTextLabel" parent="."]
visible = false
offset_left = 180.0
Expand Down
52 changes: 52 additions & 0 deletions Scripts/TutorialManager.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
extends Node

var current_tutorial: Dictionary = {}
var tutorials_completed: Dictionary = {}
var show_tutorials: bool = true
var current_step: int = 0

signal tutorial_started(level: String)
signal tutorial_step_completed(step: String)
signal tutorial_finished(level: String)

func start_level_tutorial(level_name: String):
if tutorials_completed.get(level_name, false):
return

var tutorial_path = "res://tutorial_data/" + level_name + ".json"
var tutorial_file = FileAccess.open(tutorial_path, FileAccess.READ)
if tutorial_file:
var tutorial_text = tutorial_file.get_as_text()
current_tutorial = JSON.parse_string(tutorial_text)
current_step = 0
emit_signal("tutorial_started", level_name)
show_current_step()

func show_current_step():
if current_step >= current_tutorial.steps.size():
finish_tutorial()
return

var step = current_tutorial.steps[current_step]
show_tooltip(step.text, step.get("highlight", ""))

func show_tooltip(text: String, highlight_target: String = ""):
var tooltip = preload("res://Scenes/TutorialTooltip.tscn").instantiate()
tooltip.set_text(text)
if highlight_target != "":
var target = get_tree().get_first_node_in_group(highlight_target)
if target:
tooltip.point_to(target)
add_child(tooltip)

func next_step():
current_step += 1
show_current_step()

func skip_tutorial():
finish_tutorial()

func finish_tutorial():
tutorials_completed[current_tutorial.level] = true
emit_signal("tutorial_finished", current_tutorial.level)
current_tutorial = {}
20 changes: 20 additions & 0 deletions Scripts/TutorialTooltip.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
extends Control

@onready var label = $Panel/MarginContainer/VBoxContainer/Label
@onready var next_button = $Panel/MarginContainer/VBoxContainer/HBoxContainer/NextButton
@onready var skip_button = $Panel/MarginContainer/VBoxContainer/HBoxContainer/SkipButton

func set_text(text: String):
label.text = text

func point_to(target: Node2D):
# Position tooltip near target
global_position = target.global_position + Vector2(50, -100)

func _on_next_button_pressed():
TutorialManager.next_step()
queue_free()

func _on_skip_button_pressed():
TutorialManager.skip_tutorial()
queue_free()
36 changes: 36 additions & 0 deletions Scripts/level.gd
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,44 @@ func next_level():
print("End of Levels.")
get_tree().change_scene_to_file("res://Scenes/end_of_all_levels.tscn")
else:
var failure_data = analyze_failure()
show_educational_failure(failure_data, message_display)
await message_display.show_message_for_duration(4.0)
print("Failed. Try Again")
AudioManager.play_level_fail()
message_display.show_message("Failed. Try Again")
await message_display.show_message_for_duration(2.0)
message_display.visible = false

func analyze_failure() -> Dictionary:
var reason = ""
var hint = ""
var lesson = ""

if not sinkUsed:
reason = "No events were delivered to any sink"
hint = "Click an event to select it, then click a sink to route it"
lesson = "In Knative, events must be routed from sources to sinks through triggers"

elif sinkBoxMatchNeeded[levelind] and not sinkBoxMatchPresent:
reason = "Wrong event type delivered to sink"
hint = "Use filters to match event colors with sink colors"
lesson = "Knative Triggers use filters to ensure events reach the correct subscribers based on event attributes"

elif dlsRequired[levelind] and not dlsUsed:
reason = "Failed events were not routed to Dead Letter Sink"
hint = "Click the DLS to catch events that hit the blockage"
lesson = "Dead Letter Sinks prevent data loss by catching events that fail to process"

return {
"reason": reason,
"hint": hint,
"lesson": lesson
}

func show_educational_failure(data: Dictionary, message_display: Control):
message_display.show_failure_with_lesson(
data.reason,
data.hint,
data.lesson
)
9 changes: 8 additions & 1 deletion Scripts/message_display.gd
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ extends Control

func show_message(text):
print("Message displayed")
$Label.text=text
$CenterContainer/Panel/MarginContainer/ScrollContainer/VBoxContainer/Label.text = text

func show_message_for_duration(duration: float) -> void:
var timer := Timer.new()
Expand All @@ -12,3 +12,10 @@ func show_message_for_duration(duration: float) -> void:
timer.start()
await timer.timeout
timer.queue_free()

func show_failure_with_lesson(reason: String, hint: String, lesson: String):
var full_message = "❌ Level Failed\n\n"
full_message += "Why: " + reason + "\n\n"
full_message += "💡 Hint: " + hint + "\n\n"
full_message += "🎓 Knative Concept:\n" + lesson
$CenterContainer/Panel/MarginContainer/ScrollContainer/VBoxContainer/Label.text = full_message
17 changes: 16 additions & 1 deletion Scripts/sink.gd
Original file line number Diff line number Diff line change
@@ -1,15 +1,30 @@
extends Sprite2D

@export var expectedType: String
@export var available: bool = true

var blink_timer: float = 0.0
var blink_interval: float = 0.5
var indicator_light: ColorRect = null


# Called when the node enters the scene tree for the first time.
func _ready() -> void:
pass # Replace with function body.
indicator_light = get_node_or_null("IndicatorLight")
if indicator_light == null:
push_warning("IndicatorLight not found in sink scene")


# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
if indicator_light != null:
if available:
blink_timer += delta
if blink_timer >= blink_interval:
blink_timer = 0.0
indicator_light.visible = !indicator_light.visible
else:
indicator_light.visible = false
pass


Expand Down
21 changes: 21 additions & 0 deletions tutorial_data/basicEventFlow.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"level": "basicEventFlow",
"steps": [
{
"text": "Welcome! In Knative, events flow from Sources to Sinks through Brokers.",
"highlight": ""
},
{
"text": "Click this event to select it",
"highlight": "EventBox"
},
{
"text": "Now click the sink to route the event",
"highlight": "Sink"
},
{
"text": "Great! You just created a basic Knative event flow. This is like a Trigger connecting a Broker to a Sink.",
"highlight": ""
}
]
}
25 changes: 25 additions & 0 deletions tutorial_data/boxClick.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"level": "boxClick",
"steps": [
{
"text": "Level 2: Triggers & Filters. In Knative, Triggers use filters to route events based on their attributes.",
"highlight": ""
},
{
"text": "Notice the colored events. Each has a different 'type' attribute.",
"highlight": "EventBox"
},
{
"text": "Drag a filter onto the conveyor to filter events by color",
"highlight": "Filter"
},
{
"text": "Route matching events to the correct colored sink",
"highlight": "Sink"
},
{
"text": "Perfect! Knative Triggers work the same way - filtering events by attributes like type, source, or custom fields.",
"highlight": ""
}
]
}
21 changes: 21 additions & 0 deletions tutorial_data/dlqPattern.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"level": "dlqPattern",
"steps": [
{
"text": "Level 4: Dead Letter Queue. When events fail to process, they need somewhere to go.",
"highlight": ""
},
{
"text": "Notice the blockage - events will fail here",
"highlight": "Blockage"
},
{
"text": "Click the Dead Letter Sink (DLS) to route failed events there",
"highlight": "DLS"
},
{
"text": "Perfect! In Knative, Dead Letter Sinks prevent data loss by catching failed events for debugging or retry.",
"highlight": ""
}
]
}
21 changes: 21 additions & 0 deletions tutorial_data/multiSink.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"level": "multiSink",
"steps": [
{
"text": "Level 3: Multiple Sinks. In Knative, one Broker can route events to multiple subscribers.",
"highlight": ""
},
{
"text": "You have multiple sinks. Each needs matching events.",
"highlight": "Sink"
},
{
"text": "Use filters to route each event type to its matching sink",
"highlight": "Filter"
},
{
"text": "Excellent! This is how Knative enables fan-out patterns - one event source, multiple subscribers.",
"highlight": ""
}
]
}