From 4270648743b45e6c469f796cc051a65edeb2218f Mon Sep 17 00:00:00 2001
From: Daniel Ting
Date: Fri, 21 Aug 2020 13:08:09 -0500
Subject: [PATCH 01/29] feat: add ArraySound class as ArrayView dependency
This simple implementation plays a constant sound that is only
implemented on bubble sort as the arithmetic average of the two values
being compared.
---
levels/bubble_sort.gd | 3 +++
project.godot | 6 ++++++
views/array_sound.gd | 28 ++++++++++++++++++++++++++++
views/array_view.gd | 4 ++++
4 files changed, 41 insertions(+)
create mode 100644 views/array_sound.gd
diff --git a/levels/bubble_sort.gd b/levels/bubble_sort.gd
index 97cab92..8b8e913 100644
--- a/levels/bubble_sort.gd
+++ b/levels/bubble_sort.gd
@@ -48,3 +48,6 @@ func get_effect(i):
if i >= _end:
return EFFECTS.DIMMED
return EFFECTS.NONE
+
+func get_frac():
+ return (array.frac(_index) + array.frac(_index + 1)) / 2.0
diff --git a/project.godot b/project.godot
index 2d4a05c..e5619ac 100644
--- a/project.godot
+++ b/project.godot
@@ -14,6 +14,11 @@ _global_script_classes=[ {
"language": "GDScript",
"path": "res://models/array_model.gd"
}, {
+"base": "Node",
+"class": "ArraySound",
+"language": "GDScript",
+"path": "res://views/array_sound.gd"
+}, {
"base": "HBoxContainer",
"class": "ArrayView",
"language": "GDScript",
@@ -86,6 +91,7 @@ _global_script_classes=[ {
} ]
_global_script_class_icons={
"ArrayModel": "",
+"ArraySound": "",
"ArrayView": "",
"BinaryTreeModel": "",
"BogoSort": "",
diff --git a/views/array_sound.gd b/views/array_sound.gd
new file mode 100644
index 0000000..af2479b
--- /dev/null
+++ b/views/array_sound.gd
@@ -0,0 +1,28 @@
+class_name ArraySound
+extends Node
+
+const SAMPLE_HZ = 44100
+const MIN_HZ = 110
+const MAX_HZ = 440
+
+var frac: float
+var player = AudioStreamPlayer.new()
+var _phase = 0.0
+var _playback: AudioStreamGeneratorPlayback
+
+func _fill_buffer(pulse_hz):
+ var increment = pulse_hz / SAMPLE_HZ
+ for i in range(_playback.get_frames_available()):
+ _playback.push_frame(Vector2.ONE * sin(_phase * TAU))
+ _phase = fmod(_phase + increment, 1.0)
+
+func _process(delta):
+ _fill_buffer(MIN_HZ + (MAX_HZ - MIN_HZ) * frac)
+
+func _init():
+ add_child(player)
+ player.stream = AudioStreamGenerator.new()
+ player.stream.buffer_length = 0.05
+ player.stream.mix_rate = SAMPLE_HZ
+ _playback = player.get_stream_playback()
+ player.play()
diff --git a/views/array_view.gd b/views/array_view.gd
index 70468e2..3d556d6 100644
--- a/views/array_view.gd
+++ b/views/array_view.gd
@@ -16,6 +16,7 @@ var _rects = []
var _positions = []
var _pointer = Polygon2D.new()
var _pointer_size: int
+var _sound = ArraySound.new()
onready var _separation = 128 / _level.array.size
func _init(level):
@@ -23,6 +24,7 @@ func _init(level):
add_child(_level) # NOTE: This is necessary for it to read input
add_child(_tween) # NOTE: This is necessary for it to animate
add_child(_pointer)
+ add_child(_sound)
_pointer.hide()
func _ready():
@@ -65,6 +67,7 @@ func _ready():
_pointer.show()
func _process(delta):
+ _sound.frac = _level.get_frac()
if _pointer.visible:
var pointed = _level.get_pointer()
var height = rect_size.y - _pointer_size * 2
@@ -76,6 +79,7 @@ func _process(delta):
func _on_ComparisonSort_done():
set_process(false)
+ _sound.player.stop()
_pointer.hide()
for i in range(_rects.size()):
_rects[i].color = ComparisonSort.EFFECTS.NONE
From 1b4bcd7c607dca182182b47ff4f52c89a75a0660 Mon Sep 17 00:00:00 2001
From: Daniel Ting
Date: Fri, 21 Aug 2020 13:24:35 -0500
Subject: [PATCH 02/29] feat: add sounds for all sorts
In general, the frequency scales with the current element(s) being
compared.
---
levels/bogo_sort.gd | 3 +++
levels/cocktail_sort.gd | 3 +++
levels/comb_sort.gd | 3 +++
levels/cycle_sort.gd | 3 +++
levels/insertion_sort.gd | 3 +++
levels/merge_sort.gd | 7 +++++++
levels/odd_even_sort.gd | 3 +++
levels/quick_sort.gd | 3 +++
levels/selection_sort.gd | 3 +++
levels/shell_sort.gd | 3 +++
10 files changed, 34 insertions(+)
diff --git a/levels/bogo_sort.gd b/levels/bogo_sort.gd
index fdeac81..666bf12 100644
--- a/levels/bogo_sort.gd
+++ b/levels/bogo_sort.gd
@@ -19,3 +19,6 @@ func next(action):
func get_effect(i):
return EFFECTS.NONE
+
+func get_frac():
+ return array.frac(0)
diff --git a/levels/cocktail_sort.gd b/levels/cocktail_sort.gd
index 0254244..1c3af51 100644
--- a/levels/cocktail_sort.gd
+++ b/levels/cocktail_sort.gd
@@ -56,3 +56,6 @@ func get_effect(i):
if i < _sorted and _forwards == true or i < _sorted - 1 or i >= array.size - _sorted:
return EFFECTS.DIMMED
return EFFECTS.NONE
+
+func get_frac():
+ return (array.frac(_index) + array.frac(_index + 1)) / 2.0
diff --git a/levels/comb_sort.gd b/levels/comb_sort.gd
index 0cda389..57c3015 100644
--- a/levels/comb_sort.gd
+++ b/levels/comb_sort.gd
@@ -46,3 +46,6 @@ func get_effect(i):
if i >= _end:
return EFFECTS.DIMMED
return EFFECTS.NONE
+
+func get_frac():
+ return (array.frac(_index) + array.frac(_index + _gap)) / 2.0
diff --git a/levels/cycle_sort.gd b/levels/cycle_sort.gd
index 082b195..97c9f17 100644
--- a/levels/cycle_sort.gd
+++ b/levels/cycle_sort.gd
@@ -48,3 +48,6 @@ func get_effect(i):
func get_pointer():
return _pointer
+
+func get_frac():
+ return array.frac(_index)
diff --git a/levels/insertion_sort.gd b/levels/insertion_sort.gd
index 569431d..c7d9712 100644
--- a/levels/insertion_sort.gd
+++ b/levels/insertion_sort.gd
@@ -50,3 +50,6 @@ func _grow():
if _end == array.size:
emit_signal("done")
_index = _end
+
+func get_frac():
+ return (array.frac(_index - 1) + array.frac(_index)) / 2.0
diff --git a/levels/merge_sort.gd b/levels/merge_sort.gd
index 8117b10..1de05d7 100644
--- a/levels/merge_sort.gd
+++ b/levels/merge_sort.gd
@@ -82,3 +82,10 @@ func _get_middle():
func _get_end():
"""Get the index of one past the right subarray's tail."""
return _sub_no * _sub_size + _sub_size
+
+func get_frac():
+ if _left == _get_middle():
+ return array.frac(_right)
+ if _right == _get_end():
+ return array.frac(_left)
+ return (array.frac(_left) + array.frac(_right)) / 2.0
diff --git a/levels/odd_even_sort.gd b/levels/odd_even_sort.gd
index fe42671..66ff88e 100644
--- a/levels/odd_even_sort.gd
+++ b/levels/odd_even_sort.gd
@@ -40,3 +40,6 @@ func get_effect(i):
if i == _index or i == _index + 1:
return EFFECTS.HIGHLIGHTED
return EFFECTS.NONE
+
+func get_frac():
+ return (array.frac(_index) + array.frac(_index + 1)) / 2.0
diff --git a/levels/quick_sort.gd b/levels/quick_sort.gd
index 3fab846..f9c621f 100644
--- a/levels/quick_sort.gd
+++ b/levels/quick_sort.gd
@@ -77,3 +77,6 @@ func get_effect(i):
func get_pointer():
return _pointer
+
+func get_frac():
+ return array.frac(_index)
diff --git a/levels/selection_sort.gd b/levels/selection_sort.gd
index 32bfbc7..b569693 100644
--- a/levels/selection_sort.gd
+++ b/levels/selection_sort.gd
@@ -52,3 +52,6 @@ func get_effect(i):
func get_pointer():
return _min
+
+func get_frac():
+ return array.frac(_index)
diff --git a/levels/shell_sort.gd b/levels/shell_sort.gd
index cc1675a..b05aa6b 100644
--- a/levels/shell_sort.gd
+++ b/levels/shell_sort.gd
@@ -58,3 +58,6 @@ func _grow():
_begin = 0
_end = _gap + _begin
_index = _gap + _begin
+
+func get_frac():
+ return (array.frac(_index - _gap) + array.frac(_index)) / 2.0
From 8fc061ad8518fe4e01079a7ebc0cee81a38e504d Mon Sep 17 00:00:00 2001
From: Daniel Ting
Date: Sat, 22 Aug 2020 11:52:40 -0500
Subject: [PATCH 03/29] feat: change sine wave to triangle wave
This gives the sound a more pleasant, lively, chiptune feeling.
---
views/array_sound.gd | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/views/array_sound.gd b/views/array_sound.gd
index af2479b..e716548 100644
--- a/views/array_sound.gd
+++ b/views/array_sound.gd
@@ -3,7 +3,7 @@ extends Node
const SAMPLE_HZ = 44100
const MIN_HZ = 110
-const MAX_HZ = 440
+const MAX_HZ = 880
var frac: float
var player = AudioStreamPlayer.new()
@@ -13,7 +13,7 @@ var _playback: AudioStreamGeneratorPlayback
func _fill_buffer(pulse_hz):
var increment = pulse_hz / SAMPLE_HZ
for i in range(_playback.get_frames_available()):
- _playback.push_frame(Vector2.ONE * sin(_phase * TAU))
+ _playback.push_frame(Vector2.ONE * triangle(_phase))
_phase = fmod(_phase + increment, 1.0)
func _process(delta):
@@ -26,3 +26,7 @@ func _init():
player.stream.mix_rate = SAMPLE_HZ
_playback = player.get_stream_playback()
player.play()
+
+func triangle(x):
+ """Generate a triangle wave from the given phase."""
+ return 2 / PI * asin(sin(PI * x))
From d40e5aeaedd0e70402a1db12e32af32c86ef452c Mon Sep 17 00:00:00 2001
From: Daniel Ting
Date: Sat, 22 Aug 2020 12:16:08 -0500
Subject: [PATCH 04/29] feat: add ability to toggle sound via hotkey
---
project.godot | 5 +++++
scenes/levels.tscn | 4 ++--
views/array_sound.gd | 7 +++++++
3 files changed, 14 insertions(+), 2 deletions(-)
diff --git a/project.godot b/project.godot
index e5619ac..bcca320 100644
--- a/project.godot
+++ b/project.godot
@@ -192,6 +192,11 @@ smaller={
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":83,"unicode":0,"echo":false,"script":null)
]
}
+sound_toggle={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":77,"unicode":0,"echo":false,"script":null)
+ ]
+}
[rendering]
diff --git a/scenes/levels.tscn b/scenes/levels.tscn
index a7fc2c9..b35a711 100644
--- a/scenes/levels.tscn
+++ b/scenes/levels.tscn
@@ -48,11 +48,11 @@ margin_right = 263.0
[node name="Label" type="Label" parent="LevelSelect/LevelsBorder"]
margin_left = 20.0
-margin_top = 577.0
+margin_top = 555.0
margin_right = 283.0
margin_bottom = 640.0
size_flags_vertical = 8
-text = "Use the WASD keys to adjust the size and speed of the simulation."
+text = "Use the WASD keys to adjust the size and speed of the simulation, and M to toggle sound."
autowrap = true
[node name="Preview" type="VBoxContainer" parent="LevelSelect"]
diff --git a/views/array_sound.gd b/views/array_sound.gd
index e716548..6555330 100644
--- a/views/array_sound.gd
+++ b/views/array_sound.gd
@@ -30,3 +30,10 @@ func _init():
func triangle(x):
"""Generate a triangle wave from the given phase."""
return 2 / PI * asin(sin(PI * x))
+
+func _input(event):
+ if event.is_action_pressed("sound_toggle"):
+ # Prevent event from propagating to ComparisonSort trigger
+ get_tree().set_input_as_handled()
+ var bus = AudioServer.get_bus_index("Master")
+ AudioServer.set_bus_mute(bus, not AudioServer.is_bus_mute(bus))
From be42b5660720facf343c53a8db432a832ddc4f54 Mon Sep 17 00:00:00 2001
From: Daniel Ting
Date: Mon, 24 Aug 2020 15:24:30 -0500
Subject: [PATCH 05/29] feat: remove tier system
It added a lot of complexity and is imperfect and possibly confusing for
little benefit.
---
levels/comparison_sort.gd | 3 ---
scripts/levels.gd | 26 ++++++-------------------
scripts/play.gd | 22 +++++++--------------
scripts/score.gd | 40 ++++++---------------------------------
4 files changed, 19 insertions(+), 72 deletions(-)
diff --git a/levels/comparison_sort.gd b/levels/comparison_sort.gd
index 81cc66d..65400ca 100644
--- a/levels/comparison_sort.gd
+++ b/levels/comparison_sort.gd
@@ -16,8 +16,6 @@ var DESCRIPTION = _get_header().split(" ")[1]
var CONTROLS = _get_header().split(" ")[2]
var array: ArrayModel
-var moves = 0
-var test = _get_header().split(" ")[0]
var _timer = Timer.new()
@@ -39,7 +37,6 @@ func _ready():
func _input(event):
"""Pass input events for checking and take appropriate action."""
if event.is_pressed():
- moves += 1
return next(event.as_text())
func next(action):
diff --git a/scripts/levels.gd b/scripts/levels.gd
index f4a2548..7f2f374 100644
--- a/scripts/levels.gd
+++ b/scripts/levels.gd
@@ -30,13 +30,9 @@ func _ready():
button.connect("focus_entered", self, "_on_Button_focus_entered")
button.connect("pressed", self, "_on_Button_pressed", [level])
buttons.add_child(button)
- var score = HBoxContainer.new()
- var time = Label.new()
- time.align = Label.ALIGN_RIGHT
- time.size_flags_horizontal = Control.SIZE_EXPAND_FILL
- var tier = Label.new()
- score.add_child(time)
- score.add_child(tier)
+ var score = Label.new()
+ score.align = Label.ALIGN_RIGHT
+ score.size_flags_horizontal = Control.SIZE_EXPAND_FILL
scores.add_child(score)
# Autofocus last played level
for button in buttons.get_children():
@@ -50,21 +46,11 @@ func _ready():
func _on_Button_focus_entered(size=_level.array.size):
# Update high scores
- var buttons = $LevelsBorder/Levels/LevelsContainer/Buttons
+ var container = $LevelsBorder/Levels/LevelsContainer
for i in range(LEVELS.size()):
- var score = $LevelsBorder/Levels/LevelsContainer/Scores.get_child(i)
- var name = buttons.get_child(i).text
+ var name = container.get_node("Buttons").get_child(i).text
var time = GlobalScore.get_time(name, size)
- if time == INF:
- score.get_child(0).text = ""
- score.get_child(1).text = "INF"
- score.get_child(1).add_color_override(
- "font_color", GlobalTheme.GREEN)
- else:
- score.get_child(0).text = "%.3f" % time
- score.get_child(1).text = GlobalScore.get_tier(name, size)
- score.get_child(1).add_color_override(
- "font_color", GlobalScore.get_color(name, size))
+ container.get_node("Scores").get_child(i).text = "INF" if time == INF else "%.3f" % time
# Pause a bit to show completely sorted array
if _level.array.is_sorted():
# Prevent race condition caused by keyboard input during pause
diff --git a/scripts/play.gd b/scripts/play.gd
index 33dabfe..9c8d5b1 100644
--- a/scripts/play.gd
+++ b/scripts/play.gd
@@ -30,9 +30,7 @@ func _input(event):
func _on_Level_done(level):
set_process(false)
- var name = level.NAME
- var size = level.array.size
- var score = get_score()
+ var time = get_score()
var moves = level.moves
var restart = Button.new()
restart.text = "RESTART LEVEL"
@@ -42,24 +40,18 @@ func _on_Level_done(level):
var back = Button.new()
back.text = "BACK TO LEVEL SELECT"
back.connect("pressed", self, "_on_Button_pressed", ["levels"])
- var time = Label.new()
- time.text = "%.3f" % score
- time.align = Label.ALIGN_RIGHT
- time.size_flags_horizontal = Control.SIZE_EXPAND_FILL
- var tier = Label.new()
- tier.text = GlobalScore.calculate_tier(score, moves)
- tier.align = Label.ALIGN_RIGHT
- tier.add_color_override(
- "font_color", GlobalScore.calculate_color(score, moves))
+ var score = Label.new()
+ score.text = "%.3f" % time
+ score.align = Label.ALIGN_RIGHT
+ score.size_flags_horizontal = Control.SIZE_EXPAND_FILL
$HUDBorder/HUD/Level.queue_free()
$HUDBorder/HUD/Score.queue_free()
$HUDBorder/HUD.add_child(restart)
$HUDBorder/HUD.add_child(separator)
$HUDBorder/HUD.add_child(back)
- $HUDBorder/HUD.add_child(time)
- $HUDBorder/HUD.add_child(tier)
+ $HUDBorder/HUD.add_child(score)
restart.grab_focus()
- GlobalScore.save_score(name, size, score, moves)
+ GlobalScore.save_score(level.NAME, level.array.size, time)
func _on_Button_pressed(scene):
GlobalScene.change_scene("res://scenes/" + scene + ".tscn",
diff --git a/scripts/score.gd b/scripts/score.gd
index a19eeb3..5265bc0 100644
--- a/scripts/score.gd
+++ b/scripts/score.gd
@@ -4,15 +4,7 @@ Common helper library for scoring functions.
extends Node
-const TIERS = ["F", "D", "C", "B", "A", "S"]
-const COLORS = [
- Color("f44336"),
- Color("ff9800"),
- Color("ffeb3b"),
- Color("4caf50"),
- Color("03a9f4"),
- Color("e040fb"),
-]
+const VERSION = 0 # Increment when changing save file format
var _save: Dictionary
@@ -24,39 +16,19 @@ func _init():
func get_time(name, size):
if name in _save and str(size) in _save[name]:
- return _save[name][str(size)][0]
+ return _save[name][str(size)]
return INF
-func get_tier(name, size):
- if name in _save and str(size) in _save[name]:
- return _save[name][str(size)][1]
- return ""
-
-func get_color(name, size):
- var tier = get_tier(name, size)
- return Color.black if tier.empty() else COLORS[TIERS.find(tier)]
-
-func calculate_tier(time, moves):
- return TIERS[min(int(moves / time), TIERS.size() - 1)]
-
-func calculate_color(time, moves):
- return COLORS[TIERS.find(calculate_tier(time, moves))]
-
-func save_score(name, size, time, moves):
+func save_score(name, size, time):
if not name in _save:
_save[name] = {}
if not str(size) in _save[name]:
- _save[name][str(size)] = [INF, ""]
- var prev_time = get_time(name, size)
- var prev_tier = get_tier(name, size)
- var tier = calculate_tier(time, moves)
- var tier_index = TIERS.find(tier)
- if (prev_tier.empty() or tier_index > TIERS.find(prev_tier)
- or tier_index == TIERS.find(prev_tier) and time < prev_time):
- _save[name][str(size)] = [time, tier]
+ _save[name][str(size)] = INF
+ _save[name][str(size)] = min(time, get_time(name, size))
_save()
func _save():
+ _save["VERSION"] = VERSION
var file = File.new()
file.open("user://save.json", File.WRITE)
file.store_line(to_json(_save))
From 3d43161367ae5b702113cdeb1e43a55bf2e921e2 Mon Sep 17 00:00:00 2001
From: Daniel Ting
Date: Tue, 25 Aug 2020 17:02:29 -0500
Subject: [PATCH 06/29] feat: add level name header
---
scenes/levels_redesign.gd | 36 +++++++++++++
scenes/levels_redesign.tscn | 101 ++++++++++++++++++++++++++++++++++++
2 files changed, 137 insertions(+)
create mode 100644 scenes/levels_redesign.gd
create mode 100644 scenes/levels_redesign.tscn
diff --git a/scenes/levels_redesign.gd b/scenes/levels_redesign.gd
new file mode 100644
index 0000000..d41439d
--- /dev/null
+++ b/scenes/levels_redesign.gd
@@ -0,0 +1,36 @@
+extends VBoxContainer
+
+const LEVELS = [
+ BubbleSort,
+ InsertionSort,
+ SelectionSort,
+ MergeSort,
+ QuickSort,
+ CocktailSort,
+ ShellSort,
+ CombSort,
+ CycleSort,
+ OddEvenSort,
+]
+
+var _index = 0
+var _level: ComparisonSort
+
+func _ready():
+ _level = LEVELS[_index].new(ArrayModel.new())
+ $Names/Current.text = "< " + _level.NAME + " >"
+
+func _switch_level(index):
+ if index == -1:
+ _index = LEVELS.size() - 1
+ elif index == LEVELS.size():
+ _index = 0
+ else:
+ _index = index
+ _ready()
+
+func _input(event):
+ if event.is_action_pressed("ui_left", true):
+ _switch_level(_index - 1)
+ if event.is_action_pressed("ui_right", true):
+ _switch_level(_index + 1)
diff --git a/scenes/levels_redesign.tscn b/scenes/levels_redesign.tscn
new file mode 100644
index 0000000..d9331c2
--- /dev/null
+++ b/scenes/levels_redesign.tscn
@@ -0,0 +1,101 @@
+[gd_scene load_steps=3 format=2]
+
+[ext_resource path="res://assets/theme.theme" type="Theme" id=1]
+[ext_resource path="res://scenes/levels_redesign.gd" type="Script" id=2]
+
+[node name="Viewport" type="MarginContainer"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+theme = ExtResource( 1 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Levels" type="VBoxContainer" parent="."]
+margin_left = 20.0
+margin_top = 20.0
+margin_right = 1260.0
+margin_bottom = 700.0
+script = ExtResource( 2 )
+
+[node name="Names" type="HBoxContainer" parent="Levels"]
+margin_right = 1240.0
+margin_bottom = 19.0
+
+[node name="Current" type="Label" parent="Levels/Names"]
+margin_right = 1240.0
+margin_bottom = 19.0
+size_flags_horizontal = 3
+text = "< CURRENT >"
+align = 1
+
+[node name="HBoxContainer" type="HBoxContainer" parent="Levels"]
+margin_top = 27.0
+margin_right = 1240.0
+margin_bottom = 134.0
+
+[node name="MarginContainer" type="MarginContainer" parent="Levels/HBoxContainer"]
+margin_right = 616.0
+margin_bottom = 107.0
+size_flags_horizontal = 3
+
+[node name="Label" type="Label" parent="Levels/HBoxContainer/MarginContainer"]
+margin_left = 20.0
+margin_top = 44.0
+margin_right = 596.0
+margin_bottom = 63.0
+
+[node name="VBoxContainer" type="VBoxContainer" parent="Levels/HBoxContainer"]
+margin_left = 624.0
+margin_right = 1240.0
+margin_bottom = 107.0
+size_flags_horizontal = 3
+
+[node name="MarginContainer" type="MarginContainer" parent="Levels/HBoxContainer/VBoxContainer"]
+margin_right = 616.0
+margin_bottom = 40.0
+size_flags_vertical = 3
+
+[node name="HBoxContainer" type="HBoxContainer" parent="Levels/HBoxContainer/VBoxContainer"]
+margin_top = 48.0
+margin_right = 616.0
+margin_bottom = 107.0
+size_flags_vertical = 3
+
+[node name="MarginContainer" type="MarginContainer" parent="Levels/HBoxContainer/VBoxContainer/HBoxContainer"]
+margin_right = 200.0
+margin_bottom = 59.0
+size_flags_horizontal = 3
+
+[node name="Label" type="Label" parent="Levels/HBoxContainer/VBoxContainer/HBoxContainer/MarginContainer"]
+margin_left = 20.0
+margin_top = 20.0
+margin_right = 180.0
+margin_bottom = 39.0
+size_flags_horizontal = 3
+
+[node name="MarginContainer2" type="MarginContainer" parent="Levels/HBoxContainer/VBoxContainer/HBoxContainer"]
+margin_left = 208.0
+margin_right = 408.0
+margin_bottom = 59.0
+size_flags_horizontal = 3
+
+[node name="Label" type="Label" parent="Levels/HBoxContainer/VBoxContainer/HBoxContainer/MarginContainer2"]
+margin_left = 20.0
+margin_top = 20.0
+margin_right = 180.0
+margin_bottom = 39.0
+size_flags_horizontal = 3
+
+[node name="MarginContainer3" type="MarginContainer" parent="Levels/HBoxContainer/VBoxContainer/HBoxContainer"]
+margin_left = 416.0
+margin_right = 616.0
+margin_bottom = 59.0
+size_flags_horizontal = 3
+
+[node name="Label" type="Label" parent="Levels/HBoxContainer/VBoxContainer/HBoxContainer/MarginContainer3"]
+margin_left = 20.0
+margin_top = 20.0
+margin_right = 180.0
+margin_bottom = 39.0
+size_flags_horizontal = 3
From c08b9c0a3754bbb708cb4968c2d708453e38d4c7 Mon Sep 17 00:00:00 2001
From: Daniel Ting
Date: Tue, 25 Aug 2020 17:46:16 -0500
Subject: [PATCH 07/29] feat: add preview display with feature parity
Supports adjusting size and speed just like the old design.
---
scenes/levels_redesign.gd | 36 ------------
scenes/levels_redesign.tscn | 107 +++++++++++++++++++++++-------------
scenes/menu.tscn | 30 +++++-----
scenes/play.tscn | 32 +++++------
scripts/levels_redesign.gd | 71 ++++++++++++++++++++++++
scripts/menu.gd | 2 +-
scripts/play.gd | 4 +-
views/array_sound.gd | 2 +-
8 files changed, 173 insertions(+), 111 deletions(-)
delete mode 100644 scenes/levels_redesign.gd
create mode 100644 scripts/levels_redesign.gd
diff --git a/scenes/levels_redesign.gd b/scenes/levels_redesign.gd
deleted file mode 100644
index d41439d..0000000
--- a/scenes/levels_redesign.gd
+++ /dev/null
@@ -1,36 +0,0 @@
-extends VBoxContainer
-
-const LEVELS = [
- BubbleSort,
- InsertionSort,
- SelectionSort,
- MergeSort,
- QuickSort,
- CocktailSort,
- ShellSort,
- CombSort,
- CycleSort,
- OddEvenSort,
-]
-
-var _index = 0
-var _level: ComparisonSort
-
-func _ready():
- _level = LEVELS[_index].new(ArrayModel.new())
- $Names/Current.text = "< " + _level.NAME + " >"
-
-func _switch_level(index):
- if index == -1:
- _index = LEVELS.size() - 1
- elif index == LEVELS.size():
- _index = 0
- else:
- _index = index
- _ready()
-
-func _input(event):
- if event.is_action_pressed("ui_left", true):
- _switch_level(_index - 1)
- if event.is_action_pressed("ui_right", true):
- _switch_level(_index + 1)
diff --git a/scenes/levels_redesign.tscn b/scenes/levels_redesign.tscn
index d9331c2..149eea9 100644
--- a/scenes/levels_redesign.tscn
+++ b/scenes/levels_redesign.tscn
@@ -1,7 +1,8 @@
-[gd_scene load_steps=3 format=2]
+[gd_scene load_steps=4 format=2]
[ext_resource path="res://assets/theme.theme" type="Theme" id=1]
-[ext_resource path="res://scenes/levels_redesign.gd" type="Script" id=2]
+[ext_resource path="res://scripts/levels_redesign.gd" type="Script" id=2]
+[ext_resource path="res://scripts/border.gd" type="Script" id=3]
[node name="Viewport" type="MarginContainer"]
anchor_right = 1.0
@@ -18,84 +19,116 @@ margin_right = 1260.0
margin_bottom = 700.0
script = ExtResource( 2 )
-[node name="Names" type="HBoxContainer" parent="Levels"]
+[node name="NamesContainer" type="MarginContainer" parent="Levels"]
margin_right = 1240.0
-margin_bottom = 19.0
+margin_bottom = 59.0
+script = ExtResource( 3 )
-[node name="Current" type="Label" parent="Levels/Names"]
-margin_right = 1240.0
+[node name="Names" type="HBoxContainer" parent="Levels/NamesContainer"]
+margin_left = 20.0
+margin_top = 20.0
+margin_right = 1220.0
+margin_bottom = 39.0
+
+[node name="Previous" type="Label" parent="Levels/NamesContainer/Names"]
+margin_right = 557.0
margin_bottom = 19.0
size_flags_horizontal = 3
-text = "< CURRENT >"
+text = "<"
+align = 2
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Current" type="Label" parent="Levels/NamesContainer/Names"]
+margin_left = 565.0
+margin_right = 635.0
+margin_bottom = 19.0
+custom_colors/font_color = Color( 1, 0.690196, 0, 1 )
+text = "CURRENT"
align = 1
-[node name="HBoxContainer" type="HBoxContainer" parent="Levels"]
-margin_top = 27.0
+[node name="Next" type="Label" parent="Levels/NamesContainer/Names"]
+margin_left = 643.0
+margin_right = 1200.0
+margin_bottom = 19.0
+size_flags_horizontal = 3
+text = ">"
+
+[node name="Info" type="HBoxContainer" parent="Levels"]
+margin_top = 67.0
margin_right = 1240.0
-margin_bottom = 134.0
+margin_bottom = 680.0
+size_flags_vertical = 3
-[node name="MarginContainer" type="MarginContainer" parent="Levels/HBoxContainer"]
+[node name="CodeContainer" type="MarginContainer" parent="Levels/Info"]
margin_right = 616.0
-margin_bottom = 107.0
+margin_bottom = 613.0
size_flags_horizontal = 3
+script = ExtResource( 3 )
-[node name="Label" type="Label" parent="Levels/HBoxContainer/MarginContainer"]
+[node name="Code" type="Label" parent="Levels/Info/CodeContainer"]
margin_left = 20.0
-margin_top = 44.0
+margin_top = 20.0
margin_right = 596.0
-margin_bottom = 63.0
+margin_bottom = 593.0
+size_flags_vertical = 3
-[node name="VBoxContainer" type="VBoxContainer" parent="Levels/HBoxContainer"]
+[node name="VBoxContainer" type="VBoxContainer" parent="Levels/Info"]
margin_left = 624.0
margin_right = 1240.0
-margin_bottom = 107.0
+margin_bottom = 613.0
size_flags_horizontal = 3
-[node name="MarginContainer" type="MarginContainer" parent="Levels/HBoxContainer/VBoxContainer"]
+[node name="Display" type="MarginContainer" parent="Levels/Info/VBoxContainer"]
margin_right = 616.0
-margin_bottom = 40.0
+margin_bottom = 302.0
size_flags_vertical = 3
+script = ExtResource( 3 )
-[node name="HBoxContainer" type="HBoxContainer" parent="Levels/HBoxContainer/VBoxContainer"]
-margin_top = 48.0
+[node name="HBoxContainer" type="HBoxContainer" parent="Levels/Info/VBoxContainer"]
+margin_top = 310.0
margin_right = 616.0
-margin_bottom = 107.0
+margin_bottom = 613.0
size_flags_vertical = 3
-[node name="MarginContainer" type="MarginContainer" parent="Levels/HBoxContainer/VBoxContainer/HBoxContainer"]
+[node name="MarginContainer" type="MarginContainer" parent="Levels/Info/VBoxContainer/HBoxContainer"]
margin_right = 200.0
-margin_bottom = 59.0
+margin_bottom = 303.0
size_flags_horizontal = 3
-[node name="Label" type="Label" parent="Levels/HBoxContainer/VBoxContainer/HBoxContainer/MarginContainer"]
+[node name="Label" type="Label" parent="Levels/Info/VBoxContainer/HBoxContainer/MarginContainer"]
margin_left = 20.0
-margin_top = 20.0
+margin_top = 142.0
margin_right = 180.0
-margin_bottom = 39.0
+margin_bottom = 161.0
size_flags_horizontal = 3
-[node name="MarginContainer2" type="MarginContainer" parent="Levels/HBoxContainer/VBoxContainer/HBoxContainer"]
+[node name="MarginContainer2" type="MarginContainer" parent="Levels/Info/VBoxContainer/HBoxContainer"]
margin_left = 208.0
margin_right = 408.0
-margin_bottom = 59.0
+margin_bottom = 303.0
size_flags_horizontal = 3
-[node name="Label" type="Label" parent="Levels/HBoxContainer/VBoxContainer/HBoxContainer/MarginContainer2"]
+[node name="Label" type="Label" parent="Levels/Info/VBoxContainer/HBoxContainer/MarginContainer2"]
margin_left = 20.0
-margin_top = 20.0
+margin_top = 142.0
margin_right = 180.0
-margin_bottom = 39.0
+margin_bottom = 161.0
size_flags_horizontal = 3
-[node name="MarginContainer3" type="MarginContainer" parent="Levels/HBoxContainer/VBoxContainer/HBoxContainer"]
+[node name="MarginContainer3" type="MarginContainer" parent="Levels/Info/VBoxContainer/HBoxContainer"]
margin_left = 416.0
margin_right = 616.0
-margin_bottom = 59.0
+margin_bottom = 303.0
size_flags_horizontal = 3
-[node name="Label" type="Label" parent="Levels/HBoxContainer/VBoxContainer/HBoxContainer/MarginContainer3"]
+[node name="Label" type="Label" parent="Levels/Info/VBoxContainer/HBoxContainer/MarginContainer3"]
margin_left = 20.0
-margin_top = 20.0
+margin_top = 142.0
margin_right = 180.0
-margin_bottom = 39.0
+margin_bottom = 161.0
size_flags_horizontal = 3
+
+[node name="Timer" type="Timer" parent="Levels"]
+[connection signal="timeout" from="Levels/Timer" to="Levels" method="_on_Timer_timeout"]
diff --git a/scenes/menu.tscn b/scenes/menu.tscn
index 0c692e5..e347d85 100644
--- a/scenes/menu.tscn
+++ b/scenes/menu.tscn
@@ -5,10 +5,8 @@
[ext_resource path="res://scripts/border.gd" type="Script" id=3]
[node name="Viewport" type="MarginContainer"]
-anchor_left = 0.000711028
-anchor_top = 0.00126399
-anchor_right = 1.00071
-anchor_bottom = 1.00126
+anchor_right = 1.0
+anchor_bottom = 1.0
theme = ExtResource( 2 )
custom_constants/margin_right = 40
custom_constants/margin_top = 30
@@ -21,20 +19,20 @@ __meta__ = {
[node name="MainMenu" type="VBoxContainer" parent="."]
margin_left = 40.0
margin_top = 30.0
-margin_right = 1239.0
-margin_bottom = 689.0
+margin_right = 1240.0
+margin_bottom = 690.0
script = ExtResource( 1 )
[node name="Title" type="Label" parent="MainMenu"]
-margin_right = 1199.0
+margin_right = 1200.0
margin_bottom = 19.0
text = "Human Computer Simulator"
uppercase = true
[node name="Display" type="MarginContainer" parent="MainMenu"]
margin_top = 27.0
-margin_right = 1199.0
-margin_bottom = 624.0
+margin_right = 1200.0
+margin_bottom = 625.0
size_flags_vertical = 3
script = ExtResource( 3 )
__meta__ = {
@@ -42,15 +40,15 @@ __meta__ = {
}
[node name="Spacing" type="Control" parent="MainMenu"]
-margin_top = 632.0
-margin_right = 1199.0
-margin_bottom = 632.0
+margin_top = 633.0
+margin_right = 1200.0
+margin_bottom = 633.0
[node name="Buttons" type="HBoxContainer" parent="MainMenu"]
-margin_left = 289.0
-margin_top = 640.0
-margin_right = 909.0
-margin_bottom = 659.0
+margin_left = 290.0
+margin_top = 641.0
+margin_right = 910.0
+margin_bottom = 660.0
size_flags_horizontal = 4
custom_constants/separation = 500
diff --git a/scenes/play.tscn b/scenes/play.tscn
index ba04115..6f13145 100644
--- a/scenes/play.tscn
+++ b/scenes/play.tscn
@@ -8,41 +8,37 @@
anchor_right = 1.0
anchor_bottom = 1.0
theme = ExtResource( 2 )
-custom_constants/margin_right = 30
-custom_constants/margin_top = 30
-custom_constants/margin_left = 30
-custom_constants/margin_bottom = 30
__meta__ = {
"_edit_use_anchors_": false
}
[node name="GameDisplay" type="VBoxContainer" parent="."]
-margin_left = 30.0
-margin_top = 30.0
-margin_right = 1250.0
-margin_bottom = 690.0
+margin_left = 20.0
+margin_top = 20.0
+margin_right = 1260.0
+margin_bottom = 700.0
script = ExtResource( 1 )
[node name="HUDBorder" type="MarginContainer" parent="GameDisplay"]
-margin_right = 1220.0
+margin_right = 1240.0
margin_bottom = 59.0
script = ExtResource( 3 )
[node name="HUD" type="HBoxContainer" parent="GameDisplay/HUDBorder"]
margin_left = 20.0
margin_top = 20.0
-margin_right = 1200.0
+margin_right = 1220.0
margin_bottom = 39.0
[node name="Level" type="Label" parent="GameDisplay/HUDBorder/HUD"]
-margin_right = 586.0
+margin_right = 596.0
margin_bottom = 19.0
size_flags_horizontal = 3
text = "LEVEL"
[node name="Score" type="Label" parent="GameDisplay/HUDBorder/HUD"]
-margin_left = 594.0
-margin_right = 1180.0
+margin_left = 604.0
+margin_right = 1200.0
margin_bottom = 19.0
size_flags_horizontal = 3
text = "0.000"
@@ -50,16 +46,16 @@ align = 2
[node name="Display" type="MarginContainer" parent="GameDisplay"]
margin_top = 67.0
-margin_right = 1220.0
-margin_bottom = 660.0
+margin_right = 1240.0
+margin_bottom = 680.0
size_flags_vertical = 3
script = ExtResource( 3 )
[node name="Label" type="Label" parent="GameDisplay/Display"]
margin_left = 20.0
-margin_top = 287.0
-margin_right = 1200.0
-margin_bottom = 306.0
+margin_top = 297.0
+margin_right = 1220.0
+margin_bottom = 316.0
text = "ready..."
align = 1
diff --git a/scripts/levels_redesign.gd b/scripts/levels_redesign.gd
new file mode 100644
index 0000000..d7c9b90
--- /dev/null
+++ b/scripts/levels_redesign.gd
@@ -0,0 +1,71 @@
+extends VBoxContainer
+
+const LEVELS = [
+ BubbleSort,
+ InsertionSort,
+ SelectionSort,
+ MergeSort,
+ QuickSort,
+ CocktailSort,
+ ShellSort,
+ CombSort,
+ CycleSort,
+ OddEvenSort,
+]
+
+const MIN_WAIT = 1.0 / 32 # Should be greater than maximum frame time
+const MAX_WAIT = 4
+const MIN_SIZE = 8
+const MAX_SIZE = 256
+
+var _index = 0
+var _level: ComparisonSort
+var _size = ArrayModel.DEFAULT_SIZE
+
+func _ready():
+ _level = LEVELS[_index].new(ArrayModel.new(_size))
+ _level.connect("done", self, "_on_ComparisonSort_done")
+ $NamesContainer/Names/Current.text = _level.NAME
+ for child in $Info/VBoxContainer/Display.get_children():
+ child.queue_free()
+ $Info/VBoxContainer/Display.add_child(ArrayView.new(_level))
+ $Timer.start()
+
+func _switch_level(index):
+ if index == -1:
+ _index = LEVELS.size() - 1
+ elif index == LEVELS.size():
+ _index = 0
+ else:
+ _index = index
+ _ready()
+
+func _input(event):
+ if event.is_action_pressed("ui_cancel"):
+ GlobalScene.change_scene("res://scenes/menu.tscn")
+ if event.is_action_pressed("ui_left", true):
+ _switch_level(_index - 1)
+ if event.is_action_pressed("ui_right", true):
+ _switch_level(_index + 1)
+ if event.is_action_pressed("bigger"):
+ _size = min(_size * 2, MAX_SIZE)
+ _ready()
+ if event.is_action_pressed("smaller"):
+ _size = max(_size / 2, MIN_SIZE)
+ _ready()
+ if event.is_action_pressed("faster"):
+ $Timer.wait_time = max($Timer.wait_time / 2, MIN_WAIT)
+ if event.is_action_pressed("slower"):
+ $Timer.wait_time = min($Timer.wait_time * 2, MAX_WAIT)
+ if event.is_action_pressed("ui_accept"):
+ GlobalScene.change_scene("res://scenes/play.tscn",
+ {"level": LEVELS[_index], "size": _size})
+
+func _on_ComparisonSort_done():
+ $Timer.stop()
+ yield(get_tree().create_timer(1), "timeout")
+ if _level.array.is_sorted():
+ _ready()
+
+func _on_Timer_timeout():
+ _level.next(null)
diff --git a/scripts/menu.gd b/scripts/menu.gd
index e533747..c94c120 100644
--- a/scripts/menu.gd
+++ b/scripts/menu.gd
@@ -8,7 +8,7 @@ func _ready():
$Display.add_child(ArrayView.new(_level))
func _on_Start_pressed():
- GlobalScene.change_scene("res://scenes/levels.tscn")
+ GlobalScene.change_scene("res://scenes/levels_redesign.tscn")
func _on_Credits_pressed():
GlobalScene.change_scene("res://scenes/credits.tscn")
diff --git a/scripts/play.gd b/scripts/play.gd
index 9c8d5b1..18634ea 100644
--- a/scripts/play.gd
+++ b/scripts/play.gd
@@ -26,7 +26,7 @@ func get_score():
func _input(event):
if event.is_action_pressed("ui_cancel"):
- _on_Button_pressed("levels")
+ _on_Button_pressed("levels_redesign")
func _on_Level_done(level):
set_process(false)
@@ -39,7 +39,7 @@ func _on_Level_done(level):
separator.text = " / "
var back = Button.new()
back.text = "BACK TO LEVEL SELECT"
- back.connect("pressed", self, "_on_Button_pressed", ["levels"])
+ back.connect("pressed", self, "_on_Button_pressed", ["levels_redesign"])
var score = Label.new()
score.text = "%.3f" % time
score.align = Label.ALIGN_RIGHT
diff --git a/views/array_sound.gd b/views/array_sound.gd
index 6555330..e5e7c85 100644
--- a/views/array_sound.gd
+++ b/views/array_sound.gd
@@ -22,7 +22,7 @@ func _process(delta):
func _init():
add_child(player)
player.stream = AudioStreamGenerator.new()
- player.stream.buffer_length = 0.05
+ player.stream.buffer_length = 0.1
player.stream.mix_rate = SAMPLE_HZ
_playback = player.get_stream_playback()
player.play()
From b74fed6bfca377c92e45dc03b39c113c4eb02eac Mon Sep 17 00:00:00 2001
From: Daniel Ting
Date: Thu, 27 Aug 2020 18:12:53 -0500
Subject: [PATCH 08/29] feat: add placeholder stats and high scores GUI
---
scenes/levels_redesign.tscn | 83 ++++++++++++++++++++++++++-----------
scripts/levels_redesign.gd | 6 +--
scripts/play.gd | 1 -
3 files changed, 61 insertions(+), 29 deletions(-)
diff --git a/scenes/levels_redesign.tscn b/scenes/levels_redesign.tscn
index 149eea9..05c8eef 100644
--- a/scenes/levels_redesign.tscn
+++ b/scenes/levels_redesign.tscn
@@ -55,78 +55,111 @@ margin_bottom = 19.0
size_flags_horizontal = 3
text = ">"
-[node name="Info" type="HBoxContainer" parent="Levels"]
+[node name="Level" type="HBoxContainer" parent="Levels"]
margin_top = 67.0
margin_right = 1240.0
margin_bottom = 680.0
size_flags_vertical = 3
-[node name="CodeContainer" type="MarginContainer" parent="Levels/Info"]
+[node name="CodeContainer" type="MarginContainer" parent="Levels/Level"]
margin_right = 616.0
margin_bottom = 613.0
size_flags_horizontal = 3
script = ExtResource( 3 )
-[node name="Code" type="Label" parent="Levels/Info/CodeContainer"]
+[node name="Code" type="Label" parent="Levels/Level/CodeContainer"]
margin_left = 20.0
margin_top = 20.0
margin_right = 596.0
margin_bottom = 593.0
size_flags_vertical = 3
-[node name="VBoxContainer" type="VBoxContainer" parent="Levels/Info"]
+[node name="Info" type="VBoxContainer" parent="Levels/Level"]
margin_left = 624.0
margin_right = 1240.0
margin_bottom = 613.0
size_flags_horizontal = 3
-[node name="Display" type="MarginContainer" parent="Levels/Info/VBoxContainer"]
+[node name="Display" type="MarginContainer" parent="Levels/Level/Info"]
margin_right = 616.0
margin_bottom = 302.0
size_flags_vertical = 3
script = ExtResource( 3 )
-[node name="HBoxContainer" type="HBoxContainer" parent="Levels/Info/VBoxContainer"]
+[node name="Footer" type="HBoxContainer" parent="Levels/Level/Info"]
margin_top = 310.0
margin_right = 616.0
margin_bottom = 613.0
size_flags_vertical = 3
-[node name="MarginContainer" type="MarginContainer" parent="Levels/Info/VBoxContainer/HBoxContainer"]
-margin_right = 200.0
+[node name="Meta" type="VBoxContainer" parent="Levels/Level/Info/Footer"]
+margin_right = 304.0
margin_bottom = 303.0
size_flags_horizontal = 3
-[node name="Label" type="Label" parent="Levels/Info/VBoxContainer/HBoxContainer/MarginContainer"]
+[node name="StatsContainer" type="MarginContainer" parent="Levels/Level/Info/Footer/Meta"]
+margin_right = 304.0
+margin_bottom = 125.0
+script = ExtResource( 3 )
+
+[node name="Stats" type="Label" parent="Levels/Level/Info/Footer/Meta/StatsContainer"]
margin_left = 20.0
-margin_top = 142.0
-margin_right = 180.0
-margin_bottom = 161.0
+margin_top = 20.0
+margin_right = 284.0
+margin_bottom = 105.0
size_flags_horizontal = 3
-
-[node name="MarginContainer2" type="MarginContainer" parent="Levels/Info/VBoxContainer/HBoxContainer"]
-margin_left = 208.0
-margin_right = 408.0
+text = "Best-case time:
+Average-case time:
+Worst-case time:
+Worst-case space:"
+
+[node name="ScoresContainer" type="MarginContainer" parent="Levels/Level/Info/Footer/Meta"]
+margin_top = 133.0
+margin_right = 304.0
margin_bottom = 303.0
-size_flags_horizontal = 3
+size_flags_vertical = 3
+script = ExtResource( 3 )
-[node name="Label" type="Label" parent="Levels/Info/VBoxContainer/HBoxContainer/MarginContainer2"]
+[node name="Scores" type="HBoxContainer" parent="Levels/Level/Info/Footer/Meta/ScoresContainer"]
margin_left = 20.0
-margin_top = 142.0
-margin_right = 180.0
-margin_bottom = 161.0
+margin_top = 20.0
+margin_right = 284.0
+margin_bottom = 150.0
+
+[node name="Sizes" type="Label" parent="Levels/Level/Info/Footer/Meta/ScoresContainer/Scores"]
+margin_right = 30.0
+margin_bottom = 129.0
+text = "
+8
+16
+32
+64
+128"
+
+[node name="Times" type="Label" parent="Levels/Level/Info/Footer/Meta/ScoresContainer/Scores"]
+margin_left = 38.0
+margin_right = 264.0
+margin_bottom = 129.0
size_flags_horizontal = 3
+text = "HIGH SCORES
+INF
+INF
+INF
+INF
+INF"
+align = 1
-[node name="MarginContainer3" type="MarginContainer" parent="Levels/Info/VBoxContainer/HBoxContainer"]
-margin_left = 416.0
+[node name="ControlsContainer" type="MarginContainer" parent="Levels/Level/Info/Footer"]
+margin_left = 312.0
margin_right = 616.0
margin_bottom = 303.0
size_flags_horizontal = 3
+script = ExtResource( 3 )
-[node name="Label" type="Label" parent="Levels/Info/VBoxContainer/HBoxContainer/MarginContainer3"]
+[node name="Controls" type="Label" parent="Levels/Level/Info/Footer/ControlsContainer"]
margin_left = 20.0
margin_top = 142.0
-margin_right = 180.0
+margin_right = 284.0
margin_bottom = 161.0
size_flags_horizontal = 3
diff --git a/scripts/levels_redesign.gd b/scripts/levels_redesign.gd
index d7c9b90..fe9c9b2 100644
--- a/scripts/levels_redesign.gd
+++ b/scripts/levels_redesign.gd
@@ -16,7 +16,7 @@ const LEVELS = [
const MIN_WAIT = 1.0 / 32 # Should be greater than maximum frame time
const MAX_WAIT = 4
const MIN_SIZE = 8
-const MAX_SIZE = 256
+const MAX_SIZE = 128
var _index = 0
var _level: ComparisonSort
@@ -26,9 +26,9 @@ func _ready():
_level = LEVELS[_index].new(ArrayModel.new(_size))
_level.connect("done", self, "_on_ComparisonSort_done")
$NamesContainer/Names/Current.text = _level.NAME
- for child in $Info/VBoxContainer/Display.get_children():
+ for child in $Level/Info/Display.get_children():
child.queue_free()
- $Info/VBoxContainer/Display.add_child(ArrayView.new(_level))
+ $Level/Info/Display.add_child(ArrayView.new(_level))
$Timer.start()
func _switch_level(index):
diff --git a/scripts/play.gd b/scripts/play.gd
index 18634ea..0ad6cf9 100644
--- a/scripts/play.gd
+++ b/scripts/play.gd
@@ -31,7 +31,6 @@ func _input(event):
func _on_Level_done(level):
set_process(false)
var time = get_score()
- var moves = level.moves
var restart = Button.new()
restart.text = "RESTART LEVEL"
restart.connect("pressed", self, "_on_Button_pressed", ["play"])
From 7671115930cd59cb9c395b53a67cc4b3c0fc4fdf Mon Sep 17 00:00:00 2001
From: Daniel Ting
Date: Fri, 28 Aug 2020 17:09:38 -0500
Subject: [PATCH 09/29] feat: implement score loading
---
scenes/levels_redesign.tscn | 59 ++++++++++++++++++++++++++-----------
scripts/levels_redesign.gd | 10 +++++++
2 files changed, 51 insertions(+), 18 deletions(-)
diff --git a/scenes/levels_redesign.tscn b/scenes/levels_redesign.tscn
index 05c8eef..fc44264 100644
--- a/scenes/levels_redesign.tscn
+++ b/scenes/levels_redesign.tscn
@@ -82,19 +82,19 @@ size_flags_horizontal = 3
[node name="Display" type="MarginContainer" parent="Levels/Level/Info"]
margin_right = 616.0
-margin_bottom = 302.0
+margin_bottom = 276.0
size_flags_vertical = 3
script = ExtResource( 3 )
[node name="Footer" type="HBoxContainer" parent="Levels/Level/Info"]
-margin_top = 310.0
+margin_top = 284.0
margin_right = 616.0
margin_bottom = 613.0
size_flags_vertical = 3
[node name="Meta" type="VBoxContainer" parent="Levels/Level/Info/Footer"]
margin_right = 304.0
-margin_bottom = 303.0
+margin_bottom = 329.0
size_flags_horizontal = 3
[node name="StatsContainer" type="MarginContainer" parent="Levels/Level/Info/Footer/Meta"]
@@ -116,51 +116,74 @@ Worst-case space:"
[node name="ScoresContainer" type="MarginContainer" parent="Levels/Level/Info/Footer/Meta"]
margin_top = 133.0
margin_right = 304.0
-margin_bottom = 303.0
+margin_bottom = 329.0
size_flags_vertical = 3
script = ExtResource( 3 )
-[node name="Scores" type="HBoxContainer" parent="Levels/Level/Info/Footer/Meta/ScoresContainer"]
+[node name="Scores" type="VBoxContainer" parent="Levels/Level/Info/Footer/Meta/ScoresContainer"]
margin_left = 20.0
margin_top = 20.0
margin_right = 284.0
-margin_bottom = 150.0
+margin_bottom = 176.0
-[node name="Sizes" type="Label" parent="Levels/Level/Info/Footer/Meta/ScoresContainer/Scores"]
+[node name="Header" type="HBoxContainer" parent="Levels/Level/Info/Footer/Meta/ScoresContainer/Scores"]
+margin_right = 264.0
+margin_bottom = 41.0
+
+[node name="Size" type="Label" parent="Levels/Level/Info/Footer/Meta/ScoresContainer/Scores/Header"]
+margin_right = 40.0
+margin_bottom = 41.0
+text = "SIZE
+----"
+
+[node name="Time" type="Label" parent="Levels/Level/Info/Footer/Meta/ScoresContainer/Scores/Header"]
+margin_left = 48.0
+margin_right = 264.0
+margin_bottom = 41.0
+size_flags_horizontal = 3
+text = "HIGH SCORE
+----------"
+align = 2
+
+[node name="Data" type="HBoxContainer" parent="Levels/Level/Info/Footer/Meta/ScoresContainer/Scores"]
+margin_top = 49.0
+margin_right = 264.0
+margin_bottom = 156.0
+
+[node name="Sizes" type="Label" parent="Levels/Level/Info/Footer/Meta/ScoresContainer/Scores/Data"]
margin_right = 30.0
-margin_bottom = 129.0
-text = "
-8
+margin_bottom = 107.0
+text = "8
16
32
64
128"
-[node name="Times" type="Label" parent="Levels/Level/Info/Footer/Meta/ScoresContainer/Scores"]
+[node name="Times" type="Label" parent="Levels/Level/Info/Footer/Meta/ScoresContainer/Scores/Data"]
margin_left = 38.0
margin_right = 264.0
-margin_bottom = 129.0
+margin_bottom = 107.0
size_flags_horizontal = 3
-text = "HIGH SCORES
-INF
+text = "INF
INF
INF
INF
INF"
-align = 1
+align = 2
+uppercase = true
[node name="ControlsContainer" type="MarginContainer" parent="Levels/Level/Info/Footer"]
margin_left = 312.0
margin_right = 616.0
-margin_bottom = 303.0
+margin_bottom = 329.0
size_flags_horizontal = 3
script = ExtResource( 3 )
[node name="Controls" type="Label" parent="Levels/Level/Info/Footer/ControlsContainer"]
margin_left = 20.0
-margin_top = 142.0
+margin_top = 155.0
margin_right = 284.0
-margin_bottom = 161.0
+margin_bottom = 174.0
size_flags_horizontal = 3
[node name="Timer" type="Timer" parent="Levels"]
diff --git a/scripts/levels_redesign.gd b/scripts/levels_redesign.gd
index fe9c9b2..674b9b4 100644
--- a/scripts/levels_redesign.gd
+++ b/scripts/levels_redesign.gd
@@ -30,6 +30,16 @@ func _ready():
child.queue_free()
$Level/Info/Display.add_child(ArrayView.new(_level))
$Timer.start()
+ _load_scores(_level)
+
+func _load_scores(level):
+ var data = $Level/Info/Footer/Meta/ScoresContainer/Scores/Data
+ data.get_node("Times").text = ""
+ for i in data.get_node("Sizes").text.split("\n"):
+ var time = str(GlobalScore.get_time(level.NAME, int(i)))
+ data.get_node("Times").text += time
+ if int(i) != MAX_SIZE:
+ data.get_node("Times").text += "\n"
func _switch_level(index):
if index == -1:
From 85dfe671e8a9a5bbe637c825f82c7304d93e71db Mon Sep 17 00:00:00 2001
From: Daniel Ting
Date: Fri, 28 Aug 2020 17:43:04 -0500
Subject: [PATCH 10/29] feat: implement help modal in main menu
---
scenes/menu.tscn | 57 ++++++++++++++++++++++++++++++++++++++++++------
scripts/menu.gd | 12 +++++++++-
2 files changed, 61 insertions(+), 8 deletions(-)
diff --git a/scenes/menu.tscn b/scenes/menu.tscn
index e347d85..651f267 100644
--- a/scenes/menu.tscn
+++ b/scenes/menu.tscn
@@ -5,8 +5,10 @@
[ext_resource path="res://scripts/border.gd" type="Script" id=3]
[node name="Viewport" type="MarginContainer"]
-anchor_right = 1.0
-anchor_bottom = 1.0
+anchor_left = 0.00078125
+anchor_top = 0.00138889
+anchor_right = 1.00078
+anchor_bottom = 1.00139
theme = ExtResource( 2 )
custom_constants/margin_right = 40
custom_constants/margin_top = 30
@@ -39,35 +41,76 @@ __meta__ = {
"_edit_use_anchors_": false
}
+[node name="Instructions" type="MarginContainer" parent="MainMenu/Display"]
+visible = false
+margin_left = 480.0
+margin_top = 201.0
+margin_right = 720.0
+margin_bottom = 397.0
+size_flags_horizontal = 4
+size_flags_vertical = 4
+script = ExtResource( 3 )
+
+[node name="VBoxContainer" type="VBoxContainer" parent="MainMenu/Display/Instructions"]
+margin_left = 20.0
+margin_top = 20.0
+margin_right = 220.0
+margin_bottom = 176.0
+
+[node name="Label" type="Label" parent="MainMenu/Display/Instructions/VBoxContainer"]
+margin_right = 200.0
+margin_bottom = 129.0
+size_flags_horizontal = 4
+text = "W - bigger
+A - slower
+S - smaller
+D - faster
+M - toggle sound
+F - big preview mode"
+
+[node name="Button" type="Button" parent="MainMenu/Display/Instructions/VBoxContainer"]
+margin_top = 137.0
+margin_right = 200.0
+margin_bottom = 156.0
+text = "OK"
+
[node name="Spacing" type="Control" parent="MainMenu"]
margin_top = 633.0
margin_right = 1200.0
margin_bottom = 633.0
[node name="Buttons" type="HBoxContainer" parent="MainMenu"]
-margin_left = 290.0
+margin_left = 20.0
margin_top = 641.0
-margin_right = 910.0
+margin_right = 1180.0
margin_bottom = 660.0
size_flags_horizontal = 4
custom_constants/separation = 500
+[node name="Help" type="Button" parent="MainMenu/Buttons"]
+margin_right = 40.0
+margin_bottom = 19.0
+text = "HELP"
+
[node name="Start" type="Button" parent="MainMenu/Buttons"]
-margin_right = 50.0
+margin_left = 540.0
+margin_right = 590.0
margin_bottom = 19.0
size_flags_horizontal = 4
text = "START"
flat = true
[node name="Credits" type="Button" parent="MainMenu/Buttons"]
-margin_left = 550.0
-margin_right = 620.0
+margin_left = 1090.0
+margin_right = 1160.0
margin_bottom = 19.0
text = "CREDITS"
[node name="Timer" type="Timer" parent="."]
wait_time = 0.25
autostart = true
+[connection signal="pressed" from="MainMenu/Display/Instructions/VBoxContainer/Button" to="MainMenu" method="_on_Button_pressed"]
+[connection signal="pressed" from="MainMenu/Buttons/Help" to="MainMenu" method="_on_Help_pressed"]
[connection signal="pressed" from="MainMenu/Buttons/Start" to="MainMenu" method="_on_Start_pressed"]
[connection signal="pressed" from="MainMenu/Buttons/Credits" to="MainMenu" method="_on_Credits_pressed"]
[connection signal="timeout" from="Timer" to="MainMenu" method="_on_Timer_timeout"]
diff --git a/scripts/menu.gd b/scripts/menu.gd
index c94c120..9095f26 100644
--- a/scripts/menu.gd
+++ b/scripts/menu.gd
@@ -5,7 +5,7 @@ var _level = BogoSort.new(ArrayModel.new())
func _ready():
$Buttons/Start.grab_focus()
randomize()
- $Display.add_child(ArrayView.new(_level))
+ $Display.add_child(ArrayView.new(_level), true)
func _on_Start_pressed():
GlobalScene.change_scene("res://scenes/levels_redesign.tscn")
@@ -13,6 +13,11 @@ func _on_Start_pressed():
func _on_Credits_pressed():
GlobalScene.change_scene("res://scenes/credits.tscn")
+func _on_Help_pressed():
+ $Display/Instructions.show()
+ $Display/HBoxContainer.hide()
+ $Display/Instructions/VBoxContainer/Button.grab_focus()
+
func _on_Timer_timeout():
_level.next(null)
@@ -20,3 +25,8 @@ func _input(event):
# If not in a browser, close the app
if event.is_action_pressed("ui_cancel") and OS.get_name() != "HTML5":
get_tree().quit()
+
+func _on_Button_pressed():
+ $Display/Instructions.hide()
+ $Display/HBoxContainer.show()
+ $Buttons/Start.grab_focus()
From a971c3eda86f95e3be322e2c926ffb3e85e2a128 Mon Sep 17 00:00:00 2001
From: Daniel Ting
Date: Sat, 29 Aug 2020 17:51:22 -0500
Subject: [PATCH 11/29] refactor: simplify interface
Removed the stats box for time and space complexity to allow for future
average-of-five scoring system.
---
scenes/levels_redesign.tscn | 113 ++++++++++++++++--------------------
scenes/menu.tscn | 74 ++++++++++++++---------
scripts/levels_redesign.gd | 6 +-
scripts/menu.gd | 6 +-
4 files changed, 101 insertions(+), 98 deletions(-)
diff --git a/scenes/levels_redesign.tscn b/scenes/levels_redesign.tscn
index fc44264..9364ec4 100644
--- a/scenes/levels_redesign.tscn
+++ b/scenes/levels_redesign.tscn
@@ -61,96 +61,95 @@ margin_right = 1240.0
margin_bottom = 680.0
size_flags_vertical = 3
-[node name="CodeContainer" type="MarginContainer" parent="Levels/Level"]
+[node name="Left" type="MarginContainer" parent="Levels/Level"]
margin_right = 616.0
margin_bottom = 613.0
size_flags_horizontal = 3
script = ExtResource( 3 )
-[node name="Code" type="Label" parent="Levels/Level/CodeContainer"]
+[node name="Code" type="Label" parent="Levels/Level/Left"]
margin_left = 20.0
margin_top = 20.0
margin_right = 596.0
margin_bottom = 593.0
size_flags_vertical = 3
+text = "\"\"\"
+This is a description for the level in the form of psuedocode.
+\"\"\"
-[node name="Info" type="VBoxContainer" parent="Levels/Level"]
+def algorithm(parameter):
+ return result"
+autowrap = true
+
+[node name="Right" type="VBoxContainer" parent="Levels/Level"]
margin_left = 624.0
margin_right = 1240.0
margin_bottom = 613.0
size_flags_horizontal = 3
-[node name="Display" type="MarginContainer" parent="Levels/Level/Info"]
+[node name="Display" type="MarginContainer" parent="Levels/Level/Right"]
margin_right = 616.0
-margin_bottom = 276.0
+margin_bottom = 431.0
size_flags_vertical = 3
script = ExtResource( 3 )
-[node name="Footer" type="HBoxContainer" parent="Levels/Level/Info"]
-margin_top = 284.0
+[node name="Info" type="HBoxContainer" parent="Levels/Level/Right"]
+margin_top = 439.0
margin_right = 616.0
margin_bottom = 613.0
-size_flags_vertical = 3
-[node name="Meta" type="VBoxContainer" parent="Levels/Level/Info/Footer"]
-margin_right = 304.0
-margin_bottom = 329.0
+[node name="ControlsContainer" type="MarginContainer" parent="Levels/Level/Right/Info"]
+margin_right = 405.0
+margin_bottom = 174.0
size_flags_horizontal = 3
-
-[node name="StatsContainer" type="MarginContainer" parent="Levels/Level/Info/Footer/Meta"]
-margin_right = 304.0
-margin_bottom = 125.0
+size_flags_stretch_ratio = 2.0
script = ExtResource( 3 )
-[node name="Stats" type="Label" parent="Levels/Level/Info/Footer/Meta/StatsContainer"]
+[node name="Controls" type="Label" parent="Levels/Level/Right/Info/ControlsContainer"]
margin_left = 20.0
margin_top = 20.0
-margin_right = 284.0
-margin_bottom = 105.0
+margin_right = 385.0
+margin_bottom = 154.0
+size_flags_vertical = 1
+text = "These are the controls for the level."
+autowrap = true
+
+[node name="ScoresContainer" type="MarginContainer" parent="Levels/Level/Right/Info"]
+margin_left = 413.0
+margin_right = 616.0
+margin_bottom = 174.0
size_flags_horizontal = 3
-text = "Best-case time:
-Average-case time:
-Worst-case time:
-Worst-case space:"
-
-[node name="ScoresContainer" type="MarginContainer" parent="Levels/Level/Info/Footer/Meta"]
-margin_top = 133.0
-margin_right = 304.0
-margin_bottom = 329.0
-size_flags_vertical = 3
script = ExtResource( 3 )
-[node name="Scores" type="VBoxContainer" parent="Levels/Level/Info/Footer/Meta/ScoresContainer"]
+[node name="Scores" type="VBoxContainer" parent="Levels/Level/Right/Info/ScoresContainer"]
margin_left = 20.0
margin_top = 20.0
-margin_right = 284.0
-margin_bottom = 176.0
+margin_right = 183.0
+margin_bottom = 154.0
-[node name="Header" type="HBoxContainer" parent="Levels/Level/Info/Footer/Meta/ScoresContainer/Scores"]
-margin_right = 264.0
-margin_bottom = 41.0
+[node name="Header" type="HBoxContainer" parent="Levels/Level/Right/Info/ScoresContainer/Scores"]
+margin_right = 163.0
+margin_bottom = 19.0
-[node name="Size" type="Label" parent="Levels/Level/Info/Footer/Meta/ScoresContainer/Scores/Header"]
+[node name="Size" type="Label" parent="Levels/Level/Right/Info/ScoresContainer/Scores/Header"]
margin_right = 40.0
-margin_bottom = 41.0
-text = "SIZE
-----"
+margin_bottom = 19.0
+text = "SIZE"
-[node name="Time" type="Label" parent="Levels/Level/Info/Footer/Meta/ScoresContainer/Scores/Header"]
+[node name="Time" type="Label" parent="Levels/Level/Right/Info/ScoresContainer/Scores/Header"]
margin_left = 48.0
-margin_right = 264.0
-margin_bottom = 41.0
+margin_right = 163.0
+margin_bottom = 19.0
size_flags_horizontal = 3
-text = "HIGH SCORE
-----------"
+text = "HIGH SCORE"
align = 2
-[node name="Data" type="HBoxContainer" parent="Levels/Level/Info/Footer/Meta/ScoresContainer/Scores"]
-margin_top = 49.0
-margin_right = 264.0
-margin_bottom = 156.0
+[node name="Data" type="HBoxContainer" parent="Levels/Level/Right/Info/ScoresContainer/Scores"]
+margin_top = 27.0
+margin_right = 163.0
+margin_bottom = 134.0
-[node name="Sizes" type="Label" parent="Levels/Level/Info/Footer/Meta/ScoresContainer/Scores/Data"]
+[node name="Sizes" type="Label" parent="Levels/Level/Right/Info/ScoresContainer/Scores/Data"]
margin_right = 30.0
margin_bottom = 107.0
text = "8
@@ -159,9 +158,9 @@ text = "8
64
128"
-[node name="Times" type="Label" parent="Levels/Level/Info/Footer/Meta/ScoresContainer/Scores/Data"]
+[node name="Times" type="Label" parent="Levels/Level/Right/Info/ScoresContainer/Scores/Data"]
margin_left = 38.0
-margin_right = 264.0
+margin_right = 163.0
margin_bottom = 107.0
size_flags_horizontal = 3
text = "INF
@@ -172,19 +171,5 @@ INF"
align = 2
uppercase = true
-[node name="ControlsContainer" type="MarginContainer" parent="Levels/Level/Info/Footer"]
-margin_left = 312.0
-margin_right = 616.0
-margin_bottom = 329.0
-size_flags_horizontal = 3
-script = ExtResource( 3 )
-
-[node name="Controls" type="Label" parent="Levels/Level/Info/Footer/ControlsContainer"]
-margin_left = 20.0
-margin_top = 155.0
-margin_right = 284.0
-margin_bottom = 174.0
-size_flags_horizontal = 3
-
[node name="Timer" type="Timer" parent="Levels"]
[connection signal="timeout" from="Levels/Timer" to="Levels" method="_on_Timer_timeout"]
diff --git a/scenes/menu.tscn b/scenes/menu.tscn
index 651f267..c89f1a5 100644
--- a/scenes/menu.tscn
+++ b/scenes/menu.tscn
@@ -21,19 +21,19 @@ __meta__ = {
[node name="MainMenu" type="VBoxContainer" parent="."]
margin_left = 40.0
margin_top = 30.0
-margin_right = 1240.0
+margin_right = 1239.0
margin_bottom = 690.0
script = ExtResource( 1 )
[node name="Title" type="Label" parent="MainMenu"]
-margin_right = 1200.0
+margin_right = 1199.0
margin_bottom = 19.0
text = "Human Computer Simulator"
uppercase = true
[node name="Display" type="MarginContainer" parent="MainMenu"]
margin_top = 27.0
-margin_right = 1200.0
+margin_right = 1199.0
margin_bottom = 625.0
size_flags_vertical = 3
script = ExtResource( 3 )
@@ -41,48 +41,66 @@ __meta__ = {
"_edit_use_anchors_": false
}
-[node name="Instructions" type="MarginContainer" parent="MainMenu/Display"]
+[node name="InstructionsContainer" type="MarginContainer" parent="MainMenu/Display"]
visible = false
-margin_left = 480.0
-margin_top = 201.0
-margin_right = 720.0
-margin_bottom = 397.0
+margin_left = 504.0
+margin_top = 197.0
+margin_right = 694.0
+margin_bottom = 401.0
size_flags_horizontal = 4
size_flags_vertical = 4
script = ExtResource( 3 )
-[node name="VBoxContainer" type="VBoxContainer" parent="MainMenu/Display/Instructions"]
+[node name="Instructions" type="VBoxContainer" parent="MainMenu/Display/InstructionsContainer"]
margin_left = 20.0
margin_top = 20.0
-margin_right = 220.0
-margin_bottom = 176.0
+margin_right = 170.0
+margin_bottom = 184.0
+custom_constants/separation = 16
-[node name="Label" type="Label" parent="MainMenu/Display/Instructions/VBoxContainer"]
-margin_right = 200.0
+[node name="Controls" type="HBoxContainer" parent="MainMenu/Display/InstructionsContainer/Instructions"]
+margin_right = 150.0
+margin_bottom = 129.0
+custom_constants/separation = 20
+
+[node name="Keys" type="Label" parent="MainMenu/Display/InstructionsContainer/Instructions/Controls"]
+margin_right = 10.0
margin_bottom = 129.0
size_flags_horizontal = 4
-text = "W - bigger
-A - slower
-S - smaller
-D - faster
-M - toggle sound
-F - big preview mode"
-
-[node name="Button" type="Button" parent="MainMenu/Display/Instructions/VBoxContainer"]
-margin_top = 137.0
-margin_right = 200.0
-margin_bottom = 156.0
+text = "W
+A
+S
+D
+M
+F"
+
+[node name="Actions" type="Label" parent="MainMenu/Display/InstructionsContainer/Instructions/Controls"]
+margin_left = 30.0
+margin_right = 150.0
+margin_bottom = 129.0
+text = "bigger
+slower
+smaller
+faster
+sound
+big preview "
+align = 2
+
+[node name="Button" type="Button" parent="MainMenu/Display/InstructionsContainer/Instructions"]
+margin_top = 141.0
+margin_right = 150.0
+margin_bottom = 160.0
text = "OK"
[node name="Spacing" type="Control" parent="MainMenu"]
margin_top = 633.0
-margin_right = 1200.0
+margin_right = 1199.0
margin_bottom = 633.0
[node name="Buttons" type="HBoxContainer" parent="MainMenu"]
-margin_left = 20.0
+margin_left = 19.0
margin_top = 641.0
-margin_right = 1180.0
+margin_right = 1179.0
margin_bottom = 660.0
size_flags_horizontal = 4
custom_constants/separation = 500
@@ -109,7 +127,7 @@ text = "CREDITS"
[node name="Timer" type="Timer" parent="."]
wait_time = 0.25
autostart = true
-[connection signal="pressed" from="MainMenu/Display/Instructions/VBoxContainer/Button" to="MainMenu" method="_on_Button_pressed"]
+[connection signal="pressed" from="MainMenu/Display/InstructionsContainer/Instructions/Button" to="MainMenu" method="_on_Button_pressed"]
[connection signal="pressed" from="MainMenu/Buttons/Help" to="MainMenu" method="_on_Help_pressed"]
[connection signal="pressed" from="MainMenu/Buttons/Start" to="MainMenu" method="_on_Start_pressed"]
[connection signal="pressed" from="MainMenu/Buttons/Credits" to="MainMenu" method="_on_Credits_pressed"]
diff --git a/scripts/levels_redesign.gd b/scripts/levels_redesign.gd
index 674b9b4..736146a 100644
--- a/scripts/levels_redesign.gd
+++ b/scripts/levels_redesign.gd
@@ -26,14 +26,14 @@ func _ready():
_level = LEVELS[_index].new(ArrayModel.new(_size))
_level.connect("done", self, "_on_ComparisonSort_done")
$NamesContainer/Names/Current.text = _level.NAME
- for child in $Level/Info/Display.get_children():
+ for child in $Level/Right/Display.get_children():
child.queue_free()
- $Level/Info/Display.add_child(ArrayView.new(_level))
+ $Level/Right/Display.add_child(ArrayView.new(_level))
$Timer.start()
_load_scores(_level)
func _load_scores(level):
- var data = $Level/Info/Footer/Meta/ScoresContainer/Scores/Data
+ var data = $Level/Right/Info/ScoresContainer/Scores/Data
data.get_node("Times").text = ""
for i in data.get_node("Sizes").text.split("\n"):
var time = str(GlobalScore.get_time(level.NAME, int(i)))
diff --git a/scripts/menu.gd b/scripts/menu.gd
index 9095f26..27d1e85 100644
--- a/scripts/menu.gd
+++ b/scripts/menu.gd
@@ -14,9 +14,9 @@ func _on_Credits_pressed():
GlobalScene.change_scene("res://scenes/credits.tscn")
func _on_Help_pressed():
- $Display/Instructions.show()
+ $Display/InstructionsContainer.show()
$Display/HBoxContainer.hide()
- $Display/Instructions/VBoxContainer/Button.grab_focus()
+ $Display/InstructionsContainer/Instructions/Button.grab_focus()
func _on_Timer_timeout():
_level.next(null)
@@ -27,6 +27,6 @@ func _input(event):
get_tree().quit()
func _on_Button_pressed():
- $Display/Instructions.hide()
+ $Display/InstructionsContainer.hide()
$Display/HBoxContainer.show()
$Buttons/Start.grab_focus()
From 85df253427d28c1f01836776d56659d99c608a01 Mon Sep 17 00:00:00 2001
From: Daniel Ting
Date: Sat, 29 Aug 2020 21:13:41 -0500
Subject: [PATCH 12/29] fix: reimplement remembering last played level
Going back to the level select scene will put you on the sort and size
that you just played.
---
scenes/levels_redesign.tscn | 4 +---
scripts/levels_redesign.gd | 6 ++++--
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/scenes/levels_redesign.tscn b/scenes/levels_redesign.tscn
index 9364ec4..37c7863 100644
--- a/scenes/levels_redesign.tscn
+++ b/scenes/levels_redesign.tscn
@@ -73,9 +73,7 @@ margin_top = 20.0
margin_right = 596.0
margin_bottom = 593.0
size_flags_vertical = 3
-text = "\"\"\"
-This is a description for the level in the form of psuedocode.
-\"\"\"
+text = "This is a description for the level in the form of psuedocode.
def algorithm(parameter):
return result"
diff --git a/scripts/levels_redesign.gd b/scripts/levels_redesign.gd
index 736146a..87cf192 100644
--- a/scripts/levels_redesign.gd
+++ b/scripts/levels_redesign.gd
@@ -18,11 +18,13 @@ const MAX_WAIT = 4
const MIN_SIZE = 8
const MAX_SIZE = 128
-var _index = 0
+var _index = LEVELS.find(GlobalScene.get_param("level"))
var _level: ComparisonSort
-var _size = ArrayModel.DEFAULT_SIZE
+var _size = GlobalScene.get_param("size", ArrayModel.DEFAULT_SIZE)
func _ready():
+ if _index == -1:
+ _index = 0
_level = LEVELS[_index].new(ArrayModel.new(_size))
_level.connect("done", self, "_on_ComparisonSort_done")
$NamesContainer/Names/Current.text = _level.NAME
From 17facae4f7604c06d6d0095476fc63264772454a Mon Sep 17 00:00:00 2001
From: Daniel Ting
Date: Sat, 29 Aug 2020 21:22:10 -0500
Subject: [PATCH 13/29] feat: add space and escape keys to help menu
---
scenes/menu.tscn | 40 ++++++++++++++++++++++------------------
1 file changed, 22 insertions(+), 18 deletions(-)
diff --git a/scenes/menu.tscn b/scenes/menu.tscn
index c89f1a5..43ae0a8 100644
--- a/scenes/menu.tscn
+++ b/scenes/menu.tscn
@@ -43,10 +43,10 @@ __meta__ = {
[node name="InstructionsContainer" type="MarginContainer" parent="MainMenu/Display"]
visible = false
-margin_left = 504.0
-margin_top = 197.0
-margin_right = 694.0
-margin_bottom = 401.0
+margin_left = 489.0
+margin_top = 175.0
+margin_right = 709.0
+margin_bottom = 423.0
size_flags_horizontal = 4
size_flags_vertical = 4
script = ExtResource( 3 )
@@ -54,42 +54,46 @@ script = ExtResource( 3 )
[node name="Instructions" type="VBoxContainer" parent="MainMenu/Display/InstructionsContainer"]
margin_left = 20.0
margin_top = 20.0
-margin_right = 170.0
-margin_bottom = 184.0
+margin_right = 200.0
+margin_bottom = 228.0
custom_constants/separation = 16
[node name="Controls" type="HBoxContainer" parent="MainMenu/Display/InstructionsContainer/Instructions"]
-margin_right = 150.0
-margin_bottom = 129.0
+margin_right = 180.0
+margin_bottom = 173.0
custom_constants/separation = 20
[node name="Keys" type="Label" parent="MainMenu/Display/InstructionsContainer/Instructions/Controls"]
-margin_right = 10.0
-margin_bottom = 129.0
+margin_right = 50.0
+margin_bottom = 173.0
size_flags_horizontal = 4
text = "W
A
S
D
M
-F"
+F
+space
+esc"
[node name="Actions" type="Label" parent="MainMenu/Display/InstructionsContainer/Instructions/Controls"]
-margin_left = 30.0
-margin_right = 150.0
-margin_bottom = 129.0
+margin_left = 70.0
+margin_right = 180.0
+margin_bottom = 173.0
text = "bigger
slower
smaller
faster
sound
-big preview "
+big preview
+confirm
+back"
align = 2
[node name="Button" type="Button" parent="MainMenu/Display/InstructionsContainer/Instructions"]
-margin_top = 141.0
-margin_right = 150.0
-margin_bottom = 160.0
+margin_top = 189.0
+margin_right = 180.0
+margin_bottom = 208.0
text = "OK"
[node name="Spacing" type="Control" parent="MainMenu"]
From 49db1dcc85484316ae3d1ab06fb90746c2d8c09a Mon Sep 17 00:00:00 2001
From: Daniel Ting
Date: Sat, 29 Aug 2020 21:26:00 -0500
Subject: [PATCH 14/29] feat: reimplement description and controls display
---
levels/bogo_sort.gd | 2 ++
levels/bubble_sort.gd | 2 ++
levels/cocktail_sort.gd | 2 ++
levels/comb_sort.gd | 2 ++
levels/comparison_sort.gd | 6 +++---
levels/cycle_sort.gd | 2 ++
levels/insertion_sort.gd | 2 ++
levels/merge_sort.gd | 2 ++
levels/odd_even_sort.gd | 2 ++
levels/quick_sort.gd | 2 ++
levels/selection_sort.gd | 2 ++
levels/shell_sort.gd | 2 ++
scripts/levels_redesign.gd | 2 ++
13 files changed, 27 insertions(+), 3 deletions(-)
diff --git a/levels/bogo_sort.gd b/levels/bogo_sort.gd
index 666bf12..2971418 100644
--- a/levels/bogo_sort.gd
+++ b/levels/bogo_sort.gd
@@ -1,8 +1,10 @@
"""
BOGOSORT
+
Generates random permutations until the array is sorted.
+
Keep on hitting RIGHT ARROW to CONTINUE and hope for the best!
"""
diff --git a/levels/bubble_sort.gd b/levels/bubble_sort.gd
index 8b8e913..6fdc66f 100644
--- a/levels/bubble_sort.gd
+++ b/levels/bubble_sort.gd
@@ -1,12 +1,14 @@
"""
BUBBLE SORT
+
Bubble sort iterates through the array and looks at each pair of
elements, swapping them if they are out of order. When it has gone
through the entire array without swapping a single pair, it has
finished. Though simple to understand, bubble sort is hopelessly
inefficient on all but the smallest of arrays.
+
If the two highlighted elements are out of order, hit LEFT ARROW to swap
them. Otherwise, hit RIGHT ARROW to continue.
"""
diff --git a/levels/cocktail_sort.gd b/levels/cocktail_sort.gd
index 1c3af51..1a243e3 100644
--- a/levels/cocktail_sort.gd
+++ b/levels/cocktail_sort.gd
@@ -1,9 +1,11 @@
"""
COCKTAIL SORT
+
Cocktail shaker sort is a variation of bubble sort that
alternates going backwards and forwards.
+
If the two highlighted elements are out of order, hit LEFT ARROW to swap
them. Otherwise, hit RIGHT ARROW to continue.
"""
diff --git a/levels/comb_sort.gd b/levels/comb_sort.gd
index 57c3015..693005d 100644
--- a/levels/comb_sort.gd
+++ b/levels/comb_sort.gd
@@ -1,8 +1,10 @@
"""
COMB SORT
+
Comb sort is a variant of bubble sort that operates on gapped arrays.
+
If the two highlighted elements are out of order, hit LEFT ARROW to swap
them. Otherwise, hit RIGHT ARROW to continue.
"""
diff --git a/levels/comparison_sort.gd b/levels/comparison_sort.gd
index 65400ca..30932e9 100644
--- a/levels/comparison_sort.gd
+++ b/levels/comparison_sort.gd
@@ -11,9 +11,9 @@ const EFFECTS = {
}
const DISABLE_TIME = 1.0
-var NAME = _get_header().split(" ")[0]
-var DESCRIPTION = _get_header().split(" ")[1]
-var CONTROLS = _get_header().split(" ")[2]
+var NAME = _get_header().split(" ")[0]
+var DESCRIPTION = _get_header().split(" ")[1]
+var CONTROLS = _get_header().split(" ")[2]
var array: ArrayModel
diff --git a/levels/cycle_sort.gd b/levels/cycle_sort.gd
index 97c9f17..243e352 100644
--- a/levels/cycle_sort.gd
+++ b/levels/cycle_sort.gd
@@ -1,10 +1,12 @@
"""
CYCLE SORT
+
Cycle sort repeatedly counts the number of elements less than the first
and swaps it with that index until the smallest element is reached. Then
it does this process starting at the next out-of-place element.
+
If the highlighted element is less than the pointer, hit LEFT ARROW.
Otherwise, hit RIGHT ARROW.
"""
diff --git a/levels/insertion_sort.gd b/levels/insertion_sort.gd
index c7d9712..39680de 100644
--- a/levels/insertion_sort.gd
+++ b/levels/insertion_sort.gd
@@ -1,12 +1,14 @@
"""
INSERTION SORT
+
Insertion sort goes through the array and inserts each
element into its correct position. It is most similar to how most people
would sort a deck of cards. It is also slow on large arrays but it is
one of the faster quadratic algorithms. It is often used to sort smaller
subarrays in hybrid sorting algorithms.
+
Hit LEFT ARROW to swap the two highlighted elements as long as they are
out of order. When this is no longer the case, hit RIGHT ARROW to
advance.
diff --git a/levels/merge_sort.gd b/levels/merge_sort.gd
index 1de05d7..f85de24 100644
--- a/levels/merge_sort.gd
+++ b/levels/merge_sort.gd
@@ -1,12 +1,14 @@
"""
MERGE SORT
+
Merge sort is an efficient sorting algorithm that splits the array into
single-element chunks. Then it merges each pair of chunks until only one
sorted chunk is left by repeatedly choosing the smaller element at the
head of each chunk and moving the head back. However, it needs an entire
array's worth of auxiliary memory.
+
Press the ARROW KEY corresponding to the side that the smaller
highlighted element is on. If you've reached the end of one side, press
the other side's ARROW KEY.
diff --git a/levels/odd_even_sort.gd b/levels/odd_even_sort.gd
index 66ff88e..3f7d955 100644
--- a/levels/odd_even_sort.gd
+++ b/levels/odd_even_sort.gd
@@ -1,9 +1,11 @@
"""
ODD-EVEN SORT
+
Odd-even sort is a variant of bubble sort that alternates on elements at
odd and even indices.
+
If the two highlighted elements are out of order, hit LEFT ARROW to swap
them. Otherwise, hit RIGHT ARROW to continue.
"""
diff --git a/levels/quick_sort.gd b/levels/quick_sort.gd
index f9c621f..b94e4c4 100644
--- a/levels/quick_sort.gd
+++ b/levels/quick_sort.gd
@@ -1,6 +1,7 @@
"""
QUICKSORT
+
Quicksort designates the last element as the pivot and puts everything
less than the pivot before it and everything greater after it. This
partitioning is done by iterating through the array while keeping track
@@ -9,6 +10,7 @@ less than the pivot is encountered, it is swapped with the pointed
element and the pointer moves forward. At the end, the pointer and pivot
are swapped, and the process is repeated on the left and right halves.
+
If the highlighted element is less than the pivot or the pivot has been
reached, press LEFT ARROW to swap it with the pointer. Otherwise, press
RIGHT ARROW to move on.
diff --git a/levels/selection_sort.gd b/levels/selection_sort.gd
index b569693..343d469 100644
--- a/levels/selection_sort.gd
+++ b/levels/selection_sort.gd
@@ -1,12 +1,14 @@
"""
SELECTION SORT
+
Selection sort incrementally builds a sorted array by repeatedly looking
for the smallest element and swapping it onto the end of the sorted
portion of the array, which initially starts with size zero but grows
after each round. It is faster than an unoptimized bubble sort but
slower than insertion sort.
+
Keep on hitting RIGHT ARROW until you encounter an element that is
smaller than the left highlighted element, then hit LEFT ARROW and
repeat.
diff --git a/levels/shell_sort.gd b/levels/shell_sort.gd
index b05aa6b..67dc116 100644
--- a/levels/shell_sort.gd
+++ b/levels/shell_sort.gd
@@ -1,9 +1,11 @@
"""
SHELL SORT
+
Shell sort is a variation of insertion sort that sorts arrays separated
by gaps.
+
Hit LEFT ARROW to swap the two highlighted elements as long as they are
out of order. When this is no longer the case, hit RIGHT ARROW to
"""
diff --git a/scripts/levels_redesign.gd b/scripts/levels_redesign.gd
index 87cf192..c2123cc 100644
--- a/scripts/levels_redesign.gd
+++ b/scripts/levels_redesign.gd
@@ -32,6 +32,8 @@ func _ready():
child.queue_free()
$Level/Right/Display.add_child(ArrayView.new(_level))
$Timer.start()
+ $Level/Left/Code.text = _level.DESCRIPTION
+ $Level/Right/Info/ControlsContainer/Controls.text = _level.CONTROLS
_load_scores(_level)
func _load_scores(level):
From e5b0a7d13407a58e729e24ab98be8b09d11a39ae Mon Sep 17 00:00:00 2001
From: Daniel Ting
Date: Sat, 29 Aug 2020 22:58:32 -0500
Subject: [PATCH 15/29] refactor: rename sound_toggled, remove big preview
Due to extreme difficulties of implementation, big preview mode has been
canceled.
---
project.godot | 2 +-
scenes/menu.tscn | 31 ++++++++++++++-----------------
views/array_sound.gd | 2 +-
3 files changed, 16 insertions(+), 19 deletions(-)
diff --git a/project.godot b/project.godot
index bcca320..5341c6d 100644
--- a/project.godot
+++ b/project.godot
@@ -192,7 +192,7 @@ smaller={
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":83,"unicode":0,"echo":false,"script":null)
]
}
-sound_toggle={
+sound={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":77,"unicode":0,"echo":false,"script":null)
]
diff --git a/scenes/menu.tscn b/scenes/menu.tscn
index 43ae0a8..14c9c5e 100644
--- a/scenes/menu.tscn
+++ b/scenes/menu.tscn
@@ -42,11 +42,10 @@ __meta__ = {
}
[node name="InstructionsContainer" type="MarginContainer" parent="MainMenu/Display"]
-visible = false
-margin_left = 489.0
-margin_top = 175.0
-margin_right = 709.0
-margin_bottom = 423.0
+margin_left = 509.0
+margin_top = 186.0
+margin_right = 689.0
+margin_bottom = 412.0
size_flags_horizontal = 4
size_flags_vertical = 4
script = ExtResource( 3 )
@@ -54,46 +53,44 @@ script = ExtResource( 3 )
[node name="Instructions" type="VBoxContainer" parent="MainMenu/Display/InstructionsContainer"]
margin_left = 20.0
margin_top = 20.0
-margin_right = 200.0
-margin_bottom = 228.0
+margin_right = 160.0
+margin_bottom = 206.0
custom_constants/separation = 16
[node name="Controls" type="HBoxContainer" parent="MainMenu/Display/InstructionsContainer/Instructions"]
-margin_right = 180.0
-margin_bottom = 173.0
+margin_right = 140.0
+margin_bottom = 151.0
custom_constants/separation = 20
[node name="Keys" type="Label" parent="MainMenu/Display/InstructionsContainer/Instructions/Controls"]
margin_right = 50.0
-margin_bottom = 173.0
+margin_bottom = 151.0
size_flags_horizontal = 4
text = "W
A
S
D
M
-F
space
esc"
[node name="Actions" type="Label" parent="MainMenu/Display/InstructionsContainer/Instructions/Controls"]
margin_left = 70.0
-margin_right = 180.0
-margin_bottom = 173.0
+margin_right = 140.0
+margin_bottom = 151.0
text = "bigger
slower
smaller
faster
sound
-big preview
confirm
back"
align = 2
[node name="Button" type="Button" parent="MainMenu/Display/InstructionsContainer/Instructions"]
-margin_top = 189.0
-margin_right = 180.0
-margin_bottom = 208.0
+margin_top = 167.0
+margin_right = 140.0
+margin_bottom = 186.0
text = "OK"
[node name="Spacing" type="Control" parent="MainMenu"]
diff --git a/views/array_sound.gd b/views/array_sound.gd
index e5e7c85..01da862 100644
--- a/views/array_sound.gd
+++ b/views/array_sound.gd
@@ -32,7 +32,7 @@ func triangle(x):
return 2 / PI * asin(sin(PI * x))
func _input(event):
- if event.is_action_pressed("sound_toggle"):
+ if event.is_action_pressed("sound"):
# Prevent event from propagating to ComparisonSort trigger
get_tree().set_input_as_handled()
var bus = AudioServer.get_bus_index("Master")
From 3df0d5ffc70d70412ba6408118bda4a22a588d7e Mon Sep 17 00:00:00 2001
From: Daniel Ting
Date: Sun, 30 Aug 2020 16:11:29 -0500
Subject: [PATCH 16/29] chore: replace levels with levels_redesign
---
scenes/levels.tscn | 205 ++++++++++++++++++++++++------------
scenes/levels_redesign.tscn | 173 ------------------------------
scenes/menu.tscn | 9 +-
scripts/levels.gd | 125 ++++++++++------------
scripts/levels_redesign.gd | 85 ---------------
scripts/menu.gd | 2 +-
scripts/play.gd | 4 +-
7 files changed, 201 insertions(+), 402 deletions(-)
delete mode 100644 scenes/levels_redesign.tscn
delete mode 100644 scripts/levels_redesign.gd
diff --git a/scenes/levels.tscn b/scenes/levels.tscn
index b35a711..2b43e83 100644
--- a/scenes/levels.tscn
+++ b/scenes/levels.tscn
@@ -1,106 +1,173 @@
[gd_scene load_steps=4 format=2]
-[ext_resource path="res://scripts/levels.gd" type="Script" id=1]
-[ext_resource path="res://assets/theme.theme" type="Theme" id=2]
+[ext_resource path="res://assets/theme.theme" type="Theme" id=1]
+[ext_resource path="res://scripts/levels.gd" type="Script" id=2]
[ext_resource path="res://scripts/border.gd" type="Script" id=3]
[node name="Viewport" type="MarginContainer"]
anchor_right = 1.0
anchor_bottom = 1.0
-theme = ExtResource( 2 )
-custom_constants/margin_right = 30
-custom_constants/margin_top = 30
-custom_constants/margin_left = 30
-custom_constants/margin_bottom = 30
+theme = ExtResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}
-[node name="LevelSelect" type="HBoxContainer" parent="."]
-margin_left = 30.0
-margin_top = 30.0
-margin_right = 1250.0
-margin_bottom = 690.0
-script = ExtResource( 1 )
+[node name="Levels" type="VBoxContainer" parent="."]
+margin_left = 20.0
+margin_top = 20.0
+margin_right = 1260.0
+margin_bottom = 700.0
+script = ExtResource( 2 )
-[node name="LevelsBorder" type="MarginContainer" parent="LevelSelect"]
-margin_right = 303.0
-margin_bottom = 660.0
-size_flags_horizontal = 3
+[node name="NamesContainer" type="MarginContainer" parent="Levels"]
+margin_right = 1240.0
+margin_bottom = 59.0
script = ExtResource( 3 )
-[node name="Levels" type="VBoxContainer" parent="LevelSelect/LevelsBorder"]
+[node name="Names" type="HBoxContainer" parent="Levels/NamesContainer"]
margin_left = 20.0
margin_top = 20.0
-margin_right = 283.0
-margin_bottom = 640.0
+margin_right = 1220.0
+margin_bottom = 39.0
-[node name="LevelsContainer" type="HBoxContainer" parent="LevelSelect/LevelsBorder/Levels"]
-margin_right = 263.0
+[node name="Previous" type="Label" parent="Levels/NamesContainer/Names"]
+margin_right = 557.0
+margin_bottom = 19.0
+size_flags_horizontal = 3
+text = "<"
+align = 2
+__meta__ = {
+"_edit_use_anchors_": false
+}
-[node name="Buttons" type="VBoxContainer" parent="LevelSelect/LevelsBorder/Levels/LevelsContainer"]
-margin_right = 255.0
+[node name="Current" type="Label" parent="Levels/NamesContainer/Names"]
+margin_left = 565.0
+margin_right = 635.0
+margin_bottom = 19.0
+custom_colors/font_color = Color( 1, 0.690196, 0, 1 )
+text = "CURRENT"
+align = 1
+
+[node name="Next" type="Label" parent="Levels/NamesContainer/Names"]
+margin_left = 643.0
+margin_right = 1200.0
+margin_bottom = 19.0
size_flags_horizontal = 3
+text = ">"
-[node name="Scores" type="VBoxContainer" parent="LevelSelect/LevelsBorder/Levels/LevelsContainer"]
-margin_left = 263.0
-margin_right = 263.0
+[node name="Level" type="HBoxContainer" parent="Levels"]
+margin_top = 67.0
+margin_right = 1240.0
+margin_bottom = 680.0
+size_flags_vertical = 3
+
+[node name="Left" type="MarginContainer" parent="Levels/Level"]
+margin_right = 616.0
+margin_bottom = 613.0
+size_flags_horizontal = 3
+script = ExtResource( 3 )
-[node name="Label" type="Label" parent="LevelSelect/LevelsBorder"]
+[node name="Code" type="Label" parent="Levels/Level/Left"]
margin_left = 20.0
-margin_top = 555.0
-margin_right = 283.0
-margin_bottom = 640.0
-size_flags_vertical = 8
-text = "Use the WASD keys to adjust the size and speed of the simulation, and M to toggle sound."
+margin_top = 20.0
+margin_right = 596.0
+margin_bottom = 593.0
+size_flags_vertical = 3
+text = "This is a description for the level in the form of psuedocode.
+
+def algorithm(parameter):
+ return result"
autowrap = true
-[node name="Preview" type="VBoxContainer" parent="LevelSelect"]
-margin_left = 311.0
-margin_right = 1220.0
-margin_bottom = 660.0
+[node name="Right" type="VBoxContainer" parent="Levels/Level"]
+margin_left = 624.0
+margin_right = 1240.0
+margin_bottom = 613.0
size_flags_horizontal = 3
-size_flags_stretch_ratio = 3.0
-[node name="Display" type="MarginContainer" parent="LevelSelect/Preview"]
-margin_right = 909.0
-margin_bottom = 434.0
+[node name="Display" type="MarginContainer" parent="Levels/Level/Right"]
+margin_right = 616.0
+margin_bottom = 431.0
size_flags_vertical = 3
-size_flags_stretch_ratio = 2.0
script = ExtResource( 3 )
-[node name="InfoBorder" type="MarginContainer" parent="LevelSelect/Preview"]
-margin_top = 442.0
-margin_right = 909.0
-margin_bottom = 660.0
-size_flags_vertical = 3
+[node name="Info" type="HBoxContainer" parent="Levels/Level/Right"]
+margin_top = 439.0
+margin_right = 616.0
+margin_bottom = 613.0
+
+[node name="ControlsContainer" type="MarginContainer" parent="Levels/Level/Right/Info"]
+margin_right = 405.0
+margin_bottom = 174.0
+size_flags_horizontal = 3
+size_flags_stretch_ratio = 2.0
script = ExtResource( 3 )
-[node name="Info" type="HBoxContainer" parent="LevelSelect/Preview/InfoBorder"]
+[node name="Controls" type="Label" parent="Levels/Level/Right/Info/ControlsContainer"]
margin_left = 20.0
margin_top = 20.0
-margin_right = 889.0
-margin_bottom = 198.0
-custom_constants/separation = 50
-
-[node name="Description" type="Label" parent="LevelSelect/Preview/InfoBorder/Info"]
-margin_right = 546.0
-margin_bottom = 178.0
-size_flags_horizontal = 3
-size_flags_vertical = 3
-size_flags_stretch_ratio = 2.0
-text = "This is a short description of the algorithm. It should tell how it works in a simple yet complete way and explain its relevance in computer science. It should be accessible to the layman while not being oversimplifying."
+margin_right = 385.0
+margin_bottom = 154.0
+size_flags_vertical = 1
+text = "These are the controls for the level."
autowrap = true
-[node name="Controls" type="Label" parent="LevelSelect/Preview/InfoBorder/Info"]
-margin_left = 596.0
-margin_right = 869.0
-margin_bottom = 178.0
+[node name="ScoresContainer" type="MarginContainer" parent="Levels/Level/Right/Info"]
+margin_left = 413.0
+margin_right = 616.0
+margin_bottom = 174.0
size_flags_horizontal = 3
-size_flags_vertical = 3
-text = "These are the controls for the level. They should be tailored to each level for maximum efficiency and simplicity."
-autowrap = true
+script = ExtResource( 3 )
-[node name="Timer" type="Timer" parent="LevelSelect"]
-autostart = true
-[connection signal="timeout" from="LevelSelect/Timer" to="LevelSelect" method="_on_Timer_timeout"]
+[node name="Scores" type="VBoxContainer" parent="Levels/Level/Right/Info/ScoresContainer"]
+margin_left = 20.0
+margin_top = 20.0
+margin_right = 183.0
+margin_bottom = 154.0
+
+[node name="Header" type="HBoxContainer" parent="Levels/Level/Right/Info/ScoresContainer/Scores"]
+margin_right = 163.0
+margin_bottom = 19.0
+
+[node name="Size" type="Label" parent="Levels/Level/Right/Info/ScoresContainer/Scores/Header"]
+margin_right = 40.0
+margin_bottom = 19.0
+text = "SIZE"
+
+[node name="Time" type="Label" parent="Levels/Level/Right/Info/ScoresContainer/Scores/Header"]
+margin_left = 48.0
+margin_right = 163.0
+margin_bottom = 19.0
+size_flags_horizontal = 3
+text = "HIGH SCORE"
+align = 2
+
+[node name="Data" type="HBoxContainer" parent="Levels/Level/Right/Info/ScoresContainer/Scores"]
+margin_top = 27.0
+margin_right = 163.0
+margin_bottom = 134.0
+
+[node name="Sizes" type="Label" parent="Levels/Level/Right/Info/ScoresContainer/Scores/Data"]
+margin_right = 30.0
+margin_bottom = 107.0
+text = "8
+16
+32
+64
+128"
+
+[node name="Times" type="Label" parent="Levels/Level/Right/Info/ScoresContainer/Scores/Data"]
+margin_left = 38.0
+margin_right = 163.0
+margin_bottom = 107.0
+size_flags_horizontal = 3
+text = "INF
+INF
+INF
+INF
+INF"
+align = 2
+uppercase = true
+
+[node name="Timer" type="Timer" parent="Levels"]
+[connection signal="timeout" from="Levels/Timer" to="Levels" method="_on_Timer_timeout"]
diff --git a/scenes/levels_redesign.tscn b/scenes/levels_redesign.tscn
deleted file mode 100644
index 37c7863..0000000
--- a/scenes/levels_redesign.tscn
+++ /dev/null
@@ -1,173 +0,0 @@
-[gd_scene load_steps=4 format=2]
-
-[ext_resource path="res://assets/theme.theme" type="Theme" id=1]
-[ext_resource path="res://scripts/levels_redesign.gd" type="Script" id=2]
-[ext_resource path="res://scripts/border.gd" type="Script" id=3]
-
-[node name="Viewport" type="MarginContainer"]
-anchor_right = 1.0
-anchor_bottom = 1.0
-theme = ExtResource( 1 )
-__meta__ = {
-"_edit_use_anchors_": false
-}
-
-[node name="Levels" type="VBoxContainer" parent="."]
-margin_left = 20.0
-margin_top = 20.0
-margin_right = 1260.0
-margin_bottom = 700.0
-script = ExtResource( 2 )
-
-[node name="NamesContainer" type="MarginContainer" parent="Levels"]
-margin_right = 1240.0
-margin_bottom = 59.0
-script = ExtResource( 3 )
-
-[node name="Names" type="HBoxContainer" parent="Levels/NamesContainer"]
-margin_left = 20.0
-margin_top = 20.0
-margin_right = 1220.0
-margin_bottom = 39.0
-
-[node name="Previous" type="Label" parent="Levels/NamesContainer/Names"]
-margin_right = 557.0
-margin_bottom = 19.0
-size_flags_horizontal = 3
-text = "<"
-align = 2
-__meta__ = {
-"_edit_use_anchors_": false
-}
-
-[node name="Current" type="Label" parent="Levels/NamesContainer/Names"]
-margin_left = 565.0
-margin_right = 635.0
-margin_bottom = 19.0
-custom_colors/font_color = Color( 1, 0.690196, 0, 1 )
-text = "CURRENT"
-align = 1
-
-[node name="Next" type="Label" parent="Levels/NamesContainer/Names"]
-margin_left = 643.0
-margin_right = 1200.0
-margin_bottom = 19.0
-size_flags_horizontal = 3
-text = ">"
-
-[node name="Level" type="HBoxContainer" parent="Levels"]
-margin_top = 67.0
-margin_right = 1240.0
-margin_bottom = 680.0
-size_flags_vertical = 3
-
-[node name="Left" type="MarginContainer" parent="Levels/Level"]
-margin_right = 616.0
-margin_bottom = 613.0
-size_flags_horizontal = 3
-script = ExtResource( 3 )
-
-[node name="Code" type="Label" parent="Levels/Level/Left"]
-margin_left = 20.0
-margin_top = 20.0
-margin_right = 596.0
-margin_bottom = 593.0
-size_flags_vertical = 3
-text = "This is a description for the level in the form of psuedocode.
-
-def algorithm(parameter):
- return result"
-autowrap = true
-
-[node name="Right" type="VBoxContainer" parent="Levels/Level"]
-margin_left = 624.0
-margin_right = 1240.0
-margin_bottom = 613.0
-size_flags_horizontal = 3
-
-[node name="Display" type="MarginContainer" parent="Levels/Level/Right"]
-margin_right = 616.0
-margin_bottom = 431.0
-size_flags_vertical = 3
-script = ExtResource( 3 )
-
-[node name="Info" type="HBoxContainer" parent="Levels/Level/Right"]
-margin_top = 439.0
-margin_right = 616.0
-margin_bottom = 613.0
-
-[node name="ControlsContainer" type="MarginContainer" parent="Levels/Level/Right/Info"]
-margin_right = 405.0
-margin_bottom = 174.0
-size_flags_horizontal = 3
-size_flags_stretch_ratio = 2.0
-script = ExtResource( 3 )
-
-[node name="Controls" type="Label" parent="Levels/Level/Right/Info/ControlsContainer"]
-margin_left = 20.0
-margin_top = 20.0
-margin_right = 385.0
-margin_bottom = 154.0
-size_flags_vertical = 1
-text = "These are the controls for the level."
-autowrap = true
-
-[node name="ScoresContainer" type="MarginContainer" parent="Levels/Level/Right/Info"]
-margin_left = 413.0
-margin_right = 616.0
-margin_bottom = 174.0
-size_flags_horizontal = 3
-script = ExtResource( 3 )
-
-[node name="Scores" type="VBoxContainer" parent="Levels/Level/Right/Info/ScoresContainer"]
-margin_left = 20.0
-margin_top = 20.0
-margin_right = 183.0
-margin_bottom = 154.0
-
-[node name="Header" type="HBoxContainer" parent="Levels/Level/Right/Info/ScoresContainer/Scores"]
-margin_right = 163.0
-margin_bottom = 19.0
-
-[node name="Size" type="Label" parent="Levels/Level/Right/Info/ScoresContainer/Scores/Header"]
-margin_right = 40.0
-margin_bottom = 19.0
-text = "SIZE"
-
-[node name="Time" type="Label" parent="Levels/Level/Right/Info/ScoresContainer/Scores/Header"]
-margin_left = 48.0
-margin_right = 163.0
-margin_bottom = 19.0
-size_flags_horizontal = 3
-text = "HIGH SCORE"
-align = 2
-
-[node name="Data" type="HBoxContainer" parent="Levels/Level/Right/Info/ScoresContainer/Scores"]
-margin_top = 27.0
-margin_right = 163.0
-margin_bottom = 134.0
-
-[node name="Sizes" type="Label" parent="Levels/Level/Right/Info/ScoresContainer/Scores/Data"]
-margin_right = 30.0
-margin_bottom = 107.0
-text = "8
-16
-32
-64
-128"
-
-[node name="Times" type="Label" parent="Levels/Level/Right/Info/ScoresContainer/Scores/Data"]
-margin_left = 38.0
-margin_right = 163.0
-margin_bottom = 107.0
-size_flags_horizontal = 3
-text = "INF
-INF
-INF
-INF
-INF"
-align = 2
-uppercase = true
-
-[node name="Timer" type="Timer" parent="Levels"]
-[connection signal="timeout" from="Levels/Timer" to="Levels" method="_on_Timer_timeout"]
diff --git a/scenes/menu.tscn b/scenes/menu.tscn
index 14c9c5e..171b6ea 100644
--- a/scenes/menu.tscn
+++ b/scenes/menu.tscn
@@ -42,6 +42,7 @@ __meta__ = {
}
[node name="InstructionsContainer" type="MarginContainer" parent="MainMenu/Display"]
+visible = false
margin_left = 509.0
margin_top = 186.0
margin_right = 689.0
@@ -71,8 +72,8 @@ A
S
D
M
-space
-esc"
+esc
+space"
[node name="Actions" type="Label" parent="MainMenu/Display/InstructionsContainer/Instructions/Controls"]
margin_left = 70.0
@@ -83,8 +84,8 @@ slower
smaller
faster
sound
-confirm
-back"
+back
+confirm"
align = 2
[node name="Button" type="Button" parent="MainMenu/Display/InstructionsContainer/Instructions"]
diff --git a/scripts/levels.gd b/scripts/levels.gd
index 7f2f374..c2123cc 100644
--- a/scripts/levels.gd
+++ b/scripts/levels.gd
@@ -1,4 +1,4 @@
-extends HBoxContainer
+extends VBoxContainer
const LEVELS = [
BubbleSort,
@@ -12,85 +12,74 @@ const LEVELS = [
CycleSort,
OddEvenSort,
]
+
const MIN_WAIT = 1.0 / 32 # Should be greater than maximum frame time
const MAX_WAIT = 4
const MIN_SIZE = 8
-const MAX_SIZE = 256
+const MAX_SIZE = 128
-var _level = GlobalScene.get_param("level", LEVELS[0]).new(ArrayModel.new(
- GlobalScene.get_param("size", ArrayModel.DEFAULT_SIZE)))
+var _index = LEVELS.find(GlobalScene.get_param("level"))
+var _level: ComparisonSort
+var _size = GlobalScene.get_param("size", ArrayModel.DEFAULT_SIZE)
func _ready():
- var buttons = $LevelsBorder/Levels/LevelsContainer/Buttons
- var scores = $LevelsBorder/Levels/LevelsContainer/Scores
- for level in LEVELS:
- var button = Button.new()
- button.text = level.new(ArrayModel.new()).NAME
- button.align = Button.ALIGN_LEFT
- button.connect("focus_entered", self, "_on_Button_focus_entered")
- button.connect("pressed", self, "_on_Button_pressed", [level])
- buttons.add_child(button)
- var score = Label.new()
- score.align = Label.ALIGN_RIGHT
- score.size_flags_horizontal = Control.SIZE_EXPAND_FILL
- scores.add_child(score)
- # Autofocus last played level
- for button in buttons.get_children():
- if button.text == _level.NAME:
- button.grab_focus()
- var top_button = buttons.get_children()[0]
- var bottom_button = buttons.get_children()[-1]
- # Allow looping from ends of list
- top_button.focus_neighbour_top = bottom_button.get_path()
- bottom_button.focus_neighbour_bottom = top_button.get_path()
-
-func _on_Button_focus_entered(size=_level.array.size):
- # Update high scores
- var container = $LevelsBorder/Levels/LevelsContainer
- for i in range(LEVELS.size()):
- var name = container.get_node("Buttons").get_child(i).text
- var time = GlobalScore.get_time(name, size)
- container.get_node("Scores").get_child(i).text = "INF" if time == INF else "%.3f" % time
- # Pause a bit to show completely sorted array
- if _level.array.is_sorted():
- # Prevent race condition caused by keyboard input during pause
- set_process_input(false)
- $Timer.stop()
- yield(get_tree().create_timer(1), "timeout")
- if not _level.array.is_sorted():
- return
- $Timer.start()
- set_process_input(true)
- _level = _get_level(get_focus_owner().text).new(ArrayModel.new(size))
- $Preview/InfoBorder/Info/Description.text = _level.DESCRIPTION
- $Preview/InfoBorder/Info/Controls.text = _level.CONTROLS
- # Start over when simulation is finished
- _level.connect("done", self, "_on_Button_focus_entered")
- # Replace old display with new
- for child in $Preview/Display.get_children():
+ if _index == -1:
+ _index = 0
+ _level = LEVELS[_index].new(ArrayModel.new(_size))
+ _level.connect("done", self, "_on_ComparisonSort_done")
+ $NamesContainer/Names/Current.text = _level.NAME
+ for child in $Level/Right/Display.get_children():
child.queue_free()
- $Preview/Display.add_child(ArrayView.new(_level))
+ $Level/Right/Display.add_child(ArrayView.new(_level))
+ $Timer.start()
+ $Level/Left/Code.text = _level.DESCRIPTION
+ $Level/Right/Info/ControlsContainer/Controls.text = _level.CONTROLS
+ _load_scores(_level)
+
+func _load_scores(level):
+ var data = $Level/Right/Info/ScoresContainer/Scores/Data
+ data.get_node("Times").text = ""
+ for i in data.get_node("Sizes").text.split("\n"):
+ var time = str(GlobalScore.get_time(level.NAME, int(i)))
+ data.get_node("Times").text += time
+ if int(i) != MAX_SIZE:
+ data.get_node("Times").text += "\n"
+
+func _switch_level(index):
+ if index == -1:
+ _index = LEVELS.size() - 1
+ elif index == LEVELS.size():
+ _index = 0
+ else:
+ _index = index
+ _ready()
func _input(event):
if event.is_action_pressed("ui_cancel"):
GlobalScene.change_scene("res://scenes/menu.tscn")
- elif event.is_action_pressed("faster"):
- $Timer.wait_time = max(MIN_WAIT, $Timer.wait_time / 2)
- elif event.is_action_pressed("slower"):
- $Timer.wait_time = min(MAX_WAIT, $Timer.wait_time * 2)
- elif event.is_action_pressed("bigger"):
- _on_Button_focus_entered(min(MAX_SIZE, _level.array.size * 2))
- elif event.is_action_pressed("smaller"):
- _on_Button_focus_entered(max(MIN_SIZE, _level.array.size / 2))
-
-func _on_Button_pressed(level):
- GlobalScene.change_scene("res://scenes/play.tscn",
- {"level": level, "size": _level.array.size})
+ if event.is_action_pressed("ui_left", true):
+ _switch_level(_index - 1)
+ if event.is_action_pressed("ui_right", true):
+ _switch_level(_index + 1)
+ if event.is_action_pressed("bigger"):
+ _size = min(_size * 2, MAX_SIZE)
+ _ready()
+ if event.is_action_pressed("smaller"):
+ _size = max(_size / 2, MIN_SIZE)
+ _ready()
+ if event.is_action_pressed("faster"):
+ $Timer.wait_time = max($Timer.wait_time / 2, MIN_WAIT)
+ if event.is_action_pressed("slower"):
+ $Timer.wait_time = min($Timer.wait_time * 2, MAX_WAIT)
+ if event.is_action_pressed("ui_accept"):
+ GlobalScene.change_scene("res://scenes/play.tscn",
+ {"level": LEVELS[_index], "size": _size})
-func _get_level(name):
- for level in LEVELS:
- if level.new(ArrayModel.new()).NAME == name:
- return level
+func _on_ComparisonSort_done():
+ $Timer.stop()
+ yield(get_tree().create_timer(1), "timeout")
+ if _level.array.is_sorted():
+ _ready()
func _on_Timer_timeout():
_level.next(null)
diff --git a/scripts/levels_redesign.gd b/scripts/levels_redesign.gd
deleted file mode 100644
index c2123cc..0000000
--- a/scripts/levels_redesign.gd
+++ /dev/null
@@ -1,85 +0,0 @@
-extends VBoxContainer
-
-const LEVELS = [
- BubbleSort,
- InsertionSort,
- SelectionSort,
- MergeSort,
- QuickSort,
- CocktailSort,
- ShellSort,
- CombSort,
- CycleSort,
- OddEvenSort,
-]
-
-const MIN_WAIT = 1.0 / 32 # Should be greater than maximum frame time
-const MAX_WAIT = 4
-const MIN_SIZE = 8
-const MAX_SIZE = 128
-
-var _index = LEVELS.find(GlobalScene.get_param("level"))
-var _level: ComparisonSort
-var _size = GlobalScene.get_param("size", ArrayModel.DEFAULT_SIZE)
-
-func _ready():
- if _index == -1:
- _index = 0
- _level = LEVELS[_index].new(ArrayModel.new(_size))
- _level.connect("done", self, "_on_ComparisonSort_done")
- $NamesContainer/Names/Current.text = _level.NAME
- for child in $Level/Right/Display.get_children():
- child.queue_free()
- $Level/Right/Display.add_child(ArrayView.new(_level))
- $Timer.start()
- $Level/Left/Code.text = _level.DESCRIPTION
- $Level/Right/Info/ControlsContainer/Controls.text = _level.CONTROLS
- _load_scores(_level)
-
-func _load_scores(level):
- var data = $Level/Right/Info/ScoresContainer/Scores/Data
- data.get_node("Times").text = ""
- for i in data.get_node("Sizes").text.split("\n"):
- var time = str(GlobalScore.get_time(level.NAME, int(i)))
- data.get_node("Times").text += time
- if int(i) != MAX_SIZE:
- data.get_node("Times").text += "\n"
-
-func _switch_level(index):
- if index == -1:
- _index = LEVELS.size() - 1
- elif index == LEVELS.size():
- _index = 0
- else:
- _index = index
- _ready()
-
-func _input(event):
- if event.is_action_pressed("ui_cancel"):
- GlobalScene.change_scene("res://scenes/menu.tscn")
- if event.is_action_pressed("ui_left", true):
- _switch_level(_index - 1)
- if event.is_action_pressed("ui_right", true):
- _switch_level(_index + 1)
- if event.is_action_pressed("bigger"):
- _size = min(_size * 2, MAX_SIZE)
- _ready()
- if event.is_action_pressed("smaller"):
- _size = max(_size / 2, MIN_SIZE)
- _ready()
- if event.is_action_pressed("faster"):
- $Timer.wait_time = max($Timer.wait_time / 2, MIN_WAIT)
- if event.is_action_pressed("slower"):
- $Timer.wait_time = min($Timer.wait_time * 2, MAX_WAIT)
- if event.is_action_pressed("ui_accept"):
- GlobalScene.change_scene("res://scenes/play.tscn",
- {"level": LEVELS[_index], "size": _size})
-
-func _on_ComparisonSort_done():
- $Timer.stop()
- yield(get_tree().create_timer(1), "timeout")
- if _level.array.is_sorted():
- _ready()
-
-func _on_Timer_timeout():
- _level.next(null)
diff --git a/scripts/menu.gd b/scripts/menu.gd
index 27d1e85..27f1358 100644
--- a/scripts/menu.gd
+++ b/scripts/menu.gd
@@ -8,7 +8,7 @@ func _ready():
$Display.add_child(ArrayView.new(_level), true)
func _on_Start_pressed():
- GlobalScene.change_scene("res://scenes/levels_redesign.tscn")
+ GlobalScene.change_scene("res://scenes/levels.tscn")
func _on_Credits_pressed():
GlobalScene.change_scene("res://scenes/credits.tscn")
diff --git a/scripts/play.gd b/scripts/play.gd
index 0ad6cf9..e174631 100644
--- a/scripts/play.gd
+++ b/scripts/play.gd
@@ -26,7 +26,7 @@ func get_score():
func _input(event):
if event.is_action_pressed("ui_cancel"):
- _on_Button_pressed("levels_redesign")
+ _on_Button_pressed("levels")
func _on_Level_done(level):
set_process(false)
@@ -38,7 +38,7 @@ func _on_Level_done(level):
separator.text = " / "
var back = Button.new()
back.text = "BACK TO LEVEL SELECT"
- back.connect("pressed", self, "_on_Button_pressed", ["levels_redesign"])
+ back.connect("pressed", self, "_on_Button_pressed", ["levels"])
var score = Label.new()
score.text = "%.3f" % time
score.align = Label.ALIGN_RIGHT
From 08640a1612c00ebe4eab51b19e769b855f4e60ea Mon Sep 17 00:00:00 2001
From: Daniel Ting
Date: Tue, 1 Sep 2020 13:53:05 -0500
Subject: [PATCH 17/29] feat: add custom data types
---
models/array_model.gd | 62 ++++++++++++++++++++++++++++++++++++++-----
project.godot | 7 ++++-
scenes/levels.tscn | 29 +++++++++++++++++---
scenes/menu.tscn | 32 +++++++++++-----------
scripts/levels.gd | 60 ++++++++++++++++++++++++++++++++---------
views/array_sound.gd | 4 +--
6 files changed, 154 insertions(+), 40 deletions(-)
diff --git a/models/array_model.gd b/models/array_model.gd
index 5cd361f..c9a5d38 100644
--- a/models/array_model.gd
+++ b/models/array_model.gd
@@ -6,17 +6,54 @@ signal swapped(i, j)
signal sorted(i, j)
const DEFAULT_SIZE = 16
+enum DATA_TYPES {
+ RANDOM_UNIQUE,
+ TRUE_RANDOM,
+ REVERSED,
+ FEW_UNIQUE,
+ ALL_THE_SAME,
+ NEARLY_SORTED,
+ ALREADY_SORTED,
+}
var _array = []
var size = 0 setget , get_size
var biggest = null
-func _init(size=DEFAULT_SIZE):
+func _init(size=DEFAULT_SIZE, data_type=DATA_TYPES.RANDOM_UNIQUE):
"""Randomize the array."""
- for i in range(1, size + 1):
- _array.append(i)
- _array.shuffle()
- biggest = _array.max()
+ match data_type:
+ DATA_TYPES.RANDOM_UNIQUE:
+ for i in range(1, size + 1):
+ _array.append(i)
+ _array.shuffle()
+ DATA_TYPES.TRUE_RANDOM:
+ for i in range(size):
+ _array.append(randi() % size + 1)
+ DATA_TYPES.REVERSED:
+ for i in range(size, 0, -1):
+ _array.append(i)
+ DATA_TYPES.FEW_UNIQUE:
+ var values = []
+ for i in range(sqrt(size)):
+ values.append(randi() % size + 1)
+ for i in range(size):
+ _array.append(values[randi() % values.size()])
+ DATA_TYPES.ALL_THE_SAME:
+ for i in range(size):
+ _array.append(1)
+ DATA_TYPES.NEARLY_SORTED:
+ # We interpret nearly sorted as every element being K or
+ # less places away from its sorted position, where K is a
+ # small number relative to the size of the array.
+ for i in range(1, size + 1):
+ _array.append(i)
+ _array.shuffle()
+ _nearly_sort(0, size - 1, ceil(sqrt(size)))
+ DATA_TYPES.ALREADY_SORTED:
+ for i in range(1, size + 1):
+ _array.append(i)
+ biggest = _array.max() if data_type != DATA_TYPES.ALL_THE_SAME else 0
func at(i):
"""Retrieve the value of the element at index i."""
@@ -24,7 +61,7 @@ func at(i):
func frac(i):
"""Get the quotient of the element at index i and the biggest."""
- return float(_array[i]) / biggest
+ return float(_array[i]) / biggest if biggest != 0 else 0.5
func is_sorted():
"""Check if the array is in monotonically increasing order."""
@@ -52,3 +89,16 @@ func sort(i, j):
func get_size():
return _array.size()
+
+func _nearly_sort(start, end, k):
+ # If false, then no element in this subarray is more than K places
+ # away from its sorted position, and we can exit
+ if end - start > k:
+ var pointer = start
+ for i in range(start, end):
+ if _array[i] < _array[end]:
+ swap(i, pointer)
+ pointer += 1
+ swap(pointer, end)
+ _nearly_sort(start, pointer - 1, k)
+ _nearly_sort(pointer + 1, end, k)
diff --git a/project.godot b/project.godot
index 5341c6d..ddcc9b9 100644
--- a/project.godot
+++ b/project.godot
@@ -192,11 +192,16 @@ smaller={
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":83,"unicode":0,"echo":false,"script":null)
]
}
-sound={
+toggle_sound={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":77,"unicode":0,"echo":false,"script":null)
]
}
+change_data={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":67,"unicode":0,"echo":false,"script":null)
+ ]
+}
[rendering]
diff --git a/scenes/levels.tscn b/scenes/levels.tscn
index 2b43e83..0e6bd40 100644
--- a/scenes/levels.tscn
+++ b/scenes/levels.tscn
@@ -40,13 +40,11 @@ __meta__ = {
"_edit_use_anchors_": false
}
-[node name="Current" type="Label" parent="Levels/NamesContainer/Names"]
+[node name="Current" type="Button" parent="Levels/NamesContainer/Names"]
margin_left = 565.0
margin_right = 635.0
margin_bottom = 19.0
-custom_colors/font_color = Color( 1, 0.690196, 0, 1 )
text = "CURRENT"
-align = 1
[node name="Next" type="Label" parent="Levels/NamesContainer/Names"]
margin_left = 643.0
@@ -91,6 +89,30 @@ margin_bottom = 431.0
size_flags_vertical = 3
script = ExtResource( 3 )
+[node name="TypesContainer" type="MarginContainer" parent="Levels/Level/Right/Display"]
+visible = false
+margin_left = 218.0
+margin_top = 105.0
+margin_right = 398.0
+margin_bottom = 326.0
+size_flags_horizontal = 4
+size_flags_vertical = 4
+script = ExtResource( 3 )
+
+[node name="Types" type="VBoxContainer" parent="Levels/Level/Right/Display/TypesContainer"]
+margin_left = 20.0
+margin_top = 20.0
+margin_right = 160.0
+margin_bottom = 201.0
+size_flags_horizontal = 4
+size_flags_vertical = 4
+
+[node name="ArrayView" type="HBoxContainer" parent="Levels/Level/Right/Display"]
+margin_left = 20.0
+margin_top = 20.0
+margin_right = 596.0
+margin_bottom = 411.0
+
[node name="Info" type="HBoxContainer" parent="Levels/Level/Right"]
margin_top = 439.0
margin_right = 616.0
@@ -170,4 +192,5 @@ align = 2
uppercase = true
[node name="Timer" type="Timer" parent="Levels"]
+[connection signal="pressed" from="Levels/NamesContainer/Names/Current" to="Levels" method="_on_Current_pressed"]
[connection signal="timeout" from="Levels/Timer" to="Levels" method="_on_Timer_timeout"]
diff --git a/scenes/menu.tscn b/scenes/menu.tscn
index 171b6ea..afb99d6 100644
--- a/scenes/menu.tscn
+++ b/scenes/menu.tscn
@@ -43,10 +43,10 @@ __meta__ = {
[node name="InstructionsContainer" type="MarginContainer" parent="MainMenu/Display"]
visible = false
-margin_left = 509.0
-margin_top = 186.0
-margin_right = 689.0
-margin_bottom = 412.0
+margin_left = 484.0
+margin_top = 175.0
+margin_right = 714.0
+margin_bottom = 423.0
size_flags_horizontal = 4
size_flags_vertical = 4
script = ExtResource( 3 )
@@ -54,44 +54,46 @@ script = ExtResource( 3 )
[node name="Instructions" type="VBoxContainer" parent="MainMenu/Display/InstructionsContainer"]
margin_left = 20.0
margin_top = 20.0
-margin_right = 160.0
-margin_bottom = 206.0
+margin_right = 210.0
+margin_bottom = 228.0
custom_constants/separation = 16
[node name="Controls" type="HBoxContainer" parent="MainMenu/Display/InstructionsContainer/Instructions"]
-margin_right = 140.0
-margin_bottom = 151.0
+margin_right = 190.0
+margin_bottom = 173.0
custom_constants/separation = 20
[node name="Keys" type="Label" parent="MainMenu/Display/InstructionsContainer/Instructions/Controls"]
margin_right = 50.0
-margin_bottom = 151.0
+margin_bottom = 173.0
size_flags_horizontal = 4
text = "W
A
S
D
M
+C
esc
space"
[node name="Actions" type="Label" parent="MainMenu/Display/InstructionsContainer/Instructions/Controls"]
margin_left = 70.0
-margin_right = 140.0
-margin_bottom = 151.0
+margin_right = 190.0
+margin_bottom = 173.0
text = "bigger
slower
smaller
faster
-sound
+toggle sound
+change data
back
confirm"
align = 2
[node name="Button" type="Button" parent="MainMenu/Display/InstructionsContainer/Instructions"]
-margin_top = 167.0
-margin_right = 140.0
-margin_bottom = 186.0
+margin_top = 189.0
+margin_right = 190.0
+margin_bottom = 208.0
text = "OK"
[node name="Spacing" type="Control" parent="MainMenu"]
diff --git a/scripts/levels.gd b/scripts/levels.gd
index c2123cc..10de6bd 100644
--- a/scripts/levels.gd
+++ b/scripts/levels.gd
@@ -21,20 +21,39 @@ const MAX_SIZE = 128
var _index = LEVELS.find(GlobalScene.get_param("level"))
var _level: ComparisonSort
var _size = GlobalScene.get_param("size", ArrayModel.DEFAULT_SIZE)
+var _data_type = ArrayModel.DATA_TYPES.RANDOM_UNIQUE
func _ready():
+ var types = $Level/Right/Display/TypesContainer/Types
+ for type in ArrayModel.DATA_TYPES:
+ var button = Button.new()
+ button.text = type.replace("_", " ")
+ button.connect("pressed", self, "_on_Button_pressed", [type])
+ types.add_child(button)
+ var top = types.get_child(0)
+ var bottom = types.get_child(types.get_child_count() - 1)
+ top.focus_neighbour_top = bottom.get_path()
+ bottom.focus_neighbour_bottom = top.get_path()
+ _reload()
+
+func _reload():
+ $NamesContainer/Names/Current.grab_focus()
if _index == -1:
_index = 0
- _level = LEVELS[_index].new(ArrayModel.new(_size))
+ _level = LEVELS[_index].new(ArrayModel.new(_size, _data_type))
_level.connect("done", self, "_on_ComparisonSort_done")
+ _load_scores(_level)
+ # Load level information
$NamesContainer/Names/Current.text = _level.NAME
- for child in $Level/Right/Display.get_children():
- child.queue_free()
- $Level/Right/Display.add_child(ArrayView.new(_level))
- $Timer.start()
$Level/Left/Code.text = _level.DESCRIPTION
$Level/Right/Info/ControlsContainer/Controls.text = _level.CONTROLS
- _load_scores(_level)
+ var view = $Level/Right/Display/ArrayView
+ $Level/Right/Display.remove_child(view)
+ view.queue_free()
+ view = ArrayView.new(_level)
+ view.name = "ArrayView"
+ $Level/Right/Display.add_child(view)
+ $Timer.start()
func _load_scores(level):
var data = $Level/Right/Info/ScoresContainer/Scores/Data
@@ -52,7 +71,7 @@ func _switch_level(index):
_index = 0
else:
_index = index
- _ready()
+ _reload()
func _input(event):
if event.is_action_pressed("ui_cancel"):
@@ -63,23 +82,38 @@ func _input(event):
_switch_level(_index + 1)
if event.is_action_pressed("bigger"):
_size = min(_size * 2, MAX_SIZE)
- _ready()
+ _reload()
if event.is_action_pressed("smaller"):
_size = max(_size / 2, MIN_SIZE)
- _ready()
+ _reload()
if event.is_action_pressed("faster"):
$Timer.wait_time = max($Timer.wait_time / 2, MIN_WAIT)
if event.is_action_pressed("slower"):
$Timer.wait_time = min($Timer.wait_time * 2, MAX_WAIT)
- if event.is_action_pressed("ui_accept"):
- GlobalScene.change_scene("res://scenes/play.tscn",
- {"level": LEVELS[_index], "size": _size})
+ if event.is_action_pressed("change_data"):
+ AudioServer.set_bus_mute(AudioServer.get_bus_index("Master"), true)
+ $Level/Right/Display/ArrayView.hide()
+ $Level/Right/Display/TypesContainer.show()
+ $Timer.stop()
+ $Level/Right/Display/TypesContainer/Types.get_child(0).grab_focus()
func _on_ComparisonSort_done():
$Timer.stop()
yield(get_tree().create_timer(1), "timeout")
if _level.array.is_sorted():
- _ready()
+ _reload()
func _on_Timer_timeout():
_level.next(null)
+
+func _on_Current_pressed():
+ GlobalScene.change_scene("res://scenes/play.tscn",
+ {"level": LEVELS[_index], "size": _size})
+
+func _on_Button_pressed(data_type):
+ AudioServer.set_bus_mute(AudioServer.get_bus_index("Master"), false)
+ $Level/Right/Display/TypesContainer.hide()
+ $Level/Right/Display/ArrayView.show()
+ $Timer.start()
+ _data_type = ArrayModel.DATA_TYPES[data_type]
+ _reload()
diff --git a/views/array_sound.gd b/views/array_sound.gd
index 01da862..9700188 100644
--- a/views/array_sound.gd
+++ b/views/array_sound.gd
@@ -3,7 +3,7 @@ extends Node
const SAMPLE_HZ = 44100
const MIN_HZ = 110
-const MAX_HZ = 880
+const MAX_HZ = 440
var frac: float
var player = AudioStreamPlayer.new()
@@ -32,7 +32,7 @@ func triangle(x):
return 2 / PI * asin(sin(PI * x))
func _input(event):
- if event.is_action_pressed("sound"):
+ if event.is_action_pressed("toggle_sound"):
# Prevent event from propagating to ComparisonSort trigger
get_tree().set_input_as_handled()
var bus = AudioServer.get_bus_index("Master")
From 7c03e774be8322059e77580ff2145cebc3171afa Mon Sep 17 00:00:00 2001
From: Daniel Ting
Date: Tue, 1 Sep 2020 15:35:09 -0500
Subject: [PATCH 18/29] feat: remember data type between plays
---
scenes/levels.tscn | 12 ++++++------
scripts/levels.gd | 5 +++--
scripts/play.gd | 25 ++++++++++++++-----------
3 files changed, 23 insertions(+), 19 deletions(-)
diff --git a/scenes/levels.tscn b/scenes/levels.tscn
index 0e6bd40..d95581f 100644
--- a/scenes/levels.tscn
+++ b/scenes/levels.tscn
@@ -91,10 +91,10 @@ script = ExtResource( 3 )
[node name="TypesContainer" type="MarginContainer" parent="Levels/Level/Right/Display"]
visible = false
-margin_left = 218.0
-margin_top = 105.0
-margin_right = 398.0
-margin_bottom = 326.0
+margin_left = 288.0
+margin_top = 195.0
+margin_right = 328.0
+margin_bottom = 235.0
size_flags_horizontal = 4
size_flags_vertical = 4
script = ExtResource( 3 )
@@ -102,8 +102,8 @@ script = ExtResource( 3 )
[node name="Types" type="VBoxContainer" parent="Levels/Level/Right/Display/TypesContainer"]
margin_left = 20.0
margin_top = 20.0
-margin_right = 160.0
-margin_bottom = 201.0
+margin_right = 20.0
+margin_bottom = 20.0
size_flags_horizontal = 4
size_flags_vertical = 4
diff --git a/scripts/levels.gd b/scripts/levels.gd
index 10de6bd..f240b42 100644
--- a/scripts/levels.gd
+++ b/scripts/levels.gd
@@ -21,7 +21,8 @@ const MAX_SIZE = 128
var _index = LEVELS.find(GlobalScene.get_param("level"))
var _level: ComparisonSort
var _size = GlobalScene.get_param("size", ArrayModel.DEFAULT_SIZE)
-var _data_type = ArrayModel.DATA_TYPES.RANDOM_UNIQUE
+var _data_type = GlobalScene.get_param(
+ "data_type", ArrayModel.DATA_TYPES.RANDOM_UNIQUE)
func _ready():
var types = $Level/Right/Display/TypesContainer/Types
@@ -108,7 +109,7 @@ func _on_Timer_timeout():
func _on_Current_pressed():
GlobalScene.change_scene("res://scenes/play.tscn",
- {"level": LEVELS[_index], "size": _size})
+ {"level": LEVELS[_index], "size": _size, "data_type": _data_type})
func _on_Button_pressed(data_type):
AudioServer.set_bus_mute(AudioServer.get_bus_index("Master"), false)
diff --git a/scripts/play.gd b/scripts/play.gd
index e174631..7097a9a 100644
--- a/scripts/play.gd
+++ b/scripts/play.gd
@@ -1,12 +1,13 @@
extends VBoxContainer
var _start_time = -1
-var _level = GlobalScene.get_param(
- "level", preload("res://scripts/levels.gd").LEVELS[0])
+var _level = GlobalScene.get_param("level").new(ArrayModel.new(
+ GlobalScene.get_param("size"), GlobalScene.get_param("data_type")))
func _ready():
set_process(false)
- $HUDBorder/HUD/Level.text = _level.new(ArrayModel.new()).NAME
+ $HUDBorder/HUD/Level.text = _level.NAME
+ _level.connect("done", self, "_on_Level_done")
func _process(delta):
$HUDBorder/HUD/Score.text = "%.3f" % get_score()
@@ -15,11 +16,8 @@ func _on_Timer_timeout():
set_process(true)
_start_time = OS.get_ticks_msec()
$Display/Label.queue_free() # Delete ready text
- var level = _level.new(ArrayModel.new(
- GlobalScene.get_param("size", ArrayModel.DEFAULT_SIZE)))
- level.connect("done", self, "_on_Level_done", [level])
- $Display.add_child(ArrayView.new(level))
- level.set_process_input(true)
+ $Display.add_child(ArrayView.new(_level))
+ _level.set_process_input(true)
func get_score():
return stepify((OS.get_ticks_msec() - _start_time) / 1000.0, 0.001)
@@ -28,7 +26,7 @@ func _input(event):
if event.is_action_pressed("ui_cancel"):
_on_Button_pressed("levels")
-func _on_Level_done(level):
+func _on_Level_done():
set_process(false)
var time = get_score()
var restart = Button.new()
@@ -41,6 +39,10 @@ func _on_Level_done(level):
back.connect("pressed", self, "_on_Button_pressed", ["levels"])
var score = Label.new()
score.text = "%.3f" % time
+ if GlobalScene.get_param("data_type") != ArrayModel.DATA_TYPES.RANDOM_UNIQUE:
+ score.text += " (only random unique data counts toward a high score!)"
+ else:
+ GlobalScore.save_score(_level.NAME, _level.array.size, time)
score.align = Label.ALIGN_RIGHT
score.size_flags_horizontal = Control.SIZE_EXPAND_FILL
$HUDBorder/HUD/Level.queue_free()
@@ -50,8 +52,9 @@ func _on_Level_done(level):
$HUDBorder/HUD.add_child(back)
$HUDBorder/HUD.add_child(score)
restart.grab_focus()
- GlobalScore.save_score(level.NAME, level.array.size, time)
func _on_Button_pressed(scene):
GlobalScene.change_scene("res://scenes/" + scene + ".tscn",
- {"level": _level, "size": GlobalScene.get_param("size")})
+ {"level": GlobalScene.get_param("level"),
+ "size": GlobalScene.get_param("size"),
+ "data_type": GlobalScene.get_param("data_type")})
From d17132d27325ba9c0eb70c3a530b306c74e5d25a Mon Sep 17 00:00:00 2001
From: Daniel Ting
Date: Tue, 1 Sep 2020 16:14:40 -0500
Subject: [PATCH 19/29] feat: reduce number of sizes
To keep things simple, there is now only a "small" size of 8, "medium"
size of 32, and "big" size of 128.
---
models/array_model.gd | 2 +-
scenes/levels.tscn | 28 ++++++++++++----------------
scripts/levels.gd | 10 +++++-----
3 files changed, 18 insertions(+), 22 deletions(-)
diff --git a/models/array_model.gd b/models/array_model.gd
index c9a5d38..dba8c3c 100644
--- a/models/array_model.gd
+++ b/models/array_model.gd
@@ -5,7 +5,7 @@ signal removed(i)
signal swapped(i, j)
signal sorted(i, j)
-const DEFAULT_SIZE = 16
+const DEFAULT_SIZE = 32
enum DATA_TYPES {
RANDOM_UNIQUE,
TRUE_RANDOM,
diff --git a/scenes/levels.tscn b/scenes/levels.tscn
index d95581f..3489402 100644
--- a/scenes/levels.tscn
+++ b/scenes/levels.tscn
@@ -85,16 +85,16 @@ size_flags_horizontal = 3
[node name="Display" type="MarginContainer" parent="Levels/Level/Right"]
margin_right = 616.0
-margin_bottom = 431.0
+margin_bottom = 475.0
size_flags_vertical = 3
script = ExtResource( 3 )
[node name="TypesContainer" type="MarginContainer" parent="Levels/Level/Right/Display"]
visible = false
margin_left = 288.0
-margin_top = 195.0
+margin_top = 217.0
margin_right = 328.0
-margin_bottom = 235.0
+margin_bottom = 257.0
size_flags_horizontal = 4
size_flags_vertical = 4
script = ExtResource( 3 )
@@ -111,16 +111,16 @@ size_flags_vertical = 4
margin_left = 20.0
margin_top = 20.0
margin_right = 596.0
-margin_bottom = 411.0
+margin_bottom = 455.0
[node name="Info" type="HBoxContainer" parent="Levels/Level/Right"]
-margin_top = 439.0
+margin_top = 483.0
margin_right = 616.0
margin_bottom = 613.0
[node name="ControlsContainer" type="MarginContainer" parent="Levels/Level/Right/Info"]
margin_right = 405.0
-margin_bottom = 174.0
+margin_bottom = 130.0
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
script = ExtResource( 3 )
@@ -129,7 +129,7 @@ script = ExtResource( 3 )
margin_left = 20.0
margin_top = 20.0
margin_right = 385.0
-margin_bottom = 154.0
+margin_bottom = 110.0
size_flags_vertical = 1
text = "These are the controls for the level."
autowrap = true
@@ -137,7 +137,7 @@ autowrap = true
[node name="ScoresContainer" type="MarginContainer" parent="Levels/Level/Right/Info"]
margin_left = 413.0
margin_right = 616.0
-margin_bottom = 174.0
+margin_bottom = 130.0
size_flags_horizontal = 3
script = ExtResource( 3 )
@@ -145,7 +145,7 @@ script = ExtResource( 3 )
margin_left = 20.0
margin_top = 20.0
margin_right = 183.0
-margin_bottom = 154.0
+margin_bottom = 110.0
[node name="Header" type="HBoxContainer" parent="Levels/Level/Right/Info/ScoresContainer/Scores"]
margin_right = 163.0
@@ -167,26 +167,22 @@ align = 2
[node name="Data" type="HBoxContainer" parent="Levels/Level/Right/Info/ScoresContainer/Scores"]
margin_top = 27.0
margin_right = 163.0
-margin_bottom = 134.0
+margin_bottom = 90.0
[node name="Sizes" type="Label" parent="Levels/Level/Right/Info/ScoresContainer/Scores/Data"]
margin_right = 30.0
-margin_bottom = 107.0
+margin_bottom = 63.0
text = "8
-16
32
-64
128"
[node name="Times" type="Label" parent="Levels/Level/Right/Info/ScoresContainer/Scores/Data"]
margin_left = 38.0
margin_right = 163.0
-margin_bottom = 107.0
+margin_bottom = 63.0
size_flags_horizontal = 3
text = "INF
INF
-INF
-INF
INF"
align = 2
uppercase = true
diff --git a/scripts/levels.gd b/scripts/levels.gd
index f240b42..a0c02b0 100644
--- a/scripts/levels.gd
+++ b/scripts/levels.gd
@@ -13,7 +13,7 @@ const LEVELS = [
OddEvenSort,
]
-const MIN_WAIT = 1.0 / 32 # Should be greater than maximum frame time
+const MIN_WAIT = 1.0 / 64
const MAX_WAIT = 4
const MIN_SIZE = 8
const MAX_SIZE = 128
@@ -82,15 +82,15 @@ func _input(event):
if event.is_action_pressed("ui_right", true):
_switch_level(_index + 1)
if event.is_action_pressed("bigger"):
- _size = min(_size * 2, MAX_SIZE)
+ _size = min(_size * 4, MAX_SIZE)
_reload()
if event.is_action_pressed("smaller"):
- _size = max(_size / 2, MIN_SIZE)
+ _size = max(_size / 4, MIN_SIZE)
_reload()
if event.is_action_pressed("faster"):
- $Timer.wait_time = max($Timer.wait_time / 2, MIN_WAIT)
+ $Timer.wait_time = max($Timer.wait_time / 4, MIN_WAIT)
if event.is_action_pressed("slower"):
- $Timer.wait_time = min($Timer.wait_time * 2, MAX_WAIT)
+ $Timer.wait_time = min($Timer.wait_time * 4, MAX_WAIT)
if event.is_action_pressed("change_data"):
AudioServer.set_bus_mute(AudioServer.get_bus_index("Master"), true)
$Level/Right/Display/ArrayView.hide()
From 74e22b2152bbed7cde2f16937917d62411fbca54 Mon Sep 17 00:00:00 2001
From: Daniel Ting
Date: Tue, 1 Sep 2020 16:49:04 -0500
Subject: [PATCH 20/29] refactor: clean up levels.gd
---
scenes/levels.tscn | 2 +-
scripts/levels.gd | 12 +++++-------
2 files changed, 6 insertions(+), 8 deletions(-)
diff --git a/scenes/levels.tscn b/scenes/levels.tscn
index 3489402..7ed24d1 100644
--- a/scenes/levels.tscn
+++ b/scenes/levels.tscn
@@ -107,7 +107,7 @@ margin_bottom = 20.0
size_flags_horizontal = 4
size_flags_vertical = 4
-[node name="ArrayView" type="HBoxContainer" parent="Levels/Level/Right/Display"]
+[node name="HBoxContainer" type="HBoxContainer" parent="Levels/Level/Right/Display"]
margin_left = 20.0
margin_top = 20.0
margin_right = 596.0
diff --git a/scripts/levels.gd b/scripts/levels.gd
index a0c02b0..b274bc5 100644
--- a/scripts/levels.gd
+++ b/scripts/levels.gd
@@ -48,12 +48,10 @@ func _reload():
$NamesContainer/Names/Current.text = _level.NAME
$Level/Left/Code.text = _level.DESCRIPTION
$Level/Right/Info/ControlsContainer/Controls.text = _level.CONTROLS
- var view = $Level/Right/Display/ArrayView
- $Level/Right/Display.remove_child(view)
+ var view = $Level/Right/Display/HBoxContainer
+ view.get_parent().remove_child(view)
view.queue_free()
- view = ArrayView.new(_level)
- view.name = "ArrayView"
- $Level/Right/Display.add_child(view)
+ $Level/Right/Display.add_child(ArrayView.new(_level), true)
$Timer.start()
func _load_scores(level):
@@ -93,7 +91,7 @@ func _input(event):
$Timer.wait_time = min($Timer.wait_time * 4, MAX_WAIT)
if event.is_action_pressed("change_data"):
AudioServer.set_bus_mute(AudioServer.get_bus_index("Master"), true)
- $Level/Right/Display/ArrayView.hide()
+ $Level/Right/Display/HBoxContainer.hide()
$Level/Right/Display/TypesContainer.show()
$Timer.stop()
$Level/Right/Display/TypesContainer/Types.get_child(0).grab_focus()
@@ -114,7 +112,7 @@ func _on_Current_pressed():
func _on_Button_pressed(data_type):
AudioServer.set_bus_mute(AudioServer.get_bus_index("Master"), false)
$Level/Right/Display/TypesContainer.hide()
- $Level/Right/Display/ArrayView.show()
+ $Level/Right/Display/HBoxContainer.show()
$Timer.start()
_data_type = ArrayModel.DATA_TYPES[data_type]
_reload()
From 26c84e9827ec1aee8a71ac751b459153882bceda Mon Sep 17 00:00:00 2001
From: Daniel Ting
Date: Tue, 1 Sep 2020 19:36:46 -0500
Subject: [PATCH 21/29] feat: implement big preview mode
Took more hacks than I would have liked, but getting rid of those would
require a refactor across the entire codebase.
---
project.godot | 5 ++++
scenes/levels.tscn | 31 ++++++++++++++++------
scenes/menu.tscn | 18 +++++++------
scripts/levels.gd | 66 +++++++++++++++++++++++++++++++---------------
4 files changed, 83 insertions(+), 37 deletions(-)
diff --git a/project.godot b/project.godot
index ddcc9b9..d5d4f0b 100644
--- a/project.godot
+++ b/project.godot
@@ -202,6 +202,11 @@ change_data={
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":67,"unicode":0,"echo":false,"script":null)
]
}
+big_preview={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":66,"unicode":0,"echo":false,"script":null)
+ ]
+}
[rendering]
diff --git a/scenes/levels.tscn b/scenes/levels.tscn
index 7ed24d1..fb53295 100644
--- a/scenes/levels.tscn
+++ b/scenes/levels.tscn
@@ -99,14 +99,6 @@ size_flags_horizontal = 4
size_flags_vertical = 4
script = ExtResource( 3 )
-[node name="Types" type="VBoxContainer" parent="Levels/Level/Right/Display/TypesContainer"]
-margin_left = 20.0
-margin_top = 20.0
-margin_right = 20.0
-margin_bottom = 20.0
-size_flags_horizontal = 4
-size_flags_vertical = 4
-
[node name="HBoxContainer" type="HBoxContainer" parent="Levels/Level/Right/Display"]
margin_left = 20.0
margin_top = 20.0
@@ -187,6 +179,29 @@ INF"
align = 2
uppercase = true
+[node name="BigDisplay" type="MarginContainer" parent="Levels"]
+visible = false
+margin_top = 640.0
+margin_right = 1240.0
+margin_bottom = 680.0
+size_flags_vertical = 3
+script = ExtResource( 3 )
+
+[node name="TypesContainer" type="MarginContainer" parent="Levels/BigDisplay"]
+visible = false
+margin_left = 600.0
+margin_right = 640.0
+margin_bottom = 40.0
+size_flags_horizontal = 4
+size_flags_vertical = 4
+script = ExtResource( 3 )
+
+[node name="HBoxContainer" type="HBoxContainer" parent="Levels/BigDisplay"]
+margin_left = 20.0
+margin_top = 20.0
+margin_right = 1220.0
+margin_bottom = 20.0
+
[node name="Timer" type="Timer" parent="Levels"]
[connection signal="pressed" from="Levels/NamesContainer/Names/Current" to="Levels" method="_on_Current_pressed"]
[connection signal="timeout" from="Levels/Timer" to="Levels" method="_on_Timer_timeout"]
diff --git a/scenes/menu.tscn b/scenes/menu.tscn
index afb99d6..9eff7ef 100644
--- a/scenes/menu.tscn
+++ b/scenes/menu.tscn
@@ -44,9 +44,9 @@ __meta__ = {
[node name="InstructionsContainer" type="MarginContainer" parent="MainMenu/Display"]
visible = false
margin_left = 484.0
-margin_top = 175.0
+margin_top = 164.0
margin_right = 714.0
-margin_bottom = 423.0
+margin_bottom = 434.0
size_flags_horizontal = 4
size_flags_vertical = 4
script = ExtResource( 3 )
@@ -55,17 +55,17 @@ script = ExtResource( 3 )
margin_left = 20.0
margin_top = 20.0
margin_right = 210.0
-margin_bottom = 228.0
+margin_bottom = 250.0
custom_constants/separation = 16
[node name="Controls" type="HBoxContainer" parent="MainMenu/Display/InstructionsContainer/Instructions"]
margin_right = 190.0
-margin_bottom = 173.0
+margin_bottom = 195.0
custom_constants/separation = 20
[node name="Keys" type="Label" parent="MainMenu/Display/InstructionsContainer/Instructions/Controls"]
margin_right = 50.0
-margin_bottom = 173.0
+margin_bottom = 195.0
size_flags_horizontal = 4
text = "W
A
@@ -73,27 +73,29 @@ S
D
M
C
+B
esc
space"
[node name="Actions" type="Label" parent="MainMenu/Display/InstructionsContainer/Instructions/Controls"]
margin_left = 70.0
margin_right = 190.0
-margin_bottom = 173.0
+margin_bottom = 195.0
text = "bigger
slower
smaller
faster
toggle sound
change data
+big preview
back
confirm"
align = 2
[node name="Button" type="Button" parent="MainMenu/Display/InstructionsContainer/Instructions"]
-margin_top = 189.0
+margin_top = 211.0
margin_right = 190.0
-margin_bottom = 208.0
+margin_bottom = 230.0
text = "OK"
[node name="Spacing" type="Control" parent="MainMenu"]
diff --git a/scripts/levels.gd b/scripts/levels.gd
index b274bc5..1e24608 100644
--- a/scripts/levels.gd
+++ b/scripts/levels.gd
@@ -18,14 +18,21 @@ const MAX_WAIT = 4
const MIN_SIZE = 8
const MAX_SIZE = 128
-var _index = LEVELS.find(GlobalScene.get_param("level"))
+var _index = LEVELS.find(GlobalScene.get_param("level", LEVELS[0]))
var _level: ComparisonSort
var _size = GlobalScene.get_param("size", ArrayModel.DEFAULT_SIZE)
var _data_type = GlobalScene.get_param(
"data_type", ArrayModel.DATA_TYPES.RANDOM_UNIQUE)
func _ready():
- var types = $Level/Right/Display/TypesContainer/Types
+ _load_types($Level/Right/Display/TypesContainer)
+ _load_types($BigDisplay/TypesContainer)
+ _reload()
+
+func _load_types(node):
+ var types = VBoxContainer.new()
+ types.name = "Types"
+ node.add_child(types)
for type in ArrayModel.DATA_TYPES:
var button = Button.new()
button.text = type.replace("_", " ")
@@ -35,23 +42,34 @@ func _ready():
var bottom = types.get_child(types.get_child_count() - 1)
top.focus_neighbour_top = bottom.get_path()
bottom.focus_neighbour_bottom = top.get_path()
- _reload()
func _reload():
- $NamesContainer/Names/Current.grab_focus()
- if _index == -1:
- _index = 0
- _level = LEVELS[_index].new(ArrayModel.new(_size, _data_type))
- _level.connect("done", self, "_on_ComparisonSort_done")
+ # Load everything from scratch
+ _restart()
_load_scores(_level)
- # Load level information
$NamesContainer/Names/Current.text = _level.NAME
$Level/Left/Code.text = _level.DESCRIPTION
$Level/Right/Info/ControlsContainer/Controls.text = _level.CONTROLS
- var view = $Level/Right/Display/HBoxContainer
- view.get_parent().remove_child(view)
- view.queue_free()
- $Level/Right/Display.add_child(ArrayView.new(_level), true)
+
+func _restart():
+ set_process_input(true)
+ # Only load in a restarted simulation
+ $NamesContainer/Names/Current.grab_focus()
+ _level = LEVELS[_index].new(ArrayModel.new(_size, _data_type))
+ _level.connect("done", self, "_on_ComparisonSort_done")
+ var view = $Level/Right/Display if $Level.visible else $BigDisplay
+ var other = $BigDisplay if $Level.visible else $Level/Right/Display
+ # Delete both ArrayViews if they exist
+ if other.get_node_or_null("HBoxContainer") != null:
+ other.get_node("HBoxContainer").queue_free()
+ var array_view = view.get_node_or_null("HBoxContainer")
+ if array_view != null:
+ # XXX: remove_child is needed in order to ensure that the added
+ # child will be named "HBoxContainer" and not "HBoxContainer2"
+ # because the other ArrayView hasn't been queue_free'd yet
+ view.remove_child(array_view)
+ array_view.queue_free()
+ view.add_child(ArrayView.new(_level), true)
$Timer.start()
func _load_scores(level):
@@ -91,16 +109,21 @@ func _input(event):
$Timer.wait_time = min($Timer.wait_time * 4, MAX_WAIT)
if event.is_action_pressed("change_data"):
AudioServer.set_bus_mute(AudioServer.get_bus_index("Master"), true)
- $Level/Right/Display/HBoxContainer.hide()
- $Level/Right/Display/TypesContainer.show()
+ var display = $Level/Right/Display if $Level.visible else $BigDisplay
+ display.get_node("HBoxContainer").hide()
+ display.get_node("TypesContainer").show()
$Timer.stop()
- $Level/Right/Display/TypesContainer/Types.get_child(0).grab_focus()
+ display.get_node("TypesContainer/Types").get_child(0).grab_focus()
+ if event.is_action_pressed("big_preview"):
+ $Level.visible = not $Level.visible
+ $BigDisplay.visible = not $BigDisplay.visible
+ _restart()
func _on_ComparisonSort_done():
+ set_process_input(false)
$Timer.stop()
yield(get_tree().create_timer(1), "timeout")
- if _level.array.is_sorted():
- _reload()
+ _restart()
func _on_Timer_timeout():
_level.next(null)
@@ -111,8 +134,9 @@ func _on_Current_pressed():
func _on_Button_pressed(data_type):
AudioServer.set_bus_mute(AudioServer.get_bus_index("Master"), false)
- $Level/Right/Display/TypesContainer.hide()
- $Level/Right/Display/HBoxContainer.show()
+ var display = $Level/Right/Display if $Level.visible else $BigDisplay
+ display.get_node("TypesContainer").hide()
+ display.get_node("HBoxContainer").show()
$Timer.start()
_data_type = ArrayModel.DATA_TYPES[data_type]
- _reload()
+ _restart()
From d5c9bcba8bd1037d3cbb0df45c6d8a425efd34b6 Mon Sep 17 00:00:00 2001
From: Daniel Ting
Date: Tue, 1 Sep 2020 21:00:26 -0500
Subject: [PATCH 22/29] fix: no longer unmutes after switching data types
---
scripts/levels.gd | 3 +--
views/array_view.gd | 8 ++++----
2 files changed, 5 insertions(+), 6 deletions(-)
diff --git a/scripts/levels.gd b/scripts/levels.gd
index 1e24608..8346d0b 100644
--- a/scripts/levels.gd
+++ b/scripts/levels.gd
@@ -108,9 +108,9 @@ func _input(event):
if event.is_action_pressed("slower"):
$Timer.wait_time = min($Timer.wait_time * 4, MAX_WAIT)
if event.is_action_pressed("change_data"):
- AudioServer.set_bus_mute(AudioServer.get_bus_index("Master"), true)
var display = $Level/Right/Display if $Level.visible else $BigDisplay
display.get_node("HBoxContainer").hide()
+ display.get_node("HBoxContainer").sound.set_process(false)
display.get_node("TypesContainer").show()
$Timer.stop()
display.get_node("TypesContainer/Types").get_child(0).grab_focus()
@@ -133,7 +133,6 @@ func _on_Current_pressed():
{"level": LEVELS[_index], "size": _size, "data_type": _data_type})
func _on_Button_pressed(data_type):
- AudioServer.set_bus_mute(AudioServer.get_bus_index("Master"), false)
var display = $Level/Right/Display if $Level.visible else $BigDisplay
display.get_node("TypesContainer").hide()
display.get_node("HBoxContainer").show()
diff --git a/views/array_view.gd b/views/array_view.gd
index 3d556d6..e8d966e 100644
--- a/views/array_view.gd
+++ b/views/array_view.gd
@@ -16,7 +16,7 @@ var _rects = []
var _positions = []
var _pointer = Polygon2D.new()
var _pointer_size: int
-var _sound = ArraySound.new()
+var sound = ArraySound.new()
onready var _separation = 128 / _level.array.size
func _init(level):
@@ -24,7 +24,7 @@ func _init(level):
add_child(_level) # NOTE: This is necessary for it to read input
add_child(_tween) # NOTE: This is necessary for it to animate
add_child(_pointer)
- add_child(_sound)
+ add_child(sound)
_pointer.hide()
func _ready():
@@ -67,7 +67,7 @@ func _ready():
_pointer.show()
func _process(delta):
- _sound.frac = _level.get_frac()
+ sound.frac = _level.get_frac()
if _pointer.visible:
var pointed = _level.get_pointer()
var height = rect_size.y - _pointer_size * 2
@@ -79,7 +79,7 @@ func _process(delta):
func _on_ComparisonSort_done():
set_process(false)
- _sound.player.stop()
+ sound.player.stop()
_pointer.hide()
for i in range(_rects.size()):
_rects[i].color = ComparisonSort.EFFECTS.NONE
From 1f44607e01a119947ccd8873f5fcb2bc5e25fe50 Mon Sep 17 00:00:00 2001
From: Daniel Ting
Date: Tue, 8 Sep 2020 17:12:43 -0500
Subject: [PATCH 23/29] docs: add Python-style psuedocode
---
levels/bogo_sort.gd | 6 ++++++
levels/bubble_sort.gd | 10 ++++++++++
levels/cocktail_sort.gd | 19 +++++++++++++++++++
levels/comb_sort.gd | 11 +++++++++++
levels/cycle_sort.gd | 12 ++++++++++++
levels/insertion_sort.gd | 8 ++++++++
levels/merge_sort.gd | 18 ++++++++++++++++++
levels/odd_even_sort.gd | 14 ++++++++++++++
levels/quick_sort.gd | 13 +++++++++++++
levels/selection_sort.gd | 9 +++++++++
levels/shell_sort.gd | 13 +++++++++++++
scripts/levels.gd | 4 ++--
scripts/menu.gd | 1 +
13 files changed, 136 insertions(+), 2 deletions(-)
diff --git a/levels/bogo_sort.gd b/levels/bogo_sort.gd
index 2971418..0f8bd57 100644
--- a/levels/bogo_sort.gd
+++ b/levels/bogo_sort.gd
@@ -11,6 +11,12 @@ Keep on hitting RIGHT ARROW to CONTINUE and hope for the best!
class_name BogoSort
extends ComparisonSort
+const CODE = """
+def bogosort(a):
+ while not a.sorted():
+ a.shuffle()
+"""
+
func _init(array).(array):
pass
diff --git a/levels/bubble_sort.gd b/levels/bubble_sort.gd
index 6fdc66f..5780e2f 100644
--- a/levels/bubble_sort.gd
+++ b/levels/bubble_sort.gd
@@ -16,6 +16,16 @@ them. Otherwise, hit RIGHT ARROW to continue.
class_name BubbleSort
extends ComparisonSort
+const CODE = """
+def bubble_sort(a):
+ swapped = true
+ while swapped:
+ swapped = false
+ for i in range(len(a) - 1):
+ if a[i] > a[i + 1]:
+ a.swap(i, i + 1)
+ swapped = true
+"""
const ACTIONS = {
"SWAP": "Left",
"CONTINUE": "Right",
diff --git a/levels/cocktail_sort.gd b/levels/cocktail_sort.gd
index 1a243e3..ad6a9f7 100644
--- a/levels/cocktail_sort.gd
+++ b/levels/cocktail_sort.gd
@@ -13,6 +13,25 @@ them. Otherwise, hit RIGHT ARROW to continue.
class_name CocktailSort
extends ComparisonSort
+const CODE = """
+def cocktail_sort(a):
+ swapped = true
+ while swapped:
+ swapped = false
+ for i in range(len(a) - 1):
+ if array[i] > array[i + 1]:
+ a.swap(i, i + 1)
+ swapped = true
+
+ if not swapped:
+ break
+
+ swapped = false
+ for i in range(len(a) - 1, 0, -1)
+ if a[i - 1] > a[i]:
+ a.swap(i - 1, i)
+ swapped = true
+"""
const ACTIONS = {
"SWAP": "Left",
"CONTINUE": "Right",
diff --git a/levels/comb_sort.gd b/levels/comb_sort.gd
index 693005d..360692d 100644
--- a/levels/comb_sort.gd
+++ b/levels/comb_sort.gd
@@ -12,6 +12,17 @@ them. Otherwise, hit RIGHT ARROW to continue.
class_name CombSort
extends ComparisonSort
+const CODE = """
+def comb_sort(a):
+ gap = len(a)
+ swapped = true
+ while gap != 1 or swapped:
+ gap = max(gap / 1.3, 1)
+ for i in range(len(a) - gap):
+ if a[i] > a[i + gap]:
+ a.swap(i, i + gap)
+ swapped = true
+"""
const SHRINK_FACTOR = 1.3
const ACTIONS = {
"SWAP": "Left",
diff --git a/levels/cycle_sort.gd b/levels/cycle_sort.gd
index 243e352..2b3589c 100644
--- a/levels/cycle_sort.gd
+++ b/levels/cycle_sort.gd
@@ -14,6 +14,18 @@ Otherwise, hit RIGHT ARROW.
class_name CycleSort
extends ComparisonSort
+const CODE = """
+def cycle_sort(a):
+ for i in range(len(a)):
+ while True:
+ position = 0
+ for j in a:
+ if a[j] > a[i]:
+ position += 1
+ if i == position:
+ break
+ a.swap(i, position)
+"""
const ACTIONS = {
"SMALLER": "Left",
"BIGGER": "Right",
diff --git a/levels/insertion_sort.gd b/levels/insertion_sort.gd
index 39680de..7af9479 100644
--- a/levels/insertion_sort.gd
+++ b/levels/insertion_sort.gd
@@ -17,6 +17,14 @@ advance.
class_name InsertionSort
extends ComparisonSort
+const CODE = """
+def insertion_sort(a):
+ for i in range(len(a)):
+ j = i
+ while j > 0 and a[j - 1] > a[j]:
+ a.swap(j - 1, j)
+ j -= 1
+"""
const ACTIONS = {
"SWAP": "Left",
"CONTINUE": "Right",
diff --git a/levels/merge_sort.gd b/levels/merge_sort.gd
index f85de24..a64827f 100644
--- a/levels/merge_sort.gd
+++ b/levels/merge_sort.gd
@@ -17,6 +17,24 @@ the other side's ARROW KEY.
class_name MergeSort
extends ComparisonSort
+const CODE = """
+def merge_sort(a):
+ size = 1
+ while size < len(array):
+ for block in range(len(array) / size / 2):
+ merged = []
+ begin = size * 2 * block
+ i = begin
+ j = begin + size
+ while len(merged) != size * 2:
+ if i >= begin + size or a[j] < a[i]:
+ merged.append(a[j])
+ j += 1
+ else:
+ merged.append(a[i])
+ i += 1
+ a[begin:begin + size] = merged
+"""
const ACTIONS = {
"LEFT": "Left",
"RIGHT": "Right",
diff --git a/levels/odd_even_sort.gd b/levels/odd_even_sort.gd
index 3f7d955..00299c6 100644
--- a/levels/odd_even_sort.gd
+++ b/levels/odd_even_sort.gd
@@ -13,6 +13,20 @@ them. Otherwise, hit RIGHT ARROW to continue.
class_name OddEvenSort
extends ComparisonSort
+const CODE = """
+def odd_even_sort(a):
+ swapped = true
+ while swapped:
+ swapped = false
+ for i in range(1, len(a) - 1, 2):
+ if a[i] > a[i + 1]:
+ a.swap(i, i + 1)
+ swapped = true
+ for i in range(0, len(a) - 1, 2):
+ if a[i] > a[i + 1]:
+ a.swap(i, i + 1)
+ swapped = true
+"""
const ACTIONS = {
"SWAP": "Left",
"CONTINUE": "Right",
diff --git a/levels/quick_sort.gd b/levels/quick_sort.gd
index b94e4c4..44cc84e 100644
--- a/levels/quick_sort.gd
+++ b/levels/quick_sort.gd
@@ -19,6 +19,19 @@ RIGHT ARROW to move on.
class_name QuickSort
extends ComparisonSort
+const CODE = """
+def quicksort(array, low=0, high=len(a) - 1):
+ if low < high:
+ pivot = a[high]
+ pointer = low
+ for i in range(low, high):
+ if a[i] < pivot:
+ a.swap(i, pointer)
+ pointer += 1
+ a.swap(pointer, high)
+ quicksort(a, low, pointer - 1)
+ quicksort(a, pointer + 1, high)
+"""
const ACTIONS = {
"SWAP": "Left",
"CONTINUE": "Right",
diff --git a/levels/selection_sort.gd b/levels/selection_sort.gd
index 343d469..41d6fe0 100644
--- a/levels/selection_sort.gd
+++ b/levels/selection_sort.gd
@@ -18,6 +18,15 @@ repeat.
class_name SelectionSort
extends ComparisonSort
+const CODE = """
+def selection_sort(a):
+ for i in range(len(a)):
+ smallest = i
+ for j in range(i, len(a)):
+ if a[j] < a[smallest]:
+ smallest = j
+ a.swap(i, smallest)
+"""
const ACTIONS = {
"SWAP": "Left",
"CONTINUE": "Right",
diff --git a/levels/shell_sort.gd b/levels/shell_sort.gd
index 67dc116..015148f 100644
--- a/levels/shell_sort.gd
+++ b/levels/shell_sort.gd
@@ -8,11 +8,24 @@ by gaps.
Hit LEFT ARROW to swap the two highlighted elements as long as they are
out of order. When this is no longer the case, hit RIGHT ARROW to
+advance.
"""
class_name ShellSort
extends ComparisonSort
+const CODE = """
+def shell_sort(a):
+ gap = len(a)
+ while gap != 1:
+ gap = max(gap / 2, 1)
+ for i in range(gap):
+ for j in range(i, len(a) - gap, gap):
+ k = j
+ while k > gap and a[k - gap] > a[k]:
+ a.swap(k - gap, k)
+ k -= gap
+"""
const ACTIONS = {
"SWAP": "Left",
"CONTINUE": "Right",
diff --git a/scripts/levels.gd b/scripts/levels.gd
index 8346d0b..22d1378 100644
--- a/scripts/levels.gd
+++ b/scripts/levels.gd
@@ -48,7 +48,7 @@ func _reload():
_restart()
_load_scores(_level)
$NamesContainer/Names/Current.text = _level.NAME
- $Level/Left/Code.text = _level.DESCRIPTION
+ $Level/Left/Code.text = _level.DESCRIPTION + "\n" + _level.CODE
$Level/Right/Info/ControlsContainer/Controls.text = _level.CONTROLS
func _restart():
@@ -77,7 +77,7 @@ func _load_scores(level):
data.get_node("Times").text = ""
for i in data.get_node("Sizes").text.split("\n"):
var time = str(GlobalScore.get_time(level.NAME, int(i)))
- data.get_node("Times").text += time
+ data.get_node("Times").text += "%.3f" % float(time)
if int(i) != MAX_SIZE:
data.get_node("Times").text += "\n"
diff --git a/scripts/menu.gd b/scripts/menu.gd
index 27f1358..5a57b6e 100644
--- a/scripts/menu.gd
+++ b/scripts/menu.gd
@@ -6,6 +6,7 @@ func _ready():
$Buttons/Start.grab_focus()
randomize()
$Display.add_child(ArrayView.new(_level), true)
+ AudioServer.set_bus_mute(AudioServer.get_bus_index("Master"), true)
func _on_Start_pressed():
GlobalScene.change_scene("res://scenes/levels.tscn")
From 65193360897fd7b1c73b53974d54392320c4437c Mon Sep 17 00:00:00 2001
From: Daniel Ting
Date: Wed, 9 Sep 2020 23:49:23 -0500
Subject: [PATCH 24/29] docs: rewrite descriptions
---
levels/bubble_sort.gd | 15 ++++++++++-----
levels/cocktail_sort.gd | 10 ++++++----
levels/comb_sort.gd | 10 +++++++++-
levels/comparison_sort.gd | 4 ++--
levels/cycle_sort.gd | 17 ++++++++++++-----
levels/insertion_sort.gd | 17 +++++++++--------
levels/merge_sort.gd | 14 +++++++-------
levels/odd_even_sort.gd | 8 ++++++--
levels/quick_sort.gd | 19 ++++++++++---------
levels/selection_sort.gd | 16 ++++++++--------
levels/shell_sort.gd | 15 ++++++++++-----
scenes/levels.tscn | 4 +++-
12 files changed, 92 insertions(+), 57 deletions(-)
diff --git a/levels/bubble_sort.gd b/levels/bubble_sort.gd
index 5780e2f..08fa1ef 100644
--- a/levels/bubble_sort.gd
+++ b/levels/bubble_sort.gd
@@ -2,11 +2,16 @@
BUBBLE SORT
-Bubble sort iterates through the array and looks at each pair of
-elements, swapping them if they are out of order. When it has gone
-through the entire array without swapping a single pair, it has
-finished. Though simple to understand, bubble sort is hopelessly
-inefficient on all but the smallest of arrays.
+Bubble sort looks at consecutive pairs of elements and swaps them if
+they are out of order, finishing when it has gone through the whole
+array from beginning to end without a single swap. The actual level
+contains an optimization that skips over elements guaranteed to be
+already in place.
+
+Due to its simplicity, it is commonly taught as the first sorting
+algorithm students learn in computer science classes, but is rarely used
+in real life because it is slow on large data and other simple quadratic
+algorithms like insertion sort perform better.
If the two highlighted elements are out of order, hit LEFT ARROW to swap
diff --git a/levels/cocktail_sort.gd b/levels/cocktail_sort.gd
index ad6a9f7..a564656 100644
--- a/levels/cocktail_sort.gd
+++ b/levels/cocktail_sort.gd
@@ -2,8 +2,12 @@
COCKTAIL SORT
-Cocktail shaker sort is a variation of bubble sort that
-alternates going backwards and forwards.
+Cocktail sort is a variation of bubble sort that alternates going
+backwards and forwards. The actual level contains an optimization that
+skips over elements guaranteed to be already in place.
+
+Because it is bidirectional, it is slightly faster than bubble sort, but
+is still quadratic and therefore not used on large data.
If the two highlighted elements are out of order, hit LEFT ARROW to swap
@@ -22,10 +26,8 @@ def cocktail_sort(a):
if array[i] > array[i + 1]:
a.swap(i, i + 1)
swapped = true
-
if not swapped:
break
-
swapped = false
for i in range(len(a) - 1, 0, -1)
if a[i - 1] > a[i]:
diff --git a/levels/comb_sort.gd b/levels/comb_sort.gd
index 360692d..0d37698 100644
--- a/levels/comb_sort.gd
+++ b/levels/comb_sort.gd
@@ -2,7 +2,14 @@
COMB SORT
-Comb sort is a variant of bubble sort that operates on gapped arrays.
+Comb sort is a variant of bubble sort that compares elements a certain
+gap apart instead of consecutive elements. This gap is divided after
+every pass by an experimentally determined optimal factor of about 1.3.
+Once the gap becomes 1, comb sort becomes a regular bubble sort.
+
+This allows comb sort to get rid of small values near the end more
+quickly, which turns out to be the bottleneck in bubble sort, but still
+has a quadratic worst case.
If the two highlighted elements are out of order, hit LEFT ARROW to swap
@@ -17,6 +24,7 @@ def comb_sort(a):
gap = len(a)
swapped = true
while gap != 1 or swapped:
+ swapped = false
gap = max(gap / 1.3, 1)
for i in range(len(a) - gap):
if a[i] > a[i + gap]:
diff --git a/levels/comparison_sort.gd b/levels/comparison_sort.gd
index 30932e9..ca11b37 100644
--- a/levels/comparison_sort.gd
+++ b/levels/comparison_sort.gd
@@ -12,8 +12,8 @@ const EFFECTS = {
const DISABLE_TIME = 1.0
var NAME = _get_header().split(" ")[0]
-var DESCRIPTION = _get_header().split(" ")[1]
-var CONTROLS = _get_header().split(" ")[2]
+var DESCRIPTION = _get_header().split(" ")[1].replace(" ", "\n\n")
+var CONTROLS = _get_header().split(" ")[-1]
var array: ArrayModel
diff --git a/levels/cycle_sort.gd b/levels/cycle_sort.gd
index 2b3589c..4cc2d2a 100644
--- a/levels/cycle_sort.gd
+++ b/levels/cycle_sort.gd
@@ -2,13 +2,20 @@
CYCLE SORT
-Cycle sort repeatedly counts the number of elements less than the first
-and swaps it with that index until the smallest element is reached. Then
-it does this process starting at the next out-of-place element.
+Cycle sort looks at the first element and finds its correct final
+position by counting the number of elements smaller than it. Then it
+saves the element at that index, writes the first element there, and
+repeats the process with the saved element. For the sake of
+demonstration, in the actual level, swaps are used instead.
+This results in a quadratic runtime but gives it the special property
+of being optimal in the number of writes to the array. This makes cycle
+sort useful in storage types where writes are very expensive or reduce
+its lifespan.
-If the highlighted element is less than the pointer, hit LEFT ARROW.
-Otherwise, hit RIGHT ARROW.
+
+If the highlighted element is less than the element below the blue
+pointer, hit LEFT ARROW. Otherwise, hit RIGHT ARROW.
"""
class_name CycleSort
diff --git a/levels/insertion_sort.gd b/levels/insertion_sort.gd
index 7af9479..2da6fdc 100644
--- a/levels/insertion_sort.gd
+++ b/levels/insertion_sort.gd
@@ -2,16 +2,17 @@
INSERTION SORT
-Insertion sort goes through the array and inserts each
-element into its correct position. It is most similar to how most people
-would sort a deck of cards. It is also slow on large arrays but it is
-one of the faster quadratic algorithms. It is often used to sort smaller
-subarrays in hybrid sorting algorithms.
+Insertion sort goes through the array and inserts each element into its
+correct place, like how most people would sort a hand of playing cards.
+It is one of the fastest quadratic algorithms in practice and is
+efficient on small or almost sorted data. It is also simple, stable, and
+in-place. For these reasons it is sometimes used within faster divide
+and conquer algorithms when the array has been divided to a small size.
-Hit LEFT ARROW to swap the two highlighted elements as long as they are
-out of order. When this is no longer the case, hit RIGHT ARROW to
-advance.
+
+If the two highlighted elements are out of order, hit LEFT ARROW to swap
+them. Otherwise, hit RIGHT ARROW to continue.
"""
class_name InsertionSort
diff --git a/levels/merge_sort.gd b/levels/merge_sort.gd
index a64827f..8d5e82a 100644
--- a/levels/merge_sort.gd
+++ b/levels/merge_sort.gd
@@ -2,16 +2,15 @@
MERGE SORT
-Merge sort is an efficient sorting algorithm that splits the array into
-single-element chunks. Then it merges each pair of chunks until only one
-sorted chunk is left by repeatedly choosing the smaller element at the
-head of each chunk and moving the head back. However, it needs an entire
-array's worth of auxiliary memory.
+Merge sort merges subarrays of increasing size by setting a pointer to
+the head of each half. Then it repeatedly copies the smaller pointed
+element and increments that side's pointer. When one side is exhausted,
+it copies the rest of the other side and overwrites the two halves with
+the merged copy.
Press the ARROW KEY corresponding to the side that the smaller
-highlighted element is on. If you've reached the end of one side, press
-the other side's ARROW KEY.
+highlighted element is on or the non-exhausted side.
"""
class_name MergeSort
@@ -34,6 +33,7 @@ def merge_sort(a):
merged.append(a[i])
i += 1
a[begin:begin + size] = merged
+ size *= 2
"""
const ACTIONS = {
"LEFT": "Left",
diff --git a/levels/odd_even_sort.gd b/levels/odd_even_sort.gd
index 00299c6..54fe7f2 100644
--- a/levels/odd_even_sort.gd
+++ b/levels/odd_even_sort.gd
@@ -2,8 +2,12 @@
ODD-EVEN SORT
-Odd-even sort is a variant of bubble sort that alternates on elements at
-odd and even indices.
+Odd-even sort is a variant of bubble sort that alternates between
+comparing consecutive odd-even and even-odd indexed pairs.
+
+It is not of much use on a single processor as it is designed for
+parallel processors, which can perform every comparison in a single pass
+at the same time, thus making the algorithm much more efficient.
If the two highlighted elements are out of order, hit LEFT ARROW to swap
diff --git a/levels/quick_sort.gd b/levels/quick_sort.gd
index 44cc84e..4a1738e 100644
--- a/levels/quick_sort.gd
+++ b/levels/quick_sort.gd
@@ -2,18 +2,19 @@
QUICKSORT
-Quicksort designates the last element as the pivot and puts everything
-less than the pivot before it and everything greater after it. This
-partitioning is done by iterating through the array while keeping track
-of a pointer initially set to the first element. Every time an element
-less than the pivot is encountered, it is swapped with the pointed
-element and the pointer moves forward. At the end, the pointer and pivot
-are swapped, and the process is repeated on the left and right halves.
+Quicksort designates the last element as the pivot and sets a pointer to
+the first element. Then it iterates through the array. Every time an
+element smaller than the pivot is encountered, that element is swapped
+with the pointed element and the pointer is incremented. Once the pivot
+is reached, it is swapped with the pointed element and this process is
+recursively repeated on the left and right halves.
+
+Quicksort competes with other linearithmic algorithms like merge sort,
+which it is faster than at the tradeoff of stability.
If the highlighted element is less than the pivot or the pivot has been
-reached, press LEFT ARROW to swap it with the pointer. Otherwise, press
-RIGHT ARROW to move on.
+reached, press LEFT ARROW. Otherwise, press RIGHT ARROW.
"""
class_name QuickSort
diff --git a/levels/selection_sort.gd b/levels/selection_sort.gd
index 41d6fe0..85f08d2 100644
--- a/levels/selection_sort.gd
+++ b/levels/selection_sort.gd
@@ -2,16 +2,16 @@
SELECTION SORT
-Selection sort incrementally builds a sorted array by repeatedly looking
-for the smallest element and swapping it onto the end of the sorted
-portion of the array, which initially starts with size zero but grows
-after each round. It is faster than an unoptimized bubble sort but
-slower than insertion sort.
+Selection sort incrementally builds a sorted subarray by finding the
+smallest unprocessed element and putting it in place.
+It is not very useful in real life as it is beat by insertion sort.
+However, it has the distinguishing feature of making the least number
+of swaps in the worst case.
-Keep on hitting RIGHT ARROW until you encounter an element that is
-smaller than the left highlighted element, then hit LEFT ARROW and
-repeat.
+
+If the two highlighted elements are out of order, hit LEFT ARROW to swap
+them. Otherwise, hit RIGHT ARROW to continue.
"""
diff --git a/levels/shell_sort.gd b/levels/shell_sort.gd
index 015148f..db376c4 100644
--- a/levels/shell_sort.gd
+++ b/levels/shell_sort.gd
@@ -2,13 +2,18 @@
SHELL SORT
-Shell sort is a variation of insertion sort that sorts arrays separated
-by gaps.
+Shell sort is a variant of insertion sort that compares elements a
+certain gap apart instead of consecutive elements. This gap is divided
+by 2 after every pass. Once the gap becomes 1, shell sort becomes a
+regular insertion sort.
+This allows the final pass of insertion sort to avoid having to move
+elements long distances. However, it still has a quadratic worst case,
+which can be reduced with more complex gap sequences.
-Hit LEFT ARROW to swap the two highlighted elements as long as they are
-out of order. When this is no longer the case, hit RIGHT ARROW to
-advance.
+
+If the two highlighted elements are out of order, hit LEFT ARROW to swap
+them. Otherwise, hit RIGHT ARROW to continue.
"""
class_name ShellSort
diff --git a/scenes/levels.tscn b/scenes/levels.tscn
index fb53295..f673fbf 100644
--- a/scenes/levels.tscn
+++ b/scenes/levels.tscn
@@ -71,7 +71,9 @@ margin_top = 20.0
margin_right = 596.0
margin_bottom = 593.0
size_flags_vertical = 3
-text = "This is a description for the level in the form of psuedocode.
+text = "This is a description of the algorithm in plain English.
+
+This explains the relevance of the algorithm in computer science.
def algorithm(parameter):
return result"
From 2c6036e735e2663c5eb0e17de616ef313eae3721 Mon Sep 17 00:00:00 2001
From: Daniel Ting
Date: Thu, 10 Sep 2020 00:11:40 -0500
Subject: [PATCH 25/29] fix: make cycle sort work with duplicate elements
Some logic in cycle sort assumed a random permutation of elements.
---
levels/cycle_sort.gd | 5 ++++-
models/array_model.gd | 11 +++++++++++
2 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/levels/cycle_sort.gd b/levels/cycle_sort.gd
index 4cc2d2a..9f85968 100644
--- a/levels/cycle_sort.gd
+++ b/levels/cycle_sort.gd
@@ -54,8 +54,11 @@ func next(action):
return emit_signal("mistake")
_index += 1
if _index == array.size:
+ # Skip over duplicates to avoid infinite cycling
+ while _smaller != _pointer and array.at(_pointer) == array.at(_smaller):
+ _smaller += 1
array.swap(_pointer, _smaller)
- while array.at(_pointer) == _pointer + 1:
+ while array.is_in_place(_pointer):
_pointer += 1
if _pointer == array.size:
return emit_signal("done")
diff --git a/models/array_model.gd b/models/array_model.gd
index dba8c3c..dd4a824 100644
--- a/models/array_model.gd
+++ b/models/array_model.gd
@@ -87,6 +87,17 @@ func sort(i, j):
_array = front + sorted + back
emit_signal("sorted", i, j)
+func is_in_place(i):
+ """Check if the element at index i is in its correct place."""
+ var less = 0
+ var equal = 0
+ for element in _array:
+ if element < _array[i]:
+ less += 1
+ elif element == _array[i]:
+ equal += 1
+ return less <= i and i < less + equal
+
func get_size():
return _array.size()
From 8f72fbab1d4b4461bb20f698b383c0fcf550c8a4 Mon Sep 17 00:00:00 2001
From: Daniel Ting
Date: Thu, 10 Sep 2020 13:55:17 -0500
Subject: [PATCH 26/29] docs: fix bugs in pseudocode
---
levels/cycle_sort.gd | 19 +++++++++++--------
levels/merge_sort.gd | 14 ++++++++------
levels/quick_sort.gd | 3 +--
levels/selection_sort.gd | 2 +-
levels/shell_sort.gd | 2 +-
scripts/levels.gd | 2 +-
6 files changed, 23 insertions(+), 19 deletions(-)
diff --git a/levels/cycle_sort.gd b/levels/cycle_sort.gd
index 9f85968..aa6f704 100644
--- a/levels/cycle_sort.gd
+++ b/levels/cycle_sort.gd
@@ -10,8 +10,7 @@ demonstration, in the actual level, swaps are used instead.
This results in a quadratic runtime but gives it the special property
of being optimal in the number of writes to the array. This makes cycle
-sort useful in storage types where writes are very expensive or reduce
-its lifespan.
+sort useful in situations where writes are very expensive.
If the highlighted element is less than the element below the blue
@@ -25,13 +24,17 @@ const CODE = """
def cycle_sort(a):
for i in range(len(a)):
while True:
- position = 0
- for j in a:
- if a[j] > a[i]:
- position += 1
- if i == position:
+ less = equal = 0
+ for element in a:
+ if element < a[i]:
+ less += 1
+ elif element == a[i]:
+ equal += 1
+ if less <= i and i < less + equal:
break
- a.swap(i, position)
+ while a[i] == a[less]:
+ less += 1
+ a.swap(i, less)
"""
const ACTIONS = {
"SMALLER": "Left",
diff --git a/levels/merge_sort.gd b/levels/merge_sort.gd
index 8d5e82a..afb2ea7 100644
--- a/levels/merge_sort.gd
+++ b/levels/merge_sort.gd
@@ -26,13 +26,15 @@ def merge_sort(a):
i = begin
j = begin + size
while len(merged) != size * 2:
- if i >= begin + size or a[j] < a[i]:
- merged.append(a[j])
- j += 1
+ if i >= begin + size:
+ merged += a[j:begin + size * 2]
+ elif j >= begin + size * 2:
+ merged += a[i:begin + size]
+ elif a[i] < a[j]:
+ merged.append(a[i++])
else:
- merged.append(a[i])
- i += 1
- a[begin:begin + size] = merged
+ merged.append(a[j++])
+ a[begin:begin + size * 2] = merged
size *= 2
"""
const ACTIONS = {
diff --git a/levels/quick_sort.gd b/levels/quick_sort.gd
index 4a1738e..d0ccee7 100644
--- a/levels/quick_sort.gd
+++ b/levels/quick_sort.gd
@@ -23,10 +23,9 @@ extends ComparisonSort
const CODE = """
def quicksort(array, low=0, high=len(a) - 1):
if low < high:
- pivot = a[high]
pointer = low
for i in range(low, high):
- if a[i] < pivot:
+ if a[i] < a[high]:
a.swap(i, pointer)
pointer += 1
a.swap(pointer, high)
diff --git a/levels/selection_sort.gd b/levels/selection_sort.gd
index 85f08d2..2df5b2d 100644
--- a/levels/selection_sort.gd
+++ b/levels/selection_sort.gd
@@ -22,7 +22,7 @@ const CODE = """
def selection_sort(a):
for i in range(len(a)):
smallest = i
- for j in range(i, len(a)):
+ for j in range(i + 1, len(a)):
if a[j] < a[smallest]:
smallest = j
a.swap(i, smallest)
diff --git a/levels/shell_sort.gd b/levels/shell_sort.gd
index db376c4..9b5e722 100644
--- a/levels/shell_sort.gd
+++ b/levels/shell_sort.gd
@@ -27,7 +27,7 @@ def shell_sort(a):
for i in range(gap):
for j in range(i, len(a) - gap, gap):
k = j
- while k > gap and a[k - gap] > a[k]:
+ while k > i and a[k - gap] > a[k]:
a.swap(k - gap, k)
k -= gap
"""
diff --git a/scripts/levels.gd b/scripts/levels.gd
index 22d1378..d2be354 100644
--- a/scripts/levels.gd
+++ b/scripts/levels.gd
@@ -48,7 +48,7 @@ func _reload():
_restart()
_load_scores(_level)
$NamesContainer/Names/Current.text = _level.NAME
- $Level/Left/Code.text = _level.DESCRIPTION + "\n" + _level.CODE
+ $Level/Left/Code.text = _level.DESCRIPTION + "\n\n" + _level.CODE.strip_edges()
$Level/Right/Info/ControlsContainer/Controls.text = _level.CONTROLS
func _restart():
From 31a462bc0e9c298107460d39437a50eb95a80bc3 Mon Sep 17 00:00:00 2001
From: Daniel Ting
Date: Thu, 10 Sep 2020 00:14:20 -0500
Subject: [PATCH 27/29] feat: change sizes to 16, 32, 64 and default to 16
128 was just a bit too much for quadratic algorithms and 8 is pretty
pointless.
---
models/array_model.gd | 2 +-
scenes/levels.tscn | 8 ++++----
scripts/levels.gd | 8 ++++----
3 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/models/array_model.gd b/models/array_model.gd
index dd4a824..8784e65 100644
--- a/models/array_model.gd
+++ b/models/array_model.gd
@@ -5,7 +5,7 @@ signal removed(i)
signal swapped(i, j)
signal sorted(i, j)
-const DEFAULT_SIZE = 32
+const DEFAULT_SIZE = 16
enum DATA_TYPES {
RANDOM_UNIQUE,
TRUE_RANDOM,
diff --git a/scenes/levels.tscn b/scenes/levels.tscn
index f673fbf..56b6dc6 100644
--- a/scenes/levels.tscn
+++ b/scenes/levels.tscn
@@ -164,14 +164,14 @@ margin_right = 163.0
margin_bottom = 90.0
[node name="Sizes" type="Label" parent="Levels/Level/Right/Info/ScoresContainer/Scores/Data"]
-margin_right = 30.0
+margin_right = 20.0
margin_bottom = 63.0
-text = "8
+text = "16
32
-128"
+64"
[node name="Times" type="Label" parent="Levels/Level/Right/Info/ScoresContainer/Scores/Data"]
-margin_left = 38.0
+margin_left = 28.0
margin_right = 163.0
margin_bottom = 63.0
size_flags_horizontal = 3
diff --git a/scripts/levels.gd b/scripts/levels.gd
index d2be354..dbc5619 100644
--- a/scripts/levels.gd
+++ b/scripts/levels.gd
@@ -15,8 +15,8 @@ const LEVELS = [
const MIN_WAIT = 1.0 / 64
const MAX_WAIT = 4
-const MIN_SIZE = 8
-const MAX_SIZE = 128
+const MIN_SIZE = 16
+const MAX_SIZE = 64
var _index = LEVELS.find(GlobalScene.get_param("level", LEVELS[0]))
var _level: ComparisonSort
@@ -98,10 +98,10 @@ func _input(event):
if event.is_action_pressed("ui_right", true):
_switch_level(_index + 1)
if event.is_action_pressed("bigger"):
- _size = min(_size * 4, MAX_SIZE)
+ _size = min(_size * 2, MAX_SIZE)
_reload()
if event.is_action_pressed("smaller"):
- _size = max(_size / 4, MIN_SIZE)
+ _size = max(_size / 2, MIN_SIZE)
_reload()
if event.is_action_pressed("faster"):
$Timer.wait_time = max($Timer.wait_time / 4, MIN_WAIT)
From 4290f16c4344ed7434e59a329d290cebd0c460da Mon Sep 17 00:00:00 2001
From: Daniel Ting
Date: Thu, 10 Sep 2020 16:22:44 -0500
Subject: [PATCH 28/29] refactor: move level information into constants
This effectively undoes 7cdc05f99d1c1c30a97d4e27d06d20fa89cdb9ef, which
was honestly a dumb idea to begin with, and turned out to break
exports for some reason after doing a git bisect.
---
levels/bogo_sort.gd | 15 ++++++---------
levels/bubble_sort.gd | 15 ++++++---------
...cktail_sort.gd => cocktail_shaker_sort.gd} | 19 ++++++++-----------
levels/comb_sort.gd | 15 ++++++---------
levels/comparison_sort.gd | 6 ------
levels/cycle_sort.gd | 15 ++++++---------
levels/insertion_sort.gd | 15 ++++++---------
levels/merge_sort.gd | 15 ++++++---------
levels/odd_even_sort.gd | 15 ++++++---------
levels/quick_sort.gd | 15 ++++++---------
levels/selection_sort.gd | 16 ++++++----------
levels/shell_sort.gd | 15 ++++++---------
project.godot | 2 +-
scripts/levels.gd | 8 ++++++--
14 files changed, 75 insertions(+), 111 deletions(-)
rename levels/{cocktail_sort.gd => cocktail_shaker_sort.gd} (92%)
diff --git a/levels/bogo_sort.gd b/levels/bogo_sort.gd
index 0f8bd57..f9c5389 100644
--- a/levels/bogo_sort.gd
+++ b/levels/bogo_sort.gd
@@ -1,16 +1,13 @@
-"""
-BOGOSORT
-
+class_name BogoSort
+extends ComparisonSort
+const NAME = "BOGOSORT"
+const DESCRIPTION = """
Generates random permutations until the array is sorted.
-
-
+"""
+const CONTROLS = """
Keep on hitting RIGHT ARROW to CONTINUE and hope for the best!
"""
-
-class_name BogoSort
-extends ComparisonSort
-
const CODE = """
def bogosort(a):
while not a.sorted():
diff --git a/levels/bubble_sort.gd b/levels/bubble_sort.gd
index 08fa1ef..c4aeb1e 100644
--- a/levels/bubble_sort.gd
+++ b/levels/bubble_sort.gd
@@ -1,7 +1,8 @@
-"""
-BUBBLE SORT
-
+class_name BubbleSort
+extends ComparisonSort
+const NAME = "BUBBLE SORT"
+const DESCRIPTION = """
Bubble sort looks at consecutive pairs of elements and swaps them if
they are out of order, finishing when it has gone through the whole
array from beginning to end without a single swap. The actual level
@@ -12,15 +13,11 @@ Due to its simplicity, it is commonly taught as the first sorting
algorithm students learn in computer science classes, but is rarely used
in real life because it is slow on large data and other simple quadratic
algorithms like insertion sort perform better.
-
-
+"""
+const CONTROLS = """
If the two highlighted elements are out of order, hit LEFT ARROW to swap
them. Otherwise, hit RIGHT ARROW to continue.
"""
-
-class_name BubbleSort
-extends ComparisonSort
-
const CODE = """
def bubble_sort(a):
swapped = true
diff --git a/levels/cocktail_sort.gd b/levels/cocktail_shaker_sort.gd
similarity index 92%
rename from levels/cocktail_sort.gd
rename to levels/cocktail_shaker_sort.gd
index a564656..9bec6ee 100644
--- a/levels/cocktail_sort.gd
+++ b/levels/cocktail_shaker_sort.gd
@@ -1,24 +1,21 @@
-"""
-COCKTAIL SORT
-
+class_name CocktailSort
+extends ComparisonSort
-Cocktail sort is a variation of bubble sort that alternates going
+const NAME = "COCKTAIL SHAKER SORT"
+const DESCRIPTION = """
+Cocktail shaker sort is a variation of bubble sort that alternates going
backwards and forwards. The actual level contains an optimization that
skips over elements guaranteed to be already in place.
Because it is bidirectional, it is slightly faster than bubble sort, but
is still quadratic and therefore not used on large data.
-
-
+"""
+const CONTROLS = """
If the two highlighted elements are out of order, hit LEFT ARROW to swap
them. Otherwise, hit RIGHT ARROW to continue.
"""
-
-class_name CocktailSort
-extends ComparisonSort
-
const CODE = """
-def cocktail_sort(a):
+def cocktail_shaker_sort(a):
swapped = true
while swapped:
swapped = false
diff --git a/levels/comb_sort.gd b/levels/comb_sort.gd
index 0d37698..fdea047 100644
--- a/levels/comb_sort.gd
+++ b/levels/comb_sort.gd
@@ -1,7 +1,8 @@
-"""
-COMB SORT
-
+class_name CombSort
+extends ComparisonSort
+const NAME = "COMB SORT"
+const DESCRIPTION = """
Comb sort is a variant of bubble sort that compares elements a certain
gap apart instead of consecutive elements. This gap is divided after
every pass by an experimentally determined optimal factor of about 1.3.
@@ -10,15 +11,11 @@ Once the gap becomes 1, comb sort becomes a regular bubble sort.
This allows comb sort to get rid of small values near the end more
quickly, which turns out to be the bottleneck in bubble sort, but still
has a quadratic worst case.
-
-
+"""
+const CONTROLS = """
If the two highlighted elements are out of order, hit LEFT ARROW to swap
them. Otherwise, hit RIGHT ARROW to continue.
"""
-
-class_name CombSort
-extends ComparisonSort
-
const CODE = """
def comb_sort(a):
gap = len(a)
diff --git a/levels/comparison_sort.gd b/levels/comparison_sort.gd
index ca11b37..718add6 100644
--- a/levels/comparison_sort.gd
+++ b/levels/comparison_sort.gd
@@ -11,9 +11,6 @@ const EFFECTS = {
}
const DISABLE_TIME = 1.0
-var NAME = _get_header().split(" ")[0]
-var DESCRIPTION = _get_header().split(" ")[1].replace(" ", "\n\n")
-var CONTROLS = _get_header().split(" ")[-1]
var array: ArrayModel
@@ -28,9 +25,6 @@ func _init(array):
self.connect("mistake", self, "_on_ComparisonSort_mistake")
self.connect("done", self, "_on_ComparisonSort_done")
-func _get_header():
- return get_script().source_code.replace("\n", " ").split('"""')[1].strip_edges()
-
func _ready():
set_process_input(false)
diff --git a/levels/cycle_sort.gd b/levels/cycle_sort.gd
index aa6f704..e6f4b7d 100644
--- a/levels/cycle_sort.gd
+++ b/levels/cycle_sort.gd
@@ -1,7 +1,8 @@
-"""
-CYCLE SORT
-
+class_name CycleSort
+extends ComparisonSort
+const NAME = "CYCLE SORT"
+const DESCRIPTION = """
Cycle sort looks at the first element and finds its correct final
position by counting the number of elements smaller than it. Then it
saves the element at that index, writes the first element there, and
@@ -11,15 +12,11 @@ demonstration, in the actual level, swaps are used instead.
This results in a quadratic runtime but gives it the special property
of being optimal in the number of writes to the array. This makes cycle
sort useful in situations where writes are very expensive.
-
-
+"""
+const CONTROLS = """
If the highlighted element is less than the element below the blue
pointer, hit LEFT ARROW. Otherwise, hit RIGHT ARROW.
"""
-
-class_name CycleSort
-extends ComparisonSort
-
const CODE = """
def cycle_sort(a):
for i in range(len(a)):
diff --git a/levels/insertion_sort.gd b/levels/insertion_sort.gd
index 2da6fdc..80ab2bb 100644
--- a/levels/insertion_sort.gd
+++ b/levels/insertion_sort.gd
@@ -1,7 +1,8 @@
-"""
-INSERTION SORT
-
+class_name InsertionSort
+extends ComparisonSort
+const NAME = "INSERTION SORT"
+const DESCRIPTION = """
Insertion sort goes through the array and inserts each element into its
correct place, like how most people would sort a hand of playing cards.
@@ -9,15 +10,11 @@ It is one of the fastest quadratic algorithms in practice and is
efficient on small or almost sorted data. It is also simple, stable, and
in-place. For these reasons it is sometimes used within faster divide
and conquer algorithms when the array has been divided to a small size.
-
-
+"""
+const CONTROLS = """
If the two highlighted elements are out of order, hit LEFT ARROW to swap
them. Otherwise, hit RIGHT ARROW to continue.
"""
-
-class_name InsertionSort
-extends ComparisonSort
-
const CODE = """
def insertion_sort(a):
for i in range(len(a)):
diff --git a/levels/merge_sort.gd b/levels/merge_sort.gd
index afb2ea7..1b37a7b 100644
--- a/levels/merge_sort.gd
+++ b/levels/merge_sort.gd
@@ -1,21 +1,18 @@
-"""
-MERGE SORT
-
+class_name MergeSort
+extends ComparisonSort
+const NAME = "MERGE SORT"
+const DESCRIPTION = """
Merge sort merges subarrays of increasing size by setting a pointer to
the head of each half. Then it repeatedly copies the smaller pointed
element and increments that side's pointer. When one side is exhausted,
it copies the rest of the other side and overwrites the two halves with
the merged copy.
-
-
+"""
+const CONTROLS = """
Press the ARROW KEY corresponding to the side that the smaller
highlighted element is on or the non-exhausted side.
"""
-
-class_name MergeSort
-extends ComparisonSort
-
const CODE = """
def merge_sort(a):
size = 1
diff --git a/levels/odd_even_sort.gd b/levels/odd_even_sort.gd
index 54fe7f2..9d33b35 100644
--- a/levels/odd_even_sort.gd
+++ b/levels/odd_even_sort.gd
@@ -1,22 +1,19 @@
-"""
-ODD-EVEN SORT
-
+class_name OddEvenSort
+extends ComparisonSort
+const NAME = "ODD-EVEN SORT"
+const DESCRIPTION = """
Odd-even sort is a variant of bubble sort that alternates between
comparing consecutive odd-even and even-odd indexed pairs.
It is not of much use on a single processor as it is designed for
parallel processors, which can perform every comparison in a single pass
at the same time, thus making the algorithm much more efficient.
-
-
+"""
+const CONTROLS = """
If the two highlighted elements are out of order, hit LEFT ARROW to swap
them. Otherwise, hit RIGHT ARROW to continue.
"""
-
-class_name OddEvenSort
-extends ComparisonSort
-
const CODE = """
def odd_even_sort(a):
swapped = true
diff --git a/levels/quick_sort.gd b/levels/quick_sort.gd
index d0ccee7..96dd3c5 100644
--- a/levels/quick_sort.gd
+++ b/levels/quick_sort.gd
@@ -1,7 +1,8 @@
-"""
-QUICKSORT
-
+class_name QuickSort
+extends ComparisonSort
+const NAME = "QUICKSORT"
+const DESCRIPTION = """
Quicksort designates the last element as the pivot and sets a pointer to
the first element. Then it iterates through the array. Every time an
element smaller than the pivot is encountered, that element is swapped
@@ -11,15 +12,11 @@ recursively repeated on the left and right halves.
Quicksort competes with other linearithmic algorithms like merge sort,
which it is faster than at the tradeoff of stability.
-
-
+"""
+const CONTROLS = """
If the highlighted element is less than the pivot or the pivot has been
reached, press LEFT ARROW. Otherwise, press RIGHT ARROW.
"""
-
-class_name QuickSort
-extends ComparisonSort
-
const CODE = """
def quicksort(array, low=0, high=len(a) - 1):
if low < high:
diff --git a/levels/selection_sort.gd b/levels/selection_sort.gd
index 2df5b2d..21f5b24 100644
--- a/levels/selection_sort.gd
+++ b/levels/selection_sort.gd
@@ -1,23 +1,19 @@
-"""
-SELECTION SORT
-
+class_name SelectionSort
+extends ComparisonSort
+const NAME = "SELECTION SORT"
+const DESCRIPTION = """
Selection sort incrementally builds a sorted subarray by finding the
smallest unprocessed element and putting it in place.
It is not very useful in real life as it is beat by insertion sort.
However, it has the distinguishing feature of making the least number
of swaps in the worst case.
-
-
+"""
+const CONTROLS = """
If the two highlighted elements are out of order, hit LEFT ARROW to swap
them. Otherwise, hit RIGHT ARROW to continue.
"""
-
-
-class_name SelectionSort
-extends ComparisonSort
-
const CODE = """
def selection_sort(a):
for i in range(len(a)):
diff --git a/levels/shell_sort.gd b/levels/shell_sort.gd
index 9b5e722..4d14679 100644
--- a/levels/shell_sort.gd
+++ b/levels/shell_sort.gd
@@ -1,7 +1,8 @@
-"""
-SHELL SORT
-
+class_name ShellSort
+extends ComparisonSort
+const NAME = "SHELL SORT"
+const DESCRIPTION = """
Shell sort is a variant of insertion sort that compares elements a
certain gap apart instead of consecutive elements. This gap is divided
by 2 after every pass. Once the gap becomes 1, shell sort becomes a
@@ -10,15 +11,11 @@ regular insertion sort.
This allows the final pass of insertion sort to avoid having to move
elements long distances. However, it still has a quadratic worst case,
which can be reduced with more complex gap sequences.
-
-
+"""
+const CONTROLS = """
If the two highlighted elements are out of order, hit LEFT ARROW to swap
them. Otherwise, hit RIGHT ARROW to continue.
"""
-
-class_name ShellSort
-extends ComparisonSort
-
const CODE = """
def shell_sort(a):
gap = len(a)
diff --git a/project.godot b/project.godot
index d5d4f0b..b8afbe2 100644
--- a/project.godot
+++ b/project.godot
@@ -42,7 +42,7 @@ _global_script_classes=[ {
"base": "ComparisonSort",
"class": "CocktailSort",
"language": "GDScript",
-"path": "res://levels/cocktail_sort.gd"
+"path": "res://levels/cocktail_shaker_sort.gd"
}, {
"base": "ComparisonSort",
"class": "CombSort",
diff --git a/scripts/levels.gd b/scripts/levels.gd
index dbc5619..f1a5345 100644
--- a/scripts/levels.gd
+++ b/scripts/levels.gd
@@ -48,8 +48,12 @@ func _reload():
_restart()
_load_scores(_level)
$NamesContainer/Names/Current.text = _level.NAME
- $Level/Left/Code.text = _level.DESCRIPTION + "\n\n" + _level.CODE.strip_edges()
- $Level/Right/Info/ControlsContainer/Controls.text = _level.CONTROLS
+ $Level/Left/Code.text = _format(_level.DESCRIPTION) + "\n\n" + _level.CODE.strip_edges()
+ $Level/Right/Info/ControlsContainer/Controls.text = _format(_level.CONTROLS)
+
+func _format(text):
+ # Helper method to format text
+ return text.strip_edges().replace("\n", " ").replace(" ", "\n\n")
func _restart():
set_process_input(true)
From 5abdf55b42173d79d5b67e2e0e22bdccc3d4fef7 Mon Sep 17 00:00:00 2001
From: Daniel Ting
Date: Thu, 10 Sep 2020 17:13:20 -0500
Subject: [PATCH 29/29] chore: update README
---
CONTRIBUTING.md | 4 ----
README.md | 28 ++++++++--------------------
assets/levels.png | Bin 175432 -> 207222 bytes
3 files changed, 8 insertions(+), 24 deletions(-)
delete mode 100644 CONTRIBUTING.md
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
deleted file mode 100644
index 3776231..0000000
--- a/CONTRIBUTING.md
+++ /dev/null
@@ -1,4 +0,0 @@
-As I would like to submit this project to the Congressional App
-Challenge, I unfortunately cannot accept contributions until Monday,
-October 19th, 2020 at the earliest. Kindly hold any issues and pull
-requests until then.
diff --git a/README.md b/README.md
index 202c7a1..195c113 100644
--- a/README.md
+++ b/README.md
@@ -1,35 +1,23 @@
-
+
Human Computer Simulator
- A game where you get to become your favorite algorithm or data structure!
-
- Report Bug
- ·
- Request Feature
+ Become your favorite sorting algorithm!
-## Table of Contents
-
-* [About the Project](#about-the-project)
-* [Getting Started](#getting-started)
-
-## About The Project
-
![Level select screen](assets/levels.png)
+Have you ever wondered what it feels like to be a sorting algorithm‽ Now you can find out! In *Human Computer Simulator*, you control an algorithm operating on an array, trying to sort as fast as possible. With 10 different levels, a cool visualization, and optional sound effects, you can fulfill your sorting dreams...
-You may have come across the famous [15 Sorting Algorithms in 6 Minutes](https://www.youtube.com/watch?v=kPRA0W1kECg) video by [Timo Bingmann](https://github.com/bingmann) at some point in your computer science career. There is currently no shortage of neat visualizations of all kinds of algorithms, but what if you could become the algorithm itself?
-
-In *Human Computer Simulator*, you control an algorithm operating on some data structure. Right now, the game is limited to sorting arrays. The end vision is to have a library of interactive, playable levels on anything from a search on a binary tree to Dijkstra's shortest path on a graph.
+A demo (large download warning: ~5 MB gzipped) is available on this repository's [Github Pages](https://danielzting.github.io/human-computer-simulator). It requires a desktop browser with support for WebAssembly and WebGL.
-It's written using the Godot game engine and licensed with [almost no restrictions](LICENSE.txt). Use it to make a lecture a bit more interesting, review for an assignment, or just kill time. Hope you enjoy.
+## Anti-pitch
-## Getting Started
+This is beta software, so there will inevitably be things that are confusing, broken, or straight up wrong. See the [issues](https://github.com/DanielZTing/human-computer-simulator/issues) for a list of caveats and don't hesitate to open another one if you find something new.
-This software is in an alpha stage of development and I do not plan on releasing ready-to-run builds until a stable v1.0 release. However, it is very easy to run and hack the source code yourself. Just grab the lightweight free and open source [Godot game engine](https://godotengine.org/download), import the `project.godot` file, and hit the play button.
+## Contributing
-A demo version (large download warning: ~20 MB) is available on this repository's [Github Pages](https://danielzting.github.io/human-computer-simulator). It requires a desktop browser with support for WebAssembly and WebGL; mobile is not currently supported. Since this is still in alpha, some things might be dumb, make no sense whatsoever, or just break outright. I welcome any feedback you may have.
+I welcome any bug reports or feature requests. Pull requests are appreciated as well, though I apologize for my code in advance. If you like this so much that you want to [throw money at me](https://venmo.com/DanielZTing), I will add any 80x24 ASCII text block of your choosing to the README and credits.
\ No newline at end of file
diff --git a/assets/levels.png b/assets/levels.png
index 46dddf178223227b6607c45fe21f2c4b76cdf3d5..2e9595d4ea0eb7147926871f255ceb7d9b651f8e 100644
GIT binary patch
literal 207222
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