├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md └── src ├── ELPath.py ├── algohost.py ├── algowindow.py ├── definitions.py ├── main.py ├── pathfindhost.py ├── pathingwindow.py └── resources └── fonts └── Roboto_Mono ├── LICENSE.txt ├── README.txt ├── RobotoMono-Italic-VariableFont_wght.ttf ├── RobotoMono-VariableFont_wght.ttf └── static ├── RobotoMono-Bold.ttf ├── RobotoMono-BoldItalic.ttf ├── RobotoMono-ExtraLight.ttf ├── RobotoMono-ExtraLightItalic.ttf ├── RobotoMono-Italic.ttf ├── RobotoMono-Light.ttf ├── RobotoMono-LightItalic.ttf ├── RobotoMono-Medium.ttf ├── RobotoMono-MediumItalic.ttf ├── RobotoMono-Regular.ttf ├── RobotoMono-SemiBold.ttf ├── RobotoMono-SemiBoldItalic.ttf ├── RobotoMono-Thin.ttf └── RobotoMono-ThinItalic.ttf /.gitignore: -------------------------------------------------------------------------------- 1 | **/__pycache__/ -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.formatting.provider": "autopep8" 3 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 ChrisO 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ELPath 2 | An algorithm visualizer. 3 | 4 | ![Pathfinding example (A*)](https://user-images.githubusercontent.com/39662993/129440226-7e59d85e-3e0c-4f14-b602-fe349fd9021c.png) 5 | 6 | ELPath is lovingly built using DearPyGui as a basis for its GUI and plotting functions. 7 | This is aimed at providing understandable visualizations of common sorting and pathfinding algorithms. 8 | 9 | Visualizations include Quick Sort, Merge Sort, Bubble Sort, Insertion Sort, Selection Sort, Cocktail Sort, Breadth and Depth-First Search, Dijkstra's Algorithm, and A* Search. 10 | 11 | The scale and execution speed of the demonstrations can be modiifed by the user, and a handy maze generation feature is included in the pathfinding to simplify the process of simulating algorithms in identical and fair conditions. 12 | 13 | ![Sorting](https://user-images.githubusercontent.com/39662993/129440233-a326e4be-00b0-4771-b467-158f8f74c968.png) 14 | 15 | ![Maze generation feature](https://user-images.githubusercontent.com/39662993/129440181-32e5d95f-ba5b-4e30-8689-7e363f91be6b.png) 16 | 17 | Development has officially ceased, but breaking changes to ELPath due to DearPyGui will be noticed and resolved periodically. 18 | 19 | # Dependencies: 20 | DearPyGui v1.5.1 21 | 22 | # Do note that I've made a web-based successor to ELPath, particularly for improved sorting demonstrations: [Sorgra.](https://github.com/ChrisOh431/Sorgra) 23 | -------------------------------------------------------------------------------- /src/ELPath.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | from time import sleep 3 | from algowindow import AlgorithmWindow 4 | from pathingwindow import PathingWindow 5 | import definitions as defs 6 | 7 | 8 | class ELPath(): 9 | def __init__(self): 10 | self.WINDOW_WIDTH, self.WINDOW_HEIGHT = 1250, 950 11 | self.SIDEBAR_WIDTH = 350 12 | 13 | self.step_sleep = 5 14 | self.algorithms = None 15 | self.pathing = None 16 | 17 | self.mode = "Pathfinding" 18 | 19 | self.all_algorithms = {} 20 | 21 | self.curr_window = None 22 | 23 | self.ELPath_window = None 24 | self.controls_panel = None 25 | self.simulation_window = None 26 | 27 | self.combobox = dpg.generate_uuid() 28 | self.sleepslider = dpg.generate_uuid() 29 | self.infotext = dpg.generate_uuid() 30 | 31 | self.transient_controls = [] 32 | 33 | self.__init_window() 34 | 35 | self.__mount(self.mode, "Breadth-First Search") 36 | 37 | def viewport_config(self): 38 | # Necessary to set the window size 39 | dpg.create_viewport() 40 | dpg.setup_dearpygui() 41 | dpg.show_viewport() 42 | dpg.set_viewport_title("ELPath") 43 | dpg.set_viewport_width(self.WINDOW_WIDTH) 44 | dpg.set_viewport_height(self.WINDOW_HEIGHT) 45 | 46 | def __init_window(self): 47 | with dpg.font_registry(): 48 | dpg.bind_font(dpg.add_font( 49 | "resources/fonts/Roboto_Mono/static/RobotoMono-Regular.ttf", 24)) 50 | 51 | 52 | with dpg.window(label="ELPath") as self.ELPath_window: 53 | with dpg.child_window(label="Controls_Info", width=self.SIDEBAR_WIDTH, pos=[10, 10], border=False, no_scrollbar=True): 54 | with dpg.collapsing_header(label="Controls", default_open=True) as self.controls_panel: 55 | pass 56 | with dpg.collapsing_header(label="Info", default_open=True): 57 | self.infotext = dpg.add_text("alginfo", wrap=300) 58 | 59 | with dpg.child_window(label="Simulation", height=815, width=815, no_scrollbar=True, pos=[self.SIDEBAR_WIDTH+20, 10]) as simulation_window: 60 | self.simulation_window = simulation_window 61 | 62 | dpg.set_primary_window(self.ELPath_window, True) 63 | 64 | # populate alg list for combobox 65 | tempalgs = AlgorithmWindow() 66 | for algname in tempalgs.algorithms_host.alg_list: 67 | self.all_algorithms[algname] = "Sorting" 68 | tempalgs = PathingWindow() 69 | for algname in tempalgs.pathing_host.alg_list: 70 | self.all_algorithms[algname] = "Pathfinding" 71 | tempalgs = None 72 | 73 | def __unmount(self): 74 | dpg.delete_item(self.controls_panel, 75 | children_only=True) # remove controls 76 | 77 | if self.mode == "Sorting": 78 | self.algorithms.unmount() 79 | self.algorithms = None 80 | elif self.mode == "Pathfinding": 81 | self.pathing.unmount() 82 | self.pathing = None 83 | 84 | # Should be cleared in the unmounting functions, but this is here for extra insurance. 85 | dpg.delete_item(self.simulation_window, children_only=True) 86 | 87 | def change_algorithm(self): 88 | newalg = dpg.get_value(self.combobox) 89 | newalgmode = self.all_algorithms[newalg] 90 | 91 | # User was on a sorting simulation and swapped to another sorting simulation 92 | if self.mode == "Sorting" and newalgmode == self.mode: 93 | self.sorting_callbacks["set_algorithm"](newalg) 94 | 95 | # User was on a pathfinding simulation and swapped to another sorting simulation 96 | elif self.mode == "Pathfinding" and newalgmode == self.mode: 97 | self.pathfinding_callbacks["set_algorithm"](newalg) 98 | 99 | # User swapped between a sorting/pathfinding simulation 100 | else: 101 | self.__unmount() 102 | self.__mount(newalgmode, newalg) 103 | 104 | self.update_info_no_wrapper() 105 | self.update_info_alg_definition() 106 | 107 | def __mount(self, newmode, newalg): 108 | if (newmode == "Sorting"): 109 | self.__mount_sorting(newalg) 110 | self.__link_sorting_controls() 111 | self.curr_window = self.algorithms 112 | else: 113 | self.__mount_pathing(newalg) 114 | self.__link_pathing_controls() 115 | self.curr_window = self.pathing 116 | 117 | self.mode = newmode 118 | self.update_info_no_wrapper() 119 | self.update_info_alg_definition() 120 | 121 | def __mount_sorting(self, alg): 122 | self.algorithms = AlgorithmWindow(window_tag=self.simulation_window) 123 | self.sorting_callbacks = { 124 | "decrease": self.update_info(self.algorithms.decrease_dataset), 125 | "increase": self.update_info(self.algorithms.increase_dataset), 126 | "next_step": self.update_info(self.algorithms.next_step), 127 | "original": self.update_info(self.algorithms.original_data), 128 | "randomize": self.update_info(self.algorithms.new_dataset), 129 | "run_sim": self.run_sim, 130 | "set_algorithm": self.algorithms.change_algorithm 131 | } 132 | self.algorithms.initialize_plot() 133 | self.algorithms.reset_plot() 134 | self.algorithms.change_algorithm(alg) 135 | 136 | def __mount_pathing(self, alg): 137 | self.pathing = PathingWindow(window_tag=self.simulation_window) 138 | self.pathfinding_callbacks = { 139 | "decrease": self.update_info(self.pathing.shrink_maze), 140 | "generate_maze": self.update_info(self.pathing.randmaze), 141 | "increase": self.update_info(self.pathing.grow_maze), 142 | "next_step": self.update_info(self.pathing.next_step), 143 | "reset": self.update_info(self.pathing.reset), 144 | "retry": self.update_info(self.pathing.retry), 145 | "run_sim": self.run_sim, 146 | "set_algorithm": self.pathing.change_algorithm, 147 | } 148 | self.pathing.initialize_grid() 149 | self.pathing.change_algorithm(alg) 150 | 151 | def __link_sorting_controls(self): 152 | self.transient_controls = [] 153 | 154 | def algobox(): 155 | dpg.add_text("Algorithm:", parent=self.controls_panel) 156 | combobox = dpg.add_combo(label="", parent=self.controls_panel, 157 | default_value=self.algorithms.algorithms_host.alg_name, 158 | items=list( 159 | self.all_algorithms.keys()), 160 | callback=self.change_algorithm, 161 | width=300, 162 | tag=self.combobox) 163 | 164 | self.transient_controls.append(combobox) 165 | 166 | def runandnext(): 167 | with dpg.group(horizontal=True, parent=self.controls_panel): 168 | runcheck = dpg.add_checkbox( 169 | label="Run", callback=self.sorting_callbacks["run_sim"]) 170 | nextstep = dpg.add_button( 171 | label="Next Step", callback=self.sorting_callbacks["next_step"]) 172 | 173 | self.transient_controls.append(nextstep) 174 | 175 | def datacontrols(): 176 | dpg.add_text("Data:", parent=self.controls_panel) 177 | with dpg.group(horizontal=True, parent=self.controls_panel): 178 | increase = dpg.add_button(label="Increase", 179 | callback=self.sorting_callbacks["increase"]) 180 | decrease = dpg.add_button(label="Decrease", 181 | callback=self.sorting_callbacks["decrease"]) 182 | 183 | self.transient_controls.append(increase) 184 | self.transient_controls.append(decrease) 185 | 186 | with dpg.group(horizontal=True, parent=self.controls_panel): 187 | original = dpg.add_button(label="Original Data", 188 | callback=self.sorting_callbacks["original"]) 189 | randomize = dpg.add_button(label="Randomize Data", 190 | callback=self.sorting_callbacks["randomize"]) 191 | 192 | self.transient_controls.append(original) 193 | self.transient_controls.append(randomize) 194 | 195 | def speedcontrols(): 196 | dpg.add_text("Speed:", parent=self.controls_panel) 197 | dpg.add_slider_int(label="", parent=self.controls_panel, width=300, default_value=self.step_sleep, clamped=True, 198 | min_value=0, max_value=50, tag=self.sleepslider) 199 | 200 | algobox() 201 | dpg.add_spacer(parent=self.controls_panel, width=5) 202 | 203 | runandnext() 204 | dpg.add_spacer(parent=self.controls_panel, width=5) 205 | 206 | speedcontrols() 207 | dpg.add_spacer(parent=self.controls_panel, width=5) 208 | 209 | datacontrols() 210 | dpg.add_spacer(parent=self.controls_panel, width=5) 211 | 212 | def __link_pathing_controls(self): 213 | self.transient_controls = [] 214 | 215 | def algobox(): 216 | dpg.add_text("Algorithm:", parent=self.controls_panel) 217 | combobox = dpg.add_combo(label="", parent=self.controls_panel, 218 | default_value=self.pathing.pathing_host.alg_name, 219 | items=list( 220 | self.all_algorithms.keys()), 221 | callback=self.change_algorithm, 222 | width=300, tag=self.combobox) 223 | 224 | self.transient_controls.append(combobox) 225 | 226 | def runandnext(): 227 | with dpg.group(horizontal=True, parent=self.controls_panel): 228 | runcheck = dpg.add_checkbox( 229 | label="Run", callback=self.pathfinding_callbacks["run_sim"]) 230 | nextstep = dpg.add_button( 231 | label="Next Step", callback=self.pathfinding_callbacks["next_step"]) 232 | 233 | self.transient_controls.append(nextstep) 234 | 235 | def speedcontrols(): 236 | dpg.add_text("Speed:", parent=self.controls_panel) 237 | dpg.add_slider_int(label="", parent=self.controls_panel, width=300, 238 | default_value=self.step_sleep, clamped=True, min_value=0, max_value=100, tag=self.sleepslider) 239 | 240 | def sizecontrols(): 241 | dpg.add_text("Maze:", parent=self.controls_panel) 242 | with dpg.group(horizontal=True, parent=self.controls_panel): 243 | grow = dpg.add_button( 244 | label="Grow", callback=self.pathfinding_callbacks["increase"]) 245 | shrink = dpg.add_button( 246 | label="Shrink", callback=self.pathfinding_callbacks["decrease"]) 247 | 248 | self.transient_controls.append(grow) 249 | self.transient_controls.append(shrink) 250 | 251 | def mazecontrols(): 252 | with dpg.group(horizontal=True, parent=self.controls_panel): 253 | randomize = dpg.add_button( 254 | label="Random Maze", callback=self.pathfinding_callbacks["generate_maze"]) 255 | retry = dpg.add_button( 256 | label="Retry Maze", callback=self.pathfinding_callbacks["retry"]) 257 | reset = dpg.add_button( 258 | label="Reset", parent=self.controls_panel, callback=self.pathfinding_callbacks["reset"]) 259 | 260 | self.transient_controls.append(randomize) 261 | self.transient_controls.append(retry) 262 | self.transient_controls.append(reset) 263 | 264 | algobox() 265 | dpg.add_spacer(parent=self.controls_panel, width=5) 266 | 267 | runandnext() 268 | dpg.add_spacer(parent=self.controls_panel, width=5) 269 | 270 | speedcontrols() 271 | dpg.add_spacer(parent=self.controls_panel, width=5) 272 | 273 | sizecontrols() 274 | mazecontrols() 275 | dpg.add_spacer(parent=self.controls_panel, width=5) 276 | 277 | def run_sim(self, sender): 278 | for control in self.transient_controls: 279 | dpg.configure_item(control, enabled=False) 280 | 281 | while dpg.get_value(sender): 282 | i = self.curr_window.next_step() 283 | self.update_info_no_wrapper() 284 | 285 | sleep(dpg.get_value(self.sleepslider)/100) 286 | 287 | if (not i): 288 | dpg.set_value(sender, False) 289 | self.update_info_no_wrapper() 290 | break 291 | for control in self.transient_controls: 292 | dpg.configure_item(control, enabled=True) 293 | 294 | # Info 295 | 296 | def update_info(self, func): 297 | def wrapper(): 298 | func() 299 | dpg.set_value(self.infotext, self.curr_window.message) 300 | if self.curr_window.is_initial(): 301 | self.update_info_alg_definition() 302 | 303 | return wrapper 304 | 305 | def update_info_no_wrapper(self): 306 | dpg.set_value(self.infotext, self.curr_window.message) 307 | if self.curr_window.is_initial(): 308 | self.update_info_alg_definition() 309 | 310 | def update_info_alg_definition(self): 311 | definition = defs.DEFINITIONS[self.curr_window.current_alg()] 312 | 313 | algtype = definition[0] 314 | cases = definition[1] 315 | desc = definition[2] 316 | 317 | defstring = f"{self.curr_window.current_alg()}\n\n" 318 | defstring += "Type: " + algtype + '\n\n' 319 | 320 | defstring += "Time Complexities:" if len( 321 | cases) > 1 else "Time Complexity:" 322 | for case in cases: 323 | defstring += '\n' + case 324 | 325 | defstring += "\n\n" 326 | 327 | desc = desc.replace("\n", "").replace("\t", "").strip() 328 | desc = ' '.join(desc.split()) 329 | defstring += desc 330 | 331 | dpg.set_value(self.infotext, defstring) 332 | -------------------------------------------------------------------------------- /src/algohost.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | 3 | 4 | class AlgorithmHost: 5 | def __init__(self, algorithm="Quick Sort"): 6 | self.data_set_size = 30 7 | 8 | self.step_counter = 0 9 | 10 | self.data_x = [i for i in range(self.data_set_size)] 11 | self.data_original = [] 12 | self.data_y = [] 13 | 14 | self.alg_list = { 15 | "Quick Sort": (lambda: self.quick_sort(0, self.data_set_size-1, self.data_y)), 16 | "Merge Sort": (lambda: self.merge(self.data_y)), 17 | "Bubble Sort": (lambda: self.regbubble(self.data_y)), 18 | "Optimized Bubble Sort": (lambda: self.bubble(self.data_y)), 19 | "Insertion Sort": (lambda: self.insertion(self.data_y)), 20 | "Selection Sort": (lambda: self.selection(self.data_y)), 21 | "Cocktail Sort": (lambda: self.cocktail(self.data_y)), 22 | } 23 | self.alg_name = algorithm 24 | self.current_algorithm = self.alg_list[self.alg_name]() 25 | 26 | self.set_random_data() 27 | 28 | def set_algorithm(self, name): 29 | self.alg_name = name 30 | self.step_counter = 0 31 | self.current_algorithm = self.alg_list[name]() 32 | 33 | def set_random_data(self): 34 | self.step_counter = 0 35 | self.data_y = [randint(1, self.data_set_size) 36 | for i in range(self.data_set_size)] 37 | self.data_original = self.data_y.copy() 38 | self.current_algorithm = self.alg_list[self.alg_name]() 39 | 40 | def original_data(self): 41 | self.step_counter = 0 42 | self.data_y = self.data_original.copy() 43 | self.current_algorithm = self.alg_list[self.alg_name]() 44 | 45 | def change_data_len(self, changeamnt): 46 | self.step_counter = 0 47 | 48 | if (changeamnt < 0 and self.data_set_size - abs(changeamnt) < 5) or (changeamnt > 0 and self.data_set_size + abs(changeamnt) > 100): 49 | return 50 | 51 | self.data_set_size += changeamnt 52 | 53 | self.data_x = [i for i in range(self.data_set_size)] 54 | self.data_y = [randint(1, self.data_set_size) 55 | for i in range(self.data_set_size)] 56 | 57 | self.data_original = self.data_y.copy() 58 | self.current_algorithm = self.alg_list[self.alg_name]() 59 | 60 | def next_step(self): 61 | try: 62 | self.step_counter += 1 63 | return next(self.current_algorithm) 64 | except StopIteration: 65 | self.step_counter -= 1 66 | return False 67 | 68 | # Algorithms 69 | 70 | def regbubble(self, data): 71 | n = len(data) 72 | changemade = False 73 | while not changemade: 74 | changemade = True 75 | for i in range(n-1): 76 | j = i + 1 77 | if data[j] < data[i]: 78 | yield { 79 | "red": [i, j], 80 | "message": "Unsorted pair found" 81 | } 82 | data[i], data[j] = data[j], data[i] 83 | yield { 84 | "green": [i, j], 85 | "message": "Unsorted pair swapped" 86 | } 87 | changemade = False 88 | else: 89 | yield { 90 | "green": [i, j], 91 | "message": "Pair already sorted" 92 | } 93 | 94 | def bubble(self, data): 95 | n = len(data) 96 | for i in range(n-1): 97 | for j in range(0, n-i-1): 98 | if data[j] > data[j + 1]: 99 | yield { 100 | "red": [j, j+1], 101 | "message": "Unsorted pair found" 102 | } 103 | data[j], data[j + 1] = data[j + 1], data[j] 104 | yield { 105 | "green": [j, j+1], 106 | "message": "Unsorted pair swapped" 107 | } 108 | else: 109 | yield { 110 | "green": [j, j+1], 111 | "message": "Pair already sorted" 112 | } 113 | 114 | def selection(self, data): 115 | data_length = len(data) 116 | 117 | for i in range(data_length): 118 | # assume min is firt el 119 | j_min = i 120 | yield { 121 | "yellow": [j_min], 122 | "message": "New minimum found" 123 | } 124 | 125 | for j in range(i+1, data_length): 126 | if data[j] < data[j_min]: 127 | # new min 128 | j_min = j 129 | yield { 130 | "yellow": [j_min], 131 | "message": "New minimum found" 132 | } 133 | else: 134 | yield { 135 | "yellow": [j_min], 136 | "green": [j], 137 | "message": "Searching for lower minimum" 138 | } 139 | 140 | if j_min != i: 141 | yield { 142 | "red": [j_min, i], 143 | "message": "Swapping the left bar with the lowest minimum" 144 | } 145 | data[i], data[j_min] = data[j_min], data[i] 146 | yield { 147 | "green": [j_min, i], 148 | "message": "Swap complete" 149 | } 150 | 151 | # /questions/62993954/how-do-i-make-this-merge-sort-function-a-generator-python 152 | def merge(self, data): 153 | # arr is a unique list that all levels in the recursion tree can access: 154 | 155 | def merge_rec(start, end): # separate function that can take start/end indices 156 | if end - start > 1: 157 | middle = (start + end) // 2 158 | 159 | # don't provide slice, but index range 160 | yield from merge_rec(start, middle) 161 | yield from merge_rec(middle, end) 162 | left = data[start:middle] 163 | right = data[middle:end] 164 | 165 | a = 0 166 | b = 0 167 | c = start 168 | 169 | while a < len(left) and b < len(right): 170 | if left[a] < right[b]: 171 | yield { 172 | "red": [c], 173 | "yellow": [start, end-1], 174 | "message": "Sorting between yellow bars" 175 | } 176 | data[c] = left[a] 177 | yield { 178 | "green": [c], 179 | "yellow": [start, end-1], 180 | "message": "Sorting between yellow bars" 181 | } 182 | a += 1 183 | else: 184 | yield { 185 | "red": [c], 186 | "yellow": [start, end-1], 187 | "message": "Sorting between yellow bars" 188 | } 189 | data[c] = right[b] 190 | yield { 191 | "green": [c], 192 | "yellow": [start, end-1], 193 | "message": "Sorting between yellow bars" 194 | } 195 | b += 1 196 | c += 1 197 | 198 | while a < len(left): 199 | yield { 200 | "red": [c], 201 | "yellow": [start, end-1], 202 | "message": "Adding leftover from left temp array" 203 | } 204 | data[c] = left[a] 205 | yield { 206 | "green": [c], 207 | "yellow": [start, end-1], 208 | "message": "Add complete" 209 | } 210 | a += 1 211 | c += 1 212 | 213 | while b < len(right): 214 | yield { 215 | "red": [c], 216 | "yellow": [start, end-1], 217 | "message": "Adding leftover from right temp array" 218 | } 219 | data[c] = right[b] 220 | yield { 221 | "green": [c], 222 | "yellow": [start, end-1], 223 | "message": "Add complete" 224 | } 225 | b += 1 226 | c += 1 227 | 228 | # call inner function with start/end arguments 229 | yield from merge_rec(0, len(data)) 230 | 231 | # The main function that implements QuickSort 232 | def quick_sort(self, start, end, array): 233 | if (start < end): 234 | ### 235 | intstart = start 236 | intend = end 237 | # p is partitioning index, array[p] 238 | # is at right place 239 | # Initializing pivot's index to start 240 | pivot_index = intstart 241 | pivot = array[pivot_index] 242 | 243 | yield { 244 | "yellow": [pivot_index], 245 | "message": "New pivot point selected" 246 | } 247 | 248 | # This loop runs till start pointer crosses 249 | # end pointer, and when it does we swap the 250 | # pivot with element on end pointer 251 | 252 | while intstart < intend: 253 | lnth = len(array) 254 | 255 | # Increment the start pointer till it finds an 256 | # element greater than pivot 257 | while intstart < lnth and array[intstart] <= pivot: 258 | yield { 259 | "red": [intstart], 260 | "yellow": [pivot_index], 261 | "message": "Searching for an element greater than the pivot" 262 | } 263 | intstart += 1 264 | 265 | if intstart < lnth: 266 | yield { 267 | "green": [intstart], 268 | "yellow": [pivot_index], 269 | "message": "Found an element greater than the pivot" 270 | } 271 | 272 | # Decrement the end pointer till it finds an 273 | # element less than pivot 274 | while array[intend] > pivot: 275 | yield { 276 | "red": [intend], 277 | "yellow": [pivot_index], 278 | "message": "Searching for an element smaller than the pivot" 279 | } 280 | intend -= 1 281 | 282 | yield { 283 | "green": [intend], 284 | "yellow": [pivot_index], 285 | "message": "Found an element smaller than the pivot" 286 | } 287 | # If start and end have not crossed each other, 288 | # swap the numbers on start and end 289 | if(intstart < intend): 290 | yield { 291 | "red": [intstart, intend], 292 | "yellow": [pivot_index], 293 | "message": "Swapping the smaller and greater elements" 294 | } 295 | array[intstart], array[intend] = array[intend], array[intstart] 296 | yield { 297 | "green": [intstart, intend], 298 | "yellow": [pivot_index], 299 | "message": "Swap complete" 300 | } 301 | 302 | # Swap pivot element with element on end pointer. 303 | # This puts pivot on its correct sorted place. 304 | yield { 305 | "red": [intend, pivot_index], 306 | "message": "Placing pivot where the last smaller element was" 307 | } 308 | array[intend], array[pivot_index] = array[pivot_index], array[intend] 309 | yield { 310 | "green": [intend, pivot_index], 311 | "message": "Pivot placed" 312 | } 313 | # Returning end pointer to divide the array into 2 314 | p = intend 315 | 316 | # Sort elements before partition 317 | # and after partition 318 | 319 | yield from self.quick_sort(start, p - 1, array) 320 | yield from self.quick_sort(p + 1, end, array) 321 | 322 | def cocktail(self, data): 323 | swapped = True 324 | while swapped: 325 | yield { 326 | "message": "Moving through the data from beginning to end" 327 | } 328 | swapped = False 329 | for i in range(0, len(data)-2): 330 | if data[i] > data[i+1]: 331 | yield { 332 | "red": [i, i+1], 333 | "message": "Left bar is greater than right" 334 | } 335 | data[i], data[i+1] = data[i+1], data[i] 336 | swapped = True 337 | yield { 338 | "green": [i, i+1], 339 | "message": "Swapped bars" 340 | } 341 | else: 342 | yield { 343 | "yellow": [i, i+1], 344 | "message": "Bars already ordered" 345 | } 346 | 347 | if not swapped: 348 | break 349 | 350 | yield { 351 | "message": "Moving from end to beginning now" 352 | } 353 | 354 | swapped = False 355 | for i in range(len(data)-2, 0, -1): 356 | if data[i] > data[i+1]: 357 | yield { 358 | "red": [i, i+1], 359 | "message": "Left bar is greater than right" 360 | } 361 | data[i], data[i+1] = data[i+1], data[i] 362 | swapped = True 363 | yield { 364 | "green": [i, i+1], 365 | "message": "Swapped bars" 366 | } 367 | else: 368 | yield { 369 | "yellow": [i, i+1], 370 | "message": "Bars already ordered" 371 | } 372 | 373 | yield 374 | 375 | def insertion(self, data): 376 | for i in range(1, len(data)): 377 | key = data[i] 378 | j = i - 1 379 | yield { 380 | "yellow": [i], 381 | "message": "Checking bar before this one for a greater value" 382 | } 383 | selected = False 384 | while j >= 0 and data[j] > key: 385 | if not selected: 386 | yield { 387 | "red": [i], 388 | "message": "Bar before this is greater, saving the smaller bar" 389 | } 390 | 391 | yield { 392 | "red": [j, j+1], 393 | "message": "Moving the left bar forward" 394 | } 395 | data[j+1] = data[j] 396 | yield { 397 | "green": [j, j+1], 398 | "message": "Move complete" 399 | } 400 | j -= 1 401 | selected = True 402 | yield { 403 | "yellow": [j+1], 404 | "message": "Replacing this bar with the smaller one from earlier" 405 | } 406 | data[j + 1] = key 407 | yield { 408 | "green": [j+1], 409 | "message": "Replacement complete" 410 | } 411 | -------------------------------------------------------------------------------- /src/algowindow.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | from algohost import AlgorithmHost 3 | 4 | 5 | class AlgorithmWindow: 6 | def __init__(self, window_tag=None): 7 | self.algorithms_host = AlgorithmHost() 8 | 9 | self.message = self.algorithms_host.alg_name 10 | 11 | self.highlight_list = [] 12 | 13 | self.window_tag = window_tag 14 | 15 | self.plot = None 16 | self.plot_x_axis = None 17 | self.plot_y_axis = None 18 | self.default_graph = None 19 | 20 | self.plots = { 21 | "red": None, 22 | "green": None, 23 | "yellow": None 24 | } 25 | 26 | def load_themes(self): 27 | with dpg.theme() as red: 28 | with dpg.theme_component(dpg.mvAll): 29 | dpg.add_theme_color(dpg.mvPlotCol_Fill, 30 | (196, 78, 82), category=dpg.mvThemeCat_Plots) 31 | with dpg.theme() as green: 32 | with dpg.theme_component(dpg.mvAll): 33 | dpg.add_theme_color(dpg.mvPlotCol_Fill, 34 | (85, 168, 104), category=dpg.mvThemeCat_Plots) 35 | with dpg.theme() as yellow: 36 | with dpg.theme_component(dpg.mvAll): 37 | dpg.add_theme_color(dpg.mvPlotCol_Fill, 38 | (204, 185, 116), category=dpg.mvThemeCat_Plots) 39 | 40 | dpg.bind_item_theme(self.plots["red"], red) 41 | dpg.bind_item_theme(self.plots["green"], green) 42 | dpg.bind_item_theme(self.plots["yellow"], yellow) 43 | 44 | def set_limits(self): 45 | # Because we initialize our plots starting at 0 now, we just need to see everything from -1 (to include the 0 position) to 46 | # the end of the dataset's length. The Y limit is gonna be a bit more than the highest y value, to provide breathing room 47 | XLow = -1 48 | XHigh = self.algorithms_host.data_set_size 49 | # We can just use the data_set_size for the y limit, because that's the max of the randint function when generating random data 50 | YLimit = self.algorithms_host.data_set_size+5 51 | 52 | dpg.set_axis_limits(self.plot_x_axis, XLow, XHigh) 53 | dpg.set_axis_limits(self.plot_y_axis, 0, YLimit) 54 | 55 | def initialize_plot(self): 56 | self.plot = dpg.add_plot( 57 | label=f"{self.algorithms_host.alg_name} (n = {self.algorithms_host.data_set_size})", width=-1, height=-1, parent=self.window_tag, no_menus=True, 58 | no_box_select=True, 59 | no_mouse_pos=True, 60 | crosshairs=False) 61 | 62 | self.plot_x_axis = dpg.add_plot_axis(dpg.mvXAxis, label="", no_gridlines=True, no_tick_marks=True, 63 | no_tick_labels=True, lock_min=True, lock_max=True, parent=self.plot) 64 | self.plot_y_axis = dpg.add_plot_axis(dpg.mvYAxis, label="", no_gridlines=True, no_tick_marks=True, 65 | no_tick_labels=True, lock_min=True, lock_max=True, parent=self.plot) 66 | 67 | self.default_graph = dpg.add_bar_series( 68 | self.algorithms_host.data_x, self.algorithms_host.data_y, label="", weight=0.5, parent=self.plot_y_axis) 69 | 70 | self.plots["red"] = dpg.add_bar_series( 71 | self.algorithms_host.data_x, self.algorithms_host.data_y, label="", weight=0.5, parent=self.plot_y_axis) 72 | self.plots["green"] = dpg.add_bar_series( 73 | self.algorithms_host.data_x, self.algorithms_host.data_y, label="", weight=0.5, parent=self.plot_y_axis) 74 | self.plots["yellow"] = dpg.add_bar_series( 75 | self.algorithms_host.data_x, self.algorithms_host.data_y, label="", weight=0.5, parent=self.plot_y_axis) 76 | 77 | self.load_themes() 78 | 79 | self.set_limits() 80 | 81 | def reset_plot(self): 82 | dpg.set_item_label(self.plot, f"{self.algorithms_host.alg_name} (n = {self.algorithms_host.data_set_size})") 83 | self.message = f"{self.algorithms_host.alg_name}" 84 | 85 | dpg.set_value(self.default_graph, [ 86 | self.algorithms_host.data_x, self.algorithms_host.data_y]) 87 | 88 | self.clear_highlights() 89 | self.set_limits() 90 | 91 | def update(self, new_data): 92 | dpg.set_value(self.default_graph, [ 93 | self.algorithms_host.data_x, self.algorithms_host.data_y]) 94 | 95 | if (not new_data): 96 | self.clear_highlights() 97 | dpg.set_value(self.plots["green"], [ 98 | self.algorithms_host.data_x, self.algorithms_host.data_y]) 99 | 100 | self.message = f"{self.algorithms_host.alg_name} (n = {self.algorithms_host.data_set_size}): Complete in {self.algorithms_host.step_counter} steps." 101 | 102 | else: 103 | self.clear_highlights() 104 | for highlight in new_data: 105 | if (highlight != "message"): 106 | x_highlight = new_data[highlight] 107 | 108 | dpg.set_value(self.plots[highlight], [x_highlight, [ 109 | self.algorithms_host.data_y[x_value] for x_value in x_highlight]]) 110 | 111 | self.message = f"{self.algorithms_host.alg_name} (n = {self.algorithms_host.data_set_size}) Step {self.algorithms_host.step_counter}: {new_data['message']}" 112 | 113 | def clear_highlights(self): 114 | dpg.set_value(self.plots["red"], [[0], [0]]) 115 | dpg.set_value(self.plots["green"], [[0], [0]]) 116 | dpg.set_value(self.plots["yellow"], [[0], [0]]) 117 | 118 | def new_dataset(self): 119 | self.algorithms_host.set_random_data() 120 | self.reset_plot() 121 | 122 | def increase_dataset(self): 123 | self.algorithms_host.change_data_len(5) 124 | self.reset_plot() 125 | 126 | def decrease_dataset(self): 127 | self.algorithms_host.change_data_len(-5) 128 | self.reset_plot() 129 | 130 | def next_step(self): 131 | value = self.algorithms_host.next_step() 132 | self.update(value) 133 | return value 134 | 135 | def change_algorithm(self, newalg): 136 | self.clear_highlights() 137 | self.algorithms_host.set_algorithm(newalg) 138 | self.original_data() 139 | 140 | def original_data(self): 141 | self.clear_highlights() 142 | self.algorithms_host.original_data() 143 | self.reset_plot() 144 | 145 | def unmount(self): 146 | dpg.delete_item(self.plot) 147 | self.algorithms_host = None 148 | 149 | def current_alg(self): 150 | return self.algorithms_host.alg_name 151 | 152 | def is_initial(self): 153 | if self.algorithms_host.step_counter == 0: 154 | return True 155 | return False 156 | -------------------------------------------------------------------------------- /src/definitions.py: -------------------------------------------------------------------------------- 1 | # [type, time complexity, info] 2 | DEFINITIONS = { 3 | "Quick Sort": [ 4 | "Sorting, Divide And Conquer", 5 | ["Best: O(n log n)", 6 | "Average: O(n log n)", 7 | "Worst: O(n^2)"], 8 | """ 9 | In simplest terms, Quick Sort functions by selecting a pivot point 10 | and then organizing subsets of the dataset based on whether or not 11 | they're larger than the pivot value. 12 | """ 13 | ], 14 | 15 | "Merge Sort": [ 16 | "Sorting, Divide And Conquer", 17 | ["All cases: O(n log n)"], 18 | """ 19 | Merge Sort functions by dividing a dataset into smaller pieces, 20 | sorting them in small segments, 21 | then combining the pieces in chunks of increasing size 22 | Eventually, the sorted chunks are combined to form a final, sorted data set. 23 | """ 24 | ], 25 | 26 | "Bubble Sort": [ 27 | "Sorting, In-Place", 28 | ["Best: O(n)", 29 | "Worst and Average: O(n^2)"], 30 | """ 31 | Bubble Sort is a simple-to-grasp sorting algorithm that functions based on 32 | the idea of "bubbling" larger values to the top of a dataset 33 | Though not very efficient, one of its main charms 34 | is its simplicity in concept and implementation. 35 | """ 36 | ], 37 | 38 | "Optimized Bubble Sort": [ 39 | "Sorting, In-Place", 40 | ["Best: O(n)", 41 | "Worst and Average: O(n^2)"], 42 | """ 43 | Optimized Bubble Sort improves upon the speed of regular Bubble Sort by avoiding 44 | the last n-1 elements each loop, since those had already been placed 45 | at their proper positions by the nature of Bubble Sort. 46 | """ 47 | ], 48 | 49 | "Insertion Sort": [ 50 | "Sorting, In-Place", 51 | ["Best: O(n)", 52 | "Worst and Average: O(n^2)"], 53 | """ 54 | Insertion Sort functions by simply going through a dataset in order, 55 | checking for a smaller value that's ahead of a larger one, then inserting 56 | each element before the value ahead of the smaller one, 57 | until it reaches an element smaller than the current value 58 | """ 59 | ], 60 | 61 | "Selection Sort": [ 62 | "Sorting, In-Place", 63 | ["All cases: O(n^2)"], 64 | """ 65 | Selection sort functions by going through a dataset in order, 66 | selecting a value, and searching for values smaller than it. When it finds 67 | one, it swaps the two values 68 | """ 69 | ], 70 | 71 | "Cocktail Sort": [ 72 | "Sorting, In-Place", 73 | ["Best: O(n)", 74 | "Worst and Average: O(n^2)"], 75 | """ 76 | Cocktail sort is a variation of Bubble Sort that goes through the dataset in both directions. 77 | """ 78 | ], 79 | 80 | # pathing 81 | "Breadth-First Search": [ 82 | "Graph Search", 83 | ["Best and worst case: O(|V| + |E|)"], 84 | """ 85 | Breadth-First Search operates by examining all nodes at each depth 86 | before moving on to the next level. For each node, its (unexamined) neighbors are added 87 | onto a queue to be examined later. 88 | """ 89 | ], 90 | 91 | "Depth-First Search": [ 92 | "Graph Search", 93 | ["Best and worst case: O(|V| + |E|)"], 94 | """ 95 | Depth-First Search operates by examining all nodes along a branch before moving on 96 | to the next branch. It utilizes a stack to keep track of which nodes are to be scanned next. 97 | """ 98 | ], 99 | 100 | "Dijkstra's Algorithm": [ 101 | "Graph Search", 102 | ["Best and worst case: O((|V| + |E|) log |V|)"], 103 | """ 104 | Dijkstra's functions using a graph with weighted edges, finding 105 | the lowest-cost path to the end by travelling along nodes with the lowest weights. 106 | NOTE: There is currently no way to manually set weights in ELPath, so nodes 107 | are weighted randomly. 108 | """ 109 | ], 110 | 111 | "A* Algorithm": [ 112 | "Graph Search", 113 | ["Depends on heuristic function."], 114 | """ 115 | One of The most acclaimed graph search algorithms, A* utilizes the idea of 116 | low-cost pathfinding (Which node is closest to the beginning?) 117 | as well as a heuristic function (Which node is closest to the end?). 118 | ELPath uses Manhattan distance for the heuristic, 119 | """ 120 | ], 121 | } 122 | -------------------------------------------------------------------------------- /src/main.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | from ELPath import ELPath 3 | 4 | dpg.create_context() 5 | 6 | ELPath = ELPath() 7 | 8 | 9 | ELPath.viewport_config() 10 | 11 | 12 | dpg.start_dearpygui() 13 | -------------------------------------------------------------------------------- /src/pathfindhost.py: -------------------------------------------------------------------------------- 1 | from random import choice 2 | import queue 3 | from collections import deque 4 | 5 | # 7/20/21 - pathingalgs moved into host, for simplicity's sake, 6 | # and because I don't think generators are the way to go in this case. 7 | 8 | """ 9 | 7/22/21 - Turns out I still want to use generators for this, 10 | it's such an easy way to "pause" execution of an algorithm. 11 | But, it will be different this time: Updating the window will no 12 | longer be the duty of the host's Window. Rather, the host will update 13 | the window, which kind of turns the initial philosophy on its head, 14 | but it's convenient, makes some more sense, and reduces the whole 15 | "how am i gonna make this algorithm into a generator" issue. 16 | That modified version of merge sort from the sorting portion? 17 | That's what drove this decision. 18 | """ 19 | 20 | 21 | class PathfindingHost: 22 | def __init__(self, sidecellcount, draw_node_func, draw_weights_func, algorithm="Breadth-First Search"): 23 | self.sidecellcount = sidecellcount 24 | 25 | self.grid = [] 26 | for y in range(sidecellcount): 27 | self.grid.append([]) 28 | for x in range(sidecellcount): 29 | node = Node((x, y)) 30 | self.grid[y].append(node) 31 | 32 | self.start_point = (0, 0) 33 | self.end_point = (sidecellcount-1, sidecellcount-1) 34 | 35 | self.start = self.node_from_pos(self.start_point) 36 | self.end = self.node_from_pos(self.end_point) 37 | 38 | self.start.set_state_start() 39 | self.end.set_state_end() 40 | 41 | self.draw_node = draw_node_func 42 | self.draw_weights = draw_weights_func 43 | 44 | # sorting algorithms 45 | self.alg_list = { 46 | "Breadth-First Search": (lambda: self.breadthfirst(self.draw_node)), 47 | "Depth-First Search": (lambda: self.depthfirst(self.draw_node)), 48 | "Dijkstra's Algorithm": (lambda: self.dijkstras(self.draw_node)), 49 | "A* Algorithm": (lambda: self.astar(self.draw_node)), 50 | } 51 | self.alg_name = algorithm 52 | self.current_algorithm = self.alg_list[self.alg_name] 53 | 54 | self.initialized = False 55 | 56 | # Info 57 | self.step_counter = 0 58 | self.path_length = 0 59 | self.nodes_found = 0 60 | self.barr_count = 0 61 | 62 | def set_algorithm(self, name): 63 | self.alg_name = name 64 | self.step_counter = 0 65 | self.current_algorithm = self.alg_list[name] 66 | 67 | def node_from_pos(self, pos): 68 | y = pos[1] 69 | x = pos[0] 70 | 71 | if 0 <= x < self.sidecellcount and 0 <= y < self.sidecellcount: 72 | return self.grid[y][x] 73 | else: 74 | return None 75 | 76 | def add_start(self, node): 77 | try: 78 | node.set_state_start() 79 | self.start_point = (node.x, node.y) 80 | self.start = node 81 | self.draw_node(self.start) 82 | except: 83 | self.start_point = (0, 0) 84 | 85 | self.start = self.node_from_pos(self.start_point) 86 | 87 | self.add_start(self.node_from_pos(self.start_point)) 88 | 89 | def remove_start(self): 90 | start = self.node_from_pos(self.start_point) 91 | start.set_state_empty() 92 | self.draw_node(start) 93 | self.start_point = None 94 | self.start = None 95 | 96 | def add_end(self, node): 97 | try: 98 | node.set_state_end() 99 | self.end_point = (node.x, node.y) 100 | self.end = node 101 | self.draw_node(self.end) 102 | except: 103 | self.end_point = (self.sidecellcount-1, self.sidecellcount-1) 104 | 105 | self.end = self.node_from_pos(self.end_point) 106 | 107 | self.add_end(self.node_from_pos(self.end_point)) 108 | 109 | def remove_end(self): 110 | end = self.node_from_pos(self.end_point) 111 | end.set_state_empty() 112 | self.draw_node(end) 113 | self.end_point = None 114 | self.end = None 115 | 116 | def rand_maze(self): 117 | # Generates a maze using Prim's alg 118 | def emptys_at_dist_2(node): 119 | right = self.node_from_pos((node.x+2, node.y)) 120 | left = self.node_from_pos((node.x-2, node.y)) 121 | up = self.node_from_pos((node.x, node.y+2)) 122 | down = self.node_from_pos((node.x, node.y-2)) 123 | 124 | outlist = [] 125 | if right is not None and right.state == "EMPTY": 126 | outlist.append(right) 127 | if left is not None and left.state == "EMPTY": 128 | outlist.append(left) 129 | if up is not None and up.state == "EMPTY": 130 | outlist.append(up) 131 | if down is not None and down.state == "EMPTY": 132 | outlist.append(down) 133 | 134 | return outlist 135 | if self.start is not None: 136 | self.remove_start() 137 | if self.end is not None: 138 | self.remove_end() 139 | 140 | for row in self.grid: 141 | for node in row: 142 | node.set_state_barrier() 143 | self.draw_node(node) 144 | 145 | open_nodes = [] 146 | for i in range(1, len(self.grid)-1): 147 | for j in range(1, len(self.grid)-1): 148 | node = self.grid[i][j] 149 | if i % 2 != 0 and j % 2 != 0: 150 | node.set_state_empty() 151 | self.draw_node(node) 152 | open_nodes.append(node) 153 | 154 | visited = set() 155 | stack = deque() 156 | 157 | startcell = choice(open_nodes) 158 | visited.add(startcell) 159 | stack.append(startcell) 160 | 161 | while len(stack) > 0: 162 | current_cell = stack.pop() 163 | neighbors = emptys_at_dist_2(current_cell) 164 | 165 | prevstate = current_cell.state 166 | current_cell.set_state_closed() 167 | self.draw_node(current_cell) 168 | 169 | for neighbor in neighbors: 170 | if neighbor not in visited: 171 | stack.append(current_cell) 172 | 173 | unvis = choice(neighbors) 174 | while (unvis in visited): 175 | unvis = choice(neighbors) 176 | 177 | between = ((unvis.x + current_cell.x)//2, 178 | (unvis.y + current_cell.y)//2) 179 | between = self.node_from_pos(between) 180 | 181 | between.set_state_empty() 182 | self.draw_node(between) 183 | 184 | visited.add(unvis) 185 | stack.append(unvis) 186 | break 187 | 188 | current_cell.set_state(prevstate) 189 | self.draw_node(current_cell) 190 | 191 | startnode = choice(open_nodes) 192 | endnode = choice(open_nodes) 193 | while endnode == startnode: 194 | endnode = choice(open_nodes) 195 | 196 | try: 197 | self.add_start(startnode) 198 | self.add_end(endnode) 199 | except TypeError: # start/end not initialized 200 | self.start_point = (0, 0) 201 | self.end_point = (self.sidecellcount-1, self.sidecellcount-1) 202 | 203 | self.start = self.node_from_pos(self.start_point) 204 | self.end = self.node_from_pos(self.end_point) 205 | 206 | self.add_start(self.node_from_pos(self.start_point)) 207 | self.add_end(self.node_from_pos(self.end_point)) 208 | 209 | self.retry_maze() # ok to run this now that we configure instead of creating new nodes 210 | 211 | def retry_maze(self): 212 | self.step_counter = 0 213 | 214 | for row in self.grid: 215 | for node in row: 216 | node.neighbors = [] 217 | node.state = node.altstate = node.origstate 218 | self.draw_node(node) 219 | 220 | # start/end not placed 221 | try: 222 | self.add_start(self.node_from_pos(self.start_point)) 223 | self.add_end(self.node_from_pos(self.end_point)) 224 | except TypeError: # start/end not initialized 225 | self.start_point = (0, 0) 226 | self.end_point = (self.sidecellcount-1, self.sidecellcount-1) 227 | 228 | self.start = self.node_from_pos(self.start_point) 229 | self.end = self.node_from_pos(self.end_point) 230 | 231 | self.add_start(self.node_from_pos(self.start_point)) 232 | self.add_end(self.node_from_pos(self.end_point)) 233 | 234 | self.initialized = False 235 | self.current_algorithm = self.alg_list[self.alg_name] 236 | 237 | def rand_weights(self): 238 | # Generates a random list of weights for each node - to help along Dijkstras's 239 | weightlevels = [20, 200] 240 | weightlist = {node: choice(weightlevels) 241 | for row in self.grid for node in row if 242 | node.state != "START" and node.state != "END" and node.state != "BARR"} 243 | weightlist[self.start] = 0 244 | weightlist[self.end] = 0 245 | 246 | return weightlist 247 | 248 | def reset_info(self): 249 | self.step_counter = 0 250 | self.path_length = 0 251 | self.nodes_found = 0 252 | 253 | def update_node_neighbors(self, node): 254 | right = self.node_from_pos((node.x+1, node.y)) 255 | left = self.node_from_pos((node.x-1, node.y)) 256 | up = self.node_from_pos((node.x, node.y+1)) 257 | down = self.node_from_pos((node.x, node.y-1)) 258 | 259 | if right is not None and right.state != "BARR": 260 | node.neighbors.append(right) 261 | if left is not None and left.state != "BARR": 262 | node.neighbors.append(left) 263 | if up is not None and up.state != "BARR": 264 | node.neighbors.append(up) 265 | if down is not None and down.state != "BARR": 266 | node.neighbors.append(down) 267 | 268 | def initialize_neighbors(self): 269 | self.barr_count = 0 270 | for row in self.grid: 271 | for node in row: 272 | node.origstate = node.state 273 | if node.state == "BARR": 274 | self.barr_count += 1 275 | self.update_node_neighbors(node) 276 | self.initialized = True 277 | self.current_algorithm = self.current_algorithm() 278 | 279 | def manhattanheur(self, a, b): 280 | x1, y1 = a 281 | x2, y2 = b 282 | return abs(x1 - x2) + abs(y1 - y2) 283 | 284 | def tracepath(self, draw_func, came_from): 285 | current = self.end 286 | path = [] 287 | while current != self.start: 288 | path.append(current) 289 | current.set_state_path() 290 | draw_func(current) 291 | current = came_from[current] 292 | self.path_length += 1 293 | 294 | self.start.set_alt_state("START") 295 | draw_func(self.start) 296 | self.end.set_alt_state("END") 297 | draw_func(self.end) 298 | yield 299 | self.path_length -= 1 300 | 301 | 302 | def next_step(self): 303 | try: 304 | self.step_counter += 1 305 | return next(self.current_algorithm) 306 | except StopIteration: 307 | self.step_counter -= 1 308 | return False 309 | # Notice that we don't return any actual data here, unlike the sorting next_step 310 | # We do all the window updating work in the algorithm 311 | 312 | # Algorithms 313 | 314 | def breadthfirst(self, draw_func): 315 | self.reset_info() 316 | 317 | frontier = queue.Queue() 318 | frontier.put(self.start) 319 | came_from = dict() 320 | came_from[self.start] = None 321 | 322 | while not frontier.empty(): 323 | current = frontier.get() 324 | current.set_state_open() 325 | current.set_alt_state("SPCL") 326 | self.nodes_found += 1 327 | draw_func(current) 328 | 329 | self.start.set_alt_state("START") 330 | draw_func(self.start) 331 | self.end.set_alt_state("END") 332 | draw_func(self.end) 333 | 334 | if current == self.end: 335 | yield "Visited end node, retracing path." 336 | break 337 | 338 | for nxt in current.neighbors: 339 | if nxt not in came_from: 340 | nxt.set_state_open() 341 | frontier.put(nxt) 342 | came_from[nxt] = current 343 | 344 | draw_func(nxt) 345 | yield f"Examining node at ({current.x}, {current.y})" 346 | 347 | current.reset_alt_state() 348 | current.set_state_closed() 349 | draw_func(current) 350 | 351 | self.start.set_alt_state("START") 352 | draw_func(self.start) 353 | self.end.set_alt_state("END") 354 | draw_func(self.end) 355 | 356 | for stepback in self.tracepath(draw_func, came_from): 357 | yield "Retracing Path" 358 | 359 | def depthfirst(self, draw_func): 360 | self.reset_info() 361 | 362 | stack = [] 363 | discovered = set() 364 | stack.append(self.start) 365 | came_from = dict() 366 | 367 | while len(stack) > 0: 368 | current = stack.pop() 369 | current.set_alt_state("SPCL") 370 | draw_func(current) 371 | 372 | self.start.set_alt_state("START") 373 | draw_func(self.start) 374 | self.end.set_alt_state("END") 375 | draw_func(self.end) 376 | 377 | if current == self.end: 378 | self.nodes_found += 1 379 | yield "Visited end node, retracing path." 380 | break 381 | 382 | if current not in discovered: 383 | discovered.add(current) 384 | self.nodes_found = len(discovered) 385 | for neighbor in current.neighbors: 386 | if neighbor not in discovered: 387 | stack.append(neighbor) 388 | neighbor.set_state_open() 389 | draw_func(neighbor) 390 | if neighbor not in came_from: 391 | came_from[neighbor] = current 392 | 393 | self.start.set_alt_state("START") 394 | draw_func(self.start) 395 | self.end.set_alt_state("END") 396 | draw_func(self.end) 397 | 398 | yield f"Examining node at ({current.x}, {current.y})" 399 | 400 | current.set_state_closed() 401 | draw_func(current) 402 | 403 | for stepback in self.tracepath(draw_func, came_from): 404 | yield "Retracing Path" 405 | 406 | def dijkstras(self, draw_func): 407 | self.reset_info() 408 | 409 | weights = self.rand_weights() 410 | self.draw_weights(weights) 411 | 412 | vertset = [] 413 | distances = {} 414 | came_from = {} 415 | 416 | for row in self.grid: 417 | for node in row: 418 | distances[node] = float("inf") 419 | came_from[node] = None 420 | vertset.append(node) 421 | 422 | initial_node = self.start 423 | distances[initial_node] = 0 424 | 425 | while len(vertset) > 0: 426 | tempmin = None 427 | lowest = None 428 | for node in vertset: 429 | if tempmin is None or distances[node] < tempmin: 430 | tempmin = distances[node] 431 | lowest = node 432 | 433 | vertset.remove(lowest) 434 | lowest.set_alt_state("SPCL") 435 | self.nodes_found += 1 436 | draw_func(lowest) 437 | 438 | self.start.set_alt_state("START") 439 | draw_func(self.start) 440 | self.end.set_alt_state("END") 441 | draw_func(self.end) 442 | 443 | if lowest == self.end: 444 | yield "Visited end node, retracing path." 445 | break 446 | 447 | weights_list = [] 448 | 449 | for neighbor in lowest.neighbors: 450 | weights_list.append( 451 | (neighbor, "Visited" if neighbor.state == "CLOSE" else weights[neighbor])) 452 | if neighbor in vertset: 453 | alt = distances[lowest] + weights[neighbor] 454 | if alt < distances[neighbor]: 455 | distances[neighbor] = alt 456 | came_from[neighbor] = lowest 457 | neighbor.set_state_open() 458 | draw_func(neighbor) 459 | 460 | outmessage = f"Examining neighbors around node at ({lowest.x}, {lowest.y}).\n" 461 | 462 | scorelisting = "" 463 | for score in weights_list: 464 | scorelisting += f"({score[0].x}, {score[0].y}): {score[1]}\n" 465 | 466 | outmessage += f"Neighbor node weights:\n{scorelisting}" 467 | yield outmessage 468 | 469 | lowest.set_state_closed() 470 | draw_func(lowest) 471 | 472 | for stepback in self.tracepath(draw_func, came_from): 473 | yield "Retracing Path" 474 | 475 | def astar(self, draw_func): 476 | self.reset_info() 477 | 478 | count = 0 479 | open_set = queue.PriorityQueue() 480 | open_set.put((0, count, self.start)) 481 | came_from = {} 482 | g_score = {spot: float("inf") for row in self.grid for spot in row} 483 | g_score[self.start] = 0 484 | f_score = {spot: float("inf") for row in self.grid for spot in row} 485 | f_score[self.start] = self.manhattanheur( 486 | (self.start.x, self.start.y), (self.end.x, self.end.y)) 487 | 488 | open_set_hash = {self.start} 489 | 490 | while not open_set.empty(): 491 | current = open_set.get()[2] 492 | current.set_alt_state("SPCL") 493 | self.nodes_found += 1 494 | draw_func(current) 495 | open_set_hash.remove(current) 496 | 497 | self.start.set_alt_state("START") 498 | draw_func(self.start) 499 | self.end.set_alt_state("END") 500 | draw_func(self.end) 501 | 502 | if current == self.end: 503 | yield "Visited end node, retracing path." 504 | break 505 | 506 | f_score_list = [] 507 | 508 | for neighbor in current.neighbors: 509 | temp_g_score = g_score[current] + 1 510 | f_score_list.append( 511 | (neighbor, "Visited" if neighbor.state == "CLOSE" else f_score[neighbor])) 512 | if temp_g_score < g_score[neighbor]: 513 | came_from[neighbor] = current 514 | g_score[neighbor] = temp_g_score 515 | f_score[neighbor] = temp_g_score + self.manhattanheur( 516 | (neighbor.x, neighbor.y), (self.end.x, self.end.y)) 517 | if neighbor not in open_set_hash: 518 | count += 1 519 | open_set.put((f_score[neighbor], count, neighbor)) 520 | open_set_hash.add(neighbor) 521 | neighbor.set_state_open() 522 | 523 | draw_func(neighbor) 524 | 525 | outmessage = f"Examining neighbors around node at ({current.x}, {current.y}).\n" 526 | 527 | scorelisting = "" 528 | for score in f_score_list: 529 | scorelisting += f"({score[0].x}, {score[0].y}): {score[1]}\n" 530 | 531 | outmessage += f"Neighbor node F scores:\n{scorelisting}" 532 | yield outmessage 533 | 534 | if current != self.start: 535 | current.set_state_closed() 536 | draw_func(current) 537 | 538 | for stepback in self.tracepath(draw_func, came_from): 539 | yield "Retracing Path" 540 | 541 | 542 | class Node: 543 | def __init__(self, pos): 544 | self.x, self.y = pos 545 | self.state = "EMPTY" 546 | self.altstate = "EMPTY" 547 | self.origstate = "EMPTY" 548 | # altstate is a way to keep nodes from changing color when they're drawn 549 | # For example, it would be reasonable to have the start node always be green, 550 | # even when added to the closed set of an algorithm. 551 | # Thus, in such cases, it makes sense to maintain the actual state 552 | # but use altstate as a facade of sorts, to clearly visualize what's necessary. 553 | self.neighbors = [] 554 | 555 | def __str__(self): 556 | return f"{self.state.lower()} node at x {self.x} and y {self.y}" 557 | 558 | def set_state(self, state): 559 | self.altstate = self.state = state 560 | 561 | def set_state_start(self): 562 | self.origstate = self.altstate = self.state = "START" 563 | 564 | def set_state_end(self): 565 | self.origstate = self.altstate = self.state = "END" 566 | 567 | def set_state_open(self): 568 | self.altstate = self.state = "OPEN" 569 | 570 | def set_state_closed(self): 571 | self.altstate = self.state = "CLOSE" 572 | 573 | def set_state_path(self): 574 | self.altstate = self.state = "PATH" 575 | 576 | def set_state_empty(self): 577 | self.origstate = self.altstate = self.state = "EMPTY" 578 | 579 | def set_state_barrier(self): 580 | self.origstate = self.altstate = self.state = "BARR" 581 | 582 | def set_alt_state(self, state): 583 | self.altstate = state 584 | 585 | def reset_alt_state(self): 586 | self.altstate = self.state 587 | 588 | def __lt__(a, b): 589 | return (a.x, a.y) > (b.x, b.y) 590 | -------------------------------------------------------------------------------- /src/pathingwindow.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | from pathfindhost import PathfindingHost 3 | from math import trunc 4 | 5 | 6 | class PathingWindow: 7 | def __init__(self, window_tag=None): 8 | self.window_tag = window_tag 9 | self.window_size = dpg.get_item_height( 10 | window_tag) - 16 if window_tag else 800 11 | self.side_cell_count = 33 12 | self.cell_size = self.window_size/self.side_cell_count 13 | 14 | self.min_x = 0 15 | self.min_y = 0 16 | self.max_x = self.window_size 17 | self.max_y = self.window_size 18 | 19 | self.pathing_host = PathfindingHost( 20 | self.side_cell_count, lambda node: self.draw_node(node), self.draw_weights) 21 | self.colors = { 22 | "EMPTY": [255, 255, 255], 23 | "START": [85, 168, 104], 24 | "END": [196, 78, 82], 25 | "OPEN": [204, 185, 116], 26 | "CLOSE": [76, 114, 176], 27 | "PATH": [129, 114, 179], 28 | "BARR": [37, 37, 38], 29 | "SPCL": [221, 132, 82] 30 | } 31 | self.message = self.pathing_host.alg_name 32 | 33 | self.drawlist = None 34 | self.node_grid = {} 35 | 36 | self.clickregistry = None 37 | self.clickhandler = None 38 | 39 | def change_algorithm(self): 40 | self.retry() 41 | self.pathing_host.set_algorithm(dpg.get_value("algorithm_combobox")) 42 | self.message = self.pathing_host.alg_name 43 | 44 | def change_algorithm(self, alg): 45 | self.retry() 46 | self.pathing_host.set_algorithm(alg) 47 | self.message = self.pathing_host.alg_name 48 | 49 | def draw_node(self, node): 50 | nodeident = self.node_grid[(node.x, node.y)] 51 | 52 | if node.state == node.altstate: 53 | dpg.configure_item(nodeident, fill=self.colors[node.state]) 54 | else: 55 | dpg.configure_item(nodeident, fill=self.colors[node.altstate]) 56 | 57 | def draw_weights(self, weightlist): 58 | # higher weights cost more, so make those less green - increase the tint of the color 59 | for node in weightlist: 60 | weightednode = weightlist[node] + 10 61 | nodeident = self.node_grid[(node.x, node.y)] 62 | dpg.configure_item( 63 | nodeident, fill=[weightednode, 255, weightednode, 255]) 64 | 65 | def initialize_grid(self): 66 | self.drawlist = dpg.add_drawlist(parent=self.window_tag, 67 | width=self.window_size+1, height=self.window_size+1, show=True) 68 | 69 | span = 0 70 | for row in self.pathing_host.grid: 71 | for node in row: 72 | nodeident = dpg.draw_rectangle([self.min_x+node.x*self.cell_size, self.min_y+node.y*self.cell_size], [self.min_x+( 73 | node.x+1)*self.cell_size, (node.y+1)*self.cell_size], color=self.colors[node.state], fill=self.colors[node.state], parent=self.drawlist) 74 | self.node_grid[(node.x, node.y)] = nodeident 75 | span += self.cell_size 76 | 77 | for col in range(len(self.pathing_host.grid)): 78 | dpg.draw_rectangle([self.min_x+col*self.cell_size, 0], [self.min_x+( 79 | col+1)*self.cell_size, span], color=[0, 0, 0, 255], thickness=2, fill=[0, 0, 0, 0], parent=self.drawlist) 80 | 81 | dpg.draw_rectangle([0, self.min_y+col*self.cell_size], [span, self.min_y+( 82 | col+1)*self.cell_size], color=[0, 0, 0, 255], thickness=2, fill=[0, 0, 0, 0], parent=self.drawlist) 83 | 84 | dpg.draw_line([0, 0], [0, self.window_size], color=[ 85 | 0, 0, 0], thickness=4, parent=self.drawlist) 86 | dpg.draw_line([0, 0], [self.window_size, 0], color=[ 87 | 0, 0, 0], thickness=4, parent=self.drawlist) 88 | dpg.draw_line([self.window_size, self.window_size], [ 89 | 0, self.window_size], color=[0, 0, 0], thickness=4, parent=self.drawlist) 90 | dpg.draw_line([self.window_size, self.window_size], [ 91 | self.window_size, 0], color=[0, 0, 0], thickness=4, parent=self.drawlist) 92 | 93 | with dpg.handler_registry() as self.clickregistry: 94 | self.clickhandler = dpg.add_mouse_down_handler( 95 | callback=self.cell_clicked) 96 | 97 | 98 | 99 | def change_side_len(self, changeamnt): 100 | if (changeamnt < 0 and self.side_cell_count - abs(changeamnt) < 9) or (changeamnt > 0 and self.side_cell_count + abs(changeamnt) > 60): 101 | return False 102 | 103 | self.side_cell_count += changeamnt 104 | self.cell_size = self.window_size/self.side_cell_count 105 | return True 106 | 107 | def grow_maze(self): 108 | if not self.change_side_len(3): 109 | return 110 | 111 | self.reset() 112 | 113 | def shrink_maze(self): 114 | if not self.change_side_len(-3): 115 | return 116 | 117 | self.reset() 118 | 119 | def cell_clicked(self): 120 | try: 121 | if not self.is_initial(): 122 | return 123 | except AttributeError: 124 | return 125 | 126 | # Preventing click detection when outside of window 127 | genpos = dpg.get_mouse_pos() 128 | genpos[1] -= 15 # account for window padding 129 | 130 | if (genpos[1] > self.max_y or genpos[1] < self.min_y or genpos[0] < 0 or genpos[0] > self.max_x or dpg.get_active_window() != self.window_tag): 131 | return 132 | 133 | pos = dpg.get_drawing_mouse_pos() 134 | 135 | within_x = pos[0] >= self.min_x and pos[0] <= self.max_x 136 | within_y = pos[1] >= self.min_y and pos[1] <= self.max_y 137 | 138 | x_cell = trunc(pos[0]//self.cell_size) 139 | y_cell = trunc(pos[1]//self.cell_size) 140 | 141 | clearing = True if (dpg.is_mouse_button_down( 142 | 1)) else False # True if right clicking 143 | 144 | if (within_x and within_y): 145 | node = self.pathing_host.node_from_pos((x_cell, y_cell)) 146 | 147 | if node is None: 148 | return 149 | 150 | tempstate = node.state 151 | if clearing: 152 | if (tempstate == "BARR"): 153 | node.set_state_empty() 154 | if (tempstate == "START"): 155 | self.pathing_host.remove_start() 156 | if (tempstate == "END"): 157 | self.pathing_host.remove_end() 158 | 159 | else: 160 | if (tempstate == "EMPTY"): 161 | node.set_state_barrier() 162 | if (self.pathing_host.start_point is None): 163 | self.pathing_host.add_start(node) 164 | elif (self.pathing_host.end_point is None): 165 | self.pathing_host.add_end(node) 166 | 167 | self.draw_node(node) 168 | 169 | # interfacing with ELPath 170 | def next_step(self): 171 | if not self.pathing_host.start or not self.pathing_host.end: 172 | return False 173 | 174 | if not self.pathing_host.initialized: 175 | self.pathing_host.initialize_neighbors() 176 | 177 | result = self.pathing_host.next_step() 178 | 179 | opensquares = (self.side_cell_count * 180 | self.side_cell_count-self.pathing_host.barr_count) 181 | 182 | if result: 183 | self.message = f"{self.pathing_host.alg_name} ({self.side_cell_count}x{self.side_cell_count}) " 184 | self.message += f"Step {self.pathing_host.step_counter}:\n{result}\n" 185 | self.message += f"Nodes visited: {self.pathing_host.nodes_found} / {opensquares}" 186 | 187 | else: 188 | self.message = f"{self.pathing_host.alg_name} ({self.side_cell_count}x{self.side_cell_count}): Complete in {self.pathing_host.step_counter} steps.\n" 189 | self.message += f"{self.pathing_host.nodes_found} / {opensquares} nodes visited in total. ({((self.pathing_host.nodes_found / opensquares) * 100):.2f}%)\n" 190 | self.message += f"Path of length {self.pathing_host.path_length} traced." 191 | 192 | return result 193 | 194 | def reset(self): 195 | curr_alg_name = self.pathing_host.alg_name 196 | dpg.delete_item(self.drawlist, children_only=False) 197 | self.pathing_host = PathfindingHost( 198 | self.side_cell_count, lambda node: self.draw_node(node), self.draw_weights, algorithm=curr_alg_name) 199 | self.initialize_grid() 200 | 201 | def retry(self): 202 | self.pathing_host.retry_maze() 203 | 204 | def randmaze(self): 205 | self.pathing_host.rand_maze() # drawing is handled in-alg 206 | 207 | def unmount(self): 208 | dpg.delete_item(self.drawlist, children_only=False) 209 | dpg.delete_item(self.clickregistry, children_only=False) 210 | self.pathing_host = None 211 | 212 | def current_alg(self): 213 | return self.pathing_host.alg_name 214 | 215 | def is_initial(self): 216 | if self.pathing_host.step_counter == 0: 217 | return True 218 | return False 219 | -------------------------------------------------------------------------------- /src/resources/fonts/Roboto_Mono/LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /src/resources/fonts/Roboto_Mono/README.txt: -------------------------------------------------------------------------------- 1 | Roboto Mono Variable Font 2 | ========================= 3 | 4 | This download contains Roboto Mono as both variable fonts and static fonts. 5 | 6 | Roboto Mono is a variable font with this axis: 7 | wght 8 | 9 | This means all the styles are contained in these files: 10 | RobotoMono-VariableFont_wght.ttf 11 | RobotoMono-Italic-VariableFont_wght.ttf 12 | 13 | If your app fully supports variable fonts, you can now pick intermediate styles 14 | that aren’t available as static fonts. Not all apps support variable fonts, and 15 | in those cases you can use the static font files for Roboto Mono: 16 | static/RobotoMono-Thin.ttf 17 | static/RobotoMono-ExtraLight.ttf 18 | static/RobotoMono-Light.ttf 19 | static/RobotoMono-Regular.ttf 20 | static/RobotoMono-Medium.ttf 21 | static/RobotoMono-SemiBold.ttf 22 | static/RobotoMono-Bold.ttf 23 | static/RobotoMono-ThinItalic.ttf 24 | static/RobotoMono-ExtraLightItalic.ttf 25 | static/RobotoMono-LightItalic.ttf 26 | static/RobotoMono-Italic.ttf 27 | static/RobotoMono-MediumItalic.ttf 28 | static/RobotoMono-SemiBoldItalic.ttf 29 | static/RobotoMono-BoldItalic.ttf 30 | 31 | Get started 32 | ----------- 33 | 34 | 1. Install the font files you want to use 35 | 36 | 2. Use your app's font picker to view the font family and all the 37 | available styles 38 | 39 | Learn more about variable fonts 40 | ------------------------------- 41 | 42 | https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts 43 | https://variablefonts.typenetwork.com 44 | https://medium.com/variable-fonts 45 | 46 | In desktop apps 47 | 48 | https://theblog.adobe.com/can-variable-fonts-illustrator-cc 49 | https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts 50 | 51 | Online 52 | 53 | https://developers.google.com/fonts/docs/getting_started 54 | https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide 55 | https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts 56 | 57 | Installing fonts 58 | 59 | MacOS: https://support.apple.com/en-us/HT201749 60 | Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux 61 | Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows 62 | 63 | Android Apps 64 | 65 | https://developers.google.com/fonts/docs/android 66 | https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts 67 | 68 | License 69 | ------- 70 | Please read the full license text (LICENSE.txt) to understand the permissions, 71 | restrictions and requirements for usage, redistribution, and modification. 72 | 73 | You can use them freely in your products & projects - print or digital, 74 | commercial or otherwise. However, you can't sell the fonts on their own. 75 | 76 | This isn't legal advice, please consider consulting a lawyer and see the full 77 | license for all details. 78 | -------------------------------------------------------------------------------- /src/resources/fonts/Roboto_Mono/RobotoMono-Italic-VariableFont_wght.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seesjays/ELPath/7c65c3b5c63ee6d6a61dad21d61521439b9ca8ed/src/resources/fonts/Roboto_Mono/RobotoMono-Italic-VariableFont_wght.ttf -------------------------------------------------------------------------------- /src/resources/fonts/Roboto_Mono/RobotoMono-VariableFont_wght.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seesjays/ELPath/7c65c3b5c63ee6d6a61dad21d61521439b9ca8ed/src/resources/fonts/Roboto_Mono/RobotoMono-VariableFont_wght.ttf -------------------------------------------------------------------------------- /src/resources/fonts/Roboto_Mono/static/RobotoMono-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seesjays/ELPath/7c65c3b5c63ee6d6a61dad21d61521439b9ca8ed/src/resources/fonts/Roboto_Mono/static/RobotoMono-Bold.ttf -------------------------------------------------------------------------------- /src/resources/fonts/Roboto_Mono/static/RobotoMono-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seesjays/ELPath/7c65c3b5c63ee6d6a61dad21d61521439b9ca8ed/src/resources/fonts/Roboto_Mono/static/RobotoMono-BoldItalic.ttf -------------------------------------------------------------------------------- /src/resources/fonts/Roboto_Mono/static/RobotoMono-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seesjays/ELPath/7c65c3b5c63ee6d6a61dad21d61521439b9ca8ed/src/resources/fonts/Roboto_Mono/static/RobotoMono-ExtraLight.ttf -------------------------------------------------------------------------------- /src/resources/fonts/Roboto_Mono/static/RobotoMono-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seesjays/ELPath/7c65c3b5c63ee6d6a61dad21d61521439b9ca8ed/src/resources/fonts/Roboto_Mono/static/RobotoMono-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /src/resources/fonts/Roboto_Mono/static/RobotoMono-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seesjays/ELPath/7c65c3b5c63ee6d6a61dad21d61521439b9ca8ed/src/resources/fonts/Roboto_Mono/static/RobotoMono-Italic.ttf -------------------------------------------------------------------------------- /src/resources/fonts/Roboto_Mono/static/RobotoMono-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seesjays/ELPath/7c65c3b5c63ee6d6a61dad21d61521439b9ca8ed/src/resources/fonts/Roboto_Mono/static/RobotoMono-Light.ttf -------------------------------------------------------------------------------- /src/resources/fonts/Roboto_Mono/static/RobotoMono-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seesjays/ELPath/7c65c3b5c63ee6d6a61dad21d61521439b9ca8ed/src/resources/fonts/Roboto_Mono/static/RobotoMono-LightItalic.ttf -------------------------------------------------------------------------------- /src/resources/fonts/Roboto_Mono/static/RobotoMono-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seesjays/ELPath/7c65c3b5c63ee6d6a61dad21d61521439b9ca8ed/src/resources/fonts/Roboto_Mono/static/RobotoMono-Medium.ttf -------------------------------------------------------------------------------- /src/resources/fonts/Roboto_Mono/static/RobotoMono-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seesjays/ELPath/7c65c3b5c63ee6d6a61dad21d61521439b9ca8ed/src/resources/fonts/Roboto_Mono/static/RobotoMono-MediumItalic.ttf -------------------------------------------------------------------------------- /src/resources/fonts/Roboto_Mono/static/RobotoMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seesjays/ELPath/7c65c3b5c63ee6d6a61dad21d61521439b9ca8ed/src/resources/fonts/Roboto_Mono/static/RobotoMono-Regular.ttf -------------------------------------------------------------------------------- /src/resources/fonts/Roboto_Mono/static/RobotoMono-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seesjays/ELPath/7c65c3b5c63ee6d6a61dad21d61521439b9ca8ed/src/resources/fonts/Roboto_Mono/static/RobotoMono-SemiBold.ttf -------------------------------------------------------------------------------- /src/resources/fonts/Roboto_Mono/static/RobotoMono-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seesjays/ELPath/7c65c3b5c63ee6d6a61dad21d61521439b9ca8ed/src/resources/fonts/Roboto_Mono/static/RobotoMono-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /src/resources/fonts/Roboto_Mono/static/RobotoMono-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seesjays/ELPath/7c65c3b5c63ee6d6a61dad21d61521439b9ca8ed/src/resources/fonts/Roboto_Mono/static/RobotoMono-Thin.ttf -------------------------------------------------------------------------------- /src/resources/fonts/Roboto_Mono/static/RobotoMono-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seesjays/ELPath/7c65c3b5c63ee6d6a61dad21d61521439b9ca8ed/src/resources/fonts/Roboto_Mono/static/RobotoMono-ThinItalic.ttf --------------------------------------------------------------------------------