├── mazes ├── __init__.py ├── algorithms │ ├── __init__.py │ ├── binary_tree.py │ └── sidewalker.py └── primitives.py ├── tests ├── __init__.py ├── test_binary_tree.py ├── test_sidewalker.py └── test_basic_grid.py ├── README.md ├── Taskfile.yaml ├── pyproject.toml ├── LICENSE.md ├── .gitignore └── poetry.lock /mazes/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mazes for Programmers 2 | 3 | this is my code as I work through maze algorithms 4 | -------------------------------------------------------------------------------- /Taskfile.yaml: -------------------------------------------------------------------------------- 1 | # https://taskfile.dev 2 | 3 | version: '3' 4 | 5 | tasks: 6 | default: 7 | cmds: 8 | - poetry run pytest -s --pdb 9 | -------------------------------------------------------------------------------- /mazes/algorithms/__init__.py: -------------------------------------------------------------------------------- 1 | from mazes.algorithms.binary_tree import binary_tree 2 | from mazes.algorithms.sidewalker import sidewalker 3 | 4 | __all__ = [binary_tree, sidewalker] 5 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "mazes" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Oren Mazor "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.11" 10 | numpy = "^1.24.0" 11 | isort = "^5.11.3" 12 | pytest = "^7.2.0" 13 | pdbpp = "^0.10.3" 14 | 15 | 16 | [build-system] 17 | requires = ["poetry-core"] 18 | build-backend = "poetry.core.masonry.api" 19 | -------------------------------------------------------------------------------- /tests/test_binary_tree.py: -------------------------------------------------------------------------------- 1 | from mazes.primitives import Grid 2 | from mazes.algorithms import binary_tree 3 | 4 | 5 | def test_binary_tree(): 6 | """Test a binary tree.""" 7 | grid = Grid((4, 4)) 8 | binary_tree(grid) 9 | 10 | print( 11 | "the binary tree will always bias to having full hallways that meet kitty corner from the origin. so in our case, the bottom row and the east row will be hallways." 12 | ) 13 | print(grid) 14 | -------------------------------------------------------------------------------- /tests/test_sidewalker.py: -------------------------------------------------------------------------------- 1 | from mazes.primitives import Grid 2 | from mazes.algorithms import sidewalker 3 | 4 | 5 | def test_sidewalker_tree(): 6 | """Test a sidewalker tree.""" 7 | grid = Grid((4, 4)) 8 | sidewalker(grid) 9 | 10 | print( 11 | "The sidewalker/winder algorithm biases towards having one unbroken hallway on the side opposite from the starting point. so in our case it'll be the entire bottom row clear." 12 | ) 13 | print(grid) 14 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /tests/test_basic_grid.py: -------------------------------------------------------------------------------- 1 | """Basic grid tests.""" 2 | import pytest 3 | from mazes.primitives import Cell, Grid 4 | 5 | 6 | def test_basic_grid(): 7 | """Confirm we can build a basic grid.""" 8 | grid = Grid((10, 10)) 9 | 10 | assert type(grid[(0, 0)]) == Cell 11 | assert (1, 2) == grid[(1, 2)].position 12 | 13 | me = grid[(1, 1)] 14 | assert not me.north 15 | me.link(grid[(0, 1)]) 16 | assert (0, 1) == me.north.position 17 | 18 | 19 | def test_edges(): 20 | grid = Grid((10, 10)) 21 | 22 | northwest = grid[(0, 0)] 23 | assert northwest 24 | 25 | assert not northwest.west 26 | assert not northwest.north 27 | 28 | assert not northwest.is_linked(northwest.west) 29 | assert not northwest.is_linked(None) 30 | with pytest.raises(AssertionError): 31 | northwest.link(None) 32 | -------------------------------------------------------------------------------- /mazes/algorithms/binary_tree.py: -------------------------------------------------------------------------------- 1 | """Implementation of the binary tree maze.""" 2 | from random import choice, random 3 | 4 | from mazes.primitives import Grid 5 | 6 | 7 | def binary_tree(grid: Grid) -> None: 8 | """Just a basic bitch binary tree.""" 9 | # since we are moving from the north west point instead 10 | # of the south west point, this basic walk flips the options 11 | # from north/east to south/east 12 | for position, cell in grid.each_cell(): 13 | possible_directions = {} 14 | 15 | # account for the south direction 16 | if position[0] < grid.inner.shape[0] - 1: 17 | possible_directions["south"] = (position[0] + 1, position[1]) 18 | 19 | # account for the east direction 20 | if position[1] < grid.inner.shape[1] - 1: 21 | possible_directions["east"] = (position[0], position[1] + 1) 22 | 23 | if not possible_directions: 24 | # this happens when we are at the corner 25 | # which means we're done 26 | return 27 | 28 | random_direction = choice(list(possible_directions.keys())) 29 | 30 | setattr(cell, random_direction, grid[possible_directions[random_direction]]) 31 | -------------------------------------------------------------------------------- /mazes/algorithms/sidewalker.py: -------------------------------------------------------------------------------- 1 | """Implementation of the sidewalker maze.""" 2 | from random import choice, random, randint 3 | 4 | from mazes.primitives import Grid, Cell 5 | 6 | 7 | def sidewalker(grid: Grid) -> None: 8 | """Implement a side walker algorithm.""" 9 | # since we are moving from the north west point instead 10 | # of the south west point, this changes how we walk. 11 | max_row, max_col = grid.inner.shape 12 | 13 | def at_eastern_boundary(cell: Cell): 14 | return cell.position[1] + 1 == max_col 15 | 16 | def at_southern_boundary(cell: Cell): 17 | return cell.position[0] + 1 == max_row 18 | 19 | def should_we_close_out_this_run(cell: Cell) -> bool: 20 | """Determine if the run should be closed out. 21 | 22 | A run is closed out if we are at the eastern boundary 23 | or 50% of the time. 24 | """ 25 | coin_flip = randint(0, 1) 26 | return at_eastern_boundary(cell) or ( 27 | not at_southern_boundary(cell) and coin_flip == 0 28 | ) 29 | 30 | for row in grid.each_row(): 31 | run = [] 32 | 33 | for cell in row: 34 | run.append(cell) 35 | 36 | # figure out if we're done our run 37 | # if we are at the eastern boundary 38 | # or if we feel like it 39 | if should_we_close_out_this_run(cell): 40 | # pick a random cell from our current run 41 | # and make a southern hole in it 42 | chosen_exit = choice(run) 43 | 44 | south = (chosen_exit.position[0] + 1, chosen_exit.position[1]) 45 | 46 | if not at_southern_boundary(cell): 47 | chosen_exit.south = grid[south] 48 | 49 | # then clear the run 50 | run = [] 51 | else: 52 | # we punch a whole east from this current cell 53 | east = (cell.position[0], cell.position[1] + 1) 54 | cell.east = grid[east] 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/python 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=python 3 | 4 | ### Python ### 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | cover/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | db.sqlite3 66 | db.sqlite3-journal 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | 78 | # PyBuilder 79 | .pybuilder/ 80 | target/ 81 | 82 | # Jupyter Notebook 83 | .ipynb_checkpoints 84 | 85 | # IPython 86 | profile_default/ 87 | ipython_config.py 88 | 89 | # pyenv 90 | # For a library or package, you might want to ignore these files since the code is 91 | # intended to run in multiple environments; otherwise, check them in: 92 | # .python-version 93 | 94 | # pipenv 95 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 96 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 97 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 98 | # install all needed dependencies. 99 | #Pipfile.lock 100 | 101 | # poetry 102 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 103 | # This is especially recommended for binary packages to ensure reproducibility, and is more 104 | # commonly ignored for libraries. 105 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 106 | #poetry.lock 107 | 108 | # pdm 109 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 110 | #pdm.lock 111 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 112 | # in version control. 113 | # https://pdm.fming.dev/#use-with-ide 114 | .pdm.toml 115 | 116 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 117 | __pypackages__/ 118 | 119 | # Celery stuff 120 | celerybeat-schedule 121 | celerybeat.pid 122 | 123 | # SageMath parsed files 124 | *.sage.py 125 | 126 | # Environments 127 | .env 128 | .venv 129 | env/ 130 | venv/ 131 | ENV/ 132 | env.bak/ 133 | venv.bak/ 134 | 135 | # Spyder project settings 136 | .spyderproject 137 | .spyproject 138 | 139 | # Rope project settings 140 | .ropeproject 141 | 142 | # mkdocs documentation 143 | /site 144 | 145 | # mypy 146 | .mypy_cache/ 147 | .dmypy.json 148 | dmypy.json 149 | 150 | # Pyre type checker 151 | .pyre/ 152 | 153 | # pytype static type analyzer 154 | .pytype/ 155 | 156 | # Cython debug symbols 157 | cython_debug/ 158 | 159 | # PyCharm 160 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 161 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 162 | # and can be added to the global gitignore or merged into this file. For a more nuclear 163 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 164 | #.idea/ 165 | 166 | ### Python Patch ### 167 | # Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration 168 | poetry.toml 169 | 170 | 171 | # End of https://www.toptal.com/developers/gitignore/api/python 172 | n -------------------------------------------------------------------------------- /mazes/primitives.py: -------------------------------------------------------------------------------- 1 | """Maze primitives.""" 2 | from __future__ import annotations 3 | 4 | from numpy import array, ndenumerate, full 5 | from typing import Tuple, Dict, List, Generator, Optional 6 | from random import randint 7 | 8 | 9 | class Grid(object): 10 | """A basic implementation of a maze grid.""" 11 | 12 | inner: array 13 | 14 | def __init__(self: Grid, shape: Tuple[int, int]) -> None: 15 | super().__init__() 16 | 17 | self.inner = full(shape, Cell((-1, -1))) 18 | self.populate_grid() 19 | # self.configure_cells() 20 | 21 | def populate_grid(self: Grid) -> None: 22 | """Populate the grid with disjoint cells.""" 23 | for position, _ in ndenumerate(self.inner): 24 | self.inner[position] = Cell(position=position) 25 | 26 | def __getitem__(self: Grid, key: Tuple) -> Cell: 27 | """Return item in the grid.""" 28 | return self.inner[key] 29 | 30 | def random(self: Grid) -> Cell: 31 | """Return a random cell.""" 32 | random_row = randint(0, self.inner.shape[0]) 33 | random_col = randint(0, self.inner.shape[1]) 34 | return self.inner[(random_row, random_col)] 35 | 36 | def size(self: Grid) -> int: 37 | """Return array size.""" 38 | return self.inner.size 39 | 40 | def each_row(self: Grid) -> Generator: 41 | """Yield a row at a time.""" 42 | for row in self.inner: 43 | yield row 44 | 45 | def each_cell(self: Grid) -> Generator: 46 | """Yield a cell and a position at a time.""" 47 | for position, cell in ndenumerate(self.inner): 48 | if cell: 49 | yield (position, cell) 50 | 51 | def __repr__(self: Grid) -> str: 52 | """Return a visual representation of the maze.""" 53 | 54 | def print_line() -> str: 55 | return "+" + "---+" * self.inner.shape[1] + "\n" 56 | 57 | result = "\n" + print_line() 58 | for row in self.each_row(): 59 | bottom_line = "+" 60 | current_row = "|" 61 | 62 | for cell in row: 63 | # we only look at east and south 64 | # because those are the directions 65 | # we are moving in 66 | 67 | # first add our body of the cell 68 | current_row += " " 69 | 70 | if cell.east: 71 | # we have a way east 72 | # so draw an empty space 73 | current_row += " " 74 | else: 75 | current_row += "|" 76 | 77 | if cell.south: 78 | # we have a way south 79 | # so draw an empty space 80 | bottom_line += " +" 81 | else: 82 | bottom_line += "---+" 83 | 84 | current_row += "\n" 85 | bottom_line += "\n" 86 | result += current_row 87 | result += bottom_line 88 | 89 | return result 90 | 91 | 92 | class Cell(object): 93 | """A cell is a single position in a maze and is aware of its neighbours.""" 94 | 95 | position: tuple 96 | links: dict = {} 97 | north: Optional[Cell] = None 98 | south: Optional[Cell] = None 99 | east: Optional[Cell] = None 100 | west: Optional[Cell] = None 101 | 102 | def __init__(self: Cell, position: Tuple) -> None: 103 | """Initialize the position.""" 104 | self.position = position 105 | 106 | def link(self: Cell, sibling: Cell, bidirectional: bool = False) -> None: 107 | """Link a sibling cell.""" 108 | # we should never get a none 109 | assert sibling 110 | 111 | if sibling.position[0] == self.position[0]: 112 | # we are on the same row 113 | if sibling.position > self.position: 114 | self.east = sibling 115 | else: 116 | self.west = sibling 117 | 118 | elif sibling.position[1] == self.position[1]: 119 | if sibling.position > self.position: 120 | self.south = sibling 121 | else: 122 | self.north = sibling 123 | 124 | if bidirectional: 125 | # we dont want to get in here yet 126 | return 127 | sibling.link(self) 128 | 129 | def is_linked(self: Cell, sibling: Cell) -> bool: 130 | """Check if we are linked to this cell.""" 131 | return sibling in self.neighbours() 132 | 133 | def directions(self: Cell) -> List[Tuple[int, int]]: 134 | """Return a list of possible directions from here. 135 | 136 | These may not all be legal. 137 | """ 138 | _directions = [] 139 | 140 | # we can move west 141 | _directions.append((self.position[0] - 1, self.position[1])) 142 | # we can move east 143 | _directions.append((self.position[0] + 1, self.position[1])) 144 | 145 | # we can move north 146 | _directions.append((self.position[0], self.position[1] - 1)) 147 | 148 | # we can move south 149 | _directions.append((self.position[0], self.position[1] + 1)) 150 | 151 | return _directions 152 | 153 | def neighbours(self: Cell) -> List[Cell]: 154 | """Return list of neighbours.""" 155 | return list(filter(lambda x: x, [self.north, self.south, self.east, self.west])) 156 | 157 | def __repr__(self: Cell) -> str: 158 | """Return a representation of the cell.""" 159 | result = f"Cell(position=({self.position})" 160 | 161 | if self.west: 162 | result += f", west={self.west.position}" 163 | 164 | if self.east: 165 | result += f", east={self.east.position}" 166 | 167 | if self.north: 168 | result += f", north={self.north.position}" 169 | 170 | if self.south: 171 | result += f", south={self.south.position}" 172 | 173 | return result 174 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "attrs" 3 | version = "22.1.0" 4 | description = "Classes Without Boilerplate" 5 | category = "main" 6 | optional = false 7 | python-versions = ">=3.5" 8 | 9 | [package.extras] 10 | dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] 11 | docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] 12 | tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] 13 | tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] 14 | 15 | [[package]] 16 | name = "colorama" 17 | version = "0.4.6" 18 | description = "Cross-platform colored terminal text." 19 | category = "main" 20 | optional = false 21 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 22 | 23 | [[package]] 24 | name = "fancycompleter" 25 | version = "0.9.1" 26 | description = "colorful TAB completion for Python prompt" 27 | category = "main" 28 | optional = false 29 | python-versions = "*" 30 | 31 | [package.dependencies] 32 | pyreadline = {version = "*", markers = "platform_system == \"Windows\""} 33 | pyrepl = ">=0.8.2" 34 | 35 | [[package]] 36 | name = "iniconfig" 37 | version = "1.1.1" 38 | description = "iniconfig: brain-dead simple config-ini parsing" 39 | category = "main" 40 | optional = false 41 | python-versions = "*" 42 | 43 | [[package]] 44 | name = "isort" 45 | version = "5.11.3" 46 | description = "A Python utility / library to sort Python imports." 47 | category = "main" 48 | optional = false 49 | python-versions = ">=3.7.0" 50 | 51 | [package.extras] 52 | colors = ["colorama (>=0.4.3,<0.5.0)"] 53 | pipfile-deprecated-finder = ["pipreqs", "requirementslib"] 54 | plugins = ["setuptools"] 55 | requirements-deprecated-finder = ["pip-api", "pipreqs"] 56 | 57 | [[package]] 58 | name = "numpy" 59 | version = "1.24.0" 60 | description = "Fundamental package for array computing in Python" 61 | category = "main" 62 | optional = false 63 | python-versions = ">=3.8" 64 | 65 | [[package]] 66 | name = "packaging" 67 | version = "22.0" 68 | description = "Core utilities for Python packages" 69 | category = "main" 70 | optional = false 71 | python-versions = ">=3.7" 72 | 73 | [[package]] 74 | name = "pdbpp" 75 | version = "0.10.3" 76 | description = "pdb++, a drop-in replacement for pdb" 77 | category = "main" 78 | optional = false 79 | python-versions = "*" 80 | 81 | [package.dependencies] 82 | fancycompleter = ">=0.8" 83 | pygments = "*" 84 | wmctrl = "*" 85 | 86 | [package.extras] 87 | funcsigs = ["funcsigs"] 88 | testing = ["funcsigs", "pytest"] 89 | 90 | [[package]] 91 | name = "pluggy" 92 | version = "1.0.0" 93 | description = "plugin and hook calling mechanisms for python" 94 | category = "main" 95 | optional = false 96 | python-versions = ">=3.6" 97 | 98 | [package.extras] 99 | dev = ["pre-commit", "tox"] 100 | testing = ["pytest", "pytest-benchmark"] 101 | 102 | [[package]] 103 | name = "pygments" 104 | version = "2.13.0" 105 | description = "Pygments is a syntax highlighting package written in Python." 106 | category = "main" 107 | optional = false 108 | python-versions = ">=3.6" 109 | 110 | [package.extras] 111 | plugins = ["importlib-metadata"] 112 | 113 | [[package]] 114 | name = "pyreadline" 115 | version = "2.1" 116 | description = "A python implmementation of GNU readline." 117 | category = "main" 118 | optional = false 119 | python-versions = "*" 120 | 121 | [[package]] 122 | name = "pyrepl" 123 | version = "0.9.0" 124 | description = "A library for building flexible command line interfaces" 125 | category = "main" 126 | optional = false 127 | python-versions = "*" 128 | 129 | [[package]] 130 | name = "pytest" 131 | version = "7.2.0" 132 | description = "pytest: simple powerful testing with Python" 133 | category = "main" 134 | optional = false 135 | python-versions = ">=3.7" 136 | 137 | [package.dependencies] 138 | attrs = ">=19.2.0" 139 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 140 | iniconfig = "*" 141 | packaging = "*" 142 | pluggy = ">=0.12,<2.0" 143 | 144 | [package.extras] 145 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] 146 | 147 | [[package]] 148 | name = "wmctrl" 149 | version = "0.4" 150 | description = "A tool to programmatically control windows inside X" 151 | category = "main" 152 | optional = false 153 | python-versions = "*" 154 | 155 | [metadata] 156 | lock-version = "1.1" 157 | python-versions = "^3.11" 158 | content-hash = "fc85bc4d36ee820cc0edb9b70514fdaba103dd93eef8a3c913f11c16d92520eb" 159 | 160 | [metadata.files] 161 | attrs = [ 162 | {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, 163 | {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, 164 | ] 165 | colorama = [ 166 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 167 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 168 | ] 169 | fancycompleter = [ 170 | {file = "fancycompleter-0.9.1-py3-none-any.whl", hash = "sha256:dd076bca7d9d524cc7f25ec8f35ef95388ffef9ef46def4d3d25e9b044ad7080"}, 171 | {file = "fancycompleter-0.9.1.tar.gz", hash = "sha256:09e0feb8ae242abdfd7ef2ba55069a46f011814a80fe5476be48f51b00247272"}, 172 | ] 173 | iniconfig = [ 174 | {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, 175 | {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, 176 | ] 177 | isort = [ 178 | {file = "isort-5.11.3-py3-none-any.whl", hash = "sha256:83155ffa936239d986b0f190347a3f2285f42a9b9e1725c89d865b27dd0627e5"}, 179 | {file = "isort-5.11.3.tar.gz", hash = "sha256:a8ca25fbfad0f7d5d8447a4314837298d9f6b23aed8618584c894574f626b64b"}, 180 | ] 181 | numpy = [ 182 | {file = "numpy-1.24.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6e73a1f4f5b74a42abb55bc2b3d869f1b38cbc8776da5f8b66bf110284f7a437"}, 183 | {file = "numpy-1.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9387c7d6d50e8f8c31e7bfc034241e9c6f4b3eb5db8d118d6487047b922f82af"}, 184 | {file = "numpy-1.24.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ad6a024a32ee61d18f5b402cd02e9c0e22c0fb9dc23751991b3a16d209d972e"}, 185 | {file = "numpy-1.24.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73cf2c5b5a07450f20a0c8e04d9955491970177dce8df8d6903bf253e53268e0"}, 186 | {file = "numpy-1.24.0-cp310-cp310-win32.whl", hash = "sha256:cec79ff3984b2d1d103183fc4a3361f5b55bbb66cb395cbf5a920a4bb1fd588d"}, 187 | {file = "numpy-1.24.0-cp310-cp310-win_amd64.whl", hash = "sha256:4f5e78b8b710cd7cd1a8145994cfffc6ddd5911669a437777d8cedfce6c83a98"}, 188 | {file = "numpy-1.24.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4445f472b246cad6514cc09fbb5ecb7aab09ca2acc3c16f29f8dca6c468af501"}, 189 | {file = "numpy-1.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ec3e5e8172a0a6a4f3c2e7423d4a8434c41349141b04744b11a90e017a95bad5"}, 190 | {file = "numpy-1.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9168790149f917ad8e3cf5047b353fefef753bd50b07c547da0bdf30bc15d91"}, 191 | {file = "numpy-1.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ada6c1e9608ceadaf7020e1deea508b73ace85560a16f51bef26aecb93626a72"}, 192 | {file = "numpy-1.24.0-cp311-cp311-win32.whl", hash = "sha256:f3c4a9a9f92734a4728ddbd331e0124eabbc968a0359a506e8e74a9b0d2d419b"}, 193 | {file = "numpy-1.24.0-cp311-cp311-win_amd64.whl", hash = "sha256:90075ef2c6ac6397d0035bcd8b298b26e481a7035f7a3f382c047eb9c3414db0"}, 194 | {file = "numpy-1.24.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0885d9a7666cafe5f9876c57bfee34226e2b2847bfb94c9505e18d81011e5401"}, 195 | {file = "numpy-1.24.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e63d2157f9fc98cc178870db83b0e0c85acdadd598b134b00ebec9e0db57a01f"}, 196 | {file = "numpy-1.24.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf8960f72997e56781eb1c2ea256a70124f92a543b384f89e5fb3503a308b1d3"}, 197 | {file = "numpy-1.24.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f8e0df2ecc1928ef7256f18e309c9d6229b08b5be859163f5caa59c93d53646"}, 198 | {file = "numpy-1.24.0-cp38-cp38-win32.whl", hash = "sha256:fe44e925c68fb5e8db1334bf30ac1a1b6b963b932a19cf41d2e899cf02f36aab"}, 199 | {file = "numpy-1.24.0-cp38-cp38-win_amd64.whl", hash = "sha256:d7f223554aba7280e6057727333ed357b71b7da7422d02ff5e91b857888c25d1"}, 200 | {file = "numpy-1.24.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ab11f6a7602cf8ea4c093e091938207de3068c5693a0520168ecf4395750f7ea"}, 201 | {file = "numpy-1.24.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:12bba5561d8118981f2f1ff069ecae200c05d7b6c78a5cdac0911f74bc71cbd1"}, 202 | {file = "numpy-1.24.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9af91f794d2d3007d91d749ebc955302889261db514eb24caef30e03e8ec1e41"}, 203 | {file = "numpy-1.24.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b1ddfac6a82d4f3c8e99436c90b9c2c68c0bb14658d1684cdd00f05fab241f5"}, 204 | {file = "numpy-1.24.0-cp39-cp39-win32.whl", hash = "sha256:ac4fe68f1a5a18136acebd4eff91aab8bed00d1ef2fdb34b5d9192297ffbbdfc"}, 205 | {file = "numpy-1.24.0-cp39-cp39-win_amd64.whl", hash = "sha256:667b5b1f6a352419e340f6475ef9930348ae5cb7fca15f2cc3afcb530823715e"}, 206 | {file = "numpy-1.24.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4d01f7832fa319a36fd75ba10ea4027c9338ede875792f7bf617f4b45056fc3a"}, 207 | {file = "numpy-1.24.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbb0490f0a880700a6cc4d000384baf19c1f4df59fff158d9482d4dbbca2b239"}, 208 | {file = "numpy-1.24.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:0104d8adaa3a4cc60c2777cab5196593bf8a7f416eda133be1f3803dd0838886"}, 209 | {file = "numpy-1.24.0.tar.gz", hash = "sha256:c4ab7c9711fe6b235e86487ca74c1b092a6dd59a3cb45b63241ea0a148501853"}, 210 | ] 211 | packaging = [ 212 | {file = "packaging-22.0-py3-none-any.whl", hash = "sha256:957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3"}, 213 | {file = "packaging-22.0.tar.gz", hash = "sha256:2198ec20bd4c017b8f9717e00f0c8714076fc2fd93816750ab48e2c41de2cfd3"}, 214 | ] 215 | pdbpp = [ 216 | {file = "pdbpp-0.10.3-py2.py3-none-any.whl", hash = "sha256:79580568e33eb3d6f6b462b1187f53e10cd8e4538f7d31495c9181e2cf9665d1"}, 217 | {file = "pdbpp-0.10.3.tar.gz", hash = "sha256:d9e43f4fda388eeb365f2887f4e7b66ac09dce9b6236b76f63616530e2f669f5"}, 218 | ] 219 | pluggy = [ 220 | {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, 221 | {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, 222 | ] 223 | pygments = [ 224 | {file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"}, 225 | {file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"}, 226 | ] 227 | pyreadline = [ 228 | {file = "pyreadline-2.1.zip", hash = "sha256:4530592fc2e85b25b1a9f79664433da09237c1a270e4d78ea5aa3a2c7229e2d1"}, 229 | ] 230 | pyrepl = [ 231 | {file = "pyrepl-0.9.0.tar.gz", hash = "sha256:292570f34b5502e871bbb966d639474f2b57fbfcd3373c2d6a2f3d56e681a775"}, 232 | ] 233 | pytest = [ 234 | {file = "pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"}, 235 | {file = "pytest-7.2.0.tar.gz", hash = "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"}, 236 | ] 237 | wmctrl = [ 238 | {file = "wmctrl-0.4.tar.gz", hash = "sha256:66cbff72b0ca06a22ec3883ac3a4d7c41078bdae4fb7310f52951769b10e14e0"}, 239 | ] 240 | --------------------------------------------------------------------------------