diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..f3d5c41
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,38 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: bug
+assignees: ''
+
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+1. Go to '...'
+2. Click on '....'
+3. Scroll down to '....'
+4. See error
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Screenshots**
+If applicable, add screenshots to help explain your problem.
+
+**Desktop (please complete the following information):**
+ - OS: [e.g. iOS]
+ - Browser [e.g. chrome, safari]
+ - Version [e.g. 22]
+
+**Smartphone (please complete the following information):**
+ - Device: [e.g. iPhone6]
+ - OS: [e.g. iOS8.1]
+ - Browser [e.g. stock browser, safari]
+ - Version [e.g. 22]
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..11fc491
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,20 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: enhancement
+assignees: ''
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..3776231
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,4 @@
+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 9fa6671..49e3a78 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,35 @@
-# Human Computer Simulator
-*Human Computer Simulator* is a game where you get to become your
-favorite algorithm and or data structure.
-
-# Screenshots
-![Level Select](assets/levels.png)
-
-# Download
-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 it yourself. Just grab the free and open source
-[Godot game engine](https://godotengine.org), import the `project.godot`
-file, and hit the play button.
+
+
+
+
+
Human Computer Simulator
+
+
+ A game where you get to become your favorite algorithm or data structure!
+
+ Report Bug
+ ยท
+ Request Feature
+
+
+
+## Table of Contents
+
+* [About the Project](#about-the-project)
+* [Getting Started](#getting-started)
+
+## About The Project
+
+![Level select screen](assets/levels.png)
+
+You may have come across the famous [15 Sorting Algorithms in 6 Minutes](https://www.youtube.com/watch?v=kPRA0W1kECg) video by [Timo Bingoman](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.
+
+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.
+
+## Getting Started
+
+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.
+
+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.
diff --git a/assets/levels.png b/assets/levels.png
index b269675..46dddf1 100644
Binary files a/assets/levels.png and b/assets/levels.png differ
diff --git a/assets/theme.theme b/assets/theme.theme
index 8d9e5b5..a324e0e 100644
Binary files a/assets/theme.theme and b/assets/theme.theme differ
diff --git a/levels/bubble_sort.gd b/levels/bubble_sort.gd
index e092db1..9aaaa5d 100644
--- a/levels/bubble_sort.gd
+++ b/levels/bubble_sort.gd
@@ -14,6 +14,10 @@ If the two highlighted elements are out of order, hit LEFT ARROW to swap
them. Otherwise, hit RIGHT ARROW to continue.
"""
+const ACTIONS = {
+ "SWAP": "Left",
+ "CONTINUE": "Right",
+}
var _index = 0 # First of two elements being compared
var _end = array.size # Beginning of sorted subarray
var _swapped = false
@@ -27,7 +31,7 @@ func next(action):
return emit_signal("mistake")
array.swap(_index, _index + 1)
_swapped = true
- elif action != null and action != ACTIONS.NO_SWAP:
+ elif action != null and action != ACTIONS.CONTINUE:
return emit_signal("mistake")
_index += 1
# Prevent player from having to spam tap through the end
diff --git a/levels/comparison_sort.gd b/levels/comparison_sort.gd
index db4cbf4..3156df0 100644
--- a/levels/comparison_sort.gd
+++ b/levels/comparison_sort.gd
@@ -4,14 +4,6 @@ extends Node
signal done
signal mistake
-const ACTIONS = {
- "SWAP": "ui_left",
- "NO_SWAP": "ui_right",
-
- "LEFT": "ui_left",
- "RIGHT": "ui_right",
-}
-
const EFFECTS = {
"NONE": GlobalTheme.GREEN,
"HIGHLIGHTED": GlobalTheme.ORANGE,
@@ -22,6 +14,8 @@ const DISABLE_TIME = 1.0
var array: ArrayModel
var active = true
+var done = false
+var moves = 0
var _timer = Timer.new()
@@ -32,19 +26,29 @@ func _init(array):
_timer.connect("timeout", self, "_on_Timer_timeout")
add_child(_timer)
self.connect("mistake", self, "_on_ComparisonSort_mistake")
+ self.connect("done", self, "_on_ComparisonSort_done")
func _input(event):
"""Pass input events for checking and take appropriate action."""
- if not active:
+ if done or not active:
return
- for action in ACTIONS.values():
- if event.is_action_pressed(action):
- return next(action)
+ if event.is_pressed():
+ moves += 1
+ return next(event.as_text())
func next(action):
"""Check the action and advance state or emit signal as needed."""
push_error("NotImplementedError")
+func get_effect(i):
+ return get_effect(i)
+
+func _get_effect(i):
+ push_error("NotImplementedError")
+
+func _on_ComparisonSort_done():
+ done = true
+
func _on_ComparisonSort_mistake():
"""Disable the controls for one second."""
active = false
diff --git a/levels/insertion_sort.gd b/levels/insertion_sort.gd
index c217d32..c1eec73 100644
--- a/levels/insertion_sort.gd
+++ b/levels/insertion_sort.gd
@@ -15,6 +15,10 @@ out of order. When this is no longer the case, hit RIGHT ARROW to
advance.
"""
+const ACTIONS = {
+ "SWAP": "Left",
+ "CONTINUE": "Right",
+}
var _end = 1 # Size of the sorted subarray
var _index = 1 # Position of element currently being inserted
@@ -30,14 +34,14 @@ func next(action):
if _index == 0:
_grow()
else:
- if action != null and action != ACTIONS.NO_SWAP:
+ if action != null and action != ACTIONS.CONTINUE:
return emit_signal("mistake")
_grow()
func get_effect(i):
if i == _index or i == _index - 1:
return EFFECTS.HIGHLIGHTED
- if i < _end:
+ if i <= _end:
return EFFECTS.DIMMED
return EFFECTS.NONE
diff --git a/levels/merge_sort.gd b/levels/merge_sort.gd
index 62e6594..4e2d511 100644
--- a/levels/merge_sort.gd
+++ b/levels/merge_sort.gd
@@ -15,6 +15,10 @@ highlighted element is on. If you've reached the end of one side, press
the other side's ARROW KEY.
"""
+const ACTIONS = {
+ "LEFT": "Left",
+ "RIGHT": "Right",
+}
var _left = 0 # Index of left subarray pointer
var _right = 1 # Index of right subarray pointer
var _sub_size = 2 # Combined size of left and right subarrays
@@ -27,18 +31,22 @@ func next(action):
if _left == _get_middle():
if action != null and action != ACTIONS.RIGHT:
return emit_signal("mistake")
+ array.emit_signal("removed", _right)
_right += 1
elif _right == _get_end():
if action != null and action != ACTIONS.LEFT:
return emit_signal("mistake")
+ array.emit_signal("removed", _left)
_left += 1
elif array.at(_left) <= array.at(_right):
if action != null and action != ACTIONS.LEFT:
return emit_signal("mistake")
+ array.emit_signal("removed", _left)
_left += 1
else:
if action != null and action != ACTIONS.RIGHT:
return emit_signal("mistake")
+ array.emit_signal("removed", _right)
_right += 1
# If both ends have been reached, merge and advance to next block
if _left == _get_middle() and _right == _get_end():
diff --git a/levels/quick_sort.gd b/levels/quick_sort.gd
new file mode 100644
index 0000000..3859d7f
--- /dev/null
+++ b/levels/quick_sort.gd
@@ -0,0 +1,79 @@
+class_name QuickSort
+extends ComparisonSort
+
+const NAME = "QUICKSORT"
+const ABOUT = """
+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.
+"""
+const CONTROLS = """
+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.
+"""
+
+const ACTIONS = {
+ "SWAP": "Left",
+ "CONTINUE": "Right",
+}
+var _index = 0
+var _pointer = 0
+# Bookkeep simulated recursion with a binary tree of subarray bounds
+var _stack = BinaryTreeModel.new(Vector2(0, array.size - 1))
+
+func _init(array).(array):
+ pass
+
+func next(action):
+ if _index == _pivot():
+ if action != null and action != ACTIONS.SWAP:
+ return emit_signal("mistake")
+ array.swap(_index, _pointer)
+ if _pointer - _begin() > 1:
+ _stack.left = BinaryTreeModel.new(Vector2(_begin(), _pointer - 1))
+ _stack = _stack.left
+ elif _pivot() - _pointer > 1:
+ _stack.right = BinaryTreeModel.new(Vector2(_pointer + 1, _pivot()))
+ _stack = _stack.right
+ else:
+ while (_stack.parent.right != null
+ or _stack.parent.left.value.y + 2 >= _stack.parent.value.y):
+ _stack = _stack.parent
+ if _stack.parent == null:
+ return emit_signal("done")
+ _stack.parent.right = BinaryTreeModel.new(Vector2(
+ _stack.parent.left.value.y + 2, _stack.parent.value.y))
+ _stack = _stack.parent.right
+ _index = _begin()
+ _pointer = _index
+ elif array.at(_index) < array.at(_pivot()):
+ if action != null and action != ACTIONS.SWAP:
+ return emit_signal("mistake")
+ array.swap(_index, _pointer)
+ _index += 1
+ _pointer += 1
+ else:
+ if action != null and action != ACTIONS.CONTINUE:
+ return emit_signal("mistake")
+ _index += 1
+
+func _begin():
+ return _stack.value.x
+
+func _pivot():
+ return _stack.value.y
+
+func get_effect(i):
+ if i < _begin() or i > _pivot():
+ return EFFECTS.DIMMED
+ if i == _index or i == _pivot():
+ return EFFECTS.HIGHLIGHTED
+ return EFFECTS.NONE
+
+func get_pointer():
+ return _pointer
diff --git a/levels/selection_sort.gd b/levels/selection_sort.gd
index 075a53d..9e5f237 100644
--- a/levels/selection_sort.gd
+++ b/levels/selection_sort.gd
@@ -15,6 +15,10 @@ smaller than the left highlighted element, then hit LEFT ARROW and
repeat.
"""
+const ACTIONS = {
+ "SWAP": "Left",
+ "CONTINUE": "Right",
+}
var _base = 0 # Size of sorted subarray
var _min = 0 # Index of smallest known element
var _index = 1 # Element currently being compared
@@ -27,7 +31,7 @@ func next(action):
if action != null and action != ACTIONS.SWAP:
return emit_signal("mistake")
_min = _index
- elif action != null and action != ACTIONS.NO_SWAP:
+ elif action != null and action != ACTIONS.CONTINUE:
return emit_signal("mistake")
_index += 1
if _index == array.size:
@@ -44,3 +48,6 @@ func get_effect(i):
if i < _base:
return EFFECTS.DIMMED
return EFFECTS.NONE
+
+func get_pointer():
+ return _min
diff --git a/models/array_model.gd b/models/array_model.gd
index b237231..bd038c8 100644
--- a/models/array_model.gd
+++ b/models/array_model.gd
@@ -1,38 +1,55 @@
class_name ArrayModel
extends Reference
-var array = []
-var size = 0
+# For all parameterized signals, i <= j
+signal removed(i)
+signal swapped(i, j)
+signal sorted(i, j)
-func _init(size=16):
+const DEFAULT_SIZE = 16
+
+var _array = []
+var size = 0 setget , get_size
+var biggest = null
+
+func _init(size=DEFAULT_SIZE):
"""Randomize the array."""
for i in range(1, size + 1):
- array.append(i)
- array.shuffle()
- self.size = array.size()
+ _array.append(i)
+ _array.shuffle()
+ biggest = _array.max()
func at(i):
"""Retrieve the value of the element at index i."""
- return array[i]
+ return _array[i]
+
+func frac(i):
+ """Get the quotient of the element at index i and the biggest."""
+ return float(_array[i]) / biggest
func is_sorted():
"""Check if the array is in monotonically increasing order."""
- for i in range(size - 1):
- if array[i] > array[i + 1]:
+ for i in range(get_size() - 1):
+ if _array[i] > _array[i + 1]:
return false
return true
func swap(i, j):
"""Swap the elements at indices i and j."""
- var temp = array[i]
- array[i] = array[j]
- array[j] = temp
+ var temp = _array[i]
+ _array[i] = _array[j]
+ _array[j] = temp
+ emit_signal("swapped", min(i, j), max(i, j))
func sort(i, j):
"""Sort the subarray starting at i and up to but not including j."""
# Grr to the developer who made the upper bound inclusive
- var front = array.slice(0, i - 1) if i != 0 else []
- var sorted = array.slice(i, j - 1)
+ var front = _array.slice(0, i - 1) if i != 0 else []
+ var sorted = _array.slice(i, j - 1)
sorted.sort()
- var back = array.slice(j, size - 1) if j != size else []
- array = front + sorted + back
+ var back = _array.slice(j, size - 1) if j != size else []
+ _array = front + sorted + back
+ emit_signal("sorted", i, j)
+
+func get_size():
+ return _array.size()
diff --git a/models/binary_tree_model.gd b/models/binary_tree_model.gd
new file mode 100644
index 0000000..4eb5a5f
--- /dev/null
+++ b/models/binary_tree_model.gd
@@ -0,0 +1,18 @@
+class_name BinaryTreeModel
+extends Reference
+
+var left: BinaryTreeModel setget set_left
+var right: BinaryTreeModel setget set_right
+var parent: BinaryTreeModel
+var value = null
+
+func _init(value):
+ self.value = value
+
+func set_left(child: BinaryTreeModel):
+ child.parent = self
+ left = child
+
+func set_right(child: BinaryTreeModel):
+ child.parent = self
+ right = child
diff --git a/project.godot b/project.godot
index 328acbe..c470a97 100644
--- a/project.godot
+++ b/project.godot
@@ -14,11 +14,16 @@ _global_script_classes=[ {
"language": "GDScript",
"path": "res://models/array_model.gd"
}, {
-"base": "HBoxContainer",
+"base": "ViewportContainer",
"class": "ArrayView",
"language": "GDScript",
"path": "res://views/array_view.gd"
}, {
+"base": "Reference",
+"class": "BinaryTreeModel",
+"language": "GDScript",
+"path": "res://models/binary_tree_model.gd"
+}, {
"base": "ComparisonSort",
"class": "BogoSort",
"language": "GDScript",
@@ -45,6 +50,16 @@ _global_script_classes=[ {
"path": "res://levels/merge_sort.gd"
}, {
"base": "ComparisonSort",
+"class": "QuickSort",
+"language": "GDScript",
+"path": "res://levels/quick_sort.gd"
+}, {
+"base": "Reference",
+"class": "Score",
+"language": "GDScript",
+"path": "res://scripts/score.gd"
+}, {
+"base": "ComparisonSort",
"class": "SelectionSort",
"language": "GDScript",
"path": "res://levels/selection_sort.gd"
@@ -52,11 +67,14 @@ _global_script_classes=[ {
_global_script_class_icons={
"ArrayModel": "",
"ArrayView": "",
+"BinaryTreeModel": "",
"BogoSort": "",
"BubbleSort": "",
"ComparisonSort": "",
"InsertionSort": "",
"MergeSort": "",
+"QuickSort": "",
+"Score": "",
"SelectionSort": ""
}
@@ -64,7 +82,6 @@ _global_script_class_icons={
config/name="Human Computer Simulator"
run/main_scene="res://scenes/menu.tscn"
-run/low_processor_mode=true
boot_splash/image="res://assets/splash.png"
config/icon="res://assets/icon.png"
@@ -75,9 +92,8 @@ GlobalTheme="*res://scripts/theme.gd"
[display]
-window/size/width=1920
-window/size/height=1080
-window/size/fullscreen=true
+window/size/width=1280
+window/size/height=720
window/size/always_on_top=true
window/dpi/allow_hidpi=true
window/stretch/mode="2d"
@@ -125,24 +141,24 @@ ui_down={
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":13,"pressure":0.0,"pressed":false,"script":null)
]
}
-SWAP={
+faster={
"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":16777231,"unicode":0,"echo":false,"script":null)
+"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":68,"unicode":0,"echo":false,"script":null)
]
}
-NO_SWAP={
+slower={
"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":16777233,"unicode":0,"echo":false,"script":null)
+"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":65,"unicode":0,"echo":false,"script":null)
]
}
-LEFT={
+bigger={
"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":16777231,"unicode":0,"echo":false,"script":null)
+"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":87,"unicode":0,"echo":false,"script":null)
]
}
-RIGHT={
+smaller={
"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":16777233,"unicode":0,"echo":false,"script":null)
+"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)
]
}
diff --git a/scenes/end.tscn b/scenes/end.tscn
deleted file mode 100644
index 39e2f70..0000000
--- a/scenes/end.tscn
+++ /dev/null
@@ -1,37 +0,0 @@
-[gd_scene load_steps=3 format=2]
-
-[ext_resource path="res://scripts/end.gd" type="Script" id=1]
-[ext_resource path="res://assets/theme.theme" type="Theme" id=2]
-
-[node name="Viewport" type="MarginContainer"]
-anchor_top = 0.5
-anchor_right = 1.0
-anchor_bottom = 0.5
-margin_top = -62.0
-margin_bottom = 62.0
-theme = ExtResource( 2 )
-__meta__ = {
-"_edit_use_anchors_": false
-}
-
-[node name="EndScreen" type="VBoxContainer" parent="."]
-margin_left = 20.0
-margin_top = 20.0
-margin_right = 1900.0
-margin_bottom = 104.0
-script = ExtResource( 1 )
-
-[node name="Score" type="Label" parent="EndScreen"]
-margin_right = 1880.0
-margin_bottom = 38.0
-size_flags_vertical = 1
-text = "SCORE"
-align = 1
-valign = 2
-
-[node name="Button" type="Button" parent="EndScreen"]
-margin_top = 46.0
-margin_right = 1880.0
-margin_bottom = 84.0
-text = "restart"
-[connection signal="pressed" from="EndScreen/Button" to="EndScreen" method="_on_Button_pressed"]
diff --git a/scenes/levels.tscn b/scenes/levels.tscn
index f36be90..584fa7f 100644
--- a/scenes/levels.tscn
+++ b/scenes/levels.tscn
@@ -5,9 +5,8 @@
[ext_resource path="res://scripts/border.gd" type="Script" id=3]
[node name="Viewport" type="MarginContainer"]
-anchor_top = 0.00114754
anchor_right = 1.0
-anchor_bottom = 1.00115
+anchor_bottom = 1.0
theme = ExtResource( 2 )
custom_constants/margin_right = 30
custom_constants/margin_top = 30
@@ -20,66 +19,88 @@ __meta__ = {
[node name="LevelSelect" type="HBoxContainer" parent="."]
margin_left = 30.0
margin_top = 30.0
-margin_right = 1890.0
-margin_bottom = 1050.0
+margin_right = 1250.0
+margin_bottom = 690.0
script = ExtResource( 1 )
[node name="LevelsBorder" type="MarginContainer" parent="LevelSelect"]
-margin_right = 480.0
-margin_bottom = 1020.0
-rect_min_size = Vector2( 480, 0 )
+margin_right = 303.0
+margin_bottom = 660.0
+size_flags_horizontal = 3
script = ExtResource( 3 )
[node name="Levels" type="VBoxContainer" parent="LevelSelect/LevelsBorder"]
margin_left = 20.0
margin_top = 20.0
-margin_right = 460.0
-margin_bottom = 1000.0
+margin_right = 283.0
+margin_bottom = 640.0
+
+[node name="LevelsContainer" type="HBoxContainer" parent="LevelSelect/LevelsBorder/Levels"]
+margin_right = 263.0
+
+[node name="Buttons" type="VBoxContainer" parent="LevelSelect/LevelsBorder/Levels/LevelsContainer"]
+margin_right = 255.0
+size_flags_horizontal = 3
+
+[node name="Scores" type="VBoxContainer" parent="LevelSelect/LevelsBorder/Levels/LevelsContainer"]
+margin_left = 263.0
+margin_right = 263.0
+
+[node name="Label" type="Label" parent="LevelSelect/LevelsBorder"]
+margin_left = 20.0
+margin_top = 577.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."
+autowrap = true
[node name="Preview" type="VBoxContainer" parent="LevelSelect"]
-margin_left = 488.0
-margin_right = 1860.0
-margin_bottom = 1020.0
+margin_left = 311.0
+margin_right = 1220.0
+margin_bottom = 660.0
size_flags_horizontal = 3
+size_flags_stretch_ratio = 3.0
[node name="Display" type="MarginContainer" parent="LevelSelect/Preview"]
-margin_right = 1372.0
-margin_bottom = 640.0
-rect_min_size = Vector2( 0, 640 )
+margin_right = 909.0
+margin_bottom = 434.0
+size_flags_vertical = 3
+size_flags_stretch_ratio = 2.0
script = ExtResource( 3 )
[node name="InfoBorder" type="MarginContainer" parent="LevelSelect/Preview"]
-margin_top = 648.0
-margin_right = 1372.0
-margin_bottom = 1020.0
+margin_top = 442.0
+margin_right = 909.0
+margin_bottom = 660.0
size_flags_vertical = 3
script = ExtResource( 3 )
[node name="Info" type="HBoxContainer" parent="LevelSelect/Preview/InfoBorder"]
margin_left = 20.0
margin_top = 20.0
-margin_right = 1352.0
-margin_bottom = 352.0
+margin_right = 889.0
+margin_bottom = 198.0
custom_constants/separation = 50
[node name="About" type="Label" parent="LevelSelect/Preview/InfoBorder/Info"]
-margin_right = 810.0
-margin_bottom = 332.0
-rect_min_size = Vector2( 810, 0 )
+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."
autowrap = true
[node name="Controls" type="Label" parent="LevelSelect/Preview/InfoBorder/Info"]
-margin_left = 860.0
-margin_right = 1332.0
-margin_bottom = 332.0
-rect_min_size = Vector2( 450, 0 )
+margin_left = 596.0
+margin_right = 869.0
+margin_bottom = 178.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
-[node name="Timer" type="Timer" parent="."]
+[node name="Timer" type="Timer" parent="LevelSelect"]
autostart = true
-[connection signal="timeout" from="Timer" to="LevelSelect" method="_on_Timer_timeout"]
+[connection signal="timeout" from="LevelSelect/Timer" to="LevelSelect" method="_on_Timer_timeout"]
diff --git a/scenes/menu.tscn b/scenes/menu.tscn
index 55de3e5..0c692e5 100644
--- a/scenes/menu.tscn
+++ b/scenes/menu.tscn
@@ -21,20 +21,20 @@ __meta__ = {
[node name="MainMenu" type="VBoxContainer" parent="."]
margin_left = 40.0
margin_top = 30.0
-margin_right = 1879.0
-margin_bottom = 1049.0
+margin_right = 1239.0
+margin_bottom = 689.0
script = ExtResource( 1 )
[node name="Title" type="Label" parent="MainMenu"]
-margin_right = 1839.0
-margin_bottom = 38.0
+margin_right = 1199.0
+margin_bottom = 19.0
text = "Human Computer Simulator"
uppercase = true
[node name="Display" type="MarginContainer" parent="MainMenu"]
-margin_top = 46.0
-margin_right = 1839.0
-margin_bottom = 965.0
+margin_top = 27.0
+margin_right = 1199.0
+margin_bottom = 624.0
size_flags_vertical = 3
script = ExtResource( 3 )
__meta__ = {
@@ -42,30 +42,30 @@ __meta__ = {
}
[node name="Spacing" type="Control" parent="MainMenu"]
-margin_top = 973.0
-margin_right = 1839.0
-margin_bottom = 973.0
+margin_top = 632.0
+margin_right = 1199.0
+margin_bottom = 632.0
[node name="Buttons" type="HBoxContainer" parent="MainMenu"]
-margin_left = 555.0
-margin_top = 981.0
-margin_right = 1283.0
-margin_bottom = 1019.0
+margin_left = 289.0
+margin_top = 640.0
+margin_right = 909.0
+margin_bottom = 659.0
size_flags_horizontal = 4
custom_constants/separation = 500
[node name="Start" type="Button" parent="MainMenu/Buttons"]
-margin_right = 95.0
-margin_bottom = 38.0
+margin_right = 50.0
+margin_bottom = 19.0
size_flags_horizontal = 4
-text = "start"
+text = "START"
flat = true
[node name="Credits" type="Button" parent="MainMenu/Buttons"]
-margin_left = 595.0
-margin_right = 728.0
-margin_bottom = 38.0
-text = "credits"
+margin_left = 550.0
+margin_right = 620.0
+margin_bottom = 19.0
+text = "CREDITS"
[node name="Timer" type="Timer" parent="."]
wait_time = 0.25
diff --git a/scenes/play.tscn b/scenes/play.tscn
index 681b29e..ba04115 100644
--- a/scenes/play.tscn
+++ b/scenes/play.tscn
@@ -19,51 +19,51 @@ __meta__ = {
[node name="GameDisplay" type="VBoxContainer" parent="."]
margin_left = 30.0
margin_top = 30.0
-margin_right = 1890.0
-margin_bottom = 1050.0
+margin_right = 1250.0
+margin_bottom = 690.0
script = ExtResource( 1 )
[node name="HUDBorder" type="MarginContainer" parent="GameDisplay"]
-margin_right = 1860.0
-margin_bottom = 78.0
+margin_right = 1220.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 = 1840.0
-margin_bottom = 58.0
+margin_right = 1200.0
+margin_bottom = 39.0
[node name="Level" type="Label" parent="GameDisplay/HUDBorder/HUD"]
-margin_right = 906.0
-margin_bottom = 38.0
+margin_right = 586.0
+margin_bottom = 19.0
size_flags_horizontal = 3
text = "LEVEL"
[node name="Score" type="Label" parent="GameDisplay/HUDBorder/HUD"]
-margin_left = 914.0
-margin_right = 1820.0
-margin_bottom = 38.0
+margin_left = 594.0
+margin_right = 1180.0
+margin_bottom = 19.0
size_flags_horizontal = 3
text = "0.000"
align = 2
-[node name="DisplayBorder" type="MarginContainer" parent="GameDisplay"]
-margin_top = 86.0
-margin_right = 1860.0
-margin_bottom = 1020.0
+[node name="Display" type="MarginContainer" parent="GameDisplay"]
+margin_top = 67.0
+margin_right = 1220.0
+margin_bottom = 660.0
size_flags_vertical = 3
script = ExtResource( 3 )
-[node name="Label" type="Label" parent="GameDisplay/DisplayBorder"]
+[node name="Label" type="Label" parent="GameDisplay/Display"]
margin_left = 20.0
-margin_top = 448.0
-margin_right = 1840.0
-margin_bottom = 486.0
+margin_top = 287.0
+margin_right = 1200.0
+margin_bottom = 306.0
text = "ready..."
align = 1
-[node name="Timer" type="Timer" parent="."]
+[node name="Timer" type="Timer" parent="GameDisplay"]
one_shot = true
autostart = true
-[connection signal="timeout" from="Timer" to="GameDisplay" method="_on_Timer_timeout"]
+[connection signal="timeout" from="GameDisplay/Timer" to="GameDisplay" method="_on_Timer_timeout"]
diff --git a/scripts/border.gd b/scripts/border.gd
index e6d1899..f0cbfc0 100644
--- a/scripts/border.gd
+++ b/scripts/border.gd
@@ -13,18 +13,18 @@ var _timer = Timer.new()
var _color_changes = 0
func _ready():
- """Time last return to green with reenabling of controls."""
+ # Time last return to green with reenabling of controls
_timer.wait_time = ComparisonSort.DISABLE_TIME / COLOR_CHANGES
_timer.connect("timeout", self, "_on_Timer_timeout")
add_child(_timer)
func flash():
- """Immediately flash red and then start timer."""
+ # Immediately flash red and then start timer
_on_Timer_timeout()
_timer.start()
func _on_Timer_timeout():
- """Switch between green and red."""
+ # Switch between green and red
if _color_changes == COLOR_CHANGES:
_timer.stop()
_color_changes = 0
@@ -35,5 +35,4 @@ func _on_Timer_timeout():
update()
func _draw():
- """Draw the border."""
draw_rect(Rect2(Vector2(), rect_size), _color, false, WIDTH)
diff --git a/scripts/credits.gd b/scripts/credits.gd
index 08ed1ee..0da26e6 100644
--- a/scripts/credits.gd
+++ b/scripts/credits.gd
@@ -10,4 +10,5 @@ func _process(delta):
rect_position.y -= 1
func _input(event):
- GlobalScene.change_scene("res://scenes/menu.tscn")
+ if event.is_pressed():
+ GlobalScene.change_scene("res://scenes/menu.tscn")
diff --git a/scripts/end.gd b/scripts/end.gd
deleted file mode 100644
index d39a4c0..0000000
--- a/scripts/end.gd
+++ /dev/null
@@ -1,9 +0,0 @@
-extends VBoxContainer
-
-func _ready():
- $Score.text = str(GlobalScene.get_param("score"))
- $Button.grab_focus()
-
-func _on_Button_pressed():
- GlobalScene.change_scene("res://scenes/levels.tscn",
- {"level": GlobalScene.get_param("level")})
diff --git a/scripts/levels.gd b/scripts/levels.gd
index 1ce23d6..fea6ec5 100644
--- a/scripts/levels.gd
+++ b/scripts/levels.gd
@@ -5,47 +5,100 @@ const LEVELS = [
InsertionSort,
SelectionSort,
MergeSort,
+ QuickSort,
]
-var _level: ComparisonSort
+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 _level = GlobalScene.get_param("level", LEVELS[0]).new(ArrayModel.new(
+ GlobalScene.get_param("size", ArrayModel.DEFAULT_SIZE)))
func _ready():
- """Dynamically load level data."""
+ var buttons = $LevelsBorder/Levels/LevelsContainer/Buttons
+ var scores = $LevelsBorder/Levels/LevelsContainer/Scores
for level in LEVELS:
var button = Button.new()
button.text = level.NAME
button.align = Button.ALIGN_LEFT
- button.connect("focus_entered", self, "_on_Button_focus_changed")
- button.connect("pressed", self, "_on_Button_pressed", [level.NAME])
- $LevelsBorder/Levels.add_child(button)
- # Autofocus last played level
- if GlobalScene.get_param("level") == level:
+ 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()
+# tier.align = Label.ALIGN_RIGHT
+ score.add_child(time)
+ score.add_child(tier)
+ 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 = $LevelsBorder/Levels.get_children()[0]
- var bottom_button = $LevelsBorder/Levels.get_children()[-1]
+ 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()
- # If no last played level, autofocus first level
- if GlobalScene.get_param("level") == null:
- top_button.grab_focus()
-func _on_Button_focus_changed():
- """Initialize the preview section."""
- _level = get_level(get_focus_owner().text).new(ArrayModel.new())
+func _on_Button_focus_entered(size=_level.array.size):
+ # Update high scores
+ var buttons = $LevelsBorder/Levels/LevelsContainer/Buttons
+ var save = GlobalScene.read_save()
+ for i in range(LEVELS.size()):
+ var score = $LevelsBorder/Levels/LevelsContainer/Scores.get_child(i)
+ var name = buttons.get_child(i).text
+ if name in save and str(size) in save[name]:
+ var moves = save[name][str(size)][0]
+ var time = save[name][str(size)][1]
+ score.get_child(0).text = "%.3f" % time
+ score.get_child(1).text = Score.get_tier(moves, time)
+ score.get_child(1).add_color_override(
+ "font_color", Score.get_color(moves, time))
+ else:
+ score.get_child(0).text = ""
+ score.get_child(1).text = "INF"
+ score.get_child(1).add_color_override(
+ "font_color", GlobalTheme.GREEN)
+ # Pause a bit to show completely sorted array
+ if _level.array.is_sorted():
+ $Timer.stop()
+ yield(get_tree().create_timer(1), "timeout")
+ $Timer.start()
+ # Prevent race condition caused by switching levels during pause
+ if not _level.array.is_sorted():
+ return
+ _level = _get_level(get_focus_owner().text).new(ArrayModel.new(size))
_level.active = false
$Preview/InfoBorder/Info/About.text = _cleanup(_level.ABOUT)
$Preview/InfoBorder/Info/Controls.text = _cleanup(_level.CONTROLS)
# Start over when simulation is finished
- _level.connect("done", self, "_on_Button_focus_changed")
+ _level.connect("done", self, "_on_Button_focus_entered")
# Replace old display with new
for child in $Preview/Display.get_children():
child.queue_free()
$Preview/Display.add_child(ArrayView.new(_level))
-func _on_Button_pressed(name):
- GlobalScene.change_scene("res://scenes/play.tscn", {"level": get_level(name)})
+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})
-func get_level(name):
+func _get_level(name):
for level in LEVELS:
if level.NAME == name:
return level
diff --git a/scripts/menu.gd b/scripts/menu.gd
index a2caada..ebf0f8d 100644
--- a/scripts/menu.gd
+++ b/scripts/menu.gd
@@ -16,3 +16,7 @@ func _on_Credits_pressed():
func _on_Timer_timeout():
_level.next(null)
+
+func _input(event):
+ if event.is_action_pressed("ui_cancel"):
+ get_tree().quit()
diff --git a/scripts/play.gd b/scripts/play.gd
index 08699f5..c70b21d 100644
--- a/scripts/play.gd
+++ b/scripts/play.gd
@@ -1,9 +1,11 @@
extends VBoxContainer
var _start_time = -1
+var _level = GlobalScene.get_param(
+ "level", preload("res://scripts/levels.gd").LEVELS[0])
func _ready():
- $HUDBorder/HUD/Level.text = GlobalScene.get_param("level").NAME
+ $HUDBorder/HUD/Level.text = _level.NAME
func _process(delta):
if _start_time >= 0:
@@ -11,14 +13,57 @@ func _process(delta):
func _on_Timer_timeout():
_start_time = OS.get_ticks_msec()
- $DisplayBorder/Label.queue_free() # Delete ready text
- var level = GlobalScene.get_param("level").new(ArrayModel.new())
- level.connect("done", self, "_on_Level_done")
- $DisplayBorder.add_child(ArrayView.new(level))
+ $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))
func get_score():
return stepify((OS.get_ticks_msec() - _start_time) / 1000.0, 0.001)
-func _on_Level_done():
- GlobalScene.change_scene("res://scenes/end.tscn",
- {"level": GlobalScene.get_param("level"), "score": get_score()})
+func _input(event):
+ if event.is_action_pressed("ui_cancel"):
+ _on_Button_pressed("levels")
+
+func _on_Level_done(level):
+ var moves = level.moves
+ var score = get_score()
+ var restart = Button.new()
+ restart.text = "RESTART LEVEL"
+ restart.connect("pressed", self, "_on_Button_pressed", ["play"])
+ var separator = Label.new()
+ separator.text = " / "
+ 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" % get_score()
+ time.align = Label.ALIGN_RIGHT
+ time.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ _start_time = -1
+ var tier = Label.new()
+ tier.text = Score.get_tier(moves, score)
+ tier.align = Label.ALIGN_RIGHT
+ tier.add_color_override("font_color", Score.get_color(moves, score))
+ $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)
+ restart.grab_focus()
+ var save = GlobalScene.read_save()
+ var name = _level.NAME
+ var size = str(GlobalScene.get_param("size", ArrayModel.DEFAULT_SIZE))
+ if not name in save:
+ save[name] = {}
+ if not size in save[name]:
+ save[name][size] = [-1, INF]
+ save[name][size] = [moves, min(float(time.text), save[name][size][1])]
+ GlobalScene.write_save(save)
+
+func _on_Button_pressed(scene):
+ GlobalScene.change_scene("res://scenes/" + scene + ".tscn",
+ {"level": _level, "size": GlobalScene.get_param("size")})
diff --git a/scripts/scene.gd b/scripts/scene.gd
index 0379e12..5ee37ba 100644
--- a/scripts/scene.gd
+++ b/scripts/scene.gd
@@ -10,7 +10,19 @@ func change_scene(next_scene, params=null):
_params = params
get_tree().change_scene(next_scene)
-func get_param(name):
- if _params != null and _params.has(name):
+func get_param(name, default=null):
+ if _params != null and _params.get(name) != null:
return _params[name]
- return null
+ return default
+
+func read_save():
+ var file = File.new()
+ file.open("user://save.json", File.READ)
+ return {} if not file.is_open() else parse_json(file.get_as_text())
+ file.close()
+
+func write_save(save):
+ var file = File.new()
+ file.open("user://save.json", File.WRITE)
+ file.store_line(to_json(save))
+ file.close()
diff --git a/scripts/score.gd b/scripts/score.gd
new file mode 100644
index 0000000..8e786d9
--- /dev/null
+++ b/scripts/score.gd
@@ -0,0 +1,21 @@
+"""
+Common helper library for scoring functions.
+"""
+class_name Score
+extends Reference
+
+const TIERS = ["F", "D", "C", "B", "A", "S"]
+const COLORS = [
+ Color("f44336"),
+ Color("ff9800"),
+ Color("ffeb3b"),
+ Color("4caf50"),
+ Color("03a9f4"),
+ Color("e040fb"),
+]
+
+static func get_tier(moves, seconds):
+ return TIERS[min(moves / seconds, TIERS.size() - 1)]
+
+static func get_color(moves, seconds):
+ return COLORS[min(moves / seconds, COLORS.size() - 1)]
diff --git a/scripts/theme.gd b/scripts/theme.gd
index fce2fc4..337ea07 100644
--- a/scripts/theme.gd
+++ b/scripts/theme.gd
@@ -4,7 +4,8 @@ Global constants relating to the GUI.
extends Node
-const GREEN = Color(0.2, 1, 0.2)
-const DARK_GREEN = Color(0.2, 1, 0.2, 0.5)
-const ORANGE = Color(1, 0.69, 0)
-const RED = Color(1, 0, 0)
+const GREEN = Color("33ff33")
+const DARK_GREEN = Color("7733ff33")
+const ORANGE = Color("ffb000")
+const RED = Color("f44336")
+const BLUE = Color("2196f3")
diff --git a/views/array_view.gd b/views/array_view.gd
index 70e9ac7..a7e1993 100644
--- a/views/array_view.gd
+++ b/views/array_view.gd
@@ -3,30 +3,97 @@ Visualization of an array as rectangles of varying heights.
"""
class_name ArrayView
-extends HBoxContainer
+extends ViewportContainer
+const ANIM_DURATION = 0.1
+
+var _tween = Tween.new()
var _level: ComparisonSort
var _rects = []
+var _positions = []
+var _viewport = Viewport.new()
+var _pointer = null
+var _pointer_size: int
+onready var _separation = 128 / _level.array.size
func _init(level):
- """Add colored rectangles."""
_level = level
- _level.connect("mistake", self, "_on_Level_mistake")
+ stretch = true
+ _viewport.usage = Viewport.USAGE_2D
add_child(_level) # NOTE: This is necessary for it to read input
- for i in range(level.array.size):
- var rect = ColorRect.new()
- rect.size_flags_horizontal = Control.SIZE_EXPAND_FILL
- rect.size_flags_vertical = Control.SIZE_SHRINK_END
+ add_child(_tween) # NOTE: This is necessary for it to animate
+ add_child(_viewport)
+
+func _ready():
+ yield(get_tree(), "idle_frame")
+ var unit_width = rect_size.x / _level.array.size
+ _pointer_size = max((unit_width - _separation) / 4, 2)
+ # Keep track of accumulated pixel error from integer division
+ var error = float(rect_size.x) / _level.array.size - unit_width
+ var accumulated = 0
+ var x = 0
+ _level.connect("mistake", get_parent(), "flash")
+ var width = unit_width - _separation
+ var height = rect_size.y - _pointer_size * 2
+ for i in range(_level.array.size):
+ var rect = Polygon2D.new()
+ if accumulated >= 1:
+ x += 1
+ accumulated -= 1
+ rect.polygon = [
+ Vector2(0, 0),
+ Vector2(0, height),
+ Vector2(width, height),
+ Vector2(width, 0),
+ ]
+ accumulated += error
+ rect.position = Vector2(x, rect_size.y)
+ _positions.append(x)
+ x += unit_width
_rects.append(rect)
- add_child(rect)
+ _viewport.add_child(rect)
+ _level.array.connect("swapped", self, "_on_ArrayModel_swapped")
+ _level.array.connect("sorted", self, "_on_ArrayModel_sorted")
+ if _level.has_method("get_pointer"):
+ _pointer = Polygon2D.new()
+ _pointer.polygon = [
+ Vector2(width / 2, _pointer_size),
+ Vector2(width / 2 - _pointer_size, 0),
+ Vector2(width / 2 + _pointer_size, 0),
+ ]
+ _pointer.color = GlobalTheme.BLUE
+ _viewport.add_child(_pointer)
func _process(delta):
- """Update heights of rectangles based on array values."""
- for i in range(_level.array.size):
- _rects[i].rect_scale.y = -1 # HACK: Override scale to bypass weird behavior
- _rects[i].color = _level.get_effect(i)
- _rects[i].rect_size.y = rect_size.y * _level.array.at(i) / _level.array.size
+ if _pointer != null:
+ var pointed = _level.get_pointer()
+ var height = rect_size.y - _pointer_size * 2
+ _pointer.position = Vector2(_rects[pointed].position.x,
+ height - _level.array.frac(pointed) * height)
+ if _level.done:
+ _pointer.queue_free()
+ for i in range(_rects.size()):
+ if _level.done:
+ _rects[i].color = ComparisonSort.EFFECTS.NONE
+ else:
+ _rects[i].color = _level.get_effect(i)
+ _rects[i].scale.y = -_level.array.frac(i)
+
+func _on_ArrayModel_swapped(i, j):
+ var time = ANIM_DURATION * (1 + float(j - i) / _level.array.size)
+ _tween.interpolate_property(
+ _rects[i], "position:x", null, _positions[j], time)
+ _tween.interpolate_property(
+ _rects[j], "position:x", null, _positions[i], time)
+ var temp = _rects[i]
+ _rects[i] = _rects[j]
+ _rects[j] = temp
+ _tween.start()
-func _on_Level_mistake():
- """Flash the border red on mistakes."""
- get_parent().flash()
+func _on_ArrayModel_sorted(i, j):
+ for x in range(i, j):
+ _rects[x].position.y = 0
+ for x in range(i, j):
+ _tween.interpolate_property(
+ _rects[x], "position:y", null, rect_size.y, ANIM_DURATION)
+ _tween.start()