├── .github └── workflows │ ├── CI.yml │ ├── build_docs.yml │ ├── generate_stub_file.yml │ └── test.yml ├── .gitignore ├── .readthedocs.yaml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.rst ├── assets ├── 1.png ├── 10.png ├── 11.png ├── 12.png ├── 2.png ├── 3.png ├── 4.png ├── 5.png ├── 6.png ├── 7.png ├── 8.png └── 9.png ├── docs ├── Makefile ├── build │ ├── doctrees │ │ ├── environment.pickle │ │ ├── index.doctree │ │ ├── modules.doctree │ │ └── rpg_map.doctree │ └── html │ │ ├── .buildinfo │ │ ├── .buildinfo.bak │ │ ├── _modules │ │ └── index.html │ │ ├── _sources │ │ ├── index.rst.txt │ │ ├── modules.rst.txt │ │ └── rpg_map.rst.txt │ │ ├── _static │ │ ├── _sphinx_javascript_frameworks_compat.js │ │ ├── basic.css │ │ ├── css │ │ │ ├── badge_only.css │ │ │ ├── fonts │ │ │ │ ├── Roboto-Slab-Bold.woff │ │ │ │ ├── Roboto-Slab-Bold.woff2 │ │ │ │ ├── Roboto-Slab-Regular.woff │ │ │ │ ├── Roboto-Slab-Regular.woff2 │ │ │ │ ├── fontawesome-webfont.eot │ │ │ │ ├── fontawesome-webfont.svg │ │ │ │ ├── fontawesome-webfont.ttf │ │ │ │ ├── fontawesome-webfont.woff │ │ │ │ ├── fontawesome-webfont.woff2 │ │ │ │ ├── lato-bold-italic.woff │ │ │ │ ├── lato-bold-italic.woff2 │ │ │ │ ├── lato-bold.woff │ │ │ │ ├── lato-bold.woff2 │ │ │ │ ├── lato-normal-italic.woff │ │ │ │ ├── lato-normal-italic.woff2 │ │ │ │ ├── lato-normal.woff │ │ │ │ └── lato-normal.woff2 │ │ │ └── theme.css │ │ ├── doctools.js │ │ ├── documentation_options.js │ │ ├── file.png │ │ ├── fonts │ │ │ ├── Lato │ │ │ │ ├── lato-bold.eot │ │ │ │ ├── lato-bold.ttf │ │ │ │ ├── lato-bold.woff │ │ │ │ ├── lato-bold.woff2 │ │ │ │ ├── lato-bolditalic.eot │ │ │ │ ├── lato-bolditalic.ttf │ │ │ │ ├── lato-bolditalic.woff │ │ │ │ ├── lato-bolditalic.woff2 │ │ │ │ ├── lato-italic.eot │ │ │ │ ├── lato-italic.ttf │ │ │ │ ├── lato-italic.woff │ │ │ │ ├── lato-italic.woff2 │ │ │ │ ├── lato-regular.eot │ │ │ │ ├── lato-regular.ttf │ │ │ │ ├── lato-regular.woff │ │ │ │ └── lato-regular.woff2 │ │ │ └── RobotoSlab │ │ │ │ ├── roboto-slab-v7-bold.eot │ │ │ │ ├── roboto-slab-v7-bold.ttf │ │ │ │ ├── roboto-slab-v7-bold.woff │ │ │ │ ├── roboto-slab-v7-bold.woff2 │ │ │ │ ├── roboto-slab-v7-regular.eot │ │ │ │ ├── roboto-slab-v7-regular.ttf │ │ │ │ ├── roboto-slab-v7-regular.woff │ │ │ │ └── roboto-slab-v7-regular.woff2 │ │ ├── jquery.js │ │ ├── js │ │ │ ├── badge_only.js │ │ │ ├── theme.js │ │ │ └── versions.js │ │ ├── language_data.js │ │ ├── minus.png │ │ ├── plus.png │ │ ├── pygments.css │ │ ├── searchtools.js │ │ └── sphinx_highlight.js │ │ ├── genindex.html │ │ ├── index.html │ │ ├── modules.html │ │ ├── objects.inv │ │ ├── py-modindex.html │ │ ├── rpg_map.html │ │ ├── search.html │ │ └── searchindex.js ├── make.bat ├── requirements.txt └── source │ ├── conf.py │ ├── index.rst │ ├── modules.rst │ └── rpg_map.rst ├── examples ├── pygame_poc.py ├── readme.py └── static_poc.py ├── pyproject.toml ├── rpg_map.pyi ├── src ├── bin │ └── stub_gen.rs ├── lib.rs ├── structs │ ├── map.rs │ ├── mod.rs │ ├── path.rs │ └── travel.rs └── tests │ ├── map.rs │ ├── mod.rs │ ├── path.rs │ ├── travel.rs │ └── utils.rs ├── test_assets ├── background.png ├── cat.png └── map.png ├── test_results ├── debug_with_obstacle.png ├── full.png ├── obstacle.png └── progress_and_hidden.png └── workaround ├── Cargo.lock ├── Cargo.toml └── src └── lib.rs /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | # This file is autogenerated by maturin v1.8.3 2 | # To update, run 3 | # 4 | # maturin generate-ci github 5 | # 6 | name: CI 7 | 8 | on: 9 | workflow_run: 10 | workflows: ["Run Tests"] 11 | types: 12 | - completed 13 | branches: 14 | - master 15 | push: 16 | tags: 17 | - '*' 18 | 19 | permissions: 20 | contents: read 21 | 22 | jobs: 23 | linux: 24 | if : ${{ (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') || (github.event_name != 'workflow_run') }} 25 | runs-on: ${{ matrix.platform.runner }} 26 | strategy: 27 | matrix: 28 | platform: 29 | - runner: ubuntu-22.04 30 | target: x86_64 31 | - runner: ubuntu-22.04 32 | target: x86 33 | - runner: ubuntu-22.04 34 | target: aarch64 35 | - runner: ubuntu-22.04 36 | target: armv7 37 | - runner: ubuntu-22.04 38 | target: s390x 39 | - runner: ubuntu-22.04 40 | target: ppc64le 41 | steps: 42 | - uses: actions/checkout@v4 43 | - uses: actions/setup-python@v5 44 | with: 45 | python-version: 3.x 46 | - name: Build wheels 47 | uses: PyO3/maturin-action@v1 48 | with: 49 | target: ${{ matrix.platform.target }} 50 | args: --release --out dist --find-interpreter --features "extension-module" 51 | sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} 52 | manylinux: auto 53 | - name: Upload wheels 54 | uses: actions/upload-artifact@v4 55 | with: 56 | name: wheels-linux-${{ matrix.platform.target }} 57 | path: dist 58 | 59 | musllinux: 60 | if : ${{ (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') || (github.event_name != 'workflow_run') }} 61 | runs-on: ${{ matrix.platform.runner }} 62 | strategy: 63 | matrix: 64 | platform: 65 | - runner: ubuntu-22.04 66 | target: x86_64 67 | - runner: ubuntu-22.04 68 | target: x86 69 | - runner: ubuntu-22.04 70 | target: aarch64 71 | - runner: ubuntu-22.04 72 | target: armv7 73 | steps: 74 | - uses: actions/checkout@v4 75 | - uses: actions/setup-python@v5 76 | with: 77 | python-version: 3.x 78 | - name: Build wheels 79 | uses: PyO3/maturin-action@v1 80 | with: 81 | target: ${{ matrix.platform.target }} 82 | args: --release --out dist --find-interpreter --features "extension-module" 83 | sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} 84 | manylinux: musllinux_1_2 85 | - name: Upload wheels 86 | uses: actions/upload-artifact@v4 87 | with: 88 | name: wheels-musllinux-${{ matrix.platform.target }} 89 | path: dist 90 | 91 | windows: 92 | if: ${{ (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') || (github.event_name != 'workflow_run') }} 93 | runs-on: ${{ matrix.platform.runner }} 94 | strategy: 95 | matrix: 96 | platform: 97 | - runner: windows-latest 98 | target: x64 99 | - runner: windows-latest 100 | target: x86 101 | steps: 102 | - uses: actions/checkout@v4 103 | - uses: actions/setup-python@v5 104 | with: 105 | python-version: 3.x 106 | architecture: ${{ matrix.platform.target }} 107 | - name: Build wheels 108 | uses: PyO3/maturin-action@v1 109 | with: 110 | target: ${{ matrix.platform.target }} 111 | args: --release --out dist --find-interpreter --features "extension-module" 112 | sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} 113 | - name: Upload wheels 114 | uses: actions/upload-artifact@v4 115 | with: 116 | name: wheels-windows-${{ matrix.platform.target }} 117 | path: dist 118 | 119 | macos: 120 | if: ${{ (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') || (github.event_name != 'workflow_run') }} 121 | runs-on: ${{ matrix.platform.runner }} 122 | strategy: 123 | matrix: 124 | platform: 125 | - runner: macos-13 126 | target: x86_64 127 | - runner: macos-14 128 | target: aarch64 129 | steps: 130 | - uses: actions/checkout@v4 131 | - uses: actions/setup-python@v5 132 | with: 133 | python-version: 3.x 134 | - name: Build wheels 135 | uses: PyO3/maturin-action@v1 136 | with: 137 | target: ${{ matrix.platform.target }} 138 | args: --release --out dist --find-interpreter --features "extension-module" 139 | sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} 140 | - name: Upload wheels 141 | uses: actions/upload-artifact@v4 142 | with: 143 | name: wheels-macos-${{ matrix.platform.target }} 144 | path: dist 145 | 146 | sdist: 147 | if: ${{ (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') || (startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch') }} 148 | runs-on: ubuntu-latest 149 | steps: 150 | - uses: actions/checkout@v4 151 | - name: Build sdist 152 | uses: PyO3/maturin-action@v1 153 | with: 154 | command: sdist 155 | args: --out dist 156 | - name: Upload sdist 157 | uses: actions/upload-artifact@v4 158 | with: 159 | name: wheels-sdist 160 | path: dist 161 | 162 | release: 163 | name: Release 164 | runs-on: ubuntu-latest 165 | if: ${{ (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') || (startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch') }} 166 | needs: [linux, musllinux, windows, macos, sdist] 167 | permissions: 168 | # Use to sign the release artifacts 169 | id-token: write 170 | # Used to upload release artifacts 171 | contents: write 172 | # Used to generate artifact attestation 173 | attestations: write 174 | steps: 175 | - uses: actions/download-artifact@v4 176 | - name: Generate artifact attestation 177 | uses: actions/attest-build-provenance@v2 178 | with: 179 | subject-path: 'wheels-*/*' 180 | - name: Publish to PyPI 181 | if: ${{ startsWith(github.ref, 'refs/tags/') }} 182 | uses: PyO3/maturin-action@v1 183 | env: 184 | MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_PASSWORD }} 185 | with: 186 | command: upload 187 | args: --non-interactive --skip-existing wheels-*/* 188 | -------------------------------------------------------------------------------- /.github/workflows/build_docs.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy Documentation 2 | 3 | on: 4 | workflow_run: 5 | workflows: ["Generate stub file"] # Build docs after stub file is built 6 | types: 7 | - completed 8 | 9 | 10 | jobs: 11 | build-and-test: 12 | runs-on: ubuntu-latest 13 | if : ${{ github.event.workflow_run.conclusion == 'success' }} 14 | permissions: 15 | contents: write # Required to push changes to the repository 16 | steps: 17 | - name: Checkout Repository 18 | uses: actions/checkout@v4 19 | # Fetch all history so we can compute changes later 20 | with: 21 | fetch-depth: 0 22 | 23 | - name: Set up Python 3 24 | uses: actions/setup-python@v5 25 | with: 26 | python-version: 3.x 27 | 28 | - name: Install Dependencies and Build Docs 29 | run: | 30 | python3 -m venv env 31 | source env/bin/activate 32 | cd docs 33 | pip3 install -r requirements.txt 34 | sphinx-apidoc -o source/ ../ -f # Generate API docs 35 | make html 36 | 37 | - name: Check for changes and commit 38 | id: changes 39 | run: | 40 | cd docs/build 41 | # Check if there are any changes in the generated documentation 42 | if [[ -n $(git status --porcelain) ]]; then 43 | echo "::warning title=Documentation Changes::Documentation changes detected!" 44 | git config --local user.name "GitHub Actions" 45 | git config --local user.email "actions@github.com" 46 | git add . 47 | git commit -m "Update documentation" 48 | echo "changed=true" >> $GITHUB_OUTPUT # set changed to true 49 | else 50 | echo "changed=false" >> $GITHUB_OUTPUT # set changed to false 51 | echo "No changes in documentation." 52 | fi 53 | 54 | - name: Push changes 55 | if: steps.changes.outputs.changed == 'true' 56 | uses: ad-m/github-push-action@master 57 | with: 58 | github_token: ${{ secrets.GITHUB_TOKEN }} 59 | branch: ${{ github.ref }} # Push to the same branch 60 | 61 | - name: Upload Documentation Artifact (for PRs) 62 | if: github.event_name == 'pull_request' && steps.changes.outputs.changed == 'true' 63 | uses: actions/upload-artifact@v4 64 | with: 65 | name: docs-preview 66 | path: docs/_build/html/ 67 | retention-days: 7 68 | 69 | - name: Create PR Comment (for PRs) 70 | if: github.event_name == 'pull_request' && steps.changes.outputs.changed == 'true' 71 | uses: peter-evans/create-or-update-comment@v4 72 | with: 73 | issue-number: ${{ github.event.pull_request.number }} 74 | body: | 75 | Documentation changes detected. You can preview the changes by downloading the `docs-preview` artifact from this run. 76 | Alternatively, you can view the changes after this PR is merged. 77 | token: ${{ secrets.GITHUB_TOKEN }} 78 | -------------------------------------------------------------------------------- /.github/workflows/generate_stub_file.yml: -------------------------------------------------------------------------------- 1 | name: Generate stub file 2 | 3 | on: 4 | workflow_run: 5 | workflows: ["Run Tests"] 6 | types: 7 | - completed 8 | 9 | jobs: 10 | run_stub_gen: 11 | name: Run stub_gen 12 | if: ${{ github.event.workflow_run.conclusion == 'success' }} 13 | permissions: 14 | contents: write # Required to push changes to the repository 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v4 19 | # Fetch full history to detect changes 20 | with: 21 | fetch-depth: 0 22 | - name: Set up Rust 23 | uses: dtolnay/rust-toolchain@stable 24 | - name: Run stub_gen 25 | run: cargo run --bin stub_gen --features stubgen 26 | - name: Check for changes and commit 27 | id: check_changes 28 | run: | 29 | if [[ -n $(git status --porcelain) ]]; then 30 | echo "::warning title=Changes Detected::Changes detected in rpg_map.pyi" 31 | git config --local user.name "GitHub Actions" 32 | git config --local user.email "actions@github.com" 33 | git add rpg_map.pyi 34 | git commit -m "Update rpg_map.pyi" 35 | echo "changed=true" >> $GITHUB_OUTPUT 36 | else 37 | echo "changed=false" >> $GITHUB_OUTPUT 38 | fi 39 | 40 | - name: Push changes 41 | if: steps.check_changes.outputs.changed == 'true' 42 | uses: ad-m/github-push-action@master 43 | with: 44 | github_token: ${{ secrets.GITHUB_TOKEN }} 45 | branch: ${{ github.ref }} 46 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run Tests 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | env: 8 | CARGO_TERM_COLOR: always 9 | 10 | jobs: 11 | build_and_test: 12 | name: Rust project 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | 17 | - uses: Swatinem/rust-cache@v2 18 | 19 | - name: setup toolchain 20 | uses: hecrj/setup-rust-action@v1 21 | with: 22 | rust-version: stable 23 | 24 | - name: cargo test 25 | run: cargo test 26 | 27 | - name: rustfmt 28 | run: cargo fmt --all -- --check 29 | 30 | - name: clippy 31 | run: cargo clippy --all --tests -- -D warnings -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | grid.txt 3 | *.so 4 | __pycache__ 5 | .DS_Store 6 | src/tests/logs/* -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # https://docs.readthedocs.io/en/stable/config-file/v2.html#supported-settings 2 | 3 | version: 2 4 | 5 | sphinx: 6 | configuration: docs/source/conf.py 7 | builder: html 8 | 9 | build: 10 | os: "ubuntu-20.04" 11 | tools: 12 | python: "3.9" 13 | rust: "latest" 14 | 15 | python: 16 | install: 17 | - requirements: docs/requirements.txt 18 | - method: pip 19 | path: . 20 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rpg_map" 3 | version = "0.0.3" 4 | edition = "2021" 5 | 6 | [lib] 7 | name = "rpg_map" 8 | 9 | crate-type = ["cdylib", "rlib"] 10 | 11 | [dependencies] 12 | pyo3 = { version = "0.24.1", features = ["abi3-py39"] } 13 | geo = "0.30.0" 14 | pyo3-stub-gen = "0.7.0" 15 | workaround = { path = "workaround" } 16 | 17 | [dev-dependencies] 18 | image = "0.25.6" 19 | pyo3 = { version = "0.24.1", features = ["abi3-py39", "auto-initialize"] } 20 | 21 | [features] 22 | extension-module = ["pyo3/extension-module"] 23 | stubgen = [] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2025 Kile 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | rpg_map 2 | ======= 3 | 4 | .. image:: https://img.shields.io/pypi/v/rpg_map.svg 5 | :target: https://pypi.org/project/rpg_map/ 6 | :alt: PyPI version 7 | 8 | .. image:: https://readthedocs.org/projects/rpg-map/badge/?version=latest 9 | :target: https://rpg-map.readthedocs.io/en/latest/ 10 | :alt: Documentation Status 11 | 12 | A fast, zero-dependency Python library for visualizing exploration and movement across large pixel-based maps. Built in Rust for speed and efficiency, ``rpg_map`` is perfect for turn-based or real-time RPGs, strategy games, and D&D-style map reveals. 13 | 14 | Key Features 15 | ------------ 16 | 17 | - Hides unexplored areas of the map 18 | - Reveals areas as they're explored or passed through 19 | - Draws travel paths using A* pathfinding 20 | - Fast and lightweight with zero Python dependencies 21 | - Operates directly on pixel data 22 | - Customizable visual styles and highlight zones 23 | - Includes examples using static image processing and Pygame 24 | 25 | Install 26 | ------- 27 | 28 | Install via pip: 29 | 30 | .. code:: bash 31 | 32 | pip install rpg-map 33 | 34 | Documentation 35 | ------------- 36 | 37 | Full documentation and examples available at: https://rpg-map.readthedocs.io/ 38 | 39 | How It Works 40 | ------------ 41 | 42 | The library uses step-by-step image processing to reveal and annotate the map. Here's an overview of the process: 43 | 44 | **Code:** 45 | 46 | This is the code which was used to generate the final result of the example steps below: 47 | 48 | .. code:: python 49 | 50 | from rpg_map import Travel, Map, MapType, PathStyle, PathProgressDisplayType, PathDisplayType 51 | from PIL import Image 52 | 53 | LOCAL_DIR = "../test_assets/map.png" 54 | BACKGROUND_DIR = "../test_assets/background.png" 55 | GRID_SIZE = 20 56 | START, END = (198, 390), (172, 223) 57 | START_X, START_Y = START 58 | 59 | image = Image.open(LOCAL_DIR).convert("RGBA") 60 | # get image bytes 61 | image_bytes = list(image.tobytes()) 62 | background = Image.open(BACKGROUND_DIR).convert("RGBA") 63 | # get background bytes 64 | background_bytes = list(background.tobytes()) 65 | map = Map( 66 | image_bytes, 67 | image.size[0], 68 | image.size[1], 69 | GRID_SIZE, 70 | MapType.Limited, 71 | obstacles=[[(160, 240), (134, 253), (234, 257), (208, 239)]], 72 | ) 73 | 74 | travel = Travel(map, START, END) 75 | path_bits = Map.draw_background( 76 | map.with_dot(START_X, START_Y, (255, 0, 0, 255), 4).draw_path( 77 | travel, 78 | 1.0, 79 | 2, 80 | PathStyle.DottedWithOutline((255, 0, 0, 255), (255, 255, 255, 255)), 81 | ), 82 | background_bytes 83 | ) 84 | 85 | # Display the image 86 | image = Image.frombytes("RGBA", (image.width, image.height), path_bits) 87 | image.show() 88 | 89 | ---- 90 | 91 | **Steps:** 92 | 93 | 1. **Draw Obstacles** 94 | 95 | The ``Map`` class accepts an ``obstacles`` parameter which allows you to define N-sided polygons. These are rendered onto the map as solid barriers. 96 | 97 | .. image:: https://github.com/Kile/rpg_map/blob/master/assets/1.png?raw=true 98 | :width: 600 99 | 100 | 2. **Add Padding and Convert to Pathfinding Grid** 101 | 102 | Obstacles and map edges are padded and the image is converted into a binary map (0 = path, 1 = obstacle) for pathfinding. 103 | 104 | .. image:: https://github.com/Kile/rpg_map/blob/master/assets/2.png?raw=true 105 | :width: 600 106 | 107 | 3. **Pathfinding with A\*** 108 | 109 | The library uses the A* algorithm to find the shortest path from point A to point B. The path is drawn on the map using a customizable style. 110 | 111 | .. image:: https://github.com/Kile/rpg_map/blob/master/assets/3.png?raw=true 112 | :width: 600 113 | 114 | 4. **Draw Dots** 115 | 116 | Optional dots can be placed on the map (e.g., for points of interest, the player, markers). 117 | 118 | .. image:: https://github.com/Kile/rpg_map/blob/master/assets/4.png?raw=true 119 | :width: 600 120 | 121 | 5. **Divide into Grid Squares** 122 | 123 | The image is divided into equal squares based on the ``grid_size`` parameter. 124 | 125 | .. image:: https://github.com/Kile/rpg_map/blob/master/assets/5.png?raw=true 126 | :width: 600 127 | 128 | 6. **Reveal Explored Areas** 129 | 130 | A mask overlays the map. Areas near the travel path or manually unlocked via ``Map.unlock_point`` are revealed in circular zones. 131 | 132 | .. image:: https://github.com/Kile/rpg_map/blob/master/assets/6.png?raw=true 133 | :width: 600 134 | 135 | 7. **Fill Transparent Areas** 136 | 137 | Any remaining transparent pixels are filled with a background layer. 138 | 139 | .. image:: https://github.com/Kile/rpg_map/blob/master/assets/7.png?raw=true 140 | :width: 600 141 | 142 | 8. **Final Map Output** 143 | 144 | The completed map shows explored areas, paths, markers, and hidden regions yet to be discovered. 145 | 146 | .. image:: https://github.com/Kile/rpg_map/blob/master/assets/8.png?raw=true 147 | :width: 600 148 | 149 | Advanced Features 150 | ----------------- 151 | 152 | - You can define **special grid points** where the reveal radius is larger — perfect for cities or key landmarks. 153 | - The library supports **tons of styles** for different themes and usecases. 154 | 155 | .. image:: https://github.com/Kile/rpg_map/blob/master/assets/9.png?raw=true 156 | :width: 300 157 | .. image:: https://github.com/Kile/rpg_map/blob/master/assets/10.png?raw=true 158 | :width: 300 159 | .. image:: https://github.com/Kile/rpg_map/blob/master/assets/11.png?raw=true 160 | :width: 300 161 | .. image:: https://github.com/Kile/rpg_map/blob/master/assets/12.png?raw=true 162 | :width: 300 163 | 164 | Examples 165 | -------- 166 | 167 | Check out these demos: 168 | 169 | - `examples/static_poc.py `_ – Generate one image from your code 170 | - `examples/pygame_poc `_ – Interactively do pathfinding to wherever you click 171 | - `examples/readme.py `_ – The code used to generate the final image in the steps breakdown in this README 172 | 173 | 174 | Contributing & Development 175 | ========================== 176 | 177 | We welcome contributions and ideas! If you'd like to work on ``rpg_map`` locally, here's how to get started. 178 | 179 | Set Up the Development Environment 180 | ---------------------------------- 181 | 182 | 1. **Compile the Rust Extension Locally** 183 | 184 | Use ``maturin`` to build and install the Rust extension module in your local Python environment: 185 | 186 | .. code:: bash 187 | 188 | maturin develop --features "extension-module" 189 | 190 | 2. **Generate Python Typings** ( ``.pyi`` ) 191 | 192 | The library includes a binary that auto-generates Python type stubs. Run it with: 193 | 194 | .. code:: bash 195 | 196 | cargo run --bin stub_gen --features stubgen 197 | 198 | 3. **Build the Documentation** 199 | 200 | The documentation uses Sphinx and can be built locally as follows: 201 | 202 | .. code:: bash 203 | 204 | python3 -venv env && source env/bin/activate 205 | cd docs 206 | pip3 install -r requirements.txt 207 | sphinx-apidoc -o source/ ../ -f 208 | make html 209 | 210 | The output will be available in the `docs/build/html/` directory. 211 | 212 | Additional Notes 213 | ---------------- 214 | 215 | - The Rust project uses ``pyo3`` to create Python bindings — see ``Cargo.toml`` for feature flags and build options. 216 | - Type hints are manually generated via the ``stub_gen`` tool, ensuring compatibility with type checkers and IDEs. Interestingly sphinx uses the docs defined in the Rust code though, the `pyi` file is only for IDE type hinting when using the library. 217 | 218 | License 219 | ------- 220 | 221 | MIT 222 | 223 | -------------------------------------------------------------------------------- /assets/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/assets/1.png -------------------------------------------------------------------------------- /assets/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/assets/10.png -------------------------------------------------------------------------------- /assets/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/assets/11.png -------------------------------------------------------------------------------- /assets/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/assets/12.png -------------------------------------------------------------------------------- /assets/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/assets/2.png -------------------------------------------------------------------------------- /assets/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/assets/3.png -------------------------------------------------------------------------------- /assets/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/assets/4.png -------------------------------------------------------------------------------- /assets/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/assets/5.png -------------------------------------------------------------------------------- /assets/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/assets/6.png -------------------------------------------------------------------------------- /assets/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/assets/7.png -------------------------------------------------------------------------------- /assets/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/assets/8.png -------------------------------------------------------------------------------- /assets/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/assets/9.png -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= -v 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/build/doctrees/environment.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/doctrees/environment.pickle -------------------------------------------------------------------------------- /docs/build/doctrees/index.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/doctrees/index.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/modules.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/doctrees/modules.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/rpg_map.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/doctrees/rpg_map.doctree -------------------------------------------------------------------------------- /docs/build/html/.buildinfo: -------------------------------------------------------------------------------- 1 | # Sphinx build info version 1 2 | # This file records the configuration used when building these files. When it is not found, a full rebuild will be done. 3 | config: 8731396efb970edccd073f3b86fcbc81 4 | tags: 645f666f9bcd5a90fca523b33c5a78b7 5 | -------------------------------------------------------------------------------- /docs/build/html/.buildinfo.bak: -------------------------------------------------------------------------------- 1 | # Sphinx build info version 1 2 | # This file records the configuration used when building these files. When it is not found, a full rebuild will be done. 3 | config: 39dfc39b0134a0581211e03da15a5454 4 | tags: 645f666f9bcd5a90fca523b33c5a78b7 5 | -------------------------------------------------------------------------------- /docs/build/html/_modules/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Overview: module code — rpg_map 0.0.3 documentation 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 50 | 51 |
55 | 56 |
57 |
58 |
59 |
    60 |
  • 61 | 62 |
  • 63 |
  • 64 |
65 |
66 |
67 |
68 |
69 | 70 |

All modules for which code is available

71 | 73 | 74 |
75 |
76 |
77 | 78 |
79 | 80 |
81 |

© Copyright 2025, Kile.

82 |
83 | 84 | Built with Sphinx using a 85 | theme 86 | provided by Read the Docs. 87 | 88 | 89 |
90 |
91 |
92 |
93 |
94 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /docs/build/html/_sources/index.rst.txt: -------------------------------------------------------------------------------- 1 | .. rpg_map documentation master file, created by 2 | sphinx-quickstart on Fri Apr 11 14:02:10 2025. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | rpg_map documentation 7 | ================================================= 8 | 9 | .. include:: ../../README.rst 10 | :end-before: Contributing & Development 11 | 12 | .. toctree:: 13 | :maxdepth: 2 14 | :caption: Contents: 15 | 16 | rpg_map 17 | -------------------------------------------------------------------------------- /docs/build/html/_sources/modules.rst.txt: -------------------------------------------------------------------------------- 1 | rpg_map 2 | ======= 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | -------------------------------------------------------------------------------- /docs/build/html/_sources/rpg_map.rst.txt: -------------------------------------------------------------------------------- 1 | rpg\_map package 2 | ================ 3 | 4 | Module contents 5 | --------------- 6 | 7 | .. automodule:: rpg_map 8 | :members: 9 | :show-inheritance: 10 | :undoc-members: 11 | :imported-members: -------------------------------------------------------------------------------- /docs/build/html/_static/_sphinx_javascript_frameworks_compat.js: -------------------------------------------------------------------------------- 1 | /* Compatability shim for jQuery and underscores.js. 2 | * 3 | * Copyright Sphinx contributors 4 | * Released under the two clause BSD licence 5 | */ 6 | 7 | /** 8 | * small helper function to urldecode strings 9 | * 10 | * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL 11 | */ 12 | jQuery.urldecode = function(x) { 13 | if (!x) { 14 | return x 15 | } 16 | return decodeURIComponent(x.replace(/\+/g, ' ')); 17 | }; 18 | 19 | /** 20 | * small helper function to urlencode strings 21 | */ 22 | jQuery.urlencode = encodeURIComponent; 23 | 24 | /** 25 | * This function returns the parsed url parameters of the 26 | * current request. Multiple values per key are supported, 27 | * it will always return arrays of strings for the value parts. 28 | */ 29 | jQuery.getQueryParameters = function(s) { 30 | if (typeof s === 'undefined') 31 | s = document.location.search; 32 | var parts = s.substr(s.indexOf('?') + 1).split('&'); 33 | var result = {}; 34 | for (var i = 0; i < parts.length; i++) { 35 | var tmp = parts[i].split('=', 2); 36 | var key = jQuery.urldecode(tmp[0]); 37 | var value = jQuery.urldecode(tmp[1]); 38 | if (key in result) 39 | result[key].push(value); 40 | else 41 | result[key] = [value]; 42 | } 43 | return result; 44 | }; 45 | 46 | /** 47 | * highlight a given string on a jquery object by wrapping it in 48 | * span elements with the given class name. 49 | */ 50 | jQuery.fn.highlightText = function(text, className) { 51 | function highlight(node, addItems) { 52 | if (node.nodeType === 3) { 53 | var val = node.nodeValue; 54 | var pos = val.toLowerCase().indexOf(text); 55 | if (pos >= 0 && 56 | !jQuery(node.parentNode).hasClass(className) && 57 | !jQuery(node.parentNode).hasClass("nohighlight")) { 58 | var span; 59 | var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); 60 | if (isInSVG) { 61 | span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); 62 | } else { 63 | span = document.createElement("span"); 64 | span.className = className; 65 | } 66 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 67 | node.parentNode.insertBefore(span, node.parentNode.insertBefore( 68 | document.createTextNode(val.substr(pos + text.length)), 69 | node.nextSibling)); 70 | node.nodeValue = val.substr(0, pos); 71 | if (isInSVG) { 72 | var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); 73 | var bbox = node.parentElement.getBBox(); 74 | rect.x.baseVal.value = bbox.x; 75 | rect.y.baseVal.value = bbox.y; 76 | rect.width.baseVal.value = bbox.width; 77 | rect.height.baseVal.value = bbox.height; 78 | rect.setAttribute('class', className); 79 | addItems.push({ 80 | "parent": node.parentNode, 81 | "target": rect}); 82 | } 83 | } 84 | } 85 | else if (!jQuery(node).is("button, select, textarea")) { 86 | jQuery.each(node.childNodes, function() { 87 | highlight(this, addItems); 88 | }); 89 | } 90 | } 91 | var addItems = []; 92 | var result = this.each(function() { 93 | highlight(this, addItems); 94 | }); 95 | for (var i = 0; i < addItems.length; ++i) { 96 | jQuery(addItems[i].parent).before(addItems[i].target); 97 | } 98 | return result; 99 | }; 100 | 101 | /* 102 | * backward compatibility for jQuery.browser 103 | * This will be supported until firefox bug is fixed. 104 | */ 105 | if (!jQuery.browser) { 106 | jQuery.uaMatch = function(ua) { 107 | ua = ua.toLowerCase(); 108 | 109 | var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || 110 | /(webkit)[ \/]([\w.]+)/.exec(ua) || 111 | /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || 112 | /(msie) ([\w.]+)/.exec(ua) || 113 | ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || 114 | []; 115 | 116 | return { 117 | browser: match[ 1 ] || "", 118 | version: match[ 2 ] || "0" 119 | }; 120 | }; 121 | jQuery.browser = {}; 122 | jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; 123 | } 124 | -------------------------------------------------------------------------------- /docs/build/html/_static/basic.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Sphinx stylesheet -- basic theme. 3 | */ 4 | 5 | /* -- main layout ----------------------------------------------------------- */ 6 | 7 | div.clearer { 8 | clear: both; 9 | } 10 | 11 | div.section::after { 12 | display: block; 13 | content: ''; 14 | clear: left; 15 | } 16 | 17 | /* -- relbar ---------------------------------------------------------------- */ 18 | 19 | div.related { 20 | width: 100%; 21 | font-size: 90%; 22 | } 23 | 24 | div.related h3 { 25 | display: none; 26 | } 27 | 28 | div.related ul { 29 | margin: 0; 30 | padding: 0 0 0 10px; 31 | list-style: none; 32 | } 33 | 34 | div.related li { 35 | display: inline; 36 | } 37 | 38 | div.related li.right { 39 | float: right; 40 | margin-right: 5px; 41 | } 42 | 43 | /* -- sidebar --------------------------------------------------------------- */ 44 | 45 | div.sphinxsidebarwrapper { 46 | padding: 10px 5px 0 10px; 47 | } 48 | 49 | div.sphinxsidebar { 50 | float: left; 51 | width: 230px; 52 | margin-left: -100%; 53 | font-size: 90%; 54 | word-wrap: break-word; 55 | overflow-wrap : break-word; 56 | } 57 | 58 | div.sphinxsidebar ul { 59 | list-style: none; 60 | } 61 | 62 | div.sphinxsidebar ul ul, 63 | div.sphinxsidebar ul.want-points { 64 | margin-left: 20px; 65 | list-style: square; 66 | } 67 | 68 | div.sphinxsidebar ul ul { 69 | margin-top: 0; 70 | margin-bottom: 0; 71 | } 72 | 73 | div.sphinxsidebar form { 74 | margin-top: 10px; 75 | } 76 | 77 | div.sphinxsidebar input { 78 | border: 1px solid #98dbcc; 79 | font-family: sans-serif; 80 | font-size: 1em; 81 | } 82 | 83 | div.sphinxsidebar #searchbox form.search { 84 | overflow: hidden; 85 | } 86 | 87 | div.sphinxsidebar #searchbox input[type="text"] { 88 | float: left; 89 | width: 80%; 90 | padding: 0.25em; 91 | box-sizing: border-box; 92 | } 93 | 94 | div.sphinxsidebar #searchbox input[type="submit"] { 95 | float: left; 96 | width: 20%; 97 | border-left: none; 98 | padding: 0.25em; 99 | box-sizing: border-box; 100 | } 101 | 102 | 103 | img { 104 | border: 0; 105 | max-width: 100%; 106 | } 107 | 108 | /* -- search page ----------------------------------------------------------- */ 109 | 110 | ul.search { 111 | margin-top: 10px; 112 | } 113 | 114 | ul.search li { 115 | padding: 5px 0; 116 | } 117 | 118 | ul.search li a { 119 | font-weight: bold; 120 | } 121 | 122 | ul.search li p.context { 123 | color: #888; 124 | margin: 2px 0 0 30px; 125 | text-align: left; 126 | } 127 | 128 | ul.keywordmatches li.goodmatch a { 129 | font-weight: bold; 130 | } 131 | 132 | /* -- index page ------------------------------------------------------------ */ 133 | 134 | table.contentstable { 135 | width: 90%; 136 | margin-left: auto; 137 | margin-right: auto; 138 | } 139 | 140 | table.contentstable p.biglink { 141 | line-height: 150%; 142 | } 143 | 144 | a.biglink { 145 | font-size: 1.3em; 146 | } 147 | 148 | span.linkdescr { 149 | font-style: italic; 150 | padding-top: 5px; 151 | font-size: 90%; 152 | } 153 | 154 | /* -- general index --------------------------------------------------------- */ 155 | 156 | table.indextable { 157 | width: 100%; 158 | } 159 | 160 | table.indextable td { 161 | text-align: left; 162 | vertical-align: top; 163 | } 164 | 165 | table.indextable ul { 166 | margin-top: 0; 167 | margin-bottom: 0; 168 | list-style-type: none; 169 | } 170 | 171 | table.indextable > tbody > tr > td > ul { 172 | padding-left: 0em; 173 | } 174 | 175 | table.indextable tr.pcap { 176 | height: 10px; 177 | } 178 | 179 | table.indextable tr.cap { 180 | margin-top: 10px; 181 | background-color: #f2f2f2; 182 | } 183 | 184 | img.toggler { 185 | margin-right: 3px; 186 | margin-top: 3px; 187 | cursor: pointer; 188 | } 189 | 190 | div.modindex-jumpbox { 191 | border-top: 1px solid #ddd; 192 | border-bottom: 1px solid #ddd; 193 | margin: 1em 0 1em 0; 194 | padding: 0.4em; 195 | } 196 | 197 | div.genindex-jumpbox { 198 | border-top: 1px solid #ddd; 199 | border-bottom: 1px solid #ddd; 200 | margin: 1em 0 1em 0; 201 | padding: 0.4em; 202 | } 203 | 204 | /* -- domain module index --------------------------------------------------- */ 205 | 206 | table.modindextable td { 207 | padding: 2px; 208 | border-collapse: collapse; 209 | } 210 | 211 | /* -- general body styles --------------------------------------------------- */ 212 | 213 | div.body { 214 | min-width: 360px; 215 | max-width: 800px; 216 | } 217 | 218 | div.body p, div.body dd, div.body li, div.body blockquote { 219 | -moz-hyphens: auto; 220 | -ms-hyphens: auto; 221 | -webkit-hyphens: auto; 222 | hyphens: auto; 223 | } 224 | 225 | a.headerlink { 226 | visibility: hidden; 227 | } 228 | 229 | a:visited { 230 | color: #551A8B; 231 | } 232 | 233 | h1:hover > a.headerlink, 234 | h2:hover > a.headerlink, 235 | h3:hover > a.headerlink, 236 | h4:hover > a.headerlink, 237 | h5:hover > a.headerlink, 238 | h6:hover > a.headerlink, 239 | dt:hover > a.headerlink, 240 | caption:hover > a.headerlink, 241 | p.caption:hover > a.headerlink, 242 | div.code-block-caption:hover > a.headerlink { 243 | visibility: visible; 244 | } 245 | 246 | div.body p.caption { 247 | text-align: inherit; 248 | } 249 | 250 | div.body td { 251 | text-align: left; 252 | } 253 | 254 | .first { 255 | margin-top: 0 !important; 256 | } 257 | 258 | p.rubric { 259 | margin-top: 30px; 260 | font-weight: bold; 261 | } 262 | 263 | img.align-left, figure.align-left, .figure.align-left, object.align-left { 264 | clear: left; 265 | float: left; 266 | margin-right: 1em; 267 | } 268 | 269 | img.align-right, figure.align-right, .figure.align-right, object.align-right { 270 | clear: right; 271 | float: right; 272 | margin-left: 1em; 273 | } 274 | 275 | img.align-center, figure.align-center, .figure.align-center, object.align-center { 276 | display: block; 277 | margin-left: auto; 278 | margin-right: auto; 279 | } 280 | 281 | img.align-default, figure.align-default, .figure.align-default { 282 | display: block; 283 | margin-left: auto; 284 | margin-right: auto; 285 | } 286 | 287 | .align-left { 288 | text-align: left; 289 | } 290 | 291 | .align-center { 292 | text-align: center; 293 | } 294 | 295 | .align-default { 296 | text-align: center; 297 | } 298 | 299 | .align-right { 300 | text-align: right; 301 | } 302 | 303 | /* -- sidebars -------------------------------------------------------------- */ 304 | 305 | div.sidebar, 306 | aside.sidebar { 307 | margin: 0 0 0.5em 1em; 308 | border: 1px solid #ddb; 309 | padding: 7px; 310 | background-color: #ffe; 311 | width: 40%; 312 | float: right; 313 | clear: right; 314 | overflow-x: auto; 315 | } 316 | 317 | p.sidebar-title { 318 | font-weight: bold; 319 | } 320 | 321 | nav.contents, 322 | aside.topic, 323 | div.admonition, div.topic, blockquote { 324 | clear: left; 325 | } 326 | 327 | /* -- topics ---------------------------------------------------------------- */ 328 | 329 | nav.contents, 330 | aside.topic, 331 | div.topic { 332 | border: 1px solid #ccc; 333 | padding: 7px; 334 | margin: 10px 0 10px 0; 335 | } 336 | 337 | p.topic-title { 338 | font-size: 1.1em; 339 | font-weight: bold; 340 | margin-top: 10px; 341 | } 342 | 343 | /* -- admonitions ----------------------------------------------------------- */ 344 | 345 | div.admonition { 346 | margin-top: 10px; 347 | margin-bottom: 10px; 348 | padding: 7px; 349 | } 350 | 351 | div.admonition dt { 352 | font-weight: bold; 353 | } 354 | 355 | p.admonition-title { 356 | margin: 0px 10px 5px 0px; 357 | font-weight: bold; 358 | } 359 | 360 | div.body p.centered { 361 | text-align: center; 362 | margin-top: 25px; 363 | } 364 | 365 | /* -- content of sidebars/topics/admonitions -------------------------------- */ 366 | 367 | div.sidebar > :last-child, 368 | aside.sidebar > :last-child, 369 | nav.contents > :last-child, 370 | aside.topic > :last-child, 371 | div.topic > :last-child, 372 | div.admonition > :last-child { 373 | margin-bottom: 0; 374 | } 375 | 376 | div.sidebar::after, 377 | aside.sidebar::after, 378 | nav.contents::after, 379 | aside.topic::after, 380 | div.topic::after, 381 | div.admonition::after, 382 | blockquote::after { 383 | display: block; 384 | content: ''; 385 | clear: both; 386 | } 387 | 388 | /* -- tables ---------------------------------------------------------------- */ 389 | 390 | table.docutils { 391 | margin-top: 10px; 392 | margin-bottom: 10px; 393 | border: 0; 394 | border-collapse: collapse; 395 | } 396 | 397 | table.align-center { 398 | margin-left: auto; 399 | margin-right: auto; 400 | } 401 | 402 | table.align-default { 403 | margin-left: auto; 404 | margin-right: auto; 405 | } 406 | 407 | table caption span.caption-number { 408 | font-style: italic; 409 | } 410 | 411 | table caption span.caption-text { 412 | } 413 | 414 | table.docutils td, table.docutils th { 415 | padding: 1px 8px 1px 5px; 416 | border-top: 0; 417 | border-left: 0; 418 | border-right: 0; 419 | border-bottom: 1px solid #aaa; 420 | } 421 | 422 | th { 423 | text-align: left; 424 | padding-right: 5px; 425 | } 426 | 427 | table.citation { 428 | border-left: solid 1px gray; 429 | margin-left: 1px; 430 | } 431 | 432 | table.citation td { 433 | border-bottom: none; 434 | } 435 | 436 | th > :first-child, 437 | td > :first-child { 438 | margin-top: 0px; 439 | } 440 | 441 | th > :last-child, 442 | td > :last-child { 443 | margin-bottom: 0px; 444 | } 445 | 446 | /* -- figures --------------------------------------------------------------- */ 447 | 448 | div.figure, figure { 449 | margin: 0.5em; 450 | padding: 0.5em; 451 | } 452 | 453 | div.figure p.caption, figcaption { 454 | padding: 0.3em; 455 | } 456 | 457 | div.figure p.caption span.caption-number, 458 | figcaption span.caption-number { 459 | font-style: italic; 460 | } 461 | 462 | div.figure p.caption span.caption-text, 463 | figcaption span.caption-text { 464 | } 465 | 466 | /* -- field list styles ----------------------------------------------------- */ 467 | 468 | table.field-list td, table.field-list th { 469 | border: 0 !important; 470 | } 471 | 472 | .field-list ul { 473 | margin: 0; 474 | padding-left: 1em; 475 | } 476 | 477 | .field-list p { 478 | margin: 0; 479 | } 480 | 481 | .field-name { 482 | -moz-hyphens: manual; 483 | -ms-hyphens: manual; 484 | -webkit-hyphens: manual; 485 | hyphens: manual; 486 | } 487 | 488 | /* -- hlist styles ---------------------------------------------------------- */ 489 | 490 | table.hlist { 491 | margin: 1em 0; 492 | } 493 | 494 | table.hlist td { 495 | vertical-align: top; 496 | } 497 | 498 | /* -- object description styles --------------------------------------------- */ 499 | 500 | .sig { 501 | font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; 502 | } 503 | 504 | .sig-name, code.descname { 505 | background-color: transparent; 506 | font-weight: bold; 507 | } 508 | 509 | .sig-name { 510 | font-size: 1.1em; 511 | } 512 | 513 | code.descname { 514 | font-size: 1.2em; 515 | } 516 | 517 | .sig-prename, code.descclassname { 518 | background-color: transparent; 519 | } 520 | 521 | .optional { 522 | font-size: 1.3em; 523 | } 524 | 525 | .sig-paren { 526 | font-size: larger; 527 | } 528 | 529 | .sig-param.n { 530 | font-style: italic; 531 | } 532 | 533 | /* C++ specific styling */ 534 | 535 | .sig-inline.c-texpr, 536 | .sig-inline.cpp-texpr { 537 | font-family: unset; 538 | } 539 | 540 | .sig.c .k, .sig.c .kt, 541 | .sig.cpp .k, .sig.cpp .kt { 542 | color: #0033B3; 543 | } 544 | 545 | .sig.c .m, 546 | .sig.cpp .m { 547 | color: #1750EB; 548 | } 549 | 550 | .sig.c .s, .sig.c .sc, 551 | .sig.cpp .s, .sig.cpp .sc { 552 | color: #067D17; 553 | } 554 | 555 | 556 | /* -- other body styles ----------------------------------------------------- */ 557 | 558 | ol.arabic { 559 | list-style: decimal; 560 | } 561 | 562 | ol.loweralpha { 563 | list-style: lower-alpha; 564 | } 565 | 566 | ol.upperalpha { 567 | list-style: upper-alpha; 568 | } 569 | 570 | ol.lowerroman { 571 | list-style: lower-roman; 572 | } 573 | 574 | ol.upperroman { 575 | list-style: upper-roman; 576 | } 577 | 578 | :not(li) > ol > li:first-child > :first-child, 579 | :not(li) > ul > li:first-child > :first-child { 580 | margin-top: 0px; 581 | } 582 | 583 | :not(li) > ol > li:last-child > :last-child, 584 | :not(li) > ul > li:last-child > :last-child { 585 | margin-bottom: 0px; 586 | } 587 | 588 | ol.simple ol p, 589 | ol.simple ul p, 590 | ul.simple ol p, 591 | ul.simple ul p { 592 | margin-top: 0; 593 | } 594 | 595 | ol.simple > li:not(:first-child) > p, 596 | ul.simple > li:not(:first-child) > p { 597 | margin-top: 0; 598 | } 599 | 600 | ol.simple p, 601 | ul.simple p { 602 | margin-bottom: 0; 603 | } 604 | 605 | aside.footnote > span, 606 | div.citation > span { 607 | float: left; 608 | } 609 | aside.footnote > span:last-of-type, 610 | div.citation > span:last-of-type { 611 | padding-right: 0.5em; 612 | } 613 | aside.footnote > p { 614 | margin-left: 2em; 615 | } 616 | div.citation > p { 617 | margin-left: 4em; 618 | } 619 | aside.footnote > p:last-of-type, 620 | div.citation > p:last-of-type { 621 | margin-bottom: 0em; 622 | } 623 | aside.footnote > p:last-of-type:after, 624 | div.citation > p:last-of-type:after { 625 | content: ""; 626 | clear: both; 627 | } 628 | 629 | dl.field-list { 630 | display: grid; 631 | grid-template-columns: fit-content(30%) auto; 632 | } 633 | 634 | dl.field-list > dt { 635 | font-weight: bold; 636 | word-break: break-word; 637 | padding-left: 0.5em; 638 | padding-right: 5px; 639 | } 640 | 641 | dl.field-list > dd { 642 | padding-left: 0.5em; 643 | margin-top: 0em; 644 | margin-left: 0em; 645 | margin-bottom: 0em; 646 | } 647 | 648 | dl { 649 | margin-bottom: 15px; 650 | } 651 | 652 | dd > :first-child { 653 | margin-top: 0px; 654 | } 655 | 656 | dd ul, dd table { 657 | margin-bottom: 10px; 658 | } 659 | 660 | dd { 661 | margin-top: 3px; 662 | margin-bottom: 10px; 663 | margin-left: 30px; 664 | } 665 | 666 | .sig dd { 667 | margin-top: 0px; 668 | margin-bottom: 0px; 669 | } 670 | 671 | .sig dl { 672 | margin-top: 0px; 673 | margin-bottom: 0px; 674 | } 675 | 676 | dl > dd:last-child, 677 | dl > dd:last-child > :last-child { 678 | margin-bottom: 0; 679 | } 680 | 681 | dt:target, span.highlighted { 682 | background-color: #fbe54e; 683 | } 684 | 685 | rect.highlighted { 686 | fill: #fbe54e; 687 | } 688 | 689 | dl.glossary dt { 690 | font-weight: bold; 691 | font-size: 1.1em; 692 | } 693 | 694 | .versionmodified { 695 | font-style: italic; 696 | } 697 | 698 | .system-message { 699 | background-color: #fda; 700 | padding: 5px; 701 | border: 3px solid red; 702 | } 703 | 704 | .footnote:target { 705 | background-color: #ffa; 706 | } 707 | 708 | .line-block { 709 | display: block; 710 | margin-top: 1em; 711 | margin-bottom: 1em; 712 | } 713 | 714 | .line-block .line-block { 715 | margin-top: 0; 716 | margin-bottom: 0; 717 | margin-left: 1.5em; 718 | } 719 | 720 | .guilabel, .menuselection { 721 | font-family: sans-serif; 722 | } 723 | 724 | .accelerator { 725 | text-decoration: underline; 726 | } 727 | 728 | .classifier { 729 | font-style: oblique; 730 | } 731 | 732 | .classifier:before { 733 | font-style: normal; 734 | margin: 0 0.5em; 735 | content: ":"; 736 | display: inline-block; 737 | } 738 | 739 | abbr, acronym { 740 | border-bottom: dotted 1px; 741 | cursor: help; 742 | } 743 | 744 | /* -- code displays --------------------------------------------------------- */ 745 | 746 | pre { 747 | overflow: auto; 748 | overflow-y: hidden; /* fixes display issues on Chrome browsers */ 749 | } 750 | 751 | pre, div[class*="highlight-"] { 752 | clear: both; 753 | } 754 | 755 | span.pre { 756 | -moz-hyphens: none; 757 | -ms-hyphens: none; 758 | -webkit-hyphens: none; 759 | hyphens: none; 760 | white-space: nowrap; 761 | } 762 | 763 | div[class*="highlight-"] { 764 | margin: 1em 0; 765 | } 766 | 767 | td.linenos pre { 768 | border: 0; 769 | background-color: transparent; 770 | color: #aaa; 771 | } 772 | 773 | table.highlighttable { 774 | display: block; 775 | } 776 | 777 | table.highlighttable tbody { 778 | display: block; 779 | } 780 | 781 | table.highlighttable tr { 782 | display: flex; 783 | } 784 | 785 | table.highlighttable td { 786 | margin: 0; 787 | padding: 0; 788 | } 789 | 790 | table.highlighttable td.linenos { 791 | padding-right: 0.5em; 792 | } 793 | 794 | table.highlighttable td.code { 795 | flex: 1; 796 | overflow: hidden; 797 | } 798 | 799 | .highlight .hll { 800 | display: block; 801 | } 802 | 803 | div.highlight pre, 804 | table.highlighttable pre { 805 | margin: 0; 806 | } 807 | 808 | div.code-block-caption + div { 809 | margin-top: 0; 810 | } 811 | 812 | div.code-block-caption { 813 | margin-top: 1em; 814 | padding: 2px 5px; 815 | font-size: small; 816 | } 817 | 818 | div.code-block-caption code { 819 | background-color: transparent; 820 | } 821 | 822 | table.highlighttable td.linenos, 823 | span.linenos, 824 | div.highlight span.gp { /* gp: Generic.Prompt */ 825 | user-select: none; 826 | -webkit-user-select: text; /* Safari fallback only */ 827 | -webkit-user-select: none; /* Chrome/Safari */ 828 | -moz-user-select: none; /* Firefox */ 829 | -ms-user-select: none; /* IE10+ */ 830 | } 831 | 832 | div.code-block-caption span.caption-number { 833 | padding: 0.1em 0.3em; 834 | font-style: italic; 835 | } 836 | 837 | div.code-block-caption span.caption-text { 838 | } 839 | 840 | div.literal-block-wrapper { 841 | margin: 1em 0; 842 | } 843 | 844 | code.xref, a code { 845 | background-color: transparent; 846 | font-weight: bold; 847 | } 848 | 849 | h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { 850 | background-color: transparent; 851 | } 852 | 853 | .viewcode-link { 854 | float: right; 855 | } 856 | 857 | .viewcode-back { 858 | float: right; 859 | font-family: sans-serif; 860 | } 861 | 862 | div.viewcode-block:target { 863 | margin: -1px -10px; 864 | padding: 0 10px; 865 | } 866 | 867 | /* -- math display ---------------------------------------------------------- */ 868 | 869 | img.math { 870 | vertical-align: middle; 871 | } 872 | 873 | div.body div.math p { 874 | text-align: center; 875 | } 876 | 877 | span.eqno { 878 | float: right; 879 | } 880 | 881 | span.eqno a.headerlink { 882 | position: absolute; 883 | z-index: 1; 884 | } 885 | 886 | div.math:hover a.headerlink { 887 | visibility: visible; 888 | } 889 | 890 | /* -- printout stylesheet --------------------------------------------------- */ 891 | 892 | @media print { 893 | div.document, 894 | div.documentwrapper, 895 | div.bodywrapper { 896 | margin: 0 !important; 897 | width: 100%; 898 | } 899 | 900 | div.sphinxsidebar, 901 | div.related, 902 | div.footer, 903 | #top-link { 904 | display: none; 905 | } 906 | } -------------------------------------------------------------------------------- /docs/build/html/_static/css/badge_only.css: -------------------------------------------------------------------------------- 1 | .clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions .rst-other-versions .rtd-current-item{font-weight:700}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}#flyout-search-form{padding:6px} -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/Roboto-Slab-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/css/fonts/Roboto-Slab-Bold.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/Roboto-Slab-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/css/fonts/Roboto-Slab-Bold.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/Roboto-Slab-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/css/fonts/Roboto-Slab-Regular.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/Roboto-Slab-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/css/fonts/Roboto-Slab-Regular.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/css/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/css/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/css/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/css/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-bold-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/css/fonts/lato-bold-italic.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-bold-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/css/fonts/lato-bold-italic.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/css/fonts/lato-bold.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/css/fonts/lato-bold.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-normal-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/css/fonts/lato-normal-italic.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-normal-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/css/fonts/lato-normal-italic.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-normal.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/css/fonts/lato-normal.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-normal.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/css/fonts/lato-normal.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/doctools.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Base JavaScript utilities for all Sphinx HTML documentation. 3 | */ 4 | "use strict"; 5 | 6 | const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ 7 | "TEXTAREA", 8 | "INPUT", 9 | "SELECT", 10 | "BUTTON", 11 | ]); 12 | 13 | const _ready = (callback) => { 14 | if (document.readyState !== "loading") { 15 | callback(); 16 | } else { 17 | document.addEventListener("DOMContentLoaded", callback); 18 | } 19 | }; 20 | 21 | /** 22 | * Small JavaScript module for the documentation. 23 | */ 24 | const Documentation = { 25 | init: () => { 26 | Documentation.initDomainIndexTable(); 27 | Documentation.initOnKeyListeners(); 28 | }, 29 | 30 | /** 31 | * i18n support 32 | */ 33 | TRANSLATIONS: {}, 34 | PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), 35 | LOCALE: "unknown", 36 | 37 | // gettext and ngettext don't access this so that the functions 38 | // can safely bound to a different name (_ = Documentation.gettext) 39 | gettext: (string) => { 40 | const translated = Documentation.TRANSLATIONS[string]; 41 | switch (typeof translated) { 42 | case "undefined": 43 | return string; // no translation 44 | case "string": 45 | return translated; // translation exists 46 | default: 47 | return translated[0]; // (singular, plural) translation tuple exists 48 | } 49 | }, 50 | 51 | ngettext: (singular, plural, n) => { 52 | const translated = Documentation.TRANSLATIONS[singular]; 53 | if (typeof translated !== "undefined") 54 | return translated[Documentation.PLURAL_EXPR(n)]; 55 | return n === 1 ? singular : plural; 56 | }, 57 | 58 | addTranslations: (catalog) => { 59 | Object.assign(Documentation.TRANSLATIONS, catalog.messages); 60 | Documentation.PLURAL_EXPR = new Function( 61 | "n", 62 | `return (${catalog.plural_expr})` 63 | ); 64 | Documentation.LOCALE = catalog.locale; 65 | }, 66 | 67 | /** 68 | * helper function to focus on search bar 69 | */ 70 | focusSearchBar: () => { 71 | document.querySelectorAll("input[name=q]")[0]?.focus(); 72 | }, 73 | 74 | /** 75 | * Initialise the domain index toggle buttons 76 | */ 77 | initDomainIndexTable: () => { 78 | const toggler = (el) => { 79 | const idNumber = el.id.substr(7); 80 | const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); 81 | if (el.src.substr(-9) === "minus.png") { 82 | el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; 83 | toggledRows.forEach((el) => (el.style.display = "none")); 84 | } else { 85 | el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; 86 | toggledRows.forEach((el) => (el.style.display = "")); 87 | } 88 | }; 89 | 90 | const togglerElements = document.querySelectorAll("img.toggler"); 91 | togglerElements.forEach((el) => 92 | el.addEventListener("click", (event) => toggler(event.currentTarget)) 93 | ); 94 | togglerElements.forEach((el) => (el.style.display = "")); 95 | if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); 96 | }, 97 | 98 | initOnKeyListeners: () => { 99 | // only install a listener if it is really needed 100 | if ( 101 | !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && 102 | !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS 103 | ) 104 | return; 105 | 106 | document.addEventListener("keydown", (event) => { 107 | // bail for input elements 108 | if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; 109 | // bail with special keys 110 | if (event.altKey || event.ctrlKey || event.metaKey) return; 111 | 112 | if (!event.shiftKey) { 113 | switch (event.key) { 114 | case "ArrowLeft": 115 | if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; 116 | 117 | const prevLink = document.querySelector('link[rel="prev"]'); 118 | if (prevLink && prevLink.href) { 119 | window.location.href = prevLink.href; 120 | event.preventDefault(); 121 | } 122 | break; 123 | case "ArrowRight": 124 | if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; 125 | 126 | const nextLink = document.querySelector('link[rel="next"]'); 127 | if (nextLink && nextLink.href) { 128 | window.location.href = nextLink.href; 129 | event.preventDefault(); 130 | } 131 | break; 132 | } 133 | } 134 | 135 | // some keyboard layouts may need Shift to get / 136 | switch (event.key) { 137 | case "/": 138 | if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; 139 | Documentation.focusSearchBar(); 140 | event.preventDefault(); 141 | } 142 | }); 143 | }, 144 | }; 145 | 146 | // quick alias for translations 147 | const _ = Documentation.gettext; 148 | 149 | _ready(Documentation.init); 150 | -------------------------------------------------------------------------------- /docs/build/html/_static/documentation_options.js: -------------------------------------------------------------------------------- 1 | const DOCUMENTATION_OPTIONS = { 2 | VERSION: '0.0.3', 3 | LANGUAGE: 'en', 4 | COLLAPSE_INDEX: false, 5 | BUILDER: 'html', 6 | FILE_SUFFIX: '.html', 7 | LINK_SUFFIX: '.html', 8 | HAS_SOURCE: true, 9 | SOURCELINK_SUFFIX: '.txt', 10 | NAVIGATION_WITH_KEYS: false, 11 | SHOW_SEARCH_SUMMARY: true, 12 | ENABLE_SEARCH_SHORTCUTS: true, 13 | }; -------------------------------------------------------------------------------- /docs/build/html/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/file.png -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/fonts/Lato/lato-bold.eot -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/fonts/Lato/lato-bold.ttf -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/fonts/Lato/lato-bold.woff -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/fonts/Lato/lato-bold.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-bolditalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/fonts/Lato/lato-bolditalic.eot -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-bolditalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/fonts/Lato/lato-bolditalic.ttf -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-bolditalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/fonts/Lato/lato-bolditalic.woff -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-bolditalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/fonts/Lato/lato-bolditalic.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/fonts/Lato/lato-italic.eot -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/fonts/Lato/lato-italic.ttf -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/fonts/Lato/lato-italic.woff -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/fonts/Lato/lato-italic.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/fonts/Lato/lato-regular.eot -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/fonts/Lato/lato-regular.ttf -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/fonts/Lato/lato-regular.woff -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/fonts/Lato/lato-regular.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/js/badge_only.js: -------------------------------------------------------------------------------- 1 | !function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=4)}({4:function(e,t,r){}}); -------------------------------------------------------------------------------- /docs/build/html/_static/js/theme.js: -------------------------------------------------------------------------------- 1 | !function(n){var e={};function t(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return n[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,i){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:i})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var i=Object.create(null);if(t.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(i,o,function(e){return n[e]}.bind(null,o));return i},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){t(1),n.exports=t(3)},function(n,e,t){(function(){var e="undefined"!=typeof window?window.jQuery:t(2);n.exports.ThemeNav={navBar:null,win:null,winScroll:!1,winResize:!1,linkScroll:!1,winPosition:0,winHeight:null,docHeight:null,isRunning:!1,enable:function(n){var t=this;void 0===n&&(n=!0),t.isRunning||(t.isRunning=!0,e((function(e){t.init(e),t.reset(),t.win.on("hashchange",t.reset),n&&t.win.on("scroll",(function(){t.linkScroll||t.winScroll||(t.winScroll=!0,requestAnimationFrame((function(){t.onScroll()})))})),t.win.on("resize",(function(){t.winResize||(t.winResize=!0,requestAnimationFrame((function(){t.onResize()})))})),t.onResize()})))},enableSticky:function(){this.enable(!0)},init:function(n){n(document);var e=this;this.navBar=n("div.wy-side-scroll:first"),this.win=n(window),n(document).on("click","[data-toggle='wy-nav-top']",(function(){n("[data-toggle='wy-nav-shift']").toggleClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift")})).on("click",".wy-menu-vertical .current ul li a",(function(){var t=n(this);n("[data-toggle='wy-nav-shift']").removeClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift"),e.toggleCurrent(t),e.hashChange()})).on("click","[data-toggle='rst-current-version']",(function(){n("[data-toggle='rst-versions']").toggleClass("shift-up")})),n("table.docutils:not(.field-list,.footnote,.citation)").wrap("
"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t a.language.name.localeCompare(b.language.name)); 14 | 15 | const languagesHTML = ` 16 |
17 |
Languages
18 | ${languages 19 | .map( 20 | (translation) => ` 21 |
22 | ${translation.language.code} 23 |
24 | `, 25 | ) 26 | .join("\n")} 27 |
28 | `; 29 | return languagesHTML; 30 | } 31 | 32 | function renderVersions(config) { 33 | if (!config.versions.active.length) { 34 | return ""; 35 | } 36 | const versionsHTML = ` 37 |
38 |
Versions
39 | ${config.versions.active 40 | .map( 41 | (version) => ` 42 |
43 | ${version.slug} 44 |
45 | `, 46 | ) 47 | .join("\n")} 48 |
49 | `; 50 | return versionsHTML; 51 | } 52 | 53 | function renderDownloads(config) { 54 | if (!Object.keys(config.versions.current.downloads).length) { 55 | return ""; 56 | } 57 | const downloadsNameDisplay = { 58 | pdf: "PDF", 59 | epub: "Epub", 60 | htmlzip: "HTML", 61 | }; 62 | 63 | const downloadsHTML = ` 64 |
65 |
Downloads
66 | ${Object.entries(config.versions.current.downloads) 67 | .map( 68 | ([name, url]) => ` 69 |
70 | ${downloadsNameDisplay[name]} 71 |
72 | `, 73 | ) 74 | .join("\n")} 75 |
76 | `; 77 | return downloadsHTML; 78 | } 79 | 80 | document.addEventListener("readthedocs-addons-data-ready", function (event) { 81 | const config = event.detail.data(); 82 | 83 | const flyout = ` 84 |
85 | 86 | Read the Docs 87 | v: ${config.versions.current.slug} 88 | 89 | 90 |
91 |
92 | ${renderLanguages(config)} 93 | ${renderVersions(config)} 94 | ${renderDownloads(config)} 95 |
96 |
On Read the Docs
97 |
98 | Project Home 99 |
100 |
101 | Builds 102 |
103 |
104 | Downloads 105 |
106 |
107 |
108 |
Search
109 |
110 |
111 | 118 |
119 |
120 |
121 |
122 | 123 | Hosted by Read the Docs 124 | 125 |
126 |
127 | `; 128 | 129 | // Inject the generated flyout into the body HTML element. 130 | document.body.insertAdjacentHTML("beforeend", flyout); 131 | 132 | // Trigger the Read the Docs Addons Search modal when clicking on the "Search docs" input from inside the flyout. 133 | document 134 | .querySelector("#flyout-search-form") 135 | .addEventListener("focusin", () => { 136 | const event = new CustomEvent("readthedocs-search-show"); 137 | document.dispatchEvent(event); 138 | }); 139 | }) 140 | } 141 | 142 | if (themeLanguageSelector || themeVersionSelector) { 143 | function onSelectorSwitch(event) { 144 | const option = event.target.selectedIndex; 145 | const item = event.target.options[option]; 146 | window.location.href = item.dataset.url; 147 | } 148 | 149 | document.addEventListener("readthedocs-addons-data-ready", function (event) { 150 | const config = event.detail.data(); 151 | 152 | const versionSwitch = document.querySelector( 153 | "div.switch-menus > div.version-switch", 154 | ); 155 | if (themeVersionSelector) { 156 | let versions = config.versions.active; 157 | if (config.versions.current.hidden || config.versions.current.type === "external") { 158 | versions.unshift(config.versions.current); 159 | } 160 | const versionSelect = ` 161 | 174 | `; 175 | 176 | versionSwitch.innerHTML = versionSelect; 177 | versionSwitch.firstElementChild.addEventListener("change", onSelectorSwitch); 178 | } 179 | 180 | const languageSwitch = document.querySelector( 181 | "div.switch-menus > div.language-switch", 182 | ); 183 | 184 | if (themeLanguageSelector) { 185 | if (config.projects.translations.length) { 186 | // Add the current language to the options on the selector 187 | let languages = config.projects.translations.concat( 188 | config.projects.current, 189 | ); 190 | languages = languages.sort((a, b) => 191 | a.language.name.localeCompare(b.language.name), 192 | ); 193 | 194 | const languageSelect = ` 195 | 208 | `; 209 | 210 | languageSwitch.innerHTML = languageSelect; 211 | languageSwitch.firstElementChild.addEventListener("change", onSelectorSwitch); 212 | } 213 | else { 214 | languageSwitch.remove(); 215 | } 216 | } 217 | }); 218 | } 219 | 220 | document.addEventListener("readthedocs-addons-data-ready", function (event) { 221 | // Trigger the Read the Docs Addons Search modal when clicking on "Search docs" input from the topnav. 222 | document 223 | .querySelector("[role='search'] input") 224 | .addEventListener("focusin", () => { 225 | const event = new CustomEvent("readthedocs-search-show"); 226 | document.dispatchEvent(event); 227 | }); 228 | }); -------------------------------------------------------------------------------- /docs/build/html/_static/language_data.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This script contains the language-specific data used by searchtools.js, 3 | * namely the list of stopwords, stemmer, scorer and splitter. 4 | */ 5 | 6 | var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; 7 | 8 | 9 | /* Non-minified version is copied as a separate JS file, if available */ 10 | 11 | /** 12 | * Porter Stemmer 13 | */ 14 | var Stemmer = function() { 15 | 16 | var step2list = { 17 | ational: 'ate', 18 | tional: 'tion', 19 | enci: 'ence', 20 | anci: 'ance', 21 | izer: 'ize', 22 | bli: 'ble', 23 | alli: 'al', 24 | entli: 'ent', 25 | eli: 'e', 26 | ousli: 'ous', 27 | ization: 'ize', 28 | ation: 'ate', 29 | ator: 'ate', 30 | alism: 'al', 31 | iveness: 'ive', 32 | fulness: 'ful', 33 | ousness: 'ous', 34 | aliti: 'al', 35 | iviti: 'ive', 36 | biliti: 'ble', 37 | logi: 'log' 38 | }; 39 | 40 | var step3list = { 41 | icate: 'ic', 42 | ative: '', 43 | alize: 'al', 44 | iciti: 'ic', 45 | ical: 'ic', 46 | ful: '', 47 | ness: '' 48 | }; 49 | 50 | var c = "[^aeiou]"; // consonant 51 | var v = "[aeiouy]"; // vowel 52 | var C = c + "[^aeiouy]*"; // consonant sequence 53 | var V = v + "[aeiou]*"; // vowel sequence 54 | 55 | var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 56 | var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 57 | var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 58 | var s_v = "^(" + C + ")?" + v; // vowel in stem 59 | 60 | this.stemWord = function (w) { 61 | var stem; 62 | var suffix; 63 | var firstch; 64 | var origword = w; 65 | 66 | if (w.length < 3) 67 | return w; 68 | 69 | var re; 70 | var re2; 71 | var re3; 72 | var re4; 73 | 74 | firstch = w.substr(0,1); 75 | if (firstch == "y") 76 | w = firstch.toUpperCase() + w.substr(1); 77 | 78 | // Step 1a 79 | re = /^(.+?)(ss|i)es$/; 80 | re2 = /^(.+?)([^s])s$/; 81 | 82 | if (re.test(w)) 83 | w = w.replace(re,"$1$2"); 84 | else if (re2.test(w)) 85 | w = w.replace(re2,"$1$2"); 86 | 87 | // Step 1b 88 | re = /^(.+?)eed$/; 89 | re2 = /^(.+?)(ed|ing)$/; 90 | if (re.test(w)) { 91 | var fp = re.exec(w); 92 | re = new RegExp(mgr0); 93 | if (re.test(fp[1])) { 94 | re = /.$/; 95 | w = w.replace(re,""); 96 | } 97 | } 98 | else if (re2.test(w)) { 99 | var fp = re2.exec(w); 100 | stem = fp[1]; 101 | re2 = new RegExp(s_v); 102 | if (re2.test(stem)) { 103 | w = stem; 104 | re2 = /(at|bl|iz)$/; 105 | re3 = new RegExp("([^aeiouylsz])\\1$"); 106 | re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); 107 | if (re2.test(w)) 108 | w = w + "e"; 109 | else if (re3.test(w)) { 110 | re = /.$/; 111 | w = w.replace(re,""); 112 | } 113 | else if (re4.test(w)) 114 | w = w + "e"; 115 | } 116 | } 117 | 118 | // Step 1c 119 | re = /^(.+?)y$/; 120 | if (re.test(w)) { 121 | var fp = re.exec(w); 122 | stem = fp[1]; 123 | re = new RegExp(s_v); 124 | if (re.test(stem)) 125 | w = stem + "i"; 126 | } 127 | 128 | // Step 2 129 | re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; 130 | if (re.test(w)) { 131 | var fp = re.exec(w); 132 | stem = fp[1]; 133 | suffix = fp[2]; 134 | re = new RegExp(mgr0); 135 | if (re.test(stem)) 136 | w = stem + step2list[suffix]; 137 | } 138 | 139 | // Step 3 140 | re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; 141 | if (re.test(w)) { 142 | var fp = re.exec(w); 143 | stem = fp[1]; 144 | suffix = fp[2]; 145 | re = new RegExp(mgr0); 146 | if (re.test(stem)) 147 | w = stem + step3list[suffix]; 148 | } 149 | 150 | // Step 4 151 | re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; 152 | re2 = /^(.+?)(s|t)(ion)$/; 153 | if (re.test(w)) { 154 | var fp = re.exec(w); 155 | stem = fp[1]; 156 | re = new RegExp(mgr1); 157 | if (re.test(stem)) 158 | w = stem; 159 | } 160 | else if (re2.test(w)) { 161 | var fp = re2.exec(w); 162 | stem = fp[1] + fp[2]; 163 | re2 = new RegExp(mgr1); 164 | if (re2.test(stem)) 165 | w = stem; 166 | } 167 | 168 | // Step 5 169 | re = /^(.+?)e$/; 170 | if (re.test(w)) { 171 | var fp = re.exec(w); 172 | stem = fp[1]; 173 | re = new RegExp(mgr1); 174 | re2 = new RegExp(meq1); 175 | re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); 176 | if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) 177 | w = stem; 178 | } 179 | re = /ll$/; 180 | re2 = new RegExp(mgr1); 181 | if (re.test(w) && re2.test(w)) { 182 | re = /.$/; 183 | w = w.replace(re,""); 184 | } 185 | 186 | // and turn initial Y back to y 187 | if (firstch == "y") 188 | w = firstch.toLowerCase() + w.substr(1); 189 | return w; 190 | } 191 | } 192 | 193 | -------------------------------------------------------------------------------- /docs/build/html/_static/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/minus.png -------------------------------------------------------------------------------- /docs/build/html/_static/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/_static/plus.png -------------------------------------------------------------------------------- /docs/build/html/_static/pygments.css: -------------------------------------------------------------------------------- 1 | pre { line-height: 125%; } 2 | td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } 3 | span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } 4 | td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } 5 | span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } 6 | .highlight .hll { background-color: #ffffcc } 7 | .highlight { background: #f8f8f8; } 8 | .highlight .c { color: #3D7B7B; font-style: italic } /* Comment */ 9 | .highlight .err { border: 1px solid #F00 } /* Error */ 10 | .highlight .k { color: #008000; font-weight: bold } /* Keyword */ 11 | .highlight .o { color: #666 } /* Operator */ 12 | .highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */ 13 | .highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */ 14 | .highlight .cp { color: #9C6500 } /* Comment.Preproc */ 15 | .highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */ 16 | .highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */ 17 | .highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */ 18 | .highlight .gd { color: #A00000 } /* Generic.Deleted */ 19 | .highlight .ge { font-style: italic } /* Generic.Emph */ 20 | .highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ 21 | .highlight .gr { color: #E40000 } /* Generic.Error */ 22 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 23 | .highlight .gi { color: #008400 } /* Generic.Inserted */ 24 | .highlight .go { color: #717171 } /* Generic.Output */ 25 | .highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ 26 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 27 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 28 | .highlight .gt { color: #04D } /* Generic.Traceback */ 29 | .highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ 30 | .highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ 31 | .highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ 32 | .highlight .kp { color: #008000 } /* Keyword.Pseudo */ 33 | .highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ 34 | .highlight .kt { color: #B00040 } /* Keyword.Type */ 35 | .highlight .m { color: #666 } /* Literal.Number */ 36 | .highlight .s { color: #BA2121 } /* Literal.String */ 37 | .highlight .na { color: #687822 } /* Name.Attribute */ 38 | .highlight .nb { color: #008000 } /* Name.Builtin */ 39 | .highlight .nc { color: #00F; font-weight: bold } /* Name.Class */ 40 | .highlight .no { color: #800 } /* Name.Constant */ 41 | .highlight .nd { color: #A2F } /* Name.Decorator */ 42 | .highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */ 43 | .highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */ 44 | .highlight .nf { color: #00F } /* Name.Function */ 45 | .highlight .nl { color: #767600 } /* Name.Label */ 46 | .highlight .nn { color: #00F; font-weight: bold } /* Name.Namespace */ 47 | .highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ 48 | .highlight .nv { color: #19177C } /* Name.Variable */ 49 | .highlight .ow { color: #A2F; font-weight: bold } /* Operator.Word */ 50 | .highlight .w { color: #BBB } /* Text.Whitespace */ 51 | .highlight .mb { color: #666 } /* Literal.Number.Bin */ 52 | .highlight .mf { color: #666 } /* Literal.Number.Float */ 53 | .highlight .mh { color: #666 } /* Literal.Number.Hex */ 54 | .highlight .mi { color: #666 } /* Literal.Number.Integer */ 55 | .highlight .mo { color: #666 } /* Literal.Number.Oct */ 56 | .highlight .sa { color: #BA2121 } /* Literal.String.Affix */ 57 | .highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ 58 | .highlight .sc { color: #BA2121 } /* Literal.String.Char */ 59 | .highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */ 60 | .highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ 61 | .highlight .s2 { color: #BA2121 } /* Literal.String.Double */ 62 | .highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */ 63 | .highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ 64 | .highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */ 65 | .highlight .sx { color: #008000 } /* Literal.String.Other */ 66 | .highlight .sr { color: #A45A77 } /* Literal.String.Regex */ 67 | .highlight .s1 { color: #BA2121 } /* Literal.String.Single */ 68 | .highlight .ss { color: #19177C } /* Literal.String.Symbol */ 69 | .highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ 70 | .highlight .fm { color: #00F } /* Name.Function.Magic */ 71 | .highlight .vc { color: #19177C } /* Name.Variable.Class */ 72 | .highlight .vg { color: #19177C } /* Name.Variable.Global */ 73 | .highlight .vi { color: #19177C } /* Name.Variable.Instance */ 74 | .highlight .vm { color: #19177C } /* Name.Variable.Magic */ 75 | .highlight .il { color: #666 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /docs/build/html/_static/sphinx_highlight.js: -------------------------------------------------------------------------------- 1 | /* Highlighting utilities for Sphinx HTML documentation. */ 2 | "use strict"; 3 | 4 | const SPHINX_HIGHLIGHT_ENABLED = true 5 | 6 | /** 7 | * highlight a given string on a node by wrapping it in 8 | * span elements with the given class name. 9 | */ 10 | const _highlight = (node, addItems, text, className) => { 11 | if (node.nodeType === Node.TEXT_NODE) { 12 | const val = node.nodeValue; 13 | const parent = node.parentNode; 14 | const pos = val.toLowerCase().indexOf(text); 15 | if ( 16 | pos >= 0 && 17 | !parent.classList.contains(className) && 18 | !parent.classList.contains("nohighlight") 19 | ) { 20 | let span; 21 | 22 | const closestNode = parent.closest("body, svg, foreignObject"); 23 | const isInSVG = closestNode && closestNode.matches("svg"); 24 | if (isInSVG) { 25 | span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); 26 | } else { 27 | span = document.createElement("span"); 28 | span.classList.add(className); 29 | } 30 | 31 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 32 | const rest = document.createTextNode(val.substr(pos + text.length)); 33 | parent.insertBefore( 34 | span, 35 | parent.insertBefore( 36 | rest, 37 | node.nextSibling 38 | ) 39 | ); 40 | node.nodeValue = val.substr(0, pos); 41 | /* There may be more occurrences of search term in this node. So call this 42 | * function recursively on the remaining fragment. 43 | */ 44 | _highlight(rest, addItems, text, className); 45 | 46 | if (isInSVG) { 47 | const rect = document.createElementNS( 48 | "http://www.w3.org/2000/svg", 49 | "rect" 50 | ); 51 | const bbox = parent.getBBox(); 52 | rect.x.baseVal.value = bbox.x; 53 | rect.y.baseVal.value = bbox.y; 54 | rect.width.baseVal.value = bbox.width; 55 | rect.height.baseVal.value = bbox.height; 56 | rect.setAttribute("class", className); 57 | addItems.push({ parent: parent, target: rect }); 58 | } 59 | } 60 | } else if (node.matches && !node.matches("button, select, textarea")) { 61 | node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); 62 | } 63 | }; 64 | const _highlightText = (thisNode, text, className) => { 65 | let addItems = []; 66 | _highlight(thisNode, addItems, text, className); 67 | addItems.forEach((obj) => 68 | obj.parent.insertAdjacentElement("beforebegin", obj.target) 69 | ); 70 | }; 71 | 72 | /** 73 | * Small JavaScript module for the documentation. 74 | */ 75 | const SphinxHighlight = { 76 | 77 | /** 78 | * highlight the search words provided in localstorage in the text 79 | */ 80 | highlightSearchWords: () => { 81 | if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight 82 | 83 | // get and clear terms from localstorage 84 | const url = new URL(window.location); 85 | const highlight = 86 | localStorage.getItem("sphinx_highlight_terms") 87 | || url.searchParams.get("highlight") 88 | || ""; 89 | localStorage.removeItem("sphinx_highlight_terms") 90 | url.searchParams.delete("highlight"); 91 | window.history.replaceState({}, "", url); 92 | 93 | // get individual terms from highlight string 94 | const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); 95 | if (terms.length === 0) return; // nothing to do 96 | 97 | // There should never be more than one element matching "div.body" 98 | const divBody = document.querySelectorAll("div.body"); 99 | const body = divBody.length ? divBody[0] : document.querySelector("body"); 100 | window.setTimeout(() => { 101 | terms.forEach((term) => _highlightText(body, term, "highlighted")); 102 | }, 10); 103 | 104 | const searchBox = document.getElementById("searchbox"); 105 | if (searchBox === null) return; 106 | searchBox.appendChild( 107 | document 108 | .createRange() 109 | .createContextualFragment( 110 | '" 114 | ) 115 | ); 116 | }, 117 | 118 | /** 119 | * helper function to hide the search marks again 120 | */ 121 | hideSearchWords: () => { 122 | document 123 | .querySelectorAll("#searchbox .highlight-link") 124 | .forEach((el) => el.remove()); 125 | document 126 | .querySelectorAll("span.highlighted") 127 | .forEach((el) => el.classList.remove("highlighted")); 128 | localStorage.removeItem("sphinx_highlight_terms") 129 | }, 130 | 131 | initEscapeListener: () => { 132 | // only install a listener if it is really needed 133 | if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; 134 | 135 | document.addEventListener("keydown", (event) => { 136 | // bail for input elements 137 | if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; 138 | // bail with special keys 139 | if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; 140 | if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { 141 | SphinxHighlight.hideSearchWords(); 142 | event.preventDefault(); 143 | } 144 | }); 145 | }, 146 | }; 147 | 148 | _ready(() => { 149 | /* Do not call highlightSearchWords() when we are on the search page. 150 | * It will highlight words from the *previous* search query. 151 | */ 152 | if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); 153 | SphinxHighlight.initEscapeListener(); 154 | }); 155 | -------------------------------------------------------------------------------- /docs/build/html/genindex.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Index — rpg_map 0.0.3 documentation 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 50 | 51 |
55 | 56 |
57 |
58 |
59 |
    60 |
  • 61 | 62 |
  • 63 |
  • 64 |
65 |
66 |
67 |
68 |
69 | 70 | 71 |

Index

72 | 73 |
74 | 75 |
76 | 77 | 78 |
79 |
80 |
81 | 82 |
83 | 84 |
85 |

© Copyright 2025, Kile.

86 |
87 | 88 | Built with Sphinx using a 89 | theme 90 | provided by Read the Docs. 91 | 92 | 93 |
94 |
95 |
96 |
97 |
98 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /docs/build/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | rpg_map documentation — rpg_map 0.0.3 documentation 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 52 | 53 |
57 | 58 |
59 |
60 |
61 | 68 |
69 |
70 |
71 |
72 | 73 |
74 |

rpg_map documentation

75 |
76 |
77 |

rpg_map

78 | PyPI version 79 | 80 | Documentation Status 81 | 82 |

A fast, zero-dependency Python library for visualizing exploration and movement across large pixel-based maps. Built in Rust for speed and efficiency, rpg_map is perfect for turn-based or real-time RPGs, strategy games, and D&D-style map reveals.

83 |
84 |

Key Features

85 |
    86 |
  • Hides unexplored areas of the map

  • 87 |
  • Reveals areas as they’re explored or passed through

  • 88 |
  • Draws travel paths using A* pathfinding

  • 89 |
  • Fast and lightweight with zero Python dependencies

  • 90 |
  • Operates directly on pixel data

  • 91 |
  • Customizable visual styles and highlight zones

  • 92 |
  • Includes examples using static image processing and Pygame

  • 93 |
94 |
95 |
96 |

Install

97 |

Install via pip:

98 |
pip install rpg-map
 99 | 
100 |
101 |
102 |
103 |

Documentation

104 |

Full documentation and examples available at: https://rpg-map.readthedocs.io/

105 |
106 |
107 |

How It Works

108 |

The library uses step-by-step image processing to reveal and annotate the map. Here’s an overview of the process:

109 |

Code:

110 |

This is the code which was used to generate the final result of the example steps below:

111 |
from rpg_map import Travel, Map, MapType, PathStyle, PathProgressDisplayType, PathDisplayType
112 | from PIL import Image
113 | 
114 | LOCAL_DIR = "../test_assets/map.png"
115 | BACKGROUND_DIR = "../test_assets/background.png"
116 | GRID_SIZE = 20
117 | START, END = (198, 390), (172, 223)
118 | START_X, START_Y = START
119 | 
120 | image = Image.open(LOCAL_DIR).convert("RGBA")
121 | # get image bytes
122 | image_bytes = list(image.tobytes())
123 | background = Image.open(BACKGROUND_DIR).convert("RGBA")
124 | # get background bytes
125 | background_bytes = list(background.tobytes())
126 | map = Map(
127 |    image_bytes,
128 |    image.size[0],
129 |    image.size[1],
130 |    GRID_SIZE,
131 |    MapType.Limited,
132 |    obstacles=[[(160, 240), (134, 253), (234, 257), (208, 239)]],
133 | )
134 | 
135 | travel = Travel(map, START, END)
136 | path_bits =  Map.draw_background(
137 |    map.with_dot(START_X, START_Y, (255, 0, 0, 255), 4).draw_path(
138 |       travel,
139 |       1.0,
140 |       2,
141 |       PathStyle.DottedWithOutline((255, 0, 0, 255), (255, 255, 255, 255)),
142 |    ),
143 |    background_bytes
144 | )
145 | 
146 | # Display the image
147 | image = Image.frombytes("RGBA", (image.width, image.height), path_bits)
148 | image.show()
149 | 
150 |
151 |
152 |

Steps:

153 |
    154 |
  1. Draw Obstacles

    155 |

    The Map class accepts an obstacles parameter which allows you to define N-sided polygons. These are rendered onto the map as solid barriers.

    156 | https://github.com/Kile/rpg_map/blob/master/assets/1.png?raw=true 157 | 158 |
  2. 159 |
  3. Add Padding and Convert to Pathfinding Grid

    160 |

    Obstacles and map edges are padded and the image is converted into a binary map (0 = path, 1 = obstacle) for pathfinding.

    161 | https://github.com/Kile/rpg_map/blob/master/assets/2.png?raw=true 162 | 163 |
  4. 164 |
  5. Pathfinding with A*

    165 |

    The library uses the A* algorithm to find the shortest path from point A to point B. The path is drawn on the map using a customizable style.

    166 | https://github.com/Kile/rpg_map/blob/master/assets/3.png?raw=true 167 | 168 |
  6. 169 |
  7. Draw Dots

    170 |

    Optional dots can be placed on the map (e.g., for points of interest, the player, markers).

    171 | https://github.com/Kile/rpg_map/blob/master/assets/4.png?raw=true 172 | 173 |
  8. 174 |
  9. Divide into Grid Squares

    175 |

    The image is divided into equal squares based on the grid_size parameter.

    176 | https://github.com/Kile/rpg_map/blob/master/assets/5.png?raw=true 177 | 178 |
  10. 179 |
  11. Reveal Explored Areas

    180 |

    A mask overlays the map. Areas near the travel path or manually unlocked via Map.unlock_point are revealed in circular zones.

    181 | https://github.com/Kile/rpg_map/blob/master/assets/6.png?raw=true 182 | 183 |
  12. 184 |
  13. Fill Transparent Areas

    185 |

    Any remaining transparent pixels are filled with a background layer.

    186 | https://github.com/Kile/rpg_map/blob/master/assets/7.png?raw=true 187 | 188 |
  14. 189 |
  15. Final Map Output

    190 |

    The completed map shows explored areas, paths, markers, and hidden regions yet to be discovered.

    191 | https://github.com/Kile/rpg_map/blob/master/assets/8.png?raw=true 192 | 193 |
  16. 194 |
195 |
196 |
197 |

Advanced Features

198 |
    199 |
  • You can define special grid points where the reveal radius is larger — perfect for cities or key landmarks.

  • 200 |
  • The library supports tons of styles for different themes and usecases.

    201 |
    202 |
    https://github.com/Kile/rpg_map/blob/master/assets/9.png?raw=true 203 | 204 | https://github.com/Kile/rpg_map/blob/master/assets/10.png?raw=true 205 | 206 | https://github.com/Kile/rpg_map/blob/master/assets/11.png?raw=true 207 | 208 | https://github.com/Kile/rpg_map/blob/master/assets/12.png?raw=true 209 | 210 |
    211 |
  • 212 |
213 |
214 |
215 |

Examples

216 |

Check out these demos:

217 | 222 |
223 |

Contents:

224 | 230 |
231 |
232 |
233 | 234 | 235 |
236 |
237 |
240 | 241 |
242 | 243 |
244 |

© Copyright 2025, Kile.

245 |
246 | 247 | Built with Sphinx using a 248 | theme 249 | provided by Read the Docs. 250 | 251 | 252 |
253 |
254 |
255 |
256 |
257 | 262 | 263 | 264 | -------------------------------------------------------------------------------- /docs/build/html/modules.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | rpg_map — rpg_map 0.0.3 documentation 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 51 | 52 |
56 | 57 |
58 |
59 |
60 | 67 |
68 |
69 |
70 |
71 | 72 |
73 |

rpg_map

74 |
75 |
76 |
77 | 78 | 79 |
80 |
81 |
82 | 83 |
84 | 85 |
86 |

© Copyright 2025, Kile.

87 |
88 | 89 | Built with Sphinx using a 90 | theme 91 | provided by Read the Docs. 92 | 93 | 94 |
95 |
96 |
97 |
98 |
99 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /docs/build/html/objects.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/docs/build/html/objects.inv -------------------------------------------------------------------------------- /docs/build/html/py-modindex.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Python Module Index — rpg_map 0.0.3 documentation 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 26 | 27 | 28 | 29 | 30 | 31 |
32 | 57 | 58 |
62 | 63 |
64 |
65 |
66 |
    67 |
  • 68 | 69 |
  • 70 |
  • 71 |
72 |
73 |
74 |
75 |
76 | 77 | 78 |

Python Module Index

79 | 80 |
81 | r 82 |
83 | 84 | 85 | 86 | 88 | 89 | 90 | 93 |
 
87 | r
91 | rpg_map 92 |
94 | 95 | 96 |
97 |
98 |
99 | 100 |
101 | 102 |
103 |

© Copyright 2025, Kile.

104 |
105 | 106 | Built with Sphinx using a 107 | theme 108 | provided by Read the Docs. 109 | 110 | 111 |
112 |
113 |
114 |
115 |
116 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /docs/build/html/rpg_map.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | rpg_map package — rpg_map 0.0.3 documentation 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 55 | 56 |
60 | 61 |
62 |
63 |
64 | 71 |
72 |
73 |
74 |
75 | 76 |
77 |

rpg_map package

78 |
79 |

Module contents

80 |
81 |
82 | 83 | 84 |
85 |
86 |
89 | 90 |
91 | 92 |
93 |

© Copyright 2025, Kile.

94 |
95 | 96 | Built with Sphinx using a 97 | theme 98 | provided by Read the Docs. 99 | 100 | 101 |
102 |
103 |
104 |
105 |
106 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /docs/build/html/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Search — rpg_map 0.0.3 documentation 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 53 | 54 |
58 | 59 |
60 |
61 |
62 |
    63 |
  • 64 | 65 |
  • 66 |
  • 67 |
68 |
69 |
70 |
71 |
72 | 73 | 80 | 81 | 82 |
83 | 84 |
85 | 86 |
87 |
88 |
89 | 90 |
91 | 92 |
93 |

© Copyright 2025, Kile.

94 |
95 | 96 | Built with Sphinx using a 97 | theme 98 | provided by Read the Docs. 99 | 100 | 101 |
102 |
103 |
104 |
105 |
106 | 111 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /docs/build/html/searchindex.js: -------------------------------------------------------------------------------- 1 | Search.setIndex({"alltitles":{"Advanced Features":[[0,"advanced-features"]],"Contents:":[[0,null]],"Documentation":[[0,"documentation"]],"Examples":[[0,"examples"]],"How It Works":[[0,"how-it-works"]],"Install":[[0,"install"]],"Key Features":[[0,"key-features"]],"Module contents":[[2,"module-contents"]],"rpg_map":[[0,"rpg-map"],[1,null]],"rpg_map documentation":[[0,null]],"rpg_map package":[[2,null]]},"docnames":["index","modules","rpg_map"],"envversion":{"sphinx":65,"sphinx.domains.c":3,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":9,"sphinx.domains.index":1,"sphinx.domains.javascript":3,"sphinx.domains.math":2,"sphinx.domains.python":4,"sphinx.domains.rst":2,"sphinx.domains.std":2,"sphinx.ext.intersphinx":1,"sphinx.ext.viewcode":1},"filenames":["index.rst","modules.rst","rpg_map.rst"],"indexentries":{},"objects":{},"objnames":{},"objtypes":{},"terms":{"":0,"0":0,"1":0,"134":0,"160":0,"172":0,"198":0,"1px":[],"2":0,"20":0,"208":0,"223":0,"234":0,"239":0,"240":0,"253":0,"255":0,"257":0,"2px":[],"390":0,"4":0,"A":0,"If":[],"It":[],"The":0,"These":0,"To":[],"abov":[],"abovemask":[],"accept":0,"across":0,"add":0,"algorithm":0,"alia":[],"all":[],"allow":0,"alreadi":[],"alwai":[],"an":0,"ani":0,"annot":0,"anoth":[],"ar":0,"area":0,"avail":0,"b":0,"background":0,"background_byt":0,"background_dir":0,"barrier":0,"base":0,"below":0,"belowmask":[],"binari":0,"black":[],"bool":[],"box":[],"breakdown":0,"built":0,"byte":0,"call":[],"can":0,"check":0,"circular":0,"citi":0,"class":0,"click":0,"close":[],"code":0,"color":[],"complet":0,"comput":[],"computed_path":[],"contain":[],"convert":0,"coordin":[],"current":[],"current_loc":[],"customiz":0,"d":0,"data":0,"dbg_map":[],"debug":[],"defin":0,"demo":0,"depend":0,"destin":[],"differ":0,"directli":0,"discov":0,"displai":0,"display_styl":[],"divid":0,"do":0,"dot":0,"dottedwithoutlin":0,"draw":0,"draw_background":0,"draw_path":0,"drawn":0,"e":0,"edg":0,"effici":0,"ellipsi":[],"end":0,"entri":[],"equal":0,"everi":[],"explor":0,"fals":[],"fast":0,"fault":[],"fill":0,"final":0,"find":0,"float":[],"free":[],"from":0,"frombyt":0,"full":0,"full_imag":[],"g":0,"game":0,"gener":0,"get":0,"get_bit":[],"given":[],"greyscal":[],"grid":0,"grid_siz":0,"ha":[],"height":0,"here":0,"hidden":0,"hide":0,"highlight":0,"how":[],"http":0,"i":0,"imag":0,"image_byt":0,"import":0,"includ":0,"int":[],"interact":0,"interest":0,"io":0,"landmark":0,"larg":0,"larger":0,"last":[],"layer":0,"librari":0,"lightweight":0,"limit":0,"line":[],"line_width":[],"list":0,"local_dir":0,"locat":[],"main":[],"mani":[],"manual":0,"map":0,"map_typ":[],"maptyp":0,"marker":0,"mask":0,"masked_imag":[],"method":[],"mode":[],"modul":0,"movement":0,"n":0,"note":[],"object":[],"obstacl":0,"one":0,"onli":[],"onto":0,"open":0,"oper":0,"option":0,"otherwis":[],"out":0,"outlin":[],"output":0,"overlai":0,"overview":0,"packag":0,"pad":0,"paramet":0,"pass":0,"path":0,"path_bit":0,"path_displai":[],"path_typ":[],"pathdisplaytyp":0,"pathfind":0,"pathpoint":[],"pathprogressdisplaytyp":0,"pathstyl":0,"pathstyle_debug":[],"pathstyle_dot":[],"pathstyle_dottedwithoutlin":[],"pathstyle_solid":[],"pathstyle_solidwithoutlin":[],"percentag":[],"perfect":0,"pil":0,"pip":0,"pixel":0,"place":0,"player":0,"png":0,"point":0,"polygon":0,"posit":[],"process":0,"progress":[],"progress_display_typ":[],"py":0,"pygam":0,"pygame_poc":0,"python":0,"radiu":0,"re":0,"readm":0,"readthedoc":0,"real":0,"reduct":[],"region":0,"remain":0,"render":0,"repres":[],"respect":[],"result":0,"return":[],"reveal":0,"rgba":0,"rpg":0,"rust":0,"see":[],"set":[],"shortest":0,"show":0,"side":0,"singl":[],"size":0,"solid":0,"solidwithoutlin":[],"space":[],"special":0,"special_point":[],"specifi":[],"speed":0,"squar":0,"start":0,"start_i":0,"start_x":0,"static":0,"static_poc":0,"step":0,"strategi":0,"style":0,"support":0,"take":[],"test_asset":0,"thei":0,"theme":0,"thi":0,"through":0,"time":0,"tobyt":0,"ton":0,"transpar":0,"travel":0,"true":[],"tupl":[],"turn":0,"type":[],"unexplor":0,"unlock":0,"unlock_point":0,"unlock_point_from_coordin":[],"us":0,"usecas":0,"via":0,"view":[],"visual":0,"wa":0,"wai":[],"when":[],"where":0,"wherev":0,"which":0,"white":[],"width":0,"with_dot":0,"with_grid":[],"with_obstacl":[],"x":[],"y":[],"yet":0,"you":0,"your":0,"zero":0,"zone":0},"titles":["rpg_map documentation","rpg_map","rpg_map package"],"titleterms":{"It":0,"advanc":0,"content":[0,2],"document":0,"exampl":0,"featur":0,"how":0,"instal":0,"kei":0,"modul":2,"packag":2,"rpg_map":[0,1,2],"work":0}}) -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx 2 | sphinx-rtd-theme -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -- Path setup -------------------------------------------------------------- 2 | import os 3 | import sys 4 | sys.path.insert(0, os.path.abspath('./_ext')) # If your _ext directory is in docs/source/ 5 | 6 | # Configuration file for the Sphinx documentation builder. 7 | # 8 | # For the full list of built-in configuration values, see the documentation: 9 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 10 | 11 | # -- Project information ----------------------------------------------------- 12 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 13 | 14 | project = 'rpg_map' 15 | copyright = '2025, Kile' 16 | author = 'Kile' 17 | release = '0.0.3' 18 | 19 | # -- General configuration --------------------------------------------------- 20 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration 21 | 22 | extensions = [ 23 | 'sphinx.ext.autodoc', # To automatically generate docs from docstrings 24 | 'sphinx.ext.napoleon', # To support NumPy and Google style docstrings 25 | 'sphinx.ext.intersphinx', # To link to other projects' documentation 26 | 'sphinx.ext.viewcode', # To include links to the source code 27 | 'sphinx_rtd_theme', # To use the Read the Docs theme 28 | ] 29 | 30 | templates_path = ['_templates'] 31 | exclude_patterns = [] 32 | 33 | 34 | 35 | # -- Options for HTML output ------------------------------------------------- 36 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 37 | 38 | html_theme = 'sphinx_rtd_theme' 39 | html_static_path = ['_static'] 40 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. rpg_map documentation master file, created by 2 | sphinx-quickstart on Fri Apr 11 14:02:10 2025. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | rpg_map documentation 7 | ================================================= 8 | 9 | .. include:: ../../README.rst 10 | :end-before: Contributing & Development 11 | 12 | .. toctree:: 13 | :maxdepth: 2 14 | :caption: Contents: 15 | 16 | rpg_map 17 | -------------------------------------------------------------------------------- /docs/source/modules.rst: -------------------------------------------------------------------------------- 1 | rpg_map 2 | ======= 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | -------------------------------------------------------------------------------- /docs/source/rpg_map.rst: -------------------------------------------------------------------------------- 1 | rpg\_map package 2 | ================ 3 | 4 | Module contents 5 | --------------- 6 | 7 | .. automodule:: rpg_map 8 | :members: 9 | :show-inheritance: 10 | :undoc-members: 11 | :imported-members: -------------------------------------------------------------------------------- /examples/pygame_poc.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from PIL import Image 3 | from rpg_map import Map, MapType, Travel, PathStyle, PathDisplayType, PathProgressDisplayType 4 | 5 | # Constants 6 | TILE_SIZE = 1 7 | LOCAL_DIR = "../test_assets/map.png" # Replace with the path to your image 8 | BACKGROUND_DIR = "../test_assets/background.png" # Replace with the path to your background image 9 | 10 | # Load image and create map 11 | image = Image.open(LOCAL_DIR).convert("RGBA") 12 | image_bytes = list(image.tobytes()) 13 | background = Image.open(BACKGROUND_DIR).convert("RGBA") 14 | background_bytes = list(background.tobytes()) 15 | map = Map( 16 | image_bytes, 17 | image.size[0], 18 | image.size[1], 19 | 20, 20 | MapType.Full, 21 | obstacles=[ 22 | [ 23 | (160, 240), 24 | (134, 253), 25 | (234, 257), 26 | (208, 239) 27 | ] 28 | ] 29 | ) 30 | 31 | SCREEN_HEIGHT = image.size[1] 32 | SCREEN_WIDTH = image.size[0] 33 | 34 | # Initialize PyGame 35 | pygame.init() 36 | screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) 37 | pygame.display.set_caption("Simple PyGame Map") 38 | 39 | # Player position 40 | player_x = SCREEN_WIDTH // 2 41 | player_y = SCREEN_HEIGHT // 2 42 | 43 | def update_map_with_bits(bits): 44 | for y in range(SCREEN_HEIGHT): 45 | for x in range(SCREEN_WIDTH): 46 | index = (y * SCREEN_WIDTH + x) * 4 47 | r, g, b, a = bits[index : index + 4] 48 | if a == 0: 49 | r, g, b = 255, 255, 255 50 | pygame.draw.rect( 51 | screen, (r, g, b), (x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE) 52 | ) 53 | 54 | # Update the display 55 | pygame.display.flip() 56 | 57 | map.unlock_point_from_coordinates(player_x, player_y) 58 | update_map_with_bits(Map.draw_background(map.with_dot(player_x, player_y, (255, 0, 0, 255), 5).with_obstacles().get_bits(), background_bytes)) 59 | # Game loop 60 | running = True 61 | while running: 62 | map_x = None 63 | map_y = None 64 | for event in pygame.event.get(): 65 | if event.type == pygame.QUIT: 66 | running = False 67 | 68 | # Handle mouse click 69 | if event.type == pygame.MOUSEBUTTONDOWN: 70 | # Get mouse position 71 | mouse_x, mouse_y = pygame.mouse.get_pos() 72 | # Convert mouse position to map coordinates 73 | map_x = mouse_x // TILE_SIZE 74 | map_y = mouse_y // TILE_SIZE 75 | print(f"Clicked at map coordinates: ({map_x}, {map_y})") 76 | 77 | if map_x is not None and map_y is not None: # If the player clicked on the map 78 | # Clear the screen 79 | screen.fill((0, 0, 0)) 80 | try: 81 | travel = Travel(map, (player_x, player_y), (map_x, map_y)) 82 | map_bits = Map.draw_background( 83 | map.with_dot(player_x, player_y, (255, 0, 0, 255), 5).draw_path( 84 | travel, 85 | 0.5, 86 | 2, 87 | PathStyle.DottedWithOutline((255, 0, 0, 255), (255, 255, 255, 255)), 88 | PathDisplayType.Revealing(), 89 | PathProgressDisplayType.Progress(), 90 | ), 91 | background_bytes 92 | ) 93 | update_map_with_bits(map_bits) 94 | except ValueError: 95 | print("No path found") 96 | 97 | # Cap the frame rate 98 | pygame.time.Clock().tick(30) 99 | 100 | # Quit PyGame 101 | pygame.quit() 102 | -------------------------------------------------------------------------------- /examples/readme.py: -------------------------------------------------------------------------------- 1 | from rpg_map import Travel, Map, MapType, PathStyle, PathProgressDisplayType, PathDisplayType 2 | from PIL import Image 3 | 4 | LOCAL_DIR = "../test_assets/map.png" 5 | BACKGROUND_DIR = "../test_assets/background.png" 6 | GRID_SIZE = 20 7 | START, END = (198, 390), (172, 223) 8 | START_X, START_Y = START 9 | 10 | image = Image.open(LOCAL_DIR).convert("RGBA") 11 | # get image bytes 12 | image_bytes = list(image.tobytes()) 13 | background = Image.open(BACKGROUND_DIR).convert("RGBA") 14 | # get background bytes 15 | background_bytes = list(background.tobytes()) 16 | map = Map( 17 | image_bytes, 18 | image.size[0], 19 | image.size[1], 20 | GRID_SIZE, 21 | MapType.Limited, 22 | obstacles=[[(160, 240), (134, 253), (234, 257), (208, 239)]], 23 | ) 24 | 25 | travel = Travel(map, START, END) 26 | path_bits = Map.draw_background( 27 | map.with_dot(START_X, START_Y, (255, 0, 0, 255), 4).draw_path( 28 | travel, 29 | 1.0, 30 | 2, 31 | PathStyle.DottedWithOutline((255, 0, 0, 255), (255, 255, 255, 255)), 32 | ), 33 | background_bytes 34 | ) 35 | 36 | # Display the image 37 | image = Image.frombytes("RGBA", (image.width, image.height), path_bits) 38 | image.show() -------------------------------------------------------------------------------- /examples/static_poc.py: -------------------------------------------------------------------------------- 1 | from rpg_map import Travel, Map, MapType, PathStyle, PathProgressDisplayType, PathDisplayType 2 | from PIL import Image 3 | 4 | LOCAL_DIR = "../test_assets/map.png" 5 | BACKGROUND_DIR = "../test_assets/background.png" 6 | GRID_SIZE = 20 7 | START, END = (198, 390), (330, 512) 8 | START_X, START_Y = START 9 | 10 | 11 | def main(): 12 | image = Image.open(LOCAL_DIR).convert("RGBA") 13 | # get image bytes 14 | image_bytes = list(image.tobytes()) 15 | background = Image.open(BACKGROUND_DIR).convert("RGBA") 16 | # get background bytes 17 | background_bytes = list(background.tobytes()) 18 | map = Map( 19 | image_bytes, 20 | image.size[0], 21 | image.size[1], 22 | GRID_SIZE, 23 | MapType.Hidden, 24 | obstacles=[[(160, 240), (134, 253), (234, 257), (208, 239)]], 25 | ) 26 | 27 | map.unlock_point_from_coordinates(START_X, START_Y) 28 | travel = Travel(map, START, END) 29 | path_bits = Map.draw_background( 30 | map.with_dot(START_X, START_Y, (255, 0, 0, 255), 4).draw_path( 31 | travel, 32 | 0.5, 33 | 2, 34 | PathStyle.DottedWithOutline((255, 0, 0, 255), (255, 255, 255, 255)), 35 | PathDisplayType.AboveMask, 36 | PathProgressDisplayType.Progress, 37 | ), 38 | background_bytes 39 | ) 40 | 41 | # Display the image 42 | image = Image.frombytes("RGBA", (image.width, image.height), path_bits) 43 | image.show() 44 | 45 | 46 | if __name__ == "__main__": 47 | main() 48 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["maturin>=1,<2"] 3 | build-backend = "maturin" 4 | 5 | [project] 6 | name = "rpg_map" 7 | dynamic = ["version"] 8 | requires-python = ">=3.9" 9 | classifiers = [ 10 | "Programming Language :: Rust", 11 | "Programming Language :: Python :: Implementation :: CPython", 12 | "Programming Language :: Python :: Implementation :: PyPy", 13 | ] 14 | readme = "README.rst" 15 | license = "MIT" 16 | license_files = ["LICENSE"] 17 | description = "A zero dependency, lightweight and fast library to manipulate RPG images written in Rust." 18 | keywords = ["rpg", "rpg-map", "rpg-map-rs", "rust", "fast", "lightweight", "zero-dependency"] 19 | author = "Kile" 20 | urls = {"github" = "https://github.com/Kile/rpg_map"} -------------------------------------------------------------------------------- /rpg_map.pyi: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by pyo3_stub_gen 2 | # ruff: noqa: E501, F401 3 | 4 | import builtins 5 | import typing 6 | from enum import Enum, auto 7 | 8 | class Map: 9 | r""" 10 | A class representing a map. 11 | 12 | Parameters 13 | ---------- 14 | bytes : List[int] 15 | The bytes of the image. 16 | width : int 17 | The width of the image. 18 | height : int 19 | The height of the image. 20 | grid_size : int 21 | The size of a single box in the grid defining how many map revealing points the map has. 22 | To see the grid visually, use the `with_grid` method. 23 | map_type : MapType 24 | The type of the map. Can be Hidden, Limited or Full. 25 | unlocked : List[Tuple[int, int]] 26 | The points that are unlocked on the map. 27 | special_points : List[Tuple[int, int]] 28 | The special points on the map. Used to draw the path. 29 | obstacles : List[List[List[Tuple[int, int]]]] 30 | The obstacles on the map. Used to draw the path. 31 | background : Optional[List[int]] 32 | 33 | Attributes 34 | ---------- 35 | width : int 36 | The width of the map. 37 | height : int 38 | The height of the map. 39 | unlocked : List[Tuple[int, int]] 40 | The points that are unlocked on the map. 41 | """ 42 | width: builtins.int 43 | height: builtins.int 44 | unlocked: builtins.list[tuple[builtins.int, builtins.int]] 45 | def __new__(cls,bytes:typing.Sequence[builtins.int], width:builtins.int, height:builtins.int, grid_size:builtins.int, map_type:MapType=..., unlocked:typing.Sequence[tuple[builtins.int, builtins.int]]=[], special_points:typing.Sequence[tuple[builtins.int, builtins.int]]=[], obstacles:typing.Sequence[typing.Sequence[tuple[builtins.int, builtins.int]]]=[]): ... 46 | @staticmethod 47 | def draw_background(bytes:typing.Sequence[builtins.int], background:typing.Sequence[builtins.int]) -> builtins.list[builtins.int]: 48 | r""" 49 | Draws the background image at every transparent pixel 50 | if the background is set 51 | 52 | Parameters 53 | ---------- 54 | bytes : List[int] 55 | The bytes of the image. 56 | background : Optional[List[int]] 57 | The bytes of the background of the image. 58 | """ 59 | ... 60 | 61 | def with_dot(self, x:builtins.int, y:builtins.int, color:typing.Sequence[builtins.int], radius:builtins.int) -> Map: 62 | r""" 63 | Adds a dot do be drawn on the map when :func:`Map.full_image`, :func:`Map.masked_image` or :func:`Map.get_bits` is called 64 | 65 | Parameters 66 | ---------- 67 | x : int 68 | The x coordinate of the dot. 69 | y : int 70 | The y coordinate of the dot. 71 | color : Tuple[int, int, int, int] 72 | The color of the dot. 73 | radius : int 74 | The radius of the dot. 75 | 76 | Returns 77 | ------- 78 | Map 79 | The map with the dot. 80 | """ 81 | ... 82 | 83 | def with_grid(self) -> Map: 84 | r""" 85 | If called, a grid is drawn on the map when :func:`Map.full_image`, :func:`Map.masked_image` or :func:`Map.get_bits` is called 86 | """ 87 | ... 88 | 89 | def with_obstacles(self) -> Map: 90 | r""" 91 | If called, the obstacles are drawn on the map when :func:`Map.full_image`, :func:`Map.masked_image` or :func:`Map.get_bits` is called 92 | """ 93 | ... 94 | 95 | def unlock_point_from_coordinates(self, x:builtins.int, y:builtins.int) -> builtins.bool: 96 | r""" 97 | Takes in a coordinate, if it is close to an "unlocked" grid point it will unlock it and return true, if the point is already unlocked it will return false 98 | 99 | Parameters 100 | ---------- 101 | x : int 102 | The x coordinate of the point to unlock. 103 | y : int 104 | The y coordinate of the point to unlock. 105 | 106 | Returns 107 | ------- 108 | bool 109 | True if the point was unlocked, False otherwise (already unlocked). 110 | """ 111 | ... 112 | 113 | def draw_path(self, travel:Travel, percentage:builtins.float, line_width:builtins.int, path_type:PathStyle=..., display_style:PathDisplayType=..., progress_display_type:PathProgressDisplayType=...) -> builtins.list[builtins.int]: 114 | r""" 115 | Draws the path from :func:`Travel.computed_path` on the image. 116 | 117 | Parameters 118 | ---------- 119 | travel : Travel 120 | The travel object containing the path to draw. 121 | percentage : float 122 | The percentage of the path to draw. 0.0 to 1.0. 123 | line_width : int 124 | The width of the line to draw in pixels. Note that if the line has an outline the width will be this +2px 125 | path_type : PathStyle 126 | The type of path to draw. Can be Solid, Dotted, SolidWithOutline or DottedWithOutline. 127 | path_display : PathDisplayType 128 | The type of path display to use. Can be BelowMask or AboveMask. 129 | 130 | Returns 131 | ------- 132 | List[int] 133 | The bytes of the image with the path drawn. 134 | """ 135 | ... 136 | 137 | def full_image(self) -> builtins.list[builtins.int]: 138 | r""" 139 | Returns the full image. If specified, draws the grid, obstacles, and dots. 140 | 141 | Returns 142 | ------- 143 | List[int] 144 | The bytes of the image with the grid, obstacles, and dots drawn. 145 | """ 146 | ... 147 | 148 | def masked_image(self) -> builtins.list[builtins.int]: 149 | r""" 150 | Returns the masked image. If specified, draws the grid, obstacles, and dots. 151 | 152 | Returns 153 | ------- 154 | List[int] 155 | The bytes of the image with the grid, obstacles, and dots drawn. 156 | """ 157 | ... 158 | 159 | def get_bits(self) -> builtins.list[builtins.int]: 160 | r""" 161 | The main method to get the image bytes. 162 | Respects the map type and draws the grid, obstacles, and dots if specified. 163 | 164 | Returns 165 | ------- 166 | List[int] 167 | The bytes of the image with the grid, obstacles, and dots drawn. 168 | """ 169 | ... 170 | 171 | 172 | class Travel: 173 | r""" 174 | A class representing a travel from one point to another on a map. 175 | This class contains the shortest path from point A to point B on the map. 176 | It uses the A* algorithm to find the path. 177 | 178 | Parameters 179 | ---------- 180 | map : Map 181 | The map to travel on. 182 | current_location : tuple[int, int] 183 | The current location of the traveler. Given as a tuple of (x, y) coordinates. 184 | destination : tuple[int, int] 185 | The destination of the traveler. Given as a tuple of (x, y) coordinates. 186 | 187 | Attributes 188 | --------- 189 | map : Map 190 | The map to travel on. 191 | computed_path : list[PathPoint] 192 | The computed path from the current location to the destination. 193 | """ 194 | def __new__(cls,map:Map, current_location:tuple[builtins.int, builtins.int], destination:tuple[builtins.int, builtins.int]): ... 195 | @staticmethod 196 | def dbg_map(map:Map) -> builtins.list[builtins.int]: 197 | r""" 198 | Displays the map in a black and white view where white are the 199 | obstacles and black are the free spaces. This is to debug if 200 | a fault is with the pathfinding algorithm or the map reduction 201 | algorithm. 202 | 203 | Parameters 204 | --------- 205 | map : Map 206 | The map to display the black and white view of. 207 | 208 | Returns 209 | ------- 210 | list[int] 211 | A list of bytes representing the black and white view of the map. 212 | """ 213 | ... 214 | 215 | 216 | class MapType(Enum): 217 | r""" 218 | The reveal type of the map. 219 | 220 | Attributes 221 | --------- 222 | Hidden 223 | The map reveals only the last entry in the unlocked points. 224 | Limited 225 | The map reveals all the unlocked points. 226 | Full 227 | The map reveals all the points. 228 | """ 229 | Hidden = auto() 230 | Limited = auto() 231 | Full = auto() 232 | 233 | class PathDisplayType(Enum): 234 | r""" 235 | The way of how to display the path. 236 | 237 | Attributes 238 | --------- 239 | BelowMask 240 | The path is always drawn below the mask. 241 | AboveMask 242 | The path is always drawn above the mask. 243 | """ 244 | BelowMask = auto() 245 | AboveMask = auto() 246 | 247 | class PathProgressDisplayType(Enum): 248 | r""" 249 | The type of how to display path progress. 250 | 251 | Attributes 252 | --------- 253 | Remaining 254 | The path is drawn from the current position to the destination. 255 | Travelled 256 | The path is drawn from the start to the current position. 257 | Progress 258 | The path is drawn from the start to the destination. The path already travelled is converted to greyscale. 259 | """ 260 | Remaining = auto() 261 | Travelled = auto() 262 | Progress = auto() 263 | 264 | class PathStyle(Enum): 265 | r""" 266 | The style of the path. 267 | 268 | Attributes 269 | --------- 270 | Debug 271 | The path is drawn in debug mode, only a 1px line is drawn. 272 | Solid 273 | The path is drawn as a solid line. 274 | Dotted 275 | The path is drawn as a dotted line. 276 | SolidWithOutline 277 | The path is drawn as a solid line with an outline. 278 | DottedWithOutline 279 | The path is drawn as a dotted line with an outline. 280 | """ 281 | Debug = auto() 282 | Solid = auto() 283 | Dotted = auto() 284 | SolidWithOutline = auto() 285 | DottedWithOutline = auto() 286 | 287 | -------------------------------------------------------------------------------- /src/bin/stub_gen.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "stubgen")] 2 | use pyo3_stub_gen::Result; 3 | #[cfg(feature = "stubgen")] 4 | fn main() -> Result<()> { 5 | let stub = rpg_map::stub_info()?; 6 | stub.generate()?; 7 | Ok(()) 8 | } 9 | 10 | #[cfg(not(feature = "stubgen"))] 11 | fn main() { 12 | eprintln!("The 'stubgen' feature is not enabled. Enable it with `--features stubgen`."); 13 | } 14 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use pyo3::prelude::*; 2 | #[cfg(feature = "stubgen")] 3 | use pyo3_stub_gen::define_stub_info_gatherer; 4 | 5 | mod structs; 6 | #[cfg(test)] 7 | mod tests; 8 | 9 | #[pymodule] 10 | fn rpg_map(m: &Bound<'_, PyModule>) -> PyResult<()> { 11 | m.add_class::()?; 12 | m.add_class::()?; 13 | m.add_class::()?; 14 | m.add_class::()?; 15 | m.add_class::()?; 16 | m.add_class::()?; 17 | m.add_class::()?; 18 | 19 | Ok(()) 20 | } 21 | 22 | #[cfg(feature = "stubgen")] 23 | define_stub_info_gatherer!(stub_info); 24 | -------------------------------------------------------------------------------- /src/structs/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod map; 2 | pub mod path; 3 | pub mod travel; 4 | -------------------------------------------------------------------------------- /src/structs/path.rs: -------------------------------------------------------------------------------- 1 | use pyo3::prelude::pyclass; 2 | use std::cmp::Ordering; 3 | use std::collections::{BinaryHeap, HashMap}; 4 | 5 | #[pyclass] 6 | #[derive(Clone, PartialEq, Eq, Debug, Copy)] 7 | pub struct PathPoint { 8 | pub x: u32, 9 | pub y: u32, 10 | } 11 | 12 | impl PathPoint { 13 | pub fn from_tuple(t: (u32, u32)) -> Self { 14 | PathPoint { x: t.0, y: t.1 } 15 | } 16 | } 17 | 18 | #[derive(Clone, Eq, PartialEq)] 19 | struct Node { 20 | x: u32, 21 | y: u32, 22 | cost: u32, 23 | heuristic: u32, 24 | parent: Option<(u32, u32)>, 25 | } 26 | 27 | impl Ord for Node { 28 | fn cmp(&self, other: &Self) -> Ordering { 29 | (other.cost + other.heuristic).cmp(&(self.cost + self.heuristic)) 30 | } 31 | } 32 | 33 | impl PartialOrd for Node { 34 | fn partial_cmp(&self, other: &Self) -> Option { 35 | Some(self.cmp(other)) 36 | } 37 | } 38 | 39 | pub fn astar(grid: &[Vec]) -> Option> { 40 | let rows = grid.len() as u32; 41 | if rows == 0 { 42 | return None; 43 | } 44 | let cols = grid[0].len() as u32; 45 | 46 | let mut start = None; 47 | let mut end = None; 48 | 49 | for y in 0..rows { 50 | for x in 0..cols { 51 | match grid[y as usize][x as usize] { 52 | 2 => start = Some((x, y)), 53 | 3 => end = Some((x, y)), 54 | _ => {} 55 | } 56 | } 57 | } 58 | 59 | let (start_x, start_y) = start?; 60 | let (end_x, end_y) = end?; 61 | 62 | let mut open_set = BinaryHeap::new(); 63 | let mut came_from = HashMap::new(); 64 | let mut g_score = HashMap::new(); 65 | 66 | g_score.insert((start_x, start_y), 0); 67 | 68 | open_set.push(Node { 69 | x: start_x, 70 | y: start_y, 71 | cost: 0, 72 | heuristic: heuristic(start_x, start_y, end_x, end_y), 73 | parent: None, 74 | }); 75 | 76 | while let Some(current) = open_set.pop() { 77 | if current.x == end_x && current.y == end_y { 78 | return reconstruct_path(came_from, (end_x, end_y)); 79 | } 80 | 81 | let neighbors = get_neighbors(current.x, current.y, grid); 82 | 83 | for (nx, ny) in neighbors { 84 | let tentative_g_score = g_score.get(&(current.x, current.y)).unwrap_or(&u32::MAX) + 1; 85 | 86 | if tentative_g_score < *g_score.get(&(nx, ny)).unwrap_or(&u32::MAX) { 87 | came_from.insert((nx, ny), (current.x, current.y)); 88 | g_score.insert((nx, ny), tentative_g_score); 89 | open_set.push(Node { 90 | x: nx, 91 | y: ny, 92 | cost: tentative_g_score, 93 | heuristic: heuristic(nx, ny, end_x, end_y), 94 | parent: Some((current.x, current.y)), 95 | }); 96 | } 97 | } 98 | } 99 | 100 | None 101 | } 102 | 103 | fn heuristic(x1: u32, y1: u32, x2: u32, y2: u32) -> u32 { 104 | (x1.abs_diff(x2)) + (y1.abs_diff(y2)) 105 | } 106 | 107 | fn get_neighbors(x: u32, y: u32, grid: &[Vec]) -> Vec<(u32, u32)> { 108 | let rows = grid.len() as u32; 109 | let cols = grid[0].len() as u32; 110 | let mut neighbors = Vec::new(); 111 | 112 | let possible_moves: [(i32, i32); 8] = [ 113 | (0, 1), 114 | (0, -1), 115 | (1, 0), 116 | (-1, 0), 117 | (1, 1), 118 | (1, -1), 119 | (-1, 1), 120 | (-1, -1), 121 | ]; 122 | 123 | for (dx, dy) in possible_moves { 124 | let nx = x as i32 + dx; 125 | let ny = y as i32 + dy; 126 | 127 | if nx >= 0 && nx < cols as i32 && ny >= 0 && ny < rows as i32 { 128 | let nx_u = nx as u32; 129 | let ny_u = ny as u32; 130 | 131 | if grid[ny_u as usize][nx_u as usize] != 1 { 132 | neighbors.push((nx_u, ny_u)); 133 | } 134 | } 135 | } 136 | 137 | neighbors 138 | } 139 | 140 | fn reconstruct_path( 141 | came_from: HashMap<(u32, u32), (u32, u32)>, 142 | current: (u32, u32), 143 | ) -> Option> { 144 | let mut total_path = vec![PathPoint::from_tuple(current)]; 145 | let mut current = current; 146 | 147 | while let Some(&parent) = came_from.get(¤t) { 148 | total_path.push(PathPoint::from_tuple(parent)); 149 | current = parent; 150 | } 151 | 152 | total_path.reverse(); 153 | Some(total_path) 154 | } 155 | -------------------------------------------------------------------------------- /src/structs/travel.rs: -------------------------------------------------------------------------------- 1 | use crate::structs::path::{astar, PathPoint}; 2 | use core::panic; 3 | use pyo3::prelude::*; 4 | use std::vec; 5 | use workaround::stubgen; 6 | 7 | use crate::structs::map::Map; 8 | use geo::{Contains, Coord, LineString, Point, Polygon}; 9 | 10 | /// A class representing a travel from one point to another on a map. 11 | /// This class contains the shortest path from point A to point B on the map. 12 | /// It uses the A* algorithm to find the path. 13 | /// 14 | /// Parameters 15 | /// ---------- 16 | /// map : Map 17 | /// The map to travel on. 18 | /// current_location : tuple[int, int] 19 | /// The current location of the traveler. Given as a tuple of (x, y) coordinates. 20 | /// destination : tuple[int, int] 21 | /// The destination of the traveler. Given as a tuple of (x, y) coordinates. 22 | /// 23 | /// Attributes 24 | /// --------- 25 | /// map : Map 26 | /// The map to travel on. 27 | /// computed_path : list[PathPoint] 28 | /// The computed path from the current location to the destination. 29 | #[stubgen] 30 | #[pyclass] 31 | #[derive(Clone)] 32 | pub struct Travel { 33 | pub map: Map, 34 | pub computed_path: Vec, 35 | } 36 | 37 | /// Give all 1s a X px "buffer" of 1s around them 38 | /// For efficiency reasons it will only do this if a 0 39 | /// is within a 1px radius of the 1 40 | fn buffer_edges(reduced: Vec>) -> Vec> { 41 | let buffer_size = 5; 42 | let mut new = reduced.clone(); 43 | // We do not want to mutate the original in the loop 44 | 45 | for y in 0..reduced.len() { 46 | for x in 0..reduced[y].len() { 47 | if reduced[y][x] == 0 { 48 | continue; 49 | } 50 | 51 | let mut buffer = false; 52 | for i in -1..2 { 53 | for j in -1..2 { 54 | if y as i32 + i < 0 55 | || y as i32 + i >= reduced.len() as i32 56 | || x as i32 + j < 0 57 | || x as i32 + j >= reduced[y].len() as i32 58 | { 59 | continue; 60 | } 61 | 62 | if reduced[(y as i32 + i) as usize][(x as i32 + j) as usize] == 0 { 63 | buffer = true; 64 | break; 65 | } 66 | } 67 | } 68 | 69 | if buffer { 70 | for i in -buffer_size..(buffer_size + 1) { 71 | for j in -buffer_size..(buffer_size + 1) { 72 | if y as i32 + i < 0 73 | || y as i32 + i >= reduced.len() as i32 74 | || x as i32 + j < 0 75 | || x as i32 + j >= reduced[y].len() as i32 76 | { 77 | continue; 78 | } 79 | 80 | new[(y as i32 + i) as usize][(x as i32 + j) as usize] = 1; 81 | } 82 | } 83 | } 84 | } 85 | } 86 | 87 | new 88 | } 89 | 90 | /// Converts the image to a grid where 0 is a free space and 1 is an obstacle 91 | pub fn image_to_grid(map: &mut Map) -> Vec> { 92 | let mut grid = vec![vec![0; (map.width) as usize]; (map.height) as usize]; 93 | let binding: Vec = map.get_bits(); 94 | for (i, byte) in binding.chunks_exact(4).enumerate() { 95 | let x = i % map.width as usize; 96 | let y = i / map.width as usize; 97 | let alpha = byte[3]; // Alpha channel 98 | if alpha == 0 { 99 | grid[y][x] = 1; // Transparent pixels -> Obstacle 100 | } 101 | } 102 | 103 | // Step 2: Process polygon obstacles 104 | let mut polygons: Vec = vec![]; 105 | for obstacle in &map.obstacles { 106 | if obstacle.len() < 3 { 107 | continue; // Skip invalid polygons 108 | } 109 | let exterior = obstacle 110 | .iter() 111 | .map(|&coords| Coord { 112 | x: coords.0 as f64, 113 | y: coords.1 as f64, 114 | }) 115 | .collect::>(); 116 | 117 | let polygon = Polygon::new(LineString::from(exterior), vec![]); 118 | polygons.push(polygon); 119 | } 120 | 121 | for y in 0..map.height { 122 | for x in 0..map.width { 123 | for polygon in &polygons { 124 | if polygon.contains(&Point::new(x as f64, y as f64)) { 125 | grid[y as usize][x as usize] = 1; // Mark obstacle 126 | } 127 | } 128 | } 129 | } 130 | 131 | // Step 3: Buffer edges of obstacles 132 | grid = buffer_edges(grid); 133 | 134 | grid 135 | } 136 | 137 | #[stubgen] 138 | #[pymethods] 139 | impl Travel { 140 | #[new] 141 | pub fn new( 142 | mut map: Map, 143 | current_location: (u32, u32), 144 | destination: (u32, u32), 145 | ) -> PyResult { 146 | // draw obstacles on the map 147 | let mut grid = image_to_grid(&mut map); 148 | 149 | // If current location or destination is out of bounds, return an error 150 | if current_location.0 >= map.width 151 | || current_location.1 >= map.height 152 | || destination.0 >= map.width 153 | || destination.1 >= map.height 154 | { 155 | return Err(PyErr::new::( 156 | "Current location or destination is out of bounds", 157 | )); 158 | } 159 | // If current location or destination is an obstacle, return an error 160 | if grid[current_location.1 as usize][current_location.0 as usize] == 1 161 | || grid[destination.1 as usize][destination.0 as usize] == 1 162 | { 163 | return Err(PyErr::new::( 164 | "Current location or destination is an obstacle", 165 | )); 166 | } 167 | 168 | // put in start and end 169 | grid[current_location.1 as usize][current_location.0 as usize] = 2; 170 | grid[destination.1 as usize][destination.0 as usize] = 3; 171 | 172 | match astar(&grid) { 173 | Some(path) => Ok(Travel { 174 | map, 175 | computed_path: path, 176 | }), 177 | None => Err(PyErr::new::( 178 | "No path found", 179 | )), 180 | } 181 | } 182 | 183 | /// Displays the map in a black and white view where white are the 184 | /// obstacles and black are the free spaces. This is to debug if 185 | /// a fault is with the pathfinding algorithm or the map reduction 186 | /// algorithm. 187 | /// 188 | /// Parameters 189 | /// --------- 190 | /// map : Map 191 | /// The map to display the black and white view of. 192 | /// 193 | /// Returns 194 | /// ------- 195 | /// list[int] 196 | /// A list of bytes representing the black and white view of the map. 197 | #[staticmethod] 198 | pub fn dbg_map(mut map: Map) -> Vec { 199 | let grid = image_to_grid(&mut map); 200 | let mut long_map = vec![0; map.width as usize * map.height as usize * 4]; 201 | for y in 0..grid.len() { 202 | for x in 0..grid[y].len() { 203 | let byte = match grid[y][x] { 204 | 0 => vec![255, 255, 255, 255], 205 | 1 => vec![0, 0, 0, 255], 206 | 2 => vec![0, 0, 255, 255], 207 | 3 => vec![255, 0, 0, 255], 208 | _ => panic!("Invalid grid value"), 209 | }; 210 | if y * map.width as usize + x + 4 >= long_map.len() { 211 | println!("{:?}", byte); 212 | continue; 213 | } 214 | long_map 215 | [y * map.width as usize * 4 + x * 4..y * map.width as usize * 4 + x * 4 + 4] 216 | .copy_from_slice(&byte); 217 | } 218 | } 219 | long_map 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/tests/map.rs: -------------------------------------------------------------------------------- 1 | use super::utils::{compare_images, get_image_bits}; 2 | use pyo3::prelude::{Py, PyErr, PyRefMut, Python}; 3 | 4 | #[cfg(test)] 5 | mod map_tests { 6 | use super::*; 7 | use crate::structs::map::Map; 8 | use crate::structs::map::MapType; 9 | use crate::structs::map::PathDisplayType; 10 | use crate::structs::map::PathProgressDisplayType; 11 | use crate::structs::map::PathStyle; 12 | use crate::structs::travel::Travel; 13 | 14 | #[test] 15 | fn test_full_map_creation() { 16 | let (image, image_width, image_height) = get_image_bits("test_assets", "map.png"); 17 | let (background, _, _) = get_image_bits("test_assets", "background.png"); 18 | let (expected, _, _) = get_image_bits("test_results", "full.png"); 19 | let map = Map::new( 20 | image.clone(), 21 | image_width, 22 | image_height, 23 | 20, 24 | MapType::Limited, 25 | vec![], 26 | vec![], 27 | vec![], 28 | ); 29 | let travel = Travel::new(map.clone(), (198, 390), (330, 512)).unwrap(); 30 | Python::with_gil(|py| -> Result<(), PyErr> { 31 | let n: Py = Py::new(py, map).expect("Failed to create Py"); 32 | let guard: PyRefMut<'_, Map> = n.bind(py).borrow_mut(); 33 | 34 | let result = Map::draw_background( 35 | Map::with_dot(guard, 198, 390, [255, 0, 0, 255], 5) 36 | .draw_path( 37 | travel, 38 | 1.0, 39 | 2, 40 | PathStyle::DottedWithOutline([255, 0, 0, 255], [255, 255, 255, 255]), 41 | PathDisplayType::BelowMask, 42 | PathProgressDisplayType::Travelled, 43 | ) 44 | .expect("Failed to draw path"), 45 | background, 46 | ) 47 | .expect("Failed to generate bits"); 48 | 49 | compare_images(&result, &expected, &image, image_width, image_height); 50 | 51 | Ok(()) 52 | }) 53 | .expect("Failed to execute Python code"); 54 | } 55 | 56 | #[test] 57 | fn test_map_creation_with_obstacles() { 58 | let (image, image_width, image_height) = get_image_bits("test_assets", "map.png"); 59 | let (expected, _, _) = get_image_bits("test_results", "obstacle.png"); 60 | let mut map = Map::new( 61 | image.clone(), 62 | image_width, 63 | image_height, 64 | 20, 65 | MapType::Limited, 66 | vec![], 67 | vec![], 68 | vec![vec![(160, 240), (134, 253), (234, 257), (208, 239)]], 69 | ); 70 | let travel = Travel::new(map.clone(), (198, 390), (172, 223)).unwrap(); 71 | 72 | let result = map 73 | .draw_path( 74 | travel, 75 | 1.0, 76 | 2, 77 | PathStyle::DottedWithOutline([255, 0, 0, 255], [255, 255, 255, 255]), 78 | PathDisplayType::BelowMask, 79 | PathProgressDisplayType::Travelled, 80 | ) 81 | .expect("Failed to draw path"); 82 | 83 | compare_images(&result, &expected, &image, image_width, image_height); 84 | } 85 | 86 | #[test] 87 | fn test_map_creation_hidden_and_progress() { 88 | let (image, image_width, image_height) = get_image_bits("test_assets", "map.png"); 89 | let (expected, _, _) = get_image_bits("test_results", "progress_and_hidden.png"); 90 | let mut map = Map::new( 91 | image.clone(), 92 | image_width, 93 | image_height, 94 | 20, 95 | MapType::Hidden, 96 | vec![], 97 | vec![], 98 | vec![], 99 | ); 100 | let travel = Travel::new(map.clone(), (198, 390), (330, 512)).unwrap(); 101 | 102 | let result = map 103 | .draw_path( 104 | travel, 105 | 0.5, 106 | 2, 107 | PathStyle::DottedWithOutline([255, 0, 0, 255], [255, 255, 255, 255]), 108 | PathDisplayType::AboveMask, 109 | PathProgressDisplayType::Progress, 110 | ) 111 | .expect("Failed to draw path"); 112 | 113 | compare_images(&result, &expected, &image, image_width, image_height); 114 | } 115 | 116 | #[test] 117 | fn test_wrong_background() { 118 | let (image, _, _) = get_image_bits("test_assets", "map.png"); 119 | let (background, _, _) = get_image_bits("test_assets", "cat.png"); 120 | match Map::draw_background(image, background) { 121 | Ok(_) => panic!("Expected an error, but got a valid image"), 122 | Err(e) => assert_eq!( 123 | e.to_string(), 124 | "ValueError: Background image must have the same size as the map" 125 | ), 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod map; 2 | pub mod path; 3 | pub mod travel; 4 | pub mod utils; 5 | -------------------------------------------------------------------------------- /src/tests/path.rs: -------------------------------------------------------------------------------- 1 | use crate::structs::path::astar; 2 | use crate::structs::path::PathPoint; 3 | 4 | #[test] 5 | fn test_astar_diagonal_path() { 6 | let grid = vec![vec![2, 0, 0], vec![0, 0, 0], vec![0, 0, 3]]; 7 | 8 | let path = astar(&grid).unwrap(); 9 | assert_eq!( 10 | path, 11 | vec![ 12 | PathPoint::from_tuple((0, 0)), 13 | PathPoint::from_tuple((1, 1)), 14 | PathPoint::from_tuple((2, 2)) 15 | ] 16 | ); 17 | } 18 | 19 | #[test] 20 | fn test_astar_example() { 21 | let grid = vec![ 22 | vec![0, 0, 0, 1, 0], 23 | vec![1, 1, 0, 1, 0], 24 | vec![2, 0, 0, 0, 3], 25 | vec![1, 0, 1, 1, 0], 26 | vec![0, 0, 0, 0, 0], 27 | ]; 28 | 29 | let path = astar(&grid).unwrap(); 30 | assert_eq!( 31 | path, 32 | vec![ 33 | PathPoint::from_tuple((0, 2)), 34 | PathPoint::from_tuple((1, 2)), 35 | PathPoint::from_tuple((2, 2)), 36 | PathPoint::from_tuple((3, 2)), 37 | PathPoint::from_tuple((4, 2)) 38 | ] 39 | ); 40 | 41 | let grid = vec![ 42 | vec![0, 0, 0, 1, 0], 43 | vec![1, 1, 0, 1, 0], 44 | vec![0, 0, 0, 0, 3], 45 | vec![1, 0, 1, 1, 0], 46 | vec![2, 0, 0, 0, 0], 47 | ]; 48 | let path = astar(&grid).unwrap(); 49 | assert_eq!( 50 | path, 51 | vec![ 52 | PathPoint::from_tuple((0, 4)), 53 | PathPoint::from_tuple((1, 3)), 54 | PathPoint::from_tuple((2, 2)), 55 | PathPoint::from_tuple((3, 2)), 56 | PathPoint::from_tuple((4, 2)), 57 | ] 58 | ); 59 | } 60 | -------------------------------------------------------------------------------- /src/tests/travel.rs: -------------------------------------------------------------------------------- 1 | use super::utils::{compare_images, get_image_bits}; 2 | 3 | #[cfg(test)] 4 | mod travel_tests { 5 | use super::*; 6 | use crate::structs::map::Map; 7 | use crate::structs::map::MapType; 8 | use crate::structs::travel::Travel; 9 | 10 | #[test] 11 | fn test_dbg_map() { 12 | let (image, image_width, image_height) = get_image_bits("test_assets", "map.png"); 13 | let (expected, _, _) = get_image_bits("test_results", "debug_with_obstacle.png"); 14 | let map = Map::new( 15 | image.clone(), 16 | image_width, 17 | image_height, 18 | 20, 19 | MapType::Limited, 20 | vec![], 21 | vec![], 22 | vec![vec![(160, 240), (134, 253), (234, 257), (208, 239)]], 23 | ); 24 | let result = Travel::dbg_map(map); 25 | compare_images(&result, &expected, &image, image_width, image_height); 26 | } 27 | 28 | #[test] 29 | fn test_unreachable_path() { 30 | let (image, image_width, image_height) = get_image_bits("test_assets", "map.png"); 31 | let map = Map::new( 32 | image.clone(), 33 | image_width, 34 | image_height, 35 | 20, 36 | MapType::Limited, 37 | vec![], 38 | vec![], 39 | vec![vec![(160, 240), (134, 253), (234, 257), (208, 239)]], 40 | ); 41 | // Test going into the obstacle 42 | match Travel::new(map.clone(), (198, 390), (158, 250)) { 43 | Ok(_) => panic!("Expected an error, but got a valid travel object"), 44 | Err(e) => assert_eq!( 45 | e.to_string(), 46 | "ValueError: Current location or destination is an obstacle" 47 | ), 48 | } 49 | 50 | // Test going into the boarder 51 | match Travel::new(map.clone(), (198, 390), (100, 425)) { 52 | Ok(_) => panic!("Expected an error, but got a valid travel object"), 53 | Err(e) => assert_eq!( 54 | e.to_string(), 55 | "ValueError: Current location or destination is an obstacle" 56 | ), 57 | } 58 | 59 | // Test going out of bounds 60 | match Travel::new(map.clone(), (198, 390), (1000, 1000)) { 61 | Ok(_) => panic!("Expected an error, but got a valid travel object"), 62 | Err(e) => assert_eq!( 63 | e.to_string(), 64 | "ValueError: Current location or destination is out of bounds" 65 | ), 66 | } 67 | 68 | // Test going to unreachable island 69 | match Travel::new(map.clone(), (198, 390), (60, 90)) { 70 | Ok(_) => panic!("Expected an error, but got a valid travel object"), 71 | Err(e) => assert_eq!(e.to_string(), "ValueError: No path found"), 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/tests/utils.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | extern crate image; 3 | 4 | // fn python_install_pillow(py: Python) -> Bound<'_, PyModule> { 5 | // let pip = py.import("pip").unwrap(); 6 | // pip.call_method1( 7 | // "main", 8 | // (PyList::new(py, vec!["install", "pillow"]).unwrap(),), 9 | // ) 10 | // .unwrap(); 11 | // py.import("PIL.Image").unwrap() 12 | // } 13 | 14 | // fn get_image_bits(py: Python, directory: &str, filename: &str) -> (Vec, u32, u32) { 15 | // let sys = py.import("sys").unwrap(); 16 | // let path = sys.getattr("path").unwrap(); 17 | // path.call_method1("append", ("env/lib/python3.13/site-packages",)).unwrap(); // append my venv path 18 | // // Ensure Pillow is installed 19 | // let pillow= py.import("PIL.Image") 20 | // .or_else(|_| { 21 | // Ok::<_, PyErr>(python_install_pillow(py)) 22 | // }).unwrap(); 23 | // let image = pillow 24 | // .call_method1("open", (format!("{}/{}", directory, filename),)) 25 | // .unwrap(); 26 | // let image = image.call_method1("convert", ("RGBA",)).unwrap(); 27 | // let image_width = image 28 | // .getattr("width") 29 | // .unwrap() 30 | // .extract::() 31 | // .unwrap(); 32 | // let image_height = image 33 | // .getattr("height") 34 | // .unwrap() 35 | // .extract::() 36 | // .unwrap(); 37 | // let image = image.call_method1("tobytes", ("raw", "RGBA", 0)).unwrap(); 38 | // let image = image.extract::>().unwrap(); 39 | // (image, image_width, image_height) 40 | // } 41 | 42 | /// Get image bits using the Rust image library 43 | pub fn get_image_bits(directory: &str, filename: &str) -> (Vec, u32, u32) { 44 | let img = image::open(format!("{}/{}", directory, filename)).unwrap(); 45 | let img = img.to_rgba8(); 46 | let (width, height) = img.dimensions(); 47 | let img = img.into_raw(); 48 | (img, width, height) 49 | } 50 | 51 | /// Compare the expected and actual images. 52 | pub fn compare_images( 53 | result: &[u8], 54 | expected: &[u8], 55 | original: &[u8], 56 | image_width: u32, 57 | image_height: u32, 58 | ) { 59 | assert_eq!(result.len(), expected.len()); 60 | 61 | let difference = result 62 | .chunks_exact(4) 63 | .zip(expected.chunks_exact(4)) 64 | .enumerate() 65 | .filter(|(i, (a, b))| { 66 | // This means: if a is not equal to b, and a is not transparent 67 | // if a is transparent, then it should be equal to the original. 68 | // This is because there is a lot of weirdness with somewhat transparent pixels 69 | // which I spent 3 days debugging, choosing to ignore them for now. 70 | a != b && ([0, 255].contains(&a[3]) || (**a == original[*i..*i + 4])) 71 | }) 72 | .collect::>(); 73 | 74 | if !difference.is_empty() { 75 | // Save both images to the logs folder for debugging 76 | log_image_difference( 77 | result, 78 | expected, 79 | original, 80 | image_width, 81 | image_height, 82 | "test_map_creation_with_obstacles", 83 | ); 84 | } 85 | assert_eq!(difference.len(), 0); // Easier to debug in logs 86 | } 87 | 88 | /// Logs the difference between two images by saving them to the logs folder. 89 | /// This helps to debug the differences between the expected and actual images. 90 | fn log_image_difference( 91 | result: &[u8], 92 | expected: &[u8], 93 | read: &[u8], 94 | image_width: u32, 95 | image_height: u32, 96 | name: &str, 97 | ) { 98 | let result_image = image::ImageBuffer::, Vec>::from_raw( 99 | image_width, 100 | image_height, 101 | result.to_vec(), 102 | ) 103 | .unwrap(); 104 | result_image 105 | .save(format!("src/tests/logs/{}_result.png", name)) 106 | .expect("Failed to save result image"); 107 | 108 | // Generate a new image with the differences 109 | let mut diff_image = image::ImageBuffer::new(image_width, image_height); 110 | for (x, y, pixel) in diff_image.enumerate_pixels_mut() { 111 | let index = (y * image_width + x) as usize * 4; 112 | if result[index] != expected[index] { 113 | // Log the difference 114 | println!( 115 | "Difference at pixel ({}, {}): result: {:?}, expected: {:?} (read: {:?})", 116 | x, 117 | y, 118 | &result[index..index + 4], 119 | &expected[index..index + 4], 120 | &read[index..index + 4] 121 | ); 122 | // set the pixel color to abs(expected - result) 123 | *pixel = image::Rgba([ 124 | (result[index] as i32 - expected[index] as i32).unsigned_abs() as u8, 125 | (result[index + 1] as i32 - expected[index + 1] as i32).unsigned_abs() as u8, 126 | (result[index + 2] as i32 - expected[index + 2] as i32).unsigned_abs() as u8, 127 | 255, 128 | ]); 129 | } else { 130 | *pixel = image::Rgba([0u8, 0u8, 0u8, 0u8]); // Transparent for no difference 131 | } 132 | } 133 | diff_image 134 | .save(format!("src/tests/logs/{}_diff.png", name)) 135 | .expect("Failed to save diff image"); 136 | } 137 | -------------------------------------------------------------------------------- /test_assets/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/test_assets/background.png -------------------------------------------------------------------------------- /test_assets/cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/test_assets/cat.png -------------------------------------------------------------------------------- /test_assets/map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/test_assets/map.png -------------------------------------------------------------------------------- /test_results/debug_with_obstacle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/test_results/debug_with_obstacle.png -------------------------------------------------------------------------------- /test_results/full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/test_results/full.png -------------------------------------------------------------------------------- /test_results/obstacle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/test_results/obstacle.png -------------------------------------------------------------------------------- /test_results/progress_and_hidden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kile/rpg_map/1efae8e0c6c853ee630feb947e476856faf396fb/test_results/progress_and_hidden.png -------------------------------------------------------------------------------- /workaround/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "proc-macro2" 7 | version = "1.0.94" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" 10 | dependencies = [ 11 | "unicode-ident", 12 | ] 13 | 14 | [[package]] 15 | name = "quote" 16 | version = "1.0.40" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 19 | dependencies = [ 20 | "proc-macro2", 21 | ] 22 | 23 | [[package]] 24 | name = "syn" 25 | version = "2.0.100" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" 28 | dependencies = [ 29 | "proc-macro2", 30 | "quote", 31 | "unicode-ident", 32 | ] 33 | 34 | [[package]] 35 | name = "unicode-ident" 36 | version = "1.0.18" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 39 | 40 | [[package]] 41 | name = "workaround" 42 | version = "0.1.0" 43 | dependencies = [ 44 | "quote", 45 | "syn", 46 | ] 47 | -------------------------------------------------------------------------------- /workaround/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "workaround" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | quote = "1.0.40" 8 | syn = {version = "2.0.100", features = ["full"]} 9 | 10 | [lib] 11 | proc-macro = true -------------------------------------------------------------------------------- /workaround/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use quote::quote; 3 | use syn::{parse_macro_input, Item}; 4 | 5 | #[proc_macro_attribute] 6 | pub fn stubgen(_attrs: TokenStream, item: TokenStream) -> TokenStream { 7 | let input = parse_macro_input!(item as Item); 8 | 9 | // Wrap the item with #[cfg_attr(feature = "stubgen", ...)] 10 | let output = match input { 11 | Item::Struct(s) => { 12 | quote! { 13 | #[cfg_attr(feature = "stubgen", pyo3_stub_gen::derive::gen_stub_pyclass)] 14 | #s 15 | } 16 | } 17 | Item::Impl(i) => { 18 | quote! { 19 | #[cfg_attr(feature = "stubgen", pyo3_stub_gen::derive::gen_stub_pymethods)] 20 | #i 21 | } 22 | } 23 | Item::Fn(f) => { 24 | quote! { 25 | #[cfg_attr(feature = "stubgen", pyo3_stub_gen::derive::gen_stub_pyfunction)] 26 | #f 27 | } 28 | } 29 | Item::Enum(e) => { 30 | quote! { 31 | #[cfg_attr(feature = "stubgen", pyo3_stub_gen::derive::gen_stub_pyclass_enum)] 32 | #e 33 | } 34 | } 35 | _ => { 36 | quote! { 37 | #input 38 | } 39 | } 40 | }; 41 | 42 | output.into() 43 | } 44 | --------------------------------------------------------------------------------