├── .gitignore ├── LICENSE ├── README.md ├── default_env.tres ├── icon.png ├── icon.png.import ├── plan ├── .gdignore ├── pallete.png └── plan.txt ├── project.godot ├── resources ├── fonts │ ├── amiri │ │ ├── Amiri-Bold.ttf │ │ ├── Amiri-BoldItalic.ttf │ │ ├── Amiri-Italic.ttf │ │ ├── Amiri-Regular.ttf │ │ └── OFL.txt │ └── libre-baskerville │ │ ├── LibreBaskerville-Bold.ttf │ │ ├── LibreBaskerville-Regular.ttf │ │ └── OFL.txt ├── godot │ ├── algorithm_checker_theme.tres │ ├── algorithm_popup_option.tres │ ├── main_interface_panel_lower.tres │ ├── main_interface_panel_upper.tres │ ├── main_interface_theme.tres │ ├── popup_panel.tres │ └── popup_theme.tres ├── i18n │ ├── i18n.csv │ ├── i18n.csv.import │ ├── i18n.en.translation │ └── i18n.fr.translation ├── music │ └── lazer_receivers │ │ ├── layer0.mp3 │ │ ├── layer0.mp3.import │ │ ├── layer1.mp3 │ │ ├── layer1.mp3.import │ │ ├── layer2.mp3 │ │ ├── layer2.mp3.import │ │ ├── layer3.mp3 │ │ ├── layer3.mp3.import │ │ ├── layer4.mp3 │ │ ├── layer4.mp3.import │ │ ├── layer5.mp3 │ │ ├── layer5.mp3.import │ │ ├── layer6.mp3 │ │ ├── layer6.mp3.import │ │ ├── layer7.mp3 │ │ ├── layer7.mp3.import │ │ ├── layer8.mp3 │ │ ├── layer8.mp3.import │ │ ├── layer9.mp3 │ │ └── layer9.mp3.import ├── shaders │ ├── glow.tres │ └── lazer_line_wiggles.tres └── textures │ ├── music_particles.png │ ├── music_particles.png.import │ ├── planets.png │ ├── planets.png.import │ ├── puzzle_board.png │ ├── puzzle_board.png.import │ ├── singing_lazers.png │ ├── singing_lazers.png.import │ └── visualizer_images │ ├── color_bars.PNG.import │ ├── color_bars.png │ ├── color_bars.png.import │ ├── default.png │ ├── default.png.import │ ├── planets.PNG.import │ ├── planets.png │ ├── planets.png.import │ ├── puzzle_pieces.PNG.import │ ├── puzzle_pieces.png │ ├── puzzle_pieces.png.import │ ├── singing_lazers.PNG.import │ ├── singing_lazers.png │ ├── singing_lazers.png.import │ ├── vertical_rects.PNG.import │ ├── vertical_rects.png │ └── vertical_rects.png.import └── scenes ├── autoloads ├── files_tracker.gd └── utility.gd ├── game ├── main.gd ├── main.tscn └── main_interface.gd ├── objects ├── components │ ├── lazer_receiver.gd │ ├── lazer_receiver.tscn │ ├── lazer_shooter.gd │ ├── lazer_shooter.tscn │ ├── orbiting_planet.gd │ ├── orbiting_planet.tscn │ └── popups │ │ ├── popup_base.gd │ │ ├── popup_base.tscn │ │ ├── popup_main_interface_algorithms.gd │ │ ├── popup_main_interface_algorithms.tscn │ │ ├── popup_main_interface_options.gd │ │ ├── popup_main_interface_options.tscn │ │ ├── popup_main_interface_visualizers.gd │ │ ├── popup_main_interface_visualizers.tscn │ │ ├── visualizer_data_container.gd │ │ └── visualizer_data_container.tscn ├── sorters │ ├── sorter.gd │ ├── sorter_bogo_sort.gd │ ├── sorter_bubble_sort.gd │ ├── sorter_gnome_sort.gd │ ├── sorter_heap_sort.gd │ ├── sorter_insert_sort.gd │ ├── sorter_merge_sort.gd │ ├── sorter_odd-even_sort.gd │ ├── sorter_quick_sort.gd │ ├── sorter_selection_sort.gd │ └── sorter_shell_sort.gd └── visualizers │ ├── visualizer.gd │ ├── visualizer.tscn │ ├── visualizer_color_bars.gd │ ├── visualizer_color_bars.tscn │ ├── visualizer_planets.gd │ ├── visualizer_planets.tscn │ ├── visualizer_puzzle_pieces.gd │ ├── visualizer_puzzle_pieces.tscn │ ├── visualizer_singing_lazers.gd │ ├── visualizer_singing_lazers.tscn │ ├── visualizer_vertical_lines.gd │ └── visualizer_vertical_lines.tscn └── tests ├── algorithm_checker.gd └── algorithm_checker.tscn /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .import/ 3 | settings/ 4 | 5 | *.aseprite 6 | *.flp -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2022 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sort Visualizer 2 | A "back-to-basics" project that implements a number of sorting algorithms in gdscript 3 | as well as visual effects to help see these algorithm in action! 4 | 5 | ![](https://imgur.com/pvKQpVL.gif) 6 | 7 | ![](https://imgur.com/NvqfeR8.gif) 8 | 9 | ## Contribution 10 | Any help is greatly appreciated, and I'm very much open to suggestions and PRs. 11 | The project uses a slightly modified version of the [godot style guide](https://docs.godotengine.org/en/3.5/tutorials/scripting/gdscript/gdscript_styleguide.html), so it's appreciated if you can stick to the original guide, but I can take care of reformating otherwise. 12 | 13 | thank you @DocSkellington for the help with the project and french translation! 14 | 15 | ## Project Structure 16 | The main components are **Sorters** and **Visualizers**, 17 | **Sorters** are scripts that contain the sorting algorithm, each script inherites 18 | from `sorter.gd` and must override its virtual methods to interact with the algorithm. 19 | **Visualizers** are scenes that take input from a sorter and translate that visually overtime, 20 | each visualizer must inherite from `visualizer.tscn` and override its virtual methods 21 | to retreive information. 22 | 23 | The project is designed to be highly customizable so more **Sorters** and **Visualizers** can 24 | easily be added without having to change anything outside their implementation. 25 | 26 | The **Main** scene handles the flow of data and dependency between components, 27 | 28 | The last main component in this project is the **Main Interface**, which is the UI 29 | that is used to control sorting algorithms as well as the method and speed of sorting etc. 30 | it also allows both sorters and visualizers to be changed at run-time. 31 | 32 | Interaction between **Sorters** and **Visualizers** is based on indexes, where a **Visualizer** 33 | must provide the size of items to sort (`visualizer.get_content_count`) and a callback 34 | function to compare between each index (`visualizer.determine_priority`). based on 35 | these 2 funtions a **Sorter** compares indexes and passes data back to the visualizer. 36 | what's neat about this approach (if I do say so myself :-) ) is that a sorter doesn't have to 37 | know the nature of data it's sorting, from a sorter point of view it just sorts indexes 38 | based on the callback function which allow for great control over the implementation. 39 | 40 | ## Credits 41 | Font: amiri (Open Font License) 42 | 43 | All other assets including audio and art are original and follow the same license as the project -------------------------------------------------------------------------------- /default_env.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Environment" load_steps=2 format=2] 2 | 3 | [sub_resource type="ProceduralSky" id=1] 4 | 5 | [resource] 6 | background_mode = 2 7 | background_sky = SubResource( 1 ) 8 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DissonantVoid/Godot-Sort-Visualizer/069e43b3c69df5f978aa449cf3e6c837a96a1f60/icon.png -------------------------------------------------------------------------------- /icon.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://icon.png" 13 | dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /plan/.gdignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DissonantVoid/Godot-Sort-Visualizer/069e43b3c69df5f978aa449cf3e6c837a96a1f60/plan/.gdignore -------------------------------------------------------------------------------- /plan/pallete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DissonantVoid/Godot-Sort-Visualizer/069e43b3c69df5f978aa449cf3e6c837a96a1f60/plan/pallete.png -------------------------------------------------------------------------------- /plan/plan.txt: -------------------------------------------------------------------------------- 1 | Current Task: Done! 2 | 3 | Todo (also see in-code todos by searching in all files for "todo"): 4 | -replace some buttons with icons 5 | -sorters comments are inconsistent, some of them are more detailed than others 6 | -add more images to puzzle visualizer 7 | 8 | Plan: 9 | -keep sorting implementation code independent so we can apply the sort on any custom scene 10 | -sorters (easy to hard): 11 | [X] Bubble sort, [X] Odd-Even sort, [X] Selection sort, 12 | [X] Insertion sort [X] Gnome sort, [X] Shell sort, 13 | [X] Quick sort [!] Merge sort, [X] Heap sort, 14 | [X] Bogo sort 15 | 16 | [-] Radix sort (not possible, only works on comparable data like ints and strings, not indexes) 17 | [-] Bitonic sort (not possible, requires size of the sorted list to be power of 2, quite a shame) 18 | 19 | -additional sorters for the future (if I even come back to this project :<\ ): 20 | comb sort, gravity sort, flash sort, pancake sort 21 | 22 | -visualizers: there should be at least 5 visualizers 23 | -bunch of cakes in unmatching boxes, on each sort 2 cakes are moved into a conveyor belt 24 | and transfered to each other position 25 | -humans shooting zombies, each human shoots a type of zombie, but each type require specific weapon 26 | -something like this fanciness right here: https://youtu.be/2ck0JDMsQco 27 | 28 | Keep in mind: 29 | Sorters: 30 | -since this is potentially an educational project, always keep a short description on how the algorithm works 31 | + wiki link and keep code easy to read 32 | UI: 33 | -no UI focus, i.e. focus is always set to none on UI elements 34 | -all clickables should have a hand pointer 35 | 36 | Deadline: 2 months (around february 30) -------------------------------------------------------------------------------- /project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=4 10 | 11 | _global_script_classes=[ { 12 | "base": "Reference", 13 | "class": "Sorter", 14 | "language": "GDScript", 15 | "path": "res://scenes/objects/sorters/sorter.gd" 16 | } ] 17 | _global_script_class_icons={ 18 | "Sorter": "" 19 | } 20 | 21 | [application] 22 | 23 | config/name="Sort Visualizer" 24 | run/main_scene="res://scenes/game/main.tscn" 25 | config/icon="res://icon.png" 26 | 27 | [autoload] 28 | 29 | Utility="*res://scenes/autoloads/utility.gd" 30 | FilesTracker="*res://scenes/autoloads/files_tracker.gd" 31 | 32 | [debug] 33 | 34 | gdscript/warnings/shadowed_variable=false 35 | gdscript/warnings/unused_argument=false 36 | gdscript/warnings/narrowing_conversion=false 37 | gdscript/warnings/incompatible_ternary=false 38 | gdscript/warnings/return_value_discarded=false 39 | gdscript/warnings/standalone_ternary=false 40 | 41 | [display] 42 | 43 | window/size/height=576 44 | window/stretch/mode="2d" 45 | window/stretch/aspect="keep" 46 | 47 | [gui] 48 | 49 | common/drop_mouse_on_gui_input_disabled=true 50 | 51 | [importer_defaults] 52 | 53 | texture={ 54 | "compress/bptc_ldr": 0, 55 | "compress/hdr_mode": 0, 56 | "compress/lossy_quality": 0.7, 57 | "compress/mode": 0, 58 | "compress/normal_map": 0, 59 | "detect_3d": false, 60 | "flags/anisotropic": false, 61 | "flags/filter": false, 62 | "flags/mipmaps": false, 63 | "flags/repeat": 0, 64 | "flags/srgb": 2, 65 | "process/HDR_as_SRGB": false, 66 | "process/fix_alpha_border": true, 67 | "process/invert_color": false, 68 | "process/normal_map_invert_y": false, 69 | "process/premult_alpha": false, 70 | "size_limit": 0, 71 | "stream": false, 72 | "svg/scale": 1.0 73 | } 74 | 75 | [locale] 76 | 77 | translations=PoolStringArray( "res://resources/i18n/i18n.en.translation", "res://resources/i18n/i18n.fr.translation" ) 78 | 79 | [physics] 80 | 81 | common/enable_pause_aware_picking=true 82 | 83 | [rendering] 84 | 85 | quality/driver/driver_name="GLES2" 86 | environment/default_environment="res://default_env.tres" 87 | -------------------------------------------------------------------------------- /resources/fonts/amiri/Amiri-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DissonantVoid/Godot-Sort-Visualizer/069e43b3c69df5f978aa449cf3e6c837a96a1f60/resources/fonts/amiri/Amiri-Bold.ttf -------------------------------------------------------------------------------- /resources/fonts/amiri/Amiri-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DissonantVoid/Godot-Sort-Visualizer/069e43b3c69df5f978aa449cf3e6c837a96a1f60/resources/fonts/amiri/Amiri-BoldItalic.ttf -------------------------------------------------------------------------------- /resources/fonts/amiri/Amiri-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DissonantVoid/Godot-Sort-Visualizer/069e43b3c69df5f978aa449cf3e6c837a96a1f60/resources/fonts/amiri/Amiri-Italic.ttf -------------------------------------------------------------------------------- /resources/fonts/amiri/Amiri-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DissonantVoid/Godot-Sort-Visualizer/069e43b3c69df5f978aa449cf3e6c837a96a1f60/resources/fonts/amiri/Amiri-Regular.ttf -------------------------------------------------------------------------------- /resources/fonts/amiri/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright 2010-2022 The Amiri Project Authors (https://github.com/aliftype/amiri). 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /resources/fonts/libre-baskerville/LibreBaskerville-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DissonantVoid/Godot-Sort-Visualizer/069e43b3c69df5f978aa449cf3e6c837a96a1f60/resources/fonts/libre-baskerville/LibreBaskerville-Bold.ttf -------------------------------------------------------------------------------- /resources/fonts/libre-baskerville/LibreBaskerville-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DissonantVoid/Godot-Sort-Visualizer/069e43b3c69df5f978aa449cf3e6c837a96a1f60/resources/fonts/libre-baskerville/LibreBaskerville-Regular.ttf -------------------------------------------------------------------------------- /resources/fonts/libre-baskerville/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Pablo Impallari (www.impallari.com|impallari@gmail.com), 2 | Copyright (c) 2012, Rodrigo Fuenzalida (www.rfuenzalida.com|hello�rfuenzalida.com), with Reserved Font Name Libre Baskerville. 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /resources/godot/algorithm_checker_theme.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Theme" load_steps=18 format=2] 2 | 3 | [ext_resource path="res://resources/fonts/amiri/Amiri-Bold.ttf" type="DynamicFontData" id=1] 4 | [ext_resource path="res://resources/fonts/amiri/Amiri-Italic.ttf" type="DynamicFontData" id=3] 5 | [ext_resource path="res://resources/fonts/amiri/Amiri-Regular.ttf" type="DynamicFontData" id=4] 6 | 7 | [sub_resource type="StyleBoxFlat" id=6] 8 | bg_color = Color( 0.27451, 0.258824, 0.368627, 1 ) 9 | border_width_left = 2 10 | border_width_top = 2 11 | border_width_right = 2 12 | border_width_bottom = 2 13 | border_color = Color( 1, 0.933333, 0.8, 1 ) 14 | 15 | [sub_resource type="StyleBoxFlat" id=5] 16 | bg_color = Color( 1, 0.933333, 0.8, 1 ) 17 | border_width_left = 2 18 | border_width_top = 2 19 | border_width_right = 2 20 | border_width_bottom = 2 21 | border_color = Color( 1, 0.933333, 0.8, 1 ) 22 | 23 | [sub_resource type="StyleBoxFlat" id=4] 24 | bg_color = Color( 1, 0.411765, 0.45098, 1 ) 25 | border_width_left = 2 26 | border_width_top = 2 27 | border_width_right = 2 28 | border_width_bottom = 2 29 | border_color = Color( 1, 0.933333, 0.8, 1 ) 30 | 31 | [sub_resource type="StyleBoxFlat" id=7] 32 | draw_center = false 33 | 34 | [sub_resource type="StyleBoxFlat" id=3] 35 | bg_color = Color( 0.27451, 0.258824, 0.368627, 1 ) 36 | border_width_left = 1 37 | border_width_top = 1 38 | border_width_right = 1 39 | border_width_bottom = 1 40 | border_color = Color( 0.0823529, 0.470588, 0.54902, 1 ) 41 | 42 | [sub_resource type="StyleBoxFlat" id=1] 43 | bg_color = Color( 0.0823529, 0.470588, 0.54902, 1 ) 44 | corner_radius_top_left = 6 45 | corner_radius_top_right = 6 46 | corner_radius_bottom_right = 6 47 | corner_radius_bottom_left = 6 48 | 49 | [sub_resource type="DynamicFont" id=9] 50 | size = 20 51 | font_data = ExtResource( 1 ) 52 | 53 | [sub_resource type="DynamicFont" id=2] 54 | size = 20 55 | font_data = ExtResource( 4 ) 56 | 57 | [sub_resource type="DynamicFont" id=13] 58 | size = 20 59 | font_data = ExtResource( 3 ) 60 | 61 | [sub_resource type="DynamicFont" id=14] 62 | size = 20 63 | font_data = ExtResource( 4 ) 64 | 65 | [sub_resource type="StyleBoxFlat" id=8] 66 | bg_color = Color( 1, 0.411765, 0.45098, 1 ) 67 | 68 | [sub_resource type="StyleBoxFlat" id=10] 69 | bg_color = Color( 0, 0.72549, 0.745098, 1 ) 70 | 71 | [sub_resource type="StyleBoxFlat" id=12] 72 | bg_color = Color( 1, 0.933333, 0.8, 1 ) 73 | 74 | [sub_resource type="StyleBoxFlat" id=11] 75 | draw_center = false 76 | border_width_left = 4 77 | border_width_right = 4 78 | border_color = Color( 0.0823529, 0.470588, 0.54902, 1 ) 79 | 80 | [resource] 81 | default_font = SubResource( 14 ) 82 | Button/colors/font_color = Color( 1, 0.933333, 0.8, 1 ) 83 | Button/colors/font_color_disabled = Color( 0.0823529, 0.470588, 0.54902, 1 ) 84 | Button/colors/font_color_hover = Color( 1, 0.411765, 0.45098, 1 ) 85 | Button/colors/font_color_pressed = Color( 1, 0.411765, 0.45098, 1 ) 86 | Button/styles/disabled = SubResource( 6 ) 87 | Button/styles/hover = SubResource( 5 ) 88 | Button/styles/normal = SubResource( 4 ) 89 | Button/styles/pressed = SubResource( 5 ) 90 | CheckBox/colors/font_color = Color( 1, 0.933333, 0.8, 1 ) 91 | CheckBox/colors/font_color_hover = Color( 1, 0.933333, 0.8, 1 ) 92 | CheckBox/colors/font_color_pressed = Color( 1, 0.933333, 0.8, 1 ) 93 | CheckBox/styles/hover = SubResource( 7 ) 94 | CheckBox/styles/normal = SubResource( 7 ) 95 | CheckBox/styles/pressed = SubResource( 7 ) 96 | Label/colors/font_color = Color( 1, 0.933333, 0.8, 1 ) 97 | LineEdit/colors/font_color = Color( 1, 0.933333, 0.8, 1 ) 98 | LineEdit/styles/normal = SubResource( 3 ) 99 | PanelContainer/styles/panel = SubResource( 1 ) 100 | RichTextLabel/colors/default_color = Color( 1, 0.933333, 0.8, 1 ) 101 | RichTextLabel/fonts/bold_font = SubResource( 9 ) 102 | RichTextLabel/fonts/bold_italics_font = SubResource( 2 ) 103 | RichTextLabel/fonts/italics_font = SubResource( 13 ) 104 | RichTextLabel/fonts/normal_font = SubResource( 14 ) 105 | RichTextLabel/styles/normal = SubResource( 8 ) 106 | VScrollBar/styles/grabber = SubResource( 10 ) 107 | VScrollBar/styles/grabber_highlight = SubResource( 12 ) 108 | VScrollBar/styles/grabber_pressed = SubResource( 12 ) 109 | VScrollBar/styles/scroll = SubResource( 11 ) 110 | -------------------------------------------------------------------------------- /resources/godot/algorithm_popup_option.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Theme" load_steps=5 format=2] 2 | 3 | [ext_resource path="res://resources/fonts/amiri/Amiri-Bold.ttf" type="DynamicFontData" id=1] 4 | 5 | [sub_resource type="StyleBoxFlat" id=2] 6 | bg_color = Color( 1, 0.411765, 0.45098, 1 ) 7 | 8 | [sub_resource type="StyleBoxFlat" id=3] 9 | draw_center = false 10 | 11 | [sub_resource type="DynamicFont" id=4] 12 | size = 20 13 | font_data = ExtResource( 1 ) 14 | 15 | [resource] 16 | default_font = SubResource( 4 ) 17 | Button/colors/font_color = Color( 1, 0.933333, 0.8, 1 ) 18 | Button/colors/font_color_hover = Color( 1, 0.690196, 0.639216, 1 ) 19 | Button/colors/font_color_pressed = Color( 1, 0.690196, 0.639216, 1 ) 20 | Button/styles/hover = SubResource( 2 ) 21 | Button/styles/normal = SubResource( 3 ) 22 | Button/styles/pressed = SubResource( 2 ) 23 | -------------------------------------------------------------------------------- /resources/godot/main_interface_panel_lower.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="StyleBoxFlat" format=2] 2 | 3 | [resource] 4 | bg_color = Color( 0.0823529, 0.470588, 0.54902, 1 ) 5 | border_width_left = 2 6 | border_width_right = 2 7 | border_width_bottom = 2 8 | border_color = Color( 1, 0.933333, 0.8, 1 ) 9 | corner_radius_bottom_right = 35 10 | corner_radius_bottom_left = 35 11 | shadow_size = 1 12 | shadow_offset = Vector2( 0, 1 ) 13 | -------------------------------------------------------------------------------- /resources/godot/main_interface_panel_upper.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="StyleBoxFlat" format=2] 2 | 3 | [resource] 4 | bg_color = Color( 0.0823529, 0.470588, 0.54902, 1 ) 5 | border_width_bottom = 2 6 | border_color = Color( 1, 0.933333, 0.8, 1 ) 7 | corner_radius_bottom_right = 12 8 | corner_radius_bottom_left = 12 9 | shadow_size = 1 10 | shadow_offset = Vector2( 0, 1 ) 11 | -------------------------------------------------------------------------------- /resources/godot/main_interface_theme.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Theme" load_steps=8 format=2] 2 | 3 | [ext_resource path="res://resources/fonts/amiri/Amiri-Regular.ttf" type="DynamicFontData" id=1] 4 | 5 | [sub_resource type="StyleBoxFlat" id=3] 6 | bg_color = Color( 0, 0.72549, 0.745098, 1 ) 7 | border_width_left = 1 8 | border_width_top = 1 9 | border_width_right = 1 10 | border_width_bottom = 1 11 | border_color = Color( 1, 0.933333, 0.8, 1 ) 12 | expand_margin_left = 1.0 13 | expand_margin_right = 1.0 14 | 15 | [sub_resource type="StyleBoxFlat" id=4] 16 | bg_color = Color( 1, 0.933333, 0.8, 1 ) 17 | border_width_left = 1 18 | border_width_top = 1 19 | border_width_right = 1 20 | border_width_bottom = 1 21 | border_color = Color( 1, 0.933333, 0.8, 1 ) 22 | expand_margin_left = 1.0 23 | expand_margin_right = 1.0 24 | 25 | [sub_resource type="StyleBoxFlat" id=5] 26 | bg_color = Color( 1, 0.411765, 0.45098, 1 ) 27 | border_width_left = 1 28 | border_width_top = 1 29 | border_width_right = 1 30 | border_width_bottom = 1 31 | border_color = Color( 1, 0.933333, 0.8, 1 ) 32 | 33 | [sub_resource type="StyleBoxFlat" id=6] 34 | bg_color = Color( 1, 0.933333, 0.8, 1 ) 35 | border_width_top = 1 36 | border_width_bottom = 1 37 | border_color = Color( 1, 0.933333, 0.8, 1 ) 38 | border_blend = true 39 | corner_radius_top_left = 1 40 | corner_radius_top_right = 1 41 | corner_radius_bottom_right = 1 42 | corner_radius_bottom_left = 1 43 | 44 | [sub_resource type="StyleBoxFlat" id=7] 45 | bg_color = Color( 1, 0.933333, 0.8, 1 ) 46 | border_width_left = 1 47 | border_width_right = 1 48 | border_color = Color( 1, 0.933333, 0.8, 1 ) 49 | border_blend = true 50 | corner_radius_top_left = 1 51 | corner_radius_top_right = 1 52 | corner_radius_bottom_right = 1 53 | corner_radius_bottom_left = 1 54 | 55 | [sub_resource type="DynamicFont" id=2] 56 | size = 22 57 | use_mipmaps = true 58 | font_data = ExtResource( 1 ) 59 | 60 | [resource] 61 | default_font = SubResource( 2 ) 62 | Button/colors/font_color = Color( 1, 0.933333, 0.8, 1 ) 63 | Button/colors/font_color_disabled = Color( 1, 0.933333, 0.8, 1 ) 64 | Button/colors/font_color_hover = Color( 1, 0.411765, 0.45098, 1 ) 65 | Button/colors/font_color_pressed = Color( 1, 0.411765, 0.45098, 1 ) 66 | Button/styles/disabled = SubResource( 3 ) 67 | Button/styles/hover = SubResource( 4 ) 68 | Button/styles/normal = SubResource( 5 ) 69 | Button/styles/pressed = SubResource( 4 ) 70 | HSeparator/styles/separator = SubResource( 6 ) 71 | Label/colors/font_color = Color( 1, 0.933333, 0.8, 1 ) 72 | VSeparator/styles/separator = SubResource( 7 ) 73 | -------------------------------------------------------------------------------- /resources/godot/popup_panel.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="StyleBoxFlat" format=2] 2 | 3 | [resource] 4 | bg_color = Color( 0, 0.72549, 0.745098, 1 ) 5 | -------------------------------------------------------------------------------- /resources/godot/popup_theme.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Theme" load_steps=19 format=2] 2 | 3 | [ext_resource path="res://resources/fonts/amiri/Amiri-Regular.ttf" type="DynamicFontData" id=1] 4 | 5 | [sub_resource type="StyleBoxFlat" id=3] 6 | bg_color = Color( 1, 0.933333, 0.8, 1 ) 7 | border_width_left = 1 8 | border_width_top = 1 9 | border_width_right = 1 10 | border_width_bottom = 1 11 | border_color = Color( 1, 0.933333, 0.8, 1 ) 12 | expand_margin_left = 1.0 13 | expand_margin_right = 1.0 14 | 15 | [sub_resource type="StyleBoxFlat" id=4] 16 | bg_color = Color( 1, 0.411765, 0.45098, 1 ) 17 | border_width_left = 1 18 | border_width_top = 1 19 | border_width_right = 1 20 | border_width_bottom = 1 21 | border_color = Color( 1, 0.933333, 0.8, 1 ) 22 | expand_margin_left = 1.0 23 | expand_margin_right = 1.0 24 | 25 | [sub_resource type="StyleBoxFlat" id=11] 26 | bg_color = Color( 1, 0.411765, 0.45098, 1 ) 27 | 28 | [sub_resource type="StyleBoxFlat" id=12] 29 | bg_color = Color( 1, 0.933333, 0.8, 1 ) 30 | 31 | [sub_resource type="StyleBoxFlat" id=13] 32 | draw_center = false 33 | border_width_top = 4 34 | border_width_bottom = 4 35 | border_color = Color( 0, 0.72549, 0.745098, 1 ) 36 | 37 | [sub_resource type="StyleBoxFlat" id=6] 38 | bg_color = Color( 1, 0.933333, 0.8, 1 ) 39 | border_width_top = 1 40 | border_width_bottom = 1 41 | border_color = Color( 1, 0.933333, 0.8, 1 ) 42 | border_blend = true 43 | corner_radius_top_left = 1 44 | corner_radius_top_right = 1 45 | corner_radius_bottom_right = 1 46 | corner_radius_bottom_left = 1 47 | 48 | [sub_resource type="StyleBoxFlat" id=14] 49 | bg_color = Color( 1, 0.411765, 0.45098, 1 ) 50 | corner_radius_top_left = 4 51 | corner_radius_top_right = 4 52 | corner_radius_bottom_right = 4 53 | corner_radius_bottom_left = 4 54 | 55 | [sub_resource type="StyleBoxFlat" id=15] 56 | bg_color = Color( 0, 0.72549, 0.745098, 1 ) 57 | border_width_top = 2 58 | border_width_bottom = 2 59 | border_color = Color( 0, 0.72549, 0.745098, 1 ) 60 | corner_radius_top_left = 6 61 | corner_radius_top_right = 6 62 | corner_radius_bottom_right = 6 63 | corner_radius_bottom_left = 6 64 | 65 | [sub_resource type="StyleBoxFlat" id=7] 66 | bg_color = Color( 0, 0.72549, 0.745098, 1 ) 67 | border_width_left = 2 68 | border_width_right = 2 69 | border_color = Color( 0, 0.72549, 0.745098, 1 ) 70 | 71 | [sub_resource type="StyleBoxFlat" id=2] 72 | bg_color = Color( 0.0823529, 0.470588, 0.54902, 1 ) 73 | border_width_left = 2 74 | border_width_top = 2 75 | border_width_right = 2 76 | border_width_bottom = 2 77 | border_color = Color( 1, 0.933333, 0.8, 1 ) 78 | 79 | [sub_resource type="StyleBoxFlat" id=16] 80 | bg_color = Color( 0, 0.72549, 0.745098, 1 ) 81 | 82 | [sub_resource type="StyleBoxFlat" id=17] 83 | bg_color = Color( 0.0823529, 0.470588, 0.54902, 1 ) 84 | border_width_left = 1 85 | border_width_top = 1 86 | border_width_right = 1 87 | border_width_bottom = 1 88 | border_color = Color( 1, 0.933333, 0.8, 1 ) 89 | 90 | [sub_resource type="StyleBoxFlat" id=18] 91 | bg_color = Color( 1, 0.933333, 0.8, 1 ) 92 | border_width_top = 1 93 | border_width_bottom = 1 94 | 95 | [sub_resource type="StyleBoxFlat" id=10] 96 | bg_color = Color( 1, 0.411765, 0.45098, 1 ) 97 | 98 | [sub_resource type="StyleBoxFlat" id=8] 99 | bg_color = Color( 1, 0.933333, 0.8, 1 ) 100 | 101 | [sub_resource type="StyleBoxFlat" id=9] 102 | draw_center = false 103 | border_width_left = 4 104 | border_width_right = 4 105 | border_color = Color( 0, 0.72549, 0.745098, 1 ) 106 | 107 | [sub_resource type="DynamicFont" id=5] 108 | size = 20 109 | use_mipmaps = true 110 | use_filter = true 111 | font_data = ExtResource( 1 ) 112 | 113 | [resource] 114 | default_font = SubResource( 5 ) 115 | Button/colors/font_color = Color( 1, 0.933333, 0.8, 1 ) 116 | Button/colors/font_color_hover = Color( 1, 0.411765, 0.45098, 1 ) 117 | Button/colors/font_color_pressed = Color( 1, 0.411765, 0.45098, 1 ) 118 | Button/styles/hover = SubResource( 3 ) 119 | Button/styles/normal = SubResource( 4 ) 120 | Button/styles/pressed = SubResource( 3 ) 121 | HScrollBar/styles/grabber = SubResource( 11 ) 122 | HScrollBar/styles/grabber_highlight = SubResource( 12 ) 123 | HScrollBar/styles/grabber_pressed = SubResource( 12 ) 124 | HScrollBar/styles/scroll = SubResource( 13 ) 125 | HSeparator/styles/separator = SubResource( 6 ) 126 | HSlider/styles/grabber_area = SubResource( 14 ) 127 | HSlider/styles/grabber_area_highlight = SubResource( 14 ) 128 | HSlider/styles/slider = SubResource( 15 ) 129 | Label/colors/font_color = Color( 1, 0.933333, 0.8, 1 ) 130 | LineEdit/colors/font_color = Color( 1, 0.933333, 0.8, 1 ) 131 | LineEdit/styles/normal = SubResource( 7 ) 132 | PanelContainer/styles/panel = SubResource( 2 ) 133 | PopupMenu/colors/font_color = Color( 1, 0.933333, 0.8, 1 ) 134 | PopupMenu/colors/font_color_hover = Color( 0.27451, 0.258824, 0.368627, 1 ) 135 | PopupMenu/styles/hover = SubResource( 16 ) 136 | PopupMenu/styles/panel = SubResource( 17 ) 137 | PopupMenu/styles/separator = SubResource( 18 ) 138 | VScrollBar/styles/grabber = SubResource( 10 ) 139 | VScrollBar/styles/grabber_highlight = SubResource( 8 ) 140 | VScrollBar/styles/grabber_pressed = SubResource( 8 ) 141 | VScrollBar/styles/scroll = SubResource( 9 ) 142 | -------------------------------------------------------------------------------- /resources/i18n/i18n.csv: -------------------------------------------------------------------------------- 1 | keys,en,fr 2 | OPTIONS,Options,Options 3 | HIDE,Hide,Cacher 4 | START,Start,Commencer 5 | NEXT,Next step,Étape suivante 6 | STOP,Stop,Arrêter 7 | LAST,Last step,Dernière étape 8 | CONTINUE,Continue,Continuer 9 | PAUSE,Pause,Pause 10 | RESTART,Restart,Recommencer 11 | OK,OK,OK 12 | CANCEL,Cancel,Annuler 13 | CHOOSE_ALGO,Choose an algorithm,Sélectionnez un algorithme 14 | NONE,None selected,Aucune sélection 15 | STEP_TIME,Step Time:,Délai entre deux étapes : 16 | VOLUME,Volume:,Volume : 17 | LANGUAGE,Language:,Langue : 18 | CHOOSE_VIZU,Choose a visualizer,Sélectionnez un visualisateur 19 | VERTICAL_TITLE,Vertical lines,Lignes verticales 20 | VERTICAL_DESC,The classic vertical rectangles with different height that are sorted in ascending order,Des lignes verticales de longueurs différentes qui sont triées par ordre croissant 21 | COLOR_TITLE,Color bars,Barres colorées 22 | COLOR_DESC,"Horizontal bars with different colors that are sorted from bright to dark, while visually keeping a record of their old positions","Des barres horizontales colorées qui sont triées du plus clair au plus foncé, tout en montrant l’historique des positions précédentes" 23 | PLANETS_TITLE,Planets,Planète 24 | PLANETS_DESC,Planets in a solar system that are sorted so that the smaller a planet is the closer it is to the star,Des planètes dans un système solaire qui sont triées de façon à obtenir que la plus petite planète est la plus proche du soleil 25 | PUZZLE_TITLE,Puzzle,Puzzle 26 | PUZZLE_DESC,An image split into a grid of puzzle pieces that get sorted in the right order to form the image,Une image découpée en une grille de pièces de puzzle qui sont triées pour obtenir l’image originelle 27 | LAZERS_TITLE,Singing lazers,Lazers chantants 28 | LAZERS_DESC,"Machines that shoot colorful Lazer into receivers with different colors, when a lazer matches the right receiver it will play a tune, once all lazers are in the right position they'll play a nice music (get the reference?)","Des machines qui tirent des lazers colorés vers des récepteurs colorés. Quand la couleur du lazer correspond à celle du récepteur, un son est joué. Quand tous les lazers sont correctement placés, les son forment une musique" 29 | BOGOSORT,Bogosort,Tri stupide 30 | BUBBLESORT,Bubble sort,Tri à bulles 31 | GNOMESORT,Gnome sort,Tri gnome 32 | HEAPSORT,Heap sort,Tri par tas 33 | INSERTIONSORT,Insertion sort,Tri par insertion 34 | MERGESORT,Merge sort,Tri par fusion 35 | ODDEVENSORT,Odd-even sort,Tri pair-impair 36 | QUICKSORT,Quick sort,Tri rapide (quicksort) 37 | SELECTIONSORT,Selection sort,Tri par permutation 38 | SHELLSORT,Shell sort,Tri de Shell 39 | -------------------------------------------------------------------------------- /resources/i18n/i18n.csv.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="csv_translation" 4 | type="Translation" 5 | 6 | [deps] 7 | 8 | files=[ "res://resources/i18n/i18n.en.translation", "res://resources/i18n/i18n.fr.translation" ] 9 | 10 | source_file="res://resources/i18n/i18n.csv" 11 | dest_files=[ "res://resources/i18n/i18n.en.translation", "res://resources/i18n/i18n.fr.translation" ] 12 | 13 | [params] 14 | 15 | compress=true 16 | delimiter=0 17 | -------------------------------------------------------------------------------- /resources/i18n/i18n.en.translation: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DissonantVoid/Godot-Sort-Visualizer/069e43b3c69df5f978aa449cf3e6c837a96a1f60/resources/i18n/i18n.en.translation -------------------------------------------------------------------------------- /resources/i18n/i18n.fr.translation: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DissonantVoid/Godot-Sort-Visualizer/069e43b3c69df5f978aa449cf3e6c837a96a1f60/resources/i18n/i18n.fr.translation -------------------------------------------------------------------------------- /resources/music/lazer_receivers/layer0.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DissonantVoid/Godot-Sort-Visualizer/069e43b3c69df5f978aa449cf3e6c837a96a1f60/resources/music/lazer_receivers/layer0.mp3 -------------------------------------------------------------------------------- /resources/music/lazer_receivers/layer0.mp3.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="mp3" 4 | type="AudioStreamMP3" 5 | path="res://.import/layer0.mp3-f7e580eaf7b83cd8624acd963f860d64.mp3str" 6 | 7 | [deps] 8 | 9 | source_file="res://resources/music/lazer_receivers/layer0.mp3" 10 | dest_files=[ "res://.import/layer0.mp3-f7e580eaf7b83cd8624acd963f860d64.mp3str" ] 11 | 12 | [params] 13 | 14 | loop=true 15 | loop_offset=0 16 | -------------------------------------------------------------------------------- /resources/music/lazer_receivers/layer1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DissonantVoid/Godot-Sort-Visualizer/069e43b3c69df5f978aa449cf3e6c837a96a1f60/resources/music/lazer_receivers/layer1.mp3 -------------------------------------------------------------------------------- /resources/music/lazer_receivers/layer1.mp3.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="mp3" 4 | type="AudioStreamMP3" 5 | path="res://.import/layer1.mp3-e60bb28ce3853b05a2bee49338ec89d5.mp3str" 6 | 7 | [deps] 8 | 9 | source_file="res://resources/music/lazer_receivers/layer1.mp3" 10 | dest_files=[ "res://.import/layer1.mp3-e60bb28ce3853b05a2bee49338ec89d5.mp3str" ] 11 | 12 | [params] 13 | 14 | loop=true 15 | loop_offset=0 16 | -------------------------------------------------------------------------------- /resources/music/lazer_receivers/layer2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DissonantVoid/Godot-Sort-Visualizer/069e43b3c69df5f978aa449cf3e6c837a96a1f60/resources/music/lazer_receivers/layer2.mp3 -------------------------------------------------------------------------------- /resources/music/lazer_receivers/layer2.mp3.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="mp3" 4 | type="AudioStreamMP3" 5 | path="res://.import/layer2.mp3-1286559a81f1a7b4f37892135efb59d8.mp3str" 6 | 7 | [deps] 8 | 9 | source_file="res://resources/music/lazer_receivers/layer2.mp3" 10 | dest_files=[ "res://.import/layer2.mp3-1286559a81f1a7b4f37892135efb59d8.mp3str" ] 11 | 12 | [params] 13 | 14 | loop=true 15 | loop_offset=0 16 | -------------------------------------------------------------------------------- /resources/music/lazer_receivers/layer3.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DissonantVoid/Godot-Sort-Visualizer/069e43b3c69df5f978aa449cf3e6c837a96a1f60/resources/music/lazer_receivers/layer3.mp3 -------------------------------------------------------------------------------- /resources/music/lazer_receivers/layer3.mp3.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="mp3" 4 | type="AudioStreamMP3" 5 | path="res://.import/layer3.mp3-a6a363cc4d14b30178bec7954366b376.mp3str" 6 | 7 | [deps] 8 | 9 | source_file="res://resources/music/lazer_receivers/layer3.mp3" 10 | dest_files=[ "res://.import/layer3.mp3-a6a363cc4d14b30178bec7954366b376.mp3str" ] 11 | 12 | [params] 13 | 14 | loop=true 15 | loop_offset=0 16 | -------------------------------------------------------------------------------- /resources/music/lazer_receivers/layer4.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DissonantVoid/Godot-Sort-Visualizer/069e43b3c69df5f978aa449cf3e6c837a96a1f60/resources/music/lazer_receivers/layer4.mp3 -------------------------------------------------------------------------------- /resources/music/lazer_receivers/layer4.mp3.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="mp3" 4 | type="AudioStreamMP3" 5 | path="res://.import/layer4.mp3-763a4cbf91e5a976d210319eb35ca5d5.mp3str" 6 | 7 | [deps] 8 | 9 | source_file="res://resources/music/lazer_receivers/layer4.mp3" 10 | dest_files=[ "res://.import/layer4.mp3-763a4cbf91e5a976d210319eb35ca5d5.mp3str" ] 11 | 12 | [params] 13 | 14 | loop=true 15 | loop_offset=0 16 | -------------------------------------------------------------------------------- /resources/music/lazer_receivers/layer5.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DissonantVoid/Godot-Sort-Visualizer/069e43b3c69df5f978aa449cf3e6c837a96a1f60/resources/music/lazer_receivers/layer5.mp3 -------------------------------------------------------------------------------- /resources/music/lazer_receivers/layer5.mp3.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="mp3" 4 | type="AudioStreamMP3" 5 | path="res://.import/layer5.mp3-8da15bc44044a55a6f22067c6baf6435.mp3str" 6 | 7 | [deps] 8 | 9 | source_file="res://resources/music/lazer_receivers/layer5.mp3" 10 | dest_files=[ "res://.import/layer5.mp3-8da15bc44044a55a6f22067c6baf6435.mp3str" ] 11 | 12 | [params] 13 | 14 | loop=true 15 | loop_offset=0 16 | -------------------------------------------------------------------------------- /resources/music/lazer_receivers/layer6.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DissonantVoid/Godot-Sort-Visualizer/069e43b3c69df5f978aa449cf3e6c837a96a1f60/resources/music/lazer_receivers/layer6.mp3 -------------------------------------------------------------------------------- /resources/music/lazer_receivers/layer6.mp3.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="mp3" 4 | type="AudioStreamMP3" 5 | path="res://.import/layer6.mp3-3402301d90182c72205e03459dfb9414.mp3str" 6 | 7 | [deps] 8 | 9 | source_file="res://resources/music/lazer_receivers/layer6.mp3" 10 | dest_files=[ "res://.import/layer6.mp3-3402301d90182c72205e03459dfb9414.mp3str" ] 11 | 12 | [params] 13 | 14 | loop=true 15 | loop_offset=0 16 | -------------------------------------------------------------------------------- /resources/music/lazer_receivers/layer7.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DissonantVoid/Godot-Sort-Visualizer/069e43b3c69df5f978aa449cf3e6c837a96a1f60/resources/music/lazer_receivers/layer7.mp3 -------------------------------------------------------------------------------- /resources/music/lazer_receivers/layer7.mp3.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="mp3" 4 | type="AudioStreamMP3" 5 | path="res://.import/layer7.mp3-17b3376961a86bb5fcacef9e9fa81769.mp3str" 6 | 7 | [deps] 8 | 9 | source_file="res://resources/music/lazer_receivers/layer7.mp3" 10 | dest_files=[ "res://.import/layer7.mp3-17b3376961a86bb5fcacef9e9fa81769.mp3str" ] 11 | 12 | [params] 13 | 14 | loop=true 15 | loop_offset=0 16 | -------------------------------------------------------------------------------- /resources/music/lazer_receivers/layer8.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DissonantVoid/Godot-Sort-Visualizer/069e43b3c69df5f978aa449cf3e6c837a96a1f60/resources/music/lazer_receivers/layer8.mp3 -------------------------------------------------------------------------------- /resources/music/lazer_receivers/layer8.mp3.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="mp3" 4 | type="AudioStreamMP3" 5 | path="res://.import/layer8.mp3-3895f0de52f1d440b392d9caddee4211.mp3str" 6 | 7 | [deps] 8 | 9 | source_file="res://resources/music/lazer_receivers/layer8.mp3" 10 | dest_files=[ "res://.import/layer8.mp3-3895f0de52f1d440b392d9caddee4211.mp3str" ] 11 | 12 | [params] 13 | 14 | loop=true 15 | loop_offset=0 16 | -------------------------------------------------------------------------------- /resources/music/lazer_receivers/layer9.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DissonantVoid/Godot-Sort-Visualizer/069e43b3c69df5f978aa449cf3e6c837a96a1f60/resources/music/lazer_receivers/layer9.mp3 -------------------------------------------------------------------------------- /resources/music/lazer_receivers/layer9.mp3.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="mp3" 4 | type="AudioStreamMP3" 5 | path="res://.import/layer9.mp3-0589910f678691824ed47ac10e880932.mp3str" 6 | 7 | [deps] 8 | 9 | source_file="res://resources/music/lazer_receivers/layer9.mp3" 10 | dest_files=[ "res://.import/layer9.mp3-0589910f678691824ed47ac10e880932.mp3str" ] 11 | 12 | [params] 13 | 14 | loop=true 15 | loop_offset=0 16 | -------------------------------------------------------------------------------- /resources/shaders/glow.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Shader" format=2] 2 | 3 | [resource] 4 | code = "shader_type canvas_item; 5 | 6 | const vec3 color = vec3(1, 0.93, 0.8); 7 | const float glow_speed = 2.6; 8 | 9 | // credit: stackoverflow.com/questions/10756313/javascript-jquery-map-a-range-of-numbers-to-another-range-of-numbers 10 | float map(float value, float min_, float max_, float new_min, float new_max) 11 | { 12 | return (value - min_) * (new_max - new_min) / (max_ - min_) + new_min; 13 | } 14 | 15 | void fragment() 16 | { 17 | COLOR.rgb = color; 18 | COLOR.a = map(sin(TIME * glow_speed), -1, 1, 0.2, 0.4); 19 | }" 20 | -------------------------------------------------------------------------------- /resources/shaders/lazer_line_wiggles.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Shader" format=2] 2 | 3 | [resource] 4 | code = "shader_type canvas_item; 5 | 6 | const float wiggle_dist = 5.0; 7 | uniform float offset = 1.0; 8 | 9 | void vertex() 10 | { 11 | // not the smoothest way to do this, should try again later 12 | VERTEX.x += sin(TIME * offset * VERTEX.y * 0.4) * wiggle_dist; 13 | } 14 | 15 | const float lazer_size = 0.1; 16 | const float gap_size = 1.4; 17 | const float speed = 0.5; 18 | 19 | void fragment() 20 | { 21 | if (mod(UV.x - (TIME+offset) * speed, lazer_size) > lazer_size / gap_size) 22 | COLOR.a = 0.4; 23 | }" 24 | -------------------------------------------------------------------------------- /resources/textures/music_particles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DissonantVoid/Godot-Sort-Visualizer/069e43b3c69df5f978aa449cf3e6c837a96a1f60/resources/textures/music_particles.png -------------------------------------------------------------------------------- /resources/textures/music_particles.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/music_particles.png-b735a17b04974468698d887d84dc1f0c.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://resources/textures/music_particles.png" 13 | dest_files=[ "res://.import/music_particles.png-b735a17b04974468698d887d84dc1f0c.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /resources/textures/planets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DissonantVoid/Godot-Sort-Visualizer/069e43b3c69df5f978aa449cf3e6c837a96a1f60/resources/textures/planets.png -------------------------------------------------------------------------------- /resources/textures/planets.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/planets.png-0318eef2079b9a9257e2fda178ca6efa.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://resources/textures/planets.png" 13 | dest_files=[ "res://.import/planets.png-0318eef2079b9a9257e2fda178ca6efa.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /resources/textures/puzzle_board.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DissonantVoid/Godot-Sort-Visualizer/069e43b3c69df5f978aa449cf3e6c837a96a1f60/resources/textures/puzzle_board.png -------------------------------------------------------------------------------- /resources/textures/puzzle_board.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/puzzle_board.png-dd1887394570cda6612495876b322e1a.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://resources/textures/puzzle_board.png" 13 | dest_files=[ "res://.import/puzzle_board.png-dd1887394570cda6612495876b322e1a.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /resources/textures/singing_lazers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DissonantVoid/Godot-Sort-Visualizer/069e43b3c69df5f978aa449cf3e6c837a96a1f60/resources/textures/singing_lazers.png -------------------------------------------------------------------------------- /resources/textures/singing_lazers.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/singing_lazers.png-358c2bf1ae3aa183593bd3e480da22b5.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://resources/textures/singing_lazers.png" 13 | dest_files=[ "res://.import/singing_lazers.png-358c2bf1ae3aa183593bd3e480da22b5.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /resources/textures/visualizer_images/color_bars.PNG.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/color_bars.PNG-f3f0e56b8b9d22c097971a03ae7255eb.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://resources/textures/visualizer_images/color_bars.PNG" 13 | dest_files=[ "res://.import/color_bars.PNG-f3f0e56b8b9d22c097971a03ae7255eb.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /resources/textures/visualizer_images/color_bars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DissonantVoid/Godot-Sort-Visualizer/069e43b3c69df5f978aa449cf3e6c837a96a1f60/resources/textures/visualizer_images/color_bars.png -------------------------------------------------------------------------------- /resources/textures/visualizer_images/color_bars.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/color_bars.png-2366e28c186dbce30bd8860380ab504f.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://resources/textures/visualizer_images/color_bars.png" 13 | dest_files=[ "res://.import/color_bars.png-2366e28c186dbce30bd8860380ab504f.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /resources/textures/visualizer_images/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DissonantVoid/Godot-Sort-Visualizer/069e43b3c69df5f978aa449cf3e6c837a96a1f60/resources/textures/visualizer_images/default.png -------------------------------------------------------------------------------- /resources/textures/visualizer_images/default.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/default.png-c3cbf56d658333e7d403423fd3e55cd6.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://resources/textures/visualizer_images/default.png" 13 | dest_files=[ "res://.import/default.png-c3cbf56d658333e7d403423fd3e55cd6.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /resources/textures/visualizer_images/planets.PNG.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/planets.PNG-f7a1839e110e6bf0a6081c79b8de169b.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://resources/textures/visualizer_images/planets.PNG" 13 | dest_files=[ "res://.import/planets.PNG-f7a1839e110e6bf0a6081c79b8de169b.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /resources/textures/visualizer_images/planets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DissonantVoid/Godot-Sort-Visualizer/069e43b3c69df5f978aa449cf3e6c837a96a1f60/resources/textures/visualizer_images/planets.png -------------------------------------------------------------------------------- /resources/textures/visualizer_images/planets.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/planets.png-8e2b0be650e7afab777ae4ac1e359124.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://resources/textures/visualizer_images/planets.png" 13 | dest_files=[ "res://.import/planets.png-8e2b0be650e7afab777ae4ac1e359124.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /resources/textures/visualizer_images/puzzle_pieces.PNG.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/puzzle_pieces.PNG-52a1d7ce694c569b9ff646f9fe905847.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://resources/textures/visualizer_images/puzzle_pieces.PNG" 13 | dest_files=[ "res://.import/puzzle_pieces.PNG-52a1d7ce694c569b9ff646f9fe905847.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /resources/textures/visualizer_images/puzzle_pieces.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DissonantVoid/Godot-Sort-Visualizer/069e43b3c69df5f978aa449cf3e6c837a96a1f60/resources/textures/visualizer_images/puzzle_pieces.png -------------------------------------------------------------------------------- /resources/textures/visualizer_images/puzzle_pieces.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/puzzle_pieces.png-077cc17426a6b04bde8df7e6d7cc4ccb.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://resources/textures/visualizer_images/puzzle_pieces.png" 13 | dest_files=[ "res://.import/puzzle_pieces.png-077cc17426a6b04bde8df7e6d7cc4ccb.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /resources/textures/visualizer_images/singing_lazers.PNG.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/singing_lazers.PNG-84f7debad719df2c8c254da32cf3a51b.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://resources/textures/visualizer_images/singing_lazers.PNG" 13 | dest_files=[ "res://.import/singing_lazers.PNG-84f7debad719df2c8c254da32cf3a51b.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /resources/textures/visualizer_images/singing_lazers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DissonantVoid/Godot-Sort-Visualizer/069e43b3c69df5f978aa449cf3e6c837a96a1f60/resources/textures/visualizer_images/singing_lazers.png -------------------------------------------------------------------------------- /resources/textures/visualizer_images/singing_lazers.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/singing_lazers.png-5e98fec966175126433dba9c6a1c0ce3.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://resources/textures/visualizer_images/singing_lazers.png" 13 | dest_files=[ "res://.import/singing_lazers.png-5e98fec966175126433dba9c6a1c0ce3.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /resources/textures/visualizer_images/vertical_rects.PNG.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/vertical_rects.PNG-a0270cee308091d74626780981b05afe.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://resources/textures/visualizer_images/vertical_rects.PNG" 13 | dest_files=[ "res://.import/vertical_rects.PNG-a0270cee308091d74626780981b05afe.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /resources/textures/visualizer_images/vertical_rects.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DissonantVoid/Godot-Sort-Visualizer/069e43b3c69df5f978aa449cf3e6c837a96a1f60/resources/textures/visualizer_images/vertical_rects.png -------------------------------------------------------------------------------- /resources/textures/visualizer_images/vertical_rects.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/vertical_rects.png-85af42f58b71a5ed9a0aa2ddc20cd033.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://resources/textures/visualizer_images/vertical_rects.png" 13 | dest_files=[ "res://.import/vertical_rects.png-85af42f58b71a5ed9a0aa2ddc20cd033.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /scenes/autoloads/files_tracker.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | const _sorters_path : String = "res://scenes/objects/sorters/" 4 | const _visualizers_path : String = "res://scenes/objects/visualizers/" 5 | var _sorters : Dictionary # {name:path, ..} 6 | var _visualizers : Dictionary # {name:{scene:scene path, script:script path}, ..} 7 | 8 | const screenshots_path : String = "res://resources/textures/visualizer_images/" 9 | 10 | # TODO: we store sorters and visualizers by their names, but we don't check if the name already exists 11 | 12 | func _ready(): 13 | var dir : Directory = Directory.new() 14 | assert(dir.dir_exists(_sorters_path), "sorters path has been removed or changed, either update _sorters_path or stop messing with my code :'(") 15 | assert(dir.dir_exists(_visualizers_path), "visualizers path has been removed or changed, either update _visualizers_path or stop messing with my code :'(") 16 | 17 | # sorters 18 | dir.open(_sorters_path) 19 | dir.list_dir_begin(true, true) 20 | var curr_dir : String = dir.get_next() 21 | while curr_dir.empty() == false: 22 | var sorter = load(_sorters_path + curr_dir) 23 | var metadata : Dictionary = sorter.get_metadata() 24 | if metadata["is_enabled"]: 25 | var sorter_name : String = metadata["name"] 26 | _sorters[sorter_name] = _sorters_path + curr_dir 27 | 28 | curr_dir = dir.get_next() 29 | 30 | # visualizers 31 | dir.open(_visualizers_path) 32 | dir.list_dir_begin(true, true) 33 | curr_dir = dir.get_next() 34 | while curr_dir.empty() == false: 35 | # ignore scripts 36 | if curr_dir.ends_with(".tscn"): 37 | var script_name : String = curr_dir.replace(".tscn", ".gd") 38 | assert(dir.file_exists(script_name), "visualizer " + curr_dir + " has no script, make sure that each visualizer has a script with the same name") 39 | 40 | var visualizer = load(_visualizers_path + script_name) 41 | var metadata : Dictionary = visualizer.get_metadata() 42 | if metadata["is_enabled"]: 43 | var visualizer_name : String = metadata["name"] 44 | _visualizers[visualizer_name] = { 45 | "scene": _visualizers_path + curr_dir, 46 | "script": _visualizers_path + script_name 47 | } 48 | 49 | curr_dir = dir.get_next() 50 | 51 | func get_sorters_dict() -> Dictionary: 52 | return _sorters 53 | 54 | func get_visualizers_dict() -> Dictionary: 55 | return _visualizers 56 | -------------------------------------------------------------------------------- /scenes/autoloads/utility.gd: -------------------------------------------------------------------------------- 1 | extends Node # can't be a reference since godot automaticaly adds a node to autoload scripts 2 | 3 | var rng : RandomNumberGenerator = RandomNumberGenerator.new() 4 | var viewport_size : Vector2 = Vector2(ProjectSettings.get_setting("display/window/size/width"), ProjectSettings.get_setting("display/window/size/height")) 5 | 6 | class MultiSignalYield: 7 | signal all_signals_yielded 8 | var awaiting : int = 0 9 | 10 | func add_signal(object : Object, signal_ : String): 11 | awaiting += 1 12 | object.connect(signal_, self, "on_signal_done") 13 | 14 | func on_signal_done(): 15 | awaiting -= 1 16 | if awaiting == 0: 17 | emit_signal("all_signals_yielded") 18 | 19 | func _init(): 20 | randomize() 21 | rng.randomize() 22 | 23 | func swap_elements(arr : Array, idx1 : int, idx2 : int): 24 | assert(idx1 >= 0 && idx1 < arr.size() && idx2 >= 0 && idx2 < arr.size(), "index out of bound") 25 | 26 | var temp = arr[idx1] 27 | arr[idx1] = arr[idx2] 28 | arr[idx2] = temp 29 | 30 | func switch_children(parent : Node, child1_idx : int, child2_idx : int): 31 | var low_idx_child := parent.get_child(min(child1_idx, child2_idx)) 32 | var high_idx : int = max(child1_idx, child2_idx) 33 | var high_idx_child := parent.get_child(high_idx) 34 | 35 | parent.move_child(high_idx_child, low_idx_child.get_index()) 36 | parent.move_child(low_idx_child, high_idx) 37 | 38 | func move_element(arr : Array, el_idx : int, el_new_idx : int): 39 | assert(el_idx >= 0 && el_idx < arr.size(), "el_idx must be >= 0 and < arr.size()") 40 | assert(el_new_idx >= 0 && el_new_idx <= arr.size(), "el_new_idx must be >= 0 and <= arr.size()") 41 | 42 | var element = arr[el_idx] 43 | arr.remove(el_idx) 44 | arr.insert(el_new_idx, element) 45 | 46 | func subarr_first_index_to_1d(arr : Array, subarr_idx : int) -> int: 47 | assert(subarr_idx >= 0 && subarr_idx < arr.size(), "index out of bound") 48 | 49 | # NOTE: only checks 1 depth (array of arrays), doesn't work on array of arrays of arrays and so on 50 | var index_1d : int = 0 51 | for i in subarr_idx: 52 | assert(arr[i] is Array, "array must contain arrays") 53 | index_1d += arr[i].size() 54 | 55 | return index_1d 56 | 57 | func lerp_color_arr(gradient : Array, weight : float, can_loop : bool) -> Color: 58 | var index_decimal : float = weight * (gradient.size()-1) 59 | var index : int = int(index_decimal) 60 | index_decimal -= floor(index_decimal) 61 | 62 | if can_loop == false && index == gradient.size()-1: 63 | return gradient[index] 64 | 65 | var curr_color : Color = gradient[index] 66 | var next_color : Color = gradient[(index + 1) % gradient.size()] 67 | return lerp(curr_color, next_color, index_decimal) 68 | 69 | func await_multiple_signals(objects_n_signals : Array) -> MultiSignalYield: 70 | # objects_n_signals: [object1,signal1,object2,signal2 etc...] 71 | # this helps with the multi yield issues in godot where you can't await more than 1 signal because 72 | # some signals may emit before others making the yield order problematic 73 | # Usage: yield(Utility.await_multiple_signals([...]), "all_signals_yielded") 74 | # note that this method doesn't return any information about the signal's arguments so it's 75 | # only reliable to yield multiple signals and nothing more 76 | var multi_signal_yield : MultiSignalYield = MultiSignalYield.new() 77 | var assert_message : String = "items in objects_n_signals should be in the order: [object1, signal name1, object2, signal name2, ...]" 78 | 79 | assert(objects_n_signals.size() % 2 == 0, assert_message) 80 | for i in objects_n_signals.size(): 81 | if i%2 == 0: 82 | assert(objects_n_signals[i] is Object, assert_message) 83 | multi_signal_yield.add_signal(objects_n_signals[i], objects_n_signals[i+1]) 84 | else: 85 | assert(objects_n_signals[i] is String, assert_message) 86 | 87 | return multi_signal_yield 88 | -------------------------------------------------------------------------------- /scenes/game/main.gd: -------------------------------------------------------------------------------- 1 | extends Control 2 | 3 | class Settings: 4 | # NOTE: a class instead of a dict, because dict keys are not checked untill runtime 5 | # making them prone to error and hard to keep track of reads/writes to the same key 6 | # this functions more like like a c++ Struct 7 | var time_per_step : float = 25.0 8 | var volume : float = 100 9 | var language: String = "en" 10 | 11 | var _file : ConfigFile = ConfigFile.new() 12 | var _settings_file_path : String 13 | const _section_name : String = "settings" 14 | 15 | func _init(): 16 | # create save file if doesn't exist 17 | var dir : Directory = Directory.new() 18 | if OS.has_feature("standalone"): 19 | _settings_file_path = OS.get_executable_path().get_base_dir() + "/settings" 20 | else: 21 | _settings_file_path = "res://settings" 22 | 23 | if dir.dir_exists(_settings_file_path) == false: 24 | dir.make_dir(_settings_file_path) 25 | 26 | _settings_file_path += "/settings.cfg" 27 | if dir.file_exists(_settings_file_path): 28 | _file.load(_settings_file_path) 29 | read() 30 | 31 | func read(): 32 | time_per_step = _file.get_value(_section_name, "time_per_step", time_per_step) 33 | volume = _file.get_value(_section_name, "volume", volume) 34 | language = _file.get_value(_section_name, "language", language) 35 | # ... 36 | 37 | func write(): 38 | _file.set_value(_section_name, "time_per_step", time_per_step) 39 | _file.set_value(_section_name, "volume", volume) 40 | _file.set_value(_section_name, "language", language) 41 | # ... 42 | _file.save(_settings_file_path) 43 | 44 | onready var _interface : CanvasLayer = $MainInterface 45 | onready var _continous_timer : Timer = $ContinuousTimer 46 | 47 | const _initial_visualizer : String = "VERTICAL_TITLE" 48 | const _initial_sorter : String = "BUBBLESORT" 49 | var _sorter : Sorter = null 50 | var _visualizer = null 51 | 52 | enum RunningMode {step, continuous} # step requires user input to do next sort, continous relies on timer 53 | var _running_mode : int = RunningMode.step 54 | var _is_waiting_for_visualizer : bool = false 55 | var _is_stoping_next : bool = false 56 | 57 | var _settings : Settings = Settings.new() 58 | 59 | 60 | func _ready(): 61 | # apply settings from file 62 | _apply_settings() 63 | 64 | # initial sorter/visualizer 65 | _set_visualizer(load(FilesTracker.get_visualizers_dict()[_initial_visualizer]["scene"]).instance()) 66 | _visualizer.reset() 67 | 68 | _sorter = load(FilesTracker.get_sorters_dict()[_initial_sorter]).new() 69 | _sorter.setup(_visualizer.get_content_count(), funcref(_visualizer, "determine_priority")) 70 | 71 | _interface.update_names(_initial_visualizer, _initial_sorter) 72 | 73 | func _apply_settings(): 74 | _continous_timer.wait_time = _settings.time_per_step / 1000 75 | if _settings.volume == 0: 76 | AudioServer.set_bus_mute(0, true) 77 | else: 78 | if AudioServer.is_bus_mute(0): AudioServer.set_bus_mute(0, false) 79 | AudioServer.set_bus_volume_db(0, range_lerp(_settings.volume, 0, 100, -40, 0)) 80 | TranslationServer.set_locale(_settings.language) 81 | if _sorter != null: 82 | _interface.update_names(_sorter.get_sorter_name(), _visualizer.get_metadata()["name"]) 83 | 84 | func _on_interface_sorter_changed(new_sorter): 85 | _sorter = new_sorter 86 | _reset() 87 | 88 | func _on_interface_visualizer_changed(new_visualizer): 89 | _set_visualizer(new_visualizer) 90 | _reset() 91 | 92 | func _on_interface_options_changed(settings : Settings): 93 | _settings = settings 94 | _apply_settings() 95 | 96 | _settings.write() 97 | 98 | func _on_interface_ui_visibility_changed(is_visible : bool): 99 | _visualizer.set_ui_visibility(is_visible) 100 | 101 | func _on_interface_button_pressed(button : String): 102 | match button: 103 | "options": 104 | _interface.show_options_popup(_settings) 105 | "start", "continue": 106 | _running_mode = RunningMode.continuous 107 | _continous_timer.start() 108 | "next": 109 | _next_step() 110 | "pause": 111 | _running_mode = RunningMode.step 112 | _continous_timer.stop() 113 | "stop": 114 | _is_stoping_next = true 115 | "last": 116 | _continous_timer.stop() 117 | _is_waiting_for_visualizer = true 118 | _interface.set_ui_active(false) 119 | _visualizer.update_all(_sorter.skip_to_last_step()) 120 | "restart": 121 | _reset() 122 | 123 | func _set_visualizer(visualizer): 124 | if _visualizer != null: 125 | _visualizer.queue_free() 126 | 127 | _visualizer = visualizer 128 | add_child(_visualizer) 129 | move_child(_visualizer, 0) 130 | _visualizer.connect("updated_indexes", self, "_on_visualizer_updated_indexes") 131 | _visualizer.connect("updated_all", self, "_on_visualizer_updated_all") 132 | _visualizer.connect("finished", self, "_on_visualizer_finished") 133 | 134 | func _on_visualizer_updated_indexes(): 135 | _is_waiting_for_visualizer = false 136 | _interface.set_ui_active(true) 137 | 138 | if _is_stoping_next: 139 | _is_stoping_next = false 140 | _reset() 141 | elif _running_mode == RunningMode.continuous && _continous_timer.is_stopped(): 142 | _continous_timer.start() 143 | 144 | func _on_visualizer_updated_all(): 145 | _visualizer.finish() 146 | 147 | func _on_visualizer_finished(): 148 | _is_waiting_for_visualizer = false 149 | _interface.set_ui_active(true) 150 | _interface.sorter_finished() 151 | 152 | func _on_continuous_timeout(): 153 | if _is_waiting_for_visualizer == false: 154 | _next_step() 155 | 156 | func _next_step(): 157 | var step_data : Dictionary = _sorter.next_step() 158 | assert(step_data.has("done"), "no 'done' entry in sorter.next_step() return") 159 | 160 | _is_waiting_for_visualizer = true 161 | _interface.set_ui_active(false) 162 | 163 | if step_data["done"]: 164 | if _is_stoping_next: _is_stoping_next = false # in case we press stop right before the last iteration 165 | if _running_mode == RunningMode.continuous: 166 | _continous_timer.stop() 167 | _visualizer.finish() 168 | else: 169 | assert(step_data.has("action"), "no 'action' entry in sorter.next_step() return") 170 | assert(Sorter.SortAction.values().has(step_data["action"]), " action' entry in sorter.next_step() isn't of type Sorter.SortAction") 171 | 172 | assert(step_data.has("indexes"), "no 'indexes' entry in sorter.next_step() return") 173 | assert(step_data["indexes"].size() == 2, "'indexes' entry in sorter.next_step() return must have 2 indexes") 174 | 175 | # NOTE: this line should be last in case update_indexes() emits immediately like in visualizer_vertical_lines 176 | _visualizer.update_indexes(step_data["action"], step_data["indexes"][0], step_data["indexes"][1]) 177 | 178 | func _reset(): 179 | _running_mode = RunningMode.step 180 | _continous_timer.stop() 181 | _interface.set_ui_active(true) # in case we change sorter mid sort 182 | _visualizer.reset() 183 | _sorter.setup(_visualizer.get_content_count(), funcref(_visualizer, "determine_priority")) 184 | -------------------------------------------------------------------------------- /scenes/game/main_interface.gd: -------------------------------------------------------------------------------- 1 | extends CanvasLayer 2 | 3 | signal sorter_changed(new_sorter) 4 | signal visualizer_changed(new_visualizer) 5 | signal options_changed(data) # data : dictionary 6 | signal button_pressed(button) 7 | signal ui_visibility_changed(is_visible) 8 | 9 | onready var _root_child : MarginContainer = $MarginContainer 10 | onready var _content_container : VBoxContainer = $MarginContainer/VBoxContainer/Content 11 | onready var _selected_sorter_btn : Button = $MarginContainer/VBoxContainer/Content/Upper/MarginContainer/HBoxContainer/Center/HBoxContainer/Sorter 12 | onready var _idle_buttons : HBoxContainer = $MarginContainer/VBoxContainer/Content/Lower/Lower/MarginContainer/VBoxContainer/Idle 13 | onready var _running_buttons : HBoxContainer = $MarginContainer/VBoxContainer/Content/Lower/Lower/MarginContainer/VBoxContainer/Running 14 | onready var _paused_buttons : HBoxContainer = $MarginContainer/VBoxContainer/Content/Lower/Lower/MarginContainer/VBoxContainer/Paused 15 | onready var _restart_buttons : HBoxContainer = $MarginContainer/VBoxContainer/Content/Lower/Lower/MarginContainer/VBoxContainer/Restart 16 | 17 | onready var _selected_visualizer_btn : Button = $MarginContainer/VBoxContainer/Content/Upper/MarginContainer/HBoxContainer/Center/HBoxContainer/Visualizer 18 | 19 | onready var _grabber : ColorRect = $MarginContainer/VBoxContainer/Grabber 20 | 21 | const _options_popup_scene : PackedScene = preload("res://scenes/objects/components/popups/popup_main_interface_options.tscn") 22 | const _sorter_popup_scene : PackedScene = preload("res://scenes/objects/components/popups/popup_main_interface_algorithms.tscn") 23 | const _visualizer_popup_scene : PackedScene = preload("res://scenes/objects/components/popups/popup_main_interface_visualizers.tscn") 24 | 25 | var _visualizer_control_buttons : Array # cache for set_ui_active() 26 | const _moving_time : float = 0.85 27 | var _is_moving : bool = false 28 | var _is_hidden : bool = false 29 | 30 | 31 | # TODO: add keyboard shortcuts 32 | 33 | func _ready(): 34 | # NOTE: _running_buttons are not included, because we want to allow 35 | # user to pause a running visualizer 36 | for child in _idle_buttons.get_children(): 37 | if child is Button: _visualizer_control_buttons.append(child) 38 | for child in _paused_buttons.get_children(): 39 | if child is Button: _visualizer_control_buttons.append(child) 40 | for child in _restart_buttons.get_children(): 41 | if child is Button: _visualizer_control_buttons.append(child) 42 | 43 | func update_names(visualizer_name : String, sorter_name : String): 44 | _selected_visualizer_btn.text = tr(visualizer_name) 45 | _selected_sorter_btn.text = tr(sorter_name) 46 | 47 | func sorter_finished(): 48 | _toggle_button_group(_restart_buttons) 49 | 50 | func set_ui_active(is_active : bool): 51 | for btn in _visualizer_control_buttons: 52 | btn.disabled = !is_active 53 | 54 | func show_options_popup(settings): 55 | var instance := _options_popup_scene.instance() 56 | instance.connect("ok", self, "_on_options_popup_ok") 57 | add_child(instance) 58 | instance.setup(settings) 59 | 60 | func _on_sorter_pressed(): 61 | var instance := _sorter_popup_scene.instance() 62 | add_child(instance) 63 | instance.connect("ok", self, "_on_sorter_popup_ok") 64 | 65 | func _on_visualizer_pressed(): 66 | var instance := _visualizer_popup_scene.instance() 67 | add_child(instance) 68 | instance.connect("ok", self, "_on_visualizer_popup_ok") 69 | 70 | func _on_button_clicked(button : String): 71 | match button: 72 | "options": 73 | emit_signal("button_pressed", button) 74 | "hide": 75 | if _is_moving: return 76 | _is_moving = true 77 | emit_signal("ui_visibility_changed", false) 78 | 79 | var tween : SceneTreeTween = get_tree().create_tween() 80 | tween.tween_property(_root_child, "rect_position:y", -_content_container.rect_size.y, _moving_time)\ 81 | .set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_SINE) 82 | yield(tween, "finished") 83 | _grabber.modulate.a = 1 84 | _is_hidden = true 85 | _is_moving = false 86 | 87 | "start": 88 | _toggle_button_group(_running_buttons) 89 | emit_signal("button_pressed", button) 90 | "next": 91 | if _idle_buttons.visible: 92 | _toggle_button_group(_paused_buttons) 93 | emit_signal("button_pressed", button) 94 | "pause": 95 | _toggle_button_group(_paused_buttons) 96 | emit_signal("button_pressed", button) 97 | "stop": 98 | _toggle_button_group(_idle_buttons) 99 | emit_signal("button_pressed", button) 100 | "continue": 101 | _toggle_button_group(_running_buttons) 102 | emit_signal("button_pressed", button) 103 | "last": 104 | _toggle_button_group(_restart_buttons) 105 | emit_signal("button_pressed", button) 106 | "restart": 107 | _toggle_button_group(_idle_buttons) 108 | emit_signal("button_pressed", button) 109 | 110 | func _on_sorter_popup_ok(data : Dictionary): 111 | emit_signal("sorter_changed", load(data["path"]).new()) 112 | 113 | _selected_sorter_btn.text = data["name"] 114 | _toggle_button_group(_idle_buttons) 115 | 116 | func _on_visualizer_popup_ok(data : Dictionary): 117 | emit_signal("visualizer_changed", load(data["path"]).instance()) 118 | 119 | _selected_visualizer_btn.text = data["name"] 120 | _toggle_button_group(_idle_buttons) 121 | 122 | func _on_options_popup_ok(data : Dictionary): 123 | emit_signal("options_changed", data["settings"]) 124 | 125 | # after hiding the interface, hover mouse near the very top of the screen to bring it back 126 | func _on_grabber_mouse_entered(): 127 | if _is_hidden && _is_moving == false: 128 | _is_moving = true 129 | _grabber.modulate.a = 0 130 | emit_signal("ui_visibility_changed", true) 131 | 132 | var tween : SceneTreeTween = get_tree().create_tween() 133 | tween.tween_property(_root_child, "rect_position:y", 0.0, _moving_time)\ 134 | .set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_SINE) 135 | 136 | yield(tween, "finished") 137 | _is_moving = false 138 | _is_hidden = false 139 | 140 | func _toggle_button_group(group : HBoxContainer): 141 | _idle_buttons.visible = (group == _idle_buttons) 142 | _running_buttons.visible = (group == _running_buttons) 143 | _paused_buttons.visible = (group == _paused_buttons) 144 | _restart_buttons.visible = (group == _restart_buttons) 145 | -------------------------------------------------------------------------------- /scenes/objects/components/lazer_receiver.gd: -------------------------------------------------------------------------------- 1 | extends VBoxContainer 2 | 3 | onready var _animator : AnimationPlayer = $AnimationPlayer 4 | onready var _audio_player : AudioStreamPlayer = $AudioStreamPlayer 5 | onready var _particles : CPUParticles2D = $CPUParticles2D 6 | onready var _texture : TextureRect = $Texture 7 | 8 | var _color : Color 9 | var _index : int 10 | var _is_active : bool = false 11 | 12 | 13 | func setup(is_brocken : bool, color : Color, tune_path : String = "", index : int = -1): 14 | _color = color 15 | _texture.modulate = color 16 | 17 | if is_brocken: 18 | _texture.texture.region.position.x = 192 19 | else: 20 | _animator.play("off") 21 | _audio_player.stream = load(tune_path) 22 | _index = index 23 | 24 | func lazer_changed(lazer_index : int, sync_time : float): 25 | var should_activate : bool = lazer_index == _index 26 | 27 | if should_activate == _is_active: 28 | if should_activate: _audio_player.play(sync_time) 29 | return 30 | 31 | _is_active = should_activate 32 | if _is_active: 33 | _animator.play("on") 34 | _particles.emitting = true 35 | _audio_player.play(sync_time) 36 | else: 37 | _animator.play("off") 38 | _particles.emitting = false 39 | _audio_player.stop() 40 | -------------------------------------------------------------------------------- /scenes/objects/components/lazer_receiver.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=10 format=2] 2 | 3 | [ext_resource path="res://resources/textures/singing_lazers.png" type="Texture" id=1] 4 | [ext_resource path="res://scenes/objects/components/lazer_receiver.gd" type="Script" id=2] 5 | [ext_resource path="res://resources/textures/music_particles.png" type="Texture" id=3] 6 | 7 | [sub_resource type="AtlasTexture" id=1] 8 | resource_local_to_scene = true 9 | atlas = ExtResource( 1 ) 10 | region = Rect2( 0, 0, 64, 160 ) 11 | 12 | [sub_resource type="CanvasItemMaterial" id=2] 13 | light_mode = 1 14 | particles_animation = true 15 | particles_anim_h_frames = 2 16 | particles_anim_v_frames = 1 17 | particles_anim_loop = false 18 | 19 | [sub_resource type="Curve" id=3] 20 | _data = [ Vector2( 0, 1 ), 0.0, 0.0, 0, 0, Vector2( 1, 0 ), 0.257851, 0.0, 0, 0 ] 21 | 22 | [sub_resource type="Animation" id=4] 23 | length = 0.001 24 | tracks/0/type = "value" 25 | tracks/0/path = NodePath("Texture:texture:region") 26 | tracks/0/interp = 1 27 | tracks/0/loop_wrap = true 28 | tracks/0/imported = false 29 | tracks/0/enabled = true 30 | tracks/0/keys = { 31 | "times": PoolRealArray( 0 ), 32 | "transitions": PoolRealArray( 1 ), 33 | "update": 0, 34 | "values": [ Rect2( 0, 0, 64, 160 ) ] 35 | } 36 | 37 | [sub_resource type="Animation" id=5] 38 | resource_name = "off" 39 | length = 0.1 40 | tracks/0/type = "value" 41 | tracks/0/path = NodePath("Texture:texture:region") 42 | tracks/0/interp = 1 43 | tracks/0/loop_wrap = true 44 | tracks/0/imported = false 45 | tracks/0/enabled = true 46 | tracks/0/keys = { 47 | "times": PoolRealArray( 0 ), 48 | "transitions": PoolRealArray( 1 ), 49 | "update": 0, 50 | "values": [ Rect2( 0, 0, 64, 160 ) ] 51 | } 52 | 53 | [sub_resource type="Animation" id=6] 54 | resource_name = "on" 55 | length = 0.6 56 | loop = true 57 | tracks/0/type = "value" 58 | tracks/0/path = NodePath("Texture:texture:region") 59 | tracks/0/interp = 1 60 | tracks/0/loop_wrap = true 61 | tracks/0/imported = false 62 | tracks/0/enabled = true 63 | tracks/0/keys = { 64 | "times": PoolRealArray( 0, 0.3 ), 65 | "transitions": PoolRealArray( 1, 1 ), 66 | "update": 1, 67 | "values": [ Rect2( 64, 0, 64, 160 ), Rect2( 128, 0, 64, 160 ) ] 68 | } 69 | 70 | [node name="LazerReceiver" type="VBoxContainer"] 71 | margin_right = 40.0 72 | margin_bottom = 40.0 73 | custom_constants/separation = 0 74 | script = ExtResource( 2 ) 75 | 76 | [node name="Texture" type="TextureRect" parent="."] 77 | margin_right = 64.0 78 | margin_bottom = 160.0 79 | texture = SubResource( 1 ) 80 | 81 | [node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="."] 82 | 83 | [node name="CPUParticles2D" type="CPUParticles2D" parent="."] 84 | material = SubResource( 2 ) 85 | position = Vector2( 32, 55 ) 86 | emitting = false 87 | lifetime = 4.0 88 | texture = ExtResource( 3 ) 89 | emission_shape = 2 90 | emission_rect_extents = Vector2( 10, 60 ) 91 | direction = Vector2( 0, 1 ) 92 | spread = 90.0 93 | gravity = Vector2( 0, 0 ) 94 | initial_velocity = 12.0 95 | scale_amount_curve = SubResource( 3 ) 96 | color = Color( 1, 0.933333, 0.8, 1 ) 97 | anim_offset = 1.0 98 | anim_offset_random = 1.0 99 | 100 | [node name="AnimationPlayer" type="AnimationPlayer" parent="."] 101 | anims/RESET = SubResource( 4 ) 102 | anims/off = SubResource( 5 ) 103 | anims/on = SubResource( 6 ) 104 | -------------------------------------------------------------------------------- /scenes/objects/components/lazer_shooter.gd: -------------------------------------------------------------------------------- 1 | extends TextureRect 2 | 3 | signal disappeared 4 | 5 | onready var _line : Line2D = $Line2D 6 | 7 | const _move_time : float = 0.5 8 | var _index : int 9 | var _is_brocken : bool 10 | 11 | 12 | func setup(is_brocken : bool, color : Color, index : int): 13 | if is_brocken: 14 | texture.region.position.x = texture.region.size.x 15 | else: 16 | _line.material.set_shader_param( 17 | "offset", Utility.rng.randf_range(0.1, 0.4) 18 | ) 19 | _line.default_color = color 20 | _line.show() 21 | 22 | _is_brocken = is_brocken 23 | _index = index 24 | self_modulate = color 25 | 26 | func get_order_index() -> int: 27 | return _index 28 | 29 | func disappear(): 30 | _line.hide() 31 | 32 | var tween : SceneTreeTween = get_tree().create_tween() 33 | tween.tween_property(self, "rect_position:y", rect_size.y, _move_time) 34 | 35 | yield(tween, "finished") 36 | modulate = Color.transparent # hide to avoid jerkiness as the container sorts its children 37 | emit_signal("disappeared") 38 | 39 | func appear(): 40 | var tween : SceneTreeTween = get_tree().create_tween() 41 | tween.tween_property(self, "rect_position:y", 0.0, _move_time).from(rect_size.y) 42 | 43 | modulate = Color.white 44 | yield(tween, "finished") 45 | if _is_brocken == false: 46 | _line.show() 47 | -------------------------------------------------------------------------------- /scenes/objects/components/lazer_shooter.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=6 format=2] 2 | 3 | [ext_resource path="res://resources/textures/singing_lazers.png" type="Texture" id=1] 4 | [ext_resource path="res://scenes/objects/components/lazer_shooter.gd" type="Script" id=2] 5 | [ext_resource path="res://resources/shaders/lazer_line_wiggles.tres" type="Shader" id=3] 6 | 7 | [sub_resource type="AtlasTexture" id=1] 8 | resource_local_to_scene = true 9 | atlas = ExtResource( 1 ) 10 | region = Rect2( 0, 160, 64, 99.6657 ) 11 | 12 | [sub_resource type="ShaderMaterial" id=2] 13 | resource_local_to_scene = true 14 | shader = ExtResource( 3 ) 15 | shader_param/offset = 1.0 16 | 17 | [node name="LazerShooter" type="TextureRect"] 18 | anchor_right = 1.0 19 | anchor_bottom = 1.0 20 | margin_right = -898.0 21 | margin_bottom = -485.0 22 | texture = SubResource( 1 ) 23 | script = ExtResource( 2 ) 24 | 25 | [node name="Line2D" type="Line2D" parent="."] 26 | visible = false 27 | show_behind_parent = true 28 | material = SubResource( 2 ) 29 | position = Vector2( 2, 2 ) 30 | points = PoolVector2Array( 30, 0, 30, -40, 30, -80, 30, -120, 30, -160, 30, -200, 30, -240, 30, -280, 30, -320, 30, -360, 30, -400, 30, -440 ) 31 | width = 4.0 32 | default_color = Color( 0.4, 0.501961, 1, 1 ) 33 | texture_mode = 2 34 | -------------------------------------------------------------------------------- /scenes/objects/components/orbiting_planet.gd: -------------------------------------------------------------------------------- 1 | extends TextureRect 2 | 3 | signal moved 4 | 5 | onready var _trail : Line2D = $Trail 6 | 7 | const _default_line_width : float = 8.7 8 | const _max_trail_segments : int = 12 9 | const _next_trail_time : float = 0.06 10 | var _next_trail_timer : float 11 | 12 | var _initial_size : Vector2 13 | var _pivot : Vector2 14 | var _local_center : Vector2 15 | var _orbit_speed : float 16 | 17 | const _move_speed : float = 340.0 18 | var _curve_speed : float # _curve_speed is meant to convert _move_speed into a lerp value [0-1] 19 | var _move_start : Vector2 20 | var _move_end : Vector2 21 | 22 | const _arc_height : float = 124.0 23 | var _arc_point : Vector2 24 | var _curr_arc_offset : float = 0 # [0 - 1] 25 | 26 | enum State {orbiting, moving_to} 27 | var _curr_state : int = State.orbiting 28 | 29 | 30 | func _ready(): 31 | # this is a bit hacky and has its problems 32 | # but I think it's the best we can do 33 | _trail.set_as_toplevel(true) 34 | 35 | set_process(false) 36 | _initial_size = rect_size 37 | _local_center = rect_size/2 38 | 39 | # randomize texture 40 | var tex_offsets : Array = [0, 32] 41 | texture.region.position.x = tex_offsets[ 42 | Utility.rng.randi_range(0, tex_offsets.size()-1) 43 | ] 44 | texture.region.position.y = tex_offsets[ 45 | Utility.rng.randi_range(0, tex_offsets.size()-1) 46 | ] 47 | flip_h = bool(Utility.rng.randi_range(0,1)) 48 | flip_v = bool(Utility.rng.randi_range(0,1)) 49 | 50 | func _process(delta): 51 | if _curr_state == State.orbiting: 52 | _rotate(deg2rad(_orbit_speed * delta)) 53 | 54 | elif _curr_state == State.moving_to: 55 | # we use quadratic bezier curve (see docs.godotengine.org/en/stable/tutorials/math/beziers_and_curves.html) 56 | # p0 is our pos 57 | # p1 is half distance between p0 and p2 raised by _arc_height 58 | # p2 is target pos 59 | 60 | var q0 : Vector2 = _move_start.linear_interpolate(_arc_point, _curr_arc_offset) 61 | var q1 : Vector2 = _arc_point.linear_interpolate(_move_end, _curr_arc_offset) 62 | var pos : Vector2 = q0.linear_interpolate(q1, _curr_arc_offset) 63 | rect_global_position = pos 64 | 65 | # trail 66 | _next_trail_timer -= delta 67 | if _next_trail_timer <= 0: 68 | _next_trail_timer = _next_trail_time 69 | _trail.add_point(get_center()) 70 | if _trail.points.size() > _max_trail_segments: 71 | _trail.remove_point(0) 72 | 73 | _curr_arc_offset += _curve_speed * delta 74 | if _curr_arc_offset > 1.0: 75 | rect_global_position = _move_end 76 | _curr_state = State.orbiting 77 | _trail.clear_points() 78 | 79 | emit_signal("moved") 80 | 81 | func setup(pivot : Vector2, start_position : Vector2): 82 | _pivot = pivot 83 | rect_global_position = start_position 84 | 85 | func reset(size_scale : float, orbit_speed : float, start_angle : float): 86 | # reset 87 | if _curr_state == State.moving_to: 88 | _curr_state = State.orbiting 89 | _trail.clear_points() 90 | _next_trail_timer = 0 91 | 92 | rect_size = _initial_size * size_scale 93 | _local_center = rect_size/2 94 | _trail.width = _default_line_width * size_scale 95 | _orbit_speed = orbit_speed 96 | 97 | # manual rotation so we can easily switch planets 98 | _rotate(deg2rad(start_angle)) 99 | 100 | set_process(true) 101 | 102 | func move_to(other_planet): 103 | _move_start = get_center() 104 | _move_end = other_planet.get_center() - rect_size/2 # move our center to the new position 105 | _curve_speed = _move_speed / _move_start.distance_to(_move_end) 106 | 107 | var distance : Vector2 = _move_end - _move_start 108 | _arc_point = (_move_start + distance/2) + Vector2(0, _arc_height).rotated(distance.normalized().angle()) 109 | 110 | _curr_arc_offset = 0 111 | _next_trail_timer = _next_trail_time 112 | _curr_state = State.moving_to 113 | 114 | # don't change orbit speed immediately because other_planet 115 | # will also call our get_orbit_speed() 116 | var new_orbit_speed : float = other_planet.get_orbit_speed() 117 | yield(get_tree(),"idle_frame") 118 | _orbit_speed = new_orbit_speed 119 | 120 | func get_orbit_speed() -> float: 121 | return _orbit_speed 122 | 123 | func get_size() -> Vector2: 124 | return rect_size 125 | 126 | func get_center() -> Vector2: 127 | return rect_global_position + rect_size/2 128 | 129 | func _rotate(angle_rad : float): 130 | # credit: stackoverflow.com/questions/2259476/rotating-a-point-about-another-point-2d 131 | var pivot_centered : Vector2 = _pivot - _local_center 132 | 133 | var angle_sin : float = sin(angle_rad) 134 | var angle_cos : float = cos(angle_rad) 135 | 136 | # translate point back to pivot: 137 | rect_global_position -= pivot_centered 138 | 139 | # rotate point 140 | var x_new : float = rect_global_position.x * angle_cos -\ 141 | rect_global_position.y * angle_sin 142 | var y_new : float = rect_global_position.x * angle_sin +\ 143 | rect_global_position.y * angle_cos 144 | 145 | # translate point back: 146 | rect_global_position = Vector2(x_new + pivot_centered.x, y_new + pivot_centered.y) 147 | -------------------------------------------------------------------------------- /scenes/objects/components/orbiting_planet.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=2] 2 | 3 | [ext_resource path="res://scenes/objects/components/orbiting_planet.gd" type="Script" id=1] 4 | [ext_resource path="res://resources/textures/planets.png" type="Texture" id=2] 5 | 6 | [sub_resource type="AtlasTexture" id=1] 7 | resource_local_to_scene = true 8 | atlas = ExtResource( 2 ) 9 | region = Rect2( 0, 0, 32, 32 ) 10 | 11 | [sub_resource type="Curve" id=2] 12 | _data = [ Vector2( 0, 0 ), 0.0, 0.0, 0, 0, Vector2( 1, 1 ), 0.0, 0.0, 0, 0 ] 13 | 14 | [node name="OrbitingPlanet" type="TextureRect"] 15 | margin_right = 32.0 16 | margin_bottom = 32.0 17 | texture = SubResource( 1 ) 18 | expand = true 19 | script = ExtResource( 1 ) 20 | 21 | [node name="Trail" type="Line2D" parent="."] 22 | width_curve = SubResource( 2 ) 23 | default_color = Color( 0, 0.72549, 0.745098, 1 ) 24 | -------------------------------------------------------------------------------- /scenes/objects/components/popups/popup_base.gd: -------------------------------------------------------------------------------- 1 | extends Control 2 | 3 | signal ok(data) # data : dictionary 4 | signal cancel 5 | 6 | onready var _error_panel : MarginContainer = $Error 7 | onready var _error_label : Label = $Error/Error/HBoxContainer/MarginContainer/Label 8 | 9 | const _fade_in_time : float = 0.08 10 | const _max_fade_depth : int = 3 # fade in nodes as long as they're not this deep in tree 11 | const _err_show_time : float = 0.3 12 | 13 | 14 | func _ready(): 15 | # recursively animate elements showing up 16 | var content_container : PanelContainer = $Content 17 | var tween : SceneTreeTween = get_tree().create_tween() 18 | yield(get_tree(),"idle_frame") # wait in case derived classes add nodes in _ready 19 | for child in content_container.get_children(): 20 | _tween_children(child, tween, 0) 21 | 22 | func _tween_children(node : CanvasItem, tween : SceneTreeTween, depth : int): 23 | if depth <= _max_fade_depth: 24 | for child in node.get_children(): 25 | if child is CanvasItem: 26 | _tween_children(child, tween, depth+1) 27 | 28 | node.self_modulate.a = 0 29 | tween.tween_property(node, "self_modulate:a", 1.0, _fade_in_time) 30 | else: 31 | node.modulate.a = 0 32 | tween.tween_property(node, "modulate:a", 1.0, _fade_in_time) 33 | 34 | func _on_background_gui_input(event): 35 | if event is InputEventMouseButton && event.pressed && event.button_index == BUTTON_LEFT: 36 | emit_signal("cancel") 37 | queue_free() 38 | 39 | # override and do error checking 40 | func _on_ok_pressed(): 41 | emit_signal("ok", {}) 42 | queue_free() 43 | 44 | # override 45 | func _on_cancel_pressed(): 46 | emit_signal("cancel") 47 | queue_free() 48 | 49 | func _on_close_error_pressed(): 50 | _error_panel.hide() 51 | 52 | func _error(text : String): 53 | if _error_panel.visible == false: 54 | var tween : SceneTreeTween = get_tree().create_tween() 55 | tween.tween_property(_error_panel, "modulate:a", 1.0, _err_show_time).from(0.0) 56 | 57 | _error_label.text = "Error: " + text 58 | _error_panel.show() 59 | -------------------------------------------------------------------------------- /scenes/objects/components/popups/popup_base.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=6 format=2] 2 | 3 | [ext_resource path="res://scenes/objects/components/popups/popup_base.gd" type="Script" id=1] 4 | [ext_resource path="res://resources/godot/popup_theme.tres" type="Theme" id=2] 5 | [ext_resource path="res://resources/fonts/libre-baskerville/LibreBaskerville-Bold.ttf" type="DynamicFontData" id=3] 6 | [ext_resource path="res://resources/godot/popup_panel.tres" type="StyleBox" id=4] 7 | 8 | [sub_resource type="DynamicFont" id=2] 9 | size = 22 10 | font_data = ExtResource( 3 ) 11 | 12 | [node name="PopupBase" type="MarginContainer"] 13 | anchor_right = 1.0 14 | anchor_bottom = 1.0 15 | theme = ExtResource( 2 ) 16 | script = ExtResource( 1 ) 17 | 18 | [node name="Background" type="ColorRect" parent="."] 19 | margin_right = 960.0 20 | margin_bottom = 540.0 21 | color = Color( 0.27451, 0.258824, 0.368627, 0.313726 ) 22 | 23 | [node name="Content" type="PanelContainer" parent="."] 24 | margin_left = 356.0 25 | margin_top = 226.0 26 | margin_right = 604.0 27 | margin_bottom = 314.0 28 | size_flags_horizontal = 6 29 | size_flags_vertical = 6 30 | 31 | [node name="MarginContainer" type="MarginContainer" parent="Content"] 32 | margin_left = 2.0 33 | margin_top = 2.0 34 | margin_right = 246.0 35 | margin_bottom = 86.0 36 | custom_constants/margin_right = 8 37 | custom_constants/margin_top = 8 38 | custom_constants/margin_left = 8 39 | custom_constants/margin_bottom = 8 40 | 41 | [node name="VBoxContainer" type="VBoxContainer" parent="Content/MarginContainer"] 42 | margin_left = 8.0 43 | margin_top = 8.0 44 | margin_right = 236.0 45 | margin_bottom = 76.0 46 | 47 | [node name="Title" type="Label" parent="Content/MarginContainer/VBoxContainer"] 48 | margin_right = 228.0 49 | margin_bottom = 28.0 50 | custom_colors/font_color = Color( 1, 0.933333, 0.8, 1 ) 51 | custom_fonts/font = SubResource( 2 ) 52 | custom_styles/normal = ExtResource( 4 ) 53 | text = "Ask a question here" 54 | align = 1 55 | 56 | [node name="HSeparator" type="HSeparator" parent="Content/MarginContainer/VBoxContainer"] 57 | margin_top = 32.0 58 | margin_right = 228.0 59 | margin_bottom = 36.0 60 | 61 | [node name="Choice" type="HBoxContainer" parent="Content/MarginContainer/VBoxContainer"] 62 | margin_top = 40.0 63 | margin_right = 228.0 64 | margin_bottom = 68.0 65 | 66 | [node name="OK" type="Button" parent="Content/MarginContainer/VBoxContainer/Choice"] 67 | margin_right = 112.0 68 | margin_bottom = 28.0 69 | focus_mode = 0 70 | mouse_default_cursor_shape = 2 71 | size_flags_horizontal = 3 72 | text = "OK" 73 | 74 | [node name="Cancel" type="Button" parent="Content/MarginContainer/VBoxContainer/Choice"] 75 | margin_left = 116.0 76 | margin_right = 228.0 77 | margin_bottom = 28.0 78 | focus_mode = 0 79 | mouse_default_cursor_shape = 2 80 | size_flags_horizontal = 3 81 | text = "CANCEL" 82 | 83 | [node name="Error" type="MarginContainer" parent="."] 84 | visible = false 85 | margin_left = 420.0 86 | margin_top = 524.0 87 | margin_right = 540.0 88 | margin_bottom = 540.0 89 | size_flags_horizontal = 6 90 | size_flags_vertical = 10 91 | custom_constants/margin_bottom = 12 92 | 93 | [node name="Error" type="PanelContainer" parent="Error"] 94 | margin_right = 120.0 95 | margin_bottom = 4.0 96 | rect_min_size = Vector2( 120, 0 ) 97 | 98 | [node name="HBoxContainer" type="VBoxContainer" parent="Error/Error"] 99 | margin_left = 2.0 100 | margin_top = 2.0 101 | margin_right = 118.0 102 | margin_bottom = 60.0 103 | 104 | [node name="MarginContainer" type="MarginContainer" parent="Error/Error/HBoxContainer"] 105 | margin_right = 116.0 106 | margin_bottom = 26.0 107 | custom_constants/margin_right = 6 108 | custom_constants/margin_left = 6 109 | 110 | [node name="Label" type="Label" parent="Error/Error/HBoxContainer/MarginContainer"] 111 | margin_left = 6.0 112 | margin_right = 110.0 113 | margin_bottom = 26.0 114 | size_flags_vertical = 1 115 | custom_colors/font_color = Color( 1, 0.933333, 0.8, 1 ) 116 | text = "Error: ..." 117 | align = 1 118 | 119 | [node name="Button" type="Button" parent="Error/Error/HBoxContainer"] 120 | margin_top = 30.0 121 | margin_right = 116.0 122 | margin_bottom = 58.0 123 | focus_mode = 0 124 | mouse_default_cursor_shape = 2 125 | size_flags_vertical = 10 126 | text = "Close" 127 | 128 | [connection signal="gui_input" from="Background" to="." method="_on_background_gui_input"] 129 | [connection signal="pressed" from="Content/MarginContainer/VBoxContainer/Choice/OK" to="." method="_on_ok_pressed"] 130 | [connection signal="pressed" from="Content/MarginContainer/VBoxContainer/Choice/Cancel" to="." method="_on_cancel_pressed"] 131 | [connection signal="pressed" from="Error/Error/HBoxContainer/Button" to="." method="_on_close_error_pressed"] 132 | -------------------------------------------------------------------------------- /scenes/objects/components/popups/popup_main_interface_algorithms.gd: -------------------------------------------------------------------------------- 1 | extends "res://scenes/objects/components/popups/popup_base.gd" 2 | 3 | onready var _content_container : VBoxContainer = $Content/MarginContainer/VBoxContainer 4 | onready var _options_container : VBoxContainer = $Content/MarginContainer/VBoxContainer/Algorithms/ScrollContainer/PanelContainer/VBoxContainer 5 | onready var _choice_label : Label = $Content/MarginContainer/VBoxContainer/AlgoChoice 6 | 7 | var _chosen_algo_path : String 8 | 9 | 10 | func _ready(): 11 | for key in FilesTracker.get_sorters_dict(): 12 | var btn : Button = Button.new() 13 | btn.text = key 14 | btn.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND 15 | btn.focus_mode = Control.FOCUS_NONE 16 | btn.theme = preload("res://resources/godot/algorithm_popup_option.tres") 17 | _options_container.add_child(btn) 18 | _options_container.add_child(HSeparator.new()) 19 | btn.connect("pressed", self, "_on_algo_option_pressed", [btn]) 20 | 21 | # override 22 | func _on_ok_pressed(): 23 | if _chosen_algo_path.empty(): 24 | _error("no algorithm selected") 25 | return 26 | 27 | emit_signal("ok", {"path":_chosen_algo_path, "name":_choice_label.text}) 28 | queue_free() 29 | 30 | func _on_algo_option_pressed(btn : Button): 31 | _choice_label.text = btn.text 32 | _chosen_algo_path = FilesTracker.get_sorters_dict()[btn.text] # since the btn text is also the dict key, we can just use it 33 | -------------------------------------------------------------------------------- /scenes/objects/components/popups/popup_main_interface_algorithms.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://scenes/objects/components/popups/popup_base.tscn" type="PackedScene" id=1] 4 | [ext_resource path="res://scenes/objects/components/popups/popup_main_interface_algorithms.gd" type="Script" id=2] 5 | [ext_resource path="res://resources/godot/popup_panel.tres" type="StyleBox" id=3] 6 | 7 | [node name="PopupMainInterfaceAlgorithm" instance=ExtResource( 1 )] 8 | script = ExtResource( 2 ) 9 | 10 | [node name="Content" parent="." index="1"] 11 | margin_left = 348.0 12 | margin_top = 139.0 13 | margin_right = 612.0 14 | margin_bottom = 401.0 15 | 16 | [node name="MarginContainer" parent="Content" index="0"] 17 | margin_right = 262.0 18 | margin_bottom = 260.0 19 | 20 | [node name="VBoxContainer" parent="Content/MarginContainer" index="0"] 21 | margin_right = 252.0 22 | margin_bottom = 250.0 23 | 24 | [node name="Title" parent="Content/MarginContainer/VBoxContainer" index="0"] 25 | margin_right = 244.0 26 | text = "CHOOSE_ALGO" 27 | 28 | [node name="HSeparator" parent="Content/MarginContainer/VBoxContainer" index="1"] 29 | margin_right = 244.0 30 | 31 | [node name="AlgoChoice" type="Label" parent="Content/MarginContainer/VBoxContainer" index="2"] 32 | margin_top = 40.0 33 | margin_right = 244.0 34 | margin_bottom = 66.0 35 | text = "NONE" 36 | align = 1 37 | autowrap = true 38 | 39 | [node name="Algorithms" type="MarginContainer" parent="Content/MarginContainer/VBoxContainer" index="3"] 40 | margin_top = 70.0 41 | margin_right = 244.0 42 | margin_bottom = 210.0 43 | rect_min_size = Vector2( 0, 140 ) 44 | 45 | [node name="ScrollContainer" type="ScrollContainer" parent="Content/MarginContainer/VBoxContainer/Algorithms" index="0"] 46 | margin_right = 244.0 47 | margin_bottom = 140.0 48 | scroll_horizontal_enabled = false 49 | 50 | [node name="PanelContainer" type="PanelContainer" parent="Content/MarginContainer/VBoxContainer/Algorithms/ScrollContainer" index="0"] 51 | margin_right = 244.0 52 | margin_bottom = 140.0 53 | size_flags_horizontal = 3 54 | size_flags_vertical = 3 55 | custom_styles/panel = ExtResource( 3 ) 56 | 57 | [node name="VBoxContainer" type="VBoxContainer" parent="Content/MarginContainer/VBoxContainer/Algorithms/ScrollContainer/PanelContainer" index="0"] 58 | margin_right = 244.0 59 | margin_bottom = 140.0 60 | size_flags_horizontal = 3 61 | 62 | [node name="Choice" parent="Content/MarginContainer/VBoxContainer" index="4"] 63 | margin_top = 214.0 64 | margin_right = 244.0 65 | margin_bottom = 242.0 66 | 67 | [node name="OK" parent="Content/MarginContainer/VBoxContainer/Choice" index="0"] 68 | margin_right = 120.0 69 | 70 | [node name="Cancel" parent="Content/MarginContainer/VBoxContainer/Choice" index="1"] 71 | margin_left = 124.0 72 | margin_right = 244.0 73 | -------------------------------------------------------------------------------- /scenes/objects/components/popups/popup_main_interface_options.gd: -------------------------------------------------------------------------------- 1 | extends "res://scenes/objects/components/popups/popup_base.gd" 2 | 3 | onready var _step_time_box : SpinBox = $Content/MarginContainer/VBoxContainer/GridContainer/SpinBox 4 | onready var _volume_slider : HSlider = $Content/MarginContainer/VBoxContainer/GridContainer/HSlider 5 | onready var _language_choice : OptionButton = $Content/MarginContainer/VBoxContainer/GridContainer/OptionButton 6 | onready var _locales : Array = TranslationServer.get_loaded_locales() 7 | 8 | var _settings = load("res://scenes/game/main.gd").Settings.new() 9 | 10 | 11 | func setup(settings): 12 | _step_time_box.value = settings.time_per_step 13 | _volume_slider.value = settings.volume 14 | for locale in _locales: 15 | _language_choice.add_item( 16 | TranslationServer.get_locale_name(locale) 17 | ) 18 | _language_choice.select(_locales.find(settings.language)) 19 | 20 | func _ready(): 21 | # disable selection for the spinbox text 22 | _step_time_box.get_line_edit().selecting_enabled = false 23 | 24 | # override and do error checking 25 | func _on_ok_pressed(): 26 | _settings.time_per_step = _step_time_box.value 27 | _settings.volume = _volume_slider.value 28 | _settings.language = _locales[_language_choice.selected] 29 | emit_signal("ok", {"settings":_settings}) 30 | queue_free() 31 | -------------------------------------------------------------------------------- /scenes/objects/components/popups/popup_main_interface_options.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [ext_resource path="res://scenes/objects/components/popups/popup_main_interface_options.gd" type="Script" id=1] 4 | [ext_resource path="res://scenes/objects/components/popups/popup_base.tscn" type="PackedScene" id=2] 5 | 6 | [node name="PopupMainInterfaceOptions" instance=ExtResource( 2 )] 7 | script = ExtResource( 1 ) 8 | 9 | [node name="Background" parent="." index="0"] 10 | margin_right = 1024.0 11 | margin_bottom = 576.0 12 | 13 | [node name="Content" parent="." index="1"] 14 | margin_left = 402.0 15 | margin_top = 178.0 16 | margin_right = 621.0 17 | margin_bottom = 398.0 18 | 19 | [node name="MarginContainer" parent="Content" index="0"] 20 | margin_right = 217.0 21 | margin_bottom = 218.0 22 | 23 | [node name="VBoxContainer" parent="Content/MarginContainer" index="0"] 24 | margin_right = 207.0 25 | margin_bottom = 208.0 26 | 27 | [node name="Title" parent="Content/MarginContainer/VBoxContainer" index="0"] 28 | margin_right = 199.0 29 | text = "OPTIONS" 30 | 31 | [node name="HSeparator" parent="Content/MarginContainer/VBoxContainer" index="1"] 32 | margin_right = 199.0 33 | 34 | [node name="GridContainer" type="GridContainer" parent="Content/MarginContainer/VBoxContainer" index="2"] 35 | margin_top = 40.0 36 | margin_right = 199.0 37 | margin_bottom = 158.0 38 | columns = 2 39 | 40 | [node name="Step" type="Label" parent="Content/MarginContainer/VBoxContainer/GridContainer" index="0"] 41 | margin_right = 103.0 42 | margin_bottom = 36.0 43 | text = "STEP_TIME" 44 | 45 | [node name="SpinBox" type="SpinBox" parent="Content/MarginContainer/VBoxContainer/GridContainer" index="1"] 46 | margin_left = 107.0 47 | margin_right = 199.0 48 | margin_bottom = 36.0 49 | mouse_default_cursor_shape = 2 50 | min_value = 10.0 51 | max_value = 1000.0 52 | value = 10.0 53 | rounded = true 54 | suffix = "ms" 55 | 56 | [node name="Volume" type="Label" parent="Content/MarginContainer/VBoxContainer/GridContainer" index="2"] 57 | margin_top = 40.0 58 | margin_right = 103.0 59 | margin_bottom = 76.0 60 | text = "VOLUME" 61 | 62 | [node name="HSlider" type="HSlider" parent="Content/MarginContainer/VBoxContainer/GridContainer" index="3"] 63 | margin_left = 107.0 64 | margin_top = 40.0 65 | margin_right = 199.0 66 | margin_bottom = 76.0 67 | focus_mode = 0 68 | mouse_default_cursor_shape = 2 69 | size_flags_vertical = 1 70 | value = 100.0 71 | rounded = true 72 | 73 | [node name="Language" type="Label" parent="Content/MarginContainer/VBoxContainer/GridContainer" index="4"] 74 | margin_top = 81.0 75 | margin_right = 103.0 76 | margin_bottom = 117.0 77 | text = "LANGUAGE" 78 | 79 | [node name="OptionButton" type="OptionButton" parent="Content/MarginContainer/VBoxContainer/GridContainer" index="5"] 80 | margin_left = 107.0 81 | margin_top = 80.0 82 | margin_right = 199.0 83 | margin_bottom = 118.0 84 | focus_mode = 0 85 | mouse_default_cursor_shape = 2 86 | 87 | [node name="Choice" parent="Content/MarginContainer/VBoxContainer" index="3"] 88 | margin_top = 162.0 89 | margin_right = 199.0 90 | margin_bottom = 200.0 91 | 92 | [node name="OK" parent="Content/MarginContainer/VBoxContainer/Choice" index="0"] 93 | margin_right = 97.0 94 | margin_bottom = 38.0 95 | 96 | [node name="Cancel" parent="Content/MarginContainer/VBoxContainer/Choice" index="1"] 97 | margin_left = 101.0 98 | margin_right = 199.0 99 | margin_bottom = 38.0 100 | 101 | [node name="Error" parent="." index="2"] 102 | margin_left = 452.0 103 | margin_top = 560.0 104 | margin_right = 572.0 105 | margin_bottom = 576.0 106 | 107 | [node name="Label" parent="Error/Error/HBoxContainer/MarginContainer" index="0"] 108 | margin_bottom = 36.0 109 | -------------------------------------------------------------------------------- /scenes/objects/components/popups/popup_main_interface_visualizers.gd: -------------------------------------------------------------------------------- 1 | extends "res://scenes/objects/components/popups/popup_base.gd" 2 | 3 | onready var _visualizers_container : HBoxContainer = $Content/MarginContainer/VBoxContainer/ScrollContainer/PanelContainer/MarginContainer/HBoxContainer 4 | onready var _visualizer_choice : Label = $Content/MarginContainer/VBoxContainer/VisualizerChoice 5 | 6 | const _visualizer_data_scene : PackedScene = preload("res://scenes/objects/components/popups/visualizer_data_container.tscn") 7 | 8 | var _chosen_visualizer_path : String 9 | 10 | # TODO: there is a problem with the scroll container, 11 | # try to scroll inside the scroll bar of the card with title "singing_lazers" 12 | # it should scroll the description text without moving cards 13 | # this is more of a godot issue, the ScrollContainer uses gui_input instead of unhandled_input 14 | 15 | func _ready(): 16 | for key in FilesTracker.get_visualizers_dict(): 17 | var curr_visualizer_entry : Dictionary = FilesTracker.get_visualizers_dict()[key] 18 | 19 | # TODO: this causes errors in debugger when visualizer_singing_lazers is loaded 20 | # after tracking down the problem it seems that visualizer_singing_lazers preloads 21 | # lazer_shooter (if we load it instead we won't get an error), which in turn 22 | # contains a shader that has a uniform variable (if we change it to const, we won't get an error) 23 | # it seems godot struggles with the default value of the uniform for some reason 24 | # see: https://github.com/godotengine/godot/issues/72790 25 | var metadata : Dictionary = load( 26 | curr_visualizer_entry["script"] 27 | ).get_metadata() 28 | 29 | assert( 30 | metadata.has("name") && metadata.has("image") && metadata.has("description") && metadata.has("is_enabled"), 31 | "visualizer.get_metadata() must return 4 entries even if empty: 'name' 'image' 'description' 'is_enabled'" 32 | ) 33 | 34 | var instance := _visualizer_data_scene.instance() 35 | instance.connect("pressed", self, "_on_visualizer_pressed") 36 | _visualizers_container.add_child(instance) 37 | instance.setup( 38 | metadata["name"], metadata["image"], metadata["description"], curr_visualizer_entry["scene"] 39 | ) 40 | 41 | func _on_ok_pressed(): 42 | if _chosen_visualizer_path.empty(): 43 | _error("no visualizer selected") 44 | return 45 | 46 | emit_signal("ok", {"path":_chosen_visualizer_path, "name":_visualizer_choice.text}) 47 | queue_free() 48 | 49 | func _on_visualizer_pressed(title : String, path : String): 50 | _visualizer_choice.text = title 51 | _chosen_visualizer_path = path 52 | -------------------------------------------------------------------------------- /scenes/objects/components/popups/popup_main_interface_visualizers.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://scenes/objects/components/popups/popup_base.tscn" type="PackedScene" id=1] 4 | [ext_resource path="res://scenes/objects/components/popups/popup_main_interface_visualizers.gd" type="Script" id=2] 5 | [ext_resource path="res://resources/godot/popup_panel.tres" type="StyleBox" id=3] 6 | 7 | [node name="PopupMainInterfaceVisualizer" instance=ExtResource( 1 )] 8 | script = ExtResource( 2 ) 9 | 10 | [node name="Background" parent="." index="0"] 11 | margin_right = 1024.0 12 | margin_bottom = 576.0 13 | 14 | [node name="Content" parent="." index="1"] 15 | margin_left = 202.0 16 | margin_top = 211.0 17 | margin_right = 822.0 18 | margin_bottom = 365.0 19 | rect_min_size = Vector2( 620, 0 ) 20 | 21 | [node name="MarginContainer" parent="Content" index="0"] 22 | margin_right = 618.0 23 | margin_bottom = 152.0 24 | 25 | [node name="VBoxContainer" parent="Content/MarginContainer" index="0"] 26 | margin_right = 608.0 27 | margin_bottom = 142.0 28 | 29 | [node name="Title" parent="Content/MarginContainer/VBoxContainer" index="0"] 30 | margin_right = 600.0 31 | text = "CHOOSE_VIZU" 32 | 33 | [node name="HSeparator" parent="Content/MarginContainer/VBoxContainer" index="1"] 34 | margin_right = 600.0 35 | 36 | [node name="VisualizerChoice" type="Label" parent="Content/MarginContainer/VBoxContainer" index="2"] 37 | margin_top = 40.0 38 | margin_right = 600.0 39 | margin_bottom = 76.0 40 | text = "NONE" 41 | align = 1 42 | 43 | [node name="ScrollContainer" type="ScrollContainer" parent="Content/MarginContainer/VBoxContainer" index="3"] 44 | margin_top = 80.0 45 | margin_right = 600.0 46 | margin_bottom = 92.0 47 | scroll_vertical_enabled = false 48 | 49 | [node name="PanelContainer" type="PanelContainer" parent="Content/MarginContainer/VBoxContainer/ScrollContainer" index="0"] 50 | margin_right = 600.0 51 | margin_bottom = 12.0 52 | size_flags_horizontal = 3 53 | size_flags_vertical = 3 54 | custom_styles/panel = ExtResource( 3 ) 55 | 56 | [node name="MarginContainer" type="MarginContainer" parent="Content/MarginContainer/VBoxContainer/ScrollContainer/PanelContainer" index="0"] 57 | margin_right = 600.0 58 | margin_bottom = 12.0 59 | custom_constants/margin_right = 6 60 | custom_constants/margin_top = 6 61 | custom_constants/margin_left = 6 62 | custom_constants/margin_bottom = 6 63 | 64 | [node name="HBoxContainer" type="HBoxContainer" parent="Content/MarginContainer/VBoxContainer/ScrollContainer/PanelContainer/MarginContainer" index="0"] 65 | margin_left = 6.0 66 | margin_top = 6.0 67 | margin_right = 594.0 68 | margin_bottom = 6.0 69 | 70 | [node name="Choice" parent="Content/MarginContainer/VBoxContainer" index="4"] 71 | margin_top = 96.0 72 | margin_right = 600.0 73 | margin_bottom = 134.0 74 | 75 | [node name="OK" parent="Content/MarginContainer/VBoxContainer/Choice" index="0"] 76 | margin_right = 298.0 77 | margin_bottom = 38.0 78 | 79 | [node name="Cancel" parent="Content/MarginContainer/VBoxContainer/Choice" index="1"] 80 | margin_left = 302.0 81 | margin_right = 600.0 82 | margin_bottom = 38.0 83 | 84 | [node name="Error" parent="." index="2"] 85 | margin_left = 452.0 86 | margin_top = 560.0 87 | margin_right = 572.0 88 | margin_bottom = 576.0 89 | 90 | [node name="Label" parent="Error/Error/HBoxContainer/MarginContainer" index="0"] 91 | margin_bottom = 36.0 92 | -------------------------------------------------------------------------------- /scenes/objects/components/popups/visualizer_data_container.gd: -------------------------------------------------------------------------------- 1 | extends Control 2 | 3 | signal pressed(title, path) 4 | 5 | onready var _title_label : Label = $HBoxContainer/TextContainer/VBoxContainer/Title 6 | onready var _description_label : RichTextLabel = $HBoxContainer/TextContainer/VBoxContainer/MarginContainer/Desc 7 | onready var _image : TextureRect = $HBoxContainer/ImageContainer/MarginContainer/Image 8 | onready var _panel_stylebox : StyleBoxFlat = $HBoxContainer/TextContainer.get("custom_styles/panel") 9 | 10 | var _title : String 11 | var _path : String 12 | 13 | 14 | func setup(name : String, image_path : String, description : String, path : String): 15 | _title = tr(name) 16 | _title_label.text = _title 17 | if image_path.empty() == false: 18 | _image.texture = load(FilesTracker.screenshots_path + image_path) 19 | _description_label.bbcode_text = tr(description) 20 | _path = path 21 | 22 | func _on_gui_input(event): 23 | if event is InputEventMouseButton && event.pressed && event.button_index == BUTTON_LEFT: 24 | emit_signal("pressed", _title, _path) 25 | 26 | func _on_mouse(is_in : bool): 27 | _panel_stylebox.bg_color =\ 28 | Color("ff6973") if is_in else Color("46425e") 29 | -------------------------------------------------------------------------------- /scenes/objects/components/popups/visualizer_data_container.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=10 format=2] 2 | 3 | [ext_resource path="res://scenes/objects/components/popups/visualizer_data_container.gd" type="Script" id=1] 4 | [ext_resource path="res://resources/godot/popup_theme.tres" type="Theme" id=2] 5 | [ext_resource path="res://resources/fonts/libre-baskerville/LibreBaskerville-Bold.ttf" type="DynamicFontData" id=3] 6 | [ext_resource path="res://resources/fonts/libre-baskerville/LibreBaskerville-Regular.ttf" type="DynamicFontData" id=4] 7 | [ext_resource path="res://resources/textures/visualizer_images/default.png" type="Texture" id=5] 8 | 9 | [sub_resource type="StyleBoxFlat" id=2] 10 | resource_local_to_scene = true 11 | bg_color = Color( 0.27451, 0.258824, 0.368627, 1 ) 12 | 13 | [sub_resource type="DynamicFont" id=1] 14 | size = 18 15 | outline_color = Color( 1, 0.933333, 0.8, 1 ) 16 | font_data = ExtResource( 3 ) 17 | 18 | [sub_resource type="DynamicFont" id=3] 19 | font_data = ExtResource( 4 ) 20 | 21 | [sub_resource type="StyleBoxFlat" id=4] 22 | bg_color = Color( 1, 0.690196, 0.639216, 1 ) 23 | 24 | [node name="VisualizerDataContainer" type="MarginContainer"] 25 | anchor_right = 1.0 26 | anchor_bottom = 1.0 27 | margin_right = -702.0 28 | margin_bottom = -408.0 29 | rect_min_size = Vector2( 400, 0 ) 30 | mouse_default_cursor_shape = 2 31 | size_flags_horizontal = 0 32 | size_flags_vertical = 0 33 | theme = ExtResource( 2 ) 34 | script = ExtResource( 1 ) 35 | 36 | [node name="HBoxContainer" type="HBoxContainer" parent="."] 37 | margin_right = 400.0 38 | margin_bottom = 138.0 39 | custom_constants/separation = 0 40 | 41 | [node name="TextContainer" type="PanelContainer" parent="HBoxContainer"] 42 | margin_right = 267.0 43 | margin_bottom = 138.0 44 | mouse_filter = 2 45 | size_flags_horizontal = 3 46 | custom_styles/panel = SubResource( 2 ) 47 | 48 | [node name="VBoxContainer" type="VBoxContainer" parent="HBoxContainer/TextContainer"] 49 | margin_right = 267.0 50 | margin_bottom = 138.0 51 | size_flags_horizontal = 3 52 | 53 | [node name="Title" type="Label" parent="HBoxContainer/TextContainer/VBoxContainer"] 54 | margin_right = 267.0 55 | margin_bottom = 23.0 56 | custom_fonts/font = SubResource( 1 ) 57 | text = "Title" 58 | align = 1 59 | autowrap = true 60 | 61 | [node name="MarginContainer" type="MarginContainer" parent="HBoxContainer/TextContainer/VBoxContainer"] 62 | margin_top = 27.0 63 | margin_right = 267.0 64 | margin_bottom = 138.0 65 | size_flags_vertical = 3 66 | custom_constants/margin_right = 7 67 | custom_constants/margin_top = 4 68 | custom_constants/margin_left = 7 69 | custom_constants/margin_bottom = 7 70 | 71 | [node name="Desc" type="RichTextLabel" parent="HBoxContainer/TextContainer/VBoxContainer/MarginContainer"] 72 | margin_left = 7.0 73 | margin_top = 4.0 74 | margin_right = 260.0 75 | margin_bottom = 104.0 76 | custom_colors/default_color = Color( 0.27451, 0.258824, 0.368627, 1 ) 77 | custom_fonts/normal_font = SubResource( 3 ) 78 | custom_styles/normal = SubResource( 4 ) 79 | bbcode_enabled = true 80 | bbcode_text = "Description" 81 | text = "Description" 82 | 83 | [node name="ImageContainer" type="PanelContainer" parent="HBoxContainer"] 84 | margin_left = 267.0 85 | margin_right = 400.0 86 | margin_bottom = 138.0 87 | mouse_filter = 2 88 | size_flags_horizontal = 0 89 | size_flags_vertical = 0 90 | custom_styles/panel = SubResource( 2 ) 91 | 92 | [node name="MarginContainer" type="MarginContainer" parent="HBoxContainer/ImageContainer"] 93 | margin_right = 133.0 94 | margin_bottom = 138.0 95 | mouse_filter = 2 96 | custom_constants/margin_right = 5 97 | custom_constants/margin_top = 5 98 | custom_constants/margin_bottom = 5 99 | 100 | [node name="Image" type="TextureRect" parent="HBoxContainer/ImageContainer/MarginContainer"] 101 | margin_top = 5.0 102 | margin_right = 128.0 103 | margin_bottom = 133.0 104 | rect_min_size = Vector2( 128, 128 ) 105 | texture = ExtResource( 5 ) 106 | expand = true 107 | stretch_mode = 7 108 | 109 | [connection signal="gui_input" from="." to="." method="_on_gui_input"] 110 | [connection signal="mouse_entered" from="." to="." method="_on_mouse" binds= [ true ]] 111 | [connection signal="mouse_exited" from="." to="." method="_on_mouse" binds= [ false ]] 112 | -------------------------------------------------------------------------------- /scenes/objects/sorters/sorter.gd: -------------------------------------------------------------------------------- 1 | class_name Sorter 2 | extends Reference 3 | 4 | # base class for all sorting algorithms 5 | 6 | var _data_size : int 7 | var _priority_callback : FuncRef 8 | 9 | enum SortAction {switch, move} 10 | 11 | 12 | # override 13 | static func get_metadata() -> Dictionary: 14 | # "name" 15 | # "is_enabled": sorter can be used 16 | return {"name":"", "is_enabled":false} 17 | 18 | # override 19 | func setup(data_size : int, priority_callback : FuncRef): 20 | # NOTE: this is called any time we press restart so don't forget to cleanup 21 | _data_size = data_size 22 | _priority_callback = priority_callback 23 | 24 | # override, return {"done":is done sorting, 25 | # (if "done" is true we don't need to include next entries) 26 | # "action": SortAction.switch (switch indexes[0] and [1]), 27 | # SortAction.move (move indexes[0] behind [1]) 28 | # "indexes": array of 2 indexes that were switched} 29 | # NOTE: in "indexes" the first index should preferably be smaller than the second, some visualizers 30 | # use that, like visualizer_rect which uses that for accurate coloring 31 | # also indexes[0] should never equal to indexes[1] 32 | func next_step() -> Dictionary: 33 | return {} 34 | 35 | # override, do all sorting and return new indexes 36 | # this is where you implement the pure sorting algorithm 37 | # without all the state keeping needed for next_step() 38 | func skip_to_last_step() -> Array: 39 | return [] 40 | -------------------------------------------------------------------------------- /scenes/objects/sorters/sorter_bogo_sort.gd: -------------------------------------------------------------------------------- 1 | extends "res://scenes/objects/sorters/sorter.gd" 2 | 3 | # https://en.wikipedia.org/wiki/Bogosort 4 | # this is more of a joke if anything, we simply shuffle indexes randomly 5 | # and wish for a miracle. keep in mind that there is a non zero 6 | # possibility that a bogo sort will sort your array faster than the majority of other 7 | # algorithm 8 | # 9 | # time complexity: Average: O(N * N!) 10 | # Worst: O(infinity) 11 | # Best: O(N) 12 | 13 | # NOTE: using any of the functions below in a large array (by large I mean bigger than 5 or so) 14 | # will take so long to sort that the game window will stop responding 15 | 16 | 17 | # override 18 | static func get_metadata() -> Dictionary: 19 | return {"name":"BOGOSORT", "is_enabled":true} 20 | 21 | # override 22 | func next_step() -> Dictionary: 23 | # check if array is sorted 24 | var is_sorted : bool = true 25 | for i in range(1, _data_size): 26 | if _priority_callback.call_func(i-1, i): 27 | is_sorted = false 28 | break 29 | 30 | if is_sorted: 31 | return {"done":true} 32 | 33 | var idx1 : int = Utility.rng.randi_range(0, _data_size-1) 34 | var idx2 : int = Utility.rng.randi_range(0, _data_size-2) 35 | if idx2 >= idx1: idx2 += 1 36 | 37 | return {"done":false, "action":SortAction.switch, "indexes":[idx1, idx2]} 38 | 39 | # override 40 | func skip_to_last_step() -> Array: 41 | var indexes : Array 42 | indexes.resize(_data_size) 43 | for i in _data_size: indexes[i] = i 44 | 45 | while true: 46 | # check if array is sorted 47 | var is_sorted : bool = true 48 | for i in range(1, indexes.size()): 49 | if _priority_callback.call_func(indexes[i-1], indexes[i]): 50 | is_sorted = false 51 | break 52 | 53 | if is_sorted: break 54 | indexes.shuffle() 55 | 56 | return indexes 57 | -------------------------------------------------------------------------------- /scenes/objects/sorters/sorter_bubble_sort.gd: -------------------------------------------------------------------------------- 1 | extends "res://scenes/objects/sorters/sorter.gd" 2 | 3 | # https://en.wikipedia.org/wiki/Bubble_sort 4 | # the simplest sorting algorithm that works by repeatedly 5 | # swapping adjacent elements if they are in the wrong order 6 | # 7 | # time complexity: Average: O(N^2) 8 | # Worst: O(N^2) 9 | # Best: O(N) 10 | 11 | var _index : int 12 | 13 | 14 | # override 15 | static func get_metadata() -> Dictionary: 16 | return {"name":"BUBBLESORT", "is_enabled":true} 17 | 18 | # override 19 | func setup(data_size : int, priority_callback : FuncRef): 20 | .setup(data_size, priority_callback) 21 | 22 | _index = 0 23 | 24 | # override 25 | func next_step() -> Dictionary: 26 | var indexes : Array 27 | indexes.resize(2) 28 | 29 | var changed : bool = false 30 | for i in range(_index, _data_size-1): 31 | if _priority_callback.call_func(i, i+1): 32 | changed = true 33 | indexes[0] = i 34 | indexes[1] = i+1 35 | _index = i+1 36 | break 37 | 38 | # if no change happened then we know we're done 39 | # but! if we didn't start checking from 0, recheck 40 | if changed == false: 41 | if _index > 0: 42 | _index = 0 43 | return next_step() 44 | else: 45 | return {"done":true} 46 | else: 47 | return {"done":false, "action":SortAction.switch, "indexes":indexes} 48 | 49 | # override 50 | func skip_to_last_step() -> Array: 51 | var indexes : Array 52 | indexes.resize(_data_size) 53 | for i in _data_size: indexes[i] = i 54 | 55 | while true: 56 | var changed : bool = false 57 | for i in indexes.size()-1: 58 | if _priority_callback.call_func(indexes[i], indexes[i+1]): 59 | changed = true 60 | Utility.swap_elements(indexes, i, i+1) 61 | if changed == false: break 62 | 63 | return indexes 64 | -------------------------------------------------------------------------------- /scenes/objects/sorters/sorter_gnome_sort.gd: -------------------------------------------------------------------------------- 1 | extends "res://scenes/objects/sorters/sorter.gd" 2 | 3 | # https://en.wikipedia.org/wiki/Gnome_sort 4 | # almost the same as insert_sort, except that we use 5 | # a single loop to iterate instead of nested loops 6 | # 7 | # time complexity: Average: O(N^2) 8 | # Worst: O(N^2) 9 | # Best: O(N) 10 | 11 | var _index : int 12 | 13 | 14 | # override 15 | static func get_metadata() -> Dictionary: 16 | return {"name":"GNOMESORT", "is_enabled":true} 17 | 18 | # override 19 | func setup(data_size : int, priority_callback : FuncRef): 20 | .setup(data_size, priority_callback) 21 | 22 | _index = 0 23 | 24 | # override 25 | func next_step() -> Dictionary: 26 | # really surprised by how much simpler next_step() is compared to 27 | # insert_sort.next_step(), such big difference just by changing one rule 28 | 29 | if _index == _data_size-1: return {"done":true} 30 | 31 | if _priority_callback.call_func(_index, _index+1): 32 | var indexes_to_change : Array = [_index, _index+1] 33 | 34 | if _index > 0: _index -= 1 35 | else: _index += 1 36 | return {"done":false, "action":SortAction.switch, "indexes":indexes_to_change} 37 | else: 38 | _index += 1 39 | return next_step() 40 | 41 | # override 42 | func skip_to_last_step() -> Array: 43 | var indexes : Array 44 | indexes.resize(_data_size) 45 | for i in indexes.size(): indexes[i] = i 46 | 47 | # we rely on i+1, so there should be at least 2 items in the array 48 | if indexes.size() < 2: return indexes 49 | 50 | var i : int = 0 51 | while true: 52 | if i == _data_size-1: 53 | return indexes 54 | 55 | if _priority_callback.call_func(indexes[i], indexes[i+1]): 56 | Utility.swap_elements(indexes, i, i+1) 57 | if i > 0: i -= 1 58 | else: i += 1 59 | else: 60 | i += 1 61 | 62 | return indexes 63 | -------------------------------------------------------------------------------- /scenes/objects/sorters/sorter_heap_sort.gd: -------------------------------------------------------------------------------- 1 | extends "res://scenes/objects/sorters/sorter.gd" 2 | 3 | # https://en.wikipedia.org/wiki/Heapsort 4 | # similar to selection sort, we split the array virtualy into a 5 | # sorted and not sorted halves, each step we take the smallest value 6 | # in the unsorted half and move it ... 7 | # but instead of linearly iterating over the unsorted half, we use a heap 8 | # data structure 9 | # NOTE: in Array.sort(), godot seems to use heap-sort as well as quick-sort 10 | # mixed together in an intro-sort. I could be wrong but this is what 11 | # I can see here: github.com/godotengine/godot/blob/master/core/templates/sort_array.h 12 | # 13 | # time complexity: O(N LOG N) 14 | 15 | var _index : int 16 | var _heap_arr : Array 17 | 18 | 19 | # override 20 | static func get_metadata() -> Dictionary: 21 | return {"name":"HEAPSORT", "is_enabled":true} 22 | 23 | # override 24 | func setup(data_size : int, priority_callback : FuncRef): 25 | .setup(data_size, priority_callback) 26 | 27 | _index = 0 28 | 29 | _heap_arr.clear() 30 | _heap_arr.resize(_data_size) 31 | for i in _data_size: _heap_arr[i] = i 32 | _heapify(_heap_arr) 33 | 34 | # override 35 | func next_step() -> Dictionary: 36 | if _index == _data_size: return {"done":true} 37 | 38 | var smallest_idx : int = _h_pop_root(_heap_arr) 39 | 40 | # once the smallest index is poped, and the visualizer switches the 2 children 41 | # we update the index in _heap_arr to sync with the content of the visualizer 42 | var index_pos_in_heap : int = _heap_arr.find(_index) 43 | if index_pos_in_heap != -1: 44 | _heap_arr[index_pos_in_heap] = smallest_idx 45 | 46 | _index += 1 47 | 48 | if smallest_idx == _index-1: 49 | return next_step() 50 | else: 51 | return {"done":false, "action":SortAction.switch, "indexes":[_index-1, smallest_idx]} 52 | 53 | # override 54 | func skip_to_last_step() -> Array: 55 | var indexes : Array 56 | indexes.resize(_data_size) 57 | for i in _data_size: indexes[i] = i 58 | _heapify(indexes) 59 | 60 | var sorted_arr : Array 61 | sorted_arr.resize(_data_size) 62 | for i in sorted_arr.size(): 63 | sorted_arr[i] = _h_pop_root(indexes) 64 | 65 | return sorted_arr 66 | 67 | func _heapify(arr : Array): 68 | # NOTE: this is a min-heap implementation, meaning the root is the smallest value 69 | # start from the middle, because the middle point is guaranteed to be the last non-leaf node 70 | for i in range((arr.size()/2)-1, -1, -1): 71 | _h_sift_down(arr, i) 72 | 73 | func _h_pop_root(arr : Array) -> int: 74 | var root_idx : int = arr[0] 75 | 76 | # swap root with last element, then descend new root down the tree untill it's in the right spot 77 | Utility.swap_elements(arr, 0, arr.size()-1) 78 | # delete previous root 79 | arr.remove(arr.size()-1) 80 | _h_sift_down(arr, 0) 81 | 82 | return root_idx 83 | 84 | func _h_sift_down(arr : Array, index : int): # no I didn't loose my teeth, it's sift not shift 85 | # ascend the tree untill parent is smaller or we're root 86 | # NOTE: it is common when implementing a heap sort to not actually use a binary tree object 87 | # instead we simply use an array, we can then traverse the array as if it was a tree 88 | # for example to find the parent of an index we can do (index - 1) / 2 89 | # and to find the left child (as we do bellow) we can do index * 2 + 1 and so on 90 | var left_child : int 91 | var right_child : int 92 | while true: 93 | left_child = index * 2 + 1 94 | if left_child >= arr.size(): left_child = -1 95 | 96 | right_child = index * 2 + 2 97 | if right_child >= arr.size(): right_child = -1 98 | 99 | # find the smallest child if any 100 | var smallest_idx : int 101 | if left_child == -1 && right_child == -1: 102 | break 103 | elif left_child == -1: 104 | smallest_idx = right_child 105 | elif right_child == -1: 106 | smallest_idx = left_child 107 | else: 108 | if _priority_callback.call_func(arr[right_child], arr[left_child]): 109 | smallest_idx = left_child 110 | else: 111 | smallest_idx = right_child 112 | 113 | # swap with the smallest child 114 | 115 | if (_priority_callback.call_func(arr[index], arr[smallest_idx])): 116 | Utility.swap_elements(arr, index, smallest_idx) 117 | index = smallest_idx 118 | continue 119 | 120 | # if we reach this, both children are bigger 121 | break 122 | -------------------------------------------------------------------------------- /scenes/objects/sorters/sorter_insert_sort.gd: -------------------------------------------------------------------------------- 1 | extends "res://scenes/objects/sorters/sorter.gd" 2 | 3 | # https://en.wikipedia.org/wiki/Insertion_sort 4 | # the array is virtually split into a sorted and an unsorted sides, each iteration 5 | # the algorithm removes the first element from the unsorted part 6 | # and inserts it into the correct position in the sorted part, shifting other elements as necessary. 7 | # 8 | # time complexity: Average: O(N^2) 9 | # Worst: O(N^2) 10 | # Best: O(N) 11 | 12 | var _index : int 13 | var _sub_idx : int 14 | 15 | 16 | # override 17 | static func get_metadata() -> Dictionary: 18 | return {"name":"INSERTIONSORT", "is_enabled":true} 19 | 20 | # override 21 | func setup(data_size : int, priority_callback : FuncRef): 22 | .setup(data_size, priority_callback) 23 | 24 | _index = 0 25 | _sub_idx = -1 26 | 27 | # override 28 | func next_step() -> Dictionary: 29 | if _index == _data_size-1: return {"done":true} 30 | 31 | if _sub_idx == -1: 32 | if _priority_callback.call_func(_index, _index+1): 33 | _sub_idx = _index 34 | return {"done":false, "action":SortAction.switch, "indexes":[_index, _index+1]} 35 | else: _index += 1 36 | else: 37 | if _sub_idx == 0: _sub_idx = -1 38 | 39 | for j in range(_sub_idx, 0, -1): 40 | var indexes_to_switch : Array 41 | if _priority_callback.call_func(j-1, j): 42 | indexes_to_switch = [j-1, j] 43 | 44 | if j == 1: 45 | _sub_idx = -1 46 | _index += 1 47 | else: _sub_idx -= 1 48 | 49 | if indexes_to_switch: return {"done":false, "action":SortAction.switch, "indexes":indexes_to_switch} 50 | 51 | # if we reached this point, it means that i and i+1 are sorted, 52 | # or all indexes before i are sorted 53 | return next_step() 54 | 55 | # override 56 | func skip_to_last_step() -> Array: 57 | var indexes : Array 58 | indexes.resize(_data_size) 59 | for i in indexes.size(): indexes[i] = i 60 | 61 | for i in indexes.size()-1: # NOTE: size()-1 is actually size()-2 because 'in' is exclusive 62 | if _priority_callback.call_func(indexes[i], indexes[i+1]): 63 | Utility.swap_elements(indexes, i, i+1) 64 | # keep swaping backward untill [i] is in the right position 65 | for j in range(i, 0, -1): 66 | if _priority_callback.call_func(indexes[j-1], indexes[j]): 67 | Utility.swap_elements(indexes, j, j-1) 68 | 69 | return indexes 70 | -------------------------------------------------------------------------------- /scenes/objects/sorters/sorter_merge_sort.gd: -------------------------------------------------------------------------------- 1 | extends "res://scenes/objects/sorters/sorter.gd" 2 | 3 | # https://en.wikipedia.org/wiki/Merge_sort 4 | # a divide-and-conquer sorting algorithm that works by dividing the unsorted list 5 | # into n sub-lists, each containing one element, and then repeatedly merging sub-lists 6 | # into sorted sub-lists until there is only one sub-list remaining 7 | # 8 | # time complexity: Average: O(N log N) 9 | # Worst: O(N log N) 10 | # Best: O(N log N) 11 | 12 | enum Action { 13 | SPLIT, 14 | MERGE, 15 | } 16 | 17 | # _pending_moves contains the moves to perform to sort the whole array. The next move is at the end of the array 18 | # It contains "tuples" starting with the action. 19 | # If the action is SPLIT, the remaining data are the index of the first element of the sub-array to split 20 | # and the size of the sub-array 21 | # If the action is MERGE, the remaining data are the index of the first element of the first sub-array 22 | # the size of the first sub-array 23 | # the index of the first element of the second sub-array 24 | # and the size of the second sub-array 25 | var _pending_moves : Array # [[action, idx1, idx2], .. ] 26 | 27 | 28 | # override 29 | static func get_metadata() -> Dictionary: 30 | return {"name":"MERGESORT", "is_enabled":true} 31 | 32 | # override 33 | func setup(data_size : int, priority_callback : FuncRef): 34 | .setup(data_size, priority_callback) 35 | 36 | _pending_moves.clear() 37 | # The first move is to split in half the whole array 38 | _pending_moves.append([Action.SPLIT, 0, _data_size]) 39 | 40 | # override 41 | func next_step() -> Dictionary: 42 | if _pending_moves.empty(): 43 | return {"done": true} 44 | 45 | var move = _pending_moves.pop_back() 46 | 47 | if move[0] == Action.SPLIT: 48 | var start_subarray: int = move[1] 49 | var size_subarray: int = move[2] 50 | 51 | var size_first: int = floor(size_subarray / 2.0) 52 | var size_second: int 53 | if size_first * 2 != size_subarray: 54 | size_second = size_first + 1 55 | else: 56 | size_second = size_first 57 | 58 | var start_second := start_subarray + size_first 59 | 60 | _pending_moves.append([Action.MERGE, start_subarray, start_second, size_first, size_second]) 61 | if size_second > 1: 62 | _pending_moves.append([Action.SPLIT, start_second, size_second]) 63 | if size_first > 1: 64 | _pending_moves.append([Action.SPLIT, start_subarray, size_first]) 65 | 66 | return next_step() 67 | 68 | else: # move[0] == Action.MERGE 69 | var start_first: int = move[1] 70 | var start_second: int = move[2] 71 | var size_first: int = move[3] 72 | var size_second: int = move[4] 73 | 74 | if size_first == 0 || size_second == 0 || start_second >= _data_size: 75 | # Only one of the array contains values 76 | # As the other array is already sorted, we have nothing to do 77 | return next_step() 78 | 79 | # Both arrays contain values. We pick the smallest value between start_first and start_second 80 | # if array[_start_first] < array[_start_second] 81 | if _priority_callback.call_func(start_second, start_first): 82 | # We do not have to swap anything as the smallest value is already at the front 83 | _pending_moves.append([Action.MERGE, start_first + 1, start_second, size_first - 1, size_second]) 84 | return next_step() 85 | else: 86 | # This time, we have to move the head of the second array to where the head of the first array currently lies 87 | # As we move every unsorted value to the right, the index of the first array also moves to the right 88 | _pending_moves.append([Action.MERGE, start_first + 1, start_second + 1, size_first, size_second - 1]) 89 | return {"done":false, "action":SortAction.move, "indexes": [start_second, start_first]} 90 | 91 | 92 | # override 93 | func skip_to_last_step() -> Array: 94 | return _divide_n_conquer_full(0, _data_size-1) 95 | 96 | func _divide_n_conquer_full(low_bound : int, high_bound : int) -> Array: 97 | if low_bound == high_bound: # 1 item left 98 | return [low_bound] 99 | else: 100 | var middle_idx : int = low_bound + (high_bound - low_bound) / 2 101 | var first_half : Array = _divide_n_conquer_full(low_bound, middle_idx) 102 | var second_half : Array = _divide_n_conquer_full(middle_idx+1, high_bound) 103 | 104 | return _merge(first_half, second_half) 105 | 106 | func _merge(first_half : Array, second_half : Array): 107 | # merge 2 arrays together, merging is done by comparing the first item of one array 108 | # to the first item of another, we move the smaller into 'combined' and increment 109 | # its index (first_h_idx or second_h_idx depending on which item is bigger) and so on.. 110 | # note that since arrays are already ordered (first item is smallest and last is largest) 111 | # we end up with an ordered combined array 112 | var combined : Array 113 | var first_h_idx : int = 0 114 | var second_h_idx : int = 0 115 | while first_h_idx < first_half.size() && second_h_idx < second_half.size(): 116 | if _priority_callback.call_func(first_half[first_h_idx], second_half[second_h_idx]): 117 | combined.append(second_half[second_h_idx]) 118 | second_h_idx += 1 119 | else: 120 | combined.append(first_half[first_h_idx]) 121 | first_h_idx += 1 122 | 123 | # we may end up with items remaining in one of the arrays, in that case we just 124 | # dump them into 'combined' since we know they're bigger than any item in 'combined' 125 | if first_h_idx < first_half.size(): 126 | combined.append_array(first_half.slice(first_h_idx, first_half.size()-1)) 127 | if second_h_idx < second_half.size(): 128 | combined.append_array(second_half.slice(second_h_idx, second_half.size()-1)) 129 | 130 | return combined 131 | -------------------------------------------------------------------------------- /scenes/objects/sorters/sorter_odd-even_sort.gd: -------------------------------------------------------------------------------- 1 | extends "res://scenes/objects/sorters/sorter.gd" 2 | 3 | # https://en.wikipedia.org/wiki/Odd%E2%80%93even_sort 4 | # similar to bubble sort, but instead of comparing each element to the next 5 | # we iterate over even and odd indexes separately 6 | # 7 | # time complexity: Average: O(N^2) 8 | # Worst: O(N^2) 9 | # Best: O(N) 10 | 11 | var _is_even : bool 12 | var _index : int 13 | var _ordered_halves : int # 1=the even or odd half is sorted, 2=both are sorted 14 | 15 | 16 | # override 17 | static func get_metadata() -> Dictionary: 18 | return {"name":"ODDEVENSORT", "is_enabled":true} 19 | 20 | # override 21 | func setup(data_size : int, priority_callback : FuncRef): 22 | .setup(data_size, priority_callback) 23 | 24 | _index = 0 25 | _is_even = true 26 | _ordered_halves = 0 27 | 28 | # override 29 | func next_step() -> Dictionary: 30 | var indexes : Array 31 | indexes.resize(2) 32 | 33 | var changed : bool = false 34 | for i in range(_index, _data_size-1, 2): 35 | if _priority_callback.call_func(i, i+1): 36 | changed = true 37 | indexes[0] = i 38 | indexes[1] = i+1 39 | _index = i+1 40 | # if one half was order, it is now no longer ordered 41 | _ordered_halves = 0 42 | break 43 | 44 | # if no change happened then we know 1 half is sorted 45 | # we're only truly done when both even and odd halves are sorted (_ordered_halves == 2) 46 | if changed == false: 47 | # but! if we didn't start checking from the first index, recheck 48 | if _index > (0 if _is_even else 1): 49 | _index = 0 if _is_even else 1 50 | return next_step() 51 | else: 52 | _ordered_halves += 1 53 | if _ordered_halves == 2: 54 | return {"done":true} 55 | else: 56 | # we've sorted 1 half! but another remains 57 | _is_even = !_is_even 58 | _index = 0 if _is_even else 1 59 | return next_step() 60 | else: 61 | return {"done":false, "action":SortAction.switch, "indexes":indexes} 62 | 63 | # override 64 | func skip_to_last_step() -> Array: 65 | var indexes : Array 66 | indexes.resize(_data_size) 67 | for i in _data_size: indexes[i] = i 68 | 69 | while true: 70 | var changed : bool = false 71 | for i in range(0, indexes.size()-1, 2): 72 | if _priority_callback.call_func(indexes[i], indexes[i+1]): 73 | changed = true 74 | Utility.swap_elements(indexes, i, i+1) 75 | 76 | for i in range(1, indexes.size()-1, 2): 77 | if _priority_callback.call_func(indexes[i], indexes[i+1]): 78 | changed = true 79 | Utility.swap_elements(indexes, i, i+1) 80 | 81 | if changed == false: break 82 | 83 | return indexes 84 | -------------------------------------------------------------------------------- /scenes/objects/sorters/sorter_quick_sort.gd: -------------------------------------------------------------------------------- 1 | extends "res://scenes/objects/sorters/sorter.gd" 2 | 3 | # https://en.wikipedia.org/wiki/Quicksort 4 | # based on the Divide and Conquer paradigm, 5 | # we pick a pivot and partition the other elements into two sub-arrays 6 | # according to whether they are lesser or greater than the pivot 7 | # the sub-arrays are then sorted recursively in the same way 8 | # 9 | # time complexity: Average: O(N log N) 10 | # Worst: O(N^2) 11 | # Best: O(N log N) 12 | 13 | var _pivot_idx : int 14 | var _index : int 15 | var _splits : Array # [{"low":low_bound, "high":high_bound}, ..] 16 | var _curr_split_idx : int 17 | 18 | 19 | # override 20 | static func get_metadata() -> Dictionary: 21 | return {"name":"QUICKSORT", "is_enabled":true} 22 | 23 | # override 24 | func setup(data_size : int, priority_callback : FuncRef): 25 | .setup(data_size, priority_callback) 26 | 27 | _splits = [ {"low":0, "high":_data_size-1} ] # start with whole array 28 | _curr_split_idx = 0 29 | _pivot_idx = _splits[_curr_split_idx]["low"] 30 | _index = _pivot_idx + 1 31 | 32 | # override 33 | func next_step() -> Dictionary: 34 | var curr_split : Dictionary = _splits[_curr_split_idx] 35 | 36 | # skip subarrays of size 1 37 | while curr_split["low"] == curr_split["high"]: 38 | _curr_split_idx += 1 39 | if _curr_split_idx > _splits.size()-1: 40 | return {"done":true} 41 | 42 | curr_split = _splits[_curr_split_idx] 43 | _pivot_idx = curr_split["low"] 44 | _index = _pivot_idx + 1 45 | 46 | if _index > curr_split["high"]: 47 | # once we're done with the current split, we split it into 2 arrays (plus the pivot) 48 | # and go through them one by one 49 | _splits.remove(_curr_split_idx) 50 | if _pivot_idx+1 <= curr_split["high"]: 51 | _splits.insert(_curr_split_idx, {"low":_pivot_idx+1, "high":curr_split["high"]}) 52 | _splits.insert(_curr_split_idx, {"low":_pivot_idx, "high":_pivot_idx}) 53 | if _pivot_idx-1 >= curr_split["low"]: 54 | _splits.insert(_curr_split_idx, {"low":curr_split["low"], "high":_pivot_idx-1}) 55 | 56 | curr_split = _splits[_curr_split_idx] 57 | _pivot_idx = curr_split["low"] 58 | _index = _pivot_idx + 1 59 | return next_step() 60 | 61 | if _priority_callback.call_func(_pivot_idx, _index): 62 | var index_to_move : Array = [_index, _pivot_idx] 63 | _index += 1 64 | _pivot_idx += 1 65 | return {"done":false, "action":SortAction.move, "indexes":index_to_move} 66 | else: 67 | _index += 1 68 | return next_step() 69 | 70 | # override 71 | func skip_to_last_step() -> Array: 72 | var indexes : Array 73 | indexes.resize(_data_size) 74 | for i in _data_size: indexes[i] = i 75 | 76 | return _divide_n_conquer_full(indexes, 0, _data_size-1) 77 | 78 | func _divide_n_conquer_full(arr : Array, low_bound : int, high_bound : int) -> Array: 79 | if low_bound == high_bound: 80 | return [arr[low_bound]] 81 | 82 | # we use first index as a pivot, but there are other approaches 83 | var pivot_idx : int = low_bound 84 | for i in range(low_bound+1, high_bound+1): 85 | if _priority_callback.call_func(arr[pivot_idx], arr[i]): 86 | var value : int = arr[i] 87 | arr.remove(i) 88 | arr.insert(pivot_idx, value) 89 | pivot_idx += 1 90 | 91 | var ordered_arr : Array 92 | if pivot_idx-1 >= low_bound: ordered_arr.append_array(_divide_n_conquer_full(arr, low_bound, pivot_idx-1)) 93 | ordered_arr.append(arr[pivot_idx]) 94 | if pivot_idx+1 <= high_bound: ordered_arr.append_array(_divide_n_conquer_full(arr, pivot_idx+1, high_bound)) 95 | 96 | return ordered_arr 97 | -------------------------------------------------------------------------------- /scenes/objects/sorters/sorter_selection_sort.gd: -------------------------------------------------------------------------------- 1 | extends "res://scenes/objects/sorters/sorter.gd" 2 | 3 | # https://en.wikipedia.org/wiki/Selection_sort 4 | # the array is virtually split into a sorted and an unsorted sides, each time we 5 | # move the smallest element from the unsorted half to the end of the sorted half 6 | # untill all elements are sorted 7 | # 8 | # time complexity: Average: O(N^2) 9 | # Worst: O(N^2) 10 | # Best: O(N^2) 11 | 12 | var _index : int 13 | 14 | 15 | # override 16 | static func get_metadata() -> Dictionary: 17 | return {"name":"SELECTIONSORT", "is_enabled":true} 18 | 19 | # override 20 | func setup(data_size : int, priority_callback : FuncRef): 21 | .setup(data_size, priority_callback) 22 | 23 | _index = 0 24 | 25 | # override 26 | func next_step() -> Dictionary: 27 | if _index == _data_size: return {"done":true} 28 | 29 | var smallest_idx : int = _index 30 | for j in range(_index+1, _data_size): 31 | if _priority_callback.call_func(smallest_idx, j): 32 | smallest_idx = j 33 | 34 | _index += 1 35 | if smallest_idx == _index-1: 36 | return next_step() 37 | else: 38 | return {"done":false, "action":SortAction.switch, "indexes":[_index-1, smallest_idx]} 39 | 40 | # override 41 | func skip_to_last_step() -> Array: 42 | var indexes : Array 43 | indexes.resize(_data_size) 44 | for i in _data_size: indexes[i] = i 45 | 46 | for i in indexes.size(): 47 | var smallest_idx : int = i 48 | 49 | for j in range(i+1, indexes.size()): 50 | if _priority_callback.call_func(indexes[smallest_idx], indexes[j]): 51 | smallest_idx = j 52 | 53 | Utility.swap_elements(indexes, i, smallest_idx) 54 | 55 | return indexes 56 | -------------------------------------------------------------------------------- /scenes/objects/sorters/sorter_shell_sort.gd: -------------------------------------------------------------------------------- 1 | extends "res://scenes/objects/sorters/sorter.gd" 2 | 3 | # https://en.wikipedia.org/wiki/Shellsort 4 | # similar to insertion sort, it first sorts elements that are far apart 5 | # from each other and progressively reduces the gap between the elements 6 | # to be sorted 7 | # 8 | # time complexity: Average: (depends on the sequence used) O(N^(4/3)) in our case 9 | # Worst: O(N^2) 10 | # Best: O(N log N) 11 | 12 | var _gap : int 13 | var _index : int 14 | var _sub_idx : int 15 | 16 | 17 | # override 18 | static func get_metadata() -> Dictionary: 19 | return {"name":"SHELLSORT", "is_enabled":true} 20 | 21 | # override 22 | func setup(data_size : int, priority_callback : FuncRef): 23 | .setup(data_size, priority_callback) 24 | 25 | _gap = data_size / 2 26 | _index = _gap 27 | _sub_idx = -1 28 | 29 | # override 30 | func next_step() -> Dictionary: 31 | if _index >= _data_size: 32 | _gap /= 2 33 | _index = _gap 34 | if _gap == 0: 35 | return {"done":true} 36 | 37 | if _sub_idx == -1: 38 | if _priority_callback.call_func(_index-_gap, _index): 39 | var indexes_to_switch : Array = [_index-_gap, _index] 40 | _sub_idx = _index-_gap-_gap 41 | if _sub_idx < 0: 42 | _index += 1 43 | _sub_idx = -1 44 | return {"done":false, "action":SortAction.switch, "indexes":indexes_to_switch} 45 | else: 46 | _index += 1 47 | else: 48 | var indexes_to_switch : Array 49 | if _priority_callback.call_func(_sub_idx, _sub_idx+_gap): 50 | indexes_to_switch = [_sub_idx, _sub_idx+_gap] 51 | 52 | _sub_idx -= _gap 53 | if _sub_idx < 0: 54 | _index += 1 55 | _sub_idx = -1 56 | 57 | if indexes_to_switch: return {"done":false, "action":SortAction.switch, "indexes":indexes_to_switch} 58 | 59 | return next_step() 60 | 61 | # override 62 | func skip_to_last_step() -> Array: 63 | var indexes : Array 64 | indexes.resize(_data_size) 65 | for i in _data_size: indexes[i] = i 66 | 67 | # we use half the size as a gap and increment linearly (Natural sequence) 68 | # but there are other more efficent sequences like Marcin Ciura's sequence and Knuth Sequence (see wiki link above) 69 | var gap : int = indexes.size() / 2 70 | while gap > 0: 71 | for i in range(gap, indexes.size()): 72 | if _priority_callback.call_func(indexes[i-gap], indexes[i]): 73 | Utility.swap_elements(indexes, i-gap, i) 74 | for j in range(i-gap-gap, -1, -gap): 75 | if _priority_callback.call_func(indexes[j], indexes[j+gap]): 76 | Utility.swap_elements(indexes,j ,j+gap ) 77 | 78 | gap /= 2 79 | 80 | return indexes 81 | -------------------------------------------------------------------------------- /scenes/objects/visualizers/visualizer.gd: -------------------------------------------------------------------------------- 1 | extends Control 2 | 3 | # base class for all visualizers (objects that are sorted by sorters) 4 | 5 | # we use these signals to allow visualizers to take their time and do any effects etc 6 | signal updated_indexes 7 | signal updated_all 8 | signal finished 9 | 10 | # override 11 | static func get_metadata() -> Dictionary: 12 | # "name" 13 | # "image": name and extension of an image in "res://resources/textures/visualizer_images/" 14 | # "description": short description 15 | # "is_enabled": visualizer can be used 16 | return {"name":"", "image":"", "description":"", "is_enabled":false} 17 | 18 | # override 19 | func reset(): 20 | return 21 | 22 | # override 23 | # used by sorters to know how many indexes there are 24 | func get_content_count() -> int: 25 | return 0 26 | 27 | # override, is item at idx1 bigger/better/etc.. than item at idx2? 28 | # called after each sort, except the last one 29 | func determine_priority(idx1 : int, idx2 : int) -> bool: 30 | return false 31 | 32 | # override 33 | # see Sorter.SortAction for actions 34 | func update_indexes(action : int, idx1 : int, idx2 : int): 35 | emit_signal("updated_indexes") 36 | 37 | # override, this is called after sorter.step_all() 38 | func update_all(new_indexes : Array): 39 | emit_signal("updated_all") 40 | 41 | # override, called when user hides or shows main_interface panel 42 | func set_ui_visibility(is_visible : bool): 43 | return 44 | 45 | # override, for additional effects etc.. after sorting is finished 46 | func finish(): 47 | emit_signal("finished") 48 | -------------------------------------------------------------------------------- /scenes/objects/visualizers/visualizer.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://scenes/objects/visualizers/visualizer.gd" type="Script" id=1] 4 | 5 | [node name="Visualizer" type="Control"] 6 | anchor_right = 1.0 7 | anchor_bottom = 1.0 8 | script = ExtResource( 1 ) 9 | -------------------------------------------------------------------------------- /scenes/objects/visualizers/visualizer_color_bars.gd: -------------------------------------------------------------------------------- 1 | extends "res://scenes/objects/visualizers/visualizer.gd" 2 | 3 | onready var _lines_container : Control = $Lines 4 | onready var _camera : Camera2D = $Camera2D 5 | onready var _scroll : HScrollBar = $CanvasLayer/HScrollBar 6 | onready var _scroll_graber_box : StyleBox = _scroll.get_stylebox("grabber") 7 | onready var _scroll_graber_hover_box : StyleBox = _scroll.get_stylebox("grabber_highlight") 8 | 9 | const _min_height : float = 100.0 10 | const _max_height : float = 520.0 11 | const _horizontal_gap : float = 45.0 12 | const _lines_count : int = 20 13 | const _line_width : float = 6.8 14 | const _line_tween_time : float = 0.06 15 | 16 | const _ruler_big_line_width : float = 2.2 17 | const _ruler_small_line_width : float = 0.6 18 | const _ruler_small_lines_count : int = 3 19 | const _ruler_color : Color = Color("32ffeecc") 20 | 21 | const _gradient_colors : Array = [Color("ffeecc"), Color("ff6973"), Color.black] 22 | 23 | var _active_tweens : Array 24 | var _lines_order : Array 25 | var _next_x : float 26 | # the order in _lines_order is not based on the starting point of the lines, but rather 27 | # on the order of the last point, the thing is that this last point changes over time as the line 28 | # expands and doesn't always match with the first point's position 29 | # the problem is that when we reset, we need to shuffle _lines_order to produce a new order 30 | # based on first point of each line but (again) _lines_order points to the order of the last points 31 | # the solution is to keep a record of the lines first point position before 32 | # we start sorting and reset _lines_order to it once we're done 33 | var _1st_point_lines_order : Array 34 | 35 | 36 | func _ready(): 37 | _lines_order.resize(_lines_count) 38 | var v_gap : float = (_max_height - _min_height) / _lines_count 39 | for i in _lines_count: 40 | var line : Line2D = Line2D.new() 41 | line.default_color = Utility.lerp_color_arr(_gradient_colors, float(i) / _lines_count, false) 42 | line.width = _line_width 43 | line.joint_mode = Line2D.LINE_JOINT_ROUND 44 | line.begin_cap_mode = Line2D.LINE_CAP_ROUND 45 | line.end_cap_mode = Line2D.LINE_CAP_ROUND 46 | 47 | _lines_container.add_child(line) 48 | var y_pos : float = _min_height + v_gap * i 49 | line.global_position = Vector2(0, y_pos) 50 | line.set_meta("correct_order", i) 51 | 52 | _lines_order[i] = line 53 | 54 | _1st_point_lines_order = _lines_order.duplicate() 55 | 56 | func _draw(): 57 | # draw ruler 58 | var start_x : float = stepify(_camera.global_position.x, _horizontal_gap) 59 | var end_x : float = stepify( 60 | _camera.global_position.x + Utility.viewport_size.x, _horizontal_gap 61 | ) + _horizontal_gap 62 | 63 | # apparently custom drawings won't show unless the node drawing them 64 | # is visible in the screen 65 | rect_size.x = end_x 66 | 67 | for i in range(start_x, end_x, _horizontal_gap): 68 | # big line 69 | var line_start : Vector2 = Vector2(i, Utility.viewport_size.y) 70 | draw_line( 71 | line_start, 72 | line_start - Vector2(0, Utility.viewport_size.y), 73 | _ruler_color, 74 | _ruler_big_line_width 75 | ) 76 | 77 | # small lines 78 | for j in range(i, i + _horizontal_gap, _horizontal_gap / (_ruler_small_lines_count+1)): 79 | var small_line_start : Vector2 = Vector2(j, Utility.viewport_size.y) 80 | draw_line( 81 | small_line_start, 82 | small_line_start - Vector2(0, Utility.viewport_size.y), 83 | _ruler_color, 84 | _ruler_small_line_width 85 | ) 86 | 87 | # override 88 | static func get_metadata() -> Dictionary: 89 | return { 90 | "name":"COLOR_TITLE", "image":"color_bars.png", 91 | "description":"COLOR_DESC", "is_enabled":true 92 | } 93 | 94 | # override 95 | func reset(): 96 | for i in range(_active_tweens.size()-1, -1, -1): 97 | _active_tweens[i].kill() 98 | _active_tweens.remove(i) 99 | 100 | # clear points 101 | for line in _lines_order: 102 | line.clear_points() 103 | # keep first 2 points 104 | line.add_point(Vector2.ZERO) 105 | line.add_point(Vector2(_horizontal_gap, 0)) 106 | 107 | _next_x = _horizontal_gap * 2 108 | _lines_order = _1st_point_lines_order 109 | 110 | # shuffle, on each step we swap global_position and order in _lines_order 111 | for i in _lines_order.size(): 112 | var line : Line2D = _lines_order[i] 113 | 114 | var rand_idx : int = Utility.rng.randi_range(0, _lines_count-2) 115 | if rand_idx >= i: rand_idx += 1 116 | var rand_line : Line2D = _lines_order[rand_idx] 117 | 118 | var line_pos : Vector2 = line.global_position 119 | line.global_position = rand_line.global_position 120 | rand_line.global_position = line_pos 121 | 122 | Utility.swap_elements(_lines_order, i, rand_idx) 123 | 124 | _1st_point_lines_order = _lines_order.duplicate() 125 | 126 | _scroll.value = _scroll.max_value 127 | _camera.global_position.x = 0 128 | _resize_scroll_grabber() 129 | update() 130 | 131 | # override 132 | func get_content_count() -> int: 133 | return _lines_count 134 | 135 | # override 136 | func determine_priority(idx1 : int, idx2 : int) -> bool: 137 | return _lines_order[idx1].get_meta("correct_order") >\ 138 | _lines_order[idx2].get_meta("correct_order") 139 | 140 | # override 141 | func update_indexes(action : int, idx1 : int, idx2 : int): 142 | match action: 143 | Sorter.SortAction.switch: 144 | var line1 : Line2D = _lines_order[idx1] 145 | var line2 : Line2D = _lines_order[idx2] 146 | var line1_last_p_as_line2_local : Vector2 =\ 147 | _transform_local_point_from_node_to_node(line1, line1.points[-1], line2) 148 | var line2_last_p_as_line1_local : Vector2 =\ 149 | _transform_local_point_from_node_to_node(line2, line2.points[-1], line1) 150 | 151 | # extend both lines towards each other's position 152 | var tween : SceneTreeTween = _make_tweener().set_parallel(true) 153 | _add_point_and_tween_it( 154 | tween, line1, Vector2(_next_x, line2_last_p_as_line1_local.y) 155 | ) 156 | _add_point_and_tween_it( 157 | tween, line2, Vector2(_next_x, line1_last_p_as_line2_local.y) 158 | ) 159 | 160 | Utility.swap_elements(_lines_order, idx1, idx2) 161 | yield(tween, "finished") 162 | 163 | Sorter.SortAction.move: 164 | var tween : SceneTreeTween = _make_tweener().set_parallel(true) 165 | if idx1 > idx2: 166 | # move line at idx1 above line at idx2 167 | # and shift lines in between down 168 | var idx2_old_pos : Vector2 = _lines_order[idx2].points[-1] 169 | for i in range(idx2, idx1): 170 | var other_line : Line2D = _lines_order[i+1] 171 | _add_point_and_tween_it(tween, _lines_order[i], Vector2( 172 | _next_x, 173 | _transform_local_point_from_node_to_node( 174 | other_line, other_line.points[-1], _lines_order[i] 175 | ).y 176 | )) 177 | _add_point_and_tween_it(tween, _lines_order[idx1], Vector2( 178 | _next_x, 179 | _transform_local_point_from_node_to_node( 180 | _lines_order[idx2], idx2_old_pos, _lines_order[idx1] 181 | ).y 182 | )) 183 | 184 | elif idx1 < idx2: 185 | # move line at idx1 above line at idx2 186 | # and shift lines in between up 187 | var idx2_old_pos : Vector2 = _lines_order[idx2].points[-1] 188 | for i in range(idx2, idx1, -1): 189 | var other_line : Line2D = _lines_order[i-1] 190 | _add_point_and_tween_it(tween, _lines_order[i], Vector2( 191 | _next_x, 192 | _transform_local_point_from_node_to_node( 193 | other_line, other_line.points[-1], _lines_order[i] 194 | ).y 195 | )) 196 | _add_point_and_tween_it(tween, _lines_order[idx1], Vector2( 197 | _next_x, 198 | _transform_local_point_from_node_to_node( 199 | _lines_order[idx2], idx2_old_pos, _lines_order[idx1] 200 | ).y 201 | )) 202 | 203 | yield(tween, "finished") 204 | Utility.move_element(_lines_order, idx1, idx2) 205 | 206 | yield(_extend_all_lines(_next_x), "completed") 207 | _next_x += _horizontal_gap 208 | _resize_scroll_grabber() 209 | emit_signal("updated_indexes") 210 | 211 | # override 212 | func update_all(new_indexes : Array): 213 | var new_Ys : Array # each entry represents the y of a to-be-added point to _lines_order[i] 214 | new_Ys.resize(new_indexes.size()) 215 | for i in new_indexes.size(): 216 | var line : Line2D = _lines_order[i] 217 | new_Ys[i] = line.global_position.y + line.points[-1].y 218 | 219 | # extend lines 4 times more than usual so it's not all cluttered in one small space 220 | _next_x += (_horizontal_gap * 4) 221 | 222 | var tween : SceneTreeTween = _make_tweener().set_parallel(true) 223 | for i in new_indexes.size(): 224 | var line : Line2D = _lines_order[new_indexes[i]] 225 | _add_point_and_tween_it( 226 | tween, line, Vector2(_next_x, new_Ys[i] - line.global_position.y) 227 | ) 228 | yield(tween, "finished") 229 | 230 | _next_x += _horizontal_gap 231 | _resize_scroll_grabber() 232 | emit_signal("updated_all") 233 | 234 | # override 235 | func set_ui_visibility(is_visible : bool): 236 | _scroll.visible = is_visible 237 | 238 | # override 239 | func finish(): 240 | # extend one last time 241 | yield(_extend_all_lines(_next_x), "completed") 242 | _next_x += _horizontal_gap 243 | 244 | _resize_scroll_grabber() 245 | emit_signal("finished") 246 | 247 | func _transform_local_point_from_node_to_node(node1 : Node2D, point : Vector2, node2 : Node2D): 248 | # points array of a Line2D contain points positions in relative space, making it quite painful to work with 249 | # since we have to convert a point to global space, then to local space of the second line that needs it 250 | return node1.global_position + point - node2.global_position 251 | 252 | func _extend_all_lines(extend_to : float): 253 | var tween : SceneTreeTween = _make_tweener().set_parallel(true) 254 | for line in _lines_order: 255 | var last_point : Vector2 = line.points[-1] 256 | if last_point.x < extend_to: 257 | _add_point_and_tween_it(tween, line, Vector2(extend_to, last_point.y)) 258 | 259 | yield(tween, "finished") 260 | 261 | func _make_tweener() -> SceneTreeTween: 262 | # remove inactive tweens 263 | for i in range(_active_tweens.size()-1, -1, -1): 264 | if _active_tweens[i].is_valid() == false: 265 | _active_tweens.remove(i) 266 | 267 | var tween : SceneTreeTween = get_tree().create_tween() 268 | _active_tweens.append(tween) 269 | return tween 270 | 271 | func _add_point_and_tween_it(tween : SceneTreeTween, line : Line2D, point : Vector2): 272 | var prev_point : Vector2 = line.points[-1] 273 | line.add_point(prev_point) 274 | tween.tween_method(self, "_line_point_tween_callback", prev_point, point, _line_tween_time, [line]) 275 | 276 | func _line_point_tween_callback(position : Vector2, line : Line2D): 277 | line.points[-1] = position 278 | 279 | func _on_scroll(): 280 | if _next_x > Utility.viewport_size.x: 281 | _camera.global_position.x = lerp(0, _next_x - Utility.viewport_size.x, _scroll.value) 282 | update() 283 | 284 | func _resize_scroll_grabber(): 285 | # if the scroll bar was scrolled to the end, move along new points 286 | # this is similar to a Twitch chat for example, everytime a new chat appears 287 | # the bar will auto-scroll to follow it as long as it was previously scrolled 288 | # all the way down 289 | var auto_scroll_to_end : bool = (_scroll.value == _scroll.max_value) 290 | 291 | # resize grabber 292 | var new_width : float 293 | var ratio : float = _next_x / Utility.viewport_size.x 294 | if ratio <= 1: 295 | new_width = _scroll.rect_size.x 296 | else: 297 | new_width = range_lerp( 298 | Utility.viewport_size.x, 0, _next_x, 0, _scroll.rect_size.x 299 | ) 300 | 301 | _scroll_graber_box.border_width_left = new_width / 2 302 | _scroll_graber_box.border_width_right = new_width / 2 303 | _scroll_graber_hover_box.border_width_left = new_width / 2 304 | _scroll_graber_hover_box.border_width_right = new_width / 2 305 | 306 | if auto_scroll_to_end: 307 | _on_scroll() 308 | -------------------------------------------------------------------------------- /scenes/objects/visualizers/visualizer_color_bars.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=6 format=2] 2 | 3 | [ext_resource path="res://scenes/objects/visualizers/visualizer.tscn" type="PackedScene" id=1] 4 | [ext_resource path="res://scenes/objects/visualizers/visualizer_color_bars.gd" type="Script" id=2] 5 | 6 | [sub_resource type="StyleBoxFlat" id=3] 7 | draw_center = false 8 | border_width_left = 10 9 | border_width_right = 10 10 | border_color = Color( 1, 0.411765, 0.45098, 1 ) 11 | corner_radius_top_left = 6 12 | corner_radius_top_right = 6 13 | corner_radius_bottom_right = 6 14 | corner_radius_bottom_left = 6 15 | 16 | [sub_resource type="StyleBoxFlat" id=1] 17 | draw_center = false 18 | border_width_left = 10 19 | border_width_right = 10 20 | border_color = Color( 1, 0.933333, 0.8, 1 ) 21 | corner_radius_top_left = 6 22 | corner_radius_top_right = 6 23 | corner_radius_bottom_right = 6 24 | corner_radius_bottom_left = 6 25 | 26 | [sub_resource type="StyleBoxFlat" id=2] 27 | bg_color = Color( 0.0823529, 0.470588, 0.54902, 1 ) 28 | border_width_top = 1 29 | border_width_right = 1 30 | border_width_bottom = 1 31 | border_color = Color( 0, 0.72549, 0.745098, 1 ) 32 | corner_radius_top_left = 6 33 | corner_radius_top_right = 6 34 | corner_radius_bottom_right = 6 35 | corner_radius_bottom_left = 6 36 | 37 | [node name="VisualizerColorBar" instance=ExtResource( 1 )] 38 | script = ExtResource( 2 ) 39 | 40 | [node name="Camera2D" type="Camera2D" parent="." index="0"] 41 | show_behind_parent = true 42 | anchor_mode = 0 43 | current = true 44 | 45 | [node name="Background" type="ColorRect" parent="Camera2D" index="0"] 46 | anchor_right = 1.0 47 | anchor_bottom = 1.0 48 | rect_min_size = Vector2( 1024, 576 ) 49 | color = Color( 0.27451, 0.258824, 0.368627, 1 ) 50 | 51 | [node name="Lines" type="Control" parent="." index="1"] 52 | anchor_right = 1.0 53 | anchor_bottom = 1.0 54 | 55 | [node name="CanvasLayer" type="CanvasLayer" parent="." index="2"] 56 | 57 | [node name="HScrollBar" type="HScrollBar" parent="CanvasLayer" index="0"] 58 | anchor_top = 1.0 59 | anchor_right = 1.0 60 | anchor_bottom = 1.0 61 | margin_left = 13.0 62 | margin_top = -21.0 63 | margin_right = -13.0 64 | margin_bottom = -9.0 65 | custom_styles/grabber_highlight = SubResource( 3 ) 66 | custom_styles/grabber = SubResource( 1 ) 67 | custom_styles/scroll = SubResource( 2 ) 68 | custom_styles/grabber_pressed = SubResource( 3 ) 69 | max_value = 1.0 70 | 71 | [connection signal="scrolling" from="CanvasLayer/HScrollBar" to="." method="_on_scroll"] 72 | -------------------------------------------------------------------------------- /scenes/objects/visualizers/visualizer_planets.gd: -------------------------------------------------------------------------------- 1 | extends "res://scenes/objects/visualizers/visualizer.gd" 2 | 3 | onready var _planets_container : Control = $Planets 4 | onready var _star : TextureRect = $Star 5 | onready var _camera : Camera2D = $Camera2D 6 | onready var _background : ColorRect = $Camera2D/Background 7 | 8 | onready var _zoom_in_btn : Button = $CanvasLayer/Zoom/HBoxContainer/In 9 | onready var _zoom_out_btn : Button = $CanvasLayer/Zoom/HBoxContainer/Out 10 | 11 | const _orbiting_planet_scene : PackedScene = preload("res://scenes/objects/components/orbiting_planet.tscn") 12 | 13 | var _window_size : Vector2 = Vector2( 14 | ProjectSettings.get_setting("display/window/size/width"), 15 | ProjectSettings.get_setting("display/window/size/height") 16 | ) 17 | 18 | var _star_center : Vector2 19 | const _planets_count : int = 10 20 | const _star_offset : float = 140.0 # initial offset from the sun 21 | const _planets_gap : float = 80.0 22 | 23 | const _min_planet_scale : float = 0.4 24 | const _max_planet_scale : float = 2.8 25 | const _planets_speed_modifier : float = 0.2 26 | # used to determine planets orbit speed 27 | var _furthest_planet_distance : float =\ 28 | Vector2(_star_offset + _planets_count * _planets_gap, 0).length() 29 | 30 | var _waiting_for_planets : int = 0 31 | var _is_updating_all : bool = false 32 | 33 | const _zoom_min : int = 0 34 | const _zoom_max : int = 3 35 | var _zoom_level : int = 1 36 | 37 | 38 | func _ready(): 39 | _background.rect_size = _window_size * 2 40 | _background.rect_position = -_background.rect_size/2 41 | 42 | _star.rect_pivot_offset = _star.rect_size/2 43 | _star_center = _star.rect_global_position + _star.rect_size/2 44 | 45 | for i in _planets_count: 46 | var planet := _orbiting_planet_scene.instance() 47 | planet.connect("moved", self, "_on_planet_moved") 48 | _planets_container.add_child(planet) 49 | planet.setup(_star_center, 50 | _star_center - Vector2( 51 | _star_offset + (_planets_gap * i), planet.rect_size.y/2) 52 | ) 53 | 54 | # override 55 | static func get_metadata() -> Dictionary: 56 | return { 57 | "name":"PLANETS_TITLE", "image":"planets.png", 58 | "description":"PLANETS_DESC", "is_enabled":true 59 | } 60 | 61 | # override 62 | func reset(): 63 | for i in _planets_count: 64 | var planet := _planets_container.get_child(i) 65 | 66 | planet.reset( 67 | Utility.rng.randf_range(_min_planet_scale, _max_planet_scale), 68 | _planets_speed_modifier * 69 | (_furthest_planet_distance - (planet.rect_global_position - _star_center).length()), 70 | Utility.rng.randf_range(0, 360) 71 | ) 72 | 73 | # override 74 | func get_content_count() -> int: 75 | return _planets_count 76 | 77 | # override 78 | func determine_priority(idx1 : int, idx2 : int) -> bool: 79 | return _planets_container.get_child(idx1).get_size() >\ 80 | _planets_container.get_child(idx2).get_size() 81 | 82 | # override 83 | func update_indexes(action : int, idx1 : int, idx2 : int): 84 | match action: 85 | Sorter.SortAction.switch: 86 | var child1 := _planets_container.get_child(idx1) 87 | var child2 := _planets_container.get_child(idx2) 88 | child1.move_to(child2) 89 | child2.move_to(child1) 90 | 91 | Utility.switch_children(_planets_container, idx1, idx2) 92 | _waiting_for_planets = 2 93 | 94 | Sorter.SortAction.move: 95 | var target_child := _planets_container.get_child(idx1) 96 | 97 | if idx1 > idx2: 98 | target_child.move_to(_planets_container.get_child(idx2)) 99 | for i in range(idx2, idx1): 100 | _planets_container.get_child(i).move_to(_planets_container.get_child(i+1)) 101 | _waiting_for_planets = idx1-idx2+1 102 | 103 | elif idx1 < idx2: 104 | target_child.move_to(_planets_container.get_child(idx2-1)) 105 | for i in range(idx2-1, idx1, -1): 106 | _planets_container.get_child(i).move_to(_planets_container.get_child(i-1)) 107 | _waiting_for_planets = idx2-idx1 108 | 109 | _planets_container.move_child(target_child, idx2) 110 | 111 | # override 112 | func update_all(new_indexes : Array): 113 | _is_updating_all = true 114 | 115 | var ordered_planets : Array 116 | for i in new_indexes: 117 | ordered_planets.append(_planets_container.get_child(i)) 118 | 119 | # switch planets 120 | for i in new_indexes.size(): 121 | if i != new_indexes[i]: 122 | _waiting_for_planets += 1 123 | var target_child := _planets_container.get_child(i) 124 | _planets_container.get_child(new_indexes[i]).move_to(target_child) 125 | 126 | # no point in moving children in tree since reset() will be called after this anyway 127 | 128 | # override 129 | func set_ui_visibility(is_visible : bool): 130 | _zoom_in_btn.visible = is_visible 131 | _zoom_out_btn.visible = is_visible 132 | 133 | # override 134 | func finish(): 135 | emit_signal("finished") 136 | 137 | func _on_planet_moved(): 138 | _waiting_for_planets -= 1 139 | if _waiting_for_planets == 0: 140 | if _is_updating_all: 141 | _is_updating_all = false 142 | emit_signal("updated_all") 143 | else: 144 | emit_signal("updated_indexes") 145 | 146 | func _on_zoom_pressed(is_in : bool): 147 | _zoom_level += -1 if is_in else 1 148 | 149 | var zoom_modifier : int = pow(2, _zoom_level) 150 | _camera.zoom = Vector2.ONE * zoom_modifier 151 | _background.rect_size = _window_size * zoom_modifier 152 | _background.rect_position = -_background.rect_size/2 153 | 154 | _zoom_out_btn.disabled = _zoom_level == _zoom_max 155 | _zoom_in_btn.disabled = _zoom_level == _zoom_min 156 | -------------------------------------------------------------------------------- /scenes/objects/visualizers/visualizer_planets.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=6 format=2] 2 | 3 | [ext_resource path="res://scenes/objects/visualizers/visualizer.tscn" type="PackedScene" id=1] 4 | [ext_resource path="res://scenes/objects/visualizers/visualizer_planets.gd" type="Script" id=2] 5 | [ext_resource path="res://resources/textures/planets.png" type="Texture" id=3] 6 | [ext_resource path="res://resources/godot/main_interface_theme.tres" type="Theme" id=4] 7 | 8 | [sub_resource type="AtlasTexture" id=1] 9 | atlas = ExtResource( 3 ) 10 | region = Rect2( 64, 0, 64, 64 ) 11 | 12 | [node name="VisualizerPlanets" instance=ExtResource( 1 )] 13 | script = ExtResource( 2 ) 14 | 15 | [node name="Camera2D" type="Camera2D" parent="." index="0"] 16 | position = Vector2( 480, 270 ) 17 | current = true 18 | zoom = Vector2( 2, 2 ) 19 | 20 | [node name="Background" type="ColorRect" parent="Camera2D" index="0"] 21 | anchor_right = 1.0 22 | anchor_bottom = 1.0 23 | margin_left = -480.0 24 | margin_top = -270.0 25 | margin_right = 480.0 26 | margin_bottom = 270.0 27 | color = Color( 0.27451, 0.258824, 0.368627, 1 ) 28 | 29 | [node name="Planets" type="Control" parent="." index="1"] 30 | anchor_right = 1.0 31 | anchor_bottom = 1.0 32 | 33 | [node name="Star" type="TextureRect" parent="." index="2"] 34 | anchor_left = 0.5 35 | anchor_top = 0.5 36 | anchor_right = 0.5 37 | anchor_bottom = 0.5 38 | margin_left = -64.0 39 | margin_top = -64.0 40 | margin_right = 64.0 41 | margin_bottom = 64.0 42 | texture = SubResource( 1 ) 43 | expand = true 44 | 45 | [node name="CanvasLayer" type="CanvasLayer" parent="." index="3"] 46 | 47 | [node name="Zoom" type="MarginContainer" parent="CanvasLayer" index="0"] 48 | anchor_right = 1.0 49 | anchor_bottom = 1.0 50 | custom_constants/margin_right = 12 51 | custom_constants/margin_bottom = 12 52 | 53 | [node name="HBoxContainer" type="HBoxContainer" parent="CanvasLayer/Zoom" index="0"] 54 | margin_left = 748.0 55 | margin_top = 500.0 56 | margin_right = 948.0 57 | margin_bottom = 528.0 58 | rect_min_size = Vector2( 200, 0 ) 59 | size_flags_horizontal = 8 60 | size_flags_vertical = 8 61 | theme = ExtResource( 4 ) 62 | 63 | [node name="In" type="Button" parent="CanvasLayer/Zoom/HBoxContainer" index="0"] 64 | margin_right = 98.0 65 | margin_bottom = 28.0 66 | focus_mode = 0 67 | mouse_default_cursor_shape = 2 68 | size_flags_horizontal = 3 69 | text = "+" 70 | 71 | [node name="Out" type="Button" parent="CanvasLayer/Zoom/HBoxContainer" index="1"] 72 | margin_left = 102.0 73 | margin_right = 200.0 74 | margin_bottom = 28.0 75 | focus_mode = 0 76 | mouse_default_cursor_shape = 2 77 | size_flags_horizontal = 3 78 | text = "-" 79 | 80 | [connection signal="pressed" from="CanvasLayer/Zoom/HBoxContainer/In" to="." method="_on_zoom_pressed" binds= [ true ]] 81 | [connection signal="pressed" from="CanvasLayer/Zoom/HBoxContainer/Out" to="." method="_on_zoom_pressed" binds= [ false ]] 82 | -------------------------------------------------------------------------------- /scenes/objects/visualizers/visualizer_puzzle_pieces.gd: -------------------------------------------------------------------------------- 1 | extends "res://scenes/objects/visualizers/visualizer.gd" 2 | 3 | onready var _board : TextureRect = $MarginContainer/MarginContainer/Board 4 | onready var _pieces_container : GridContainer = $MarginContainer/MarginContainer/Pieces 5 | onready var _margin : MarginContainer = $MarginContainer/MarginContainer 6 | 7 | const _piece_size : int = 32 # can also be 16, 64 etc.. 8 | var _order : Array # idx : piece, idx 0 is top left piece, idx size-1 is bottom right piece 9 | 10 | const _switch_all_tween_time : float = 0.8 11 | const _pieces_scale_factor : float = 0.8 # scale down to make space for the interface 12 | 13 | 14 | func _ready(): 15 | _board.hide() 16 | var board_size : Vector2 = _board.texture.get_size() 17 | _pieces_container.columns = board_size.x/_piece_size 18 | 19 | # split board image into pieces 20 | for x in board_size.x/_piece_size: 21 | for y in board_size.y/_piece_size: 22 | var tex_rect : TextureRect = TextureRect.new() 23 | var texture : ImageTexture = ImageTexture.new() 24 | var image : Image = Image.new() 25 | # BUG?? for some reason FORMAT_RGBA8 only works if the png image has at least 26 | # one transparent pixel, otherwise we have to use FORMAT_RGB8 27 | # is this a godot issue or png specification issue? 28 | image.create(_piece_size, _piece_size, false, Image.FORMAT_RGB8) 29 | image.blit_rect( 30 | _board.texture.get_data(), Rect2( 31 | # NOTE: y first or the grid will be rotated 32 | y * _piece_size, x * _piece_size, _piece_size, _piece_size 33 | ), Vector2.ZERO 34 | ) 35 | texture.create_from_image(image, 0) 36 | tex_rect.texture = texture 37 | tex_rect.expand = true 38 | tex_rect.rect_min_size = Vector2.ONE * _piece_size * _pieces_scale_factor 39 | 40 | _order.append(tex_rect) 41 | _pieces_container.add_child(tex_rect) 42 | 43 | # override 44 | static func get_metadata() -> Dictionary: 45 | return { 46 | "name":"PUZZLE_TITLE", "image":"puzzle_pieces.png", 47 | "description":"PUZZLE_DESC", "is_enabled":true 48 | } 49 | 50 | # override 51 | func reset(): 52 | _pieces_container.add_constant_override("hseparation", 2) 53 | _pieces_container.add_constant_override("vseparation", 2) 54 | 55 | # shuffle pieces 56 | for i in _pieces_container.get_child_count(): 57 | _pieces_container.move_child( 58 | _pieces_container.get_child(i), Utility.rng.randi_range(0, _pieces_container.get_child_count()-1) 59 | ) 60 | 61 | # override 62 | func get_content_count() -> int: 63 | return _pieces_container.get_child_count() 64 | 65 | # override 66 | func determine_priority(idx1 : int, idx2 : int) -> bool: 67 | return _order.find(_pieces_container.get_child(idx1)) >\ 68 | _order.find(_pieces_container.get_child(idx2)) 69 | 70 | # override 71 | func update_indexes(action : int, idx1 : int, idx2 : int): 72 | match action: 73 | Sorter.SortAction.switch: 74 | Utility.switch_children(_pieces_container, idx1, idx2) 75 | Sorter.SortAction.move: 76 | _pieces_container.move_child(_pieces_container.get_child(idx1), idx2) 77 | 78 | emit_signal("updated_indexes") 79 | 80 | # override 81 | func update_all(new_indexes : Array): 82 | var ordered_pieces : Array 83 | for i in new_indexes: 84 | ordered_pieces.append(_pieces_container.get_child(i)) 85 | 86 | # tween each child to its new position before removing children from tree 87 | # so that once we add them back they continue to tween, giving the illusion that they're 88 | # being sorted 89 | var tween : SceneTreeTween = get_tree().create_tween() 90 | tween.connect("finished", self, "_on_switch_all_tween_finished") 91 | for i in new_indexes.size(): 92 | var first_child : TextureRect = _pieces_container.get_child(new_indexes[i]) 93 | var second_child : TextureRect = _pieces_container.get_child(i) 94 | tween.tween_property( 95 | first_child, "rect_position", second_child.rect_position, _switch_all_tween_time 96 | ).from(first_child.rect_position) 97 | tween.parallel() 98 | 99 | for i in range(_pieces_container.get_child_count()-1, -1, -1): 100 | _pieces_container.remove_child(_pieces_container.get_child(i)) 101 | 102 | for piece in ordered_pieces: 103 | _pieces_container.add_child(piece) 104 | 105 | # override 106 | func set_ui_visibility(is_visible : bool): 107 | return 108 | 109 | # override 110 | func finish(): 111 | _pieces_container.add_constant_override("hseparation", 0) 112 | _pieces_container.add_constant_override("vseparation", 0) 113 | emit_signal("finished") 114 | 115 | func _on_switch_all_tween_finished(): 116 | emit_signal("updated_all") 117 | -------------------------------------------------------------------------------- /scenes/objects/visualizers/visualizer_puzzle_pieces.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://scenes/objects/visualizers/visualizer.tscn" type="PackedScene" id=1] 4 | [ext_resource path="res://scenes/objects/visualizers/visualizer_puzzle_pieces.gd" type="Script" id=2] 5 | [ext_resource path="res://resources/textures/puzzle_board.png" type="Texture" id=3] 6 | 7 | [node name="Visualizer_puzzle_pieces" instance=ExtResource( 1 )] 8 | script = ExtResource( 2 ) 9 | 10 | [node name="Background" type="ColorRect" parent="." index="0"] 11 | anchor_right = 1.0 12 | anchor_bottom = 1.0 13 | color = Color( 0.27451, 0.258824, 0.368627, 1 ) 14 | 15 | [node name="MarginContainer" type="MarginContainer" parent="." index="1"] 16 | anchor_right = 1.0 17 | anchor_bottom = 1.0 18 | 19 | [node name="MarginContainer" type="MarginContainer" parent="MarginContainer" index="0"] 20 | margin_left = 224.0 21 | margin_top = 16.0 22 | margin_right = 736.0 23 | margin_bottom = 540.0 24 | size_flags_horizontal = 6 25 | size_flags_vertical = 10 26 | custom_constants/margin_bottom = 12 27 | 28 | [node name="Board" type="TextureRect" parent="MarginContainer/MarginContainer" index="0"] 29 | margin_right = 512.0 30 | margin_bottom = 512.0 31 | texture = ExtResource( 3 ) 32 | 33 | [node name="Pieces" type="GridContainer" parent="MarginContainer/MarginContainer" index="1"] 34 | margin_right = 512.0 35 | margin_bottom = 512.0 36 | custom_constants/vseparation = 0 37 | custom_constants/hseparation = 0 38 | -------------------------------------------------------------------------------- /scenes/objects/visualizers/visualizer_singing_lazers.gd: -------------------------------------------------------------------------------- 1 | extends "res://scenes/objects/visualizers/visualizer.gd" 2 | 3 | onready var _lazers_container : HBoxContainer = $MarginContainer/Lazers 4 | onready var _receivers_container : HBoxContainer = $MarginContainer/Receivers 5 | onready var _sync_timer : Timer = $MusicSyncTimer 6 | onready var _fireworks_container : Node2D = $Fireworks 7 | onready var _fireworks_timer : Timer = $FireworksTimer 8 | 9 | const _lazer_scene : PackedScene = preload("res://scenes/objects/components/lazer_shooter.tscn") 10 | const _lazer_receiver : PackedScene = preload("res://scenes/objects/components/lazer_receiver.tscn") 11 | 12 | const _lazers_count : int = 16 13 | const _working_lazers_count : int = 10 # this is based on available music inside resources/music/lazer_receivers/ 14 | const _music_lenght : float = 5.16 # each lazer tune is this lenght 15 | const _gradient_colors : Array = [Color.red, Color.orange, Color.yellow, Color.green, Color.blue, Color.indigo, Color.violet] 16 | const _brocken_color : Color = Color.gray 17 | 18 | const _firework_pool_size : int = 5 19 | const _firework_intervals_time : float = 2.0 20 | var _curr_pool_index : int = 0 21 | 22 | 23 | func _ready(): 24 | _sync_timer.wait_time = _music_lenght 25 | _sync_timer.start() 26 | _fireworks_timer.wait_time = _firework_intervals_time 27 | 28 | for i in _lazers_count: 29 | var lazer_instance := _lazer_scene.instance() 30 | _lazers_container.add_child(lazer_instance) 31 | var receiver_instance := _lazer_receiver.instance() 32 | _receivers_container.add_child(receiver_instance) 33 | 34 | if i < _working_lazers_count: 35 | # find the right color based on maping i from (int)[0,_working_lazers_count] to (float)[0,_rainbow_colors.size()-1] 36 | # and using its floored number as index, and its decimal as lerp value 37 | var normalized_i : float = float(i) / _working_lazers_count 38 | var color : Color = Utility.lerp_color_arr(_gradient_colors, normalized_i, true) 39 | 40 | lazer_instance.setup(false, color, i) 41 | 42 | var tune_path : String = "res://resources/music/lazer_receivers/layer" + str(i) + ".mp3" 43 | receiver_instance.setup(false, color, tune_path, i) 44 | else: 45 | lazer_instance.setup(true, _brocken_color, 999) # any index would work as long as it's bigger than _working_lazers_count 46 | receiver_instance.setup(true, _brocken_color) 47 | 48 | # create fireworks pool 49 | var original_firework : CPUParticles2D = _fireworks_container.get_child(0) 50 | for i in _firework_pool_size-1: 51 | _fireworks_container.add_child(original_firework.duplicate()) 52 | 53 | # override 54 | static func get_metadata() -> Dictionary: 55 | return { 56 | "name":"LAZERS_TITLE", "image":"singing_lazers.png", 57 | "description":"LAZERS_DESC", "is_enabled":true 58 | } 59 | 60 | # override 61 | func reset(): 62 | _fireworks_timer.stop() 63 | 64 | # shuffle lazers 65 | for lazer in _lazers_container.get_children(): 66 | _lazers_container.move_child( 67 | lazer, Utility.rng.randi_range(0, _lazers_container.get_child_count()) 68 | ) 69 | 70 | _update_receivers() 71 | 72 | # override 73 | func get_content_count() -> int: 74 | return _lazers_container.get_child_count() 75 | 76 | # override 77 | func determine_priority(idx1 : int, idx2 : int) -> bool: 78 | return _lazers_container.get_child(idx1).get_order_index() >\ 79 | _lazers_container.get_child(idx2).get_order_index() 80 | 81 | # override 82 | func update_indexes(action : int, idx1 : int, idx2 : int): 83 | match action: 84 | Sorter.SortAction.switch: 85 | var child1 := _lazers_container.get_child(idx1) 86 | var child2 := _lazers_container.get_child(idx2) 87 | child1.disappear() 88 | child2.disappear() 89 | 90 | yield(Utility.await_multiple_signals( 91 | [child1, "disappeared", child2, "disappeared"] 92 | ), "all_signals_yielded") 93 | 94 | Utility.switch_children(_lazers_container, idx1, idx2) 95 | 96 | yield(get_tree(), "idle_frame") # wait for container to sort children 97 | child1.appear() 98 | child2.appear() 99 | 100 | Sorter.SortAction.move: 101 | var child := _lazers_container.get_child(idx1) 102 | child.disappear() 103 | yield(child, "disappeared") 104 | 105 | yield(get_tree(), "idle_frame") # wait for container to sort children 106 | _lazers_container.move_child(child, idx2) 107 | child.appear() 108 | 109 | _update_receivers() 110 | 111 | emit_signal("updated_indexes") 112 | 113 | # override 114 | func update_all(new_indexes : Array): 115 | var signals : Array 116 | for lazer in _lazers_container.get_children(): 117 | lazer.disappear() 118 | signals.append(lazer) 119 | signals.append("disappeared") 120 | 121 | yield(Utility.await_multiple_signals(signals), "all_signals_yielded") 122 | 123 | # reorder lazers 124 | var ordered_lazers : Array 125 | for i in new_indexes: 126 | ordered_lazers.append(_lazers_container.get_child(i)) 127 | 128 | for i in range(ordered_lazers.size()-1, -1, -1): 129 | _lazers_container.move_child(ordered_lazers[i], 0) 130 | 131 | yield(get_tree(), "idle_frame") # wait for container to sort children 132 | 133 | for lazer in _lazers_container.get_children(): 134 | lazer.appear() 135 | _update_receivers() 136 | emit_signal("updated_all") 137 | 138 | # override 139 | func set_ui_visibility(is_visible : bool): 140 | return 141 | 142 | # override 143 | func finish(): 144 | _fireworks_timer.start() 145 | emit_signal("finished") 146 | 147 | func _on_fireworks_timeout(): 148 | var firework : CPUParticles2D = _fireworks_container.get_child(_curr_pool_index) 149 | firework.global_position = Vector2( 150 | Utility.rng.randf_range(0, get_viewport().size.x), 151 | Utility.rng.randf_range(0, get_viewport().size.y) 152 | ) 153 | firework.color = _gradient_colors[ 154 | Utility.rng.randi_range(0, _gradient_colors.size()-1) 155 | ] 156 | firework.emitting = true 157 | _curr_pool_index = (_curr_pool_index + 1) % _firework_pool_size 158 | 159 | func _update_receivers(): 160 | # while this is a bit inefficient because we're updating all lazers even 161 | # if only 2 changed, the performance loss is insignificant and much 162 | # better than calculating what lazers need to be updated in 4 different places 163 | # in code 164 | for i in _working_lazers_count: 165 | var current_child := _receivers_container.get_child(i) 166 | 167 | current_child.lazer_changed( 168 | _lazers_container.get_child(i).get_order_index(), 169 | _music_lenght - _sync_timer.time_left 170 | ) 171 | -------------------------------------------------------------------------------- /scenes/objects/visualizers/visualizer_singing_lazers.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://scenes/objects/visualizers/visualizer_singing_lazers.gd" type="Script" id=1] 4 | [ext_resource path="res://scenes/objects/visualizers/visualizer.tscn" type="PackedScene" id=2] 5 | 6 | [sub_resource type="Curve" id=1] 7 | _data = [ Vector2( 0, 0 ), 0.0, 8.45454, 0, 0, Vector2( 0.0572917, 0.995454 ), -0.0484848, -0.0484848, 0, 0, Vector2( 1, 0 ), 0.0, 0.0, 0, 0 ] 8 | 9 | [node name="VisualizerSingingLazers" instance=ExtResource( 2 )] 10 | script = ExtResource( 1 ) 11 | 12 | [node name="Background" type="ColorRect" parent="." index="0"] 13 | anchor_right = 1.0 14 | anchor_bottom = 1.0 15 | color = Color( 0.27451, 0.258824, 0.368627, 1 ) 16 | 17 | [node name="Fireworks" type="Node2D" parent="." index="1"] 18 | 19 | [node name="Firework" type="CPUParticles2D" parent="Fireworks" index="0"] 20 | emitting = false 21 | amount = 120 22 | lifetime = 3.0 23 | one_shot = true 24 | explosiveness = 1.0 25 | local_coords = false 26 | spread = 180.0 27 | gravity = Vector2( 0, 66 ) 28 | initial_velocity = 200.0 29 | initial_velocity_random = 0.6 30 | damping = 100.0 31 | damping_random = 0.2 32 | angle = 90.0 33 | angle_random = 1.0 34 | scale_amount = 8.0 35 | scale_amount_random = 0.8 36 | scale_amount_curve = SubResource( 1 ) 37 | 38 | [node name="MarginContainer" type="MarginContainer" parent="." index="2"] 39 | anchor_right = 1.0 40 | anchor_bottom = 1.0 41 | 42 | [node name="Lazers" type="HBoxContainer" parent="MarginContainer" index="0"] 43 | margin_top = 540.0 44 | margin_right = 960.0 45 | margin_bottom = 540.0 46 | size_flags_vertical = 10 47 | custom_constants/separation = 0 48 | 49 | [node name="Receivers" type="HBoxContainer" parent="MarginContainer" index="1"] 50 | margin_right = 960.0 51 | size_flags_vertical = 0 52 | custom_constants/separation = 0 53 | 54 | [node name="MusicSyncTimer" type="Timer" parent="." index="3"] 55 | 56 | [node name="FireworksTimer" type="Timer" parent="." index="4"] 57 | 58 | [connection signal="timeout" from="FireworksTimer" to="." method="_on_fireworks_timeout"] 59 | -------------------------------------------------------------------------------- /scenes/objects/visualizers/visualizer_vertical_lines.gd: -------------------------------------------------------------------------------- 1 | extends "res://scenes/objects/visualizers/visualizer.gd" 2 | 3 | onready var _rects_container : HBoxContainer = $MarginContainer/HBoxContainer 4 | 5 | const _h_gap : int = 2 6 | const _rect_count : int = 146 7 | const _rect_min_height : float = 10.0 8 | const _rect_max_height : float = 480.0 9 | 10 | var _colored_rects : Array 11 | var _default_clr : Color = Color("ffeecc") 12 | var _selected_high_clr : Color = Color("ff6973") 13 | var _selected_low_clr : Color = Color("00b9be") 14 | 15 | 16 | func _ready(): 17 | _rects_container.add_constant_override("separation", _h_gap) 18 | 19 | # split height gap evenly between rects 20 | var rect_size_intervals : Array 21 | rect_size_intervals.resize(_rect_count) 22 | var v_gap : float = (_rect_max_height - _rect_min_height) / _rect_count 23 | for i in _rect_count: 24 | rect_size_intervals[i] = _rect_min_height + v_gap * i 25 | rect_size_intervals.shuffle() # NOTE: utility.gd calls randomize() 26 | 27 | # TODO: there is a Godot bug that causes box containers to treat children size as int 28 | # when sorting instead of a float after some digging, this seems to be the problem: 29 | # https://github.com/godotengine/godot/blob/3.5/scene/gui/box_container.cpp#L64 30 | # see issue: https://github.com/godotengine/godot/issues/76265 31 | # for now the work around is the tweak _rect_count and _h_gap until it looks ok 32 | 33 | var rect_width : float = (Utility.viewport_size.x - _h_gap*_rect_count) / _rect_count 34 | for i in _rect_count: 35 | var rect : ColorRect = ColorRect.new() 36 | rect.color = _default_clr 37 | rect.size_flags_horizontal = 0 # no SIZE_NONE ??? 38 | rect.size_flags_vertical = SIZE_SHRINK_END 39 | rect.rect_min_size.x = rect_width 40 | rect.rect_min_size.y = rect_size_intervals.pop_back() 41 | 42 | _rects_container.add_child(rect) 43 | 44 | # override 45 | static func get_metadata() -> Dictionary: 46 | return { 47 | "name":"VERTICAL_TITLE", "image":"vertical_rects.png", 48 | "description":"VERTICAL_DESC", "is_enabled":true 49 | } 50 | 51 | # override 52 | func reset(): 53 | _clear_colors() 54 | # reshuffle children 55 | for i in _rects_container.get_child_count(): 56 | var curr_child := _rects_container.get_child(i) 57 | _rects_container.move_child(curr_child, Utility.rng.randi_range(0, _rects_container.get_child_count())) 58 | 59 | # override 60 | func get_content_count() -> int: 61 | return _rects_container.get_child_count() 62 | 63 | # override 64 | func determine_priority(idx1 : int, idx2 : int) -> bool: 65 | return _rects_container.get_child(idx1).rect_min_size.y > _rects_container.get_child(idx2).rect_min_size.y 66 | 67 | # override 68 | func update_indexes(action : int, idx1 : int, idx2 : int): 69 | _clear_colors() 70 | 71 | match action: 72 | Sorter.SortAction.switch: 73 | # coloring 74 | var child1 = _rects_container.get_child(idx1) 75 | var child2 = _rects_container.get_child(idx2) 76 | child1.color = _selected_low_clr 77 | child2.color = _selected_high_clr 78 | _colored_rects.append(child1) 79 | _colored_rects.append(child2) 80 | 81 | Utility.switch_children(_rects_container, idx1, idx2) 82 | Sorter.SortAction.move: 83 | # coloring 84 | var child = _rects_container.get_child(idx1) 85 | child.color = _selected_low_clr 86 | _colored_rects.append(child) 87 | 88 | _rects_container.move_child(child, idx2) 89 | 90 | emit_signal("updated_indexes") 91 | 92 | # override 93 | func update_all(new_indexes : Array): 94 | var ordered_rects : Array 95 | for i in new_indexes: 96 | ordered_rects.append(_rects_container.get_child(i)) 97 | 98 | for i in range(_rects_container.get_child_count()-1, -1, -1): 99 | _rects_container.remove_child(_rects_container.get_child(i)) 100 | 101 | for rect in ordered_rects: 102 | _rects_container.add_child(rect) 103 | 104 | emit_signal("updated_all") 105 | 106 | # override 107 | func set_ui_visibility(is_visible : bool): 108 | return 109 | 110 | # override 111 | func finish(): 112 | _clear_colors() 113 | emit_signal("finished") 114 | 115 | func _clear_colors(): 116 | for rect in _colored_rects: 117 | rect.color = _default_clr 118 | _colored_rects.clear() 119 | -------------------------------------------------------------------------------- /scenes/objects/visualizers/visualizer_vertical_lines.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [ext_resource path="res://scenes/objects/visualizers/visualizer.tscn" type="PackedScene" id=1] 4 | [ext_resource path="res://scenes/objects/visualizers/visualizer_vertical_lines.gd" type="Script" id=2] 5 | 6 | [node name="VisualizerRect" instance=ExtResource( 1 )] 7 | script = ExtResource( 2 ) 8 | 9 | [node name="Background" type="ColorRect" parent="." index="0"] 10 | anchor_right = 1.0 11 | anchor_bottom = 1.0 12 | color = Color( 0.27451, 0.258824, 0.368627, 1 ) 13 | 14 | [node name="MarginContainer" type="MarginContainer" parent="." index="1"] 15 | anchor_right = 1.0 16 | anchor_bottom = 1.0 17 | 18 | [node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer" index="0"] 19 | margin_left = 512.0 20 | margin_top = 576.0 21 | margin_right = 512.0 22 | margin_bottom = 576.0 23 | size_flags_horizontal = 6 24 | size_flags_vertical = 10 25 | -------------------------------------------------------------------------------- /scenes/tests/algorithm_checker.gd: -------------------------------------------------------------------------------- 1 | extends MarginContainer 2 | 3 | onready var _sorters_container : HFlowContainer = $MarginContainer/VBoxContainer/Sorter/Algorithms/Options 4 | onready var _methods_container : HBoxContainer = $MarginContainer/VBoxContainer/Method/PanelContainer/Method 5 | 6 | onready var _run_btn : Button = $MarginContainer/VBoxContainer/InputOutput/VBoxContainer/Run/MarginContainer/HBoxContainer/Run 7 | onready var _run_util_err_btn : Button = $MarginContainer/VBoxContainer/InputOutput/VBoxContainer/Run/MarginContainer/HBoxContainer/RunUntilErr 8 | onready var _console : RichTextLabel = $MarginContainer/VBoxContainer/InputOutput/VBoxContainer/VBoxContainer/Console 9 | 10 | const _starting_sorter : String = "BUBBLESORT" 11 | var _selected_sorter_name : String 12 | var _use_next_step_func : bool = true 13 | var _array_size : int = 10 14 | var _allow_duplicates : bool = true 15 | var _trace_steps : bool = false 16 | 17 | const _max_printable_array_size : int = 30 # _current_input is printed to console as long as it's smaller than this 18 | const _continuous_test_max_loops : int = 100 19 | var _current_input : Array 20 | var _prev_report_end_idx_cache : int = -1 21 | var _dash_char_width : float 22 | 23 | const _good_color : String = "#92e229" 24 | const _warn_color : String = "#c9c14b" 25 | const _bad_color : String = "#a21515" 26 | 27 | 28 | func _ready(): 29 | var font : Font = _console.get_font("normal_font") 30 | _dash_char_width = font.get_char_size(ord('-')).x 31 | 32 | # add sorters 33 | for key in FilesTracker.get_sorters_dict(): 34 | var box : CheckBox = CheckBox.new() 35 | box.text = key 36 | box.focus_mode = Control.FOCUS_NONE 37 | box.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND 38 | box.connect("toggled", self, "_on_sorter_toggled", [box]) 39 | _sorters_container.add_child(box) 40 | 41 | # pick one sorter by default 42 | _selected_sorter_name = _starting_sorter 43 | for sorter_box in _sorters_container.get_children(): 44 | if sorter_box.text == _starting_sorter: 45 | sorter_box.set_pressed_no_signal(true) 46 | break 47 | 48 | # connect methods 49 | for method_box in _methods_container.get_children(): 50 | method_box.connect("toggled", self, "_on_method_toggled", [method_box]) 51 | 52 | func _on_sorter_toggled(toggled : bool, box : CheckBox): 53 | if toggled: 54 | _toggle_checkbox(box, _sorters_container.get_children()) 55 | _selected_sorter_name = box.text 56 | else: 57 | box.set_pressed_no_signal(true) 58 | 59 | func _on_method_toggled(toggled : bool, box : CheckBox): 60 | if toggled: 61 | _toggle_checkbox(box, _methods_container.get_children()) 62 | if box.name == "Next": _use_next_step_func = true 63 | elif box.name == "Last": _use_next_step_func = false 64 | else: 65 | box.set_pressed_no_signal(true) 66 | 67 | func _on_array_size_changed(value : float): 68 | _array_size = value 69 | 70 | func _on_allow_duplicates_toggled(button_pressed : bool): 71 | _allow_duplicates = button_pressed 72 | 73 | func _on_trace_steps_toggled(button_pressed : bool): 74 | _trace_steps = button_pressed 75 | 76 | func _on_run_test_pressed(): 77 | _run_btn.disabled = true 78 | _run_util_err_btn.disabled = true 79 | _prev_report_end_idx_cache = _console.bbcode_text.length() 80 | 81 | # setup 82 | _setup_test_input() 83 | _print_test_summery(true) 84 | 85 | # setup, sorter 86 | var sorter_object : Sorter = load(FilesTracker.get_sorters_dict()[_selected_sorter_name]).new() 87 | sorter_object.setup(_array_size, funcref(self, "_test_callback")) 88 | 89 | var original_input : Array = _current_input.duplicate() 90 | var succeeded : bool = _run_single_test(sorter_object, original_input) 91 | 92 | if _array_size < _max_printable_array_size: 93 | _console_print("input array:") 94 | _console_print(str(original_input)) 95 | _console_print("result array:") 96 | _console_print(str(_current_input)) 97 | 98 | if succeeded: 99 | _console_print("sorting finished successfully", _good_color) 100 | else: 101 | _console_print("sorting finished with errors", _bad_color) 102 | 103 | _console_separate() 104 | 105 | _run_btn.disabled = false 106 | _run_util_err_btn.disabled = false 107 | 108 | func _on_run_test_until_err_pressed(): 109 | _run_btn.disabled = true 110 | _run_util_err_btn.disabled = true 111 | _prev_report_end_idx_cache = _console.bbcode_text.length() 112 | 113 | # setup 114 | _setup_test_input() 115 | _print_test_summery(false) 116 | 117 | # setup, sorter 118 | var sorter_object : Sorter = load(FilesTracker.get_sorters_dict()[_selected_sorter_name]).new() 119 | sorter_object.setup(_array_size, funcref(self, "_test_callback")) 120 | 121 | # run tests 122 | var succeeded : bool 123 | var iteration : int = 0 124 | while iteration <= _continuous_test_max_loops: 125 | # report 126 | succeeded = _run_single_test(sorter_object, _current_input.duplicate()) 127 | if succeeded == false: break 128 | 129 | iteration += 1 130 | _setup_test_input() 131 | sorter_object.setup(_array_size, funcref(self, "_test_callback")) 132 | 133 | if iteration == _continuous_test_max_loops: 134 | _console_print("continuous testing reached max attemps without errors", _good_color) 135 | elif succeeded == false: 136 | _console_print("continuous testing encountered an error after " + str(iteration) + " iterations", _bad_color) 137 | 138 | if succeeded: 139 | _console_print("sorting finished successfully", _good_color) 140 | else: 141 | _console_print("sorting finished with errors", _bad_color) 142 | 143 | _console_separate() 144 | 145 | _run_btn.disabled = false 146 | _run_util_err_btn.disabled = false 147 | 148 | func _on_delete_old_reports_pressed(): 149 | if _prev_report_end_idx_cache == -1: return 150 | 151 | _console.bbcode_text = _console.bbcode_text.substr(_prev_report_end_idx_cache) 152 | _prev_report_end_idx_cache = -1 153 | 154 | func _setup_test_input(): 155 | # populate _current_input 156 | _current_input.resize(_array_size) 157 | if _allow_duplicates: 158 | for i in _array_size: 159 | _current_input[i] = Utility.rng.randi_range(0, _array_size-1) 160 | else: 161 | for i in _array_size: _current_input[i] = i 162 | 163 | for i in _array_size: 164 | var rand_idx : int = Utility.rng.randi_range(0, _array_size-1) 165 | var temp_i : int = _current_input[i] 166 | _current_input[i] = _current_input[rand_idx] 167 | _current_input[rand_idx] = temp_i 168 | 169 | func _print_test_summery(is_running_once : bool): 170 | # print summery based on class variables 171 | _console_print("running tests for [b]" + tr(_selected_sorter_name) + "[/b]") 172 | if _use_next_step_func: 173 | _console_print("using [b]sorter.next_step()[/b]") 174 | else: 175 | _console_print("using [b]sorter.skip_to_last_step()[/b]") 176 | 177 | _console_print("using array of size " + str(_array_size) + 178 | (", duplicates allowed" if _allow_duplicates else ", no duplicates") ) 179 | if is_running_once && _array_size < _max_printable_array_size: 180 | _console_print("input array: " + str(_current_input)) 181 | if is_running_once == false: 182 | _console_print("this test will run [b]repeatedly[/b] until an error occurs or it has run " + str(_continuous_test_max_loops) + " times") 183 | 184 | if _array_size >= _max_printable_array_size: 185 | _console_print( 186 | "NOTE: array is too big to print, make array size smaller than " + 187 | str(_max_printable_array_size) + 188 | " if you want to see input/output content" 189 | ) 190 | 191 | _console_print("") 192 | 193 | func _run_single_test(sorter_object : Sorter, original_input : Array) -> bool: 194 | # step 1: run tests 195 | if _use_next_step_func: # next_step() 196 | var has_errors : bool = false 197 | var iterations : int = 0 198 | var can_trace_steps : bool = _trace_steps && _array_size < _max_printable_array_size 199 | while true: 200 | var result : Dictionary = sorter_object.next_step() 201 | if result.has("done") == false: 202 | _console_print("sorter.next_step() return doesn't contain 'done' entry", _bad_color) 203 | has_errors = true 204 | break 205 | 206 | if result["done"]: break 207 | else: 208 | var incomplete_entry_err : String 209 | if result.has("action") == false: 210 | incomplete_entry_err =\ 211 | "sorter.next_step() return doesn't contain 'action' entry" 212 | elif Sorter.SortAction.values().has(result["action"]) == false: 213 | incomplete_entry_err =\ 214 | "sorter.next_step() 'action' entry isn't of type Sorter.SortAction" 215 | elif result.has("indexes") == false: 216 | incomplete_entry_err =\ 217 | "sorter.next_step() return doesn't contain 'indexes' entry" 218 | elif result["indexes"].size() != 2: 219 | incomplete_entry_err =\ 220 | "sorter.next_step() 'indexes' entry must contain 2 entries" 221 | 222 | if incomplete_entry_err.empty() == false: 223 | _console_print(incomplete_entry_err, _bad_color) 224 | has_errors = true 225 | break 226 | 227 | if result["indexes"][0] == result["indexes"][1]: 228 | _console_print( 229 | "sorter.next_step() 'indexes' both values are the same," + 230 | "this is pointless and can cause issues with some visualizers", 231 | _warn_color 232 | ) 233 | 234 | if result["action"] == Sorter.SortAction.switch: 235 | Utility.swap_elements(_current_input, result["indexes"][0], result["indexes"][1]) 236 | elif result["action"] == Sorter.SortAction.move: 237 | Utility.move_element(_current_input, result["indexes"][0], result["indexes"][1]) 238 | 239 | if can_trace_steps: 240 | # output _current_input while highlighting the 2 indexes that were switched 241 | var content_string : String = "[" 242 | for i in _current_input.size(): 243 | var input_str : String = str(_current_input[i]) + ', ' 244 | 245 | if i == result["indexes"][0]: 246 | input_str = "[color=" + _good_color + "]" + input_str + "[/color]" 247 | elif i == result["indexes"][1]: 248 | input_str = "[color=" + _bad_color + "]" + input_str + "[/color]" 249 | 250 | content_string += input_str 251 | content_string += "]" 252 | 253 | _console_print("step " + str(iterations) + ": " + content_string) 254 | 255 | iterations += 1 256 | 257 | if can_trace_steps: _console_print("") # new line after steps 258 | 259 | if has_errors: 260 | _console_print("sorter.next_step() encountered an error after " + str(iterations) + " iterations", _bad_color) 261 | return false 262 | else: 263 | _console_print("sorter.next_step() finished after " + str(iterations) + " iterations") 264 | 265 | else: # skip_to_last_step() 266 | var sort_result : Array = sorter_object.skip_to_last_step() 267 | if sort_result.size() != _current_input.size(): 268 | _console_print( 269 | "returned array size (" + str(sort_result.size()) + 270 | ") doesn't match input array size (" + str(_current_input.size()) + ")", _bad_color 271 | ) 272 | return false 273 | else: 274 | # reorder _current_input 275 | var new_arr : Array 276 | new_arr.resize(sort_result.size()) 277 | 278 | for i in sort_result.size(): 279 | new_arr[i] = _current_input[sort_result[i]] 280 | 281 | _current_input = new_arr 282 | 283 | # step 2: validation 284 | # validate that items in returned array are the same as items in original array 285 | var values_record : Dictionary 286 | for el in original_input: 287 | if values_record.has(el) == false: values_record[el] = 0 288 | values_record[el] += 1 289 | for el in _current_input: 290 | if values_record.has(el) == false: 291 | _console_print("sorted array has different elements than input array, data is corrupted", _bad_color) 292 | return false 293 | 294 | values_record[el] -= 1 295 | 296 | for val in values_record.values(): 297 | if val != 0: 298 | _console_print("sorted array has different elements than input array, data is corrupted", _bad_color) 299 | return false 300 | 301 | # validate returned array order 302 | for i in range(1, _current_input.size()): 303 | if _current_input[i] < _current_input[i-1]: 304 | _console_print("sorted array order is wrong", _bad_color) 305 | return false 306 | 307 | return true 308 | 309 | func _test_callback(idx1 : int, idx2 : int) -> bool: 310 | # just like visualizer.determine_priority() 311 | return _current_input[idx1] > _current_input[idx2] 312 | 313 | func _console_print(text : String, color_hex : String = ""): 314 | # TODO: make an issue about how it isn't obvious how Color.to_html() returns a hex string 315 | if color_hex.empty() == false: 316 | text = "[color=" + color_hex + "]" + text + "[/color]" 317 | 318 | _console.bbcode_text += text + '\n' 319 | 320 | func _console_separate(): 321 | # not the most accurate, but this calculates how many chars we need to fill a line 322 | var console_width : float = _console.rect_size.x 323 | if _console.get_v_scroll().visible: console_width -= _console.get_v_scroll().rect_size.x 324 | 325 | for i in floor(console_width / _dash_char_width) - 1: 326 | _console.bbcode_text += '-' 327 | 328 | _console.bbcode_text += '\n' 329 | 330 | func _toggle_checkbox(target : CheckBox, all_boxes : Array): 331 | for box in all_boxes: 332 | if box != target: 333 | box.set_pressed_no_signal(false) 334 | -------------------------------------------------------------------------------- /scenes/tests/algorithm_checker.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://scenes/tests/algorithm_checker.gd" type="Script" id=1] 4 | [ext_resource path="res://resources/godot/algorithm_checker_theme.tres" type="Theme" id=2] 5 | 6 | [sub_resource type="StyleBoxFlat" id=1] 7 | draw_center = false 8 | border_width_left = 2 9 | border_width_top = 2 10 | border_width_right = 2 11 | border_width_bottom = 2 12 | border_color = Color( 1, 0.933333, 0.8, 1 ) 13 | corner_radius_bottom_right = 8 14 | corner_radius_bottom_left = 8 15 | 16 | [node name="algorithm_checker" type="MarginContainer"] 17 | anchor_right = 1.0 18 | anchor_bottom = 1.0 19 | theme = ExtResource( 2 ) 20 | script = ExtResource( 1 ) 21 | 22 | [node name="ColorRect" type="ColorRect" parent="."] 23 | margin_right = 1024.0 24 | margin_bottom = 576.0 25 | color = Color( 0.27451, 0.258824, 0.368627, 1 ) 26 | 27 | [node name="MarginContainer" type="MarginContainer" parent="."] 28 | margin_right = 1024.0 29 | margin_bottom = 576.0 30 | custom_constants/margin_right = 45 31 | custom_constants/margin_top = 6 32 | custom_constants/margin_left = 45 33 | custom_constants/margin_bottom = 6 34 | 35 | [node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"] 36 | margin_left = 45.0 37 | margin_top = 6.0 38 | margin_right = 979.0 39 | margin_bottom = 570.0 40 | custom_constants/separation = 12 41 | 42 | [node name="Sorter" type="VBoxContainer" parent="MarginContainer/VBoxContainer"] 43 | margin_right = 934.0 44 | margin_bottom = 80.0 45 | 46 | [node name="Label" type="Label" parent="MarginContainer/VBoxContainer/Sorter"] 47 | margin_right = 934.0 48 | margin_bottom = 36.0 49 | text = "Sorter:" 50 | align = 1 51 | 52 | [node name="Algorithms" type="PanelContainer" parent="MarginContainer/VBoxContainer/Sorter"] 53 | margin_top = 40.0 54 | margin_right = 934.0 55 | margin_bottom = 80.0 56 | rect_min_size = Vector2( 0, 40 ) 57 | 58 | [node name="Options" type="HFlowContainer" parent="MarginContainer/VBoxContainer/Sorter/Algorithms"] 59 | margin_top = 20.0 60 | margin_right = 934.0 61 | margin_bottom = 20.0 62 | size_flags_vertical = 6 63 | custom_constants/hseparation = 20 64 | 65 | [node name="Method" type="VBoxContainer" parent="MarginContainer/VBoxContainer"] 66 | margin_top = 92.0 67 | margin_right = 934.0 68 | margin_bottom = 172.0 69 | 70 | [node name="Label" type="Label" parent="MarginContainer/VBoxContainer/Method"] 71 | margin_right = 934.0 72 | margin_bottom = 36.0 73 | text = "Method:" 74 | align = 1 75 | 76 | [node name="PanelContainer" type="PanelContainer" parent="MarginContainer/VBoxContainer/Method"] 77 | margin_top = 40.0 78 | margin_right = 934.0 79 | margin_bottom = 80.0 80 | rect_min_size = Vector2( 0, 40 ) 81 | 82 | [node name="Method" type="HBoxContainer" parent="MarginContainer/VBoxContainer/Method/PanelContainer"] 83 | margin_top = 2.0 84 | margin_right = 934.0 85 | margin_bottom = 38.0 86 | size_flags_vertical = 6 87 | 88 | [node name="Next" type="CheckBox" parent="MarginContainer/VBoxContainer/Method/PanelContainer/Method"] 89 | margin_left = 176.0 90 | margin_right = 288.0 91 | margin_bottom = 36.0 92 | focus_mode = 0 93 | mouse_default_cursor_shape = 2 94 | size_flags_horizontal = 6 95 | pressed = true 96 | text = "next_step()" 97 | 98 | [node name="Last" type="CheckBox" parent="MarginContainer/VBoxContainer/Method/PanelContainer/Method"] 99 | margin_left = 615.0 100 | margin_right = 788.0 101 | margin_bottom = 36.0 102 | focus_mode = 0 103 | mouse_default_cursor_shape = 2 104 | size_flags_horizontal = 6 105 | text = "skip_to_last_step()" 106 | 107 | [node name="Data" type="VBoxContainer" parent="MarginContainer/VBoxContainer"] 108 | margin_top = 184.0 109 | margin_right = 934.0 110 | margin_bottom = 302.0 111 | 112 | [node name="Label" type="Label" parent="MarginContainer/VBoxContainer/Data"] 113 | margin_right = 934.0 114 | margin_bottom = 36.0 115 | text = "Input Data:" 116 | align = 1 117 | 118 | [node name="PanelContainer" type="PanelContainer" parent="MarginContainer/VBoxContainer/Data"] 119 | margin_top = 40.0 120 | margin_right = 934.0 121 | margin_bottom = 118.0 122 | rect_min_size = Vector2( 0, 40 ) 123 | 124 | [node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Data/PanelContainer"] 125 | margin_right = 934.0 126 | margin_bottom = 78.0 127 | size_flags_vertical = 6 128 | 129 | [node name="HBoxContainer3" type="HBoxContainer" parent="MarginContainer/VBoxContainer/Data/PanelContainer/VBoxContainer"] 130 | margin_right = 934.0 131 | margin_bottom = 38.0 132 | 133 | [node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer/Data/PanelContainer/VBoxContainer/HBoxContainer3"] 134 | margin_left = 143.0 135 | margin_right = 322.0 136 | margin_bottom = 38.0 137 | size_flags_horizontal = 6 138 | 139 | [node name="Label" type="Label" parent="MarginContainer/VBoxContainer/Data/PanelContainer/VBoxContainer/HBoxContainer3/HBoxContainer"] 140 | margin_top = 1.0 141 | margin_right = 85.0 142 | margin_bottom = 37.0 143 | text = "Array Size:" 144 | 145 | [node name="SpinBox" type="SpinBox" parent="MarginContainer/VBoxContainer/Data/PanelContainer/VBoxContainer/HBoxContainer3/HBoxContainer"] 146 | margin_left = 89.0 147 | margin_right = 179.0 148 | margin_bottom = 38.0 149 | mouse_default_cursor_shape = 2 150 | min_value = 4.0 151 | max_value = 1000.0 152 | value = 10.0 153 | rounded = true 154 | align = 1 155 | 156 | [node name="HBoxContainer2" type="HBoxContainer" parent="MarginContainer/VBoxContainer/Data/PanelContainer/VBoxContainer/HBoxContainer3"] 157 | margin_left = 623.0 158 | margin_right = 780.0 159 | margin_bottom = 38.0 160 | size_flags_horizontal = 6 161 | 162 | [node name="Label" type="Label" parent="MarginContainer/VBoxContainer/Data/PanelContainer/VBoxContainer/HBoxContainer3/HBoxContainer2"] 163 | margin_top = 1.0 164 | margin_right = 137.0 165 | margin_bottom = 37.0 166 | text = "Allow Duplicates:" 167 | 168 | [node name="CheckBox" type="CheckBox" parent="MarginContainer/VBoxContainer/Data/PanelContainer/VBoxContainer/HBoxContainer3/HBoxContainer2"] 169 | margin_left = 141.0 170 | margin_right = 157.0 171 | margin_bottom = 38.0 172 | focus_mode = 0 173 | mouse_default_cursor_shape = 2 174 | pressed = true 175 | 176 | [node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer/Data/PanelContainer/VBoxContainer"] 177 | margin_top = 42.0 178 | margin_right = 934.0 179 | margin_bottom = 78.0 180 | 181 | [node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer/Data/PanelContainer/VBoxContainer/HBoxContainer"] 182 | margin_left = 412.0 183 | margin_right = 522.0 184 | margin_bottom = 36.0 185 | size_flags_horizontal = 6 186 | 187 | [node name="Label" type="Label" parent="MarginContainer/VBoxContainer/Data/PanelContainer/VBoxContainer/HBoxContainer/HBoxContainer"] 188 | margin_right = 90.0 189 | margin_bottom = 36.0 190 | text = "Trace Steps" 191 | 192 | [node name="CheckBox" type="CheckBox" parent="MarginContainer/VBoxContainer/Data/PanelContainer/VBoxContainer/HBoxContainer/HBoxContainer"] 193 | margin_left = 94.0 194 | margin_right = 110.0 195 | margin_bottom = 36.0 196 | focus_mode = 0 197 | mouse_default_cursor_shape = 2 198 | 199 | [node name="InputOutput" type="PanelContainer" parent="MarginContainer/VBoxContainer"] 200 | margin_top = 314.0 201 | margin_right = 934.0 202 | margin_bottom = 564.0 203 | size_flags_vertical = 3 204 | custom_styles/panel = SubResource( 1 ) 205 | 206 | [node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/InputOutput"] 207 | margin_left = 2.0 208 | margin_top = 2.0 209 | margin_right = 932.0 210 | margin_bottom = 248.0 211 | 212 | [node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/InputOutput/VBoxContainer"] 213 | margin_right = 930.0 214 | margin_bottom = 198.0 215 | size_flags_vertical = 3 216 | 217 | [node name="Label" type="Label" parent="MarginContainer/VBoxContainer/InputOutput/VBoxContainer/VBoxContainer"] 218 | margin_right = 930.0 219 | margin_bottom = 36.0 220 | text = "Results:" 221 | align = 1 222 | 223 | [node name="Console" type="RichTextLabel" parent="MarginContainer/VBoxContainer/InputOutput/VBoxContainer/VBoxContainer"] 224 | margin_top = 40.0 225 | margin_right = 930.0 226 | margin_bottom = 198.0 227 | size_flags_vertical = 3 228 | bbcode_enabled = true 229 | scroll_following = true 230 | deselect_on_focus_loss_enabled = false 231 | 232 | [node name="Run" type="PanelContainer" parent="MarginContainer/VBoxContainer/InputOutput/VBoxContainer"] 233 | margin_top = 202.0 234 | margin_right = 930.0 235 | margin_bottom = 246.0 236 | rect_min_size = Vector2( 0, 40 ) 237 | 238 | [node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/InputOutput/VBoxContainer/Run"] 239 | margin_right = 930.0 240 | margin_bottom = 44.0 241 | custom_constants/margin_right = 2 242 | custom_constants/margin_top = 2 243 | custom_constants/margin_left = 2 244 | custom_constants/margin_bottom = 2 245 | 246 | [node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer/InputOutput/VBoxContainer/Run/MarginContainer"] 247 | margin_left = 265.0 248 | margin_top = 2.0 249 | margin_right = 665.0 250 | margin_bottom = 42.0 251 | rect_min_size = Vector2( 400, 0 ) 252 | size_flags_horizontal = 6 253 | 254 | [node name="Run" type="Button" parent="MarginContainer/VBoxContainer/InputOutput/VBoxContainer/Run/MarginContainer/HBoxContainer"] 255 | margin_right = 198.0 256 | margin_bottom = 40.0 257 | focus_mode = 0 258 | mouse_default_cursor_shape = 2 259 | size_flags_horizontal = 3 260 | text = "Run Test" 261 | 262 | [node name="RunUntilErr" type="Button" parent="MarginContainer/VBoxContainer/InputOutput/VBoxContainer/Run/MarginContainer/HBoxContainer"] 263 | margin_left = 202.0 264 | margin_right = 400.0 265 | margin_bottom = 40.0 266 | focus_mode = 0 267 | mouse_default_cursor_shape = 2 268 | size_flags_horizontal = 3 269 | text = "Run Until Error" 270 | 271 | [node name="Clear" type="Button" parent="MarginContainer/VBoxContainer/InputOutput/VBoxContainer/Run/MarginContainer"] 272 | margin_left = 781.0 273 | margin_top = 2.0 274 | margin_right = 928.0 275 | margin_bottom = 42.0 276 | rect_min_size = Vector2( 120, 0 ) 277 | focus_mode = 0 278 | mouse_default_cursor_shape = 2 279 | size_flags_horizontal = 10 280 | text = "Clear Old Reports" 281 | 282 | [connection signal="value_changed" from="MarginContainer/VBoxContainer/Data/PanelContainer/VBoxContainer/HBoxContainer3/HBoxContainer/SpinBox" to="." method="_on_array_size_changed"] 283 | [connection signal="toggled" from="MarginContainer/VBoxContainer/Data/PanelContainer/VBoxContainer/HBoxContainer3/HBoxContainer2/CheckBox" to="." method="_on_allow_duplicates_toggled"] 284 | [connection signal="toggled" from="MarginContainer/VBoxContainer/Data/PanelContainer/VBoxContainer/HBoxContainer/HBoxContainer/CheckBox" to="." method="_on_trace_steps_toggled"] 285 | [connection signal="pressed" from="MarginContainer/VBoxContainer/InputOutput/VBoxContainer/Run/MarginContainer/HBoxContainer/Run" to="." method="_on_run_test_pressed"] 286 | [connection signal="pressed" from="MarginContainer/VBoxContainer/InputOutput/VBoxContainer/Run/MarginContainer/HBoxContainer/RunUntilErr" to="." method="_on_run_test_until_err_pressed"] 287 | [connection signal="pressed" from="MarginContainer/VBoxContainer/InputOutput/VBoxContainer/Run/MarginContainer/Clear" to="." method="_on_delete_old_reports_pressed"] 288 | --------------------------------------------------------------------------------