diff --git a/.appveyor/install_python.ps1 b/.appveyor/install_python.ps1
deleted file mode 100644
index fc82eff1..00000000
--- a/.appveyor/install_python.ps1
+++ /dev/null
@@ -1,30 +0,0 @@
-if ($env:PYPY -or $env:PYPY3) {
- if($env:PYPY3){
- $env:PYPY_EXE='pypy3.exe'
- $env:PYPY=$env:PYPY3
- } else {
- $env:PYPY_EXE='pypy.exe'
- }
- $env:PYTHON = 'C:\' + $env:PYPY + '\' + $env:PYPY_EXE
- $env:PATH += ';' + 'C:\' + $env:PYPY + '\'
- $PYPY_DOWNLOAD = 'https://bitbucket.org/pypy/pypy/downloads/' + $env:PYPY + '.zip'
- Invoke-WebRequest $PYPY_DOWNLOAD -OutFile C:\pypy.zip
- & '7z' x C:\pypy.zip -oC:\
- & $env:PYTHON -m ensurepip
-}
-if ($env:WEB_PYTHON) {
- $PYTHON_INSTALLER = 'C:\python-webinstall.exe'
- Start-FileDownload $env:WEB_PYTHON -FileName $PYTHON_INSTALLER
- Start-Process $PYTHON_INSTALLER -Wait -ArgumentList "/quiet InstallAllUsers=1 TargetDir=C:\UserPython Include_doc=0 Include_launcher=0 Include_test=0 Shortcuts=0"
- $env:PYTHON = 'C:\UserPython\python.exe'
-}
-& $env:PYTHON -m pip install --disable-pip-version-check virtualenv
-& $env:PYTHON -m virtualenv venv
-
-if ($env:PYPY) {
- $env:ACTIVATE_VENV='venv\bin\activate.bat'
-} else {
- $env:ACTIVATE_VENV='venv\Scripts\activate.bat'
-}
-
-if($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode ) }
diff --git a/.codeclimate.yml b/.codeclimate.yml
deleted file mode 100644
index 834a6236..00000000
--- a/.codeclimate.yml
+++ /dev/null
@@ -1,27 +0,0 @@
----
-engines:
- duplication:
- enabled: true
- config:
- languages:
- - ruby
- - javascript
- - python
- - php
- exclude_paths:
- - examples/
- fixme:
- enabled: true
- radon:
- enabled: true
-ratings:
- paths:
- - "**.inc"
- - "**.js"
- - "**.jsx"
- - "**.module"
- - "**.php"
- - "**.py"
- - "**.rb"
-exclude_paths:
-- tests/
diff --git a/codecov.yml b/.codecov.yml
similarity index 60%
rename from codecov.yml
rename to .codecov.yml
index dd31e005..79c5a87d 100644
--- a/codecov.yml
+++ b/.codecov.yml
@@ -3,6 +3,7 @@ coverage:
project:
default:
target: auto
- threshold: 0.1
+ threshold: "0.1"
base: auto
-comment: off
+comment:
+ require_changes: true
diff --git a/.editorconfig b/.editorconfig
index cd57b23f..bd4f19f2 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -22,3 +22,7 @@ indent_size = 2
[*.{h,c,cpp}]
indent_style = space
indent_size = 2
+
+[*.json]
+indent_style = space
+indent_size = 4
diff --git a/.gitattributes b/.gitattributes
index 740e182d..2dc9f280 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -19,6 +19,3 @@
# Custom for this project
*.bat eol=crlf
*.txt eol=crlf
-
-# Fix an issue with Python 2 on Linux
-tcod/libtcod_cdef.h eol=lf
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 00000000..ac5a1634
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,3 @@
+# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/displaying-a-sponsor-button-in-your-repository
+
+github: HexDecimal
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 00000000..dc89cbfd
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,28 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: bug
+assignees: ''
+
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior. A minimal code example is recommended.
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Screenshots**
+If applicable, add screenshots to help explain your problem.
+
+**Desktop (please complete the following information):**
+ - OS: [e.g. Windows 11]
+ - Python version [e.g. Python 3.11, PyPy 3.9]
+ - tcod version [e.g. 16.0.2]
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 00000000..11fc491e
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,20 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: enhancement
+assignees: ''
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000..2b2eb8c4
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,15 @@
+# Keep GitHub Actions up to date with GitHub's Dependabot...
+# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot
+# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem
+version: 2
+updates:
+ - package-ecosystem: github-actions
+ directory: /
+ groups:
+ github-actions:
+ patterns:
+ - "*" # Group all Actions updates into a single larger pull request
+ schedule:
+ interval: weekly
+ cooldown:
+ default-days: 7
diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml
new file mode 100644
index 00000000..7896754a
--- /dev/null
+++ b/.github/workflows/python-package.yml
@@ -0,0 +1,399 @@
+# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
+# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
+
+name: Package
+
+on:
+ workflow_dispatch:
+ push:
+ pull_request:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: true
+
+defaults:
+ run:
+ shell: bash
+
+env:
+ git-depth: 0 # Depth to search for tags.
+ sdl-version: "3.2.16" # SDL version to bundle
+
+jobs:
+ ruff:
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+ - name: Install Ruff
+ run: pip install ruff
+ - name: Ruff Check
+ run: ruff check . --fix-only --exit-non-zero-on-fix --output-format=github
+ - name: Ruff Format
+ run: ruff format . --check
+
+ mypy:
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+ - name: Checkout submodules
+ run: git submodule update --init --recursive --depth 1
+ - name: Install typing dependencies
+ run: pip install mypy pytest -r requirements.txt
+ - name: Mypy
+ uses: liskin/gh-problem-matcher-wrap@v3
+ with:
+ linters: mypy
+ run: mypy --show-column-numbers
+
+ sdist:
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ steps:
+ - uses: libsdl-org/setup-sdl@1894460666e4fe0c6603ea0cce5733f1cca56ba1
+ with:
+ install-linux-dependencies: true
+ build-type: "Debug"
+ version: ${{ env.sdl-version }}
+ - uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+ fetch-depth: ${{ env.git-depth }}
+ - name: Checkout submodules
+ run: git submodule update --init --recursive --depth 1
+ - name: Install build
+ run: pip install build
+ - name: Build source distribution
+ run: python -m build --sdist
+ - uses: actions/upload-artifact@v7
+ with:
+ name: sdist
+ path: dist/tcod-*.tar.gz
+ retention-days: 7
+ compression-level: 0
+
+ # This makes sure that the latest versions of the SDL headers parse correctly.
+ parse_sdl:
+ needs: [ruff, mypy]
+ runs-on: ${{ matrix.os }}
+ timeout-minutes: 5
+ strategy:
+ matrix:
+ os: ["windows-latest", "macos-latest"]
+ sdl-version: ["3.2.16"]
+ fail-fast: true
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+ fetch-depth: ${{ env.git-depth }}
+ - name: Checkout submodules
+ run: git submodule update --init --recursive --depth 1
+ - uses: actions/setup-python@v6
+ with:
+ python-version: "3.x"
+ - name: Install build dependencies
+ run: pip install -r requirements.txt
+ - name: Test SDL parsing
+ run: python build_sdl.py
+ env:
+ SDL_VERSION: ${{ matrix.sdl-version }}
+
+ build:
+ needs: [ruff, mypy, sdist]
+ runs-on: ${{ matrix.os }}
+ timeout-minutes: 15
+ strategy:
+ matrix:
+ os: ["ubuntu-latest", "windows-latest"]
+ python-version: ["3.10", "3.14t", "pypy-3.10"]
+ architecture: ["x64"]
+ include:
+ - os: "windows-latest"
+ python-version: "3.10"
+ architecture: "x86"
+ - os: "windows-latest"
+ python-version: "3.14t"
+ architecture: "x86"
+ - os: "windows-11-arm"
+ python-version: "3.11"
+ architecture: "arm64"
+ - os: "windows-11-arm"
+ python-version: "3.14t"
+ architecture: "arm64"
+ fail-fast: false
+
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+ fetch-depth: ${{ env.git-depth }}
+ - name: Checkout submodules
+ run: git submodule update --init --recursive --depth 1
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v6
+ with:
+ python-version: ${{ matrix.python-version }}
+ architecture: ${{ matrix.architecture }}
+ - name: Install APT dependencies
+ if: runner.os == 'Linux'
+ run: |
+ sudo apt-get update
+ sudo apt-get install xvfb
+ - uses: libsdl-org/setup-sdl@1894460666e4fe0c6603ea0cce5733f1cca56ba1
+ if: runner.os == 'Linux'
+ with:
+ install-linux-dependencies: true
+ build-type: "Release"
+ version: ${{ env.sdl-version }}
+ - name: Install Python dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install pytest pytest-cov pytest-benchmark pytest-timeout build
+ if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
+ - name: Initialize package
+ run: pip install -e . # Install the package in-place.
+ - name: Build package
+ run: python -m build
+ - name: Test with pytest
+ if: runner.os == 'Windows'
+ run: pytest --cov-report=xml --timeout=300
+ - name: Test with pytest (Xvfb)
+ if: always() && runner.os != 'Windows'
+ run: xvfb-run -e /tmp/xvfb.log --server-num=$RANDOM --auto-servernum pytest --cov-report=xml --timeout=300
+ - name: Xvfb logs
+ if: runner.os != 'Windows'
+ run: cat /tmp/xvfb.log
+ - uses: codecov/codecov-action@v6
+ - uses: actions/upload-artifact@v7
+ if: runner.os == 'Windows'
+ with:
+ name: wheels-windows-${{ matrix.architecture }}-${{ matrix.python-version }}
+ path: dist/*.whl
+ retention-days: 7
+ compression-level: 0
+
+ test-docs:
+ needs: [ruff, mypy, sdist]
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+ steps:
+ - uses: libsdl-org/setup-sdl@1894460666e4fe0c6603ea0cce5733f1cca56ba1
+ if: runner.os == 'Linux'
+ with:
+ install-linux-dependencies: true
+ build-type: "Debug"
+ version: ${{ env.sdl-version }}
+ - uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+ fetch-depth: ${{ env.git-depth }}
+ - name: Checkout submodules
+ run: git submodule update --init --recursive --depth 1
+ - name: Install Python dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install -r docs/requirements.txt
+ - name: Install package
+ run: pip install -e .
+ env:
+ TDL_BUILD: DEBUG
+ - name: Test doc generation
+ working-directory: docs
+ run: python -m sphinx -T -E -W --keep-going . _build/html
+
+ tox:
+ needs: [ruff, sdist]
+ runs-on: ${{ matrix.os }}
+ timeout-minutes: 15
+ strategy:
+ matrix:
+ os: ["ubuntu-latest"] # "windows-latest" disabled due to free-threaded build issues
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+ fetch-depth: ${{ env.git-depth }}
+ - name: Checkout submodules
+ run: git submodule update --init --depth 1
+ - name: Set up Python
+ uses: actions/setup-python@v6
+ with:
+ python-version: "3.x"
+ - name: Install Python dependencies
+ run: |
+ python -m pip install --upgrade pip tox
+ - uses: libsdl-org/setup-sdl@1894460666e4fe0c6603ea0cce5733f1cca56ba1
+ if: runner.os == 'Linux'
+ with:
+ install-linux-dependencies: true
+ build-type: "Debug"
+ version: ${{ env.sdl-version }}
+ - name: Run tox
+ run: |
+ tox -vv
+
+ linux-wheels:
+ needs: [ruff, mypy, sdist]
+ runs-on: ${{ matrix.arch == 'aarch64' && 'ubuntu-24.04-arm' || 'ubuntu-latest'}}
+ timeout-minutes: 15
+ strategy:
+ matrix:
+ arch: ["x86_64", "aarch64"]
+ build: ["cp310-manylinux*", "pp310-manylinux*", "cp314t-manylinux*"]
+ env:
+ BUILD_DESC: ""
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+ fetch-depth: ${{ env.git-depth }}
+ - name: Checkout submodules
+ run: git submodule update --init --recursive --depth 1
+ - name: Build wheels
+ uses: pypa/cibuildwheel@v3.1.4
+ env:
+ CIBW_BUILD: ${{ matrix.build }}
+ CIBW_ARCHS_LINUX: ${{ matrix.arch }}
+ CIBW_MANYLINUX_*_IMAGE: manylinux2014
+ CIBW_MANYLINUX_PYPY_X86_64_IMAGE: manylinux2014
+ CIBW_MANYLINUX_PYPY_AARCH64_IMAGE: manylinux2014
+ CIBW_BEFORE_ALL_LINUX: >
+ yum install -y epel-release &&
+ yum-config-manager --enable epel &&
+ yum install -y gcc git-core make cmake \
+ alsa-lib-devel pulseaudio-libs-devel \
+ libX11-devel libXext-devel libXrandr-devel libXcursor-devel libXfixes-devel \
+ libXi-devel libXScrnSaver-devel dbus-devel ibus-devel \
+ systemd-devel mesa-libGL-devel libxkbcommon-devel mesa-libGLES-devel \
+ mesa-libEGL-devel vulkan-devel wayland-devel wayland-protocols-devel \
+ libdrm-devel mesa-libgbm-devel libusb-devel
+ git clone --depth 1 --branch release-${{env.sdl-version}} https://github.com/libsdl-org/SDL.git sdl_repo &&
+ cmake -S sdl_repo -B sdl_build &&
+ cmake --build sdl_build --config Release &&
+ cmake --install sdl_build --config Release --prefix /usr/local &&
+ cp --verbose /usr/local/lib64/pkgconfig/sdl3.pc /lib64/pkgconfig/sdl3.pc
+ CIBW_BEFORE_TEST: pip install numpy
+ CIBW_TEST_COMMAND: python -c "import tcod.context"
+ # Skip test on emulated architectures
+ CIBW_TEST_SKIP: "*_aarch64"
+ - name: Remove asterisk from label
+ env:
+ BUILD_DESC: ${{ matrix.build }}
+ run: |
+ BUILD_DESC=${BUILD_DESC//\*}
+ echo BUILD_DESC=${BUILD_DESC} >> $GITHUB_ENV
+ - name: Archive wheel
+ uses: actions/upload-artifact@v7
+ with:
+ name: wheels-linux-${{ matrix.arch }}-${{ env.BUILD_DESC }}
+ path: wheelhouse/*.whl
+ retention-days: 7
+ compression-level: 0
+
+ build-macos:
+ needs: [ruff, mypy, sdist]
+ runs-on: "macos-14"
+ timeout-minutes: 15
+ strategy:
+ fail-fast: true
+ matrix:
+ python: ["cp310-*_universal2", "cp314t-*_universal2", "pp310-*"]
+ env:
+ PYTHON_DESC: ""
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+ fetch-depth: ${{ env.git-depth }}
+ - name: Checkout submodules
+ run: git submodule update --init --recursive --depth 1
+ - uses: actions/setup-python@v6
+ with:
+ python-version: "3.x"
+ - name: Install Python dependencies
+ run: pip install -r requirements.txt
+ - name: Prepare package
+ # Downloads SDL for the later step.
+ run: python build_sdl.py
+ - name: Build wheels
+ uses: pypa/cibuildwheel@v3.1.4
+ env:
+ CIBW_BUILD: ${{ matrix.python }}
+ CIBW_ARCHS_MACOS: x86_64 arm64 universal2
+ CIBW_BEFORE_BUILD_MACOS: pip install --upgrade delocate
+ CIBW_BEFORE_TEST: pip install numpy
+ CIBW_TEST_COMMAND: python -c "import tcod.context"
+ CIBW_TEST_SKIP: "pp* *-macosx_arm64 *-macosx_universal2:arm64"
+ MACOSX_DEPLOYMENT_TARGET: "10.13"
+ - name: Remove asterisk from label
+ env:
+ PYTHON_DESC: ${{ matrix.python }}
+ run: |
+ PYTHON_DESC=${PYTHON_DESC//\*/X}
+ echo PYTHON_DESC=${PYTHON_DESC} >> $GITHUB_ENV
+ - name: Archive wheel
+ uses: actions/upload-artifact@v7
+ with:
+ name: wheels-macos-${{ env.PYTHON_DESC }}
+ path: wheelhouse/*.whl
+ retention-days: 7
+ compression-level: 0
+
+ pyodide:
+ needs: [ruff, mypy, sdist]
+ runs-on: ubuntu-24.04
+ timeout-minutes: 15
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+ fetch-depth: ${{ env.git-depth }}
+ - name: Checkout submodules
+ run: git submodule update --init --recursive --depth 1
+ - uses: libsdl-org/setup-sdl@1894460666e4fe0c6603ea0cce5733f1cca56ba1
+ with:
+ install-linux-dependencies: true
+ build-type: "Debug"
+ version: "3.2.4" # Should be equal or less than the version used by Emscripten
+ - uses: pypa/cibuildwheel@v3.1.4
+ env:
+ CIBW_BUILD: cp313-pyodide_wasm32
+ CIBW_PLATFORM: pyodide
+ - name: Archive wheel
+ uses: actions/upload-artifact@v7
+ with:
+ name: pyodide
+ path: wheelhouse/*.whl
+ retention-days: 30
+ compression-level: 0
+
+ publish:
+ needs: [sdist, build, build-macos, linux-wheels, pyodide]
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ if: github.ref_type == 'tag'
+ environment:
+ name: pypi
+ url: https://pypi.org/project/tcod/${{ github.ref_name }}
+ permissions:
+ id-token: write # Attestation
+ steps:
+ - uses: actions/download-artifact@v8
+ with:
+ name: sdist
+ path: dist/
+ - uses: actions/download-artifact@v8
+ with:
+ pattern: wheels-*
+ path: dist/
+ merge-multiple: true
+ - uses: pypa/gh-action-pypi-publish@release/v1
+ with:
+ skip-existing: true
diff --git a/.github/workflows/release-on-tag.yml b/.github/workflows/release-on-tag.yml
new file mode 100644
index 00000000..d2e52c2f
--- /dev/null
+++ b/.github/workflows/release-on-tag.yml
@@ -0,0 +1,28 @@
+on:
+ push:
+ tags:
+ - "*.*.*"
+
+name: Create Release
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: false
+
+jobs:
+ build:
+ name: Create Release
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ permissions:
+ contents: write # Publish GitHub Releases
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+ - name: Generate body
+ run: scripts/get_release_description.py | tee release_body.md
+ - name: Create Release
+ id: create_release
+ # https://cli.github.com/manual/gh_release_create
+ run: gh release create "${GITHUB_REF_NAME}" --verify-tag --notes-file release_body.md
diff --git a/.github/zizmor.yaml b/.github/zizmor.yaml
new file mode 100644
index 00000000..b247c2d3
--- /dev/null
+++ b/.github/zizmor.yaml
@@ -0,0 +1,7 @@
+rules:
+ anonymous-definition:
+ disable: true
+ excessive-permissions:
+ disable: true
+ unpinned-uses:
+ disable: true
diff --git a/.gitignore b/.gitignore
index a3676581..28aabdcd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -55,15 +55,24 @@ docs/_build/
# PyBuilder
target/
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
# Custom
Release/
tcod/_*.c
tcod/_*.pyd
.benchmarks
-dependencies/SDL2*
+dependencies/SDL*
tcod/x86
tcod/x64
-tcod/SDL2.framework
+tcod/SDL?.framework
deb_dist/
*.tar.gz
tcod/version.py
@@ -73,3 +82,5 @@ debian/files
debian/python*
.pytest_cache
Thumbs.db
+.mypy_cache/
+.ruff_cache/
diff --git a/.gitmodules b/.gitmodules
index fcc1ec6c..4f2f7179 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,4 @@
[submodule "libtcod"]
path = libtcod
url = https://github.com/libtcod/libtcod
+ shallow = true
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 00000000..6baa9d9b
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,28 @@
+# See https://pre-commit.com for more information
+# See https://pre-commit.com/hooks.html for more hooks
+ci:
+ autoupdate_schedule: quarterly
+repos:
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v6.0.0
+ hooks:
+ - id: trailing-whitespace
+ - id: end-of-file-fixer
+ - id: check-yaml
+ - id: check-added-large-files
+ - id: check-shebang-scripts-are-executable
+ - id: check-merge-conflict
+ - id: check-toml
+ - id: debug-statements
+ - id: fix-byte-order-marker
+ - id: detect-private-key
+ - repo: https://github.com/astral-sh/ruff-pre-commit
+ rev: v0.15.9
+ hooks:
+ - id: ruff-check
+ args: [--fix-only, --exit-non-zero-on-fix]
+ - id: ruff-format
+ - repo: https://github.com/zizmorcore/zizmor-pre-commit
+ rev: v1.23.1
+ hooks:
+ - id: zizmor
diff --git a/.pyup.yml b/.pyup.yml
index bf632f0b..fa58aa6e 100644
--- a/.pyup.yml
+++ b/.pyup.yml
@@ -1,4 +1,4 @@
-# autogenerated pyup.io config file
+# autogenerated pyup.io config file
# see https://pyup.io/docs/configuration/ for all available options
update: false
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
new file mode 100644
index 00000000..098d76db
--- /dev/null
+++ b/.readthedocs.yaml
@@ -0,0 +1,40 @@
+# .readthedocs.yaml
+# Read the Docs configuration file
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
+
+version: 2
+
+build:
+ os: ubuntu-24.04
+ tools:
+ python: "3.11"
+ apt_packages:
+ - build-essential
+ - make
+ - pkg-config
+ - cmake
+ - ninja-build
+ jobs:
+ pre_install:
+ - git clone --depth 1 --branch release-3.2.16 https://github.com/libsdl-org/SDL.git sdl_repo
+ - cmake -S sdl_repo -B sdl_build -D CMAKE_INSTALL_PREFIX=~/.local
+ - cmake --build sdl_build --config Debug
+ - cmake --install sdl_build
+
+submodules:
+ include: all
+
+# Build documentation in the docs/ directory with Sphinx
+sphinx:
+ configuration: docs/conf.py
+ fail_on_warning: true
+
+# If using Sphinx, optionally build your docs in additional formats such as PDF
+formats: all
+
+# Optionally declare the Python requirements required to build your docs
+python:
+ install:
+ - requirements: docs/requirements.txt
+ - method: pip
+ path: .
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index b4ff44fe..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,67 +0,0 @@
-language: python
-matrix:
- include:
- - os: linux
- python: 3.5
- env: CC=gcc-8 CXX=g++-8
- - os: linux
- python: 3.6
- env: CC=gcc-8 CXX=g++-8
- - os: linux
- python: 3.7
- dist: xenial
- sudo: required
- env: CC=gcc-8 CXX=g++-8
- - os: linux
- python: nightly
- dist: xenial
- sudo: required
- env: CC=gcc-8 CXX=g++-8
- - os: osx
- language: generic
- env: MB_PYTHON_VERSION=3.7.1
- allow_failures:
- - python: nightly
-
- fast_finish: true
-
-# sudo is required to prevent xvfb crashes from container-based workers.
-sudo: required
-dist: trusty
-cache: pip
-
-addons:
- apt:
- sources:
- - ubuntu-toolchain-r-test
- packages:
- - g++-8
- - libsdl2-dev
-
-before_install:
-- source .travis/before_install.sh
-- 'if [[ "$TRAVIS_OS_NAME" == "osx" && "$USE_OPENMP" == "true" ]]; then source .travis/install_openmp.sh; fi'
-- source .travis/install_python.sh
-- 'wget https://bootstrap.pypa.io/get-pip.py'
-- python get-pip.py
-- pip install --upgrade setuptools
-- pip install --pre --upgrade wheel
-- pip install --requirement requirements.txt
-install:
-- if [[ -z "$TRAVIS_TAG" ]]; then export BUILD_FLAGS="-g"; fi
-- python setup.py build $BUILD_FLAGS develop bdist_wheel --py-limited-api=cp35
-- 'if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then python -m pip install delocate; fi'
-- 'if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then delocate-wheel -v dist/*.whl; fi'
-- 'if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then delocate-listdeps --all dist/*.whl; fi'
-before_script:
-- pip install pytest pytest-cov
-script:
-- pytest -v
-after_success:
-- pip install codacy-coverage python-coveralls codecov
-- codecov
-- coveralls
-- coverage xml
-- if [[ -n "$CODACY_PROJECT_TOKEN" ]]; then python-codacy-coverage -r coverage.xml; fi
-- if [[ -n "$TRAVIS_TAG" ]]; then pip install --upgrade twine pyOpenSSL; fi
-- if [[ -n "$TRAVIS_TAG" && "$TRAVIS_OS_NAME" == "osx" ]]; then twine upload --skip-existing dist/*; fi
diff --git a/.travis/before_install.sh b/.travis/before_install.sh
deleted file mode 100644
index d5b30c61..00000000
--- a/.travis/before_install.sh
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/bash
-if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
-
- # Update SDL2 to a recent version.
- wget -O - https://www.libsdl.org/release/SDL2-2.0.8.tar.gz | tar xz
- (cd SDL2-* && ./configure --prefix=$HOME/.local && make -j 3 install)
- PATH=~/.local/bin:$PATH
-fi
diff --git a/.travis/install_openmp.sh b/.travis/install_openmp.sh
deleted file mode 100755
index ed440425..00000000
--- a/.travis/install_openmp.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/bin/sh
-brew update
-brew install llvm
-export PATH=/usr/local/opt/llvm/bin:$PATH
-export LDFLAGS=-L/usr/local/opt/llvm/lib $LDFLAGS
-export CPPFLAGS=-I/usr/local/opt/llvm/include $CPPFLAGS
-# Installs clang with OpenMP via brew
diff --git a/.travis/install_python.sh b/.travis/install_python.sh
deleted file mode 100755
index b265b61b..00000000
--- a/.travis/install_python.sh
+++ /dev/null
@@ -1,238 +0,0 @@
-#!/bin/bash
-# *********************
-# Copyright and License
-# *********************
-
-# The multibuild package, including all examples, code snippets and attached
-# documentation is covered by the 2-clause BSD license.
-
- # Copyright (c) 2013-2016, Matt Terry and Matthew Brett; all rights
- # reserved.
-
- # Redistribution and use in source and binary forms, with or without
- # modification, are permitted provided that the following conditions are
- # met:
-
- # 1. Redistributions of source code must retain the above copyright notice,
- # this list of conditions and the following disclaimer.
-
- # 2. Redistributions in binary form must reproduce the above copyright
- # notice, this list of conditions and the following disclaimer in the
- # documentation and/or other materials provided with the distribution.
-
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
- # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
- # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
- # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-set -e
-
-# Work round bug in travis xcode image described at
-# https://github.com/direnv/direnv/issues/210
-shell_session_update() { :; }
-
-MACPYTHON_URL=https://www.python.org/ftp/python
-PYPY_URL=https://bitbucket.org/pypy/pypy/downloads
-MACPYTHON_PY_PREFIX=/Library/Frameworks/Python.framework/Versions
-WORKING_SDIR=working
-DOWNLOADS_SDIR=/tmp
-
-function untar {
- local in_fname=$1
- if [ -z "$in_fname" ];then echo "in_fname not defined"; exit 1; fi
- local extension=${in_fname##*.}
- case $extension in
- tar) tar -xf $in_fname ;;
- gz|tgz) tar -zxf $in_fname ;;
- bz2) tar -jxf $in_fname ;;
- zip) unzip $in_fname ;;
- xz) unxz -c $in_fname | tar -xf ;;
- *) echo Did not recognize extension $extension; exit 1 ;;
- esac
-}
-
-function lex_ver {
- # Echoes dot-separated version string padded with zeros
- # Thus:
- # 3.2.1 -> 003002001
- # 3 -> 003000000
- echo $1 | awk -F "." '{printf "%03d%03d%03d", $1, $2, $3}'
-}
-
-function unlex_ver {
- # Reverses lex_ver to produce major.minor.micro
- # Thus:
- # 003002001 -> 3.2.1
- # 003000000 -> 3.0.0
- echo "$((10#${1:0:3}+0)).$((10#${1:3:3}+0)).$((10#${1:6:3}+0))"
-}
-
-function strip_ver_suffix {
- echo $(unlex_ver $(lex_ver $1))
-}
-
-function check_var {
- if [ -z "$1" ]; then
- echo "required variable not defined"
- exit 1
- fi
-}
-
-function pyinst_ext_for_version {
- # echo "pkg" or "dmg" depending on the passed Python version
- # Parameters
- # $py_version (python version in major.minor.extra format)
- #
- # Earlier Python installers are .dmg, later are .pkg.
- local py_version=$1
- check_var $py_version
- local py_0=${py_version:0:1}
- if [ $py_0 -eq 2 ]; then
- if [ "$(lex_ver $py_version)" -ge "$(lex_ver 2.7.9)" ]; then
- echo "pkg"
- else
- echo "dmg"
- fi
- elif [ $py_0 -ge 3 ]; then
- if [ "$(lex_ver $py_version)" -ge "$(lex_ver 3.4.2)" ]; then
- echo "pkg"
- else
- echo "dmg"
- fi
- fi
-}
-
-function get_pypy_build_prefix {
- # gets the file prefix of the pypy.org PyPy2
- #
- # Parameters:
- # $version : pypy2 version number
- local version=$1
- if [[ $version =~ ([0-9]+)\.([0-9]+) ]]; then
- local major=${BASH_REMATCH[1]}
- local minor=${BASH_REMATCH[2]}
- if (( $major > 5 || ($major == 5 && $minor >= 3) )); then
- echo "pypy2-v"
- else
- echo "pypy-"
- fi
- else
- echo "error: expected version number, got $1" 1>&2
- exit 1
- fi
-}
-
-function get_pypy3_build_prefix {
- # gets the file prefix of the pypy.org PyPy3
- #
- # Parameters:
- # $version : pypy3 version number
- local version=$1
- if [[ $version =~ ([0-9]+)\.([0-9]+) ]]; then
- local major=${BASH_REMATCH[1]}
- local minor=${BASH_REMATCH[2]}
- if (( $major == 5 && $minor <= 5 )); then
- echo "pypy3.3-v"
- elif (( $major < 5 )); then
- echo "pypy3-"
- else
- echo "pypy3-v"
- fi
- else
- echo "error: expected version number, got $1" 1>&2
- exit 1
- fi
-}
-
-function install_python {
- # Picks an implementation of Python determined by the current enviroment
- # variables then installs it
- # Sub-function will set $PYTHON_EXE variable to the python executable
- if [ -n "$MB_PYTHON_VERSION" ]; then
- install_macpython $MB_PYTHON_VERSION
- elif [ -n "$PYPY_VERSION" ]; then
- install_mac_pypy $PYPY_VERSION
- elif [ -n "$PYPY3_VERSION" ]; then
- install_mac_pypy3 $PYPY3_VERSION
- elif [ -z "$BREW_PYTHON3" ]; then
- # Default Python install is missing virtualenv.
- python -m ensurepip
- python -m pip install virtualenv
- fi
-}
-
-function install_macpython {
- # Installs Python.org Python
- # Parameter $version
- # Version given in major or major.minor or major.minor.micro e.g
- # "3" or "3.4" or "3.4.1".
- # sets $PYTHON_EXE variable to python executable
- local py_version=$1
- local py_stripped=$(strip_ver_suffix $py_version)
- local inst_ext=$(pyinst_ext_for_version $py_version)
- local py_inst=python-$py_version-macosx10.9.$inst_ext
- local inst_path=$DOWNLOADS_SDIR/$py_inst
- mkdir -p $DOWNLOADS_SDIR
- wget -nv $MACPYTHON_URL/$py_stripped/${py_inst} -P $DOWNLOADS_SDIR
- if [ "$inst_ext" == "dmg" ]; then
- hdiutil attach $inst_path -mountpoint /Volumes/Python
- inst_path=/Volumes/Python/Python.mpkg
- fi
- sudo installer -pkg $inst_path -target /
- local py_mm=${py_version:0:3}
- PYTHON_EXE=$MACPYTHON_PY_PREFIX/$py_mm/bin/python$py_mm
- # Install certificates for Python 3.6
- local inst_cmd="/Applications/Python ${py_mm}/Install Certificates.command"
- if [ -e "$inst_cmd" ]; then
- sh "$inst_cmd"
- fi
-}
-
-function install_mac_pypy {
- # Installs pypy.org PyPy
- # Parameter $version
- # Version given in full major.minor.micro e.g "3.4.1".
- # sets $PYTHON_EXE variable to python executable
- local py_version=$1
- local py_build=$(get_pypy_build_prefix $py_version)$py_version-osx64
- local py_zip=$py_build.tar.bz2
- local zip_path=$DOWNLOADS_SDIR/$py_zip
- mkdir -p $DOWNLOADS_SDIR
- wget -nv $PYPY_URL/${py_zip} -P $DOWNLOADS_SDIR
- untar $zip_path
- PYTHON_EXE=$(realpath $py_build/bin/pypy)
-}
-
-function install_mac_pypy3 {
- # Installs pypy.org PyPy3
- # Parameter $version
- # Version given in full major.minor.micro e.g "3.4.1".
- # sets $PYTHON_EXE variable to python executable
- local py_version=$1
- local py_build=$(get_pypy3_build_prefix $py_version)$py_version-osx64
- local py_zip=$py_build.tar.bz2
- local zip_path=$DOWNLOADS_SDIR/$py_zip
- mkdir -p $DOWNLOADS_SDIR
- wget -nv $PYPY_URL/${py_zip} -P $DOWNLOADS_SDIR
- mkdir -p $py_build
- tar -xjf $zip_path -C $py_build --strip-components=1
- PYTHON_EXE=$(realpath $py_build/bin/pypy3)
-}
-
-if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
- install_python
- if [[ -n "$PYTHON_EXE" ]]; then
- # Default Python install is missing virtualenv.
- $PYTHON_EXE -m pip install --upgrade pip virtualenv
- $PYTHON_EXE -m virtualenv venv -p $PYTHON_EXE
- else
- virtualenv venv
- fi
- source venv/bin/activate
-fi
diff --git a/.travis/launchpad.key.enc b/.travis/launchpad.key.enc
deleted file mode 100644
index 95ffbd8c..00000000
Binary files a/.travis/launchpad.key.enc and /dev/null differ
diff --git a/.travis/upload_ppa.sh b/.travis/upload_ppa.sh
deleted file mode 100755
index 39222cd1..00000000
--- a/.travis/upload_ppa.sh
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/bash
-function deb_install {
- if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then
- pip install stdeb
- pip3 install --upgrade pip --user
- pip3 install --requirement requirements.txt --user
- ./setup.py sdist
- ./setup.py --command-packages=stdeb.command sdist_dsc --use-premade-distfile dist/tdl-*.tar.gz --with-python2=True --with-python3=True --debian-version artful1 --suite artful
- ./setup.py --command-packages=stdeb.command sdist_dsc --use-premade-distfile dist/tdl-*.tar.gz --with-python2=True --with-python3=True --debian-version bionic1 --suite bionic
- export DEB_READY='yes'
- fi
-}
-function deb_upload {
- if [[ -n "$TRAVIS_TAG" && -n "$DEB_READY" ]]; then
- openssl aes-256-cbc -K $encrypted_765c87af1f2f_key -iv $encrypted_765c87af1f2f_iv -in .travis/launchpad.key.enc -d | gpg --import
- debsign -k5B69F065 deb_dist/*.changes
- dput ppa:4b796c65/ppa deb_dist/*.changes
- fi
-}
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 00000000..15ad54f7
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,19 @@
+{
+ // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
+ // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
+ // List of extensions which should be recommended for users of this workspace.
+ "recommendations": [
+ "austin.code-gnu-global",
+ "editorconfig.editorconfig",
+ "ms-python.python",
+ "ms-python.vscode-pylance",
+ "ms-vscode.cpptools",
+ "redhat.vscode-yaml",
+ "streetsidesoftware.code-spell-checker",
+ "tamasfe.even-better-toml",
+ "xaver.clang-format",
+ "charliermarsh.ruff"
+ ],
+ // List of extensions recommended by VS Code that should not be recommended for users of this workspace.
+ "unwantedRecommendations": []
+}
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 00000000..a4d5d9e7
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,48 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Python: Current File",
+ "type": "debugpy",
+ "request": "launch",
+ "program": "${file}",
+ "console": "integratedTerminal",
+ },
+ {
+ // Run the Python samples.
+ // tcod will be built and installed in editalbe mode.
+ "name": "Python: Python samples",
+ "type": "debugpy",
+ "request": "launch",
+ "program": "${workspaceFolder}/examples/samples_tcod.py",
+ "cwd": "${workspaceFolder}/examples",
+ "console": "integratedTerminal",
+ },
+ {
+ "name": "Python: Run tests",
+ "type": "debugpy",
+ "request": "launch",
+ "module": "pytest",
+ "preLaunchTask": "develop python-tcod",
+ },
+ {
+ "name": "Documentation: Launch Chrome",
+ "request": "launch",
+ "type": "chrome",
+ "url": "file://${workspaceFolder}/docs/_build/html/index.html",
+ "webRoot": "${workspaceFolder}",
+ "preLaunchTask": "build documentation",
+ },
+ {
+ "name": "Documentation: Launch Edge",
+ "request": "launch",
+ "type": "msedge",
+ "url": "file://${workspaceFolder}/docs/_build/html/index.html",
+ "webRoot": "${workspaceFolder}",
+ "preLaunchTask": "build documentation",
+ }
+ ]
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 00000000..10b92720
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,568 @@
+{
+ "editor.rulers": [
+ 79,
+ 120
+ ],
+ "editor.formatOnSave": true,
+ "editor.codeActionsOnSave": {
+ "source.fixAll": "always",
+ "source.organizeImports": "never"
+ },
+ "cmake.configureOnOpen": false,
+ "files.trimFinalNewlines": true,
+ "files.insertFinalNewline": true,
+ "files.trimTrailingWhitespace": true,
+ "files.associations": {
+ "*.spec": "python"
+ },
+ "mypy-type-checker.importStrategy": "fromEnvironment",
+ "cSpell.words": [
+ "aarch",
+ "ADDA",
+ "ADDALPHA",
+ "ADDEVENT",
+ "addoption",
+ "addopts",
+ "addressof",
+ "addsub",
+ "addx",
+ "addy",
+ "algo",
+ "ALPH",
+ "alsa",
+ "ALTERASE",
+ "arange",
+ "ARCHS",
+ "arctan",
+ "asarray",
+ "ascontiguousarray",
+ "astar",
+ "astype",
+ "atexit",
+ "AUDIOCVT",
+ "AUDIOFASTFORWARD",
+ "AUDIOMUTE",
+ "AUDIONEXT",
+ "AUDIOPLAY",
+ "AUDIOPREV",
+ "AUDIOREWIND",
+ "AUDIOSTOP",
+ "autoclass",
+ "AUTOCORRECT",
+ "autofunction",
+ "autogenerated",
+ "automodule",
+ "autoupdate",
+ "backlinks",
+ "bdist",
+ "Benesch",
+ "bezier",
+ "bfade",
+ "bgcolor",
+ "bitmask",
+ "bitorder",
+ "BITSIZE",
+ "BKGND",
+ "Blit",
+ "blits",
+ "blitting",
+ "BORDERLESS",
+ "bresenham",
+ "Bresenham",
+ "BRIGHTNESSDOWN",
+ "BRIGHTNESSUP",
+ "bysource",
+ "caeldera",
+ "CAPSLOCK",
+ "ccoef",
+ "cdef",
+ "cffi",
+ "cflags",
+ "CFLAGS",
+ "CHARMAP",
+ "Chebyshev",
+ "choicelist",
+ "cibuildwheel",
+ "CIBW",
+ "CLEARAGAIN",
+ "CLEARENTRY",
+ "CMWC",
+ "Codacy",
+ "Codecov",
+ "codepoint",
+ "codepoints",
+ "coef",
+ "Coef",
+ "COLCTRL",
+ "COMPILEDVERSION",
+ "condlist",
+ "consolas",
+ "contextdata",
+ "contextlib",
+ "CONTROLLERAXISMOTION",
+ "CONTROLLERBUTTONDOWN",
+ "CONTROLLERBUTTONUP",
+ "CONTROLLERDEVICEADDED",
+ "CONTROLLERDEVICEREMAPPED",
+ "CONTROLLERDEVICEREMOVED",
+ "cooldown",
+ "cplusplus",
+ "CPLUSPLUS",
+ "cpython",
+ "CRSEL",
+ "ctypes",
+ "CURRENCYSUBUNIT",
+ "CURRENCYUNIT",
+ "currentmodule",
+ "dataclasses",
+ "datas",
+ "DBLAMPERSAND",
+ "DBLAPOSTROPHE",
+ "DBLVERTICALBAR",
+ "dbus",
+ "dcost",
+ "DCROSS",
+ "DECIMALSEPARATOR",
+ "dejavu",
+ "delocate",
+ "deque",
+ "desaturated",
+ "DESATURATED",
+ "detailmenu",
+ "devel",
+ "DHLINE",
+ "DISPLAYSWITCH",
+ "distutils",
+ "dlopen",
+ "docstrings",
+ "doctest",
+ "documentclass",
+ "Doryen",
+ "DPAD",
+ "DTEEE",
+ "DTEEN",
+ "DTEES",
+ "DTEEW",
+ "dtype",
+ "dtypes",
+ "dunder",
+ "DVLINE",
+ "elif",
+ "Emscripten",
+ "EMSDK",
+ "ENDCALL",
+ "endianness",
+ "epel",
+ "epub",
+ "EQUALSAS",
+ "errorvf",
+ "EXCLAM",
+ "EXSEL",
+ "faulthandler",
+ "favicon",
+ "ffade",
+ "fgcolor",
+ "fheight",
+ "filterwarnings",
+ "Flecs",
+ "flto",
+ "fmean",
+ "fontx",
+ "fonty",
+ "freethreading",
+ "freetype",
+ "frombuffer",
+ "fullscreen",
+ "furo",
+ "fwidth",
+ "GAMECONTROLLER",
+ "gamedev",
+ "gamepad",
+ "gaxis",
+ "gbutton",
+ "gdevice",
+ "genindex",
+ "getbbox",
+ "GFORCE",
+ "GLES",
+ "globaltoc",
+ "GLSL",
+ "greyscale",
+ "groupwise",
+ "guass",
+ "hasattr",
+ "heapify",
+ "heightmap",
+ "heightmaps",
+ "hflip",
+ "hiddenimports",
+ "HIGHDPI",
+ "hillclimb",
+ "hline",
+ "horiz",
+ "howto",
+ "htbp",
+ "htmlhelp",
+ "htmlzip",
+ "IBEAM",
+ "ibus",
+ "ICCPROF",
+ "ifdef",
+ "ifndef",
+ "iinfo",
+ "IJKL",
+ "imageio",
+ "imread",
+ "includepath",
+ "INCOL",
+ "INPUTTYPE",
+ "INROW",
+ "interactable",
+ "intersphinx",
+ "isinstance",
+ "isort",
+ "issubdtype",
+ "itemsize",
+ "itleref",
+ "ivar",
+ "jaxis",
+ "jball",
+ "jbutton",
+ "jdevice",
+ "jhat",
+ "jice",
+ "jieba",
+ "jmccardle",
+ "JOYAXISMOTION",
+ "JOYBALLMOTION",
+ "JOYBUTTONDOWN",
+ "JOYBUTTONUP",
+ "JOYDEVICEADDED",
+ "JOYDEVICEREMOVED",
+ "JOYHATMOTION",
+ "Kaczor",
+ "KBDILLUMDOWN",
+ "KBDILLUMTOGGLE",
+ "KBDILLUMUP",
+ "kernelsize",
+ "keychar",
+ "KEYDOWN",
+ "keyname",
+ "keypress",
+ "keysym",
+ "KEYUP",
+ "KMOD",
+ "KPADD",
+ "KPDEC",
+ "KPDIV",
+ "KPENTER",
+ "KPMUL",
+ "KPSUB",
+ "LACUNARITY",
+ "LALT",
+ "lbutton",
+ "LCTRL",
+ "LDFLAGS",
+ "LEFTBRACE",
+ "LEFTBRACKET",
+ "LEFTDOWN",
+ "LEFTPAREN",
+ "LEFTSHOULDER",
+ "LEFTSTICK",
+ "LEFTUP",
+ "LEFTX",
+ "lerp",
+ "letterpaper",
+ "LGUI",
+ "LHYPER",
+ "libdrm",
+ "libgbm",
+ "libsdl",
+ "libtcod",
+ "libtcodpy",
+ "libusb",
+ "libxkbcommon",
+ "linspace",
+ "liskin",
+ "LMASK",
+ "lmeta",
+ "lodepng",
+ "LPAREN",
+ "LTCG",
+ "lucida",
+ "LWIN",
+ "manylinux",
+ "maxarray",
+ "maxdepth",
+ "mbutton",
+ "MEDIASELECT",
+ "MEIPASS",
+ "MEMADD",
+ "MEMCLEAR",
+ "MEMDIVIDE",
+ "MEMMULTIPLY",
+ "MEMRECALL",
+ "MEMSTORE",
+ "MEMSUBTRACT",
+ "mersenne",
+ "meshgrid",
+ "mgrid",
+ "milli",
+ "minmax",
+ "minversion",
+ "mipmap",
+ "mipmaps",
+ "MMASK",
+ "modindex",
+ "moduleauthor",
+ "modversion",
+ "MOUSEBUTTONDOWN",
+ "MOUSEBUTTONUP",
+ "MOUSEMOTION",
+ "MOUSESTATE",
+ "msilib",
+ "MSVC",
+ "msvcr",
+ "mult",
+ "mulx",
+ "muly",
+ "mypy",
+ "namegen",
+ "ncipollo",
+ "ndarray",
+ "ndim",
+ "newaxis",
+ "newh",
+ "neww",
+ "noarchive",
+ "NODISCARD",
+ "NOLONGLONG",
+ "NOMESSAGE",
+ "Nonrepresentable",
+ "NONUSBACKSLASH",
+ "NONUSHASH",
+ "nullptr",
+ "NUMLOCK",
+ "NUMLOCKCLEAR",
+ "Numpad",
+ "numpy",
+ "ogrid",
+ "ogrids",
+ "oldnames",
+ "onefile",
+ "OPENGL",
+ "opensearch",
+ "OPER",
+ "OVERSCAN",
+ "packbits",
+ "PAGEDOWN",
+ "pagerefs",
+ "PAGEUP",
+ "papersize",
+ "passthru",
+ "PATCHLEVEL",
+ "pathfinding",
+ "pathlib",
+ "pcpp",
+ "PERLIN",
+ "PILCROW",
+ "pilmode",
+ "PIXELART",
+ "PIXELFORMAT",
+ "PLUSMINUS",
+ "pointsize",
+ "popleft",
+ "PRESENTVSYNC",
+ "PRINTF",
+ "printn",
+ "PRINTSCREEN",
+ "propname",
+ "pulseaudio",
+ "pushdown",
+ "pycall",
+ "pycparser",
+ "pydocstyle",
+ "pyinstaller",
+ "pyodide",
+ "pypa",
+ "PYPI",
+ "pypiwin",
+ "pypy",
+ "pytest",
+ "pytestmark",
+ "PYTHONHASHSEED",
+ "PYTHONOPTIMIZE",
+ "Pyup",
+ "quickstart",
+ "QUOTEDBL",
+ "RALT",
+ "randint",
+ "randomizer",
+ "rbutton",
+ "RCTRL",
+ "rects",
+ "redist",
+ "Redistributable",
+ "redistributables",
+ "repr",
+ "rexpaint",
+ "rgba",
+ "RGUI",
+ "RHYPER",
+ "RIGHTBRACE",
+ "RIGHTBRACKET",
+ "RIGHTDOWN",
+ "RIGHTPAREN",
+ "RIGHTSHOULDER",
+ "RIGHTSTICK",
+ "RIGHTUP",
+ "RIGHTX",
+ "RMASK",
+ "rmeta",
+ "roguelike",
+ "roguelikedev",
+ "rpath",
+ "RRGGBB",
+ "rtype",
+ "RWIN",
+ "RWOPS",
+ "SCALEMODE",
+ "scalex",
+ "scaley",
+ "Scancode",
+ "scancodes",
+ "scipy",
+ "scoef",
+ "Scrn",
+ "SCROLLLOCK",
+ "sdist",
+ "SDL's",
+ "SDLCALL",
+ "sdlevent",
+ "SDLK",
+ "searchbox",
+ "sectionauthor",
+ "seealso",
+ "servernum",
+ "setuptools",
+ "SHADOWCAST",
+ "SIZEALL",
+ "SIZENESW",
+ "SIZENS",
+ "SIZENWSE",
+ "SIZEWE",
+ "SMILIE",
+ "snprintf",
+ "SOFTLEFT",
+ "SOFTRIGHT",
+ "soundfile",
+ "sourcelink",
+ "sphinxstrong",
+ "sphinxtitleref",
+ "staticmethod",
+ "stdarg",
+ "stddef",
+ "stdeb",
+ "struct",
+ "structs",
+ "subclassing",
+ "SUBP",
+ "SYSREQ",
+ "tablefmt",
+ "Tamzen",
+ "TARGETTEXTURE",
+ "tcod",
+ "tcoddoc",
+ "TCODK",
+ "TCODLIB",
+ "TEEE",
+ "TEEW",
+ "termbox",
+ "testpaths",
+ "TEXTUREACCESS",
+ "thirdparty",
+ "THOUSANDSSEPARATOR",
+ "Tileset",
+ "tilesets",
+ "tilesheet",
+ "tilesheets",
+ "timeit",
+ "toctree",
+ "todos",
+ "tolist",
+ "touchpad",
+ "traceback",
+ "TRIGGERLEFT",
+ "TRIGGERRIGHT",
+ "tris",
+ "truetype",
+ "tryddle",
+ "typestr",
+ "undoc",
+ "Unifont",
+ "UNISTD",
+ "unraisable",
+ "unraisablehook",
+ "unraiseable",
+ "upscaling",
+ "userdata",
+ "VAFUNC",
+ "VALUELIST",
+ "vcoef",
+ "venv",
+ "vertic",
+ "VERTICALBAR",
+ "vflip",
+ "viewcode",
+ "VITAFILE",
+ "vline",
+ "VOLUMEDOWN",
+ "VOLUMEUP",
+ "voronoi",
+ "VRAM",
+ "vsync",
+ "VULKAN",
+ "WAITARROW",
+ "WASD",
+ "waterlevel",
+ "WINAPI",
+ "windowclose",
+ "windowenter",
+ "WINDOWEVENT",
+ "windowexposed",
+ "windowfocusgained",
+ "windowfocuslost",
+ "windowhidden",
+ "windowhittest",
+ "windowleave",
+ "windowmaximized",
+ "windowminimized",
+ "windowmoved",
+ "WINDOWPOS",
+ "WINDOWRESIZED",
+ "windowrestored",
+ "windowshown",
+ "windowsizechanged",
+ "windowtakefocus",
+ "xcframework",
+ "Xcursor",
+ "xdst",
+ "Xext",
+ "Xfixes",
+ "Xrandr",
+ "xrel",
+ "xvfb",
+ "ydst",
+ "yrel",
+ "zizmor"
+ ],
+ "python.testing.pytestArgs": [],
+ "python.testing.unittestEnabled": false,
+ "python.testing.pytestEnabled": true,
+ "[python]": {
+ "editor.defaultFormatter": "charliermarsh.ruff"
+ },
+ "[html]": {
+ "editor.formatOnSave": false
+ }
+}
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 00000000..5e1a0fb2
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,44 @@
+{
+ // See https://go.microsoft.com/fwlink/?LinkId=733558
+ // for the documentation about the tasks.json format
+ "version": "2.0.0",
+ "tasks": [
+ {
+ // Installs tcod in editable mode.
+ "label": "develop python-tcod",
+ "type": "shell",
+ "command": "python setup.py develop"
+ },
+ {
+ "label": "install documentation requirements",
+ "type": "process",
+ "command": "python",
+ "args": [
+ "-m",
+ "pip",
+ "install",
+ "--requirement",
+ "docs/requirements.txt"
+ ],
+ "problemMatcher": []
+ },
+ {
+ "label": "build documentation",
+ "type": "shell",
+ "command": "make",
+ "args": [
+ "html"
+ ],
+ "options": {
+ "cwd": "${workspaceFolder}/docs"
+ },
+ "problemMatcher": [],
+ "dependsOn": [
+ "install documentation requirements"
+ ],
+ "windows": {
+ "command": "./make.bat"
+ }
+ }
+ ]
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 00000000..bc5f79f3
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,2177 @@
+# Changelog
+
+Changes relevant to the users of python-tcod are documented here.
+
+This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`.
+
+## [Unreleased]
+
+## [21.2.0] - 2026-04-04
+
+### Added
+
+- `tcod.event.MouseWheel` now has `which`, `window_id`, `position` and `integer_position` attributes.
+
+## Fixed
+
+- `tcod.event.convert_coordinates_from_window` was not converting all types of mouse events.
+
+## [21.1.0] - 2026-04-04
+
+### Added
+
+- `MouseButtonEvent.integer_position` property.
+
+## Fixed
+
+- `integer_position` was missing from mouse button events.
+
+## [21.0.0] - 2026-03-13
+
+### Added
+
+- `tcod.sdl.video.Window` now accepts an SDL WindowID.
+- `tcod.event`:
+ - `MouseState.integer_position` and `MouseMotion.integer_motion` to handle cases where integer values are preferred.
+ - `ClipboardUpdate` event.
+ - `Drop` event.
+ - `KeyboardEvent.pressed`, `KeyboardEvent.which`, `KeyboardEvent.window_id` attributes.
+ - `WindowEvent.data` and `WindowEvent.window_id` attributes and added missing SDL3 window events.
+ - `which` and `window_id` attributes for mouse events.
+ - Events now have `Event.timestamp` and `Event.timestamp_ns` which use SDL's timer at `tcod.event.time` and `tcod.event.time_ns`.
+
+### Changed
+
+- Event classes are now more strict with attribute types
+- Event class initializers are keyword-only and no longer take a type parameter, with exceptions.
+ Generally event class initialization is an internal process.
+- `MouseButtonEvent` no longer a subclass of `MouseState`.
+- `tcod.event.Point` is now a generic type containing `int` or `float` values depending on the context.
+- When converting mouse events to tiles:
+ `MouseState.position` and `MouseMotion.motion` refers to sub-tile coordinates.
+ `MouseState.integer_position` and `MouseMotion.integer_motion` refers to integer tile coordinates.
+
+### Deprecated
+
+- `Event.type` is deprecated except for special cases such as `ControllerDevice`, `WindowEvent`, etc.
+- `MouseButtonEvent.state` is deprecated, replaced by the existing `.button` attribute.
+
+### Fixed
+
+- Fixed incorrect C FFI types inside `tcod.event.get_mouse_state`.
+- Fixed regression in mouse event tile coordinates being `float` instead of `int`.
+ `convert_coordinates_from_window` can be used if sub-tile coordinates were desired.
+- Fixed regression in `libtcodpy.bsp_split_recursive` not accepting `0`.
+
+## [20.1.0] - 2026-02-25
+
+### Added
+
+- `Tileset` now supports `MutableMapping` semantics.
+ Can get, set, or iterate over tiles as if it were a dictionary containing tile glyph arrays.
+ Also supports `+`, `|`, `+=`, and `|=` with other tilesets or mappings to merge them into a single Tileset.
+- `tcod.tileset.procedural_block_elements` can take a tile shape and return a tileset.
+
+### Deprecated
+
+- `Tileset.set_tile(codepoint, tile)` was replaced with `tileset[codepoint] = tile` syntax.
+- `Tileset.get_tile(codepoint)` was soft replaced with `tileset[codepoint]` syntax.
+- `tcod.tileset.procedural_block_elements` should be used with dictionary semantics instead of passing in a tileset.
+
+## [20.0.0] - 2026-02-06
+
+### Added
+
+- Now supports free-threaded Python, deploys with `cp314t` wheels.
+- Added methods: `Renderer.coordinates_from_window` and `Renderer.coordinates_to_window`
+- Added `tcod.event.convert_coordinates_from_window`.
+
+### Changed
+
+- `Renderer.logical_size` now returns `None` instead of `(0, 0)` when logical size is unset.
+
+## [19.6.3] - 2026-01-12
+
+Fix missing deployment
+
+## [19.6.2] - 2026-01-12
+
+### Changed
+
+- Update to libtcod 2.2.2
+
+### Fixed
+
+- Mouse coordinate to tile conversions now support SDL renderer logical size and scaling.
+
+## [19.6.1] - 2025-12-15
+
+### Fixed
+
+- `tcod.event.add_watch` was crashing due to a cdef type mismatch.
+
+## [19.6.0] - 2025-10-20
+
+### Added
+
+- Alternative syntax for number symbols with `KeySym`, can now specify `KeySym["3"]`, etc.
+ Only available on Python 3.13 or later.
+
+### Fixed
+
+- Fixed regression with lowercase key symbols with `tcod.event.K_*` and `KeySym.*` constants, these are still deprecated.
+ Event constants are only fixed for `tcod.event.K_*`, not the undocumented `tcod.event_constants` module.
+ Lowercase `KeySym.*` constants are only available on Python 3.13 or later.
+- `BSP.split_recursive` did not accept a `Random` class as the seed. #168
+
+## [19.5.0] - 2025-09-13
+
+### Changed
+
+- Update to libtcod 2.2.1.
+- Scaling defaults to nearest, set `os.environ["SDL_RENDER_SCALE_QUALITY"] = "linear"` if linear scaling was preferred.
+
+### Fixed
+
+- `SDL_RENDER_SCALE_QUALITY` is now respected again since the change to SDL3.
+- Fixed crash on controller events.
+
+## [19.4.1] - 2025-08-27
+
+### Fixed
+
+- Fixed dangling pointer in `Pathfinder.clear` method.
+- Fixed hang in `Pathfinder.rebuild_frontier` method.
+
+## [19.4.0] - 2025-08-06
+
+### Changed
+
+- Checking "WindowSizeChanged" was not valid since SDL 3 and was also not valid in previous examples.
+ You must no longer check the type of the `WindowResized` event.
+
+### Fixed
+
+- Corrected some inconsistent angle brackets in the `__str__` of Event subclasses. #165
+- Fix regression with window events causing them to be `Unknown` and uncheckable.
+
+## [19.3.1] - 2025-08-02
+
+Solved a deprecation warning which was internal to tcod and no doubt annoyed many devs.
+Thanks to jmccardle for forcing me to resolve this.
+
+### Fixed
+
+- Silenced internal deprecation warnings within `Context.convert_event`.
+
+## [19.3.0] - 2025-07-26
+
+Thanks to cr0ne for pointing out missing texture scaling options.
+These options did not exist in SDL2.
+
+### Added
+
+- `tcod.sdl.render`: Added `ScaleMode` enum and `Texture.scale_mode` attribute.
+
+## [19.2.0] - 2025-07-20
+
+Thanks to tryddle for demonstrating how bad the current API was with chunked world generation.
+
+### Added
+
+- `tcod.noise.grid` now has the `offset` parameter for easier sampling of noise chunks.
+
+## [19.1.0] - 2025-07-12
+
+### Added
+
+- Added text input support to `tcod.sdl.video.Window` which was missing since the SDL3 update.
+ After creating a context use `assert context.sdl_window` or `if context.sdl_window:` to verify that an SDL window exists then use `context.sdl_window.start_text_input` to enable text input events.
+ Keep in mind that this can open an on-screen keyboard.
+
+## [19.0.2] - 2025-07-11
+
+Resolve wheel deployment issue.
+
+## [19.0.1] - 2025-07-11
+
+### Fixed
+
+- `Console.print` methods using `string` keyword were marked as invalid instead of deprecated.
+
+## [19.0.0] - 2025-06-13
+
+Finished port to SDL3, this has caused several breaking changes from SDL such as lowercase key constants now being uppercase and mouse events returning `float` instead of `int`.
+Be sure to run [Mypy](https://mypy.readthedocs.io/en/stable/getting_started.html) on your projects to catch any issues from this update.
+
+### Changed
+
+- Updated libtcod to 2.1.1
+- Updated SDL to 3.2.16
+ This will cause several breaking changes such as the names of keyboard constants and other SDL enums.
+- `tcod.sdl.video.Window.grab` has been split into `.mouse_grab` and `.keyboard_grab` attributes.
+- `tcod.event.KeySym` single letter symbols are now all uppercase.
+- Relative mouse mode is set via `tcod.sdl.video.Window.relative_mouse_mode` instead of `tcod.sdl.mouse.set_relative_mode`.
+- `tcod.sdl.render.new_renderer`: Removed `software` and `target_textures` parameters, `vsync` takes `int`, `driver` takes `str` instead of `int`.
+- `tcod.sdl.render.Renderer`: `integer_scaling` and `logical_size` are now set with `set_logical_presentation` method.
+- `tcod.sdl.render.Renderer.geometry` now takes float values for `color` instead of 8-bit integers.
+- `tcod.event.Point` and other mouse/tile coordinate types now use `float` instead of `int`.
+ SDL3 has decided that mouse events have subpixel precision.
+ If you see any usual `float` types in your code then this is why.
+- `tcod.sdl.audio` has been affected by major changes to SDL3.
+ - `tcod.sdl.audio.open` has new behavior due to SDL3 and should be avoided.
+ - Callbacks which were assigned to `AudioDevice`'s must now be applied to `AudioStream`'s instead.
+ - `AudioDevice`'s are now opened using references to existing devices.
+ - Sound queueing methods were moved from `AudioDevice` to a new `AudioStream` class.
+ - `BasicMixer` may require manually specifying `frequency` and `channels` to replicate old behavior.
+ - `get_devices` and `get_capture_devices` now return `dict[str, AudioDevice]`.
+- `TextInput` events are no longer enabled by default.
+
+### Deprecated
+
+- `tcod.sdl.audio.open` was replaced with a newer API, get a default device with `tcod.sdl.audio.get_default_playback().open()`.
+- `tcod.sdl.audio.BasicMixer` should be replaced with `AudioStream`'s.
+- Should no longer use `tcod.sdl.audio.AudioDevice` in a context, use `contextlib.closing` for the old behavior.
+
+### Removed
+
+- Support dropped for Python 3.8 and 3.9.
+- Removed `Joystick.get_current_power` due to SDL3 changes.
+- `WindowFlags.FULLSCREEN_DESKTOP` is now just `WindowFlags.FULLSCREEN`
+- `tcod.sdl.render.Renderer.integer_scaling` removed.
+- Removed `callback`, `spec`, `queued_samples`, `queue_audio`, and `dequeue_audio` attributes from `tcod.sdl.audio.AudioDevice`.
+- `tcod.event.WindowResized`: `type="WindowSizeChanged"` removed and must no longer be checked for.
+ `EventDispatch.ev_windowsizechanged` is no longer called.
+
+### Fixed
+
+- `Joystick.get_ball` was broken.
+
+## [18.1.0] - 2025-05-05
+
+### Added
+
+- `tcod.path.path2d` to compute paths for the most basic cases.
+
+### Fixed
+
+- `tcod.noise.grid` would raise `TypeError` when given a plain integer for scale.
+
+## [18.0.0] - 2025-04-08
+
+### Changed
+
+- `Console.print` now accepts `height` and `width` keywords and has renamed `string` to `text`.
+- Text printed with `Console.print` using right-alignment has been shifted to the left by 1-tile.
+
+### Deprecated
+
+- In general the `fg`, `bg`, and `bg_blend` keywords are too hard to keep track of as positional arguments so they must be replaced with keyword arguments instead.
+- `Console.print`: deprecated `string`, `fg`, `bg`, and `bg_blend` being given as positional arguments.
+ The `string` parameter has been renamed to `text`.
+- `Console.print_box` has been replaced by `Console.print`.
+- `Console.draw_frame`: deprecated `clear`, `fg`, `bg`, and `bg_blend` being given as positional arguments.
+- `Console.draw_rect`: deprecated `fg`, `bg`, and `bg_blend` being given as positional arguments.
+- The `EventDispatch` class is now deprecated.
+ This class was made before Python supported protocols and structural pattern matching,
+ now the class serves little purpose and its usage can create a minor technical burden.
+
+## [17.1.0] - 2025-03-29
+
+### Added
+
+- SDL renderer primitive drawing methods now support sequences of tuples.
+
+### Fixed
+
+- `tcod.sdl.Renderer.draw_lines` type hint was too narrow.
+- Fixed crash in `tcod.sdl.Renderer.geometry`.
+
+## [17.0.0] - 2025-03-28
+
+### Changed
+
+- `EventDispatch`'s on event methods are now defined as positional parameters, so renaming the `event` parameter is now valid in subclasses.
+
+### Deprecated
+
+- Keyboard bitmask modifiers `tcod.event.KMOD_*` have been replaced by `tcod.event.Modifier`.
+
+### Fixed
+
+- Suppressed internal `mouse.tile_motion` deprecation warning.
+- Fixed SDL renderer primitive drawing methods. #159
+
+## [16.2.3] - 2024-07-16
+
+### Fixed
+
+- Fixed access violation when events are polled before SDL is initialized.
+- Fixed access violation when libtcod images fail to load.
+- Verify input files exist when calling `libtcodpy.parser_run`, `libtcodpy.namegen_parse`, `tcod.image.load`.
+
+## [16.2.2] - 2024-01-16
+
+### Fixed
+
+- Ignore the locale when encoding file paths outside of Windows.
+- Fix performance when calling joystick functions.
+
+## [16.2.1] - 2023-09-24
+
+### Fixed
+
+- Fixed errors loading files on Windows where their paths are non-ASCII and the locale is not UTF-8.
+
+## [16.2.0] - 2023-09-20
+
+### Changed
+
+- Renamed `gauss` methods to fix typos.
+
+## [16.1.1] - 2023-07-10
+
+### Changed
+
+- Added an empty `__slots__` to `EventDispatch`.
+- Bundle `SDL 2.28.1` on Windows and MacOS.
+
+### Fixed
+
+- Fixed "SDL failed to get a vertex buffer for this Direct3D 9 rendering batch!"
+ https://github.com/libtcod/python-tcod/issues/131
+
+### Removed
+
+- Dropped support for Python 3.7.
+
+## [16.1.0] - 2023-06-23
+
+### Added
+
+- Added the enums `tcod.event.MouseButton` and `tcod.event.MouseButtonMask`.
+
+### Changed
+
+- Using `libtcod 1.24.0`.
+
+### Deprecated
+
+- Mouse button and mask constants have been replaced by enums.
+
+### Fixed
+
+- `WindowResized` literal annotations were in the wrong case.
+
+## [16.0.3] - 2023-06-04
+
+### Changed
+
+- Enabled logging for libtcod and SDL.
+
+### Deprecated
+
+- Deprecated using `tcod` as an implicit alias for `libtcodpy`.
+ You should use `from tcod import libtcodpy` if you want to access this module.
+- Deprecated constants being held directly in `tcod`, get these from `tcod.libtcodpy` instead.
+- Deprecated `tcod.Console` which should be accessed from `tcod.console.Console` instead.
+
+## [16.0.2] - 2023-06-02
+
+### Fixed
+
+- Joystick/controller device events would raise `RuntimeError` when accessed after removal.
+
+## [16.0.1] - 2023-05-28
+
+### Fixed
+
+- `AudioDevice.stopped` was inverted.
+- Fixed the audio mixer stop and fadeout methods.
+- Exceptions raised in the audio mixer callback no longer cause a messy crash, they now go to `sys.unraisablehook`.
+
+## [16.0.0] - 2023-05-27
+
+### Added
+
+- Added PathLike support to more libtcodpy functions.
+- New `tcod.sdl.mouse.show` function for querying or setting mouse visibility.
+- New class method `tcod.image.Image.from_file` to load images with. This replaces `tcod.image_load`.
+- `tcod.sdl.audio.AudioDevice` is now a context manager.
+
+### Changed
+
+- SDL audio conversion will now pass unconvertible floating types as float32 instead of raising.
+
+### Deprecated
+
+- Deprecated the libtcodpy functions for images and noise generators.
+
+### Removed
+
+- `tcod.console_set_custom_font` can no longer take bytes as the file path.
+
+### Fixed
+
+- Fix `tcod.sdl.mouse.warp_in_window` function.
+- Fix `TypeError: '_AudioCallbackUserdata' object is not callable` when using an SDL audio device callback.
+ [#128](https://github.com/libtcod/python-tcod/issues/128)
+
+## [15.0.3] - 2023-05-25
+
+### Deprecated
+
+- Deprecated all libtcod color constants. Replace these with your own manually defined colors.
+ Using a color will tell you the color values of the deprecated color in the warning.
+- Deprecated older scancode and keysym constants. These were replaced with the Scancode and KeySym enums.
+
+### Fixed
+
+- DLL loader could fail to load `SDL2.dll` when other tcod namespace packages were installed.
+
+## [15.0.1] - 2023-03-30
+
+### Added
+
+- Added support for `tcod.sdl` namespace packages.
+
+### Fixed
+
+- `Renderer.read_pixels` method was completely broken.
+
+## [15.0.0] - 2023-01-04
+
+### Changed
+
+- Modified the letter case of window event types to match their type annotations.
+ This may cause regressions. Run Mypy to check for `[comparison-overlap]` errors.
+- Mouse event attributes have been changed `.pixel -> .position` and `.pixel_motion -> .motion`.
+- `Context.convert_event` now returns copies of events with mouse coordinates converted into tile positions.
+
+### Deprecated
+
+- Mouse event pixel and tile attributes have been deprecated.
+
+## [14.0.0] - 2022-12-09
+
+### Added
+
+- Added explicit support for namespace packages.
+
+### Changed
+
+- Using `libtcod 1.23.1`.
+- Bundle `SDL 2.26.0` on Windows and MacOS.
+- Code Page 437: Character 0x7F is now assigned to 0x2302 (HOUSE).
+- Forced all renderers to `RENDERER_SDL2` to fix rare graphical artifacts with OpenGL.
+
+### Deprecated
+
+- The `renderer` parameter of new contexts is now deprecated.
+
+## [13.8.1] - 2022-09-23
+
+### Fixed
+
+- `EventDispatch` was missing new event names.
+
+## [13.8.0] - 2022-09-22
+
+### Added
+
+- Ported SDL2 joystick handing as `tcod.sdl.joystick`.
+- New joystick related events.
+
+### Changed
+
+- Using `libtcod 1.22.3`.
+- Bundle `SDL 2.24.0` on Windows and MacOS.
+
+### Deprecated
+
+- Renderers other than `tcod.RENDERER_SDL2` are now discouraged.
+
+### Fixed
+
+- Fixed double present bug in non-context flush functions.
+ This was affecting performance and also caused a screen flicker whenever the global fade color was active.
+- Fixed the parsing of SDL 2.24.0 headers on Windows.
+
+## [13.7.0] - 2022-08-07
+
+### Added
+
+- You can new use `SDLConsoleRender.atlas` to access the `SDLTilesetAtlas` used to create it.
+ [#121](https://github.com/libtcod/python-tcod/issues/121)
+
+### Fixed
+
+- Fixed the parsing of SDL 2.0.22 headers. Specifically `SDL_FLT_EPSILON`.
+
+## [13.6.2] - 2022-05-02
+
+### Fixed
+
+- SDL renderers were ignoring tiles where only the background red channel was changed.
+
+## [13.6.1] - 2022-03-29
+
+### Changed
+
+- The SDL2 renderer has had a major performance update when compiled with SDL 2.0.18.
+- SDL2 is now the default renderer to avoid rare issues with the OpenGL 2 renderer.
+
+## [13.6.0] - 2022-02-19
+
+### Added
+
+- `BasicMixer` and `Channel` classes added to `tcod.sdl.audio`. These handle simple audio mixing.
+- `AudioDevice.convert` added to handle simple conversions to the active devices format.
+- `tcod.sdl.audio.convert_audio` added to handle any other conversions needed.
+
+## [13.5.0] - 2022-02-11
+
+### Added
+
+- `tcod.sdl.audio`, a new module exposing SDL audio devices. This does not include an audio mixer yet.
+- `tcod.sdl.mouse`, for SDL mouse and cursor handing.
+- `Context.sdl_atlas`, which provides the relevant `SDLTilesetAtlas` when one is being used by the context.
+- Several missing features were added to `tcod.sdl.render`.
+- `Window.mouse_rect` added to SDL windows to set the mouse confinement area.
+
+### Changed
+
+- `Texture.access` and `Texture.blend_mode` properties now return enum instances.
+ You can still set `blend_mode` with `int` but Mypy will complain.
+
+## [13.4.0] - 2022-02-04
+
+### Added
+
+- Adds `sdl_window` and `sdl_renderer` properties to tcod contexts.
+- Adds `tcod.event.add_watch` and `tcod.event.remove_watch` to handle SDL events via callback.
+- Adds the `tcod.sdl.video` module to handle SDL windows.
+- Adds the `tcod.sdl.render` module to handle SDL renderers.
+- Adds the `tcod.render` module which gives more control over the rendering of consoles and tilesets.
+
+### Fixed
+
+- Fixed handling of non-Path PathLike parameters and filepath encodings.
+
+## [13.3.0] - 2022-01-07
+
+### Added
+
+- New experimental renderer `tcod.context.RENDERER_XTERM`.
+
+### Changed
+
+- Using `libtcod 1.20.1`.
+
+### Fixed
+
+- Functions accepting `Path`-like parameters now accept the more correct `os.PathLike` type.
+- BDF files with blank lines no longer fail to load with an "Unknown keyword" error.
+
+## [13.2.0] - 2021-12-24
+
+### Added
+
+- New `console` parameter in `tcod.context.new` which sets parameters from an existing Console.
+
+### Changed
+
+- Using `libtcod 1.20.0`.
+
+### Fixed
+
+- Fixed segfault when an OpenGL2 context fails to load.
+- Gaussian number generation no longer affects the results of unrelated RNG's.
+- Gaussian number generation is now reentrant and thread-safe.
+- Fixed potential crash in PNG image loading.
+
+## [13.1.0] - 2021-10-22
+
+### Added
+
+- Added the `tcod.tileset.procedural_block_elements` function.
+
+### Removed
+
+- Python 3.6 is no longer supported.
+
+## [13.0.0] - 2021-09-20
+
+### Changed
+
+- Console print and drawing functions now always use absolute coordinates for negative numbers.
+
+## [12.7.3] - 2021-08-13
+
+### Deprecated
+
+- `tcod.console_is_key_pressed` was replaced with `tcod.event.get_keyboard_state`.
+- `tcod.console_from_file` is deprecated.
+- The `.asc` and `.apf` formats are no longer actively supported.
+
+### Fixed
+
+- Fixed the parsing of SDL 2.0.16 headers.
+
+## [12.7.2] - 2021-07-01
+
+### Fixed
+
+- _Scancode_ and _KeySym_ enums no longer crash when SDL returns an unexpected value.
+
+## [12.7.1] - 2021-06-30
+
+### Added
+
+- Started uploading wheels for ARM64 macOS.
+
+## [12.7.0] - 2021-06-29
+
+### Added
+
+- _tcod.image_ and _tcod.tileset_ now support _pathlib_.
+
+### Fixed
+
+- Wheels for 32-bit Windows now deploy again.
+
+## [12.6.2] - 2021-06-15
+
+### Fixed
+
+- Git is no longer required to install from source.
+
+## [12.6.1] - 2021-06-09
+
+### Fixed
+
+- Fixed version mismatch when building from sources.
+
+## [12.6.0] - 2021-06-09
+
+### Added
+
+- Added the _decoration_ parameter to _Console.draw_frame_.
+ You may use this parameter to designate custom glyphs as the frame border.
+
+### Deprecated
+
+- The handling of negative indexes given to console drawing and printing
+ functions will be changed to be used as absolute coordinates in the future.
+
+## [12.5.1] - 2021-05-30
+
+### Fixed
+
+- The setup script should no longer fail silently when cffi is unavailable.
+
+## [12.5.0] - 2021-05-21
+
+### Changed
+
+- `KeyboardEvent`'s '`scancode`, `sym`, and `mod` attributes now use their respective enums.
+
+## [12.4.0] - 2021-05-21
+
+### Added
+
+- Added modernized REXPaint saving/loading functions.
+ - `tcod.console.load_xp`
+ - `tcod.console.save_xp`
+
+### Changed
+
+- Using `libtcod 1.18.1`.
+- `tcod.event.KeySym` and `tcod.event.Scancode` can now be hashed.
+
+## [12.3.2] - 2021-05-15
+
+### Changed
+
+- Using `libtcod 1.17.1`.
+
+### Fixed
+
+- Fixed regression with loading PNG images.
+
+## [12.3.1] - 2021-05-13
+
+### Fixed
+
+- Fix Windows deployment.
+
+## [12.3.0] - 2021-05-13
+
+### Added
+
+- New keyboard enums:
+ - `tcod.event.KeySym`
+ - `tcod.event.Scancode`
+ - `tcod.event.Modifier`
+- New functions:
+ - `tcod.event.get_keyboard_state`
+ - `tcod.event.get_modifier_state`
+- Added `tcod.console.rgb_graphic` and `tcod.console.rgba_graphic` dtypes.
+- Another name for the Console array attributes: `Console.rgb` and `Console.rgba`.
+
+### Changed
+
+- Using `libtcod 1.17.0`.
+
+### Deprecated
+
+- `Console_tiles_rgb` is being renamed to `Console.rgb`.
+- `Console_tiles` being renamed to `Console.rgba`.
+
+### Fixed
+
+- Contexts now give a more useful error when pickled.
+- Fixed regressions with `tcod.console_print_frame` and `Console.print_frame`
+ when given empty strings as the banner.
+
+## [12.2.0] - 2021-04-09
+
+### Added
+
+- Added `tcod.noise.Algorithm` and `tcod.noise.Implementation` enums.
+- Added `tcod.noise.grid` helper function.
+
+### Deprecated
+
+- The non-enum noise implementation names have been deprecated.
+
+### Fixed
+
+- Indexing Noise classes now works with the FBM implementation.
+
+## [12.1.0] - 2021-04-01
+
+### Added
+
+- Added package-level PyInstaller hook.
+
+### Changed
+
+- Using `libtcod 1.16.7`.
+- `tcod.path.dijkstra2d` now returns the output and accepts an `out` parameter.
+
+### Deprecated
+
+- In the future `tcod.path.dijkstra2d` will no longer modify the input by default. Until then an `out` parameter must be given.
+
+### Fixed
+
+- Fixed crashes from loading tilesets with non-square tile sizes.
+- Tilesets with a size of 0 should no longer crash when used.
+- Prevent division by zero from recommended-console-size functions.
+
+## [12.0.0] - 2021-03-05
+
+### Added
+
+- Now includes PyInstaller hooks within the package itself.
+
+### Deprecated
+
+- The Random class will now warn if the seed it's given will not used
+ deterministically. It will no longer accept non-integer seeds in the future.
+
+### Changed
+
+- Now bundles SDL 2.0.14 for MacOS.
+- `tcod.event` can now detect and will warn about uninitialized tile
+ attributes on mouse events.
+
+### Removed
+
+- Python 3.5 is no longer supported.
+- The `tdl` module has been dropped.
+
+## [11.19.3] - 2021-01-07
+
+### Fixed
+
+- Some wheels had broken version metadata.
+
+## [11.19.2] - 2020-12-30
+
+### Changed
+
+- Now bundles SDL 2.0.10 for MacOS and SDL 2.0.14 for Windows.
+
+### Fixed
+
+- MacOS wheels were failing to bundle dependencies for SDL2.
+
+## [11.19.1] - 2020-12-29
+
+### Fixed
+
+- MacOS wheels failed to deploy for the previous version.
+
+## [11.19.0] - 2020-12-29
+
+### Added
+
+- Added the important `order` parameter to `Context.new_console`.
+
+## [11.18.3] - 2020-12-28
+
+### Changed
+
+- Now bundles SDL 2.0.14 for Windows/MacOS.
+
+### Deprecated
+
+- Support for Python 3.5 will be dropped.
+- `tcod.console_load_xp` has been deprecated, `tcod.console_from_xp` can load
+ these files without modifying an existing console.
+
+### Fixed
+
+- `tcod.console_from_xp` now has better error handling (instead of crashing.)
+- Can now compile with SDL 2.0.14 headers.
+
+## [11.18.2] - 2020-12-03
+
+### Fixed
+
+- Fixed missing `tcod.FOV_SYMMETRIC_SHADOWCAST` constant.
+- Fixed regression in `tcod.sys_get_current_resolution` behavior. This
+ function now returns the monitor resolution as was previously expected.
+
+## [11.18.1] - 2020-11-30
+
+### Fixed
+
+- Code points from the Private Use Area will now print correctly.
+
+## [11.18.0] - 2020-11-13
+
+### Added
+
+- New context method `Context.new_console`.
+
+### Changed
+
+- Using `libtcod 1.16.0-alpha.15`.
+
+## [11.17.0] - 2020-10-30
+
+### Added
+
+- New FOV implementation: `tcod.FOV_SYMMETRIC_SHADOWCAST`.
+
+### Changed
+
+- Using `libtcod 1.16.0-alpha.14`.
+
+## [11.16.1] - 2020-10-28
+
+### Deprecated
+
+- Changed context deprecations to PendingDeprecationWarning to reduce mass
+ panic from tutorial followers.
+
+### Fixed
+
+- Fixed garbled titles and crashing on some platforms.
+
+## [11.16.0] - 2020-10-23
+
+### Added
+
+- Added `tcod.context.new` function.
+- Contexts now support a CLI.
+- You can now provide the window x,y position when making contexts.
+- `tcod.noise.Noise` instances can now be indexed to generate noise maps.
+
+### Changed
+
+- Using `libtcod 1.16.0-alpha.13`.
+- The OpenGL 2 renderer can now use `SDL_HINT_RENDER_SCALE_QUALITY` to
+ determine the tileset upscaling filter.
+- Improved performance of the FOV_BASIC algorithm.
+
+### Deprecated
+
+- `tcod.context.new_window` and `tcod.context.new_terminal` have been replaced
+ by `tcod.context.new`.
+
+### Fixed
+
+- Pathfinders will now work with boolean arrays.
+- Console blits now ignore alpha compositing which would result in division by
+ zero.
+- `tcod.console_is_key_pressed` should work even if libtcod events are ignored.
+- The `TCOD_RENDERER` and `TCOD_VSYNC` environment variables should work now.
+- `FOV_PERMISSIVE` algorithm is now reentrant.
+
+## [11.15.3] - 2020-07-30
+
+### Fixed
+
+- `tcod.tileset.Tileset.remap`, codepoint and index were swapped.
+
+## [11.15.2] - 2020-07-27
+
+### Fixed
+
+- `tcod.path.dijkstra2d`, fixed corrupted output with int8 arrays.
+
+## [11.15.1] - 2020-07-26
+
+### Changed
+
+- `tcod.event.EventDispatch` now uses the absolute names for event type hints
+ so that IDE's can better auto-complete method overrides.
+
+### Fixed
+
+- Fixed libtcodpy heightmap data alignment issues on non-square maps.
+
+## [11.15.0] - 2020-06-29
+
+### Added
+
+- `tcod.path.SimpleGraph` for pathfinding on simple 2D arrays.
+
+### Changed
+
+- `tcod.path.CustomGraph` now accepts an `order` parameter.
+
+## [11.14.0] - 2020-06-23
+
+### Added
+
+- New `tcod.los` module for NumPy-based line-of-sight algorithms.
+ Includes `tcod.los.bresenham`.
+
+### Deprecated
+
+- `tcod.line_where` and `tcod.line_iter` have been deprecated.
+
+## [11.13.6] - 2020-06-19
+
+### Deprecated
+
+- `console_init_root` and `console_set_custom_font` have been replaced by the
+ modern API.
+- All functions which handle SDL windows without a context are deprecated.
+- All functions which modify a globally active tileset are deprecated.
+- `tcod.map.Map` is deprecated, NumPy arrays should be passed to functions
+ directly instead of through this class.
+
+## [11.13.5] - 2020-06-15
+
+### Fixed
+
+- Install requirements will no longer try to downgrade `cffi`.
+
+## [11.13.4] - 2020-06-15
+
+## [11.13.3] - 2020-06-13
+
+### Fixed
+
+- `cffi` requirement has been updated to version `1.13.0`.
+ The older versions raise TypeError's.
+
+## [11.13.2] - 2020-06-12
+
+### Fixed
+
+- SDL related errors during package installation are now more readable.
+
+## [11.13.1] - 2020-05-30
+
+### Fixed
+
+- `tcod.event.EventDispatch`: `ev_*` methods now allow `Optional[T]` return
+ types.
+
+## [11.13.0] - 2020-05-22
+
+### Added
+
+- `tcod.path`: New `Pathfinder` and `CustomGraph` classes.
+
+### Changed
+
+- Added `edge_map` parameter to `tcod.path.dijkstra2d` and
+ `tcod.path.hillclimb2d`.
+
+### Fixed
+
+- tcod.console_init_root` and context initializing functions were not
+ raising exceptions on failure.
+
+## [11.12.1] - 2020-05-02
+
+### Fixed
+
+- Prevent adding non-existent 2nd halves to potential double-wide charterers.
+
+## [11.12.0] - 2020-04-30
+
+### Added
+
+- Added `tcod.context` module. You now have more options for making libtcod
+ controlled contexts.
+- `tcod.tileset.load_tilesheet`: Load a simple tilesheet as a Tileset.
+- `Tileset.remap`: Reassign codepoints to tiles on a Tileset.
+- `tcod.tileset.CHARMAP_CP437`: Character mapping for `load_tilesheet`.
+- `tcod.tileset.CHARMAP_TCOD`: Older libtcod layout.
+
+### Changed
+
+- `EventDispatch.dispatch` can now return the values returned by the `ev_*`
+ methods. The class is now generic to support type checking these values.
+- Event mouse coordinates are now strictly int types.
+- Submodules are now implicitly imported.
+
+## [11.11.4] - 2020-04-26
+
+### Changed
+
+- Using `libtcod 1.16.0-alpha.10`.
+
+### Fixed
+
+- Fixed characters being dropped when color codes were used.
+
+## [11.11.3] - 2020-04-24
+
+### Changed
+
+- Using `libtcod 1.16.0-alpha.9`.
+
+### Fixed
+
+- `FOV_DIAMOND` and `FOV_RESTRICTIVE` algorithms are now reentrant.
+ [libtcod#48](https://github.com/libtcod/libtcod/pull/48)
+- The `TCOD_VSYNC` environment variable was being ignored.
+
+## [11.11.2] - 2020-04-22
+
+## [11.11.1] - 2020-04-03
+
+### Changed
+
+- Using `libtcod 1.16.0-alpha.8`.
+
+### Fixed
+
+- Changing the active tileset now redraws tiles correctly on the next frame.
+
+## [11.11.0] - 2020-04-02
+
+### Added
+
+- Added `Console.close` as a more obvious way to close the active window of a
+ root console.
+
+### Changed
+
+- GCC is no longer needed to compile the library on Windows.
+- Using `libtcod 1.16.0-alpha.7`.
+- `tcod.console_flush` will now accept an RGB tuple as a `clear_color`.
+
+### Fixed
+
+- Changing the active tileset will now properly show it on the next render.
+
+## [11.10.0] - 2020-03-26
+
+### Added
+
+- Added `tcod.tileset.load_bdf`, you can now load BDF fonts.
+- `tcod.tileset.set_default` and `tcod.tileset.get_default` are now stable.
+
+### Changed
+
+- Using `libtcod 1.16.0-alpha.6`.
+
+### Deprecated
+
+- The `snap_to_integer` parameter in `tcod.console_flush` has been deprecated
+ since it can cause minor scaling issues which don't exist when using
+ `integer_scaling` instead.
+
+## [11.9.2] - 2020-03-17
+
+### Fixed
+
+- Fixed segfault after the Tileset returned by `tcod.tileset.get_default` goes
+ out of scope.
+
+## [11.9.1] - 2020-02-28
+
+### Changed
+
+- Using `libtcod 1.16.0-alpha.5`.
+- Mouse tile coordinates are now always zero before the first call to
+ `tcod.console_flush`.
+
+## [11.9.0] - 2020-02-22
+
+### Added
+
+- New method `Tileset.render` renders an RGBA NumPy array from a tileset and
+ a console.
+
+## [11.8.2] - 2020-02-22
+
+### Fixed
+
+- Prevent KeyError when representing unusual keyboard symbol constants.
+
+## [11.8.1] - 2020-02-22
+
+### Changed
+
+- Using `libtcod 1.16.0-alpha.4`.
+
+### Fixed
+
+- Mouse tile coordinates are now correct on any resized window.
+
+## [11.8.0] - 2020-02-21
+
+### Added
+
+- Added `tcod.console.recommended_size` for when you want to change your main
+ console size at runtime.
+- Added `Console.tiles_rgb` as a replacement for `Console.tiles2`.
+
+### Changed
+
+- Using `libtcod 1.16.0-alpha.3`.
+- Added parameters to `tcod.console_flush`, you can now manually provide a
+ console and adjust how it is presented.
+
+### Deprecated
+
+- `Console.tiles2` is deprecated in favour of `Console.tiles_rgb`.
+- `Console.buffer` is now deprecated in favour of `Console.tiles`, instead of
+ the other way around.
+
+### Fixed
+
+- Fixed keyboard state and mouse state functions losing state when events were
+ flushed.
+
+## [11.7.2] - 2020-02-16
+
+### Fixed
+
+- Fixed regression in `tcod.console_clear`.
+
+## [11.7.1] - 2020-02-16
+
+### Fixed
+
+- Fixed regression in `Console.draw_frame`.
+- The wavelet noise generator now excludes -1.0f and 1.0f as return values.
+- Fixed console fading color regression.
+
+## [11.7.0] - 2020-02-14
+
+### Changed
+
+- Using `libtcod 1.16.0-alpha.2`.
+- When a renderer fails to load it will now fallback to a different one.
+ The order is: OPENGL2 -> OPENGL -> SDL2.
+- The default renderer is now SDL2.
+- The SDL and OPENGL renderers are no longer deprecated, but they now point to
+ slightly different backward compatible implementations.
+
+### Deprecated
+
+- The use of `libtcod.cfg` and `terminal.png` is deprecated.
+
+### Fixed
+
+- `tcod.sys_update_char` now works with the newer renderers.
+- Fixed buffer overflow in name generator.
+- `tcod.image_from_console` now works with the newer renderers.
+- New renderers now auto-load fonts from `libtcod.cfg` or `terminal.png`.
+
+## [11.6.0] - 2019-12-05
+
+### Changed
+
+- Console blit operations now perform per-cell alpha transparency.
+
+## [11.5.1] - 2019-11-23
+
+### Fixed
+
+- Python 3.8 wheels failed to deploy.
+
+## [11.5.0] - 2019-11-22
+
+### Changed
+
+- Quarter block elements are now rendered using Unicode instead of a custom
+ encoding.
+
+### Fixed
+
+- `OPENGL` and `GLSL` renderers were not properly clearing space characters.
+
+## [11.4.1] - 2019-10-15
+
+### Added
+
+- Uploaded Python 3.8 wheels to PyPI.
+
+## [11.4.0] - 2019-09-20
+
+### Added
+
+- Added `__array_interface__` to the Image class.
+- Added `Console.draw_semigraphics` as a replacement for blit_2x functions.
+ `draw_semigraphics` can handle array-like objects.
+- `Image.from_array` class method creates an Image from an array-like object.
+- `tcod.image.load` loads a PNG file as an RGBA array.
+
+### Changed
+
+- `Console.tiles` is now named `Console.buffer`.
+
+## [11.3.0] - 2019-09-06
+
+### Added
+
+- New attribute `Console.tiles2` is similar to `Console.tiles` but without an
+ alpha channel.
+
+## [11.2.2] - 2019-08-25
+
+### Fixed
+
+- Fixed a regression preventing PyInstaller distributions from loading SDL2.
+
+## [11.2.1] - 2019-08-25
+
+## [11.2.0] - 2019-08-24
+
+### Added
+
+- `tcod.path.dijkstra2d`: Computes Dijkstra from an arbitrary initial state.
+- `tcod.path.hillclimb2d`: Returns a path from a distance array.
+- `tcod.path.maxarray`: Creates arrays filled with maximum finite values.
+
+### Fixed
+
+- Changing the tiles of an active tileset on OPENGL2 will no longer leave
+ temporary artifact tiles.
+- It's now harder to accidentally import tcod's internal modules.
+
+## [11.1.2] - 2019-08-02
+
+### Changed
+
+- Now bundles SDL 2.0.10 for Windows/MacOS.
+
+### Fixed
+
+- Can now parse SDL 2.0.10 headers during installation without crashing.
+
+## [11.1.1] - 2019-08-01
+
+### Deprecated
+
+- Using an out-of-bounds index for field-of-view operations now raises a
+ warning, which will later become an error.
+
+### Fixed
+
+- Changing the tiles of an active tileset will now work correctly.
+
+## [11.1.0] - 2019-07-05
+
+### Added
+
+- You can now set the `TCOD_RENDERER` and `TCOD_VSYNC` environment variables to
+ force specific options to be used.
+ Example: `TCOD_RENDERER=sdl2 TCOD_VSYNC=1`
+
+### Changed
+
+- `tcod.sys_set_renderer` now raises an exception if it fails.
+
+### Fixed
+
+- `tcod.console_map_ascii_code_to_font` functions will now work when called
+ before `tcod.console_init_root`.
+
+## [11.0.2] - 2019-06-21
+
+### Changed
+
+- You no longer need OpenGL to build python-tcod.
+
+## [11.0.1] - 2019-06-21
+
+### Changed
+
+- Better runtime checks for Windows dependencies should now give distinct
+ errors depending on if the issue is SDL2 or missing redistributables.
+
+### Fixed
+
+- Changed NumPy type hints from `np.array` to `np.ndarray` which should
+ resolve issues.
+
+## [11.0.0] - 2019-06-14
+
+### Changed
+
+- `tcod.map.compute_fov` now takes a 2-item tuple instead of separate `x` and
+ `y` parameters. This causes less confusion over how axes are aligned.
+
+## [10.1.1] - 2019-06-02
+
+### Changed
+
+- Better string representations for `tcod.event.Event` subclasses.
+
+### Fixed
+
+- Fixed regressions in text alignment for non-rectangle print functions.
+
+## [10.1.0] - 2019-05-24
+
+### Added
+
+- `tcod.console_init_root` now has an optional `vsync` parameter.
+
+## [10.0.5] - 2019-05-17
+
+### Fixed
+
+- Fixed shader compilation issues in the OPENGL2 renderer.
+- Fallback fonts should fail less on Linux.
+
+## [10.0.4] - 2019-05-17
+
+### Changed
+
+- Now depends on cffi 0.12 or later.
+
+### Fixed
+
+- `tcod.console_init_root` and `tcod.console_set_custom_font` will raise
+ exceptions instead of terminating.
+- Fixed issues preventing `tcod.event` from working on 32-bit Windows.
+
+## [10.0.3] - 2019-05-10
+
+### Fixed
+
+- Corrected bounding box issues with the `Console.print_box` method.
+
+## [10.0.2] - 2019-04-26
+
+### Fixed
+
+- Resolved Color warnings when importing tcod.
+- When compiling, fixed a name conflict with endianness macros on FreeBSD.
+
+## [10.0.1] - 2019-04-19
+
+### Fixed
+
+- Fixed horizontal alignment for TrueType fonts.
+- Fixed taking screenshots with the older SDL renderer.
+
+## [10.0.0] - 2019-03-29
+
+### Added
+
+- New `Console.tiles` array attribute.
+
+### Changed
+
+- `Console.DTYPE` changed to add alpha to its color types.
+
+### Fixed
+
+- Console printing was ignoring color codes at the beginning of a string.
+
+## [9.3.0] - 2019-03-15
+
+### Added
+
+- The SDL2/OPENGL2 renderers can potentially use a fall-back font when none
+ are provided.
+- New function `tcod.event.get_mouse_state`.
+- New function `tcod.map.compute_fov` lets you get a visibility array directly
+ from a transparency array.
+
+### Deprecated
+
+- The following functions and classes have been deprecated.
+ - `tcod.Key`
+ - `tcod.Mouse`
+ - `tcod.mouse_get_status`
+ - `tcod.console_is_window_closed`
+ - `tcod.console_check_for_keypress`
+ - `tcod.console_wait_for_keypress`
+ - `tcod.console_delete`
+ - `tcod.sys_check_for_event`
+ - `tcod.sys_wait_for_event`
+- The SDL, OPENGL, and GLSL renderers have been deprecated.
+- Many libtcodpy functions have been marked with PendingDeprecationWarning's.
+
+### Fixed
+
+- To be more compatible with libtcodpy `tcod.console_init_root` will default
+ to the SDL render, but will raise warnings when an old renderer is used.
+
+## [9.2.5] - 2019-03-04
+
+### Fixed
+
+- Fixed `tcod.namegen_generate_custom`.
+
+## [9.2.4] - 2019-03-02
+
+### Fixed
+
+- The `tcod` package is has been marked as typed and will now work with MyPy.
+
+## [9.2.3] - 2019-03-01
+
+### Deprecated
+
+- The behavior for negative indexes on the new print functions may change in
+ the future.
+- Methods and functionality preventing `tcod.Color` from behaving like a tuple
+ have been deprecated.
+
+## [9.2.2] - 2019-02-26
+
+### Fixed
+
+- `Console.print_box` wasn't setting the background color by default.
+
+## [9.2.1] - 2019-02-25
+
+### Fixed
+
+- `tcod.sys_get_char_size` fixed on the new renderers.
+
+## [9.2.0] - 2019-02-24
+
+### Added
+
+- New `tcod.console.get_height_rect` function, which can be used to get the
+ height of a print call without an existing console.
+- New `tcod.tileset` module, with a `set_truetype_font` function.
+
+### Fixed
+
+- The new print methods now handle alignment according to how they were
+ documented.
+- `SDL2` and `OPENGL2` now support screenshots.
+- Windows and MacOS builds now restrict exported SDL2 symbols to only
+ SDL 2.0.5; This will avoid hard to debug import errors when the wrong
+ version of SDL is dynamically linked.
+- The root console now starts with a white foreground.
+
+## [9.1.0] - 2019-02-23
+
+### Added
+
+- Added the `tcod.random.MULTIPLY_WITH_CARRY` constant.
+
+### Changed
+
+- The overhead for warnings has been reduced when running Python with the
+ optimize `-O` flag.
+- `tcod.random.Random` now provides a default algorithm.
+
+## [9.0.0] - 2019-02-17
+
+### Changed
+
+- New console methods now default to an `fg` and `bg` of None instead of
+ white-on-black.
+
+## [8.5.0] - 2019-02-15
+
+### Added
+
+- `tcod.console.Console` now supports `str` and `repr`.
+- Added new Console methods which are independent from the console defaults.
+- You can now give an array when initializing a `tcod.console.Console`
+ instance.
+- `Console.clear` can now take `ch`, `fg`, and `bg` parameters.
+
+### Changed
+
+- Updated libtcod to 1.10.6
+- Printing generates more compact layouts.
+
+### Deprecated
+
+- Most libtcodpy console functions have been replaced by the tcod.console
+ module.
+- Deprecated the `set_key_color` functions. You can pass key colors to
+ `Console.blit` instead.
+- `Console.clear` should be given the colors to clear with as parameters,
+ rather than by using `default_fg` or `default_bg`.
+- Most functions which depend on console default values have been deprecated.
+ The new deprecation warnings will give details on how to make default values
+ explicit.
+
+### Fixed
+
+- `tcod.console.Console.blit` was ignoring the key color set by
+ `Console.set_key_color`.
+- The `SDL2` and `OPENGL2` renders can now large numbers of tiles.
+
+## [8.4.3] - 2019-02-06
+
+### Changed
+
+- Updated libtcod to 1.10.5
+- The SDL2/OPENGL2 renderers will now auto-detect a custom fonts key-color.
+
+## [8.4.2] - 2019-02-05
+
+### Deprecated
+
+- The tdl module has been deprecated.
+- The libtcodpy parser functions have been deprecated.
+
+### Fixed
+
+- `tcod.image_is_pixel_transparent` and `tcod.image_get_alpha` now return
+ values.
+- `Console.print_frame` was clearing tiles outside if its bounds.
+- The `FONT_LAYOUT_CP437` layout was incorrect.
+
+## [8.4.1] - 2019-02-01
+
+### Fixed
+
+- Window event types were not upper-case.
+- Fixed regression where libtcodpy mouse wheel events unset mouse coordinates.
+
+## [8.4.0] - 2019-01-31
+
+### Added
+
+- Added tcod.event module, based off of the sdlevent.py shim.
+
+### Changed
+
+- Updated libtcod to 1.10.3
+
+### Fixed
+
+- Fixed libtcodpy `struct_add_value_list` function.
+- Use correct math for tile-based delta in mouse events.
+- New renderers now support tile-based mouse coordinates.
+- SDL2 renderer will now properly refresh after the window is resized.
+
+## [8.3.2] - 2018-12-28
+
+### Fixed
+
+- Fixed rare access violations for some functions which took strings as
+ parameters, such as `tcod.console_init_root`.
+
+## [8.3.1] - 2018-12-28
+
+### Fixed
+
+- libtcodpy key and mouse functions will no longer accept the wrong types.
+- The `new_struct` method was not being called for libtcodpy's custom parsers.
+
+## [8.3.0] - 2018-12-08
+
+### Added
+
+- Added BSP traversal methods in tcod.bsp for parity with libtcodpy.
+
+### Deprecated
+
+- Already deprecated bsp functions are now even more deprecated.
+
+## [8.2.0] - 2018-11-27
+
+### Added
+
+- New layout `tcod.FONT_LAYOUT_CP437`.
+
+### Changed
+
+- Updated libtcod to 1.10.2
+- `tcod.console_print_frame` and `Console.print_frame` now support Unicode
+ strings.
+
+### Deprecated
+
+- Deprecated using bytes strings for all printing functions.
+
+### Fixed
+
+- Console objects are now initialized with spaces. This fixes some blit
+ operations.
+- Unicode code-points above U+FFFF will now work on all platforms.
+
+## [8.1.1] - 2018-11-16
+
+### Fixed
+
+- Printing a frame with an empty string no longer displays a title bar.
+
+## [8.1.0] - 2018-11-15
+
+### Changed
+
+- Heightmap functions now support 'F_CONTIGUOUS' arrays.
+- `tcod.heightmap_new` now has an `order` parameter.
+- Updated SDL to 2.0.9
+
+### Deprecated
+
+- Deprecated heightmap functions which sample noise grids, this can be done
+ using the `Noise.sample_ogrid` method.
+
+## [8.0.0] - 2018-11-02
+
+### Changed
+
+- The default renderer can now be anything if not set manually.
+- Better error message for when a font file isn't found.
+
+## [7.0.1] - 2018-10-27
+
+### Fixed
+
+- Building from source was failing because `console_2tris.glsl*` was missing
+ from source distributions.
+
+## [7.0.0] - 2018-10-25
+
+### Added
+
+- New `RENDERER_SDL2` and `RENDERER_OPENGL2` renderers.
+
+### Changed
+
+- Updated libtcod to 1.9.0
+- Now requires SDL 2.0.5, which is not trivially installable on
+ Ubuntu 16.04 LTS.
+
+### Removed
+
+- Dropped support for Python versions before 3.5
+- Dropped support for MacOS versions before 10.9 Mavericks.
+
+## [6.0.7] - 2018-10-24
+
+### Fixed
+
+- The root console no longer loses track of buffers and console defaults on a
+ renderer change.
+
+## [6.0.6] - 2018-10-01
+
+### Fixed
+
+- Replaced missing wheels for older and 32-bit versions of MacOS.
+
+## [6.0.5] - 2018-09-28
+
+### Fixed
+
+- Resolved CDefError error during source installs.
+
+## [6.0.4] - 2018-09-11
+
+### Fixed
+
+- tcod.Key right-hand modifiers are now set independently at initialization,
+ instead of mirroring the left-hand modifier value.
+
+## [6.0.3] - 2018-09-05
+
+### Fixed
+
+- tcod.Key and tcod.Mouse no longer ignore initiation parameters.
+
+## [6.0.2] - 2018-08-28
+
+### Fixed
+
+- Fixed color constants missing at build-time.
+
+## [6.0.1] - 2018-08-24
+
+### Fixed
+
+- Source distributions were missing C++ source files.
+
+## [6.0.0] - 2018-08-23
+
+### Changed
+
+- Project renamed to tcod on PyPI.
+
+### Deprecated
+
+- Passing bytes strings to libtcodpy print functions is deprecated.
+
+### Fixed
+
+- Fixed libtcodpy print functions not accepting bytes strings.
+- libtcod constants are now generated at build-time fixing static analysis
+ tools.
+
+## [5.0.1] - 2018-07-08
+
+### Fixed
+
+- tdl.event no longer crashes with StopIteration on Python 3.7
+
+## [5.0.0] - 2018-07-05
+
+### Changed
+
+- tcod.path: all classes now use `shape` instead of `width` and `height`.
+- tcod.path now respects NumPy array shape, instead of assuming that arrays
+ need to be transposed from C memory order. From now on `x` and `y` mean
+ 1st and 2nd axis. This doesn't affect non-NumPy code.
+- tcod.path now has full support of non-contiguous memory.
+
+## [4.6.1] - 2018-06-30
+
+### Added
+
+- New function `tcod.line_where` for indexing NumPy arrays using a Bresenham
+ line.
+
+### Deprecated
+
+- Python 2.7 support will be dropped in the near future.
+
+## [4.5.2] - 2018-06-29
+
+### Added
+
+- New wheels for Python3.7 on Windows.
+
+### Fixed
+
+- Arrays from `tcod.heightmap_new` are now properly zeroed out.
+
+## [4.5.1] - 2018-06-23
+
+### Deprecated
+
+- Deprecated all libtcodpy map functions.
+
+### Fixed
+
+- `tcod.map_copy` could break the `tcod.map.Map` class.
+- `tcod.map_clear` `transparent` and `walkable` parameters were reversed.
+- When multiple SDL2 headers were installed, the wrong ones would be used when
+ the library is built.
+- Fails to build via pip unless Numpy is installed first.
+
+## [4.5.0] - 2018-06-12
+
+### Changed
+
+- Updated libtcod to v1.7.0
+- Updated SDL to v2.0.8
+- Error messages when failing to create an SDL window should be a less vague.
+- You no longer need to initialize libtcod before you can print to an
+ off-screen console.
+
+### Fixed
+
+- Avoid crashes if the root console has a character code higher than expected.
+
+### Removed
+
+- No more debug output when loading fonts.
+
+## [4.4.0] - 2018-05-02
+
+### Added
+
+- Added the libtcodpy module as an alias for tcod. Actual use of it is
+ deprecated, it exists primarily for backward compatibility.
+- Adding missing libtcodpy functions `console_has_mouse_focus` and
+ `console_is_active`.
+
+### Changed
+
+- Updated libtcod to v1.6.6
+
+## [4.3.2] - 2018-03-18
+
+### Deprecated
+
+- Deprecated the use of falsy console parameters with libtcodpy functions.
+
+### Fixed
+
+- Fixed libtcodpy image functions not supporting falsy console parameters.
+- Fixed tdl `Window.get_char` method. (Kaczor2704)
+
+## [4.3.1] - 2018-03-07
+
+### Fixed
+
+- Fixed cffi.api.FFIError "unsupported expression: expected a simple numeric
+ constant" error when building on platforms with an older cffi module and
+ newer SDL headers.
+- tcod/tdl Map and Console objects were not saving stride data when pickled.
+
+## [4.3.0] - 2018-02-01
+
+### Added
+
+- You can now set the numpy memory order on tcod.console.Console,
+ tcod.map.Map, and tdl.map.Map objects well as from the
+ tcod.console_init_root function.
+
+### Changed
+
+- The `console_init_root` `title` parameter is now optional.
+
+### Fixed
+
+- OpenGL renderer alpha blending is now consistent with all other render
+ modes.
+
+## [4.2.3] - 2018-01-06
+
+### Fixed
+
+- Fixed setup.py regression that could prevent building outside of the git
+ repository.
+
+## [4.2.2] - 2018-01-06
+
+### Fixed
+
+- The Windows dynamic linker will now prefer the bundled version of SDL.
+ This fixes:
+ "ImportError: DLL load failed: The specified procedure could not be found."
+- `key.c` is no longer set when `key.vk == KEY_TEXT`, this fixes a regression
+ which was causing events to be heard twice in the libtcod/Python tutorial.
+
+## [4.2.0] - 2018-01-02
+
+### Changed
+
+- Updated libtcod backend to v1.6.4
+- Updated SDL to v2.0.7 for Windows/MacOS.
+
+### Removed
+
+- Source distributions no longer include tests, examples, or fonts.
+ [Find these on GitHub.](https://github.com/libtcod/python-tcod)
+
+### Fixed
+
+- Fixed "final link failed: Nonrepresentable section on output" error
+ when compiling for Linux.
+- `tcod.console_init_root` defaults to the SDL renderer, other renderers
+ cause issues with mouse movement events.
+
+## [4.1.1] - 2017-11-02
+
+### Fixed
+
+- Fixed `ConsoleBuffer.blit` regression.
+- Console defaults corrected, the root console's blend mode and alignment is
+ the default value for newly made Console's.
+- You can give a byte string as a filename to load parsers.
+
+## [4.1.0] - 2017-07-19
+
+### Added
+
+- tdl Map class can now be pickled.
+
+### Changed
+
+- Added protection to the `transparent`, `walkable`, and `fov`
+ attributes in tcod and tdl Map classes, to prevent them from being
+ accidentally overridden.
+- tcod and tdl Map classes now use numpy arrays as their attributes.
+
+## [4.0.1] - 2017-07-12
+
+### Fixed
+
+- tdl: Fixed NameError in `set_fps`.
+
+## [4.0.0] - 2017-07-08
+
+### Changed
+
+- tcod.bsp: `BSP.split_recursive` parameter `random` is now `seed`.
+- tcod.console: `Console.blit` parameters have been rearranged.
+ Most of the parameters are now optional.
+- tcod.noise: `Noise.__init__` parameter `rand` is now named `seed`.
+- tdl: Changed `set_fps` parameter name to `fps`.
+
+### Fixed
+
+- tcod.bsp: Corrected spelling of max_vertical_ratio.
+
+## [3.2.0] - 2017-07-04
+
+### Changed
+
+- Merged libtcod-cffi dependency with TDL.
+
+### Fixed
+
+- Fixed boolean related crashes with Key 'text' events.
+- tdl.noise: Fixed crash when given a negative seed. As well as cases
+ where an instance could lose its seed being pickled.
+
+## [3.1.0] - 2017-05-28
+
+### Added
+
+- You can now pass tdl Console instances as parameters to libtcod-cffi
+ functions expecting a tcod Console.
+
+### Changed
+
+- Dependencies updated: `libtcod-cffi>=2.5.0,<3`
+- The `Console.tcod_console` attribute is being renamed to
+ `Console.console_c`.
+
+### Deprecated
+
+- The tdl.noise and tdl.map modules will be deprecated in the future.
+
+### Fixed
+
+- Resolved crash-on-exit issues for Windows platforms.
+
+## [3.0.2] - 2017-04-13
+
+### Changed
+
+- Dependencies updated: `libtcod-cffi>=2.4.3,<3`
+- You can now create Console instances before a call to `tdl.init`.
+
+### Removed
+
+- Dropped support for Python 3.3
+
+### Fixed
+
+- Resolved issues with MacOS builds.
+- 'OpenGL' and 'GLSL' renderers work again.
+
+## [3.0.1] - 2017-03-22
+
+### Changed
+
+- `KeyEvent`'s with `text` now have all their modifier keys set to False.
+
+### Fixed
+
+- Undefined behavior in text events caused crashes on 32-bit builds.
+
+## [3.0.0] - 2017-03-21
+
+### Added
+
+- `KeyEvent` supports libtcod text and meta keys.
+
+### Changed
+
+- `KeyEvent` parameters have been moved.
+- This version requires `libtcod-cffi>=2.3.0`.
+
+### Deprecated
+
+- `KeyEvent` camel capped attribute names are deprecated.
+
+### Fixed
+
+- Crashes with key-codes undefined by libtcod.
+- `tdl.map` typedef issues with libtcod-cffi.
+
+## [2.0.1] - 2017-02-22
+
+### Fixed
+
+- `tdl.init` renderer was defaulted to OpenGL which is not supported in the
+ current version of libtcod.
+
+## [2.0.0] - 2017-02-15
+
+### Changed
+
+- Dependencies updated, tdl now requires libtcod-cffi 2.x.x
+- Some event behaviors have changed with SDL2, event keys might be different
+ than what you expect.
+
+### Removed
+
+- Key repeat functions were removed from SDL2.
+ `set_key_repeat` is now stubbed, and does nothing.
+
+## [1.6.0] - 2016-11-18
+
+- Console.blit methods can now take fg_alpha and bg_alpha parameters.
+
+## [1.5.3] - 2016-06-04
+
+- set_font no longer crashes when loading a file without the implied font
+ size in its name
+
+## [1.5.2] - 2016-03-11
+
+- Fixed non-square Map instances
+
+## [1.5.1] - 2015-12-20
+
+- Fixed errors with Unicode and non-Unicode literals on Python 2
+- Fixed attribute error in compute_fov
+
+## [1.5.0] - 2015-07-13
+
+- python-tdl distributions are now universal builds
+- New Map class
+- map.bresenham now returns a list
+- This release will require libtcod-cffi v0.2.3 or later
+
+## [1.4.0] - 2015-06-22
+
+- The DLL's have been moved into another library which you can find at
+ https://github.com/HexDecimal/libtcod-cffi
+ You can use this library to have some raw access to libtcod if you want.
+ Plus it can be used alongside TDL.
+- The libtcod console objects in Console instances have been made public.
+- Added tdl.event.wait function. This function can called with a timeout and
+ can automatically call tdl.flush.
+
+## [1.3.1] - 2015-06-19
+
+- Fixed pathfinding regressions.
+
+## [1.3.0] - 2015-06-19
+
+- Updated backend to use python-cffi instead of ctypes. This gives decent
+ boost to speed in CPython and a drastic to boost in speed in PyPy.
+
+## [1.2.0] - 2015-06-06
+
+- The set*colors method now changes the default colors used by the draw*\*
+ methods. You can use Python's Ellipsis to explicitly select default colors
+ this way.
+- Functions and Methods renamed to match Python's style-guide PEP 8, the old
+ function names still exist and are depreciated.
+- The fgcolor and bgcolor parameters have been shortened to fg and bg.
+
+## [1.1.7] - 2015-03-19
+
+- Noise generator now seeds properly.
+- The OS event queue will now be handled during a call to tdl.flush. This
+ prevents a common newbie programmer hang where events are handled
+ infrequently during long animations, simulations, or early development.
+- Fixed a major bug that would cause a crash in later versions of Python 3
+
+## [1.1.6] - 2014-06-27
+
+- Fixed a race condition when importing on some platforms.
+- Fixed a type issue with quickFOV on Linux.
+- Added a bresenham function to the tdl.map module.
+
+## [1.1.5] - 2013-11-10
+
+- A for loop can iterate over all coordinates of a Console.
+- drawStr can be configured to scroll or raise an error.
+- You can now configure or disable key repeating with tdl.event.setKeyRepeat
+- Typewriter class removed, use a Window instance for the same functionality.
+- setColors method fixed.
+
+## [1.1.4] - 2013-03-06
+
+- Merged the Typewriter and MetaConsole classes,
+ You now have a virtual cursor with Console and Window objects.
+- Fixed the clear method on the Window class.
+- Fixed screenshot function.
+- Fixed some drawing operations with unchanging backgrounds.
+- Instances of Console and Noise can be pickled and copied.
+- Added KeyEvent.keychar
+- Fixed event.keyWait, and now converts window closed events into Alt+F4.
+
+## [1.1.3] - 2012-12-17
+
+- Some of the setFont parameters were incorrectly labeled and documented.
+- setFont can auto-detect tilesets if the font sizes are in the filenames.
+- Added some X11 unicode tilesets, including Unifont.
+
+## [1.1.2] - 2012-12-13
+
+- Window title now defaults to the running scripts filename.
+- Fixed incorrect deltaTime for App.update
+- App will no longer call tdl.flush on its own, you'll need to call this
+ yourself.
+- tdl.noise module added.
+- clear method now defaults to black on black.
+
+## [1.1.1] - 2012-12-05
+
+- Map submodule added with AStar class and quickFOV function.
+- New Typewriter class.
+- Most console functions can use Python-style negative indexes now.
+- New App.runOnce method.
+- Rectangle geometry is less strict.
+
+## [1.1.0] - 2012-10-04
+
+- KeyEvent.keyname is now KeyEvent.key
+- MouseButtonEvent.button now behaves like KeyEvent.keyname does.
+- event.App class added.
+- Drawing methods no longer have a default for the character parameter.
+- KeyEvent.ctrl is now KeyEvent.control
+
+## [1.0.8] - 2010-04-07
+
+- No longer works in Python 2.5 but now works in 3.x and has been partly
+ tested.
+- Many bug fixes.
+
+## [1.0.5] - 2010-04-06
+
+- Got rid of setuptools dependency, this will make it much more compatible
+ with Python 3.x
+- Fixed a typo with the MacOS library import.
+
+## [1.0.4] - 2010-04-06
+
+- All constant colors (C\_\*) have been removed, they may be put back in later.
+- Made some type assertion failures show the value they received to help in
+ general debugging. Still working on it.
+- Added MacOS and 64-bit Linux support.
+
+## [1.0.0] - 2009-01-31
+
+- First public release.
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
deleted file mode 100755
index 7e43ca90..00000000
--- a/CHANGELOG.rst
+++ /dev/null
@@ -1,458 +0,0 @@
-===========
- Changelog
-===========
-Changes relevant for users of the the tdl and tcod packages are documented
-here.
-
-This project adheres to `Semantic Versioning `_ since
-v2.0.0
-
-Unreleased
-------------------
-
-8.1.0 - 2018-11-15
-------------------
-Changed
- - Heightmap functions now support 'F_CONTIGUOUS' arrays.
- - `tcod.heightmap_new` now has an `order` parameter.
- - Updated SDL to 2.0.9
-Deprecated
- - Deprecated heightmap functions which sample noise grids, this can be done
- using the `Noise.sample_ogrid` method.
-
-8.0.0 - 2018-11-02
-------------------
-Changed
- - The default renderer can now be anything if not set manually.
- - Better error message for when a font file isn't found.
-
-7.0.1 - 2018-10-27
-------------------
-Fixed
- - Building from source was failing because `console_2tris.glsl*` was missing
- from source distributions.
-
-7.0.0 - 2018-10-25
-------------------
-Added
- - New `RENDERER_SDL2` and `RENDERER_OPENGL2` renderers.
-Changed
- - Updated libtcod to 1.9.0
- - Now requires SDL 2.0.5, which is not trivially installable on
- Ubuntu 16.04 LTS.
-Removed
- - Dropped support for Python versions before 3.5
- - Dropped support for MacOS versions before 10.9 Mavericks.
-
-6.0.7 - 2018-10-24
-------------------
-Fixed
- - The root console no longer loses track of buffers and console defaults on a
- renderer change.
-
-6.0.6 - 2018-10-01
-------------------
-Fixed
- - Replaced missing wheels for older and 32-bit versions of MacOS.
-
-6.0.5 - 2018-09-28
-------------------
-Fixed
- - Resolved CDefError error during source installs.
-
-6.0.4 - 2018-09-11
-------------------
-Fixed
- - tcod.Key right-hand modifiers are now set independently at initialization,
- instead of mirroring the left-hand modifier value.
-
-6.0.3 - 2018-09-05
-------------------
-Fixed
- - tcod.Key and tcod.Mouse no longer ignore initiation parameters.
-
-6.0.2 - 2018-08-28
-------------------
-Fixed
- - Fixed color constants missing at build-time.
-
-6.0.1 - 2018-08-24
-------------------
-Fixed
- - Source distributions were missing C++ source files.
-
-6.0.0 - 2018-08-23
-------------------
-Changed
- - Project renamed to tcod on PyPI.
-Deprecated
- - Passing bytes strings to libtcodpy print functions is deprecated.
-Fixed
- - Fixed libtcodpy print functions not accepting bytes strings.
- - libtcod constants are now generated at build-time fixing static analysis
- tools.
-
-5.0.1 - 2018-07-08
-------------------
-Fixed
- - tdl.event no longer crashes with StopIteration on Python 3.7
-
-5.0.0 - 2018-07-05
-------------------
-Changed
- - tcod.path: all classes now use `shape` instead of `width` and `height`.
- - tcod.path now respects NumPy array shape, instead of assuming that arrays
- need to be transposed from C memory order. From now on `x` and `y` mean
- 1st and 2nd axis. This doesn't affect non-NumPy code.
- - tcod.path now has full support of non-contiguous memory.
-
-4.6.1 - 2018-06-30
-------------------
-Added
- - New function `tcod.line_where` for indexing NumPy arrays using a Bresenham
- line.
-Deprecated
- - Python 2.7 support will be dropped in the near future.
-
-4.5.2 - 2018-06-29
-------------------
-Added
- - New wheels for Python3.7 on Windows.
-Fixed
- - Arrays from `tcod.heightmap_new` are now properly zeroed out.
-
-4.5.1 - 2018-06-23
-------------------
-Deprecated
- - Deprecated all libtcodpy map functions.
-Fixed
- - `tcod.map_copy` could break the `tcod.map.Map` class.
- - `tcod.map_clear` `transparent` and `walkable` parameters were reversed.
- - When multiple SDL2 headers were installed, the wrong ones would be used when
- the library is built.
- - Fails to build via pip unless Numpy is installed first.
-
-4.5.0 - 2018-06-12
-------------------
-Changed
- - Updated libtcod to v1.7.0
- - Updated SDL to v2.0.8
- - Error messages when failing to create an SDL window should be a less vague.
- - You no longer need to initialize libtcod before you can print to an
- off-screen console.
-Fixed
- - Avoid crashes if the root console has a character code higher than expected.
-Removed
- - No more debug output when loading fonts.
-
-4.4.0 - 2018-05-02
-------------------
-Added
- - Added the libtcodpy module as an alias for tcod. Actual use of it is
- deprecated, it exists primarily for backward compatibility.
- - Adding missing libtcodpy functions `console_has_mouse_focus` and
- `console_is_active`.
-Changed
- - Updated libtcod to v1.6.6
-
-4.3.2 - 2018-03-18
-------------------
-Deprecated
- - Deprecated the use of falsy console parameters with libtcodpy functions.
-Fixed
- - Fixed libtcodpy image functions not supporting falsy console parameters.
- - Fixed tdl `Window.get_char` method. (Kaczor2704)
-
-4.3.1 - 2018-03-07
-------------------
-Fixed
- - Fixed cffi.api.FFIError "unsupported expression: expected a simple numeric
- constant" error when building on platforms with an older cffi module and
- newer SDL headers.
- - tcod/tdl Map and Console objects were not saving stride data when pickled.
-
-4.3.0 - 2018-02-01
-------------------
-Added
- - You can now set the numpy memory order on tcod.console.Console,
- tcod.map.Map, and tdl.map.Map objects well as from the
- tcod.console_init_root function.
-Changed
- - The `console_init_root` `title` parameter is now optional.
-Fixed
- - OpenGL renderer alpha blending is now consistent with all other render
- modes.
-
-4.2.3 - 2018-01-06
-------------------
-Fixed
- - Fixed setup.py regression that could prevent building outside of the git
- repository.
-
-4.2.2 - 2018-01-06
-------------------
-Fixed
- - The Windows dynamic linker will now prefer the bundled version of SDL.
- This fixes:
- "ImportError: DLL load failed: The specified procedure could not be found."
- - `key.c` is no longer set when `key.vk == KEY_TEXT`, this fixes a regression
- which was causing events to be heard twice in the libtcod/Python tutorial.
-
-4.2.0 - 2018-01-02
-------------------
-Changed
- - Updated libtcod backend to v1.6.4
- - Updated SDL to v2.0.7 for Windows/MacOS.
-Removed
- - Source distributions no longer include tests, examples, or fonts.
- `Find these on GitHub. `_
-Fixed
- - Fixed "final link failed: Nonrepresentable section on output" error
- when compiling for Linux.
- - `tcod.console_init_root` defaults to the SDL renderer, other renderers
- cause issues with mouse movement events.
-
-4.1.1 - 2017-11-02
-------------------
-Fixed
- - Fixed `ConsoleBuffer.blit` regression.
- - Console defaults corrected, the root console's blend mode and alignment is
- the default value for newly made Console's.
- - You can give a byte string as a filename to load parsers.
-
-4.1.0 - 2017-07-19
-------------------
-Added
- - tdl Map class can now be pickled.
-Changed
- - Added protection to the `transparent`, `walkable`, and `fov`
- attributes in tcod and tdl Map classes, to prevent them from being
- accidentally overridden.
- - tcod and tdl Map classes now use numpy arrays as their attributes.
-
-4.0.1 - 2017-07-12
-------------------
-Fixed
- - tdl: Fixed NameError in `set_fps`.
-
-4.0.0 - 2017-07-08
-------------------
-Changed
- - tcod.bsp: `BSP.split_recursive` parameter `random` is now `seed`.
- - tcod.console: `Console.blit` parameters have been rearranged.
- Most of the parameters are now optional.
- - tcod.noise: `Noise.__init__` parameter `rand` is now named `seed`.
- - tdl: Changed `set_fps` paramter name to `fps`.
-Fixed
- - tcod.bsp: Corrected spelling of max_vertical_ratio.
-
-3.2.0 - 2017-07-04
-------------------
-Changed
- - Merged libtcod-cffi dependency with TDL.
-Fixed
- - Fixed boolean related crashes with Key 'text' events.
- - tdl.noise: Fixed crash when given a negative seed. As well as cases
- where an instance could lose its seed being pickled.
-
-3.1.0 - 2017-05-28
-------------------
-Added
- - You can now pass tdl Console instances as parameters to libtcod-cffi
- functions expecting a tcod Console.
-Changed
- - Dependencies updated: `libtcod-cffi>=2.5.0,<3`
- - The `Console.tcod_console` attribute is being renamed to
- `Console.console_c`.
-Deprecated
- - The tdl.noise and tdl.map modules will be deprecated in the future.
-Fixed
- - Resolved crash-on-exit issues for Windows platforms.
-
-3.0.2 - 2017-04-13
-------------------
-Changed
- - Dependencies updated: `libtcod-cffi>=2.4.3,<3`
- - You can now create Console instances before a call to `tdl.init`.
-Removed
- - Dropped support for Python 3.3
-Fixed
- - Resolved issues with MacOS builds.
- - 'OpenGL' and 'GLSL' renderers work again.
-
-3.0.1 - 2017-03-22
-------------------
-Changed
- - `KeyEvent`'s with `text` now have all their modifier keys set to False.
-Fixed
- - Undefined behaviour in text events caused crashes on 32-bit builds.
-
-3.0.0 - 2017-03-21
-------------------
-Added
- - `KeyEvent` supports libtcod text and meta keys.
-Changed
- - `KeyEvent` parameters have been moved.
- - This version requires `libtcod-cffi>=2.3.0`.
-Deprecated
- - `KeyEvent` camel capped attribute names are deprecated.
-Fixed
- - Crashes with key-codes undefined by libtcod.
- - `tdl.map` typedef issues with libtcod-cffi.
-
-
-2.0.1 - 2017-02-22
-------------------
-Fixed
- - `tdl.init` renderer was defaulted to OpenGL which is not supported in the
- current version of libtcod.
-
-2.0.0 - 2017-02-15
-------------------
-Changed
- - Dependencies updated, tdl now requires libtcod-cffi 2.x.x
- - Some event behaviours have changed with SDL2, event keys might be different
- than what you expect.
-Removed
- - Key repeat functions were removed from SDL2.
- `set_key_repeat` is now stubbed, and does nothing.
-
-1.6.0 - 2016-11-18
-------------------
-- Console.blit methods can now take fg_alpha and bg_alpha parameters.
-
-1.5.3 - 2016-06-04
-------------------
-- set_font no longer crashes when loading a file without the implied font
- size in its name
-
-1.5.2 - 2016-03-11
-------------------
-- Fixed non-square Map instances
-
-1.5.1 - 2015-12-20
-------------------
-- Fixed errors with Unicode and non-Unicode literals on Python 2
-- Fixed attribute error in compute_fov
-
-1.5.0 - 2015-07-13
-------------------
-- python-tdl distributions are now universal builds
-- New Map class
-- map.bresenham now returns a list
-- This release will require libtcod-cffi v0.2.3 or later
-
-1.4.0 - 2015-06-22
-------------------
-- The DLL's have been moved into another library which you can find at
- https://github.com/HexDecimal/libtcod-cffi
- You can use this library to have some raw access to libtcod if you want.
- Plus it can be used alongside TDL.
-- The libtocd console objects in Console instances have been made public.
-- Added tdl.event.wait function. This function can called with a timeout and
- can automatically call tdl.flush.
-
-1.3.1 - 2015-06-19
-------------------
-- Fixed pathfinding regressions.
-
-1.3.0 - 2015-06-19
-------------------
-- Updated backend to use python-cffi instead of ctypes. This gives decent
- boost to speed in CPython and a drastic to boost in speed in PyPy.
-
-1.2.0 - 2015-06-06
-------------------
-- The set_colors method now changes the default colors used by the draw_*
- methods. You can use Python's Ellipsis to explicitly select default colors
- this way.
-- Functions and Methods renamed to match Python's style-guide PEP 8, the old
- function names still exist and are depreciated.
-- The fgcolor and bgcolor parameters have been shortened to fg and bg.
-
-1.1.7 - 2015-03-19
-------------------
-- Noise generator now seeds properly.
-- The OS event queue will now be handled during a call to tdl.flush. This
- prevents a common newbie programmer hang where events are handled
- infrequently during long animations, simulations, or early development.
-- Fixed a major bug that would cause a crash in later versions of Python 3
-
-1.1.6 - 2014-06-27
-------------------
-- Fixed a race condition when importing on some platforms.
-- Fixed a type issue with quickFOV on Linux.
-- Added a bresenham function to the tdl.map module.
-
-1.1.5 - 2013-11-10
-------------------
-- A for loop can iterate over all coordinates of a Console.
-- drawStr can be configured to scroll or raise an error.
-- You can now configure or disable key repeating with tdl.event.setKeyRepeat
-- Typewriter class removed, use a Window instance for the same functionality.
-- setColors method fixed.
-
-1.1.4 - 2013-03-06
-------------------
-- Merged the Typewriter and MetaConsole classes,
- You now have a virtual cursor with Console and Window objects.
-- Fixed the clear method on the Window class.
-- Fixed screenshot function.
-- Fixed some drawing operations with unchanging backgrounds.
-- Instances of Console and Noise can be pickled and copied.
-- Added KeyEvent.keychar
-- Fixed event.keyWait, and now converts window closed events into Alt+F4.
-
-1.1.3 - 2012-12-17
-------------------
-- Some of the setFont parameters were incorrectly labeled and documented.
-- setFont can auto-detect tilesets if the font sizes are in the filenames.
-- Added some X11 unicode tilesets, including unifont.
-
-1.1.2 - 2012-12-13
-------------------
-- Window title now defaults to the running scripts filename.
-- Fixed incorrect deltaTime for App.update
-- App will no longer call tdl.flush on its own, you'll need to call this
- yourself.
-- tdl.noise module added.
-- clear method now defaults to black on black.
-
-1.1.1 - 2012-12-05
-------------------
-- Map submodule added with AStar class and quickFOV function.
-- New Typewriter class.
-- Most console functions can use Python-style negative indexes now.
-- New App.runOnce method.
-- Rectangle geometry is less strict.
-
-1.1.0 - 2012-10-04
-------------------
-- KeyEvent.keyname is now KeyEvent.key
-- MouseButtonEvent.button now behaves like KeyEvent.keyname does.
-- event.App class added.
-- Drawing methods no longer have a default for the character parameter.
-- KeyEvent.ctrl is now KeyEvent.control
-
-1.0.8 - 2010-04-07
-------------------
-- No longer works in Python 2.5 but now works in 3.x and has been partly
- tested.
-- Many bug fixes.
-
-1.0.5 - 2010-04-06
-------------------
-- Got rid of setuptools dependency, this will make it much more compatible
- with Python 3.x
-- Fixed a typo with the MacOS library import.
-
-1.0.4 - 2010-04-06
-------------------
-- All constant colors (C_*) have been removed, they may be put back in later.
-- Made some type assertion failures show the value they received to help in
- general debugging. Still working on it.
-- Added MacOS and 64-bit Linux support.
-
-1.0.0 - 2009-01-31
-------------------
-- First public release.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 4b8aaa03..846d492c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,42 +1,45 @@
+## Code style
+Code styles are enforced using black and linters.
+These are best enabled with a pre-commit which you can setup with:
+```sh
+pip install pre-commit
+pre-commit install
+```
-# Building tdl
+## Building python-tcod
-To work with the tdl source, your environment must be set up to build
-Python C extensions. You'll also need `cpp` installed for
+To work with the tcod source, your environment must be set up to build
+Python C extensions. You'll also need `cpp` installed for
use with pycparser.
-## Windows
+### Windows
- Install [Microsoft Visual Studio](https://www.visualstudio.com/vs/community/)
--- When asked, choose to install the Python development tools.
-- Install [MinGW](http://www.mingw.org/).
--- Installer is [here](https://sourceforge.net/projects/mingw/files/latest/download).
--- Add the binary folder (default folder is `C:\MinGW\bin`) to your user
- environment PATH variable.
+ - When asked, choose to install the Python development tools.
- Open a command prompt in the cloned git directory.
- Make sure the libtcod submodule is downloaded with this command:
`git submodule update --init`
-- Install an editable version of tdl with this command:
+- Install an editable version of tcod with this command:
`py -m pip install --editable . --verbose`
-## MacOS
+### MacOS
- Open a command prompt in the cloned git directory.
- Install the Xcode command line tools with this command:
`xcode-select --install`
- Make sure the libtcod submodule is downloaded with this command:
`git submodule update --init`
-- Install an editable version of tdl with this command:
+- Install an editable version of tcod with this command:
`pip install --editable . --verbose`
-## Linux
+### Linux
- Open a command prompt in the cloned git directory.
- Assuming a Debian based distribution of Linux.
- Install tdl's dependancies with this command:
- `sudo apt install gcc python-dev libsdl2-dev libffi-dev libomp-dev`
+ Install tcod's dependencies with this command:
+ `sudo apt install gcc python-dev libsdl2-dev libffi-dev`
- Make sure the libtcod submodule is downloaded with this command:
`git submodule update --init`
- Install an editable version of tdl with this command:
diff --git a/LICENSE.txt b/LICENSE.txt
index a81b877c..8af4c736 100755
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,4 +1,6 @@
-Copyright (c) 2009-2018, Kyle Stewart
+BSD 2-Clause License
+
+Copyright (c) 2009-2026, Kyle Benesch and the python-tcod contributors.
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -6,21 +8,18 @@ modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
+
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
-ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-The views and conclusions contained in the software and documentation are those
-of the authors and should not be interpreted as representing official policies,
-either expressed or implied, of the FreeBSD Project.
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/MANIFEST.in b/MANIFEST.in
index eea46284..9fc63cae 100755
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,17 +1,23 @@
-prune examples
-prune fonts
-prune libtcod
-prune tests
-
-include *.py *.cfg *.txt *.rst
+global-exclude .*
+prune .*
-recursive-include tdl *.py *.png *.txt *.md *.rst
+include *.py
+include *.txt
+include *.rst
+include *.toml
+include *.md
-recursive-include tcod *.py *.c* *.h
+recursive-include tcod *.py *.c *.h
-recursive-include libtcod/src *.c* *.h* *.glsl*
-include libtcod/*.txt libtcod/*.md
+prune libtcod
+recursive-include libtcod/src *.glsl* *.c *.h
+include libtcod/*.txt
+include libtcod/*.md
-recursive-include dependencies/fake_libc_include *.h
+prune docs
+prune examples
+prune fonts
+prune scripts
+prune tests
-exclude tcod/*/SDL2.dll
+global-exclude *.dll
diff --git a/README.rst b/README.rst
index c7ef2366..32e9164c 100755
--- a/README.rst
+++ b/README.rst
@@ -6,9 +6,7 @@
========
|VersionsBadge| |ImplementationBadge| |LicenseBadge|
-|PyPI| |RTD| |Appveyor| |Travis| |Coveralls| |Codecov| |Codacy|
-
-|Requires| |Pyup|
+|PyPI| |RTD| |Codecov| |Pyup| |CommitsSinceLastRelease|
=======
About
@@ -32,42 +30,32 @@ project.
Guides and Tutorials for libtcodpy should work with the tcod module.
-The latest documentation can be found
-`here `_.
+The latest documentation can be found here:
+https://python-tcod.readthedocs.io/en/latest/
==============
Installation
==============
-The recommended way to install is by using pip. Older versions of pip will
-have issues installing tcod, so make sure it's up-to-date.
-
-Windows / MacOS
----------------
-To install using pip, use the following command::
-
- > python -m pip install tcod
+Detailed installation instructions are here:
+https://python-tcod.readthedocs.io/en/latest/installation.html
-If you get the error "ImportError: DLL load failed: The specified module could
-not be found." when trying to import tcod/tdl then you may need the latest
-`Microsoft Visual C runtime
-`_.
+For the most part it's just::
-Linux
------
-On Linux python-tcod will need to be built from source.
-Assuming you have Python, pip, and apt-get, then you'll run these commands to
-install python-tcod and its dependencies to your user environment::
-
- $ sudo apt-get install gcc python-dev python3-dev libsdl2-dev libffi-dev libomp5
- $ pip2 install tcod
- $ pip3 install tcod
+ pip3 install tcod
==============
Requirements
==============
-* Python 3.5+
+* Python 3.10+
* Windows, Linux, or MacOS X 10.9+.
-* On Linux, requires libsdl2 (2.0.5+) and libomp5 to run.
+* On Linux, requires libsdl3
+
+===========
+ Changelog
+===========
+
+You can find the most recent changelog
+`here `_.
=========
License
@@ -103,28 +91,15 @@ python-tcod is distributed under the `Simplified 2-clause FreeBSD license
:target: http://python-tcod.readthedocs.io/en/latest/?badge=latest
:alt: Documentation Status
-.. |Appveyor| image:: https://ci.appveyor.com/api/projects/status/bb04bpankj0h1cpa/branch/master?svg=true
- :target: https://ci.appveyor.com/project/HexDecimal/python-tdl/branch/master
-
-.. |Travis| image:: https://travis-ci.org/libtcod/python-tcod.svg?branch=master
- :target: https://travis-ci.org/libtcod/python-tcod
-
-.. |Coveralls| image:: https://coveralls.io/repos/github/HexDecimal/python-tdl/badge.svg?branch=master
- :target: https://coveralls.io/github/HexDecimal/python-tdl?branch=master
-
.. |Codecov| image:: https://codecov.io/gh/libtcod/python-tcod/branch/master/graph/badge.svg
:target: https://codecov.io/gh/libtcod/python-tcod
.. |Issues| image:: https://img.shields.io/github/issues/libtcod/python-tcod.svg?maxAge=3600
:target: https://github.com/libtcod/python-tcod/issues
-.. |Codacy| image:: https://api.codacy.com/project/badge/Grade/b9df9aff87fb4968a0508a72aeb74a72
- :target: https://www.codacy.com/app/4b796c65-github/python-tcod?utm_source=github.com&utm_medium=referral&utm_content=libtcod/python-tcod&utm_campaign=Badge_Grade
-
-.. |Requires| image:: https://requires.io/github/libtcod/python-tcod/requirements.svg?branch=master
- :target: https://requires.io/github/libtcod/python-tcod/requirements/?branch=master
- :alt: Requirements Status
-
.. |Pyup| image:: https://pyup.io/repos/github/libtcod/python-tcod/shield.svg
:target: https://pyup.io/repos/github/libtcod/python-tcod/
:alt: Updates
+
+.. |CommitsSinceLastRelease| image:: https://img.shields.io/github/commits-since/libtcod/python-tcod/latest
+ :target: https://github.com/libtcod/python-tcod/blob/main/CHANGELOG.md
diff --git a/appveyor.yml b/appveyor.yml
deleted file mode 100644
index 83b37843..00000000
--- a/appveyor.yml
+++ /dev/null
@@ -1,57 +0,0 @@
-environment:
- TWINE_USERNAME:
- secure: 6EhNSnUl0yOO26EeQ5WG1pQx8v/vp99/u24NRpZvF0k=
- TWINE_PASSWORD:
- secure: PwPzzV8DPqw/+5M66FJQ5tbiF7rlOIK+bQIl8dFdF34=
- CODACY_PROJECT_TOKEN:
- secure: xprpiCGL823NKrs/K2Cps1UVBEmpezXReLxcfLyU1M43ZBBOK91xvjdIJamYKi8D
- DEPLOY_ONLY: false
- matrix:
- - PYTHON: C:\Python35-x64\python.exe
- platform: x64
- - PYTHON: C:\Python35\python.exe
- platform: Any CPU
- - PYTHON: C:\Python36-x64\python.exe
- platform: x64
- - PYTHON: C:\Python36\python.exe
- platform: Any CPU
- - PYTHON: C:\Python37-x64\python.exe
- platform: x64
- - PYTHON: C:\Python37\python.exe
- platform: Any CPU
-
-matrix:
- allow_failures:
- - DEPLOY_ONLY: true
-clone_depth: 50
-
-cache:
- - '%localappdata%\pip\cache -> setup.py'
-
-init:
-- cmd: "if %APPVEYOR_REPO_TAG%==false if %DEPLOY_ONLY%==true exit /b 1"
-
-install:
-- cmd: "git submodule update --init --recursive"
-- ps: ". .appveyor/install_python.ps1"
-- cmd: "%ACTIVATE_VENV%"
-- cmd: "python --version"
-- cmd: "set PATH=%PATH%;C:\\MinGW\\bin"
-- cmd: "pip install --upgrade setuptools"
-- cmd: "if defined PYPY pip install git+https://bitbucket.org/pypy/numpy.git"
-- cmd: "pip install --requirement requirements.txt"
-- cmd: "python setup.py build sdist develop bdist_wheel"
-build: off
-before_test:
-- cmd: "pip install pytest pytest-cov"
-test_script:
-- ps: "pytest -v"
-
-on_success:
-- pip install codacy-coverage python-coveralls
-- coverage xml
-- if defined CODACY_PROJECT_TOKEN python-codacy-coverage -r coverage.xml
-
-deploy_script:
-- "if defined APPVEYOR_REPO_TAG_NAME pip install twine"
-- "if defined APPVEYOR_REPO_TAG_NAME twine upload --skip-existing dist/*"
diff --git a/build_libtcod.py b/build_libtcod.py
old mode 100644
new mode 100755
index 5d0e3b76..14a46710
--- a/build_libtcod.py
+++ b/build_libtcod.py
@@ -1,300 +1,250 @@
-#!/usr/bin/env python3
+#!/usr/bin/env python
+"""Parse and compile libtcod and SDL sources for CFFI."""
-import os
-import sys
+from __future__ import annotations
+import ast
+import contextlib
import glob
+import os
+import platform
+import re
+import subprocess
+import sys
+from pathlib import Path
+from typing import TYPE_CHECKING, Any, ClassVar, Final
+import attrs
+import pycparser # type: ignore[import-untyped]
+import pycparser.c_ast # type: ignore[import-untyped]
+import pycparser.c_generator # type: ignore[import-untyped]
from cffi import FFI
-from pycparser import c_parser, c_ast, parse_file, c_generator
-import shutil
-import subprocess
-import platform
-try:
- from urllib import urlretrieve
-except ImportError:
- from urllib.request import urlretrieve
-import zipfile
+# ruff: noqa: T201
+
+sys.path.append(str(Path(__file__).parent)) # Allow importing local modules.
+
+import build_sdl
+
+if TYPE_CHECKING:
+ from collections.abc import Iterable, Iterator
-SDL2_VERSION = os.environ.get('SDL_VERSION', '2.0.9')
-TDL_NO_SDL2_EXPORTS = os.environ.get('TDL_NO_SDL2_EXPORTS', '0') == '1'
+Py_LIMITED_API: None | int = 0x03100000
-CFFI_HEADER = 'tcod/cffi.h'
-CFFI_EXTRA_CDEFS = 'tcod/cdef.h'
+HEADER_PARSE_PATHS = ("tcod/", "libtcod/src/libtcod/")
+HEADER_PARSE_EXCLUDES = ("gl2_ext_.h", "renderer_gl_internal.h", "event.h")
+
+BIT_SIZE, LINKAGE = platform.architecture()
+
+# Regular expressions to parse the headers for cffi.
+RE_COMMENT = re.compile(r"\s*/\*.*?\*/|\s*//*?$", re.DOTALL | re.MULTILINE)
+RE_CPLUSPLUS = re.compile(r"#ifdef __cplusplus.*?#endif.*?$", re.DOTALL | re.MULTILINE)
+RE_PREPROCESSOR = re.compile(r"(?!#define\s+\w+\s+\d+$)#.*?(? None:
+ """Initialize and organize a header file."""
+ self.path = path = path.resolve(strict=True)
+ directory = path.parent
+ depends = set()
+ header = self.path.read_text(encoding="utf-8")
+ header = RE_COMMENT.sub("", header)
+ header = RE_CPLUSPLUS.sub("", header)
+ for dependency in RE_INCLUDE.findall(header):
+ depends.add((directory / str(dependency)).resolve(strict=True))
+ header = RE_PREPROCESSOR.sub("", header)
+ header = RE_TAGS.sub("", header)
+ header = RE_VAFUNC.sub("", header)
+ header = RE_INLINE.sub(r"\1;", header)
+ self.header = header.strip()
+ self.depends = frozenset(depends)
+ self.all_headers[self.path] = self
+
+ def parsed_depends(self) -> Iterator[ParsedHeader]:
+ """Return dependencies excluding ones that were not loaded."""
+ for dep in self.depends:
+ with contextlib.suppress(KeyError):
+ yield self.all_headers[dep]
+
+ def __str__(self) -> str:
+ """Return useful info on this object."""
+ return "Parsed harder at '{}'\n Depends on: {}".format(
+ self.path,
+ "\n\t".join(str(d) for d in self.depends),
+ )
+
+ def __repr__(self) -> str:
+ """Return the representation of this object."""
+ return f"ParsedHeader({self.path!r})"
+
+
+def walk_includes(directory: str) -> Iterator[ParsedHeader]:
+ """Parse all the include files in a directory and subdirectories."""
+ for path, _dirs, files in os.walk(directory):
+ for file in files:
+ if file in HEADER_PARSE_EXCLUDES:
+ continue
+ if file.endswith(".h"):
+ yield ParsedHeader(Path(path, file).resolve(strict=True))
+
+
+def resolve_dependencies(
+ includes: Iterable[ParsedHeader],
+) -> list[ParsedHeader]:
+ """Sort headers by their correct include order."""
+ unresolved = set(includes)
+ resolved: set[ParsedHeader] = set()
+ result = []
+ while unresolved:
+ for item in unresolved:
+ if frozenset(item.parsed_depends()).issubset(resolved):
+ resolved.add(item)
+ result.append(item)
+ if not unresolved & resolved:
+ msg = (
+ "Could not resolve header load order."
+ "\nPossible cyclic dependency with the unresolved headers:"
+ f"\n{unresolved}"
+ )
+ raise RuntimeError(msg)
+ unresolved -= resolved
+ return result
+
+
+def parse_includes() -> list[ParsedHeader]:
+ """Collect all parsed header files and return them.
+
+ Reads HEADER_PARSE_PATHS and HEADER_PARSE_EXCLUDES.
+ """
+ includes: list[ParsedHeader] = []
+ for dirpath in HEADER_PARSE_PATHS:
+ includes.extend(walk_includes(dirpath))
+ return resolve_dependencies(includes)
-BITSIZE, LINKAGE = platform.architecture()
-def walk_sources(directory):
- for path, dirs, files in os.walk(directory):
+def walk_sources(directory: str) -> Iterator[str]:
+ """Iterate over the C sources of a directory recursively."""
+ for path, _dirs, files in os.walk(directory):
for source in files:
- if source.endswith('.c') or source.endswith('.cpp'):
- yield os.path.join(path, source)
+ if source.endswith(".c"):
+ yield str(Path(path, source))
-def find_sources(directory):
- return [os.path.join(directory, source)
- for source in os.listdir(directory)
- if source.endswith('.c')]
-def get_sdl2_file(version):
- if sys.platform == 'win32':
- sdl2_file = 'SDL2-devel-%s-VC.zip' % (version,)
- else:
- assert sys.platform == 'darwin'
- sdl2_file = 'SDL2-%s.dmg' % (version,)
- sdl2_local_file = os.path.join('dependencies', sdl2_file)
- sdl2_remote_file = 'https://www.libsdl.org/release/%s' % sdl2_file
- if not os.path.exists(sdl2_local_file):
- print('Downloading %s' % sdl2_remote_file)
- urlretrieve(sdl2_remote_file, sdl2_local_file)
- return sdl2_local_file
-
-def unpack_sdl2(version):
- sdl2_path = 'dependencies/SDL2-%s' % (version,)
- if sys.platform == 'darwin':
- sdl2_dir = sdl2_path
- sdl2_path += '/SDL2.framework'
- if os.path.exists(sdl2_path):
- return sdl2_path
- sdl2_arc = get_sdl2_file(version)
- print('Extracting %s' % sdl2_arc)
- if sdl2_arc.endswith('.zip'):
- with zipfile.ZipFile(sdl2_arc) as zf:
- zf.extractall('dependencies/')
- else:
- assert sdl2_arc.endswith('.dmg')
- subprocess.check_call(['hdiutil', 'mount', sdl2_arc])
- subprocess.check_call(['mkdir', '-p', sdl2_dir])
- subprocess.check_call(['cp', '-r', '/Volumes/SDL2/SDL2.framework',
- sdl2_dir])
- subprocess.check_call(['hdiutil', 'unmount', '/Volumes/SDL2'])
- return sdl2_path
-
-module_name = 'tcod._libtcod'
-include_dirs = [
- '.',
- 'libtcod/src/vendor/',
- 'libtcod/src/vendor/zlib/',
+includes = parse_includes()
+
+module_name = "tcod._libtcod"
+include_dirs: list[str] = [
+ ".",
+ "libtcod/src/vendor/",
+ "libtcod/src/vendor/utf8proc",
+ "libtcod/src/vendor/zlib/",
+ *build_sdl.include_dirs,
]
-extra_parse_args = []
-extra_compile_args = []
-extra_link_args = []
-sources = []
-
-libraries = []
-library_dirs = []
-define_macros = []
-
-sources += walk_sources('tcod/')
-sources += walk_sources('tdl/')
-sources += walk_sources('libtcod/src/libtcod')
-sources += ['libtcod/src/vendor/glad.c']
-sources += ['libtcod/src/vendor/lodepng.cpp']
-sources += ['libtcod/src/vendor/stb.c']
-sources += ['libtcod/src/vendor/utf8proc/utf8proc.c']
-sources += glob.glob('libtcod/src/vendor/zlib/*.c')
-
-if TDL_NO_SDL2_EXPORTS:
- extra_parse_args.append('-DTDL_NO_SDL2_EXPORTS')
-
-if sys.platform == 'win32':
- libraries += ['User32', 'OpenGL32']
- define_macros.append(('TCODLIB_API', ''))
- define_macros.append(('_CRT_SECURE_NO_WARNINGS', None))
-
-if 'linux' in sys.platform:
- libraries += ['GL']
-
-if sys.platform == 'darwin':
- extra_link_args += ['-framework', 'OpenGL']
- extra_link_args += ['-framework', 'SDL2']
-else:
- libraries += ['SDL2']
+extra_compile_args: list[str] = [*build_sdl.extra_compile_args]
+extra_link_args: list[str] = [*build_sdl.extra_link_args]
+sources: list[str] = []
-# included SDL headers are for whatever OS's don't easily come with them
+libraries: list[str] = [*build_sdl.libraries]
+library_dirs: list[str] = [*build_sdl.library_dirs]
+define_macros: list[tuple[str, Any]] = []
-if sys.platform in ['win32', 'darwin']:
- SDL2_PATH = unpack_sdl2(SDL2_VERSION)
- include_dirs.append('libtcod/src/zlib/')
+if "free-threading build" in sys.version:
+ Py_LIMITED_API = None
+if "PYODIDE" in os.environ:
+ # Unable to apply Py_LIMITED_API to Pyodide in cffi<=1.17.1
+ # https://github.com/python-cffi/cffi/issues/179
+ Py_LIMITED_API = None
-if sys.platform == 'win32':
- include_dirs.append(os.path.join(SDL2_PATH, 'include'))
- ARCH_MAPPING = {'32bit': 'x86', '64bit': 'x64'}
- SDL2_LIB_DIR = os.path.join(SDL2_PATH, 'lib/', ARCH_MAPPING[BITSIZE])
- library_dirs.append(SDL2_LIB_DIR)
- SDL2_LIB_DEST = os.path.join('tcod', ARCH_MAPPING[BITSIZE])
- if not os.path.exists(SDL2_LIB_DEST):
- os.mkdir(SDL2_LIB_DEST)
- shutil.copy(os.path.join(SDL2_LIB_DIR, 'SDL2.dll'), SDL2_LIB_DEST)
+if Py_LIMITED_API:
+ define_macros.append(("Py_LIMITED_API", Py_LIMITED_API))
-def fix_header(filepath):
- """Removes leading whitespace from a MacOS header file.
+sources += walk_sources("tcod/")
+sources += walk_sources("libtcod/src/libtcod/")
+sources += ["libtcod/src/vendor/stb.c"]
+sources += ["libtcod/src/vendor/lodepng.c"]
+sources += ["libtcod/src/vendor/utf8proc/utf8proc.c"]
+sources += glob.glob("libtcod/src/vendor/zlib/*.c")
- This whitespace is causing issues with directives on some platforms.
- """
- with open(filepath, 'r+') as f:
- current = f.read()
- fixed = '\n'.join(line.strip() for line in current.split('\n'))
- if current == fixed:
- return
- f.seek(0)
- f.truncate()
- f.write(fixed)
-
-if sys.platform == 'darwin':
- HEADER_DIR = os.path.join(SDL2_PATH, 'Headers')
- fix_header(os.path.join(HEADER_DIR, 'SDL_assert.h'))
- fix_header(os.path.join(HEADER_DIR, 'SDL_config_macosx.h'))
- include_dirs.append(HEADER_DIR)
- extra_link_args += ['-F%s/..' % SDL2_PATH]
- extra_link_args += ['-rpath', '%s/..' % SDL2_PATH]
- extra_link_args += ['-rpath', '/usr/local/opt/llvm/lib/']
-
-if sys.platform not in ['win32', 'darwin']:
- extra_parse_args += subprocess.check_output(['sdl2-config', '--cflags'],
- universal_newlines=True
- ).strip().split()
- extra_compile_args += extra_parse_args
- extra_link_args += subprocess.check_output(['sdl2-config', '--libs'],
- universal_newlines=True
- ).strip().split()
-
-class CustomPostParser(c_ast.NodeVisitor):
-
- def __init__(self):
- self.ast = None
- self.typedefs = []
- self.removeable_typedefs = []
- self.funcdefs = []
-
- def parse(self, ast):
- self.ast = ast
- self.visit(ast)
- for node in self.funcdefs:
- ast.ext.remove(node)
- for node in self.removeable_typedefs:
- ast.ext.remove(node)
- return ast
-
- def visit_Typedef(self, node):
- if node.name in ['wchar_t', 'size_t']:
- # remove fake typedef placeholders
- self.removeable_typedefs.append(node)
- else:
- self.generic_visit(node)
- if node.name in self.typedefs:
- print('warning: %s redefined' % node.name)
- self.removeable_typedefs.append(node)
- self.typedefs.append(node.name)
-
- def visit_EnumeratorList(self, node):
- """Replace enumerator expressions with '...' stubs."""
- for type, enum in node.children():
- if enum.value is None:
- pass
- elif isinstance(enum.value, (c_ast.BinaryOp, c_ast.UnaryOp)):
- enum.value = c_ast.Constant('int', '...')
- elif hasattr(enum.value, 'type'):
- enum.value = c_ast.Constant(enum.value.type, '...')
-
- def visit_ArrayDecl(self, node):
- if not node.dim:
- return
- if isinstance(node.dim, (c_ast.BinaryOp, c_ast.UnaryOp)):
- node.dim = c_ast.Constant('int', '...')
-
- def visit_Decl(self, node):
- if node.name is None:
- self.generic_visit(node)
- elif (node.name and 'vsprint' in node.name or
- node.name in ['SDL_vsscanf',
- 'SDL_vsnprintf',
- 'SDL_LogMessageV']):
- # exclude va_list related functions
- self.ast.ext.remove(node)
- elif node.name in ['screen']:
- # exclude outdated 'extern SDL_Surface* screen;' line
- self.ast.ext.remove(node)
- else:
- self.generic_visit(node)
-
- def visit_FuncDef(self, node):
- """Exclude function definitions. Should be declarations only."""
- self.funcdefs.append(node)
-
-def get_cdef():
- generator = c_generator.CGenerator()
- return generator.visit(get_ast())
-
-def get_ast():
- global extra_parse_args
- if 'win32' in sys.platform:
- extra_parse_args += [r'-I%s/include' % SDL2_PATH]
- if 'darwin' in sys.platform:
- extra_parse_args += [r'-I%s/Headers' % SDL2_PATH]
-
- ast = parse_file(filename=CFFI_HEADER, use_cpp=True,
- cpp_args=[r'-Idependencies/fake_libc_include',
- r'-DDECLSPEC=',
- r'-DSDLCALL=',
- r'-DTCODLIB_API=',
- r'-DSDL_FORCE_INLINE=',
- r'-U__GNUC__',
- r'-D_SDL_thread_h',
- r'-DDOXYGEN_SHOULD_IGNORE_THIS',
- r'-DMAC_OS_X_VERSION_MIN_REQUIRED=1060',
- r'-D__attribute__(x)=',
- r'-D_PSTDINT_H_INCLUDED',
- ] + extra_parse_args)
- ast = CustomPostParser().parse(ast)
- return ast
-
-# Can force the use of OpenMP with this variable.
-try:
- USE_OPENMP = eval(os.environ.get('USE_OPENMP', 'None').title())
-except Exception:
- USE_OPENMP = None
-
-tdl_build = os.environ.get('TDL_BUILD', 'RELEASE').upper()
-
-MSVC_CFLAGS = {
- 'DEBUG': ['/Od'],
- 'RELEASE': ['/GL', '/O2', '/GS-'],
-}
-MSVC_LDFLAGS = {
- 'DEBUG': [],
- 'RELEASE': ['/LTCG'],
-}
+if sys.platform == "win32":
+ libraries += ["User32"]
+ define_macros.append(("TCODLIB_API", ""))
+ define_macros.append(("_CRT_SECURE_NO_WARNINGS", None))
+
+if sys.platform in ["win32", "darwin"]:
+ include_dirs.append("libtcod/src/zlib/")
+
+
+if sys.platform != "win32":
+ # Fix implicit declaration of multiple functions in zlib.
+ define_macros.append(("HAVE_UNISTD_H", 1))
+
+
+tdl_build = os.environ.get("TDL_BUILD", "RELEASE").upper()
+
+MSVC_CFLAGS = {"DEBUG": ["/Od"], "RELEASE": ["/GL", "/O2", "/GS-", "/wd4996"]}
+MSVC_LDFLAGS: dict[str, list[str]] = {"DEBUG": [], "RELEASE": ["/LTCG"]}
GCC_CFLAGS = {
- 'DEBUG': ['-O0'],
- 'RELEASE': ['-flto', '-O3', '-fPIC'],
+ "DEBUG": ["-std=c99", "-Og", "-g", "-fPIC"],
+ "RELEASE": [
+ "-std=c99",
+ "-flto",
+ "-O3",
+ "-g",
+ "-fPIC",
+ "-Wno-deprecated-declarations",
+ "-Wno-discarded-qualifiers", # Ignore discarded restrict qualifiers.
+ ],
}
-if sys.platform == 'win32' and '--compiler=mingw32' not in sys.argv:
+if sys.platform == "win32" and "--compiler=mingw32" not in sys.argv:
extra_compile_args.extend(MSVC_CFLAGS[tdl_build])
extra_link_args.extend(MSVC_LDFLAGS[tdl_build])
-
- if USE_OPENMP is None:
- USE_OPENMP = sys.version_info[:2] >= (3, 5)
-
- if USE_OPENMP:
- extra_compile_args.append('/openmp')
else:
extra_compile_args.extend(GCC_CFLAGS[tdl_build])
extra_link_args.extend(GCC_CFLAGS[tdl_build])
- if USE_OPENMP is None:
- USE_OPENMP = sys.platform != 'darwin'
-
- if USE_OPENMP:
- extra_compile_args.append('-fopenmp')
- extra_link_args.append('-fopenmp')
ffi = FFI()
-ffi.cdef(get_cdef())
-ffi.cdef(open(CFFI_EXTRA_CDEFS, 'r').read())
+sdl_cdef, sdl_strings = build_sdl.get_cdef()
+ffi.cdef(sdl_cdef)
+for include in includes:
+ try:
+ ffi.cdef(include.header)
+ except Exception: # noqa: PERF203
+ # Print the source, for debugging.
+ print(f"Error with: {include.path}")
+ for i, line in enumerate(include.header.split("\n"), 1):
+ print(f"{i:03i} {line}")
+ raise
+ffi.cdef(
+ """
+#define TCOD_COMPILEDVERSION ...
+"""
+)
ffi.set_source(
- module_name, '#include ',
+ module_name,
+ """\
+#include
+#define SDL_oldnames_h_
+#include """,
include_dirs=include_dirs,
library_dirs=library_dirs,
sources=sources,
@@ -302,48 +252,420 @@ def get_ast():
extra_compile_args=extra_compile_args,
extra_link_args=extra_link_args,
define_macros=define_macros,
+ py_limited_api=True,
)
-CONSTANT_MODULE_HEADER = '''"""
- Constants from the libtcod C API.
+CONSTANT_MODULE_HEADER = '''"""Constants from the libtcod C API.
- This module is auto-generated by `build_libtcod.py`.
+This module is auto-generated by `build_libtcod.py`.
"""
-from __future__ import absolute_import
from tcod.color import Color
'''
-def write_library_constants():
+EVENT_CONSTANT_MODULE_HEADER = '''"""Event constants from SDL's C API.
+
+This module is auto-generated by `build_libtcod.py`.
+"""
+'''
+
+
+def find_sdl_attrs(prefix: str) -> Iterator[tuple[str, int | str | Any]]:
+ """Return names and values from `tcod.lib`.
+
+ `prefix` is used to filter out which names to copy.
+ """
+ from tcod._libtcod import lib # noqa: PLC0415
+
+ if prefix.startswith("SDL_"):
+ name_starts_at = 4
+ elif prefix.startswith("SDL"):
+ name_starts_at = 3
+ else:
+ name_starts_at = 0
+ for attr in dir(lib):
+ if attr.startswith(prefix):
+ yield attr[name_starts_at:], getattr(lib, attr)
+
+
+def parse_sdl_attrs(prefix: str, all_names: list[str] | None) -> tuple[str, str]:
+ """Return the name/value pairs, and the final dictionary string for the library attributes with `prefix`.
+
+ Append matching names to the `all_names` list.
+ """
+ names = []
+ lookup = []
+ for name, value in sorted(find_sdl_attrs(prefix), key=lambda item: item[1]):
+ if name == "KMOD_RESERVED":
+ continue
+ if all_names is not None:
+ all_names.append(name)
+ names.append(f"{name} = {value}")
+ lookup.append(f'{value}: "{name}"')
+ return "\n".join(names), "{{\n {},\n}}".format(",\n ".join(lookup))
+
+
+EXCLUDE_CONSTANTS = [
+ "TCOD_MAJOR_VERSION",
+ "TCOD_MINOR_VERSION",
+ "TCOD_PATCHLEVEL",
+ "TCOD_COMPILEDVERSION",
+ "TCOD_PATHFINDER_MAX_DIMENSIONS",
+ "TCOD_KEY_TEXT_SIZE",
+ "TCOD_NOISE_MAX_DIMENSIONS",
+ "TCOD_NOISE_MAX_OCTAVES",
+ "TCOD_FALLBACK_FONT_SIZE",
+]
+
+EXCLUDE_CONSTANT_PREFIXES = [
+ "TCOD_E_",
+ "TCOD_HEAP_",
+ "TCOD_LEX_",
+ "TCOD_CHARMAP_",
+ "TCOD_LOG_",
+]
+
+
+RE_CONSTANTS_ALL: Final = re.compile(
+ r"(.*# --- From constants.py ---).*(# --- End constants.py ---.*)",
+ re.DOTALL,
+)
+
+
+def update_module_all(filename: Path, new_all: str) -> None:
+ """Update the __all__ of a file with the constants from new_all."""
+ match = RE_CONSTANTS_ALL.match(filename.read_text(encoding="utf-8"))
+ assert match, f"Can't determine __all__ subsection in {filename}!"
+ header, footer = match.groups()
+ filename.write_text(f"{header}\n {new_all},\n {footer}", encoding="utf-8")
+
+
+def generate_enums(prefix: str) -> Iterator[str]:
+ """Generate attribute assignments suitable for a Python enum."""
+ for symbol, value in sorted(find_sdl_attrs(prefix), key=lambda item: item[1]):
+ _, name = symbol.split("_", 1)
+ if name.isdigit():
+ name = f"N{name}"
+ if name in "IOl": # Ignore ambiguous variable name warnings.
+ yield f"{name} = {value} # noqa: E741"
+ else:
+ yield f"{name} = {value}"
+
+
+def write_library_constants() -> None:
"""Write libtcod constants into the tcod.constants module."""
- from tcod._libtcod import lib, ffi
- import tcod.color
- with open('tcod/constants.py', 'w') as f:
+ import tcod.color # noqa: PLC0415
+ from tcod._libtcod import ffi, lib # noqa: PLC0415
+
+ with Path("tcod/constants.py").open("w", encoding="utf-8") as f:
+ all_names = []
f.write(CONSTANT_MODULE_HEADER)
for name in dir(lib):
+ # To exclude specific names use either EXCLUDE_CONSTANTS or
+ # EXCLUDE_CONSTANT_PREFIXES before editing this.
+ if name.endswith("_"):
+ continue
+ if name in EXCLUDE_CONSTANTS:
+ continue
+ if any(name.startswith(prefix) for prefix in EXCLUDE_CONSTANT_PREFIXES):
+ continue
value = getattr(lib, name)
- if name[:5] == 'TCOD_':
- if name.isupper(): # const names
- f.write('%s = %r\n' % (name[5:], value))
- elif name.startswith('FOV'): # fov const names
- f.write('%s = %r\n' % (name, value))
- elif name[:6] == 'TCODK_': # key name
- f.write('KEY_%s = %r\n' % (name[6:], value))
-
- f.write('\n# --- colors ---\n')
+ if name[:5] == "TCOD_":
+ if name.isupper(): # const names
+ f.write(f"{name[5:]} = {value!r}\n")
+ all_names.append(name[5:])
+ elif name.startswith("FOV"): # fov const names
+ f.write(f"{name} = {value!r}\n")
+ all_names.append(name)
+ elif name[:6] == "TCODK_": # key name
+ f.write(f"KEY_{name[6:]} = {value!r}\n")
+ all_names.append(f"KEY_{name[6:]}")
+
+ f.write("\n# --- colors ---\n")
for name in dir(lib):
- if name[:5] != 'TCOD_':
+ if name[:5] != "TCOD_":
continue
value = getattr(lib, name)
if not isinstance(value, ffi.CData):
continue
- if ffi.typeof(value) != ffi.typeof('TCOD_color_t'):
+ if ffi.typeof(value) != ffi.typeof("TCOD_color_t"):
continue
color = tcod.color.Color._new_from_cdata(value)
- f.write('%s = %r\n' % (name[5:], color))
+ f.write(f"{name[5:]} = {color!r}\n")
+
+ all_names_merged = ",\n ".join(f'"{name}"' for name in all_names)
+ f.write(f"\n__all__ = [ # noqa: RUF022\n {all_names_merged},\n]\n")
+ update_module_all(Path("tcod/libtcodpy.py"), all_names_merged)
+
+ with Path("tcod/event_constants.py").open("w", encoding="utf-8") as f:
+ all_names = []
+ f.write(EVENT_CONSTANT_MODULE_HEADER)
+ f.write("\n# --- SDL scancodes ---\n")
+ f.write(f"""{parse_sdl_attrs("SDL_SCANCODE", None)[0]}\n""")
+
+ f.write("\n# --- SDL keyboard symbols ---\n")
+ f.write(f"""{parse_sdl_attrs("SDLK_", None)[0]}\n""")
+
+ f.write("\n# --- SDL keyboard modifiers ---\n")
+ f.write("{}\n_REVERSE_MOD_TABLE = {}\n".format(*parse_sdl_attrs("SDL_KMOD", None)))
+
+ f.write("\n# --- SDL wheel ---\n")
+ f.write("{}\n_REVERSE_WHEEL_TABLE = {}\n".format(*parse_sdl_attrs("SDL_MOUSEWHEEL", all_names)))
+ all_names_merged = ",\n ".join(f'"{name}"' for name in all_names)
+ f.write(f"\n__all__ = [ # noqa: RUF022\n {all_names_merged},\n]\n")
+
+ event_py = Path("tcod/event.py").read_text(encoding="utf-8")
+
+ event_py = re.sub(
+ r"(?<=# --- SDL scancodes ---\n ).*?(?=\n # --- end ---\n)",
+ "\n ".join(generate_enums("SDL_SCANCODE")),
+ event_py,
+ flags=re.DOTALL,
+ )
+ event_py = re.sub(
+ r"(?<=# --- SDL keyboard symbols ---\n ).*?(?=\n # --- end ---\n)",
+ "\n ".join(generate_enums("SDLK")),
+ event_py,
+ flags=re.DOTALL,
+ )
+
+ Path("tcod/event.py").write_text(event_py, encoding="utf-8")
+
+ with Path("tcod/sdl/constants.py").open("w", encoding="utf-8") as f:
+ f.write('"""SDL private constants."""\n\n')
+ for name, value in sdl_strings.items():
+ f.write(f"{name} = {ast.literal_eval(value)!r}\n")
+
+ subprocess.run(["ruff", "format", "--silent", Path("tcod/sdl/constants.py")], check=True) # noqa: S603, S607
+
+
+def _fix_reserved_name(name: str) -> str:
+ """Add underscores to reserved Python keywords."""
+ assert isinstance(name, str)
+ if name in ("def", "in"):
+ return name + "_"
+ return name
+
+
+@attrs.define(frozen=True)
+class ConvertedParam:
+ """Converted type parameter from C types into Python type-hints."""
+
+ name: str = attrs.field(converter=_fix_reserved_name)
+ hint: str
+ original: str
+
+
+def _type_from_names(names: list[str]) -> str:
+ if not names:
+ return ""
+ if names[-1] == "void":
+ return "None"
+ if names in (["unsigned", "char"], ["bool"]):
+ return "bool"
+ if names[-1] in ("size_t", "int", "ptrdiff_t"):
+ return "int"
+ if names[-1] in ("float", "double"):
+ return "float"
+ return "Any"
+
+
+def _param_as_hint(node: pycparser.c_ast.Node, default_name: str) -> ConvertedParam: # noqa: PLR0911
+ """Return a Python type-hint from a C AST node."""
+ original = pycparser.c_generator.CGenerator().visit(node)
+ name: str
+ names: list[str]
+ match node:
+ case pycparser.c_ast.Typename(type=pycparser.c_ast.TypeDecl(type=pycparser.c_ast.IdentifierType(names=names))):
+ # Unnamed type
+ return ConvertedParam(default_name, _type_from_names(names), original)
+ case pycparser.c_ast.Decl(
+ name=name, type=pycparser.c_ast.TypeDecl(type=pycparser.c_ast.IdentifierType(names=names))
+ ):
+ # Named type
+ return ConvertedParam(name, _type_from_names(names), original)
+ case pycparser.c_ast.Decl(
+ name=name,
+ type=pycparser.c_ast.ArrayDecl(
+ type=pycparser.c_ast.TypeDecl(type=pycparser.c_ast.IdentifierType(names=names))
+ ),
+ ):
+ # Named array
+ return ConvertedParam(name, "Any", original)
+ case pycparser.c_ast.Decl(name=name, type=pycparser.c_ast.PtrDecl()):
+ # Named pointer
+ return ConvertedParam(name, "Any", original)
+ case pycparser.c_ast.Typename(name=name, type=pycparser.c_ast.PtrDecl()):
+ # Forwarded struct
+ return ConvertedParam(name or default_name, "Any", original)
+ case pycparser.c_ast.TypeDecl(type=pycparser.c_ast.IdentifierType(names=names)):
+ # Return type
+ return ConvertedParam(default_name, _type_from_names(names), original)
+ case pycparser.c_ast.PtrDecl():
+ # Return pointer
+ return ConvertedParam(default_name, "Any", original)
+ case pycparser.c_ast.EllipsisParam():
+ # C variable args
+ return ConvertedParam("*__args", "Any", original)
+ case _:
+ raise AssertionError
+
+
+class DefinitionCollector(pycparser.c_ast.NodeVisitor): # type: ignore[misc]
+ """Gathers functions and names from C headers."""
+
+ def __init__(self) -> None:
+ """Initialize the object with empty values."""
+ self.functions: list[str] = []
+ """Indented Python function definitions."""
+ self.variables: set[str] = set()
+ """Python variable definitions."""
+
+ def parse_defines(self, string: str, /) -> None:
+ """Parse C define directives into hinted names."""
+ for match in re.finditer(r"#define\s+(\S+)\s+(\S+)\s*", string):
+ name, value = match.groups()
+ if value == "...":
+ self.variables.add(f"{name}: Final[int]")
+ else:
+ self.variables.add(f"{name}: Final[Literal[{value}]] = {value}")
+
+ def visit_Decl(self, node: pycparser.c_ast.Decl) -> None: # noqa: N802
+ """Parse C FFI functions into type hinted Python functions."""
+ match node:
+ case pycparser.c_ast.Decl(
+ type=pycparser.c_ast.FuncDecl(),
+ ):
+ assert isinstance(node.type.args, pycparser.c_ast.ParamList), type(node.type.args)
+ arg_hints = [_param_as_hint(param, f"arg{i}") for i, param in enumerate(node.type.args.params)]
+ return_hint = _param_as_hint(node.type.type, "")
+ if len(arg_hints) == 1 and arg_hints[0].hint == "None": # Remove void parameter
+ arg_hints = []
+
+ python_params = [f"{p.name}: {p.hint}" for p in arg_hints]
+ if python_params:
+ if arg_hints[-1].name.startswith("*"):
+ python_params.insert(-1, "/")
+ else:
+ python_params.append("/")
+ c_def = pycparser.c_generator.CGenerator().visit(node)
+ python_def = f"""def {node.name}({", ".join(python_params)}) -> {return_hint.hint}:"""
+ self.functions.append(f''' {python_def}\n """{c_def}"""''')
+
+ def visit_Enumerator(self, node: pycparser.c_ast.Enumerator) -> None: # noqa: N802
+ """Parse C enums into hinted names."""
+ name: str | None
+ value: str | int
+ match node:
+ case pycparser.c_ast.Enumerator(name=name, value=None):
+ self.variables.add(f"{name}: Final[int]")
+ case pycparser.c_ast.Enumerator(name=name, value=pycparser.c_ast.ID()):
+ self.variables.add(f"{name}: Final[int]")
+ case pycparser.c_ast.Enumerator(name=name, value=pycparser.c_ast.Constant(value=value)):
+ value = int(str(value).removesuffix("u"), base=0)
+ self.variables.add(f"{name}: Final[Literal[{value}]] = {value}")
+ case pycparser.c_ast.Enumerator(
+ name=name, value=pycparser.c_ast.UnaryOp(op="-", expr=pycparser.c_ast.Constant(value=value))
+ ):
+ value = -int(str(value).removesuffix("u"), base=0)
+ self.variables.add(f"{name}: Final[Literal[{value}]] = {value}")
+ case pycparser.c_ast.Enumerator(name=name):
+ self.variables.add(f"{name}: Final[int]")
+ case _:
+ raise AssertionError
+
+
+def write_hints() -> None:
+ """Write a custom _libtcod.pyi file from C definitions."""
+ function_collector = DefinitionCollector()
+ c = pycparser.CParser()
+
+ # Parse SDL headers
+ cdef = sdl_cdef
+ cdef = cdef.replace("int...", "int")
+ cdef = (
+ """
+typedef int bool;
+typedef int int8_t;
+typedef int uint8_t;
+typedef int int16_t;
+typedef int uint16_t;
+typedef int int32_t;
+typedef int uint32_t;
+typedef int int64_t;
+typedef int uint64_t;
+typedef int wchar_t;
+typedef int intptr_t;
+"""
+ + cdef
+ )
+ for match in re.finditer(r"SDL_PIXELFORMAT_\w+", cdef):
+ function_collector.variables.add(f"{match.group()}: int")
+ cdef = re.sub(r"(typedef enum SDL_PixelFormat).*(SDL_PixelFormat;)", r"\1 \2", cdef, flags=re.DOTALL)
+ cdef = cdef.replace("padding[...]", "padding[]")
+ cdef = cdef.replace("...;} SDL_TouchFingerEvent;", "} SDL_TouchFingerEvent;")
+ function_collector.parse_defines(cdef)
+ cdef = re.sub(r"\n#define .*", "", cdef)
+ cdef = re.sub(r"""extern "Python" \{(.*?)\}""", r"\1", cdef, flags=re.DOTALL)
+ cdef = re.sub(r"//.*", "", cdef)
+ cdef = cdef.replace("...;", ";")
+ ast = c.parse(cdef)
+ function_collector.visit(ast)
+
+ # Parse libtcod headers
+ cdef = "\n".join(include.header for include in includes)
+ function_collector.parse_defines(cdef)
+ cdef = re.sub(r"\n?#define .*", "", cdef)
+ cdef = re.sub(r"//.*", "", cdef)
+ cdef = (
+ """
+typedef int int8_t;
+typedef int uint8_t;
+typedef int int16_t;
+typedef int uint16_t;
+typedef int int32_t;
+typedef int uint32_t;
+typedef int int64_t;
+typedef int uint64_t;
+typedef int wchar_t;
+typedef int intptr_t;
+typedef int ptrdiff_t;
+typedef int size_t;
+typedef unsigned char bool;
+typedef void* SDL_PropertiesID;
+"""
+ + cdef
+ )
+ cdef = re.sub(r"""extern "Python" \{(.*?)\}""", r"\1", cdef, flags=re.DOTALL)
+ function_collector.visit(c.parse(cdef))
+ function_collector.variables.add("TCOD_ctx: Any")
+ function_collector.variables.add("TCOD_COMPILEDVERSION: Final[int]")
+
+ # Write PYI file
+ out_functions = """\n\n @staticmethod\n""".join(sorted(function_collector.functions))
+ out_variables = "\n ".join(sorted(function_collector.variables))
+
+ pyi = f"""\
+# Autogenerated with build_libtcod.py
+from typing import Any, Final, Literal
+
+# pyi files for CFFI ports are not standard
+# ruff: noqa: A002, ANN401, D402, D403, D415, N801, N802, N803, N815, PLW0211, PYI021
+
+class _lib:
+ @staticmethod
+{out_functions}
+
+ {out_variables}
+
+lib: _lib
+ffi: Any
+"""
+ Path("tcod/_libtcod.pyi").write_text(pyi)
+ subprocess.run(["ruff", "format", "--silent", Path("tcod/_libtcod.pyi")], check=True) # noqa: S603, S607
if __name__ == "__main__":
- ffi.compile()
+ write_hints()
write_library_constants()
diff --git a/build_sdl.py b/build_sdl.py
new file mode 100755
index 00000000..a1b8f6a2
--- /dev/null
+++ b/build_sdl.py
@@ -0,0 +1,429 @@
+#!/usr/bin/env python
+"""Build script to parse SDL headers and generate CFFI bindings."""
+
+from __future__ import annotations
+
+import functools
+import io
+import logging
+import os
+import re
+import shutil
+import subprocess
+import sys
+import zipfile
+from pathlib import Path
+from tempfile import TemporaryDirectory
+from typing import Any
+
+import pcpp # type: ignore[import-untyped]
+import requests
+
+# This script calls a lot of programs.
+# ruff: noqa: S603, S607
+
+# Ignore f-strings in logging, these will eventually be replaced with t-strings.
+# ruff: noqa: G004
+
+logger = logging.getLogger(__name__)
+
+
+RE_MACHINE = re.compile(r".*\((.+)\)\]", re.DOTALL)
+
+
+@functools.cache
+def python_machine() -> str:
+ """Return the Python machine architecture (e.g. 'i386', 'AMD64', 'ARM64')."""
+ # Only needs to function correctly for Windows platforms.
+ match = RE_MACHINE.match(sys.version)
+ assert match, repr(sys.version)
+ (machine,) = match.groups()
+ machine = {"Intel": "i386"}.get(machine, machine)
+ logger.info(f"python_machine: {machine}")
+ return machine
+
+
+# Reject versions of SDL older than this, update the requirements in the readme if you change this.
+SDL_MIN_VERSION = (3, 2, 0)
+# The SDL version to parse and export symbols from.
+SDL_PARSE_VERSION = os.environ.get("SDL_VERSION", "3.2.16")
+# The SDL version to include in binary distributions.
+SDL_BUNDLE_VERSION = os.environ.get("SDL_VERSION", "3.2.16")
+
+
+# Used to remove excessive newlines in debug outputs.
+RE_NEWLINES = re.compile(r"\n\n+")
+# Functions using va_list need to be culled.
+RE_VAFUNC = re.compile(r"^.*?\([^()]*va_list[^()]*\)\s*;\s*$", re.MULTILINE)
+# Static inline functions need to be culled.
+RE_INLINE = re.compile(r"^static inline.*?^}$", re.MULTILINE | re.DOTALL)
+# Most SDL_PIXELFORMAT names need their values scrubbed.
+RE_PIXELFORMAT = re.compile(r"(?PSDL_PIXELFORMAT_\w+) =[^,}]*")
+# Most SDLK names need their values scrubbed.
+RE_SDLK = re.compile(r"(?PSDLK_\w+) =.*?(?=,\n|}\n)")
+# Remove compile time assertions from the cdef.
+RE_ASSERT = re.compile(r"^.*SDL_COMPILE_TIME_ASSERT.*$", re.MULTILINE)
+# Padding values need to be scrubbed.
+RE_PADDING = re.compile(r"padding\[[^;]*\];")
+
+# These structs have an unusual size when packed by SDL on 32-bit platforms.
+FLEXIBLE_STRUCTS = (
+ "SDL_CommonEvent",
+ "SDL_DisplayEvent",
+ "SDL_WindowEvent",
+ "SDL_KeyboardDeviceEvent",
+ "SDL_KeyboardEvent",
+ "SDL_TextEditingEvent",
+ "SDL_TextEditingCandidatesEvent",
+ "SDL_TextInputEvent",
+ "SDL_MouseDeviceEvent",
+ "SDL_MouseMotionEvent",
+ "SDL_MouseButtonEvent",
+ "SDL_MouseWheelEvent",
+ "SDL_JoyAxisEvent",
+ "SDL_JoyBallEvent",
+ "SDL_JoyHatEvent",
+ "SDL_JoyButtonEvent",
+ "SDL_JoyDeviceEvent",
+ "SDL_JoyBatteryEvent",
+ "SDL_GamepadAxisEvent",
+ "SDL_GamepadButtonEvent",
+ "SDL_GamepadDeviceEvent",
+ "SDL_GamepadTouchpadEvent",
+ "SDL_GamepadSensorEvent",
+ "SDL_AudioDeviceEvent",
+ "SDL_CameraDeviceEvent",
+ "SDL_RenderEvent",
+ "SDL_TouchFingerEvent",
+ "SDL_PenProximityEvent",
+ "SDL_PenMotionEvent",
+ "SDL_PenTouchEvent",
+ "SDL_PenButtonEvent",
+ "SDL_PenAxisEvent",
+ "SDL_DropEvent",
+ "SDL_ClipboardEvent",
+ "SDL_SensorEvent",
+ "SDL_QuitEvent",
+ "SDL_UserEvent",
+)
+
+# Other defined names which sometimes cause issues when parsed.
+IGNORE_DEFINES = frozenset(
+ (
+ "SDL_DEPRECATED",
+ "SDL_INLINE",
+ "SDL_FORCE_INLINE",
+ "SDL_FALLTHROUGH",
+ "SDL_HAS_FALLTHROUGH",
+ "SDL_NO_THREAD_SAFETY_ANALYSIS",
+ "SDL_SCOPED_CAPABILITY",
+ "SDL_NODISCARD",
+ "SDL_NOLONGLONG",
+ "SDL_WINAPI_FAMILY_PHONE",
+ # Might show up in parsing and not in source.
+ "SDL_ANDROID_EXTERNAL_STORAGE_READ",
+ "SDL_ANDROID_EXTERNAL_STORAGE_WRITE",
+ "SDL_ASSEMBLY_ROUTINES",
+ "SDL_RWOPS_VITAFILE",
+ # Prevent double definition.
+ "SDL_FALSE",
+ "SDL_TRUE",
+ # Ignore floating point symbols.
+ "SDL_FLT_EPSILON",
+ # Conditional config flags which might be missing.
+ "SDL_VIDEO_RENDER_D3D12",
+ "SDL_SENSOR_WINDOWS",
+ "SDL_SENSOR_DUMMY",
+ )
+)
+
+
+def check_sdl_version() -> None:
+ """Check the local SDL3 version on Linux distributions."""
+ if not sys.platform.startswith("linux"):
+ return
+ needed_version = f"{SDL_MIN_VERSION[0]}.{SDL_MIN_VERSION[1]}.{SDL_MIN_VERSION[2]}"
+ try:
+ sdl_version_str = subprocess.check_output(
+ ["pkg-config", "sdl3", "--modversion"], universal_newlines=True
+ ).strip()
+ except FileNotFoundError:
+ try:
+ sdl_version_str = subprocess.check_output(["sdl3-config", "--version"], universal_newlines=True).strip()
+ except FileNotFoundError as exc:
+ msg = (
+ f"libsdl3-dev or equivalent must be installed on your system and must be at least version {needed_version}.\n"
+ "sdl3-config must be on PATH."
+ )
+ raise RuntimeError(msg) from exc
+ except subprocess.CalledProcessError as exc:
+ if sys.version_info >= (3, 11):
+ exc.add_note(f"Note: {os.environ.get('PKG_CONFIG_PATH')=}")
+ raise
+ logger.info(f"Found SDL {sdl_version_str}.")
+ sdl_version = tuple(int(s) for s in sdl_version_str.split("."))
+ if sdl_version < SDL_MIN_VERSION:
+ msg = f"SDL version must be at least {needed_version}, (found {sdl_version_str})"
+ raise RuntimeError(msg)
+
+
+def get_sdl_file(version: str) -> Path:
+ """Return a path to an SDL3 archive for the current platform. The archive is downloaded if missing."""
+ if sys.platform == "win32":
+ sdl_archive = f"SDL3-devel-{version}-VC.zip"
+ else:
+ assert sys.platform == "darwin"
+ sdl_archive = f"SDL3-{version}.dmg"
+ sdl_local_file = Path("dependencies", sdl_archive)
+ sdl_remote_url = f"https://www.libsdl.org/release/{sdl_archive}"
+ if not sdl_local_file.exists():
+ logger.info(f"Downloading {sdl_remote_url}")
+ Path("dependencies/").mkdir(parents=True, exist_ok=True)
+ with requests.get(sdl_remote_url) as response: # noqa: S113
+ response.raise_for_status()
+ sdl_local_file.write_bytes(response.content)
+ return sdl_local_file
+
+
+def unpack_sdl(version: str) -> Path:
+ """Return the path to an extracted SDL distribution. Creates it if missing."""
+ sdl_path = Path(f"dependencies/SDL3-{version}")
+ if sys.platform == "darwin":
+ sdl_dir = sdl_path
+ sdl_path /= "SDL3.framework"
+ if sdl_path.exists():
+ return sdl_path
+ sdl_archive = get_sdl_file(version)
+ logger.info(f"Extracting {sdl_archive}")
+ if sdl_archive.suffix == ".zip":
+ with zipfile.ZipFile(sdl_archive) as zf:
+ zf.extractall("dependencies/")
+ elif sys.platform == "darwin":
+ assert sdl_archive.suffix == ".dmg"
+ subprocess.check_call(["hdiutil", "mount", sdl_archive])
+ subprocess.check_call(["mkdir", "-p", sdl_dir])
+ subprocess.check_call(["cp", "-r", "/Volumes/SDL3/SDL3.xcframework/macos-arm64_x86_64/SDL3.framework", sdl_dir])
+ subprocess.check_call(["hdiutil", "unmount", "/Volumes/SDL3"])
+ return sdl_path
+
+
+class SDLParser(pcpp.Preprocessor): # type: ignore[misc]
+ """A modified preprocessor to output code in a format for CFFI."""
+
+ def __init__(self) -> None:
+ """Initialise the object with empty values."""
+ super().__init__()
+ self.line_directive = None # Don't output line directives.
+ self.known_string_defines: dict[str, str] = {}
+ self.known_defines: set[str] = set()
+
+ def get_output(self) -> str:
+ """Return this objects current tokens as a string."""
+ with io.StringIO() as buffer:
+ self.write(buffer)
+ for name in self.known_defines:
+ buffer.write(f"#define {name} ...\n")
+ return buffer.getvalue()
+
+ def on_file_open(self, is_system_include: bool, includepath: str) -> Any: # noqa: ANN401, FBT001
+ """Ignore includes other than SDL headers."""
+ if not Path(includepath).parent.name == "SDL3":
+ raise FileNotFoundError
+ return super().on_file_open(is_system_include, includepath)
+
+ def on_include_not_found(self, is_malformed: bool, is_system_include: bool, curdir: str, includepath: str) -> None: # noqa: ARG002, FBT001
+ """Remove bad includes such as stddef.h and stdarg.h."""
+ assert "SDL3/SDL" not in includepath, (includepath, curdir)
+ raise pcpp.OutputDirective(pcpp.Action.IgnoreAndRemove)
+
+ def _should_track_define(self, tokens: list[Any]) -> bool: # noqa: PLR0911
+ if len(tokens) < 3: # noqa: PLR2004
+ return False
+ if tokens[0].value in IGNORE_DEFINES:
+ return False
+ if not tokens[0].value.isupper():
+ return False # Function-like name, such as SDL_snprintf.
+ if tokens[0].value.startswith("_") or tokens[0].value.endswith("_"):
+ return False # Private name.
+ if tokens[2].value.startswith("_") or tokens[2].value.endswith("_"):
+ return False # Likely calls a private function.
+ if tokens[1].type == "CPP_LPAREN":
+ return False # Function-like macro.
+ if len(tokens) >= 4 and tokens[2].type == "CPP_INTEGER" and tokens[3].type == "CPP_DOT": # noqa: PLR2004
+ return False # Value is a floating point number.
+ if tokens[0].value.startswith("SDL_PR") and (tokens[0].value.endswith("32") or tokens[0].value.endswith("64")):
+ return False # Data type for printing, which is not needed.
+ if tokens[0].value.startswith("SDL_PLATFORM_"):
+ return False # Ignore platform definitions
+ return bool(str(tokens[0].value).startswith(("SDL_", "SDLK_")))
+
+ def on_directive_handle(
+ self,
+ directive: Any, # noqa: ANN401
+ tokens: list[Any],
+ if_passthru: bool, # noqa: FBT001
+ preceding_tokens: list[Any],
+ ) -> Any: # noqa: ANN401
+ """Catch and store definitions."""
+ if directive.value == "define" and self._should_track_define(tokens):
+ if tokens[2].type == "CPP_STRING":
+ self.known_string_defines[tokens[0].value] = tokens[2].value
+ elif tokens[2].value in self.known_string_defines:
+ self.known_string_defines[tokens[0].value] = "..."
+ else:
+ self.known_defines.add(tokens[0].value)
+ return super().on_directive_handle(directive, tokens, if_passthru, preceding_tokens)
+
+
+def get_emscripten_include_dir() -> Path:
+ """Find and return the Emscripten include dir."""
+ # None of the EMSDK environment variables exist! Search PATH for Emscripten as a workaround
+ for path in os.environ["PATH"].split(os.pathsep)[::-1]:
+ if Path(path).match("upstream/emscripten"):
+ return Path(path, "system/include").resolve(strict=True)
+ raise AssertionError(os.environ["PATH"])
+
+
+check_sdl_version()
+
+SDL_PARSE_PATH: Path | None = None
+SDL_BUNDLE_PATH: Path | None = None
+if (sys.platform == "win32" or sys.platform == "darwin") and "PYODIDE" not in os.environ:
+ SDL_PARSE_PATH = unpack_sdl(SDL_PARSE_VERSION)
+ SDL_BUNDLE_PATH = unpack_sdl(SDL_BUNDLE_VERSION)
+
+SDL_INCLUDE: Path
+if sys.platform == "win32" and SDL_PARSE_PATH is not None:
+ SDL_INCLUDE = SDL_PARSE_PATH / "include"
+elif sys.platform == "darwin" and SDL_PARSE_PATH is not None:
+ SDL_INCLUDE = SDL_PARSE_PATH / "Versions/A/Headers"
+else: # Unix
+ matches = re.findall(
+ r"-I(\S+)",
+ subprocess.check_output(["pkg-config", "sdl3", "--cflags"], universal_newlines=True),
+ )
+ if not matches:
+ matches = ["/usr/include"]
+
+ for match in matches:
+ if Path(match, "SDL3/SDL.h").is_file():
+ SDL_INCLUDE = Path(match)
+ break
+ else:
+ raise AssertionError(matches)
+ assert SDL_INCLUDE
+
+logger.info(f"{SDL_INCLUDE=}")
+
+EXTRA_CDEF = """
+#define SDLK_SCANCODE_MASK ...
+
+extern "Python" {
+// SDL_AudioCallback callback.
+void _sdl_audio_stream_callback(void* userdata, SDL_AudioStream *stream, int additional_amount, int total_amount);
+// SDL to Python log function.
+void _sdl_log_output_function(void *userdata, int category, SDL_LogPriority priority, const char *message);
+// Generic event watcher callback.
+bool _sdl_event_watcher(void* userdata, SDL_Event* event);
+}
+"""
+
+
+def get_cdef() -> tuple[str, dict[str, str]]:
+ """Return the parsed code of SDL for CFFI."""
+ with TemporaryDirectory() as temp_dir:
+ # Add a false SDL_oldnames.h to prevent old symbols from being collected
+ fake_header_dir = Path(temp_dir, "SDL3")
+ fake_header_dir.mkdir()
+ (fake_header_dir / "SDL_oldnames.h").write_text("")
+
+ parser = SDLParser()
+ parser.add_path(temp_dir)
+ parser.add_path(SDL_INCLUDE)
+ if Path(SDL_INCLUDE, "../Headers/SDL.h").exists(): # Using MacOS dmg archive
+ fake_sdl_dir = Path(SDL_INCLUDE, "SDL3")
+ if not fake_sdl_dir.exists():
+ fake_sdl_dir.mkdir(exist_ok=False)
+ for file in SDL_INCLUDE.glob("SDL*.h"):
+ shutil.copyfile(file, fake_sdl_dir / file.name)
+ else: # Regular path
+ assert Path(SDL_INCLUDE, "SDL3/SDL.h").exists(), SDL_INCLUDE
+ parser.parse(
+ """
+ // Remove extern keyword.
+ #define extern
+ // Ignore some SDL assert statements.
+ #define DOXYGEN_SHOULD_IGNORE_THIS
+ #define SDL_COMPILE_TIME_ASSERT(x, y)
+
+ #define _SIZE_T_DEFINED_
+ typedef int... size_t;
+ #define bool _Bool
+
+ #define SDL_oldnames_h_
+
+ #include
+ """
+ )
+ sdl_cdef = parser.get_output()
+
+ sdl_cdef = sdl_cdef.replace("_Bool", "bool")
+ sdl_cdef = RE_VAFUNC.sub("", sdl_cdef)
+ sdl_cdef = RE_INLINE.sub("", sdl_cdef)
+ sdl_cdef = RE_PIXELFORMAT.sub(r"\g = ...", sdl_cdef)
+ sdl_cdef = RE_SDLK.sub(r"\g = ...", sdl_cdef)
+ sdl_cdef = RE_NEWLINES.sub("\n", sdl_cdef)
+ sdl_cdef = RE_PADDING.sub("padding[...];", sdl_cdef)
+ sdl_cdef = sdl_cdef.replace("typedef unsigned int uintptr_t;", "typedef int... uintptr_t;").replace(
+ "typedef unsigned int size_t;", "typedef int... size_t;"
+ )
+ for name in FLEXIBLE_STRUCTS:
+ sdl_cdef = sdl_cdef.replace(f"}} {name};", f"...;}} {name};")
+ return sdl_cdef + EXTRA_CDEF, parser.known_string_defines
+
+
+include_dirs: list[str] = []
+extra_compile_args: list[str] = []
+extra_link_args: list[str] = []
+
+libraries: list[str] = []
+library_dirs: list[str] = []
+
+if "PYODIDE" in os.environ:
+ pass
+elif sys.platform == "darwin":
+ extra_link_args += ["-framework", "SDL3"]
+else:
+ libraries += ["SDL3"]
+
+# Bundle the Windows SDL DLL.
+if sys.platform == "win32" and SDL_BUNDLE_PATH is not None:
+ include_dirs.append(str(SDL_INCLUDE))
+ ARCH_MAPPING = {"i386": "x86", "AMD64": "x64", "ARM64": "arm64"}
+ SDL_LIB_DIR = Path(SDL_BUNDLE_PATH, "lib/", ARCH_MAPPING[python_machine()])
+ library_dirs.append(str(SDL_LIB_DIR))
+ SDL_LIB_DEST = Path("tcod", ARCH_MAPPING[python_machine()])
+ SDL_LIB_DEST.mkdir(exist_ok=True)
+ SDL_LIB_DEST_FILE = SDL_LIB_DEST / "SDL3.dll"
+ SDL_LIB_FILE = SDL_LIB_DIR / "SDL3.dll"
+ if not SDL_LIB_DEST_FILE.exists() or SDL_LIB_FILE.read_bytes() != SDL_LIB_DEST_FILE.read_bytes():
+ shutil.copy(SDL_LIB_FILE, SDL_LIB_DEST_FILE)
+
+# Link to the SDL framework on MacOS.
+# Delocate will bundle the binaries in a later step.
+if sys.platform == "darwin" and SDL_BUNDLE_PATH is not None:
+ include_dirs.append(SDL_INCLUDE)
+ extra_link_args += [f"-F{SDL_BUNDLE_PATH}/.."]
+ extra_link_args += ["-rpath", f"{SDL_BUNDLE_PATH}/.."]
+ extra_link_args += ["-rpath", "/usr/local/opt/llvm/lib/"]
+
+if "PYODIDE" in os.environ:
+ extra_compile_args += ["--use-port=sdl3"]
+elif sys.platform not in ["win32", "darwin"]:
+ # Use sdl-config to link to SDL on Linux.
+ extra_compile_args += (
+ subprocess.check_output(["pkg-config", "sdl3", "--cflags"], universal_newlines=True).strip().split()
+ )
+ extra_link_args += (
+ subprocess.check_output(["pkg-config", "sdl3", "--libs"], universal_newlines=True).strip().split()
+ )
diff --git a/debian/changelog b/debian/changelog
deleted file mode 100644
index c2de2136..00000000
--- a/debian/changelog
+++ /dev/null
@@ -1,5 +0,0 @@
-tdl (4.3.2-1) unstable; urgency=low
-
- * source package automatically created by stdeb 0.8.5
-
- -- Kyle Stewart <4b796c65+tdl@gmail.com> Mon, 02 Apr 2018 16:36:32 -0700
diff --git a/debian/compat b/debian/compat
deleted file mode 100644
index ec635144..00000000
--- a/debian/compat
+++ /dev/null
@@ -1 +0,0 @@
-9
diff --git a/debian/control b/debian/control
deleted file mode 100644
index 9bb4c85a..00000000
--- a/debian/control
+++ /dev/null
@@ -1,62 +0,0 @@
-Source: tdl
-Maintainer: Kyle Stewart <4b796c65+tdl@gmail.com>
-Section: python
-Priority: optional
-Build-Depends: dh-python, python-setuptools (>= 0.6b3), python3-setuptools, python-all-dev (>= 2.6.6-3), python3-all-dev, debhelper (>= 9), libsdl2-dev, python-pycparser, python3-pycparser, python-cffi, python3-cffi, python-numpy, python3-numpy
-Standards-Version: 3.9.6
-X-Python-Version: >= 2.7
-X-Python3-Version: >= 3.4
-Homepage: https://github.com/HexDecimal/python-tdl
-
-Package: python-tdl
-Provides: python-libtcod
-Conflicts: python-libtcod
-Architecture: any
-Depends: ${misc:Depends}, ${python:Depends}, ${shlibs:Depends}, libsdl2-2.0-0
-Description: Pythonic cffi port of libtcod.
- .. contents::
- :backlinks: top
- .
- ========
- Status
- ========
- |VersionsBadge| |ImplementationBadge| |LicenseBadge|
- .
- |PyPI| |RTD| |Appveyor| |Travis| |Coveralls| |Codecov| |Codacy| |Scrutinizer| |Landscape|
- .
- |Requires| |Pyup|
- .
- =======
- About
- =======
- This is a Python cffi_ port of libtcod_.
- .
- This library is `hosted on GitHub `_.
- .
- Any issues you have with this module can be reported at the
-
-Package: python3-tdl
-Architecture: any
-Depends: ${misc:Depends}, ${python3:Depends}, ${shlibs:Depends}
-Description: Pythonic cffi port of libtcod.
- .. contents::
- :backlinks: top
- .
- ========
- Status
- ========
- |VersionsBadge| |ImplementationBadge| |LicenseBadge|
- .
- |PyPI| |RTD| |Appveyor| |Travis| |Coveralls| |Codecov| |Codacy| |Scrutinizer| |Landscape|
- .
- |Requires| |Pyup|
- .
- =======
- About
- =======
- This is a Python cffi_ port of libtcod_.
- .
- This library is `hosted on GitHub `_.
- .
- Any issues you have with this module can be reported at the
-
diff --git a/debian/copyright b/debian/copyright
deleted file mode 100755
index 5817ca46..00000000
--- a/debian/copyright
+++ /dev/null
@@ -1,26 +0,0 @@
-Copyright (c) 2017, Kyle Stewart
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-1. Redistributions of source code must retain the above copyright notice, this
- list of conditions and the following disclaimer.
-2. Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
-ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-The views and conclusions contained in the software and documentation are those
-of the authors and should not be interpreted as representing official policies,
-either expressed or implied, of the FreeBSD Project.
\ No newline at end of file
diff --git a/debian/rules b/debian/rules
deleted file mode 100755
index 18fea380..00000000
--- a/debian/rules
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/usr/bin/make -f
-
-# This file was automatically generated by stdeb 0.8.5 at
-# Mon, 02 Apr 2018 16:36:32 -0700
-export PYBUILD_NAME=tdl
-%:
- dh $@ --with python2,python3 --buildsystem=pybuild
-
-# Ignore possible dependency issues with libGL
-# Can be removed if GL is no longer linked.
-override_dh_shlibdeps:
- dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info
diff --git a/debian/source/format b/debian/source/format
deleted file mode 100644
index 163aaf8d..00000000
--- a/debian/source/format
+++ /dev/null
@@ -1 +0,0 @@
-3.0 (quilt)
diff --git a/debian/watch b/debian/watch
deleted file mode 100644
index afee809b..00000000
--- a/debian/watch
+++ /dev/null
@@ -1,4 +0,0 @@
-# please also check http://pypi.debian.net/tdl/watch
-version=3
-opts=uversionmangle=s/(rc|a|b|c)/~$1/ \
-http://pypi.debian.net/tdl/tdl-(.+)\.(?:zip|tgz|tbz|txz|(?:tar\.(?:gz|bz2|xz)))
\ No newline at end of file
diff --git a/dependencies/fake_libc_include/AvailabilityMacros.h b/dependencies/fake_libc_include/AvailabilityMacros.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/AvailabilityMacros.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/TargetConditionals.h b/dependencies/fake_libc_include/TargetConditionals.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/TargetConditionals.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/_ansi.h b/dependencies/fake_libc_include/_ansi.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/_ansi.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/_fake_defines.h b/dependencies/fake_libc_include/_fake_defines.h
deleted file mode 100644
index 41c5d0f9..00000000
--- a/dependencies/fake_libc_include/_fake_defines.h
+++ /dev/null
@@ -1,43 +0,0 @@
-#ifndef _FAKE_DEFINES_H
-#define _FAKE_DEFINES_H
-
-#ifndef NULL
-#define NULL 0
-#endif
-#define BUFSIZ 1024
-#define FOPEN_MAX 20
-#define FILENAME_MAX 1024
-
-#ifndef SEEK_SET
-#define SEEK_SET 0 /* set file offset to offset */
-#endif
-#ifndef SEEK_CUR
-#define SEEK_CUR 1 /* set file offset to current plus offset */
-#endif
-#ifndef SEEK_END
-#define SEEK_END 2 /* set file offset to EOF plus offset */
-#endif
-
-#define __LITTLE_ENDIAN 1234
-#define LITTLE_ENDIAN __LITTLE_ENDIAN
-#define __BIG_ENDIAN 4321
-#define BIG_ENDIAN __BIG_ENDIAN
-#define __BYTE_ORDER __LITTLE_ENDIAN
-#define BYTE_ORDER __BYTE_ORDER
-
-#define EXIT_FAILURE 1
-#define EXIT_SUCCESS 0
-
-#define UCHAR_MAX 255
-#define USHRT_MAX 65535
-#define UINT_MAX 4294967295U
-#define RAND_MAX 32767
-#define INT_MAX 32767
-
-/* va_arg macros and type*/
-typedef int va_list;
-#define va_start(_ap, _type) __builtin_va_start((_ap))
-#define va_arg(_ap, _type) __builtin_va_arg((_ap))
-#define va_end(_list)
-
-#endif
diff --git a/dependencies/fake_libc_include/_fake_typedefs.h b/dependencies/fake_libc_include/_fake_typedefs.h
deleted file mode 100644
index 06f6dc22..00000000
--- a/dependencies/fake_libc_include/_fake_typedefs.h
+++ /dev/null
@@ -1,159 +0,0 @@
-#ifndef _FAKE_TYPEDEFS_H
-#define _FAKE_TYPEDEFS_H
-
-typedef int size_t;
-typedef int __builtin_va_list;
-typedef int __gnuc_va_list;
-/*
-typedef int __int8_t;
-typedef int __uint8_t;
-typedef int __int16_t;
-typedef int __uint16_t;
-typedef int __int_least16_t;
-typedef int __uint_least16_t;
-typedef int __int32_t;
-typedef int __uint32_t;
-typedef int __int64_t;
-typedef int __uint64_t;
-*/
-typedef int __int_least32_t;
-typedef int __uint_least32_t;
-typedef int __s8;
-typedef int __u8;
-typedef int __s16;
-typedef int __u16;
-typedef int __s32;
-typedef int __u32;
-typedef int __s64;
-typedef int __u64;
-typedef int _LOCK_T;
-typedef int _LOCK_RECURSIVE_T;
-typedef int _off_t;
-typedef int __dev_t;
-typedef int __uid_t;
-typedef int __gid_t;
-typedef int _off64_t;
-typedef int _fpos_t;
-typedef int _ssize_t;
-typedef int wint_t;
-typedef int _mbstate_t;
-typedef int _flock_t;
-typedef int _iconv_t;
-typedef int __ULong;
-typedef int __FILE;
-typedef int ptrdiff_t;
-typedef int wchar_t;
-typedef int __off_t;
-typedef int __pid_t;
-typedef int __loff_t;
-typedef int u_char;
-typedef int u_short;
-typedef int u_int;
-typedef int u_long;
-typedef int ushort;
-typedef int uint;
-typedef int clock_t;
-typedef int time_t;
-typedef int daddr_t;
-typedef int caddr_t;
-typedef int ino_t;
-typedef int off_t;
-typedef int dev_t;
-typedef int uid_t;
-typedef int gid_t;
-typedef int pid_t;
-typedef int key_t;
-typedef int ssize_t;
-typedef int mode_t;
-typedef int nlink_t;
-typedef int fd_mask;
-typedef int _types_fd_set;
-typedef int clockid_t;
-typedef int timer_t;
-typedef int useconds_t;
-typedef int suseconds_t;
-typedef int FILE;
-typedef int fpos_t;
-typedef int cookie_read_function_t;
-typedef int cookie_write_function_t;
-typedef int cookie_seek_function_t;
-typedef int cookie_close_function_t;
-typedef int cookie_io_functions_t;
-typedef int div_t;
-typedef int ldiv_t;
-typedef int lldiv_t;
-typedef int sigset_t;
-typedef int __sigset_t;
-typedef int _sig_func_ptr;
-typedef int sig_atomic_t;
-typedef int __tzrule_type;
-typedef int __tzinfo_type;
-typedef int mbstate_t;
-typedef int sem_t;
-typedef int pthread_t;
-typedef int pthread_attr_t;
-typedef int pthread_mutex_t;
-typedef int pthread_mutexattr_t;
-typedef int pthread_cond_t;
-typedef int pthread_condattr_t;
-typedef int pthread_key_t;
-typedef int pthread_once_t;
-typedef int pthread_rwlock_t;
-typedef int pthread_rwlockattr_t;
-typedef int pthread_spinlock_t;
-typedef int pthread_barrier_t;
-typedef int pthread_barrierattr_t;
-typedef int jmp_buf;
-typedef int rlim_t;
-typedef int sa_family_t;
-typedef int sigjmp_buf;
-typedef int stack_t;
-typedef int siginfo_t;
-typedef int z_stream;
-
-/* C99 exact-width integer types */
-typedef signed char int8_t;
-typedef unsigned char uint8_t;
-typedef signed short int16_t;
-typedef unsigned short uint16_t;
-typedef signed int int32_t;
-typedef unsigned int uint32_t;
-typedef signed long int64_t;
-typedef unsigned long uint64_t;
-
-
-/* C99 minimum-width integer types */
-typedef int int_least8_t;
-typedef int uint_least8_t;
-typedef int int_least16_t;
-typedef int uint_least16_t;
-typedef int int_least32_t;
-typedef int uint_least32_t;
-typedef int int_least64_t;
-typedef int uint_least64_t;
-
-/* C99 fastest minimum-width integer types */
-typedef int int_fast8_t;
-typedef int uint_fast8_t;
-typedef int int_fast16_t;
-typedef int uint_fast16_t;
-typedef int int_fast32_t;
-typedef int uint_fast32_t;
-typedef int int_fast64_t;
-typedef int uint_fast64_t;
-
-/* C99 integer types capable of holding object pointers */
-typedef int intptr_t;
-/*
-typedef int uintptr_t;
-*/
-
-/* C99 greatest-width integer types */
-typedef long long intmax_t;
-typedef unsigned long long uintmax_t;
-
-/*
-typedef int va_list;
-*/
-#define bool _Bool
-#endif
diff --git a/dependencies/fake_libc_include/_syslist.h b/dependencies/fake_libc_include/_syslist.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/_syslist.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/alloca.h b/dependencies/fake_libc_include/alloca.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/alloca.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/ar.h b/dependencies/fake_libc_include/ar.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/ar.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/argz.h b/dependencies/fake_libc_include/argz.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/argz.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/arpa/inet.h b/dependencies/fake_libc_include/arpa/inet.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/arpa/inet.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/asm-generic/int-ll64.h b/dependencies/fake_libc_include/asm-generic/int-ll64.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/asm-generic/int-ll64.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/assert.h b/dependencies/fake_libc_include/assert.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/assert.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/avx2intrin.h b/dependencies/fake_libc_include/avx2intrin.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/avx2intrin.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/avx5124fmapsintrin.h b/dependencies/fake_libc_include/avx5124fmapsintrin.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/avx5124fmapsintrin.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/avx5124vnniwintrin.h b/dependencies/fake_libc_include/avx5124vnniwintrin.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/avx5124vnniwintrin.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/avx512bitalgintrin.h b/dependencies/fake_libc_include/avx512bitalgintrin.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/avx512bitalgintrin.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/avx512bwintrin.h b/dependencies/fake_libc_include/avx512bwintrin.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/avx512bwintrin.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/avx512cdintrin.h b/dependencies/fake_libc_include/avx512cdintrin.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/avx512cdintrin.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/avx512dqintrin.h b/dependencies/fake_libc_include/avx512dqintrin.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/avx512dqintrin.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/avx512erintrin.h b/dependencies/fake_libc_include/avx512erintrin.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/avx512erintrin.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/avx512fintrin.h b/dependencies/fake_libc_include/avx512fintrin.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/avx512fintrin.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/avx512ifmaintrin.h b/dependencies/fake_libc_include/avx512ifmaintrin.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/avx512ifmaintrin.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/avx512ifmavlintrin.h b/dependencies/fake_libc_include/avx512ifmavlintrin.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/avx512ifmavlintrin.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/avx512pfintrin.h b/dependencies/fake_libc_include/avx512pfintrin.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/avx512pfintrin.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/avx512vbmi2intrin.h b/dependencies/fake_libc_include/avx512vbmi2intrin.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/avx512vbmi2intrin.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/avx512vbmi2vlintrin.h b/dependencies/fake_libc_include/avx512vbmi2vlintrin.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/avx512vbmi2vlintrin.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/avx512vbmiintrin.h b/dependencies/fake_libc_include/avx512vbmiintrin.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/avx512vbmiintrin.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/avx512vbmivlintrin.h b/dependencies/fake_libc_include/avx512vbmivlintrin.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/avx512vbmivlintrin.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/avx512vlbwintrin.h b/dependencies/fake_libc_include/avx512vlbwintrin.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/avx512vlbwintrin.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/avx512vldqintrin.h b/dependencies/fake_libc_include/avx512vldqintrin.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/avx512vldqintrin.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/avx512vlintrin.h b/dependencies/fake_libc_include/avx512vlintrin.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/avx512vlintrin.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/avx512vnniintrin.h b/dependencies/fake_libc_include/avx512vnniintrin.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/avx512vnniintrin.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/avx512vnnivlintrin.h b/dependencies/fake_libc_include/avx512vnnivlintrin.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/avx512vnnivlintrin.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/avx512vpopcntdqintrin.h b/dependencies/fake_libc_include/avx512vpopcntdqintrin.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/avx512vpopcntdqintrin.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/avx512vpopcntdqvlintrin.h b/dependencies/fake_libc_include/avx512vpopcntdqvlintrin.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/avx512vpopcntdqvlintrin.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/avxintrin.h b/dependencies/fake_libc_include/avxintrin.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/avxintrin.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/complex.h b/dependencies/fake_libc_include/complex.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/complex.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/ctype.h b/dependencies/fake_libc_include/ctype.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/ctype.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/dirent.h b/dependencies/fake_libc_include/dirent.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/dirent.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/dlfcn.h b/dependencies/fake_libc_include/dlfcn.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/dlfcn.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/emmintrin.h b/dependencies/fake_libc_include/emmintrin.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/emmintrin.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/endian.h b/dependencies/fake_libc_include/endian.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/endian.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/envz.h b/dependencies/fake_libc_include/envz.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/envz.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/errno.h b/dependencies/fake_libc_include/errno.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/errno.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/fastmath.h b/dependencies/fake_libc_include/fastmath.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/fastmath.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/fcntl.h b/dependencies/fake_libc_include/fcntl.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/fcntl.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/features.h b/dependencies/fake_libc_include/features.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/features.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/fenv.h b/dependencies/fake_libc_include/fenv.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/fenv.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/float.h b/dependencies/fake_libc_include/float.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/float.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/getopt.h b/dependencies/fake_libc_include/getopt.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/getopt.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/grp.h b/dependencies/fake_libc_include/grp.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/grp.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/iconv.h b/dependencies/fake_libc_include/iconv.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/iconv.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/ieeefp.h b/dependencies/fake_libc_include/ieeefp.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/ieeefp.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/immintrin.h b/dependencies/fake_libc_include/immintrin.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/immintrin.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/inttypes.h b/dependencies/fake_libc_include/inttypes.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/inttypes.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/iso646.h b/dependencies/fake_libc_include/iso646.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/iso646.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/langinfo.h b/dependencies/fake_libc_include/langinfo.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/langinfo.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/libgen.h b/dependencies/fake_libc_include/libgen.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/libgen.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/libintl.h b/dependencies/fake_libc_include/libintl.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/libintl.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/limits.h b/dependencies/fake_libc_include/limits.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/limits.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/linux/socket.h b/dependencies/fake_libc_include/linux/socket.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/linux/socket.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/linux/version.h b/dependencies/fake_libc_include/linux/version.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/linux/version.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/locale.h b/dependencies/fake_libc_include/locale.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/locale.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/malloc.h b/dependencies/fake_libc_include/malloc.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/malloc.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/math.h b/dependencies/fake_libc_include/math.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/math.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/mm_malloc.h b/dependencies/fake_libc_include/mm_malloc.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/mm_malloc.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/mmintrin.h b/dependencies/fake_libc_include/mmintrin.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/mmintrin.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/netdb.h b/dependencies/fake_libc_include/netdb.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/netdb.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/netinet/in.h b/dependencies/fake_libc_include/netinet/in.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/netinet/in.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/netinet/tcp.h b/dependencies/fake_libc_include/netinet/tcp.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/netinet/tcp.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/newlib.h b/dependencies/fake_libc_include/newlib.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/newlib.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/openssl/err.h b/dependencies/fake_libc_include/openssl/err.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/openssl/err.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/openssl/evp.h b/dependencies/fake_libc_include/openssl/evp.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/openssl/evp.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/openssl/hmac.h b/dependencies/fake_libc_include/openssl/hmac.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/openssl/hmac.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/openssl/ssl.h b/dependencies/fake_libc_include/openssl/ssl.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/openssl/ssl.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/openssl/x509v3.h b/dependencies/fake_libc_include/openssl/x509v3.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/openssl/x509v3.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/paths.h b/dependencies/fake_libc_include/paths.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/paths.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/pmmintrin.h b/dependencies/fake_libc_include/pmmintrin.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/pmmintrin.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/process.h b/dependencies/fake_libc_include/process.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/process.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/pthread.h b/dependencies/fake_libc_include/pthread.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/pthread.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/pwd.h b/dependencies/fake_libc_include/pwd.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/pwd.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/reent.h b/dependencies/fake_libc_include/reent.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/reent.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/regdef.h b/dependencies/fake_libc_include/regdef.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/regdef.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/regex.h b/dependencies/fake_libc_include/regex.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/regex.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/sched.h b/dependencies/fake_libc_include/sched.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/sched.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/search.h b/dependencies/fake_libc_include/search.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/search.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/semaphore.h b/dependencies/fake_libc_include/semaphore.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/semaphore.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/setjmp.h b/dependencies/fake_libc_include/setjmp.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/setjmp.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/shaintrin.h b/dependencies/fake_libc_include/shaintrin.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/shaintrin.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/signal.h b/dependencies/fake_libc_include/signal.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/signal.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/smmintrin.h b/dependencies/fake_libc_include/smmintrin.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/smmintrin.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/stdarg.h b/dependencies/fake_libc_include/stdarg.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/stdarg.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/stdbool.h b/dependencies/fake_libc_include/stdbool.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/stdbool.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/stddef.h b/dependencies/fake_libc_include/stddef.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/stddef.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/stdint.h b/dependencies/fake_libc_include/stdint.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/stdint.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/stdio.h b/dependencies/fake_libc_include/stdio.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/stdio.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/stdlib.h b/dependencies/fake_libc_include/stdlib.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/stdlib.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/string.h b/dependencies/fake_libc_include/string.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/string.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/strings.h b/dependencies/fake_libc_include/strings.h
deleted file mode 100644
index 616b4714..00000000
--- a/dependencies/fake_libc_include/strings.h
+++ /dev/null
@@ -1 +0,0 @@
-#include "string.h"
\ No newline at end of file
diff --git a/dependencies/fake_libc_include/sys/ioctl.h b/dependencies/fake_libc_include/sys/ioctl.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/sys/ioctl.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/sys/mman.h b/dependencies/fake_libc_include/sys/mman.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/sys/mman.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/sys/poll.h b/dependencies/fake_libc_include/sys/poll.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/sys/poll.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/sys/resource.h b/dependencies/fake_libc_include/sys/resource.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/sys/resource.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/sys/select.h b/dependencies/fake_libc_include/sys/select.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/sys/select.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/sys/socket.h b/dependencies/fake_libc_include/sys/socket.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/sys/socket.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/sys/stat.h b/dependencies/fake_libc_include/sys/stat.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/sys/stat.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/sys/sysctl.h b/dependencies/fake_libc_include/sys/sysctl.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/sys/sysctl.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/sys/time.h b/dependencies/fake_libc_include/sys/time.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/sys/time.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/sys/types.h b/dependencies/fake_libc_include/sys/types.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/sys/types.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/sys/uio.h b/dependencies/fake_libc_include/sys/uio.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/sys/uio.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/sys/un.h b/dependencies/fake_libc_include/sys/un.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/sys/un.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/sys/utsname.h b/dependencies/fake_libc_include/sys/utsname.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/sys/utsname.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/sys/wait.h b/dependencies/fake_libc_include/sys/wait.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/sys/wait.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/syslog.h b/dependencies/fake_libc_include/syslog.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/syslog.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/tar.h b/dependencies/fake_libc_include/tar.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/tar.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/termios.h b/dependencies/fake_libc_include/termios.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/termios.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/tgmath.h b/dependencies/fake_libc_include/tgmath.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/tgmath.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/time.h b/dependencies/fake_libc_include/time.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/time.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/tmmintrin.h b/dependencies/fake_libc_include/tmmintrin.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/tmmintrin.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/unctrl.h b/dependencies/fake_libc_include/unctrl.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/unctrl.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/unistd.h b/dependencies/fake_libc_include/unistd.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/unistd.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/utime.h b/dependencies/fake_libc_include/utime.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/utime.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/utmp.h b/dependencies/fake_libc_include/utmp.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/utmp.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/wchar.h b/dependencies/fake_libc_include/wchar.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/wchar.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/wctype.h b/dependencies/fake_libc_include/wctype.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/wctype.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/wmmintrin.h b/dependencies/fake_libc_include/wmmintrin.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/wmmintrin.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/x86intrin.h b/dependencies/fake_libc_include/x86intrin.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/x86intrin.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/xmmintrin.h b/dependencies/fake_libc_include/xmmintrin.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/xmmintrin.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dependencies/fake_libc_include/zlib.h b/dependencies/fake_libc_include/zlib.h
deleted file mode 100644
index f952c1d6..00000000
--- a/dependencies/fake_libc_include/zlib.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "_fake_defines.h"
-#include "_fake_typedefs.h"
diff --git a/dev/benchmark.py b/dev/benchmark.py
deleted file mode 100644
index 01570661..00000000
--- a/dev/benchmark.py
+++ /dev/null
@@ -1,117 +0,0 @@
-#!/usr/bin/env python
-
-from __future__ import print_function
-
-import time
-import platform
-
-import tdl
-
-WIDTH = 80 # must be divisible by 16
-HEIGHT = 48
-
-RENDERER = 'OpenGL'
-
-log = None
-
-def print_result(string):
- print(string)
- print(string, file=log)
-
-class Benchmark:
- default_frames = 100
-
- def run(self, console, frames=None, times=4):
- if times > 1:
- print_result('Running %s' % self.__class__.__name__)
- while times > 0:
- self.run(console, frames, times=1)
- times -= 1
- print_result('')
- return
- if frames is None:
- frames = self.default_frames
- self.total_frames = 0
- self.tiles = 0
- console.clear()
- self.start_time = time.clock()
- while self.total_frames < frames:
- self.total_frames += 1
- self.test(console)
- for event in tdl.event.get():
- if event.type == 'QUIT':
- raise SystemExit('Benchmark Canceled')
- self.total_time = time.clock() - self.start_time
- self.tiles_per_second = self.tiles / self.total_time
- print_result(
- '%i tiles drawn in %.2f seconds, %.2f characters/ms, %.2f FPS' %
- (self.tiles, self.total_time,self.tiles_per_second / 1000,
- self.total_frames / self.total_time))
-
- def test(self, console):
- for x,y in console:
- console.draw_char(x, y, '.')
- tiles += 1
- tdl.flush()
-
-
-class Benchmark_DrawChar_DefaultColor(Benchmark):
-
- def test(self, console):
- for x,y in console:
- console.draw_char(x, y, 'A')
- self.tiles += 1
- tdl.flush()
-
-
-class Benchmark_DrawChar_NoColor(Benchmark):
-
- def test(self, console):
- for x,y in console:
- console.draw_char(x, y, 'B', None, None)
- self.tiles += 1
- tdl.flush()
-
-
-class Benchmark_DrawStr16_DefaultColor(Benchmark):
- default_frames = 100
-
- def test(self, console):
- for y in range(HEIGHT):
- for x in range(0, WIDTH, 16):
- console.draw_str(x, y, '0123456789ABCDEF')
- self.tiles += 16
- tdl.flush()
-
-
-class Benchmark_DrawStr16_NoColor(Benchmark):
- default_frames = 100
-
- def test(self, console):
- for y in range(HEIGHT):
- for x in range(0, WIDTH, 16):
- console.draw_str(x, y, '0123456789ABCDEF', None, None)
- self.tiles += 16
- tdl.flush()
-
-def run_benchmark():
- global log
- log = open('results.log', 'a')
- print('', file=log)
- console = tdl.init(WIDTH, HEIGHT, renderer=RENDERER)
-
- print_result('Benchmark run on %s' % time.ctime())
- print_result('Running under %s %s' % (platform.python_implementation(),
- platform.python_version()))
- print_result('In %s mode' % (['release', 'debug'][__debug__]))
- print_result('%i characters/frame' % (WIDTH * HEIGHT))
- print_result('Opened console in %s mode' % RENDERER)
- Benchmark_DrawChar_DefaultColor().run(console)
- Benchmark_DrawChar_NoColor().run(console)
- #Benchmark_DrawStr16_DefaultColor().run(console)
- #Benchmark_DrawStr16_NoColor().run(console)
- log.close()
- print('results written to results.log')
-
-if __name__ == '__main__':
- run_benchmark()
diff --git a/dev/noise_gen.py b/dev/noise_gen.py
deleted file mode 100755
index 5eeddba3..00000000
--- a/dev/noise_gen.py
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/usr/bin/env python
-
-# allow a noise test
-# give a directory and this will save simple bitmaps of each noise example
-import sys, os
-
-sys.path.insert(0, '..')
-from tdl.noise import Noise
-
-IMGSIZE = 128 # width and height of the saved image
-NOISE_SCALE = 1 / 12
-saveDir = ''
-kargs = {}
-if len(sys.argv) >= 2:
- saveDir = sys.argv[1] # get save directory
- kargs = dict((param.split('=') for param in sys.argv[2:])) # get parameters
-if not saveDir:
- raise SystemExit('Provide a directory to save the noise examples.')
-for algo in ['PERLIN', 'SIMPLEX', 'WAVELET']:
- for mode in ['FLAT', 'FBM', 'TURBULENCE']:
- noise = Noise(algo, mode)
- noiseFile = open(os.path.join(saveDir, '%s_%s.pgm' % (algo, mode)), 'wb')
- print('Generating %s' % noiseFile.name)
- # make a greyscale Netpbm file
- noiseFile.write(b'P5\n')
- noiseFile.write(('%i %i\n' % (IMGSIZE, IMGSIZE)).encode('ascii'))
- noiseFile.write(b'255\n')
- for y in range(IMGSIZE):
- noiseY = y * NOISE_SCALE
- for x in range(IMGSIZE):
- noiseX = x * NOISE_SCALE
- if x == 0 or x == IMGSIZE - 1 or y == 0 or y == IMGSIZE - 1:
- val = 0 # use black border
- else:
- val = int(noise.getPoint(noiseX, noiseY) * 255)
- noiseFile.write(bytes((val,)))
diff --git a/dev/samples.py b/dev/samples.py
deleted file mode 100755
index 8044de75..00000000
--- a/dev/samples.py
+++ /dev/null
@@ -1,101 +0,0 @@
-#!/usr/bin/env python
-"""
- Sample pack showcasing the abilities of TDL
-
- This example is not finished quite yet
-"""
-
-import sys
-
-import random
-
-sys.path.insert(0, '../')
-import tdl
-
-sampleIndex = 0
-
-class SampleApp(tdl.event.App):
- name = ''
-
- def key_UP(self, event):
- global sampleIndex
- sampleIndex = (sampleIndex - 1) % len(samples)
-
- def key_DOWN(self, event):
- global sampleIndex
- sampleIndex = (sampleIndex - 1) % len(samples)
-
-class TrueColorSample(SampleApp):
- name = 'True Colors'
-
- def update(self, deltaTime):
- width, height = samplewin.getSize()
- for x in range(width):
- for y in range(height):
- char = random.getrandbits(8)
- samplewin.drawChar(x, y, char, (255, 255, 255), (0, 0, 0))
-
-class NoiseSample(SampleApp):
- name = 'Noise'
- SPEED = 3
-
- NOISE_KEYS = {'1': 'PERLIN', '2': 'SIMPLEX', '3': 'WAVELET'}
- MODE_KEYS = {'4': 'FLAT', '5': 'FBM', '6': 'TURBULENCE'}
-
- def __init__(self):
- self.noiseType = 'PERLIN'
- self.noiseMode = 'FLAT'
- self.x = 0
- self.y = 0
- self.z = 0
- self.zoom = 4
- self.generateNoise()
-
- def generateNoise(self):
- self.noise = tdl.noise.Noise(self.noiseType, self.noiseMode, seed=42)
-
- def ev_KEYDOWN(self, event):
- if event.char in self.NOISE_KEYS:
- self.noiseType = self.NOISE_KEYS[event.char]
- print('noise set to %s' % self.noiseType)
- if event.char in self.MODE_KEYS:
- self.noiseMode = self.MODE_KEYS[event.char]
- print('mode set to %s' % self.noiseMode)
- self.generateNoise()
-
- def update(self, deltaTime):
- self.x += self.SPEED * deltaTime# * self.zoom
- self.y += self.SPEED * deltaTime# * self.zoom
- self.z += deltaTime / 4
-
- width, height = samplewin.getSize()
- for x in range(width):
- for y in range(height):
- val = self.noise.getPoint((x + self.x) / width * self.zoom,
- (y + self.y) / height * self.zoom,
- self.z)
- bgcolor = (int(val * 255),) * 2 + (min(255, int(val * 2 * 255)),)
- samplewin.drawChar(x, y, ' ', (255, 255, 255), bgcolor)
-
-WIDTH, HEIGHT = 80, 40
-SAMPLE_WINDOW_RECT = (20, 10, 46, 20)
-
-FONT = '../fonts/X11/8x13.png'
-
-if __name__ == '__main__':
- tdl.setFont(FONT)
- console = tdl.init(WIDTH, HEIGHT, renderer='opengl')
- samplewin = tdl.Window(console, *SAMPLE_WINDOW_RECT)
-
- samples = [cls() for cls in [TrueColorSample, NoiseSample]]
-
- while 1:
- console.clear()
- samples[sampleIndex].runOnce()
- for i, sample in enumerate(samples):
- bgcolor = (0, 0, 0)
- if sampleIndex == i:
- bgcolor = (0, 0, 192)
- console.drawStr(0, -5 + i, '%s' % sample.name, (255, 255, 255), bgcolor)
- console.drawStr(0, -1, '%i FPS' % tdl.getFPS())
- tdl.flush()
diff --git a/dev/sdl_hook_test.py b/dev/sdl_hook_test.py
deleted file mode 100644
index 454ad642..00000000
--- a/dev/sdl_hook_test.py
+++ /dev/null
@@ -1,41 +0,0 @@
-#!/usr/bin/env python
-"""
- example of libtcod's SDL hook
-
- draws a simple white square.
-"""
-
-import tcod
-import tdl
-
-import numpy as np
-
-# generate a callback for libtcod
-@tcod.ffi.callback('SDL_renderer_t')
-def sdl_hook(surface):
- tcod.lib.SDL_UpperBlit(my_surface, tcod.ffi.NULL, surface, [{'x':0, 'y':0}])
-
-pixels = np.zeros((100, 150, 4), dtype=np.uint8)
-my_surface = tcod.lib.SDL_CreateRGBSurfaceWithFormatFrom(
- tcod.ffi.cast('void*', pixels.ctypes.data),
- pixels.shape[1], pixels.shape[0], 32,
- pixels.strides[0],
- tcod.lib.SDL_PIXELFORMAT_RGBA32,
-)
-
-
-if __name__ == '__main__':
- # hook callback to libtcod
- tcod.sys_register_SDL_renderer(sdl_hook)
-
- con = tdl.init(32, 32, renderer='SDL') # MUST BE SDL RENDERER
-
- pixels[:] = 255
-
- tick = 0
- while(True):
- tick += 1
- for event in tdl.event.get():
- if event.type == 'QUIT':
- raise SystemExit()
- tdl.flush() # will call sdl_hook
diff --git a/dev/stress_test.py b/dev/stress_test.py
deleted file mode 100755
index 87c2f92a..00000000
--- a/dev/stress_test.py
+++ /dev/null
@@ -1,167 +0,0 @@
-#!/usr/bin/env python
-"""
- Stress test designed to test tdl under harsh conditions
-
- Note that one of the slower parts is converting colors over ctypes so you
- can get a bit of speed avoiding the fgcolor and bgcolor parameters.
- Another thing slowing things down are mass calls to ctypes functions.
- Eventually these will be optimized to work better.
-"""
-import sys
-
-import itertools
-import random
-import time
-
-sys.path.insert(0, '../')
-import tdl
-
-class StopWatch:
- "Simple tool used to count time within a block using the with statement"
- MAXSNAPSHOTS = 10
-
- def __init__(self):
- self.enterTime = None
- self.snapshots = []
-
- def __enter__(self):
- self.enterTime = time.clock()
-
- def __exit__(self, *exc):
- self.snapshots.append(time.clock() - self.enterTime)
- if len(self.snapshots) > self.MAXSNAPSHOTS:
- self.snapshots.pop(0)
-
- def getMeanTime(self):
- if not self.snapshots:
- return 0
- return sum(self.snapshots) / len(self.snapshots)
-
-
-class TestApp(tdl.event.App):
-
- def __init__(self, console):
- self.console = console
- self.writer = self.console
- self.console.setMode('scroll')
- self.width, self.height = self.console.getSize()
- self.total = self.width * self.height
- self.cells = list(itertools.product(range(self.width), range(self.height)))
- self.tick = 0
- self.init()
-
- def init(self):
- pass
-
- def ev_MOUSEDOWN(self, event):
- self.suspend()
-
- def update(self, deltaTime):
- self.updateTest(deltaTime)
- self.tick += 1
- #if self.tick % 50 == 0:
- tdl.setTitle('%s: %i FPS' % (self.__class__.__name__, tdl.getFPS()))
- tdl.flush()
-
-class FullDrawCharTest(TestApp):
-
- def updateTest(self, deltaTime):
- # getrandbits is around 5x faster than using randint
- bgcolors = [(random.getrandbits(7), random.getrandbits(7), random.getrandbits(7)) for _ in range(self.total)]
- char = [random.getrandbits(8) for _ in range(self.total)]
- fgcolor = (255, 255, 255)
- for (x,y), bgcolor, char in zip(self.cells, bgcolors, char):
- self.console.draw_char(x, y, char, fgcolor, bgcolor)
-
-class PreCompiledColorTest(TestApp):
- """
- This test was to see if the cast to the ctypes Color object was a big
- impact on performance, turns out there's no hit with this class.
- """
-
- def init(self):
- self.bgcolors = [tdl._Color(random.getrandbits(7),
- random.getrandbits(7),
- random.getrandbits(7))
- for _ in range(256)]
- self.fgcolor = tdl._Color(255, 255, 255)
-
- def updateTest(self, deltaTime):
- # getrandbits is around 5x faster than using randint
- char = [random.getrandbits(8) for _ in range(self.total)]
- for (x,y), char in zip(self.cells, char):
- self.console.draw_char(x, y, char,
- self.fgcolor, random.choice(self.bgcolors))
-
-
-class CharOnlyTest(TestApp):
-
- def updateTest(self, deltaTime):
- self.console.clear((255, 255, 255), (0, 0, 0))
- char = [random.getrandbits(8) for _ in range(self.total)]
- for (x,y), char in zip(self.cells, char):
- self.console.draw_char(x, y, char)
-
-class TypewriterCharOnlyTest(TestApp):
-
- def updateTest(self, deltaTime):
- self.writer.move(0, 0)
- char = [random.getrandbits(8) for _ in range(self.total)]
- for (x,y), char in zip(self.cells, char):
- self.writer.move(x, y)
- self.writer.print_str(chr(char))
-
-class ColorOnlyTest(TestApp):
-
- def updateTest(self, deltaTime):
- # getrandbits is around 5x faster than using randint
- bgcolors = [(random.getrandbits(6), random.getrandbits(6), random.getrandbits(6)) for _ in range(self.total)]
- for (x,y), bgcolor in zip(self.cells, bgcolors):
- self.console.draw_char(x, y, None, None, bgcolor)
-
-class GetCharTest(TestApp):
-
- def init(self):
- for (x,y) in self.cells:
- bgcolor = (random.getrandbits(6), random.getrandbits(6), random.getrandbits(6))
- ch = random.getrandbits(8)
- self.console.get_char(x, y, ch, bgcolor=bgcolor)
-
- def updateTest(self, deltaTime):
- for (x,y) in self.cells:
- self.console.draw_char(x, y, *self.console.get_char(x, y))
-
-class SingleRectTest(TestApp):
-
- def updateTest(self, deltaTime):
- bgcolor = (random.getrandbits(6), random.getrandbits(6), random.getrandbits(6))
- self.console.draw_rect(0, 0, None, None, ' ', (255, 255, 255), bgcolor)
-
-class DrawStrTest(TestApp):
-
- def updateTest(self, deltaTime):
- for y in range(self.height):
- bgcolor = (random.getrandbits(6), random.getrandbits(6), random.getrandbits(6))
- string = [random.getrandbits(8) for x in range(self.width)]
- self.console.draw_str(0, y, string, (255, 255, 255), bgcolor)
-
-class BlitScrollTest(TestApp):
- def updateTest(self, deltaTime):
- self.console.scroll(0, 1)
- for x in range(self.width):
- bgcolor = (random.getrandbits(6), random.getrandbits(6), random.getrandbits(6))
- ch = random.getrandbits(8)
- self.console.draw_char(x, 0, ch, bg=bgcolor)
-
-# match libtcod sample screen
-WIDTH = 46
-HEIGHT = 20
-def main():
- console = tdl.init(46, 20, renderer='OPENGL')
- for Test in [FullDrawCharTest, CharOnlyTest, TypewriterCharOnlyTest, ColorOnlyTest, GetCharTest,
- SingleRectTest, DrawStrTest, BlitScrollTest]:
- Test(console).run()
- console.clear()
-
-if __name__ == '__main__':
- main()
diff --git a/docs/_static/css/custom.css b/docs/_static/css/custom.css
new file mode 100644
index 00000000..e14001c0
--- /dev/null
+++ b/docs/_static/css/custom.css
@@ -0,0 +1,3 @@
+code.literal {
+ white-space: nowrap
+}
diff --git a/docs/_templates/page.html b/docs/_templates/page.html
new file mode 100644
index 00000000..213c761f
--- /dev/null
+++ b/docs/_templates/page.html
@@ -0,0 +1,21 @@
+{% extends "!page.html" %}
+{% block content %}
+ {{ super() }}
+
+
+{% endblock %}
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 565b0521..2f021a4b 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -1 +1,6 @@
-.. include:: ../CHANGELOG.rst
+===========
+ Changelog
+===========
+
+You can find the most recent changelog
+`here `_.
diff --git a/docs/conf.py b/docs/conf.py
index 0db27d4b..70be25bf 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -1,6 +1,5 @@
-# -*- coding: utf-8 -*-
-#
-# tdl documentation build configuration file, created by
+"""Sphinx config file.""" # noqa: INP001
+# python-tcod documentation build configuration file, created by
# sphinx-quickstart on Fri Nov 25 12:49:46 2016.
#
# This file is execfile()d with the current directory set to its
@@ -16,9 +15,19 @@
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
+from __future__ import annotations
+
import os
+import re
+import subprocess
import sys
-sys.path.insert(0, os.path.abspath('..'))
+from pathlib import Path
+
+sys.path.insert(0, str(Path("..").resolve(strict=True)))
+
+THIS_DIR = Path(__file__).parent
+
+# ruff: noqa: ERA001
# -- General configuration ------------------------------------------------
@@ -30,54 +39,61 @@
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
- 'sphinx.ext.autodoc',
- 'sphinx.ext.intersphinx',
- 'sphinx.ext.coverage',
- 'sphinx.ext.ifconfig',
- 'sphinx.ext.viewcode',
- 'sphinx.ext.napoleon',
+ "sphinx.ext.autodoc",
+ "sphinx.ext.intersphinx",
+ "sphinx.ext.coverage",
+ "sphinx.ext.ifconfig",
+ "sphinx.ext.viewcode",
+ "sphinx.ext.napoleon",
]
# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
+templates_path = ["_templates"]
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
-source_suffix = '.rst'
+source_suffix = ".rst"
# The encoding of source files.
#
# source_encoding = 'utf-8-sig'
# The master toctree document.
-master_doc = 'index'
+master_doc = "index"
# General information about the project.
-project = u'python-tcod/tdl'
-copyright = u'2009-2018, Kyle Stewart'
-author = u'Kyle Stewart'
+project = "python-tcod"
+copyright = "2009-2026, Kyle Benesch" # noqa: A001
+author = "Kyle Benesch"
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The full version, including alpha/beta/rc tags.
-from subprocess import check_output
-release = check_output(['git', 'describe', '--abbrev=0'],
- universal_newlines=True).strip()
-print('release version: %r' % release)
+release = subprocess.run(
+ ["git", "describe", "--abbrev=0"], # noqa: S607
+ stdout=subprocess.PIPE,
+ text=True,
+ check=True,
+).stdout.strip()
+assert release
+print(f"release version: {release!r}")
+
# The short X.Y version.
-import re
-version = re.match(r'([0-9]+\.[0-9]+).*?', release).group()
+match_version = re.match(r"([0-9]+\.[0-9]+).*?", release)
+assert match_version
+version = match_version.group()
+print(f"short version: {version!r}")
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
-language = None
+language = "en"
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
@@ -91,7 +107,7 @@
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
-exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '/epilog.rst']
+exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "epilog.rst", "prolog.rst"]
# The reST default role (used for this markup: `text`) to use for all
# documents.
@@ -113,7 +129,7 @@
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
+pygments_style = "sphinx"
# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
@@ -125,7 +141,13 @@
todo_include_todos = False
-autodoc_member_order = 'groupwise'
+autodoc_member_order = "groupwise"
+
+# Prevent type aliases from expanding
+autodoc_type_aliases = {
+ "ArrayLike": "numpy.typing.ArrayLike",
+ "NDArray": "numpy.typing.NDArray",
+}
# -- Options for HTML output ----------------------------------------------
@@ -133,7 +155,7 @@
# a list of builtin themes.
#
-html_theme = "sphinx_rtd_theme"
+html_theme = "furo"
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
@@ -145,9 +167,9 @@
# html_theme_path = []
# The name for this set of Sphinx documents.
-# " v documentation" by default.
+# " documentation" by default.
#
-# html_title = u'tdl v1'
+html_title = f"{project} {release} documentation"
# A shorter title for the navigation bar. Default is the same as html_title.
#
@@ -167,7 +189,9 @@
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
+html_static_path = ["_static"]
+
+html_css_files = ["css/custom.css"]
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
@@ -188,7 +212,7 @@
# Custom sidebar templates, maps document names to template names.
#
-#html_sidebars = {'**': ['globaltoc.html', 'searchbox.html']}
+# html_sidebars = {'**': ['globaltoc.html', 'searchbox.html']}
# Additional templates that should be rendered to pages, maps page names to
# template names.
@@ -247,34 +271,26 @@
# html_search_scorer = 'scorer.js'
# Output file base name for HTML help builder.
-htmlhelp_basename = 'tdldoc'
+htmlhelp_basename = "tcoddoc"
# -- Options for LaTeX output ---------------------------------------------
-latex_elements = {
- # The paper size ('letterpaper' or 'a4paper').
- #
- # 'papersize': 'letterpaper',
-
- # The font size ('10pt', '11pt' or '12pt').
- #
- # 'pointsize': '10pt',
-
- # Additional stuff for the LaTeX preamble.
- #
- # 'preamble': '',
-
- # Latex figure (float) alignment
- #
- # 'figure_align': 'htbp',
+latex_elements: dict[str, str] = {
+ # The paper size ('letterpaper' or 'a4paper').
+ # 'papersize': 'letterpaper',
+ # The font size ('10pt', '11pt' or '12pt').
+ # 'pointsize': '10pt',
+ # Additional stuff for the LaTeX preamble.
+ # 'preamble': '',
+ # Latex figure (float) alignment
+ # 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
- (master_doc, 'tdl.tex', u'tdl Documentation',
- u'Kyle Stewart', 'manual'),
+ (master_doc, "python-tcod.tex", "python-tcod Documentation", "Kyle Benesch", "manual"),
]
# The name of an image file (relative to this directory) to place at the top of
@@ -314,10 +330,7 @@
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
-man_pages = [
- (master_doc, 'tdl', u'tdl Documentation',
- [author], 1)
-]
+man_pages = [(master_doc, "python-tcod", "python-tcod Documentation", [author], 1)]
# If true, show URL addresses after external links.
#
@@ -330,9 +343,15 @@
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
- (master_doc, 'tdl', u'tdl Documentation',
- author, 'tdl', 'One line description of project.',
- 'Miscellaneous'),
+ (
+ master_doc,
+ "python-tcod",
+ "python-tcod Documentation",
+ author,
+ "python-tcod",
+ "libtcod for Python.",
+ "Miscellaneous",
+ ),
]
# Documents to append as an appendix to all manuals.
@@ -363,12 +382,14 @@
napoleon_use_param = True
napoleon_use_rtype = True
-rst_epilog = ".. include:: /epilog.rst"
+rst_prolog = (THIS_DIR / "prolog.rst").read_text(encoding="utf-8") # Added to the beginning of every source file.
+rst_epilog = (THIS_DIR / "epilog.rst").read_text(encoding="utf-8") # Added to the end of every source file.
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {
- 'https://docs.python.org/3/': None,
- 'https://docs.scipy.org/doc/numpy/': None,
+ "python": ("https://docs.python.org/3/", None),
+ "numpy": ("https://numpy.org/doc/stable/", None),
+ "tcod-ecs": ("https://python-tcod-ecs.readthedocs.io/en/latest/", None),
}
-os.environ['READTHEDOCS'] = 'True'
+os.environ["READTHEDOCS"] = "True"
diff --git a/docs/epilog.rst b/docs/epilog.rst
index e69de29b..c3ee806c 100644
--- a/docs/epilog.rst
+++ b/docs/epilog.rst
@@ -0,0 +1,3 @@
+
+.. _tcod-ecs: https://github.com/HexDecimal/python-tcod-ecs
+.. _Flecs: https://github.com/SanderMertens/flecs
diff --git a/docs/faq.rst b/docs/faq.rst
new file mode 100644
index 00000000..ed7987fe
--- /dev/null
+++ b/docs/faq.rst
@@ -0,0 +1,52 @@
+Frequently Asked Questions
+==========================
+
+How do you set a frames-per-second while using contexts?
+--------------------------------------------------------
+
+You'll need to use an external tool to manage the framerate.
+This can either be your own custom tool or you can copy the Clock class from the
+`framerate.py `_
+example.
+
+
+I get ``No module named 'tcod'`` when I try to ``import tcod`` in PyCharm.
+--------------------------------------------------------------------------
+
+`PyCharm`_ will automatically setup a `Python virtual environment `_ for new or added projects.
+By default this virtual environment is isolated and will ignore global Python packages installed from the standard terminal. **In this case you MUST install tcod inside of your per-project virtual environment.**
+
+The recommended way to work with PyCharm is to add a ``requirements.txt`` file to the root of your PyCharm project with a `requirement specifier `_ for `tcod`.
+This file should have the following:
+
+.. code-block:: python
+
+ # requirements.txt
+ # https://pip.pypa.io/en/stable/cli/pip_install/#requirements-file-format
+ tcod
+
+Once this file is saved to your projects root directory then PyCharm will detect it and ask if you want these requirements installed. Say yes and `tcod` will be installed to the `virtual environment`. Be sure to add more specifiers for any modules you're using other than `tcod`, such as `numpy`.
+
+Alternatively you can open the `Terminal` tab in PyCharm and run ``pip install tcod`` there. This will install `tcod` to the currently open project.
+
+
+How do I add custom tiles?
+--------------------------
+
+Libtcod uses Unicode to identify tiles.
+To prevent conflicts with real glyphs you should decide on codepoints from a `Private Use Area `_ before continuing.
+If you're unsure, then use the codepoints from ``0x100000`` to ``0x10FFFD`` for your custom tiles.
+
+Normally you load a font with :func:`tcod.tileset.load_tilesheet` which will return a :any:`Tileset` that gets passed to :func:`tcod.context.new`'s `tileset` parameter.
+:func:`tcod.tileset.load_tilesheet` assigns the codepoints from `charmap` to the tilesheet in row-major order.
+
+There are two ways to extend a tileset like the above:
+
+- Increase the tilesheet size vertically and update the `rows` parameter in :func:`tcod.tileset.load_tilesheet` to match the new image size, then modify the `charmap` parameter to map the new tiles to codepoints.
+ If you edited a CP437 tileset this way then you'd add your new codepoints to the end of :any:`tcod.tileset.CHARMAP_CP437` before using the result as the `charmap` parameter.
+ You can also use :any:`Tileset.remap` if you want to reassign tiles based on their position rather than editing `charmap`.
+- Or do not modify the original tilesheet.
+ Load the tileset normally, then add new tiles with :any:`Tileset.set_tile` with manually loaded images.
+
+
+.. _PyCharm: https://www.jetbrains.com/pycharm/
diff --git a/docs/glossary.rst b/docs/glossary.rst
index 8b6bbabf..89ece9b3 100644
--- a/docs/glossary.rst
+++ b/docs/glossary.rst
@@ -8,36 +8,58 @@ Glossary
The default values implied by any Console print or put functions which
don't explicitly ask for them as parameters.
+ These have been deprecated since version `8.5`.
+
+ tcod
+ `tcod` on its own is shorthand for both :term:`libtcod` and all of its bindings including :term:`python-tcod`.
+
+ It originated as an acronym for the game the library was first created for:
+ `The Chronicles Of Doryen `_
+
+ libtcod
+ This is the original C library which contains the implementations and algorithms used by C programs.
+
+ :term:`python-tcod` includes a statically linked version of this library.
+
libtcod-cffi
This is the `cffi` implementation of libtcodpy, the original was
made using `ctypes` which was more difficult to maintain.
- `libtcod-cffi` is now part of :term:`python-tcod`.
+ `libtcod-cffi` has since been part of :term:`python-tcod` providing
+ all of the :term:`libtcodpy` API until the newer features could be
+ implemented.
python-tcod
- `python-tcod` is a superset of the :term:`libtcodpy` API. The major
- additions include class functionality in returned objects, no manual
+ `python-tcod` is the main Python port of :term:`libtcod`.
+
+ Originally a superset of the :term:`libtcodpy` API. The major
+ additions included class functionality in returned objects, no manual
memory management, pickle-able objects, and `numpy` array attributes
in most objects.
- The `numpy` attributes in particular can be used to dramatically speed
- up the performance of your program compared to using :term:`libtcodpy`.
+ The `numpy` functions in particular can be used to dramatically speed
+ up the performance of a program compared to using :term:`libtcodpy`.
python-tdl
`tdl` is a high-level wrapper over :term:`libtcodpy` although it now
uses :term:`python-tcod`, it doesn't do anything that you couldn't do
yourself with just :term:`libtcodpy` and Python.
+ It included a lot of core functions written in Python that most
+ definitely shouldn't have been. `tdl` was very to use, but the cost
+ was severe performance issues throughout the entire module.
+ This left it impractical for any real use as a roguelike library.
+
Currently no new features are planned for `tdl`, instead new features
- are added to `libtcod` itself and then ported to :term:`python-tcod`.
+ are added to :term:`libtcod` itself and then ported to :term:`python-tcod`.
:term:`python-tdl` and :term:`libtcodpy` are included in installations
of `python-tcod`.
libtcodpy
- `libtcodpy` is more or less a direct port of `libtcod`'s C API to
- Python. This caused a handful of issues including instances needing
- to be freed manually or a memory leak will occur and some functions
+ :term:`libtcodpy` is more or less a direct port of :term:`libtcod`'s C API to Python.
+ This caused a handful of issues including instances needing to be
+ freed manually or else a memory leak would occur, and many functions
performing badly in Python due to the need to call them frequently.
These issues are fixed in :term:`python-tcod` which implements the full
@@ -46,3 +68,8 @@ Glossary
So if you come across a project using the original `libtcodpy` you can
delete the `libtcodpy/` folder and then :term:`python-tcod` will load
instead.
+
+ color control
+ color controls
+ Libtcod's old system which assigns colors to specific codepoints.
+ See :any:`libtcodpy.COLCTRL_STOP`, :any:`libtcodpy.COLCTRL_FORE_RGB`, and :any:`libtcodpy.COLCTRL_BACK_RGB` for examples.
diff --git a/docs/index.rst b/docs/index.rst
index a0a14574..afc353a7 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,10 +1,10 @@
-.. tdl documentation master file, created by
+.. tcod documentation master file, created by
sphinx-quickstart on Fri Nov 25 12:49:46 2016.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
-Welcome to the python-tcod/tdl documentation!
-==============================================
+Welcome to the python-tcod documentation!
+=========================================
.. module:: tcod
@@ -13,25 +13,43 @@ Welcome to the python-tcod/tdl documentation!
Contents:
.. toctree::
- :maxdepth: 2
- :caption: Readme
+ :maxdepth: 2
- readme
- changelog
- glossary
+ installation
+ glossary
+ changelog
+ faq
.. toctree::
- :maxdepth: 2
- :caption: python-tcod API
+ :maxdepth: 2
+ :caption: How To
- tcod
- libtcodpy
+ tutorial/index
.. toctree::
- :maxdepth: 2
- :caption: Legacy tdl API
-
- tdl
+ :maxdepth: 2
+ :caption: python-tcod API
+
+ tcod/getting-started
+ tcod/charmap-reference
+ tcod/bsp
+ tcod/console
+ tcod/context
+ tcod/event
+ tcod/image
+ tcod/los
+ tcod/map
+ tcod/noise
+ tcod/path
+ tcod/random
+ tcod/render
+ tcod/tileset
+ libtcodpy
+ sdl/audio
+ sdl/joystick
+ sdl/render
+ sdl/mouse
+ sdl/video
Indices and tables
==================
@@ -39,4 +57,3 @@ Indices and tables
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
-
diff --git a/docs/installation.rst b/docs/installation.rst
new file mode 100644
index 00000000..4b8e04ce
--- /dev/null
+++ b/docs/installation.rst
@@ -0,0 +1,160 @@
+.. _installation:
+
+Installation
+============
+Once python-tcod is installed, you'll be able to import the `tcod` module.
+
+The latest version of Python 3 is recommended for a normal install.
+Python 2 can not be used with the latest versions of python-tcod.
+These instructions include installing Python if you don't have it yet.
+
+Windows
+-------
+`First install the latest recent version of Python 3.
+`_
+
+.. important::
+ Make sure ``Add Python to environment variables`` is checked in the installer.
+ Otherwise Python will not be added to the Windows ``PATH``.
+ If you forgot to do this then you can reopen the installer and *modify* your installation.
+
+If you don't already have it, then install the latest
+`Microsoft Visual C++ Redistributable
+`_.
+**vc_redist.x86.exe** for a 32-bit install of Python, or **vc_redist.x64.exe**
+for a 64-bit install. You'll need to keep this in mind when distributing any
+libtcod program to end-users.
+
+You should verify your Python install with your terminal.
+The terminal you use can be the Windows Command Prompt, PowerShell, GitBash, or similar.
+It can not be the Python interpreter (indicated with a ``>>>`` prompt.)
+Run the following commands (excluding the ``>``) to verify your Python installation::
+
+ >python -V
+ Python 3.10.0
+
+ >pip -V
+ pip 21.2.4 from ...\Python310\lib\site-packages\pip (python 3.10)
+
+The above outputs would be the result of Python 3.10 being installed.
+**Make sure the mentioned Python versions you get are not different than the latest version you just installed.**
+
+To install python-tcod run the following from a Windows command line::
+
+ >pip install tcod
+
+If Python was installed for all users then you may need to add the ``--user``
+flag to pip.
+
+You can then verify that ``tcod`` is importable from the Python interpreter::
+
+ >python
+
+ >>> import tcod.context
+
+If ``import tcod.context`` doesn't throw an ``ImportError`` then ``tcod`` has been installed correctly to your system libraries.
+
+Some IDE's such as PyCharm will create a virtual environment which will ignore your system libraries and require tcod to be installed again in that new environment.
+
+MacOS
+-----
+The latest version of python-tcod only supports MacOS 10.9 (Mavericks) or
+later.
+
+`First install a recent version of Python 3.
+`_
+
+Then to install using pip in a user environment, use the following command::
+
+ python3 -m pip install --user tcod
+
+Linux (Debian-based)
+--------------------
+On Linux python-tcod will need to be built from source.
+You can run this command to download python-tcod's dependencies with apt::
+
+ sudo apt install build-essential python3-dev python3-pip python3-numpy libsdl2-dev libffi-dev
+
+If your GCC version is less than 6.1, or your SDL version is less than 2.0.5,
+then you will need to perform a distribution upgrade before continuing.
+
+Once dependencies are resolved you can build and install python-tcod using pip
+in a user environment::
+
+ python3 -m pip install --user tcod
+
+PyCharm
+-------
+PyCharm will often run your project in a virtual environment, hiding any modules
+you installed system-wide. You must install python-tcod inside of the virtual
+environment in order for it to be importable in your projects scripts.
+
+By default the bottom bar of PyCharm will have a tab labeled `terminal`.
+Open this tab and you should see a prompt with ``(venv)`` on it.
+This means your commands will run in the virtual environment of your project.
+
+From this terminal you can install ``tcod`` to the virtual environment with the following command::
+
+ pip install tcod
+
+You can now use ``import tcod``.
+
+If you are working with multiple people or computers or are using a Git repository then it is recommend to pin
+the tcod version in a `requirements.txt file `_.
+PyCharm will automatically update the virtual environment from these files.
+
+Upgrading python-tcod
+---------------------
+python-tcod is updated often, you can re-run pip with the ``--upgrade`` flag
+to ensure you have the latest version, for example::
+
+ python3 -m pip install --upgrade tcod
+
+Upgrading from libtcodpy to python-tcod
+---------------------------------------
+`libtcodpy` is no longer maintained and using it can make it difficult to
+collaborate with developers across multiple operating systems, or to distribute
+to those platforms.
+New API features are only available on `python-tcod`.
+
+You can recognize a libtcodpy program because it includes this file structure::
+
+ libtcodpy/ (or libtcodpy.py)
+ libtcod.dll (or libtcod-mingw.dll)
+ SDL2.dll (or SDL.dll)
+
+First make sure your libtcodpy project works in Python 3. libtcodpy
+already supports both 2 and 3 so you don't need to worry about updating it,
+but you will need to worry about bit-size. If you're using a
+32-bit version of Python 2 then you'll need to upgrade to a 32-bit version of
+Python 3 until libtcodpy can be completely removed.
+
+For Python 3 you'll want the latest version of `tcod`, for Python 2 you'll need
+to install ``tcod==6.0.7`` instead, see the Python 2.7 instructions below.
+
+Once you've installed python-tcod you can safely delete the ``libtcodpy/``
+folder, the ``libtcodpy.py`` script, and all the DLL files of a libtcodpy
+program, python-tcod will seamlessly and immediately take the place of
+libtcodpy's API.
+
+From then on anyone can follow the instructions in this guide to install
+python-tcod and your project will work for them regardless of their platform.
+
+Distributing
+------------
+Once your project is finished, it can be distributed using
+`PyInstaller `_.
+
+Python 2.7
+----------
+While it's not recommended, you can still install `python-tcod` on
+`Python 2.7`.
+
+`Keep in mind that Python 2's end-of-life has already passed. You should not be
+starting any new projects in Python 2!
+`_
+
+Follow the instructions for your platform normally. When it comes to
+install with pip, tell it to get python-tcod version 6::
+
+ python2 -m pip install tcod==6.0.7
diff --git a/docs/libtcodpy.rst b/docs/libtcodpy.rst
index 71ee904d..9dbeb3f5 100644
--- a/docs/libtcodpy.rst
+++ b/docs/libtcodpy.rst
@@ -1,135 +1,173 @@
-libtcodpy
-=========
+Old API Functions ``libtcodpy``
+===============================
+
+This is all the functions included since the start of the Python port.
+This collection is often called :term:`libtcodpy`, the name of the original
+Python port. These functions are reproduced by python-tcod in their entirely.
+
+Use ``from tcod import libtcodpy`` to access this module.
+
+**A large majority of these functions are deprecated and will be removed in
+the future.
+In general this entire section should be avoided whenever possible.**
+See :ref:`getting-started` for how to make a new python-tcod project with its
+modern API.
bsp
---
-.. autofunction:: tcod.bsp_new_with_size
-.. autofunction:: tcod.bsp_split_once
-.. autofunction:: tcod.bsp_split_recursive
-.. autofunction:: tcod.bsp_resize
-.. autofunction:: tcod.bsp_left
-.. autofunction:: tcod.bsp_right
-.. autofunction:: tcod.bsp_father
-.. autofunction:: tcod.bsp_is_leaf
-.. autofunction:: tcod.bsp_contains
-.. autofunction:: tcod.bsp_find_node
-.. autofunction:: tcod.bsp_traverse_pre_order
-.. autofunction:: tcod.bsp_traverse_in_order
-.. autofunction:: tcod.bsp_traverse_post_order
-.. autofunction:: tcod.bsp_traverse_level_order
-.. autofunction:: tcod.bsp_traverse_inverted_level_order
-.. autofunction:: tcod.bsp_remove_sons
-.. autofunction:: tcod.bsp_delete
+.. autofunction:: libtcodpy.bsp_new_with_size
+.. autofunction:: libtcodpy.bsp_split_once
+.. autofunction:: libtcodpy.bsp_split_recursive
+.. autofunction:: libtcodpy.bsp_resize
+.. autofunction:: libtcodpy.bsp_left
+.. autofunction:: libtcodpy.bsp_right
+.. autofunction:: libtcodpy.bsp_father
+.. autofunction:: libtcodpy.bsp_is_leaf
+.. autofunction:: libtcodpy.bsp_contains
+.. autofunction:: libtcodpy.bsp_find_node
+.. autofunction:: libtcodpy.bsp_traverse_pre_order
+.. autofunction:: libtcodpy.bsp_traverse_in_order
+.. autofunction:: libtcodpy.bsp_traverse_post_order
+.. autofunction:: libtcodpy.bsp_traverse_level_order
+.. autofunction:: libtcodpy.bsp_traverse_inverted_level_order
+.. autofunction:: libtcodpy.bsp_remove_sons
+.. autofunction:: libtcodpy.bsp_delete
color
-----
-.. autoclass:: tcod.Color
+.. autoclass:: libtcodpy.Color
:member-order: bysource
:members:
-.. autofunction:: tcod.color_lerp
-.. autofunction:: tcod.color_set_hsv
-.. autofunction:: tcod.color_get_hsv
-.. autofunction:: tcod.color_scale_HSV
-.. autofunction:: tcod.color_gen_map
+.. autofunction:: libtcodpy.color_lerp
+.. autofunction:: libtcodpy.color_set_hsv
+.. autofunction:: libtcodpy.color_get_hsv
+.. autofunction:: libtcodpy.color_scale_HSV
+.. autofunction:: libtcodpy.color_gen_map
color controls
~~~~~~~~~~~~~~
-Configurable color control constants which can be set up with
-:any:`tcod.console_set_color_control`.
+Libtcod color control constants.
+These can be inserted into Python strings with the ``%c`` format specifier as shown below.
+
+.. data:: libtcodpy.COLCTRL_1
+
+ These can be configured with :any:`libtcodpy.console_set_color_control`.
+ However, it is recommended to use :any:`libtcodpy.COLCTRL_FORE_RGB` and :any:`libtcodpy.COLCTRL_BACK_RGB` instead.
+
+.. data:: libtcodpy.COLCTRL_2
+.. data:: libtcodpy.COLCTRL_3
+.. data:: libtcodpy.COLCTRL_4
+.. data:: libtcodpy.COLCTRL_5
+
+.. data:: libtcodpy.COLCTRL_STOP
+
+ When this control character is inserted into a string the foreground and background colors will be reset for the
+ remaining characters of the string.
+
+ >>> import tcod
+ >>> reset_color = f"{libtcodpy.COLCTRL_STOP:c}"
+
+.. data:: libtcodpy.COLCTRL_FORE_RGB
+
+ Sets the foreground color to the next 3 Unicode characters for the remaining characters.
+
+ >>> fg = (255, 255, 255)
+ >>> change_fg = f"{libtcodpy.COLCTRL_FORE_RGB:c}{fg[0]:c}{fg[1]:c}{fg[2]:c}"
+ >>> string = f"Old color {change_fg}new color{libtcodpy.COLCTRL_STOP:c} old color."
+
+.. data:: libtcodpy.COLCTRL_BACK_RGB
-.. data:: tcod.COLCTRL_1
-.. data:: tcod.COLCTRL_2
-.. data:: tcod.COLCTRL_3
-.. data:: tcod.COLCTRL_4
-.. data:: tcod.COLCTRL_5
+ Sets the background color to the next 3 Unicode characters for the remaining characters.
-.. data:: tcod.COLCTRL_STOP
-.. data:: tcod.COLCTRL_FORE_RGB
-.. data:: tcod.COLCTRL_BACK_RGB
+ >>> from typing import Tuple
+ >>> def change_colors(fg: Tuple[int, int, int], bg: Tuple[int, int, int]) -> str:
+ ... """Return the control codes to change the foreground and background colors."""
+ ... return "%c%c%c%c%c%c%c%c" % (libtcodpy.COLCTRL_FORE_RGB, *fg, libtcodpy.COLCTRL_BACK_RGB, *bg)
+ >>> string = f"Old {change_colors(fg=(255, 255, 255), bg=(0, 0, 255))}new"
console
-------
-.. autofunction:: tcod.console_set_custom_font
-.. autofunction:: tcod.console_init_root
-.. autofunction:: tcod.console_flush
-
-.. autofunction:: tcod.console_blit
-.. autofunction:: tcod.console_check_for_keypress
-.. autofunction:: tcod.console_clear
-.. autofunction:: tcod.console_credits
-.. autofunction:: tcod.console_credits_render
-.. autofunction:: tcod.console_credits_reset
-.. autofunction:: tcod.console_delete
-.. autofunction:: tcod.console_fill_background
-.. autofunction:: tcod.console_fill_char
-.. autofunction:: tcod.console_fill_foreground
-.. autofunction:: tcod.console_from_file
-.. autofunction:: tcod.console_from_xp
-.. autofunction:: tcod.console_get_alignment
-.. autofunction:: tcod.console_get_background_flag
-.. autofunction:: tcod.console_get_char
-.. autofunction:: tcod.console_get_char_background
-.. autofunction:: tcod.console_get_char_foreground
-.. autofunction:: tcod.console_get_default_background
-.. autofunction:: tcod.console_get_default_foreground
-.. autofunction:: tcod.console_get_fade
-.. autofunction:: tcod.console_get_fading_color
-.. autofunction:: tcod.console_get_height
-.. autofunction:: tcod.console_get_height_rect
-.. autofunction:: tcod.console_get_width
-.. autofunction:: tcod.console_hline
-.. autofunction:: tcod.console_is_fullscreen
-.. autofunction:: tcod.console_is_key_pressed
-.. autofunction:: tcod.console_is_window_closed
-.. autofunction:: tcod.console_load_apf
-.. autofunction:: tcod.console_load_asc
-.. autofunction:: tcod.console_load_xp
-.. autofunction:: tcod.console_list_load_xp
-.. autofunction:: tcod.console_list_save_xp
-.. autofunction:: tcod.console_map_ascii_code_to_font
-.. autofunction:: tcod.console_map_ascii_codes_to_font
-.. autofunction:: tcod.console_map_string_to_font
-.. autofunction:: tcod.console_new
-.. autofunction:: tcod.console_print
-.. autofunction:: tcod.console_print_ex
-.. autofunction:: tcod.console_print_frame
-.. autofunction:: tcod.console_print_rect
-.. autofunction:: tcod.console_print_rect_ex
-.. autofunction:: tcod.console_put_char
-.. autofunction:: tcod.console_put_char_ex
-.. autofunction:: tcod.console_rect
-.. autofunction:: tcod.console_save_apf
-.. autofunction:: tcod.console_save_asc
-.. autofunction:: tcod.console_save_xp
-.. autofunction:: tcod.console_set_alignment
-.. autofunction:: tcod.console_set_background_flag
-.. autofunction:: tcod.console_set_char
-.. autofunction:: tcod.console_set_char_background
-.. autofunction:: tcod.console_set_char_foreground
-.. autofunction:: tcod.console_set_color_control
-.. autofunction:: tcod.console_set_default_background
-.. autofunction:: tcod.console_set_default_foreground
-.. autofunction:: tcod.console_set_fade
-.. autofunction:: tcod.console_set_fullscreen
-.. autofunction:: tcod.console_set_key_color
-.. autofunction:: tcod.console_set_window_title
-.. autofunction:: tcod.console_vline
-.. autofunction:: tcod.console_wait_for_keypress
-
-.. autoclass: tcod.ConsoleBuffer
+.. autofunction:: libtcodpy.console_set_custom_font
+.. autofunction:: libtcodpy.console_init_root
+.. autofunction:: libtcodpy.console_flush
+
+.. autofunction:: libtcodpy.console_blit
+.. autofunction:: libtcodpy.console_check_for_keypress
+.. autofunction:: libtcodpy.console_clear
+.. autofunction:: libtcodpy.console_credits
+.. autofunction:: libtcodpy.console_credits_render
+.. autofunction:: libtcodpy.console_credits_reset
+.. autofunction:: libtcodpy.console_delete
+.. autofunction:: libtcodpy.console_fill_background
+.. autofunction:: libtcodpy.console_fill_char
+.. autofunction:: libtcodpy.console_fill_foreground
+.. autofunction:: libtcodpy.console_from_file
+.. autofunction:: libtcodpy.console_from_xp
+.. autofunction:: libtcodpy.console_get_alignment
+.. autofunction:: libtcodpy.console_get_background_flag
+.. autofunction:: libtcodpy.console_get_char
+.. autofunction:: libtcodpy.console_get_char_background
+.. autofunction:: libtcodpy.console_get_char_foreground
+.. autofunction:: libtcodpy.console_get_default_background
+.. autofunction:: libtcodpy.console_get_default_foreground
+.. autofunction:: libtcodpy.console_get_fade
+.. autofunction:: libtcodpy.console_get_fading_color
+.. autofunction:: libtcodpy.console_get_height
+.. autofunction:: libtcodpy.console_get_height_rect
+.. autofunction:: libtcodpy.console_get_width
+.. autofunction:: libtcodpy.console_hline
+.. autofunction:: libtcodpy.console_is_fullscreen
+.. autofunction:: libtcodpy.console_is_key_pressed
+.. autofunction:: libtcodpy.console_is_window_closed
+.. autofunction:: libtcodpy.console_load_apf
+.. autofunction:: libtcodpy.console_load_asc
+.. autofunction:: libtcodpy.console_load_xp
+.. autofunction:: libtcodpy.console_list_load_xp
+.. autofunction:: libtcodpy.console_list_save_xp
+.. autofunction:: libtcodpy.console_map_ascii_code_to_font
+.. autofunction:: libtcodpy.console_map_ascii_codes_to_font
+.. autofunction:: libtcodpy.console_map_string_to_font
+.. autofunction:: libtcodpy.console_new
+.. autofunction:: libtcodpy.console_print
+.. autofunction:: libtcodpy.console_print_ex
+.. autofunction:: libtcodpy.console_print_frame
+.. autofunction:: libtcodpy.console_print_rect
+.. autofunction:: libtcodpy.console_print_rect_ex
+.. autofunction:: libtcodpy.console_put_char
+.. autofunction:: libtcodpy.console_put_char_ex
+.. autofunction:: libtcodpy.console_rect
+.. autofunction:: libtcodpy.console_save_apf
+.. autofunction:: libtcodpy.console_save_asc
+.. autofunction:: libtcodpy.console_save_xp
+.. autofunction:: libtcodpy.console_set_alignment
+.. autofunction:: libtcodpy.console_set_background_flag
+.. autofunction:: libtcodpy.console_set_char
+.. autofunction:: libtcodpy.console_set_char_background
+.. autofunction:: libtcodpy.console_set_char_foreground
+.. autofunction:: libtcodpy.console_set_color_control
+.. autofunction:: libtcodpy.console_set_default_background
+.. autofunction:: libtcodpy.console_set_default_foreground
+.. autofunction:: libtcodpy.console_set_fade
+.. autofunction:: libtcodpy.console_set_fullscreen
+.. autofunction:: libtcodpy.console_set_key_color
+.. autofunction:: libtcodpy.console_set_window_title
+.. autofunction:: libtcodpy.console_vline
+.. autofunction:: libtcodpy.console_wait_for_keypress
+
+.. autoclass: libtcodpy.ConsoleBuffer
:members:
Event
-----
-.. autoclass:: tcod.Key()
+.. autoclass:: libtcodpy.Key()
:members:
-.. autoclass:: tcod.Mouse()
+.. autoclass:: libtcodpy.Mouse()
:members:
.. _event types:
@@ -137,236 +175,236 @@ Event
Event Types
~~~~~~~~~~~
-.. data:: tcod.EVENT_NONE
-.. data:: tcod.EVENT_KEY_PRESS
-.. data:: tcod.EVENT_KEY_RELEASE
-.. data:: tcod.EVENT_KEY
+.. data:: libtcodpy.EVENT_NONE
+.. data:: libtcodpy.EVENT_KEY_PRESS
+.. data:: libtcodpy.EVENT_KEY_RELEASE
+.. data:: libtcodpy.EVENT_KEY
- Same as ``tcod.EVENT_KEY_PRESS | tcod.EVENT_KEY_RELEASE``
+ Same as ``libtcodpy.EVENT_KEY_PRESS | libtcodpy.EVENT_KEY_RELEASE``
-.. data:: tcod.EVENT_MOUSE_MOVE
-.. data:: tcod.EVENT_MOUSE_PRESS
-.. data:: tcod.EVENT_MOUSE_RELEASE
-.. data:: tcod.EVENT_MOUSE
+.. data:: libtcodpy.EVENT_MOUSE_MOVE
+.. data:: libtcodpy.EVENT_MOUSE_PRESS
+.. data:: libtcodpy.EVENT_MOUSE_RELEASE
+.. data:: libtcodpy.EVENT_MOUSE
- Same as ``tcod.EVENT_MOUSE_MOVE | tcod.EVENT_MOUSE_PRESS | tcod.EVENT_MOUSE_RELEASE``
+ Same as ``libtcodpy.EVENT_MOUSE_MOVE | libtcodpy.EVENT_MOUSE_PRESS | libtcodpy.EVENT_MOUSE_RELEASE``
-.. data:: tcod.EVENT_FINGER_MOVE
-.. data:: tcod.EVENT_FINGER_PRESS
-.. data:: tcod.EVENT_FINGER_RELEASE
-.. data:: tcod.EVENT_FINGER
+.. data:: libtcodpy.EVENT_FINGER_MOVE
+.. data:: libtcodpy.EVENT_FINGER_PRESS
+.. data:: libtcodpy.EVENT_FINGER_RELEASE
+.. data:: libtcodpy.EVENT_FINGER
- Same as ``tcod.EVENT_FINGER_MOVE | tcod.EVENT_FINGER_PRESS | tcod.EVENT_FINGER_RELEASE``
+ Same as ``libtcodpy.EVENT_FINGER_MOVE | libtcodpy.EVENT_FINGER_PRESS | libtcodpy.EVENT_FINGER_RELEASE``
-.. data:: tcod.EVENT_ANY
+.. data:: libtcodpy.EVENT_ANY
- Same as ``tcod.EVENT_KEY | tcod.EVENT_MOUSE | tcod.EVENT_FINGER``
+ Same as ``libtcodpy.EVENT_KEY | libtcodpy.EVENT_MOUSE | libtcodpy.EVENT_FINGER``
sys
---
-.. autofunction:: tcod.sys_set_fps
-.. autofunction:: tcod.sys_get_fps
-.. autofunction:: tcod.sys_get_last_frame_length
-.. autofunction:: tcod.sys_sleep_milli
-.. autofunction:: tcod.sys_elapsed_milli
-.. autofunction:: tcod.sys_elapsed_seconds
-.. autofunction:: tcod.sys_set_renderer
-.. autofunction:: tcod.sys_get_renderer
-.. autofunction:: tcod.sys_save_screenshot
-.. autofunction:: tcod.sys_force_fullscreen_resolution
-.. autofunction:: tcod.sys_get_current_resolution
-.. autofunction:: tcod.sys_get_char_size
-.. autofunction:: tcod.sys_update_char
-.. autofunction:: tcod.sys_register_SDL_renderer
-.. autofunction:: tcod.sys_check_for_event
-.. autofunction:: tcod.sys_wait_for_event
+.. autofunction:: libtcodpy.sys_set_fps
+.. autofunction:: libtcodpy.sys_get_fps
+.. autofunction:: libtcodpy.sys_get_last_frame_length
+.. autofunction:: libtcodpy.sys_sleep_milli
+.. autofunction:: libtcodpy.sys_elapsed_milli
+.. autofunction:: libtcodpy.sys_elapsed_seconds
+.. autofunction:: libtcodpy.sys_set_renderer
+.. autofunction:: libtcodpy.sys_get_renderer
+.. autofunction:: libtcodpy.sys_save_screenshot
+.. autofunction:: libtcodpy.sys_force_fullscreen_resolution
+.. autofunction:: libtcodpy.sys_get_current_resolution
+.. autofunction:: libtcodpy.sys_get_char_size
+.. autofunction:: libtcodpy.sys_update_char
+.. autofunction:: libtcodpy.sys_register_SDL_renderer
+.. autofunction:: libtcodpy.sys_check_for_event
+.. autofunction:: libtcodpy.sys_wait_for_event
pathfinding
-----------
-.. autofunction:: tcod.dijkstra_compute
-.. autofunction:: tcod.dijkstra_delete
-.. autofunction:: tcod.dijkstra_get
-.. autofunction:: tcod.dijkstra_get_distance
-.. autofunction:: tcod.dijkstra_is_empty
-.. autofunction:: tcod.dijkstra_new
-.. autofunction:: tcod.dijkstra_new_using_function
-.. autofunction:: tcod.dijkstra_path_set
-.. autofunction:: tcod.dijkstra_path_walk
-.. autofunction:: tcod.dijkstra_reverse
-.. autofunction:: tcod.dijkstra_size
-
-.. autofunction:: tcod.path_compute
-.. autofunction:: tcod.path_delete
-.. autofunction:: tcod.path_get
-.. autofunction:: tcod.path_get_destination
-.. autofunction:: tcod.path_get_origin
-.. autofunction:: tcod.path_is_empty
-.. autofunction:: tcod.path_new_using_function
-.. autofunction:: tcod.path_new_using_map
-.. autofunction:: tcod.path_reverse
-.. autofunction:: tcod.path_size
-.. autofunction:: tcod.path_walk
+.. autofunction:: libtcodpy.dijkstra_compute
+.. autofunction:: libtcodpy.dijkstra_delete
+.. autofunction:: libtcodpy.dijkstra_get
+.. autofunction:: libtcodpy.dijkstra_get_distance
+.. autofunction:: libtcodpy.dijkstra_is_empty
+.. autofunction:: libtcodpy.dijkstra_new
+.. autofunction:: libtcodpy.dijkstra_new_using_function
+.. autofunction:: libtcodpy.dijkstra_path_set
+.. autofunction:: libtcodpy.dijkstra_path_walk
+.. autofunction:: libtcodpy.dijkstra_reverse
+.. autofunction:: libtcodpy.dijkstra_size
+
+.. autofunction:: libtcodpy.path_compute
+.. autofunction:: libtcodpy.path_delete
+.. autofunction:: libtcodpy.path_get
+.. autofunction:: libtcodpy.path_get_destination
+.. autofunction:: libtcodpy.path_get_origin
+.. autofunction:: libtcodpy.path_is_empty
+.. autofunction:: libtcodpy.path_new_using_function
+.. autofunction:: libtcodpy.path_new_using_map
+.. autofunction:: libtcodpy.path_reverse
+.. autofunction:: libtcodpy.path_size
+.. autofunction:: libtcodpy.path_walk
heightmap
---------
-.. autofunction:: tcod.heightmap_add
-.. autofunction:: tcod.heightmap_add_fbm
-.. autofunction:: tcod.heightmap_add_hill
-.. autofunction:: tcod.heightmap_add_hm
-.. autofunction:: tcod.heightmap_add_voronoi
-.. autofunction:: tcod.heightmap_clamp
-.. autofunction:: tcod.heightmap_clear
-.. autofunction:: tcod.heightmap_copy
-.. autofunction:: tcod.heightmap_count_cells
-.. autofunction:: tcod.heightmap_delete
-.. autofunction:: tcod.heightmap_dig_bezier
-.. autofunction:: tcod.heightmap_dig_hill
-.. autofunction:: tcod.heightmap_get_interpolated_value
-.. autofunction:: tcod.heightmap_get_minmax
-.. autofunction:: tcod.heightmap_get_normal
-.. autofunction:: tcod.heightmap_get_slope
-.. autofunction:: tcod.heightmap_get_value
-.. autofunction:: tcod.heightmap_has_land_on_border
-.. autofunction:: tcod.heightmap_kernel_transform
-.. autofunction:: tcod.heightmap_lerp_hm
-.. autofunction:: tcod.heightmap_multiply_hm
-.. autofunction:: tcod.heightmap_new
-.. autofunction:: tcod.heightmap_normalize
-.. autofunction:: tcod.heightmap_rain_erosion
-.. autofunction:: tcod.heightmap_scale
-.. autofunction:: tcod.heightmap_scale_fbm
-.. autofunction:: tcod.heightmap_set_value
+.. autofunction:: libtcodpy.heightmap_add
+.. autofunction:: libtcodpy.heightmap_add_fbm
+.. autofunction:: libtcodpy.heightmap_add_hill
+.. autofunction:: libtcodpy.heightmap_add_hm
+.. autofunction:: libtcodpy.heightmap_add_voronoi
+.. autofunction:: libtcodpy.heightmap_clamp
+.. autofunction:: libtcodpy.heightmap_clear
+.. autofunction:: libtcodpy.heightmap_copy
+.. autofunction:: libtcodpy.heightmap_count_cells
+.. autofunction:: libtcodpy.heightmap_delete
+.. autofunction:: libtcodpy.heightmap_dig_bezier
+.. autofunction:: libtcodpy.heightmap_dig_hill
+.. autofunction:: libtcodpy.heightmap_get_interpolated_value
+.. autofunction:: libtcodpy.heightmap_get_minmax
+.. autofunction:: libtcodpy.heightmap_get_normal
+.. autofunction:: libtcodpy.heightmap_get_slope
+.. autofunction:: libtcodpy.heightmap_get_value
+.. autofunction:: libtcodpy.heightmap_has_land_on_border
+.. autofunction:: libtcodpy.heightmap_kernel_transform
+.. autofunction:: libtcodpy.heightmap_lerp_hm
+.. autofunction:: libtcodpy.heightmap_multiply_hm
+.. autofunction:: libtcodpy.heightmap_new
+.. autofunction:: libtcodpy.heightmap_normalize
+.. autofunction:: libtcodpy.heightmap_rain_erosion
+.. autofunction:: libtcodpy.heightmap_scale
+.. autofunction:: libtcodpy.heightmap_scale_fbm
+.. autofunction:: libtcodpy.heightmap_set_value
image
-----
-.. autofunction:: tcod.image_load
-.. autofunction:: tcod.image_from_console
-
-.. autofunction:: tcod.image_blit
-.. autofunction:: tcod.image_blit_2x
-.. autofunction:: tcod.image_blit_rect
-.. autofunction:: tcod.image_clear
-.. autofunction:: tcod.image_delete
-.. autofunction:: tcod.image_get_alpha
-.. autofunction:: tcod.image_get_mipmap_pixel
-.. autofunction:: tcod.image_get_pixel
-.. autofunction:: tcod.image_get_size
-.. autofunction:: tcod.image_hflip
-.. autofunction:: tcod.image_invert
-.. autofunction:: tcod.image_is_pixel_transparent
-.. autofunction:: tcod.image_new
-.. autofunction:: tcod.image_put_pixel
-.. autofunction:: tcod.image_refresh_console
-.. autofunction:: tcod.image_rotate90
-.. autofunction:: tcod.image_save
-.. autofunction:: tcod.image_scale
-.. autofunction:: tcod.image_set_key_color
-.. autofunction:: tcod.image_vflip
+.. autofunction:: libtcodpy.image_load
+.. autofunction:: libtcodpy.image_from_console
+
+.. autofunction:: libtcodpy.image_blit
+.. autofunction:: libtcodpy.image_blit_2x
+.. autofunction:: libtcodpy.image_blit_rect
+.. autofunction:: libtcodpy.image_clear
+.. autofunction:: libtcodpy.image_delete
+.. autofunction:: libtcodpy.image_get_alpha
+.. autofunction:: libtcodpy.image_get_mipmap_pixel
+.. autofunction:: libtcodpy.image_get_pixel
+.. autofunction:: libtcodpy.image_get_size
+.. autofunction:: libtcodpy.image_hflip
+.. autofunction:: libtcodpy.image_invert
+.. autofunction:: libtcodpy.image_is_pixel_transparent
+.. autofunction:: libtcodpy.image_new
+.. autofunction:: libtcodpy.image_put_pixel
+.. autofunction:: libtcodpy.image_refresh_console
+.. autofunction:: libtcodpy.image_rotate90
+.. autofunction:: libtcodpy.image_save
+.. autofunction:: libtcodpy.image_scale
+.. autofunction:: libtcodpy.image_set_key_color
+.. autofunction:: libtcodpy.image_vflip
line
----
-.. autofunction:: tcod.line_init
-.. autofunction:: tcod.line_step
-.. autofunction:: tcod.line
-.. autofunction:: tcod.line_iter
-.. autofunction:: tcod.line_where
+.. autofunction:: libtcodpy.line_init
+.. autofunction:: libtcodpy.line_step
+.. autofunction:: libtcodpy.line
+.. autofunction:: libtcodpy.line_iter
+.. autofunction:: libtcodpy.line_where
map
---
-.. autofunction:: tcod.map_clear
-.. autofunction:: tcod.map_compute_fov
-.. autofunction:: tcod.map_copy
-.. autofunction:: tcod.map_delete
-.. autofunction:: tcod.map_get_height
-.. autofunction:: tcod.map_get_width
-.. autofunction:: tcod.map_is_in_fov
-.. autofunction:: tcod.map_is_transparent
-.. autofunction:: tcod.map_is_walkable
-.. autofunction:: tcod.map_new
-.. autofunction:: tcod.map_set_properties
+.. autofunction:: libtcodpy.map_clear
+.. autofunction:: libtcodpy.map_compute_fov
+.. autofunction:: libtcodpy.map_copy
+.. autofunction:: libtcodpy.map_delete
+.. autofunction:: libtcodpy.map_get_height
+.. autofunction:: libtcodpy.map_get_width
+.. autofunction:: libtcodpy.map_is_in_fov
+.. autofunction:: libtcodpy.map_is_transparent
+.. autofunction:: libtcodpy.map_is_walkable
+.. autofunction:: libtcodpy.map_new
+.. autofunction:: libtcodpy.map_set_properties
mouse
-----
-.. autofunction:: tcod.mouse_get_status
-.. autofunction:: tcod.mouse_is_cursor_visible
-.. autofunction:: tcod.mouse_move
-.. autofunction:: tcod.mouse_show_cursor
+.. autofunction:: libtcodpy.mouse_get_status
+.. autofunction:: libtcodpy.mouse_is_cursor_visible
+.. autofunction:: libtcodpy.mouse_move
+.. autofunction:: libtcodpy.mouse_show_cursor
namegen
-------
-.. autofunction:: tcod.namegen_destroy
-.. autofunction:: tcod.namegen_generate
-.. autofunction:: tcod.namegen_generate_custom
-.. autofunction:: tcod.namegen_get_sets
-.. autofunction:: tcod.namegen_parse
+.. autofunction:: libtcodpy.namegen_destroy
+.. autofunction:: libtcodpy.namegen_generate
+.. autofunction:: libtcodpy.namegen_generate_custom
+.. autofunction:: libtcodpy.namegen_get_sets
+.. autofunction:: libtcodpy.namegen_parse
noise
-----
-.. autofunction:: tcod.noise_delete
-.. autofunction:: tcod.noise_get
-.. autofunction:: tcod.noise_get_fbm
-.. autofunction:: tcod.noise_get_turbulence
-.. autofunction:: tcod.noise_new
-.. autofunction:: tcod.noise_set_type
+.. autofunction:: libtcodpy.noise_delete
+.. autofunction:: libtcodpy.noise_get
+.. autofunction:: libtcodpy.noise_get_fbm
+.. autofunction:: libtcodpy.noise_get_turbulence
+.. autofunction:: libtcodpy.noise_new
+.. autofunction:: libtcodpy.noise_set_type
parser
------
-.. autofunction:: tcod.parser_delete
-.. autofunction:: tcod.parser_get_bool_property
-.. autofunction:: tcod.parser_get_char_property
-.. autofunction:: tcod.parser_get_color_property
-.. autofunction:: tcod.parser_get_dice_property
-.. autofunction:: tcod.parser_get_float_property
-.. autofunction:: tcod.parser_get_int_property
-.. autofunction:: tcod.parser_get_list_property
-.. autofunction:: tcod.parser_get_string_property
-.. autofunction:: tcod.parser_new
-.. autofunction:: tcod.parser_new_struct
-.. autofunction:: tcod.parser_run
+.. autofunction:: libtcodpy.parser_delete
+.. autofunction:: libtcodpy.parser_get_bool_property
+.. autofunction:: libtcodpy.parser_get_char_property
+.. autofunction:: libtcodpy.parser_get_color_property
+.. autofunction:: libtcodpy.parser_get_dice_property
+.. autofunction:: libtcodpy.parser_get_float_property
+.. autofunction:: libtcodpy.parser_get_int_property
+.. autofunction:: libtcodpy.parser_get_list_property
+.. autofunction:: libtcodpy.parser_get_string_property
+.. autofunction:: libtcodpy.parser_new
+.. autofunction:: libtcodpy.parser_new_struct
+.. autofunction:: libtcodpy.parser_run
random
------
-.. autofunction:: tcod.random_delete
-.. autofunction:: tcod.random_get_double
-.. autofunction:: tcod.random_get_double_mean
-.. autofunction:: tcod.random_get_float
-.. autofunction:: tcod.random_get_float_mean
-.. autofunction:: tcod.random_get_instance
-.. autofunction:: tcod.random_get_int
-.. autofunction:: tcod.random_get_int_mean
-.. autofunction:: tcod.random_new
-.. autofunction:: tcod.random_new_from_seed
-.. autofunction:: tcod.random_restore
-.. autofunction:: tcod.random_save
-.. autofunction:: tcod.random_set_distribution
+.. autofunction:: libtcodpy.random_delete
+.. autofunction:: libtcodpy.random_get_double
+.. autofunction:: libtcodpy.random_get_double_mean
+.. autofunction:: libtcodpy.random_get_float
+.. autofunction:: libtcodpy.random_get_float_mean
+.. autofunction:: libtcodpy.random_get_instance
+.. autofunction:: libtcodpy.random_get_int
+.. autofunction:: libtcodpy.random_get_int_mean
+.. autofunction:: libtcodpy.random_new
+.. autofunction:: libtcodpy.random_new_from_seed
+.. autofunction:: libtcodpy.random_restore
+.. autofunction:: libtcodpy.random_save
+.. autofunction:: libtcodpy.random_set_distribution
struct
------
-.. autofunction:: tcod.struct_add_flag
-.. autofunction:: tcod.struct_add_list_property
-.. autofunction:: tcod.struct_add_property
-.. autofunction:: tcod.struct_add_structure
-.. autofunction:: tcod.struct_add_value_list
-.. autofunction:: tcod.struct_get_name
-.. autofunction:: tcod.struct_get_type
-.. autofunction:: tcod.struct_is_mandatory
+.. autofunction:: libtcodpy.struct_add_flag
+.. autofunction:: libtcodpy.struct_add_list_property
+.. autofunction:: libtcodpy.struct_add_property
+.. autofunction:: libtcodpy.struct_add_structure
+.. autofunction:: libtcodpy.struct_add_value_list
+.. autofunction:: libtcodpy.struct_get_name
+.. autofunction:: libtcodpy.struct_get_type
+.. autofunction:: libtcodpy.struct_is_mandatory
other
-----
-.. autoclass:: tcod.ConsoleBuffer
+.. autoclass:: libtcodpy.ConsoleBuffer
:members:
-.. autoclass:: tcod.Dice(nb_dices=0, nb_faces=0, multiplier=0, addsub=0)
+.. autoclass:: libtcodpy.Dice(nb_dices=0, nb_faces=0, multiplier=0, addsub=0)
:members:
diff --git a/docs/prolog.rst b/docs/prolog.rst
new file mode 100644
index 00000000..cdcd4c22
--- /dev/null
+++ b/docs/prolog.rst
@@ -0,0 +1,6 @@
+.. Add inline code roles to all sources
+.. role:: python(code)
+ :language: python
+
+.. role:: shell(code)
+ :language: shell
diff --git a/docs/readme.rst b/docs/readme.rst
deleted file mode 100644
index 72a33558..00000000
--- a/docs/readme.rst
+++ /dev/null
@@ -1 +0,0 @@
-.. include:: ../README.rst
diff --git a/docs/requirements.txt b/docs/requirements.txt
new file mode 100644
index 00000000..4cb56294
--- /dev/null
+++ b/docs/requirements.txt
@@ -0,0 +1,2 @@
+sphinx>=7.0.1
+furo>=2023.5.20
diff --git a/docs/sdl/audio.rst b/docs/sdl/audio.rst
new file mode 100644
index 00000000..6eb14a1f
--- /dev/null
+++ b/docs/sdl/audio.rst
@@ -0,0 +1,5 @@
+SDL Audio ``tcod.sdl.audio``
+============================
+
+.. automodule:: tcod.sdl.audio
+ :members:
diff --git a/docs/sdl/joystick.rst b/docs/sdl/joystick.rst
new file mode 100644
index 00000000..397cd1e2
--- /dev/null
+++ b/docs/sdl/joystick.rst
@@ -0,0 +1,5 @@
+SDL Joystick Support ``tcod.sdl.joystick``
+==========================================
+
+.. automodule:: tcod.sdl.joystick
+ :members:
diff --git a/docs/sdl/mouse.rst b/docs/sdl/mouse.rst
new file mode 100644
index 00000000..ab73d06c
--- /dev/null
+++ b/docs/sdl/mouse.rst
@@ -0,0 +1,5 @@
+SDL Mouse Functions ``tcod.sdl.mouse``
+======================================
+
+.. automodule:: tcod.sdl.mouse
+ :members:
diff --git a/docs/sdl/render.rst b/docs/sdl/render.rst
new file mode 100644
index 00000000..f5922b1f
--- /dev/null
+++ b/docs/sdl/render.rst
@@ -0,0 +1,5 @@
+SDL Rendering ``tcod.sdl.render``
+=================================
+
+.. automodule:: tcod.sdl.render
+ :members:
diff --git a/docs/sdl/video.rst b/docs/sdl/video.rst
new file mode 100644
index 00000000..25dffb17
--- /dev/null
+++ b/docs/sdl/video.rst
@@ -0,0 +1,5 @@
+SDL Window and Display API ``tcod.sdl.video``
+=============================================
+
+.. automodule:: tcod.sdl.video
+ :members:
diff --git a/docs/tcod.rst b/docs/tcod.rst
deleted file mode 100644
index c1a1c574..00000000
--- a/docs/tcod.rst
+++ /dev/null
@@ -1,42 +0,0 @@
-
-tcod.console
-============
-
-.. automodule:: tcod.console
- :members:
-
-tcod.map
-========
-
-.. automodule:: tcod.map
- :members:
-
-tcod.bsp
-=========
-
-.. automodule:: tcod.bsp
- :members:
-
-tcod.path
-=========
-
-.. automodule:: tcod.path
- :members:
-
-tcod.image
-==========
-
-.. automodule:: tcod.image
- :members:
-
-tcod.random
-===========
-
-.. automodule:: tcod.random
- :members:
-
-tcod.noise
-==========
-
-.. automodule:: tcod.noise
- :members:
diff --git a/docs/tcod/bsp.rst b/docs/tcod/bsp.rst
new file mode 100644
index 00000000..14ddcdcd
--- /dev/null
+++ b/docs/tcod/bsp.rst
@@ -0,0 +1,5 @@
+Binary Space Partitioning ``tcod.bsp``
+======================================
+
+.. automodule:: tcod.bsp
+ :members:
diff --git a/docs/tcod/charmap-reference.rst b/docs/tcod/charmap-reference.rst
new file mode 100644
index 00000000..3ad42b16
--- /dev/null
+++ b/docs/tcod/charmap-reference.rst
@@ -0,0 +1,477 @@
+Character Table Reference
+=========================
+
+This document exists as an easy reference for using non-ASCII glyphs in
+standard tcod functions.
+
+*Tile Index* is the position of the glyph in the tileset image.
+This is relevant for loading the tileset and for using :any:`Tileset.remap` to
+reassign tiles to new code points.
+
+*Unicode* is the Unicode code point as a hexadecimal number.
+You can use :any:`chr` to convert these numbers into a string.
+Character maps such as :any:`tcod.tileset.CHARMAP_CP437` are simply a list of
+Unicode numbers, where the index of the list is the *Tile Index*.
+
+*String* is the Python string for that character.
+This lets you use that character inline with print functions.
+These will work with :any:`ord` to convert them into a number.
+
+*Name* is the official name of a Unicode character.
+
+The symbols currently shown under *String* are provided by your browser, they
+typically won't match the graphics provided by your tileset or could even be
+missing from your browsers font entirely. You could experience similar issues
+with your editor and IDE.
+
+
+.. _code-page-437:
+
+Code Page 437
+-------------
+
+The layout for tilesets loaded with: :any:`tcod.tileset.CHARMAP_CP437`
+
+This is one of the more common character mappings.
+Used for several games in the DOS era, and still used today whenever you want
+an old school aesthetic.
+
+The Dwarf Fortress community is known to have a large collection of tilesets in
+this format:
+https://dwarffortresswiki.org/index.php/Tileset_repository
+
+Wikipedia also has a good reference for this character mapping:
+https://en.wikipedia.org/wiki/Code_page_437
+
+============ ========= ========= ==================================================
+ Tile Index Unicode String Name
+============ ========= ========= ==================================================
+ 0 0x00 \'\\x00\'
+ 1 0x263A \'☺\' WHITE SMILING FACE
+ 2 0x263B \'☻\' BLACK SMILING FACE
+ 3 0x2665 \'♥\' BLACK HEART SUIT
+ 4 0x2666 \'♦\' BLACK DIAMOND SUIT
+ 5 0x2663 \'♣\' BLACK CLUB SUIT
+ 6 0x2660 \'♠\' BLACK SPADE SUIT
+ 7 0x2022 \'•\' BULLET
+ 8 0x25D8 \'◘\' INVERSE BULLET
+ 9 0x25CB \'○\' WHITE CIRCLE
+ 10 0x25D9 \'◙\' INVERSE WHITE CIRCLE
+ 11 0x2642 \'♂\' MALE SIGN
+ 12 0x2640 \'♀\' FEMALE SIGN
+ 13 0x266A \'♪\' EIGHTH NOTE
+ 14 0x266B \'♫\' BEAMED EIGHTH NOTES
+ 15 0x263C \'☼\' WHITE SUN WITH RAYS
+ 16 0x25BA \'►\' BLACK RIGHT-POINTING POINTER
+ 17 0x25C4 \'◄\' BLACK LEFT-POINTING POINTER
+ 18 0x2195 \'↕\' UP DOWN ARROW
+ 19 0x203C \'‼\' DOUBLE EXCLAMATION MARK
+ 20 0xB6 \'¶\' PILCROW SIGN
+ 21 0xA7 \'§\' SECTION SIGN
+ 22 0x25AC \'▬\' BLACK RECTANGLE
+ 23 0x21A8 \'↨\' UP DOWN ARROW WITH BASE
+ 24 0x2191 \'↑\' UPWARDS ARROW
+ 25 0x2193 \'↓\' DOWNWARDS ARROW
+ 26 0x2192 \'→\' RIGHTWARDS ARROW
+ 27 0x2190 \'←\' LEFTWARDS ARROW
+ 28 0x221F \'∟\' RIGHT ANGLE
+ 29 0x2194 \'↔\' LEFT RIGHT ARROW
+ 30 0x25B2 \'▲\' BLACK UP-POINTING TRIANGLE
+ 31 0x25BC \'▼\' BLACK DOWN-POINTING TRIANGLE
+ 32 0x20 \' \' SPACE
+ 33 0x21 \'!\' EXCLAMATION MARK
+ 34 0x22 \'\"\' QUOTATION MARK
+ 35 0x23 \'#\' NUMBER SIGN
+ 36 0x24 \'$\' DOLLAR SIGN
+ 37 0x25 \'%\' PERCENT SIGN
+ 38 0x26 \'&\' AMPERSAND
+ 39 0x27 \"\'\" APOSTROPHE
+ 40 0x28 \'(\' LEFT PARENTHESIS
+ 41 0x29 \')\' RIGHT PARENTHESIS
+ 42 0x2A \'\*\' ASTERISK
+ 43 0x2B \'+\' PLUS SIGN
+ 44 0x2C \',\' COMMA
+ 45 0x2D \'-\' HYPHEN-MINUS
+ 46 0x2E \'.\' FULL STOP
+ 47 0x2F \'/\' SOLIDUS
+ 48 0x30 \'0\' DIGIT ZERO
+ 49 0x31 \'1\' DIGIT ONE
+ 50 0x32 \'2\' DIGIT TWO
+ 51 0x33 \'3\' DIGIT THREE
+ 52 0x34 \'4\' DIGIT FOUR
+ 53 0x35 \'5\' DIGIT FIVE
+ 54 0x36 \'6\' DIGIT SIX
+ 55 0x37 \'7\' DIGIT SEVEN
+ 56 0x38 \'8\' DIGIT EIGHT
+ 57 0x39 \'9\' DIGIT NINE
+ 58 0x3A \':\' COLON
+ 59 0x3B \';\' SEMICOLON
+ 60 0x3C \'<\' LESS-THAN SIGN
+ 61 0x3D \'=\' EQUALS SIGN
+ 62 0x3E \'>\' GREATER-THAN SIGN
+ 63 0x3F \'?\' QUESTION MARK
+ 64 0x40 \'@\' COMMERCIAL AT
+ 65 0x41 \'A\' LATIN CAPITAL LETTER A
+ 66 0x42 \'B\' LATIN CAPITAL LETTER B
+ 67 0x43 \'C\' LATIN CAPITAL LETTER C
+ 68 0x44 \'D\' LATIN CAPITAL LETTER D
+ 69 0x45 \'E\' LATIN CAPITAL LETTER E
+ 70 0x46 \'F\' LATIN CAPITAL LETTER F
+ 71 0x47 \'G\' LATIN CAPITAL LETTER G
+ 72 0x48 \'H\' LATIN CAPITAL LETTER H
+ 73 0x49 \'I\' LATIN CAPITAL LETTER I
+ 74 0x4A \'J\' LATIN CAPITAL LETTER J
+ 75 0x4B \'K\' LATIN CAPITAL LETTER K
+ 76 0x4C \'L\' LATIN CAPITAL LETTER L
+ 77 0x4D \'M\' LATIN CAPITAL LETTER M
+ 78 0x4E \'N\' LATIN CAPITAL LETTER N
+ 79 0x4F \'O\' LATIN CAPITAL LETTER O
+ 80 0x50 \'P\' LATIN CAPITAL LETTER P
+ 81 0x51 \'Q\' LATIN CAPITAL LETTER Q
+ 82 0x52 \'R\' LATIN CAPITAL LETTER R
+ 83 0x53 \'S\' LATIN CAPITAL LETTER S
+ 84 0x54 \'T\' LATIN CAPITAL LETTER T
+ 85 0x55 \'U\' LATIN CAPITAL LETTER U
+ 86 0x56 \'V\' LATIN CAPITAL LETTER V
+ 87 0x57 \'W\' LATIN CAPITAL LETTER W
+ 88 0x58 \'X\' LATIN CAPITAL LETTER X
+ 89 0x59 \'Y\' LATIN CAPITAL LETTER Y
+ 90 0x5A \'Z\' LATIN CAPITAL LETTER Z
+ 91 0x5B \'[\' LEFT SQUARE BRACKET
+ 92 0x5C \'\\\\\' REVERSE SOLIDUS
+ 93 0x5D \']\' RIGHT SQUARE BRACKET
+ 94 0x5E \'^\' CIRCUMFLEX ACCENT
+ 95 0x5F \'_\' LOW LINE
+ 96 0x60 \'\`\' GRAVE ACCENT
+ 97 0x61 \'a\' LATIN SMALL LETTER A
+ 98 0x62 \'b\' LATIN SMALL LETTER B
+ 99 0x63 \'c\' LATIN SMALL LETTER C
+ 100 0x64 \'d\' LATIN SMALL LETTER D
+ 101 0x65 \'e\' LATIN SMALL LETTER E
+ 102 0x66 \'f\' LATIN SMALL LETTER F
+ 103 0x67 \'g\' LATIN SMALL LETTER G
+ 104 0x68 \'h\' LATIN SMALL LETTER H
+ 105 0x69 \'i\' LATIN SMALL LETTER I
+ 106 0x6A \'j\' LATIN SMALL LETTER J
+ 107 0x6B \'k\' LATIN SMALL LETTER K
+ 108 0x6C \'l\' LATIN SMALL LETTER L
+ 109 0x6D \'m\' LATIN SMALL LETTER M
+ 110 0x6E \'n\' LATIN SMALL LETTER N
+ 111 0x6F \'o\' LATIN SMALL LETTER O
+ 112 0x70 \'p\' LATIN SMALL LETTER P
+ 113 0x71 \'q\' LATIN SMALL LETTER Q
+ 114 0x72 \'r\' LATIN SMALL LETTER R
+ 115 0x73 \'s\' LATIN SMALL LETTER S
+ 116 0x74 \'t\' LATIN SMALL LETTER T
+ 117 0x75 \'u\' LATIN SMALL LETTER U
+ 118 0x76 \'v\' LATIN SMALL LETTER V
+ 119 0x77 \'w\' LATIN SMALL LETTER W
+ 120 0x78 \'x\' LATIN SMALL LETTER X
+ 121 0x79 \'y\' LATIN SMALL LETTER Y
+ 122 0x7A \'z\' LATIN SMALL LETTER Z
+ 123 0x7B \'{\' LEFT CURLY BRACKET
+ 124 0x7C \'\|\' VERTICAL LINE
+ 125 0x7D \'}\' RIGHT CURLY BRACKET
+ 126 0x7E \'~\' TILDE
+ 127 0x2302 \'⌂\' HOUSE
+ 128 0xC7 \'Ç\' LATIN CAPITAL LETTER C WITH CEDILLA
+ 129 0xFC \'ü\' LATIN SMALL LETTER U WITH DIAERESIS
+ 130 0xE9 \'é\' LATIN SMALL LETTER E WITH ACUTE
+ 131 0xE2 \'â\' LATIN SMALL LETTER A WITH CIRCUMFLEX
+ 132 0xE4 \'ä\' LATIN SMALL LETTER A WITH DIAERESIS
+ 133 0xE0 \'à\' LATIN SMALL LETTER A WITH GRAVE
+ 134 0xE5 \'å\' LATIN SMALL LETTER A WITH RING ABOVE
+ 135 0xE7 \'ç\' LATIN SMALL LETTER C WITH CEDILLA
+ 136 0xEA \'ê\' LATIN SMALL LETTER E WITH CIRCUMFLEX
+ 137 0xEB \'ë\' LATIN SMALL LETTER E WITH DIAERESIS
+ 138 0xE8 \'è\' LATIN SMALL LETTER E WITH GRAVE
+ 139 0xEF \'ï\' LATIN SMALL LETTER I WITH DIAERESIS
+ 140 0xEE \'î\' LATIN SMALL LETTER I WITH CIRCUMFLEX
+ 141 0xEC \'ì\' LATIN SMALL LETTER I WITH GRAVE
+ 142 0xC4 \'Ä\' LATIN CAPITAL LETTER A WITH DIAERESIS
+ 143 0xC5 \'Å\' LATIN CAPITAL LETTER A WITH RING ABOVE
+ 144 0xC9 \'É\' LATIN CAPITAL LETTER E WITH ACUTE
+ 145 0xE6 \'æ\' LATIN SMALL LETTER AE
+ 146 0xC6 \'Æ\' LATIN CAPITAL LETTER AE
+ 147 0xF4 \'ô\' LATIN SMALL LETTER O WITH CIRCUMFLEX
+ 148 0xF6 \'ö\' LATIN SMALL LETTER O WITH DIAERESIS
+ 149 0xF2 \'ò\' LATIN SMALL LETTER O WITH GRAVE
+ 150 0xFB \'û\' LATIN SMALL LETTER U WITH CIRCUMFLEX
+ 151 0xF9 \'ù\' LATIN SMALL LETTER U WITH GRAVE
+ 152 0xFF \'ÿ\' LATIN SMALL LETTER Y WITH DIAERESIS
+ 153 0xD6 \'Ö\' LATIN CAPITAL LETTER O WITH DIAERESIS
+ 154 0xDC \'Ü\' LATIN CAPITAL LETTER U WITH DIAERESIS
+ 155 0xA2 \'¢\' CENT SIGN
+ 156 0xA3 \'£\' POUND SIGN
+ 157 0xA5 \'¥\' YEN SIGN
+ 158 0x20A7 \'₧\' PESETA SIGN
+ 159 0x0192 \'ƒ\' LATIN SMALL LETTER F WITH HOOK
+ 160 0xE1 \'á\' LATIN SMALL LETTER A WITH ACUTE
+ 161 0xED \'í\' LATIN SMALL LETTER I WITH ACUTE
+ 162 0xF3 \'ó\' LATIN SMALL LETTER O WITH ACUTE
+ 163 0xFA \'ú\' LATIN SMALL LETTER U WITH ACUTE
+ 164 0xF1 \'ñ\' LATIN SMALL LETTER N WITH TILDE
+ 165 0xD1 \'Ñ\' LATIN CAPITAL LETTER N WITH TILDE
+ 166 0xAA \'ª\' FEMININE ORDINAL INDICATOR
+ 167 0xBA \'º\' MASCULINE ORDINAL INDICATOR
+ 168 0xBF \'¿\' INVERTED QUESTION MARK
+ 169 0x2310 \'⌐\' REVERSED NOT SIGN
+ 170 0xAC \'¬\' NOT SIGN
+ 171 0xBD \'½\' VULGAR FRACTION ONE HALF
+ 172 0xBC \'¼\' VULGAR FRACTION ONE QUARTER
+ 173 0xA1 \'¡\' INVERTED EXCLAMATION MARK
+ 174 0xAB \'«\' LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+ 175 0xBB \'»\' RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+ 176 0x2591 \'░\' LIGHT SHADE
+ 177 0x2592 \'▒\' MEDIUM SHADE
+ 178 0x2593 \'▓\' DARK SHADE
+ 179 0x2502 \'│\' BOX DRAWINGS LIGHT VERTICAL
+ 180 0x2524 \'┤\' BOX DRAWINGS LIGHT VERTICAL AND LEFT
+ 181 0x2561 \'╡\' BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE
+ 182 0x2562 \'╢\' BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE
+ 183 0x2556 \'╖\' BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE
+ 184 0x2555 \'╕\' BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE
+ 185 0x2563 \'╣\' BOX DRAWINGS DOUBLE VERTICAL AND LEFT
+ 186 0x2551 \'║\' BOX DRAWINGS DOUBLE VERTICAL
+ 187 0x2557 \'╗\' BOX DRAWINGS DOUBLE DOWN AND LEFT
+ 188 0x255D \'╝\' BOX DRAWINGS DOUBLE UP AND LEFT
+ 189 0x255C \'╜\' BOX DRAWINGS UP DOUBLE AND LEFT SINGLE
+ 190 0x255B \'╛\' BOX DRAWINGS UP SINGLE AND LEFT DOUBLE
+ 191 0x2510 \'┐\' BOX DRAWINGS LIGHT DOWN AND LEFT
+ 192 0x2514 \'└\' BOX DRAWINGS LIGHT UP AND RIGHT
+ 193 0x2534 \'┴\' BOX DRAWINGS LIGHT UP AND HORIZONTAL
+ 194 0x252C \'┬\' BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
+ 195 0x251C \'├\' BOX DRAWINGS LIGHT VERTICAL AND RIGHT
+ 196 0x2500 \'─\' BOX DRAWINGS LIGHT HORIZONTAL
+ 197 0x253C \'┼\' BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
+ 198 0x255E \'╞\' BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE
+ 199 0x255F \'╟\' BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE
+ 200 0x255A \'╚\' BOX DRAWINGS DOUBLE UP AND RIGHT
+ 201 0x2554 \'╔\' BOX DRAWINGS DOUBLE DOWN AND RIGHT
+ 202 0x2569 \'╩\' BOX DRAWINGS DOUBLE UP AND HORIZONTAL
+ 203 0x2566 \'╦\' BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL
+ 204 0x2560 \'╠\' BOX DRAWINGS DOUBLE VERTICAL AND RIGHT
+ 205 0x2550 \'═\' BOX DRAWINGS DOUBLE HORIZONTAL
+ 206 0x256C \'╬\' BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL
+ 207 0x2567 \'╧\' BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE
+ 208 0x2568 \'╨\' BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE
+ 209 0x2564 \'╤\' BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE
+ 210 0x2565 \'╥\' BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE
+ 211 0x2559 \'╙\' BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE
+ 212 0x2558 \'╘\' BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE
+ 213 0x2552 \'╒\' BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE
+ 214 0x2553 \'╓\' BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE
+ 215 0x256B \'╫\' BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE
+ 216 0x256A \'╪\' BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE
+ 217 0x2518 \'┘\' BOX DRAWINGS LIGHT UP AND LEFT
+ 218 0x250C \'┌\' BOX DRAWINGS LIGHT DOWN AND RIGHT
+ 219 0x2588 \'█\' FULL BLOCK
+ 220 0x2584 \'▄\' LOWER HALF BLOCK
+ 221 0x258C \'▌\' LEFT HALF BLOCK
+ 222 0x2590 \'▐\' RIGHT HALF BLOCK
+ 223 0x2580 \'▀\' UPPER HALF BLOCK
+ 224 0x03B1 \'α\' GREEK SMALL LETTER ALPHA
+ 225 0xDF \'ß\' LATIN SMALL LETTER SHARP S
+ 226 0x0393 \'Γ\' GREEK CAPITAL LETTER GAMMA
+ 227 0x03C0 \'π\' GREEK SMALL LETTER PI
+ 228 0x03A3 \'Σ\' GREEK CAPITAL LETTER SIGMA
+ 229 0x03C3 \'σ\' GREEK SMALL LETTER SIGMA
+ 230 0xB5 \'µ\' MICRO SIGN
+ 231 0x03C4 \'τ\' GREEK SMALL LETTER TAU
+ 232 0x03A6 \'Φ\' GREEK CAPITAL LETTER PHI
+ 233 0x0398 \'Θ\' GREEK CAPITAL LETTER THETA
+ 234 0x03A9 \'Ω\' GREEK CAPITAL LETTER OMEGA
+ 235 0x03B4 \'δ\' GREEK SMALL LETTER DELTA
+ 236 0x221E \'∞\' INFINITY
+ 237 0x03C6 \'φ\' GREEK SMALL LETTER PHI
+ 238 0x03B5 \'ε\' GREEK SMALL LETTER EPSILON
+ 239 0x2229 \'∩\' INTERSECTION
+ 240 0x2261 \'≡\' IDENTICAL TO
+ 241 0xB1 \'±\' PLUS-MINUS SIGN
+ 242 0x2265 \'≥\' GREATER-THAN OR EQUAL TO
+ 243 0x2264 \'≤\' LESS-THAN OR EQUAL TO
+ 244 0x2320 \'⌠\' TOP HALF INTEGRAL
+ 245 0x2321 \'⌡\' BOTTOM HALF INTEGRAL
+ 246 0xF7 \'÷\' DIVISION SIGN
+ 247 0x2248 \'≈\' ALMOST EQUAL TO
+ 248 0xB0 \'°\' DEGREE SIGN
+ 249 0x2219 \'∙\' BULLET OPERATOR
+ 250 0xB7 \'·\' MIDDLE DOT
+ 251 0x221A \'√\' SQUARE ROOT
+ 252 0x207F \'ⁿ\' SUPERSCRIPT LATIN SMALL LETTER N
+ 253 0xB2 \'²\' SUPERSCRIPT TWO
+ 254 0x25A0 \'■\' BLACK SQUARE
+ 255 0xA0 \'\\xa0\' NO-BREAK SPACE
+============ ========= ========= ==================================================
+
+.. _deprecated-tcod-layout:
+
+Deprecated TCOD Layout
+----------------------
+
+The layout for tilesets loaded with: :any:`tcod.tileset.CHARMAP_TCOD`
+
+============ ========= ========= ===========================================
+ Tile Index Unicode String Name
+============ ========= ========= ===========================================
+ 0 0x20 \' \' SPACE
+ 1 0x21 \'!\' EXCLAMATION MARK
+ 2 0x22 \'\"\' QUOTATION MARK
+ 3 0x23 \'#\' NUMBER SIGN
+ 4 0x24 \'$\' DOLLAR SIGN
+ 5 0x25 \'%\' PERCENT SIGN
+ 6 0x26 \'&\' AMPERSAND
+ 7 0x27 \"\'\" APOSTROPHE
+ 8 0x28 \'(\' LEFT PARENTHESIS
+ 9 0x29 \')\' RIGHT PARENTHESIS
+ 10 0x2A \'\*\' ASTERISK
+ 11 0x2B \'+\' PLUS SIGN
+ 12 0x2C \',\' COMMA
+ 13 0x2D \'-\' HYPHEN-MINUS
+ 14 0x2E \'.\' FULL STOP
+ 15 0x2F \'/\' SOLIDUS
+ 16 0x30 \'0\' DIGIT ZERO
+ 17 0x31 \'1\' DIGIT ONE
+ 18 0x32 \'2\' DIGIT TWO
+ 19 0x33 \'3\' DIGIT THREE
+ 20 0x34 \'4\' DIGIT FOUR
+ 21 0x35 \'5\' DIGIT FIVE
+ 22 0x36 \'6\' DIGIT SIX
+ 23 0x37 \'7\' DIGIT SEVEN
+ 24 0x38 \'8\' DIGIT EIGHT
+ 25 0x39 \'9\' DIGIT NINE
+ 26 0x3A \':\' COLON
+ 27 0x3B \';\' SEMICOLON
+ 28 0x3C \'<\' LESS-THAN SIGN
+ 29 0x3D \'=\' EQUALS SIGN
+ 30 0x3E \'>\' GREATER-THAN SIGN
+ 31 0x3F \'?\' QUESTION MARK
+ 32 0x40 \'@\' COMMERCIAL AT
+ 33 0x5B \'[\' LEFT SQUARE BRACKET
+ 34 0x5C \'\\\\\' REVERSE SOLIDUS
+ 35 0x5D \']\' RIGHT SQUARE BRACKET
+ 36 0x5E \'^\' CIRCUMFLEX ACCENT
+ 37 0x5F \'_\' LOW LINE
+ 38 0x60 \'\`\' GRAVE ACCENT
+ 39 0x7B \'{\' LEFT CURLY BRACKET
+ 40 0x7C \'\|\' VERTICAL LINE
+ 41 0x7D \'}\' RIGHT CURLY BRACKET
+ 42 0x7E \'~\' TILDE
+ 43 0x2591 \'░\' LIGHT SHADE
+ 44 0x2592 \'▒\' MEDIUM SHADE
+ 45 0x2593 \'▓\' DARK SHADE
+ 46 0x2502 \'│\' BOX DRAWINGS LIGHT VERTICAL
+ 47 0x2500 \'─\' BOX DRAWINGS LIGHT HORIZONTAL
+ 48 0x253C \'┼\' BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
+ 49 0x2524 \'┤\' BOX DRAWINGS LIGHT VERTICAL AND LEFT
+ 50 0x2534 \'┴\' BOX DRAWINGS LIGHT UP AND HORIZONTAL
+ 51 0x251C \'├\' BOX DRAWINGS LIGHT VERTICAL AND RIGHT
+ 52 0x252C \'┬\' BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
+ 53 0x2514 \'└\' BOX DRAWINGS LIGHT UP AND RIGHT
+ 54 0x250C \'┌\' BOX DRAWINGS LIGHT DOWN AND RIGHT
+ 55 0x2510 \'┐\' BOX DRAWINGS LIGHT DOWN AND LEFT
+ 56 0x2518 \'┘\' BOX DRAWINGS LIGHT UP AND LEFT
+ 57 0x2598 \'▘\' QUADRANT UPPER LEFT
+ 58 0x259D \'▝\' QUADRANT UPPER RIGHT
+ 59 0x2580 \'▀\' UPPER HALF BLOCK
+ 60 0x2596 \'▖\' QUADRANT LOWER LEFT
+ 61 0x259A \'▚\' QUADRANT UPPER LEFT AND LOWER RIGHT
+ 62 0x2590 \'▐\' RIGHT HALF BLOCK
+ 63 0x2597 \'▗\' QUADRANT LOWER RIGHT
+ 64 0x2191 \'↑\' UPWARDS ARROW
+ 65 0x2193 \'↓\' DOWNWARDS ARROW
+ 66 0x2190 \'←\' LEFTWARDS ARROW
+ 67 0x2192 \'→\' RIGHTWARDS ARROW
+ 68 0x25B2 \'▲\' BLACK UP-POINTING TRIANGLE
+ 69 0x25BC \'▼\' BLACK DOWN-POINTING TRIANGLE
+ 70 0x25C4 \'◄\' BLACK LEFT-POINTING POINTER
+ 71 0x25BA \'►\' BLACK RIGHT-POINTING POINTER
+ 72 0x2195 \'↕\' UP DOWN ARROW
+ 73 0x2194 \'↔\' LEFT RIGHT ARROW
+ 74 0x2610 \'☐\' BALLOT BOX
+ 75 0x2611 \'☑\' BALLOT BOX WITH CHECK
+ 76 0x25CB \'○\' WHITE CIRCLE
+ 77 0x25C9 \'◉\' FISHEYE
+ 78 0x2551 \'║\' BOX DRAWINGS DOUBLE VERTICAL
+ 79 0x2550 \'═\' BOX DRAWINGS DOUBLE HORIZONTAL
+ 80 0x256C \'╬\' BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL
+ 81 0x2563 \'╣\' BOX DRAWINGS DOUBLE VERTICAL AND LEFT
+ 82 0x2569 \'╩\' BOX DRAWINGS DOUBLE UP AND HORIZONTAL
+ 83 0x2560 \'╠\' BOX DRAWINGS DOUBLE VERTICAL AND RIGHT
+ 84 0x2566 \'╦\' BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL
+ 85 0x255A \'╚\' BOX DRAWINGS DOUBLE UP AND RIGHT
+ 86 0x2554 \'╔\' BOX DRAWINGS DOUBLE DOWN AND RIGHT
+ 87 0x2557 \'╗\' BOX DRAWINGS DOUBLE DOWN AND LEFT
+ 88 0x255D \'╝\' BOX DRAWINGS DOUBLE UP AND LEFT
+ 89 0x00 \'\\x00\'
+ 90 0x00 \'\\x00\'
+ 91 0x00 \'\\x00\'
+ 92 0x00 \'\\x00\'
+ 93 0x00 \'\\x00\'
+ 94 0x00 \'\\x00\'
+ 95 0x00 \'\\x00\'
+ 96 0x41 \'A\' LATIN CAPITAL LETTER A
+ 97 0x42 \'B\' LATIN CAPITAL LETTER B
+ 98 0x43 \'C\' LATIN CAPITAL LETTER C
+ 99 0x44 \'D\' LATIN CAPITAL LETTER D
+ 100 0x45 \'E\' LATIN CAPITAL LETTER E
+ 101 0x46 \'F\' LATIN CAPITAL LETTER F
+ 102 0x47 \'G\' LATIN CAPITAL LETTER G
+ 103 0x48 \'H\' LATIN CAPITAL LETTER H
+ 104 0x49 \'I\' LATIN CAPITAL LETTER I
+ 105 0x4A \'J\' LATIN CAPITAL LETTER J
+ 106 0x4B \'K\' LATIN CAPITAL LETTER K
+ 107 0x4C \'L\' LATIN CAPITAL LETTER L
+ 108 0x4D \'M\' LATIN CAPITAL LETTER M
+ 109 0x4E \'N\' LATIN CAPITAL LETTER N
+ 110 0x4F \'O\' LATIN CAPITAL LETTER O
+ 111 0x50 \'P\' LATIN CAPITAL LETTER P
+ 112 0x51 \'Q\' LATIN CAPITAL LETTER Q
+ 113 0x52 \'R\' LATIN CAPITAL LETTER R
+ 114 0x53 \'S\' LATIN CAPITAL LETTER S
+ 115 0x54 \'T\' LATIN CAPITAL LETTER T
+ 116 0x55 \'U\' LATIN CAPITAL LETTER U
+ 117 0x56 \'V\' LATIN CAPITAL LETTER V
+ 118 0x57 \'W\' LATIN CAPITAL LETTER W
+ 119 0x58 \'X\' LATIN CAPITAL LETTER X
+ 120 0x59 \'Y\' LATIN CAPITAL LETTER Y
+ 121 0x5A \'Z\' LATIN CAPITAL LETTER Z
+ 122 0x00 \'\\x00\'
+ 123 0x00 \'\\x00\'
+ 124 0x00 \'\\x00\'
+ 125 0x00 \'\\x00\'
+ 126 0x00 \'\\x00\'
+ 127 0x00 \'\\x00\'
+ 128 0x61 \'a\' LATIN SMALL LETTER A
+ 129 0x62 \'b\' LATIN SMALL LETTER B
+ 130 0x63 \'c\' LATIN SMALL LETTER C
+ 131 0x64 \'d\' LATIN SMALL LETTER D
+ 132 0x65 \'e\' LATIN SMALL LETTER E
+ 133 0x66 \'f\' LATIN SMALL LETTER F
+ 134 0x67 \'g\' LATIN SMALL LETTER G
+ 135 0x68 \'h\' LATIN SMALL LETTER H
+ 136 0x69 \'i\' LATIN SMALL LETTER I
+ 137 0x6A \'j\' LATIN SMALL LETTER J
+ 138 0x6B \'k\' LATIN SMALL LETTER K
+ 139 0x6C \'l\' LATIN SMALL LETTER L
+ 140 0x6D \'m\' LATIN SMALL LETTER M
+ 141 0x6E \'n\' LATIN SMALL LETTER N
+ 142 0x6F \'o\' LATIN SMALL LETTER O
+ 143 0x70 \'p\' LATIN SMALL LETTER P
+ 144 0x71 \'q\' LATIN SMALL LETTER Q
+ 145 0x72 \'r\' LATIN SMALL LETTER R
+ 146 0x73 \'s\' LATIN SMALL LETTER S
+ 147 0x74 \'t\' LATIN SMALL LETTER T
+ 148 0x75 \'u\' LATIN SMALL LETTER U
+ 149 0x76 \'v\' LATIN SMALL LETTER V
+ 150 0x77 \'w\' LATIN SMALL LETTER W
+ 151 0x78 \'x\' LATIN SMALL LETTER X
+ 152 0x79 \'y\' LATIN SMALL LETTER Y
+ 153 0x7A \'z\' LATIN SMALL LETTER Z
+ 154 0x00 \'\\x00\'
+ 155 0x00 \'\\x00\'
+ 156 0x00 \'\\x00\'
+ 157 0x00 \'\\x00\'
+ 158 0x00 \'\\x00\'
+ 159 0x00 \'\\x00\'
+============ ========= ========= ===========================================
diff --git a/docs/tcod/console.rst b/docs/tcod/console.rst
new file mode 100644
index 00000000..04ad7672
--- /dev/null
+++ b/docs/tcod/console.rst
@@ -0,0 +1,5 @@
+Tile Drawing/Printing ``tcod.console``
+======================================
+
+.. automodule:: tcod.console
+ :members:
diff --git a/docs/tcod/context.rst b/docs/tcod/context.rst
new file mode 100644
index 00000000..308f9a19
--- /dev/null
+++ b/docs/tcod/context.rst
@@ -0,0 +1,5 @@
+Window Management ``tcod.context``
+==================================
+
+.. automodule:: tcod.context
+ :members:
diff --git a/docs/tcod/event.rst b/docs/tcod/event.rst
new file mode 100644
index 00000000..1fd5c7bb
--- /dev/null
+++ b/docs/tcod/event.rst
@@ -0,0 +1,39 @@
+SDL Event Handling ``tcod.event``
+==================================
+
+.. automodule:: tcod.event
+ :members:
+ :inherited-members: object, int, str, tuple
+ :member-order: bysource
+ :exclude-members:
+ KeySym, Scancode, Modifier, get, wait
+
+
+Getting events
+--------------
+
+The primary way to capture events is with the :any:`tcod.event.get` and :any:`tcod.event.wait` functions.
+These functions return events in a loop until the internal event queue is empty.
+Use :func:`isinstance`, :any:`tcod.event.EventDispatch`, or `match statements `_
+(introduced in Python 3.10) to determine which event was returned.
+
+.. autofunction:: tcod.event.get
+.. autofunction:: tcod.event.wait
+
+Keyboard Enums
+--------------
+
+- :class:`KeySym`: Keys based on their glyph.
+- :class:`Scancode`: Keys based on their physical location.
+- :class:`Modifier`: Keyboard modifier keys.
+
+.. autoclass:: KeySym
+ :members:
+
+.. autoclass:: Scancode
+ :members:
+
+.. autoclass:: Modifier
+ :members:
+ :member-order: bysource
+ :undoc-members:
diff --git a/docs/tcod/getting-started.rst b/docs/tcod/getting-started.rst
new file mode 100644
index 00000000..aabb4f7f
--- /dev/null
+++ b/docs/tcod/getting-started.rst
@@ -0,0 +1,122 @@
+.. _getting-started:
+
+Getting Started
+===============
+
+Python 3 and python-tcod must be installed, see :ref:`installation`.
+
+Fixed-size console
+------------------
+
+This example is a hello world script which handles font loading,
+fixed-sized consoles, window contexts, and event handling.
+This example requires the
+`dejavu10x10_gs_tc.png `_
+font to be in the same directory as the script.
+
+By default this will create a window which can be resized and the fixed-size
+console will be stretched to fit the window. You can add arguments to
+:any:`Context.present` to fix the aspect ratio or only scale the console by
+integer increments.
+
+.. code-block:: python
+
+ #!/usr/bin/env python
+ # Make sure 'dejavu10x10_gs_tc.png' is in the same directory as this script.
+ import tcod.console
+ import tcod.context
+ import tcod.event
+ import tcod.tileset
+
+ WIDTH, HEIGHT = 80, 60 # Console width and height in tiles.
+
+
+ def main() -> None:
+ """Script entry point."""
+ # Load the font, a 32 by 8 tile font with libtcod's old character layout.
+ tileset = tcod.tileset.load_tilesheet(
+ "dejavu10x10_gs_tc.png", 32, 8, tcod.tileset.CHARMAP_TCOD,
+ )
+ # Create the main console.
+ console = tcod.console.Console(WIDTH, HEIGHT)
+ # Create a window based on this console and tileset.
+ with tcod.context.new( # New window for a console of size columns×rows.
+ columns=console.width, rows=console.height, tileset=tileset,
+ ) as context:
+ while True: # Main loop, runs until SystemExit is raised.
+ console.clear()
+ console.print(x=0, y=0, text="Hello World!")
+ context.present(console) # Show the console.
+
+ # This event loop will wait until at least one event is processed before exiting.
+ # For a non-blocking event loop replace `tcod.event.wait` with `tcod.event.get`.
+ for event in tcod.event.wait():
+ context.convert_event(event) # Sets tile coordinates for mouse events.
+ print(event) # Print event names and attributes.
+ match event:
+ case tcod.event.Quit():
+ raise SystemExit
+ # The window will be closed after the above with-block exits.
+
+
+ if __name__ == "__main__":
+ main()
+
+Dynamically-sized console
+-------------------------
+
+The next example shows a more advanced setup. A maximized window is created
+and the console is dynamically scaled to fit within it. If the window is
+resized then the console will be resized to match it.
+
+Because a tileset wasn't manually loaded in this example an OS dependent
+fallback font will be used. This is useful for prototyping but it's not
+recommended to release with this font since it can fail to load on some
+platforms.
+
+The `integer_scaling` parameter to :any:`Context.present` prevents the console
+from being slightly stretched, since the console will rarely be the prefect
+size a small border will exist. This border is black by default but can be
+changed to another color.
+
+You'll need to consider things like the console being too small for your code
+to handle or the tiles being small compared to an extra large monitor
+resolution. :any:`Context.new_console` can be given a minimum size that it
+will never go below.
+
+You can call :any:`Context.new_console` every frame or only when the window
+is resized. This example creates a new console every frame instead of
+clearing the console every frame and replacing it only on resizing the window.
+
+.. code-block:: python
+
+ #!/usr/bin/env python
+ import tcod.context
+ import tcod.event
+
+ WIDTH, HEIGHT = 720, 480 # Window pixel resolution (when not maximized.)
+ FLAGS = tcod.context.SDL_WINDOW_RESIZABLE | tcod.context.SDL_WINDOW_MAXIMIZED
+
+
+ def main() -> None:
+ """Script entry point."""
+ with tcod.context.new( # New window with pixel resolution of width×height.
+ width=WIDTH, height=HEIGHT, sdl_window_flags=FLAGS
+ ) as context:
+ while True:
+ console = context.new_console() # Console size based on window resolution and tile size.
+ console.print(0, 0, "Hello World")
+ context.present(console, integer_scaling=True)
+
+ for event in tcod.event.wait():
+ event = context.convert_event(event) # Sets tile coordinates for mouse events.
+ print(event) # Print event names and attributes.
+ match event:
+ case tcod.event.Quit():
+ raise SystemExit
+ case tcod.event.WindowResized(width=width, height=height): # Size in pixels
+ pass # The next call to context.new_console may return a different size.
+
+
+ if __name__ == "__main__":
+ main()
diff --git a/docs/tcod/image.rst b/docs/tcod/image.rst
new file mode 100644
index 00000000..0d4aaecc
--- /dev/null
+++ b/docs/tcod/image.rst
@@ -0,0 +1,5 @@
+Image Handling ``tcod.image``
+=============================
+
+.. automodule:: tcod.image
+ :members:
diff --git a/docs/tcod/los.rst b/docs/tcod/los.rst
new file mode 100644
index 00000000..bb82265a
--- /dev/null
+++ b/docs/tcod/los.rst
@@ -0,0 +1,5 @@
+Line of Sight ``tcod.los``
+==========================
+
+.. automodule:: tcod.los
+ :members:
diff --git a/docs/tcod/map.rst b/docs/tcod/map.rst
new file mode 100644
index 00000000..1a02a9a0
--- /dev/null
+++ b/docs/tcod/map.rst
@@ -0,0 +1,5 @@
+Field of View ``tcod.map``
+==========================
+
+.. automodule:: tcod.map
+ :members:
diff --git a/docs/tcod/noise.rst b/docs/tcod/noise.rst
new file mode 100644
index 00000000..59c23165
--- /dev/null
+++ b/docs/tcod/noise.rst
@@ -0,0 +1,6 @@
+Noise Map Generators ``tcod.noise``
+===================================
+
+.. automodule:: tcod.noise
+ :members:
+ :member-order: bysource
diff --git a/docs/tcod/path.rst b/docs/tcod/path.rst
new file mode 100644
index 00000000..7d5f3a79
--- /dev/null
+++ b/docs/tcod/path.rst
@@ -0,0 +1,5 @@
+Pathfinding ``tcod.path``
+=========================
+
+.. automodule:: tcod.path
+ :members:
diff --git a/docs/tcod/random.rst b/docs/tcod/random.rst
new file mode 100644
index 00000000..2d7c035d
--- /dev/null
+++ b/docs/tcod/random.rst
@@ -0,0 +1,5 @@
+Random Number Generators ``tcod.random``
+========================================
+
+.. automodule:: tcod.random
+ :members:
diff --git a/docs/tcod/render.rst b/docs/tcod/render.rst
new file mode 100644
index 00000000..547a6d81
--- /dev/null
+++ b/docs/tcod/render.rst
@@ -0,0 +1,5 @@
+Console Rendering Extension ``tcod.render``
+===========================================
+
+.. automodule:: tcod.render
+ :members:
diff --git a/docs/tcod/tileset.rst b/docs/tcod/tileset.rst
new file mode 100644
index 00000000..57b2cc3c
--- /dev/null
+++ b/docs/tcod/tileset.rst
@@ -0,0 +1,5 @@
+Font Loading Functions ``tcod.tileset``
+=======================================
+
+.. automodule:: tcod.tileset
+ :members:
diff --git a/docs/tdl.rst b/docs/tdl.rst
deleted file mode 100644
index 46eb9223..00000000
--- a/docs/tdl.rst
+++ /dev/null
@@ -1,47 +0,0 @@
-
-.. currentmodule:: tdl
-
-tdl
-===
-.. automodule:: tdl
-
-tdl API
--------------
-.. autofunction:: tdl.set_font
-.. autofunction:: tdl.init
-.. autofunction:: tdl.flush
-.. autofunction:: tdl.screenshot
-.. autofunction:: tdl.get_fullscreen
-.. autofunction:: tdl.set_fullscreen
-.. autofunction:: tdl.set_title
-.. autofunction:: tdl.get_fps
-.. autofunction:: tdl.set_fps
-.. autofunction:: tdl.force_resolution
-
-.. autoexception:: tdl.TDLError
-
-tdl.Console
------------
-.. autoclass:: tdl.Console
- :members:
- :inherited-members:
-
-tdl.Window
-----------
-.. autoclass:: tdl.Window
- :members:
-
-tdl.event
-=========
-.. automodule:: tdl.event
- :members:
-
-tdl.map
-=======
-.. automodule:: tdl.map
- :members:
-
-tdl.noise
-=========
-.. automodule:: tdl.noise
- :members:
diff --git a/docs/tutorial/index.rst b/docs/tutorial/index.rst
new file mode 100644
index 00000000..b43a8971
--- /dev/null
+++ b/docs/tutorial/index.rst
@@ -0,0 +1,16 @@
+Tutorial
+##############################################################################
+
+.. include:: notice.rst
+
+.. note::
+ This a Python tutorial reliant on a Modern ECS implementation.
+ In this case `tcod-ecs`_ will be used.
+ Most other Python ECS libraries do not support entity relationships and arbitrary tags required by this tutorial.
+ If you wish to use this tutorial with another language you may need a Modern ECS implementation on par with `Flecs`_.
+
+.. toctree::
+ :maxdepth: 1
+ :glob:
+
+ part-*
diff --git a/docs/tutorial/notice.rst b/docs/tutorial/notice.rst
new file mode 100644
index 00000000..707baf09
--- /dev/null
+++ b/docs/tutorial/notice.rst
@@ -0,0 +1,6 @@
+.. note::
+ This tutorial is still a work-in-progress.
+ `The resources being used are tracked here `_.
+ Feel free to discuss this tutorial or share your progress on the `Github Discussions`_ forum.
+
+.. _Github Discussions: https://github.com/libtcod/python-tcod/discussions
diff --git a/docs/tutorial/part-00.rst b/docs/tutorial/part-00.rst
new file mode 100644
index 00000000..a00a7fe9
--- /dev/null
+++ b/docs/tutorial/part-00.rst
@@ -0,0 +1,51 @@
+.. _part-0:
+
+Part 0 - Setting up a project
+##############################################################################
+
+.. include:: notice.rst
+
+Starting tools
+==============================================================================
+
+The IDE used for this tutorial is `Visual Studio Code `_ [#vscode]_ (not to be mistaken for Visual Studio).
+
+Git will be used for version control.
+`Follow the instructions here `_.
+
+Python 3.11 was used to make this tutorial.
+`Get the latest version of Python here `_.
+If there exists a version of Python later then 3.11 then install that version instead.
+
+
+First script
+==============================================================================
+
+First start with a modern top-level script.
+Create a script in the project root folder called ``main.py`` which checks :python:`if __name__ == "__main__":` and calls a ``main`` function.
+Any modern script using type-hinting will also have :python:`from __future__ import annotations` near the top.
+
+.. code-block:: python
+
+ from __future__ import annotations
+
+
+ def main() -> None:
+ print("Hello World!")
+
+
+ if __name__ == "__main__":
+ main()
+
+In VSCode on the left sidebar is a **Run and Debug** tab.
+On this tab select **create a launch.json** file.
+This will prompt about what kind of program to launch.
+Pick ``Python``, then ``Module``, then when asked for the module name type ``main``.
+From now on the :kbd:`F5` key will launch ``main.py`` in debug mode.
+
+Run the script now and ``Hello World!`` should be visible in the terminal output.
+
+.. rubric:: Footnotes
+
+.. [#vscode] Alternatives like `PyCharm `_ were considered,
+ but VSCode works the best with Git projects since workspace settings are portable and can be committed without issues.
diff --git a/docs/tutorial/part-01.rst b/docs/tutorial/part-01.rst
new file mode 100644
index 00000000..b18b7531
--- /dev/null
+++ b/docs/tutorial/part-01.rst
@@ -0,0 +1,312 @@
+.. _part-1:
+
+Part 1 - Moving a player around the screen
+##############################################################################
+
+.. include:: notice.rst
+
+In part 1 you will become familiar with the initialization, rendering, and event system of tcod.
+This will be done as a series of small implementations.
+It is recommend to save your progress after each section is finished and tested.
+
+Initial script
+==============================================================================
+
+First start with a modern top-level script.
+You should have ``main.py`` script from :ref:`part-0`:
+
+.. code-block:: python
+
+ from __future__ import annotations
+
+
+ def main() -> None:
+ ...
+
+
+ if __name__ == "__main__":
+ main()
+
+You will replace body of the ``main`` function in the following section.
+
+Loading a tileset and opening a window
+==============================================================================
+
+From here it is time to setup a ``tcod`` program.
+Download `Alloy_curses_12x12.png `_ [#tileset]_ and place this file in your projects ``data/`` directory.
+This tileset is from the `Dwarf Fortress tileset repository `_.
+These kinds of tilesets are always loaded with :python:`columns=16, rows=16, charmap=tcod.tileset.CHARMAP_CP437`.
+Use the string :python:`"data/Alloy_curses_12x12.png"` to refer to the path of the tileset. [#why_not_pathlib]_
+
+Load the tileset with :any:`tcod.tileset.load_tilesheet`.
+Pass the tileset to :any:`tcod.tileset.procedural_block_elements` which will fill in most `Block Elements `_ missing from `Code Page 437 `_.
+Then pass the tileset to :any:`tcod.context.new`, you only need to provide the ``tileset`` parameter.
+
+:any:`tcod.context.new` returns a :any:`Context` which will be used with Python's :python:`with` statement.
+We want to keep the name of the context, so use the syntax: :python:`with tcod.context.new(tileset=tileset) as context:`.
+The new block can't be empty, so add :python:`pass` to the with statement body.
+
+These functions are part of modules which have not been imported yet, so new imports for ``tcod.context`` and ``tcod.tileset`` must be added to the top of the script.
+
+.. code-block:: python
+ :emphasize-lines: 3,4,8-14
+
+ from __future__ import annotations
+
+ import tcod.context # Add these imports
+ import tcod.tileset
+
+
+ def main() -> None:
+ """Load a tileset and open a window using it, this window will immediately close."""
+ tileset = tcod.tileset.load_tilesheet(
+ "data/Alloy_curses_12x12.png", columns=16, rows=16, charmap=tcod.tileset.CHARMAP_CP437
+ )
+ tcod.tileset.procedural_block_elements(tileset=tileset)
+ with tcod.context.new(tileset=tileset) as context:
+ pass # The window will stay open for the duration of this block
+
+
+ if __name__ == "__main__":
+ main()
+
+If an import fails that means you do not have ``tcod`` installed on the Python environment you just used to run the script.
+If you use an IDE then make sure the Python environment it is using is correct and then run :shell:`pip install tcod` from the shell terminal within that IDE.
+
+There is no game loop, so if you run this script now then a window will open and then immediately close.
+If that happens without seeing a traceback in your terminal then the script is correct.
+
+Configuring an event loop
+==============================================================================
+
+The next step is to keep the window open until the user closes it.
+
+Since nothing is displayed yet a :any:`Console` should be created with :python:`"Hello World"` printed to it.
+The size of the console can be used as a reference to create the context by adding the console to :any:`tcod.context.new`. [#init_context]_
+
+Begin the main game loop with a :python:`while True:` statement.
+
+To actually display the console to the window the :any:`Context.present` method must be called with the console as a parameter.
+Do this first in the game loop before handing events.
+
+Events are checked by iterating over all pending events with :any:`tcod.event.wait`.
+Use the code :python:`for event in tcod.event.wait():` to begin handing events.
+
+In the event loop start with the line :python:`print(event)` so that all events can be viewed from the program output.
+Then test if an event is for closing the window with :python:`if isinstance(event, tcod.event.Quit):`.
+If this is True then you should exit the function with :python:`raise SystemExit`. [#why_raise]_
+
+.. code-block:: python
+ :emphasize-lines: 3,5,15-23
+
+ from __future__ import annotations
+
+ import tcod.console
+ import tcod.context
+ import tcod.event
+ import tcod.tileset
+
+
+ def main() -> None:
+ """Show "Hello World" until the window is closed."""
+ tileset = tcod.tileset.load_tilesheet(
+ "data/Alloy_curses_12x12.png", columns=16, rows=16, charmap=tcod.tileset.CHARMAP_CP437
+ )
+ tcod.tileset.procedural_block_elements(tileset=tileset)
+ console = tcod.console.Console(80, 50)
+ console.print(0, 0, "Hello World") # Test text by printing "Hello World" to the console
+ with tcod.context.new(console=console, tileset=tileset) as context:
+ while True: # Main loop
+ context.present(console) # Render the console to the window and show it
+ for event in tcod.event.wait(): # Event loop, blocks until pending events exist
+ print(event)
+ if isinstance(event, tcod.event.Quit):
+ raise SystemExit
+
+
+ if __name__ == "__main__":
+ main()
+
+If you run this then you get a window saying :python:`"Hello World"`.
+The window can be resized and the console will be stretched to fit the new resolution.
+When you do anything such as press a key or interact with the window the event for that action will be printed to the program output.
+
+An example game state
+==============================================================================
+
+What exists now is not very interactive.
+The next step is to change state based on user input.
+
+Like ``tcod`` you'll need to install ``attrs`` with Pip, such as with :shell:`pip install attrs`.
+
+Start by adding an ``attrs`` class called ``ExampleState``.
+This a normal class with the :python:`@attrs.define()` decorator added.
+
+This class should hold coordinates for the player.
+It should also have a ``on_draw`` method which takes :any:`tcod.console.Console` as a parameter and marks the player position on it.
+The parameters for ``on_draw`` are ``self`` because this is an instance method and :python:`console: tcod.console.Console`.
+``on_draw`` returns nothing, so be sure to add :python:`-> None`.
+
+:any:`Console.print` is the simplest way to draw the player because other options would require bounds-checking.
+Call this method using the players current coordinates and the :python:`"@"` character.
+
+.. code-block:: python
+ :emphasize-lines: 3,10-21
+
+ from __future__ import annotations
+
+ import attrs
+ import tcod.console
+ import tcod.context
+ import tcod.event
+ import tcod.tileset
+
+
+ @attrs.define()
+ class ExampleState:
+ """Example state with a hard-coded player position."""
+
+ player_x: int
+ """Player X position, left-most position is zero."""
+ player_y: int
+ """Player Y position, top-most position is zero."""
+
+ def on_draw(self, console: tcod.console.Console) -> None:
+ """Draw the player glyph."""
+ console.print(self.player_x, self.player_y, "@")
+
+ ...
+
+Now remove the :python:`console.print(0, 0, "Hello World")` line from ``main``.
+
+Before the context is made create a new ``ExampleState`` with player coordinates on the screen.
+Each :any:`Console` has :python:`.width` and :python:`.height` attributes which you can divide by 2 to get a centered coordinate for the player.
+Use Python's floor division operator :python:`//` so that the resulting type is :python:`int`.
+
+Modify the drawing routine so that the console is cleared, then passed to :python:`ExampleState.on_draw`, then passed to :any:`Context.present`.
+
+.. code-block:: python
+ :emphasize-lines: 9,12-14
+
+ ...
+ def main() -> None:
+ """Run ExampleState."""
+ tileset = tcod.tileset.load_tilesheet(
+ "data/Alloy_curses_12x12.png", columns=16, rows=16, charmap=tcod.tileset.CHARMAP_CP437
+ )
+ tcod.tileset.procedural_block_elements(tileset=tileset)
+ console = tcod.console.Console(80, 50)
+ state = ExampleState(player_x=console.width // 2, player_y=console.height // 2)
+ with tcod.context.new(console=console, tileset=tileset) as context:
+ while True:
+ console.clear() # Clear the console before any drawing
+ state.on_draw(console) # Draw the current state
+ context.present(console) # Display the console on the window
+ for event in tcod.event.wait():
+ print(event)
+ if isinstance(event, tcod.event.Quit):
+ raise SystemExit
+
+
+ if __name__ == "__main__":
+ main()
+
+Now if you run the script you'll see ``@``.
+
+The next step is to move the player on events.
+A new method will be added to the ``ExampleState`` for this called ``on_event``.
+``on_event`` takes a ``self`` and a :any:`tcod.event.Event` parameter and returns nothing.
+
+Events are best handled using Python's `Structural Pattern Matching `_.
+Consider reading `Python's Structural Pattern Matching Tutorial `_.
+
+Begin matching with :python:`match event:`.
+The equivalent to :python:`if isinstance(event, tcod.event.Quit):` is :python:`case tcod.event.Quit():`.
+Keyboard keys can be checked with :python:`case tcod.event.KeyDown(sym=tcod.event.KeySym.LEFT):`.
+Make a case for each arrow key: ``LEFT`` ``RIGHT`` ``UP`` ``DOWN`` and move the player in the direction of that key.
+Since events are printed you can check the :any:`KeySym` of a key by pressing that key and looking at the printed output.
+See :any:`KeySym` for a list of all keys.
+
+Finally replace the event handling code in ``main`` to defer to the states ``on_event`` method.
+The full script so far is:
+
+.. code-block:: python
+ :emphasize-lines: 23-35,53
+
+ from __future__ import annotations
+
+ import attrs
+ import tcod.console
+ import tcod.context
+ import tcod.event
+ import tcod.tileset
+
+
+ @attrs.define()
+ class ExampleState:
+ """Example state with a hard-coded player position."""
+
+ player_x: int
+ """Player X position, left-most position is zero."""
+ player_y: int
+ """Player Y position, top-most position is zero."""
+
+ def on_draw(self, console: tcod.console.Console) -> None:
+ """Draw the player glyph."""
+ console.print(self.player_x, self.player_y, "@")
+
+ def on_event(self, event: tcod.event.Event) -> None:
+ """Move the player on events and handle exiting. Movement is hard-coded."""
+ match event:
+ case tcod.event.Quit():
+ raise SystemExit
+ case tcod.event.KeyDown(sym=tcod.event.KeySym.LEFT):
+ self.player_x -= 1
+ case tcod.event.KeyDown(sym=tcod.event.KeySym.RIGHT):
+ self.player_x += 1
+ case tcod.event.KeyDown(sym=tcod.event.KeySym.UP):
+ self.player_y -= 1
+ case tcod.event.KeyDown(sym=tcod.event.KeySym.DOWN):
+ self.player_y += 1
+
+
+ def main() -> None:
+ """Run ExampleState."""
+ tileset = tcod.tileset.load_tilesheet(
+ "data/Alloy_curses_12x12.png", columns=16, rows=16, charmap=tcod.tileset.CHARMAP_CP437
+ )
+ tcod.tileset.procedural_block_elements(tileset=tileset)
+ console = tcod.console.Console(80, 50)
+ state = ExampleState(player_x=console.width // 2, player_y=console.height // 2)
+ with tcod.context.new(console=console, tileset=tileset) as context:
+ while True:
+ console.clear()
+ state.on_draw(console)
+ context.present(console)
+ for event in tcod.event.wait():
+ print(event)
+ state.on_event(event) # Pass events to the state
+
+
+ if __name__ == "__main__":
+ main()
+
+Now when you run this script you have a player character you can move around with the arrow keys before closing the window.
+
+You can review the part-1 source code `here `_.
+
+.. rubric:: Footnotes
+
+.. [#tileset] The choice of tileset came down to what looked nice while also being square.
+ Other options such as using a BDF font were considered, but in the end this tutorial won't go too much into Unicode.
+
+.. [#why_not_pathlib]
+ :any:`pathlib` is not used because this example is too simple for that.
+ The working directory will always be the project root folder for the entire tutorial, including distributions.
+ :any:`pathlib` will be used later for saved games and configuration directories, and not for static data.
+
+.. [#init_context] This tutorial follows the setup for a fixed-size console.
+ The alternatives shown in :ref:`getting-started` are outside the scope of this tutorial.
+
+.. [#why_raise] You could use :python:`return` here to exit the ``main`` function and end the program, but :python:`raise SystemExit` is used because it will close the program from anywhere.
+ :python:`raise SystemExit` is also more useful to teach than :any:`sys.exit`.
diff --git a/docs/tutorial/part-02.rst b/docs/tutorial/part-02.rst
new file mode 100644
index 00000000..293ad055
--- /dev/null
+++ b/docs/tutorial/part-02.rst
@@ -0,0 +1,590 @@
+.. _part-2:
+
+Part 2 - Entities
+##############################################################################
+
+.. include:: notice.rst
+
+In part 2 entities will be added and a new state will be created to handle them.
+This part will also begin to split logic into multiple Python modules using a namespace called ``game``.
+
+Entities will be handled with an ECS implementation, in this case: `tcod-ecs`_.
+``tcod-ecs`` is a standalone package and is installed separately from ``tcod``.
+Use :shell:`pip install tcod-ecs` to install this package.
+
+Namespace package
+==============================================================================
+
+Create a new folder called ``game`` and inside the folder create a new python file named ``__init__.py``.
+``game/__init__.py`` only needs a docstring describing that it is a namespace package:
+
+.. code-block:: python
+
+ """Game namespace package."""
+
+This package will be used to organize new modules.
+
+Organizing globals
+==============================================================================
+
+There are a few variables which will need to be accessible from multiple modules.
+Any global variables which might be assigned from other modules will need to a tracked and handled with care.
+
+Create a new module: ``g.py`` [#g]_.
+This module is exceptional and will be placed at the top-level instead of in the ``game`` folder.
+
+In ``g.py`` import ``tcod.context`` and ``tcod.ecs``.
+
+``context`` from ``main.py`` will now be annotated in ``g.py`` by adding the line :python:`context: tcod.context.Context` by itself.
+Notice that is this only a type-hinted name and nothing is assigned to it.
+This means that type-checking will assume the variable always exists but using it before it is assigned will crash at run-time.
+
+``main.py`` should add :python:`import g` and replace the variables named ``context`` with ``g.context``.
+
+Then add the :python:`world: tcod.ecs.Registry` global to hold the ECS scope.
+
+It is important to document all variables placed in this module with docstrings.
+
+.. code-block:: python
+
+ """This module stores globally mutable variables used by this program."""
+
+ from __future__ import annotations
+
+ import tcod.context
+ import tcod.ecs
+
+ context: tcod.context.Context
+ """The window managed by tcod."""
+
+ world: tcod.ecs.Registry
+ """The active ECS registry and current session."""
+
+Ideally you should not overuse this module for too many things.
+When a variable can either be taken as a function parameter or accessed as a global then passing as a parameter is always preferable.
+
+ECS tags
+==============================================================================
+
+Create ``game/tags.py``.
+This will hold some sentinel values to be used as tags for ``tcod-ecs``.
+These tags can be anything that's both unique and unchanging, in this case Python strings are used.
+
+For example :python:`IsPlayer: Final = "IsPlayer"` will tag an object as being controlled by the player.
+The name is ``IsPlayer`` and string is the same as the name.
+The ``Final`` annotation clarifies that this a constant.
+Sentinel values for ``tcod-ecs`` are named like classes, similar to names like :python:`None` or :python:`False`.
+
+Repeat this for ``IsActor`` and ``IsItem`` tags.
+The ``game/tags.py`` module should look like this:
+
+.. code-block:: python
+
+ """Collection of common tags."""
+
+ from __future__ import annotations
+
+ from typing import Final
+
+ IsPlayer: Final = "IsPlayer"
+ """Entity is the player."""
+
+ IsActor: Final = "IsActor"
+ """Entity is an actor."""
+
+ IsItem: Final = "IsItem"
+ """Entity is an item."""
+
+ECS components
+==============================================================================
+
+Next is a new ``game/components.py`` module.
+This will hold the components for the graphics and position of entities.
+
+Start by adding an import for ``attrs``.
+The ability to easily design small classes which are frozen/immutable is important for working with ``tcod-ecs``.
+
+The first component will be a ``Position`` class.
+This class will be decorated with :python:`@attrs.define(frozen=True)`.
+For attributes this class will have :python:`x: int` and :python:`y: int`.
+
+It will be common to add vectors to a ``Position`` with code such as :python:`new_pos: Position = Position(0, 0) + (0, 1)`.
+Create the dunder method :python:`def __add__(self, direction: tuple[int, int]) -> Self:` to allow this syntax.
+Unpack the input with :python:`x, y = direction`.
+:python:`self.__class__` is the current class so :python:`self.__class__(self.x + x, self.y + y)` will create a new instance with the direction added to the previous values.
+
+The new class will look like this:
+
+.. code-block:: python
+
+ @attrs.define(frozen=True)
+ class Position:
+ """An entities position."""
+
+ x: int
+ y: int
+
+ def __add__(self, direction: tuple[int, int]) -> Self:
+ """Add a vector to this position."""
+ x, y = direction
+ return self.__class__(self.x + x, self.y + y)
+
+Because ``Position`` is immutable, ``tcod-ecs`` is able to reliably track changes to this component.
+Normally you can only query entities by which components they have.
+A callback can be registered with ``tcod-ecs`` to mirror component values as tags.
+This allows querying an entity by its exact position.
+
+Add :python:`import tcod.ecs.callbacks` and :python:`from tcod.ecs import Entity`.
+Then create the new function :python:`def on_position_changed(entity: Entity, old: Position | None, new: Position | None) -> None:` decorated with :python:`@tcod.ecs.callbacks.register_component_changed(component=Position)`.
+This function is called when the ``Position`` component is either added, removed, or modified by assignment.
+The goal of this function is to mirror the current position to the :class:`set`-like attribute ``entity.tags``.
+
+:python:`if old == new:` then a position was assigned its own value or an equivalent value.
+The cost of discarding and adding the same value can sometimes be high so this case should be guarded and ignored.
+:python:`if old is not None:` then the value tracked by ``entity.tags`` is outdated and must be removed.
+:python:`if new is not None:` then ``new`` is the up-to-date value to be tracked by ``entity.tags``.
+
+The function should look like this:
+
+.. code-block:: python
+
+ @tcod.ecs.callbacks.register_component_changed(component=Position)
+ def on_position_changed(entity: Entity, old: Position | None, new: Position | None) -> None:
+ """Mirror position components as a tag."""
+ if old == new: # New position is equivalent to its previous value
+ return # Ignore and return
+ if old is not None: # Position component removed or changed
+ entity.tags.discard(old) # Remove old position from tags
+ if new is not None: # Position component added or changed
+ entity.tags.add(new) # Add new position to tags
+
+Next is the ``Graphic`` component.
+This will have the attributes :python:`ch: int = ord("!")` and :python:`fg: tuple[int, int, int] = (255, 255, 255)`.
+By default all new components should be marked as frozen.
+
+.. code-block:: python
+
+ @attrs.define(frozen=True)
+ class Graphic:
+ """An entities icon and color."""
+
+ ch: int = ord("!")
+ fg: tuple[int, int, int] = (255, 255, 255)
+
+One last component: ``Gold``.
+Define this as :python:`Gold: Final = ("Gold", int)`.
+``(name, type)`` is tcod-ecs specific syntax to handle multiple components sharing the same type.
+
+.. code-block:: python
+
+ Gold: Final = ("Gold", int)
+ """Amount of gold."""
+
+That was the last component.
+The ``game/components.py`` module should look like this:
+
+.. code-block:: python
+
+ """Collection of common components."""
+
+ from __future__ import annotations
+
+ from typing import Final, Self
+
+ import attrs
+ import tcod.ecs.callbacks
+ from tcod.ecs import Entity
+
+
+ @attrs.define(frozen=True)
+ class Position:
+ """An entities position."""
+
+ x: int
+ y: int
+
+ def __add__(self, direction: tuple[int, int]) -> Self:
+ """Add a vector to this position."""
+ x, y = direction
+ return self.__class__(self.x + x, self.y + y)
+
+
+ @tcod.ecs.callbacks.register_component_changed(component=Position)
+ def on_position_changed(entity: Entity, old: Position | None, new: Position | None) -> None:
+ """Mirror position components as a tag."""
+ if old == new:
+ return
+ if old is not None:
+ entity.tags.discard(old)
+ if new is not None:
+ entity.tags.add(new)
+
+
+ @attrs.define(frozen=True)
+ class Graphic:
+ """An entities icon and color."""
+
+ ch: int = ord("!")
+ fg: tuple[int, int, int] = (255, 255, 255)
+
+
+ Gold: Final = ("Gold", int)
+ """Amount of gold."""
+
+ECS entities and registry
+==============================================================================
+
+Now it is time to create entities.
+To do that you need to create the ECS registry.
+
+Make a new script called ``game/world_tools.py``.
+This module will be used to create the ECS registry.
+
+Random numbers from :mod:`random` will be used.
+In this case we want to use ``Random`` as a component so add :python:`from random import Random`.
+Get the registry with :python:`from tcod.ecs import Registry`.
+Collect all our components and tags with :python:`from game.components import Gold, Graphic, Position` and :python:`from game.tags import IsActor, IsItem, IsPlayer`.
+
+This module will have one function: :python:`def new_world() -> Registry:`.
+Think of the ECS registry as containing the world since this is how it will be used.
+Start this function with :python:`world = Registry()`.
+
+Entities are referenced with the syntax :python:`world[unique_id]`.
+If the same ``unique_id`` is used then you will access the same entity.
+:python:`new_entity = world[object()]` is the syntax to spawn new entities because ``object()`` is always unique.
+Whenever a global entity is needed then :python:`world[None]` will be used.
+
+Create an instance of :python:`Random()` and assign it to both :python:`world[None].components[Random]` and ``rng``.
+This can done on one line with :python:`rng = world[None].components[Random] = Random()`.
+
+Next create the player entity with :python:`player = world[object()]`.
+Assign the following components to the new player entity: :python:`player.components[Position] = Position(5, 5)`, :python:`player.components[Graphic] = Graphic(ord("@"))`, and :python:`player.components[Gold] = 0`.
+Then update the players tags with :python:`player.tags |= {IsPlayer, IsActor}`.
+
+To add some variety we will scatter gold randomly across the world.
+Start a for-loop with :python:`for _ in range(10):` then create a ``gold`` entity in this loop.
+
+The ``Random`` instance ``rng`` has access to functions from Python's random module such as :any:`random.randint`.
+Set ``Position`` to :python:`Position(rng.randint(0, 20), rng.randint(0, 20))`.
+Set ``Graphic`` to :python:`Graphic(ord("$"), fg=(255, 255, 0))`.
+Set ``Gold`` to :python:`rng.randint(1, 10)`.
+Then add ``IsItem`` as a tag.
+
+Once the for-loop exits then :python:`return world`.
+Make sure :python:`return` has the correct indentation and is not part of the for-loop or else you will only spawn one gold.
+
+``game/world_tools.py`` should look like this:
+
+.. code-block:: python
+
+ """Functions for working with worlds."""
+
+ from __future__ import annotations
+
+ from random import Random
+
+ from tcod.ecs import Registry
+
+ from game.components import Gold, Graphic, Position
+ from game.tags import IsActor, IsItem, IsPlayer
+
+
+ def new_world() -> Registry:
+ """Return a freshly generated world."""
+ world = Registry()
+
+ rng = world[None].components[Random] = Random()
+
+ player = world[object()]
+ player.components[Position] = Position(5, 5)
+ player.components[Graphic] = Graphic(ord("@"))
+ player.components[Gold] = 0
+ player.tags |= {IsPlayer, IsActor}
+
+ for _ in range(10):
+ gold = world[object()]
+ gold.components[Position] = Position(rng.randint(0, 20), rng.randint(0, 20))
+ gold.components[Graphic] = Graphic(ord("$"), fg=(255, 255, 0))
+ gold.components[Gold] = rng.randint(1, 10)
+ gold.tags |= {IsItem}
+
+ return world
+
+New InGame state
+==============================================================================
+
+Now there is a new ECS world but the example state does not know how to render it.
+A new state needs to be made which is aware of the new entities.
+
+Before adding a new state it is time to add a more complete set of directional keys.
+Create a new module called ``game/constants.py``.
+Keys will be mapped to direction using a dictionary which can be reused anytime we want to know how a key translates to a direction.
+Use :python:`from tcod.event import KeySym` to make ``KeySym`` enums easier to write.
+
+``game/constants.py`` should look like this:
+
+.. code-block:: python
+
+ """Global constants are stored here."""
+
+ from __future__ import annotations
+
+ from typing import Final
+
+ from tcod.event import KeySym
+
+ DIRECTION_KEYS: Final = {
+ # Arrow keys
+ KeySym.LEFT: (-1, 0),
+ KeySym.RIGHT: (1, 0),
+ KeySym.UP: (0, -1),
+ KeySym.DOWN: (0, 1),
+ # Arrow key diagonals
+ KeySym.HOME: (-1, -1),
+ KeySym.END: (-1, 1),
+ KeySym.PAGEUP: (1, -1),
+ KeySym.PAGEDOWN: (1, 1),
+ # Keypad
+ KeySym.KP_4: (-1, 0),
+ KeySym.KP_6: (1, 0),
+ KeySym.KP_8: (0, -1),
+ KeySym.KP_2: (0, 1),
+ KeySym.KP_7: (-1, -1),
+ KeySym.KP_1: (-1, 1),
+ KeySym.KP_9: (1, -1),
+ KeySym.KP_3: (1, 1),
+ # VI keys
+ KeySym.h: (-1, 0),
+ KeySym.l: (1, 0),
+ KeySym.k: (0, -1),
+ KeySym.j: (0, 1),
+ KeySym.y: (-1, -1),
+ KeySym.b: (-1, 1),
+ KeySym.u: (1, -1),
+ KeySym.n: (1, 1),
+ }
+
+Create a new module called ``game/states.py``.
+``states`` is for derived classes, ``state`` is for the abstract class.
+New states will be created in this module and this module will be allowed to import many first party modules without issues.
+
+Create a new :python:`class InGame:` decorated with :python:`@attrs.define()`.
+States will always use ``g.world`` to access the ECS registry.
+
+.. code-block:: python
+
+ @attrs.define()
+ class InGame:
+ """Primary in-game state."""
+ ...
+
+Create an ``on_event`` and ``on_draw`` method matching the ``ExampleState`` class.
+Copying ``ExampleState`` and modifying it should be enough since this wil replace ``ExampleState``.
+
+Now to do an tcod-ecs query to fetch the player entity.
+In tcod-ecs queries most often start with :python:`g.world.Q.all_of(components=[], tags=[])`.
+Which components and tags are asked for will narrow down the returned set of entities to only those matching the requirements.
+The query to fetch player entities is :python:`g.world.Q.all_of(tags=[IsPlayer])`.
+We expect only one player so the result will be unpacked into a single name: :python:`(player,) = g.world.Q.all_of(tags=[IsPlayer])`.
+
+Next is to handle the event.
+Handling :python:`case tcod.event.Quit():` is the same as before: :python:`raise SystemExit`.
+
+The case for direction keys will now be done in a single case: :python:`case tcod.event.KeyDown(sym=sym) if sym in DIRECTION_KEYS:`.
+``sym=sym`` assigns from the event attribute to a local name.
+The left side is the ``event.sym`` attribute and right side is the local name ``sym`` being assigned to.
+The case also has a condition which must pass for this branch to be taken and in this case we ensure that only keys from the ``DIRECTION_KEYS`` dictionary are valid ``sym``'s.
+
+Inside this branch moving the player is simple.
+Access the ``(x, y)`` vector with :python:`DIRECTION_KEYS[sym]` and use ``+=`` to add it to the players current ``Position`` component.
+This triggers the earlier written ``__add__`` dunder method and ``on_position_changed`` callback.
+
+Now that the player has moved it would be a good time to interact with the gold entities.
+The query to see if the player has stepped on gold is to check for whichever entities have a ``Gold`` component, an ``IsItem`` tag, and the players current position as a tag.
+The query for this is :python:`g.world.Q.all_of(components=[Gold], tags=[player.components[Position], IsItem]):`.
+
+We will iterate over whatever matches this query using a :python:`for gold in ...:` loop.
+Add the entities ``Gold`` component to the players similar component.
+Keep in mind that ``Gold`` is treated like an ``int`` so its usage is predictable.
+
+Format the added and total of gold using a Python f-string_: :python:`text = f"Picked up {gold.components[Gold]}g, total: {player.components[Gold]}g"`.
+Store ``text`` globally in the ECS registry with :python:`g.world[None].components[("Text", str)] = text`.
+This is done as two lines to avoid creating a line with an excessive length.
+
+Then use :python:`gold.clear()` at the end to remove all components and tags from the gold entity which will effectively delete it.
+
+.. code-block:: python
+
+ ...
+ def on_event(self, event: tcod.event.Event) -> None:
+ """Handle events for the in-game state."""
+ (player,) = g.world.Q.all_of(tags=[IsPlayer])
+ match event:
+ case tcod.event.Quit():
+ raise SystemExit
+ case tcod.event.KeyDown(sym=sym) if sym in DIRECTION_KEYS:
+ player.components[Position] += DIRECTION_KEYS[sym]
+ # Auto pickup gold
+ for gold in g.world.Q.all_of(components=[Gold], tags=[player.components[Position], IsItem]):
+ player.components[Gold] += gold.components[Gold]
+ text = f"Picked up {gold.components[Gold]}g, total: {player.components[Gold]}g"
+ g.world[None].components[str] = text
+ gold.clear()
+ ...
+
+Now start with the ``on_draw`` method.
+Any entity with both a ``Position`` and a ``Graphic`` is drawable.
+Iterate over these entities with :python:`for entity in g.world.Q.all_of(components=[Position, Graphic]):`.
+Accessing components can be slow in a loop, so assign components to local names before using them (:python:`pos = entity.components[Position]` and :python:`graphic = entity.components[Graphic]`).
+
+Check if a components position is in the bounds of the console.
+:python:`0 <= pos.x < console.width and 0 <= pos.y < console.height` tells if the position is in bounds.
+Instead of nesting this method further, this check should be a guard using :python:`if not (...):` and :python:`continue`.
+
+Draw the graphic by assigning it to the consoles Numpy array directly with :python:`console.rgb[["ch", "fg"]][pos.y, pos.x] = graphic.ch, graphic.fg`.
+``console.rgb`` is a ``ch,fg,bg`` array and :python:`[["ch", "fg"]]` narrows it down to only ``ch,fg``.
+The array is in C row-major memory order so you access it with yx (or ij) ordering.
+
+That ends the entity rendering loop.
+Next is to print the ``("Text", str)`` component if it exists.
+A normal access will raise ``KeyError`` if the component is accessed before being assigned.
+This case will be handled by the ``.get`` method of the ``Entity.components`` attribute.
+:python:`g.world[None].components.get(("Text", str))` will return :python:`None` instead of raising ``KeyError``.
+Assigning this result to ``text`` and then checking :python:`if text:` will ensure that ``text`` within the branch is not None and that the string is not empty.
+We will not use ``text`` outside of the branch, so an assignment expression can be used here to check and assign the name at the same time with :python:`if text := g.world[None].components.get(("Text", str)):`.
+
+In this branch you will print ``text`` to the bottom of the console with a white foreground and black background.
+The call to do this is :python:`console.print(x=0, y=console.height - 1, string=text, fg=(255, 255, 255), bg=(0, 0, 0))`.
+
+.. code-block:: python
+
+ ...
+ def on_draw(self, console: tcod.console.Console) -> None:
+ """Draw the standard screen."""
+ for entity in g.world.Q.all_of(components=[Position, Graphic]):
+ pos = entity.components[Position]
+ if not (0 <= pos.x < console.width and 0 <= pos.y < console.height):
+ continue
+ graphic = entity.components[Graphic]
+ console.rgb[["ch", "fg"]][pos.y, pos.x] = graphic.ch, graphic.fg
+
+ if text := g.world[None].components.get(("Text", str)):
+ console.print(x=0, y=console.height - 1, string=text, fg=(255, 255, 255), bg=(0, 0, 0))
+
+Verify the indentation of the ``if`` branch is correct.
+It should be at the same level as the ``for`` loop and not inside of it.
+
+``game/states.py`` should now look like this:
+
+.. code-block:: python
+
+ """A collection of game states."""
+
+ from __future__ import annotations
+
+ import attrs
+ import tcod.console
+ import tcod.event
+
+ import g
+ from game.components import Gold, Graphic, Position
+ from game.constants import DIRECTION_KEYS
+ from game.tags import IsItem, IsPlayer
+
+
+ @attrs.define()
+ class InGame:
+ """Primary in-game state."""
+
+ def on_event(self, event: tcod.event.Event) -> None:
+ """Handle events for the in-game state."""
+ (player,) = g.world.Q.all_of(tags=[IsPlayer])
+ match event:
+ case tcod.event.Quit():
+ raise SystemExit
+ case tcod.event.KeyDown(sym=sym) if sym in DIRECTION_KEYS:
+ player.components[Position] += DIRECTION_KEYS[sym]
+ # Auto pickup gold
+ for gold in g.world.Q.all_of(components=[Gold], tags=[player.components[Position], IsItem]):
+ player.components[Gold] += gold.components[Gold]
+ text = f"Picked up {gold.components[Gold]}g, total: {player.components[Gold]}g"
+ g.world[None].components[("Text", str)] = text
+ gold.clear()
+
+ def on_draw(self, console: tcod.console.Console) -> None:
+ """Draw the standard screen."""
+ for entity in g.world.Q.all_of(components=[Position, Graphic]):
+ pos = entity.components[Position]
+ if not (0 <= pos.x < console.width and 0 <= pos.y < console.height):
+ continue
+ graphic = entity.components[Graphic]
+ console.rgb[["ch", "fg"]][pos.y, pos.x] = graphic.ch, graphic.fg
+
+ if text := g.world[None].components.get(("Text", str)):
+ console.print(x=0, y=console.height - 1, string=text, fg=(255, 255, 255), bg=(0, 0, 0))
+
+Main script update
+==============================================================================
+
+Back to ``main.py``.
+At this point you should know to import the modules needed.
+
+The ``ExampleState`` class is obsolete and will be removed.
+``state`` will be created with :python:`game.states.InGame()` instead.
+
+If you have not replaced ``context`` with ``g.context`` yet then do it now.
+
+Add :python:`g.world = game.world_tools.new_world()` before the main loop.
+
+``main.py`` will look like this:
+
+.. code-block:: python
+ :emphasize-lines: 10-12,22-24,28
+
+ #!/usr/bin/env python3
+ """Main entry-point module. This script is used to start the program."""
+
+ from __future__ import annotations
+
+ import tcod.console
+ import tcod.context
+ import tcod.event
+ import tcod.tileset
+
+ import g
+ import game.states
+ import game.world_tools
+
+
+ def main() -> None:
+ """Entry point function."""
+ tileset = tcod.tileset.load_tilesheet(
+ "data/Alloy_curses_12x12.png", columns=16, rows=16, charmap=tcod.tileset.CHARMAP_CP437
+ )
+ tcod.tileset.procedural_block_elements(tileset=tileset)
+ console = tcod.console.Console(80, 50)
+ state = game.states.InGame()
+ g.world = game.world_tools.new_world()
+ with tcod.context.new(console=console, tileset=tileset) as g.context:
+ while True: # Main loop
+ console.clear() # Clear the console before any drawing
+ state.on_draw(console) # Draw the current state
+ g.context.present(console) # Render the console to the window and show it
+ for event in tcod.event.wait(): # Event loop, blocks until pending events exist
+ print(event)
+ state.on_event(event) # Dispatch events to the state
+
+
+ if __name__ == "__main__":
+ main()
+
+Now you can play a simple game where you wander around collecting gold.
+
+You can review the part-2 source code `here `_.
+
+.. rubric:: Footnotes
+
+.. [#g] ``global``, ``globals``, and ``glob`` were already taken by keywords, built-ins, and the standard library.
+ The alternatives are to either put this in the ``game`` namespace or to add an underscore such as ``globals_.py``.
+
+.. _f-string: https://docs.python.org/3/tutorial/inputoutput.html#formatted-string-literals
diff --git a/docs/tutorial/part-03.rst b/docs/tutorial/part-03.rst
new file mode 100644
index 00000000..528a39ae
--- /dev/null
+++ b/docs/tutorial/part-03.rst
@@ -0,0 +1,459 @@
+.. _part-3:
+
+Part 3 - UI State
+##############################################################################
+
+.. include:: notice.rst
+
+.. warning::
+
+ **This part is still a draft and is being worked on.
+ Sections here will be incorrect as these examples were hastily moved from an earlier part.**
+
+
+State protocol
+==============================================================================
+
+To have more states than ``ExampleState`` one must use an abstract type which can be used to refer to any state.
+In this case a `Protocol`_ will be used, called ``State``.
+
+Create a new module: ``game/state.py``.
+In this module add the class :python:`class State(Protocol):`.
+``Protocol`` is from Python's ``typing`` module.
+``State`` should have the ``on_event`` and ``on_draw`` methods from ``ExampleState`` but these methods will be empty other than the docstrings describing what they are for.
+These methods refer to types from ``tcod`` and those types will need to be imported.
+``State`` should also have :python:`__slots__ = ()` [#slots]_ in case the class is used for a subclass.
+
+Now add a few small classes using :python:`@attrs.define()`:
+A ``Push`` class with a :python:`state: State` attribute.
+A ``Pop`` class with no attributes.
+A ``Reset`` class with a :python:`state: State` attribute.
+
+Then add a :python:`StateResult: TypeAlias = "Push | Pop | Reset | None"`.
+This is a type which combines all of the previous classes.
+
+Edit ``State``'s ``on_event`` method to return ``StateResult``.
+
+``game/state.py`` should look like this:
+
+.. code-block:: python
+
+ """Base classes for states."""
+
+ from __future__ import annotations
+
+ from typing import Protocol, TypeAlias
+
+ import attrs
+ import tcod.console
+ import tcod.event
+
+
+ class State(Protocol):
+ """An abstract game state."""
+
+ __slots__ = ()
+
+ def on_event(self, event: tcod.event.Event) -> StateResult:
+ """Called on events."""
+
+ def on_draw(self, console: tcod.console.Console) -> None:
+ """Called when the state is being drawn."""
+
+
+ @attrs.define()
+ class Push:
+ """Push a new state on top of the stack."""
+
+ state: State
+
+
+ @attrs.define()
+ class Pop:
+ """Remove the current state from the stack."""
+
+
+ @attrs.define()
+ class Reset:
+ """Replace the entire stack with a new state."""
+
+ state: State
+
+
+ StateResult: TypeAlias = "Push | Pop | Reset | None"
+ """Union of state results."""
+
+The ``InGame`` class does not need to be updated since it is already a structural subtype of ``State``.
+Note that subclasses of ``State`` will never be in same module as ``State``, this will be the same for all abstract classes.
+
+New globals
+==============================================================================
+
+A new global will be added: :python:`states: list[game.state.State] = []`.
+States are implemented as a list/stack to support `pushdown automata `_.
+Representing states as a stack makes it easier to implement popup windows, sub-menus, and other prompts.
+
+The ``console`` variable from ``main.py`` will be moved to ``g.py``.
+Add :python:`console: tcod.console.Console` and replace all references to ``console`` in ``main.py`` with ``g.console``.
+
+.. code-block:: python
+ :emphasize-lines: 9,17-21
+
+ """This module stores globally mutable variables used by this program."""
+
+ from __future__ import annotations
+
+ import tcod.console
+ import tcod.context
+ import tcod.ecs
+
+ import game.state
+
+ context: tcod.context.Context
+ """The window managed by tcod."""
+
+ world: tcod.ecs.Registry
+ """The active ECS registry and current session."""
+
+ states: list[game.state.State] = []
+ """A stack of states with the last item being the active state."""
+
+ console: tcod.console.Console
+ """The current main console."""
+
+
+State functions
+==============================================================================
+
+Create a new module: ``game/state_tools.py``.
+This module will handle events and rendering of the global state.
+
+In this module add the function :python:`def main_draw() -> None:`.
+This will hold the "clear, draw, present" logic from the ``main`` function which will be moved to this function.
+Render the active state with :python:`g.states[-1].on_draw(g.console)`.
+If ``g.states`` is empty then this function should immediately :python:`return` instead of doing anything.
+Empty containers in Python are :python:`False` when checked for truthiness.
+
+Next is to handle the ``StateResult`` type.
+Start by adding the :python:`def apply_state_result(result: StateResult) -> None:` function.
+This function will :python:`match result:` to decide on what to do.
+
+:python:`case Push(state=state):` should append ``state`` to ``g.states``.
+
+:python:`case Pop():` should simply call :python:`g.states.pop()`.
+
+:python:`case Reset(state=state):` should call :python:`apply_state_result(Pop())` until ``g.state`` is empty then call :python:`apply_state_result(Push(state))`.
+
+:python:`case None:` should be handled by explicitly ignoring it.
+
+:python:`case _:` handles anything else and should invoke :python:`raise TypeError(result)` since no other types are expected.
+
+Now the function :python:`def main_loop() -> None:` is created.
+The :python:`while` loop from ``main`` will be moved to this function.
+The while loop will be replaced by :python:`while g.states:` so that this function will exit if no state exists.
+Drawing will be replaced by a call to ``main_draw``.
+Events with mouse coordinates should be converted to tiles using :python:`tile_event = g.context.convert_event(event)` before being passed to a state.
+:python:`apply_state_result(g.states[-1].on_event(tile_event))` will pass the event and handle the return result at the same time.
+``g.states`` must be checked to be non-empty inside the event handing for-loop because ``apply_state_result`` could cause ``g.states`` to become empty.
+
+Next is the utility function :python:`def get_previous_state(state: State) -> State | None:`.
+Get the index of ``state`` in ``g.states`` by identity [#identity]_ using :python:`current_index = next(index for index, value in enumerate(g.states) if value is state)`.
+Return the previous state if :python:`current_index > 0` or else return None using :python:`return g.states[current_index - 1] if current_index > 0 else None`.
+
+Next is :python:`def draw_previous_state(state: State, console: tcod.console.Console, dim: bool = True) -> None:`.
+Call ``get_previous_state`` to get the previous state and return early if the result is :python:`None`.
+Then call the previous states :python:`State.on_draw` method as normal.
+Afterwards test :python:`dim and state is g.states[-1]` to see if the console should be dimmed.
+If it should be dimmed then reduce the color values of the console with :python:`console.rgb["fg"] //= 4` and :python:`console.rgb["bg"] //= 4`.
+This is used to indicate that any graphics behind the active state are non-interactable.
+
+
+.. code-block:: python
+
+ """State handling functions."""
+
+ from __future__ import annotations
+
+ import tcod.console
+
+ import g
+ from game.state import Pop, Push, Reset, StateResult
+
+
+ def main_draw() -> None:
+ """Render and present the active state."""
+ if not g.states:
+ return
+ g.console.clear()
+ g.states[-1].on_draw(g.console)
+ g.context.present(g.console)
+
+
+ def apply_state_result(result: StateResult) -> None:
+ """Apply a StateResult to `g.states`."""
+ match result:
+ case Push(state=state):
+ g.states.append(state)
+ case Pop():
+ g.states.pop()
+ case Reset(state=state):
+ while g.states:
+ apply_state_result(Pop())
+ apply_state_result(Push(state))
+ case None:
+ pass
+ case _:
+ raise TypeError(result)
+
+
+ def main_loop() -> None:
+ """Run the active state forever."""
+ while g.states:
+ main_draw()
+ for event in tcod.event.wait():
+ tile_event = g.context.convert_event(event)
+ if g.states:
+ apply_state_result(g.states[-1].on_event(tile_event))
+
+
+ def get_previous_state(state: State) -> State | None:
+ """Return the state before `state` in the stack if it exists."""
+ current_index = next(index for index, value in enumerate(g.states) if value is state)
+ return g.states[current_index - 1] if current_index > 0 else None
+
+
+ def draw_previous_state(state: State, console: tcod.console.Console, dim: bool = True) -> None:
+ """Draw previous states, optionally dimming all but the active state."""
+ prev_state = get_previous_state(state)
+ if prev_state is None:
+ return
+ prev_state.on_draw(console)
+ if dim and state is g.states[-1]:
+ console.rgb["fg"] //= 4
+ console.rgb["bg"] //= 4
+
+Menus
+==============================================================================
+
+.. code-block:: python
+
+ """Menu UI classes."""
+
+ from __future__ import annotations
+
+ from collections.abc import Callable
+ from typing import Protocol
+
+ import attrs
+ import tcod.console
+ import tcod.event
+ from tcod.event import KeySym
+
+ import game.state_tools
+ from game.constants import DIRECTION_KEYS
+ from game.state import Pop, State, StateResult
+
+
+ class MenuItem(Protocol):
+ """Menu item protocol."""
+
+ __slots__ = ()
+
+ def on_event(self, event: tcod.event.Event) -> StateResult:
+ """Handle events passed to the menu item."""
+
+ def on_draw(self, console: tcod.console.Console, x: int, y: int, highlight: bool) -> None:
+ """Draw is item at the given position."""
+
+
+ @attrs.define()
+ class SelectItem(MenuItem):
+ """Clickable menu item."""
+
+ label: str
+ callback: Callable[[], StateResult]
+
+ def on_event(self, event: tcod.event.Event) -> StateResult:
+ """Handle events selecting this item."""
+ match event:
+ case tcod.event.KeyDown(sym=sym) if sym in {KeySym.RETURN, KeySym.RETURN2, KeySym.KP_ENTER}:
+ return self.callback()
+ case tcod.event.MouseButtonUp(button=tcod.event.MouseButton.LEFT):
+ return self.callback()
+ case _:
+ return None
+
+ def on_draw(self, console: tcod.console.Console, x: int, y: int, highlight: bool) -> None:
+ """Render this items label."""
+ console.print(x, y, self.label, fg=(255, 255, 255), bg=(64, 64, 64) if highlight else (0, 0, 0))
+
+
+ @attrs.define()
+ class ListMenu(State):
+ """Simple list menu state."""
+
+ items: tuple[MenuItem, ...]
+ selected: int | None = 0
+ x: int = 0
+ y: int = 0
+
+ def on_event(self, event: tcod.event.Event) -> StateResult:
+ """Handle events for menus."""
+ match event:
+ case tcod.event.Quit():
+ raise SystemExit
+ case tcod.event.KeyDown(sym=sym) if sym in DIRECTION_KEYS:
+ dx, dy = DIRECTION_KEYS[sym]
+ if dx != 0 or dy == 0:
+ return self.activate_selected(event)
+ if self.selected is not None:
+ self.selected += dy
+ self.selected %= len(self.items)
+ else:
+ self.selected = 0 if dy == 1 else len(self.items) - 1
+ return None
+ case tcod.event.MouseMotion(position=(_, y)):
+ y -= self.y
+ self.selected = y if 0 <= y < len(self.items) else None
+ return None
+ case tcod.event.KeyDown(sym=KeySym.ESCAPE):
+ return self.on_cancel()
+ case tcod.event.MouseButtonUp(button=tcod.event.MouseButton.RIGHT):
+ return self.on_cancel()
+ case _:
+ return self.activate_selected(event)
+
+ def activate_selected(self, event: tcod.event.Event) -> StateResult:
+ """Call the selected menu items callback."""
+ if self.selected is not None:
+ return self.items[self.selected].on_event(event)
+ return None
+
+ def on_cancel(self) -> StateResult:
+ """Handle escape or right click being pressed on menus."""
+ return Pop()
+
+ def on_draw(self, console: tcod.console.Console) -> None:
+ """Render the menu."""
+ game.state_tools.draw_previous_state(self, console)
+ for i, item in enumerate(self.items):
+ item.on_draw(console, x=self.x, y=self.y + i, highlight=i == self.selected)
+
+Update states
+==============================================================================
+
+.. code-block:: python
+
+ class MainMenu(game.menus.ListMenu):
+ """Main/escape menu."""
+
+ __slots__ = ()
+
+ def __init__(self) -> None:
+ """Initialize the main menu."""
+ items = [
+ game.menus.SelectItem("New game", self.new_game),
+ game.menus.SelectItem("Quit", self.quit),
+ ]
+ if hasattr(g, "world"):
+ items.insert(0, game.menus.SelectItem("Continue", self.continue_))
+
+ super().__init__(
+ items=tuple(items),
+ selected=0,
+ x=5,
+ y=5,
+ )
+
+ @staticmethod
+ def continue_() -> StateResult:
+ """Return to the game."""
+ return Reset(InGame())
+
+ @staticmethod
+ def new_game() -> StateResult:
+ """Begin a new game."""
+ g.world = game.world_tools.new_world()
+ return Reset(InGame())
+
+ @staticmethod
+ def quit() -> StateResult:
+ """Close the program."""
+ raise SystemExit
+
+.. code-block:: python
+ :emphasize-lines: 2,5,19-23
+
+ @attrs.define()
+ class InGame(State):
+ """Primary in-game state."""
+
+ def on_event(self, event: tcod.event.Event) -> StateResult:
+ """Handle events for the in-game state."""
+ (player,) = g.world.Q.all_of(tags=[IsPlayer])
+ match event:
+ case tcod.event.Quit():
+ raise SystemExit
+ case tcod.event.KeyDown(sym=sym) if sym in DIRECTION_KEYS:
+ player.components[Position] += DIRECTION_KEYS[sym]
+ # Auto pickup gold
+ for gold in g.world.Q.all_of(components=[Gold], tags=[player.components[Position], IsItem]):
+ player.components[Gold] += gold.components[Gold]
+ text = f"Picked up {gold.components[Gold]}g, total: {player.components[Gold]}g"
+ g.world[None].components[("Text", str)] = text
+ gold.clear()
+ return None
+ case tcod.event.KeyDown(sym=KeySym.ESCAPE):
+ return Push(MainMenu())
+ case _:
+ return None
+
+ ...
+
+Update main.py
+==============================================================================
+
+Now ``main.py`` can be edited to use the global variables and the new game loop.
+
+Add :python:`import g` and :python:`import game.state_tools`.
+Replace references to ``console`` with ``g.console``.
+
+States are initialed by assigning a list with the initial state to ``g.states``.
+The previous game loop is replaced by a call to :python:`game.state_tools.main_loop()`.
+
+.. code-block:: python
+ :emphasize-lines: 3-4,13-16
+
+ ...
+
+ import g
+ import game.state_tools
+ import game.states
+
+ def main() -> None:
+ """Entry point function."""
+ tileset = tcod.tileset.load_tilesheet(
+ "data/Alloy_curses_12x12.png", columns=16, rows=16, charmap=tcod.tileset.CHARMAP_CP437
+ )
+ tcod.tileset.procedural_block_elements(tileset=tileset)
+ g.console = tcod.console.Console(80, 50)
+ g.states = [game.states.MainMenu()]
+ with tcod.context.new(console=g.console, tileset=tileset) as g.context:
+ game.state_tools.main_loop()
+ ...
+
+After this you can test the game.
+There should be no visible differences from before.
+
+You can review the part-3 source code `here `_.
+
+.. rubric:: Footnotes
+
+.. [#slots] This is done to prevent subclasses from requiring a ``__dict__`` attribute.
+ See :any:`slots` for a detailed explanation of what they are.
+
+.. [#identity] See :any:`is`.
+ Since ``State`` classes use ``attrs`` they might compare equal when they're not the same object.
+ This means :python:`list.index` won't work for this case.
+
+.. _Protocol: https://mypy.readthedocs.io/en/stable/protocols.html
diff --git a/examples/DejaVuSerif.ttf b/examples/DejaVuSerif.ttf
new file mode 100644
index 00000000..0b803d20
Binary files /dev/null and b/examples/DejaVuSerif.ttf differ
diff --git a/examples/README.md b/examples/README.md
new file mode 100644
index 00000000..8f13fa79
--- /dev/null
+++ b/examples/README.md
@@ -0,0 +1,12 @@
+This directory contains a few example scripts for using python-tcod.
+
+`samples_tcod.py` is the mail example which uses most of the newer API. This
+can be compared to `samples_libtcodpy.py` which mostly uses deprecated
+functions from the old API.
+
+Examples in the `distribution/` folder show how to distribute projects made
+using python-tcod.
+
+Examples in the `experimental/` folder show off features that might later be
+added the python-tcod API. You can use those features by copying those modules
+into your own project.
diff --git a/examples/VeraMono.ttf b/examples/VeraMono.ttf
new file mode 100644
index 00000000..139f0b43
Binary files /dev/null and b/examples/VeraMono.ttf differ
diff --git a/examples/audio_tone.py b/examples/audio_tone.py
new file mode 100755
index 00000000..b65bb732
--- /dev/null
+++ b/examples/audio_tone.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python
+"""Shows how to use tcod.sdl.audio to play audio.
+
+Opens an audio device using SDL then plays tones using various methods.
+"""
+
+import math
+import time
+
+import attrs
+import numpy as np
+from scipy import signal # type: ignore[import-untyped]
+
+import tcod.sdl.audio
+
+VOLUME = 10 ** (-12 / 10) # -12dB, square waves can be loud
+
+
+@attrs.define
+class PullWave:
+ """Square wave stream generator for an SDL audio device in pull mode."""
+
+ frequency: float
+ time: float = 0.0
+
+ def __call__(self, stream: tcod.sdl.audio.AudioStream, request: tcod.sdl.audio.AudioStreamCallbackData) -> None:
+ """Stream a square wave to SDL on demand.
+
+ This function must run faster than the stream duration.
+ Numpy is used to keep performance within these limits.
+ """
+ duration = request.additional_samples / self.frequency
+
+ t = np.linspace(self.time, self.time + duration, request.additional_samples, endpoint=False)
+ self.time += duration
+ wave = signal.square(t * (math.tau * 440)).astype(np.float32)
+ stream.queue_audio(wave)
+
+
+if __name__ == "__main__":
+ device = tcod.sdl.audio.get_default_playback().open(channels=1, frequency=44100)
+ print(f"{device.name=}")
+ device.gain = VOLUME
+ print(device)
+
+ print("Sawtooth wave queued with AudioStream.queue_audio")
+ stream = device.new_stream(format=np.float32, channels=1, frequency=44100)
+ t = np.linspace(0, 1.0, 44100, endpoint=False)
+ wave = signal.sawtooth(t * (math.tau * 440)).astype(np.float32)
+ stream.queue_audio(wave)
+ stream.flush()
+ while stream.queued_samples:
+ time.sleep(0.01)
+
+ print("---")
+ time.sleep(0.5)
+
+ print("Square wave attached to AudioStream.getter_callback")
+ stream = device.new_stream(format=np.float32, channels=1, frequency=44100)
+ stream.getter_callback = PullWave(device.frequency)
+
+ time.sleep(1)
+ stream.getter_callback = None
+
+ print("---")
+ time.sleep(0.5)
+
+ print("Sawtooth wave played with BasicMixer.play")
+ mixer = tcod.sdl.audio.BasicMixer(device, frequency=44100, channels=2)
+ channel = mixer.play(wave)
+ while channel.busy:
+ time.sleep(0.01)
+
+ print("---")
+ device.close()
diff --git a/examples/cavegen.py b/examples/cavegen.py
new file mode 100755
index 00000000..ace6bb98
--- /dev/null
+++ b/examples/cavegen.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python
+"""A basic cellular automata cave generation example using SciPy.
+
+http://www.roguebasin.com/index.php?title=Cellular_Automata_Method_for_Generating_Random_Cave-Like_Levels
+
+This will print the result to the console, so be sure to run this from the
+command line.
+"""
+
+from typing import Any
+
+import numpy as np
+import scipy.signal # type: ignore[import-untyped]
+from numpy.typing import NDArray
+
+
+def convolve(tiles: NDArray[Any], wall_rule: int = 5) -> NDArray[np.bool_]:
+ """Return the next step of the cave generation algorithm.
+
+ `tiles` is the input array. (0: wall, 1: floor)
+
+ If the 3x3 area around a tile (including itself) has `wall_rule` number of
+ walls then the tile will become a wall.
+ """
+ # Use convolve2d, the 2nd input is a 3x3 ones array.
+ neighbors: NDArray[Any] = scipy.signal.convolve2d(tiles == 0, [[1, 1, 1], [1, 1, 1], [1, 1, 1]], "same")
+ next_tiles: NDArray[np.bool_] = neighbors < wall_rule # Apply the wall rule.
+ return next_tiles
+
+
+def show(tiles: NDArray[Any]) -> None:
+ """Print out the tiles of an array."""
+ for line in tiles:
+ print("".join("# "[int(cell)] for cell in line))
+
+
+if __name__ == "__main__":
+ WIDTH, HEIGHT = 60, 20
+ INITIAL_CHANCE = 0.45 # Initial wall chance.
+ CONVOLVE_STEPS = 4
+ # 0: wall, 1: floor
+ tiles: NDArray[np.bool_] = np.random.random((HEIGHT, WIDTH)) > INITIAL_CHANCE
+ for _ in range(CONVOLVE_STEPS):
+ tiles = convolve(tiles)
+ tiles[[0, -1], :] = 0 # Ensure surrounding wall.
+ tiles[:, [0, -1]] = 0
+ show(tiles)
diff --git a/examples/data/cfg/sample.cfg b/examples/data/cfg/sample.cfg
deleted file mode 100644
index 4da7a5bb..00000000
--- a/examples/data/cfg/sample.cfg
+++ /dev/null
@@ -1,43 +0,0 @@
-myStruct "struct_name" {
- //
- bool_field=true
- char_field='Z'
- int_field=24
- float_field=3.14
- string_field="hello"
- color_field="255,128,128"
- dice_field="0.5x3d5+2"
-
- // dynamically declared fields
- bool bool_field2=false
- char char_field2='@'
- int int_field2=4
- float float_field2=4.3
- string string_field2="world"
- color color_field2=#FF22CC
- dice dice_field2="3d20"
-
- bool_list=[true, false, true]
- char_list=['a', 'b', 'z']
- integer_list=[13,2,-3]
- float_list=[5.0,2.0,-3.5]
- string_list=["item one","time_two"]
- color_list=["42,42,142","128,64,128"]
-// dice_list=["3d4","2d4+2","0.75x4d6-1"]
-
- // dynamically declared list fields
- bool[] bool_list2=[true, false, true]
- char[] char_list2=['a', 'b', 'z']
- int[] integer_list2=[13,2,-3]
- float[] float_list2=[5.0,2.0,-3.5]
- string[] string_list2=["item one","time_two"]
- color[] color_list2=["42,42,142","128,64,128"]
-
-}
-// a completely dynamic structure (not declared at compile time)
-struct dynStruct {
- int someVar=4
- struct subStruct {
- float anotherVar=4.3
- }
-}
diff --git a/examples/data/fonts/consolas10x10_gs_tc.png b/examples/data/fonts/consolas10x10_gs_tc.png
deleted file mode 100644
index 4b9b913f..00000000
Binary files a/examples/data/fonts/consolas10x10_gs_tc.png and /dev/null differ
diff --git a/examples/data/img/circle.png b/examples/data/img/circle.png
deleted file mode 100644
index 1c1404f3..00000000
Binary files a/examples/data/img/circle.png and /dev/null differ
diff --git a/examples/data/img/skull.png b/examples/data/img/skull.png
deleted file mode 100644
index db0014e8..00000000
Binary files a/examples/data/img/skull.png and /dev/null differ
diff --git a/examples/data/namegen/README.txt b/examples/data/namegen/README.txt
deleted file mode 100644
index b814c879..00000000
--- a/examples/data/namegen/README.txt
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Collections of syllables and other data used for name generation.
- *
- * All the strings must be enclosed between quotation marks, with no semicolon
- * at the end.
- *
- * SYLLABLE SETS:
- * Please use only latin characters, apostrophes and dashes for syllables. All
- * other characters will be treated as separators between syllables, eg. "ish"
- * and "is'h" are syllables, but "|sh" and "ish!" aren't (the erroneous
- * syllables will be read as "sh" and "ish", respectively). If you wish to use
- * a special character, please write it after a slash, eg. a semicolon will need
- * to be written as "/;" in order to be correctly parsed. Beware: a slash at the
- * end of a string will not trigger an error whatsoever, but the final syllable
- * will not be added to the list at all. Spaces are a special case: they can be
- * triggered either with the above method, or with a single underscore: "\ " and
- * "_" are both valid and will produce a space.
- *
- * PHONEME SETS:
- * Phoneme sets should be single characters or digraphs. Please use lowercase
- * characters only. "ch" and "tz" are valid consonants, but "Ch" or "trz" are
- * not. They will be rejected upon generating phoneme lists.
- *
- * RULES:
- * These denote how a word is generated. A rule is a string consisting of
- * normal characters [a-z,A-Z,',-], special characters preceded by a slash (see
- * the notes concerning syllables), underscores to denote spaces and wildcards.
- * Wildcards are preceded by a dollar sign. Here's the full list:
- * "$P" - a random Pre syllable
- * "$s" - a random Start syllable
- * "$m" - a random Middle syllable
- * "$e" - a random End syllable
- * "$p" - a random Post syllable
- * "$v" - a random vocal
- * "$c" - a random consonant
- * "$?" - a random phoneme
- * So, if we hav the following data:
- * syllablesStart = "Ivan"
- * syllablesEnd = "Terrible"
- * rules = "$s_the_$e"
- * the generator will output "Ivan the Terrible".
- * The wildcards may also include an integer number. This number marks the per
- * cent chance of actually appearing the related wildcard has. The number is
- * placed after the asterisk, but before the corresponding character. For
- * instance, "*50m" means "50% chance of adding a Middle syllable".
- * If multiple rules are specified, they should be separated by characters that
- * are not special character or wildcard indicators. A comma is a legible
- * separator.
- * A rule may be preceded by a special wildcard consisting of a per cent sign
- * "%" and an integer number. This means the per cent chance of picking this
- * rule should the RNG encounter it. For instance, if two rules are specified,
- * each will have 50% chance of being chosen. However, if one of them is
- * preceded by the "%50" sign, it will actually have a 100/2*50% = 25% chance of
- * being selected (100/2 is the initial chance any single rule from a set of two
- * will be picked, for five rules this would be 100/5, etc.).
- * The rules are a mandatory field. Also, any field thai it references are to be
- * included as well, lest it produce errors or, in the best of cases, generate
- * an empty syllable as output.
- *
- * Don't get paranoid about controlling whether the syllables are repeated. The
- * program will ignore repeated entries anyway. This applies to phonemes too.
- *
- * Please make sure you have enough syllables specified to ensure variety in the
- * generated names. A string with 512 characters should be sufficient in most
- * cases. Anything below that is a risk of making the names predictable.
- *
- * I hope this little tool is both fun and useful for you. Take care!
- *
- * -Mingos
- */
diff --git a/examples/data/namegen/jice_celtic.cfg b/examples/data/namegen/jice_celtic.cfg
deleted file mode 100644
index 744d25b9..00000000
--- a/examples/data/namegen/jice_celtic.cfg
+++ /dev/null
@@ -1,15 +0,0 @@
-//Celtic names from Jice's "The Cave"
-name "Celtic male" {
- syllablesStart = "Aen, Agno, All, Ba, Beo, Brig, Ci, Cre, Dan, Del, Ela, Eo, En, Er, Et, In, Io, Morr, Nem, Nu, Og, Or, Ta"
- syllablesMiddle = "a, ar, ba, bo, ch, d, ig"
- syllablesEnd = "aid, ain, an, and, th, ed, eth, gus, lam, lor, man, od, t, thach"
- rules = "$s$m$e, $s$e"
-}
-
-name "Celtic female" {
- syllablesStart = "Aen, Agno, All, Ba, Beo, Brig, Ci, Cre, Dan, Del, Ela, Eo, En, Er, Et, In, Io, Morr, Nem, Nu, Og, Or, Ta"
- syllablesMiddle = "a, ar, ba, bo, ch, d, ig"
- syllablesEnd = "ai, an, da, id, iu, ma, me, na, ne, tha"
- rules = "$s$m$e, $s$e"
-}
-
diff --git a/examples/data/namegen/jice_fantasy.cfg b/examples/data/namegen/jice_fantasy.cfg
deleted file mode 100644
index ce3e95d7..00000000
--- a/examples/data/namegen/jice_fantasy.cfg
+++ /dev/null
@@ -1,17 +0,0 @@
-//Fantasy names from Jice's "The Cave"
-name "Fantasy male" {
- syllablesStart = "Aer, An, Ar, Ban, Bar, Ber, Beth, Bett, Cut, Dan, Dar, Dell, Der, Edr, Er, Eth, Ett, Fin, Ian, Iarr, Ill, Jed, Kan, Kar, Ker, Kurr, Kyr, Man, Mar, Mer, Mir, Tsal, Tser, Tsir, Van, Var, Yur, Yyr"
- syllablesMiddle = "al, an, ar, el, en, ess, ian, onn, or"
- syllablesEnd = "ai, an, ar, ath, en, eo, ian, is, u, or"
- illegal = "orar, arrar"
- rules = "$s$m$e, $s$e"
-}
-
-name "Fantasy female" {
- syllablesStart = "Aer, An, Ar, Ban, Bar, Ber, Beth, Bett, Cut, Dan, Dar, Dell, Der, Edr, Er, Eth, Ett, Fin, Ian, Iarr, Ill, Jed, Kan, Kar, Ker, Kurr, Kyr, Man, Mar, Mer, Mir, Tsal, Tser, Tsir, Van, Var, Yur, Yyr"
- syllablesMiddle = "al, an, ar, el, en, ess, ian, onn, or"
- syllablesEnd = "a, ae, aelle, ai, ea, i, ia, u, wen, wyn"
- illegal = "arrar"
- rules = "$s$m$e, $s$e"
-}
-
diff --git a/examples/data/namegen/jice_mesopotamian.cfg b/examples/data/namegen/jice_mesopotamian.cfg
deleted file mode 100644
index 8ecab25b..00000000
--- a/examples/data/namegen/jice_mesopotamian.cfg
+++ /dev/null
@@ -1,17 +0,0 @@
-//Mesopotamian names from Jice's "The Cave"
-name "Mesopotamian male" {
- syllablesStart = "A, Ann, Ash, E', En, Er, Gil, In, Ir, Ish, Mar, Ni, Nin, Re, Ti, Ur"
- syllablesMiddle = "am, an, du, esh, gam, gir, ka, ki, li, un, ur, ta"
- syllablesEnd = "aki, al, ar, at, du, eph, esh, il, im, ki, nu, uk, ur, uz"
- illegal = "aa, e'e"
- rules = "$s$m$e, $s$e"
-}
-
-name "Mesopotamian female" {
- syllablesStart = "A, Ann, Ash, E', En, Er, Gil, In, Ir, Ish, Mar, Ni, Nin, Re, Ti, Ur"
- syllablesMiddle = "am, an, du, esh, gam, gir, ka, ki, li, un, ur, ta"
- syllablesEnd = "ag, il, la, na, sag, su, ta"
- illegal = "aa, e'e"
- rules = "$s$m$e, $s$e"
-}
-
diff --git a/examples/data/namegen/jice_norse.cfg b/examples/data/namegen/jice_norse.cfg
deleted file mode 100644
index 01bd584a..00000000
--- a/examples/data/namegen/jice_norse.cfg
+++ /dev/null
@@ -1,17 +0,0 @@
-//Norse names from Jice's "The Cave"
-name "Norse male" {
- syllablesStart = "Al, Ae, As, Bi, Fen, Ha, Hag, Ho, Hu, Iv, Jot, Ma, Mio, Mu, Nid, Ors, Ra, Sta, Svar, Tys, Vae, Van, Vol, Y, Ygg"
- syllablesMiddle = "an, ar, ba, da, dra, gar, na, tal"
- syllablesEnd = "ad, ald, agr, ar, ard, eyr, far, frost, heim, hogg, in, mir, nar, nir, or, osk, rir, sil, sir, ttir, urd"
- illegal = "yor, yar, yad, yin"
- rules = "$s$m$e, $s$e"
-}
-
-name "Norse female" {
- syllablesStart = "Al, Ae, As, Bi, Fen, Ha, Hag, Ho, Hu, Iv, Jot, Ma, Mio, Mu, Nid, Ors, Ra, Sta, Svar, Tys, Vae, Van, Vol, Y, Ygg"
- syllablesMiddle = "an, ar, ba, da, dra, gar, na, tal"
- syllablesEnd = "a, la, li, va"
- illegal = "raa, ya, aea, aea"
- rules = "$s$m$e, $s$e"
-}
-
diff --git a/examples/data/namegen/jice_region.cfg b/examples/data/namegen/jice_region.cfg
deleted file mode 100644
index 73f5508a..00000000
--- a/examples/data/namegen/jice_region.cfg
+++ /dev/null
@@ -1,7 +0,0 @@
-//Region names from Jice's "The Cave"
-name "region" {
- syllablesStart = "Act, Afr, Ag, Agr, Alb, Am, An, Angl, Ant, As, Asys, Asis, At, Atl, Brund, Cath, Cor, Dan, Eb, Eg, Er, Esc, Esp, Est, Eth, Eur, Flor, It, Lyr, Mal, Mir, Myr, Nor, Pel, Rom, Seg, Sib, Sylv, Terr, Tir, Tr, Tyr, Xan"
- syllablesMiddle = "ad, ag, al, an, and, ant, anth, ar, ard, as, at, atr, eg, en, ent, ern, et, ian, in, itr, on, op, ov, ur, ymn, yr"
- syllablesEnd = "a, aia, ana, as, ea, ene, eos, esia, ia, iad, ias, is, ium, ius, on, ona, or, ova, um, us, ya"
- rules = "$s$m$e, $s$e"
-}
diff --git a/examples/data/namegen/jice_town.cfg b/examples/data/namegen/jice_town.cfg
deleted file mode 100644
index 5a79a0ee..00000000
--- a/examples/data/namegen/jice_town.cfg
+++ /dev/null
@@ -1,6 +0,0 @@
-//Town names from Jice's "The Cave"
-name "town" {
- syllablesStart = "Ael, Ash, Barrow, Bel, Black, Clear, Cold, Crystal, Deep, Edge, Falcon, Fair, Fall, Glass, Gold, Ice, Iron, Mill, Moon, Mor, Ray, Red, Rock, Rose, Shadow, Silver, Spell, Spring, Stone, Strong, Summer, Swyn, Wester, Winter"
- syllablesEnd = "ash, burn, barrow, bridge, castle, cliff, coast, crest, dale, dell, dor, fall, field, ford, fort, gate, haven, hill, hold, hollow, iron, lake, marsh, mill, mist, mount, moor, pond, shade, shore, summer, town, wick"
- rules = "$s$e"
-}
diff --git a/examples/data/namegen/mingos_demon.cfg b/examples/data/namegen/mingos_demon.cfg
deleted file mode 100644
index e2919ba2..00000000
--- a/examples/data/namegen/mingos_demon.cfg
+++ /dev/null
@@ -1,16 +0,0 @@
-//Demon names
-name "demon male" {
- phonemesVocals = "a, e, i, o, u"
- syllablesStart = "Aam, Ab, Ad, Ahr, Alas, Al-A'w, All, Al-M, Ap, As, Ast, Az, Bal, Bal S, Bag, Balb, Ban, Bansh, Baph, Barb, Bath, Bazt, Be'L, Beel, Beelz, Bel, Belph, Ber, Bh, Bifr, Biul, Bush, Caac, Cagn, Caim, Chalk, Char, Chem, Coal, Dag, Dant, Decer, Demog, Dev, Dj, Dragh, Elig, Emp, Errt, Etr, Ett, Eur, Euryn, Gorg, Graph, Grig, Haag, Halph, Haur, Hoeth, Ifr, Inc, Ibl, Ith, Kabh, Kas, Kokb', Kray, Lab, Lam, Lech, Leg, Lil, Lioth, Lix, Luc, Mal, Malph, Mamm, March, Mast, Math, Meph, Merm, Mol, Murm, Naam, Naph, Nek, Neph, Neq, Nix, Noud, Onom, Onos, Orc, Orob, Oul, Paim, Phen, Pont, Proc, Rah, Rak, Raksh, Ram, Rang, Raum, Raz, Rimm, Rub, Rus, Sabn, Salps, Sam, Sat, Sc, Scarm, Seer, Sem, Set, Shait, Shax, Shed, Shez, Sidr, Sitr, Sth, Succ, Surg, Tann, Tart, Tch, Teer, Thamm, Thub, Tlal, Tsab, Val, Vap, Vass, Vep, Verr, Vin, Vol, Vual, Xaph, Xiph, Xitr, Zaeb, Zim, Ziz, Zaln"
- syllablesMiddle = "b'ae, ba, be, chi, dra, du, ga, ghi, go, lia, ma, mba, mu, n'e, na, nti, nzu, phe, pho, r'e, rba, rgo, ssa, thi, tryu, ttu, tzi, v-e, vna, xra, ya"
- syllablesEnd = "b'ael, bel, bub, bur, bus, ces, chus, dai, ddon, des, dhaka, el, fer, flas, gion, gon, gor, klet, kor, ksha, kuth, laas, lech, les, lion, lith, loch, lsu, mael, math, mejes, meus, mon, moth, mmut, mosh, nai, nar, neus, nex, nias, nnin, nomos, phas, r'el, raal, rept, res, rgon, riax, rith, rius, rous, rus, ruth, sias, stor, swath, tath, than, the, thra, tryus, tura, vart, ztuk"
- rules = "$s$v$35m$10m$e"
-}
-
-name "demon female" {
- phonemesVocals = "a, e, i, o, u"
- syllablesStart = "Aam, Ab, Ad, Ahr, Alas, Al-A'w, All, Al-M, Ap, As, Ast, Az, Bal, Bal S, Bag, Balb, Ban, Bansh, Baph, Barb, Bath, Bazt, Be'L, Beel, Beelz, Bel, Belph, Ber, Bh, Bifr, Biul, Bush, Caac, Cagn, Caim, Chalk, Char, Chem, Coal, Dag, Dant, Decer, Demog, Dev, Dj, Dragh, Elig, Emp, Errt, Etr, Ett, Eur, Euryn, Gorg, Graph, Grig, Haag, Halph, Haur, Hoeth, Ifr, Inc, Ibl, Ith, Kabh, Kas, Kokb', Kray, Lab, Lam, Lech, Leg, Lil, Lioth, Lix, Luc, Mal, Malph, Mamm, March, Mast, Math, Meph, Merm, Mol, Murm, Naam, Naph, Nek, Neph, Neq, Nix, Noud, Onom, Onos, Orc, Orob, Oul, Paim, Phen, Pont, Proc, Rah, Rak, Raksh, Ram, Rang, Raum, Raz, Rimm, Rub, Rus, Sabn, Salps, Sam, Sat, Sc, Scarm, Seer, Sem, Set, Shait, Shax, Shed, Shez, Sidr, Sitr, Sth, Succ, Surg, Tann, Tart, Tch, Teer, Thamm, Thub, Tlal, Tsab, Val, Vap, Vass, Vep, Verr, Vin, Vol, Vual, Xaph, Xiph, Xitr, Zaeb, Zim, Ziz, Zaln"
- syllablesMiddle = "b'ae, ba, be, chi, dra, du, ga, ghi, go, lia, ma, mba, mu, n'e, na, nti, nzu, phe, pho, r'e, rba, rgo, ssa, thi, tryu, ttu, tzi, v-e, vna, xra, ya"
- syllablesEnd = "b'a, bel, bua, bure, buth, cess, chia, dai, ddea, dea, dhaka, el, fea, fla, gia, goa, gora, klath, kore, ksha, kua, laal, lexa, less, lia, lith, loth, lsa, mara, math, maja, mea, moa, moth, mmuth, mosh, na, nai, neuth, nex, nia, nnine, nomoa, pha, r'el, raala, repte, reshe, rgona, riaxe, rith, rish, rothe, rushe, ruth, sia, stora, swath, tath, thann, the, thra, trya, tura, varte, ztura"
- rules = "$s$v$35m$10m$e"
-}
diff --git a/examples/data/namegen/mingos_dwarf.cfg b/examples/data/namegen/mingos_dwarf.cfg
deleted file mode 100644
index 300a0b00..00000000
--- a/examples/data/namegen/mingos_dwarf.cfg
+++ /dev/null
@@ -1,29 +0,0 @@
-//dwarf names
-name "dwarf male" {
- syllablesStart = "A, An, Ba, Bi, Bo, Bom, Da, Dar, De, Do, Du, Due, Duer, Dwa, Fa, Fal, Fi, Fre, Fun, Ga, Gar, Gim, Glo, Go, Gom, Gro, Gwar, Ib, Jor, Ka, Ki, Kil, Lo, Mar, Na, Nal, O, Ras, Ren, Ro, Ta, Tar, Tel, Thi, Tho, Thon, Thra, Tor, Von, We, Wer, Yen, Yur"
- syllablesEnd = "bil, bin, bur, char, den, dir, dur, fri, fur, in, li, lin, mil, mur, ni, nur, ran, ri, ril, rimm, rin, thur, tri, ulf, un, ur, vi, vil, vim, vin, vri"
- rules = "$s$e"
- illegal = "rur, ueu"
-}
-
-name "dwarf female" {
- syllablesStart = "A, An, Ba, Bi, Bo, Bom, Da, Dar, De, Do, Du, Due, Duer, Dwa, Fa, Fal, Fi, Fre, Fun, Ga, Gar, Gim, Glo, Go, Gom, Gro, Gwar, Ib, Jor, Ka, Ki, Kil, Lo, Mar, Na, Nal, O, Ras, Ren, Ro, Ta, Tar, Tel, Thi, Tho, Thon, Thra, Tor, Von, We, Wer, Yen, Yur"
- syllablesEnd = "al, ali, ba, bida, bra, da, deth, di, fra, gret, hild, iess, kala, la, laani, li, lona, ma, mae, mala, na, nuda, ra, ta, tala, tu, tuna, vada, vara, ya"
- rules = "$s$e"
- illegal = "dueal, frefra, grogret"
-}
-
-//surnames have semantic information. Here, they're separated into three
-//somewhat coherent sets and either is chosen
-name "dwarf surname" {
- //1st set - smith & Mr.Muscle surnames
- syllablesPre = "Boulder, Bronze, Coal, Copper, Gem, Granite, Hammer, Iron, Marble, Metal, Rock, Steel, Stone, Thunder"
- syllablesPost = "bender, breaker, carver, club, crusher, cutter, digger, fist, foot, forger, heart, smasher, smith"
- //2nd set - warrior surnames
- syllablesStart = "Bear, Boar, Dragon, Giant, Goblin, Elf, Ettin, Foe, Kobold, Ogre, Orc,Spider, Troll, Wolf"
- syllablesEnd = "bane, basher, _Battler, _Beheader, boxer, _Butcher, choker, cleaver, crusher, cutter, doom, eater, _Executioner, _Fighter, _Garrotter, grapple, _Gutter, hammer, killer, mauler, masher, ripper, slasher, slayer, slicer, smasher, _Strangler, striker, _Wrestler"
- //3rd set - heroic and general
- phonemesVocals = "Black, Blood, Bronze, Fire, Firm, Grey, Hard, Ice, Iron, Moon, Oak, Onyx, Red, Steel, Stone, Strong, Thunder, White"
- phonemesConsonants = "axe, beard, blade, brand, cheek, fist, foot, hair, hammer, hand, head, heart, pick, shield, spear, spike, sword"
- rules = "$P$p, $s$e, $v$c"
-}
\ No newline at end of file
diff --git a/examples/data/namegen/mingos_norse.cfg b/examples/data/namegen/mingos_norse.cfg
deleted file mode 100644
index f1d9f7c9..00000000
--- a/examples/data/namegen/mingos_norse.cfg
+++ /dev/null
@@ -1,19 +0,0 @@
-//Norse names. Most of them are syllables extracted from names that
-//actually appear in Norse written texts. Norse names consist of two parts,
-//which is easy to reflect in a generator such as this one.
-name "Mingos Norse male" {
- //these are ready-made names
- syllablesPre = "Aunn, Bjoern, Bjolfr, Bjorr, Boltr, Byulfr, Erik, Erpr, Eykr, Feitr, Fotr, Froekn, Gaukr, Gauss, Gils, Gimp, Griss, Gyi, Haegwin, Haengr, Hakon, Hand, Harekr, Hattr, Haukr, Helf, Hjalli, Hjaerne, Hjarrandi, Hnaki, Hneitr, Hrafn, Jarl, Karl, Kar-Toki, Kaun, Kilfisr, Kiuli, Knut, Knutr, Krakr, Leifr, Lokki, Manni, Mar, Moegr, Naemr, Nagli, Nef-Bjoern, Njall, Oelfun, Oenn, Oern, Rafn, Roki, Skjalf, Skog, Spjall, Sveinn, Tannr, Trani, Trjonn, Utryggr, Vagn, Varg, Ve-Finnr, Voettr, Vragi, Vrai"
- //and these are syllables to stick together
- syllablesStart = "Ab, Adal, Adi, Alf, An, And, Ans, Arn, Arm, Ask, Au, Audh, Ba, Bae, Bag, Bal, Bar, Bas, Bein, Berg, Bern, Bjad, Bjarn, Bjart, Boan, Boed, Boerk, Bogg, Bor, Bot, Bram, Bran, Bratt, Brei, Bro, Brunn, Bukk, Dag, Djur, Dor, Duf, Dun, Ed, Ei, Ein, Ekk, Ey, Fa, Fad, Fal, Far, Fast, Fen, Finn, Fjall, Fjoel, Flae, Fol, Folk, Foest, Frey, Frid, Frost, Ful, Fuld, Gaes, Geir, Gag, Gal, Gam, Gar, Gaut, Geir, Ginn, Gis, Gjaf, Gjal, God, Gnaudi, Gny, Gret, Grim, Grom, Grum, Gud, Gull, Gunn, Gutt, Gyll, Gyr, Ha, Haf, Hag, Hagn, Half, Hall, Ham, Har, Haur, Hedin, Hef, Heg, Heil, Hein, Hel, Hildi, Hjall, Hjalm, Hjoer, Hlif, Hloed, Hoeg, Hoegg, Hoer, Hoes, Hol, Holm, Hord, Horn, Hrad, Hrafn, Hring, Hroeng, Hross, Hug, Hul, Hum, Hus, Hvit, Hyr, Igul, Illu, In, Ingi, Is, Ja, Jar, Jarn, Jat, Jo, Joefur, Kjoet, Kol, Kon, Lamb, Lids, Lik, Ljot, Lyd, Nadd, Nef, Odal, Odd, Oeg, Oel, Oen, Oeng, Oes, Rad, Rafn, Ragn, Rask, Reid, Reyr, Roegn, Rok, Run, Sae, Sig, Skae, Skjald, Skjoeld, Skol, Slag, Snae, Soel, Soend, Spjall, Stafn, Stark, Stein, Stig, Stod, Stygg, Styr, Sunn, Svein, Svart, Svarta, Tid, Tindr, Tjoer, Trygg, Tyr, Thyr, Ud, Ulf, Yngv, Vae, Val, Varg, Ve, Ved, Vest, Vetr, Vid, Vig, Vik"
- syllablesEnd = "adr, afr, all, andi, arfr, arr, astr, autr, bi, beinn, bert, bjoern, bodi, bodr, bori, brandr, burinn, burt, daenni, dan, di, din, diarfr, dinn, dr, dridr, dur, eifr, eirr, fast, fasti, fastr, fidr, fill, fing, fingr, finnr, fli, fri, frimr, fuss, gall, geir, geirr, gils, gir, gisl, glir, glumr, grimr, gripr, gur, guri, haldr, hegn, hjalmr, hjoefr, hjofr, hoefdi, hoess, hofdi, horir, horr, hoess, hvatr, ilir, ill, ingr, inn, jadr, jarn, jarr, jartan, jartr, joern, jofr, karl, kell, ketill, kirr, kuldr, kull, kundr, kunnr, laugr, lan, leifr, leikr, li, lidi, lidr, lingr, madr, maer, mann, marr, mingr, modr, mr, mund, mundr, nall, narr, nefr, nir, niutr, olfr, ormr, phorr, pli, r, raeifr, radr, rik, rikr, ring, rinn, rir, roedr, rudr, rukr, si, sir, skegg, skeggi, sjall, steinn, styrr, sur, tir, tyr, utr, ulf, ulfr, undr, ungr, urd, urdr, valdr, vandi, vandill, veinr, ver, vett, vi, vidr, vifr, vind, vindr, vi, vini, vir, visl"
- rules = "$s$e, %10$P"
- illegal = "bjarnbj, gp, vv, aea, aee, aeo, drd"
-}
-
-name "Mingos Norse female" {
- syllablesStart = "A, Aer, Aerin, Aes, Aet, Afri, Agaer, Ager, Al, Alf, Alm, Arinn, Arn, As, Au, Aud, Bau, Be, Beg, Berg, Bir, Bjol, Bod, Bol, Bor, Borg, Bot, Bri, Brun, Bryn, Bus, Dag, Dis, Dom, Dor, Dot, Dri, Dyr, Ed, Ei, Em, Emb, Engil, Er, Es, Ev, Ey, Fal, Fast, Fin, Fjol, Fjor, Fjot, Folk, Frey, Frid, Frost, Gaut, Geir, Ger, Gil, Ginn, Gis, Gjaf, Gre, Grim, Gud, Gy, Gyd, Haf, Hall, Haur, Hedin, Heil, Heim, Hel, Her, Hidin, Hil, Hildi, Hjalm, Hjor, Hlad, Hlif, Holm, Hrim, Hrod, Hun, Igul, In, Ingi, Ingil, Is, Jar, Jo, Jofur, Jor, Jut, Ljuf, Lofn, Mal, Malm, Mar, Mat, Matt, Mund, Nid, Odd, Ol, Olm, Ot, Rad, Ragn, Rand, Rann, Regin, Run, Sal, Sae, Sig, Skjald, Sol, Stein, Svan, Svein, Tid, Ulf, Vet, Val, Ve, Vig, Vil, Yng"
- syllablesEnd = "bjorg, borg, da, dis, disa, disla, dora, eida, erna, fasta, finna, frida, frosta, fura, ga, gard, gauta, geid, gerda, gida, gret, grid, grima, gudr, gunn, gunna, heidr, hilda, ja, la, laug, lina, linn, ma, maer, rida, run, ta, trid, trida, truda, unn, ve, velda, vi, vilda, vina"
- illegal = "ii, iei, edeid, tg, ee, vev"
- rules = "$s$e"
-}
diff --git a/examples/data/namegen/mingos_standard.cfg b/examples/data/namegen/mingos_standard.cfg
deleted file mode 100644
index f7e71854..00000000
--- a/examples/data/namegen/mingos_standard.cfg
+++ /dev/null
@@ -1,17 +0,0 @@
-//Names based on syllables from J.R.R. Tolkien's and David Eddings' novels.
-name "male" {
- phonemesVocals = "a, e, i, o, u, y"
- phonemesConsonants = "b, c, ch, ck, cz, d, dh, f, g, gh, h, j, k, kh, l, m, n, p, ph, q, r, rh, s, sh, t, th, ts, tz, v, w, x, z, zh"
- syllablesStart = "Aer, Al, Am, An, Ar, Arm, Arth, B, Bal, Bar, Be, Bel, Ber, Bok, Bor, Bran, Breg, Bren, Brod, Cam, Chal, Cham, Ch, Cuth, Dag, Daim, Dair, Del, Dr, Dur, Duv, Ear, Elen, Er, Erel, Erem, Fal, Ful, Gal, G, Get, Gil, Gor, Grin, Gun, H, Hal, Han, Har, Hath, Hett, Hur, Iss, Khel, K, Kor, Lel, Lor, M, Mal, Man, Mard, N, Ol, Radh, Rag, Relg, Rh, Run, Sam, Tarr, T, Tor, Tul, Tur, Ul, Ulf, Unr, Ur, Urth, Yar, Z, Zan, Zer"
- syllablesMiddle = "de, do, dra, du, duna, ga, go, hara, kaltho, la, latha, le, ma, nari, ra, re, rego, ro, rodda, romi, rui, sa, to, ya, zila"
- syllablesEnd = "bar, bers, blek, chak, chik, dan, dar, das, dig, dil, din, dir, dor, dur, fang, fast, gar, gas, gen, gorn, grim, gund, had, hek, hell, hir, hor, kan, kath, khad, kor, lach, lar, ldil, ldir, leg, len, lin, mas, mnir, ndil, ndur, neg, nik, ntir, rab, rach, rain, rak, ran, rand, rath, rek, rig, rim, rin, rion, sin, sta, stir, sus, tar, thad, thel, tir, von, vor, yon, zor"
- rules = "$s$v$35m$10m$e"
-}
-
-name "female" {
- phonemesVocals = "a, e, i, o, u, y"
- syllablesStart = "Ad, Aer, Ar, Bel, Bet, Beth, Ce'N, Cyr, Eilin, El, Em, Emel, G, Gl, Glor, Is, Isl, Iv, Lay, Lis, May, Ner, Pol, Por, Sal, Sil, Vel, Vor, X, Xan, Xer, Yv, Zub"
- syllablesMiddle = "bre, da, dhe, ga, lda, le, lra, mi, ra, ri, ria, re, se, ya"
- syllablesEnd = "ba, beth, da, kira, laith, lle, ma, mina, mira, na, nn, nne, nor, ra, rin, ssra, ta, th, tha, thra, tira, tta, vea, vena, we, wen, wyn"
- rules = "$s$v$35m$10m$e"
-}
diff --git a/examples/data/namegen/mingos_town.cfg b/examples/data/namegen/mingos_town.cfg
deleted file mode 100644
index cd093e21..00000000
--- a/examples/data/namegen/mingos_town.cfg
+++ /dev/null
@@ -1,9 +0,0 @@
-//Town names. The town name construction is based on real British town names,
-//although many starting syllables are made up.
-name "Mingos town" {
- syllablesPre = "East, Fort, Great, High, Lower, Middle, Mount, New, North, Old, Royal, Saint, South, Upper, West"
- syllablesStart = "Ales, Apple, Ash, Bald, Bay, Bed, Bell, Birdling, Black, Blue, Bow, Bran, Brass, Bright, Brown, Bruns, Bulls, Camp, Cherry, Clark, Clarks, Clay, Clear, Copper, Corn, Cross, Crystal, Dark, Deep, Deer, Drac, Eagle, Earth, Elk, Elles, Elm, Ester, Ewes, Fair, Falcon, Ferry, Fire, Fleet, Fox, Gold, Grand, Green, Grey, Guild, Hammer, Hart, Hawks, Hay, Haze, Hazel, Hemlock, Ice, Iron, Kent, Kings, Knox, Layne, Lint, Lor, Mable, Maple, Marble, Mare, Marsh, Mist, Mor, Mud, Nor, Oak, Orms, Ox, Oxen, Pear, Pine, Pitts, Port, Purple, Red, Rich, Roch, Rock, Rose, Ross, Rye, Salis, Salt, Shadow, Silver, Skeg, Smith, Snow, Sows, Spring, Spruce, Staff, Star, Steel, Still, Stock, Stone, Strong, Summer, Swan, Swine, Sword, Yellow, Val, Wart, Water, Well, Wheat, White, Wild, Winter, Wolf, Wool, Wor"
- syllablesEnd = "bank, borne, borough, brook, burg, burgh, bury, castle, cester, cliff, crest, croft, dale, dam, dorf, edge, field, ford, gate, grad, hall, ham, hollow, holm, hurst, keep, kirk, land, ley, lyn, mere, mill, minster, mont, moor, mouth, ness, pool, river, shire, shore, side, stead, stoke, ston, thorpe, ton, town, vale, ville, way, wich, wick, wood, worth"
- syllablesPost = "Annex, Barrens, Barrow, Corner, Cove, Crossing, Dell, Dales, Estates, Forest, Furnace, Grove, Haven, Heath, Hill, Junction, Landing, Meadow, Park, Plain, Point, Reserve, Retreat, Ridge, Springs, View, Village, Wells, Woods"
- rules = "$15P_$s$e_$15p"
-}
diff --git a/examples/distribution/PyInstaller/README.rst b/examples/distribution/PyInstaller/README.rst
index a5b71208..ee863880 100644
--- a/examples/distribution/PyInstaller/README.rst
+++ b/examples/distribution/PyInstaller/README.rst
@@ -1,26 +1,26 @@
PyInstaller Example
===================
-First, install the packages: ``tdl`` and ``PyInstaller``.
+It's recommended to use a virtual environment to package Python executables.
+Use the following guide on how to set one up:
+https://docs.python.org/3/tutorial/venv.html
-On Windows you must also install the ``pywin32`` package
-(named ``pypiwin32`` if you're using pip install.)
+Once the virtual environment is active you should install ``tcod``, ``PyInstaller``, and ``pypiwin32`` if on Windows from the ``requirements.txt`` file:
-Next, download the `hook-tcod.py` and `hook-tdl.py` files from this repository.
-Give PyInstaller the location of these files with the `--additional-hooks-dir`
-argument.
+ pip install -r requirements.txt
-`hook-tcod.py` is always needed. `hook-tdl.py` only installs the default
-font used by the tdl module and is optional if a custom font is used.
+Then run PyInstaller on the included Spec file::
-Then run the PyInstaller script with this command::
+ PyInstaller main.spec
- PyInstaller hello_world.py --additional-hooks-dir=.
+The finished build will be placed in the ``dist/`` directory.
-The finished build will be placed at ``dist/hello_world``. You should see references to the `hook-tdl` in the output.
+You can also build to one file using the following command::
-You can also build to one file with the command::
+ PyInstaller main.spec --onefile
- PyInstaller hello_world.py --additional-hooks-dir=. -F
+Single file distributions have performance downsides so it is preferred to distribute a larger program this way.
-The PyInstaller manual can be found at: https://pythonhosted.org/PyInstaller/
+For `tcod` it is recommended to set the ``PYTHONOPTIMIZE=1`` environment variable before running PyInstaller. This disables warnings for `tcod`'s deprecated functions and will improve the performance of those functions if you were using them.
+
+The PyInstaller documentation can be found at: https://pythonhosted.org/PyInstaller/
diff --git a/examples/tutorial/terminal8x8_gs_ro.png b/examples/distribution/PyInstaller/data/terminal8x8_gs_ro.png
old mode 100755
new mode 100644
similarity index 100%
rename from examples/tutorial/terminal8x8_gs_ro.png
rename to examples/distribution/PyInstaller/data/terminal8x8_gs_ro.png
diff --git a/examples/distribution/PyInstaller/hello_world.py b/examples/distribution/PyInstaller/hello_world.py
deleted file mode 100644
index eb1943ab..00000000
--- a/examples/distribution/PyInstaller/hello_world.py
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/env python
-
-import tdl
-
-
-WIDTH, HEIGHT = 80, 60
-console = None
-
-def main():
- global console
- console = tdl.init(WIDTH, HEIGHT)
- console.draw_str(0, 0, "Hello World")
- tdl.flush()
- tdl.event.key_wait()
-
-
-if __name__ == '__main__':
- main()
diff --git a/examples/distribution/PyInstaller/hook-tcod.py b/examples/distribution/PyInstaller/hook-tcod.py
deleted file mode 100644
index e3d1bec8..00000000
--- a/examples/distribution/PyInstaller/hook-tcod.py
+++ /dev/null
@@ -1,9 +0,0 @@
-"""
- Hook for https://github.com/libtcod/python-tcod
-"""
-from PyInstaller.utils.hooks import collect_dynamic_libs
-
-hiddenimports = ['_cffi_backend']
-
-# Install shared libraries to the working directory.
-binaries = collect_dynamic_libs('tcod', destdir='.')
diff --git a/examples/distribution/PyInstaller/hook-tdl.py b/examples/distribution/PyInstaller/hook-tdl.py
deleted file mode 100644
index 09386571..00000000
--- a/examples/distribution/PyInstaller/hook-tdl.py
+++ /dev/null
@@ -1,9 +0,0 @@
-"""
- Hook for https://github.com/libtcod/python-tcod
-
- You should skip this hook if you're using a custom font.
-"""
-from PyInstaller.utils.hooks import collect_data_files
-
-# Package tdl's 'default' font file.
-datas = collect_data_files('tdl')
diff --git a/examples/distribution/PyInstaller/icon.ico b/examples/distribution/PyInstaller/icon.ico
new file mode 100644
index 00000000..465ee268
Binary files /dev/null and b/examples/distribution/PyInstaller/icon.ico differ
diff --git a/examples/distribution/PyInstaller/main.py b/examples/distribution/PyInstaller/main.py
new file mode 100755
index 00000000..ce500635
--- /dev/null
+++ b/examples/distribution/PyInstaller/main.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+# To the extent possible under law, the libtcod maintainers have waived all
+# copyright and related or neighboring rights for the "hello world" PyInstaller
+# example script. This work is published from: United States.
+# https://creativecommons.org/publicdomain/zero/1.0/
+"""PyInstaller main script example."""
+
+from pathlib import Path
+
+import tcod.console
+import tcod.context
+import tcod.event
+import tcod.tileset
+
+WIDTH, HEIGHT = 80, 60
+
+BASE_DIR = Path(__file__).parent
+"""The directory of this script."""
+
+FONT_PATH = BASE_DIR / "data/terminal8x8_gs_ro.png"
+
+
+def main() -> None:
+ """Entry point function."""
+ tileset = tcod.tileset.load_tilesheet(FONT_PATH, 16, 16, tcod.tileset.CHARMAP_CP437)
+ with tcod.context.new(columns=WIDTH, rows=HEIGHT, tileset=tileset) as context:
+ while True:
+ console = tcod.console.Console(WIDTH, HEIGHT)
+ console.print(0, 0, "Hello World")
+ context.present(console)
+ for event in tcod.event.wait():
+ if isinstance(event, tcod.event.Quit):
+ raise SystemExit
+
+
+if __name__ == "__main__":
+ main()
diff --git a/examples/distribution/PyInstaller/main.spec b/examples/distribution/PyInstaller/main.spec
new file mode 100644
index 00000000..8f1375ee
--- /dev/null
+++ b/examples/distribution/PyInstaller/main.spec
@@ -0,0 +1,42 @@
+# -*- mode: python ; coding: utf-8 -*-
+
+block_cipher = None
+
+
+a = Analysis(
+ ["main.py"],
+ binaries=[],
+ datas=[("data", "data")], # Include all files in the 'data' directory.
+ hiddenimports=[],
+ hookspath=[],
+ runtime_hooks=[],
+ excludes=[],
+ win_no_prefer_redirects=False,
+ win_private_assemblies=False,
+ cipher=block_cipher,
+ noarchive=False,
+)
+pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
+exe = EXE(
+ pyz,
+ a.scripts,
+ [],
+ exclude_binaries=True,
+ name="start", # Name of the executable.
+ debug=False,
+ bootloader_ignore_signals=False,
+ strip=False,
+ upx=True,
+ console=True, # Set to False to disable the Windows terminal.
+ icon="icon.ico", # Windows icon file.
+)
+coll = COLLECT(
+ exe,
+ a.binaries,
+ a.zipfiles,
+ a.datas,
+ strip=False,
+ upx=True,
+ upx_exclude=[],
+ name="hello_world", # Name of the distribution directory.
+)
diff --git a/examples/distribution/PyInstaller/requirements.txt b/examples/distribution/PyInstaller/requirements.txt
new file mode 100644
index 00000000..24379896
--- /dev/null
+++ b/examples/distribution/PyInstaller/requirements.txt
@@ -0,0 +1,3 @@
+tcod==16.2.3
+pyinstaller==6.14.2
+pypiwin32; sys_platform=="win32"
diff --git a/examples/distribution/cx_Freeze/README.rst b/examples/distribution/cx_Freeze/README.rst
index 507d4f36..12d768c0 100644
--- a/examples/distribution/cx_Freeze/README.rst
+++ b/examples/distribution/cx_Freeze/README.rst
@@ -1,11 +1,14 @@
cx_Freeze Example
=================
-First, install the packages: ``tdl`` and ``cx_Freeze``.
+It's recommended to use a virtual environment to package Python executables.
+Use the following guide on how to set one up:
+https://docs.python.org/3/tutorial/venv.html
-Then run the command::
+Once the virtual environment is active you should install `tcod` and `cx_Freeze` from the `requirements.txt` file, then build using `setup.py`:
- python setup.py build_exe
+ pip install -r requirements.txt
+ python setup.py build
An executable package will be placed in ``build//``
diff --git a/examples/distribution/cx_Freeze/hello_world.py b/examples/distribution/cx_Freeze/hello_world.py
deleted file mode 100644
index 893045fb..00000000
--- a/examples/distribution/cx_Freeze/hello_world.py
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/usr/bin/env python
-
-import tdl
-
-
-WIDTH, HEIGHT = 80, 60
-console = None
-
-def main():
- global console
- tdl.set_font('data/terminal8x8_gs_ro.png')
- console = tdl.init(WIDTH, HEIGHT)
- console.draw_str(0, 0, "Hello World")
- tdl.flush()
- tdl.event.key_wait()
-
-
-if __name__ == '__main__':
- main()
diff --git a/examples/distribution/cx_Freeze/main.py b/examples/distribution/cx_Freeze/main.py
new file mode 100755
index 00000000..569dddad
--- /dev/null
+++ b/examples/distribution/cx_Freeze/main.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+"""cx_Freeze main script example."""
+
+import tcod.console
+import tcod.context
+import tcod.event
+import tcod.tileset
+
+WIDTH, HEIGHT = 80, 60
+console = None
+
+
+def main() -> None:
+ """Entry point function."""
+ tileset = tcod.tileset.load_tilesheet("data/terminal8x8_gs_ro.png", 16, 16, tcod.tileset.CHARMAP_CP437)
+ with tcod.context.new(columns=WIDTH, rows=HEIGHT, tileset=tileset) as context:
+ while True:
+ console = tcod.console.Console(WIDTH, HEIGHT)
+ console.print(0, 0, "Hello World")
+ context.present(console)
+ for event in tcod.event.wait():
+ if isinstance(event, tcod.event.Quit):
+ raise SystemExit
+
+
+if __name__ == "__main__":
+ main()
diff --git a/examples/distribution/cx_Freeze/requirements.txt b/examples/distribution/cx_Freeze/requirements.txt
new file mode 100644
index 00000000..a5d45074
--- /dev/null
+++ b/examples/distribution/cx_Freeze/requirements.txt
@@ -0,0 +1,2 @@
+tcod==12.0.0
+cx-freeze==6.5.3
diff --git a/examples/distribution/cx_Freeze/setup.py b/examples/distribution/cx_Freeze/setup.py
old mode 100644
new mode 100755
index 5a5e5219..50a588db
--- a/examples/distribution/cx_Freeze/setup.py
+++ b/examples/distribution/cx_Freeze/setup.py
@@ -1,14 +1,37 @@
-
+#!/usr/bin/env python
import sys
-from cx_Freeze import setup, Executable
+from cx_Freeze import Executable, setup # type: ignore
-# cx_Freeze options, see documentation.
+# cx_Freeze options, see documentation:
+# https://cx-freeze.readthedocs.io/en/latest/distutils.html#build-exe
build_exe_options = {
- 'packages': ['cffi'],
- 'excludes': [],
- 'include_files': ['data'],
- }
+ "packages": [],
+ "excludes": [
+ "numpy.core.tests",
+ "numpy.distutils",
+ "numpy.doc",
+ "numpy.f2py.tests",
+ "numpy.lib.tests",
+ "numpy.ma.tests",
+ "numpy.ma.testutils",
+ "numpy.matrixlib.tests",
+ "numpy.polynomial.tests",
+ "numpy.random.tests",
+ "numpy.testing",
+ "numpy.tests",
+ "numpy.typing.tests",
+ "distutils",
+ "setuptools",
+ "msilib",
+ "test",
+ "tkinter",
+ "unittest",
+ ],
+ "include_files": ["data"], # Bundle the data directory.
+ "optimize": 1, # Enable release mode.
+ "include_msvcr": False,
+}
# Hide the terminal on Windows apps.
base = None
@@ -16,7 +39,15 @@
base = "Win32GUI"
setup(
- name='tdl cxfreeze example',
- options = {'build_exe': build_exe_options},
- executables = [Executable('hello_world.py', base=base)],
- )
+ name="tcod cx_Freeze example",
+ options={"build_exe": build_exe_options},
+ executables=[
+ # cx_Freeze Executable options:
+ # https://cx-freeze.readthedocs.io/en/latest/distutils.html#cx-freeze-executable
+ Executable(
+ script="main.py",
+ base=base,
+ target_name="start", # Name of the target executable.
+ )
+ ],
+)
diff --git a/examples/eventget.py b/examples/eventget.py
new file mode 100755
index 00000000..9dc82ca3
--- /dev/null
+++ b/examples/eventget.py
@@ -0,0 +1,62 @@
+#!/usr/bin/env python
+# To the extent possible under law, the libtcod maintainers have waived all
+# copyright and related or neighboring rights for this example. This work is
+# published from: United States.
+# https://creativecommons.org/publicdomain/zero/1.0/
+"""An demonstration of event handling using the tcod.event module."""
+
+import tcod.context
+import tcod.event
+import tcod.sdl.joystick
+
+WIDTH, HEIGHT = 1280, 720
+
+
+def main() -> None: # noqa: C901, PLR0912
+ """Example program for tcod.event."""
+ event_log: list[str] = []
+ motion_desc = ""
+ tcod.sdl.joystick.init()
+ controllers: set[tcod.sdl.joystick.GameController] = set()
+ joysticks: set[tcod.sdl.joystick.Joystick] = set()
+
+ with tcod.context.new(width=WIDTH, height=HEIGHT) as context:
+ if context.sdl_window:
+ context.sdl_window.start_text_input()
+ console = context.new_console()
+ while True:
+ # Display all event items.
+ console.clear()
+ console.print(0, console.height - 1, motion_desc)
+ for i, item in enumerate(event_log[::-1]):
+ y = console.height - 3 - i
+ if y < 0:
+ break
+ console.print(0, y, item)
+ context.present(console, integer_scaling=True)
+
+ # Handle events.
+ for event in tcod.event.wait():
+ context.convert_event(event) # Set tile coordinates for event.
+ print(repr(event))
+ match event:
+ case tcod.event.Quit():
+ raise SystemExit
+ case tcod.event.WindowResized(type="WindowResized"):
+ console = context.new_console()
+ case tcod.event.ControllerDevice(type="CONTROLLERDEVICEADDED", controller=controller):
+ controllers.add(controller)
+ case tcod.event.ControllerDevice(type="CONTROLLERDEVICEREMOVED", controller=controller):
+ controllers.remove(controller)
+ case tcod.event.JoystickDevice(type="JOYDEVICEADDED", joystick=joystick):
+ joysticks.add(joystick)
+ case tcod.event.JoystickDevice(type="JOYDEVICEREMOVED", joystick=joystick):
+ joysticks.remove(joystick)
+ case tcod.event.MouseMotion():
+ motion_desc = str(event)
+ if not isinstance(event, tcod.event.MouseMotion): # Log all events other than MouseMotion
+ event_log.append(repr(event))
+
+
+if __name__ == "__main__":
+ main()
diff --git a/examples/eventget_tcod.py b/examples/eventget_tcod.py
deleted file mode 100644
index 3e31dd7d..00000000
--- a/examples/eventget_tcod.py
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/usr/bin/env python
-
-import tcod
-
-WIDTH, HEIGHT = 80, 60
-
-key = tcod.Key()
-mouse = tcod.Mouse()
-
-with tcod.console_init_root(WIDTH, HEIGHT, 'tcod events example') as console:
- tcod.sys_set_fps(24)
- while not tcod.console_is_window_closed():
- ev = tcod.sys_wait_for_event(tcod.EVENT_ANY, key, mouse, False)
- if ev & tcod.EVENT_KEY:
- console.blit(console, 0, 0, 0, 1, WIDTH, HEIGHT - 2)
- console.print_(0, HEIGHT - 3, repr(key))
- print(key)
- if ev & tcod.EVENT_MOUSE_MOVE:
- console.rect(0, HEIGHT - 1, WIDTH, 1, True)
- console.print_(0, HEIGHT - 1, repr(mouse))
- print(mouse)
- elif ev & tcod.EVENT_MOUSE:
- console.blit(console, 0, 0, 0, 1, WIDTH, HEIGHT - 2)
- console.print_(0, HEIGHT - 3, repr(mouse))
- print(mouse)
- tcod.console_flush()
diff --git a/examples/eventget_tdl.py b/examples/eventget_tdl.py
deleted file mode 100755
index 4941c5de..00000000
--- a/examples/eventget_tdl.py
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/usr/bin/env python
-"""
- An interactive example of what events are available.
-"""
-
-import tdl
-
-WIDTH, HEIGHT = 80, 60
-
-console = tdl.init(WIDTH, HEIGHT)
-
-# the scrolling text window
-textWindow = tdl.Window(console, 0, 0, WIDTH, -2)
-
-# slow down the program so that the user can more clearly see the motion events
-tdl.set_fps(24)
-
-while 1:
- event = tdl.event.wait()
- print(event)
- if event.type == 'QUIT':
- raise SystemExit()
- elif event.type == 'MOUSEMOTION':
- # clear and print to the bottom of the console
- console.draw_rect(0, HEIGHT - 1, None, None, ' ')
- console.draw_str(0, HEIGHT - 1, 'MOUSEMOTION event - pos=%i,%i cell=%i,%i motion=%i,%i cellmotion=%i,%i' % (event.pos + event.cell + event.motion + event.cellmotion))
- continue # prevent scrolling
-
- textWindow.scroll(0, -1)
- if event.type == 'KEYDOWN' or event.type == 'KEYUP':
- textWindow.draw_str(0, HEIGHT-3, '%s event - char=%s key=%s alt=%i control=%i shift=%i' % (event.type.ljust(7), repr(event.char), repr(event.key), event.alt, event.control, event.shift))
- elif event.type == 'MOUSEDOWN' or event.type == 'MOUSEUP':
- textWindow.draw_str(0, HEIGHT-3, '%s event - pos=%i,%i cell=%i,%i button=%s' % ((event.type.ljust(9),) + event.pos + event.cell + (repr(event.button),)))
diff --git a/examples/framerate.py b/examples/framerate.py
new file mode 100755
index 00000000..cc1195de
--- /dev/null
+++ b/examples/framerate.py
@@ -0,0 +1,146 @@
+#!/usr/bin/env python
+# To the extent possible under law, the libtcod maintainers have waived all
+# copyright and related or neighboring rights for this example. This work is
+# published from: United States.
+# https://creativecommons.org/publicdomain/zero/1.0/
+"""A system to control time since the original libtcod tools are deprecated."""
+
+import statistics
+import time
+from collections import deque
+
+import tcod
+
+WIDTH, HEIGHT = 720, 480
+
+
+class Clock:
+ """Measure framerate performance and sync to a given framerate.
+
+ Everything important is handled by `Clock.sync`. You can use the fps
+ properties to track the performance of an application.
+ """
+
+ def __init__(self) -> None:
+ """Initialize this object with empty data."""
+ self.last_time = time.perf_counter() # Last time this was synced.
+ self.time_samples: deque[float] = deque() # Delta time samples.
+ self.max_samples = 64 # Number of fps samples to log. Can be changed.
+ self.drift_time = 0.0 # Tracks how much the last frame was overshot.
+
+ def sync(self, fps: float | None = None) -> float:
+ """Sync to a given framerate and return the delta time.
+
+ `fps` is the desired framerate in frames-per-second. If None is given
+ then this function will track the time and framerate without waiting.
+
+ `fps` must be above zero when given.
+ """
+ if fps is not None:
+ # Wait until a target time based on the last time and framerate.
+ desired_frame_time = 1 / fps
+ target_time = self.last_time + desired_frame_time - self.drift_time
+ # Sleep might take slightly longer than asked.
+ sleep_time = max(0, target_time - self.last_time - 0.001)
+ if sleep_time:
+ time.sleep(sleep_time)
+ # Busy wait until the target_time is reached.
+ while (drift_time := time.perf_counter() - target_time) < 0:
+ pass
+ self.drift_time = min(drift_time, desired_frame_time)
+
+ # Get the delta time.
+ current_time = time.perf_counter()
+ delta_time = max(0, current_time - self.last_time)
+ self.last_time = current_time
+
+ # Record the performance of the current frame.
+ self.time_samples.append(delta_time)
+ while len(self.time_samples) > self.max_samples:
+ self.time_samples.popleft()
+
+ return delta_time
+
+ @property
+ def min_fps(self) -> float:
+ """The FPS of the slowest frame."""
+ try:
+ return 1 / max(self.time_samples)
+ except (ValueError, ZeroDivisionError):
+ return 0
+
+ @property
+ def max_fps(self) -> float:
+ """The FPS of the fastest frame."""
+ try:
+ return 1 / min(self.time_samples)
+ except (ValueError, ZeroDivisionError):
+ return 0
+
+ @property
+ def mean_fps(self) -> float:
+ """The FPS of the sampled frames overall."""
+ if not self.time_samples:
+ return 0
+ try:
+ return 1 / statistics.fmean(self.time_samples)
+ except ZeroDivisionError:
+ return 0
+
+ @property
+ def median_fps(self) -> float:
+ """The FPS of the median frame."""
+ if not self.time_samples:
+ return 0
+ try:
+ return 1 / statistics.median(self.time_samples)
+ except ZeroDivisionError:
+ return 0
+
+ @property
+ def last_fps(self) -> float:
+ """The FPS of the most recent frame."""
+ if not self.time_samples or self.time_samples[-1] == 0:
+ return 0
+ return 1 / self.time_samples[-1]
+
+
+def main() -> None:
+ """Example program for Clock."""
+ # vsync is False in this example, but you'll want it to be True unless you
+ # need to benchmark or set framerates above 60 FPS.
+ with tcod.context.new(width=WIDTH, height=HEIGHT, vsync=False) as context:
+ line_x = 0 # Highlight a line, helpful to measure frames visually.
+ clock = Clock()
+ delta_time = 0.0 # The time passed between frames.
+ desired_fps = 50
+ while True:
+ console = context.new_console(order="F")
+ console.tiles_rgb["bg"][line_x % console.width, :] = (255, 0, 0)
+ console.print(
+ 1,
+ 1,
+ f"Current time:{time.perf_counter() * 1000:8.2f}ms"
+ f"\nDelta time:{delta_time * 1000:8.2f}ms"
+ f"\nDesired FPS:{desired_fps:3d} (use scroll wheel to adjust)"
+ f"\n last:{clock.last_fps:.2f} fps"
+ f"\n mean:{clock.mean_fps:.2f} fps"
+ f"\nmedian:{clock.median_fps:.2f} fps"
+ f"\n min:{clock.min_fps:.2f} fps"
+ f"\n max:{clock.max_fps:.2f} fps",
+ )
+ context.present(console, integer_scaling=True)
+ delta_time = clock.sync(fps=desired_fps)
+ line_x += 1
+
+ # Handle events.
+ for event in tcod.event.get():
+ context.convert_event(event) # Set tile coordinates for event.
+ if isinstance(event, tcod.event.Quit):
+ raise SystemExit
+ if isinstance(event, tcod.event.MouseWheel):
+ desired_fps = max(1, desired_fps + event.y)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/examples/helloWorld.py b/examples/helloWorld.py
deleted file mode 100755
index a56014ee..00000000
--- a/examples/helloWorld.py
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/usr/bin/env python
-"""
- Example showing some of the most basic functions needed to say "Hello World"
-"""
-# import the tdl library and all of it's functions, this gives us access to anything
-# starting with "tdl."
-import tdl
-
-# start the main console, this will open the window that you see and give you a Console.
-# we make a small window that's 20 tiles wide and 16 tile's tall for this example.
-console = tdl.init(20, 16)
-
-# draw the string "Hello World" at the top left corner using the default colors:
-# a white forground on a black background.
-console.draw_str(0, 0, 'Hello World')
-
-# display the changes to the console with flush.
-# if you forget this part the screen will stay black emptiness forever.
-tdl.flush()
-
-# wait for a key press, any key pressed now will cause the program flow to the next part
-# which closes out of the program.
-tdl.event.keyWait()
-
-# if you run this example in IDLE then we'll need to delete the console manually
-# otherwise IDLE prevents the window from closing causing it to hang
-del console
diff --git a/examples/interactive.py b/examples/interactive.py
deleted file mode 100755
index bfd16c3c..00000000
--- a/examples/interactive.py
+++ /dev/null
@@ -1,124 +0,0 @@
-#!/usr/bin/env python
-"""
- Not much commentary in this example. It's more of a demo.
-"""
-import sys
-import code
-import io
-import time
-import traceback
-
-import tdl
-
-sys.ps1 = '>>> '
-sys.ps2 = '... '
-
-WIDTH, HEIGHT = 80, 50
-console = tdl.init(WIDTH, HEIGHT, 'Python Interpeter in TDL')
-console.setMode('scroll')
-
-class TDLPrint(io.TextIOBase):
- def __init__(self, fgcolor=(255, 255, 255), bgcolor=(0, 0, 0)):
- self.colors = fgcolor, bgcolor
-
- def write(self, string):
- olderr.write(string)
- console.setColors(*self.colors)
- console.write(string)
-
-#sys.stdout = TDLPrint()
-sys.stdout = console
-sys.stdout.move(0, HEIGHT-1)
-olderr = newerr = sys.stderr
-newerr = TDLPrint((255, 255, 255), (127, 0, 0))
-
-def exit():
- raise SystemExit
-
-interpeter = code.InteractiveConsole({'tdl':tdl,
- 'console':console,
- 'exit':exit})
-
-def main():
- print()
- print('Python %s' % sys.version)
- print('Press ESC to quit')
-
- buffer = ''
- commands = ['']
- banner = sys.ps1
- cursor = 0
- while 1:
- console.draw_rect(0, HEIGHT-1, None, 1, ' ', (255, 255, 255), (0, 0, 0))
- console.draw_str(0, HEIGHT-1, banner + buffer)
- try:
- console.draw_char(len(banner) + cursor, HEIGHT-1, None, None, (0, 255, 255))
- except tdl.TDLError:
- pass
- tdl.flush()
-
- for event in tdl.event.get():
- if event.type == 'QUIT':
- raise SystemExit()
- if event.type == 'KEYDOWN':
- if event.key == 'ENTER' or event.key == 'KPENTER':
- sys.stderr = newerr
- try:
- console.draw_rect(0, HEIGHT-1, None, 1, None, (255, 255, 255), (0, 0, 0))
- console.scroll(0, -1)
- if interpeter.push(buffer):
- banner = sys.ps2
- else:
- banner = sys.ps1
- except SystemExit:
- raise
- except:
- sys.excepthook(*sys.exc_info())
- banner = sys.ps1
- finally:
- sys.stderr = olderr
- sys.stdout = olderr
- sys.stdout = console
- if buffer not in commands:
- commands.append(buffer)
- buffer = ''
- elif event.key == 'BACKSPACE':
- if cursor == 0:
- continue
- if buffer[:cursor][-4:] == ' ':
- buffer = buffer[:cursor-4] + buffer[cursor:]
- cursor -= 4
- elif buffer:
- buffer = buffer[:cursor-1] + buffer[cursor:]
- cursor -= 1
- elif event.key == 'DELETE':
- buffer = buffer[:cursor] + buffer[cursor+1:]
- elif event.key == 'LEFT':
- cursor -= 1
- elif event.key == 'RIGHT':
- cursor += 1
- elif event.key == 'HOME':
- cursor = 0
- elif event.key == 'END':
- cursor = len(buffer)
- elif event.key == 'UP':
- commands.insert(0, buffer)
- buffer = commands.pop()
- cursor = len(buffer)
- elif event.key == 'DOWN':
- commands.append(buffer)
- buffer = commands.pop(0)
- cursor = len(buffer)
- elif event.key == 'TAB':
- buffer = buffer[:cursor] + ' ' + buffer[cursor:]
- cursor += 4
- elif event.key == 'ESCAPE':
- raise SystemExit()
- elif event.char:
- buffer = buffer[:cursor] + event.char + buffer[cursor:]
- cursor += 1
- cursor = max(0, min(cursor, len(buffer)))
- time.sleep(.01)
-
-if __name__ == '__main__':
- main()
diff --git a/examples/life.py b/examples/life.py
deleted file mode 100755
index d6bc8726..00000000
--- a/examples/life.py
+++ /dev/null
@@ -1,167 +0,0 @@
-#!/usr/bin/env python
-
-import random
-import time
-
-import tdl
-
-WIDTH = 80
-HEIGHT = 40
-
-class LifeBoard():
-
- def __init__(self, width, height):
- self.width = width
- self.height = height
- self.live_cells = set()
- self.wrap = True
-
- def set(self, x, y, value):
- if value:
- self.live_cells.add((x, y))
- else:
- self.live_cells.discard((x, y))
-
- def set_batch(self, x, y, batch):
- for y_, line in enumerate(batch):
- for x_, char in enumerate(line):
- self.set(x + x_, y + y_, char != ' ')
-
- def get(self, x, y):
- if(self.wrap is False
- and not (0 <= x < self.width and 0 <= y < self.height)):
- return False
- return (x % self.width, y % self.height) in self.live_cells
-
- def clear(self):
- self.live_cells.clear()
-
- def toggle(self, x, y):
- self.live_cells.symmetric_difference_update([(x, y)])
-
- def wrap_edges(self):
- for x in range(-1, self.width + 1):
- self.set(x, -1, self.get(x, -1))
- self.set(x, self.height, self.get(x, self.height))
- for y in range(self.height):
- self.set(-1, y, self.get(-1, y))
- self.set(self.width, y, self.get(self.width, y))
-
-
- def get_neighbours(self, x, y):
- return len(self.live_cells & {(x - 1, y - 1), (x, y - 1),
- (x + 1,y - 1), (x + 1, y),
- (x + 1, y + 1), (x, y + 1),
- (x - 1, y + 1), (x - 1, y)})
-
- def rule(self, is_alive, neighbours):
- """
- 1. Any live cell with fewer than two live neighbours dies, as if caused
- by under-population.
- 2. Any live cell with two or three live neighbours lives on to the next
- generation.
- 3. Any live cell with more than three live neighbours dies, as if by
- overcrowding.
- 4. Any dead cell with exactly three live neighbours becomes a live
- cell, as if by reproduction.
- """
- if is_alive:
- return 2 <= neighbours <= 3
- else:
- return neighbours == 3
-
- def step(self):
- self.wrap_edges()
- next_generation = set()
- for x in range(self.width):
- for y in range(self.height):
- if self.rule(self.get(x, y), self.get_neighbours(x, y)):
- next_generation.add((x, y))
- self.live_cells = next_generation
-
-def main():
- console = tdl.init(WIDTH, HEIGHT)
- board = LifeBoard(WIDTH, HEIGHT - 1)
- # The R-pentomino
- #board.set_batch(WIDTH // 2 - 2,HEIGHT // 2 - 2,
- # [' **',
- # '** ',
- # ' * '])
-
- # Diehard
- #board.set_batch(WIDTH // 2 - 5,HEIGHT // 2 - 2,
- # [' * ',
- # '** ',
- # ' * ***'])
-
- # Gosper glider gun
- board.set_batch(1, 1,
- [' ',
- ' * ',
- ' * * ',
- ' ** ** **',
- ' * * ** **',
- '** * * ** ',
- '** * * ** * * ',
- ' * * * ',
- ' * * ',
- ' ** '])
-
- play = False
- redraw = True
- mouse_drawing = None
- mouse_x = -1
- mouse_y = -1
- while True:
- for event in tdl.event.get():
- if event.type == 'QUIT':
- return
- elif event.type == 'KEYDOWN':
- if event.key == 'SPACE':
- play = not play
- redraw = True
- elif event.char.upper() == 'S':
- board.step()
- redraw = True
- elif event.char.upper() == 'C':
- board.clear()
- redraw = True
- elif event.char.upper() == 'W':
- board.wrap = not board.wrap
- redraw = True
- elif event.type == 'MOUSEDOWN':
- x, y, = event.cell
- board.toggle(x, y)
- mouse_drawing = event.cell
- redraw = True
- elif event.type == 'MOUSEUP':
- mouse_drawing = None
- elif event.type == 'MOUSEMOTION':
- if(mouse_drawing and mouse_drawing != event.cell):
- x, y = mouse_drawing = event.cell
- board.toggle(x, y)
- mouse_x, mouse_y = event.cell
- redraw = True
- if play and mouse_drawing is None:
- board.step()
- redraw = True
- if redraw:
- redraw = False
- console.clear()
- for x, y in board.live_cells:
- console.draw_char(x, y, '*')
- #console.draw_rect(0, -1, None, None, None, bg=(64, 64, 80))
- console.draw_rect(0, -1, None, None, None, bg=(64, 64, 80))
- console.draw_str(0, -1, "Mouse:Toggle Cells, Space:%5s, [S]tep, [C]lear, [W]rap Turn %s" % (['Play', 'Pause'][play], ['On', 'Off'][board.wrap]), None, None)
- if (mouse_x, mouse_y) in console:
- console.draw_char(mouse_x, mouse_y,
- None, (0, 0, 0), (255, 255, 255))
- else:
- time.sleep(0.01)
- tdl.flush()
- tdl.set_title("Conway's Game of Life - %i FPS" % tdl.get_fps())
-
-
-if __name__ == '__main__':
- main()
-
diff --git a/examples/samples_libtcodpy.py b/examples/samples_libtcodpy.py
old mode 100644
new mode 100755
index 49fa1d2d..71780d97
--- a/examples/samples_libtcodpy.py
+++ b/examples/samples_libtcodpy.py
@@ -4,27 +4,34 @@
# This code demonstrates various usages of libtcod modules
# It's in the public domain.
#
+# ruff: noqa
from __future__ import division
import math
import os
+import sys
+import warnings
-import tcod as libtcod
+from tcod import tcod as libtcod
+
+if not sys.warnoptions:
+ warnings.simplefilter("ignore") # Prevent flood of deprecation warnings.
+
+
+def get_data(path: str) -> str:
+ """Return the path to a resource in the libtcod data directory,"""
+ SCRIPT_DIR = os.path.dirname(__file__)
+ DATA_DIR = os.path.join(SCRIPT_DIR, "../libtcod/data")
+ return os.path.join(DATA_DIR, path)
-# Import Psyco if available
-try:
- import psyco
- psyco.full()
-except ImportError:
- pass
SAMPLE_SCREEN_WIDTH = 46
SAMPLE_SCREEN_HEIGHT = 20
SAMPLE_SCREEN_X = 20
SAMPLE_SCREEN_Y = 10
-font = os.path.join(b'data', b'fonts', b'consolas10x10_gs_tc.png')
+font = get_data("fonts/dejavu10x10_gs_tc.png")
libtcod.console_set_custom_font(font, libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_TCOD)
-libtcod.console_init_root(80, 50, b'libtcod python sample', False)
+libtcod.console_init_root(80, 50, b"libtcod python sample", False)
sample_console = libtcod.console_new(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT)
#############################################
@@ -32,99 +39,75 @@
#############################################
# parser declaration
if True:
- print ('***** File Parser test *****')
- parser=libtcod.parser_new()
- struct=libtcod.parser_new_struct(parser, b'myStruct')
- libtcod.struct_add_property(struct, b'bool_field', libtcod.TYPE_BOOL, True)
- libtcod.struct_add_property(struct, b'char_field', libtcod.TYPE_CHAR, True)
- libtcod.struct_add_property(struct, b'int_field', libtcod.TYPE_INT, True)
- libtcod.struct_add_property(struct, b'float_field', libtcod.TYPE_FLOAT, True)
- libtcod.struct_add_property(struct, b'color_field', libtcod.TYPE_COLOR, True)
- libtcod.struct_add_property(struct, b'dice_field', libtcod.TYPE_DICE, True)
- libtcod.struct_add_property(struct, b'string_field', libtcod.TYPE_STRING,
- True)
- libtcod.struct_add_list_property(struct, b'bool_list', libtcod.TYPE_BOOL,
- True)
- libtcod.struct_add_list_property(struct, b'char_list', libtcod.TYPE_CHAR,
- True)
- libtcod.struct_add_list_property(struct, b'integer_list', libtcod.TYPE_INT,
- True)
- libtcod.struct_add_list_property(struct, b'float_list', libtcod.TYPE_FLOAT,
- True)
- libtcod.struct_add_list_property(struct, b'string_list', libtcod.TYPE_STRING,
- True)
- libtcod.struct_add_list_property(struct, b'color_list', libtcod.TYPE_COLOR,
- True)
-## # dice lists doesn't work yet
-## libtcod.struct_add_list_property(struct, b'dice_list', libtcod.TYPE_DICE,
-## True)
+ print("***** File Parser test *****")
+ parser = libtcod.parser_new()
+ struct = libtcod.parser_new_struct(parser, b"myStruct")
+ libtcod.struct_add_property(struct, b"bool_field", libtcod.TYPE_BOOL, True)
+ libtcod.struct_add_property(struct, b"char_field", libtcod.TYPE_CHAR, True)
+ libtcod.struct_add_property(struct, b"int_field", libtcod.TYPE_INT, True)
+ libtcod.struct_add_property(struct, b"float_field", libtcod.TYPE_FLOAT, True)
+ libtcod.struct_add_property(struct, b"color_field", libtcod.TYPE_COLOR, True)
+ libtcod.struct_add_property(struct, b"dice_field", libtcod.TYPE_DICE, True)
+ libtcod.struct_add_property(struct, b"string_field", libtcod.TYPE_STRING, True)
+ libtcod.struct_add_list_property(struct, b"bool_list", libtcod.TYPE_BOOL, True)
+ libtcod.struct_add_list_property(struct, b"char_list", libtcod.TYPE_CHAR, True)
+ libtcod.struct_add_list_property(struct, b"integer_list", libtcod.TYPE_INT, True)
+ libtcod.struct_add_list_property(struct, b"float_list", libtcod.TYPE_FLOAT, True)
+ libtcod.struct_add_list_property(struct, b"string_list", libtcod.TYPE_STRING, True)
+ libtcod.struct_add_list_property(struct, b"color_list", libtcod.TYPE_COLOR, True)
+ ## # dice lists doesn't work yet
+ ## libtcod.struct_add_list_property(struct, b'dice_list', libtcod.TYPE_DICE,
+ ## True)
# default listener
- print ('***** Default listener *****')
- libtcod.parser_run(parser, os.path.join(b'data', b'cfg', b'sample.cfg'))
- print ('bool_field : ', \
- libtcod.parser_get_bool_property(parser, b'myStruct.bool_field'))
- print ('char_field : ', \
- libtcod.parser_get_char_property(parser, b'myStruct.char_field'))
- print ('int_field : ', \
- libtcod.parser_get_int_property(parser, b'myStruct.int_field'))
- print ('float_field : ', \
- libtcod.parser_get_float_property(parser, b'myStruct.float_field'))
- print ('color_field : ', \
- libtcod.parser_get_color_property(parser, b'myStruct.color_field'))
- print ('dice_field : ', \
- libtcod.parser_get_dice_property(parser, b'myStruct.dice_field'))
- print ('string_field : ', \
- libtcod.parser_get_string_property(parser, b'myStruct.string_field'))
- print ('bool_list : ', \
- libtcod.parser_get_list_property(parser, b'myStruct.bool_list',
- libtcod.TYPE_BOOL))
- print ('char_list : ', \
- libtcod.parser_get_list_property(parser, b'myStruct.char_list',
- libtcod.TYPE_CHAR))
- print ('integer_list : ', \
- libtcod.parser_get_list_property(parser, b'myStruct.integer_list',
- libtcod.TYPE_INT))
- print ('float_list : ', \
- libtcod.parser_get_list_property(parser, b'myStruct.float_list',
- libtcod.TYPE_FLOAT))
- print ('string_list : ', \
- libtcod.parser_get_list_property(parser, b'myStruct.string_list',
- libtcod.TYPE_STRING))
- print ('color_list : ', \
- libtcod.parser_get_list_property(parser, b'myStruct.color_list',
- libtcod.TYPE_COLOR))
-## print ('dice_list : ', \
-## libtcod.parser_get_list_property(parser, b'myStruct.dice_list',
-## libtcod.TYPE_DICE))
+ print("***** Default listener *****")
+ libtcod.parser_run(parser, get_data("cfg/sample.cfg"))
+ print("bool_field : ", libtcod.parser_get_bool_property(parser, b"myStruct.bool_field"))
+ print("char_field : ", libtcod.parser_get_char_property(parser, b"myStruct.char_field"))
+ print("int_field : ", libtcod.parser_get_int_property(parser, b"myStruct.int_field"))
+ print("float_field : ", libtcod.parser_get_float_property(parser, b"myStruct.float_field"))
+ print("color_field : ", libtcod.parser_get_color_property(parser, b"myStruct.color_field"))
+ print("dice_field : ", libtcod.parser_get_dice_property(parser, b"myStruct.dice_field"))
+ print("string_field : ", libtcod.parser_get_string_property(parser, b"myStruct.string_field"))
+ print("bool_list : ", libtcod.parser_get_list_property(parser, b"myStruct.bool_list", libtcod.TYPE_BOOL))
+ print("char_list : ", libtcod.parser_get_list_property(parser, b"myStruct.char_list", libtcod.TYPE_CHAR))
+ print("integer_list : ", libtcod.parser_get_list_property(parser, b"myStruct.integer_list", libtcod.TYPE_INT))
+ print("float_list : ", libtcod.parser_get_list_property(parser, b"myStruct.float_list", libtcod.TYPE_FLOAT))
+ print("string_list : ", libtcod.parser_get_list_property(parser, b"myStruct.string_list", libtcod.TYPE_STRING))
+ print("color_list : ", libtcod.parser_get_list_property(parser, b"myStruct.color_list", libtcod.TYPE_COLOR))
+ ## print ('dice_list : ', \
+ ## libtcod.parser_get_list_property(parser, b'myStruct.dice_list',
+ ## libtcod.TYPE_DICE))
# custom listener
- print ('***** Custom listener *****')
+ print("***** Custom listener *****")
+
class MyListener:
def new_struct(self, struct, name):
- print ('new structure type', libtcod.struct_get_name(struct), \
- ' named ', name )
+ print("new structure type", libtcod.struct_get_name(struct), " named ", name)
return True
+
def new_flag(self, name):
- print ('new flag named ', name)
+ print("new flag named ", name)
return True
- def new_property(self,name, typ, value):
- type_names = ['NONE', 'BOOL', 'CHAR', 'INT', 'FLOAT', 'STRING', \
- 'COLOR', 'DICE']
- type_name = type_names[typ & 0xff]
+
+ def new_property(self, name, typ, value):
+ type_names = ["NONE", "BOOL", "CHAR", "INT", "FLOAT", "STRING", "COLOR", "DICE"]
+ type_name = type_names[typ & 0xFF]
if typ & libtcod.TYPE_LIST:
- type_name = 'LIST<%s>' % type_name
- print ('new property named ', name,' type ',type_name, \
- ' value ', value)
+ type_name = "LIST<%s>" % type_name
+ print("new property named ", name, " type ", type_name, " value ", value)
return True
+
def end_struct(self, struct, name):
- print ('end structure type', libtcod.struct_get_name(struct), \
- ' named ', name)
+ print("end structure type", libtcod.struct_get_name(struct), " named ", name)
return True
- def error(self,msg):
- print ('error : ', msg)
+
+ def error(self, msg):
+ print("error : ", msg)
return True
- libtcod.parser_run(parser, os.path.join(b'data',b'cfg',b'sample.cfg'), MyListener())
+
+ libtcod.parser_run(parser, get_data("cfg/sample.cfg"), MyListener())
#############################################
# end of parser unit test
#############################################
@@ -132,14 +115,17 @@ def error(self,msg):
#############################################
# true color sample
#############################################
-tc_cols = [libtcod.Color(50, 40, 150),
- libtcod.Color(240, 85, 5),
- libtcod.Color(50, 35, 240),
- libtcod.Color(10, 200, 130),
- ]
+tc_cols = [
+ libtcod.Color(50, 40, 150),
+ libtcod.Color(240, 85, 5),
+ libtcod.Color(50, 35, 240),
+ libtcod.Color(10, 200, 130),
+]
tc_dirr = [1, -1, 1, 1]
tc_dirg = [1, -1, -1, 1]
tc_dirb = [1, 1, 1, -1]
+
+
def render_colors(first, key, mouse):
global tc_cols, tc_dirr, tc_dirg, tc_dirb, tc_fast
@@ -153,7 +139,7 @@ def render_colors(first, key, mouse):
tc_fast = False
for c in range(4):
# move each corner color
- component=libtcod.random_get_int(None, 0, 2)
+ component = libtcod.random_get_int(None, 0, 2)
if component == 0:
tc_cols[c].r += 5 * tc_dirr[c]
if tc_cols[c].r == 255:
@@ -178,15 +164,12 @@ def render_colors(first, key, mouse):
for x in range(SAMPLE_SCREEN_WIDTH):
xcoef = float(x) / (SAMPLE_SCREEN_WIDTH - 1)
top = libtcod.color_lerp(tc_cols[TOPLEFT], tc_cols[TOPRIGHT], xcoef)
- bottom = libtcod.color_lerp(tc_cols[BOTTOMLEFT], tc_cols[BOTTOMRIGHT],
- xcoef)
+ bottom = libtcod.color_lerp(tc_cols[BOTTOMLEFT], tc_cols[BOTTOMRIGHT], xcoef)
for y in range(SAMPLE_SCREEN_HEIGHT):
ycoef = float(y) / (SAMPLE_SCREEN_HEIGHT - 1)
curColor = libtcod.color_lerp(top, bottom, ycoef)
- libtcod.console_set_char_background(sample_console, x, y, curColor,
- libtcod.BKGND_SET)
- textColor = libtcod.console_get_char_background(sample_console,
- SAMPLE_SCREEN_WIDTH // 2, 5)
+ libtcod.console_set_char_background(sample_console, x, y, curColor, libtcod.BKGND_SET)
+ textColor = libtcod.console_get_char_background(sample_console, SAMPLE_SCREEN_WIDTH // 2, 5)
textColor.r = 255 - textColor.r
textColor.g = 255 - textColor.g
textColor.b = 255 - textColor.b
@@ -195,14 +178,13 @@ def render_colors(first, key, mouse):
for y in range(SAMPLE_SCREEN_HEIGHT):
col = libtcod.console_get_char_background(sample_console, x, y)
col = libtcod.color_lerp(col, libtcod.black, 0.5)
- c = libtcod.random_get_int(None, ord('a'), ord('z'))
+ c = libtcod.random_get_int(None, ord("a"), ord("z"))
libtcod.console_set_default_foreground(sample_console, col)
- libtcod.console_put_char(sample_console, x, y, c,
- libtcod.BKGND_NONE)
+ libtcod.console_put_char(sample_console, x, y, c, libtcod.BKGND_NONE)
else:
# same, but using the ConsoleBuffer class to speed up rendering
buffer = libtcod.ConsoleBuffer(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT) # initialize buffer
- c = libtcod.random_get_int(None, ord('a'), ord('z'))
+ c = libtcod.random_get_int(None, ord("a"), ord("z"))
for x in range(SAMPLE_SCREEN_WIDTH):
xcoef = float(x) / (SAMPLE_SCREEN_WIDTH - 1)
top = libtcod.color_lerp(tc_cols[TOPLEFT], tc_cols[TOPRIGHT], xcoef)
@@ -215,25 +197,32 @@ def render_colors(first, key, mouse):
g = int(top.g * ycoef + bottom.g * (1 - ycoef))
b = int(top.b * ycoef + bottom.b * (1 - ycoef))
c += 1
- if c > ord('z'): c = ord('a')
+ if c > ord("z"):
+ c = ord("a")
# set background, foreground and char with a single function
buffer.set(x, y, r, g, b, r // 2, g // 2, b // 2, chr(c))
buffer.blit(sample_console) # update console with the buffer's contents
libtcod.console_set_default_foreground(sample_console, libtcod.Color(int(r), int(g), int(b)))
libtcod.console_set_default_background(sample_console, libtcod.grey)
- libtcod.console_print_rect_ex(sample_console, SAMPLE_SCREEN_WIDTH // 2,
- 5, SAMPLE_SCREEN_WIDTH - 2,
- SAMPLE_SCREEN_HEIGHT - 1,
- libtcod.BKGND_MULTIPLY, libtcod.CENTER,
- "The Doryen library uses 24 bits "
- "colors, for both background and "
- "foreground.")
-
- if key.c == ord('f'): tc_fast = not tc_fast
+ libtcod.console_print_rect_ex(
+ sample_console,
+ SAMPLE_SCREEN_WIDTH // 2,
+ 5,
+ SAMPLE_SCREEN_WIDTH - 2,
+ SAMPLE_SCREEN_HEIGHT - 1,
+ libtcod.BKGND_MULTIPLY,
+ libtcod.CENTER,
+ "The Doryen library uses 24 bits colors, for both background and foreground.",
+ )
+
+ if key.c == ord("f"):
+ tc_fast = not tc_fast
libtcod.console_set_default_foreground(sample_console, libtcod.white)
- libtcod.console_print(sample_console, 1, SAMPLE_SCREEN_HEIGHT - 2,
- "F : turn fast rendering %s" % ("off" if tc_fast else "on"))
+ libtcod.console_print(
+ sample_console, 1, SAMPLE_SCREEN_HEIGHT - 2, "F : turn fast rendering %s" % ("off" if tc_fast else "on")
+ )
+
#############################################
# offscreen console sample
@@ -246,32 +235,40 @@ def render_colors(first, key, mouse):
oc_init = False
oc_xdir = 1
oc_ydir = 1
+
+
def render_offscreen(first, key, mouse):
global oc_secondary, oc_screenshot
global oc_counter, oc_x, oc_y, oc_init, oc_xdir, oc_ydir
if not oc_init:
oc_init = True
- oc_secondary = libtcod.console_new(SAMPLE_SCREEN_WIDTH // 2,
- SAMPLE_SCREEN_HEIGHT // 2)
- oc_screenshot = libtcod.console_new(SAMPLE_SCREEN_WIDTH,
- SAMPLE_SCREEN_HEIGHT)
- libtcod.console_print_frame(oc_secondary, 0, 0, SAMPLE_SCREEN_WIDTH // 2,
- SAMPLE_SCREEN_HEIGHT // 2, False, libtcod.BKGND_NONE,
- b'Offscreen console')
- libtcod.console_print_rect_ex(oc_secondary, SAMPLE_SCREEN_WIDTH // 4,
- 2, SAMPLE_SCREEN_WIDTH // 2 - 2,
- SAMPLE_SCREEN_HEIGHT // 2,
- libtcod.BKGND_NONE, libtcod.CENTER,
- b"You can render to an offscreen "
- b"console and blit in on another "
- b"one, simulating alpha "
- b"transparency.")
+ oc_secondary = libtcod.console_new(SAMPLE_SCREEN_WIDTH // 2, SAMPLE_SCREEN_HEIGHT // 2)
+ oc_screenshot = libtcod.console_new(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT)
+ libtcod.console_print_frame(
+ oc_secondary,
+ 0,
+ 0,
+ SAMPLE_SCREEN_WIDTH // 2,
+ SAMPLE_SCREEN_HEIGHT // 2,
+ False,
+ libtcod.BKGND_NONE,
+ b"Offscreen console",
+ )
+ libtcod.console_print_rect_ex(
+ oc_secondary,
+ SAMPLE_SCREEN_WIDTH // 4,
+ 2,
+ SAMPLE_SCREEN_WIDTH // 2 - 2,
+ SAMPLE_SCREEN_HEIGHT // 2,
+ libtcod.BKGND_NONE,
+ libtcod.CENTER,
+ b"You can render to an offscreen console and blit in on another one, simulating alpha transparency.",
+ )
if first:
libtcod.sys_set_fps(30)
# get a "screenshot" of the current sample screen
- libtcod.console_blit(sample_console, 0, 0, SAMPLE_SCREEN_WIDTH,
- SAMPLE_SCREEN_HEIGHT, oc_screenshot, 0, 0)
+ libtcod.console_blit(sample_console, 0, 0, SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT, oc_screenshot, 0, 0)
oc_counter += 1
if oc_counter % 20 == 0:
oc_x += oc_xdir
@@ -284,11 +281,11 @@ def render_offscreen(first, key, mouse):
oc_ydir = -1
elif oc_y == -5:
oc_ydir = 1
- libtcod.console_blit(oc_screenshot, 0, 0, SAMPLE_SCREEN_WIDTH,
- SAMPLE_SCREEN_HEIGHT, sample_console, 0, 0)
- libtcod.console_blit(oc_secondary, 0, 0, SAMPLE_SCREEN_WIDTH // 2,
- SAMPLE_SCREEN_HEIGHT // 2, sample_console, oc_x, oc_y,
- 1.0,0.75)
+ libtcod.console_blit(oc_screenshot, 0, 0, SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT, sample_console, 0, 0)
+ libtcod.console_blit(
+ oc_secondary, 0, 0, SAMPLE_SCREEN_WIDTH // 2, SAMPLE_SCREEN_HEIGHT // 2, sample_console, oc_x, oc_y, 1.0, 0.75
+ )
+
#############################################
# line drawing sample
@@ -297,33 +294,35 @@ def render_offscreen(first, key, mouse):
line_init = False
line_bk_flag = libtcod.BKGND_SET
+
def render_lines(first, key, mouse):
global line_bk, line_init, line_bk_flag
- flag_names=['BKGND_NONE',
- 'BKGND_SET',
- 'BKGND_MULTIPLY',
- 'BKGND_LIGHTEN',
- 'BKGND_DARKEN',
- 'BKGND_SCREEN',
- 'BKGND_COLOR_DODGE',
- 'BKGND_COLOR_BURN',
- 'BKGND_ADD',
- 'BKGND_ADDALPHA',
- 'BKGND_BURN',
- 'BKGND_OVERLAY',
- 'BKGND_ALPHA',
- ]
+ flag_names = [
+ "BKGND_NONE",
+ "BKGND_SET",
+ "BKGND_MULTIPLY",
+ "BKGND_LIGHTEN",
+ "BKGND_DARKEN",
+ "BKGND_SCREEN",
+ "BKGND_COLOR_DODGE",
+ "BKGND_COLOR_BURN",
+ "BKGND_ADD",
+ "BKGND_ADDALPHA",
+ "BKGND_BURN",
+ "BKGND_OVERLAY",
+ "BKGND_ALPHA",
+ ]
if key.vk in (libtcod.KEY_ENTER, libtcod.KEY_KPENTER):
line_bk_flag += 1
- if (line_bk_flag & 0xff) > libtcod.BKGND_ALPH:
- line_bk_flag=libtcod.BKGND_NONE
+ if (line_bk_flag & 0xFF) > libtcod.BKGND_ALPH:
+ line_bk_flag = libtcod.BKGND_NONE
alpha = 0.0
- if (line_bk_flag & 0xff) == libtcod.BKGND_ALPH:
+ if (line_bk_flag & 0xFF) == libtcod.BKGND_ALPH:
# for the alpha mode, update alpha every frame
alpha = (1.0 + math.cos(libtcod.sys_elapsed_seconds() * 2)) / 2.0
line_bk_flag = libtcod.BKGND_ALPHA(alpha)
- elif (line_bk_flag & 0xff) == libtcod.BKGND_ADDA:
+ elif (line_bk_flag & 0xFF) == libtcod.BKGND_ADDA:
# for the add alpha mode, update alpha every frame
alpha = (1.0 + math.cos(libtcod.sys_elapsed_seconds() * 2)) / 2.0
line_bk_flag = libtcod.BKGND_ADDALPHA(alpha)
@@ -332,45 +331,39 @@ def render_lines(first, key, mouse):
# initialize the colored background
for x in range(SAMPLE_SCREEN_WIDTH):
for y in range(SAMPLE_SCREEN_HEIGHT):
- col = libtcod.Color(x * 255 // (SAMPLE_SCREEN_WIDTH - 1),
- (x + y) * 255 // (SAMPLE_SCREEN_WIDTH - 1 +
- SAMPLE_SCREEN_HEIGHT - 1),
- y * 255 // (SAMPLE_SCREEN_HEIGHT-1))
+ col = libtcod.Color(
+ x * 255 // (SAMPLE_SCREEN_WIDTH - 1),
+ (x + y) * 255 // (SAMPLE_SCREEN_WIDTH - 1 + SAMPLE_SCREEN_HEIGHT - 1),
+ y * 255 // (SAMPLE_SCREEN_HEIGHT - 1),
+ )
libtcod.console_set_char_background(line_bk, x, y, col, libtcod.BKGND_SET)
line_init = True
if first:
libtcod.sys_set_fps(30)
libtcod.console_set_default_foreground(sample_console, libtcod.white)
- libtcod.console_blit(line_bk, 0, 0, SAMPLE_SCREEN_WIDTH,
- SAMPLE_SCREEN_HEIGHT, sample_console, 0, 0)
- recty = int((SAMPLE_SCREEN_HEIGHT - 2) * ((1.0 +
- math.cos(libtcod.sys_elapsed_seconds())) / 2.0))
+ libtcod.console_blit(line_bk, 0, 0, SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT, sample_console, 0, 0)
+ recty = int((SAMPLE_SCREEN_HEIGHT - 2) * ((1.0 + math.cos(libtcod.sys_elapsed_seconds())) / 2.0))
for x in range(SAMPLE_SCREEN_WIDTH):
- col = libtcod.Color(x * 255 // SAMPLE_SCREEN_WIDTH,
- x * 255 // SAMPLE_SCREEN_WIDTH,
- x * 255 // SAMPLE_SCREEN_WIDTH)
+ col = libtcod.Color(
+ x * 255 // SAMPLE_SCREEN_WIDTH, x * 255 // SAMPLE_SCREEN_WIDTH, x * 255 // SAMPLE_SCREEN_WIDTH
+ )
libtcod.console_set_char_background(sample_console, x, recty, col, line_bk_flag)
- libtcod.console_set_char_background(sample_console, x, recty + 1, col,
- line_bk_flag)
- libtcod.console_set_char_background(sample_console, x, recty + 2, col,
- line_bk_flag)
+ libtcod.console_set_char_background(sample_console, x, recty + 1, col, line_bk_flag)
+ libtcod.console_set_char_background(sample_console, x, recty + 2, col, line_bk_flag)
angle = libtcod.sys_elapsed_seconds() * 2.0
- cos_angle=math.cos(angle)
- sin_angle=math.sin(angle)
+ cos_angle = math.cos(angle)
+ sin_angle = math.sin(angle)
xo = int(SAMPLE_SCREEN_WIDTH // 2 * (1 + cos_angle))
yo = int(SAMPLE_SCREEN_HEIGHT // 2 + sin_angle * SAMPLE_SCREEN_WIDTH // 2)
xd = int(SAMPLE_SCREEN_WIDTH // 2 * (1 - cos_angle))
yd = int(SAMPLE_SCREEN_HEIGHT // 2 - sin_angle * SAMPLE_SCREEN_WIDTH // 2)
# draw the line
# in python the easiest way is to use the line iterator
- for x,y in libtcod.line_iter(xo, yo, xd, yd):
- if 0 <= x < SAMPLE_SCREEN_WIDTH and \
- 0 <= y < SAMPLE_SCREEN_HEIGHT:
- libtcod.console_set_char_background(sample_console, x, y,
- libtcod.light_blue, line_bk_flag)
- libtcod.console_print(sample_console, 2, 2,
- '%s (ENTER to change)' %
- flag_names[line_bk_flag & 0xff])
+ for x, y in libtcod.line_iter(xo, yo, xd, yd):
+ if 0 <= x < SAMPLE_SCREEN_WIDTH and 0 <= y < SAMPLE_SCREEN_HEIGHT:
+ libtcod.console_set_char_background(sample_console, x, y, libtcod.light_blue, line_bk_flag)
+ libtcod.console_print(sample_console, 2, 2, "%s (ENTER to change)" % flag_names[line_bk_flag & 0xFF])
+
#############################################
# noise sample
@@ -383,7 +376,9 @@ def render_lines(first, key, mouse):
noise_hurst = libtcod.NOISE_DEFAULT_HURST
noise_lacunarity = libtcod.NOISE_DEFAULT_LACUNARITY
noise = libtcod.noise_new(2)
-noise_img=libtcod.image_new(SAMPLE_SCREEN_WIDTH*2,SAMPLE_SCREEN_HEIGHT*2)
+noise_img = libtcod.image_new(SAMPLE_SCREEN_WIDTH * 2, SAMPLE_SCREEN_HEIGHT * 2)
+
+
def render_noise(first, key, mouse):
global noise_func, noise_img
global noise_dx, noise_dy
@@ -398,26 +393,28 @@ def render_noise(first, key, mouse):
TURBULENCE_SIMPLEX = 6
FBM_WAVELET = 7
TURBULENCE_WAVELET = 8
- funcName=[
- '1 : perlin noise ',
- '2 : simplex noise ',
- '3 : wavelet noise ',
- '4 : perlin fbm ',
- '5 : perlin turbulence ',
- '6 : simplex fbm ',
- '7 : simplex turbulence ',
- '8 : wavelet fbm ',
- '9 : wavelet turbulence ',
+ funcName = [
+ "1 : perlin noise ",
+ "2 : simplex noise ",
+ "3 : wavelet noise ",
+ "4 : perlin fbm ",
+ "5 : perlin turbulence ",
+ "6 : simplex fbm ",
+ "7 : simplex turbulence ",
+ "8 : wavelet fbm ",
+ "9 : wavelet turbulence ",
]
if first:
libtcod.sys_set_fps(30)
libtcod.console_clear(sample_console)
noise_dx += 0.01
noise_dy += 0.01
- for y in range(2*SAMPLE_SCREEN_HEIGHT):
- for x in range(2*SAMPLE_SCREEN_WIDTH):
- f = [noise_zoom * x / (2*SAMPLE_SCREEN_WIDTH) + noise_dx,
- noise_zoom * y / (2*SAMPLE_SCREEN_HEIGHT) + noise_dy]
+ for y in range(2 * SAMPLE_SCREEN_HEIGHT):
+ for x in range(2 * SAMPLE_SCREEN_WIDTH):
+ f = [
+ noise_zoom * x / (2 * SAMPLE_SCREEN_WIDTH) + noise_dx,
+ noise_zoom * y / (2 * SAMPLE_SCREEN_HEIGHT) + noise_dy,
+ ]
value = 0.0
if noise_func == PERLIN:
value = libtcod.noise_get(noise, f, libtcod.NOISE_PERLIN)
@@ -432,85 +429,75 @@ def render_noise(first, key, mouse):
elif noise_func == FBM_SIMPLEX:
value = libtcod.noise_get_fbm(noise, f, noise_octaves, libtcod.NOISE_SIMPLEX)
elif noise_func == TURBULENCE_SIMPLEX:
- value = libtcod.noise_get_turbulence(noise, f,
- noise_octaves, libtcod.NOISE_SIMPLEX)
+ value = libtcod.noise_get_turbulence(noise, f, noise_octaves, libtcod.NOISE_SIMPLEX)
elif noise_func == FBM_WAVELET:
value = libtcod.noise_get_fbm(noise, f, noise_octaves, libtcod.NOISE_WAVELET)
elif noise_func == TURBULENCE_WAVELET:
- value = libtcod.noise_get_turbulence(noise, f,
- noise_octaves, libtcod.NOISE_WAVELET)
+ value = libtcod.noise_get_turbulence(noise, f, noise_octaves, libtcod.NOISE_WAVELET)
c = int((value + 1.0) / 2.0 * 255)
if c < 0:
c = 0
elif c > 255:
c = 255
col = libtcod.Color(c // 2, c // 2, c)
- libtcod.image_put_pixel(noise_img,x,y,col)
+ libtcod.image_put_pixel(noise_img, x, y, col)
libtcod.console_set_default_background(sample_console, libtcod.grey)
rectw = 24
recth = 13
if noise_func <= WAVELET:
recth = 10
- libtcod.image_blit_2x(noise_img,sample_console,0,0)
- libtcod.console_rect(sample_console, 2, 2, rectw, recth, False,
- libtcod.BKGND_MULTIPLY)
- for y in range(2,2+recth):
- for x in range(2,2+rectw):
- col=libtcod.console_get_char_foreground(sample_console,x,y)
+ libtcod.image_blit_2x(noise_img, sample_console, 0, 0)
+ libtcod.console_rect(sample_console, 2, 2, rectw, recth, False, libtcod.BKGND_MULTIPLY)
+ for y in range(2, 2 + recth):
+ for x in range(2, 2 + rectw):
+ col = libtcod.console_get_char_foreground(sample_console, x, y)
col = col * libtcod.grey
- libtcod.console_set_char_foreground(sample_console,x,y,col)
+ libtcod.console_set_char_foreground(sample_console, x, y, col)
for curfunc in range(TURBULENCE_WAVELET + 1):
if curfunc == noise_func:
libtcod.console_set_default_foreground(sample_console, libtcod.white)
- libtcod.console_set_default_background(sample_console,
- libtcod.light_blue)
- libtcod.console_print_ex(sample_console, 2, 2 + curfunc,
- libtcod.BKGND_SET, libtcod.LEFT, funcName[curfunc])
+ libtcod.console_set_default_background(sample_console, libtcod.light_blue)
+ libtcod.console_print_ex(sample_console, 2, 2 + curfunc, libtcod.BKGND_SET, libtcod.LEFT, funcName[curfunc])
else:
libtcod.console_set_default_foreground(sample_console, libtcod.grey)
- libtcod.console_print(sample_console, 2, 2 + curfunc,
- funcName[curfunc])
+ libtcod.console_print(sample_console, 2, 2 + curfunc, funcName[curfunc])
libtcod.console_set_default_foreground(sample_console, libtcod.white)
- libtcod.console_print(sample_console, 2, 11,
- 'Y/H : zoom (%2.1f)' % noise_zoom)
+ libtcod.console_print(sample_console, 2, 11, "Y/H : zoom (%2.1f)" % noise_zoom)
if noise_func > WAVELET:
- libtcod.console_print(sample_console, 2, 12,
- 'E/D : hurst (%2.1f)' % noise_hurst)
- libtcod.console_print(sample_console, 2, 13,
- 'R/F : lacunarity (%2.1f)' %
- noise_lacunarity)
- libtcod.console_print(sample_console, 2, 14,
- 'T/G : octaves (%2.1f)' % noise_octaves)
+ libtcod.console_print(sample_console, 2, 12, "E/D : hurst (%2.1f)" % noise_hurst)
+ libtcod.console_print(sample_console, 2, 13, "R/F : lacunarity (%2.1f)" % noise_lacunarity)
+ libtcod.console_print(sample_console, 2, 14, "T/G : octaves (%2.1f)" % noise_octaves)
if key.vk == libtcod.KEY_NONE:
return
- if ord('9') >= key.c >= ord('1'):
- noise_func = key.c - ord('1')
- elif key.c in (ord('E'), ord('e')):
+ if ord("9") >= key.c >= ord("1"):
+ noise_func = key.c - ord("1")
+ elif key.c in (ord("E"), ord("e")):
noise_hurst += 0.1
libtcod.noise_delete(noise)
- noise = libtcod.noise_new(2,noise_hurst,noise_lacunarity)
- elif key.c in (ord('D'), ord('d')):
+ noise = libtcod.noise_new(2, noise_hurst, noise_lacunarity)
+ elif key.c in (ord("D"), ord("d")):
noise_hurst -= 0.1
libtcod.noise_delete(noise)
noise = libtcod.noise_new(2, noise_hurst, noise_lacunarity)
- elif key.c in (ord('R'), ord('r')):
+ elif key.c in (ord("R"), ord("r")):
noise_lacunarity += 0.5
libtcod.noise_delete(noise)
noise = libtcod.noise_new(2, noise_hurst, noise_lacunarity)
- elif key.c in (ord('F'), ord('f')):
+ elif key.c in (ord("F"), ord("f")):
noise_lacunarity -= 0.5
libtcod.noise_delete(noise)
noise = libtcod.noise_new(2, noise_hurst, noise_lacunarity)
- elif key.c in (ord('T'), ord('t')):
+ elif key.c in (ord("T"), ord("t")):
noise_octaves += 0.5
- elif key.c in (ord('G'), ord('g')):
+ elif key.c in (ord("G"), ord("g")):
noise_octaves -= 0.5
- elif key.c in (ord('Y'), ord('y')):
+ elif key.c in (ord("Y"), ord("y")):
noise_zoom += 0.2
- elif key.c in (ord('H'), ord('h')):
+ elif key.c in (ord("H"), ord("h")):
noise_zoom -= 0.2
+
#############################################
# field of view sample
#############################################
@@ -528,36 +515,51 @@ def render_noise(first, key, mouse):
fov_init = False
fov_light_walls = True
fov_algo_num = 0
-fov_algo_names = ['BASIC ','DIAMOND ', 'SHADOW ',
- 'PERMISSIVE0','PERMISSIVE1','PERMISSIVE2','PERMISSIVE3','PERMISSIVE4',
- 'PERMISSIVE5','PERMISSIVE6','PERMISSIVE7','PERMISSIVE8','RESTRICTIVE']
+fov_algo_names = [
+ "BASIC ",
+ "DIAMOND ",
+ "SHADOW ",
+ "PERMISSIVE0",
+ "PERMISSIVE1",
+ "PERMISSIVE2",
+ "PERMISSIVE3",
+ "PERMISSIVE4",
+ "PERMISSIVE5",
+ "PERMISSIVE6",
+ "PERMISSIVE7",
+ "PERMISSIVE8",
+ "RESTRICTIVE",
+]
+
+
def render_fov(first, key, mouse):
global fov_px, fov_py, fov_map, fov_dark_wall, fov_light_wall
global fov_dark_ground, fov_light_ground
global fov_recompute, fov_torch, fov_noise, fov_torchx, fov_init
global fov_light_walls, fov_algo_num, fov_algo_names
- smap = ['##############################################',
- '####################### #################',
- '##################### # ###############',
- '###################### ### ###########',
- '################## ##### ####',
- '################ ######## ###### ####',
- '############### #################### ####',
- '################ ###### ##',
- '######## ####### ###### # # # ##',
- '######## ###### ### ##',
- '######## ##',
- '#### ###### ### # # # ##',
- '#### ### ########## #### ##',
- '#### ### ########## ###########=##########',
- '#### ################## ##### #####',
- '#### ### #### ##### #####',
- '#### # #### #####',
- '######## # #### ##### #####',
- '######## ##### ####################',
- '##############################################',
- ]
+ smap = [
+ "##############################################",
+ "####################### #################",
+ "##################### # ###############",
+ "###################### ### ###########",
+ "################## ##### ####",
+ "################ ######## ###### ####",
+ "############### #################### ####",
+ "################ ###### ##",
+ "######## ####### ###### # # # ##",
+ "######## ###### ### ##",
+ "######## ##",
+ "#### ###### ### # # # ##",
+ "#### ### ########## #### ##",
+ "#### ### ########## ###########=##########",
+ "#### ################## ##### #####",
+ "#### ### #### ##### #####",
+ "#### # #### #####",
+ "######## # #### ##### #####",
+ "######## ##### ####################",
+ "##############################################",
+ ]
TORCH_RADIUS = 10
SQUARED_TORCH_RADIUS = TORCH_RADIUS * TORCH_RADIUS
dx = 0.0
@@ -568,20 +570,20 @@ def render_fov(first, key, mouse):
fov_map = libtcod.map_new(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT)
for y in range(SAMPLE_SCREEN_HEIGHT):
for x in range(SAMPLE_SCREEN_WIDTH):
- if smap[y][x] == ' ':
+ if smap[y][x] == " ":
# ground
libtcod.map_set_properties(fov_map, x, y, True, True)
- elif smap[y][x] == '=':
+ elif smap[y][x] == "=":
# window
libtcod.map_set_properties(fov_map, x, y, True, False)
# 1d noise for the torch flickering
fov_noise = libtcod.noise_new(1, 1.0, 1.0)
- torchs = 'off'
- lights = 'off'
+ torchs = "off"
+ lights = "off"
if fov_torch:
- torchs = 'on '
- if fov_light_walls :
- lights='on '
+ torchs = "on "
+ if fov_light_walls:
+ lights = "on "
if first:
libtcod.sys_set_fps(30)
# we draw the foreground only the first time.
@@ -590,19 +592,20 @@ def render_fov(first, key, mouse):
# draw the help text & player @
libtcod.console_clear(sample_console)
libtcod.console_set_default_foreground(sample_console, libtcod.white)
- libtcod.console_print(sample_console, 1, 1,
- "IJKL : move around\nT : torch fx %s\nW : light walls %s\n+-: algo %s" %
- (torchs,lights,fov_algo_names[fov_algo_num]))
+ libtcod.console_print(
+ sample_console,
+ 1,
+ 1,
+ "IJKL : move around\nT : torch fx %s\nW : light walls %s\n+-: algo %s"
+ % (torchs, lights, fov_algo_names[fov_algo_num]),
+ )
libtcod.console_set_default_foreground(sample_console, libtcod.black)
- libtcod.console_put_char(sample_console, fov_px, fov_py, '@',
- libtcod.BKGND_NONE)
+ libtcod.console_put_char(sample_console, fov_px, fov_py, "@", libtcod.BKGND_NONE)
# draw windows
for y in range(SAMPLE_SCREEN_HEIGHT):
for x in range(SAMPLE_SCREEN_WIDTH):
- if smap[y][x] == '=':
- libtcod.console_put_char(sample_console, x, y,
- libtcod.CHAR_DHLINE,
- libtcod.BKGND_NONE)
+ if smap[y][x] == "=":
+ libtcod.console_put_char(sample_console, x, y, libtcod.CHAR_DHLINE, libtcod.BKGND_NONE)
if fov_recompute:
fov_recompute = False
if fov_torch:
@@ -614,32 +617,25 @@ def render_fov(first, key, mouse):
fov_torchx += 0.2
# randomize the light position between -1.5 and 1.5
tdx = [fov_torchx + 20.0]
- dx = libtcod.noise_get(noise, tdx, libtcod.NOISE_SIMPLEX) * 1.5
+ dx = libtcod.noise_get(noise, tdx, libtcod.NOISE_SIMPLEX) * 1.5
tdx[0] += 30.0
- dy = libtcod.noise_get(noise, tdx, libtcod.NOISE_SIMPLEX) * 1.5
+ dy = libtcod.noise_get(noise, tdx, libtcod.NOISE_SIMPLEX) * 1.5
di = 0.2 * libtcod.noise_get(noise, [fov_torchx], libtcod.NOISE_SIMPLEX)
for y in range(SAMPLE_SCREEN_HEIGHT):
for x in range(SAMPLE_SCREEN_WIDTH):
visible = libtcod.map_is_in_fov(fov_map, x, y)
- wall = (smap[y][x] == '#')
+ wall = smap[y][x] == "#"
if not visible:
if wall:
- libtcod.console_set_char_background(sample_console, x, y,
- fov_dark_wall, libtcod.BKGND_SET)
+ libtcod.console_set_char_background(sample_console, x, y, fov_dark_wall, libtcod.BKGND_SET)
else:
- libtcod.console_set_char_background(sample_console, x, y,
- fov_dark_ground,
- libtcod.BKGND_SET)
+ libtcod.console_set_char_background(sample_console, x, y, fov_dark_ground, libtcod.BKGND_SET)
else:
if not fov_torch:
if wall:
- libtcod.console_set_char_background(sample_console, x, y,
- fov_light_wall,
- libtcod.BKGND_SET )
+ libtcod.console_set_char_background(sample_console, x, y, fov_light_wall, libtcod.BKGND_SET)
else:
- libtcod.console_set_char_background(sample_console, x, y,
- fov_light_ground,
- libtcod.BKGND_SET )
+ libtcod.console_set_char_background(sample_console, x, y, fov_light_ground, libtcod.BKGND_SET)
else:
if wall:
base = fov_dark_wall
@@ -648,77 +644,79 @@ def render_fov(first, key, mouse):
base = fov_dark_ground
light = fov_light_ground
# cell distance to torch (squared)
- r = float(x - fov_px + dx) * (x - fov_px + dx) + \
- (y - fov_py + dy) * (y - fov_py + dy)
+ r = float(x - fov_px + dx) * (x - fov_px + dx) + (y - fov_py + dy) * (y - fov_py + dy)
if r < SQUARED_TORCH_RADIUS:
- l = (SQUARED_TORCH_RADIUS - r) / SQUARED_TORCH_RADIUS \
- + di
- if l < 0.0:
+ l = (SQUARED_TORCH_RADIUS - r) / SQUARED_TORCH_RADIUS + di
+ if l < 0.0:
l = 0.0
- elif l> 1.0:
+ elif l > 1.0:
l = 1.0
base = libtcod.color_lerp(base, light, l)
- libtcod.console_set_char_background(sample_console, x, y, base,
- libtcod.BKGND_SET)
- if key.c in (ord('I'), ord('i')):
- if smap[fov_py-1][fov_px] == ' ':
- libtcod.console_put_char(sample_console, fov_px, fov_py, ' ',
- libtcod.BKGND_NONE)
+ libtcod.console_set_char_background(sample_console, x, y, base, libtcod.BKGND_SET)
+ if key.c in (ord("I"), ord("i")):
+ if smap[fov_py - 1][fov_px] == " ":
+ libtcod.console_put_char(sample_console, fov_px, fov_py, " ", libtcod.BKGND_NONE)
fov_py -= 1
- libtcod.console_put_char(sample_console, fov_px, fov_py, '@',
- libtcod.BKGND_NONE)
+ libtcod.console_put_char(sample_console, fov_px, fov_py, "@", libtcod.BKGND_NONE)
fov_recompute = True
- elif key.c in (ord('K'), ord('k')):
- if smap[fov_py+1][fov_px] == ' ':
- libtcod.console_put_char(sample_console, fov_px, fov_py, ' ',
- libtcod.BKGND_NONE)
+ elif key.c in (ord("K"), ord("k")):
+ if smap[fov_py + 1][fov_px] == " ":
+ libtcod.console_put_char(sample_console, fov_px, fov_py, " ", libtcod.BKGND_NONE)
fov_py += 1
- libtcod.console_put_char(sample_console, fov_px, fov_py, '@',
- libtcod.BKGND_NONE)
+ libtcod.console_put_char(sample_console, fov_px, fov_py, "@", libtcod.BKGND_NONE)
fov_recompute = True
- elif key.c in (ord('J'), ord('j')):
- if smap[fov_py][fov_px-1] == ' ':
- libtcod.console_put_char(sample_console, fov_px, fov_py, ' ',
- libtcod.BKGND_NONE)
+ elif key.c in (ord("J"), ord("j")):
+ if smap[fov_py][fov_px - 1] == " ":
+ libtcod.console_put_char(sample_console, fov_px, fov_py, " ", libtcod.BKGND_NONE)
fov_px -= 1
- libtcod.console_put_char(sample_console, fov_px, fov_py, '@',
- libtcod.BKGND_NONE)
+ libtcod.console_put_char(sample_console, fov_px, fov_py, "@", libtcod.BKGND_NONE)
fov_recompute = True
- elif key.c in (ord('L'), ord('l')):
- if smap[fov_py][fov_px+1] == ' ':
- libtcod.console_put_char(sample_console, fov_px, fov_py, ' ',
- libtcod.BKGND_NONE)
+ elif key.c in (ord("L"), ord("l")):
+ if smap[fov_py][fov_px + 1] == " ":
+ libtcod.console_put_char(sample_console, fov_px, fov_py, " ", libtcod.BKGND_NONE)
fov_px += 1
- libtcod.console_put_char(sample_console, fov_px, fov_py, '@',
- libtcod.BKGND_NONE)
+ libtcod.console_put_char(sample_console, fov_px, fov_py, "@", libtcod.BKGND_NONE)
fov_recompute = True
- elif key.c in (ord('T'), ord('t')):
+ elif key.c in (ord("T"), ord("t")):
fov_torch = not fov_torch
libtcod.console_set_default_foreground(sample_console, libtcod.white)
- libtcod.console_print(sample_console, 1, 1,
- "IJKL : move around\nT : torch fx %s\nW : light walls %s\n+-: algo %s" %
- (torchs,lights,fov_algo_names[fov_algo_num]))
+ libtcod.console_print(
+ sample_console,
+ 1,
+ 1,
+ "IJKL : move around\nT : torch fx %s\nW : light walls %s\n+-: algo %s"
+ % (torchs, lights, fov_algo_names[fov_algo_num]),
+ )
libtcod.console_set_default_foreground(sample_console, libtcod.black)
- elif key.c in (ord('W'), ord('w')):
+ elif key.c in (ord("W"), ord("w")):
fov_light_walls = not fov_light_walls
libtcod.console_set_default_foreground(sample_console, libtcod.white)
- libtcod.console_print(sample_console, 1, 1,
- "IJKL : move around\nT : torch fx %s\nW : light walls %s\n+-: algo %s" %
- (torchs,lights,fov_algo_names[fov_algo_num]))
+ libtcod.console_print(
+ sample_console,
+ 1,
+ 1,
+ "IJKL : move around\nT : torch fx %s\nW : light walls %s\n+-: algo %s"
+ % (torchs, lights, fov_algo_names[fov_algo_num]),
+ )
libtcod.console_set_default_foreground(sample_console, libtcod.black)
fov_recompute = True
- elif key.c in (ord('+'), ord('-')):
- if key.c == ord('+') and fov_algo_num < libtcod.NB_FOV_ALGORITHMS-1:
+ elif key.c in (ord("+"), ord("-")):
+ if key.c == ord("+") and fov_algo_num < libtcod.NB_FOV_ALGORITHMS - 1:
fov_algo_num = fov_algo_num + 1
- elif fov_algo_num > 0 :
+ elif fov_algo_num > 0:
fov_algo_num = fov_algo_num - 1
libtcod.console_set_default_foreground(sample_console, libtcod.white)
- libtcod.console_print(sample_console, 1, 1,
- "IJKL : move around\nT : torch fx %s\nW : light walls %s\n+-: algo %s" %
- (torchs,lights,fov_algo_names[fov_algo_num]))
+ libtcod.console_print(
+ sample_console,
+ 1,
+ 1,
+ "IJKL : move around\nT : torch fx %s\nW : light walls %s\n+-: algo %s"
+ % (torchs, lights, fov_algo_names[fov_algo_num]),
+ )
libtcod.console_set_default_foreground(sample_console, libtcod.black)
fov_recompute = True
+
#############################################
# pathfinding sample
#############################################
@@ -733,34 +731,37 @@ def render_fov(first, key, mouse):
path_dijk = None
path_recalculate = False
path_busy = 0.0
-path_oldchar = ' '
+path_oldchar = " "
path_init = False
+
+
def render_path(first, key, mouse):
global path_px, path_py, path_dx, path_dy, path_map, path, path_busy
global path_oldchar, path_init, path_recalculate
global path_dijk_dist, path_using_astar, path_dijk
- smap = ['##############################################',
- '####################### #################',
- '##################### # ###############',
- '###################### ### ###########',
- '################## ##### ####',
- '################ ######## ###### ####',
- '############### #################### ####',
- '################ ###### ##',
- '######## ####### ###### # # # ##',
- '######## ###### ### ##',
- '######## ##',
- '#### ###### ### # # # ##',
- '#### ### ########## #### ##',
- '#### ### ########## ###########=##########',
- '#### ################## ##### #####',
- '#### ### #### ##### #####',
- '#### # #### #####',
- '######## # #### ##### #####',
- '######## ##### ####################',
- '##############################################',
- ]
+ smap = [
+ "##############################################",
+ "####################### #################",
+ "##################### # ###############",
+ "###################### ### ###########",
+ "################## ##### ####",
+ "################ ######## ###### ####",
+ "############### #################### ####",
+ "################ ###### ##",
+ "######## ####### ###### # # # ##",
+ "######## ###### ### ##",
+ "######## ##",
+ "#### ###### ### # # # ##",
+ "#### ### ########## #### ##",
+ "#### ### ########## ###########=##########",
+ "#### ################## ##### #####",
+ "#### ### #### ##### #####",
+ "#### # #### #####",
+ "######## # #### ##### #####",
+ "######## ##### ####################",
+ "##############################################",
+ ]
TORCH_RADIUS = 10.0
SQUARED_TORCH_RADIUS = TORCH_RADIUS * TORCH_RADIUS
if not path_init:
@@ -768,10 +769,10 @@ def render_path(first, key, mouse):
path_map = libtcod.map_new(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT)
for y in range(SAMPLE_SCREEN_HEIGHT):
for x in range(SAMPLE_SCREEN_WIDTH):
- if smap[y][x] == ' ':
+ if smap[y][x] == " ":
# ground
libtcod.map_set_properties(path_map, x, y, True, True)
- elif smap[y][x] == '=':
+ elif smap[y][x] == "=":
# window
libtcod.map_set_properties(path_map, x, y, True, False)
path = libtcod.path_new_using_map(path_map)
@@ -784,153 +785,132 @@ def render_path(first, key, mouse):
# draw the help text & player @
libtcod.console_clear(sample_console)
libtcod.console_set_default_foreground(sample_console, libtcod.white)
- libtcod.console_put_char(sample_console, path_dx, path_dy, '+',
- libtcod.BKGND_NONE)
- libtcod.console_put_char(sample_console, path_px, path_py, '@',
- libtcod.BKGND_NONE)
- libtcod.console_print(sample_console, 1, 1,
- "IJKL / mouse :\nmove destination\nTAB : A*/dijkstra")
- libtcod.console_print(sample_console, 1, 4,
- "Using : A*")
+ libtcod.console_put_char(sample_console, path_dx, path_dy, "+", libtcod.BKGND_NONE)
+ libtcod.console_put_char(sample_console, path_px, path_py, "@", libtcod.BKGND_NONE)
+ libtcod.console_print(sample_console, 1, 1, "IJKL / mouse :\nmove destination\nTAB : A*/dijkstra")
+ libtcod.console_print(sample_console, 1, 4, "Using : A*")
# draw windows
for y in range(SAMPLE_SCREEN_HEIGHT):
for x in range(SAMPLE_SCREEN_WIDTH):
- if smap[y][x] == '=':
- libtcod.console_put_char(sample_console, x, y,
- libtcod.CHAR_DHLINE,
- libtcod.BKGND_NONE)
+ if smap[y][x] == "=":
+ libtcod.console_put_char(sample_console, x, y, libtcod.CHAR_DHLINE, libtcod.BKGND_NONE)
path_recalculate = True
if path_recalculate:
- if path_using_astar :
+ if path_using_astar:
libtcod.path_compute(path, path_px, path_py, path_dx, path_dy)
else:
path_dijk_dist = 0.0
# compute dijkstra grid (distance from px,py)
- libtcod.dijkstra_compute(path_dijk,path_px,path_py)
+ libtcod.dijkstra_compute(path_dijk, path_px, path_py)
# get the maximum distance (needed for rendering)
for y in range(SAMPLE_SCREEN_HEIGHT):
for x in range(SAMPLE_SCREEN_WIDTH):
- d=libtcod.dijkstra_get_distance(path_dijk,x,y)
+ d = libtcod.dijkstra_get_distance(path_dijk, x, y)
if d > path_dijk_dist:
- path_dijk_dist=d
+ path_dijk_dist = d
# compute path from px,py to dx,dy
- libtcod.dijkstra_path_set(path_dijk,path_dx,path_dy)
+ libtcod.dijkstra_path_set(path_dijk, path_dx, path_dy)
path_recalculate = False
path_busy = 0.2
# draw the dungeon
for y in range(SAMPLE_SCREEN_HEIGHT):
for x in range(SAMPLE_SCREEN_WIDTH):
- if smap[y][x] == '#':
- libtcod.console_set_char_background(sample_console, x, y, fov_dark_wall,
- libtcod.BKGND_SET)
+ if smap[y][x] == "#":
+ libtcod.console_set_char_background(sample_console, x, y, fov_dark_wall, libtcod.BKGND_SET)
else:
- libtcod.console_set_char_background(sample_console, x, y, fov_dark_ground,
- libtcod.BKGND_SET)
+ libtcod.console_set_char_background(sample_console, x, y, fov_dark_ground, libtcod.BKGND_SET)
# draw the path
- if path_using_astar :
+ if path_using_astar:
for i in range(libtcod.path_size(path)):
- x,y = libtcod.path_get(path, i)
- libtcod.console_set_char_background(sample_console, x, y,
- fov_light_ground, libtcod.BKGND_SET)
+ x, y = libtcod.path_get(path, i)
+ libtcod.console_set_char_background(sample_console, x, y, fov_light_ground, libtcod.BKGND_SET)
else:
for y in range(SAMPLE_SCREEN_HEIGHT):
for x in range(SAMPLE_SCREEN_WIDTH):
- if smap[y][x] != '#':
- libtcod.console_set_char_background(sample_console, x, y, libtcod.color_lerp(fov_light_ground,fov_dark_ground,
- 0.9 * libtcod.dijkstra_get_distance(path_dijk,x,y) / path_dijk_dist), libtcod.BKGND_SET)
+ if smap[y][x] != "#":
+ libtcod.console_set_char_background(
+ sample_console,
+ x,
+ y,
+ libtcod.color_lerp(
+ fov_light_ground,
+ fov_dark_ground,
+ 0.9 * libtcod.dijkstra_get_distance(path_dijk, x, y) / path_dijk_dist,
+ ),
+ libtcod.BKGND_SET,
+ )
for i in range(libtcod.dijkstra_size(path_dijk)):
- x,y=libtcod.dijkstra_get(path_dijk,i)
- libtcod.console_set_char_background(sample_console,x,y,fov_light_ground, libtcod.BKGND_SET )
+ x, y = libtcod.dijkstra_get(path_dijk, i)
+ libtcod.console_set_char_background(sample_console, x, y, fov_light_ground, libtcod.BKGND_SET)
# move the creature
path_busy -= libtcod.sys_get_last_frame_length()
if path_busy <= 0.0:
path_busy = 0.2
- if path_using_astar :
+ if path_using_astar:
if not libtcod.path_is_empty(path):
- libtcod.console_put_char(sample_console, path_px, path_py, ' ',
- libtcod.BKGND_NONE)
+ libtcod.console_put_char(sample_console, path_px, path_py, " ", libtcod.BKGND_NONE)
path_px, path_py = libtcod.path_walk(path, True)
- libtcod.console_put_char(sample_console, path_px, path_py, '@',
- libtcod.BKGND_NONE)
+ libtcod.console_put_char(sample_console, path_px, path_py, "@", libtcod.BKGND_NONE)
else:
if not libtcod.dijkstra_is_empty(path_dijk):
- libtcod.console_put_char(sample_console, path_px, path_py, ' ',
- libtcod.BKGND_NONE)
+ libtcod.console_put_char(sample_console, path_px, path_py, " ", libtcod.BKGND_NONE)
path_px, path_py = libtcod.dijkstra_path_walk(path_dijk)
- libtcod.console_put_char(sample_console, path_px, path_py, '@',
- libtcod.BKGND_NONE)
+ libtcod.console_put_char(sample_console, path_px, path_py, "@", libtcod.BKGND_NONE)
path_recalculate = True
- if key.c in (ord('I'), ord('i')) and path_dy > 0:
+ if key.c in (ord("I"), ord("i")) and path_dy > 0:
# destination move north
- libtcod.console_put_char(sample_console, path_dx, path_dy, path_oldchar,
- libtcod.BKGND_NONE)
+ libtcod.console_put_char(sample_console, path_dx, path_dy, path_oldchar, libtcod.BKGND_NONE)
path_dy -= 1
- path_oldchar = libtcod.console_get_char(sample_console, path_dx,
- path_dy)
- libtcod.console_put_char(sample_console, path_dx, path_dy, '+',
- libtcod.BKGND_NONE)
- if smap[path_dy][path_dx] == ' ':
+ path_oldchar = libtcod.console_get_char(sample_console, path_dx, path_dy)
+ libtcod.console_put_char(sample_console, path_dx, path_dy, "+", libtcod.BKGND_NONE)
+ if smap[path_dy][path_dx] == " ":
path_recalculate = True
- elif key.c in (ord('K'), ord('k')) and path_dy < SAMPLE_SCREEN_HEIGHT - 1:
+ elif key.c in (ord("K"), ord("k")) and path_dy < SAMPLE_SCREEN_HEIGHT - 1:
# destination move south
- libtcod.console_put_char(sample_console, path_dx, path_dy, path_oldchar,
- libtcod.BKGND_NONE)
+ libtcod.console_put_char(sample_console, path_dx, path_dy, path_oldchar, libtcod.BKGND_NONE)
path_dy += 1
- path_oldchar = libtcod.console_get_char(sample_console, path_dx,
- path_dy)
- libtcod.console_put_char(sample_console, path_dx, path_dy, '+',
- libtcod.BKGND_NONE)
- if smap[path_dy][path_dx] == ' ':
+ path_oldchar = libtcod.console_get_char(sample_console, path_dx, path_dy)
+ libtcod.console_put_char(sample_console, path_dx, path_dy, "+", libtcod.BKGND_NONE)
+ if smap[path_dy][path_dx] == " ":
path_recalculate = True
- elif key.c in (ord('J'), ord('j')) and path_dx > 0:
+ elif key.c in (ord("J"), ord("j")) and path_dx > 0:
# destination move west
- libtcod.console_put_char(sample_console, path_dx, path_dy, path_oldchar,
- libtcod.BKGND_NONE)
+ libtcod.console_put_char(sample_console, path_dx, path_dy, path_oldchar, libtcod.BKGND_NONE)
path_dx -= 1
- path_oldchar = libtcod.console_get_char(sample_console, path_dx,
- path_dy)
- libtcod.console_put_char(sample_console, path_dx, path_dy, '+',
- libtcod.BKGND_NONE)
- if smap[path_dy][path_dx] == ' ':
+ path_oldchar = libtcod.console_get_char(sample_console, path_dx, path_dy)
+ libtcod.console_put_char(sample_console, path_dx, path_dy, "+", libtcod.BKGND_NONE)
+ if smap[path_dy][path_dx] == " ":
path_recalculate = True
- elif key.c in (ord('L'), ord('l')) and path_dx < SAMPLE_SCREEN_WIDTH - 1:
+ elif key.c in (ord("L"), ord("l")) and path_dx < SAMPLE_SCREEN_WIDTH - 1:
# destination move east
- libtcod.console_put_char(sample_console, path_dx, path_dy, path_oldchar,
- libtcod.BKGND_NONE)
+ libtcod.console_put_char(sample_console, path_dx, path_dy, path_oldchar, libtcod.BKGND_NONE)
path_dx += 1
- path_oldchar = libtcod.console_get_char(sample_console, path_dx,
- path_dy)
- libtcod.console_put_char(sample_console, path_dx, path_dy, '+',
- libtcod.BKGND_NONE)
- if smap[path_dy][path_dx] == ' ':
+ path_oldchar = libtcod.console_get_char(sample_console, path_dx, path_dy)
+ libtcod.console_put_char(sample_console, path_dx, path_dy, "+", libtcod.BKGND_NONE)
+ if smap[path_dy][path_dx] == " ":
path_recalculate = True
elif key.vk == libtcod.KEY_TAB:
path_using_astar = not path_using_astar
- if path_using_astar :
- libtcod.console_print(sample_console, 1, 4,
- "Using : A* ")
+ if path_using_astar:
+ libtcod.console_print(sample_console, 1, 4, "Using : A* ")
else:
- libtcod.console_print(sample_console, 1, 4,
- "Using : Dijkstra")
- path_recalculate=True
+ libtcod.console_print(sample_console, 1, 4, "Using : Dijkstra")
+ path_recalculate = True
mx = mouse.cx - SAMPLE_SCREEN_X
my = mouse.cy - SAMPLE_SCREEN_Y
- if 0 <= mx < SAMPLE_SCREEN_WIDTH and 0 <= my < SAMPLE_SCREEN_HEIGHT and \
- (path_dx != mx or path_dy != my):
- libtcod.console_put_char(sample_console, path_dx, path_dy, path_oldchar,
- libtcod.BKGND_NONE)
+ if 0 <= mx < SAMPLE_SCREEN_WIDTH and 0 <= my < SAMPLE_SCREEN_HEIGHT and (path_dx != mx or path_dy != my):
+ libtcod.console_put_char(sample_console, path_dx, path_dy, path_oldchar, libtcod.BKGND_NONE)
path_dx = mx
path_dy = my
- path_oldchar = libtcod.console_get_char(sample_console, path_dx,
- path_dy)
- libtcod.console_put_char(sample_console, path_dx, path_dy, '+',
- libtcod.BKGND_NONE)
- if smap[path_dy][path_dx] == ' ':
+ path_oldchar = libtcod.console_get_char(sample_console, path_dx, path_dy)
+ libtcod.console_put_char(sample_console, path_dx, path_dy, "+", libtcod.BKGND_NONE)
+ if smap[path_dy][path_dx] == " ":
path_recalculate = True
+
#############################################
# bsp sample
#############################################
@@ -941,44 +921,52 @@ def render_path(first, key, mouse):
# if true, there is always a wall on north & west side of a room
bsp_room_walls = True
bsp_map = None
+
+
# draw a vertical line
def vline(m, x, y1, y2):
if y1 > y2:
- y1,y2 = y2,y1
- for y in range(y1,y2+1):
+ y1, y2 = y2, y1
+ for y in range(y1, y2 + 1):
m[x][y] = True
+
# draw a vertical line up until we reach an empty space
def vline_up(m, x, y):
while y >= 0 and not m[x][y]:
m[x][y] = True
y -= 1
+
# draw a vertical line down until we reach an empty space
def vline_down(m, x, y):
while y < SAMPLE_SCREEN_HEIGHT and not m[x][y]:
m[x][y] = True
y += 1
+
# draw a horizontal line
def hline(m, x1, y, x2):
if x1 > x2:
- x1,x2 = x2,x1
- for x in range(x1,x2+1):
+ x1, x2 = x2, x1
+ for x in range(x1, x2 + 1):
m[x][y] = True
+
# draw a horizontal line left until we reach an empty space
def hline_left(m, x, y):
while x >= 0 and not m[x][y]:
m[x][y] = True
x -= 1
+
# draw a horizontal line right until we reach an empty space
def hline_right(m, x, y):
while x < SAMPLE_SCREEN_WIDTH and not m[x][y]:
- m[x][y]=True
+ m[x][y] = True
x += 1
+
# the class building the dungeon from the bsp nodes
def traverse_node(node, *dat):
global bsp_map
@@ -992,7 +980,7 @@ def traverse_node(node, *dat):
if minx > 1:
minx -= 1
if miny > 1:
- miny -=1
+ miny -= 1
if maxx == SAMPLE_SCREEN_WIDTH - 1:
maxx -= 1
if maxy == SAMPLE_SCREEN_HEIGHT - 1:
@@ -1005,8 +993,8 @@ def traverse_node(node, *dat):
# resize the node to fit the room
node.x = minx
node.y = miny
- node.w = maxx-minx + 1
- node.h = maxy-miny + 1
+ node.w = maxx - minx + 1
+ node.h = maxy - miny + 1
# dig the room
for x in range(minx, maxx + 1):
for y in range(miny, maxy + 1):
@@ -1056,9 +1044,12 @@ def traverse_node(node, *dat):
hline_right(bsp_map, right.x, y)
return True
+
bsp = None
bsp_generate = True
bsp_refresh = False
+
+
def render_bsp(first, key, mouse):
global bsp, bsp_generate, bsp_refresh, bsp_map
global bsp_random_room, bsp_room_walls, bsp_depth, bsp_min_room_size
@@ -1066,12 +1057,10 @@ def render_bsp(first, key, mouse):
# dungeon generation
if bsp is None:
# create the bsp
- bsp = libtcod.bsp_new_with_size(0, 0, SAMPLE_SCREEN_WIDTH,
- SAMPLE_SCREEN_HEIGHT)
+ bsp = libtcod.bsp_new_with_size(0, 0, SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT)
else:
# restore the nodes size
- libtcod.bsp_resize(bsp, 0, 0, SAMPLE_SCREEN_WIDTH,
- SAMPLE_SCREEN_HEIGHT)
+ libtcod.bsp_resize(bsp, 0, 0, SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT)
bsp_map = list()
for x in range(SAMPLE_SCREEN_WIDTH):
bsp_map.append([False] * SAMPLE_SCREEN_HEIGHT)
@@ -1079,69 +1068,66 @@ def render_bsp(first, key, mouse):
# build a new random bsp tree
libtcod.bsp_remove_sons(bsp)
if bsp_room_walls:
- libtcod.bsp_split_recursive(bsp, 0, bsp_depth,
- bsp_min_room_size + 1,
- bsp_min_room_size + 1, 1.5, 1.5)
+ libtcod.bsp_split_recursive(bsp, 0, bsp_depth, bsp_min_room_size + 1, bsp_min_room_size + 1, 1.5, 1.5)
else:
- libtcod.bsp_split_recursive(bsp, 0, bsp_depth,
- bsp_min_room_size,
- bsp_min_room_size, 1.5, 1.5)
+ libtcod.bsp_split_recursive(bsp, 0, bsp_depth, bsp_min_room_size, bsp_min_room_size, 1.5, 1.5)
# create the dungeon from the bsp
libtcod.bsp_traverse_inverted_level_order(bsp, traverse_node)
bsp_generate = False
bsp_refresh = False
libtcod.console_clear(sample_console)
libtcod.console_set_default_foreground(sample_console, libtcod.white)
- rooms = 'OFF'
+ rooms = "OFF"
if bsp_random_room:
- rooms = 'ON'
- libtcod.console_print(sample_console, 1, 1,
- "ENTER : rebuild bsp\n"
- "SPACE : rebuild dungeon\n"
- "+-: bsp depth %d\n"
- "*/: room size %d\n"
- "1 : random room size %s" % (bsp_depth,
- bsp_min_room_size, rooms))
+ rooms = "ON"
+ libtcod.console_print(
+ sample_console,
+ 1,
+ 1,
+ "ENTER : rebuild bsp\n"
+ "SPACE : rebuild dungeon\n"
+ "+-: bsp depth %d\n"
+ "*/: room size %d\n"
+ "1 : random room size %s" % (bsp_depth, bsp_min_room_size, rooms),
+ )
if bsp_random_room:
- walls = 'OFF'
+ walls = "OFF"
if bsp_room_walls:
- walls ='ON'
- libtcod.console_print(sample_console, 1, 6,
- '2 : room walls %s' % walls)
+ walls = "ON"
+ libtcod.console_print(sample_console, 1, 6, "2 : room walls %s" % walls)
# render the level
for y in range(SAMPLE_SCREEN_HEIGHT):
for x in range(SAMPLE_SCREEN_WIDTH):
if not bsp_map[x][y]:
- libtcod.console_set_char_background(sample_console, x, y, fov_dark_wall,
- libtcod.BKGND_SET)
+ libtcod.console_set_char_background(sample_console, x, y, fov_dark_wall, libtcod.BKGND_SET)
else:
- libtcod.console_set_char_background(sample_console, x, y, fov_dark_ground,
- libtcod.BKGND_SET)
- if key.vk in (libtcod.KEY_ENTER ,libtcod.KEY_KPENTER):
+ libtcod.console_set_char_background(sample_console, x, y, fov_dark_ground, libtcod.BKGND_SET)
+ if key.vk in (libtcod.KEY_ENTER, libtcod.KEY_KPENTER):
bsp_generate = True
- elif key.c==ord(' '):
+ elif key.c == ord(" "):
bsp_refresh = True
- elif key.c == ord('+'):
+ elif key.c == ord("+"):
bsp_depth += 1
bsp_generate = True
- elif key.c == ord('-') and bsp_depth > 1:
+ elif key.c == ord("-") and bsp_depth > 1:
bsp_depth -= 1
bsp_generate = True
- elif key.c==ord('*'):
+ elif key.c == ord("*"):
bsp_min_room_size += 1
bsp_generate = True
- elif key.c == ord('/') and bsp_min_room_size > 2:
+ elif key.c == ord("/") and bsp_min_room_size > 2:
bsp_min_room_size -= 1
bsp_generate = True
- elif key.c == ord('1') or key.vk in (libtcod.KEY_1, libtcod.KEY_KP1):
+ elif key.c == ord("1") or key.vk in (libtcod.KEY_1, libtcod.KEY_KP1):
bsp_random_room = not bsp_random_room
if not bsp_random_room:
bsp_room_walls = True
bsp_refresh = True
- elif key.c == ord('2') or key.vk in (libtcod.KEY_2, libtcod.KEY_KP2):
+ elif key.c == ord("2") or key.vk in (libtcod.KEY_2, libtcod.KEY_KP2):
bsp_room_walls = not bsp_room_walls
bsp_refresh = True
+
#############################################
# image sample
#############################################
@@ -1149,19 +1135,21 @@ def render_bsp(first, key, mouse):
img_circle = None
img_blue = libtcod.Color(0, 0, 255)
img_green = libtcod.Color(0, 255, 0)
+
+
def render_image(first, key, mouse):
- global img,img_circle,img_blue,img_green
+ global img, img_circle, img_blue, img_green
if img is None:
- img = libtcod.image_load(os.path.join(b'data',b'img',b'skull.png'))
- libtcod.image_set_key_color(img,libtcod.black)
- img_circle = libtcod.image_load(os.path.join(b'data',b'img',b'circle.png'))
+ img = libtcod.image_load(get_data("img/skull.png"))
+ libtcod.image_set_key_color(img, libtcod.black)
+ img_circle = libtcod.image_load(get_data(("img/circle.png")))
if first:
libtcod.sys_set_fps(30)
libtcod.console_set_default_background(sample_console, libtcod.black)
libtcod.console_clear(sample_console)
x = SAMPLE_SCREEN_WIDTH / 2 + math.cos(libtcod.sys_elapsed_seconds()) * 10.0
y = float(SAMPLE_SCREEN_HEIGHT / 2)
- scalex=0.2 + 1.8 * (1.0 + math.cos(libtcod.sys_elapsed_seconds() / 2)) / 2.0
+ scalex = 0.2 + 1.8 * (1.0 + math.cos(libtcod.sys_elapsed_seconds() / 2)) / 2.0
scaley = scalex
angle = libtcod.sys_elapsed_seconds()
elapsed = libtcod.sys_elapsed_milli() // 2000
@@ -1169,32 +1157,23 @@ def render_image(first, key, mouse):
# split the color channels of circle.png
# the red channel
libtcod.console_set_default_background(sample_console, libtcod.red)
- libtcod.console_rect(sample_console, 0, 3, 15, 15, False,
- libtcod.BKGND_SET)
- libtcod.image_blit_rect(img_circle, sample_console, 0, 3, -1, -1,
- libtcod.BKGND_MULTIPLY)
+ libtcod.console_rect(sample_console, 0, 3, 15, 15, False, libtcod.BKGND_SET)
+ libtcod.image_blit_rect(img_circle, sample_console, 0, 3, -1, -1, libtcod.BKGND_MULTIPLY)
# the green channel
libtcod.console_set_default_background(sample_console, img_green)
- libtcod.console_rect(sample_console, 15, 3, 15, 15, False,
- libtcod.BKGND_SET)
- libtcod.image_blit_rect(img_circle,sample_console, 15, 3, -1, -1,
- libtcod.BKGND_MULTIPLY)
+ libtcod.console_rect(sample_console, 15, 3, 15, 15, False, libtcod.BKGND_SET)
+ libtcod.image_blit_rect(img_circle, sample_console, 15, 3, -1, -1, libtcod.BKGND_MULTIPLY)
# the blue channel
libtcod.console_set_default_background(sample_console, img_blue)
- libtcod.console_rect(sample_console, 30, 3, 15, 15, False,
- libtcod.BKGND_SET)
- libtcod.image_blit_rect(img_circle, sample_console, 30, 3, -1, -1,
- libtcod.BKGND_MULTIPLY)
+ libtcod.console_rect(sample_console, 30, 3, 15, 15, False, libtcod.BKGND_SET)
+ libtcod.image_blit_rect(img_circle, sample_console, 30, 3, -1, -1, libtcod.BKGND_MULTIPLY)
else:
# render circle.png with normal blitting
- libtcod.image_blit_rect(img_circle, sample_console, 0, 3, -1, -1,
- libtcod.BKGND_SET)
- libtcod.image_blit_rect(img_circle, sample_console, 15, 3, -1, -1,
- libtcod.BKGND_SET)
- libtcod.image_blit_rect(img_circle, sample_console, 30, 3, -1, -1,
- libtcod.BKGND_SET)
- libtcod.image_blit(img, sample_console, x, y, libtcod.BKGND_SET,
- scalex, scaley, angle)
+ libtcod.image_blit_rect(img_circle, sample_console, 0, 3, -1, -1, libtcod.BKGND_SET)
+ libtcod.image_blit_rect(img_circle, sample_console, 15, 3, -1, -1, libtcod.BKGND_SET)
+ libtcod.image_blit_rect(img_circle, sample_console, 30, 3, -1, -1, libtcod.BKGND_SET)
+ libtcod.image_blit(img, sample_console, x, y, libtcod.BKGND_SET, scalex, scaley, angle)
+
#############################################
# mouse sample
@@ -1202,15 +1181,16 @@ def render_image(first, key, mouse):
mouse_lbut = 0
mouse_mbut = 0
mouse_rbut = 0
+
+
def render_mouse(first, key, mouse):
global mouse_lbut
global mouse_mbut
global mouse_rbut
- butstatus=('OFF', 'ON')
+ butstatus = ("OFF", "ON")
if first:
libtcod.console_set_default_background(sample_console, libtcod.grey)
- libtcod.console_set_default_foreground(sample_console,
- libtcod.light_yellow)
+ libtcod.console_set_default_foreground(sample_console, libtcod.light_yellow)
libtcod.mouse_move(320, 200)
libtcod.mouse_show_cursor(True)
libtcod.sys_set_fps(30)
@@ -1221,33 +1201,45 @@ def render_mouse(first, key, mouse):
mouse_rbut = 1 - mouse_rbut
if mouse.mbutton_pressed:
mouse_mbut = 1 - mouse_mbut
- wheel=""
- if mouse.wheel_up :
- wheel="UP"
- elif mouse.wheel_down :
- wheel="DOWN"
- libtcod.console_print(sample_console, 1, 1,
- "Mouse position : %4dx%4d\n"
- "Mouse cell : %4dx%4d\n"
- "Mouse movement : %4dx%4d\n"
- "Left button : %s (toggle %s)\n"
- "Right button : %s (toggle %s)\n"
- "Middle button : %s (toggle %s)\n"
- "Wheel : %s" %
- (mouse.x, mouse.y,
- mouse.cx, mouse.cy,
- mouse.dx, mouse.dy,
- butstatus[mouse.lbutton], butstatus[mouse_lbut],
- butstatus[mouse.rbutton], butstatus[mouse_rbut],
- butstatus[mouse.mbutton], butstatus[mouse_mbut],
- wheel))
- libtcod.console_print(sample_console, 1, 10,
- "1 : Hide cursor\n2 : Show cursor")
- if key.c == ord('1'):
+ wheel = ""
+ if mouse.wheel_up:
+ wheel = "UP"
+ elif mouse.wheel_down:
+ wheel = "DOWN"
+ libtcod.console_print(
+ sample_console,
+ 1,
+ 1,
+ "Mouse position : %4dx%4d\n"
+ "Mouse cell : %4dx%4d\n"
+ "Mouse movement : %4dx%4d\n"
+ "Left button : %s (toggle %s)\n"
+ "Right button : %s (toggle %s)\n"
+ "Middle button : %s (toggle %s)\n"
+ "Wheel : %s"
+ % (
+ mouse.x,
+ mouse.y,
+ mouse.cx,
+ mouse.cy,
+ mouse.dx,
+ mouse.dy,
+ butstatus[mouse.lbutton],
+ butstatus[mouse_lbut],
+ butstatus[mouse.rbutton],
+ butstatus[mouse_rbut],
+ butstatus[mouse.mbutton],
+ butstatus[mouse_mbut],
+ wheel,
+ ),
+ )
+ libtcod.console_print(sample_console, 1, 10, "1 : Hide cursor\n2 : Show cursor")
+ if key.c == ord("1"):
libtcod.mouse_show_cursor(False)
- elif key.c == ord('2'):
+ elif key.c == ord("2"):
libtcod.mouse_show_cursor(True)
+
#############################################
# name generator sample
#############################################
@@ -1256,6 +1248,8 @@ def render_mouse(first, key, mouse):
ng_delay = 0.0
ng_names = []
ng_sets = None
+
+
def render_name(first, key, mouse):
global ng_curset
global ng_nbsets
@@ -1264,118 +1258,127 @@ def render_name(first, key, mouse):
global ng_sets
if ng_nbsets == 0:
# parse all *.cfg files in data/namegen
- for file in os.listdir(b'data/namegen') :
- if file.find(b'.cfg') > 0 :
- libtcod.namegen_parse(os.path.join(b'data',b'namegen',file))
+ for file in os.listdir(get_data("namegen")):
+ if file.find(".cfg") > 0:
+ libtcod.namegen_parse(get_data(os.path.join("namegen", file)))
# get the sets list
- ng_sets=libtcod.namegen_get_sets()
- print (ng_sets)
- ng_nbsets=len(ng_sets)
+ ng_sets = libtcod.namegen_get_sets()
+ print(ng_sets)
+ ng_nbsets = len(ng_sets)
if first:
libtcod.sys_set_fps(30)
- while len(ng_names)> 15:
+ while len(ng_names) > 15:
ng_names.pop(0)
libtcod.console_clear(sample_console)
- libtcod.console_set_default_foreground(sample_console,libtcod.white)
- libtcod.console_print(sample_console,1,1,"%s\n\n+ : next generator\n- : prev generator" %
- ng_sets[ng_curset])
- for i in range(len(ng_names)) :
- libtcod.console_print_ex(sample_console,SAMPLE_SCREEN_WIDTH-2,2+i,
- libtcod.BKGND_NONE,libtcod.RIGHT,ng_names[i])
+ libtcod.console_set_default_foreground(sample_console, libtcod.white)
+ libtcod.console_print(sample_console, 1, 1, "%s\n\n+ : next generator\n- : prev generator" % ng_sets[ng_curset])
+ for i in range(len(ng_names)):
+ libtcod.console_print_ex(
+ sample_console, SAMPLE_SCREEN_WIDTH - 2, 2 + i, libtcod.BKGND_NONE, libtcod.RIGHT, ng_names[i]
+ )
ng_delay += libtcod.sys_get_last_frame_length()
- if ng_delay > 0.5 :
+ if ng_delay > 0.5:
ng_delay -= 0.5
ng_names.append(libtcod.namegen_generate(ng_sets[ng_curset]))
- if key.c == ord('+'):
+ if key.c == ord("+"):
ng_curset += 1
- if ng_curset == ng_nbsets :
- ng_curset=0
+ if ng_curset == ng_nbsets:
+ ng_curset = 0
ng_names.append("======")
- elif key.c == ord('-'):
+ elif key.c == ord("-"):
ng_curset -= 1
- if ng_curset < 0 :
- ng_curset=ng_nbsets-1
+ if ng_curset < 0:
+ ng_curset = ng_nbsets - 1
ng_names.append("======")
+
#############################################
# python fast render sample
#############################################
-try: #import NumPy
+try: # import NumPy
import numpy as np
+
numpy_available = True
except ImportError:
numpy_available = False
-use_numpy = numpy_available #default option
+use_numpy = numpy_available # default option
SCREEN_W = SAMPLE_SCREEN_WIDTH
SCREEN_H = SAMPLE_SCREEN_HEIGHT
HALF_W = SCREEN_W // 2
HALF_H = SCREEN_H // 2
-RES_U = 80 #texture resolution
+RES_U = 80 # texture resolution
RES_V = 80
-TEX_STRETCH = 5 #texture stretching with tunnel depth
+TEX_STRETCH = 5 # texture stretching with tunnel depth
SPEED = 15
-LIGHT_BRIGHTNESS = 3.5 #brightness multiplier for all lights (changes their radius)
-LIGHTS_CHANCE = 0.07 #chance of a light appearing
+LIGHT_BRIGHTNESS = 3.5 # brightness multiplier for all lights (changes their radius)
+LIGHTS_CHANCE = 0.07 # chance of a light appearing
MAX_LIGHTS = 6
MIN_LIGHT_STRENGTH = 0.2
-LIGHT_UPDATE = 0.05 #how much the ambient light changes to reflect current light sources
-AMBIENT_LIGHT = 0.8 #brightness of tunnel texture
+LIGHT_UPDATE = 0.05 # how much the ambient light changes to reflect current light sources
+AMBIENT_LIGHT = 0.8 # brightness of tunnel texture
-#the coordinates of all tiles in the screen, as numpy arrays. example: (4x3 pixels screen)
-#xc = [[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]]
-#yc = [[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3]]
+# the coordinates of all tiles in the screen, as numpy arrays. example: (4x3 pixels screen)
+# xc = [[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]]
+# yc = [[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3]]
if numpy_available:
(xc, yc) = np.meshgrid(range(SCREEN_W), range(SCREEN_H))
- #translate coordinates of all pixels to center
+ # translate coordinates of all pixels to center
xc = xc - HALF_W
yc = yc - HALF_H
noise2d = libtcod.noise_new(2, 0.5, 2.0)
-if numpy_available: #the texture starts empty
+if numpy_available: # the texture starts empty
texture = np.zeros((RES_U, RES_V))
-#create lists to work without numpy
+# create lists to work without numpy
texture2 = [0 for i in range(RES_U * RES_V)]
brightness2 = [0 for i in range(SCREEN_W * SCREEN_H)]
R2 = [0 for i in range(SCREEN_W * SCREEN_H)]
G2 = [0 for i in range(SCREEN_W * SCREEN_H)]
B2 = [0 for i in range(SCREEN_W * SCREEN_H)]
+
class Light:
def __init__(self, x, y, z, r, g, b, strength):
- self.x, self.y, self.z = x, y, z #pos.
- self.r, self.g, self.b = r, g, b #color
- self.strength = strength #between 0 and 1, defines brightness
+ self.x, self.y, self.z = x, y, z # pos.
+ self.r, self.g, self.b = r, g, b # color
+ self.strength = strength # between 0 and 1, defines brightness
+
def render_py(first, key, mouse):
global use_numpy, frac_t, abs_t, lights, tex_r, tex_g, tex_b, xc, yc, texture, texture2, brightness2, R2, G2, B2
- if key.c == ord(' ') and numpy_available: #toggle renderer
+ if key.c == ord(" ") and numpy_available: # toggle renderer
use_numpy = not use_numpy
first = True
- if first: #initialize stuff
+ if first: # initialize stuff
libtcod.sys_set_fps(0)
- libtcod.console_clear(sample_console) #render status message
- if not numpy_available: text = 'NumPy uninstalled, using default renderer'
- elif use_numpy: text = 'Renderer: NumPy \nSpacebar to change'
- else: text = 'Renderer: default\nSpacebar to change'
- libtcod.console_set_default_foreground(sample_console,libtcod.white)
+ libtcod.console_clear(sample_console) # render status message
+ if not numpy_available:
+ text = "NumPy uninstalled, using default renderer"
+ elif use_numpy:
+ text = "Renderer: NumPy \nSpacebar to change"
+ else:
+ text = "Renderer: default\nSpacebar to change"
+ libtcod.console_set_default_foreground(sample_console, libtcod.white)
libtcod.console_print(sample_console, 1, SCREEN_H - 3, text)
- frac_t = RES_V - 1 #time is represented in number of pixels of the texture, start later in time to initialize texture
+ frac_t = (
+ RES_V - 1
+ ) # time is represented in number of pixels of the texture, start later in time to initialize texture
abs_t = RES_V - 1
- lights = [] #lights list, and current color of the tunnel texture
+ lights = [] # lights list, and current color of the tunnel texture
tex_r, tex_g, tex_b = 0, 0, 0
- time_delta = libtcod.sys_get_last_frame_length() * SPEED #advance time
- frac_t += time_delta #increase fractional (always < 1.0) time
- abs_t += time_delta #increase absolute elapsed time
- int_t = int(frac_t) #integer time units that passed this frame (number of texture pixels to advance)
- frac_t -= int_t #keep this < 1.0
+ time_delta = libtcod.sys_get_last_frame_length() * SPEED # advance time
+ frac_t += time_delta # increase fractional (always < 1.0) time
+ abs_t += time_delta # increase absolute elapsed time
+ int_t = int(frac_t) # integer time units that passed this frame (number of texture pixels to advance)
+ frac_t -= int_t # keep this < 1.0
- #change texture color according to presence of lights (basically, sum them
- #to get ambient light and smoothly change the current color into that)
+ # change texture color according to presence of lights (basically, sum them
+ # to get ambient light and smoothly change the current color into that)
ambient_r = AMBIENT_LIGHT * sum([light.r * light.strength for light in lights])
ambient_g = AMBIENT_LIGHT * sum([light.g * light.strength for light in lights])
ambient_b = AMBIENT_LIGHT * sum([light.b * light.strength for light in lights])
@@ -1384,47 +1387,48 @@ def render_py(first, key, mouse):
tex_g = tex_g * (1 - alpha) + ambient_g * alpha
tex_b = tex_b * (1 - alpha) + ambient_b * alpha
- if int_t >= 1: #roll texture (ie, advance in tunnel) according to int_t
- int_t = int_t % RES_V #can't roll more than the texture's size (can happen when time_delta is large)
- int_abs_t = int(abs_t) #new pixels are based on absolute elapsed time
+ if int_t >= 1: # roll texture (ie, advance in tunnel) according to int_t
+ int_t = int_t % RES_V # can't roll more than the texture's size (can happen when time_delta is large)
+ int_abs_t = int(abs_t) # new pixels are based on absolute elapsed time
if use_numpy:
texture = np.roll(texture, -int_t, 1)
- #replace new stretch of texture with new values
+ # replace new stretch of texture with new values
for v in range(RES_V - int_t, RES_V):
for u in range(0, RES_U):
tex_v = (v + int_abs_t) / float(RES_V)
- texture[u,v] = (libtcod.noise_get_fbm(noise2d, [u/float(RES_U), tex_v], 32.0) +
- libtcod.noise_get_fbm(noise2d, [1 - u/float(RES_U), tex_v], 32.0))
+ texture[u, v] = libtcod.noise_get_fbm(
+ noise2d, [u / float(RES_U), tex_v], 32.0
+ ) + libtcod.noise_get_fbm(noise2d, [1 - u / float(RES_U), tex_v], 32.0)
- else: #"roll" texture, without numpy
- temp = texture2[0 : RES_U*int_t]
- texture2 = texture2[RES_U*int_t : ]
+ else: # "roll" texture, without numpy
+ temp = texture2[0 : RES_U * int_t]
+ texture2 = texture2[RES_U * int_t :]
texture2.extend(temp)
- #replace new stretch of texture with new values
+ # replace new stretch of texture with new values
for v in range(RES_V - int_t, RES_V):
for u in range(0, RES_U):
tex_v = (v + int_abs_t) / float(RES_V)
- texture2[u + v*RES_U] = (
- libtcod.noise_get_fbm(noise2d, [u/float(RES_U), tex_v], 32.0) +
- libtcod.noise_get_fbm(noise2d, [1 - u/float(RES_U), tex_v], 32.0))
+ texture2[u + v * RES_U] = libtcod.noise_get_fbm(
+ noise2d, [u / float(RES_U), tex_v], 32.0
+ ) + libtcod.noise_get_fbm(noise2d, [1 - u / float(RES_U), tex_v], 32.0)
if use_numpy:
- #squared distance from center, clipped to sensible minimum and maximum values
+ # squared distance from center, clipped to sensible minimum and maximum values
sqr_dist = xc**2 + yc**2
sqr_dist = sqr_dist.clip(1.0 / RES_V, RES_V**2)
- #one coordinate into the texture, represents depth in the tunnel
+ # one coordinate into the texture, represents depth in the tunnel
v = TEX_STRETCH * float(RES_V) / sqr_dist + frac_t
v = v.clip(0, RES_V - 1)
- #another coordinate, represents rotation around the tunnel
+ # another coordinate, represents rotation around the tunnel
u = np.mod(RES_U * (np.arctan2(yc, xc) / (2 * np.pi) + 0.5), RES_U)
- #retrieve corresponding pixels from texture
+ # retrieve corresponding pixels from texture
brightness = texture[u.astype(int), v.astype(int)] / 4.0 + 0.5
- #use the brightness map to compose the final color of the tunnel
+ # use the brightness map to compose the final color of the tunnel
R = brightness * tex_r
G = brightness * tex_g
B = brightness * tex_b
@@ -1432,62 +1436,62 @@ def render_py(first, key, mouse):
i = 0
for y in range(-HALF_H, HALF_H):
for x in range(-HALF_W, HALF_W):
- #squared distance from center, clipped to sensible minimum and maximum values
+ # squared distance from center, clipped to sensible minimum and maximum values
sqr_dist = x**2 + y**2
sqr_dist = min(max(sqr_dist, 1.0 / RES_V), RES_V**2)
- #one coordinate into the texture, represents depth in the tunnel
+ # one coordinate into the texture, represents depth in the tunnel
v = TEX_STRETCH * float(RES_V) / sqr_dist + frac_t
v = min(v, RES_V - 1)
- #another coordinate, represents rotation around the tunnel
+ # another coordinate, represents rotation around the tunnel
u = (RES_U * (math.atan2(y, x) / (2 * math.pi) + 0.5)) % RES_U
- #retrieve corresponding pixels from texture
- brightness = texture2[int(u) + int(v)*RES_U] / 4.0 + 0.5
+ # retrieve corresponding pixels from texture
+ brightness = texture2[int(u) + int(v) * RES_U] / 4.0 + 0.5
- #use the brightness map to compose the final color of the tunnel
+ # use the brightness map to compose the final color of the tunnel
R2[i] = brightness * tex_r
G2[i] = brightness * tex_g
B2[i] = brightness * tex_b
i += 1
- #create new light source
+ # create new light source
if libtcod.random_get_float(0, 0, 1) <= time_delta * LIGHTS_CHANCE and len(lights) < MAX_LIGHTS:
x = libtcod.random_get_float(0, -0.5, 0.5)
y = libtcod.random_get_float(0, -0.5, 0.5)
strength = libtcod.random_get_float(0, MIN_LIGHT_STRENGTH, 1.0)
- color = libtcod.Color(0, 0, 0) #create bright colors with random hue
+ color = libtcod.Color(0, 0, 0) # create bright colors with random hue
hue = libtcod.random_get_float(0, 0, 360)
libtcod.color_set_hsv(color, hue, 0.5, strength)
lights.append(Light(x, y, TEX_STRETCH, color.r, color.g, color.b, strength))
- #eliminate lights that are going to be out of view
+ # eliminate lights that are going to be out of view
lights = [light for light in lights if light.z - time_delta > 1.0 / RES_V]
- for light in lights: #render lights
- #move light's Z coordinate with time, then project its XYZ coordinates to screen-space
+ for light in lights: # render lights
+ # move light's Z coordinate with time, then project its XYZ coordinates to screen-space
light.z -= float(time_delta) / TEX_STRETCH
xl = light.x / light.z * SCREEN_H
yl = light.y / light.z * SCREEN_H
if use_numpy:
- #calculate brightness of light according to distance from viewer and strength,
- #then calculate brightness of each pixel with inverse square distance law
+ # calculate brightness of light according to distance from viewer and strength,
+ # then calculate brightness of each pixel with inverse square distance law
light_brightness = LIGHT_BRIGHTNESS * light.strength * (1.0 - light.z / TEX_STRETCH)
- brightness = light_brightness / ((xc - xl)**2 + (yc - yl)**2)
+ brightness = light_brightness / ((xc - xl) ** 2 + (yc - yl) ** 2)
- #make all pixels shine around this light
+ # make all pixels shine around this light
R += brightness * light.r
G += brightness * light.g
B += brightness * light.b
else:
- i = 0 #same, without numpy
+ i = 0 # same, without numpy
for y in range(-HALF_H, HALF_H):
for x in range(-HALF_W, HALF_W):
light_brightness = LIGHT_BRIGHTNESS * light.strength * (1.0 - light.z / TEX_STRETCH)
- brightness = light_brightness / ((x - xl)**2 + (y - yl)**2)
+ brightness = light_brightness / ((x - xl) ** 2 + (y - yl) ** 2)
R2[i] += brightness * light.r
G2[i] += brightness * light.g
@@ -1495,22 +1499,23 @@ def render_py(first, key, mouse):
i += 1
if use_numpy:
- #truncate values
+ # truncate values
R = R.clip(0, 255)
G = G.clip(0, 255)
B = B.clip(0, 255)
- #fill the screen with these background colors
+ # fill the screen with these background colors
libtcod.console_fill_background(sample_console, R, G, B)
else:
- #truncate and convert to integer
+ # truncate and convert to integer
R2 = [int(min(r, 255)) for r in R2]
G2 = [int(min(g, 255)) for g in G2]
B2 = [int(min(b, 255)) for b in B2]
- #fill the screen with these background colors
+ # fill the screen with these background colors
libtcod.console_fill_background(sample_console, R2, G2, B2)
+
#############################################
# main loop
#############################################
@@ -1519,32 +1524,35 @@ def __init__(self, name, func):
self.name = name
self.func = func
-samples = (Sample(' True colors ', render_colors),
- Sample(' Offscreen console ', render_offscreen),
- Sample(' Line drawing ', render_lines),
- Sample(' Noise ', render_noise),
- Sample(' Field of view ', render_fov),
- Sample(' Path finding ', render_path),
- Sample(' Bsp toolkit ', render_bsp),
- Sample(' Image toolkit ', render_image),
- Sample(' Mouse support ', render_mouse),
- Sample(' Name generator ', render_name),
- Sample(' Python fast render ', render_py))
+
+samples = (
+ Sample(" True colors ", render_colors),
+ Sample(" Offscreen console ", render_offscreen),
+ Sample(" Line drawing ", render_lines),
+ Sample(" Noise ", render_noise),
+ Sample(" Field of view ", render_fov),
+ Sample(" Path finding ", render_path),
+ Sample(" Bsp toolkit ", render_bsp),
+ Sample(" Image toolkit ", render_image),
+ Sample(" Mouse support ", render_mouse),
+ Sample(" Name generator ", render_name),
+ Sample(" Python fast render ", render_py),
+)
cur_sample = 0
credits_end = False
first = True
cur_renderer = 0
-renderer_name=('F1 GLSL ','F2 OPENGL ','F3 SDL ','F4 SDL2 ','F5 OPENGL2')
-key=libtcod.Key()
-mouse=libtcod.Mouse()
+renderer_name = ("F1 GLSL ", "F2 OPENGL ", "F3 SDL ", "F4 SDL2 ", "F5 OPENGL2")
+key = libtcod.Key()
+mouse = libtcod.Mouse()
while not libtcod.console_is_window_closed():
- libtcod.sys_check_for_event(libtcod.EVENT_KEY_PRESS|libtcod.EVENT_MOUSE,key,mouse)
+ libtcod.sys_check_for_event(libtcod.EVENT_KEY_PRESS | libtcod.EVENT_MOUSE, key, mouse)
# render the sample
samples[cur_sample].func(first, key, mouse)
first = False
- libtcod.console_blit(sample_console,
- 0, 0, SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT,
- 0, SAMPLE_SCREEN_X, SAMPLE_SCREEN_Y)
+ libtcod.console_blit(
+ sample_console, 0, 0, SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT, 0, SAMPLE_SCREEN_X, SAMPLE_SCREEN_Y
+ )
# render credits
if not credits_end:
credits_end = libtcod.console_credits_render(60, 43, 0)
@@ -1556,49 +1564,58 @@ def __init__(self, name, func):
else:
libtcod.console_set_default_foreground(None, libtcod.grey)
libtcod.console_set_default_background(None, libtcod.black)
- libtcod.console_print_ex(None, 2, 46 - (len(samples) - i),
- libtcod.BKGND_SET, libtcod.LEFT, samples[i].name)
+ libtcod.console_print_ex(None, 2, 46 - (len(samples) - i), libtcod.BKGND_SET, libtcod.LEFT, samples[i].name)
# render stats
libtcod.console_set_default_foreground(None, libtcod.grey)
- libtcod.console_print_ex(None, 79, 46, libtcod.BKGND_NONE, libtcod.RIGHT,
- 'last frame : %3d ms (%3d fps)' %
- (int(libtcod.sys_get_last_frame_length() *
- 1000.0), libtcod.sys_get_fps()))
- libtcod.console_print_ex(None, 79, 47, libtcod.BKGND_NONE, libtcod.RIGHT,
- 'elapsed : %8d ms %4.2fs' %
- (libtcod.sys_elapsed_milli(),
- libtcod.sys_elapsed_seconds()))
-
- cur_renderer=libtcod.sys_get_renderer()
- libtcod.console_set_default_foreground(None,libtcod.grey)
- libtcod.console_set_default_background(None,libtcod.black)
- libtcod.console_print_ex(None,42,46-(libtcod.NB_RENDERERS+1),libtcod.BKGND_SET, libtcod.LEFT, "Renderer :")
- for i in range(libtcod.NB_RENDERERS) :
- if i==cur_renderer :
- libtcod.console_set_default_foreground(None,libtcod.white)
- libtcod.console_set_default_background(None,libtcod.light_blue)
- else :
- libtcod.console_set_default_foreground(None,libtcod.grey)
- libtcod.console_set_default_background(None,libtcod.black)
- libtcod.console_print_ex(None,42,46-(libtcod.NB_RENDERERS-i),libtcod.BKGND_SET,libtcod.LEFT,renderer_name[i])
+ libtcod.console_print_ex(
+ None,
+ 79,
+ 46,
+ libtcod.BKGND_NONE,
+ libtcod.RIGHT,
+ "last frame : %3d ms (%3d fps)" % (int(libtcod.sys_get_last_frame_length() * 1000.0), libtcod.sys_get_fps()),
+ )
+ libtcod.console_print_ex(
+ None,
+ 79,
+ 47,
+ libtcod.BKGND_NONE,
+ libtcod.RIGHT,
+ "elapsed : %8d ms %4.2fs" % (libtcod.sys_elapsed_milli(), libtcod.sys_elapsed_seconds()),
+ )
+
+ cur_renderer = libtcod.sys_get_renderer()
+ libtcod.console_set_default_foreground(None, libtcod.grey)
+ libtcod.console_set_default_background(None, libtcod.black)
+ libtcod.console_print_ex(None, 42, 46 - (libtcod.NB_RENDERERS + 1), libtcod.BKGND_SET, libtcod.LEFT, "Renderer :")
+ for i in range(len(renderer_name)):
+ if i == cur_renderer:
+ libtcod.console_set_default_foreground(None, libtcod.white)
+ libtcod.console_set_default_background(None, libtcod.light_blue)
+ else:
+ libtcod.console_set_default_foreground(None, libtcod.grey)
+ libtcod.console_set_default_background(None, libtcod.black)
+ libtcod.console_print_ex(
+ None, 42, 46 - (libtcod.NB_RENDERERS - i), libtcod.BKGND_SET, libtcod.LEFT, renderer_name[i]
+ )
# key handler
if key.vk == libtcod.KEY_DOWN:
- cur_sample = (cur_sample+1) % len(samples)
+ cur_sample = (cur_sample + 1) % len(samples)
first = True
elif key.vk == libtcod.KEY_UP:
- cur_sample = (cur_sample-1) % len(samples)
+ cur_sample = (cur_sample - 1) % len(samples)
first = True
elif key.vk == libtcod.KEY_ENTER and key.lalt:
libtcod.console_set_fullscreen(not libtcod.console_is_fullscreen())
- elif key.vk == libtcod.KEY_PRINTSCREEN or key.c == 'p':
- print ("screenshot")
- if key.lalt :
- libtcod.console_save_apf(None,"samples.apf")
- print ("apf")
- else :
+ elif key.vk == libtcod.KEY_PRINTSCREEN or key.c == "p":
+ print("screenshot")
+ if key.lalt:
+ libtcod.console_save_apf(None, "samples.apf")
+ print("apf")
+ else:
libtcod.sys_save_screenshot()
- print ("png")
+ print("png")
elif key.vk == libtcod.KEY_ESCAPE:
break
elif key.vk == libtcod.KEY_F1:
@@ -1612,5 +1629,3 @@ def __init__(self, name, func):
elif key.vk == libtcod.KEY_F5:
libtcod.sys_set_renderer(libtcod.RENDERER_OPENGL2)
libtcod.console_flush()
-
-
diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py
old mode 100644
new mode 100755
index 6861fcd2..bae9acd6
--- a/examples/samples_tcod.py
+++ b/examples/samples_tcod.py
@@ -1,321 +1,399 @@
#!/usr/bin/env python
-#
-# libtcod python samples
-# This code demonstrates various usages of libtcod modules
-# It's in the public domain.
-#
-from __future__ import division
+"""This code demonstrates various usages of python-tcod."""
-import os
+# To the extent possible under law, the libtcod maintainers have waived all
+# copyright and related or neighboring rights to these samples.
+# https://creativecommons.org/publicdomain/zero/1.0/
+from __future__ import annotations
+import copy
+import importlib.metadata
import math
+import random
+import sys
+import time
+import warnings
+from dataclasses import dataclass
+from pathlib import Path
+from typing import TYPE_CHECKING, Any
import numpy as np
-import tcod as libtcod
+
+import tcod.bsp
+import tcod.cffi
+import tcod.console
+import tcod.constants
+import tcod.context
+import tcod.event
+import tcod.image
+import tcod.los
+import tcod.map
+import tcod.noise
+import tcod.path
+import tcod.render
+import tcod.sdl.mouse
+import tcod.sdl.render
+import tcod.tileset
+from tcod import libtcodpy
+from tcod.event import KeySym
+
+if TYPE_CHECKING:
+ from numpy.typing import NDArray
+
+
+DATA_DIR = Path(__file__).parent / "../libtcod/data"
+"""Path of the samples data directory."""
+
+assert DATA_DIR.exists(), "Data directory is missing, did you forget to run `git submodule update --init`?"
+
+WHITE = (255, 255, 255)
+GREY = (127, 127, 127)
+BLACK = (0, 0, 0)
+LIGHT_BLUE = (63, 63, 255)
+LIGHT_YELLOW = (255, 255, 63)
SAMPLE_SCREEN_WIDTH = 46
SAMPLE_SCREEN_HEIGHT = 20
SAMPLE_SCREEN_X = 20
SAMPLE_SCREEN_Y = 10
-font = os.path.join('data/fonts/consolas10x10_gs_tc.png')
-libtcod.console_set_custom_font(
- font, libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_TCOD)
-root_console = libtcod.console_init_root(80, 50, "tcod python sample", False)
-sample_console = libtcod.console_new(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT)
+FONT = DATA_DIR / "fonts/dejavu10x10_gs_tc.png"
+
+# Mutable global names.
+context: tcod.context.Context
+tileset: tcod.tileset.Tileset
+console_render: tcod.render.SDLConsoleRender # Optional SDL renderer.
+sample_minimap: tcod.sdl.render.Texture # Optional minimap texture.
+root_console = tcod.console.Console(80, 50)
+sample_console = tcod.console.Console(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT)
+cur_sample = 0 # Current selected sample.
+frame_times = [time.perf_counter()]
+frame_length = [0.0]
+START_TIME = time.perf_counter()
+
+
+def _get_elapsed_time() -> float:
+ """Return time passed since the start of the program."""
+ return time.perf_counter() - START_TIME
+
+
+class Sample:
+ """Samples base class."""
+
+ name: str = "???"
+
+ def on_enter(self) -> None:
+ """Called when entering a sample."""
+
+ def on_draw(self) -> None:
+ """Called every frame."""
+
+ def on_event(self, event: tcod.event.Event) -> None:
+ """Called for each event."""
+ global cur_sample
+ match event:
+ case tcod.event.Quit() | tcod.event.KeyDown(sym=KeySym.ESCAPE):
+ raise SystemExit
+ case tcod.event.KeyDown(sym=KeySym.DOWN):
+ cur_sample = (cur_sample + 1) % len(SAMPLES)
+ SAMPLES[cur_sample].on_enter()
+ draw_samples_menu()
+ case tcod.event.KeyDown(sym=KeySym.UP):
+ cur_sample = (cur_sample - 1) % len(SAMPLES)
+ SAMPLES[cur_sample].on_enter()
+ draw_samples_menu()
+ case tcod.event.KeyDown(sym=KeySym.RETURN, mod=mod) if mod & tcod.event.Modifier.ALT:
+ sdl_window = context.sdl_window
+ if sdl_window:
+ sdl_window.fullscreen = not sdl_window.fullscreen
+ case tcod.event.KeyDown(sym=tcod.event.KeySym.PRINTSCREEN | tcod.event.KeySym.P):
+ print("screenshot")
+ if event.mod & tcod.event.Modifier.ALT:
+ libtcodpy.console_save_apf(root_console, "samples.apf")
+ print("apf")
+ else:
+ libtcodpy.sys_save_screenshot()
+ print("png")
+ case tcod.event.KeyDown(sym=sym) if sym in RENDERER_KEYS:
+ init_context(RENDERER_KEYS[sym]) # Swap the active context for one with a different renderer
-class Sample():
- def __init__(self, name='', func=None):
- self.name = name
- self.func = func
- def on_enter(self):
- pass
+class TrueColorSample(Sample):
+ """Simple performance benchmark."""
- def on_draw(self, delta_time):
- pass
+ name = "True colors"
- def on_key(self, key):
- pass
+ def __init__(self) -> None:
+ """Initialize random generators."""
+ self.noise = tcod.noise.Noise(2, tcod.noise.Algorithm.SIMPLEX)
+ """Noise for generating color."""
- def on_mouse(self, mouse):
- pass
+ self.generator = np.random.default_rng()
+ """Numpy generator for random text."""
-#############################################
-# true color sample
-#############################################
-class TrueColorSample(Sample):
-
- def __init__(self):
- self.name = "True colors"
- # corner colors
- self.colors = np.array([(50, 40, 150), (240, 85, 5),
- (50, 35, 240), (10, 200, 130)], dtype=np.int16)
- # color shift direction
- self.slide_dir = np.array([[1, 1, 1], [-1, -1, 1],
- [1, -1, 1], [1, 1, -1]], dtype=np.int16)
- # corner indexes
- self.corners = np.array([0, 1, 2, 3])
- # sample screen mesh-grid
- self.mgrid = np.mgrid[0:1:SAMPLE_SCREEN_HEIGHT * 1j,
- 0:1:SAMPLE_SCREEN_WIDTH * 1j]
-
- def on_enter(self):
- libtcod.sys_set_fps(0)
- sample_console.clear()
-
- def on_draw(self, delta_time):
- self.slide_corner_colors()
+ def on_draw(self) -> None:
+ """Draw this sample."""
self.interpolate_corner_colors()
self.darken_background_characters()
- self.randomize_sample_conole()
- self.print_banner()
-
- def slide_corner_colors(self):
- # pick random RGB channels for each corner
- rand_channels = np.random.randint(low=0, high=3, size=4)
-
- # shift picked color channels in the direction of slide_dir
- self.colors[self.corners, rand_channels] += \
- self.slide_dir[self.corners, rand_channels] * 5
-
- # reverse slide_dir values when limits are reached
- self.slide_dir[self.colors[:] == 255] = -1
- self.slide_dir[self.colors[:] == 0] = 1
-
- def interpolate_corner_colors(self):
- # interpolate corner colors across the sample console
- for i in range(3): # for each color channel
- left = ((self.colors[2, i] - self.colors[0, i]) * self.mgrid[0] +
- self.colors[0, i])
- right = ((self.colors[3, i] - self.colors[1, i]) * self.mgrid[0] +
- self.colors[1, i])
- sample_console.bg[:, :, i] = (right - left) * self.mgrid[1] + left
-
- def darken_background_characters(self):
- # darken background characters
+ self.randomize_sample_console()
+ sample_console.print(
+ x=1,
+ y=5,
+ width=sample_console.width - 2,
+ height=sample_console.height - 1,
+ text="The Doryen library uses 24 bits colors, for both background and foreground.",
+ fg=WHITE,
+ bg=GREY,
+ bg_blend=libtcodpy.BKGND_MULTIPLY,
+ alignment=libtcodpy.CENTER,
+ )
+
+ def get_corner_colors(self) -> NDArray[np.uint8]:
+ """Return 4 random 8-bit colors, smoothed over time."""
+ noise_samples_ij = (
+ [ # i coordinates are per color channel per color
+ [0, 1, 2],
+ [3, 4, 5],
+ [6, 7, 8],
+ [9, 10, 11],
+ ],
+ time.perf_counter(), # j coordinate is time broadcast to all samples
+ )
+ colors = self.noise[noise_samples_ij]
+ colors = ((colors + 1.0) * (0.5 * 255.0)).clip(min=0, max=255) # Convert -1..1 to 0..255
+ return colors.astype(np.uint8)
+
+ def interpolate_corner_colors(self) -> None:
+ """Interpolate corner colors across the sample console."""
+ colors = self.get_corner_colors()
+ top = np.linspace(colors[0], colors[1], SAMPLE_SCREEN_WIDTH)
+ bottom = np.linspace(colors[2], colors[3], SAMPLE_SCREEN_WIDTH)
+ sample_console.bg[:] = np.linspace(top, bottom, SAMPLE_SCREEN_HEIGHT)
+
+ def darken_background_characters(self) -> None:
+ """Darken background characters."""
sample_console.fg[:] = sample_console.bg[:]
sample_console.fg[:] //= 2
- def randomize_sample_conole(self):
- # randomize sample console characters
- sample_console.ch[:] = np.random.randint(
- low=ord('a'), high=ord('z') + 1,
+ def randomize_sample_console(self) -> None:
+ """Randomize sample console characters."""
+ sample_console.ch[:] = self.generator.integers(
+ low=ord("a"),
+ high=ord("z"),
+ endpoint=True,
size=sample_console.ch.size,
dtype=np.intc,
- ).reshape(sample_console.ch.shape)
-
- def print_banner(self):
- # print text on top of samples
- sample_console.default_bg = libtcod.grey
- sample_console.print_rect(
- x=sample_console.width // 2, y=5,
- width=sample_console.width - 2, height=sample_console.height - 1,
- string="The Doryen library uses 24 bits colors, for both "
- "background and foreground.",
- bg_blend=libtcod.BKGND_MULTIPLY, alignment=libtcod.CENTER,
- )
+ ).reshape(sample_console.ch.shape)
-#############################################
-# offscreen console sample
-#############################################
class OffscreenConsoleSample(Sample):
+ """Console blit example."""
+
+ name = "Offscreen console"
- def __init__(self):
- self.name = 'Offscreen console'
- self.secondary = libtcod.console.Console(sample_console.width // 2,
- sample_console.height // 2)
- self.screenshot = libtcod.console.Console(sample_console.width,
- sample_console.height)
- self.counter = 0
+ CONSOLE_MOVE_RATE = 1 / 2
+ CONSOLE_MOVE_MARGIN = 5
+
+ def __init__(self) -> None:
+ """Initialize the offscreen console."""
+ self.secondary = tcod.console.Console(sample_console.width // 2, sample_console.height // 2)
+ self.screenshot = tcod.console.Console(sample_console.width, sample_console.height)
+ self.counter = 0.0
self.x = 0
self.y = 0
- self.xdir = 1
- self.ydir = 1
-
- self.secondary.print_frame(
- 0, 0, sample_console.width // 2, sample_console.height // 2,
- "Offscreen console",
- False,
- libtcod.BKGND_NONE
- )
+ self.x_dir = 1
+ self.y_dir = 1
+
+ self.secondary.draw_frame(0, 0, self.secondary.width, self.secondary.height, clear=False, fg=WHITE, bg=BLACK)
+ self.secondary.print(
+ 0,
+ 0,
+ width=self.secondary.width,
+ height=self.secondary.height,
+ text=" Offscreen console ",
+ fg=BLACK,
+ bg=WHITE,
+ alignment=tcod.constants.CENTER,
+ )
- self.secondary.print_rect(
- sample_console.width // 4, 2,
- sample_console.width // 2 - 2, sample_console.height // 2,
- "You can render to an offscreen console and blit in on another "
- "one, simulating alpha transparency.",
- libtcod.BKGND_NONE, libtcod.CENTER
- )
+ self.secondary.print(
+ x=1,
+ y=2,
+ width=sample_console.width // 2 - 2,
+ height=sample_console.height // 2,
+ text="You can render to an offscreen console and blit in on another one, simulating alpha transparency.",
+ fg=WHITE,
+ bg=None,
+ alignment=libtcodpy.CENTER,
+ )
- def on_enter(self):
- libtcod.sys_set_fps(0)
- # get a "screenshot" of the current sample screen
- sample_console.blit(0, 0, sample_console.width, sample_console.height,
- self.screenshot, 0, 0)
-
- def on_draw(self, delta_time):
- self.counter += delta_time * 1.5
- if self.counter >= 1:
- self.counter -= 1
- self.x += self.xdir
- self.y += self.ydir
- if self.x == sample_console.width / 2 + 5:
- self.xdir = -1
- elif self.x == -5:
- self.xdir = 1
- if self.y == sample_console.height / 2 + 5:
- self.ydir = -1
- elif self.y == -5:
- self.ydir = 1
- self.screenshot.blit(0, 0, sample_console.width, sample_console.height,
- sample_console, 0, 0)
+ def on_enter(self) -> None:
+ """Capture the previous sample screen as this samples background."""
+ self.counter = _get_elapsed_time()
+ sample_console.blit(dest=self.screenshot) # get a "screenshot" of the current sample screen
+
+ def on_draw(self) -> None:
+ """Draw and animate the offscreen console."""
+ if _get_elapsed_time() - self.counter >= self.CONSOLE_MOVE_RATE:
+ self.counter = _get_elapsed_time()
+ self.x += self.x_dir
+ self.y += self.y_dir
+ if self.x == sample_console.width / 2 + self.CONSOLE_MOVE_MARGIN:
+ self.x_dir = -1
+ elif self.x == -self.CONSOLE_MOVE_MARGIN:
+ self.x_dir = 1
+ if self.y == sample_console.height / 2 + self.CONSOLE_MOVE_MARGIN:
+ self.y_dir = -1
+ elif self.y == -self.CONSOLE_MOVE_MARGIN:
+ self.y_dir = 1
+ self.screenshot.blit(sample_console)
self.secondary.blit(
- 0, 0, sample_console.width // 2, sample_console.height // 2,
- sample_console, self.x, self.y, 1.0, 0.75
- )
+ sample_console, self.x, self.y, 0, 0, sample_console.width // 2, sample_console.height // 2, 1.0, 0.75
+ )
-#############################################
-# line drawing sample
-#############################################
class LineDrawingSample(Sample):
+ name = "Line drawing"
+
+ FLAG_NAMES = (
+ "BKGND_NONE",
+ "BKGND_SET",
+ "BKGND_MULTIPLY",
+ "BKGND_LIGHTEN",
+ "BKGND_DARKEN",
+ "BKGND_SCREEN",
+ "BKGND_COLOR_DODGE",
+ "BKGND_COLOR_BURN",
+ "BKGND_ADD",
+ "BKGND_ADDALPHA",
+ "BKGND_BURN",
+ "BKGND_OVERLAY",
+ "BKGND_ALPHA",
+ )
+
+ def __init__(self) -> None:
+ self.mk_flag = libtcodpy.BKGND_SET
+ self.bk_flag = libtcodpy.BKGND_SET
- FLAG_NAMES = [
- 'BKGND_NONE',
- 'BKGND_SET',
- 'BKGND_MULTIPLY',
- 'BKGND_LIGHTEN',
- 'BKGND_DARKEN',
- 'BKGND_SCREEN',
- 'BKGND_COLOR_DODGE',
- 'BKGND_COLOR_BURN',
- 'BKGND_ADD',
- 'BKGND_ADDALPHA',
- 'BKGND_BURN',
- 'BKGND_OVERLAY',
- 'BKGND_ALPHA',
- ]
-
- def __init__(self):
- self.name = 'Line drawing'
- self.mk_flag = libtcod.BKGND_SET
- self.bk_flag = libtcod.BKGND_SET
-
- self.bk = libtcod.console_new(sample_console.width,
- sample_console.height)
+ self.background = tcod.console.Console(sample_console.width, sample_console.height)
# initialize the colored background
- for x in range(sample_console.width):
- for y in range(sample_console.height):
- col = libtcod.Color(
- x * 255 // (sample_console.width - 1),
- (x + y) * 255 // (sample_console.width - 1 +
- sample_console.height - 1),
- y * 255 // (sample_console.height-1),
- )
- self.bk.bg[y, x] = col
- self.bk.ch[:] = ord(' ')
-
-
- def on_key(self, key):
- if key.vk in (libtcod.KEY_ENTER, libtcod.KEY_KPENTER):
- self.bk_flag += 1
- if (self.bk_flag & 0xff) > libtcod.BKGND_ALPH:
- self.bk_flag = libtcod.BKGND_NONE
-
- def on_enter(self):
- libtcod.sys_set_fps(0)
- libtcod.console_set_default_foreground(sample_console, libtcod.white)
-
- def on_draw(self, delta_time):
+ self.background.bg[:, :, 0] = np.linspace(0, 255, self.background.width)
+ self.background.bg[:, :, 2] = np.linspace(0, 255, self.background.height)[:, np.newaxis]
+ self.background.bg[:, :, 1] = (self.background.bg[:, :, 0].astype(int) + self.background.bg[:, :, 2]) / 2
+ self.background.ch[:] = ord(" ")
+
+ def on_event(self, event: tcod.event.Event) -> None:
+ match event:
+ case tcod.event.KeyDown(sym=KeySym.RETURN | KeySym.KP_ENTER):
+ self.bk_flag += 1
+ if (self.bk_flag & 0xFF) > libtcodpy.BKGND_ALPH:
+ self.bk_flag = libtcodpy.BKGND_NONE
+ case _:
+ super().on_event(event)
+
+ def on_draw(self) -> None:
alpha = 0.0
- if (self.bk_flag & 0xff) == libtcod.BKGND_ALPH:
+ if (self.bk_flag & 0xFF) == libtcodpy.BKGND_ALPH:
# for the alpha mode, update alpha every frame
- alpha = (1.0 + math.cos(libtcod.sys_elapsed_seconds() * 2)) / 2.0
- self.bk_flag = libtcod.BKGND_ALPHA(alpha)
- elif (self.bk_flag & 0xff) == libtcod.BKGND_ADDA:
+ alpha = (1.0 + math.cos(time.time() * 2)) / 2.0
+ self.bk_flag = libtcodpy.BKGND_ALPHA(int(alpha))
+ elif (self.bk_flag & 0xFF) == libtcodpy.BKGND_ADDA:
# for the add alpha mode, update alpha every frame
- alpha = (1.0 + math.cos(libtcod.sys_elapsed_seconds() * 2)) / 2.0
- self.bk_flag = libtcod.BKGND_ADDALPHA(alpha)
+ alpha = (1.0 + math.cos(time.time() * 2)) / 2.0
+ self.bk_flag = libtcodpy.BKGND_ADDALPHA(int(alpha))
- self.bk.blit(0, 0, sample_console.width,
- sample_console.height, sample_console, 0, 0)
- recty = int((sample_console.height - 2)
- * ((1.0 + math.cos(libtcod.sys_elapsed_seconds())) / 2.0))
+ self.background.blit(sample_console)
+ rect_y = int((sample_console.height - 2) * ((1.0 + math.cos(time.time())) / 2.0))
for x in range(sample_console.width):
- col = libtcod.Color(x * 255 // sample_console.width,
- x * 255 // sample_console.width,
- x * 255 // sample_console.width)
- libtcod.console_set_char_background(
- sample_console, x, recty, col, self.bk_flag)
- libtcod.console_set_char_background(
- sample_console, x, recty + 1, col, self.bk_flag)
- libtcod.console_set_char_background(
- sample_console, x, recty + 2, col, self.bk_flag)
- angle = libtcod.sys_elapsed_seconds() * 2.0
+ value = x * 255 // sample_console.width
+ col = (value, value, value)
+ sample_console.draw_rect(x=x, y=rect_y, width=1, height=3, ch=0, fg=None, bg=col, bg_blend=self.bk_flag)
+ angle = time.time() * 2.0
cos_angle = math.cos(angle)
sin_angle = math.sin(angle)
xo = int(sample_console.width // 2 * (1 + cos_angle))
- yo = int(sample_console.height // 2
- + sin_angle * sample_console.width // 2)
+ yo = int(sample_console.height // 2 + sin_angle * sample_console.width // 2)
xd = int(sample_console.width // 2 * (1 - cos_angle))
- yd = int(sample_console.height // 2
- - sin_angle * sample_console.width // 2)
+ yd = int(sample_console.height // 2 - sin_angle * sample_console.width // 2)
# draw the line
# in python the easiest way is to use the line iterator
- for x, y in libtcod.line_iter(xo, yo, xd, yd):
- if 0 <= x < sample_console.width and \
- 0 <= y < sample_console.height:
- libtcod.console_set_char_background(
- sample_console, x, y, libtcod.light_blue, self.bk_flag)
- sample_console.print_(
- 2, 2,
- '%s (ENTER to change)' % self.FLAG_NAMES[self.bk_flag & 0xff]
- )
-
-#############################################
-# noise sample
-#############################################
+ for x, y in tcod.los.bresenham((xo, yo), (xd, yd)).tolist():
+ if 0 <= x < sample_console.width and 0 <= y < sample_console.height:
+ sample_console.draw_rect(x, y, width=1, height=1, ch=0, fg=None, bg=LIGHT_BLUE, bg_blend=self.bk_flag)
+ sample_console.print(2, 2, f"{self.FLAG_NAMES[self.bk_flag & 0xFF]} (ENTER to change)", fg=WHITE, bg=None)
-NOISE_OPTIONS = [ # [name, algorithm, implementation],
- ['perlin noise', libtcod.NOISE_PERLIN, libtcod.noise.SIMPLE],
- ['simplex noise', libtcod.NOISE_SIMPLEX, libtcod.noise.SIMPLE],
- ['wavelet noise', libtcod.NOISE_WAVELET, libtcod.noise.SIMPLE],
- ['perlin fbm', libtcod.NOISE_PERLIN, libtcod.noise.FBM],
- ['perlin turbulence', libtcod.NOISE_PERLIN, libtcod.noise.TURBULENCE],
- ['simplex fbm', libtcod.NOISE_SIMPLEX, libtcod.noise.FBM],
- ['simplex turbulence',
- libtcod.NOISE_SIMPLEX, libtcod.noise.TURBULENCE],
- ['wavelet fbm', libtcod.NOISE_WAVELET, libtcod.noise.FBM],
- ['wavelet turbulence',
- libtcod.NOISE_WAVELET, libtcod.noise.TURBULENCE],
- ]
class NoiseSample(Sample):
+ name = "Noise"
+
+ NOISE_OPTIONS = ( # (name, algorithm, implementation)
+ (
+ "perlin noise",
+ tcod.noise.Algorithm.PERLIN,
+ tcod.noise.Implementation.SIMPLE,
+ ),
+ (
+ "simplex noise",
+ tcod.noise.Algorithm.SIMPLEX,
+ tcod.noise.Implementation.SIMPLE,
+ ),
+ (
+ "wavelet noise",
+ tcod.noise.Algorithm.WAVELET,
+ tcod.noise.Implementation.SIMPLE,
+ ),
+ (
+ "perlin fbm",
+ tcod.noise.Algorithm.PERLIN,
+ tcod.noise.Implementation.FBM,
+ ),
+ (
+ "perlin turbulence",
+ tcod.noise.Algorithm.PERLIN,
+ tcod.noise.Implementation.TURBULENCE,
+ ),
+ (
+ "simplex fbm",
+ tcod.noise.Algorithm.SIMPLEX,
+ tcod.noise.Implementation.FBM,
+ ),
+ (
+ "simplex turbulence",
+ tcod.noise.Algorithm.SIMPLEX,
+ tcod.noise.Implementation.TURBULENCE,
+ ),
+ (
+ "wavelet fbm",
+ tcod.noise.Algorithm.WAVELET,
+ tcod.noise.Implementation.FBM,
+ ),
+ (
+ "wavelet turbulence",
+ tcod.noise.Algorithm.WAVELET,
+ tcod.noise.Implementation.TURBULENCE,
+ ),
+ )
- def __init__(self):
- self.name = 'Noise'
+ def __init__(self) -> None:
self.func = 0
self.dx = 0.0
self.dy = 0.0
self.octaves = 4.0
self.zoom = 3.0
- self.hurst = libtcod.NOISE_DEFAULT_HURST
- self.lacunarity = libtcod.NOISE_DEFAULT_LACUNARITY
+ self.hurst = libtcodpy.NOISE_DEFAULT_HURST
+ self.lacunarity = libtcodpy.NOISE_DEFAULT_LACUNARITY
self.noise = self.get_noise()
- self.img = libtcod.image_new(SAMPLE_SCREEN_WIDTH * 2,
- SAMPLE_SCREEN_HEIGHT * 2)
+ self.img = tcod.image.Image(SAMPLE_SCREEN_WIDTH * 2, SAMPLE_SCREEN_HEIGHT * 2)
@property
- def algorithm(self):
- return NOISE_OPTIONS[self.func][1]
+ def algorithm(self) -> int:
+ return self.NOISE_OPTIONS[self.func][1]
@property
- def implementation(self):
- return NOISE_OPTIONS[self.func][2]
+ def implementation(self) -> int:
+ return self.NOISE_OPTIONS[self.func][2]
- def get_noise(self):
- return libtcod.noise.Noise(
+ def get_noise(self) -> tcod.noise.Noise:
+ return tcod.noise.Noise(
2,
self.algorithm,
self.implementation,
@@ -323,1041 +401,895 @@ def get_noise(self):
self.lacunarity,
self.octaves,
seed=None,
- )
-
- def on_enter(self):
- libtcod.sys_set_fps(0)
+ )
- def on_draw(self, delta_time):
- sample_console.clear()
- self.dx += delta_time * 0.25
- self.dy += delta_time * 0.25
+ def on_draw(self) -> None:
+ self.dx = _get_elapsed_time() * 0.25
+ self.dy = _get_elapsed_time() * 0.25
for y in range(2 * sample_console.height):
for x in range(2 * sample_console.width):
- f = [self.zoom * x / (2 * sample_console.width) + self.dx,
- self.zoom * y / (2 * sample_console.height) + self.dy]
+ f = [
+ self.zoom * x / (2 * sample_console.width) + self.dx,
+ self.zoom * y / (2 * sample_console.height) + self.dy,
+ ]
value = self.noise.get_point(*f)
c = int((value + 1.0) / 2.0 * 255)
c = max(0, min(c, 255))
self.img.put_pixel(x, y, (c // 2, c // 2, c))
- sample_console.default_bg = libtcod.grey
- rectw = 24
- recth = 13
- if self.implementation == libtcod.noise.SIMPLE:
- recth = 10
- self.img.blit_2x(sample_console, 0, 0)
- sample_console.default_bg = libtcod.grey
- sample_console.rect(2, 2, rectw, recth, False, libtcod.BKGND_MULTIPLY)
- sample_console.fg[2:2+recth, 2:2+rectw] = \
- (sample_console.fg[2:2+recth, 2:2+rectw] *
- sample_console.default_bg / 255)
-
- for curfunc in range(len(NOISE_OPTIONS)):
- text = '%i : %s' % (curfunc + 1, NOISE_OPTIONS[curfunc][0])
- if curfunc == self.func:
- sample_console.default_fg = libtcod.white
- sample_console.default_bg = libtcod.light_blue
- sample_console.print_(2, 2 + curfunc, text,
- libtcod.BKGND_SET, libtcod.LEFT)
+ rect_w = 24
+ rect_h = 13
+ if self.implementation == tcod.noise.Implementation.SIMPLE:
+ rect_h = 10
+ sample_console.draw_semigraphics(np.asarray(self.img))
+ sample_console.draw_rect(
+ 2,
+ 2,
+ rect_w,
+ rect_h,
+ ch=0,
+ fg=None,
+ bg=GREY,
+ bg_blend=libtcodpy.BKGND_MULTIPLY,
+ )
+ sample_console.fg[2 : 2 + rect_h, 2 : 2 + rect_w] = (
+ sample_console.fg[2 : 2 + rect_h, 2 : 2 + rect_w] * GREY / 255
+ )
+
+ for cur_func in range(len(self.NOISE_OPTIONS)):
+ text = f"{cur_func + 1} : {self.NOISE_OPTIONS[cur_func][0]}"
+ if cur_func == self.func:
+ sample_console.print(2, 2 + cur_func, text, fg=WHITE, bg=LIGHT_BLUE)
else:
- sample_console.default_fg = libtcod.grey
- sample_console.print_(2, 2 + curfunc, text)
- sample_console.default_fg = libtcod.white
- sample_console.print_(2, 11, 'Y/H : zoom (%2.1f)' % self.zoom)
- if self.implementation != libtcod.noise.SIMPLE:
- sample_console.print_(2, 12, 'E/D : hurst (%2.1f)' % self.hurst)
- sample_console.print_(2, 13,
- 'R/F : lacunarity (%2.1f)' %
- self.lacunarity)
- sample_console.print_(2, 14,
- 'T/G : octaves (%2.1f)' % self.octaves)
-
- def on_key(self, key):
- if key.vk == libtcod.KEY_NONE:
- return
- if ord('9') >= key.c >= ord('1'):
- self.func = key.c - ord('1')
- self.noise = self.get_noise()
- elif key.c in (ord('E'), ord('e')):
- self.hurst += 0.1
- self.noise = self.get_noise()
- elif key.c in (ord('D'), ord('d')):
- self.hurst -= 0.1
- self.noise = self.get_noise()
- elif key.c in (ord('R'), ord('r')):
- self.lacunarity += 0.5
- self.noise = self.get_noise()
- elif key.c in (ord('F'), ord('f')):
- self.lacunarity -= 0.5
- self.noise = self.get_noise()
- elif key.c in (ord('T'), ord('t')):
- self.octaves += 0.5
- self.noise.octaves = self.octaves
- elif key.c in (ord('G'), ord('g')):
- self.octaves -= 0.5
- self.noise.octaves = self.octaves
- elif key.c in (ord('Y'), ord('y')):
- self.zoom += 0.2
- elif key.c in (ord('H'), ord('h')):
- self.zoom -= 0.2
+ sample_console.print(2, 2 + cur_func, text, fg=GREY, bg=None)
+ sample_console.print(2, 11, f"Y/H : zoom ({self.zoom:2.1f})", fg=WHITE, bg=None)
+ if self.implementation != tcod.noise.Implementation.SIMPLE:
+ sample_console.print(
+ 2,
+ 12,
+ f"E/D : hurst ({self.hurst:2.1f})",
+ fg=WHITE,
+ bg=None,
+ )
+ sample_console.print(
+ 2,
+ 13,
+ f"R/F : lacunarity ({self.lacunarity:2.1f})",
+ fg=WHITE,
+ bg=None,
+ )
+ sample_console.print(
+ 2,
+ 14,
+ f"T/G : octaves ({self.octaves:2.1f})",
+ fg=WHITE,
+ bg=None,
+ )
+
+ def on_event(self, event: tcod.event.Event) -> None:
+ match event:
+ case tcod.event.KeyDown(sym=sym) if KeySym.N9 >= sym >= KeySym.N1:
+ self.func = sym - tcod.event.KeySym.N1
+ self.noise = self.get_noise()
+ case tcod.event.KeyDown(sym=KeySym.E):
+ self.hurst += 0.1
+ self.noise = self.get_noise()
+ case tcod.event.KeyDown(sym=KeySym.D):
+ self.hurst -= 0.1
+ self.noise = self.get_noise()
+ case tcod.event.KeyDown(sym=KeySym.R):
+ self.lacunarity += 0.5
+ self.noise = self.get_noise()
+ case tcod.event.KeyDown(sym=KeySym.F):
+ self.lacunarity -= 0.5
+ self.noise = self.get_noise()
+ case tcod.event.KeyDown(sym=KeySym.T):
+ self.octaves += 0.5
+ self.noise.octaves = self.octaves
+ case tcod.event.KeyDown(sym=KeySym.G):
+ self.octaves -= 0.5
+ self.noise.octaves = self.octaves
+ case tcod.event.KeyDown(sym=KeySym.Y):
+ self.zoom += 0.2
+ case tcod.event.KeyDown(sym=KeySym.H):
+ self.zoom -= 0.2
+ case _:
+ super().on_event(event)
+
#############################################
# field of view sample
#############################################
-DARK_WALL = libtcod.Color(0, 0, 100)
-LIGHT_WALL = libtcod.Color(130, 110, 50)
-DARK_GROUND = libtcod.Color(50, 50, 150)
-LIGHT_GROUND = libtcod.Color(200, 180, 50)
-
-SAMPLE_MAP = [
- '##############################################',
- '####################### #################',
- '##################### # ###############',
- '###################### ### ###########',
- '################## ##### ####',
- '################ ######## ###### ####',
- '############### #################### ####',
- '################ ###### ##',
- '######## ####### ###### # # # ##',
- '######## ###### ### ##',
- '######## ##',
- '#### ###### ### # # # ##',
- '#### ### ########## #### ##',
- '#### ### ########## ###########=##########',
- '#### ################## ##### #####',
- '#### ### #### ##### #####',
- '#### # #### #####',
- '######## # #### ##### #####',
- '######## ##### ####################',
- '##############################################',
- ]
-
-SAMPLE_MAP = np.array([list(line) for line in SAMPLE_MAP])
-
-FOV_ALGO_NAMES = [
- 'BASIC ',
- 'DIAMOND ',
- 'SHADOW ',
- 'PERMISSIVE0',
- 'PERMISSIVE1',
- 'PERMISSIVE2',
- 'PERMISSIVE3',
- 'PERMISSIVE4',
- 'PERMISSIVE5',
- 'PERMISSIVE6',
- 'PERMISSIVE7',
- 'PERMISSIVE8',
- 'RESTRICTIVE',
- ]
+DARK_WALL = (0, 0, 100)
+LIGHT_WALL = (130, 110, 50)
+DARK_GROUND = (50, 50, 150)
+LIGHT_GROUND = (200, 180, 50)
+
+SAMPLE_MAP_ = (
+ "##############################################",
+ "####################### #################",
+ "##################### # ###############",
+ "###################### ### ###########",
+ "################## ##### ####",
+ "################ ######## ###### ####",
+ "############### #################### ####",
+ "################ ###### ##",
+ "######## ####### ###### # # # ##",
+ "######## ###### ### ##",
+ "######## ##",
+ "#### ###### ### # # # ##",
+ "#### ### ########## #### ##",
+ "#### ### ########## ###########=##########",
+ "#### ################## ##### #####",
+ "#### ### #### ##### #####",
+ "#### # #### #####",
+ "######## # #### ##### #####",
+ "######## ##### ####################",
+ "##############################################",
+)
+
+SAMPLE_MAP: NDArray[Any] = np.array([[ord(c) for c in line] for line in SAMPLE_MAP_])
+
+FOV_ALGO_NAMES = (
+ "BASIC ",
+ "DIAMOND ",
+ "SHADOW ",
+ "PERMISSIVE0",
+ "PERMISSIVE1",
+ "PERMISSIVE2",
+ "PERMISSIVE3",
+ "PERMISSIVE4",
+ "PERMISSIVE5",
+ "PERMISSIVE6",
+ "PERMISSIVE7",
+ "PERMISSIVE8",
+ "RESTRICTIVE",
+ "SYMMETRIC_SHADOWCAST",
+)
TORCH_RADIUS = 10
SQUARED_TORCH_RADIUS = TORCH_RADIUS * TORCH_RADIUS
-class FOVSample(Sample):
- def __init__(self):
- self.name = 'Field of view'
+class FOVSample(Sample):
+ name = "Field of view"
- self.px = 20
- self.py = 10
- self.recompute = True
+ def __init__(self) -> None:
+ self.player_x = 20
+ self.player_y = 10
self.torch = False
- self.map = None
- self.noise = None
- self.torchx = 0.0
self.light_walls = True
- self.algo_num = 0
- # 1d noise for the torch flickering
- self.noise = libtcod.noise_new(1, 1.0, 1.0)
-
- self.map = libtcod.map_new(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT)
- self.map.walkable[:] = SAMPLE_MAP[:] == ' '
- self.map.transparent[:] = self.map.walkable[:] | (SAMPLE_MAP == '=')
-
- self.light_map_bg = np.full(SAMPLE_MAP.shape + (3,), LIGHT_GROUND,
- dtype=np.uint8)
- self.light_map_bg[SAMPLE_MAP[:] == '#'] = LIGHT_WALL
- self.dark_map_bg = np.full(SAMPLE_MAP.shape + (3,), DARK_GROUND,
- dtype=np.uint8)
- self.dark_map_bg[SAMPLE_MAP[:] == '#'] = DARK_WALL
-
- def draw_ui(self):
- libtcod.console_set_default_foreground(sample_console, libtcod.white)
- libtcod.console_print(
- sample_console, 1, 1,
- "IJKL : move around\n"
- "T : torch fx %s\n"
- "W : light walls %s\n"
- "+-: algo %s" % ('on ' if self.torch else 'off',
- 'on ' if self.light_walls else 'off',
- FOV_ALGO_NAMES[self.algo_num],
- ),
- )
- libtcod.console_set_default_foreground(sample_console, libtcod.black)
-
- def on_enter(self):
- libtcod.sys_set_fps(60)
- # we draw the foreground only the first time.
- # during the player movement, only the @ is redrawn.
- # the rest impacts only the background color
- # draw the help text & player @
- libtcod.console_clear(sample_console)
+ self.algo_num = libtcodpy.FOV_SYMMETRIC_SHADOWCAST
+ self.noise = tcod.noise.Noise(1) # 1D noise for the torch flickering.
+
+ map_shape = (SAMPLE_SCREEN_HEIGHT, SAMPLE_SCREEN_WIDTH)
+
+ self.walkable: NDArray[np.bool_] = np.zeros(map_shape, dtype=bool)
+ self.walkable[:] = SAMPLE_MAP[:] == ord(" ")
+
+ self.transparent: NDArray[np.bool_] = np.zeros(map_shape, dtype=bool)
+ self.transparent[:] = self.walkable[:] | (SAMPLE_MAP[:] == ord("="))
+
+ # Lit background colors for the map.
+ self.light_map_bg: NDArray[np.uint8] = np.full(SAMPLE_MAP.shape, LIGHT_GROUND, dtype="3B")
+ self.light_map_bg[SAMPLE_MAP[:] == ord("#")] = LIGHT_WALL
+ # Dark background colors for the map.
+ self.dark_map_bg: NDArray[np.uint8] = np.full(SAMPLE_MAP.shape, DARK_GROUND, dtype="3B")
+ self.dark_map_bg[SAMPLE_MAP[:] == ord("#")] = DARK_WALL
+
+ def draw_ui(self) -> None:
+ sample_console.print(
+ 1,
+ 1,
+ "IJKL : move around\nT : torch fx {}\nW : light walls {}\n+-: algo {}".format(
+ "on " if self.torch else "off",
+ "on " if self.light_walls else "off",
+ FOV_ALGO_NAMES[self.algo_num],
+ ),
+ fg=WHITE,
+ bg=None,
+ )
+
+ def on_draw(self) -> None:
+ sample_console.clear()
+ # Draw the help text & player @.
self.draw_ui()
- libtcod.console_put_char(sample_console, self.px, self.py, '@',
- libtcod.BKGND_NONE)
- # draw windows
- sample_console.ch[np.where(SAMPLE_MAP == '=')] = libtcod.CHAR_DHLINE
- sample_console.fg[np.where(SAMPLE_MAP == '=')] = libtcod.black
-
- def on_draw(self, delta_time):
- dx = 0.0
- dy = 0.0
- di = 0.0
- if self.recompute:
- self.recompute = False
- self.map.compute_fov(
- self.px,
- self.py,
- TORCH_RADIUS if self.torch else 0,
- self.light_walls,
- self.algo_num
- )
- sample_console.bg[:] = self.dark_map_bg[:]
+ sample_console.print(self.player_x, self.player_y, "@")
+ # Draw windows.
+ sample_console.rgb["ch"][SAMPLE_MAP[:] == ord("=")] = 0x2550 # BOX DRAWINGS DOUBLE HORIZONTAL
+ sample_console.rgb["fg"][SAMPLE_MAP[:] == ord("=")] = BLACK
+
+ # Get a 2D boolean array of visible cells.
+ fov = tcod.map.compute_fov(
+ transparency=self.transparent,
+ pov=(self.player_y, self.player_x),
+ radius=TORCH_RADIUS if self.torch else 0,
+ light_walls=self.light_walls,
+ algorithm=self.algo_num,
+ )
+
if self.torch:
- # slightly change the perlin noise parameter
- self.torchx += 0.1
- # randomize the light position between -1.5 and 1.5
- tdx = [self.torchx + 20.0]
- dx = libtcod.noise_get(self.noise, tdx,
- libtcod.NOISE_SIMPLEX) * 1.5
- tdx[0] += 30.0
- dy = libtcod.noise_get(self.noise, tdx,
- libtcod.NOISE_SIMPLEX) * 1.5
- di = 0.2 * libtcod.noise_get(self.noise, [self.torchx],
- libtcod.NOISE_SIMPLEX)
- #where_fov = np.where(self.map.fov[:])
- mgrid = np.mgrid[:SAMPLE_SCREEN_HEIGHT, :SAMPLE_SCREEN_WIDTH]
- # get squared distance
- light = ((mgrid[0] - self.py + dy) ** 2 +
- (mgrid[1] - self.px + dx) ** 2)
- light = light.astype(np.float16)
- where_visible = np.where((light < SQUARED_TORCH_RADIUS) &
- self.map.fov[:])
- light[where_visible] = SQUARED_TORCH_RADIUS - light[where_visible]
- light[where_visible] /= SQUARED_TORCH_RADIUS
- light[where_visible] += di
- light[where_visible] = light[where_visible].clip(0, 1)
-
- for yx in zip(*where_visible):
- sample_console.bg[yx] = libtcod.color_lerp(
- tuple(self.dark_map_bg[yx]),
- tuple(self.light_map_bg[yx]),
- light[yx],
- )
+ # Derive the touch from noise based on the current time.
+ torch_t = _get_elapsed_time() * 5
+ # Randomize the light position between -1.5 and 1.5
+ torch_x = self.player_x + self.noise.get_point(torch_t) * 1.5
+ torch_y = self.player_y + self.noise.get_point(torch_t + 11) * 1.5
+ # Extra light brightness.
+ brightness = 0.2 * self.noise.get_point(torch_t + 17)
+
+ # Get the squared distance using a mesh grid.
+ y, x = np.mgrid[:SAMPLE_SCREEN_HEIGHT, :SAMPLE_SCREEN_WIDTH]
+ # Center the mesh grid on the torch position.
+ x = x.astype(np.float32) - torch_x
+ y = y.astype(np.float32) - torch_y
+
+ distance_squared = x**2 + y**2 # 2D squared distance array.
+
+ # Get the currently visible cells.
+ visible = (distance_squared < SQUARED_TORCH_RADIUS) & fov
+
+ # Invert the values, so that the center is the 'brightest' point.
+ light = SQUARED_TORCH_RADIUS - distance_squared
+ light /= SQUARED_TORCH_RADIUS # Convert into non-squared distance.
+ light += brightness # Add random brightness.
+ light.clip(0, 1, out=light) # Clamp values in-place.
+ light[~visible] = 0 # Set non-visible areas to darkness.
+
+ # Setup background colors for floating point math.
+ light_bg: NDArray[np.float16] = self.light_map_bg.astype(np.float16)
+ dark_bg: NDArray[np.float16] = self.dark_map_bg.astype(np.float16)
+
+ # Linear interpolation between colors.
+ sample_console.rgb["bg"] = dark_bg + (light_bg - dark_bg) * light[..., np.newaxis]
else:
- where_fov = np.where(self.map.fov[:])
- sample_console.bg[where_fov] = self.light_map_bg[where_fov]
-
+ sample_console.bg[...] = np.select(
+ condlist=[fov[:, :, np.newaxis]],
+ choicelist=[self.light_map_bg],
+ default=self.dark_map_bg,
+ )
- def on_key(self, key):
- MOVE_KEYS = {
- ord('i'): (0, -1),
- ord('j'): (-1, 0),
- ord('k'): (0, 1),
- ord('l'): (1, 0),
+ def on_event(self, event: tcod.event.Event) -> None:
+ MOVE_KEYS = { # noqa: N806
+ tcod.event.KeySym.I: (0, -1),
+ tcod.event.KeySym.J: (-1, 0),
+ tcod.event.KeySym.K: (0, 1),
+ tcod.event.KeySym.L: (1, 0),
}
- FOV_SELECT_KEYS = {ord('-'): -1, ord('='): 1}
- if key.c in MOVE_KEYS:
- x, y = MOVE_KEYS[key.c]
- if SAMPLE_MAP[self.py + y][self.px + x] == ' ':
- libtcod.console_put_char(sample_console, self.px, self.py, ' ',
- libtcod.BKGND_NONE)
- self.px += x
- self.py += y
- libtcod.console_put_char(sample_console, self.px, self.py, '@',
- libtcod.BKGND_NONE)
- self.recompute = True
- elif key.c == ord('t'):
- self.torch = not self.torch
- self.draw_ui()
- self.recompute = True
- elif key.c == ord('w'):
- self.light_walls = not self.light_walls
- self.draw_ui()
- self.recompute = True
- elif key.c in FOV_SELECT_KEYS:
- self.algo_num += FOV_SELECT_KEYS[key.c]
- self.algo_num %= libtcod.NB_FOV_ALGORITHMS
- self.draw_ui()
- self.recompute = True
+ FOV_SELECT_KEYS = { # noqa: N806
+ tcod.event.KeySym.MINUS: -1,
+ tcod.event.KeySym.EQUALS: 1,
+ tcod.event.KeySym.KP_MINUS: -1,
+ tcod.event.KeySym.KP_PLUS: 1,
+ }
+ match event:
+ case tcod.event.KeyDown(sym=sym) if sym in MOVE_KEYS:
+ x, y = MOVE_KEYS[sym]
+ if self.walkable[self.player_y + y, self.player_x + x]:
+ self.player_x += x
+ self.player_y += y
+ case tcod.event.KeyDown(sym=KeySym.T):
+ self.torch = not self.torch
+ case tcod.event.KeyDown(sym=KeySym.W):
+ self.light_walls = not self.light_walls
+ case tcod.event.KeyDown(sym=sym) if sym in FOV_SELECT_KEYS:
+ self.algo_num += FOV_SELECT_KEYS[sym]
+ self.algo_num %= len(FOV_ALGO_NAMES)
+ case _:
+ super().on_event(event)
-#############################################
-# pathfinding sample
-#############################################
class PathfindingSample(Sample):
- def __init__(self):
- self.name = 'Path finding'
-
- self.px = 20
- self.py = 10
- self.dx = 24
- self.dy = 1
- self.map = None
- self.path = None
- self.dijk_dist = 0.0
+ name = "Path finding"
+
+ def __init__(self) -> None:
+ """Initialize this sample."""
+ self.player_x = 20
+ self.player_y = 10
+ self.dest_x = 24
+ self.dest_y = 1
self.using_astar = True
- self.dijk = None
- self.recalculate = False
self.busy = 0.0
- self.oldchar = ' '
+ self.cost = SAMPLE_MAP[:] == ord(" ")
+ self.graph = tcod.path.SimpleGraph(cost=self.cost, cardinal=70, diagonal=99)
+ self.pathfinder = tcod.path.Pathfinder(graph=self.graph)
+
+ self.background_console = tcod.console.Console(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT)
- self.map = libtcod.map_new(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT)
- for y in range(SAMPLE_SCREEN_HEIGHT):
- for x in range(SAMPLE_SCREEN_WIDTH):
- if SAMPLE_MAP[y][x] == ' ':
- # ground
- libtcod.map_set_properties(self.map, x, y, True, True)
- elif SAMPLE_MAP[y][x] == '=':
- # window
- libtcod.map_set_properties(self.map, x, y, True, False)
- self.path = libtcod.path_new_using_map(self.map)
- self.dijk = libtcod.dijkstra_new(self.map)
-
- def on_enter(self):
- libtcod.sys_set_fps(60)
- # we draw the foreground only the first time.
- # during the player movement, only the @ is redrawn.
- # the rest impacts only the background color
- # draw the help text & player @
- libtcod.console_clear(sample_console)
- libtcod.console_set_default_foreground(sample_console, libtcod.white)
- libtcod.console_put_char(sample_console, self.dx, self.dy, '+',
- libtcod.BKGND_NONE)
- libtcod.console_put_char(sample_console, self.px, self.py, '@',
- libtcod.BKGND_NONE)
- libtcod.console_print(
- sample_console, 1, 1,
- "IJKL / mouse :\nmove destination\nTAB : A*/dijkstra")
- libtcod.console_print(sample_console, 1, 4, "Using : A*")
- # draw windows
- for y in range(SAMPLE_SCREEN_HEIGHT):
- for x in range(SAMPLE_SCREEN_WIDTH):
- if SAMPLE_MAP[y][x] == '=':
- libtcod.console_put_char(sample_console, x, y,
- libtcod.CHAR_DHLINE,
- libtcod.BKGND_NONE)
- self.recalculate = True
-
- def on_draw(self, delta_time):
- if self.recalculate:
- if self.using_astar:
- libtcod.path_compute(self.path, self.px, self.py,
- self.dx, self.dy)
- else:
- self.dijk_dist = 0.0
- # compute dijkstra grid (distance from px,py)
- libtcod.dijkstra_compute(self.dijk, self.px, self.py)
- # get the maximum distance (needed for rendering)
- for y in range(SAMPLE_SCREEN_HEIGHT):
- for x in range(SAMPLE_SCREEN_WIDTH):
- d = libtcod.dijkstra_get_distance(self.dijk, x, y)
- if d > self.dijk_dist:
- self.dijk_dist = d
- # compute path from px,py to dx,dy
- libtcod.dijkstra_path_set(self.dijk, self.dx, self.dy)
- self.recalculate = False
- self.busy = 0.2
# draw the dungeon
- for y in range(SAMPLE_SCREEN_HEIGHT):
- for x in range(SAMPLE_SCREEN_WIDTH):
- if SAMPLE_MAP[y][x] == '#':
- libtcod.console_set_char_background(
- sample_console, x, y, DARK_WALL, libtcod.BKGND_SET)
- else:
- libtcod.console_set_char_background(
- sample_console, x, y, DARK_GROUND, libtcod.BKGND_SET)
+ self.background_console.rgb["fg"] = BLACK
+ self.background_console.rgb["bg"] = DARK_GROUND
+ self.background_console.rgb["bg"][SAMPLE_MAP[:] == ord("#")] = DARK_WALL
+ self.background_console.rgb["ch"][SAMPLE_MAP[:] == ord("=")] = ord("═")
+
+ def on_enter(self) -> None:
+ """Do nothing."""
+
+ def on_draw(self) -> None:
+ """Recompute and render pathfinding."""
+ self.pathfinder = tcod.path.Pathfinder(graph=self.graph)
+ # self.pathfinder.clear() # Known issues, needs fixing # noqa: ERA001
+ self.pathfinder.add_root((self.player_y, self.player_x))
+
+ # draw the dungeon
+ self.background_console.blit(dest=sample_console)
+
+ sample_console.print(self.dest_x, self.dest_y, "+", fg=WHITE)
+ sample_console.print(self.player_x, self.player_y, "@", fg=WHITE)
+ sample_console.print(1, 1, "IJKL / mouse :\nmove destination\nTAB : A*/dijkstra", fg=WHITE, bg=None)
+ sample_console.print(1, 4, "Using : A*", fg=WHITE, bg=None)
+
+ if not self.using_astar:
+ self.pathfinder.resolve(goal=None)
+ reachable = self.pathfinder.distance != np.iinfo(self.pathfinder.distance.dtype).max
+
+ # draw distance from player
+ dijkstra_max_dist = float(self.pathfinder.distance[reachable].max())
+ np.array(self.pathfinder.distance, copy=True, dtype=np.float32)
+ interpolate = self.pathfinder.distance[reachable] * 0.9 / dijkstra_max_dist
+ color_delta = (np.array(DARK_GROUND) - np.array(LIGHT_GROUND)).astype(np.float32)
+ sample_console.rgb["bg"][reachable] = np.array(LIGHT_GROUND) + interpolate[:, np.newaxis] * color_delta
+
# draw the path
- if self.using_astar:
- for i in range(libtcod.path_size(self.path)):
- x, y = libtcod.path_get(self.path, i)
- libtcod.console_set_char_background(
- sample_console, x, y, LIGHT_GROUND, libtcod.BKGND_SET)
- else:
- for y in range(SAMPLE_SCREEN_HEIGHT):
- for x in range(SAMPLE_SCREEN_WIDTH):
- if SAMPLE_MAP[y][x] != '#':
- libtcod.console_set_char_background(
- sample_console, x, y,
- libtcod.color_lerp(
- LIGHT_GROUND, DARK_GROUND,
- 0.9* libtcod.dijkstra_get_distance(self.dijk,
- x, y)
- / self.dijk_dist),
- libtcod.BKGND_SET,
- )
- for i in range(libtcod.dijkstra_size(self.dijk)):
- x, y = libtcod.dijkstra_get(self.dijk, i)
- libtcod.console_set_char_background(
- sample_console, x, y, LIGHT_GROUND, libtcod.BKGND_SET)
+ path = self.pathfinder.path_to((self.dest_y, self.dest_x))[1:]
+ sample_console.rgb["bg"][tuple(path.T)] = LIGHT_GROUND
# move the creature
- self.busy -= libtcod.sys_get_last_frame_length()
+ self.busy -= frame_length[-1]
if self.busy <= 0.0:
self.busy = 0.2
- if self.using_astar:
- if not libtcod.path_is_empty(self.path):
- libtcod.console_put_char(sample_console, self.px, self.py,
- ' ', libtcod.BKGND_NONE)
- self.px, self.py = libtcod.path_walk(self.path, True)
- libtcod.console_put_char(sample_console, self.px, self.py,
- '@', libtcod.BKGND_NONE)
- else:
- if not libtcod.dijkstra_is_empty(self.dijk):
- libtcod.console_put_char(sample_console, self.px, self.py,
- ' ', libtcod.BKGND_NONE)
- self.px, self.py = libtcod.dijkstra_path_walk(self.dijk)
- libtcod.console_put_char(sample_console, self.px, self.py,
- '@', libtcod.BKGND_NONE)
- self.recalculate = True
-
- def on_key(self, key):
- if key.c in (ord('I'), ord('i')) and self.dy > 0:
- # destination move north
- libtcod.console_put_char(sample_console, self.dx, self.dy,
- self.oldchar, libtcod.BKGND_NONE)
- self.dy -= 1
- self.oldchar = libtcod.console_get_char(sample_console, self.dx,
- self.dy)
- libtcod.console_put_char(sample_console, self.dx, self.dy, '+',
- libtcod.BKGND_NONE)
- if SAMPLE_MAP[self.dy][self.dx] == ' ':
- self.recalculate = True
- elif (key.c in (ord('K'), ord('k'))
- and self.dy < SAMPLE_SCREEN_HEIGHT - 1):
- # destination move south
- libtcod.console_put_char(sample_console, self.dx, self.dy,
- self.oldchar, libtcod.BKGND_NONE)
- self.dy += 1
- self.oldchar = libtcod.console_get_char(sample_console, self.dx,
- self.dy)
- libtcod.console_put_char(sample_console, self.dx, self.dy, '+',
- libtcod.BKGND_NONE)
- if SAMPLE_MAP[self.dy][self.dx] == ' ':
- self.recalculate = True
- elif key.c in (ord('J'), ord('j')) and self.dx > 0:
- # destination move west
- libtcod.console_put_char(sample_console, self.dx, self.dy,
- self.oldchar, libtcod.BKGND_NONE)
- self.dx -= 1
- self.oldchar = libtcod.console_get_char(sample_console, self.dx,
- self.dy)
- libtcod.console_put_char(sample_console, self.dx, self.dy, '+',
- libtcod.BKGND_NONE)
- if SAMPLE_MAP[self.dy][self.dx] == ' ':
- self.recalculate = True
- elif (key.c in (ord('L'), ord('l')) and
- self.dx < SAMPLE_SCREEN_WIDTH - 1):
- # destination move east
- libtcod.console_put_char(sample_console, self.dx, self.dy,
- self.oldchar, libtcod.BKGND_NONE)
- self.dx += 1
- self.oldchar = libtcod.console_get_char(sample_console, self.dx,
- self.dy)
- libtcod.console_put_char(sample_console, self.dx, self.dy, '+',
- libtcod.BKGND_NONE)
- if SAMPLE_MAP[self.dy][self.dx] == ' ':
- self.recalculate = True
- elif key.vk == libtcod.KEY_TAB:
- self.using_astar = not self.using_astar
- if self.using_astar:
- libtcod.console_print(sample_console, 1, 4, "Using : A* ")
- else:
- libtcod.console_print(sample_console, 1, 4, "Using : Dijkstra")
- self.recalculate = True
-
- def on_mouse(self, mouse):
- mx = mouse.cx - SAMPLE_SCREEN_X
- my = mouse.cy - SAMPLE_SCREEN_Y
- if (0 <= mx < SAMPLE_SCREEN_WIDTH and 0 <= my < SAMPLE_SCREEN_HEIGHT
- and (self.dx != mx or self.dy != my)):
- libtcod.console_put_char(sample_console, self.dx, self.dy,
- self.oldchar, libtcod.BKGND_NONE)
- self.dx = mx
- self.dy = my
- self.oldchar = libtcod.console_get_char(sample_console, self.dx,
- self.dy)
- libtcod.console_put_char(sample_console, self.dx, self.dy, '+',
- libtcod.BKGND_NONE)
- if SAMPLE_MAP[self.dy][self.dx] == ' ':
- self.recalculate = True
+ if len(path):
+ self.player_y = int(path.item(0, 0))
+ self.player_x = int(path.item(0, 1))
+
+ def on_event(self, event: tcod.event.Event) -> None:
+ """Handle movement and UI."""
+ match event:
+ case tcod.event.KeyDown(sym=KeySym.I) if self.dest_y > 0: # destination move north
+ self.dest_y -= 1
+ case tcod.event.KeyDown(sym=KeySym.K) if self.dest_y < SAMPLE_SCREEN_HEIGHT - 1: # destination move south
+ self.dest_y += 1
+ case tcod.event.KeyDown(sym=KeySym.J) if self.dest_x > 0: # destination move west
+ self.dest_x -= 1
+ case tcod.event.KeyDown(sym=KeySym.L) if self.dest_x < SAMPLE_SCREEN_WIDTH - 1: # destination move east
+ self.dest_x += 1
+ case tcod.event.KeyDown(sym=KeySym.TAB):
+ self.using_astar = not self.using_astar
+ case tcod.event.MouseMotion(): # Move destination via mouseover
+ mx = event.tile.x - SAMPLE_SCREEN_X
+ my = event.tile.y - SAMPLE_SCREEN_Y
+ if 0 <= mx < SAMPLE_SCREEN_WIDTH and 0 <= my < SAMPLE_SCREEN_HEIGHT:
+ self.dest_x = int(mx)
+ self.dest_y = int(my)
+ case _:
+ super().on_event(event)
+
#############################################
# bsp sample
#############################################
-bsp_depth = 8
-bsp_min_room_size = 4
-# a room fills a random part of the node or the maximum available space ?
-bsp_random_room = False
-# if true, there is always a wall on north & west side of a room
-bsp_room_walls = True
-bsp_map = None
+
+
# draw a vertical line
-def vline(m, x, y1, y2):
+def vline(map_: NDArray[np.bool_], x: int, y1: int, y2: int) -> None:
if y1 > y2:
y1, y2 = y2, y1
for y in range(y1, y2 + 1):
- m[x][y] = True
+ map_[y, x] = True
+
# draw a vertical line up until we reach an empty space
-def vline_up(m, x, y):
- while y >= 0 and not m[x][y]:
- m[x][y] = True
+def vline_up(map_: NDArray[np.bool_], x: int, y: int) -> None:
+ while y >= 0 and not map_[y, x]:
+ map_[y, x] = True
y -= 1
+
# draw a vertical line down until we reach an empty space
-def vline_down(m, x, y):
- while y < SAMPLE_SCREEN_HEIGHT and not m[x][y]:
- m[x][y] = True
+def vline_down(map_: NDArray[np.bool_], x: int, y: int) -> None:
+ while y < SAMPLE_SCREEN_HEIGHT and not map_[y, x]:
+ map_[y, x] = True
y += 1
+
# draw a horizontal line
-def hline(m, x1, y, x2):
+def hline(map_: NDArray[np.bool_], x1: int, y: int, x2: int) -> None:
if x1 > x2:
x1, x2 = x2, x1
for x in range(x1, x2 + 1):
- m[x][y] = True
+ map_[y, x] = True
+
# draw a horizontal line left until we reach an empty space
-def hline_left(m, x, y):
- while x >= 0 and not m[x][y]:
- m[x][y] = True
+def hline_left(map_: NDArray[np.bool_], x: int, y: int) -> None:
+ while x >= 0 and not map_[y, x]:
+ map_[y, x] = True
x -= 1
+
# draw a horizontal line right until we reach an empty space
-def hline_right(m, x, y):
- while x < SAMPLE_SCREEN_WIDTH and not m[x][y]:
- m[x][y] = True
+def hline_right(map_: NDArray[np.bool_], x: int, y: int) -> None:
+ while x < SAMPLE_SCREEN_WIDTH and not map_[y, x]:
+ map_[y, x] = True
x += 1
+
# the class building the dungeon from the bsp nodes
-def traverse_node(node, *dat):
- global bsp_map
- if libtcod.bsp_is_leaf(node):
+def traverse_node(
+ bsp_map: NDArray[np.bool_],
+ node: tcod.bsp.BSP,
+ *,
+ bsp_min_room_size: int,
+ bsp_random_room: bool,
+ bsp_room_walls: bool,
+) -> None:
+ if not node.children:
# calculate the room size
- minx = node.x + 1
- maxx = node.x + node.w - 1
- miny = node.y + 1
- maxy = node.y + node.h - 1
- if not bsp_room_walls:
- if minx > 1:
- minx -= 1
- if miny > 1:
- miny -= 1
- if maxx == SAMPLE_SCREEN_WIDTH - 1:
- maxx -= 1
- if maxy == SAMPLE_SCREEN_HEIGHT - 1:
- maxy -= 1
+ if bsp_room_walls:
+ node.width -= 1
+ node.height -= 1
if bsp_random_room:
- minx = libtcod.random_get_int(None,
- minx, maxx - bsp_min_room_size + 1)
- miny = libtcod.random_get_int(None,
- miny, maxy - bsp_min_room_size + 1)
- maxx = libtcod.random_get_int(None,
- minx + bsp_min_room_size - 1, maxx)
- maxy = libtcod.random_get_int(None,
- miny + bsp_min_room_size - 1, maxy)
- # resize the node to fit the room
- node.x = minx
- node.y = miny
- node.w = maxx - minx + 1
- node.h = maxy - miny + 1
+ new_width = random.randint(min(node.width, bsp_min_room_size), node.width)
+ new_height = random.randint(min(node.height, bsp_min_room_size), node.height)
+ node.x += random.randint(0, node.width - new_width)
+ node.y += random.randint(0, node.height - new_height)
+ node.width, node.height = new_width, new_height
# dig the room
- for x in range(minx, maxx + 1):
- for y in range(miny, maxy + 1):
- bsp_map[x][y] = True
+ bsp_map[node.y : node.y + node.height, node.x : node.x + node.width] = True
else:
# resize the node to fit its sons
- left = libtcod.bsp_left(node)
- right = libtcod.bsp_right(node)
+ left, right = node.children
node.x = min(left.x, right.x)
node.y = min(left.y, right.y)
- node.w = max(left.x + left.w, right.x + right.w) - node.x
- node.h = max(left.y + left.h, right.y + right.h) - node.y
+ node.width = max(left.x + left.width, right.x + right.width) - node.x
+ node.height = max(left.y + left.height, right.y + right.height) - node.y
# create a corridor between the two lower nodes
if node.horizontal:
# vertical corridor
- if left.x + left.w - 1 < right.x or right.x + right.w - 1 < left.x:
+ if left.x + left.width - 1 < right.x or right.x + right.width - 1 < left.x:
# no overlapping zone. we need a Z shaped corridor
- x1 = libtcod.random_get_int(None, left.x, left.x + left.w - 1)
- x2 = libtcod.random_get_int(None,
- right.x, right.x + right.w - 1)
- y = libtcod.random_get_int(None, left.y + left.h, right.y)
+ x1 = random.randint(left.x, left.x + left.width - 1)
+ x2 = random.randint(right.x, right.x + right.width - 1)
+ y = random.randint(left.y + left.height, right.y)
vline_up(bsp_map, x1, y - 1)
hline(bsp_map, x1, y, x2)
vline_down(bsp_map, x2, y + 1)
else:
# straight vertical corridor
- minx = max(left.x, right.x)
- maxx = min(left.x + left.w - 1, right.x + right.w - 1)
- x = libtcod.random_get_int(None, minx, maxx)
+ min_x = max(left.x, right.x)
+ max_x = min(left.x + left.width - 1, right.x + right.width - 1)
+ x = random.randint(min_x, max_x)
vline_down(bsp_map, x, right.y)
vline_up(bsp_map, x, right.y - 1)
+ elif left.y + left.height - 1 < right.y or right.y + right.height - 1 < left.y: # horizontal corridor
+ # no overlapping zone. we need a Z shaped corridor
+ y1 = random.randint(left.y, left.y + left.height - 1)
+ y2 = random.randint(right.y, right.y + right.height - 1)
+ x = random.randint(left.x + left.width, right.x)
+ hline_left(bsp_map, x - 1, y1)
+ vline(bsp_map, x, y1, y2)
+ hline_right(bsp_map, x + 1, y2)
else:
- # horizontal corridor
- if left.y + left.h - 1 < right.y or right.y + right.h - 1 < left.y:
- # no overlapping zone. we need a Z shaped corridor
- y1 = libtcod.random_get_int(None, left.y, left.y + left.h - 1)
- y2 = libtcod.random_get_int(None,
- right.y, right.y + right.h - 1)
- x = libtcod.random_get_int(None, left.x + left.w, right.x)
- hline_left(bsp_map, x - 1, y1)
- vline(bsp_map, x, y1, y2)
- hline_right(bsp_map, x + 1, y2)
- else:
- # straight horizontal corridor
- miny = max(left.y, right.y)
- maxy = min(left.y + left.h - 1, right.y + right.h - 1)
- y = libtcod.random_get_int(None, miny, maxy)
- hline_left(bsp_map, right.x - 1, y)
- hline_right(bsp_map, right.x, y)
- return True
-
-bsp = None
-bsp_generate = True
-bsp_refresh = False
+ # straight horizontal corridor
+ min_y = max(left.y, right.y)
+ max_y = min(left.y + left.height - 1, right.y + right.height - 1)
+ y = random.randint(min_y, max_y)
+ hline_left(bsp_map, right.x - 1, y)
+ hline_right(bsp_map, right.x, y)
+
+
class BSPSample(Sample):
- def __init__(self):
- self.name = 'Bsp toolkit'
-
- def on_draw(self, delta_time):
- global bsp, bsp_generate, bsp_refresh, bsp_map
- global bsp_random_room, bsp_room_walls, bsp_depth, bsp_min_room_size
- if bsp_generate or bsp_refresh:
- # dungeon generation
- if bsp is None:
- # create the bsp
- bsp = libtcod.bsp_new_with_size(0, 0, SAMPLE_SCREEN_WIDTH,
- SAMPLE_SCREEN_HEIGHT)
- else:
- # restore the nodes size
- libtcod.bsp_resize(bsp, 0, 0, SAMPLE_SCREEN_WIDTH,
- SAMPLE_SCREEN_HEIGHT)
- bsp_map = list()
- for x in range(SAMPLE_SCREEN_WIDTH):
- bsp_map.append([False] * SAMPLE_SCREEN_HEIGHT)
- if bsp_generate:
- # build a new random bsp tree
- libtcod.bsp_remove_sons(bsp)
- if bsp_room_walls:
- libtcod.bsp_split_recursive(bsp, 0, bsp_depth,
- bsp_min_room_size + 1,
- bsp_min_room_size + 1, 1.5, 1.5)
- else:
- libtcod.bsp_split_recursive(bsp, 0, bsp_depth,
- bsp_min_room_size,
- bsp_min_room_size, 1.5, 1.5)
- # create the dungeon from the bsp
- libtcod.bsp_traverse_inverted_level_order(bsp, traverse_node)
- bsp_generate = False
- bsp_refresh = False
- libtcod.console_clear(sample_console)
- libtcod.console_set_default_foreground(sample_console, libtcod.white)
- rooms = 'OFF'
- if bsp_random_room:
- rooms = 'ON'
- libtcod.console_print(sample_console, 1, 1,
- "ENTER : rebuild bsp\n"
- "SPACE : rebuild dungeon\n"
- "+-: bsp depth %d\n"
- "*/: room size %d\n"
- "1 : random room size %s"
- % (bsp_depth, bsp_min_room_size, rooms))
- if bsp_random_room:
- walls = 'OFF'
- if bsp_room_walls:
- walls = 'ON'
- libtcod.console_print(sample_console, 1, 6,
- '2 : room walls %s' % walls)
+ name = "Bsp toolkit"
+
+ def __init__(self) -> None:
+ self.bsp = tcod.bsp.BSP(1, 1, SAMPLE_SCREEN_WIDTH - 1, SAMPLE_SCREEN_HEIGHT - 1)
+ self.bsp_map: NDArray[np.bool_] = np.zeros((SAMPLE_SCREEN_HEIGHT, SAMPLE_SCREEN_WIDTH), dtype=bool)
+
+ self.bsp_depth = 8
+ self.bsp_min_room_size = 4
+ self.bsp_random_room = False # a room fills a random part of the node or the maximum available space ?
+ self.bsp_room_walls = True # if true, there is always a wall on north & west side of a room
+
+ self.bsp_generate()
+
+ def bsp_generate(self) -> None:
+ self.bsp.children = ()
+ if self.bsp_room_walls:
+ self.bsp.split_recursive(
+ self.bsp_depth,
+ self.bsp_min_room_size + 1,
+ self.bsp_min_room_size + 1,
+ 1.5,
+ 1.5,
+ )
+ else:
+ self.bsp.split_recursive(self.bsp_depth, self.bsp_min_room_size, self.bsp_min_room_size, 1.5, 1.5)
+ self.bsp_refresh()
+
+ def bsp_refresh(self) -> None:
+ self.bsp_map[...] = False
+ for node in copy.deepcopy(self.bsp).inverted_level_order():
+ traverse_node(
+ self.bsp_map,
+ node,
+ bsp_min_room_size=self.bsp_min_room_size,
+ bsp_random_room=self.bsp_random_room,
+ bsp_room_walls=self.bsp_room_walls,
+ )
+
+ def on_draw(self) -> None:
+ sample_console.clear()
+ rooms = "OFF"
+ if self.bsp_random_room:
+ rooms = "ON"
+ sample_console.print(
+ 1,
+ 1,
+ "ENTER : rebuild bsp\n"
+ "SPACE : rebuild dungeon\n"
+ f"+-: bsp depth {self.bsp_depth}\n"
+ f"*/: room size {self.bsp_min_room_size}\n"
+ f"1 : random room size {rooms}",
+ fg=WHITE,
+ bg=None,
+ )
+ if self.bsp_random_room:
+ walls = "OFF"
+ if self.bsp_room_walls:
+ walls = "ON"
+ sample_console.print(1, 6, f"2 : room walls {walls}", fg=WHITE, bg=None)
# render the level
for y in range(SAMPLE_SCREEN_HEIGHT):
for x in range(SAMPLE_SCREEN_WIDTH):
- if not bsp_map[x][y]:
- libtcod.console_set_char_background(
- sample_console, x, y, DARK_WALL, libtcod.BKGND_SET)
- else:
- libtcod.console_set_char_background(
- sample_console, x, y, DARK_GROUND, libtcod.BKGND_SET)
-
- def on_key(self, key):
- global bsp, bsp_generate, bsp_refresh, bsp_map
- global bsp_random_room, bsp_room_walls, bsp_depth, bsp_min_room_size
- if key.vk in (libtcod.KEY_ENTER, libtcod.KEY_KPENTER):
- bsp_generate = True
- elif key.c == ord(' '):
- bsp_refresh = True
- elif key.c == ord('='):
- bsp_depth += 1
- bsp_generate = True
- elif key.c == ord('-') and bsp_depth > 1:
- bsp_depth -= 1
- bsp_generate = True
- elif key.c == ord('*'):
- bsp_min_room_size += 1
- bsp_generate = True
- elif key.c == ord('/') and bsp_min_room_size > 2:
- bsp_min_room_size -= 1
- bsp_generate = True
- elif key.c == ord('1') or key.vk in (libtcod.KEY_1, libtcod.KEY_KP1):
- bsp_random_room = not bsp_random_room
- if not bsp_random_room:
- bsp_room_walls = True
- bsp_refresh = True
- elif key.c == ord('2') or key.vk in (libtcod.KEY_2, libtcod.KEY_KP2):
- bsp_room_walls = not bsp_room_walls
- bsp_refresh = True
+ color = DARK_GROUND if self.bsp_map[y, x] else DARK_WALL
+ libtcodpy.console_set_char_background(sample_console, x, y, color, libtcodpy.BKGND_SET)
+
+ def on_event(self, event: tcod.event.Event) -> None:
+ match event:
+ case tcod.event.KeyDown(sym=KeySym.RETURN | KeySym.KP_ENTER):
+ self.bsp_generate()
+ case tcod.event.KeyDown(sym=KeySym.SPACE):
+ self.bsp_refresh()
+ case tcod.event.KeyDown(sym=KeySym.EQUALS | KeySym.KP_PLUS):
+ self.bsp_depth += 1
+ self.bsp_generate()
+ case tcod.event.KeyDown(sym=KeySym.MINUS | KeySym.KP_MINUS):
+ self.bsp_depth = max(1, self.bsp_depth - 1)
+ self.bsp_generate()
+ case tcod.event.KeyDown(sym=KeySym.N8 | KeySym.KP_MULTIPLY):
+ self.bsp_min_room_size += 1
+ self.bsp_generate()
+ case tcod.event.KeyDown(sym=KeySym.SLASH | KeySym.KP_DIVIDE):
+ self.bsp_min_room_size = max(2, self.bsp_min_room_size - 1)
+ self.bsp_generate()
+ case tcod.event.KeyDown(sym=KeySym.N1 | KeySym.KP_1):
+ self.bsp_random_room = not self.bsp_random_room
+ if not self.bsp_random_room:
+ self.bsp_room_walls = True
+ self.bsp_refresh()
+ case tcod.event.KeyDown(sym=KeySym.N2 | KeySym.KP_2):
+ self.bsp_room_walls = not self.bsp_room_walls
+ self.bsp_refresh()
+ case _:
+ super().on_event(event)
-#############################################
-# image sample
-#############################################
-img_blue = libtcod.Color(0, 0, 255)
-img_green = libtcod.Color(0, 255, 0)
-class ImageSample(Sample):
- def __init__(self):
- self.name = 'Image toolkit'
- self.img = libtcod.image_load('data/img/skull.png')
- self.img.set_key_color(libtcod.black)
- self.circle = libtcod.image_load('data/img/circle.png')
+class ImageSample(Sample):
+ name = "Image toolkit"
- def on_enter(self):
- libtcod.sys_set_fps(0)
+ def __init__(self) -> None:
+ self.img = tcod.image.Image.from_file(DATA_DIR / "img/skull.png")
+ self.img.set_key_color(BLACK)
+ self.circle = tcod.image.Image.from_file(DATA_DIR / "img/circle.png")
- def on_draw(self, delta_time):
- sample_console.default_bg = libtcod.black
+ def on_draw(self) -> None:
sample_console.clear()
- x = (sample_console.width / 2
- + math.cos(libtcod.sys_elapsed_seconds()) * 10.0)
- y = float(sample_console.height / 2)
- scalex = (0.2 + 1.8
- * (1.0 + math.cos(libtcod.sys_elapsed_seconds() / 2)) / 2.0)
+ x = sample_console.width / 2 + math.cos(time.time()) * 10.0
+ y = sample_console.height / 2
+ scalex = 0.2 + 1.8 * (1.0 + math.cos(time.time() / 2)) / 2.0
scaley = scalex
- angle = libtcod.sys_elapsed_seconds()
- elapsed = libtcod.sys_elapsed_milli() // 2000
- if elapsed & 1 != 0:
+ angle = _get_elapsed_time()
+ if int(time.time()) % 2:
# split the color channels of circle.png
# the red channel
- sample_console.default_bg = libtcod.red
-
- sample_console.rect(0, 3, 15, 15, False, libtcod.BKGND_SET)
- self.circle.blit_rect(sample_console, 0, 3, -1, -1,
- libtcod.BKGND_MULTIPLY)
+ sample_console.draw_rect(0, 3, 15, 15, 0, None, (255, 0, 0))
+ self.circle.blit_rect(sample_console, 0, 3, -1, -1, libtcodpy.BKGND_MULTIPLY)
# the green channel
- sample_console.default_bg = img_green
- sample_console.rect(15, 3, 15, 15, False, libtcod.BKGND_SET)
- self.circle.blit_rect(sample_console,
- 15, 3, -1, -1, libtcod.BKGND_MULTIPLY)
+ sample_console.draw_rect(15, 3, 15, 15, 0, None, (0, 255, 0))
+ self.circle.blit_rect(sample_console, 15, 3, -1, -1, libtcodpy.BKGND_MULTIPLY)
# the blue channel
- sample_console.default_bg = img_blue
- sample_console.rect(30, 3, 15, 15, False, libtcod.BKGND_SET)
- self.circle.blit_rect(sample_console,
- 30, 3, -1, -1, libtcod.BKGND_MULTIPLY)
+ sample_console.draw_rect(30, 3, 15, 15, 0, None, (0, 0, 255))
+ self.circle.blit_rect(sample_console, 30, 3, -1, -1, libtcodpy.BKGND_MULTIPLY)
else:
# render circle.png with normal blitting
- self.circle.blit_rect(sample_console,
- 0, 3, -1, -1, libtcod.BKGND_SET)
- self.circle.blit_rect(sample_console,
- 15, 3, -1, -1, libtcod.BKGND_SET)
- self.circle.blit_rect(sample_console,
- 30, 3, -1, -1, libtcod.BKGND_SET)
- self.img.blit(sample_console, x, y,
- libtcod.BKGND_SET, scalex, scaley, angle)
+ self.circle.blit_rect(sample_console, 0, 3, -1, -1, libtcodpy.BKGND_SET)
+ self.circle.blit_rect(sample_console, 15, 3, -1, -1, libtcodpy.BKGND_SET)
+ self.circle.blit_rect(sample_console, 30, 3, -1, -1, libtcodpy.BKGND_SET)
+ self.img.blit(sample_console, x, y, libtcodpy.BKGND_SET, scalex, scaley, angle)
-#############################################
-# mouse sample
-#############################################
-butstatus = ('OFF', 'ON')
class MouseSample(Sample):
- def __init__(self):
- self.name = 'Mouse support'
-
- self.lbut = self.mbut = self.rbut = 0
-
- def on_enter(self):
- libtcod.console_set_default_background(sample_console, libtcod.grey)
- libtcod.console_set_default_foreground(sample_console,
- libtcod.light_yellow)
- libtcod.mouse_move(320, 200)
- libtcod.mouse_show_cursor(True)
- libtcod.sys_set_fps(60)
-
- def on_mouse(self, mouse):
- libtcod.console_clear(sample_console)
- if mouse.lbutton_pressed:
- self.lbut = not self.lbut
- if mouse.rbutton_pressed:
- self.rbut = not self.rbut
- if mouse.mbutton_pressed:
- self.mbut = not self.mbut
- wheel = ""
- if mouse.wheel_up:
- wheel = "UP"
- elif mouse.wheel_down:
- wheel = "DOWN"
- sample_console.print_(
- 1, 1,
- "Mouse position : %4dx%4d\n"
- "Mouse cell : %4dx%4d\n"
- "Mouse movement : %4dx%4d\n"
- "Left button : %s (toggle %s)\n"
- "Right button : %s (toggle %s)\n"
- "Middle button : %s (toggle %s)\n"
- "Wheel : %s"
- % (mouse.x, mouse.y,
- mouse.cx, mouse.cy,
- mouse.dx, mouse.dy,
- butstatus[mouse.lbutton], butstatus[self.lbut],
- butstatus[mouse.rbutton], butstatus[self.rbut],
- butstatus[mouse.mbutton], butstatus[self.mbut],
- wheel,
- )
- )
- sample_console.print_(1, 10, "1 : Hide cursor\n2 : Show cursor")
+ name = "Mouse support"
+
+ def __init__(self) -> None:
+ self.motion = tcod.event.MouseMotion()
+ self.log: list[str] = []
+
+ def on_enter(self) -> None:
+ sdl_window = context.sdl_window
+ if sdl_window:
+ tcod.sdl.mouse.warp_in_window(sdl_window, 320, 200)
+ tcod.sdl.mouse.show(True)
+
+ def on_draw(self) -> None:
+ sample_console.clear(bg=GREY)
+ mouse_state = tcod.event.get_mouse_state()
+ sample_console.print(
+ 1,
+ 1,
+ f"Pixel position : {mouse_state.position.x:4.0f}x{mouse_state.position.y:4.0f}\n"
+ f"Tile position : {self.motion.tile.x:4d}x{self.motion.tile.y:4d}\n"
+ f"Tile movement : {self.motion.tile_motion.x:4d}x{self.motion.tile_motion.y:4d}\n"
+ f"Left button : {'ON' if mouse_state.state & tcod.event.MouseButtonMask.LEFT else 'OFF'}\n"
+ f"Middle button : {'ON' if mouse_state.state & tcod.event.MouseButtonMask.MIDDLE else 'OFF'}\n"
+ f"Right button : {'ON' if mouse_state.state & tcod.event.MouseButtonMask.RIGHT else 'OFF'}\n"
+ f"X1 button : {'ON' if mouse_state.state & tcod.event.MouseButtonMask.X1 else 'OFF'}\n"
+ f"X2 button : {'ON' if mouse_state.state & tcod.event.MouseButtonMask.X2 else 'OFF'}\n",
+ fg=LIGHT_YELLOW,
+ bg=None,
+ )
+ sample_console.print(
+ 1,
+ 10,
+ "1 : Hide cursor\n2 : Show cursor",
+ fg=LIGHT_YELLOW,
+ bg=None,
+ )
- def on_key(self, key):
- if key.c == ord('1'):
- libtcod.mouse_show_cursor(False)
- elif key.c == ord('2'):
- libtcod.mouse_show_cursor(True)
+ def on_event(self, event: tcod.event.Event) -> None:
+ match event:
+ case tcod.event.MouseMotion():
+ self.motion = event
+ case tcod.event.KeyDown(sym=KeySym.N1):
+ tcod.sdl.mouse.show(visible=False)
+ case tcod.event.KeyDown(sym=KeySym.N2):
+ tcod.sdl.mouse.show(visible=True)
+ case _:
+ super().on_event(event)
-#############################################
-# name generator sample
-#############################################
class NameGeneratorSample(Sample):
- def __init__(self):
- self.name = 'Name generator'
+ name = "Name generator"
- self.curset = 0
- self.nbsets = 0
+ def __init__(self) -> None:
+ self.current_set = 0
self.delay = 0.0
- self.names = []
- self.sets = None
-
- def on_enter(self):
- libtcod.sys_set_fps(60)
+ self.names: list[str] = []
+ self.sets: list[str] = []
- def on_draw(self, delta_time):
- if self.nbsets == 0:
+ def on_draw(self) -> None:
+ if not self.sets:
# parse all *.cfg files in data/namegen
- for file in os.listdir(b'data/namegen'):
- if file.find(b'.cfg') > 0:
- libtcod.namegen_parse(
- os.path.join(b'data', b'namegen', file))
+ for file in (DATA_DIR / "namegen").iterdir():
+ if file.suffix == ".cfg":
+ libtcodpy.namegen_parse(file)
# get the sets list
- self.sets = libtcod.namegen_get_sets()
+ self.sets = libtcodpy.namegen_get_sets()
print(self.sets)
- self.nbsets = len(self.sets)
while len(self.names) > 15:
self.names.pop(0)
- libtcod.console_clear(sample_console)
- libtcod.console_set_default_foreground(sample_console, libtcod.white)
- libtcod.console_print(sample_console, 1, 1,
- "%s\n\n+ : next generator\n- : prev generator"
- % self.sets[self.curset])
+ sample_console.clear(bg=GREY)
+ sample_console.print(
+ 1,
+ 1,
+ f"{self.sets[self.current_set]}\n\n+ : next generator\n- : prev generator",
+ fg=WHITE,
+ bg=None,
+ )
for i in range(len(self.names)):
- libtcod.console_print_ex(
- sample_console, SAMPLE_SCREEN_WIDTH-2, 2+i,
- libtcod.BKGND_NONE, libtcod.RIGHT, self.names[i])
- self.delay += libtcod.sys_get_last_frame_length()
+ sample_console.print(
+ SAMPLE_SCREEN_WIDTH - 1,
+ 2 + i,
+ self.names[i],
+ fg=WHITE,
+ bg=None,
+ alignment=libtcodpy.RIGHT,
+ )
+ self.delay += frame_length[-1]
if self.delay > 0.5:
self.delay -= 0.5
- self.names.append(libtcod.namegen_generate(self.sets[self.curset]))
-
- def on_key(self, key):
- if key.c == ord('='):
- self.curset += 1
- if self.curset == self.nbsets:
- self.curset = 0
- self.names.append("======")
- elif key.c == ord('-'):
- self.curset -= 1
- if self.curset < 0:
- self.curset = self.nbsets-1
- self.names.append("======")
+ self.names.append(libtcodpy.namegen_generate(self.sets[self.current_set]))
+
+ def on_event(self, event: tcod.event.Event) -> None:
+ match event:
+ case tcod.event.KeyDown(sym=KeySym.EQUALS):
+ self.current_set += 1
+ self.names.append("======")
+ case tcod.event.KeyDown(sym=KeySym.MINUS):
+ self.current_set -= 1
+ self.names.append("======")
+ case _:
+ super().on_event(event)
+ self.current_set %= len(self.sets)
+
#############################################
# python fast render sample
#############################################
-numpy_available = True
-
-use_numpy = numpy_available #default option
SCREEN_W = SAMPLE_SCREEN_WIDTH
SCREEN_H = SAMPLE_SCREEN_HEIGHT
HALF_W = SCREEN_W // 2
HALF_H = SCREEN_H // 2
-RES_U = 80 #texture resolution
+RES_U = 80 # texture resolution
RES_V = 80
-TEX_STRETCH = 5 #texture stretching with tunnel depth
+TEX_STRETCH = 5 # texture stretching with tunnel depth
SPEED = 15
-LIGHT_BRIGHTNESS = 3.5 #brightness multiplier for all lights (changes their radius)
-LIGHTS_CHANCE = 0.07 #chance of a light appearing
+LIGHT_BRIGHTNESS = 3.5 # brightness multiplier for all lights (changes their radius)
+LIGHTS_CHANCE = 0.07 # chance of a light appearing
MAX_LIGHTS = 6
MIN_LIGHT_STRENGTH = 0.2
-LIGHT_UPDATE = 0.05 #how much the ambient light changes to reflect current light sources
-AMBIENT_LIGHT = 0.8 #brightness of tunnel texture
-
-#the coordinates of all tiles in the screen, as numpy arrays. example: (4x3 pixels screen)
-#xc = [[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]]
-#yc = [[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3]]
-if numpy_available:
- (xc, yc) = np.meshgrid(range(SCREEN_W), range(SCREEN_H))
- #translate coordinates of all pixels to center
- xc = xc - HALF_W
- yc = yc - HALF_H
-
-noise2d = libtcod.noise_new(2, 0.5, 2.0)
-if numpy_available: #the texture starts empty
- texture = np.zeros((RES_U, RES_V))
+LIGHT_UPDATE = 0.05 # how much the ambient light changes to reflect current light sources
+AMBIENT_LIGHT = 0.8 # brightness of tunnel texture
+# the coordinates of all tiles in the screen, as numpy arrays.
+# example: (4x3 pixels screen)
+# xc = [[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]] # noqa: ERA001
+# yc = [[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3]] # noqa: ERA001
+(xc, yc) = np.meshgrid(range(SCREEN_W), range(SCREEN_H))
+# translate coordinates of all pixels to center
+xc = xc - HALF_W
+yc = yc - HALF_H
+
+
+@dataclass(frozen=False, slots=True)
class Light:
- def __init__(self, x, y, z, r, g, b, strength):
- self.x, self.y, self.z = x, y, z #pos.
- self.r, self.g, self.b = r, g, b #color
- self.strength = strength #between 0 and 1, defines brightness
+ """Lighting effect entity."""
-class FastRenderSample(Sample):
- def __init__(self):
- self.name = 'Python fast render'
-
- def on_enter(self):
- global frac_t, abs_t, lights, tex_r, tex_g, tex_b
- libtcod.sys_set_fps(0)
- libtcod.console_clear(sample_console) #render status message
- libtcod.console_set_default_foreground(sample_console, libtcod.white)
- libtcod.console_print(sample_console, 1, SCREEN_H - 3,
- "Renderer: NumPy")
-
- frac_t = RES_V - 1 #time is represented in number of pixels of the texture, start later in time to initialize texture
- abs_t = RES_V - 1
- lights = [] #lights list, and current color of the tunnel texture
- tex_r, tex_g, tex_b = 0, 0, 0
-
- def on_draw(self, delta_time):
- global use_numpy, frac_t, abs_t, lights, tex_r, tex_g, tex_b, xc, yc, texture, texture2, brightness2, R2, G2, B2
-
- time_delta = libtcod.sys_get_last_frame_length() * SPEED #advance time
- frac_t += time_delta #increase fractional (always < 1.0) time
- abs_t += time_delta #increase absolute elapsed time
- int_t = int(frac_t) #integer time units that passed this frame (number of texture pixels to advance)
- frac_t -= int_t #keep this < 1.0
-
- #change texture color according to presence of lights (basically, sum them
- #to get ambient light and smoothly change the current color into that)
- ambient_r = (AMBIENT_LIGHT
- * sum(light.r * light.strength for light in lights))
- ambient_g = (AMBIENT_LIGHT
- * sum(light.g * light.strength for light in lights))
- ambient_b = (AMBIENT_LIGHT
- * sum(light.b * light.strength for light in lights))
- alpha = LIGHT_UPDATE * time_delta
- tex_r = tex_r * (1 - alpha) + ambient_r * alpha
- tex_g = tex_g * (1 - alpha) + ambient_g * alpha
- tex_b = tex_b * (1 - alpha) + ambient_b * alpha
+ x: float # pos
+ y: float
+ z: float
+ r: int # color
+ g: int
+ b: int
+ strength: float # between 0 and 1, defines brightness
- if int_t >= 1: #roll texture (ie, advance in tunnel) according to int_t
- int_t = int_t % RES_V #can't roll more than the texture's size (can happen when time_delta is large)
- int_abs_t = int(abs_t) #new pixels are based on absolute elapsed time
- texture = np.roll(texture, -int_t, 1)
- #replace new stretch of texture with new values
+class FastRenderSample(Sample):
+ name = "Python fast render"
+
+ def __init__(self) -> None:
+ self.texture = np.zeros((RES_U, RES_V))
+ self.noise2d = tcod.noise.Noise(2, hurst=0.5, lacunarity=2.0)
+
+ def on_enter(self) -> None:
+ sample_console.clear() # render status message
+ sample_console.print(1, SCREEN_H - 3, "Renderer: NumPy", fg=WHITE, bg=None)
+
+ # time is represented in number of pixels of the texture, start later
+ # in time to initialize texture
+ self.frac_t: float = RES_V - 1
+ self.abs_t: float = RES_V - 1
+ # light and current color of the tunnel texture
+ self.lights: list[Light] = []
+ self.tex_r = 0.0
+ self.tex_g = 0.0
+ self.tex_b = 0.0
+
+ def on_draw(self) -> None:
+ time_delta = frame_length[-1] * SPEED # advance time
+ self.frac_t += time_delta # increase fractional (always < 1.0) time
+ self.abs_t += time_delta # increase absolute elapsed time
+ # integer time units that passed this frame (number of texture pixels
+ # to advance)
+ int_t = int(self.frac_t)
+ self.frac_t %= 1.0 # keep this < 1.0
+
+ # change texture color according to presence of lights (basically, sum
+ # them to get ambient light and smoothly change the current color into
+ # that)
+ ambient_r = AMBIENT_LIGHT * sum(light.r * light.strength for light in self.lights)
+ ambient_g = AMBIENT_LIGHT * sum(light.g * light.strength for light in self.lights)
+ ambient_b = AMBIENT_LIGHT * sum(light.b * light.strength for light in self.lights)
+ alpha = LIGHT_UPDATE * time_delta
+ self.tex_r = self.tex_r * (1 - alpha) + ambient_r * alpha
+ self.tex_g = self.tex_g * (1 - alpha) + ambient_g * alpha
+ self.tex_b = self.tex_b * (1 - alpha) + ambient_b * alpha
+
+ if int_t >= 1:
+ # roll texture (ie, advance in tunnel) according to int_t
+ # can't roll more than the texture's size (can happen when
+ # time_delta is large)
+ int_t = int_t % RES_V
+ # new pixels are based on absolute elapsed time
+ int_abs_t = int(self.abs_t)
+
+ self.texture = np.roll(self.texture, -int_t, 1)
+ # replace new stretch of texture with new values
for v in range(RES_V - int_t, RES_V):
- for u in range(0, RES_U):
+ for u in range(RES_U):
tex_v = (v + int_abs_t) / float(RES_V)
- texture[u, v] = (
- libtcod.noise_get_fbm(
- noise2d, [u/float(RES_U), tex_v], 32.0)
- + libtcod.noise_get_fbm(
- noise2d, [1 - u/float(RES_U), tex_v], 32.0)
- )
+ self.texture[u, v] = libtcodpy.noise_get_fbm(
+ self.noise2d, [u / float(RES_U), tex_v], 32.0
+ ) + libtcodpy.noise_get_fbm(self.noise2d, [1 - u / float(RES_U), tex_v], 32.0)
# squared distance from center,
# clipped to sensible minimum and maximum values
- sqr_dist = xc ** 2 + yc ** 2
- sqr_dist = sqr_dist.clip(1.0 / RES_V, RES_V ** 2)
-
- #one coordinate into the texture, represents depth in the tunnel
- v = TEX_STRETCH * float(RES_V) / sqr_dist + frac_t
- v = v.clip(0, RES_V - 1)
-
- #another coordinate, represents rotation around the tunnel
- u = np.mod(RES_U * (np.arctan2(yc, xc) / (2 * np.pi) + 0.5), RES_U)
-
- #retrieve corresponding pixels from texture
- brightness = texture[u.astype(int), v.astype(int)] / 4.0 + 0.5
-
- #use the brightness map to compose the final color of the tunnel
- R = brightness * tex_r
- G = brightness * tex_g
- B = brightness * tex_b
-
- #create new light source
- if (libtcod.random_get_float(0, 0, 1) <= time_delta * LIGHTS_CHANCE and
- len(lights) < MAX_LIGHTS):
- x = libtcod.random_get_float(0, -0.5, 0.5)
- y = libtcod.random_get_float(0, -0.5, 0.5)
- strength = libtcod.random_get_float(0, MIN_LIGHT_STRENGTH, 1.0)
-
- color = libtcod.Color(0, 0, 0) #create bright colors with random hue
- hue = libtcod.random_get_float(0, 0, 360)
- libtcod.color_set_hsv(color, hue, 0.5, strength)
- lights.append(Light(x, y, TEX_STRETCH,
- color.r, color.g, color.b, strength))
-
- #eliminate lights that are going to be out of view
- lights = [light for light in lights
- if light.z - time_delta > 1.0 / RES_V]
-
- for light in lights: #render lights
- #move light's Z coordinate with time, then project its XYZ coordinates to screen-space
- light.z -= float(time_delta) / TEX_STRETCH
+ sqr_dist = xc**2 + yc**2
+ sqr_dist = sqr_dist.clip(1.0 / RES_V, RES_V**2)
+
+ # one coordinate into the texture, represents depth in the tunnel
+ vv = TEX_STRETCH * float(RES_V) / sqr_dist + self.frac_t
+ vv = vv.clip(0, RES_V - 1)
+
+ # another coordinate, represents rotation around the tunnel
+ uu = np.mod(RES_U * (np.arctan2(yc, xc) / (2 * np.pi) + 0.5), RES_U)
+
+ # retrieve corresponding pixels from texture
+ brightness = self.texture[uu.astype(int), vv.astype(int)] / 4.0 + 0.5
+
+ # use the brightness map to compose the final color of the tunnel
+ rr = brightness * self.tex_r
+ gg = brightness * self.tex_g
+ bb = brightness * self.tex_b
+
+ # create new light source
+ if random.random() <= time_delta * LIGHTS_CHANCE and len(self.lights) < MAX_LIGHTS:
+ x = random.uniform(-0.5, 0.5)
+ y = random.uniform(-0.5, 0.5)
+ strength = random.uniform(MIN_LIGHT_STRENGTH, 1.0)
+
+ color = libtcodpy.Color(0, 0, 0) # create bright colors with random hue
+ hue = random.uniform(0, 360)
+ libtcodpy.color_set_hsv(color, hue, 0.5, strength)
+ self.lights.append(Light(x, y, TEX_STRETCH, color.r, color.g, color.b, strength))
+
+ # eliminate lights that are going to be out of view
+ self.lights = [light for light in self.lights if light.z - time_delta > 1.0 / RES_V]
+
+ for light in self.lights: # render lights
+ # move light's Z coordinate with time, then project its XYZ
+ # coordinates to screen-space
+ light.z -= time_delta / TEX_STRETCH
xl = light.x / light.z * SCREEN_H
yl = light.y / light.z * SCREEN_H
- #calculate brightness of light according to distance from viewer and strength,
- #then calculate brightness of each pixel with inverse square distance law
- light_brightness = (LIGHT_BRIGHTNESS * light.strength
- * (1.0 - light.z / TEX_STRETCH))
+ # calculate brightness of light according to distance from viewer
+ # and strength, then calculate brightness of each pixel with
+ # inverse square distance law
+ light_brightness = LIGHT_BRIGHTNESS * light.strength * (1.0 - light.z / TEX_STRETCH)
brightness = light_brightness / ((xc - xl) ** 2 + (yc - yl) ** 2)
- #make all pixels shine around this light
- R += brightness * light.r
- G += brightness * light.g
- B += brightness * light.b
+ # make all pixels shine around this light
+ rr += brightness * light.r
+ gg += brightness * light.g
+ bb += brightness * light.b
- #truncate values
- R = R.clip(0, 255)
- G = G.clip(0, 255)
- B = B.clip(0, 255)
+ # truncate values
+ rr = rr.clip(0, 255)
+ gg = gg.clip(0, 255)
+ bb = bb.clip(0, 255)
+
+ # fill the screen with these background colors
+ sample_console.bg.transpose(2, 0, 1)[...] = (rr, gg, bb)
- #fill the screen with these background colors
- sample_console.bg.transpose()[:] = [R.T, G.T, B.T]
#############################################
# main loop
#############################################
RENDERER_KEYS = {
- libtcod.KEY_F1: libtcod.RENDERER_GLSL,
- libtcod.KEY_F2: libtcod.RENDERER_OPENGL,
- libtcod.KEY_F3: libtcod.RENDERER_SDL,
- libtcod.KEY_F4: libtcod.RENDERER_SDL2,
- libtcod.KEY_F5: libtcod.RENDERER_OPENGL2,
- }
-
-RENDERER_NAMES = ('F1 GLSL ', 'F2 OPENGL ', 'F3 SDL ', 'F4 SDL2 ',
- 'F5 OPENGL2')
+ tcod.event.KeySym.F1: libtcodpy.RENDERER_GLSL,
+ tcod.event.KeySym.F2: libtcodpy.RENDERER_OPENGL,
+ tcod.event.KeySym.F3: libtcodpy.RENDERER_SDL,
+ tcod.event.KeySym.F4: libtcodpy.RENDERER_SDL2,
+ tcod.event.KeySym.F5: libtcodpy.RENDERER_OPENGL2,
+}
+
+RENDERER_NAMES = (
+ "F1 GLSL ",
+ "F2 OPENGL ",
+ "F3 SDL ",
+ "F4 SDL2 ",
+ "F5 OPENGL2",
+)
SAMPLES = (
TrueColorSample(),
@@ -1370,115 +1302,189 @@ def on_draw(self, delta_time):
ImageSample(),
MouseSample(),
NameGeneratorSample(),
- FastRenderSample()
+ FastRenderSample(),
+)
+
+
+def init_context(renderer: int) -> None:
+ """Setup or reset a global context with common parameters set.
+
+ This function exists to more easily switch between renderers.
+ """
+ global context, console_render, sample_minimap
+ if "context" in globals():
+ context.close()
+ libtcod_version = (
+ f"{tcod.cffi.lib.TCOD_MAJOR_VERSION}.{tcod.cffi.lib.TCOD_MINOR_VERSION}.{tcod.cffi.lib.TCOD_PATCHLEVEL}"
)
+ context = tcod.context.new(
+ columns=root_console.width,
+ rows=root_console.height,
+ title=f"""python-tcod samples (python-tcod {importlib.metadata.version("tcod")}, libtcod {libtcod_version})""",
+ vsync=False, # VSync turned off since this is for benchmarking.
+ tileset=tileset,
+ )
+ if context.sdl_renderer: # If this context supports SDL rendering.
+ # Start by setting the logical size so that window resizing doesn't break anything.
+ context.sdl_renderer.set_logical_presentation(
+ resolution=(tileset.tile_width * root_console.width, tileset.tile_height * root_console.height),
+ mode=tcod.sdl.render.LogicalPresentation.STRETCH,
+ )
+ assert context.sdl_atlas
+ # Generate the console renderer and minimap.
+ console_render = tcod.render.SDLConsoleRender(context.sdl_atlas)
+ sample_minimap = context.sdl_renderer.new_texture(
+ SAMPLE_SCREEN_WIDTH,
+ SAMPLE_SCREEN_HEIGHT,
+ format=tcod.cffi.lib.SDL_PIXELFORMAT_RGB24,
+ access=tcod.sdl.render.TextureAccess.STREAMING, # Updated every frame.
+ )
+
+
+def main() -> None:
+ global tileset
+ tileset = tcod.tileset.load_tilesheet(FONT, 32, 8, tcod.tileset.CHARMAP_TCOD)
+ init_context(libtcodpy.RENDERER_SDL2)
+ try:
+ SAMPLES[cur_sample].on_enter()
-cur_sample = 0
+ while True:
+ redraw_display()
+ handle_time()
+ handle_events()
+ finally:
+ # Normally context would be used in a with block and closed
+ # automatically. but since this context might be switched to one with a
+ # different renderer it is closed manually here.
+ context.close()
-def main():
- global cur_sample
- credits_end = False
- SAMPLES[cur_sample].on_enter()
+
+def redraw_display() -> None:
+ """Full clear-draw-present of the screen."""
+ root_console.clear()
draw_samples_menu()
draw_renderer_menu()
- while not libtcod.console_is_window_closed():
- root_console.default_fg = (255, 255, 255)
- root_console.default_bg = (0, 0, 0)
- root_console.clear()
- draw_samples_menu()
- draw_renderer_menu()
- # render credits
- if not credits_end:
- credits_end = libtcod.console_credits_render(60, 43, 0)
-
- # render the sample
- SAMPLES[cur_sample].on_draw(libtcod.sys_get_last_frame_length())
- sample_console.blit(0, 0, sample_console.width, sample_console.height,
- root_console, SAMPLE_SCREEN_X, SAMPLE_SCREEN_Y)
- draw_stats()
- handle_events()
- libtcod.console_flush()
-
-def handle_events():
- global cur_sample
- key = libtcod.Key()
- mouse = libtcod.Mouse()
- EVENT_MASK = libtcod.EVENT_MOUSE | libtcod.EVENT_KEY_PRESS
- while libtcod.sys_check_for_event(EVENT_MASK, key, mouse):
- SAMPLES[cur_sample].on_mouse(mouse)
- SAMPLES[cur_sample].on_key(key)
- # key handler
- if key.vk == libtcod.KEY_DOWN:
- cur_sample = (cur_sample + 1) % len(SAMPLES)
- SAMPLES[cur_sample].on_enter()
- draw_samples_menu()
- elif key.vk == libtcod.KEY_UP:
- cur_sample = (cur_sample - 1) % len(SAMPLES)
- SAMPLES[cur_sample].on_enter()
- draw_samples_menu()
- elif key.vk == libtcod.KEY_ENTER and key.lalt:
- libtcod.console_set_fullscreen(not libtcod.console_is_fullscreen())
- elif key.vk == libtcod.KEY_PRINTSCREEN or key.c == 'p':
- print("screenshot")
- if key.lalt:
- libtcod.console_save_apf(None, "samples.apf")
- print("apf")
- else:
- libtcod.sys_save_screenshot()
- print("png")
- elif key.vk == libtcod.KEY_ESCAPE:
- raise SystemExit()
- elif key.vk in RENDERER_KEYS:
- libtcod.sys_set_renderer(RENDERER_KEYS[key.vk])
- draw_renderer_menu()
-
-def draw_samples_menu():
+ # render the sample
+ SAMPLES[cur_sample].on_draw()
+ sample_console.blit(root_console, SAMPLE_SCREEN_X, SAMPLE_SCREEN_Y)
+ draw_stats()
+ if 0 <= mouse_tile_xy[0] < root_console.width and 0 <= mouse_tile_xy[1] < root_console.height:
+ root_console.rgb[["fg", "bg"]].T[mouse_tile_xy] = (0, 0, 0), (255, 255, 255) # Highlight mouse tile
+ if context.sdl_renderer:
+ # Clear the screen to ensure no garbage data outside of the logical area is displayed
+ context.sdl_renderer.draw_color = (0, 0, 0, 255)
+ context.sdl_renderer.clear()
+ # SDL renderer support, upload the sample console background to a minimap texture.
+ sample_minimap.update(sample_console.rgb["bg"])
+ # Render the root_console normally, this is the drawing step of context.present without presenting.
+ context.sdl_renderer.copy(console_render.render(root_console))
+ # Render the minimap to the screen.
+ context.sdl_renderer.copy(
+ sample_minimap,
+ dest=(
+ tileset.tile_width * 24,
+ tileset.tile_height * 36,
+ SAMPLE_SCREEN_WIDTH * 3,
+ SAMPLE_SCREEN_HEIGHT * 3,
+ ),
+ )
+ context.sdl_renderer.present()
+ else: # No SDL renderer, just use plain context rendering.
+ context.present(root_console)
+
+
+def handle_time() -> None:
+ if len(frame_times) > 100:
+ frame_times.pop(0)
+ frame_length.pop(0)
+ frame_times.append(time.perf_counter())
+ frame_length.append(frame_times[-1] - frame_times[-2])
+
+
+mouse_tile_xy = (-1, -1)
+"""Last known mouse tile position."""
+
+
+def handle_events() -> None:
+ global mouse_tile_xy
+ for event in tcod.event.get():
+ tile_event = tcod.event.convert_coordinates_from_window(event, context, root_console)
+ SAMPLES[cur_sample].on_event(tile_event)
+ match tile_event:
+ case tcod.event.MouseMotion(integer_position=(x, y)):
+ mouse_tile_xy = x, y
+ case tcod.event.WindowEvent(type="WindowLeave"):
+ mouse_tile_xy = -1, -1
+
+
+def draw_samples_menu() -> None:
for i, sample in enumerate(SAMPLES):
if i == cur_sample:
- root_console.default_fg = libtcod.white
- root_console.default_bg = libtcod.light_blue
+ fg = WHITE
+ bg = LIGHT_BLUE
else:
- root_console.default_fg = libtcod.grey
- root_console.default_bg = libtcod.black
- root_console.print_(2, 46 - (len(SAMPLES) - i),
- ' %s' % sample.name.ljust(19),
- libtcod.BKGND_SET, libtcod.LEFT)
-
-def draw_stats():
- root_console.default_fg = libtcod.grey
- root_console.print_(
- 79, 46,
- ' last frame : %3d ms (%3d fps)' % (
- libtcod.sys_get_last_frame_length() * 1000.0,
- libtcod.sys_get_fps(),
- ),
- libtcod.BKGND_NONE, libtcod.RIGHT
- )
- root_console.print_(
- 79, 47,
- 'elapsed : %8d ms %4.2fs' % (libtcod.sys_elapsed_milli(),
- libtcod.sys_elapsed_seconds()),
- libtcod.BKGND_NONE, libtcod.RIGHT,
+ fg = GREY
+ bg = BLACK
+ root_console.print(
+ 2,
+ 46 - (len(SAMPLES) - i),
+ f" {sample.name.ljust(19)}",
+ fg,
+ bg,
+ alignment=libtcodpy.LEFT,
)
-def draw_renderer_menu():
- current_renderer = libtcod.sys_get_renderer()
- root_console.default_fg = libtcod.grey
- root_console.default_bg = libtcod.black
- root_console.print_(42, 46 - (libtcod.NB_RENDERERS + 1),
- "Renderer :", libtcod.BKGND_SET, libtcod.LEFT)
+
+def draw_stats() -> None:
+ try:
+ fps = 1 / (sum(frame_length) / len(frame_length))
+ except ZeroDivisionError:
+ fps = 0
+ root_console.print(
+ root_console.width,
+ 46,
+ f"last frame :{frame_length[-1] * 1000.0:5.1f} ms ({int(fps):4d} fps)",
+ fg=GREY,
+ alignment=libtcodpy.RIGHT,
+ )
+ root_console.print(
+ root_console.width,
+ 47,
+ f"elapsed : {int(_get_elapsed_time() * 1000):8d} ms {_get_elapsed_time():5.2f}s",
+ fg=GREY,
+ alignment=libtcodpy.RIGHT,
+ )
+
+
+def draw_renderer_menu() -> None:
+ root_console.print(
+ 42,
+ 46 - (libtcodpy.NB_RENDERERS + 1),
+ "Renderer :",
+ fg=GREY,
+ bg=BLACK,
+ )
for i, name in enumerate(RENDERER_NAMES):
- if i == current_renderer:
- root_console.default_fg = libtcod.white
- root_console.default_bg = libtcod.light_blue
+ if i == context.renderer_type:
+ fg = WHITE
+ bg = LIGHT_BLUE
else:
- root_console.default_fg = libtcod.grey
- root_console.default_bg = libtcod.black
- root_console.print_(
- 42, 46 - (libtcod.NB_RENDERERS - i),
- name, libtcod.BKGND_SET, libtcod.LEFT
- )
+ fg = GREY
+ bg = BLACK
+ root_console.print(42, 46 - libtcodpy.NB_RENDERERS + i, name, fg, bg)
+
+
+if __name__ == "__main__":
+ if not sys.warnoptions:
+ warnings.simplefilter("default") # Show all warnings.
+
+ @tcod.event.add_watch
+ def _handle_events(event: tcod.event.Event) -> None:
+ """Keep window responsive during resize events."""
+ match event:
+ case tcod.event.WindowEvent(type="WindowExposed"):
+ redraw_display()
+ handle_time()
-if __name__ == '__main__':
main()
diff --git a/examples/sdl-hello-world.py b/examples/sdl-hello-world.py
new file mode 100644
index 00000000..02017f12
--- /dev/null
+++ b/examples/sdl-hello-world.py
@@ -0,0 +1,50 @@
+"""Hello world using tcod's SDL API and using Pillow for the TTF rendering."""
+
+from pathlib import Path
+
+import numpy as np
+from PIL import Image, ImageDraw, ImageFont # pip install Pillow
+
+import tcod.event
+import tcod.sdl.render
+import tcod.sdl.video
+
+CURRENT_DIR = Path(__file__).parent # Directory of this script.
+font = ImageFont.truetype(bytes(CURRENT_DIR / "DejaVuSerif.ttf"), size=18) # Preloaded font file.
+
+
+def render_text(renderer: tcod.sdl.render.Renderer, text: str) -> tcod.sdl.render.Texture:
+ """Render text, upload it to VRAM, then return it as an SDL Texture."""
+ # Use Pillow to render the font.
+ _left, _top, right, bottom = font.getbbox(text)
+ width, height = int(right), int(bottom)
+ image = Image.new("RGBA", (width, height))
+ draw = ImageDraw.Draw(image)
+ draw.text((0, 0), text, font=font)
+ # Push to VRAM using SDL.
+ texture = renderer.upload_texture(np.asarray(image))
+ texture.blend_mode = tcod.sdl.render.BlendMode.BLEND # Enable alpha blending by default.
+ return texture
+
+
+def main() -> None:
+ """Show hello world until the window is closed."""
+ # Open an SDL window and renderer.
+ window = tcod.sdl.video.new_window(720, 480, flags=tcod.sdl.video.WindowFlags.RESIZABLE)
+ renderer = tcod.sdl.render.new_renderer(window)
+ # Render the text once, then reuse the texture.
+ hello_world = render_text(renderer, "Hello World")
+ hello_world.color_mod = (64, 255, 64) # Set the color when copied.
+
+ while True:
+ renderer.draw_color = (0, 0, 0, 255)
+ renderer.clear()
+ renderer.copy(hello_world, dest=(0, 0, hello_world.width, hello_world.height))
+ renderer.present()
+ for event in tcod.event.get():
+ if isinstance(event, tcod.event.Quit):
+ raise SystemExit
+
+
+if __name__ == "__main__":
+ main()
diff --git a/examples/sdlevent.py b/examples/sdlevent.py
deleted file mode 100644
index 8aeb1228..00000000
--- a/examples/sdlevent.py
+++ /dev/null
@@ -1,335 +0,0 @@
-#!/usr/bin/env python
-"""
- An alternative, more direct implementation of event handling using cffi
- calls to SDL functions. The current code is incomplete, but can be
- extended easily by following the official SDL documentation.
-
- This module be run directly like any other event get example, but is meant
- to be copied into your code base. Then you can use the sdlevent.get and
- sdlevent.wait functions in your code.
-
- Printing any event will tell you its attributes in a human readable format.
- An events type attr is just the classes name with all letters upper-case.
-
- Like in tdl, events use a type attribute to tell events apart. Unlike tdl
- and tcod the names and values used are directly derived from SDL.
-
- As a general guideline for turn-based rouge-likes, you should use
- KeyDown.sym for commands, and TextInput.text for name entry.
-
- This module may be included as a tcod.event module once it is more mature.
-
- An absolute minimal example:
-
- import tcod
- import sdlevent
-
- with tcod.console_init_root(80, 60, 'title') as console:
- while True:
- for event in sdlevent.wait():
- print(event)
- if event.type == 'QUIT':
- raise SystemExit()
- tcod.console_flush()
-"""
-# CC0 License: https://creativecommons.org/publicdomain/zero/1.0/
-# To the extent possible under law, Kyle Stewart has waived all copyright and
-# related or neighboring rights to this sdlevent.py module.
-
-import tcod
-
-def _copy_attrs(prefix):
- """Copy names and values from tcod.lib into this modules top-level scope.
-
- This is a private function, used internally.
-
- Args:
- prefix (str): Used to filter out which names to copy.
-
- Returns:
- Dict[Any,str]: A reverse lookup table used in Event repr functions.
- """
- g = globals() # dynamically add names to the global state
- # removes the SDL prefix, this module already has SDL in the name
- if prefix.startswith('SDL_'):
- name_starts_at = 4
- elif prefix.startswith('SDL'):
- name_starts_at = 3
- else:
- name_starts_at = 0
- revere_table = {}
- for attr in dir(tcod.lib):
- if attr.startswith(prefix):
- name = attr[name_starts_at:]
- value = getattr(tcod.lib, attr)
- revere_table[value] = 'sdlevent.' + name
- g[name] = value
-
- return revere_table
-
-def _describe_bitmask(bits, table, default='0'):
- """Returns a bitmask in human readable form.
-
- This is a private function, used internally.
-
- Args:
- bits (int): The bitmask to be represented.
- table (Dict[Any,str]): A reverse lookup table.
- default (Any): A default return value when bits is 0.
-
- Returns: str: A printable version of the bits variable.
- """
- result = []
- for bit, name in table.items():
- if bit & bits:
- result.append(name)
- if not result:
- return default
- return '|'.join(result)
-
-
-_REVSRSE_SCANCODE_TABLE = _copy_attrs('SDL_SCANCODE')
-_REVSRSE_SYM_TABLE = _copy_attrs('SDLK')
-_REVSRSE_MOD_TABLE = _copy_attrs('KMOD')
-_REVSRSE_WHEEL_TABLE = _copy_attrs('SDL_MOUSEWHEEL')
-
-# manually define names for SDL macros
-BUTTON_LEFT = 1
-BUTTON_MIDDLE = 2
-BUTTON_RIGHT = 3
-BUTTON_X1 = 4
-BUTTON_X2 = 5
-BUTTON_LMASK = 0x01
-BUTTON_MMASK = 0x02
-BUTTON_RMASK = 0x04
-BUTTON_X1MASK = 0x08
-BUTTON_X2MASK = 0x10
-
-_REVSRSE_BUTTON_TABLE = {
- BUTTON_LEFT: 'sdlevent.BUTTON_LEFT',
- BUTTON_MIDDLE: 'sdlevent.BUTTON_MIDDLE',
- BUTTON_RIGHT: 'sdlevent.BUTTON_RIGHT',
- BUTTON_X1: 'sdlevent.BUTTON_X1',
- BUTTON_X2: 'sdlevent.BUTTON_X2',
-}
-
-_REVSRSE_BUTTON_MASK_TABLE = {
- BUTTON_LMASK: 'sdlevent.BUTTON_LMASK',
- BUTTON_MMASK: 'sdlevent.BUTTON_MMASK',
- BUTTON_RMASK: 'sdlevent.BUTTON_RMASK',
- BUTTON_X1MASK: 'sdlevent.BUTTON_X1MASK',
- BUTTON_X2MASK: 'sdlevent.BUTTON_X2MASK',
-}
-
-class Event(object):
- """The base event class."""
- @classmethod
- def from_sdl_event(cls, sdl_event):
- """Return a class instance from a cffi SDL_Event pointer."""
- raise NotImplementedError()
-
- @property
- def type(self):
- """All event types are just the class name, but all upper-case."""
- return self.__class__.__name__.upper()
-
-
-class Quit(Event):
- """An application quit request event.
-
- For more info on when this event is triggered see:
- https://wiki.libsdl.org/SDL_EventType#SDL_QUIT
- """
- @classmethod
- def from_sdl_event(cls, sdl_event):
- return cls()
-
- def __repr__(self):
- return 'sdlevent.%s()' % self.__class__.__name__
-
-
-class KeyboardEvent(Event):
-
- def __init__(self, scancode, sym, mod, repeat=False):
- self.scancode = scancode
- self.sym = sym
- self.mod = mod
- self.repeat = repeat
-
- @classmethod
- def from_sdl_event(cls, sdl_event):
- keysym = sdl_event.key.keysym
- return cls(keysym.scancode, keysym.sym, keysym.mod,
- bool(sdl_event.key.repeat))
-
- def __repr__(self):
- return ('sdlevent.%s(scancode=%s, sym=%s, mod=%s%s)' %
- (self.__class__.__name__,
- _REVSRSE_SCANCODE_TABLE[self.scancode],
- _REVSRSE_SYM_TABLE[self.sym],
- _describe_bitmask(self.mod, _REVSRSE_MOD_TABLE),
- ', repeat=True' if self.repeat else '',
- )
- )
-
-
-class KeyDown(KeyboardEvent):
- pass
-
-
-class KeyUp(KeyboardEvent):
- pass
-
-
-class MouseMotion(Event):
-
- def __init__(self, x, y, xrel, yrel, state):
- self.x = x
- self.y = y
- self.xrel = xrel
- self.yrel = yrel
- self.state = state
-
- @classmethod
- def from_sdl_event(cls, sdl_event):
- motion = sdl_event.motion
- return cls(motion.x, motion.y, motion.xrel, motion.yrel, motion.state)
-
- def __repr__(self):
- return ('sdlevent.%s(x=%i, y=%i, xrel=%i, yrel=%i, state=%s)' %
- (self.__class__.__name__,
- self.x, self.y,
- self.xrel, self.yrel,
- _describe_bitmask(self.state, _REVSRSE_BUTTON_MASK_TABLE),
- )
- )
-
-
-class MouseButtonEvent(Event):
-
- def __init__(self, x, y, button):
- self.x = x
- self.y = y
- self.button = button
-
- @classmethod
- def from_sdl_event(cls, sdl_event):
- button = sdl_event.button
- return cls(button.x, button.y, button.button)
-
- def __repr__(self):
- return ('sdlevent.%s(x=%i, y=%i, button=%s)' %
- (self.__class__.__name__,
- self.x, self.y, _REVSRSE_BUTTON_TABLE[self.button],
- )
- )
-
-
-class MouseButtonDown(MouseButtonEvent):
- pass
-
-
-class MouseButtonUp(MouseButtonEvent):
- pass
-
-
-class MouseWheel(Event):
-
- def __init__(self, x, y, direction):
- self.x = x
- self.y = y
- self.direction = direction
-
- @classmethod
- def from_sdl_event(cls, sdl_event):
- wheel = sdl_event.wheel
- return cls(wheel.x, wheel.y, wheel.direction)
-
- def __repr__(self):
- return ('sdlevent.%s(x=%i, y=%i, direction=%s)' %
- (self.__class__.__name__,
- self.x, self.y,
- _REVSRSE_WHEEL_TABLE[self.direction],
- )
- )
-
-
-class TextInput(Event):
-
- def __init__(self, text):
- self.text = text
-
- @classmethod
- def from_sdl_event(cls, sdl_event):
- return cls(tcod.ffi.string(sdl_event.text.text, 32).decode('utf8'))
-
- def __repr__(self):
- return ('sdlevent.%s(text=%r)' % (self.__class__.__name__, self.text))
-
-
-_SDL_TO_CLASS_TABLE = {
- tcod.lib.SDL_QUIT: Quit,
- tcod.lib.SDL_KEYDOWN: KeyDown,
- tcod.lib.SDL_KEYUP: KeyUp,
- tcod.lib.SDL_MOUSEMOTION: MouseMotion,
- tcod.lib.SDL_MOUSEBUTTONDOWN: MouseButtonDown,
- tcod.lib.SDL_MOUSEBUTTONUP: MouseButtonUp,
- tcod.lib.SDL_MOUSEWHEEL: MouseWheel,
- tcod.lib.SDL_TEXTINPUT: TextInput,
-}
-
-def get():
- """Iterate over all pending events.
-
- Returns:
- Iterator[sdlevent.Event]:
- An iterator of Event subclasses.
- """
- sdl_event = tcod.ffi.new('SDL_Event*')
- while tcod.lib.SDL_PollEvent(sdl_event):
- if sdl_event.type in _SDL_TO_CLASS_TABLE:
- yield _SDL_TO_CLASS_TABLE[sdl_event.type].from_sdl_event(sdl_event)
-
-def wait(timeout=None):
- """Block until an event exists, then iterate over all events.
-
- Keep in mind that this function will wake even for events not handled by
- this module.
-
- Args:
- timeout (Optional[int]):
- Maximum number of milliseconds to wait, or None to wait forever.
-
- Returns:
- Iterator[sdlevent.Event]: Same iterator as a call to sdlevent.get
- """
- if timeout is not None:
- tcod.lib.SDL_WaitEventTimeout(tcod.ffi.NULL, timeout)
- else:
- tcod.lib.SDL_WaitEvent(tcod.ffi.NULL)
- return get()
-
-def _main():
- """An example program for when this module is run directly."""
- WIDTH, HEIGHT = 120, 60
- TITLE = 'sdlevent.py engine'
-
- with tcod.console_init_root(WIDTH, HEIGHT, TITLE, order='F') as console:
- tcod.sys_set_fps(24)
- while True:
- for event in wait():
- print(event)
- if event.type == 'QUIT':
- raise SystemExit()
- elif event.type == 'MOUSEMOTION':
- console.ch[:,-1] = 0
- console.print_(0, HEIGHT - 1, repr(event))
- else:
- console.blit(console, 0, 0, 0, 1, WIDTH, HEIGHT - 2)
- console.ch[:,-3] = 0
- console.print_(0, HEIGHT - 3, repr(event))
-
- tcod.console_flush()
-
-if __name__ == '__main__':
- _main()
diff --git a/examples/termbox/README.md b/examples/termbox/README.md
deleted file mode 100644
index 3ec77f56..00000000
--- a/examples/termbox/README.md
+++ /dev/null
@@ -1,59 +0,0 @@
-API of `termbox` Python module implemented in `tld`.
-
-The code here are modified files from
-[termbox repository](https://github.com/nsf/termbox/), so please consult
-it for the license and other info.
-
-
-The code consists of two part - `termbox.py` module with API, translation
-of official binding form the description below into `tld`:
-
-https://github.com/nsf/termbox/blob/b20c0a11/src/python/termboxmodule.pyx
-
-And the example `termboxtest.py` which is copied verbatim from:
-
-https://github.com/nsf/termbox/blob/b20c0a11/test_termboxmodule.py
-
-
-### API Mapping Notes
-
-Notes taken while mapping the Termbox class:
-
- tb_init() // initialization console = tdl.init(132, 60)
- tb_shutdown() // shutdown
-
- tb_width() // width of the terminal screen console.width
- tb_height() // height of the terminal screen console.height
-
- tb_clear() // clear buffer console.clear()
- tb_present() // sync internal buffer with terminal tdl.flush()
-
- tb_put_cell()
- tb_change_cell() console.draw_char(x, y, ch, fg, bg)
- tb_blit() // drawing functions
-
- tb_select_input_mode() // change input mode
- tb_peek_event() // peek a keyboard event
- tb_poll_event() // wait for a keyboard event * tdl.event.get()
-
-
- * - means the translation is not direct
-
-
-
- init...
- tdl doesn't allow to resize window (or rather libtcod)
- tb works in existing terminal window and queries it rather than making own
-
- colors...
- tdl uses RGB values
- tb uses it own constants
-
- event...
- tb returns event one by one
- tdl return an event iterator
-
-
- tb Event tdl Event
- .type .type
- EVENT_KEY KEYDOWN
diff --git a/examples/termbox/termbox.py b/examples/termbox/termbox.py
deleted file mode 100644
index 787360b3..00000000
--- a/examples/termbox/termbox.py
+++ /dev/null
@@ -1,290 +0,0 @@
-"""
-Implementation of Termbox Python API in tdl.
-
-See README.md for details.
-"""
-
-import tdl
-
-"""
-Implementation status:
- [ ] tdl.init() needs a window, made 132x60
- [ ] Termbox.close() is not implemented, does nothing
- [ ] poll_event needs review, because it does not
- completely follows the original logic
- [ ] peek is stubbed, but not implemented
- [ ] not all keys/events are mapped
-"""
-
-class TermboxException(Exception):
- def __init__(self, msg):
- self.msg = msg
- def __str__(self):
- return self.msg
-
-_instance = None
-
-# keys ----------------------------------
-KEY_F1 = (0xFFFF-0)
-KEY_F2 = (0xFFFF-1)
-KEY_F3 = (0xFFFF-2)
-KEY_F4 = (0xFFFF-3)
-KEY_F5 = (0xFFFF-4)
-KEY_F6 = (0xFFFF-5)
-KEY_F7 = (0xFFFF-6)
-KEY_F8 = (0xFFFF-7)
-KEY_F9 = (0xFFFF-8)
-KEY_F10 = (0xFFFF-9)
-KEY_F11 = (0xFFFF-10)
-KEY_F12 = (0xFFFF-11)
-KEY_INSERT = (0xFFFF-12)
-KEY_DELETE = (0xFFFF-13)
-
-KEY_PGUP = (0xFFFF-16)
-KEY_PGDN = (0xFFFF-17)
-
-KEY_MOUSE_LEFT =(0xFFFF-22)
-KEY_MOUSE_RIGHT =(0xFFFF-23)
-KEY_MOUSE_MIDDLE =(0xFFFF-24)
-KEY_MOUSE_RELEASE =(0xFFFF-25)
-KEY_MOUSE_WHEEL_UP =(0xFFFF-26)
-KEY_MOUSE_WHEEL_DOWN =(0xFFFF-27)
-
-KEY_CTRL_TILDE = 0x00
-KEY_CTRL_2 = 0x00
-KEY_CTRL_A = 0x01
-KEY_CTRL_B = 0x02
-KEY_CTRL_C = 0x03
-KEY_CTRL_D = 0x04
-KEY_CTRL_E = 0x05
-KEY_CTRL_F = 0x06
-KEY_CTRL_G = 0x07
-KEY_BACKSPACE = 0x08
-KEY_CTRL_H = 0x08
-KEY_TAB = 0x09
-KEY_CTRL_I = 0x09
-KEY_CTRL_J = 0x0A
-KEY_CTRL_K = 0x0B
-KEY_CTRL_L = 0x0C
-KEY_ENTER = 0x0D
-KEY_CTRL_M = 0x0D
-KEY_CTRL_N = 0x0E
-KEY_CTRL_O = 0x0F
-KEY_CTRL_P = 0x10
-KEY_CTRL_Q = 0x11
-KEY_CTRL_R = 0x12
-KEY_CTRL_S = 0x13
-KEY_CTRL_T = 0x14
-KEY_CTRL_U = 0x15
-KEY_CTRL_V = 0x16
-KEY_CTRL_W = 0x17
-KEY_CTRL_X = 0x18
-KEY_CTRL_Y = 0x19
-KEY_CTRL_Z = 0x1A
-
-
-# -- mapped to tdl
-KEY_HOME = 'HOME'
-KEY_END = 'END'
-KEY_ARROW_UP = 'UP'
-KEY_ARROW_DOWN = 'DOWN'
-KEY_ARROW_LEFT = 'LEFT'
-KEY_ARROW_RIGHT = 'RIGHT'
-KEY_ESC = 'ESCAPE'
-# /--
-
-
-KEY_CTRL_LSQ_BRACKET = 0x1B
-KEY_CTRL_3 = 0x1B
-KEY_CTRL_4 = 0x1C
-KEY_CTRL_BACKSLASH = 0x1C
-KEY_CTRL_5 = 0x1D
-KEY_CTRL_RSQ_BRACKET = 0x1D
-KEY_CTRL_6 = 0x1E
-KEY_CTRL_7 = 0x1F
-KEY_CTRL_SLASH = 0x1F
-KEY_CTRL_UNDERSCORE = 0x1F
-KEY_SPACE = 0x20
-KEY_BACKSPACE2 = 0x7F
-KEY_CTRL_8 = 0x7F
-
-MOD_ALT = 0x01
-
-# attributes ----------------------
-
-#-- mapped to tdl
-DEFAULT = Ellipsis
-
-BLACK = 0x000000
-RED = 0xFF0000
-GREEN = 0x00FF00
-YELLOW = 0xFFFF00
-BLUE = 0x0000FF
-MAGENTA = 0xFF00FF
-CYAN = 0x00FFFF
-WHITE = 0xFFFFFF
-#/--
-
-BOLD = 0x10
-UNDERLINE = 0x20
-REVERSE = 0x40
-
-# misc ----------------------------
-
-HIDE_CURSOR = -1
-INPUT_CURRENT = 0
-INPUT_ESC = 1
-INPUT_ALT = 2
-OUTPUT_CURRENT = 0
-OUTPUT_NORMAL = 1
-OUTPUT_256 = 2
-OUTPUT_216 = 3
-OUTPUT_GRAYSCALE = 4
-
-
-# -- mapped to tdl
-EVENT_KEY = 'KEYDOWN'
-# /--
-EVENT_RESIZE = 2
-EVENT_MOUSE = 3
-
-class Event:
- """ Aggregate for Termbox Event structure """
- type = None
- ch = None
- key = None
- mod = None
- width = None
- height = None
- mousex = None
- mousey = None
-
- def gettuple(self):
- return (self.type, self.ch, self.key, self.mod, self.width, self.height, self.mousex, self.mousey)
-
-class Termbox:
- def __init__(self, width=132, height=60):
- global _instance
- if _instance:
- raise TermboxException("It is possible to create only one instance of Termbox")
-
- try:
- self.console = tdl.init(width, height)
- except tdl.TDLException as e:
- raise TermboxException(e)
-
- self.e = Event() # cache for event data
-
- _instance = self
-
- def __del__(self):
- self.close()
-
- def __exit__(self, *args):#t, value, traceback):
- self.close()
-
- def __enter__(self):
- return self
-
- def close(self):
- global _instance
- # tb_shutdown()
- _instance = None
- # TBD, does nothing
-
- def present(self):
- """Sync state of the internal cell buffer with the terminal.
- """
- tdl.flush()
-
- def change_cell(self, x, y, ch, fg, bg):
- """Change cell in position (x;y).
- """
- self.console.draw_char(x, y, ch, fg, bg)
-
- def width(self):
- """Returns width of the terminal screen.
- """
- return self.console.width
-
- def height(self):
- """Return height of the terminal screen.
- """
- return self.console.height
-
- def clear(self):
- """Clear the internal cell buffer.
- """
- self.console.clear()
-
- def set_cursor(self, x, y):
- """Set cursor position to (x;y).
-
- Set both arguments to HIDE_CURSOR or use 'hide_cursor' function to hide it.
- """
- tb_set_cursor(x, y)
-
- def hide_cursor(self):
- """Hide cursor.
- """
- tb_set_cursor(-1, -1)
-
- def select_input_mode(self, mode):
- """Select preferred input mode: INPUT_ESC or INPUT_ALT.
-
- INPUT_CURRENT returns the selected mode without changing anything.
- """
- return int(tb_select_input_mode(mode))
-
- def select_output_mode(self, mode):
- """Select preferred output mode: one of OUTPUT_* constants.
-
- OUTPUT_CURRENT returns the selected mode without changing anything.
- """
- return int(tb_select_output_mode(mode))
-
- def peek_event(self, timeout=0):
- """Wait for an event up to 'timeout' milliseconds and return it.
-
- Returns None if there was no event and timeout is expired.
- Returns a tuple otherwise: (type, unicode character, key, mod, width, height, mousex, mousey).
- """
- """
- cdef tb_event e
- with self._poll_lock:
- with nogil:
- result = tb_peek_event(&e, timeout)
- assert(result >= 0)
- if result == 0:
- return None
- if e.ch:
- uch = unichr(e.ch)
- else:
- uch = None
- """
- pass #return (e.type, uch, e.key, e.mod, e.w, e.h, e.x, e.y)
-
- def poll_event(self):
- """Wait for an event and return it.
-
- Returns a tuple: (type, unicode character, key, mod, width, height, mousex, mousey).
- """
- """
- cdef tb_event e
- with self._poll_lock:
- with nogil:
- result = tb_poll_event(&e)
- assert(result >= 0)
- if e.ch:
- uch = unichr(e.ch)
- else:
- uch = None
- """
- for e in tdl.event.get():
- # [ ] not all events are passed thru
- self.e.type = e.type
- if e.type == 'KEYDOWN':
- self.e.key = e.key
- return self.e.gettuple()
-
- #return (e.type, uch, e.key, e.mod, e.w, e.h, e.x, e.y)
diff --git a/examples/termbox/termboxtest.py b/examples/termbox/termboxtest.py
deleted file mode 100644
index f9585feb..00000000
--- a/examples/termbox/termboxtest.py
+++ /dev/null
@@ -1,129 +0,0 @@
-#!/usr/bin/python
-# -*- encoding: utf-8 -*-
-
-import termbox
-import time
-import sys
-import random
-#import psyco
-
-#psyco.full()
-
-spaceord = ord(u" ")
-
-def print_line(t, msg, y, fg, bg):
- w = t.width()
- l = len(msg)
- x = 0
- for i in range(w):
- c = spaceord
- if i < l:
- c = ord(msg[i])
- t.change_cell(x+i, y, c, fg, bg)
-
-class SelectBox(object):
- def __init__(self, tb, choices, active=-1):
- self.tb = tb
- self.active = active
- self.choices = choices
- self.color_active = (termbox.BLACK, termbox.CYAN)
- self.color_normal = (termbox.WHITE, termbox.BLACK)
-
- def draw(self):
- for i, c in enumerate(self.choices):
- color = self.color_normal
- if i == self.active:
- color = self.color_active
- print_line(self.tb, c, i, *color)
-
- def validate_active(self):
- if self.active < 0:
- self.active = 0
- if self.active >= len(self.choices):
- self.active = len(self.choices)-1
-
- def set_active(self, i):
- self.active = i
- self.validate_active()
-
- def move_up(self):
- self.active -= 1
- self.validate_active()
-
- def move_down(self):
- self.active += 1
- self.validate_active()
-
-choices = [
- u"This instructs Psyco",
- u"to compile and run as",
- u"much of your application",
- u"code as possible. This is the",
- u"simplest interface to Psyco.",
- u"In good cases you can just add",
- u"these two lines and enjoy the speed-up.",
- u"If your application does a lot",
- u"of initialization stuff before",
- u"the real work begins, you can put",
- u"the above two lines after this",
- u"initialization - e.g. after importing",
- u"modules, creating constant global objects, etc.",
- u"This instructs Psyco",
- u"to compile and run as",
- u"much of your application",
- u"code as possible. This is the",
- u"simplest interface to Psyco.",
- u"In good cases you can just add",
- u"these two lines and enjoy the speed-up.",
- u"If your application does a lot",
- u"of initialization stuff before",
- u"the real work begins, you can put",
- u"the above two lines after this",
- u"initialization - e.g. after importing",
- u"modules, creating constant global objects, etc."
-]
-
-def draw_bottom_line(t, i):
- i = i % 8
- w = t.width()
- h = t.height()
- c = i
- palette = [termbox.DEFAULT, termbox.BLACK, termbox.RED, termbox.GREEN,
- termbox.YELLOW, termbox.BLUE, termbox.MAGENTA, termbox.CYAN,
- termbox.WHITE]
- for x in range(w):
- t.change_cell(x, h-1, ord(u' '), termbox.BLACK, palette[c])
- t.change_cell(x, h-2, ord(u' '), termbox.BLACK, palette[c])
- c += 1
- if c > 7:
- c = 0
-
-with termbox.Termbox() as t:
- sb = SelectBox(t, choices, 0)
- t.clear()
- sb.draw()
- t.present()
- i = 0
- run_app = True
- while run_app:
- event_here = t.poll_event()
- while event_here:
- (type, ch, key, mod, w, h, x, y) = event_here
- if type == termbox.EVENT_KEY and key == termbox.KEY_ESC:
- run_app = False
- if type == termbox.EVENT_KEY:
- if key == termbox.KEY_ARROW_DOWN:
- sb.move_down()
- elif key == termbox.KEY_ARROW_UP:
- sb.move_up()
- elif key == termbox.KEY_HOME:
- sb.set_active(-1)
- elif key == termbox.KEY_END:
- sb.set_active(999)
- event_here = t.peek_event()
-
- t.clear()
- sb.draw()
- draw_bottom_line(t, i)
- t.present()
- i += 1
diff --git a/examples/terminal.png b/examples/terminal.png
deleted file mode 100644
index 3cffb5b3..00000000
Binary files a/examples/terminal.png and /dev/null differ
diff --git a/examples/thread_jobs.py b/examples/thread_jobs.py
old mode 100644
new mode 100755
index 9afd123c..9f6cf6c1
--- a/examples/thread_jobs.py
+++ b/examples/thread_jobs.py
@@ -1,18 +1,27 @@
#!/usr/bin/env python
-
-import sys
-
+# To the extent possible under law, the libtcod maintainers have waived all
+# copyright and related or neighboring rights for this example. This work is
+# published from: United States.
+# https://creativecommons.org/publicdomain/zero/1.0/
+"""A parallelization example for the field-of-view and path-finding tasks.
+
+Because of the Python GIL you can have only one thread in Python at a time.
+However, many C functions with long computations will release the GIL for their
+duration, allowing another Python thread to call into another C function.
+
+This script tests the viability of running python-tcod tasks in parallel.
+Typically the field-of-view tasks run good but not great, and the path-finding
+tasks run poorly.
+"""
+
+import concurrent.futures
import multiprocessing
import platform
-import threading
-try:
- import Queue as queue
-except ImportError:
- import queue
+import sys
import timeit
+from collections.abc import Callable
-import tcod
-#import libtcodpy as tcod
+import tcod.map
THREADS = multiprocessing.cpu_count()
@@ -20,85 +29,72 @@
MAP_HEIGHT = 100
MAP_NUMBER = 500
-PATH_NUMBER = 500
-
-class JobConsumer(threading.Thread):
-
- def __init__(self, i):
- threading.Thread.__init__(self)
- self.daemon = True
- self.astar = tcod.path_new_using_map(maps[i])
-
- def run(self):
- while 1:
- job, obj = jobs.get()
- if job == 'fov':
- tcod.map_compute_fov(obj, MAP_WIDTH // 2, MAP_HEIGHT // 2)
- elif job == 'astar':
- tcod.path_compute(self.astar,
- 0, 0, MAP_WIDTH - 1 , MAP_HEIGHT - 1)
- x, y = tcod.path_walk(self.astar, False)
- while x is not None:
- x, y = tcod.path_walk(self.astar, False)
- jobs.task_done()
-
-maps = [tcod.map_new(MAP_WIDTH, MAP_HEIGHT) for i in range(MAP_NUMBER)]
-jobs = queue.Queue()
-threads = [JobConsumer(i) for i in range(THREADS)]
-
-def test_fov_single():
- for m in maps:
- tcod.map_compute_fov(m, MAP_WIDTH // 2, MAP_HEIGHT // 2)
-
-def test_fov_threads():
- for m in maps:
- jobs.put(('fov', m))
- jobs.join()
-
-def test_astar_single():
- astar = tcod.path_new_using_map(maps[0])
- for _ in range(PATH_NUMBER):
- tcod.path_compute(astar, 0, 0, MAP_WIDTH - 1 , MAP_HEIGHT - 1)
- x, y = tcod.path_walk(astar, False)
- while x is not None:
- x, y = tcod.path_walk(astar, False)
-
-def test_astar_threads():
- for _ in range(PATH_NUMBER):
- jobs.put(('astar', None))
- jobs.join()
-
-def main():
- for m in maps:
- for y in range(MAP_HEIGHT):
- for x in range(MAP_WIDTH):
- tcod.map_set_properties(m, x, y, True, True)
-
- for thread in threads:
- thread.start()
-
- print('Python %s\n%s\n%s' % (sys.version, platform.platform(),
- platform.processor()))
-
- print('\nComputing field-of-view for %i empty %ix%i maps.' %
- (len(maps), MAP_WIDTH, MAP_HEIGHT))
- single_time = min(timeit.repeat(test_fov_single, number=1))
- print('1 thread: %.2fms' % (single_time * 1000))
-
- multi_time = min(timeit.repeat(test_fov_threads, number=1))
- print('%i threads: %.2fms' % (THREADS, multi_time * 1000))
- print('%.2f%% efficiency' %
- (single_time / (multi_time * THREADS) * 100))
-
- print('\nComputing AStar from corner to corner %i times on seperate empty'
- ' %ix%i maps.' % (PATH_NUMBER, MAP_WIDTH, MAP_HEIGHT))
- single_time = min(timeit.repeat(test_astar_single, number=1))
- print('1 thread: %.2fms' % (single_time * 1000))
-
- multi_time = min(timeit.repeat(test_astar_threads, number=1))
- print('%i threads: %.2fms' % (THREADS, multi_time * 1000))
- print('%.2f%% efficiency' %
- (single_time / (multi_time * THREADS) * 100))
-
-if __name__ == '__main__':
+REPEAT = 10 # Number to times to run a test. Only the fastest result is shown.
+
+
+def test_fov(map_: tcod.map.Map) -> tcod.map.Map:
+ map_.compute_fov(MAP_WIDTH // 2, MAP_HEIGHT // 2)
+ return map_
+
+
+def test_fov_single(maps: list[tcod.map.Map]) -> None:
+ for map_ in maps:
+ test_fov(map_)
+
+
+def test_fov_threads(executor: concurrent.futures.Executor, maps: list[tcod.map.Map]) -> None:
+ for _result in executor.map(test_fov, maps):
+ pass
+
+
+def test_astar(map_: tcod.map.Map) -> list[tuple[int, int]]:
+ astar = tcod.path.AStar(map_)
+ return astar.get_path(0, 0, MAP_WIDTH - 1, MAP_HEIGHT - 1)
+
+
+def test_astar_single(maps: list[tcod.map.Map]) -> None:
+ for map_ in maps:
+ test_astar(map_)
+
+
+def test_astar_threads(executor: concurrent.futures.Executor, maps: list[tcod.map.Map]) -> None:
+ for _result in executor.map(test_astar, maps):
+ pass
+
+
+def run_test(
+ maps: list[tcod.map.Map],
+ single_func: Callable[[list[tcod.map.Map]], None],
+ multi_func: Callable[[concurrent.futures.Executor, list[tcod.map.Map]], None],
+) -> None:
+ """Run a function designed for a single thread and compare it to a threaded version.
+
+ This prints the results of these tests.
+ """
+ single_time = min(timeit.repeat(lambda: single_func(maps), number=1, repeat=REPEAT))
+ print(f"Single threaded: {single_time * 1000:.2f}ms")
+
+ for i in range(1, THREADS + 1):
+ executor = concurrent.futures.ThreadPoolExecutor(i)
+ multi_time = min(timeit.repeat(lambda: multi_func(executor, maps), number=1, repeat=REPEAT))
+ print(f"{i} threads: {multi_time * 1000:.2f}ms, {single_time / (multi_time * i) * 100:.2f}% efficiency")
+
+
+def main() -> None:
+ """Setup and run tests."""
+ maps = [tcod.map.Map(MAP_WIDTH, MAP_HEIGHT) for i in range(MAP_NUMBER)]
+ for map_ in maps:
+ map_.walkable[...] = True
+ map_.transparent[...] = True
+
+ print(f"Python {sys.version}\n{platform.platform()}\n{platform.processor()}")
+
+ print(f"\nComputing field-of-view for {len(maps)} empty {MAP_WIDTH}x{MAP_HEIGHT} maps.")
+ run_test(maps, test_fov_single, test_fov_threads)
+
+ print(f"\nComputing AStar from corner to corner {len(maps)} times on separate empty {MAP_WIDTH}x{MAP_HEIGHT} maps.")
+ run_test(maps, test_astar_single, test_astar_threads)
+
+
+if __name__ == "__main__":
main()
diff --git a/examples/ttf.py b/examples/ttf.py
new file mode 100755
index 00000000..4a6041a8
--- /dev/null
+++ b/examples/ttf.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python
+"""A TrueType Font example using the FreeType library.
+
+You will need to get this external library from PyPI:
+
+ pip install freetype-py
+"""
+
+# To the extent possible under law, the libtcod maintainers have waived all
+# copyright and related or neighboring rights to this example script.
+# https://creativecommons.org/publicdomain/zero/1.0/
+from typing import TYPE_CHECKING
+
+import freetype # type: ignore # pip install freetype-py
+import numpy as np
+
+import tcod.console
+import tcod.context
+import tcod.event
+import tcod.tileset
+
+if TYPE_CHECKING:
+ from numpy.typing import NDArray
+
+FONT = "VeraMono.ttf"
+
+
+def load_ttf(path: str, size: tuple[int, int]) -> tcod.tileset.Tileset:
+ """Load a TTF file and return a tcod Tileset.
+
+ `path` is the file path to the font, this can be any font supported by the
+ FreeType library.
+
+ `size` is the (width, height) of the Tileset in pixels.
+
+ Feel free to use this function in your own code.
+ """
+ ttf = freetype.Face(path)
+ ttf.set_pixel_sizes(*size)
+
+ tileset = tcod.tileset.Tileset(*size)
+ for codepoint, glyph_index in ttf.get_chars():
+ # Add every glyph to the Tileset.
+ ttf.load_glyph(glyph_index)
+ bitmap = ttf.glyph.bitmap
+ assert bitmap.pixel_mode == freetype.FT_PIXEL_MODE_GRAY
+ bitmap_array: NDArray[np.uint8] = np.asarray(bitmap.buffer).reshape((bitmap.width, bitmap.rows), order="F")
+ if bitmap_array.size == 0:
+ continue # Skip blank glyphs.
+ output_image: NDArray[np.uint8] = np.zeros(size, dtype=np.uint8, order="F")
+ out_slice = output_image
+
+ # Adjust the position to center this glyph on the tile.
+ left = (size[0] - bitmap.width) // 2
+ top = size[1] - ttf.glyph.bitmap_top + ttf.size.descender // 64
+
+ # `max` is used because I was too lazy to properly slice the array.
+ out_slice = out_slice[max(0, left) :, max(0, top) :]
+ out_slice[: bitmap_array.shape[0], : bitmap_array.shape[1]] = bitmap_array[
+ : out_slice.shape[0], : out_slice.shape[1]
+ ]
+
+ tileset.set_tile(codepoint, output_image.transpose())
+ return tileset
+
+
+def main() -> None:
+ """True-type font example script."""
+ console = tcod.console.Console(16, 12, order="F")
+ with tcod.context.new(
+ columns=console.width,
+ rows=console.height,
+ tileset=load_ttf(FONT, (24, 24)),
+ ) as context:
+ while True:
+ console.clear()
+ # Draw checkerboard.
+ console.tiles_rgb["bg"][::2, ::2] = 0x20
+ console.tiles_rgb["bg"][1::2, 1::2] = 0x20
+ # Print ASCII characters.
+ console.tiles_rgb["ch"][:16, :6] = np.arange(0x20, 0x80).reshape(0x10, -1, order="F")
+ console.print(0, 7, "Example text.")
+ context.present(console, integer_scaling=True)
+ for event in tcod.event.wait():
+ if isinstance(event, tcod.event.Quit):
+ raise SystemExit
+ if isinstance(event, tcod.event.WindowResized):
+ # Resize the Tileset to match the new screen size.
+ context.change_tileset(
+ load_ttf(
+ path=FONT,
+ size=(
+ event.width // console.width,
+ event.height // console.height,
+ ),
+ )
+ )
+
+
+if __name__ == "__main__":
+ tcod_version = tuple(int(n) for n in tcod.__version__.split(".") if n.isdigit())
+ assert tcod_version[:2] >= (12, 1), "Must be using tcod 12.1 or later."
+ main()
diff --git a/examples/tutorial/1-Basics.py b/examples/tutorial/1-Basics.py
deleted file mode 100755
index a05d6dc0..00000000
--- a/examples/tutorial/1-Basics.py
+++ /dev/null
@@ -1,50 +0,0 @@
-#!/usr/bin/env python
-"""
- This script shows the basic use of the tdl module.
-
- The font is configured and the module is initialized, the script then goes
- into an infinite loop where it will draw "Hello World" on the screen and
- then check for an event that tells that the user has closed the window.
-
- When the window is closed the script quits out by raising a SystemExit
- exception.
-"""
-
-import tdl
-
-# Define the window size (in character tiles.) We'll pick something small.
-WIDTH, HEIGHT = 40, 30 # 320x240 when the font size is taken into account
-
-# Set the font to the example font in the tutorial folder. This font is
-# equivalent to the default font you will get if you skip this call.
-# With the characters rows first and the font size included in the filename
-# you won't have to specify any parameters other than the font file itself.
-tdl.setFont('terminal8x8_gs_ro.png')
-
-# Call tdl.init to create the root console.
-# We will call drawing operations on the returned object.
-console = tdl.init(WIDTH, HEIGHT, 'python-tdl tutorial')
-
-# Start an infinite loop. Drawing and game logic will be put in this loop.
-while True:
-
- # Reset the console to a blank slate before drawing on it.
- console.clear()
-
- # Now draw out 'Hello World' starting at an x,y of 1,2.
- console.draw_str(1, 2, 'Hello World')
-
- # Now to update the image on the window we make sure to call tdl.flush
- # in every loop.
- tdl.flush()
-
- # Handle events by iterating over the values returned by tdl.event.get
- for event in tdl.event.get():
- # Check if this is a 'QUIT' event
- if event.type == 'QUIT':
- # Later we may want to save the game or confirm if the user really
- # wants to quit but for now we break out of the loop by raising a
- # SystemExit exception.
- # The optional string parameter will be printed out on the
- # terminal after the script exits.
- raise SystemExit('The window has been closed.')
diff --git a/examples/tutorial/2-Movement.py b/examples/tutorial/2-Movement.py
deleted file mode 100755
index e970e172..00000000
--- a/examples/tutorial/2-Movement.py
+++ /dev/null
@@ -1,77 +0,0 @@
-#!/usr/bin/env python
-"""
-
-"""
-
-import tdl
-
-WIDTH, HEIGHT = 40, 30 # Defines the window size.
-
-# Create a dictionary that maps keys to vectors.
-# Names of the available keys can be found in the online documentation:
-# http://packages.python.org/tdl/tdl.event-module.html
-MOVEMENT_KEYS = {
- # standard arrow keys
- 'UP': [0, -1],
- 'DOWN': [0, 1],
- 'LEFT': [-1, 0],
- 'RIGHT': [1, 0],
-
- # diagonal keys
- # keep in mind that the keypad won't use these keys even if
- # num-lock is off
- 'HOME': [-1, -1],
- 'PAGEUP': [1, -1],
- 'PAGEDOWN': [1, 1],
- 'END': [-1, 1],
-
- # number-pad keys
- # These keys will always show as KPx regardless if num-lock
- # is on or off. Keep in mind that some keyboards and laptops
- # may be missing a keypad entirely.
- # 7 8 9
- # 4 6
- # 1 2 3
- 'KP1': [-1, 1],
- 'KP2': [0, 1],
- 'KP3': [1, 1],
- 'KP4': [-1, 0],
- 'KP6': [1, 0],
- 'KP7': [-1, -1],
- 'KP8': [0, -1],
- 'KP9': [1, -1],
- }
-
-
-tdl.setFont('terminal8x8_gs_ro.png') # Configure the font.
-
-# Create the root console.
-console = tdl.init(WIDTH, HEIGHT, 'python-tdl tutorial')
-
-# player coordinates
-playerX, playerY = 1, 2
-
-while True: # Continue in an infinite game loop.
-
- console.clear() # Blank the console.
-
- # Using "(x, y) in console" we can quickly check if a position is inside of
- # a console. And skip a draw operation that would otherwise fail.
- if (playerX, playerY) in console:
- console.draw_char(playerX, playerY, '@')
-
- tdl.flush() # Update the window.
-
- for event in tdl.event.get(): # Iterate over recent events.
- if event.type == 'KEYDOWN':
- # We mix special keys with normal characters so we use keychar.
- if event.keychar.upper() in MOVEMENT_KEYS:
- # Get the vector and unpack it into these two variables.
- keyX, keyY = MOVEMENT_KEYS[event.keychar.upper()]
- # Then we add the vector to the current player position.
- playerX += keyX
- playerY += keyY
-
- if event.type == 'QUIT':
- # Halt the script using SystemExit
- raise SystemExit('The window has been closed.')
diff --git a/fonts/README.md b/fonts/README.md
new file mode 100644
index 00000000..94500021
--- /dev/null
+++ b/fonts/README.md
@@ -0,0 +1,2 @@
+The regular libtcod fonts are found at:
+https://github.com/libtcod/libtcod/tree/master/data/fonts
diff --git a/fonts/X11/10x20.png b/fonts/X11/10x20.png
deleted file mode 100755
index 6e622751..00000000
Binary files a/fonts/X11/10x20.png and /dev/null differ
diff --git a/fonts/X11/4x6.png b/fonts/X11/4x6.png
deleted file mode 100755
index 8ef8cc79..00000000
Binary files a/fonts/X11/4x6.png and /dev/null differ
diff --git a/fonts/X11/5x7.png b/fonts/X11/5x7.png
deleted file mode 100755
index 09bd1c61..00000000
Binary files a/fonts/X11/5x7.png and /dev/null differ
diff --git a/fonts/X11/5x8.png b/fonts/X11/5x8.png
deleted file mode 100755
index e31c12f0..00000000
Binary files a/fonts/X11/5x8.png and /dev/null differ
diff --git a/fonts/X11/6x10.png b/fonts/X11/6x10.png
deleted file mode 100755
index e6aaa0e9..00000000
Binary files a/fonts/X11/6x10.png and /dev/null differ
diff --git a/fonts/X11/6x12.png b/fonts/X11/6x12.png
deleted file mode 100755
index 317574ba..00000000
Binary files a/fonts/X11/6x12.png and /dev/null differ
diff --git a/fonts/X11/6x13.png b/fonts/X11/6x13.png
deleted file mode 100755
index 1782db67..00000000
Binary files a/fonts/X11/6x13.png and /dev/null differ
diff --git a/fonts/X11/6x13B.png b/fonts/X11/6x13B.png
deleted file mode 100755
index 33fb6d80..00000000
Binary files a/fonts/X11/6x13B.png and /dev/null differ
diff --git a/fonts/X11/6x13O.png b/fonts/X11/6x13O.png
deleted file mode 100755
index ec338af7..00000000
Binary files a/fonts/X11/6x13O.png and /dev/null differ
diff --git a/fonts/X11/6x9.png b/fonts/X11/6x9.png
deleted file mode 100755
index 2be02ae3..00000000
Binary files a/fonts/X11/6x9.png and /dev/null differ
diff --git a/fonts/X11/7x13.png b/fonts/X11/7x13.png
deleted file mode 100755
index 86c0668b..00000000
Binary files a/fonts/X11/7x13.png and /dev/null differ
diff --git a/fonts/X11/7x13B.png b/fonts/X11/7x13B.png
deleted file mode 100755
index b152ce47..00000000
Binary files a/fonts/X11/7x13B.png and /dev/null differ
diff --git a/fonts/X11/7x13O.png b/fonts/X11/7x13O.png
deleted file mode 100755
index d9508de8..00000000
Binary files a/fonts/X11/7x13O.png and /dev/null differ
diff --git a/fonts/X11/7x14.png b/fonts/X11/7x14.png
deleted file mode 100755
index dd0a2a39..00000000
Binary files a/fonts/X11/7x14.png and /dev/null differ
diff --git a/fonts/X11/7x14B.png b/fonts/X11/7x14B.png
deleted file mode 100755
index 8ad21b3c..00000000
Binary files a/fonts/X11/7x14B.png and /dev/null differ
diff --git a/fonts/X11/8x13.png b/fonts/X11/8x13.png
deleted file mode 100755
index f894f1ed..00000000
Binary files a/fonts/X11/8x13.png and /dev/null differ
diff --git a/fonts/X11/8x13B.png b/fonts/X11/8x13B.png
deleted file mode 100755
index af13a50d..00000000
Binary files a/fonts/X11/8x13B.png and /dev/null differ
diff --git a/fonts/X11/8x13O.png b/fonts/X11/8x13O.png
deleted file mode 100755
index d609602d..00000000
Binary files a/fonts/X11/8x13O.png and /dev/null differ
diff --git a/fonts/X11/9x15.png b/fonts/X11/9x15.png
deleted file mode 100755
index 73a082fa..00000000
Binary files a/fonts/X11/9x15.png and /dev/null differ
diff --git a/fonts/X11/9x15B.png b/fonts/X11/9x15B.png
deleted file mode 100755
index 451ac491..00000000
Binary files a/fonts/X11/9x15B.png and /dev/null differ
diff --git a/fonts/X11/9x18.png b/fonts/X11/9x18.png
deleted file mode 100755
index 4c27276e..00000000
Binary files a/fonts/X11/9x18.png and /dev/null differ
diff --git a/fonts/X11/9x18B.png b/fonts/X11/9x18B.png
deleted file mode 100755
index 10965ddd..00000000
Binary files a/fonts/X11/9x18B.png and /dev/null differ
diff --git a/fonts/X11/AUTHORS.txt b/fonts/X11/AUTHORS.txt
deleted file mode 100755
index 71316c53..00000000
--- a/fonts/X11/AUTHORS.txt
+++ /dev/null
@@ -1,42 +0,0 @@
-The identity of the designer(s) of the original ASCII repertoire and
-the later Latin-1 extension of the misc-fixed BDF fonts appears to
-have been lost in history. (It is likely that many of these 7-bit
-ASCII fonts were created in the early or mid 1980s as part of MIT's
-Project Athena, or at its industrial partner, DEC.)
-
-In 1997, Markus Kuhn at the University of Cambridge Computer
-Laboratory initiated and headed a project to extend the misc-fixed BDF
-fonts to as large a subset of Unicode/ISO 10646 as is feasible for
-each of the available font sizes, as part of a wider effort to
-encourage users of POSIX systems to migrate from ISO 8859 to UTF-8.
-
-Robert Brady and Birger Langkjer
- contributed thousands of glyphs and made
-very substantial contributions and improvements on almost all fonts.
-Constantine Stathopoulos contributed all the
-Greek characters. Markus Kuhn did
-most 6x13 glyphs and the italic fonts and provided many more glyphs,
-coordination, and quality assurance for the other fonts. Mark Leisher
- contributed to 6x13 Armenian, Georgian, the
-first version of Latin Extended Block A and some Cyrillic. Serge V.
-Vakulenko donated the original Cyrillic glyphs
-from his 6x13 ISO 8859-5 font. Nozomi Ytow
-contributed 6x13 halfwidth Katakana. Henning Brunzel
- contributed glyphs to 10x20.bdf. Theppitak
-Karoonboonyanan contributed Thai for 7x13,
-7x13B, 7x13O, 7x14, 7x14B, 8x13, 8x13B, 8x13O, 9x15, 9x15B, and 10x20.
-Karl Koehler contributed Arabic to 9x15,
-9x15B, and 10x20 and Roozbeh Pournader and
-Behdad Esfahbod revised and extended Arabic in 10x20. Raphael Finkel
- revised Hebrew/Yiddish in 10x20. Jungshik Shin
- prepared 18x18ko.bdf. Won-kyu Park
- prepared the Hangul glyphs used in 12x13ja.
-Janne V. Kujala contributed 4x6. Daniel Yacob
- revised some Ethiopic glyphs. Ted Zlatanov
- did some 7x14. Mikael Öhman
-worked on 6x12.
-
-The fonts are still maintained by Markus Kuhn and the original
-distribution can be found at:
-
- http://www.cl.cam.ac.uk/~mgk25/ucs-fonts.html
diff --git a/fonts/X11/LICENCES.txt b/fonts/X11/LICENCES.txt
deleted file mode 100755
index 0ddf13db..00000000
--- a/fonts/X11/LICENCES.txt
+++ /dev/null
@@ -1,42 +0,0 @@
-All the fonts here are public domain with the exceptions of clR6x12.png and helvR12_14x15.png
-
-== clR6x12.png ==
- Copyright 1989 Dale Schumacher, dal@syntel.mn.org
- 399 Beacon Ave.
- St. Paul, MN 55104-3527
-
- Permission to use, copy, modify, and distribute this software and
- its documentation for any purpose and without fee is hereby
- granted, provided that the above copyright notice appear in all
- copies and that both that copyright notice and this permission
- notice appear in supporting documentation, and that the name of
- Dale Schumacher not be used in advertising or publicity pertaining to
- distribution of the software without specific, written prior
- permission. Dale Schumacher makes no representations about the
- suitability of this software for any purpose. It is provided "as
- is" without express or implied warranty.
-
-
- Modified by Robert Brady,
-
-
-== helvR12_14x15.png ==
- Copyright 1984-1989, 1994 Adobe Systems Incorporated.
- Copyright 1988, 1994 Digital Equipment Corporation.
-
- Adobe is a trademark of Adobe Systems Incorporated which may be
- registered in certain jurisdictions.
- Permission to use these trademarks is hereby granted only in
- association with the images described in this file.
-
- Permission to use, copy, modify, distribute and sell this software
- and its documentation for any purpose and without fee is hereby
- granted, provided that the above copyright notices appear in all
- copies and that both those copyright notices and this permission
- notice appear in supporting documentation, and that the names of
- Adobe Systems and Digital Equipment Corporation not be used in
- advertising or publicity pertaining to distribution of the software
- without specific, written prior permission. Adobe Systems and
- Digital Equipment Corporation make no representations about the
- suitability of this software for any purpose. It is provided "as
- is" without express or implied warranty.
diff --git a/fonts/X11/README-TDL.txt b/fonts/X11/README-TDL.txt
deleted file mode 100755
index 692a4399..00000000
--- a/fonts/X11/README-TDL.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-To use these unicode fonts with python-tdl you should call tdl.setFont with only the filename leaving all other parameters at the defaults. Remember to do this before the call to tdl.init. If using libtcod then note that each tileset has 64 columns and 1024 rows.
-
-unifont_16x16.png is the recommended font with the best unicode support.
-
-Unicode support varies across the rest of the fonts. This is detailed in README-X11.txt with some fonts meeting better support targets than others.
-
-After unifont the fonts with the best unicode support are:
- 6x13.png 8x13.png 9x15.png 9x18.png 10x20.png
-
-Also note that libtcod will be put under stress when using an extra large tileset such as 10x20.png; When decompressed the 10x20.png image will take around 40MB of video memory. Modern graphic cards can handle this can get better compatibility by using the SDL rasterizer instead of OPENGL.
diff --git a/fonts/X11/README-X11.txt b/fonts/X11/README-X11.txt
deleted file mode 100755
index d4f407ef..00000000
--- a/fonts/X11/README-X11.txt
+++ /dev/null
@@ -1,369 +0,0 @@
-
-Unicode versions of the X11 "misc-fixed-*" fonts
-------------------------------------------------
-
-Markus Kuhn -- 2008-04-21
-
-
-This package contains the X Window System bitmap fonts
-
- -Misc-Fixed-*-*-*--*-*-*-*-C-*-ISO10646-1
-
-These are Unicode (ISO 10646-1) extensions of the classic ISO 8859-1
-X11 terminal fonts that are widely used with many X11 applications
-such as xterm, emacs, etc.
-
-COVERAGE
---------
-
-None of these fonts covers Unicode completely. Complete coverage
-simply would not make much sense here. Unicode 5.1 contains over
-100000 characters, and the large majority of them are
-Chinese/Japanese/Korean Han ideographs (~70000) and Korean Hangul
-Syllables (~11000) that cannot adequately be displayed in the small
-pixel sizes of the fixed fonts. Similarly, Arabic characters are
-difficult to fit nicely together with European characters into the
-fixed character cells and X11 lacks the ligature substitution
-mechanisms required for using Indic scripts.
-
-Therefore these fonts primarily attempt to cover Unicode subsets that
-fit together with European scripts. This includes the Latin, Greek,
-Cyrillic, Armenian, Georgian, and Hebrew scripts, plus a lot of
-linguistic, technical and mathematical symbols. Some of the fixed
-fonts now also cover Arabic, Thai, Ethiopian, halfwidth Katakana, and
-some other non-European scripts.
-
-We have defined 3 different target character repertoires (ISO 10646-1
-subsets) that the various fonts were checked against for minimal
-guaranteed coverage:
-
- TARGET1 617 characters
- Covers all characters of ISO 8859 part 1-5,7-10,13-16,
- CEN MES-1, ISO 6937, Microsoft CP1251/CP1252, DEC VT100
- graphics symbols, and the replacement and default
- character. It is intended for small bold, italic, and
- proportional fonts, for which adding block graphics
- characters would make little sense. This repertoire
- covers the following ISO 10646-1:2000 collections
- completely: 1-3, 8, 12.
-
- TARGET2 886 characters
- Adds to TARGET1 the characters of the Adobe/Microsoft
- Windows Glyph List 4 (WGL4), plus a selected set of
- mathematical characters (covering most of ISO 31-11
- high-school level math symbols) and some combining
- characters. It is intended to be covered by all normal
- "fixed" fonts and covers all European IBM, Microsoft, and
- Macintosh character sets. This repertoire covers the
- following ISO 10646-1:2000 (including Amd 1:2002)
- collections completely: 1-3, 8, 12, 33, 45.
-
- TARGET3 3282 characters
-
- Adds to TARGET2 all characters of all European scripts
- (Latin, Greek, Cyrillic, Armenian, Georgian), all
- phonetic alphabet symbols, many mathematical symbols
- (including all those available in LaTeX), all typographic
- punctuation, all box-drawing characters, control code
- pictures, graphical shapes and some more that you would
- expect in a very comprehensive Unicode 4.0 font for
- European users. It is intended for some of the more
- useful and more widely used normal "fixed" fonts. This
- repertoire is, with two exceptions, a superset of all
- graphical characters in CEN MES-3A and covers the
- following ISO 10646-1:2000 (including Amd 1:2002)
- collections completely: 1-12, 27, 30-31, 32 (only
- graphical characters), 33-42, 44-47, 63, 65, 70 (only
- graphical characters).
-
- [The two MES-3A characters deliberately omitted are the
- angle bracket characters U+2329 and U+232A. ISO and CEN
- appears to have included these into collection 40 and
- MES-3A by accident, because there they are the only
- characters in the Unicode EastAsianWidth "wide" class.]
-
-CURRENT STATUS:
-
- 6x13.bdf 8x13.bdf 9x15.bdf 9x18.bdf 10x20.bdf:
-
- Complete (TARGET3 reached and checked)
-
- 5x7.bdf 5x8.bdf 6x9.bdf 6x10.bdf 6x12.bdf 7x13.bdf 7x14.bdf clR6x12.bdf:
-
- Complete (TARGET2 reached and checked)
-
- 6x13B.bdf 7x13B.bdf 7x14B.bdf 8x13B.bdf 9x15B.bdf 9x18B.bdf:
-
- Complete (TARGET1 reached and checked)
-
- 6x13O.bdf 7x13O.bdf 8x13O.bdf
-
- Complete (TARGET1 minus Hebrew and block graphics)
-
-[None of the above fonts contains any character that has in Unicode
-the East Asian Width Property "W" or "F" assigned. This way, the
-desired combination of "half-width" and "full-width" glyphs can be
-achieved easily. Most font mechanisms display a character that is not
-covered in a font by using a glyph from another font that appears
-later in a priority list, which can be arranged to be a "full-width"
-font.]
-
-The supplement package
-
- http://www.cl.cam.ac.uk/~mgk25/download/ucs-fonts-asian.tar.gz
-
-contains the following additional square fonts with Han characters for
-East Asian users:
-
- 12x13ja.bdf:
-
- Covers TARGET2, JIS X 0208, Hangul, and a few more. This font is
- primarily intended to provide Japanese full-width Hiragana,
- Katakana, and Kanji for applications that take the remaining
- ("halfwidth") characters from 6x13.bdf. The Greek lowercase
- characters in it are still a bit ugly and will need some work.
-
- 18x18ja.bdf:
-
- Covers all JIS X 0208, JIS X 0212, GB 2312-80, KS X 1001:1992,
- ISO 8859-1,2,3,4,5,7,9,10,15, CP437, CP850 and CP1252 characters,
- plus a few more, where priority was given to Japanese han style
- variants. This font should have everything needed to cover the
- full ISO-2022-JP-2 (RFC 1554) repertoire. This font is primarily
- intended to provide Japanese full-width Hiragana, Katakana, and
- Kanji for applications that take the remaining ("halfwidth")
- characters from 9x18.bdf.
-
- 18x18ko.bdf:
-
- Covers the same repertoire as 18x18ja plus full coverage of all
- Hangul syllables and priority was given to Hanja glyphs in the
- unified CJK area as they are used for writing Korean.
-
-The 9x18 and 6x12 fonts are recommended for use with overstriking
-combining characters.
-
-Bug reports, suggestions for improvement, and especially contributed
-extensions are very welcome!
-
-INSTALLATION
-------------
-
-You install the fonts under Unix roughly like this (details depending
-on your system of course):
-
-System-wide installation (root access required):
-
- cd submission/
- make
- su
- mv -b *.pcf.gz /usr/lib/X11/fonts/misc/
- cd /usr/lib/X11/fonts/misc/
- mkfontdir
- xset fp rehash
-
-Alternative: Installation in your private user directory:
-
- cd submission/
- make
- mkdir -p ~/local/lib/X11/fonts/
- mv *.pcf.gz ~/local/lib/X11/fonts/
- cd ~/local/lib/X11/fonts/
- mkfontdir
- xset +fp ~/local/lib/X11/fonts (put this last line also in ~/.xinitrc)
-
-Now you can have a look at say the 6x13 font with the command
-
- xfd -fn '-misc-fixed-medium-r-semicondensed--13-120-75-75-c-60-iso10646-1'
-
-If you want to have short names for the Unicode fonts, you can also
-append the fonts.alias file to that in the directory where you install
-the fonts, call "mkfontdir" and "xset fp rehash" again, and then you
-can also write
-
- xfd -fn 6x13U
-
-Note: If you use an old version of xfontsel, you might notice that it
-treats every font that contains characters >0x00ff as a Japanese JIS
-font and therefore selects inappropriate sample characters for display
-of ISO 10646-1 fonts. An updated xfontsel version with this bug fixed
-comes with XFree86 4.0 / X11R6.8 or newer.
-
-If you use the Exceed X server on Microsoft Windows, then you will
-have to convert the BDF files into Microsoft FON files using the
-"Compile Fonts" function of Exceed xconfig. See the file exceed.txt
-for more information.
-
-There is one significant efficiency problem that X11R6 has with the
-sparsely populated ISO10646-1 fonts. X11 transmits and allocates 12
-bytes with the XFontStruct data structure for the difference between
-the lowest and the highest code value found in a font, no matter
-whether the code positions in between are used for characters or not.
-Even a tiny font that contains only two glyphs at positions 0x0000 and
-0xfffd causes 12 bytes * 65534 codes = 786 kbytes to be requested and
-stored by the client. Since all the ISO10646-1 BDF files provided in
-this package contain characters in the U+00xx (ASCII) and U+ffxx
-(ligatures, etc.) range, all of them would result in 786 kbyte large
-XCharStruct arrays in the per_char array of the corresponding
-XFontStruct (even for CharCell fonts!) when loaded by an X client.
-Until this problem is fixed by extending the X11 font protocol and
-implementation, non-CJK ISO10646-1 fonts that lack the (anyway not
-very interesting) characters above U+31FF seem to be the best
-compromise. The bdftruncate.pl program in this package can be used to
-deactivate any glyphs above a threshold code value in BDF files. This
-way, we get relatively memory-economic ISO10646-1 fonts that cause
-"only" 150 kbyte large XCharStruct arrays to be allocated. The
-deactivated glyphs are still present in the BDF files, but with an
-encoding value of -1 that causes them to be ignored.
-
-The ISO10646-1 fonts can not only be used directly by Unicode aware
-software, they can also be used to create any 8-bit font. The
-ucs2any.pl Perl script converts a ISO10646-1 BDF font into a BDF font
-file with some different encoding. For instance the command
-
- perl ucs2any.pl 6x13.bdf MAPPINGS/8859-7.TXT ISO8859-7
-
-will generate the file 6x13-ISO8859-7.bdf according to the 8859-7.TXT
-Latin/Greek mapping table, which available from
-. [The shell script
-./map_fonts automatically generates a subdirectory derived-fonts/ with
-many *.bdf and *.pcf.gz 8-bit versions of all the
--misc-fixed-*-iso10646-1 fonts.]
-
-When you do a "make" in the submission/ subdirectory as suggested in
-the installation instructions above, this will generate exactly the
-set of fonts that have been submitted to the XFree86 project for
-inclusion into XFree86 4.0. These consists of all the ISO10646-1 fonts
-processed with "bdftruncate.pl U+3200" plus a selected set of derived
-8-bit fonts generated with ucs2any.pl.
-
-Every font comes with a *.repertoire-utf8 file that lists all the
-characters in this font.
-
-
-CONTRIBUTING
-------------
-
-If you want to help me in extending or improving the fonts, or if you
-want to start your own ISO 10646-1 font project, you will have to edit
-BDF font files. This is most comfortably done with the gbdfed font
-editor (version 1.3 or higher), which is available from
-
- http://crl.nmsu.edu/~mleisher/gbdfed.html
-
-Once you are familiar with gbdfed, you will notice that it is no
-problem to design up to 100 nice characters per hour (even more if
-only placing accents is involved).
-
-Information about other X11 font tools and Unicode fonts for X11 in
-general can be found on
-
- http://www.cl.cam.ac.uk/~mgk25/ucs-fonts.html
-
-The latest version of this package is available from
-
- http://www.cl.cam.ac.uk/~mgk25/download/ucs-fonts.tar.gz
-
-If you want to contribute, then get the very latest version of this
-package, check which glyphs are still missing or inappropriate for
-your needs, and send me whatever you had the time to add and fix. Just
-email me the extended BDF-files back, or even better, send me a patch
-file of what you changed. The best way of preparing a patch file is
-
- ./touch_id newfile.bdf
- diff -d -u -F STARTCHAR oldfile.bdf newfile.bdf >file.diff
-
-which ensures that the patch file preserves information about which
-exact version you worked on and what character each "hunk" changes.
-
-I will try to update this packet on a daily basis. By sending me
-extensions to these fonts, you agree that the resulting improved font
-files will remain in the public domain for everyone's free use. Always
-make sure to load the very latest version of the package immediately
-before your start, and send me your results as soon as you are done,
-in order to avoid revision overlaps with other contributors.
-
-Please try to be careful with the glyphs you generate:
-
- - Always look first at existing similar characters in order to
- preserve a consistent look and feel for the entire font and
- within the font family. For block graphics characters and geometric
- symbols, take care of correct alignment.
-
- - Read issues.txt, which contains some design hints for certain
- characters.
-
- - All characters of CharCell (C) fonts must strictly fit into
- the pixel matrix and absolutely no out-of-box ink is allowed.
-
- - The character cells will be displayed directly next to each other,
- without any additional pixels in between. Therefore, always make
- sure that at least the rightmost pixel column remains white, as
- otherwise letters will stick together, except of course for
- characters -- like Arabic or block graphics -- that are supposed to
- stick together.
-
- - Place accents as low as possible on the Latin characters.
-
- - Try to keep the shape of accents consistent among each other and
- with the combining characters in the U+03xx range.
-
- - Use gbdfed only to edit the BDF file directly and do not import
- the font that you want to edit from the X server. Use gbdfed 1.3
- or higher.
-
- - The glyph names should be the Adobe names for Unicode characters
- defined at
-
- http://www.adobe.com/devnet/opentype/archives/glyph.html
-
- which gbdfed can set automatically. To make the Edit/Rename Glyphs/
- Adobe Names function work, you have to download the file
-
- http://www.adobe.com/devnet/opentype/archives/glyphlist.txt
-
- and configure its location either in Edit/Preferences/Editing Options/
- Adobe Glyph List, or as "adobe_name_file" in "~/.gbdfed".
-
- - Be careful to not change the FONTBOUNDINGBOX box accidentally in
- a patch.
-
-You should have a copy of the ISO 10646 standard
-
- ISO/IEC 10646:2003, Information technology -- Universal
- Multiple-Octet Coded Character Set (UCS),
- International Organization for Standardization, Geneva, 2003.
- http://standards.iso.org/ittf/PubliclyAvailableStandards/
-
-and/or the Unicode 5.0 book:
-
- The Unicode Consortium: The Unicode Standard, Version 5.0,
- Reading, MA, Addison-Wesley, 2006,
- ISBN 9780321480910.
- http://www.amazon.com/exec/obidos/ASIN/0321480910/mgk25
-
-All these fonts are from time to time resubmitted to the X.Org
-project, XFree86 (they have been in there since XFree86 4.0), and to
-other X server developers for inclusion into their normal X11
-distributions.
-
-Starting with XFree86 4.0, xterm has included UTF-8 support. This
-version is also available from
-
- http://dickey.his.com/xterm/xterm.html
-
-Please make the developer of your favourite software aware of the
-UTF-8 definition in RFC 2279 and of the existence of this font
-collection. For more information on how to use UTF-8, please check out
-
- http://www.cl.cam.ac.uk/~mgk25/unicode.html
- ftp://ftp.ilog.fr/pub/Users/haible/utf8/Unicode-HOWTO.html
-
-where you will also find information on joining the
-linux-utf8@nl.linux.org mailing list.
-
-A number of UTF-8 example text files can be found in the examples/
-subdirectory or on
-
- http://www.cl.cam.ac.uk/~mgk25/ucs/examples/
-
diff --git a/fonts/X11/README.md b/fonts/X11/README.md
new file mode 100644
index 00000000..52921145
--- /dev/null
+++ b/fonts/X11/README.md
@@ -0,0 +1,14 @@
+To use these BDF Unicode fonts with python-tcod you should use the
+`tcod.tileset` module:
+
+```python
+import tcod
+import tcod.tileset
+
+# Load the BDF file as the active tileset.
+tcod.tileset.set_default(tcod.tileset.load_bdf("file_to_load.bdf"))
+
+# Start python-tcod normally.
+with libtcodpy.console_init_root(...) as root_console:
+ ...
+```
diff --git a/fonts/X11/TARGET1.txt b/fonts/X11/TARGET1.txt
deleted file mode 100755
index da42e224..00000000
--- a/fonts/X11/TARGET1.txt
+++ /dev/null
@@ -1,23 +0,0 @@
-# Target repertoire for bold/italic fixed character sets
-# (CP1252, MES-1, ISO 8859-1,2,3,4,5,7,8,9,10,14,15, etc.)
-
-# Plane 00
-# Rows Positions (Cells)
-
- 00 20-7E A0-FF
- 01 00-7F 8F 92
- 02 18-1B 59 C6-C7 D8-DD
- 03 74-75 7A 7E 84-8A 8C 8E-A1 A3-CE
- 04 01-0C 0E-4F 51-5C 5E-5F 90-91
- 05 D0-EA
- 1E 02-03 0A-0B 1E-1F 40-41 56-57 60-61 6A-6B 80-85 F2-F3
- 20 10-22 26 30 39-3A AC AF
- 21 16 22 26 5B-5E 90-93
- 22 60 64-65
- 23 BA-BD
- 24 09-0D 24
- 25 00 02 0C 10 14 18 1C 24 2C 34 3C 92 C6
- 26 6A
- FF FD
-
-# Number of characters in above table: 617
diff --git a/fonts/X11/TARGET2.txt b/fonts/X11/TARGET2.txt
deleted file mode 100755
index 6c1de726..00000000
--- a/fonts/X11/TARGET2.txt
+++ /dev/null
@@ -1,27 +0,0 @@
-# Target repertoire for normal fixed character sets
-# (TARGET1, WGL4, some math, some combining)
-
-# Plane 00
-# Rows Positions (Cells)
-
- 00 20-7E A0-FF
- 01 00-7F 8F 92 FA-FF
- 02 18-1B 59 C6-C7 C9 D8-DD
- 03 00-11 74-75 7A 7E 84-8A 8C 8E-A1 A3-CE D1 D5-D6 F1
- 04 01-0C 0E-4F 51-5C 5E-5F 90-91
- 05 D0-EA
- 1E 02-03 0A-0B 1E-1F 40-41 56-57 60-61 6A-6B 80-85 F2-F3
- 20 10-22 26 30 32-34 39-3A 3C 3E 44 70-71 74-8E A3-A4 A7 AC AF D0-D7
- 21 02 05 13 15-16 1A 1D 22 24 26 2E 5B-5E 90-95 A4-A8 D0-D5
- 22 00-09 0B-0C 0F 11-13 15 18-1A 1D-1F 21 24-2B 2E 3C 43 45 48-49
- 22 59 5F-62 64-65 6A-6B 82-8B 95 97 A4-A7 C2-C3 C5
- 23 00 02 08-0B 10 20-21 BA-BD
- 24 09-0D 24
- 25 00 02 0C 10 14 18 1C 24 2C 34 3C 4C-73 80-A1 AA-AC B2-B3 BA BC
- 25 C4 C6 CA-CB CF D8-D9 E6
- 26 3A-3C 40 42 60 63 65-66 6A-6B
- 27 E8-E9
- FB 01-02
- FF FD
-
-# Number of characters in above table: 886
diff --git a/fonts/X11/TARGET3.txt b/fonts/X11/TARGET3.txt
deleted file mode 100755
index 92e74f26..00000000
--- a/fonts/X11/TARGET3.txt
+++ /dev/null
@@ -1,30 +0,0 @@
-# Target repertoire for comprehensive fixed character sets
-
-# Plane 00
-# Rows Positions (Cells)
-
- 00 20-7E A0-FF
- 01 00-FF
- 02 00-FF
- 03 00-77 7A-7E 84-8A 8C 8E-A1 A3-FF
- 04 00-FF
- 05 00-23 31-56 59-5F 61-87 89-8A B0-C7 D0-EA F0-F4
- 10 D0-FC
- 1E 00-FF
- 1F 00-15 18-1D 20-45 48-4D 50-57 59 5B 5D 5F-7D 80-B4 B6-C4 C6-D3
- 1F D6-DB DD-EF F2-F4 F6-FE
- 20 00-0A 10-27 2F-64 70-71 74-8E 90-94 A0-B5 D0-F0
- 21 00-4F 53-88 90-FF
- 22 00-FF
- 23 00-28 2B-E7
- 24 00-26 40-4A
- 25 00-FF
- 26 00-9D A0-BC C0-C3
- 27 E6-EB F5-FF
- 2A 00-06 1D 3F
- 30 3F
- FB 00-06 13-17 1D-36 38-3C 3E 40-41 43-44 46-4F
- FE 20-26
- FF 61-9F FD
-
-# Number of characters in above table: 3480
diff --git a/fonts/X11/bdf/bdf2png.py b/fonts/X11/bdf/bdf2png.py
deleted file mode 100755
index 2d1902bb..00000000
--- a/fonts/X11/bdf/bdf2png.py
+++ /dev/null
@@ -1,184 +0,0 @@
-#!/usr/bin/python
-"""
- This script converts bdf files into png Unicode tilesets for use with
- programs such as libtcod or python-tdl.
-
- Requires scipy, numpy, and PIL. Run from the command line.
-"""
-
-from __future__ import division
-
-import sys
-import os
-
-import re
-import math
-import itertools
-import glob
-import argparse
-import multiprocessing
-
-import scipy.ndimage
-import scipy.misc
-try:
- scipy.misc.imsave
-except AttributeError:
- raise SystemExit('Must have python PIL installed')
-import numpy
-
-class Glyph:
-
- def __init__(self, data, bbox):
- "Make a new glyph with the data between STARTCHAR and ENDCHAR"
- if verbose:
- print(data)
- # get character index
- self.encoding = int(re.search('ENCODING ([0-9-]+)', data).groups()[0])
- if self.encoding < 0:
- # I ran into a -1 encoding once, not sure what to do with it
- self.encoding += 65536 # just put it at the end I guess
-
- # get local bbox
- match = re.search('\nBBX ([0-9-]+) ([0-9-]+) ([0-9-]+) ([0-9-]+)', data)
- if match:
- gbbox = [int(i) for i in match.groups()]
- else:
- gbbox = bbox
- self.font_bbox = bbox
- self.bbox = gbbox
- self.width, self.height = self.bbox[:2]
-
- # get bitmap
- match = re.search('\nBITMAP *\n([0-9A-F\n]*)', data, re.IGNORECASE)
- self.bitmap = numpy.empty([self.height, self.width], bool)
- if self.height == self.width == 0:
- return
- for y,hexcode in enumerate(match.groups()[0].split('\n')):
- for x, bit in self.parseBits(hexcode, self.width):
- self.bitmap[y,x] = bit
-
- self.sizeAdjust()
-
- def sizeAdjust(self):
- """If the glyph is bigger than the font (because the user set it smaller)
- this should be able to shorten the size"""
- font_width, font_height = self.font_bbox[:2]
- self.width = min(self.width, font_width)
- self.height = min(self.height, font_height)
- self.bbox[:2] = self.width, self.height
-
- self.crop()
-
- def crop(self):
- self.bitmap = self.bitmap[-self.height:, :self.width]
-
- def zoom(self):
- h, w = self.bitmap.shape
- zoom = [self.height / h, self.width / w]
- self.bitmap = scipy.ndimage.zoom(self.bitmap, zoom, output=float)
-
- def blit(self, image, x, y):
- """blit to the image array"""
- # adjust the position with the local bbox
- x += self.font_bbox[2] - self.bbox[2]
- y += self.font_bbox[3] - self.bbox[3]
- x += self.font_bbox[0] - self.bbox[0]
- y += self.font_bbox[1] - self.bbox[1]
- image[y:y+self.height, x:x+self.width] = self.bitmap * 255
-
- def parseBits(self, hexcode, width):
- """enumerate over bits in a line of data"""
- bitarray = []
- for byte in hexcode[::-1]:
- bits = int(byte, 16)
- for x in range(4):
- bitarray.append(bool((2 ** x) & bits))
- bitarray = bitarray[::-1]
- return enumerate(bitarray[:width])
-
-def glyphThreadInit(verbose_):
- # pass verbose to threads
- global verbose
- verbose = verbose_
-
-def glyphThread(args):
- # split args to Glyph
- return Glyph(*args)
-
-def convert(filename):
- print('Converting %s...' % filename)
- bdf = open(filename, 'r').read()
-
- # name the output file
- outfile = os.path.basename(filename)
- if '.' in outfile:
- outfile = outfile.rsplit('.', 1)[0] + '.png'
-
- # print out comments
- for comment in re.findall('\nCOMMENT (.*)', bdf):
- print(comment)
- # and copyright
- match = re.search('\n(COPYRIGHT ".*")', bdf)
- if match:
- print(match.groups()[0])
-
- # get bounding box
- match = re.search('\nFONTBOUNDINGBOX ([0-9-]+) ([0-9-]+) ([0-9-]+) ([0-9-]+)', bdf)
- bbox = [int(i) for i in match.groups()]
- if args.font_size:
- bbox = args.font_size + bbox[2:]
- fontWidth, fontHeight, fontOffsetX, fontOffsetY = bbox
- print('Font size: %ix%i' % (fontWidth, fontHeight))
- print('Font offset: %i,%i' % (fontOffsetX, fontOffsetY))
-
- # generate glyphs
- pool = multiprocessing.Pool(args.threads, glyphThreadInit, (verbose,))
- glyphData = re.findall('\nSTARTCHAR [^\n]*\n(.*?)\nENDCHAR', bdf, re.DOTALL)
- glyphTotal = len(glyphData)
- print('Found %i glyphs' % glyphTotal)
- sys.stdout.write('please wait...')
- glyphs = pool.map(glyphThread, zip(glyphData, [bbox] * glyphTotal))
-
- print 'done!'
-
- # start rendering to an array
- imgColumns = args.columns
- imgRows = 65536 // imgColumns
- print('Generating a %ix%i tileset' % (imgColumns, imgRows))
- imgWidth = imgColumns * fontWidth
- imgHeight = imgRows * fontHeight
- image = numpy.zeros([imgHeight, imgWidth], 'u1')
- for glyph in glyphs:
- y, x = divmod(glyph.encoding, imgColumns)
- x, y = x * fontWidth, y * fontHeight
- glyph.blit(image, x, y)
-
- # save as png
-
- #rgba = numpy.empty([imgHeight, imgWidth, 4])
- #rgba[...,...,0] = image
- #rgba[...,...,1] = image
- #rgba[...,...,2] = image
- #rgba[...,...,:3] = 255
- #rgba[...,...,3] = image
- #scipy.misc.imsave(outfile, rgba)
-
- scipy.misc.imsave(outfile, image)
- print('Saved as %s' % outfile)
-
-parser = argparse.ArgumentParser(description='Convert *.bdf fonts to *.png tilesets')
-parser.add_argument('-v', action='store_true', help='Print debug infromation.')
-parser.add_argument('-c', '--columns', nargs='?', type=int, default=64, help='Number of characters per row.')
-parser.add_argument('-t', '--threads', nargs='?', type=int, default=None, help='Number of threads to run. Auto-detects by default.')
-parser.add_argument('-s', '--font-size', nargs=2, metavar=('width', 'height'), type=int, default=None, help='Scale to this font size.')
-parser.add_argument('file', nargs='+', help='*.bdf files to convert')
-
-verbose = False
-
-if __name__ == '__main__':
- args = parser.parse_args()
- print(args)
- verbose = args.v
- for globs in (glob.iglob(arg) for arg in args.file):
- for filename in globs:
- convert(filename)
diff --git a/fonts/X11/clR6x12.png b/fonts/X11/clR6x12.png
deleted file mode 100755
index 8ef19c5f..00000000
Binary files a/fonts/X11/clR6x12.png and /dev/null differ
diff --git a/fonts/X11/helvR12_14x15.png b/fonts/X11/helvR12_14x15.png
deleted file mode 100755
index 208ca510..00000000
Binary files a/fonts/X11/helvR12_14x15.png and /dev/null differ
diff --git a/fonts/X11/bdf/ucs-fonts.7z b/fonts/X11/ucs-fonts.7z
old mode 100755
new mode 100644
similarity index 100%
rename from fonts/X11/bdf/ucs-fonts.7z
rename to fonts/X11/ucs-fonts.7z
diff --git a/fonts/libtcod/README.txt b/fonts/libtcod/README.txt
index c9726fbc..772f3efc 100755
--- a/fonts/libtcod/README.txt
+++ b/fonts/libtcod/README.txt
@@ -1,5 +1,5 @@
This directory contains antialiased fonts for libtcod.
-These fonts are in public domain.
+These fonts are in public domain.
The file names are composed with :
__.png
diff --git a/fonts/libtcod/arial10x10.png b/fonts/libtcod/arial10x10.png
deleted file mode 100755
index 64691f1b..00000000
Binary files a/fonts/libtcod/arial10x10.png and /dev/null differ
diff --git a/fonts/libtcod/arial12x12.png b/fonts/libtcod/arial12x12.png
deleted file mode 100755
index fa4018a7..00000000
Binary files a/fonts/libtcod/arial12x12.png and /dev/null differ
diff --git a/fonts/libtcod/arial8x8.png b/fonts/libtcod/arial8x8.png
deleted file mode 100755
index b05f533e..00000000
Binary files a/fonts/libtcod/arial8x8.png and /dev/null differ
diff --git a/fonts/libtcod/caeldera8x8_gs_tc.png b/fonts/libtcod/caeldera8x8_gs_tc.png
deleted file mode 100755
index 9d861820..00000000
Binary files a/fonts/libtcod/caeldera8x8_gs_tc.png and /dev/null differ
diff --git a/fonts/libtcod/celtic_garamond_10x10_gs_tc.png b/fonts/libtcod/celtic_garamond_10x10_gs_tc.png
deleted file mode 100755
index 7a0bc4a0..00000000
Binary files a/fonts/libtcod/celtic_garamond_10x10_gs_tc.png and /dev/null differ
diff --git a/fonts/libtcod/consolas10x10_gs_tc.png b/fonts/libtcod/consolas10x10_gs_tc.png
deleted file mode 100755
index 4b9b913f..00000000
Binary files a/fonts/libtcod/consolas10x10_gs_tc.png and /dev/null differ
diff --git a/fonts/libtcod/consolas12x12_gs_tc.png b/fonts/libtcod/consolas12x12_gs_tc.png
deleted file mode 100755
index 4789d02f..00000000
Binary files a/fonts/libtcod/consolas12x12_gs_tc.png and /dev/null differ
diff --git a/fonts/libtcod/consolas8x8_gs_tc.png b/fonts/libtcod/consolas8x8_gs_tc.png
deleted file mode 100755
index fab3c4c3..00000000
Binary files a/fonts/libtcod/consolas8x8_gs_tc.png and /dev/null differ
diff --git a/fonts/libtcod/consolas_unicode_10x10.png b/fonts/libtcod/consolas_unicode_10x10.png
deleted file mode 100755
index f2e824cb..00000000
Binary files a/fonts/libtcod/consolas_unicode_10x10.png and /dev/null differ
diff --git a/fonts/libtcod/consolas_unicode_12x12.png b/fonts/libtcod/consolas_unicode_12x12.png
deleted file mode 100755
index 608b28c0..00000000
Binary files a/fonts/libtcod/consolas_unicode_12x12.png and /dev/null differ
diff --git a/fonts/libtcod/consolas_unicode_16x16.png b/fonts/libtcod/consolas_unicode_16x16.png
deleted file mode 100755
index f6ee8aba..00000000
Binary files a/fonts/libtcod/consolas_unicode_16x16.png and /dev/null differ
diff --git a/fonts/libtcod/consolas_unicode_8x8.png b/fonts/libtcod/consolas_unicode_8x8.png
deleted file mode 100755
index 6716f3dd..00000000
Binary files a/fonts/libtcod/consolas_unicode_8x8.png and /dev/null differ
diff --git a/fonts/libtcod/courier10x10_aa_tc.png b/fonts/libtcod/courier10x10_aa_tc.png
deleted file mode 100755
index 50498e3e..00000000
Binary files a/fonts/libtcod/courier10x10_aa_tc.png and /dev/null differ
diff --git a/fonts/libtcod/courier12x12_aa_tc.png b/fonts/libtcod/courier12x12_aa_tc.png
deleted file mode 100755
index a1c4555d..00000000
Binary files a/fonts/libtcod/courier12x12_aa_tc.png and /dev/null differ
diff --git a/fonts/libtcod/courier8x8_aa_tc.png b/fonts/libtcod/courier8x8_aa_tc.png
deleted file mode 100755
index 675a743d..00000000
Binary files a/fonts/libtcod/courier8x8_aa_tc.png and /dev/null differ
diff --git a/fonts/libtcod/dundalk12x12_gs_tc.png b/fonts/libtcod/dundalk12x12_gs_tc.png
deleted file mode 100755
index d7309794..00000000
Binary files a/fonts/libtcod/dundalk12x12_gs_tc.png and /dev/null differ
diff --git a/fonts/libtcod/lucida10x10_gs_tc.png b/fonts/libtcod/lucida10x10_gs_tc.png
deleted file mode 100755
index 4e68dce8..00000000
Binary files a/fonts/libtcod/lucida10x10_gs_tc.png and /dev/null differ
diff --git a/fonts/libtcod/lucida12x12_gs_tc.png b/fonts/libtcod/lucida12x12_gs_tc.png
deleted file mode 100755
index 0ee95ff2..00000000
Binary files a/fonts/libtcod/lucida12x12_gs_tc.png and /dev/null differ
diff --git a/fonts/libtcod/lucida8x8_gs_tc.png b/fonts/libtcod/lucida8x8_gs_tc.png
deleted file mode 100755
index 0dbf229a..00000000
Binary files a/fonts/libtcod/lucida8x8_gs_tc.png and /dev/null differ
diff --git a/fonts/libtcod/prestige10x10_gs_tc.png b/fonts/libtcod/prestige10x10_gs_tc.png
deleted file mode 100755
index b6398cf7..00000000
Binary files a/fonts/libtcod/prestige10x10_gs_tc.png and /dev/null differ
diff --git a/fonts/libtcod/prestige12x12_gs_tc.png b/fonts/libtcod/prestige12x12_gs_tc.png
deleted file mode 100755
index a4b99ccd..00000000
Binary files a/fonts/libtcod/prestige12x12_gs_tc.png and /dev/null differ
diff --git a/fonts/libtcod/prestige8x8_gs_tc.png b/fonts/libtcod/prestige8x8_gs_tc.png
deleted file mode 100755
index 17945049..00000000
Binary files a/fonts/libtcod/prestige8x8_gs_tc.png and /dev/null differ
diff --git a/fonts/libtcod/terminal8x8_aa_as.png b/fonts/libtcod/terminal8x8_aa_as.png
deleted file mode 100755
index 25c351d6..00000000
Binary files a/fonts/libtcod/terminal8x8_aa_as.png and /dev/null differ
diff --git a/fonts/libtcod/terminal8x8_gs_as.png b/fonts/libtcod/terminal8x8_gs_as.png
deleted file mode 100755
index 36a19378..00000000
Binary files a/fonts/libtcod/terminal8x8_gs_as.png and /dev/null differ
diff --git a/libtcod b/libtcod
index f1089129..27c2dbc9 160000
--- a/libtcod
+++ b/libtcod
@@ -1 +1 @@
-Subproject commit f1089129890bbf2d84abed08b73874c129d6ec6f
+Subproject commit 27c2dbc9d97bacb18b9fd43a5c7f070dc34339ed
diff --git a/libtcodpy.py b/libtcodpy.py
index 1c352389..76317e99 100644
--- a/libtcodpy.py
+++ b/libtcodpy.py
@@ -1,4 +1,12 @@
-"""This module just an alias for tcod"""
+"""Deprecated module alias for tcod.libtcodpy, use 'import tcod as libtcodpy' instead."""
+
import warnings
-warnings.warn("`import tcod` is preferred.", DeprecationWarning, stacklevel=2)
-from tcod import *
+
+from tcod.libtcodpy import * # noqa: F403
+from tcod.libtcodpy import __getattr__ # noqa: F401
+
+warnings.warn(
+ "'import tcod as libtcodpy' is preferred.",
+ DeprecationWarning,
+ stacklevel=2,
+)
diff --git a/pyproject.toml b/pyproject.toml
index c7a16d74..b8c7cc34 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,7 +1,174 @@
[build-system]
requires = [
- "setuptools",
- "wheel",
- "cffi>=1.8.1,<2",
- "pycparser>=2.14,<3",
+ # setuptools >=64.0.0 might break editable installs
+ # https://github.com/pypa/setuptools/issues/3548
+ "setuptools >=77.0.3",
+ "setuptools_scm[toml]>=6.2",
+ "packaging>=24.2",
+ "wheel>=0.37.1",
+ "cffi>=1.15",
+ "pycparser>=2.14",
+ "pcpp==1.30",
+ "requests>=2.28.1",
+ "attrs",
]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "tcod"
+dynamic = ["version"]
+description = "The official Python port of libtcod."
+authors = [{ name = "Kyle Benesch", email = "4b796c65+tcod@gmail.com" }]
+readme = "README.rst"
+requires-python = ">=3.10"
+license = "BSD-2-Clause"
+license-files = [
+ "LICENSE.txt",
+ "libtcod/LICENSE.txt",
+ "libtcod/LIBTCOD-CREDITS.txt",
+]
+dependencies = [
+ "attrs>=25.2.0",
+ "cffi>=1.15",
+ 'numpy>=1.21.4; implementation_name != "pypy"',
+ "typing_extensions>=4.12.2",
+]
+keywords = [
+ "roguelike",
+ "roguelikedev",
+ "gamedev",
+ "Unicode",
+ "libtcod",
+ "libtcodpy",
+ "field-of-view",
+ "pathfinding",
+]
+classifiers = [ # https://pypi.org/classifiers/
+ "Development Status :: 5 - Production/Stable",
+ "Environment :: Win32 (MS Windows)",
+ "Environment :: MacOS X",
+ "Environment :: X11 Applications",
+ "Intended Audience :: Developers",
+ "Natural Language :: English",
+ "Operating System :: POSIX",
+ "Operating System :: MacOS :: MacOS X",
+ "Operating System :: Microsoft :: Windows",
+ "Programming Language :: C",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: Free Threading :: 2 - Beta",
+ "Programming Language :: Python :: Implementation :: CPython",
+ "Programming Language :: Python :: Implementation :: PyPy",
+ "Topic :: Games/Entertainment",
+ "Topic :: Multimedia :: Graphics",
+ "Topic :: Software Development :: Libraries :: Python Modules",
+ "Typing :: Typed",
+]
+
+[project.entry-points.pyinstaller40]
+hook-dirs = "tcod.__pyinstaller:get_hook_dirs"
+
+[project.urls]
+Homepage = "https://github.com/libtcod/python-tcod"
+Documentation = "https://python-tcod.readthedocs.io"
+Changelog = "https://github.com/libtcod/python-tcod/blob/main/CHANGELOG.md"
+Source = "https://github.com/libtcod/python-tcod"
+Tracker = "https://github.com/libtcod/python-tcod/issues"
+Forum = "https://github.com/libtcod/python-tcod/discussions"
+
+[tool.setuptools_scm]
+write_to = "tcod/version.py"
+
+[tool.pytest.ini_options]
+minversion = "6.0"
+required_plugins = ["pytest-cov", "pytest-benchmark"]
+testpaths = ["tcod/", "tests/", "docs/"]
+addopts = [
+ "--doctest-modules",
+ "--doctest-glob='*.rst'",
+ "--cov=tcod",
+ "--capture=sys",
+ "--ignore=tcod/__pyinstaller",
+]
+log_file_level = "DEBUG"
+faulthandler_timeout = 5
+filterwarnings = [
+ "ignore:This function may be deprecated in the future:PendingDeprecationWarning",
+ "ignore:This class may perform poorly and is no longer needed.::tcod.map",
+ "ignore:'import tcod as libtcodpy' is preferred.",
+]
+
+[tool.coverage.report] # https://coverage.readthedocs.io/en/latest/config.html
+exclude_lines = ['^\s*\.\.\.', "if TYPE_CHECKING:", "# pragma: no cover"]
+omit = ["tcod/__pyinstaller/*"]
+
+[tool.cibuildwheel] # https://cibuildwheel.pypa.io/en/stable/options/
+enable = ["pypy", "pyodide-prerelease"]
+
+[tool.mypy]
+files = ["."]
+python_version = "3.10"
+warn_unused_configs = true
+show_error_codes = true
+disallow_subclassing_any = true
+disallow_any_generics = true
+disallow_untyped_calls = true
+disallow_untyped_defs = true
+disallow_incomplete_defs = true
+check_untyped_defs = true
+disallow_untyped_decorators = true
+no_implicit_optional = true
+warn_redundant_casts = true
+warn_unused_ignores = true
+warn_return_any = true
+implicit_reexport = false
+strict_equality = true
+strict_bytes = true
+extra_checks = true
+exclude = [
+ "build/",
+ "venv/",
+ "libtcod/",
+ "docs/",
+ "distribution/",
+ "termbox/",
+ "samples_libtcodpy.py",
+]
+
+[[tool.mypy.overrides]]
+module = "numpy.*"
+ignore_missing_imports = true
+
+[[tool.mypy.overrides]]
+module = "tcod.version"
+ignore_missing_imports = true
+
+[[tool.mypy.overrides]]
+module = "tcod._libtcod"
+ignore_missing_imports = true
+
+[tool.ruff]
+extend-exclude = ["libtcod"] # Ignore submodule
+line-length = 120
+
+[tool.ruff.lint] # https://docs.astral.sh/ruff/rules/
+select = ["ALL"]
+ignore = [
+ "COM", # flake8-commas
+ "E501", # line-too-long
+ "PYI064", # redundant-final-literal
+ "S101", # assert
+ "S301", # suspicious-pickle-usage
+ "S311", # suspicious-non-cryptographic-random-usage
+ "SLF001", # private-member-access
+]
+[tool.ruff.lint.per-file-ignores]
+"**/{tests}/*" = [
+ "D103", # undocumented-public-function
+]
+"**/{tests,docs,examples,scripts}/*" = [
+ "D103", # undocumented-public-function
+ "T201", # print
+]
+
+[tool.ruff.lint.pydocstyle] # https://docs.astral.sh/ruff/settings/#lintpydocstyle
+convention = "google"
diff --git a/requirements.txt b/requirements.txt
index d4b7b762..06667de0 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,13 @@
-cffi>=1.8.1,<2
-numpy>=1.10,<2;
-pycparser>=2.14,<3
-setuptools>=36.0.1
\ No newline at end of file
+attrs>=23.1.0
+cffi>=1.15
+numpy>=1.21.4
+pycparser>=2.14
+requests>=2.28.1
+setuptools>=80.8.0
+types-cffi
+types-requests
+types-Pillow
+types-setuptools
+types-tabulate
+typing_extensions
+pcpp==1.30
diff --git a/scripts/README.md b/scripts/README.md
new file mode 100644
index 00000000..148ae426
--- /dev/null
+++ b/scripts/README.md
@@ -0,0 +1 @@
+This directory contains scripts which help with the development of python-tcod.
diff --git a/scripts/generate_charmap_table.py b/scripts/generate_charmap_table.py
new file mode 100755
index 00000000..bc7efb19
--- /dev/null
+++ b/scripts/generate_charmap_table.py
@@ -0,0 +1,84 @@
+#!/usr/bin/env python
+"""This script is used to generate the tables for `charmap-reference.rst`.
+
+Uses the tabulate module from PyPI.
+"""
+
+from __future__ import annotations
+
+import argparse
+import unicodedata
+from collections.abc import Iterable, Iterator # noqa: TC003
+
+from tabulate import tabulate
+
+import tcod.tileset
+
+
+def get_character_maps() -> Iterator[str]:
+ """Return an iterator of the current character maps from tcod.tileset."""
+ for name in dir(tcod.tileset):
+ if name.startswith("CHARMAP_"):
+ yield name[len("CHARMAP_") :].lower()
+
+
+def escape_rst(string: str) -> str:
+ """Escape RST symbols and disable Sphinx smart quotes."""
+ return (
+ string.replace("\\", "\\\\")
+ .replace("*", "\\*")
+ .replace("|", "\\|")
+ .replace("`", "\\`")
+ .replace("'", "\\'")
+ .replace('"', '\\"')
+ )
+
+
+def generate_table(charmap: Iterable[int]) -> str:
+ """Generate and RST table for `charmap`."""
+ headers = ("Tile Index", "Unicode", "String", "Name")
+ table = []
+
+ for i, ch in enumerate(charmap):
+ hex_len = len(f"{ch:x}")
+ if hex_len % 2: # Prevent an odd number of hex digits.
+ hex_len += 1
+ try:
+ name = unicodedata.name(chr(ch))
+ except ValueError:
+ # Skip names rather than guessing, the official names are enough.
+ name = ""
+ string = escape_rst(f"{chr(ch)!r}")
+ table.append((i, f"0x{ch:0{hex_len}X}", string, name))
+ return tabulate(table, headers, tablefmt="rst")
+
+
+def main() -> None:
+ """Main entry point."""
+ parser = argparse.ArgumentParser(
+ description="Generate an RST table for a tcod character map.",
+ )
+ parser.add_argument(
+ "charmap",
+ action="store",
+ choices=list(get_character_maps()),
+ type=str,
+ help="which character map to generate a table from",
+ )
+ parser.add_argument(
+ "-o",
+ "--out-file",
+ action="store",
+ type=argparse.FileType("w", encoding="utf-8"),
+ default="-",
+ help="where to write the table to (stdout by default)",
+ )
+ args = parser.parse_args()
+ charmap = getattr(tcod.tileset, f"CHARMAP_{args.charmap.upper()}")
+ output = generate_table(charmap)
+ with args.out_file as f:
+ f.write(output)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/get_release_description.py b/scripts/get_release_description.py
new file mode 100755
index 00000000..889feced
--- /dev/null
+++ b/scripts/get_release_description.py
@@ -0,0 +1,24 @@
+#!/usr/bin/env python
+"""Print the description used for GitHub Releases."""
+
+from __future__ import annotations
+
+import re
+from pathlib import Path
+
+TAG_BANNER = r"## \[[\w.]*\] - \d+-\d+-\d+\n"
+
+RE_BODY = re.compile(rf".*?{TAG_BANNER}(.*?){TAG_BANNER}", re.DOTALL)
+
+
+def main() -> None:
+ """Output the most recently tagged changelog body to stdout."""
+ match = RE_BODY.match(Path("CHANGELOG.md").read_text(encoding="utf-8"))
+ assert match
+ body = match.groups()[0].strip()
+
+ print(body)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/tag_release.py b/scripts/tag_release.py
new file mode 100755
index 00000000..5f39e4b2
--- /dev/null
+++ b/scripts/tag_release.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+"""Automate tagged releases of this project."""
+
+from __future__ import annotations
+
+import argparse
+import datetime
+import os
+import re
+import subprocess
+import sys
+from pathlib import Path
+
+# ruff: noqa: S603, S607
+
+PROJECT_DIR = Path(__file__).parent.parent
+
+parser = argparse.ArgumentParser(description="Tags and releases the next version of this project.")
+
+parser.add_argument("tag", help="Semantic version number to use as the tag.")
+
+parser.add_argument("-e", "--edit", action="store_true", help="Force edits of git commits.")
+
+parser.add_argument("-n", "--dry-run", action="store_true", help="Don't modify files.")
+
+parser.add_argument("-v", "--verbose", action="store_true", help="Print debug information.")
+
+
+def parse_changelog(args: argparse.Namespace) -> tuple[str, str]:
+ """Return an updated changelog and and the list of changes."""
+ match = re.match(
+ pattern=r"(.*?## \[Unreleased]\n)(.+?\n)(\n*## \[.*)",
+ string=(PROJECT_DIR / "CHANGELOG.md").read_text(encoding="utf-8"),
+ flags=re.DOTALL,
+ )
+ assert match
+ header, changes, tail = match.groups()
+
+ iso_date = datetime.datetime.now(tz=datetime.timezone.utc).date().isoformat()
+ tagged = f"\n## [{args.tag}] - {iso_date}\n{changes}"
+ if args.verbose:
+ print("--- Tagged section:")
+ print(tagged)
+
+ return f"{header}{tagged}{tail}", changes
+
+
+def replace_unreleased_tags(tag: str, *, dry_run: bool) -> None:
+ """Walk though sources and replace pending tags with the new tag."""
+ match = re.match(r"\d+\.\d+", tag)
+ assert match
+ short_tag = match.group()
+ for directory, _, files in os.walk(PROJECT_DIR / "tcod"):
+ for filename in files:
+ file = Path(directory, filename)
+ if file.suffix != ".py":
+ continue
+ text = file.read_text(encoding="utf-8")
+ new_text = re.sub(r":: *unreleased", rf":: {short_tag}", text, flags=re.IGNORECASE)
+ if text != new_text:
+ print(f"Update tags in {file}")
+ if not dry_run:
+ file.write_text(new_text, encoding="utf-8")
+
+
+def main() -> None:
+ """Entry function."""
+ if len(sys.argv) <= 1:
+ parser.print_help(sys.stderr)
+ sys.exit(1)
+
+ args = parser.parse_args()
+
+ new_changelog, changes = parse_changelog(args)
+
+ if args.verbose:
+ print("--- New changelog:")
+ print(new_changelog)
+
+ replace_unreleased_tags(args.tag, dry_run=args.dry_run)
+
+ if not args.dry_run:
+ (PROJECT_DIR / "CHANGELOG.md").write_text(new_changelog, encoding="utf-8")
+ edit = ["-e"] if args.edit else []
+ subprocess.check_call(["git", "commit", "-avm", f"Prepare {args.tag} release.", *edit])
+ subprocess.check_call(["git", "tag", args.tag, "-am", f"{args.tag}\n\n{changes}", *edit])
+
+
+if __name__ == "__main__":
+ main()
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index 86f10e1b..00000000
--- a/setup.cfg
+++ /dev/null
@@ -1,5 +0,0 @@
-[aliases]
-test=pytest
-
-[tool:pytest]
-addopts=tcod/ tdl/ tests/ --doctest-modules --cov=tcod --cov=tdl
diff --git a/setup.py b/setup.py
index 58897db3..3ff98114 100755
--- a/setup.py
+++ b/setup.py
@@ -1,148 +1,58 @@
#!/usr/bin/env python
+"""Python-tcod setup script."""
+
+from __future__ import annotations
import sys
+from pathlib import Path
from setuptools import setup
-from subprocess import check_output
-import platform
-
-from distutils.unixccompiler import UnixCCompiler
-
-C_STANDARD = '-std=c99'
-CPP_STANDARD = '-std=c++14'
-
-old_compile = UnixCCompiler._compile
-
-def new_compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts):
- cc_args = list(cc_args)
- if UnixCCompiler.language_map[ext] == 'c':
- cc_args.append(C_STANDARD)
- elif UnixCCompiler.language_map[ext] == 'c++':
- cc_args.append(CPP_STANDARD)
- return old_compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts)
+# ruff: noqa: T201
-UnixCCompiler._compile = new_compile
+SDL_VERSION_NEEDED = (3, 2, 0)
-def get_version():
- """Get the current version from a git tag, or by reading tcod/version.py"""
- try:
- tag = check_output(['git', 'describe', '--abbrev=0'],
- universal_newlines=True).strip()
- assert not tag.startswith('v')
- version = tag
+SETUP_DIR = Path(__file__).parent # setup.py current directory
- # add .devNN if needed
- log = check_output(['git', 'log', '%s..HEAD' % tag, '--oneline'],
- universal_newlines=True)
- commits_since_tag = log.count('\n')
- if commits_since_tag:
- version += '.dev%i' % commits_since_tag
- # update tcod/version.py
- open('tcod/version.py', 'w').write('__version__ = %r\n' % version)
- return version
- except:
- exec(open('tcod/version.py').read(), globals())
- return __version__
-
-is_pypy = platform.python_implementation() == 'PyPy'
-
-def get_package_data():
- '''get data files which will be included in the main tcod/ directory'''
- BITSIZE, LINKAGE = platform.architecture()
+def get_package_data() -> list[str]:
+ """Get data files which will be included in the main tcod/ directory."""
files = [
- 'lib/LIBTCOD-CREDITS.txt',
- 'lib/LIBTCOD-LICENSE.txt',
- 'lib/README-SDL.txt'
- ]
- if 'win32' in sys.platform:
- if BITSIZE == '32bit':
- files += ['x86/SDL2.dll']
+ "py.typed",
+ "lib/LIBTCOD-CREDITS.txt",
+ "lib/LIBTCOD-LICENSE.txt",
+ "lib/README-SDL.txt",
+ ]
+ if sys.platform == "win32":
+ if "ARM64" in sys.version:
+ files += ["arm64/SDL3.dll"]
+ elif "AMD64" in sys.version:
+ files += ["x64/SDL3.dll"]
else:
- files += ['x64/SDL2.dll']
- if sys.platform == 'darwin':
- files += ['SDL2.framework/Versions/A/SDL2']
+ files += ["x86/SDL3.dll"]
+ if sys.platform == "darwin":
+ files += ["SDL3.framework/Versions/A/SDL3"]
return files
-def get_long_description():
- """Return this projects description."""
- with open('README.rst', 'r') as f:
- readme = f.read()
- with open('CHANGELOG.rst', 'r') as f:
- changelog = f.read()
- changelog = changelog.replace('\nUnreleased\n------------------', '')
- return '\n'.join([readme, changelog])
-
-if sys.version_info < (3, 5):
- error = """
- python-tcod supports Python 3.5 and above.
- The last version supporting Python 2.7/3.4 was 'tcod==6.0.7'
-
- Python {py} detected.
- """.format(py='.'.join([str(v) for v in sys.version_info[:3]]))
- print(error)
+if not (SETUP_DIR / "libtcod/src").exists():
+ print("Libtcod submodule is uninitialized.")
+ print("Did you forget to run 'git submodule update --init'?")
sys.exit(1)
-needs_pytest = {'pytest', 'test', 'ptr'}.intersection(sys.argv)
-pytest_runner = ['pytest-runner'] if needs_pytest else []
+options = {
+ "bdist_wheel": {
+ "py_limited_api": "cp310",
+ }
+}
+if "free-threading build" in sys.version:
+ del options["bdist_wheel"]["py_limited_api"]
setup(
- name='tcod',
- version=get_version(),
- author='Kyle Stewart',
- author_email='4B796C65+tdl@gmail.com',
- description='Pythonic cffi port of libtcod.',
- long_description=get_long_description(),
- url='https://github.com/libtcod/python-tcod',
- py_modules=['libtcodpy'],
- packages=['tdl', 'tcod'],
- package_data={
- 'tdl': ['*.png'],
- 'tcod': get_package_data(),
- },
- python_requires='>=3.5',
- install_requires=[
- 'cffi>=1.8.1,<2',
- 'numpy>=1.10,<2' if not is_pypy else '',
- ],
- cffi_modules=['build_libtcod.py:ffi'],
- setup_requires=[
- 'cffi>=1.8.1,<2',
- 'pycparser>=2.14,<3',
- ] + pytest_runner,
- tests_require=[
- 'pytest',
- 'pytest-cov',
- 'pytest-benchmark',
- ],
- classifiers=[
- 'Development Status :: 5 - Production/Stable',
- 'Environment :: Win32 (MS Windows)',
- 'Environment :: MacOS X',
- 'Environment :: X11 Applications',
- 'Intended Audience :: Developers',
- 'License :: OSI Approved :: BSD License',
- 'Natural Language :: English',
- 'Operating System :: POSIX',
- 'Operating System :: MacOS :: MacOS X',
- 'Operating System :: Microsoft :: Windows',
- 'Programming Language :: Python :: 3',
- 'Programming Language :: Python :: 3.5',
- 'Programming Language :: Python :: 3.6',
- 'Programming Language :: Python :: 3.7',
- 'Programming Language :: Python :: Implementation :: CPython',
- 'Programming Language :: Python :: Implementation :: PyPy',
- 'Topic :: Games/Entertainment',
- 'Topic :: Multimedia :: Graphics',
- 'Topic :: Software Development :: Libraries :: Python Modules',
- ],
- keywords='roguelike cffi Unicode libtcod fov heightmap namegen',
- platforms=[
- 'Windows',
- 'MacOS',
- 'Linux',
- ],
- license='Simplified BSD License',
- )
+ py_modules=["libtcodpy"],
+ packages=["tcod", "tcod.sdl", "tcod.__pyinstaller"],
+ package_data={"tcod": get_package_data()},
+ cffi_modules=["build_libtcod.py:ffi"],
+ platforms=["Windows", "MacOS", "Linux"],
+ options=options,
+)
diff --git a/stdeb.cfg b/stdeb.cfg
deleted file mode 100644
index 8b9fefd9..00000000
--- a/stdeb.cfg
+++ /dev/null
@@ -1,6 +0,0 @@
-[DEFAULT]
-Depends: libsdl2-2.0-0, libomp5
-Build-Depends: libsdl2-dev, python-pycparser, python3-pycparser, python-cffi, python3-cffi, python-numpy, python3-numpy
-XS-Python-Version: >= 2.7
-X-Python3-Version: >= 3.4
-Copyright-File: LICENSE.txt
diff --git a/tcod/.clang-format b/tcod/.clang-format
new file mode 100644
index 00000000..1c1a215d
--- /dev/null
+++ b/tcod/.clang-format
@@ -0,0 +1,13 @@
+BasedOnStyle: Google
+IndentWidth: 2
+Language: Cpp
+
+DerivePointerAlignment: false
+PointerAlignment: Left
+
+ColumnLimit: 120
+AlignAfterOpenBracket: AlwaysBreak
+BinPackArguments: false
+BinPackParameters: false
+AllowShortIfStatementsOnASingleLine: WithoutElse
+AllowShortBlocksOnASingleLine: Always
diff --git a/tcod/__init__.py b/tcod/__init__.py
index 5ae07ef0..2db18f54 100644
--- a/tcod/__init__.py
+++ b/tcod/__init__.py
@@ -1,34 +1,40 @@
-"""
- This module provides a simple CFFI API to libtcod.
-
- This port has large partial support for libtcod's C functions.
- Use tcod/libtcod_cdef.h in the source distribution to see specially what
- functions were exported and what new functions have been added by TDL.
+"""The fast Python port of libtcod.
- The ffi and lib variables should be familiar to anyone that has used CFFI
- before, otherwise it's time to read up on how they work:
- https://cffi.readthedocs.org/en/latest/using.html
+This module can be used as a drop in replacement for the official libtcodpy module.
- Otherwise this module can be used as a drop in replacement for the official
- libtcod.py module.
+Bring any issues or feature requests to GitHub: https://github.com/libtcod/python-tcod
- Bring any issues or requests to GitHub:
- https://github.com/HexDecimal/libtcod-cffi
+Read the documentation online: https://python-tcod.readthedocs.io/en/latest/
"""
-from __future__ import absolute_import
-
-import sys
-
-import warnings
-
-from tcod.libtcodpy import *
-try:
- from tcod.version import __version__
-except ImportError: # Gets imported without version.py by ReadTheDocs
- __version__ = ''
-if sys.version_info[0] == 2:
- warnings.warn(
- "python-tcod has dropped support for Python 2.7.",
- DeprecationWarning
- )
+from __future__ import annotations
+
+from pkgutil import extend_path
+
+__path__ = extend_path(__path__, __name__)
+
+from tcod import bsp, color, console, context, event, image, los, map, noise, path, random, tileset # noqa: A004
+from tcod.cffi import __sdl_version__, ffi, lib
+from tcod.tcod import __getattr__ # noqa: F401
+from tcod.version import __version__
+
+__all__ = [
+ "Console",
+ "__sdl_version__",
+ "__version__",
+ "bsp",
+ "color",
+ "console",
+ "context",
+ "event",
+ "ffi",
+ "image",
+ "lib",
+ "los",
+ "map",
+ "noise",
+ "path",
+ "random",
+ "tileset",
+ "tileset",
+]
diff --git a/tcod/__pyinstaller/__init__.py b/tcod/__pyinstaller/__init__.py
new file mode 100644
index 00000000..2b67c1c0
--- /dev/null
+++ b/tcod/__pyinstaller/__init__.py
@@ -0,0 +1,10 @@
+"""PyInstaller entry point for tcod."""
+
+from __future__ import annotations
+
+from pathlib import Path
+
+
+def get_hook_dirs() -> list[str]:
+ """Return the current directory."""
+ return [str(Path(__file__).parent)]
diff --git a/tcod/__pyinstaller/hook-tcod.py b/tcod/__pyinstaller/hook-tcod.py
new file mode 100644
index 00000000..b0cabf38
--- /dev/null
+++ b/tcod/__pyinstaller/hook-tcod.py
@@ -0,0 +1,14 @@
+"""PyInstaller hook for tcod.
+
+There were added since tcod 12.0.0.
+
+If this hook is ever modified then the contributed hook needs to be removed from:
+https://github.com/pyinstaller/pyinstaller-hooks-contrib
+"""
+
+from PyInstaller.utils.hooks import collect_dynamic_libs # type: ignore
+
+hiddenimports = ["_cffi_backend"]
+
+# Install shared libraries to the working directory.
+binaries = collect_dynamic_libs("tcod")
diff --git a/tcod/_internal.py b/tcod/_internal.py
index e5a0aad2..b67242b8 100644
--- a/tcod/_internal.py
+++ b/tcod/_internal.py
@@ -1,25 +1,225 @@
+"""Internal helper functions used by the rest of the library."""
-import functools
+from __future__ import annotations
+
+import locale
+import sys
import warnings
+from collections.abc import Callable
+from typing import TYPE_CHECKING, Any, AnyStr, Literal, NoReturn, SupportsInt, TypeVar
+
+from typing_extensions import deprecated
+
+from tcod.cffi import ffi, lib
+
+if TYPE_CHECKING:
+ from pathlib import Path
+ from types import TracebackType
+
+
+FuncType = Callable[..., Any]
+F = TypeVar("F", bound=FuncType)
+T = TypeVar("T")
+
+
+def _deprecate_passthrough(
+ message: str, # noqa: ARG001
+ /,
+ *,
+ category: type[Warning] = DeprecationWarning, # noqa: ARG001
+ stacklevel: int = 0, # noqa: ARG001
+) -> Callable[[F], F]:
+ """Return a decorator which skips wrapping a warning onto functions. This is used for non-debug runs."""
+
+ def decorator(func: F) -> F:
+ return func
-def deprecate(message, category=DeprecationWarning, stacklevel=0):
- def decorator(func):
- @functools.wraps(func)
- def wrapper(*args, **kargs):
- warnings.warn(message, category, stacklevel=stacklevel + 2)
- return func(*args, **kargs)
- return wrapper
return decorator
-def verify_order(order):
- order = order.upper()
- if order != 'C' and order != 'F':
- raise TypeError("order must be 'C' or 'F', not %r" % (order,))
+
+deprecate = deprecated if __debug__ or TYPE_CHECKING else _deprecate_passthrough
+
+
+def verify_order(order: Literal["C", "F"]) -> Literal["C", "F"]:
+ """Verify and return a Numpy order string."""
+ order = order.upper() # type: ignore[assignment]
+ if order not in ("C", "F"):
+ msg = f"order must be 'C' or 'F', not {order!r}"
+ raise TypeError(msg)
return order
-def handle_order(shape, order):
- order = verify_order(order)
- if order == 'C':
- return shape
- else:
- return tuple(reversed(shape))
+
+def _raise_tcod_error() -> NoReturn:
+ """Raise an error from libtcod, this function assumes an error exists."""
+ raise RuntimeError(ffi.string(lib.TCOD_get_error()).decode("utf-8"))
+
+
+def _check(error: int) -> int:
+ """Detect and convert a libtcod error code into an exception."""
+ if error < 0:
+ _raise_tcod_error()
+ return error
+
+
+def _check_p(pointer: T) -> T:
+ """Treats NULL pointers as errors and raises a libtcod exception."""
+ if not pointer:
+ _raise_tcod_error()
+ return pointer
+
+
+def _check_warn(error: int, stacklevel: int = 2) -> int:
+ """Like _check, but raises a warning on positive error codes."""
+ if _check(error) > 0:
+ warnings.warn(
+ ffi.string(lib.TCOD_get_error()).decode(),
+ RuntimeWarning,
+ stacklevel=stacklevel + 1,
+ )
+ return error
+
+
+def _unpack_char_p(char_p: Any) -> str: # noqa: ANN401
+ if char_p == ffi.NULL:
+ return ""
+ return str(ffi.string(char_p), encoding="utf-8")
+
+
+def _int(int_or_str: SupportsInt | str | bytes) -> int:
+ """Return an integer where a single character string may be expected."""
+ if isinstance(int_or_str, str):
+ return ord(int_or_str)
+ if isinstance(int_or_str, bytes):
+ return int_or_str[0]
+ return int(int_or_str)
+
+
+def _bytes(string: AnyStr) -> bytes:
+ if isinstance(string, str):
+ return string.encode("utf-8")
+ return string
+
+
+def _unicode(string: AnyStr, stacklevel: int = 2) -> str:
+ if isinstance(string, bytes):
+ warnings.warn(
+ "Passing byte strings as parameters to Unicode functions is deprecated.",
+ FutureWarning,
+ stacklevel=stacklevel + 1,
+ )
+ return string.decode("latin-1")
+ return str(string)
+
+
+def _fmt(string: str, stacklevel: int = 2) -> bytes:
+ if isinstance(string, bytes):
+ warnings.warn(
+ "Passing byte strings as parameters to Unicode functions is deprecated.",
+ FutureWarning,
+ stacklevel=stacklevel + 1,
+ )
+ string = string.decode("latin-1")
+ return string.encode("utf-8").replace(b"%", b"%%")
+
+
+def _path_encode(path: Path) -> bytes:
+ """Return a bytes file path for the current locale when on Windows, uses fsdecode for other platforms."""
+ if sys.platform != "win32":
+ return bytes(path) # Sane and expected behavior for converting Path into bytes
+ try:
+ return str(path).encode(locale.getlocale()[1] or "utf-8") # Stay classy, Windows
+ except UnicodeEncodeError as exc:
+ if sys.version_info >= (3, 11):
+ exc.add_note("""Consider calling 'locale.setlocale(locale.LC_CTYPES, ".UTF8")' to support Unicode paths.""")
+ raise
+
+
+class _PropagateException:
+ """Context manager designed to propagate exceptions outside of a cffi callback context.
+
+ Normally cffi suppresses the exception.
+
+ When propagate is called this class will hold onto the error until the
+ control flow leaves the context, then the error will be raised.
+
+ with _PropagateException as propagate:
+ # give propagate as onerror parameter for ffi.def_extern
+ """
+
+ def __init__(self) -> None:
+ self.caught: BaseException | None = None
+
+ def propagate(self, *exc_info: Any) -> None: # noqa: ANN401
+ """Set an exception to be raised once this context exits.
+
+ If multiple errors are caught, only keep the first exception raised.
+ """
+ if self.caught is None:
+ self.caught = exc_info[1]
+
+ def __enter__(self) -> Callable[[Any], None]:
+ """Once in context, only the propagate call is needed to use this class effectively."""
+ return self.propagate
+
+ def __exit__(
+ self, _type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None
+ ) -> None:
+ """If we're holding on to an exception, raise it now.
+
+ self.caught is reset now in case of nested manager shenanigans.
+ """
+ to_raise, self.caught = self.caught, None
+ if to_raise is not None:
+ raise to_raise from value
+
+
+class _CDataWrapper:
+ """A generally deprecated CData wrapper class used by libtcodpy."""
+
+ def __init__(self, *args: Any, **kwargs: Any) -> None: # noqa: ANN401
+ self.cdata = self._get_cdata_from_args(*args, **kwargs)
+ if self.cdata is None:
+ self.cdata = ffi.NULL
+ super().__init__()
+
+ @staticmethod
+ def _get_cdata_from_args(*args: Any, **kwargs: Any) -> Any: # noqa: ANN401
+ if len(args) == 1 and isinstance(args[0], ffi.CData) and not kwargs:
+ return args[0]
+ return None
+
+ def __hash__(self) -> int:
+ return hash(self.cdata)
+
+ def __eq__(self, other: object) -> bool:
+ if not isinstance(other, _CDataWrapper):
+ return NotImplemented
+ return bool(self.cdata == other.cdata)
+
+ def __getattr__(self, attr: str) -> Any: # noqa: ANN401
+ if "cdata" in self.__dict__:
+ return getattr(self.__dict__["cdata"], attr)
+ raise AttributeError(attr)
+
+ def __setattr__(self, attr: str, value: Any) -> None: # noqa: ANN401
+ if hasattr(self, "cdata") and hasattr(self.cdata, attr):
+ setattr(self.cdata, attr, value)
+ else:
+ super().__setattr__(attr, value)
+
+
+def _console(console: Any) -> Any: # noqa: ANN401
+ """Return a cffi console pointer."""
+ try:
+ return console.console_c
+ except AttributeError:
+ warnings.warn(
+ (
+ "Falsy console parameters are deprecated, "
+ "always use the root console instance returned by "
+ "console_init_root."
+ ),
+ DeprecationWarning,
+ stacklevel=3,
+ )
+ return ffi.NULL
diff --git a/tcod/_libtcod.pyi b/tcod/_libtcod.pyi
new file mode 100644
index 00000000..04fba29b
--- /dev/null
+++ b/tcod/_libtcod.pyi
@@ -0,0 +1,9593 @@
+# Autogenerated with build_libtcod.py
+from typing import Any, Final, Literal
+
+# pyi files for CFFI ports are not standard
+# ruff: noqa: A002, ANN401, D402, D403, D415, N801, N802, N803, N815, PLW0211, PYI021
+
+class _lib:
+ @staticmethod
+ def NoiseGetSample(noise: Any, xyzw: Any, /) -> float:
+ """float NoiseGetSample(TDLNoise *noise, float *xyzw)"""
+
+ @staticmethod
+ def NoiseSampleMeshGrid(noise: Any, len: Any, in_: Any, out: Any, /) -> None:
+ """void NoiseSampleMeshGrid(TDLNoise *noise, const long len, const float *in, float *out)"""
+
+ @staticmethod
+ def NoiseSampleOpenMeshGrid(noise: Any, ndim: int, shape: Any, ogrid_in: Any, out: Any, /) -> None:
+ """void NoiseSampleOpenMeshGrid(TDLNoise *noise, const int ndim, const long *shape, const float **ogrid_in, float *out)"""
+
+ @staticmethod
+ def PathCostArrayFloat32(x1: int, y1: int, x2: int, y2: int, map: Any, /) -> float:
+ """float PathCostArrayFloat32(int x1, int y1, int x2, int y2, const struct PathCostArray *map)"""
+
+ @staticmethod
+ def PathCostArrayInt16(x1: int, y1: int, x2: int, y2: int, map: Any, /) -> float:
+ """float PathCostArrayInt16(int x1, int y1, int x2, int y2, const struct PathCostArray *map)"""
+
+ @staticmethod
+ def PathCostArrayInt32(x1: int, y1: int, x2: int, y2: int, map: Any, /) -> float:
+ """float PathCostArrayInt32(int x1, int y1, int x2, int y2, const struct PathCostArray *map)"""
+
+ @staticmethod
+ def PathCostArrayInt8(x1: int, y1: int, x2: int, y2: int, map: Any, /) -> float:
+ """float PathCostArrayInt8(int x1, int y1, int x2, int y2, const struct PathCostArray *map)"""
+
+ @staticmethod
+ def PathCostArrayUInt16(x1: int, y1: int, x2: int, y2: int, map: Any, /) -> float:
+ """float PathCostArrayUInt16(int x1, int y1, int x2, int y2, const struct PathCostArray *map)"""
+
+ @staticmethod
+ def PathCostArrayUInt32(x1: int, y1: int, x2: int, y2: int, map: Any, /) -> float:
+ """float PathCostArrayUInt32(int x1, int y1, int x2, int y2, const struct PathCostArray *map)"""
+
+ @staticmethod
+ def PathCostArrayUInt8(x1: int, y1: int, x2: int, y2: int, map: Any, /) -> float:
+ """float PathCostArrayUInt8(int x1, int y1, int x2, int y2, const struct PathCostArray *map)"""
+
+ @staticmethod
+ def SDL_AcquireCameraFrame(camera: Any, timestampNS: Any, /) -> Any:
+ """SDL_Surface *SDL_AcquireCameraFrame(SDL_Camera *camera, Uint64 *timestampNS)"""
+
+ @staticmethod
+ def SDL_AcquireGPUCommandBuffer(device: Any, /) -> Any:
+ """SDL_GPUCommandBuffer *SDL_AcquireGPUCommandBuffer(SDL_GPUDevice *device)"""
+
+ @staticmethod
+ def SDL_AcquireGPUSwapchainTexture(
+ command_buffer: Any,
+ window: Any,
+ swapchain_texture: Any,
+ swapchain_texture_width: Any,
+ swapchain_texture_height: Any,
+ /,
+ ) -> bool:
+ """bool SDL_AcquireGPUSwapchainTexture(SDL_GPUCommandBuffer *command_buffer, SDL_Window *window, SDL_GPUTexture **swapchain_texture, Uint32 *swapchain_texture_width, Uint32 *swapchain_texture_height)"""
+
+ @staticmethod
+ def SDL_AddAtomicInt(a: Any, v: int, /) -> int:
+ """int SDL_AddAtomicInt(SDL_AtomicInt *a, int v)"""
+
+ @staticmethod
+ def SDL_AddEventWatch(filter: Any, userdata: Any, /) -> bool:
+ """bool SDL_AddEventWatch(SDL_EventFilter filter, void *userdata)"""
+
+ @staticmethod
+ def SDL_AddGamepadMapping(mapping: Any, /) -> int:
+ """int SDL_AddGamepadMapping(const char *mapping)"""
+
+ @staticmethod
+ def SDL_AddGamepadMappingsFromFile(file: Any, /) -> int:
+ """int SDL_AddGamepadMappingsFromFile(const char *file)"""
+
+ @staticmethod
+ def SDL_AddGamepadMappingsFromIO(src: Any, closeio: bool, /) -> int:
+ """int SDL_AddGamepadMappingsFromIO(SDL_IOStream *src, bool closeio)"""
+
+ @staticmethod
+ def SDL_AddHintCallback(name: Any, callback: Any, userdata: Any, /) -> bool:
+ """bool SDL_AddHintCallback(const char *name, SDL_HintCallback callback, void *userdata)"""
+
+ @staticmethod
+ def SDL_AddSurfaceAlternateImage(surface: Any, image: Any, /) -> bool:
+ """bool SDL_AddSurfaceAlternateImage(SDL_Surface *surface, SDL_Surface *image)"""
+
+ @staticmethod
+ def SDL_AddTimer(interval: Any, callback: Any, userdata: Any, /) -> Any:
+ """SDL_TimerID SDL_AddTimer(Uint32 interval, SDL_TimerCallback callback, void *userdata)"""
+
+ @staticmethod
+ def SDL_AddTimerNS(interval: Any, callback: Any, userdata: Any, /) -> Any:
+ """SDL_TimerID SDL_AddTimerNS(Uint64 interval, SDL_NSTimerCallback callback, void *userdata)"""
+
+ @staticmethod
+ def SDL_AddVulkanRenderSemaphores(
+ renderer: Any, wait_stage_mask: Any, wait_semaphore: Any, signal_semaphore: Any, /
+ ) -> bool:
+ """bool SDL_AddVulkanRenderSemaphores(SDL_Renderer *renderer, Uint32 wait_stage_mask, Sint64 wait_semaphore, Sint64 signal_semaphore)"""
+
+ @staticmethod
+ def SDL_AsyncIOFromFile(file: Any, mode: Any, /) -> Any:
+ """SDL_AsyncIO *SDL_AsyncIOFromFile(const char *file, const char *mode)"""
+
+ @staticmethod
+ def SDL_AttachVirtualJoystick(desc: Any, /) -> Any:
+ """SDL_JoystickID SDL_AttachVirtualJoystick(const SDL_VirtualJoystickDesc *desc)"""
+
+ @staticmethod
+ def SDL_AudioDevicePaused(devid: Any, /) -> bool:
+ """bool SDL_AudioDevicePaused(SDL_AudioDeviceID devid)"""
+
+ @staticmethod
+ def SDL_AudioStreamDevicePaused(stream: Any, /) -> bool:
+ """bool SDL_AudioStreamDevicePaused(SDL_AudioStream *stream)"""
+
+ @staticmethod
+ def SDL_BeginGPUComputePass(
+ command_buffer: Any,
+ storage_texture_bindings: Any,
+ num_storage_texture_bindings: Any,
+ storage_buffer_bindings: Any,
+ num_storage_buffer_bindings: Any,
+ /,
+ ) -> Any:
+ """SDL_GPUComputePass *SDL_BeginGPUComputePass(SDL_GPUCommandBuffer *command_buffer, const SDL_GPUStorageTextureReadWriteBinding *storage_texture_bindings, Uint32 num_storage_texture_bindings, const SDL_GPUStorageBufferReadWriteBinding *storage_buffer_bindings, Uint32 num_storage_buffer_bindings)"""
+
+ @staticmethod
+ def SDL_BeginGPUCopyPass(command_buffer: Any, /) -> Any:
+ """SDL_GPUCopyPass *SDL_BeginGPUCopyPass(SDL_GPUCommandBuffer *command_buffer)"""
+
+ @staticmethod
+ def SDL_BeginGPURenderPass(
+ command_buffer: Any, color_target_infos: Any, num_color_targets: Any, depth_stencil_target_info: Any, /
+ ) -> Any:
+ """SDL_GPURenderPass *SDL_BeginGPURenderPass(SDL_GPUCommandBuffer *command_buffer, const SDL_GPUColorTargetInfo *color_target_infos, Uint32 num_color_targets, const SDL_GPUDepthStencilTargetInfo *depth_stencil_target_info)"""
+
+ @staticmethod
+ def SDL_BindAudioStream(devid: Any, stream: Any, /) -> bool:
+ """bool SDL_BindAudioStream(SDL_AudioDeviceID devid, SDL_AudioStream *stream)"""
+
+ @staticmethod
+ def SDL_BindAudioStreams(devid: Any, streams: Any, num_streams: int, /) -> bool:
+ """bool SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_AudioStream * const *streams, int num_streams)"""
+
+ @staticmethod
+ def SDL_BindGPUComputePipeline(compute_pass: Any, compute_pipeline: Any, /) -> None:
+ """void SDL_BindGPUComputePipeline(SDL_GPUComputePass *compute_pass, SDL_GPUComputePipeline *compute_pipeline)"""
+
+ @staticmethod
+ def SDL_BindGPUComputeSamplers(
+ compute_pass: Any, first_slot: Any, texture_sampler_bindings: Any, num_bindings: Any, /
+ ) -> None:
+ """void SDL_BindGPUComputeSamplers(SDL_GPUComputePass *compute_pass, Uint32 first_slot, const SDL_GPUTextureSamplerBinding *texture_sampler_bindings, Uint32 num_bindings)"""
+
+ @staticmethod
+ def SDL_BindGPUComputeStorageBuffers(
+ compute_pass: Any, first_slot: Any, storage_buffers: Any, num_bindings: Any, /
+ ) -> None:
+ """void SDL_BindGPUComputeStorageBuffers(SDL_GPUComputePass *compute_pass, Uint32 first_slot, SDL_GPUBuffer * const *storage_buffers, Uint32 num_bindings)"""
+
+ @staticmethod
+ def SDL_BindGPUComputeStorageTextures(
+ compute_pass: Any, first_slot: Any, storage_textures: Any, num_bindings: Any, /
+ ) -> None:
+ """void SDL_BindGPUComputeStorageTextures(SDL_GPUComputePass *compute_pass, Uint32 first_slot, SDL_GPUTexture * const *storage_textures, Uint32 num_bindings)"""
+
+ @staticmethod
+ def SDL_BindGPUFragmentSamplers(
+ render_pass: Any, first_slot: Any, texture_sampler_bindings: Any, num_bindings: Any, /
+ ) -> None:
+ """void SDL_BindGPUFragmentSamplers(SDL_GPURenderPass *render_pass, Uint32 first_slot, const SDL_GPUTextureSamplerBinding *texture_sampler_bindings, Uint32 num_bindings)"""
+
+ @staticmethod
+ def SDL_BindGPUFragmentStorageBuffers(
+ render_pass: Any, first_slot: Any, storage_buffers: Any, num_bindings: Any, /
+ ) -> None:
+ """void SDL_BindGPUFragmentStorageBuffers(SDL_GPURenderPass *render_pass, Uint32 first_slot, SDL_GPUBuffer * const *storage_buffers, Uint32 num_bindings)"""
+
+ @staticmethod
+ def SDL_BindGPUFragmentStorageTextures(
+ render_pass: Any, first_slot: Any, storage_textures: Any, num_bindings: Any, /
+ ) -> None:
+ """void SDL_BindGPUFragmentStorageTextures(SDL_GPURenderPass *render_pass, Uint32 first_slot, SDL_GPUTexture * const *storage_textures, Uint32 num_bindings)"""
+
+ @staticmethod
+ def SDL_BindGPUGraphicsPipeline(render_pass: Any, graphics_pipeline: Any, /) -> None:
+ """void SDL_BindGPUGraphicsPipeline(SDL_GPURenderPass *render_pass, SDL_GPUGraphicsPipeline *graphics_pipeline)"""
+
+ @staticmethod
+ def SDL_BindGPUIndexBuffer(render_pass: Any, binding: Any, index_element_size: Any, /) -> None:
+ """void SDL_BindGPUIndexBuffer(SDL_GPURenderPass *render_pass, const SDL_GPUBufferBinding *binding, SDL_GPUIndexElementSize index_element_size)"""
+
+ @staticmethod
+ def SDL_BindGPUVertexBuffers(render_pass: Any, first_slot: Any, bindings: Any, num_bindings: Any, /) -> None:
+ """void SDL_BindGPUVertexBuffers(SDL_GPURenderPass *render_pass, Uint32 first_slot, const SDL_GPUBufferBinding *bindings, Uint32 num_bindings)"""
+
+ @staticmethod
+ def SDL_BindGPUVertexSamplers(
+ render_pass: Any, first_slot: Any, texture_sampler_bindings: Any, num_bindings: Any, /
+ ) -> None:
+ """void SDL_BindGPUVertexSamplers(SDL_GPURenderPass *render_pass, Uint32 first_slot, const SDL_GPUTextureSamplerBinding *texture_sampler_bindings, Uint32 num_bindings)"""
+
+ @staticmethod
+ def SDL_BindGPUVertexStorageBuffers(
+ render_pass: Any, first_slot: Any, storage_buffers: Any, num_bindings: Any, /
+ ) -> None:
+ """void SDL_BindGPUVertexStorageBuffers(SDL_GPURenderPass *render_pass, Uint32 first_slot, SDL_GPUBuffer * const *storage_buffers, Uint32 num_bindings)"""
+
+ @staticmethod
+ def SDL_BindGPUVertexStorageTextures(
+ render_pass: Any, first_slot: Any, storage_textures: Any, num_bindings: Any, /
+ ) -> None:
+ """void SDL_BindGPUVertexStorageTextures(SDL_GPURenderPass *render_pass, Uint32 first_slot, SDL_GPUTexture * const *storage_textures, Uint32 num_bindings)"""
+
+ @staticmethod
+ def SDL_BlitGPUTexture(command_buffer: Any, info: Any, /) -> None:
+ """void SDL_BlitGPUTexture(SDL_GPUCommandBuffer *command_buffer, const SDL_GPUBlitInfo *info)"""
+
+ @staticmethod
+ def SDL_BlitSurface(src: Any, srcrect: Any, dst: Any, dstrect: Any, /) -> bool:
+ """bool SDL_BlitSurface(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface *dst, const SDL_Rect *dstrect)"""
+
+ @staticmethod
+ def SDL_BlitSurface9Grid(
+ src: Any,
+ srcrect: Any,
+ left_width: int,
+ right_width: int,
+ top_height: int,
+ bottom_height: int,
+ scale: float,
+ scaleMode: Any,
+ dst: Any,
+ dstrect: Any,
+ /,
+ ) -> bool:
+ """bool SDL_BlitSurface9Grid(SDL_Surface *src, const SDL_Rect *srcrect, int left_width, int right_width, int top_height, int bottom_height, float scale, SDL_ScaleMode scaleMode, SDL_Surface *dst, const SDL_Rect *dstrect)"""
+
+ @staticmethod
+ def SDL_BlitSurfaceScaled(src: Any, srcrect: Any, dst: Any, dstrect: Any, scaleMode: Any, /) -> bool:
+ """bool SDL_BlitSurfaceScaled(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface *dst, const SDL_Rect *dstrect, SDL_ScaleMode scaleMode)"""
+
+ @staticmethod
+ def SDL_BlitSurfaceTiled(src: Any, srcrect: Any, dst: Any, dstrect: Any, /) -> bool:
+ """bool SDL_BlitSurfaceTiled(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface *dst, const SDL_Rect *dstrect)"""
+
+ @staticmethod
+ def SDL_BlitSurfaceTiledWithScale(
+ src: Any, srcrect: Any, scale: float, scaleMode: Any, dst: Any, dstrect: Any, /
+ ) -> bool:
+ """bool SDL_BlitSurfaceTiledWithScale(SDL_Surface *src, const SDL_Rect *srcrect, float scale, SDL_ScaleMode scaleMode, SDL_Surface *dst, const SDL_Rect *dstrect)"""
+
+ @staticmethod
+ def SDL_BlitSurfaceUnchecked(src: Any, srcrect: Any, dst: Any, dstrect: Any, /) -> bool:
+ """bool SDL_BlitSurfaceUnchecked(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface *dst, const SDL_Rect *dstrect)"""
+
+ @staticmethod
+ def SDL_BlitSurfaceUncheckedScaled(src: Any, srcrect: Any, dst: Any, dstrect: Any, scaleMode: Any, /) -> bool:
+ """bool SDL_BlitSurfaceUncheckedScaled(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface *dst, const SDL_Rect *dstrect, SDL_ScaleMode scaleMode)"""
+
+ @staticmethod
+ def SDL_BroadcastCondition(cond: Any, /) -> None:
+ """void SDL_BroadcastCondition(SDL_Condition *cond)"""
+
+ @staticmethod
+ def SDL_CalculateGPUTextureFormatSize(format: Any, width: Any, height: Any, depth_or_layer_count: Any, /) -> Any:
+ """Uint32 SDL_CalculateGPUTextureFormatSize(SDL_GPUTextureFormat format, Uint32 width, Uint32 height, Uint32 depth_or_layer_count)"""
+
+ @staticmethod
+ def SDL_CancelGPUCommandBuffer(command_buffer: Any, /) -> bool:
+ """bool SDL_CancelGPUCommandBuffer(SDL_GPUCommandBuffer *command_buffer)"""
+
+ @staticmethod
+ def SDL_CaptureMouse(enabled: bool, /) -> bool:
+ """bool SDL_CaptureMouse(bool enabled)"""
+
+ @staticmethod
+ def SDL_ClaimWindowForGPUDevice(device: Any, window: Any, /) -> bool:
+ """bool SDL_ClaimWindowForGPUDevice(SDL_GPUDevice *device, SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_CleanupTLS() -> None:
+ """void SDL_CleanupTLS(void)"""
+
+ @staticmethod
+ def SDL_ClearAudioStream(stream: Any, /) -> bool:
+ """bool SDL_ClearAudioStream(SDL_AudioStream *stream)"""
+
+ @staticmethod
+ def SDL_ClearClipboardData() -> bool:
+ """bool SDL_ClearClipboardData(void)"""
+
+ @staticmethod
+ def SDL_ClearComposition(window: Any, /) -> bool:
+ """bool SDL_ClearComposition(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_ClearError() -> bool:
+ """bool SDL_ClearError(void)"""
+
+ @staticmethod
+ def SDL_ClearProperty(props: Any, name: Any, /) -> bool:
+ """bool SDL_ClearProperty(SDL_PropertiesID props, const char *name)"""
+
+ @staticmethod
+ def SDL_ClearSurface(surface: Any, r: float, g: float, b: float, a: float, /) -> bool:
+ """bool SDL_ClearSurface(SDL_Surface *surface, float r, float g, float b, float a)"""
+
+ @staticmethod
+ def SDL_ClickTrayEntry(entry: Any, /) -> None:
+ """void SDL_ClickTrayEntry(SDL_TrayEntry *entry)"""
+
+ @staticmethod
+ def SDL_CloseAsyncIO(asyncio: Any, flush: bool, queue: Any, userdata: Any, /) -> bool:
+ """bool SDL_CloseAsyncIO(SDL_AsyncIO *asyncio, bool flush, SDL_AsyncIOQueue *queue, void *userdata)"""
+
+ @staticmethod
+ def SDL_CloseAudioDevice(devid: Any, /) -> None:
+ """void SDL_CloseAudioDevice(SDL_AudioDeviceID devid)"""
+
+ @staticmethod
+ def SDL_CloseCamera(camera: Any, /) -> None:
+ """void SDL_CloseCamera(SDL_Camera *camera)"""
+
+ @staticmethod
+ def SDL_CloseGamepad(gamepad: Any, /) -> None:
+ """void SDL_CloseGamepad(SDL_Gamepad *gamepad)"""
+
+ @staticmethod
+ def SDL_CloseHaptic(haptic: Any, /) -> None:
+ """void SDL_CloseHaptic(SDL_Haptic *haptic)"""
+
+ @staticmethod
+ def SDL_CloseIO(context: Any, /) -> bool:
+ """bool SDL_CloseIO(SDL_IOStream *context)"""
+
+ @staticmethod
+ def SDL_CloseJoystick(joystick: Any, /) -> None:
+ """void SDL_CloseJoystick(SDL_Joystick *joystick)"""
+
+ @staticmethod
+ def SDL_CloseSensor(sensor: Any, /) -> None:
+ """void SDL_CloseSensor(SDL_Sensor *sensor)"""
+
+ @staticmethod
+ def SDL_CloseStorage(storage: Any, /) -> bool:
+ """bool SDL_CloseStorage(SDL_Storage *storage)"""
+
+ @staticmethod
+ def SDL_CompareAndSwapAtomicInt(a: Any, oldval: int, newval: int, /) -> bool:
+ """bool SDL_CompareAndSwapAtomicInt(SDL_AtomicInt *a, int oldval, int newval)"""
+
+ @staticmethod
+ def SDL_CompareAndSwapAtomicPointer(a: Any, oldval: Any, newval: Any, /) -> bool:
+ """bool SDL_CompareAndSwapAtomicPointer(void **a, void *oldval, void *newval)"""
+
+ @staticmethod
+ def SDL_CompareAndSwapAtomicU32(a: Any, oldval: Any, newval: Any, /) -> bool:
+ """bool SDL_CompareAndSwapAtomicU32(SDL_AtomicU32 *a, Uint32 oldval, Uint32 newval)"""
+
+ @staticmethod
+ def SDL_ComposeCustomBlendMode(
+ srcColorFactor: Any,
+ dstColorFactor: Any,
+ colorOperation: Any,
+ srcAlphaFactor: Any,
+ dstAlphaFactor: Any,
+ alphaOperation: Any,
+ /,
+ ) -> Any:
+ """SDL_BlendMode SDL_ComposeCustomBlendMode(SDL_BlendFactor srcColorFactor, SDL_BlendFactor dstColorFactor, SDL_BlendOperation colorOperation, SDL_BlendFactor srcAlphaFactor, SDL_BlendFactor dstAlphaFactor, SDL_BlendOperation alphaOperation)"""
+
+ @staticmethod
+ def SDL_ConvertAudioSamples(
+ src_spec: Any, src_data: Any, src_len: int, dst_spec: Any, dst_data: Any, dst_len: Any, /
+ ) -> bool:
+ """bool SDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_data, int src_len, const SDL_AudioSpec *dst_spec, Uint8 **dst_data, int *dst_len)"""
+
+ @staticmethod
+ def SDL_ConvertEventToRenderCoordinates(renderer: Any, event: Any, /) -> bool:
+ """bool SDL_ConvertEventToRenderCoordinates(SDL_Renderer *renderer, SDL_Event *event)"""
+
+ @staticmethod
+ def SDL_ConvertPixels(
+ width: int, height: int, src_format: Any, src: Any, src_pitch: int, dst_format: Any, dst: Any, dst_pitch: int, /
+ ) -> bool:
+ """bool SDL_ConvertPixels(int width, int height, SDL_PixelFormat src_format, const void *src, int src_pitch, SDL_PixelFormat dst_format, void *dst, int dst_pitch)"""
+
+ @staticmethod
+ def SDL_ConvertPixelsAndColorspace(
+ width: int,
+ height: int,
+ src_format: Any,
+ src_colorspace: Any,
+ src_properties: Any,
+ src: Any,
+ src_pitch: int,
+ dst_format: Any,
+ dst_colorspace: Any,
+ dst_properties: Any,
+ dst: Any,
+ dst_pitch: int,
+ /,
+ ) -> bool:
+ """bool SDL_ConvertPixelsAndColorspace(int width, int height, SDL_PixelFormat src_format, SDL_Colorspace src_colorspace, SDL_PropertiesID src_properties, const void *src, int src_pitch, SDL_PixelFormat dst_format, SDL_Colorspace dst_colorspace, SDL_PropertiesID dst_properties, void *dst, int dst_pitch)"""
+
+ @staticmethod
+ def SDL_ConvertSurface(surface: Any, format: Any, /) -> Any:
+ """SDL_Surface *SDL_ConvertSurface(SDL_Surface *surface, SDL_PixelFormat format)"""
+
+ @staticmethod
+ def SDL_ConvertSurfaceAndColorspace(surface: Any, format: Any, palette: Any, colorspace: Any, props: Any, /) -> Any:
+ """SDL_Surface *SDL_ConvertSurfaceAndColorspace(SDL_Surface *surface, SDL_PixelFormat format, SDL_Palette *palette, SDL_Colorspace colorspace, SDL_PropertiesID props)"""
+
+ @staticmethod
+ def SDL_CopyFile(oldpath: Any, newpath: Any, /) -> bool:
+ """bool SDL_CopyFile(const char *oldpath, const char *newpath)"""
+
+ @staticmethod
+ def SDL_CopyGPUBufferToBuffer(copy_pass: Any, source: Any, destination: Any, size: Any, cycle: bool, /) -> None:
+ """void SDL_CopyGPUBufferToBuffer(SDL_GPUCopyPass *copy_pass, const SDL_GPUBufferLocation *source, const SDL_GPUBufferLocation *destination, Uint32 size, bool cycle)"""
+
+ @staticmethod
+ def SDL_CopyGPUTextureToTexture(
+ copy_pass: Any, source: Any, destination: Any, w: Any, h: Any, d: Any, cycle: bool, /
+ ) -> None:
+ """void SDL_CopyGPUTextureToTexture(SDL_GPUCopyPass *copy_pass, const SDL_GPUTextureLocation *source, const SDL_GPUTextureLocation *destination, Uint32 w, Uint32 h, Uint32 d, bool cycle)"""
+
+ @staticmethod
+ def SDL_CopyProperties(src: Any, dst: Any, /) -> bool:
+ """bool SDL_CopyProperties(SDL_PropertiesID src, SDL_PropertiesID dst)"""
+
+ @staticmethod
+ def SDL_CopyStorageFile(storage: Any, oldpath: Any, newpath: Any, /) -> bool:
+ """bool SDL_CopyStorageFile(SDL_Storage *storage, const char *oldpath, const char *newpath)"""
+
+ @staticmethod
+ def SDL_CreateAsyncIOQueue() -> Any:
+ """SDL_AsyncIOQueue *SDL_CreateAsyncIOQueue(void)"""
+
+ @staticmethod
+ def SDL_CreateAudioStream(src_spec: Any, dst_spec: Any, /) -> Any:
+ """SDL_AudioStream *SDL_CreateAudioStream(const SDL_AudioSpec *src_spec, const SDL_AudioSpec *dst_spec)"""
+
+ @staticmethod
+ def SDL_CreateColorCursor(surface: Any, hot_x: int, hot_y: int, /) -> Any:
+ """SDL_Cursor *SDL_CreateColorCursor(SDL_Surface *surface, int hot_x, int hot_y)"""
+
+ @staticmethod
+ def SDL_CreateCondition() -> Any:
+ """SDL_Condition *SDL_CreateCondition(void)"""
+
+ @staticmethod
+ def SDL_CreateCursor(data: Any, mask: Any, w: int, h: int, hot_x: int, hot_y: int, /) -> Any:
+ """SDL_Cursor *SDL_CreateCursor(const Uint8 *data, const Uint8 *mask, int w, int h, int hot_x, int hot_y)"""
+
+ @staticmethod
+ def SDL_CreateDirectory(path: Any, /) -> bool:
+ """bool SDL_CreateDirectory(const char *path)"""
+
+ @staticmethod
+ def SDL_CreateEnvironment(populated: bool, /) -> Any:
+ """SDL_Environment *SDL_CreateEnvironment(bool populated)"""
+
+ @staticmethod
+ def SDL_CreateGPUBuffer(device: Any, createinfo: Any, /) -> Any:
+ """SDL_GPUBuffer *SDL_CreateGPUBuffer(SDL_GPUDevice *device, const SDL_GPUBufferCreateInfo *createinfo)"""
+
+ @staticmethod
+ def SDL_CreateGPUComputePipeline(device: Any, createinfo: Any, /) -> Any:
+ """SDL_GPUComputePipeline *SDL_CreateGPUComputePipeline(SDL_GPUDevice *device, const SDL_GPUComputePipelineCreateInfo *createinfo)"""
+
+ @staticmethod
+ def SDL_CreateGPUDevice(format_flags: Any, debug_mode: bool, name: Any, /) -> Any:
+ """SDL_GPUDevice *SDL_CreateGPUDevice(SDL_GPUShaderFormat format_flags, bool debug_mode, const char *name)"""
+
+ @staticmethod
+ def SDL_CreateGPUDeviceWithProperties(props: Any, /) -> Any:
+ """SDL_GPUDevice *SDL_CreateGPUDeviceWithProperties(SDL_PropertiesID props)"""
+
+ @staticmethod
+ def SDL_CreateGPUGraphicsPipeline(device: Any, createinfo: Any, /) -> Any:
+ """SDL_GPUGraphicsPipeline *SDL_CreateGPUGraphicsPipeline(SDL_GPUDevice *device, const SDL_GPUGraphicsPipelineCreateInfo *createinfo)"""
+
+ @staticmethod
+ def SDL_CreateGPUSampler(device: Any, createinfo: Any, /) -> Any:
+ """SDL_GPUSampler *SDL_CreateGPUSampler(SDL_GPUDevice *device, const SDL_GPUSamplerCreateInfo *createinfo)"""
+
+ @staticmethod
+ def SDL_CreateGPUShader(device: Any, createinfo: Any, /) -> Any:
+ """SDL_GPUShader *SDL_CreateGPUShader(SDL_GPUDevice *device, const SDL_GPUShaderCreateInfo *createinfo)"""
+
+ @staticmethod
+ def SDL_CreateGPUTexture(device: Any, createinfo: Any, /) -> Any:
+ """SDL_GPUTexture *SDL_CreateGPUTexture(SDL_GPUDevice *device, const SDL_GPUTextureCreateInfo *createinfo)"""
+
+ @staticmethod
+ def SDL_CreateGPUTransferBuffer(device: Any, createinfo: Any, /) -> Any:
+ """SDL_GPUTransferBuffer *SDL_CreateGPUTransferBuffer(SDL_GPUDevice *device, const SDL_GPUTransferBufferCreateInfo *createinfo)"""
+
+ @staticmethod
+ def SDL_CreateHapticEffect(haptic: Any, effect: Any, /) -> int:
+ """int SDL_CreateHapticEffect(SDL_Haptic *haptic, const SDL_HapticEffect *effect)"""
+
+ @staticmethod
+ def SDL_CreateMutex() -> Any:
+ """SDL_Mutex *SDL_CreateMutex(void)"""
+
+ @staticmethod
+ def SDL_CreatePalette(ncolors: int, /) -> Any:
+ """SDL_Palette *SDL_CreatePalette(int ncolors)"""
+
+ @staticmethod
+ def SDL_CreatePopupWindow(parent: Any, offset_x: int, offset_y: int, w: int, h: int, flags: Any, /) -> Any:
+ """SDL_Window *SDL_CreatePopupWindow(SDL_Window *parent, int offset_x, int offset_y, int w, int h, SDL_WindowFlags flags)"""
+
+ @staticmethod
+ def SDL_CreateProcess(args: Any, pipe_stdio: bool, /) -> Any:
+ """SDL_Process *SDL_CreateProcess(const char * const *args, bool pipe_stdio)"""
+
+ @staticmethod
+ def SDL_CreateProcessWithProperties(props: Any, /) -> Any:
+ """SDL_Process *SDL_CreateProcessWithProperties(SDL_PropertiesID props)"""
+
+ @staticmethod
+ def SDL_CreateProperties() -> Any:
+ """SDL_PropertiesID SDL_CreateProperties(void)"""
+
+ @staticmethod
+ def SDL_CreateRWLock() -> Any:
+ """SDL_RWLock *SDL_CreateRWLock(void)"""
+
+ @staticmethod
+ def SDL_CreateRenderer(window: Any, name: Any, /) -> Any:
+ """SDL_Renderer *SDL_CreateRenderer(SDL_Window *window, const char *name)"""
+
+ @staticmethod
+ def SDL_CreateRendererWithProperties(props: Any, /) -> Any:
+ """SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props)"""
+
+ @staticmethod
+ def SDL_CreateSemaphore(initial_value: Any, /) -> Any:
+ """SDL_Semaphore *SDL_CreateSemaphore(Uint32 initial_value)"""
+
+ @staticmethod
+ def SDL_CreateSoftwareRenderer(surface: Any, /) -> Any:
+ """SDL_Renderer *SDL_CreateSoftwareRenderer(SDL_Surface *surface)"""
+
+ @staticmethod
+ def SDL_CreateStorageDirectory(storage: Any, path: Any, /) -> bool:
+ """bool SDL_CreateStorageDirectory(SDL_Storage *storage, const char *path)"""
+
+ @staticmethod
+ def SDL_CreateSurface(width: int, height: int, format: Any, /) -> Any:
+ """SDL_Surface *SDL_CreateSurface(int width, int height, SDL_PixelFormat format)"""
+
+ @staticmethod
+ def SDL_CreateSurfaceFrom(width: int, height: int, format: Any, pixels: Any, pitch: int, /) -> Any:
+ """SDL_Surface *SDL_CreateSurfaceFrom(int width, int height, SDL_PixelFormat format, void *pixels, int pitch)"""
+
+ @staticmethod
+ def SDL_CreateSurfacePalette(surface: Any, /) -> Any:
+ """SDL_Palette *SDL_CreateSurfacePalette(SDL_Surface *surface)"""
+
+ @staticmethod
+ def SDL_CreateSystemCursor(id: Any, /) -> Any:
+ """SDL_Cursor *SDL_CreateSystemCursor(SDL_SystemCursor id)"""
+
+ @staticmethod
+ def SDL_CreateTexture(renderer: Any, format: Any, access: Any, w: int, h: int, /) -> Any:
+ """SDL_Texture *SDL_CreateTexture(SDL_Renderer *renderer, SDL_PixelFormat format, SDL_TextureAccess access, int w, int h)"""
+
+ @staticmethod
+ def SDL_CreateTextureFromSurface(renderer: Any, surface: Any, /) -> Any:
+ """SDL_Texture *SDL_CreateTextureFromSurface(SDL_Renderer *renderer, SDL_Surface *surface)"""
+
+ @staticmethod
+ def SDL_CreateTextureWithProperties(renderer: Any, props: Any, /) -> Any:
+ """SDL_Texture *SDL_CreateTextureWithProperties(SDL_Renderer *renderer, SDL_PropertiesID props)"""
+
+ @staticmethod
+ def SDL_CreateThreadRuntime(fn: Any, name: Any, data: Any, pfnBeginThread: Any, pfnEndThread: Any, /) -> Any:
+ """SDL_Thread *SDL_CreateThreadRuntime(SDL_ThreadFunction fn, const char *name, void *data, SDL_FunctionPointer pfnBeginThread, SDL_FunctionPointer pfnEndThread)"""
+
+ @staticmethod
+ def SDL_CreateThreadWithPropertiesRuntime(props: Any, pfnBeginThread: Any, pfnEndThread: Any, /) -> Any:
+ """SDL_Thread *SDL_CreateThreadWithPropertiesRuntime(SDL_PropertiesID props, SDL_FunctionPointer pfnBeginThread, SDL_FunctionPointer pfnEndThread)"""
+
+ @staticmethod
+ def SDL_CreateTray(icon: Any, tooltip: Any, /) -> Any:
+ """SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)"""
+
+ @staticmethod
+ def SDL_CreateTrayMenu(tray: Any, /) -> Any:
+ """SDL_TrayMenu *SDL_CreateTrayMenu(SDL_Tray *tray)"""
+
+ @staticmethod
+ def SDL_CreateTraySubmenu(entry: Any, /) -> Any:
+ """SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry)"""
+
+ @staticmethod
+ def SDL_CreateWindow(title: Any, w: int, h: int, flags: Any, /) -> Any:
+ """SDL_Window *SDL_CreateWindow(const char *title, int w, int h, SDL_WindowFlags flags)"""
+
+ @staticmethod
+ def SDL_CreateWindowAndRenderer(
+ title: Any, width: int, height: int, window_flags: Any, window: Any, renderer: Any, /
+ ) -> bool:
+ """bool SDL_CreateWindowAndRenderer(const char *title, int width, int height, SDL_WindowFlags window_flags, SDL_Window **window, SDL_Renderer **renderer)"""
+
+ @staticmethod
+ def SDL_CreateWindowWithProperties(props: Any, /) -> Any:
+ """SDL_Window *SDL_CreateWindowWithProperties(SDL_PropertiesID props)"""
+
+ @staticmethod
+ def SDL_CursorVisible() -> bool:
+ """bool SDL_CursorVisible(void)"""
+
+ @staticmethod
+ def SDL_DateTimeToTime(dt: Any, ticks: Any, /) -> bool:
+ """bool SDL_DateTimeToTime(const SDL_DateTime *dt, SDL_Time *ticks)"""
+
+ @staticmethod
+ def SDL_Delay(ms: Any, /) -> None:
+ """void SDL_Delay(Uint32 ms)"""
+
+ @staticmethod
+ def SDL_DelayNS(ns: Any, /) -> None:
+ """void SDL_DelayNS(Uint64 ns)"""
+
+ @staticmethod
+ def SDL_DelayPrecise(ns: Any, /) -> None:
+ """void SDL_DelayPrecise(Uint64 ns)"""
+
+ @staticmethod
+ def SDL_DestroyAsyncIOQueue(queue: Any, /) -> None:
+ """void SDL_DestroyAsyncIOQueue(SDL_AsyncIOQueue *queue)"""
+
+ @staticmethod
+ def SDL_DestroyAudioStream(stream: Any, /) -> None:
+ """void SDL_DestroyAudioStream(SDL_AudioStream *stream)"""
+
+ @staticmethod
+ def SDL_DestroyCondition(cond: Any, /) -> None:
+ """void SDL_DestroyCondition(SDL_Condition *cond)"""
+
+ @staticmethod
+ def SDL_DestroyCursor(cursor: Any, /) -> None:
+ """void SDL_DestroyCursor(SDL_Cursor *cursor)"""
+
+ @staticmethod
+ def SDL_DestroyEnvironment(env: Any, /) -> None:
+ """void SDL_DestroyEnvironment(SDL_Environment *env)"""
+
+ @staticmethod
+ def SDL_DestroyGPUDevice(device: Any, /) -> None:
+ """void SDL_DestroyGPUDevice(SDL_GPUDevice *device)"""
+
+ @staticmethod
+ def SDL_DestroyHapticEffect(haptic: Any, effect: int, /) -> None:
+ """void SDL_DestroyHapticEffect(SDL_Haptic *haptic, int effect)"""
+
+ @staticmethod
+ def SDL_DestroyMutex(mutex: Any, /) -> None:
+ """void SDL_DestroyMutex(SDL_Mutex *mutex)"""
+
+ @staticmethod
+ def SDL_DestroyPalette(palette: Any, /) -> None:
+ """void SDL_DestroyPalette(SDL_Palette *palette)"""
+
+ @staticmethod
+ def SDL_DestroyProcess(process: Any, /) -> None:
+ """void SDL_DestroyProcess(SDL_Process *process)"""
+
+ @staticmethod
+ def SDL_DestroyProperties(props: Any, /) -> None:
+ """void SDL_DestroyProperties(SDL_PropertiesID props)"""
+
+ @staticmethod
+ def SDL_DestroyRWLock(rwlock: Any, /) -> None:
+ """void SDL_DestroyRWLock(SDL_RWLock *rwlock)"""
+
+ @staticmethod
+ def SDL_DestroyRenderer(renderer: Any, /) -> None:
+ """void SDL_DestroyRenderer(SDL_Renderer *renderer)"""
+
+ @staticmethod
+ def SDL_DestroySemaphore(sem: Any, /) -> None:
+ """void SDL_DestroySemaphore(SDL_Semaphore *sem)"""
+
+ @staticmethod
+ def SDL_DestroySurface(surface: Any, /) -> None:
+ """void SDL_DestroySurface(SDL_Surface *surface)"""
+
+ @staticmethod
+ def SDL_DestroyTexture(texture: Any, /) -> None:
+ """void SDL_DestroyTexture(SDL_Texture *texture)"""
+
+ @staticmethod
+ def SDL_DestroyTray(tray: Any, /) -> None:
+ """void SDL_DestroyTray(SDL_Tray *tray)"""
+
+ @staticmethod
+ def SDL_DestroyWindow(window: Any, /) -> None:
+ """void SDL_DestroyWindow(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_DestroyWindowSurface(window: Any, /) -> bool:
+ """bool SDL_DestroyWindowSurface(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_DetachThread(thread: Any, /) -> None:
+ """void SDL_DetachThread(SDL_Thread *thread)"""
+
+ @staticmethod
+ def SDL_DetachVirtualJoystick(instance_id: Any, /) -> bool:
+ """bool SDL_DetachVirtualJoystick(SDL_JoystickID instance_id)"""
+
+ @staticmethod
+ def SDL_DisableScreenSaver() -> bool:
+ """bool SDL_DisableScreenSaver(void)"""
+
+ @staticmethod
+ def SDL_DispatchGPUCompute(compute_pass: Any, groupcount_x: Any, groupcount_y: Any, groupcount_z: Any, /) -> None:
+ """void SDL_DispatchGPUCompute(SDL_GPUComputePass *compute_pass, Uint32 groupcount_x, Uint32 groupcount_y, Uint32 groupcount_z)"""
+
+ @staticmethod
+ def SDL_DispatchGPUComputeIndirect(compute_pass: Any, buffer: Any, offset: Any, /) -> None:
+ """void SDL_DispatchGPUComputeIndirect(SDL_GPUComputePass *compute_pass, SDL_GPUBuffer *buffer, Uint32 offset)"""
+
+ @staticmethod
+ def SDL_DownloadFromGPUBuffer(copy_pass: Any, source: Any, destination: Any, /) -> None:
+ """void SDL_DownloadFromGPUBuffer(SDL_GPUCopyPass *copy_pass, const SDL_GPUBufferRegion *source, const SDL_GPUTransferBufferLocation *destination)"""
+
+ @staticmethod
+ def SDL_DownloadFromGPUTexture(copy_pass: Any, source: Any, destination: Any, /) -> None:
+ """void SDL_DownloadFromGPUTexture(SDL_GPUCopyPass *copy_pass, const SDL_GPUTextureRegion *source, const SDL_GPUTextureTransferInfo *destination)"""
+
+ @staticmethod
+ def SDL_DrawGPUIndexedPrimitives(
+ render_pass: Any,
+ num_indices: Any,
+ num_instances: Any,
+ first_index: Any,
+ vertex_offset: Any,
+ first_instance: Any,
+ /,
+ ) -> None:
+ """void SDL_DrawGPUIndexedPrimitives(SDL_GPURenderPass *render_pass, Uint32 num_indices, Uint32 num_instances, Uint32 first_index, Sint32 vertex_offset, Uint32 first_instance)"""
+
+ @staticmethod
+ def SDL_DrawGPUIndexedPrimitivesIndirect(render_pass: Any, buffer: Any, offset: Any, draw_count: Any, /) -> None:
+ """void SDL_DrawGPUIndexedPrimitivesIndirect(SDL_GPURenderPass *render_pass, SDL_GPUBuffer *buffer, Uint32 offset, Uint32 draw_count)"""
+
+ @staticmethod
+ def SDL_DrawGPUPrimitives(
+ render_pass: Any, num_vertices: Any, num_instances: Any, first_vertex: Any, first_instance: Any, /
+ ) -> None:
+ """void SDL_DrawGPUPrimitives(SDL_GPURenderPass *render_pass, Uint32 num_vertices, Uint32 num_instances, Uint32 first_vertex, Uint32 first_instance)"""
+
+ @staticmethod
+ def SDL_DrawGPUPrimitivesIndirect(render_pass: Any, buffer: Any, offset: Any, draw_count: Any, /) -> None:
+ """void SDL_DrawGPUPrimitivesIndirect(SDL_GPURenderPass *render_pass, SDL_GPUBuffer *buffer, Uint32 offset, Uint32 draw_count)"""
+
+ @staticmethod
+ def SDL_DuplicateSurface(surface: Any, /) -> Any:
+ """SDL_Surface *SDL_DuplicateSurface(SDL_Surface *surface)"""
+
+ @staticmethod
+ def SDL_EGL_GetCurrentConfig() -> Any:
+ """SDL_EGLConfig SDL_EGL_GetCurrentConfig(void)"""
+
+ @staticmethod
+ def SDL_EGL_GetCurrentDisplay() -> Any:
+ """SDL_EGLDisplay SDL_EGL_GetCurrentDisplay(void)"""
+
+ @staticmethod
+ def SDL_EGL_GetProcAddress(proc: Any, /) -> Any:
+ """SDL_FunctionPointer SDL_EGL_GetProcAddress(const char *proc)"""
+
+ @staticmethod
+ def SDL_EGL_GetWindowSurface(window: Any, /) -> Any:
+ """SDL_EGLSurface SDL_EGL_GetWindowSurface(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_EGL_SetAttributeCallbacks(
+ platformAttribCallback: Any, surfaceAttribCallback: Any, contextAttribCallback: Any, userdata: Any, /
+ ) -> None:
+ """void SDL_EGL_SetAttributeCallbacks(SDL_EGLAttribArrayCallback platformAttribCallback, SDL_EGLIntArrayCallback surfaceAttribCallback, SDL_EGLIntArrayCallback contextAttribCallback, void *userdata)"""
+
+ @staticmethod
+ def SDL_EnableScreenSaver() -> bool:
+ """bool SDL_EnableScreenSaver(void)"""
+
+ @staticmethod
+ def SDL_EndGPUComputePass(compute_pass: Any, /) -> None:
+ """void SDL_EndGPUComputePass(SDL_GPUComputePass *compute_pass)"""
+
+ @staticmethod
+ def SDL_EndGPUCopyPass(copy_pass: Any, /) -> None:
+ """void SDL_EndGPUCopyPass(SDL_GPUCopyPass *copy_pass)"""
+
+ @staticmethod
+ def SDL_EndGPURenderPass(render_pass: Any, /) -> None:
+ """void SDL_EndGPURenderPass(SDL_GPURenderPass *render_pass)"""
+
+ @staticmethod
+ def SDL_EnumerateDirectory(path: Any, callback: Any, userdata: Any, /) -> bool:
+ """bool SDL_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback callback, void *userdata)"""
+
+ @staticmethod
+ def SDL_EnumerateProperties(props: Any, callback: Any, userdata: Any, /) -> bool:
+ """bool SDL_EnumerateProperties(SDL_PropertiesID props, SDL_EnumeratePropertiesCallback callback, void *userdata)"""
+
+ @staticmethod
+ def SDL_EnumerateStorageDirectory(storage: Any, path: Any, callback: Any, userdata: Any, /) -> bool:
+ """bool SDL_EnumerateStorageDirectory(SDL_Storage *storage, const char *path, SDL_EnumerateDirectoryCallback callback, void *userdata)"""
+
+ @staticmethod
+ def SDL_EventEnabled(type: Any, /) -> bool:
+ """bool SDL_EventEnabled(Uint32 type)"""
+
+ @staticmethod
+ def SDL_FillSurfaceRect(dst: Any, rect: Any, color: Any, /) -> bool:
+ """bool SDL_FillSurfaceRect(SDL_Surface *dst, const SDL_Rect *rect, Uint32 color)"""
+
+ @staticmethod
+ def SDL_FillSurfaceRects(dst: Any, rects: Any, count: int, color: Any, /) -> bool:
+ """bool SDL_FillSurfaceRects(SDL_Surface *dst, const SDL_Rect *rects, int count, Uint32 color)"""
+
+ @staticmethod
+ def SDL_FilterEvents(filter: Any, userdata: Any, /) -> None:
+ """void SDL_FilterEvents(SDL_EventFilter filter, void *userdata)"""
+
+ @staticmethod
+ def SDL_FlashWindow(window: Any, operation: Any, /) -> bool:
+ """bool SDL_FlashWindow(SDL_Window *window, SDL_FlashOperation operation)"""
+
+ @staticmethod
+ def SDL_FlipSurface(surface: Any, flip: Any, /) -> bool:
+ """bool SDL_FlipSurface(SDL_Surface *surface, SDL_FlipMode flip)"""
+
+ @staticmethod
+ def SDL_FlushAudioStream(stream: Any, /) -> bool:
+ """bool SDL_FlushAudioStream(SDL_AudioStream *stream)"""
+
+ @staticmethod
+ def SDL_FlushEvent(type: Any, /) -> None:
+ """void SDL_FlushEvent(Uint32 type)"""
+
+ @staticmethod
+ def SDL_FlushEvents(minType: Any, maxType: Any, /) -> None:
+ """void SDL_FlushEvents(Uint32 minType, Uint32 maxType)"""
+
+ @staticmethod
+ def SDL_FlushIO(context: Any, /) -> bool:
+ """bool SDL_FlushIO(SDL_IOStream *context)"""
+
+ @staticmethod
+ def SDL_FlushRenderer(renderer: Any, /) -> bool:
+ """bool SDL_FlushRenderer(SDL_Renderer *renderer)"""
+
+ @staticmethod
+ def SDL_GL_CreateContext(window: Any, /) -> Any:
+ """SDL_GLContext SDL_GL_CreateContext(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_GL_DestroyContext(context: Any, /) -> bool:
+ """bool SDL_GL_DestroyContext(SDL_GLContext context)"""
+
+ @staticmethod
+ def SDL_GL_ExtensionSupported(extension: Any, /) -> bool:
+ """bool SDL_GL_ExtensionSupported(const char *extension)"""
+
+ @staticmethod
+ def SDL_GL_GetAttribute(attr: Any, value: Any, /) -> bool:
+ """bool SDL_GL_GetAttribute(SDL_GLAttr attr, int *value)"""
+
+ @staticmethod
+ def SDL_GL_GetCurrentContext() -> Any:
+ """SDL_GLContext SDL_GL_GetCurrentContext(void)"""
+
+ @staticmethod
+ def SDL_GL_GetCurrentWindow() -> Any:
+ """SDL_Window *SDL_GL_GetCurrentWindow(void)"""
+
+ @staticmethod
+ def SDL_GL_GetProcAddress(proc: Any, /) -> Any:
+ """SDL_FunctionPointer SDL_GL_GetProcAddress(const char *proc)"""
+
+ @staticmethod
+ def SDL_GL_GetSwapInterval(interval: Any, /) -> bool:
+ """bool SDL_GL_GetSwapInterval(int *interval)"""
+
+ @staticmethod
+ def SDL_GL_LoadLibrary(path: Any, /) -> bool:
+ """bool SDL_GL_LoadLibrary(const char *path)"""
+
+ @staticmethod
+ def SDL_GL_MakeCurrent(window: Any, context: Any, /) -> bool:
+ """bool SDL_GL_MakeCurrent(SDL_Window *window, SDL_GLContext context)"""
+
+ @staticmethod
+ def SDL_GL_ResetAttributes() -> None:
+ """void SDL_GL_ResetAttributes(void)"""
+
+ @staticmethod
+ def SDL_GL_SetAttribute(attr: Any, value: int, /) -> bool:
+ """bool SDL_GL_SetAttribute(SDL_GLAttr attr, int value)"""
+
+ @staticmethod
+ def SDL_GL_SetSwapInterval(interval: int, /) -> bool:
+ """bool SDL_GL_SetSwapInterval(int interval)"""
+
+ @staticmethod
+ def SDL_GL_SwapWindow(window: Any, /) -> bool:
+ """bool SDL_GL_SwapWindow(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_GL_UnloadLibrary() -> None:
+ """void SDL_GL_UnloadLibrary(void)"""
+
+ @staticmethod
+ def SDL_GPUSupportsProperties(props: Any, /) -> bool:
+ """bool SDL_GPUSupportsProperties(SDL_PropertiesID props)"""
+
+ @staticmethod
+ def SDL_GPUSupportsShaderFormats(format_flags: Any, name: Any, /) -> bool:
+ """bool SDL_GPUSupportsShaderFormats(SDL_GPUShaderFormat format_flags, const char *name)"""
+
+ @staticmethod
+ def SDL_GPUTextureFormatTexelBlockSize(format: Any, /) -> Any:
+ """Uint32 SDL_GPUTextureFormatTexelBlockSize(SDL_GPUTextureFormat format)"""
+
+ @staticmethod
+ def SDL_GPUTextureSupportsFormat(device: Any, format: Any, type: Any, usage: Any, /) -> bool:
+ """bool SDL_GPUTextureSupportsFormat(SDL_GPUDevice *device, SDL_GPUTextureFormat format, SDL_GPUTextureType type, SDL_GPUTextureUsageFlags usage)"""
+
+ @staticmethod
+ def SDL_GPUTextureSupportsSampleCount(device: Any, format: Any, sample_count: Any, /) -> bool:
+ """bool SDL_GPUTextureSupportsSampleCount(SDL_GPUDevice *device, SDL_GPUTextureFormat format, SDL_GPUSampleCount sample_count)"""
+
+ @staticmethod
+ def SDL_GUIDToString(guid: Any, pszGUID: Any, cbGUID: int, /) -> None:
+ """void SDL_GUIDToString(SDL_GUID guid, char *pszGUID, int cbGUID)"""
+
+ @staticmethod
+ def SDL_GamepadConnected(gamepad: Any, /) -> bool:
+ """bool SDL_GamepadConnected(SDL_Gamepad *gamepad)"""
+
+ @staticmethod
+ def SDL_GamepadEventsEnabled() -> bool:
+ """bool SDL_GamepadEventsEnabled(void)"""
+
+ @staticmethod
+ def SDL_GamepadHasAxis(gamepad: Any, axis: Any, /) -> bool:
+ """bool SDL_GamepadHasAxis(SDL_Gamepad *gamepad, SDL_GamepadAxis axis)"""
+
+ @staticmethod
+ def SDL_GamepadHasButton(gamepad: Any, button: Any, /) -> bool:
+ """bool SDL_GamepadHasButton(SDL_Gamepad *gamepad, SDL_GamepadButton button)"""
+
+ @staticmethod
+ def SDL_GamepadHasSensor(gamepad: Any, type: Any, /) -> bool:
+ """bool SDL_GamepadHasSensor(SDL_Gamepad *gamepad, SDL_SensorType type)"""
+
+ @staticmethod
+ def SDL_GamepadSensorEnabled(gamepad: Any, type: Any, /) -> bool:
+ """bool SDL_GamepadSensorEnabled(SDL_Gamepad *gamepad, SDL_SensorType type)"""
+
+ @staticmethod
+ def SDL_GenerateMipmapsForGPUTexture(command_buffer: Any, texture: Any, /) -> None:
+ """void SDL_GenerateMipmapsForGPUTexture(SDL_GPUCommandBuffer *command_buffer, SDL_GPUTexture *texture)"""
+
+ @staticmethod
+ def SDL_GetAppMetadataProperty(name: Any, /) -> Any:
+ """const char *SDL_GetAppMetadataProperty(const char *name)"""
+
+ @staticmethod
+ def SDL_GetAssertionHandler(puserdata: Any, /) -> Any:
+ """SDL_AssertionHandler SDL_GetAssertionHandler(void **puserdata)"""
+
+ @staticmethod
+ def SDL_GetAssertionReport() -> Any:
+ """const SDL_AssertData *SDL_GetAssertionReport(void)"""
+
+ @staticmethod
+ def SDL_GetAsyncIOResult(queue: Any, outcome: Any, /) -> bool:
+ """bool SDL_GetAsyncIOResult(SDL_AsyncIOQueue *queue, SDL_AsyncIOOutcome *outcome)"""
+
+ @staticmethod
+ def SDL_GetAsyncIOSize(asyncio: Any, /) -> Any:
+ """Sint64 SDL_GetAsyncIOSize(SDL_AsyncIO *asyncio)"""
+
+ @staticmethod
+ def SDL_GetAtomicInt(a: Any, /) -> int:
+ """int SDL_GetAtomicInt(SDL_AtomicInt *a)"""
+
+ @staticmethod
+ def SDL_GetAtomicPointer(a: Any, /) -> Any:
+ """void *SDL_GetAtomicPointer(void **a)"""
+
+ @staticmethod
+ def SDL_GetAtomicU32(a: Any, /) -> Any:
+ """Uint32 SDL_GetAtomicU32(SDL_AtomicU32 *a)"""
+
+ @staticmethod
+ def SDL_GetAudioDeviceChannelMap(devid: Any, count: Any, /) -> Any:
+ """int *SDL_GetAudioDeviceChannelMap(SDL_AudioDeviceID devid, int *count)"""
+
+ @staticmethod
+ def SDL_GetAudioDeviceFormat(devid: Any, spec: Any, sample_frames: Any, /) -> bool:
+ """bool SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SDL_AudioSpec *spec, int *sample_frames)"""
+
+ @staticmethod
+ def SDL_GetAudioDeviceGain(devid: Any, /) -> float:
+ """float SDL_GetAudioDeviceGain(SDL_AudioDeviceID devid)"""
+
+ @staticmethod
+ def SDL_GetAudioDeviceName(devid: Any, /) -> Any:
+ """const char *SDL_GetAudioDeviceName(SDL_AudioDeviceID devid)"""
+
+ @staticmethod
+ def SDL_GetAudioDriver(index: int, /) -> Any:
+ """const char *SDL_GetAudioDriver(int index)"""
+
+ @staticmethod
+ def SDL_GetAudioFormatName(format: Any, /) -> Any:
+ """const char *SDL_GetAudioFormatName(SDL_AudioFormat format)"""
+
+ @staticmethod
+ def SDL_GetAudioPlaybackDevices(count: Any, /) -> Any:
+ """SDL_AudioDeviceID *SDL_GetAudioPlaybackDevices(int *count)"""
+
+ @staticmethod
+ def SDL_GetAudioRecordingDevices(count: Any, /) -> Any:
+ """SDL_AudioDeviceID *SDL_GetAudioRecordingDevices(int *count)"""
+
+ @staticmethod
+ def SDL_GetAudioStreamAvailable(stream: Any, /) -> int:
+ """int SDL_GetAudioStreamAvailable(SDL_AudioStream *stream)"""
+
+ @staticmethod
+ def SDL_GetAudioStreamData(stream: Any, buf: Any, len: int, /) -> int:
+ """int SDL_GetAudioStreamData(SDL_AudioStream *stream, void *buf, int len)"""
+
+ @staticmethod
+ def SDL_GetAudioStreamDevice(stream: Any, /) -> Any:
+ """SDL_AudioDeviceID SDL_GetAudioStreamDevice(SDL_AudioStream *stream)"""
+
+ @staticmethod
+ def SDL_GetAudioStreamFormat(stream: Any, src_spec: Any, dst_spec: Any, /) -> bool:
+ """bool SDL_GetAudioStreamFormat(SDL_AudioStream *stream, SDL_AudioSpec *src_spec, SDL_AudioSpec *dst_spec)"""
+
+ @staticmethod
+ def SDL_GetAudioStreamFrequencyRatio(stream: Any, /) -> float:
+ """float SDL_GetAudioStreamFrequencyRatio(SDL_AudioStream *stream)"""
+
+ @staticmethod
+ def SDL_GetAudioStreamGain(stream: Any, /) -> float:
+ """float SDL_GetAudioStreamGain(SDL_AudioStream *stream)"""
+
+ @staticmethod
+ def SDL_GetAudioStreamInputChannelMap(stream: Any, count: Any, /) -> Any:
+ """int *SDL_GetAudioStreamInputChannelMap(SDL_AudioStream *stream, int *count)"""
+
+ @staticmethod
+ def SDL_GetAudioStreamOutputChannelMap(stream: Any, count: Any, /) -> Any:
+ """int *SDL_GetAudioStreamOutputChannelMap(SDL_AudioStream *stream, int *count)"""
+
+ @staticmethod
+ def SDL_GetAudioStreamProperties(stream: Any, /) -> Any:
+ """SDL_PropertiesID SDL_GetAudioStreamProperties(SDL_AudioStream *stream)"""
+
+ @staticmethod
+ def SDL_GetAudioStreamQueued(stream: Any, /) -> int:
+ """int SDL_GetAudioStreamQueued(SDL_AudioStream *stream)"""
+
+ @staticmethod
+ def SDL_GetBasePath() -> Any:
+ """const char *SDL_GetBasePath(void)"""
+
+ @staticmethod
+ def SDL_GetBooleanProperty(props: Any, name: Any, default_value: bool, /) -> bool:
+ """bool SDL_GetBooleanProperty(SDL_PropertiesID props, const char *name, bool default_value)"""
+
+ @staticmethod
+ def SDL_GetCPUCacheLineSize() -> int:
+ """int SDL_GetCPUCacheLineSize(void)"""
+
+ @staticmethod
+ def SDL_GetCameraDriver(index: int, /) -> Any:
+ """const char *SDL_GetCameraDriver(int index)"""
+
+ @staticmethod
+ def SDL_GetCameraFormat(camera: Any, spec: Any, /) -> bool:
+ """bool SDL_GetCameraFormat(SDL_Camera *camera, SDL_CameraSpec *spec)"""
+
+ @staticmethod
+ def SDL_GetCameraID(camera: Any, /) -> Any:
+ """SDL_CameraID SDL_GetCameraID(SDL_Camera *camera)"""
+
+ @staticmethod
+ def SDL_GetCameraName(instance_id: Any, /) -> Any:
+ """const char *SDL_GetCameraName(SDL_CameraID instance_id)"""
+
+ @staticmethod
+ def SDL_GetCameraPermissionState(camera: Any, /) -> int:
+ """int SDL_GetCameraPermissionState(SDL_Camera *camera)"""
+
+ @staticmethod
+ def SDL_GetCameraPosition(instance_id: Any, /) -> Any:
+ """SDL_CameraPosition SDL_GetCameraPosition(SDL_CameraID instance_id)"""
+
+ @staticmethod
+ def SDL_GetCameraProperties(camera: Any, /) -> Any:
+ """SDL_PropertiesID SDL_GetCameraProperties(SDL_Camera *camera)"""
+
+ @staticmethod
+ def SDL_GetCameraSupportedFormats(instance_id: Any, count: Any, /) -> Any:
+ """SDL_CameraSpec **SDL_GetCameraSupportedFormats(SDL_CameraID instance_id, int *count)"""
+
+ @staticmethod
+ def SDL_GetCameras(count: Any, /) -> Any:
+ """SDL_CameraID *SDL_GetCameras(int *count)"""
+
+ @staticmethod
+ def SDL_GetClipboardData(mime_type: Any, size: Any, /) -> Any:
+ """void *SDL_GetClipboardData(const char *mime_type, size_t *size)"""
+
+ @staticmethod
+ def SDL_GetClipboardMimeTypes(num_mime_types: Any, /) -> Any:
+ """char **SDL_GetClipboardMimeTypes(size_t *num_mime_types)"""
+
+ @staticmethod
+ def SDL_GetClipboardText() -> Any:
+ """char *SDL_GetClipboardText(void)"""
+
+ @staticmethod
+ def SDL_GetClosestFullscreenDisplayMode(
+ displayID: Any, w: int, h: int, refresh_rate: float, include_high_density_modes: bool, closest: Any, /
+ ) -> bool:
+ """bool SDL_GetClosestFullscreenDisplayMode(SDL_DisplayID displayID, int w, int h, float refresh_rate, bool include_high_density_modes, SDL_DisplayMode *closest)"""
+
+ @staticmethod
+ def SDL_GetCurrentAudioDriver() -> Any:
+ """const char *SDL_GetCurrentAudioDriver(void)"""
+
+ @staticmethod
+ def SDL_GetCurrentCameraDriver() -> Any:
+ """const char *SDL_GetCurrentCameraDriver(void)"""
+
+ @staticmethod
+ def SDL_GetCurrentDirectory() -> Any:
+ """char *SDL_GetCurrentDirectory(void)"""
+
+ @staticmethod
+ def SDL_GetCurrentDisplayMode(displayID: Any, /) -> Any:
+ """const SDL_DisplayMode *SDL_GetCurrentDisplayMode(SDL_DisplayID displayID)"""
+
+ @staticmethod
+ def SDL_GetCurrentDisplayOrientation(displayID: Any, /) -> Any:
+ """SDL_DisplayOrientation SDL_GetCurrentDisplayOrientation(SDL_DisplayID displayID)"""
+
+ @staticmethod
+ def SDL_GetCurrentRenderOutputSize(renderer: Any, w: Any, h: Any, /) -> bool:
+ """bool SDL_GetCurrentRenderOutputSize(SDL_Renderer *renderer, int *w, int *h)"""
+
+ @staticmethod
+ def SDL_GetCurrentThreadID() -> Any:
+ """SDL_ThreadID SDL_GetCurrentThreadID(void)"""
+
+ @staticmethod
+ def SDL_GetCurrentTime(ticks: Any, /) -> bool:
+ """bool SDL_GetCurrentTime(SDL_Time *ticks)"""
+
+ @staticmethod
+ def SDL_GetCurrentVideoDriver() -> Any:
+ """const char *SDL_GetCurrentVideoDriver(void)"""
+
+ @staticmethod
+ def SDL_GetCursor() -> Any:
+ """SDL_Cursor *SDL_GetCursor(void)"""
+
+ @staticmethod
+ def SDL_GetDateTimeLocalePreferences(dateFormat: Any, timeFormat: Any, /) -> bool:
+ """bool SDL_GetDateTimeLocalePreferences(SDL_DateFormat *dateFormat, SDL_TimeFormat *timeFormat)"""
+
+ @staticmethod
+ def SDL_GetDayOfWeek(year: int, month: int, day: int, /) -> int:
+ """int SDL_GetDayOfWeek(int year, int month, int day)"""
+
+ @staticmethod
+ def SDL_GetDayOfYear(year: int, month: int, day: int, /) -> int:
+ """int SDL_GetDayOfYear(int year, int month, int day)"""
+
+ @staticmethod
+ def SDL_GetDaysInMonth(year: int, month: int, /) -> int:
+ """int SDL_GetDaysInMonth(int year, int month)"""
+
+ @staticmethod
+ def SDL_GetDefaultAssertionHandler() -> Any:
+ """SDL_AssertionHandler SDL_GetDefaultAssertionHandler(void)"""
+
+ @staticmethod
+ def SDL_GetDefaultCursor() -> Any:
+ """SDL_Cursor *SDL_GetDefaultCursor(void)"""
+
+ @staticmethod
+ def SDL_GetDefaultLogOutputFunction() -> Any:
+ """SDL_LogOutputFunction SDL_GetDefaultLogOutputFunction(void)"""
+
+ @staticmethod
+ def SDL_GetDesktopDisplayMode(displayID: Any, /) -> Any:
+ """const SDL_DisplayMode *SDL_GetDesktopDisplayMode(SDL_DisplayID displayID)"""
+
+ @staticmethod
+ def SDL_GetDisplayBounds(displayID: Any, rect: Any, /) -> bool:
+ """bool SDL_GetDisplayBounds(SDL_DisplayID displayID, SDL_Rect *rect)"""
+
+ @staticmethod
+ def SDL_GetDisplayContentScale(displayID: Any, /) -> float:
+ """float SDL_GetDisplayContentScale(SDL_DisplayID displayID)"""
+
+ @staticmethod
+ def SDL_GetDisplayForPoint(point: Any, /) -> Any:
+ """SDL_DisplayID SDL_GetDisplayForPoint(const SDL_Point *point)"""
+
+ @staticmethod
+ def SDL_GetDisplayForRect(rect: Any, /) -> Any:
+ """SDL_DisplayID SDL_GetDisplayForRect(const SDL_Rect *rect)"""
+
+ @staticmethod
+ def SDL_GetDisplayForWindow(window: Any, /) -> Any:
+ """SDL_DisplayID SDL_GetDisplayForWindow(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_GetDisplayName(displayID: Any, /) -> Any:
+ """const char *SDL_GetDisplayName(SDL_DisplayID displayID)"""
+
+ @staticmethod
+ def SDL_GetDisplayProperties(displayID: Any, /) -> Any:
+ """SDL_PropertiesID SDL_GetDisplayProperties(SDL_DisplayID displayID)"""
+
+ @staticmethod
+ def SDL_GetDisplayUsableBounds(displayID: Any, rect: Any, /) -> bool:
+ """bool SDL_GetDisplayUsableBounds(SDL_DisplayID displayID, SDL_Rect *rect)"""
+
+ @staticmethod
+ def SDL_GetDisplays(count: Any, /) -> Any:
+ """SDL_DisplayID *SDL_GetDisplays(int *count)"""
+
+ @staticmethod
+ def SDL_GetEnvironment() -> Any:
+ """SDL_Environment *SDL_GetEnvironment(void)"""
+
+ @staticmethod
+ def SDL_GetEnvironmentVariable(env: Any, name: Any, /) -> Any:
+ """const char *SDL_GetEnvironmentVariable(SDL_Environment *env, const char *name)"""
+
+ @staticmethod
+ def SDL_GetEnvironmentVariables(env: Any, /) -> Any:
+ """char **SDL_GetEnvironmentVariables(SDL_Environment *env)"""
+
+ @staticmethod
+ def SDL_GetError() -> Any:
+ """const char *SDL_GetError(void)"""
+
+ @staticmethod
+ def SDL_GetEventFilter(filter: Any, userdata: Any, /) -> bool:
+ """bool SDL_GetEventFilter(SDL_EventFilter *filter, void **userdata)"""
+
+ @staticmethod
+ def SDL_GetFloatProperty(props: Any, name: Any, default_value: float, /) -> float:
+ """float SDL_GetFloatProperty(SDL_PropertiesID props, const char *name, float default_value)"""
+
+ @staticmethod
+ def SDL_GetFullscreenDisplayModes(displayID: Any, count: Any, /) -> Any:
+ """SDL_DisplayMode **SDL_GetFullscreenDisplayModes(SDL_DisplayID displayID, int *count)"""
+
+ @staticmethod
+ def SDL_GetGPUDeviceDriver(device: Any, /) -> Any:
+ """const char *SDL_GetGPUDeviceDriver(SDL_GPUDevice *device)"""
+
+ @staticmethod
+ def SDL_GetGPUDriver(index: int, /) -> Any:
+ """const char *SDL_GetGPUDriver(int index)"""
+
+ @staticmethod
+ def SDL_GetGPUShaderFormats(device: Any, /) -> Any:
+ """SDL_GPUShaderFormat SDL_GetGPUShaderFormats(SDL_GPUDevice *device)"""
+
+ @staticmethod
+ def SDL_GetGPUSwapchainTextureFormat(device: Any, window: Any, /) -> Any:
+ """SDL_GPUTextureFormat SDL_GetGPUSwapchainTextureFormat(SDL_GPUDevice *device, SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_GetGamepadAppleSFSymbolsNameForAxis(gamepad: Any, axis: Any, /) -> Any:
+ """const char *SDL_GetGamepadAppleSFSymbolsNameForAxis(SDL_Gamepad *gamepad, SDL_GamepadAxis axis)"""
+
+ @staticmethod
+ def SDL_GetGamepadAppleSFSymbolsNameForButton(gamepad: Any, button: Any, /) -> Any:
+ """const char *SDL_GetGamepadAppleSFSymbolsNameForButton(SDL_Gamepad *gamepad, SDL_GamepadButton button)"""
+
+ @staticmethod
+ def SDL_GetGamepadAxis(gamepad: Any, axis: Any, /) -> Any:
+ """Sint16 SDL_GetGamepadAxis(SDL_Gamepad *gamepad, SDL_GamepadAxis axis)"""
+
+ @staticmethod
+ def SDL_GetGamepadAxisFromString(str: Any, /) -> Any:
+ """SDL_GamepadAxis SDL_GetGamepadAxisFromString(const char *str)"""
+
+ @staticmethod
+ def SDL_GetGamepadBindings(gamepad: Any, count: Any, /) -> Any:
+ """SDL_GamepadBinding **SDL_GetGamepadBindings(SDL_Gamepad *gamepad, int *count)"""
+
+ @staticmethod
+ def SDL_GetGamepadButton(gamepad: Any, button: Any, /) -> bool:
+ """bool SDL_GetGamepadButton(SDL_Gamepad *gamepad, SDL_GamepadButton button)"""
+
+ @staticmethod
+ def SDL_GetGamepadButtonFromString(str: Any, /) -> Any:
+ """SDL_GamepadButton SDL_GetGamepadButtonFromString(const char *str)"""
+
+ @staticmethod
+ def SDL_GetGamepadButtonLabel(gamepad: Any, button: Any, /) -> Any:
+ """SDL_GamepadButtonLabel SDL_GetGamepadButtonLabel(SDL_Gamepad *gamepad, SDL_GamepadButton button)"""
+
+ @staticmethod
+ def SDL_GetGamepadButtonLabelForType(type: Any, button: Any, /) -> Any:
+ """SDL_GamepadButtonLabel SDL_GetGamepadButtonLabelForType(SDL_GamepadType type, SDL_GamepadButton button)"""
+
+ @staticmethod
+ def SDL_GetGamepadConnectionState(gamepad: Any, /) -> Any:
+ """SDL_JoystickConnectionState SDL_GetGamepadConnectionState(SDL_Gamepad *gamepad)"""
+
+ @staticmethod
+ def SDL_GetGamepadFirmwareVersion(gamepad: Any, /) -> Any:
+ """Uint16 SDL_GetGamepadFirmwareVersion(SDL_Gamepad *gamepad)"""
+
+ @staticmethod
+ def SDL_GetGamepadFromID(instance_id: Any, /) -> Any:
+ """SDL_Gamepad *SDL_GetGamepadFromID(SDL_JoystickID instance_id)"""
+
+ @staticmethod
+ def SDL_GetGamepadFromPlayerIndex(player_index: int, /) -> Any:
+ """SDL_Gamepad *SDL_GetGamepadFromPlayerIndex(int player_index)"""
+
+ @staticmethod
+ def SDL_GetGamepadGUIDForID(instance_id: Any, /) -> Any:
+ """SDL_GUID SDL_GetGamepadGUIDForID(SDL_JoystickID instance_id)"""
+
+ @staticmethod
+ def SDL_GetGamepadID(gamepad: Any, /) -> Any:
+ """SDL_JoystickID SDL_GetGamepadID(SDL_Gamepad *gamepad)"""
+
+ @staticmethod
+ def SDL_GetGamepadJoystick(gamepad: Any, /) -> Any:
+ """SDL_Joystick *SDL_GetGamepadJoystick(SDL_Gamepad *gamepad)"""
+
+ @staticmethod
+ def SDL_GetGamepadMapping(gamepad: Any, /) -> Any:
+ """char *SDL_GetGamepadMapping(SDL_Gamepad *gamepad)"""
+
+ @staticmethod
+ def SDL_GetGamepadMappingForGUID(guid: Any, /) -> Any:
+ """char *SDL_GetGamepadMappingForGUID(SDL_GUID guid)"""
+
+ @staticmethod
+ def SDL_GetGamepadMappingForID(instance_id: Any, /) -> Any:
+ """char *SDL_GetGamepadMappingForID(SDL_JoystickID instance_id)"""
+
+ @staticmethod
+ def SDL_GetGamepadMappings(count: Any, /) -> Any:
+ """char **SDL_GetGamepadMappings(int *count)"""
+
+ @staticmethod
+ def SDL_GetGamepadName(gamepad: Any, /) -> Any:
+ """const char *SDL_GetGamepadName(SDL_Gamepad *gamepad)"""
+
+ @staticmethod
+ def SDL_GetGamepadNameForID(instance_id: Any, /) -> Any:
+ """const char *SDL_GetGamepadNameForID(SDL_JoystickID instance_id)"""
+
+ @staticmethod
+ def SDL_GetGamepadPath(gamepad: Any, /) -> Any:
+ """const char *SDL_GetGamepadPath(SDL_Gamepad *gamepad)"""
+
+ @staticmethod
+ def SDL_GetGamepadPathForID(instance_id: Any, /) -> Any:
+ """const char *SDL_GetGamepadPathForID(SDL_JoystickID instance_id)"""
+
+ @staticmethod
+ def SDL_GetGamepadPlayerIndex(gamepad: Any, /) -> int:
+ """int SDL_GetGamepadPlayerIndex(SDL_Gamepad *gamepad)"""
+
+ @staticmethod
+ def SDL_GetGamepadPlayerIndexForID(instance_id: Any, /) -> int:
+ """int SDL_GetGamepadPlayerIndexForID(SDL_JoystickID instance_id)"""
+
+ @staticmethod
+ def SDL_GetGamepadPowerInfo(gamepad: Any, percent: Any, /) -> Any:
+ """SDL_PowerState SDL_GetGamepadPowerInfo(SDL_Gamepad *gamepad, int *percent)"""
+
+ @staticmethod
+ def SDL_GetGamepadProduct(gamepad: Any, /) -> Any:
+ """Uint16 SDL_GetGamepadProduct(SDL_Gamepad *gamepad)"""
+
+ @staticmethod
+ def SDL_GetGamepadProductForID(instance_id: Any, /) -> Any:
+ """Uint16 SDL_GetGamepadProductForID(SDL_JoystickID instance_id)"""
+
+ @staticmethod
+ def SDL_GetGamepadProductVersion(gamepad: Any, /) -> Any:
+ """Uint16 SDL_GetGamepadProductVersion(SDL_Gamepad *gamepad)"""
+
+ @staticmethod
+ def SDL_GetGamepadProductVersionForID(instance_id: Any, /) -> Any:
+ """Uint16 SDL_GetGamepadProductVersionForID(SDL_JoystickID instance_id)"""
+
+ @staticmethod
+ def SDL_GetGamepadProperties(gamepad: Any, /) -> Any:
+ """SDL_PropertiesID SDL_GetGamepadProperties(SDL_Gamepad *gamepad)"""
+
+ @staticmethod
+ def SDL_GetGamepadSensorData(gamepad: Any, type: Any, data: Any, num_values: int, /) -> bool:
+ """bool SDL_GetGamepadSensorData(SDL_Gamepad *gamepad, SDL_SensorType type, float *data, int num_values)"""
+
+ @staticmethod
+ def SDL_GetGamepadSensorDataRate(gamepad: Any, type: Any, /) -> float:
+ """float SDL_GetGamepadSensorDataRate(SDL_Gamepad *gamepad, SDL_SensorType type)"""
+
+ @staticmethod
+ def SDL_GetGamepadSerial(gamepad: Any, /) -> Any:
+ """const char *SDL_GetGamepadSerial(SDL_Gamepad *gamepad)"""
+
+ @staticmethod
+ def SDL_GetGamepadSteamHandle(gamepad: Any, /) -> Any:
+ """Uint64 SDL_GetGamepadSteamHandle(SDL_Gamepad *gamepad)"""
+
+ @staticmethod
+ def SDL_GetGamepadStringForAxis(axis: Any, /) -> Any:
+ """const char *SDL_GetGamepadStringForAxis(SDL_GamepadAxis axis)"""
+
+ @staticmethod
+ def SDL_GetGamepadStringForButton(button: Any, /) -> Any:
+ """const char *SDL_GetGamepadStringForButton(SDL_GamepadButton button)"""
+
+ @staticmethod
+ def SDL_GetGamepadStringForType(type: Any, /) -> Any:
+ """const char *SDL_GetGamepadStringForType(SDL_GamepadType type)"""
+
+ @staticmethod
+ def SDL_GetGamepadTouchpadFinger(
+ gamepad: Any, touchpad: int, finger: int, down: Any, x: Any, y: Any, pressure: Any, /
+ ) -> bool:
+ """bool SDL_GetGamepadTouchpadFinger(SDL_Gamepad *gamepad, int touchpad, int finger, bool *down, float *x, float *y, float *pressure)"""
+
+ @staticmethod
+ def SDL_GetGamepadType(gamepad: Any, /) -> Any:
+ """SDL_GamepadType SDL_GetGamepadType(SDL_Gamepad *gamepad)"""
+
+ @staticmethod
+ def SDL_GetGamepadTypeForID(instance_id: Any, /) -> Any:
+ """SDL_GamepadType SDL_GetGamepadTypeForID(SDL_JoystickID instance_id)"""
+
+ @staticmethod
+ def SDL_GetGamepadTypeFromString(str: Any, /) -> Any:
+ """SDL_GamepadType SDL_GetGamepadTypeFromString(const char *str)"""
+
+ @staticmethod
+ def SDL_GetGamepadVendor(gamepad: Any, /) -> Any:
+ """Uint16 SDL_GetGamepadVendor(SDL_Gamepad *gamepad)"""
+
+ @staticmethod
+ def SDL_GetGamepadVendorForID(instance_id: Any, /) -> Any:
+ """Uint16 SDL_GetGamepadVendorForID(SDL_JoystickID instance_id)"""
+
+ @staticmethod
+ def SDL_GetGamepads(count: Any, /) -> Any:
+ """SDL_JoystickID *SDL_GetGamepads(int *count)"""
+
+ @staticmethod
+ def SDL_GetGlobalMouseState(x: Any, y: Any, /) -> Any:
+ """SDL_MouseButtonFlags SDL_GetGlobalMouseState(float *x, float *y)"""
+
+ @staticmethod
+ def SDL_GetGlobalProperties() -> Any:
+ """SDL_PropertiesID SDL_GetGlobalProperties(void)"""
+
+ @staticmethod
+ def SDL_GetGrabbedWindow() -> Any:
+ """SDL_Window *SDL_GetGrabbedWindow(void)"""
+
+ @staticmethod
+ def SDL_GetHapticEffectStatus(haptic: Any, effect: int, /) -> bool:
+ """bool SDL_GetHapticEffectStatus(SDL_Haptic *haptic, int effect)"""
+
+ @staticmethod
+ def SDL_GetHapticFeatures(haptic: Any, /) -> Any:
+ """Uint32 SDL_GetHapticFeatures(SDL_Haptic *haptic)"""
+
+ @staticmethod
+ def SDL_GetHapticFromID(instance_id: Any, /) -> Any:
+ """SDL_Haptic *SDL_GetHapticFromID(SDL_HapticID instance_id)"""
+
+ @staticmethod
+ def SDL_GetHapticID(haptic: Any, /) -> Any:
+ """SDL_HapticID SDL_GetHapticID(SDL_Haptic *haptic)"""
+
+ @staticmethod
+ def SDL_GetHapticName(haptic: Any, /) -> Any:
+ """const char *SDL_GetHapticName(SDL_Haptic *haptic)"""
+
+ @staticmethod
+ def SDL_GetHapticNameForID(instance_id: Any, /) -> Any:
+ """const char *SDL_GetHapticNameForID(SDL_HapticID instance_id)"""
+
+ @staticmethod
+ def SDL_GetHaptics(count: Any, /) -> Any:
+ """SDL_HapticID *SDL_GetHaptics(int *count)"""
+
+ @staticmethod
+ def SDL_GetHint(name: Any, /) -> Any:
+ """const char *SDL_GetHint(const char *name)"""
+
+ @staticmethod
+ def SDL_GetHintBoolean(name: Any, default_value: bool, /) -> bool:
+ """bool SDL_GetHintBoolean(const char *name, bool default_value)"""
+
+ @staticmethod
+ def SDL_GetIOProperties(context: Any, /) -> Any:
+ """SDL_PropertiesID SDL_GetIOProperties(SDL_IOStream *context)"""
+
+ @staticmethod
+ def SDL_GetIOSize(context: Any, /) -> Any:
+ """Sint64 SDL_GetIOSize(SDL_IOStream *context)"""
+
+ @staticmethod
+ def SDL_GetIOStatus(context: Any, /) -> Any:
+ """SDL_IOStatus SDL_GetIOStatus(SDL_IOStream *context)"""
+
+ @staticmethod
+ def SDL_GetJoystickAxis(joystick: Any, axis: int, /) -> Any:
+ """Sint16 SDL_GetJoystickAxis(SDL_Joystick *joystick, int axis)"""
+
+ @staticmethod
+ def SDL_GetJoystickAxisInitialState(joystick: Any, axis: int, state: Any, /) -> bool:
+ """bool SDL_GetJoystickAxisInitialState(SDL_Joystick *joystick, int axis, Sint16 *state)"""
+
+ @staticmethod
+ def SDL_GetJoystickBall(joystick: Any, ball: int, dx: Any, dy: Any, /) -> bool:
+ """bool SDL_GetJoystickBall(SDL_Joystick *joystick, int ball, int *dx, int *dy)"""
+
+ @staticmethod
+ def SDL_GetJoystickButton(joystick: Any, button: int, /) -> bool:
+ """bool SDL_GetJoystickButton(SDL_Joystick *joystick, int button)"""
+
+ @staticmethod
+ def SDL_GetJoystickConnectionState(joystick: Any, /) -> Any:
+ """SDL_JoystickConnectionState SDL_GetJoystickConnectionState(SDL_Joystick *joystick)"""
+
+ @staticmethod
+ def SDL_GetJoystickFirmwareVersion(joystick: Any, /) -> Any:
+ """Uint16 SDL_GetJoystickFirmwareVersion(SDL_Joystick *joystick)"""
+
+ @staticmethod
+ def SDL_GetJoystickFromID(instance_id: Any, /) -> Any:
+ """SDL_Joystick *SDL_GetJoystickFromID(SDL_JoystickID instance_id)"""
+
+ @staticmethod
+ def SDL_GetJoystickFromPlayerIndex(player_index: int, /) -> Any:
+ """SDL_Joystick *SDL_GetJoystickFromPlayerIndex(int player_index)"""
+
+ @staticmethod
+ def SDL_GetJoystickGUID(joystick: Any, /) -> Any:
+ """SDL_GUID SDL_GetJoystickGUID(SDL_Joystick *joystick)"""
+
+ @staticmethod
+ def SDL_GetJoystickGUIDForID(instance_id: Any, /) -> Any:
+ """SDL_GUID SDL_GetJoystickGUIDForID(SDL_JoystickID instance_id)"""
+
+ @staticmethod
+ def SDL_GetJoystickGUIDInfo(guid: Any, vendor: Any, product: Any, version: Any, crc16: Any, /) -> None:
+ """void SDL_GetJoystickGUIDInfo(SDL_GUID guid, Uint16 *vendor, Uint16 *product, Uint16 *version, Uint16 *crc16)"""
+
+ @staticmethod
+ def SDL_GetJoystickHat(joystick: Any, hat: int, /) -> Any:
+ """Uint8 SDL_GetJoystickHat(SDL_Joystick *joystick, int hat)"""
+
+ @staticmethod
+ def SDL_GetJoystickID(joystick: Any, /) -> Any:
+ """SDL_JoystickID SDL_GetJoystickID(SDL_Joystick *joystick)"""
+
+ @staticmethod
+ def SDL_GetJoystickName(joystick: Any, /) -> Any:
+ """const char *SDL_GetJoystickName(SDL_Joystick *joystick)"""
+
+ @staticmethod
+ def SDL_GetJoystickNameForID(instance_id: Any, /) -> Any:
+ """const char *SDL_GetJoystickNameForID(SDL_JoystickID instance_id)"""
+
+ @staticmethod
+ def SDL_GetJoystickPath(joystick: Any, /) -> Any:
+ """const char *SDL_GetJoystickPath(SDL_Joystick *joystick)"""
+
+ @staticmethod
+ def SDL_GetJoystickPathForID(instance_id: Any, /) -> Any:
+ """const char *SDL_GetJoystickPathForID(SDL_JoystickID instance_id)"""
+
+ @staticmethod
+ def SDL_GetJoystickPlayerIndex(joystick: Any, /) -> int:
+ """int SDL_GetJoystickPlayerIndex(SDL_Joystick *joystick)"""
+
+ @staticmethod
+ def SDL_GetJoystickPlayerIndexForID(instance_id: Any, /) -> int:
+ """int SDL_GetJoystickPlayerIndexForID(SDL_JoystickID instance_id)"""
+
+ @staticmethod
+ def SDL_GetJoystickPowerInfo(joystick: Any, percent: Any, /) -> Any:
+ """SDL_PowerState SDL_GetJoystickPowerInfo(SDL_Joystick *joystick, int *percent)"""
+
+ @staticmethod
+ def SDL_GetJoystickProduct(joystick: Any, /) -> Any:
+ """Uint16 SDL_GetJoystickProduct(SDL_Joystick *joystick)"""
+
+ @staticmethod
+ def SDL_GetJoystickProductForID(instance_id: Any, /) -> Any:
+ """Uint16 SDL_GetJoystickProductForID(SDL_JoystickID instance_id)"""
+
+ @staticmethod
+ def SDL_GetJoystickProductVersion(joystick: Any, /) -> Any:
+ """Uint16 SDL_GetJoystickProductVersion(SDL_Joystick *joystick)"""
+
+ @staticmethod
+ def SDL_GetJoystickProductVersionForID(instance_id: Any, /) -> Any:
+ """Uint16 SDL_GetJoystickProductVersionForID(SDL_JoystickID instance_id)"""
+
+ @staticmethod
+ def SDL_GetJoystickProperties(joystick: Any, /) -> Any:
+ """SDL_PropertiesID SDL_GetJoystickProperties(SDL_Joystick *joystick)"""
+
+ @staticmethod
+ def SDL_GetJoystickSerial(joystick: Any, /) -> Any:
+ """const char *SDL_GetJoystickSerial(SDL_Joystick *joystick)"""
+
+ @staticmethod
+ def SDL_GetJoystickType(joystick: Any, /) -> Any:
+ """SDL_JoystickType SDL_GetJoystickType(SDL_Joystick *joystick)"""
+
+ @staticmethod
+ def SDL_GetJoystickTypeForID(instance_id: Any, /) -> Any:
+ """SDL_JoystickType SDL_GetJoystickTypeForID(SDL_JoystickID instance_id)"""
+
+ @staticmethod
+ def SDL_GetJoystickVendor(joystick: Any, /) -> Any:
+ """Uint16 SDL_GetJoystickVendor(SDL_Joystick *joystick)"""
+
+ @staticmethod
+ def SDL_GetJoystickVendorForID(instance_id: Any, /) -> Any:
+ """Uint16 SDL_GetJoystickVendorForID(SDL_JoystickID instance_id)"""
+
+ @staticmethod
+ def SDL_GetJoysticks(count: Any, /) -> Any:
+ """SDL_JoystickID *SDL_GetJoysticks(int *count)"""
+
+ @staticmethod
+ def SDL_GetKeyFromName(name: Any, /) -> Any:
+ """SDL_Keycode SDL_GetKeyFromName(const char *name)"""
+
+ @staticmethod
+ def SDL_GetKeyFromScancode(scancode: Any, modstate: Any, key_event: bool, /) -> Any:
+ """SDL_Keycode SDL_GetKeyFromScancode(SDL_Scancode scancode, SDL_Keymod modstate, bool key_event)"""
+
+ @staticmethod
+ def SDL_GetKeyName(key: Any, /) -> Any:
+ """const char *SDL_GetKeyName(SDL_Keycode key)"""
+
+ @staticmethod
+ def SDL_GetKeyboardFocus() -> Any:
+ """SDL_Window *SDL_GetKeyboardFocus(void)"""
+
+ @staticmethod
+ def SDL_GetKeyboardNameForID(instance_id: Any, /) -> Any:
+ """const char *SDL_GetKeyboardNameForID(SDL_KeyboardID instance_id)"""
+
+ @staticmethod
+ def SDL_GetKeyboardState(numkeys: Any, /) -> Any:
+ """const bool *SDL_GetKeyboardState(int *numkeys)"""
+
+ @staticmethod
+ def SDL_GetKeyboards(count: Any, /) -> Any:
+ """SDL_KeyboardID *SDL_GetKeyboards(int *count)"""
+
+ @staticmethod
+ def SDL_GetLogOutputFunction(callback: Any, userdata: Any, /) -> None:
+ """void SDL_GetLogOutputFunction(SDL_LogOutputFunction *callback, void **userdata)"""
+
+ @staticmethod
+ def SDL_GetLogPriority(category: int, /) -> Any:
+ """SDL_LogPriority SDL_GetLogPriority(int category)"""
+
+ @staticmethod
+ def SDL_GetMasksForPixelFormat(format: Any, bpp: Any, Rmask: Any, Gmask: Any, Bmask: Any, Amask: Any, /) -> bool:
+ """bool SDL_GetMasksForPixelFormat(SDL_PixelFormat format, int *bpp, Uint32 *Rmask, Uint32 *Gmask, Uint32 *Bmask, Uint32 *Amask)"""
+
+ @staticmethod
+ def SDL_GetMaxHapticEffects(haptic: Any, /) -> int:
+ """int SDL_GetMaxHapticEffects(SDL_Haptic *haptic)"""
+
+ @staticmethod
+ def SDL_GetMaxHapticEffectsPlaying(haptic: Any, /) -> int:
+ """int SDL_GetMaxHapticEffectsPlaying(SDL_Haptic *haptic)"""
+
+ @staticmethod
+ def SDL_GetMemoryFunctions(malloc_func: Any, calloc_func: Any, realloc_func: Any, free_func: Any, /) -> None:
+ """void SDL_GetMemoryFunctions(SDL_malloc_func *malloc_func, SDL_calloc_func *calloc_func, SDL_realloc_func *realloc_func, SDL_free_func *free_func)"""
+
+ @staticmethod
+ def SDL_GetMice(count: Any, /) -> Any:
+ """SDL_MouseID *SDL_GetMice(int *count)"""
+
+ @staticmethod
+ def SDL_GetModState() -> Any:
+ """SDL_Keymod SDL_GetModState(void)"""
+
+ @staticmethod
+ def SDL_GetMouseFocus() -> Any:
+ """SDL_Window *SDL_GetMouseFocus(void)"""
+
+ @staticmethod
+ def SDL_GetMouseNameForID(instance_id: Any, /) -> Any:
+ """const char *SDL_GetMouseNameForID(SDL_MouseID instance_id)"""
+
+ @staticmethod
+ def SDL_GetMouseState(x: Any, y: Any, /) -> Any:
+ """SDL_MouseButtonFlags SDL_GetMouseState(float *x, float *y)"""
+
+ @staticmethod
+ def SDL_GetNaturalDisplayOrientation(displayID: Any, /) -> Any:
+ """SDL_DisplayOrientation SDL_GetNaturalDisplayOrientation(SDL_DisplayID displayID)"""
+
+ @staticmethod
+ def SDL_GetNumAllocations() -> int:
+ """int SDL_GetNumAllocations(void)"""
+
+ @staticmethod
+ def SDL_GetNumAudioDrivers() -> int:
+ """int SDL_GetNumAudioDrivers(void)"""
+
+ @staticmethod
+ def SDL_GetNumCameraDrivers() -> int:
+ """int SDL_GetNumCameraDrivers(void)"""
+
+ @staticmethod
+ def SDL_GetNumGPUDrivers() -> int:
+ """int SDL_GetNumGPUDrivers(void)"""
+
+ @staticmethod
+ def SDL_GetNumGamepadTouchpadFingers(gamepad: Any, touchpad: int, /) -> int:
+ """int SDL_GetNumGamepadTouchpadFingers(SDL_Gamepad *gamepad, int touchpad)"""
+
+ @staticmethod
+ def SDL_GetNumGamepadTouchpads(gamepad: Any, /) -> int:
+ """int SDL_GetNumGamepadTouchpads(SDL_Gamepad *gamepad)"""
+
+ @staticmethod
+ def SDL_GetNumHapticAxes(haptic: Any, /) -> int:
+ """int SDL_GetNumHapticAxes(SDL_Haptic *haptic)"""
+
+ @staticmethod
+ def SDL_GetNumJoystickAxes(joystick: Any, /) -> int:
+ """int SDL_GetNumJoystickAxes(SDL_Joystick *joystick)"""
+
+ @staticmethod
+ def SDL_GetNumJoystickBalls(joystick: Any, /) -> int:
+ """int SDL_GetNumJoystickBalls(SDL_Joystick *joystick)"""
+
+ @staticmethod
+ def SDL_GetNumJoystickButtons(joystick: Any, /) -> int:
+ """int SDL_GetNumJoystickButtons(SDL_Joystick *joystick)"""
+
+ @staticmethod
+ def SDL_GetNumJoystickHats(joystick: Any, /) -> int:
+ """int SDL_GetNumJoystickHats(SDL_Joystick *joystick)"""
+
+ @staticmethod
+ def SDL_GetNumLogicalCPUCores() -> int:
+ """int SDL_GetNumLogicalCPUCores(void)"""
+
+ @staticmethod
+ def SDL_GetNumRenderDrivers() -> int:
+ """int SDL_GetNumRenderDrivers(void)"""
+
+ @staticmethod
+ def SDL_GetNumVideoDrivers() -> int:
+ """int SDL_GetNumVideoDrivers(void)"""
+
+ @staticmethod
+ def SDL_GetNumberProperty(props: Any, name: Any, default_value: Any, /) -> Any:
+ """Sint64 SDL_GetNumberProperty(SDL_PropertiesID props, const char *name, Sint64 default_value)"""
+
+ @staticmethod
+ def SDL_GetOriginalMemoryFunctions(
+ malloc_func: Any, calloc_func: Any, realloc_func: Any, free_func: Any, /
+ ) -> None:
+ """void SDL_GetOriginalMemoryFunctions(SDL_malloc_func *malloc_func, SDL_calloc_func *calloc_func, SDL_realloc_func *realloc_func, SDL_free_func *free_func)"""
+
+ @staticmethod
+ def SDL_GetPathInfo(path: Any, info: Any, /) -> bool:
+ """bool SDL_GetPathInfo(const char *path, SDL_PathInfo *info)"""
+
+ @staticmethod
+ def SDL_GetPerformanceCounter() -> Any:
+ """Uint64 SDL_GetPerformanceCounter(void)"""
+
+ @staticmethod
+ def SDL_GetPerformanceFrequency() -> Any:
+ """Uint64 SDL_GetPerformanceFrequency(void)"""
+
+ @staticmethod
+ def SDL_GetPixelFormatDetails(format: Any, /) -> Any:
+ """const SDL_PixelFormatDetails *SDL_GetPixelFormatDetails(SDL_PixelFormat format)"""
+
+ @staticmethod
+ def SDL_GetPixelFormatForMasks(bpp: int, Rmask: Any, Gmask: Any, Bmask: Any, Amask: Any, /) -> Any:
+ """SDL_PixelFormat SDL_GetPixelFormatForMasks(int bpp, Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask)"""
+
+ @staticmethod
+ def SDL_GetPixelFormatName(format: Any, /) -> Any:
+ """const char *SDL_GetPixelFormatName(SDL_PixelFormat format)"""
+
+ @staticmethod
+ def SDL_GetPlatform() -> Any:
+ """const char *SDL_GetPlatform(void)"""
+
+ @staticmethod
+ def SDL_GetPointerProperty(props: Any, name: Any, default_value: Any, /) -> Any:
+ """void *SDL_GetPointerProperty(SDL_PropertiesID props, const char *name, void *default_value)"""
+
+ @staticmethod
+ def SDL_GetPowerInfo(seconds: Any, percent: Any, /) -> Any:
+ """SDL_PowerState SDL_GetPowerInfo(int *seconds, int *percent)"""
+
+ @staticmethod
+ def SDL_GetPrefPath(org: Any, app: Any, /) -> Any:
+ """char *SDL_GetPrefPath(const char *org, const char *app)"""
+
+ @staticmethod
+ def SDL_GetPreferredLocales(count: Any, /) -> Any:
+ """SDL_Locale **SDL_GetPreferredLocales(int *count)"""
+
+ @staticmethod
+ def SDL_GetPrimaryDisplay() -> Any:
+ """SDL_DisplayID SDL_GetPrimaryDisplay(void)"""
+
+ @staticmethod
+ def SDL_GetPrimarySelectionText() -> Any:
+ """char *SDL_GetPrimarySelectionText(void)"""
+
+ @staticmethod
+ def SDL_GetProcessInput(process: Any, /) -> Any:
+ """SDL_IOStream *SDL_GetProcessInput(SDL_Process *process)"""
+
+ @staticmethod
+ def SDL_GetProcessOutput(process: Any, /) -> Any:
+ """SDL_IOStream *SDL_GetProcessOutput(SDL_Process *process)"""
+
+ @staticmethod
+ def SDL_GetProcessProperties(process: Any, /) -> Any:
+ """SDL_PropertiesID SDL_GetProcessProperties(SDL_Process *process)"""
+
+ @staticmethod
+ def SDL_GetPropertyType(props: Any, name: Any, /) -> Any:
+ """SDL_PropertyType SDL_GetPropertyType(SDL_PropertiesID props, const char *name)"""
+
+ @staticmethod
+ def SDL_GetRGB(pixel: Any, format: Any, palette: Any, r: Any, g: Any, b: Any, /) -> None:
+ """void SDL_GetRGB(Uint32 pixel, const SDL_PixelFormatDetails *format, const SDL_Palette *palette, Uint8 *r, Uint8 *g, Uint8 *b)"""
+
+ @staticmethod
+ def SDL_GetRGBA(pixel: Any, format: Any, palette: Any, r: Any, g: Any, b: Any, a: Any, /) -> None:
+ """void SDL_GetRGBA(Uint32 pixel, const SDL_PixelFormatDetails *format, const SDL_Palette *palette, Uint8 *r, Uint8 *g, Uint8 *b, Uint8 *a)"""
+
+ @staticmethod
+ def SDL_GetRealGamepadType(gamepad: Any, /) -> Any:
+ """SDL_GamepadType SDL_GetRealGamepadType(SDL_Gamepad *gamepad)"""
+
+ @staticmethod
+ def SDL_GetRealGamepadTypeForID(instance_id: Any, /) -> Any:
+ """SDL_GamepadType SDL_GetRealGamepadTypeForID(SDL_JoystickID instance_id)"""
+
+ @staticmethod
+ def SDL_GetRectAndLineIntersection(rect: Any, X1: Any, Y1: Any, X2: Any, Y2: Any, /) -> bool:
+ """bool SDL_GetRectAndLineIntersection(const SDL_Rect *rect, int *X1, int *Y1, int *X2, int *Y2)"""
+
+ @staticmethod
+ def SDL_GetRectAndLineIntersectionFloat(rect: Any, X1: Any, Y1: Any, X2: Any, Y2: Any, /) -> bool:
+ """bool SDL_GetRectAndLineIntersectionFloat(const SDL_FRect *rect, float *X1, float *Y1, float *X2, float *Y2)"""
+
+ @staticmethod
+ def SDL_GetRectEnclosingPoints(points: Any, count: int, clip: Any, result: Any, /) -> bool:
+ """bool SDL_GetRectEnclosingPoints(const SDL_Point *points, int count, const SDL_Rect *clip, SDL_Rect *result)"""
+
+ @staticmethod
+ def SDL_GetRectEnclosingPointsFloat(points: Any, count: int, clip: Any, result: Any, /) -> bool:
+ """bool SDL_GetRectEnclosingPointsFloat(const SDL_FPoint *points, int count, const SDL_FRect *clip, SDL_FRect *result)"""
+
+ @staticmethod
+ def SDL_GetRectIntersection(A: Any, B: Any, result: Any, /) -> bool:
+ """bool SDL_GetRectIntersection(const SDL_Rect *A, const SDL_Rect *B, SDL_Rect *result)"""
+
+ @staticmethod
+ def SDL_GetRectIntersectionFloat(A: Any, B: Any, result: Any, /) -> bool:
+ """bool SDL_GetRectIntersectionFloat(const SDL_FRect *A, const SDL_FRect *B, SDL_FRect *result)"""
+
+ @staticmethod
+ def SDL_GetRectUnion(A: Any, B: Any, result: Any, /) -> bool:
+ """bool SDL_GetRectUnion(const SDL_Rect *A, const SDL_Rect *B, SDL_Rect *result)"""
+
+ @staticmethod
+ def SDL_GetRectUnionFloat(A: Any, B: Any, result: Any, /) -> bool:
+ """bool SDL_GetRectUnionFloat(const SDL_FRect *A, const SDL_FRect *B, SDL_FRect *result)"""
+
+ @staticmethod
+ def SDL_GetRelativeMouseState(x: Any, y: Any, /) -> Any:
+ """SDL_MouseButtonFlags SDL_GetRelativeMouseState(float *x, float *y)"""
+
+ @staticmethod
+ def SDL_GetRenderClipRect(renderer: Any, rect: Any, /) -> bool:
+ """bool SDL_GetRenderClipRect(SDL_Renderer *renderer, SDL_Rect *rect)"""
+
+ @staticmethod
+ def SDL_GetRenderColorScale(renderer: Any, scale: Any, /) -> bool:
+ """bool SDL_GetRenderColorScale(SDL_Renderer *renderer, float *scale)"""
+
+ @staticmethod
+ def SDL_GetRenderDrawBlendMode(renderer: Any, blendMode: Any, /) -> bool:
+ """bool SDL_GetRenderDrawBlendMode(SDL_Renderer *renderer, SDL_BlendMode *blendMode)"""
+
+ @staticmethod
+ def SDL_GetRenderDrawColor(renderer: Any, r: Any, g: Any, b: Any, a: Any, /) -> bool:
+ """bool SDL_GetRenderDrawColor(SDL_Renderer *renderer, Uint8 *r, Uint8 *g, Uint8 *b, Uint8 *a)"""
+
+ @staticmethod
+ def SDL_GetRenderDrawColorFloat(renderer: Any, r: Any, g: Any, b: Any, a: Any, /) -> bool:
+ """bool SDL_GetRenderDrawColorFloat(SDL_Renderer *renderer, float *r, float *g, float *b, float *a)"""
+
+ @staticmethod
+ def SDL_GetRenderDriver(index: int, /) -> Any:
+ """const char *SDL_GetRenderDriver(int index)"""
+
+ @staticmethod
+ def SDL_GetRenderLogicalPresentation(renderer: Any, w: Any, h: Any, mode: Any, /) -> bool:
+ """bool SDL_GetRenderLogicalPresentation(SDL_Renderer *renderer, int *w, int *h, SDL_RendererLogicalPresentation *mode)"""
+
+ @staticmethod
+ def SDL_GetRenderLogicalPresentationRect(renderer: Any, rect: Any, /) -> bool:
+ """bool SDL_GetRenderLogicalPresentationRect(SDL_Renderer *renderer, SDL_FRect *rect)"""
+
+ @staticmethod
+ def SDL_GetRenderMetalCommandEncoder(renderer: Any, /) -> Any:
+ """void *SDL_GetRenderMetalCommandEncoder(SDL_Renderer *renderer)"""
+
+ @staticmethod
+ def SDL_GetRenderMetalLayer(renderer: Any, /) -> Any:
+ """void *SDL_GetRenderMetalLayer(SDL_Renderer *renderer)"""
+
+ @staticmethod
+ def SDL_GetRenderOutputSize(renderer: Any, w: Any, h: Any, /) -> bool:
+ """bool SDL_GetRenderOutputSize(SDL_Renderer *renderer, int *w, int *h)"""
+
+ @staticmethod
+ def SDL_GetRenderSafeArea(renderer: Any, rect: Any, /) -> bool:
+ """bool SDL_GetRenderSafeArea(SDL_Renderer *renderer, SDL_Rect *rect)"""
+
+ @staticmethod
+ def SDL_GetRenderScale(renderer: Any, scaleX: Any, scaleY: Any, /) -> bool:
+ """bool SDL_GetRenderScale(SDL_Renderer *renderer, float *scaleX, float *scaleY)"""
+
+ @staticmethod
+ def SDL_GetRenderTarget(renderer: Any, /) -> Any:
+ """SDL_Texture *SDL_GetRenderTarget(SDL_Renderer *renderer)"""
+
+ @staticmethod
+ def SDL_GetRenderVSync(renderer: Any, vsync: Any, /) -> bool:
+ """bool SDL_GetRenderVSync(SDL_Renderer *renderer, int *vsync)"""
+
+ @staticmethod
+ def SDL_GetRenderViewport(renderer: Any, rect: Any, /) -> bool:
+ """bool SDL_GetRenderViewport(SDL_Renderer *renderer, SDL_Rect *rect)"""
+
+ @staticmethod
+ def SDL_GetRenderWindow(renderer: Any, /) -> Any:
+ """SDL_Window *SDL_GetRenderWindow(SDL_Renderer *renderer)"""
+
+ @staticmethod
+ def SDL_GetRenderer(window: Any, /) -> Any:
+ """SDL_Renderer *SDL_GetRenderer(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_GetRendererFromTexture(texture: Any, /) -> Any:
+ """SDL_Renderer *SDL_GetRendererFromTexture(SDL_Texture *texture)"""
+
+ @staticmethod
+ def SDL_GetRendererName(renderer: Any, /) -> Any:
+ """const char *SDL_GetRendererName(SDL_Renderer *renderer)"""
+
+ @staticmethod
+ def SDL_GetRendererProperties(renderer: Any, /) -> Any:
+ """SDL_PropertiesID SDL_GetRendererProperties(SDL_Renderer *renderer)"""
+
+ @staticmethod
+ def SDL_GetRevision() -> Any:
+ """const char *SDL_GetRevision(void)"""
+
+ @staticmethod
+ def SDL_GetSIMDAlignment() -> int:
+ """size_t SDL_GetSIMDAlignment(void)"""
+
+ @staticmethod
+ def SDL_GetSandbox() -> Any:
+ """SDL_Sandbox SDL_GetSandbox(void)"""
+
+ @staticmethod
+ def SDL_GetScancodeFromKey(key: Any, modstate: Any, /) -> Any:
+ """SDL_Scancode SDL_GetScancodeFromKey(SDL_Keycode key, SDL_Keymod *modstate)"""
+
+ @staticmethod
+ def SDL_GetScancodeFromName(name: Any, /) -> Any:
+ """SDL_Scancode SDL_GetScancodeFromName(const char *name)"""
+
+ @staticmethod
+ def SDL_GetScancodeName(scancode: Any, /) -> Any:
+ """const char *SDL_GetScancodeName(SDL_Scancode scancode)"""
+
+ @staticmethod
+ def SDL_GetSemaphoreValue(sem: Any, /) -> Any:
+ """Uint32 SDL_GetSemaphoreValue(SDL_Semaphore *sem)"""
+
+ @staticmethod
+ def SDL_GetSensorData(sensor: Any, data: Any, num_values: int, /) -> bool:
+ """bool SDL_GetSensorData(SDL_Sensor *sensor, float *data, int num_values)"""
+
+ @staticmethod
+ def SDL_GetSensorFromID(instance_id: Any, /) -> Any:
+ """SDL_Sensor *SDL_GetSensorFromID(SDL_SensorID instance_id)"""
+
+ @staticmethod
+ def SDL_GetSensorID(sensor: Any, /) -> Any:
+ """SDL_SensorID SDL_GetSensorID(SDL_Sensor *sensor)"""
+
+ @staticmethod
+ def SDL_GetSensorName(sensor: Any, /) -> Any:
+ """const char *SDL_GetSensorName(SDL_Sensor *sensor)"""
+
+ @staticmethod
+ def SDL_GetSensorNameForID(instance_id: Any, /) -> Any:
+ """const char *SDL_GetSensorNameForID(SDL_SensorID instance_id)"""
+
+ @staticmethod
+ def SDL_GetSensorNonPortableType(sensor: Any, /) -> int:
+ """int SDL_GetSensorNonPortableType(SDL_Sensor *sensor)"""
+
+ @staticmethod
+ def SDL_GetSensorNonPortableTypeForID(instance_id: Any, /) -> int:
+ """int SDL_GetSensorNonPortableTypeForID(SDL_SensorID instance_id)"""
+
+ @staticmethod
+ def SDL_GetSensorProperties(sensor: Any, /) -> Any:
+ """SDL_PropertiesID SDL_GetSensorProperties(SDL_Sensor *sensor)"""
+
+ @staticmethod
+ def SDL_GetSensorType(sensor: Any, /) -> Any:
+ """SDL_SensorType SDL_GetSensorType(SDL_Sensor *sensor)"""
+
+ @staticmethod
+ def SDL_GetSensorTypeForID(instance_id: Any, /) -> Any:
+ """SDL_SensorType SDL_GetSensorTypeForID(SDL_SensorID instance_id)"""
+
+ @staticmethod
+ def SDL_GetSensors(count: Any, /) -> Any:
+ """SDL_SensorID *SDL_GetSensors(int *count)"""
+
+ @staticmethod
+ def SDL_GetSilenceValueForFormat(format: Any, /) -> int:
+ """int SDL_GetSilenceValueForFormat(SDL_AudioFormat format)"""
+
+ @staticmethod
+ def SDL_GetStorageFileSize(storage: Any, path: Any, length: Any, /) -> bool:
+ """bool SDL_GetStorageFileSize(SDL_Storage *storage, const char *path, Uint64 *length)"""
+
+ @staticmethod
+ def SDL_GetStoragePathInfo(storage: Any, path: Any, info: Any, /) -> bool:
+ """bool SDL_GetStoragePathInfo(SDL_Storage *storage, const char *path, SDL_PathInfo *info)"""
+
+ @staticmethod
+ def SDL_GetStorageSpaceRemaining(storage: Any, /) -> Any:
+ """Uint64 SDL_GetStorageSpaceRemaining(SDL_Storage *storage)"""
+
+ @staticmethod
+ def SDL_GetStringProperty(props: Any, name: Any, default_value: Any, /) -> Any:
+ """const char *SDL_GetStringProperty(SDL_PropertiesID props, const char *name, const char *default_value)"""
+
+ @staticmethod
+ def SDL_GetSurfaceAlphaMod(surface: Any, alpha: Any, /) -> bool:
+ """bool SDL_GetSurfaceAlphaMod(SDL_Surface *surface, Uint8 *alpha)"""
+
+ @staticmethod
+ def SDL_GetSurfaceBlendMode(surface: Any, blendMode: Any, /) -> bool:
+ """bool SDL_GetSurfaceBlendMode(SDL_Surface *surface, SDL_BlendMode *blendMode)"""
+
+ @staticmethod
+ def SDL_GetSurfaceClipRect(surface: Any, rect: Any, /) -> bool:
+ """bool SDL_GetSurfaceClipRect(SDL_Surface *surface, SDL_Rect *rect)"""
+
+ @staticmethod
+ def SDL_GetSurfaceColorKey(surface: Any, key: Any, /) -> bool:
+ """bool SDL_GetSurfaceColorKey(SDL_Surface *surface, Uint32 *key)"""
+
+ @staticmethod
+ def SDL_GetSurfaceColorMod(surface: Any, r: Any, g: Any, b: Any, /) -> bool:
+ """bool SDL_GetSurfaceColorMod(SDL_Surface *surface, Uint8 *r, Uint8 *g, Uint8 *b)"""
+
+ @staticmethod
+ def SDL_GetSurfaceColorspace(surface: Any, /) -> Any:
+ """SDL_Colorspace SDL_GetSurfaceColorspace(SDL_Surface *surface)"""
+
+ @staticmethod
+ def SDL_GetSurfaceImages(surface: Any, count: Any, /) -> Any:
+ """SDL_Surface **SDL_GetSurfaceImages(SDL_Surface *surface, int *count)"""
+
+ @staticmethod
+ def SDL_GetSurfacePalette(surface: Any, /) -> Any:
+ """SDL_Palette *SDL_GetSurfacePalette(SDL_Surface *surface)"""
+
+ @staticmethod
+ def SDL_GetSurfaceProperties(surface: Any, /) -> Any:
+ """SDL_PropertiesID SDL_GetSurfaceProperties(SDL_Surface *surface)"""
+
+ @staticmethod
+ def SDL_GetSystemRAM() -> int:
+ """int SDL_GetSystemRAM(void)"""
+
+ @staticmethod
+ def SDL_GetSystemTheme() -> Any:
+ """SDL_SystemTheme SDL_GetSystemTheme(void)"""
+
+ @staticmethod
+ def SDL_GetTLS(id: Any, /) -> Any:
+ """void *SDL_GetTLS(SDL_TLSID *id)"""
+
+ @staticmethod
+ def SDL_GetTextInputArea(window: Any, rect: Any, cursor: Any, /) -> bool:
+ """bool SDL_GetTextInputArea(SDL_Window *window, SDL_Rect *rect, int *cursor)"""
+
+ @staticmethod
+ def SDL_GetTextureAlphaMod(texture: Any, alpha: Any, /) -> bool:
+ """bool SDL_GetTextureAlphaMod(SDL_Texture *texture, Uint8 *alpha)"""
+
+ @staticmethod
+ def SDL_GetTextureAlphaModFloat(texture: Any, alpha: Any, /) -> bool:
+ """bool SDL_GetTextureAlphaModFloat(SDL_Texture *texture, float *alpha)"""
+
+ @staticmethod
+ def SDL_GetTextureBlendMode(texture: Any, blendMode: Any, /) -> bool:
+ """bool SDL_GetTextureBlendMode(SDL_Texture *texture, SDL_BlendMode *blendMode)"""
+
+ @staticmethod
+ def SDL_GetTextureColorMod(texture: Any, r: Any, g: Any, b: Any, /) -> bool:
+ """bool SDL_GetTextureColorMod(SDL_Texture *texture, Uint8 *r, Uint8 *g, Uint8 *b)"""
+
+ @staticmethod
+ def SDL_GetTextureColorModFloat(texture: Any, r: Any, g: Any, b: Any, /) -> bool:
+ """bool SDL_GetTextureColorModFloat(SDL_Texture *texture, float *r, float *g, float *b)"""
+
+ @staticmethod
+ def SDL_GetTextureProperties(texture: Any, /) -> Any:
+ """SDL_PropertiesID SDL_GetTextureProperties(SDL_Texture *texture)"""
+
+ @staticmethod
+ def SDL_GetTextureScaleMode(texture: Any, scaleMode: Any, /) -> bool:
+ """bool SDL_GetTextureScaleMode(SDL_Texture *texture, SDL_ScaleMode *scaleMode)"""
+
+ @staticmethod
+ def SDL_GetTextureSize(texture: Any, w: Any, h: Any, /) -> bool:
+ """bool SDL_GetTextureSize(SDL_Texture *texture, float *w, float *h)"""
+
+ @staticmethod
+ def SDL_GetThreadID(thread: Any, /) -> Any:
+ """SDL_ThreadID SDL_GetThreadID(SDL_Thread *thread)"""
+
+ @staticmethod
+ def SDL_GetThreadName(thread: Any, /) -> Any:
+ """const char *SDL_GetThreadName(SDL_Thread *thread)"""
+
+ @staticmethod
+ def SDL_GetThreadState(thread: Any, /) -> Any:
+ """SDL_ThreadState SDL_GetThreadState(SDL_Thread *thread)"""
+
+ @staticmethod
+ def SDL_GetTicks() -> Any:
+ """Uint64 SDL_GetTicks(void)"""
+
+ @staticmethod
+ def SDL_GetTicksNS() -> Any:
+ """Uint64 SDL_GetTicksNS(void)"""
+
+ @staticmethod
+ def SDL_GetTouchDeviceName(touchID: Any, /) -> Any:
+ """const char *SDL_GetTouchDeviceName(SDL_TouchID touchID)"""
+
+ @staticmethod
+ def SDL_GetTouchDeviceType(touchID: Any, /) -> Any:
+ """SDL_TouchDeviceType SDL_GetTouchDeviceType(SDL_TouchID touchID)"""
+
+ @staticmethod
+ def SDL_GetTouchDevices(count: Any, /) -> Any:
+ """SDL_TouchID *SDL_GetTouchDevices(int *count)"""
+
+ @staticmethod
+ def SDL_GetTouchFingers(touchID: Any, count: Any, /) -> Any:
+ """SDL_Finger **SDL_GetTouchFingers(SDL_TouchID touchID, int *count)"""
+
+ @staticmethod
+ def SDL_GetTrayEntries(menu: Any, count: Any, /) -> Any:
+ """const SDL_TrayEntry **SDL_GetTrayEntries(SDL_TrayMenu *menu, int *count)"""
+
+ @staticmethod
+ def SDL_GetTrayEntryChecked(entry: Any, /) -> bool:
+ """bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry)"""
+
+ @staticmethod
+ def SDL_GetTrayEntryEnabled(entry: Any, /) -> bool:
+ """bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry)"""
+
+ @staticmethod
+ def SDL_GetTrayEntryLabel(entry: Any, /) -> Any:
+ """const char *SDL_GetTrayEntryLabel(SDL_TrayEntry *entry)"""
+
+ @staticmethod
+ def SDL_GetTrayEntryParent(entry: Any, /) -> Any:
+ """SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry)"""
+
+ @staticmethod
+ def SDL_GetTrayMenu(tray: Any, /) -> Any:
+ """SDL_TrayMenu *SDL_GetTrayMenu(SDL_Tray *tray)"""
+
+ @staticmethod
+ def SDL_GetTrayMenuParentEntry(menu: Any, /) -> Any:
+ """SDL_TrayEntry *SDL_GetTrayMenuParentEntry(SDL_TrayMenu *menu)"""
+
+ @staticmethod
+ def SDL_GetTrayMenuParentTray(menu: Any, /) -> Any:
+ """SDL_Tray *SDL_GetTrayMenuParentTray(SDL_TrayMenu *menu)"""
+
+ @staticmethod
+ def SDL_GetTraySubmenu(entry: Any, /) -> Any:
+ """SDL_TrayMenu *SDL_GetTraySubmenu(SDL_TrayEntry *entry)"""
+
+ @staticmethod
+ def SDL_GetUserFolder(folder: Any, /) -> Any:
+ """const char *SDL_GetUserFolder(SDL_Folder folder)"""
+
+ @staticmethod
+ def SDL_GetVersion() -> int:
+ """int SDL_GetVersion(void)"""
+
+ @staticmethod
+ def SDL_GetVideoDriver(index: int, /) -> Any:
+ """const char *SDL_GetVideoDriver(int index)"""
+
+ @staticmethod
+ def SDL_GetWindowAspectRatio(window: Any, min_aspect: Any, max_aspect: Any, /) -> bool:
+ """bool SDL_GetWindowAspectRatio(SDL_Window *window, float *min_aspect, float *max_aspect)"""
+
+ @staticmethod
+ def SDL_GetWindowBordersSize(window: Any, top: Any, left: Any, bottom: Any, right: Any, /) -> bool:
+ """bool SDL_GetWindowBordersSize(SDL_Window *window, int *top, int *left, int *bottom, int *right)"""
+
+ @staticmethod
+ def SDL_GetWindowDisplayScale(window: Any, /) -> float:
+ """float SDL_GetWindowDisplayScale(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_GetWindowFlags(window: Any, /) -> Any:
+ """SDL_WindowFlags SDL_GetWindowFlags(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_GetWindowFromEvent(event: Any, /) -> Any:
+ """SDL_Window *SDL_GetWindowFromEvent(const SDL_Event *event)"""
+
+ @staticmethod
+ def SDL_GetWindowFromID(id: Any, /) -> Any:
+ """SDL_Window *SDL_GetWindowFromID(SDL_WindowID id)"""
+
+ @staticmethod
+ def SDL_GetWindowFullscreenMode(window: Any, /) -> Any:
+ """const SDL_DisplayMode *SDL_GetWindowFullscreenMode(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_GetWindowICCProfile(window: Any, size: Any, /) -> Any:
+ """void *SDL_GetWindowICCProfile(SDL_Window *window, size_t *size)"""
+
+ @staticmethod
+ def SDL_GetWindowID(window: Any, /) -> Any:
+ """SDL_WindowID SDL_GetWindowID(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_GetWindowKeyboardGrab(window: Any, /) -> bool:
+ """bool SDL_GetWindowKeyboardGrab(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_GetWindowMaximumSize(window: Any, w: Any, h: Any, /) -> bool:
+ """bool SDL_GetWindowMaximumSize(SDL_Window *window, int *w, int *h)"""
+
+ @staticmethod
+ def SDL_GetWindowMinimumSize(window: Any, w: Any, h: Any, /) -> bool:
+ """bool SDL_GetWindowMinimumSize(SDL_Window *window, int *w, int *h)"""
+
+ @staticmethod
+ def SDL_GetWindowMouseGrab(window: Any, /) -> bool:
+ """bool SDL_GetWindowMouseGrab(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_GetWindowMouseRect(window: Any, /) -> Any:
+ """const SDL_Rect *SDL_GetWindowMouseRect(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_GetWindowOpacity(window: Any, /) -> float:
+ """float SDL_GetWindowOpacity(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_GetWindowParent(window: Any, /) -> Any:
+ """SDL_Window *SDL_GetWindowParent(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_GetWindowPixelDensity(window: Any, /) -> float:
+ """float SDL_GetWindowPixelDensity(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_GetWindowPixelFormat(window: Any, /) -> Any:
+ """SDL_PixelFormat SDL_GetWindowPixelFormat(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_GetWindowPosition(window: Any, x: Any, y: Any, /) -> bool:
+ """bool SDL_GetWindowPosition(SDL_Window *window, int *x, int *y)"""
+
+ @staticmethod
+ def SDL_GetWindowProperties(window: Any, /) -> Any:
+ """SDL_PropertiesID SDL_GetWindowProperties(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_GetWindowRelativeMouseMode(window: Any, /) -> bool:
+ """bool SDL_GetWindowRelativeMouseMode(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_GetWindowSafeArea(window: Any, rect: Any, /) -> bool:
+ """bool SDL_GetWindowSafeArea(SDL_Window *window, SDL_Rect *rect)"""
+
+ @staticmethod
+ def SDL_GetWindowSize(window: Any, w: Any, h: Any, /) -> bool:
+ """bool SDL_GetWindowSize(SDL_Window *window, int *w, int *h)"""
+
+ @staticmethod
+ def SDL_GetWindowSizeInPixels(window: Any, w: Any, h: Any, /) -> bool:
+ """bool SDL_GetWindowSizeInPixels(SDL_Window *window, int *w, int *h)"""
+
+ @staticmethod
+ def SDL_GetWindowSurface(window: Any, /) -> Any:
+ """SDL_Surface *SDL_GetWindowSurface(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_GetWindowSurfaceVSync(window: Any, vsync: Any, /) -> bool:
+ """bool SDL_GetWindowSurfaceVSync(SDL_Window *window, int *vsync)"""
+
+ @staticmethod
+ def SDL_GetWindowTitle(window: Any, /) -> Any:
+ """const char *SDL_GetWindowTitle(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_GetWindows(count: Any, /) -> Any:
+ """SDL_Window **SDL_GetWindows(int *count)"""
+
+ @staticmethod
+ def SDL_GlobDirectory(path: Any, pattern: Any, flags: Any, count: Any, /) -> Any:
+ """char **SDL_GlobDirectory(const char *path, const char *pattern, SDL_GlobFlags flags, int *count)"""
+
+ @staticmethod
+ def SDL_GlobStorageDirectory(storage: Any, path: Any, pattern: Any, flags: Any, count: Any, /) -> Any:
+ """char **SDL_GlobStorageDirectory(SDL_Storage *storage, const char *path, const char *pattern, SDL_GlobFlags flags, int *count)"""
+
+ @staticmethod
+ def SDL_HapticEffectSupported(haptic: Any, effect: Any, /) -> bool:
+ """bool SDL_HapticEffectSupported(SDL_Haptic *haptic, const SDL_HapticEffect *effect)"""
+
+ @staticmethod
+ def SDL_HapticRumbleSupported(haptic: Any, /) -> bool:
+ """bool SDL_HapticRumbleSupported(SDL_Haptic *haptic)"""
+
+ @staticmethod
+ def SDL_HasARMSIMD() -> bool:
+ """bool SDL_HasARMSIMD(void)"""
+
+ @staticmethod
+ def SDL_HasAVX() -> bool:
+ """bool SDL_HasAVX(void)"""
+
+ @staticmethod
+ def SDL_HasAVX2() -> bool:
+ """bool SDL_HasAVX2(void)"""
+
+ @staticmethod
+ def SDL_HasAVX512F() -> bool:
+ """bool SDL_HasAVX512F(void)"""
+
+ @staticmethod
+ def SDL_HasAltiVec() -> bool:
+ """bool SDL_HasAltiVec(void)"""
+
+ @staticmethod
+ def SDL_HasClipboardData(mime_type: Any, /) -> bool:
+ """bool SDL_HasClipboardData(const char *mime_type)"""
+
+ @staticmethod
+ def SDL_HasClipboardText() -> bool:
+ """bool SDL_HasClipboardText(void)"""
+
+ @staticmethod
+ def SDL_HasEvent(type: Any, /) -> bool:
+ """bool SDL_HasEvent(Uint32 type)"""
+
+ @staticmethod
+ def SDL_HasEvents(minType: Any, maxType: Any, /) -> bool:
+ """bool SDL_HasEvents(Uint32 minType, Uint32 maxType)"""
+
+ @staticmethod
+ def SDL_HasGamepad() -> bool:
+ """bool SDL_HasGamepad(void)"""
+
+ @staticmethod
+ def SDL_HasJoystick() -> bool:
+ """bool SDL_HasJoystick(void)"""
+
+ @staticmethod
+ def SDL_HasKeyboard() -> bool:
+ """bool SDL_HasKeyboard(void)"""
+
+ @staticmethod
+ def SDL_HasLASX() -> bool:
+ """bool SDL_HasLASX(void)"""
+
+ @staticmethod
+ def SDL_HasLSX() -> bool:
+ """bool SDL_HasLSX(void)"""
+
+ @staticmethod
+ def SDL_HasMMX() -> bool:
+ """bool SDL_HasMMX(void)"""
+
+ @staticmethod
+ def SDL_HasMouse() -> bool:
+ """bool SDL_HasMouse(void)"""
+
+ @staticmethod
+ def SDL_HasNEON() -> bool:
+ """bool SDL_HasNEON(void)"""
+
+ @staticmethod
+ def SDL_HasPrimarySelectionText() -> bool:
+ """bool SDL_HasPrimarySelectionText(void)"""
+
+ @staticmethod
+ def SDL_HasProperty(props: Any, name: Any, /) -> bool:
+ """bool SDL_HasProperty(SDL_PropertiesID props, const char *name)"""
+
+ @staticmethod
+ def SDL_HasRectIntersection(A: Any, B: Any, /) -> bool:
+ """bool SDL_HasRectIntersection(const SDL_Rect *A, const SDL_Rect *B)"""
+
+ @staticmethod
+ def SDL_HasRectIntersectionFloat(A: Any, B: Any, /) -> bool:
+ """bool SDL_HasRectIntersectionFloat(const SDL_FRect *A, const SDL_FRect *B)"""
+
+ @staticmethod
+ def SDL_HasSSE() -> bool:
+ """bool SDL_HasSSE(void)"""
+
+ @staticmethod
+ def SDL_HasSSE2() -> bool:
+ """bool SDL_HasSSE2(void)"""
+
+ @staticmethod
+ def SDL_HasSSE3() -> bool:
+ """bool SDL_HasSSE3(void)"""
+
+ @staticmethod
+ def SDL_HasSSE41() -> bool:
+ """bool SDL_HasSSE41(void)"""
+
+ @staticmethod
+ def SDL_HasSSE42() -> bool:
+ """bool SDL_HasSSE42(void)"""
+
+ @staticmethod
+ def SDL_HasScreenKeyboardSupport() -> bool:
+ """bool SDL_HasScreenKeyboardSupport(void)"""
+
+ @staticmethod
+ def SDL_HideCursor() -> bool:
+ """bool SDL_HideCursor(void)"""
+
+ @staticmethod
+ def SDL_HideWindow(window: Any, /) -> bool:
+ """bool SDL_HideWindow(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_IOFromConstMem(mem: Any, size: int, /) -> Any:
+ """SDL_IOStream *SDL_IOFromConstMem(const void *mem, size_t size)"""
+
+ @staticmethod
+ def SDL_IOFromDynamicMem() -> Any:
+ """SDL_IOStream *SDL_IOFromDynamicMem(void)"""
+
+ @staticmethod
+ def SDL_IOFromFile(file: Any, mode: Any, /) -> Any:
+ """SDL_IOStream *SDL_IOFromFile(const char *file, const char *mode)"""
+
+ @staticmethod
+ def SDL_IOFromMem(mem: Any, size: int, /) -> Any:
+ """SDL_IOStream *SDL_IOFromMem(void *mem, size_t size)"""
+
+ @staticmethod
+ def SDL_IOprintf(context: Any, fmt: Any, /, *__args: Any) -> int:
+ """size_t SDL_IOprintf(SDL_IOStream *context, const char *fmt, ...)"""
+
+ @staticmethod
+ def SDL_Init(flags: Any, /) -> bool:
+ """bool SDL_Init(SDL_InitFlags flags)"""
+
+ @staticmethod
+ def SDL_InitHapticRumble(haptic: Any, /) -> bool:
+ """bool SDL_InitHapticRumble(SDL_Haptic *haptic)"""
+
+ @staticmethod
+ def SDL_InitSubSystem(flags: Any, /) -> bool:
+ """bool SDL_InitSubSystem(SDL_InitFlags flags)"""
+
+ @staticmethod
+ def SDL_InsertGPUDebugLabel(command_buffer: Any, text: Any, /) -> None:
+ """void SDL_InsertGPUDebugLabel(SDL_GPUCommandBuffer *command_buffer, const char *text)"""
+
+ @staticmethod
+ def SDL_InsertTrayEntryAt(menu: Any, pos: int, label: Any, flags: Any, /) -> Any:
+ """SDL_TrayEntry *SDL_InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_TrayEntryFlags flags)"""
+
+ @staticmethod
+ def SDL_IsAudioDevicePhysical(devid: Any, /) -> bool:
+ """bool SDL_IsAudioDevicePhysical(SDL_AudioDeviceID devid)"""
+
+ @staticmethod
+ def SDL_IsAudioDevicePlayback(devid: Any, /) -> bool:
+ """bool SDL_IsAudioDevicePlayback(SDL_AudioDeviceID devid)"""
+
+ @staticmethod
+ def SDL_IsGamepad(instance_id: Any, /) -> bool:
+ """bool SDL_IsGamepad(SDL_JoystickID instance_id)"""
+
+ @staticmethod
+ def SDL_IsJoystickHaptic(joystick: Any, /) -> bool:
+ """bool SDL_IsJoystickHaptic(SDL_Joystick *joystick)"""
+
+ @staticmethod
+ def SDL_IsJoystickVirtual(instance_id: Any, /) -> bool:
+ """bool SDL_IsJoystickVirtual(SDL_JoystickID instance_id)"""
+
+ @staticmethod
+ def SDL_IsMainThread() -> bool:
+ """bool SDL_IsMainThread(void)"""
+
+ @staticmethod
+ def SDL_IsMouseHaptic() -> bool:
+ """bool SDL_IsMouseHaptic(void)"""
+
+ @staticmethod
+ def SDL_IsTV() -> bool:
+ """bool SDL_IsTV(void)"""
+
+ @staticmethod
+ def SDL_IsTablet() -> bool:
+ """bool SDL_IsTablet(void)"""
+
+ @staticmethod
+ def SDL_JoystickConnected(joystick: Any, /) -> bool:
+ """bool SDL_JoystickConnected(SDL_Joystick *joystick)"""
+
+ @staticmethod
+ def SDL_JoystickEventsEnabled() -> bool:
+ """bool SDL_JoystickEventsEnabled(void)"""
+
+ @staticmethod
+ def SDL_KillProcess(process: Any, force: bool, /) -> bool:
+ """bool SDL_KillProcess(SDL_Process *process, bool force)"""
+
+ @staticmethod
+ def SDL_LoadBMP(file: Any, /) -> Any:
+ """SDL_Surface *SDL_LoadBMP(const char *file)"""
+
+ @staticmethod
+ def SDL_LoadBMP_IO(src: Any, closeio: bool, /) -> Any:
+ """SDL_Surface *SDL_LoadBMP_IO(SDL_IOStream *src, bool closeio)"""
+
+ @staticmethod
+ def SDL_LoadFile(file: Any, datasize: Any, /) -> Any:
+ """void *SDL_LoadFile(const char *file, size_t *datasize)"""
+
+ @staticmethod
+ def SDL_LoadFileAsync(file: Any, queue: Any, userdata: Any, /) -> bool:
+ """bool SDL_LoadFileAsync(const char *file, SDL_AsyncIOQueue *queue, void *userdata)"""
+
+ @staticmethod
+ def SDL_LoadFile_IO(src: Any, datasize: Any, closeio: bool, /) -> Any:
+ """void *SDL_LoadFile_IO(SDL_IOStream *src, size_t *datasize, bool closeio)"""
+
+ @staticmethod
+ def SDL_LoadFunction(handle: Any, name: Any, /) -> Any:
+ """SDL_FunctionPointer SDL_LoadFunction(SDL_SharedObject *handle, const char *name)"""
+
+ @staticmethod
+ def SDL_LoadObject(sofile: Any, /) -> Any:
+ """SDL_SharedObject *SDL_LoadObject(const char *sofile)"""
+
+ @staticmethod
+ def SDL_LoadWAV(path: Any, spec: Any, audio_buf: Any, audio_len: Any, /) -> bool:
+ """bool SDL_LoadWAV(const char *path, SDL_AudioSpec *spec, Uint8 **audio_buf, Uint32 *audio_len)"""
+
+ @staticmethod
+ def SDL_LoadWAV_IO(src: Any, closeio: bool, spec: Any, audio_buf: Any, audio_len: Any, /) -> bool:
+ """bool SDL_LoadWAV_IO(SDL_IOStream *src, bool closeio, SDL_AudioSpec *spec, Uint8 **audio_buf, Uint32 *audio_len)"""
+
+ @staticmethod
+ def SDL_LockAudioStream(stream: Any, /) -> bool:
+ """bool SDL_LockAudioStream(SDL_AudioStream *stream)"""
+
+ @staticmethod
+ def SDL_LockJoysticks() -> None:
+ """void SDL_LockJoysticks(void)"""
+
+ @staticmethod
+ def SDL_LockMutex(mutex: Any, /) -> None:
+ """void SDL_LockMutex(SDL_Mutex *mutex)"""
+
+ @staticmethod
+ def SDL_LockProperties(props: Any, /) -> bool:
+ """bool SDL_LockProperties(SDL_PropertiesID props)"""
+
+ @staticmethod
+ def SDL_LockRWLockForReading(rwlock: Any, /) -> None:
+ """void SDL_LockRWLockForReading(SDL_RWLock *rwlock)"""
+
+ @staticmethod
+ def SDL_LockRWLockForWriting(rwlock: Any, /) -> None:
+ """void SDL_LockRWLockForWriting(SDL_RWLock *rwlock)"""
+
+ @staticmethod
+ def SDL_LockSpinlock(lock: Any, /) -> None:
+ """void SDL_LockSpinlock(SDL_SpinLock *lock)"""
+
+ @staticmethod
+ def SDL_LockSurface(surface: Any, /) -> bool:
+ """bool SDL_LockSurface(SDL_Surface *surface)"""
+
+ @staticmethod
+ def SDL_LockTexture(texture: Any, rect: Any, pixels: Any, pitch: Any, /) -> bool:
+ """bool SDL_LockTexture(SDL_Texture *texture, const SDL_Rect *rect, void **pixels, int *pitch)"""
+
+ @staticmethod
+ def SDL_LockTextureToSurface(texture: Any, rect: Any, surface: Any, /) -> bool:
+ """bool SDL_LockTextureToSurface(SDL_Texture *texture, const SDL_Rect *rect, SDL_Surface **surface)"""
+
+ @staticmethod
+ def SDL_Log(fmt: Any, /, *__args: Any) -> None:
+ """void SDL_Log(const char *fmt, ...)"""
+
+ @staticmethod
+ def SDL_LogCritical(category: int, fmt: Any, /, *__args: Any) -> None:
+ """void SDL_LogCritical(int category, const char *fmt, ...)"""
+
+ @staticmethod
+ def SDL_LogDebug(category: int, fmt: Any, /, *__args: Any) -> None:
+ """void SDL_LogDebug(int category, const char *fmt, ...)"""
+
+ @staticmethod
+ def SDL_LogError(category: int, fmt: Any, /, *__args: Any) -> None:
+ """void SDL_LogError(int category, const char *fmt, ...)"""
+
+ @staticmethod
+ def SDL_LogInfo(category: int, fmt: Any, /, *__args: Any) -> None:
+ """void SDL_LogInfo(int category, const char *fmt, ...)"""
+
+ @staticmethod
+ def SDL_LogMessage(category: int, priority: Any, fmt: Any, /, *__args: Any) -> None:
+ """void SDL_LogMessage(int category, SDL_LogPriority priority, const char *fmt, ...)"""
+
+ @staticmethod
+ def SDL_LogTrace(category: int, fmt: Any, /, *__args: Any) -> None:
+ """void SDL_LogTrace(int category, const char *fmt, ...)"""
+
+ @staticmethod
+ def SDL_LogVerbose(category: int, fmt: Any, /, *__args: Any) -> None:
+ """void SDL_LogVerbose(int category, const char *fmt, ...)"""
+
+ @staticmethod
+ def SDL_LogWarn(category: int, fmt: Any, /, *__args: Any) -> None:
+ """void SDL_LogWarn(int category, const char *fmt, ...)"""
+
+ @staticmethod
+ def SDL_MapGPUTransferBuffer(device: Any, transfer_buffer: Any, cycle: bool, /) -> Any:
+ """void *SDL_MapGPUTransferBuffer(SDL_GPUDevice *device, SDL_GPUTransferBuffer *transfer_buffer, bool cycle)"""
+
+ @staticmethod
+ def SDL_MapRGB(format: Any, palette: Any, r: Any, g: Any, b: Any, /) -> Any:
+ """Uint32 SDL_MapRGB(const SDL_PixelFormatDetails *format, const SDL_Palette *palette, Uint8 r, Uint8 g, Uint8 b)"""
+
+ @staticmethod
+ def SDL_MapRGBA(format: Any, palette: Any, r: Any, g: Any, b: Any, a: Any, /) -> Any:
+ """Uint32 SDL_MapRGBA(const SDL_PixelFormatDetails *format, const SDL_Palette *palette, Uint8 r, Uint8 g, Uint8 b, Uint8 a)"""
+
+ @staticmethod
+ def SDL_MapSurfaceRGB(surface: Any, r: Any, g: Any, b: Any, /) -> Any:
+ """Uint32 SDL_MapSurfaceRGB(SDL_Surface *surface, Uint8 r, Uint8 g, Uint8 b)"""
+
+ @staticmethod
+ def SDL_MapSurfaceRGBA(surface: Any, r: Any, g: Any, b: Any, a: Any, /) -> Any:
+ """Uint32 SDL_MapSurfaceRGBA(SDL_Surface *surface, Uint8 r, Uint8 g, Uint8 b, Uint8 a)"""
+
+ @staticmethod
+ def SDL_MaximizeWindow(window: Any, /) -> bool:
+ """bool SDL_MaximizeWindow(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_MemoryBarrierAcquireFunction() -> None:
+ """void SDL_MemoryBarrierAcquireFunction(void)"""
+
+ @staticmethod
+ def SDL_MemoryBarrierReleaseFunction() -> None:
+ """void SDL_MemoryBarrierReleaseFunction(void)"""
+
+ @staticmethod
+ def SDL_Metal_CreateView(window: Any, /) -> Any:
+ """SDL_MetalView SDL_Metal_CreateView(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_Metal_DestroyView(view: Any, /) -> None:
+ """void SDL_Metal_DestroyView(SDL_MetalView view)"""
+
+ @staticmethod
+ def SDL_Metal_GetLayer(view: Any, /) -> Any:
+ """void *SDL_Metal_GetLayer(SDL_MetalView view)"""
+
+ @staticmethod
+ def SDL_MinimizeWindow(window: Any, /) -> bool:
+ """bool SDL_MinimizeWindow(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_MixAudio(dst: Any, src: Any, format: Any, len: Any, volume: float, /) -> bool:
+ """bool SDL_MixAudio(Uint8 *dst, const Uint8 *src, SDL_AudioFormat format, Uint32 len, float volume)"""
+
+ @staticmethod
+ def SDL_OnApplicationDidEnterBackground() -> None:
+ """void SDL_OnApplicationDidEnterBackground(void)"""
+
+ @staticmethod
+ def SDL_OnApplicationDidEnterForeground() -> None:
+ """void SDL_OnApplicationDidEnterForeground(void)"""
+
+ @staticmethod
+ def SDL_OnApplicationDidReceiveMemoryWarning() -> None:
+ """void SDL_OnApplicationDidReceiveMemoryWarning(void)"""
+
+ @staticmethod
+ def SDL_OnApplicationWillEnterBackground() -> None:
+ """void SDL_OnApplicationWillEnterBackground(void)"""
+
+ @staticmethod
+ def SDL_OnApplicationWillEnterForeground() -> None:
+ """void SDL_OnApplicationWillEnterForeground(void)"""
+
+ @staticmethod
+ def SDL_OnApplicationWillTerminate() -> None:
+ """void SDL_OnApplicationWillTerminate(void)"""
+
+ @staticmethod
+ def SDL_OpenAudioDevice(devid: Any, spec: Any, /) -> Any:
+ """SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSpec *spec)"""
+
+ @staticmethod
+ def SDL_OpenAudioDeviceStream(devid: Any, spec: Any, callback: Any, userdata: Any, /) -> Any:
+ """SDL_AudioStream *SDL_OpenAudioDeviceStream(SDL_AudioDeviceID devid, const SDL_AudioSpec *spec, SDL_AudioStreamCallback callback, void *userdata)"""
+
+ @staticmethod
+ def SDL_OpenCamera(instance_id: Any, spec: Any, /) -> Any:
+ """SDL_Camera *SDL_OpenCamera(SDL_CameraID instance_id, const SDL_CameraSpec *spec)"""
+
+ @staticmethod
+ def SDL_OpenFileStorage(path: Any, /) -> Any:
+ """SDL_Storage *SDL_OpenFileStorage(const char *path)"""
+
+ @staticmethod
+ def SDL_OpenGamepad(instance_id: Any, /) -> Any:
+ """SDL_Gamepad *SDL_OpenGamepad(SDL_JoystickID instance_id)"""
+
+ @staticmethod
+ def SDL_OpenHaptic(instance_id: Any, /) -> Any:
+ """SDL_Haptic *SDL_OpenHaptic(SDL_HapticID instance_id)"""
+
+ @staticmethod
+ def SDL_OpenHapticFromJoystick(joystick: Any, /) -> Any:
+ """SDL_Haptic *SDL_OpenHapticFromJoystick(SDL_Joystick *joystick)"""
+
+ @staticmethod
+ def SDL_OpenHapticFromMouse() -> Any:
+ """SDL_Haptic *SDL_OpenHapticFromMouse(void)"""
+
+ @staticmethod
+ def SDL_OpenIO(iface: Any, userdata: Any, /) -> Any:
+ """SDL_IOStream *SDL_OpenIO(const SDL_IOStreamInterface *iface, void *userdata)"""
+
+ @staticmethod
+ def SDL_OpenJoystick(instance_id: Any, /) -> Any:
+ """SDL_Joystick *SDL_OpenJoystick(SDL_JoystickID instance_id)"""
+
+ @staticmethod
+ def SDL_OpenSensor(instance_id: Any, /) -> Any:
+ """SDL_Sensor *SDL_OpenSensor(SDL_SensorID instance_id)"""
+
+ @staticmethod
+ def SDL_OpenStorage(iface: Any, userdata: Any, /) -> Any:
+ """SDL_Storage *SDL_OpenStorage(const SDL_StorageInterface *iface, void *userdata)"""
+
+ @staticmethod
+ def SDL_OpenTitleStorage(override: Any, props: Any, /) -> Any:
+ """SDL_Storage *SDL_OpenTitleStorage(const char *override, SDL_PropertiesID props)"""
+
+ @staticmethod
+ def SDL_OpenURL(url: Any, /) -> bool:
+ """bool SDL_OpenURL(const char *url)"""
+
+ @staticmethod
+ def SDL_OpenUserStorage(org: Any, app: Any, props: Any, /) -> Any:
+ """SDL_Storage *SDL_OpenUserStorage(const char *org, const char *app, SDL_PropertiesID props)"""
+
+ @staticmethod
+ def SDL_OutOfMemory() -> bool:
+ """bool SDL_OutOfMemory(void)"""
+
+ @staticmethod
+ def SDL_PauseAudioDevice(devid: Any, /) -> bool:
+ """bool SDL_PauseAudioDevice(SDL_AudioDeviceID devid)"""
+
+ @staticmethod
+ def SDL_PauseAudioStreamDevice(stream: Any, /) -> bool:
+ """bool SDL_PauseAudioStreamDevice(SDL_AudioStream *stream)"""
+
+ @staticmethod
+ def SDL_PauseHaptic(haptic: Any, /) -> bool:
+ """bool SDL_PauseHaptic(SDL_Haptic *haptic)"""
+
+ @staticmethod
+ def SDL_PeepEvents(events: Any, numevents: int, action: Any, minType: Any, maxType: Any, /) -> int:
+ """int SDL_PeepEvents(SDL_Event *events, int numevents, SDL_EventAction action, Uint32 minType, Uint32 maxType)"""
+
+ @staticmethod
+ def SDL_PlayHapticRumble(haptic: Any, strength: float, length: Any, /) -> bool:
+ """bool SDL_PlayHapticRumble(SDL_Haptic *haptic, float strength, Uint32 length)"""
+
+ @staticmethod
+ def SDL_PollEvent(event: Any, /) -> bool:
+ """bool SDL_PollEvent(SDL_Event *event)"""
+
+ @staticmethod
+ def SDL_PopGPUDebugGroup(command_buffer: Any, /) -> None:
+ """void SDL_PopGPUDebugGroup(SDL_GPUCommandBuffer *command_buffer)"""
+
+ @staticmethod
+ def SDL_PremultiplyAlpha(
+ width: int,
+ height: int,
+ src_format: Any,
+ src: Any,
+ src_pitch: int,
+ dst_format: Any,
+ dst: Any,
+ dst_pitch: int,
+ linear: bool,
+ /,
+ ) -> bool:
+ """bool SDL_PremultiplyAlpha(int width, int height, SDL_PixelFormat src_format, const void *src, int src_pitch, SDL_PixelFormat dst_format, void *dst, int dst_pitch, bool linear)"""
+
+ @staticmethod
+ def SDL_PremultiplySurfaceAlpha(surface: Any, linear: bool, /) -> bool:
+ """bool SDL_PremultiplySurfaceAlpha(SDL_Surface *surface, bool linear)"""
+
+ @staticmethod
+ def SDL_PumpEvents() -> None:
+ """void SDL_PumpEvents(void)"""
+
+ @staticmethod
+ def SDL_PushEvent(event: Any, /) -> bool:
+ """bool SDL_PushEvent(SDL_Event *event)"""
+
+ @staticmethod
+ def SDL_PushGPUComputeUniformData(command_buffer: Any, slot_index: Any, data: Any, length: Any, /) -> None:
+ """void SDL_PushGPUComputeUniformData(SDL_GPUCommandBuffer *command_buffer, Uint32 slot_index, const void *data, Uint32 length)"""
+
+ @staticmethod
+ def SDL_PushGPUDebugGroup(command_buffer: Any, name: Any, /) -> None:
+ """void SDL_PushGPUDebugGroup(SDL_GPUCommandBuffer *command_buffer, const char *name)"""
+
+ @staticmethod
+ def SDL_PushGPUFragmentUniformData(command_buffer: Any, slot_index: Any, data: Any, length: Any, /) -> None:
+ """void SDL_PushGPUFragmentUniformData(SDL_GPUCommandBuffer *command_buffer, Uint32 slot_index, const void *data, Uint32 length)"""
+
+ @staticmethod
+ def SDL_PushGPUVertexUniformData(command_buffer: Any, slot_index: Any, data: Any, length: Any, /) -> None:
+ """void SDL_PushGPUVertexUniformData(SDL_GPUCommandBuffer *command_buffer, Uint32 slot_index, const void *data, Uint32 length)"""
+
+ @staticmethod
+ def SDL_PutAudioStreamData(stream: Any, buf: Any, len: int, /) -> bool:
+ """bool SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len)"""
+
+ @staticmethod
+ def SDL_QueryGPUFence(device: Any, fence: Any, /) -> bool:
+ """bool SDL_QueryGPUFence(SDL_GPUDevice *device, SDL_GPUFence *fence)"""
+
+ @staticmethod
+ def SDL_Quit() -> None:
+ """void SDL_Quit(void)"""
+
+ @staticmethod
+ def SDL_QuitSubSystem(flags: Any, /) -> None:
+ """void SDL_QuitSubSystem(SDL_InitFlags flags)"""
+
+ @staticmethod
+ def SDL_RaiseWindow(window: Any, /) -> bool:
+ """bool SDL_RaiseWindow(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_ReadAsyncIO(asyncio: Any, ptr: Any, offset: Any, size: Any, queue: Any, userdata: Any, /) -> bool:
+ """bool SDL_ReadAsyncIO(SDL_AsyncIO *asyncio, void *ptr, Uint64 offset, Uint64 size, SDL_AsyncIOQueue *queue, void *userdata)"""
+
+ @staticmethod
+ def SDL_ReadIO(context: Any, ptr: Any, size: int, /) -> int:
+ """size_t SDL_ReadIO(SDL_IOStream *context, void *ptr, size_t size)"""
+
+ @staticmethod
+ def SDL_ReadProcess(process: Any, datasize: Any, exitcode: Any, /) -> Any:
+ """void *SDL_ReadProcess(SDL_Process *process, size_t *datasize, int *exitcode)"""
+
+ @staticmethod
+ def SDL_ReadS16BE(src: Any, value: Any, /) -> bool:
+ """bool SDL_ReadS16BE(SDL_IOStream *src, Sint16 *value)"""
+
+ @staticmethod
+ def SDL_ReadS16LE(src: Any, value: Any, /) -> bool:
+ """bool SDL_ReadS16LE(SDL_IOStream *src, Sint16 *value)"""
+
+ @staticmethod
+ def SDL_ReadS32BE(src: Any, value: Any, /) -> bool:
+ """bool SDL_ReadS32BE(SDL_IOStream *src, Sint32 *value)"""
+
+ @staticmethod
+ def SDL_ReadS32LE(src: Any, value: Any, /) -> bool:
+ """bool SDL_ReadS32LE(SDL_IOStream *src, Sint32 *value)"""
+
+ @staticmethod
+ def SDL_ReadS64BE(src: Any, value: Any, /) -> bool:
+ """bool SDL_ReadS64BE(SDL_IOStream *src, Sint64 *value)"""
+
+ @staticmethod
+ def SDL_ReadS64LE(src: Any, value: Any, /) -> bool:
+ """bool SDL_ReadS64LE(SDL_IOStream *src, Sint64 *value)"""
+
+ @staticmethod
+ def SDL_ReadS8(src: Any, value: Any, /) -> bool:
+ """bool SDL_ReadS8(SDL_IOStream *src, Sint8 *value)"""
+
+ @staticmethod
+ def SDL_ReadStorageFile(storage: Any, path: Any, destination: Any, length: Any, /) -> bool:
+ """bool SDL_ReadStorageFile(SDL_Storage *storage, const char *path, void *destination, Uint64 length)"""
+
+ @staticmethod
+ def SDL_ReadSurfacePixel(surface: Any, x: int, y: int, r: Any, g: Any, b: Any, a: Any, /) -> bool:
+ """bool SDL_ReadSurfacePixel(SDL_Surface *surface, int x, int y, Uint8 *r, Uint8 *g, Uint8 *b, Uint8 *a)"""
+
+ @staticmethod
+ def SDL_ReadSurfacePixelFloat(surface: Any, x: int, y: int, r: Any, g: Any, b: Any, a: Any, /) -> bool:
+ """bool SDL_ReadSurfacePixelFloat(SDL_Surface *surface, int x, int y, float *r, float *g, float *b, float *a)"""
+
+ @staticmethod
+ def SDL_ReadU16BE(src: Any, value: Any, /) -> bool:
+ """bool SDL_ReadU16BE(SDL_IOStream *src, Uint16 *value)"""
+
+ @staticmethod
+ def SDL_ReadU16LE(src: Any, value: Any, /) -> bool:
+ """bool SDL_ReadU16LE(SDL_IOStream *src, Uint16 *value)"""
+
+ @staticmethod
+ def SDL_ReadU32BE(src: Any, value: Any, /) -> bool:
+ """bool SDL_ReadU32BE(SDL_IOStream *src, Uint32 *value)"""
+
+ @staticmethod
+ def SDL_ReadU32LE(src: Any, value: Any, /) -> bool:
+ """bool SDL_ReadU32LE(SDL_IOStream *src, Uint32 *value)"""
+
+ @staticmethod
+ def SDL_ReadU64BE(src: Any, value: Any, /) -> bool:
+ """bool SDL_ReadU64BE(SDL_IOStream *src, Uint64 *value)"""
+
+ @staticmethod
+ def SDL_ReadU64LE(src: Any, value: Any, /) -> bool:
+ """bool SDL_ReadU64LE(SDL_IOStream *src, Uint64 *value)"""
+
+ @staticmethod
+ def SDL_ReadU8(src: Any, value: Any, /) -> bool:
+ """bool SDL_ReadU8(SDL_IOStream *src, Uint8 *value)"""
+
+ @staticmethod
+ def SDL_RegisterEvents(numevents: int, /) -> Any:
+ """Uint32 SDL_RegisterEvents(int numevents)"""
+
+ @staticmethod
+ def SDL_ReleaseCameraFrame(camera: Any, frame: Any, /) -> None:
+ """void SDL_ReleaseCameraFrame(SDL_Camera *camera, SDL_Surface *frame)"""
+
+ @staticmethod
+ def SDL_ReleaseGPUBuffer(device: Any, buffer: Any, /) -> None:
+ """void SDL_ReleaseGPUBuffer(SDL_GPUDevice *device, SDL_GPUBuffer *buffer)"""
+
+ @staticmethod
+ def SDL_ReleaseGPUComputePipeline(device: Any, compute_pipeline: Any, /) -> None:
+ """void SDL_ReleaseGPUComputePipeline(SDL_GPUDevice *device, SDL_GPUComputePipeline *compute_pipeline)"""
+
+ @staticmethod
+ def SDL_ReleaseGPUFence(device: Any, fence: Any, /) -> None:
+ """void SDL_ReleaseGPUFence(SDL_GPUDevice *device, SDL_GPUFence *fence)"""
+
+ @staticmethod
+ def SDL_ReleaseGPUGraphicsPipeline(device: Any, graphics_pipeline: Any, /) -> None:
+ """void SDL_ReleaseGPUGraphicsPipeline(SDL_GPUDevice *device, SDL_GPUGraphicsPipeline *graphics_pipeline)"""
+
+ @staticmethod
+ def SDL_ReleaseGPUSampler(device: Any, sampler: Any, /) -> None:
+ """void SDL_ReleaseGPUSampler(SDL_GPUDevice *device, SDL_GPUSampler *sampler)"""
+
+ @staticmethod
+ def SDL_ReleaseGPUShader(device: Any, shader: Any, /) -> None:
+ """void SDL_ReleaseGPUShader(SDL_GPUDevice *device, SDL_GPUShader *shader)"""
+
+ @staticmethod
+ def SDL_ReleaseGPUTexture(device: Any, texture: Any, /) -> None:
+ """void SDL_ReleaseGPUTexture(SDL_GPUDevice *device, SDL_GPUTexture *texture)"""
+
+ @staticmethod
+ def SDL_ReleaseGPUTransferBuffer(device: Any, transfer_buffer: Any, /) -> None:
+ """void SDL_ReleaseGPUTransferBuffer(SDL_GPUDevice *device, SDL_GPUTransferBuffer *transfer_buffer)"""
+
+ @staticmethod
+ def SDL_ReleaseWindowFromGPUDevice(device: Any, window: Any, /) -> None:
+ """void SDL_ReleaseWindowFromGPUDevice(SDL_GPUDevice *device, SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_ReloadGamepadMappings() -> bool:
+ """bool SDL_ReloadGamepadMappings(void)"""
+
+ @staticmethod
+ def SDL_RemoveEventWatch(filter: Any, userdata: Any, /) -> None:
+ """void SDL_RemoveEventWatch(SDL_EventFilter filter, void *userdata)"""
+
+ @staticmethod
+ def SDL_RemoveHintCallback(name: Any, callback: Any, userdata: Any, /) -> None:
+ """void SDL_RemoveHintCallback(const char *name, SDL_HintCallback callback, void *userdata)"""
+
+ @staticmethod
+ def SDL_RemovePath(path: Any, /) -> bool:
+ """bool SDL_RemovePath(const char *path)"""
+
+ @staticmethod
+ def SDL_RemoveStoragePath(storage: Any, path: Any, /) -> bool:
+ """bool SDL_RemoveStoragePath(SDL_Storage *storage, const char *path)"""
+
+ @staticmethod
+ def SDL_RemoveSurfaceAlternateImages(surface: Any, /) -> None:
+ """void SDL_RemoveSurfaceAlternateImages(SDL_Surface *surface)"""
+
+ @staticmethod
+ def SDL_RemoveTimer(id: Any, /) -> bool:
+ """bool SDL_RemoveTimer(SDL_TimerID id)"""
+
+ @staticmethod
+ def SDL_RemoveTrayEntry(entry: Any, /) -> None:
+ """void SDL_RemoveTrayEntry(SDL_TrayEntry *entry)"""
+
+ @staticmethod
+ def SDL_RenamePath(oldpath: Any, newpath: Any, /) -> bool:
+ """bool SDL_RenamePath(const char *oldpath, const char *newpath)"""
+
+ @staticmethod
+ def SDL_RenameStoragePath(storage: Any, oldpath: Any, newpath: Any, /) -> bool:
+ """bool SDL_RenameStoragePath(SDL_Storage *storage, const char *oldpath, const char *newpath)"""
+
+ @staticmethod
+ def SDL_RenderClear(renderer: Any, /) -> bool:
+ """bool SDL_RenderClear(SDL_Renderer *renderer)"""
+
+ @staticmethod
+ def SDL_RenderClipEnabled(renderer: Any, /) -> bool:
+ """bool SDL_RenderClipEnabled(SDL_Renderer *renderer)"""
+
+ @staticmethod
+ def SDL_RenderCoordinatesFromWindow(renderer: Any, window_x: float, window_y: float, x: Any, y: Any, /) -> bool:
+ """bool SDL_RenderCoordinatesFromWindow(SDL_Renderer *renderer, float window_x, float window_y, float *x, float *y)"""
+
+ @staticmethod
+ def SDL_RenderCoordinatesToWindow(renderer: Any, x: float, y: float, window_x: Any, window_y: Any, /) -> bool:
+ """bool SDL_RenderCoordinatesToWindow(SDL_Renderer *renderer, float x, float y, float *window_x, float *window_y)"""
+
+ @staticmethod
+ def SDL_RenderDebugText(renderer: Any, x: float, y: float, str: Any, /) -> bool:
+ """bool SDL_RenderDebugText(SDL_Renderer *renderer, float x, float y, const char *str)"""
+
+ @staticmethod
+ def SDL_RenderDebugTextFormat(renderer: Any, x: float, y: float, fmt: Any, /, *__args: Any) -> bool:
+ """bool SDL_RenderDebugTextFormat(SDL_Renderer *renderer, float x, float y, const char *fmt, ...)"""
+
+ @staticmethod
+ def SDL_RenderFillRect(renderer: Any, rect: Any, /) -> bool:
+ """bool SDL_RenderFillRect(SDL_Renderer *renderer, const SDL_FRect *rect)"""
+
+ @staticmethod
+ def SDL_RenderFillRects(renderer: Any, rects: Any, count: int, /) -> bool:
+ """bool SDL_RenderFillRects(SDL_Renderer *renderer, const SDL_FRect *rects, int count)"""
+
+ @staticmethod
+ def SDL_RenderGeometry(
+ renderer: Any, texture: Any, vertices: Any, num_vertices: int, indices: Any, num_indices: int, /
+ ) -> bool:
+ """bool SDL_RenderGeometry(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Vertex *vertices, int num_vertices, const int *indices, int num_indices)"""
+
+ @staticmethod
+ def SDL_RenderGeometryRaw(
+ renderer: Any,
+ texture: Any,
+ xy: Any,
+ xy_stride: int,
+ color: Any,
+ color_stride: int,
+ uv: Any,
+ uv_stride: int,
+ num_vertices: int,
+ indices: Any,
+ num_indices: int,
+ size_indices: int,
+ /,
+ ) -> bool:
+ """bool SDL_RenderGeometryRaw(SDL_Renderer *renderer, SDL_Texture *texture, const float *xy, int xy_stride, const SDL_FColor *color, int color_stride, const float *uv, int uv_stride, int num_vertices, const void *indices, int num_indices, int size_indices)"""
+
+ @staticmethod
+ def SDL_RenderLine(renderer: Any, x1: float, y1: float, x2: float, y2: float, /) -> bool:
+ """bool SDL_RenderLine(SDL_Renderer *renderer, float x1, float y1, float x2, float y2)"""
+
+ @staticmethod
+ def SDL_RenderLines(renderer: Any, points: Any, count: int, /) -> bool:
+ """bool SDL_RenderLines(SDL_Renderer *renderer, const SDL_FPoint *points, int count)"""
+
+ @staticmethod
+ def SDL_RenderPoint(renderer: Any, x: float, y: float, /) -> bool:
+ """bool SDL_RenderPoint(SDL_Renderer *renderer, float x, float y)"""
+
+ @staticmethod
+ def SDL_RenderPoints(renderer: Any, points: Any, count: int, /) -> bool:
+ """bool SDL_RenderPoints(SDL_Renderer *renderer, const SDL_FPoint *points, int count)"""
+
+ @staticmethod
+ def SDL_RenderPresent(renderer: Any, /) -> bool:
+ """bool SDL_RenderPresent(SDL_Renderer *renderer)"""
+
+ @staticmethod
+ def SDL_RenderReadPixels(renderer: Any, rect: Any, /) -> Any:
+ """SDL_Surface *SDL_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rect *rect)"""
+
+ @staticmethod
+ def SDL_RenderRect(renderer: Any, rect: Any, /) -> bool:
+ """bool SDL_RenderRect(SDL_Renderer *renderer, const SDL_FRect *rect)"""
+
+ @staticmethod
+ def SDL_RenderRects(renderer: Any, rects: Any, count: int, /) -> bool:
+ """bool SDL_RenderRects(SDL_Renderer *renderer, const SDL_FRect *rects, int count)"""
+
+ @staticmethod
+ def SDL_RenderTexture(renderer: Any, texture: Any, srcrect: Any, dstrect: Any, /) -> bool:
+ """bool SDL_RenderTexture(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, const SDL_FRect *dstrect)"""
+
+ @staticmethod
+ def SDL_RenderTexture9Grid(
+ renderer: Any,
+ texture: Any,
+ srcrect: Any,
+ left_width: float,
+ right_width: float,
+ top_height: float,
+ bottom_height: float,
+ scale: float,
+ dstrect: Any,
+ /,
+ ) -> bool:
+ """bool SDL_RenderTexture9Grid(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, float left_width, float right_width, float top_height, float bottom_height, float scale, const SDL_FRect *dstrect)"""
+
+ @staticmethod
+ def SDL_RenderTextureAffine(
+ renderer: Any, texture: Any, srcrect: Any, origin: Any, right: Any, down: Any, /
+ ) -> bool:
+ """bool SDL_RenderTextureAffine(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, const SDL_FPoint *origin, const SDL_FPoint *right, const SDL_FPoint *down)"""
+
+ @staticmethod
+ def SDL_RenderTextureRotated(
+ renderer: Any, texture: Any, srcrect: Any, dstrect: Any, angle: float, center: Any, flip: Any, /
+ ) -> bool:
+ """bool SDL_RenderTextureRotated(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, const SDL_FRect *dstrect, double angle, const SDL_FPoint *center, SDL_FlipMode flip)"""
+
+ @staticmethod
+ def SDL_RenderTextureTiled(renderer: Any, texture: Any, srcrect: Any, scale: float, dstrect: Any, /) -> bool:
+ """bool SDL_RenderTextureTiled(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, float scale, const SDL_FRect *dstrect)"""
+
+ @staticmethod
+ def SDL_RenderViewportSet(renderer: Any, /) -> bool:
+ """bool SDL_RenderViewportSet(SDL_Renderer *renderer)"""
+
+ @staticmethod
+ def SDL_ReportAssertion(data: Any, func: Any, file: Any, line: int, /) -> Any:
+ """SDL_AssertState SDL_ReportAssertion(SDL_AssertData *data, const char *func, const char *file, int line)"""
+
+ @staticmethod
+ def SDL_ResetAssertionReport() -> None:
+ """void SDL_ResetAssertionReport(void)"""
+
+ @staticmethod
+ def SDL_ResetHint(name: Any, /) -> bool:
+ """bool SDL_ResetHint(const char *name)"""
+
+ @staticmethod
+ def SDL_ResetHints() -> None:
+ """void SDL_ResetHints(void)"""
+
+ @staticmethod
+ def SDL_ResetKeyboard() -> None:
+ """void SDL_ResetKeyboard(void)"""
+
+ @staticmethod
+ def SDL_ResetLogPriorities() -> None:
+ """void SDL_ResetLogPriorities(void)"""
+
+ @staticmethod
+ def SDL_RestoreWindow(window: Any, /) -> bool:
+ """bool SDL_RestoreWindow(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_ResumeAudioDevice(devid: Any, /) -> bool:
+ """bool SDL_ResumeAudioDevice(SDL_AudioDeviceID devid)"""
+
+ @staticmethod
+ def SDL_ResumeAudioStreamDevice(stream: Any, /) -> bool:
+ """bool SDL_ResumeAudioStreamDevice(SDL_AudioStream *stream)"""
+
+ @staticmethod
+ def SDL_ResumeHaptic(haptic: Any, /) -> bool:
+ """bool SDL_ResumeHaptic(SDL_Haptic *haptic)"""
+
+ @staticmethod
+ def SDL_RumbleGamepad(
+ gamepad: Any, low_frequency_rumble: Any, high_frequency_rumble: Any, duration_ms: Any, /
+ ) -> bool:
+ """bool SDL_RumbleGamepad(SDL_Gamepad *gamepad, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)"""
+
+ @staticmethod
+ def SDL_RumbleGamepadTriggers(gamepad: Any, left_rumble: Any, right_rumble: Any, duration_ms: Any, /) -> bool:
+ """bool SDL_RumbleGamepadTriggers(SDL_Gamepad *gamepad, Uint16 left_rumble, Uint16 right_rumble, Uint32 duration_ms)"""
+
+ @staticmethod
+ def SDL_RumbleJoystick(
+ joystick: Any, low_frequency_rumble: Any, high_frequency_rumble: Any, duration_ms: Any, /
+ ) -> bool:
+ """bool SDL_RumbleJoystick(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)"""
+
+ @staticmethod
+ def SDL_RumbleJoystickTriggers(joystick: Any, left_rumble: Any, right_rumble: Any, duration_ms: Any, /) -> bool:
+ """bool SDL_RumbleJoystickTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble, Uint32 duration_ms)"""
+
+ @staticmethod
+ def SDL_RunHapticEffect(haptic: Any, effect: int, iterations: Any, /) -> bool:
+ """bool SDL_RunHapticEffect(SDL_Haptic *haptic, int effect, Uint32 iterations)"""
+
+ @staticmethod
+ def SDL_RunOnMainThread(callback: Any, userdata: Any, wait_complete: bool, /) -> bool:
+ """bool SDL_RunOnMainThread(SDL_MainThreadCallback callback, void *userdata, bool wait_complete)"""
+
+ @staticmethod
+ def SDL_SaveBMP(surface: Any, file: Any, /) -> bool:
+ """bool SDL_SaveBMP(SDL_Surface *surface, const char *file)"""
+
+ @staticmethod
+ def SDL_SaveBMP_IO(surface: Any, dst: Any, closeio: bool, /) -> bool:
+ """bool SDL_SaveBMP_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio)"""
+
+ @staticmethod
+ def SDL_SaveFile(file: Any, data: Any, datasize: int, /) -> bool:
+ """bool SDL_SaveFile(const char *file, const void *data, size_t datasize)"""
+
+ @staticmethod
+ def SDL_SaveFile_IO(src: Any, data: Any, datasize: int, closeio: bool, /) -> bool:
+ """bool SDL_SaveFile_IO(SDL_IOStream *src, const void *data, size_t datasize, bool closeio)"""
+
+ @staticmethod
+ def SDL_ScaleSurface(surface: Any, width: int, height: int, scaleMode: Any, /) -> Any:
+ """SDL_Surface *SDL_ScaleSurface(SDL_Surface *surface, int width, int height, SDL_ScaleMode scaleMode)"""
+
+ @staticmethod
+ def SDL_ScreenKeyboardShown(window: Any, /) -> bool:
+ """bool SDL_ScreenKeyboardShown(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_ScreenSaverEnabled() -> bool:
+ """bool SDL_ScreenSaverEnabled(void)"""
+
+ @staticmethod
+ def SDL_SeekIO(context: Any, offset: Any, whence: Any, /) -> Any:
+ """Sint64 SDL_SeekIO(SDL_IOStream *context, Sint64 offset, SDL_IOWhence whence)"""
+
+ @staticmethod
+ def SDL_SendGamepadEffect(gamepad: Any, data: Any, size: int, /) -> bool:
+ """bool SDL_SendGamepadEffect(SDL_Gamepad *gamepad, const void *data, int size)"""
+
+ @staticmethod
+ def SDL_SendJoystickEffect(joystick: Any, data: Any, size: int, /) -> bool:
+ """bool SDL_SendJoystickEffect(SDL_Joystick *joystick, const void *data, int size)"""
+
+ @staticmethod
+ def SDL_SendJoystickVirtualSensorData(
+ joystick: Any, type: Any, sensor_timestamp: Any, data: Any, num_values: int, /
+ ) -> bool:
+ """bool SDL_SendJoystickVirtualSensorData(SDL_Joystick *joystick, SDL_SensorType type, Uint64 sensor_timestamp, const float *data, int num_values)"""
+
+ @staticmethod
+ def SDL_SetAppMetadata(appname: Any, appversion: Any, appidentifier: Any, /) -> bool:
+ """bool SDL_SetAppMetadata(const char *appname, const char *appversion, const char *appidentifier)"""
+
+ @staticmethod
+ def SDL_SetAppMetadataProperty(name: Any, value: Any, /) -> bool:
+ """bool SDL_SetAppMetadataProperty(const char *name, const char *value)"""
+
+ @staticmethod
+ def SDL_SetAssertionHandler(handler: Any, userdata: Any, /) -> None:
+ """void SDL_SetAssertionHandler(SDL_AssertionHandler handler, void *userdata)"""
+
+ @staticmethod
+ def SDL_SetAtomicInt(a: Any, v: int, /) -> int:
+ """int SDL_SetAtomicInt(SDL_AtomicInt *a, int v)"""
+
+ @staticmethod
+ def SDL_SetAtomicPointer(a: Any, v: Any, /) -> Any:
+ """void *SDL_SetAtomicPointer(void **a, void *v)"""
+
+ @staticmethod
+ def SDL_SetAtomicU32(a: Any, v: Any, /) -> Any:
+ """Uint32 SDL_SetAtomicU32(SDL_AtomicU32 *a, Uint32 v)"""
+
+ @staticmethod
+ def SDL_SetAudioDeviceGain(devid: Any, gain: float, /) -> bool:
+ """bool SDL_SetAudioDeviceGain(SDL_AudioDeviceID devid, float gain)"""
+
+ @staticmethod
+ def SDL_SetAudioPostmixCallback(devid: Any, callback: Any, userdata: Any, /) -> bool:
+ """bool SDL_SetAudioPostmixCallback(SDL_AudioDeviceID devid, SDL_AudioPostmixCallback callback, void *userdata)"""
+
+ @staticmethod
+ def SDL_SetAudioStreamFormat(stream: Any, src_spec: Any, dst_spec: Any, /) -> bool:
+ """bool SDL_SetAudioStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *src_spec, const SDL_AudioSpec *dst_spec)"""
+
+ @staticmethod
+ def SDL_SetAudioStreamFrequencyRatio(stream: Any, ratio: float, /) -> bool:
+ """bool SDL_SetAudioStreamFrequencyRatio(SDL_AudioStream *stream, float ratio)"""
+
+ @staticmethod
+ def SDL_SetAudioStreamGain(stream: Any, gain: float, /) -> bool:
+ """bool SDL_SetAudioStreamGain(SDL_AudioStream *stream, float gain)"""
+
+ @staticmethod
+ def SDL_SetAudioStreamGetCallback(stream: Any, callback: Any, userdata: Any, /) -> bool:
+ """bool SDL_SetAudioStreamGetCallback(SDL_AudioStream *stream, SDL_AudioStreamCallback callback, void *userdata)"""
+
+ @staticmethod
+ def SDL_SetAudioStreamInputChannelMap(stream: Any, chmap: Any, count: int, /) -> bool:
+ """bool SDL_SetAudioStreamInputChannelMap(SDL_AudioStream *stream, const int *chmap, int count)"""
+
+ @staticmethod
+ def SDL_SetAudioStreamOutputChannelMap(stream: Any, chmap: Any, count: int, /) -> bool:
+ """bool SDL_SetAudioStreamOutputChannelMap(SDL_AudioStream *stream, const int *chmap, int count)"""
+
+ @staticmethod
+ def SDL_SetAudioStreamPutCallback(stream: Any, callback: Any, userdata: Any, /) -> bool:
+ """bool SDL_SetAudioStreamPutCallback(SDL_AudioStream *stream, SDL_AudioStreamCallback callback, void *userdata)"""
+
+ @staticmethod
+ def SDL_SetBooleanProperty(props: Any, name: Any, value: bool, /) -> bool:
+ """bool SDL_SetBooleanProperty(SDL_PropertiesID props, const char *name, bool value)"""
+
+ @staticmethod
+ def SDL_SetClipboardData(
+ callback: Any, cleanup: Any, userdata: Any, mime_types: Any, num_mime_types: int, /
+ ) -> bool:
+ """bool SDL_SetClipboardData(SDL_ClipboardDataCallback callback, SDL_ClipboardCleanupCallback cleanup, void *userdata, const char **mime_types, size_t num_mime_types)"""
+
+ @staticmethod
+ def SDL_SetClipboardText(text: Any, /) -> bool:
+ """bool SDL_SetClipboardText(const char *text)"""
+
+ @staticmethod
+ def SDL_SetCurrentThreadPriority(priority: Any, /) -> bool:
+ """bool SDL_SetCurrentThreadPriority(SDL_ThreadPriority priority)"""
+
+ @staticmethod
+ def SDL_SetCursor(cursor: Any, /) -> bool:
+ """bool SDL_SetCursor(SDL_Cursor *cursor)"""
+
+ @staticmethod
+ def SDL_SetEnvironmentVariable(env: Any, name: Any, value: Any, overwrite: bool, /) -> bool:
+ """bool SDL_SetEnvironmentVariable(SDL_Environment *env, const char *name, const char *value, bool overwrite)"""
+
+ @staticmethod
+ def SDL_SetError(fmt: Any, /, *__args: Any) -> bool:
+ """bool SDL_SetError(const char *fmt, ...)"""
+
+ @staticmethod
+ def SDL_SetEventEnabled(type: Any, enabled: bool, /) -> None:
+ """void SDL_SetEventEnabled(Uint32 type, bool enabled)"""
+
+ @staticmethod
+ def SDL_SetEventFilter(filter: Any, userdata: Any, /) -> None:
+ """void SDL_SetEventFilter(SDL_EventFilter filter, void *userdata)"""
+
+ @staticmethod
+ def SDL_SetFloatProperty(props: Any, name: Any, value: float, /) -> bool:
+ """bool SDL_SetFloatProperty(SDL_PropertiesID props, const char *name, float value)"""
+
+ @staticmethod
+ def SDL_SetGPUAllowedFramesInFlight(device: Any, allowed_frames_in_flight: Any, /) -> bool:
+ """bool SDL_SetGPUAllowedFramesInFlight(SDL_GPUDevice *device, Uint32 allowed_frames_in_flight)"""
+
+ @staticmethod
+ def SDL_SetGPUBlendConstants(render_pass: Any, blend_constants: Any, /) -> None:
+ """void SDL_SetGPUBlendConstants(SDL_GPURenderPass *render_pass, SDL_FColor blend_constants)"""
+
+ @staticmethod
+ def SDL_SetGPUBufferName(device: Any, buffer: Any, text: Any, /) -> None:
+ """void SDL_SetGPUBufferName(SDL_GPUDevice *device, SDL_GPUBuffer *buffer, const char *text)"""
+
+ @staticmethod
+ def SDL_SetGPUScissor(render_pass: Any, scissor: Any, /) -> None:
+ """void SDL_SetGPUScissor(SDL_GPURenderPass *render_pass, const SDL_Rect *scissor)"""
+
+ @staticmethod
+ def SDL_SetGPUStencilReference(render_pass: Any, reference: Any, /) -> None:
+ """void SDL_SetGPUStencilReference(SDL_GPURenderPass *render_pass, Uint8 reference)"""
+
+ @staticmethod
+ def SDL_SetGPUSwapchainParameters(
+ device: Any, window: Any, swapchain_composition: Any, present_mode: Any, /
+ ) -> bool:
+ """bool SDL_SetGPUSwapchainParameters(SDL_GPUDevice *device, SDL_Window *window, SDL_GPUSwapchainComposition swapchain_composition, SDL_GPUPresentMode present_mode)"""
+
+ @staticmethod
+ def SDL_SetGPUTextureName(device: Any, texture: Any, text: Any, /) -> None:
+ """void SDL_SetGPUTextureName(SDL_GPUDevice *device, SDL_GPUTexture *texture, const char *text)"""
+
+ @staticmethod
+ def SDL_SetGPUViewport(render_pass: Any, viewport: Any, /) -> None:
+ """void SDL_SetGPUViewport(SDL_GPURenderPass *render_pass, const SDL_GPUViewport *viewport)"""
+
+ @staticmethod
+ def SDL_SetGamepadEventsEnabled(enabled: bool, /) -> None:
+ """void SDL_SetGamepadEventsEnabled(bool enabled)"""
+
+ @staticmethod
+ def SDL_SetGamepadLED(gamepad: Any, red: Any, green: Any, blue: Any, /) -> bool:
+ """bool SDL_SetGamepadLED(SDL_Gamepad *gamepad, Uint8 red, Uint8 green, Uint8 blue)"""
+
+ @staticmethod
+ def SDL_SetGamepadMapping(instance_id: Any, mapping: Any, /) -> bool:
+ """bool SDL_SetGamepadMapping(SDL_JoystickID instance_id, const char *mapping)"""
+
+ @staticmethod
+ def SDL_SetGamepadPlayerIndex(gamepad: Any, player_index: int, /) -> bool:
+ """bool SDL_SetGamepadPlayerIndex(SDL_Gamepad *gamepad, int player_index)"""
+
+ @staticmethod
+ def SDL_SetGamepadSensorEnabled(gamepad: Any, type: Any, enabled: bool, /) -> bool:
+ """bool SDL_SetGamepadSensorEnabled(SDL_Gamepad *gamepad, SDL_SensorType type, bool enabled)"""
+
+ @staticmethod
+ def SDL_SetHapticAutocenter(haptic: Any, autocenter: int, /) -> bool:
+ """bool SDL_SetHapticAutocenter(SDL_Haptic *haptic, int autocenter)"""
+
+ @staticmethod
+ def SDL_SetHapticGain(haptic: Any, gain: int, /) -> bool:
+ """bool SDL_SetHapticGain(SDL_Haptic *haptic, int gain)"""
+
+ @staticmethod
+ def SDL_SetHint(name: Any, value: Any, /) -> bool:
+ """bool SDL_SetHint(const char *name, const char *value)"""
+
+ @staticmethod
+ def SDL_SetHintWithPriority(name: Any, value: Any, priority: Any, /) -> bool:
+ """bool SDL_SetHintWithPriority(const char *name, const char *value, SDL_HintPriority priority)"""
+
+ @staticmethod
+ def SDL_SetInitialized(state: Any, initialized: bool, /) -> None:
+ """void SDL_SetInitialized(SDL_InitState *state, bool initialized)"""
+
+ @staticmethod
+ def SDL_SetJoystickEventsEnabled(enabled: bool, /) -> None:
+ """void SDL_SetJoystickEventsEnabled(bool enabled)"""
+
+ @staticmethod
+ def SDL_SetJoystickLED(joystick: Any, red: Any, green: Any, blue: Any, /) -> bool:
+ """bool SDL_SetJoystickLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)"""
+
+ @staticmethod
+ def SDL_SetJoystickPlayerIndex(joystick: Any, player_index: int, /) -> bool:
+ """bool SDL_SetJoystickPlayerIndex(SDL_Joystick *joystick, int player_index)"""
+
+ @staticmethod
+ def SDL_SetJoystickVirtualAxis(joystick: Any, axis: int, value: Any, /) -> bool:
+ """bool SDL_SetJoystickVirtualAxis(SDL_Joystick *joystick, int axis, Sint16 value)"""
+
+ @staticmethod
+ def SDL_SetJoystickVirtualBall(joystick: Any, ball: int, xrel: Any, yrel: Any, /) -> bool:
+ """bool SDL_SetJoystickVirtualBall(SDL_Joystick *joystick, int ball, Sint16 xrel, Sint16 yrel)"""
+
+ @staticmethod
+ def SDL_SetJoystickVirtualButton(joystick: Any, button: int, down: bool, /) -> bool:
+ """bool SDL_SetJoystickVirtualButton(SDL_Joystick *joystick, int button, bool down)"""
+
+ @staticmethod
+ def SDL_SetJoystickVirtualHat(joystick: Any, hat: int, value: Any, /) -> bool:
+ """bool SDL_SetJoystickVirtualHat(SDL_Joystick *joystick, int hat, Uint8 value)"""
+
+ @staticmethod
+ def SDL_SetJoystickVirtualTouchpad(
+ joystick: Any, touchpad: int, finger: int, down: bool, x: float, y: float, pressure: float, /
+ ) -> bool:
+ """bool SDL_SetJoystickVirtualTouchpad(SDL_Joystick *joystick, int touchpad, int finger, bool down, float x, float y, float pressure)"""
+
+ @staticmethod
+ def SDL_SetLogOutputFunction(callback: Any, userdata: Any, /) -> None:
+ """void SDL_SetLogOutputFunction(SDL_LogOutputFunction callback, void *userdata)"""
+
+ @staticmethod
+ def SDL_SetLogPriorities(priority: Any, /) -> None:
+ """void SDL_SetLogPriorities(SDL_LogPriority priority)"""
+
+ @staticmethod
+ def SDL_SetLogPriority(category: int, priority: Any, /) -> None:
+ """void SDL_SetLogPriority(int category, SDL_LogPriority priority)"""
+
+ @staticmethod
+ def SDL_SetLogPriorityPrefix(priority: Any, prefix: Any, /) -> bool:
+ """bool SDL_SetLogPriorityPrefix(SDL_LogPriority priority, const char *prefix)"""
+
+ @staticmethod
+ def SDL_SetMemoryFunctions(malloc_func: Any, calloc_func: Any, realloc_func: Any, free_func: Any, /) -> bool:
+ """bool SDL_SetMemoryFunctions(SDL_malloc_func malloc_func, SDL_calloc_func calloc_func, SDL_realloc_func realloc_func, SDL_free_func free_func)"""
+
+ @staticmethod
+ def SDL_SetModState(modstate: Any, /) -> None:
+ """void SDL_SetModState(SDL_Keymod modstate)"""
+
+ @staticmethod
+ def SDL_SetNumberProperty(props: Any, name: Any, value: Any, /) -> bool:
+ """bool SDL_SetNumberProperty(SDL_PropertiesID props, const char *name, Sint64 value)"""
+
+ @staticmethod
+ def SDL_SetPaletteColors(palette: Any, colors: Any, firstcolor: int, ncolors: int, /) -> bool:
+ """bool SDL_SetPaletteColors(SDL_Palette *palette, const SDL_Color *colors, int firstcolor, int ncolors)"""
+
+ @staticmethod
+ def SDL_SetPointerProperty(props: Any, name: Any, value: Any, /) -> bool:
+ """bool SDL_SetPointerProperty(SDL_PropertiesID props, const char *name, void *value)"""
+
+ @staticmethod
+ def SDL_SetPointerPropertyWithCleanup(props: Any, name: Any, value: Any, cleanup: Any, userdata: Any, /) -> bool:
+ """bool SDL_SetPointerPropertyWithCleanup(SDL_PropertiesID props, const char *name, void *value, SDL_CleanupPropertyCallback cleanup, void *userdata)"""
+
+ @staticmethod
+ def SDL_SetPrimarySelectionText(text: Any, /) -> bool:
+ """bool SDL_SetPrimarySelectionText(const char *text)"""
+
+ @staticmethod
+ def SDL_SetRenderClipRect(renderer: Any, rect: Any, /) -> bool:
+ """bool SDL_SetRenderClipRect(SDL_Renderer *renderer, const SDL_Rect *rect)"""
+
+ @staticmethod
+ def SDL_SetRenderColorScale(renderer: Any, scale: float, /) -> bool:
+ """bool SDL_SetRenderColorScale(SDL_Renderer *renderer, float scale)"""
+
+ @staticmethod
+ def SDL_SetRenderDrawBlendMode(renderer: Any, blendMode: Any, /) -> bool:
+ """bool SDL_SetRenderDrawBlendMode(SDL_Renderer *renderer, SDL_BlendMode blendMode)"""
+
+ @staticmethod
+ def SDL_SetRenderDrawColor(renderer: Any, r: Any, g: Any, b: Any, a: Any, /) -> bool:
+ """bool SDL_SetRenderDrawColor(SDL_Renderer *renderer, Uint8 r, Uint8 g, Uint8 b, Uint8 a)"""
+
+ @staticmethod
+ def SDL_SetRenderDrawColorFloat(renderer: Any, r: float, g: float, b: float, a: float, /) -> bool:
+ """bool SDL_SetRenderDrawColorFloat(SDL_Renderer *renderer, float r, float g, float b, float a)"""
+
+ @staticmethod
+ def SDL_SetRenderLogicalPresentation(renderer: Any, w: int, h: int, mode: Any, /) -> bool:
+ """bool SDL_SetRenderLogicalPresentation(SDL_Renderer *renderer, int w, int h, SDL_RendererLogicalPresentation mode)"""
+
+ @staticmethod
+ def SDL_SetRenderScale(renderer: Any, scaleX: float, scaleY: float, /) -> bool:
+ """bool SDL_SetRenderScale(SDL_Renderer *renderer, float scaleX, float scaleY)"""
+
+ @staticmethod
+ def SDL_SetRenderTarget(renderer: Any, texture: Any, /) -> bool:
+ """bool SDL_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)"""
+
+ @staticmethod
+ def SDL_SetRenderVSync(renderer: Any, vsync: int, /) -> bool:
+ """bool SDL_SetRenderVSync(SDL_Renderer *renderer, int vsync)"""
+
+ @staticmethod
+ def SDL_SetRenderViewport(renderer: Any, rect: Any, /) -> bool:
+ """bool SDL_SetRenderViewport(SDL_Renderer *renderer, const SDL_Rect *rect)"""
+
+ @staticmethod
+ def SDL_SetScancodeName(scancode: Any, name: Any, /) -> bool:
+ """bool SDL_SetScancodeName(SDL_Scancode scancode, const char *name)"""
+
+ @staticmethod
+ def SDL_SetStringProperty(props: Any, name: Any, value: Any, /) -> bool:
+ """bool SDL_SetStringProperty(SDL_PropertiesID props, const char *name, const char *value)"""
+
+ @staticmethod
+ def SDL_SetSurfaceAlphaMod(surface: Any, alpha: Any, /) -> bool:
+ """bool SDL_SetSurfaceAlphaMod(SDL_Surface *surface, Uint8 alpha)"""
+
+ @staticmethod
+ def SDL_SetSurfaceBlendMode(surface: Any, blendMode: Any, /) -> bool:
+ """bool SDL_SetSurfaceBlendMode(SDL_Surface *surface, SDL_BlendMode blendMode)"""
+
+ @staticmethod
+ def SDL_SetSurfaceClipRect(surface: Any, rect: Any, /) -> bool:
+ """bool SDL_SetSurfaceClipRect(SDL_Surface *surface, const SDL_Rect *rect)"""
+
+ @staticmethod
+ def SDL_SetSurfaceColorKey(surface: Any, enabled: bool, key: Any, /) -> bool:
+ """bool SDL_SetSurfaceColorKey(SDL_Surface *surface, bool enabled, Uint32 key)"""
+
+ @staticmethod
+ def SDL_SetSurfaceColorMod(surface: Any, r: Any, g: Any, b: Any, /) -> bool:
+ """bool SDL_SetSurfaceColorMod(SDL_Surface *surface, Uint8 r, Uint8 g, Uint8 b)"""
+
+ @staticmethod
+ def SDL_SetSurfaceColorspace(surface: Any, colorspace: Any, /) -> bool:
+ """bool SDL_SetSurfaceColorspace(SDL_Surface *surface, SDL_Colorspace colorspace)"""
+
+ @staticmethod
+ def SDL_SetSurfacePalette(surface: Any, palette: Any, /) -> bool:
+ """bool SDL_SetSurfacePalette(SDL_Surface *surface, SDL_Palette *palette)"""
+
+ @staticmethod
+ def SDL_SetSurfaceRLE(surface: Any, enabled: bool, /) -> bool:
+ """bool SDL_SetSurfaceRLE(SDL_Surface *surface, bool enabled)"""
+
+ @staticmethod
+ def SDL_SetTLS(id: Any, value: Any, destructor: Any, /) -> bool:
+ """bool SDL_SetTLS(SDL_TLSID *id, const void *value, SDL_TLSDestructorCallback destructor)"""
+
+ @staticmethod
+ def SDL_SetTextInputArea(window: Any, rect: Any, cursor: int, /) -> bool:
+ """bool SDL_SetTextInputArea(SDL_Window *window, const SDL_Rect *rect, int cursor)"""
+
+ @staticmethod
+ def SDL_SetTextureAlphaMod(texture: Any, alpha: Any, /) -> bool:
+ """bool SDL_SetTextureAlphaMod(SDL_Texture *texture, Uint8 alpha)"""
+
+ @staticmethod
+ def SDL_SetTextureAlphaModFloat(texture: Any, alpha: float, /) -> bool:
+ """bool SDL_SetTextureAlphaModFloat(SDL_Texture *texture, float alpha)"""
+
+ @staticmethod
+ def SDL_SetTextureBlendMode(texture: Any, blendMode: Any, /) -> bool:
+ """bool SDL_SetTextureBlendMode(SDL_Texture *texture, SDL_BlendMode blendMode)"""
+
+ @staticmethod
+ def SDL_SetTextureColorMod(texture: Any, r: Any, g: Any, b: Any, /) -> bool:
+ """bool SDL_SetTextureColorMod(SDL_Texture *texture, Uint8 r, Uint8 g, Uint8 b)"""
+
+ @staticmethod
+ def SDL_SetTextureColorModFloat(texture: Any, r: float, g: float, b: float, /) -> bool:
+ """bool SDL_SetTextureColorModFloat(SDL_Texture *texture, float r, float g, float b)"""
+
+ @staticmethod
+ def SDL_SetTextureScaleMode(texture: Any, scaleMode: Any, /) -> bool:
+ """bool SDL_SetTextureScaleMode(SDL_Texture *texture, SDL_ScaleMode scaleMode)"""
+
+ @staticmethod
+ def SDL_SetTrayEntryCallback(entry: Any, callback: Any, userdata: Any, /) -> None:
+ """void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata)"""
+
+ @staticmethod
+ def SDL_SetTrayEntryChecked(entry: Any, checked: bool, /) -> None:
+ """void SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, bool checked)"""
+
+ @staticmethod
+ def SDL_SetTrayEntryEnabled(entry: Any, enabled: bool, /) -> None:
+ """void SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, bool enabled)"""
+
+ @staticmethod
+ def SDL_SetTrayEntryLabel(entry: Any, label: Any, /) -> None:
+ """void SDL_SetTrayEntryLabel(SDL_TrayEntry *entry, const char *label)"""
+
+ @staticmethod
+ def SDL_SetTrayIcon(tray: Any, icon: Any, /) -> None:
+ """void SDL_SetTrayIcon(SDL_Tray *tray, SDL_Surface *icon)"""
+
+ @staticmethod
+ def SDL_SetTrayTooltip(tray: Any, tooltip: Any, /) -> None:
+ """void SDL_SetTrayTooltip(SDL_Tray *tray, const char *tooltip)"""
+
+ @staticmethod
+ def SDL_SetWindowAlwaysOnTop(window: Any, on_top: bool, /) -> bool:
+ """bool SDL_SetWindowAlwaysOnTop(SDL_Window *window, bool on_top)"""
+
+ @staticmethod
+ def SDL_SetWindowAspectRatio(window: Any, min_aspect: float, max_aspect: float, /) -> bool:
+ """bool SDL_SetWindowAspectRatio(SDL_Window *window, float min_aspect, float max_aspect)"""
+
+ @staticmethod
+ def SDL_SetWindowBordered(window: Any, bordered: bool, /) -> bool:
+ """bool SDL_SetWindowBordered(SDL_Window *window, bool bordered)"""
+
+ @staticmethod
+ def SDL_SetWindowFocusable(window: Any, focusable: bool, /) -> bool:
+ """bool SDL_SetWindowFocusable(SDL_Window *window, bool focusable)"""
+
+ @staticmethod
+ def SDL_SetWindowFullscreen(window: Any, fullscreen: bool, /) -> bool:
+ """bool SDL_SetWindowFullscreen(SDL_Window *window, bool fullscreen)"""
+
+ @staticmethod
+ def SDL_SetWindowFullscreenMode(window: Any, mode: Any, /) -> bool:
+ """bool SDL_SetWindowFullscreenMode(SDL_Window *window, const SDL_DisplayMode *mode)"""
+
+ @staticmethod
+ def SDL_SetWindowHitTest(window: Any, callback: Any, callback_data: Any, /) -> bool:
+ """bool SDL_SetWindowHitTest(SDL_Window *window, SDL_HitTest callback, void *callback_data)"""
+
+ @staticmethod
+ def SDL_SetWindowIcon(window: Any, icon: Any, /) -> bool:
+ """bool SDL_SetWindowIcon(SDL_Window *window, SDL_Surface *icon)"""
+
+ @staticmethod
+ def SDL_SetWindowKeyboardGrab(window: Any, grabbed: bool, /) -> bool:
+ """bool SDL_SetWindowKeyboardGrab(SDL_Window *window, bool grabbed)"""
+
+ @staticmethod
+ def SDL_SetWindowMaximumSize(window: Any, max_w: int, max_h: int, /) -> bool:
+ """bool SDL_SetWindowMaximumSize(SDL_Window *window, int max_w, int max_h)"""
+
+ @staticmethod
+ def SDL_SetWindowMinimumSize(window: Any, min_w: int, min_h: int, /) -> bool:
+ """bool SDL_SetWindowMinimumSize(SDL_Window *window, int min_w, int min_h)"""
+
+ @staticmethod
+ def SDL_SetWindowModal(window: Any, modal: bool, /) -> bool:
+ """bool SDL_SetWindowModal(SDL_Window *window, bool modal)"""
+
+ @staticmethod
+ def SDL_SetWindowMouseGrab(window: Any, grabbed: bool, /) -> bool:
+ """bool SDL_SetWindowMouseGrab(SDL_Window *window, bool grabbed)"""
+
+ @staticmethod
+ def SDL_SetWindowMouseRect(window: Any, rect: Any, /) -> bool:
+ """bool SDL_SetWindowMouseRect(SDL_Window *window, const SDL_Rect *rect)"""
+
+ @staticmethod
+ def SDL_SetWindowOpacity(window: Any, opacity: float, /) -> bool:
+ """bool SDL_SetWindowOpacity(SDL_Window *window, float opacity)"""
+
+ @staticmethod
+ def SDL_SetWindowParent(window: Any, parent: Any, /) -> bool:
+ """bool SDL_SetWindowParent(SDL_Window *window, SDL_Window *parent)"""
+
+ @staticmethod
+ def SDL_SetWindowPosition(window: Any, x: int, y: int, /) -> bool:
+ """bool SDL_SetWindowPosition(SDL_Window *window, int x, int y)"""
+
+ @staticmethod
+ def SDL_SetWindowRelativeMouseMode(window: Any, enabled: bool, /) -> bool:
+ """bool SDL_SetWindowRelativeMouseMode(SDL_Window *window, bool enabled)"""
+
+ @staticmethod
+ def SDL_SetWindowResizable(window: Any, resizable: bool, /) -> bool:
+ """bool SDL_SetWindowResizable(SDL_Window *window, bool resizable)"""
+
+ @staticmethod
+ def SDL_SetWindowShape(window: Any, shape: Any, /) -> bool:
+ """bool SDL_SetWindowShape(SDL_Window *window, SDL_Surface *shape)"""
+
+ @staticmethod
+ def SDL_SetWindowSize(window: Any, w: int, h: int, /) -> bool:
+ """bool SDL_SetWindowSize(SDL_Window *window, int w, int h)"""
+
+ @staticmethod
+ def SDL_SetWindowSurfaceVSync(window: Any, vsync: int, /) -> bool:
+ """bool SDL_SetWindowSurfaceVSync(SDL_Window *window, int vsync)"""
+
+ @staticmethod
+ def SDL_SetWindowTitle(window: Any, title: Any, /) -> bool:
+ """bool SDL_SetWindowTitle(SDL_Window *window, const char *title)"""
+
+ @staticmethod
+ def SDL_SetX11EventHook(callback: Any, userdata: Any, /) -> None:
+ """void SDL_SetX11EventHook(SDL_X11EventHook callback, void *userdata)"""
+
+ @staticmethod
+ def SDL_ShouldInit(state: Any, /) -> bool:
+ """bool SDL_ShouldInit(SDL_InitState *state)"""
+
+ @staticmethod
+ def SDL_ShouldQuit(state: Any, /) -> bool:
+ """bool SDL_ShouldQuit(SDL_InitState *state)"""
+
+ @staticmethod
+ def SDL_ShowCursor() -> bool:
+ """bool SDL_ShowCursor(void)"""
+
+ @staticmethod
+ def SDL_ShowFileDialogWithProperties(type: Any, callback: Any, userdata: Any, props: Any, /) -> None:
+ """void SDL_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props)"""
+
+ @staticmethod
+ def SDL_ShowMessageBox(messageboxdata: Any, buttonid: Any, /) -> bool:
+ """bool SDL_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid)"""
+
+ @staticmethod
+ def SDL_ShowOpenFileDialog(
+ callback: Any,
+ userdata: Any,
+ window: Any,
+ filters: Any,
+ nfilters: int,
+ default_location: Any,
+ allow_many: bool,
+ /,
+ ) -> None:
+ """void SDL_ShowOpenFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const SDL_DialogFileFilter *filters, int nfilters, const char *default_location, bool allow_many)"""
+
+ @staticmethod
+ def SDL_ShowOpenFolderDialog(
+ callback: Any, userdata: Any, window: Any, default_location: Any, allow_many: bool, /
+ ) -> None:
+ """void SDL_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const char *default_location, bool allow_many)"""
+
+ @staticmethod
+ def SDL_ShowSaveFileDialog(
+ callback: Any, userdata: Any, window: Any, filters: Any, nfilters: int, default_location: Any, /
+ ) -> None:
+ """void SDL_ShowSaveFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const SDL_DialogFileFilter *filters, int nfilters, const char *default_location)"""
+
+ @staticmethod
+ def SDL_ShowSimpleMessageBox(flags: Any, title: Any, message: Any, window: Any, /) -> bool:
+ """bool SDL_ShowSimpleMessageBox(SDL_MessageBoxFlags flags, const char *title, const char *message, SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_ShowWindow(window: Any, /) -> bool:
+ """bool SDL_ShowWindow(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_ShowWindowSystemMenu(window: Any, x: int, y: int, /) -> bool:
+ """bool SDL_ShowWindowSystemMenu(SDL_Window *window, int x, int y)"""
+
+ @staticmethod
+ def SDL_SignalAsyncIOQueue(queue: Any, /) -> None:
+ """void SDL_SignalAsyncIOQueue(SDL_AsyncIOQueue *queue)"""
+
+ @staticmethod
+ def SDL_SignalCondition(cond: Any, /) -> None:
+ """void SDL_SignalCondition(SDL_Condition *cond)"""
+
+ @staticmethod
+ def SDL_SignalSemaphore(sem: Any, /) -> None:
+ """void SDL_SignalSemaphore(SDL_Semaphore *sem)"""
+
+ @staticmethod
+ def SDL_StartTextInput(window: Any, /) -> bool:
+ """bool SDL_StartTextInput(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_StartTextInputWithProperties(window: Any, props: Any, /) -> bool:
+ """bool SDL_StartTextInputWithProperties(SDL_Window *window, SDL_PropertiesID props)"""
+
+ @staticmethod
+ def SDL_StepBackUTF8(start: Any, pstr: Any, /) -> Any:
+ """Uint32 SDL_StepBackUTF8(const char *start, const char **pstr)"""
+
+ @staticmethod
+ def SDL_StepUTF8(pstr: Any, pslen: Any, /) -> Any:
+ """Uint32 SDL_StepUTF8(const char **pstr, size_t *pslen)"""
+
+ @staticmethod
+ def SDL_StopHapticEffect(haptic: Any, effect: int, /) -> bool:
+ """bool SDL_StopHapticEffect(SDL_Haptic *haptic, int effect)"""
+
+ @staticmethod
+ def SDL_StopHapticEffects(haptic: Any, /) -> bool:
+ """bool SDL_StopHapticEffects(SDL_Haptic *haptic)"""
+
+ @staticmethod
+ def SDL_StopHapticRumble(haptic: Any, /) -> bool:
+ """bool SDL_StopHapticRumble(SDL_Haptic *haptic)"""
+
+ @staticmethod
+ def SDL_StopTextInput(window: Any, /) -> bool:
+ """bool SDL_StopTextInput(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_StorageReady(storage: Any, /) -> bool:
+ """bool SDL_StorageReady(SDL_Storage *storage)"""
+
+ @staticmethod
+ def SDL_StretchSurface(src: Any, srcrect: Any, dst: Any, dstrect: Any, scaleMode: Any, /) -> bool:
+ """bool SDL_StretchSurface(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface *dst, const SDL_Rect *dstrect, SDL_ScaleMode scaleMode)"""
+
+ @staticmethod
+ def SDL_StringToGUID(pchGUID: Any, /) -> Any:
+ """SDL_GUID SDL_StringToGUID(const char *pchGUID)"""
+
+ @staticmethod
+ def SDL_SubmitGPUCommandBuffer(command_buffer: Any, /) -> bool:
+ """bool SDL_SubmitGPUCommandBuffer(SDL_GPUCommandBuffer *command_buffer)"""
+
+ @staticmethod
+ def SDL_SubmitGPUCommandBufferAndAcquireFence(command_buffer: Any, /) -> Any:
+ """SDL_GPUFence *SDL_SubmitGPUCommandBufferAndAcquireFence(SDL_GPUCommandBuffer *command_buffer)"""
+
+ @staticmethod
+ def SDL_SurfaceHasAlternateImages(surface: Any, /) -> bool:
+ """bool SDL_SurfaceHasAlternateImages(SDL_Surface *surface)"""
+
+ @staticmethod
+ def SDL_SurfaceHasColorKey(surface: Any, /) -> bool:
+ """bool SDL_SurfaceHasColorKey(SDL_Surface *surface)"""
+
+ @staticmethod
+ def SDL_SurfaceHasRLE(surface: Any, /) -> bool:
+ """bool SDL_SurfaceHasRLE(SDL_Surface *surface)"""
+
+ @staticmethod
+ def SDL_SyncWindow(window: Any, /) -> bool:
+ """bool SDL_SyncWindow(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_TellIO(context: Any, /) -> Any:
+ """Sint64 SDL_TellIO(SDL_IOStream *context)"""
+
+ @staticmethod
+ def SDL_TextInputActive(window: Any, /) -> bool:
+ """bool SDL_TextInputActive(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_TimeFromWindows(dwLowDateTime: Any, dwHighDateTime: Any, /) -> Any:
+ """SDL_Time SDL_TimeFromWindows(Uint32 dwLowDateTime, Uint32 dwHighDateTime)"""
+
+ @staticmethod
+ def SDL_TimeToDateTime(ticks: Any, dt: Any, localTime: bool, /) -> bool:
+ """bool SDL_TimeToDateTime(SDL_Time ticks, SDL_DateTime *dt, bool localTime)"""
+
+ @staticmethod
+ def SDL_TimeToWindows(ticks: Any, dwLowDateTime: Any, dwHighDateTime: Any, /) -> None:
+ """void SDL_TimeToWindows(SDL_Time ticks, Uint32 *dwLowDateTime, Uint32 *dwHighDateTime)"""
+
+ @staticmethod
+ def SDL_TryLockMutex(mutex: Any, /) -> bool:
+ """bool SDL_TryLockMutex(SDL_Mutex *mutex)"""
+
+ @staticmethod
+ def SDL_TryLockRWLockForReading(rwlock: Any, /) -> bool:
+ """bool SDL_TryLockRWLockForReading(SDL_RWLock *rwlock)"""
+
+ @staticmethod
+ def SDL_TryLockRWLockForWriting(rwlock: Any, /) -> bool:
+ """bool SDL_TryLockRWLockForWriting(SDL_RWLock *rwlock)"""
+
+ @staticmethod
+ def SDL_TryLockSpinlock(lock: Any, /) -> bool:
+ """bool SDL_TryLockSpinlock(SDL_SpinLock *lock)"""
+
+ @staticmethod
+ def SDL_TryWaitSemaphore(sem: Any, /) -> bool:
+ """bool SDL_TryWaitSemaphore(SDL_Semaphore *sem)"""
+
+ @staticmethod
+ def SDL_UCS4ToUTF8(codepoint: Any, dst: Any, /) -> Any:
+ """char *SDL_UCS4ToUTF8(Uint32 codepoint, char *dst)"""
+
+ @staticmethod
+ def SDL_UnbindAudioStream(stream: Any, /) -> None:
+ """void SDL_UnbindAudioStream(SDL_AudioStream *stream)"""
+
+ @staticmethod
+ def SDL_UnbindAudioStreams(streams: Any, num_streams: int, /) -> None:
+ """void SDL_UnbindAudioStreams(SDL_AudioStream * const *streams, int num_streams)"""
+
+ @staticmethod
+ def SDL_UnloadObject(handle: Any, /) -> None:
+ """void SDL_UnloadObject(SDL_SharedObject *handle)"""
+
+ @staticmethod
+ def SDL_UnlockAudioStream(stream: Any, /) -> bool:
+ """bool SDL_UnlockAudioStream(SDL_AudioStream *stream)"""
+
+ @staticmethod
+ def SDL_UnlockJoysticks() -> None:
+ """void SDL_UnlockJoysticks(void)"""
+
+ @staticmethod
+ def SDL_UnlockMutex(mutex: Any, /) -> None:
+ """void SDL_UnlockMutex(SDL_Mutex *mutex)"""
+
+ @staticmethod
+ def SDL_UnlockProperties(props: Any, /) -> None:
+ """void SDL_UnlockProperties(SDL_PropertiesID props)"""
+
+ @staticmethod
+ def SDL_UnlockRWLock(rwlock: Any, /) -> None:
+ """void SDL_UnlockRWLock(SDL_RWLock *rwlock)"""
+
+ @staticmethod
+ def SDL_UnlockSpinlock(lock: Any, /) -> None:
+ """void SDL_UnlockSpinlock(SDL_SpinLock *lock)"""
+
+ @staticmethod
+ def SDL_UnlockSurface(surface: Any, /) -> None:
+ """void SDL_UnlockSurface(SDL_Surface *surface)"""
+
+ @staticmethod
+ def SDL_UnlockTexture(texture: Any, /) -> None:
+ """void SDL_UnlockTexture(SDL_Texture *texture)"""
+
+ @staticmethod
+ def SDL_UnmapGPUTransferBuffer(device: Any, transfer_buffer: Any, /) -> None:
+ """void SDL_UnmapGPUTransferBuffer(SDL_GPUDevice *device, SDL_GPUTransferBuffer *transfer_buffer)"""
+
+ @staticmethod
+ def SDL_UnsetEnvironmentVariable(env: Any, name: Any, /) -> bool:
+ """bool SDL_UnsetEnvironmentVariable(SDL_Environment *env, const char *name)"""
+
+ @staticmethod
+ def SDL_UpdateGamepads() -> None:
+ """void SDL_UpdateGamepads(void)"""
+
+ @staticmethod
+ def SDL_UpdateHapticEffect(haptic: Any, effect: int, data: Any, /) -> bool:
+ """bool SDL_UpdateHapticEffect(SDL_Haptic *haptic, int effect, const SDL_HapticEffect *data)"""
+
+ @staticmethod
+ def SDL_UpdateJoysticks() -> None:
+ """void SDL_UpdateJoysticks(void)"""
+
+ @staticmethod
+ def SDL_UpdateNVTexture(texture: Any, rect: Any, Yplane: Any, Ypitch: int, UVplane: Any, UVpitch: int, /) -> bool:
+ """bool SDL_UpdateNVTexture(SDL_Texture *texture, const SDL_Rect *rect, const Uint8 *Yplane, int Ypitch, const Uint8 *UVplane, int UVpitch)"""
+
+ @staticmethod
+ def SDL_UpdateSensors() -> None:
+ """void SDL_UpdateSensors(void)"""
+
+ @staticmethod
+ def SDL_UpdateTexture(texture: Any, rect: Any, pixels: Any, pitch: int, /) -> bool:
+ """bool SDL_UpdateTexture(SDL_Texture *texture, const SDL_Rect *rect, const void *pixels, int pitch)"""
+
+ @staticmethod
+ def SDL_UpdateTrays() -> None:
+ """void SDL_UpdateTrays(void)"""
+
+ @staticmethod
+ def SDL_UpdateWindowSurface(window: Any, /) -> bool:
+ """bool SDL_UpdateWindowSurface(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_UpdateWindowSurfaceRects(window: Any, rects: Any, numrects: int, /) -> bool:
+ """bool SDL_UpdateWindowSurfaceRects(SDL_Window *window, const SDL_Rect *rects, int numrects)"""
+
+ @staticmethod
+ def SDL_UpdateYUVTexture(
+ texture: Any, rect: Any, Yplane: Any, Ypitch: int, Uplane: Any, Upitch: int, Vplane: Any, Vpitch: int, /
+ ) -> bool:
+ """bool SDL_UpdateYUVTexture(SDL_Texture *texture, const SDL_Rect *rect, const Uint8 *Yplane, int Ypitch, const Uint8 *Uplane, int Upitch, const Uint8 *Vplane, int Vpitch)"""
+
+ @staticmethod
+ def SDL_UploadToGPUBuffer(copy_pass: Any, source: Any, destination: Any, cycle: bool, /) -> None:
+ """void SDL_UploadToGPUBuffer(SDL_GPUCopyPass *copy_pass, const SDL_GPUTransferBufferLocation *source, const SDL_GPUBufferRegion *destination, bool cycle)"""
+
+ @staticmethod
+ def SDL_UploadToGPUTexture(copy_pass: Any, source: Any, destination: Any, cycle: bool, /) -> None:
+ """void SDL_UploadToGPUTexture(SDL_GPUCopyPass *copy_pass, const SDL_GPUTextureTransferInfo *source, const SDL_GPUTextureRegion *destination, bool cycle)"""
+
+ @staticmethod
+ def SDL_WaitAndAcquireGPUSwapchainTexture(
+ command_buffer: Any,
+ window: Any,
+ swapchain_texture: Any,
+ swapchain_texture_width: Any,
+ swapchain_texture_height: Any,
+ /,
+ ) -> bool:
+ """bool SDL_WaitAndAcquireGPUSwapchainTexture(SDL_GPUCommandBuffer *command_buffer, SDL_Window *window, SDL_GPUTexture **swapchain_texture, Uint32 *swapchain_texture_width, Uint32 *swapchain_texture_height)"""
+
+ @staticmethod
+ def SDL_WaitAsyncIOResult(queue: Any, outcome: Any, timeoutMS: Any, /) -> bool:
+ """bool SDL_WaitAsyncIOResult(SDL_AsyncIOQueue *queue, SDL_AsyncIOOutcome *outcome, Sint32 timeoutMS)"""
+
+ @staticmethod
+ def SDL_WaitCondition(cond: Any, mutex: Any, /) -> None:
+ """void SDL_WaitCondition(SDL_Condition *cond, SDL_Mutex *mutex)"""
+
+ @staticmethod
+ def SDL_WaitConditionTimeout(cond: Any, mutex: Any, timeoutMS: Any, /) -> bool:
+ """bool SDL_WaitConditionTimeout(SDL_Condition *cond, SDL_Mutex *mutex, Sint32 timeoutMS)"""
+
+ @staticmethod
+ def SDL_WaitEvent(event: Any, /) -> bool:
+ """bool SDL_WaitEvent(SDL_Event *event)"""
+
+ @staticmethod
+ def SDL_WaitEventTimeout(event: Any, timeoutMS: Any, /) -> bool:
+ """bool SDL_WaitEventTimeout(SDL_Event *event, Sint32 timeoutMS)"""
+
+ @staticmethod
+ def SDL_WaitForGPUFences(device: Any, wait_all: bool, fences: Any, num_fences: Any, /) -> bool:
+ """bool SDL_WaitForGPUFences(SDL_GPUDevice *device, bool wait_all, SDL_GPUFence * const *fences, Uint32 num_fences)"""
+
+ @staticmethod
+ def SDL_WaitForGPUIdle(device: Any, /) -> bool:
+ """bool SDL_WaitForGPUIdle(SDL_GPUDevice *device)"""
+
+ @staticmethod
+ def SDL_WaitForGPUSwapchain(device: Any, window: Any, /) -> bool:
+ """bool SDL_WaitForGPUSwapchain(SDL_GPUDevice *device, SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_WaitProcess(process: Any, block: bool, exitcode: Any, /) -> bool:
+ """bool SDL_WaitProcess(SDL_Process *process, bool block, int *exitcode)"""
+
+ @staticmethod
+ def SDL_WaitSemaphore(sem: Any, /) -> None:
+ """void SDL_WaitSemaphore(SDL_Semaphore *sem)"""
+
+ @staticmethod
+ def SDL_WaitSemaphoreTimeout(sem: Any, timeoutMS: Any, /) -> bool:
+ """bool SDL_WaitSemaphoreTimeout(SDL_Semaphore *sem, Sint32 timeoutMS)"""
+
+ @staticmethod
+ def SDL_WaitThread(thread: Any, status: Any, /) -> None:
+ """void SDL_WaitThread(SDL_Thread *thread, int *status)"""
+
+ @staticmethod
+ def SDL_WarpMouseGlobal(x: float, y: float, /) -> bool:
+ """bool SDL_WarpMouseGlobal(float x, float y)"""
+
+ @staticmethod
+ def SDL_WarpMouseInWindow(window: Any, x: float, y: float, /) -> None:
+ """void SDL_WarpMouseInWindow(SDL_Window *window, float x, float y)"""
+
+ @staticmethod
+ def SDL_WasInit(flags: Any, /) -> Any:
+ """SDL_InitFlags SDL_WasInit(SDL_InitFlags flags)"""
+
+ @staticmethod
+ def SDL_WindowHasSurface(window: Any, /) -> bool:
+ """bool SDL_WindowHasSurface(SDL_Window *window)"""
+
+ @staticmethod
+ def SDL_WindowSupportsGPUPresentMode(device: Any, window: Any, present_mode: Any, /) -> bool:
+ """bool SDL_WindowSupportsGPUPresentMode(SDL_GPUDevice *device, SDL_Window *window, SDL_GPUPresentMode present_mode)"""
+
+ @staticmethod
+ def SDL_WindowSupportsGPUSwapchainComposition(device: Any, window: Any, swapchain_composition: Any, /) -> bool:
+ """bool SDL_WindowSupportsGPUSwapchainComposition(SDL_GPUDevice *device, SDL_Window *window, SDL_GPUSwapchainComposition swapchain_composition)"""
+
+ @staticmethod
+ def SDL_WriteAsyncIO(asyncio: Any, ptr: Any, offset: Any, size: Any, queue: Any, userdata: Any, /) -> bool:
+ """bool SDL_WriteAsyncIO(SDL_AsyncIO *asyncio, void *ptr, Uint64 offset, Uint64 size, SDL_AsyncIOQueue *queue, void *userdata)"""
+
+ @staticmethod
+ def SDL_WriteIO(context: Any, ptr: Any, size: int, /) -> int:
+ """size_t SDL_WriteIO(SDL_IOStream *context, const void *ptr, size_t size)"""
+
+ @staticmethod
+ def SDL_WriteS16BE(dst: Any, value: Any, /) -> bool:
+ """bool SDL_WriteS16BE(SDL_IOStream *dst, Sint16 value)"""
+
+ @staticmethod
+ def SDL_WriteS16LE(dst: Any, value: Any, /) -> bool:
+ """bool SDL_WriteS16LE(SDL_IOStream *dst, Sint16 value)"""
+
+ @staticmethod
+ def SDL_WriteS32BE(dst: Any, value: Any, /) -> bool:
+ """bool SDL_WriteS32BE(SDL_IOStream *dst, Sint32 value)"""
+
+ @staticmethod
+ def SDL_WriteS32LE(dst: Any, value: Any, /) -> bool:
+ """bool SDL_WriteS32LE(SDL_IOStream *dst, Sint32 value)"""
+
+ @staticmethod
+ def SDL_WriteS64BE(dst: Any, value: Any, /) -> bool:
+ """bool SDL_WriteS64BE(SDL_IOStream *dst, Sint64 value)"""
+
+ @staticmethod
+ def SDL_WriteS64LE(dst: Any, value: Any, /) -> bool:
+ """bool SDL_WriteS64LE(SDL_IOStream *dst, Sint64 value)"""
+
+ @staticmethod
+ def SDL_WriteS8(dst: Any, value: Any, /) -> bool:
+ """bool SDL_WriteS8(SDL_IOStream *dst, Sint8 value)"""
+
+ @staticmethod
+ def SDL_WriteStorageFile(storage: Any, path: Any, source: Any, length: Any, /) -> bool:
+ """bool SDL_WriteStorageFile(SDL_Storage *storage, const char *path, const void *source, Uint64 length)"""
+
+ @staticmethod
+ def SDL_WriteSurfacePixel(surface: Any, x: int, y: int, r: Any, g: Any, b: Any, a: Any, /) -> bool:
+ """bool SDL_WriteSurfacePixel(SDL_Surface *surface, int x, int y, Uint8 r, Uint8 g, Uint8 b, Uint8 a)"""
+
+ @staticmethod
+ def SDL_WriteSurfacePixelFloat(surface: Any, x: int, y: int, r: float, g: float, b: float, a: float, /) -> bool:
+ """bool SDL_WriteSurfacePixelFloat(SDL_Surface *surface, int x, int y, float r, float g, float b, float a)"""
+
+ @staticmethod
+ def SDL_WriteU16BE(dst: Any, value: Any, /) -> bool:
+ """bool SDL_WriteU16BE(SDL_IOStream *dst, Uint16 value)"""
+
+ @staticmethod
+ def SDL_WriteU16LE(dst: Any, value: Any, /) -> bool:
+ """bool SDL_WriteU16LE(SDL_IOStream *dst, Uint16 value)"""
+
+ @staticmethod
+ def SDL_WriteU32BE(dst: Any, value: Any, /) -> bool:
+ """bool SDL_WriteU32BE(SDL_IOStream *dst, Uint32 value)"""
+
+ @staticmethod
+ def SDL_WriteU32LE(dst: Any, value: Any, /) -> bool:
+ """bool SDL_WriteU32LE(SDL_IOStream *dst, Uint32 value)"""
+
+ @staticmethod
+ def SDL_WriteU64BE(dst: Any, value: Any, /) -> bool:
+ """bool SDL_WriteU64BE(SDL_IOStream *dst, Uint64 value)"""
+
+ @staticmethod
+ def SDL_WriteU64LE(dst: Any, value: Any, /) -> bool:
+ """bool SDL_WriteU64LE(SDL_IOStream *dst, Uint64 value)"""
+
+ @staticmethod
+ def SDL_WriteU8(dst: Any, value: Any, /) -> bool:
+ """bool SDL_WriteU8(SDL_IOStream *dst, Uint8 value)"""
+
+ @staticmethod
+ def SDL_abs(x: int, /) -> int:
+ """int SDL_abs(int x)"""
+
+ @staticmethod
+ def SDL_acos(x: float, /) -> float:
+ """double SDL_acos(double x)"""
+
+ @staticmethod
+ def SDL_acosf(x: float, /) -> float:
+ """float SDL_acosf(float x)"""
+
+ @staticmethod
+ def SDL_aligned_alloc(alignment: int, size: int, /) -> Any:
+ """void *SDL_aligned_alloc(size_t alignment, size_t size)"""
+
+ @staticmethod
+ def SDL_aligned_free(mem: Any, /) -> None:
+ """void SDL_aligned_free(void *mem)"""
+
+ @staticmethod
+ def SDL_asin(x: float, /) -> float:
+ """double SDL_asin(double x)"""
+
+ @staticmethod
+ def SDL_asinf(x: float, /) -> float:
+ """float SDL_asinf(float x)"""
+
+ @staticmethod
+ def SDL_asprintf(strp: Any, fmt: Any, /, *__args: Any) -> int:
+ """int SDL_asprintf(char **strp, const char *fmt, ...)"""
+
+ @staticmethod
+ def SDL_atan(x: float, /) -> float:
+ """double SDL_atan(double x)"""
+
+ @staticmethod
+ def SDL_atan2(y: float, x: float, /) -> float:
+ """double SDL_atan2(double y, double x)"""
+
+ @staticmethod
+ def SDL_atan2f(y: float, x: float, /) -> float:
+ """float SDL_atan2f(float y, float x)"""
+
+ @staticmethod
+ def SDL_atanf(x: float, /) -> float:
+ """float SDL_atanf(float x)"""
+
+ @staticmethod
+ def SDL_atof(str: Any, /) -> float:
+ """double SDL_atof(const char *str)"""
+
+ @staticmethod
+ def SDL_atoi(str: Any, /) -> int:
+ """int SDL_atoi(const char *str)"""
+
+ @staticmethod
+ def SDL_bsearch(key: Any, base: Any, nmemb: int, size: int, compare: Any, /) -> Any:
+ """void *SDL_bsearch(const void *key, const void *base, size_t nmemb, size_t size, SDL_CompareCallback compare)"""
+
+ @staticmethod
+ def SDL_bsearch_r(key: Any, base: Any, nmemb: int, size: int, compare: Any, userdata: Any, /) -> Any:
+ """void *SDL_bsearch_r(const void *key, const void *base, size_t nmemb, size_t size, SDL_CompareCallback_r compare, void *userdata)"""
+
+ @staticmethod
+ def SDL_calloc(nmemb: int, size: int, /) -> Any:
+ """void *SDL_calloc(size_t nmemb, size_t size)"""
+
+ @staticmethod
+ def SDL_ceil(x: float, /) -> float:
+ """double SDL_ceil(double x)"""
+
+ @staticmethod
+ def SDL_ceilf(x: float, /) -> float:
+ """float SDL_ceilf(float x)"""
+
+ @staticmethod
+ def SDL_copysign(x: float, y: float, /) -> float:
+ """double SDL_copysign(double x, double y)"""
+
+ @staticmethod
+ def SDL_copysignf(x: float, y: float, /) -> float:
+ """float SDL_copysignf(float x, float y)"""
+
+ @staticmethod
+ def SDL_cos(x: float, /) -> float:
+ """double SDL_cos(double x)"""
+
+ @staticmethod
+ def SDL_cosf(x: float, /) -> float:
+ """float SDL_cosf(float x)"""
+
+ @staticmethod
+ def SDL_crc16(crc: Any, data: Any, len: int, /) -> Any:
+ """Uint16 SDL_crc16(Uint16 crc, const void *data, size_t len)"""
+
+ @staticmethod
+ def SDL_crc32(crc: Any, data: Any, len: int, /) -> Any:
+ """Uint32 SDL_crc32(Uint32 crc, const void *data, size_t len)"""
+
+ @staticmethod
+ def SDL_exp(x: float, /) -> float:
+ """double SDL_exp(double x)"""
+
+ @staticmethod
+ def SDL_expf(x: float, /) -> float:
+ """float SDL_expf(float x)"""
+
+ @staticmethod
+ def SDL_fabs(x: float, /) -> float:
+ """double SDL_fabs(double x)"""
+
+ @staticmethod
+ def SDL_fabsf(x: float, /) -> float:
+ """float SDL_fabsf(float x)"""
+
+ @staticmethod
+ def SDL_floor(x: float, /) -> float:
+ """double SDL_floor(double x)"""
+
+ @staticmethod
+ def SDL_floorf(x: float, /) -> float:
+ """float SDL_floorf(float x)"""
+
+ @staticmethod
+ def SDL_fmod(x: float, y: float, /) -> float:
+ """double SDL_fmod(double x, double y)"""
+
+ @staticmethod
+ def SDL_fmodf(x: float, y: float, /) -> float:
+ """float SDL_fmodf(float x, float y)"""
+
+ @staticmethod
+ def SDL_free(mem: Any, /) -> None:
+ """void SDL_free(void *mem)"""
+
+ @staticmethod
+ def SDL_getenv(name: Any, /) -> Any:
+ """const char *SDL_getenv(const char *name)"""
+
+ @staticmethod
+ def SDL_getenv_unsafe(name: Any, /) -> Any:
+ """const char *SDL_getenv_unsafe(const char *name)"""
+
+ @staticmethod
+ def SDL_hid_ble_scan(active: bool, /) -> None:
+ """void SDL_hid_ble_scan(bool active)"""
+
+ @staticmethod
+ def SDL_hid_close(dev: Any, /) -> int:
+ """int SDL_hid_close(SDL_hid_device *dev)"""
+
+ @staticmethod
+ def SDL_hid_device_change_count() -> Any:
+ """Uint32 SDL_hid_device_change_count(void)"""
+
+ @staticmethod
+ def SDL_hid_enumerate(vendor_id: Any, product_id: Any, /) -> Any:
+ """SDL_hid_device_info *SDL_hid_enumerate(unsigned short vendor_id, unsigned short product_id)"""
+
+ @staticmethod
+ def SDL_hid_exit() -> int:
+ """int SDL_hid_exit(void)"""
+
+ @staticmethod
+ def SDL_hid_free_enumeration(devs: Any, /) -> None:
+ """void SDL_hid_free_enumeration(SDL_hid_device_info *devs)"""
+
+ @staticmethod
+ def SDL_hid_get_device_info(dev: Any, /) -> Any:
+ """SDL_hid_device_info *SDL_hid_get_device_info(SDL_hid_device *dev)"""
+
+ @staticmethod
+ def SDL_hid_get_feature_report(dev: Any, data: Any, length: int, /) -> int:
+ """int SDL_hid_get_feature_report(SDL_hid_device *dev, unsigned char *data, size_t length)"""
+
+ @staticmethod
+ def SDL_hid_get_indexed_string(dev: Any, string_index: int, string: Any, maxlen: int, /) -> int:
+ """int SDL_hid_get_indexed_string(SDL_hid_device *dev, int string_index, wchar_t *string, size_t maxlen)"""
+
+ @staticmethod
+ def SDL_hid_get_input_report(dev: Any, data: Any, length: int, /) -> int:
+ """int SDL_hid_get_input_report(SDL_hid_device *dev, unsigned char *data, size_t length)"""
+
+ @staticmethod
+ def SDL_hid_get_manufacturer_string(dev: Any, string: Any, maxlen: int, /) -> int:
+ """int SDL_hid_get_manufacturer_string(SDL_hid_device *dev, wchar_t *string, size_t maxlen)"""
+
+ @staticmethod
+ def SDL_hid_get_product_string(dev: Any, string: Any, maxlen: int, /) -> int:
+ """int SDL_hid_get_product_string(SDL_hid_device *dev, wchar_t *string, size_t maxlen)"""
+
+ @staticmethod
+ def SDL_hid_get_report_descriptor(dev: Any, buf: Any, buf_size: int, /) -> int:
+ """int SDL_hid_get_report_descriptor(SDL_hid_device *dev, unsigned char *buf, size_t buf_size)"""
+
+ @staticmethod
+ def SDL_hid_get_serial_number_string(dev: Any, string: Any, maxlen: int, /) -> int:
+ """int SDL_hid_get_serial_number_string(SDL_hid_device *dev, wchar_t *string, size_t maxlen)"""
+
+ @staticmethod
+ def SDL_hid_init() -> int:
+ """int SDL_hid_init(void)"""
+
+ @staticmethod
+ def SDL_hid_open(vendor_id: Any, product_id: Any, serial_number: Any, /) -> Any:
+ """SDL_hid_device *SDL_hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number)"""
+
+ @staticmethod
+ def SDL_hid_open_path(path: Any, /) -> Any:
+ """SDL_hid_device *SDL_hid_open_path(const char *path)"""
+
+ @staticmethod
+ def SDL_hid_read(dev: Any, data: Any, length: int, /) -> int:
+ """int SDL_hid_read(SDL_hid_device *dev, unsigned char *data, size_t length)"""
+
+ @staticmethod
+ def SDL_hid_read_timeout(dev: Any, data: Any, length: int, milliseconds: int, /) -> int:
+ """int SDL_hid_read_timeout(SDL_hid_device *dev, unsigned char *data, size_t length, int milliseconds)"""
+
+ @staticmethod
+ def SDL_hid_send_feature_report(dev: Any, data: Any, length: int, /) -> int:
+ """int SDL_hid_send_feature_report(SDL_hid_device *dev, const unsigned char *data, size_t length)"""
+
+ @staticmethod
+ def SDL_hid_set_nonblocking(dev: Any, nonblock: int, /) -> int:
+ """int SDL_hid_set_nonblocking(SDL_hid_device *dev, int nonblock)"""
+
+ @staticmethod
+ def SDL_hid_write(dev: Any, data: Any, length: int, /) -> int:
+ """int SDL_hid_write(SDL_hid_device *dev, const unsigned char *data, size_t length)"""
+
+ @staticmethod
+ def SDL_iconv(cd: Any, inbuf: Any, inbytesleft: Any, outbuf: Any, outbytesleft: Any, /) -> int:
+ """size_t SDL_iconv(SDL_iconv_t cd, const char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft)"""
+
+ @staticmethod
+ def SDL_iconv_close(cd: Any, /) -> int:
+ """int SDL_iconv_close(SDL_iconv_t cd)"""
+
+ @staticmethod
+ def SDL_iconv_open(tocode: Any, fromcode: Any, /) -> Any:
+ """SDL_iconv_t SDL_iconv_open(const char *tocode, const char *fromcode)"""
+
+ @staticmethod
+ def SDL_iconv_string(tocode: Any, fromcode: Any, inbuf: Any, inbytesleft: int, /) -> Any:
+ """char *SDL_iconv_string(const char *tocode, const char *fromcode, const char *inbuf, size_t inbytesleft)"""
+
+ @staticmethod
+ def SDL_isalnum(x: int, /) -> int:
+ """int SDL_isalnum(int x)"""
+
+ @staticmethod
+ def SDL_isalpha(x: int, /) -> int:
+ """int SDL_isalpha(int x)"""
+
+ @staticmethod
+ def SDL_isblank(x: int, /) -> int:
+ """int SDL_isblank(int x)"""
+
+ @staticmethod
+ def SDL_iscntrl(x: int, /) -> int:
+ """int SDL_iscntrl(int x)"""
+
+ @staticmethod
+ def SDL_isdigit(x: int, /) -> int:
+ """int SDL_isdigit(int x)"""
+
+ @staticmethod
+ def SDL_isgraph(x: int, /) -> int:
+ """int SDL_isgraph(int x)"""
+
+ @staticmethod
+ def SDL_isinf(x: float, /) -> int:
+ """int SDL_isinf(double x)"""
+
+ @staticmethod
+ def SDL_isinff(x: float, /) -> int:
+ """int SDL_isinff(float x)"""
+
+ @staticmethod
+ def SDL_islower(x: int, /) -> int:
+ """int SDL_islower(int x)"""
+
+ @staticmethod
+ def SDL_isnan(x: float, /) -> int:
+ """int SDL_isnan(double x)"""
+
+ @staticmethod
+ def SDL_isnanf(x: float, /) -> int:
+ """int SDL_isnanf(float x)"""
+
+ @staticmethod
+ def SDL_isprint(x: int, /) -> int:
+ """int SDL_isprint(int x)"""
+
+ @staticmethod
+ def SDL_ispunct(x: int, /) -> int:
+ """int SDL_ispunct(int x)"""
+
+ @staticmethod
+ def SDL_isspace(x: int, /) -> int:
+ """int SDL_isspace(int x)"""
+
+ @staticmethod
+ def SDL_isupper(x: int, /) -> int:
+ """int SDL_isupper(int x)"""
+
+ @staticmethod
+ def SDL_isxdigit(x: int, /) -> int:
+ """int SDL_isxdigit(int x)"""
+
+ @staticmethod
+ def SDL_itoa(value: int, str: Any, radix: int, /) -> Any:
+ """char *SDL_itoa(int value, char *str, int radix)"""
+
+ @staticmethod
+ def SDL_lltoa(value: Any, str: Any, radix: int, /) -> Any:
+ """char *SDL_lltoa(long long value, char *str, int radix)"""
+
+ @staticmethod
+ def SDL_log(x: float, /) -> float:
+ """double SDL_log(double x)"""
+
+ @staticmethod
+ def SDL_log10(x: float, /) -> float:
+ """double SDL_log10(double x)"""
+
+ @staticmethod
+ def SDL_log10f(x: float, /) -> float:
+ """float SDL_log10f(float x)"""
+
+ @staticmethod
+ def SDL_logf(x: float, /) -> float:
+ """float SDL_logf(float x)"""
+
+ @staticmethod
+ def SDL_lround(x: float, /) -> Any:
+ """long SDL_lround(double x)"""
+
+ @staticmethod
+ def SDL_lroundf(x: float, /) -> Any:
+ """long SDL_lroundf(float x)"""
+
+ @staticmethod
+ def SDL_ltoa(value: Any, str: Any, radix: int, /) -> Any:
+ """char *SDL_ltoa(long value, char *str, int radix)"""
+
+ @staticmethod
+ def SDL_malloc(size: int, /) -> Any:
+ """void *SDL_malloc(size_t size)"""
+
+ @staticmethod
+ def SDL_memcmp(s1: Any, s2: Any, len: int, /) -> int:
+ """int SDL_memcmp(const void *s1, const void *s2, size_t len)"""
+
+ @staticmethod
+ def SDL_memcpy(dst: Any, src: Any, len: int, /) -> Any:
+ """void *SDL_memcpy(void *dst, const void *src, size_t len)"""
+
+ @staticmethod
+ def SDL_memmove(dst: Any, src: Any, len: int, /) -> Any:
+ """void *SDL_memmove(void *dst, const void *src, size_t len)"""
+
+ @staticmethod
+ def SDL_memset(dst: Any, c: int, len: int, /) -> Any:
+ """void *SDL_memset(void *dst, int c, size_t len)"""
+
+ @staticmethod
+ def SDL_memset4(dst: Any, val: Any, dwords: int, /) -> Any:
+ """void *SDL_memset4(void *dst, Uint32 val, size_t dwords)"""
+
+ @staticmethod
+ def SDL_modf(x: float, y: Any, /) -> float:
+ """double SDL_modf(double x, double *y)"""
+
+ @staticmethod
+ def SDL_modff(x: float, y: Any, /) -> float:
+ """float SDL_modff(float x, float *y)"""
+
+ @staticmethod
+ def SDL_murmur3_32(data: Any, len: int, seed: Any, /) -> Any:
+ """Uint32 SDL_murmur3_32(const void *data, size_t len, Uint32 seed)"""
+
+ @staticmethod
+ def SDL_pow(x: float, y: float, /) -> float:
+ """double SDL_pow(double x, double y)"""
+
+ @staticmethod
+ def SDL_powf(x: float, y: float, /) -> float:
+ """float SDL_powf(float x, float y)"""
+
+ @staticmethod
+ def SDL_qsort(base: Any, nmemb: int, size: int, compare: Any, /) -> None:
+ """void SDL_qsort(void *base, size_t nmemb, size_t size, SDL_CompareCallback compare)"""
+
+ @staticmethod
+ def SDL_qsort_r(base: Any, nmemb: int, size: int, compare: Any, userdata: Any, /) -> None:
+ """void SDL_qsort_r(void *base, size_t nmemb, size_t size, SDL_CompareCallback_r compare, void *userdata)"""
+
+ @staticmethod
+ def SDL_rand(n: Any, /) -> Any:
+ """Sint32 SDL_rand(Sint32 n)"""
+
+ @staticmethod
+ def SDL_rand_bits() -> Any:
+ """Uint32 SDL_rand_bits(void)"""
+
+ @staticmethod
+ def SDL_rand_bits_r(state: Any, /) -> Any:
+ """Uint32 SDL_rand_bits_r(Uint64 *state)"""
+
+ @staticmethod
+ def SDL_rand_r(state: Any, n: Any, /) -> Any:
+ """Sint32 SDL_rand_r(Uint64 *state, Sint32 n)"""
+
+ @staticmethod
+ def SDL_randf() -> float:
+ """float SDL_randf(void)"""
+
+ @staticmethod
+ def SDL_randf_r(state: Any, /) -> float:
+ """float SDL_randf_r(Uint64 *state)"""
+
+ @staticmethod
+ def SDL_realloc(mem: Any, size: int, /) -> Any:
+ """void *SDL_realloc(void *mem, size_t size)"""
+
+ @staticmethod
+ def SDL_round(x: float, /) -> float:
+ """double SDL_round(double x)"""
+
+ @staticmethod
+ def SDL_roundf(x: float, /) -> float:
+ """float SDL_roundf(float x)"""
+
+ @staticmethod
+ def SDL_scalbn(x: float, n: int, /) -> float:
+ """double SDL_scalbn(double x, int n)"""
+
+ @staticmethod
+ def SDL_scalbnf(x: float, n: int, /) -> float:
+ """float SDL_scalbnf(float x, int n)"""
+
+ @staticmethod
+ def SDL_setenv_unsafe(name: Any, value: Any, overwrite: int, /) -> int:
+ """int SDL_setenv_unsafe(const char *name, const char *value, int overwrite)"""
+
+ @staticmethod
+ def SDL_sin(x: float, /) -> float:
+ """double SDL_sin(double x)"""
+
+ @staticmethod
+ def SDL_sinf(x: float, /) -> float:
+ """float SDL_sinf(float x)"""
+
+ @staticmethod
+ def SDL_snprintf(text: Any, maxlen: int, fmt: Any, /, *__args: Any) -> int:
+ """int SDL_snprintf(char *text, size_t maxlen, const char *fmt, ...)"""
+
+ @staticmethod
+ def SDL_sqrt(x: float, /) -> float:
+ """double SDL_sqrt(double x)"""
+
+ @staticmethod
+ def SDL_sqrtf(x: float, /) -> float:
+ """float SDL_sqrtf(float x)"""
+
+ @staticmethod
+ def SDL_srand(seed: Any, /) -> None:
+ """void SDL_srand(Uint64 seed)"""
+
+ @staticmethod
+ def SDL_sscanf(text: Any, fmt: Any, /, *__args: Any) -> int:
+ """int SDL_sscanf(const char *text, const char *fmt, ...)"""
+
+ @staticmethod
+ def SDL_strcasecmp(str1: Any, str2: Any, /) -> int:
+ """int SDL_strcasecmp(const char *str1, const char *str2)"""
+
+ @staticmethod
+ def SDL_strcasestr(haystack: Any, needle: Any, /) -> Any:
+ """char *SDL_strcasestr(const char *haystack, const char *needle)"""
+
+ @staticmethod
+ def SDL_strchr(str: Any, c: int, /) -> Any:
+ """char *SDL_strchr(const char *str, int c)"""
+
+ @staticmethod
+ def SDL_strcmp(str1: Any, str2: Any, /) -> int:
+ """int SDL_strcmp(const char *str1, const char *str2)"""
+
+ @staticmethod
+ def SDL_strdup(str: Any, /) -> Any:
+ """char *SDL_strdup(const char *str)"""
+
+ @staticmethod
+ def SDL_strlcat(dst: Any, src: Any, maxlen: int, /) -> int:
+ """size_t SDL_strlcat(char *dst, const char *src, size_t maxlen)"""
+
+ @staticmethod
+ def SDL_strlcpy(dst: Any, src: Any, maxlen: int, /) -> int:
+ """size_t SDL_strlcpy(char *dst, const char *src, size_t maxlen)"""
+
+ @staticmethod
+ def SDL_strlen(str: Any, /) -> int:
+ """size_t SDL_strlen(const char *str)"""
+
+ @staticmethod
+ def SDL_strlwr(str: Any, /) -> Any:
+ """char *SDL_strlwr(char *str)"""
+
+ @staticmethod
+ def SDL_strncasecmp(str1: Any, str2: Any, maxlen: int, /) -> int:
+ """int SDL_strncasecmp(const char *str1, const char *str2, size_t maxlen)"""
+
+ @staticmethod
+ def SDL_strncmp(str1: Any, str2: Any, maxlen: int, /) -> int:
+ """int SDL_strncmp(const char *str1, const char *str2, size_t maxlen)"""
+
+ @staticmethod
+ def SDL_strndup(str: Any, maxlen: int, /) -> Any:
+ """char *SDL_strndup(const char *str, size_t maxlen)"""
+
+ @staticmethod
+ def SDL_strnlen(str: Any, maxlen: int, /) -> int:
+ """size_t SDL_strnlen(const char *str, size_t maxlen)"""
+
+ @staticmethod
+ def SDL_strnstr(haystack: Any, needle: Any, maxlen: int, /) -> Any:
+ """char *SDL_strnstr(const char *haystack, const char *needle, size_t maxlen)"""
+
+ @staticmethod
+ def SDL_strpbrk(str: Any, breakset: Any, /) -> Any:
+ """char *SDL_strpbrk(const char *str, const char *breakset)"""
+
+ @staticmethod
+ def SDL_strrchr(str: Any, c: int, /) -> Any:
+ """char *SDL_strrchr(const char *str, int c)"""
+
+ @staticmethod
+ def SDL_strrev(str: Any, /) -> Any:
+ """char *SDL_strrev(char *str)"""
+
+ @staticmethod
+ def SDL_strstr(haystack: Any, needle: Any, /) -> Any:
+ """char *SDL_strstr(const char *haystack, const char *needle)"""
+
+ @staticmethod
+ def SDL_strtod(str: Any, endp: Any, /) -> float:
+ """double SDL_strtod(const char *str, char **endp)"""
+
+ @staticmethod
+ def SDL_strtok_r(str: Any, delim: Any, saveptr: Any, /) -> Any:
+ """char *SDL_strtok_r(char *str, const char *delim, char **saveptr)"""
+
+ @staticmethod
+ def SDL_strtol(str: Any, endp: Any, base: int, /) -> Any:
+ """long SDL_strtol(const char *str, char **endp, int base)"""
+
+ @staticmethod
+ def SDL_strtoll(str: Any, endp: Any, base: int, /) -> Any:
+ """long long SDL_strtoll(const char *str, char **endp, int base)"""
+
+ @staticmethod
+ def SDL_strtoul(str: Any, endp: Any, base: int, /) -> Any:
+ """unsigned long SDL_strtoul(const char *str, char **endp, int base)"""
+
+ @staticmethod
+ def SDL_strtoull(str: Any, endp: Any, base: int, /) -> Any:
+ """unsigned long long SDL_strtoull(const char *str, char **endp, int base)"""
+
+ @staticmethod
+ def SDL_strupr(str: Any, /) -> Any:
+ """char *SDL_strupr(char *str)"""
+
+ @staticmethod
+ def SDL_swprintf(text: Any, maxlen: int, fmt: Any, /, *__args: Any) -> int:
+ """int SDL_swprintf(wchar_t *text, size_t maxlen, const wchar_t *fmt, ...)"""
+
+ @staticmethod
+ def SDL_tan(x: float, /) -> float:
+ """double SDL_tan(double x)"""
+
+ @staticmethod
+ def SDL_tanf(x: float, /) -> float:
+ """float SDL_tanf(float x)"""
+
+ @staticmethod
+ def SDL_tolower(x: int, /) -> int:
+ """int SDL_tolower(int x)"""
+
+ @staticmethod
+ def SDL_toupper(x: int, /) -> int:
+ """int SDL_toupper(int x)"""
+
+ @staticmethod
+ def SDL_trunc(x: float, /) -> float:
+ """double SDL_trunc(double x)"""
+
+ @staticmethod
+ def SDL_truncf(x: float, /) -> float:
+ """float SDL_truncf(float x)"""
+
+ @staticmethod
+ def SDL_uitoa(value: int, str: Any, radix: int, /) -> Any:
+ """char *SDL_uitoa(unsigned int value, char *str, int radix)"""
+
+ @staticmethod
+ def SDL_ulltoa(value: Any, str: Any, radix: int, /) -> Any:
+ """char *SDL_ulltoa(unsigned long long value, char *str, int radix)"""
+
+ @staticmethod
+ def SDL_ultoa(value: Any, str: Any, radix: int, /) -> Any:
+ """char *SDL_ultoa(unsigned long value, char *str, int radix)"""
+
+ @staticmethod
+ def SDL_unsetenv_unsafe(name: Any, /) -> int:
+ """int SDL_unsetenv_unsafe(const char *name)"""
+
+ @staticmethod
+ def SDL_utf8strlcpy(dst: Any, src: Any, dst_bytes: int, /) -> int:
+ """size_t SDL_utf8strlcpy(char *dst, const char *src, size_t dst_bytes)"""
+
+ @staticmethod
+ def SDL_utf8strlen(str: Any, /) -> int:
+ """size_t SDL_utf8strlen(const char *str)"""
+
+ @staticmethod
+ def SDL_utf8strnlen(str: Any, bytes: int, /) -> int:
+ """size_t SDL_utf8strnlen(const char *str, size_t bytes)"""
+
+ @staticmethod
+ def SDL_wcscasecmp(str1: Any, str2: Any, /) -> int:
+ """int SDL_wcscasecmp(const wchar_t *str1, const wchar_t *str2)"""
+
+ @staticmethod
+ def SDL_wcscmp(str1: Any, str2: Any, /) -> int:
+ """int SDL_wcscmp(const wchar_t *str1, const wchar_t *str2)"""
+
+ @staticmethod
+ def SDL_wcsdup(wstr: Any, /) -> Any:
+ """wchar_t *SDL_wcsdup(const wchar_t *wstr)"""
+
+ @staticmethod
+ def SDL_wcslcat(dst: Any, src: Any, maxlen: int, /) -> int:
+ """size_t SDL_wcslcat(wchar_t *dst, const wchar_t *src, size_t maxlen)"""
+
+ @staticmethod
+ def SDL_wcslcpy(dst: Any, src: Any, maxlen: int, /) -> int:
+ """size_t SDL_wcslcpy(wchar_t *dst, const wchar_t *src, size_t maxlen)"""
+
+ @staticmethod
+ def SDL_wcslen(wstr: Any, /) -> int:
+ """size_t SDL_wcslen(const wchar_t *wstr)"""
+
+ @staticmethod
+ def SDL_wcsncasecmp(str1: Any, str2: Any, maxlen: int, /) -> int:
+ """int SDL_wcsncasecmp(const wchar_t *str1, const wchar_t *str2, size_t maxlen)"""
+
+ @staticmethod
+ def SDL_wcsncmp(str1: Any, str2: Any, maxlen: int, /) -> int:
+ """int SDL_wcsncmp(const wchar_t *str1, const wchar_t *str2, size_t maxlen)"""
+
+ @staticmethod
+ def SDL_wcsnlen(wstr: Any, maxlen: int, /) -> int:
+ """size_t SDL_wcsnlen(const wchar_t *wstr, size_t maxlen)"""
+
+ @staticmethod
+ def SDL_wcsnstr(haystack: Any, needle: Any, maxlen: int, /) -> Any:
+ """wchar_t *SDL_wcsnstr(const wchar_t *haystack, const wchar_t *needle, size_t maxlen)"""
+
+ @staticmethod
+ def SDL_wcsstr(haystack: Any, needle: Any, /) -> Any:
+ """wchar_t *SDL_wcsstr(const wchar_t *haystack, const wchar_t *needle)"""
+
+ @staticmethod
+ def SDL_wcstol(str: Any, endp: Any, base: int, /) -> Any:
+ """long SDL_wcstol(const wchar_t *str, wchar_t **endp, int base)"""
+
+ @staticmethod
+ def TCOD_bsp_contains(node: Any, x: int, y: int, /) -> bool:
+ """bool TCOD_bsp_contains(TCOD_bsp_t *node, int x, int y)"""
+
+ @staticmethod
+ def TCOD_bsp_delete(node: Any, /) -> None:
+ """void TCOD_bsp_delete(TCOD_bsp_t *node)"""
+
+ @staticmethod
+ def TCOD_bsp_father(node: Any, /) -> Any:
+ """TCOD_bsp_t *TCOD_bsp_father(TCOD_bsp_t *node)"""
+
+ @staticmethod
+ def TCOD_bsp_find_node(node: Any, x: int, y: int, /) -> Any:
+ """TCOD_bsp_t *TCOD_bsp_find_node(TCOD_bsp_t *node, int x, int y)"""
+
+ @staticmethod
+ def TCOD_bsp_is_leaf(node: Any, /) -> bool:
+ """bool TCOD_bsp_is_leaf(TCOD_bsp_t *node)"""
+
+ @staticmethod
+ def TCOD_bsp_left(node: Any, /) -> Any:
+ """TCOD_bsp_t *TCOD_bsp_left(TCOD_bsp_t *node)"""
+
+ @staticmethod
+ def TCOD_bsp_new() -> Any:
+ """TCOD_bsp_t *TCOD_bsp_new(void)"""
+
+ @staticmethod
+ def TCOD_bsp_new_with_size(x: int, y: int, w: int, h: int, /) -> Any:
+ """TCOD_bsp_t *TCOD_bsp_new_with_size(int x, int y, int w, int h)"""
+
+ @staticmethod
+ def TCOD_bsp_remove_sons(node: Any, /) -> None:
+ """void TCOD_bsp_remove_sons(TCOD_bsp_t *node)"""
+
+ @staticmethod
+ def TCOD_bsp_resize(node: Any, x: int, y: int, w: int, h: int, /) -> None:
+ """void TCOD_bsp_resize(TCOD_bsp_t *node, int x, int y, int w, int h)"""
+
+ @staticmethod
+ def TCOD_bsp_right(node: Any, /) -> Any:
+ """TCOD_bsp_t *TCOD_bsp_right(TCOD_bsp_t *node)"""
+
+ @staticmethod
+ def TCOD_bsp_split_once(node: Any, horizontal: bool, position: int, /) -> None:
+ """void TCOD_bsp_split_once(TCOD_bsp_t *node, bool horizontal, int position)"""
+
+ @staticmethod
+ def TCOD_bsp_split_recursive(
+ node: Any, randomizer: Any, nb: int, minHSize: int, minVSize: int, maxHRatio: float, maxVRatio: float, /
+ ) -> None:
+ """void TCOD_bsp_split_recursive(TCOD_bsp_t *node, TCOD_Random *randomizer, int nb, int minHSize, int minVSize, float maxHRatio, float maxVRatio)"""
+
+ @staticmethod
+ def TCOD_bsp_traverse_in_order(node: Any, listener: Any, userData: Any, /) -> bool:
+ """bool TCOD_bsp_traverse_in_order(TCOD_bsp_t *node, TCOD_bsp_callback_t listener, void *userData)"""
+
+ @staticmethod
+ def TCOD_bsp_traverse_inverted_level_order(node: Any, listener: Any, userData: Any, /) -> bool:
+ """bool TCOD_bsp_traverse_inverted_level_order(TCOD_bsp_t *node, TCOD_bsp_callback_t listener, void *userData)"""
+
+ @staticmethod
+ def TCOD_bsp_traverse_level_order(node: Any, listener: Any, userData: Any, /) -> bool:
+ """bool TCOD_bsp_traverse_level_order(TCOD_bsp_t *node, TCOD_bsp_callback_t listener, void *userData)"""
+
+ @staticmethod
+ def TCOD_bsp_traverse_post_order(node: Any, listener: Any, userData: Any, /) -> bool:
+ """bool TCOD_bsp_traverse_post_order(TCOD_bsp_t *node, TCOD_bsp_callback_t listener, void *userData)"""
+
+ @staticmethod
+ def TCOD_bsp_traverse_pre_order(node: Any, listener: Any, userData: Any, /) -> bool:
+ """bool TCOD_bsp_traverse_pre_order(TCOD_bsp_t *node, TCOD_bsp_callback_t listener, void *userData)"""
+
+ @staticmethod
+ def TCOD_clear_error() -> None:
+ """void TCOD_clear_error(void)"""
+
+ @staticmethod
+ def TCOD_close_library(arg0: Any, /) -> None:
+ """void TCOD_close_library(TCOD_library_t)"""
+
+ @staticmethod
+ def TCOD_color_HSV(hue: float, saturation: float, value: float, /) -> Any:
+ """TCOD_color_t TCOD_color_HSV(float hue, float saturation, float value)"""
+
+ @staticmethod
+ def TCOD_color_RGB(r: Any, g: Any, b: Any, /) -> Any:
+ """TCOD_color_t TCOD_color_RGB(uint8_t r, uint8_t g, uint8_t b)"""
+
+ @staticmethod
+ def TCOD_color_add(c1: Any, c2: Any, /) -> Any:
+ """TCOD_color_t TCOD_color_add(TCOD_color_t c1, TCOD_color_t c2)"""
+
+ @staticmethod
+ def TCOD_color_add_wrapper(c1: Any, c2: Any, /) -> Any:
+ """colornum_t TCOD_color_add_wrapper(colornum_t c1, colornum_t c2)"""
+
+ @staticmethod
+ def TCOD_color_alpha_blend(dst: Any, src: Any, /) -> None:
+ """void TCOD_color_alpha_blend(TCOD_ColorRGBA *dst, const TCOD_ColorRGBA *src)"""
+
+ @staticmethod
+ def TCOD_color_equals(c1: Any, c2: Any, /) -> bool:
+ """bool TCOD_color_equals(TCOD_color_t c1, TCOD_color_t c2)"""
+
+ @staticmethod
+ def TCOD_color_equals_wrapper(c1: Any, c2: Any, /) -> bool:
+ """bool TCOD_color_equals_wrapper(colornum_t c1, colornum_t c2)"""
+
+ @staticmethod
+ def TCOD_color_gen_map(map: Any, nb_key: int, key_color: Any, key_index: Any, /) -> None:
+ """void TCOD_color_gen_map(TCOD_color_t *map, int nb_key, const TCOD_color_t *key_color, const int *key_index)"""
+
+ @staticmethod
+ def TCOD_color_get_HSV(color: Any, hue: Any, saturation: Any, value: Any, /) -> None:
+ """void TCOD_color_get_HSV(TCOD_color_t color, float *hue, float *saturation, float *value)"""
+
+ @staticmethod
+ def TCOD_color_get_HSV_wrapper(c: Any, h: Any, s: Any, v: Any, /) -> None:
+ """void TCOD_color_get_HSV_wrapper(colornum_t c, float *h, float *s, float *v)"""
+
+ @staticmethod
+ def TCOD_color_get_hue(color: Any, /) -> float:
+ """float TCOD_color_get_hue(TCOD_color_t color)"""
+
+ @staticmethod
+ def TCOD_color_get_hue_wrapper(c: Any, /) -> float:
+ """float TCOD_color_get_hue_wrapper(colornum_t c)"""
+
+ @staticmethod
+ def TCOD_color_get_saturation(color: Any, /) -> float:
+ """float TCOD_color_get_saturation(TCOD_color_t color)"""
+
+ @staticmethod
+ def TCOD_color_get_saturation_wrapper(c: Any, /) -> float:
+ """float TCOD_color_get_saturation_wrapper(colornum_t c)"""
+
+ @staticmethod
+ def TCOD_color_get_value(color: Any, /) -> float:
+ """float TCOD_color_get_value(TCOD_color_t color)"""
+
+ @staticmethod
+ def TCOD_color_get_value_wrapper(c: Any, /) -> float:
+ """float TCOD_color_get_value_wrapper(colornum_t c)"""
+
+ @staticmethod
+ def TCOD_color_lerp(c1: Any, c2: Any, coef: float, /) -> Any:
+ """TCOD_color_t TCOD_color_lerp(TCOD_color_t c1, TCOD_color_t c2, float coef)"""
+
+ @staticmethod
+ def TCOD_color_lerp_wrapper(c1: Any, c2: Any, coef: float, /) -> Any:
+ """colornum_t TCOD_color_lerp_wrapper(colornum_t c1, colornum_t c2, float coef)"""
+
+ @staticmethod
+ def TCOD_color_multiply(c1: Any, c2: Any, /) -> Any:
+ """TCOD_color_t TCOD_color_multiply(TCOD_color_t c1, TCOD_color_t c2)"""
+
+ @staticmethod
+ def TCOD_color_multiply_scalar(c1: Any, value: float, /) -> Any:
+ """TCOD_color_t TCOD_color_multiply_scalar(TCOD_color_t c1, float value)"""
+
+ @staticmethod
+ def TCOD_color_multiply_scalar_wrapper(c1: Any, value: float, /) -> Any:
+ """colornum_t TCOD_color_multiply_scalar_wrapper(colornum_t c1, float value)"""
+
+ @staticmethod
+ def TCOD_color_multiply_wrapper(c1: Any, c2: Any, /) -> Any:
+ """colornum_t TCOD_color_multiply_wrapper(colornum_t c1, colornum_t c2)"""
+
+ @staticmethod
+ def TCOD_color_scale_HSV(color: Any, saturation_coef: float, value_coef: float, /) -> None:
+ """void TCOD_color_scale_HSV(TCOD_color_t *color, float saturation_coef, float value_coef)"""
+
+ @staticmethod
+ def TCOD_color_set_HSV(color: Any, hue: float, saturation: float, value: float, /) -> None:
+ """void TCOD_color_set_HSV(TCOD_color_t *color, float hue, float saturation, float value)"""
+
+ @staticmethod
+ def TCOD_color_set_hue(color: Any, hue: float, /) -> None:
+ """void TCOD_color_set_hue(TCOD_color_t *color, float hue)"""
+
+ @staticmethod
+ def TCOD_color_set_saturation(color: Any, saturation: float, /) -> None:
+ """void TCOD_color_set_saturation(TCOD_color_t *color, float saturation)"""
+
+ @staticmethod
+ def TCOD_color_set_value(color: Any, value: float, /) -> None:
+ """void TCOD_color_set_value(TCOD_color_t *color, float value)"""
+
+ @staticmethod
+ def TCOD_color_shift_hue(color: Any, shift: float, /) -> None:
+ """void TCOD_color_shift_hue(TCOD_color_t *color, float shift)"""
+
+ @staticmethod
+ def TCOD_color_subtract(c1: Any, c2: Any, /) -> Any:
+ """TCOD_color_t TCOD_color_subtract(TCOD_color_t c1, TCOD_color_t c2)"""
+
+ @staticmethod
+ def TCOD_color_subtract_wrapper(c1: Any, c2: Any, /) -> Any:
+ """colornum_t TCOD_color_subtract_wrapper(colornum_t c1, colornum_t c2)"""
+
+ @staticmethod
+ def TCOD_condition_broadcast(sem: Any, /) -> None:
+ """void TCOD_condition_broadcast(TCOD_cond_t sem)"""
+
+ @staticmethod
+ def TCOD_condition_delete(sem: Any, /) -> None:
+ """void TCOD_condition_delete(TCOD_cond_t sem)"""
+
+ @staticmethod
+ def TCOD_condition_new() -> Any:
+ """TCOD_cond_t TCOD_condition_new(void)"""
+
+ @staticmethod
+ def TCOD_condition_signal(sem: Any, /) -> None:
+ """void TCOD_condition_signal(TCOD_cond_t sem)"""
+
+ @staticmethod
+ def TCOD_condition_wait(sem: Any, mut: Any, /) -> None:
+ """void TCOD_condition_wait(TCOD_cond_t sem, TCOD_mutex_t mut)"""
+
+ @staticmethod
+ def TCOD_console_blit(
+ src: Any,
+ xSrc: int,
+ ySrc: int,
+ wSrc: int,
+ hSrc: int,
+ dst: Any,
+ xDst: int,
+ yDst: int,
+ foreground_alpha: float,
+ background_alpha: float,
+ /,
+ ) -> None:
+ """void TCOD_console_blit(const TCOD_Console *src, int xSrc, int ySrc, int wSrc, int hSrc, TCOD_Console *dst, int xDst, int yDst, float foreground_alpha, float background_alpha)"""
+
+ @staticmethod
+ def TCOD_console_blit_key_color(
+ src: Any,
+ xSrc: int,
+ ySrc: int,
+ wSrc: int,
+ hSrc: int,
+ dst: Any,
+ xDst: int,
+ yDst: int,
+ foreground_alpha: float,
+ background_alpha: float,
+ key_color: Any,
+ /,
+ ) -> None:
+ """void TCOD_console_blit_key_color(const TCOD_Console *src, int xSrc, int ySrc, int wSrc, int hSrc, TCOD_Console *dst, int xDst, int yDst, float foreground_alpha, float background_alpha, const TCOD_color_t *key_color)"""
+
+ @staticmethod
+ def TCOD_console_check_for_keypress(flags: int, /) -> Any:
+ """TCOD_key_t TCOD_console_check_for_keypress(int flags)"""
+
+ @staticmethod
+ def TCOD_console_check_for_keypress_wrapper(holder: Any, flags: int, /) -> bool:
+ """bool TCOD_console_check_for_keypress_wrapper(TCOD_key_t *holder, int flags)"""
+
+ @staticmethod
+ def TCOD_console_clear(con: Any, /) -> None:
+ """void TCOD_console_clear(TCOD_Console *con)"""
+
+ @staticmethod
+ def TCOD_console_credits() -> None:
+ """void TCOD_console_credits(void)"""
+
+ @staticmethod
+ def TCOD_console_credits_render(x: int, y: int, alpha: bool, /) -> bool:
+ """bool TCOD_console_credits_render(int x, int y, bool alpha)"""
+
+ @staticmethod
+ def TCOD_console_credits_render_ex(console: Any, x: int, y: int, alpha: bool, delta_time: float, /) -> bool:
+ """bool TCOD_console_credits_render_ex(TCOD_Console *console, int x, int y, bool alpha, float delta_time)"""
+
+ @staticmethod
+ def TCOD_console_credits_reset() -> None:
+ """void TCOD_console_credits_reset(void)"""
+
+ @staticmethod
+ def TCOD_console_delete(console: Any, /) -> None:
+ """void TCOD_console_delete(TCOD_Console *console)"""
+
+ @staticmethod
+ def TCOD_console_disable_keyboard_repeat() -> None:
+ """void TCOD_console_disable_keyboard_repeat(void)"""
+
+ @staticmethod
+ def TCOD_console_double_hline(con: Any, x: int, y: int, l: int, flag: Any, /) -> None:
+ """void TCOD_console_double_hline(TCOD_console_t con, int x, int y, int l, TCOD_bkgnd_flag_t flag)"""
+
+ @staticmethod
+ def TCOD_console_double_vline(con: Any, x: int, y: int, l: int, flag: Any, /) -> None:
+ """void TCOD_console_double_vline(TCOD_console_t con, int x, int y, int l, TCOD_bkgnd_flag_t flag)"""
+
+ @staticmethod
+ def TCOD_console_draw_frame_rgb(
+ con: Any, x: int, y: int, width: int, height: int, decoration: Any, fg: Any, bg: Any, flag: Any, clear: bool, /
+ ) -> Any:
+ """TCOD_Error TCOD_console_draw_frame_rgb(struct TCOD_Console *con, int x, int y, int width, int height, const int *decoration, const TCOD_ColorRGB *fg, const TCOD_ColorRGB *bg, TCOD_bkgnd_flag_t flag, bool clear)"""
+
+ @staticmethod
+ def TCOD_console_draw_rect_rgb(
+ console: Any, x: int, y: int, width: int, height: int, ch: int, fg: Any, bg: Any, flag: Any, /
+ ) -> Any:
+ """TCOD_Error TCOD_console_draw_rect_rgb(TCOD_Console *console, int x, int y, int width, int height, int ch, const TCOD_color_t *fg, const TCOD_color_t *bg, TCOD_bkgnd_flag_t flag)"""
+
+ @staticmethod
+ def TCOD_console_fill_background(con: Any, r: Any, g: Any, b: Any, /) -> None:
+ """void TCOD_console_fill_background(TCOD_console_t con, int *r, int *g, int *b)"""
+
+ @staticmethod
+ def TCOD_console_fill_char(con: Any, arr: Any, /) -> None:
+ """void TCOD_console_fill_char(TCOD_console_t con, int *arr)"""
+
+ @staticmethod
+ def TCOD_console_fill_foreground(con: Any, r: Any, g: Any, b: Any, /) -> None:
+ """void TCOD_console_fill_foreground(TCOD_console_t con, int *r, int *g, int *b)"""
+
+ @staticmethod
+ def TCOD_console_flush() -> Any:
+ """TCOD_Error TCOD_console_flush(void)"""
+
+ @staticmethod
+ def TCOD_console_flush_ex(console: Any, viewport: Any, /) -> Any:
+ """TCOD_Error TCOD_console_flush_ex(TCOD_Console *console, struct TCOD_ViewportOptions *viewport)"""
+
+ @staticmethod
+ def TCOD_console_forward(s: Any, l: int, /) -> Any:
+ """unsigned char *TCOD_console_forward(unsigned char *s, int l)"""
+
+ @staticmethod
+ def TCOD_console_from_file(filename: Any, /) -> Any:
+ """TCOD_console_t TCOD_console_from_file(const char *filename)"""
+
+ @staticmethod
+ def TCOD_console_from_xp(filename: Any, /) -> Any:
+ """TCOD_console_t TCOD_console_from_xp(const char *filename)"""
+
+ @staticmethod
+ def TCOD_console_get_alignment(con: Any, /) -> Any:
+ """TCOD_alignment_t TCOD_console_get_alignment(TCOD_Console *con)"""
+
+ @staticmethod
+ def TCOD_console_get_background_flag(con: Any, /) -> Any:
+ """TCOD_bkgnd_flag_t TCOD_console_get_background_flag(TCOD_Console *con)"""
+
+ @staticmethod
+ def TCOD_console_get_char(con: Any, x: int, y: int, /) -> int:
+ """int TCOD_console_get_char(const TCOD_Console *con, int x, int y)"""
+
+ @staticmethod
+ def TCOD_console_get_char_background(con: Any, x: int, y: int, /) -> Any:
+ """TCOD_color_t TCOD_console_get_char_background(const TCOD_Console *con, int x, int y)"""
+
+ @staticmethod
+ def TCOD_console_get_char_background_wrapper(con: Any, x: int, y: int, /) -> Any:
+ """colornum_t TCOD_console_get_char_background_wrapper(TCOD_console_t con, int x, int y)"""
+
+ @staticmethod
+ def TCOD_console_get_char_foreground(con: Any, x: int, y: int, /) -> Any:
+ """TCOD_color_t TCOD_console_get_char_foreground(const TCOD_Console *con, int x, int y)"""
+
+ @staticmethod
+ def TCOD_console_get_char_foreground_wrapper(con: Any, x: int, y: int, /) -> Any:
+ """colornum_t TCOD_console_get_char_foreground_wrapper(TCOD_console_t con, int x, int y)"""
+
+ @staticmethod
+ def TCOD_console_get_default_background(con: Any, /) -> Any:
+ """TCOD_color_t TCOD_console_get_default_background(TCOD_Console *con)"""
+
+ @staticmethod
+ def TCOD_console_get_default_background_wrapper(con: Any, /) -> Any:
+ """colornum_t TCOD_console_get_default_background_wrapper(TCOD_console_t con)"""
+
+ @staticmethod
+ def TCOD_console_get_default_foreground(con: Any, /) -> Any:
+ """TCOD_color_t TCOD_console_get_default_foreground(TCOD_Console *con)"""
+
+ @staticmethod
+ def TCOD_console_get_default_foreground_wrapper(con: Any, /) -> Any:
+ """colornum_t TCOD_console_get_default_foreground_wrapper(TCOD_console_t con)"""
+
+ @staticmethod
+ def TCOD_console_get_fade() -> Any:
+ """uint8_t TCOD_console_get_fade(void)"""
+
+ @staticmethod
+ def TCOD_console_get_fading_color() -> Any:
+ """TCOD_color_t TCOD_console_get_fading_color(void)"""
+
+ @staticmethod
+ def TCOD_console_get_fading_color_wrapper() -> Any:
+ """colornum_t TCOD_console_get_fading_color_wrapper(void)"""
+
+ @staticmethod
+ def TCOD_console_get_height(con: Any, /) -> int:
+ """int TCOD_console_get_height(const TCOD_Console *con)"""
+
+ @staticmethod
+ def TCOD_console_get_height_rect(con: Any, x: int, y: int, w: int, h: int, fmt: Any, /, *__args: Any) -> int:
+ """int TCOD_console_get_height_rect(TCOD_Console *con, int x, int y, int w, int h, const char *fmt, ...)"""
+
+ @staticmethod
+ def TCOD_console_get_height_rect_fmt(con: Any, x: int, y: int, w: int, h: int, fmt: Any, /, *__args: Any) -> int:
+ """int TCOD_console_get_height_rect_fmt(TCOD_Console *con, int x, int y, int w, int h, const char *fmt, ...)"""
+
+ @staticmethod
+ def TCOD_console_get_height_rect_n(
+ console: Any, x: int, y: int, width: int, height: int, n: int, str: Any, /
+ ) -> int:
+ """int TCOD_console_get_height_rect_n(TCOD_Console *console, int x, int y, int width, int height, size_t n, const char *str)"""
+
+ @staticmethod
+ def TCOD_console_get_height_rect_utf(con: Any, x: int, y: int, w: int, h: int, fmt: Any, /, *__args: Any) -> int:
+ """int TCOD_console_get_height_rect_utf(TCOD_Console *con, int x, int y, int w, int h, const wchar_t *fmt, ...)"""
+
+ @staticmethod
+ def TCOD_console_get_height_rect_wn(width: int, n: int, str: Any, /) -> int:
+ """int TCOD_console_get_height_rect_wn(int width, size_t n, const char *str)"""
+
+ @staticmethod
+ def TCOD_console_get_width(con: Any, /) -> int:
+ """int TCOD_console_get_width(const TCOD_Console *con)"""
+
+ @staticmethod
+ def TCOD_console_has_mouse_focus() -> bool:
+ """bool TCOD_console_has_mouse_focus(void)"""
+
+ @staticmethod
+ def TCOD_console_hline(con: Any, x: int, y: int, l: int, flag: Any, /) -> None:
+ """void TCOD_console_hline(TCOD_Console *con, int x, int y, int l, TCOD_bkgnd_flag_t flag)"""
+
+ @staticmethod
+ def TCOD_console_init_root(w: int, h: int, title: Any, fullscreen: bool, renderer: Any, /) -> Any:
+ """TCOD_Error TCOD_console_init_root(int w, int h, const char *title, bool fullscreen, TCOD_renderer_t renderer)"""
+
+ @staticmethod
+ def TCOD_console_init_root_(w: int, h: int, title: Any, fullscreen: bool, renderer: Any, vsync: bool, /) -> Any:
+ """TCOD_Error TCOD_console_init_root_(int w, int h, const char *title, bool fullscreen, TCOD_renderer_t renderer, bool vsync)"""
+
+ @staticmethod
+ def TCOD_console_is_active() -> bool:
+ """bool TCOD_console_is_active(void)"""
+
+ @staticmethod
+ def TCOD_console_is_fullscreen() -> bool:
+ """bool TCOD_console_is_fullscreen(void)"""
+
+ @staticmethod
+ def TCOD_console_is_index_valid_(console: Any, x: int, y: int, /) -> bool:
+ """inline static bool TCOD_console_is_index_valid_(const TCOD_Console *console, int x, int y)"""
+
+ @staticmethod
+ def TCOD_console_is_key_pressed(key: Any, /) -> bool:
+ """bool TCOD_console_is_key_pressed(TCOD_keycode_t key)"""
+
+ @staticmethod
+ def TCOD_console_is_window_closed() -> bool:
+ """bool TCOD_console_is_window_closed(void)"""
+
+ @staticmethod
+ def TCOD_console_list_from_xp(filename: Any, /) -> Any:
+ """TCOD_list_t TCOD_console_list_from_xp(const char *filename)"""
+
+ @staticmethod
+ def TCOD_console_list_save_xp(console_list: Any, filename: Any, compress_level: int, /) -> bool:
+ """bool TCOD_console_list_save_xp(TCOD_list_t console_list, const char *filename, int compress_level)"""
+
+ @staticmethod
+ def TCOD_console_load_apf(con: Any, filename: Any, /) -> bool:
+ """bool TCOD_console_load_apf(TCOD_console_t con, const char *filename)"""
+
+ @staticmethod
+ def TCOD_console_load_asc(con: Any, filename: Any, /) -> bool:
+ """bool TCOD_console_load_asc(TCOD_console_t con, const char *filename)"""
+
+ @staticmethod
+ def TCOD_console_load_xp(con: Any, filename: Any, /) -> bool:
+ """bool TCOD_console_load_xp(TCOD_Console *con, const char *filename)"""
+
+ @staticmethod
+ def TCOD_console_map_ascii_code_to_font(asciiCode: int, fontCharX: int, fontCharY: int, /) -> None:
+ """void TCOD_console_map_ascii_code_to_font(int asciiCode, int fontCharX, int fontCharY)"""
+
+ @staticmethod
+ def TCOD_console_map_ascii_codes_to_font(asciiCode: int, nbCodes: int, fontCharX: int, fontCharY: int, /) -> None:
+ """void TCOD_console_map_ascii_codes_to_font(int asciiCode, int nbCodes, int fontCharX, int fontCharY)"""
+
+ @staticmethod
+ def TCOD_console_map_string_to_font(s: Any, fontCharX: int, fontCharY: int, /) -> None:
+ """void TCOD_console_map_string_to_font(const char *s, int fontCharX, int fontCharY)"""
+
+ @staticmethod
+ def TCOD_console_map_string_to_font_utf(s: Any, fontCharX: int, fontCharY: int, /) -> None:
+ """void TCOD_console_map_string_to_font_utf(const wchar_t *s, int fontCharX, int fontCharY)"""
+
+ @staticmethod
+ def TCOD_console_new(w: int, h: int, /) -> Any:
+ """TCOD_Console *TCOD_console_new(int w, int h)"""
+
+ @staticmethod
+ def TCOD_console_print(con: Any, x: int, y: int, fmt: Any, /, *__args: Any) -> None:
+ """void TCOD_console_print(TCOD_Console *con, int x, int y, const char *fmt, ...)"""
+
+ @staticmethod
+ def TCOD_console_print_double_frame(
+ con: Any, x: int, y: int, w: int, h: int, empty: bool, flag: Any, fmt: Any, /, *__args: Any
+ ) -> None:
+ """void TCOD_console_print_double_frame(TCOD_console_t con, int x, int y, int w, int h, bool empty, TCOD_bkgnd_flag_t flag, const char *fmt, ...)"""
+
+ @staticmethod
+ def TCOD_console_print_ex(con: Any, x: int, y: int, flag: Any, alignment: Any, fmt: Any, /, *__args: Any) -> None:
+ """void TCOD_console_print_ex(TCOD_Console *con, int x, int y, TCOD_bkgnd_flag_t flag, TCOD_alignment_t alignment, const char *fmt, ...)"""
+
+ @staticmethod
+ def TCOD_console_print_ex_utf(
+ con: Any, x: int, y: int, flag: Any, alignment: Any, fmt: Any, /, *__args: Any
+ ) -> None:
+ """void TCOD_console_print_ex_utf(TCOD_Console *con, int x, int y, TCOD_bkgnd_flag_t flag, TCOD_alignment_t alignment, const wchar_t *fmt, ...)"""
+
+ @staticmethod
+ def TCOD_console_print_frame(
+ con: Any, x: int, y: int, w: int, h: int, empty: bool, flag: Any, fmt: Any, /, *__args: Any
+ ) -> None:
+ """void TCOD_console_print_frame(TCOD_console_t con, int x, int y, int w, int h, bool empty, TCOD_bkgnd_flag_t flag, const char *fmt, ...)"""
+
+ @staticmethod
+ def TCOD_console_print_internal(
+ con: Any, x: int, y: int, w: int, h: int, flag: Any, align: Any, msg: Any, can_split: bool, count_only: bool, /
+ ) -> int:
+ """int TCOD_console_print_internal(TCOD_Console *con, int x, int y, int w, int h, TCOD_bkgnd_flag_t flag, TCOD_alignment_t align, char *msg, bool can_split, bool count_only)"""
+
+ @staticmethod
+ def TCOD_console_print_internal_utf(
+ con: Any,
+ x: int,
+ y: int,
+ rw: int,
+ rh: int,
+ flag: Any,
+ align: Any,
+ msg: Any,
+ can_split: bool,
+ count_only: bool,
+ /,
+ ) -> int:
+ """int TCOD_console_print_internal_utf(TCOD_console_t con, int x, int y, int rw, int rh, TCOD_bkgnd_flag_t flag, TCOD_alignment_t align, wchar_t *msg, bool can_split, bool count_only)"""
+
+ @staticmethod
+ def TCOD_console_print_rect(con: Any, x: int, y: int, w: int, h: int, fmt: Any, /, *__args: Any) -> int:
+ """int TCOD_console_print_rect(TCOD_Console *con, int x, int y, int w, int h, const char *fmt, ...)"""
+
+ @staticmethod
+ def TCOD_console_print_rect_ex(
+ con: Any, x: int, y: int, w: int, h: int, flag: Any, alignment: Any, fmt: Any, /, *__args: Any
+ ) -> int:
+ """int TCOD_console_print_rect_ex(TCOD_Console *con, int x, int y, int w, int h, TCOD_bkgnd_flag_t flag, TCOD_alignment_t alignment, const char *fmt, ...)"""
+
+ @staticmethod
+ def TCOD_console_print_rect_ex_utf(
+ con: Any, x: int, y: int, w: int, h: int, flag: Any, alignment: Any, fmt: Any, /, *__args: Any
+ ) -> int:
+ """int TCOD_console_print_rect_ex_utf(TCOD_Console *con, int x, int y, int w, int h, TCOD_bkgnd_flag_t flag, TCOD_alignment_t alignment, const wchar_t *fmt, ...)"""
+
+ @staticmethod
+ def TCOD_console_print_rect_utf(con: Any, x: int, y: int, w: int, h: int, fmt: Any, /, *__args: Any) -> int:
+ """int TCOD_console_print_rect_utf(TCOD_Console *con, int x, int y, int w, int h, const wchar_t *fmt, ...)"""
+
+ @staticmethod
+ def TCOD_console_print_return_string(
+ con: Any,
+ x: int,
+ y: int,
+ rw: int,
+ rh: int,
+ flag: Any,
+ align: Any,
+ msg: Any,
+ can_split: bool,
+ count_only: bool,
+ /,
+ ) -> Any:
+ """char *TCOD_console_print_return_string(TCOD_console_t con, int x, int y, int rw, int rh, TCOD_bkgnd_flag_t flag, TCOD_alignment_t align, char *msg, bool can_split, bool count_only)"""
+
+ @staticmethod
+ def TCOD_console_print_utf(con: Any, x: int, y: int, fmt: Any, /, *__args: Any) -> None:
+ """void TCOD_console_print_utf(TCOD_Console *con, int x, int y, const wchar_t *fmt, ...)"""
+
+ @staticmethod
+ def TCOD_console_printf(con: Any, x: int, y: int, fmt: Any, /, *__args: Any) -> Any:
+ """TCOD_Error TCOD_console_printf(TCOD_Console *con, int x, int y, const char *fmt, ...)"""
+
+ @staticmethod
+ def TCOD_console_printf_ex(con: Any, x: int, y: int, flag: Any, alignment: Any, fmt: Any, /, *__args: Any) -> Any:
+ """TCOD_Error TCOD_console_printf_ex(TCOD_Console *con, int x, int y, TCOD_bkgnd_flag_t flag, TCOD_alignment_t alignment, const char *fmt, ...)"""
+
+ @staticmethod
+ def TCOD_console_printf_frame(
+ con: Any, x: int, y: int, w: int, h: int, empty: int, flag: Any, fmt: Any, /, *__args: Any
+ ) -> Any:
+ """TCOD_Error TCOD_console_printf_frame(TCOD_Console *con, int x, int y, int w, int h, int empty, TCOD_bkgnd_flag_t flag, const char *fmt, ...)"""
+
+ @staticmethod
+ def TCOD_console_printf_rect(con: Any, x: int, y: int, w: int, h: int, fmt: Any, /, *__args: Any) -> int:
+ """int TCOD_console_printf_rect(TCOD_Console *con, int x, int y, int w, int h, const char *fmt, ...)"""
+
+ @staticmethod
+ def TCOD_console_printf_rect_ex(
+ con: Any, x: int, y: int, w: int, h: int, flag: Any, alignment: Any, fmt: Any, /, *__args: Any
+ ) -> int:
+ """int TCOD_console_printf_rect_ex(TCOD_Console *con, int x, int y, int w, int h, TCOD_bkgnd_flag_t flag, TCOD_alignment_t alignment, const char *fmt, ...)"""
+
+ @staticmethod
+ def TCOD_console_printn(
+ console: Any, x: int, y: int, n: int, str: Any, fg: Any, bg: Any, flag: Any, alignment: Any, /
+ ) -> Any:
+ """TCOD_Error TCOD_console_printn(TCOD_Console *console, int x, int y, size_t n, const char *str, const TCOD_ColorRGB *fg, const TCOD_ColorRGB *bg, TCOD_bkgnd_flag_t flag, TCOD_alignment_t alignment)"""
+
+ @staticmethod
+ def TCOD_console_printn_frame(
+ console: Any,
+ x: int,
+ y: int,
+ width: int,
+ height: int,
+ n: int,
+ title: Any,
+ fg: Any,
+ bg: Any,
+ flag: Any,
+ clear: bool,
+ /,
+ ) -> Any:
+ """TCOD_Error TCOD_console_printn_frame(TCOD_Console *console, int x, int y, int width, int height, size_t n, const char *title, const TCOD_ColorRGB *fg, const TCOD_ColorRGB *bg, TCOD_bkgnd_flag_t flag, bool clear)"""
+
+ @staticmethod
+ def TCOD_console_printn_rect(
+ console: Any,
+ x: int,
+ y: int,
+ width: int,
+ height: int,
+ n: int,
+ str: Any,
+ fg: Any,
+ bg: Any,
+ flag: Any,
+ alignment: Any,
+ /,
+ ) -> int:
+ """int TCOD_console_printn_rect(TCOD_Console *console, int x, int y, int width, int height, size_t n, const char *str, const TCOD_ColorRGB *fg, const TCOD_ColorRGB *bg, TCOD_bkgnd_flag_t flag, TCOD_alignment_t alignment)"""
+
+ @staticmethod
+ def TCOD_console_put_char(con: Any, x: int, y: int, c: int, flag: Any, /) -> None:
+ """void TCOD_console_put_char(TCOD_Console *con, int x, int y, int c, TCOD_bkgnd_flag_t flag)"""
+
+ @staticmethod
+ def TCOD_console_put_char_ex(con: Any, x: int, y: int, c: int, fore: Any, back: Any, /) -> None:
+ """void TCOD_console_put_char_ex(TCOD_Console *con, int x, int y, int c, TCOD_color_t fore, TCOD_color_t back)"""
+
+ @staticmethod
+ def TCOD_console_put_char_ex_wrapper(con: Any, x: int, y: int, c: int, fore: Any, back: Any, /) -> None:
+ """void TCOD_console_put_char_ex_wrapper(TCOD_console_t con, int x, int y, int c, colornum_t fore, colornum_t back)"""
+
+ @staticmethod
+ def TCOD_console_put_rgb(console: Any, x: int, y: int, ch: int, fg: Any, bg: Any, flag: Any, /) -> None:
+ """void TCOD_console_put_rgb(TCOD_Console *console, int x, int y, int ch, const TCOD_color_t *fg, const TCOD_color_t *bg, TCOD_bkgnd_flag_t flag)"""
+
+ @staticmethod
+ def TCOD_console_rect(con: Any, x: int, y: int, rw: int, rh: int, clear: bool, flag: Any, /) -> None:
+ """void TCOD_console_rect(TCOD_Console *con, int x, int y, int rw, int rh, bool clear, TCOD_bkgnd_flag_t flag)"""
+
+ @staticmethod
+ def TCOD_console_resize_(console: Any, width: int, height: int, /) -> None:
+ """void TCOD_console_resize_(TCOD_Console *console, int width, int height)"""
+
+ @staticmethod
+ def TCOD_console_save_apf(con: Any, filename: Any, /) -> bool:
+ """bool TCOD_console_save_apf(TCOD_console_t con, const char *filename)"""
+
+ @staticmethod
+ def TCOD_console_save_asc(con: Any, filename: Any, /) -> bool:
+ """bool TCOD_console_save_asc(TCOD_console_t con, const char *filename)"""
+
+ @staticmethod
+ def TCOD_console_save_xp(con: Any, filename: Any, compress_level: int, /) -> bool:
+ """bool TCOD_console_save_xp(const TCOD_Console *con, const char *filename, int compress_level)"""
+
+ @staticmethod
+ def TCOD_console_set_alignment(con: Any, alignment: Any, /) -> None:
+ """void TCOD_console_set_alignment(TCOD_Console *con, TCOD_alignment_t alignment)"""
+
+ @staticmethod
+ def TCOD_console_set_background_flag(con: Any, flag: Any, /) -> None:
+ """void TCOD_console_set_background_flag(TCOD_Console *con, TCOD_bkgnd_flag_t flag)"""
+
+ @staticmethod
+ def TCOD_console_set_char(con: Any, x: int, y: int, c: int, /) -> None:
+ """void TCOD_console_set_char(TCOD_Console *con, int x, int y, int c)"""
+
+ @staticmethod
+ def TCOD_console_set_char_background(con: Any, x: int, y: int, col: Any, flag: Any, /) -> None:
+ """void TCOD_console_set_char_background(TCOD_Console *con, int x, int y, TCOD_color_t col, TCOD_bkgnd_flag_t flag)"""
+
+ @staticmethod
+ def TCOD_console_set_char_background_wrapper(con: Any, x: int, y: int, col: Any, flag: Any, /) -> None:
+ """void TCOD_console_set_char_background_wrapper(TCOD_console_t con, int x, int y, colornum_t col, TCOD_bkgnd_flag_t flag)"""
+
+ @staticmethod
+ def TCOD_console_set_char_foreground(con: Any, x: int, y: int, col: Any, /) -> None:
+ """void TCOD_console_set_char_foreground(TCOD_Console *con, int x, int y, TCOD_color_t col)"""
+
+ @staticmethod
+ def TCOD_console_set_char_foreground_wrapper(con: Any, x: int, y: int, col: Any, /) -> None:
+ """void TCOD_console_set_char_foreground_wrapper(TCOD_console_t con, int x, int y, colornum_t col)"""
+
+ @staticmethod
+ def TCOD_console_set_color_control(con: Any, fore: Any, back: Any, /) -> None:
+ """void TCOD_console_set_color_control(TCOD_colctrl_t con, TCOD_color_t fore, TCOD_color_t back)"""
+
+ @staticmethod
+ def TCOD_console_set_color_control_wrapper(con: Any, fore: Any, back: Any, /) -> None:
+ """void TCOD_console_set_color_control_wrapper(TCOD_colctrl_t con, colornum_t fore, colornum_t back)"""
+
+ @staticmethod
+ def TCOD_console_set_custom_font(fontFile: Any, flags: int, nb_char_horiz: int, nb_char_vertic: int, /) -> Any:
+ """TCOD_Error TCOD_console_set_custom_font(const char *fontFile, int flags, int nb_char_horiz, int nb_char_vertic)"""
+
+ @staticmethod
+ def TCOD_console_set_default_background(con: Any, col: Any, /) -> None:
+ """void TCOD_console_set_default_background(TCOD_Console *con, TCOD_color_t col)"""
+
+ @staticmethod
+ def TCOD_console_set_default_background_wrapper(con: Any, col: Any, /) -> None:
+ """void TCOD_console_set_default_background_wrapper(TCOD_console_t con, colornum_t col)"""
+
+ @staticmethod
+ def TCOD_console_set_default_foreground(con: Any, col: Any, /) -> None:
+ """void TCOD_console_set_default_foreground(TCOD_Console *con, TCOD_color_t col)"""
+
+ @staticmethod
+ def TCOD_console_set_default_foreground_wrapper(con: Any, col: Any, /) -> None:
+ """void TCOD_console_set_default_foreground_wrapper(TCOD_console_t con, colornum_t col)"""
+
+ @staticmethod
+ def TCOD_console_set_dirty(x: int, y: int, w: int, h: int, /) -> None:
+ """void TCOD_console_set_dirty(int x, int y, int w, int h)"""
+
+ @staticmethod
+ def TCOD_console_set_fade(val: Any, fade_color: Any, /) -> None:
+ """void TCOD_console_set_fade(uint8_t val, TCOD_color_t fade_color)"""
+
+ @staticmethod
+ def TCOD_console_set_fade_wrapper(val: Any, fade: Any, /) -> None:
+ """void TCOD_console_set_fade_wrapper(uint8_t val, colornum_t fade)"""
+
+ @staticmethod
+ def TCOD_console_set_fullscreen(fullscreen: bool, /) -> None:
+ """void TCOD_console_set_fullscreen(bool fullscreen)"""
+
+ @staticmethod
+ def TCOD_console_set_key_color(con: Any, col: Any, /) -> None:
+ """void TCOD_console_set_key_color(TCOD_Console *con, TCOD_color_t col)"""
+
+ @staticmethod
+ def TCOD_console_set_key_color_wrapper(con: Any, c: Any, /) -> None:
+ """void TCOD_console_set_key_color_wrapper(TCOD_console_t con, colornum_t c)"""
+
+ @staticmethod
+ def TCOD_console_set_keyboard_repeat(initial_delay: int, interval: int, /) -> None:
+ """void TCOD_console_set_keyboard_repeat(int initial_delay, int interval)"""
+
+ @staticmethod
+ def TCOD_console_set_window_title(title: Any, /) -> None:
+ """void TCOD_console_set_window_title(const char *title)"""
+
+ @staticmethod
+ def TCOD_console_stringLength(s: Any, /) -> int:
+ """int TCOD_console_stringLength(const unsigned char *s)"""
+
+ @staticmethod
+ def TCOD_console_validate_(console: Any, /) -> Any:
+ """inline static TCOD_Console *TCOD_console_validate_(const TCOD_Console *console)"""
+
+ @staticmethod
+ def TCOD_console_vline(con: Any, x: int, y: int, l: int, flag: Any, /) -> None:
+ """void TCOD_console_vline(TCOD_Console *con, int x, int y, int l, TCOD_bkgnd_flag_t flag)"""
+
+ @staticmethod
+ def TCOD_console_wait_for_keypress(flush: bool, /) -> Any:
+ """TCOD_key_t TCOD_console_wait_for_keypress(bool flush)"""
+
+ @staticmethod
+ def TCOD_console_wait_for_keypress_wrapper(holder: Any, flush: bool, /) -> None:
+ """void TCOD_console_wait_for_keypress_wrapper(TCOD_key_t *holder, bool flush)"""
+
+ @staticmethod
+ def TCOD_context_change_tileset(self: Any, tileset: Any, /) -> Any:
+ """TCOD_Error TCOD_context_change_tileset(struct TCOD_Context *self, TCOD_Tileset *tileset)"""
+
+ @staticmethod
+ def TCOD_context_convert_event_coordinates(context: Any, event: Any, /) -> Any:
+ """TCOD_Error TCOD_context_convert_event_coordinates(struct TCOD_Context *context, union SDL_Event *event)"""
+
+ @staticmethod
+ def TCOD_context_delete(renderer: Any, /) -> None:
+ """void TCOD_context_delete(struct TCOD_Context *renderer)"""
+
+ @staticmethod
+ def TCOD_context_get_renderer_type(context: Any, /) -> int:
+ """int TCOD_context_get_renderer_type(struct TCOD_Context *context)"""
+
+ @staticmethod
+ def TCOD_context_get_sdl_renderer(context: Any, /) -> Any:
+ """struct SDL_Renderer *TCOD_context_get_sdl_renderer(struct TCOD_Context *context)"""
+
+ @staticmethod
+ def TCOD_context_get_sdl_window(context: Any, /) -> Any:
+ """struct SDL_Window *TCOD_context_get_sdl_window(struct TCOD_Context *context)"""
+
+ @staticmethod
+ def TCOD_context_new(params: Any, out: Any, /) -> Any:
+ """TCOD_Error TCOD_context_new(const TCOD_ContextParams *params, TCOD_Context **out)"""
+
+ @staticmethod
+ def TCOD_context_new_() -> Any:
+ """struct TCOD_Context *TCOD_context_new_(void)"""
+
+ @staticmethod
+ def TCOD_context_present(context: Any, console: Any, viewport: Any, /) -> Any:
+ """TCOD_Error TCOD_context_present(struct TCOD_Context *context, const struct TCOD_Console *console, const struct TCOD_ViewportOptions *viewport)"""
+
+ @staticmethod
+ def TCOD_context_recommended_console_size(context: Any, magnification: float, columns: Any, rows: Any, /) -> Any:
+ """TCOD_Error TCOD_context_recommended_console_size(struct TCOD_Context *context, float magnification, int *columns, int *rows)"""
+
+ @staticmethod
+ def TCOD_context_save_screenshot(context: Any, filename: Any, /) -> Any:
+ """TCOD_Error TCOD_context_save_screenshot(struct TCOD_Context *context, const char *filename)"""
+
+ @staticmethod
+ def TCOD_context_screen_capture(context: Any, out_pixels: Any, width: Any, height: Any, /) -> Any:
+ """TCOD_Error TCOD_context_screen_capture(struct TCOD_Context *context, TCOD_ColorRGBA *out_pixels, int *width, int *height)"""
+
+ @staticmethod
+ def TCOD_context_screen_capture_alloc(context: Any, width: Any, height: Any, /) -> Any:
+ """TCOD_ColorRGBA *TCOD_context_screen_capture_alloc(struct TCOD_Context *context, int *width, int *height)"""
+
+ @staticmethod
+ def TCOD_context_screen_pixel_to_tile_d(context: Any, x: Any, y: Any, /) -> Any:
+ """TCOD_Error TCOD_context_screen_pixel_to_tile_d(struct TCOD_Context *context, double *x, double *y)"""
+
+ @staticmethod
+ def TCOD_context_screen_pixel_to_tile_i(context: Any, x: Any, y: Any, /) -> Any:
+ """TCOD_Error TCOD_context_screen_pixel_to_tile_i(struct TCOD_Context *context, int *x, int *y)"""
+
+ @staticmethod
+ def TCOD_context_set_mouse_transform(context: Any, transform: Any, /) -> Any:
+ """TCOD_Error TCOD_context_set_mouse_transform(struct TCOD_Context *context, const TCOD_MouseTransform *transform)"""
+
+ @staticmethod
+ def TCOD_dijkstra_compute(dijkstra: Any, root_x: int, root_y: int, /) -> None:
+ """void TCOD_dijkstra_compute(TCOD_Dijkstra *dijkstra, int root_x, int root_y)"""
+
+ @staticmethod
+ def TCOD_dijkstra_delete(dijkstra: Any, /) -> None:
+ """void TCOD_dijkstra_delete(TCOD_Dijkstra *dijkstra)"""
+
+ @staticmethod
+ def TCOD_dijkstra_get(path: Any, index: int, x: Any, y: Any, /) -> None:
+ """void TCOD_dijkstra_get(TCOD_Dijkstra *path, int index, int *x, int *y)"""
+
+ @staticmethod
+ def TCOD_dijkstra_get_distance(dijkstra: Any, x: int, y: int, /) -> float:
+ """float TCOD_dijkstra_get_distance(TCOD_Dijkstra *dijkstra, int x, int y)"""
+
+ @staticmethod
+ def TCOD_dijkstra_is_empty(path: Any, /) -> bool:
+ """bool TCOD_dijkstra_is_empty(TCOD_Dijkstra *path)"""
+
+ @staticmethod
+ def TCOD_dijkstra_new(map: Any, diagonalCost: float, /) -> Any:
+ """TCOD_Dijkstra *TCOD_dijkstra_new(TCOD_Map *map, float diagonalCost)"""
+
+ @staticmethod
+ def TCOD_dijkstra_new_using_function(
+ map_width: int, map_height: int, func: Any, user_data: Any, diagonalCost: float, /
+ ) -> Any:
+ """TCOD_Dijkstra *TCOD_dijkstra_new_using_function(int map_width, int map_height, TCOD_path_func_t func, void *user_data, float diagonalCost)"""
+
+ @staticmethod
+ def TCOD_dijkstra_path_set(dijkstra: Any, x: int, y: int, /) -> bool:
+ """bool TCOD_dijkstra_path_set(TCOD_Dijkstra *dijkstra, int x, int y)"""
+
+ @staticmethod
+ def TCOD_dijkstra_path_walk(dijkstra: Any, x: Any, y: Any, /) -> bool:
+ """bool TCOD_dijkstra_path_walk(TCOD_Dijkstra *dijkstra, int *x, int *y)"""
+
+ @staticmethod
+ def TCOD_dijkstra_reverse(path: Any, /) -> None:
+ """void TCOD_dijkstra_reverse(TCOD_Dijkstra *path)"""
+
+ @staticmethod
+ def TCOD_dijkstra_size(path: Any, /) -> int:
+ """int TCOD_dijkstra_size(TCOD_Dijkstra *path)"""
+
+ @staticmethod
+ def TCOD_frontier_clear(frontier: Any, /) -> Any:
+ """TCOD_Error TCOD_frontier_clear(struct TCOD_Frontier *frontier)"""
+
+ @staticmethod
+ def TCOD_frontier_delete(frontier: Any, /) -> None:
+ """void TCOD_frontier_delete(struct TCOD_Frontier *frontier)"""
+
+ @staticmethod
+ def TCOD_frontier_new(ndim: int, /) -> Any:
+ """struct TCOD_Frontier *TCOD_frontier_new(int ndim)"""
+
+ @staticmethod
+ def TCOD_frontier_pop(frontier: Any, /) -> Any:
+ """TCOD_Error TCOD_frontier_pop(struct TCOD_Frontier *frontier)"""
+
+ @staticmethod
+ def TCOD_frontier_push(frontier: Any, index: Any, dist: int, heuristic: int, /) -> Any:
+ """TCOD_Error TCOD_frontier_push(struct TCOD_Frontier *frontier, const int *index, int dist, int heuristic)"""
+
+ @staticmethod
+ def TCOD_frontier_size(frontier: Any, /) -> int:
+ """int TCOD_frontier_size(const struct TCOD_Frontier *frontier)"""
+
+ @staticmethod
+ def TCOD_get_default_tileset() -> Any:
+ """TCOD_Tileset *TCOD_get_default_tileset(void)"""
+
+ @staticmethod
+ def TCOD_get_error() -> Any:
+ """const char *TCOD_get_error(void)"""
+
+ @staticmethod
+ def TCOD_get_function_address(library: Any, function_name: Any, /) -> Any:
+ """void *TCOD_get_function_address(TCOD_library_t library, const char *function_name)"""
+
+ @staticmethod
+ def TCOD_heap_clear(heap: Any, /) -> None:
+ """void TCOD_heap_clear(struct TCOD_Heap *heap)"""
+
+ @staticmethod
+ def TCOD_heap_init(heap: Any, data_size: int, /) -> int:
+ """int TCOD_heap_init(struct TCOD_Heap *heap, size_t data_size)"""
+
+ @staticmethod
+ def TCOD_heap_uninit(heap: Any, /) -> None:
+ """void TCOD_heap_uninit(struct TCOD_Heap *heap)"""
+
+ @staticmethod
+ def TCOD_heightmap_add(hm: Any, value: float, /) -> None:
+ """void TCOD_heightmap_add(TCOD_heightmap_t *hm, float value)"""
+
+ @staticmethod
+ def TCOD_heightmap_add_fbm(
+ hm: Any,
+ noise: Any,
+ mul_x: float,
+ mul_y: float,
+ add_x: float,
+ add_y: float,
+ octaves: float,
+ delta: float,
+ scale: float,
+ /,
+ ) -> None:
+ """void TCOD_heightmap_add_fbm(TCOD_heightmap_t *hm, TCOD_noise_t noise, float mul_x, float mul_y, float add_x, float add_y, float octaves, float delta, float scale)"""
+
+ @staticmethod
+ def TCOD_heightmap_add_hill(hm: Any, hx: float, hy: float, h_radius: float, h_height: float, /) -> None:
+ """void TCOD_heightmap_add_hill(TCOD_heightmap_t *hm, float hx, float hy, float h_radius, float h_height)"""
+
+ @staticmethod
+ def TCOD_heightmap_add_hm(hm1: Any, hm2: Any, out: Any, /) -> None:
+ """void TCOD_heightmap_add_hm(const TCOD_heightmap_t *hm1, const TCOD_heightmap_t *hm2, TCOD_heightmap_t *out)"""
+
+ @staticmethod
+ def TCOD_heightmap_add_voronoi(hm: Any, nbPoints: int, nbCoef: int, coef: Any, rnd: Any, /) -> None:
+ """void TCOD_heightmap_add_voronoi(TCOD_heightmap_t *hm, int nbPoints, int nbCoef, const float *coef, TCOD_Random *rnd)"""
+
+ @staticmethod
+ def TCOD_heightmap_clamp(hm: Any, min: float, max: float, /) -> None:
+ """void TCOD_heightmap_clamp(TCOD_heightmap_t *hm, float min, float max)"""
+
+ @staticmethod
+ def TCOD_heightmap_clear(hm: Any, /) -> None:
+ """void TCOD_heightmap_clear(TCOD_heightmap_t *hm)"""
+
+ @staticmethod
+ def TCOD_heightmap_copy(hm_source: Any, hm_dest: Any, /) -> None:
+ """void TCOD_heightmap_copy(const TCOD_heightmap_t *hm_source, TCOD_heightmap_t *hm_dest)"""
+
+ @staticmethod
+ def TCOD_heightmap_count_cells(hm: Any, min: float, max: float, /) -> int:
+ """int TCOD_heightmap_count_cells(const TCOD_heightmap_t *hm, float min, float max)"""
+
+ @staticmethod
+ def TCOD_heightmap_delete(hm: Any, /) -> None:
+ """void TCOD_heightmap_delete(TCOD_heightmap_t *hm)"""
+
+ @staticmethod
+ def TCOD_heightmap_dig_bezier(
+ hm: Any, px: Any, py: Any, startRadius: float, startDepth: float, endRadius: float, endDepth: float, /
+ ) -> None:
+ """void TCOD_heightmap_dig_bezier(TCOD_heightmap_t *hm, int px[4], int py[4], float startRadius, float startDepth, float endRadius, float endDepth)"""
+
+ @staticmethod
+ def TCOD_heightmap_dig_hill(hm: Any, hx: float, hy: float, h_radius: float, h_height: float, /) -> None:
+ """void TCOD_heightmap_dig_hill(TCOD_heightmap_t *hm, float hx, float hy, float h_radius, float h_height)"""
+
+ @staticmethod
+ def TCOD_heightmap_get_interpolated_value(hm: Any, x: float, y: float, /) -> float:
+ """float TCOD_heightmap_get_interpolated_value(const TCOD_heightmap_t *hm, float x, float y)"""
+
+ @staticmethod
+ def TCOD_heightmap_get_minmax(hm: Any, min: Any, max: Any, /) -> None:
+ """void TCOD_heightmap_get_minmax(const TCOD_heightmap_t *hm, float *min, float *max)"""
+
+ @staticmethod
+ def TCOD_heightmap_get_normal(hm: Any, x: float, y: float, n: Any, waterLevel: float, /) -> None:
+ """void TCOD_heightmap_get_normal(const TCOD_heightmap_t *hm, float x, float y, float n[3], float waterLevel)"""
+
+ @staticmethod
+ def TCOD_heightmap_get_slope(hm: Any, x: int, y: int, /) -> float:
+ """float TCOD_heightmap_get_slope(const TCOD_heightmap_t *hm, int x, int y)"""
+
+ @staticmethod
+ def TCOD_heightmap_get_value(hm: Any, x: int, y: int, /) -> float:
+ """float TCOD_heightmap_get_value(const TCOD_heightmap_t *hm, int x, int y)"""
+
+ @staticmethod
+ def TCOD_heightmap_has_land_on_border(hm: Any, waterLevel: float, /) -> bool:
+ """bool TCOD_heightmap_has_land_on_border(const TCOD_heightmap_t *hm, float waterLevel)"""
+
+ @staticmethod
+ def TCOD_heightmap_islandify(hm: Any, seaLevel: float, rnd: Any, /) -> None:
+ """void TCOD_heightmap_islandify(TCOD_heightmap_t *hm, float seaLevel, TCOD_Random *rnd)"""
+
+ @staticmethod
+ def TCOD_heightmap_kernel_transform(
+ hm: Any, kernel_size: int, dx: Any, dy: Any, weight: Any, minLevel: float, maxLevel: float, /
+ ) -> None:
+ """void TCOD_heightmap_kernel_transform(TCOD_heightmap_t *hm, int kernel_size, const int *dx, const int *dy, const float *weight, float minLevel, float maxLevel)"""
+
+ @staticmethod
+ def TCOD_heightmap_lerp_hm(hm1: Any, hm2: Any, out: Any, coef: float, /) -> None:
+ """void TCOD_heightmap_lerp_hm(const TCOD_heightmap_t *hm1, const TCOD_heightmap_t *hm2, TCOD_heightmap_t *out, float coef)"""
+
+ @staticmethod
+ def TCOD_heightmap_mid_point_displacement(hm: Any, rnd: Any, roughness: float, /) -> None:
+ """void TCOD_heightmap_mid_point_displacement(TCOD_heightmap_t *hm, TCOD_Random *rnd, float roughness)"""
+
+ @staticmethod
+ def TCOD_heightmap_multiply_hm(hm1: Any, hm2: Any, out: Any, /) -> None:
+ """void TCOD_heightmap_multiply_hm(const TCOD_heightmap_t *hm1, const TCOD_heightmap_t *hm2, TCOD_heightmap_t *out)"""
+
+ @staticmethod
+ def TCOD_heightmap_new(w: int, h: int, /) -> Any:
+ """TCOD_heightmap_t *TCOD_heightmap_new(int w, int h)"""
+
+ @staticmethod
+ def TCOD_heightmap_normalize(hm: Any, min: float, max: float, /) -> None:
+ """void TCOD_heightmap_normalize(TCOD_heightmap_t *hm, float min, float max)"""
+
+ @staticmethod
+ def TCOD_heightmap_rain_erosion(
+ hm: Any, nbDrops: int, erosionCoef: float, sedimentationCoef: float, rnd: Any, /
+ ) -> None:
+ """void TCOD_heightmap_rain_erosion(TCOD_heightmap_t *hm, int nbDrops, float erosionCoef, float sedimentationCoef, TCOD_Random *rnd)"""
+
+ @staticmethod
+ def TCOD_heightmap_scale(hm: Any, value: float, /) -> None:
+ """void TCOD_heightmap_scale(TCOD_heightmap_t *hm, float value)"""
+
+ @staticmethod
+ def TCOD_heightmap_scale_fbm(
+ hm: Any,
+ noise: Any,
+ mul_x: float,
+ mul_y: float,
+ add_x: float,
+ add_y: float,
+ octaves: float,
+ delta: float,
+ scale: float,
+ /,
+ ) -> None:
+ """void TCOD_heightmap_scale_fbm(TCOD_heightmap_t *hm, TCOD_noise_t noise, float mul_x, float mul_y, float add_x, float add_y, float octaves, float delta, float scale)"""
+
+ @staticmethod
+ def TCOD_heightmap_set_value(hm: Any, x: int, y: int, value: float, /) -> None:
+ """void TCOD_heightmap_set_value(TCOD_heightmap_t *hm, int x, int y, float value)"""
+
+ @staticmethod
+ def TCOD_image_blit(
+ image: Any, console: Any, x: float, y: float, bkgnd_flag: Any, scale_x: float, scale_y: float, angle: float, /
+ ) -> None:
+ """void TCOD_image_blit(TCOD_Image *image, TCOD_console_t console, float x, float y, TCOD_bkgnd_flag_t bkgnd_flag, float scale_x, float scale_y, float angle)"""
+
+ @staticmethod
+ def TCOD_image_blit_2x(image: Any, dest: Any, dx: int, dy: int, sx: int, sy: int, w: int, h: int, /) -> None:
+ """void TCOD_image_blit_2x(const TCOD_Image *image, TCOD_Console *dest, int dx, int dy, int sx, int sy, int w, int h)"""
+
+ @staticmethod
+ def TCOD_image_blit_rect(image: Any, console: Any, x: int, y: int, w: int, h: int, bkgnd_flag: Any, /) -> None:
+ """void TCOD_image_blit_rect(TCOD_Image *image, TCOD_console_t console, int x, int y, int w, int h, TCOD_bkgnd_flag_t bkgnd_flag)"""
+
+ @staticmethod
+ def TCOD_image_clear(image: Any, color: Any, /) -> None:
+ """void TCOD_image_clear(TCOD_Image *image, TCOD_color_t color)"""
+
+ @staticmethod
+ def TCOD_image_clear_wrapper(image: Any, color: Any, /) -> None:
+ """void TCOD_image_clear_wrapper(TCOD_image_t image, colornum_t color)"""
+
+ @staticmethod
+ def TCOD_image_delete(image: Any, /) -> None:
+ """void TCOD_image_delete(TCOD_Image *image)"""
+
+ @staticmethod
+ def TCOD_image_from_console(console: Any, /) -> Any:
+ """TCOD_Image *TCOD_image_from_console(const TCOD_Console *console)"""
+
+ @staticmethod
+ def TCOD_image_get_alpha(image: Any, x: int, y: int, /) -> int:
+ """int TCOD_image_get_alpha(const TCOD_Image *image, int x, int y)"""
+
+ @staticmethod
+ def TCOD_image_get_mipmap_pixel(image: Any, x0: float, y0: float, x1: float, y1: float, /) -> Any:
+ """TCOD_color_t TCOD_image_get_mipmap_pixel(TCOD_Image *image, float x0, float y0, float x1, float y1)"""
+
+ @staticmethod
+ def TCOD_image_get_mipmap_pixel_wrapper(image: Any, x0: float, y0: float, x1: float, y1: float, /) -> Any:
+ """colornum_t TCOD_image_get_mipmap_pixel_wrapper(TCOD_image_t image, float x0, float y0, float x1, float y1)"""
+
+ @staticmethod
+ def TCOD_image_get_pixel(image: Any, x: int, y: int, /) -> Any:
+ """TCOD_color_t TCOD_image_get_pixel(const TCOD_Image *image, int x, int y)"""
+
+ @staticmethod
+ def TCOD_image_get_pixel_wrapper(image: Any, x: int, y: int, /) -> Any:
+ """colornum_t TCOD_image_get_pixel_wrapper(TCOD_image_t image, int x, int y)"""
+
+ @staticmethod
+ def TCOD_image_get_size(image: Any, w: Any, h: Any, /) -> None:
+ """void TCOD_image_get_size(const TCOD_Image *image, int *w, int *h)"""
+
+ @staticmethod
+ def TCOD_image_hflip(image: Any, /) -> None:
+ """void TCOD_image_hflip(TCOD_Image *image)"""
+
+ @staticmethod
+ def TCOD_image_invert(image: Any, /) -> None:
+ """void TCOD_image_invert(TCOD_Image *image)"""
+
+ @staticmethod
+ def TCOD_image_is_pixel_transparent(image: Any, x: int, y: int, /) -> bool:
+ """bool TCOD_image_is_pixel_transparent(const TCOD_Image *image, int x, int y)"""
+
+ @staticmethod
+ def TCOD_image_load(filename: Any, /) -> Any:
+ """TCOD_Image *TCOD_image_load(const char *filename)"""
+
+ @staticmethod
+ def TCOD_image_new(width: int, height: int, /) -> Any:
+ """TCOD_Image *TCOD_image_new(int width, int height)"""
+
+ @staticmethod
+ def TCOD_image_put_pixel(image: Any, x: int, y: int, col: Any, /) -> None:
+ """void TCOD_image_put_pixel(TCOD_Image *image, int x, int y, TCOD_color_t col)"""
+
+ @staticmethod
+ def TCOD_image_put_pixel_wrapper(image: Any, x: int, y: int, col: Any, /) -> None:
+ """void TCOD_image_put_pixel_wrapper(TCOD_image_t image, int x, int y, colornum_t col)"""
+
+ @staticmethod
+ def TCOD_image_refresh_console(image: Any, console: Any, /) -> None:
+ """void TCOD_image_refresh_console(TCOD_Image *image, const TCOD_Console *console)"""
+
+ @staticmethod
+ def TCOD_image_rotate90(image: Any, numRotations: int, /) -> None:
+ """void TCOD_image_rotate90(TCOD_Image *image, int numRotations)"""
+
+ @staticmethod
+ def TCOD_image_save(image: Any, filename: Any, /) -> Any:
+ """TCOD_Error TCOD_image_save(const TCOD_Image *image, const char *filename)"""
+
+ @staticmethod
+ def TCOD_image_scale(image: Any, new_w: int, new_h: int, /) -> None:
+ """void TCOD_image_scale(TCOD_Image *image, int new_w, int new_h)"""
+
+ @staticmethod
+ def TCOD_image_set_key_color(image: Any, key_color: Any, /) -> None:
+ """void TCOD_image_set_key_color(TCOD_Image *image, TCOD_color_t key_color)"""
+
+ @staticmethod
+ def TCOD_image_set_key_color_wrapper(image: Any, key_color: Any, /) -> None:
+ """void TCOD_image_set_key_color_wrapper(TCOD_image_t image, colornum_t key_color)"""
+
+ @staticmethod
+ def TCOD_image_vflip(image: Any, /) -> None:
+ """void TCOD_image_vflip(TCOD_Image *image)"""
+
+ @staticmethod
+ def TCOD_lex_delete(lex: Any, /) -> None:
+ """void TCOD_lex_delete(TCOD_lex_t *lex)"""
+
+ @staticmethod
+ def TCOD_lex_expect_token_type(lex: Any, token_type: int, /) -> bool:
+ """bool TCOD_lex_expect_token_type(TCOD_lex_t *lex, int token_type)"""
+
+ @staticmethod
+ def TCOD_lex_expect_token_value(lex: Any, token_type: int, token_value: Any, /) -> bool:
+ """bool TCOD_lex_expect_token_value(TCOD_lex_t *lex, int token_type, const char *token_value)"""
+
+ @staticmethod
+ def TCOD_lex_get_last_javadoc(lex: Any, /) -> Any:
+ """char *TCOD_lex_get_last_javadoc(TCOD_lex_t *lex)"""
+
+ @staticmethod
+ def TCOD_lex_get_token_name(token_type: int, /) -> Any:
+ """const char *TCOD_lex_get_token_name(int token_type)"""
+
+ @staticmethod
+ def TCOD_lex_hextoint(c: Any, /) -> int:
+ """int TCOD_lex_hextoint(char c)"""
+
+ @staticmethod
+ def TCOD_lex_new(
+ symbols: Any,
+ keywords: Any,
+ simpleComment: Any,
+ commentStart: Any,
+ commentStop: Any,
+ javadocCommentStart: Any,
+ stringDelim: Any,
+ flags: int,
+ /,
+ ) -> Any:
+ """TCOD_lex_t *TCOD_lex_new(const char * const *symbols, const char * const *keywords, const char *simpleComment, const char *commentStart, const char *commentStop, const char *javadocCommentStart, const char *stringDelim, int flags)"""
+
+ @staticmethod
+ def TCOD_lex_new_intern() -> Any:
+ """TCOD_lex_t *TCOD_lex_new_intern(void)"""
+
+ @staticmethod
+ def TCOD_lex_parse(lex: Any, /) -> int:
+ """int TCOD_lex_parse(TCOD_lex_t *lex)"""
+
+ @staticmethod
+ def TCOD_lex_parse_until_token_type(lex: Any, token_type: int, /) -> int:
+ """int TCOD_lex_parse_until_token_type(TCOD_lex_t *lex, int token_type)"""
+
+ @staticmethod
+ def TCOD_lex_parse_until_token_value(lex: Any, token_value: Any, /) -> int:
+ """int TCOD_lex_parse_until_token_value(TCOD_lex_t *lex, const char *token_value)"""
+
+ @staticmethod
+ def TCOD_lex_restore(lex: Any, savepoint: Any, /) -> None:
+ """void TCOD_lex_restore(TCOD_lex_t *lex, TCOD_lex_t *savepoint)"""
+
+ @staticmethod
+ def TCOD_lex_savepoint(lex: Any, savepoint: Any, /) -> None:
+ """void TCOD_lex_savepoint(TCOD_lex_t *lex, TCOD_lex_t *savepoint)"""
+
+ @staticmethod
+ def TCOD_lex_set_data_buffer(lex: Any, dat: Any, /) -> None:
+ """void TCOD_lex_set_data_buffer(TCOD_lex_t *lex, char *dat)"""
+
+ @staticmethod
+ def TCOD_lex_set_data_file(lex: Any, filename: Any, /) -> bool:
+ """bool TCOD_lex_set_data_file(TCOD_lex_t *lex, const char *filename)"""
+
+ @staticmethod
+ def TCOD_line(xFrom: int, yFrom: int, xTo: int, yTo: int, listener: Any, /) -> bool:
+ """bool TCOD_line(int xFrom, int yFrom, int xTo, int yTo, TCOD_line_listener_t listener)"""
+
+ @staticmethod
+ def TCOD_line_init(xFrom: int, yFrom: int, xTo: int, yTo: int, /) -> None:
+ """void TCOD_line_init(int xFrom, int yFrom, int xTo, int yTo)"""
+
+ @staticmethod
+ def TCOD_line_init_mt(xFrom: int, yFrom: int, xTo: int, yTo: int, data: Any, /) -> None:
+ """void TCOD_line_init_mt(int xFrom, int yFrom, int xTo, int yTo, TCOD_bresenham_data_t *data)"""
+
+ @staticmethod
+ def TCOD_line_mt(xFrom: int, yFrom: int, xTo: int, yTo: int, listener: Any, data: Any, /) -> bool:
+ """bool TCOD_line_mt(int xFrom, int yFrom, int xTo, int yTo, TCOD_line_listener_t listener, TCOD_bresenham_data_t *data)"""
+
+ @staticmethod
+ def TCOD_line_step(xCur: Any, yCur: Any, /) -> bool:
+ """bool TCOD_line_step(int *xCur, int *yCur)"""
+
+ @staticmethod
+ def TCOD_line_step_mt(xCur: Any, yCur: Any, data: Any, /) -> bool:
+ """bool TCOD_line_step_mt(int *xCur, int *yCur, TCOD_bresenham_data_t *data)"""
+
+ @staticmethod
+ def TCOD_list_add_all(l: Any, l2: Any, /) -> None:
+ """void TCOD_list_add_all(TCOD_list_t l, TCOD_list_t l2)"""
+
+ @staticmethod
+ def TCOD_list_allocate(nb_elements: int, /) -> Any:
+ """TCOD_list_t TCOD_list_allocate(int nb_elements)"""
+
+ @staticmethod
+ def TCOD_list_begin(l: Any, /) -> Any:
+ """void **TCOD_list_begin(TCOD_list_t l)"""
+
+ @staticmethod
+ def TCOD_list_clear(l: Any, /) -> None:
+ """void TCOD_list_clear(TCOD_list_t l)"""
+
+ @staticmethod
+ def TCOD_list_clear_and_delete(l: Any, /) -> None:
+ """void TCOD_list_clear_and_delete(TCOD_list_t l)"""
+
+ @staticmethod
+ def TCOD_list_contains(l: Any, elt: Any, /) -> bool:
+ """bool TCOD_list_contains(TCOD_list_t l, const void *elt)"""
+
+ @staticmethod
+ def TCOD_list_delete(l: Any, /) -> None:
+ """void TCOD_list_delete(TCOD_list_t l)"""
+
+ @staticmethod
+ def TCOD_list_duplicate(l: Any, /) -> Any:
+ """TCOD_list_t TCOD_list_duplicate(TCOD_list_t l)"""
+
+ @staticmethod
+ def TCOD_list_end(l: Any, /) -> Any:
+ """void **TCOD_list_end(TCOD_list_t l)"""
+
+ @staticmethod
+ def TCOD_list_get(l: Any, idx: int, /) -> Any:
+ """void *TCOD_list_get(TCOD_list_t l, int idx)"""
+
+ @staticmethod
+ def TCOD_list_insert_before(l: Any, elt: Any, before: int, /) -> Any:
+ """void **TCOD_list_insert_before(TCOD_list_t l, const void *elt, int before)"""
+
+ @staticmethod
+ def TCOD_list_is_empty(l: Any, /) -> bool:
+ """bool TCOD_list_is_empty(TCOD_list_t l)"""
+
+ @staticmethod
+ def TCOD_list_new() -> Any:
+ """TCOD_list_t TCOD_list_new(void)"""
+
+ @staticmethod
+ def TCOD_list_peek(l: Any, /) -> Any:
+ """void *TCOD_list_peek(TCOD_list_t l)"""
+
+ @staticmethod
+ def TCOD_list_pop(l: Any, /) -> Any:
+ """void *TCOD_list_pop(TCOD_list_t l)"""
+
+ @staticmethod
+ def TCOD_list_push(l: Any, elt: Any, /) -> None:
+ """void TCOD_list_push(TCOD_list_t l, const void *elt)"""
+
+ @staticmethod
+ def TCOD_list_remove(l: Any, elt: Any, /) -> None:
+ """void TCOD_list_remove(TCOD_list_t l, const void *elt)"""
+
+ @staticmethod
+ def TCOD_list_remove_fast(l: Any, elt: Any, /) -> None:
+ """void TCOD_list_remove_fast(TCOD_list_t l, const void *elt)"""
+
+ @staticmethod
+ def TCOD_list_remove_iterator(l: Any, elt: Any, /) -> Any:
+ """void **TCOD_list_remove_iterator(TCOD_list_t l, void **elt)"""
+
+ @staticmethod
+ def TCOD_list_remove_iterator_fast(l: Any, elt: Any, /) -> Any:
+ """void **TCOD_list_remove_iterator_fast(TCOD_list_t l, void **elt)"""
+
+ @staticmethod
+ def TCOD_list_reverse(l: Any, /) -> None:
+ """void TCOD_list_reverse(TCOD_list_t l)"""
+
+ @staticmethod
+ def TCOD_list_set(l: Any, elt: Any, idx: int, /) -> None:
+ """void TCOD_list_set(TCOD_list_t l, const void *elt, int idx)"""
+
+ @staticmethod
+ def TCOD_list_set_size(l: Any, size: int, /) -> None:
+ """void TCOD_list_set_size(TCOD_list_t l, int size)"""
+
+ @staticmethod
+ def TCOD_list_size(l: Any, /) -> int:
+ """int TCOD_list_size(TCOD_list_t l)"""
+
+ @staticmethod
+ def TCOD_load_bdf(path: Any, /) -> Any:
+ """TCOD_Tileset *TCOD_load_bdf(const char *path)"""
+
+ @staticmethod
+ def TCOD_load_bdf_memory(size: int, buffer: Any, /) -> Any:
+ """TCOD_Tileset *TCOD_load_bdf_memory(int size, const unsigned char *buffer)"""
+
+ @staticmethod
+ def TCOD_load_library(path: Any, /) -> Any:
+ """TCOD_library_t TCOD_load_library(const char *path)"""
+
+ @staticmethod
+ def TCOD_load_truetype_font_(path: Any, tile_width: int, tile_height: int, /) -> Any:
+ """TCOD_Tileset *TCOD_load_truetype_font_(const char *path, int tile_width, int tile_height)"""
+
+ @staticmethod
+ def TCOD_load_xp(path: Any, n: int, out: Any, /) -> int:
+ """int TCOD_load_xp(const char *path, int n, TCOD_Console **out)"""
+
+ @staticmethod
+ def TCOD_load_xp_from_memory(n_data: int, data: Any, n_out: int, out: Any, /) -> int:
+ """int TCOD_load_xp_from_memory(int n_data, const unsigned char *data, int n_out, TCOD_Console **out)"""
+
+ @staticmethod
+ def TCOD_log_verbose_(msg: Any, level: int, source: Any, line: int, /) -> None:
+ """void TCOD_log_verbose_(const char *msg, int level, const char *source, int line)"""
+
+ @staticmethod
+ def TCOD_log_verbose_fmt_(level: int, source: Any, line: int, fmt: Any, /, *__args: Any) -> None:
+ """void TCOD_log_verbose_fmt_(int level, const char *source, int line, const char *fmt, ...)"""
+
+ @staticmethod
+ def TCOD_map_clear(map: Any, transparent: bool, walkable: bool, /) -> None:
+ """void TCOD_map_clear(TCOD_Map *map, bool transparent, bool walkable)"""
+
+ @staticmethod
+ def TCOD_map_compute_fov(map: Any, pov_x: int, pov_y: int, max_radius: int, light_walls: bool, algo: Any, /) -> Any:
+ """TCOD_Error TCOD_map_compute_fov(TCOD_Map *map, int pov_x, int pov_y, int max_radius, bool light_walls, TCOD_fov_algorithm_t algo)"""
+
+ @staticmethod
+ def TCOD_map_compute_fov_circular_raycasting(
+ map: Any, pov_x: int, pov_y: int, max_radius: int, light_walls: bool, /
+ ) -> Any:
+ """TCOD_Error TCOD_map_compute_fov_circular_raycasting(TCOD_Map *map, int pov_x, int pov_y, int max_radius, bool light_walls)"""
+
+ @staticmethod
+ def TCOD_map_compute_fov_diamond_raycasting(
+ map: Any, pov_x: int, pov_y: int, max_radius: int, light_walls: bool, /
+ ) -> Any:
+ """TCOD_Error TCOD_map_compute_fov_diamond_raycasting(TCOD_Map *map, int pov_x, int pov_y, int max_radius, bool light_walls)"""
+
+ @staticmethod
+ def TCOD_map_compute_fov_permissive2(
+ map: Any, pov_x: int, pov_y: int, max_radius: int, light_walls: bool, permissiveness: int, /
+ ) -> Any:
+ """TCOD_Error TCOD_map_compute_fov_permissive2(TCOD_Map *map, int pov_x, int pov_y, int max_radius, bool light_walls, int permissiveness)"""
+
+ @staticmethod
+ def TCOD_map_compute_fov_recursive_shadowcasting(
+ map: Any, pov_x: int, pov_y: int, max_radius: int, light_walls: bool, /
+ ) -> Any:
+ """TCOD_Error TCOD_map_compute_fov_recursive_shadowcasting(TCOD_Map *map, int pov_x, int pov_y, int max_radius, bool light_walls)"""
+
+ @staticmethod
+ def TCOD_map_compute_fov_restrictive_shadowcasting(
+ map: Any, pov_x: int, pov_y: int, max_radius: int, light_walls: bool, /
+ ) -> Any:
+ """TCOD_Error TCOD_map_compute_fov_restrictive_shadowcasting(TCOD_Map *map, int pov_x, int pov_y, int max_radius, bool light_walls)"""
+
+ @staticmethod
+ def TCOD_map_compute_fov_symmetric_shadowcast(
+ map: Any, pov_x: int, pov_y: int, max_radius: int, light_walls: bool, /
+ ) -> Any:
+ """TCOD_Error TCOD_map_compute_fov_symmetric_shadowcast(TCOD_Map *map, int pov_x, int pov_y, int max_radius, bool light_walls)"""
+
+ @staticmethod
+ def TCOD_map_copy(source: Any, dest: Any, /) -> Any:
+ """TCOD_Error TCOD_map_copy(const TCOD_Map *source, TCOD_Map *dest)"""
+
+ @staticmethod
+ def TCOD_map_delete(map: Any, /) -> None:
+ """void TCOD_map_delete(TCOD_Map *map)"""
+
+ @staticmethod
+ def TCOD_map_get_height(map: Any, /) -> int:
+ """int TCOD_map_get_height(const TCOD_Map *map)"""
+
+ @staticmethod
+ def TCOD_map_get_nb_cells(map: Any, /) -> int:
+ """int TCOD_map_get_nb_cells(const TCOD_Map *map)"""
+
+ @staticmethod
+ def TCOD_map_get_width(map: Any, /) -> int:
+ """int TCOD_map_get_width(const TCOD_Map *map)"""
+
+ @staticmethod
+ def TCOD_map_in_bounds(map: Any, x: int, y: int, /) -> bool:
+ """inline static bool TCOD_map_in_bounds(const struct TCOD_Map *map, int x, int y)"""
+
+ @staticmethod
+ def TCOD_map_is_in_fov(map: Any, x: int, y: int, /) -> bool:
+ """bool TCOD_map_is_in_fov(const TCOD_Map *map, int x, int y)"""
+
+ @staticmethod
+ def TCOD_map_is_transparent(map: Any, x: int, y: int, /) -> bool:
+ """bool TCOD_map_is_transparent(const TCOD_Map *map, int x, int y)"""
+
+ @staticmethod
+ def TCOD_map_is_walkable(map: Any, x: int, y: int, /) -> bool:
+ """bool TCOD_map_is_walkable(TCOD_Map *map, int x, int y)"""
+
+ @staticmethod
+ def TCOD_map_new(width: int, height: int, /) -> Any:
+ """TCOD_Map *TCOD_map_new(int width, int height)"""
+
+ @staticmethod
+ def TCOD_map_postprocess(map: Any, pov_x: int, pov_y: int, radius: int, /) -> Any:
+ """TCOD_Error TCOD_map_postprocess(TCOD_Map *map, int pov_x, int pov_y, int radius)"""
+
+ @staticmethod
+ def TCOD_map_set_in_fov(map: Any, x: int, y: int, fov: bool, /) -> None:
+ """void TCOD_map_set_in_fov(TCOD_Map *map, int x, int y, bool fov)"""
+
+ @staticmethod
+ def TCOD_map_set_properties(map: Any, x: int, y: int, is_transparent: bool, is_walkable: bool, /) -> None:
+ """void TCOD_map_set_properties(TCOD_Map *map, int x, int y, bool is_transparent, bool is_walkable)"""
+
+ @staticmethod
+ def TCOD_minheap_heapify(minheap: Any, /) -> None:
+ """void TCOD_minheap_heapify(struct TCOD_Heap *minheap)"""
+
+ @staticmethod
+ def TCOD_minheap_pop(minheap: Any, out: Any, /) -> None:
+ """void TCOD_minheap_pop(struct TCOD_Heap *minheap, void *out)"""
+
+ @staticmethod
+ def TCOD_minheap_push(minheap: Any, priority: int, data: Any, /) -> int:
+ """int TCOD_minheap_push(struct TCOD_Heap *minheap, int priority, const void *data)"""
+
+ @staticmethod
+ def TCOD_mouse_get_status() -> Any:
+ """TCOD_mouse_t TCOD_mouse_get_status(void)"""
+
+ @staticmethod
+ def TCOD_mouse_get_status_wrapper(holder: Any, /) -> None:
+ """void TCOD_mouse_get_status_wrapper(TCOD_mouse_t *holder)"""
+
+ @staticmethod
+ def TCOD_mouse_includes_touch(enable: bool, /) -> None:
+ """void TCOD_mouse_includes_touch(bool enable)"""
+
+ @staticmethod
+ def TCOD_mouse_is_cursor_visible() -> bool:
+ """bool TCOD_mouse_is_cursor_visible(void)"""
+
+ @staticmethod
+ def TCOD_mouse_move(x: int, y: int, /) -> None:
+ """void TCOD_mouse_move(int x, int y)"""
+
+ @staticmethod
+ def TCOD_mouse_show_cursor(visible: bool, /) -> None:
+ """void TCOD_mouse_show_cursor(bool visible)"""
+
+ @staticmethod
+ def TCOD_mutex_delete(mut: Any, /) -> None:
+ """void TCOD_mutex_delete(TCOD_mutex_t mut)"""
+
+ @staticmethod
+ def TCOD_mutex_in(mut: Any, /) -> None:
+ """void TCOD_mutex_in(TCOD_mutex_t mut)"""
+
+ @staticmethod
+ def TCOD_mutex_new() -> Any:
+ """TCOD_mutex_t TCOD_mutex_new(void)"""
+
+ @staticmethod
+ def TCOD_mutex_out(mut: Any, /) -> None:
+ """void TCOD_mutex_out(TCOD_mutex_t mut)"""
+
+ @staticmethod
+ def TCOD_namegen_destroy() -> None:
+ """void TCOD_namegen_destroy(void)"""
+
+ @staticmethod
+ def TCOD_namegen_generate(name: Any, allocate: bool, /) -> Any:
+ """char *TCOD_namegen_generate(const char *name, bool allocate)"""
+
+ @staticmethod
+ def TCOD_namegen_generate_custom(name: Any, rule: Any, allocate: bool, /) -> Any:
+ """char *TCOD_namegen_generate_custom(const char *name, const char *rule, bool allocate)"""
+
+ @staticmethod
+ def TCOD_namegen_get_nb_sets_wrapper() -> int:
+ """int TCOD_namegen_get_nb_sets_wrapper(void)"""
+
+ @staticmethod
+ def TCOD_namegen_get_sets() -> Any:
+ """TCOD_list_t TCOD_namegen_get_sets(void)"""
+
+ @staticmethod
+ def TCOD_namegen_get_sets_wrapper(sets: Any, /) -> None:
+ """void TCOD_namegen_get_sets_wrapper(char **sets)"""
+
+ @staticmethod
+ def TCOD_namegen_parse(filename: Any, random: Any, /) -> None:
+ """void TCOD_namegen_parse(const char *filename, TCOD_Random *random)"""
+
+ @staticmethod
+ def TCOD_noise_delete(noise: Any, /) -> None:
+ """void TCOD_noise_delete(TCOD_Noise *noise)"""
+
+ @staticmethod
+ def TCOD_noise_get(noise: Any, f: Any, /) -> float:
+ """float TCOD_noise_get(TCOD_Noise *noise, const float *f)"""
+
+ @staticmethod
+ def TCOD_noise_get_ex(noise: Any, f: Any, type: Any, /) -> float:
+ """float TCOD_noise_get_ex(TCOD_Noise *noise, const float *f, TCOD_noise_type_t type)"""
+
+ @staticmethod
+ def TCOD_noise_get_fbm(noise: Any, f: Any, octaves: float, /) -> float:
+ """float TCOD_noise_get_fbm(TCOD_Noise *noise, const float *f, float octaves)"""
+
+ @staticmethod
+ def TCOD_noise_get_fbm_ex(noise: Any, f: Any, octaves: float, type: Any, /) -> float:
+ """float TCOD_noise_get_fbm_ex(TCOD_Noise *noise, const float *f, float octaves, TCOD_noise_type_t type)"""
+
+ @staticmethod
+ def TCOD_noise_get_fbm_vectorized(
+ noise: Any, type: Any, octaves: float, n: int, x: Any, y: Any, z: Any, w: Any, out: Any, /
+ ) -> None:
+ """void TCOD_noise_get_fbm_vectorized(TCOD_Noise *noise, TCOD_noise_type_t type, float octaves, int n, float *x, float *y, float *z, float *w, float *out)"""
+
+ @staticmethod
+ def TCOD_noise_get_turbulence(noise: Any, f: Any, octaves: float, /) -> float:
+ """float TCOD_noise_get_turbulence(TCOD_Noise *noise, const float *f, float octaves)"""
+
+ @staticmethod
+ def TCOD_noise_get_turbulence_ex(noise: Any, f: Any, octaves: float, type: Any, /) -> float:
+ """float TCOD_noise_get_turbulence_ex(TCOD_Noise *noise, const float *f, float octaves, TCOD_noise_type_t type)"""
+
+ @staticmethod
+ def TCOD_noise_get_turbulence_vectorized(
+ noise: Any, type: Any, octaves: float, n: int, x: Any, y: Any, z: Any, w: Any, out: Any, /
+ ) -> None:
+ """void TCOD_noise_get_turbulence_vectorized(TCOD_Noise *noise, TCOD_noise_type_t type, float octaves, int n, float *x, float *y, float *z, float *w, float *out)"""
+
+ @staticmethod
+ def TCOD_noise_get_vectorized(noise: Any, type: Any, n: int, x: Any, y: Any, z: Any, w: Any, out: Any, /) -> None:
+ """void TCOD_noise_get_vectorized(TCOD_Noise *noise, TCOD_noise_type_t type, int n, float *x, float *y, float *z, float *w, float *out)"""
+
+ @staticmethod
+ def TCOD_noise_new(dimensions: int, hurst: float, lacunarity: float, random: Any, /) -> Any:
+ """TCOD_Noise *TCOD_noise_new(int dimensions, float hurst, float lacunarity, TCOD_Random *random)"""
+
+ @staticmethod
+ def TCOD_noise_set_type(noise: Any, type: Any, /) -> None:
+ """void TCOD_noise_set_type(TCOD_Noise *noise, TCOD_noise_type_t type)"""
+
+ @staticmethod
+ def TCOD_parse_bool_value() -> Any:
+ """TCOD_value_t TCOD_parse_bool_value(void)"""
+
+ @staticmethod
+ def TCOD_parse_char_value() -> Any:
+ """TCOD_value_t TCOD_parse_char_value(void)"""
+
+ @staticmethod
+ def TCOD_parse_color_value() -> Any:
+ """TCOD_value_t TCOD_parse_color_value(void)"""
+
+ @staticmethod
+ def TCOD_parse_dice_value() -> Any:
+ """TCOD_value_t TCOD_parse_dice_value(void)"""
+
+ @staticmethod
+ def TCOD_parse_float_value() -> Any:
+ """TCOD_value_t TCOD_parse_float_value(void)"""
+
+ @staticmethod
+ def TCOD_parse_integer_value() -> Any:
+ """TCOD_value_t TCOD_parse_integer_value(void)"""
+
+ @staticmethod
+ def TCOD_parse_property_value(parser: Any, def_: Any, propname: Any, list: bool, /) -> Any:
+ """TCOD_value_t TCOD_parse_property_value(TCOD_Parser *parser, TCOD_ParserStruct *def, char *propname, bool list)"""
+
+ @staticmethod
+ def TCOD_parse_string_value() -> Any:
+ """TCOD_value_t TCOD_parse_string_value(void)"""
+
+ @staticmethod
+ def TCOD_parse_value_list_value(def_: Any, list_num: int, /) -> Any:
+ """TCOD_value_t TCOD_parse_value_list_value(TCOD_ParserStruct *def, int list_num)"""
+
+ @staticmethod
+ def TCOD_parser_delete(parser: Any, /) -> None:
+ """void TCOD_parser_delete(TCOD_Parser *parser)"""
+
+ @staticmethod
+ def TCOD_parser_error(msg: Any, /, *__args: Any) -> None:
+ """void TCOD_parser_error(const char *msg, ...)"""
+
+ @staticmethod
+ def TCOD_parser_get_bool_property(parser: Any, name: Any, /) -> bool:
+ """bool TCOD_parser_get_bool_property(TCOD_Parser *parser, const char *name)"""
+
+ @staticmethod
+ def TCOD_parser_get_char_property(parser: Any, name: Any, /) -> int:
+ """int TCOD_parser_get_char_property(TCOD_Parser *parser, const char *name)"""
+
+ @staticmethod
+ def TCOD_parser_get_color_property(parser: Any, name: Any, /) -> Any:
+ """TCOD_color_t TCOD_parser_get_color_property(TCOD_Parser *parser, const char *name)"""
+
+ @staticmethod
+ def TCOD_parser_get_color_property_wrapper(parser: Any, name: Any, /) -> Any:
+ """colornum_t TCOD_parser_get_color_property_wrapper(TCOD_parser_t parser, const char *name)"""
+
+ @staticmethod
+ def TCOD_parser_get_custom_property(parser: Any, name: Any, /) -> Any:
+ """void *TCOD_parser_get_custom_property(TCOD_Parser *parser, const char *name)"""
+
+ @staticmethod
+ def TCOD_parser_get_dice_property(parser: Any, name: Any, /) -> Any:
+ """TCOD_dice_t TCOD_parser_get_dice_property(TCOD_Parser *parser, const char *name)"""
+
+ @staticmethod
+ def TCOD_parser_get_dice_property_py(parser: Any, name: Any, dice: Any, /) -> None:
+ """void TCOD_parser_get_dice_property_py(TCOD_Parser *parser, const char *name, TCOD_dice_t *dice)"""
+
+ @staticmethod
+ def TCOD_parser_get_float_property(parser: Any, name: Any, /) -> float:
+ """float TCOD_parser_get_float_property(TCOD_Parser *parser, const char *name)"""
+
+ @staticmethod
+ def TCOD_parser_get_int_property(parser: Any, name: Any, /) -> int:
+ """int TCOD_parser_get_int_property(TCOD_Parser *parser, const char *name)"""
+
+ @staticmethod
+ def TCOD_parser_get_list_property(parser: Any, name: Any, type: Any, /) -> Any:
+ """TCOD_list_t TCOD_parser_get_list_property(TCOD_Parser *parser, const char *name, TCOD_value_type_t type)"""
+
+ @staticmethod
+ def TCOD_parser_get_string_property(parser: Any, name: Any, /) -> Any:
+ """const char *TCOD_parser_get_string_property(TCOD_Parser *parser, const char *name)"""
+
+ @staticmethod
+ def TCOD_parser_has_property(parser: Any, name: Any, /) -> bool:
+ """bool TCOD_parser_has_property(TCOD_Parser *parser, const char *name)"""
+
+ @staticmethod
+ def TCOD_parser_new() -> Any:
+ """TCOD_Parser *TCOD_parser_new(void)"""
+
+ @staticmethod
+ def TCOD_parser_new_custom_type(parser: Any, custom_type_parser: Any, /) -> Any:
+ """TCOD_value_type_t TCOD_parser_new_custom_type(TCOD_Parser *parser, TCOD_parser_custom_t custom_type_parser)"""
+
+ @staticmethod
+ def TCOD_parser_new_struct(parser: Any, name: Any, /) -> Any:
+ """TCOD_ParserStruct *TCOD_parser_new_struct(TCOD_Parser *parser, const char *name)"""
+
+ @staticmethod
+ def TCOD_parser_run(parser: Any, filename: Any, listener: Any, /) -> None:
+ """void TCOD_parser_run(TCOD_Parser *parser, const char *filename, TCOD_parser_listener_t *listener)"""
+
+ @staticmethod
+ def TCOD_path_compute(path: Any, ox: int, oy: int, dx: int, dy: int, /) -> bool:
+ """bool TCOD_path_compute(TCOD_path_t path, int ox, int oy, int dx, int dy)"""
+
+ @staticmethod
+ def TCOD_path_delete(path: Any, /) -> None:
+ """void TCOD_path_delete(TCOD_path_t path)"""
+
+ @staticmethod
+ def TCOD_path_get(path: Any, index: int, x: Any, y: Any, /) -> None:
+ """void TCOD_path_get(TCOD_path_t path, int index, int *x, int *y)"""
+
+ @staticmethod
+ def TCOD_path_get_destination(path: Any, x: Any, y: Any, /) -> None:
+ """void TCOD_path_get_destination(TCOD_path_t path, int *x, int *y)"""
+
+ @staticmethod
+ def TCOD_path_get_origin(path: Any, x: Any, y: Any, /) -> None:
+ """void TCOD_path_get_origin(TCOD_path_t path, int *x, int *y)"""
+
+ @staticmethod
+ def TCOD_path_is_empty(path: Any, /) -> bool:
+ """bool TCOD_path_is_empty(TCOD_path_t path)"""
+
+ @staticmethod
+ def TCOD_path_new_using_function(
+ map_width: int, map_height: int, func: Any, user_data: Any, diagonalCost: float, /
+ ) -> Any:
+ """TCOD_path_t TCOD_path_new_using_function(int map_width, int map_height, TCOD_path_func_t func, void *user_data, float diagonalCost)"""
+
+ @staticmethod
+ def TCOD_path_new_using_map(map: Any, diagonalCost: float, /) -> Any:
+ """TCOD_path_t TCOD_path_new_using_map(TCOD_Map *map, float diagonalCost)"""
+
+ @staticmethod
+ def TCOD_path_reverse(path: Any, /) -> None:
+ """void TCOD_path_reverse(TCOD_path_t path)"""
+
+ @staticmethod
+ def TCOD_path_size(path: Any, /) -> int:
+ """int TCOD_path_size(TCOD_path_t path)"""
+
+ @staticmethod
+ def TCOD_path_walk(path: Any, x: Any, y: Any, recalculate_when_needed: bool, /) -> bool:
+ """bool TCOD_path_walk(TCOD_path_t path, int *x, int *y, bool recalculate_when_needed)"""
+
+ @staticmethod
+ def TCOD_pf_compute(path: Any, /) -> int:
+ """int TCOD_pf_compute(struct TCOD_Pathfinder *path)"""
+
+ @staticmethod
+ def TCOD_pf_compute_step(path: Any, /) -> int:
+ """int TCOD_pf_compute_step(struct TCOD_Pathfinder *path)"""
+
+ @staticmethod
+ def TCOD_pf_delete(path: Any, /) -> None:
+ """void TCOD_pf_delete(struct TCOD_Pathfinder *path)"""
+
+ @staticmethod
+ def TCOD_pf_new(ndim: int, shape: Any, /) -> Any:
+ """struct TCOD_Pathfinder *TCOD_pf_new(int ndim, const size_t *shape)"""
+
+ @staticmethod
+ def TCOD_pf_recompile(path: Any, /) -> int:
+ """int TCOD_pf_recompile(struct TCOD_Pathfinder *path)"""
+
+ @staticmethod
+ def TCOD_pf_set_distance_pointer(path: Any, data: Any, int_type: int, strides: Any, /) -> None:
+ """void TCOD_pf_set_distance_pointer(struct TCOD_Pathfinder *path, void *data, int int_type, const size_t *strides)"""
+
+ @staticmethod
+ def TCOD_pf_set_graph2d_pointer(
+ path: Any, data: Any, int_type: int, strides: Any, cardinal: int, diagonal: int, /
+ ) -> None:
+ """void TCOD_pf_set_graph2d_pointer(struct TCOD_Pathfinder *path, void *data, int int_type, const size_t *strides, int cardinal, int diagonal)"""
+
+ @staticmethod
+ def TCOD_pf_set_traversal_pointer(path: Any, data: Any, int_type: int, strides: Any, /) -> None:
+ """void TCOD_pf_set_traversal_pointer(struct TCOD_Pathfinder *path, void *data, int int_type, const size_t *strides)"""
+
+ @staticmethod
+ def TCOD_printf_rgb(console: Any, params: Any, fmt: Any, /, *__args: Any) -> int:
+ """int TCOD_printf_rgb(TCOD_Console *console, TCOD_PrintParamsRGB params, const char *fmt, ...)"""
+
+ @staticmethod
+ def TCOD_printn_rgb(console: Any, params: Any, n: int, str: Any, /) -> int:
+ """int TCOD_printn_rgb(TCOD_Console *console, TCOD_PrintParamsRGB params, int n, const char *str)"""
+
+ @staticmethod
+ def TCOD_quit() -> None:
+ """void TCOD_quit(void)"""
+
+ @staticmethod
+ def TCOD_random_delete(mersenne: Any, /) -> None:
+ """void TCOD_random_delete(TCOD_Random *mersenne)"""
+
+ @staticmethod
+ def TCOD_random_dice_new(s: Any, /) -> Any:
+ """TCOD_dice_t TCOD_random_dice_new(const char *s)"""
+
+ @staticmethod
+ def TCOD_random_dice_roll(mersenne: Any, dice: Any, /) -> int:
+ """int TCOD_random_dice_roll(TCOD_Random *mersenne, TCOD_dice_t dice)"""
+
+ @staticmethod
+ def TCOD_random_dice_roll_s(mersenne: Any, s: Any, /) -> int:
+ """int TCOD_random_dice_roll_s(TCOD_Random *mersenne, const char *s)"""
+
+ @staticmethod
+ def TCOD_random_get_d(mersenne: Any, min: float, max: float, /) -> float:
+ """double TCOD_random_get_d(TCOD_random_t mersenne, double min, double max)"""
+
+ @staticmethod
+ def TCOD_random_get_double(mersenne: Any, min: float, max: float, /) -> float:
+ """double TCOD_random_get_double(TCOD_Random *mersenne, double min, double max)"""
+
+ @staticmethod
+ def TCOD_random_get_double_mean(mersenne: Any, min: float, max: float, mean: float, /) -> float:
+ """double TCOD_random_get_double_mean(TCOD_Random *mersenne, double min, double max, double mean)"""
+
+ @staticmethod
+ def TCOD_random_get_float(mersenne: Any, min: float, max: float, /) -> float:
+ """float TCOD_random_get_float(TCOD_Random *mersenne, float min, float max)"""
+
+ @staticmethod
+ def TCOD_random_get_float_mean(mersenne: Any, min: float, max: float, mean: float, /) -> float:
+ """float TCOD_random_get_float_mean(TCOD_Random *mersenne, float min, float max, float mean)"""
+
+ @staticmethod
+ def TCOD_random_get_gaussian_double(mersenne: Any, mean: float, std_deviation: float, /) -> float:
+ """double TCOD_random_get_gaussian_double(TCOD_random_t mersenne, double mean, double std_deviation)"""
+
+ @staticmethod
+ def TCOD_random_get_gaussian_double_inv(mersenne: Any, mean: float, std_deviation: float, /) -> float:
+ """double TCOD_random_get_gaussian_double_inv(TCOD_random_t mersenne, double mean, double std_deviation)"""
+
+ @staticmethod
+ def TCOD_random_get_gaussian_double_range(mersenne: Any, min: float, max: float, /) -> float:
+ """double TCOD_random_get_gaussian_double_range(TCOD_random_t mersenne, double min, double max)"""
+
+ @staticmethod
+ def TCOD_random_get_gaussian_double_range_custom(mersenne: Any, min: float, max: float, mean: float, /) -> float:
+ """double TCOD_random_get_gaussian_double_range_custom(TCOD_random_t mersenne, double min, double max, double mean)"""
+
+ @staticmethod
+ def TCOD_random_get_gaussian_double_range_custom_inv(
+ mersenne: Any, min: float, max: float, mean: float, /
+ ) -> float:
+ """double TCOD_random_get_gaussian_double_range_custom_inv(TCOD_random_t mersenne, double min, double max, double mean)"""
+
+ @staticmethod
+ def TCOD_random_get_gaussian_double_range_inv(mersenne: Any, min: float, max: float, /) -> float:
+ """double TCOD_random_get_gaussian_double_range_inv(TCOD_random_t mersenne, double min, double max)"""
+
+ @staticmethod
+ def TCOD_random_get_i(mersenne: Any, min: int, max: int, /) -> int:
+ """int TCOD_random_get_i(TCOD_random_t mersenne, int min, int max)"""
+
+ @staticmethod
+ def TCOD_random_get_instance() -> Any:
+ """TCOD_Random *TCOD_random_get_instance(void)"""
+
+ @staticmethod
+ def TCOD_random_get_int(mersenne: Any, min: int, max: int, /) -> int:
+ """int TCOD_random_get_int(TCOD_Random *mersenne, int min, int max)"""
+
+ @staticmethod
+ def TCOD_random_get_int_mean(mersenne: Any, min: int, max: int, mean: int, /) -> int:
+ """int TCOD_random_get_int_mean(TCOD_Random *mersenne, int min, int max, int mean)"""
+
+ @staticmethod
+ def TCOD_random_new(algo: Any, /) -> Any:
+ """TCOD_Random *TCOD_random_new(TCOD_random_algo_t algo)"""
+
+ @staticmethod
+ def TCOD_random_new_from_seed(algo: Any, seed: Any, /) -> Any:
+ """TCOD_Random *TCOD_random_new_from_seed(TCOD_random_algo_t algo, uint32_t seed)"""
+
+ @staticmethod
+ def TCOD_random_restore(mersenne: Any, backup: Any, /) -> None:
+ """void TCOD_random_restore(TCOD_Random *mersenne, TCOD_Random *backup)"""
+
+ @staticmethod
+ def TCOD_random_save(mersenne: Any, /) -> Any:
+ """TCOD_Random *TCOD_random_save(TCOD_Random *mersenne)"""
+
+ @staticmethod
+ def TCOD_random_set_distribution(mersenne: Any, distribution: Any, /) -> None:
+ """void TCOD_random_set_distribution(TCOD_Random *mersenne, TCOD_distribution_t distribution)"""
+
+ @staticmethod
+ def TCOD_renderer_init_sdl2(
+ x: int, y: int, width: int, height: int, title: Any, window_flags: int, vsync: int, tileset: Any, /
+ ) -> Any:
+ """struct TCOD_Context *TCOD_renderer_init_sdl2(int x, int y, int width, int height, const char *title, int window_flags, int vsync, struct TCOD_Tileset *tileset)"""
+
+ @staticmethod
+ def TCOD_renderer_init_sdl3(window_props: Any, renderer_props: Any, tileset: Any, /) -> Any:
+ """TCOD_Context *TCOD_renderer_init_sdl3(SDL_PropertiesID window_props, SDL_PropertiesID renderer_props, struct TCOD_Tileset *tileset)"""
+
+ @staticmethod
+ def TCOD_renderer_init_xterm(
+ window_x: int, window_y: int, pixel_width: int, pixel_height: int, columns: int, rows: int, window_title: Any, /
+ ) -> Any:
+ """TCOD_Context *TCOD_renderer_init_xterm(int window_x, int window_y, int pixel_width, int pixel_height, int columns, int rows, const char *window_title)"""
+
+ @staticmethod
+ def TCOD_rng_splitmix64_next(state: Any, /) -> Any:
+ """inline static uint64_t TCOD_rng_splitmix64_next(uint64_t *state)"""
+
+ @staticmethod
+ def TCOD_save_xp(n: int, consoles: Any, path: Any, compress_level: int, /) -> Any:
+ """TCOD_Error TCOD_save_xp(int n, const TCOD_Console * const *consoles, const char *path, int compress_level)"""
+
+ @staticmethod
+ def TCOD_save_xp_to_memory(n_consoles: int, consoles: Any, n_out: int, out: Any, compression_level: int, /) -> int:
+ """int TCOD_save_xp_to_memory(int n_consoles, const TCOD_Console * const *consoles, int n_out, unsigned char *out, int compression_level)"""
+
+ @staticmethod
+ def TCOD_sdl2_atlas_delete(atlas: Any, /) -> None:
+ """void TCOD_sdl2_atlas_delete(struct TCOD_TilesetAtlasSDL2 *atlas)"""
+
+ @staticmethod
+ def TCOD_sdl2_atlas_new(renderer: Any, tileset: Any, /) -> Any:
+ """struct TCOD_TilesetAtlasSDL2 *TCOD_sdl2_atlas_new(struct SDL_Renderer *renderer, struct TCOD_Tileset *tileset)"""
+
+ @staticmethod
+ def TCOD_sdl2_render_texture(atlas: Any, console: Any, cache: Any, target: Any, /) -> Any:
+ """TCOD_Error TCOD_sdl2_render_texture(const struct TCOD_TilesetAtlasSDL2 *atlas, const struct TCOD_Console *console, struct TCOD_Console *cache, struct SDL_Texture *target)"""
+
+ @staticmethod
+ def TCOD_sdl2_render_texture_setup(atlas: Any, console: Any, cache: Any, target: Any, /) -> Any:
+ """TCOD_Error TCOD_sdl2_render_texture_setup(const struct TCOD_TilesetAtlasSDL2 *atlas, const struct TCOD_Console *console, struct TCOD_Console **cache, struct SDL_Texture **target)"""
+
+ @staticmethod
+ def TCOD_semaphore_delete(sem: Any, /) -> None:
+ """void TCOD_semaphore_delete(TCOD_semaphore_t sem)"""
+
+ @staticmethod
+ def TCOD_semaphore_lock(sem: Any, /) -> None:
+ """void TCOD_semaphore_lock(TCOD_semaphore_t sem)"""
+
+ @staticmethod
+ def TCOD_semaphore_new(initVal: int, /) -> Any:
+ """TCOD_semaphore_t TCOD_semaphore_new(int initVal)"""
+
+ @staticmethod
+ def TCOD_semaphore_unlock(sem: Any, /) -> None:
+ """void TCOD_semaphore_unlock(TCOD_semaphore_t sem)"""
+
+ @staticmethod
+ def TCOD_set_default_tileset(tileset: Any, /) -> None:
+ """void TCOD_set_default_tileset(TCOD_Tileset *tileset)"""
+
+ @staticmethod
+ def TCOD_set_error(msg: Any, /) -> Any:
+ """TCOD_Error TCOD_set_error(const char *msg)"""
+
+ @staticmethod
+ def TCOD_set_errorf(fmt: Any, /, *__args: Any) -> Any:
+ """TCOD_Error TCOD_set_errorf(const char *fmt, ...)"""
+
+ @staticmethod
+ def TCOD_set_log_callback(callback: Any, userdata: Any, /) -> None:
+ """void TCOD_set_log_callback(TCOD_LoggingCallback callback, void *userdata)"""
+
+ @staticmethod
+ def TCOD_set_log_level(level: int, /) -> None:
+ """void TCOD_set_log_level(int level)"""
+
+ @staticmethod
+ def TCOD_strcasecmp(s1: Any, s2: Any, /) -> int:
+ """int TCOD_strcasecmp(const char *s1, const char *s2)"""
+
+ @staticmethod
+ def TCOD_strdup(s: Any, /) -> Any:
+ """char *TCOD_strdup(const char *s)"""
+
+ @staticmethod
+ def TCOD_strncasecmp(s1: Any, s2: Any, n: int, /) -> int:
+ """int TCOD_strncasecmp(const char *s1, const char *s2, size_t n)"""
+
+ @staticmethod
+ def TCOD_struct_add_flag(def_: Any, propname: Any, /) -> None:
+ """void TCOD_struct_add_flag(TCOD_ParserStruct *def, const char *propname)"""
+
+ @staticmethod
+ def TCOD_struct_add_list_property(def_: Any, name: Any, type: Any, mandatory: bool, /) -> None:
+ """void TCOD_struct_add_list_property(TCOD_ParserStruct *def, const char *name, TCOD_value_type_t type, bool mandatory)"""
+
+ @staticmethod
+ def TCOD_struct_add_property(def_: Any, name: Any, type: Any, mandatory: bool, /) -> None:
+ """void TCOD_struct_add_property(TCOD_ParserStruct *def, const char *name, TCOD_value_type_t type, bool mandatory)"""
+
+ @staticmethod
+ def TCOD_struct_add_structure(def_: Any, sub_structure: Any, /) -> None:
+ """void TCOD_struct_add_structure(TCOD_ParserStruct *def, const TCOD_ParserStruct *sub_structure)"""
+
+ @staticmethod
+ def TCOD_struct_add_value_list(def_: Any, name: Any, value_list: Any, mandatory: bool, /) -> None:
+ """void TCOD_struct_add_value_list(TCOD_ParserStruct *def, const char *name, const char * const *value_list, bool mandatory)"""
+
+ @staticmethod
+ def TCOD_struct_add_value_list_sized(def_: Any, name: Any, value_list: Any, size: int, mandatory: bool, /) -> None:
+ """void TCOD_struct_add_value_list_sized(TCOD_ParserStruct *def, const char *name, const char * const *value_list, int size, bool mandatory)"""
+
+ @staticmethod
+ def TCOD_struct_get_name(def_: Any, /) -> Any:
+ """const char *TCOD_struct_get_name(const TCOD_ParserStruct *def)"""
+
+ @staticmethod
+ def TCOD_struct_get_type(def_: Any, propname: Any, /) -> Any:
+ """TCOD_value_type_t TCOD_struct_get_type(const TCOD_ParserStruct *def, const char *propname)"""
+
+ @staticmethod
+ def TCOD_struct_is_mandatory(def_: Any, propname: Any, /) -> bool:
+ """bool TCOD_struct_is_mandatory(TCOD_ParserStruct *def, const char *propname)"""
+
+ @staticmethod
+ def TCOD_sys_accumulate_console(console: Any, /) -> int:
+ """int TCOD_sys_accumulate_console(const TCOD_Console *console)"""
+
+ @staticmethod
+ def TCOD_sys_accumulate_console_(console: Any, viewport: Any, /) -> int:
+ """int TCOD_sys_accumulate_console_(const TCOD_Console *console, const struct SDL_Rect *viewport)"""
+
+ @staticmethod
+ def TCOD_sys_check_for_event(eventMask: int, key: Any, mouse: Any, /) -> Any:
+ """TCOD_event_t TCOD_sys_check_for_event(int eventMask, TCOD_key_t *key, TCOD_mouse_t *mouse)"""
+
+ @staticmethod
+ def TCOD_sys_check_for_keypress(flags: int, /) -> Any:
+ """TCOD_key_t TCOD_sys_check_for_keypress(int flags)"""
+
+ @staticmethod
+ def TCOD_sys_check_magic_number(filename: Any, size: int, data: Any, /) -> bool:
+ """bool TCOD_sys_check_magic_number(const char *filename, size_t size, uint8_t *data)"""
+
+ @staticmethod
+ def TCOD_sys_clipboard_get() -> Any:
+ """char *TCOD_sys_clipboard_get(void)"""
+
+ @staticmethod
+ def TCOD_sys_clipboard_set(value: Any, /) -> bool:
+ """bool TCOD_sys_clipboard_set(const char *value)"""
+
+ @staticmethod
+ def TCOD_sys_create_directory(path: Any, /) -> bool:
+ """bool TCOD_sys_create_directory(const char *path)"""
+
+ @staticmethod
+ def TCOD_sys_decode_font_() -> None:
+ """void TCOD_sys_decode_font_(void)"""
+
+ @staticmethod
+ def TCOD_sys_delete_directory(path: Any, /) -> bool:
+ """bool TCOD_sys_delete_directory(const char *path)"""
+
+ @staticmethod
+ def TCOD_sys_delete_file(path: Any, /) -> bool:
+ """bool TCOD_sys_delete_file(const char *path)"""
+
+ @staticmethod
+ def TCOD_sys_elapsed_milli() -> Any:
+ """uint32_t TCOD_sys_elapsed_milli(void)"""
+
+ @staticmethod
+ def TCOD_sys_elapsed_seconds() -> float:
+ """float TCOD_sys_elapsed_seconds(void)"""
+
+ @staticmethod
+ def TCOD_sys_file_exists(filename: Any, /, *__args: Any) -> bool:
+ """bool TCOD_sys_file_exists(const char *filename, ...)"""
+
+ @staticmethod
+ def TCOD_sys_force_fullscreen_resolution(width: int, height: int, /) -> None:
+ """void TCOD_sys_force_fullscreen_resolution(int width, int height)"""
+
+ @staticmethod
+ def TCOD_sys_get_SDL_renderer() -> Any:
+ """struct SDL_Renderer *TCOD_sys_get_SDL_renderer(void)"""
+
+ @staticmethod
+ def TCOD_sys_get_SDL_window() -> Any:
+ """struct SDL_Window *TCOD_sys_get_SDL_window(void)"""
+
+ @staticmethod
+ def TCOD_sys_get_char_size(w: Any, h: Any, /) -> None:
+ """void TCOD_sys_get_char_size(int *w, int *h)"""
+
+ @staticmethod
+ def TCOD_sys_get_current_resolution(w: Any, h: Any, /) -> Any:
+ """TCOD_Error TCOD_sys_get_current_resolution(int *w, int *h)"""
+
+ @staticmethod
+ def TCOD_sys_get_current_resolution_x() -> int:
+ """int TCOD_sys_get_current_resolution_x(void)"""
+
+ @staticmethod
+ def TCOD_sys_get_current_resolution_y() -> int:
+ """int TCOD_sys_get_current_resolution_y(void)"""
+
+ @staticmethod
+ def TCOD_sys_get_directory_content(path: Any, pattern: Any, /) -> Any:
+ """TCOD_list_t TCOD_sys_get_directory_content(const char *path, const char *pattern)"""
+
+ @staticmethod
+ def TCOD_sys_get_fps() -> int:
+ """int TCOD_sys_get_fps(void)"""
+
+ @staticmethod
+ def TCOD_sys_get_fullscreen_offsets(offset_x: Any, offset_y: Any, /) -> None:
+ """void TCOD_sys_get_fullscreen_offsets(int *offset_x, int *offset_y)"""
+
+ @staticmethod
+ def TCOD_sys_get_image_alpha(image: Any, x: int, y: int, /) -> int:
+ """int TCOD_sys_get_image_alpha(const struct SDL_Surface *image, int x, int y)"""
+
+ @staticmethod
+ def TCOD_sys_get_image_pixel(image: Any, x: int, y: int, /) -> Any:
+ """TCOD_color_t TCOD_sys_get_image_pixel(const struct SDL_Surface *image, int x, int y)"""
+
+ @staticmethod
+ def TCOD_sys_get_image_size(image: Any, w: Any, h: Any, /) -> None:
+ """void TCOD_sys_get_image_size(const struct SDL_Surface *image, int *w, int *h)"""
+
+ @staticmethod
+ def TCOD_sys_get_internal_console() -> Any:
+ """TCOD_Console *TCOD_sys_get_internal_console(void)"""
+
+ @staticmethod
+ def TCOD_sys_get_internal_context() -> Any:
+ """TCOD_Context *TCOD_sys_get_internal_context(void)"""
+
+ @staticmethod
+ def TCOD_sys_get_last_frame_length() -> float:
+ """float TCOD_sys_get_last_frame_length(void)"""
+
+ @staticmethod
+ def TCOD_sys_get_num_cores() -> int:
+ """int TCOD_sys_get_num_cores(void)"""
+
+ @staticmethod
+ def TCOD_sys_get_renderer() -> Any:
+ """TCOD_renderer_t TCOD_sys_get_renderer(void)"""
+
+ @staticmethod
+ def TCOD_sys_get_sdl_renderer() -> Any:
+ """struct SDL_Renderer *TCOD_sys_get_sdl_renderer(void)"""
+
+ @staticmethod
+ def TCOD_sys_get_sdl_window() -> Any:
+ """struct SDL_Window *TCOD_sys_get_sdl_window(void)"""
+
+ @staticmethod
+ def TCOD_sys_handle_key_event(ev: Any, key: Any, /) -> Any:
+ """TCOD_event_t TCOD_sys_handle_key_event(const union SDL_Event *ev, TCOD_key_t *key)"""
+
+ @staticmethod
+ def TCOD_sys_handle_mouse_event(ev: Any, mouse: Any, /) -> Any:
+ """TCOD_event_t TCOD_sys_handle_mouse_event(const union SDL_Event *ev, TCOD_mouse_t *mouse)"""
+
+ @staticmethod
+ def TCOD_sys_is_directory(path: Any, /) -> bool:
+ """bool TCOD_sys_is_directory(const char *path)"""
+
+ @staticmethod
+ def TCOD_sys_is_key_pressed(key: Any, /) -> bool:
+ """bool TCOD_sys_is_key_pressed(TCOD_keycode_t key)"""
+
+ @staticmethod
+ def TCOD_sys_load_image(filename: Any, /) -> Any:
+ """struct SDL_Surface *TCOD_sys_load_image(const char *filename)"""
+
+ @staticmethod
+ def TCOD_sys_load_player_config() -> Any:
+ """TCOD_Error TCOD_sys_load_player_config(void)"""
+
+ @staticmethod
+ def TCOD_sys_map_ascii_to_font(asciiCode: int, fontCharX: int, fontCharY: int, /) -> None:
+ """void TCOD_sys_map_ascii_to_font(int asciiCode, int fontCharX, int fontCharY)"""
+
+ @staticmethod
+ def TCOD_sys_pixel_to_tile(x: Any, y: Any, /) -> None:
+ """void TCOD_sys_pixel_to_tile(double *x, double *y)"""
+
+ @staticmethod
+ def TCOD_sys_read_file(filename: Any, buf: Any, size: Any, /) -> bool:
+ """bool TCOD_sys_read_file(const char *filename, unsigned char **buf, size_t *size)"""
+
+ @staticmethod
+ def TCOD_sys_register_SDL_renderer(renderer: Any, /) -> None:
+ """void TCOD_sys_register_SDL_renderer(SDL_renderer_t renderer)"""
+
+ @staticmethod
+ def TCOD_sys_restore_fps() -> None:
+ """void TCOD_sys_restore_fps(void)"""
+
+ @staticmethod
+ def TCOD_sys_save_bitmap(bitmap: Any, filename: Any, /) -> Any:
+ """TCOD_Error TCOD_sys_save_bitmap(struct SDL_Surface *bitmap, const char *filename)"""
+
+ @staticmethod
+ def TCOD_sys_save_fps() -> None:
+ """void TCOD_sys_save_fps(void)"""
+
+ @staticmethod
+ def TCOD_sys_save_screenshot(filename: Any, /) -> None:
+ """void TCOD_sys_save_screenshot(const char *filename)"""
+
+ @staticmethod
+ def TCOD_sys_set_fps(val: int, /) -> None:
+ """void TCOD_sys_set_fps(int val)"""
+
+ @staticmethod
+ def TCOD_sys_set_renderer(renderer: Any, /) -> int:
+ """int TCOD_sys_set_renderer(TCOD_renderer_t renderer)"""
+
+ @staticmethod
+ def TCOD_sys_shutdown() -> None:
+ """void TCOD_sys_shutdown(void)"""
+
+ @staticmethod
+ def TCOD_sys_sleep_milli(val: Any, /) -> None:
+ """void TCOD_sys_sleep_milli(uint32_t val)"""
+
+ @staticmethod
+ def TCOD_sys_startup() -> None:
+ """void TCOD_sys_startup(void)"""
+
+ @staticmethod
+ def TCOD_sys_update_char(asciiCode: int, font_x: int, font_y: int, img: Any, x: int, y: int, /) -> None:
+ """void TCOD_sys_update_char(int asciiCode, int font_x, int font_y, const TCOD_Image *img, int x, int y)"""
+
+ @staticmethod
+ def TCOD_sys_wait_for_event(eventMask: int, key: Any, mouse: Any, flush: bool, /) -> Any:
+ """TCOD_event_t TCOD_sys_wait_for_event(int eventMask, TCOD_key_t *key, TCOD_mouse_t *mouse, bool flush)"""
+
+ @staticmethod
+ def TCOD_sys_wait_for_keypress(flush: bool, /) -> Any:
+ """TCOD_key_t TCOD_sys_wait_for_keypress(bool flush)"""
+
+ @staticmethod
+ def TCOD_sys_write_file(filename: Any, buf: Any, size: Any, /) -> bool:
+ """bool TCOD_sys_write_file(const char *filename, unsigned char *buf, uint32_t size)"""
+
+ @staticmethod
+ def TCOD_text_delete(txt: Any, /) -> None:
+ """void TCOD_text_delete(TCOD_text_t txt)"""
+
+ @staticmethod
+ def TCOD_text_get(txt: Any, /) -> Any:
+ """const char *TCOD_text_get(TCOD_text_t txt)"""
+
+ @staticmethod
+ def TCOD_text_init(x: int, y: int, w: int, h: int, max_chars: int, /) -> Any:
+ """TCOD_text_t TCOD_text_init(int x, int y, int w, int h, int max_chars)"""
+
+ @staticmethod
+ def TCOD_text_init2(w: int, h: int, max_chars: int, /) -> Any:
+ """TCOD_text_t TCOD_text_init2(int w, int h, int max_chars)"""
+
+ @staticmethod
+ def TCOD_text_render(txt: Any, con: Any, /) -> None:
+ """void TCOD_text_render(TCOD_text_t txt, TCOD_console_t con)"""
+
+ @staticmethod
+ def TCOD_text_reset(txt: Any, /) -> None:
+ """void TCOD_text_reset(TCOD_text_t txt)"""
+
+ @staticmethod
+ def TCOD_text_set_colors(txt: Any, fore: Any, back: Any, back_transparency: float, /) -> None:
+ """void TCOD_text_set_colors(TCOD_text_t txt, TCOD_color_t fore, TCOD_color_t back, float back_transparency)"""
+
+ @staticmethod
+ def TCOD_text_set_pos(txt: Any, x: int, y: int, /) -> None:
+ """void TCOD_text_set_pos(TCOD_text_t txt, int x, int y)"""
+
+ @staticmethod
+ def TCOD_text_set_properties(
+ txt: Any, cursor_char: int, blink_interval: int, prompt: Any, tab_size: int, /
+ ) -> None:
+ """void TCOD_text_set_properties(TCOD_text_t txt, int cursor_char, int blink_interval, const char *prompt, int tab_size)"""
+
+ @staticmethod
+ def TCOD_text_update(txt: Any, key: Any, /) -> bool:
+ """bool TCOD_text_update(TCOD_text_t txt, TCOD_key_t key)"""
+
+ @staticmethod
+ def TCOD_thread_delete(th: Any, /) -> None:
+ """void TCOD_thread_delete(TCOD_thread_t th)"""
+
+ @staticmethod
+ def TCOD_thread_new(func: Any, data: Any, /) -> Any:
+ """TCOD_thread_t TCOD_thread_new(int (*func)(void *), void *data)"""
+
+ @staticmethod
+ def TCOD_thread_wait(th: Any, /) -> None:
+ """void TCOD_thread_wait(TCOD_thread_t th)"""
+
+ @staticmethod
+ def TCOD_tileset_assign_tile(tileset: Any, tile_id: int, codepoint: int, /) -> int:
+ """int TCOD_tileset_assign_tile(struct TCOD_Tileset *tileset, int tile_id, int codepoint)"""
+
+ @staticmethod
+ def TCOD_tileset_delete(tileset: Any, /) -> None:
+ """void TCOD_tileset_delete(TCOD_Tileset *tileset)"""
+
+ @staticmethod
+ def TCOD_tileset_get_tile(tileset: Any, codepoint: int, /) -> Any:
+ """const struct TCOD_ColorRGBA *TCOD_tileset_get_tile(const TCOD_Tileset *tileset, int codepoint)"""
+
+ @staticmethod
+ def TCOD_tileset_get_tile_(tileset: Any, codepoint: int, buffer: Any, /) -> Any:
+ """TCOD_Error TCOD_tileset_get_tile_(const TCOD_Tileset *tileset, int codepoint, struct TCOD_ColorRGBA *buffer)"""
+
+ @staticmethod
+ def TCOD_tileset_get_tile_height_(tileset: Any, /) -> int:
+ """int TCOD_tileset_get_tile_height_(const TCOD_Tileset *tileset)"""
+
+ @staticmethod
+ def TCOD_tileset_get_tile_width_(tileset: Any, /) -> int:
+ """int TCOD_tileset_get_tile_width_(const TCOD_Tileset *tileset)"""
+
+ @staticmethod
+ def TCOD_tileset_load(filename: Any, columns: int, rows: int, n: int, charmap: Any, /) -> Any:
+ """TCOD_Tileset *TCOD_tileset_load(const char *filename, int columns, int rows, int n, const int *charmap)"""
+
+ @staticmethod
+ def TCOD_tileset_load_fallback_font_(tile_width: int, tile_height: int, /) -> Any:
+ """TCOD_Tileset *TCOD_tileset_load_fallback_font_(int tile_width, int tile_height)"""
+
+ @staticmethod
+ def TCOD_tileset_load_mem(buffer_length: int, buffer: Any, columns: int, rows: int, n: int, charmap: Any, /) -> Any:
+ """TCOD_Tileset *TCOD_tileset_load_mem(size_t buffer_length, const unsigned char *buffer, int columns, int rows, int n, const int *charmap)"""
+
+ @staticmethod
+ def TCOD_tileset_load_raw(
+ width: int, height: int, pixels: Any, columns: int, rows: int, n: int, charmap: Any, /
+ ) -> Any:
+ """TCOD_Tileset *TCOD_tileset_load_raw(int width, int height, const struct TCOD_ColorRGBA *pixels, int columns, int rows, int n, const int *charmap)"""
+
+ @staticmethod
+ def TCOD_tileset_load_truetype_(path: Any, tile_width: int, tile_height: int, /) -> Any:
+ """TCOD_Error TCOD_tileset_load_truetype_(const char *path, int tile_width, int tile_height)"""
+
+ @staticmethod
+ def TCOD_tileset_new(tile_width: int, tile_height: int, /) -> Any:
+ """TCOD_Tileset *TCOD_tileset_new(int tile_width, int tile_height)"""
+
+ @staticmethod
+ def TCOD_tileset_notify_tile_changed(tileset: Any, tile_id: int, /) -> None:
+ """void TCOD_tileset_notify_tile_changed(TCOD_Tileset *tileset, int tile_id)"""
+
+ @staticmethod
+ def TCOD_tileset_observer_delete(observer: Any, /) -> None:
+ """void TCOD_tileset_observer_delete(struct TCOD_TilesetObserver *observer)"""
+
+ @staticmethod
+ def TCOD_tileset_observer_new(tileset: Any, /) -> Any:
+ """struct TCOD_TilesetObserver *TCOD_tileset_observer_new(struct TCOD_Tileset *tileset)"""
+
+ @staticmethod
+ def TCOD_tileset_render_to_surface(tileset: Any, console: Any, cache: Any, surface_out: Any, /) -> Any:
+ """TCOD_Error TCOD_tileset_render_to_surface(const TCOD_Tileset *tileset, const TCOD_Console *console, TCOD_Console **cache, struct SDL_Surface **surface_out)"""
+
+ @staticmethod
+ def TCOD_tileset_reserve(tileset: Any, desired: int, /) -> Any:
+ """TCOD_Error TCOD_tileset_reserve(TCOD_Tileset *tileset, int desired)"""
+
+ @staticmethod
+ def TCOD_tileset_set_tile_(tileset: Any, codepoint: int, buffer: Any, /) -> Any:
+ """TCOD_Error TCOD_tileset_set_tile_(TCOD_Tileset *tileset, int codepoint, const struct TCOD_ColorRGBA *buffer)"""
+
+ @staticmethod
+ def TCOD_tree_add_son(node: Any, son: Any, /) -> None:
+ """void TCOD_tree_add_son(TCOD_tree_t *node, TCOD_tree_t *son)"""
+
+ @staticmethod
+ def TCOD_tree_new() -> Any:
+ """TCOD_tree_t *TCOD_tree_new(void)"""
+
+ @staticmethod
+ def TCOD_viewport_delete(viewport: Any, /) -> None:
+ """void TCOD_viewport_delete(TCOD_ViewportOptions *viewport)"""
+
+ @staticmethod
+ def TCOD_viewport_new() -> Any:
+ """TCOD_ViewportOptions *TCOD_viewport_new(void)"""
+
+ @staticmethod
+ def TCOD_zip_delete(zip: Any, /) -> None:
+ """void TCOD_zip_delete(TCOD_zip_t zip)"""
+
+ @staticmethod
+ def TCOD_zip_get_char(zip: Any, /) -> Any:
+ """char TCOD_zip_get_char(TCOD_zip_t zip)"""
+
+ @staticmethod
+ def TCOD_zip_get_color(zip: Any, /) -> Any:
+ """TCOD_color_t TCOD_zip_get_color(TCOD_zip_t zip)"""
+
+ @staticmethod
+ def TCOD_zip_get_console(zip: Any, /) -> Any:
+ """TCOD_console_t TCOD_zip_get_console(TCOD_zip_t zip)"""
+
+ @staticmethod
+ def TCOD_zip_get_current_bytes(zip: Any, /) -> Any:
+ """uint32_t TCOD_zip_get_current_bytes(TCOD_zip_t zip)"""
+
+ @staticmethod
+ def TCOD_zip_get_data(zip: Any, nbBytes: int, data: Any, /) -> int:
+ """int TCOD_zip_get_data(TCOD_zip_t zip, int nbBytes, void *data)"""
+
+ @staticmethod
+ def TCOD_zip_get_float(zip: Any, /) -> float:
+ """float TCOD_zip_get_float(TCOD_zip_t zip)"""
+
+ @staticmethod
+ def TCOD_zip_get_image(zip: Any, /) -> Any:
+ """TCOD_Image *TCOD_zip_get_image(TCOD_zip_t zip)"""
+
+ @staticmethod
+ def TCOD_zip_get_int(zip: Any, /) -> int:
+ """int TCOD_zip_get_int(TCOD_zip_t zip)"""
+
+ @staticmethod
+ def TCOD_zip_get_random(zip: Any, /) -> Any:
+ """TCOD_Random *TCOD_zip_get_random(TCOD_zip_t zip)"""
+
+ @staticmethod
+ def TCOD_zip_get_remaining_bytes(zip: Any, /) -> Any:
+ """uint32_t TCOD_zip_get_remaining_bytes(TCOD_zip_t zip)"""
+
+ @staticmethod
+ def TCOD_zip_get_string(zip: Any, /) -> Any:
+ """const char *TCOD_zip_get_string(TCOD_zip_t zip)"""
+
+ @staticmethod
+ def TCOD_zip_load_from_file(zip: Any, filename: Any, /) -> int:
+ """int TCOD_zip_load_from_file(TCOD_zip_t zip, const char *filename)"""
+
+ @staticmethod
+ def TCOD_zip_new() -> Any:
+ """TCOD_zip_t TCOD_zip_new(void)"""
+
+ @staticmethod
+ def TCOD_zip_put_char(zip: Any, val: Any, /) -> None:
+ """void TCOD_zip_put_char(TCOD_zip_t zip, char val)"""
+
+ @staticmethod
+ def TCOD_zip_put_color(zip: Any, val: Any, /) -> None:
+ """void TCOD_zip_put_color(TCOD_zip_t zip, const TCOD_color_t val)"""
+
+ @staticmethod
+ def TCOD_zip_put_console(zip: Any, val: Any, /) -> None:
+ """void TCOD_zip_put_console(TCOD_zip_t zip, const TCOD_Console *val)"""
+
+ @staticmethod
+ def TCOD_zip_put_data(zip: Any, nbBytes: int, data: Any, /) -> None:
+ """void TCOD_zip_put_data(TCOD_zip_t zip, int nbBytes, const void *data)"""
+
+ @staticmethod
+ def TCOD_zip_put_float(zip: Any, val: float, /) -> None:
+ """void TCOD_zip_put_float(TCOD_zip_t zip, float val)"""
+
+ @staticmethod
+ def TCOD_zip_put_image(zip: Any, val: Any, /) -> None:
+ """void TCOD_zip_put_image(TCOD_zip_t zip, const TCOD_Image *val)"""
+
+ @staticmethod
+ def TCOD_zip_put_int(zip: Any, val: int, /) -> None:
+ """void TCOD_zip_put_int(TCOD_zip_t zip, int val)"""
+
+ @staticmethod
+ def TCOD_zip_put_random(zip: Any, val: Any, /) -> None:
+ """void TCOD_zip_put_random(TCOD_zip_t zip, const TCOD_Random *val)"""
+
+ @staticmethod
+ def TCOD_zip_put_string(zip: Any, val: Any, /) -> None:
+ """void TCOD_zip_put_string(TCOD_zip_t zip, const char *val)"""
+
+ @staticmethod
+ def TCOD_zip_save_to_file(zip: Any, filename: Any, /) -> int:
+ """int TCOD_zip_save_to_file(TCOD_zip_t zip, const char *filename)"""
+
+ @staticmethod
+ def TCOD_zip_skip_bytes(zip: Any, nbBytes: Any, /) -> None:
+ """void TCOD_zip_skip_bytes(TCOD_zip_t zip, uint32_t nbBytes)"""
+
+ @staticmethod
+ def _libtcod_log_watcher(message: Any, userdata: Any, /) -> None:
+ """void _libtcod_log_watcher(const TCOD_LogMessage *message, void *userdata)"""
+
+ @staticmethod
+ def _pycall_bsp_callback(node: Any, userData: Any, /) -> bool:
+ """bool _pycall_bsp_callback(TCOD_bsp_t *node, void *userData)"""
+
+ @staticmethod
+ def _pycall_cli_output(userdata: Any, output: Any, /) -> None:
+ """void _pycall_cli_output(void *userdata, const char *output)"""
+
+ @staticmethod
+ def _pycall_parser_end_struct(str: Any, name: Any, /) -> bool:
+ """bool _pycall_parser_end_struct(TCOD_parser_struct_t str, const char *name)"""
+
+ @staticmethod
+ def _pycall_parser_error(msg: Any, /) -> None:
+ """void _pycall_parser_error(const char *msg)"""
+
+ @staticmethod
+ def _pycall_parser_new_flag(name: Any, /) -> bool:
+ """bool _pycall_parser_new_flag(const char *name)"""
+
+ @staticmethod
+ def _pycall_parser_new_property(propname: Any, type: Any, value: Any, /) -> bool:
+ """bool _pycall_parser_new_property(const char *propname, TCOD_value_type_t type, TCOD_value_t value)"""
+
+ @staticmethod
+ def _pycall_parser_new_struct(str: Any, name: Any, /) -> bool:
+ """bool _pycall_parser_new_struct(TCOD_parser_struct_t str, const char *name)"""
+
+ @staticmethod
+ def _pycall_path_dest_only(x1: int, y1: int, x2: int, y2: int, user_data: Any, /) -> float:
+ """float _pycall_path_dest_only(int x1, int y1, int x2, int y2, void *user_data)"""
+
+ @staticmethod
+ def _pycall_path_old(x: int, y: int, xDest: int, yDest: int, user_data: Any, /) -> float:
+ """float _pycall_path_old(int x, int y, int xDest, int yDest, void *user_data)"""
+
+ @staticmethod
+ def _pycall_path_simple(x: int, y: int, xDest: int, yDest: int, user_data: Any, /) -> float:
+ """float _pycall_path_simple(int x, int y, int xDest, int yDest, void *user_data)"""
+
+ @staticmethod
+ def _pycall_path_swap_src_dest(x1: int, y1: int, x2: int, y2: int, user_data: Any, /) -> float:
+ """float _pycall_path_swap_src_dest(int x1, int y1, int x2, int y2, void *user_data)"""
+
+ @staticmethod
+ def _pycall_sdl_hook(arg0: Any, /) -> None:
+ """void _pycall_sdl_hook(struct SDL_Surface *)"""
+
+ @staticmethod
+ def _sdl_audio_stream_callback(userdata: Any, stream: Any, additional_amount: int, total_amount: int, /) -> None:
+ """void _sdl_audio_stream_callback(void *userdata, SDL_AudioStream *stream, int additional_amount, int total_amount)"""
+
+ @staticmethod
+ def _sdl_event_watcher(userdata: Any, event: Any, /) -> int:
+ """int _sdl_event_watcher(void *userdata, SDL_Event *event)"""
+
+ @staticmethod
+ def _sdl_log_output_function(userdata: Any, category: int, priority: Any, message: Any, /) -> None:
+ """void _sdl_log_output_function(void *userdata, int category, SDL_LogPriority priority, const char *message)"""
+
+ @staticmethod
+ def alloca(arg0: int, /) -> Any:
+ """void *alloca(size_t)"""
+
+ @staticmethod
+ def bresenham(x1: int, y1: int, x2: int, y2: int, n: int, out: Any, /) -> int:
+ """int bresenham(int x1, int y1, int x2, int y2, int n, int *out)"""
+
+ @staticmethod
+ def compute_heuristic(heuristic: Any, ndim: int, index: Any, /) -> int:
+ """int compute_heuristic(const struct PathfinderHeuristic *heuristic, int ndim, const int *index)"""
+
+ @staticmethod
+ def dijkstra2d(dist: Any, cost: Any, edges_2d_n: int, edges_2d: Any, /) -> int:
+ """int dijkstra2d(struct NArray *dist, const struct NArray *cost, int edges_2d_n, const int *edges_2d)"""
+
+ @staticmethod
+ def dijkstra2d_basic(dist: Any, cost: Any, cardinal: int, diagonal: int, /) -> int:
+ """int dijkstra2d_basic(struct NArray *dist, const struct NArray *cost, int cardinal, int diagonal)"""
+
+ @staticmethod
+ def frontier_has_index(frontier: Any, index: Any, /) -> int:
+ """int frontier_has_index(const struct TCOD_Frontier *frontier, const int *index)"""
+
+ @staticmethod
+ def get_travel_path(ndim: Any, travel_map: Any, start: Any, out: Any, /) -> int:
+ """ptrdiff_t get_travel_path(int8_t ndim, const NArray *travel_map, const int *start, int *out)"""
+
+ @staticmethod
+ def hillclimb2d(dist_array: Any, start_i: int, start_j: int, edges_2d_n: int, edges_2d: Any, out: Any, /) -> int:
+ """int hillclimb2d(const struct NArray *dist_array, int start_i, int start_j, int edges_2d_n, const int *edges_2d, int *out)"""
+
+ @staticmethod
+ def hillclimb2d_basic(dist: Any, x: int, y: int, cardinal: bool, diagonal: bool, out: Any, /) -> int:
+ """int hillclimb2d_basic(const struct NArray *dist, int x, int y, bool cardinal, bool diagonal, int *out)"""
+
+ @staticmethod
+ def path_compute(frontier: Any, dist_map: Any, travel_map: Any, n: int, rules: Any, heuristic: Any, /) -> int:
+ """int path_compute(struct TCOD_Frontier *frontier, struct NArray *dist_map, struct NArray *travel_map, int n, const struct PathfinderRule *rules, const struct PathfinderHeuristic *heuristic)"""
+
+ @staticmethod
+ def path_compute_step(frontier: Any, dist_map: Any, travel_map: Any, n: int, rules: Any, heuristic: Any, /) -> int:
+ """int path_compute_step(struct TCOD_Frontier *frontier, struct NArray *dist_map, struct NArray *travel_map, int n, const struct PathfinderRule *rules, const struct PathfinderHeuristic *heuristic)"""
+
+ @staticmethod
+ def rebuild_frontier_from_distance(frontier: Any, dist_map: Any, /) -> int:
+ """int rebuild_frontier_from_distance(struct TCOD_Frontier *frontier, const NArray *dist_map)"""
+
+ @staticmethod
+ def sync_time_() -> None:
+ """void sync_time_(void)"""
+
+ @staticmethod
+ def update_frontier_heuristic(frontier: Any, heuristic: Any, /) -> int:
+ """int update_frontier_heuristic(struct TCOD_Frontier *frontier, const struct PathfinderHeuristic *heuristic)"""
+
+ FOV_BASIC: Final[int]
+ FOV_DIAMOND: Final[int]
+ FOV_PERMISSIVE_0: Final[int]
+ FOV_PERMISSIVE_1: Final[int]
+ FOV_PERMISSIVE_2: Final[int]
+ FOV_PERMISSIVE_3: Final[int]
+ FOV_PERMISSIVE_4: Final[int]
+ FOV_PERMISSIVE_5: Final[int]
+ FOV_PERMISSIVE_6: Final[int]
+ FOV_PERMISSIVE_7: Final[int]
+ FOV_PERMISSIVE_8: Final[int]
+ FOV_RESTRICTIVE: Final[int]
+ FOV_SHADOW: Final[int]
+ FOV_SYMMETRIC_SHADOWCAST: Final[int]
+ NB_FOV_ALGORITHMS: Final[int]
+ SDLK_0: Final[int]
+ SDLK_1: Final[int]
+ SDLK_2: Final[int]
+ SDLK_3: Final[int]
+ SDLK_4: Final[int]
+ SDLK_5: Final[int]
+ SDLK_6: Final[int]
+ SDLK_7: Final[int]
+ SDLK_8: Final[int]
+ SDLK_9: Final[int]
+ SDLK_A: Final[int]
+ SDLK_AC_BACK: Final[int]
+ SDLK_AC_BOOKMARKS: Final[int]
+ SDLK_AC_CLOSE: Final[int]
+ SDLK_AC_EXIT: Final[int]
+ SDLK_AC_FORWARD: Final[int]
+ SDLK_AC_HOME: Final[int]
+ SDLK_AC_NEW: Final[int]
+ SDLK_AC_OPEN: Final[int]
+ SDLK_AC_PRINT: Final[int]
+ SDLK_AC_PROPERTIES: Final[int]
+ SDLK_AC_REFRESH: Final[int]
+ SDLK_AC_SAVE: Final[int]
+ SDLK_AC_SEARCH: Final[int]
+ SDLK_AC_STOP: Final[int]
+ SDLK_AGAIN: Final[int]
+ SDLK_ALTERASE: Final[int]
+ SDLK_AMPERSAND: Final[int]
+ SDLK_APOSTROPHE: Final[int]
+ SDLK_APPLICATION: Final[int]
+ SDLK_ASTERISK: Final[int]
+ SDLK_AT: Final[int]
+ SDLK_B: Final[int]
+ SDLK_BACKSLASH: Final[int]
+ SDLK_BACKSPACE: Final[int]
+ SDLK_C: Final[int]
+ SDLK_CALL: Final[int]
+ SDLK_CANCEL: Final[int]
+ SDLK_CAPSLOCK: Final[int]
+ SDLK_CARET: Final[int]
+ SDLK_CHANNEL_DECREMENT: Final[int]
+ SDLK_CHANNEL_INCREMENT: Final[int]
+ SDLK_CLEAR: Final[int]
+ SDLK_CLEARAGAIN: Final[int]
+ SDLK_COLON: Final[int]
+ SDLK_COMMA: Final[int]
+ SDLK_COPY: Final[int]
+ SDLK_CRSEL: Final[int]
+ SDLK_CURRENCYSUBUNIT: Final[int]
+ SDLK_CURRENCYUNIT: Final[int]
+ SDLK_CUT: Final[int]
+ SDLK_D: Final[int]
+ SDLK_DBLAPOSTROPHE: Final[int]
+ SDLK_DECIMALSEPARATOR: Final[int]
+ SDLK_DELETE: Final[int]
+ SDLK_DOLLAR: Final[int]
+ SDLK_DOWN: Final[int]
+ SDLK_E: Final[int]
+ SDLK_END: Final[int]
+ SDLK_ENDCALL: Final[int]
+ SDLK_EQUALS: Final[int]
+ SDLK_ESCAPE: Final[int]
+ SDLK_EXCLAIM: Final[int]
+ SDLK_EXECUTE: Final[int]
+ SDLK_EXSEL: Final[int]
+ SDLK_EXTENDED_MASK: Final[int]
+ SDLK_F10: Final[int]
+ SDLK_F11: Final[int]
+ SDLK_F12: Final[int]
+ SDLK_F13: Final[int]
+ SDLK_F14: Final[int]
+ SDLK_F15: Final[int]
+ SDLK_F16: Final[int]
+ SDLK_F17: Final[int]
+ SDLK_F18: Final[int]
+ SDLK_F19: Final[int]
+ SDLK_F1: Final[int]
+ SDLK_F20: Final[int]
+ SDLK_F21: Final[int]
+ SDLK_F22: Final[int]
+ SDLK_F23: Final[int]
+ SDLK_F24: Final[int]
+ SDLK_F2: Final[int]
+ SDLK_F3: Final[int]
+ SDLK_F4: Final[int]
+ SDLK_F5: Final[int]
+ SDLK_F6: Final[int]
+ SDLK_F7: Final[int]
+ SDLK_F8: Final[int]
+ SDLK_F9: Final[int]
+ SDLK_F: Final[int]
+ SDLK_FIND: Final[int]
+ SDLK_G: Final[int]
+ SDLK_GRAVE: Final[int]
+ SDLK_GREATER: Final[int]
+ SDLK_H: Final[int]
+ SDLK_HASH: Final[int]
+ SDLK_HELP: Final[int]
+ SDLK_HOME: Final[int]
+ SDLK_I: Final[int]
+ SDLK_INSERT: Final[int]
+ SDLK_J: Final[int]
+ SDLK_K: Final[int]
+ SDLK_KP_000: Final[int]
+ SDLK_KP_00: Final[int]
+ SDLK_KP_0: Final[int]
+ SDLK_KP_1: Final[int]
+ SDLK_KP_2: Final[int]
+ SDLK_KP_3: Final[int]
+ SDLK_KP_4: Final[int]
+ SDLK_KP_5: Final[int]
+ SDLK_KP_6: Final[int]
+ SDLK_KP_7: Final[int]
+ SDLK_KP_8: Final[int]
+ SDLK_KP_9: Final[int]
+ SDLK_KP_A: Final[int]
+ SDLK_KP_AMPERSAND: Final[int]
+ SDLK_KP_AT: Final[int]
+ SDLK_KP_B: Final[int]
+ SDLK_KP_BACKSPACE: Final[int]
+ SDLK_KP_BINARY: Final[int]
+ SDLK_KP_C: Final[int]
+ SDLK_KP_CLEAR: Final[int]
+ SDLK_KP_CLEARENTRY: Final[int]
+ SDLK_KP_COLON: Final[int]
+ SDLK_KP_COMMA: Final[int]
+ SDLK_KP_D: Final[int]
+ SDLK_KP_DBLAMPERSAND: Final[int]
+ SDLK_KP_DBLVERTICALBAR: Final[int]
+ SDLK_KP_DECIMAL: Final[int]
+ SDLK_KP_DIVIDE: Final[int]
+ SDLK_KP_E: Final[int]
+ SDLK_KP_ENTER: Final[int]
+ SDLK_KP_EQUALS: Final[int]
+ SDLK_KP_EQUALSAS400: Final[int]
+ SDLK_KP_EXCLAM: Final[int]
+ SDLK_KP_F: Final[int]
+ SDLK_KP_GREATER: Final[int]
+ SDLK_KP_HASH: Final[int]
+ SDLK_KP_HEXADECIMAL: Final[int]
+ SDLK_KP_LEFTBRACE: Final[int]
+ SDLK_KP_LEFTPAREN: Final[int]
+ SDLK_KP_LESS: Final[int]
+ SDLK_KP_MEMADD: Final[int]
+ SDLK_KP_MEMCLEAR: Final[int]
+ SDLK_KP_MEMDIVIDE: Final[int]
+ SDLK_KP_MEMMULTIPLY: Final[int]
+ SDLK_KP_MEMRECALL: Final[int]
+ SDLK_KP_MEMSTORE: Final[int]
+ SDLK_KP_MEMSUBTRACT: Final[int]
+ SDLK_KP_MINUS: Final[int]
+ SDLK_KP_MULTIPLY: Final[int]
+ SDLK_KP_OCTAL: Final[int]
+ SDLK_KP_PERCENT: Final[int]
+ SDLK_KP_PERIOD: Final[int]
+ SDLK_KP_PLUS: Final[int]
+ SDLK_KP_PLUSMINUS: Final[int]
+ SDLK_KP_POWER: Final[int]
+ SDLK_KP_RIGHTBRACE: Final[int]
+ SDLK_KP_RIGHTPAREN: Final[int]
+ SDLK_KP_SPACE: Final[int]
+ SDLK_KP_TAB: Final[int]
+ SDLK_KP_VERTICALBAR: Final[int]
+ SDLK_KP_XOR: Final[int]
+ SDLK_L: Final[int]
+ SDLK_LALT: Final[int]
+ SDLK_LCTRL: Final[int]
+ SDLK_LEFT: Final[int]
+ SDLK_LEFTBRACE: Final[int]
+ SDLK_LEFTBRACKET: Final[int]
+ SDLK_LEFTPAREN: Final[int]
+ SDLK_LEFT_TAB: Final[int]
+ SDLK_LESS: Final[int]
+ SDLK_LEVEL5_SHIFT: Final[int]
+ SDLK_LGUI: Final[int]
+ SDLK_LHYPER: Final[int]
+ SDLK_LMETA: Final[int]
+ SDLK_LSHIFT: Final[int]
+ SDLK_M: Final[int]
+ SDLK_MEDIA_EJECT: Final[int]
+ SDLK_MEDIA_FAST_FORWARD: Final[int]
+ SDLK_MEDIA_NEXT_TRACK: Final[int]
+ SDLK_MEDIA_PAUSE: Final[int]
+ SDLK_MEDIA_PLAY: Final[int]
+ SDLK_MEDIA_PLAY_PAUSE: Final[int]
+ SDLK_MEDIA_PREVIOUS_TRACK: Final[int]
+ SDLK_MEDIA_RECORD: Final[int]
+ SDLK_MEDIA_REWIND: Final[int]
+ SDLK_MEDIA_SELECT: Final[int]
+ SDLK_MEDIA_STOP: Final[int]
+ SDLK_MENU: Final[int]
+ SDLK_MINUS: Final[int]
+ SDLK_MODE: Final[int]
+ SDLK_MULTI_KEY_COMPOSE: Final[int]
+ SDLK_MUTE: Final[int]
+ SDLK_N: Final[int]
+ SDLK_NUMLOCKCLEAR: Final[int]
+ SDLK_O: Final[int]
+ SDLK_OPER: Final[int]
+ SDLK_OUT: Final[int]
+ SDLK_P: Final[int]
+ SDLK_PAGEDOWN: Final[int]
+ SDLK_PAGEUP: Final[int]
+ SDLK_PASTE: Final[int]
+ SDLK_PAUSE: Final[int]
+ SDLK_PERCENT: Final[int]
+ SDLK_PERIOD: Final[int]
+ SDLK_PIPE: Final[int]
+ SDLK_PLUS: Final[int]
+ SDLK_PLUSMINUS: Final[int]
+ SDLK_POWER: Final[int]
+ SDLK_PRINTSCREEN: Final[int]
+ SDLK_PRIOR: Final[int]
+ SDLK_Q: Final[int]
+ SDLK_QUESTION: Final[int]
+ SDLK_R: Final[int]
+ SDLK_RALT: Final[int]
+ SDLK_RCTRL: Final[int]
+ SDLK_RETURN2: Final[int]
+ SDLK_RETURN: Final[int]
+ SDLK_RGUI: Final[int]
+ SDLK_RHYPER: Final[int]
+ SDLK_RIGHT: Final[int]
+ SDLK_RIGHTBRACE: Final[int]
+ SDLK_RIGHTBRACKET: Final[int]
+ SDLK_RIGHTPAREN: Final[int]
+ SDLK_RMETA: Final[int]
+ SDLK_RSHIFT: Final[int]
+ SDLK_S: Final[int]
+ SDLK_SCANCODE_MASK: Final[int]
+ SDLK_SCROLLLOCK: Final[int]
+ SDLK_SELECT: Final[int]
+ SDLK_SEMICOLON: Final[int]
+ SDLK_SEPARATOR: Final[int]
+ SDLK_SLASH: Final[int]
+ SDLK_SLEEP: Final[int]
+ SDLK_SOFTLEFT: Final[int]
+ SDLK_SOFTRIGHT: Final[int]
+ SDLK_SPACE: Final[int]
+ SDLK_STOP: Final[int]
+ SDLK_SYSREQ: Final[int]
+ SDLK_T: Final[int]
+ SDLK_TAB: Final[int]
+ SDLK_THOUSANDSSEPARATOR: Final[int]
+ SDLK_TILDE: Final[int]
+ SDLK_U: Final[int]
+ SDLK_UNDERSCORE: Final[int]
+ SDLK_UNDO: Final[int]
+ SDLK_UNKNOWN: Final[int]
+ SDLK_UP: Final[int]
+ SDLK_V: Final[int]
+ SDLK_VOLUMEDOWN: Final[int]
+ SDLK_VOLUMEUP: Final[int]
+ SDLK_W: Final[int]
+ SDLK_WAKE: Final[int]
+ SDLK_X: Final[int]
+ SDLK_Y: Final[int]
+ SDLK_Z: Final[int]
+ SDL_ADDEVENT: Final[int]
+ SDL_ALPHA_OPAQUE: Final[int]
+ SDL_ALPHA_TRANSPARENT: Final[int]
+ SDL_APP_CONTINUE: Final[int]
+ SDL_APP_FAILURE: Final[int]
+ SDL_APP_SUCCESS: Final[int]
+ SDL_ARRAYORDER_ABGR: Final[int]
+ SDL_ARRAYORDER_ARGB: Final[int]
+ SDL_ARRAYORDER_BGR: Final[int]
+ SDL_ARRAYORDER_BGRA: Final[int]
+ SDL_ARRAYORDER_NONE: Final[int]
+ SDL_ARRAYORDER_RGB: Final[int]
+ SDL_ARRAYORDER_RGBA: Final[int]
+ SDL_ASSERTION_ABORT: Final[int]
+ SDL_ASSERTION_ALWAYS_IGNORE: Final[int]
+ SDL_ASSERTION_BREAK: Final[int]
+ SDL_ASSERTION_IGNORE: Final[int]
+ SDL_ASSERTION_RETRY: Final[int]
+ SDL_ASSERT_LEVEL: Final[int]
+ SDL_ASYNCIO_CANCELED: Final[int]
+ SDL_ASYNCIO_COMPLETE: Final[int]
+ SDL_ASYNCIO_FAILURE: Final[int]
+ SDL_ASYNCIO_TASK_CLOSE: Final[int]
+ SDL_ASYNCIO_TASK_READ: Final[int]
+ SDL_ASYNCIO_TASK_WRITE: Final[int]
+ SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK: Final[int]
+ SDL_AUDIO_DEVICE_DEFAULT_RECORDING: Final[int]
+ SDL_AUDIO_F32: Final[int]
+ SDL_AUDIO_F32BE: Final[Literal[37152]] = 37152
+ SDL_AUDIO_F32LE: Final[Literal[33056]] = 33056
+ SDL_AUDIO_MASK_BIG_ENDIAN: Final[int]
+ SDL_AUDIO_MASK_BITSIZE: Final[int]
+ SDL_AUDIO_MASK_FLOAT: Final[int]
+ SDL_AUDIO_MASK_SIGNED: Final[int]
+ SDL_AUDIO_S16: Final[int]
+ SDL_AUDIO_S16BE: Final[Literal[36880]] = 36880
+ SDL_AUDIO_S16LE: Final[Literal[32784]] = 32784
+ SDL_AUDIO_S32: Final[int]
+ SDL_AUDIO_S32BE: Final[Literal[36896]] = 36896
+ SDL_AUDIO_S32LE: Final[Literal[32800]] = 32800
+ SDL_AUDIO_S8: Final[Literal[32776]] = 32776
+ SDL_AUDIO_U8: Final[Literal[8]] = 8
+ SDL_AUDIO_UNKNOWN: Final[Literal[0]] = 0
+ SDL_BIG_ENDIAN: Final[int]
+ SDL_BITMAPORDER_1234: Final[int]
+ SDL_BITMAPORDER_4321: Final[int]
+ SDL_BITMAPORDER_NONE: Final[int]
+ SDL_BLENDFACTOR_DST_ALPHA: Final[Literal[9]] = 9
+ SDL_BLENDFACTOR_DST_COLOR: Final[Literal[7]] = 7
+ SDL_BLENDFACTOR_ONE: Final[Literal[2]] = 2
+ SDL_BLENDFACTOR_ONE_MINUS_DST_ALPHA: Final[Literal[10]] = 10
+ SDL_BLENDFACTOR_ONE_MINUS_DST_COLOR: Final[Literal[8]] = 8
+ SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA: Final[Literal[6]] = 6
+ SDL_BLENDFACTOR_ONE_MINUS_SRC_COLOR: Final[Literal[4]] = 4
+ SDL_BLENDFACTOR_SRC_ALPHA: Final[Literal[5]] = 5
+ SDL_BLENDFACTOR_SRC_COLOR: Final[Literal[3]] = 3
+ SDL_BLENDFACTOR_ZERO: Final[Literal[1]] = 1
+ SDL_BLENDMODE_ADD: Final[int]
+ SDL_BLENDMODE_ADD_PREMULTIPLIED: Final[int]
+ SDL_BLENDMODE_BLEND: Final[int]
+ SDL_BLENDMODE_BLEND_PREMULTIPLIED: Final[int]
+ SDL_BLENDMODE_INVALID: Final[int]
+ SDL_BLENDMODE_MOD: Final[int]
+ SDL_BLENDMODE_MUL: Final[int]
+ SDL_BLENDMODE_NONE: Final[int]
+ SDL_BLENDOPERATION_ADD: Final[Literal[1]] = 1
+ SDL_BLENDOPERATION_MAXIMUM: Final[Literal[5]] = 5
+ SDL_BLENDOPERATION_MINIMUM: Final[Literal[4]] = 4
+ SDL_BLENDOPERATION_REV_SUBTRACT: Final[Literal[3]] = 3
+ SDL_BLENDOPERATION_SUBTRACT: Final[Literal[2]] = 2
+ SDL_BUTTON_LEFT: Final[int]
+ SDL_BUTTON_LMASK: Final[int]
+ SDL_BUTTON_MIDDLE: Final[int]
+ SDL_BUTTON_MMASK: Final[int]
+ SDL_BUTTON_RIGHT: Final[int]
+ SDL_BUTTON_RMASK: Final[int]
+ SDL_BUTTON_X1: Final[int]
+ SDL_BUTTON_X1MASK: Final[int]
+ SDL_BUTTON_X2: Final[int]
+ SDL_BUTTON_X2MASK: Final[int]
+ SDL_BYTEORDER: Final[int]
+ SDL_CACHELINE_SIZE: Final[int]
+ SDL_CAMERA_POSITION_BACK_FACING: Final[int]
+ SDL_CAMERA_POSITION_FRONT_FACING: Final[int]
+ SDL_CAMERA_POSITION_UNKNOWN: Final[int]
+ SDL_CAPITALIZE_LETTERS: Final[int]
+ SDL_CAPITALIZE_NONE: Final[int]
+ SDL_CAPITALIZE_SENTENCES: Final[int]
+ SDL_CAPITALIZE_WORDS: Final[int]
+ SDL_CHROMA_LOCATION_CENTER: Final[Literal[2]] = 2
+ SDL_CHROMA_LOCATION_LEFT: Final[Literal[1]] = 1
+ SDL_CHROMA_LOCATION_NONE: Final[Literal[0]] = 0
+ SDL_CHROMA_LOCATION_TOPLEFT: Final[Literal[3]] = 3
+ SDL_COLORSPACE_BT2020_FULL: Final[Literal[571483657]] = 571483657
+ SDL_COLORSPACE_BT2020_LIMITED: Final[Literal[554706441]] = 554706441
+ SDL_COLORSPACE_BT601_FULL: Final[Literal[571480262]] = 571480262
+ SDL_COLORSPACE_BT601_LIMITED: Final[Literal[554703046]] = 554703046
+ SDL_COLORSPACE_BT709_FULL: Final[Literal[571474977]] = 571474977
+ SDL_COLORSPACE_BT709_LIMITED: Final[Literal[554697761]] = 554697761
+ SDL_COLORSPACE_HDR10: Final[Literal[301999616]] = 301999616
+ SDL_COLORSPACE_JPEG: Final[Literal[570426566]] = 570426566
+ SDL_COLORSPACE_RGB_DEFAULT: Final[int]
+ SDL_COLORSPACE_SRGB: Final[Literal[301991328]] = 301991328
+ SDL_COLORSPACE_SRGB_LINEAR: Final[Literal[301991168]] = 301991168
+ SDL_COLORSPACE_UNKNOWN: Final[Literal[0]] = 0
+ SDL_COLORSPACE_YUV_DEFAULT: Final[int]
+ SDL_COLOR_PRIMARIES_BT2020: Final[Literal[9]] = 9
+ SDL_COLOR_PRIMARIES_BT470BG: Final[Literal[5]] = 5
+ SDL_COLOR_PRIMARIES_BT470M: Final[Literal[4]] = 4
+ SDL_COLOR_PRIMARIES_BT601: Final[Literal[6]] = 6
+ SDL_COLOR_PRIMARIES_BT709: Final[Literal[1]] = 1
+ SDL_COLOR_PRIMARIES_CUSTOM: Final[Literal[31]] = 31
+ SDL_COLOR_PRIMARIES_EBU3213: Final[Literal[22]] = 22
+ SDL_COLOR_PRIMARIES_GENERIC_FILM: Final[Literal[8]] = 8
+ SDL_COLOR_PRIMARIES_SMPTE240: Final[Literal[7]] = 7
+ SDL_COLOR_PRIMARIES_SMPTE431: Final[Literal[11]] = 11
+ SDL_COLOR_PRIMARIES_SMPTE432: Final[Literal[12]] = 12
+ SDL_COLOR_PRIMARIES_UNKNOWN: Final[Literal[0]] = 0
+ SDL_COLOR_PRIMARIES_UNSPECIFIED: Final[Literal[2]] = 2
+ SDL_COLOR_PRIMARIES_XYZ: Final[Literal[10]] = 10
+ SDL_COLOR_RANGE_FULL: Final[Literal[2]] = 2
+ SDL_COLOR_RANGE_LIMITED: Final[Literal[1]] = 1
+ SDL_COLOR_RANGE_UNKNOWN: Final[Literal[0]] = 0
+ SDL_COLOR_TYPE_RGB: Final[Literal[1]] = 1
+ SDL_COLOR_TYPE_UNKNOWN: Final[Literal[0]] = 0
+ SDL_COLOR_TYPE_YCBCR: Final[Literal[2]] = 2
+ SDL_DATE_FORMAT_DDMMYYYY: Final[Literal[1]] = 1
+ SDL_DATE_FORMAT_MMDDYYYY: Final[Literal[2]] = 2
+ SDL_DATE_FORMAT_YYYYMMDD: Final[Literal[0]] = 0
+ SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE: Final[int]
+ SDL_ENUM_CONTINUE: Final[int]
+ SDL_ENUM_FAILURE: Final[int]
+ SDL_ENUM_SUCCESS: Final[int]
+ SDL_EVENT_AUDIO_DEVICE_ADDED: Final[Literal[4352]] = 4352
+ SDL_EVENT_AUDIO_DEVICE_FORMAT_CHANGED: Final[int]
+ SDL_EVENT_AUDIO_DEVICE_REMOVED: Final[int]
+ SDL_EVENT_CAMERA_DEVICE_ADDED: Final[Literal[5120]] = 5120
+ SDL_EVENT_CAMERA_DEVICE_APPROVED: Final[int]
+ SDL_EVENT_CAMERA_DEVICE_DENIED: Final[int]
+ SDL_EVENT_CAMERA_DEVICE_REMOVED: Final[int]
+ SDL_EVENT_CLIPBOARD_UPDATE: Final[Literal[2304]] = 2304
+ SDL_EVENT_DID_ENTER_BACKGROUND: Final[int]
+ SDL_EVENT_DID_ENTER_FOREGROUND: Final[int]
+ SDL_EVENT_DISPLAY_ADDED: Final[int]
+ SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED: Final[int]
+ SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED: Final[int]
+ SDL_EVENT_DISPLAY_DESKTOP_MODE_CHANGED: Final[int]
+ SDL_EVENT_DISPLAY_FIRST: Final[int]
+ SDL_EVENT_DISPLAY_LAST: Final[int]
+ SDL_EVENT_DISPLAY_MOVED: Final[int]
+ SDL_EVENT_DISPLAY_ORIENTATION: Final[Literal[337]] = 337
+ SDL_EVENT_DISPLAY_REMOVED: Final[int]
+ SDL_EVENT_DROP_BEGIN: Final[int]
+ SDL_EVENT_DROP_COMPLETE: Final[int]
+ SDL_EVENT_DROP_FILE: Final[Literal[4096]] = 4096
+ SDL_EVENT_DROP_POSITION: Final[int]
+ SDL_EVENT_DROP_TEXT: Final[int]
+ SDL_EVENT_ENUM_PADDING: Final[Literal[2147483647]] = 2147483647
+ SDL_EVENT_FINGER_CANCELED: Final[int]
+ SDL_EVENT_FINGER_DOWN: Final[Literal[1792]] = 1792
+ SDL_EVENT_FINGER_MOTION: Final[int]
+ SDL_EVENT_FINGER_UP: Final[int]
+ SDL_EVENT_FIRST: Final[Literal[0]] = 0
+ SDL_EVENT_GAMEPAD_ADDED: Final[int]
+ SDL_EVENT_GAMEPAD_AXIS_MOTION: Final[Literal[1616]] = 1616
+ SDL_EVENT_GAMEPAD_BUTTON_DOWN: Final[int]
+ SDL_EVENT_GAMEPAD_BUTTON_UP: Final[int]
+ SDL_EVENT_GAMEPAD_REMAPPED: Final[int]
+ SDL_EVENT_GAMEPAD_REMOVED: Final[int]
+ SDL_EVENT_GAMEPAD_SENSOR_UPDATE: Final[int]
+ SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED: Final[int]
+ SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN: Final[int]
+ SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION: Final[int]
+ SDL_EVENT_GAMEPAD_TOUCHPAD_UP: Final[int]
+ SDL_EVENT_GAMEPAD_UPDATE_COMPLETE: Final[int]
+ SDL_EVENT_JOYSTICK_ADDED: Final[int]
+ SDL_EVENT_JOYSTICK_AXIS_MOTION: Final[Literal[1536]] = 1536
+ SDL_EVENT_JOYSTICK_BALL_MOTION: Final[int]
+ SDL_EVENT_JOYSTICK_BATTERY_UPDATED: Final[int]
+ SDL_EVENT_JOYSTICK_BUTTON_DOWN: Final[int]
+ SDL_EVENT_JOYSTICK_BUTTON_UP: Final[int]
+ SDL_EVENT_JOYSTICK_HAT_MOTION: Final[int]
+ SDL_EVENT_JOYSTICK_REMOVED: Final[int]
+ SDL_EVENT_JOYSTICK_UPDATE_COMPLETE: Final[int]
+ SDL_EVENT_KEYBOARD_ADDED: Final[int]
+ SDL_EVENT_KEYBOARD_REMOVED: Final[int]
+ SDL_EVENT_KEYMAP_CHANGED: Final[int]
+ SDL_EVENT_KEY_DOWN: Final[Literal[768]] = 768
+ SDL_EVENT_KEY_UP: Final[int]
+ SDL_EVENT_LAST: Final[Literal[65535]] = 65535
+ SDL_EVENT_LOCALE_CHANGED: Final[int]
+ SDL_EVENT_LOW_MEMORY: Final[int]
+ SDL_EVENT_MOUSE_ADDED: Final[int]
+ SDL_EVENT_MOUSE_BUTTON_DOWN: Final[int]
+ SDL_EVENT_MOUSE_BUTTON_UP: Final[int]
+ SDL_EVENT_MOUSE_MOTION: Final[Literal[1024]] = 1024
+ SDL_EVENT_MOUSE_REMOVED: Final[int]
+ SDL_EVENT_MOUSE_WHEEL: Final[int]
+ SDL_EVENT_PEN_AXIS: Final[int]
+ SDL_EVENT_PEN_BUTTON_DOWN: Final[int]
+ SDL_EVENT_PEN_BUTTON_UP: Final[int]
+ SDL_EVENT_PEN_DOWN: Final[int]
+ SDL_EVENT_PEN_MOTION: Final[int]
+ SDL_EVENT_PEN_PROXIMITY_IN: Final[Literal[4864]] = 4864
+ SDL_EVENT_PEN_PROXIMITY_OUT: Final[int]
+ SDL_EVENT_PEN_UP: Final[int]
+ SDL_EVENT_POLL_SENTINEL: Final[Literal[32512]] = 32512
+ SDL_EVENT_PRIVATE0: Final[Literal[16384]] = 16384
+ SDL_EVENT_PRIVATE1: Final[int]
+ SDL_EVENT_PRIVATE2: Final[int]
+ SDL_EVENT_PRIVATE3: Final[int]
+ SDL_EVENT_QUIT: Final[Literal[256]] = 256
+ SDL_EVENT_RENDER_DEVICE_LOST: Final[int]
+ SDL_EVENT_RENDER_DEVICE_RESET: Final[int]
+ SDL_EVENT_RENDER_TARGETS_RESET: Final[Literal[8192]] = 8192
+ SDL_EVENT_SENSOR_UPDATE: Final[Literal[4608]] = 4608
+ SDL_EVENT_SYSTEM_THEME_CHANGED: Final[int]
+ SDL_EVENT_TERMINATING: Final[int]
+ SDL_EVENT_TEXT_EDITING: Final[int]
+ SDL_EVENT_TEXT_EDITING_CANDIDATES: Final[int]
+ SDL_EVENT_TEXT_INPUT: Final[int]
+ SDL_EVENT_USER: Final[Literal[32768]] = 32768
+ SDL_EVENT_WILL_ENTER_BACKGROUND: Final[int]
+ SDL_EVENT_WILL_ENTER_FOREGROUND: Final[int]
+ SDL_EVENT_WINDOW_CLOSE_REQUESTED: Final[int]
+ SDL_EVENT_WINDOW_DESTROYED: Final[int]
+ SDL_EVENT_WINDOW_DISPLAY_CHANGED: Final[int]
+ SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED: Final[int]
+ SDL_EVENT_WINDOW_ENTER_FULLSCREEN: Final[int]
+ SDL_EVENT_WINDOW_EXPOSED: Final[int]
+ SDL_EVENT_WINDOW_FIRST: Final[int]
+ SDL_EVENT_WINDOW_FOCUS_GAINED: Final[int]
+ SDL_EVENT_WINDOW_FOCUS_LOST: Final[int]
+ SDL_EVENT_WINDOW_HDR_STATE_CHANGED: Final[int]
+ SDL_EVENT_WINDOW_HIDDEN: Final[int]
+ SDL_EVENT_WINDOW_HIT_TEST: Final[int]
+ SDL_EVENT_WINDOW_ICCPROF_CHANGED: Final[int]
+ SDL_EVENT_WINDOW_LAST: Final[int]
+ SDL_EVENT_WINDOW_LEAVE_FULLSCREEN: Final[int]
+ SDL_EVENT_WINDOW_MAXIMIZED: Final[int]
+ SDL_EVENT_WINDOW_METAL_VIEW_RESIZED: Final[int]
+ SDL_EVENT_WINDOW_MINIMIZED: Final[int]
+ SDL_EVENT_WINDOW_MOUSE_ENTER: Final[int]
+ SDL_EVENT_WINDOW_MOUSE_LEAVE: Final[int]
+ SDL_EVENT_WINDOW_MOVED: Final[int]
+ SDL_EVENT_WINDOW_OCCLUDED: Final[int]
+ SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: Final[int]
+ SDL_EVENT_WINDOW_RESIZED: Final[int]
+ SDL_EVENT_WINDOW_RESTORED: Final[int]
+ SDL_EVENT_WINDOW_SAFE_AREA_CHANGED: Final[int]
+ SDL_EVENT_WINDOW_SHOWN: Final[Literal[514]] = 514
+ SDL_FILEDIALOG_OPENFILE: Final[int]
+ SDL_FILEDIALOG_OPENFOLDER: Final[int]
+ SDL_FILEDIALOG_SAVEFILE: Final[int]
+ SDL_FLASH_BRIEFLY: Final[int]
+ SDL_FLASH_CANCEL: Final[int]
+ SDL_FLASH_UNTIL_FOCUSED: Final[int]
+ SDL_FLIP_HORIZONTAL: Final[int]
+ SDL_FLIP_NONE: Final[int]
+ SDL_FLIP_VERTICAL: Final[int]
+ SDL_FLOATWORDORDER: Final[int]
+ SDL_FOLDER_COUNT: Final[int]
+ SDL_FOLDER_DESKTOP: Final[int]
+ SDL_FOLDER_DOCUMENTS: Final[int]
+ SDL_FOLDER_DOWNLOADS: Final[int]
+ SDL_FOLDER_HOME: Final[int]
+ SDL_FOLDER_MUSIC: Final[int]
+ SDL_FOLDER_PICTURES: Final[int]
+ SDL_FOLDER_PUBLICSHARE: Final[int]
+ SDL_FOLDER_SAVEDGAMES: Final[int]
+ SDL_FOLDER_SCREENSHOTS: Final[int]
+ SDL_FOLDER_TEMPLATES: Final[int]
+ SDL_FOLDER_VIDEOS: Final[int]
+ SDL_GAMEPAD_AXIS_COUNT: Final[int]
+ SDL_GAMEPAD_AXIS_INVALID: Final[Literal[-1]] = -1
+ SDL_GAMEPAD_AXIS_LEFTX: Final[int]
+ SDL_GAMEPAD_AXIS_LEFTY: Final[int]
+ SDL_GAMEPAD_AXIS_LEFT_TRIGGER: Final[int]
+ SDL_GAMEPAD_AXIS_RIGHTX: Final[int]
+ SDL_GAMEPAD_AXIS_RIGHTY: Final[int]
+ SDL_GAMEPAD_AXIS_RIGHT_TRIGGER: Final[int]
+ SDL_GAMEPAD_BINDTYPE_AXIS: Final[int]
+ SDL_GAMEPAD_BINDTYPE_BUTTON: Final[int]
+ SDL_GAMEPAD_BINDTYPE_HAT: Final[int]
+ SDL_GAMEPAD_BINDTYPE_NONE: Final[Literal[0]] = 0
+ SDL_GAMEPAD_BUTTON_BACK: Final[int]
+ SDL_GAMEPAD_BUTTON_COUNT: Final[int]
+ SDL_GAMEPAD_BUTTON_DPAD_DOWN: Final[int]
+ SDL_GAMEPAD_BUTTON_DPAD_LEFT: Final[int]
+ SDL_GAMEPAD_BUTTON_DPAD_RIGHT: Final[int]
+ SDL_GAMEPAD_BUTTON_DPAD_UP: Final[int]
+ SDL_GAMEPAD_BUTTON_EAST: Final[int]
+ SDL_GAMEPAD_BUTTON_GUIDE: Final[int]
+ SDL_GAMEPAD_BUTTON_INVALID: Final[Literal[-1]] = -1
+ SDL_GAMEPAD_BUTTON_LABEL_A: Final[int]
+ SDL_GAMEPAD_BUTTON_LABEL_B: Final[int]
+ SDL_GAMEPAD_BUTTON_LABEL_CIRCLE: Final[int]
+ SDL_GAMEPAD_BUTTON_LABEL_CROSS: Final[int]
+ SDL_GAMEPAD_BUTTON_LABEL_SQUARE: Final[int]
+ SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE: Final[int]
+ SDL_GAMEPAD_BUTTON_LABEL_UNKNOWN: Final[int]
+ SDL_GAMEPAD_BUTTON_LABEL_X: Final[int]
+ SDL_GAMEPAD_BUTTON_LABEL_Y: Final[int]
+ SDL_GAMEPAD_BUTTON_LEFT_PADDLE1: Final[int]
+ SDL_GAMEPAD_BUTTON_LEFT_PADDLE2: Final[int]
+ SDL_GAMEPAD_BUTTON_LEFT_SHOULDER: Final[int]
+ SDL_GAMEPAD_BUTTON_LEFT_STICK: Final[int]
+ SDL_GAMEPAD_BUTTON_MISC1: Final[int]
+ SDL_GAMEPAD_BUTTON_MISC2: Final[int]
+ SDL_GAMEPAD_BUTTON_MISC3: Final[int]
+ SDL_GAMEPAD_BUTTON_MISC4: Final[int]
+ SDL_GAMEPAD_BUTTON_MISC5: Final[int]
+ SDL_GAMEPAD_BUTTON_MISC6: Final[int]
+ SDL_GAMEPAD_BUTTON_NORTH: Final[int]
+ SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1: Final[int]
+ SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2: Final[int]
+ SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER: Final[int]
+ SDL_GAMEPAD_BUTTON_RIGHT_STICK: Final[int]
+ SDL_GAMEPAD_BUTTON_SOUTH: Final[int]
+ SDL_GAMEPAD_BUTTON_START: Final[int]
+ SDL_GAMEPAD_BUTTON_TOUCHPAD: Final[int]
+ SDL_GAMEPAD_BUTTON_WEST: Final[int]
+ SDL_GAMEPAD_TYPE_COUNT: Final[int]
+ SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT: Final[int]
+ SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR: Final[int]
+ SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT: Final[int]
+ SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO: Final[int]
+ SDL_GAMEPAD_TYPE_PS3: Final[int]
+ SDL_GAMEPAD_TYPE_PS4: Final[int]
+ SDL_GAMEPAD_TYPE_PS5: Final[int]
+ SDL_GAMEPAD_TYPE_STANDARD: Final[int]
+ SDL_GAMEPAD_TYPE_UNKNOWN: Final[Literal[0]] = 0
+ SDL_GAMEPAD_TYPE_XBOX360: Final[int]
+ SDL_GAMEPAD_TYPE_XBOXONE: Final[int]
+ SDL_GETEVENT: Final[int]
+ SDL_GLOB_CASEINSENSITIVE: Final[int]
+ SDL_GL_ACCELERATED_VISUAL: Final[int]
+ SDL_GL_ACCUM_ALPHA_SIZE: Final[int]
+ SDL_GL_ACCUM_BLUE_SIZE: Final[int]
+ SDL_GL_ACCUM_GREEN_SIZE: Final[int]
+ SDL_GL_ACCUM_RED_SIZE: Final[int]
+ SDL_GL_ALPHA_SIZE: Final[int]
+ SDL_GL_BLUE_SIZE: Final[int]
+ SDL_GL_BUFFER_SIZE: Final[int]
+ SDL_GL_CONTEXT_DEBUG_FLAG: Final[int]
+ SDL_GL_CONTEXT_FLAGS: Final[int]
+ SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG: Final[int]
+ SDL_GL_CONTEXT_MAJOR_VERSION: Final[int]
+ SDL_GL_CONTEXT_MINOR_VERSION: Final[int]
+ SDL_GL_CONTEXT_NO_ERROR: Final[int]
+ SDL_GL_CONTEXT_PROFILE_COMPATIBILITY: Final[int]
+ SDL_GL_CONTEXT_PROFILE_CORE: Final[int]
+ SDL_GL_CONTEXT_PROFILE_ES: Final[int]
+ SDL_GL_CONTEXT_PROFILE_MASK: Final[int]
+ SDL_GL_CONTEXT_RELEASE_BEHAVIOR: Final[int]
+ SDL_GL_CONTEXT_RELEASE_BEHAVIOR_FLUSH: Final[int]
+ SDL_GL_CONTEXT_RELEASE_BEHAVIOR_NONE: Final[int]
+ SDL_GL_CONTEXT_RESET_ISOLATION_FLAG: Final[int]
+ SDL_GL_CONTEXT_RESET_LOSE_CONTEXT: Final[int]
+ SDL_GL_CONTEXT_RESET_NOTIFICATION: Final[int]
+ SDL_GL_CONTEXT_RESET_NO_NOTIFICATION: Final[int]
+ SDL_GL_CONTEXT_ROBUST_ACCESS_FLAG: Final[int]
+ SDL_GL_DEPTH_SIZE: Final[int]
+ SDL_GL_DOUBLEBUFFER: Final[int]
+ SDL_GL_EGL_PLATFORM: Final[int]
+ SDL_GL_FLOATBUFFERS: Final[int]
+ SDL_GL_FRAMEBUFFER_SRGB_CAPABLE: Final[int]
+ SDL_GL_GREEN_SIZE: Final[int]
+ SDL_GL_MULTISAMPLEBUFFERS: Final[int]
+ SDL_GL_MULTISAMPLESAMPLES: Final[int]
+ SDL_GL_RED_SIZE: Final[int]
+ SDL_GL_RETAINED_BACKING: Final[int]
+ SDL_GL_SHARE_WITH_CURRENT_CONTEXT: Final[int]
+ SDL_GL_STENCIL_SIZE: Final[int]
+ SDL_GL_STEREO: Final[int]
+ SDL_GPU_BLENDFACTOR_CONSTANT_COLOR: Final[int]
+ SDL_GPU_BLENDFACTOR_DST_ALPHA: Final[int]
+ SDL_GPU_BLENDFACTOR_DST_COLOR: Final[int]
+ SDL_GPU_BLENDFACTOR_INVALID: Final[int]
+ SDL_GPU_BLENDFACTOR_ONE: Final[int]
+ SDL_GPU_BLENDFACTOR_ONE_MINUS_CONSTANT_COLOR: Final[int]
+ SDL_GPU_BLENDFACTOR_ONE_MINUS_DST_ALPHA: Final[int]
+ SDL_GPU_BLENDFACTOR_ONE_MINUS_DST_COLOR: Final[int]
+ SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA: Final[int]
+ SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_COLOR: Final[int]
+ SDL_GPU_BLENDFACTOR_SRC_ALPHA: Final[int]
+ SDL_GPU_BLENDFACTOR_SRC_ALPHA_SATURATE: Final[int]
+ SDL_GPU_BLENDFACTOR_SRC_COLOR: Final[int]
+ SDL_GPU_BLENDFACTOR_ZERO: Final[int]
+ SDL_GPU_BLENDOP_ADD: Final[int]
+ SDL_GPU_BLENDOP_INVALID: Final[int]
+ SDL_GPU_BLENDOP_MAX: Final[int]
+ SDL_GPU_BLENDOP_MIN: Final[int]
+ SDL_GPU_BLENDOP_REVERSE_SUBTRACT: Final[int]
+ SDL_GPU_BLENDOP_SUBTRACT: Final[int]
+ SDL_GPU_BUFFERUSAGE_COMPUTE_STORAGE_READ: Final[int]
+ SDL_GPU_BUFFERUSAGE_COMPUTE_STORAGE_WRITE: Final[int]
+ SDL_GPU_BUFFERUSAGE_GRAPHICS_STORAGE_READ: Final[int]
+ SDL_GPU_BUFFERUSAGE_INDEX: Final[int]
+ SDL_GPU_BUFFERUSAGE_INDIRECT: Final[int]
+ SDL_GPU_BUFFERUSAGE_VERTEX: Final[int]
+ SDL_GPU_COLORCOMPONENT_A: Final[int]
+ SDL_GPU_COLORCOMPONENT_B: Final[int]
+ SDL_GPU_COLORCOMPONENT_G: Final[int]
+ SDL_GPU_COLORCOMPONENT_R: Final[int]
+ SDL_GPU_COMPAREOP_ALWAYS: Final[int]
+ SDL_GPU_COMPAREOP_EQUAL: Final[int]
+ SDL_GPU_COMPAREOP_GREATER: Final[int]
+ SDL_GPU_COMPAREOP_GREATER_OR_EQUAL: Final[int]
+ SDL_GPU_COMPAREOP_INVALID: Final[int]
+ SDL_GPU_COMPAREOP_LESS: Final[int]
+ SDL_GPU_COMPAREOP_LESS_OR_EQUAL: Final[int]
+ SDL_GPU_COMPAREOP_NEVER: Final[int]
+ SDL_GPU_COMPAREOP_NOT_EQUAL: Final[int]
+ SDL_GPU_CUBEMAPFACE_NEGATIVEX: Final[int]
+ SDL_GPU_CUBEMAPFACE_NEGATIVEY: Final[int]
+ SDL_GPU_CUBEMAPFACE_NEGATIVEZ: Final[int]
+ SDL_GPU_CUBEMAPFACE_POSITIVEX: Final[int]
+ SDL_GPU_CUBEMAPFACE_POSITIVEY: Final[int]
+ SDL_GPU_CUBEMAPFACE_POSITIVEZ: Final[int]
+ SDL_GPU_CULLMODE_BACK: Final[int]
+ SDL_GPU_CULLMODE_FRONT: Final[int]
+ SDL_GPU_CULLMODE_NONE: Final[int]
+ SDL_GPU_FILLMODE_FILL: Final[int]
+ SDL_GPU_FILLMODE_LINE: Final[int]
+ SDL_GPU_FILTER_LINEAR: Final[int]
+ SDL_GPU_FILTER_NEAREST: Final[int]
+ SDL_GPU_FRONTFACE_CLOCKWISE: Final[int]
+ SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE: Final[int]
+ SDL_GPU_INDEXELEMENTSIZE_16BIT: Final[int]
+ SDL_GPU_INDEXELEMENTSIZE_32BIT: Final[int]
+ SDL_GPU_LOADOP_CLEAR: Final[int]
+ SDL_GPU_LOADOP_DONT_CARE: Final[int]
+ SDL_GPU_LOADOP_LOAD: Final[int]
+ SDL_GPU_PRESENTMODE_IMMEDIATE: Final[int]
+ SDL_GPU_PRESENTMODE_MAILBOX: Final[int]
+ SDL_GPU_PRESENTMODE_VSYNC: Final[int]
+ SDL_GPU_PRIMITIVETYPE_LINELIST: Final[int]
+ SDL_GPU_PRIMITIVETYPE_LINESTRIP: Final[int]
+ SDL_GPU_PRIMITIVETYPE_POINTLIST: Final[int]
+ SDL_GPU_PRIMITIVETYPE_TRIANGLELIST: Final[int]
+ SDL_GPU_PRIMITIVETYPE_TRIANGLESTRIP: Final[int]
+ SDL_GPU_SAMPLECOUNT_1: Final[int]
+ SDL_GPU_SAMPLECOUNT_2: Final[int]
+ SDL_GPU_SAMPLECOUNT_4: Final[int]
+ SDL_GPU_SAMPLECOUNT_8: Final[int]
+ SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE: Final[int]
+ SDL_GPU_SAMPLERADDRESSMODE_MIRRORED_REPEAT: Final[int]
+ SDL_GPU_SAMPLERADDRESSMODE_REPEAT: Final[int]
+ SDL_GPU_SAMPLERMIPMAPMODE_LINEAR: Final[int]
+ SDL_GPU_SAMPLERMIPMAPMODE_NEAREST: Final[int]
+ SDL_GPU_SHADERFORMAT_DXBC: Final[int]
+ SDL_GPU_SHADERFORMAT_DXIL: Final[int]
+ SDL_GPU_SHADERFORMAT_INVALID: Final[int]
+ SDL_GPU_SHADERFORMAT_METALLIB: Final[int]
+ SDL_GPU_SHADERFORMAT_MSL: Final[int]
+ SDL_GPU_SHADERFORMAT_PRIVATE: Final[int]
+ SDL_GPU_SHADERFORMAT_SPIRV: Final[int]
+ SDL_GPU_SHADERSTAGE_FRAGMENT: Final[int]
+ SDL_GPU_SHADERSTAGE_VERTEX: Final[int]
+ SDL_GPU_STENCILOP_DECREMENT_AND_CLAMP: Final[int]
+ SDL_GPU_STENCILOP_DECREMENT_AND_WRAP: Final[int]
+ SDL_GPU_STENCILOP_INCREMENT_AND_CLAMP: Final[int]
+ SDL_GPU_STENCILOP_INCREMENT_AND_WRAP: Final[int]
+ SDL_GPU_STENCILOP_INVALID: Final[int]
+ SDL_GPU_STENCILOP_INVERT: Final[int]
+ SDL_GPU_STENCILOP_KEEP: Final[int]
+ SDL_GPU_STENCILOP_REPLACE: Final[int]
+ SDL_GPU_STENCILOP_ZERO: Final[int]
+ SDL_GPU_STOREOP_DONT_CARE: Final[int]
+ SDL_GPU_STOREOP_RESOLVE: Final[int]
+ SDL_GPU_STOREOP_RESOLVE_AND_STORE: Final[int]
+ SDL_GPU_STOREOP_STORE: Final[int]
+ SDL_GPU_SWAPCHAINCOMPOSITION_HDR10_ST2084: Final[int]
+ SDL_GPU_SWAPCHAINCOMPOSITION_HDR_EXTENDED_LINEAR: Final[int]
+ SDL_GPU_SWAPCHAINCOMPOSITION_SDR: Final[int]
+ SDL_GPU_SWAPCHAINCOMPOSITION_SDR_LINEAR: Final[int]
+ SDL_GPU_TEXTUREFORMAT_A8_UNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_10x10_FLOAT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_10x10_UNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_10x10_UNORM_SRGB: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_10x5_FLOAT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_10x5_UNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_10x5_UNORM_SRGB: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_10x6_FLOAT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_10x6_UNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_10x6_UNORM_SRGB: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_10x8_FLOAT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_10x8_UNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_10x8_UNORM_SRGB: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_12x10_FLOAT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_12x10_UNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_12x10_UNORM_SRGB: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_12x12_FLOAT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_12x12_UNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_12x12_UNORM_SRGB: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_4x4_FLOAT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_4x4_UNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_4x4_UNORM_SRGB: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_5x4_FLOAT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_5x4_UNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_5x4_UNORM_SRGB: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_5x5_FLOAT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_5x5_UNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_5x5_UNORM_SRGB: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_6x5_FLOAT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_6x5_UNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_6x5_UNORM_SRGB: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_6x6_FLOAT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_6x6_UNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_6x6_UNORM_SRGB: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_8x5_FLOAT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_8x5_UNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_8x5_UNORM_SRGB: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_8x6_FLOAT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_8x6_UNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_8x6_UNORM_SRGB: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_8x8_FLOAT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_8x8_UNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_ASTC_8x8_UNORM_SRGB: Final[int]
+ SDL_GPU_TEXTUREFORMAT_B4G4R4A4_UNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_B5G5R5A1_UNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_B5G6R5_UNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM_SRGB: Final[int]
+ SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM_SRGB: Final[int]
+ SDL_GPU_TEXTUREFORMAT_BC2_RGBA_UNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_BC2_RGBA_UNORM_SRGB: Final[int]
+ SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM_SRGB: Final[int]
+ SDL_GPU_TEXTUREFORMAT_BC4_R_UNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_BC5_RG_UNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_BC6H_RGB_FLOAT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_BC6H_RGB_UFLOAT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_BC7_RGBA_UNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_BC7_RGBA_UNORM_SRGB: Final[int]
+ SDL_GPU_TEXTUREFORMAT_D16_UNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_D24_UNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_D32_FLOAT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_D32_FLOAT_S8_UINT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_INVALID: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R10G10B10A2_UNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R11G11B10_UFLOAT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R16G16B16A16_INT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R16G16B16A16_SNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UINT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R16G16_FLOAT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R16G16_INT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R16G16_SNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R16G16_UINT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R16G16_UNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R16_FLOAT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R16_INT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R16_SNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R16_UINT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R16_UNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R32G32B32A32_INT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R32G32B32A32_UINT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R32G32_FLOAT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R32G32_INT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R32G32_UINT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R32_FLOAT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R32_INT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R32_UINT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R8G8B8A8_INT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R8G8B8A8_SNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UINT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM_SRGB: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R8G8_INT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R8G8_SNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R8G8_UINT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R8G8_UNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R8_INT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R8_SNORM: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R8_UINT: Final[int]
+ SDL_GPU_TEXTUREFORMAT_R8_UNORM: Final[int]
+ SDL_GPU_TEXTURETYPE_2D: Final[int]
+ SDL_GPU_TEXTURETYPE_2D_ARRAY: Final[int]
+ SDL_GPU_TEXTURETYPE_3D: Final[int]
+ SDL_GPU_TEXTURETYPE_CUBE: Final[int]
+ SDL_GPU_TEXTURETYPE_CUBE_ARRAY: Final[int]
+ SDL_GPU_TEXTUREUSAGE_COLOR_TARGET: Final[int]
+ SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_READ: Final[int]
+ SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_SIMULTANEOUS_READ_WRITE: Final[int]
+ SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_WRITE: Final[int]
+ SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET: Final[int]
+ SDL_GPU_TEXTUREUSAGE_GRAPHICS_STORAGE_READ: Final[int]
+ SDL_GPU_TEXTUREUSAGE_SAMPLER: Final[int]
+ SDL_GPU_TRANSFERBUFFERUSAGE_DOWNLOAD: Final[int]
+ SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD: Final[int]
+ SDL_GPU_VERTEXELEMENTFORMAT_BYTE2: Final[int]
+ SDL_GPU_VERTEXELEMENTFORMAT_BYTE2_NORM: Final[int]
+ SDL_GPU_VERTEXELEMENTFORMAT_BYTE4: Final[int]
+ SDL_GPU_VERTEXELEMENTFORMAT_BYTE4_NORM: Final[int]
+ SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2: Final[int]
+ SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3: Final[int]
+ SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4: Final[int]
+ SDL_GPU_VERTEXELEMENTFORMAT_FLOAT: Final[int]
+ SDL_GPU_VERTEXELEMENTFORMAT_HALF2: Final[int]
+ SDL_GPU_VERTEXELEMENTFORMAT_HALF4: Final[int]
+ SDL_GPU_VERTEXELEMENTFORMAT_INT2: Final[int]
+ SDL_GPU_VERTEXELEMENTFORMAT_INT3: Final[int]
+ SDL_GPU_VERTEXELEMENTFORMAT_INT4: Final[int]
+ SDL_GPU_VERTEXELEMENTFORMAT_INT: Final[int]
+ SDL_GPU_VERTEXELEMENTFORMAT_INVALID: Final[int]
+ SDL_GPU_VERTEXELEMENTFORMAT_SHORT2: Final[int]
+ SDL_GPU_VERTEXELEMENTFORMAT_SHORT2_NORM: Final[int]
+ SDL_GPU_VERTEXELEMENTFORMAT_SHORT4: Final[int]
+ SDL_GPU_VERTEXELEMENTFORMAT_SHORT4_NORM: Final[int]
+ SDL_GPU_VERTEXELEMENTFORMAT_UBYTE2: Final[int]
+ SDL_GPU_VERTEXELEMENTFORMAT_UBYTE2_NORM: Final[int]
+ SDL_GPU_VERTEXELEMENTFORMAT_UBYTE4: Final[int]
+ SDL_GPU_VERTEXELEMENTFORMAT_UBYTE4_NORM: Final[int]
+ SDL_GPU_VERTEXELEMENTFORMAT_UINT2: Final[int]
+ SDL_GPU_VERTEXELEMENTFORMAT_UINT3: Final[int]
+ SDL_GPU_VERTEXELEMENTFORMAT_UINT4: Final[int]
+ SDL_GPU_VERTEXELEMENTFORMAT_UINT: Final[int]
+ SDL_GPU_VERTEXELEMENTFORMAT_USHORT2: Final[int]
+ SDL_GPU_VERTEXELEMENTFORMAT_USHORT2_NORM: Final[int]
+ SDL_GPU_VERTEXELEMENTFORMAT_USHORT4: Final[int]
+ SDL_GPU_VERTEXELEMENTFORMAT_USHORT4_NORM: Final[int]
+ SDL_GPU_VERTEXINPUTRATE_INSTANCE: Final[int]
+ SDL_GPU_VERTEXINPUTRATE_VERTEX: Final[int]
+ SDL_HAPTIC_AUTOCENTER: Final[int]
+ SDL_HAPTIC_CARTESIAN: Final[int]
+ SDL_HAPTIC_CONSTANT: Final[int]
+ SDL_HAPTIC_CUSTOM: Final[int]
+ SDL_HAPTIC_DAMPER: Final[int]
+ SDL_HAPTIC_FRICTION: Final[int]
+ SDL_HAPTIC_GAIN: Final[int]
+ SDL_HAPTIC_INERTIA: Final[int]
+ SDL_HAPTIC_INFINITY: Final[int]
+ SDL_HAPTIC_LEFTRIGHT: Final[int]
+ SDL_HAPTIC_PAUSE: Final[int]
+ SDL_HAPTIC_POLAR: Final[int]
+ SDL_HAPTIC_RAMP: Final[int]
+ SDL_HAPTIC_RESERVED1: Final[int]
+ SDL_HAPTIC_RESERVED2: Final[int]
+ SDL_HAPTIC_RESERVED3: Final[int]
+ SDL_HAPTIC_SAWTOOTHDOWN: Final[int]
+ SDL_HAPTIC_SAWTOOTHUP: Final[int]
+ SDL_HAPTIC_SINE: Final[int]
+ SDL_HAPTIC_SPHERICAL: Final[int]
+ SDL_HAPTIC_SPRING: Final[int]
+ SDL_HAPTIC_SQUARE: Final[int]
+ SDL_HAPTIC_STATUS: Final[int]
+ SDL_HAPTIC_STEERING_AXIS: Final[int]
+ SDL_HAPTIC_TRIANGLE: Final[int]
+ SDL_HAT_CENTERED: Final[int]
+ SDL_HAT_DOWN: Final[int]
+ SDL_HAT_LEFT: Final[int]
+ SDL_HAT_LEFTDOWN: Final[int]
+ SDL_HAT_LEFTUP: Final[int]
+ SDL_HAT_RIGHT: Final[int]
+ SDL_HAT_RIGHTDOWN: Final[int]
+ SDL_HAT_RIGHTUP: Final[int]
+ SDL_HAT_UP: Final[int]
+ SDL_HID_API_BUS_BLUETOOTH: Final[Literal[2]] = 2
+ SDL_HID_API_BUS_I2C: Final[Literal[3]] = 3
+ SDL_HID_API_BUS_SPI: Final[Literal[4]] = 4
+ SDL_HID_API_BUS_UNKNOWN: Final[Literal[0]] = 0
+ SDL_HID_API_BUS_USB: Final[Literal[1]] = 1
+ SDL_HINT_DEFAULT: Final[int]
+ SDL_HINT_NORMAL: Final[int]
+ SDL_HINT_OVERRIDE: Final[int]
+ SDL_HITTEST_DRAGGABLE: Final[int]
+ SDL_HITTEST_NORMAL: Final[int]
+ SDL_HITTEST_RESIZE_BOTTOM: Final[int]
+ SDL_HITTEST_RESIZE_BOTTOMLEFT: Final[int]
+ SDL_HITTEST_RESIZE_BOTTOMRIGHT: Final[int]
+ SDL_HITTEST_RESIZE_LEFT: Final[int]
+ SDL_HITTEST_RESIZE_RIGHT: Final[int]
+ SDL_HITTEST_RESIZE_TOP: Final[int]
+ SDL_HITTEST_RESIZE_TOPLEFT: Final[int]
+ SDL_HITTEST_RESIZE_TOPRIGHT: Final[int]
+ SDL_ICONV_E2BIG: Final[int]
+ SDL_ICONV_EILSEQ: Final[int]
+ SDL_ICONV_EINVAL: Final[int]
+ SDL_ICONV_ERROR: Final[int]
+ SDL_INIT_AUDIO: Final[int]
+ SDL_INIT_CAMERA: Final[int]
+ SDL_INIT_EVENTS: Final[int]
+ SDL_INIT_GAMEPAD: Final[int]
+ SDL_INIT_HAPTIC: Final[int]
+ SDL_INIT_JOYSTICK: Final[int]
+ SDL_INIT_SENSOR: Final[int]
+ SDL_INIT_STATUS_INITIALIZED: Final[int]
+ SDL_INIT_STATUS_INITIALIZING: Final[int]
+ SDL_INIT_STATUS_UNINITIALIZED: Final[int]
+ SDL_INIT_STATUS_UNINITIALIZING: Final[int]
+ SDL_INIT_VIDEO: Final[int]
+ SDL_INVALID_UNICODE_CODEPOINT: Final[int]
+ SDL_IO_SEEK_CUR: Final[int]
+ SDL_IO_SEEK_END: Final[int]
+ SDL_IO_SEEK_SET: Final[int]
+ SDL_IO_STATUS_EOF: Final[int]
+ SDL_IO_STATUS_ERROR: Final[int]
+ SDL_IO_STATUS_NOT_READY: Final[int]
+ SDL_IO_STATUS_READONLY: Final[int]
+ SDL_IO_STATUS_READY: Final[int]
+ SDL_IO_STATUS_WRITEONLY: Final[int]
+ SDL_JOYSTICK_AXIS_MAX: Final[int]
+ SDL_JOYSTICK_AXIS_MIN: Final[int]
+ SDL_JOYSTICK_CONNECTION_INVALID: Final[Literal[-1]] = -1
+ SDL_JOYSTICK_CONNECTION_UNKNOWN: Final[int]
+ SDL_JOYSTICK_CONNECTION_WIRED: Final[int]
+ SDL_JOYSTICK_CONNECTION_WIRELESS: Final[int]
+ SDL_JOYSTICK_TYPE_ARCADE_PAD: Final[int]
+ SDL_JOYSTICK_TYPE_ARCADE_STICK: Final[int]
+ SDL_JOYSTICK_TYPE_COUNT: Final[int]
+ SDL_JOYSTICK_TYPE_DANCE_PAD: Final[int]
+ SDL_JOYSTICK_TYPE_DRUM_KIT: Final[int]
+ SDL_JOYSTICK_TYPE_FLIGHT_STICK: Final[int]
+ SDL_JOYSTICK_TYPE_GAMEPAD: Final[int]
+ SDL_JOYSTICK_TYPE_GUITAR: Final[int]
+ SDL_JOYSTICK_TYPE_THROTTLE: Final[int]
+ SDL_JOYSTICK_TYPE_UNKNOWN: Final[int]
+ SDL_JOYSTICK_TYPE_WHEEL: Final[int]
+ SDL_KMOD_ALT: Final[int]
+ SDL_KMOD_CAPS: Final[int]
+ SDL_KMOD_CTRL: Final[int]
+ SDL_KMOD_GUI: Final[int]
+ SDL_KMOD_LALT: Final[int]
+ SDL_KMOD_LCTRL: Final[int]
+ SDL_KMOD_LEVEL5: Final[int]
+ SDL_KMOD_LGUI: Final[int]
+ SDL_KMOD_LSHIFT: Final[int]
+ SDL_KMOD_MODE: Final[int]
+ SDL_KMOD_NONE: Final[int]
+ SDL_KMOD_NUM: Final[int]
+ SDL_KMOD_RALT: Final[int]
+ SDL_KMOD_RCTRL: Final[int]
+ SDL_KMOD_RGUI: Final[int]
+ SDL_KMOD_RSHIFT: Final[int]
+ SDL_KMOD_SCROLL: Final[int]
+ SDL_KMOD_SHIFT: Final[int]
+ SDL_LIL_ENDIAN: Final[int]
+ SDL_LOGICAL_PRESENTATION_DISABLED: Final[int]
+ SDL_LOGICAL_PRESENTATION_INTEGER_SCALE: Final[int]
+ SDL_LOGICAL_PRESENTATION_LETTERBOX: Final[int]
+ SDL_LOGICAL_PRESENTATION_OVERSCAN: Final[int]
+ SDL_LOGICAL_PRESENTATION_STRETCH: Final[int]
+ SDL_LOG_CATEGORY_APPLICATION: Final[int]
+ SDL_LOG_CATEGORY_ASSERT: Final[int]
+ SDL_LOG_CATEGORY_AUDIO: Final[int]
+ SDL_LOG_CATEGORY_CUSTOM: Final[int]
+ SDL_LOG_CATEGORY_ERROR: Final[int]
+ SDL_LOG_CATEGORY_GPU: Final[int]
+ SDL_LOG_CATEGORY_INPUT: Final[int]
+ SDL_LOG_CATEGORY_RENDER: Final[int]
+ SDL_LOG_CATEGORY_RESERVED10: Final[int]
+ SDL_LOG_CATEGORY_RESERVED2: Final[int]
+ SDL_LOG_CATEGORY_RESERVED3: Final[int]
+ SDL_LOG_CATEGORY_RESERVED4: Final[int]
+ SDL_LOG_CATEGORY_RESERVED5: Final[int]
+ SDL_LOG_CATEGORY_RESERVED6: Final[int]
+ SDL_LOG_CATEGORY_RESERVED7: Final[int]
+ SDL_LOG_CATEGORY_RESERVED8: Final[int]
+ SDL_LOG_CATEGORY_RESERVED9: Final[int]
+ SDL_LOG_CATEGORY_SYSTEM: Final[int]
+ SDL_LOG_CATEGORY_TEST: Final[int]
+ SDL_LOG_CATEGORY_VIDEO: Final[int]
+ SDL_LOG_PRIORITY_COUNT: Final[int]
+ SDL_LOG_PRIORITY_CRITICAL: Final[int]
+ SDL_LOG_PRIORITY_DEBUG: Final[int]
+ SDL_LOG_PRIORITY_ERROR: Final[int]
+ SDL_LOG_PRIORITY_INFO: Final[int]
+ SDL_LOG_PRIORITY_INVALID: Final[int]
+ SDL_LOG_PRIORITY_TRACE: Final[int]
+ SDL_LOG_PRIORITY_VERBOSE: Final[int]
+ SDL_LOG_PRIORITY_WARN: Final[int]
+ SDL_MAJOR_VERSION: Final[int]
+ SDL_MATRIX_COEFFICIENTS_BT2020_CL: Final[Literal[10]] = 10
+ SDL_MATRIX_COEFFICIENTS_BT2020_NCL: Final[Literal[9]] = 9
+ SDL_MATRIX_COEFFICIENTS_BT470BG: Final[Literal[5]] = 5
+ SDL_MATRIX_COEFFICIENTS_BT601: Final[Literal[6]] = 6
+ SDL_MATRIX_COEFFICIENTS_BT709: Final[Literal[1]] = 1
+ SDL_MATRIX_COEFFICIENTS_CHROMA_DERIVED_CL: Final[Literal[13]] = 13
+ SDL_MATRIX_COEFFICIENTS_CHROMA_DERIVED_NCL: Final[Literal[12]] = 12
+ SDL_MATRIX_COEFFICIENTS_CUSTOM: Final[Literal[31]] = 31
+ SDL_MATRIX_COEFFICIENTS_FCC: Final[Literal[4]] = 4
+ SDL_MATRIX_COEFFICIENTS_ICTCP: Final[Literal[14]] = 14
+ SDL_MATRIX_COEFFICIENTS_IDENTITY: Final[Literal[0]] = 0
+ SDL_MATRIX_COEFFICIENTS_SMPTE2085: Final[Literal[11]] = 11
+ SDL_MATRIX_COEFFICIENTS_SMPTE240: Final[Literal[7]] = 7
+ SDL_MATRIX_COEFFICIENTS_UNSPECIFIED: Final[Literal[2]] = 2
+ SDL_MATRIX_COEFFICIENTS_YCGCO: Final[Literal[8]] = 8
+ SDL_MAX_SINT16: Final[int]
+ SDL_MAX_SINT32: Final[int]
+ SDL_MAX_SINT64: Final[int]
+ SDL_MAX_SINT8: Final[int]
+ SDL_MAX_TIME: Final[int]
+ SDL_MAX_UINT16: Final[int]
+ SDL_MAX_UINT32: Final[int]
+ SDL_MAX_UINT64: Final[int]
+ SDL_MAX_UINT8: Final[int]
+ SDL_MESSAGEBOX_BUTTONS_LEFT_TO_RIGHT: Final[int]
+ SDL_MESSAGEBOX_BUTTONS_RIGHT_TO_LEFT: Final[int]
+ SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT: Final[int]
+ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT: Final[int]
+ SDL_MESSAGEBOX_COLOR_BACKGROUND: Final[int]
+ SDL_MESSAGEBOX_COLOR_BUTTON_BACKGROUND: Final[int]
+ SDL_MESSAGEBOX_COLOR_BUTTON_BORDER: Final[int]
+ SDL_MESSAGEBOX_COLOR_BUTTON_SELECTED: Final[int]
+ SDL_MESSAGEBOX_COLOR_COUNT: Final[int]
+ SDL_MESSAGEBOX_COLOR_TEXT: Final[int]
+ SDL_MESSAGEBOX_ERROR: Final[int]
+ SDL_MESSAGEBOX_INFORMATION: Final[int]
+ SDL_MESSAGEBOX_WARNING: Final[int]
+ SDL_MICRO_VERSION: Final[int]
+ SDL_MINOR_VERSION: Final[int]
+ SDL_MIN_SINT16: Final[int]
+ SDL_MIN_SINT32: Final[int]
+ SDL_MIN_SINT64: Final[int]
+ SDL_MIN_SINT8: Final[int]
+ SDL_MIN_TIME: Final[int]
+ SDL_MIN_UINT16: Final[int]
+ SDL_MIN_UINT32: Final[int]
+ SDL_MIN_UINT64: Final[int]
+ SDL_MIN_UINT8: Final[int]
+ SDL_MOUSEWHEEL_FLIPPED: Final[int]
+ SDL_MOUSEWHEEL_NORMAL: Final[int]
+ SDL_MOUSE_TOUCHID: Final[int]
+ SDL_MS_PER_SECOND: Final[int]
+ SDL_NS_PER_MS: Final[int]
+ SDL_NS_PER_SECOND: Final[int]
+ SDL_NS_PER_US: Final[int]
+ SDL_NULL_WHILE_LOOP_CONDITION: Final[int]
+ SDL_ORIENTATION_LANDSCAPE: Final[int]
+ SDL_ORIENTATION_LANDSCAPE_FLIPPED: Final[int]
+ SDL_ORIENTATION_PORTRAIT: Final[int]
+ SDL_ORIENTATION_PORTRAIT_FLIPPED: Final[int]
+ SDL_ORIENTATION_UNKNOWN: Final[int]
+ SDL_PACKEDLAYOUT_1010102: Final[int]
+ SDL_PACKEDLAYOUT_1555: Final[int]
+ SDL_PACKEDLAYOUT_2101010: Final[int]
+ SDL_PACKEDLAYOUT_332: Final[int]
+ SDL_PACKEDLAYOUT_4444: Final[int]
+ SDL_PACKEDLAYOUT_5551: Final[int]
+ SDL_PACKEDLAYOUT_565: Final[int]
+ SDL_PACKEDLAYOUT_8888: Final[int]
+ SDL_PACKEDLAYOUT_NONE: Final[int]
+ SDL_PACKEDORDER_ABGR: Final[int]
+ SDL_PACKEDORDER_ARGB: Final[int]
+ SDL_PACKEDORDER_BGRA: Final[int]
+ SDL_PACKEDORDER_BGRX: Final[int]
+ SDL_PACKEDORDER_NONE: Final[int]
+ SDL_PACKEDORDER_RGBA: Final[int]
+ SDL_PACKEDORDER_RGBX: Final[int]
+ SDL_PACKEDORDER_XBGR: Final[int]
+ SDL_PACKEDORDER_XRGB: Final[int]
+ SDL_PATHTYPE_DIRECTORY: Final[int]
+ SDL_PATHTYPE_FILE: Final[int]
+ SDL_PATHTYPE_NONE: Final[int]
+ SDL_PATHTYPE_OTHER: Final[int]
+ SDL_PEEKEVENT: Final[int]
+ SDL_PEN_AXIS_COUNT: Final[int]
+ SDL_PEN_AXIS_DISTANCE: Final[int]
+ SDL_PEN_AXIS_PRESSURE: Final[int]
+ SDL_PEN_AXIS_ROTATION: Final[int]
+ SDL_PEN_AXIS_SLIDER: Final[int]
+ SDL_PEN_AXIS_TANGENTIAL_PRESSURE: Final[int]
+ SDL_PEN_AXIS_XTILT: Final[int]
+ SDL_PEN_AXIS_YTILT: Final[int]
+ SDL_PEN_INPUT_BUTTON_1: Final[int]
+ SDL_PEN_INPUT_BUTTON_2: Final[int]
+ SDL_PEN_INPUT_BUTTON_3: Final[int]
+ SDL_PEN_INPUT_BUTTON_4: Final[int]
+ SDL_PEN_INPUT_BUTTON_5: Final[int]
+ SDL_PEN_INPUT_DOWN: Final[int]
+ SDL_PEN_INPUT_ERASER_TIP: Final[int]
+ SDL_PEN_MOUSEID: Final[int]
+ SDL_PEN_TOUCHID: Final[int]
+ SDL_PIXELFORMAT_ABGR128_FLOAT: int
+ SDL_PIXELFORMAT_ABGR1555: int
+ SDL_PIXELFORMAT_ABGR2101010: int
+ SDL_PIXELFORMAT_ABGR32: int
+ SDL_PIXELFORMAT_ABGR4444: int
+ SDL_PIXELFORMAT_ABGR64: int
+ SDL_PIXELFORMAT_ABGR64_FLOAT: int
+ SDL_PIXELFORMAT_ABGR8888: int
+ SDL_PIXELFORMAT_ARGB128_FLOAT: int
+ SDL_PIXELFORMAT_ARGB1555: int
+ SDL_PIXELFORMAT_ARGB2101010: int
+ SDL_PIXELFORMAT_ARGB32: int
+ SDL_PIXELFORMAT_ARGB4444: int
+ SDL_PIXELFORMAT_ARGB64: int
+ SDL_PIXELFORMAT_ARGB64_FLOAT: int
+ SDL_PIXELFORMAT_ARGB8888: int
+ SDL_PIXELFORMAT_BGR24: int
+ SDL_PIXELFORMAT_BGR48: int
+ SDL_PIXELFORMAT_BGR48_FLOAT: int
+ SDL_PIXELFORMAT_BGR565: int
+ SDL_PIXELFORMAT_BGR96_FLOAT: int
+ SDL_PIXELFORMAT_BGRA128_FLOAT: int
+ SDL_PIXELFORMAT_BGRA32: int
+ SDL_PIXELFORMAT_BGRA4444: int
+ SDL_PIXELFORMAT_BGRA5551: int
+ SDL_PIXELFORMAT_BGRA64: int
+ SDL_PIXELFORMAT_BGRA64_FLOAT: int
+ SDL_PIXELFORMAT_BGRA8888: int
+ SDL_PIXELFORMAT_BGRX32: int
+ SDL_PIXELFORMAT_BGRX8888: int
+ SDL_PIXELFORMAT_EXTERNAL_OES: int
+ SDL_PIXELFORMAT_INDEX1LSB: int
+ SDL_PIXELFORMAT_INDEX1MSB: int
+ SDL_PIXELFORMAT_INDEX2LSB: int
+ SDL_PIXELFORMAT_INDEX2MSB: int
+ SDL_PIXELFORMAT_INDEX4LSB: int
+ SDL_PIXELFORMAT_INDEX4MSB: int
+ SDL_PIXELFORMAT_INDEX8: int
+ SDL_PIXELFORMAT_IYUV: int
+ SDL_PIXELFORMAT_MJPG: int
+ SDL_PIXELFORMAT_NV12: int
+ SDL_PIXELFORMAT_NV21: int
+ SDL_PIXELFORMAT_P010: int
+ SDL_PIXELFORMAT_RGB24: int
+ SDL_PIXELFORMAT_RGB332: int
+ SDL_PIXELFORMAT_RGB48: int
+ SDL_PIXELFORMAT_RGB48_FLOAT: int
+ SDL_PIXELFORMAT_RGB565: int
+ SDL_PIXELFORMAT_RGB96_FLOAT: int
+ SDL_PIXELFORMAT_RGBA128_FLOAT: int
+ SDL_PIXELFORMAT_RGBA32: int
+ SDL_PIXELFORMAT_RGBA4444: int
+ SDL_PIXELFORMAT_RGBA5551: int
+ SDL_PIXELFORMAT_RGBA64: int
+ SDL_PIXELFORMAT_RGBA64_FLOAT: int
+ SDL_PIXELFORMAT_RGBA8888: int
+ SDL_PIXELFORMAT_RGBX32: int
+ SDL_PIXELFORMAT_RGBX8888: int
+ SDL_PIXELFORMAT_UNKNOWN: int
+ SDL_PIXELFORMAT_UYVY: int
+ SDL_PIXELFORMAT_XBGR1555: int
+ SDL_PIXELFORMAT_XBGR2101010: int
+ SDL_PIXELFORMAT_XBGR32: int
+ SDL_PIXELFORMAT_XBGR4444: int
+ SDL_PIXELFORMAT_XBGR8888: int
+ SDL_PIXELFORMAT_XRGB1555: int
+ SDL_PIXELFORMAT_XRGB2101010: int
+ SDL_PIXELFORMAT_XRGB32: int
+ SDL_PIXELFORMAT_XRGB4444: int
+ SDL_PIXELFORMAT_XRGB8888: int
+ SDL_PIXELFORMAT_YUY2: int
+ SDL_PIXELFORMAT_YV12: int
+ SDL_PIXELFORMAT_YVYU: int
+ SDL_PIXELTYPE_ARRAYF16: Final[int]
+ SDL_PIXELTYPE_ARRAYF32: Final[int]
+ SDL_PIXELTYPE_ARRAYU16: Final[int]
+ SDL_PIXELTYPE_ARRAYU32: Final[int]
+ SDL_PIXELTYPE_ARRAYU8: Final[int]
+ SDL_PIXELTYPE_INDEX1: Final[int]
+ SDL_PIXELTYPE_INDEX2: Final[int]
+ SDL_PIXELTYPE_INDEX4: Final[int]
+ SDL_PIXELTYPE_INDEX8: Final[int]
+ SDL_PIXELTYPE_PACKED16: Final[int]
+ SDL_PIXELTYPE_PACKED32: Final[int]
+ SDL_PIXELTYPE_PACKED8: Final[int]
+ SDL_PIXELTYPE_UNKNOWN: Final[int]
+ SDL_POWERSTATE_CHARGED: Final[int]
+ SDL_POWERSTATE_CHARGING: Final[int]
+ SDL_POWERSTATE_ERROR: Final[Literal[-1]] = -1
+ SDL_POWERSTATE_NO_BATTERY: Final[int]
+ SDL_POWERSTATE_ON_BATTERY: Final[int]
+ SDL_POWERSTATE_UNKNOWN: Final[int]
+ SDL_PROCESS_STDIO_APP: Final[int]
+ SDL_PROCESS_STDIO_INHERITED: Final[int]
+ SDL_PROCESS_STDIO_NULL: Final[int]
+ SDL_PROCESS_STDIO_REDIRECT: Final[int]
+ SDL_PROPERTY_TYPE_BOOLEAN: Final[int]
+ SDL_PROPERTY_TYPE_FLOAT: Final[int]
+ SDL_PROPERTY_TYPE_INVALID: Final[int]
+ SDL_PROPERTY_TYPE_NUMBER: Final[int]
+ SDL_PROPERTY_TYPE_POINTER: Final[int]
+ SDL_PROPERTY_TYPE_STRING: Final[int]
+ SDL_RENDERER_VSYNC_ADAPTIVE: Final[int]
+ SDL_RENDERER_VSYNC_DISABLED: Final[int]
+ SDL_SANDBOX_FLATPAK: Final[int]
+ SDL_SANDBOX_MACOS: Final[int]
+ SDL_SANDBOX_NONE: Final[Literal[0]] = 0
+ SDL_SANDBOX_SNAP: Final[int]
+ SDL_SANDBOX_UNKNOWN_CONTAINER: Final[int]
+ SDL_SCALEMODE_INVALID: Final[Literal[-1]] = -1
+ SDL_SCALEMODE_LINEAR: Final[int]
+ SDL_SCALEMODE_NEAREST: Final[int]
+ SDL_SCANCODE_0: Final[Literal[39]] = 39
+ SDL_SCANCODE_1: Final[Literal[30]] = 30
+ SDL_SCANCODE_2: Final[Literal[31]] = 31
+ SDL_SCANCODE_3: Final[Literal[32]] = 32
+ SDL_SCANCODE_4: Final[Literal[33]] = 33
+ SDL_SCANCODE_5: Final[Literal[34]] = 34
+ SDL_SCANCODE_6: Final[Literal[35]] = 35
+ SDL_SCANCODE_7: Final[Literal[36]] = 36
+ SDL_SCANCODE_8: Final[Literal[37]] = 37
+ SDL_SCANCODE_9: Final[Literal[38]] = 38
+ SDL_SCANCODE_A: Final[Literal[4]] = 4
+ SDL_SCANCODE_AC_BACK: Final[Literal[282]] = 282
+ SDL_SCANCODE_AC_BOOKMARKS: Final[Literal[286]] = 286
+ SDL_SCANCODE_AC_CLOSE: Final[Literal[275]] = 275
+ SDL_SCANCODE_AC_EXIT: Final[Literal[276]] = 276
+ SDL_SCANCODE_AC_FORWARD: Final[Literal[283]] = 283
+ SDL_SCANCODE_AC_HOME: Final[Literal[281]] = 281
+ SDL_SCANCODE_AC_NEW: Final[Literal[273]] = 273
+ SDL_SCANCODE_AC_OPEN: Final[Literal[274]] = 274
+ SDL_SCANCODE_AC_PRINT: Final[Literal[278]] = 278
+ SDL_SCANCODE_AC_PROPERTIES: Final[Literal[279]] = 279
+ SDL_SCANCODE_AC_REFRESH: Final[Literal[285]] = 285
+ SDL_SCANCODE_AC_SAVE: Final[Literal[277]] = 277
+ SDL_SCANCODE_AC_SEARCH: Final[Literal[280]] = 280
+ SDL_SCANCODE_AC_STOP: Final[Literal[284]] = 284
+ SDL_SCANCODE_AGAIN: Final[Literal[121]] = 121
+ SDL_SCANCODE_ALTERASE: Final[Literal[153]] = 153
+ SDL_SCANCODE_APOSTROPHE: Final[Literal[52]] = 52
+ SDL_SCANCODE_APPLICATION: Final[Literal[101]] = 101
+ SDL_SCANCODE_B: Final[Literal[5]] = 5
+ SDL_SCANCODE_BACKSLASH: Final[Literal[49]] = 49
+ SDL_SCANCODE_BACKSPACE: Final[Literal[42]] = 42
+ SDL_SCANCODE_C: Final[Literal[6]] = 6
+ SDL_SCANCODE_CALL: Final[Literal[289]] = 289
+ SDL_SCANCODE_CANCEL: Final[Literal[155]] = 155
+ SDL_SCANCODE_CAPSLOCK: Final[Literal[57]] = 57
+ SDL_SCANCODE_CHANNEL_DECREMENT: Final[Literal[261]] = 261
+ SDL_SCANCODE_CHANNEL_INCREMENT: Final[Literal[260]] = 260
+ SDL_SCANCODE_CLEAR: Final[Literal[156]] = 156
+ SDL_SCANCODE_CLEARAGAIN: Final[Literal[162]] = 162
+ SDL_SCANCODE_COMMA: Final[Literal[54]] = 54
+ SDL_SCANCODE_COPY: Final[Literal[124]] = 124
+ SDL_SCANCODE_COUNT: Final[Literal[512]] = 512
+ SDL_SCANCODE_CRSEL: Final[Literal[163]] = 163
+ SDL_SCANCODE_CURRENCYSUBUNIT: Final[Literal[181]] = 181
+ SDL_SCANCODE_CURRENCYUNIT: Final[Literal[180]] = 180
+ SDL_SCANCODE_CUT: Final[Literal[123]] = 123
+ SDL_SCANCODE_D: Final[Literal[7]] = 7
+ SDL_SCANCODE_DECIMALSEPARATOR: Final[Literal[179]] = 179
+ SDL_SCANCODE_DELETE: Final[Literal[76]] = 76
+ SDL_SCANCODE_DOWN: Final[Literal[81]] = 81
+ SDL_SCANCODE_E: Final[Literal[8]] = 8
+ SDL_SCANCODE_END: Final[Literal[77]] = 77
+ SDL_SCANCODE_ENDCALL: Final[Literal[290]] = 290
+ SDL_SCANCODE_EQUALS: Final[Literal[46]] = 46
+ SDL_SCANCODE_ESCAPE: Final[Literal[41]] = 41
+ SDL_SCANCODE_EXECUTE: Final[Literal[116]] = 116
+ SDL_SCANCODE_EXSEL: Final[Literal[164]] = 164
+ SDL_SCANCODE_F10: Final[Literal[67]] = 67
+ SDL_SCANCODE_F11: Final[Literal[68]] = 68
+ SDL_SCANCODE_F12: Final[Literal[69]] = 69
+ SDL_SCANCODE_F13: Final[Literal[104]] = 104
+ SDL_SCANCODE_F14: Final[Literal[105]] = 105
+ SDL_SCANCODE_F15: Final[Literal[106]] = 106
+ SDL_SCANCODE_F16: Final[Literal[107]] = 107
+ SDL_SCANCODE_F17: Final[Literal[108]] = 108
+ SDL_SCANCODE_F18: Final[Literal[109]] = 109
+ SDL_SCANCODE_F19: Final[Literal[110]] = 110
+ SDL_SCANCODE_F1: Final[Literal[58]] = 58
+ SDL_SCANCODE_F20: Final[Literal[111]] = 111
+ SDL_SCANCODE_F21: Final[Literal[112]] = 112
+ SDL_SCANCODE_F22: Final[Literal[113]] = 113
+ SDL_SCANCODE_F23: Final[Literal[114]] = 114
+ SDL_SCANCODE_F24: Final[Literal[115]] = 115
+ SDL_SCANCODE_F2: Final[Literal[59]] = 59
+ SDL_SCANCODE_F3: Final[Literal[60]] = 60
+ SDL_SCANCODE_F4: Final[Literal[61]] = 61
+ SDL_SCANCODE_F5: Final[Literal[62]] = 62
+ SDL_SCANCODE_F6: Final[Literal[63]] = 63
+ SDL_SCANCODE_F7: Final[Literal[64]] = 64
+ SDL_SCANCODE_F8: Final[Literal[65]] = 65
+ SDL_SCANCODE_F9: Final[Literal[66]] = 66
+ SDL_SCANCODE_F: Final[Literal[9]] = 9
+ SDL_SCANCODE_FIND: Final[Literal[126]] = 126
+ SDL_SCANCODE_G: Final[Literal[10]] = 10
+ SDL_SCANCODE_GRAVE: Final[Literal[53]] = 53
+ SDL_SCANCODE_H: Final[Literal[11]] = 11
+ SDL_SCANCODE_HELP: Final[Literal[117]] = 117
+ SDL_SCANCODE_HOME: Final[Literal[74]] = 74
+ SDL_SCANCODE_I: Final[Literal[12]] = 12
+ SDL_SCANCODE_INSERT: Final[Literal[73]] = 73
+ SDL_SCANCODE_INTERNATIONAL1: Final[Literal[135]] = 135
+ SDL_SCANCODE_INTERNATIONAL2: Final[Literal[136]] = 136
+ SDL_SCANCODE_INTERNATIONAL3: Final[Literal[137]] = 137
+ SDL_SCANCODE_INTERNATIONAL4: Final[Literal[138]] = 138
+ SDL_SCANCODE_INTERNATIONAL5: Final[Literal[139]] = 139
+ SDL_SCANCODE_INTERNATIONAL6: Final[Literal[140]] = 140
+ SDL_SCANCODE_INTERNATIONAL7: Final[Literal[141]] = 141
+ SDL_SCANCODE_INTERNATIONAL8: Final[Literal[142]] = 142
+ SDL_SCANCODE_INTERNATIONAL9: Final[Literal[143]] = 143
+ SDL_SCANCODE_J: Final[Literal[13]] = 13
+ SDL_SCANCODE_K: Final[Literal[14]] = 14
+ SDL_SCANCODE_KP_000: Final[Literal[177]] = 177
+ SDL_SCANCODE_KP_00: Final[Literal[176]] = 176
+ SDL_SCANCODE_KP_0: Final[Literal[98]] = 98
+ SDL_SCANCODE_KP_1: Final[Literal[89]] = 89
+ SDL_SCANCODE_KP_2: Final[Literal[90]] = 90
+ SDL_SCANCODE_KP_3: Final[Literal[91]] = 91
+ SDL_SCANCODE_KP_4: Final[Literal[92]] = 92
+ SDL_SCANCODE_KP_5: Final[Literal[93]] = 93
+ SDL_SCANCODE_KP_6: Final[Literal[94]] = 94
+ SDL_SCANCODE_KP_7: Final[Literal[95]] = 95
+ SDL_SCANCODE_KP_8: Final[Literal[96]] = 96
+ SDL_SCANCODE_KP_9: Final[Literal[97]] = 97
+ SDL_SCANCODE_KP_A: Final[Literal[188]] = 188
+ SDL_SCANCODE_KP_AMPERSAND: Final[Literal[199]] = 199
+ SDL_SCANCODE_KP_AT: Final[Literal[206]] = 206
+ SDL_SCANCODE_KP_B: Final[Literal[189]] = 189
+ SDL_SCANCODE_KP_BACKSPACE: Final[Literal[187]] = 187
+ SDL_SCANCODE_KP_BINARY: Final[Literal[218]] = 218
+ SDL_SCANCODE_KP_C: Final[Literal[190]] = 190
+ SDL_SCANCODE_KP_CLEAR: Final[Literal[216]] = 216
+ SDL_SCANCODE_KP_CLEARENTRY: Final[Literal[217]] = 217
+ SDL_SCANCODE_KP_COLON: Final[Literal[203]] = 203
+ SDL_SCANCODE_KP_COMMA: Final[Literal[133]] = 133
+ SDL_SCANCODE_KP_D: Final[Literal[191]] = 191
+ SDL_SCANCODE_KP_DBLAMPERSAND: Final[Literal[200]] = 200
+ SDL_SCANCODE_KP_DBLVERTICALBAR: Final[Literal[202]] = 202
+ SDL_SCANCODE_KP_DECIMAL: Final[Literal[220]] = 220
+ SDL_SCANCODE_KP_DIVIDE: Final[Literal[84]] = 84
+ SDL_SCANCODE_KP_E: Final[Literal[192]] = 192
+ SDL_SCANCODE_KP_ENTER: Final[Literal[88]] = 88
+ SDL_SCANCODE_KP_EQUALS: Final[Literal[103]] = 103
+ SDL_SCANCODE_KP_EQUALSAS400: Final[Literal[134]] = 134
+ SDL_SCANCODE_KP_EXCLAM: Final[Literal[207]] = 207
+ SDL_SCANCODE_KP_F: Final[Literal[193]] = 193
+ SDL_SCANCODE_KP_GREATER: Final[Literal[198]] = 198
+ SDL_SCANCODE_KP_HASH: Final[Literal[204]] = 204
+ SDL_SCANCODE_KP_HEXADECIMAL: Final[Literal[221]] = 221
+ SDL_SCANCODE_KP_LEFTBRACE: Final[Literal[184]] = 184
+ SDL_SCANCODE_KP_LEFTPAREN: Final[Literal[182]] = 182
+ SDL_SCANCODE_KP_LESS: Final[Literal[197]] = 197
+ SDL_SCANCODE_KP_MEMADD: Final[Literal[211]] = 211
+ SDL_SCANCODE_KP_MEMCLEAR: Final[Literal[210]] = 210
+ SDL_SCANCODE_KP_MEMDIVIDE: Final[Literal[214]] = 214
+ SDL_SCANCODE_KP_MEMMULTIPLY: Final[Literal[213]] = 213
+ SDL_SCANCODE_KP_MEMRECALL: Final[Literal[209]] = 209
+ SDL_SCANCODE_KP_MEMSTORE: Final[Literal[208]] = 208
+ SDL_SCANCODE_KP_MEMSUBTRACT: Final[Literal[212]] = 212
+ SDL_SCANCODE_KP_MINUS: Final[Literal[86]] = 86
+ SDL_SCANCODE_KP_MULTIPLY: Final[Literal[85]] = 85
+ SDL_SCANCODE_KP_OCTAL: Final[Literal[219]] = 219
+ SDL_SCANCODE_KP_PERCENT: Final[Literal[196]] = 196
+ SDL_SCANCODE_KP_PERIOD: Final[Literal[99]] = 99
+ SDL_SCANCODE_KP_PLUS: Final[Literal[87]] = 87
+ SDL_SCANCODE_KP_PLUSMINUS: Final[Literal[215]] = 215
+ SDL_SCANCODE_KP_POWER: Final[Literal[195]] = 195
+ SDL_SCANCODE_KP_RIGHTBRACE: Final[Literal[185]] = 185
+ SDL_SCANCODE_KP_RIGHTPAREN: Final[Literal[183]] = 183
+ SDL_SCANCODE_KP_SPACE: Final[Literal[205]] = 205
+ SDL_SCANCODE_KP_TAB: Final[Literal[186]] = 186
+ SDL_SCANCODE_KP_VERTICALBAR: Final[Literal[201]] = 201
+ SDL_SCANCODE_KP_XOR: Final[Literal[194]] = 194
+ SDL_SCANCODE_L: Final[Literal[15]] = 15
+ SDL_SCANCODE_LALT: Final[Literal[226]] = 226
+ SDL_SCANCODE_LANG1: Final[Literal[144]] = 144
+ SDL_SCANCODE_LANG2: Final[Literal[145]] = 145
+ SDL_SCANCODE_LANG3: Final[Literal[146]] = 146
+ SDL_SCANCODE_LANG4: Final[Literal[147]] = 147
+ SDL_SCANCODE_LANG5: Final[Literal[148]] = 148
+ SDL_SCANCODE_LANG6: Final[Literal[149]] = 149
+ SDL_SCANCODE_LANG7: Final[Literal[150]] = 150
+ SDL_SCANCODE_LANG8: Final[Literal[151]] = 151
+ SDL_SCANCODE_LANG9: Final[Literal[152]] = 152
+ SDL_SCANCODE_LCTRL: Final[Literal[224]] = 224
+ SDL_SCANCODE_LEFT: Final[Literal[80]] = 80
+ SDL_SCANCODE_LEFTBRACKET: Final[Literal[47]] = 47
+ SDL_SCANCODE_LGUI: Final[Literal[227]] = 227
+ SDL_SCANCODE_LSHIFT: Final[Literal[225]] = 225
+ SDL_SCANCODE_M: Final[Literal[16]] = 16
+ SDL_SCANCODE_MEDIA_EJECT: Final[Literal[270]] = 270
+ SDL_SCANCODE_MEDIA_FAST_FORWARD: Final[Literal[265]] = 265
+ SDL_SCANCODE_MEDIA_NEXT_TRACK: Final[Literal[267]] = 267
+ SDL_SCANCODE_MEDIA_PAUSE: Final[Literal[263]] = 263
+ SDL_SCANCODE_MEDIA_PLAY: Final[Literal[262]] = 262
+ SDL_SCANCODE_MEDIA_PLAY_PAUSE: Final[Literal[271]] = 271
+ SDL_SCANCODE_MEDIA_PREVIOUS_TRACK: Final[Literal[268]] = 268
+ SDL_SCANCODE_MEDIA_RECORD: Final[Literal[264]] = 264
+ SDL_SCANCODE_MEDIA_REWIND: Final[Literal[266]] = 266
+ SDL_SCANCODE_MEDIA_SELECT: Final[Literal[272]] = 272
+ SDL_SCANCODE_MEDIA_STOP: Final[Literal[269]] = 269
+ SDL_SCANCODE_MENU: Final[Literal[118]] = 118
+ SDL_SCANCODE_MINUS: Final[Literal[45]] = 45
+ SDL_SCANCODE_MODE: Final[Literal[257]] = 257
+ SDL_SCANCODE_MUTE: Final[Literal[127]] = 127
+ SDL_SCANCODE_N: Final[Literal[17]] = 17
+ SDL_SCANCODE_NONUSBACKSLASH: Final[Literal[100]] = 100
+ SDL_SCANCODE_NONUSHASH: Final[Literal[50]] = 50
+ SDL_SCANCODE_NUMLOCKCLEAR: Final[Literal[83]] = 83
+ SDL_SCANCODE_O: Final[Literal[18]] = 18
+ SDL_SCANCODE_OPER: Final[Literal[161]] = 161
+ SDL_SCANCODE_OUT: Final[Literal[160]] = 160
+ SDL_SCANCODE_P: Final[Literal[19]] = 19
+ SDL_SCANCODE_PAGEDOWN: Final[Literal[78]] = 78
+ SDL_SCANCODE_PAGEUP: Final[Literal[75]] = 75
+ SDL_SCANCODE_PASTE: Final[Literal[125]] = 125
+ SDL_SCANCODE_PAUSE: Final[Literal[72]] = 72
+ SDL_SCANCODE_PERIOD: Final[Literal[55]] = 55
+ SDL_SCANCODE_POWER: Final[Literal[102]] = 102
+ SDL_SCANCODE_PRINTSCREEN: Final[Literal[70]] = 70
+ SDL_SCANCODE_PRIOR: Final[Literal[157]] = 157
+ SDL_SCANCODE_Q: Final[Literal[20]] = 20
+ SDL_SCANCODE_R: Final[Literal[21]] = 21
+ SDL_SCANCODE_RALT: Final[Literal[230]] = 230
+ SDL_SCANCODE_RCTRL: Final[Literal[228]] = 228
+ SDL_SCANCODE_RESERVED: Final[Literal[400]] = 400
+ SDL_SCANCODE_RETURN2: Final[Literal[158]] = 158
+ SDL_SCANCODE_RETURN: Final[Literal[40]] = 40
+ SDL_SCANCODE_RGUI: Final[Literal[231]] = 231
+ SDL_SCANCODE_RIGHT: Final[Literal[79]] = 79
+ SDL_SCANCODE_RIGHTBRACKET: Final[Literal[48]] = 48
+ SDL_SCANCODE_RSHIFT: Final[Literal[229]] = 229
+ SDL_SCANCODE_S: Final[Literal[22]] = 22
+ SDL_SCANCODE_SCROLLLOCK: Final[Literal[71]] = 71
+ SDL_SCANCODE_SELECT: Final[Literal[119]] = 119
+ SDL_SCANCODE_SEMICOLON: Final[Literal[51]] = 51
+ SDL_SCANCODE_SEPARATOR: Final[Literal[159]] = 159
+ SDL_SCANCODE_SLASH: Final[Literal[56]] = 56
+ SDL_SCANCODE_SLEEP: Final[Literal[258]] = 258
+ SDL_SCANCODE_SOFTLEFT: Final[Literal[287]] = 287
+ SDL_SCANCODE_SOFTRIGHT: Final[Literal[288]] = 288
+ SDL_SCANCODE_SPACE: Final[Literal[44]] = 44
+ SDL_SCANCODE_STOP: Final[Literal[120]] = 120
+ SDL_SCANCODE_SYSREQ: Final[Literal[154]] = 154
+ SDL_SCANCODE_T: Final[Literal[23]] = 23
+ SDL_SCANCODE_TAB: Final[Literal[43]] = 43
+ SDL_SCANCODE_THOUSANDSSEPARATOR: Final[Literal[178]] = 178
+ SDL_SCANCODE_U: Final[Literal[24]] = 24
+ SDL_SCANCODE_UNDO: Final[Literal[122]] = 122
+ SDL_SCANCODE_UNKNOWN: Final[Literal[0]] = 0
+ SDL_SCANCODE_UP: Final[Literal[82]] = 82
+ SDL_SCANCODE_V: Final[Literal[25]] = 25
+ SDL_SCANCODE_VOLUMEDOWN: Final[Literal[129]] = 129
+ SDL_SCANCODE_VOLUMEUP: Final[Literal[128]] = 128
+ SDL_SCANCODE_W: Final[Literal[26]] = 26
+ SDL_SCANCODE_WAKE: Final[Literal[259]] = 259
+ SDL_SCANCODE_X: Final[Literal[27]] = 27
+ SDL_SCANCODE_Y: Final[Literal[28]] = 28
+ SDL_SCANCODE_Z: Final[Literal[29]] = 29
+ SDL_SENSOR_ACCEL: Final[int]
+ SDL_SENSOR_ACCEL_L: Final[int]
+ SDL_SENSOR_ACCEL_R: Final[int]
+ SDL_SENSOR_GYRO: Final[int]
+ SDL_SENSOR_GYRO_L: Final[int]
+ SDL_SENSOR_GYRO_R: Final[int]
+ SDL_SENSOR_INVALID: Final[Literal[-1]] = -1
+ SDL_SENSOR_UNKNOWN: Final[int]
+ SDL_SIZE_MAX: Final[int]
+ SDL_SURFACE_LOCKED: Final[int]
+ SDL_SURFACE_LOCK_NEEDED: Final[int]
+ SDL_SURFACE_PREALLOCATED: Final[int]
+ SDL_SURFACE_SIMD_ALIGNED: Final[int]
+ SDL_SYSTEM_CURSOR_COUNT: Final[int]
+ SDL_SYSTEM_CURSOR_CROSSHAIR: Final[int]
+ SDL_SYSTEM_CURSOR_DEFAULT: Final[int]
+ SDL_SYSTEM_CURSOR_EW_RESIZE: Final[int]
+ SDL_SYSTEM_CURSOR_E_RESIZE: Final[int]
+ SDL_SYSTEM_CURSOR_MOVE: Final[int]
+ SDL_SYSTEM_CURSOR_NESW_RESIZE: Final[int]
+ SDL_SYSTEM_CURSOR_NE_RESIZE: Final[int]
+ SDL_SYSTEM_CURSOR_NOT_ALLOWED: Final[int]
+ SDL_SYSTEM_CURSOR_NS_RESIZE: Final[int]
+ SDL_SYSTEM_CURSOR_NWSE_RESIZE: Final[int]
+ SDL_SYSTEM_CURSOR_NW_RESIZE: Final[int]
+ SDL_SYSTEM_CURSOR_N_RESIZE: Final[int]
+ SDL_SYSTEM_CURSOR_POINTER: Final[int]
+ SDL_SYSTEM_CURSOR_PROGRESS: Final[int]
+ SDL_SYSTEM_CURSOR_SE_RESIZE: Final[int]
+ SDL_SYSTEM_CURSOR_SW_RESIZE: Final[int]
+ SDL_SYSTEM_CURSOR_S_RESIZE: Final[int]
+ SDL_SYSTEM_CURSOR_TEXT: Final[int]
+ SDL_SYSTEM_CURSOR_WAIT: Final[int]
+ SDL_SYSTEM_CURSOR_W_RESIZE: Final[int]
+ SDL_SYSTEM_THEME_DARK: Final[int]
+ SDL_SYSTEM_THEME_LIGHT: Final[int]
+ SDL_SYSTEM_THEME_UNKNOWN: Final[int]
+ SDL_TEXTINPUT_TYPE_NUMBER: Final[int]
+ SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_HIDDEN: Final[int]
+ SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_VISIBLE: Final[int]
+ SDL_TEXTINPUT_TYPE_TEXT: Final[int]
+ SDL_TEXTINPUT_TYPE_TEXT_EMAIL: Final[int]
+ SDL_TEXTINPUT_TYPE_TEXT_NAME: Final[int]
+ SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_HIDDEN: Final[int]
+ SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_VISIBLE: Final[int]
+ SDL_TEXTINPUT_TYPE_TEXT_USERNAME: Final[int]
+ SDL_TEXTUREACCESS_STATIC: Final[int]
+ SDL_TEXTUREACCESS_STREAMING: Final[int]
+ SDL_TEXTUREACCESS_TARGET: Final[int]
+ SDL_THREAD_ALIVE: Final[int]
+ SDL_THREAD_COMPLETE: Final[int]
+ SDL_THREAD_DETACHED: Final[int]
+ SDL_THREAD_PRIORITY_HIGH: Final[int]
+ SDL_THREAD_PRIORITY_LOW: Final[int]
+ SDL_THREAD_PRIORITY_NORMAL: Final[int]
+ SDL_THREAD_PRIORITY_TIME_CRITICAL: Final[int]
+ SDL_THREAD_UNKNOWN: Final[int]
+ SDL_TIME_FORMAT_12HR: Final[Literal[1]] = 1
+ SDL_TIME_FORMAT_24HR: Final[Literal[0]] = 0
+ SDL_TOUCH_DEVICE_DIRECT: Final[int]
+ SDL_TOUCH_DEVICE_INDIRECT_ABSOLUTE: Final[int]
+ SDL_TOUCH_DEVICE_INDIRECT_RELATIVE: Final[int]
+ SDL_TOUCH_DEVICE_INVALID: Final[Literal[-1]] = -1
+ SDL_TOUCH_MOUSEID: Final[int]
+ SDL_TRANSFER_CHARACTERISTICS_BT1361: Final[Literal[12]] = 12
+ SDL_TRANSFER_CHARACTERISTICS_BT2020_10BIT: Final[Literal[14]] = 14
+ SDL_TRANSFER_CHARACTERISTICS_BT2020_12BIT: Final[Literal[15]] = 15
+ SDL_TRANSFER_CHARACTERISTICS_BT601: Final[Literal[6]] = 6
+ SDL_TRANSFER_CHARACTERISTICS_BT709: Final[Literal[1]] = 1
+ SDL_TRANSFER_CHARACTERISTICS_CUSTOM: Final[Literal[31]] = 31
+ SDL_TRANSFER_CHARACTERISTICS_GAMMA22: Final[Literal[4]] = 4
+ SDL_TRANSFER_CHARACTERISTICS_GAMMA28: Final[Literal[5]] = 5
+ SDL_TRANSFER_CHARACTERISTICS_HLG: Final[Literal[18]] = 18
+ SDL_TRANSFER_CHARACTERISTICS_IEC61966: Final[Literal[11]] = 11
+ SDL_TRANSFER_CHARACTERISTICS_LINEAR: Final[Literal[8]] = 8
+ SDL_TRANSFER_CHARACTERISTICS_LOG100: Final[Literal[9]] = 9
+ SDL_TRANSFER_CHARACTERISTICS_LOG100_SQRT10: Final[Literal[10]] = 10
+ SDL_TRANSFER_CHARACTERISTICS_PQ: Final[Literal[16]] = 16
+ SDL_TRANSFER_CHARACTERISTICS_SMPTE240: Final[Literal[7]] = 7
+ SDL_TRANSFER_CHARACTERISTICS_SMPTE428: Final[Literal[17]] = 17
+ SDL_TRANSFER_CHARACTERISTICS_SRGB: Final[Literal[13]] = 13
+ SDL_TRANSFER_CHARACTERISTICS_UNKNOWN: Final[Literal[0]] = 0
+ SDL_TRANSFER_CHARACTERISTICS_UNSPECIFIED: Final[Literal[2]] = 2
+ SDL_TRAYENTRY_BUTTON: Final[int]
+ SDL_TRAYENTRY_CHECKBOX: Final[int]
+ SDL_TRAYENTRY_CHECKED: Final[int]
+ SDL_TRAYENTRY_DISABLED: Final[int]
+ SDL_TRAYENTRY_SUBMENU: Final[int]
+ SDL_US_PER_SECOND: Final[int]
+ SDL_VERSION: Final[int]
+ SDL_WINDOWPOS_CENTERED: Final[int]
+ SDL_WINDOWPOS_CENTERED_MASK: Final[int]
+ SDL_WINDOWPOS_UNDEFINED: Final[int]
+ SDL_WINDOWPOS_UNDEFINED_MASK: Final[int]
+ SDL_WINDOW_ALWAYS_ON_TOP: Final[int]
+ SDL_WINDOW_BORDERLESS: Final[int]
+ SDL_WINDOW_EXTERNAL: Final[int]
+ SDL_WINDOW_FULLSCREEN: Final[int]
+ SDL_WINDOW_HIDDEN: Final[int]
+ SDL_WINDOW_HIGH_PIXEL_DENSITY: Final[int]
+ SDL_WINDOW_INPUT_FOCUS: Final[int]
+ SDL_WINDOW_KEYBOARD_GRABBED: Final[int]
+ SDL_WINDOW_MAXIMIZED: Final[int]
+ SDL_WINDOW_METAL: Final[int]
+ SDL_WINDOW_MINIMIZED: Final[int]
+ SDL_WINDOW_MODAL: Final[int]
+ SDL_WINDOW_MOUSE_CAPTURE: Final[int]
+ SDL_WINDOW_MOUSE_FOCUS: Final[int]
+ SDL_WINDOW_MOUSE_GRABBED: Final[int]
+ SDL_WINDOW_MOUSE_RELATIVE_MODE: Final[int]
+ SDL_WINDOW_NOT_FOCUSABLE: Final[int]
+ SDL_WINDOW_OCCLUDED: Final[int]
+ SDL_WINDOW_OPENGL: Final[int]
+ SDL_WINDOW_POPUP_MENU: Final[int]
+ SDL_WINDOW_RESIZABLE: Final[int]
+ SDL_WINDOW_SURFACE_VSYNC_ADAPTIVE: Final[int]
+ SDL_WINDOW_SURFACE_VSYNC_DISABLED: Final[int]
+ SDL_WINDOW_TOOLTIP: Final[int]
+ SDL_WINDOW_TRANSPARENT: Final[int]
+ SDL_WINDOW_UTILITY: Final[int]
+ SDL_WINDOW_VULKAN: Final[int]
+ TCODK_0: Final[int]
+ TCODK_1: Final[int]
+ TCODK_2: Final[int]
+ TCODK_3: Final[int]
+ TCODK_4: Final[int]
+ TCODK_5: Final[int]
+ TCODK_6: Final[int]
+ TCODK_7: Final[int]
+ TCODK_8: Final[int]
+ TCODK_9: Final[int]
+ TCODK_ALT: Final[int]
+ TCODK_APPS: Final[int]
+ TCODK_BACKSPACE: Final[int]
+ TCODK_CAPSLOCK: Final[int]
+ TCODK_CHAR: Final[int]
+ TCODK_CONTROL: Final[int]
+ TCODK_DELETE: Final[int]
+ TCODK_DOWN: Final[int]
+ TCODK_END: Final[int]
+ TCODK_ENTER: Final[int]
+ TCODK_ESCAPE: Final[int]
+ TCODK_F10: Final[int]
+ TCODK_F11: Final[int]
+ TCODK_F12: Final[int]
+ TCODK_F1: Final[int]
+ TCODK_F2: Final[int]
+ TCODK_F3: Final[int]
+ TCODK_F4: Final[int]
+ TCODK_F5: Final[int]
+ TCODK_F6: Final[int]
+ TCODK_F7: Final[int]
+ TCODK_F8: Final[int]
+ TCODK_F9: Final[int]
+ TCODK_HOME: Final[int]
+ TCODK_INSERT: Final[int]
+ TCODK_KP0: Final[int]
+ TCODK_KP1: Final[int]
+ TCODK_KP2: Final[int]
+ TCODK_KP3: Final[int]
+ TCODK_KP4: Final[int]
+ TCODK_KP5: Final[int]
+ TCODK_KP6: Final[int]
+ TCODK_KP7: Final[int]
+ TCODK_KP8: Final[int]
+ TCODK_KP9: Final[int]
+ TCODK_KPADD: Final[int]
+ TCODK_KPDEC: Final[int]
+ TCODK_KPDIV: Final[int]
+ TCODK_KPENTER: Final[int]
+ TCODK_KPMUL: Final[int]
+ TCODK_KPSUB: Final[int]
+ TCODK_LEFT: Final[int]
+ TCODK_LWIN: Final[int]
+ TCODK_NONE: Final[int]
+ TCODK_NUMLOCK: Final[int]
+ TCODK_PAGEDOWN: Final[int]
+ TCODK_PAGEUP: Final[int]
+ TCODK_PAUSE: Final[int]
+ TCODK_PRINTSCREEN: Final[int]
+ TCODK_RIGHT: Final[int]
+ TCODK_RWIN: Final[int]
+ TCODK_SCROLLLOCK: Final[int]
+ TCODK_SHIFT: Final[int]
+ TCODK_SPACE: Final[int]
+ TCODK_TAB: Final[int]
+ TCODK_TEXT: Final[int]
+ TCODK_UP: Final[int]
+ TCOD_BKGND_ADD: Final[int]
+ TCOD_BKGND_ADDA: Final[int]
+ TCOD_BKGND_ALPH: Final[int]
+ TCOD_BKGND_BURN: Final[int]
+ TCOD_BKGND_COLOR_BURN: Final[int]
+ TCOD_BKGND_COLOR_DODGE: Final[int]
+ TCOD_BKGND_DARKEN: Final[int]
+ TCOD_BKGND_DEFAULT: Final[int]
+ TCOD_BKGND_LIGHTEN: Final[int]
+ TCOD_BKGND_MULTIPLY: Final[int]
+ TCOD_BKGND_NONE: Final[int]
+ TCOD_BKGND_OVERLAY: Final[int]
+ TCOD_BKGND_SCREEN: Final[int]
+ TCOD_BKGND_SET: Final[int]
+ TCOD_CENTER: Final[int]
+ TCOD_CHAR_ARROW2_E: Final[Literal[16]] = 16
+ TCOD_CHAR_ARROW2_N: Final[Literal[30]] = 30
+ TCOD_CHAR_ARROW2_S: Final[Literal[31]] = 31
+ TCOD_CHAR_ARROW2_W: Final[Literal[17]] = 17
+ TCOD_CHAR_ARROW_E: Final[Literal[26]] = 26
+ TCOD_CHAR_ARROW_N: Final[Literal[24]] = 24
+ TCOD_CHAR_ARROW_S: Final[Literal[25]] = 25
+ TCOD_CHAR_ARROW_W: Final[Literal[27]] = 27
+ TCOD_CHAR_BLOCK1: Final[Literal[176]] = 176
+ TCOD_CHAR_BLOCK2: Final[Literal[177]] = 177
+ TCOD_CHAR_BLOCK3: Final[Literal[178]] = 178
+ TCOD_CHAR_BULLET: Final[Literal[7]] = 7
+ TCOD_CHAR_BULLET_INV: Final[Literal[8]] = 8
+ TCOD_CHAR_BULLET_SQUARE: Final[Literal[254]] = 254
+ TCOD_CHAR_CENT: Final[Literal[189]] = 189
+ TCOD_CHAR_CHECKBOX_SET: Final[Literal[225]] = 225
+ TCOD_CHAR_CHECKBOX_UNSET: Final[Literal[224]] = 224
+ TCOD_CHAR_CLUB: Final[Literal[5]] = 5
+ TCOD_CHAR_COPYRIGHT: Final[Literal[184]] = 184
+ TCOD_CHAR_CROSS: Final[Literal[197]] = 197
+ TCOD_CHAR_CURRENCY: Final[Literal[207]] = 207
+ TCOD_CHAR_DARROW_H: Final[Literal[29]] = 29
+ TCOD_CHAR_DARROW_V: Final[Literal[18]] = 18
+ TCOD_CHAR_DCROSS: Final[Literal[206]] = 206
+ TCOD_CHAR_DHLINE: Final[Literal[205]] = 205
+ TCOD_CHAR_DIAMOND: Final[Literal[4]] = 4
+ TCOD_CHAR_DIVISION: Final[Literal[246]] = 246
+ TCOD_CHAR_DNE: Final[Literal[187]] = 187
+ TCOD_CHAR_DNW: Final[Literal[201]] = 201
+ TCOD_CHAR_DSE: Final[Literal[188]] = 188
+ TCOD_CHAR_DSW: Final[Literal[200]] = 200
+ TCOD_CHAR_DTEEE: Final[Literal[204]] = 204
+ TCOD_CHAR_DTEEN: Final[Literal[202]] = 202
+ TCOD_CHAR_DTEES: Final[Literal[203]] = 203
+ TCOD_CHAR_DTEEW: Final[Literal[185]] = 185
+ TCOD_CHAR_DVLINE: Final[Literal[186]] = 186
+ TCOD_CHAR_EXCLAM_DOUBLE: Final[Literal[19]] = 19
+ TCOD_CHAR_FEMALE: Final[Literal[12]] = 12
+ TCOD_CHAR_FUNCTION: Final[Literal[159]] = 159
+ TCOD_CHAR_GRADE: Final[Literal[248]] = 248
+ TCOD_CHAR_HALF: Final[Literal[171]] = 171
+ TCOD_CHAR_HEART: Final[Literal[3]] = 3
+ TCOD_CHAR_HLINE: Final[Literal[196]] = 196
+ TCOD_CHAR_LIGHT: Final[Literal[15]] = 15
+ TCOD_CHAR_MALE: Final[Literal[11]] = 11
+ TCOD_CHAR_MULTIPLICATION: Final[Literal[158]] = 158
+ TCOD_CHAR_NE: Final[Literal[191]] = 191
+ TCOD_CHAR_NOTE: Final[Literal[13]] = 13
+ TCOD_CHAR_NOTE_DOUBLE: Final[Literal[14]] = 14
+ TCOD_CHAR_NW: Final[Literal[218]] = 218
+ TCOD_CHAR_ONE_QUARTER: Final[Literal[172]] = 172
+ TCOD_CHAR_PILCROW: Final[Literal[20]] = 20
+ TCOD_CHAR_POUND: Final[Literal[156]] = 156
+ TCOD_CHAR_POW1: Final[Literal[251]] = 251
+ TCOD_CHAR_POW2: Final[Literal[253]] = 253
+ TCOD_CHAR_POW3: Final[Literal[252]] = 252
+ TCOD_CHAR_RADIO_SET: Final[Literal[10]] = 10
+ TCOD_CHAR_RADIO_UNSET: Final[Literal[9]] = 9
+ TCOD_CHAR_RESERVED: Final[Literal[169]] = 169
+ TCOD_CHAR_SE: Final[Literal[217]] = 217
+ TCOD_CHAR_SECTION: Final[Literal[21]] = 21
+ TCOD_CHAR_SMILIE: Final[Literal[1]] = 1
+ TCOD_CHAR_SMILIE_INV: Final[Literal[2]] = 2
+ TCOD_CHAR_SPADE: Final[Literal[6]] = 6
+ TCOD_CHAR_SUBP_DIAG: Final[Literal[230]] = 230
+ TCOD_CHAR_SUBP_E: Final[Literal[231]] = 231
+ TCOD_CHAR_SUBP_N: Final[Literal[228]] = 228
+ TCOD_CHAR_SUBP_NE: Final[Literal[227]] = 227
+ TCOD_CHAR_SUBP_NW: Final[Literal[226]] = 226
+ TCOD_CHAR_SUBP_SE: Final[Literal[229]] = 229
+ TCOD_CHAR_SUBP_SW: Final[Literal[232]] = 232
+ TCOD_CHAR_SW: Final[Literal[192]] = 192
+ TCOD_CHAR_TEEE: Final[Literal[195]] = 195
+ TCOD_CHAR_TEEN: Final[Literal[193]] = 193
+ TCOD_CHAR_TEES: Final[Literal[194]] = 194
+ TCOD_CHAR_TEEW: Final[Literal[180]] = 180
+ TCOD_CHAR_THREE_QUARTERS: Final[Literal[243]] = 243
+ TCOD_CHAR_UMLAUT: Final[Literal[249]] = 249
+ TCOD_CHAR_VLINE: Final[Literal[179]] = 179
+ TCOD_CHAR_YEN: Final[Literal[190]] = 190
+ TCOD_COLCTRL_1: Final[Literal[1]] = 1
+ TCOD_COLCTRL_2: Final[int]
+ TCOD_COLCTRL_3: Final[int]
+ TCOD_COLCTRL_4: Final[int]
+ TCOD_COLCTRL_5: Final[int]
+ TCOD_COLCTRL_BACK_RGB: Final[int]
+ TCOD_COLCTRL_FORE_RGB: Final[int]
+ TCOD_COLCTRL_NUMBER: Final[Literal[5]] = 5
+ TCOD_COLCTRL_STOP: Final[int]
+ TCOD_COMPILEDVERSION: Final[int]
+ TCOD_DISTRIBUTION_GAUSSIAN: Final[int]
+ TCOD_DISTRIBUTION_GAUSSIAN_INVERSE: Final[int]
+ TCOD_DISTRIBUTION_GAUSSIAN_RANGE: Final[int]
+ TCOD_DISTRIBUTION_GAUSSIAN_RANGE_INVERSE: Final[int]
+ TCOD_DISTRIBUTION_LINEAR: Final[int]
+ TCOD_EVENT_ANY: Final[int]
+ TCOD_EVENT_FINGER: Final[int]
+ TCOD_EVENT_FINGER_MOVE: Final[Literal[32]] = 32
+ TCOD_EVENT_FINGER_PRESS: Final[Literal[64]] = 64
+ TCOD_EVENT_FINGER_RELEASE: Final[Literal[128]] = 128
+ TCOD_EVENT_KEY: Final[int]
+ TCOD_EVENT_KEY_PRESS: Final[Literal[1]] = 1
+ TCOD_EVENT_KEY_RELEASE: Final[Literal[2]] = 2
+ TCOD_EVENT_MOUSE: Final[int]
+ TCOD_EVENT_MOUSE_MOVE: Final[Literal[4]] = 4
+ TCOD_EVENT_MOUSE_PRESS: Final[Literal[8]] = 8
+ TCOD_EVENT_MOUSE_RELEASE: Final[Literal[16]] = 16
+ TCOD_EVENT_NONE: Final[Literal[0]] = 0
+ TCOD_E_ERROR: Final[Literal[-1]] = -1
+ TCOD_E_INVALID_ARGUMENT: Final[Literal[-2]] = -2
+ TCOD_E_OK: Final[Literal[0]] = 0
+ TCOD_E_OUT_OF_MEMORY: Final[Literal[-3]] = -3
+ TCOD_E_REQUIRES_ATTENTION: Final[Literal[-4]] = -4
+ TCOD_E_WARN: Final[Literal[1]] = 1
+ TCOD_FALLBACK_FONT_SIZE: Final[Literal[16]] = 16
+ TCOD_FONT_LAYOUT_ASCII_INCOL: Final[Literal[1]] = 1
+ TCOD_FONT_LAYOUT_ASCII_INROW: Final[Literal[2]] = 2
+ TCOD_FONT_LAYOUT_CP437: Final[Literal[16]] = 16
+ TCOD_FONT_LAYOUT_TCOD: Final[Literal[8]] = 8
+ TCOD_FONT_TYPE_GRAYSCALE: Final[Literal[4]] = 4
+ TCOD_FONT_TYPE_GREYSCALE: Final[Literal[4]] = 4
+ TCOD_KEY_PRESSED: Final[Literal[1]] = 1
+ TCOD_KEY_RELEASED: Final[Literal[2]] = 2
+ TCOD_KEY_TEXT_SIZE: Final[Literal[32]] = 32
+ TCOD_LEFT: Final[int]
+ TCOD_LEX_CHAR: Final[Literal[7]] = 7
+ TCOD_LEX_COMMENT: Final[Literal[9]] = 9
+ TCOD_LEX_EOF: Final[Literal[8]] = 8
+ TCOD_LEX_FLAG_NESTING_COMMENT: Final[Literal[2]] = 2
+ TCOD_LEX_FLAG_NOCASE: Final[Literal[1]] = 1
+ TCOD_LEX_FLAG_TOKENIZE_COMMENTS: Final[Literal[4]] = 4
+ TCOD_LEX_FLOAT: Final[Literal[6]] = 6
+ TCOD_LEX_IDEN: Final[Literal[3]] = 3
+ TCOD_LEX_INTEGER: Final[Literal[5]] = 5
+ TCOD_LEX_KEYWORD: Final[Literal[2]] = 2
+ TCOD_LEX_KEYWORD_SIZE: Final[Literal[20]] = 20
+ TCOD_LEX_MAX_KEYWORDS: Final[Literal[100]] = 100
+ TCOD_LEX_MAX_SYMBOLS: Final[Literal[100]] = 100
+ TCOD_LEX_STRING: Final[Literal[4]] = 4
+ TCOD_LEX_SYMBOL: Final[Literal[1]] = 1
+ TCOD_LEX_SYMBOL_SIZE: Final[Literal[5]] = 5
+ TCOD_LEX_UNKNOWN: Final[Literal[0]] = 0
+ TCOD_LOG_CRITICAL: Final[Literal[50]] = 50
+ TCOD_LOG_DEBUG: Final[Literal[10]] = 10
+ TCOD_LOG_ERROR: Final[Literal[40]] = 40
+ TCOD_LOG_INFO: Final[Literal[20]] = 20
+ TCOD_LOG_WARNING: Final[Literal[30]] = 30
+ TCOD_MAJOR_VERSION: Final[Literal[2]] = 2
+ TCOD_MINOR_VERSION: Final[Literal[2]] = 2
+ TCOD_NB_RENDERERS: Final[int]
+ TCOD_NOISE_DEFAULT: Final[Literal[0]] = 0
+ TCOD_NOISE_MAX_DIMENSIONS: Final[Literal[4]] = 4
+ TCOD_NOISE_MAX_OCTAVES: Final[Literal[128]] = 128
+ TCOD_NOISE_PERLIN: Final[Literal[1]] = 1
+ TCOD_NOISE_SIMPLEX: Final[Literal[2]] = 2
+ TCOD_NOISE_WAVELET: Final[Literal[4]] = 4
+ TCOD_PATCHLEVEL: Final[Literal[1]] = 1
+ TCOD_PATHFINDER_MAX_DIMENSIONS: Final[Literal[4]] = 4
+ TCOD_RENDERER_GLSL: Final[int]
+ TCOD_RENDERER_OPENGL2: Final[int]
+ TCOD_RENDERER_OPENGL: Final[int]
+ TCOD_RENDERER_SDL2: Final[int]
+ TCOD_RENDERER_SDL: Final[int]
+ TCOD_RENDERER_XTERM: Final[int]
+ TCOD_RIGHT: Final[int]
+ TCOD_RNG_CMWC: Final[int]
+ TCOD_RNG_MT: Final[int]
+ TCOD_TYPE_BOOL: Final[int]
+ TCOD_TYPE_CHAR: Final[int]
+ TCOD_TYPE_COLOR: Final[int]
+ TCOD_TYPE_CUSTOM00: Final[int]
+ TCOD_TYPE_CUSTOM01: Final[int]
+ TCOD_TYPE_CUSTOM02: Final[int]
+ TCOD_TYPE_CUSTOM03: Final[int]
+ TCOD_TYPE_CUSTOM04: Final[int]
+ TCOD_TYPE_CUSTOM05: Final[int]
+ TCOD_TYPE_CUSTOM06: Final[int]
+ TCOD_TYPE_CUSTOM07: Final[int]
+ TCOD_TYPE_CUSTOM08: Final[int]
+ TCOD_TYPE_CUSTOM09: Final[int]
+ TCOD_TYPE_CUSTOM10: Final[int]
+ TCOD_TYPE_CUSTOM11: Final[int]
+ TCOD_TYPE_CUSTOM12: Final[int]
+ TCOD_TYPE_CUSTOM13: Final[int]
+ TCOD_TYPE_CUSTOM14: Final[int]
+ TCOD_TYPE_CUSTOM15: Final[int]
+ TCOD_TYPE_DICE: Final[int]
+ TCOD_TYPE_FLOAT: Final[int]
+ TCOD_TYPE_INT: Final[int]
+ TCOD_TYPE_LIST: Final[Literal[1024]] = 1024
+ TCOD_TYPE_NONE: Final[int]
+ TCOD_TYPE_STRING: Final[int]
+ TCOD_TYPE_VALUELIST00: Final[int]
+ TCOD_TYPE_VALUELIST01: Final[int]
+ TCOD_TYPE_VALUELIST02: Final[int]
+ TCOD_TYPE_VALUELIST03: Final[int]
+ TCOD_TYPE_VALUELIST04: Final[int]
+ TCOD_TYPE_VALUELIST05: Final[int]
+ TCOD_TYPE_VALUELIST06: Final[int]
+ TCOD_TYPE_VALUELIST07: Final[int]
+ TCOD_TYPE_VALUELIST08: Final[int]
+ TCOD_TYPE_VALUELIST09: Final[int]
+ TCOD_TYPE_VALUELIST10: Final[int]
+ TCOD_TYPE_VALUELIST11: Final[int]
+ TCOD_TYPE_VALUELIST12: Final[int]
+ TCOD_TYPE_VALUELIST13: Final[int]
+ TCOD_TYPE_VALUELIST14: Final[int]
+ TCOD_TYPE_VALUELIST15: Final[int]
+ TCOD_ctx: Any
+ kNoiseImplementationFBM: Final[int]
+ kNoiseImplementationSimple: Final[int]
+ kNoiseImplementationTurbulence: Final[int]
+ np_float16: Final[int]
+ np_float32: Final[int]
+ np_float64: Final[int]
+ np_int16: Final[int]
+ np_int32: Final[int]
+ np_int64: Final[int]
+ np_int8: Final[int]
+ np_uint16: Final[int]
+ np_uint32: Final[int]
+ np_uint64: Final[int]
+ np_uint8: Final[int]
+ np_undefined: Final[Literal[0]] = 0
+
+lib: _lib
+ffi: Any
diff --git a/tcod/bsp.py b/tcod/bsp.py
index c73a414f..21aa093d 100644
--- a/tcod/bsp.py
+++ b/tcod/bsp.py
@@ -1,31 +1,12 @@
-'''
-The following example shows how to travrse the BSP tree using Python. This
+r"""Libtcod's Binary Space Partitioning.
+
+The following example shows how to traverse the BSP tree using Python. This
assumes `create_room` and `connect_rooms` will be replaced by custom code.
Example::
import tcod.bsp
- def create_room(node):
- """Initialize the room at this current node."""
- print('Create a room for %s.' % node)
-
- def connect_rooms(node):
- """Connect two fully initialized rooms."""
- node1, node2 = node.children
- print('Connect the rooms:\\n%s\\n%s' % (node1, node2))
-
- def traverse(node):
- """Traverse a BSP tree dispatching nodes to the correct calls."""
- # For nodes without children, node.children is an empty tuple.
- for child in node.children:
- traverse(child)
-
- if node.children:
- connect_rooms(node)
- else:
- create_room(node)
-
bsp = tcod.bsp.BSP(x=0, y=0, width=80, height=60)
bsp.split_recursive(
depth=5,
@@ -33,17 +14,33 @@ def traverse(node):
min_height=3,
max_horizontal_ratio=1.5,
max_vertical_ratio=1.5,
- )
- traverse(bsp)
-'''
-from __future__ import absolute_import as _
+ )
-from tcod.libtcod import lib, ffi
+ # In pre order, leaf nodes are visited before the nodes that connect them.
+ for node in bsp.pre_order():
+ if node.children:
+ node1, node2 = node.children
+ print('Connect the rooms:\n%s\n%s' % (node1, node2))
+ else:
+ print('Dig a room for %s.' % node)
+"""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any
+
+from typing_extensions import deprecated
+from tcod.cffi import ffi, lib
-class BSP(object):
- """A binary space partitioning tree which can be used for simple dungeon
- generation.
+if TYPE_CHECKING:
+ from collections.abc import Iterator
+
+ import tcod.random
+
+
+class BSP:
+ """A binary space partitioning tree which can be used for simple dungeon generation.
Attributes:
x (int): Rectangle left coordinate.
@@ -54,9 +51,9 @@ class BSP(object):
position (int): The integer of where the node was split.
horizontal (bool): This nodes split orientation.
parent (Optional[BSP]): This nodes parent or None
- children (Optional[Tuple[BSP, BSP]]):
+ children (Union[Tuple[()], Tuple[BSP, BSP]]):
A tuple of (left, right) BSP instances, or
- None if this BSP has no children.
+ an empty tuple if this BSP has no children.
Args:
x (int): Rectangle left coordinate.
@@ -65,7 +62,8 @@ class BSP(object):
height (int): Rectangle height.
"""
- def __init__(self, x, y, width, height):
+ def __init__(self, x: int, y: int, width: int, height: int) -> None:
+ """Initialize a root node of a BSP tree."""
self.x = x
self.y = y
self.width = width
@@ -75,42 +73,44 @@ def __init__(self, x, y, width, height):
self.position = 0
self.horizontal = False
- self.parent = None
- self.children = ()
+ self.parent: BSP | None = None
+ self.children: tuple[()] | tuple[BSP, BSP] = ()
@property
- def w(self):
+ @deprecated("This attribute has been renamed to `width`.", category=FutureWarning)
+ def w(self) -> int: # noqa: D102
return self.width
+
@w.setter
- def w(self, value):
+ def w(self, value: int) -> None:
self.width = value
@property
- def h(self):
+ @deprecated("This attribute has been renamed to `height`.", category=FutureWarning)
+ def h(self) -> int: # noqa: D102
return self.height
+
@h.setter
- def h(self, value):
+ def h(self, value: int) -> None:
self.height = value
- def _as_cdata(self):
- cdata = ffi.gc(lib.TCOD_bsp_new_with_size(self.x, self.y,
- self.width, self.height),
- lib.TCOD_bsp_delete)
+ def _as_cdata(self) -> Any: # noqa: ANN401
+ cdata = ffi.gc(
+ lib.TCOD_bsp_new_with_size(self.x, self.y, self.width, self.height),
+ lib.TCOD_bsp_delete,
+ )
cdata.level = self.level
return cdata
- def __str__(self):
+ def __repr__(self) -> str:
"""Provide a useful readout when printed."""
- status = 'leaf'
+ status = "leaf"
if self.children:
- status = ('split at position=%i,horizontal=%r' %
- (self.position, self.horizontal))
+ status = f"split at position={self.position},horizontal={self.horizontal!r}"
- return ('<%s(x=%i,y=%i,width=%i,height=%i)level=%i,%s>' %
- (self.__class__.__name__,
- self.x, self.y, self.width, self.height, self.level, status))
+ return f"<{self.__class__.__name__}(x={self.x},y={self.y},width={self.width},height={self.height}) level={self.level} {status}>"
- def _unpack_bsp_tree(self, cdata):
+ def _unpack_bsp_tree(self, cdata: Any) -> None: # noqa: ANN401
self.x = cdata.x
self.y = cdata.y
self.width = cdata.w
@@ -126,87 +126,122 @@ def _unpack_bsp_tree(self, cdata):
self.children[1].parent = self
self.children[1]._unpack_bsp_tree(lib.TCOD_bsp_right(cdata))
- def split_once(self, horizontal, position):
+ def split_once(
+ self,
+ horizontal: bool, # noqa: FBT001
+ position: int,
+ ) -> None:
"""Split this partition into 2 sub-partitions.
Args:
- horizontal (bool):
- position (int):
+ horizontal (bool): If True then the sub-partition is split into an upper and bottom half.
+ position (int): The position of where to put the divider relative to the current node.
"""
cdata = self._as_cdata()
lib.TCOD_bsp_split_once(cdata, horizontal, position)
self._unpack_bsp_tree(cdata)
- def split_recursive(self, depth, min_width, min_height,
- max_horizontal_ratio, max_vertical_ratio, seed=None):
+ def split_recursive( # noqa: PLR0913
+ self,
+ depth: int,
+ min_width: int,
+ min_height: int,
+ max_horizontal_ratio: float,
+ max_vertical_ratio: float,
+ seed: tcod.random.Random | None = None,
+ ) -> None:
"""Divide this partition recursively.
Args:
- depth (int): The maximum depth to divide this object recursively.
- min_width (int): The minimum width of any individual partition.
- min_height (int): The minimum height of any individual partition.
- max_horizontal_ratio (float):
- Prevent creating a horizontal ratio more extreme than this.
- max_vertical_ratio (float):
- Prevent creating a vertical ratio more extreme than this.
- seed (Optional[tcod.random.Random]):
- The random number generator to use.
+ depth: The maximum depth to divide this object recursively.
+ min_width: The minimum width of any individual partition.
+ min_height: The minimum height of any individual partition.
+ max_horizontal_ratio: Prevent creating a horizontal ratio more extreme than this.
+ max_vertical_ratio: Prevent creating a vertical ratio more extreme than this.
+ seed: The random number generator to use.
"""
cdata = self._as_cdata()
lib.TCOD_bsp_split_recursive(
cdata,
- seed or ffi.NULL,
+ seed.random_c if seed is not None else ffi.NULL,
depth,
- min_width, min_height,
- max_horizontal_ratio, max_vertical_ratio,
+ min_width,
+ min_height,
+ max_horizontal_ratio,
+ max_vertical_ratio,
)
self._unpack_bsp_tree(cdata)
- def walk(self):
- """Iterate over this BSP's hieracrhy.
-
- The iterator will include the instance which called it.
- It will traverse its own children and grandchildren, in no particular
- order.
-
- Returns:
- Iterator[BSP]: An iterator of BSP nodes.
+ @deprecated("Use pre_order method instead of walk.")
+ def walk(self) -> Iterator[BSP]:
+ """Iterate over this BSP's hierarchy in pre order.
.. deprecated:: 2.3
- See the module example for how to iterate over a BSP tree.
+ Use :any:`pre_order` instead.
"""
- return self._iter_post_order()
+ return self.post_order()
+
+ def pre_order(self) -> Iterator[BSP]:
+ """Iterate over this BSP's hierarchy in pre order.
- def _iter_pre_order(self):
+ .. versionadded:: 8.3
+ """
yield self
for child in self.children:
- for grandchild in child._iter_pre_order():
- yield grandchild
+ yield from child.pre_order()
+
+ def in_order(self) -> Iterator[BSP]:
+ """Iterate over this BSP's hierarchy in order.
- def _iter_in_order(self):
+ .. versionadded:: 8.3
+ """
if self.children:
- for grandchild in self.children[0]._iter_in_order():
- yield grandchild
+ yield from self.children[0].in_order()
yield self
- for grandchild in self.children[1]._iter_in_order():
- yield grandchild
+ yield from self.children[1].in_order()
else:
yield self
- def _iter_post_order(self):
+ def post_order(self) -> Iterator[BSP]:
+ """Iterate over this BSP's hierarchy in post order.
+
+ .. versionadded:: 8.3
+ """
for child in self.children:
- for grandchild in child._iter_post_order():
- yield grandchild
+ yield from child.post_order()
yield self
- def _iter_level_order(self):
- return sorted(self._iter_pre_order(), key=lambda n:n.level)
-
- def _iter_inverted_level_order(self):
- return reversed(self._iter_level_order())
+ def level_order(self) -> Iterator[BSP]:
+ """Iterate over this BSP's hierarchy in level order.
- def contains(self, x, y):
- """Returns True if this node contains these coordinates.
+ .. versionadded:: 8.3
+ """
+ next_ = [self]
+ while next_:
+ level = next_
+ next_ = []
+ yield from level
+ for node in level:
+ next_.extend(node.children)
+
+ def inverted_level_order(self) -> Iterator[BSP]:
+ """Iterate over this BSP's hierarchy in inverse level order.
+
+ .. versionadded:: 8.3
+ """
+ levels: list[list[BSP]] = []
+ next_: list[BSP] = [self]
+ while next_:
+ levels.append(next_)
+ level = next_
+ next_ = []
+ for node in level:
+ next_.extend(node.children)
+ while levels:
+ yield from levels.pop()
+
+ def contains(self, x: int, y: int) -> bool:
+ """Return True if this node contains these coordinates.
Args:
x (int): X position to check.
@@ -216,14 +251,13 @@ def contains(self, x, y):
bool: True if this node contains these coordinates.
Otherwise False.
"""
- return (self.x <= x < self.x + self.width and
- self.y <= y < self.y + self.height)
+ return self.x <= x < self.x + self.width and self.y <= y < self.y + self.height
- def find_node(self, x, y):
+ def find_node(self, x: int, y: int) -> BSP | None:
"""Return the deepest node which contains these coordinates.
Returns:
- Optional[BSP]: BSP object or None.
+ BSP object or None.
"""
if not self.contains(x, y):
return None
@@ -232,3 +266,6 @@ def find_node(self, x, y):
if found:
return found
return self
+
+
+__all__ = ["BSP"]
diff --git a/tcod/cdef.h b/tcod/cdef.h
index e2139b53..aecccc9c 100644
--- a/tcod/cdef.h
+++ b/tcod/cdef.h
@@ -1,21 +1,23 @@
/* Python specific cdefs which are loaded directly into cffi. */
+#include "../libtcod/src/libtcod/libtcod.h"
extern "Python" {
+bool _pycall_parser_new_struct(TCOD_parser_struct_t str, const char* name);
+bool _pycall_parser_new_flag(const char* name);
+bool _pycall_parser_new_property(const char* propname, TCOD_value_type_t type, TCOD_value_t value);
+bool _pycall_parser_end_struct(TCOD_parser_struct_t str, const char* name);
+void _pycall_parser_error(const char* msg);
-bool _pycall_parser_new_struct(TCOD_parser_struct_t str, const char *name);
-bool _pycall_parser_new_flag(const char *name);
-bool _pycall_parser_new_property(
- const char *propname, TCOD_value_type_t type, TCOD_value_t value);
-bool _pycall_parser_end_struct(TCOD_parser_struct_t str, const char *name);
-void _pycall_parser_error(const char *msg);
+bool _pycall_bsp_callback(TCOD_bsp_t* node, void* userData);
-bool _pycall_bsp_callback(TCOD_bsp_t *node, void *userData);
+float _pycall_path_old(int x, int y, int xDest, int yDest, void* user_data);
+float _pycall_path_simple(int x, int y, int xDest, int yDest, void* user_data);
+float _pycall_path_swap_src_dest(int x1, int y1, int x2, int y2, void* user_data);
+float _pycall_path_dest_only(int x1, int y1, int x2, int y2, void* user_data);
-float _pycall_path_old(int x, int y, int xDest, int yDest, void *user_data);
-float _pycall_path_simple(int x, int y, int xDest, int yDest, void *user_data);
-float _pycall_path_swap_src_dest(int x1, int y1,
- int x2, int y2, void *user_data);
-float _pycall_path_dest_only(int x1, int y1, int x2, int y2, void *user_data);
+void _pycall_sdl_hook(struct SDL_Surface*);
-void _pycall_sdl_hook(void *);
+void _pycall_cli_output(void* userdata, const char* output);
+// Libtcod log watch function.
+void _libtcod_log_watcher(const TCOD_LogMessage* message, void* userdata);
}
diff --git a/tcod/cffi.h b/tcod/cffi.h
index 4d0443e9..5f8fa494 100644
--- a/tcod/cffi.h
+++ b/tcod/cffi.h
@@ -1,19 +1,11 @@
-
/* This header is the entry point for the cffi parser.
- Anything included here will be accessible from tcod.libtcod.lib */
-
-#ifndef TDL_NO_SDL2_EXPORTS
-/* Ignore headers with issues. */
-#define SDL_thread_h_
-#include
-#endif
-
+ Anything included here will be accessible from tcod.loader.lib */
#include "../libtcod/src/libtcod/libtcod.h"
#include "../libtcod/src/libtcod/libtcod_int.h"
+#include "../libtcod/src/libtcod/renderer_xterm.h"
+#include "../libtcod/src/libtcod/tileset_truetype.h"
#include "../libtcod/src/libtcod/wrappers.h"
-
#include "noise.h"
#include "path.h"
#include "random.h"
#include "tcod.h"
-#include "tdl.h"
diff --git a/tcod/cffi.py b/tcod/cffi.py
new file mode 100644
index 00000000..57f4039b
--- /dev/null
+++ b/tcod/cffi.py
@@ -0,0 +1,77 @@
+"""This module handles loading of the libtcod cffi API."""
+
+from __future__ import annotations
+
+import logging
+import os
+import platform
+import sys
+from pathlib import Path
+from typing import Any, Literal
+
+import cffi
+
+logger = logging.getLogger("tcod")
+
+__sdl_version__ = ""
+
+REQUIRED_SDL_VERSION = (3, 2, 0)
+
+ffi_check = cffi.FFI()
+ffi_check.cdef(
+ """
+int SDL_GetVersion(void);
+"""
+)
+
+
+def verify_dependencies() -> None:
+ """Try to make sure dependencies exist on this system."""
+ if sys.platform == "win32":
+ lib_test: Any = ffi_check.dlopen("SDL3.dll") # Make sure SDL3.dll is here.
+ int_version = lib_test.SDL_GetVersion() # Need to check this version.
+ major = int_version // 1000000
+ minor = (int_version // 1000) % 1000
+ patch = int_version % 1000
+ version_tuple = major, minor, patch
+ if version_tuple < REQUIRED_SDL_VERSION:
+ msg = f"Tried to load an old version of SDL {version_tuple!r}"
+ raise RuntimeError(msg)
+
+
+def get_architecture() -> Literal["x86", "x64", "arm64"]:
+ """Return the Windows architecture."""
+ if "(ARM64)" in sys.version:
+ return "arm64"
+ return "x86" if platform.architecture()[0] == "32bit" else "x64"
+
+
+def get_sdl_version() -> str:
+ int_version = lib.SDL_GetVersion()
+ return f"{int_version // 1000000}.{(int_version // 1000) % 1000}.{int_version % 1000}"
+
+
+if sys.platform == "win32":
+ # add Windows dll's to PATH
+ os.environ["PATH"] = f"""{Path(__file__).parent / get_architecture()}{os.pathsep}{os.environ["PATH"]}"""
+
+
+verify_dependencies()
+from tcod._libtcod import ffi, lib # noqa: E402
+
+__sdl_version__ = get_sdl_version()
+
+
+@ffi.def_extern() # type: ignore[untyped-decorator]
+def _libtcod_log_watcher(message: Any, _userdata: None) -> None: # noqa: ANN401
+ text = str(ffi.string(message.message), encoding="utf-8")
+ source = str(ffi.string(message.source), encoding="utf-8")
+ level = int(message.level)
+ lineno = int(message.lineno)
+ logger.log(level, "%s:%d:%s", source, lineno, text)
+
+
+lib.TCOD_set_log_callback(lib._libtcod_log_watcher, ffi.NULL)
+lib.TCOD_set_log_level(0)
+
+__all__ = ["__sdl_version__", "ffi", "lib"]
diff --git a/tcod/color.py b/tcod/color.py
index b95055b1..bf9dabb3 100644
--- a/tcod/color.py
+++ b/tcod/color.py
@@ -1,14 +1,16 @@
-"""
+"""Old libtcod color management."""
-"""
+from __future__ import annotations
-from __future__ import absolute_import
+import warnings
+from typing import Any
-from tcod.libtcod import ffi, lib
+from tcod._internal import deprecate
+from tcod.cffi import lib
-class Color(list):
- """
+class Color(list[int]):
+ """Old-style libtcodpy color class.
Args:
r (int): Red value, from 0 to 255.
@@ -16,52 +18,80 @@ class Color(list):
b (int): Blue value, from 0 to 255.
"""
- def __init__(self, r=0, g=0, b=0):
- self[:] = (r & 0xff, g & 0xff, b & 0xff)
+ def __init__(self, r: int = 0, g: int = 0, b: int = 0) -> None: # noqa: D107
+ list.__setitem__(self, slice(None), (r & 0xFF, g & 0xFF, b & 0xFF))
@property
- def r(self):
- """int: Red value, always normalised to 0-255."""
- return self[0]
+ def r(self) -> int:
+ """int: Red value, always normalized to 0-255.
+
+ .. deprecated:: 9.2
+ Color attributes will not be mutable in the future.
+ """
+ return int(self[0])
@r.setter
- def r(self, value):
- self[0] = value & 0xff
+ @deprecate("Setting color attributes has been deprecated.", category=FutureWarning)
+ def r(self, value: int) -> None:
+ self[0] = value & 0xFF
@property
- def g(self):
- """int: Green value, always normalised to 0-255."""
- return self[1]
+ def g(self) -> int:
+ """int: Green value, always normalized to 0-255.
+
+ .. deprecated:: 9.2
+ Color attributes will not be mutable in the future.
+ """
+ return int(self[1])
+
@g.setter
- def g(self, value):
- self[1] = value & 0xff
+ @deprecate("Setting color attributes has been deprecated.", category=FutureWarning)
+ def g(self, value: int) -> None:
+ self[1] = value & 0xFF
@property
- def b(self):
- """int: Blue value, always normalised to 0-255."""
- return self[2]
+ def b(self) -> int:
+ """int: Blue value, always normalized to 0-255.
+
+ .. deprecated:: 9.2
+ Color attributes will not be mutable in the future.
+ """
+ return int(self[2])
+
@b.setter
- def b(self, value):
- self[2] = value & 0xff
+ @deprecate("Setting color attributes has been deprecated.", category=FutureWarning)
+ def b(self, value: int) -> None:
+ self[2] = value & 0xFF
@classmethod
- def _new_from_cdata(cls, cdata):
- """new in libtcod-cffi"""
+ def _new_from_cdata(cls, cdata: Any) -> Color: # noqa: ANN401
return cls(cdata.r, cdata.g, cdata.b)
- def __getitem__(self, index):
- try:
- return list.__getitem__(self, index)
- except TypeError:
- return list.__getitem__(self, 'rgb'.index(index))
+ def __getitem__(self, index: Any) -> Any: # noqa: ANN401
+ """Return a color channel.
- def __setitem__(self, index, value):
- try:
- list.__setitem__(self, index, value)
- except TypeError:
- list.__setitem__(self, 'rgb'.index(index), value)
+ .. deprecated:: 9.2
+ Accessing colors via a letter index is deprecated.
+ """
+ if isinstance(index, str):
+ warnings.warn(
+ "Accessing colors via a letter index is deprecated",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return super().__getitem__("rgb".index(index))
+ return super().__getitem__(index)
+
+ @deprecate("This class will not be mutable in the future.", category=FutureWarning)
+ def __setitem__(self, index: Any, value: Any) -> None: # noqa: ANN401, D105
+ if isinstance(index, str):
+ super().__setitem__("rgb".index(index), value)
+ else:
+ super().__setitem__(index, value)
- def __eq__(self, other):
+ __hash__ = None
+
+ def __eq__(self, other: object) -> bool:
"""Compare equality between colors.
Also compares with standard sequences such as 3-item tuples or lists.
@@ -71,23 +101,35 @@ def __eq__(self, other):
except TypeError:
return False
- def __add__(self, other):
- """Add two colors together."""
+ @deprecate("Use NumPy instead for color math operations.", category=FutureWarning)
+ def __add__(self, other: object) -> Color: # type: ignore[override]
+ """Add two colors together.
+
+ .. deprecated:: 9.2
+ Use NumPy instead for color math operations.
+ """
return Color._new_from_cdata(lib.TCOD_color_add(self, other))
- def __sub__(self, other):
- """Subtract one color from another."""
+ @deprecate("Use NumPy instead for color math operations.", category=FutureWarning)
+ def __sub__(self, other: object) -> Color:
+ """Subtract one color from another.
+
+ .. deprecated:: 9.2
+ Use NumPy instead for color math operations.
+ """
return Color._new_from_cdata(lib.TCOD_color_subtract(self, other))
- def __mul__(self, other):
- """Multiply with a scaler or another color."""
+ @deprecate("Use NumPy instead for color math operations.", category=FutureWarning)
+ def __mul__(self, other: object) -> Color:
+ """Multiply with a scaler or another color.
+
+ .. deprecated:: 9.2
+ Use NumPy instead for color math operations.
+ """
if isinstance(other, (Color, list, tuple)):
return Color._new_from_cdata(lib.TCOD_color_multiply(self, other))
- else:
- return Color._new_from_cdata(
- lib.TCOD_color_multiply_scalar(self, other))
+ return Color._new_from_cdata(lib.TCOD_color_multiply_scalar(self, other)) # type: ignore[arg-type]
- def __repr__(self):
+ def __repr__(self) -> str:
"""Return a printable representation of the current color."""
- return "%s(%i,%i,%i)" % (self.__class__.__name__,
- self.r, self.g, self.b)
+ return f"{self.__class__.__name__}({self.r!r}, {self.g!r}, {self.b!r})"
diff --git a/tcod/console.py b/tcod/console.py
index 250ab818..bfcbce00 100644
--- a/tcod/console.py
+++ b/tcod/console.py
@@ -1,230 +1,505 @@
+"""Libtcod tile-based Consoles and printing functions.
+
+Libtcod consoles are a strictly tile-based representation of colored glyphs/tiles.
+To render a console you need a tileset and a window to render to.
+See :ref:`getting-started` for info on how to set those up.
"""
-libtcod works with a special 'root' console. You create this console using
-the :any:`tcod.console_init_root` function. Usually after setting the font
-with :any:`console_set_custom_font` first.
-Example::
+from __future__ import annotations
- # Make sure 'arial10x10.png' is in the same directory as this script.
- import time
+import warnings
+from pathlib import Path
+from typing import TYPE_CHECKING, Any, Literal, overload
- import tcod
+import numpy as np
+from typing_extensions import Self, deprecated
- # Setup the font.
- tcod.console_set_custom_font(
- 'arial10x10.png',
- tcod.FONT_LAYOUT_TCOD,
- )
- # Initialize the root console in a context.
- with tcod.console_init_root(80, 60, 'title') as root_console:
- root_console.print_(x=0, y=0, string='Hello World!')
- tcod.console_flush() # Show the console.
- time.sleep(3) # Wait 3 seconds.
- # The window is closed here, after the above context exits.
+import tcod._internal
+import tcod.constants
+import tcod.image
+from tcod._internal import _check, _path_encode
+from tcod.cffi import ffi, lib
+
+if TYPE_CHECKING:
+ from collections.abc import Iterable
+ from os import PathLike
+
+ from numpy.typing import ArrayLike, NDArray
+
+
+def _fmt(string: str) -> bytes:
+ """Return a string that escapes 'C printf' side effects."""
+ return string.encode("utf-8").replace(b"%", b"%%")
+
+
+_root_console = None
+
+rgba_graphic: np.dtype[Any] = np.dtype([("ch", np.intc), ("fg", "4B"), ("bg", "4B")])
+"""A NumPy :any:`dtype` compatible with :any:`Console.rgba`.
+
+This dtype is: ``np.dtype([("ch", np.intc), ("fg", "4B"), ("bg", "4B")])``
+
+.. versionadded:: 12.3
"""
-from __future__ import absolute_import
+rgb_graphic: np.dtype[Any] = np.dtype([("ch", np.intc), ("fg", "3B"), ("bg", "3B")])
+"""A NumPy :any:`dtype` compatible with :any:`Console.rgb`.
-import sys
+This dtype is: ``np.dtype([("ch", np.intc), ("fg", "3B"), ("bg", "3B")])``
-import warnings
+.. versionadded:: 12.3
+"""
-import numpy as np
-import tcod.libtcod
-from tcod.libtcod import ffi, lib
-import tcod._internal
+class Console:
+ """A console object containing a grid of characters with foreground/background colors.
-if sys.version_info[0] == 2: # Python 2
- def _fmt(string):
- if not isinstance(string, unicode):
- string = string.decode('latin-1')
- return string.replace(u'%', u'%%')
-else:
- def _fmt(string):
- """Return a string that escapes 'C printf' side effects."""
- return string.replace('%', '%%')
+ `width` and `height` are the size of the console (in tiles.)
-_root_console = None
+ `order` determines how the axes of NumPy array attributes are arranged.
+ With the default setting of `"C"` you use [y, x] to index a consoles
+ arrays such as :any:`Console.rgb`.
+ `order="F"` will swap the first two axes which allows for more intuitive
+ `[x, y]` indexing. To be consistent you may have to do this for every
+ NumPy array to create.
-class Console(object):
- """A console object containing a grid of characters with
- foreground/background colors.
+ With `buffer` the console can be initialized from another array. The
+ `buffer` should be compatible with the `width`, `height`, and `order`
+ given; and should also have a dtype compatible with :any:`Console.DTYPE`.
.. versionchanged:: 4.3
Added `order` parameter.
- Args:
- width (int): Width of the new Console.
- height (int): Height of the new Console.
- order (str): Which numpy memory order to use.
+ .. versionchanged:: 8.5
+ Added `buffer`, `copy`, and default parameters.
+ Arrays are initialized as if the :any:`clear` method was called.
+
+ .. versionchanged:: 10.0
+ `DTYPE` changed, `buffer` now requires colors with an alpha channel.
Attributes:
- console_c (CData): A cffi pointer to a TCOD_console_t object.
+ console_c: A python-cffi "TCOD_Console*" object.
+ DTYPE:
+ A class attribute which provides a dtype compatible with this
+ class.
+
+ ``[("ch", np.intc), ("fg", "(4,)u1"), ("bg", "(4,)u1")]``
+
+ Example::
+
+ >>> buffer = np.zeros(
+ ... shape=(20, 3),
+ ... dtype=tcod.console.Console.DTYPE,
+ ... order="F",
+ ... )
+ >>> buffer["ch"] = ord('_')
+ >>> buffer["ch"][:, 1] = ord('x')
+ >>> c = tcod.console.Console(20, 3, order="F", buffer=buffer)
+ >>> print(c)
+ <____________________
+ xxxxxxxxxxxxxxxxxxxx
+ ____________________>
+
+ .. versionadded:: 8.5
+
+ .. versionchanged:: 10.0
+ Added an alpha channel to the color types.
"""
- def __init__(self, width, height, order='C'):
- self._key_color = None
- self._ch = np.zeros((height, width), dtype=np.intc)
- self._fg = np.zeros((height, width), dtype='(3,)u1')
- self._bg = np.zeros((height, width), dtype='(3,)u1')
- self._order = tcod._internal.verify_order(order)
+ DTYPE = rgba_graphic
+
+ # A structured array type with the added "fg_rgb" and "bg_rgb" fields.
+ _DTYPE_RGB: np.dtype[Any] = np.dtype(
+ {
+ "names": ["ch", "fg", "bg"],
+ "formats": [np.int32, "3u1", "3u1"],
+ "offsets": [0, 4, 8],
+ "itemsize": 12,
+ }
+ )
+
+ def __init__(
+ self,
+ width: int,
+ height: int,
+ order: Literal["C", "F"] = "C",
+ buffer: NDArray[Any] | None = None,
+ ) -> None:
+ """Initialize the console."""
+ self._key_color: tuple[int, int, int] | None = None
+ self._order: Literal["C", "F"] = tcod._internal.verify_order(order)
+ if buffer is not None:
+ if self._order == "F":
+ buffer = buffer.transpose()
+ self._tiles: NDArray[Any] = np.ascontiguousarray(buffer, self.DTYPE)
+ else:
+ self._tiles = np.ndarray((height, width), dtype=self.DTYPE)
# libtcod uses the root console for defaults.
- bkgnd_flag = alignment = 0
+ default_bg_blend = 0
+ default_alignment = 0
if lib.TCOD_ctx.root != ffi.NULL:
- bkgnd_flag = lib.TCOD_ctx.root.bkgnd_flag
- alignment = lib.TCOD_ctx.root.alignment
+ default_bg_blend = lib.TCOD_ctx.root.bkgnd_flag
+ default_alignment = lib.TCOD_ctx.root.alignment
self._console_data = self.console_c = ffi.new(
- 'struct TCOD_Console*',
+ "struct TCOD_Console*",
{
- 'w': width, 'h': height,
- 'ch_array': ffi.cast('int*', self._ch.ctypes.data),
- 'fg_array': ffi.cast('TCOD_color_t*', self._fg.ctypes.data),
- 'bg_array': ffi.cast('TCOD_color_t*', self._bg.ctypes.data),
- 'bkgnd_flag': bkgnd_flag,
- 'alignment': alignment,
- 'fore': (255, 255, 255),
- 'back': (0, 0, 0),
+ "w": width,
+ "h": height,
+ "elements": width * height,
+ "tiles": ffi.from_buffer("struct TCOD_ConsoleTile*", self._tiles),
+ "bkgnd_flag": default_bg_blend,
+ "alignment": default_alignment,
+ "fore": (255, 255, 255),
+ "back": (0, 0, 0),
},
)
+ if buffer is None:
+ self.clear()
+
@classmethod
- def _from_cdata(cls, cdata, order='C'):
+ def _from_cdata(cls, cdata: Any, order: Literal["C", "F"] = "C") -> Console: # noqa: ANN401
+ """Return a Console instance which wraps this `TCOD_Console*` object."""
if isinstance(cdata, cls):
return cdata
- self = object.__new__(cls)
+ self: Console = object.__new__(cls)
self.console_c = cdata
self._init_setup_console_data(order)
return self
@classmethod
- def _get_root(cls, order=None):
+ def _get_root(cls, order: Literal["C", "F"] | None = None) -> Console:
"""Return a root console singleton with valid buffers.
This function will also update an already active root console.
"""
- global _root_console
+ global _root_console # noqa: PLW0603
if _root_console is None:
_root_console = object.__new__(cls)
- self = _root_console
+ self: Console = _root_console
if order is not None:
self._order = order
self.console_c = ffi.NULL
self._init_setup_console_data(self._order)
return self
- def _init_setup_console_data(self, order='C'):
+ def _init_setup_console_data(self, order: Literal["C", "F"] = "C") -> None:
"""Setup numpy arrays over libtcod data buffers."""
- global _root_console
+ global _root_console # noqa: PLW0603
self._key_color = None
if self.console_c == ffi.NULL:
_root_console = self
self._console_data = lib.TCOD_ctx.root
else:
- self._console_data = ffi.cast('struct TCOD_Console*', self.console_c)
+ self._console_data = ffi.cast("struct TCOD_Console*", self.console_c)
- def unpack_color(color_data):
- """return a (height, width, 3) shaped array from an image struct"""
- color_buffer = ffi.buffer(color_data[0:self.width * self.height])
- array = np.frombuffer(color_buffer, np.uint8)
- return array.reshape((self.height, self.width, 3))
-
- self._fg = unpack_color(self._console_data.fg_array)
- self._bg = unpack_color(self._console_data.bg_array)
-
- buf = self._console_data.ch_array
- buf = ffi.buffer(buf[0:self.width * self.height])
- self._ch = np.frombuffer(buf, np.intc).reshape((self.height,
- self.width))
+ self._tiles = np.frombuffer(
+ ffi.buffer(self._console_data.tiles[0 : self.width * self.height]),
+ dtype=self.DTYPE,
+ ).reshape((self.height, self.width))
self._order = tcod._internal.verify_order(order)
@property
- def width(self):
- """int: The width of this Console. (read-only)"""
- return lib.TCOD_console_get_width(self.console_c)
+ def width(self) -> int:
+ """The width of this Console."""
+ return int(lib.TCOD_console_get_width(self.console_c))
@property
- def height(self):
- """int: The height of this Console. (read-only)"""
- return lib.TCOD_console_get_height(self.console_c)
+ def height(self) -> int:
+ """The height of this Console."""
+ return int(lib.TCOD_console_get_height(self.console_c))
@property
- def bg(self):
+ def bg(self) -> NDArray[np.uint8]:
"""A uint8 array with the shape (height, width, 3).
You can change the consoles background colors by using this array.
- Index this array with ``console.bg[i, j, channel] # order='C'`` or
- ``console.bg[x, y, channel] # order='F'``.
+ Index this array with ``console.bg[i, j, channel] # order='C'`` or
+ ``console.bg[x, y, channel] # order='F'``.
"""
- return self._bg.transpose(1, 0, 2) if self._order == 'F' else self._bg
+ bg: np.ndarray[Any, np.dtype[np.uint8]] = self._tiles["bg"][..., :3]
+ if self._order == "F":
+ bg = bg.transpose(1, 0, 2)
+ return bg
@property
- def fg(self):
+ def fg(self) -> NDArray[np.uint8]:
"""A uint8 array with the shape (height, width, 3).
You can change the consoles foreground colors by using this array.
- Index this array with ``console.fg[i, j, channel] # order='C'`` or
- ``console.fg[x, y, channel] # order='F'``.
+ Index this array with ``console.fg[i, j, channel] # order='C'`` or
+ ``console.fg[x, y, channel] # order='F'``.
"""
- return self._fg.transpose(1, 0, 2) if self._order == 'F' else self._fg
+ fg: np.ndarray[Any, np.dtype[np.uint8]] = self._tiles["fg"][..., :3]
+ if self._order == "F":
+ fg = fg.transpose(1, 0, 2)
+ return fg
@property
- def ch(self):
+ def ch(self) -> NDArray[np.intc]:
"""An integer array with the shape (height, width).
You can change the consoles character codes by using this array.
- Index this array with ``console.ch[i, j] # order='C'`` or
- ``console.ch[x, y] # order='F'``.
+ Index this array with ``console.ch[i, j] # order='C'`` or
+ ``console.ch[x, y] # order='F'``.
+ """
+ return self._tiles["ch"].T if self._order == "F" else self._tiles["ch"]
+
+ @property
+ @deprecated("This attribute has been renamed to `rgba`.", category=FutureWarning)
+ def tiles(self) -> NDArray[Any]:
+ """An array of this consoles raw tile data.
+
+ This acts as a combination of the `ch`, `fg`, and `bg` attributes.
+ Colors include an alpha channel but how alpha works is currently
+ undefined.
+
+ .. versionadded:: 10.0
+
+ .. deprecated:: 12.3
+ Use :any:`Console.rgba` instead.
+ """
+ return self.rgba
+
+ @property
+ @deprecated("This attribute has been renamed to `rgba`.", category=FutureWarning)
+ def buffer(self) -> NDArray[Any]:
+ """An array of this consoles raw tile data.
+
+ .. versionadded:: 11.4
+
+ .. deprecated:: 11.8
+ Use :any:`Console.rgba` instead.
+ """
+ return self.rgba
+
+ @property
+ @deprecated("This attribute has been renamed to `rgb`.", category=FutureWarning)
+ def tiles_rgb(self) -> NDArray[Any]:
+ """An array of this consoles data without the alpha channel.
+
+ .. versionadded:: 11.8
+
+ .. deprecated:: 12.3
+ Use :any:`Console.rgb` instead.
+ """
+ return self.rgb
+
+ @property
+ @deprecated("This attribute has been renamed to `rgb`.", category=FutureWarning)
+ def tiles2(self) -> NDArray[Any]:
+ """This name is deprecated in favour of :any:`rgb`.
+
+ .. versionadded:: 11.3
+
+ .. deprecated:: 11.8
+ Use :any:`Console.rgb` instead.
+ """
+ return self.rgb
+
+ @property
+ def rgba(self) -> NDArray[Any]:
+ """An array of this consoles raw tile data.
+
+ The axes of this array is affected by the `order` parameter given to
+ initialize the console.
+
+ Example:
+ >>> con = tcod.console.Console(10, 2)
+ >>> WHITE, BLACK = (255, 255, 255), (0, 0, 0)
+ >>> con.rgba[0, 0] = (
+ ... ord("X"),
+ ... (*WHITE, 255),
+ ... (*BLACK, 255),
+ ... )
+ >>> print(f"{con.rgba[0, 0]=}")
+ con.rgba[0, 0]=...(88, [255, 255, 255, 255], [ 0, 0, 0, 255])...
+
+ .. versionadded:: 12.3
"""
- return self._ch.T if self._order == 'F' else self._ch
+ return self._tiles.T if self._order == "F" else self._tiles
@property
- def default_bg(self):
- """Tuple[int, int, int]: The default background color."""
+ def rgb(self) -> NDArray[Any]:
+ """An array of this consoles data without the alpha channel.
+
+ The axes of this array is affected by the `order` parameter given to
+ initialize the console.
+
+ The :any:`rgb_graphic` dtype can be used to make arrays compatible
+ with this attribute that are independent of a :any:`Console`.
+
+ Example:
+ >>> tile_graphics = np.array( # Tile graphics lookup table
+ ... [ # (Unicode, foreground, background)
+ ... (ord("."), (255, 255, 255), (0, 0, 0)), # Tile 0
+ ... (ord("#"), (255, 255, 255), (0, 0, 0)), # Tile 1
+ ... (ord("^"), (255, 255, 255), (0, 0, 0)), # Tile 2
+ ... (ord("~"), (255, 255, 255), (0, 0, 0)), # Tile 3
+ ... ],
+ ... dtype=tcod.console.rgb_graphic,
+ ... )
+ >>> console = tcod.console.Console(6, 5)
+ >>> console.rgb[:] = tile_graphics[ # Convert 2D array of indexes to tile graphics
+ ... [
+ ... [1, 1, 1, 1, 1, 1],
+ ... [1, 0, 2, 0, 0, 1],
+ ... [1, 0, 0, 3, 3, 1],
+ ... [1, 0, 0, 3, 3, 1],
+ ... [1, 1, 1, 1, 1, 1],
+ ... ],
+ ... ]
+ >>> print(console)
+ <######
+ #.^..#
+ #..~~#
+ #..~~#
+ ######>
+
+ Example:
+ >>> con = tcod.console.Console(10, 2)
+ >>> BLUE, YELLOW, BLACK = (0, 0, 255), (255, 255, 0), (0, 0, 0)
+ >>> con.rgb[0, 0] = ord("@"), YELLOW, BLACK
+ >>> print(f"{con.rgb[0, 0]=}")
+ con.rgb[0, 0]=...(64, [255, 255, 0], [0, 0, 0])...
+ >>> con.rgb["bg"] = BLUE
+ >>> print(f"{con.rgb[0, 0]=}")
+ con.rgb[0, 0]=...(64, [255, 255, 0], [ 0, 0, 255])...
+
+ .. versionadded:: 12.3
+ """
+ return self.rgba.view(self._DTYPE_RGB)
+
+ _DEPRECATE_CONSOLE_DEFAULTS_MSG = """Console defaults have been deprecated.
+Consider one of the following:
+
+ # Set parameters once then pass them as kwargs
+ DEFAULT_COLOR = {"bg": (0, 0, 127), "fg": (127, 127, 255)}
+ console.print(x, y, string, **DEFAULT_COLOR)
+
+ # Clear the console to a color and then skip setting colors on printing/drawing
+ console.clear(fg=(127, 127, 255), bg=(0, 0, 127))
+ console.print(x, y, string, fg=None)
+"""
+
+ @property # Getters used internally, so only deprecate the setters.
+ def default_bg(self) -> tuple[int, int, int]:
+ """Tuple[int, int, int]: The default background color.
+
+ .. deprecated:: 8.5
+ These should not be used. Prefer passing defaults as kwargs.
+
+ .. code-block::
+
+ DEFAULT_COLOR = {"bg": (0, 0, 127), "fg": (127, 127, 255)}
+ console.print(x, y, string, **DEFAULT_COLOR)
+ """
color = self._console_data.back
return color.r, color.g, color.b
+
@default_bg.setter
- def default_bg(self, color):
+ @deprecated(_DEPRECATE_CONSOLE_DEFAULTS_MSG, category=FutureWarning)
+ def default_bg(self, color: tuple[int, int, int]) -> None:
self._console_data.back = color
@property
- def default_fg(self):
- """Tuple[int, int, int]: The default foreground color."""
+ def default_fg(self) -> tuple[int, int, int]:
+ """Tuple[int, int, int]: The default foreground color.
+
+ .. deprecated:: 8.5
+ These should not be used. Prefer passing defaults as kwargs.
+ """
color = self._console_data.fore
return color.r, color.g, color.b
+
@default_fg.setter
- def default_fg(self, color):
+ @deprecated(_DEPRECATE_CONSOLE_DEFAULTS_MSG, category=FutureWarning)
+ def default_fg(self, color: tuple[int, int, int]) -> None:
self._console_data.fore = color
@property
- def default_bg_blend(self):
- """int: The default blending mode."""
- return self._console_data.bkgnd_flag
+ def default_bg_blend(self) -> int:
+ """int: The default blending mode.
+
+ .. deprecated:: 8.5
+ These should not be used. Prefer passing defaults as kwargs.
+ """
+ return int(self._console_data.bkgnd_flag)
+
@default_bg_blend.setter
- def default_bg_blend(self, value):
+ @deprecated(_DEPRECATE_CONSOLE_DEFAULTS_MSG, category=FutureWarning)
+ def default_bg_blend(self, value: int) -> None:
self._console_data.bkgnd_flag = value
@property
- def default_alignment(self):
- """int: The default text alignment."""
- return self._console_data.alignment
+ def default_alignment(self) -> int:
+ """int: The default text alignment.
+
+ .. deprecated:: 8.5
+ These should not be used. Prefer passing defaults as kwargs.
+ """
+ return int(self._console_data.alignment)
+
@default_alignment.setter
- def default_alignment(self, value):
+ @deprecated(_DEPRECATE_CONSOLE_DEFAULTS_MSG, category=FutureWarning)
+ def default_alignment(self, value: int) -> None:
self._console_data.alignment = value
- def clear(self):
- """Reset this console to its default colors and the space character.
- """
- lib.TCOD_console_clear(self.console_c)
+ def __clear_warning(self, name: str, value: tuple[int, int, int]) -> None:
+ """Raise a warning for bad default values during calls to clear."""
+ warnings.warn(
+ f"Clearing with the console default values is deprecated.\nAdd {name}={value!r} to this call.",
+ FutureWarning,
+ stacklevel=3,
+ )
+
+ def clear(
+ self,
+ ch: int = 0x20,
+ fg: tuple[int, int, int] = ..., # type: ignore[assignment]
+ bg: tuple[int, int, int] = ..., # type: ignore[assignment]
+ ) -> None:
+ """Reset all values in this console to a single value.
+
+ `ch` is the character to clear the console with. Defaults to the space
+ character.
- def put_char(self, x, y, ch, bg_blend=tcod.libtcod.BKGND_DEFAULT):
+ `fg` and `bg` are the colors to clear the console with. Defaults to
+ white-on-black if the console defaults are untouched.
+
+ .. note::
+ If `fg`/`bg` are not set, they will default to
+ :any:`default_fg`/:any:`default_bg`.
+ However, default values other than white-on-back are deprecated.
+
+ .. versionchanged:: 8.5
+ Added the `ch`, `fg`, and `bg` parameters.
+ Non-white-on-black default values are deprecated.
+ """
+ if fg is ...: # type: ignore[comparison-overlap]
+ fg = self.default_fg
+ if fg != (255, 255, 255):
+ self.__clear_warning("fg", fg)
+ if bg is ...: # type: ignore[comparison-overlap]
+ bg = self.default_bg
+ if bg != (0, 0, 0):
+ self.__clear_warning("bg", bg)
+ self._tiles[...] = ch, (*fg, 255), (*bg, 255)
+
+ def put_char(
+ self,
+ x: int,
+ y: int,
+ ch: int,
+ bg_blend: int = tcod.constants.BKGND_DEFAULT,
+ ) -> None:
"""Draw the character c at x,y using the default colors and a blend mode.
Args:
@@ -235,24 +510,119 @@ def put_char(self, x, y, ch, bg_blend=tcod.libtcod.BKGND_DEFAULT):
"""
lib.TCOD_console_put_char(self.console_c, x, y, ch, bg_blend)
- def print_(self, x, y, string, bg_blend=tcod.libtcod.BKGND_DEFAULT,
- alignment=None):
+ __ALIGNMENT_LOOKUP = (
+ "tcod.LEFT",
+ "tcod.RIGHT",
+ "tcod.CENTER",
+ )
+
+ __BG_BLEND_LOOKUP = (
+ "tcod.BKGND_NONE",
+ "tcod.BKGND_SET",
+ "tcod.BKGND_MULTIPLY",
+ "tcod.BKGND_LIGHTEN",
+ "tcod.BKGND_DARKEN",
+ "tcod.BKGND_SCREEN",
+ "tcod.BKGND_COLOR_DODGE",
+ "tcod.BKGND_COLOR_BURN",
+ "tcod.BKGND_ADD",
+ "tcod.BKGND_ADDA",
+ "tcod.BKGND_BURN",
+ "tcod.BKGND_OVERLAY",
+ "tcod.BKGND_ALPH",
+ "tcod.BKGND_DEFAULT",
+ )
+
+ def __deprecate_defaults( # noqa: C901, PLR0912
+ self,
+ new_func: str,
+ bg_blend: Any, # noqa: ANN401
+ alignment: Any = ..., # noqa: ANN401
+ clear: Any = ..., # noqa: ANN401
+ ) -> None:
+ """Return the parameters needed to recreate the current default state."""
+ if not __debug__:
+ return
+
+ fg: tuple[int, int, int] | None = self.default_fg
+ bg: tuple[int, int, int] | None = self.default_bg
+ if bg_blend == tcod.constants.BKGND_NONE:
+ bg = None
+ bg_blend = self.default_bg_blend if bg_blend == tcod.constants.BKGND_DEFAULT else None
+ if bg_blend == tcod.constants.BKGND_NONE:
+ bg = None
+ bg_blend = None
+ if bg_blend == tcod.constants.BKGND_SET:
+ bg_blend = None
+ if alignment is None:
+ alignment = self.default_alignment
+ if alignment == tcod.constants.LEFT:
+ alignment = None
+ else:
+ alignment = None
+ if clear is not ...:
+ fg = None
+ params = []
+ if clear is True:
+ params.append('ch=ord(" ")')
+ if clear is False:
+ params.append("ch=0")
+ if fg is not None:
+ params.append(f"fg={fg}")
+ if bg is not None:
+ params.append(f"bg={bg}")
+ if bg_blend is not None:
+ params.append(f"bg_blend={self.__BG_BLEND_LOOKUP[bg_blend]}")
+ if alignment is not None:
+ params.append(f"alignment={self.__ALIGNMENT_LOOKUP[alignment]}")
+ param_str = ", ".join(params)
+ param_str = "." if not param_str else f" and add the following parameters:\n{param_str}"
+ warnings.warn(
+ "Console functions using default values have been deprecated.\n"
+ f"Replace this method with `Console.{new_func}`{param_str}",
+ FutureWarning,
+ stacklevel=3,
+ )
+
+ @deprecated("Switch to using keywords and then replace with 'console.print(...)'")
+ def print_(
+ self,
+ x: int,
+ y: int,
+ string: str,
+ bg_blend: int = tcod.constants.BKGND_DEFAULT,
+ alignment: int | None = None,
+ ) -> None:
"""Print a color formatted string on a console.
Args:
x (int): The x coordinate from the left.
y (int): The y coordinate from the top.
- string (Text): A Unicode string optionaly using color codes.
+ string (str): A Unicode string optionally using color codes.
bg_blend (int): Blending mode to use, defaults to BKGND_DEFAULT.
- alignment (Optinal[int]): Text alignment.
+ alignment (Optional[int]): Text alignment.
+
+ .. deprecated:: 8.5
+ Console methods which depend on console defaults have been
+ deprecated.
+ Use :any:`Console.print` instead, calling this function will print
+ a warning detailing which default values need to be made explicit.
"""
+ self.__deprecate_defaults("print", bg_blend, alignment)
alignment = self.default_alignment if alignment is None else alignment
-
- lib.TCOD_console_print_ex_utf(self.console_c, x, y,
- bg_blend, alignment, _fmt(string))
-
- def print_rect(self, x, y, width, height, string,
- bg_blend=tcod.libtcod.BKGND_DEFAULT, alignment=None):
+ lib.TCOD_console_printf_ex(self.console_c, x, y, bg_blend, alignment, _fmt(string))
+
+ @deprecated("Switch to using keywords and then replace with 'console.print(...)'")
+ def print_rect( # noqa: PLR0913
+ self,
+ x: int,
+ y: int,
+ width: int,
+ height: int,
+ string: str,
+ bg_blend: int = tcod.constants.BKGND_DEFAULT,
+ alignment: int | None = None,
+ ) -> int:
"""Print a string constrained to a rectangle.
If h > 0 and the bottom of the rectangle is reached,
@@ -264,18 +634,36 @@ def print_rect(self, x, y, width, height, string,
y (int): The y coordinate from the top.
width (int): Maximum width to render the text.
height (int): Maximum lines to render the text.
- string (Text): A Unicode string.
+ string (str): A Unicode string.
bg_blend (int): Background blending flag.
alignment (Optional[int]): Alignment flag.
Returns:
int: The number of lines of text once word-wrapped.
+
+ .. deprecated:: 8.5
+ Console methods which depend on console defaults have been
+ deprecated.
+ Use :any:`Console.print_box` instead, calling this function will
+ print a warning detailing which default values need to be made
+ explicit.
"""
+ self.__deprecate_defaults("print_box", bg_blend, alignment)
alignment = self.default_alignment if alignment is None else alignment
- return lib.TCOD_console_print_rect_ex_utf(self.console_c,
- x, y, width, height, bg_blend, alignment, _fmt(string))
+ return int(
+ lib.TCOD_console_printf_rect_ex(
+ self.console_c,
+ x,
+ y,
+ width,
+ height,
+ bg_blend,
+ alignment,
+ _fmt(string),
+ )
+ )
- def get_height_rect(self, x, y, width, height, string):
+ def get_height_rect(self, x: int, y: int, width: int, height: int, string: str) -> int:
"""Return the height of this text word-wrapped into this rectangle.
Args:
@@ -283,19 +671,27 @@ def get_height_rect(self, x, y, width, height, string):
y (int): The y coordinate from the top.
width (int): Maximum width to render the text.
height (int): Maximum lines to render the text.
- string (Text): A Unicode string.
+ string (str): A Unicode string.
Returns:
int: The number of lines of text once word-wrapped.
"""
- return lib.TCOD_console_get_height_rect_utf(
- self.console_c, x, y, width, height, _fmt(string))
-
- def rect(self, x, y, width, height, clear,
- bg_blend=tcod.libtcod.BKGND_DEFAULT):
+ string_ = string.encode("utf-8")
+ return int(lib.TCOD_console_get_height_rect_n(self.console_c, x, y, width, height, len(string_), string_))
+
+ @deprecated("""Replace with 'console.draw_rect(x, y, width, height, ch=..., fg=..., bg=..., bg_blend=...)'""")
+ def rect( # noqa: PLR0913
+ self,
+ x: int,
+ y: int,
+ width: int,
+ height: int,
+ clear: bool, # noqa: FBT001
+ bg_blend: int = tcod.constants.BKGND_DEFAULT,
+ ) -> None:
"""Draw a the background color on a rect optionally clearing the text.
- If clr is True the affected tiles are changed to space character.
+ If `clear` is True the affected tiles are changed to space character.
Args:
x (int): The x coordinate from the left.
@@ -305,70 +701,139 @@ def rect(self, x, y, width, height, clear,
clear (bool): If True all text in the affected area will be
removed.
bg_blend (int): Background blending flag.
- """
- lib.TCOD_console_rect(self.console_c, x, y, width, height, clear,
- bg_blend)
- def hline(self, x, y, width, bg_blend=tcod.libtcod.BKGND_DEFAULT):
+ .. deprecated:: 8.5
+ Console methods which depend on console defaults have been
+ deprecated.
+ Use :any:`Console.draw_rect` instead, calling this function will
+ print a warning detailing which default values need to be made
+ explicit.
+ """
+ self.__deprecate_defaults("draw_rect", bg_blend, clear=bool(clear))
+ lib.TCOD_console_rect(self.console_c, x, y, width, height, clear, bg_blend)
+
+ @deprecated(
+ """Replace with 'console.draw_rect(x, y, width=width, height=1, ch=ord("─"), fg=..., bg=..., bg_blend=...)'"""
+ )
+ def hline(
+ self,
+ x: int,
+ y: int,
+ width: int,
+ bg_blend: int = tcod.constants.BKGND_DEFAULT,
+ ) -> None:
"""Draw a horizontal line on the console.
- This always uses the character 196, the horizontal line character.
+ This always uses ord("─"), the horizontal line character.
Args:
x (int): The x coordinate from the left.
y (int): The y coordinate from the top.
- width (int): The horozontal length of this line.
+ width (int): The horizontal length of this line.
bg_blend (int): The background blending flag.
+
+ .. deprecated:: 8.5
+ Console methods which depend on console defaults have been
+ deprecated.
+ Use :any:`Console.draw_rect` instead, calling this function will
+ print a warning detailing which default values need to be made
+ explicit.
"""
+ self.__deprecate_defaults("draw_rect", bg_blend)
lib.TCOD_console_hline(self.console_c, x, y, width, bg_blend)
- def vline(self, x, y, height, bg_blend=tcod.libtcod.BKGND_DEFAULT):
+ @deprecated(
+ """Replace with 'console.draw_rect(x, y, width=1, height=height, ch=ord("│"), fg=..., bg=..., bg_blend=...)'"""
+ )
+ def vline(
+ self,
+ x: int,
+ y: int,
+ height: int,
+ bg_blend: int = tcod.constants.BKGND_DEFAULT,
+ ) -> None:
"""Draw a vertical line on the console.
- This always uses the character 179, the vertical line character.
+ This always uses ord("│"), the vertical line character.
Args:
x (int): The x coordinate from the left.
y (int): The y coordinate from the top.
- height (int): The horozontal length of this line.
+ height (int): The horizontal length of this line.
bg_blend (int): The background blending flag.
+
+ .. deprecated:: 8.5
+ Console methods which depend on console defaults have been
+ deprecated.
+ Use :any:`Console.draw_rect` instead, calling this function will
+ print a warning detailing which default values need to be made
+ explicit.
"""
+ self.__deprecate_defaults("draw_rect", bg_blend)
lib.TCOD_console_vline(self.console_c, x, y, height, bg_blend)
- def print_frame(self, x, y, width, height, string='',
- clear=True, bg_blend=tcod.libtcod.BKGND_DEFAULT):
- """Draw a framed rectangle with optinal text.
+ @deprecated("Replace with 'console.draw_frame(...)', use a separate print call for frame titles")
+ def print_frame( # noqa: PLR0913
+ self,
+ x: int,
+ y: int,
+ width: int,
+ height: int,
+ string: str = "",
+ clear: bool = True, # noqa: FBT001, FBT002
+ bg_blend: int = tcod.constants.BKGND_DEFAULT,
+ ) -> None:
+ """Draw a framed rectangle with optional text.
This uses the default background color and blend mode to fill the
rectangle and the default foreground to draw the outline.
- string will be printed on the inside of the rectangle, word-wrapped.
+ `string` will be printed on the inside of the rectangle, word-wrapped.
+ If `string` is empty then no title will be drawn.
Args:
x (int): The x coordinate from the left.
y (int): The y coordinate from the top.
width (int): The width if the frame.
height (int): The height of the frame.
- string (Text): A Unicode string to print.
+ string (str): A Unicode string to print.
clear (bool): If True all text in the affected area will be
removed.
bg_blend (int): The background blending flag.
- Note:
- This method does not support Unicode outside of the 0-255 range.
- """
- lib.TCOD_console_print_frame(self.console_c, x, y, width, height,
- clear, bg_blend, string.encode('latin-1'))
+ .. versionchanged:: 8.2
+ Now supports Unicode strings.
- def blit(self, dest, dest_x=0, dest_y=0,
- src_x=0, src_y=0, width=0, height=0,
- fg_alpha=1.0, bg_alpha=1.0, key_color=None):
+ .. deprecated:: 8.5
+ Console methods which depend on console defaults have been
+ deprecated.
+ Use :any:`Console.draw_frame` instead, calling this function will
+ print a warning detailing which default values need to be made
+ explicit.
+ """
+ self.__deprecate_defaults("draw_frame", bg_blend)
+ string_: Any = _fmt(string) if string else ffi.NULL
+ _check(lib.TCOD_console_printf_frame(self.console_c, x, y, width, height, clear, bg_blend, string_))
+
+ def blit( # noqa: PLR0913
+ self,
+ dest: Console,
+ dest_x: int = 0,
+ dest_y: int = 0,
+ src_x: int = 0,
+ src_y: int = 0,
+ width: int = 0,
+ height: int = 0,
+ fg_alpha: float = 1.0,
+ bg_alpha: float = 1.0,
+ key_color: tuple[int, int, int] | None = None,
+ ) -> None:
"""Blit from this console onto the ``dest`` console.
Args:
- dest (Console): The destintaion console to blit onto.
- dest_x (int): Leftmost coordinate of the destintaion console.
- dest_y (int): Topmost coordinate of the destintaion console.
+ dest (Console): The destination console to blit onto.
+ dest_x (int): Leftmost coordinate of the destination console.
+ dest_y (int): Topmost coordinate of the destination console.
src_x (int): X coordinate from this console to blit, from the left.
src_y (int): Y coordinate from this console to blit, from the top.
width (int): The width of the region to blit.
@@ -377,102 +842,810 @@ def blit(self, dest, dest_x=0, dest_y=0,
height (int): The height of the region to blit.
If this is 0 the maximum possible height will be used.
- fg_alpha (float): Foreground color alpha vaule.
- bg_alpha (float): Background color alpha vaule.
+ fg_alpha (float): Foreground color alpha value.
+ bg_alpha (float): Background color alpha value.
key_color (Optional[Tuple[int, int, int]]):
None, or a (red, green, blue) tuple with values of 0-255.
.. versionchanged:: 4.0
- Parameters were rearraged and made optional.
+ Parameters were rearranged and made optional.
Previously they were:
`(x, y, width, height, dest, dest_x, dest_y, *)`
+
+ .. versionchanged:: 11.6
+ Now supports per-cell alpha transparency.
+
+ Use :any:`Console.buffer` to set tile alpha before blit.
"""
# The old syntax is easy to detect and correct.
- if hasattr(src_y, 'console_c'):
- src_x, src_y, width, height, dest, dest_x, dest_y = \
- dest, dest_x, dest_y, src_x, src_y, width, height
+ if hasattr(src_y, "console_c"):
+ (src_x, src_y, width, height, dest, dest_x, dest_y) = (
+ dest, # type: ignore[assignment]
+ dest_x,
+ dest_y,
+ src_x,
+ src_y, # type: ignore[assignment]
+ width,
+ height,
+ )
warnings.warn(
"Parameter names have been moved around, see documentation.",
DeprecationWarning,
stacklevel=2,
- )
+ )
- if key_color or self._key_color:
- key_color = ffi.new('TCOD_color_t*', key_color)
+ key_color = key_color or self._key_color
+ if key_color:
+ key_color = ffi.new("TCOD_color_t*", key_color)
lib.TCOD_console_blit_key_color(
- self.console_c, src_x, src_y, width, height,
- dest.console_c, dest_x, dest_y, fg_alpha, bg_alpha, key_color
+ self.console_c,
+ src_x,
+ src_y,
+ width,
+ height,
+ dest.console_c,
+ dest_x,
+ dest_y,
+ fg_alpha,
+ bg_alpha,
+ key_color,
)
else:
lib.TCOD_console_blit(
- self.console_c, src_x, src_y, width, height,
- dest.console_c, dest_x, dest_y, fg_alpha, bg_alpha
+ self.console_c,
+ src_x,
+ src_y,
+ width,
+ height,
+ dest.console_c,
+ dest_x,
+ dest_y,
+ fg_alpha,
+ bg_alpha,
)
- def set_key_color(self, color):
+ @deprecated("Pass the key color to Console.blit instead of calling this function.")
+ def set_key_color(self, color: tuple[int, int, int] | None) -> None:
"""Set a consoles blit transparent color.
- Args:
- color (Tuple[int, int, int]):
+ `color` is the (r, g, b) color, or None to disable key color.
+
+ .. deprecated:: 8.5
+ Pass the key color to :any:`Console.blit` instead of calling this
+ function.
"""
self._key_color = color
- def __enter__(self):
- """Returns this console in a managed context.
+ def __enter__(self) -> Self:
+ """Return this console in a managed context.
When the root console is used as a context, the graphical window will
- close once the context is left as if :any:`tcod.console_delete` was
+ close once the context is left as if :any:`libtcodpy.console_delete` was
called on it.
This is useful for some Python IDE's like IDLE, where the window would
not be closed on its own otherwise.
+
+ .. seealso::
+ :any:`libtcodpy.console_init_root`
"""
if self.console_c != ffi.NULL:
- raise NotImplementedError('Only the root console has a context.')
+ msg = "Only the root console has a context."
+ raise NotImplementedError(msg)
return self
- def __exit__(self, *args):
- """Closes the graphical window on exit.
+ def close(self) -> None:
+ """Close the active window managed by libtcod.
- Some tcod functions may have undefined behavior after this point.
+ This must only be called on the root console, which is returned from
+ :any:`libtcodpy.console_init_root`.
+
+ .. versionadded:: 11.11
"""
+ if self.console_c != ffi.NULL:
+ msg = "Only the root console can be used to close libtcod's window."
+ raise NotImplementedError(msg)
lib.TCOD_console_delete(self.console_c)
- def __bool__(self):
- """Returns False if this is the root console.
+ def __exit__(self, *_: object) -> None:
+ """Close the graphical window on exit.
- This mimics libtcodpy behavior.
+ Some tcod functions may have undefined behavior after this point.
"""
- return self.console_c != ffi.NULL
+ self.close()
- __nonzero__ = __bool__
+ def __bool__(self) -> bool:
+ """Return False if this is the root console.
- def __getstate__(self):
+ This mimics libtcodpy behavior.
+ """
+ return bool(self.console_c != ffi.NULL)
+
+ def __getstate__(self) -> dict[str, Any]:
+ """Support serialization via :mod:`pickle`."""
state = self.__dict__.copy()
- del state['console_c']
- state['_console_data'] = {
- 'w': self.width, 'h': self.height,
- 'bkgnd_flag': self.default_bg_blend,
- 'alignment': self.default_alignment,
- 'fore': self.default_fg,
- 'back': self.default_bg,
+ del state["console_c"]
+ state["_console_data"] = {
+ "w": self.width,
+ "h": self.height,
+ "bkgnd_flag": self.default_bg_blend,
+ "alignment": self.default_alignment,
+ "fore": self.default_fg,
+ "back": self.default_bg,
}
if self.console_c == ffi.NULL:
- state['_ch'] = np.copy(self._ch)
- state['_fg'] = np.copy(self._fg)
- state['_bg'] = np.copy(self._bg)
+ state["_tiles"] = np.array(self._tiles, copy=True)
return state
- def __setstate__(self, state):
+ def __setstate__(self, state: dict[str, Any]) -> None:
+ """Support serialization via :mod:`pickle`."""
self._key_color = None
+ if "_tiles" not in state:
+ tiles: NDArray[Any] = np.ndarray((self.height, self.width), dtype=self.DTYPE)
+ tiles["ch"] = state["_ch"]
+ tiles["fg"][..., :3] = state["_fg"]
+ tiles["fg"][..., 3] = 255
+ tiles["bg"][..., :3] = state["_bg"]
+ tiles["bg"][..., 3] = 255
+ state["_tiles"] = tiles
+ del state["_ch"]
+ del state["_fg"]
+ del state["_bg"]
+
self.__dict__.update(state)
- self._console_data.update(
- {
- 'ch_array': ffi.cast('int*', self._ch.ctypes.data),
- 'fg_array': ffi.cast('TCOD_color_t*', self._fg.ctypes.data),
- 'bg_array': ffi.cast('TCOD_color_t*', self._bg.ctypes.data),
- }
+ self._console_data["tiles"] = ffi.from_buffer("struct TCOD_ConsoleTile*", self._tiles)
+ self._console_data = self.console_c = ffi.new("struct TCOD_Console*", self._console_data)
+
+ def __repr__(self) -> str:
+ """Return a string representation of this console."""
+ return f"tcod.console.Console(width={self.width}, height={self.height}, order={self._order!r},buffer=\n{self.rgba!r})"
+
+ def __str__(self) -> str:
+ """Return a simplified representation of this consoles contents."""
+ return "<{}>".format("\n ".join("".join(chr(c) for c in line) for line in self._tiles["ch"]))
+
+ @overload
+ def print(
+ self,
+ x: int,
+ y: int,
+ text: str,
+ *,
+ width: int | None = None,
+ height: int | None = None,
+ fg: tuple[int, int, int] | None = None,
+ bg: tuple[int, int, int] | None = None,
+ bg_blend: int = tcod.constants.BKGND_SET,
+ alignment: int = tcod.constants.LEFT,
+ ) -> int: ...
+
+ @overload
+ @deprecated(
+ "Replace text, fg, bg, bg_blend, and alignment with keyword arguments."
+ "\n'string' keyword should be renamed to `text`"
+ )
+ def print(
+ self,
+ x: int,
+ y: int,
+ text: str,
+ fg: tuple[int, int, int] | None = None,
+ bg: tuple[int, int, int] | None = None,
+ bg_blend: int = tcod.constants.BKGND_SET,
+ alignment: int = tcod.constants.LEFT,
+ *,
+ string: str = "",
+ ) -> int: ...
+
+ @overload
+ @deprecated(
+ "Replace text, fg, bg, bg_blend, and alignment with keyword arguments."
+ "\n'string' keyword should be renamed to `text`"
+ )
+ def print(
+ self,
+ x: int,
+ y: int,
+ text: str = "",
+ fg: tuple[int, int, int] | None = None,
+ bg: tuple[int, int, int] | None = None,
+ bg_blend: int = tcod.constants.BKGND_SET,
+ alignment: int = tcod.constants.LEFT,
+ *,
+ string: str,
+ ) -> int: ...
+
+ def print( # noqa: PLR0913
+ self,
+ x: int,
+ y: int,
+ text: str = "",
+ fg: tuple[int, int, int] | None = None,
+ bg: tuple[int, int, int] | None = None,
+ bg_blend: int = tcod.constants.BKGND_SET,
+ alignment: int = tcod.constants.LEFT,
+ *,
+ width: int | None = None,
+ height: int | None = None,
+ string: str = "",
+ ) -> int:
+ r"""Print a string of Unicode text on this console.
+
+ Prefer using keywords for this method call to avoid ambiguous parameters.
+
+ Args:
+ x: Starting X coordinate, with the left-most tile as zero.
+ y: Starting Y coordinate, with the top-most tile as zero.
+ text: A Unicode string which may include color control characters.
+ width: Width in tiles to constrain the printing region.
+ If a `width` is given then `text` will have automatic word wrapping applied to it.
+ A `width` of `None` means `text` will only have manual line breaks.
+ height: Height in tiles to constrain the printing region.
+ fg: RGB tuple to use as the foreground color, or `None` to leave the foreground unchanged.
+ Tuple values should be 0-255.
+ Must be given as a keyword argument.
+ bg: RGB tuple to use as the background color, or `None` to leave the foreground unchanged.
+ Tuple values should be 0-255.
+ Must be given as a keyword argument.
+ bg_blend: Background blend type used by libtcod.
+ Typically starts with `libtcodpy.BKGND_*`.
+ Must be given as a keyword argument.
+ alignment: One of `libtcodpy.LEFT`, `libtcodpy.CENTER`, or `libtcodpy.RIGHT`
+ Must be given as a keyword argument.
+ string: Older deprecated name of the `text` parameter.
+
+ Returns:
+ The height of `text` in lines via word wrapping and line breaks.
+
+ Example::
+
+ >>> from tcod import libtcodpy
+ >>> console = tcod.console.Console(20, 1)
+ >>> console.clear(ch=ord('·'))
+ >>> console.print(x=0, y=0, text="left")
+ 1
+ >>> console.print(x=console.width, y=0, text="right", alignment=libtcodpy.RIGHT)
+ 1
+ >>> console.print(x=10, y=0, text="center", alignment=libtcodpy.CENTER)
+ 1
+ >>> print(console)
+
+
+ >>> console = tcod.console.Console(20, 4)
+ >>> console.clear(ch=ord('·'))
+ >>> console.print(x=1, y=1, text="words within bounds", width=8)
+ 3
+ >>> print(console)
+ <····················
+ ·words··············
+ ·within·············
+ ·bounds·············>
+ >>> WHITE = (255, 255, 255)
+ >>> BLACK = (0, 0, 0)
+ >>> console.print(x=0, y=0, text="Black text on white background", fg=BLACK, bg=WHITE)
+ 1
+
+ .. versionadded:: 8.5
+
+ .. versionchanged:: 9.0
+
+ `fg` and `bg` now default to `None` instead of white-on-black.
+
+ .. versionchanged:: 13.0
+
+ `x` and `y` are now always used as an absolute position for negative values.
+
+ .. versionchanged:: 18.0
+
+ Deprecated giving `string`, `fg`, `bg`, and `bg_blend` as positional arguments.
+
+ Added `text` parameter to replace `string`.
+
+ Added `width` and `height` keyword parameters to bind text to a rectangle and replace other print functions.
+ Right-aligned text with `width=None` now treats the `x` coordinate as a past-the-end index, this will shift
+ the text of older calls to the left by 1 tile.
+
+ Now returns the number of lines printed via word wrapping,
+ same as previous print functions bound to rectangles.
+ """
+ if width is not None and width <= 0:
+ return 0
+ if width is None and alignment == tcod.constants.LEFT: # Fix alignment
+ width = 0x100000
+ if width is None and alignment == tcod.constants.CENTER: # Fix center alignment
+ x -= 0x100000
+ width = 0x200000
+ if width is None and alignment == tcod.constants.RIGHT: # Fix right alignment
+ x -= 0x100000
+ width = 0x100000
+ rgb_fg = ffi.new("TCOD_ColorRGB*", fg) if fg is not None else ffi.NULL
+ rgb_bg = ffi.new("TCOD_ColorRGB*", bg) if bg is not None else ffi.NULL
+ utf8 = (string or text).encode("utf-8")
+ return _check(
+ int(
+ lib.TCOD_printn_rgb(
+ self.console_c,
+ {
+ "x": x,
+ "y": y,
+ "width": width or 0,
+ "height": height or 0,
+ "fg": rgb_fg,
+ "bg": rgb_bg,
+ "flag": bg_blend,
+ "alignment": alignment,
+ },
+ len(utf8),
+ utf8,
+ )
+ )
)
- self._console_data = self.console_c = ffi.new(
- 'struct TCOD_Console*', self._console_data)
+
+ @deprecated(
+ "Switch parameters to keywords, then replace method with 'console.print(...)', then replace 'string=' with 'text='"
+ )
+ def print_box( # noqa: PLR0913
+ self,
+ x: int,
+ y: int,
+ width: int,
+ height: int,
+ string: str,
+ fg: tuple[int, int, int] | None = None,
+ bg: tuple[int, int, int] | None = None,
+ bg_blend: int = tcod.constants.BKGND_SET,
+ alignment: int = tcod.constants.LEFT,
+ ) -> int:
+ """Print a string constrained to a rectangle and return the height.
+
+ `x` and `y` are the starting tile, with ``0,0`` as the upper-left
+ corner of the console.
+
+ `width` and `height` determine the bounds of the rectangle, the text
+ will automatically be word-wrapped to fit within these bounds.
+
+ `string` is a Unicode string which may include color control
+ characters.
+
+ `fg` and `bg` are the foreground text color and background tile color
+ respectfully. This is a 3-item tuple with (r, g, b) color values from
+ 0 to 255. These parameters can also be set to `None` to leave the
+ colors unchanged.
+
+ `bg_blend` is the blend type used by libtcod.
+
+ `alignment` can be `tcod.LEFT`, `tcod.CENTER`, or `tcod.RIGHT`.
+
+ Returns the actual height of the printed area.
+
+ .. versionadded:: 8.5
+
+ .. versionchanged:: 9.0
+ `fg` and `bg` now default to `None` instead of white-on-black.
+
+ .. versionchanged:: 13.0
+ `x` and `y` are now always used as an absolute position for negative values.
+
+ .. deprecated:: 18.0
+ This method was replaced by more functional :any:`Console.print` method.
+ """
+ string_ = string.encode("utf-8")
+ return int(
+ lib.TCOD_console_printn_rect(
+ self.console_c,
+ x,
+ y,
+ width,
+ height,
+ len(string_),
+ string_,
+ (fg,) if fg is not None else ffi.NULL,
+ (bg,) if bg is not None else ffi.NULL,
+ bg_blend,
+ alignment,
+ )
+ )
+
+ @overload
+ def draw_frame(
+ self,
+ x: int,
+ y: int,
+ width: int,
+ height: int,
+ *,
+ clear: bool = True,
+ fg: tuple[int, int, int] | None = None,
+ bg: tuple[int, int, int] | None = None,
+ bg_blend: int = tcod.constants.BKGND_SET,
+ decoration: str | tuple[int, int, int, int, int, int, int, int, int] = "┌─┐│ │└─┘",
+ ) -> None: ...
+
+ @overload
+ @deprecated("Parameters clear, fg, bg, bg_blend should be keyword arguments. Remove title parameter")
+ def draw_frame(
+ self,
+ x: int,
+ y: int,
+ width: int,
+ height: int,
+ title: str = "",
+ clear: bool = True, # noqa: FBT001, FBT002
+ fg: tuple[int, int, int] | None = None,
+ bg: tuple[int, int, int] | None = None,
+ bg_blend: int = tcod.constants.BKGND_SET,
+ *,
+ decoration: str | tuple[int, int, int, int, int, int, int, int, int] = "┌─┐│ │└─┘",
+ ) -> None: ...
+
+ def draw_frame( # noqa: PLR0913
+ self,
+ x: int,
+ y: int,
+ width: int,
+ height: int,
+ title: str = "",
+ clear: bool = True, # noqa: FBT001, FBT002
+ fg: tuple[int, int, int] | None = None,
+ bg: tuple[int, int, int] | None = None,
+ bg_blend: int = tcod.constants.BKGND_SET,
+ *,
+ decoration: str | tuple[int, int, int, int, int, int, int, int, int] = "┌─┐│ │└─┘",
+ ) -> None:
+ r"""Draw a framed rectangle with an optional title.
+
+ `x` and `y` are the starting tile, with ``0,0`` as the upper-left
+ corner of the console.
+
+ `width` and `height` determine the size of the frame.
+
+ `title` is a Unicode string. The title is drawn with `bg` as the text
+ color and `fg` as the background.
+ Using the `title` parameter is discouraged since the style it uses is
+ hard-coded into libtcod. You should print over the top or bottom
+ border with :any:`Console.print_box` using your own style.
+
+ If `clear` is True than the region inside of the frame will be cleared.
+ Must be given as a keyword argument.
+
+ `fg` and `bg` are the foreground and background colors for the frame
+ border. This is a 3-item tuple with (r, g, b) color values from
+ 0 to 255. These parameters can also be set to `None` to leave the
+ colors unchanged.
+ Must be given as a keyword argument.
+
+ `bg_blend` is the blend type used by libtcod.
+ Must be given as a keyword argument.
+
+ `decoration` is a sequence of glyphs to use for rendering the borders.
+ This a str or tuple of int's with 9 items with the items arranged in
+ row-major order.
+ If a `decoration` is given then `title` can not be used because the
+ style for `title` is hard-coded. You can easily print along the upper
+ or lower border of the frame manually.
+
+ .. versionadded:: 8.5
+
+ .. versionchanged:: 9.0
+ `fg` and `bg` now default to `None` instead of white-on-black.
+
+ .. versionchanged:: 12.6
+ Added `decoration` parameter.
+
+ .. versionchanged:: 13.0
+ `x` and `y` are now always used as an absolute position for negative values.
+
+ .. versionchanged:: 18.0
+ Deprecated `clear`, `fg`, `bg`, and `bg_blend` being given as positional arguments.
+ These should be keyword arguments only.
+
+ Example::
+
+ >>> from tcod import libtcodpy
+ >>> console = tcod.console.Console(12, 6)
+ >>> console.draw_frame(x=0, y=0, width=3, height=3)
+ >>> console.draw_frame(x=3, y=0, width=3, height=3, decoration="╔═╗║ ║╚═╝")
+ >>> console.draw_frame(x=6, y=0, width=3, height=3, decoration="123456789")
+ >>> console.draw_frame(x=9, y=0, width=3, height=3, decoration="/-\\| |\\-/")
+ >>> console.draw_frame(x=0, y=3, width=12, height=3)
+ >>> console.print(x=0, y=3, width=12, height=1, string=" Title ", alignment=libtcodpy.CENTER)
+ 1
+ >>> console.print(x=0, y=5, width=12, height=1, string="┤Lower├", alignment=libtcodpy.CENTER)
+ 1
+ >>> print(console)
+ <┌─┐╔═╗123/-\
+ │ │║ ║456| |
+ └─┘╚═╝789\-/
+ ┌─ Title ──┐
+ │ │
+ └─┤Lower├──┘>
+ """
+ if title and decoration != "┌─┐│ │└─┘":
+ msg = "The title and decoration parameters are mutually exclusive. You should print the title manually."
+ raise TypeError(msg)
+ if title:
+ warnings.warn(
+ "The title parameter will be removed in the future since the style is hard-coded.",
+ PendingDeprecationWarning,
+ stacklevel=2,
+ )
+ title_ = title.encode("utf-8")
+ lib.TCOD_console_printn_frame(
+ self.console_c,
+ x,
+ y,
+ width,
+ height,
+ len(title_),
+ title_,
+ (fg,) if fg is not None else ffi.NULL,
+ (bg,) if bg is not None else ffi.NULL,
+ bg_blend,
+ clear,
+ )
+ return
+ decoration_ = [ord(c) for c in decoration] if isinstance(decoration, str) else decoration
+ if len(decoration_) != 9: # noqa: PLR2004
+ msg = f"Decoration must have a length of 9 (len(decoration) is {len(decoration_)}.)"
+ raise TypeError(msg)
+ _check(
+ lib.TCOD_console_draw_frame_rgb(
+ self.console_c,
+ x,
+ y,
+ width,
+ height,
+ decoration_,
+ (fg,) if fg is not None else ffi.NULL,
+ (bg,) if bg is not None else ffi.NULL,
+ bg_blend,
+ clear,
+ )
+ )
+
+ @overload
+ def draw_rect(
+ self,
+ x: int,
+ y: int,
+ width: int,
+ height: int,
+ *,
+ ch: int,
+ fg: tuple[int, int, int] | None = None,
+ bg: tuple[int, int, int] | None = None,
+ bg_blend: int = tcod.constants.BKGND_SET,
+ ) -> None: ...
+
+ @overload
+ @deprecated("Parameters ch, fg, bg, bg_blend should be keyword arguments")
+ def draw_rect(
+ self,
+ x: int,
+ y: int,
+ width: int,
+ height: int,
+ ch: int,
+ fg: tuple[int, int, int] | None = None,
+ bg: tuple[int, int, int] | None = None,
+ bg_blend: int = tcod.constants.BKGND_SET,
+ ) -> None: ...
+
+ def draw_rect( # noqa: PLR0913
+ self,
+ x: int,
+ y: int,
+ width: int,
+ height: int,
+ ch: int,
+ fg: tuple[int, int, int] | None = None,
+ bg: tuple[int, int, int] | None = None,
+ bg_blend: int = tcod.constants.BKGND_SET,
+ ) -> None:
+ """Draw characters and colors over a rectangular region.
+
+ `x` and `y` are the starting tile, with ``0,0`` as the upper-left
+ corner of the console.
+
+ `width` and `height` determine the size of the rectangle.
+
+ `ch` is a Unicode integer. You can use 0 to leave the current characters unchanged.
+ Must be given as a keyword argument.
+
+ `fg` and `bg` are the foreground text color and background tile color respectfully.
+ This is a 3-item tuple with (r, g, b) color values from 0 to 255.
+ These parameters can also be set to `None` to leave the colors unchanged.
+ Must be given as a keyword argument.
+
+ `bg_blend` is the blend type used by libtcod.
+ Must be given as a keyword argument.
+
+ .. versionadded:: 8.5
+
+ .. versionchanged:: 9.0
+ `fg` and `bg` now default to `None` instead of white-on-black.
+
+ .. versionchanged:: 13.0
+ `x` and `y` are now always used as an absolute position for negative values.
+
+ .. versionchanged:: 18.0
+ Deprecated `ch`, `fg`, `bg`, and `bg_blend` being given as positional arguments.
+ These should be keyword arguments only.
+ """
+ lib.TCOD_console_draw_rect_rgb(
+ self.console_c,
+ x,
+ y,
+ width,
+ height,
+ ch,
+ (fg,) if fg is not None else ffi.NULL,
+ (bg,) if bg is not None else ffi.NULL,
+ bg_blend,
+ )
+
+ def draw_semigraphics(self, pixels: ArrayLike | tcod.image.Image, x: int = 0, y: int = 0) -> None:
+ """Draw a block of 2x2 semi-graphics into this console.
+
+ `pixels` is an Image or an array-like object. It will be down-sampled
+ into 2x2 blocks when drawn. Array-like objects must be in the shape of
+ `(height, width, RGB)` and should have a `dtype` of `numpy.uint8`.
+
+ `x` and `y` is the upper-left tile position to start drawing.
+
+ .. versionadded:: 11.4
+ """
+ image = tcod.image._as_image(pixels)
+ lib.TCOD_image_blit_2x(image.image_c, self.console_c, x, y, 0, 0, -1, -1)
+
+
+def get_height_rect(width: int, string: str) -> int:
+ """Return the number of lines which would be printed from these parameters.
+
+ `width` is the width of the print boundary.
+
+ `string` is a Unicode string which may include color control characters.
+
+ .. versionadded:: 9.2
+ """
+ string_ = string.encode("utf-8")
+ return int(lib.TCOD_console_get_height_rect_wn(width, len(string_), string_))
+
+
+@deprecated("This function does not support contexts.", category=FutureWarning)
+def recommended_size() -> tuple[int, int]:
+ """Return the recommended size of a console for the current active window.
+
+ The return is determined from the active tileset size and active window
+ size. This result should be used create an :any:`Console` instance.
+
+ This function will raise RuntimeError if libtcod has not been initialized.
+
+ .. versionadded:: 11.8
+
+ .. seealso::
+ :any:`libtcodpy.console_init_root`
+ :any:`libtcodpy.console_flush`
+
+ .. deprecated:: 11.13
+ This function does not support contexts.
+ Use :any:`Context.recommended_console_size` instead.
+ """
+ if not lib.TCOD_ctx.engine:
+ msg = "The libtcod engine was not initialized first."
+ raise RuntimeError(msg)
+ window = lib.TCOD_sys_get_sdl_window()
+ renderer = lib.TCOD_sys_get_sdl_renderer()
+ with ffi.new("int[2]") as xy:
+ if renderer:
+ lib.SDL_GetCurrentRenderOutputSize(renderer, xy, xy + 1)
+ else: # Assume OpenGL if a renderer does not exist.
+ lib.SDL_GetWindowSizeInPixels(window, xy, xy + 1)
+ w = max(1, xy[0] // lib.TCOD_ctx.tileset.tile_width)
+ h = max(1, xy[1] // lib.TCOD_ctx.tileset.tile_height)
+ return w, h
+
+
+def load_xp(path: str | PathLike[str], order: Literal["C", "F"] = "C") -> tuple[Console, ...]:
+ """Load a REXPaint file as a tuple of consoles.
+
+ `path` is the name of the REXPaint file to load.
+ Usually ending with `.xp`.
+
+ `order` is the memory order of the Console's array buffer,
+ see :any:`tcod.console.Console`.
+
+ .. versionadded:: 12.4
+
+ Example::
+
+ import numpy as np
+ import tcod.console
+ import tcod.tileset
+
+ path = "example.xp" # REXPaint file with one layer.
+
+ # Load a REXPaint file with a single layer.
+ # The comma after console is used to unpack a single item tuple.
+ console, = tcod.console.load_xp(path, order="F")
+
+ # Convert tcod's Code Page 437 character mapping into a NumPy array.
+ CP437_TO_UNICODE = np.asarray(tcod.tileset.CHARMAP_CP437)
+
+ # Convert from REXPaint's CP437 encoding to Unicode in-place.
+ console.ch[:] = CP437_TO_UNICODE[console.ch]
+
+ # Apply REXPaint's alpha key color.
+ KEY_COLOR = (255, 0, 255)
+ is_transparent = (console.rgb["bg"] == KEY_COLOR).all(axis=-1)
+ console.rgba[is_transparent] = (ord(" "), (0,), (0,))
+ """
+ path = Path(path).resolve(strict=True)
+ layers = _check(tcod.lib.TCOD_load_xp(_path_encode(path), 0, ffi.NULL))
+ consoles = ffi.new("TCOD_Console*[]", layers)
+ _check(tcod.lib.TCOD_load_xp(_path_encode(path), layers, consoles))
+ return tuple(Console._from_cdata(console_p, order=order) for console_p in consoles)
+
+
+def save_xp(
+ path: str | PathLike[str],
+ consoles: Iterable[Console],
+ compress_level: int = 9,
+) -> None:
+ """Save tcod Consoles to a REXPaint file.
+
+ `path` is where to save the file.
+
+ `consoles` are the :any:`tcod.console.Console` objects to be saved.
+
+ `compress_level` is the zlib compression level to be used.
+
+ Color alpha will be lost during saving.
+
+ Consoles will be saved as-is as much as possible. You may need to convert
+ characters from Unicode to CP437 if you want to load the file in REXPaint.
+
+ .. versionadded:: 12.4
+
+ Example::
+
+ import numpy as np
+ import tcod.console
+ import tcod.tileset
+
+ console = tcod.console.Console(80, 24) # Example console.
+
+ # Convert from Unicode to REXPaint's encoding.
+ # Required to load this console correctly in the REXPaint tool.
+
+ # Convert tcod's Code Page 437 character mapping into a NumPy array.
+ CP437_TO_UNICODE = np.asarray(tcod.tileset.CHARMAP_CP437)
+
+ # Initialize a Unicode-to-CP437 array.
+ # 0x20000 is the current full range of Unicode.
+ # fill_value=ord("?") means that "?" will be the result of any unknown codepoint.
+ UNICODE_TO_CP437 = np.full(0x20000, fill_value=ord("?"))
+ # Assign the CP437 mappings.
+ UNICODE_TO_CP437[CP437_TO_UNICODE] = np.arange(len(CP437_TO_UNICODE))
+
+ # Convert from Unicode to CP437 in-place.
+ console.ch[:] = UNICODE_TO_CP437[console.ch]
+
+ # Convert console alpha into REXPaint's alpha key color.
+ KEY_COLOR = (255, 0, 255)
+ is_transparent = console.rgba["bg"][:, :, 3] == 0
+ console.rgb["bg"][is_transparent] = KEY_COLOR
+
+ tcod.console.save_xp("example.xp", [console])
+ """
+ path = Path(path)
+ consoles_c = ffi.new("TCOD_Console*[]", [c.console_c for c in consoles])
+ _check(
+ tcod.lib.TCOD_save_xp(
+ len(consoles_c),
+ consoles_c,
+ _path_encode(path),
+ compress_level,
+ )
+ )
diff --git a/tcod/constants.py b/tcod/constants.py
index b559c440..ebaa4f4b 100644
--- a/tcod/constants.py
+++ b/tcod/constants.py
@@ -1,9 +1,7 @@
-"""
- Constants from the libtcod C API.
+"""Constants from the libtcod C API.
- This module is auto-generated by `build_libtcod.py`.
+This module is auto-generated by `build_libtcod.py`.
"""
-from __future__ import absolute_import
from tcod.color import Color
@@ -20,6 +18,7 @@
FOV_PERMISSIVE_8 = 11
FOV_RESTRICTIVE = 12
FOV_SHADOW = 2
+FOV_SYMMETRIC_SHADOWCAST = 13
KEY_0 = 24
KEY_1 = 25
KEY_2 = 26
@@ -242,13 +241,14 @@
EVENT_NONE = 0
FONT_LAYOUT_ASCII_INCOL = 1
FONT_LAYOUT_ASCII_INROW = 2
+FONT_LAYOUT_CP437 = 16
FONT_LAYOUT_TCOD = 8
FONT_TYPE_GRAYSCALE = 4
FONT_TYPE_GREYSCALE = 4
KEY_PRESSED = 1
KEY_RELEASED = 2
LEFT = 0
-NB_RENDERERS = 5
+NB_RENDERERS = 6
NOISE_DEFAULT = 0
NOISE_PERLIN = 1
NOISE_SIMPLEX = 2
@@ -258,6 +258,7 @@
RENDERER_OPENGL2 = 4
RENDERER_SDL = 2
RENDERER_SDL2 = 3
+RENDERER_XTERM = 5
RIGHT = 1
RNG_CMWC = 1
RNG_MT = 0
@@ -304,200 +305,501 @@
TYPE_VALUELIST15 = 23
# --- colors ---
-amber = Color(255,191,0)
-azure = Color(0,127,255)
-black = Color(0,0,0)
-blue = Color(0,0,255)
-brass = Color(191,151,96)
-celadon = Color(172,255,175)
-chartreuse = Color(127,255,0)
-copper = Color(197,136,124)
-crimson = Color(255,0,63)
-cyan = Color(0,255,255)
-dark_amber = Color(191,143,0)
-dark_azure = Color(0,95,191)
-dark_blue = Color(0,0,191)
-dark_chartreuse = Color(95,191,0)
-dark_crimson = Color(191,0,47)
-dark_cyan = Color(0,191,191)
-dark_flame = Color(191,47,0)
-dark_fuchsia = Color(191,0,191)
-dark_gray = Color(95,95,95)
-dark_green = Color(0,191,0)
-dark_grey = Color(95,95,95)
-dark_han = Color(47,0,191)
-dark_lime = Color(143,191,0)
-dark_magenta = Color(191,0,143)
-dark_orange = Color(191,95,0)
-dark_pink = Color(191,0,95)
-dark_purple = Color(143,0,191)
-dark_red = Color(191,0,0)
-dark_sea = Color(0,191,95)
-dark_sepia = Color(94,75,47)
-dark_sky = Color(0,143,191)
-dark_turquoise = Color(0,191,143)
-dark_violet = Color(95,0,191)
-dark_yellow = Color(191,191,0)
-darker_amber = Color(127,95,0)
-darker_azure = Color(0,63,127)
-darker_blue = Color(0,0,127)
-darker_chartreuse = Color(63,127,0)
-darker_crimson = Color(127,0,31)
-darker_cyan = Color(0,127,127)
-darker_flame = Color(127,31,0)
-darker_fuchsia = Color(127,0,127)
-darker_gray = Color(63,63,63)
-darker_green = Color(0,127,0)
-darker_grey = Color(63,63,63)
-darker_han = Color(31,0,127)
-darker_lime = Color(95,127,0)
-darker_magenta = Color(127,0,95)
-darker_orange = Color(127,63,0)
-darker_pink = Color(127,0,63)
-darker_purple = Color(95,0,127)
-darker_red = Color(127,0,0)
-darker_sea = Color(0,127,63)
-darker_sepia = Color(63,50,31)
-darker_sky = Color(0,95,127)
-darker_turquoise = Color(0,127,95)
-darker_violet = Color(63,0,127)
-darker_yellow = Color(127,127,0)
-darkest_amber = Color(63,47,0)
-darkest_azure = Color(0,31,63)
-darkest_blue = Color(0,0,63)
-darkest_chartreuse = Color(31,63,0)
-darkest_crimson = Color(63,0,15)
-darkest_cyan = Color(0,63,63)
-darkest_flame = Color(63,15,0)
-darkest_fuchsia = Color(63,0,63)
-darkest_gray = Color(31,31,31)
-darkest_green = Color(0,63,0)
-darkest_grey = Color(31,31,31)
-darkest_han = Color(15,0,63)
-darkest_lime = Color(47,63,0)
-darkest_magenta = Color(63,0,47)
-darkest_orange = Color(63,31,0)
-darkest_pink = Color(63,0,31)
-darkest_purple = Color(47,0,63)
-darkest_red = Color(63,0,0)
-darkest_sea = Color(0,63,31)
-darkest_sepia = Color(31,24,15)
-darkest_sky = Color(0,47,63)
-darkest_turquoise = Color(0,63,47)
-darkest_violet = Color(31,0,63)
-darkest_yellow = Color(63,63,0)
-desaturated_amber = Color(127,111,63)
-desaturated_azure = Color(63,95,127)
-desaturated_blue = Color(63,63,127)
-desaturated_chartreuse = Color(95,127,63)
-desaturated_crimson = Color(127,63,79)
-desaturated_cyan = Color(63,127,127)
-desaturated_flame = Color(127,79,63)
-desaturated_fuchsia = Color(127,63,127)
-desaturated_green = Color(63,127,63)
-desaturated_han = Color(79,63,127)
-desaturated_lime = Color(111,127,63)
-desaturated_magenta = Color(127,63,111)
-desaturated_orange = Color(127,95,63)
-desaturated_pink = Color(127,63,95)
-desaturated_purple = Color(111,63,127)
-desaturated_red = Color(127,63,63)
-desaturated_sea = Color(63,127,95)
-desaturated_sky = Color(63,111,127)
-desaturated_turquoise = Color(63,127,111)
-desaturated_violet = Color(95,63,127)
-desaturated_yellow = Color(127,127,63)
-flame = Color(255,63,0)
-fuchsia = Color(255,0,255)
-gold = Color(229,191,0)
-gray = Color(127,127,127)
-green = Color(0,255,0)
-grey = Color(127,127,127)
-han = Color(63,0,255)
-light_amber = Color(255,207,63)
-light_azure = Color(63,159,255)
-light_blue = Color(63,63,255)
-light_chartreuse = Color(159,255,63)
-light_crimson = Color(255,63,111)
-light_cyan = Color(63,255,255)
-light_flame = Color(255,111,63)
-light_fuchsia = Color(255,63,255)
-light_gray = Color(159,159,159)
-light_green = Color(63,255,63)
-light_grey = Color(159,159,159)
-light_han = Color(111,63,255)
-light_lime = Color(207,255,63)
-light_magenta = Color(255,63,207)
-light_orange = Color(255,159,63)
-light_pink = Color(255,63,159)
-light_purple = Color(207,63,255)
-light_red = Color(255,63,63)
-light_sea = Color(63,255,159)
-light_sepia = Color(158,134,100)
-light_sky = Color(63,207,255)
-light_turquoise = Color(63,255,207)
-light_violet = Color(159,63,255)
-light_yellow = Color(255,255,63)
-lighter_amber = Color(255,223,127)
-lighter_azure = Color(127,191,255)
-lighter_blue = Color(127,127,255)
-lighter_chartreuse = Color(191,255,127)
-lighter_crimson = Color(255,127,159)
-lighter_cyan = Color(127,255,255)
-lighter_flame = Color(255,159,127)
-lighter_fuchsia = Color(255,127,255)
-lighter_gray = Color(191,191,191)
-lighter_green = Color(127,255,127)
-lighter_grey = Color(191,191,191)
-lighter_han = Color(159,127,255)
-lighter_lime = Color(223,255,127)
-lighter_magenta = Color(255,127,223)
-lighter_orange = Color(255,191,127)
-lighter_pink = Color(255,127,191)
-lighter_purple = Color(223,127,255)
-lighter_red = Color(255,127,127)
-lighter_sea = Color(127,255,191)
-lighter_sepia = Color(191,171,143)
-lighter_sky = Color(127,223,255)
-lighter_turquoise = Color(127,255,223)
-lighter_violet = Color(191,127,255)
-lighter_yellow = Color(255,255,127)
-lightest_amber = Color(255,239,191)
-lightest_azure = Color(191,223,255)
-lightest_blue = Color(191,191,255)
-lightest_chartreuse = Color(223,255,191)
-lightest_crimson = Color(255,191,207)
-lightest_cyan = Color(191,255,255)
-lightest_flame = Color(255,207,191)
-lightest_fuchsia = Color(255,191,255)
-lightest_gray = Color(223,223,223)
-lightest_green = Color(191,255,191)
-lightest_grey = Color(223,223,223)
-lightest_han = Color(207,191,255)
-lightest_lime = Color(239,255,191)
-lightest_magenta = Color(255,191,239)
-lightest_orange = Color(255,223,191)
-lightest_pink = Color(255,191,223)
-lightest_purple = Color(239,191,255)
-lightest_red = Color(255,191,191)
-lightest_sea = Color(191,255,223)
-lightest_sepia = Color(222,211,195)
-lightest_sky = Color(191,239,255)
-lightest_turquoise = Color(191,255,239)
-lightest_violet = Color(223,191,255)
-lightest_yellow = Color(255,255,191)
-lime = Color(191,255,0)
-magenta = Color(255,0,191)
-orange = Color(255,127,0)
-peach = Color(255,159,127)
-pink = Color(255,0,127)
-purple = Color(191,0,255)
-red = Color(255,0,0)
-sea = Color(0,255,127)
-sepia = Color(127,101,63)
-silver = Color(203,203,203)
-sky = Color(0,191,255)
-turquoise = Color(0,255,191)
-violet = Color(127,0,255)
-white = Color(255,255,255)
-yellow = Color(255,255,0)
+amber = Color(255, 191, 0)
+azure = Color(0, 127, 255)
+black = Color(0, 0, 0)
+blue = Color(0, 0, 255)
+brass = Color(191, 151, 96)
+celadon = Color(172, 255, 175)
+chartreuse = Color(127, 255, 0)
+copper = Color(197, 136, 124)
+crimson = Color(255, 0, 63)
+cyan = Color(0, 255, 255)
+dark_amber = Color(191, 143, 0)
+dark_azure = Color(0, 95, 191)
+dark_blue = Color(0, 0, 191)
+dark_chartreuse = Color(95, 191, 0)
+dark_crimson = Color(191, 0, 47)
+dark_cyan = Color(0, 191, 191)
+dark_flame = Color(191, 47, 0)
+dark_fuchsia = Color(191, 0, 191)
+dark_gray = Color(95, 95, 95)
+dark_green = Color(0, 191, 0)
+dark_grey = Color(95, 95, 95)
+dark_han = Color(47, 0, 191)
+dark_lime = Color(143, 191, 0)
+dark_magenta = Color(191, 0, 143)
+dark_orange = Color(191, 95, 0)
+dark_pink = Color(191, 0, 95)
+dark_purple = Color(143, 0, 191)
+dark_red = Color(191, 0, 0)
+dark_sea = Color(0, 191, 95)
+dark_sepia = Color(94, 75, 47)
+dark_sky = Color(0, 143, 191)
+dark_turquoise = Color(0, 191, 143)
+dark_violet = Color(95, 0, 191)
+dark_yellow = Color(191, 191, 0)
+darker_amber = Color(127, 95, 0)
+darker_azure = Color(0, 63, 127)
+darker_blue = Color(0, 0, 127)
+darker_chartreuse = Color(63, 127, 0)
+darker_crimson = Color(127, 0, 31)
+darker_cyan = Color(0, 127, 127)
+darker_flame = Color(127, 31, 0)
+darker_fuchsia = Color(127, 0, 127)
+darker_gray = Color(63, 63, 63)
+darker_green = Color(0, 127, 0)
+darker_grey = Color(63, 63, 63)
+darker_han = Color(31, 0, 127)
+darker_lime = Color(95, 127, 0)
+darker_magenta = Color(127, 0, 95)
+darker_orange = Color(127, 63, 0)
+darker_pink = Color(127, 0, 63)
+darker_purple = Color(95, 0, 127)
+darker_red = Color(127, 0, 0)
+darker_sea = Color(0, 127, 63)
+darker_sepia = Color(63, 50, 31)
+darker_sky = Color(0, 95, 127)
+darker_turquoise = Color(0, 127, 95)
+darker_violet = Color(63, 0, 127)
+darker_yellow = Color(127, 127, 0)
+darkest_amber = Color(63, 47, 0)
+darkest_azure = Color(0, 31, 63)
+darkest_blue = Color(0, 0, 63)
+darkest_chartreuse = Color(31, 63, 0)
+darkest_crimson = Color(63, 0, 15)
+darkest_cyan = Color(0, 63, 63)
+darkest_flame = Color(63, 15, 0)
+darkest_fuchsia = Color(63, 0, 63)
+darkest_gray = Color(31, 31, 31)
+darkest_green = Color(0, 63, 0)
+darkest_grey = Color(31, 31, 31)
+darkest_han = Color(15, 0, 63)
+darkest_lime = Color(47, 63, 0)
+darkest_magenta = Color(63, 0, 47)
+darkest_orange = Color(63, 31, 0)
+darkest_pink = Color(63, 0, 31)
+darkest_purple = Color(47, 0, 63)
+darkest_red = Color(63, 0, 0)
+darkest_sea = Color(0, 63, 31)
+darkest_sepia = Color(31, 24, 15)
+darkest_sky = Color(0, 47, 63)
+darkest_turquoise = Color(0, 63, 47)
+darkest_violet = Color(31, 0, 63)
+darkest_yellow = Color(63, 63, 0)
+desaturated_amber = Color(127, 111, 63)
+desaturated_azure = Color(63, 95, 127)
+desaturated_blue = Color(63, 63, 127)
+desaturated_chartreuse = Color(95, 127, 63)
+desaturated_crimson = Color(127, 63, 79)
+desaturated_cyan = Color(63, 127, 127)
+desaturated_flame = Color(127, 79, 63)
+desaturated_fuchsia = Color(127, 63, 127)
+desaturated_green = Color(63, 127, 63)
+desaturated_han = Color(79, 63, 127)
+desaturated_lime = Color(111, 127, 63)
+desaturated_magenta = Color(127, 63, 111)
+desaturated_orange = Color(127, 95, 63)
+desaturated_pink = Color(127, 63, 95)
+desaturated_purple = Color(111, 63, 127)
+desaturated_red = Color(127, 63, 63)
+desaturated_sea = Color(63, 127, 95)
+desaturated_sky = Color(63, 111, 127)
+desaturated_turquoise = Color(63, 127, 111)
+desaturated_violet = Color(95, 63, 127)
+desaturated_yellow = Color(127, 127, 63)
+flame = Color(255, 63, 0)
+fuchsia = Color(255, 0, 255)
+gold = Color(229, 191, 0)
+gray = Color(127, 127, 127)
+green = Color(0, 255, 0)
+grey = Color(127, 127, 127)
+han = Color(63, 0, 255)
+light_amber = Color(255, 207, 63)
+light_azure = Color(63, 159, 255)
+light_blue = Color(63, 63, 255)
+light_chartreuse = Color(159, 255, 63)
+light_crimson = Color(255, 63, 111)
+light_cyan = Color(63, 255, 255)
+light_flame = Color(255, 111, 63)
+light_fuchsia = Color(255, 63, 255)
+light_gray = Color(159, 159, 159)
+light_green = Color(63, 255, 63)
+light_grey = Color(159, 159, 159)
+light_han = Color(111, 63, 255)
+light_lime = Color(207, 255, 63)
+light_magenta = Color(255, 63, 207)
+light_orange = Color(255, 159, 63)
+light_pink = Color(255, 63, 159)
+light_purple = Color(207, 63, 255)
+light_red = Color(255, 63, 63)
+light_sea = Color(63, 255, 159)
+light_sepia = Color(158, 134, 100)
+light_sky = Color(63, 207, 255)
+light_turquoise = Color(63, 255, 207)
+light_violet = Color(159, 63, 255)
+light_yellow = Color(255, 255, 63)
+lighter_amber = Color(255, 223, 127)
+lighter_azure = Color(127, 191, 255)
+lighter_blue = Color(127, 127, 255)
+lighter_chartreuse = Color(191, 255, 127)
+lighter_crimson = Color(255, 127, 159)
+lighter_cyan = Color(127, 255, 255)
+lighter_flame = Color(255, 159, 127)
+lighter_fuchsia = Color(255, 127, 255)
+lighter_gray = Color(191, 191, 191)
+lighter_green = Color(127, 255, 127)
+lighter_grey = Color(191, 191, 191)
+lighter_han = Color(159, 127, 255)
+lighter_lime = Color(223, 255, 127)
+lighter_magenta = Color(255, 127, 223)
+lighter_orange = Color(255, 191, 127)
+lighter_pink = Color(255, 127, 191)
+lighter_purple = Color(223, 127, 255)
+lighter_red = Color(255, 127, 127)
+lighter_sea = Color(127, 255, 191)
+lighter_sepia = Color(191, 171, 143)
+lighter_sky = Color(127, 223, 255)
+lighter_turquoise = Color(127, 255, 223)
+lighter_violet = Color(191, 127, 255)
+lighter_yellow = Color(255, 255, 127)
+lightest_amber = Color(255, 239, 191)
+lightest_azure = Color(191, 223, 255)
+lightest_blue = Color(191, 191, 255)
+lightest_chartreuse = Color(223, 255, 191)
+lightest_crimson = Color(255, 191, 207)
+lightest_cyan = Color(191, 255, 255)
+lightest_flame = Color(255, 207, 191)
+lightest_fuchsia = Color(255, 191, 255)
+lightest_gray = Color(223, 223, 223)
+lightest_green = Color(191, 255, 191)
+lightest_grey = Color(223, 223, 223)
+lightest_han = Color(207, 191, 255)
+lightest_lime = Color(239, 255, 191)
+lightest_magenta = Color(255, 191, 239)
+lightest_orange = Color(255, 223, 191)
+lightest_pink = Color(255, 191, 223)
+lightest_purple = Color(239, 191, 255)
+lightest_red = Color(255, 191, 191)
+lightest_sea = Color(191, 255, 223)
+lightest_sepia = Color(222, 211, 195)
+lightest_sky = Color(191, 239, 255)
+lightest_turquoise = Color(191, 255, 239)
+lightest_violet = Color(223, 191, 255)
+lightest_yellow = Color(255, 255, 191)
+lime = Color(191, 255, 0)
+magenta = Color(255, 0, 191)
+orange = Color(255, 127, 0)
+peach = Color(255, 159, 127)
+pink = Color(255, 0, 127)
+purple = Color(191, 0, 255)
+red = Color(255, 0, 0)
+sea = Color(0, 255, 127)
+sepia = Color(127, 101, 63)
+silver = Color(203, 203, 203)
+sky = Color(0, 191, 255)
+turquoise = Color(0, 255, 191)
+violet = Color(127, 0, 255)
+white = Color(255, 255, 255)
+yellow = Color(255, 255, 0)
+
+__all__ = [ # noqa: RUF022
+ "FOV_BASIC",
+ "FOV_DIAMOND",
+ "FOV_PERMISSIVE_0",
+ "FOV_PERMISSIVE_1",
+ "FOV_PERMISSIVE_2",
+ "FOV_PERMISSIVE_3",
+ "FOV_PERMISSIVE_4",
+ "FOV_PERMISSIVE_5",
+ "FOV_PERMISSIVE_6",
+ "FOV_PERMISSIVE_7",
+ "FOV_PERMISSIVE_8",
+ "FOV_RESTRICTIVE",
+ "FOV_SHADOW",
+ "FOV_SYMMETRIC_SHADOWCAST",
+ "KEY_0",
+ "KEY_1",
+ "KEY_2",
+ "KEY_3",
+ "KEY_4",
+ "KEY_5",
+ "KEY_6",
+ "KEY_7",
+ "KEY_8",
+ "KEY_9",
+ "KEY_ALT",
+ "KEY_APPS",
+ "KEY_BACKSPACE",
+ "KEY_CAPSLOCK",
+ "KEY_CHAR",
+ "KEY_CONTROL",
+ "KEY_DELETE",
+ "KEY_DOWN",
+ "KEY_END",
+ "KEY_ENTER",
+ "KEY_ESCAPE",
+ "KEY_F1",
+ "KEY_F10",
+ "KEY_F11",
+ "KEY_F12",
+ "KEY_F2",
+ "KEY_F3",
+ "KEY_F4",
+ "KEY_F5",
+ "KEY_F6",
+ "KEY_F7",
+ "KEY_F8",
+ "KEY_F9",
+ "KEY_HOME",
+ "KEY_INSERT",
+ "KEY_KP0",
+ "KEY_KP1",
+ "KEY_KP2",
+ "KEY_KP3",
+ "KEY_KP4",
+ "KEY_KP5",
+ "KEY_KP6",
+ "KEY_KP7",
+ "KEY_KP8",
+ "KEY_KP9",
+ "KEY_KPADD",
+ "KEY_KPDEC",
+ "KEY_KPDIV",
+ "KEY_KPENTER",
+ "KEY_KPMUL",
+ "KEY_KPSUB",
+ "KEY_LEFT",
+ "KEY_LWIN",
+ "KEY_NONE",
+ "KEY_NUMLOCK",
+ "KEY_PAGEDOWN",
+ "KEY_PAGEUP",
+ "KEY_PAUSE",
+ "KEY_PRINTSCREEN",
+ "KEY_RIGHT",
+ "KEY_RWIN",
+ "KEY_SCROLLLOCK",
+ "KEY_SHIFT",
+ "KEY_SPACE",
+ "KEY_TAB",
+ "KEY_TEXT",
+ "KEY_UP",
+ "BKGND_ADD",
+ "BKGND_ADDA",
+ "BKGND_ALPH",
+ "BKGND_BURN",
+ "BKGND_COLOR_BURN",
+ "BKGND_COLOR_DODGE",
+ "BKGND_DARKEN",
+ "BKGND_DEFAULT",
+ "BKGND_LIGHTEN",
+ "BKGND_MULTIPLY",
+ "BKGND_NONE",
+ "BKGND_OVERLAY",
+ "BKGND_SCREEN",
+ "BKGND_SET",
+ "CENTER",
+ "CHAR_ARROW2_E",
+ "CHAR_ARROW2_N",
+ "CHAR_ARROW2_S",
+ "CHAR_ARROW2_W",
+ "CHAR_ARROW_E",
+ "CHAR_ARROW_N",
+ "CHAR_ARROW_S",
+ "CHAR_ARROW_W",
+ "CHAR_BLOCK1",
+ "CHAR_BLOCK2",
+ "CHAR_BLOCK3",
+ "CHAR_BULLET",
+ "CHAR_BULLET_INV",
+ "CHAR_BULLET_SQUARE",
+ "CHAR_CENT",
+ "CHAR_CHECKBOX_SET",
+ "CHAR_CHECKBOX_UNSET",
+ "CHAR_CLUB",
+ "CHAR_COPYRIGHT",
+ "CHAR_CROSS",
+ "CHAR_CURRENCY",
+ "CHAR_DARROW_H",
+ "CHAR_DARROW_V",
+ "CHAR_DCROSS",
+ "CHAR_DHLINE",
+ "CHAR_DIAMOND",
+ "CHAR_DIVISION",
+ "CHAR_DNE",
+ "CHAR_DNW",
+ "CHAR_DSE",
+ "CHAR_DSW",
+ "CHAR_DTEEE",
+ "CHAR_DTEEN",
+ "CHAR_DTEES",
+ "CHAR_DTEEW",
+ "CHAR_DVLINE",
+ "CHAR_EXCLAM_DOUBLE",
+ "CHAR_FEMALE",
+ "CHAR_FUNCTION",
+ "CHAR_GRADE",
+ "CHAR_HALF",
+ "CHAR_HEART",
+ "CHAR_HLINE",
+ "CHAR_LIGHT",
+ "CHAR_MALE",
+ "CHAR_MULTIPLICATION",
+ "CHAR_NE",
+ "CHAR_NOTE",
+ "CHAR_NOTE_DOUBLE",
+ "CHAR_NW",
+ "CHAR_ONE_QUARTER",
+ "CHAR_PILCROW",
+ "CHAR_POUND",
+ "CHAR_POW1",
+ "CHAR_POW2",
+ "CHAR_POW3",
+ "CHAR_RADIO_SET",
+ "CHAR_RADIO_UNSET",
+ "CHAR_RESERVED",
+ "CHAR_SE",
+ "CHAR_SECTION",
+ "CHAR_SMILIE",
+ "CHAR_SMILIE_INV",
+ "CHAR_SPADE",
+ "CHAR_SUBP_DIAG",
+ "CHAR_SUBP_E",
+ "CHAR_SUBP_N",
+ "CHAR_SUBP_NE",
+ "CHAR_SUBP_NW",
+ "CHAR_SUBP_SE",
+ "CHAR_SUBP_SW",
+ "CHAR_SW",
+ "CHAR_TEEE",
+ "CHAR_TEEN",
+ "CHAR_TEES",
+ "CHAR_TEEW",
+ "CHAR_THREE_QUARTERS",
+ "CHAR_UMLAUT",
+ "CHAR_VLINE",
+ "CHAR_YEN",
+ "COLCTRL_1",
+ "COLCTRL_2",
+ "COLCTRL_3",
+ "COLCTRL_4",
+ "COLCTRL_5",
+ "COLCTRL_BACK_RGB",
+ "COLCTRL_FORE_RGB",
+ "COLCTRL_NUMBER",
+ "COLCTRL_STOP",
+ "COLOR_AMBER",
+ "COLOR_AZURE",
+ "COLOR_BLUE",
+ "COLOR_CHARTREUSE",
+ "COLOR_CRIMSON",
+ "COLOR_CYAN",
+ "COLOR_DARK",
+ "COLOR_DARKER",
+ "COLOR_DARKEST",
+ "COLOR_DESATURATED",
+ "COLOR_FLAME",
+ "COLOR_FUCHSIA",
+ "COLOR_GREEN",
+ "COLOR_HAN",
+ "COLOR_LEVELS",
+ "COLOR_LIGHT",
+ "COLOR_LIGHTER",
+ "COLOR_LIGHTEST",
+ "COLOR_LIME",
+ "COLOR_MAGENTA",
+ "COLOR_NB",
+ "COLOR_NORMAL",
+ "COLOR_ORANGE",
+ "COLOR_PINK",
+ "COLOR_PURPLE",
+ "COLOR_RED",
+ "COLOR_SEA",
+ "COLOR_SKY",
+ "COLOR_TURQUOISE",
+ "COLOR_VIOLET",
+ "COLOR_YELLOW",
+ "DISTRIBUTION_GAUSSIAN",
+ "DISTRIBUTION_GAUSSIAN_INVERSE",
+ "DISTRIBUTION_GAUSSIAN_RANGE",
+ "DISTRIBUTION_GAUSSIAN_RANGE_INVERSE",
+ "DISTRIBUTION_LINEAR",
+ "EVENT_ANY",
+ "EVENT_FINGER",
+ "EVENT_FINGER_MOVE",
+ "EVENT_FINGER_PRESS",
+ "EVENT_FINGER_RELEASE",
+ "EVENT_KEY",
+ "EVENT_KEY_PRESS",
+ "EVENT_KEY_RELEASE",
+ "EVENT_MOUSE",
+ "EVENT_MOUSE_MOVE",
+ "EVENT_MOUSE_PRESS",
+ "EVENT_MOUSE_RELEASE",
+ "EVENT_NONE",
+ "FONT_LAYOUT_ASCII_INCOL",
+ "FONT_LAYOUT_ASCII_INROW",
+ "FONT_LAYOUT_CP437",
+ "FONT_LAYOUT_TCOD",
+ "FONT_TYPE_GRAYSCALE",
+ "FONT_TYPE_GREYSCALE",
+ "KEY_PRESSED",
+ "KEY_RELEASED",
+ "LEFT",
+ "NB_RENDERERS",
+ "NOISE_DEFAULT",
+ "NOISE_PERLIN",
+ "NOISE_SIMPLEX",
+ "NOISE_WAVELET",
+ "RENDERER_GLSL",
+ "RENDERER_OPENGL",
+ "RENDERER_OPENGL2",
+ "RENDERER_SDL",
+ "RENDERER_SDL2",
+ "RENDERER_XTERM",
+ "RIGHT",
+ "RNG_CMWC",
+ "RNG_MT",
+ "TYPE_BOOL",
+ "TYPE_CHAR",
+ "TYPE_COLOR",
+ "TYPE_CUSTOM00",
+ "TYPE_CUSTOM01",
+ "TYPE_CUSTOM02",
+ "TYPE_CUSTOM03",
+ "TYPE_CUSTOM04",
+ "TYPE_CUSTOM05",
+ "TYPE_CUSTOM06",
+ "TYPE_CUSTOM07",
+ "TYPE_CUSTOM08",
+ "TYPE_CUSTOM09",
+ "TYPE_CUSTOM10",
+ "TYPE_CUSTOM11",
+ "TYPE_CUSTOM12",
+ "TYPE_CUSTOM13",
+ "TYPE_CUSTOM14",
+ "TYPE_CUSTOM15",
+ "TYPE_DICE",
+ "TYPE_FLOAT",
+ "TYPE_INT",
+ "TYPE_LIST",
+ "TYPE_NONE",
+ "TYPE_STRING",
+ "TYPE_VALUELIST00",
+ "TYPE_VALUELIST01",
+ "TYPE_VALUELIST02",
+ "TYPE_VALUELIST03",
+ "TYPE_VALUELIST04",
+ "TYPE_VALUELIST05",
+ "TYPE_VALUELIST06",
+ "TYPE_VALUELIST07",
+ "TYPE_VALUELIST08",
+ "TYPE_VALUELIST09",
+ "TYPE_VALUELIST10",
+ "TYPE_VALUELIST11",
+ "TYPE_VALUELIST12",
+ "TYPE_VALUELIST13",
+ "TYPE_VALUELIST14",
+ "TYPE_VALUELIST15",
+]
diff --git a/tcod/context.py b/tcod/context.py
new file mode 100644
index 00000000..01ed61f7
--- /dev/null
+++ b/tcod/context.py
@@ -0,0 +1,635 @@
+"""This module is used to create and handle libtcod contexts.
+
+See :ref:`getting-started` for beginner examples on how to use this module.
+
+:any:`Context`'s are intended to replace several libtcod functions such as
+:any:`libtcodpy.console_init_root`, :any:`libtcodpy.console_flush`,
+:any:`tcod.console.recommended_size`, and many other functions which rely on
+hidden global objects within libtcod. If you begin using contexts then
+most of these functions will no longer work properly.
+
+Instead of calling :any:`libtcodpy.console_init_root` you can call
+:any:`tcod.context.new` with different keywords depending on how you plan
+to setup the size of the console. You should use
+:any:`tcod.tileset` to load the font for a context.
+
+.. note::
+ If you use contexts then expect deprecated functions from ``libtcodpy`` to no longer work correctly.
+ Those functions rely on a global console or tileset which doesn't exists with contexts.
+ Also ``libtcodpy`` event functions will no longer return tile coordinates for the mouse.
+
+ New programs not using ``libtcodpy`` can ignore this warning.
+
+.. versionadded:: 11.12
+"""
+
+from __future__ import annotations
+
+import copy
+import pickle
+import sys
+import warnings
+from math import floor
+from pathlib import Path
+from typing import TYPE_CHECKING, Any, Literal, NoReturn, TypeVar
+
+from typing_extensions import Self, deprecated
+
+import tcod.console
+import tcod.event
+import tcod.render
+import tcod.sdl.render
+import tcod.sdl.video
+import tcod.tileset
+from tcod._internal import _check, _check_warn
+from tcod.cffi import ffi, lib
+
+if TYPE_CHECKING:
+ from collections.abc import Iterable
+
+__all__ = (
+ "RENDERER_OPENGL",
+ "RENDERER_OPENGL2",
+ "RENDERER_SDL",
+ "RENDERER_SDL2",
+ "RENDERER_XTERM",
+ "SDL_WINDOW_ALLOW_HIGHDPI",
+ "SDL_WINDOW_BORDERLESS",
+ "SDL_WINDOW_FULLSCREEN",
+ "SDL_WINDOW_FULLSCREEN_DESKTOP",
+ "SDL_WINDOW_HIDDEN",
+ "SDL_WINDOW_INPUT_GRABBED",
+ "SDL_WINDOW_MAXIMIZED",
+ "SDL_WINDOW_MINIMIZED",
+ "SDL_WINDOW_RESIZABLE",
+ "Context",
+ "new",
+ "new_terminal",
+ "new_window",
+)
+
+_Event = TypeVar("_Event", bound="tcod.event.Event")
+
+SDL_WINDOW_FULLSCREEN = lib.SDL_WINDOW_FULLSCREEN
+"""Fullscreen mode."""
+# SDL_WINDOW_FULLSCREEN_DESKTOP = lib.SDL_WINDOW_FULLSCREEN_DESKTOP
+# """A borderless fullscreen window at the desktop resolution."""
+SDL_WINDOW_HIDDEN = lib.SDL_WINDOW_HIDDEN
+"""Window is hidden."""
+SDL_WINDOW_BORDERLESS = lib.SDL_WINDOW_BORDERLESS
+"""Window has no decorative border."""
+SDL_WINDOW_RESIZABLE = lib.SDL_WINDOW_RESIZABLE
+"""Window can be resized."""
+SDL_WINDOW_MINIMIZED = lib.SDL_WINDOW_MINIMIZED
+"""Window is minimized."""
+SDL_WINDOW_MAXIMIZED = lib.SDL_WINDOW_MAXIMIZED
+"""Window is maximized."""
+SDL_WINDOW_INPUT_GRABBED = lib.SDL_WINDOW_MOUSE_GRABBED
+"""Window has grabbed the input."""
+SDL_WINDOW_ALLOW_HIGHDPI = lib.SDL_WINDOW_HIGH_PIXEL_DENSITY
+"""High DPI mode, see the SDL documentation."""
+
+RENDERER_OPENGL = lib.TCOD_RENDERER_OPENGL
+"""A renderer for older versions of OpenGL.
+
+Should support OpenGL 1 and GLES 1
+"""
+RENDERER_OPENGL2 = lib.TCOD_RENDERER_OPENGL2
+"""An SDL2/OPENGL2 renderer. Usually faster than regular SDL2.
+
+Recommended if you need a high performance renderer.
+
+Should support OpenGL 2.0 and GLES 2.0.
+"""
+RENDERER_SDL = lib.TCOD_RENDERER_SDL
+"""Same as RENDERER_SDL2, but forces SDL2 into software mode."""
+RENDERER_SDL2 = lib.TCOD_RENDERER_SDL2
+"""The main SDL2 renderer.
+
+Rendering is decided by SDL2 and can be changed by using an SDL2 hint:
+https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER
+"""
+RENDERER_XTERM = lib.TCOD_RENDERER_XTERM
+"""A renderer targeting modern terminals with 24-bit color support.
+
+This is an experimental renderer with partial support for XTerm and SSH.
+This will work best on those terminals.
+
+Terminal inputs and events will be passed to SDL's event system.
+
+There is poor support for ANSI escapes on Windows 10.
+It is not recommended to use this renderer on Windows.
+
+.. versionadded:: 13.3
+"""
+
+
+def _handle_tileset(tileset: tcod.tileset.Tileset | None) -> Any: # noqa: ANN401
+ """Get the TCOD_Tileset pointer from a Tileset or return a NULL pointer."""
+ return tileset._tileset_p if tileset is not None else ffi.NULL
+
+
+def _handle_title(title: str | None) -> Any: # noqa: ANN401
+ """Return title as a CFFI string.
+
+ If title is None then return a decent default title is returned.
+ """
+ if title is None:
+ title = Path(sys.argv[0]).name
+ return ffi.new("char[]", title.encode("utf-8"))
+
+
+class Context:
+ """Context manager for libtcod context objects.
+
+ Use :any:`tcod.context.new` to create a new context.
+ """
+
+ def __init__(self, context_p: Any) -> None: # noqa: ANN401
+ """Create a context from a cffi pointer."""
+ self._context_p = context_p
+
+ @classmethod
+ def _claim(cls, context_p: Any) -> Context: # noqa: ANN401
+ """Return a new instance wrapping a context pointer."""
+ return cls(ffi.gc(context_p, lib.TCOD_context_delete))
+
+ @property
+ def _p(self) -> Any: # noqa: ANN401
+ """Return the context pointer or raise if it is missing."""
+ try:
+ return self._context_p
+ except AttributeError:
+ msg = "This context has been closed can no longer be used."
+ raise RuntimeError(msg) from None
+
+ def __enter__(self) -> Self:
+ """Enter this context which will close on exiting."""
+ return self
+
+ def close(self) -> None:
+ """Close this context, closing any windows opened by this context.
+
+ Afterwards doing anything with this instance other than closing it again is invalid.
+ """
+ if hasattr(self, "_context_p"):
+ ffi.release(self._context_p)
+ del self._context_p
+
+ def __exit__(self, *_: object) -> None:
+ """Automatically close on the context on exit."""
+ self.close()
+
+ def present(
+ self,
+ console: tcod.console.Console,
+ *,
+ keep_aspect: bool = False,
+ integer_scaling: bool = False,
+ clear_color: tuple[int, int, int] = (0, 0, 0),
+ align: tuple[float, float] = (0.5, 0.5),
+ ) -> None:
+ """Present a console to this context's display.
+
+ `console` is the console you want to present.
+
+ If `keep_aspect` is True then the console aspect will be preserved with
+ a letterbox. Otherwise the console will be stretched to fill the
+ screen.
+
+ If `integer_scaling` is True then the console will be scaled in integer
+ increments. This will have no effect if the console must be shrunk.
+ You can use :any:`tcod.console.recommended_size` to create a console
+ which will fit the window without needing to be scaled.
+
+ `clear_color` is an RGB tuple used to clear the screen before the
+ console is presented, this will affect the border/letterbox color.
+
+ `align` is an (x, y) tuple determining where the console will be placed
+ when letter-boxing exists. Values of 0 will put the console at the
+ upper-left corner. Values of 0.5 will center the console.
+ """
+ clear_rgba = (clear_color[0], clear_color[1], clear_color[2], 255)
+ viewport_args = ffi.new(
+ "TCOD_ViewportOptions*",
+ {
+ "tcod_version": lib.TCOD_COMPILEDVERSION,
+ "keep_aspect": keep_aspect,
+ "integer_scaling": integer_scaling,
+ "clear_color": clear_rgba,
+ "align_x": align[0],
+ "align_y": align[1],
+ },
+ )
+ _check(lib.TCOD_context_present(self._p, console.console_c, viewport_args))
+
+ def pixel_to_tile(self, x: float, y: float) -> tuple[float, float]:
+ """Convert window pixel coordinates to tile coordinates."""
+ with ffi.new("double[2]", (x, y)) as xy:
+ _check(lib.TCOD_context_screen_pixel_to_tile_d(self._p, xy, xy + 1))
+ return xy[0], xy[1]
+
+ @deprecated("Use pixel_to_tile method instead.")
+ def pixel_to_subtile(self, x: float, y: float) -> tuple[float, float]:
+ """Convert window pixel coordinates to sub-tile coordinates."""
+ with ffi.new("double[2]", (x, y)) as xy:
+ _check(lib.TCOD_context_screen_pixel_to_tile_d(self._p, xy, xy + 1))
+ return xy[0], xy[1]
+
+ def convert_event(self, event: _Event) -> _Event:
+ """Return an event with mouse pixel coordinates converted into tile coordinates.
+
+ Example::
+
+ context: tcod.context.Context
+ for event in tcod.event.get():
+ event_tile = context.convert_event(event)
+ if isinstance(event, tcod.event.MouseMotion):
+ # Events start with pixel coordinates and motion.
+ print(f"Pixels: {event.position=}, {event.motion=}")
+ if isinstance(event_tile, tcod.event.MouseMotion):
+ # Tile coordinates are used in the returned event.
+ print(f"Tiles: {event_tile.position=}, {event_tile.motion=}")
+
+ .. versionchanged:: 15.0
+ Now returns a new event with the coordinates converted into tiles.
+ """
+ event_copy = copy.copy(event)
+ if isinstance(event, tcod.event._MouseEventWithPosition):
+ assert isinstance(event_copy, tcod.event._MouseEventWithPosition)
+ event_copy.position = tcod.event.Point(*self.pixel_to_tile(event.position[0], event.position[1]))
+ if isinstance(event, tcod.event._MouseEventWithTile):
+ event._tile = tcod.event.Point(floor(event_copy.position[0]), floor(event_copy.position[1]))
+ if isinstance(event, tcod.event.MouseMotion):
+ assert isinstance(event_copy, tcod.event.MouseMotion)
+ assert event._tile is not None
+ prev_tile = self.pixel_to_tile(
+ event.position[0] - event.motion[0],
+ event.position[1] - event.motion[1],
+ )
+ event_copy.motion = tcod.event.Point(
+ event_copy.position[0] - prev_tile[0],
+ event_copy.position[1] - prev_tile[1],
+ )
+ event._tile_motion = tcod.event.Point(
+ event._tile[0] - floor(prev_tile[0]),
+ event._tile[1] - floor(prev_tile[1]),
+ )
+ return event_copy
+
+ def save_screenshot(self, path: str | None = None) -> None:
+ """Save a screen-shot to the given file path."""
+ c_path = path.encode("utf-8") if path is not None else ffi.NULL
+ _check(lib.TCOD_context_save_screenshot(self._p, c_path))
+
+ def change_tileset(self, tileset: tcod.tileset.Tileset | None) -> None:
+ """Change the active tileset used by this context.
+
+ The new tileset will take effect on the next call to :any:`present`.
+ Contexts not using a renderer with an emulated terminal will be unaffected by this method.
+
+ This does not do anything to resize the window, keep this in mind if the tileset as a differing tile size.
+ Access the window with :any:`sdl_window` to resize it manually, if needed.
+
+ Using this method only one tileset is active per-frame.
+ See :any:`tcod.render` if you want to renderer with multiple tilesets in a single frame.
+ """
+ _check(lib.TCOD_context_change_tileset(self._p, _handle_tileset(tileset)))
+
+ def new_console(
+ self,
+ min_columns: int = 1,
+ min_rows: int = 1,
+ magnification: float = 1.0,
+ order: Literal["C", "F"] = "C",
+ ) -> tcod.console.Console:
+ """Return a new console sized for this context.
+
+ `min_columns` and `min_rows` are the minimum size to use for the new
+ console.
+
+ `magnification` determines the apparent size of the tiles on the output
+ display. A `magnification` larger then 1.0 will output smaller
+ consoles, which will show as larger tiles when presented.
+ `magnification` must be greater than zero.
+
+ `order` is passed to :any:`tcod.console.Console` to determine the
+ memory order of its NumPy attributes.
+
+ The times where it is the most useful to call this method are:
+
+ * After the context is created, even if the console was given a
+ specific size.
+ * After the :any:`change_tileset` method is called.
+ * After any window resized event, or any manual resizing of the window.
+
+ .. versionadded:: 11.18
+
+ .. versionchanged:: 11.19
+ Added `order` parameter.
+
+ .. seealso::
+ :any:`tcod.console.Console`
+
+ Example::
+
+ scale = 1 # Tile size scale. This example uses integers but floating point numbers are also valid.
+ context = tcod.context.new()
+ while True:
+ # Create a cleared, dynamically-sized console for each frame.
+ console = context.new_console(magnification=scale)
+ # This printed output will wrap if the window is shrunk.
+ console.print_box(0, 0, console.width, console.height, "Hello world")
+ # Use integer_scaling to prevent subpixel distortion.
+ # This may add padding around the rendered console.
+ context.present(console, integer_scaling=True)
+ for event in tcod.event.wait():
+ if isinstance(event, tcod.event.Quit):
+ raise SystemExit()
+ elif isinstance(event, tcod.event.MouseWheel):
+ # Use the mouse wheel to change the rendered tile size.
+ scale = max(1, scale + event.y)
+ """
+ if magnification < 0:
+ msg = f"Magnification must be greater than zero. (Got {magnification:f})"
+ raise ValueError(msg)
+ size = ffi.new("int[2]")
+ _check(lib.TCOD_context_recommended_console_size(self._p, magnification, size, size + 1))
+ width, height = max(min_columns, size[0]), max(min_rows, size[1])
+ return tcod.console.Console(width, height, order=order)
+
+ def recommended_console_size(self, min_columns: int = 1, min_rows: int = 1) -> tuple[int, int]:
+ """Return the recommended (columns, rows) of a console for this context.
+
+ `min_columns`, `min_rows` are the lowest values which will be returned.
+
+ If result is only used to create a new console then you may want to call :any:`Context.new_console` instead.
+ """
+ with ffi.new("int[2]") as size:
+ _check(lib.TCOD_context_recommended_console_size(self._p, 1.0, size, size + 1))
+ return max(min_columns, size[0]), max(min_rows, size[1])
+
+ @property
+ def renderer_type(self) -> int:
+ """Return the libtcod renderer type used by this context."""
+ return _check(lib.TCOD_context_get_renderer_type(self._p))
+
+ @property
+ def sdl_window_p(self) -> Any: # noqa: ANN401
+ '''A cffi `SDL_Window*` pointer. This pointer might be NULL.
+
+ This pointer will become invalid if the context is closed or goes out
+ of scope.
+
+ Python-tcod's FFI provides most SDL functions. So it's possible for
+ anyone with the SDL2 documentation to work directly with SDL's
+ pointers.
+
+ Example::
+
+ import tcod
+
+ def toggle_fullscreen(context: tcod.context.Context) -> None:
+ """Toggle a context window between fullscreen and windowed modes."""
+ if not context.sdl_window_p:
+ return
+ fullscreen = tcod.lib.SDL_GetWindowFlags(context.sdl_window_p) & (
+ tcod.lib.SDL_WINDOW_FULLSCREEN | tcod.lib.SDL_WINDOW_FULLSCREEN_DESKTOP
+ )
+ tcod.lib.SDL_SetWindowFullscreen(
+ context.sdl_window_p,
+ 0 if fullscreen else tcod.lib.SDL_WINDOW_FULLSCREEN_DESKTOP,
+ )
+
+ '''
+ return lib.TCOD_context_get_sdl_window(self._p)
+
+ @property
+ def sdl_window(self) -> tcod.sdl.video.Window | None:
+ '''Return a :any:`tcod.sdl.video.Window` referencing this contexts SDL window if it exists.
+
+ Example::
+
+ import tcod
+ import tcod.sdl.video
+
+ def toggle_fullscreen(context: tcod.context.Context) -> None:
+ """Toggle a context window between fullscreen and windowed modes."""
+ window = context.sdl_window
+ if not window:
+ return
+ if window.fullscreen:
+ window.fullscreen = False
+ else:
+ window.fullscreen = tcod.sdl.video.WindowFlags.FULLSCREEN_DESKTOP
+
+ .. versionadded:: 13.4
+ '''
+ p = self.sdl_window_p
+ return tcod.sdl.video.Window(p) if p else None
+
+ @property
+ def sdl_renderer(self) -> tcod.sdl.render.Renderer | None:
+ """Return a :any:`tcod.sdl.render.Renderer` referencing this contexts SDL renderer if it exists.
+
+ .. versionadded:: 13.4
+ """
+ p = lib.TCOD_context_get_sdl_renderer(self._p)
+ return tcod.sdl.render.Renderer(p) if p else None
+
+ @property
+ def sdl_atlas(self) -> tcod.render.SDLTilesetAtlas | None:
+ """Return a :any:`tcod.render.SDLTilesetAtlas` referencing libtcod's SDL texture atlas if it exists.
+
+ .. versionadded:: 13.5
+ """
+ if self._p.type not in (lib.TCOD_RENDERER_SDL, lib.TCOD_RENDERER_SDL2):
+ return None
+ context_data = ffi.cast("struct TCOD_RendererSDL2*", self._context_p.contextdata_)
+ return tcod.render.SDLTilesetAtlas._from_ref(context_data.renderer, context_data.atlas)
+
+ def __reduce__(self) -> NoReturn:
+ """Contexts can not be pickled, so this class will raise :class:`pickle.PicklingError`."""
+ msg = "Python-tcod contexts can not be pickled."
+ raise pickle.PicklingError(msg)
+
+
+@ffi.def_extern() # type: ignore[untyped-decorator]
+def _pycall_cli_output(catch_reference: Any, output: Any) -> None: # noqa: ANN401
+ """Callback for the libtcod context CLI.
+
+ Catches the CLI output.
+ """
+ catch: list[str] = ffi.from_handle(catch_reference)
+ catch.append(ffi.string(output).decode("utf-8"))
+
+
+def new( # noqa: PLR0913
+ *,
+ x: int | None = None,
+ y: int | None = None,
+ width: int | None = None,
+ height: int | None = None,
+ columns: int | None = None,
+ rows: int | None = None,
+ renderer: int | None = None,
+ tileset: tcod.tileset.Tileset | None = None,
+ vsync: bool = True,
+ sdl_window_flags: int | None = None,
+ title: str | None = None,
+ argv: Iterable[str] | None = None,
+ console: tcod.console.Console | None = None,
+) -> Context:
+ """Create a new context with the desired pixel size.
+
+ `x`, `y`, `width`, and `height` are the desired position and size of the
+ window. If these are None then they will be derived from `columns` and
+ `rows`. So if you plan on having a console of a fixed size then you should
+ set `columns` and `rows` instead of the window keywords.
+
+ `columns` and `rows` is the desired size of the console. Can be left as
+ `None` when you're setting a context by a window size instead of a console.
+
+ `console` automatically fills in the `columns` and `rows` parameters from an existing :any:`tcod.console.Console`
+ instance.
+
+ Providing no size information at all is also acceptable.
+
+ `renderer` now does nothing and should not be set. It may be removed in the future.
+
+ `tileset` is the font/tileset for the new context to render with.
+ The fall-back tileset available from passing None is useful for
+ prototyping, but will be unreliable across platforms.
+
+ `vsync` is the Vertical Sync option for the window. The default of True
+ is recommended but you may want to use False for benchmarking purposes.
+
+ `sdl_window_flags` is a bit-field of SDL window flags, if None is given
+ then a default of :any:`tcod.context.SDL_WINDOW_RESIZABLE` is used.
+ There's more info on the SDL documentation:
+ https://wiki.libsdl.org/SDL_CreateWindow#Remarks
+
+ `title` is the desired title of the window.
+
+ `argv` these arguments are passed to libtcod and allow an end-user to make
+ last second changes such as forcing fullscreen or windowed mode, or
+ changing the libtcod renderer.
+ By default this will pass in `sys.argv` but you can disable this feature
+ by providing an empty list instead.
+ Certain commands such as ``-help`` will raise a SystemExit exception from
+ this function with the output message.
+
+ When a window size is given instead of a console size then you can use
+ :any:`Context.recommended_console_size` to automatically find the size of
+ the console which should be used.
+
+ .. versionadded:: 11.16
+
+ .. versionchanged:: 13.2
+ Added the `console` parameter.
+ """
+ if renderer is not None:
+ warnings.warn(
+ "The renderer parameter was deprecated and will likely be removed in a future version of libtcod. "
+ "Remove the renderer parameter to fix this warning.",
+ FutureWarning,
+ stacklevel=2,
+ )
+ renderer = RENDERER_SDL2
+ if sdl_window_flags is None:
+ sdl_window_flags = SDL_WINDOW_RESIZABLE
+ if argv is None:
+ argv = sys.argv
+ if console is not None:
+ columns = columns or console.width
+ rows = rows or console.height
+ argv_encoded = [ffi.new("char[]", arg.encode("utf-8")) for arg in argv] # Needs to be kept alive for argv_c.
+ argv_c = ffi.new("char*[]", argv_encoded)
+
+ catch_msg: list[str] = []
+ catch_handle = ffi.new_handle(catch_msg) # Keep alive.
+
+ title_p = _handle_title(title) # Keep alive.
+
+ params = ffi.new(
+ "struct TCOD_ContextParams*",
+ {
+ "tcod_version": lib.TCOD_COMPILEDVERSION,
+ "window_x": x if x is not None else lib.SDL_WINDOWPOS_UNDEFINED,
+ "window_y": y if y is not None else lib.SDL_WINDOWPOS_UNDEFINED,
+ "pixel_width": width or 0,
+ "pixel_height": height or 0,
+ "columns": columns or 0,
+ "rows": rows or 0,
+ "renderer_type": renderer,
+ "tileset": _handle_tileset(tileset),
+ "vsync": vsync,
+ "sdl_window_flags": sdl_window_flags,
+ "window_title": title_p,
+ "argc": len(argv_c),
+ "argv": argv_c,
+ "cli_output": ffi.addressof(lib, "_pycall_cli_output"),
+ "cli_userdata": catch_handle,
+ "window_xy_defined": True,
+ },
+ )
+ context_pp = ffi.new("TCOD_Context**")
+ error = lib.TCOD_context_new(params, context_pp)
+ if error == lib.TCOD_E_REQUIRES_ATTENTION:
+ raise SystemExit(catch_msg[0])
+ _check_warn(error)
+ return Context._claim(context_pp[0])
+
+
+@deprecated("Call tcod.context.new with width and height as keyword parameters.")
+def new_window( # noqa: PLR0913
+ width: int,
+ height: int,
+ *,
+ renderer: int | None = None,
+ tileset: tcod.tileset.Tileset | None = None,
+ vsync: bool = True,
+ sdl_window_flags: int | None = None,
+ title: str | None = None,
+) -> Context:
+ """Create a new context with the desired pixel size.
+
+ .. deprecated:: 11.16
+ :any:`tcod.context.new` provides more options, such as window position.
+ """
+ return new(
+ width=width,
+ height=height,
+ renderer=renderer,
+ tileset=tileset,
+ vsync=vsync,
+ sdl_window_flags=sdl_window_flags,
+ title=title,
+ )
+
+
+@deprecated("Call tcod.context.new with columns and rows as keyword parameters.")
+def new_terminal( # noqa: PLR0913
+ columns: int,
+ rows: int,
+ *,
+ renderer: int | None = None,
+ tileset: tcod.tileset.Tileset | None = None,
+ vsync: bool = True,
+ sdl_window_flags: int | None = None,
+ title: str | None = None,
+) -> Context:
+ """Create a new context with the desired console size.
+
+ .. deprecated:: 11.16
+ :any:`tcod.context.new` provides more options.
+ """
+ return new(
+ columns=columns,
+ rows=rows,
+ renderer=renderer,
+ tileset=tileset,
+ vsync=vsync,
+ sdl_window_flags=sdl_window_flags,
+ title=title,
+ )
diff --git a/tcod/event.py b/tcod/event.py
new file mode 100644
index 00000000..6b9453cf
--- /dev/null
+++ b/tcod/event.py
@@ -0,0 +1,3232 @@
+"""A light-weight implementation of event handling built on calls to SDL.
+
+Many event constants are derived directly from SDL.
+For example: ``tcod.event.KeySym.UP`` and ``tcod.event.Scancode.A`` refer to
+SDL's ``SDLK_UP`` and ``SDL_SCANCODE_A`` respectfully.
+`See this table for all of SDL's keyboard constants.
+`_
+
+Printing any event will tell you its attributes in a human readable format.
+An events type attribute if omitted is just the classes name with all letters upper-case.
+
+As a general guideline, you should use :any:`KeyboardEvent.sym` for command inputs,
+and :any:`TextInput.text` for name entry fields.
+
+Example::
+
+ import tcod
+
+ KEY_COMMANDS = {
+ tcod.event.KeySym.UP: "move N",
+ tcod.event.KeySym.DOWN: "move S",
+ tcod.event.KeySym.LEFT: "move W",
+ tcod.event.KeySym.RIGHT: "move E",
+ }
+
+ context = tcod.context.new()
+ while True:
+ console = context.new_console()
+ context.present(console, integer_scaling=True)
+ for pixel_event in tcod.event.wait():
+ event = context.convert_event(pixel_event) # Convert mouse pixel coordinates to tile coordinates
+ print(event) # Print all events, for learning and debugging
+ if isinstance(event, tcod.event.Quit):
+ raise SystemExit()
+ elif isinstance(event, tcod.event.KeyDown):
+ print(f"{event.sym=}, {event.scancode=}") # Show Scancode and KeySym enum names
+ if event.sym in KEY_COMMANDS:
+ print(f"Command: {KEY_COMMANDS[event.sym]}")
+ elif isinstance(event, tcod.event.MouseButtonDown):
+ print(f"{event.button=}, {event.integer_position=}") # Show mouse button and tile
+ elif isinstance(event, tcod.event.MouseMotion):
+ print(f"{event.integer_position=}, {event.integer_motion=}") # Current mouse tile and tile motion
+
+Python 3.10 introduced `match statements `_
+which can be used to dispatch events more gracefully:
+
+Example::
+
+ import tcod
+
+ KEY_COMMANDS = {
+ tcod.event.KeySym.UP: "move N",
+ tcod.event.KeySym.DOWN: "move S",
+ tcod.event.KeySym.LEFT: "move W",
+ tcod.event.KeySym.RIGHT: "move E",
+ }
+
+ context = tcod.context.new()
+ while True:
+ console = context.new_console()
+ context.present(console, integer_scaling=True)
+ for pixel_event in tcod.event.wait():
+ event = context.convert_event(pixel_event) # Converts mouse pixel coordinates to tile coordinates.
+ match event:
+ case tcod.event.Quit():
+ raise SystemExit()
+ case tcod.event.KeyDown(sym=sym) if sym in KEY_COMMANDS:
+ print(f"Command: {KEY_COMMANDS[sym]}")
+ case tcod.event.KeyDown(sym=sym, scancode=scancode, mod=mod, repeat=repeat):
+ print(f"KeyDown: {sym=}, {scancode=}, {mod=}, {repeat=}")
+ case tcod.event.MouseButtonDown(button=button, integer_position=tile):
+ print(f"MouseButtonDown: {button=}, {tile=}")
+ case tcod.event.MouseMotion(integer_position=tile, integer_motion=tile_motion):
+ assert isinstance(pixel_event, tcod.event.MouseMotion)
+ pixel_motion = pixel_event.motion
+ print(f"MouseMotion: {pixel_motion=}, {tile=}, {tile_motion=}")
+ case tcod.event.Event() as event:
+ print(event) # Print unhandled events
+
+.. versionadded:: 8.4
+"""
+
+from __future__ import annotations
+
+import enum
+import functools
+import sys
+import warnings
+from collections.abc import Callable, Iterator, Mapping
+from math import floor
+from pathlib import Path
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Final,
+ Generic,
+ Literal,
+ NamedTuple,
+ Protocol,
+ TypeAlias,
+ TypedDict,
+ TypeVar,
+ overload,
+ runtime_checkable,
+)
+
+import attrs
+import numpy as np
+from typing_extensions import Self, deprecated
+
+import tcod.context
+import tcod.event_constants
+import tcod.sdl.joystick
+import tcod.sdl.render
+import tcod.sdl.sys
+from tcod.cffi import ffi, lib
+from tcod.event_constants import * # noqa: F403
+from tcod.sdl.joystick import _HAT_DIRECTIONS
+
+if TYPE_CHECKING:
+ from numpy.typing import NDArray
+
+T = TypeVar("T")
+_EventType = TypeVar("_EventType", bound="Event")
+
+_C_SDL_Event: TypeAlias = Any
+"""A CFFI pointer to an SDL_Event union.
+
+See SDL docs: https://wiki.libsdl.org/SDL3/SDL_Event
+"""
+
+
+class _ConstantsWithPrefix(Mapping[int, str]):
+ def __init__(self, constants: Mapping[int, str]) -> None:
+ self.constants = constants
+
+ def __getitem__(self, key: int) -> str:
+ return "tcod.event." + self.constants[key]
+
+ def __len__(self) -> int:
+ return len(self.constants)
+
+ def __iter__(self) -> Iterator[int]:
+ return iter(self.constants)
+
+
+def _describe_bitmask(bits: int, table: Mapping[int, str], default: str = "0") -> str:
+ """Return a bitmask in human readable form.
+
+ This is a private function, used internally.
+
+ `bits` is the bitmask to be represented.
+
+ `table` is a reverse lookup table.
+
+ `default` is returned when no other bits can be represented.
+ """
+ result = []
+ for bit, name in table.items():
+ if bit & bits:
+ result.append(name)
+ if not result:
+ return default
+ return "|".join(result)
+
+
+def _pixel_to_tile(xy: tuple[float, float], /) -> Point[float] | None:
+ """Convert pixel coordinates to tile coordinates."""
+ if not lib.TCOD_ctx.engine:
+ return None
+ xy_out = ffi.new("double[2]", xy)
+ lib.TCOD_sys_pixel_to_tile(xy_out, xy_out + 1)
+ return Point(float(xy_out[0]), float(xy_out[1]))
+
+
+if sys.version_info >= (3, 11) or TYPE_CHECKING:
+
+ class Point(NamedTuple, Generic[T]):
+ """A 2D position used for events with mouse coordinates.
+
+ .. seealso::
+ :any:`MouseMotion` :any:`MouseButtonDown` :any:`MouseButtonUp`
+
+ .. versionchanged:: 19.0
+ Now uses floating point coordinates due to the port to SDL3.
+ """
+
+ x: T
+ """A pixel or tile coordinate starting with zero as the left-most position."""
+ y: T
+ """A pixel or tile coordinate starting with zero as the top-most position."""
+else:
+
+ class Point(NamedTuple): # noqa: D101
+ x: Any
+ y: Any
+
+
+def _verify_tile_coordinates(xy: Point[int] | None) -> Point[int]:
+ """Check if an events tile coordinate is initialized and warn if not.
+
+ Always returns a valid Point object for backwards compatibility.
+ """
+ if xy is not None:
+ return xy
+ warnings.warn(
+ "This events tile coordinates are uninitialized!"
+ "\nYou MUST pass this event to `Context.convert_event` before you can"
+ " read its tile attributes.",
+ RuntimeWarning,
+ stacklevel=3, # Called within other functions, never directly.
+ )
+ return Point(0, 0)
+
+
+def _init_sdl_video() -> None:
+ """Keyboard layout stuff needs SDL to be initialized first."""
+ if lib.SDL_WasInit(lib.SDL_INIT_VIDEO):
+ return
+ lib.SDL_InitSubSystem(lib.SDL_INIT_VIDEO)
+
+
+class Modifier(enum.IntFlag):
+ """Keyboard modifier flags, a bit-field of held modifier keys.
+
+ Use `bitwise and` to check if a modifier key is held.
+
+ The following example shows some common ways of checking modifiers.
+ All non-zero return values are considered true.
+
+ Example::
+
+ >>> import tcod.event
+ >>> mod = tcod.event.Modifier(4098)
+ >>> mod & tcod.event.Modifier.SHIFT # Check if any shift key is held.
+
+ >>> mod & tcod.event.Modifier.LSHIFT # Check if left shift key is held.
+
+ >>> not mod & tcod.event.Modifier.LSHIFT # Check if left shift key is NOT held.
+ True
+ >>> mod & tcod.event.Modifier.SHIFT and mod & tcod.event.Modifier.CTRL # Check if Shift+Control is held.
+
+
+ .. versionadded:: 12.3
+ """
+
+ NONE = 0
+ LSHIFT = 1
+ """Left shift."""
+ RSHIFT = 2
+ """Right shift."""
+ SHIFT = LSHIFT | RSHIFT
+ """LSHIFT | RSHIFT"""
+ LCTRL = 64
+ """Left control."""
+ RCTRL = 128
+ """Right control."""
+ CTRL = LCTRL | RCTRL
+ """LCTRL | RCTRL"""
+ LALT = 256
+ """Left alt."""
+ RALT = 512
+ """Right alt."""
+ ALT = LALT | RALT
+ """LALT | RALT"""
+ LGUI = 1024
+ """Left meta key."""
+ RGUI = 2048
+ """Right meta key."""
+ GUI = LGUI | RGUI
+ """LGUI | RGUI"""
+ NUM = 4096
+ """Numpad lock."""
+ CAPS = 8192
+ """Caps lock."""
+ MODE = 16384
+ """Alt graph."""
+
+
+class MouseButton(enum.IntEnum):
+ """An enum for mouse buttons.
+
+ .. versionadded:: 16.1
+ """
+
+ LEFT = 1
+ """Left mouse button."""
+ MIDDLE = 2
+ """Middle mouse button."""
+ RIGHT = 3
+ """Right mouse button."""
+ X1 = 4
+ """Back mouse button."""
+ X2 = 5
+ """Forward mouse button."""
+
+ def __repr__(self) -> str:
+ """Return the enum name, excluding the value."""
+ return f"{self.__class__.__name__}.{self.name}"
+
+
+class MouseButtonMask(enum.IntFlag):
+ """A mask enum for held mouse buttons.
+
+ .. versionadded:: 16.1
+ """
+
+ LEFT = 0x1
+ """Left mouse button is held."""
+ MIDDLE = 0x2
+ """Middle mouse button is held."""
+ RIGHT = 0x4
+ """Right mouse button is held."""
+ X1 = 0x8
+ """Back mouse button is held."""
+ X2 = 0x10
+ """Forward mouse button is held."""
+
+ def __repr__(self) -> str:
+ """Return the bitwise OR flag combination of this value."""
+ if self.value == 0:
+ return f"{self.__class__.__name__}(0)"
+ return "|".join(f"{self.__class__.__name__}.{self.__class__(bit).name}" for bit in self.__class__ if bit & self)
+
+
+class _CommonSDLEventAttributes(TypedDict):
+ """Common keywords for Event subclasses."""
+
+ sdl_event: _C_SDL_Event
+ timestamp_ns: int
+
+
+def _unpack_sdl_event(sdl_event: _C_SDL_Event) -> _CommonSDLEventAttributes:
+ """Unpack an SDL_Event union into common attributes, such as timestamp."""
+ return {
+ "sdl_event": sdl_event,
+ "timestamp_ns": sdl_event.common.timestamp,
+ }
+
+
+@attrs.define(slots=True, kw_only=True)
+class Event:
+ """The base event class."""
+
+ sdl_event: _C_SDL_Event = attrs.field(default=None, eq=False, repr=False)
+ """Holds a python-cffi ``SDL_Event*`` pointer for this event when available."""
+
+ timestamp_ns: int = attrs.field(default=0, eq=False)
+ """The time of this event in nanoseconds since SDL has been initialized.
+
+ .. seealso::
+ :any:`tcod.event.time_ns`
+
+ .. versionadded:: 21.0
+ """
+
+ @property
+ def timestamp(self) -> float:
+ """The time of this event in seconds since SDL has been initialized.
+
+ .. seealso::
+ :any:`tcod.event.time`
+
+ .. versionadded:: 21.0
+ """
+ return self.timestamp_ns / 1_000_000_000
+
+ @property
+ @deprecated("The Event.type attribute is deprecated, use isinstance instead.")
+ def type(self) -> str:
+ """This events type.
+
+ .. deprecated:: 21.0
+ Using this attribute is now actively discouraged. Use :func:`isinstance` or :ref:`match`.
+
+ :meta private:
+ """
+ type_override: str | None = getattr(self, "_type", None)
+ if type_override is not None:
+ return type_override
+ return self.__class__.__name__.upper()
+
+ @classmethod
+ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Event:
+ """Return a class instance from a python-cffi 'SDL_Event*' pointer.
+
+ .. versionchanged:: 21.0
+ This method was unsuitable for the public API and is now private.
+ """
+ raise NotImplementedError
+
+
+@attrs.define(slots=True, kw_only=True)
+class Quit(Event):
+ """An application quit request event.
+
+ For more info on when this event is triggered see:
+ https://wiki.libsdl.org/SDL_EventType#SDL_QUIT
+ """
+
+ @classmethod
+ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self:
+ return cls(**_unpack_sdl_event(sdl_event))
+
+
+@attrs.define(slots=True, kw_only=True)
+class KeyboardEvent(Event):
+ """Base keyboard event.
+
+ .. versionchanged:: 12.5
+ `scancode`, `sym`, and `mod` now use their respective enums.
+ """
+
+ scancode: Scancode
+ """The keyboard scan-code, this is the physical location
+ of the key on the keyboard rather than the keys symbol."""
+ sym: KeySym
+ """The keyboard symbol."""
+ mod: Modifier
+ """A bitmask of the currently held modifier keys.
+
+ For example, if shift is held then
+ ``event.mod & tcod.event.Modifier.SHIFT`` will evaluate to a true
+ value.
+ """
+ repeat: bool = False
+ """True if this event exists because of key repeat."""
+ which: int = 0
+ """The SDL keyboard instance ID. Zero if unknown or virtual.
+
+ .. versionadded:: 21.0
+ """
+ window_id: int = 0
+ """The SDL window ID with keyboard focus.
+
+ .. versionadded:: 21.0
+ """
+ pressed: bool = False
+ """True if the key was pressed, False if the key was released.
+
+ .. versionadded:: 21.0
+ """
+
+ @classmethod
+ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self:
+ keysym = sdl_event.key
+ return cls(
+ scancode=Scancode(keysym.scancode),
+ sym=KeySym(keysym.key),
+ mod=Modifier(keysym.mod),
+ repeat=bool(keysym.repeat),
+ pressed=bool(keysym.down),
+ which=int(keysym.which),
+ window_id=int(keysym.windowID),
+ **_unpack_sdl_event(sdl_event),
+ )
+
+
+@attrs.define(slots=True, kw_only=True)
+class KeyDown(KeyboardEvent):
+ """A :any:`KeyboardEvent` where the key was pressed."""
+
+
+@attrs.define(slots=True, kw_only=True)
+class KeyUp(KeyboardEvent):
+ """A :any:`KeyboardEvent` where the key was released."""
+
+
+@attrs.define(slots=True, kw_only=True)
+class MouseState(Event):
+ """Mouse state.
+
+ .. versionadded:: 9.3
+
+ .. versionchanged:: 15.0
+ Renamed `pixel` attribute to `position`.
+ """
+
+ position: Point[float] = attrs.field(default=Point(0.0, 0.0))
+ """The position coordinates of the mouse."""
+ _tile: Point[int] | None = attrs.field(default=Point(0, 0), alias="tile")
+
+ state: MouseButtonMask = attrs.field(default=MouseButtonMask(0))
+ """A bitmask of which mouse buttons are currently held."""
+
+ which: int = 0
+ """The mouse device ID for this event.
+
+ .. versionadded:: 21.0
+ """
+
+ window_id: int = 0
+ """The window ID with mouse focus.
+
+ .. versionadded:: 21.0
+ """
+
+ @property
+ def integer_position(self) -> Point[int]:
+ """Integer coordinates of this event.
+
+ .. versionadded:: 21.0
+ """
+ x, y = self.position
+ return Point(floor(x), floor(y))
+
+ @property
+ @deprecated("The mouse.pixel attribute is deprecated. Use mouse.position instead.")
+ def pixel(self) -> Point[float]: # noqa: D102 # Skip docstring for deprecated attribute
+ return self.position
+
+ @pixel.setter
+ def pixel(self, value: Point[float]) -> None:
+ self.position = value
+
+ @property
+ @deprecated(
+ "The mouse.tile attribute is deprecated."
+ " Use mouse.integer_position of the event returned by context.convert_event instead."
+ )
+ def tile(self) -> Point[int]:
+ """The integer tile coordinates of the mouse on the screen.
+
+ .. deprecated:: 21.0
+ Use :any:`integer_position` of the event returned by :any:`Context.convert_event` instead.
+ """
+ return _verify_tile_coordinates(self._tile)
+
+ @tile.setter
+ @deprecated(
+ "The mouse.tile attribute is deprecated."
+ " Use mouse.integer_position of the event returned by context.convert_event instead."
+ )
+ def tile(self, xy: tuple[int, int]) -> None:
+ self._tile = Point(*xy)
+
+
+@attrs.define(slots=True, kw_only=True)
+class MouseMotion(MouseState):
+ """Mouse motion event.
+
+ .. versionchanged:: 15.0
+ Renamed `pixel` attribute to `position`.
+ Renamed `pixel_motion` attribute to `motion`.
+
+ .. versionchanged:: 19.0
+ `position` and `motion` now use floating point coordinates.
+ """
+
+ motion: Point[float] = attrs.field(default=Point(0.0, 0.0))
+ """The pixel delta."""
+ _tile_motion: Point[int] | None = attrs.field(default=Point(0, 0), alias="tile_motion")
+
+ @property
+ def integer_motion(self) -> Point[int]:
+ """Integer motion of this event.
+
+ .. versionadded:: 21.0
+ """
+ x, y = self.position
+ dx, dy = self.motion
+ prev_x, prev_y = x - dx, y - dy
+ return Point(floor(x) - floor(prev_x), floor(y) - floor(prev_y))
+
+ @property
+ @deprecated("The mouse.pixel_motion attribute is deprecated. Use mouse.motion instead.")
+ def pixel_motion(self) -> Point[float]: # noqa: D102 # Skip docstring for deprecated attribute
+ return self.motion
+
+ @pixel_motion.setter
+ @deprecated("The mouse.pixel_motion attribute is deprecated. Use mouse.motion instead.")
+ def pixel_motion(self, value: Point[float]) -> None:
+ self.motion = value
+
+ @property
+ @deprecated(
+ "The mouse.tile_motion attribute is deprecated."
+ " Use mouse.integer_motion of the event returned by context.convert_event instead."
+ )
+ def tile_motion(self) -> Point[int]:
+ """The tile delta.
+
+ .. deprecated:: 21.0
+ Use :any:`integer_motion` of the event returned by :any:`Context.convert_event` instead.
+ """
+ return _verify_tile_coordinates(self._tile_motion)
+
+ @tile_motion.setter
+ @deprecated(
+ "The mouse.tile_motion attribute is deprecated."
+ " Use mouse.integer_motion of the event returned by context.convert_event instead."
+ )
+ def tile_motion(self, xy: tuple[int, int]) -> None:
+ self._tile_motion = Point(*xy)
+
+ @classmethod
+ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self:
+ motion = sdl_event.motion
+ common = {"which": int(motion.which), "window_id": int(motion.windowID)}
+ state = MouseButtonMask(motion.state)
+
+ pixel = Point(float(motion.x), float(motion.y))
+ pixel_motion = Point(float(motion.xrel), float(motion.yrel))
+ subtile = _pixel_to_tile(pixel)
+ if subtile is None:
+ self = cls(
+ position=pixel,
+ motion=pixel_motion,
+ tile=None,
+ tile_motion=None,
+ state=state,
+ **common,
+ **_unpack_sdl_event(sdl_event),
+ )
+ else:
+ tile = Point(floor(subtile[0]), floor(subtile[1]))
+ prev_pixel = (pixel[0] - pixel_motion[0], pixel[1] - pixel_motion[1])
+ prev_subtile = _pixel_to_tile(prev_pixel) or (0, 0)
+ prev_tile = floor(prev_subtile[0]), floor(prev_subtile[1])
+ tile_motion = Point(tile[0] - prev_tile[0], tile[1] - prev_tile[1])
+ self = cls(
+ position=pixel,
+ motion=pixel_motion,
+ tile=tile,
+ tile_motion=tile_motion,
+ state=state,
+ **common,
+ **_unpack_sdl_event(sdl_event),
+ )
+ self.sdl_event = sdl_event
+ return self
+
+
+@attrs.define(slots=True, kw_only=True)
+class MouseButtonEvent(Event):
+ """Mouse button event.
+
+ .. versionchanged:: 19.0
+ `position` and `tile` now use floating point coordinates.
+
+ .. versionchanged:: 21.0
+ No longer a subclass of :any:`MouseState`.
+ """
+
+ position: Point[float] = attrs.field(default=Point(0.0, 0.0))
+ """The coordinates of the mouse."""
+ _tile: Point[int] | None = attrs.field(default=Point(0, 0), alias="tile")
+ """The tile integer coordinates of the mouse on the screen. Deprecated."""
+ button: MouseButton
+ """Which mouse button index was pressed or released in this event.
+
+ .. versionchanged:: 21.0
+ Is now strictly a :any:`MouseButton` type.
+ """
+
+ which: int = 0
+ """The mouse device ID for this event.
+
+ .. versionadded:: 21.0
+ """
+
+ window_id: int = 0
+ """The window ID with mouse focus.
+
+ .. versionadded:: 21.0
+ """
+
+ @property
+ def integer_position(self) -> Point[int]:
+ """Integer coordinates of this event.
+
+ .. versionadded:: 21.1
+ """
+ x, y = self.position
+ return Point(floor(x), floor(y))
+
+ @classmethod
+ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self:
+ button = sdl_event.button
+ pixel = Point(float(button.x), float(button.y))
+ subtile = _pixel_to_tile(pixel)
+ if subtile is None:
+ tile: Point[int] | None = None
+ else:
+ tile = Point(floor(subtile[0]), floor(subtile[1]))
+ self = cls(
+ position=pixel,
+ tile=tile,
+ button=MouseButton(button.button),
+ which=int(button.which),
+ window_id=int(button.windowID),
+ **_unpack_sdl_event(sdl_event),
+ )
+ self.sdl_event = sdl_event
+ return self
+
+ @property
+ @deprecated(
+ "This attribute is for mouse state and mouse motion only. Use `event.button` instead.", category=FutureWarning
+ )
+ def state(self) -> int: # noqa: D102 # Skip docstring for deprecated property
+ return int(self.button)
+
+
+@attrs.define(slots=True, kw_only=True)
+class MouseButtonDown(MouseButtonEvent):
+ """Mouse button has been pressed."""
+
+
+@attrs.define(slots=True, kw_only=True)
+class MouseButtonUp(MouseButtonEvent):
+ """Mouse button has been released."""
+
+
+@attrs.define(slots=True, kw_only=True)
+class MouseWheel(Event):
+ """Mouse wheel event."""
+
+ x: int
+ """Horizontal scrolling. A positive value means scrolling right."""
+ y: int
+ """Vertical scrolling. A positive value means scrolling away from the user."""
+ flipped: bool
+ """If True then the values of `x` and `y` are the opposite of their usual values.
+ This depends on the operating system settings.
+ """
+
+ position: Point[float] = attrs.field(default=Point(0.0, 0.0))
+ """Coordinates of the mouse for this event.
+
+ .. versionadded:: 21.2
+ """
+
+ which: int = 0
+ """Mouse device ID for this event.
+
+ .. versionadded:: 21.2
+ """
+
+ window_id: int = 0
+ """Window ID with mouse focus.
+
+ .. versionadded:: 21.2
+ """
+
+ @property
+ def integer_position(self) -> Point[int]:
+ """Integer coordinates of this event.
+
+ .. versionadded:: 21.2
+ """
+ x, y = self.position
+ return Point(floor(x), floor(y))
+
+ @classmethod
+ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self:
+ wheel = sdl_event.wheel
+ return cls(
+ x=int(wheel.integer_x),
+ y=int(wheel.integer_y),
+ flipped=bool(wheel.direction),
+ position=Point(float(wheel.mouse_x), float(wheel.mouse_y)),
+ which=int(wheel.which),
+ window_id=int(wheel.windowID),
+ **_unpack_sdl_event(sdl_event),
+ )
+
+
+@runtime_checkable
+class _MouseEventWithPosition(Protocol):
+ """Mouse event with position. Used internally to handle conversions."""
+
+ position: Point[float]
+
+
+@runtime_checkable
+class _MouseEventWithTile(Protocol):
+ """Mouse event with position and deprecated tile attribute. Used internally to handle conversions."""
+
+ position: Point[float]
+ _tile: Point[int] | None
+
+
+@attrs.define(slots=True, kw_only=True)
+class TextInput(Event):
+ """SDL text input event.
+
+ .. warning::
+ These events are not enabled by default since `19.0`.
+
+ Use :any:`Window.start_text_input` to enable this event.
+ """
+
+ text: str
+ """A Unicode string with the input."""
+
+ @classmethod
+ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self:
+ return cls(text=str(ffi.string(sdl_event.text.text, 32), encoding="utf8"), **_unpack_sdl_event(sdl_event))
+
+
+_WindowTypes = Literal[
+ "WindowShown",
+ "WindowHidden",
+ "WindowExposed",
+ "WindowMoved",
+ "WindowResized",
+ "PixelSizeChanged",
+ "MetalViewResized",
+ "WindowMinimized",
+ "WindowMaximized",
+ "WindowRestored",
+ "WindowEnter",
+ "WindowLeave",
+ "WindowFocusGained",
+ "WindowFocusLost",
+ "WindowClose",
+ "WindowTakeFocus",
+ "WindowHitTest",
+ "ICCProfileChanged",
+ "DisplayChanged",
+ "DisplayScaleChanged",
+ "SafeAreaChanged",
+ "Occluded",
+ "EnterFullscreen",
+ "LeaveFullscreen",
+ "Destroyed",
+ "HDRStateChanged",
+]
+
+
+@attrs.define(slots=True, kw_only=True)
+class WindowEvent(Event):
+ """A window event.
+
+ Example::
+
+ match event:
+ case tcod.event.WindowEvent(type="WindowShown", window_id=window_id):
+ print(f"Window {window_id} was shown")
+ case tcod.event.WindowEvent(type="WindowHidden", window_id=window_id):
+ print(f"Window {window_id} was hidden")
+ case tcod.event.WindowEvent(type="WindowExposed", window_id=window_id):
+ print(f"Window {window_id} was exposed and needs to be redrawn")
+ case tcod.event.WindowEvent(type="WindowMoved", data=(x, y), window_id=window_id):
+ print(f"Window {window_id} was moved to {x=},{y=}")
+ case tcod.event.WindowEvent(type="WindowResized", data=(width, height), window_id=window_id):
+ print(f"Window {window_id} was resized to {width=},{height=}")
+ case tcod.event.WindowEvent(type="WindowMinimized", window_id=window_id):
+ print(f"Window {window_id} was minimized")
+ case tcod.event.WindowEvent(type="WindowMaximized", window_id=window_id):
+ print(f"Window {window_id} was maximized")
+ case tcod.event.WindowEvent(type="WindowRestored", window_id=window_id):
+ print(f"Window {window_id} was restored")
+ case tcod.event.WindowEvent(type="WindowEnter", window_id=window_id):
+ print(f"Mouse cursor has entered window {window_id}")
+ case tcod.event.WindowEvent(type="WindowLeave", window_id=window_id):
+ print(f"Mouse cursor has left window {window_id}")
+ case tcod.event.WindowEvent(type="WindowFocusGained", window_id=window_id):
+ print(f"Window {window_id} has gained keyboard focus")
+ case tcod.event.WindowEvent(type="WindowFocusLost", window_id=window_id):
+ print(f"Window {window_id} has lost keyboard focus")
+ case tcod.event.WindowEvent(type="WindowClose", window_id=window_id):
+ print(f"Window {window_id} has been closed")
+ case tcod.event.WindowEvent(type="DisplayChanged", data=(display_id, _), window_id=window_id):
+ print(f"Window {window_id} has been moved to display {display_id}")
+ case tcod.event.WindowEvent(type=subtype, data=data, window_id=window_id):
+ print(f"Other window event {subtype} on window {window_id} with {data=}")
+
+ .. versionchanged:: 21.0
+ Added `data` and `window_id` attributes and added missing SDL3 window events.
+ """
+
+ type: Final[_WindowTypes]
+ """The current window event. This can be one of various options."""
+
+ window_id: int
+ """The SDL window ID associated with this event."""
+
+ data: tuple[int, int]
+ """The SDL data associated with this event. What these values are for depends on the event sub-type."""
+
+ @classmethod
+ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> WindowEvent | Undefined:
+ if sdl_event.type not in _WINDOW_TYPES_FROM_ENUM:
+ return Undefined._from_sdl_event(sdl_event)
+ event_type: Final = _WINDOW_TYPES_FROM_ENUM[sdl_event.type]
+ new_cls = cls
+ if sdl_event.type == lib.SDL_EVENT_WINDOW_MOVED:
+ new_cls = WindowMoved
+ elif sdl_event.type == lib.SDL_EVENT_WINDOW_RESIZED:
+ new_cls = WindowResized
+ return new_cls(
+ type=event_type,
+ window_id=int(sdl_event.window.windowID),
+ data=(int(sdl_event.window.data1), int(sdl_event.window.data2)),
+ **_unpack_sdl_event(sdl_event),
+ )
+
+
+_WINDOW_TYPES_FROM_ENUM: Final[dict[int, _WindowTypes]] = {
+ lib.SDL_EVENT_WINDOW_SHOWN: "WindowShown",
+ lib.SDL_EVENT_WINDOW_HIDDEN: "WindowHidden",
+ lib.SDL_EVENT_WINDOW_EXPOSED: "WindowExposed",
+ lib.SDL_EVENT_WINDOW_MOVED: "WindowMoved",
+ lib.SDL_EVENT_WINDOW_RESIZED: "WindowResized",
+ lib.SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: "PixelSizeChanged",
+ lib.SDL_EVENT_WINDOW_METAL_VIEW_RESIZED: "MetalViewResized",
+ lib.SDL_EVENT_WINDOW_MINIMIZED: "WindowMinimized",
+ lib.SDL_EVENT_WINDOW_MAXIMIZED: "WindowMaximized",
+ lib.SDL_EVENT_WINDOW_RESTORED: "WindowRestored",
+ lib.SDL_EVENT_WINDOW_MOUSE_ENTER: "WindowEnter",
+ lib.SDL_EVENT_WINDOW_MOUSE_LEAVE: "WindowLeave",
+ lib.SDL_EVENT_WINDOW_FOCUS_GAINED: "WindowFocusGained",
+ lib.SDL_EVENT_WINDOW_FOCUS_LOST: "WindowFocusLost",
+ lib.SDL_EVENT_WINDOW_CLOSE_REQUESTED: "WindowClose",
+ lib.SDL_EVENT_WINDOW_HIT_TEST: "WindowHitTest",
+ lib.SDL_EVENT_WINDOW_ICCPROF_CHANGED: "ICCProfileChanged",
+ lib.SDL_EVENT_WINDOW_DISPLAY_CHANGED: "DisplayChanged",
+ lib.SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED: "DisplayScaleChanged",
+ lib.SDL_EVENT_WINDOW_SAFE_AREA_CHANGED: "SafeAreaChanged",
+ lib.SDL_EVENT_WINDOW_OCCLUDED: "Occluded",
+ lib.SDL_EVENT_WINDOW_ENTER_FULLSCREEN: "EnterFullscreen",
+ lib.SDL_EVENT_WINDOW_LEAVE_FULLSCREEN: "LeaveFullscreen",
+ lib.SDL_EVENT_WINDOW_DESTROYED: "Destroyed",
+ lib.SDL_EVENT_WINDOW_HDR_STATE_CHANGED: "HDRStateChanged",
+}
+
+
+@attrs.define(slots=True, kw_only=True)
+class WindowMoved(WindowEvent):
+ """Window moved event."""
+
+ @property
+ def x(self) -> int:
+ """Movement on the x-axis."""
+ return self.data[0]
+
+ @property
+ def y(self) -> int:
+ """Movement on the y-axis."""
+ return self.data[1]
+
+
+@attrs.define(slots=True, kw_only=True)
+class WindowResized(WindowEvent):
+ """Window resized event.
+
+ .. versionchanged:: 19.4
+ Removed "WindowSizeChanged" type.
+ """
+
+ @property
+ def width(self) -> int:
+ """The current width of the window."""
+ return self.data[0]
+
+ @property
+ def height(self) -> int:
+ """The current height of the window."""
+ return self.data[1]
+
+
+@attrs.define(slots=True, kw_only=True)
+class JoystickEvent(Event):
+ """A base class for joystick events.
+
+ .. versionadded:: 13.8
+ """
+
+ which: int
+ """The ID of the joystick this event is for."""
+
+ @property
+ def joystick(self) -> tcod.sdl.joystick.Joystick:
+ """The :any:`Joystick` for this event."""
+ if isinstance(self, JoystickDevice) and self.type == "JOYDEVICEADDED":
+ return tcod.sdl.joystick.Joystick._open(self.which)
+ return tcod.sdl.joystick.Joystick._from_instance_id(self.which)
+
+
+@attrs.define(slots=True, kw_only=True)
+class JoystickAxis(JoystickEvent):
+ """When a joystick axis changes in value.
+
+ .. versionadded:: 13.8
+
+ .. seealso::
+ :any:`tcod.sdl.joystick`
+ """
+
+ _type: Final[Literal["JOYAXISMOTION"]] = "JOYAXISMOTION"
+
+ axis: int
+ """The index of the changed axis."""
+ value: int
+ """The raw value of the axis in the range -32768 to 32767."""
+
+ @classmethod
+ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self:
+ return cls(
+ which=int(sdl_event.jaxis.which),
+ axis=int(sdl_event.jaxis.axis),
+ value=int(sdl_event.jaxis.value),
+ **_unpack_sdl_event(sdl_event),
+ )
+
+
+@attrs.define(slots=True, kw_only=True)
+class JoystickBall(JoystickEvent):
+ """When a joystick ball is moved.
+
+ .. versionadded:: 13.8
+
+ .. seealso::
+ :any:`tcod.sdl.joystick`
+ """
+
+ _type: Final[Literal["JOYBALLMOTION"]] = "JOYBALLMOTION"
+
+ ball: int
+ """The index of the moved ball."""
+ dx: int
+ """The X motion of the ball."""
+ dy: int
+ """The Y motion of the ball."""
+
+ @classmethod
+ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self:
+ return cls(
+ which=int(sdl_event.jball.which),
+ ball=int(sdl_event.jball.ball),
+ dx=int(sdl_event.jball.xrel),
+ dy=int(sdl_event.jball.yrel),
+ **_unpack_sdl_event(sdl_event),
+ )
+
+
+@attrs.define(slots=True, kw_only=True)
+class JoystickHat(JoystickEvent):
+ """When a joystick hat changes direction.
+
+ .. versionadded:: 13.8
+
+ .. seealso::
+ :any:`tcod.sdl.joystick`
+ """
+
+ _type: Final[Literal["JOYHATMOTION"]] = "JOYHATMOTION"
+
+ x: Literal[-1, 0, 1]
+ """The new X direction of the hat."""
+ y: Literal[-1, 0, 1]
+ """The new Y direction of the hat."""
+
+ @classmethod
+ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self:
+ x, y = _HAT_DIRECTIONS[sdl_event.jhat.hat]
+ return cls(which=int(sdl_event.jhat.which), x=x, y=y, **_unpack_sdl_event(sdl_event))
+
+
+@attrs.define(slots=True, kw_only=True)
+class JoystickButton(JoystickEvent):
+ """When a joystick button is pressed or released.
+
+ .. versionadded:: 13.8
+
+ Example::
+
+ for event in tcod.event.get():
+ match event:
+ case JoystickButton(which=which, button=button, pressed=True):
+ print(f"Pressed {button=} on controller {which}.")
+ case JoystickButton(which=which, button=button, pressed=False):
+ print(f"Released {button=} on controller {which}.")
+ """
+
+ button: int
+ """The index of the button this event is for."""
+ pressed: bool
+ """True if the button was pressed, False if the button was released."""
+
+ @property
+ @deprecated("Check 'JoystickButton.pressed' instead of '.type'.")
+ def type(self) -> Literal["JOYBUTTONUP", "JOYBUTTONDOWN"]:
+ """Button state as a string.
+
+ .. deprecated:: 21.0
+ Use :any:`pressed` instead.
+ """
+ return ("JOYBUTTONUP", "JOYBUTTONDOWN")[self.pressed]
+
+ @classmethod
+ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self:
+ return cls(
+ which=int(sdl_event.jbutton.which),
+ button=int(sdl_event.jbutton.button),
+ pressed=bool(sdl_event.jbutton.down),
+ **_unpack_sdl_event(sdl_event),
+ )
+
+
+@attrs.define(slots=True, kw_only=True)
+class JoystickDevice(JoystickEvent):
+ """An event for when a joystick is added or removed.
+
+ .. versionadded:: 13.8
+
+ Example::
+
+ joysticks: set[tcod.sdl.joystick.Joystick] = {}
+ for event in tcod.event.get():
+ match event:
+ case tcod.event.JoystickDevice(type="JOYDEVICEADDED", joystick=new_joystick):
+ joysticks.add(new_joystick)
+ case tcod.event.JoystickDevice(type="JOYDEVICEREMOVED", joystick=joystick):
+ joysticks.remove(joystick)
+ """
+
+ type: Final[Literal["JOYDEVICEADDED", "JOYDEVICEREMOVED"]]
+
+ which: int
+ """When type="JOYDEVICEADDED" this is the device ID.
+ When type="JOYDEVICEREMOVED" this is the instance ID.
+ """
+
+ @classmethod
+ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self:
+ types: Final[dict[int, Literal["JOYDEVICEADDED", "JOYDEVICEREMOVED"]]] = {
+ lib.SDL_EVENT_JOYSTICK_ADDED: "JOYDEVICEADDED",
+ lib.SDL_EVENT_JOYSTICK_REMOVED: "JOYDEVICEREMOVED",
+ }
+ return cls(type=types[sdl_event.type], which=int(sdl_event.jdevice.which), **_unpack_sdl_event(sdl_event))
+
+
+@attrs.define(slots=True, kw_only=True)
+class ControllerEvent(Event):
+ """Base class for controller events.
+
+ .. versionadded:: 13.8
+ """
+
+ which: int
+ """The ID of the controller this event is for."""
+
+ @property
+ def controller(self) -> tcod.sdl.joystick.GameController:
+ """The :any:`GameController` for this event."""
+ if isinstance(self, ControllerDevice) and self.type == "CONTROLLERDEVICEADDED":
+ return tcod.sdl.joystick.GameController._open(self.which)
+ return tcod.sdl.joystick.GameController._from_instance_id(self.which)
+
+
+@attrs.define(slots=True, kw_only=True)
+class ControllerAxis(ControllerEvent):
+ """When a controller axis is moved.
+
+ .. versionadded:: 13.8
+ """
+
+ _type: Final[Literal["CONTROLLERAXISMOTION"]] = "CONTROLLERAXISMOTION"
+
+ axis: int
+ """Which axis is being moved. One of :any:`ControllerAxis`."""
+ value: int
+ """The new value of this events axis.
+
+ This will be -32768 to 32767 for all axes except for triggers which are 0 to 32767 instead."""
+
+ @classmethod
+ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self:
+ return cls(
+ which=int(sdl_event.gaxis.which),
+ axis=tcod.sdl.joystick.ControllerAxis(sdl_event.gaxis.axis),
+ value=int(sdl_event.gaxis.value),
+ **_unpack_sdl_event(sdl_event),
+ )
+
+
+@attrs.define(slots=True, kw_only=True)
+class ControllerButton(ControllerEvent):
+ """When a controller button is pressed or released.
+
+ .. versionadded:: 13.8
+ """
+
+ button: tcod.sdl.joystick.ControllerButton
+ """The button for this event. One of :any:`ControllerButton`."""
+ pressed: bool
+ """True if the button was pressed, False if it was released."""
+
+ @property
+ @deprecated("Check 'ControllerButton.pressed' instead of '.type'.")
+ def type(self) -> Literal["CONTROLLERBUTTONUP", "CONTROLLERBUTTONDOWN"]:
+ """Button state as a string.
+
+ .. deprecated:: 21.0
+ Use :any:`pressed` instead.
+ """
+ return ("CONTROLLERBUTTONUP", "CONTROLLERBUTTONDOWN")[self.pressed]
+
+ @classmethod
+ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self:
+ return cls(
+ which=int(sdl_event.gbutton.which),
+ button=tcod.sdl.joystick.ControllerButton(sdl_event.gbutton.button),
+ pressed=bool(sdl_event.gbutton.down),
+ **_unpack_sdl_event(sdl_event),
+ )
+
+
+@attrs.define(slots=True, kw_only=True)
+class ControllerDevice(ControllerEvent):
+ """When a controller is added, removed, or remapped.
+
+ .. versionadded:: 13.8
+ """
+
+ type: Final[Literal["CONTROLLERDEVICEADDED", "CONTROLLERDEVICEREMOVED", "CONTROLLERDEVICEREMAPPED"]]
+
+ @classmethod
+ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self:
+ types: dict[int, Literal["CONTROLLERDEVICEADDED", "CONTROLLERDEVICEREMOVED", "CONTROLLERDEVICEREMAPPED"]] = {
+ lib.SDL_EVENT_GAMEPAD_ADDED: "CONTROLLERDEVICEADDED",
+ lib.SDL_EVENT_GAMEPAD_REMOVED: "CONTROLLERDEVICEREMOVED",
+ lib.SDL_EVENT_GAMEPAD_REMAPPED: "CONTROLLERDEVICEREMAPPED",
+ }
+ return cls(type=types[sdl_event.type], which=int(sdl_event.gdevice.which), **_unpack_sdl_event(sdl_event))
+
+
+@attrs.define(slots=True, kw_only=True)
+class ClipboardUpdate(Event):
+ """Announces changed contents of the clipboard.
+
+ .. versionadded:: 21.0
+ """
+
+ mime_types: tuple[str, ...]
+ """The MIME types of the clipboard."""
+
+ @classmethod
+ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self:
+ return cls(
+ mime_types=tuple(
+ str(ffi.string(sdl_event.clipboard.mime_types[i]), encoding="utf8")
+ for i in range(sdl_event.clipboard.num_mime_types)
+ ),
+ **_unpack_sdl_event(sdl_event),
+ )
+
+
+@attrs.define(slots=True, kw_only=True)
+class Drop(Event):
+ """Handle dropping text or files on the window.
+
+ Example::
+
+ match event:
+ case tcod.event.Drop(type="BEGIN"):
+ print("Object dragged over the window")
+ case tcod.event.Drop(type="POSITION", position=position):
+ pass
+ case tcod.event.Drop(type="TEXT", position=position, text=text):
+ print(f"Dropped {text=} at {position=}")
+ case tcod.event.Drop(type="FILE", position=position, path=path):
+ print(f"Dropped {path=} at {position=}")
+ case tcod.event.Drop(type="COMPLETE"):
+ print("Drop handling finished")
+
+ .. versionadded:: 21.0
+ """
+
+ type: Literal["BEGIN", "FILE", "TEXT", "COMPLETE", "POSITION"]
+ """The subtype of this event."""
+ window_id: int
+ """The active window ID for this event."""
+ position: Point[float]
+ """Mouse position relative to the window. Available in all subtypes except for ``type="BEGIN"``."""
+ source: str
+ """The source app for this event, or an empty string if unavailable."""
+ text: str
+ """The dropped data of a ``Drop(type="TEXT")`` or ``Drop(type="FILE")`` event.
+
+ - If ``Drop(type="TEXT")`` then `text` is the dropped string.
+ - If ``Drop(type="FILE")`` then `text` is the str path of the dropped file.
+ Alternatively :any:`path` can be used.
+ - Otherwise `text` is an empty string.
+ """
+
+ @property
+ def path(self) -> Path:
+ """Return the current `text` as a :any:`Path`."""
+ return Path(self.text)
+
+ @classmethod
+ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self:
+ types: dict[int, Literal["BEGIN", "FILE", "TEXT", "COMPLETE", "POSITION"]] = {
+ lib.SDL_EVENT_DROP_BEGIN: "BEGIN",
+ lib.SDL_EVENT_DROP_FILE: "FILE",
+ lib.SDL_EVENT_DROP_TEXT: "TEXT",
+ lib.SDL_EVENT_DROP_COMPLETE: "COMPLETE",
+ lib.SDL_EVENT_DROP_POSITION: "POSITION",
+ }
+ return cls(
+ type=types[sdl_event.drop.type],
+ window_id=int(sdl_event.drop.windowID),
+ position=Point(float(sdl_event.drop.x), float(sdl_event.drop.y)),
+ source=str(ffi.string(sdl_event.drop.source), encoding="utf8") if sdl_event.drop.source else "",
+ text=str(ffi.string(sdl_event.drop.data), encoding="utf8") if sdl_event.drop.data else "",
+ **_unpack_sdl_event(sdl_event),
+ )
+
+
+@functools.cache
+def _find_event_name(index: int, /) -> str:
+ """Return the SDL event name for this index."""
+ for attr in dir(lib):
+ if attr.startswith("SDL_EVENT_") and getattr(lib, attr) == index:
+ return attr
+ return "???"
+
+
+@attrs.define(slots=True, kw_only=True)
+class Undefined(Event):
+ """This class is a place holder for SDL events without their own tcod.event class."""
+
+ @classmethod
+ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self:
+ return cls(**_unpack_sdl_event(sdl_event))
+
+ def __repr__(self) -> str:
+ """Return debug info for this undefined event, including the SDL event name."""
+ return f""
+
+
+_SDL_TO_CLASS_TABLE: dict[int, type[Event]] = {
+ lib.SDL_EVENT_QUIT: Quit,
+ lib.SDL_EVENT_KEY_DOWN: KeyDown,
+ lib.SDL_EVENT_KEY_UP: KeyUp,
+ lib.SDL_EVENT_MOUSE_MOTION: MouseMotion,
+ lib.SDL_EVENT_MOUSE_BUTTON_DOWN: MouseButtonDown,
+ lib.SDL_EVENT_MOUSE_BUTTON_UP: MouseButtonUp,
+ lib.SDL_EVENT_MOUSE_WHEEL: MouseWheel,
+ lib.SDL_EVENT_TEXT_INPUT: TextInput,
+ lib.SDL_EVENT_JOYSTICK_AXIS_MOTION: JoystickAxis,
+ lib.SDL_EVENT_JOYSTICK_BALL_MOTION: JoystickBall,
+ lib.SDL_EVENT_JOYSTICK_HAT_MOTION: JoystickHat,
+ lib.SDL_EVENT_JOYSTICK_BUTTON_DOWN: JoystickButton,
+ lib.SDL_EVENT_JOYSTICK_BUTTON_UP: JoystickButton,
+ lib.SDL_EVENT_JOYSTICK_ADDED: JoystickDevice,
+ lib.SDL_EVENT_JOYSTICK_REMOVED: JoystickDevice,
+ lib.SDL_EVENT_GAMEPAD_AXIS_MOTION: ControllerAxis,
+ lib.SDL_EVENT_GAMEPAD_BUTTON_DOWN: ControllerButton,
+ lib.SDL_EVENT_GAMEPAD_BUTTON_UP: ControllerButton,
+ lib.SDL_EVENT_GAMEPAD_ADDED: ControllerDevice,
+ lib.SDL_EVENT_GAMEPAD_REMOVED: ControllerDevice,
+ lib.SDL_EVENT_GAMEPAD_REMAPPED: ControllerDevice,
+ lib.SDL_EVENT_CLIPBOARD_UPDATE: ClipboardUpdate,
+ lib.SDL_EVENT_DROP_BEGIN: Drop,
+ lib.SDL_EVENT_DROP_FILE: Drop,
+ lib.SDL_EVENT_DROP_TEXT: Drop,
+ lib.SDL_EVENT_DROP_COMPLETE: Drop,
+ lib.SDL_EVENT_DROP_POSITION: Drop,
+}
+
+
+def _parse_event(sdl_event: _C_SDL_Event) -> Event:
+ """Convert a C SDL_Event* type into a tcod Event sub-class."""
+ if sdl_event.type in _SDL_TO_CLASS_TABLE:
+ return _SDL_TO_CLASS_TABLE[sdl_event.type]._from_sdl_event(sdl_event)
+ if sdl_event.type in _WINDOW_TYPES_FROM_ENUM:
+ return WindowEvent._from_sdl_event(sdl_event)
+ return Undefined._from_sdl_event(sdl_event)
+
+
+def get() -> Iterator[Event]:
+ """Return an iterator for all pending events.
+
+ Events are processed as the iterator is consumed.
+ Breaking out of, or discarding the iterator will leave the remaining events on the event queue.
+ It is also safe to call this function inside of a loop that is already handling events
+ (the event iterator is reentrant.)
+ """
+ if not lib.SDL_WasInit(tcod.sdl.sys.Subsystem.EVENTS):
+ warnings.warn(
+ "Events polled before SDL was initialized.",
+ RuntimeWarning,
+ stacklevel=1,
+ )
+ return
+ sdl_event = ffi.new("SDL_Event*")
+ while lib.SDL_PollEvent(sdl_event):
+ yield _parse_event(sdl_event)
+
+
+def wait(timeout: float | None = None) -> Iterator[Event]:
+ """Block until events exist, then return an event iterator.
+
+ `timeout` is the maximum number of seconds to wait as a floating point
+ number with millisecond precision, or it can be None to wait forever.
+
+ Returns the same iterator as a call to :any:`tcod.event.get`.
+
+ This function is useful for simple games with little to no animations.
+ The following example sleeps whenever no events are queued:
+
+ Example::
+
+ context: tcod.context.Context # Context object initialized earlier.
+ while True: # Main game-loop.
+ console: tcod.console.Console # Console used for rendering.
+ ... # Render the frame to `console` and then:
+ context.present(console) # Show the console to the display.
+ # The ordering to draw first before waiting for events is important.
+ for event in tcod.event.wait(): # Sleeps until the next events exist.
+ ... # All events are handled at once before the next frame.
+
+ See :any:`tcod.event.get` examples for how different events are handled.
+ """
+ if timeout is not None:
+ lib.SDL_WaitEventTimeout(ffi.NULL, int(timeout * 1000))
+ else:
+ lib.SDL_WaitEvent(ffi.NULL)
+ return get()
+
+
+@deprecated(
+ """EventDispatch is no longer maintained.
+Event dispatching should be handled via a single custom method in a Protocol instead of this class.""",
+ category=DeprecationWarning,
+)
+class EventDispatch(Generic[T]):
+ '''Dispatches events to methods depending on the events type attribute.
+
+ To use this class, make a sub-class and override the relevant `ev_*` methods.
+ Then send events to the dispatch method.
+
+ .. versionchanged:: 11.12
+ This is now a generic class.
+ The type hints at the return value of :any:`dispatch` and the `ev_*` methods.
+
+ .. deprecated:: 18.0
+ Event dispatch should be handled via a single custom method in a :class:`~typing.Protocol` instead of this class.
+ Note that events can and should be handled using :ref:`match`.
+
+ Example::
+
+ import tcod
+
+ MOVE_KEYS = { # key_symbol: (x, y)
+ # Arrow keys.
+ tcod.event.KeySym.LEFT: (-1, 0),
+ tcod.event.KeySym.RIGHT: (1, 0),
+ tcod.event.KeySym.UP: (0, -1),
+ tcod.event.KeySym.DOWN: (0, 1),
+ tcod.event.KeySym.HOME: (-1, -1),
+ tcod.event.KeySym.END: (-1, 1),
+ tcod.event.KeySym.PAGEUP: (1, -1),
+ tcod.event.KeySym.PAGEDOWN: (1, 1),
+ tcod.event.KeySym.PERIOD: (0, 0),
+ # Numpad keys.
+ tcod.event.KeySym.KP_1: (-1, 1),
+ tcod.event.KeySym.KP_2: (0, 1),
+ tcod.event.KeySym.KP_3: (1, 1),
+ tcod.event.KeySym.KP_4: (-1, 0),
+ tcod.event.KeySym.KP_5: (0, 0),
+ tcod.event.KeySym.KP_6: (1, 0),
+ tcod.event.KeySym.KP_7: (-1, -1),
+ tcod.event.KeySym.KP_8: (0, -1),
+ tcod.event.KeySym.KP_9: (1, -1),
+ tcod.event.KeySym.CLEAR: (0, 0), # Numpad `clear` key.
+ # Vi Keys.
+ tcod.event.KeySym.h: (-1, 0),
+ tcod.event.KeySym.j: (0, 1),
+ tcod.event.KeySym.k: (0, -1),
+ tcod.event.KeySym.l: (1, 0),
+ tcod.event.KeySym.y: (-1, -1),
+ tcod.event.KeySym.u: (1, -1),
+ tcod.event.KeySym.b: (-1, 1),
+ tcod.event.KeySym.n: (1, 1),
+ }
+
+
+ class State(tcod.event.EventDispatch[None]):
+ """A state-based superclass that converts `events` into `commands`.
+
+ The configuration used to convert events to commands are hard-coded
+ in this example, but could be modified to be user controlled.
+
+ Subclasses will override the `cmd_*` methods with their own
+ functionality. There could be a subclass for every individual state
+ of your game.
+ """
+
+ def ev_quit(self, event: tcod.event.Quit) -> None:
+ """The window close button was clicked or Alt+F$ was pressed."""
+ print(event)
+ self.cmd_quit()
+
+ def ev_keydown(self, event: tcod.event.KeyDown) -> None:
+ """A key was pressed."""
+ print(event)
+ if event.sym in MOVE_KEYS:
+ # Send movement keys to the cmd_move method with parameters.
+ self.cmd_move(*MOVE_KEYS[event.sym])
+ elif event.sym == tcod.event.KeySym.ESCAPE:
+ self.cmd_escape()
+
+ def ev_mousebuttondown(self, event: tcod.event.MouseButtonDown) -> None:
+ """The window was clicked."""
+ print(event)
+
+ def ev_mousemotion(self, event: tcod.event.MouseMotion) -> None:
+ """The mouse has moved within the window."""
+ print(event)
+
+ def cmd_move(self, x: int, y: int) -> None:
+ """Intent to move: `x` and `y` is the direction, both may be 0."""
+ print("Command move: " + str((x, y)))
+
+ def cmd_escape(self) -> None:
+ """Intent to exit this state."""
+ print("Command escape.")
+ self.cmd_quit()
+
+ def cmd_quit(self) -> None:
+ """Intent to exit the game."""
+ print("Command quit.")
+ raise SystemExit()
+
+
+ root_console = libtcodpy.console_init_root(80, 60)
+ state = State()
+ while True:
+ libtcodpy.console_flush()
+ for event in tcod.event.wait():
+ state.dispatch(event)
+ '''
+
+ __slots__ = ()
+
+ def dispatch(self, event: Any) -> T | None: # noqa: ANN401
+ """Send an event to an `ev_*` method.
+
+ `*` will be the `event.type` attribute converted to lower-case.
+
+ Values returned by `ev_*` calls will be returned by this function.
+ This value always defaults to None for any non-overridden method.
+
+ .. versionchanged:: 11.12
+ Now returns the return value of `ev_*` methods.
+ `event.type` values of None are deprecated.
+ """
+ if event.type is None:
+ warnings.warn(
+ "`event.type` attribute should not be None.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return None
+ func_name = f"ev_{event.type.lower()}"
+ func: Callable[[Any], T | None] | None = getattr(self, func_name, None)
+ if func is None:
+ warnings.warn(f"{func_name} is missing from this EventDispatch object.", RuntimeWarning, stacklevel=2)
+ return None
+ return func(event)
+
+ def event_get(self) -> None: # noqa: D102
+ for event in get():
+ self.dispatch(event)
+
+ def event_wait(self, timeout: float | None) -> None: # noqa: D102
+ wait(timeout)
+ self.event_get()
+
+ def ev_quit(self, event: tcod.event.Quit, /) -> T | None:
+ """Called when the termination of the program is requested."""
+
+ def ev_keydown(self, event: tcod.event.KeyDown, /) -> T | None:
+ """Called when a keyboard key is pressed or repeated."""
+
+ def ev_keyup(self, event: tcod.event.KeyUp, /) -> T | None:
+ """Called when a keyboard key is released."""
+
+ def ev_mousemotion(self, event: tcod.event.MouseMotion, /) -> T | None:
+ """Called when the mouse is moved."""
+
+ def ev_mousebuttondown(self, event: tcod.event.MouseButtonDown, /) -> T | None:
+ """Called when a mouse button is pressed."""
+
+ def ev_mousebuttonup(self, event: tcod.event.MouseButtonUp, /) -> T | None:
+ """Called when a mouse button is released."""
+
+ def ev_mousewheel(self, event: tcod.event.MouseWheel, /) -> T | None:
+ """Called when the mouse wheel is scrolled."""
+
+ def ev_textinput(self, event: tcod.event.TextInput, /) -> T | None:
+ """Called to handle Unicode input."""
+
+ def ev_windowshown(self, event: tcod.event.WindowEvent, /) -> T | None:
+ """Called when the window is shown."""
+
+ def ev_windowhidden(self, event: tcod.event.WindowEvent, /) -> T | None:
+ """Called when the window is hidden."""
+
+ def ev_windowexposed(self, event: tcod.event.WindowEvent, /) -> T | None:
+ """Called when a window is exposed, and needs to be refreshed.
+
+ This usually means a call to :any:`libtcodpy.console_flush` is necessary.
+ """
+
+ def ev_windowmoved(self, event: tcod.event.WindowMoved, /) -> T | None:
+ """Called when the window is moved."""
+
+ def ev_windowresized(self, event: tcod.event.WindowResized, /) -> T | None:
+ """Called when the window is resized."""
+
+ def ev_windowminimized(self, event: tcod.event.WindowEvent, /) -> T | None:
+ """Called when the window is minimized."""
+
+ def ev_windowmaximized(self, event: tcod.event.WindowEvent, /) -> T | None:
+ """Called when the window is maximized."""
+
+ def ev_windowrestored(self, event: tcod.event.WindowEvent, /) -> T | None:
+ """Called when the window is restored."""
+
+ def ev_windowenter(self, event: tcod.event.WindowEvent, /) -> T | None:
+ """Called when the window gains mouse focus."""
+
+ def ev_windowleave(self, event: tcod.event.WindowEvent, /) -> T | None:
+ """Called when the window loses mouse focus."""
+
+ def ev_windowfocusgained(self, event: tcod.event.WindowEvent, /) -> T | None:
+ """Called when the window gains keyboard focus."""
+
+ def ev_windowfocuslost(self, event: tcod.event.WindowEvent, /) -> T | None:
+ """Called when the window loses keyboard focus."""
+
+ def ev_windowclose(self, event: tcod.event.WindowEvent, /) -> T | None:
+ """Called when the window manager requests the window to be closed."""
+
+ def ev_windowtakefocus(self, event: tcod.event.WindowEvent, /) -> T | None: # noqa: D102
+ pass
+
+ def ev_windowhittest(self, event: tcod.event.WindowEvent, /) -> T | None: # noqa: D102
+ pass
+
+ def ev_joyaxismotion(self, event: tcod.event.JoystickAxis, /) -> T | None:
+ """Called when a joystick analog is moved.
+
+ .. versionadded:: 13.8
+ """
+
+ def ev_joyballmotion(self, event: tcod.event.JoystickBall, /) -> T | None:
+ """Called when a joystick ball is moved.
+
+ .. versionadded:: 13.8
+ """
+
+ def ev_joyhatmotion(self, event: tcod.event.JoystickHat, /) -> T | None:
+ """Called when a joystick hat is moved.
+
+ .. versionadded:: 13.8
+ """
+
+ def ev_joybuttondown(self, event: tcod.event.JoystickButton, /) -> T | None:
+ """Called when a joystick button is pressed.
+
+ .. versionadded:: 13.8
+ """
+
+ def ev_joybuttonup(self, event: tcod.event.JoystickButton, /) -> T | None:
+ """Called when a joystick button is released.
+
+ .. versionadded:: 13.8
+ """
+
+ def ev_joydeviceadded(self, event: tcod.event.JoystickDevice, /) -> T | None:
+ """Called when a joystick is added.
+
+ .. versionadded:: 13.8
+ """
+
+ def ev_joydeviceremoved(self, event: tcod.event.JoystickDevice, /) -> T | None:
+ """Called when a joystick is removed.
+
+ .. versionadded:: 13.8
+ """
+
+ def ev_controlleraxismotion(self, event: tcod.event.ControllerAxis, /) -> T | None:
+ """Called when a controller analog is moved.
+
+ .. versionadded:: 13.8
+ """
+
+ def ev_controllerbuttondown(self, event: tcod.event.ControllerButton, /) -> T | None:
+ """Called when a controller button is pressed.
+
+ .. versionadded:: 13.8
+ """
+
+ def ev_controllerbuttonup(self, event: tcod.event.ControllerButton, /) -> T | None:
+ """Called when a controller button is released.
+
+ .. versionadded:: 13.8
+ """
+
+ def ev_controllerdeviceadded(self, event: tcod.event.ControllerDevice, /) -> T | None:
+ """Called when a standard controller is added.
+
+ .. versionadded:: 13.8
+ """
+
+ def ev_controllerdeviceremoved(self, event: tcod.event.ControllerDevice, /) -> T | None:
+ """Called when a standard controller is removed.
+
+ .. versionadded:: 13.8
+ """
+
+ def ev_controllerdeviceremapped(self, event: tcod.event.ControllerDevice, /) -> T | None:
+ """Called when a standard controller is remapped.
+
+ .. versionadded:: 13.8
+ """
+
+ def ev_(self, event: Any, /) -> T | None: # noqa: ANN401, D102
+ pass
+
+
+def get_mouse_state() -> MouseState:
+ """Return the current state of the mouse.
+
+ .. versionadded:: 9.3
+ """
+ xy = ffi.new("float[2]")
+ buttons = lib.SDL_GetMouseState(xy, xy + 1)
+ tile = _pixel_to_tile(tuple(xy))
+ if tile is None:
+ return MouseState(position=Point(xy[0], xy[1]), tile=None, state=buttons)
+ return MouseState(position=Point(xy[0], xy[1]), tile=Point(floor(tile[0]), floor(tile[1])), state=buttons)
+
+
+@overload
+def convert_coordinates_from_window(
+ event: _EventType,
+ /,
+ context: tcod.context.Context | tcod.sdl.render.Renderer,
+ console: tcod.console.Console | tuple[int, int],
+ dest_rect: tuple[int, int, int, int] | None = None,
+) -> _EventType: ...
+@overload
+def convert_coordinates_from_window(
+ xy: tuple[float, float],
+ /,
+ context: tcod.context.Context | tcod.sdl.render.Renderer,
+ console: tcod.console.Console | tuple[int, int],
+ dest_rect: tuple[int, int, int, int] | None = None,
+) -> tuple[float, float]: ...
+def convert_coordinates_from_window(
+ event: _EventType | tuple[float, float],
+ /,
+ context: tcod.context.Context | tcod.sdl.render.Renderer,
+ console: tcod.console.Console | tuple[int, int],
+ dest_rect: tuple[int, int, int, int] | None = None,
+) -> _EventType | tuple[float, float]:
+ """Return an event or position with window mouse coordinates converted into console tile coordinates.
+
+ Args:
+ event: :any:`Event` to convert, or the `(x, y)` coordinates to convert.
+ context: Context or Renderer to fetch the SDL renderer from for reference with conversions.
+ console: A console used as a size reference.
+ Otherwise the `(columns, rows)` can be given directly as a tuple.
+ dest_rect: The consoles rendering destination as `(x, y, width, height)`.
+ If None is given then the whole rendering target is assumed.
+
+ .. versionadded:: 20.0
+ """
+ if isinstance(context, tcod.context.Context):
+ maybe_renderer: Final = context.sdl_renderer
+ if maybe_renderer is None:
+ return event
+ context = maybe_renderer
+
+ if isinstance(console, tcod.console.Console):
+ console = console.width, console.height
+
+ if dest_rect is None:
+ dest_rect = (0, 0, *(context.logical_size or context.output_size))
+
+ x_scale: Final = console[0] / dest_rect[2]
+ y_scale: Final = console[1] / dest_rect[3]
+ x_offset: Final = dest_rect[0]
+ y_offset: Final = dest_rect[1]
+
+ if not isinstance(event, Event):
+ x, y = context.coordinates_from_window(event)
+ return (x - x_offset) * x_scale, (y - y_offset) * y_scale
+
+ if isinstance(event, MouseMotion):
+ previous_position = convert_coordinates_from_window(
+ ((event.position[0] - event.motion[0]), (event.position[1] - event.motion[1])), context, console, dest_rect
+ )
+ position = convert_coordinates_from_window(event.position, context, console, dest_rect)
+ event.motion = Point(position[0] - previous_position[0], position[1] - previous_position[1])
+ event._tile_motion = Point(
+ floor(position[0]) - floor(previous_position[0]), floor(position[1]) - floor(previous_position[1])
+ )
+ elif isinstance(event, _MouseEventWithPosition):
+ event.position = Point(*convert_coordinates_from_window(event.position, context, console, dest_rect))
+ if isinstance(event, _MouseEventWithTile):
+ event._tile = Point(floor(event.position[0]), floor(event.position[1]))
+ return event
+
+
+@ffi.def_extern() # type: ignore[untyped-decorator]
+def _sdl_event_watcher(userdata: Any, sdl_event: _C_SDL_Event) -> int: # noqa: ANN401
+ callback: Callable[[Event], None] = ffi.from_handle(userdata)
+ callback(_parse_event(sdl_event))
+ return 0
+
+
+_EventCallback = TypeVar("_EventCallback", bound=Callable[[Event], None])
+_event_watch_handles: dict[Callable[[Event], None], Any] = {} # Callbacks and their FFI handles.
+
+
+def add_watch(callback: _EventCallback) -> _EventCallback:
+ """Add a callback for watching events.
+
+ This function can be called with the callback to register, or be used as a decorator.
+
+ Callbacks added as event watchers can later be removed with :any:`tcod.event.remove_watch`.
+
+ .. warning::
+ How uncaught exceptions in a callback are handled is not currently defined by tcod.
+ They will likely be handled by :any:`sys.unraisablehook`.
+ This may be later changed to pass the exception to a :any:`tcod.event.get` or :any:`tcod.event.wait` call.
+
+ Args:
+ callback (Callable[[Event], None]):
+ A function which accepts :any:`Event` parameters.
+
+ Example::
+
+ import tcod.event
+
+ @tcod.event.add_watch
+ def handle_events(event: tcod.event.Event) -> None:
+ if isinstance(event, tcod.event.KeyDown):
+ print(event)
+
+ .. versionadded:: 13.4
+ """
+ if callback in _event_watch_handles:
+ warnings.warn(
+ f"{callback} is already an active event watcher, nothing was added.", RuntimeWarning, stacklevel=2
+ )
+ return callback
+ handle = _event_watch_handles[callback] = ffi.new_handle(callback)
+ lib.SDL_AddEventWatch(lib._sdl_event_watcher, handle)
+ return callback
+
+
+def remove_watch(callback: Callable[[Event], None]) -> None:
+ """Remove a callback as an event watcher.
+
+ Args:
+ callback (Callable[[Event], None]):
+ A function which has been previously registered with :any:`tcod.event.add_watch`.
+
+ .. versionadded:: 13.4
+ """
+ if callback not in _event_watch_handles:
+ warnings.warn(f"{callback} is not an active event watcher, nothing was removed.", RuntimeWarning, stacklevel=2)
+ return
+ handle = _event_watch_handles[callback]
+ lib.SDL_RemoveEventWatch(lib._sdl_event_watcher, handle)
+ del _event_watch_handles[callback]
+
+
+def get_keyboard_state() -> NDArray[np.bool_]:
+ """Return a boolean array with the current keyboard state.
+
+ Index this array with a scancode. The value will be True if the key is
+ currently held.
+
+ Example::
+
+ state = tcod.event.get_keyboard_state()
+
+ # Get a WASD movement vector:
+ x = int(state[tcod.event.Scancode.D]) - int(state[tcod.event.Scancode.A])
+ y = int(state[tcod.event.Scancode.S]) - int(state[tcod.event.Scancode.W])
+
+ # Key with 'z' glyph is held:
+ is_z_held = state[tcod.event.KeySym.z.scancode]
+
+
+ .. versionadded:: 12.3
+ """
+ num_keys = ffi.new("int[1]")
+ keyboard_state = lib.SDL_GetKeyboardState(num_keys)
+ out: NDArray[np.bool_] = np.frombuffer(ffi.buffer(keyboard_state[0 : num_keys[0]]), dtype=np.bool_)
+ out.flags["WRITEABLE"] = False # This buffer is supposed to be const.
+ return out
+
+
+def get_modifier_state() -> Modifier:
+ """Return a bitmask of the active keyboard modifiers.
+
+ .. versionadded:: 12.3
+ """
+ return Modifier(lib.SDL_GetModState())
+
+
+class Scancode(enum.IntEnum):
+ """A Scancode represents the physical location of a key.
+
+ For example the scan codes for WASD remain in the same physical location
+ regardless of the actual keyboard layout.
+
+ These names are derived from SDL except for the numbers which are prefixed
+ with ``N`` (since raw numbers can not be a Python name.)
+
+ .. versionadded:: 12.3
+
+ ================== ===
+ UNKNOWN 0
+ A 4
+ B 5
+ C 6
+ D 7
+ E 8
+ F 9
+ G 10
+ H 11
+ I 12
+ J 13
+ K 14
+ L 15
+ M 16
+ N 17
+ O 18
+ P 19
+ Q 20
+ R 21
+ S 22
+ T 23
+ U 24
+ V 25
+ W 26
+ X 27
+ Y 28
+ Z 29
+ N1 30
+ N2 31
+ N3 32
+ N4 33
+ N5 34
+ N6 35
+ N7 36
+ N8 37
+ N9 38
+ N0 39
+ RETURN 40
+ ESCAPE 41
+ BACKSPACE 42
+ TAB 43
+ SPACE 44
+ MINUS 45
+ EQUALS 46
+ LEFTBRACKET 47
+ RIGHTBRACKET 48
+ BACKSLASH 49
+ NONUSHASH 50
+ SEMICOLON 51
+ APOSTROPHE 52
+ GRAVE 53
+ COMMA 54
+ PERIOD 55
+ SLASH 56
+ CAPSLOCK 57
+ F1 58
+ F2 59
+ F3 60
+ F4 61
+ F5 62
+ F6 63
+ F7 64
+ F8 65
+ F9 66
+ F10 67
+ F11 68
+ F12 69
+ PRINTSCREEN 70
+ SCROLLLOCK 71
+ PAUSE 72
+ INSERT 73
+ HOME 74
+ PAGEUP 75
+ DELETE 76
+ END 77
+ PAGEDOWN 78
+ RIGHT 79
+ LEFT 80
+ DOWN 81
+ UP 82
+ NUMLOCKCLEAR 83
+ KP_DIVIDE 84
+ KP_MULTIPLY 85
+ KP_MINUS 86
+ KP_PLUS 87
+ KP_ENTER 88
+ KP_1 89
+ KP_2 90
+ KP_3 91
+ KP_4 92
+ KP_5 93
+ KP_6 94
+ KP_7 95
+ KP_8 96
+ KP_9 97
+ KP_0 98
+ KP_PERIOD 99
+ NONUSBACKSLASH 100
+ APPLICATION 101
+ POWER 102
+ KP_EQUALS 103
+ F13 104
+ F14 105
+ F15 106
+ F16 107
+ F17 108
+ F18 109
+ F19 110
+ F20 111
+ F21 112
+ F22 113
+ F23 114
+ F24 115
+ EXECUTE 116
+ HELP 117
+ MENU 118
+ SELECT 119
+ STOP 120
+ AGAIN 121
+ UNDO 122
+ CUT 123
+ COPY 124
+ PASTE 125
+ FIND 126
+ MUTE 127
+ VOLUMEUP 128
+ VOLUMEDOWN 129
+ KP_COMMA 133
+ KP_EQUALSAS400 134
+ INTERNATIONAL1 135
+ INTERNATIONAL2 136
+ INTERNATIONAL3 137
+ INTERNATIONAL4 138
+ INTERNATIONAL5 139
+ INTERNATIONAL6 140
+ INTERNATIONAL7 141
+ INTERNATIONAL8 142
+ INTERNATIONAL9 143
+ LANG1 144
+ LANG2 145
+ LANG3 146
+ LANG4 147
+ LANG5 148
+ LANG6 149
+ LANG7 150
+ LANG8 151
+ LANG9 152
+ ALTERASE 153
+ SYSREQ 154
+ CANCEL 155
+ CLEAR 156
+ PRIOR 157
+ RETURN2 158
+ SEPARATOR 159
+ OUT 160
+ OPER 161
+ CLEARAGAIN 162
+ CRSEL 163
+ EXSEL 164
+ KP_00 176
+ KP_000 177
+ THOUSANDSSEPARATOR 178
+ DECIMALSEPARATOR 179
+ CURRENCYUNIT 180
+ CURRENCYSUBUNIT 181
+ KP_LEFTPAREN 182
+ KP_RIGHTPAREN 183
+ KP_LEFTBRACE 184
+ KP_RIGHTBRACE 185
+ KP_TAB 186
+ KP_BACKSPACE 187
+ KP_A 188
+ KP_B 189
+ KP_C 190
+ KP_D 191
+ KP_E 192
+ KP_F 193
+ KP_XOR 194
+ KP_POWER 195
+ KP_PERCENT 196
+ KP_LESS 197
+ KP_GREATER 198
+ KP_AMPERSAND 199
+ KP_DBLAMPERSAND 200
+ KP_VERTICALBAR 201
+ KP_DBLVERTICALBAR 202
+ KP_COLON 203
+ KP_HASH 204
+ KP_SPACE 205
+ KP_AT 206
+ KP_EXCLAM 207
+ KP_MEMSTORE 208
+ KP_MEMRECALL 209
+ KP_MEMCLEAR 210
+ KP_MEMADD 211
+ KP_MEMSUBTRACT 212
+ KP_MEMMULTIPLY 213
+ KP_MEMDIVIDE 214
+ KP_PLUSMINUS 215
+ KP_CLEAR 216
+ KP_CLEARENTRY 217
+ KP_BINARY 218
+ KP_OCTAL 219
+ KP_DECIMAL 220
+ KP_HEXADECIMAL 221
+ LCTRL 224
+ LSHIFT 225
+ LALT 226
+ LGUI 227
+ RCTRL 228
+ RSHIFT 229
+ RALT 230
+ RGUI 231
+ MODE 257
+ AUDIONEXT 258
+ AUDIOPREV 259
+ AUDIOSTOP 260
+ AUDIOPLAY 261
+ AUDIOMUTE 262
+ MEDIASELECT 263
+ WWW 264
+ MAIL 265
+ CALCULATOR 266
+ COMPUTER 267
+ AC_SEARCH 268
+ AC_HOME 269
+ AC_BACK 270
+ AC_FORWARD 271
+ AC_STOP 272
+ AC_REFRESH 273
+ AC_BOOKMARKS 274
+ BRIGHTNESSDOWN 275
+ BRIGHTNESSUP 276
+ DISPLAYSWITCH 277
+ KBDILLUMTOGGLE 278
+ KBDILLUMDOWN 279
+ KBDILLUMUP 280
+ EJECT 281
+ SLEEP 282
+ APP1 283
+ APP2 284
+ ================== ===
+
+ """
+
+ # --- SDL scancodes ---
+ UNKNOWN = 0
+ A = 4
+ B = 5
+ C = 6
+ D = 7
+ E = 8
+ F = 9
+ G = 10
+ H = 11
+ I = 12 # noqa: E741
+ J = 13
+ K = 14
+ L = 15
+ M = 16
+ N = 17
+ O = 18 # noqa: E741
+ P = 19
+ Q = 20
+ R = 21
+ S = 22
+ T = 23
+ U = 24
+ V = 25
+ W = 26
+ X = 27
+ Y = 28
+ Z = 29
+ N1 = 30
+ N2 = 31
+ N3 = 32
+ N4 = 33
+ N5 = 34
+ N6 = 35
+ N7 = 36
+ N8 = 37
+ N9 = 38
+ N0 = 39
+ RETURN = 40
+ ESCAPE = 41
+ BACKSPACE = 42
+ TAB = 43
+ SPACE = 44
+ MINUS = 45
+ EQUALS = 46
+ LEFTBRACKET = 47
+ RIGHTBRACKET = 48
+ BACKSLASH = 49
+ NONUSHASH = 50
+ SEMICOLON = 51
+ APOSTROPHE = 52
+ GRAVE = 53
+ COMMA = 54
+ PERIOD = 55
+ SLASH = 56
+ CAPSLOCK = 57
+ F1 = 58
+ F2 = 59
+ F3 = 60
+ F4 = 61
+ F5 = 62
+ F6 = 63
+ F7 = 64
+ F8 = 65
+ F9 = 66
+ F10 = 67
+ F11 = 68
+ F12 = 69
+ PRINTSCREEN = 70
+ SCROLLLOCK = 71
+ PAUSE = 72
+ INSERT = 73
+ HOME = 74
+ PAGEUP = 75
+ DELETE = 76
+ END = 77
+ PAGEDOWN = 78
+ RIGHT = 79
+ LEFT = 80
+ DOWN = 81
+ UP = 82
+ NUMLOCKCLEAR = 83
+ KP_DIVIDE = 84
+ KP_MULTIPLY = 85
+ KP_MINUS = 86
+ KP_PLUS = 87
+ KP_ENTER = 88
+ KP_1 = 89
+ KP_2 = 90
+ KP_3 = 91
+ KP_4 = 92
+ KP_5 = 93
+ KP_6 = 94
+ KP_7 = 95
+ KP_8 = 96
+ KP_9 = 97
+ KP_0 = 98
+ KP_PERIOD = 99
+ NONUSBACKSLASH = 100
+ APPLICATION = 101
+ POWER = 102
+ KP_EQUALS = 103
+ F13 = 104
+ F14 = 105
+ F15 = 106
+ F16 = 107
+ F17 = 108
+ F18 = 109
+ F19 = 110
+ F20 = 111
+ F21 = 112
+ F22 = 113
+ F23 = 114
+ F24 = 115
+ EXECUTE = 116
+ HELP = 117
+ MENU = 118
+ SELECT = 119
+ STOP = 120
+ AGAIN = 121
+ UNDO = 122
+ CUT = 123
+ COPY = 124
+ PASTE = 125
+ FIND = 126
+ MUTE = 127
+ VOLUMEUP = 128
+ VOLUMEDOWN = 129
+ KP_COMMA = 133
+ KP_EQUALSAS400 = 134
+ INTERNATIONAL1 = 135
+ INTERNATIONAL2 = 136
+ INTERNATIONAL3 = 137
+ INTERNATIONAL4 = 138
+ INTERNATIONAL5 = 139
+ INTERNATIONAL6 = 140
+ INTERNATIONAL7 = 141
+ INTERNATIONAL8 = 142
+ INTERNATIONAL9 = 143
+ LANG1 = 144
+ LANG2 = 145
+ LANG3 = 146
+ LANG4 = 147
+ LANG5 = 148
+ LANG6 = 149
+ LANG7 = 150
+ LANG8 = 151
+ LANG9 = 152
+ ALTERASE = 153
+ SYSREQ = 154
+ CANCEL = 155
+ CLEAR = 156
+ PRIOR = 157
+ RETURN2 = 158
+ SEPARATOR = 159
+ OUT = 160
+ OPER = 161
+ CLEARAGAIN = 162
+ CRSEL = 163
+ EXSEL = 164
+ KP_00 = 176
+ KP_000 = 177
+ THOUSANDSSEPARATOR = 178
+ DECIMALSEPARATOR = 179
+ CURRENCYUNIT = 180
+ CURRENCYSUBUNIT = 181
+ KP_LEFTPAREN = 182
+ KP_RIGHTPAREN = 183
+ KP_LEFTBRACE = 184
+ KP_RIGHTBRACE = 185
+ KP_TAB = 186
+ KP_BACKSPACE = 187
+ KP_A = 188
+ KP_B = 189
+ KP_C = 190
+ KP_D = 191
+ KP_E = 192
+ KP_F = 193
+ KP_XOR = 194
+ KP_POWER = 195
+ KP_PERCENT = 196
+ KP_LESS = 197
+ KP_GREATER = 198
+ KP_AMPERSAND = 199
+ KP_DBLAMPERSAND = 200
+ KP_VERTICALBAR = 201
+ KP_DBLVERTICALBAR = 202
+ KP_COLON = 203
+ KP_HASH = 204
+ KP_SPACE = 205
+ KP_AT = 206
+ KP_EXCLAM = 207
+ KP_MEMSTORE = 208
+ KP_MEMRECALL = 209
+ KP_MEMCLEAR = 210
+ KP_MEMADD = 211
+ KP_MEMSUBTRACT = 212
+ KP_MEMMULTIPLY = 213
+ KP_MEMDIVIDE = 214
+ KP_PLUSMINUS = 215
+ KP_CLEAR = 216
+ KP_CLEARENTRY = 217
+ KP_BINARY = 218
+ KP_OCTAL = 219
+ KP_DECIMAL = 220
+ KP_HEXADECIMAL = 221
+ LCTRL = 224
+ LSHIFT = 225
+ LALT = 226
+ LGUI = 227
+ RCTRL = 228
+ RSHIFT = 229
+ RALT = 230
+ RGUI = 231
+ MODE = 257
+ SLEEP = 258
+ WAKE = 259
+ CHANNEL_INCREMENT = 260
+ CHANNEL_DECREMENT = 261
+ MEDIA_PLAY = 262
+ MEDIA_PAUSE = 263
+ MEDIA_RECORD = 264
+ MEDIA_FAST_FORWARD = 265
+ MEDIA_REWIND = 266
+ MEDIA_NEXT_TRACK = 267
+ MEDIA_PREVIOUS_TRACK = 268
+ MEDIA_STOP = 269
+ MEDIA_EJECT = 270
+ MEDIA_PLAY_PAUSE = 271
+ MEDIA_SELECT = 272
+ AC_NEW = 273
+ AC_OPEN = 274
+ AC_CLOSE = 275
+ AC_EXIT = 276
+ AC_SAVE = 277
+ AC_PRINT = 278
+ AC_PROPERTIES = 279
+ AC_SEARCH = 280
+ AC_HOME = 281
+ AC_BACK = 282
+ AC_FORWARD = 283
+ AC_STOP = 284
+ AC_REFRESH = 285
+ AC_BOOKMARKS = 286
+ SOFTLEFT = 287
+ SOFTRIGHT = 288
+ CALL = 289
+ ENDCALL = 290
+ RESERVED = 400
+ COUNT = 512
+ # --- end ---
+
+ @property
+ def label(self) -> str:
+ """Return a human-readable name of a key based on its scancode.
+
+ Be sure not to confuse this with ``.name``, which will return the enum
+ name rather than the human-readable name.
+
+ .. seealso::
+ :any:`KeySym.label`
+ """
+ return self.keysym.label
+
+ @property
+ def keysym(self) -> KeySym:
+ """Return a :class:`KeySym` from a scancode.
+
+ Based on the current keyboard layout.
+ """
+ _init_sdl_video()
+ return KeySym(lib.SDL_GetKeyFromScancode(self.value, 0, False)) # noqa: FBT003
+
+ @property
+ def scancode(self) -> Scancode:
+ """Return a scancode from a keycode.
+
+ Returns itself since it is already a :class:`Scancode`.
+
+ .. seealso::
+ :any:`KeySym.scancode`
+ """
+ return self
+
+ @classmethod
+ def _missing_(cls, value: object) -> Scancode | None:
+ if not isinstance(value, int):
+ return None
+ result = cls(0)
+ result._value_ = value
+ return result
+
+ def __eq__(self, other: object) -> bool:
+ """Compare with another Scancode value.
+
+ Comparison between :any:`KeySym` and :any:`Scancode` is not allowed and will raise :any:`TypeError`.
+ """
+ if isinstance(other, KeySym):
+ msg = "Scancode and KeySym enums can not be compared directly. Convert one or the other to the same type."
+ raise TypeError(msg)
+ return super().__eq__(other)
+
+ def __hash__(self) -> int:
+ """Return the hash for this value."""
+ return super().__hash__() # __eq__ was defined, so __hash__ must be defined
+
+ def __repr__(self) -> str:
+ """Return the fully qualified name of this enum."""
+ return f"tcod.event.{self.__class__.__name__}.{self.name}"
+
+
+class KeySym(enum.IntEnum):
+ """Keyboard constants based on their symbol.
+
+ These names are derived from SDL except for numbers which are prefixed with ``N`` (since raw numbers can not be a Python name).
+ Alternatively ``KeySym["9"]`` can be used to represent numbers (since Python 3.13).
+
+ .. versionadded:: 12.3
+
+ .. versionchanged:: 19.0
+ SDL backend was updated to 3.x, which means some enums have been renamed.
+ Single letters are now uppercase.
+
+ .. versionchanged:: 19.6
+ Number symbols can now be fetched with ``KeySym["9"]``, etc.
+ With Python 3.13 or later.
+
+ ================== ==========
+ UNKNOWN 0
+ BACKSPACE 8
+ TAB 9
+ RETURN 13
+ ESCAPE 27
+ SPACE 32
+ EXCLAIM 33
+ QUOTEDBL 34
+ HASH 35
+ DOLLAR 36
+ PERCENT 37
+ AMPERSAND 38
+ QUOTE 39
+ LEFTPAREN 40
+ RIGHTPAREN 41
+ ASTERISK 42
+ PLUS 43
+ COMMA 44
+ MINUS 45
+ PERIOD 46
+ SLASH 47
+ N0 48
+ N1 49
+ N2 50
+ N3 51
+ N4 52
+ N5 53
+ N6 54
+ N7 55
+ N8 56
+ N9 57
+ COLON 58
+ SEMICOLON 59
+ LESS 60
+ EQUALS 61
+ GREATER 62
+ QUESTION 63
+ AT 64
+ LEFTBRACKET 91
+ BACKSLASH 92
+ RIGHTBRACKET 93
+ CARET 94
+ UNDERSCORE 95
+ BACKQUOTE 96
+ A 97
+ B 98
+ C 99
+ D 100
+ E 101
+ F 102
+ G 103
+ H 104
+ I 105
+ J 106
+ K 107
+ L 108
+ M 109
+ N 110
+ O 111
+ P 112
+ Q 113
+ R 114
+ S 115
+ T 116
+ U 117
+ V 118
+ W 119
+ X 120
+ Y 121
+ Z 122
+ DELETE 127
+ SCANCODE_MASK 1073741824
+ CAPSLOCK 1073741881
+ F1 1073741882
+ F2 1073741883
+ F3 1073741884
+ F4 1073741885
+ F5 1073741886
+ F6 1073741887
+ F7 1073741888
+ F8 1073741889
+ F9 1073741890
+ F10 1073741891
+ F11 1073741892
+ F12 1073741893
+ PRINTSCREEN 1073741894
+ SCROLLLOCK 1073741895
+ PAUSE 1073741896
+ INSERT 1073741897
+ HOME 1073741898
+ PAGEUP 1073741899
+ END 1073741901
+ PAGEDOWN 1073741902
+ RIGHT 1073741903
+ LEFT 1073741904
+ DOWN 1073741905
+ UP 1073741906
+ NUMLOCKCLEAR 1073741907
+ KP_DIVIDE 1073741908
+ KP_MULTIPLY 1073741909
+ KP_MINUS 1073741910
+ KP_PLUS 1073741911
+ KP_ENTER 1073741912
+ KP_1 1073741913
+ KP_2 1073741914
+ KP_3 1073741915
+ KP_4 1073741916
+ KP_5 1073741917
+ KP_6 1073741918
+ KP_7 1073741919
+ KP_8 1073741920
+ KP_9 1073741921
+ KP_0 1073741922
+ KP_PERIOD 1073741923
+ APPLICATION 1073741925
+ POWER 1073741926
+ KP_EQUALS 1073741927
+ F13 1073741928
+ F14 1073741929
+ F15 1073741930
+ F16 1073741931
+ F17 1073741932
+ F18 1073741933
+ F19 1073741934
+ F20 1073741935
+ F21 1073741936
+ F22 1073741937
+ F23 1073741938
+ F24 1073741939
+ EXECUTE 1073741940
+ HELP 1073741941
+ MENU 1073741942
+ SELECT 1073741943
+ STOP 1073741944
+ AGAIN 1073741945
+ UNDO 1073741946
+ CUT 1073741947
+ COPY 1073741948
+ PASTE 1073741949
+ FIND 1073741950
+ MUTE 1073741951
+ VOLUMEUP 1073741952
+ VOLUMEDOWN 1073741953
+ KP_COMMA 1073741957
+ KP_EQUALSAS400 1073741958
+ ALTERASE 1073741977
+ SYSREQ 1073741978
+ CANCEL 1073741979
+ CLEAR 1073741980
+ PRIOR 1073741981
+ RETURN2 1073741982
+ SEPARATOR 1073741983
+ OUT 1073741984
+ OPER 1073741985
+ CLEARAGAIN 1073741986
+ CRSEL 1073741987
+ EXSEL 1073741988
+ KP_00 1073742000
+ KP_000 1073742001
+ THOUSANDSSEPARATOR 1073742002
+ DECIMALSEPARATOR 1073742003
+ CURRENCYUNIT 1073742004
+ CURRENCYSUBUNIT 1073742005
+ KP_LEFTPAREN 1073742006
+ KP_RIGHTPAREN 1073742007
+ KP_LEFTBRACE 1073742008
+ KP_RIGHTBRACE 1073742009
+ KP_TAB 1073742010
+ KP_BACKSPACE 1073742011
+ KP_A 1073742012
+ KP_B 1073742013
+ KP_C 1073742014
+ KP_D 1073742015
+ KP_E 1073742016
+ KP_F 1073742017
+ KP_XOR 1073742018
+ KP_POWER 1073742019
+ KP_PERCENT 1073742020
+ KP_LESS 1073742021
+ KP_GREATER 1073742022
+ KP_AMPERSAND 1073742023
+ KP_DBLAMPERSAND 1073742024
+ KP_VERTICALBAR 1073742025
+ KP_DBLVERTICALBAR 1073742026
+ KP_COLON 1073742027
+ KP_HASH 1073742028
+ KP_SPACE 1073742029
+ KP_AT 1073742030
+ KP_EXCLAM 1073742031
+ KP_MEMSTORE 1073742032
+ KP_MEMRECALL 1073742033
+ KP_MEMCLEAR 1073742034
+ KP_MEMADD 1073742035
+ KP_MEMSUBTRACT 1073742036
+ KP_MEMMULTIPLY 1073742037
+ KP_MEMDIVIDE 1073742038
+ KP_PLUSMINUS 1073742039
+ KP_CLEAR 1073742040
+ KP_CLEARENTRY 1073742041
+ KP_BINARY 1073742042
+ KP_OCTAL 1073742043
+ KP_DECIMAL 1073742044
+ KP_HEXADECIMAL 1073742045
+ LCTRL 1073742048
+ LSHIFT 1073742049
+ LALT 1073742050
+ LGUI 1073742051
+ RCTRL 1073742052
+ RSHIFT 1073742053
+ RALT 1073742054
+ RGUI 1073742055
+ MODE 1073742081
+ AUDIONEXT 1073742082
+ AUDIOPREV 1073742083
+ AUDIOSTOP 1073742084
+ AUDIOPLAY 1073742085
+ AUDIOMUTE 1073742086
+ MEDIASELECT 1073742087
+ WWW 1073742088
+ MAIL 1073742089
+ CALCULATOR 1073742090
+ COMPUTER 1073742091
+ AC_SEARCH 1073742092
+ AC_HOME 1073742093
+ AC_BACK 1073742094
+ AC_FORWARD 1073742095
+ AC_STOP 1073742096
+ AC_REFRESH 1073742097
+ AC_BOOKMARKS 1073742098
+ BRIGHTNESSDOWN 1073742099
+ BRIGHTNESSUP 1073742100
+ DISPLAYSWITCH 1073742101
+ KBDILLUMTOGGLE 1073742102
+ KBDILLUMDOWN 1073742103
+ KBDILLUMUP 1073742104
+ EJECT 1073742105
+ SLEEP 1073742106
+ ================== ==========
+ """
+
+ # --- SDL keyboard symbols ---
+ UNKNOWN = 0
+ BACKSPACE = 8
+ TAB = 9
+ RETURN = 13
+ ESCAPE = 27
+ SPACE = 32
+ EXCLAIM = 33
+ DBLAPOSTROPHE = 34
+ HASH = 35
+ DOLLAR = 36
+ PERCENT = 37
+ AMPERSAND = 38
+ APOSTROPHE = 39
+ LEFTPAREN = 40
+ RIGHTPAREN = 41
+ ASTERISK = 42
+ PLUS = 43
+ COMMA = 44
+ MINUS = 45
+ PERIOD = 46
+ SLASH = 47
+ N0 = 48
+ N1 = 49
+ N2 = 50
+ N3 = 51
+ N4 = 52
+ N5 = 53
+ N6 = 54
+ N7 = 55
+ N8 = 56
+ N9 = 57
+ COLON = 58
+ SEMICOLON = 59
+ LESS = 60
+ EQUALS = 61
+ GREATER = 62
+ QUESTION = 63
+ AT = 64
+ LEFTBRACKET = 91
+ BACKSLASH = 92
+ RIGHTBRACKET = 93
+ CARET = 94
+ UNDERSCORE = 95
+ GRAVE = 96
+ A = 97
+ B = 98
+ C = 99
+ D = 100
+ E = 101
+ F = 102
+ G = 103
+ H = 104
+ I = 105 # noqa: E741
+ J = 106
+ K = 107
+ L = 108
+ M = 109
+ N = 110
+ O = 111 # noqa: E741
+ P = 112
+ Q = 113
+ R = 114
+ S = 115
+ T = 116
+ U = 117
+ V = 118
+ W = 119
+ X = 120
+ Y = 121
+ Z = 122
+ LEFTBRACE = 123
+ PIPE = 124
+ RIGHTBRACE = 125
+ TILDE = 126
+ DELETE = 127
+ PLUSMINUS = 177
+ EXTENDED_MASK = 536870912
+ LEFT_TAB = 536870913
+ LEVEL5_SHIFT = 536870914
+ MULTI_KEY_COMPOSE = 536870915
+ LMETA = 536870916
+ RMETA = 536870917
+ LHYPER = 536870918
+ RHYPER = 536870919
+ SCANCODE_MASK = 1073741824
+ CAPSLOCK = 1073741881
+ F1 = 1073741882
+ F2 = 1073741883
+ F3 = 1073741884
+ F4 = 1073741885
+ F5 = 1073741886
+ F6 = 1073741887
+ F7 = 1073741888
+ F8 = 1073741889
+ F9 = 1073741890
+ F10 = 1073741891
+ F11 = 1073741892
+ F12 = 1073741893
+ PRINTSCREEN = 1073741894
+ SCROLLLOCK = 1073741895
+ PAUSE = 1073741896
+ INSERT = 1073741897
+ HOME = 1073741898
+ PAGEUP = 1073741899
+ END = 1073741901
+ PAGEDOWN = 1073741902
+ RIGHT = 1073741903
+ LEFT = 1073741904
+ DOWN = 1073741905
+ UP = 1073741906
+ NUMLOCKCLEAR = 1073741907
+ KP_DIVIDE = 1073741908
+ KP_MULTIPLY = 1073741909
+ KP_MINUS = 1073741910
+ KP_PLUS = 1073741911
+ KP_ENTER = 1073741912
+ KP_1 = 1073741913
+ KP_2 = 1073741914
+ KP_3 = 1073741915
+ KP_4 = 1073741916
+ KP_5 = 1073741917
+ KP_6 = 1073741918
+ KP_7 = 1073741919
+ KP_8 = 1073741920
+ KP_9 = 1073741921
+ KP_0 = 1073741922
+ KP_PERIOD = 1073741923
+ APPLICATION = 1073741925
+ POWER = 1073741926
+ KP_EQUALS = 1073741927
+ F13 = 1073741928
+ F14 = 1073741929
+ F15 = 1073741930
+ F16 = 1073741931
+ F17 = 1073741932
+ F18 = 1073741933
+ F19 = 1073741934
+ F20 = 1073741935
+ F21 = 1073741936
+ F22 = 1073741937
+ F23 = 1073741938
+ F24 = 1073741939
+ EXECUTE = 1073741940
+ HELP = 1073741941
+ MENU = 1073741942
+ SELECT = 1073741943
+ STOP = 1073741944
+ AGAIN = 1073741945
+ UNDO = 1073741946
+ CUT = 1073741947
+ COPY = 1073741948
+ PASTE = 1073741949
+ FIND = 1073741950
+ MUTE = 1073741951
+ VOLUMEUP = 1073741952
+ VOLUMEDOWN = 1073741953
+ KP_COMMA = 1073741957
+ KP_EQUALSAS400 = 1073741958
+ ALTERASE = 1073741977
+ SYSREQ = 1073741978
+ CANCEL = 1073741979
+ CLEAR = 1073741980
+ PRIOR = 1073741981
+ RETURN2 = 1073741982
+ SEPARATOR = 1073741983
+ OUT = 1073741984
+ OPER = 1073741985
+ CLEARAGAIN = 1073741986
+ CRSEL = 1073741987
+ EXSEL = 1073741988
+ KP_00 = 1073742000
+ KP_000 = 1073742001
+ THOUSANDSSEPARATOR = 1073742002
+ DECIMALSEPARATOR = 1073742003
+ CURRENCYUNIT = 1073742004
+ CURRENCYSUBUNIT = 1073742005
+ KP_LEFTPAREN = 1073742006
+ KP_RIGHTPAREN = 1073742007
+ KP_LEFTBRACE = 1073742008
+ KP_RIGHTBRACE = 1073742009
+ KP_TAB = 1073742010
+ KP_BACKSPACE = 1073742011
+ KP_A = 1073742012
+ KP_B = 1073742013
+ KP_C = 1073742014
+ KP_D = 1073742015
+ KP_E = 1073742016
+ KP_F = 1073742017
+ KP_XOR = 1073742018
+ KP_POWER = 1073742019
+ KP_PERCENT = 1073742020
+ KP_LESS = 1073742021
+ KP_GREATER = 1073742022
+ KP_AMPERSAND = 1073742023
+ KP_DBLAMPERSAND = 1073742024
+ KP_VERTICALBAR = 1073742025
+ KP_DBLVERTICALBAR = 1073742026
+ KP_COLON = 1073742027
+ KP_HASH = 1073742028
+ KP_SPACE = 1073742029
+ KP_AT = 1073742030
+ KP_EXCLAM = 1073742031
+ KP_MEMSTORE = 1073742032
+ KP_MEMRECALL = 1073742033
+ KP_MEMCLEAR = 1073742034
+ KP_MEMADD = 1073742035
+ KP_MEMSUBTRACT = 1073742036
+ KP_MEMMULTIPLY = 1073742037
+ KP_MEMDIVIDE = 1073742038
+ KP_PLUSMINUS = 1073742039
+ KP_CLEAR = 1073742040
+ KP_CLEARENTRY = 1073742041
+ KP_BINARY = 1073742042
+ KP_OCTAL = 1073742043
+ KP_DECIMAL = 1073742044
+ KP_HEXADECIMAL = 1073742045
+ LCTRL = 1073742048
+ LSHIFT = 1073742049
+ LALT = 1073742050
+ LGUI = 1073742051
+ RCTRL = 1073742052
+ RSHIFT = 1073742053
+ RALT = 1073742054
+ RGUI = 1073742055
+ MODE = 1073742081
+ SLEEP = 1073742082
+ WAKE = 1073742083
+ CHANNEL_INCREMENT = 1073742084
+ CHANNEL_DECREMENT = 1073742085
+ MEDIA_PLAY = 1073742086
+ MEDIA_PAUSE = 1073742087
+ MEDIA_RECORD = 1073742088
+ MEDIA_FAST_FORWARD = 1073742089
+ MEDIA_REWIND = 1073742090
+ MEDIA_NEXT_TRACK = 1073742091
+ MEDIA_PREVIOUS_TRACK = 1073742092
+ MEDIA_STOP = 1073742093
+ MEDIA_EJECT = 1073742094
+ MEDIA_PLAY_PAUSE = 1073742095
+ MEDIA_SELECT = 1073742096
+ AC_NEW = 1073742097
+ AC_OPEN = 1073742098
+ AC_CLOSE = 1073742099
+ AC_EXIT = 1073742100
+ AC_SAVE = 1073742101
+ AC_PRINT = 1073742102
+ AC_PROPERTIES = 1073742103
+ AC_SEARCH = 1073742104
+ AC_HOME = 1073742105
+ AC_BACK = 1073742106
+ AC_FORWARD = 1073742107
+ AC_STOP = 1073742108
+ AC_REFRESH = 1073742109
+ AC_BOOKMARKS = 1073742110
+ SOFTLEFT = 1073742111
+ SOFTRIGHT = 1073742112
+ CALL = 1073742113
+ ENDCALL = 1073742114
+ # --- end ---
+
+ @property
+ def label(self) -> str:
+ """A human-readable name of a keycode.
+
+ Returns "" if the keycode doesn't have a name.
+
+ Be sure not to confuse this with ``.name``, which will return the enum
+ name rather than the human-readable name.
+
+ Example::
+
+ >>> import tcod.event
+ >>> tcod.event.KeySym.F1.label
+ 'F1'
+ >>> tcod.event.KeySym.BACKSPACE.label
+ 'Backspace'
+ """
+ return str(ffi.string(lib.SDL_GetKeyName(self.value)), encoding="utf-8")
+
+ @property
+ def keysym(self) -> KeySym:
+ """Return a keycode from a scancode.
+
+ Returns itself since it is already a :class:`KeySym`.
+
+ .. seealso::
+ :any:`Scancode.keysym`
+ """
+ return self
+
+ @property
+ def scancode(self) -> Scancode:
+ """Return a scancode from a keycode.
+
+ Based on the current keyboard layout.
+ """
+ _init_sdl_video()
+ return Scancode(lib.SDL_GetScancodeFromKey(self.value, ffi.NULL))
+
+ @classmethod
+ def _missing_(cls, value: object) -> KeySym | None:
+ if not isinstance(value, int):
+ return None
+ result = cls(0)
+ result._value_ = value
+ return result
+
+ def __eq__(self, other: object) -> bool:
+ """Compare with another KeySym value.
+
+ Comparison between :any:`KeySym` and :any:`Scancode` is not allowed and will raise :any:`TypeError`.
+ """
+ if isinstance(other, Scancode):
+ msg = "Scancode and KeySym enums can not be compared directly. Convert one or the other to the same type."
+ raise TypeError(msg)
+ return super().__eq__(other)
+
+ def __hash__(self) -> int:
+ """Return the hash for this value."""
+ return super().__hash__() # __eq__ was defined, so __hash__ must be defined
+
+ def __repr__(self) -> str:
+ """Return the fully qualified name of this enum."""
+ return f"tcod.event.{self.__class__.__name__}.{self.name}"
+
+
+if sys.version_info >= (3, 13):
+ # Alias for lower case letters removed from SDL3
+ KeySym.A._add_alias_("a")
+ KeySym.B._add_alias_("b")
+ KeySym.C._add_alias_("c")
+ KeySym.D._add_alias_("d")
+ KeySym.E._add_alias_("e")
+ KeySym.F._add_alias_("f")
+ KeySym.G._add_alias_("g")
+ KeySym.H._add_alias_("h")
+ KeySym.I._add_alias_("i")
+ KeySym.J._add_alias_("j")
+ KeySym.K._add_alias_("k")
+ KeySym.L._add_alias_("l")
+ KeySym.M._add_alias_("m")
+ KeySym.N._add_alias_("n")
+ KeySym.O._add_alias_("o")
+ KeySym.P._add_alias_("p")
+ KeySym.Q._add_alias_("q")
+ KeySym.R._add_alias_("r")
+ KeySym.S._add_alias_("s")
+ KeySym.T._add_alias_("t")
+ KeySym.U._add_alias_("u")
+ KeySym.V._add_alias_("v")
+ KeySym.W._add_alias_("w")
+ KeySym.X._add_alias_("x")
+ KeySym.Y._add_alias_("y")
+ KeySym.Z._add_alias_("z")
+
+ # Alias for numbers, since Python enum names can not be number literals
+ KeySym.N0._add_alias_("0")
+ KeySym.N1._add_alias_("1")
+ KeySym.N2._add_alias_("2")
+ KeySym.N3._add_alias_("3")
+ KeySym.N4._add_alias_("4")
+ KeySym.N5._add_alias_("5")
+ KeySym.N6._add_alias_("6")
+ KeySym.N7._add_alias_("7")
+ KeySym.N8._add_alias_("8")
+ KeySym.N9._add_alias_("9")
+
+
+def __getattr__(name: str) -> int:
+ """Migrate deprecated access of event constants."""
+ if name.startswith("BUTTON_"):
+ replacement = {
+ "BUTTON_LEFT": MouseButton.LEFT,
+ "BUTTON_MIDDLE": MouseButton.MIDDLE,
+ "BUTTON_RIGHT": MouseButton.RIGHT,
+ "BUTTON_X1": MouseButton.X1,
+ "BUTTON_X2": MouseButton.X2,
+ "BUTTON_LMASK": MouseButtonMask.LEFT,
+ "BUTTON_MMASK": MouseButtonMask.MIDDLE,
+ "BUTTON_RMASK": MouseButtonMask.RIGHT,
+ "BUTTON_X1MASK": MouseButtonMask.X1,
+ "BUTTON_X2MASK": MouseButtonMask.X2,
+ }[name]
+ warnings.warn(
+ "Key constants have been replaced with enums.\n"
+ f"'tcod.event.{name}' should be replaced with 'tcod.event.{replacement!r}'",
+ FutureWarning,
+ stacklevel=2,
+ )
+ return replacement
+
+ if name.startswith("K_") and len(name) == 3: # noqa: PLR2004
+ name = name.upper() # Silently fix single letter key symbols removed from SDL3, these are still deprecated
+
+ value: int | None = getattr(tcod.event_constants, name, None)
+ if not value:
+ msg = f"module {__name__!r} has no attribute {name!r}"
+ raise AttributeError(msg)
+ if name.startswith("SCANCODE_"):
+ scancode = name[9:]
+ if scancode.isdigit():
+ scancode = f"N{scancode}"
+ warnings.warn(
+ "Key constants have been replaced with enums.\n"
+ f"`tcod.event.{name}` should be replaced with `tcod.event.Scancode.{scancode}`",
+ FutureWarning,
+ stacklevel=2,
+ )
+ elif name.startswith("K_"):
+ sym = name[2:]
+ if sym.isdigit():
+ sym = f"N{sym}"
+ warnings.warn(
+ "Key constants have been replaced with enums.\n"
+ f"`tcod.event.{name}` should be replaced with `tcod.event.KeySym.{sym}`",
+ FutureWarning,
+ stacklevel=2,
+ )
+ elif name.startswith("KMOD_"):
+ modifier = name[5:]
+ warnings.warn(
+ "Key modifiers have been replaced with the Modifier IntFlag.\n"
+ f"`tcod.event.{modifier}` should be replaced with `tcod.event.Modifier.{modifier}`",
+ FutureWarning,
+ stacklevel=2,
+ )
+ return value
+
+
+def time_ns() -> int:
+ """Return the nanoseconds elapsed since SDL was initialized.
+
+ .. versionadded:: 21.0
+ """
+ return int(lib.SDL_GetTicksNS())
+
+
+def time() -> float:
+ """Return the seconds elapsed since SDL was initialized.
+
+ .. versionadded:: 21.0
+ """
+ return time_ns() / 1_000_000_000
+
+
+__all__ = ( # noqa: F405 RUF022
+ "Point",
+ "Modifier",
+ "MouseButton",
+ "MouseButtonMask",
+ "Event",
+ "Quit",
+ "KeyboardEvent",
+ "KeyDown",
+ "KeyUp",
+ "MouseState",
+ "MouseMotion",
+ "MouseButtonEvent",
+ "MouseButtonDown",
+ "MouseButtonUp",
+ "MouseWheel",
+ "TextInput",
+ "WindowEvent",
+ "WindowMoved",
+ "WindowResized",
+ "JoystickEvent",
+ "JoystickAxis",
+ "JoystickBall",
+ "JoystickHat",
+ "JoystickButton",
+ "JoystickDevice",
+ "ControllerEvent",
+ "ControllerAxis",
+ "ControllerButton",
+ "ControllerDevice",
+ "Undefined",
+ "get",
+ "wait",
+ "get_mouse_state",
+ "add_watch",
+ "remove_watch",
+ "EventDispatch",
+ "get_keyboard_state",
+ "get_modifier_state",
+ "Scancode",
+ "KeySym",
+ "time_ns",
+ "time",
+ # --- From event_constants.py ---
+ "MOUSEWHEEL_NORMAL",
+ "MOUSEWHEEL_FLIPPED",
+)
diff --git a/tcod/event_constants.py b/tcod/event_constants.py
new file mode 100644
index 00000000..efeecc56
--- /dev/null
+++ b/tcod/event_constants.py
@@ -0,0 +1,568 @@
+"""Event constants from SDL's C API.
+
+This module is auto-generated by `build_libtcod.py`.
+"""
+
+# --- SDL scancodes ---
+SCANCODE_UNKNOWN = 0
+SCANCODE_A = 4
+SCANCODE_B = 5
+SCANCODE_C = 6
+SCANCODE_D = 7
+SCANCODE_E = 8
+SCANCODE_F = 9
+SCANCODE_G = 10
+SCANCODE_H = 11
+SCANCODE_I = 12
+SCANCODE_J = 13
+SCANCODE_K = 14
+SCANCODE_L = 15
+SCANCODE_M = 16
+SCANCODE_N = 17
+SCANCODE_O = 18
+SCANCODE_P = 19
+SCANCODE_Q = 20
+SCANCODE_R = 21
+SCANCODE_S = 22
+SCANCODE_T = 23
+SCANCODE_U = 24
+SCANCODE_V = 25
+SCANCODE_W = 26
+SCANCODE_X = 27
+SCANCODE_Y = 28
+SCANCODE_Z = 29
+SCANCODE_1 = 30
+SCANCODE_2 = 31
+SCANCODE_3 = 32
+SCANCODE_4 = 33
+SCANCODE_5 = 34
+SCANCODE_6 = 35
+SCANCODE_7 = 36
+SCANCODE_8 = 37
+SCANCODE_9 = 38
+SCANCODE_0 = 39
+SCANCODE_RETURN = 40
+SCANCODE_ESCAPE = 41
+SCANCODE_BACKSPACE = 42
+SCANCODE_TAB = 43
+SCANCODE_SPACE = 44
+SCANCODE_MINUS = 45
+SCANCODE_EQUALS = 46
+SCANCODE_LEFTBRACKET = 47
+SCANCODE_RIGHTBRACKET = 48
+SCANCODE_BACKSLASH = 49
+SCANCODE_NONUSHASH = 50
+SCANCODE_SEMICOLON = 51
+SCANCODE_APOSTROPHE = 52
+SCANCODE_GRAVE = 53
+SCANCODE_COMMA = 54
+SCANCODE_PERIOD = 55
+SCANCODE_SLASH = 56
+SCANCODE_CAPSLOCK = 57
+SCANCODE_F1 = 58
+SCANCODE_F2 = 59
+SCANCODE_F3 = 60
+SCANCODE_F4 = 61
+SCANCODE_F5 = 62
+SCANCODE_F6 = 63
+SCANCODE_F7 = 64
+SCANCODE_F8 = 65
+SCANCODE_F9 = 66
+SCANCODE_F10 = 67
+SCANCODE_F11 = 68
+SCANCODE_F12 = 69
+SCANCODE_PRINTSCREEN = 70
+SCANCODE_SCROLLLOCK = 71
+SCANCODE_PAUSE = 72
+SCANCODE_INSERT = 73
+SCANCODE_HOME = 74
+SCANCODE_PAGEUP = 75
+SCANCODE_DELETE = 76
+SCANCODE_END = 77
+SCANCODE_PAGEDOWN = 78
+SCANCODE_RIGHT = 79
+SCANCODE_LEFT = 80
+SCANCODE_DOWN = 81
+SCANCODE_UP = 82
+SCANCODE_NUMLOCKCLEAR = 83
+SCANCODE_KP_DIVIDE = 84
+SCANCODE_KP_MULTIPLY = 85
+SCANCODE_KP_MINUS = 86
+SCANCODE_KP_PLUS = 87
+SCANCODE_KP_ENTER = 88
+SCANCODE_KP_1 = 89
+SCANCODE_KP_2 = 90
+SCANCODE_KP_3 = 91
+SCANCODE_KP_4 = 92
+SCANCODE_KP_5 = 93
+SCANCODE_KP_6 = 94
+SCANCODE_KP_7 = 95
+SCANCODE_KP_8 = 96
+SCANCODE_KP_9 = 97
+SCANCODE_KP_0 = 98
+SCANCODE_KP_PERIOD = 99
+SCANCODE_NONUSBACKSLASH = 100
+SCANCODE_APPLICATION = 101
+SCANCODE_POWER = 102
+SCANCODE_KP_EQUALS = 103
+SCANCODE_F13 = 104
+SCANCODE_F14 = 105
+SCANCODE_F15 = 106
+SCANCODE_F16 = 107
+SCANCODE_F17 = 108
+SCANCODE_F18 = 109
+SCANCODE_F19 = 110
+SCANCODE_F20 = 111
+SCANCODE_F21 = 112
+SCANCODE_F22 = 113
+SCANCODE_F23 = 114
+SCANCODE_F24 = 115
+SCANCODE_EXECUTE = 116
+SCANCODE_HELP = 117
+SCANCODE_MENU = 118
+SCANCODE_SELECT = 119
+SCANCODE_STOP = 120
+SCANCODE_AGAIN = 121
+SCANCODE_UNDO = 122
+SCANCODE_CUT = 123
+SCANCODE_COPY = 124
+SCANCODE_PASTE = 125
+SCANCODE_FIND = 126
+SCANCODE_MUTE = 127
+SCANCODE_VOLUMEUP = 128
+SCANCODE_VOLUMEDOWN = 129
+SCANCODE_KP_COMMA = 133
+SCANCODE_KP_EQUALSAS400 = 134
+SCANCODE_INTERNATIONAL1 = 135
+SCANCODE_INTERNATIONAL2 = 136
+SCANCODE_INTERNATIONAL3 = 137
+SCANCODE_INTERNATIONAL4 = 138
+SCANCODE_INTERNATIONAL5 = 139
+SCANCODE_INTERNATIONAL6 = 140
+SCANCODE_INTERNATIONAL7 = 141
+SCANCODE_INTERNATIONAL8 = 142
+SCANCODE_INTERNATIONAL9 = 143
+SCANCODE_LANG1 = 144
+SCANCODE_LANG2 = 145
+SCANCODE_LANG3 = 146
+SCANCODE_LANG4 = 147
+SCANCODE_LANG5 = 148
+SCANCODE_LANG6 = 149
+SCANCODE_LANG7 = 150
+SCANCODE_LANG8 = 151
+SCANCODE_LANG9 = 152
+SCANCODE_ALTERASE = 153
+SCANCODE_SYSREQ = 154
+SCANCODE_CANCEL = 155
+SCANCODE_CLEAR = 156
+SCANCODE_PRIOR = 157
+SCANCODE_RETURN2 = 158
+SCANCODE_SEPARATOR = 159
+SCANCODE_OUT = 160
+SCANCODE_OPER = 161
+SCANCODE_CLEARAGAIN = 162
+SCANCODE_CRSEL = 163
+SCANCODE_EXSEL = 164
+SCANCODE_KP_00 = 176
+SCANCODE_KP_000 = 177
+SCANCODE_THOUSANDSSEPARATOR = 178
+SCANCODE_DECIMALSEPARATOR = 179
+SCANCODE_CURRENCYUNIT = 180
+SCANCODE_CURRENCYSUBUNIT = 181
+SCANCODE_KP_LEFTPAREN = 182
+SCANCODE_KP_RIGHTPAREN = 183
+SCANCODE_KP_LEFTBRACE = 184
+SCANCODE_KP_RIGHTBRACE = 185
+SCANCODE_KP_TAB = 186
+SCANCODE_KP_BACKSPACE = 187
+SCANCODE_KP_A = 188
+SCANCODE_KP_B = 189
+SCANCODE_KP_C = 190
+SCANCODE_KP_D = 191
+SCANCODE_KP_E = 192
+SCANCODE_KP_F = 193
+SCANCODE_KP_XOR = 194
+SCANCODE_KP_POWER = 195
+SCANCODE_KP_PERCENT = 196
+SCANCODE_KP_LESS = 197
+SCANCODE_KP_GREATER = 198
+SCANCODE_KP_AMPERSAND = 199
+SCANCODE_KP_DBLAMPERSAND = 200
+SCANCODE_KP_VERTICALBAR = 201
+SCANCODE_KP_DBLVERTICALBAR = 202
+SCANCODE_KP_COLON = 203
+SCANCODE_KP_HASH = 204
+SCANCODE_KP_SPACE = 205
+SCANCODE_KP_AT = 206
+SCANCODE_KP_EXCLAM = 207
+SCANCODE_KP_MEMSTORE = 208
+SCANCODE_KP_MEMRECALL = 209
+SCANCODE_KP_MEMCLEAR = 210
+SCANCODE_KP_MEMADD = 211
+SCANCODE_KP_MEMSUBTRACT = 212
+SCANCODE_KP_MEMMULTIPLY = 213
+SCANCODE_KP_MEMDIVIDE = 214
+SCANCODE_KP_PLUSMINUS = 215
+SCANCODE_KP_CLEAR = 216
+SCANCODE_KP_CLEARENTRY = 217
+SCANCODE_KP_BINARY = 218
+SCANCODE_KP_OCTAL = 219
+SCANCODE_KP_DECIMAL = 220
+SCANCODE_KP_HEXADECIMAL = 221
+SCANCODE_LCTRL = 224
+SCANCODE_LSHIFT = 225
+SCANCODE_LALT = 226
+SCANCODE_LGUI = 227
+SCANCODE_RCTRL = 228
+SCANCODE_RSHIFT = 229
+SCANCODE_RALT = 230
+SCANCODE_RGUI = 231
+SCANCODE_MODE = 257
+SCANCODE_SLEEP = 258
+SCANCODE_WAKE = 259
+SCANCODE_CHANNEL_INCREMENT = 260
+SCANCODE_CHANNEL_DECREMENT = 261
+SCANCODE_MEDIA_PLAY = 262
+SCANCODE_MEDIA_PAUSE = 263
+SCANCODE_MEDIA_RECORD = 264
+SCANCODE_MEDIA_FAST_FORWARD = 265
+SCANCODE_MEDIA_REWIND = 266
+SCANCODE_MEDIA_NEXT_TRACK = 267
+SCANCODE_MEDIA_PREVIOUS_TRACK = 268
+SCANCODE_MEDIA_STOP = 269
+SCANCODE_MEDIA_EJECT = 270
+SCANCODE_MEDIA_PLAY_PAUSE = 271
+SCANCODE_MEDIA_SELECT = 272
+SCANCODE_AC_NEW = 273
+SCANCODE_AC_OPEN = 274
+SCANCODE_AC_CLOSE = 275
+SCANCODE_AC_EXIT = 276
+SCANCODE_AC_SAVE = 277
+SCANCODE_AC_PRINT = 278
+SCANCODE_AC_PROPERTIES = 279
+SCANCODE_AC_SEARCH = 280
+SCANCODE_AC_HOME = 281
+SCANCODE_AC_BACK = 282
+SCANCODE_AC_FORWARD = 283
+SCANCODE_AC_STOP = 284
+SCANCODE_AC_REFRESH = 285
+SCANCODE_AC_BOOKMARKS = 286
+SCANCODE_SOFTLEFT = 287
+SCANCODE_SOFTRIGHT = 288
+SCANCODE_CALL = 289
+SCANCODE_ENDCALL = 290
+SCANCODE_RESERVED = 400
+SCANCODE_COUNT = 512
+
+# --- SDL keyboard symbols ---
+K_UNKNOWN = 0
+K_BACKSPACE = 8
+K_TAB = 9
+K_RETURN = 13
+K_ESCAPE = 27
+K_SPACE = 32
+K_EXCLAIM = 33
+K_DBLAPOSTROPHE = 34
+K_HASH = 35
+K_DOLLAR = 36
+K_PERCENT = 37
+K_AMPERSAND = 38
+K_APOSTROPHE = 39
+K_LEFTPAREN = 40
+K_RIGHTPAREN = 41
+K_ASTERISK = 42
+K_PLUS = 43
+K_COMMA = 44
+K_MINUS = 45
+K_PERIOD = 46
+K_SLASH = 47
+K_0 = 48
+K_1 = 49
+K_2 = 50
+K_3 = 51
+K_4 = 52
+K_5 = 53
+K_6 = 54
+K_7 = 55
+K_8 = 56
+K_9 = 57
+K_COLON = 58
+K_SEMICOLON = 59
+K_LESS = 60
+K_EQUALS = 61
+K_GREATER = 62
+K_QUESTION = 63
+K_AT = 64
+K_LEFTBRACKET = 91
+K_BACKSLASH = 92
+K_RIGHTBRACKET = 93
+K_CARET = 94
+K_UNDERSCORE = 95
+K_GRAVE = 96
+K_A = 97
+K_B = 98
+K_C = 99
+K_D = 100
+K_E = 101
+K_F = 102
+K_G = 103
+K_H = 104
+K_I = 105
+K_J = 106
+K_K = 107
+K_L = 108
+K_M = 109
+K_N = 110
+K_O = 111
+K_P = 112
+K_Q = 113
+K_R = 114
+K_S = 115
+K_T = 116
+K_U = 117
+K_V = 118
+K_W = 119
+K_X = 120
+K_Y = 121
+K_Z = 122
+K_LEFTBRACE = 123
+K_PIPE = 124
+K_RIGHTBRACE = 125
+K_TILDE = 126
+K_DELETE = 127
+K_PLUSMINUS = 177
+K_EXTENDED_MASK = 536870912
+K_LEFT_TAB = 536870913
+K_LEVEL5_SHIFT = 536870914
+K_MULTI_KEY_COMPOSE = 536870915
+K_LMETA = 536870916
+K_RMETA = 536870917
+K_LHYPER = 536870918
+K_RHYPER = 536870919
+K_SCANCODE_MASK = 1073741824
+K_CAPSLOCK = 1073741881
+K_F1 = 1073741882
+K_F2 = 1073741883
+K_F3 = 1073741884
+K_F4 = 1073741885
+K_F5 = 1073741886
+K_F6 = 1073741887
+K_F7 = 1073741888
+K_F8 = 1073741889
+K_F9 = 1073741890
+K_F10 = 1073741891
+K_F11 = 1073741892
+K_F12 = 1073741893
+K_PRINTSCREEN = 1073741894
+K_SCROLLLOCK = 1073741895
+K_PAUSE = 1073741896
+K_INSERT = 1073741897
+K_HOME = 1073741898
+K_PAGEUP = 1073741899
+K_END = 1073741901
+K_PAGEDOWN = 1073741902
+K_RIGHT = 1073741903
+K_LEFT = 1073741904
+K_DOWN = 1073741905
+K_UP = 1073741906
+K_NUMLOCKCLEAR = 1073741907
+K_KP_DIVIDE = 1073741908
+K_KP_MULTIPLY = 1073741909
+K_KP_MINUS = 1073741910
+K_KP_PLUS = 1073741911
+K_KP_ENTER = 1073741912
+K_KP_1 = 1073741913
+K_KP_2 = 1073741914
+K_KP_3 = 1073741915
+K_KP_4 = 1073741916
+K_KP_5 = 1073741917
+K_KP_6 = 1073741918
+K_KP_7 = 1073741919
+K_KP_8 = 1073741920
+K_KP_9 = 1073741921
+K_KP_0 = 1073741922
+K_KP_PERIOD = 1073741923
+K_APPLICATION = 1073741925
+K_POWER = 1073741926
+K_KP_EQUALS = 1073741927
+K_F13 = 1073741928
+K_F14 = 1073741929
+K_F15 = 1073741930
+K_F16 = 1073741931
+K_F17 = 1073741932
+K_F18 = 1073741933
+K_F19 = 1073741934
+K_F20 = 1073741935
+K_F21 = 1073741936
+K_F22 = 1073741937
+K_F23 = 1073741938
+K_F24 = 1073741939
+K_EXECUTE = 1073741940
+K_HELP = 1073741941
+K_MENU = 1073741942
+K_SELECT = 1073741943
+K_STOP = 1073741944
+K_AGAIN = 1073741945
+K_UNDO = 1073741946
+K_CUT = 1073741947
+K_COPY = 1073741948
+K_PASTE = 1073741949
+K_FIND = 1073741950
+K_MUTE = 1073741951
+K_VOLUMEUP = 1073741952
+K_VOLUMEDOWN = 1073741953
+K_KP_COMMA = 1073741957
+K_KP_EQUALSAS400 = 1073741958
+K_ALTERASE = 1073741977
+K_SYSREQ = 1073741978
+K_CANCEL = 1073741979
+K_CLEAR = 1073741980
+K_PRIOR = 1073741981
+K_RETURN2 = 1073741982
+K_SEPARATOR = 1073741983
+K_OUT = 1073741984
+K_OPER = 1073741985
+K_CLEARAGAIN = 1073741986
+K_CRSEL = 1073741987
+K_EXSEL = 1073741988
+K_KP_00 = 1073742000
+K_KP_000 = 1073742001
+K_THOUSANDSSEPARATOR = 1073742002
+K_DECIMALSEPARATOR = 1073742003
+K_CURRENCYUNIT = 1073742004
+K_CURRENCYSUBUNIT = 1073742005
+K_KP_LEFTPAREN = 1073742006
+K_KP_RIGHTPAREN = 1073742007
+K_KP_LEFTBRACE = 1073742008
+K_KP_RIGHTBRACE = 1073742009
+K_KP_TAB = 1073742010
+K_KP_BACKSPACE = 1073742011
+K_KP_A = 1073742012
+K_KP_B = 1073742013
+K_KP_C = 1073742014
+K_KP_D = 1073742015
+K_KP_E = 1073742016
+K_KP_F = 1073742017
+K_KP_XOR = 1073742018
+K_KP_POWER = 1073742019
+K_KP_PERCENT = 1073742020
+K_KP_LESS = 1073742021
+K_KP_GREATER = 1073742022
+K_KP_AMPERSAND = 1073742023
+K_KP_DBLAMPERSAND = 1073742024
+K_KP_VERTICALBAR = 1073742025
+K_KP_DBLVERTICALBAR = 1073742026
+K_KP_COLON = 1073742027
+K_KP_HASH = 1073742028
+K_KP_SPACE = 1073742029
+K_KP_AT = 1073742030
+K_KP_EXCLAM = 1073742031
+K_KP_MEMSTORE = 1073742032
+K_KP_MEMRECALL = 1073742033
+K_KP_MEMCLEAR = 1073742034
+K_KP_MEMADD = 1073742035
+K_KP_MEMSUBTRACT = 1073742036
+K_KP_MEMMULTIPLY = 1073742037
+K_KP_MEMDIVIDE = 1073742038
+K_KP_PLUSMINUS = 1073742039
+K_KP_CLEAR = 1073742040
+K_KP_CLEARENTRY = 1073742041
+K_KP_BINARY = 1073742042
+K_KP_OCTAL = 1073742043
+K_KP_DECIMAL = 1073742044
+K_KP_HEXADECIMAL = 1073742045
+K_LCTRL = 1073742048
+K_LSHIFT = 1073742049
+K_LALT = 1073742050
+K_LGUI = 1073742051
+K_RCTRL = 1073742052
+K_RSHIFT = 1073742053
+K_RALT = 1073742054
+K_RGUI = 1073742055
+K_MODE = 1073742081
+K_SLEEP = 1073742082
+K_WAKE = 1073742083
+K_CHANNEL_INCREMENT = 1073742084
+K_CHANNEL_DECREMENT = 1073742085
+K_MEDIA_PLAY = 1073742086
+K_MEDIA_PAUSE = 1073742087
+K_MEDIA_RECORD = 1073742088
+K_MEDIA_FAST_FORWARD = 1073742089
+K_MEDIA_REWIND = 1073742090
+K_MEDIA_NEXT_TRACK = 1073742091
+K_MEDIA_PREVIOUS_TRACK = 1073742092
+K_MEDIA_STOP = 1073742093
+K_MEDIA_EJECT = 1073742094
+K_MEDIA_PLAY_PAUSE = 1073742095
+K_MEDIA_SELECT = 1073742096
+K_AC_NEW = 1073742097
+K_AC_OPEN = 1073742098
+K_AC_CLOSE = 1073742099
+K_AC_EXIT = 1073742100
+K_AC_SAVE = 1073742101
+K_AC_PRINT = 1073742102
+K_AC_PROPERTIES = 1073742103
+K_AC_SEARCH = 1073742104
+K_AC_HOME = 1073742105
+K_AC_BACK = 1073742106
+K_AC_FORWARD = 1073742107
+K_AC_STOP = 1073742108
+K_AC_REFRESH = 1073742109
+K_AC_BOOKMARKS = 1073742110
+K_SOFTLEFT = 1073742111
+K_SOFTRIGHT = 1073742112
+K_CALL = 1073742113
+K_ENDCALL = 1073742114
+
+# --- SDL keyboard modifiers ---
+KMOD_NONE = 0
+KMOD_LSHIFT = 1
+KMOD_RSHIFT = 2
+KMOD_SHIFT = 3
+KMOD_LEVEL5 = 4
+KMOD_LCTRL = 64
+KMOD_RCTRL = 128
+KMOD_CTRL = 192
+KMOD_LALT = 256
+KMOD_RALT = 512
+KMOD_ALT = 768
+KMOD_LGUI = 1024
+KMOD_RGUI = 2048
+KMOD_GUI = 3072
+KMOD_NUM = 4096
+KMOD_CAPS = 8192
+KMOD_MODE = 16384
+KMOD_SCROLL = 32768
+_REVERSE_MOD_TABLE = {
+ 0: "KMOD_NONE",
+ 1: "KMOD_LSHIFT",
+ 2: "KMOD_RSHIFT",
+ 3: "KMOD_SHIFT",
+ 4: "KMOD_LEVEL5",
+ 64: "KMOD_LCTRL",
+ 128: "KMOD_RCTRL",
+ 192: "KMOD_CTRL",
+ 256: "KMOD_LALT",
+ 512: "KMOD_RALT",
+ 768: "KMOD_ALT",
+ 1024: "KMOD_LGUI",
+ 2048: "KMOD_RGUI",
+ 3072: "KMOD_GUI",
+ 4096: "KMOD_NUM",
+ 8192: "KMOD_CAPS",
+ 16384: "KMOD_MODE",
+ 32768: "KMOD_SCROLL",
+}
+
+# --- SDL wheel ---
+MOUSEWHEEL_NORMAL = 0
+MOUSEWHEEL_FLIPPED = 1
+_REVERSE_WHEEL_TABLE = {
+ 0: "MOUSEWHEEL_NORMAL",
+ 1: "MOUSEWHEEL_FLIPPED",
+}
+
+__all__ = [ # noqa: RUF022
+ "MOUSEWHEEL_NORMAL",
+ "MOUSEWHEEL_FLIPPED",
+]
diff --git a/tcod/image.py b/tcod/image.py
index fa0b6da3..0f154450 100644
--- a/tcod/image.py
+++ b/tcod/image.py
@@ -1,38 +1,36 @@
+"""Libtcod functionality for handling images.
-from __future__ import absolute_import
+This module is generally seen as outdated.
+To load images you should typically use `Pillow `_ or
+`imageio `_ unless you need to use a feature exclusive to libtcod.
-import numpy as np
+**Python-tcod is unable to render pixels to consoles.**
+The best it can do with consoles is convert an image into semigraphics which can be shown on non-emulated terminals.
+For true pixel-based rendering you'll want to access the SDL rendering port at :any:`tcod.sdl.render`.
+"""
-from tcod.libtcod import ffi, lib
-from tcod.tcod import _console
+from __future__ import annotations
-class _ImageBufferArray(np.ndarray):
+from pathlib import Path
+from typing import TYPE_CHECKING, Any
- def __new__(cls, image):
- size = image.height * image.width
- self = np.frombuffer(ffi.buffer(lib.TCOD_image_get_colors()[size]),
- np.uint8)
- self = self.reshape((image.height, image.width, 3)).view(cls)
- self._image_c = image.cdata
- return self
+import numpy as np
+from typing_extensions import deprecated
- def __array_finalize__(self, obj):
- if obj is None:
- return
- self._image_c = getattr(obj, '_image_c', None)
+from tcod._internal import _console, _path_encode
+from tcod.cffi import ffi, lib
- def __repr__(self):
- return repr(self.view(np.ndarray))
+if TYPE_CHECKING:
+ from os import PathLike
- def __setitem__(self, index, value):
- """Must invalidate mipmaps on any write."""
- np.ndarray.__setitem__(self, index, value)
- if self._image_c is not None:
- lib.TCOD_image_invalidate_mipmaps(self._image_c)
+ from numpy.typing import ArrayLike, NDArray
+ import tcod.console
+
+
+class Image:
+ """A libtcod image.
-class Image(object):
- """
Args:
width (int): Width of the new Image.
height (int): Height of the new Image.
@@ -41,19 +39,54 @@ class Image(object):
width (int): Read only width of this Image.
height (int): Read only height of this Image.
"""
- def __init__(self, width, height):
+
+ def __init__(self, width: int, height: int) -> None:
+ """Initialize a blank image."""
self.width, self.height = width, height
- self.image_c = ffi.gc(lib.TCOD_image_new(width, height),
- lib.TCOD_image_delete)
+ self.image_c = ffi.gc(lib.TCOD_image_new(width, height), lib.TCOD_image_delete)
+ if self.image_c == ffi.NULL:
+ msg = "Failed to allocate image."
+ raise MemoryError(msg)
@classmethod
- def _from_cdata(cls, cdata):
- self = object.__new__(cls)
+ def _from_cdata(cls, cdata: Any) -> Image: # noqa: ANN401
+ self: Image = object.__new__(cls)
+ if cdata == ffi.NULL:
+ msg = "Pointer must not be NULL."
+ raise RuntimeError(msg)
self.image_c = cdata
self.width, self.height = self._get_size()
return self
- def clear(self, color):
+ @classmethod
+ def from_array(cls, array: ArrayLike) -> Image:
+ """Create a new Image from a copy of an array-like object.
+
+ Example:
+ >>> import numpy as np
+ >>> import tcod
+ >>> array = np.zeros((5, 5, 3), dtype=np.uint8)
+ >>> image = tcod.image.Image.from_array(array)
+
+ .. versionadded:: 11.4
+ """
+ array = np.asarray(array, dtype=np.uint8)
+ height, width, _depth = array.shape
+ image = cls(width, height)
+ image_array: NDArray[np.uint8] = np.asarray(image)
+ image_array[...] = array
+ return image
+
+ @classmethod
+ def from_file(cls, path: str | PathLike[str]) -> Image:
+ """Return a new Image loaded from the given `path`.
+
+ .. versionadded:: 16.0
+ """
+ path = Path(path).resolve(strict=True)
+ return cls._from_cdata(ffi.gc(lib.TCOD_image_load(_path_encode(path)), lib.TCOD_image_delete))
+
+ def clear(self, color: tuple[int, int, int]) -> None:
"""Fill this entire Image with color.
Args:
@@ -62,15 +95,15 @@ def clear(self, color):
"""
lib.TCOD_image_clear(self.image_c, color)
- def invert(self):
+ def invert(self) -> None:
"""Invert all colors in this Image."""
lib.TCOD_image_invert(self.image_c)
- def hflip(self):
+ def hflip(self) -> None:
"""Horizontally flip this Image."""
lib.TCOD_image_hflip(self.image_c)
- def rotate90(self, rotations=1):
+ def rotate90(self, rotations: int = 1) -> None:
"""Rotate this Image clockwise in 90 degree steps.
Args:
@@ -78,11 +111,11 @@ def rotate90(self, rotations=1):
"""
lib.TCOD_image_rotate90(self.image_c, rotations)
- def vflip(self):
+ def vflip(self) -> None:
"""Vertically flip this Image."""
lib.TCOD_image_vflip(self.image_c)
- def scale(self, width, height):
+ def scale(self, width: int, height: int) -> None:
"""Scale this Image to the new width and height.
Args:
@@ -92,7 +125,7 @@ def scale(self, width, height):
lib.TCOD_image_scale(self.image_c, width, height)
self.width, self.height = width, height
- def set_key_color(self, color):
+ def set_key_color(self, color: tuple[int, int, int]) -> None:
"""Set a color to be transparent during blitting functions.
Args:
@@ -101,7 +134,7 @@ def set_key_color(self, color):
"""
lib.TCOD_image_set_key_color(self.image_c, color)
- def get_alpha(self, x, y):
+ def get_alpha(self, x: int, y: int) -> int:
"""Get the Image alpha of the pixel at x, y.
Args:
@@ -112,15 +145,15 @@ def get_alpha(self, x, y):
int: The alpha value of the pixel.
With 0 being fully transparent and 255 being fully opaque.
"""
- return lib.TCOD_image_get_alpha(self.image_c, x, y)
+ return int(lib.TCOD_image_get_alpha(self.image_c, x, y))
- def refresh_console(self, console):
- """Update an Image created with :any:`tcod.image_from_console`.
+ def refresh_console(self, console: tcod.console.Console) -> None:
+ """Update an Image created with :any:`libtcodpy.image_from_console`.
The console used with this function should have the same width and
- height as the Console given to :any:`tcod.image_from_console`.
+ height as the Console given to :any:`libtcodpy.image_from_console`.
The font width and height must also be the same as when
- :any:`tcod.image_from_console` was called.
+ :any:`libtcodpy.image_from_console` was called.
Args:
console (Console): A Console with a pixel width and height
@@ -128,18 +161,18 @@ def refresh_console(self, console):
"""
lib.TCOD_image_refresh_console(self.image_c, _console(console))
- def _get_size(self):
+ def _get_size(self) -> tuple[int, int]:
"""Return the (width, height) for this Image.
Returns:
Tuple[int, int]: The (width, height) of this Image
"""
- w = ffi.new('int *')
- h = ffi.new('int *')
+ w = ffi.new("int *")
+ h = ffi.new("int *")
lib.TCOD_image_get_size(self.image_c, w, h)
return w[0], h[0]
- def get_pixel(self, x, y):
+ def get_pixel(self, x: int, y: int) -> tuple[int, int, int]:
"""Get the color of a pixel in this Image.
Args:
@@ -154,7 +187,7 @@ def get_pixel(self, x, y):
color = lib.TCOD_image_get_pixel(self.image_c, x, y)
return color.r, color.g, color.b
- def get_mipmap_pixel(self, left, top, right, bottom):
+ def get_mipmap_pixel(self, left: float, top: float, right: float, bottom: float) -> tuple[int, int, int]:
"""Get the average color of a rectangle in this Image.
Parameters should stay within the following limits:
@@ -162,21 +195,20 @@ def get_mipmap_pixel(self, left, top, right, bottom):
* 0 <= top < bottom < Image.height
Args:
- left (int): Left corner of the region.
- top (int): Top corner of the region.
- right (int): Right corner of the region.
- bottom (int): Bottom corner of the region.
+ left (float): Left corner of the region.
+ top (float): Top corner of the region.
+ right (float): Right corner of the region.
+ bottom (float): Bottom corner of the region.
Returns:
Tuple[int, int, int]:
An (r, g, b) tuple containing the averaged color value.
Values are in a 0 to 255 range.
"""
- color = lib.TCOD_image_get_mipmap_pixel(self.image_c,
- left, top, right, bottom)
+ color = lib.TCOD_image_get_mipmap_pixel(self.image_c, left, top, right, bottom)
return (color.r, color.g, color.b)
- def put_pixel(self, x, y, color):
+ def put_pixel(self, x: int, y: int, color: tuple[int, int, int]) -> None:
"""Change a pixel on this Image.
Args:
@@ -187,13 +219,22 @@ def put_pixel(self, x, y, color):
"""
lib.TCOD_image_put_pixel(self.image_c, x, y, color)
- def blit(self, console, x, y, bg_blend, scale_x, scale_y, angle):
+ def blit( # noqa: PLR0913
+ self,
+ console: tcod.console.Console,
+ x: float,
+ y: float,
+ bg_blend: int,
+ scale_x: float,
+ scale_y: float,
+ angle: float,
+ ) -> None:
"""Blit onto a Console using scaling and rotation.
Args:
console (Console): Blit destination Console.
- x (int): Console X position for the center of the Image blit.
- y (int): Console Y position for the center of the Image blit.
+ x (float): Console X position for the center of the Image blit.
+ y (float): Console Y position for the center of the Image blit.
The Image blit is centered on this position.
bg_blend (int): Background blending mode to use.
scale_x (float): Scaling along Image x axis.
@@ -203,10 +244,25 @@ def blit(self, console, x, y, bg_blend, scale_x, scale_y, angle):
angle (float): Rotation angle in radians. (Clockwise?)
"""
lib.TCOD_image_blit(
- self.image_c, _console(console),
- x, y, bg_blend, scale_x, scale_y, angle)
-
- def blit_rect(self, console, x, y, width, height, bg_blend):
+ self.image_c,
+ _console(console),
+ x,
+ y,
+ bg_blend,
+ scale_x,
+ scale_y,
+ angle,
+ )
+
+ def blit_rect( # noqa: PLR0913
+ self,
+ console: tcod.console.Console,
+ x: int,
+ y: int,
+ width: int,
+ height: int,
+ bg_blend: int,
+ ) -> None:
"""Blit onto a Console without scaling or rotation.
Args:
@@ -217,11 +273,18 @@ def blit_rect(self, console, x, y, width, height, bg_blend):
height (int): Use -1 for Image height.
bg_blend (int): Background blending mode to use.
"""
- lib.TCOD_image_blit_rect(
- self.image_c, _console(console), x, y, width, height, bg_blend)
-
- def blit_2x(self, console, dest_x, dest_y,
- img_x=0, img_y=0, img_width=-1, img_height=-1):
+ lib.TCOD_image_blit_rect(self.image_c, _console(console), x, y, width, height, bg_blend)
+
+ def blit_2x( # noqa: PLR0913
+ self,
+ console: tcod.console.Console,
+ dest_x: int,
+ dest_y: int,
+ img_x: int = 0,
+ img_y: int = 0,
+ img_width: int = -1,
+ img_height: int = -1,
+ ) -> None:
"""Blit onto a Console with double resolution.
Args:
@@ -236,13 +299,120 @@ def blit_2x(self, console, dest_x, dest_y,
Use -1 for the full Image height.
"""
lib.TCOD_image_blit_2x(
- self.image_c, _console(console),
- dest_x, dest_y, img_x, img_y, img_width, img_height)
-
- def save_as(self, filename):
+ self.image_c,
+ _console(console),
+ dest_x,
+ dest_y,
+ img_x,
+ img_y,
+ img_width,
+ img_height,
+ )
+
+ def save_as(self, filename: str | PathLike[str]) -> None:
"""Save the Image to a 32-bit .bmp or .png file.
Args:
filename (Text): File path to same this Image.
+
+ .. versionchanged:: 16.0
+ Added PathLike support.
"""
- lib.TCOD_image_save(self.image_c, filename.encode('utf-8'))
+ lib.TCOD_image_save(self.image_c, _path_encode(Path(filename)))
+
+ @property
+ def __array_interface__(self) -> dict[str, Any]:
+ """Return an interface for this images pixel buffer.
+
+ Use :any:`numpy.asarray` to get the read-write array of this Image.
+
+ This will often return an RGB array, but could also return an RGBA
+ array or fail silently. Future versions might change what type of
+ array is returned.
+
+ You can use ``dtype=numpy.uint8`` to ensure that errors are not ignored
+ by NumPy.
+
+ .. versionadded:: 11.4
+ """
+ strides = None
+ if self.image_c.mipmaps: # Libtcod RGB array.
+ depth = 3
+ data = int(ffi.cast("size_t", self.image_c.mipmaps[0].buf))
+ else:
+ msg = "Image has no initialized data."
+ raise TypeError(msg)
+ return {
+ "shape": (self.height, self.width, depth),
+ "typestr": "|u1",
+ "data": (data, False),
+ "strides": strides,
+ "version": 3,
+ }
+
+
+@deprecated(
+ "This function may be removed in the future."
+ " It's recommended to load images with a more complete image library such as python-Pillow or python-imageio.",
+)
+def load(filename: str | PathLike[str]) -> NDArray[np.uint8]:
+ """Load a PNG file as an RGBA array.
+
+ `filename` is the name of the file to load.
+
+ The returned array is in the shape: `(height, width, RGBA)`.
+
+ .. versionadded:: 11.4
+ """
+ filename = Path(filename).resolve(strict=True)
+ image = Image._from_cdata(ffi.gc(lib.TCOD_image_load(_path_encode(filename)), lib.TCOD_image_delete))
+ array: NDArray[np.uint8] = np.asarray(image, dtype=np.uint8)
+ height, width, depth = array.shape
+ if depth == 3: # noqa: PLR2004
+ array = np.concatenate(
+ (
+ array,
+ np.full((height, width, 1), fill_value=255, dtype=np.uint8),
+ ),
+ axis=2,
+ )
+ return array
+
+
+class _TempImage:
+ """An Image-like container for NumPy arrays."""
+
+ def __init__(self, array: ArrayLike) -> None:
+ """Initialize an image from the given array. May copy or reference the array."""
+ self._array: NDArray[np.uint8] = np.ascontiguousarray(array, dtype=np.uint8)
+ height, width, depth = self._array.shape
+ if depth != 3: # noqa: PLR2004
+ msg = f"Array must have RGB channels. Shape is: {self._array.shape!r}"
+ raise TypeError(msg)
+ self._buffer = ffi.from_buffer("TCOD_color_t[]", self._array)
+ self._mipmaps = ffi.new(
+ "struct TCOD_mipmap_*",
+ {
+ "width": width,
+ "height": height,
+ "fwidth": width,
+ "fheight": height,
+ "buf": self._buffer,
+ "dirty": True,
+ },
+ )
+ self.image_c = ffi.new(
+ "TCOD_Image*",
+ {
+ "nb_mipmaps": 1,
+ "mipmaps": self._mipmaps,
+ "has_key_color": False,
+ },
+ )
+
+
+def _as_image(image: ArrayLike | Image | _TempImage) -> _TempImage | Image:
+ """Convert this input into an Image-like object."""
+ if isinstance(image, (Image, _TempImage)):
+ return image
+ return _TempImage(image)
diff --git a/tcod/libtcod.py b/tcod/libtcod.py
deleted file mode 100644
index afd2cdc3..00000000
--- a/tcod/libtcod.py
+++ /dev/null
@@ -1,63 +0,0 @@
-"""This module handles loading of the libtcod cffi API.
-"""
-from __future__ import absolute_import as _
-
-import sys as _sys
-import os as _os
-
-import platform as _platform
-
-
-from tcod import __path__
-
-if _sys.platform == 'win32':
- # add Windows dll's to PATH
- _bits, _linkage = _platform.architecture()
- _os.environ['PATH'] = '%s;%s' % (
- _os.path.join(__path__[0], 'x86' if _bits == '32bit' else 'x64'),
- _os.environ['PATH'],
- )
-
-NOISE_DEFAULT_HURST = 0.5
-NOISE_DEFAULT_LACUNARITY = 2.0
-
-def FOV_PERMISSIVE(p) :
- return FOV_PERMISSIVE_0+p
-
-def BKGND_ALPHA(a):
- return BKGND_ALPH | (int(a * 255) << 8)
-
-def BKGND_ADDALPHA(a):
- return BKGND_ADDA | (int(a * 255) << 8)
-
-class _Mock(object):
- """Mock object needed for ReadTheDocs."""
-
- CData = () # This gets passed to an isinstance call.
-
- @staticmethod
- def def_extern():
- """Pass def_extern call silently."""
- return lambda func: func
-
- def __getattr__(self, attr):
- """This object pretends to have everything."""
- return self
-
- def __call__(self, *args, **kargs):
- """Suppress any other calls"""
- return self
-
- def __str__(self):
- """Just have ? in case anything leaks as a parameter default."""
- return '?'
-
-
-if _os.environ.get('READTHEDOCS'):
- # Mock the lib and ffi objects needed to compile docs for readthedocs.io
- # Allows an import without building the cffi module first.
- lib = ffi = _Mock()
-else:
- from tcod._libtcod import lib, ffi
-
-from tcod.constants import *
diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py
index 2c16368a..bae62a1f 100644
--- a/tcod/libtcodpy.py
+++ b/tcod/libtcodpy.py
@@ -1,43 +1,93 @@
-"""This module handles backward compatibility with the ctypes libtcodpy module.
-"""
+"""This module handles backward compatibility with the ctypes libtcodpy module."""
-from __future__ import absolute_import as _
+from __future__ import annotations
-import os
+import atexit
import sys
-
-import threading as _threading
-from typing import Any, AnyStr, Optional, Sequence, Tuple
+import threading
import warnings
+from pathlib import Path
+from typing import TYPE_CHECKING, Any, Final, Literal
-import numpy as _np
import numpy as np
-
-from tcod.libtcod import *
-
-from tcod._internal import deprecate
-
-from tcod.tcod import _int, _unpack_char_p
-from tcod.tcod import _bytes, _unicode, _fmt_bytes, _fmt_unicode
-from tcod.tcod import _CDataWrapper
-from tcod.tcod import _PropagateException
-from tcod.tcod import _console
+from typing_extensions import deprecated
import tcod.bsp
-from tcod.color import Color
import tcod.console
+import tcod.constants
import tcod.image
+import tcod.los
import tcod.map
import tcod.noise
import tcod.path
import tcod.random
+from tcod._internal import (
+ _bytes,
+ _CDataWrapper,
+ _check,
+ _check_p,
+ _check_warn,
+ _console,
+ _fmt,
+ _int,
+ _path_encode,
+ _PropagateException,
+ _unicode,
+ _unpack_char_p,
+ deprecate,
+)
+from tcod.cffi import ffi, lib
+from tcod.color import Color
+from tcod.constants import * # noqa: F403
+from tcod.constants import (
+ BKGND_ADDA,
+ BKGND_ALPH,
+ BKGND_DEFAULT,
+ BKGND_SET,
+ FONT_LAYOUT_ASCII_INCOL,
+ FOV_PERMISSIVE_0,
+ FOV_RESTRICTIVE,
+ KEY_RELEASED,
+ NOISE_DEFAULT,
+)
+
+if TYPE_CHECKING:
+ from collections.abc import Callable, Hashable, Iterable, Iterator, Sequence
+ from os import PathLike
+
+ from numpy.typing import NDArray
+
+# Functions are too deprecated to make changes.
+# ruff: noqa: ANN401 PLR0913 D102 D103 D105 D107
Bsp = tcod.bsp.BSP
+NB_FOV_ALGORITHMS = 13
+
+NOISE_DEFAULT_HURST = 0.5
+NOISE_DEFAULT_LACUNARITY = 2.0
+
-class ConsoleBuffer(object):
- """Simple console that allows direct (fast) access to cells. simplifies
- use of the "fill" functions.
+def FOV_PERMISSIVE(p: int) -> int: # noqa: N802
+ return FOV_PERMISSIVE_0 + p
+
+
+def BKGND_ALPHA(a: int) -> int: # noqa: N802
+ return BKGND_ALPH | (int(a * 255) << 8)
+
+
+def BKGND_ADDALPHA(a: int) -> int: # noqa: N802
+ return BKGND_ADDA | (int(a * 255) << 8)
+
+
+_PENDING_DEPRECATE_MSG: Final = (
+ "This function may be deprecated in the future. Consider raising an issue on GitHub if you need this feature."
+)
+
+
+@deprecated("Console array attributes perform better than this class.")
+class ConsoleBuffer:
+ """Simple console that allows direct (fast) access to cells. Simplifies use of the "fill" functions.
.. deprecated:: 6.0
Console array attributes perform better than this class.
@@ -53,22 +103,40 @@ class ConsoleBuffer(object):
fore_b (int): Blue foreground color, from 0 to 255.
char (AnyStr): A single character str or bytes object.
"""
- def __init__(self, width, height, back_r=0, back_g=0, back_b=0, fore_r=0, fore_g=0, fore_b=0, char=' '):
- """initialize with given width and height. values to fill the buffer
- are optional, defaults to black with no characters.
+
+ def __init__(
+ self,
+ width: int,
+ height: int,
+ back_r: int = 0,
+ back_g: int = 0,
+ back_b: int = 0,
+ fore_r: int = 0,
+ fore_g: int = 0,
+ fore_b: int = 0,
+ char: str = " ",
+ ) -> None:
+ """Initialize with given width and height.
+
+ Values to fill the buffer are optional, defaults to black with no characters.
"""
- warnings.warn(
- "Console array attributes perform better than this class.",
- DeprecationWarning,
- stacklevel=2,
- )
self.width = width
self.height = height
self.clear(back_r, back_g, back_b, fore_r, fore_g, fore_b, char)
- def clear(self, back_r=0, back_g=0, back_b=0, fore_r=0, fore_g=0, fore_b=0, char=' '):
- """Clears the console. Values to fill it with are optional, defaults
- to black with no characters.
+ def clear(
+ self,
+ back_r: int = 0,
+ back_g: int = 0,
+ back_b: int = 0,
+ fore_r: int = 0,
+ fore_g: int = 0,
+ fore_b: int = 0,
+ char: str = " ",
+ ) -> None:
+ """Clear the console.
+
+ Values to fill it with are optional, defaults to black with no characters.
Args:
back_r (int): Red background color, from 0 to 255.
@@ -88,8 +156,8 @@ def clear(self, back_r=0, back_g=0, back_b=0, fore_r=0, fore_g=0, fore_b=0, char
self.fore_b = [fore_b] * n
self.char = [ord(char)] * n
- def copy(self):
- """Returns a copy of this ConsoleBuffer.
+ def copy(self) -> ConsoleBuffer:
+ """Return a copy of this ConsoleBuffer.
Returns:
ConsoleBuffer: A new ConsoleBuffer copy.
@@ -106,7 +174,7 @@ def copy(self):
other.char = list(self.char)
return other
- def set_fore(self, x, y, r, g, b, char):
+ def set_fore(self, x: int, y: int, r: int, g: int, b: int, char: str) -> None:
"""Set the character and foreground color of one cell.
Args:
@@ -123,7 +191,7 @@ def set_fore(self, x, y, r, g, b, char):
self.fore_b[i] = b
self.char[i] = ord(char)
- def set_back(self, x, y, r, g, b):
+ def set_back(self, x: int, y: int, r: int, g: int, b: int) -> None:
"""Set the background color of one cell.
Args:
@@ -132,14 +200,24 @@ def set_back(self, x, y, r, g, b):
r (int): Red background color, from 0 to 255.
g (int): Green background color, from 0 to 255.
b (int): Blue background color, from 0 to 255.
- char (AnyStr): A single character str or bytes object.
"""
i = self.width * y + x
self.back_r[i] = r
self.back_g[i] = g
self.back_b[i] = b
- def set(self, x, y, back_r, back_g, back_b, fore_r, fore_g, fore_b, char):
+ def set(
+ self,
+ x: int,
+ y: int,
+ back_r: int,
+ back_g: int,
+ back_b: int,
+ fore_r: int,
+ fore_g: int,
+ fore_b: int,
+ char: str,
+ ) -> None:
"""Set the background color, foreground color and character of one cell.
Args:
@@ -162,7 +240,12 @@ def set(self, x, y, back_r, back_g, back_b, fore_r, fore_g, fore_b, char):
self.fore_b[i] = fore_b
self.char[i] = ord(char)
- def blit(self, dest, fill_fore=True, fill_back=True):
+ def blit(
+ self,
+ dest: tcod.console.Console,
+ fill_fore: bool = True, # noqa: FBT001, FBT002
+ fill_back: bool = True, # noqa: FBT001, FBT002
+ ) -> None:
"""Use libtcod's "fill" functions to write the buffer to a console.
Args:
@@ -174,9 +257,9 @@ def blit(self, dest, fill_fore=True, fill_back=True):
"""
if not dest:
dest = tcod.console.Console._from_cdata(ffi.NULL)
- if (dest.width != self.width or
- dest.height != self.height):
- raise ValueError('ConsoleBuffer.blit: Destination console has an incorrect size.')
+ if dest.width != self.width or dest.height != self.height:
+ msg = "ConsoleBuffer.blit: Destination console has an incorrect size."
+ raise ValueError(msg)
if fill_back:
bg = dest.bg.ravel()
@@ -191,8 +274,10 @@ def blit(self, dest, fill_fore=True, fill_back=True):
fg[2::3] = self.fore_b
dest.ch.ravel()[:] = self.char
+
+@deprecated("Using this class is not recommended.")
class Dice(_CDataWrapper):
- """
+ """A libtcod dice object.
Args:
nb_dices (int): Number of dice.
@@ -205,51 +290,54 @@ class Dice(_CDataWrapper):
which is tied to a CData object.
"""
- def __init__(self, *args, **kargs):
- warnings.warn("Using this class is not recommended.",
- DeprecationWarning, stacklevel=2)
- super(Dice, self).__init__(*args, **kargs)
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
+ super().__init__(*args, **kwargs)
if self.cdata == ffi.NULL:
- self._init(*args, **kargs)
-
- def _init(self, nb_dices=0, nb_faces=0, multiplier=0, addsub=0):
- self.cdata = ffi.new('TCOD_dice_t*')
+ self._init(*args, **kwargs)
+
+ def _init(
+ self,
+ nb_dices: int = 0,
+ nb_faces: int = 0,
+ multiplier: float = 0,
+ addsub: float = 0,
+ ) -> None:
+ self.cdata = ffi.new("TCOD_dice_t*")
self.nb_dices = nb_dices
self.nb_faces = nb_faces
self.multiplier = multiplier
self.addsub = addsub
- def _get_nb_dices(self):
+ @property
+ def nb_dices(self) -> int:
return self.nb_rolls
- def _set_nb_dices(self, value):
+
+ @nb_dices.setter
+ def nb_dices(self, value: int) -> None:
self.nb_rolls = value
- nb_dices = property(_get_nb_dices, _set_nb_dices)
- def __str__(self):
- add = '+(%s)' % self.addsub if self.addsub != 0 else ''
- return '%id%ix%s%s' % (self.nb_dices, self.nb_faces,
- self.multiplier, add)
+ def __str__(self) -> str:
+ add = f"+({self.addsub})" if self.addsub != 0 else ""
+ return f"{self.nb_dices}d{self.nb_faces}x{self.multiplier}{add}"
- def __repr__(self):
- return ('%s(nb_dices=%r,nb_faces=%r,multiplier=%r,addsub=%r)' %
- (self.__class__.__name__, self.nb_dices, self.nb_faces,
- self.multiplier, self.addsub))
+ def __repr__(self) -> str:
+ return f"{self.__class__.__name__}(nb_dices={self.nb_dices!r},nb_faces={self.nb_faces!r},multiplier={self.multiplier!r},addsub={self.addsub!r})"
# reverse lookup table for KEY_X attributes, used by Key.__repr__
-_LOOKUP_VK = {
- value:'KEY_%s' % key[6:] for key,value in lib.__dict__.items()
- if key.startswith('TCODK')
- }
+_LOOKUP_VK = {value: f"KEY_{key[6:]}" for key, value in lib.__dict__.items() if key.startswith("TCODK")}
+
class Key(_CDataWrapper):
- """Key Event instance
+ r"""Key Event instance.
Attributes:
vk (int): TCOD_keycode_t key code
c (int): character if vk == TCODK_CHAR else 0
- text (Text): text[TCOD_KEY_TEXT_SIZE]; text if vk == TCODK_TEXT else text[0] == '\0'
- pressed (bool): does this correspond to a key press or key release event ?
+ text (Text): text[TCOD_KEY_TEXT_SIZE];
+ text if vk == TCODK_TEXT else text[0] == '\0'
+ pressed (bool): does this correspond to a key press or key release
+ event?
lalt (bool): True when left alt is held.
lctrl (bool): True when left control is held.
lmeta (bool): True when left meta key is held.
@@ -257,18 +345,42 @@ class Key(_CDataWrapper):
rctrl (bool): True when right control is held.
rmeta (bool): True when right meta key is held.
shift (bool): True when any shift is held.
+
+ .. deprecated:: 9.3
+ Use events from the :any:`tcod.event` module instead.
"""
- _BOOL_ATTRIBUTES = ('lalt', 'lctrl', 'lmeta',
- 'ralt', 'rctrl', 'rmeta', 'pressed', 'shift')
+ cdata: Any
- def __init__(self, vk=0, c=0, text='', pressed=False,
- lalt=False, lctrl=False, lmeta=False,
- ralt=False, rctrl=False, rmeta=False, shift=False):
+ _BOOL_ATTRIBUTES = (
+ "lalt",
+ "lctrl",
+ "lmeta",
+ "ralt",
+ "rctrl",
+ "rmeta",
+ "pressed",
+ "shift",
+ )
+
+ def __init__(
+ self,
+ vk: int = 0,
+ c: int = 0,
+ text: str = "",
+ pressed: bool = False, # noqa: FBT001, FBT002
+ lalt: bool = False, # noqa: FBT001, FBT002
+ lctrl: bool = False, # noqa: FBT001, FBT002
+ lmeta: bool = False, # noqa: FBT001, FBT002
+ ralt: bool = False, # noqa: FBT001, FBT002
+ rctrl: bool = False, # noqa: FBT001, FBT002
+ rmeta: bool = False, # noqa: FBT001, FBT002
+ shift: bool = False, # noqa: FBT001, FBT002
+ ) -> None:
if isinstance(vk, ffi.CData):
self.cdata = vk
return
- self.cdata = ffi.new('TCOD_key_t*')
+ self.cdata = ffi.new("TCOD_key_t*")
self.vk = vk
self.c = c
self.text = text
@@ -281,41 +393,51 @@ def __init__(self, vk=0, c=0, text='', pressed=False,
self.rmeta = rmeta
self.shift = shift
- def __getattr__(self, attr):
+ def __getattr__(self, attr: str) -> Any:
if attr in self._BOOL_ATTRIBUTES:
return bool(getattr(self.cdata, attr))
- if attr == 'c':
+ if attr == "c":
return ord(self.cdata.c)
- if attr == 'text':
+ if attr == "text":
return ffi.string(self.cdata.text).decode()
- return super(Key, self).__getattr__(attr)
+ return super().__getattr__(attr)
- def __setattr__(self, attr, value):
- if attr == 'c':
- self.cdata.c = chr(value).encode('latin-1')
- elif attr == 'text':
+ def __setattr__(self, attr: str, value: Any) -> None:
+ if attr == "c":
+ self.cdata.c = chr(value).encode("latin-1")
+ elif attr == "text":
self.cdata.text = value.encode()
else:
- super(Key, self).__setattr__(attr, value)
+ super().__setattr__(attr, value)
- def __repr__(self):
+ def __repr__(self) -> str:
"""Return a representation of this Key object."""
params = []
- params.append('pressed=%r, vk=tcod.%s' %
- (self.pressed, _LOOKUP_VK[self.vk]))
+ params.append(f"pressed={self.pressed!r}, vk=libtcodpy.{_LOOKUP_VK[self.vk]}")
if self.c:
- params.append('c=ord(%r)' % chr(self.c))
+ params.append(f"c=ord({chr(self.c)!r})")
if self.text:
- params.append('text=%r' % self.text)
- for attr in ['shift', 'lalt', 'lctrl', 'lmeta',
- 'ralt', 'rctrl', 'rmeta']:
+ params.append(f"text={self.text!r}")
+ for attr in [
+ "shift",
+ "lalt",
+ "lctrl",
+ "lmeta",
+ "ralt",
+ "rctrl",
+ "rmeta",
+ ]:
if getattr(self, attr):
- params.append('%s=%r' % (attr, getattr(self, attr)))
- return 'tcod.Key(%s)' % ', '.join(params)
+ params.append(f"{attr}={getattr(self, attr)!r}") # noqa: PERF401
+ return "libtcodpy.Key({})".format(", ".join(params))
+
+ @property
+ def key_p(self) -> Any:
+ return self.cdata
class Mouse(_CDataWrapper):
- """Mouse event instance
+ """Mouse event instance.
Attributes:
x (int): Absolute mouse position at pixel x.
@@ -334,14 +456,27 @@ class Mouse(_CDataWrapper):
mbutton_pressed (bool): Middle button pressed event.
wheel_up (bool): Wheel up event.
wheel_down (bool): Wheel down event.
- """
- def __init__(self, x=0, y=0, dx=0, dy=0, cx=0, cy=0, dcx=0, dcy=0,
- **kargs):
+ .. deprecated:: 9.3
+ Use events from the :any:`tcod.event` module instead.
+ """
+
+ def __init__(
+ self,
+ x: int = 0,
+ y: int = 0,
+ dx: int = 0,
+ dy: int = 0,
+ cx: int = 0,
+ cy: int = 0,
+ dcx: int = 0,
+ dcy: int = 0,
+ **kwargs: Any,
+ ) -> None:
if isinstance(x, ffi.CData):
self.cdata = x
return
- self.cdata = ffi.new('TCOD_mouse_t*')
+ self.cdata = ffi.new("TCOD_mouse_t*")
self.x = x
self.y = y
self.dx = dx
@@ -350,25 +485,37 @@ def __init__(self, x=0, y=0, dx=0, dy=0, cx=0, cy=0, dcx=0, dcy=0,
self.cy = cy
self.dcx = dcx
self.dcy = dcy
- for attr, value in kargs.items():
+ for attr, value in kwargs.items():
setattr(self, attr, value)
- def __repr__(self):
+ def __repr__(self) -> str:
"""Return a representation of this Mouse object."""
params = []
- for attr in ['x', 'y', 'dx', 'dy', 'cx', 'cy', 'dcx', 'dcy']:
+ for attr in ["x", "y", "dx", "dy", "cx", "cy", "dcx", "dcy"]:
if getattr(self, attr) == 0:
continue
- params.append('%s=%r' % (attr, getattr(self, attr)))
- for attr in ['lbutton', 'rbutton', 'mbutton',
- 'lbutton_pressed', 'rbutton_pressed', 'mbutton_pressed',
- 'wheel_up', 'wheel_down']:
+ params.append(f"{attr}={getattr(self, attr)!r}")
+ for attr in [
+ "lbutton",
+ "rbutton",
+ "mbutton",
+ "lbutton_pressed",
+ "rbutton_pressed",
+ "mbutton_pressed",
+ "wheel_up",
+ "wheel_down",
+ ]:
if getattr(self, attr):
- params.append('%s=%r' % (attr, getattr(self, attr)))
- return 'tcod.Mouse(%s)' % ', '.join(params)
+ params.append(f"{attr}={getattr(self, attr)!r}") # noqa: PERF401
+ return "libtcodpy.Mouse({})".format(", ".join(params))
+
+ @property
+ def mouse_p(self) -> Any:
+ return self.cdata
-def bsp_new_with_size(x, y, w, h):
+@deprecate("Call tcod.bsp.BSP(x, y, width, height) instead.", category=FutureWarning)
+def bsp_new_with_size(x: int, y: int, w: int, h: int) -> tcod.bsp.BSP:
"""Create a new BSP instance with the given rectangle.
Args:
@@ -385,24 +532,41 @@ def bsp_new_with_size(x, y, w, h):
"""
return Bsp(x, y, w, h)
-def bsp_split_once(node, horizontal, position):
- """
+
+@deprecate("Call node.split_once instead.", category=FutureWarning)
+def bsp_split_once(node: tcod.bsp.BSP, horizontal: bool, position: int) -> None: # noqa: FBT001
+ """Deprecated function.
+
.. deprecated:: 2.0
Use :any:`BSP.split_once` instead.
"""
node.split_once(horizontal, position)
-def bsp_split_recursive(node, randomizer, nb, minHSize, minVSize, maxHRatio,
- maxVRatio):
- """
+
+@deprecate("Call node.split_recursive instead.", category=FutureWarning)
+def bsp_split_recursive(
+ node: tcod.bsp.BSP,
+ randomizer: Literal[0] | tcod.random.Random | None,
+ nb: int,
+ minHSize: int, # noqa: N803
+ minVSize: int, # noqa: N803
+ maxHRatio: float, # noqa: N803
+ maxVRatio: float, # noqa: N803
+) -> None:
+ """Deprecated function.
+
.. deprecated:: 2.0
Use :any:`BSP.split_recursive` instead.
"""
- node.split_recursive(nb, minHSize, minVSize,
- maxHRatio, maxVRatio, randomizer)
+ if randomizer == 0:
+ randomizer = None
+ node.split_recursive(nb, minHSize, minVSize, maxHRatio, maxVRatio, randomizer)
+
+
+@deprecate("Assign values via attribute instead.", category=FutureWarning)
+def bsp_resize(node: tcod.bsp.BSP, x: int, y: int, w: int, h: int) -> None:
+ """Deprecated function.
-def bsp_resize(node, x, y, w, h):
- """
.. deprecated:: 2.0
Assign directly to :any:`BSP` attributes instead.
"""
@@ -411,96 +575,149 @@ def bsp_resize(node, x, y, w, h):
node.width = w
node.height = h
-def bsp_left(node):
- """
+
+@deprecate("Access children with 'node.children' instead.")
+def bsp_left(node: tcod.bsp.BSP) -> tcod.bsp.BSP | None:
+ """Deprecated function.
+
.. deprecated:: 2.0
Use :any:`BSP.children` instead.
"""
return None if not node.children else node.children[0]
-def bsp_right(node):
- """
+
+@deprecate("Access children with 'node.children' instead.")
+def bsp_right(node: tcod.bsp.BSP) -> tcod.bsp.BSP | None:
+ """Deprecated function.
+
.. deprecated:: 2.0
Use :any:`BSP.children` instead.
"""
return None if not node.children else node.children[1]
-def bsp_father(node):
- """
+
+@deprecate("Get the parent with 'node.parent' instead.", category=FutureWarning)
+def bsp_father(node: tcod.bsp.BSP) -> tcod.bsp.BSP | None:
+ """Deprecated function.
+
.. deprecated:: 2.0
Use :any:`BSP.parent` instead.
"""
return node.parent
-def bsp_is_leaf(node):
- """
+
+@deprecate("Check for children with 'bool(node.children)' instead.", category=FutureWarning)
+def bsp_is_leaf(node: tcod.bsp.BSP) -> bool:
+ """Deprecated function.
+
.. deprecated:: 2.0
Use :any:`BSP.children` instead.
"""
return not node.children
-def bsp_contains(node, cx, cy):
- """
+
+@deprecate("Use 'node.contains' instead.", category=FutureWarning)
+def bsp_contains(node: tcod.bsp.BSP, cx: int, cy: int) -> bool:
+ """Deprecated function.
+
.. deprecated:: 2.0
Use :any:`BSP.contains` instead.
"""
return node.contains(cx, cy)
-def bsp_find_node(node, cx, cy):
- """
+
+@deprecate("Use 'node.find_node' instead.", category=FutureWarning)
+def bsp_find_node(node: tcod.bsp.BSP, cx: int, cy: int) -> tcod.bsp.BSP | None:
+ """Deprecated function.
+
.. deprecated:: 2.0
Use :any:`BSP.find_node` instead.
"""
return node.find_node(cx, cy)
-def _bsp_traverse(node_iter, callback, userData):
- """pack callback into a handle for use with the callback
- _pycall_bsp_callback
- """
+
+def _bsp_traverse(
+ node_iter: Iterable[tcod.bsp.BSP],
+ callback: Callable[[tcod.bsp.BSP, Any], None],
+ userData: Any, # noqa: N803
+) -> None:
+ """Pack callback into a handle for use with the callback _pycall_bsp_callback."""
for node in node_iter:
callback(node, userData)
-def bsp_traverse_pre_order(node, callback, userData=0):
+
+@deprecate("Iterate over nodes using 'for n in node.pre_order():' instead.")
+def bsp_traverse_pre_order(
+ node: tcod.bsp.BSP,
+ callback: Callable[[tcod.bsp.BSP, Any], None],
+ userData: Any = 0, # noqa: N803
+) -> None:
"""Traverse this nodes hierarchy with a callback.
.. deprecated:: 2.0
- Use :any:`BSP.walk` instead.
+ Use :any:`BSP.pre_order` instead.
"""
- _bsp_traverse(node._iter_pre_order(), callback, userData)
+ _bsp_traverse(node.pre_order(), callback, userData)
-def bsp_traverse_in_order(node, callback, userData=0):
+
+@deprecate("Iterate over nodes using 'for n in node.in_order():' instead.")
+def bsp_traverse_in_order(
+ node: tcod.bsp.BSP,
+ callback: Callable[[tcod.bsp.BSP, Any], None],
+ userData: Any = 0, # noqa: N803
+) -> None:
"""Traverse this nodes hierarchy with a callback.
.. deprecated:: 2.0
- Use :any:`BSP.walk` instead.
+ Use :any:`BSP.in_order` instead.
"""
- _bsp_traverse(node._iter_in_order(), callback, userData)
+ _bsp_traverse(node.in_order(), callback, userData)
+
-def bsp_traverse_post_order(node, callback, userData=0):
+@deprecate("Iterate over nodes using 'for n in node.post_order():' instead.")
+def bsp_traverse_post_order(
+ node: tcod.bsp.BSP,
+ callback: Callable[[tcod.bsp.BSP, Any], None],
+ userData: Any = 0, # noqa: N803
+) -> None:
"""Traverse this nodes hierarchy with a callback.
.. deprecated:: 2.0
- Use :any:`BSP.walk` instead.
+ Use :any:`BSP.post_order` instead.
"""
- _bsp_traverse(node._iter_post_order(), callback, userData)
+ _bsp_traverse(node.post_order(), callback, userData)
-def bsp_traverse_level_order(node, callback, userData=0):
+
+@deprecate("Iterate over nodes using 'for n in node.level_order():' instead.")
+def bsp_traverse_level_order(
+ node: tcod.bsp.BSP,
+ callback: Callable[[tcod.bsp.BSP, Any], None],
+ userData: Any = 0, # noqa: N803
+) -> None:
"""Traverse this nodes hierarchy with a callback.
.. deprecated:: 2.0
- Use :any:`BSP.walk` instead.
+ Use :any:`BSP.level_order` instead.
"""
- _bsp_traverse(node._iter_level_order(), callback, userData)
+ _bsp_traverse(node.level_order(), callback, userData)
+
-def bsp_traverse_inverted_level_order(node, callback, userData=0):
+@deprecate("Iterate over nodes using 'for n in node.inverted_level_order():' instead.")
+def bsp_traverse_inverted_level_order(
+ node: tcod.bsp.BSP,
+ callback: Callable[[tcod.bsp.BSP, Any], None],
+ userData: Any = 0, # noqa: N803
+) -> None:
"""Traverse this nodes hierarchy with a callback.
.. deprecated:: 2.0
- Use :any:`BSP.walk` instead.
+ Use :any:`BSP.inverted_level_order` instead.
"""
- _bsp_traverse(node._iter_inverted_level_order(), callback, userData)
+ _bsp_traverse(node.inverted_level_order(), callback, userData)
-def bsp_remove_sons(node):
+
+@deprecate("Delete bsp children using 'node.children = ()' instead.")
+def bsp_remove_sons(node: tcod.bsp.BSP) -> None:
"""Delete all children of a given node. Not recommended.
.. note::
@@ -512,8 +729,9 @@ def bsp_remove_sons(node):
"""
node.children = ()
-def bsp_delete(node):
- # type: (Any) -> None
+
+@deprecate("libtcod objects are deleted automatically.", category=FutureWarning)
+def bsp_delete(node: tcod.bsp.BSP) -> None:
"""Exists for backward compatibility. Does nothing.
BSP's created by this library are automatically garbage collected once
@@ -524,11 +742,13 @@ def bsp_delete(node):
BSP deletion is automatic.
"""
-def color_lerp(c1, c2, a):
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def color_lerp(c1: tuple[int, int, int], c2: tuple[int, int, int], a: float) -> Color:
"""Return the linear interpolation between two colors.
- ``a`` is the interpolation value, with 0 returing ``c1``,
- 1 returning ``c2``, and 0.5 returing a color halfway between both.
+ ``a`` is the interpolation value, with 0 returning ``c1``,
+ 1 returning ``c2``, and 0.5 returning a color halfway between both.
Args:
c1 (Union[Tuple[int, int, int], Sequence[int]]):
@@ -542,10 +762,12 @@ def color_lerp(c1, c2, a):
"""
return Color._new_from_cdata(lib.TCOD_color_lerp(c1, c2, a))
-def color_set_hsv(c, h, s, v):
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def color_set_hsv(c: Color, h: float, s: float, v: float) -> None:
"""Set a color using: hue, saturation, and value parameters.
- Does not return a new Color. ``c`` is modified inplace.
+ Does not return a new Color. ``c`` is modified in-place.
Args:
c (Union[Color, List[Any]]): A Color instance, or a list of any kind.
@@ -553,11 +775,13 @@ def color_set_hsv(c, h, s, v):
s (float): Saturation, from 0 to 1.
v (float): Value, from 0 to 1.
"""
- new_color = ffi.new('TCOD_color_t*')
+ new_color = ffi.new("TCOD_color_t*")
lib.TCOD_color_set_HSV(new_color, h, s, v)
c[:] = new_color.r, new_color.g, new_color.b
-def color_get_hsv(c):
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def color_get_hsv(c: tuple[int, int, int]) -> tuple[float, float, float]:
"""Return the (hue, saturation, value) of a color.
Args:
@@ -568,14 +792,16 @@ def color_get_hsv(c):
Tuple[float, float, float]:
A tuple with (hue, saturation, value) values, from 0 to 1.
"""
- hsv = ffi.new('float [3]')
+ hsv = ffi.new("float [3]")
lib.TCOD_color_get_HSV(c, hsv, hsv + 1, hsv + 2)
return hsv[0], hsv[1], hsv[2]
-def color_scale_HSV(c, scoef, vcoef):
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def color_scale_HSV(c: Color, scoef: float, vcoef: float) -> None: # noqa: N802
"""Scale a color's saturation and value.
- Does not return a new Color. ``c`` is modified inplace.
+ Does not return a new Color. ``c`` is modified in-place.
Args:
c (Union[Color, List[int]]): A Color instance, or an [r, g, b] list.
@@ -584,12 +810,14 @@ def color_scale_HSV(c, scoef, vcoef):
vcoef (float): Value multiplier, from 0 to 1.
Use 1 to keep current value.
"""
- color_p = ffi.new('TCOD_color_t*')
+ color_p = ffi.new("TCOD_color_t*")
color_p.r, color_p.g, color_p.b = c.r, c.g, c.b
lib.TCOD_color_scale_HSV(color_p, scoef, vcoef)
c[:] = color_p.r, color_p.g, color_p.b
-def color_gen_map(colors, indexes):
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def color_gen_map(colors: Iterable[tuple[int, int, int]], indexes: Iterable[int]) -> list[Color]:
"""Return a smoothly defined scale of colors.
If ``indexes`` is [0, 3, 9] for example, the first color from ``colors``
@@ -605,20 +833,32 @@ def color_gen_map(colors, indexes):
List[Color]: A list of Color instances.
Example:
- >>> tcod.color_gen_map([(0, 0, 0), (255, 128, 0)], [0, 5])
- [Color(0,0,0), Color(51,25,0), Color(102,51,0), Color(153,76,0), \
-Color(204,102,0), Color(255,128,0)]
- """
- ccolors = ffi.new('TCOD_color_t[]', colors)
- cindexes = ffi.new('int[]', indexes)
- cres = ffi.new('TCOD_color_t[]', max(indexes) + 1)
- lib.TCOD_color_gen_map(cres, len(colors), ccolors, cindexes)
- return [Color._new_from_cdata(cdata) for cdata in cres]
-
-
+ >>> from tcod import libtcodpy
+ >>> libtcodpy.color_gen_map([(0, 0, 0), (255, 128, 0)], [0, 5])
+ [Color(0, 0, 0), Color(51, 25, 0), Color(102, 51, 0), \
+Color(153, 76, 0), Color(204, 102, 0), Color(255, 128, 0)]
+ """
+ c_colors = ffi.new("TCOD_color_t[]", colors)
+ c_indexes = ffi.new("int[]", indexes)
+ c_out = ffi.new("TCOD_color_t[]", max(indexes) + 1)
+ lib.TCOD_color_gen_map(c_out, len(c_colors), c_colors, c_indexes)
+ return [Color._new_from_cdata(cdata) for cdata in c_out]
+
+
+@deprecate(
+ """console_init_root is deprecated in favor of using libtcod contexts.
+See the Getting Started documentation:
+https://python-tcod.readthedocs.io/en/latest/tcod/getting-started.html"""
+)
def console_init_root(
- w: int, h: int, title: Optional[AnyStr]=None, fullscreen: bool=False,
- renderer: Optional[int]=None, order: str='C') -> tcod.console.Console:
+ w: int,
+ h: int,
+ title: str | None = None,
+ fullscreen: bool = False, # noqa: FBT001, FBT002
+ renderer: int | None = None,
+ order: Literal["C", "F"] = "C",
+ vsync: bool | None = None, # noqa: FBT001
+) -> tcod.console.Console:
"""Set up the primary display and return the root console.
`w` and `h` are the columns and rows of the new window (in tiles.)
@@ -626,20 +866,47 @@ def console_init_root(
`title` is an optional string to display on the windows title bar.
`fullscreen` determines if the window will start in fullscreen. Fullscreen
- mode is unreliable unless the renderer is set to `RENDERER_SDL2` or
- `RENDERER_OPENGL2`.
-
- `renderer` is the rendering back-end that libtcod will use. Options are:
-
- * `tcod.RENDERER_SDL`
- * `tcod.RENDERER_OPENGL`
- * `tcod.RENDERER_GLSL`
- * `tcod.RENDERER_SDL2`
- * `tcod.RENDERER_OPENGL2`
+ mode is unreliable unless the renderer is set to `tcod.RENDERER_SDL2` or
+ `tcod.RENDERER_OPENGL2`.
+
+ `renderer` is the rendering back-end that libtcod will use.
+ If you don't know which to pick, then use `tcod.RENDERER_SDL2`.
+ Options are:
+
+ * `tcod.RENDERER_SDL`:
+ Forces the SDL2 renderer into software mode.
+ * `tcod.RENDERER_OPENGL`:
+ An OpenGL 1 implementation.
+ * `tcod.RENDERER_GLSL`:
+ A deprecated SDL2/OpenGL2 renderer.
+ * `tcod.RENDERER_SDL2`:
+ The recommended SDL2 renderer. Rendering is decided by SDL2 and can be
+ changed by using an SDL2 hint.
+ * `tcod.RENDERER_OPENGL2`:
+ An SDL2/OPENGL2 renderer. Usually faster than regular SDL2.
+ Requires OpenGL 2.0 Core.
`order` will affect how the array attributes of the returned root console
are indexed. `order='C'` is the default, but `order='F'` is recommended.
+ If `vsync` is True then the frame-rate will be synchronized to the monitors
+ vertical refresh rate. This prevents screen tearing and avoids wasting
+ computing power on overdraw. If `vsync` is False then the frame-rate will
+ be uncapped. The default is False but will change to True in the future.
+ This option only works with the SDL2 or OPENGL2 renderers, any other
+ renderer will always have `vsync` disabled.
+
+ The returned object is the root console. You don't need to use this object
+ but you should at least close it when you're done with the libtcod window.
+ You can do this by calling :any:`Console.close` or by using this function
+ in a context, like in the following example:
+
+ .. code-block:: python
+
+ with libtcodpy.console_init_root(80, 50, vsync=True) as root_console:
+ ... # Put your game loop here.
+ ... # Window closes at the end of the above block.
+
.. versionchanged:: 4.3
Added `order` parameter.
`title` parameter is now optional.
@@ -647,42 +914,84 @@ def console_init_root(
.. versionchanged:: 8.0
The default `renderer` is now automatic instead of always being
`RENDERER_SDL`.
+
+ .. versionchanged:: 10.1
+ Added the `vsync` parameter.
+
+ .. deprecated:: 11.13
+ Use :any:`tcod.context` for window management.
+ See :ref:`getting-started` for more info.
"""
if title is None:
# Use the scripts filename as the title.
- title = os.path.basename(sys.argv[0])
+ title = Path(sys.argv[0]).name
if renderer is None:
- renderer = RENDERER_GLSL # Stable for now.
- lib.TCOD_console_init_root(w, h, _bytes(title), fullscreen, renderer)
- return tcod.console.Console._get_root(order)
-
-
-def console_set_custom_font(fontFile: AnyStr, flags:
- int=FONT_LAYOUT_ASCII_INCOL,
- nb_char_horiz: int=0, nb_char_vertic: int=0):
+ renderer = tcod.constants.RENDERER_SDL2
+ elif renderer == tcod.constants.RENDERER_GLSL:
+ warnings.warn(
+ "The GLSL renderer is deprecated.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ if vsync is None:
+ vsync = False
+ warnings.warn(
+ "vsync defaults to False, but the default will change to True in "
+ "the future. Provide a value for vsync to suppress this warning.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ _check_warn(lib.TCOD_console_init_root_(w, h, _bytes(title), fullscreen, renderer, vsync))
+ console = tcod.console.Console._get_root(order)
+ console.clear()
+ return console
+
+
+@deprecate(
+ """console_set_custom_font is deprecated in favor of using contexts.
+See the Getting Started documentation:
+https://python-tcod.readthedocs.io/en/latest/tcod/getting-started.html"""
+)
+def console_set_custom_font(
+ fontFile: str | PathLike[str], # noqa: N803
+ flags: int = FONT_LAYOUT_ASCII_INCOL,
+ nb_char_horiz: int = 0,
+ nb_char_vertic: int = 0,
+) -> None:
"""Load the custom font file at `fontFile`.
- Call this before function before calling :any:`tcod.console_init_root`.
+ Call this before function before calling :any:`libtcodpy.console_init_root`.
Flags can be a mix of the following:
- * tcod.FONT_LAYOUT_ASCII_INCOL
- * tcod.FONT_LAYOUT_ASCII_INROW
- * tcod.FONT_TYPE_GREYSCALE
- * tcod.FONT_TYPE_GRAYSCALE
- * tcod.FONT_LAYOUT_TCOD
+ * libtcodpy.FONT_LAYOUT_ASCII_INCOL:
+ Decode tileset raw in column-major order.
+ * libtcodpy.FONT_LAYOUT_ASCII_INROW:
+ Decode tileset raw in row-major order.
+ * libtcodpy.FONT_TYPE_GREYSCALE:
+ Force tileset to be read as greyscale.
+ * libtcodpy.FONT_TYPE_GRAYSCALE
+ * libtcodpy.FONT_LAYOUT_TCOD:
+ Unique layout used by libtcod.
+ * libtcodpy.FONT_LAYOUT_CP437:
+ Decode a row-major Code Page 437 tileset into Unicode.
`nb_char_horiz` and `nb_char_vertic` are the columns and rows of the font
file respectfully.
+
+ .. deprecated:: 11.13
+ Load fonts using :any:`tcod.tileset.load_tilesheet` instead.
+ See :ref:`getting-started` for more info.
+
+ .. versionchanged:: 16.0
+ Added PathLike support. `fontFile` no longer takes bytes.
"""
- if not os.path.exists(fontFile):
- raise RuntimeError("File not found:\n\t%s"
- % (os.path.realpath(fontFile),))
- lib.TCOD_console_set_custom_font(_bytes(fontFile), flags,
- nb_char_horiz, nb_char_vertic)
+ fontFile = Path(fontFile).resolve(strict=True) # noqa: N806
+ _check(lib.TCOD_console_set_custom_font(_path_encode(fontFile), flags, nb_char_horiz, nb_char_vertic))
-def console_get_width(con):
+@deprecate("Check `con.width` instead.")
+def console_get_width(con: tcod.console.Console) -> int:
"""Return the width of a console.
Args:
@@ -694,9 +1003,11 @@ def console_get_width(con):
.. deprecated:: 2.0
Use `Console.width` instead.
"""
- return lib.TCOD_console_get_width(_console(con))
+ return int(lib.TCOD_console_get_width(_console(con)))
+
-def console_get_height(con):
+@deprecate("Check `con.height` instead.")
+def console_get_height(con: tcod.console.Console) -> int:
"""Return the height of a console.
Args:
@@ -708,14 +1019,14 @@ def console_get_height(con):
.. deprecated:: 2.0
Use `Console.height` instead.
"""
- return lib.TCOD_console_get_height(_console(con))
+ return int(lib.TCOD_console_get_height(_console(con)))
+
-def console_map_ascii_code_to_font(asciiCode, fontCharX, fontCharY):
+@deprecate("Setup fonts using the tcod.tileset module.")
+def console_map_ascii_code_to_font(asciiCode: int, fontCharX: int, fontCharY: int) -> None: # noqa: N803
"""Set a character code to new coordinates on the tile-set.
- `asciiCode` must be within the bounds created during the initialization of
- the loaded tile-set. For example, you can't use 255 here unless you have a
- 256 tile tile-set loaded. This applies to all functions in this group.
+ `asciiCode` should be any Unicode codepoint.
Args:
asciiCode (int): The character code to change.
@@ -723,12 +1034,16 @@ def console_map_ascii_code_to_font(asciiCode, fontCharX, fontCharY):
0 is the leftmost tile.
fontCharY (int): The Y tile coordinate on the loaded tileset.
0 is the topmost tile.
+
+ .. deprecated:: 11.13
+ Setup fonts using the :any:`tcod.tileset` module.
+ :any:`Tileset.remap` replaces this function.
"""
- lib.TCOD_console_map_ascii_code_to_font(_int(asciiCode), fontCharX,
- fontCharY)
+ lib.TCOD_console_map_ascii_code_to_font(_int(asciiCode), fontCharX, fontCharY)
+
-def console_map_ascii_codes_to_font(firstAsciiCode, nbCodes, fontCharX,
- fontCharY):
+@deprecate("Setup fonts using the tcod.tileset module.")
+def console_map_ascii_codes_to_font(firstAsciiCode: int, nbCodes: int, fontCharX: int, fontCharY: int) -> None: # noqa: N803
"""Remap a contiguous set of codes to a contiguous set of tiles.
Both the tile-set and character codes must be contiguous to use this
@@ -743,96 +1058,216 @@ def console_map_ascii_codes_to_font(firstAsciiCode, nbCodes, fontCharX,
fontCharY (int): The starting Y tile coordinate on the loaded tileset.
0 is the topmost tile.
+ .. deprecated:: 11.13
+ Setup fonts using the :any:`tcod.tileset` module.
+ :any:`Tileset.remap` replaces this function.
+
"""
- lib.TCOD_console_map_ascii_codes_to_font(_int(firstAsciiCode), nbCodes,
- fontCharX, fontCharY)
+ lib.TCOD_console_map_ascii_codes_to_font(_int(firstAsciiCode), nbCodes, fontCharX, fontCharY)
+
-def console_map_string_to_font(s, fontCharX, fontCharY):
- """Remap a string of codes to a contiguous set of tiles.
+@deprecate("Setup fonts using the tcod.tileset module.")
+def console_map_string_to_font(s: str, fontCharX: int, fontCharY: int) -> None: # noqa: N803
+ r"""Remap a string of codes to a contiguous set of tiles.
Args:
s (AnyStr): A string of character codes to map to new values.
- The null character `'\\x00'` will prematurely end this
- function.
+ Any null character `'\x00'` will prematurely end the printed text.
fontCharX (int): The starting X tile coordinate on the loaded tileset.
0 is the leftmost tile.
fontCharY (int): The starting Y tile coordinate on the loaded tileset.
0 is the topmost tile.
+
+ .. deprecated:: 11.13
+ Setup fonts using the :any:`tcod.tileset` module.
+ :any:`Tileset.remap` replaces this function.
"""
lib.TCOD_console_map_string_to_font_utf(_unicode(s), fontCharX, fontCharY)
-def console_is_fullscreen():
+
+@deprecate("This function is not supported if contexts are being used.")
+def console_is_fullscreen() -> bool:
"""Returns True if the display is fullscreen.
Returns:
bool: True if the display is fullscreen, otherwise False.
+
+ .. deprecated:: 11.13
+ This function is not supported by contexts.
"""
return bool(lib.TCOD_console_is_fullscreen())
-def console_set_fullscreen(fullscreen):
+
+@deprecate("This function is not supported if contexts are being used.")
+def console_set_fullscreen(fullscreen: bool) -> None: # noqa: FBT001
"""Change the display to be fullscreen or windowed.
Args:
fullscreen (bool): Use True to change to fullscreen.
Use False to change to windowed.
+
+ .. deprecated:: 11.13
+ This function is not supported by contexts.
"""
lib.TCOD_console_set_fullscreen(fullscreen)
-def console_is_window_closed():
- """Returns True if the window has received and exit event."""
- return lib.TCOD_console_is_window_closed()
-def console_has_mouse_focus():
- """Return True if the window has mouse focus."""
- return lib.TCOD_console_has_mouse_focus()
+@deprecate('Use the tcod.event module to check for "QUIT" type events.')
+def console_is_window_closed() -> bool:
+ """Returns True if the window has received and exit event.
+
+ .. deprecated:: 9.3
+ Use the :any:`tcod.event` module to check for "QUIT" type events.
+ """
+ return bool(lib.TCOD_console_is_window_closed())
+
+
+@deprecate("This function is not supported if contexts are being used.")
+def console_has_mouse_focus() -> bool:
+ """Return True if the window has mouse focus.
+
+ .. deprecated:: 11.13
+ This function is not supported by contexts.
+ """
+ return bool(lib.TCOD_console_has_mouse_focus())
+
+
+@deprecate("This function is not supported if contexts are being used.")
+def console_is_active() -> bool:
+ """Return True if the window has keyboard focus.
-def console_is_active():
- """Return True if the window has keyboard focus."""
- return lib.TCOD_console_is_active()
+ .. deprecated:: 11.13
+ This function is not supported by contexts.
+ """
+ return bool(lib.TCOD_console_is_active())
-def console_set_window_title(title):
+
+@deprecate("This function is not supported if contexts are being used.")
+def console_set_window_title(title: str) -> None:
"""Change the current title bar string.
Args:
title (AnyStr): A string to change the title bar to.
+
+ .. deprecated:: 11.13
+ This function is not supported by contexts.
"""
lib.TCOD_console_set_window_title(_bytes(title))
-def console_credits():
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def console_credits() -> None:
lib.TCOD_console_credits()
-def console_credits_reset():
+
+def console_credits_reset() -> None:
lib.TCOD_console_credits_reset()
-def console_credits_render(x, y, alpha):
- return lib.TCOD_console_credits_render(x, y, alpha)
-def console_flush():
- """Update the display to represent the root consoles current state."""
- lib.TCOD_console_flush()
+def console_credits_render(x: int, y: int, alpha: bool) -> bool: # noqa: FBT001
+ return bool(lib.TCOD_console_credits_render(x, y, alpha))
+
+
+@deprecate("This function is not supported if contexts are being used.")
+def console_flush(
+ console: tcod.console.Console | None = None,
+ *,
+ keep_aspect: bool = False,
+ integer_scaling: bool = False,
+ snap_to_integer: bool | None = None,
+ clear_color: tuple[int, int, int] | tuple[int, int, int, int] = (0, 0, 0),
+ align: tuple[float, float] = (0.5, 0.5),
+) -> None:
+ """Update the display to represent the root consoles current state.
+
+ `console` is the console you want to present. If not given the root
+ console will be used.
+
+ If `keep_aspect` is True then the console aspect will be preserved with
+ a letterbox. Otherwise the console will be stretched to fill the screen.
+
+ If `integer_scaling` is True then the console will be scaled in integer
+ increments. This will have no effect if the console must be shrunk.
+ You can use :any:`tcod.console.recommended_size` to create a console which
+ will fit the window without needing to be scaled.
+
+ `clear_color` is an RGB or RGBA tuple used to clear the screen before the
+ console is presented, this will normally affect the border/letterbox color.
+
+ `align` determines where the console will be placed when letter-boxing
+ exists. Values of 0 will put the console at the upper-left corner.
+ Values of 0.5 will center the console.
+
+ `snap_to_integer` is deprecated and setting it will have no effect.
+ It will be removed in a later version.
+
+ .. versionchanged:: 11.8
+ The parameters `console`, `keep_aspect`, `integer_scaling`,
+ `snap_to_integer`, `clear_color`, and `align` were added.
+
+ .. versionchanged:: 11.11
+ `clear_color` can now be an RGB tuple.
+
+ .. seealso::
+ :any:`libtcodpy.console_init_root`
+ :any:`tcod.console.recommended_size`
+
+ .. deprecated:: 11.13
+ This function is not supported by contexts.
+ """
+ if snap_to_integer is not None:
+ warnings.warn(
+ "The snap_to_integer parameter is deprecated and will be removed.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ if len(clear_color) == 3: # noqa: PLR2004
+ clear_color = clear_color[0], clear_color[1], clear_color[2], 255
+ options = {
+ "keep_aspect": keep_aspect,
+ "integer_scaling": integer_scaling,
+ "clear_color": clear_color,
+ "align_x": align[0],
+ "align_y": align[1],
+ }
+ console_p = ffi.NULL if console is None else _console(console)
+ with ffi.new("struct TCOD_ViewportOptions*", options) as viewport_opts:
+ _check(lib.TCOD_console_flush_ex(console_p, viewport_opts))
+
# drawing on a console
-def console_set_default_background(con, col):
+@deprecate("Set the `con.default_bg` attribute instead.")
+def console_set_default_background(con: tcod.console.Console, col: tuple[int, int, int]) -> None:
"""Change the default background color for a console.
Args:
con (Console): Any Console instance.
col (Union[Tuple[int, int, int], Sequence[int]]):
An (r, g, b) sequence or Color instance.
+
+ .. deprecated:: 8.5
+ Use :any:`Console.default_bg` instead.
"""
lib.TCOD_console_set_default_background(_console(con), col)
-def console_set_default_foreground(con, col):
+
+@deprecate("Set the `con.default_fg` attribute instead.")
+def console_set_default_foreground(con: tcod.console.Console, col: tuple[int, int, int]) -> None:
"""Change the default foreground color for a console.
Args:
con (Console): Any Console instance.
col (Union[Tuple[int, int, int], Sequence[int]]):
An (r, g, b) sequence or Color instance.
+
+ .. deprecated:: 8.5
+ Use :any:`Console.default_fg` instead.
"""
lib.TCOD_console_set_default_foreground(_console(con), col)
-def console_clear(con):
+
+@deprecate("Call the `con.clear()` method instead.")
+def console_clear(con: tcod.console.Console) -> None:
"""Reset a console to its default colors and the space character.
Args:
@@ -841,10 +1276,21 @@ def console_clear(con):
.. seealso::
:any:`console_set_default_background`
:any:`console_set_default_foreground`
+
+ .. deprecated:: 8.5
+ Call the :any:`Console.clear` method instead.
"""
- return lib.TCOD_console_clear(_console(con))
+ lib.TCOD_console_clear(_console(con))
-def console_put_char(con, x, y, c, flag=BKGND_DEFAULT):
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def console_put_char(
+ con: tcod.console.Console,
+ x: int,
+ y: int,
+ c: int | str,
+ flag: int = BKGND_DEFAULT,
+) -> None:
"""Draw the character c at x,y using the default colors and a blend mode.
Args:
@@ -856,7 +1302,16 @@ def console_put_char(con, x, y, c, flag=BKGND_DEFAULT):
"""
lib.TCOD_console_put_char(_console(con), x, y, _int(c), flag)
-def console_put_char_ex(con, x, y, c, fore, back):
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def console_put_char_ex(
+ con: tcod.console.Console,
+ x: int,
+ y: int,
+ c: int | str,
+ fore: tuple[int, int, int],
+ back: tuple[int, int, int],
+) -> None:
"""Draw the character c at x,y using the colors fore and back.
Args:
@@ -871,7 +1326,15 @@ def console_put_char_ex(con, x, y, c, fore, back):
"""
lib.TCOD_console_put_char_ex(_console(con), x, y, _int(c), fore, back)
-def console_set_char_background(con, x, y, col, flag=BKGND_SET):
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def console_set_char_background(
+ con: tcod.console.Console,
+ x: int,
+ y: int,
+ col: tuple[int, int, int],
+ flag: int = BKGND_SET,
+) -> None:
"""Change the background color of x,y to col using a blend mode.
Args:
@@ -884,8 +1347,9 @@ def console_set_char_background(con, x, y, col, flag=BKGND_SET):
"""
lib.TCOD_console_set_char_background(_console(con), x, y, col, flag)
+
@deprecate("Directly access a consoles foreground color with `console.fg`")
-def console_set_char_foreground(con, x, y, col):
+def console_set_char_foreground(con: tcod.console.Console, x: int, y: int, col: tuple[int, int, int]) -> None:
"""Change the foreground color of x,y to col.
Args:
@@ -894,11 +1358,16 @@ def console_set_char_foreground(con, x, y, col):
y (int): Character y position from the top.
col (Union[Tuple[int, int, int], Sequence[int]]):
An (r, g, b) sequence or Color instance.
+
+ .. deprecated:: 8.4
+ Array access performs significantly faster than using this function.
+ See :any:`Console.fg`.
"""
lib.TCOD_console_set_char_foreground(_console(con), x, y, col)
+
@deprecate("Directly access a consoles characters with `console.ch`")
-def console_set_char(con, x, y, c):
+def console_set_char(con: tcod.console.Console, x: int, y: int, c: int | str) -> None:
"""Change the character at x,y to c, keeping the current colors.
Args:
@@ -906,27 +1375,43 @@ def console_set_char(con, x, y, c):
x (int): Character x position from the left.
y (int): Character y position from the top.
c (Union[int, AnyStr]): Character to draw, can be an integer or string.
+
+ .. deprecated:: 8.4
+ Array access performs significantly faster than using this function.
+ See :any:`Console.ch`.
"""
lib.TCOD_console_set_char(_console(con), x, y, _int(c))
-def console_set_background_flag(con, flag):
+
+@deprecate("Set the `con.default_bg_blend` attribute instead.")
+def console_set_background_flag(con: tcod.console.Console, flag: int) -> None:
"""Change the default blend mode for this console.
Args:
con (Console): Any Console instance.
flag (int): Blend mode to use by default.
+
+ .. deprecated:: 8.5
+ Set :any:`Console.default_bg_blend` instead.
"""
lib.TCOD_console_set_background_flag(_console(con), flag)
-def console_get_background_flag(con):
+
+@deprecate("Check the `con.default_bg_blend` attribute instead.")
+def console_get_background_flag(con: tcod.console.Console) -> int:
"""Return this consoles current blend mode.
Args:
con (Console): Any Console instance.
+
+ .. deprecated:: 8.5
+ Check :any:`Console.default_bg_blend` instead.
"""
- return lib.TCOD_console_get_background_flag(_console(con))
+ return int(lib.TCOD_console_get_background_flag(_console(con)))
-def console_set_alignment(con, alignment):
+
+@deprecate("Set the `con.default_alignment` attribute instead.")
+def console_set_alignment(con: tcod.console.Console, alignment: int) -> None:
"""Change this consoles current alignment mode.
* tcod.LEFT
@@ -935,41 +1420,70 @@ def console_set_alignment(con, alignment):
Args:
con (Console): Any Console instance.
- alignment (int):
+ alignment (int): The libtcod alignment constant.
+
+ .. deprecated:: 8.5
+ Set :any:`Console.default_alignment` instead.
"""
lib.TCOD_console_set_alignment(_console(con), alignment)
-def console_get_alignment(con):
+
+@deprecate("Check the `con.default_alignment` attribute instead.")
+def console_get_alignment(con: tcod.console.Console) -> int:
"""Return this consoles current alignment mode.
Args:
con (Console): Any Console instance.
+
+ .. deprecated:: 8.5
+ Check :any:`Console.default_alignment` instead.
"""
- return lib.TCOD_console_get_alignment(_console(con))
+ return int(lib.TCOD_console_get_alignment(_console(con)))
+
-def console_print(con, x, y, fmt):
+@deprecate("Call the `con.print_` method instead.")
+def console_print(con: tcod.console.Console, x: int, y: int, fmt: str) -> None:
"""Print a color formatted string on a console.
Args:
con (Console): Any Console instance.
x (int): Character x position from the left.
y (int): Character y position from the top.
- fmt (AnyStr): A unicode or bytes string optionaly using color codes.
+ fmt (AnyStr): A unicode or bytes string optionally using color codes.
+
+ .. deprecated:: 8.5
+ Use :any:`Console.print_` instead.
"""
- lib.TCOD_console_print(_console(con), x, y, _fmt_unicode(fmt))
+ lib.TCOD_console_printf(_console(con), x, y, _fmt(fmt))
+
-def console_print_ex(con, x, y, flag, alignment, fmt):
+@deprecate("Call the `con.print_` method instead.")
+def console_print_ex(
+ con: tcod.console.Console,
+ x: int,
+ y: int,
+ flag: int,
+ alignment: int,
+ fmt: str,
+) -> None:
"""Print a string on a console using a blend mode and alignment mode.
Args:
con (Console): Any Console instance.
x (int): Character x position from the left.
y (int): Character y position from the top.
+ flag: Blending mode to use.
+ alignment: The libtcod alignment constant.
+ fmt: A unicode or bytes string, optionally using color codes.
+
+ .. deprecated:: 8.5
+ Use :any:`Console.print_` instead.
"""
- lib.TCOD_console_print_ex(_console(con),
- x, y, flag, alignment, _fmt_unicode(fmt))
+ lib.TCOD_console_printf_ex(_console(con), x, y, flag, alignment, _fmt(fmt))
+
-def console_print_rect(con, x, y, w, h, fmt):
+@deprecate("Call the `con.print_rect` method instead.")
+def console_print_rect(con: tcod.console.Console, x: int, y: int, w: int, h: int, fmt: str) -> int:
"""Print a string constrained to a rectangle.
If h > 0 and the bottom of the rectangle is reached,
@@ -980,62 +1494,136 @@ def console_print_rect(con, x, y, w, h, fmt):
Returns:
int: The number of lines of text once word-wrapped.
+
+ .. deprecated:: 8.5
+ Use :any:`Console.print_rect` instead.
"""
- return lib.TCOD_console_print_rect(
- _console(con), x, y, w, h, _fmt_unicode(fmt))
+ return int(lib.TCOD_console_printf_rect(_console(con), x, y, w, h, _fmt(fmt)))
+
-def console_print_rect_ex(con, x, y, w, h, flag, alignment, fmt):
+@deprecate("Call the `con.print_rect` method instead.")
+def console_print_rect_ex(
+ con: tcod.console.Console,
+ x: int,
+ y: int,
+ w: int,
+ h: int,
+ flag: int,
+ alignment: int,
+ fmt: str,
+) -> int:
"""Print a string constrained to a rectangle with blend and alignment.
Returns:
int: The number of lines of text once word-wrapped.
+
+ .. deprecated:: 8.5
+ Use :any:`Console.print_rect` instead.
"""
- return lib.TCOD_console_print_rect_ex(
- _console(con), x, y, w, h, flag, alignment, _fmt_unicode(fmt))
+ return int(lib.TCOD_console_printf_rect_ex(_console(con), x, y, w, h, flag, alignment, _fmt(fmt)))
+
-def console_get_height_rect(con, x, y, w, h, fmt):
+@deprecate("Call the `con.get_height_rect` method instead.")
+def console_get_height_rect(con: tcod.console.Console, x: int, y: int, w: int, h: int, fmt: str) -> int:
"""Return the height of this text once word-wrapped into this rectangle.
Returns:
int: The number of lines of text once word-wrapped.
+
+ .. deprecated:: 8.5
+ Use :any:`Console.get_height_rect` instead.
"""
- return lib.TCOD_console_get_height_rect(
- _console(con), x, y, w, h, _fmt_unicode(fmt))
+ return int(lib.TCOD_console_get_height_rect_fmt(_console(con), x, y, w, h, _fmt(fmt)))
-def console_rect(con, x, y, w, h, clr, flag=BKGND_DEFAULT):
+
+@deprecate("Call the `con.rect` method instead.")
+def console_rect(
+ con: tcod.console.Console,
+ x: int,
+ y: int,
+ w: int,
+ h: int,
+ clr: bool, # noqa: FBT001
+ flag: int = BKGND_DEFAULT,
+) -> None:
"""Draw a the background color on a rect optionally clearing the text.
If clr is True the affected tiles are changed to space character.
+
+ .. deprecated:: 8.5
+ Use :any:`Console.rect` instead.
"""
lib.TCOD_console_rect(_console(con), x, y, w, h, clr, flag)
-def console_hline(con, x, y, l, flag=BKGND_DEFAULT):
+
+@deprecate("Call the `con.hline` method instead.")
+def console_hline(
+ con: tcod.console.Console,
+ x: int,
+ y: int,
+ l: int, # noqa: E741
+ flag: int = BKGND_DEFAULT,
+) -> None:
"""Draw a horizontal line on the console.
This always uses the character 196, the horizontal line character.
+
+ .. deprecated:: 8.5
+ Use :any:`Console.hline` instead.
"""
lib.TCOD_console_hline(_console(con), x, y, l, flag)
-def console_vline(con, x, y, l, flag=BKGND_DEFAULT):
+
+@deprecate("Call the `con.vline` method instead.")
+def console_vline(
+ con: tcod.console.Console,
+ x: int,
+ y: int,
+ l: int, # noqa: E741
+ flag: int = BKGND_DEFAULT,
+) -> None:
"""Draw a vertical line on the console.
This always uses the character 179, the vertical line character.
+
+ .. deprecated:: 8.5
+ Use :any:`Console.vline` instead.
"""
lib.TCOD_console_vline(_console(con), x, y, l, flag)
-def console_print_frame(con, x, y, w, h, clear=True, flag=BKGND_DEFAULT, fmt=b''):
- """Draw a framed rectangle with optinal text.
+
+@deprecate("Call the `con.print_frame` method instead.")
+def console_print_frame(
+ con: tcod.console.Console,
+ x: int,
+ y: int,
+ w: int,
+ h: int,
+ clear: bool = True, # noqa: FBT001, FBT002
+ flag: int = BKGND_DEFAULT,
+ fmt: str = "",
+) -> None:
+ """Draw a framed rectangle with optional text.
This uses the default background color and blend mode to fill the
rectangle and the default foreground to draw the outline.
- fmt will be printed on the inside of the rectangle, word-wrapped.
+ `fmt` will be printed on the inside of the rectangle, word-wrapped.
+ If `fmt` is empty then no title will be drawn.
+
+ .. versionchanged:: 8.2
+ Now supports Unicode strings.
+
+ .. deprecated:: 8.5
+ Use :any:`Console.print_frame` instead.
"""
- lib.TCOD_console_print_frame(
- _console(con), x, y, w, h, clear, flag, _fmt_bytes(fmt))
+ fmt_: Any = _fmt(fmt) if fmt else ffi.NULL
+ _check(lib.TCOD_console_printf_frame(_console(con), x, y, w, h, clear, flag, fmt_))
-def console_set_color_control(con, fore, back):
- """Configure :any:`color controls`.
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def console_set_color_control(con: int, fore: tuple[int, int, int], back: tuple[int, int, int]) -> None:
+ """Configure :term:`color controls`.
Args:
con (int): :any:`Color control` constant to modify.
@@ -1046,276 +1634,518 @@ def console_set_color_control(con, fore, back):
"""
lib.TCOD_console_set_color_control(con, fore, back)
-def console_get_default_background(con):
- """Return this consoles default background color."""
- return Color._new_from_cdata(
- lib.TCOD_console_get_default_background(_console(con)))
-def console_get_default_foreground(con):
- """Return this consoles default foreground color."""
- return Color._new_from_cdata(
- lib.TCOD_console_get_default_foreground(_console(con)))
+@deprecate("Check the `con.default_bg` attribute instead.")
+def console_get_default_background(con: tcod.console.Console) -> Color:
+ """Return this consoles default background color.
+
+ .. deprecated:: 8.5
+ Use :any:`Console.default_bg` instead.
+ """
+ return Color._new_from_cdata(lib.TCOD_console_get_default_background(_console(con)))
+
+
+@deprecate("Check the `con.default_fg` attribute instead.")
+def console_get_default_foreground(con: tcod.console.Console) -> Color:
+ """Return this consoles default foreground color.
+
+ .. deprecated:: 8.5
+ Use :any:`Console.default_fg` instead.
+ """
+ return Color._new_from_cdata(lib.TCOD_console_get_default_foreground(_console(con)))
+
@deprecate("Directly access a consoles background color with `console.bg`")
-def console_get_char_background(con, x, y):
- """Return the background color at the x,y of this console."""
- return Color._new_from_cdata(
- lib.TCOD_console_get_char_background(_console(con), x, y))
+def console_get_char_background(con: tcod.console.Console, x: int, y: int) -> Color:
+ """Return the background color at the x,y of this console.
+
+ .. deprecated:: 8.4
+ Array access performs significantly faster than using this function.
+ See :any:`Console.bg`.
+ """
+ return Color._new_from_cdata(lib.TCOD_console_get_char_background(_console(con), x, y))
+
@deprecate("Directly access a consoles foreground color with `console.fg`")
-def console_get_char_foreground(con, x, y):
- """Return the foreground color at the x,y of this console."""
- return Color._new_from_cdata(
- lib.TCOD_console_get_char_foreground(_console(con), x, y))
+def console_get_char_foreground(con: tcod.console.Console, x: int, y: int) -> Color:
+ """Return the foreground color at the x,y of this console.
+
+ .. deprecated:: 8.4
+ Array access performs significantly faster than using this function.
+ See :any:`Console.fg`.
+ """
+ return Color._new_from_cdata(lib.TCOD_console_get_char_foreground(_console(con), x, y))
+
@deprecate("Directly access a consoles characters with `console.ch`")
-def console_get_char(con, x, y):
- """Return the character at the x,y of this console."""
+def console_get_char(con: tcod.console.Console, x: int, y: int) -> int:
+ """Return the character at the x,y of this console.
+
+ .. deprecated:: 8.4
+ Array access performs significantly faster than using this function.
+ See :any:`Console.ch`.
+ """
return lib.TCOD_console_get_char(_console(con), x, y)
-def console_set_fade(fade, fadingColor):
+
+@deprecate("This function is not supported if contexts are being used.", category=FutureWarning)
+def console_set_fade(fade: int, fadingColor: tuple[int, int, int]) -> None: # noqa: N803
+ """Deprecated function.
+
+ .. deprecated:: 11.13
+ This function is not supported by contexts.
+ """
lib.TCOD_console_set_fade(fade, fadingColor)
-def console_get_fade():
- return lib.TCOD_console_get_fade()
-def console_get_fading_color():
+@deprecate("This function is not supported if contexts are being used.", category=FutureWarning)
+def console_get_fade() -> int:
+ """Deprecated function.
+
+ .. deprecated:: 11.13
+ This function is not supported by contexts.
+ """
+ return int(lib.TCOD_console_get_fade())
+
+
+@deprecate("This function is not supported if contexts are being used.", category=FutureWarning)
+def console_get_fading_color() -> Color:
+ """Deprecated function.
+
+ .. deprecated:: 11.13
+ This function is not supported by contexts.
+ """
return Color._new_from_cdata(lib.TCOD_console_get_fading_color())
+
# handling keyboard input
-def console_wait_for_keypress(flush):
+@deprecate("Use the tcod.event.wait function to wait for events.")
+def console_wait_for_keypress(flush: bool) -> Key: # noqa: FBT001
"""Block until the user presses a key, then returns a new Key.
Args:
- flush bool: If True then the event queue is cleared before waiting
- for the next event.
+ flush: If True then the event queue is cleared before waiting for the next event.
Returns:
Key: A new Key instance.
+
+ .. deprecated:: 9.3
+ Use the :any:`tcod.event.wait` function to wait for events.
+
+ Example::
+
+ for event in tcod.event.wait():
+ if isinstance(event, tcod.event.KeyDown):
+ ...
+ """
+ key = Key()
+ lib.TCOD_console_wait_for_keypress_wrapper(key.key_p, flush)
+ return key
+
+
+@deprecate("Use the tcod.event.get function to check for events.")
+def console_check_for_keypress(flags: int = KEY_RELEASED) -> Key:
+ """Return a recently pressed key.
+
+ .. deprecated:: 9.3
+ Use the :any:`tcod.event.get` function to check for events.
+
+ Example::
+
+ for event in tcod.event.get():
+ if isinstance(event, tcod.event.KeyDown):
+ ...
"""
- k=Key()
- lib.TCOD_console_wait_for_keypress_wrapper(k.cdata, flush)
- return k
+ key = Key()
+ lib.TCOD_console_check_for_keypress_wrapper(key.key_p, flags)
+ return key
+
-def console_check_for_keypress(flags=KEY_RELEASED):
- k=Key()
- lib.TCOD_console_check_for_keypress_wrapper(k.cdata, flags)
- return k
+@deprecate("Use tcod.event.get_keyboard_state to see if a key is held.", category=FutureWarning)
+def console_is_key_pressed(key: int) -> bool:
+ """Return True if a key is held.
+
+ .. deprecated:: 12.7
+ Use :any:`tcod.event.get_keyboard_state` to check if a key is held.
+ """
+ return bool(lib.TCOD_console_is_key_pressed(key))
-def console_is_key_pressed(key):
- return lib.TCOD_console_is_key_pressed(key)
# using offscreen consoles
-def console_new(w, h):
- """Return an offscreen console of size: w,h."""
+@deprecate("Create a console using `tcod.console.Console(...)` instead.")
+def console_new(w: int, h: int) -> tcod.console.Console:
+ """Return an offscreen console of size: w,h.
+
+ .. deprecated:: 8.5
+ Create new consoles using :any:`tcod.console.Console` instead of this
+ function.
+ """
return tcod.console.Console(w, h)
-def console_from_file(filename):
+
+@deprecate("This loading method is no longer supported, use tcod.console_load_xp instead.")
+def console_from_file(filename: str | PathLike[str]) -> tcod.console.Console:
"""Return a new console object from a filename.
- The file format is automactially determined. This can load REXPaint `.xp`,
+ The file format is automatically determined. This can load REXPaint `.xp`,
ASCII Paint `.apf`, or Non-delimited ASCII `.asc` files.
Args:
filename (Text): The path to the file, as a string.
Returns: A new :any`Console` instance.
+
+ .. deprecated:: 12.7
+ Use :any:`libtcodpy.console_load_xp` to load REXPaint consoles.
+
+ Other formats are not actively supported.
+
+ .. versionchanged:: 16.0
+ Added PathLike support.
"""
- return tcod.console.Console._from_cdata(
- lib.TCOD_console_from_file(filename.encode('utf-8')))
+ filename = Path(filename).resolve(strict=True)
+ return tcod.console.Console._from_cdata(_check_p(lib.TCOD_console_from_file(_path_encode(filename))))
+
-def console_blit(src, x, y, w, h, dst, xdst, ydst, ffade=1.0,bfade=1.0):
- """Blit the console src from x,y,w,h to console dst at xdst,ydst."""
- lib.TCOD_console_blit(
- _console(src), x, y, w, h,
- _console(dst), xdst, ydst, ffade, bfade)
+@deprecate("Call the `Console.blit` method instead.")
+def console_blit(
+ src: tcod.console.Console,
+ x: int,
+ y: int,
+ w: int,
+ h: int,
+ dst: tcod.console.Console,
+ xdst: int,
+ ydst: int,
+ ffade: float = 1.0,
+ bfade: float = 1.0,
+) -> None:
+ """Blit the console src from x,y,w,h to console dst at xdst,ydst.
+
+ .. deprecated:: 8.5
+ Call the :any:`Console.blit` method instead.
+ """
+ lib.TCOD_console_blit(_console(src), x, y, w, h, _console(dst), xdst, ydst, ffade, bfade)
-def console_set_key_color(con, col):
- """Set a consoles blit transparent color."""
+
+@deprecate("Pass the key color to `Console.blit` instead of calling this function.")
+def console_set_key_color(con: tcod.console.Console, col: tuple[int, int, int]) -> None:
+ """Set a consoles blit transparent color.
+
+ .. deprecated:: 8.5
+ Pass the key color to :any:`tcod.console.Console.blit` instead of
+ calling this function.
+ """
lib.TCOD_console_set_key_color(_console(con), col)
- if hasattr(con, 'set_key_color'):
+ if hasattr(con, "set_key_color"):
con.set_key_color(col)
-def console_delete(con):
- # type: (Console) -> None
+
+@deprecate("This function is no longer needed.")
+def console_delete(con: tcod.console.Console) -> None:
"""Closes the window if `con` is the root console.
libtcod objects are automatically garbage collected once they go out of
scope.
This function exists for backwards compatibility.
+
+ .. deprecated:: 9.3
+ This function is not needed for normal :any:`tcod.console.Console`'s.
+ The root console should be used in a with statement instead to ensure
+ that it closes.
"""
- # type: (Any) -> None
con = _console(con)
if con == ffi.NULL:
lib.TCOD_console_delete(con)
+ warnings.warn(
+ "Instead of this call you should use Console.close,"
+ " or use a with statement to ensure the root console closes,"
+ " for example:"
+ "\n with libtcodpy.console_init_root(...) as root_console:"
+ "\n ...",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ else:
+ warnings.warn(
+ "You no longer need to make this call, Console's are deleted when they go out of scope.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+
-# fast color filling
-def console_fill_foreground(con, r, g, b):
- """Fill the foregound of a console with r,g,b.
+@deprecate("Assign to the console.fg array instead.")
+def console_fill_foreground(
+ con: tcod.console.Console,
+ r: Sequence[int],
+ g: Sequence[int],
+ b: Sequence[int],
+) -> None:
+ """Fill the foreground of a console with r,g,b.
Args:
con (Console): Any Console instance.
r (Sequence[int]): An array of integers with a length of width*height.
g (Sequence[int]): An array of integers with a length of width*height.
b (Sequence[int]): An array of integers with a length of width*height.
+
+ .. deprecated:: 8.4
+ You should assign to :any:`tcod.console.Console.fg` instead.
"""
if len(r) != len(g) or len(r) != len(b):
- raise TypeError('R, G and B must all have the same size.')
- if (isinstance(r, _np.ndarray) and isinstance(g, _np.ndarray) and
- isinstance(b, _np.ndarray)):
- #numpy arrays, use numpy's ctypes functions
- r = _np.ascontiguousarray(r, dtype=_np.intc)
- g = _np.ascontiguousarray(g, dtype=_np.intc)
- b = _np.ascontiguousarray(b, dtype=_np.intc)
- cr = ffi.cast('int *', r.ctypes.data)
- cg = ffi.cast('int *', g.ctypes.data)
- cb = ffi.cast('int *', b.ctypes.data)
+ msg = "R, G and B must all have the same size."
+ raise TypeError(msg)
+ if isinstance(r, np.ndarray) and isinstance(g, np.ndarray) and isinstance(b, np.ndarray):
+ # numpy arrays, use numpy's ctypes functions
+ r_ = np.ascontiguousarray(r, dtype=np.intc)
+ g_ = np.ascontiguousarray(g, dtype=np.intc)
+ b_ = np.ascontiguousarray(b, dtype=np.intc)
+ cr = ffi.from_buffer("int *", r_)
+ cg = ffi.from_buffer("int *", g_)
+ cb = ffi.from_buffer("int *", b_)
else:
# otherwise convert using ffi arrays
- cr = ffi.new('int[]', r)
- cg = ffi.new('int[]', g)
- cb = ffi.new('int[]', b)
+ cr = ffi.new("int[]", r)
+ cg = ffi.new("int[]", g)
+ cb = ffi.new("int[]", b)
lib.TCOD_console_fill_foreground(_console(con), cr, cg, cb)
-def console_fill_background(con, r, g, b):
- """Fill the backgound of a console with r,g,b.
+
+@deprecate("Assign to the console.bg array instead.")
+def console_fill_background(
+ con: tcod.console.Console,
+ r: Sequence[int],
+ g: Sequence[int],
+ b: Sequence[int],
+) -> None:
+ """Fill the background of a console with r,g,b.
Args:
con (Console): Any Console instance.
r (Sequence[int]): An array of integers with a length of width*height.
g (Sequence[int]): An array of integers with a length of width*height.
b (Sequence[int]): An array of integers with a length of width*height.
+
+ .. deprecated:: 8.4
+ You should assign to :any:`tcod.console.Console.bg` instead.
"""
if len(r) != len(g) or len(r) != len(b):
- raise TypeError('R, G and B must all have the same size.')
- if (isinstance(r, _np.ndarray) and isinstance(g, _np.ndarray) and
- isinstance(b, _np.ndarray)):
- #numpy arrays, use numpy's ctypes functions
- r = _np.ascontiguousarray(r, dtype=_np.intc)
- g = _np.ascontiguousarray(g, dtype=_np.intc)
- b = _np.ascontiguousarray(b, dtype=_np.intc)
- cr = ffi.cast('int *', r.ctypes.data)
- cg = ffi.cast('int *', g.ctypes.data)
- cb = ffi.cast('int *', b.ctypes.data)
+ msg = "R, G and B must all have the same size."
+ raise TypeError(msg)
+ if isinstance(r, np.ndarray) and isinstance(g, np.ndarray) and isinstance(b, np.ndarray):
+ # numpy arrays, use numpy's ctypes functions
+ r_ = np.ascontiguousarray(r, dtype=np.intc)
+ g_ = np.ascontiguousarray(g, dtype=np.intc)
+ b_ = np.ascontiguousarray(b, dtype=np.intc)
+ cr = ffi.from_buffer("int *", r_)
+ cg = ffi.from_buffer("int *", g_)
+ cb = ffi.from_buffer("int *", b_)
else:
# otherwise convert using ffi arrays
- cr = ffi.new('int[]', r)
- cg = ffi.new('int[]', g)
- cb = ffi.new('int[]', b)
+ cr = ffi.new("int[]", r)
+ cg = ffi.new("int[]", g)
+ cb = ffi.new("int[]", b)
lib.TCOD_console_fill_background(_console(con), cr, cg, cb)
-def console_fill_char(con,arr):
+
+@deprecate("Assign to the console.ch array instead.")
+def console_fill_char(con: tcod.console.Console, arr: Sequence[int]) -> None:
"""Fill the character tiles of a console with an array.
- Args:
- con (Console): Any Console instance.
- arr (Sequence[int]): An array of integers with a length of width*height.
+ `arr` is an array of integers with a length of the consoles width and
+ height.
+
+ .. deprecated:: 8.4
+ You should assign to :any:`tcod.console.Console.ch` instead.
"""
- if isinstance(arr, _np.ndarray):
- #numpy arrays, use numpy's ctypes functions
- arr = _np.ascontiguousarray(arr, dtype=_np.intc)
- carr = ffi.cast('int *', arr.ctypes.data)
+ if isinstance(arr, np.ndarray):
+ # numpy arrays, use numpy's ctypes functions
+ np_array = np.ascontiguousarray(arr, dtype=np.intc)
+ carr = ffi.from_buffer("int *", np_array)
else:
- #otherwise convert using the ffi module
- carr = ffi.new('int[]', arr)
+ # otherwise convert using the ffi module
+ carr = ffi.new("int[]", arr)
lib.TCOD_console_fill_char(_console(con), carr)
-def console_load_asc(con, filename):
- """Update a console from a non-delimited ASCII `.asc` file."""
- return lib.TCOD_console_load_asc(_console(con), filename.encode('utf-8'))
-def console_save_asc(con, filename):
- """Save a console to a non-delimited ASCII `.asc` file."""
- return lib.TCOD_console_save_asc(_console(con), filename.encode('utf-8'))
+@deprecate("This format is not actively supported")
+def console_load_asc(con: tcod.console.Console, filename: str | PathLike[str]) -> bool:
+ """Update a console from a non-delimited ASCII `.asc` file.
-def console_load_apf(con, filename):
- """Update a console from an ASCII Paint `.apf` file."""
- return lib.TCOD_console_load_apf(_console(con), filename.encode('utf-8'))
+ .. deprecated:: 12.7
+ This format is no longer supported.
-def console_save_apf(con, filename):
- """Save a console to an ASCII Paint `.apf` file."""
- return lib.TCOD_console_save_apf(_console(con), filename.encode('utf-8'))
+ .. versionchanged:: 16.0
+ Added PathLike support.
+ """
+ filename = Path(filename).resolve(strict=True)
+ return bool(lib.TCOD_console_load_asc(_console(con), _path_encode(filename)))
-def console_load_xp(con, filename):
- """Update a console from a REXPaint `.xp` file."""
- return lib.TCOD_console_load_xp(_console(con), filename.encode('utf-8'))
-def console_save_xp(con, filename, compress_level=9):
- """Save a console to a REXPaint `.xp` file."""
- return lib.TCOD_console_save_xp(
- _console(con),
- filename.encode('utf-8'),
- compress_level,
- )
+@deprecate("This format is not actively supported")
+def console_save_asc(con: tcod.console.Console, filename: str | PathLike[str]) -> bool:
+ """Save a console to a non-delimited ASCII `.asc` file.
+
+ .. deprecated:: 12.7
+ This format is no longer supported.
+
+ .. versionchanged:: 16.0
+ Added PathLike support.
+ """
+ return bool(lib.TCOD_console_save_asc(_console(con), _path_encode(Path(filename))))
+
+
+@deprecate("This format is not actively supported")
+def console_load_apf(con: tcod.console.Console, filename: str | PathLike[str]) -> bool:
+ """Update a console from an ASCII Paint `.apf` file.
+
+ .. deprecated:: 12.7
+ This format is no longer supported.
+
+ .. versionchanged:: 16.0
+ Added PathLike support.
+ """
+ filename = Path(filename).resolve(strict=True)
+ return bool(lib.TCOD_console_load_apf(_console(con), _path_encode(filename)))
+
+
+@deprecate("This format is not actively supported")
+def console_save_apf(con: tcod.console.Console, filename: str | PathLike[str]) -> bool:
+ """Save a console to an ASCII Paint `.apf` file.
+
+ .. deprecated:: 12.7
+ This format is no longer supported.
+
+ .. versionchanged:: 16.0
+ Added PathLike support.
+ """
+ return bool(lib.TCOD_console_save_apf(_console(con), _path_encode(Path(filename))))
+
+
+@deprecate("Use tcod.console.load_xp to load this file.")
+def console_load_xp(con: tcod.console.Console, filename: str | PathLike[str]) -> bool:
+ """Update a console from a REXPaint `.xp` file.
+
+ .. deprecated:: 11.18
+ Functions modifying console objects in-place are deprecated.
+ Use :any:`libtcodpy.console_from_xp` to load a Console from a file.
-def console_from_xp(filename):
- """Return a single console from a REXPaint `.xp` file."""
- return tcod.console.Console._from_cdata(
- lib.TCOD_console_from_xp(filename.encode('utf-8')))
+ .. versionchanged:: 16.0
+ Added PathLike support.
+ """
+ filename = Path(filename).resolve(strict=True)
+ return bool(lib.TCOD_console_load_xp(_console(con), _path_encode(filename)))
+
+
+@deprecate("Use tcod.console.save_xp to save this console.")
+def console_save_xp(con: tcod.console.Console, filename: str | PathLike[str], compress_level: int = 9) -> bool:
+ """Save a console to a REXPaint `.xp` file.
+
+ .. versionchanged:: 16.0
+ Added PathLike support.
+ """
+ return bool(lib.TCOD_console_save_xp(_console(con), _path_encode(Path(filename)), compress_level))
+
+
+@deprecate("Use tcod.console.load_xp to load this file.")
+def console_from_xp(filename: str | PathLike[str]) -> tcod.console.Console:
+ """Return a single console from a REXPaint `.xp` file.
+
+ .. versionchanged:: 16.0
+ Added PathLike support.
+ """
+ filename = Path(filename).resolve(strict=True)
+ return tcod.console.Console._from_cdata(_check_p(lib.TCOD_console_from_xp(_path_encode(filename))))
-def console_list_load_xp(filename):
- """Return a list of consoles from a REXPaint `.xp` file."""
- tcod_list = lib.TCOD_console_list_from_xp(filename.encode('utf-8'))
+
+@deprecate("Use tcod.console.load_xp to load this file.")
+def console_list_load_xp(
+ filename: str | PathLike[str],
+) -> list[tcod.console.Console] | None:
+ """Return a list of consoles from a REXPaint `.xp` file.
+
+ .. versionchanged:: 16.0
+ Added PathLike support.
+ """
+ filename = Path(filename).resolve(strict=True)
+ tcod_list = lib.TCOD_console_list_from_xp(_path_encode(filename))
if tcod_list == ffi.NULL:
return None
try:
python_list = []
lib.TCOD_list_reverse(tcod_list)
while not lib.TCOD_list_is_empty(tcod_list):
- python_list.append(
- tcod.console.Console._from_cdata(lib.TCOD_list_pop(tcod_list)),
- )
+ python_list.append(tcod.console.Console._from_cdata(lib.TCOD_list_pop(tcod_list)))
return python_list
finally:
lib.TCOD_list_delete(tcod_list)
-def console_list_save_xp(console_list, filename, compress_level=9):
- """Save a list of consoles to a REXPaint `.xp` file."""
+
+@deprecate("Use tcod.console.save_xp to save these consoles.")
+def console_list_save_xp(
+ console_list: Sequence[tcod.console.Console],
+ filename: str | PathLike[str],
+ compress_level: int = 9,
+) -> bool:
+ """Save a list of consoles to a REXPaint `.xp` file.
+
+ .. versionchanged:: 16.0
+ Added PathLike support.
+ """
tcod_list = lib.TCOD_list_new()
try:
for console in console_list:
lib.TCOD_list_push(tcod_list, _console(console))
- return lib.TCOD_console_list_save_xp(
- tcod_list, filename.encode('utf-8'), compress_level
- )
+ return bool(lib.TCOD_console_list_save_xp(tcod_list, _path_encode(Path(filename)), compress_level))
finally:
lib.TCOD_list_delete(tcod_list)
-def path_new_using_map(m, dcost=1.41):
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def path_new_using_map(m: tcod.map.Map, dcost: float = 1.41) -> tcod.path.AStar:
"""Return a new AStar using the given Map.
Args:
m (Map): A Map instance.
dcost (float): The path-finding cost of diagonal movement.
Can be set to 0 to disable diagonal movement.
+
Returns:
AStar: A new AStar instance.
"""
return tcod.path.AStar(m, dcost)
-def path_new_using_function(w, h, func, userData=0, dcost=1.41):
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def path_new_using_function(
+ w: int,
+ h: int,
+ func: Callable[[int, int, int, int, Any], float],
+ userData: Any = 0, # noqa: N803
+ dcost: float = 1.41,
+) -> tcod.path.AStar:
"""Return a new AStar using the given callable function.
Args:
w (int): Clipping width.
h (int): Clipping height.
- func (Callable[[int, int, int, int, Any], float]):
- userData (Any):
+ func: Callback function with the format: `f(origin_x, origin_y, dest_x, dest_y, userData) -> float`
+ userData (Any): An object passed to the callback.
dcost (float): A multiplier for the cost of diagonal movement.
Can be set to 0 to disable diagonal movement.
+
Returns:
AStar: A new AStar instance.
"""
- return tcod.path.AStar(
- tcod.path._EdgeCostFunc((func, userData), (w, h)),
- dcost,
- )
+ return tcod.path.AStar(tcod.path._EdgeCostFunc((func, userData), (w, h)), dcost)
-def path_compute(p, ox, oy, dx, dy):
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def path_compute(p: tcod.path.AStar, ox: int, oy: int, dx: int, dy: int) -> bool:
"""Find a path from (ox, oy) to (dx, dy). Return True if path is found.
Args:
@@ -1324,50 +2154,62 @@ def path_compute(p, ox, oy, dx, dy):
oy (int): Starting y position.
dx (int): Destination x position.
dy (int): Destination y position.
+
Returns:
bool: True if a valid path was found. Otherwise False.
"""
- return lib.TCOD_path_compute(p._path_c, ox, oy, dx, dy)
+ return bool(lib.TCOD_path_compute(p._path_c, ox, oy, dx, dy))
-def path_get_origin(p):
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def path_get_origin(p: tcod.path.AStar) -> tuple[int, int]:
"""Get the current origin position.
This point moves when :any:`path_walk` returns the next x,y step.
Args:
p (AStar): An AStar instance.
+
Returns:
Tuple[int, int]: An (x, y) point.
"""
- x = ffi.new('int *')
- y = ffi.new('int *')
+ x = ffi.new("int *")
+ y = ffi.new("int *")
lib.TCOD_path_get_origin(p._path_c, x, y)
return x[0], y[0]
-def path_get_destination(p):
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def path_get_destination(p: tcod.path.AStar) -> tuple[int, int]:
"""Get the current destination position.
Args:
p (AStar): An AStar instance.
+
Returns:
Tuple[int, int]: An (x, y) point.
"""
- x = ffi.new('int *')
- y = ffi.new('int *')
+ x = ffi.new("int *")
+ y = ffi.new("int *")
lib.TCOD_path_get_destination(p._path_c, x, y)
return x[0], y[0]
-def path_size(p):
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def path_size(p: tcod.path.AStar) -> int:
"""Return the current length of the computed path.
Args:
p (AStar): An AStar instance.
+
Returns:
int: Length of the path.
"""
- return lib.TCOD_path_size(p._path_c)
+ return int(lib.TCOD_path_size(p._path_c))
-def path_reverse(p):
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def path_reverse(p: tcod.path.AStar) -> None:
"""Reverse the direction of a path.
This effectively swaps the origin and destination points.
@@ -1377,29 +2219,36 @@ def path_reverse(p):
"""
lib.TCOD_path_reverse(p._path_c)
-def path_get(p, idx):
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def path_get(p: tcod.path.AStar, idx: int) -> tuple[int, int]:
"""Get a point on a path.
Args:
p (AStar): An AStar instance.
idx (int): Should be in range: 0 <= inx < :any:`path_size`
"""
- x = ffi.new('int *')
- y = ffi.new('int *')
+ x = ffi.new("int *")
+ y = ffi.new("int *")
lib.TCOD_path_get(p._path_c, idx, x, y)
return x[0], y[0]
-def path_is_empty(p):
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def path_is_empty(p: tcod.path.AStar) -> bool:
"""Return True if a path is empty.
Args:
p (AStar): An AStar instance.
+
Returns:
bool: True if a path is empty. Otherwise False.
"""
- return lib.TCOD_path_is_empty(p._path_c)
+ return bool(lib.TCOD_path_is_empty(p._path_c))
-def path_walk(p, recompute):
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def path_walk(p: tcod.path.AStar, recompute: bool) -> tuple[int, int] | tuple[None, None]: # noqa: FBT001
"""Return the next (x, y) point in a path, or (None, None) if it's empty.
When ``recompute`` is True and a previously valid path reaches a point
@@ -1408,86 +2257,119 @@ def path_walk(p, recompute):
Args:
p (AStar): An AStar instance.
recompute (bool): Recompute the path automatically.
+
Returns:
Union[Tuple[int, int], Tuple[None, None]]:
A single (x, y) point, or (None, None)
"""
- x = ffi.new('int *')
- y = ffi.new('int *')
+ x = ffi.new("int *")
+ y = ffi.new("int *")
if lib.TCOD_path_walk(p._path_c, x, y, recompute):
return x[0], y[0]
- return None,None
+ return None, None
-def path_delete(p):
- # type (Any) -> None
+
+@deprecate("libtcod objects are deleted automatically.")
+def path_delete(p: tcod.path.AStar) -> None:
"""Does nothing. libtcod objects are managed by Python's garbage collector.
This function exists for backwards compatibility with libtcodpy.
"""
-def dijkstra_new(m, dcost=1.41):
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def dijkstra_new(m: tcod.map.Map, dcost: float = 1.41) -> tcod.path.Dijkstra:
return tcod.path.Dijkstra(m, dcost)
-def dijkstra_new_using_function(w, h, func, userData=0, dcost=1.41):
- return tcod.path.Dijkstra(
- tcod.path._EdgeCostFunc((func, userData), (w, h)),
- dcost,
- )
-def dijkstra_compute(p, ox, oy):
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def dijkstra_new_using_function(
+ w: int,
+ h: int,
+ func: Callable[[int, int, int, int, Any], float],
+ userData: Any = 0, # noqa: N803
+ dcost: float = 1.41,
+) -> tcod.path.Dijkstra:
+ return tcod.path.Dijkstra(tcod.path._EdgeCostFunc((func, userData), (w, h)), dcost)
+
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def dijkstra_compute(p: tcod.path.Dijkstra, ox: int, oy: int) -> None:
lib.TCOD_dijkstra_compute(p._path_c, ox, oy)
-def dijkstra_path_set(p, x, y):
- return lib.TCOD_dijkstra_path_set(p._path_c, x, y)
-def dijkstra_get_distance(p, x, y):
- return lib.TCOD_dijkstra_get_distance(p._path_c, x, y)
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def dijkstra_path_set(p: tcod.path.Dijkstra, x: int, y: int) -> bool:
+ return bool(lib.TCOD_dijkstra_path_set(p._path_c, x, y))
+
-def dijkstra_size(p):
- return lib.TCOD_dijkstra_size(p._path_c)
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def dijkstra_get_distance(p: tcod.path.Dijkstra, x: int, y: int) -> int:
+ return int(lib.TCOD_dijkstra_get_distance(p._path_c, x, y))
-def dijkstra_reverse(p):
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def dijkstra_size(p: tcod.path.Dijkstra) -> int:
+ return int(lib.TCOD_dijkstra_size(p._path_c))
+
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def dijkstra_reverse(p: tcod.path.Dijkstra) -> None:
lib.TCOD_dijkstra_reverse(p._path_c)
-def dijkstra_get(p, idx):
- x = ffi.new('int *')
- y = ffi.new('int *')
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def dijkstra_get(p: tcod.path.Dijkstra, idx: int) -> tuple[int, int]:
+ x = ffi.new("int *")
+ y = ffi.new("int *")
lib.TCOD_dijkstra_get(p._path_c, idx, x, y)
return x[0], y[0]
-def dijkstra_is_empty(p):
- return lib.TCOD_dijkstra_is_empty(p._path_c)
-def dijkstra_path_walk(p):
- x = ffi.new('int *')
- y = ffi.new('int *')
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def dijkstra_is_empty(p: tcod.path.Dijkstra) -> bool:
+ return bool(lib.TCOD_dijkstra_is_empty(p._path_c))
+
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def dijkstra_path_walk(
+ p: tcod.path.Dijkstra,
+) -> tuple[int, int] | tuple[None, None]:
+ x = ffi.new("int *")
+ y = ffi.new("int *")
if lib.TCOD_dijkstra_path_walk(p._path_c, x, y):
return x[0], y[0]
- return None,None
+ return None, None
-def dijkstra_delete(p):
- # type (Any) -> None
+
+@deprecate("libtcod objects are deleted automatically.")
+def dijkstra_delete(p: tcod.path.Dijkstra) -> None:
"""Does nothing. libtcod objects are managed by Python's garbage collector.
This function exists for backwards compatibility with libtcodpy.
"""
-def _heightmap_cdata(array: np.ndarray) -> ffi.CData:
+
+def _heightmap_cdata(array: NDArray[np.float32]) -> Any:
"""Return a new TCOD_heightmap_t instance using an array.
Formatting is verified during this function.
"""
- if array.flags['F_CONTIGUOUS']:
+ if array.flags["F_CONTIGUOUS"]:
array = array.transpose()
- if not array.flags['C_CONTIGUOUS']:
- raise ValueError('array must be a contiguous segment.')
- if array.dtype != _np.float32:
- raise ValueError('array dtype must be float32, not %r' % array.dtype)
- width, height = array.shape
- pointer = ffi.cast('float *', array.ctypes.data)
- return ffi.new('TCOD_heightmap_t *', (width, height, pointer))
-
-def heightmap_new(w: int, h: int, order: str='C') -> np.ndarray:
+ if not array.flags["C_CONTIGUOUS"]:
+ msg = "array must be a contiguous segment."
+ raise ValueError(msg)
+ if array.dtype != np.float32:
+ msg = f"array dtype must be float32, not {array.dtype!r}"
+ raise ValueError(msg)
+ height, width = array.shape
+ pointer = ffi.from_buffer("float *", array)
+ return ffi.new("TCOD_heightmap_t *", (width, height, pointer))
+
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def heightmap_new(w: int, h: int, order: str = "C") -> NDArray[np.float32]:
"""Return a new numpy.ndarray formatted for use with heightmap functions.
`w` and `h` are the width and height of the array.
@@ -1498,40 +2380,49 @@ def heightmap_new(w: int, h: int, order: str='C') -> np.ndarray:
following are true::
* The array is 2 dimensional.
* The array has the C_CONTIGUOUS or F_CONTIGUOUS flag.
- * The array's dtype is :any:`dtype.float32`.
+ * The array's dtype is `dtype.float32`.
The returned NumPy array will fit all these conditions.
.. versionchanged:: 8.1
Added the `order` parameter.
"""
- if order == 'C':
- return np.zeros((h, w), _np.float32, order='C')
- elif order == 'F':
- return np.zeros((w, h), _np.float32, order='F')
- else:
- raise ValueError("Invalid order parameter, should be 'C' or 'F'.")
+ if order == "C":
+ return np.zeros((h, w), np.float32, order="C")
+ if order == "F":
+ return np.zeros((w, h), np.float32, order="F")
+ msg = "Invalid order parameter, should be 'C' or 'F'."
+ raise ValueError(msg)
+
-def heightmap_set_value(hm: np.ndarray, x: int, y: int, value: float) -> None:
+@deprecate("Assign to heightmaps as a NumPy array instead.")
+def heightmap_set_value(hm: NDArray[np.float32], x: int, y: int, value: float) -> None:
"""Set the value of a point on a heightmap.
.. deprecated:: 2.0
`hm` is a NumPy array, so values should be assigned to it directly.
"""
- if hm.flags['C_CONTIGUOUS']:
- warnings.warn("Assign to this heightmap with hm[i,j] = value\n"
- "consider using order='F'",
- DeprecationWarning, stacklevel=2)
+ if hm.flags["C_CONTIGUOUS"]:
+ warnings.warn(
+ "Assign to this heightmap with hm[i,j] = value\nconsider using order='F'",
+ DeprecationWarning,
+ stacklevel=2,
+ )
hm[y, x] = value
- elif hm.flags['F_CONTIGUOUS']:
- warnings.warn("Assign to this heightmap with hm[x,y] = value",
- DeprecationWarning, stacklevel=2)
+ elif hm.flags["F_CONTIGUOUS"]:
+ warnings.warn(
+ "Assign to this heightmap with hm[x,y] = value",
+ DeprecationWarning,
+ stacklevel=2,
+ )
hm[x, y] = value
else:
- raise ValueError("This array is not contiguous.")
+ msg = "This array is not contiguous."
+ raise ValueError(msg)
+
@deprecate("Add a scalar to an array using `hm[:] += value`")
-def heightmap_add(hm: np.ndarray, value: float) -> None:
+def heightmap_add(hm: NDArray[np.float32], value: float) -> None:
"""Add value to all values on this heightmap.
Args:
@@ -1543,8 +2434,9 @@ def heightmap_add(hm: np.ndarray, value: float) -> None:
"""
hm[:] += value
+
@deprecate("Multiply an array with a scaler using `hm[:] *= value`")
-def heightmap_scale(hm: np.ndarray, value: float) -> None:
+def heightmap_scale(hm: NDArray[np.float32], value: float) -> None:
"""Multiply all items on this heightmap by value.
Args:
@@ -1556,8 +2448,9 @@ def heightmap_scale(hm: np.ndarray, value: float) -> None:
"""
hm[:] *= value
+
@deprecate("Clear an array with`hm[:] = 0`")
-def heightmap_clear(hm: np.ndarray) -> None:
+def heightmap_clear(hm: NDArray[np.float32]) -> None:
"""Add value to all values on this heightmap.
Args:
@@ -1568,9 +2461,10 @@ def heightmap_clear(hm: np.ndarray) -> None:
"""
hm[:] = 0
+
@deprecate("Clamp array values using `hm.clip(mi, ma)`")
-def heightmap_clamp(hm: np.ndarray, mi: float, ma: float) -> None:
- """Clamp all values on this heightmap between ``mi`` and ``ma``
+def heightmap_clamp(hm: NDArray[np.float32], mi: float, ma: float) -> None:
+ """Clamp all values on this heightmap between ``mi`` and ``ma``.
Args:
hm (numpy.ndarray): A numpy.ndarray formatted for heightmap functions.
@@ -1582,11 +2476,13 @@ def heightmap_clamp(hm: np.ndarray, mi: float, ma: float) -> None:
"""
hm.clip(mi, ma)
+
@deprecate("Copy an array using `hm2[:] = hm1[:]`, or `hm1.copy()`")
-def heightmap_copy(hm1: np.ndarray, hm2: np.ndarray) -> None:
+def heightmap_copy(hm1: NDArray[np.float32], hm2: NDArray[np.float32]) -> None:
"""Copy the heightmap ``hm1`` to ``hm2``.
Args:
+ hm: A numpy.ndarray formatted for heightmap functions.
hm1 (numpy.ndarray): The source heightmap.
hm2 (numpy.ndarray): The destination heightmap.
@@ -1595,19 +2491,27 @@ def heightmap_copy(hm1: np.ndarray, hm2: np.ndarray) -> None:
"""
hm2[:] = hm1[:]
-def heightmap_normalize(hm: np.ndarray, mi: float=0.0, ma: float=1.0) -> None:
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def heightmap_normalize(hm: NDArray[np.float32], mi: float = 0.0, ma: float = 1.0) -> None:
"""Normalize heightmap values between ``mi`` and ``ma``.
Args:
+ hm: A numpy.ndarray formatted for heightmap functions.
mi (float): The lowest value after normalization.
ma (float): The highest value after normalization.
"""
lib.TCOD_heightmap_normalize(_heightmap_cdata(hm), mi, ma)
-def heightmap_lerp_hm(hm1: np.ndarray, hm2: np.ndarray, hm3: np.ndarray,
- coef: float) -> None:
- """Perform linear interpolation between two heightmaps storing the result
- in ``hm3``.
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def heightmap_lerp_hm(
+ hm1: NDArray[np.float32],
+ hm2: NDArray[np.float32],
+ hm3: NDArray[np.float32],
+ coef: float,
+) -> None:
+ """Perform linear interpolation between two heightmaps storing the result in ``hm3``.
This is the same as doing ``hm3[:] = hm1[:] + (hm2[:] - hm1[:]) * coef``
@@ -1617,12 +2521,20 @@ def heightmap_lerp_hm(hm1: np.ndarray, hm2: np.ndarray, hm3: np.ndarray,
hm3 (numpy.ndarray): A destination heightmap to store the result.
coef (float): The linear interpolation coefficient.
"""
- lib.TCOD_heightmap_lerp_hm(_heightmap_cdata(hm1), _heightmap_cdata(hm2),
- _heightmap_cdata(hm3), coef)
+ lib.TCOD_heightmap_lerp_hm(
+ _heightmap_cdata(hm1),
+ _heightmap_cdata(hm2),
+ _heightmap_cdata(hm3),
+ coef,
+ )
+
@deprecate("Add 2 arrays using `hm3 = hm1 + hm2`")
-def heightmap_add_hm(hm1: np.ndarray, hm2: np.ndarray,
- hm3: np.ndarray) -> None:
+def heightmap_add_hm(
+ hm1: NDArray[np.float32],
+ hm2: NDArray[np.float32],
+ hm3: NDArray[np.float32],
+) -> None:
"""Add two heightmaps together and stores the result in ``hm3``.
Args:
@@ -1635,9 +2547,13 @@ def heightmap_add_hm(hm1: np.ndarray, hm2: np.ndarray,
"""
hm3[:] = hm1[:] + hm2[:]
+
@deprecate("Multiply 2 arrays using `hm3 = hm1 * hm2`")
-def heightmap_multiply_hm(hm1: np.ndarray, hm2: np.ndarray,
- hm3: np.ndarray) -> None:
+def heightmap_multiply_hm(
+ hm1: NDArray[np.float32],
+ hm2: NDArray[np.float32],
+ hm3: NDArray[np.float32],
+) -> None:
"""Multiplies two heightmap's together and stores the result in ``hm3``.
Args:
@@ -1651,8 +2567,9 @@ def heightmap_multiply_hm(hm1: np.ndarray, hm2: np.ndarray,
"""
hm3[:] = hm1[:] * hm2[:]
-def heightmap_add_hill(hm: np.ndarray, x: float, y: float, radius: float,
- height: float) -> None:
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def heightmap_add_hill(hm: NDArray[np.float32], x: float, y: float, radius: float, height: float) -> None:
"""Add a hill (a half spheroid) at given position.
If height == radius or -radius, the hill is a half-sphere.
@@ -1666,12 +2583,12 @@ def heightmap_add_hill(hm: np.ndarray, x: float, y: float, radius: float,
"""
lib.TCOD_heightmap_add_hill(_heightmap_cdata(hm), x, y, radius, height)
-def heightmap_dig_hill(hm: np.ndarray, x: float, y: float, radius: float,
- height: float) -> None:
- """
- This function takes the highest value (if height > 0) or the lowest
- (if height < 0) between the map and the hill.
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def heightmap_dig_hill(hm: NDArray[np.float32], x: float, y: float, radius: float, height: float) -> None:
+ """Dig a hill in a heightmap.
+
+ This function takes the highest value (if height > 0) or the lowest (if height < 0) between the map and the hill.
It's main goal is to carve things in maps (like rivers) by digging hills along a curve.
@@ -1684,9 +2601,15 @@ def heightmap_dig_hill(hm: np.ndarray, x: float, y: float, radius: float,
"""
lib.TCOD_heightmap_dig_hill(_heightmap_cdata(hm), x, y, radius, height)
-def heightmap_rain_erosion(hm: np.ndarray, nbDrops: int, erosionCoef: float,
- sedimentationCoef: float,
- rnd: Optional[tcod.random.Random]=None) -> None:
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def heightmap_rain_erosion(
+ hm: NDArray[np.float32],
+ nbDrops: int, # noqa: N803
+ erosionCoef: float, # noqa: N803
+ sedimentationCoef: float, # noqa: N803
+ rnd: tcod.random.Random | None = None,
+) -> None:
"""Simulate the effect of rain drops on the terrain, resulting in erosion.
``nbDrops`` should be at least hm.size.
@@ -1700,16 +2623,25 @@ def heightmap_rain_erosion(hm: np.ndarray, nbDrops: int, erosionCoef: float,
rnd (Optional[Random]): A tcod.Random instance, or None.
"""
lib.TCOD_heightmap_rain_erosion(
- _heightmap_cdata(hm), nbDrops, erosionCoef,
- sedimentationCoef, rnd.random_c if rnd else ffi.NULL
+ _heightmap_cdata(hm),
+ nbDrops,
+ erosionCoef,
+ sedimentationCoef,
+ rnd.random_c if rnd else ffi.NULL,
)
-def heightmap_kernel_transform(hm: np.ndarray, kernelsize: int,
- dx: Sequence[int], dy: Sequence[int],
- weight: Sequence[float],
- minLevel: float, maxLevel: float) -> None:
- """Apply a generic transformation on the map, so that each resulting cell
- value is the weighted sum of several neighbour cells.
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def heightmap_kernel_transform(
+ hm: NDArray[np.float32],
+ kernelsize: int,
+ dx: Sequence[int],
+ dy: Sequence[int],
+ weight: Sequence[float],
+ minLevel: float, # noqa: N803
+ maxLevel: float, # noqa: N803
+) -> None:
+ """Apply a generic transformation on the map, so that each resulting cell value is the weighted sum of several neighbor cells.
This can be used to smooth/sharpen the map.
@@ -1717,10 +2649,10 @@ def heightmap_kernel_transform(hm: np.ndarray, kernelsize: int,
hm (numpy.ndarray): A numpy.ndarray formatted for heightmap functions.
kernelsize (int): Should be set to the length of the parameters::
dx, dy, and weight.
- dx (Sequence[int]): A sequence of x coorinates.
- dy (Sequence[int]): A sequence of y coorinates.
+ dx (Sequence[int]): A sequence of x coordinates.
+ dy (Sequence[int]): A sequence of y coordinates.
weight (Sequence[float]): A sequence of kernelSize cells weight.
- The value of each neighbour cell is scaled by
+ The value of each neighbor cell is scaled by
its corresponding weight
minLevel (float): No transformation will apply to cells
below this value.
@@ -1741,23 +2673,28 @@ def heightmap_kernel_transform(hm: np.ndarray, kernelsize: int,
Example:
>>> import numpy as np
+ >>> from tcod import libtcodpy
>>> heightmap = np.zeros((3, 3), dtype=np.float32)
>>> heightmap[:,1] = 1
>>> dx = [-1, 1, 0]
>>> dy = [0, 0, 0]
>>> weight = [0.33, 0.33, 0.33]
- >>> tcod.heightmap_kernel_transform(heightmap, 3, dx, dy, weight,
- ... 0.0, 1.0)
- """
- cdx = ffi.new('int[]', dx)
- cdy = ffi.new('int[]', dy)
- cweight = ffi.new('float[]', weight)
- lib.TCOD_heightmap_kernel_transform(_heightmap_cdata(hm), kernelsize,
- cdx, cdy, cweight, minLevel, maxLevel)
-
-def heightmap_add_voronoi(hm: np.ndarray, nbPoints: Any, nbCoef: int,
- coef: Sequence[float],
- rnd: Optional[tcod.random.Random]=None) -> None:
+ >>> libtcodpy.heightmap_kernel_transform(heightmap, 3, dx, dy, weight, 0.0, 1.0)
+ """
+ c_dx = ffi.new("int[]", dx)
+ c_dy = ffi.new("int[]", dy)
+ c_weight = ffi.new("float[]", weight)
+ lib.TCOD_heightmap_kernel_transform(_heightmap_cdata(hm), kernelsize, c_dx, c_dy, c_weight, minLevel, maxLevel)
+
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def heightmap_add_voronoi(
+ hm: NDArray[np.float32],
+ nbPoints: Any, # noqa: N803
+ nbCoef: int, # noqa: N803
+ coef: Sequence[float],
+ rnd: tcod.random.Random | None = None,
+) -> None:
"""Add values from a Voronoi diagram to the heightmap.
Args:
@@ -1771,16 +2708,29 @@ def heightmap_add_voronoi(hm: np.ndarray, nbPoints: Any, nbCoef: int,
second closest site : coef[1], ...
rnd (Optional[Random]): A Random instance, or None.
"""
- nbPoints = len(coef)
- ccoef = ffi.new('float[]', coef)
+ nbPoints = len(coef) # noqa: N806
+ ccoef = ffi.new("float[]", coef)
lib.TCOD_heightmap_add_voronoi(
- _heightmap_cdata(hm), nbPoints,
- nbCoef, ccoef, rnd.random_c if rnd else ffi.NULL)
+ _heightmap_cdata(hm),
+ nbPoints,
+ nbCoef,
+ ccoef,
+ rnd.random_c if rnd else ffi.NULL,
+ )
+
@deprecate("Arrays of noise should be sampled using the tcod.noise module.")
-def heightmap_add_fbm(hm: np.ndarray, noise: tcod.noise.Noise,
- mulx: float, muly: float, addx: float, addy: float,
- octaves: float, delta: float, scale: float) -> None:
+def heightmap_add_fbm(
+ hm: NDArray[np.float32],
+ noise: tcod.noise.Noise,
+ mulx: float,
+ muly: float,
+ addx: float,
+ addy: float,
+ octaves: float,
+ delta: float,
+ scale: float,
+) -> None:
"""Add FBM noise to the heightmap.
The noise coordinate for each map cell is
@@ -1804,14 +2754,32 @@ def heightmap_add_fbm(hm: np.ndarray, noise: tcod.noise.Noise,
as :any:`Noise.sample_ogrid`.
"""
noise = noise.noise_c if noise is not None else ffi.NULL
- lib.TCOD_heightmap_add_fbm(_heightmap_cdata(hm), noise,
- mulx, muly, addx, addy, octaves, delta, scale)
+ lib.TCOD_heightmap_add_fbm(
+ _heightmap_cdata(hm),
+ noise,
+ mulx,
+ muly,
+ addx,
+ addy,
+ octaves,
+ delta,
+ scale,
+ )
+
@deprecate("Arrays of noise should be sampled using the tcod.noise module.")
-def heightmap_scale_fbm(hm: np.ndarray, noise: tcod.noise.Noise,
- mulx: float, muly: float, addx: float, addy: float,
- octaves: float, delta: float, scale: float) -> None:
- """Multiply the heighmap values with FBM noise.
+def heightmap_scale_fbm(
+ hm: NDArray[np.float32],
+ noise: tcod.noise.Noise,
+ mulx: float,
+ muly: float,
+ addx: float,
+ addy: float,
+ octaves: float,
+ delta: float,
+ scale: float,
+) -> None:
+ """Multiply the heightmap values with FBM noise.
Args:
hm (numpy.ndarray): A numpy.ndarray formatted for heightmap functions.
@@ -1829,14 +2797,29 @@ def heightmap_scale_fbm(hm: np.ndarray, noise: tcod.noise.Noise,
as :any:`Noise.sample_ogrid`.
"""
noise = noise.noise_c if noise is not None else ffi.NULL
- lib.TCOD_heightmap_scale_fbm(_heightmap_cdata(hm), noise,
- mulx, muly, addx, addy, octaves, delta, scale)
-
-def heightmap_dig_bezier(hm: np.ndarray,
- px: Tuple[int, int, int, int],
- py: Tuple[int, int, int, int],
- startRadius: float, startDepth: float,
- endRadius: float, endDepth: float) -> None:
+ lib.TCOD_heightmap_scale_fbm(
+ _heightmap_cdata(hm),
+ noise,
+ mulx,
+ muly,
+ addx,
+ addy,
+ octaves,
+ delta,
+ scale,
+ )
+
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def heightmap_dig_bezier(
+ hm: NDArray[np.float32],
+ px: tuple[int, int, int, int],
+ py: tuple[int, int, int, int],
+ startRadius: float, # noqa: N803
+ startDepth: float, # noqa: N803
+ endRadius: float, # noqa: N803
+ endDepth: float, # noqa: N803
+) -> None:
"""Carve a path along a cubic Bezier curve.
Both radius and depth can vary linearly along the path.
@@ -1850,31 +2833,44 @@ def heightmap_dig_bezier(hm: np.ndarray,
endRadius (float): The ending radius size.
endDepth (float): The ending depth.
"""
- lib.TCOD_heightmap_dig_bezier(_heightmap_cdata(hm), px, py, startRadius,
- startDepth, endRadius,
- endDepth)
+ lib.TCOD_heightmap_dig_bezier(
+ _heightmap_cdata(hm),
+ px,
+ py,
+ startRadius,
+ startDepth,
+ endRadius,
+ endDepth,
+ )
+
-def heightmap_get_value(hm: np.ndarray, x: int, y: int) -> float:
+@deprecate("This object can be accessed as a NumPy array.")
+def heightmap_get_value(hm: NDArray[np.float32], x: int, y: int) -> float:
"""Return the value at ``x``, ``y`` in a heightmap.
.. deprecated:: 2.0
Access `hm` as a NumPy array instead.
"""
- if hm.flags['C_CONTIGUOUS']:
- warnings.warn("Get a value from this heightmap with hm[i,j]\n"
- "consider using order='F'",
- DeprecationWarning, stacklevel=2)
- return hm[y, x]
- elif hm.flags['F_CONTIGUOUS']:
- warnings.warn("Get a value from this heightmap with hm[x,y]",
- DeprecationWarning, stacklevel=2)
- return hm[x, y]
- else:
- raise ValueError("This array is not contiguous.")
+ if hm.flags["C_CONTIGUOUS"]:
+ warnings.warn(
+ "Get a value from this heightmap with hm[i,j]\nconsider using order='F'",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return hm.item(y, x)
+ if hm.flags["F_CONTIGUOUS"]:
+ warnings.warn(
+ "Get a value from this heightmap with hm[x,y]",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return hm.item(x, y)
+ msg = "This array is not contiguous."
+ raise ValueError(msg)
-def heightmap_get_interpolated_value(hm: np.ndarray,
- x: float, y: float) -> float:
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def heightmap_get_interpolated_value(hm: NDArray[np.float32], x: float, y: float) -> float:
"""Return the interpolated height at non integer coordinates.
Args:
@@ -1885,10 +2881,11 @@ def heightmap_get_interpolated_value(hm: np.ndarray,
Returns:
float: The value at ``x``, ``y``.
"""
- return lib.TCOD_heightmap_get_interpolated_value(_heightmap_cdata(hm),
- x, y)
+ return float(lib.TCOD_heightmap_get_interpolated_value(_heightmap_cdata(hm), x, y))
+
-def heightmap_get_slope(hm: np.ndarray, x: int, y: int) -> float:
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def heightmap_get_slope(hm: NDArray[np.float32], x: int, y: int) -> float:
"""Return the slope between 0 and (pi / 2) at given coordinates.
Args:
@@ -1899,10 +2896,11 @@ def heightmap_get_slope(hm: np.ndarray, x: int, y: int) -> float:
Returns:
float: The steepness at ``x``, ``y``. From 0 to (pi / 2)
"""
- return lib.TCOD_heightmap_get_slope(_heightmap_cdata(hm), x, y)
+ return float(lib.TCOD_heightmap_get_slope(_heightmap_cdata(hm), x, y))
-def heightmap_get_normal(hm: np.ndarray, x: float, y: float,
- waterLevel: float) -> Tuple[float, float, float]:
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def heightmap_get_normal(hm: NDArray[np.float32], x: float, y: float, waterLevel: float) -> tuple[float, float, float]: # noqa: N803
"""Return the map normal at given coordinates.
Args:
@@ -1914,12 +2912,13 @@ def heightmap_get_normal(hm: np.ndarray, x: float, y: float,
Returns:
Tuple[float, float, float]: An (x, y, z) vector normal.
"""
- cn = ffi.new('float[3]')
+ cn = ffi.new("float[3]")
lib.TCOD_heightmap_get_normal(_heightmap_cdata(hm), x, y, cn, waterLevel)
return tuple(cn)
+
@deprecate("This function is deprecated, see documentation.")
-def heightmap_count_cells(hm: np.ndarray, mi: float, ma: float) -> int:
+def heightmap_count_cells(hm: NDArray[np.float32], mi: float, ma: float) -> int:
"""Return the number of map cells which value is between ``mi`` and ``ma``.
Args:
@@ -1934,23 +2933,25 @@ def heightmap_count_cells(hm: np.ndarray, mi: float, ma: float) -> int:
Can be replaced by an equivalent NumPy function such as:
``numpy.count_nonzero((mi <= hm) & (hm < ma))``
"""
- return lib.TCOD_heightmap_count_cells(_heightmap_cdata(hm), mi, ma)
+ return int(lib.TCOD_heightmap_count_cells(_heightmap_cdata(hm), mi, ma))
-def heightmap_has_land_on_border(hm: np.ndarray, waterlevel: float) -> bool:
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def heightmap_has_land_on_border(hm: NDArray[np.float32], waterlevel: float) -> bool:
"""Returns True if the map edges are below ``waterlevel``, otherwise False.
Args:
hm (numpy.ndarray): A numpy.ndarray formatted for heightmap functions.
- waterLevel (float): The water level to use.
+ waterlevel (float): The water level to use.
Returns:
bool: True if the map edges are below ``waterlevel``, otherwise False.
"""
- return lib.TCOD_heightmap_has_land_on_border(_heightmap_cdata(hm),
- waterlevel)
+ return bool(lib.TCOD_heightmap_has_land_on_border(_heightmap_cdata(hm), waterlevel))
+
@deprecate("Use `hm.min()` and `hm.max()` instead.")
-def heightmap_get_minmax(hm: np.ndarray) -> Tuple[float, float]:
+def heightmap_get_minmax(hm: NDArray[np.float32]) -> tuple[float, float]:
"""Return the min and max values of this heightmap.
Args:
@@ -1962,11 +2963,12 @@ def heightmap_get_minmax(hm: np.ndarray) -> Tuple[float, float]:
.. deprecated:: 2.0
Use ``hm.min()`` or ``hm.max()`` instead.
"""
- mi = ffi.new('float *')
- ma = ffi.new('float *')
+ mi = ffi.new("float *")
+ ma = ffi.new("float *")
lib.TCOD_heightmap_get_minmax(_heightmap_cdata(hm), mi, ma)
return mi[0], ma[0]
+
@deprecate("libtcod objects are deleted automatically.")
def heightmap_delete(hm: Any) -> None:
"""Does nothing. libtcod objects are managed by Python's garbage collector.
@@ -1977,98 +2979,184 @@ def heightmap_delete(hm: Any) -> None:
libtcod-cffi deletes heightmaps automatically.
"""
-def image_new(width, height):
+
+@deprecate("Use `tcod.image.Image(width, height)` instead.", category=FutureWarning)
+def image_new(width: int, height: int) -> tcod.image.Image:
return tcod.image.Image(width, height)
-def image_clear(image, col):
+
+@deprecate("Use the `image.clear()` method instead.", category=FutureWarning)
+def image_clear(image: tcod.image.Image, col: tuple[int, int, int]) -> None:
image.clear(col)
-def image_invert(image):
+
+@deprecate("Use the `image.invert()` method instead.", category=FutureWarning)
+def image_invert(image: tcod.image.Image) -> None:
image.invert()
-def image_hflip(image):
+
+@deprecate("Use the `image.hflip()` method instead.", category=FutureWarning)
+def image_hflip(image: tcod.image.Image) -> None:
image.hflip()
-def image_rotate90(image, num=1):
+
+@deprecate("Use the `image.rotate90(n)` method instead.", category=FutureWarning)
+def image_rotate90(image: tcod.image.Image, num: int = 1) -> None:
image.rotate90(num)
-def image_vflip(image):
+
+@deprecate("Use the `image.vflip()` method instead.", category=FutureWarning)
+def image_vflip(image: tcod.image.Image) -> None:
image.vflip()
-def image_scale(image, neww, newh):
+
+@deprecate("Use the `image.scale(new_width, new_height)` method instead.", category=FutureWarning)
+def image_scale(image: tcod.image.Image, neww: int, newh: int) -> None:
image.scale(neww, newh)
-def image_set_key_color(image, col):
+
+@deprecate("Use the `image.image_set_key_color(rgb)` method instead.", category=FutureWarning)
+def image_set_key_color(image: tcod.image.Image, col: tuple[int, int, int]) -> None:
image.set_key_color(col)
-def image_get_alpha(image, x, y):
- image.get_alpha(x, y)
-def image_is_pixel_transparent(image, x, y):
- lib.TCOD_image_is_pixel_transparent(image.image_c, x, y)
+@deprecate("Use `np.asarray(image)[y, x, 3]` instead.", category=FutureWarning)
+def image_get_alpha(image: tcod.image.Image, x: int, y: int) -> int:
+ return image.get_alpha(x, y)
+
-def image_load(filename):
+@deprecate("Use the Numpy array interface to check alpha or color keys.", category=FutureWarning)
+def image_is_pixel_transparent(image: tcod.image.Image, x: int, y: int) -> bool:
+ return bool(lib.TCOD_image_is_pixel_transparent(image.image_c, x, y))
+
+
+@deprecate(
+ "Call the classmethod `tcod.image.Image.from_file` instead to load images."
+ "\nIt's recommended to load images with a more complete image library such as python-Pillow or python-imageio.",
+ category=FutureWarning,
+)
+def image_load(filename: str | PathLike[str]) -> tcod.image.Image:
"""Load an image file into an Image instance and return it.
Args:
- filename (AnyStr): Path to a .bmp or .png image file.
+ filename: Path to a .bmp or .png image file.
+
+ .. versionchanged:: 16.0
+ Added PathLike support.
+
+ .. deprecated:: 16.0
+ Use :any:`tcod.image.Image.from_file` instead.
"""
- return tcod.image.Image._from_cdata(
- ffi.gc(lib.TCOD_image_load(_bytes(filename)),
- lib.TCOD_image_delete)
- )
+ return tcod.image.Image.from_file(filename)
-def image_from_console(console):
+
+@deprecate("Use `Tileset.render` instead of this function.", category=FutureWarning)
+def image_from_console(console: tcod.console.Console) -> tcod.image.Image:
"""Return an Image with a Consoles pixel data.
This effectively takes a screen-shot of the Console.
Args:
console (Console): Any Console instance.
+
+ .. deprecated:: 16.0
+ :any:`Tileset.render` is a better alternative.
"""
return tcod.image.Image._from_cdata(
ffi.gc(
lib.TCOD_image_from_console(_console(console)),
lib.TCOD_image_delete,
- )
)
+ )
+
-def image_refresh_console(image, console):
+@deprecate("Use `Tileset.render` instead of this function.", category=FutureWarning)
+def image_refresh_console(image: tcod.image.Image, console: tcod.console.Console) -> None:
+ """Update an image made with :any:`image_from_console`.
+
+ .. deprecated:: 16.0
+ This function is unnecessary, use :any:`Tileset.render` instead.
+ """
image.refresh_console(console)
-def image_get_size(image):
+
+@deprecate("Access an images size with `image.width` or `image.height`.", category=FutureWarning)
+def image_get_size(image: tcod.image.Image) -> tuple[int, int]:
return image.width, image.height
-def image_get_pixel(image, x, y):
+
+@deprecate("Use `np.asarray(image)[y, x, :3]` instead.", category=FutureWarning)
+def image_get_pixel(image: tcod.image.Image, x: int, y: int) -> tuple[int, int, int]:
return image.get_pixel(x, y)
-def image_get_mipmap_pixel(image, x0, y0, x1, y1):
+
+@deprecate("Use the `image.get_mipmap_pixel(...)` method instead.", category=FutureWarning)
+def image_get_mipmap_pixel(image: tcod.image.Image, x0: float, y0: float, x1: float, y1: float) -> tuple[int, int, int]:
return image.get_mipmap_pixel(x0, y0, x1, y1)
-def image_put_pixel(image, x, y, col):
+
+@deprecate("Use `np.asarray(image)[y, x, :3] = rgb` instead.", category=FutureWarning)
+def image_put_pixel(image: tcod.image.Image, x: int, y: int, col: tuple[int, int, int]) -> None:
image.put_pixel(x, y, col)
-def image_blit(image, console, x, y, bkgnd_flag, scalex, scaley, angle):
+
+@deprecate("Use the `image.blit(...)` method instead.", category=FutureWarning)
+def image_blit(
+ image: tcod.image.Image,
+ console: tcod.console.Console,
+ x: float,
+ y: float,
+ bkgnd_flag: int,
+ scalex: float,
+ scaley: float,
+ angle: float,
+) -> None:
image.blit(console, x, y, bkgnd_flag, scalex, scaley, angle)
-def image_blit_rect(image, console, x, y, w, h, bkgnd_flag):
+
+@deprecate("Use the `image.blit_rect(...)` method instead.", category=FutureWarning)
+def image_blit_rect(
+ image: tcod.image.Image,
+ console: tcod.console.Console,
+ x: int,
+ y: int,
+ w: int,
+ h: int,
+ bkgnd_flag: int,
+) -> None:
image.blit_rect(console, x, y, w, h, bkgnd_flag)
-def image_blit_2x(image, console, dx, dy, sx=0, sy=0, w=-1, h=-1):
+
+@deprecate("Use `Console.draw_semigraphics(image, ...)` instead.", category=FutureWarning)
+def image_blit_2x(
+ image: tcod.image.Image,
+ console: tcod.console.Console,
+ dx: int,
+ dy: int,
+ sx: int = 0,
+ sy: int = 0,
+ w: int = -1,
+ h: int = -1,
+) -> None:
image.blit_2x(console, dx, dy, sx, sy, w, h)
-def image_save(image, filename):
+
+@deprecate("Use the `image.save_as` method instead.", category=FutureWarning)
+def image_save(image: tcod.image.Image, filename: str | PathLike[str]) -> None:
image.save_as(filename)
-def image_delete(image):
- # type (Any) -> None
+
+@deprecate("libtcod objects are deleted automatically.", category=FutureWarning)
+def image_delete(image: tcod.image.Image) -> None:
"""Does nothing. libtcod objects are managed by Python's garbage collector.
This function exists for backwards compatibility with libtcodpy.
"""
-def line_init(xo, yo, xd, yd):
- """Initilize a line whose points will be returned by `line_step`.
+
+@deprecate("Use tcod.los.bresenham instead.", category=FutureWarning)
+def line_init(xo: int, yo: int, xd: int, yd: int) -> None:
+ """Initialize a line whose points will be returned by `line_step`.
This function does not return anything on its own.
@@ -2081,11 +3169,13 @@ def line_init(xo, yo, xd, yd):
yd (int): Y destination point.
.. deprecated:: 2.0
- Use `line_iter` instead.
+ This function was replaced by :any:`tcod.los.bresenham`.
"""
lib.TCOD_line_init(xo, yo, xd, yd)
-def line_step():
+
+@deprecate("Use tcod.los.bresenham instead.", category=FutureWarning)
+def line_step() -> tuple[int, int] | tuple[None, None]:
"""After calling line_init returns (x, y) points of the line.
Once all points are exhausted this function will return (None, None)
@@ -2096,18 +3186,19 @@ def line_step():
or (None, None) if there are no more points.
.. deprecated:: 2.0
- Use `line_iter` instead.
+ This function was replaced by :any:`tcod.los.bresenham`.
"""
- x = ffi.new('int *')
- y = ffi.new('int *')
+ x = ffi.new("int *")
+ y = ffi.new("int *")
ret = lib.TCOD_line_step(x, y)
if not ret:
return x[0], y[0]
- return None,None
+ return None, None
-def line(xo, yo, xd, yd, py_callback):
- """ Iterate over a line using a callback function.
+@deprecate("Use tcod.los.bresenham instead.", category=FutureWarning)
+def line(xo: int, yo: int, xd: int, yd: int, py_callback: Callable[[int, int], bool]) -> bool:
+ """Iterate over a line using a callback function.
Your callback function will take x and y parameters and return True to
continue iteration or False to stop iteration and return.
@@ -2123,11 +3214,11 @@ def line(xo, yo, xd, yd, py_callback):
A callback which takes x and y parameters and returns bool.
Returns:
- bool: False if the callback cancels the line interation by
+ bool: False if the callback cancels the line interaction by
returning False or None, otherwise True.
.. deprecated:: 2.0
- Use `line_iter` instead.
+ This function was replaced by :any:`tcod.los.bresenham`.
"""
for x, y in line_iter(xo, yo, xd, yd):
if not py_callback(x, y):
@@ -2137,10 +3228,11 @@ def line(xo, yo, xd, yd, py_callback):
return False
-def line_iter(xo, yo, xd, yd):
- """ returns an iterator
+@deprecate("This function has been replaced by tcod.los.bresenham.", category=FutureWarning)
+def line_iter(xo: int, yo: int, xd: int, yd: int) -> Iterator[tuple[int, int]]:
+ """Returns an Iterable over a Bresenham line.
- This iterator does not include the origin point.
+ This Iterable does not include the origin point.
Args:
xo (int): X starting point.
@@ -2149,65 +3241,40 @@ def line_iter(xo, yo, xd, yd):
yd (int): Y destination point.
Returns:
- Iterator[Tuple[int,int]]: An iterator of (x,y) points.
+ Iterable[Tuple[int,int]]: An Iterable of (x,y) points.
+
+ .. deprecated:: 11.14
+ This function was replaced by :any:`tcod.los.bresenham`.
"""
- data = ffi.new('TCOD_bresenham_data_t *')
+ data = ffi.new("TCOD_bresenham_data_t *")
lib.TCOD_line_init_mt(xo, yo, xd, yd, data)
- x = ffi.new('int *')
- y = ffi.new('int *')
+ x = ffi.new("int *")
+ y = ffi.new("int *")
yield xo, yo
while not lib.TCOD_line_step_mt(x, y, data):
yield (x[0], y[0])
-def line_where(x1, y1, x2, y2, inclusive=True):
- # type: (int, int, int, int, bool) -> tuple[np.ndarray, np.ndarray]
+@deprecate("This function has been replaced by tcod.los.bresenham.", category=FutureWarning)
+def line_where(x1: int, y1: int, x2: int, y2: int, inclusive: bool = True) -> tuple[NDArray[np.intc], NDArray[np.intc]]: # noqa: FBT001, FBT002
"""Return a NumPy index array following a Bresenham line.
If `inclusive` is true then the start point is included in the result.
- Example:
- >>> where = tcod.line_where(1, 0, 3, 4)
- >>> where
- (array([1, 1, 2, 2, 3]...), array([0, 1, 2, 3, 4]...))
- >>> array = np.zeros((5, 5), dtype=np.int32)
- >>> array[where] = np.arange(len(where[0])) + 1
- >>> array
- array([[0, 0, 0, 0, 0],
- [1, 2, 0, 0, 0],
- [0, 0, 3, 4, 0],
- [0, 0, 0, 0, 5],
- [0, 0, 0, 0, 0]]...)
-
.. versionadded:: 4.6
+
+ .. deprecated:: 11.14
+ This function was replaced by :any:`tcod.los.bresenham`.
"""
- length = max(abs(x1 - x2), abs(y1 - y2)) + 1
- array = np.ndarray((2, length), dtype=np.intc)
- x = ffi.cast('int*', array[0].ctypes.data)
- y = ffi.cast('int*', array[1].ctypes.data)
- lib.LineWhere(x1, y1, x2, y2, x, y)
+ array = tcod.los.bresenham((x1, y1), (x2, y2)).T
if not inclusive:
array = array[:, 1:]
- return tuple(array)
-
-
-FOV_BASIC = 0
-FOV_DIAMOND = 1
-FOV_SHADOW = 2
-FOV_PERMISSIVE_0 = 3
-FOV_PERMISSIVE_1 = 4
-FOV_PERMISSIVE_2 = 5
-FOV_PERMISSIVE_3 = 6
-FOV_PERMISSIVE_4 = 7
-FOV_PERMISSIVE_5 = 8
-FOV_PERMISSIVE_6 = 9
-FOV_PERMISSIVE_7 = 10
-FOV_PERMISSIVE_8 = 11
-FOV_RESTRICTIVE = 12
-NB_FOV_ALGORITHMS = 13
+ i, j = array
+ return i, j
+
-def map_new(w, h):
- # type: (int, int) -> tcod.map.Map
+@deprecate("Call tcod.map.Map(width, height) instead.", category=FutureWarning)
+def map_new(w: int, h: int) -> tcod.map.Map:
"""Return a :any:`tcod.map.Map` with a width and height.
.. deprecated:: 4.5
@@ -2216,8 +3283,9 @@ def map_new(w, h):
"""
return tcod.map.Map(w, h)
-def map_copy(source, dest):
- # type: (tcod.map.Map, tcod.map.Map) -> None
+
+@deprecate("Use Python's standard copy module instead.", category=FutureWarning)
+def map_copy(source: tcod.map.Map, dest: tcod.map.Map) -> None:
"""Copy map data from `source` to `dest`.
.. deprecated:: 4.5
@@ -2225,11 +3293,12 @@ def map_copy(source, dest):
array attributes manually.
"""
if source.width != dest.width or source.height != dest.height:
- dest.__init__(source.width, source.height, source._order)
- dest._Map__buffer[:] = source._Map__buffer[:]
+ tcod.map.Map.__init__(dest, source.width, source.height, source._order)
+ dest._buffer[:] = source._buffer[:]
+
-def map_set_properties(m, x, y, isTrans, isWalk):
- # type: (tcod.map.Map, int, int, bool, bool) -> None
+@deprecate("Set properties using the m.transparent and m.walkable arrays.", category=FutureWarning)
+def map_set_properties(m: tcod.map.Map, x: int, y: int, isTrans: bool, isWalk: bool) -> None: # noqa: FBT001, N803
"""Set the properties of a single cell.
.. note::
@@ -2240,8 +3309,9 @@ def map_set_properties(m, x, y, isTrans, isWalk):
"""
lib.TCOD_map_set_properties(m.map_c, x, y, isTrans, isWalk)
-def map_clear(m, transparent=False, walkable=False):
- # type: (tcod.map.Map, bool, bool) -> None
+
+@deprecate("Clear maps using NumPy broadcast rules instead.", category=FutureWarning)
+def map_clear(m: tcod.map.Map, transparent: bool = False, walkable: bool = False) -> None: # noqa: FBT001, FBT002
"""Change all map cells to a specific value.
.. deprecated:: 4.5
@@ -2251,8 +3321,16 @@ def map_clear(m, transparent=False, walkable=False):
m.transparent[:] = transparent
m.walkable[:] = walkable
-def map_compute_fov(m, x, y, radius=0, light_walls=True, algo=FOV_RESTRICTIVE ):
- # type: (tcod.map.Map, int, int, int, bool, int) -> None
+
+@deprecate("Call the map.compute_fov method instead.", category=FutureWarning)
+def map_compute_fov(
+ m: tcod.map.Map,
+ x: int,
+ y: int,
+ radius: int = 0,
+ light_walls: bool = True, # noqa: FBT001, FBT002
+ algo: int = FOV_RESTRICTIVE,
+) -> None:
"""Compute the field-of-view for a map instance.
.. deprecated:: 4.5
@@ -2260,47 +3338,53 @@ def map_compute_fov(m, x, y, radius=0, light_walls=True, algo=FOV_RESTRICTIVE ):
"""
m.compute_fov(x, y, radius, light_walls, algo)
-def map_is_in_fov(m, x, y):
- # type: (tcod.map.Map, int, int) -> bool
- """Return True if the cell at x,y is lit by the last field-of-view
- algorithm.
+
+@deprecate("Use map.fov to check for this property.", category=FutureWarning)
+def map_is_in_fov(m: tcod.map.Map, x: int, y: int) -> bool:
+ """Return True if the cell at x,y is lit by the last field-of-view algorithm.
.. note::
This function is slow.
.. deprecated:: 4.5
Use :any:`tcod.map.Map.fov` to check this property.
"""
- return lib.TCOD_map_is_in_fov(m.map_c, x, y)
+ return bool(lib.TCOD_map_is_in_fov(m.map_c, x, y))
+
+
+@deprecate("Use map.transparent to check for this property.", category=FutureWarning)
+def map_is_transparent(m: tcod.map.Map, x: int, y: int) -> bool:
+ """Return True is a map cell is transparent.
-def map_is_transparent(m, x, y):
- # type: (tcod.map.Map, int, int) -> bool
- """
.. note::
This function is slow.
.. deprecated:: 4.5
Use :any:`tcod.map.Map.transparent` to check this property.
"""
- return lib.TCOD_map_is_transparent(m.map_c, x, y)
+ return bool(lib.TCOD_map_is_transparent(m.map_c, x, y))
+
+
+@deprecate("Use map.walkable to check for this property.", category=FutureWarning)
+def map_is_walkable(m: tcod.map.Map, x: int, y: int) -> bool:
+ """Return True is a map cell is walkable.
-def map_is_walkable(m, x, y):
- # type: (tcod.map.Map, int, int) -> bool
- """
.. note::
This function is slow.
.. deprecated:: 4.5
Use :any:`tcod.map.Map.walkable` to check this property.
"""
- return lib.TCOD_map_is_walkable(m.map_c, x, y)
+ return bool(lib.TCOD_map_is_walkable(m.map_c, x, y))
-def map_delete(m):
- # type (Any) -> None
+
+@deprecate("libtcod objects are deleted automatically.", category=FutureWarning)
+def map_delete(m: tcod.map.Map) -> None:
"""Does nothing. libtcod objects are managed by Python's garbage collector.
This function exists for backwards compatibility with libtcodpy.
"""
-def map_get_width(map):
- # type: (tcod.map.Map) -> int
+
+@deprecate("Check the map.width attribute instead.", category=FutureWarning)
+def map_get_width(map: tcod.map.Map) -> int: # noqa: A002
"""Return the width of a map.
.. deprecated:: 4.5
@@ -2308,8 +3392,9 @@ def map_get_width(map):
"""
return map.width
-def map_get_height(map):
- # type: (tcod.map.Map) -> int
+
+@deprecate("Check the map.height attribute instead.", category=FutureWarning)
+def map_get_height(map: tcod.map.Map) -> int: # noqa: A002
"""Return the height of a map.
.. deprecated:: 4.5
@@ -2317,49 +3402,76 @@ def map_get_height(map):
"""
return map.height
-def mouse_show_cursor(visible):
- # type: (bool) -> None
- """Change the visibility of the mouse cursor."""
+
+@deprecate("Use `tcod.sdl.mouse.show(visible)` instead.", category=FutureWarning)
+def mouse_show_cursor(visible: bool) -> None: # noqa: FBT001
+ """Change the visibility of the mouse cursor.
+
+ .. deprecated:: 16.0
+ Use :any:`tcod.sdl.mouse.show` instead.
+ """
lib.TCOD_mouse_show_cursor(visible)
-def mouse_is_cursor_visible():
- # type: () -> bool
- """Return True if the mouse cursor is visible."""
- return lib.TCOD_mouse_is_cursor_visible()
-def mouse_move(x, y):
- # type (int, int) -> None
+@deprecate("Use `is_visible = tcod.sdl.mouse.show()` instead.", category=FutureWarning)
+def mouse_is_cursor_visible() -> bool:
+ """Return True if the mouse cursor is visible.
+
+ .. deprecated:: 16.0
+ Use :any:`tcod.sdl.mouse.show` instead.
+ """
+ return bool(lib.TCOD_mouse_is_cursor_visible())
+
+
+@deprecate("Use `tcod.sdl.mouse.warp_in_window` instead.", category=FutureWarning)
+def mouse_move(x: int, y: int) -> None:
lib.TCOD_mouse_move(x, y)
-def mouse_get_status():
- # type: () -> Mouse
+
+@deprecate("Use tcod.event.get_mouse_state() instead.", category=FutureWarning)
+def mouse_get_status() -> Mouse:
return Mouse(lib.TCOD_mouse_get_status())
-def namegen_parse(filename,random=None):
- lib.TCOD_namegen_parse(_bytes(filename), random or ffi.NULL)
-def namegen_generate(name):
- return _unpack_char_p(lib.TCOD_namegen_generate(_bytes(name), False))
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def namegen_parse(filename: str | PathLike[str], random: tcod.random.Random | None = None) -> None:
+ lib.TCOD_namegen_parse(_path_encode(Path(filename).resolve(strict=True)), random or ffi.NULL)
+
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def namegen_generate(name: str) -> str:
+ return _unpack_char_p(lib.TCOD_namegen_generate(_bytes(name), False)) # noqa: FBT003
+
-def namegen_generate_custom(name, rule):
- return _unpack_char_p(lib.TCOD_namegen_generate(_bytes(name),
- _bytes(rule), False))
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def namegen_generate_custom(name: str, rule: str) -> str:
+ return _unpack_char_p(lib.TCOD_namegen_generate_custom(_bytes(name), _bytes(rule), False)) # noqa: FBT003
-def namegen_get_sets():
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def namegen_get_sets() -> list[str]:
sets = lib.TCOD_namegen_get_sets()
try:
lst = []
while not lib.TCOD_list_is_empty(sets):
- lst.append(_unpack_char_p(ffi.cast('char *', lib.TCOD_list_pop(sets))))
+ lst.append(_unpack_char_p(ffi.cast("char *", lib.TCOD_list_pop(sets))))
finally:
lib.TCOD_list_delete(sets)
return lst
-def namegen_destroy():
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def namegen_destroy() -> None:
lib.TCOD_namegen_destroy()
-def noise_new(dim, h=NOISE_DEFAULT_HURST, l=NOISE_DEFAULT_LACUNARITY,
- random=None):
+
+@deprecate("Use `tcod.noise.Noise(dimensions, hurst=, lacunarity=)` instead.", category=FutureWarning)
+def noise_new(
+ dim: int,
+ h: float = NOISE_DEFAULT_HURST,
+ l: float = NOISE_DEFAULT_LACUNARITY, # noqa: E741
+ random: tcod.random.Random | None = None,
+) -> tcod.noise.Noise:
"""Return a new Noise instance.
Args:
@@ -2373,21 +3485,26 @@ def noise_new(dim, h=NOISE_DEFAULT_HURST, l=NOISE_DEFAULT_LACUNARITY,
"""
return tcod.noise.Noise(dim, hurst=h, lacunarity=l, seed=random)
-def noise_set_type(n, typ):
+
+@deprecate("Use `noise.algorithm = x` instead.", category=FutureWarning)
+def noise_set_type(n: tcod.noise.Noise, typ: int) -> None:
"""Set a Noise objects default noise algorithm.
Args:
+ n: Noise object.
typ (int): Any NOISE_* constant.
"""
n.algorithm = typ
-def noise_get(n, f, typ=NOISE_DEFAULT):
+
+@deprecate("Use `value = noise[x]` instead.", category=FutureWarning)
+def noise_get(n: tcod.noise.Noise, f: Sequence[float], typ: int = NOISE_DEFAULT) -> float:
"""Return the noise value sampled from the ``f`` coordinate.
- ``f`` should be a tuple or list with a length matching
- :any:`Noise.dimensions`.
- If ``f`` is shoerter than :any:`Noise.dimensions` the missing coordinates
- will be filled with zeros.
+ ``f`` should be a tuple or list with a length matching the `dimensions`
+ attribute of :any:`Noise`.
+ If ``f`` is shorter than `dimensions` the missing coordinates will be
+ filled with zeros.
Args:
n (Noise): A Noise instance.
@@ -2397,170 +3514,208 @@ def noise_get(n, f, typ=NOISE_DEFAULT):
Returns:
float: The sampled noise value.
"""
- return lib.TCOD_noise_get_ex(n.noise_c, ffi.new('float[4]', f), typ)
+ return float(lib.TCOD_noise_get_ex(n.noise_c, ffi.new("float[4]", f), typ))
+
-def noise_get_fbm(n, f, oc, typ=NOISE_DEFAULT):
+@deprecate("Configure a Noise instance for FBM and then sample it like normal.", category=FutureWarning)
+def noise_get_fbm(
+ n: tcod.noise.Noise,
+ f: Sequence[float],
+ oc: float,
+ typ: int = NOISE_DEFAULT,
+) -> float:
"""Return the fractal Brownian motion sampled from the ``f`` coordinate.
Args:
n (Noise): A Noise instance.
f (Sequence[float]): The point to sample the noise from.
typ (int): The noise algorithm to use.
- octaves (float): The level of level. Should be more than 1.
+ oc (float): The level of level. Should be more than 1.
Returns:
float: The sampled noise value.
"""
- return lib.TCOD_noise_get_fbm_ex(n.noise_c, ffi.new('float[4]', f),
- oc, typ)
+ return float(lib.TCOD_noise_get_fbm_ex(n.noise_c, ffi.new("float[4]", f), oc, typ))
-def noise_get_turbulence(n, f, oc, typ=NOISE_DEFAULT):
+
+@deprecate("Configure a Noise instance for FBM and then sample it like normal.", category=FutureWarning)
+def noise_get_turbulence(
+ n: tcod.noise.Noise,
+ f: Sequence[float],
+ oc: float,
+ typ: int = NOISE_DEFAULT,
+) -> float:
"""Return the turbulence noise sampled from the ``f`` coordinate.
Args:
n (Noise): A Noise instance.
f (Sequence[float]): The point to sample the noise from.
typ (int): The noise algorithm to use.
- octaves (float): The level of level. Should be more than 1.
+ oc (float): The level of level. Should be more than 1.
Returns:
float: The sampled noise value.
"""
- return lib.TCOD_noise_get_turbulence_ex(n.noise_c, ffi.new('float[4]', f),
- oc, typ)
+ return float(lib.TCOD_noise_get_turbulence_ex(n.noise_c, ffi.new("float[4]", f), oc, typ))
+
-def noise_delete(n):
+@deprecate("libtcod objects are deleted automatically.", category=FutureWarning)
+def noise_delete(n: tcod.noise.Noise) -> None:
# type (Any) -> None
"""Does nothing. libtcod objects are managed by Python's garbage collector.
This function exists for backwards compatibility with libtcodpy.
"""
-_chr = chr
-try:
- _chr = unichr # Python 2
-except NameError:
- pass
-def _unpack_union(type_, union):
- '''
- unpack items from parser new_property (value_converter)
- '''
+def _unpack_union(type_: int, union: Any) -> Any: # noqa: PLR0911
+ """Unpack items from parser new_property (value_converter)."""
if type_ == lib.TCOD_TYPE_BOOL:
return bool(union.b)
- elif type_ == lib.TCOD_TYPE_CHAR:
- return union.c.decode('latin-1')
- elif type_ == lib.TCOD_TYPE_INT:
+ if type_ == lib.TCOD_TYPE_CHAR:
+ return union.c.decode("latin-1")
+ if type_ == lib.TCOD_TYPE_INT:
return union.i
- elif type_ == lib.TCOD_TYPE_FLOAT:
+ if type_ == lib.TCOD_TYPE_FLOAT:
return union.f
- elif (type_ == lib.TCOD_TYPE_STRING or
- lib.TCOD_TYPE_VALUELIST15 >= type_ >= lib.TCOD_TYPE_VALUELIST00):
- return _unpack_char_p(union.s)
- elif type_ == lib.TCOD_TYPE_COLOR:
+ if type_ == lib.TCOD_TYPE_STRING or lib.TCOD_TYPE_VALUELIST15 >= type_ >= lib.TCOD_TYPE_VALUELIST00:
+ return _unpack_char_p(union.s)
+ if type_ == lib.TCOD_TYPE_COLOR:
return Color._new_from_cdata(union.col)
- elif type_ == lib.TCOD_TYPE_DICE:
+ if type_ == lib.TCOD_TYPE_DICE:
return Dice(union.dice)
- elif type_ & lib.TCOD_TYPE_LIST:
+ if type_ & lib.TCOD_TYPE_LIST:
return _convert_TCODList(union.list, type_ & 0xFF)
- else:
- raise RuntimeError('Unknown libtcod type: %i' % type_)
+ msg = f"Unknown libtcod type: {type_}"
+ raise RuntimeError(msg)
+
-def _convert_TCODList(clist, type_):
- return [_unpack_union(type_, lib.TDL_list_get_union(clist, i))
- for i in range(lib.TCOD_list_size(clist))]
+def _convert_TCODList(c_list: Any, type_: int) -> Any: # noqa: N802
+ with ffi.new("TCOD_value_t[]", lib.TCOD_list_size(c_list)) as unions:
+ for i, union in enumerate(unions):
+ union.custom = lib.TCOD_list_get(c_list, i)
+ return [_unpack_union(type_, union) for union in unions]
-def parser_new():
+
+@deprecate("Parser functions have been deprecated.")
+def parser_new() -> Any:
return ffi.gc(lib.TCOD_parser_new(), lib.TCOD_parser_delete)
-def parser_new_struct(parser, name):
- return lib.TCOD_parser_new_struct(parser, name)
+
+@deprecate("Parser functions have been deprecated.")
+def parser_new_struct(parser: Any, name: str) -> Any:
+ return lib.TCOD_parser_new_struct(parser, _bytes(name))
+
# prevent multiple threads from messing with def_extern callbacks
-_parser_callback_lock = _threading.Lock()
-_parser_listener = None # temporary global pointer to a listener instance
+_parser_callback_lock = threading.Lock()
+# temporary global pointer to a listener instance
+_parser_listener: Any = None
+
+
+@ffi.def_extern() # type: ignore[untyped-decorator]
+def _pycall_parser_new_struct(struct: Any, name: str) -> Any:
+ return _parser_listener.new_struct(struct, _unpack_char_p(name))
-@ffi.def_extern()
-def _pycall_parser_new_struct(struct, name):
- return _parser_listener.end_struct(struct, _unpack_char_p(name))
-@ffi.def_extern()
-def _pycall_parser_new_flag(name):
+@ffi.def_extern() # type: ignore[untyped-decorator]
+def _pycall_parser_new_flag(name: str) -> Any:
return _parser_listener.new_flag(_unpack_char_p(name))
-@ffi.def_extern()
-def _pycall_parser_new_property(propname, type, value):
- return _parser_listener.new_property(_unpack_char_p(propname), type,
- _unpack_union(type, value))
-@ffi.def_extern()
-def _pycall_parser_end_struct(struct, name):
+@ffi.def_extern() # type: ignore[untyped-decorator]
+def _pycall_parser_new_property(propname: Any, type: Any, value: Any) -> Any: # noqa: A002
+ return _parser_listener.new_property(_unpack_char_p(propname), type, _unpack_union(type, value))
+
+
+@ffi.def_extern() # type: ignore[untyped-decorator]
+def _pycall_parser_end_struct(struct: Any, name: Any) -> Any:
return _parser_listener.end_struct(struct, _unpack_char_p(name))
-@ffi.def_extern()
-def _pycall_parser_error(msg):
+
+@ffi.def_extern() # type: ignore[untyped-decorator]
+def _pycall_parser_error(msg: Any) -> None:
_parser_listener.error(_unpack_char_p(msg))
-def parser_run(parser, filename, listener=None):
- global _parser_listener
+
+@deprecate("Parser functions have been deprecated.")
+def parser_run(parser: Any, filename: str | PathLike[str], listener: Any = None) -> None:
+ global _parser_listener # noqa: PLW0603
+ filename = Path(filename).resolve(strict=True)
if not listener:
- lib.TCOD_parser_run(parser, _bytes(filename), ffi.NULL)
+ lib.TCOD_parser_run(parser, _path_encode(filename), ffi.NULL)
return
propagate_manager = _PropagateException()
- propagate = propagate_manager.propagate
- clistener = ffi.new(
- 'TCOD_parser_listener_t *',
+ c_listener = ffi.new(
+ "TCOD_parser_listener_t *",
{
- 'new_struct': lib._pycall_parser_new_struct,
- 'new_flag': lib._pycall_parser_new_flag,
- 'new_property': lib._pycall_parser_new_property,
- 'end_struct': lib._pycall_parser_end_struct,
- 'error': lib._pycall_parser_error,
+ "new_struct": lib._pycall_parser_new_struct,
+ "new_flag": lib._pycall_parser_new_flag,
+ "new_property": lib._pycall_parser_new_property,
+ "end_struct": lib._pycall_parser_end_struct,
+ "error": lib._pycall_parser_error,
},
)
with _parser_callback_lock:
_parser_listener = listener
with propagate_manager:
- lib.TCOD_parser_run(parser, _bytes(filename), clistener)
+ lib.TCOD_parser_run(parser, _path_encode(filename), c_listener)
-def parser_delete(parser):
+
+@deprecate("libtcod objects are deleted automatically.")
+def parser_delete(parser: Any) -> None:
# type (Any) -> None
"""Does nothing. libtcod objects are managed by Python's garbage collector.
This function exists for backwards compatibility with libtcodpy.
"""
-def parser_get_bool_property(parser, name):
+
+@deprecate("Parser functions have been deprecated.")
+def parser_get_bool_property(parser: Any, name: str) -> bool:
return bool(lib.TCOD_parser_get_bool_property(parser, _bytes(name)))
-def parser_get_int_property(parser, name):
- return lib.TCOD_parser_get_int_property(parser, _bytes(name))
-def parser_get_char_property(parser, name):
- return _chr(lib.TCOD_parser_get_char_property(parser, _bytes(name)))
+@deprecate("Parser functions have been deprecated.")
+def parser_get_int_property(parser: Any, name: str) -> int:
+ return int(lib.TCOD_parser_get_int_property(parser, _bytes(name)))
+
+
+@deprecate("Parser functions have been deprecated.")
+def parser_get_char_property(parser: Any, name: str) -> str:
+ return chr(lib.TCOD_parser_get_char_property(parser, _bytes(name)))
+
+
+@deprecate("Parser functions have been deprecated.")
+def parser_get_float_property(parser: Any, name: str) -> float:
+ return float(lib.TCOD_parser_get_float_property(parser, _bytes(name)))
+
-def parser_get_float_property(parser, name):
- return lib.TCOD_parser_get_float_property(parser, _bytes(name))
+@deprecate("Parser functions have been deprecated.")
+def parser_get_string_property(parser: Any, name: str) -> str:
+ return _unpack_char_p(lib.TCOD_parser_get_string_property(parser, _bytes(name)))
-def parser_get_string_property(parser, name):
- return _unpack_char_p(
- lib.TCOD_parser_get_string_property(parser, _bytes(name)))
-def parser_get_color_property(parser, name):
- return Color._new_from_cdata(
- lib.TCOD_parser_get_color_property(parser, _bytes(name)))
+@deprecate("Parser functions have been deprecated.")
+def parser_get_color_property(parser: Any, name: str) -> Color:
+ return Color._new_from_cdata(lib.TCOD_parser_get_color_property(parser, _bytes(name)))
-def parser_get_dice_property(parser, name):
- d = ffi.new('TCOD_dice_t *')
+
+@deprecate("Parser functions have been deprecated.")
+def parser_get_dice_property(parser: Any, name: str) -> Dice:
+ d = ffi.new("TCOD_dice_t *")
lib.TCOD_parser_get_dice_property_py(parser, _bytes(name), d)
return Dice(d)
-def parser_get_list_property(parser, name, type):
- clist = lib.TCOD_parser_get_list_property(parser, _bytes(name), type)
- return _convert_TCODList(clist, type)
+
+@deprecate("Parser functions have been deprecated.")
+def parser_get_list_property(parser: Any, name: str, type: Any) -> Any: # noqa: A002
+ c_list = lib.TCOD_parser_get_list_property(parser, _bytes(name), type)
+ return _convert_TCODList(c_list, type)
+
RNG_MT = 0
RNG_CMWC = 1
@@ -2571,16 +3726,19 @@ def parser_get_list_property(parser, name, type):
DISTRIBUTION_GAUSSIAN_INVERSE = 3
DISTRIBUTION_GAUSSIAN_RANGE_INVERSE = 4
-def random_get_instance():
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def random_get_instance() -> tcod.random.Random:
"""Return the default Random instance.
Returns:
Random: A Random instance using the default random number generator.
"""
- return tcod.random.Random._new_from_cdata(
- ffi.cast('mersenne_data_t*', lib.TCOD_random_get_instance()))
+ return tcod.random.Random._new_from_cdata(lib.TCOD_random_get_instance())
+
-def random_new(algo=RNG_CMWC):
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def random_new(algo: int = RNG_CMWC) -> tcod.random.Random:
"""Return a new Random instance. Using ``algo``.
Args:
@@ -2591,7 +3749,9 @@ def random_new(algo=RNG_CMWC):
"""
return tcod.random.Random(algo)
-def random_new_from_seed(seed, algo=RNG_CMWC):
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def random_new_from_seed(seed: Hashable, algo: int = RNG_CMWC) -> tcod.random.Random:
"""Return a new Random instance. Using the given ``seed`` and ``algo``.
Args:
@@ -2604,7 +3764,9 @@ def random_new_from_seed(seed, algo=RNG_CMWC):
"""
return tcod.random.Random(algo, seed)
-def random_set_distribution(rnd, dist):
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def random_set_distribution(rnd: tcod.random.Random | None, dist: int) -> None:
"""Change the distribution mode of a random number generator.
Args:
@@ -2613,189 +3775,233 @@ def random_set_distribution(rnd, dist):
"""
lib.TCOD_random_set_distribution(rnd.random_c if rnd else ffi.NULL, dist)
-def random_get_int(rnd, mi, ma):
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def random_get_int(rnd: tcod.random.Random | None, mi: int, ma: int) -> int:
"""Return a random integer in the range: ``mi`` <= n <= ``ma``.
- The result is affacted by calls to :any:`random_set_distribution`.
+ The result is affected by calls to :any:`random_set_distribution`.
Args:
rnd (Optional[Random]): A Random instance, or None to use the default.
- low (int): The lower bound of the random range, inclusive.
- high (int): The upper bound of the random range, inclusive.
+ mi (int): The lower bound of the random range, inclusive.
+ ma (int): The upper bound of the random range, inclusive.
Returns:
int: A random integer in the range ``mi`` <= n <= ``ma``.
"""
- return lib.TCOD_random_get_int(rnd.random_c if rnd else ffi.NULL, mi, ma)
+ return int(lib.TCOD_random_get_int(rnd.random_c if rnd else ffi.NULL, mi, ma))
+
-def random_get_float(rnd, mi, ma):
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def random_get_float(rnd: tcod.random.Random | None, mi: float, ma: float) -> float:
"""Return a random float in the range: ``mi`` <= n <= ``ma``.
- The result is affacted by calls to :any:`random_set_distribution`.
+ The result is affected by calls to :any:`random_set_distribution`.
Args:
rnd (Optional[Random]): A Random instance, or None to use the default.
- low (float): The lower bound of the random range, inclusive.
- high (float): The upper bound of the random range, inclusive.
+ mi (float): The lower bound of the random range, inclusive.
+ ma (float): The upper bound of the random range, inclusive.
Returns:
float: A random double precision float
in the range ``mi`` <= n <= ``ma``.
"""
- return lib.TCOD_random_get_double(
- rnd.random_c if rnd else ffi.NULL, mi, ma)
+ return float(lib.TCOD_random_get_double(rnd.random_c if rnd else ffi.NULL, mi, ma))
-def random_get_double(rnd, mi, ma):
+
+@deprecate("Call tcod.random_get_float instead.")
+def random_get_double(rnd: tcod.random.Random | None, mi: float, ma: float) -> float:
"""Return a random float in the range: ``mi`` <= n <= ``ma``.
.. deprecated:: 2.0
Use :any:`random_get_float` instead.
- Both funtions return a double precision float.
+ Both functions return a double precision float.
"""
- return lib.TCOD_random_get_double(
- rnd.random_c if rnd else ffi.NULL, mi, ma)
+ return float(lib.TCOD_random_get_double(rnd.random_c if rnd else ffi.NULL, mi, ma))
+
-def random_get_int_mean(rnd, mi, ma, mean):
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def random_get_int_mean(rnd: tcod.random.Random | None, mi: int, ma: int, mean: int) -> int:
"""Return a random weighted integer in the range: ``mi`` <= n <= ``ma``.
- The result is affacted by calls to :any:`random_set_distribution`.
+ The result is affected by calls to :any:`random_set_distribution`.
Args:
rnd (Optional[Random]): A Random instance, or None to use the default.
- low (int): The lower bound of the random range, inclusive.
- high (int): The upper bound of the random range, inclusive.
+ mi (int): The lower bound of the random range, inclusive.
+ ma (int): The upper bound of the random range, inclusive.
mean (int): The mean return value.
Returns:
int: A random weighted integer in the range ``mi`` <= n <= ``ma``.
"""
- return lib.TCOD_random_get_int_mean(
- rnd.random_c if rnd else ffi.NULL, mi, ma, mean)
+ return int(lib.TCOD_random_get_int_mean(rnd.random_c if rnd else ffi.NULL, mi, ma, mean))
-def random_get_float_mean(rnd, mi, ma, mean):
+
+@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning)
+def random_get_float_mean(rnd: tcod.random.Random | None, mi: float, ma: float, mean: float) -> float:
"""Return a random weighted float in the range: ``mi`` <= n <= ``ma``.
- The result is affacted by calls to :any:`random_set_distribution`.
+ The result is affected by calls to :any:`random_set_distribution`.
Args:
rnd (Optional[Random]): A Random instance, or None to use the default.
- low (float): The lower bound of the random range, inclusive.
- high (float): The upper bound of the random range, inclusive.
+ mi (float): The lower bound of the random range, inclusive.
+ ma (float): The upper bound of the random range, inclusive.
mean (float): The mean return value.
Returns:
float: A random weighted double precision float
in the range ``mi`` <= n <= ``ma``.
"""
- return lib.TCOD_random_get_double_mean(
- rnd.random_c if rnd else ffi.NULL, mi, ma, mean)
+ return float(lib.TCOD_random_get_double_mean(rnd.random_c if rnd else ffi.NULL, mi, ma, mean))
+
-def random_get_double_mean(rnd, mi, ma, mean):
+@deprecate("Call tcod.random_get_float_mean instead.")
+def random_get_double_mean(rnd: tcod.random.Random | None, mi: float, ma: float, mean: float) -> float:
"""Return a random weighted float in the range: ``mi`` <= n <= ``ma``.
.. deprecated:: 2.0
Use :any:`random_get_float_mean` instead.
- Both funtions return a double precision float.
+ Both functions return a double precision float.
"""
- return lib.TCOD_random_get_double_mean(
- rnd.random_c if rnd else ffi.NULL, mi, ma, mean)
+ return float(lib.TCOD_random_get_double_mean(rnd.random_c if rnd else ffi.NULL, mi, ma, mean))
-def random_save(rnd):
- """Return a copy of a random number generator.
- Args:
- rnd (Optional[Random]): A Random instance, or None to use the default.
+@deprecate("Use the standard library 'copy' module instead.")
+def random_save(rnd: tcod.random.Random | None) -> tcod.random.Random:
+ """Return a copy of a random number generator.
- Returns:
- Random: A Random instance with a copy of the random generator.
+ .. deprecated:: 8.4
+ You can use the standard library copy and pickle modules to save a
+ random state.
"""
return tcod.random.Random._new_from_cdata(
ffi.gc(
- ffi.cast('mersenne_data_t*',
- lib.TCOD_random_save(rnd.random_c if rnd else ffi.NULL)),
- lib.TCOD_random_delete),
+ lib.TCOD_random_save(rnd.random_c if rnd else ffi.NULL),
+ lib.TCOD_random_delete,
)
+ )
-def random_restore(rnd, backup):
+
+@deprecate("This function is deprecated.")
+def random_restore(rnd: tcod.random.Random | None, backup: tcod.random.Random) -> None:
"""Restore a random number generator from a backed up copy.
Args:
rnd (Optional[Random]): A Random instance, or None to use the default.
backup (Random): The Random instance which was used as a backup.
+
+ .. deprecated:: 8.4
+ You can use the standard library copy and pickle modules to save a
+ random state.
"""
- lib.TCOD_random_restore(rnd.random_c if rnd else ffi.NULL,
- backup.random_c)
+ lib.TCOD_random_restore(rnd.random_c if rnd else ffi.NULL, backup.random_c)
-def random_delete(rnd):
- # type (Any) -> None
+
+@deprecate("libtcod objects are deleted automatically.")
+def random_delete(rnd: tcod.random.Random) -> None:
"""Does nothing. libtcod objects are managed by Python's garbage collector.
This function exists for backwards compatibility with libtcodpy.
"""
-def struct_add_flag(struct, name):
- lib.TCOD_struct_add_flag(struct, name)
-def struct_add_property(struct, name, typ, mandatory):
- lib.TCOD_struct_add_property(struct, name, typ, mandatory)
+@deprecate("This function is deprecated.")
+def struct_add_flag(struct: Any, name: str) -> None:
+ lib.TCOD_struct_add_flag(struct, _bytes(name))
+
+
+@deprecate("This function is deprecated.")
+def struct_add_property(struct: Any, name: str, typ: int, mandatory: bool) -> None: # noqa: FBT001
+ lib.TCOD_struct_add_property(struct, _bytes(name), typ, mandatory)
+
-def struct_add_value_list(struct, name, value_list, mandatory):
- CARRAY = c_char_p * (len(value_list) + 1)
- cvalue_list = CARRAY()
- for i, value in enumerate(value_list):
- cvalue_list[i] = cast(value, c_char_p)
- cvalue_list[len(value_list)] = 0
- lib.TCOD_struct_add_value_list(struct, name, cvalue_list, mandatory)
+@deprecate("This function is deprecated.")
+def struct_add_value_list(struct: Any, name: str, value_list: Iterable[str], mandatory: bool) -> None: # noqa: FBT001
+ c_strings = [ffi.new("char[]", value.encode("utf-8")) for value in value_list]
+ c_value_list = ffi.new("char*[]", c_strings)
+ lib.TCOD_struct_add_value_list(struct, name, c_value_list, mandatory)
-def struct_add_list_property(struct, name, typ, mandatory):
- lib.TCOD_struct_add_list_property(struct, name, typ, mandatory)
-def struct_add_structure(struct, sub_struct):
+@deprecate("This function is deprecated.")
+def struct_add_list_property(struct: Any, name: str, typ: int, mandatory: bool) -> None: # noqa: FBT001
+ lib.TCOD_struct_add_list_property(struct, _bytes(name), typ, mandatory)
+
+
+@deprecate("This function is deprecated.")
+def struct_add_structure(struct: Any, sub_struct: Any) -> None:
lib.TCOD_struct_add_structure(struct, sub_struct)
-def struct_get_name(struct):
+
+@deprecate("This function is deprecated.")
+def struct_get_name(struct: Any) -> str:
return _unpack_char_p(lib.TCOD_struct_get_name(struct))
-def struct_is_mandatory(struct, name):
- return lib.TCOD_struct_is_mandatory(struct, name)
-def struct_get_type(struct, name):
- return lib.TCOD_struct_get_type(struct, name)
+@deprecate("This function is deprecated.")
+def struct_is_mandatory(struct: Any, name: str) -> bool:
+ return bool(lib.TCOD_struct_is_mandatory(struct, _bytes(name)))
+
+
+@deprecate("This function is deprecated.")
+def struct_get_type(struct: Any, name: str) -> int:
+ return int(lib.TCOD_struct_get_type(struct, _bytes(name)))
+
# high precision time functions
-def sys_set_fps(fps):
+
+
+@deprecate("This function is not supported if contexts are being used.")
+def sys_set_fps(fps: int) -> None:
"""Set the maximum frame rate.
You can disable the frame limit again by setting fps to 0.
Args:
fps (int): A frame rate limit (i.e. 60)
+
+ .. deprecated:: 11.13
+ This function is not supported by contexts.
"""
lib.TCOD_sys_set_fps(fps)
-def sys_get_fps():
+
+@deprecate("This function is not supported if contexts are being used.")
+def sys_get_fps() -> int:
"""Return the current frames per second.
This the actual frame rate, not the frame limit set by
- :any:`tcod.sys_set_fps`.
+ :any:`libtcodpy.sys_set_fps`.
This number is updated every second.
Returns:
int: The currently measured frame rate.
+
+ .. deprecated:: 11.13
+ This function is not supported by contexts.
"""
- return lib.TCOD_sys_get_fps()
+ return int(lib.TCOD_sys_get_fps())
+
-def sys_get_last_frame_length():
+@deprecate("This function is not supported if contexts are being used.")
+def sys_get_last_frame_length() -> float:
"""Return the delta time of the last rendered frame in seconds.
Returns:
float: The delta time of the last rendered frame.
+
+ .. deprecated:: 11.13
+ This function is not supported by contexts.
"""
- return lib.TCOD_sys_get_last_frame_length()
+ return float(lib.TCOD_sys_get_last_frame_length())
+
@deprecate("Use Python's standard 'time' module instead of this function.")
-def sys_sleep_milli(val):
+def sys_sleep_milli(val: int) -> None:
"""Sleep for 'val' milliseconds.
Args:
@@ -2806,48 +4012,59 @@ def sys_sleep_milli(val):
"""
lib.TCOD_sys_sleep_milli(val)
+
@deprecate("Use Python's standard 'time' module instead of this function.")
-def sys_elapsed_milli():
+def sys_elapsed_milli() -> int:
"""Get number of milliseconds since the start of the program.
Returns:
- int: Time since the progeam has started in milliseconds.
+ int: Time since the program has started in milliseconds.
.. deprecated:: 2.0
- Use :any:`time.clock` instead.
+ Use Python's :mod:`time` module instead.
"""
- return lib.TCOD_sys_elapsed_milli()
+ return int(lib.TCOD_sys_elapsed_milli())
+
@deprecate("Use Python's standard 'time' module instead of this function.")
-def sys_elapsed_seconds():
+def sys_elapsed_seconds() -> float:
"""Get number of seconds since the start of the program.
Returns:
- float: Time since the progeam has started in seconds.
+ float: Time since the program has started in seconds.
.. deprecated:: 2.0
- Use :any:`time.clock` instead.
+ Use Python's :mod:`time` module instead.
"""
- return lib.TCOD_sys_elapsed_seconds()
+ return float(lib.TCOD_sys_elapsed_seconds())
-def sys_set_renderer(renderer):
+
+@deprecate("This function is not supported if contexts are being used.")
+def sys_set_renderer(renderer: int) -> None:
"""Change the current rendering mode to renderer.
- .. deprecated:: 2.0
- RENDERER_GLSL and RENDERER_OPENGL are not currently available.
+ .. deprecated:: 11.13
+ This function is not supported by contexts.
"""
- lib.TCOD_sys_set_renderer(renderer)
+ _check(lib.TCOD_sys_set_renderer(renderer))
if tcod.console._root_console is not None:
tcod.console.Console._get_root()
-def sys_get_renderer():
+
+@deprecate("This function is not supported if contexts are being used.")
+def sys_get_renderer() -> int:
"""Return the current rendering mode.
+ .. deprecated:: 11.13
+ This function is not supported by contexts.
+ Check :any:`Context.renderer_type` instead.
"""
- return lib.TCOD_sys_get_renderer()
+ return int(lib.TCOD_sys_get_renderer())
+
# easy screenshots
-def sys_save_screenshot(name=None):
+@deprecate("This function is not supported if contexts are being used.")
+def sys_save_screenshot(name: str | PathLike[str] | None = None) -> None:
"""Save a screenshot to a file.
By default this will automatically save screenshots in the working
@@ -2857,14 +4074,21 @@ def sys_save_screenshot(name=None):
screenshot000.png, screenshot001.png, etc. Whichever is available first.
Args:
- file Optional[AnyStr]: File path to save screenshot.
+ name: File path to save screenshot.
+
+ .. deprecated:: 11.13
+ This function is not supported by contexts.
+ Use :any:`Context.save_screenshot` instead.
+
+ .. versionchanged:: 16.0
+ Added PathLike support.
"""
- if name is not None:
- name = _bytes(name)
- lib.TCOD_sys_save_screenshot(name or ffi.NULL)
+ lib.TCOD_sys_save_screenshot(_path_encode(Path(name)) if name is not None else ffi.NULL)
+
# custom fullscreen resolution
-def sys_force_fullscreen_resolution(width, height):
+@deprecate("This function is not supported if contexts are being used.")
+def sys_force_fullscreen_resolution(width: int, height: int) -> None:
"""Force a specific resolution in fullscreen.
Will use the smallest available resolution so that:
@@ -2877,37 +4101,56 @@ def sys_force_fullscreen_resolution(width, height):
Args:
width (int): The desired resolution width.
height (int): The desired resolution height.
+
+ .. deprecated:: 11.13
+ This function is not supported by contexts.
"""
lib.TCOD_sys_force_fullscreen_resolution(width, height)
-def sys_get_current_resolution():
- """Return the current resolution as (width, height)
- Returns:
- Tuple[int,int]: The current resolution.
+@deprecate("This function is deprecated, which monitor is detected is ambiguous.")
+def sys_get_current_resolution() -> tuple[int, int]:
+ """Return a monitors pixel resolution as (width, height).
+
+ .. deprecated:: 11.13
+ This function is deprecated, which monitor is detected is ambiguous.
"""
- w = ffi.new('int *')
- h = ffi.new('int *')
+ w = ffi.new("int *")
+ h = ffi.new("int *")
lib.TCOD_sys_get_current_resolution(w, h)
return w[0], h[0]
-def sys_get_char_size():
- """Return the current fonts character size as (width, height)
+
+@deprecate("This function is not supported if contexts are being used.")
+def sys_get_char_size() -> tuple[int, int]:
+ """Return the current fonts character size as (width, height).
Returns:
Tuple[int,int]: The current font glyph size in (width, height)
+
+ .. deprecated:: 11.13
+ This function is not supported by contexts.
"""
- w = ffi.new('int *')
- h = ffi.new('int *')
+ w = ffi.new("int *")
+ h = ffi.new("int *")
lib.TCOD_sys_get_char_size(w, h)
return w[0], h[0]
+
# update font bitmap
-def sys_update_char(asciiCode, fontx, fonty, img, x, y):
- """Dynamically update the current frot with img.
+@deprecate("This function is not supported if contexts are being used.")
+def sys_update_char(
+ asciiCode: int, # noqa: N803
+ fontx: int,
+ fonty: int,
+ img: tcod.image.Image,
+ x: int,
+ y: int,
+) -> None:
+ """Dynamically update the current font with img.
All cells using this asciiCode will be updated
- at the next call to :any:`tcod.console_flush`.
+ at the next call to :any:`libtcodpy.console_flush`.
Args:
asciiCode (int): Ascii code corresponding to the character to update.
@@ -2918,31 +4161,43 @@ def sys_update_char(asciiCode, fontx, fonty, img, x, y):
img (Image): An image containing the new character bitmap.
x (int): Left pixel of the character in the image.
y (int): Top pixel of the character in the image.
+
+ .. deprecated:: 11.13
+ This function is not supported by contexts.
+ Use :any:`Tileset.set_tile` instead to update tiles.
"""
lib.TCOD_sys_update_char(_int(asciiCode), fontx, fonty, img, x, y)
-def sys_register_SDL_renderer(callback):
- """Register a custom randering function with libtcod.
+
+@deprecate("This function is not supported if contexts are being used.")
+def sys_register_SDL_renderer(callback: Callable[[Any], None]) -> None: # noqa: N802
+ """Register a custom rendering function with libtcod.
Note:
This callback will only be called by the SDL renderer.
- The callack will receive a :any:`CData ` void* to an
- SDL_Surface* struct.
+ The callback will receive a CData `void*` pointer to an
+ `SDL_Surface*` struct.
- The callback is called on every call to :any:`tcod.console_flush`.
+ The callback is called on every call to :any:`libtcodpy.console_flush`.
Args:
- callback Callable[[CData], None]:
- A function which takes a single argument.
+ callback: A function which takes a single argument.
+
+ .. deprecated:: 11.13
+ This function is not supported by contexts.
"""
with _PropagateException() as propagate:
- @ffi.def_extern(onerror=propagate)
- def _pycall_sdl_hook(sdl_surface):
+
+ @ffi.def_extern(onerror=propagate) # type: ignore[untyped-decorator]
+ def _pycall_sdl_hook(sdl_surface: Any) -> None:
callback(sdl_surface)
+
lib.TCOD_sys_register_SDL_renderer(lib._pycall_sdl_hook)
-def sys_check_for_event(mask, k, m):
+
+@deprecate("Use tcod.event.get to check for events.")
+def sys_check_for_event(mask: int, k: Key | None, m: Mouse | None) -> int:
"""Check for and return an event.
Args:
@@ -2951,15 +4206,19 @@ def sys_check_for_event(mask, k, m):
an event. Can be None.
m (Optional[Mouse]): A tcod.Mouse instance which might be updated
with an event. Can be None.
+
+ .. deprecated:: 9.3
+ Use the :any:`tcod.event.get` function to check for events.
"""
- return lib.TCOD_sys_check_for_event(
- mask, k.cdata if k else ffi.NULL, m.cdata if m else ffi.NULL)
+ return int(lib.TCOD_sys_check_for_event(mask, k.key_p if k else ffi.NULL, m.mouse_p if m else ffi.NULL))
-def sys_wait_for_event(mask, k, m, flush):
+
+@deprecate("Use tcod.event.wait to wait for events.")
+def sys_wait_for_event(mask: int, k: Key | None, m: Mouse | None, flush: bool) -> int: # noqa: FBT001
"""Wait for an event then return.
If flush is True then the buffer will be cleared before waiting. Otherwise
- each available event will be returned in the order they're recieved.
+ each available event will be returned in the order they're received.
Args:
mask (int): :any:`Event types` to wait for.
@@ -2968,24 +4227,623 @@ def sys_wait_for_event(mask, k, m, flush):
m (Optional[Mouse]): A tcod.Mouse instance which might be updated
with an event. Can be None.
flush (bool): Clear the event buffer before waiting.
+
+ .. deprecated:: 9.3
+ Use the :any:`tcod.event.wait` function to wait for events.
"""
- return lib.TCOD_sys_wait_for_event(
- mask, k.cdata if k else ffi.NULL, m.cdata if m else ffi.NULL, flush)
+ return int(
+ lib.TCOD_sys_wait_for_event(
+ mask,
+ k.key_p if k else ffi.NULL,
+ m.mouse_p if m else ffi.NULL,
+ flush,
+ )
+ )
+
@deprecate("This function does not provide reliable access to the clipboard.")
-def sys_clipboard_set(text):
+def sys_clipboard_set(text: str) -> bool:
"""Sets the clipboard to `text`.
.. deprecated:: 6.0
This function does not provide reliable access to the clipboard.
"""
- return lib.TCOD_sys_clipboard_set(text.encode('utf-8'))
+ return bool(lib.TCOD_sys_clipboard_set(text.encode("utf-8")))
+
@deprecate("This function does not provide reliable access to the clipboard.")
-def sys_clipboard_get():
+def sys_clipboard_get() -> str:
"""Return the current value of the clipboard.
.. deprecated:: 6.0
This function does not provide reliable access to the clipboard.
"""
- return ffi.string(lib.TCOD_sys_clipboard_get()).decode('utf-8')
+ return str(ffi.string(lib.TCOD_sys_clipboard_get()).decode("utf-8"))
+
+
+@atexit.register
+def _atexit_verify() -> None:
+ """Warns if the libtcod root console is implicitly deleted."""
+ if lib and lib.TCOD_ctx.root:
+ warnings.warn(
+ "The libtcod root console was implicitly deleted.\n"
+ "Make sure the 'with' statement is used with the root console to"
+ " ensure that it closes properly.\n"
+ "Alternatively, call the root console's close method as the"
+ " program exits.",
+ ResourceWarning,
+ stacklevel=2,
+ )
+ lib.TCOD_console_delete(ffi.NULL)
+
+
+def __getattr__(name: str) -> Color:
+ """Mark access to color constants as deprecated."""
+ value: object = getattr(tcod.constants, name, None)
+ if isinstance(value, Color):
+ warnings.warn(
+ f"Color constants will be removed from future releases.\nReplace 'tcod.{name}' with '{tuple(value)}'.",
+ FutureWarning,
+ stacklevel=2,
+ )
+ return value
+ msg = f"module {__name__!r} has no attribute {name!r}"
+ raise AttributeError(msg) from None
+
+
+__all__ = [ # noqa: F405 RUF022
+ "Color",
+ "Bsp",
+ "NB_FOV_ALGORITHMS",
+ "NOISE_DEFAULT_HURST",
+ "NOISE_DEFAULT_LACUNARITY",
+ "ConsoleBuffer",
+ "Dice",
+ "Key",
+ "Mouse",
+ "FOV_PERMISSIVE",
+ "BKGND_ALPHA",
+ "BKGND_ADDALPHA",
+ "bsp_new_with_size",
+ "bsp_split_once",
+ "bsp_split_recursive",
+ "bsp_resize",
+ "bsp_left",
+ "bsp_right",
+ "bsp_father",
+ "bsp_is_leaf",
+ "bsp_contains",
+ "bsp_find_node",
+ "bsp_traverse_pre_order",
+ "bsp_traverse_in_order",
+ "bsp_traverse_post_order",
+ "bsp_traverse_level_order",
+ "bsp_traverse_inverted_level_order",
+ "bsp_remove_sons",
+ "bsp_delete",
+ "color_lerp",
+ "color_set_hsv",
+ "color_get_hsv",
+ "color_scale_HSV",
+ "color_gen_map",
+ "console_init_root",
+ "console_set_custom_font",
+ "console_get_width",
+ "console_get_height",
+ "console_map_ascii_code_to_font",
+ "console_map_ascii_codes_to_font",
+ "console_map_string_to_font",
+ "console_is_fullscreen",
+ "console_set_fullscreen",
+ "console_is_window_closed",
+ "console_has_mouse_focus",
+ "console_is_active",
+ "console_set_window_title",
+ "console_credits",
+ "console_credits_reset",
+ "console_credits_render",
+ "console_flush",
+ "console_set_default_background",
+ "console_set_default_foreground",
+ "console_clear",
+ "console_put_char",
+ "console_put_char_ex",
+ "console_set_char_background",
+ "console_set_char_foreground",
+ "console_set_char",
+ "console_set_background_flag",
+ "console_get_background_flag",
+ "console_set_alignment",
+ "console_get_alignment",
+ "console_print",
+ "console_print_ex",
+ "console_print_rect",
+ "console_print_rect_ex",
+ "console_get_height_rect",
+ "console_rect",
+ "console_hline",
+ "console_vline",
+ "console_print_frame",
+ "console_set_color_control",
+ "console_get_default_background",
+ "console_get_default_foreground",
+ "console_get_char_background",
+ "console_get_char_foreground",
+ "console_get_char",
+ "console_set_fade",
+ "console_get_fade",
+ "console_get_fading_color",
+ "console_wait_for_keypress",
+ "console_check_for_keypress",
+ "console_is_key_pressed",
+ "console_new",
+ "console_from_file",
+ "console_blit",
+ "console_set_key_color",
+ "console_delete",
+ "console_fill_foreground",
+ "console_fill_background",
+ "console_fill_char",
+ "console_load_asc",
+ "console_save_asc",
+ "console_load_apf",
+ "console_save_apf",
+ "console_load_xp",
+ "console_save_xp",
+ "console_from_xp",
+ "console_list_load_xp",
+ "console_list_save_xp",
+ "path_new_using_map",
+ "path_new_using_function",
+ "path_compute",
+ "path_get_origin",
+ "path_get_destination",
+ "path_size",
+ "path_reverse",
+ "path_get",
+ "path_is_empty",
+ "path_walk",
+ "path_delete",
+ "dijkstra_new",
+ "dijkstra_new_using_function",
+ "dijkstra_compute",
+ "dijkstra_path_set",
+ "dijkstra_get_distance",
+ "dijkstra_size",
+ "dijkstra_reverse",
+ "dijkstra_get",
+ "dijkstra_is_empty",
+ "dijkstra_path_walk",
+ "dijkstra_delete",
+ "heightmap_new",
+ "heightmap_set_value",
+ "heightmap_add",
+ "heightmap_scale",
+ "heightmap_clear",
+ "heightmap_clamp",
+ "heightmap_copy",
+ "heightmap_normalize",
+ "heightmap_lerp_hm",
+ "heightmap_add_hm",
+ "heightmap_multiply_hm",
+ "heightmap_add_hill",
+ "heightmap_dig_hill",
+ "heightmap_rain_erosion",
+ "heightmap_kernel_transform",
+ "heightmap_add_voronoi",
+ "heightmap_add_fbm",
+ "heightmap_scale_fbm",
+ "heightmap_dig_bezier",
+ "heightmap_get_value",
+ "heightmap_get_interpolated_value",
+ "heightmap_get_slope",
+ "heightmap_get_normal",
+ "heightmap_count_cells",
+ "heightmap_has_land_on_border",
+ "heightmap_get_minmax",
+ "heightmap_delete",
+ "image_new",
+ "image_clear",
+ "image_invert",
+ "image_hflip",
+ "image_rotate90",
+ "image_vflip",
+ "image_scale",
+ "image_set_key_color",
+ "image_get_alpha",
+ "image_is_pixel_transparent",
+ "image_load",
+ "image_from_console",
+ "image_refresh_console",
+ "image_get_size",
+ "image_get_pixel",
+ "image_get_mipmap_pixel",
+ "image_put_pixel",
+ "image_blit",
+ "image_blit_rect",
+ "image_blit_2x",
+ "image_save",
+ "image_delete",
+ "line_init",
+ "line_step",
+ "line",
+ "line_iter",
+ "line_where",
+ "map_new",
+ "map_copy",
+ "map_set_properties",
+ "map_clear",
+ "map_compute_fov",
+ "map_is_in_fov",
+ "map_is_transparent",
+ "map_is_walkable",
+ "map_delete",
+ "map_get_width",
+ "map_get_height",
+ "mouse_show_cursor",
+ "mouse_is_cursor_visible",
+ "mouse_move",
+ "mouse_get_status",
+ "namegen_parse",
+ "namegen_generate",
+ "namegen_generate_custom",
+ "namegen_get_sets",
+ "namegen_destroy",
+ "noise_new",
+ "noise_set_type",
+ "noise_get",
+ "noise_get_fbm",
+ "noise_get_turbulence",
+ "noise_delete",
+ "parser_new",
+ "parser_new_struct",
+ "parser_run",
+ "parser_delete",
+ "parser_get_bool_property",
+ "parser_get_int_property",
+ "parser_get_char_property",
+ "parser_get_float_property",
+ "parser_get_string_property",
+ "parser_get_color_property",
+ "parser_get_dice_property",
+ "parser_get_list_property",
+ "random_get_instance",
+ "random_new",
+ "random_new_from_seed",
+ "random_set_distribution",
+ "random_get_int",
+ "random_get_float",
+ "random_get_double",
+ "random_get_int_mean",
+ "random_get_float_mean",
+ "random_get_double_mean",
+ "random_save",
+ "random_restore",
+ "random_delete",
+ "struct_add_flag",
+ "struct_add_property",
+ "struct_add_value_list",
+ "struct_add_list_property",
+ "struct_add_structure",
+ "struct_get_name",
+ "struct_is_mandatory",
+ "struct_get_type",
+ "sys_set_fps",
+ "sys_get_fps",
+ "sys_get_last_frame_length",
+ "sys_sleep_milli",
+ "sys_elapsed_milli",
+ "sys_elapsed_seconds",
+ "sys_set_renderer",
+ "sys_get_renderer",
+ "sys_save_screenshot",
+ "sys_force_fullscreen_resolution",
+ "sys_get_current_resolution",
+ "sys_get_char_size",
+ "sys_update_char",
+ "sys_register_SDL_renderer",
+ "sys_check_for_event",
+ "sys_wait_for_event",
+ "sys_clipboard_set",
+ "sys_clipboard_get",
+ # --- From constants.py ---
+ "FOV_BASIC",
+ "FOV_DIAMOND",
+ "FOV_PERMISSIVE_0",
+ "FOV_PERMISSIVE_1",
+ "FOV_PERMISSIVE_2",
+ "FOV_PERMISSIVE_3",
+ "FOV_PERMISSIVE_4",
+ "FOV_PERMISSIVE_5",
+ "FOV_PERMISSIVE_6",
+ "FOV_PERMISSIVE_7",
+ "FOV_PERMISSIVE_8",
+ "FOV_RESTRICTIVE",
+ "FOV_SHADOW",
+ "FOV_SYMMETRIC_SHADOWCAST",
+ "KEY_0",
+ "KEY_1",
+ "KEY_2",
+ "KEY_3",
+ "KEY_4",
+ "KEY_5",
+ "KEY_6",
+ "KEY_7",
+ "KEY_8",
+ "KEY_9",
+ "KEY_ALT",
+ "KEY_APPS",
+ "KEY_BACKSPACE",
+ "KEY_CAPSLOCK",
+ "KEY_CHAR",
+ "KEY_CONTROL",
+ "KEY_DELETE",
+ "KEY_DOWN",
+ "KEY_END",
+ "KEY_ENTER",
+ "KEY_ESCAPE",
+ "KEY_F1",
+ "KEY_F10",
+ "KEY_F11",
+ "KEY_F12",
+ "KEY_F2",
+ "KEY_F3",
+ "KEY_F4",
+ "KEY_F5",
+ "KEY_F6",
+ "KEY_F7",
+ "KEY_F8",
+ "KEY_F9",
+ "KEY_HOME",
+ "KEY_INSERT",
+ "KEY_KP0",
+ "KEY_KP1",
+ "KEY_KP2",
+ "KEY_KP3",
+ "KEY_KP4",
+ "KEY_KP5",
+ "KEY_KP6",
+ "KEY_KP7",
+ "KEY_KP8",
+ "KEY_KP9",
+ "KEY_KPADD",
+ "KEY_KPDEC",
+ "KEY_KPDIV",
+ "KEY_KPENTER",
+ "KEY_KPMUL",
+ "KEY_KPSUB",
+ "KEY_LEFT",
+ "KEY_LWIN",
+ "KEY_NONE",
+ "KEY_NUMLOCK",
+ "KEY_PAGEDOWN",
+ "KEY_PAGEUP",
+ "KEY_PAUSE",
+ "KEY_PRINTSCREEN",
+ "KEY_RIGHT",
+ "KEY_RWIN",
+ "KEY_SCROLLLOCK",
+ "KEY_SHIFT",
+ "KEY_SPACE",
+ "KEY_TAB",
+ "KEY_TEXT",
+ "KEY_UP",
+ "BKGND_ADD",
+ "BKGND_ADDA",
+ "BKGND_ALPH",
+ "BKGND_BURN",
+ "BKGND_COLOR_BURN",
+ "BKGND_COLOR_DODGE",
+ "BKGND_DARKEN",
+ "BKGND_DEFAULT",
+ "BKGND_LIGHTEN",
+ "BKGND_MULTIPLY",
+ "BKGND_NONE",
+ "BKGND_OVERLAY",
+ "BKGND_SCREEN",
+ "BKGND_SET",
+ "CENTER",
+ "CHAR_ARROW2_E",
+ "CHAR_ARROW2_N",
+ "CHAR_ARROW2_S",
+ "CHAR_ARROW2_W",
+ "CHAR_ARROW_E",
+ "CHAR_ARROW_N",
+ "CHAR_ARROW_S",
+ "CHAR_ARROW_W",
+ "CHAR_BLOCK1",
+ "CHAR_BLOCK2",
+ "CHAR_BLOCK3",
+ "CHAR_BULLET",
+ "CHAR_BULLET_INV",
+ "CHAR_BULLET_SQUARE",
+ "CHAR_CENT",
+ "CHAR_CHECKBOX_SET",
+ "CHAR_CHECKBOX_UNSET",
+ "CHAR_CLUB",
+ "CHAR_COPYRIGHT",
+ "CHAR_CROSS",
+ "CHAR_CURRENCY",
+ "CHAR_DARROW_H",
+ "CHAR_DARROW_V",
+ "CHAR_DCROSS",
+ "CHAR_DHLINE",
+ "CHAR_DIAMOND",
+ "CHAR_DIVISION",
+ "CHAR_DNE",
+ "CHAR_DNW",
+ "CHAR_DSE",
+ "CHAR_DSW",
+ "CHAR_DTEEE",
+ "CHAR_DTEEN",
+ "CHAR_DTEES",
+ "CHAR_DTEEW",
+ "CHAR_DVLINE",
+ "CHAR_EXCLAM_DOUBLE",
+ "CHAR_FEMALE",
+ "CHAR_FUNCTION",
+ "CHAR_GRADE",
+ "CHAR_HALF",
+ "CHAR_HEART",
+ "CHAR_HLINE",
+ "CHAR_LIGHT",
+ "CHAR_MALE",
+ "CHAR_MULTIPLICATION",
+ "CHAR_NE",
+ "CHAR_NOTE",
+ "CHAR_NOTE_DOUBLE",
+ "CHAR_NW",
+ "CHAR_ONE_QUARTER",
+ "CHAR_PILCROW",
+ "CHAR_POUND",
+ "CHAR_POW1",
+ "CHAR_POW2",
+ "CHAR_POW3",
+ "CHAR_RADIO_SET",
+ "CHAR_RADIO_UNSET",
+ "CHAR_RESERVED",
+ "CHAR_SE",
+ "CHAR_SECTION",
+ "CHAR_SMILIE",
+ "CHAR_SMILIE_INV",
+ "CHAR_SPADE",
+ "CHAR_SUBP_DIAG",
+ "CHAR_SUBP_E",
+ "CHAR_SUBP_N",
+ "CHAR_SUBP_NE",
+ "CHAR_SUBP_NW",
+ "CHAR_SUBP_SE",
+ "CHAR_SUBP_SW",
+ "CHAR_SW",
+ "CHAR_TEEE",
+ "CHAR_TEEN",
+ "CHAR_TEES",
+ "CHAR_TEEW",
+ "CHAR_THREE_QUARTERS",
+ "CHAR_UMLAUT",
+ "CHAR_VLINE",
+ "CHAR_YEN",
+ "COLCTRL_1",
+ "COLCTRL_2",
+ "COLCTRL_3",
+ "COLCTRL_4",
+ "COLCTRL_5",
+ "COLCTRL_BACK_RGB",
+ "COLCTRL_FORE_RGB",
+ "COLCTRL_NUMBER",
+ "COLCTRL_STOP",
+ "COLOR_AMBER",
+ "COLOR_AZURE",
+ "COLOR_BLUE",
+ "COLOR_CHARTREUSE",
+ "COLOR_CRIMSON",
+ "COLOR_CYAN",
+ "COLOR_DARK",
+ "COLOR_DARKER",
+ "COLOR_DARKEST",
+ "COLOR_DESATURATED",
+ "COLOR_FLAME",
+ "COLOR_FUCHSIA",
+ "COLOR_GREEN",
+ "COLOR_HAN",
+ "COLOR_LEVELS",
+ "COLOR_LIGHT",
+ "COLOR_LIGHTER",
+ "COLOR_LIGHTEST",
+ "COLOR_LIME",
+ "COLOR_MAGENTA",
+ "COLOR_NB",
+ "COLOR_NORMAL",
+ "COLOR_ORANGE",
+ "COLOR_PINK",
+ "COLOR_PURPLE",
+ "COLOR_RED",
+ "COLOR_SEA",
+ "COLOR_SKY",
+ "COLOR_TURQUOISE",
+ "COLOR_VIOLET",
+ "COLOR_YELLOW",
+ "DISTRIBUTION_GAUSSIAN",
+ "DISTRIBUTION_GAUSSIAN_INVERSE",
+ "DISTRIBUTION_GAUSSIAN_RANGE",
+ "DISTRIBUTION_GAUSSIAN_RANGE_INVERSE",
+ "DISTRIBUTION_LINEAR",
+ "EVENT_ANY",
+ "EVENT_FINGER",
+ "EVENT_FINGER_MOVE",
+ "EVENT_FINGER_PRESS",
+ "EVENT_FINGER_RELEASE",
+ "EVENT_KEY",
+ "EVENT_KEY_PRESS",
+ "EVENT_KEY_RELEASE",
+ "EVENT_MOUSE",
+ "EVENT_MOUSE_MOVE",
+ "EVENT_MOUSE_PRESS",
+ "EVENT_MOUSE_RELEASE",
+ "EVENT_NONE",
+ "FONT_LAYOUT_ASCII_INCOL",
+ "FONT_LAYOUT_ASCII_INROW",
+ "FONT_LAYOUT_CP437",
+ "FONT_LAYOUT_TCOD",
+ "FONT_TYPE_GRAYSCALE",
+ "FONT_TYPE_GREYSCALE",
+ "KEY_PRESSED",
+ "KEY_RELEASED",
+ "LEFT",
+ "NB_RENDERERS",
+ "NOISE_DEFAULT",
+ "NOISE_PERLIN",
+ "NOISE_SIMPLEX",
+ "NOISE_WAVELET",
+ "RENDERER_GLSL",
+ "RENDERER_OPENGL",
+ "RENDERER_OPENGL2",
+ "RENDERER_SDL",
+ "RENDERER_SDL2",
+ "RENDERER_XTERM",
+ "RIGHT",
+ "RNG_CMWC",
+ "RNG_MT",
+ "TYPE_BOOL",
+ "TYPE_CHAR",
+ "TYPE_COLOR",
+ "TYPE_CUSTOM00",
+ "TYPE_CUSTOM01",
+ "TYPE_CUSTOM02",
+ "TYPE_CUSTOM03",
+ "TYPE_CUSTOM04",
+ "TYPE_CUSTOM05",
+ "TYPE_CUSTOM06",
+ "TYPE_CUSTOM07",
+ "TYPE_CUSTOM08",
+ "TYPE_CUSTOM09",
+ "TYPE_CUSTOM10",
+ "TYPE_CUSTOM11",
+ "TYPE_CUSTOM12",
+ "TYPE_CUSTOM13",
+ "TYPE_CUSTOM14",
+ "TYPE_CUSTOM15",
+ "TYPE_DICE",
+ "TYPE_FLOAT",
+ "TYPE_INT",
+ "TYPE_LIST",
+ "TYPE_NONE",
+ "TYPE_STRING",
+ "TYPE_VALUELIST00",
+ "TYPE_VALUELIST01",
+ "TYPE_VALUELIST02",
+ "TYPE_VALUELIST03",
+ "TYPE_VALUELIST04",
+ "TYPE_VALUELIST05",
+ "TYPE_VALUELIST06",
+ "TYPE_VALUELIST07",
+ "TYPE_VALUELIST08",
+ "TYPE_VALUELIST09",
+ "TYPE_VALUELIST10",
+ "TYPE_VALUELIST11",
+ "TYPE_VALUELIST12",
+ "TYPE_VALUELIST13",
+ "TYPE_VALUELIST14",
+ "TYPE_VALUELIST15",
+ # --- End constants.py ---
+]
diff --git a/tcod/los.py b/tcod/los.py
new file mode 100644
index 00000000..f09b073c
--- /dev/null
+++ b/tcod/los.py
@@ -0,0 +1,66 @@
+"""This modules holds functions for NumPy-based line of sight algorithms."""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any
+
+import numpy as np
+
+from tcod.cffi import ffi, lib
+
+if TYPE_CHECKING:
+ from numpy.typing import NDArray
+
+
+def bresenham(start: tuple[int, int], end: tuple[int, int]) -> NDArray[np.intc]:
+ """Return a thin Bresenham line as a NumPy array of shape (length, 2).
+
+ `start` and `end` are the endpoints of the line.
+ The result always includes both endpoints, and will always contain at
+ least one index.
+
+ You might want to use the results as is, convert them into a list with
+ :any:`numpy.ndarray.tolist` or transpose them and use that to index
+ another 2D array.
+
+ Example::
+
+ >>> import tcod
+ >>> tcod.los.bresenham((3, 5),(7, 7)).tolist() # Convert into list.
+ [[3, 5], [4, 5], [5, 6], [6, 6], [7, 7]]
+ >>> tcod.los.bresenham((0, 0), (0, 0))
+ array([[0, 0]]...)
+ >>> tcod.los.bresenham((0, 0), (4, 4))[1:-1] # Clip both endpoints.
+ array([[1, 1],
+ [2, 2],
+ [3, 3]]...)
+
+ >>> array = np.zeros((5, 5), dtype=np.int8)
+ >>> array
+ array([[0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0]], dtype=int8)
+ >>> tcod.los.bresenham((0, 0), (3, 4)).T # Transposed results.
+ array([[0, 1, 1, 2, 3],
+ [0, 1, 2, 3, 4]]...)
+ >>> indexes_ij = tuple(tcod.los.bresenham((0, 0), (3, 4)).T)
+ >>> array[indexes_ij] = np.arange(len(indexes_ij[0]))
+ >>> array
+ array([[0, 0, 0, 0, 0],
+ [0, 1, 2, 0, 0],
+ [0, 0, 0, 3, 0],
+ [0, 0, 0, 0, 4],
+ [0, 0, 0, 0, 0]], dtype=int8)
+ >>> array[indexes_ij]
+ array([0, 1, 2, 3, 4], dtype=int8)
+
+ .. versionadded:: 11.14
+ """
+ x1, y1 = start
+ x2, y2 = end
+ length = lib.bresenham(x1, y1, x2, y2, 0, ffi.NULL)
+ array: np.ndarray[Any, np.dtype[np.intc]] = np.ndarray((length, 2), dtype=np.intc)
+ lib.bresenham(x1, y1, x2, y2, length, ffi.from_buffer("int*", array))
+ return array
diff --git a/tcod/map.py b/tcod/map.py
index d3150be2..e1f4403b 100644
--- a/tcod/map.py
+++ b/tcod/map.py
@@ -1,46 +1,23 @@
-"""libtcod map attributes and field-of-view functions.
-
-Example::
-
- >>> import tcod.map
- >>> m = tcod.map.Map(width=3, height=4)
- >>> m.walkable
- array([[False, False, False],
- [False, False, False],
- [False, False, False],
- [False, False, False]]...)
-
- # Like the rest of the tcod modules, all arrays here are
- # in row-major order and are addressed with [y,x]
- >>> m.transparent[:] = True # Sets all to True.
- >>> m.transparent[1:3,0] = False # Sets (1, 0) and (2, 0) to False.
- >>> m.transparent
- array([[ True, True, True],
- [False, True, True],
- [False, True, True],
- [ True, True, True]]...)
-
- >>> m.compute_fov(0, 0)
- >>> m.fov
- array([[ True, True, True],
- [ True, True, True],
- [False, True, True],
- [False, False, True]]...)
- >>> m.fov[3,1]
- False
-
-"""
-
-from __future__ import absolute_import
+"""libtcod map attributes and field-of-view functions."""
+
+from __future__ import annotations
+
+import warnings
+from typing import TYPE_CHECKING, Any, Final, Literal
import numpy as np
+from typing_extensions import deprecated
-from tcod.libtcod import lib, ffi
import tcod._internal
import tcod.constants
+from tcod.cffi import ffi, lib
+if TYPE_CHECKING:
+ from numpy.typing import ArrayLike, NDArray
-class Map(object):
+
+@deprecated("This class may perform poorly and is no longer needed.")
+class Map:
"""A map containing libtcod attributes.
.. versionchanged:: 4.1
@@ -54,53 +31,94 @@ class Map(object):
height (int): Height of the new Map.
order (str): Which numpy memory order to use.
- Attributes:
- width (int): Read only width of this Map.
- height (int): Read only height of this Map.
- transparent: A boolean array of transparent cells.
- walkable: A boolean array of walkable cells.
- fov: A boolean array of the cells lit by :any:'compute_fov'.
+ Example::
+
+ >>> import tcod
+ >>> m = tcod.map.Map(width=3, height=4)
+ >>> m.walkable
+ array([[False, False, False],
+ [False, False, False],
+ [False, False, False],
+ [False, False, False]]...)
+ # Like the rest of the tcod modules, all arrays here are
+ # in row-major order and are addressed with [y,x]
+ >>> m.transparent[:] = True # Sets all to True.
+ >>> m.transparent[1:3,0] = False # Sets (1, 0) and (2, 0) to False.
+ >>> m.transparent
+ array([[ True, True, True],
+ [False, True, True],
+ [False, True, True],
+ [ True, True, True]]...)
+
+ >>> m.compute_fov(0, 0)
+ >>> m.fov
+ array([[ True, True, True],
+ [ True, True, True],
+ [False, True, True],
+ [False, False, True]]...)
+ >>> m.fov.item(3, 1)
+ False
+
+ .. deprecated:: 11.13
+ You no longer need to use this class to hold data for field-of-view
+ or pathfinding as those functions can now take NumPy arrays directly.
+ See :any:`tcod.map.compute_fov` and :any:`tcod.path`.
"""
- def __init__(self, width, height, order='C'):
- self.width = width
- self.height = height
- self._order = tcod._internal.verify_order(order)
+ def __init__(
+ self,
+ width: int,
+ height: int,
+ order: Literal["C", "F"] = "C",
+ ) -> None:
+ """Initialize the map."""
+ self.width: Final = width
+ """Read only width of this Map."""
+ self.height: Final = height
+ """Read only height of this Map."""
+ self._order: Literal["C", "F"] = tcod._internal.verify_order(order)
- self.__buffer = np.zeros((height, width, 3), dtype=np.bool_)
+ self._buffer: NDArray[np.bool_] = np.zeros((height, width, 3), dtype=np.bool_)
self.map_c = self.__as_cdata()
-
- def __as_cdata(self):
+ def __as_cdata(self) -> Any: # noqa: ANN401
return ffi.new(
- 'struct TCOD_Map*',
+ "struct TCOD_Map*",
(
self.width,
self.height,
self.width * self.height,
- ffi.cast('struct TCOD_MapCell*', self.__buffer.ctypes.data),
- )
+ ffi.from_buffer("struct TCOD_MapCell*", self._buffer),
+ ),
)
@property
- def transparent(self):
- buffer = self.__buffer[:,:,0]
- return buffer.T if self._order == 'F' else buffer
+ def transparent(self) -> NDArray[np.bool_]:
+ """A boolean array of transparent cells."""
+ buffer: np.ndarray[Any, np.dtype[np.bool_]] = self._buffer[:, :, 0]
+ return buffer.T if self._order == "F" else buffer
@property
- def walkable(self):
- buffer = self.__buffer[:,:,1]
- return buffer.T if self._order == 'F' else buffer
+ def walkable(self) -> NDArray[np.bool_]:
+ """A boolean array of walkable cells."""
+ buffer: np.ndarray[Any, np.dtype[np.bool_]] = self._buffer[:, :, 1]
+ return buffer.T if self._order == "F" else buffer
@property
- def fov(self):
- buffer = self.__buffer[:,:,2]
- return buffer.T if self._order == 'F' else buffer
+ def fov(self) -> NDArray[np.bool_]:
+ """A boolean array of the cells lit by :any:'compute_fov'."""
+ buffer: np.ndarray[Any, np.dtype[np.bool_]] = self._buffer[:, :, 2]
+ return buffer.T if self._order == "F" else buffer
- def compute_fov(self, x, y, radius=0, light_walls=True,
- algorithm=tcod.constants.FOV_RESTRICTIVE):
- # type (int, int, int, bool, int) -> None
+ def compute_fov(
+ self,
+ x: int,
+ y: int,
+ radius: int = 0,
+ light_walls: bool = True, # noqa: FBT001, FBT002
+ algorithm: int = tcod.constants.FOV_RESTRICTIVE,
+ ) -> None:
"""Compute a field-of-view on the current instance.
Args:
@@ -111,27 +129,136 @@ def compute_fov(self, x, y, radius=0, light_walls=True,
A value of `0` will give an infinite distance.
light_walls (bool): Light up walls, or only the floor.
algorithm (int): Defaults to tcod.FOV_RESTRICTIVE
+
+ If you already have transparency in a NumPy array then you could use
+ :any:`tcod.map.compute_fov` instead.
"""
- lib.TCOD_map_compute_fov(
- self.map_c, x, y, radius, light_walls, algorithm)
-
- def __setstate__(self, state):
- if '_Map__buffer' not in state: # deprecated
- # remove this check on major version update
- self.__buffer = np.zeros((state['height'], state['width'], 3),
- dtype=np.bool_)
- self.__buffer[:,:,0] = state['buffer'] & 0x01
- self.__buffer[:,:,1] = state['buffer'] & 0x02
- self.__buffer[:,:,2] = state['buffer'] & 0x04
- del state['buffer']
- state['_order'] = 'F'
- if '_order' not in state: # remove this check on major version update
- raise RuntimeError(
- 'This Map was saved with a bad version of tdl.')
+ if not (0 <= x < self.width and 0 <= y < self.height):
+ warnings.warn(
+ f"Index ({x}, {y}) is outside of this maps shape ({self.width}, {self.height})."
+ "\nThis will raise an error in future versions.",
+ RuntimeWarning,
+ stacklevel=2,
+ )
+
+ lib.TCOD_map_compute_fov(self.map_c, x, y, radius, light_walls, algorithm)
+
+ def __setstate__(self, state: dict[str, Any]) -> None:
+ """Unpickle this instance."""
+ if "_Map__buffer" in state: # Deprecated since 19.6
+ state["_buffer"] = state.pop("_Map__buffer")
+ if "buffer" in state: # Deprecated
+ self._buffer = np.zeros((state["height"], state["width"], 3), dtype=np.bool_)
+ self._buffer[:, :, 0] = state["buffer"] & 0x01
+ self._buffer[:, :, 1] = state["buffer"] & 0x02
+ self._buffer[:, :, 2] = state["buffer"] & 0x04
+ del state["buffer"]
+ state["_order"] = "F"
self.__dict__.update(state)
self.map_c = self.__as_cdata()
- def __getstate__(self):
+ def __getstate__(self) -> dict[str, Any]:
+ """Pickle this instance."""
state = self.__dict__.copy()
- del state['map_c']
+ del state["map_c"]
return state
+
+
+def compute_fov(
+ transparency: ArrayLike,
+ pov: tuple[int, int],
+ radius: int = 0,
+ light_walls: bool = True, # noqa: FBT001, FBT002
+ algorithm: int = tcod.constants.FOV_RESTRICTIVE,
+) -> NDArray[np.bool_]:
+ """Return a boolean mask of the area covered by a field-of-view.
+
+ `transparency` is a 2 dimensional array where all non-zero values are
+ considered transparent. The returned array will match the shape of this
+ array.
+
+ `pov` is the point-of-view origin point. Areas are visible if they can
+ be seen from this position. `pov` should be a 2D index matching the axes
+ of the `transparency` array, and must be within the bounds of the
+ `transparency` array.
+
+ `radius` is the maximum view distance from `pov`. If this is zero then
+ the maximum distance is used.
+
+ If `light_walls` is True then visible obstacles will be returned, otherwise
+ only transparent areas will be.
+
+ `algorithm` is the field-of-view algorithm to run. The default value is
+ `tcod.FOV_RESTRICTIVE`.
+ The options are:
+
+ * `tcod.FOV_BASIC`:
+ Simple ray-cast implementation.
+ * `tcod.FOV_DIAMOND`
+ * `tcod.FOV_SHADOW`:
+ Recursive shadow caster.
+ * `tcod.FOV_PERMISSIVE(n)`:
+ `n` starts at 0 (most restrictive) and goes up to 8 (most permissive.)
+ * `tcod.FOV_RESTRICTIVE`
+ * `tcod.FOV_SYMMETRIC_SHADOWCAST`
+
+ .. versionadded:: 9.3
+
+ .. versionchanged:: 11.0
+ The parameters `x` and `y` have been changed to `pov`.
+
+ .. versionchanged:: 11.17
+ Added `tcod.FOV_SYMMETRIC_SHADOWCAST` option.
+
+ Example:
+ >>> explored = np.zeros((3, 5), dtype=bool, order="F")
+ >>> transparency = np.ones((3, 5), dtype=bool, order="F")
+ >>> transparency[:2, 2] = False
+ >>> transparency # Transparent area.
+ array([[ True, True, False, True, True],
+ [ True, True, False, True, True],
+ [ True, True, True, True, True]]...)
+ >>> visible = tcod.map.compute_fov(transparency, (0, 0))
+ >>> visible # Visible area.
+ array([[ True, True, True, False, False],
+ [ True, True, True, False, False],
+ [ True, True, True, True, False]]...)
+ >>> explored |= visible # Keep track of an explored area.
+
+ .. seealso::
+ :any:`numpy.where`: For selecting between two arrays using a boolean
+ array, like the one returned by this function.
+
+ :any:`numpy.select`: Select between arrays based on multiple
+ conditions.
+ """
+ transparency = np.asarray(transparency)
+ if len(transparency.shape) != 2: # noqa: PLR2004
+ msg = f"transparency must be an array of 2 dimensions (shape is {transparency.shape!r})"
+ raise TypeError(msg)
+ if isinstance(pov, int):
+ msg = "The tcod.map.compute_fov function has changed. The `x` and `y` parameters should now be given as a single tuple."
+ raise TypeError(msg)
+ if not (0 <= pov[0] < transparency.shape[0] and 0 <= pov[1] < transparency.shape[1]):
+ warnings.warn(
+ f"Given pov index {pov!r} is outside the array of shape {transparency.shape!r}."
+ "\nThis will raise an error in future versions.",
+ RuntimeWarning,
+ stacklevel=2,
+ )
+ map_buffer: NDArray[np.bool_] = np.empty(
+ transparency.shape,
+ dtype=[("transparent", bool), ("walkable", bool), ("fov", bool)],
+ )
+ map_cdata = ffi.new(
+ "struct TCOD_Map*",
+ (
+ map_buffer.shape[1],
+ map_buffer.shape[0],
+ map_buffer.shape[1] * map_buffer.shape[0],
+ ffi.from_buffer("struct TCOD_MapCell*", map_buffer),
+ ),
+ )
+ map_buffer["transparent"] = transparency # type: ignore[call-overload]
+ lib.TCOD_map_compute_fov(map_cdata, pov[1], pov[0], radius, light_walls, algorithm)
+ return map_buffer["fov"] # type: ignore[no-any-return,call-overload]
diff --git a/tcod/noise.c b/tcod/noise.c
index 3372619d..17a90a74 100644
--- a/tcod/noise.c
+++ b/tcod/noise.c
@@ -2,7 +2,7 @@
#include "../libtcod/src/libtcod/libtcod.h"
-float NoiseGetSample(TDLNoise *noise, float *xyzw) {
+float NoiseGetSample(TDLNoise* noise, float* __restrict xyzw) {
switch (noise->implementation) {
default:
case kNoiseImplementationSimple:
@@ -15,34 +15,30 @@ float NoiseGetSample(TDLNoise *noise, float *xyzw) {
return TCOD_noise_get_turbulence(noise->noise, xyzw, noise->octaves);
}
}
-void NoiseSampleMeshGrid(
- TDLNoise *noise, const long len, const float *in, float *out) {
-# pragma omp parallel
+void NoiseSampleMeshGrid(TDLNoise* noise, const long len, const float* __restrict in, float* __restrict out) {
{
long i;
-# pragma omp for schedule(static)
for (i = 0; i < len; ++i) {
int axis;
float xyzw[TCOD_NOISE_MAX_DIMENSIONS];
- for (axis = 0; axis < noise->dimensions; ++axis) {
- xyzw[axis] = in[axis * len + i];
- }
+ for (axis = 0; axis < noise->dimensions; ++axis) { xyzw[axis] = in[axis * len + i]; }
out[i] = NoiseGetSample(noise, xyzw);
}
}
}
-static long GetSizeFromShape(const int ndim, const long *shape){
- long size=1;
+static long GetSizeFromShape(const int ndim, const long* shape) {
+ long size = 1;
long i;
- for (i = 0; i < ndim; ++i){
- size *= shape[i];
- }
+ for (i = 0; i < ndim; ++i) { size *= shape[i]; }
return size;
}
static float GetOpenMeshGridValue(
- TDLNoise *noise, const int ndim, const long *shape,
- const float **ogrid_in, const long index) {
- int axis=ndim - 1;
+ TDLNoise* __restrict noise,
+ const int ndim,
+ const long* __restrict shape,
+ const float* __restrict* __restrict ogrid_in,
+ const long index) {
+ int axis = ndim - 1;
long xyzw_indexes[TCOD_NOISE_MAX_DIMENSIONS];
float xyzw_values[TCOD_NOISE_MAX_DIMENSIONS];
/* Convert index -> xyzw_indexes -> xyzw_values */
@@ -54,16 +50,15 @@ static float GetOpenMeshGridValue(
}
return NoiseGetSample(noise, xyzw_values);
}
-void NoiseSampleOpenMeshGrid(TDLNoise *noise, const int ndim_in,
- const long *shape,
- const float **ogrid_in, float *out) {
-# pragma omp parallel
+void NoiseSampleOpenMeshGrid(
+ TDLNoise* __restrict noise,
+ const int ndim_in,
+ const long* __restrict shape,
+ const float* __restrict* __restrict ogrid_in,
+ float* __restrict out) {
{
long i;
- long len=GetSizeFromShape(ndim_in, shape);
-# pragma omp for schedule(static)
- for (i = 0; i < len; ++i){
- out[i] = GetOpenMeshGridValue(noise, ndim_in, shape, ogrid_in, i);
- }
+ long len = GetSizeFromShape(ndim_in, shape);
+ for (i = 0; i < len; ++i) { out[i] = GetOpenMeshGridValue(noise, ndim_in, shape, ogrid_in, i); }
}
}
diff --git a/tcod/noise.h b/tcod/noise.h
index be56d501..393c18e2 100644
--- a/tcod/noise.h
+++ b/tcod/noise.h
@@ -1,12 +1,12 @@
#ifndef PYTHON_TCOD_NOISE_H_
#define PYTHON_TCOD_NOISE_H_
+#include "../libtcod/src/libtcod/mersenne.h"
#include "../libtcod/src/libtcod/noise.h"
#include "../libtcod/src/libtcod/noise_defaults.h"
-#include "../libtcod/src/libtcod/mersenne.h"
typedef enum NoiseImplementation {
- kNoiseImplementationSimple,
+ kNoiseImplementationSimple,
kNoiseImplementationFBM,
kNoiseImplementationTurbulence,
} NoiseImplementation;
@@ -19,15 +19,17 @@ typedef struct TDLNoise {
} TDLNoise;
/* Return a single sample from noise. */
-float NoiseGetSample(TDLNoise *noise, float *xyzw);
+float NoiseGetSample(TDLNoise* noise, float* __restrict xyzw);
/* Fill `out` with samples derived from the mesh-grid `in`. */
-void NoiseSampleMeshGrid(
- TDLNoise *noise, const long len, const float *in, float *out);
+void NoiseSampleMeshGrid(TDLNoise* noise, const long len, const float* __restrict in, float* __restrict out);
/* Fill `out` with samples derived from the open mesh-grid `in`. */
-void NoiseSampleOpenMeshGrid(TDLNoise *noise,
- const int ndim, const long *shape,
- const float **ogrid_in, float *out);
+void NoiseSampleOpenMeshGrid(
+ TDLNoise* __restrict noise,
+ const int ndim,
+ const long* __restrict shape,
+ const float* __restrict* __restrict ogrid_in,
+ float* __restrict out);
#endif /* PYTHON_TCOD_NOISE_H_ */
diff --git a/tcod/noise.py b/tcod/noise.py
index 36acf010..161dfc26 100644
--- a/tcod/noise.py
+++ b/tcod/noise.py
@@ -1,256 +1,412 @@
-"""
-The :any:`Noise.sample_mgrid` and :any:`Noise.sample_ogrid` methods are
-multi-threaded operations when the Python runtime supports OpenMP.
-Even when single threaded these methods will perform much better than
-multiple calls to :any:`Noise.get_point`.
+"""Noise map generators are provided by this module.
-Example::
+The :any:`Noise.sample_mgrid` and :any:`Noise.sample_ogrid` methods perform
+much better than multiple calls to :any:`Noise.get_point`.
- import numpy as np
- import tcod
- import tcod.noise
-
- noise = tcod.noise.Noise(
- dimensions=2,
- algorithm=tcod.NOISE_SIMPLEX,
- implementation=tcod.noise.TURBULENCE,
- hurst=0.5,
- lacunarity=2.0,
- octaves=4,
- seed=None,
- )
+Example::
- # Create a 5x5 open multi-dimensional mesh-grid.
- ogrid = [np.arange(5, dtype=np.float32),
- np.arange(5, dtype=np.float32)]
- print(ogrid)
+ >>> import numpy as np
+ >>> import tcod
+ >>> noise = tcod.noise.Noise(
+ ... dimensions=2,
+ ... algorithm=tcod.noise.Algorithm.SIMPLEX,
+ ... seed=42,
+ ... )
+ >>> samples = noise[tcod.noise.grid(shape=(5, 4), scale=0.25, offset=(0, 0))]
+ >>> samples # Samples are a grid of floats between -1.0 and 1.0
+ array([[ 0. , -0.55046356, -0.76072866, -0.7088647 , -0.68165785],
+ [-0.27523372, -0.7205134 , -0.74057037, -0.43919194, -0.29195625],
+ [-0.40398532, -0.57662135, -0.33160293, 0.12860827, 0.2864191 ],
+ [-0.50773406, -0.2643614 , 0.24446318, 0.6390255 , 0.5922846 ]],
+ dtype=float32)
+ >>> (samples + 1.0) * 0.5 # You can normalize samples to 0.0 - 1.0
+ array([[0.5 , 0.22476822, 0.11963567, 0.14556766, 0.15917107],
+ [0.36238313, 0.1397433 , 0.12971482, 0.28040403, 0.35402188],
+ [0.29800734, 0.21168932, 0.33419853, 0.5643041 , 0.6432096 ],
+ [0.24613297, 0.3678193 , 0.6222316 , 0.8195127 , 0.79614234]],
+ dtype=float32)
+ >>> ((samples + 1.0) * (256 / 2)).astype(np.uint8) # Or as 8-bit unsigned bytes.
+ array([[128, 57, 30, 37, 40],
+ [ 92, 35, 33, 71, 90],
+ [ 76, 54, 85, 144, 164],
+ [ 63, 94, 159, 209, 203]], dtype=uint8)
+"""
- # Scale the grid.
- ogrid[0] *= 0.25
- ogrid[1] *= 0.25
+from __future__ import annotations
- # Return the sampled noise from this grid of points.
- samples = noise.sample_ogrid(ogrid)
- print(samples)
-"""
-from __future__ import absolute_import
+import enum
+import warnings
+from typing import TYPE_CHECKING, Any, Literal
import numpy as np
-from tcod.libtcod import ffi, lib
-import tcod.libtcod
+import tcod.constants
+import tcod.random
+from tcod.cffi import ffi, lib
+
+if TYPE_CHECKING:
+ from collections.abc import Sequence
-"""Noise implementation constants"""
-SIMPLE = 0
-FBM = 1
-TURBULENCE = 2
+ from numpy.typing import ArrayLike, NDArray
-class Noise(object):
+
+class Algorithm(enum.IntEnum):
+ """Libtcod noise algorithms.
+
+ .. versionadded:: 12.2
"""
+ PERLIN = 1
+ """Perlin noise."""
+
+ SIMPLEX = 2
+ """Simplex noise."""
+
+ WAVELET = 4
+ """Wavelet noise."""
+
+ def __repr__(self) -> str:
+ """Return the string representation for this algorithm."""
+ return f"tcod.noise.Algorithm.{self.name}"
+
+
+class Implementation(enum.IntEnum):
+ """Noise implementations.
+
+ .. versionadded:: 12.2
+ """
+
+ SIMPLE = 0
+ """Generate plain noise."""
+
+ FBM = 1
+ """Fractional Brownian motion.
+
+ https://en.wikipedia.org/wiki/Fractional_Brownian_motion
+ """
+
+ TURBULENCE = 2
+ """Turbulence noise implementation."""
+
+ def __repr__(self) -> str:
+ """Return the string representation for this implementation."""
+ return f"tcod.noise.Implementation.{self.name}"
+
+
+def __getattr__(name: str) -> Implementation:
+ if name in Implementation.__members__:
+ warnings.warn(
+ f"'tcod.noise.{name}' is deprecated, use 'tcod.noise.Implementation.{name}' instead.",
+ FutureWarning,
+ stacklevel=2,
+ )
+ return Implementation[name]
+ msg = f"module {__name__} has no attribute {name}"
+ raise AttributeError(msg)
+
+
+class Noise:
+ """A configurable noise sampler.
+
The ``hurst`` exponent describes the raggedness of the resultant noise,
with a higher value leading to a smoother noise.
Not used with tcod.noise.SIMPLE.
- ``lacunarity`` is a multiplier that determines how fast the noise
- frequency increases for each successive octave.
+ ``lacunarity`` is a multiplier that determines how fast the noise frequency increases for each successive octave.
Not used with tcod.noise.SIMPLE.
Args:
- dimensions (int): Must be from 1 to 4.
- algorithm (int): Defaults to NOISE_SIMPLEX
- implementation (int): Defaults to tcod.noise.SIMPLE
- hurst (float): The hurst exponent. Should be in the 0.0-1.0 range.
- lacunarity (float): The noise lacunarity.
- octaves (float): The level of detail on fBm and turbulence
- implementations.
- seed (Optional[Random]): A Random instance, or None.
+ dimensions: Must be from 1 to 4.
+ algorithm: Defaults to :any:`tcod.noise.Algorithm.SIMPLEX`
+ implementation: Defaults to :any:`tcod.noise.Implementation.SIMPLE`
+ hurst: The hurst exponent. Should be in the 0.0-1.0 range.
+ lacunarity: The noise lacunarity.
+ octaves: The level of detail on fBm and turbulence implementations.
+ seed: A Random instance, or None.
Attributes:
noise_c (CData): A cffi pointer to a TCOD_noise_t object.
"""
- def __init__(self, dimensions, algorithm=2, implementation=SIMPLE,
- hurst=0.5, lacunarity=2.0, octaves=4, seed=None):
- if not 0 < dimensions <= 4:
- raise ValueError('dimensions must be in range 0 < n <= 4, got %r' %
- (dimensions,))
- self._random = seed
- _random_c = seed.random_c if seed else ffi.NULL
- self._algorithm = algorithm
+ def __init__( # noqa: PLR0913
+ self,
+ dimensions: int,
+ algorithm: int = Algorithm.SIMPLEX,
+ implementation: int = Implementation.SIMPLE,
+ hurst: float = 0.5,
+ lacunarity: float = 2.0,
+ octaves: float = 4,
+ seed: int | tcod.random.Random | None = None,
+ ) -> None:
+ """Initialize and seed the noise object."""
+ if not 0 < dimensions <= 4: # noqa: PLR2004
+ msg = f"dimensions must be in range 0 < n <= 4, got {dimensions}"
+ raise ValueError(msg)
+ self._seed = seed
+ self._random = self.__rng_from_seed(seed)
+ _random_c = self._random.random_c
self.noise_c = ffi.gc(
ffi.cast(
- 'struct TCOD_Noise*',
- lib.TCOD_noise_new(dimensions, hurst, lacunarity,
- _random_c),
- ),
- lib.TCOD_noise_delete)
- self._tdl_noise_c = ffi.new('TDLNoise*', (self.noise_c,
- dimensions,
- 0,
- octaves))
- self.implementation = implementation # sanity check
+ "struct TCOD_Noise*",
+ lib.TCOD_noise_new(dimensions, hurst, lacunarity, _random_c),
+ ),
+ lib.TCOD_noise_delete,
+ )
+ self._tdl_noise_c = ffi.new("TDLNoise*", (self.noise_c, dimensions, 0, octaves))
+ self.algorithm = algorithm
+ self.implementation = implementation # sanity check
+
+ @staticmethod
+ def __rng_from_seed(seed: None | int | tcod.random.Random) -> tcod.random.Random:
+ if seed is None or isinstance(seed, int):
+ return tcod.random.Random(seed=seed, algorithm=tcod.random.MERSENNE_TWISTER)
+ return seed
+
+ def __repr__(self) -> str:
+ """Return the string representation of this noise instance."""
+ parameters = [
+ f"dimensions={self.dimensions}",
+ f"algorithm={self.algorithm!r}",
+ f"implementation={Implementation(self.implementation)!r}",
+ ]
+ if self.hurst != 0.5: # noqa: PLR2004 # Default value
+ parameters.append(f"hurst={self.hurst}")
+ if self.lacunarity != 2: # noqa: PLR2004 # Default value
+ parameters.append(f"lacunarity={self.lacunarity}")
+ if self.octaves != 4: # noqa: PLR2004 # Default value
+ parameters.append(f"octaves={self.octaves}")
+ if self._seed is not None:
+ parameters.append(f"seed={self._seed}")
+ return f"tcod.noise.Noise({', '.join(parameters)})"
@property
- def dimensions(self):
- return self._tdl_noise_c.dimensions
+ def dimensions(self) -> int:
+ """Number of dimensions supported by this noise generator."""
+ return int(self._tdl_noise_c.dimensions)
@property
- def dimentions(self): # deprecated
- return self.dimensions
+ def algorithm(self) -> int:
+ """Current selected algorithm. Can be changed."""
+ noise_type = self.noise_c.noise_type
+ return Algorithm(noise_type) if noise_type else Algorithm.SIMPLEX
- @property
- def algorithm(self):
- return self.noise_c.noise_type
@algorithm.setter
- def algorithm(self, value):
+ def algorithm(self, value: int) -> None:
lib.TCOD_noise_set_type(self.noise_c, value)
@property
- def implementation(self):
- return self._tdl_noise_c.implementation
+ def implementation(self) -> int:
+ """Current selected implementation. Can be changed."""
+ return Implementation(self._tdl_noise_c.implementation)
+
@implementation.setter
- def implementation(self, value):
- if not 0 <= value < 3:
- raise ValueError('%r is not a valid implementation. ' % (value,))
+ def implementation(self, value: int) -> None:
+ if not 0 <= value < 3: # noqa: PLR2004
+ msg = f"{value!r} is not a valid implementation. "
+ raise ValueError(msg)
self._tdl_noise_c.implementation = value
@property
- def hurst(self):
- return self.noise_c.H
+ def hurst(self) -> float:
+ """Noise hurst exponent. Can be changed."""
+ return float(self.noise_c.H)
@property
- def lacunarity(self):
- return self.noise_c.lacunarity
+ def lacunarity(self) -> float:
+ """Noise lacunarity. Can be changed."""
+ return float(self.noise_c.lacunarity)
@property
- def octaves(self):
- return self._tdl_noise_c.octaves
+ def octaves(self) -> float:
+ """Level of detail on fBm and turbulence implementations. Can be changed."""
+ return float(self._tdl_noise_c.octaves)
+
@octaves.setter
- def octaves(self, value):
+ def octaves(self, value: float) -> None:
self._tdl_noise_c.octaves = value
- def get_point(self, x=0, y=0, z=0, w=0):
+ def get_point(self, x: float = 0, y: float = 0, z: float = 0, w: float = 0) -> float:
"""Return the noise value at the (x, y, z, w) point.
Args:
- x (float): The position on the 1st axis.
- y (float): The position on the 2nd axis.
- z (float): The position on the 3rd axis.
- w (float): The position on the 4th axis.
+ x: The position on the 1st axis.
+ y: The position on the 2nd axis.
+ z: The position on the 3rd axis.
+ w: The position on the 4th axis.
"""
- return lib.NoiseGetSample(self._tdl_noise_c, (x, y, z, w))
+ return float(lib.NoiseGetSample(self._tdl_noise_c, (x, y, z, w)))
+
+ def __getitem__(self, indexes: Any) -> NDArray[np.float32]:
+ """Sample a noise map through NumPy indexing.
+
+ This follows NumPy's advanced indexing rules, but allows for floating point values.
+
+ .. versionadded:: 11.16
+ """
+ if not isinstance(indexes, tuple):
+ indexes = (indexes,)
+ if len(indexes) > self.dimensions:
+ msg = f"This noise generator has {self.dimensions} dimensions, but was indexed with {len(indexes)}."
+ raise IndexError(msg)
+ indexes = list(np.broadcast_arrays(*indexes))
+ c_input = [ffi.NULL, ffi.NULL, ffi.NULL, ffi.NULL]
+ for i, index in enumerate(indexes):
+ if index.dtype.type == np.object_:
+ msg = "Index arrays can not be of dtype np.object_."
+ raise TypeError(msg)
+ indexes[i] = np.ascontiguousarray(index, dtype=np.float32)
+ c_input[i] = ffi.from_buffer("float*", indexes[i])
+
+ c_input_tuple = tuple(c_input)
+ assert len(c_input_tuple) == 4 # noqa: PLR2004
+
+ out: NDArray[np.float32] = np.empty(indexes[0].shape, dtype=np.float32)
+ if self.implementation == Implementation.SIMPLE:
+ lib.TCOD_noise_get_vectorized(
+ self.noise_c,
+ self.algorithm,
+ out.size,
+ *c_input_tuple,
+ ffi.from_buffer("float*", out),
+ )
+ elif self.implementation == Implementation.FBM:
+ lib.TCOD_noise_get_fbm_vectorized(
+ self.noise_c,
+ self.algorithm,
+ self.octaves,
+ out.size,
+ *c_input_tuple,
+ ffi.from_buffer("float*", out),
+ )
+ elif self.implementation == Implementation.TURBULENCE:
+ lib.TCOD_noise_get_turbulence_vectorized(
+ self.noise_c,
+ self.algorithm,
+ self.octaves,
+ out.size,
+ *c_input_tuple,
+ ffi.from_buffer("float*", out),
+ )
+ else:
+ msg = f"Unexpected {self.implementation!r}"
+ raise TypeError(msg)
- def sample_mgrid(self, mgrid):
+ return out
+
+ def sample_mgrid(self, mgrid: ArrayLike) -> NDArray[np.float32]:
"""Sample a mesh-grid array and return the result.
The :any:`sample_ogrid` method performs better as there is a lot of
overhead when working with large mesh-grids.
Args:
- mgrid (numpy.ndarray): A mesh-grid array of points to sample.
+ mgrid: A mesh-grid array of points to sample.
A contiguous array of type `numpy.float32` is preferred.
Returns:
- numpy.ndarray: An array of sampled points.
+ An array of sampled points.
- This array has the shape: ``mgrid.shape[:-1]``.
- The ``dtype`` is `numpy.float32`.
+ This array has the shape: ``mgrid.shape[:-1]``.
+ The ``dtype`` is `numpy.float32`.
"""
mgrid = np.ascontiguousarray(mgrid, np.float32)
if mgrid.shape[0] != self.dimensions:
- raise ValueError('mgrid.shape[0] must equal self.dimensions, '
- '%r[0] != %r' % (mgrid.shape, self.dimensions))
- out = np.ndarray(mgrid.shape[1:], np.float32)
+ msg = f"mgrid.shape[0] must equal self.dimensions, {mgrid.shape!r}[0] != {self.dimensions!r}"
+ raise ValueError(msg)
+ out: np.ndarray[Any, np.dtype[np.float32]] = np.ndarray(mgrid.shape[1:], np.float32)
if mgrid.shape[1:] != out.shape:
- raise ValueError('mgrid.shape[1:] must equal out.shape, '
- '%r[1:] != %r' % (mgrid.shape, out.shape))
- lib.NoiseSampleMeshGrid(self._tdl_noise_c, out.size,
- ffi.cast('float*', mgrid.ctypes.data),
- ffi.cast('float*', out.ctypes.data))
+ msg = f"mgrid.shape[1:] must equal out.shape, {mgrid.shape!r}[1:] != {out.shape!r}"
+ raise ValueError(msg)
+ lib.NoiseSampleMeshGrid(
+ self._tdl_noise_c,
+ out.size,
+ ffi.from_buffer("float*", mgrid),
+ ffi.from_buffer("float*", out),
+ )
return out
- def sample_ogrid(self, ogrid):
+ def sample_ogrid(self, ogrid: Sequence[ArrayLike]) -> NDArray[np.float32]:
"""Sample an open mesh-grid array and return the result.
- Args
- ogrid (Sequence[Sequence[float]]): An open mesh-grid.
+ Args:
+ ogrid: An open mesh-grid.
Returns:
- numpy.ndarray: An array of sampled points.
+ An array of sampled points.
- The ``shape`` is based on the lengths of the open mesh-grid
- arrays.
- The ``dtype`` is `numpy.float32`.
+ The ``shape`` is based on the lengths of the open mesh-grid arrays.
+ The ``dtype`` is `numpy.float32`.
"""
if len(ogrid) != self.dimensions:
- raise ValueError('len(ogrid) must equal self.dimensions, '
- '%r != %r' % (len(ogrid), self.dimensions))
- ogrids = [np.ascontiguousarray(array, np.float32) for array in ogrid]
- out = np.ndarray([array.size for array in ogrids], np.float32)
+ msg = f"len(ogrid) must equal self.dimensions, {len(ogrid)!r} != {self.dimensions!r}"
+ raise ValueError(msg)
+ ogrids: list[NDArray[np.float32]] = [np.ascontiguousarray(array, np.float32) for array in ogrid]
+ out: np.ndarray[Any, np.dtype[np.float32]] = np.ndarray([array.size for array in ogrids], np.float32)
lib.NoiseSampleOpenMeshGrid(
self._tdl_noise_c,
len(ogrids),
out.shape,
- [ffi.cast('float*', array.ctypes.data) for array in ogrids],
- ffi.cast('float*', out.ctypes.data),
- )
+ [ffi.from_buffer("float*", array) for array in ogrids],
+ ffi.from_buffer("float*", out),
+ )
return out
- def __getstate__(self):
+ def __getstate__(self) -> dict[str, Any]:
+ """Support picking this instance."""
state = self.__dict__.copy()
- if self.dimensions < 4 and self.noise_c.waveletTileData == ffi.NULL:
+ if self.dimensions < 4 and self.noise_c.waveletTileData == ffi.NULL: # noqa: PLR2004
# Trigger a side effect of wavelet, so that copies will be synced.
saved_algo = self.algorithm
- self.algorithm = tcod.libtcod.NOISE_WAVELET
+ self.algorithm = tcod.constants.NOISE_WAVELET
self.get_point()
self.algorithm = saved_algo
- waveletTileData = None
+ waveletTileData = None # noqa: N806
if self.noise_c.waveletTileData != ffi.NULL:
- waveletTileData = list(self.noise_c.waveletTileData[0:32*32*32])
- state['_waveletTileData'] = waveletTileData
-
- state['noise_c'] = {
- 'ndim': self.noise_c.ndim,
- 'map': list(self.noise_c.map),
- 'buffer': [list(sub_buffer) for sub_buffer in self.noise_c.buffer],
- 'H': self.noise_c.H,
- 'lacunarity': self.noise_c.lacunarity,
- 'exponent': list(self.noise_c.exponent),
- 'waveletTileData': waveletTileData,
- 'noise_type': self.noise_c.noise_type,
- }
- state['_tdl_noise_c'] = {
- 'dimensions': self._tdl_noise_c.dimensions,
- 'implementation': self._tdl_noise_c.implementation,
- 'octaves': self._tdl_noise_c.octaves,
- }
+ waveletTileData = list(self.noise_c.waveletTileData[0 : 32 * 32 * 32]) # noqa: N806
+ state["_waveletTileData"] = waveletTileData
+
+ state["noise_c"] = {
+ "ndim": self.noise_c.ndim,
+ "map": list(self.noise_c.map),
+ "buffer": [list(sub_buffer) for sub_buffer in self.noise_c.buffer],
+ "H": self.noise_c.H,
+ "lacunarity": self.noise_c.lacunarity,
+ "exponent": list(self.noise_c.exponent),
+ "waveletTileData": waveletTileData,
+ "noise_type": self.noise_c.noise_type,
+ }
+ state["_tdl_noise_c"] = {
+ "dimensions": self._tdl_noise_c.dimensions,
+ "implementation": self._tdl_noise_c.implementation,
+ "octaves": self._tdl_noise_c.octaves,
+ }
return state
- def __setstate__(self, state):
- if isinstance(state, tuple): # deprecated format
+ def __setstate__(self, state: dict[str, Any]) -> None:
+ """Unpickle this instance."""
+ if isinstance(state, tuple): # deprecated format
return self._setstate_old(state)
# unpack wavelet tile data if it exists
- if '_waveletTileData' in state:
- state['_waveletTileData'] = ffi.new('float[]',
- state['_waveletTileData'])
- state['noise_c']['waveletTileData'] = state['_waveletTileData']
+ if "_waveletTileData" in state:
+ state["_waveletTileData"] = ffi.new("float[]", state["_waveletTileData"])
+ state["noise_c"]["waveletTileData"] = state["_waveletTileData"]
else:
- state['noise_c']['waveletTileData'] = ffi.NULL
+ state["noise_c"]["waveletTileData"] = ffi.NULL
# unpack TCOD_Noise and link to Random instance
- state['noise_c']['rand'] = state['_random'].random_c
- state['noise_c'] = ffi.new('struct TCOD_Noise*', state['noise_c'])
+ state["noise_c"]["rand"] = state["_random"].random_c
+ state["noise_c"] = ffi.new("struct TCOD_Noise*", state["noise_c"])
# unpack TDLNoise and link to libtcod noise
- state['_tdl_noise_c']['noise'] = state['noise_c']
- state['_tdl_noise_c'] = ffi.new('TDLNoise*', state['_tdl_noise_c'])
+ state["_tdl_noise_c"]["noise"] = state["noise_c"]
+ state["_tdl_noise_c"] = ffi.new("TDLNoise*", state["_tdl_noise_c"])
self.__dict__.update(state)
+ return None
- def _setstate_old(self, state):
+ def _setstate_old(self, state: tuple[Any, ...]) -> None:
self._random = state[0]
- self.noise_c = ffi.new('struct TCOD_Noise*')
+ self.noise_c = ffi.new("struct TCOD_Noise*")
self.noise_c.ndim = state[3]
ffi.buffer(self.noise_c.map)[:] = state[4]
ffi.buffer(self.noise_c.buffer)[:] = state[5]
@@ -259,9 +415,97 @@ def _setstate_old(self, state):
ffi.buffer(self.noise_c.exponent)[:] = state[8]
if state[9]:
# high change of this being prematurely garbage collected!
- self.__waveletTileData = ffi.new('float[]', 32*32*32)
+ self.__waveletTileData = ffi.new("float[]", 32 * 32 * 32)
ffi.buffer(self.__waveletTileData)[:] = state[9]
self.noise_c.noise_type = state[10]
- self._tdl_noise_c = ffi.new('TDLNoise*',
- (self.noise_c, self.noise_c.ndim,
- state[1], state[2]))
+ self._tdl_noise_c = ffi.new("TDLNoise*", (self.noise_c, self.noise_c.ndim, state[1], state[2]))
+
+
+def grid(
+ shape: tuple[int, ...],
+ scale: tuple[float, ...] | float,
+ origin: tuple[float, ...] | None = None,
+ indexing: Literal["ij", "xy"] = "xy",
+ *,
+ offset: tuple[float, ...] | None = None,
+) -> tuple[NDArray[np.number], ...]:
+ """Generate a mesh-grid of sample points to use with noise sampling.
+
+ Args:
+ shape: The shape of the grid.
+ This can be any number of dimensions, but :class:`Noise` classes only support up to 4.
+ scale: The step size between samples.
+ This can be a single float, or it can be a tuple of floats with one float for each axis in `shape`.
+ A lower scale gives smoother transitions between noise values.
+ origin: The position of the first sample.
+ If `None` then the `origin` will be zero on each axis.
+ `origin` is not scaled by the `scale` parameter.
+ indexing: Passed to :any:`numpy.meshgrid`.
+ offset: The offset into the shape to generate.
+ Similar to `origin` but is scaled by the `scale` parameter.
+ Can be multiples of `shape` to index noise samples by chunk.
+
+ .. versionadded:: 19.2
+
+ Returns:
+ A sparse mesh-grid to be passed into a :class:`Noise` instance.
+
+ Example::
+
+ >>> noise = tcod.noise.Noise(dimensions=2, seed=42)
+
+ # Common case for ij-indexed arrays
+ >>> noise[tcod.noise.grid(shape=(3, 5), scale=0.25, indexing="ij")]
+ array([[ 0. , -0.27523372, -0.40398532, -0.50773406, -0.64945626],
+ [-0.55046356, -0.7205134 , -0.57662135, -0.2643614 , -0.12529983],
+ [-0.76072866, -0.74057037, -0.33160293, 0.24446318, 0.5346834 ]],
+ dtype=float32)
+
+ # Transpose an xy-indexed array to get a standard order="F" result
+ >>> noise[tcod.noise.grid(shape=(4, 5), scale=(0.5, 0.25), origin=(1.0, 1.0))].T
+ array([[ 0.52655405, 0.25038874, -0.03488023, -0.18455243, -0.16333057],
+ [-0.5037453 , -0.75348294, -0.73630923, -0.35063767, 0.18149695],
+ [-0.81221616, -0.6379566 , -0.12449139, 0.4495706 , 0.7547447 ],
+ [-0.7057655 , -0.5817767 , -0.22774395, 0.02399864, -0.07006818]],
+ dtype=float32)
+
+ # Can sample noise by chunk using the offset keyword
+ >>> noise[tcod.noise.grid(shape=(3, 5), scale=0.25, indexing="ij", offset=(0, 0))]
+ array([[ 0. , -0.27523372, -0.40398532, -0.50773406, -0.64945626],
+ [-0.55046356, -0.7205134 , -0.57662135, -0.2643614 , -0.12529983],
+ [-0.76072866, -0.74057037, -0.33160293, 0.24446318, 0.5346834 ]],
+ dtype=float32)
+ >>> noise[tcod.noise.grid(shape=(3, 5), scale=0.25, indexing="ij", offset=(3, 0))]
+ array([[-0.7088647 , -0.43919194, 0.12860827, 0.6390255 , 0.80402255],
+ [-0.68165785, -0.29195625, 0.2864191 , 0.5922846 , 0.52655405],
+ [-0.7841389 , -0.46131462, 0.0159424 , 0.17141782, -0.04198273]],
+ dtype=float32)
+ >>> noise[tcod.noise.grid(shape=(3, 5), scale=0.25, indexing="ij", offset=(6, 0))]
+ array([[-0.779634 , -0.60696834, -0.27446985, -0.23233278, -0.5037453 ],
+ [-0.5474089 , -0.54476213, -0.42235228, -0.49519652, -0.7101793 ],
+ [-0.28291094, -0.4326369 , -0.5227732 , -0.69655263, -0.81221616]],
+ dtype=float32)
+
+ .. versionadded:: 12.2
+ """
+ if isinstance(scale, (int, float)):
+ scale = (scale,) * len(shape)
+ if origin is None:
+ origin = (0,) * len(shape)
+ if len(shape) != len(scale):
+ msg = "shape must have the same length as scale"
+ raise TypeError(msg)
+ if len(shape) != len(origin):
+ msg = "shape must have the same length as origin"
+ raise TypeError(msg)
+ if offset is not None:
+ if len(shape) != len(offset):
+ msg = "shape must have the same length as offset"
+ raise TypeError(msg)
+ origin = tuple(
+ i_origin + i_scale * i_offset for i_scale, i_offset, i_origin in zip(scale, offset, origin, strict=True)
+ )
+ indexes = (
+ np.arange(i_shape) * i_scale + i_origin for i_shape, i_scale, i_origin in zip(shape, scale, origin, strict=True)
+ )
+ return tuple(np.meshgrid(*indexes, copy=False, sparse=True, indexing=indexing))
diff --git a/tcod/path.c b/tcod/path.c
new file mode 100644
index 00000000..8f4f2fc6
--- /dev/null
+++ b/tcod/path.c
@@ -0,0 +1,492 @@
+#include "path.h"
+
+#include
+#include
+#include
+#include
+
+#include "../libtcod/src/libtcod/error.h"
+#include "../libtcod/src/libtcod/pathfinder_frontier.h"
+#include "../libtcod/src/libtcod/utility.h"
+
+static void* pick_array_pointer(const struct PathCostArray* map, int i, int j) {
+ return (void*)(map->array + map->strides[0] * i + map->strides[1] * j);
+}
+float PathCostArrayFloat32(int x1, int y1, int x2, int y2, const struct PathCostArray* map) {
+ return *(float*)pick_array_pointer(map, x2, y2);
+}
+float PathCostArrayInt8(int x1, int y1, int x2, int y2, const struct PathCostArray* map) {
+ return *(int8_t*)pick_array_pointer(map, x2, y2);
+}
+float PathCostArrayInt16(int x1, int y1, int x2, int y2, const struct PathCostArray* map) {
+ return *(int16_t*)pick_array_pointer(map, x2, y2);
+}
+float PathCostArrayInt32(int x1, int y1, int x2, int y2, const struct PathCostArray* map) {
+ return (float)(*(int32_t*)pick_array_pointer(map, x2, y2));
+}
+float PathCostArrayUInt8(int x1, int y1, int x2, int y2, const struct PathCostArray* map) {
+ return *(uint8_t*)pick_array_pointer(map, x2, y2);
+}
+float PathCostArrayUInt16(int x1, int y1, int x2, int y2, const struct PathCostArray* map) {
+ return *(uint16_t*)pick_array_pointer(map, x2, y2);
+}
+float PathCostArrayUInt32(int x1, int y1, int x2, int y2, const struct PathCostArray* map) {
+ return (float)(*(uint32_t*)pick_array_pointer(map, x2, y2));
+}
+
+static bool array_in_range(const struct NArray* arr, int n, const int* index) {
+ for (int i = 0; i < n; ++i) {
+ if (index[i] < 0 || index[i] >= arr->shape[i]) { return 0; }
+ }
+ return 1;
+}
+static bool array2d_in_range(const struct NArray* arr, int i, int j) {
+ return 0 <= i && i < arr->shape[0] && 0 <= j && j < arr->shape[1];
+}
+static void* get_array_ptr(const struct NArray* arr, int n, const int* index) {
+ unsigned char* ptr = (unsigned char*)arr->data;
+ for (int i = 0; i < n; ++i) { ptr += arr->strides[i] * index[i]; }
+ return (void*)ptr;
+}
+static int64_t get_array_int64(const struct NArray* arr, int n, const int* index) {
+ const void* ptr = get_array_ptr(arr, n, index);
+ switch (arr->type) {
+ case np_int8:
+ return *(const int8_t*)ptr;
+ case np_int16:
+ return *(const int16_t*)ptr;
+ case np_int32:
+ return *(const int32_t*)ptr;
+ case np_int64:
+ return *(const int64_t*)ptr;
+ case np_uint8:
+ return *(const uint8_t*)ptr;
+ case np_uint16:
+ return *(const uint16_t*)ptr;
+ case np_uint32:
+ return *(const uint32_t*)ptr;
+ case np_uint64:
+ return *(const uint64_t*)ptr;
+ default:
+ return 0;
+ }
+}
+static int get_array_int(const struct NArray* arr, int n, const int* index) {
+ return (int)get_array_int64(arr, n, index);
+}
+static void set_array_int64(struct NArray* __restrict arr, int n, const int* __restrict index, int64_t value) {
+ void* ptr = get_array_ptr(arr, n, index);
+ switch (arr->type) {
+ case np_int8:
+ *(int8_t*)ptr = (int8_t)value;
+ return;
+ case np_int16:
+ *(int16_t*)ptr = (int16_t)value;
+ return;
+ case np_int32:
+ *(int32_t*)ptr = (int32_t)value;
+ return;
+ case np_int64:
+ *(int64_t*)ptr = value;
+ return;
+ case np_uint8:
+ *(uint8_t*)ptr = (uint8_t)value;
+ return;
+ case np_uint16:
+ *(uint16_t*)ptr = (uint16_t)value;
+ return;
+ case np_uint32:
+ *(uint32_t*)ptr = (uint32_t)value;
+ return;
+ case np_uint64:
+ *(uint64_t*)ptr = (uint64_t)value;
+ return;
+ default:
+ return;
+ }
+}
+static void set_array2d_int64(struct NArray* __restrict arr, int i, int j, int64_t value) {
+ int index[2] = {i, j};
+ set_array_int64(arr, 2, index, value);
+}
+static void set_array_int(struct NArray* __restrict arr, int n, const int* index, int value) {
+ set_array_int64(arr, n, index, value);
+}
+static void set_array2d_int(struct NArray* __restrict arr, int i, int j, int value) {
+ set_array2d_int64(arr, i, j, value);
+}
+static int64_t get_array_is_max(const struct NArray* arr, int n, const int* index) {
+ const void* ptr = get_array_ptr(arr, n, index);
+ switch (arr->type) {
+ case np_int8:
+ return *(const int8_t*)ptr == SCHAR_MAX;
+ case np_int16:
+ return *(const int16_t*)ptr == SHRT_MAX;
+ case np_int32:
+ return *(const int32_t*)ptr == INT_MAX;
+ case np_int64:
+ return *(const int64_t*)ptr == LONG_MAX;
+ case np_uint8:
+ return *(const uint8_t*)ptr == UCHAR_MAX;
+ case np_uint16:
+ return *(const uint16_t*)ptr == USHRT_MAX;
+ case np_uint32:
+ return *(const uint32_t*)ptr == UINT_MAX;
+ case np_uint64:
+ return *(const uint64_t*)ptr == ULONG_MAX;
+ default:
+ return 0;
+ }
+}
+
+static const int CARDINAL_[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
+static const int DIAGONAL_[4][2] = {{-1, -1}, {1, -1}, {-1, 1}, {1, 1}};
+
+static void dijkstra2d_add_edge(
+ struct TCOD_Frontier* __restrict frontier,
+ struct NArray* __restrict dist_array,
+ const struct NArray* __restrict cost,
+ int edge_cost,
+ const int* __restrict dir // dir[2]
+) {
+ const int index[2] = {frontier->active_index[0] + dir[0], frontier->active_index[1] + dir[1]};
+ if (!array_in_range(dist_array, 2, index)) { return; }
+ edge_cost *= get_array_int(cost, 2, index);
+ if (edge_cost <= 0) { return; }
+ int distance = frontier->active_dist + edge_cost;
+ if (get_array_int(dist_array, 2, index) <= distance) { return; }
+ set_array_int(dist_array, 2, index, distance);
+ TCOD_frontier_push(frontier, index, distance, distance);
+}
+
+int dijkstra2d(
+ struct NArray* __restrict dist_array,
+ const struct NArray* __restrict cost,
+ int edges_2d_n,
+ const int* __restrict edges_2d) {
+ struct TCOD_Frontier* frontier = TCOD_frontier_new(2);
+ if (!frontier) { return TCOD_E_ERROR; }
+ for (int i = 0; i < dist_array->shape[0]; ++i) {
+ for (int j = 0; j < dist_array->shape[1]; ++j) {
+ const int index[2] = {i, j};
+ if (get_array_is_max(dist_array, 2, index)) { continue; }
+ int dist = get_array_int(dist_array, 2, index);
+ TCOD_frontier_push(frontier, index, dist, dist);
+ }
+ }
+ while (TCOD_frontier_size(frontier)) {
+ TCOD_frontier_pop(frontier);
+ int distance_here = get_array_int(dist_array, 2, frontier->active_index);
+ if (frontier->active_dist != distance_here) { continue; }
+ for (int i = 0; i < edges_2d_n; ++i) {
+ dijkstra2d_add_edge(frontier, dist_array, cost, edges_2d[i * 3 + 2], &edges_2d[i * 3]);
+ }
+ }
+ return TCOD_E_OK;
+}
+
+int dijkstra2d_basic(
+ struct NArray* __restrict dist_array, const struct NArray* __restrict cost, int cardinal, int diagonal) {
+ struct TCOD_Frontier* frontier = TCOD_frontier_new(2);
+ if (!frontier) { return TCOD_E_ERROR; }
+ for (int i = 0; i < dist_array->shape[0]; ++i) {
+ for (int j = 0; j < dist_array->shape[1]; ++j) {
+ const int index[2] = {i, j};
+ if (get_array_is_max(dist_array, 2, index)) { continue; }
+ int dist = get_array_int(dist_array, 2, index);
+ TCOD_frontier_push(frontier, index, dist, dist);
+ }
+ }
+ while (TCOD_frontier_size(frontier)) {
+ TCOD_frontier_pop(frontier);
+ int distance_here = get_array_int(dist_array, 2, frontier->active_index);
+ if (frontier->active_dist != distance_here) { continue; }
+ if (cardinal > 0) {
+ dijkstra2d_add_edge(frontier, dist_array, cost, cardinal, CARDINAL_[0]);
+ dijkstra2d_add_edge(frontier, dist_array, cost, cardinal, CARDINAL_[1]);
+ dijkstra2d_add_edge(frontier, dist_array, cost, cardinal, CARDINAL_[2]);
+ dijkstra2d_add_edge(frontier, dist_array, cost, cardinal, CARDINAL_[3]);
+ }
+ if (diagonal > 0) {
+ dijkstra2d_add_edge(frontier, dist_array, cost, diagonal, DIAGONAL_[0]);
+ dijkstra2d_add_edge(frontier, dist_array, cost, diagonal, DIAGONAL_[1]);
+ dijkstra2d_add_edge(frontier, dist_array, cost, diagonal, DIAGONAL_[2]);
+ dijkstra2d_add_edge(frontier, dist_array, cost, diagonal, DIAGONAL_[3]);
+ }
+ }
+ return TCOD_E_OK;
+}
+static void hillclimb2d_check_edge(
+ const struct NArray* __restrict dist_array,
+ int* __restrict distance_in_out,
+ const int* __restrict origin, // origin[2]
+ const int* __restrict dir, // dir[2]
+ int* __restrict index_out // index_out[2]
+) {
+ const int next[2] = {origin[0] + dir[0], origin[1] + dir[1]};
+ if (!array_in_range(dist_array, 2, next)) { return; }
+ const int next_distance = get_array_int(dist_array, 2, next);
+ if (next_distance < *distance_in_out) {
+ *distance_in_out = next_distance;
+ index_out[0] = next[0];
+ index_out[1] = next[1];
+ }
+}
+int hillclimb2d(
+ const struct NArray* __restrict dist_array,
+ int start_i,
+ int start_j,
+ int edges_2d_n,
+ const int* __restrict edges_2d,
+ int* __restrict out) {
+ int next[2] = {start_i, start_j};
+ int old_dist = get_array_int(dist_array, 2, next);
+ int new_dist = old_dist;
+ int length = 0;
+ while (1) {
+ ++length;
+ if (out) {
+ out[0] = next[0];
+ out[1] = next[1];
+ out += 2;
+ }
+ const int origin[2] = {next[0], next[1]};
+ for (int i = 0; i < edges_2d_n; ++i) {
+ hillclimb2d_check_edge(dist_array, &new_dist, origin, &edges_2d[i * 2], next);
+ }
+ if (old_dist == new_dist) { return length; }
+ old_dist = new_dist;
+ }
+}
+int hillclimb2d_basic(
+ const struct NArray* __restrict dist_array,
+ int start_i,
+ int start_j,
+ bool cardinal,
+ bool diagonal,
+ int* __restrict out) {
+ int next[2] = {start_i, start_j};
+ int old_dist = get_array_int(dist_array, 2, next);
+ int new_dist = old_dist;
+ int length = 0;
+ while (1) {
+ ++length;
+ if (out) {
+ out[0] = next[0];
+ out[1] = next[1];
+ out += 2;
+ }
+ const int origin[2] = {next[0], next[1]};
+ if (cardinal) {
+ hillclimb2d_check_edge(dist_array, &new_dist, origin, CARDINAL_[0], next);
+ hillclimb2d_check_edge(dist_array, &new_dist, origin, CARDINAL_[1], next);
+ hillclimb2d_check_edge(dist_array, &new_dist, origin, CARDINAL_[2], next);
+ hillclimb2d_check_edge(dist_array, &new_dist, origin, CARDINAL_[3], next);
+ }
+ if (diagonal) {
+ hillclimb2d_check_edge(dist_array, &new_dist, origin, DIAGONAL_[0], next);
+ hillclimb2d_check_edge(dist_array, &new_dist, origin, DIAGONAL_[1], next);
+ hillclimb2d_check_edge(dist_array, &new_dist, origin, DIAGONAL_[2], next);
+ hillclimb2d_check_edge(dist_array, &new_dist, origin, DIAGONAL_[3], next);
+ }
+ if (old_dist == new_dist) { return length; }
+ old_dist = new_dist;
+ }
+}
+int compute_heuristic(const struct PathfinderHeuristic* __restrict heuristic, int ndim, const int* __restrict index) {
+ if (!heuristic) { return 0; }
+ int x = 0;
+ int y = 0;
+ int z = 0;
+ int w = 0;
+ switch (ndim) {
+ case 4:
+ w = abs(index[ndim - 4] - heuristic->target[ndim - 4]);
+ //@fallthrough@
+ case 3:
+ z = abs(index[ndim - 3] - heuristic->target[ndim - 3]);
+ //@fallthrough@
+ case 2:
+ y = abs(index[ndim - 2] - heuristic->target[ndim - 2]);
+ //@fallthrough@
+ case 1:
+ x = abs(index[ndim - 1] - heuristic->target[ndim - 1]);
+ break;
+ default:
+ return 0;
+ }
+ int diagonal = heuristic->diagonal != 0 ? TCOD_MIN(x, y) : 0;
+ int straight = TCOD_MAX(x, y) - diagonal;
+ return (straight * heuristic->cardinal + diagonal * heuristic->diagonal + w * heuristic->w + z * heuristic->z);
+}
+void path_compute_add_edge(
+ struct TCOD_Frontier* __restrict frontier,
+ struct NArray* __restrict dist_map,
+ struct NArray* __restrict travel_map,
+ const struct NArray* __restrict cost_map,
+ const int* __restrict edge_rule,
+ const struct PathfinderHeuristic* __restrict heuristic) {
+ int dest[TCOD_PATHFINDER_MAX_DIMENSIONS];
+ for (int i = 0; i < frontier->ndim; ++i) { dest[i] = frontier->active_index[i] + edge_rule[i]; }
+ if (!array_in_range(dist_map, frontier->ndim, dest)) { return; }
+ int edge_cost = edge_rule[frontier->ndim];
+ edge_cost *= get_array_int(cost_map, frontier->ndim, dest);
+ if (edge_cost <= 0) { return; }
+ int distance = frontier->active_dist + edge_cost;
+ if (get_array_int(dist_map, frontier->ndim, dest) <= distance) { return; }
+ set_array_int(dist_map, frontier->ndim, dest, distance);
+ int* path = get_array_ptr(travel_map, frontier->ndim, dest);
+ for (int i = 0; i < frontier->ndim; ++i) { path[i] = frontier->active_index[i]; }
+ int priority = distance + compute_heuristic(heuristic, frontier->ndim, dest);
+ TCOD_frontier_push(frontier, dest, distance, priority);
+}
+/**
+ Returns true if the heuristic target has been reached by the active_node.
+ */
+static bool path_compute_at_goal(
+ const struct TCOD_Frontier* __restrict frontier, const struct PathfinderHeuristic* __restrict heuristic) {
+ if (!heuristic) { return 0; }
+ for (int i = 0; i < frontier->ndim; ++i) {
+ if (frontier->active_index[i] != heuristic->target[i]) { return 0; }
+ }
+ return 1;
+}
+int path_compute_step(
+ struct TCOD_Frontier* __restrict frontier,
+ struct NArray* __restrict dist_map,
+ struct NArray* __restrict travel_map,
+ int n,
+ const struct PathfinderRule* __restrict rules,
+ const struct PathfinderHeuristic* __restrict heuristic) {
+ if (!frontier) { return TCOD_set_errorv("Missing frontier."); }
+ if (frontier->ndim <= 0 || frontier->ndim > TCOD_PATHFINDER_MAX_DIMENSIONS) {
+ return TCOD_set_errorv("Invalid frontier->ndim.");
+ }
+ if (!dist_map) { return TCOD_set_errorv("Missing dist_map."); }
+ if (frontier->ndim != dist_map->ndim) { return TCOD_set_errorv("Invalid or corrupt input."); }
+ if (travel_map && frontier->ndim + 1 != travel_map->ndim) { return TCOD_set_errorv("Invalid or corrupt input."); }
+ TCOD_frontier_pop(frontier);
+ for (int i = 0; i < n; ++i) {
+ if (rules[i].condition.type) {
+ if (!get_array_int(&rules[i].condition, frontier->ndim, frontier->active_index)) { continue; }
+ }
+ for (int edge_i = 0; edge_i < rules[i].edge_count; ++edge_i) {
+ path_compute_add_edge(
+ frontier,
+ dist_map,
+ travel_map,
+ &rules[i].cost,
+ &rules[i].edge_array[edge_i * (frontier->ndim + 1)],
+ heuristic);
+ }
+ }
+ if (path_compute_at_goal(frontier, heuristic)) {
+ return 1; // Heuristic target reached.
+ }
+ return 0;
+}
+int path_compute(
+ struct TCOD_Frontier* __restrict frontier,
+ struct NArray* __restrict dist_map,
+ struct NArray* __restrict travel_map,
+ int n,
+ const struct PathfinderRule* __restrict rules,
+ const struct PathfinderHeuristic* __restrict heuristic) {
+ if (!frontier) { return TCOD_set_errorv("Missing frontier."); }
+ while (TCOD_frontier_size(frontier)) {
+ int err = path_compute_step(frontier, dist_map, travel_map, n, rules, heuristic);
+ if (err != 0) { return err; }
+ }
+ return 0;
+}
+ptrdiff_t get_travel_path(
+ int8_t ndim, const struct NArray* __restrict travel_map, const int* __restrict start, int* __restrict out) {
+ if (ndim <= 0 || ndim > TCOD_PATHFINDER_MAX_DIMENSIONS) { return TCOD_set_errorv("Invalid ndim."); }
+ if (!travel_map) { return TCOD_set_errorv("Missing travel_map."); }
+ if (!start) { return TCOD_set_errorv("Missing start."); }
+ if (ndim != travel_map->ndim - 1) { return TCOD_set_errorv("Invalid or corrupt input."); }
+ const int* next = get_array_ptr(travel_map, ndim, start);
+ const int* current = start;
+ ptrdiff_t max_loops = 1;
+ ptrdiff_t length = 0;
+ for (int i = 0; i < ndim; ++i) { max_loops *= travel_map->shape[i]; }
+ while (current != next) {
+ ++length;
+ if (out) {
+ for (int i = 0; i < ndim; ++i) { out[i] = current[i]; }
+ out += ndim;
+ }
+ current = next;
+ if (!array_in_range(travel_map, ndim, next)) {
+ switch (ndim) {
+ case 1:
+ return TCOD_set_errorvf("Index (%i) is out of range.", next[0]);
+ case 2:
+ return TCOD_set_errorvf("Index (%i, %i) is out of range.", next[0], next[1]);
+ case 3:
+ return TCOD_set_errorvf("Index (%i, %i, %i) is out of range.", next[0], next[1], next[2]);
+ case 4:
+ return TCOD_set_errorvf("Index (%i, %i, %i, %i) is out of range.", next[0], next[1], next[2], next[3]);
+ }
+ }
+ next = get_array_ptr(travel_map, ndim, next);
+ if (!out && length == max_loops) { return TCOD_set_errorv("Possible cyclic loop detected."); }
+ }
+ return length;
+}
+int update_frontier_heuristic(
+ struct TCOD_Frontier* __restrict frontier, const struct PathfinderHeuristic* __restrict heuristic) {
+ if (!frontier) { return TCOD_set_errorv("Missing frontier."); }
+ for (int i = 0; i < frontier->heap.size; ++i) {
+ unsigned char* heap_ptr = (unsigned char*)frontier->heap.heap;
+ heap_ptr += frontier->heap.node_size * i;
+ int* priority = (int*)heap_ptr;
+ struct FrontierNode* f_node = (struct FrontierNode*)(heap_ptr + frontier->heap.data_offset);
+ *priority = (f_node->distance + compute_heuristic(heuristic, frontier->ndim, f_node->index));
+ }
+ TCOD_minheap_heapify(&frontier->heap);
+ return 0;
+}
+static int update_frontier_from_distance_iterator(
+ struct TCOD_Frontier* __restrict frontier, const struct NArray* __restrict dist_map, int dimension, int* index) {
+ if (dimension == frontier->ndim) {
+ if (get_array_is_max(dist_map, dimension, index)) { return 0; }
+ int dist = get_array_int(dist_map, dimension, index);
+ return TCOD_frontier_push(frontier, index, dist, dist);
+ }
+ for (int i = 0; i < dist_map->shape[dimension]; ++i) {
+ index[dimension] = i;
+ int err = update_frontier_from_distance_iterator(frontier, dist_map, dimension + 1, index);
+ if (err) { return err; }
+ }
+ return 0;
+}
+int rebuild_frontier_from_distance(
+ struct TCOD_Frontier* __restrict frontier, const struct NArray* __restrict dist_map) {
+ if (!frontier) { return TCOD_set_errorv("Missing frontier."); }
+ if (!dist_map) { return TCOD_set_errorv("Missing dist_map."); }
+ TCOD_frontier_clear(frontier);
+ int index[TCOD_PATHFINDER_MAX_DIMENSIONS];
+ return update_frontier_from_distance_iterator(frontier, dist_map, 0, index);
+}
+int frontier_has_index(
+ const struct TCOD_Frontier* __restrict frontier,
+ const int* __restrict index) // index[frontier->ndim]
+{
+ if (!frontier) { return TCOD_set_errorv("Missing frontier."); }
+ if (!index) { return TCOD_set_errorv("Missing index."); }
+ for (int i = 0; i < frontier->heap.size; ++i) {
+ const unsigned char* heap_ptr = (const unsigned char*)frontier->heap.heap;
+ heap_ptr += frontier->heap.node_size * i;
+ const struct FrontierNode* f_node = (const void*)(heap_ptr + frontier->heap.data_offset);
+ bool found = 1;
+ for (int j = 0; j < frontier->ndim; ++j) {
+ if (index[j] != f_node->index[j]) {
+ found = 0;
+ break;
+ }
+ }
+ if (found) { return 1; }
+ }
+ return 0;
+}
diff --git a/tcod/path.cpp b/tcod/path.cpp
deleted file mode 100644
index 185b9c78..00000000
--- a/tcod/path.cpp
+++ /dev/null
@@ -1,47 +0,0 @@
-#include "path.h"
-
-#include
-
-static char* PickArrayValue(const struct PathCostArray *map, int i, int j) {
- return map->array + map->strides[0] * i + map->strides[1] * j;
-}
-
-template
-static float GetPathCost(const struct PathCostArray *map, int i, int j) {
- return (float)(((T*)PickArrayValue(map, i, j))[0]);
-}
-
-float PathCostArrayFloat32(int x1, int y1, int x2, int y2,
- const struct PathCostArray *map){
- return GetPathCost(map, x2, y2);
-}
-
-float PathCostArrayInt8(int x1, int y1, int x2, int y2,
- const struct PathCostArray *map){
- return GetPathCost(map, x2, y2);
-}
-
-float PathCostArrayInt16(int x1, int y1, int x2, int y2,
- const struct PathCostArray *map){
- return GetPathCost(map, x2, y2);
-}
-
-float PathCostArrayInt32(int x1, int y1, int x2, int y2,
- const struct PathCostArray *map){
- return GetPathCost(map, x2, y2);
-}
-
-float PathCostArrayUInt8(int x1, int y1, int x2, int y2,
- const struct PathCostArray *map){
- return GetPathCost(map, x2, y2);
-}
-
-float PathCostArrayUInt16(int x1, int y1, int x2, int y2,
- const struct PathCostArray *map){
- return GetPathCost(map, x2, y2);
-}
-
-float PathCostArrayUInt32(int x1, int y1, int x2, int y2,
- const struct PathCostArray *map){
- return GetPathCost(map, x2, y2);
-}
diff --git a/tcod/path.h b/tcod/path.h
index 487245dd..75754d1b 100644
--- a/tcod/path.h
+++ b/tcod/path.h
@@ -1,36 +1,152 @@
#ifndef PYTHON_TCOD_PATH_H_
#define PYTHON_TCOD_PATH_H_
+#include
+#include
+#include
+
+#include "../libtcod/src/libtcod/pathfinder_frontier.h"
+
#ifdef __cplusplus
extern "C" {
#endif
+/**
+ * Common NumPy data types.
+ */
+typedef enum NP_Type {
+ np_undefined = 0,
+ np_int8,
+ np_int16,
+ np_int32,
+ np_int64,
+ np_uint8,
+ np_uint16,
+ np_uint32,
+ np_uint64,
+ np_float16,
+ np_float32,
+ np_float64,
+} NP_Type;
+/**
+ * A simple 4D NumPy array ctype.
+ */
+typedef struct NArray {
+ NP_Type type;
+ int8_t ndim;
+ char* __restrict data;
+ ptrdiff_t shape[5]; // TCOD_PATHFINDER_MAX_DIMENSIONS + 1
+ ptrdiff_t strides[5]; // TCOD_PATHFINDER_MAX_DIMENSIONS + 1
+} NArray;
+
+struct PathfinderRule {
+ /** Rule condition, could be uninitialized zeros. */
+ NArray condition;
+ /** Edge cost map, required. */
+ NArray cost;
+ /** Number of edge rules in `edge_array`. */
+ int edge_count;
+ /** Example of 2D edges: [i, j, cost, i_2, j_2, cost_2, ...] */
+ int* __restrict edge_array;
+};
+
+struct PathfinderHeuristic {
+ int cardinal;
+ int diagonal;
+ int z;
+ int w;
+ int target[TCOD_PATHFINDER_MAX_DIMENSIONS];
+};
+
+struct FrontierNode {
+ int distance;
+ int index[];
+};
struct PathCostArray {
- char *array;
- long long strides[2];
+ char* __restrict array;
+ long long strides[2];
};
-float PathCostArrayFloat32(int x1, int y1, int x2, int y2,
- const struct PathCostArray *map);
+float PathCostArrayFloat32(int x1, int y1, int x2, int y2, const struct PathCostArray* map);
+
+float PathCostArrayUInt8(int x1, int y1, int x2, int y2, const struct PathCostArray* map);
+
+float PathCostArrayUInt16(int x1, int y1, int x2, int y2, const struct PathCostArray* map);
+
+float PathCostArrayUInt32(int x1, int y1, int x2, int y2, const struct PathCostArray* map);
+
+float PathCostArrayInt8(int x1, int y1, int x2, int y2, const struct PathCostArray* map);
+
+float PathCostArrayInt16(int x1, int y1, int x2, int y2, const struct PathCostArray* map);
+
+float PathCostArrayInt32(int x1, int y1, int x2, int y2, const struct PathCostArray* map);
+
+/**
+ Return the value to add to the distance to sort nodes by A*.
+
+ `heuristic` can be NULL.
+
+ `index[ndim]` must not be NULL.
+ */
+int compute_heuristic(const struct PathfinderHeuristic* __restrict heuristic, int ndim, const int* __restrict index);
+int dijkstra2d(
+ struct NArray* __restrict dist,
+ const struct NArray* __restrict cost,
+ int edges_2d_n,
+ const int* __restrict edges_2d);
-float PathCostArrayUInt8(int x1, int y1, int x2, int y2,
- const struct PathCostArray *map);
+int dijkstra2d_basic(struct NArray* __restrict dist, const struct NArray* __restrict cost, int cardinal, int diagonal);
-float PathCostArrayUInt16(int x1, int y1, int x2, int y2,
- const struct PathCostArray *map);
+int hillclimb2d(
+ const struct NArray* __restrict dist_array,
+ int start_i,
+ int start_j,
+ int edges_2d_n,
+ const int* __restrict edges_2d,
+ int* __restrict out);
-float PathCostArrayUInt32(int x1, int y1, int x2, int y2,
- const struct PathCostArray *map);
+int hillclimb2d_basic(
+ const struct NArray* __restrict dist, int x, int y, bool cardinal, bool diagonal, int* __restrict out);
-float PathCostArrayInt8(int x1, int y1, int x2, int y2,
- const struct PathCostArray *map);
+int path_compute_step(
+ struct TCOD_Frontier* __restrict frontier,
+ struct NArray* __restrict dist_map,
+ struct NArray* __restrict travel_map,
+ int n,
+ const struct PathfinderRule* __restrict rules, // rules[n]
+ const struct PathfinderHeuristic* heuristic);
-float PathCostArrayInt16(int x1, int y1, int x2, int y2,
- const struct PathCostArray *map);
+int path_compute(
+ struct TCOD_Frontier* __restrict frontier,
+ struct NArray* __restrict dist_map,
+ struct NArray* __restrict travel_map,
+ int n,
+ const struct PathfinderRule* __restrict rules, // rules[n]
+ const struct PathfinderHeuristic* __restrict heuristic);
+/**
+ Find and get a path along `travel_map`.
-float PathCostArrayInt32(int x1, int y1, int x2, int y2,
- const struct PathCostArray *map);
+ Returns the length of the path, `out` must be NULL or `out[n*ndim]`.
+ Where `n` is the value return from a previous call with the same
+ parameters.
+ */
+ptrdiff_t get_travel_path(
+ int8_t ndim, const NArray* __restrict travel_map, const int* __restrict start, int* __restrict out);
+/**
+ Update the priority of nodes on the frontier and sort them.
+ */
+int update_frontier_heuristic(
+ struct TCOD_Frontier* __restrict frontier, const struct PathfinderHeuristic* __restrict heuristic);
+/**
+ Update a frontier from a distance array.
+ Assumes no heuristic is active.
+ */
+int rebuild_frontier_from_distance(struct TCOD_Frontier* __restrict frontier, const NArray* __restrict dist_map);
+/**
+ Return true if `index[frontier->ndim]` is a node in `frontier`.
+ */
+int frontier_has_index(const struct TCOD_Frontier* __restrict frontier, const int* __restrict index);
#ifdef __cplusplus
}
#endif
diff --git a/tcod/path.py b/tcod/path.py
index 75c091ff..ecdb6b46 100644
--- a/tcod/path.py
+++ b/tcod/path.py
@@ -1,112 +1,95 @@
-"""
+"""This module provides a fast configurable pathfinding implementation.
+
+To get started create a 2D NumPy array of integers where a value of zero is a
+blocked node and any higher value is the cost to move to that node.
+You then pass this array to :any:`SimpleGraph`, and then pass that graph to
+:any:`Pathfinder`.
-Example::
-
- >>> import numpy as np
- >>> import tcod.path
- >>> dungeon = np.array(
- ... [
- ... [1, 0, 1, 1, 1],
- ... [1, 0, 1, 0, 1],
- ... [1, 1, 1, 0, 1],
- ... ],
- ... dtype=np.int8,
- ... )
- ...
-
- # Create a pathfinder from a numpy array.
- # This is the recommended way to use the tcod.path module.
- >>> astar = tcod.path.AStar(dungeon)
- >>> print(astar.get_path(0, 0, 2, 4))
- [(1, 0), (2, 1), (1, 2), (0, 3), (1, 4), (2, 4)]
- >>> astar.cost[0, 1] = 1 # You can access the map array via this attribute.
- >>> print(astar.get_path(0, 0, 2, 4))
- [(0, 1), (0, 2), (0, 3), (1, 4), (2, 4)]
-
- # Create a pathfinder from an edge_cost function.
- # Calling Python functions from C is known to be very slow.
- >>> def edge_cost(my_x, my_y, dest_x, dest_y):
- ... return dungeon[dest_x, dest_y]
- ...
- >>> dijkstra = tcod.path.Dijkstra(
- ... tcod.path.EdgeCostCallback(edge_cost, dungeon.shape),
- ... )
- ...
- >>> dijkstra.set_goal(0, 0)
- >>> print(dijkstra.get_path(2, 4))
- [(0, 1), (0, 2), (0, 3), (1, 4), (2, 4)]
+Once you have a :any:`Pathfinder` you call :any:`Pathfinder.add_root` to set
+the root node. You can then get a path towards or away from the root with
+:any:`Pathfinder.path_from` and :any:`Pathfinder.path_to` respectively.
+
+:any:`SimpleGraph` includes a code example of the above process.
.. versionchanged:: 5.0
All path-finding functions now respect the NumPy array shape (if a NumPy
array is used.)
"""
-from __future__ import absolute_import
+from __future__ import annotations
+
+import functools
+import itertools
+import warnings
+from typing import TYPE_CHECKING, Any, Final, Literal
import numpy as np
+from typing_extensions import Self
-from tcod.libtcod import lib, ffi
+import tcod.map
+from tcod._internal import _check
+from tcod.cffi import ffi, lib
+if TYPE_CHECKING:
+ from collections.abc import Callable, Sequence
-@ffi.def_extern()
-def _pycall_path_old(x1, y1, x2, y2, handle):
- # type: (int, int, int, int, cffi.CData]) -> float
- """libtcodpy style callback, needs to preserve the old userData issue."""
- func, userData = ffi.from_handle(handle)
- return func(x1, y1, x2, y2, userData)
+ from numpy.typing import ArrayLike, DTypeLike, NDArray
-@ffi.def_extern()
-def _pycall_path_simple(x1, y1, x2, y2, handle):
- # type: (int, int, int, int, cffi.CData]) -> float
+@ffi.def_extern() # type: ignore[untyped-decorator]
+def _pycall_path_old(x1: int, y1: int, x2: int, y2: int, handle: Any) -> float: # noqa: ANN401
+ """Libtcodpy style callback, needs to preserve the old userdata issue."""
+ func, userdata = ffi.from_handle(handle)
+ return func(x1, y1, x2, y2, userdata) # type: ignore[no-any-return]
+
+
+@ffi.def_extern() # type: ignore[untyped-decorator]
+def _pycall_path_simple(x1: int, y1: int, x2: int, y2: int, handle: Any) -> float: # noqa: ANN401
"""Does less and should run faster, just calls the handle function."""
- return ffi.from_handle(handle)(x1, y1, x2, y2)
+ return ffi.from_handle(handle)(x1, y1, x2, y2) # type: ignore[no-any-return]
-@ffi.def_extern()
-def _pycall_path_swap_src_dest(x1, y1, x2, y2, handle):
- # type: (int, int, int, int, cffi.CData]) -> float
+@ffi.def_extern() # type: ignore[untyped-decorator]
+def _pycall_path_swap_src_dest(x1: int, y1: int, x2: int, y2: int, handle: Any) -> float: # noqa: ANN401
"""A TDL function dest comes first to match up with a dest only call."""
- return ffi.from_handle(handle)(x2, y2, x1, y1)
+ return ffi.from_handle(handle)(x2, y2, x1, y1) # type: ignore[no-any-return]
-@ffi.def_extern()
-def _pycall_path_dest_only(x1, y1, x2, y2, handle):
- # type: (int, int, int, int, cffi.CData]) -> float
+@ffi.def_extern() # type: ignore[untyped-decorator]
+def _pycall_path_dest_only(_x1: int, _y1: int, x2: int, y2: int, handle: Any) -> float: # noqa: ANN401
"""A TDL function which samples the dest coordinate only."""
- return ffi.from_handle(handle)(x2, y2)
+ return ffi.from_handle(handle)(x2, y2) # type: ignore[no-any-return]
-def _get_pathcost_func(name):
- # type: (str) -> Callable[[int, int, int, int, cffi.CData], float]
+def _get_path_cost_func(
+ name: str,
+) -> Callable[[int, int, int, int, Any], float]:
"""Return a properly cast PathCostArray callback."""
- return ffi.cast('TCOD_path_func_t', ffi.addressof(lib, name))
+ if not ffi:
+ return lambda _x1, _y1, _x2, _y2, _: 0
+ return ffi.cast("TCOD_path_func_t", ffi.addressof(lib, name)) # type: ignore[no-any-return]
-class _EdgeCostFunc(object):
+class _EdgeCostFunc:
"""Generic edge-cost function factory.
`userdata` is the custom userdata to send to the C call.
`shape` is the maximum boundary for the algorithm.
"""
+
_CALLBACK_P = lib._pycall_path_old
- def __init__(self, userdata, shape):
- # type: (Any, Tuple[int, int]) -> None
+ def __init__(self, userdata: object, shape: tuple[int, int]) -> None:
self._userdata = userdata
self.shape = shape
- def get_tcod_path_ffi(self):
- # type: () -> Tuple[cffi.CData, cffi.CData, Tuple[int, int]]
- """Return (C callback, userdata handle, shape)"""
+ def get_tcod_path_ffi(self) -> tuple[Any, Any, tuple[int, int]]:
+ """Return (C callback, userdata handle, shape)."""
return self._CALLBACK_P, ffi.new_handle(self._userdata), self.shape
- def __repr__(self):
- return '%s(%r, shape=%r)' % (
- self.__class__.__name__,
- self._userdata, self.shape,
- )
+ def __repr__(self) -> str:
+ return f"{self.__class__.__name__}({self._userdata!r}, shape={self.shape!r})"
class EdgeCostCallback(_EdgeCostFunc):
@@ -121,12 +104,18 @@ class EdgeCostCallback(_EdgeCostFunc):
.. versionchanged:: 5.0
Now only accepts a `shape` argument instead of `width` and `height`.
"""
+
_CALLBACK_P = lib._pycall_path_simple
- def __init__(self, callback, shape):
- # type: (Callable[[int, int, int, int], float], Tuple[int, int])
+ def __init__(
+ self,
+ callback: Callable[[int, int, int, int], float],
+ shape: tuple[int, int],
+ ) -> None:
+ """Initialize this callback."""
self.callback = callback
- super(EdgeCostCallback, self).__init__(callback, shape)
+ super().__init__(callback, shape)
+
class NodeCostArray(np.ndarray):
"""Calculate cost from a numpy array of nodes.
@@ -135,96 +124,94 @@ class NodeCostArray(np.ndarray):
A cost of 0 means the node is blocking.
"""
- _C_ARRAY_CALLBACKS = {
- np.float32: ('float*', _get_pathcost_func('PathCostArrayFloat32')),
- np.bool_: ('int8_t*', _get_pathcost_func('PathCostArrayInt8')),
- np.int8: ('int8_t*', _get_pathcost_func('PathCostArrayInt8')),
- np.uint8: ('uint8_t*', _get_pathcost_func('PathCostArrayUInt8')),
- np.int16: ('int16_t*', _get_pathcost_func('PathCostArrayInt16')),
- np.uint16: ('uint16_t*', _get_pathcost_func('PathCostArrayUInt16')),
- np.int32: ('int32_t*', _get_pathcost_func('PathCostArrayInt32')),
- np.uint32: ('uint32_t*', _get_pathcost_func('PathCostArrayUInt32')),
- }
-
- def __new__(cls, array):
+ _C_ARRAY_CALLBACKS: Final = {
+ np.float32: ("float*", _get_path_cost_func("PathCostArrayFloat32")),
+ np.bool_: ("int8_t*", _get_path_cost_func("PathCostArrayInt8")),
+ np.int8: ("int8_t*", _get_path_cost_func("PathCostArrayInt8")),
+ np.uint8: ("uint8_t*", _get_path_cost_func("PathCostArrayUInt8")),
+ np.int16: ("int16_t*", _get_path_cost_func("PathCostArrayInt16")),
+ np.uint16: ("uint16_t*", _get_path_cost_func("PathCostArrayUInt16")),
+ np.int32: ("int32_t*", _get_path_cost_func("PathCostArrayInt32")),
+ np.uint32: ("uint32_t*", _get_path_cost_func("PathCostArrayUInt32")),
+ }
+
+ def __new__(cls, array: ArrayLike) -> Self:
"""Validate a numpy array and setup a C callback."""
- self = np.asarray(array).view(cls)
- return self
-
- def __repr__(self):
- return '%s(%r)' % (self.__class__.__name__,
- repr(self.view(np.ndarray)))
-
- def get_tcod_path_ffi(self):
- # type: () -> Tuple[cffi.CData, cffi.CData, Tuple[int, int]]
- if len(self.shape) != 2:
- raise ValueError('Array must have a 2d shape, shape is %r' %
- (self.shape,))
+ return np.asarray(array).view(cls)
+
+ def __repr__(self) -> str:
+ """Return the string representation of this object."""
+ return f"{self.__class__.__name__}({repr(self.view(np.ndarray))!r})"
+
+ def get_tcod_path_ffi(self) -> tuple[Any, Any, tuple[int, int]]:
+ if len(self.shape) != 2: # noqa: PLR2004
+ msg = f"Array must have a 2d shape, shape is {self.shape!r}"
+ raise ValueError(msg)
if self.dtype.type not in self._C_ARRAY_CALLBACKS:
- raise ValueError('dtype must be one of %r, dtype is %r' %
- (self._C_ARRAY_CALLBACKS.keys(), self.dtype.type))
+ msg = f"dtype must be one of {self._C_ARRAY_CALLBACKS.keys()!r}, dtype is {self.dtype.type!r}"
+ raise ValueError(msg)
- array_type, callback = \
- self._C_ARRAY_CALLBACKS[self.dtype.type]
+ _array_type, callback = self._C_ARRAY_CALLBACKS[self.dtype.type]
userdata = ffi.new(
- 'struct PathCostArray*',
- (ffi.cast('char*', self.ctypes.data), self.strides),
+ "struct PathCostArray*",
+ (ffi.cast("char*", self.ctypes.data), self.strides),
)
- return callback, userdata, self.shape
+ return callback, userdata, (self.shape[0], self.shape[1])
-class _PathFinder(object):
+class _PathFinder:
"""A class sharing methods used by AStar and Dijkstra."""
- def __init__(self, cost, diagonal=1.41):
- # type: (Union[tcod.map.Map, numpy.ndarray, Any], float)
+ def __init__(self, cost: tcod.map.Map | ArrayLike | _EdgeCostFunc, diagonal: float = 1.41) -> None:
self.cost = cost
self.diagonal = diagonal
- self._path_c = None
+ self._path_c: Any = None
self._callback = self._userdata = None
- if hasattr(self.cost, 'map_c'):
+ if isinstance(self.cost, tcod.map.Map):
self.shape = self.cost.width, self.cost.height
self._path_c = ffi.gc(
self._path_new_using_map(self.cost.map_c, diagonal),
self._path_delete,
- )
+ )
return
- if not hasattr(self.cost, 'get_tcod_path_ffi'):
- assert not callable(self.cost), \
- "Any callback alone is missing shape information. " \
- "Wrap your callback in tcod.path.EdgeCostCallback"
+ if not isinstance(self.cost, _EdgeCostFunc):
+ assert not callable(self.cost), (
+ "Any callback alone is missing shape information. Wrap your callback in tcod.path.EdgeCostCallback"
+ )
self.cost = NodeCostArray(self.cost)
- self._callback, self._userdata, self.shape = \
- self.cost.get_tcod_path_ffi()
+ (
+ self._callback,
+ self._userdata,
+ self.shape,
+ ) = self.cost.get_tcod_path_ffi()
self._path_c = ffi.gc(
self._path_new_using_function(
self.cost.shape[0],
self.cost.shape[1],
self._callback,
self._userdata,
- diagonal
- ),
+ diagonal,
+ ),
self._path_delete,
- )
+ )
- def __repr__(self):
- return '%s(cost=%r, diagonal=%r)' % (self.__class__.__name__,
- self.cost, self.diagonal)
+ def __repr__(self) -> str:
+ return f"{self.__class__.__name__}(cost={self.cost!r}, diagonal={self.diagonal!r})"
- def __getstate__(self):
+ def __getstate__(self) -> dict[str, Any]:
state = self.__dict__.copy()
- del state['_path_c']
- del state['shape']
- del state['_callback']
- del state['_userdata']
+ del state["_path_c"]
+ del state["shape"]
+ del state["_callback"]
+ del state["_userdata"]
return state
- def __setstate__(self, state):
+ def __setstate__(self, state: dict[str, Any]) -> None:
self.__dict__.update(state)
- self.__init__(self.cost, self.diagonal)
+ _PathFinder.__init__(self, self.cost, self.diagonal)
_path_new_using_map = lib.TCOD_path_new_using_map
_path_new_using_function = lib.TCOD_path_new_using_function
@@ -232,15 +219,15 @@ def __setstate__(self, state):
class AStar(_PathFinder):
- """
+ """The older libtcod A* pathfinder.
+
Args:
cost (Union[tcod.map.Map, numpy.ndarray, Any]):
diagonal (float): Multiplier for diagonal movement.
A value of 0 will disable diagonal movement entirely.
"""
- def get_path(self, start_x, start_y, goal_x, goal_y):
- # type: (int, int, int, int) -> List[Tuple[int, int]]
+ def get_path(self, start_x: int, start_y: int, goal_x: int, goal_y: int) -> list[tuple[int, int]]:
"""Return a list of (x, y) steps to reach the goal point, if possible.
Args:
@@ -248,21 +235,23 @@ def get_path(self, start_x, start_y, goal_x, goal_y):
start_y (int): Starting Y position.
goal_x (int): Destination X position.
goal_y (int): Destination Y position.
+
Returns:
List[Tuple[int, int]]:
A list of points, or an empty list if there is no valid path.
"""
lib.TCOD_path_compute(self._path_c, start_x, start_y, goal_x, goal_y)
path = []
- x = ffi.new('int[2]')
+ x = ffi.new("int[2]")
y = x + 1
- while lib.TCOD_path_walk(self._path_c, x, y, False):
+ while lib.TCOD_path_walk(self._path_c, x, y, False): # noqa: FBT003
path.append((x[0], y[0]))
return path
class Dijkstra(_PathFinder):
- """
+ """The older libtcod Dijkstra pathfinder.
+
Args:
cost (Union[tcod.map.Map, numpy.ndarray, Any]):
diagonal (float): Multiplier for diagonal movement.
@@ -273,20 +262,1296 @@ class Dijkstra(_PathFinder):
_path_new_using_function = lib.TCOD_dijkstra_new_using_function
_path_delete = lib.TCOD_dijkstra_delete
- def set_goal(self, x, y):
- # type: (int, int) -> None
- """Set the goal point and recompute the Dijkstra path-finder.
- """
+ def set_goal(self, x: int, y: int) -> None:
+ """Set the goal point and recompute the Dijkstra path-finder."""
lib.TCOD_dijkstra_compute(self._path_c, x, y)
- def get_path(self, x, y):
- # type: (int, int) -> List[Tuple[int, int]]
- """Return a list of (x, y) steps to reach the goal point, if possible.
- """
+ def get_path(self, x: int, y: int) -> list[tuple[int, int]]:
+ """Return a list of (x, y) steps to reach the goal point, if possible."""
lib.TCOD_dijkstra_path_set(self._path_c, x, y)
path = []
- pointer_x = ffi.new('int[2]')
+ pointer_x = ffi.new("int[2]")
pointer_y = pointer_x + 1
while lib.TCOD_dijkstra_path_walk(self._path_c, pointer_x, pointer_y):
path.append((pointer_x[0], pointer_y[0]))
return path
+
+
+_INT_TYPES = {
+ np.bool_: lib.np_uint8,
+ np.int8: lib.np_int8,
+ np.int16: lib.np_int16,
+ np.int32: lib.np_int32,
+ np.intc: lib.np_int32,
+ np.int64: lib.np_int64,
+ np.uint8: lib.np_uint8,
+ np.uint16: lib.np_uint16,
+ np.uint32: lib.np_uint32,
+ np.uint64: lib.np_uint64,
+}
+
+
+def maxarray(
+ shape: tuple[int, ...],
+ dtype: DTypeLike = np.int32,
+ order: Literal["C", "F"] = "C",
+) -> NDArray[Any]:
+ """Return a new array filled with the maximum finite value for `dtype`.
+
+ `shape` is of the new array. Same as other NumPy array initializers.
+
+ `dtype` should be a single NumPy integer type.
+
+ `order` can be "C" or "F".
+
+ This works the same as
+ ``np.full(shape, np.iinfo(dtype).max, dtype, order)``.
+
+ This kind of array is an ideal starting point for distance maps. Just set
+ any point to a lower value such as 0 and then pass this array to a
+ function such as :any:`dijkstra2d`.
+ """
+ return np.full(shape, np.iinfo(dtype).max, dtype, order)
+
+
+def _export_dict(array: NDArray[Any]) -> dict[str, Any]:
+ """Convert a NumPy array into a format compatible with CFFI."""
+ if array.dtype.type not in _INT_TYPES:
+ msg = f"dtype was {array.dtype.type}, but must be one of {tuple(_INT_TYPES.keys())}."
+ raise TypeError(msg)
+ return {
+ "type": _INT_TYPES[array.dtype.type],
+ "ndim": array.ndim,
+ "data": ffi.cast("void*", array.ctypes.data),
+ "shape": array.shape,
+ "strides": array.strides,
+ }
+
+
+def _export(array: NDArray[np.integer]) -> Any: # noqa: ANN401
+ """Convert a NumPy array into a cffi object."""
+ return ffi.new("struct NArray*", _export_dict(array))
+
+
+def _compile_cost_edges(edge_map: ArrayLike) -> tuple[NDArray[np.intc], int]:
+ """Return an edge_cost array using an integer map."""
+ edge_map = np.array(edge_map, copy=True)
+ if edge_map.ndim != 2: # noqa: PLR2004
+ msg = f"edge_map must be 2 dimensional. (Got {edge_map.ndim})"
+ raise ValueError(msg)
+ edge_center = edge_map.shape[0] // 2, edge_map.shape[1] // 2
+ edge_map[edge_center] = 0
+ edge_map[edge_map < 0] = 0
+ edge_nz = edge_map.nonzero()
+ edge_array = np.transpose(edge_nz)
+ edge_array -= edge_center
+ c_edges = ffi.new("int[]", len(edge_array) * 3)
+ edges = np.frombuffer(ffi.buffer(c_edges), dtype=np.intc).reshape(len(edge_array), 3)
+ edges[:, :2] = edge_array
+ edges[:, 2] = edge_map[edge_nz]
+ return c_edges, len(edge_array)
+
+
+def dijkstra2d( # noqa: PLR0913
+ distance: ArrayLike,
+ cost: ArrayLike,
+ cardinal: int | None = None,
+ diagonal: int | None = None,
+ *,
+ edge_map: ArrayLike | None = None,
+ out: NDArray[np.integer] | None = ..., # type: ignore[assignment, unused-ignore]
+) -> NDArray[np.integer]:
+ """Return the computed distance of all nodes on a 2D Dijkstra grid.
+
+ `distance` is an input array of node distances. Is this often an
+ array filled with maximum finite values and 1 or more points with a low
+ value such as 0. Distance will flow from these low values to adjacent
+ nodes based the cost to reach those nodes.
+
+ `cost` is an array of node costs. Any node with a cost less than or equal
+ to 0 is considered blocked off. Positive values are the distance needed to
+ reach that node.
+
+ `cardinal` and `diagonal` are the cost multipliers for edges in those
+ directions. A value of None or 0 will disable those directions. Typical
+ values could be: ``1, None``, ``1, 1``, ``2, 3``, etc.
+
+ `edge_map` is a 2D array of edge costs with the origin point centered on
+ the array. This can be used to define the edges used from one node to
+ another. This parameter can be hard to understand so you should see how
+ it's used in the examples.
+
+ `out` is the array to fill with the computed Dijkstra distance map.
+ Having `out` be the same as `distance` will modify the array in-place,
+ which is normally the fastest option.
+ If `out` is `None` then the result is returned as a new array.
+
+ Example::
+
+ >>> import numpy as np
+ >>> import tcod
+ >>> cost = np.ones((3, 3), dtype=np.uint8)
+ >>> cost[:2, 1] = 0
+ >>> cost
+ array([[1, 0, 1],
+ [1, 0, 1],
+ [1, 1, 1]], dtype=uint8)
+ >>> dist = tcod.path.maxarray((3, 3), dtype=np.int32)
+ >>> dist[0, 0] = 0
+ >>> dist
+ array([[ 0, 2147483647, 2147483647],
+ [2147483647, 2147483647, 2147483647],
+ [2147483647, 2147483647, 2147483647]]...)
+ >>> tcod.path.dijkstra2d(dist, cost, 2, 3, out=dist)
+ array([[ 0, 2147483647, 10],
+ [ 2, 2147483647, 8],
+ [ 4, 5, 7]]...)
+ >>> path = tcod.path.hillclimb2d(dist, (2, 2), True, True)
+ >>> path
+ array([[2, 2],
+ [2, 1],
+ [1, 0],
+ [0, 0]], dtype=int32)
+ >>> path = path[::-1].tolist()
+ >>> while path:
+ ... print(path.pop(0))
+ [0, 0]
+ [1, 0]
+ [2, 1]
+ [2, 2]
+
+ `edge_map` is used for more complicated graphs. The following example
+ uses a 'knight move' edge map.
+
+ Example::
+
+ >>> import numpy as np
+ >>> import tcod
+ >>> knight_moves = [
+ ... [0, 1, 0, 1, 0],
+ ... [1, 0, 0, 0, 1],
+ ... [0, 0, 0, 0, 0],
+ ... [1, 0, 0, 0, 1],
+ ... [0, 1, 0, 1, 0],
+ ... ]
+ >>> dist = tcod.path.maxarray((8, 8))
+ >>> dist[0,0] = 0
+ >>> cost = np.ones((8, 8), int)
+ >>> tcod.path.dijkstra2d(dist, cost, edge_map=knight_moves, out=dist)
+ array([[0, 3, 2, 3, 2, 3, 4, 5],
+ [3, 4, 1, 2, 3, 4, 3, 4],
+ [2, 1, 4, 3, 2, 3, 4, 5],
+ [3, 2, 3, 2, 3, 4, 3, 4],
+ [2, 3, 2, 3, 4, 3, 4, 5],
+ [3, 4, 3, 4, 3, 4, 5, 4],
+ [4, 3, 4, 3, 4, 5, 4, 5],
+ [5, 4, 5, 4, 5, 4, 5, 6]]...)
+ >>> tcod.path.hillclimb2d(dist, (7, 7), edge_map=knight_moves)
+ array([[7, 7],
+ [5, 6],
+ [3, 5],
+ [1, 4],
+ [0, 2],
+ [2, 1],
+ [0, 0]], dtype=int32)
+
+ `edge_map` can also be used to define a hex-grid.
+ See https://www.redblobgames.com/grids/hexagons/ for more info.
+ The following example is using axial coordinates.
+
+ Example::
+
+ hex_edges = [
+ [0, 1, 1],
+ [1, 0, 1],
+ [1, 1, 0],
+ ]
+
+ .. versionadded:: 11.2
+
+ .. versionchanged:: 11.13
+ Added the `edge_map` parameter.
+
+ .. versionchanged:: 12.1
+ Added `out` parameter. Now returns the output array.
+ """
+ dist: NDArray[Any] = np.asarray(distance)
+ if out is ...: # type: ignore[comparison-overlap]
+ out = dist
+ warnings.warn(
+ "No `out` parameter was given. "
+ "Currently this modifies the distance array in-place, but this "
+ "will change in the future to return a copy instead. "
+ "To ensure the existing behavior is kept you must add an `out` "
+ "parameter with the same array as the `distance` parameter.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ elif out is None:
+ out = np.array(distance, copy=True)
+ else:
+ out[...] = dist
+
+ if dist.shape != out.shape:
+ msg = f"distance and output must have the same shape {dist.shape!r} != {out.shape!r}"
+ raise TypeError(msg)
+ cost = np.asarray(cost)
+ if dist.shape != cost.shape:
+ msg = f"output and cost must have the same shape {out.shape!r} != {cost.shape!r}"
+ raise TypeError(msg)
+ c_dist = _export(out)
+ if edge_map is not None:
+ if cardinal is not None or diagonal is not None:
+ msg = "`edge_map` can not be set at the same time as `cardinal` or `diagonal`."
+ raise TypeError(msg)
+ c_edges, n_edges = _compile_cost_edges(edge_map)
+ _check(lib.dijkstra2d(c_dist, _export(cost), n_edges, c_edges))
+ else:
+ if cardinal is None:
+ cardinal = 0
+ if diagonal is None:
+ diagonal = 0
+ _check(lib.dijkstra2d_basic(c_dist, _export(cost), cardinal, diagonal))
+ return out
+
+
+def _compile_bool_edges(edge_map: ArrayLike) -> tuple[Any, int]:
+ """Return an edge array using a boolean map."""
+ edge_map = np.array(edge_map, copy=True)
+ edge_center = edge_map.shape[0] // 2, edge_map.shape[1] // 2
+ edge_map[edge_center] = 0
+ edge_array = np.transpose(edge_map.nonzero())
+ edge_array -= edge_center
+ return ffi.new("int[]", list(edge_array.flat)), len(edge_array)
+
+
+def hillclimb2d(
+ distance: ArrayLike,
+ start: tuple[int, int],
+ cardinal: bool | None = None, # noqa: FBT001
+ diagonal: bool | None = None, # noqa: FBT001
+ *,
+ edge_map: ArrayLike | None = None,
+) -> NDArray[np.intc]:
+ """Return a path on a grid from `start` to the lowest point.
+
+ `distance` should be a fully computed distance array. This kind of array
+ is returned by :any:`dijkstra2d`.
+
+ `start` is a 2-item tuple with starting coordinates. The axes if these
+ coordinates should match the axis of the `distance` array.
+ An out-of-bounds `start` index will raise an IndexError.
+
+ At each step nodes adjacent toe current will be checked for a value lower
+ than the current one. Which directions are checked is decided by the
+ boolean values `cardinal` and `diagonal`. This process is repeated until
+ all adjacent nodes are equal to or larger than the last point on the path.
+
+ If `edge_map` was used with :any:`tcod.path.dijkstra2d` then it should be
+ reused for this function. Keep in mind that `edge_map` must be
+ bidirectional since hill-climbing will traverse the map backwards.
+
+ The returned array is a 2D NumPy array with the shape: (length, axis).
+ This array always includes both the starting and ending point and will
+ always have at least one item.
+
+ Typical uses of the returned array will be to either convert it into a list
+ which can be popped from, or transpose it and convert it into a tuple which
+ can be used to index other arrays using NumPy's advanced indexing rules.
+
+ .. versionadded:: 11.2
+
+ .. versionchanged:: 11.13
+ Added `edge_map` parameter.
+ """
+ x, y = start
+ dist: NDArray[Any] = np.asarray(distance)
+ if not (0 <= x < dist.shape[0] and 0 <= y < dist.shape[1]):
+ msg = f"Starting point {start!r} not in shape {dist.shape!r}"
+ raise IndexError(msg)
+ c_dist = _export(dist)
+ if edge_map is not None:
+ if cardinal is not None or diagonal is not None:
+ msg = "`edge_map` can not be set at the same time as `cardinal` or `diagonal`."
+ raise TypeError(msg)
+ c_edges, n_edges = _compile_bool_edges(edge_map)
+ func = functools.partial(lib.hillclimb2d, c_dist, x, y, n_edges, c_edges)
+ else:
+ func = functools.partial(lib.hillclimb2d_basic, c_dist, x, y, bool(cardinal), bool(diagonal))
+ length = _check(func(ffi.NULL))
+ path: np.ndarray[Any, np.dtype[np.intc]] = np.ndarray((length, 2), dtype=np.intc)
+ c_path = ffi.from_buffer("int*", path)
+ _check(func(c_path))
+ return path
+
+
+def _world_array(shape: tuple[int, ...], dtype: DTypeLike = np.int32) -> NDArray[Any]:
+ """Return an array where ``ij == arr[ij]``."""
+ return np.ascontiguousarray(
+ np.transpose(
+ np.meshgrid(
+ *(np.arange(i, dtype=dtype) for i in shape),
+ indexing="ij",
+ copy=False,
+ ),
+ axes=(*range(1, len(shape) + 1), 0),
+ )
+ )
+
+
+def _as_hashable(obj: NDArray[Any] | None) -> object | None:
+ """Return NumPy arrays as a more hashable form."""
+ if obj is None:
+ return obj
+ return obj.ctypes.data, tuple(obj.shape), tuple(obj.strides)
+
+
+class CustomGraph:
+ """A customizable graph defining how a pathfinder traverses the world.
+
+ If you only need to path over a 2D array with typical edge rules then you
+ should use :any:`SimpleGraph`.
+ This is an advanced interface for defining custom edge rules which would
+ allow things such as 3D movement.
+
+ The graph is created with a `shape` defining the size and number of
+ dimensions of the graph. The `shape` can only be 4 dimensions or lower.
+
+ `order` determines what style of indexing the interface expects.
+ This is inherited by the pathfinder and will affect the `ij/xy` indexing
+ order of all methods in the graph and pathfinder objects.
+ The default order of `"C"` is for `ij` indexing.
+ The `order` can be set to `"F"` for `xy` indexing.
+
+ After this graph is created you'll need to add edges which define the
+ rules of the pathfinder. These rules usually define movement in the
+ cardinal and diagonal directions, but can also include stairway type edges.
+ :any:`set_heuristic` should also be called so that the pathfinder will use
+ A*.
+
+ After all edge rules are added the graph can be used to make one or more
+ :any:`Pathfinder` instances.
+
+ Example::
+
+ >>> import numpy as np
+ >>> import tcod
+ >>> graph = tcod.path.CustomGraph((5, 5))
+ >>> cost = np.ones((5, 5), dtype=np.int8)
+ >>> CARDINAL = [
+ ... [0, 1, 0],
+ ... [1, 0, 1],
+ ... [0, 1, 0],
+ ... ]
+ >>> graph.add_edges(edge_map=CARDINAL, cost=cost)
+ >>> pf = tcod.path.Pathfinder(graph)
+ >>> pf.add_root((0, 0))
+ >>> pf.resolve()
+ >>> pf.distance
+ array([[0, 1, 2, 3, 4],
+ [1, 2, 3, 4, 5],
+ [2, 3, 4, 5, 6],
+ [3, 4, 5, 6, 7],
+ [4, 5, 6, 7, 8]]...)
+ >>> pf.path_to((3, 3))
+ array([[0, 0],
+ [0, 1],
+ [1, 1],
+ [2, 1],
+ [2, 2],
+ [2, 3],
+ [3, 3]]...)
+
+ .. versionadded:: 11.13
+
+ .. versionchanged:: 11.15
+ Added the `order` parameter.
+ """
+
+ def __init__(self, shape: tuple[int, ...], *, order: str = "C") -> None:
+ """Initialize the custom graph."""
+ self._shape = self._shape_c = tuple(shape)
+ self._ndim = len(self._shape)
+ self._order = order
+ if self._order == "F":
+ self._shape_c = self._shape_c[::-1]
+ if not 0 < self._ndim <= 4: # noqa: PLR2004
+ msg = "Graph dimensions must be 1 <= n <= 4."
+ raise TypeError(msg)
+ self._graph: dict[tuple[Any, ...], dict[str, Any]] = {}
+ self._edge_rules_keep_alive: list[Any] = []
+ self._edge_rules_p: Any = None
+ self._heuristic: tuple[int, int, int, int] | None = None
+
+ @property
+ def ndim(self) -> int:
+ """Return the number of dimensions."""
+ return self._ndim
+
+ @property
+ def shape(self) -> tuple[int, ...]:
+ """Return the shape of this graph."""
+ return self._shape
+
+ def add_edge(
+ self,
+ edge_dir: tuple[int, ...],
+ edge_cost: int = 1,
+ *,
+ cost: NDArray[Any],
+ condition: ArrayLike | None = None,
+ ) -> None:
+ """Add a single edge rule.
+
+ `edge_dir` is a tuple with the same length as the graphs dimensions.
+ The edge is relative to any node.
+
+ `edge_cost` is the cost multiplier of the edge. Its multiplied with the
+ `cost` array to the edges actual cost.
+
+ `cost` is a NumPy array where each node has the cost for movement into
+ that node. Zero or negative values are used to mark blocked areas.
+
+ `condition` is an optional array to mark which nodes have this edge.
+ If the node in `condition` is zero then the edge will be skipped.
+ This is useful to mark portals or stairs for some edges.
+
+ The expected indexing for `edge_dir`, `cost`, and `condition` depend
+ on the graphs `order`.
+
+ Example::
+
+ >>> import numpy as np
+ >>> import tcod
+ >>> graph3d = tcod.path.CustomGraph((2, 5, 5))
+ >>> cost = np.ones((2, 5, 5), dtype=np.int8)
+ >>> up_stairs = np.zeros((2, 5, 5), dtype=np.int8)
+ >>> down_stairs = np.zeros((2, 5, 5), dtype=np.int8)
+ >>> up_stairs[0, 0, 4] = 1
+ >>> down_stairs[1, 0, 4] = 1
+ >>> CARDINAL = [[0, 1, 0], [1, 0, 1], [0, 1, 0]]
+ >>> graph3d.add_edges(edge_map=CARDINAL, cost=cost)
+ >>> graph3d.add_edge((1, 0, 0), 1, cost=cost, condition=up_stairs)
+ >>> graph3d.add_edge((-1, 0, 0), 1, cost=cost, condition=down_stairs)
+ >>> pf3d = tcod.path.Pathfinder(graph3d)
+ >>> pf3d.add_root((0, 1, 1))
+ >>> pf3d.path_to((1, 2, 2))
+ array([[0, 1, 1],
+ [0, 1, 2],
+ [0, 1, 3],
+ [0, 0, 3],
+ [0, 0, 4],
+ [1, 0, 4],
+ [1, 1, 4],
+ [1, 1, 3],
+ [1, 2, 3],
+ [1, 2, 2]]...)
+
+ Note in the above example that both sets of up/down stairs were added,
+ but bidirectional edges are not a requirement for the graph.
+ One directional edges such as pits can be added which will
+ only allow movement outwards from the root nodes of the pathfinder.
+ """
+ self._edge_rules_p = None
+ edge_dir = tuple(edge_dir)
+ cost = np.asarray(cost)
+ if len(edge_dir) != self._ndim:
+ msg = f"edge_dir must have exactly {self._ndim} items, got {edge_dir!r}"
+ raise TypeError(msg)
+ if edge_cost <= 0:
+ msg = f"edge_cost must be greater than zero, got {edge_cost!r}"
+ raise ValueError(msg)
+ if cost.shape != self._shape:
+ msg = f"cost array must be shape {self._shape!r}, got {cost.shape!r}"
+ raise TypeError(msg)
+ if condition is not None:
+ condition = np.asarray(condition)
+ if condition.shape != self._shape:
+ msg = f"condition array must be shape {self._shape!r}, got {condition.shape!r}"
+ raise TypeError(msg)
+ if self._order == "F":
+ # Inputs need to be converted to C.
+ edge_dir = edge_dir[::-1]
+ cost = cost.T
+ if condition is not None:
+ condition = condition.T
+ key = (_as_hashable(cost), _as_hashable(condition)) # type: ignore[arg-type]
+ try:
+ rule = self._graph[key]
+ except KeyError:
+ rule = self._graph[key] = {
+ "cost": cost,
+ "edge_list": [],
+ }
+ if condition is not None:
+ rule["condition"] = condition
+ edge = (*edge_dir, edge_cost)
+ if edge not in rule["edge_list"]:
+ rule["edge_list"].append(edge)
+
+ def add_edges(
+ self,
+ *,
+ edge_map: ArrayLike | NDArray[np.integer],
+ cost: NDArray[Any],
+ condition: ArrayLike | None = None,
+ ) -> None:
+ """Add a rule with multiple edges.
+
+ `edge_map` is a NumPy array mapping the edges and their costs.
+ This is easier to understand by looking at the examples below.
+ Edges are relative to center of the array. The center most value is
+ always ignored. If `edge_map` has fewer dimensions than the graph then
+ it will apply to the right-most axes of the graph.
+
+ `cost` is a NumPy array where each node has the cost for movement into
+ that node. Zero or negative values are used to mark blocked areas.
+
+ `condition` is an optional array to mark which nodes have this edge.
+ See :any:`add_edge`.
+ If `condition` is the same array as `cost` then the pathfinder will
+ not move into open area from a non-open ones.
+
+ The expected indexing for `edge_map`, `cost`, and `condition` depend
+ on the graphs `order`. You may need to transpose the examples below
+ if you're using `xy` indexing.
+
+ Example::
+
+ # 2D edge maps:
+ CARDINAL = [ # Simple arrow-key moves. Manhattan distance.
+ [0, 1, 0],
+ [1, 0, 1],
+ [0, 1, 0],
+ ]
+ CHEBYSHEV = [ # Chess king moves. Chebyshev distance.
+ [1, 1, 1],
+ [1, 0, 1],
+ [1, 1, 1],
+ ]
+ EUCLIDEAN = [ # Approximate euclidean distance.
+ [99, 70, 99],
+ [70, 0, 70],
+ [99, 70, 99],
+ ]
+ EUCLIDEAN_SIMPLE = [ # Very approximate euclidean distance.
+ [3, 2, 3],
+ [2, 0, 2],
+ [3, 2, 3],
+ ]
+ KNIGHT_MOVE = [ # Chess knight L-moves.
+ [0, 1, 0, 1, 0],
+ [1, 0, 0, 0, 1],
+ [0, 0, 0, 0, 0],
+ [1, 0, 0, 0, 1],
+ [0, 1, 0, 1, 0],
+ ]
+ AXIAL = [ # https://www.redblobgames.com/grids/hexagons/
+ [0, 1, 1],
+ [1, 0, 1],
+ [1, 1, 0],
+ ]
+ # 3D edge maps:
+ CARDINAL_PLUS_Z = [ # Cardinal movement with Z up/down edges.
+ [
+ [0, 0, 0],
+ [0, 1, 0],
+ [0, 0, 0],
+ ],
+ [
+ [0, 1, 0],
+ [1, 0, 1],
+ [0, 1, 0],
+ ],
+ [
+ [0, 0, 0],
+ [0, 1, 0],
+ [0, 0, 0],
+ ],
+ ]
+ CHEBYSHEV_3D = [ # Chebyshev distance, but in 3D.
+ [
+ [1, 1, 1],
+ [1, 1, 1],
+ [1, 1, 1],
+ ],
+ [
+ [1, 1, 1],
+ [1, 0, 1],
+ [1, 1, 1],
+ ],
+ [
+ [1, 1, 1],
+ [1, 1, 1],
+ [1, 1, 1],
+ ],
+ ]
+ """
+ edge_map = np.array(edge_map, copy=True)
+ if edge_map.ndim < self._ndim:
+ edge_map = np.asarray(edge_map[(np.newaxis,) * (self._ndim - edge_map.ndim)])
+ if edge_map.ndim != self._ndim:
+ msg = f"edge_map must must match graph dimensions ({self.ndim}). (Got {edge_map.ndim})"
+ raise TypeError(msg)
+ if self._order == "F":
+ # edge_map needs to be converted into C.
+ # The other parameters are converted by the add_edge method.
+ edge_map = edge_map.T
+ edge_center = tuple(i // 2 for i in edge_map.shape) # type: ignore[union-attr]
+ edge_map[edge_center] = 0 # type: ignore[index]
+ edge_map[edge_map < 0] = 0 # type: ignore[index, operator]
+ edge_nz = edge_map.nonzero() # type: ignore[union-attr]
+ edge_costs = edge_map[edge_nz] # type: ignore[index]
+ edge_array = np.transpose(edge_nz)
+ edge_array -= edge_center
+ for edge, edge_cost in zip(
+ edge_array,
+ edge_costs, # type: ignore[arg-type]
+ strict=True,
+ ):
+ self.add_edge(
+ tuple(edge.tolist()),
+ edge_cost,
+ cost=cost,
+ condition=condition,
+ )
+
+ def set_heuristic(self, *, cardinal: int = 0, diagonal: int = 0, z: int = 0, w: int = 0) -> None:
+ """Set a pathfinder heuristic so that pathfinding can done with A*.
+
+ `cardinal`, `diagonal`, `z, and `w` are the lower-bound cost of
+ movement in those directions. Values above the lower-bound can be
+ used to create a greedy heuristic, which will be faster at the cost of
+ accuracy.
+
+ Example::
+
+ >>> import numpy as np
+ >>> import tcod
+ >>> graph = tcod.path.CustomGraph((5, 5))
+ >>> cost = np.ones((5, 5), dtype=np.int8)
+ >>> EUCLIDEAN = [[99, 70, 99], [70, 0, 70], [99, 70, 99]]
+ >>> graph.add_edges(edge_map=EUCLIDEAN, cost=cost)
+ >>> graph.set_heuristic(cardinal=70, diagonal=99)
+ >>> pf = tcod.path.Pathfinder(graph)
+ >>> pf.add_root((0, 0))
+ >>> pf.path_to((4, 4))
+ array([[0, 0],
+ [1, 1],
+ [2, 2],
+ [3, 3],
+ [4, 4]]...)
+ >>> pf.distance
+ array([[ 0, 70, 198, 2147483647, 2147483647],
+ [ 70, 99, 169, 297, 2147483647],
+ [ 198, 169, 198, 268, 396],
+ [2147483647, 297, 268, 297, 367],
+ [2147483647, 2147483647, 396, 367, 396]]...)
+ >>> pf.path_to((2, 0))
+ array([[0, 0],
+ [1, 0],
+ [2, 0]]...)
+ >>> pf.distance
+ array([[ 0, 70, 198, 2147483647, 2147483647],
+ [ 70, 99, 169, 297, 2147483647],
+ [ 140, 169, 198, 268, 396],
+ [ 210, 239, 268, 297, 367],
+ [2147483647, 2147483647, 396, 367, 396]]...)
+
+ Without a heuristic the above example would need to evaluate the entire
+ array to reach the opposite side of it.
+ With a heuristic several nodes can be skipped, which will process
+ faster. Some of the distances in the above example look incorrect,
+ that's because those nodes are only partially evaluated, but
+ pathfinding to those nodes will work correctly as long as the heuristic
+ isn't greedy.
+ """
+ if 0 == cardinal == diagonal == z == w:
+ self._heuristic = None
+ if diagonal and cardinal > diagonal:
+ msg = "Diagonal parameter can not be lower than cardinal."
+ raise ValueError(msg)
+ if cardinal < 0 or diagonal < 0 or z < 0 or w < 0:
+ msg = "Parameters can not be set to negative values."
+ raise ValueError(msg)
+ self._heuristic = (cardinal, diagonal, z, w)
+
+ def _compile_rules(self) -> Any: # noqa: ANN401
+ """Compile this graph into a C struct array."""
+ if not self._edge_rules_p:
+ self._edge_rules_keep_alive = []
+ rules = []
+ for rule_ in self._graph.values():
+ rule = rule_.copy()
+ rule["edge_count"] = len(rule["edge_list"])
+ # Edge rule format: [i, j, cost, ...] etc.
+ edge_obj = ffi.new("int[]", len(rule["edge_list"]) * (self._ndim + 1))
+ edge_obj[0 : len(edge_obj)] = itertools.chain(*rule["edge_list"])
+ self._edge_rules_keep_alive.append(edge_obj)
+ rule["edge_array"] = edge_obj
+ self._edge_rules_keep_alive.append(rule["cost"])
+ rule["cost"] = _export_dict(rule["cost"])
+ if "condition" in rule:
+ self._edge_rules_keep_alive.append(rule["condition"])
+ rule["condition"] = _export_dict(rule["condition"])
+ del rule["edge_list"]
+ rules.append(rule)
+ self._edge_rules_p = ffi.new("struct PathfinderRule[]", rules)
+ return self._edge_rules_p, self._edge_rules_keep_alive
+
+ def _resolve(self, pathfinder: Pathfinder) -> None:
+ """Run the pathfinding algorithm for this graph."""
+ rules, _keep_alive = self._compile_rules()
+ _check(
+ lib.path_compute(
+ pathfinder._frontier_p,
+ _export(pathfinder._distance),
+ _export(pathfinder._travel),
+ len(rules),
+ rules,
+ pathfinder._heuristic_p,
+ )
+ )
+
+
+class SimpleGraph:
+ """A simple 2D graph implementation.
+
+ `cost` is a NumPy array where each node has the cost for movement into
+ that node. Zero or negative values are used to mark blocked areas.
+ A reference of this array is used. Any changes to the array will be
+ reflected in the graph.
+
+ `cardinal` and `diagonal` are the cost to move along the edges for those
+ directions. The total cost to move from one node to another is the `cost`
+ array value multiplied by the edge cost.
+ A value of zero will block that direction.
+
+ `greed` is used to define the heuristic.
+ To get the fastest accurate heuristic `greed` should be the lowest
+ non-zero value on the `cost` array.
+ Higher values may be used for an inaccurate but faster heuristic.
+
+ Example::
+
+ >>> import numpy as np
+ >>> import tcod
+ >>> cost = np.ones((5, 10), dtype=np.int8, order="F")
+ >>> graph = tcod.path.SimpleGraph(cost=cost, cardinal=2, diagonal=3)
+ >>> pf = tcod.path.Pathfinder(graph)
+ >>> pf.add_root((2, 4))
+ >>> pf.path_to((3, 7)).tolist()
+ [[2, 4], [2, 5], [2, 6], [3, 7]]
+
+ .. versionadded:: 11.15
+ """
+
+ def __init__(self, *, cost: ArrayLike, cardinal: int, diagonal: int, greed: int = 1) -> None:
+ """Initialize the graph."""
+ cost = np.asarray(cost)
+ if cost.ndim != 2: # noqa: PLR2004
+ msg = f"The cost array must e 2 dimensional, array of shape {cost.shape!r} given."
+ raise TypeError(msg)
+ if greed <= 0:
+ msg = f"Greed must be greater than zero, got {greed}"
+ raise ValueError(msg)
+ edge_map = (
+ (diagonal, cardinal, diagonal),
+ (cardinal, 0, cardinal),
+ (diagonal, cardinal, diagonal),
+ )
+ self._order = "C" if cost.strides[0] > cost.strides[1] else "F"
+ self._subgraph = CustomGraph(cost.shape, order=self._order)
+ self._ndim = 2
+ self._shape = self._subgraph._shape[0], self._subgraph._shape[1]
+ self._shape_c = self._subgraph._shape_c
+ self._subgraph.add_edges(edge_map=edge_map, cost=cost)
+ self.set_heuristic(cardinal=cardinal * greed, diagonal=diagonal * greed)
+
+ @property
+ def ndim(self) -> int:
+ """Number of dimensions."""
+ return 2
+
+ @property
+ def shape(self) -> tuple[int, int]:
+ """Shape of this graph."""
+ return self._shape
+
+ @property
+ def _heuristic(self) -> tuple[int, int, int, int] | None:
+ return self._subgraph._heuristic
+
+ def set_heuristic(self, *, cardinal: int, diagonal: int) -> None:
+ """Change the heuristic for this graph.
+
+ When created a :any:`SimpleGraph` will automatically have a heuristic.
+ So calling this method is often unnecessary.
+
+ `cardinal` and `diagonal` are weights for the heuristic.
+ Higher values are more greedy.
+ The default values are set to ``cardinal * greed`` and
+ ``diagonal * greed`` when the :any:`SimpleGraph` is created.
+ """
+ self._subgraph.set_heuristic(cardinal=cardinal, diagonal=diagonal)
+
+ def _resolve(self, pathfinder: Pathfinder) -> None:
+ self._subgraph._resolve(pathfinder)
+
+
+class Pathfinder:
+ """A generic modular pathfinder.
+
+ How the pathfinder functions depends on the graph provided. see
+ :any:`SimpleGraph` for how to set one up.
+
+ .. versionadded:: 11.13
+ """
+
+ def __init__(self, graph: CustomGraph | SimpleGraph) -> None:
+ """Initialize the pathfinder from a graph."""
+ self._graph = graph
+ self._order = graph._order
+ self._frontier_p = ffi.gc(lib.TCOD_frontier_new(self._graph._ndim), lib.TCOD_frontier_delete)
+ self._distance = maxarray(self._graph._shape_c)
+ self._travel = _world_array(self._graph._shape_c)
+ self._heuristic: tuple[int, int, int, int, tuple[int, ...]] | None = None
+ self._heuristic_p: Any = ffi.NULL
+
+ @property
+ def distance(self) -> NDArray[Any]:
+ """Distance values of the pathfinder.
+
+ The array returned from this property maintains the graphs `order`.
+
+ Unreachable or unresolved points will be at their maximum values.
+ You can use :any:`numpy.iinfo` if you need to check for these.
+
+ Example::
+
+ pf # Resolved Pathfinder instance.
+ reachable = pf.distance != numpy.iinfo(pf.distance.dtype).max
+ reachable # A boolean array of reachable area.
+
+ You may edit this array manually, but the pathfinder won't know of
+ your changes until :any:`rebuild_frontier` is called.
+ """
+ return self._distance.T if self._order == "F" else self._distance
+
+ @property
+ def traversal(self) -> NDArray[Any]:
+ """Array used to generate paths from any point to the nearest root.
+
+ The array returned from this property maintains the graphs `order`.
+ It has an extra dimension which includes the index of the next path.
+
+ Example::
+
+ # This example demonstrates the purpose of the traversal array.
+ >>> import tcod.path
+ >>> graph = tcod.path.SimpleGraph(
+ ... cost=np.ones((5, 5), np.int8), cardinal=2, diagonal=3,
+ ... )
+ >>> pf = tcod.path.Pathfinder(graph)
+ >>> pf.add_root((0, 0))
+ >>> pf.resolve()
+ >>> pf.traversal[3, 3].tolist() # Faster.
+ [2, 2]
+ >>> pf.path_from((3, 3))[1].tolist() # Slower.
+ [2, 2]
+ >>> i, j = (3, 3) # Starting index.
+ >>> path = [(i, j)] # List of nodes from the start to the root.
+ >>> while not (pf.traversal[i, j] == (i, j)).all():
+ ... i, j = pf.traversal[i, j].tolist()
+ ... path.append((i, j))
+ >>> path # Slower.
+ [(3, 3), (2, 2), (1, 1), (0, 0)]
+ >>> pf.path_from((3, 3)).tolist() # Faster.
+ [[3, 3], [2, 2], [1, 1], [0, 0]]
+
+
+ The above example is slow and will not detect infinite loops. Use
+ :any:`path_from` or :any:`path_to` when you need to get a path.
+
+ As the pathfinder is resolved this array is filled
+ """
+ if self._order == "F":
+ axes = range(self._travel.ndim)
+ return self._travel.transpose((*axes[-2::-1], axes[-1]))[..., ::-1] # type: ignore[no-any-return]
+ return self._travel
+
+ def clear(self) -> None:
+ """Reset the pathfinder to its initial state.
+
+ This sets all values on the :any:`distance` array to their maximum value.
+
+ Example::
+
+ >>> import tcod.path
+ >>> graph = tcod.path.SimpleGraph(
+ ... cost=np.ones((5, 5), np.int8), cardinal=2, diagonal=3,
+ ... )
+ >>> pf = tcod.path.Pathfinder(graph)
+ >>> pf.add_root((0, 0))
+ >>> pf.path_to((2, 2)).tolist()
+ [[0, 0], [1, 1], [2, 2]]
+ >>> pf.clear() # Reset Pathfinder to its initial state
+ >>> pf.add_root((0, 2))
+ >>> pf.path_to((2, 2)).tolist()
+ [[0, 2], [1, 2], [2, 2]]
+ """
+ self._distance[...] = np.iinfo(self._distance.dtype).max
+ self._travel = _world_array(self._graph._shape_c)
+ lib.TCOD_frontier_clear(self._frontier_p)
+
+ def add_root(self, index: tuple[int, ...], value: int = 0) -> None:
+ """Add a root node and insert it into the pathfinder frontier.
+
+ `index` is the root point to insert. The length of `index` must match
+ the dimensions of the graph.
+
+ `value` is the distance to use for this root. Zero is typical, but
+ if multiple roots are added they can be given different weights.
+ """
+ index = tuple(index) # Check for bad input.
+ if self._order == "F": # Convert to ij indexing order.
+ index = index[::-1]
+ if len(index) != self._distance.ndim:
+ msg = f"Index must be {self._distance.ndim} items, got {index!r}"
+ raise TypeError(msg)
+ self._distance[index] = value
+ self._update_heuristic(None)
+ lib.TCOD_frontier_push(self._frontier_p, index, value, value)
+
+ def _update_heuristic(self, goal_ij: tuple[int, ...] | None) -> bool:
+ """Update the active heuristic. Return True if the heuristic changed."""
+ if goal_ij is None:
+ heuristic = None
+ elif self._graph._heuristic is None:
+ heuristic = (0, 0, 0, 0, goal_ij)
+ else:
+ heuristic = (*self._graph._heuristic, goal_ij)
+ if self._heuristic == heuristic:
+ return False # Frontier does not need updating.
+ self._heuristic = heuristic
+ if heuristic is None:
+ self._heuristic_p = ffi.NULL
+ else:
+ self._heuristic_p = ffi.new("struct PathfinderHeuristic*", heuristic)
+ lib.update_frontier_heuristic(self._frontier_p, self._heuristic_p)
+ return True # Frontier was updated.
+
+ def rebuild_frontier(self) -> None:
+ """Reconstruct the frontier using the current distance array.
+
+ If you are using :any:`add_root` then you will not need to call this
+ function. This is only needed if the :any:`distance` array has been
+ modified manually.
+
+ After you are finished editing :any:`distance` you must call this
+ function before calling :any:`resolve` or any function which calls
+ :any:`resolve` implicitly such as :any:`path_from` or :any:`path_to`.
+
+ Example::
+
+ >>> import tcod.path
+ >>> graph = tcod.path.SimpleGraph(
+ ... cost=np.ones((5, 5), np.int8), cardinal=2, diagonal=3,
+ ... )
+ >>> pf = tcod.path.Pathfinder(graph)
+ >>> pf.distance[:, 0] = 0 # Set roots along entire left edge
+ >>> pf.rebuild_frontier()
+ >>> pf.path_to((0, 2)).tolist() # Finds best path from [:, 0]
+ [[0, 0], [0, 1], [0, 2]]
+ >>> pf.path_to((4, 2)).tolist()
+ [[4, 0], [4, 1], [4, 2]]
+ """
+ lib.TCOD_frontier_clear(self._frontier_p)
+ self._update_heuristic(None)
+ _check(lib.rebuild_frontier_from_distance(self._frontier_p, _export(self._distance)))
+
+ def resolve(self, goal: tuple[int, ...] | None = None) -> None:
+ """Manually run the pathfinder algorithm.
+
+ The :any:`path_from` and :any:`path_to` methods will automatically
+ call this method on demand.
+
+ If `goal` is `None` then this will attempt to complete the entire
+ :any:`distance` and :any:`traversal` arrays without a heuristic.
+ This is similar to Dijkstra.
+
+ If `goal` is given an index then it will attempt to resolve the
+ :any:`distance` and :any:`traversal` arrays only up to the `goal`.
+ If the graph has set a heuristic then it will be used with a process
+ similar to `A*`.
+
+ Example::
+
+ >>> import tcod.path
+ >>> graph = tcod.path.SimpleGraph(
+ ... cost=np.ones((4, 4), np.int8), cardinal=2, diagonal=3,
+ ... )
+ >>> pf = tcod.path.Pathfinder(graph)
+ >>> pf.distance
+ array([[2147483647, 2147483647, 2147483647, 2147483647],
+ [2147483647, 2147483647, 2147483647, 2147483647],
+ [2147483647, 2147483647, 2147483647, 2147483647],
+ [2147483647, 2147483647, 2147483647, 2147483647]]...)
+ >>> pf.add_root((0, 0))
+ >>> pf.distance
+ array([[ 0, 2147483647, 2147483647, 2147483647],
+ [2147483647, 2147483647, 2147483647, 2147483647],
+ [2147483647, 2147483647, 2147483647, 2147483647],
+ [2147483647, 2147483647, 2147483647, 2147483647]]...)
+ >>> pf.resolve((1, 1)) # Resolve up to (1, 1) as A*.
+ >>> pf.distance # Partially resolved distance.
+ array([[ 0, 2, 6, 2147483647],
+ [ 2, 3, 5, 2147483647],
+ [ 6, 5, 6, 2147483647],
+ [2147483647, 2147483647, 2147483647, 2147483647]]...)
+ >>> pf.resolve() # Resolve the full graph as Dijkstra.
+ >>> pf.distance # Fully resolved distance.
+ array([[0, 2, 4, 6],
+ [2, 3, 5, 7],
+ [4, 5, 6, 8],
+ [6, 7, 8, 9]]...)
+ """
+ if goal is not None:
+ goal = tuple(goal) # Check for bad input.
+ if len(goal) != self._distance.ndim:
+ msg = f"Goal must be {self._distance.ndim} items, got {goal!r}"
+ raise TypeError(msg)
+ if self._order == "F":
+ # Goal is now ij indexed for the rest of this function.
+ goal = goal[::-1]
+ if self._distance[goal] != np.iinfo(self._distance.dtype).max: # noqa: SIM102
+ if not lib.frontier_has_index(self._frontier_p, goal):
+ return
+ self._update_heuristic(goal)
+ self._graph._resolve(self)
+
+ def path_from(self, index: tuple[int, ...]) -> NDArray[np.intc]:
+ """Return the shortest path from `index` to the nearest root.
+
+ The returned array is of shape `(length, ndim)` where `length` is the
+ total inclusive length of the path and `ndim` is the dimensions of the
+ pathfinder defined by the graph.
+
+ The return value is inclusive, including both the starting and ending
+ points on the path. If the root point is unreachable or `index` is
+ already at a root then `index` will be the only point returned.
+
+ This automatically calls :any:`resolve` if the pathfinder has not
+ yet reached `index`.
+
+ A common usage is to slice off the starting point and convert the array
+ into a list.
+
+ Example::
+
+ >>> import tcod.path
+ >>> cost = np.ones((5, 5), dtype=np.int8)
+ >>> cost[:, 3:] = 0
+ >>> graph = tcod.path.SimpleGraph(cost=cost, cardinal=2, diagonal=3)
+ >>> pf = tcod.path.Pathfinder(graph)
+ >>> pf.add_root((0, 0))
+ >>> pf.path_from((2, 2)).tolist()
+ [[2, 2], [1, 1], [0, 0]]
+ >>> pf.path_from((2, 2))[1:].tolist() # Exclude the starting point by slicing the array.
+ [[1, 1], [0, 0]]
+ >>> pf.path_from((4, 4)).tolist() # Blocked paths will only have the index point.
+ [[4, 4]]
+ >>> pf.path_from((4, 4))[1:].tolist() # Exclude the starting point so that a blocked path is an empty list.
+ []
+
+ """
+ index = tuple(index) # Check for bad input.
+ if len(index) != self._graph._ndim:
+ msg = f"Index must be {self._distance.ndim} items, got {index!r}"
+ raise TypeError(msg)
+ self.resolve(index)
+ if self._order == "F": # Convert to ij indexing order.
+ index = index[::-1]
+ _travel_p = _export(self._travel)
+ length = _check(lib.get_travel_path(self._graph._ndim, _travel_p, index, ffi.NULL))
+ path: np.ndarray[Any, np.dtype[np.intc]] = np.ndarray((length, self._graph._ndim), dtype=np.intc)
+ _check(
+ lib.get_travel_path(
+ self._graph._ndim,
+ _travel_p,
+ index,
+ ffi.from_buffer("int*", path),
+ )
+ )
+ return path[:, ::-1] if self._order == "F" else path
+
+ def path_to(self, index: tuple[int, ...]) -> NDArray[np.intc]:
+ """Return the shortest path from the nearest root to `index`.
+
+ See :any:`path_from`.
+ This is an alias for ``path_from(...)[::-1]``.
+
+ This is the method to call when the root is an entity to move to a
+ position rather than a destination itself.
+
+ Example::
+
+ >>> import tcod.path
+ >>> graph = tcod.path.SimpleGraph(
+ ... cost=np.ones((5, 5), np.int8), cardinal=2, diagonal=3,
+ ... )
+ >>> pf = tcod.path.Pathfinder(graph)
+ >>> pf.add_root((0, 0))
+ >>> pf.path_to((0, 0)).tolist() # This method always returns at least one point.
+ [[0, 0]]
+ >>> pf.path_to((3, 3)).tolist() # Always includes both ends on a valid path.
+ [[0, 0], [1, 1], [2, 2], [3, 3]]
+ >>> pf.path_to((3, 3))[1:].tolist() # Exclude the starting point by slicing the array.
+ [[1, 1], [2, 2], [3, 3]]
+ >>> pf.path_to((0, 0))[1:].tolist() # Exclude the starting point so that a blocked path is an empty list.
+ []
+ """
+ return self.path_from(index)[::-1]
+
+
+def path2d( # noqa: C901, PLR0912, PLR0913
+ cost: ArrayLike,
+ *,
+ start_points: Sequence[tuple[int, int]],
+ end_points: Sequence[tuple[int, int]],
+ cardinal: int,
+ diagonal: int | None = None,
+ check_bounds: bool = True,
+) -> NDArray[np.intc]:
+ """Return a path between `start_points` and `end_points`.
+
+ If `start_points` or `end_points` has only one item then this is equivalent to A*.
+ Otherwise it is equivalent to Dijkstra.
+
+ If multiple `start_points` or `end_points` are given then the single shortest path between them is returned.
+
+ Points placed on nodes with a cost of 0 are treated as always reachable from adjacent nodes.
+
+ Args:
+ cost: A 2D array of integers with the cost of each node.
+ start_points: A sequence of one or more starting points indexing `cost`.
+ end_points: A sequence of one or more ending points indexing `cost`.
+ cardinal: The relative cost to move a cardinal direction.
+ diagonal: The relative cost to move a diagonal direction.
+ `None` or `0` will disable diagonal movement.
+ check_bounds: If `False` then out-of-bounds points are silently ignored.
+ If `True` (default) then out-of-bounds points raise :any:`IndexError`.
+
+ Returns:
+ A `(length, 2)` array of indexes of the path including the start and end points.
+ If there is no path then an array with zero items will be returned.
+
+ Example::
+
+ # Note: coordinates in this example are (i, j), or (y, x)
+ >>> cost = np.array([
+ ... [1, 0, 1, 1, 1, 0, 1],
+ ... [1, 0, 1, 1, 1, 0, 1],
+ ... [1, 0, 1, 0, 1, 0, 1],
+ ... [1, 1, 1, 1, 1, 0, 1],
+ ... ])
+
+ # Endpoints are reachable even when endpoints are on blocked nodes
+ >>> tcod.path.path2d(cost, start_points=[(0, 0)], end_points=[(2, 3)], cardinal=70, diagonal=99)
+ array([[0, 0],
+ [1, 0],
+ [2, 0],
+ [3, 1],
+ [2, 2],
+ [2, 3]], dtype=int...)
+
+ # Unreachable endpoints return a zero length array
+ >>> tcod.path.path2d(cost, start_points=[(0, 0)], end_points=[(3, 6)], cardinal=70, diagonal=99)
+ array([], shape=(0, 2), dtype=int...)
+ >>> tcod.path.path2d(cost, start_points=[(0, 0), (3, 0)], end_points=[(0, 6), (3, 6)], cardinal=70, diagonal=99)
+ array([], shape=(0, 2), dtype=int...)
+ >>> tcod.path.path2d(cost, start_points=[], end_points=[], cardinal=70, diagonal=99)
+ array([], shape=(0, 2), dtype=int...)
+
+ # Overlapping endpoints return a single step
+ >>> tcod.path.path2d(cost, start_points=[(0, 0)], end_points=[(0, 0)], cardinal=70, diagonal=99)
+ array([[0, 0]], dtype=int32)
+
+ # Multiple endpoints return the shortest path
+ >>> tcod.path.path2d(
+ ... cost, start_points=[(0, 0)], end_points=[(1, 3), (3, 3), (2, 2), (2, 4)], cardinal=70, diagonal=99)
+ array([[0, 0],
+ [1, 0],
+ [2, 0],
+ [3, 1],
+ [2, 2]], dtype=int...)
+ >>> tcod.path.path2d(
+ ... cost, start_points=[(0, 0), (0, 2)], end_points=[(1, 3), (3, 3), (2, 2), (2, 4)], cardinal=70, diagonal=99)
+ array([[0, 2],
+ [1, 3]], dtype=int...)
+ >>> tcod.path.path2d(cost, start_points=[(0, 0), (0, 2)], end_points=[(3, 2)], cardinal=1)
+ array([[0, 2],
+ [1, 2],
+ [2, 2],
+ [3, 2]], dtype=int...)
+
+ # Checking for out-of-bounds points may be toggled
+ >>> tcod.path.path2d(cost, start_points=[(0, 0)], end_points=[(-1, -1), (3, 1)], cardinal=1)
+ Traceback (most recent call last):
+ ...
+ IndexError: End point (-1, -1) is out-of-bounds of cost shape (4, 7)
+ >>> tcod.path.path2d(cost, start_points=[(0, 0)], end_points=[(-1, -1), (3, 1)], cardinal=1, check_bounds=False)
+ array([[0, 0],
+ [1, 0],
+ [2, 0],
+ [3, 0],
+ [3, 1]], dtype=int...)
+
+ .. versionadded:: 18.1
+ """
+ cost = np.copy(cost) # Copy array to later modify nodes to be always reachable
+
+ # Check bounds of endpoints
+ if check_bounds:
+ for points, name in [(start_points, "start"), (end_points, "end")]:
+ for i, j in points:
+ if not (0 <= i < cost.shape[0] and 0 <= j < cost.shape[1]):
+ msg = f"{name.capitalize()} point {(i, j)!r} is out-of-bounds of cost shape {cost.shape!r}"
+ raise IndexError(msg)
+ else:
+ start_points = [(i, j) for i, j in start_points if 0 <= i < cost.shape[0] and 0 <= j < cost.shape[1]]
+ end_points = [(i, j) for i, j in end_points if 0 <= i < cost.shape[0] and 0 <= j < cost.shape[1]]
+
+ if not start_points or not end_points:
+ return np.zeros((0, 2), dtype=np.intc) # Missing endpoints
+
+ # Check if endpoints can be manipulated to use A* for a one-to-many computation
+ reversed_path = False
+ if len(end_points) == 1 and len(start_points) > 1:
+ # Swap endpoints to ensure single start point as the A* goal
+ reversed_path = True
+ start_points, end_points = end_points, start_points
+
+ for ij in start_points:
+ cost[ij] = 1 # Enforce reachability of endpoint
+
+ graph = SimpleGraph(cost=cost, cardinal=cardinal, diagonal=diagonal or 0)
+ pf = Pathfinder(graph)
+ for ij in end_points:
+ pf.add_root(ij)
+
+ if len(start_points) == 1: # Compute A* from possibly multiple roots to one goal
+ out = pf.path_from(start_points[0])
+ if pf.distance[start_points[0]] == np.iinfo(pf.distance.dtype).max:
+ return np.zeros((0, 2), dtype=np.intc) # Unreachable endpoint
+ if reversed_path:
+ out = out[::-1]
+ return out
+
+ # Crude Dijkstra implementation until issues with Pathfinder are fixed
+ pf.resolve(None)
+ best_distance, best_ij = min((pf.distance[ij], ij) for ij in start_points)
+ if best_distance == np.iinfo(pf.distance.dtype).max:
+ return np.zeros((0, 2), dtype=np.intc) # All endpoints unreachable
+
+ return hillclimb2d(pf.distance, best_ij, cardinal=bool(cardinal), diagonal=bool(diagonal))
diff --git a/tcod/py.typed b/tcod/py.typed
new file mode 100644
index 00000000..e69de29b
diff --git a/tcod/random.h b/tcod/random.h
index 360073ca..cc7109db 100644
--- a/tcod/random.h
+++ b/tcod/random.h
@@ -1,21 +1,16 @@
#ifndef PYTHON_TCOD_RANDOM_H_
#define PYTHON_TCOD_RANDOM_H_
+#include "../libtcod/src/libtcod/mersenne.h"
/* define libtcod random functions */
int TCOD_random_get_i(TCOD_random_t mersenne, int min, int max);
double TCOD_random_get_d(TCOD_random_t mersenne, double min, double max);
-double TCOD_random_get_gaussian_double(
- TCOD_random_t mersenne, double mean, double std_deviation);
-double TCOD_random_get_gaussian_double_range(
- TCOD_random_t mersenne, double min, double max);
-double TCOD_random_get_gaussian_double_range_custom(
- TCOD_random_t mersenne, double min, double max, double mean);
-double TCOD_random_get_gaussian_double_inv(
- TCOD_random_t mersenne, double mean, double std_deviation);
-double TCOD_random_get_gaussian_double_range_inv(
- TCOD_random_t mersenne, double min, double max);
-double TCOD_random_get_gaussian_double_range_custom_inv(
- TCOD_random_t mersenne, double min, double max, double mean);
+double TCOD_random_get_gaussian_double(TCOD_random_t mersenne, double mean, double std_deviation);
+double TCOD_random_get_gaussian_double_range(TCOD_random_t mersenne, double min, double max);
+double TCOD_random_get_gaussian_double_range_custom(TCOD_random_t mersenne, double min, double max, double mean);
+double TCOD_random_get_gaussian_double_inv(TCOD_random_t mersenne, double mean, double std_deviation);
+double TCOD_random_get_gaussian_double_range_inv(TCOD_random_t mersenne, double min, double max);
+double TCOD_random_get_gaussian_double_range_custom_inv(TCOD_random_t mersenne, double min, double max, double mean);
#endif /* PYTHON_TCOD_RANDOM_H_ */
diff --git a/tcod/random.py b/tcod/random.py
index ed9beb0d..1b7b16c7 100644
--- a/tcod/random.py
+++ b/tcod/random.py
@@ -1,50 +1,98 @@
+"""Ports of the libtcod random number generator.
+
+Usually it's recommend to the Python's standard library `random` module
+instead of this one.
+
+However, you will need to use these generators to get deterministic results
+from the :any:`Noise` and :any:`BSP` classes.
"""
- Random module docs.
-"""
-from __future__ import absolute_import as _
+from __future__ import annotations
+
+import os
+import random
+import warnings
+from typing import TYPE_CHECKING, Any
-import time
+from typing_extensions import deprecated
-from tcod.libtcod import ffi, lib
-from tcod.libtcod import RNG_MT as MERSENNE_TWISTER
-from tcod.libtcod import RNG_CMWC as COMPLEMENTARY_MULTIPLY_WITH_CARRY
+import tcod.constants
+from tcod.cffi import ffi, lib
+if TYPE_CHECKING:
+ from collections.abc import Hashable
-class Random(object):
+MERSENNE_TWISTER = tcod.constants.RNG_MT
+COMPLEMENTARY_MULTIPLY_WITH_CARRY = tcod.constants.RNG_CMWC
+MULTIPLY_WITH_CARRY = tcod.constants.RNG_CMWC
+
+
+class Random:
"""The libtcod random number generator.
- If all you need is a random number generator then it's recommended
- that you use the :any:`random` module from the Python standard library.
+ `algorithm` defaults to Mersenne Twister, it can be one of:
- If ``seed`` is None then a random seed will be generated.
+ * tcod.random.MERSENNE_TWISTER
+ * tcod.random.MULTIPLY_WITH_CARRY
- Args:
- algorithm (int): The algorithm to use.
- seed (Optional[Hashable]):
- Could be a 32-bit integer, but any hashable object is accepted.
+ `seed` is a 32-bit number or any Python hashable object like a string.
+ Using the same seed will cause the generator to return deterministic
+ values. The default `seed` of None will generate a random seed instead.
Attributes:
random_c (CData): A cffi pointer to a TCOD_random_t object.
+
+ .. warning::
+ A non-integer seed is only deterministic if the environment variable
+ ``PYTHONHASHSEED`` is set. In the future this function will only
+ accept `int`'s as a seed.
+
+ .. versionchanged:: 9.1
+ Added `tcod.random.MULTIPLY_WITH_CARRY` constant.
+ `algorithm` parameter now defaults to `tcod.random.MERSENNE_TWISTER`.
"""
- def __init__(self, algorithm, seed=None):
+
+ def __init__(
+ self,
+ algorithm: int = MERSENNE_TWISTER,
+ seed: Hashable | None = None,
+ ) -> None:
"""Create a new instance using this algorithm and seed."""
if seed is None:
- seed = time.time() + time.clock()
+ seed = random.getrandbits(32)
+ elif not isinstance(seed, int):
+ warnings.warn(
+ "In the future this class will only accept integer seeds.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ if __debug__ and "PYTHONHASHSEED" not in os.environ:
+ warnings.warn(
+ "Python's hash algorithm is not configured to be"
+ " deterministic so this non-integer seed will not be"
+ " deterministic."
+ "\nYou should do one of the following to fix this error:"
+ "\n* Use an integer as a seed instead (recommended.)"
+ "\n* Set the PYTHONHASHSEED environment variable before"
+ " starting Python.",
+ RuntimeWarning,
+ stacklevel=2,
+ )
+ seed = hash(seed)
+
self.random_c = ffi.gc(
- ffi.cast('mersenne_data_t*',
- lib.TCOD_random_new_from_seed(algorithm,
- hash(seed) % (1 << 32))),
- lib.TCOD_random_delete)
+ lib.TCOD_random_new_from_seed(algorithm, seed & 0xFFFFFFFF),
+ lib.TCOD_random_delete,
+ )
@classmethod
- def _new_from_cdata(cls, cdata):
+ def _new_from_cdata(cls, cdata: Any) -> Random: # noqa: ANN401
"""Return a new instance encapsulating this cdata."""
- self = object.__new__(cls)
+ self: Random = object.__new__(cls)
self.random_c = cdata
return self
- def randint(self, low, high):
+ def randint(self, low: int, high: int) -> int:
"""Return a random integer within the linear range: low <= n <= high.
Args:
@@ -54,21 +102,21 @@ def randint(self, low, high):
Returns:
int: A random integer.
"""
- return lib.TCOD_random_get_i(self.random_c, low, high)
+ return int(lib.TCOD_random_get_i(self.random_c, low, high))
- def uniform(self, low, high):
+ def uniform(self, low: float, high: float) -> float:
"""Return a random floating number in the range: low <= n <= high.
Args:
- low (int): The lower bound of the random range.
- high (int): The upper bound of the random range.
+ low (float): The lower bound of the random range.
+ high (float): The upper bound of the random range.
Returns:
float: A random float.
"""
- return lib.TCOD_random_get_double(self.random_c, low, high)
+ return float(lib.TCOD_random_get_double(self.random_c, low, high))
- def guass(self, mu, sigma):
+ def gauss(self, mu: float, sigma: float) -> float:
"""Return a random number using Gaussian distribution.
Args:
@@ -77,10 +125,17 @@ def guass(self, mu, sigma):
Returns:
float: A random float.
+
+ .. versionchanged:: 16.2
+ Renamed from `guass` to `gauss`.
"""
- return lib.TCOD_random_get_gaussian_double(self.random_c, mu, sigma)
+ return float(lib.TCOD_random_get_gaussian_double(self.random_c, mu, sigma))
+
+ @deprecated("This is a typo, rename this to 'gauss'", category=FutureWarning)
+ def guass(self, mu: float, sigma: float) -> float: # noqa: D102
+ return self.gauss(mu, sigma)
- def inverse_guass(self, mu, sigma):
+ def inverse_gauss(self, mu: float, sigma: float) -> float:
"""Return a random Gaussian number using the Box-Muller transform.
Args:
@@ -89,29 +144,38 @@ def inverse_guass(self, mu, sigma):
Returns:
float: A random float.
+
+ .. versionchanged:: 16.2
+ Renamed from `inverse_guass` to `inverse_gauss`.
"""
- return lib.TCOD_random_get_gaussian_double_inv(self.random_c, mu, sigma)
+ return float(lib.TCOD_random_get_gaussian_double_inv(self.random_c, mu, sigma))
+
+ @deprecated("This is a typo, rename this to 'inverse_gauss'", category=FutureWarning)
+ def inverse_guass(self, mu: float, sigma: float) -> float: # noqa: D102
+ return self.inverse_gauss(mu, sigma)
- def __getstate__(self):
+ def __getstate__(self) -> dict[str, Any]:
"""Pack the self.random_c attribute into a portable state."""
state = self.__dict__.copy()
- state['random_c'] = {
- 'algo': self.random_c.algo,
- 'distribution': self.random_c.distribution,
- 'mt': list(self.random_c.mt),
- 'cur_mt': self.random_c.cur_mt,
- 'Q': list(self.random_c.Q),
- 'c': self.random_c.c,
- 'cur': self.random_c.cur,
+ state["random_c"] = {
+ "mt_cmwc": {
+ "algorithm": self.random_c.mt_cmwc.algorithm,
+ "distribution": self.random_c.mt_cmwc.distribution,
+ "mt": list(self.random_c.mt_cmwc.mt),
+ "cur_mt": self.random_c.mt_cmwc.cur_mt,
+ "Q": list(self.random_c.mt_cmwc.Q),
+ "c": self.random_c.mt_cmwc.c,
+ "cur": self.random_c.mt_cmwc.cur,
}
+ }
return state
- def __setstate__(self, state):
- """Create a new cdata object with the stored paramaters."""
- try:
- cdata = state['random_c']
- except KeyError: # old/deprecated format
- cdata = state['cdata']
- del state['cdata']
- state['random_c'] = ffi.new('mersenne_data_t*', cdata)
+ def __setstate__(self, state: dict[str, Any]) -> None:
+ """Create a new cdata object with the stored parameters."""
+ if "algo" in state["random_c"]:
+ # Handle old/deprecated format. Covert to libtcod's new union type.
+ state["random_c"]["algorithm"] = state["random_c"]["algo"]
+ del state["random_c"]["algo"]
+ state["random_c"] = {"mt_cmwc": state["random_c"]}
+ state["random_c"] = ffi.new("TCOD_Random*", state["random_c"])
self.__dict__.update(state)
diff --git a/tcod/render.py b/tcod/render.py
new file mode 100644
index 00000000..09d3a137
--- /dev/null
+++ b/tcod/render.py
@@ -0,0 +1,106 @@
+"""Handles the rendering of libtcod's tilesets.
+
+Using this module you can render a console to an SDL :any:`Texture` directly, letting you have full control over how
+consoles are displayed.
+This includes rendering multiple tilesets in a single frame and rendering consoles on top of each other.
+
+Example::
+
+ tileset = tcod.tileset.load_tilesheet("dejavu16x16_gs_tc.png", 32, 8, tcod.tileset.CHARMAP_TCOD)
+ console = tcod.console.Console(20, 8)
+ console.print(0, 0, "Hello World")
+ sdl_window = tcod.sdl.video.new_window(
+ console.width * tileset.tile_width,
+ console.height * tileset.tile_height,
+ flags=tcod.lib.SDL_WINDOW_RESIZABLE,
+ )
+ sdl_renderer = tcod.sdl.render.new_renderer(sdl_window, target_textures=True)
+ atlas = tcod.render.SDLTilesetAtlas(sdl_renderer, tileset)
+ console_render = tcod.render.SDLConsoleRender(atlas)
+ while True:
+ sdl_renderer.copy(console_render.render(console))
+ sdl_renderer.present()
+ for event in tcod.event.wait():
+ if isinstance(event, tcod.event.Quit):
+ raise SystemExit()
+
+.. versionadded:: 13.4
+"""
+
+from __future__ import annotations
+
+from typing import Any, Final
+
+import tcod.console
+import tcod.sdl.render
+import tcod.tileset
+from tcod._internal import _check, _check_p
+from tcod.cffi import ffi, lib
+
+
+class SDLTilesetAtlas:
+ """Prepares a tileset for rendering using SDL."""
+
+ def __init__(self, renderer: tcod.sdl.render.Renderer, tileset: tcod.tileset.Tileset) -> None:
+ """Initialize the tileset atlas."""
+ self._renderer = renderer
+ self.tileset: Final[tcod.tileset.Tileset] = tileset
+ """The tileset used to create this SDLTilesetAtlas."""
+ self.p: Final = ffi.gc(
+ _check_p(lib.TCOD_sdl2_atlas_new(renderer.p, tileset._tileset_p)), lib.TCOD_sdl2_atlas_delete
+ )
+
+ @classmethod
+ def _from_ref(cls, renderer_p: Any, atlas_p: Any) -> SDLTilesetAtlas: # noqa: ANN401
+ self = object.__new__(cls)
+ # Ignore Final reassignment type errors since this is an alternative constructor.
+ # This could be a sign that the current constructor was badly implemented.
+ self._renderer = tcod.sdl.render.Renderer(renderer_p)
+ self.tileset = tcod.tileset.Tileset._from_ref(atlas_p.tileset) # type: ignore[misc]
+ self.p = atlas_p # type: ignore[misc]
+ return self
+
+
+class SDLConsoleRender:
+ """Holds an internal cache console and texture which are used to optimized console rendering."""
+
+ def __init__(self, atlas: SDLTilesetAtlas) -> None:
+ """Initialize the console renderer."""
+ self.atlas: Final[SDLTilesetAtlas] = atlas
+ """The SDLTilesetAtlas used to create this SDLConsoleRender.
+
+ .. versionadded:: 13.7
+ """
+ self._renderer = atlas._renderer
+ self._cache_console: tcod.console.Console | None = None
+ self._texture: tcod.sdl.render.Texture | None = None
+
+ def render(self, console: tcod.console.Console) -> tcod.sdl.render.Texture:
+ """Render a console to a cached Texture and then return the Texture.
+
+ You should not draw onto the returned Texture as only changed parts of it will be updated on the next call.
+
+ This function requires the SDL renderer to have target texture support.
+ It will also change the SDL target texture for the duration of the call.
+ """
+ if self._cache_console and (
+ self._cache_console.width != console.width or self._cache_console.height != console.height
+ ):
+ self._cache_console = None
+ self._texture = None
+ if self._cache_console is None or self._texture is None:
+ self._cache_console = tcod.console.Console(console.width, console.height)
+ self._texture = self._renderer.new_texture(
+ self.atlas.tileset.tile_width * console.width,
+ self.atlas.tileset.tile_height * console.height,
+ format=int(lib.SDL_PIXELFORMAT_RGBA32),
+ access=int(lib.SDL_TEXTUREACCESS_TARGET),
+ )
+
+ with self._renderer.set_render_target(self._texture):
+ _check(
+ lib.TCOD_sdl2_render_texture(
+ self.atlas.p, console.console_c, self._cache_console.console_c, self._texture.p
+ )
+ )
+ return self._texture
diff --git a/tcod/sdl/__init__.py b/tcod/sdl/__init__.py
new file mode 100644
index 00000000..22e82845
--- /dev/null
+++ b/tcod/sdl/__init__.py
@@ -0,0 +1,5 @@
+"""tcod.sdl package."""
+
+from pkgutil import extend_path
+
+__path__ = extend_path(__path__, __name__)
diff --git a/tcod/sdl/_internal.py b/tcod/sdl/_internal.py
new file mode 100644
index 00000000..db67fa2e
--- /dev/null
+++ b/tcod/sdl/_internal.py
@@ -0,0 +1,219 @@
+"""tcod.sdl private functions."""
+
+from __future__ import annotations
+
+import logging
+import sys
+from dataclasses import dataclass
+from typing import TYPE_CHECKING, Any, NoReturn, Protocol, TypeVar, overload, runtime_checkable
+
+from typing_extensions import Self
+
+from tcod.cffi import ffi, lib
+
+if TYPE_CHECKING:
+ from collections.abc import Callable
+ from types import TracebackType
+
+T = TypeVar("T")
+
+logger = logging.getLogger("tcod.sdl")
+
+_LOG_PRIORITY = {
+ 1: logging.DEBUG, # SDL_LOG_PRIORITY_VERBOSE
+ 2: logging.DEBUG, # SDL_LOG_PRIORITY_DEBUG
+ 3: logging.INFO, # SDL_LOG_PRIORITY_INFO
+ 4: logging.WARNING, # SDL_LOG_PRIORITY_WARN
+ 5: logging.ERROR, # SDL_LOG_PRIORITY_ERROR
+ 6: logging.CRITICAL, # SDL_LOG_PRIORITY_CRITICAL
+}
+
+_LOG_CATEGORY = {
+ int(lib.SDL_LOG_CATEGORY_APPLICATION): "APPLICATION",
+ int(lib.SDL_LOG_CATEGORY_ERROR): "ERROR",
+ int(lib.SDL_LOG_CATEGORY_ASSERT): "ASSERT",
+ int(lib.SDL_LOG_CATEGORY_SYSTEM): "SYSTEM",
+ int(lib.SDL_LOG_CATEGORY_AUDIO): "AUDIO",
+ int(lib.SDL_LOG_CATEGORY_VIDEO): "VIDEO",
+ int(lib.SDL_LOG_CATEGORY_RENDER): "RENDER",
+ int(lib.SDL_LOG_CATEGORY_INPUT): "INPUT",
+ int(lib.SDL_LOG_CATEGORY_TEST): "TEST",
+ int(lib.SDL_LOG_CATEGORY_CUSTOM): "",
+}
+
+
+@dataclass
+class _UnraisableHookArgs:
+ exc_type: type[BaseException]
+ exc_value: BaseException | None
+ exc_traceback: TracebackType | None
+ err_msg: str | None
+ object: object
+
+
+class _ProtectedContext:
+ def __init__(self, obj: object = None) -> None:
+ self.obj = obj
+
+ def __enter__(self) -> None:
+ pass
+
+ def __exit__(
+ self, exc_type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None
+ ) -> bool:
+ if exc_type is None:
+ return False
+ sys.unraisablehook(_UnraisableHookArgs(exc_type, value, traceback, None, self.obj))
+ return True
+
+
+@runtime_checkable
+class PropertyPointer(Protocol):
+ """Methods for classes which support pointers being set to properties."""
+
+ @classmethod
+ def _from_property_pointer(cls, raw_cffi_pointer: Any, /) -> Self: # noqa: ANN401
+ """Convert a raw pointer to this class."""
+ ...
+
+ def _as_property_pointer(self) -> Any: # noqa: ANN401
+ """Return a CFFI pointer for this object."""
+ ...
+
+
+class Properties:
+ """SDL properties interface."""
+
+ def __init__(self, p: Any | None = None) -> None: # noqa: ANN401
+ """Create new properties or use an existing pointer."""
+ if p is None:
+ self.p = ffi.gc(
+ ffi.cast("SDL_PropertiesID", _check_int(lib.SDL_CreateProperties(), failure=0)),
+ lib.SDL_DestroyProperties,
+ )
+ else:
+ self.p = p
+
+ @overload
+ def __getitem__(self, key: tuple[str, type[bool]], /) -> bool: ...
+ @overload
+ def __getitem__(self, key: tuple[str, type[int]], /) -> int: ...
+ @overload
+ def __getitem__(self, key: tuple[str, type[float]], /) -> float: ...
+ @overload
+ def __getitem__(self, key: tuple[str, type[str]], /) -> str: ...
+
+ def __getitem__(self, key: tuple[str, type[Any]], /) -> Any:
+ """Get a typed value from this property."""
+ key_, type_ = key
+ name = key_.encode("utf-8")
+ match lib.SDL_GetPropertyType(self.p, name):
+ case lib.SDL_PROPERTY_TYPE_STRING:
+ assert type_ is str
+ return str(ffi.string(lib.SDL_GetStringProperty(self.p, name, ffi.NULL)), encoding="utf-8")
+ case lib.SDL_PROPERTY_TYPE_NUMBER:
+ assert type_ is int
+ return int(lib.SDL_GetNumberProperty(self.p, name, 0))
+ case lib.SDL_PROPERTY_TYPE_FLOAT:
+ assert type_ is float
+ return float(lib.SDL_GetFloatProperty(self.p, name, 0.0))
+ case lib.SDL_PROPERTY_TYPE_BOOLEAN:
+ assert type_ is bool
+ return bool(lib.SDL_GetBooleanProperty(self.p, name, False)) # noqa: FBT003
+ case lib.SDL_PROPERTY_TYPE_POINTER:
+ assert isinstance(type_, PropertyPointer)
+ return type_._from_property_pointer(lib.SDL_GetPointerProperty(self.p, name, ffi.NULL))
+ case lib.SDL_PROPERTY_TYPE_INVALID:
+ raise KeyError("Invalid type.") # noqa: EM101, TRY003
+ case _:
+ raise AssertionError
+
+ def __setitem__(self, key: tuple[str, type[T]], value: T, /) -> None:
+ """Assign a property."""
+ key_, type_ = key
+ name = key_.encode("utf-8")
+ if type_ is str:
+ assert isinstance(value, str)
+ lib.SDL_SetStringProperty(self.p, name, value.encode("utf-8"))
+ elif type_ is int:
+ assert isinstance(value, int)
+ lib.SDL_SetNumberProperty(self.p, name, value)
+ elif type_ is float:
+ assert isinstance(value, (int, float))
+ lib.SDL_SetFloatProperty(self.p, name, value)
+ elif type_ is bool:
+ lib.SDL_SetFloatProperty(self.p, name, bool(value))
+ else:
+ assert isinstance(type_, PropertyPointer)
+ assert isinstance(value, PropertyPointer)
+ lib.SDL_SetPointerProperty(self.p, name, value._as_property_pointer())
+
+
+@ffi.def_extern() # type: ignore[untyped-decorator]
+def _sdl_log_output_function(_userdata: None, category: int, priority: int, message_p: Any) -> None: # noqa: ANN401
+ """Pass logs sent by SDL to Python's logging system."""
+ message = str(ffi.string(message_p), encoding="utf-8")
+ logger.log(_LOG_PRIORITY.get(priority, 0), "%s:%s", _LOG_CATEGORY.get(category, ""), message)
+
+
+def _get_error() -> str:
+ """Return a message from SDL_GetError as a Unicode string."""
+ return str(ffi.string(lib.SDL_GetError()), encoding="utf-8")
+
+
+def _check(result: bool, /) -> bool: # noqa: FBT001
+ """Check if an SDL function returned without errors, and raise an exception if it did."""
+ if not result:
+ raise RuntimeError(_get_error())
+ return result
+
+
+def _check_int(result: int, /, failure: int) -> int:
+ """Check if an SDL function returned without errors, and raise an exception if it did."""
+ if result == failure:
+ raise RuntimeError(_get_error())
+ return result
+
+
+def _check_float(result: float, /, failure: float) -> float:
+ """Check if an SDL function returned without errors, and raise an exception if it did."""
+ if result == failure:
+ raise RuntimeError(_get_error())
+ return result
+
+
+def _check_p(result: Any) -> Any: # noqa: ANN401
+ """Check if an SDL function returned NULL, and raise an exception if it did."""
+ if not result:
+ raise RuntimeError(_get_error())
+ return result
+
+
+def _compiled_version() -> tuple[int, int, int]:
+ return int(lib.SDL_MAJOR_VERSION), int(lib.SDL_MINOR_VERSION), int(lib.SDL_MICRO_VERSION)
+
+
+def _version_at_least(required: tuple[int, int, int]) -> None:
+ """Raise an error if the compiled version is less than required. Used to guard recently defined SDL functions."""
+ if required <= _compiled_version():
+ return
+ msg = f"This feature requires SDL version {required}, but tcod was compiled with version {_compiled_version()}"
+ raise RuntimeError(msg)
+
+
+def _required_version(required: tuple[int, int, int]) -> Callable[[T], T]:
+ if not lib: # Read the docs mock object.
+ return lambda x: x
+ if required <= _compiled_version():
+ return lambda x: x
+
+ def replacement(*_args: object, **_kwargs: object) -> NoReturn:
+ msg = f"This feature requires SDL version {required}, but tcod was compiled with version {_compiled_version()}"
+ raise RuntimeError(msg)
+
+ return lambda _: replacement # type: ignore[return-value]
+
+
+lib.SDL_SetLogOutputFunction(lib._sdl_log_output_function, ffi.NULL)
+if __debug__:
+ lib.SDL_SetLogPriorities(lib.SDL_LOG_PRIORITY_VERBOSE)
diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py
new file mode 100644
index 00000000..0cb7a9e9
--- /dev/null
+++ b/tcod/sdl/audio.py
@@ -0,0 +1,1075 @@
+"""SDL audio playback and recording tools.
+
+This module includes SDL's low-level audio API and a naive implementation of an SDL mixer.
+If you have experience with audio mixing then you might be better off writing your own mixer or
+modifying the existing one which was written using Python/Numpy.
+
+This module is designed to integrate with the wider Python ecosystem.
+It leaves the loading to sound samples to other libraries such as
+`soundfile `_.
+
+Example::
+
+ # Synchronous audio example
+ import time
+
+ import soundfile # pip install soundfile
+ import tcod.sdl.audio
+
+ device = tcod.sdl.get_default_playback().open() # Open the default output device
+
+ # AudioDevice's can be opened again to form a hierarchy
+ # This can be used to give music and sound effects their own configuration
+ device_music = device.open()
+ device_music.gain = 0 # Mute music
+ device_effects = device.open()
+ device_effects.gain = 10 ** (-6 / 10) # -6dB
+
+ sound, sample_rate = soundfile.read("example_sound.wav", dtype="float32") # Load an audio sample using SoundFile
+ stream = device_effects.new_stream(format=sound.dtype, frequency=sample_rate, channels=sound.shape[1])
+ stream.queue_audio(sound) # Play audio by appending it to the audio stream
+ stream.flush()
+
+ while stream.queued_samples: # Wait until stream is finished
+ time.sleep(0.001)
+
+.. versionadded:: 13.5
+"""
+
+from __future__ import annotations
+
+import contextlib
+import enum
+import sys
+import threading
+import weakref
+from dataclasses import dataclass
+from typing import TYPE_CHECKING, Any, Final, Literal, NamedTuple
+
+import numpy as np
+from typing_extensions import Self, deprecated
+
+import tcod.sdl.sys
+from tcod.cffi import ffi, lib
+from tcod.sdl._internal import _check, _check_float, _check_int, _check_p
+
+if TYPE_CHECKING:
+ import builtins
+ from collections.abc import Callable, Hashable, Iterable, Iterator
+ from types import TracebackType
+
+ from numpy.typing import ArrayLike, DTypeLike, NDArray
+
+
+def _get_format(format: DTypeLike, /) -> int: # noqa: A002
+ """Return a SDL_AudioFormat bit-field from a NumPy dtype."""
+ dt: Any = np.dtype(format)
+ assert dt.fields is None
+ bitsize = dt.itemsize * 8
+ assert 0 < bitsize <= lib.SDL_AUDIO_MASK_BITSIZE
+ if dt.str[1] not in "uif":
+ msg = f"Unexpected dtype: {dt}"
+ raise TypeError(msg)
+ is_signed = dt.str[1] != "u"
+ is_float = dt.str[1] == "f"
+ byteorder = dt.byteorder
+ if byteorder == "=":
+ byteorder = "<" if sys.byteorder == "little" else ">"
+
+ return int(
+ bitsize
+ | (lib.SDL_AUDIO_MASK_FLOAT * is_float)
+ | (lib.SDL_AUDIO_MASK_BIG_ENDIAN * (byteorder == ">"))
+ | (lib.SDL_AUDIO_MASK_SIGNED * is_signed)
+ )
+
+
+def _dtype_from_format(format: int, /) -> np.dtype[Any]: # noqa: A002
+ """Return a dtype from a SDL_AudioFormat.
+
+ >>> _dtype_from_format(tcod.lib.SDL_AUDIO_F32LE)
+ dtype('float32')
+ >>> _dtype_from_format(tcod.lib.SDL_AUDIO_F32BE)
+ dtype('>f4')
+ >>> _dtype_from_format(tcod.lib.SDL_AUDIO_S16LE)
+ dtype('int16')
+ >>> _dtype_from_format(tcod.lib.SDL_AUDIO_S16BE)
+ dtype('>i2')
+ >>> _dtype_from_format(tcod.lib.SDL_AUDIO_S8)
+ dtype('int8')
+ >>> _dtype_from_format(tcod.lib.SDL_AUDIO_U8)
+ dtype('uint8')
+ """
+ bitsize = format & lib.SDL_AUDIO_MASK_BITSIZE
+ assert bitsize % 8 == 0
+ byte_size = bitsize // 8
+ byteorder = ">" if format & lib.SDL_AUDIO_MASK_BIG_ENDIAN else "<"
+ if format & lib.SDL_AUDIO_MASK_FLOAT:
+ kind = "f"
+ elif format & lib.SDL_AUDIO_MASK_SIGNED:
+ kind = "i"
+ else:
+ kind = "u"
+ return np.dtype(f"{byteorder}{kind}{byte_size}")
+
+
+def _silence_value_for_format(dtype: DTypeLike, /) -> int:
+ """Return the silence value for the given dtype format."""
+ return int(lib.SDL_GetSilenceValueForFormat(_get_format(dtype)))
+
+
+class _AudioSpec(NamedTuple):
+ """Named tuple for `SDL_AudioSpec`."""
+
+ format: int
+ channels: int
+ frequency: int
+
+ @classmethod
+ def from_c(cls, c_spec_p: Any) -> Self: # noqa: ANN401
+ return cls(int(c_spec_p.format), int(c_spec_p.channels), int(c_spec_p.freq))
+
+ @property
+ def _dtype(self) -> np.dtype[Any]:
+ return _dtype_from_format(self.format)
+
+
+def convert_audio(
+ in_sound: ArrayLike, in_rate: int, *, out_rate: int, out_format: DTypeLike, out_channels: int
+) -> NDArray[np.number]:
+ """Convert an audio sample into a format supported by this device.
+
+ Returns the converted array in the shape `(sample, channel)`.
+ This will reference the input array data if no conversion was needed.
+
+ Args:
+ in_sound: The input ArrayLike sound sample. Input format and channels are derived from the array.
+ in_rate: The sample-rate of the input array.
+ out_rate: The sample-rate of the output array.
+ out_format: The output format of the converted array.
+ out_channels: The number of audio channels of the output array.
+
+ Examples::
+
+ >>> tcod.sdl.audio.convert_audio(np.zeros(5), 44100, out_rate=44100, out_format=np.uint8, out_channels=1).T
+ array([[128, 128, 128, 128, 128]], dtype=uint8)
+ >>> tcod.sdl.audio.convert_audio(np.zeros(3), 22050, out_rate=44100, out_format=np.int8, out_channels=2).T
+ array([[0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0]], dtype=int8)
+
+ .. versionadded:: 13.6
+
+ .. versionchanged:: 16.0
+ Now converts floating types to `np.float32` when SDL doesn't support the specific format.
+
+ .. seealso::
+ :any:`AudioDevice.convert`
+ """
+ in_array: NDArray[Any] = np.asarray(in_sound)
+ if len(in_array.shape) == 1:
+ in_array = in_array[:, np.newaxis]
+ elif len(in_array.shape) != 2: # noqa: PLR2004
+ msg = f"Expected a 1 or 2 ndim input, got {in_array.shape} instead."
+ raise TypeError(msg)
+ in_spec = _AudioSpec(format=_get_format(in_array.dtype), channels=in_array.shape[1], frequency=in_rate)
+ out_spec = _AudioSpec(format=_get_format(out_format), channels=out_channels, frequency=out_rate)
+ if in_spec == out_spec:
+ return in_array # No conversion needed
+
+ out_buffer = ffi.new("uint8_t**")
+ out_length = ffi.new("int*")
+ try:
+ _check(
+ lib.SDL_ConvertAudioSamples(
+ [in_spec],
+ ffi.from_buffer("const uint8_t*", in_array),
+ len(in_array) * in_array.itemsize,
+ [out_spec],
+ out_buffer,
+ out_length,
+ )
+ )
+ return ( # type: ignore[no-any-return]
+ np.frombuffer(ffi.buffer(out_buffer[0], out_length[0]), dtype=out_format).reshape(-1, out_channels).copy()
+ )
+ except RuntimeError as exc:
+ if ( # SDL now only supports float32, but later versions may add more support for more formats.
+ exc.args[0] == "Parameter 'src_spec->format' is invalid"
+ and np.issubdtype(in_array.dtype, np.floating)
+ and in_array.dtype != np.float32
+ ):
+ return convert_audio( # Try again with float32
+ in_array.astype(np.float32),
+ in_rate,
+ out_rate=out_rate,
+ out_format=out_format,
+ out_channels=out_channels,
+ )
+ raise
+ finally:
+ lib.SDL_free(out_buffer[0])
+
+
+class AudioDevice:
+ """An SDL audio device.
+
+ Example::
+
+ device = tcod.sdl.audio.get_default_playback().open() # Open a common audio device
+
+ .. versionchanged:: 16.0
+ Can now be used as a context which will close the device on exit.
+
+ .. versionchanged:: 19.0
+ Removed `spec` and `callback` attribute.
+
+ `queued_samples`, `queue_audio`, and `dequeue_audio` moved to :any:`AudioStream` class.
+
+ """
+
+ __slots__ = (
+ "__weakref__",
+ "_device_id",
+ "buffer_bytes",
+ "buffer_samples",
+ "channels",
+ "device_id",
+ "format",
+ "frequency",
+ "is_capture",
+ "is_physical",
+ "silence",
+ )
+
+ def __init__(
+ self,
+ device_id: Any, # noqa: ANN401
+ /,
+ ) -> None:
+ """Initialize the class from a raw `SDL_AudioDeviceID`."""
+ assert device_id >= 0
+ assert ffi.typeof(device_id) is ffi.typeof("SDL_AudioDeviceID"), ffi.typeof(device_id)
+ spec = ffi.new("SDL_AudioSpec*")
+ samples = ffi.new("int*")
+ _check(lib.SDL_GetAudioDeviceFormat(device_id, spec, samples))
+ self._device_id: object = device_id
+ self.device_id: Final[int] = int(device_id)
+ """The SDL device identifier used for SDL C functions."""
+ self.frequency: Final[int] = spec.freq
+ """The audio device sound frequency."""
+ self.is_capture: Final[bool] = bool(not lib.SDL_IsAudioDevicePlayback(device_id))
+ """True if this is a recording device instead of an output device."""
+ self.format: Final[np.dtype[Any]] = _dtype_from_format(spec.format)
+ """The format used for audio samples with this device."""
+ self.channels: Final[int] = int(spec.channels)
+ """The number of audio channels for this device."""
+ self.silence: float = int(lib.SDL_GetSilenceValueForFormat(spec.format))
+ """The value of silence, according to SDL."""
+ self.buffer_samples: Final[int] = int(samples[0])
+ """The size of the audio buffer in samples."""
+ self.buffer_bytes: Final[int] = int(self.format.itemsize * self.channels * self.buffer_samples)
+ """The size of the audio buffer in bytes."""
+ self.is_physical: Final[bool] = bool(lib.SDL_IsAudioDevicePhysical(device_id))
+ """True of this is a physical device, or False if this is a logical device.
+
+ .. versionadded:: 19.0
+ """
+
+ def __repr__(self) -> str:
+ """Return a representation of this device."""
+ items = [
+ f"{self.__class__.__name__}(device_id={self.device_id})",
+ f"frequency={self.frequency}",
+ f"is_capture={self.is_capture}",
+ f"is_physical={self.is_physical}",
+ f"format={self.format}",
+ f"channels={self.channels}",
+ f"buffer_samples={self.buffer_samples}",
+ f"buffer_bytes={self.buffer_bytes}",
+ f"paused={self.paused}",
+ ]
+
+ if self.silence:
+ items.append(f"silence={self.silence}")
+ return f"""<{" ".join(items)}>"""
+
+ @property
+ def name(self) -> str:
+ """Name of the device.
+
+ .. versionadded:: 19.0
+ """
+ return str(ffi.string(_check_p(lib.SDL_GetAudioDeviceName(self.device_id))), encoding="utf-8")
+
+ @property
+ def gain(self) -> float:
+ """Get or set the logical audio device gain.
+
+ Default is 1.0 but can be set higher or zero.
+
+ .. versionadded:: 19.0
+ """
+ return _check_float(lib.SDL_GetAudioDeviceGain(self.device_id), failure=-1.0)
+
+ @gain.setter
+ def gain(self, value: float, /) -> None:
+ _check(lib.SDL_SetAudioDeviceGain(self.device_id, value))
+
+ def open(
+ self,
+ format: DTypeLike | None = None, # noqa: A002
+ channels: int | None = None,
+ frequency: int | None = None,
+ ) -> Self:
+ """Open a new logical audio device for this device.
+
+ .. versionadded:: 19.0
+
+ .. seealso::
+ https://wiki.libsdl.org/SDL3/SDL_OpenAudioDevice
+ """
+ new_spec = _AudioSpec(
+ format=_get_format(format if format is not None else self.format),
+ channels=channels if channels is not None else self.channels,
+ frequency=frequency if frequency is not None else self.frequency,
+ )
+ return self.__class__(
+ ffi.gc(
+ ffi.cast(
+ "SDL_AudioDeviceID", _check_int(lib.SDL_OpenAudioDevice(self.device_id, (new_spec,)), failure=0)
+ ),
+ lib.SDL_CloseAudioDevice,
+ )
+ )
+
+ @property
+ def _sample_size(self) -> int:
+ """The size of a sample in bytes."""
+ return self.format.itemsize * self.channels
+
+ @property
+ @deprecated("This is no longer used by the SDL3 API")
+ def stopped(self) -> bool:
+ """Is True if the device has failed or was closed.
+
+ .. deprecated:: 19.0
+ No longer used by the SDL3 API.
+ """
+ return bool(not hasattr(self, "device_id"))
+
+ @property
+ def paused(self) -> bool:
+ """Get or set the device paused state."""
+ return bool(lib.SDL_AudioDevicePaused(self.device_id))
+
+ @paused.setter
+ def paused(self, value: bool) -> None:
+ if value:
+ _check(lib.SDL_PauseAudioDevice(self.device_id))
+ else:
+ _check(lib.SDL_ResumeAudioDevice(self.device_id))
+
+ def _verify_array_format(self, samples: NDArray[Any]) -> NDArray[Any]:
+ if samples.dtype != self.format:
+ msg = f"Expected an array of dtype {self.format}, got {samples.dtype} instead."
+ raise TypeError(msg)
+ return samples
+
+ def _convert_array(self, samples_: ArrayLike) -> NDArray[np.number]:
+ if isinstance(samples_, np.ndarray):
+ samples_ = self._verify_array_format(samples_)
+ samples: NDArray[np.number] = np.asarray(samples_, dtype=self.format)
+ if len(samples.shape) < 2: # noqa: PLR2004
+ samples = samples[:, np.newaxis]
+ return np.ascontiguousarray(np.broadcast_to(samples, (samples.shape[0], self.channels)), dtype=self.format)
+
+ def convert(self, sound: ArrayLike, rate: int | None = None) -> NDArray[np.number]:
+ """Convert an audio sample into a format supported by this device.
+
+ Returns the converted array. This might be a reference to the input array if no conversion was needed.
+
+ Args:
+ sound: An ArrayLike sound sample.
+ rate: The sample-rate of the input array.
+ If None is given then it's assumed to be the same as the device.
+
+ .. versionadded:: 13.6
+
+ .. seealso::
+ :any:`convert_audio`
+ """
+ in_array: NDArray[Any] = np.asarray(sound)
+ if len(in_array.shape) == 1:
+ in_array = in_array[:, np.newaxis]
+ return convert_audio(
+ in_sound=sound,
+ in_rate=rate if rate is not None else self.frequency,
+ out_channels=self.channels if in_array.shape[1] > 1 else 1,
+ out_format=self.format,
+ out_rate=self.frequency,
+ )
+
+ def close(self) -> None:
+ """Close this audio device. Using this object after it has been closed is invalid."""
+ if not hasattr(self, "device_id"):
+ return
+ ffi.release(self._device_id)
+ del self._device_id
+
+ @deprecated("Use contextlib.closing if you want to close this device after a context.")
+ def __enter__(self) -> Self:
+ """Return self and enter a managed context.
+
+ .. deprecated:: 19.0
+ Use :func:`contextlib.closing` if you want to close this device after a context.
+ """
+ return self
+
+ def __exit__(
+ self,
+ _type: builtins.type[BaseException] | None, # Explicit builtins prefix to disambiguate Sphinx cross-reference
+ _value: BaseException | None,
+ _traceback: TracebackType | None,
+ /,
+ ) -> None:
+ """Close the device when exiting the context."""
+ self.close()
+
+ @staticmethod
+ def __default_callback(device: AudioDevice, stream: NDArray[Any]) -> None:
+ stream[...] = device.silence
+
+ def new_stream(
+ self,
+ format: DTypeLike, # noqa: A002
+ channels: int,
+ frequency: int,
+ ) -> AudioStream:
+ """Create, bind, and return a new :any:`AudioStream` for this device.
+
+ .. versionadded:: 19.0
+ """
+ new_stream = AudioStream.new(format=format, channels=channels, frequency=frequency)
+ self.bind((new_stream,))
+ return new_stream
+
+ def bind(self, streams: Iterable[AudioStream], /) -> None:
+ """Bind one or more :any:`AudioStream`'s to this device.
+
+ .. seealso::
+ https://wiki.libsdl.org/SDL3/SDL_BindAudioStreams
+ """
+ streams = list(streams)
+ _check(lib.SDL_BindAudioStreams(self.device_id, [s._stream_p for s in streams], len(streams)))
+
+
+@dataclass(frozen=True)
+class AudioStreamCallbackData:
+ """Data provided to AudioStream callbacks.
+
+ .. versionadded:: 19.0
+ """
+
+ additional_bytes: int
+ """Amount of bytes needed to fulfill the request of the caller. Can be zero."""
+ additional_samples: int
+ """Amount of samples needed to fulfill the request of the caller. Can be zero."""
+ total_bytes: int
+ """Amount of bytes requested or provided by the caller."""
+ total_samples: int
+ """Amount of samples requested or provided by the caller."""
+
+
+_audio_stream_get_callbacks: dict[AudioStream, Callable[[AudioStream, AudioStreamCallbackData], Any]] = {}
+_audio_stream_put_callbacks: dict[AudioStream, Callable[[AudioStream, AudioStreamCallbackData], Any]] = {}
+
+_audio_stream_registry: weakref.WeakValueDictionary[int, AudioStream] = weakref.WeakValueDictionary()
+
+
+class AudioStream:
+ """An SDL audio stream.
+
+ This class is commonly created with :any:`AudioDevice.new_stream` which creates a new stream bound to the device.
+
+ .. versionadded:: 19.0
+ """
+
+ __slots__ = ("__weakref__", "_stream_p")
+
+ _stream_p: Any
+
+ def __new__( # noqa: PYI034
+ cls,
+ stream_p: Any, # noqa: ANN401
+ /,
+ ) -> AudioStream:
+ """Return an AudioStream for the provided `SDL_AudioStream*` C pointer."""
+ assert ffi.typeof(stream_p) is ffi.typeof("SDL_AudioStream*"), ffi.typeof(stream_p)
+ stream_int = int(ffi.cast("intptr_t", stream_p))
+ self = super().__new__(cls)
+ self._stream_p = stream_p
+ return _audio_stream_registry.setdefault(stream_int, self)
+
+ @classmethod
+ def new( # noqa: PLR0913
+ cls,
+ format: DTypeLike, # noqa: A002
+ channels: int,
+ frequency: int,
+ out_format: DTypeLike | None = None,
+ out_channels: int | None = None,
+ out_frequency: int | None = None,
+ ) -> Self:
+ """Create a new unbound AudioStream."""
+ in_spec = _AudioSpec(format=_get_format(format), channels=channels, frequency=frequency)
+ out_spec = _AudioSpec(
+ format=_get_format(out_format) if out_format is not None else in_spec.format,
+ channels=out_channels if out_channels is not None else channels,
+ frequency=out_frequency if out_frequency is not None else frequency,
+ )
+ return cls(ffi.gc(_check_p(lib.SDL_CreateAudioStream((in_spec,), (out_spec,))), lib.SDL_DestroyAudioStream))
+
+ def close(self) -> None:
+ """Close this AudioStream and release its resources."""
+ if not hasattr(self, "_stream_p"):
+ return
+ self.getter_callback = None
+ self.putter_callback = None
+ ffi.release(self._stream_p)
+
+ def unbind(self) -> None:
+ """Unbind this stream from its currently bound device."""
+ lib.SDL_UnbindAudioStream(self._stream_p)
+
+ @property
+ @contextlib.contextmanager
+ def _lock(self) -> Iterator[None]:
+ """Lock context for this stream."""
+ try:
+ lib.SDL_LockAudioStream(self._stream_p)
+ yield
+ finally:
+ lib.SDL_UnlockAudioStream(self._stream_p)
+
+ @property
+ def _src_spec(self) -> _AudioSpec:
+ c_spec = ffi.new("SDL_AudioSpec*")
+ _check(lib.SDL_GetAudioStreamFormat(self._stream_p, c_spec, ffi.NULL))
+ return _AudioSpec.from_c(c_spec)
+
+ @property
+ def _src_sample_size(self) -> int:
+ spec = self._src_spec
+ return spec._dtype.itemsize * spec.channels
+
+ @property
+ def _dst_sample_size(self) -> int:
+ spec = self._dst_spec
+ return spec._dtype.itemsize * spec.channels
+
+ @property
+ def _dst_spec(self) -> _AudioSpec:
+ c_spec = ffi.new("SDL_AudioSpec*")
+ _check(lib.SDL_GetAudioStreamFormat(self._stream_p, ffi.NULL, c_spec))
+ return _AudioSpec.from_c(c_spec)
+
+ @property
+ def queued_bytes(self) -> int:
+ """The current amount of bytes remaining in the audio queue."""
+ return _check_int(lib.SDL_GetAudioStreamQueued(self._stream_p), failure=-1)
+
+ @property
+ def queued_samples(self) -> int:
+ """The estimated amount of samples remaining in the audio queue."""
+ return self.queued_bytes // self._src_sample_size
+
+ @property
+ def available_bytes(self) -> int:
+ """The current amount of converted data in this audio stream."""
+ return _check_int(lib.SDL_GetAudioStreamAvailable(self._stream_p), failure=-1)
+
+ @property
+ def available_samples(self) -> int:
+ """The current amount of converted samples in this audio stream."""
+ return self.available_bytes // self._dst_sample_size
+
+ def queue_audio(self, samples: ArrayLike) -> None:
+ """Append audio samples to the audio data queue."""
+ with self._lock:
+ src_spec = self._src_spec
+ src_format = _dtype_from_format(src_spec.format)
+ if isinstance(samples, np.ndarray) and samples.dtype != src_format:
+ msg = f"Expected an array of dtype {src_format}, got {samples.dtype} instead."
+ raise TypeError(msg)
+ samples = np.asarray(samples, dtype=src_format)
+ if len(samples.shape) < 2: # noqa: PLR2004
+ samples = samples[:, np.newaxis]
+ samples = np.ascontiguousarray(
+ np.broadcast_to(samples, (samples.shape[0], src_spec.channels)), dtype=src_format
+ )
+ buffer = ffi.from_buffer(samples)
+ _check(lib.SDL_PutAudioStreamData(self._stream_p, buffer, len(buffer)))
+
+ def flush(self) -> None:
+ """Ensure all queued data is available.
+
+ This may queue silence to the end of the stream.
+
+ .. seealso::
+ https://wiki.libsdl.org/SDL3/SDL_FlushAudioStream
+ """
+ _check(lib.SDL_FlushAudioStream(self._stream_p))
+
+ def dequeue_audio(self) -> NDArray[Any]:
+ """Return the converted output audio from this stream."""
+ with self._lock:
+ dst_spec = self._dst_spec
+ out_samples = self.available_samples
+ out = np.empty((out_samples, dst_spec.channels), _dtype_from_format(dst_spec.format))
+ buffer = ffi.from_buffer(out)
+ bytes_returned = _check_int(lib.SDL_GetAudioStreamData(self._stream_p, buffer, len(buffer)), failure=-1)
+ samples_returned = bytes_returned // self._dst_sample_size
+ return out[:samples_returned]
+
+ @property
+ def gain(self) -> float:
+ """Get or set the audio stream gain.
+
+ Default is 1.0 but can be set higher or zero.
+ """
+ return _check_float(lib.SDL_GetAudioStreamGain(self._stream_p), failure=-1.0)
+
+ @gain.setter
+ def gain(self, value: float, /) -> None:
+ _check(lib.SDL_SetAudioStreamGain(self._stream_p, value))
+
+ @property
+ def frequency_ratio(self) -> float:
+ """Get or set the frequency ratio, affecting the speed and pitch of the stream.
+
+ Higher values play the audio faster.
+
+ Default is 1.0.
+ """
+ return _check_float(lib.SDL_GetAudioStreamFrequencyRatio(self._stream_p), failure=-1.0)
+
+ @frequency_ratio.setter
+ def frequency_ratio(self, value: float, /) -> None:
+ _check(lib.SDL_SetAudioStreamFrequencyRatio(self._stream_p, value))
+
+ @property
+ def getter_callback(self) -> Callable[[AudioStream, AudioStreamCallbackData], Any] | None:
+ """Get or assign the stream get-callback for this stream.
+
+ .. seealso::
+ https://wiki.libsdl.org/SDL3/SDL_SetAudioStreamGetCallback
+ """
+ return _audio_stream_get_callbacks.get(self)
+
+ @getter_callback.setter
+ def getter_callback(self, callback: Callable[[AudioStream, AudioStreamCallbackData], Any] | None, /) -> None:
+ if callback is None:
+ _check(lib.SDL_SetAudioStreamGetCallback(self._stream_p, ffi.NULL, ffi.NULL))
+ _audio_stream_get_callbacks.pop(self, None)
+ else:
+ _audio_stream_get_callbacks[self] = callback
+ _check(
+ lib.SDL_SetAudioStreamGetCallback(self._stream_p, lib._sdl_audio_stream_callback, ffi.cast("void*", 0))
+ )
+
+ @property
+ def putter_callback(self) -> Callable[[AudioStream, AudioStreamCallbackData], Any] | None:
+ """Get or assign the stream put-callback for this stream.
+
+ .. seealso::
+ https://wiki.libsdl.org/SDL3/SDL_SetAudioStreamPutCallback
+ """
+ return _audio_stream_put_callbacks.get(self)
+
+ @putter_callback.setter
+ def putter_callback(self, callback: Callable[[AudioStream, AudioStreamCallbackData], Any] | None, /) -> None:
+ if callback is None:
+ _check(lib.SDL_SetAudioStreamPutCallback(self._stream_p, ffi.NULL, ffi.NULL))
+ _audio_stream_put_callbacks.pop(self, None)
+ else:
+ _audio_stream_put_callbacks[self] = callback
+ _check(
+ lib.SDL_SetAudioStreamPutCallback(self._stream_p, lib._sdl_audio_stream_callback, ffi.cast("void*", 1))
+ )
+
+
+class _LoopSoundFunc:
+ def __init__(self, sound: NDArray[Any], loops: int, on_end: Callable[[Channel], None] | None) -> None:
+ self.sound = sound
+ self.loops = loops
+ self.on_end = on_end
+
+ def __call__(self, channel: Channel) -> None:
+ if not self.loops:
+ if self.on_end is not None:
+ self.on_end(channel)
+ return
+ channel.play(self.sound, volume=channel.volume, on_end=self)
+ if self.loops > 0:
+ self.loops -= 1
+
+
+class Channel:
+ """An audio channel for :any:`BasicMixer`. Use :any:`BasicMixer.get_channel` to initialize this object.
+
+ .. versionadded:: 13.6
+ """
+
+ mixer: BasicMixer
+ """The :any:`BasicMixer` is channel belongs to."""
+
+ def __init__(self) -> None:
+ """Initialize this channel with generic attributes."""
+ self._lock = threading.RLock()
+ self.volume: float | tuple[float, ...] = 1.0
+ self.sound_queue: list[NDArray[Any]] = []
+ self.on_end_callback: Callable[[Channel], None] | None = None
+
+ @property
+ def busy(self) -> bool:
+ """Is True when this channel is playing audio."""
+ return bool(self.sound_queue)
+
+ def play(
+ self,
+ sound: ArrayLike,
+ *,
+ volume: float | tuple[float, ...] = 1.0,
+ loops: int = 0,
+ on_end: Callable[[Channel], None] | None = None,
+ ) -> None:
+ """Play an audio sample, stopping any audio currently playing on this channel.
+
+ Parameters are the same as :any:`BasicMixer.play`.
+ """
+ sound = self._verify_audio_sample(sound)
+ with self._lock:
+ self.volume = volume
+ self.sound_queue[:] = [sound]
+ self.on_end_callback = on_end
+ if loops:
+ self.on_end_callback = _LoopSoundFunc(sound, loops, on_end)
+
+ def _verify_audio_sample(self, sample: ArrayLike) -> NDArray[Any]:
+ """Verify an audio sample is valid and return it as a Numpy array."""
+ array: NDArray[Any] = np.asarray(sample)
+ if array.dtype != self.mixer.device.format:
+ msg = f"Audio sample must be dtype={self.mixer.device.format}, input was dtype={array.dtype}"
+ raise TypeError(msg)
+ if len(array.shape) == 1:
+ array = array[:, np.newaxis]
+ return array
+
+ def _on_mix(self, stream: NDArray[Any]) -> None:
+ """Mix the next part of this channels audio into an active audio stream."""
+ with self._lock:
+ while self.sound_queue and stream.size:
+ buffer = self.sound_queue[0]
+ if buffer.shape[0] > stream.shape[0]:
+ # Mix part of the buffer into the stream.
+ stream[:] += buffer[: stream.shape[0]] * self.volume
+ self.sound_queue[0] = buffer[stream.shape[0] :]
+ break # Stream was filled.
+ # Remaining buffer fits the stream array.
+ stream[: buffer.shape[0]] += buffer * self.volume
+ stream = stream[buffer.shape[0] :]
+ self.sound_queue.pop(0)
+ if not self.sound_queue and self.on_end_callback is not None:
+ self.on_end_callback(self)
+
+ def fadeout(self, time: float) -> None:
+ """Fadeout this channel then stop playing."""
+ with self._lock:
+ if not self.sound_queue:
+ return
+ time_samples = round(time * self.mixer.device.frequency) + 1
+ buffer: NDArray[np.float32] = np.zeros((time_samples, self.mixer.device.channels), np.float32)
+ self._on_mix(buffer)
+ buffer *= np.linspace(1.0, 0.0, time_samples + 1, endpoint=False)[1:, np.newaxis]
+ self.sound_queue[:] = [buffer]
+
+ def stop(self) -> None:
+ """Stop audio on this channel."""
+ self.fadeout(0.0005)
+
+
+@deprecated(
+ "Changes in the SDL3 API have made this classes usefulness questionable."
+ "\nThis class should be replaced with custom streams."
+)
+class BasicMixer:
+ """An SDL sound mixer implemented in Python and Numpy.
+
+ Example::
+
+ import time
+
+ import soundfile # pip install soundfile
+ import tcod.sdl.audio
+
+ device = tcod.sdl.audio.get_default_playback().open()
+ mixer = tcod.sdl.audio.BasicMixer(device) # Setup BasicMixer with the default audio output
+ sound, sample_rate = soundfile.read("example_sound.wav") # Load an audio sample using SoundFile
+ sound = mixer.device.convert(sound, sample_rate) # Convert this sample to the format expected by the device
+ channel = mixer.play(sound) # Start asynchronous playback, audio is mixed on a separate Python thread
+ while channel.busy: # Wait until the sample is done playing
+ time.sleep(0.001)
+
+
+ .. versionadded:: 13.6
+
+ .. versionchanged:: 19.0
+ Added `frequency` and `channels` parameters.
+
+ .. deprecated:: 19.0
+ Changes in the SDL3 API have made this classes usefulness questionable.
+ This class should be replaced with custom streams.
+ """
+
+ def __init__(self, device: AudioDevice, *, frequency: int | None = None, channels: int | None = None) -> None:
+ """Initialize this mixer using the provided device."""
+ self.channels: dict[Hashable, Channel] = {}
+ self.device = device
+ """The :any:`AudioDevice`"""
+ self._frequency = frequency if frequency is not None else device.frequency
+ self._channels = channels if channels is not None else device.channels
+ self._lock = threading.RLock()
+ self._stream = device.new_stream(format=np.float32, frequency=self._frequency, channels=self._channels)
+ self._stream.getter_callback = self._on_stream
+
+ def close(self) -> None:
+ """Shutdown this mixer, all playing audio will be abruptly stopped."""
+ self._stream.close()
+
+ def get_channel(self, key: Hashable) -> Channel:
+ """Return a channel tied to with the given key.
+
+ Channels are initialized as you access them with this function.
+ :any:`int` channels starting from zero are used internally.
+
+ This can be used to generate a ``"music"`` channel for example.
+ """
+ with self._lock:
+ if key not in self.channels:
+ self.channels[key] = Channel()
+ self.channels[key].mixer = self
+ return self.channels[key]
+
+ def _get_next_channel(self) -> Channel:
+ """Return the next available channel for the play method."""
+ with self._lock:
+ i = 0
+ while True:
+ if not self.get_channel(i).busy:
+ return self.channels[i]
+ i += 1
+
+ def play(
+ self,
+ sound: ArrayLike,
+ *,
+ volume: float | tuple[float, ...] = 1.0,
+ loops: int = 0,
+ on_end: Callable[[Channel], None] | None = None,
+ ) -> Channel:
+ """Play a sound, return the channel the sound is playing on.
+
+ Args:
+ sound: The sound to play. This a Numpy array matching the format of the loaded audio device.
+ volume: The volume to play the sound at.
+ You can also pass a tuple of floats to set the volume for each channel/speaker.
+ loops: How many times to play the sound, `-1` can be used to loop the sound forever.
+ on_end: A function to call when this sound has ended.
+ This is called with the :any:`Channel` which was playing the sound.
+ """
+ channel = self._get_next_channel()
+ channel.play(sound, volume=volume, loops=loops, on_end=on_end)
+ return channel
+
+ def stop(self) -> None:
+ """Stop playback on all channels from this mixer."""
+ with self._lock:
+ for channel in self.channels.values():
+ channel.stop()
+
+ def _on_stream(self, audio_stream: AudioStream, data: AudioStreamCallbackData) -> None:
+ """Called to fill the audio buffer."""
+ if data.additional_samples <= 0:
+ return
+ stream: NDArray[np.float32] = np.zeros((data.additional_samples, self._channels), dtype=np.float32)
+ with self._lock:
+ for channel in list(self.channels.values()):
+ channel._on_mix(stream)
+ audio_stream.queue_audio(stream)
+
+
+@ffi.def_extern() # type: ignore[untyped-decorator]
+def _sdl_audio_stream_callback(userdata: Any, stream_p: Any, additional_amount: int, total_amount: int, /) -> None: # noqa: ANN401
+ """Handle audio device callbacks."""
+ stream = AudioStream(stream_p)
+ is_put_callback = bool(userdata)
+ callback = (_audio_stream_put_callbacks if is_put_callback else _audio_stream_get_callbacks).get(stream)
+ if callback is None:
+ return
+ sample_size = stream._dst_sample_size if is_put_callback else stream._src_sample_size
+ callback(
+ stream,
+ AudioStreamCallbackData(
+ additional_bytes=additional_amount,
+ additional_samples=additional_amount // sample_size,
+ total_bytes=total_amount,
+ total_samples=total_amount // sample_size,
+ ),
+ )
+
+
+def get_devices() -> dict[str, AudioDevice]:
+ """Iterate over the available audio output devices.
+
+ .. versionchanged:: 19.0
+ Now returns a dictionary of :any:`AudioDevice`.
+ """
+ tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.AUDIO)
+ count = ffi.new("int[1]")
+ devices_array = ffi.gc(lib.SDL_GetAudioPlaybackDevices(count), lib.SDL_free)
+ return {
+ device.name: device
+ for device in (AudioDevice(ffi.cast("SDL_AudioDeviceID", p)) for p in devices_array[0 : count[0]])
+ }
+
+
+def get_capture_devices() -> dict[str, AudioDevice]:
+ """Iterate over the available audio capture devices.
+
+ .. versionchanged:: 19.0
+ Now returns a dictionary of :any:`AudioDevice`.
+ """
+ tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.AUDIO)
+ count = ffi.new("int[1]")
+ devices_array = ffi.gc(lib.SDL_GetAudioRecordingDevices(count), lib.SDL_free)
+ return {
+ device.name: device
+ for device in (AudioDevice(ffi.cast("SDL_AudioDeviceID", p)) for p in devices_array[0 : count[0]])
+ }
+
+
+def get_default_playback() -> AudioDevice:
+ """Return the default playback device.
+
+ Example::
+
+ playback_device = tcod.sdl.audio.get_default_playback().open()
+
+ .. versionadded:: 19.0
+ """
+ tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.AUDIO)
+ return AudioDevice(ffi.cast("SDL_AudioDeviceID", lib.SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK))
+
+
+def get_default_recording() -> AudioDevice:
+ """Return the default recording device.
+
+ Example::
+
+ recording_device = tcod.sdl.audio.get_default_recording().open()
+
+ .. versionadded:: 19.0
+ """
+ tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.AUDIO)
+ return AudioDevice(ffi.cast("SDL_AudioDeviceID", lib.SDL_AUDIO_DEVICE_DEFAULT_RECORDING))
+
+
+@deprecated("This is no longer used", category=FutureWarning)
+class AllowedChanges(enum.IntFlag):
+ """Which parameters are allowed to be changed when the values given are not supported.
+
+ .. deprecated:: 19.0
+ This is no longer used.
+ """
+
+ NONE = 0
+ """"""
+ FREQUENCY = 0x01
+ """"""
+ FORMAT = 0x02
+ """"""
+ CHANNELS = 0x04
+ """"""
+ SAMPLES = 0x08
+ """"""
+ ANY = FREQUENCY | FORMAT | CHANNELS | SAMPLES
+ """"""
+
+
+@deprecated(
+ "This is an outdated method.\nUse 'tcod.sdl.audio.get_default_playback().open()' instead.", category=FutureWarning
+)
+def open( # noqa: A001, PLR0913
+ name: str | None = None,
+ capture: bool = False, # noqa: FBT001, FBT002
+ *,
+ frequency: int = 44100,
+ format: DTypeLike = np.float32, # noqa: A002
+ channels: int = 2,
+ samples: int = 0, # noqa: ARG001
+ allowed_changes: AllowedChanges = AllowedChanges.NONE, # noqa: ARG001
+ paused: bool = False,
+ callback: None | Literal[True] | Callable[[AudioDevice, NDArray[Any]], None] = None,
+) -> AudioDevice:
+ """Open an audio device for playback or capture and return it.
+
+ Args:
+ name: The name of the device to open, or None for the most reasonable default.
+ capture: True if this is a recording device, or False if this is an output device.
+ frequency: The desired sample rate to open the device with.
+ format: The data format to use for samples as a NumPy dtype.
+ channels: The number of speakers for the device. 1, 2, 4, or 6 are typical options.
+ samples: This parameter is ignored.
+ allowed_changes: This parameter is ignored.
+ paused:
+ If True then the device will begin in a paused state.
+ It can then be unpaused by assigning False to :any:`AudioDevice.paused`.
+ callback: An optional callback to use, this is deprecated.
+
+ If a callback is given then it will be called with the `AudioDevice` and a Numpy buffer of the data stream.
+ This callback will be run on a separate thread.
+
+ .. versionchanged:: 19.0
+ SDL3 returns audio devices differently, exact formatting is set with :any:`AudioDevice.new_stream` instead.
+
+ `samples` and `allowed_changes` are ignored.
+
+ .. deprecated:: 19.0
+ This is an outdated method.
+ Use :any:`AudioDevice.open` instead, for example:
+ ``tcod.sdl.audio.get_default_playback().open()``
+ """
+ tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.AUDIO)
+ if name is None:
+ device = get_default_playback() if not capture else get_default_recording()
+ else:
+ device = (get_devices() if not capture else get_capture_devices())[name]
+ assert device.is_capture is capture
+ device = device.open(frequency=frequency, format=format, channels=channels)
+ device.paused = paused
+
+ if callback is not None and callback is not True:
+ stream = device.new_stream(format=format, channels=channels, frequency=frequency)
+
+ def _get_callback(stream: AudioStream, data: AudioStreamCallbackData) -> None:
+ if data.additional_samples <= 0:
+ return
+ buffer = np.full(
+ (data.additional_samples, channels), fill_value=_silence_value_for_format(format), dtype=format
+ )
+ callback(device, buffer)
+ stream.queue_audio(buffer)
+
+ stream.getter_callback = _get_callback
+
+ return device
diff --git a/tcod/sdl/constants.py b/tcod/sdl/constants.py
new file mode 100644
index 00000000..39d766f4
--- /dev/null
+++ b/tcod/sdl/constants.py
@@ -0,0 +1,489 @@
+"""SDL private constants."""
+
+SDL_PRILL_PREFIX = "ll"
+SDL_PRILLX = Ellipsis
+SDL_FUNCTION = "???"
+SDL_PROP_THREAD_CREATE_ENTRY_FUNCTION_POINTER = "SDL.thread.create.entry_function"
+SDL_PROP_THREAD_CREATE_NAME_STRING = "SDL.thread.create.name"
+SDL_PROP_THREAD_CREATE_USERDATA_POINTER = "SDL.thread.create.userdata"
+SDL_PROP_THREAD_CREATE_STACKSIZE_NUMBER = "SDL.thread.create.stacksize"
+SDL_PROP_IOSTREAM_WINDOWS_HANDLE_POINTER = "SDL.iostream.windows.handle"
+SDL_PROP_IOSTREAM_STDIO_FILE_POINTER = "SDL.iostream.stdio.file"
+SDL_PROP_IOSTREAM_FILE_DESCRIPTOR_NUMBER = "SDL.iostream.file_descriptor"
+SDL_PROP_IOSTREAM_ANDROID_AASSET_POINTER = "SDL.iostream.android.aasset"
+SDL_PROP_IOSTREAM_MEMORY_POINTER = "SDL.iostream.memory.base"
+SDL_PROP_IOSTREAM_MEMORY_SIZE_NUMBER = "SDL.iostream.memory.size"
+SDL_PROP_IOSTREAM_DYNAMIC_MEMORY_POINTER = "SDL.iostream.dynamic.memory"
+SDL_PROP_IOSTREAM_DYNAMIC_CHUNKSIZE_NUMBER = "SDL.iostream.dynamic.chunksize"
+SDL_PROP_SURFACE_SDR_WHITE_POINT_FLOAT = "SDL.surface.SDR_white_point"
+SDL_PROP_SURFACE_HDR_HEADROOM_FLOAT = "SDL.surface.HDR_headroom"
+SDL_PROP_SURFACE_TONEMAP_OPERATOR_STRING = "SDL.surface.tonemap"
+SDL_PROP_SURFACE_HOTSPOT_X_NUMBER = "SDL.surface.hotspot.x"
+SDL_PROP_SURFACE_HOTSPOT_Y_NUMBER = "SDL.surface.hotspot.y"
+SDL_PROP_GLOBAL_VIDEO_WAYLAND_WL_DISPLAY_POINTER = "SDL.video.wayland.wl_display"
+SDL_PROP_DISPLAY_HDR_ENABLED_BOOLEAN = "SDL.display.HDR_enabled"
+SDL_PROP_DISPLAY_KMSDRM_PANEL_ORIENTATION_NUMBER = "SDL.display.KMSDRM.panel_orientation"
+SDL_PROP_WINDOW_CREATE_ALWAYS_ON_TOP_BOOLEAN = "SDL.window.create.always_on_top"
+SDL_PROP_WINDOW_CREATE_BORDERLESS_BOOLEAN = "SDL.window.create.borderless"
+SDL_PROP_WINDOW_CREATE_FOCUSABLE_BOOLEAN = "SDL.window.create.focusable"
+SDL_PROP_WINDOW_CREATE_EXTERNAL_GRAPHICS_CONTEXT_BOOLEAN = "SDL.window.create.external_graphics_context"
+SDL_PROP_WINDOW_CREATE_FLAGS_NUMBER = "SDL.window.create.flags"
+SDL_PROP_WINDOW_CREATE_FULLSCREEN_BOOLEAN = "SDL.window.create.fullscreen"
+SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER = "SDL.window.create.height"
+SDL_PROP_WINDOW_CREATE_HIDDEN_BOOLEAN = "SDL.window.create.hidden"
+SDL_PROP_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN = "SDL.window.create.high_pixel_density"
+SDL_PROP_WINDOW_CREATE_MAXIMIZED_BOOLEAN = "SDL.window.create.maximized"
+SDL_PROP_WINDOW_CREATE_MENU_BOOLEAN = "SDL.window.create.menu"
+SDL_PROP_WINDOW_CREATE_METAL_BOOLEAN = "SDL.window.create.metal"
+SDL_PROP_WINDOW_CREATE_MINIMIZED_BOOLEAN = "SDL.window.create.minimized"
+SDL_PROP_WINDOW_CREATE_MODAL_BOOLEAN = "SDL.window.create.modal"
+SDL_PROP_WINDOW_CREATE_MOUSE_GRABBED_BOOLEAN = "SDL.window.create.mouse_grabbed"
+SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN = "SDL.window.create.opengl"
+SDL_PROP_WINDOW_CREATE_PARENT_POINTER = "SDL.window.create.parent"
+SDL_PROP_WINDOW_CREATE_RESIZABLE_BOOLEAN = "SDL.window.create.resizable"
+SDL_PROP_WINDOW_CREATE_TITLE_STRING = "SDL.window.create.title"
+SDL_PROP_WINDOW_CREATE_TRANSPARENT_BOOLEAN = "SDL.window.create.transparent"
+SDL_PROP_WINDOW_CREATE_TOOLTIP_BOOLEAN = "SDL.window.create.tooltip"
+SDL_PROP_WINDOW_CREATE_UTILITY_BOOLEAN = "SDL.window.create.utility"
+SDL_PROP_WINDOW_CREATE_VULKAN_BOOLEAN = "SDL.window.create.vulkan"
+SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER = "SDL.window.create.width"
+SDL_PROP_WINDOW_CREATE_X_NUMBER = "SDL.window.create.x"
+SDL_PROP_WINDOW_CREATE_Y_NUMBER = "SDL.window.create.y"
+SDL_PROP_WINDOW_CREATE_COCOA_WINDOW_POINTER = "SDL.window.create.cocoa.window"
+SDL_PROP_WINDOW_CREATE_COCOA_VIEW_POINTER = "SDL.window.create.cocoa.view"
+SDL_PROP_WINDOW_CREATE_WAYLAND_SURFACE_ROLE_CUSTOM_BOOLEAN = "SDL.window.create.wayland.surface_role_custom"
+SDL_PROP_WINDOW_CREATE_WAYLAND_CREATE_EGL_WINDOW_BOOLEAN = "SDL.window.create.wayland.create_egl_window"
+SDL_PROP_WINDOW_CREATE_WAYLAND_WL_SURFACE_POINTER = "SDL.window.create.wayland.wl_surface"
+SDL_PROP_WINDOW_CREATE_WIN32_HWND_POINTER = "SDL.window.create.win32.hwnd"
+SDL_PROP_WINDOW_CREATE_WIN32_PIXEL_FORMAT_HWND_POINTER = "SDL.window.create.win32.pixel_format_hwnd"
+SDL_PROP_WINDOW_CREATE_X11_WINDOW_NUMBER = "SDL.window.create.x11.window"
+SDL_PROP_WINDOW_SHAPE_POINTER = "SDL.window.shape"
+SDL_PROP_WINDOW_HDR_ENABLED_BOOLEAN = "SDL.window.HDR_enabled"
+SDL_PROP_WINDOW_SDR_WHITE_LEVEL_FLOAT = "SDL.window.SDR_white_level"
+SDL_PROP_WINDOW_HDR_HEADROOM_FLOAT = "SDL.window.HDR_headroom"
+SDL_PROP_WINDOW_ANDROID_WINDOW_POINTER = "SDL.window.android.window"
+SDL_PROP_WINDOW_ANDROID_SURFACE_POINTER = "SDL.window.android.surface"
+SDL_PROP_WINDOW_UIKIT_WINDOW_POINTER = "SDL.window.uikit.window"
+SDL_PROP_WINDOW_UIKIT_METAL_VIEW_TAG_NUMBER = "SDL.window.uikit.metal_view_tag"
+SDL_PROP_WINDOW_UIKIT_OPENGL_FRAMEBUFFER_NUMBER = "SDL.window.uikit.opengl.framebuffer"
+SDL_PROP_WINDOW_UIKIT_OPENGL_RENDERBUFFER_NUMBER = "SDL.window.uikit.opengl.renderbuffer"
+SDL_PROP_WINDOW_UIKIT_OPENGL_RESOLVE_FRAMEBUFFER_NUMBER = "SDL.window.uikit.opengl.resolve_framebuffer"
+SDL_PROP_WINDOW_KMSDRM_DEVICE_INDEX_NUMBER = "SDL.window.kmsdrm.dev_index"
+SDL_PROP_WINDOW_KMSDRM_DRM_FD_NUMBER = "SDL.window.kmsdrm.drm_fd"
+SDL_PROP_WINDOW_KMSDRM_GBM_DEVICE_POINTER = "SDL.window.kmsdrm.gbm_dev"
+SDL_PROP_WINDOW_COCOA_WINDOW_POINTER = "SDL.window.cocoa.window"
+SDL_PROP_WINDOW_COCOA_METAL_VIEW_TAG_NUMBER = "SDL.window.cocoa.metal_view_tag"
+SDL_PROP_WINDOW_OPENVR_OVERLAY_ID = "SDL.window.openvr.overlay_id"
+SDL_PROP_WINDOW_VIVANTE_DISPLAY_POINTER = "SDL.window.vivante.display"
+SDL_PROP_WINDOW_VIVANTE_WINDOW_POINTER = "SDL.window.vivante.window"
+SDL_PROP_WINDOW_VIVANTE_SURFACE_POINTER = "SDL.window.vivante.surface"
+SDL_PROP_WINDOW_WIN32_HWND_POINTER = "SDL.window.win32.hwnd"
+SDL_PROP_WINDOW_WIN32_HDC_POINTER = "SDL.window.win32.hdc"
+SDL_PROP_WINDOW_WIN32_INSTANCE_POINTER = "SDL.window.win32.instance"
+SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER = "SDL.window.wayland.display"
+SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER = "SDL.window.wayland.surface"
+SDL_PROP_WINDOW_WAYLAND_VIEWPORT_POINTER = "SDL.window.wayland.viewport"
+SDL_PROP_WINDOW_WAYLAND_EGL_WINDOW_POINTER = "SDL.window.wayland.egl_window"
+SDL_PROP_WINDOW_WAYLAND_XDG_SURFACE_POINTER = "SDL.window.wayland.xdg_surface"
+SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_POINTER = "SDL.window.wayland.xdg_toplevel"
+SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_EXPORT_HANDLE_STRING = "SDL.window.wayland.xdg_toplevel_export_handle"
+SDL_PROP_WINDOW_WAYLAND_XDG_POPUP_POINTER = "SDL.window.wayland.xdg_popup"
+SDL_PROP_WINDOW_WAYLAND_XDG_POSITIONER_POINTER = "SDL.window.wayland.xdg_positioner"
+SDL_PROP_WINDOW_X11_DISPLAY_POINTER = "SDL.window.x11.display"
+SDL_PROP_WINDOW_X11_SCREEN_NUMBER = "SDL.window.x11.screen"
+SDL_PROP_WINDOW_X11_WINDOW_NUMBER = "SDL.window.x11.window"
+SDL_PROP_FILE_DIALOG_FILTERS_POINTER = "SDL.filedialog.filters"
+SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER = "SDL.filedialog.nfilters"
+SDL_PROP_FILE_DIALOG_WINDOW_POINTER = "SDL.filedialog.window"
+SDL_PROP_FILE_DIALOG_LOCATION_STRING = "SDL.filedialog.location"
+SDL_PROP_FILE_DIALOG_MANY_BOOLEAN = "SDL.filedialog.many"
+SDL_PROP_FILE_DIALOG_TITLE_STRING = "SDL.filedialog.title"
+SDL_PROP_FILE_DIALOG_ACCEPT_STRING = "SDL.filedialog.accept"
+SDL_PROP_FILE_DIALOG_CANCEL_STRING = "SDL.filedialog.cancel"
+SDL_PROP_JOYSTICK_CAP_MONO_LED_BOOLEAN = "SDL.joystick.cap.mono_led"
+SDL_PROP_JOYSTICK_CAP_RGB_LED_BOOLEAN = "SDL.joystick.cap.rgb_led"
+SDL_PROP_JOYSTICK_CAP_PLAYER_LED_BOOLEAN = "SDL.joystick.cap.player_led"
+SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN = "SDL.joystick.cap.rumble"
+SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN = "SDL.joystick.cap.trigger_rumble"
+SDL_PROP_GAMEPAD_CAP_MONO_LED_BOOLEAN = Ellipsis
+SDL_PROP_GAMEPAD_CAP_RGB_LED_BOOLEAN = Ellipsis
+SDL_PROP_GAMEPAD_CAP_PLAYER_LED_BOOLEAN = Ellipsis
+SDL_PROP_GAMEPAD_CAP_RUMBLE_BOOLEAN = Ellipsis
+SDL_PROP_GAMEPAD_CAP_TRIGGER_RUMBLE_BOOLEAN = Ellipsis
+SDL_PROP_TEXTINPUT_TYPE_NUMBER = "SDL.textinput.type"
+SDL_PROP_TEXTINPUT_CAPITALIZATION_NUMBER = "SDL.textinput.capitalization"
+SDL_PROP_TEXTINPUT_AUTOCORRECT_BOOLEAN = "SDL.textinput.autocorrect"
+SDL_PROP_TEXTINPUT_MULTILINE_BOOLEAN = "SDL.textinput.multiline"
+SDL_PROP_TEXTINPUT_ANDROID_INPUTTYPE_NUMBER = "SDL.textinput.android.inputtype"
+SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN = "SDL.gpu.device.create.debugmode"
+SDL_PROP_GPU_DEVICE_CREATE_PREFERLOWPOWER_BOOLEAN = "SDL.gpu.device.create.preferlowpower"
+SDL_PROP_GPU_DEVICE_CREATE_NAME_STRING = "SDL.gpu.device.create.name"
+SDL_PROP_GPU_DEVICE_CREATE_SHADERS_PRIVATE_BOOLEAN = "SDL.gpu.device.create.shaders.private"
+SDL_PROP_GPU_DEVICE_CREATE_SHADERS_SPIRV_BOOLEAN = "SDL.gpu.device.create.shaders.spirv"
+SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXBC_BOOLEAN = "SDL.gpu.device.create.shaders.dxbc"
+SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXIL_BOOLEAN = "SDL.gpu.device.create.shaders.dxil"
+SDL_PROP_GPU_DEVICE_CREATE_SHADERS_MSL_BOOLEAN = "SDL.gpu.device.create.shaders.msl"
+SDL_PROP_GPU_DEVICE_CREATE_SHADERS_METALLIB_BOOLEAN = "SDL.gpu.device.create.shaders.metallib"
+SDL_PROP_GPU_DEVICE_CREATE_D3D12_SEMANTIC_NAME_STRING = "SDL.gpu.device.create.d3d12.semantic"
+SDL_PROP_GPU_COMPUTEPIPELINE_CREATE_NAME_STRING = "SDL.gpu.computepipeline.create.name"
+SDL_PROP_GPU_GRAPHICSPIPELINE_CREATE_NAME_STRING = "SDL.gpu.graphicspipeline.create.name"
+SDL_PROP_GPU_SAMPLER_CREATE_NAME_STRING = "SDL.gpu.sampler.create.name"
+SDL_PROP_GPU_SHADER_CREATE_NAME_STRING = "SDL.gpu.shader.create.name"
+SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_R_FLOAT = "SDL.gpu.texture.create.d3d12.clear.r"
+SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_G_FLOAT = "SDL.gpu.texture.create.d3d12.clear.g"
+SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_B_FLOAT = "SDL.gpu.texture.create.d3d12.clear.b"
+SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_A_FLOAT = "SDL.gpu.texture.create.d3d12.clear.a"
+SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_DEPTH_FLOAT = "SDL.gpu.texture.create.d3d12.clear.depth"
+SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_STENCIL_NUMBER = "SDL.gpu.texture.create.d3d12.clear.stencil"
+SDL_PROP_GPU_TEXTURE_CREATE_NAME_STRING = "SDL.gpu.texture.create.name"
+SDL_PROP_GPU_BUFFER_CREATE_NAME_STRING = "SDL.gpu.buffer.create.name"
+SDL_PROP_GPU_TRANSFERBUFFER_CREATE_NAME_STRING = "SDL.gpu.transferbuffer.create.name"
+SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED = "SDL_ALLOW_ALT_TAB_WHILE_GRABBED"
+SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY = "SDL_ANDROID_ALLOW_RECREATE_ACTIVITY"
+SDL_HINT_ANDROID_BLOCK_ON_PAUSE = "SDL_ANDROID_BLOCK_ON_PAUSE"
+SDL_HINT_ANDROID_LOW_LATENCY_AUDIO = "SDL_ANDROID_LOW_LATENCY_AUDIO"
+SDL_HINT_ANDROID_TRAP_BACK_BUTTON = "SDL_ANDROID_TRAP_BACK_BUTTON"
+SDL_HINT_APP_ID = "SDL_APP_ID"
+SDL_HINT_APP_NAME = "SDL_APP_NAME"
+SDL_HINT_APPLE_TV_CONTROLLER_UI_EVENTS = "SDL_APPLE_TV_CONTROLLER_UI_EVENTS"
+SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION = "SDL_APPLE_TV_REMOTE_ALLOW_ROTATION"
+SDL_HINT_AUDIO_ALSA_DEFAULT_DEVICE = "SDL_AUDIO_ALSA_DEFAULT_DEVICE"
+SDL_HINT_AUDIO_ALSA_DEFAULT_PLAYBACK_DEVICE = "SDL_AUDIO_ALSA_DEFAULT_PLAYBACK_DEVICE"
+SDL_HINT_AUDIO_ALSA_DEFAULT_RECORDING_DEVICE = "SDL_AUDIO_ALSA_DEFAULT_RECORDING_DEVICE"
+SDL_HINT_AUDIO_CATEGORY = "SDL_AUDIO_CATEGORY"
+SDL_HINT_AUDIO_CHANNELS = "SDL_AUDIO_CHANNELS"
+SDL_HINT_AUDIO_DEVICE_APP_ICON_NAME = "SDL_AUDIO_DEVICE_APP_ICON_NAME"
+SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES = "SDL_AUDIO_DEVICE_SAMPLE_FRAMES"
+SDL_HINT_AUDIO_DEVICE_STREAM_NAME = "SDL_AUDIO_DEVICE_STREAM_NAME"
+SDL_HINT_AUDIO_DEVICE_STREAM_ROLE = "SDL_AUDIO_DEVICE_STREAM_ROLE"
+SDL_HINT_AUDIO_DISK_INPUT_FILE = "SDL_AUDIO_DISK_INPUT_FILE"
+SDL_HINT_AUDIO_DISK_OUTPUT_FILE = "SDL_AUDIO_DISK_OUTPUT_FILE"
+SDL_HINT_AUDIO_DISK_TIMESCALE = "SDL_AUDIO_DISK_TIMESCALE"
+SDL_HINT_AUDIO_DRIVER = "SDL_AUDIO_DRIVER"
+SDL_HINT_AUDIO_DUMMY_TIMESCALE = "SDL_AUDIO_DUMMY_TIMESCALE"
+SDL_HINT_AUDIO_FORMAT = "SDL_AUDIO_FORMAT"
+SDL_HINT_AUDIO_FREQUENCY = "SDL_AUDIO_FREQUENCY"
+SDL_HINT_AUDIO_INCLUDE_MONITORS = "SDL_AUDIO_INCLUDE_MONITORS"
+SDL_HINT_AUTO_UPDATE_JOYSTICKS = "SDL_AUTO_UPDATE_JOYSTICKS"
+SDL_HINT_AUTO_UPDATE_SENSORS = "SDL_AUTO_UPDATE_SENSORS"
+SDL_HINT_BMP_SAVE_LEGACY_FORMAT = "SDL_BMP_SAVE_LEGACY_FORMAT"
+SDL_HINT_CAMERA_DRIVER = "SDL_CAMERA_DRIVER"
+SDL_HINT_CPU_FEATURE_MASK = "SDL_CPU_FEATURE_MASK"
+SDL_HINT_JOYSTICK_DIRECTINPUT = "SDL_JOYSTICK_DIRECTINPUT"
+SDL_HINT_FILE_DIALOG_DRIVER = "SDL_FILE_DIALOG_DRIVER"
+SDL_HINT_DISPLAY_USABLE_BOUNDS = "SDL_DISPLAY_USABLE_BOUNDS"
+SDL_HINT_EMSCRIPTEN_ASYNCIFY = "SDL_EMSCRIPTEN_ASYNCIFY"
+SDL_HINT_EMSCRIPTEN_CANVAS_SELECTOR = "SDL_EMSCRIPTEN_CANVAS_SELECTOR"
+SDL_HINT_EMSCRIPTEN_KEYBOARD_ELEMENT = "SDL_EMSCRIPTEN_KEYBOARD_ELEMENT"
+SDL_HINT_ENABLE_SCREEN_KEYBOARD = "SDL_ENABLE_SCREEN_KEYBOARD"
+SDL_HINT_EVDEV_DEVICES = "SDL_EVDEV_DEVICES"
+SDL_HINT_EVENT_LOGGING = "SDL_EVENT_LOGGING"
+SDL_HINT_FORCE_RAISEWINDOW = "SDL_FORCE_RAISEWINDOW"
+SDL_HINT_FRAMEBUFFER_ACCELERATION = "SDL_FRAMEBUFFER_ACCELERATION"
+SDL_HINT_GAMECONTROLLERCONFIG = "SDL_GAMECONTROLLERCONFIG"
+SDL_HINT_GAMECONTROLLERCONFIG_FILE = "SDL_GAMECONTROLLERCONFIG_FILE"
+SDL_HINT_GAMECONTROLLERTYPE = "SDL_GAMECONTROLLERTYPE"
+SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES = "SDL_GAMECONTROLLER_IGNORE_DEVICES"
+SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT = "SDL_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT"
+SDL_HINT_GAMECONTROLLER_SENSOR_FUSION = "SDL_GAMECONTROLLER_SENSOR_FUSION"
+SDL_HINT_GDK_TEXTINPUT_DEFAULT_TEXT = "SDL_GDK_TEXTINPUT_DEFAULT_TEXT"
+SDL_HINT_GDK_TEXTINPUT_DESCRIPTION = "SDL_GDK_TEXTINPUT_DESCRIPTION"
+SDL_HINT_GDK_TEXTINPUT_MAX_LENGTH = "SDL_GDK_TEXTINPUT_MAX_LENGTH"
+SDL_HINT_GDK_TEXTINPUT_SCOPE = "SDL_GDK_TEXTINPUT_SCOPE"
+SDL_HINT_GDK_TEXTINPUT_TITLE = "SDL_GDK_TEXTINPUT_TITLE"
+SDL_HINT_HIDAPI_LIBUSB = "SDL_HIDAPI_LIBUSB"
+SDL_HINT_HIDAPI_LIBUSB_WHITELIST = "SDL_HIDAPI_LIBUSB_WHITELIST"
+SDL_HINT_HIDAPI_UDEV = "SDL_HIDAPI_UDEV"
+SDL_HINT_GPU_DRIVER = "SDL_GPU_DRIVER"
+SDL_HINT_HIDAPI_ENUMERATE_ONLY_CONTROLLERS = "SDL_HIDAPI_ENUMERATE_ONLY_CONTROLLERS"
+SDL_HINT_HIDAPI_IGNORE_DEVICES = "SDL_HIDAPI_IGNORE_DEVICES"
+SDL_HINT_IME_IMPLEMENTED_UI = "SDL_IME_IMPLEMENTED_UI"
+SDL_HINT_IOS_HIDE_HOME_INDICATOR = "SDL_IOS_HIDE_HOME_INDICATOR"
+SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS = "SDL_JOYSTICK_ALLOW_BACKGROUND_EVENTS"
+SDL_HINT_JOYSTICK_ARCADESTICK_DEVICES = "SDL_JOYSTICK_ARCADESTICK_DEVICES"
+SDL_HINT_JOYSTICK_ARCADESTICK_DEVICES_EXCLUDED = "SDL_JOYSTICK_ARCADESTICK_DEVICES_EXCLUDED"
+SDL_HINT_JOYSTICK_BLACKLIST_DEVICES = "SDL_JOYSTICK_BLACKLIST_DEVICES"
+SDL_HINT_JOYSTICK_BLACKLIST_DEVICES_EXCLUDED = "SDL_JOYSTICK_BLACKLIST_DEVICES_EXCLUDED"
+SDL_HINT_JOYSTICK_DEVICE = "SDL_JOYSTICK_DEVICE"
+SDL_HINT_JOYSTICK_ENHANCED_REPORTS = "SDL_JOYSTICK_ENHANCED_REPORTS"
+SDL_HINT_JOYSTICK_FLIGHTSTICK_DEVICES = "SDL_JOYSTICK_FLIGHTSTICK_DEVICES"
+SDL_HINT_JOYSTICK_FLIGHTSTICK_DEVICES_EXCLUDED = "SDL_JOYSTICK_FLIGHTSTICK_DEVICES_EXCLUDED"
+SDL_HINT_JOYSTICK_GAMEINPUT = "SDL_JOYSTICK_GAMEINPUT"
+SDL_HINT_JOYSTICK_GAMECUBE_DEVICES = "SDL_JOYSTICK_GAMECUBE_DEVICES"
+SDL_HINT_JOYSTICK_GAMECUBE_DEVICES_EXCLUDED = "SDL_JOYSTICK_GAMECUBE_DEVICES_EXCLUDED"
+SDL_HINT_JOYSTICK_HIDAPI = "SDL_JOYSTICK_HIDAPI"
+SDL_HINT_JOYSTICK_HIDAPI_COMBINE_JOY_CONS = "SDL_JOYSTICK_HIDAPI_COMBINE_JOY_CONS"
+SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE = "SDL_JOYSTICK_HIDAPI_GAMECUBE"
+SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE_RUMBLE_BRAKE = "SDL_JOYSTICK_HIDAPI_GAMECUBE_RUMBLE_BRAKE"
+SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS = "SDL_JOYSTICK_HIDAPI_JOY_CONS"
+SDL_HINT_JOYSTICK_HIDAPI_JOYCON_HOME_LED = "SDL_JOYSTICK_HIDAPI_JOYCON_HOME_LED"
+SDL_HINT_JOYSTICK_HIDAPI_LUNA = "SDL_JOYSTICK_HIDAPI_LUNA"
+SDL_HINT_JOYSTICK_HIDAPI_NINTENDO_CLASSIC = "SDL_JOYSTICK_HIDAPI_NINTENDO_CLASSIC"
+SDL_HINT_JOYSTICK_HIDAPI_PS3 = "SDL_JOYSTICK_HIDAPI_PS3"
+SDL_HINT_JOYSTICK_HIDAPI_PS3_SIXAXIS_DRIVER = "SDL_JOYSTICK_HIDAPI_PS3_SIXAXIS_DRIVER"
+SDL_HINT_JOYSTICK_HIDAPI_PS4 = "SDL_JOYSTICK_HIDAPI_PS4"
+SDL_HINT_JOYSTICK_HIDAPI_PS4_REPORT_INTERVAL = "SDL_JOYSTICK_HIDAPI_PS4_REPORT_INTERVAL"
+SDL_HINT_JOYSTICK_HIDAPI_PS5 = "SDL_JOYSTICK_HIDAPI_PS5"
+SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED = "SDL_JOYSTICK_HIDAPI_PS5_PLAYER_LED"
+SDL_HINT_JOYSTICK_HIDAPI_SHIELD = "SDL_JOYSTICK_HIDAPI_SHIELD"
+SDL_HINT_JOYSTICK_HIDAPI_STADIA = "SDL_JOYSTICK_HIDAPI_STADIA"
+SDL_HINT_JOYSTICK_HIDAPI_STEAM = "SDL_JOYSTICK_HIDAPI_STEAM"
+SDL_HINT_JOYSTICK_HIDAPI_STEAM_HOME_LED = "SDL_JOYSTICK_HIDAPI_STEAM_HOME_LED"
+SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK = "SDL_JOYSTICK_HIDAPI_STEAMDECK"
+SDL_HINT_JOYSTICK_HIDAPI_STEAM_HORI = "SDL_JOYSTICK_HIDAPI_STEAM_HORI"
+SDL_HINT_JOYSTICK_HIDAPI_SWITCH = "SDL_JOYSTICK_HIDAPI_SWITCH"
+SDL_HINT_JOYSTICK_HIDAPI_SWITCH_HOME_LED = "SDL_JOYSTICK_HIDAPI_SWITCH_HOME_LED"
+SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED = "SDL_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED"
+SDL_HINT_JOYSTICK_HIDAPI_VERTICAL_JOY_CONS = "SDL_JOYSTICK_HIDAPI_VERTICAL_JOY_CONS"
+SDL_HINT_JOYSTICK_HIDAPI_WII = "SDL_JOYSTICK_HIDAPI_WII"
+SDL_HINT_JOYSTICK_HIDAPI_WII_PLAYER_LED = "SDL_JOYSTICK_HIDAPI_WII_PLAYER_LED"
+SDL_HINT_JOYSTICK_HIDAPI_XBOX = "SDL_JOYSTICK_HIDAPI_XBOX"
+SDL_HINT_JOYSTICK_HIDAPI_XBOX_360 = "SDL_JOYSTICK_HIDAPI_XBOX_360"
+SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED = "SDL_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED"
+SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_WIRELESS = "SDL_JOYSTICK_HIDAPI_XBOX_360_WIRELESS"
+SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE = "SDL_JOYSTICK_HIDAPI_XBOX_ONE"
+SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE_HOME_LED = "SDL_JOYSTICK_HIDAPI_XBOX_ONE_HOME_LED"
+SDL_HINT_JOYSTICK_IOKIT = "SDL_JOYSTICK_IOKIT"
+SDL_HINT_JOYSTICK_LINUX_CLASSIC = "SDL_JOYSTICK_LINUX_CLASSIC"
+SDL_HINT_JOYSTICK_LINUX_DEADZONES = "SDL_JOYSTICK_LINUX_DEADZONES"
+SDL_HINT_JOYSTICK_LINUX_DIGITAL_HATS = "SDL_JOYSTICK_LINUX_DIGITAL_HATS"
+SDL_HINT_JOYSTICK_LINUX_HAT_DEADZONES = "SDL_JOYSTICK_LINUX_HAT_DEADZONES"
+SDL_HINT_JOYSTICK_MFI = "SDL_JOYSTICK_MFI"
+SDL_HINT_JOYSTICK_RAWINPUT = "SDL_JOYSTICK_RAWINPUT"
+SDL_HINT_JOYSTICK_RAWINPUT_CORRELATE_XINPUT = "SDL_JOYSTICK_RAWINPUT_CORRELATE_XINPUT"
+SDL_HINT_JOYSTICK_ROG_CHAKRAM = "SDL_JOYSTICK_ROG_CHAKRAM"
+SDL_HINT_JOYSTICK_THREAD = "SDL_JOYSTICK_THREAD"
+SDL_HINT_JOYSTICK_THROTTLE_DEVICES = "SDL_JOYSTICK_THROTTLE_DEVICES"
+SDL_HINT_JOYSTICK_THROTTLE_DEVICES_EXCLUDED = "SDL_JOYSTICK_THROTTLE_DEVICES_EXCLUDED"
+SDL_HINT_JOYSTICK_WGI = "SDL_JOYSTICK_WGI"
+SDL_HINT_JOYSTICK_WHEEL_DEVICES = "SDL_JOYSTICK_WHEEL_DEVICES"
+SDL_HINT_JOYSTICK_WHEEL_DEVICES_EXCLUDED = "SDL_JOYSTICK_WHEEL_DEVICES_EXCLUDED"
+SDL_HINT_JOYSTICK_ZERO_CENTERED_DEVICES = "SDL_JOYSTICK_ZERO_CENTERED_DEVICES"
+SDL_HINT_JOYSTICK_HAPTIC_AXES = "SDL_JOYSTICK_HAPTIC_AXES"
+SDL_HINT_KEYCODE_OPTIONS = "SDL_KEYCODE_OPTIONS"
+SDL_HINT_KMSDRM_DEVICE_INDEX = "SDL_KMSDRM_DEVICE_INDEX"
+SDL_HINT_KMSDRM_REQUIRE_DRM_MASTER = "SDL_KMSDRM_REQUIRE_DRM_MASTER"
+SDL_HINT_LOGGING = "SDL_LOGGING"
+SDL_HINT_MAC_BACKGROUND_APP = "SDL_MAC_BACKGROUND_APP"
+SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK = "SDL_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK"
+SDL_HINT_MAC_OPENGL_ASYNC_DISPATCH = "SDL_MAC_OPENGL_ASYNC_DISPATCH"
+SDL_HINT_MAC_OPTION_AS_ALT = "SDL_MAC_OPTION_AS_ALT"
+SDL_HINT_MAC_SCROLL_MOMENTUM = "SDL_MAC_SCROLL_MOMENTUM"
+SDL_HINT_MAIN_CALLBACK_RATE = "SDL_MAIN_CALLBACK_RATE"
+SDL_HINT_MOUSE_AUTO_CAPTURE = "SDL_MOUSE_AUTO_CAPTURE"
+SDL_HINT_MOUSE_DOUBLE_CLICK_RADIUS = "SDL_MOUSE_DOUBLE_CLICK_RADIUS"
+SDL_HINT_MOUSE_DOUBLE_CLICK_TIME = "SDL_MOUSE_DOUBLE_CLICK_TIME"
+SDL_HINT_MOUSE_DEFAULT_SYSTEM_CURSOR = "SDL_MOUSE_DEFAULT_SYSTEM_CURSOR"
+SDL_HINT_MOUSE_EMULATE_WARP_WITH_RELATIVE = "SDL_MOUSE_EMULATE_WARP_WITH_RELATIVE"
+SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH = "SDL_MOUSE_FOCUS_CLICKTHROUGH"
+SDL_HINT_MOUSE_NORMAL_SPEED_SCALE = "SDL_MOUSE_NORMAL_SPEED_SCALE"
+SDL_HINT_MOUSE_RELATIVE_MODE_CENTER = "SDL_MOUSE_RELATIVE_MODE_CENTER"
+SDL_HINT_MOUSE_RELATIVE_SPEED_SCALE = "SDL_MOUSE_RELATIVE_SPEED_SCALE"
+SDL_HINT_MOUSE_RELATIVE_SYSTEM_SCALE = "SDL_MOUSE_RELATIVE_SYSTEM_SCALE"
+SDL_HINT_MOUSE_RELATIVE_WARP_MOTION = "SDL_MOUSE_RELATIVE_WARP_MOTION"
+SDL_HINT_MOUSE_RELATIVE_CURSOR_VISIBLE = "SDL_MOUSE_RELATIVE_CURSOR_VISIBLE"
+SDL_HINT_MOUSE_TOUCH_EVENTS = "SDL_MOUSE_TOUCH_EVENTS"
+SDL_HINT_MUTE_CONSOLE_KEYBOARD = "SDL_MUTE_CONSOLE_KEYBOARD"
+SDL_HINT_NO_SIGNAL_HANDLERS = "SDL_NO_SIGNAL_HANDLERS"
+SDL_HINT_OPENGL_LIBRARY = "SDL_OPENGL_LIBRARY"
+SDL_HINT_EGL_LIBRARY = "SDL_EGL_LIBRARY"
+SDL_HINT_OPENGL_ES_DRIVER = "SDL_OPENGL_ES_DRIVER"
+SDL_HINT_OPENVR_LIBRARY = "SDL_OPENVR_LIBRARY"
+SDL_HINT_ORIENTATIONS = "SDL_ORIENTATIONS"
+SDL_HINT_POLL_SENTINEL = "SDL_POLL_SENTINEL"
+SDL_HINT_PREFERRED_LOCALES = "SDL_PREFERRED_LOCALES"
+SDL_HINT_QUIT_ON_LAST_WINDOW_CLOSE = "SDL_QUIT_ON_LAST_WINDOW_CLOSE"
+SDL_HINT_RENDER_DIRECT3D_THREADSAFE = "SDL_RENDER_DIRECT3D_THREADSAFE"
+SDL_HINT_RENDER_DIRECT3D11_DEBUG = "SDL_RENDER_DIRECT3D11_DEBUG"
+SDL_HINT_RENDER_VULKAN_DEBUG = "SDL_RENDER_VULKAN_DEBUG"
+SDL_HINT_RENDER_GPU_DEBUG = "SDL_RENDER_GPU_DEBUG"
+SDL_HINT_RENDER_GPU_LOW_POWER = "SDL_RENDER_GPU_LOW_POWER"
+SDL_HINT_RENDER_DRIVER = "SDL_RENDER_DRIVER"
+SDL_HINT_RENDER_LINE_METHOD = "SDL_RENDER_LINE_METHOD"
+SDL_HINT_RENDER_METAL_PREFER_LOW_POWER_DEVICE = "SDL_RENDER_METAL_PREFER_LOW_POWER_DEVICE"
+SDL_HINT_RENDER_VSYNC = "SDL_RENDER_VSYNC"
+SDL_HINT_RETURN_KEY_HIDES_IME = "SDL_RETURN_KEY_HIDES_IME"
+SDL_HINT_ROG_GAMEPAD_MICE = "SDL_ROG_GAMEPAD_MICE"
+SDL_HINT_ROG_GAMEPAD_MICE_EXCLUDED = "SDL_ROG_GAMEPAD_MICE_EXCLUDED"
+SDL_HINT_RPI_VIDEO_LAYER = "SDL_RPI_VIDEO_LAYER"
+SDL_HINT_SCREENSAVER_INHIBIT_ACTIVITY_NAME = "SDL_SCREENSAVER_INHIBIT_ACTIVITY_NAME"
+SDL_HINT_SHUTDOWN_DBUS_ON_QUIT = "SDL_SHUTDOWN_DBUS_ON_QUIT"
+SDL_HINT_STORAGE_TITLE_DRIVER = "SDL_STORAGE_TITLE_DRIVER"
+SDL_HINT_STORAGE_USER_DRIVER = "SDL_STORAGE_USER_DRIVER"
+SDL_HINT_THREAD_FORCE_REALTIME_TIME_CRITICAL = "SDL_THREAD_FORCE_REALTIME_TIME_CRITICAL"
+SDL_HINT_THREAD_PRIORITY_POLICY = "SDL_THREAD_PRIORITY_POLICY"
+SDL_HINT_TIMER_RESOLUTION = "SDL_TIMER_RESOLUTION"
+SDL_HINT_TOUCH_MOUSE_EVENTS = "SDL_TOUCH_MOUSE_EVENTS"
+SDL_HINT_TRACKPAD_IS_TOUCH_ONLY = "SDL_TRACKPAD_IS_TOUCH_ONLY"
+SDL_HINT_TV_REMOTE_AS_JOYSTICK = "SDL_TV_REMOTE_AS_JOYSTICK"
+SDL_HINT_VIDEO_ALLOW_SCREENSAVER = "SDL_VIDEO_ALLOW_SCREENSAVER"
+SDL_HINT_VIDEO_DISPLAY_PRIORITY = "SDL_VIDEO_DISPLAY_PRIORITY"
+SDL_HINT_VIDEO_DOUBLE_BUFFER = "SDL_VIDEO_DOUBLE_BUFFER"
+SDL_HINT_VIDEO_DRIVER = "SDL_VIDEO_DRIVER"
+SDL_HINT_VIDEO_DUMMY_SAVE_FRAMES = "SDL_VIDEO_DUMMY_SAVE_FRAMES"
+SDL_HINT_VIDEO_EGL_ALLOW_GETDISPLAY_FALLBACK = "SDL_VIDEO_EGL_ALLOW_GETDISPLAY_FALLBACK"
+SDL_HINT_VIDEO_FORCE_EGL = "SDL_VIDEO_FORCE_EGL"
+SDL_HINT_VIDEO_MAC_FULLSCREEN_SPACES = "SDL_VIDEO_MAC_FULLSCREEN_SPACES"
+SDL_HINT_VIDEO_MAC_FULLSCREEN_MENU_VISIBILITY = "SDL_VIDEO_MAC_FULLSCREEN_MENU_VISIBILITY"
+SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS = "SDL_VIDEO_MINIMIZE_ON_FOCUS_LOSS"
+SDL_HINT_VIDEO_OFFSCREEN_SAVE_FRAMES = "SDL_VIDEO_OFFSCREEN_SAVE_FRAMES"
+SDL_HINT_VIDEO_SYNC_WINDOW_OPERATIONS = "SDL_VIDEO_SYNC_WINDOW_OPERATIONS"
+SDL_HINT_VIDEO_WAYLAND_ALLOW_LIBDECOR = "SDL_VIDEO_WAYLAND_ALLOW_LIBDECOR"
+SDL_HINT_VIDEO_WAYLAND_MODE_EMULATION = "SDL_VIDEO_WAYLAND_MODE_EMULATION"
+SDL_HINT_VIDEO_WAYLAND_MODE_SCALING = "SDL_VIDEO_WAYLAND_MODE_SCALING"
+SDL_HINT_VIDEO_WAYLAND_PREFER_LIBDECOR = "SDL_VIDEO_WAYLAND_PREFER_LIBDECOR"
+SDL_HINT_VIDEO_WAYLAND_SCALE_TO_DISPLAY = "SDL_VIDEO_WAYLAND_SCALE_TO_DISPLAY"
+SDL_HINT_VIDEO_WIN_D3DCOMPILER = "SDL_VIDEO_WIN_D3DCOMPILER"
+SDL_HINT_VIDEO_X11_EXTERNAL_WINDOW_INPUT = "SDL_VIDEO_X11_EXTERNAL_WINDOW_INPUT"
+SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR = "SDL_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR"
+SDL_HINT_VIDEO_X11_NET_WM_PING = "SDL_VIDEO_X11_NET_WM_PING"
+SDL_HINT_VIDEO_X11_NODIRECTCOLOR = "SDL_VIDEO_X11_NODIRECTCOLOR"
+SDL_HINT_VIDEO_X11_SCALING_FACTOR = "SDL_VIDEO_X11_SCALING_FACTOR"
+SDL_HINT_VIDEO_X11_VISUALID = "SDL_VIDEO_X11_VISUALID"
+SDL_HINT_VIDEO_X11_WINDOW_VISUALID = "SDL_VIDEO_X11_WINDOW_VISUALID"
+SDL_HINT_VIDEO_X11_XRANDR = "SDL_VIDEO_X11_XRANDR"
+SDL_HINT_VITA_ENABLE_BACK_TOUCH = "SDL_VITA_ENABLE_BACK_TOUCH"
+SDL_HINT_VITA_ENABLE_FRONT_TOUCH = "SDL_VITA_ENABLE_FRONT_TOUCH"
+SDL_HINT_VITA_MODULE_PATH = "SDL_VITA_MODULE_PATH"
+SDL_HINT_VITA_PVR_INIT = "SDL_VITA_PVR_INIT"
+SDL_HINT_VITA_RESOLUTION = "SDL_VITA_RESOLUTION"
+SDL_HINT_VITA_PVR_OPENGL = "SDL_VITA_PVR_OPENGL"
+SDL_HINT_VITA_TOUCH_MOUSE_DEVICE = "SDL_VITA_TOUCH_MOUSE_DEVICE"
+SDL_HINT_VULKAN_DISPLAY = "SDL_VULKAN_DISPLAY"
+SDL_HINT_VULKAN_LIBRARY = "SDL_VULKAN_LIBRARY"
+SDL_HINT_WAVE_FACT_CHUNK = "SDL_WAVE_FACT_CHUNK"
+SDL_HINT_WAVE_CHUNK_LIMIT = "SDL_WAVE_CHUNK_LIMIT"
+SDL_HINT_WAVE_RIFF_CHUNK_SIZE = "SDL_WAVE_RIFF_CHUNK_SIZE"
+SDL_HINT_WAVE_TRUNCATION = "SDL_WAVE_TRUNCATION"
+SDL_HINT_WINDOW_ACTIVATE_WHEN_RAISED = "SDL_WINDOW_ACTIVATE_WHEN_RAISED"
+SDL_HINT_WINDOW_ACTIVATE_WHEN_SHOWN = "SDL_WINDOW_ACTIVATE_WHEN_SHOWN"
+SDL_HINT_WINDOW_ALLOW_TOPMOST = "SDL_WINDOW_ALLOW_TOPMOST"
+SDL_HINT_WINDOW_FRAME_USABLE_WHILE_CURSOR_HIDDEN = "SDL_WINDOW_FRAME_USABLE_WHILE_CURSOR_HIDDEN"
+SDL_HINT_WINDOWS_CLOSE_ON_ALT_F4 = "SDL_WINDOWS_CLOSE_ON_ALT_F4"
+SDL_HINT_WINDOWS_ENABLE_MENU_MNEMONICS = "SDL_WINDOWS_ENABLE_MENU_MNEMONICS"
+SDL_HINT_WINDOWS_ENABLE_MESSAGELOOP = "SDL_WINDOWS_ENABLE_MESSAGELOOP"
+SDL_HINT_WINDOWS_GAMEINPUT = "SDL_WINDOWS_GAMEINPUT"
+SDL_HINT_WINDOWS_RAW_KEYBOARD = "SDL_WINDOWS_RAW_KEYBOARD"
+SDL_HINT_WINDOWS_FORCE_SEMAPHORE_KERNEL = "SDL_WINDOWS_FORCE_SEMAPHORE_KERNEL"
+SDL_HINT_WINDOWS_INTRESOURCE_ICON = "SDL_WINDOWS_INTRESOURCE_ICON"
+SDL_HINT_WINDOWS_INTRESOURCE_ICON_SMALL = "SDL_WINDOWS_INTRESOURCE_ICON_SMALL"
+SDL_HINT_WINDOWS_USE_D3D9EX = "SDL_WINDOWS_USE_D3D9EX"
+SDL_HINT_WINDOWS_ERASE_BACKGROUND_MODE = "SDL_WINDOWS_ERASE_BACKGROUND_MODE"
+SDL_HINT_X11_FORCE_OVERRIDE_REDIRECT = "SDL_X11_FORCE_OVERRIDE_REDIRECT"
+SDL_HINT_X11_WINDOW_TYPE = "SDL_X11_WINDOW_TYPE"
+SDL_HINT_X11_XCB_LIBRARY = "SDL_X11_XCB_LIBRARY"
+SDL_HINT_XINPUT_ENABLED = "SDL_XINPUT_ENABLED"
+SDL_HINT_ASSERT = "SDL_ASSERT"
+SDL_HINT_PEN_MOUSE_EVENTS = "SDL_PEN_MOUSE_EVENTS"
+SDL_HINT_PEN_TOUCH_EVENTS = "SDL_PEN_TOUCH_EVENTS"
+SDL_PROP_APP_METADATA_NAME_STRING = "SDL.app.metadata.name"
+SDL_PROP_APP_METADATA_VERSION_STRING = "SDL.app.metadata.version"
+SDL_PROP_APP_METADATA_IDENTIFIER_STRING = "SDL.app.metadata.identifier"
+SDL_PROP_APP_METADATA_CREATOR_STRING = "SDL.app.metadata.creator"
+SDL_PROP_APP_METADATA_COPYRIGHT_STRING = "SDL.app.metadata.copyright"
+SDL_PROP_APP_METADATA_URL_STRING = "SDL.app.metadata.url"
+SDL_PROP_APP_METADATA_TYPE_STRING = "SDL.app.metadata.type"
+SDL_PROP_PROCESS_CREATE_ARGS_POINTER = "SDL.process.create.args"
+SDL_PROP_PROCESS_CREATE_ENVIRONMENT_POINTER = "SDL.process.create.environment"
+SDL_PROP_PROCESS_CREATE_STDIN_NUMBER = "SDL.process.create.stdin_option"
+SDL_PROP_PROCESS_CREATE_STDIN_POINTER = "SDL.process.create.stdin_source"
+SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER = "SDL.process.create.stdout_option"
+SDL_PROP_PROCESS_CREATE_STDOUT_POINTER = "SDL.process.create.stdout_source"
+SDL_PROP_PROCESS_CREATE_STDERR_NUMBER = "SDL.process.create.stderr_option"
+SDL_PROP_PROCESS_CREATE_STDERR_POINTER = "SDL.process.create.stderr_source"
+SDL_PROP_PROCESS_CREATE_STDERR_TO_STDOUT_BOOLEAN = "SDL.process.create.stderr_to_stdout"
+SDL_PROP_PROCESS_CREATE_BACKGROUND_BOOLEAN = "SDL.process.create.background"
+SDL_PROP_PROCESS_PID_NUMBER = "SDL.process.pid"
+SDL_PROP_PROCESS_STDIN_POINTER = "SDL.process.stdin"
+SDL_PROP_PROCESS_STDOUT_POINTER = "SDL.process.stdout"
+SDL_PROP_PROCESS_STDERR_POINTER = "SDL.process.stderr"
+SDL_PROP_PROCESS_BACKGROUND_BOOLEAN = "SDL.process.background"
+SDL_SOFTWARE_RENDERER = "software"
+SDL_PROP_RENDERER_CREATE_NAME_STRING = "SDL.renderer.create.name"
+SDL_PROP_RENDERER_CREATE_WINDOW_POINTER = "SDL.renderer.create.window"
+SDL_PROP_RENDERER_CREATE_SURFACE_POINTER = "SDL.renderer.create.surface"
+SDL_PROP_RENDERER_CREATE_OUTPUT_COLORSPACE_NUMBER = "SDL.renderer.create.output_colorspace"
+SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_NUMBER = "SDL.renderer.create.present_vsync"
+SDL_PROP_RENDERER_CREATE_VULKAN_INSTANCE_POINTER = "SDL.renderer.create.vulkan.instance"
+SDL_PROP_RENDERER_CREATE_VULKAN_SURFACE_NUMBER = "SDL.renderer.create.vulkan.surface"
+SDL_PROP_RENDERER_CREATE_VULKAN_PHYSICAL_DEVICE_POINTER = "SDL.renderer.create.vulkan.physical_device"
+SDL_PROP_RENDERER_CREATE_VULKAN_DEVICE_POINTER = "SDL.renderer.create.vulkan.device"
+SDL_PROP_RENDERER_CREATE_VULKAN_GRAPHICS_QUEUE_FAMILY_INDEX_NUMBER = (
+ "SDL.renderer.create.vulkan.graphics_queue_family_index"
+)
+SDL_PROP_RENDERER_CREATE_VULKAN_PRESENT_QUEUE_FAMILY_INDEX_NUMBER = (
+ "SDL.renderer.create.vulkan.present_queue_family_index"
+)
+SDL_PROP_RENDERER_NAME_STRING = "SDL.renderer.name"
+SDL_PROP_RENDERER_WINDOW_POINTER = "SDL.renderer.window"
+SDL_PROP_RENDERER_SURFACE_POINTER = "SDL.renderer.surface"
+SDL_PROP_RENDERER_VSYNC_NUMBER = "SDL.renderer.vsync"
+SDL_PROP_RENDERER_MAX_TEXTURE_SIZE_NUMBER = "SDL.renderer.max_texture_size"
+SDL_PROP_RENDERER_TEXTURE_FORMATS_POINTER = "SDL.renderer.texture_formats"
+SDL_PROP_RENDERER_OUTPUT_COLORSPACE_NUMBER = "SDL.renderer.output_colorspace"
+SDL_PROP_RENDERER_HDR_ENABLED_BOOLEAN = "SDL.renderer.HDR_enabled"
+SDL_PROP_RENDERER_SDR_WHITE_POINT_FLOAT = "SDL.renderer.SDR_white_point"
+SDL_PROP_RENDERER_HDR_HEADROOM_FLOAT = "SDL.renderer.HDR_headroom"
+SDL_PROP_RENDERER_D3D9_DEVICE_POINTER = "SDL.renderer.d3d9.device"
+SDL_PROP_RENDERER_D3D11_DEVICE_POINTER = "SDL.renderer.d3d11.device"
+SDL_PROP_RENDERER_D3D11_SWAPCHAIN_POINTER = "SDL.renderer.d3d11.swap_chain"
+SDL_PROP_RENDERER_D3D12_DEVICE_POINTER = "SDL.renderer.d3d12.device"
+SDL_PROP_RENDERER_D3D12_SWAPCHAIN_POINTER = "SDL.renderer.d3d12.swap_chain"
+SDL_PROP_RENDERER_D3D12_COMMAND_QUEUE_POINTER = "SDL.renderer.d3d12.command_queue"
+SDL_PROP_RENDERER_VULKAN_INSTANCE_POINTER = "SDL.renderer.vulkan.instance"
+SDL_PROP_RENDERER_VULKAN_SURFACE_NUMBER = "SDL.renderer.vulkan.surface"
+SDL_PROP_RENDERER_VULKAN_PHYSICAL_DEVICE_POINTER = "SDL.renderer.vulkan.physical_device"
+SDL_PROP_RENDERER_VULKAN_DEVICE_POINTER = "SDL.renderer.vulkan.device"
+SDL_PROP_RENDERER_VULKAN_GRAPHICS_QUEUE_FAMILY_INDEX_NUMBER = "SDL.renderer.vulkan.graphics_queue_family_index"
+SDL_PROP_RENDERER_VULKAN_PRESENT_QUEUE_FAMILY_INDEX_NUMBER = "SDL.renderer.vulkan.present_queue_family_index"
+SDL_PROP_RENDERER_VULKAN_SWAPCHAIN_IMAGE_COUNT_NUMBER = "SDL.renderer.vulkan.swapchain_image_count"
+SDL_PROP_RENDERER_GPU_DEVICE_POINTER = "SDL.renderer.gpu.device"
+SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER = "SDL.texture.create.colorspace"
+SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER = "SDL.texture.create.format"
+SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER = "SDL.texture.create.access"
+SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER = "SDL.texture.create.width"
+SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER = "SDL.texture.create.height"
+SDL_PROP_TEXTURE_CREATE_SDR_WHITE_POINT_FLOAT = "SDL.texture.create.SDR_white_point"
+SDL_PROP_TEXTURE_CREATE_HDR_HEADROOM_FLOAT = "SDL.texture.create.HDR_headroom"
+SDL_PROP_TEXTURE_CREATE_D3D11_TEXTURE_POINTER = "SDL.texture.create.d3d11.texture"
+SDL_PROP_TEXTURE_CREATE_D3D11_TEXTURE_U_POINTER = "SDL.texture.create.d3d11.texture_u"
+SDL_PROP_TEXTURE_CREATE_D3D11_TEXTURE_V_POINTER = "SDL.texture.create.d3d11.texture_v"
+SDL_PROP_TEXTURE_CREATE_D3D12_TEXTURE_POINTER = "SDL.texture.create.d3d12.texture"
+SDL_PROP_TEXTURE_CREATE_D3D12_TEXTURE_U_POINTER = "SDL.texture.create.d3d12.texture_u"
+SDL_PROP_TEXTURE_CREATE_D3D12_TEXTURE_V_POINTER = "SDL.texture.create.d3d12.texture_v"
+SDL_PROP_TEXTURE_CREATE_METAL_PIXELBUFFER_POINTER = "SDL.texture.create.metal.pixelbuffer"
+SDL_PROP_TEXTURE_CREATE_OPENGL_TEXTURE_NUMBER = "SDL.texture.create.opengl.texture"
+SDL_PROP_TEXTURE_CREATE_OPENGL_TEXTURE_UV_NUMBER = "SDL.texture.create.opengl.texture_uv"
+SDL_PROP_TEXTURE_CREATE_OPENGL_TEXTURE_U_NUMBER = "SDL.texture.create.opengl.texture_u"
+SDL_PROP_TEXTURE_CREATE_OPENGL_TEXTURE_V_NUMBER = "SDL.texture.create.opengl.texture_v"
+SDL_PROP_TEXTURE_CREATE_OPENGLES2_TEXTURE_NUMBER = "SDL.texture.create.opengles2.texture"
+SDL_PROP_TEXTURE_CREATE_OPENGLES2_TEXTURE_UV_NUMBER = "SDL.texture.create.opengles2.texture_uv"
+SDL_PROP_TEXTURE_CREATE_OPENGLES2_TEXTURE_U_NUMBER = "SDL.texture.create.opengles2.texture_u"
+SDL_PROP_TEXTURE_CREATE_OPENGLES2_TEXTURE_V_NUMBER = "SDL.texture.create.opengles2.texture_v"
+SDL_PROP_TEXTURE_CREATE_VULKAN_TEXTURE_NUMBER = "SDL.texture.create.vulkan.texture"
+SDL_PROP_TEXTURE_COLORSPACE_NUMBER = "SDL.texture.colorspace"
+SDL_PROP_TEXTURE_FORMAT_NUMBER = "SDL.texture.format"
+SDL_PROP_TEXTURE_ACCESS_NUMBER = "SDL.texture.access"
+SDL_PROP_TEXTURE_WIDTH_NUMBER = "SDL.texture.width"
+SDL_PROP_TEXTURE_HEIGHT_NUMBER = "SDL.texture.height"
+SDL_PROP_TEXTURE_SDR_WHITE_POINT_FLOAT = "SDL.texture.SDR_white_point"
+SDL_PROP_TEXTURE_HDR_HEADROOM_FLOAT = "SDL.texture.HDR_headroom"
+SDL_PROP_TEXTURE_D3D11_TEXTURE_POINTER = "SDL.texture.d3d11.texture"
+SDL_PROP_TEXTURE_D3D11_TEXTURE_U_POINTER = "SDL.texture.d3d11.texture_u"
+SDL_PROP_TEXTURE_D3D11_TEXTURE_V_POINTER = "SDL.texture.d3d11.texture_v"
+SDL_PROP_TEXTURE_D3D12_TEXTURE_POINTER = "SDL.texture.d3d12.texture"
+SDL_PROP_TEXTURE_D3D12_TEXTURE_U_POINTER = "SDL.texture.d3d12.texture_u"
+SDL_PROP_TEXTURE_D3D12_TEXTURE_V_POINTER = "SDL.texture.d3d12.texture_v"
+SDL_PROP_TEXTURE_OPENGL_TEXTURE_NUMBER = "SDL.texture.opengl.texture"
+SDL_PROP_TEXTURE_OPENGL_TEXTURE_UV_NUMBER = "SDL.texture.opengl.texture_uv"
+SDL_PROP_TEXTURE_OPENGL_TEXTURE_U_NUMBER = "SDL.texture.opengl.texture_u"
+SDL_PROP_TEXTURE_OPENGL_TEXTURE_V_NUMBER = "SDL.texture.opengl.texture_v"
+SDL_PROP_TEXTURE_OPENGL_TEXTURE_TARGET_NUMBER = "SDL.texture.opengl.target"
+SDL_PROP_TEXTURE_OPENGL_TEX_W_FLOAT = "SDL.texture.opengl.tex_w"
+SDL_PROP_TEXTURE_OPENGL_TEX_H_FLOAT = "SDL.texture.opengl.tex_h"
+SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_NUMBER = "SDL.texture.opengles2.texture"
+SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_UV_NUMBER = "SDL.texture.opengles2.texture_uv"
+SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_U_NUMBER = "SDL.texture.opengles2.texture_u"
+SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_V_NUMBER = "SDL.texture.opengles2.texture_v"
+SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_TARGET_NUMBER = "SDL.texture.opengles2.target"
+SDL_PROP_TEXTURE_VULKAN_TEXTURE_NUMBER = "SDL.texture.vulkan.texture"
diff --git a/tcod/sdl/joystick.py b/tcod/sdl/joystick.py
new file mode 100644
index 00000000..e6f2de84
--- /dev/null
+++ b/tcod/sdl/joystick.py
@@ -0,0 +1,403 @@
+"""SDL Joystick Support.
+
+.. versionadded:: 13.8
+"""
+
+from __future__ import annotations
+
+import enum
+from typing import Any, ClassVar, Final, Literal
+from weakref import WeakValueDictionary
+
+import tcod.sdl.sys
+from tcod.cffi import ffi, lib
+from tcod.sdl._internal import _check, _check_int, _check_p
+
+_HAT_DIRECTIONS: dict[int, tuple[Literal[-1, 0, 1], Literal[-1, 0, 1]]] = {
+ int(lib.SDL_HAT_CENTERED): (0, 0),
+ int(lib.SDL_HAT_UP): (0, -1),
+ int(lib.SDL_HAT_RIGHT): (1, 0),
+ int(lib.SDL_HAT_DOWN): (0, 1),
+ int(lib.SDL_HAT_LEFT): (-1, 0),
+ int(lib.SDL_HAT_RIGHTUP): (1, -1),
+ int(lib.SDL_HAT_RIGHTDOWN): (1, 1),
+ int(lib.SDL_HAT_LEFTUP): (-1, -1),
+ int(lib.SDL_HAT_LEFTDOWN): (-1, 1),
+}
+
+
+class ControllerAxis(enum.IntEnum):
+ """The standard axes for a game controller."""
+
+ INVALID = int(lib.SDL_GAMEPAD_AXIS_INVALID)
+ LEFTX = int(lib.SDL_GAMEPAD_AXIS_LEFTX)
+ """"""
+ LEFTY = int(lib.SDL_GAMEPAD_AXIS_LEFTY)
+ """"""
+ RIGHTX = int(lib.SDL_GAMEPAD_AXIS_RIGHTX)
+ """"""
+ RIGHTY = int(lib.SDL_GAMEPAD_AXIS_RIGHTY)
+ """"""
+ TRIGGERLEFT = int(lib.SDL_GAMEPAD_AXIS_LEFT_TRIGGER)
+ """"""
+ TRIGGERRIGHT = int(lib.SDL_GAMEPAD_AXIS_RIGHT_TRIGGER)
+ """"""
+
+
+class ControllerButton(enum.IntEnum):
+ """The standard buttons for a game controller."""
+
+ INVALID = int(lib.SDL_GAMEPAD_BUTTON_INVALID)
+ A = int(lib.SDL_GAMEPAD_BUTTON_SOUTH)
+ """"""
+ B = int(lib.SDL_GAMEPAD_BUTTON_EAST)
+ """"""
+ X = int(lib.SDL_GAMEPAD_BUTTON_WEST)
+ """"""
+ Y = int(lib.SDL_GAMEPAD_BUTTON_NORTH)
+ """"""
+ BACK = int(lib.SDL_GAMEPAD_BUTTON_BACK)
+ """"""
+ GUIDE = int(lib.SDL_GAMEPAD_BUTTON_GUIDE)
+ """"""
+ START = int(lib.SDL_GAMEPAD_BUTTON_START)
+ """"""
+ LEFTSTICK = int(lib.SDL_GAMEPAD_BUTTON_LEFT_STICK)
+ """"""
+ RIGHTSTICK = int(lib.SDL_GAMEPAD_BUTTON_RIGHT_STICK)
+ """"""
+ LEFTSHOULDER = int(lib.SDL_GAMEPAD_BUTTON_LEFT_SHOULDER)
+ """"""
+ RIGHTSHOULDER = int(lib.SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER)
+ """"""
+ DPAD_UP = int(lib.SDL_GAMEPAD_BUTTON_DPAD_UP)
+ """"""
+ DPAD_DOWN = int(lib.SDL_GAMEPAD_BUTTON_DPAD_DOWN)
+ """"""
+ DPAD_LEFT = int(lib.SDL_GAMEPAD_BUTTON_DPAD_LEFT)
+ """"""
+ DPAD_RIGHT = int(lib.SDL_GAMEPAD_BUTTON_DPAD_RIGHT)
+ """"""
+ MISC1 = 15
+ """"""
+ PADDLE1 = 16
+ """"""
+ PADDLE2 = 17
+ """"""
+ PADDLE3 = 18
+ """"""
+ PADDLE4 = 19
+ """"""
+ TOUCHPAD = 20
+ """"""
+
+
+class Joystick:
+ """A low-level SDL joystick.
+
+ .. seealso::
+ https://wiki.libsdl.org/CategoryJoystick
+ """
+
+ _by_instance_id: ClassVar[WeakValueDictionary[int, Joystick]] = WeakValueDictionary()
+ """Currently opened joysticks."""
+
+ def __init__(self, sdl_joystick_p: Any) -> None: # noqa: ANN401
+ """Wrap an SDL joystick C pointer."""
+ self.sdl_joystick_p: Final = sdl_joystick_p
+ """The CFFI pointer to an SDL_Joystick struct."""
+ self.axes: Final[int] = _check_int(lib.SDL_GetNumJoystickAxes(self.sdl_joystick_p), failure=-1)
+ """The total number of axes."""
+ self.balls: Final[int] = _check_int(lib.SDL_GetNumJoystickBalls(self.sdl_joystick_p), failure=-1)
+ """The total number of trackballs."""
+ self.buttons: Final[int] = _check_int(lib.SDL_GetNumJoystickButtons(self.sdl_joystick_p), failure=-1)
+ """The total number of buttons."""
+ self.hats: Final[int] = _check_int(lib.SDL_GetNumJoystickHats(self.sdl_joystick_p), failure=-1)
+ """The total number of hats."""
+ self.name: Final[str] = str(ffi.string(lib.SDL_GetJoystickName(self.sdl_joystick_p)), encoding="utf-8")
+ """The name of this joystick."""
+ self.guid: Final[str] = self._get_guid()
+ """The GUID of this joystick."""
+ self.id: Final[int] = _check(lib.SDL_GetJoystickID(self.sdl_joystick_p))
+ """The instance ID of this joystick. This is not the same as the device ID."""
+ self._keep_alive: Any = None
+ """The owner of this objects memory if this object does not own itself."""
+
+ self._by_instance_id[self.id] = self
+
+ @classmethod
+ def _open(cls, device_index: int) -> Joystick:
+ tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.JOYSTICK)
+ p = _check_p(ffi.gc(lib.SDL_OpenJoystick(device_index), lib.SDL_CloseJoystick))
+ return cls(p)
+
+ @classmethod
+ def _from_instance_id(cls, instance_id: int) -> Joystick:
+ return cls._by_instance_id[instance_id]
+
+ def __eq__(self, other: object) -> bool:
+ """Return True if `self` and `other` refer to the same joystick."""
+ if isinstance(other, Joystick):
+ return self.id == other.id
+ return NotImplemented
+
+ def __hash__(self) -> int:
+ """Return the joystick id as a hash."""
+ return hash(self.id)
+
+ def _get_guid(self) -> str:
+ guid_str = ffi.new("char[33]")
+ lib.SDL_GUIDToString(lib.SDL_GetJoystickGUID(self.sdl_joystick_p), guid_str, len(guid_str))
+ return str(ffi.string(guid_str), encoding="ascii")
+
+ def get_axis(self, axis: int) -> int:
+ """Return the raw value of `axis` in the range -32768 to 32767."""
+ return int(lib.SDL_GetJoystickAxis(self.sdl_joystick_p, axis))
+
+ def get_ball(self, ball: int) -> tuple[int, int]:
+ """Return the values (delta_x, delta_y) of `ball` since the last poll."""
+ xy = ffi.new("int[2]")
+ _check(lib.SDL_GetJoystickBall(self.sdl_joystick_p, ball, xy, xy + 1))
+ return int(xy[0]), int(xy[1])
+
+ def get_button(self, button: int) -> bool:
+ """Return True if `button` is currently held."""
+ return bool(lib.SDL_GetJoystickButton(self.sdl_joystick_p, button))
+
+ def get_hat(self, hat: int) -> tuple[Literal[-1, 0, 1], Literal[-1, 0, 1]]:
+ """Return the direction of `hat` as (x, y). With (-1, -1) being in the upper-left."""
+ return _HAT_DIRECTIONS[lib.SDL_GetJoystickHat(self.sdl_joystick_p, hat)]
+
+
+class GameController:
+ """A standard interface for an Xbox 360 style game controller."""
+
+ _by_instance_id: ClassVar[WeakValueDictionary[int, GameController]] = WeakValueDictionary()
+ """Currently opened controllers."""
+
+ def __init__(self, sdl_controller_p: Any) -> None: # noqa: ANN401
+ """Wrap an SDL controller C pointer."""
+ self.sdl_controller_p: Final = sdl_controller_p
+ self.joystick: Final = Joystick(lib.SDL_GetGamepadJoystick(self.sdl_controller_p))
+ """The :any:`Joystick` associated with this controller."""
+ self.joystick._keep_alive = self.sdl_controller_p # This objects real owner needs to be kept alive.
+ self._by_instance_id[self.joystick.id] = self
+
+ @classmethod
+ def _open(cls, joystick_index: int) -> GameController:
+ return cls(_check_p(ffi.gc(lib.SDL_OpenGamepad(joystick_index), lib.SDL_CloseGamepad)))
+
+ @classmethod
+ def _from_instance_id(cls, instance_id: int) -> GameController:
+ return cls._by_instance_id[instance_id]
+
+ def get_button(self, button: ControllerButton) -> bool:
+ """Return True if `button` is currently held."""
+ return bool(lib.SDL_GetGamepadButton(self.sdl_controller_p, button))
+
+ def get_axis(self, axis: ControllerAxis) -> int:
+ """Return the state of the given `axis`.
+
+ The state is usually a value from -32768 to 32767, with positive values towards the lower-right direction.
+ Triggers have the range of 0 to 32767 instead.
+ """
+ return int(lib.SDL_GetGamepadAxis(self.sdl_controller_p, axis))
+
+ def __eq__(self, other: object) -> bool:
+ """Return True if `self` and `other` are both controllers referring to the same joystick."""
+ if isinstance(other, GameController):
+ return self.joystick.id == other.joystick.id
+ return NotImplemented
+
+ def __hash__(self) -> int:
+ """Return the joystick id as a hash."""
+ return hash(self.joystick.id)
+
+ # These could exist as convenience functions, but the get_X functions are probably better.
+ @property
+ def _left_x(self) -> int:
+ """Return the position of this axis (-32768 to 32767)."""
+ return int(lib.SDL_GetGamepadAxis(self.sdl_controller_p, lib.SDL_GAMEPAD_AXIS_LEFTX))
+
+ @property
+ def _left_y(self) -> int:
+ """Return the position of this axis (-32768 to 32767)."""
+ return int(lib.SDL_GetGamepadAxis(self.sdl_controller_p, lib.SDL_GAMEPAD_AXIS_LEFTY))
+
+ @property
+ def _right_x(self) -> int:
+ """Return the position of this axis (-32768 to 32767)."""
+ return int(lib.SDL_GetGamepadAxis(self.sdl_controller_p, lib.SDL_GAMEPAD_AXIS_RIGHTX))
+
+ @property
+ def _right_y(self) -> int:
+ """Return the position of this axis (-32768 to 32767)."""
+ return int(lib.SDL_GetGamepadAxis(self.sdl_controller_p, lib.SDL_GAMEPAD_AXIS_RIGHTY))
+
+ @property
+ def _trigger_left(self) -> int:
+ """Return the position of this trigger (0 to 32767)."""
+ return int(lib.SDL_GetGamepadAxis(self.sdl_controller_p, lib.SDL_GAMEPAD_AXIS_LEFT_TRIGGER))
+
+ @property
+ def _trigger_right(self) -> int:
+ """Return the position of this trigger (0 to 32767)."""
+ return int(lib.SDL_GetGamepadAxis(self.sdl_controller_p, lib.SDL_GAMEPAD_AXIS_RIGHT_TRIGGER))
+
+ @property
+ def _a(self) -> bool:
+ """Return True if this button is held."""
+ return bool(lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_SOUTH))
+
+ @property
+ def _b(self) -> bool:
+ """Return True if this button is held."""
+ return bool(lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_EAST))
+
+ @property
+ def _x(self) -> bool:
+ """Return True if this button is held."""
+ return bool(lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_WEST))
+
+ @property
+ def _y(self) -> bool:
+ """Return True if this button is held."""
+ return bool(lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_NORTH))
+
+ @property
+ def _back(self) -> bool:
+ """Return True if this button is held."""
+ return bool(lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_BACK))
+
+ @property
+ def _guide(self) -> bool:
+ """Return True if this button is held."""
+ return bool(lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_GUIDE))
+
+ @property
+ def _start(self) -> bool:
+ """Return True if this button is held."""
+ return bool(lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_START))
+
+ @property
+ def _left_stick(self) -> bool:
+ """Return True if this button is held."""
+ return bool(lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_LEFT_STICK))
+
+ @property
+ def _right_stick(self) -> bool:
+ """Return True if this button is held."""
+ return bool(lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_RIGHT_STICK))
+
+ @property
+ def _left_shoulder(self) -> bool:
+ """Return True if this button is held."""
+ return bool(lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_LEFT_SHOULDER))
+
+ @property
+ def _right_shoulder(self) -> bool:
+ """Return True if this button is held."""
+ return bool(lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER))
+
+ @property
+ def _dpad(self) -> tuple[Literal[-1, 0, 1], Literal[-1, 0, 1]]:
+ return (
+ lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_DPAD_RIGHT)
+ - lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_DPAD_LEFT),
+ lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_DPAD_DOWN)
+ - lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_DPAD_UP),
+ ) # type: ignore[return-value] # Boolean math has predictable values
+
+ @property
+ def _misc1(self) -> bool:
+ """Return True if this button is held."""
+ return bool(lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_MISC1))
+
+ @property
+ def _paddle1(self) -> bool:
+ """Return True if this button is held."""
+ return bool(lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1))
+
+ @property
+ def _paddle2(self) -> bool:
+ """Return True if this button is held."""
+ return bool(lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_LEFT_PADDLE1))
+
+ @property
+ def _paddle3(self) -> bool:
+ """Return True if this button is held."""
+ return bool(lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2))
+
+ @property
+ def _paddle4(self) -> bool:
+ """Return True if this button is held."""
+ return bool(lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_LEFT_PADDLE2))
+
+ @property
+ def _touchpad(self) -> bool:
+ """Return True if this button is held."""
+ return bool(lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_TOUCHPAD))
+
+
+def init() -> None:
+ """Initialize SDL's joystick and game controller subsystems."""
+ controller_systems = tcod.sdl.sys.Subsystem.JOYSTICK | tcod.sdl.sys.Subsystem.GAMECONTROLLER
+ if tcod.sdl.sys.Subsystem(lib.SDL_WasInit(controller_systems)) == controller_systems:
+ return # Already initialized
+ tcod.sdl.sys.init(controller_systems)
+
+
+def _get_number() -> int:
+ """Return the number of attached joysticks."""
+ init()
+ count = ffi.new("int*")
+ lib.SDL_GetJoysticks(count)
+ return int(count[0])
+
+
+def get_joysticks() -> list[Joystick]:
+ """Return a list of all connected joystick devices."""
+ return [Joystick._open(i) for i in range(_get_number())]
+
+
+def get_controllers() -> list[GameController]:
+ """Return a list of all connected game controllers.
+
+ This ignores joysticks without a game controller mapping.
+ """
+ return [GameController._open(i) for i in range(_get_number()) if lib.SDL_IsGamepad(i)]
+
+
+def _get_all() -> list[Joystick | GameController]:
+ """Return a list of all connected joystick or controller devices.
+
+ If the joystick has a controller mapping then it is returned as a :any:`GameController`.
+ Otherwise it is returned as a :any:`Joystick`.
+ """
+ return [GameController._open(i) if lib.SDL_IsGamepad(i) else Joystick._open(i) for i in range(_get_number())]
+
+
+def joystick_event_state(new_state: bool | None = None) -> bool: # noqa: FBT001
+ """Check or set joystick event polling.
+
+ .. seealso::
+ https://wiki.libsdl.org/SDL3/SDL_SetJoystickEventsEnabled
+ """
+ if new_state is True:
+ lib.SDL_SetJoystickEventsEnabled(True) # noqa: FBT003
+ elif new_state is False:
+ lib.SDL_SetJoystickEventsEnabled(False) # noqa: FBT003
+ return lib.SDL_JoystickEventsEnabled()
+
+
+def controller_event_state(new_state: bool | None = None) -> bool: # noqa: FBT001
+ """Check or set game controller event polling.
+
+ .. seealso::
+ https://wiki.libsdl.org/SDL3/SDL_SetGamepadEventsEnabled
+ """
+ if new_state is True:
+ lib.SDL_SetGamepadEventsEnabled(True) # noqa: FBT003
+ elif new_state is False:
+ lib.SDL_SetGamepadEventsEnabled(False) # noqa: FBT003
+ return lib.SDL_GamepadEventsEnabled()
diff --git a/tcod/sdl/mouse.py b/tcod/sdl/mouse.py
new file mode 100644
index 00000000..c1dbff7f
--- /dev/null
+++ b/tcod/sdl/mouse.py
@@ -0,0 +1,274 @@
+"""SDL mouse and cursor functions.
+
+You can use this module to move or capture the cursor.
+
+You can also set the cursor icon to an OS-defined or custom icon.
+
+.. versionadded:: 13.5
+"""
+
+from __future__ import annotations
+
+import enum
+from typing import TYPE_CHECKING, Any
+
+import numpy as np
+from typing_extensions import deprecated
+
+import tcod.event
+import tcod.sdl.video
+from tcod.cffi import ffi, lib
+from tcod.sdl._internal import _check, _check_p
+
+if TYPE_CHECKING:
+ from numpy.typing import ArrayLike, NDArray
+
+
+class Cursor:
+ """A cursor icon for use with :any:`set_cursor`."""
+
+ def __init__(self, sdl_cursor_p: Any) -> None: # noqa: ANN401
+ """Wrap an SDL cursor C pointer."""
+ if ffi.typeof(sdl_cursor_p) is not ffi.typeof("struct SDL_Cursor*"):
+ msg = f"Expected a {ffi.typeof('struct SDL_Cursor*')} type (was {ffi.typeof(sdl_cursor_p)})."
+ raise TypeError(msg)
+ if not sdl_cursor_p:
+ msg = "C pointer must not be null."
+ raise TypeError(msg)
+ self.p = sdl_cursor_p
+
+ def __eq__(self, other: object) -> bool:
+ """Return True if `self` is the same cursor as `other`."""
+ if isinstance(other, Cursor):
+ return bool(self.p == getattr(other, "p", None))
+ return NotImplemented
+
+ def __hash__(self) -> int:
+ """Returns the hash of this objects C pointer."""
+ return hash(self.p)
+
+ @classmethod
+ def _claim(cls, sdl_cursor_p: Any) -> Cursor: # noqa: ANN401
+ """Verify and wrap this pointer in a garbage collector before returning a Cursor."""
+ return cls(ffi.gc(_check_p(sdl_cursor_p), lib.SDL_DestroyCursor))
+
+
+class SystemCursor(enum.IntEnum):
+ """An enumerator of system cursor icons."""
+
+ ARROW = 0
+ """"""
+ IBEAM = enum.auto()
+ """"""
+ WAIT = enum.auto()
+ """"""
+ CROSSHAIR = enum.auto()
+ """"""
+ WAITARROW = enum.auto()
+ """"""
+ SIZENWSE = enum.auto()
+ """"""
+ SIZENESW = enum.auto()
+ """"""
+ SIZEWE = enum.auto()
+ """"""
+ SIZENS = enum.auto()
+ """"""
+ SIZEALL = enum.auto()
+ """"""
+ NO = enum.auto()
+ """"""
+ HAND = enum.auto()
+ """"""
+
+
+def new_cursor(data: NDArray[np.bool_], mask: NDArray[np.bool_], hot_xy: tuple[int, int] = (0, 0)) -> Cursor:
+ """Return a new non-color Cursor from the provided parameters.
+
+ Args:
+ data: A row-major boolean array for the data parameters. See the SDL docs for more info.
+ mask: A row-major boolean array for the mask parameters. See the SDL docs for more info.
+ hot_xy: The position of the pointer relative to the mouse sprite, starting from the upper-left at (0, 0).
+
+ .. seealso::
+ :any:`set_cursor`
+ https://wiki.libsdl.org/SDL_CreateCursor
+ """
+ if len(data.shape) != 2: # noqa: PLR2004
+ msg = "Data and mask arrays must be 2D."
+ raise TypeError(msg)
+ if data.shape != mask.shape:
+ msg = "Data and mask arrays must have the same shape."
+ raise TypeError(msg)
+ height, width = data.shape
+ data_packed = np.packbits(data, axis=0, bitorder="big")
+ mask_packed = np.packbits(mask, axis=0, bitorder="big")
+ return Cursor._claim(
+ lib.SDL_CreateCursor(
+ ffi.from_buffer("uint8_t*", data_packed), ffi.from_buffer("uint8_t*", mask_packed), width, height, *hot_xy
+ )
+ )
+
+
+def new_color_cursor(pixels: ArrayLike, hot_xy: tuple[int, int]) -> Cursor:
+ """Create a new color cursor.
+
+ Args:
+ pixels: A row-major array of RGB or RGBA pixels.
+ hot_xy: The position of the pointer relative to the mouse sprite, starting from the upper-left at (0, 0).
+
+ .. seealso::
+ :any:`set_cursor`
+ """
+ surface = tcod.sdl.video._TempSurface(pixels)
+ return Cursor._claim(lib.SDL_CreateColorCursor(surface.p, *hot_xy))
+
+
+def new_system_cursor(cursor: SystemCursor) -> Cursor:
+ """Return a new Cursor from one of the system cursors labeled by SystemCursor.
+
+ .. seealso::
+ :any:`set_cursor`
+ """
+ return Cursor._claim(lib.SDL_CreateSystemCursor(cursor))
+
+
+def set_cursor(cursor: Cursor | SystemCursor | None) -> None:
+ """Change the active cursor to the one provided.
+
+ Args:
+ cursor: A cursor created from :any:`new_cursor`, :any:`new_color_cursor`, or :any:`new_system_cursor`.
+ Can also take values of :any:`SystemCursor` directly.
+ None will force the current cursor to be redrawn.
+ """
+ if isinstance(cursor, SystemCursor):
+ cursor = new_system_cursor(cursor)
+ lib.SDL_SetCursor(cursor.p if cursor is not None else ffi.NULL)
+
+
+def get_default_cursor() -> Cursor:
+ """Return the default cursor."""
+ return Cursor(_check_p(lib.SDL_GetDefaultCursor()))
+
+
+def get_cursor() -> Cursor | None:
+ """Return the active cursor, or None if these is no mouse."""
+ cursor_p = lib.SDL_GetCursor()
+ return Cursor(cursor_p) if cursor_p else None
+
+
+def capture(enable: bool) -> None: # noqa: FBT001
+ """Enable or disable mouse capture to track the mouse outside of a window.
+
+ It is highly recommended to read the related remarks section in the SDL docs before using this.
+
+ Example::
+
+ # Make mouse button presses capture the mouse until all buttons are released.
+ # This means that dragging the mouse outside of the window will not cause an interruption in motion events.
+ for event in tcod.event.get():
+ match event:
+ case tcod.event.MouseButtonDown(button=button, pixel=pixel): # Clicking the window captures the mouse.
+ tcod.sdl.mouse.capture(True)
+ case tcod.event.MouseButtonUp(): # When all buttons are released then the mouse is released.
+ if tcod.event.mouse.get_global_state().state == 0:
+ tcod.sdl.mouse.capture(False)
+ case tcod.event.MouseMotion(pixel=pixel, pixel_motion=pixel_motion, state=state):
+ pass # While a button is held this event is still captured outside of the window.
+
+ .. seealso::
+ :any:`tcod.sdl.mouse.set_relative_mode`
+ https://wiki.libsdl.org/SDL_CaptureMouse
+ """
+ _check(lib.SDL_CaptureMouse(enable))
+
+
+@deprecated("Set 'Window.relative_mouse_mode = value' instead.")
+def set_relative_mode(enable: bool) -> None: # noqa: FBT001
+ """Enable or disable relative mouse mode which will lock and hide the mouse and only report mouse motion.
+
+ .. seealso::
+ :any:`tcod.sdl.mouse.capture`
+ https://wiki.libsdl.org/SDL_SetWindowRelativeMouseMode
+
+ .. deprecated:: 19.0
+ Replaced with :any:`tcod.sdl.video.Window.relative_mouse_mode`
+ """
+ _check(lib.SDL_SetWindowRelativeMouseMode(lib.SDL_GetMouseFocus(), enable))
+
+
+@deprecated("Check 'Window.relative_mouse_mode' instead.")
+def get_relative_mode() -> bool:
+ """Return True if relative mouse mode is enabled.
+
+ .. deprecated:: 19.0
+ Replaced with :any:`tcod.sdl.video.Window.relative_mouse_mode`
+ """
+ return bool(lib.SDL_GetWindowRelativeMouseMode(lib.SDL_GetMouseFocus()))
+
+
+def get_global_state() -> tcod.event.MouseState:
+ """Return the mouse state relative to the desktop.
+
+ .. seealso::
+ https://wiki.libsdl.org/SDL_GetGlobalMouseState
+ """
+ xy = ffi.new("int[2]")
+ state = lib.SDL_GetGlobalMouseState(xy, xy + 1)
+ return tcod.event.MouseState(position=tcod.event.Point(xy[0], xy[1]), state=state)
+
+
+def get_relative_state() -> tcod.event.MouseState:
+ """Return the mouse state, the coordinates are relative to the last time this function was called.
+
+ .. seealso::
+ https://wiki.libsdl.org/SDL_GetRelativeMouseState
+ """
+ xy = ffi.new("int[2]")
+ state = lib.SDL_GetRelativeMouseState(xy, xy + 1)
+ return tcod.event.MouseState(position=tcod.event.Point(xy[0], xy[1]), state=state)
+
+
+def get_state() -> tcod.event.MouseState:
+ """Return the mouse state relative to the window with mouse focus.
+
+ .. seealso::
+ https://wiki.libsdl.org/SDL_GetMouseState
+ """
+ xy = ffi.new("int[2]")
+ state = lib.SDL_GetMouseState(xy, xy + 1)
+ return tcod.event.MouseState(position=tcod.event.Point(xy[0], xy[1]), state=state)
+
+
+def get_focus() -> tcod.sdl.video.Window | None:
+ """Return the window which currently has mouse focus."""
+ window_p = lib.SDL_GetMouseFocus()
+ return tcod.sdl.video.Window(window_p) if window_p else None
+
+
+def warp_global(x: int, y: int) -> None:
+ """Move the mouse cursor to a position on the desktop."""
+ _check(lib.SDL_WarpMouseGlobal(x, y))
+
+
+def warp_in_window(window: tcod.sdl.video.Window, x: int, y: int) -> None:
+ """Move the mouse cursor to a position within a window."""
+ lib.SDL_WarpMouseInWindow(window.p, x, y)
+
+
+def show(visible: bool | None = None) -> bool: # noqa: FBT001
+ """Optionally show or hide the mouse cursor then return the state of the cursor.
+
+ Args:
+ visible: If None then only return the current state. Otherwise set the mouse visibility.
+
+ Returns:
+ True if the cursor is visible.
+
+ .. versionadded:: 16.0
+ """
+ if visible is True:
+ lib.SDL_ShowCursor()
+ elif visible is False:
+ lib.SDL_HideCursor()
+ return lib.SDL_CursorVisible()
diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py
new file mode 100644
index 00000000..05147825
--- /dev/null
+++ b/tcod/sdl/render.py
@@ -0,0 +1,819 @@
+"""SDL Rendering functionality.
+
+.. versionadded:: 13.4
+"""
+
+from __future__ import annotations
+
+import enum
+from typing import TYPE_CHECKING, Any, Final, Literal
+
+import numpy as np
+from typing_extensions import deprecated
+
+import tcod.sdl.constants
+import tcod.sdl.video
+from tcod.cffi import ffi, lib
+from tcod.sdl._internal import Properties, _check, _check_p
+
+if TYPE_CHECKING:
+ from collections.abc import Sequence
+
+ from numpy.typing import NDArray
+
+
+class TextureAccess(enum.IntEnum):
+ """Determines how a texture is expected to be used."""
+
+ STATIC = 0
+ """Texture rarely changes."""
+ STREAMING = 1
+ """Texture frequently changes."""
+ TARGET = 2
+ """Texture will be used as a render target."""
+
+
+class RendererFlip(enum.IntFlag):
+ """Flip parameter for :any:`Renderer.copy`."""
+
+ NONE = 0
+ """Default value, no flip."""
+ HORIZONTAL = 1
+ """Flip the image horizontally."""
+ VERTICAL = 2
+ """Flip the image vertically."""
+
+
+class LogicalPresentation(enum.IntEnum):
+ """SDL logical presentation modes.
+
+ See https://wiki.libsdl.org/SDL3/SDL_RendererLogicalPresentation
+
+ .. versionadded:: 19.0
+ """
+
+ DISABLED = 0
+ """"""
+ STRETCH = 1
+ """"""
+ LETTERBOX = 2
+ """"""
+ OVERSCAN = 3
+ """"""
+ INTEGER_SCALE = 4
+ """"""
+
+
+class BlendFactor(enum.IntEnum):
+ """SDL blend factors.
+
+ .. seealso::
+ :any:`compose_blend_mode`
+ https://wiki.libsdl.org/SDL_BlendFactor
+
+ .. versionadded:: 13.5
+ """
+
+ ZERO = 0x1
+ """"""
+ ONE = 0x2
+ """"""
+ SRC_COLOR = 0x3
+ """"""
+ ONE_MINUS_SRC_COLOR = 0x4
+ """"""
+ SRC_ALPHA = 0x5
+ """"""
+ ONE_MINUS_SRC_ALPHA = 0x6
+ """"""
+ DST_COLOR = 0x7
+ """"""
+ ONE_MINUS_DST_COLOR = 0x8
+ """"""
+ DST_ALPHA = 0x9
+ """"""
+ ONE_MINUS_DST_ALPHA = 0xA
+ """"""
+
+
+class BlendOperation(enum.IntEnum):
+ """SDL blend operations.
+
+ .. seealso::
+ :any:`compose_blend_mode`
+ https://wiki.libsdl.org/SDL_BlendOperation
+
+ .. versionadded:: 13.5
+ """
+
+ ADD = 0x1
+ """dest + source"""
+ SUBTRACT = 0x2
+ """dest - source"""
+ REV_SUBTRACT = 0x3
+ """source - dest"""
+ MINIMUM = 0x4
+ """min(dest, source)"""
+ MAXIMUM = 0x5
+ """max(dest, source)"""
+
+
+class BlendMode(enum.IntEnum):
+ """SDL blend modes.
+
+ .. seealso::
+ :any:`Texture.blend_mode`
+ :any:`Renderer.draw_blend_mode`
+ :any:`compose_blend_mode`
+
+ .. versionadded:: 13.5
+ """
+
+ NONE = 0x00000000
+ """"""
+ BLEND = 0x00000001
+ """"""
+ ADD = 0x00000002
+ """"""
+ MOD = 0x00000004
+ """"""
+ INVALID = 0x7FFFFFFF
+ """"""
+
+
+class ScaleMode(enum.IntEnum):
+ """Texture scaling modes.
+
+ .. versionadded:: 19.3
+ """
+
+ NEAREST = lib.SDL_SCALEMODE_NEAREST
+ """Nearing neighbor."""
+ LINEAR = lib.SDL_SCALEMODE_LINEAR
+ """Linier filtering."""
+ # PIXELART = lib.SDL_SCALEMODE_PIXELART # Needs SDL 3.4 # noqa: ERA001
+
+
+def compose_blend_mode( # noqa: PLR0913
+ source_color_factor: BlendFactor,
+ dest_color_factor: BlendFactor,
+ color_operation: BlendOperation,
+ source_alpha_factor: BlendFactor,
+ dest_alpha_factor: BlendFactor,
+ alpha_operation: BlendOperation,
+) -> BlendMode:
+ """Return a custom blend mode composed of the given factors and operations.
+
+ .. seealso::
+ https://wiki.libsdl.org/SDL_ComposeCustomBlendMode
+
+ .. versionadded:: 13.5
+ """
+ return BlendMode(
+ lib.SDL_ComposeCustomBlendMode(
+ source_color_factor,
+ dest_color_factor,
+ color_operation,
+ source_alpha_factor,
+ dest_alpha_factor,
+ alpha_operation,
+ )
+ )
+
+
+class Texture:
+ """SDL hardware textures.
+
+ Create a new texture using :any:`Renderer.new_texture` or :any:`Renderer.upload_texture`.
+ """
+
+ def __init__(self, sdl_texture_p: Any, sdl_renderer_p: Any = None) -> None: # noqa: ANN401
+ """Encapsulate an SDL_Texture pointer. This function is private."""
+ self.p = sdl_texture_p
+ self._sdl_renderer_p = sdl_renderer_p # Keep alive.
+
+ props = Properties(lib.SDL_GetTextureProperties(self.p))
+ self.format: Final[int] = props[(tcod.sdl.constants.SDL_PROP_TEXTURE_FORMAT_NUMBER, int)]
+ """Texture format, read only."""
+ self.access: Final[TextureAccess] = TextureAccess(
+ props[(tcod.sdl.constants.SDL_PROP_TEXTURE_ACCESS_NUMBER, int)]
+ )
+ """Texture access mode, read only.
+
+ .. versionchanged:: 13.5
+ Attribute is now a :any:`TextureAccess` value.
+ """
+ self.width: Final[int] = props[(tcod.sdl.constants.SDL_PROP_TEXTURE_WIDTH_NUMBER, int)]
+ """Texture pixel width, read only."""
+ self.height: Final[int] = props[(tcod.sdl.constants.SDL_PROP_TEXTURE_HEIGHT_NUMBER, int)]
+ """Texture pixel height, read only."""
+
+ def __eq__(self, other: object) -> bool:
+ """Return True if compared to the same texture."""
+ if isinstance(other, Texture):
+ return bool(self.p == other.p)
+ return NotImplemented
+
+ def __hash__(self) -> int:
+ """Return hash for the owned pointer."""
+ return hash(self.p)
+
+ def update(self, pixels: NDArray[Any], rect: tuple[int, int, int, int] | None = None) -> None:
+ """Update the pixel data of this texture.
+
+ .. versionadded:: 13.5
+ """
+ if rect is None:
+ rect = (0, 0, self.width, self.height)
+ assert pixels.shape[:2] == (self.height, self.width)
+ if not pixels[0].flags.c_contiguous:
+ pixels = np.ascontiguousarray(pixels)
+ _check(lib.SDL_UpdateTexture(self.p, (rect,), ffi.cast("void*", pixels.ctypes.data), pixels.strides[0]))
+
+ @property
+ def alpha_mod(self) -> int:
+ """Texture alpha modulate value, can be set to 0 - 255."""
+ out = ffi.new("uint8_t*")
+ _check(lib.SDL_GetTextureAlphaMod(self.p, out))
+ return int(out[0])
+
+ @alpha_mod.setter
+ def alpha_mod(self, value: int) -> None:
+ _check(lib.SDL_SetTextureAlphaMod(self.p, value))
+
+ @property
+ def blend_mode(self) -> BlendMode:
+ """Texture blend mode, can be set.
+
+ .. versionchanged:: 13.5
+ Property now returns a BlendMode instance.
+ """
+ out = ffi.new("SDL_BlendMode*")
+ _check(lib.SDL_GetTextureBlendMode(self.p, out))
+ return BlendMode(out[0])
+
+ @blend_mode.setter
+ def blend_mode(self, value: int) -> None:
+ _check(lib.SDL_SetTextureBlendMode(self.p, value))
+
+ @property
+ def color_mod(self) -> tuple[int, int, int]:
+ """Texture RGB color modulate values, can be set."""
+ rgb = ffi.new("uint8_t[3]")
+ _check(lib.SDL_GetTextureColorMod(self.p, rgb, rgb + 1, rgb + 2))
+ return int(rgb[0]), int(rgb[1]), int(rgb[2])
+
+ @color_mod.setter
+ def color_mod(self, rgb: tuple[int, int, int]) -> None:
+ _check(lib.SDL_SetTextureColorMod(self.p, rgb[0], rgb[1], rgb[2]))
+
+ @property
+ def scale_mode(self) -> ScaleMode:
+ """Get or set this textures :any:`ScaleMode`.
+
+ .. versionadded:: 19.3
+ """
+ mode = ffi.new("SDL_ScaleMode*")
+ _check(lib.SDL_GetTextureScaleMode(self.p, mode))
+ return ScaleMode(mode[0])
+
+ @scale_mode.setter
+ def scale_mode(self, value: ScaleMode, /) -> None:
+ _check(lib.SDL_SetTextureScaleMode(self.p, value))
+
+
+class _RestoreTargetContext:
+ """A context manager which tracks the current render target and restores it on exiting."""
+
+ def __init__(self, renderer: Renderer) -> None:
+ self.renderer = renderer
+ self.old_texture_p = lib.SDL_GetRenderTarget(renderer.p)
+
+ def __enter__(self) -> None:
+ pass
+
+ def __exit__(self, *_: object) -> None:
+ _check(lib.SDL_SetRenderTarget(self.renderer.p, self.old_texture_p))
+
+
+class Renderer:
+ """SDL Renderer."""
+
+ def __init__(self, sdl_renderer_p: Any) -> None: # noqa: ANN401
+ """Encapsulate an SDL_Renderer pointer. This function is private."""
+ if ffi.typeof(sdl_renderer_p) is not ffi.typeof("struct SDL_Renderer*"):
+ msg = f"Expected a {ffi.typeof('struct SDL_Window*')} type (was {ffi.typeof(sdl_renderer_p)})."
+ raise TypeError(msg)
+ if not sdl_renderer_p:
+ msg = "C pointer must not be null."
+ raise TypeError(msg)
+ self.p = sdl_renderer_p
+
+ def __eq__(self, other: object) -> bool:
+ """Return True if compared to the same renderer."""
+ if isinstance(other, Renderer):
+ return bool(self.p == other.p)
+ return NotImplemented
+
+ def __hash__(self) -> int:
+ """Return hash for the owned pointer."""
+ return hash(self.p)
+
+ def copy( # noqa: PLR0913
+ self,
+ texture: Texture,
+ source: tuple[float, float, float, float] | None = None,
+ dest: tuple[float, float, float, float] | None = None,
+ angle: float = 0,
+ center: tuple[float, float] | None = None,
+ flip: RendererFlip = RendererFlip.NONE,
+ ) -> None:
+ """Copy a texture to the rendering target.
+
+ Args:
+ texture: The texture to copy onto the current texture target.
+ source: The (x, y, width, height) region of `texture` to copy. If None then the entire texture is copied.
+ dest: The (x, y, width, height) region of the target. If None then the entire target is drawn over.
+ angle: The angle in degrees to rotate the image clockwise.
+ center: The (x, y) point where rotation is applied. If None then the center of `dest` is used.
+ flip: Flips the `texture` when drawing it.
+
+ .. versionchanged:: 13.5
+ `source` and `dest` can now be float tuples.
+ Added the `angle`, `center`, and `flip` parameters.
+ """
+ _check(
+ lib.SDL_RenderTextureRotated(
+ self.p,
+ texture.p,
+ (source,) if source is not None else ffi.NULL,
+ (dest,) if dest is not None else ffi.NULL,
+ angle,
+ (center,) if center is not None else ffi.NULL,
+ flip,
+ )
+ )
+
+ def present(self) -> None:
+ """Present the currently rendered image to the screen."""
+ lib.SDL_RenderPresent(self.p)
+
+ def set_render_target(self, texture: Texture) -> _RestoreTargetContext:
+ """Change the render target to `texture`, returns a context that will restore the original target when exited."""
+ restore = _RestoreTargetContext(self)
+ _check(lib.SDL_SetRenderTarget(self.p, texture.p))
+ return restore
+
+ def new_texture(self, width: int, height: int, *, format: int | None = None, access: int | None = None) -> Texture: # noqa: A002
+ """Allocate and return a new Texture for this renderer.
+
+ Args:
+ width: The pixel width of the new texture.
+ height: The pixel height of the new texture.
+ format: The format the new texture.
+ access: The access mode of the texture. Defaults to :any:`TextureAccess.STATIC`.
+ See :any:`TextureAccess` for more options.
+ """
+ if format is None:
+ format = 0 # noqa: A001
+ if access is None:
+ access = int(lib.SDL_TEXTUREACCESS_STATIC)
+ texture_p = ffi.gc(lib.SDL_CreateTexture(self.p, format, access, width, height), lib.SDL_DestroyTexture)
+ return Texture(texture_p, self.p)
+
+ def upload_texture(self, pixels: NDArray[Any], *, format: int | None = None, access: int | None = None) -> Texture: # noqa: A002
+ """Return a new Texture from an array of pixels.
+
+ Args:
+ pixels: An RGB or RGBA array of pixels in row-major order.
+ format: The format of `pixels` when it isn't a simple RGB or RGBA array.
+ access: The access mode of the texture. Defaults to :any:`TextureAccess.STATIC`.
+ See :any:`TextureAccess` for more options.
+ """
+ if format is None:
+ assert len(pixels.shape) == 3 # noqa: PLR2004
+ assert pixels.dtype == np.uint8
+ if pixels.shape[2] == 4: # noqa: PLR2004
+ format = int(lib.SDL_PIXELFORMAT_RGBA32) # noqa: A001
+ elif pixels.shape[2] == 3: # noqa: PLR2004
+ format = int(lib.SDL_PIXELFORMAT_RGB24) # noqa: A001
+ else:
+ msg = f"Can't determine the format required for an array of shape {pixels.shape}."
+ raise TypeError(msg)
+
+ texture = self.new_texture(pixels.shape[1], pixels.shape[0], format=format, access=access)
+ if not pixels[0].flags["C_CONTIGUOUS"]:
+ pixels = np.ascontiguousarray(pixels)
+ _check(
+ lib.SDL_UpdateTexture(texture.p, ffi.NULL, ffi.cast("const void*", pixels.ctypes.data), pixels.strides[0])
+ )
+ return texture
+
+ @property
+ def draw_color(self) -> tuple[int, int, int, int]:
+ """Get or set the active RGBA draw color for this renderer.
+
+ .. versionadded:: 13.5
+ """
+ rgba = ffi.new("uint8_t[4]")
+ _check(lib.SDL_GetRenderDrawColor(self.p, rgba, rgba + 1, rgba + 2, rgba + 3))
+ return tuple(rgba)
+
+ @draw_color.setter
+ def draw_color(self, rgba: tuple[int, int, int, int]) -> None:
+ _check(lib.SDL_SetRenderDrawColor(self.p, *rgba))
+
+ @property
+ def draw_blend_mode(self) -> BlendMode:
+ """Get or set the active blend mode of this renderer.
+
+ .. versionadded:: 13.5
+ """
+ out = ffi.new("SDL_BlendMode*")
+ _check(lib.SDL_GetRenderDrawBlendMode(self.p, out))
+ return BlendMode(out[0])
+
+ @draw_blend_mode.setter
+ def draw_blend_mode(self, value: int) -> None:
+ _check(lib.SDL_SetRenderDrawBlendMode(self.p, value))
+
+ @property
+ def output_size(self) -> tuple[int, int]:
+ """Get the (width, height) pixel resolution of the current rendering context.
+
+ .. seealso::
+ https://wiki.libsdl.org/SDL3/SDL_GetCurrentRenderOutputSize
+
+ .. versionadded:: 13.5
+ """
+ out = ffi.new("int[2]")
+ _check(lib.SDL_GetCurrentRenderOutputSize(self.p, out, out + 1))
+ return tuple(out)
+
+ @property
+ def clip_rect(self) -> tuple[int, int, int, int] | None:
+ """Get or set the clipping rectangle of this renderer.
+
+ Set to None to disable clipping.
+
+ .. versionadded:: 13.5
+ """
+ if not lib.SDL_RenderClipEnabled(self.p):
+ return None
+ rect = ffi.new("SDL_Rect*")
+ lib.SDL_GetRenderClipRect(self.p, rect)
+ return rect.x, rect.y, rect.w, rect.h
+
+ @clip_rect.setter
+ def clip_rect(self, rect: tuple[int, int, int, int] | None) -> None:
+ rect_p = ffi.NULL if rect is None else ffi.new("SDL_Rect*", rect)
+ _check(lib.SDL_SetRenderClipRect(self.p, rect_p))
+
+ def set_logical_presentation(self, resolution: tuple[int, int], mode: LogicalPresentation) -> None:
+ """Set this renderers device independent resolution.
+
+ .. seealso::
+ https://wiki.libsdl.org/SDL3/SDL_SetRenderLogicalPresentation
+
+ .. versionadded:: 19.0
+ """
+ width, height = resolution
+ _check(lib.SDL_SetRenderLogicalPresentation(self.p, width, height, mode))
+
+ @property
+ def logical_size(self) -> tuple[int, int] | None:
+ """Get current independent (width, height) resolution, or None if logical size is unset.
+
+ .. seealso::
+ https://wiki.libsdl.org/SDL3/SDL_GetRenderLogicalPresentation
+
+ .. versionadded:: 13.5
+
+ .. versionchanged:: 19.0
+ Setter is deprecated, use :any:`set_logical_presentation` instead.
+
+ .. versionchanged:: 20.0
+ Return ``None`` instead of ``(0, 0)`` when logical size is disabled.
+ """
+ out = ffi.new("int[2]")
+ lib.SDL_GetRenderLogicalPresentation(self.p, out, out + 1, ffi.NULL)
+ out_tuple = tuple(out)
+ return None if out_tuple == (0, 0) else out_tuple
+
+ @logical_size.setter
+ @deprecated("Use set_logical_presentation method to correctly setup logical size.")
+ def logical_size(self, size: tuple[int, int]) -> None:
+ width, height = size
+ _check(lib.SDL_SetRenderLogicalPresentation(self.p, width, height, lib.SDL_LOGICAL_PRESENTATION_STRETCH))
+
+ @property
+ def scale(self) -> tuple[float, float]:
+ """Get or set an (x_scale, y_scale) multiplier for drawing.
+
+ .. seealso::
+ https://wiki.libsdl.org/SDL3/SDL_SetRenderScale
+
+ .. versionadded:: 13.5
+ """
+ out = ffi.new("float[2]")
+ lib.SDL_GetRenderScale(self.p, out, out + 1)
+ return out[0], out[1]
+
+ @scale.setter
+ def scale(self, scale: tuple[float, float]) -> None:
+ _check(lib.SDL_SetRenderScale(self.p, *scale))
+
+ @property
+ def viewport(self) -> tuple[int, int, int, int] | None:
+ """Get or set the drawing area for the current rendering target.
+
+ .. seealso::
+ https://wiki.libsdl.org/SDL3/SDL_SetRenderViewport
+
+ .. versionadded:: 13.5
+ """
+ rect = ffi.new("SDL_Rect*")
+ lib.SDL_GetRenderViewport(self.p, rect)
+ return rect.x, rect.y, rect.w, rect.h
+
+ @viewport.setter
+ def viewport(self, rect: tuple[int, int, int, int] | None) -> None:
+ _check(lib.SDL_SetRenderViewport(self.p, (rect,)))
+
+ def set_vsync(self, enable: bool) -> None: # noqa: FBT001
+ """Enable or disable VSync for this renderer.
+
+ .. versionadded:: 13.5
+ """
+ _check(lib.SDL_SetRenderVSync(self.p, enable))
+
+ def read_pixels(
+ self,
+ *,
+ rect: tuple[int, int, int, int] | None = None,
+ format: Literal["RGB", "RGBA"] = "RGBA", # noqa: A002
+ out: NDArray[np.uint8] | None = None,
+ ) -> NDArray[np.uint8]:
+ """Fetch the pixel contents of the current rendering target to an array.
+
+ By default returns an RGBA pixel array of the full target in the shape: ``(height, width, rgba)``.
+ The target can be changed with :any:`set_render_target`
+
+ Args:
+ rect: The ``(left, top, width, height)`` region of the target to fetch, or None for the entire target.
+ format: The pixel format. Defaults to ``"RGBA"``.
+ out: The output array.
+ Can be None or must be an ``np.uint8`` array of shape: ``(height, width, channels)``.
+ Must be C contiguous along the ``(width, channels)`` axes.
+
+ This operation is slow due to coping from VRAM to RAM.
+ When reading the main rendering target this should be called after rendering and before :any:`present`.
+ See https://wiki.libsdl.org/SDL3/SDL_RenderReadPixels
+
+ Returns:
+ The output uint8 array of shape ``(height, width, channels)`` with the fetched pixels.
+
+ .. versionadded:: 15.0
+
+ .. versionchanged:: 19.0
+ `format` no longer accepts `int` values.
+ """
+ surface = _check_p(
+ ffi.gc(lib.SDL_RenderReadPixels(self.p, (rect,) if rect is not None else ffi.NULL), lib.SDL_DestroySurface)
+ )
+ width, height = rect[2:4] if rect is not None else (int(surface.w), int(surface.h))
+ depth = {"RGB": 3, "RGBA": 4}.get(format)
+ if depth is None:
+ msg = f"Pixel format {format!r} not supported by tcod."
+ raise TypeError(msg)
+ expected_shape = height, width, depth
+ if out is None:
+ out = np.empty(expected_shape, dtype=np.uint8)
+ if out.dtype != np.uint8:
+ msg = "`out` must be a uint8 array."
+ raise TypeError(msg)
+ if out.shape != expected_shape:
+ msg = f"Expected `out` to be an array of shape {expected_shape}, got {out.shape} instead."
+ raise TypeError(msg)
+ if not out[0].flags.c_contiguous:
+ msg = "`out` array must be C contiguous."
+ out_surface = tcod.sdl.video._TempSurface(out)
+ _check(lib.SDL_BlitSurface(surface, ffi.NULL, out_surface.p, ffi.NULL))
+ return out
+
+ def clear(self) -> None:
+ """Clear the current render target with :any:`draw_color`.
+
+ .. versionadded:: 13.5
+ """
+ _check(lib.SDL_RenderClear(self.p))
+
+ def fill_rect(self, rect: tuple[float, float, float, float]) -> None:
+ """Fill a rectangle with :any:`draw_color`.
+
+ .. versionadded:: 13.5
+ """
+ _check(lib.SDL_RenderFillRect(self.p, (rect,)))
+
+ def draw_rect(self, rect: tuple[float, float, float, float]) -> None:
+ """Draw a rectangle outline.
+
+ .. versionadded:: 13.5
+ """
+ _check(lib.SDL_RenderFillRects(self.p, (rect,), 1))
+
+ def draw_point(self, xy: tuple[float, float]) -> None:
+ """Draw a point.
+
+ .. versionadded:: 13.5
+ """
+ x, y = xy
+ _check(lib.SDL_RenderPoint(self.p, x, y))
+
+ def draw_line(self, start: tuple[float, float], end: tuple[float, float]) -> None:
+ """Draw a single line.
+
+ .. versionadded:: 13.5
+ """
+ x1, y1 = start
+ x2, y2 = end
+ _check(lib.SDL_RenderLine(self.p, x1, y1, x2, y2))
+
+ @staticmethod
+ def _convert_array(array: NDArray[np.number] | Sequence[Sequence[float]], item_length: int) -> NDArray[np.float32]:
+ """Convert ndarray for a SDL function expecting a C contiguous array of either intc or float32.
+
+ Array shape is enforced to be (n, item_length)
+ """
+ out = np.ascontiguousarray(array, np.float32)
+ if len(out.shape) != 2: # noqa: PLR2004
+ msg = f"Array must have 2 axes, but shape is {out.shape!r}"
+ raise TypeError(msg)
+ if out.shape[1] != item_length:
+ msg = f"Array shape[1] must be {item_length}, but shape is {out.shape!r}"
+ raise TypeError(msg)
+ return out
+
+ def fill_rects(self, rects: NDArray[np.number] | Sequence[tuple[float, float, float, float]]) -> None:
+ """Fill multiple rectangles from an array.
+
+ Args:
+ rects: A sequence or array of (x, y, width, height) rectangles.
+
+ .. versionadded:: 13.5
+ """
+ rects = self._convert_array(rects, item_length=4)
+ _check(lib.SDL_RenderFillRects(self.p, ffi.from_buffer("SDL_FRect*", rects), rects.shape[0]))
+
+ def draw_rects(self, rects: NDArray[np.number] | Sequence[tuple[float, float, float, float]]) -> None:
+ """Draw multiple outlined rectangles from an array.
+
+ Args:
+ rects: A sequence or array of (x, y, width, height) rectangles.
+
+ .. versionadded:: 13.5
+ """
+ rects = self._convert_array(rects, item_length=4)
+ assert len(rects.shape) == 2 # noqa: PLR2004
+ assert rects.shape[1] == 4 # noqa: PLR2004
+ _check(lib.SDL_RenderRects(self.p, ffi.from_buffer("SDL_FRect*", rects), rects.shape[0]))
+
+ def draw_points(self, points: NDArray[np.number] | Sequence[tuple[float, float]]) -> None:
+ """Draw an array of points.
+
+ Args:
+ points: A sequence or array of (x, y) points.
+
+ .. versionadded:: 13.5
+ """
+ points = self._convert_array(points, item_length=2)
+ _check(lib.SDL_RenderPoints(self.p, ffi.from_buffer("SDL_FPoint*", points), points.shape[0]))
+
+ def draw_lines(self, points: NDArray[np.number] | Sequence[tuple[float, float]]) -> None:
+ """Draw a connected series of lines from an array.
+
+ Args:
+ points: A sequence or array of (x, y) points.
+
+ .. versionadded:: 13.5
+ """
+ points = self._convert_array(points, item_length=2)
+ _check(lib.SDL_RenderLines(self.p, ffi.from_buffer("SDL_FPoint*", points), points.shape[0]))
+
+ def geometry(
+ self,
+ texture: Texture | None,
+ xy: NDArray[np.float32] | Sequence[tuple[float, float]],
+ color: NDArray[np.float32] | Sequence[tuple[float, float, float, float]],
+ uv: NDArray[np.float32] | Sequence[tuple[float, float]],
+ indices: NDArray[np.uint8 | np.uint16 | np.uint32] | None = None,
+ ) -> None:
+ """Render triangles from texture and vertex data.
+
+ Args:
+ texture: The SDL texture to render from.
+ xy: A sequence of (x, y) points to buffer.
+ color: A sequence of (r, g, b, a) colors to buffer.
+ uv: A sequence of (x, y) coordinates to buffer.
+ indices: A sequence of indexes referring to the buffered data, every 3 indexes is a triangle to render.
+
+ .. versionadded:: 13.5
+
+ .. versionchanged:: 19.0
+ `color` now takes float values instead of 8-bit integers.
+ """
+ xy = np.ascontiguousarray(xy, np.float32)
+ assert len(xy.shape) == 2 # noqa: PLR2004
+ assert xy.shape[1] == 2 # noqa: PLR2004
+
+ color = np.ascontiguousarray(color, np.float32)
+ assert len(color.shape) == 2 # noqa: PLR2004
+ assert color.shape[1] == 4 # noqa: PLR2004
+
+ uv = np.ascontiguousarray(uv, np.float32)
+ assert len(uv.shape) == 2 # noqa: PLR2004
+ assert uv.shape[1] == 2 # noqa: PLR2004
+ if indices is not None:
+ assert indices.dtype.type in (np.uint8, np.uint16, np.uint32, np.int8, np.int16, np.int32)
+ indices = np.ascontiguousarray(indices)
+ assert len(indices.shape) == 1
+ assert xy.shape[0] == color.shape[0] == uv.shape[0]
+ _check(
+ lib.SDL_RenderGeometryRaw(
+ self.p,
+ texture.p if texture else ffi.NULL,
+ ffi.cast("float*", xy.ctypes.data),
+ xy.strides[0],
+ ffi.cast("SDL_FColor*", color.ctypes.data),
+ color.strides[0],
+ ffi.cast("float*", uv.ctypes.data),
+ uv.strides[0],
+ xy.shape[0], # Number of vertices.
+ ffi.cast("void*", indices.ctypes.data) if indices is not None else ffi.NULL,
+ indices.size if indices is not None else 0,
+ indices.itemsize if indices is not None else 0,
+ )
+ )
+
+ def coordinates_from_window(self, xy: tuple[float, float], /) -> tuple[float, float]:
+ """Return the renderer coordinates from the given windows coordinates.
+
+ .. seealso::
+ https://wiki.libsdl.org/SDL3/SDL_RenderCoordinatesFromWindow
+
+ .. versionadded:: 20.0
+ """
+ x, y = xy
+ out_xy = ffi.new("float[2]")
+ _check(lib.SDL_RenderCoordinatesFromWindow(self.p, x, y, out_xy, out_xy + 1))
+ return tuple(out_xy)
+
+ def coordinates_to_window(self, xy: tuple[float, float], /) -> tuple[float, float]:
+ """Return the window coordinates from the given render coordinates.
+
+ .. seealso::
+ https://wiki.libsdl.org/SDL3/SDL_RenderCoordinatesToWindow
+
+ .. versionadded:: 20.0
+ """
+ x, y = xy
+ out_xy = ffi.new("float[2]")
+ _check(lib.SDL_RenderCoordinatesToWindow(self.p, x, y, out_xy, out_xy + 1))
+ return tuple(out_xy)
+
+
+def new_renderer(
+ window: tcod.sdl.video.Window,
+ *,
+ driver: str | None = None,
+ vsync: int = True,
+) -> Renderer:
+ """Initialize and return a new SDL Renderer.
+
+ Args:
+ window: The window that this renderer will be attached to.
+ driver: Force SDL to use a specific video driver.
+ vsync: If True then Vsync will be enabled.
+
+ Example::
+
+ # Start by creating a window.
+ sdl_window = tcod.sdl.video.new_window(640, 480)
+ # Create a renderer with target texture support.
+ sdl_renderer = tcod.sdl.render.new_renderer(sdl_window)
+
+ .. seealso::
+ :func:`tcod.sdl.video.new_window`
+
+ .. versionchanged:: 19.0
+ Removed `software` and `target_textures` parameters.
+ `vsync` now takes an integer.
+ `driver` now take a string.
+ """
+ props = Properties()
+ props[(tcod.sdl.constants.SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_NUMBER, int)] = vsync
+ props[(tcod.sdl.constants.SDL_PROP_RENDERER_CREATE_WINDOW_POINTER, tcod.sdl.video.Window)] = window
+ if driver is not None:
+ props[(tcod.sdl.constants.SDL_PROP_RENDERER_CREATE_NAME_STRING, str)] = driver
+ renderer_p = _check_p(ffi.gc(lib.SDL_CreateRendererWithProperties(props.p), lib.SDL_DestroyRenderer))
+ return Renderer(renderer_p)
diff --git a/tcod/sdl/sys.py b/tcod/sdl/sys.py
new file mode 100644
index 00000000..c4056c69
--- /dev/null
+++ b/tcod/sdl/sys.py
@@ -0,0 +1,80 @@
+from __future__ import annotations
+
+import enum
+import warnings
+
+from typing_extensions import Self
+
+from tcod.cffi import ffi, lib
+from tcod.sdl._internal import _check, _get_error
+
+
+class Subsystem(enum.IntFlag):
+ TIMER = 0x00000001
+ AUDIO = 0x00000010
+ VIDEO = 0x00000020
+ JOYSTICK = 0x00000200
+ HAPTIC = 0x00001000
+ GAMECONTROLLER = 0x00002000
+ EVENTS = 0x00004000
+ SENSOR = 0x00008000
+
+
+def init(flags: int) -> None:
+ _check(lib.SDL_InitSubSystem(flags))
+
+
+def quit(flags: int | None = None) -> None: # noqa: A001
+ if flags is None:
+ lib.SDL_Quit()
+ return
+ lib.SDL_QuitSubSystem(flags)
+
+
+class _ScopeInit:
+ def __init__(self, flags: int) -> None:
+ init(flags)
+ self.flags = flags
+
+ def close(self) -> None:
+ if self.flags:
+ quit(self.flags)
+ self.flags = 0
+
+ def __del__(self) -> None:
+ self.close()
+
+ def __enter__(self) -> Self:
+ return self
+
+ def __exit__(self, *_: object) -> None:
+ self.close()
+
+
+class _PowerState(enum.IntEnum):
+ UNKNOWN = 0
+ ON_BATTERY = enum.auto()
+ NO_BATTERY = enum.auto()
+ CHARGING = enum.auto()
+ CHARGED = enum.auto()
+
+
+def _get_power_info() -> tuple[_PowerState, int, int]:
+ buffer = ffi.new("int[2]")
+ power_state = _PowerState(lib.SDL_GetPowerInfo(buffer, buffer + 1))
+ seconds_of_power = buffer[0]
+ percentage = buffer[1]
+ return power_state, seconds_of_power, percentage
+
+
+def _get_clipboard() -> str:
+ """Return the text of the clipboard."""
+ text = str(ffi.string(lib.SDL_GetClipboardText()), encoding="utf-8")
+ if not text: # Show the reason for an empty return, this should probably be logged instead.
+ warnings.warn(f"Return string is empty because: {_get_error()}", RuntimeWarning, stacklevel=2)
+ return text
+
+
+def _set_clipboard(text: str) -> None:
+ """Replace the clipboard with text."""
+ _check(lib.SDL_SetClipboardText(text.encode("utf-8")))
diff --git a/tcod/sdl/video.py b/tcod/sdl/video.py
new file mode 100644
index 00000000..e9565f88
--- /dev/null
+++ b/tcod/sdl/video.py
@@ -0,0 +1,591 @@
+"""SDL Window and Display handling.
+
+There are two main ways to access the SDL window.
+Either you can use this module to open a window yourself bypassing libtcod's context,
+or you can use :any:`Context.sdl_window` to get the window being controlled by that context (if the context has one.)
+
+.. versionadded:: 13.4
+"""
+
+from __future__ import annotations
+
+import enum
+import sys
+from typing import TYPE_CHECKING, Any
+
+import numpy as np
+from typing_extensions import Self, deprecated
+
+import tcod.sdl.constants
+from tcod.cffi import ffi, lib
+from tcod.sdl._internal import Properties, _check, _check_p, _required_version
+
+if TYPE_CHECKING:
+ from numpy.typing import ArrayLike, NDArray
+
+__all__ = (
+ "Capitalization",
+ "FlashOperation",
+ "TextInputType",
+ "Window",
+ "WindowFlags",
+ "get_grabbed_window",
+ "new_window",
+ "screen_saver_allowed",
+)
+
+
+class WindowFlags(enum.IntFlag):
+ """Bit flags which make up a windows state.
+
+ .. seealso::
+ https://wiki.libsdl.org/SDL_WindowFlags
+ """
+
+ FULLSCREEN = int(lib.SDL_WINDOW_FULLSCREEN)
+ """"""
+ OPENGL = int(lib.SDL_WINDOW_OPENGL)
+ """"""
+ HIDDEN = int(lib.SDL_WINDOW_HIDDEN)
+ """"""
+ BORDERLESS = int(lib.SDL_WINDOW_BORDERLESS)
+ """"""
+ RESIZABLE = int(lib.SDL_WINDOW_RESIZABLE)
+ """"""
+ MINIMIZED = int(lib.SDL_WINDOW_MINIMIZED)
+ """"""
+ MAXIMIZED = int(lib.SDL_WINDOW_MAXIMIZED)
+ """"""
+ MOUSE_GRABBED = int(lib.SDL_WINDOW_MOUSE_GRABBED)
+ """"""
+ INPUT_FOCUS = int(lib.SDL_WINDOW_INPUT_FOCUS)
+ """"""
+ MOUSE_FOCUS = int(lib.SDL_WINDOW_MOUSE_FOCUS)
+ """"""
+ ALLOW_HIGHDPI = int(lib.SDL_WINDOW_HIGH_PIXEL_DENSITY)
+ """"""
+ MOUSE_CAPTURE = int(lib.SDL_WINDOW_MOUSE_CAPTURE)
+ """"""
+ ALWAYS_ON_TOP = int(lib.SDL_WINDOW_ALWAYS_ON_TOP)
+ """"""
+ UTILITY = int(lib.SDL_WINDOW_UTILITY)
+ """"""
+ TOOLTIP = int(lib.SDL_WINDOW_TOOLTIP)
+ """"""
+ POPUP_MENU = int(lib.SDL_WINDOW_POPUP_MENU)
+ """"""
+ VULKAN = int(lib.SDL_WINDOW_VULKAN)
+ """"""
+ METAL = int(getattr(lib, "SDL_WINDOW_METAL", 0x20000000)) # SDL >= 2.0.14
+ """"""
+
+
+class FlashOperation(enum.IntEnum):
+ """Values for :any:`Window.flash`."""
+
+ CANCEL = 0
+ """Stop flashing."""
+ BRIEFLY = 1
+ """Flash briefly."""
+ UNTIL_FOCUSED = 2
+ """Flash until focus is gained."""
+
+
+class TextInputType(enum.IntEnum):
+ """SDL input types for text input.
+
+ .. seealso::
+ :any:`Window.start_text_input`
+ https://wiki.libsdl.org/SDL3/SDL_TextInputType
+
+ .. versionadded:: 19.1
+ """
+
+ TEXT = lib.SDL_TEXTINPUT_TYPE_TEXT
+ """The input is text."""
+ TEXT_NAME = lib.SDL_TEXTINPUT_TYPE_TEXT_NAME
+ """The input is a person's name."""
+ TEXT_EMAIL = lib.SDL_TEXTINPUT_TYPE_TEXT_EMAIL
+ """The input is an e-mail address."""
+ TEXT_USERNAME = lib.SDL_TEXTINPUT_TYPE_TEXT_USERNAME
+ """The input is a username."""
+ TEXT_PASSWORD_HIDDEN = lib.SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_HIDDEN
+ """The input is a secure password that is hidden."""
+ TEXT_PASSWORD_VISIBLE = lib.SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_VISIBLE
+ """The input is a secure password that is visible."""
+ NUMBER = lib.SDL_TEXTINPUT_TYPE_NUMBER
+ """The input is a number."""
+ NUMBER_PASSWORD_HIDDEN = lib.SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_HIDDEN
+ """The input is a secure PIN that is hidden."""
+ NUMBER_PASSWORD_VISIBLE = lib.SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_VISIBLE
+ """The input is a secure PIN that is visible."""
+
+
+class Capitalization(enum.IntEnum):
+ """Text capitalization for text input.
+
+ .. seealso::
+ :any:`Window.start_text_input`
+ https://wiki.libsdl.org/SDL3/SDL_Capitalization
+
+ .. versionadded:: 19.1
+ """
+
+ NONE = lib.SDL_CAPITALIZE_NONE
+ """No auto-capitalization will be done."""
+ SENTENCES = lib.SDL_CAPITALIZE_SENTENCES
+ """The first letter of sentences will be capitalized."""
+ WORDS = lib.SDL_CAPITALIZE_WORDS
+ """The first letter of words will be capitalized."""
+ LETTERS = lib.SDL_CAPITALIZE_LETTERS
+ """All letters will be capitalized."""
+
+
+class _TempSurface:
+ """Holds a temporary surface derived from a NumPy array."""
+
+ def __init__(self, pixels: ArrayLike) -> None:
+ self._array: NDArray[np.uint8] = np.ascontiguousarray(pixels, dtype=np.uint8)
+ if len(self._array.shape) != 3: # noqa: PLR2004
+ msg = f"NumPy shape must be 3D [y, x, ch] (got {self._array.shape})"
+ raise TypeError(msg)
+ if not (3 <= self._array.shape[2] <= 4): # noqa: PLR2004
+ msg = f"NumPy array must have RGB or RGBA channels. (got {self._array.shape})"
+ raise TypeError(msg)
+ self.p = ffi.gc(
+ _check_p(
+ lib.SDL_CreateSurfaceFrom(
+ self._array.shape[1],
+ self._array.shape[0],
+ lib.SDL_PIXELFORMAT_RGBA32 if self._array.shape[2] == 4 else lib.SDL_PIXELFORMAT_RGB24, # noqa: PLR2004
+ ffi.from_buffer("void*", self._array),
+ self._array.strides[0],
+ )
+ ),
+ lib.SDL_DestroySurface,
+ )
+
+
+class Window:
+ """An SDL Window object.
+
+ Created from :any:`tcod.sdl.video.new_window` when working with SDL directly.
+
+ When using the libtcod :any:`Context` you can access its `Window` via :any:`Context.sdl_window`.
+ """
+
+ def __init__(self, sdl_window_p: Any | int) -> None: # noqa: ANN401
+ """Wrap a SDL_Window pointer or SDL WindowID.
+
+ .. versionchanged:: 21.0
+ Now accepts `int` types as an SDL WindowID.
+ """
+ if isinstance(sdl_window_p, int):
+ sdl_window_p = _check_p(lib.SDL_GetWindowFromID(sdl_window_p))
+ if ffi.typeof(sdl_window_p) is not ffi.typeof("struct SDL_Window*"):
+ msg = "sdl_window_p must be {!r} type (was {!r}).".format(
+ ffi.typeof("struct SDL_Window*"), ffi.typeof(sdl_window_p)
+ )
+ raise TypeError(msg)
+ if not sdl_window_p:
+ msg = "sdl_window_p can not be a null pointer."
+ raise TypeError(msg)
+ self.p = sdl_window_p
+
+ def __eq__(self, other: object) -> bool:
+ """Return True if `self` and `other` wrap the same window."""
+ if not isinstance(other, Window):
+ return NotImplemented
+ return bool(self.p == other.p)
+
+ def __hash__(self) -> int:
+ """Return the hash of this instances SDL window pointer."""
+ return hash(self.p)
+
+ def _as_property_pointer(self) -> Any: # noqa: ANN401
+ return self.p
+
+ @classmethod
+ def _from_property_pointer(cls, raw_cffi_pointer: Any, /) -> Self: # noqa: ANN401
+ return cls(raw_cffi_pointer)
+
+ def set_icon(self, pixels: ArrayLike) -> None:
+ """Set the window icon from an image.
+
+ Args:
+ pixels: A row-major array of RGB or RGBA pixel values.
+ """
+ surface = _TempSurface(pixels)
+ lib.SDL_SetWindowIcon(self.p, surface.p)
+
+ @property
+ def position(self) -> tuple[int, int]:
+ """Get or set the (x, y) position of the window.
+
+ This attribute can be set the move the window.
+ The constants tcod.lib.SDL_WINDOWPOS_CENTERED or tcod.lib.SDL_WINDOWPOS_UNDEFINED may be used.
+ """
+ xy = ffi.new("int[2]")
+ lib.SDL_GetWindowPosition(self.p, xy, xy + 1)
+ return xy[0], xy[1]
+
+ @position.setter
+ def position(self, xy: tuple[int, int]) -> None:
+ x, y = xy
+ lib.SDL_SetWindowPosition(self.p, x, y)
+
+ @property
+ def size(self) -> tuple[int, int]:
+ """Get or set the pixel (width, height) of the window client area.
+
+ This attribute can be set to change the size of the window but the given size must be greater than (1, 1) or
+ else ValueError will be raised.
+ """
+ xy = ffi.new("int[2]")
+ lib.SDL_GetWindowSize(self.p, xy, xy + 1)
+ return xy[0], xy[1]
+
+ @size.setter
+ def size(self, xy: tuple[int, int]) -> None:
+ if any(i <= 0 for i in xy):
+ msg = f"Window size must be greater than zero, not {xy}"
+ raise ValueError(msg)
+ x, y = xy
+ lib.SDL_SetWindowSize(self.p, x, y)
+
+ @property
+ def min_size(self) -> tuple[int, int]:
+ """Get or set this windows minimum client area."""
+ xy = ffi.new("int[2]")
+ lib.SDL_GetWindowMinimumSize(self.p, xy, xy + 1)
+ return xy[0], xy[1]
+
+ @min_size.setter
+ def min_size(self, xy: tuple[int, int]) -> None:
+ lib.SDL_SetWindowMinimumSize(self.p, xy[0], xy[1])
+
+ @property
+ def max_size(self) -> tuple[int, int]:
+ """Get or set this windows maximum client area."""
+ xy = ffi.new("int[2]")
+ lib.SDL_GetWindowMaximumSize(self.p, xy, xy + 1)
+ return xy[0], xy[1]
+
+ @max_size.setter
+ def max_size(self, xy: tuple[int, int]) -> None:
+ lib.SDL_SetWindowMaximumSize(self.p, xy[0], xy[1])
+
+ @property
+ def title(self) -> str:
+ """Get or set the title of the window."""
+ return str(ffi.string(lib.SDL_GetWindowTitle(self.p)), encoding="utf-8")
+
+ @title.setter
+ def title(self, value: str) -> None:
+ lib.SDL_SetWindowTitle(self.p, value.encode("utf-8"))
+
+ @property
+ def flags(self) -> WindowFlags:
+ """The current flags of this window, read-only."""
+ return WindowFlags(lib.SDL_GetWindowFlags(self.p))
+
+ @property
+ def fullscreen(self) -> bool:
+ """Get or set the fullscreen status of this window.
+
+ Example::
+
+ # Toggle fullscreen.
+ window: tcod.sdl.video.Window
+ window.fullscreen = not window.fullscreen
+ """
+ return bool(self.flags & WindowFlags.FULLSCREEN)
+
+ @fullscreen.setter
+ def fullscreen(self, value: bool) -> None:
+ _check(lib.SDL_SetWindowFullscreen(self.p, value))
+
+ @property
+ def resizable(self) -> bool:
+ """Get or set if this window can be resized."""
+ return bool(self.flags & WindowFlags.RESIZABLE)
+
+ @resizable.setter
+ def resizable(self, value: bool) -> None:
+ lib.SDL_SetWindowResizable(self.p, value)
+
+ @property
+ def border_size(self) -> tuple[int, int, int, int]:
+ """Get the (top, left, bottom, right) size of the window decorations around the client area.
+
+ If this fails or the window doesn't have decorations yet then the value will be (0, 0, 0, 0).
+
+ .. seealso::
+ https://wiki.libsdl.org/SDL_GetWindowBordersSize
+ """
+ borders = ffi.new("int[4]")
+ # The return code is ignored.
+ _ = lib.SDL_GetWindowBordersSize(self.p, borders, borders + 1, borders + 2, borders + 3)
+ return borders[0], borders[1], borders[2], borders[3]
+
+ @property
+ def opacity(self) -> float:
+ """Get or set this windows opacity. 0.0 is fully transparent and 1.0 is fully opaque.
+
+ Will error if you try to set this and opacity isn't supported.
+ """
+ return float(lib.SDL_GetWindowOpacity(self.p))
+
+ @opacity.setter
+ def opacity(self, value: float) -> None:
+ _check(lib.SDL_SetWindowOpacity(self.p, value))
+
+ @property
+ @deprecated("This attribute as been split into mouse_grab and keyboard_grab")
+ def grab(self) -> bool:
+ """Get or set this windows input grab mode.
+
+ .. deprecated:: 19.0
+ This attribute as been split into :any:`mouse_grab` and :any:`keyboard_grab`.
+ """
+ return self.mouse_grab
+
+ @grab.setter
+ def grab(self, value: bool) -> None:
+ self.mouse_grab = value
+
+ @property
+ def mouse_grab(self) -> bool:
+ """Get or set this windows mouse input grab mode.
+
+ .. versionadded:: 19.0
+ """
+ return bool(lib.SDL_GetWindowMouseGrab(self.p))
+
+ @mouse_grab.setter
+ def mouse_grab(self, value: bool, /) -> None:
+ lib.SDL_SetWindowMouseGrab(self.p, value)
+
+ @property
+ def keyboard_grab(self) -> bool:
+ """Get or set this windows keyboard input grab mode.
+
+ https://wiki.libsdl.org/SDL3/SDL_SetWindowKeyboardGrab
+
+ .. versionadded:: 19.0
+ """
+ return bool(lib.SDL_GetWindowKeyboardGrab(self.p))
+
+ @keyboard_grab.setter
+ def keyboard_grab(self, value: bool, /) -> None:
+ lib.SDL_SetWindowKeyboardGrab(self.p, value)
+
+ @property
+ def mouse_rect(self) -> tuple[int, int, int, int] | None:
+ """Get or set the mouse confinement area when the window has mouse focus.
+
+ Setting this will not automatically grab the cursor.
+
+ .. versionadded:: 13.5
+ """
+ rect = lib.SDL_GetWindowMouseRect(self.p)
+ return (rect.x, rect.y, rect.w, rect.h) if rect else None
+
+ @mouse_rect.setter
+ def mouse_rect(self, rect: tuple[int, int, int, int] | None) -> None:
+ _check(lib.SDL_SetWindowMouseRect(self.p, (rect,) if rect else ffi.NULL))
+
+ @_required_version((2, 0, 16))
+ def flash(self, operation: FlashOperation = FlashOperation.UNTIL_FOCUSED) -> None:
+ """Get the users attention."""
+ _check(lib.SDL_FlashWindow(self.p, operation))
+
+ def raise_window(self) -> None:
+ """Raise the window and set input focus."""
+ lib.SDL_RaiseWindow(self.p)
+
+ def restore(self) -> None:
+ """Restore a minimized or maximized window to its original size and position."""
+ lib.SDL_RestoreWindow(self.p)
+
+ def maximize(self) -> None:
+ """Make the window as big as possible."""
+ lib.SDL_MaximizeWindow(self.p)
+
+ def minimize(self) -> None:
+ """Minimize the window to an iconic state."""
+ lib.SDL_MinimizeWindow(self.p)
+
+ def show(self) -> None:
+ """Show this window."""
+ lib.SDL_ShowWindow(self.p)
+
+ def hide(self) -> None:
+ """Hide this window."""
+ lib.SDL_HideWindow(self.p)
+
+ @property
+ def relative_mouse_mode(self) -> bool:
+ """Enable or disable relative mouse mode which will lock and hide the mouse and only report mouse motion.
+
+ .. seealso::
+ :any:`tcod.sdl.mouse.capture`
+ https://wiki.libsdl.org/SDL_SetWindowRelativeMouseMode
+ """
+ return bool(lib.SDL_GetWindowRelativeMouseMode(self.p))
+
+ @relative_mouse_mode.setter
+ def relative_mouse_mode(self, enable: bool, /) -> None:
+ _check(lib.SDL_SetWindowRelativeMouseMode(self.p, enable))
+
+ def start_text_input(
+ self,
+ *,
+ type: TextInputType = TextInputType.TEXT, # noqa: A002
+ capitalization: Capitalization | None = None,
+ autocorrect: bool = True,
+ multiline: bool | None = None,
+ android_type: int | None = None,
+ ) -> None:
+ """Start receiving text input events supporting Unicode. This may open an on-screen keyboard.
+
+ This method is meant to be paired with :any:`set_text_input_area`.
+
+ Args:
+ type: Type of text being inputted, see :any:`TextInputType`
+ capitalization: Capitalization hint, default is based on `type` given, see :any:`Capitalization`.
+ autocorrect: Enable auto completion and auto correction.
+ multiline: Allow multiple lines of text.
+ android_type: Input type for Android, see SDL docs.
+
+ Example::
+
+ context: tcod.context.Context # Assuming tcod context is used
+
+ if context.sdl_window:
+ context.sdl_window.start_text_input()
+
+ ... # Handle Unicode input using TextInput events
+
+ context.sdl_window.stop_text_input() # Close on-screen keyboard when done
+
+ .. seealso::
+ :any:`stop_text_input`
+ :any:`set_text_input_area`
+ :any:`TextInput`
+ https://wiki.libsdl.org/SDL3/SDL_StartTextInputWithProperties
+
+ .. versionadded:: 19.1
+ """
+ props = Properties()
+ props[("SDL_PROP_TEXTINPUT_TYPE_NUMBER", int)] = int(type)
+ if capitalization is not None:
+ props[("SDL_PROP_TEXTINPUT_CAPITALIZATION_NUMBER", int)] = int(capitalization)
+ props[("SDL_PROP_TEXTINPUT_AUTOCORRECT_BOOLEAN", bool)] = autocorrect
+ if multiline is not None:
+ props[("SDL_PROP_TEXTINPUT_MULTILINE_BOOLEAN", bool)] = multiline
+ if android_type is not None:
+ props[("SDL_PROP_TEXTINPUT_ANDROID_INPUTTYPE_NUMBER", int)] = int(android_type)
+ _check(lib.SDL_StartTextInputWithProperties(self.p, props.p))
+
+ def set_text_input_area(self, rect: tuple[int, int, int, int], cursor: int) -> None:
+ """Assign the area used for entering Unicode text input.
+
+ Args:
+ rect: `(x, y, width, height)` rectangle used for text input
+ cursor: Cursor X position, relative to `rect[0]`
+
+ .. seealso::
+ :any:`start_text_input`
+ https://wiki.libsdl.org/SDL3/SDL_SetTextInputArea
+
+ .. versionadded:: 19.1
+ """
+ _check(lib.SDL_SetTextInputArea(self.p, (rect,), cursor))
+
+ def stop_text_input(self) -> None:
+ """Stop receiving text events for this window and close relevant on-screen keyboards.
+
+ .. seealso::
+ :any:`start_text_input`
+
+ .. versionadded:: 19.1
+ """
+ _check(lib.SDL_StopTextInput(self.p))
+
+
+def new_window( # noqa: PLR0913
+ width: int,
+ height: int,
+ *,
+ x: int | None = None,
+ y: int | None = None,
+ title: str | None = None,
+ flags: int = 0,
+) -> Window:
+ """Initialize and return a new SDL Window.
+
+ Args:
+ width: The requested pixel width of the window.
+ height: The requested pixel height of the window.
+ x: The left-most position of the window.
+ y: The top-most position of the window.
+ title: The title text of the new window. If no option is given then `sys.arg[0]` will be used as the title.
+ flags: The SDL flags to use for this window, such as `tcod.sdl.video.WindowFlags.RESIZABLE`.
+ See :any:`WindowFlags` for more options.
+
+ Example::
+
+ import tcod.sdl.video
+ # Create a new resizable window with a custom title.
+ window = tcod.sdl.video.new_window(640, 480, title="Title bar text", flags=tcod.sdl.video.WindowFlags.RESIZABLE)
+
+ .. seealso::
+ :func:`tcod.sdl.render.new_renderer`
+ """
+ if title is None:
+ title = sys.argv[0]
+ window_props = Properties()
+ window_props[(tcod.sdl.constants.SDL_PROP_WINDOW_CREATE_FLAGS_NUMBER, int)] = flags
+ window_props[(tcod.sdl.constants.SDL_PROP_WINDOW_CREATE_TITLE_STRING, str)] = title
+ if x is not None:
+ window_props[(tcod.sdl.constants.SDL_PROP_WINDOW_CREATE_X_NUMBER, int)] = x
+ if y is not None:
+ window_props[(tcod.sdl.constants.SDL_PROP_WINDOW_CREATE_Y_NUMBER, int)] = y
+ window_props[(tcod.sdl.constants.SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, int)] = width
+ window_props[(tcod.sdl.constants.SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, int)] = height
+ window_p = ffi.gc(lib.SDL_CreateWindowWithProperties(window_props.p), lib.SDL_DestroyWindow)
+ return Window(_check_p(window_p))
+
+
+def get_grabbed_window() -> Window | None:
+ """Return the window which has input grab enabled, if any."""
+ sdl_window_p = lib.SDL_GetGrabbedWindow()
+ return Window(sdl_window_p) if sdl_window_p else None
+
+
+def screen_saver_allowed(allow: bool | None = None) -> bool: # noqa: FBT001
+ """Allow or prevent a screen saver from being displayed and return the current allowed status.
+
+ If `allow` is `None` then only the current state is returned.
+ Otherwise it will change the state before checking it.
+
+ SDL typically disables the screensaver by default.
+ If you're unsure, then don't touch this.
+
+ Example::
+
+ import tcod.sdl.video
+
+ print(f"Screen saver was allowed: {tcod.sdl.video.screen_saver_allowed()}")
+ # Allow the screen saver.
+ # Might be okay for some turn-based games which don't use a gamepad.
+ tcod.sdl.video.screen_saver_allowed(True)
+ """
+ if allow is None:
+ pass
+ elif allow:
+ lib.SDL_EnableScreenSaver()
+ else:
+ lib.SDL_DisableScreenSaver()
+ return bool(lib.SDL_ScreenSaverEnabled())
diff --git a/tcod/tcod.c b/tcod/tcod.c
index 98194872..91f7cc6f 100644
--- a/tcod/tcod.c
+++ b/tcod/tcod.c
@@ -1,22 +1,32 @@
#include "tcod.h"
-#include "../libtcod/src/libtcod/bresenham.h"
+#include
+#include "../libtcod/src/libtcod/bresenham.h"
+#include "../libtcod/src/libtcod/console_drawing.h"
+#include "../libtcod/src/libtcod/console_printing.h"
+#include "../libtcod/src/libtcod/error.h"
+#include "../libtcod/src/libtcod/utility.h"
/**
- * Write a Bresenham line to the `x_out` and `y_out` arrays.
- *
- * `x_out` and `y_out` must be large enough to contain the entire line that
- * this will output. Typically `max(abs(x1 - x2), abs(y1 - y2)) + 1`.
- *
- * This function includes both endpoints.
+ Write a Bresenham line to the `out[n * 2]` array.
+
+ The result includes both endpoints.
+
+ The length of the array is returned, when `out` is given `n` must be equal
+ or greater then the length.
*/
-int LineWhere(int x1, int y1, int x2, int y2, int *x_out, int *y_out) {
+int bresenham(int x1, int y1, int x2, int y2, int n, int* __restrict out) {
+ // Bresenham length is Chebyshev distance.
+ int length = TCOD_MAX(abs(x1 - x2), abs(y1 - y2)) + 1;
+ if (!out) { return length; }
+ if (n < length) { return TCOD_set_errorv("Bresenham output length mismatched."); }
TCOD_bresenham_data_t bresenham;
- *x_out = x1;
- *y_out = y1;
- if (x1 == x2 && y1 == y2) { return 0; }
+ out[0] = x1;
+ out[1] = y1;
+ out += 2;
+ if (x1 == x2 && y1 == y2) { return length; }
TCOD_line_init_mt(x1, y1, x2, y2, &bresenham);
- while (!TCOD_line_step_mt(++x_out, ++y_out, &bresenham)) {}
- return 0;
+ while (!TCOD_line_step_mt(&out[0], &out[1], &bresenham)) { out += 2; }
+ return length;
}
diff --git a/tcod/tcod.h b/tcod/tcod.h
index f64cc0a9..3648a340 100644
--- a/tcod/tcod.h
+++ b/tcod/tcod.h
@@ -2,6 +2,14 @@
#ifndef TCOD_TCOD_H_
#define TCOD_TCOD_H_
-int LineWhere(int x1, int y1, int x2, int y2, int *x_out, int *y_out);
+#include "../libtcod/src/libtcod/color.h"
+#include "../libtcod/src/libtcod/console.h"
-#endif /* TCOD_TCOD_H_ */
\ No newline at end of file
+#ifdef __cplusplus
+extern "C" {
+#endif
+int bresenham(int x1, int y1, int x2, int y2, int n, int* __restrict out);
+#ifdef __cplusplus
+} // extern "C"
+#endif
+#endif /* TCOD_TCOD_H_ */
diff --git a/tcod/tcod.py b/tcod/tcod.py
index b8fd80c8..39240194 100644
--- a/tcod/tcod.py
+++ b/tcod/tcod.py
@@ -1,145 +1,90 @@
-"""This module focuses on improvements to the Python libtcod API.
-"""
-from __future__ import absolute_import as _
-
-import sys as _sys
-
-from typing import Any, AnyStr
-import warnings
-
-from tcod.libtcod import lib, ffi, BKGND_DEFAULT, BKGND_SET
+"""The fast Python port of libtcod.
+This module can be used as a drop in replacement for the official libtcodpy module.
-def _unpack_char_p(char_p):
- if char_p == ffi.NULL:
- return ''
- return ffi.string(char_p).decode()
+Bring any issues or feature requests to GitHub: https://github.com/libtcod/python-tcod
+Read the documentation online: https://python-tcod.readthedocs.io/en/latest/
+"""
-def _int(int_or_str: Any) -> int:
- 'return an integer where a single character string may be expected'
- if isinstance(int_or_str, str):
- return ord(int_or_str)
- if isinstance(int_or_str, bytes):
- return int_or_str[0]
- return int(int_or_str) # check for __count__
-
-def _bytes(string: AnyStr) -> bytes:
- if isinstance(string, str):
- return string.encode('utf-8')
- return string
+from __future__ import annotations
-def _unicode(string: AnyStr, stacklevel: int=2) -> str:
- if isinstance(string, bytes):
+import warnings
+from typing import Any
+
+from tcod import (
+ bsp,
+ color,
+ console,
+ constants,
+ context,
+ event,
+ image,
+ libtcodpy,
+ los,
+ map, # noqa: A004
+ noise,
+ path,
+ random,
+ tileset,
+)
+from tcod.version import __version__
+
+
+def __getattr__(name: str, stacklevel: int = 1) -> Any: # noqa: ANN401
+ """Mark access to color constants as deprecated."""
+ if name == "Console":
warnings.warn(
- ("Passing byte strings as parameters to Unicode functions is "
- "deprecated."),
- DeprecationWarning,
+ "tcod.Console is deprecated.\nReplace 'tcod.Console' with 'tcod.console.Console'",
+ FutureWarning,
stacklevel=stacklevel + 1,
- )
- return string.decode('latin-1')
- return string
-
-
-def _fmt_bytes(string: AnyStr) -> bytes:
- return _bytes(string).replace(b'%', b'%%')
-
-def _fmt_unicode(string: AnyStr) -> str:
- return _unicode(string, stacklevel=3).encode('utf-8').replace(b'%', b'%%')
-
-
-class _PropagateException():
- """ context manager designed to propagate exceptions outside of a cffi
- callback context. normally cffi suppresses the exception
-
- when propagate is called this class will hold onto the error until the
- control flow leaves the context, then the error will be raised
-
- with _PropagateException as propagate:
- # give propagate as onerror parameter for ffi.def_extern
- """
-
- def __init__(self):
- self.exc_info = None # (exception, exc_value, traceback)
-
- def propagate(self, *exc_info):
- """ set an exception to be raised once this context exits
-
- if multiple errors are caught, only keep the first exception raised
- """
- if not self.exc_info:
- self.exc_info = exc_info
-
- def __enter__(self):
- """ once in context, only the propagate call is needed to use this
- class effectively
- """
- return self.propagate
-
- def __exit__(self, type, value, traceback):
- """ if we're holding on to an exception, raise it now
-
- prefers our held exception over any current raising error
-
- self.exc_info is reset now in case of nested manager shenanigans
- """
- if self.exc_info:
- type, value, traceback = self.exc_info
- self.exc_info = None
- if type:
- # Python 2/3 compatible throw
- exception = type(value)
- exception.__traceback__ = traceback
- raise exception
-
-
-class _CDataWrapper(object):
-
- def __init__(self, *args, **kargs):
- self.cdata = self._get_cdata_from_args(*args, **kargs)
- if self.cdata == None:
- self.cdata = ffi.NULL
- super(_CDataWrapper, self).__init__()
-
- @staticmethod
- def _get_cdata_from_args(*args, **kargs):
- if len(args) == 1 and isinstance(args[0], ffi.CData) and not kargs:
- return args[0]
- else:
- return None
-
-
- def __hash__(self):
- return hash(self.cdata)
-
- def __eq__(self, other):
- try:
- return self.cdata == other.cdata
- except AttributeError:
- return NotImplemented
-
- def __getattr__(self, attr):
- if 'cdata' in self.__dict__:
- return getattr(self.__dict__['cdata'], attr)
- raise AttributeError(attr)
-
- def __setattr__(self, attr, value):
- if hasattr(self, 'cdata') and hasattr(self.cdata, attr):
- setattr(self.cdata, attr, value)
- else:
- super(_CDataWrapper, self).__setattr__(attr, value)
-
-
-def _console(console):
- """Return a cffi console."""
- try:
- return console.console_c
- except AttributeError:
+ )
+ return console.Console
+ value: Any = getattr(constants, name, None)
+ if isinstance(value, color.Color):
warnings.warn(
- ("Falsy console parameters are deprecated, "
- "always use the root console instance returned by "
- "console_init_root."),
- DeprecationWarning,
- stacklevel=3,
- )
- return ffi.NULL
+ f"Color constants will be removed from future releases.\nReplace 'tcod.{name}' with '{tuple(value)}'",
+ FutureWarning,
+ stacklevel=stacklevel + 1,
+ )
+ return value
+ if value is not None:
+ warnings.warn(
+ "Soon the 'tcod' module will no longer hold constants directly."
+ "\nAdd 'from tcod import libtcodpy' if you haven't already."
+ f"\nReplace 'tcod.{name}' with 'libtcodpy.{name}'",
+ FutureWarning,
+ stacklevel=stacklevel + 1,
+ )
+ return value
+ value = getattr(libtcodpy, name, None)
+ if value is not None:
+ warnings.warn(
+ "Soon the 'tcod' module will no longer be an implicit reference to 'libtcodpy'."
+ "\nAdd 'from tcod import libtcodpy' if you haven't already."
+ f"\nReplace 'tcod.{name}' with 'libtcodpy.{name}'",
+ FutureWarning,
+ stacklevel=stacklevel + 1,
+ )
+ return value
+
+ msg = f"module {__name__!r} has no attribute {name!r}"
+ raise AttributeError(msg)
+
+
+__all__ = [
+ "__version__",
+ "bsp",
+ "color",
+ "console",
+ "context",
+ "event",
+ "image",
+ "los",
+ "map",
+ "noise",
+ "path",
+ "random",
+ "tileset",
+ "tileset",
+]
diff --git a/tcod/tdl.c b/tcod/tdl.c
deleted file mode 100644
index 5745ca36..00000000
--- a/tcod/tdl.c
+++ /dev/null
@@ -1,238 +0,0 @@
-/* extra functions provided for the python-tdl library */
-#include "tdl.h"
-
-#include "../libtcod/src/libtcod/wrappers.h"
-
-void SDL_main(void){};
-
-TCOD_value_t TDL_list_get_union(TCOD_list_t l,int idx){
- TCOD_value_t item;
- item.custom = TCOD_list_get(l, idx);
- return item;
-}
-
-bool TDL_list_get_bool(TCOD_list_t l,int idx){
- return TDL_list_get_union(l, idx).b;
-}
-
-char TDL_list_get_char(TCOD_list_t l,int idx){
- return TDL_list_get_union(l, idx).c;
-}
-
-int TDL_list_get_int(TCOD_list_t l,int idx){
- return TDL_list_get_union(l, idx).i;
-}
-
-float TDL_list_get_float(TCOD_list_t l,int idx){
- return TDL_list_get_union(l, idx).f;
-}
-
-char* TDL_list_get_string(TCOD_list_t l,int idx){
- return TDL_list_get_union(l, idx).s;
-}
-
-TCOD_color_t TDL_list_get_color(TCOD_list_t l,int idx){
- return TDL_list_get_union(l, idx).col;
-}
-
-TCOD_dice_t TDL_list_get_dice(TCOD_list_t l,int idx){
- return TDL_list_get_union(l, idx).dice;
-}
-
-
-/* get a TCOD color type from a 0xRRGGBB formatted integer */
-TCOD_color_t TDL_color_from_int(int color){
- TCOD_color_t tcod_color={(color >> 16) & 0xff,
- (color >> 8) & 0xff,
- color & 0xff};
- return tcod_color;
-}
-
-int TDL_color_to_int(TCOD_color_t *color){
- return (color->r << 16) | (color->g << 8) | color->b;
-}
-
-int* TDL_color_int_to_array(int color){
- static int array[3];
- array[0] = (color >> 16) & 0xff;
- array[1] = (color >> 8) & 0xff;
- array[2] = color & 0xff;
- return array;
-}
-
-int TDL_color_RGB(int r, int g, int b){
- return ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff);
-}
-
-int TDL_color_HSV(float h, float s, float v){
- TCOD_color_t tcod_color=TCOD_color_HSV(h, s, v);
- return TDL_color_to_int(&tcod_color);
-}
-
-bool TDL_color_equals(int c1, int c2){
- return (c1 == c2);
-}
-
-int TDL_color_add(int c1, int c2){
- TCOD_color_t tc1=TDL_color_from_int(c1);
- TCOD_color_t tc2=TDL_color_from_int(c2);
- tc1=TCOD_color_add(tc1, tc2);
- return TDL_color_to_int(&tc1);
-}
-
-int TDL_color_subtract(int c1, int c2){
- TCOD_color_t tc1=TDL_color_from_int(c1);
- TCOD_color_t tc2=TDL_color_from_int(c2);
- tc1=TCOD_color_subtract(tc1, tc2);
- return TDL_color_to_int(&tc1);
-}
-
-int TDL_color_multiply(int c1, int c2){
- TCOD_color_t tc1=TDL_color_from_int(c1);
- TCOD_color_t tc2=TDL_color_from_int(c2);
- tc1=TCOD_color_multiply(tc1, tc2);
- return TDL_color_to_int(&tc1);
-}
-
-int TDL_color_multiply_scalar(int c, float value){
- TCOD_color_t tc=TDL_color_from_int(c);
- tc=TCOD_color_multiply_scalar(tc, value);
- return TDL_color_to_int(&tc);
-}
-
-int TDL_color_lerp(int c1, int c2, float coef){
- TCOD_color_t tc1=TDL_color_from_int(c1);
- TCOD_color_t tc2=TDL_color_from_int(c2);
- tc1=TCOD_color_lerp(tc1, tc2, coef);
- return TDL_color_to_int(&tc1);
-}
-
-float TDL_color_get_hue(int color){
- TCOD_color_t tcod_color=TDL_color_from_int(color);
- return TCOD_color_get_hue(tcod_color);
-}
-float TDL_color_get_saturation(int color){
- TCOD_color_t tcod_color=TDL_color_from_int(color);
- return TCOD_color_get_saturation(tcod_color);
-}
-float TDL_color_get_value(int color){
- TCOD_color_t tcod_color=TDL_color_from_int(color);
- return TCOD_color_get_value(tcod_color);
-}
-int TDL_color_set_hue(int color, float h){
- TCOD_color_t tcod_color=TDL_color_from_int(color);
- TCOD_color_set_hue(&tcod_color, h);
- return TDL_color_to_int(&tcod_color);
-}
-int TDL_color_set_saturation(int color, float h){
- TCOD_color_t tcod_color=TDL_color_from_int(color);
- TCOD_color_set_saturation(&tcod_color, h);
- return TDL_color_to_int(&tcod_color);
-}
-int TDL_color_set_value(int color, float h){
- TCOD_color_t tcod_color=TDL_color_from_int(color);
- TCOD_color_set_value(&tcod_color, h);
- return TDL_color_to_int(&tcod_color);
-}
-int TDL_color_shift_hue(int color, float hshift){
- TCOD_color_t tcod_color=TDL_color_from_int(color);
- TCOD_color_shift_hue(&tcod_color, hshift);
- return TDL_color_to_int(&tcod_color);
-}
-int TDL_color_scale_HSV(int color, float scoef, float vcoef){
- TCOD_color_t tcod_color=TDL_color_from_int(color);
- TCOD_color_scale_HSV(&tcod_color, scoef, vcoef);
- return TDL_color_to_int(&tcod_color);
-}
-
-
-#define TRANSPARENT_BIT 1
-#define WALKABLE_BIT 2
-#define FOV_BIT 4
-
-/* set map transparent and walkable flags from a buffer */
-void TDL_map_data_from_buffer(TCOD_map_t map, uint8_t *buffer) {
- int width=TCOD_map_get_width(map);
- int height=TCOD_map_get_height(map);
- int x;
- int y;
- for (y = 0; y < height; y++){
- for (x = 0; x < width; x++){
- int i = y * width + x;
- TCOD_map_set_properties(map, x, y,
- (buffer[i] & TRANSPARENT_BIT) != 0,
- (buffer[i] & WALKABLE_BIT) != 0);
- }
- }
-}
-
-/* get fov from tcod map */
-void TDL_map_fov_to_buffer(TCOD_map_t map, uint8_t *buffer, bool cumulative) {
- int width=TCOD_map_get_width(map);
- int height=TCOD_map_get_height(map);
- int x;
- int y;
- for (y = 0; y < height; y++){
- for (x = 0; x < width; x++){
- int i = y * width + x;
- if (!cumulative) {
- buffer[i] &= ~FOV_BIT;
- }
- if (TCOD_map_is_in_fov(map, x, y)) {
- buffer[i] |= FOV_BIT;
- }
- }
- }
-}
-
-/* set functions are called conditionally for ch/fg/bg (-1 is ignored)
- colors are converted to TCOD_color_t types in C and is much faster than in
- Python.
- Also Python indexing is used, negative x/y will index to (width-x, etc.) */
-int TDL_console_put_char_ex(TCOD_console_t console, int x, int y,
- int ch, int fg, int bg, TCOD_bkgnd_flag_t blend) {
- int width=TCOD_console_get_width(console);
- int height=TCOD_console_get_height(console);
- TCOD_color_t color;
-
- if (x < -width || x >= width || y < -height || y >= height) {
- return -1; /* outside of console */
- }
-
- /* normalize x, y */
- if (x < 0) { x += width; };
- if (y < 0) { y += height; };
-
- if (ch != -1) {
- TCOD_console_set_char(console, x, y, ch);
- }
- if (fg != -1) {
- color = TDL_color_from_int(fg);
- TCOD_console_set_char_foreground(console, x, y, color);
- }
- if (bg != -1) {
- color = TDL_color_from_int(bg);
- TCOD_console_set_char_background(console, x, y, color, blend);
- }
- return 0;
-}
-
-int TDL_console_get_bg(TCOD_console_t console, int x, int y) {
- TCOD_color_t tcod_color=TCOD_console_get_char_background(console, x, y);
- return TDL_color_to_int(&tcod_color);
-}
-
-int TDL_console_get_fg(TCOD_console_t console, int x, int y){
- TCOD_color_t tcod_color=TCOD_console_get_char_foreground(console, x, y);
- return TDL_color_to_int(&tcod_color);
-}
-
-void TDL_console_set_bg(TCOD_console_t console, int x, int y, int color,
- TCOD_bkgnd_flag_t flag){
- TCOD_console_set_char_background(console, x, y, TDL_color_from_int(color),
- flag);
-}
-
-void TDL_console_set_fg(TCOD_console_t console, int x, int y, int color){
- TCOD_console_set_char_foreground(console, x, y, TDL_color_from_int(color));
-}
diff --git a/tcod/tdl.h b/tcod/tdl.h
deleted file mode 100644
index 2f752bb1..00000000
--- a/tcod/tdl.h
+++ /dev/null
@@ -1,55 +0,0 @@
-#ifndef PYTHON_TCOD_TDL_H_
-#define PYTHON_TCOD_TDL_H_
-
-#include "../libtcod/src/libtcod/libtcod.h"
-
-/* TDL FUNCTONS ----------------------------------------------------------- */
-
-TCOD_value_t TDL_list_get_union(TCOD_list_t l,int idx);
-bool TDL_list_get_bool(TCOD_list_t l,int idx);
-char TDL_list_get_char(TCOD_list_t l,int idx);
-int TDL_list_get_int(TCOD_list_t l,int idx);
-float TDL_list_get_float(TCOD_list_t l,int idx);
-char* TDL_list_get_string(TCOD_list_t l,int idx);
-TCOD_color_t TDL_list_get_color(TCOD_list_t l,int idx);
-TCOD_dice_t TDL_list_get_dice(TCOD_list_t l,int idx);
-/*bool (*TDL_parser_new_property_func)(const char *propname, TCOD_value_type_t type, TCOD_value_t *value);*/
-
-/* color functions modified to use integers instead of structs */
-TCOD_color_t TDL_color_from_int(int color);
-int TDL_color_to_int(TCOD_color_t *color);
-int* TDL_color_int_to_array(int color);
-int TDL_color_RGB(int r, int g, int b);
-int TDL_color_HSV(float h, float s, float v);
-bool TDL_color_equals(int c1, int c2);
-int TDL_color_add(int c1, int c2);
-int TDL_color_subtract(int c1, int c2);
-int TDL_color_multiply(int c1, int c2);
-int TDL_color_multiply_scalar(int c, float value);
-int TDL_color_lerp(int c1, int c2, float coef);
-float TDL_color_get_hue(int color);
-float TDL_color_get_saturation(int color);
-float TDL_color_get_value(int color);
-int TDL_color_set_hue(int color, float h);
-int TDL_color_set_saturation(int color, float h);
-int TDL_color_set_value(int color, float h);
-int TDL_color_shift_hue(int color, float hshift);
-int TDL_color_scale_HSV(int color, float scoef, float vcoef);
-
-/* map data functions using a bitmap of:
- * 1 = is_transparant
- * 2 = is_walkable
- * 4 = in_fov
- */
-void TDL_map_data_from_buffer(TCOD_map_t map, uint8_t *buffer);
-void TDL_map_fov_to_buffer(TCOD_map_t map, uint8_t *buffer, bool cumulative);
-
-int TDL_console_put_char_ex(TCOD_console_t console, int x, int y,
- int ch, int fg, int bg, TCOD_bkgnd_flag_t flag);
-int TDL_console_get_bg(TCOD_console_t console, int x, int y);
-int TDL_console_get_fg(TCOD_console_t console, int x, int y);
-void TDL_console_set_bg(TCOD_console_t console, int x, int y, int color,
- TCOD_bkgnd_flag_t flag);
-void TDL_console_set_fg(TCOD_console_t console, int x, int y, int color);
-
-#endif /* PYTHON_TCOD_TDL_H_ */
diff --git a/tcod/tileset.py b/tcod/tileset.py
new file mode 100644
index 00000000..58ddf058
--- /dev/null
+++ b/tcod/tileset.py
@@ -0,0 +1,1084 @@
+"""Tileset and font related functions.
+
+If you want to load a tileset from a common tileset image then you only need :any:`tcod.tileset.load_tilesheet`.
+
+Tilesets can be loaded as a whole from tile-sheets or True-Type fonts,
+or they can be put together from multiple tile images by loading them separately using :any:`Tileset.__setitem__`.
+
+A major restriction with libtcod is that all tiles must be the same size and tiles can't overlap when rendered.
+For sprite-based rendering it can be useful to use `an alternative library for graphics rendering
+`_ while continuing to use
+python-tcod's pathfinding and field-of-view algorithms.
+"""
+
+from __future__ import annotations
+
+import copy
+import itertools
+from collections.abc import Iterator, Mapping, MutableMapping
+from pathlib import Path
+from typing import TYPE_CHECKING, Any, SupportsInt, overload
+
+import numpy as np
+from typing_extensions import Self, deprecated
+
+from tcod._internal import _check, _check_p, _console, _path_encode, _raise_tcod_error
+from tcod.cffi import ffi, lib
+
+if TYPE_CHECKING:
+ from collections.abc import Iterable
+ from os import PathLike
+
+ from numpy.typing import ArrayLike, NDArray
+
+ import tcod.console
+
+
+class Tileset(MutableMapping[int, "NDArray[np.uint8]"]):
+ """A collection of graphical tiles.
+
+ .. versionchanged:: 20.1
+ Is now a :class:`collections.abc.MutableMapping` type.
+ """
+
+ def __init__(self, tile_width: int, tile_height: int) -> None:
+ """Initialize the tileset."""
+ self._tileset_p = _check_p(
+ ffi.gc(
+ lib.TCOD_tileset_new(tile_width, tile_height),
+ lib.TCOD_tileset_delete,
+ )
+ )
+
+ @classmethod
+ def _claim(cls, cdata: Any) -> Tileset: # noqa: ANN401
+ """Return a new Tileset that owns the provided TCOD_Tileset* object."""
+ self = object.__new__(cls)
+ if cdata == ffi.NULL:
+ msg = "Tileset initialized with nullptr."
+ raise RuntimeError(msg)
+ self._tileset_p = ffi.gc(cdata, lib.TCOD_tileset_delete)
+ return self
+
+ @classmethod
+ def _from_ref(cls, tileset_p: Any) -> Tileset: # noqa: ANN401
+ self = object.__new__(cls)
+ self._tileset_p = tileset_p
+ return self
+
+ @property
+ def tile_width(self) -> int:
+ """Width of the tile in pixels."""
+ return int(lib.TCOD_tileset_get_tile_width_(self._tileset_p))
+
+ @property
+ def tile_height(self) -> int:
+ """Height of the tile in pixels."""
+ return int(lib.TCOD_tileset_get_tile_height_(self._tileset_p))
+
+ @property
+ def tile_shape(self) -> tuple[int, int]:
+ """Shape (height, width) of the tile in pixels."""
+ return self.tile_height, self.tile_width
+
+ def __copy__(self) -> Self:
+ """Return a clone of this tileset.
+
+ This is not an exact copy. :any:`remap` will not work on the clone.
+
+ .. versionadded:: 20.1
+ """
+ clone = self.__class__(self.tile_width, self.tile_height)
+ for codepoint, tile in self.items():
+ clone[codepoint] = tile
+ return clone
+
+ @staticmethod
+ def _iter_items(
+ tiles: Iterable[tuple[int, ArrayLike | NDArray[np.uint8]]] | Mapping[int, ArrayLike | NDArray[np.uint8]], /
+ ) -> Iterable[tuple[int, ArrayLike | NDArray[np.uint8]]]:
+ """Convert a potential mapping to an iterator."""
+ if isinstance(tiles, Mapping):
+ return tiles.items() # pyright: ignore[reportReturnType]
+ return tiles
+
+ def __iadd__(
+ self,
+ other: Iterable[tuple[int, ArrayLike | NDArray[np.uint8]]] | Mapping[int, ArrayLike | NDArray[np.uint8]],
+ /,
+ ) -> Self:
+ """Add tiles to this tileset inplace, prefers replacing tiles.
+
+ .. versionadded:: 20.1
+ """
+ for codepoint, tile in self._iter_items(other):
+ self[codepoint] = tile
+ return self
+
+ def __add__(
+ self,
+ other: Iterable[tuple[int, ArrayLike | NDArray[np.uint8]]] | Mapping[int, ArrayLike | NDArray[np.uint8]],
+ /,
+ ) -> Self:
+ """Combine tiles with this tileset, prefers replacing tiles.
+
+ .. versionadded:: 20.1
+ """
+ clone = copy.copy(self)
+ clone += other
+ return clone
+
+ def __ior__(
+ self,
+ other: Iterable[tuple[int, ArrayLike | NDArray[np.uint8]]] | Mapping[int, ArrayLike | NDArray[np.uint8]],
+ /,
+ ) -> Self:
+ """Add tiles to this tileset inplace, keeps existing tiles instead of replacing them.
+
+ .. versionadded:: 20.1
+ """
+ for codepoint, tile in self._iter_items(other):
+ if codepoint not in self:
+ self[codepoint] = tile
+ return self
+
+ def __or__(
+ self,
+ other: Iterable[tuple[int, ArrayLike | NDArray[np.uint8]]] | Mapping[int, ArrayLike | NDArray[np.uint8]],
+ /,
+ ) -> Self:
+ """Combine tiles with this tileset, prefers keeping existing tiles instead of replacing them.
+
+ .. versionadded:: 20.1
+ """
+ clone = copy.copy(self)
+ clone |= other
+ return clone
+
+ def __contains__(self, codepoint: object, /) -> bool:
+ """Test if a tileset has a codepoint with ``n in tileset``."""
+ if not isinstance(codepoint, SupportsInt):
+ return False
+ codepoint = int(codepoint)
+ if not 0 <= codepoint < self._tileset_p.character_map_length:
+ return False
+ return bool(self._get_character_map()[int(codepoint)] > 0)
+
+ def __getitem__(self, codepoint: int, /) -> NDArray[np.uint8]:
+ """Return the RGBA tile data for the given codepoint.
+
+ The tile will have a shape of (height, width, rgba) and a dtype of uint8.
+ Note that most grey-scale tilesets will only use the alpha channel with a solid white color channel.
+
+ .. versionadded:: 20.1
+ """
+ if codepoint not in self:
+ raise KeyError(codepoint)
+ tile_p = lib.TCOD_tileset_get_tile(self._tileset_p, codepoint)
+ return np.frombuffer(ffi.buffer(tile_p[0 : self.tile_shape[0] * self.tile_shape[1]]), dtype=np.uint8).reshape(
+ *self.tile_shape, 4, copy=True
+ )
+
+ def __setitem__(self, codepoint: int, tile: ArrayLike | NDArray[np.uint8], /) -> None:
+ """Upload a tile into this array.
+
+ Args:
+ codepoint (int): The Unicode codepoint you are assigning to.
+ If the tile is a sprite rather than a common glyph then consider assigning it to a
+ `Private Use Area `_.
+ tile (Union[ArrayLike, NDArray[np.uint8]]):
+ The pixels to use for this tile in row-major order and must be in the same shape as :any:`tile_shape`.
+ `tile` can be an RGBA array with the shape of ``(height, width, rgba)``, or a grey-scale array with the
+ shape ``(height, width)``.
+ The `tile` array will be converted to a dtype of ``np.uint8``.
+
+ An RGB array as an input is too ambiguous and an alpha channel must be added, for example if an image has a key
+ color than the key color pixels must have their alpha channel set to zero.
+
+ This data may be immediately sent to VRAM, which can be a slow operation.
+
+ Example::
+
+ # Examples use imageio for image loading, see https://imageio.readthedocs.io
+ tileset: tcod.tileset.Tileset # This example assumes you are modifying an existing tileset.
+
+ # Normal usage when a tile already has its own alpha channel.
+ # The loaded tile must be the correct shape for the tileset you assign it to.
+ # The tile is assigned to a private use area and will not conflict with any exiting codepoint.
+ tileset[0x100000] = imageio.imread("rgba_tile.png")
+
+ # Load a greyscale tile.
+ tileset[0x100001] = imageio.imread("greyscale_tile.png", mode="L")
+ # If you are stuck with an RGB array then you can use the red channel as the input: `rgb[:, :, 0]`
+
+ # Loads an RGB sprite without a background.
+ tileset[0x100002] = imageio.imread("rgb_no_background.png", mode="RGBA")
+ # If you're stuck with an RGB array then you can pad the channel axis with an alpha of 255:
+ # rgba = np.pad(rgb, pad_width=((0, 0), (0, 0), (0, 1)), constant_values=255)
+
+ # Loads an RGB sprite with a key color background.
+ KEY_COLOR = np.asarray((255, 0, 255), dtype=np.uint8)
+ sprite_rgb = imageio.imread("rgb_tile.png")
+ # Compare the RGB colors to KEY_COLOR, compress full matches to a 2D mask.
+ sprite_mask = (sprite_rgb != KEY_COLOR).all(axis=2)
+ # Generate the alpha array, with 255 as the foreground and 0 as the background.
+ sprite_alpha = sprite_mask.astype(np.uint8) * 255
+ # Combine the RGB and alpha arrays into an RGBA array.
+ sprite_rgba = np.append(sprite_rgb, sprite_alpha, axis=2)
+ tileset[0x100003] = sprite_rgba
+
+ .. versionadded:: 20.1
+ """
+ tile = np.ascontiguousarray(tile, dtype=np.uint8)
+ if tile.shape == self.tile_shape:
+ full_tile: NDArray[np.uint8] = np.empty((*self.tile_shape, 4), dtype=np.uint8)
+ full_tile[:, :, :3] = 255
+ full_tile[:, :, 3] = tile
+ return self.set_tile(codepoint, full_tile)
+ required = (*self.tile_shape, 4)
+ if tile.shape != required:
+ note = ""
+ if len(tile.shape) == 3 and tile.shape[2] == 3: # noqa: PLR2004
+ note = (
+ "\nNote: An RGB array is too ambiguous,"
+ " an alpha channel must be added to this array to divide the background/foreground areas."
+ )
+ msg = f"Tile shape must be {required} or {self.tile_shape}, got {tile.shape}.{note}"
+ raise ValueError(msg)
+ lib.TCOD_tileset_set_tile_(
+ self._tileset_p,
+ codepoint,
+ ffi.from_buffer("struct TCOD_ColorRGBA*", tile),
+ )
+ return None
+
+ def _get_character_map(self) -> NDArray[np.intc]:
+ """Return the internal character mapping as an array.
+
+ This reference will break if the tileset is modified.
+ """
+ return np.frombuffer(
+ ffi.buffer(self._tileset_p.character_map[0 : self._tileset_p.character_map_length]), dtype=np.intc
+ )
+
+ def __delitem__(self, codepoint: int, /) -> None:
+ """Unmap a `codepoint` from this tileset.
+
+ Tilesets are optimized for set-and-forget, deleting a tile may not free up memory.
+
+ .. versionadded:: 20.1
+ """
+ if codepoint not in self:
+ raise KeyError(codepoint)
+ self._get_character_map()[codepoint] = 0
+
+ def __len__(self) -> int:
+ """Return the total count of codepoints assigned in this tileset.
+
+ .. versionadded:: 20.1
+ """
+ return int((self._get_character_map() > 0).sum())
+
+ def __iter__(self) -> Iterator[int]:
+ """Iterate over the assigned codepoints of this tileset.
+
+ .. versionadded:: 20.1
+ """
+ # tolist makes a copy, otherwise the reference to character_map can dangle during iteration
+ for i, v in enumerate(self._get_character_map().tolist()):
+ if v:
+ yield i
+
+ def get_tile(self, codepoint: int) -> NDArray[np.uint8]:
+ """Return a copy of a tile for the given codepoint.
+
+ If the tile does not exist then a blank zero array will be returned.
+
+ The tile will have a shape of (height, width, rgba) and a dtype of
+ uint8. Note that most grey-scale tiles will only use the alpha
+ channel and will usually have a solid white color channel.
+ """
+ try:
+ return self[codepoint]
+ except KeyError:
+ return np.zeros((*self.tile_shape, 4), dtype=np.uint8)
+
+ @deprecated("Assign to a tile using 'tileset[codepoint] = tile' instead.")
+ def set_tile(self, codepoint: int, tile: ArrayLike | NDArray[np.uint8]) -> None:
+ """Upload a tile into this array.
+
+ .. deprecated:: 20.1
+ Was replaced with :any:`Tileset.__setitem__`.
+ Use ``tileset[codepoint] = tile`` syntax instead of this method.
+ """
+ self[codepoint] = tile
+
+ def render(self, console: tcod.console.Console) -> NDArray[np.uint8]:
+ """Render an RGBA array, using console with this tileset.
+
+ `console` is the Console object to render, this can not be the root console.
+
+ The output array will be a np.uint8 array with the shape of:
+ ``(con_height * tile_height, con_width * tile_width, 4)``.
+
+ .. versionadded:: 11.9
+ """
+ if not console:
+ msg = "'console' must not be the root console."
+ raise ValueError(msg)
+ width = console.width * self.tile_width
+ height = console.height * self.tile_height
+ out: NDArray[np.uint8] = np.empty((height, width, 4), np.uint8)
+ out[:] = 9
+ surface_p = ffi.gc(
+ lib.SDL_CreateSurfaceFrom(
+ width,
+ height,
+ lib.SDL_PIXELFORMAT_RGBA32,
+ ffi.from_buffer("void*", out),
+ out.strides[0],
+ ),
+ lib.SDL_DestroySurface,
+ )
+ with surface_p, ffi.new("SDL_Surface**", surface_p) as surface_p_p:
+ _check(
+ lib.TCOD_tileset_render_to_surface(
+ self._tileset_p,
+ _console(console),
+ ffi.NULL,
+ surface_p_p,
+ )
+ )
+ return out
+
+ def remap(self, codepoint: int, x: int, y: int = 0) -> None:
+ """Reassign a codepoint to a character in this tileset.
+
+ `codepoint` is the Unicode codepoint to assign.
+
+ `x` and `y` is the position of the tilesheet to assign to `codepoint`.
+ This is the tile position itself, not the pixel position of the tile.
+ Large values of `x` will wrap to the next row, so using `x` by itself
+ is equivalent to `Tile Index` in the :any:`charmap-reference`.
+
+ This is typically used on tilesets loaded with :any:`load_tilesheet`.
+ Other methods of Tileset creation will not have reliable tile indexes.
+
+ .. versionadded:: 11.12
+ """
+ tile_i = x + y * self._tileset_p.virtual_columns
+ if not (0 <= tile_i < self._tileset_p.tiles_count):
+ msg = (
+ f"Tile {tile_i} is non-existent and can't be assigned."
+ f" (Tileset has {self._tileset_p.tiles_count} tiles.)"
+ )
+ raise IndexError(msg)
+ _check(
+ lib.TCOD_tileset_assign_tile(
+ self._tileset_p,
+ tile_i,
+ codepoint,
+ )
+ )
+
+
+@deprecated("Using the default tileset is deprecated.")
+def get_default() -> Tileset:
+ """Return a reference to the default Tileset.
+
+ .. versionadded:: 11.10
+
+ .. deprecated:: 11.13
+ The default tileset is deprecated.
+ With contexts this is no longer needed.
+ """
+ return Tileset._claim(lib.TCOD_get_default_tileset())
+
+
+@deprecated("Using the default tileset is deprecated.")
+def set_default(tileset: Tileset) -> None:
+ """Set the default tileset.
+
+ The display will use this new tileset immediately.
+
+ .. versionadded:: 11.10
+
+ .. deprecated:: 11.13
+ The default tileset is deprecated.
+ With contexts this is no longer needed.
+ """
+ lib.TCOD_set_default_tileset(tileset._tileset_p)
+
+
+def load_truetype_font(path: str | PathLike[str], tile_width: int, tile_height: int) -> Tileset:
+ """Return a new Tileset from a `.ttf` or `.otf` file.
+
+ Same as :any:`set_truetype_font`, but returns a :any:`Tileset` instead.
+ You can send this Tileset to :any:`set_default`.
+
+ This function is provisional. The API may change.
+ """
+ path = Path(path).resolve(strict=True)
+ cdata = lib.TCOD_load_truetype_font_(_path_encode(path), tile_width, tile_height)
+ if not cdata:
+ raise RuntimeError(ffi.string(lib.TCOD_get_error()))
+ return Tileset._claim(cdata)
+
+
+@deprecated("Accessing the default tileset is deprecated.")
+def set_truetype_font(path: str | PathLike[str], tile_width: int, tile_height: int) -> None:
+ """Set the default tileset from a `.ttf` or `.otf` file.
+
+ `path` is the file path for the font file.
+
+ `tile_width` and `tile_height` are the desired size of the tiles in the new
+ tileset. The font will be scaled to fit the given `tile_height` and
+ `tile_width`.
+
+ This function must be called before :any:`libtcodpy.console_init_root`. Once
+ the root console is setup you may call this function again to change the
+ font. The tileset can be changed but the window will not be resized
+ automatically.
+
+ .. versionadded:: 9.2
+
+ .. deprecated:: 11.13
+ This function does not support contexts.
+ Use :any:`load_truetype_font` instead.
+ """
+ path = Path(path).resolve(strict=True)
+ if lib.TCOD_tileset_load_truetype_(_path_encode(path), tile_width, tile_height):
+ raise RuntimeError(ffi.string(lib.TCOD_get_error()))
+
+
+def load_bdf(path: str | PathLike[str]) -> Tileset:
+ """Return a new Tileset from a `.bdf` file.
+
+ For the best results the font should be monospace, cell-based, and
+ single-width. As an example, a good set of fonts would be the
+ `Unicode fonts and tools for X11 `_
+ package.
+
+ Pass the returned Tileset to :any:`tcod.tileset.set_default` and it will
+ take effect when `libtcodpy.console_init_root` is called.
+
+ .. versionadded:: 11.10
+ """
+ path = Path(path).resolve(strict=True)
+ cdata = lib.TCOD_load_bdf(_path_encode(path))
+ if not cdata:
+ raise RuntimeError(ffi.string(lib.TCOD_get_error()).decode())
+ return Tileset._claim(cdata)
+
+
+def load_tilesheet(path: str | PathLike[str], columns: int, rows: int, charmap: Iterable[int] | None) -> Tileset:
+ """Return a new Tileset from a simple tilesheet image.
+
+ `path` is the file path to a PNG file with the tileset.
+
+ `columns` and `rows` is the shape of the tileset. Tiles are assumed to
+ take up the entire space of the image.
+
+ `charmap` is a sequence of codepoints to map the tilesheet to in row-major order.
+ This is a list or generator of codepoints which map the tiles like this: ``charmap[tile_index] = codepoint``.
+ For common tilesets `charmap` should be :any:`tcod.tileset.CHARMAP_CP437`.
+ Generators will be sliced so :any:`itertools.count` can be used which will
+ give all tiles the same codepoint as their index, but this will not map
+ tiles onto proper Unicode.
+ If `None` is used then no tiles will be mapped, you will need to use
+ :any:`Tileset.remap` to assign codepoints to this Tileset.
+
+ Image alpha and key colors are handled automatically.
+ For example any tileset from the `Dwarf Fortress tileset repository `_
+ will load correctly with the following example:
+
+ Example::
+
+ from pathlib import Path
+
+ import tcod.tileset
+
+ THIS_DIR = Path(__file__, "..") # Directory of this script file
+ FONT = THIS_DIR / "assets/tileset.png" # Replace with any tileset from the DF tileset repository
+
+ # Will raise FileNotFoundError if the font is missing!
+ tileset = tcod.tileset.load_tilesheet(FONT, 16, 16, tcod.tileset.CHARMAP_CP437)
+
+ The tileset return value is usually passed to the `tileset` parameter of :any:`tcod.context.new`.
+
+ .. versionadded:: 11.12
+ """
+ path = Path(path).resolve(strict=True)
+ mapping = []
+ if charmap is not None:
+ mapping = list(itertools.islice(charmap, columns * rows))
+ cdata = lib.TCOD_tileset_load(_path_encode(path), columns, rows, len(mapping), mapping)
+ if not cdata:
+ _raise_tcod_error()
+ return Tileset._claim(cdata)
+
+
+@overload
+@deprecated(
+ "Prefer assigning tiles using dictionary semantics:\n"
+ "'tileset += tcod.tileset.procedural_block_elements(shape=tileset.tile_shape)'"
+)
+def procedural_block_elements(*, tileset: Tileset) -> Tileset: ...
+@overload
+def procedural_block_elements(*, shape: tuple[int, int]) -> Tileset: ...
+
+
+def procedural_block_elements(*, tileset: Tileset | None = None, shape: tuple[int, int] | None = None) -> Tileset:
+ """Generate and return a :any:`Tileset` with procedurally generated block elements.
+
+ Args:
+ tileset: A :any:`Tileset` with tiles of any shape. The codepoints of this tileset will be overwritten.
+ This parameter is deprecated and only shape `should` be used.
+ shape: The ``(height, width)`` tile size to generate.
+
+ This will overwrite all of the codepoints `listed here `_
+ except for the shade glyphs.
+
+ This function is useful for other functions such as :any:`Console.draw_semigraphics` which use more types of block
+ elements than are found in Code Page 437.
+
+ .. versionadded:: 13.1
+
+ .. versionchanged:: 20.1
+ Added `shape` parameter, now returns a `Tileset`.
+ `tileset` parameter is deprecated.
+
+ Example::
+
+ >>> import tcod.tileset
+ >>> tileset = tcod.tileset.Tileset(8, 8)
+ >>> tileset += tcod.tileset.procedural_block_elements(shape=tileset.tile_shape)
+ >>> tileset.get_tile(0x259E)[:, :, 3] # "▞" Quadrant upper right and lower left.
+ array([[ 0, 0, 0, 0, 255, 255, 255, 255],
+ [ 0, 0, 0, 0, 255, 255, 255, 255],
+ [ 0, 0, 0, 0, 255, 255, 255, 255],
+ [ 0, 0, 0, 0, 255, 255, 255, 255],
+ [255, 255, 255, 255, 0, 0, 0, 0],
+ [255, 255, 255, 255, 0, 0, 0, 0],
+ [255, 255, 255, 255, 0, 0, 0, 0],
+ [255, 255, 255, 255, 0, 0, 0, 0]], dtype=uint8)
+ >>> tileset.get_tile(0x2581)[:, :, 3] # "▁" Lower one eighth block.
+ array([[ 0, 0, 0, 0, 0, 0, 0, 0],
+ [ 0, 0, 0, 0, 0, 0, 0, 0],
+ [ 0, 0, 0, 0, 0, 0, 0, 0],
+ [ 0, 0, 0, 0, 0, 0, 0, 0],
+ [ 0, 0, 0, 0, 0, 0, 0, 0],
+ [ 0, 0, 0, 0, 0, 0, 0, 0],
+ [ 0, 0, 0, 0, 0, 0, 0, 0],
+ [255, 255, 255, 255, 255, 255, 255, 255]], dtype=uint8)
+ >>> tileset.get_tile(0x258D)[:, :, 3] # "▍" Left three eighths block.
+ array([[255, 255, 255, 0, 0, 0, 0, 0],
+ [255, 255, 255, 0, 0, 0, 0, 0],
+ [255, 255, 255, 0, 0, 0, 0, 0],
+ [255, 255, 255, 0, 0, 0, 0, 0],
+ [255, 255, 255, 0, 0, 0, 0, 0],
+ [255, 255, 255, 0, 0, 0, 0, 0],
+ [255, 255, 255, 0, 0, 0, 0, 0],
+ [255, 255, 255, 0, 0, 0, 0, 0]], dtype=uint8)
+ """
+ if tileset is None:
+ assert shape is not None
+ tileset = Tileset(shape[1], shape[0])
+ quadrants: NDArray[np.uint8] = np.zeros(tileset.tile_shape, dtype=np.uint8)
+ half_height = tileset.tile_height // 2
+ half_width = tileset.tile_width // 2
+ quadrants[:half_height, :half_width] = 0b1000 # Top-left.
+ quadrants[:half_height, half_width:] = 0b0100 # Top-right.
+ quadrants[half_height:, :half_width] = 0b0010 # Bottom-left.
+ quadrants[half_height:, half_width:] = 0b0001 # Bottom-right.
+
+ for codepoint, quad_mask in (
+ (0x2580, 0b1100), # "▀" Upper half block.
+ (0x2584, 0b0011), # "▄" Lower half block.
+ (0x2588, 0b1111), # "█" Full block.
+ (0x258C, 0b1010), # "▌" Left half block.
+ (0x2590, 0b0101), # "▐" Right half block.
+ (0x2596, 0b0010), # "▖" Quadrant lower left.
+ (0x2597, 0b0001), # "▗" Quadrant lower right.
+ (0x2598, 0b1000), # "▘" Quadrant upper left.
+ (0x2599, 0b1011), # "▙" Quadrant upper left and lower left and lower right.
+ (0x259A, 0b1001), # "▚" Quadrant upper left and lower right.
+ (0x259B, 0b1110), # "▛" Quadrant upper left and upper right and lower left.
+ (0x259C, 0b1101), # "▜" Quadrant upper left and upper right and lower right.
+ (0x259D, 0b0100), # "▝" Quadrant upper right.
+ (0x259E, 0b0110), # "▞" Quadrant upper right and lower left.
+ (0x259F, 0b0111), # "▟" Quadrant upper right and lower left and lower right.
+ ):
+ alpha: NDArray[np.uint8] = np.asarray((quadrants & quad_mask) != 0, dtype=np.uint8)
+ alpha *= 255
+ tileset[codepoint] = alpha
+
+ for codepoint, axis, fraction, negative in (
+ (0x2581, 0, 7, True), # "▁" Lower one eighth block.
+ (0x2582, 0, 6, True), # "▂" Lower one quarter block.
+ (0x2583, 0, 5, True), # "▃" Lower three eighths block.
+ (0x2585, 0, 3, True), # "▅" Lower five eighths block.
+ (0x2586, 0, 2, True), # "▆" Lower three quarters block.
+ (0x2587, 0, 1, True), # "▇" Lower seven eighths block.
+ (0x2589, 1, 7, False), # "▉" Left seven eighths block.
+ (0x258A, 1, 6, False), # "▊" Left three quarters block.
+ (0x258B, 1, 5, False), # "▋" Left five eighths block.
+ (0x258D, 1, 3, False), # "▍" Left three eighths block.
+ (0x258E, 1, 2, False), # "▎" Left one quarter block.
+ (0x258F, 1, 1, False), # "▏" Left one eighth block.
+ (0x2594, 0, 1, False), # "▔" Upper one eighth block.
+ (0x2595, 1, 7, True), # "▕" Right one eighth block .
+ ):
+ indexes = [slice(None), slice(None)]
+ divide = tileset.tile_shape[axis] * fraction // 8
+ # If negative then shade from the far corner, otherwise shade from the near corner.
+ indexes[axis] = slice(divide, None) if negative else slice(None, divide)
+ alpha = np.zeros(tileset.tile_shape, dtype=np.uint8)
+ alpha[tuple(indexes)] = 255
+ tileset[codepoint] = alpha
+ return tileset
+
+
+CHARMAP_CP437 = [
+ 0x0000,
+ 0x263A,
+ 0x263B,
+ 0x2665,
+ 0x2666,
+ 0x2663,
+ 0x2660,
+ 0x2022,
+ 0x25D8,
+ 0x25CB,
+ 0x25D9,
+ 0x2642,
+ 0x2640,
+ 0x266A,
+ 0x266B,
+ 0x263C,
+ 0x25BA,
+ 0x25C4,
+ 0x2195,
+ 0x203C,
+ 0x00B6,
+ 0x00A7,
+ 0x25AC,
+ 0x21A8,
+ 0x2191,
+ 0x2193,
+ 0x2192,
+ 0x2190,
+ 0x221F,
+ 0x2194,
+ 0x25B2,
+ 0x25BC,
+ 0x0020,
+ 0x0021,
+ 0x0022,
+ 0x0023,
+ 0x0024,
+ 0x0025,
+ 0x0026,
+ 0x0027,
+ 0x0028,
+ 0x0029,
+ 0x002A,
+ 0x002B,
+ 0x002C,
+ 0x002D,
+ 0x002E,
+ 0x002F,
+ 0x0030,
+ 0x0031,
+ 0x0032,
+ 0x0033,
+ 0x0034,
+ 0x0035,
+ 0x0036,
+ 0x0037,
+ 0x0038,
+ 0x0039,
+ 0x003A,
+ 0x003B,
+ 0x003C,
+ 0x003D,
+ 0x003E,
+ 0x003F,
+ 0x0040,
+ 0x0041,
+ 0x0042,
+ 0x0043,
+ 0x0044,
+ 0x0045,
+ 0x0046,
+ 0x0047,
+ 0x0048,
+ 0x0049,
+ 0x004A,
+ 0x004B,
+ 0x004C,
+ 0x004D,
+ 0x004E,
+ 0x004F,
+ 0x0050,
+ 0x0051,
+ 0x0052,
+ 0x0053,
+ 0x0054,
+ 0x0055,
+ 0x0056,
+ 0x0057,
+ 0x0058,
+ 0x0059,
+ 0x005A,
+ 0x005B,
+ 0x005C,
+ 0x005D,
+ 0x005E,
+ 0x005F,
+ 0x0060,
+ 0x0061,
+ 0x0062,
+ 0x0063,
+ 0x0064,
+ 0x0065,
+ 0x0066,
+ 0x0067,
+ 0x0068,
+ 0x0069,
+ 0x006A,
+ 0x006B,
+ 0x006C,
+ 0x006D,
+ 0x006E,
+ 0x006F,
+ 0x0070,
+ 0x0071,
+ 0x0072,
+ 0x0073,
+ 0x0074,
+ 0x0075,
+ 0x0076,
+ 0x0077,
+ 0x0078,
+ 0x0079,
+ 0x007A,
+ 0x007B,
+ 0x007C,
+ 0x007D,
+ 0x007E,
+ 0x2302,
+ 0x00C7,
+ 0x00FC,
+ 0x00E9,
+ 0x00E2,
+ 0x00E4,
+ 0x00E0,
+ 0x00E5,
+ 0x00E7,
+ 0x00EA,
+ 0x00EB,
+ 0x00E8,
+ 0x00EF,
+ 0x00EE,
+ 0x00EC,
+ 0x00C4,
+ 0x00C5,
+ 0x00C9,
+ 0x00E6,
+ 0x00C6,
+ 0x00F4,
+ 0x00F6,
+ 0x00F2,
+ 0x00FB,
+ 0x00F9,
+ 0x00FF,
+ 0x00D6,
+ 0x00DC,
+ 0x00A2,
+ 0x00A3,
+ 0x00A5,
+ 0x20A7,
+ 0x0192,
+ 0x00E1,
+ 0x00ED,
+ 0x00F3,
+ 0x00FA,
+ 0x00F1,
+ 0x00D1,
+ 0x00AA,
+ 0x00BA,
+ 0x00BF,
+ 0x2310,
+ 0x00AC,
+ 0x00BD,
+ 0x00BC,
+ 0x00A1,
+ 0x00AB,
+ 0x00BB,
+ 0x2591,
+ 0x2592,
+ 0x2593,
+ 0x2502,
+ 0x2524,
+ 0x2561,
+ 0x2562,
+ 0x2556,
+ 0x2555,
+ 0x2563,
+ 0x2551,
+ 0x2557,
+ 0x255D,
+ 0x255C,
+ 0x255B,
+ 0x2510,
+ 0x2514,
+ 0x2534,
+ 0x252C,
+ 0x251C,
+ 0x2500,
+ 0x253C,
+ 0x255E,
+ 0x255F,
+ 0x255A,
+ 0x2554,
+ 0x2569,
+ 0x2566,
+ 0x2560,
+ 0x2550,
+ 0x256C,
+ 0x2567,
+ 0x2568,
+ 0x2564,
+ 0x2565,
+ 0x2559,
+ 0x2558,
+ 0x2552,
+ 0x2553,
+ 0x256B,
+ 0x256A,
+ 0x2518,
+ 0x250C,
+ 0x2588,
+ 0x2584,
+ 0x258C,
+ 0x2590,
+ 0x2580,
+ 0x03B1,
+ 0x00DF,
+ 0x0393,
+ 0x03C0,
+ 0x03A3,
+ 0x03C3,
+ 0x00B5,
+ 0x03C4,
+ 0x03A6,
+ 0x0398,
+ 0x03A9,
+ 0x03B4,
+ 0x221E,
+ 0x03C6,
+ 0x03B5,
+ 0x2229,
+ 0x2261,
+ 0x00B1,
+ 0x2265,
+ 0x2264,
+ 0x2320,
+ 0x2321,
+ 0x00F7,
+ 0x2248,
+ 0x00B0,
+ 0x2219,
+ 0x00B7,
+ 0x221A,
+ 0x207F,
+ 0x00B2,
+ 0x25A0,
+ 0x00A0,
+]
+"""A code page 437 character mapping.
+
+See :ref:`code-page-437` for more info and a table of glyphs.
+
+.. versionadded:: 11.12
+
+.. versionchanged:: 14.0
+ Character at index ``0x7F`` was changed from value ``0x7F`` to the HOUSE ``⌂`` glyph ``0x2302``.
+"""
+
+CHARMAP_TCOD = [
+ 0x20,
+ 0x21,
+ 0x22,
+ 0x23,
+ 0x24,
+ 0x25,
+ 0x26,
+ 0x27,
+ 0x28,
+ 0x29,
+ 0x2A,
+ 0x2B,
+ 0x2C,
+ 0x2D,
+ 0x2E,
+ 0x2F,
+ 0x30,
+ 0x31,
+ 0x32,
+ 0x33,
+ 0x34,
+ 0x35,
+ 0x36,
+ 0x37,
+ 0x38,
+ 0x39,
+ 0x3A,
+ 0x3B,
+ 0x3C,
+ 0x3D,
+ 0x3E,
+ 0x3F,
+ 0x40,
+ 0x5B,
+ 0x5C,
+ 0x5D,
+ 0x5E,
+ 0x5F,
+ 0x60,
+ 0x7B,
+ 0x7C,
+ 0x7D,
+ 0x7E,
+ 0x2591,
+ 0x2592,
+ 0x2593,
+ 0x2502,
+ 0x2500,
+ 0x253C,
+ 0x2524,
+ 0x2534,
+ 0x251C,
+ 0x252C,
+ 0x2514,
+ 0x250C,
+ 0x2510,
+ 0x2518,
+ 0x2598,
+ 0x259D,
+ 0x2580,
+ 0x2596,
+ 0x259A,
+ 0x2590,
+ 0x2597,
+ 0x2191,
+ 0x2193,
+ 0x2190,
+ 0x2192,
+ 0x25B2,
+ 0x25BC,
+ 0x25C4,
+ 0x25BA,
+ 0x2195,
+ 0x2194,
+ 0x2610,
+ 0x2611,
+ 0x25CB,
+ 0x25C9,
+ 0x2551,
+ 0x2550,
+ 0x256C,
+ 0x2563,
+ 0x2569,
+ 0x2560,
+ 0x2566,
+ 0x255A,
+ 0x2554,
+ 0x2557,
+ 0x255D,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x41,
+ 0x42,
+ 0x43,
+ 0x44,
+ 0x45,
+ 0x46,
+ 0x47,
+ 0x48,
+ 0x49,
+ 0x4A,
+ 0x4B,
+ 0x4C,
+ 0x4D,
+ 0x4E,
+ 0x4F,
+ 0x50,
+ 0x51,
+ 0x52,
+ 0x53,
+ 0x54,
+ 0x55,
+ 0x56,
+ 0x57,
+ 0x58,
+ 0x59,
+ 0x5A,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x61,
+ 0x62,
+ 0x63,
+ 0x64,
+ 0x65,
+ 0x66,
+ 0x67,
+ 0x68,
+ 0x69,
+ 0x6A,
+ 0x6B,
+ 0x6C,
+ 0x6D,
+ 0x6E,
+ 0x6F,
+ 0x70,
+ 0x71,
+ 0x72,
+ 0x73,
+ 0x74,
+ 0x75,
+ 0x76,
+ 0x77,
+ 0x78,
+ 0x79,
+ 0x7A,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+]
+"""The layout used by older libtcod fonts, in Unicode.
+
+This layout is non-standard, and it's not recommend to make a font for it, but
+you might need it to load an existing font made for libtcod.
+
+This character map is in Unicode, so old code using the non-Unicode
+`tcod.CHAR_*` constants will need to be updated.
+
+See :ref:`deprecated-tcod-layout` for a table of glyphs used in this character
+map.
+
+.. versionadded:: 11.12
+"""
diff --git a/tdl/__init__.py b/tdl/__init__.py
deleted file mode 100755
index 494027c2..00000000
--- a/tdl/__init__.py
+++ /dev/null
@@ -1,1330 +0,0 @@
-"""
- Getting Started
- ===============
- Once the library is imported you can load the font you want to use with
- :any:`tdl.set_font`.
- This is optional and when skipped will use a decent default font.
-
- After that you call :any:`tdl.init` to set the size of the window and
- get the root console in return.
- This console is the canvas to what will appear on the screen.
-
- Indexing Consoles
- =================
- For most methods taking a position you can use Python-style negative
- indexes to refer to the opposite side of a console with (-1, -1)
- starting at the bottom right.
- You can also check if a point is part of a console using containment
- logic i.e. ((x, y) in console).
-
- You may also iterate over a console using a for statement. This returns
- every x,y coordinate available to draw on but it will be extremely slow
- to actually operate on every coordinate individualy.
- Try to minimize draws by using an offscreen :any:`Console`, only drawing
- what needs to be updated, and using :any:`Console.blit`.
-
- Drawing and Colors
- ==================
-
- Once you have the root console from :any:`tdl.init` you can start drawing
- on it using a method such as :any:`Console.draw_char`.
- When using this method you can have the char parameter be an integer or a
- single character string.
-
- The fg and bg parameters expect a variety of types.
- The parameters default to Ellipsis which will tell the function to
- use the colors previously set by the :any:`Console.set_colors` method.
- The colors set by :any`Console.set_colors` are per each
- :any:`Console`/:any:`Window` and default to white on black.
- You can use a 3-item list/tuple of [red, green, blue] with integers in
- the 0-255 range with [0, 0, 0] being black and [255, 255, 255] being
- white.
- You can even use a single integer of 0xRRGGBB if you like.
-
- Using None in the place of any of the three parameters (char, fg, bg)
- will tell the function to not overwrite that color or character.
-
- After the drawing functions are called a call to :any:`tdl.flush` will
- update the screen.
-"""
-
-from __future__ import (absolute_import, division,
- print_function, unicode_literals)
-
-import sys as _sys
-import os as _os
-
-import array as _array
-import weakref as _weakref
-import itertools as _itertools
-import textwrap as _textwrap
-import struct as _struct
-import re as _re
-import warnings as _warnings
-
-from tcod import ffi as _ffi
-from tcod import lib as _lib
-
-import tdl.event
-import tdl.noise
-import tdl.map
-import tdl.style as _style
-
-from tcod import __version__
-
-_IS_PYTHON3 = (_sys.version_info[0] == 3)
-
-if _IS_PYTHON3: # some type lists to use with isinstance
- _INTTYPES = (int,)
- _NUMTYPES = (int, float)
- _STRTYPES = (str, bytes)
-else:
- _INTTYPES = (int, long)
- _NUMTYPES = (int, long, float)
- _STRTYPES = (str, unicode)
-
-def _encodeString(string): # still used for filepaths, and that's about it
- "changes string into bytes if running in python 3, for sending to ctypes"
- if isinstance(string, _STRTYPES):
- return string.encode()
- return string
-
-def _format_char(char):
- """Prepares a single character for passing to ctypes calls, needs to return
- an integer but can also pass None which will keep the current character
- instead of overwriting it.
-
- This is called often and needs to be optimized whenever possible.
- """
- if char is None:
- return -1
- if isinstance(char, _STRTYPES) and len(char) == 1:
- return ord(char)
- try:
- return int(char) # allow all int-like objects
- except:
- raise TypeError('char single character string, integer, or None\nReceived: ' + repr(char))
-
-_utf32_codec = {'little': 'utf-32le', 'big': 'utf-32le'}[_sys.byteorder]
-
-def _format_str(string):
- """Attempt fast string handing by decoding directly into an array."""
- if isinstance(string, _STRTYPES):
- if _IS_PYTHON3:
- array = _array.array('I')
- array.frombytes(string.encode(_utf32_codec))
- else: # Python 2
- if isinstance(string, unicode):
- array = _array.array(b'I')
- array.fromstring(string.encode(_utf32_codec))
- else:
- array = _array.array(b'B')
- array.fromstring(string)
- return array
- return string
-
-_fontinitialized = False
-_rootinitialized = False
-_rootConsoleRef = None
-
-_put_char_ex = _lib.TDL_console_put_char_ex
-
-# python 2 to 3 workaround
-if _sys.version_info[0] == 2:
- int_types = (int, long)
-else:
- int_types = int
-
-
-def _format_color(color, default=Ellipsis):
- if color is Ellipsis:
- return default
- if color is None:
- return -1
- if isinstance(color, (tuple, list)) and len(color) == 3:
- return (color[0] << 16) + (color[1] << 8) + color[2]
- try:
- return int(color) # allow all int-like objects
- except:
- raise TypeError('fg and bg must be a 3 item tuple, integer, Ellipsis, or None\nReceived: ' + repr(color))
-
-def _to_tcod_color(color):
- return _ffi.new('TCOD_color_t *', (color >> 16 & 0xff,
- color >> 8 & 0xff,
- color & 0xff))
-
-def _getImageSize(filename):
- """Try to get the width and height of a bmp of png image file"""
- result = None
- file = open(filename, 'rb')
- if file.read(8) == b'\x89PNG\r\n\x1a\n': # PNG
- while 1:
- length, = _struct.unpack('>i', file.read(4))
- chunkID = file.read(4)
- if chunkID == '': # EOF
- break
- if chunkID == b'IHDR':
- # return width, height
- result = _struct.unpack('>ii', file.read(8))
- break
- file.seek(4 + length, 1)
- file.close()
- return result
- file.seek(0)
- if file.read(8) == b'BM': # Bitmap
- file.seek(18, 0) # skip to size data
- result = _struct.unpack('= width:
- x -= width
- y += 1
- while y >= height:
- if self._scrollMode == 'scroll':
- y -= 1
- self.scroll(0, -1)
- elif self._scrollMode == 'error':
- # reset the cursor on error
- self._cursor = (0, 0)
- raise TDLError('Cursor has reached the end of the console')
- return (x, y)
-
- def set_mode(self, mode):
- """Configure how this console will react to the cursor writing past the
- end if the console.
-
- This is for methods that use the virtual cursor, such as
- :any:`print_str`.
-
- Args:
- mode (Text): The mode to set.
-
- Possible settings are:
-
- - 'error' - A TDLError will be raised once the cursor
- reaches the end of the console. Everything up until
- the error will still be drawn.
- This is the default setting.
- - 'scroll' - The console will scroll up as stuff is
- written to the end.
- You can restrict the region with :any:`tdl.Window` when
- doing this.
-
- ..seealso:: :any:`write`, :any:`print_str`
- """
- MODES = ['error', 'scroll']
- if mode.lower() not in MODES:
- raise TDLError('mode must be one of %s, got %s' % (MODES, repr(mode)))
- self._scrollMode = mode.lower()
-
- def set_colors(self, fg=None, bg=None):
- """Sets the colors to be used with the L{print_str} and draw_* methods.
-
- Values of None will only leave the current values unchanged.
-
- Args:
- fg (Optional[Union[Tuple[int, int, int], int, Ellipsis]])
- bg (Optional[Union[Tuple[int, int, int], int, Ellipsis]])
- .. seealso:: :any:`move`, :any:`print_str`
- """
- if fg is not None:
- self._fg = _format_color(fg, self._fg)
- if bg is not None:
- self._bg = _format_color(bg, self._bg)
-
- def print_str(self, string):
- """Print a string at the virtual cursor.
-
- Handles special characters such as '\\n' and '\\r'.
- Printing past the bottom of the console will scroll everything upwards
- if :any:`set_mode` is set to 'scroll'.
-
- Colors can be set with :any:`set_colors` and the virtual cursor can
- be moved with :any:`move`.
-
- Args:
- string (Text): The text to print.
-
- .. seealso:: :any:`draw_str`, :any:`move`, :any:`set_colors`,
- :any:`set_mode`, :any:`write`, :any:`Window`
- """
- x, y = self._cursor
- for char in string:
- if char == '\n': # line break
- x = 0
- y += 1
- continue
- if char == '\r': # return
- x = 0
- continue
- x, y = self._normalizeCursor(x, y)
- self.draw_char(x, y, char, self._fg, self._bg)
- x += 1
- self._cursor = (x, y)
-
- def write(self, string):
- """This method mimics basic file-like behaviour.
-
- Because of this method you can replace sys.stdout or sys.stderr with
- a :any:`Console` or :any:`Window` instance.
-
- This is a convoluted process and behaviour seen now can be excepted to
- change on later versions.
-
- Args:
- string (Text): The text to write out.
-
- .. seealso:: :any:`set_colors`, :any:`set_mode`, :any:`Window`
- """
- # some 'basic' line buffer stuff.
- # there must be an easier way to do this. The textwrap module didn't
- # help much.
- x, y = self._normalizeCursor(*self._cursor)
- width, height = self.get_size()
- wrapper = _textwrap.TextWrapper(initial_indent=(' '*x), width=width)
- writeLines = []
- for line in string.split('\n'):
- if line:
- writeLines += wrapper.wrap(line)
- wrapper.initial_indent = ''
- else:
- writeLines.append([])
-
- for line in writeLines:
- x, y = self._normalizeCursor(x, y)
- self.draw_str(x, y, line[x:], self._fg, self._bg)
- y += 1
- x = 0
- y -= 1
- self._cursor = (x, y)
-
- def draw_char(self, x, y, char, fg=Ellipsis, bg=Ellipsis):
- """Draws a single character.
-
- Args:
- x (int): x-coordinate to draw on.
- y (int): y-coordinate to draw on.
- char (Optional[Union[int, Text]]): An integer, single character
- string, or None.
-
- You can set the char parameter as None if you only want to change
- the colors of the tile.
- fg (Optional[Union[Tuple[int, int, int], int, Ellipsis]])
- bg (Optional[Union[Tuple[int, int, int], int, Ellipsis]])
-
- Raises: AssertionError: Having x or y values that can't be placed
- inside of the console will raise an AssertionError.
- You can use always use ((x, y) in console) to
- check if a tile is drawable.
-
- .. seealso:: :any:`get_char`
- """
- #x, y = self._normalizePoint(x, y)
- _put_char_ex(self.console_c, x, y, _format_char(char),
- _format_color(fg, self._fg), _format_color(bg, self._bg), 1)
-
- def draw_str(self, x, y, string, fg=Ellipsis, bg=Ellipsis):
- """Draws a string starting at x and y.
-
- A string that goes past the right side will wrap around. A string
- wrapping to below the console will raise :any:`tdl.TDLError` but will
- still be written out.
- This means you can safely ignore the errors with a
- try..except block if you're fine with partially written strings.
-
- \\r and \\n are drawn on the console as normal character tiles. No
- special encoding is done and any string will translate to the character
- table as is.
-
- For a string drawing operation that respects special characters see
- :any:`print_str`.
-
- Args:
- x (int): x-coordinate to start at.
- y (int): y-coordinate to start at.
- string (Union[Text, Iterable[int]]): A string or an iterable of
- numbers.
-
- Special characters are ignored and rendered as any other
- character.
- fg (Optional[Union[Tuple[int, int, int], int, Ellipsis]])
- bg (Optional[Union[Tuple[int, int, int], int, Ellipsis]])
-
- Raises:
- AssertionError: Having x or y values that can't be placed inside
- of the console will raise an AssertionError.
-
- You can use always use ``((x, y) in console)`` to check if
- a tile is drawable.
-
- .. seealso:: :any:`print_str`
- """
-
- x, y = self._normalizePoint(x, y)
- fg, bg = _format_color(fg, self._fg), _format_color(bg, self._bg)
- width, height = self.get_size()
- def _drawStrGen(x=x, y=y, string=string, width=width, height=height):
- """Generator for draw_str
-
- Iterates over ((x, y), ch) data for _set_batch, raising an
- error if the end of the console is reached.
- """
- for char in _format_str(string):
- if y == height:
- raise TDLError('End of console reached.')
- yield((x, y), char)
- x += 1 # advance cursor
- if x == width: # line break
- x = 0
- y += 1
- self._set_batch(_drawStrGen(), fg, bg)
-
- def draw_rect(self, x, y, width, height, string, fg=Ellipsis, bg=Ellipsis):
- """Draws a rectangle starting from x and y and extending to width and height.
-
- If width or height are None then it will extend to the edge of the console.
-
- Args:
- x (int): x-coordinate for the top side of the rect.
- y (int): y-coordinate for the left side of the rect.
- width (Optional[int]): The width of the rectangle.
-
- Can be None to extend to the bottom right of the
- console or can be a negative number to be sized reltive
- to the total size of the console.
- height (Optional[int]): The height of the rectangle.
- string (Optional[Union[Text, int]]): An integer, single character
- string, or None.
-
- You can set the string parameter as None if you only want
- to change the colors of an area.
- fg (Optional[Union[Tuple[int, int, int], int, Ellipsis]])
- bg (Optional[Union[Tuple[int, int, int], int, Ellipsis]])
-
- Raises:
- AssertionError: Having x or y values that can't be placed inside
- of the console will raise an AssertionError.
-
- You can use always use ``((x, y) in console)`` to check if a tile
- is drawable.
- .. seealso:: :any:`clear`, :any:`draw_frame`
- """
- x, y, width, height = self._normalizeRect(x, y, width, height)
- fg, bg = _format_color(fg, self._fg), _format_color(bg, self._bg)
- char = _format_char(string)
- # use itertools to make an x,y grid
- # using ctypes here reduces type converstions later
- #grid = _itertools.product((_ctypes.c_int(x) for x in range(x, x + width)),
- # (_ctypes.c_int(y) for y in range(y, y + height)))
- grid = _itertools.product((x for x in range(x, x + width)),
- (y for y in range(y, y + height)))
- # zip the single character in a batch variable
- batch = zip(grid, _itertools.repeat(char, width * height))
- self._set_batch(batch, fg, bg, nullChar=(char is None))
-
- def draw_frame(self, x, y, width, height, string, fg=Ellipsis, bg=Ellipsis):
- """Similar to L{draw_rect} but only draws the outline of the rectangle.
-
- `width or `height` can be None to extend to the bottom right of the
- console or can be a negative number to be sized reltive
- to the total size of the console.
-
- Args:
- x (int): The x-coordinate to start on.
- y (int): The y-coordinate to start on.
- width (Optional[int]): Width of the rectangle.
- height (Optional[int]): Height of the rectangle.
- string (Optional[Union[Text, int]]): An integer, single character
- string, or None.
-
- You can set this parameter as None if you only want
- to change the colors of an area.
- fg (Optional[Union[Tuple[int, int, int], int, Ellipsis]])
- bg (Optional[Union[Tuple[int, int, int], int, Ellipsis]])
-
- Raises:
- AssertionError: Having x or y values that can't be placed inside
- of the console will raise an AssertionError.
-
- You can use always use ``((x, y) in console)`` to check if a tile
- is drawable.
- .. seealso:: :any:`draw_rect`, :any:`Window`
- """
- x, y, width, height = self._normalizeRect(x, y, width, height)
- fg, bg = _format_color(fg, self._fg), _format_color(bg, self._bg)
- char = _format_char(string)
- if width == 1 or height == 1: # it's just a single width line here
- return self.draw_rect(x, y, width, height, char, fg, bg)
-
- # draw sides of frame with draw_rect
- self.draw_rect(x, y, 1, height, char, fg, bg)
- self.draw_rect(x, y, width, 1, char, fg, bg)
- self.draw_rect(x + width - 1, y, 1, height, char, fg, bg)
- self.draw_rect(x, y + height - 1, width, 1, char, fg, bg)
-
- def blit(self, source, x=0, y=0, width=None, height=None, srcX=0, srcY=0,
- fg_alpha=1.0, bg_alpha=1.0):
- """Blit another console or Window onto the current console.
-
- By default it blits the entire source to the topleft corner.
-
- Args:
- source (Union[tdl.Console, tdl.Window]): The blitting source.
- A console can blit to itself without any problems.
- x (int): x-coordinate of this console to blit on.
- y (int): y-coordinate of this console to blit on.
- width (Optional[int]): Width of the rectangle.
-
- Can be None to extend as far as possible to the
- bottom right corner of the blit area or can be a negative
- number to be sized reltive to the total size of the
- B{destination} console.
- height (Optional[int]): Height of the rectangle.
- srcX (int): x-coordinate of the source region to blit.
- srcY (int): y-coordinate of the source region to blit.
- fg_alpha (float): The foreground alpha.
- """
- assert isinstance(source, (Console, Window)), "source muse be a Window or Console instance"
-
- # handle negative indexes and rects
- # negative width and height will be set realtive to the destination
- # and will also be clamped to the smallest Console
- x, y, width, height = self._normalizeRect(x, y, width, height)
- srcX, srcY, width, height = source._normalizeRect(srcX, srcY, width, height)
-
- # translate source and self if any of them are Window instances
- srcX, srcY = source._translate(srcX, srcY)
- source = source.console
- x, y = self._translate(x, y)
- self = self.console
-
- if self == source:
- # if we are the same console then we need a third console to hold
- # onto the data, otherwise it tries to copy into itself and
- # starts destroying everything
- tmp = Console(width, height)
- _lib.TCOD_console_blit(source.console_c,
- srcX, srcY, width, height,
- tmp.console_c, 0, 0, fg_alpha, bg_alpha)
- _lib.TCOD_console_blit(tmp.console_c, 0, 0, width, height,
- self.console_c, x, y, fg_alpha, bg_alpha)
- else:
- _lib.TCOD_console_blit(source.console_c,
- srcX, srcY, width, height,
- self.console_c, x, y, fg_alpha, bg_alpha)
-
- def get_cursor(self):
- """Return the virtual cursor position.
-
- The cursor can be moved with the :any:`move` method.
-
- Returns:
- Tuple[int, int]: The (x, y) coordinate of where :any:`print_str`
- will continue from.
-
- .. seealso:: :any:move`
- """
- x, y = self._cursor
- width, height = self.parent.get_size()
- while x >= width:
- x -= width
- y += 1
- if y >= height and self.scrollMode == 'scroll':
- y = height - 1
- return x, y
-
- def get_size(self):
- """Return the size of the console as (width, height)
-
- Returns:
- Tuple[int, int]: A (width, height) tuple.
- """
- return self.width, self.height
-
- def __iter__(self):
- """Return an iterator with every possible (x, y) value for this console.
-
- It goes without saying that working on the console this way is a
- slow process, especially for Python, and should be minimized.
-
- Returns:
- Iterator[Tuple[int, int]]: An ((x, y), ...) iterator.
- """
- return _itertools.product(range(self.width), range(self.height))
-
- def move(self, x, y):
- """Move the virtual cursor.
-
- Args:
- x (int): x-coordinate to place the cursor.
- y (int): y-coordinate to place the cursor.
-
- .. seealso:: :any:`get_cursor`, :any:`print_str`, :any:`write`
- """
- self._cursor = self._normalizePoint(x, y)
-
- def scroll(self, x, y):
- """Scroll the contents of the console in the direction of x,y.
-
- Uncovered areas will be cleared to the default background color.
- Does not move the virutal cursor.
-
- Args:
- x (int): Distance to scroll along the x-axis.
- y (int): Distance to scroll along the y-axis.
-
- Returns:
- Iterator[Tuple[int, int]]: An iterator over the (x, y) coordinates
- of any tile uncovered after scrolling.
-
- .. seealso:: :any:`set_colors`
- """
- assert isinstance(x, _INTTYPES), "x must be an integer, got %s" % repr(x)
- assert isinstance(y, _INTTYPES), "y must be an integer, got %s" % repr(x)
- def getSlide(x, length):
- """get the parameters needed to scroll the console in the given
- direction with x
- returns (x, length, srcx)
- """
- if x > 0:
- srcx = 0
- length -= x
- elif x < 0:
- srcx = abs(x)
- x = 0
- length -= srcx
- else:
- srcx = 0
- return x, length, srcx
- def getCover(x, length):
- """return the (x, width) ranges of what is covered and uncovered"""
- cover = (0, length) # everything covered
- uncover = None # nothing uncovered
- if x > 0: # left side uncovered
- cover = (x, length - x)
- uncover = (0, x)
- elif x < 0: # right side uncovered
- x = abs(x)
- cover = (0, length - x)
- uncover = (length - x, x)
- return cover, uncover
-
- width, height = self.get_size()
- if abs(x) >= width or abs(y) >= height:
- return self.clear() # just clear the console normally
-
- # get the ranges of the areas that will be uncovered
- coverX, uncoverX = getCover(x, width)
- coverY, uncoverY = getCover(y, height)
- # so at this point we know that coverX and coverY makes a rect that
- # encases the area that we end up blitting to. uncoverX/Y makes a
- # rect in the corner of the uncovered area. So we need to combine
- # the uncoverX/Y with coverY/X to make what's left of the uncovered
- # area. Explaining it makes it mush easier to do now.
-
- # But first we need to blit.
- x, width, srcx = getSlide(x, width)
- y, height, srcy = getSlide(y, height)
- self.blit(self, x, y, width, height, srcx, srcy)
- if uncoverX: # clear sides (0x20 is space)
- self.draw_rect(uncoverX[0], coverY[0], uncoverX[1], coverY[1],
- 0x20, self._fg, self._bg)
- if uncoverY: # clear top/bottom
- self.draw_rect(coverX[0], uncoverY[0], coverX[1], uncoverY[1],
- 0x20, self._fg, self._bg)
- if uncoverX and uncoverY: # clear corner
- self.draw_rect(uncoverX[0], uncoverY[0], uncoverX[1], uncoverY[1],
- 0x20, self._fg, self._bg)
-
- def clear(self, fg=Ellipsis, bg=Ellipsis):
- """Clears the entire L{Console}/L{Window}.
-
- Unlike other drawing functions, fg and bg can not be None.
-
- Args:
- fg (Union[Tuple[int, int, int], int, Ellipsis])
- bg (Union[Tuple[int, int, int], int, Ellipsis])
-
- .. seealso:: :any:`draw_rect`
- """
- raise NotImplementedError('this method is overwritten by subclasses')
-
- def get_char(self, x, y):
- """Return the character and colors of a tile as (ch, fg, bg)
-
- This method runs very slowly as is not recommended to be called
- frequently.
-
- Args:
- x (int): The x-coordinate to pick.
- y (int): The y-coordinate to pick.
-
- Returns:
- Tuple[int, Tuple[int, int, int], Tuple[int, int, int]]: A 3-item
- tuple: `(int, fg, bg)`
-
- The first item is an integer of the
- character at the position (x, y) the second and third are the
- foreground and background colors respectfully.
-
- .. seealso:: :any:`draw_char`
- """
- raise NotImplementedError('Method here only exists for the docstring')
-
- def __contains__(self, position):
- """Use ``((x, y) in console)`` to check if a position is drawable on
- this console.
- """
- x, y = position
- return (0 <= x < self.width) and (0 <= y < self.height)
-
-class Console(_BaseConsole):
- """Contains character and color data and can be drawn to.
-
- The console created by the :any:`tdl.init` function is the root console
- and is the console that is rendered to the screen with :any:`flush`.
-
- Any console created from the Console class is an off-screen console that
- can be drawn on before being :any:`blit` to the root console.
-
- Args:
- width (int): Width of the new console in tiles
- height (int): Height of the new console in tiles
- """
-
- def __init__(self, width, height):
- _BaseConsole.__init__(self)
- self.console_c = _lib.TCOD_console_new(width, height)
- self.console = self
- self.width = width
- self.height = height
-
- @property
- def tcod_console(self):
- return self.console_c
- @tcod_console.setter
- def tcod_console(self, value):
- self.console_c = value
-
- @classmethod
- def _newConsole(cls, console):
- """Make a Console instance, from a console ctype"""
- self = cls.__new__(cls)
- _BaseConsole.__init__(self)
- self.console_c = console
- self.console = self
- self.width = _lib.TCOD_console_get_width(console)
- self.height = _lib.TCOD_console_get_height(console)
- return self
-
- def _root_unhook(self):
- """Change this root console into a normal Console object and
- delete the root console from TCOD
- """
- global _rootinitialized, _rootConsoleRef
- # do we recognise this as the root console?
- # if not then assume the console has already been taken care of
- if(_rootConsoleRef and _rootConsoleRef() is self):
- # turn this console into a regular console
- unhooked = _lib.TCOD_console_new(self.width, self.height)
- _lib.TCOD_console_blit(self.console_c,
- 0, 0, self.width, self.height,
- unhooked, 0, 0, 1, 1)
- # delete root console from TDL and TCOD
- _rootinitialized = False
- _rootConsoleRef = None
- _lib.TCOD_console_delete(self.console_c)
- # this Console object is now a regular console
- self.console_c = unhooked
-
- def __del__(self):
- """
- If the main console is garbage collected then the window will be closed as well
- """
- if self.console_c is None:
- return # this console was already deleted
- if self.console_c is _ffi.NULL:
- # a pointer to the special root console
- self._root_unhook() # unhook the console and leave it to the GC
- return
- # this is a normal console pointer and can be safely deleted
- _lib.TCOD_console_delete(self.console_c)
- self.console_c = None
-
- def __copy__(self):
- # make a new class and blit
- clone = self.__class__(self.width, self.height)
- clone.blit(self)
- return clone
-
- def __getstate__(self):
- # save data from get_char
- data = [self.get_char(x, y) for x,y in
- _itertools.product(range(self.width), range(self.height))]
- return self.width, self.height, data
-
- def __setstate__(self, state):
- # make console from __init__ and unpack a get_char array
- width, height, data = state
- self.__init__(width, height)
- for (x, y), graphic in zip(_itertools.product(range(width),
- range(height)), data):
- self.draw_char(x, y, *graphic)
-
- @staticmethod
- def _translate(x, y):
- """Convertion x and y to their position on the root Console for this Window
-
- Because this is a Console instead of a Window we return the paramaters
- untouched"""
- return x, y
-
- def clear(self, fg=Ellipsis, bg=Ellipsis):
- # inherit docstring
- assert fg is not None and bg is not None, 'Can not use None with clear'
- fg = _format_color(fg, self._fg)
- bg = _format_color(bg, self._bg)
- _lib.TCOD_console_set_default_foreground(self.console_c,
- _to_tcod_color(fg)[0])
- _lib.TCOD_console_set_default_background(self.console_c,
- _to_tcod_color(bg)[0])
- _lib.TCOD_console_clear(self.console_c)
-
-
- def _set_char(self, x, y, char, fg=None, bg=None,
- bgblend=_lib.TCOD_BKGND_SET):
- """
- Sets a character.
- This is called often and is designed to be as fast as possible.
-
- Because of the need for speed this function will do NO TYPE CHECKING
- AT ALL, it's up to the drawing functions to use the functions:
- _format_char and _format_color before passing to this."""
- # values are already formatted, honestly this function is redundant
- return _put_char_ex(self.console_c, x, y, char, fg, bg, bgblend)
-
- def _set_batch(self, batch, fg, bg, bgblend=1, nullChar=False):
- """
- Try to perform a batch operation otherwise fall back to _set_char.
- If fg and bg are defined then this is faster but not by very
- much.
-
- if any character is None then nullChar is True
-
- batch is a iterable of [(x, y), ch] items
- """
- for (x, y), char in batch:
- self._set_char(x, y, char, fg, bg, bgblend)
-
- def get_char(self, x, y):
- # inherit docstring
- x, y = self._normalizePoint(x, y)
- char = _lib.TCOD_console_get_char(self.console_c, x, y)
- bg = _lib.TCOD_console_get_char_background(self.console_c, x, y)
- fg = _lib.TCOD_console_get_char_foreground(self.console_c, x, y)
- return char, (fg.r, fg.g, fg.b), (bg.r, bg.g, bg.b)
-
- # Copy docstrings for Sphinx
- clear.__doc__ = _BaseConsole.clear.__doc__
- get_char.__doc__ = _BaseConsole.get_char.__doc__
-
- def __repr__(self):
- return "" % (self.width, self.height)
-
-
-class Window(_BaseConsole):
- """Isolate part of a :any:`Console` or :any:`Window` instance.
-
- This classes methods are the same as :any:`tdl.Console`
-
- Making a Window and setting its width or height to None will extend it to
- the edge of the console.
-
- This follows the normal rules for indexing so you can use a
- negative integer to place the Window relative to the bottom
- right of the parent Console instance.
-
- `width` or `height` can be set to None to extend as far as possible
- to the bottom right corner of the parent Console or can be a
- negative number to be sized reltive to the Consoles total size.
-
- Args:
- console (Union(tdl.Console, tdl.Window)): The parent object.
- x (int): x-coordinate to place the Window.
- y (int): y-coordinate to place the Window.
- width (Optional[int]): Width of the Window.
- height (Optional[int]): Height of the Window.
- """
-
- __slots__ = ('parent', 'x', 'y')
-
- def __init__(self, console, x, y, width, height):
- _BaseConsole.__init__(self)
- assert isinstance(console, (Console, Window)), 'console parameter must be a Console or Window instance, got %s' % repr(console)
- self.parent = console
- self.x, self.y, self.width, self.height = console._normalizeRect(x, y, width, height)
- if isinstance(console, Console):
- self.console = console
- else:
- self.console = self.parent.console
-
- def _translate(self, x, y):
- """Convertion x and y to their position on the root Console"""
- # we add our position relative to our parent and then call then next parent up
- return self.parent._translate((x + self.x), (y + self.y))
-
- def clear(self, fg=Ellipsis, bg=Ellipsis):
- # inherit docstring
- assert fg is not None and bg is not None, 'Can not use None with clear'
- if fg is Ellipsis:
- fg = self._fg
- if bg is Ellipsis:
- bg = self._bg
- self.draw_rect(0, 0, None, None, 0x20, fg, bg)
-
- def _set_char(self, x, y, char=None, fg=None, bg=None, bgblend=1):
- self.parent._set_char((x + self.x), (y + self.y), char, fg, bg, bgblend)
-
- def _set_batch(self, batch, *args, **kargs):
- # positional values will need to be translated to the parent console
- myX = self.x # remove dots for speed up
- myY = self.y
- self.parent._set_batch((((x + myX, y + myY), ch)
- for ((x, y), ch) in batch), *args, **kargs)
-
-
- def draw_char(self, x, y, char, fg=Ellipsis, bg=Ellipsis):
- # inherit docstring
- x, y = self._normalizePoint(x, y)
- if fg is Ellipsis:
- fg = self._fg
- if bg is Ellipsis:
- bg = self._bg
- self.parent.draw_char(x + self.x, y + self.y, char, fg, bg)
-
- def draw_rect(self, x, y, width, height, string, fg=Ellipsis, bg=Ellipsis):
- # inherit docstring
- x, y, width, height = self._normalizeRect(x, y, width, height)
- if fg is Ellipsis:
- fg = self._fg
- if bg is Ellipsis:
- bg = self._bg
- self.parent.draw_rect(x + self.x, y + self.y, width, height,
- string, fg, bg)
-
- def draw_frame(self, x, y, width, height, string, fg=Ellipsis, bg=Ellipsis):
- # inherit docstring
- x, y, width, height = self._normalizeRect(x, y, width, height)
- if fg is Ellipsis:
- fg = self._fg
- if bg is Ellipsis:
- bg = self._bg
- self.parent.draw_frame(x + self.x, y + self.y, width, height,
- string, fg, bg)
-
- def get_char(self, x, y):
- # inherit docstring
- x, y = self._normalizePoint(x, y)
- xtrans, ytrans = self._translate(x, y)
- return self.console.get_char(xtrans, ytrans)
-
- def __repr__(self):
- return "" % (self.x, self.y,
- self.width,
- self.height)
-
-
-def init(width, height, title=None, fullscreen=False, renderer='SDL'):
- """Start the main console with the given width and height and return the
- root console.
-
- Call the consoles drawing functions. Then remember to use L{tdl.flush} to
- make what's drawn visible on the console.
-
- Args:
- width (int): width of the root console (in tiles)
- height (int): height of the root console (in tiles)
- title (Optiona[Text]):
- Text to display as the window title.
-
- If left None it defaults to the running scripts filename.
- fullscreen (bool): Can be set to True to start in fullscreen mode.
- renderer (Text): Can be one of 'GLSL', 'OPENGL', or 'SDL'.
-
- Due to way Python works you're unlikely to see much of an
- improvement by using 'GLSL' over 'OPENGL' as most of the
- time Python is slow interacting with the console and the
- rendering itself is pretty fast even on 'SDL'.
-
- Returns:
- tdl.Console: The root console.
-
- Only what is drawn on the root console is
- what's visible after a call to :any:`tdl.flush`.
- After the root console is garbage collected, the window made by
- this function will close.
-
- .. seealso::
- :any:`Console` :any:`set_font`
- """
- RENDERERS = {'GLSL': 0, 'OPENGL': 1, 'SDL': 2}
- global _rootinitialized, _rootConsoleRef
- if not _fontinitialized: # set the default font to the one that comes with tdl
- set_font(_os.path.join(__path__[0], 'terminal8x8.png'),
- None, None, True, True)
-
- if renderer.upper() not in RENDERERS:
- raise TDLError('No such render type "%s", expected one of "%s"' % (renderer, '", "'.join(RENDERERS)))
- renderer = RENDERERS[renderer.upper()]
-
- # If a console already exists then make a clone to replace it
- if _rootConsoleRef and _rootConsoleRef():
- # unhook the root console, turning into a regular console and deleting
- # the root console from libTCOD
- _rootConsoleRef()._root_unhook()
-
- if title is None: # use a default title
- if _sys.argv:
- # Use the script filename as the title.
- title = _os.path.basename(_sys.argv[0])
- else:
- title = 'python-tdl'
-
- _lib.TCOD_console_init_root(width, height, _encodeString(title), fullscreen, renderer)
-
- #event.get() # flush the libtcod event queue to fix some issues
- # issues may be fixed already
-
- event._eventsflushed = False
- _rootinitialized = True
- rootconsole = Console._newConsole(_ffi.NULL)
- _rootConsoleRef = _weakref.ref(rootconsole)
-
- return rootconsole
-
-def flush():
- """Make all changes visible and update the screen.
-
- Remember to call this function after drawing operations.
- Calls to flush will enfore the frame rate limit set by :any:`tdl.set_fps`.
-
- This function can only be called after :any:`tdl.init`
- """
- if not _rootinitialized:
- raise TDLError('Cannot flush without first initializing with tdl.init')
- # flush the OS event queue, preventing lock-ups if not done manually
- event.get()
- _lib.TCOD_console_flush()
-
-def set_font(path, columns=None, rows=None, columnFirst=False,
- greyscale=False, altLayout=False):
- """Changes the font to be used for this session.
- This should be called before :any:`tdl.init`
-
- If the font specifies its size in its filename (i.e. font_NxN.png) then this
- function can auto-detect the tileset formatting and the parameters columns
- and rows can be left None.
-
- While it's possible you can change the font mid program it can sometimes
- break in rare circumstances. So use caution when doing this.
-
- Args:
- path (Text): A file path to a `.bmp` or `.png` file.
- columns (int):
- Number of columns in the tileset.
-
- Can be left None for auto-detection.
- rows (int):
- Number of rows in the tileset.
-
- Can be left None for auto-detection.
- columnFirst (bool):
- Defines if the characer order goes along the rows or colomns.
-
- It should be True if the charater codes 0-15 are in the
- first column, and should be False if the characters 0-15
- are in the first row.
- greyscale (bool):
- Creates an anti-aliased font from a greyscale bitmap.
- Otherwise it uses the alpha channel for anti-aliasing.
-
- Unless you actually need anti-aliasing from a font you
- know uses a smooth greyscale channel you should leave
- this on False.
- altLayout (bool)
- An alternative layout with space in the upper left corner.
- The colomn parameter is ignored if this is True,
- find examples of this layout in the `font/libtcod/`
- directory included with the python-tdl source.
-
- Raises:
- TDLError: Will be raised if no file is found at path or if auto-
- detection fails.
- """
- # put up some constants that are only used here
- FONT_LAYOUT_ASCII_INCOL = 1
- FONT_LAYOUT_ASCII_INROW = 2
- FONT_TYPE_GREYSCALE = 4
- FONT_LAYOUT_TCOD = 8
- global _fontinitialized
- _fontinitialized = True
- flags = 0
- if altLayout:
- flags |= FONT_LAYOUT_TCOD
- elif columnFirst:
- flags |= FONT_LAYOUT_ASCII_INCOL
- else:
- flags |= FONT_LAYOUT_ASCII_INROW
- if greyscale:
- flags |= FONT_TYPE_GREYSCALE
- if not _os.path.exists(path):
- raise TDLError('Font %r not found.' % _os.path.abspath(path))
- path = _os.path.abspath(path)
-
- # and the rest is the auto-detect script
- imgSize = _getImageSize(path) # try to find image size
- if imgSize:
- fontWidth, fontHeight = None, None
- imgWidth, imgHeight = imgSize
- # try to get font size from filename
- match = _re.match('.*?([0-9]+)[xX]([0-9]+)', _os.path.basename(path))
- if match:
- fontWidth, fontHeight = match.groups()
- fontWidth, fontHeight = int(fontWidth), int(fontHeight)
-
- # estimate correct tileset size
- estColumns, remC = divmod(imgWidth, fontWidth)
- estRows, remR = divmod(imgHeight, fontHeight)
- if remC or remR:
- _warnings.warn("Font may be incorrectly formatted.")
-
- if not columns:
- columns = estColumns
- if not rows:
- rows = estRows
- else:
- # filename doesn't contain NxN, but we can still estimate the fontWidth
- # and fontHeight given number of columns and rows.
- if columns and rows:
- fontWidth, remC = divmod(imgWidth, columns)
- fontHeight, remR = divmod(imgHeight, rows)
- if remC or remR:
- _warnings.warn("Font may be incorrectly formatted.")
-
- # the font name excluded the fonts size
- if not (columns and rows):
- # no matched font size and no tileset is given
- raise TDLError('%s has no font size in filename' % _os.path.basename(path))
-
- if columns and rows:
- # confirm user set options
- if (fontWidth * columns != imgWidth or
- fontHeight * rows != imgHeight):
- _warnings.warn("set_font parameters are set as if the image size is (%d, %d) when the detected size is actually (%i, %i)"
- % (fontWidth * columns, fontHeight * rows,
- imgWidth, imgHeight))
- else:
- _warnings.warn("%s is probably not an image." % _os.path.basename(path))
-
- if not (columns and rows):
- # didn't auto-detect
- raise TDLError('Can not auto-detect the tileset of %s' % _os.path.basename(path))
-
- _lib.TCOD_console_set_custom_font(_encodeString(path), flags, columns, rows)
-
-def get_fullscreen():
- """Returns True if program is fullscreen.
-
- Returns:
- bool: Returns True if the application is in full-screen mode.
- Otherwise returns False.
- """
- if not _rootinitialized:
- raise TDLError('Initialize first with tdl.init')
- return _lib.TCOD_console_is_fullscreen()
-
-def set_fullscreen(fullscreen):
- """Changes the fullscreen state.
-
- Args:
- fullscreen (bool): True for full-screen, False for windowed mode.
- """
- if not _rootinitialized:
- raise TDLError('Initialize first with tdl.init')
- _lib.TCOD_console_set_fullscreen(fullscreen)
-
-def set_title(title):
- """Change the window title.
-
- Args:
- title (Text): The new title text.
- """
- if not _rootinitialized:
- raise TDLError('Not initilized. Set title with tdl.init')
- _lib.TCOD_console_set_window_title(_encodeString(title))
-
-def screenshot(path=None):
- """Capture the screen and save it as a png file.
-
- If path is None then the image will be placed in the current
- folder with the names:
- ``screenshot001.png, screenshot002.png, ...``
-
- Args:
- path (Optional[Text]): The file path to save the screenshot.
- """
- if not _rootinitialized:
- raise TDLError('Initialize first with tdl.init')
- if isinstance(path, str):
- _lib.TCOD_sys_save_screenshot(_encodeString(path))
- elif path is None: # save to screenshot001.png, screenshot002.png, ...
- filelist = _os.listdir('.')
- n = 1
- filename = 'screenshot%.3i.png' % n
- while filename in filelist:
- n += 1
- filename = 'screenshot%.3i.png' % n
- _lib.TCOD_sys_save_screenshot(_encodeString(filename))
- else: # assume file like obj
- #save to temp file and copy to file-like obj
- tmpname = _os.tempnam()
- _lib.TCOD_sys_save_screenshot(_encodeString(tmpname))
- with tmpname as tmpfile:
- path.write(tmpfile.read())
- _os.remove(tmpname)
- #else:
- # raise TypeError('path is an invalid type: %s' % type(path))
-
-def set_fps(fps):
- """Set the maximum frame rate.
-
- Further calls to :any:`tdl.flush` will limit the speed of
- the program to run at `fps` frames per second. This can
- also be set to None to remove the frame rate limit.
-
- Args:
- fps (optional[int]): The frames per second limit, or None.
- """
- _lib.TCOD_sys_set_fps(fps or 0)
-
-def get_fps():
- """Return the current frames per second of the running program set by
- :any:`set_fps`
-
- Returns:
- int: The frame rate set by :any:`set_fps`.
- If there is no current limit, this will return 0.
- """
- return _lib.TCOD_sys_get_fps()
-
-def force_resolution(width, height):
- """Change the fullscreen resoulution.
-
- Args:
- width (int): Width in pixels.
- height (int): Height in pixels.
- """
- _lib.TCOD_sys_force_fullscreen_resolution(width, height)
-
-
-__all__ = [_var for _var in locals().keys() if _var[0] != '_'] # remove modules from __all__
-__all__ += ['_BaseConsole'] # keep this object public to show the documentation in epydoc
-__all__.remove('absolute_import')
-__all__.remove('division')
-__all__.remove('print_function')
-__all__.remove('unicode_literals')
-
-# backported function names
-_BaseConsole.setMode = _style.backport(_BaseConsole.set_mode)
-_BaseConsole.setColors = _style.backport(_BaseConsole.set_colors)
-_BaseConsole.printStr = _style.backport(_BaseConsole.print_str)
-_BaseConsole.drawChar = _style.backport(_BaseConsole.draw_char)
-_BaseConsole.drawStr = _style.backport(_BaseConsole.draw_str)
-_BaseConsole.drawRect = _style.backport(_BaseConsole.draw_rect)
-_BaseConsole.drawFrame = _style.backport(_BaseConsole.draw_frame)
-_BaseConsole.getCursor = _style.backport(_BaseConsole.get_cursor)
-_BaseConsole.getSize = _style.backport(_BaseConsole.get_size)
-_BaseConsole.getChar = _style.backport(_BaseConsole.get_char)
-
-Console.getChar = _style.backport(Console.get_char)
-
-Window.drawChar = _style.backport(Window.draw_char)
-Window.drawRect = _style.backport(Window.draw_rect)
-Window.drawFrame = _style.backport(Window.draw_frame)
-Window.getChar = _style.backport(Window.get_char)
-
-setFont = _style.backport(set_font)
-getFullscreen = _style.backport(get_fullscreen)
-setFullscreen = _style.backport(set_fullscreen)
-setTitle = _style.backport(set_title)
-setFPS = _style.backport(set_fps)
-getFPS = _style.backport(get_fps)
-forceResolution = _style.backport(force_resolution)
diff --git a/tdl/event.py b/tdl/event.py
deleted file mode 100755
index 82f8aa48..00000000
--- a/tdl/event.py
+++ /dev/null
@@ -1,511 +0,0 @@
-"""
- This module handles user input.
-
- To handle user input you will likely want to use the :any:`event.get`
- function or create a subclass of :any:`event.App`.
-
- - :any:`tdl.event.get` iterates over recent events.
- - :any:`tdl.event.App` passes events to the overridable methods: ``ev_*``
- and ``key_*``.
-
- But there are other options such as :any:`event.key_wait` and
- :any:`event.is_window_closed`.
-
- A few event attributes are actually string constants.
- Here's a reference for those:
-
- - :any:`Event.type`:
- 'QUIT', 'KEYDOWN', 'KEYUP', 'MOUSEDOWN', 'MOUSEUP', or 'MOUSEMOTION.'
- - :any:`MouseButtonEvent.button` (found in :any:`MouseDown` and
- :any:`MouseUp` events):
- 'LEFT', 'MIDDLE', 'RIGHT', 'SCROLLUP', 'SCROLLDOWN'
- - :any:`KeyEvent.key` (found in :any:`KeyDown` and :any:`KeyUp` events):
- 'NONE', 'ESCAPE', 'BACKSPACE', 'TAB', 'ENTER', 'SHIFT', 'CONTROL',
- 'ALT', 'PAUSE', 'CAPSLOCK', 'PAGEUP', 'PAGEDOWN', 'END', 'HOME', 'UP',
- 'LEFT', 'RIGHT', 'DOWN', 'PRINTSCREEN', 'INSERT', 'DELETE', 'LWIN',
- 'RWIN', 'APPS', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
- 'KP0', 'KP1', 'KP2', 'KP3', 'KP4', 'KP5', 'KP6', 'KP7', 'KP8', 'KP9',
- 'KPADD', 'KPSUB', 'KPDIV', 'KPMUL', 'KPDEC', 'KPENTER', 'F1', 'F2',
- 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12',
- 'NUMLOCK', 'SCROLLLOCK', 'SPACE', 'CHAR'
-"""
-
-import time as _time
-
-from tcod import ffi as _ffi
-from tcod import lib as _lib
-
-import tdl as _tdl
-from . import style as _style
-
-_eventQueue = []
-_pushedEvents = []
-_eventsflushed = False
-
-_mousel = 0
-_mousem = 0
-_mouser = 0
-
-# this interprets the constants from libtcod and makes a key -> keyname dictionary
-def _parseKeyNames(lib):
- """
- returns a dictionary mapping of human readable key names to their keycodes
- this parses constants with the names of K_* and makes code=name pairs
- this is for KeyEvent.key variable and that enables things like:
- if (event.key == 'PAGEUP'):
- """
- _keyNames = {}
- for attr in dir(lib): # from the modules variables
- if attr[:6] == 'TCODK_': # get the K_* constants
- _keyNames[getattr(lib, attr)] = attr[6:] # and make CODE=NAME pairs
- return _keyNames
-
-_keyNames = _parseKeyNames(_lib)
-
-class Event(object):
- """Base Event class.
-
- You can easily subclass this to make your own events. Be sure to set
- the class attribute L{Event.type} for it to be passed to a custom
- :any:`App` ev_* method.
- """
- type = None
- """String constant representing the type of event.
-
- The :any:`App` ev_* methods depend on this attribute.
-
- Can be: 'QUIT', 'KEYDOWN', 'KEYUP', 'MOUSEDOWN', 'MOUSEUP',
- or 'MOUSEMOTION.'
- """
-
- def __repr__(self):
- """List an events public attributes when printed.
- """
- attrdict = {}
- for varname in dir(self):
- if '_' == varname[0]:
- continue
- attrdict[varname] = self.__getattribute__(varname)
- return '%s Event %s' % (self.__class__.__name__, repr(attrdict))
-
-class Quit(Event):
- """Fired when the window is closed by the user.
- """
- __slots__ = ()
- type = 'QUIT'
-
-class KeyEvent(Event):
- """Base class for key events."""
-
- def __init__(self, key='', char='', text='', shift=False,
- left_alt=False, right_alt=False,
- left_control=False, right_control=False,
- left_meta=False, right_meta=False):
- # Convert keycodes into string, but use string if passed
- self.key = key if isinstance(key, str) else _keyNames[key]
- """Text: Human readable names of the key pressed.
- Non special characters will show up as 'CHAR'.
-
- Can be one of
- 'NONE', 'ESCAPE', 'BACKSPACE', 'TAB', 'ENTER', 'SHIFT', 'CONTROL',
- 'ALT', 'PAUSE', 'CAPSLOCK', 'PAGEUP', 'PAGEDOWN', 'END', 'HOME', 'UP',
- 'LEFT', 'RIGHT', 'DOWN', 'PRINTSCREEN', 'INSERT', 'DELETE', 'LWIN',
- 'RWIN', 'APPS', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
- 'KP0', 'KP1', 'KP2', 'KP3', 'KP4', 'KP5', 'KP6', 'KP7', 'KP8', 'KP9',
- 'KPADD', 'KPSUB', 'KPDIV', 'KPMUL', 'KPDEC', 'KPENTER', 'F1', 'F2',
- 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12',
- 'NUMLOCK', 'SCROLLLOCK', 'SPACE', 'CHAR'
-
- For the actual character instead of 'CHAR' use :any:`keychar`.
- """
- self.char = char.replace('\x00', '') # change null to empty string
- """Text: A single character string of the letter or symbol pressed.
-
- Special characters like delete and return are not cross-platform.
- L{key} or L{keychar} should be used instead for special keys.
- Characters are also case sensitive.
- """
- # get the best out of self.key and self.char
- self.keychar = self.char if self.key == 'CHAR' else self.key
- """Similar to L{key} but returns a case sensitive letter or symbol
- instead of 'CHAR'.
-
- This variable makes available the widest variety of symbols and should
- be used for key-mappings or anywhere where a narrower sample of keys
- isn't needed.
- """
- self.text = text
-
- self.left_alt = self.leftAlt = bool(left_alt)
- """bool:"""
- self.right_alt = self.rightAlt = bool(right_alt)
- """bool:"""
- self.left_control = self.leftCtrl = bool(left_control)
- """bool:"""
- self.right_control = self.rightCtrl = bool(right_control)
- """bool:"""
- self.shift = bool(shift)
- """bool: True if shift was held down during this event."""
- self.alt = self.left_alt or self.right_alt
- """bool: True if alt was held down during this event."""
- self.control = self.left_control or self.right_control
- """bool: True if control was held down during this event."""
- self.left_meta = bool(left_meta)
- self.right_meta = bool(right_meta)
- self.meta = self.left_meta or self.right_meta
-
- def __repr__(self):
- parameters = []
- for attr in ('key', 'char', 'text', 'shift',
- 'left_alt', 'right_alt',
- 'left_control', 'right_control',
- 'left_meta', 'right_meta'):
- value = getattr(self, attr)
- if value:
- parameters.append('%s=%r' % (attr, value))
- return '%s(%s)' % (self.__class__.__name__, ', '.join(parameters))
-
-class KeyDown(KeyEvent):
- """Fired when the user presses a key on the keyboard or a key repeats.
- """
- type = 'KEYDOWN'
-
-class KeyUp(KeyEvent):
- """Fired when the user releases a key on the keyboard.
- """
- type = 'KEYUP'
-
-_mouseNames = {1: 'LEFT', 2: 'MIDDLE', 3: 'RIGHT', 4: 'SCROLLUP', 5: 'SCROLLDOWN'}
-class MouseButtonEvent(Event):
- """Base class for mouse button events."""
-
- def __init__(self, button, pos, cell):
- self.button = _mouseNames[button]
- """Text: Can be one of
- 'LEFT', 'MIDDLE', 'RIGHT', 'SCROLLUP', 'SCROLLDOWN'
- """
- self.pos = pos
- """Tuple[int, int]: (x, y) position of the mouse on the screen."""
- self.cell = cell
- """Tuple[int, int]: (x, y) position of the mouse snapped to a cell on
- the root console
- """
-
-class MouseDown(MouseButtonEvent):
- """Fired when a mouse button is pressed."""
- __slots__ = ()
- type = 'MOUSEDOWN'
-
-class MouseUp(MouseButtonEvent):
- """Fired when a mouse button is released."""
- __slots__ = ()
- type = 'MOUSEUP'
-
-class MouseMotion(Event):
- """Fired when the mouse is moved."""
- type = 'MOUSEMOTION'
-
- def __init__(self, pos, cell, motion, cellmotion):
- self.pos = pos
- """(x, y) position of the mouse on the screen.
- type: (int, int)"""
- self.cell = cell
- """(x, y) position of the mouse snapped to a cell on the root console.
- type: (int, int)"""
- self.motion = motion
- """(x, y) motion of the mouse on the screen.
- type: (int, int)"""
- self.cellmotion = cellmotion
- """(x, y) mostion of the mouse moving over cells on the root console.
- type: (int, int)"""
-
-class App(object):
- """
- Application framework.
-
- - ev_*: Events are passed to methods based on their :any:`Event.type`
- attribute.
- If an event type is 'KEYDOWN' the ev_KEYDOWN method will be called
- with the event instance as a parameter.
-
- - key_*: When a key is pressed another method will be called based on the
- :any:`KeyEvent.key` attribute. For example the 'ENTER' key will call
- key_ENTER with the associated :any:`KeyDown` event as its parameter.
-
- - :any:`update`: This method is called every loop. It is passed a single
- parameter detailing the time in seconds since the last update
- (often known as deltaTime.)
-
- You may want to call drawing routines in this method followed by
- :any:`tdl.flush`.
-
- """
- __slots__ = ('__running', '__prevTime')
-
- def ev_QUIT(self, event):
- """Unless overridden this method raises a SystemExit exception closing
- the program."""
- raise SystemExit()
-
- def ev_KEYDOWN(self, event):
- """Override this method to handle a :any:`KeyDown` event."""
-
- def ev_KEYUP(self, event):
- """Override this method to handle a :any:`KeyUp` event."""
-
- def ev_MOUSEDOWN(self, event):
- """Override this method to handle a :any:`MouseDown` event."""
-
- def ev_MOUSEUP(self, event):
- """Override this method to handle a :any:`MouseUp` event."""
-
- def ev_MOUSEMOTION(self, event):
- """Override this method to handle a :any:`MouseMotion` event."""
-
- def update(self, deltaTime):
- """Override this method to handle per frame logic and drawing.
-
- Args:
- deltaTime (float):
- This parameter tells the amount of time passed since
- the last call measured in seconds as a floating point
- number.
-
- You can use this variable to make your program
- frame rate independent.
- Use this parameter to adjust the speed of motion,
- timers, and other game logic.
- """
- pass
-
- def suspend(self):
- """When called the App will begin to return control to where
- :any:`App.run` was called.
-
- Some further events are processed and the :any:`App.update` method
- will be called one last time before exiting
- (unless suspended during a call to :any:`App.update`.)
- """
- self.__running = False
-
- def run(self):
- """Delegate control over to this App instance. This function will
- process all events and send them to the special methods ev_* and key_*.
-
- A call to :any:`App.suspend` will return the control flow back to where
- this function is called. And then the App can be run again.
- But a single App instance can not be run multiple times simultaneously.
- """
- if getattr(self, '_App__running', False):
- raise _tdl.TDLError('An App can not be run multiple times simultaneously')
- self.__running = True
- while self.__running:
- self.runOnce()
-
- def run_once(self):
- """Pump events to this App instance and then return.
-
- This works in the way described in :any:`App.run` except it immediately
- returns after the first :any:`update` call.
-
- Having multiple :any:`App` instances and selectively calling runOnce on
- them is a decent way to create a state machine.
- """
- if not hasattr(self, '_App__prevTime'):
- self.__prevTime = _time.clock() # initiate __prevTime
- for event in get():
- if event.type: # exclude custom events with a blank type variable
- # call the ev_* methods
- method = 'ev_%s' % event.type # ev_TYPE
- getattr(self, method)(event)
- if event.type == 'KEYDOWN':
- # call the key_* methods
- method = 'key_%s' % event.key # key_KEYNAME
- if hasattr(self, method): # silently exclude undefined methods
- getattr(self, method)(event)
- newTime = _time.clock()
- self.update(newTime - self.__prevTime)
- self.__prevTime = newTime
- #_tdl.flush()
-
-def _processEvents():
- """Flushes the event queue from libtcod into the global list _eventQueue"""
- global _mousel, _mousem, _mouser, _eventsflushed, _pushedEvents
- _eventsflushed = True
- events = _pushedEvents # get events from event.push
- _pushedEvents = [] # then clear the pushed events queue
-
- mouse = _ffi.new('TCOD_mouse_t *')
- libkey = _ffi.new('TCOD_key_t *')
- while 1:
- libevent = _lib.TCOD_sys_check_for_event(_lib.TCOD_EVENT_ANY, libkey, mouse)
- if not libevent: # no more events from libtcod
- break
-
- #if mouse.dx or mouse.dy:
- if libevent & _lib.TCOD_EVENT_MOUSE_MOVE:
- events.append(MouseMotion((mouse.x, mouse.y),
- (mouse.cx, mouse.cy),
- (mouse.dx, mouse.dy),
- (mouse.dcx, mouse.dcy)))
-
- mousepos = ((mouse.x, mouse.y), (mouse.cx, mouse.cy))
-
- for oldstate, newstate, released, button in \
- zip((_mousel, _mousem, _mouser),
- (mouse.lbutton, mouse.mbutton, mouse.rbutton),
- (mouse.lbutton_pressed, mouse.mbutton_pressed,
- mouse.rbutton_pressed),
- (1, 2, 3)):
- if released:
- if not oldstate:
- events.append(MouseDown(button, *mousepos))
- events.append(MouseUp(button, *mousepos))
- if newstate:
- events.append(MouseDown(button, *mousepos))
- elif newstate and not oldstate:
- events.append(MouseDown(button, *mousepos))
-
- if mouse.wheel_up:
- events.append(MouseDown(4, *mousepos))
- if mouse.wheel_down:
- events.append(MouseDown(5, *mousepos))
-
- _mousel = mouse.lbutton
- _mousem = mouse.mbutton
- _mouser = mouse.rbutton
-
- if libkey.vk == _lib.TCODK_NONE:
- break
- if libkey.pressed:
- keyevent = KeyDown
- else:
- keyevent = KeyUp
-
- events.append(
- keyevent(
- libkey.vk,
- libkey.c.decode('ascii', errors='ignore'),
- _ffi.string(libkey.text).decode('utf-8'),
- libkey.shift,
- libkey.lalt,
- libkey.ralt,
- libkey.lctrl,
- libkey.rctrl,
- libkey.lmeta,
- libkey.rmeta,
- )
- )
-
- if _lib.TCOD_console_is_window_closed():
- events.append(Quit())
-
- _eventQueue.extend(events)
-
-def get():
- """Flushes the event queue and returns the list of events.
-
- This function returns :any:`Event` objects that can be identified by their
- type attribute or their class.
-
- Returns: Iterator[Type[Event]]: An iterable of Events or anything
- put in a :any:`push` call.
-
- If the iterator is deleted or otherwise interrupted before finishing
- the excess items are preserved for the next call.
- """
- _processEvents()
- return _event_generator()
-
-def _event_generator():
- while _eventQueue:
- # if there is an interruption the rest of the events stay untouched
- # this means you can break out of a event.get loop without losing
- # the leftover events
- yield(_eventQueue.pop(0))
-
-
-def wait(timeout=None, flush=True):
- """Wait for an event.
-
- Args:
- timeout (Optional[int]): The time in seconds that this function will
- wait before giving up and returning None.
-
- With the default value of None, this will block forever.
- flush (bool): If True a call to :any:`tdl.flush` will be made before
- listening for events.
-
- Returns: Type[Event]: An event, or None if the function
- has timed out.
- Anything added via :any:`push` will also be returned.
- """
- if timeout is not None:
- timeout = timeout + _time.clock() # timeout at this time
- while True:
- if _eventQueue:
- return _eventQueue.pop(0)
- if flush:
- # a full 'round' of events need to be processed before flushing
- _tdl.flush()
- if timeout and _time.clock() >= timeout:
- return None # return None on timeout
- _time.sleep(0.001) # sleep 1ms
- _processEvents()
-
-
-def push(event):
- """Push an event into the event buffer.
-
- Args:
- event (Any): This event will be available on the next call to
- :any:`event.get`.
-
- An event pushed in the middle of a :any:`get` will not show until
- the next time :any:`get` called preventing push related
- infinite loops.
-
- This object should at least have a 'type' attribute.
- """
- _pushedEvents.append(event)
-
-def key_wait():
- """Waits until the user presses a key.
- Then returns a :any:`KeyDown` event.
-
- Key events will repeat if held down.
-
- A click to close the window will be converted into an Alt+F4 KeyDown event.
-
- Returns:
- tdl.event.KeyDown: The pressed key.
- """
- while 1:
- for event in get():
- if event.type == 'KEYDOWN':
- return event
- if event.type == 'QUIT':
- # convert QUIT into alt+F4
- return KeyDown('F4', '', True, False, True, False, False)
- _time.sleep(.001)
-
-def set_key_repeat(delay=500, interval=0):
- """Does nothing.
- """
- pass
-
-def is_window_closed():
- """Returns True if the exit button on the window has been clicked and
- stays True afterwards.
-
- Returns: bool:
- """
- return _lib.TCOD_console_is_window_closed()
-
-__all__ = [_var for _var in locals().keys() if _var[0] != '_']
-
-App.runOnce = _style.backport(App.run_once)
-keyWait = _style.backport(key_wait)
-setKeyRepeat = _style.backport(set_key_repeat)
-isWindowClosed = _style.backport(is_window_closed)
-
diff --git a/tdl/map.py b/tdl/map.py
deleted file mode 100644
index d03c2002..00000000
--- a/tdl/map.py
+++ /dev/null
@@ -1,353 +0,0 @@
-"""
- Rogue-like map utilitys such as line-of-sight, field-of-view, and path-finding.
-
- .. deprecated:: 3.2
- The features provided here are better realized in the
- :any:`tcod.map` and :any:`tcod.path` modules.
-"""
-
-from __future__ import absolute_import
-
-import itertools as _itertools
-import math as _math
-
-import numpy as np
-
-from tcod import ffi as _ffi
-from tcod import lib as _lib
-from tcod import ffi, lib
-
-from tcod._internal import deprecate
-import tcod.map
-import tcod.path
-import tdl as _tdl
-from . import style as _style
-
-_FOVTYPES = {'BASIC' : 0, 'DIAMOND': 1, 'SHADOW': 2, 'RESTRICTIVE': 12,
- 'PERMISSIVE': 11}
-
-def _get_fov_type(fov):
- "Return a FOV from a string"
- oldFOV = fov
- fov = str(fov).upper()
- if fov in _FOVTYPES:
- return _FOVTYPES[fov]
- if fov[:10] == 'PERMISSIVE' and fov[10].isdigit() and fov[10] != '9':
- return 4 + int(fov[10])
- raise _tdl.TDLError('No such fov option as %s' % oldFOV)
-
-class Map(tcod.map.Map):
- """Field-of-view and path-finding on stored data.
-
- .. versionchanged:: 4.1
- `transparent`, `walkable`, and `fov` are now numpy boolean arrays.
-
- .. versionchanged:: 4.3
- Added `order` parameter.
-
- .. deprecated:: 3.2
- :any:`tcod.map.Map` should be used instead.
-
- Set map conditions with the walkable and transparency attributes, this
- object can be iterated and checked for containment similar to consoles.
-
- For example, you can set all tiles and transparent and walkable with the
- following code:
-
- Example:
- >>> import tdl.map
- >>> map_ = tdl.map.Map(80, 60)
- >>> map_.transparent[:] = True
- >>> map_.walkable[:] = True
-
- Attributes:
- transparent: Map transparency
-
- Access this attribute with ``map.transparent[x,y]``
-
- Set to True to allow field-of-view rays, False will
- block field-of-view.
-
- Transparent tiles only affect field-of-view.
- walkable: Map accessibility
-
- Access this attribute with ``map.walkable[x,y]``
-
- Set to True to allow path-finding through that tile,
- False will block passage to that tile.
-
- Walkable tiles only affect path-finding.
-
- fov: Map tiles touched by a field-of-view computation.
-
- Access this attribute with ``map.fov[x,y]``
-
- Is True if a the tile is if view, otherwise False.
-
- You can set to this attribute if you want, but you'll typically
- be using it to read the field-of-view of a :any:`compute_fov` call.
- """
-
- def __init__(self, width, height, order='F'):
- super(Map, self).__init__(width, height, order)
-
- def compute_fov(self, x, y, fov='PERMISSIVE', radius=None,
- light_walls=True, sphere=True, cumulative=False):
- """Compute the field-of-view of this Map and return an iterator of the
- points touched.
-
- Args:
- x (int): Point of view, x-coordinate.
- y (int): Point of view, y-coordinate.
- fov (Text): The type of field-of-view to be used.
-
- Available types are:
- 'BASIC', 'DIAMOND', 'SHADOW', 'RESTRICTIVE', 'PERMISSIVE',
- 'PERMISSIVE0', 'PERMISSIVE1', ..., 'PERMISSIVE8'
- radius (Optional[int]): Maximum view distance from the point of
- view.
-
- A value of 0 will give an infinite distance.
- light_walls (bool): Light up walls, or only the floor.
- sphere (bool): If True the lit area will be round instead of
- square.
- cumulative (bool): If True the lit cells will accumulate instead
- of being cleared before the computation.
-
- Returns:
- Iterator[Tuple[int, int]]: An iterator of (x, y) points of tiles
- touched by the field-of-view.
- """
- # refresh cdata
- if radius is None: # infinite radius
- radius = 0
- if cumulative:
- fov_copy = self.fov.copy()
- lib.TCOD_map_compute_fov(
- self.map_c, x, y, radius, light_walls, _get_fov_type(fov))
- if cumulative:
- self.fov[:] |= fov_copy
- return zip(*np.where(self.fov))
-
-
- def compute_path(self, start_x, start_y, dest_x, dest_y,
- diagonal_cost=_math.sqrt(2)):
- """Get the shortest path between two points.
-
- Args:
- start_x (int): Starting x-position.
- start_y (int): Starting y-position.
- dest_x (int): Destination x-position.
- dest_y (int): Destination y-position.
- diagonal_cost (float): Multiplier for diagonal movement.
-
- Can be set to zero to disable diagonal movement entirely.
-
- Returns:
- List[Tuple[int, int]]: The shortest list of points to the
- destination position from the starting position.
-
- The start point is not included in this list.
- """
- return tcod.path.AStar(self, diagonal_cost).get_path(start_x, start_y,
- dest_x, dest_y)
-
- def __iter__(self):
- return _itertools.product(range(self.width), range(self.height))
-
- def __contains__(self, position):
- x, y = position
- return (0 <= x < self.width) and (0 <= y < self.height)
-
-
-
-class AStar(tcod.path.AStar):
- """An A* pathfinder using a callback.
-
- .. deprecated:: 3.2
- See :any:`tcod.path`.
-
- Before crating this instance you should make one of two types of
- callbacks:
-
- - A function that returns the cost to move to (x, y)
- - A function that returns the cost to move between
- (destX, destY, sourceX, sourceY)
-
- If path is blocked the function should return zero or None.
- When using the second type of callback be sure to set advanced=True
-
- Args:
- width (int): Width of the pathfinding area (in tiles.)
- height (int): Height of the pathfinding area (in tiles.)
- callback (Union[Callable[[int, int], float],
- Callable[[int, int, int, int], float]]): A callback
- returning the cost of a tile or edge.
-
- A callback taking parameters depending on the setting
- of 'advanced' and returning the cost of
- movement for an open tile or zero for a
- blocked tile.
- diagnalCost (float): Multiplier for diagonal movement.
-
- Can be set to zero to disable diagonal movement entirely.
- advanced (bool): Give 2 additional parameters to the callback.
-
- A simple callback with 2 positional parameters may not
- provide enough information. Setting this to True will
- call the callback with 2 additional parameters giving
- you both the destination and the source of movement.
-
- When True the callback will need to accept
- (destX, destY, sourceX, sourceY) as parameters.
- Instead of just (destX, destY).
- """
-
- class __DeprecatedEdgeCost(tcod.path.EdgeCostCallback):
- _CALLBACK_P = lib._pycall_path_swap_src_dest
-
- class __DeprecatedNodeCost(tcod.path.EdgeCostCallback):
- _CALLBACK_P = lib._pycall_path_dest_only
-
- def __init__(self, width, height, callback,
- diagnalCost=_math.sqrt(2), advanced=False):
- if advanced:
- cost = self.__DeprecatedEdgeCost(callback, (width, height))
- else:
- cost = self.__DeprecatedNodeCost(callback, (width, height))
- super(AStar, self).__init__(cost, diagnalCost or 0.0)
-
- def get_path(self, origX, origY, destX, destY):
- """
- Get the shortest path from origXY to destXY.
-
- Returns:
- List[Tuple[int, int]]: Returns a list walking the path from orig
- to dest.
-
- This excludes the starting point and includes the destination.
-
- If no path is found then an empty list is returned.
- """
- return super(AStar, self).get_path(origX, origY, destX, destY)
-
-@deprecate("This function is very slow.")
-def quick_fov(x, y, callback, fov='PERMISSIVE', radius=7.5, lightWalls=True,
- sphere=True):
- """All field-of-view functionality in one call.
-
- Before using this call be sure to make a function, lambda, or method that takes 2
- positional parameters and returns True if light can pass through the tile or False
- for light-blocking tiles and for indexes that are out of bounds of the
- dungeon.
-
- This function is 'quick' as in no hassle but can quickly become a very slow
- function call if a large radius is used or the callback provided itself
- isn't optimized.
-
- Always check if the index is in bounds both in the callback and in the
- returned values. These values can go into the negatives as well.
-
- Args:
- x (int): x center of the field-of-view
- y (int): y center of the field-of-view
- callback (Callable[[int, int], bool]):
-
- This should be a function that takes two positional arguments x,y
- and returns True if the tile at that position is transparent
- or False if the tile blocks light or is out of bounds.
- fov (Text): The type of field-of-view to be used.
-
- Available types are:
- 'BASIC', 'DIAMOND', 'SHADOW', 'RESTRICTIVE', 'PERMISSIVE',
- 'PERMISSIVE0', 'PERMISSIVE1', ..., 'PERMISSIVE8'
- radius (float) Radius of the field-of-view.
-
- When sphere is True a floating point can be used to fine-tune
- the range. Otherwise the radius is just rounded up.
-
- Be careful as a large radius has an exponential affect on
- how long this function takes.
- lightWalls (bool): Include or exclude wall tiles in the field-of-view.
- sphere (bool): True for a spherical field-of-view.
- False for a square one.
-
- Returns:
- Set[Tuple[int, int]]: A set of (x, y) points that are within the
- field-of-view.
- """
- trueRadius = radius
- radius = int(_math.ceil(radius))
- mapSize = radius * 2 + 1
- fov = _get_fov_type(fov)
-
- setProp = _lib.TCOD_map_set_properties # make local
- inFOV = _lib.TCOD_map_is_in_fov
-
- tcodMap = _lib.TCOD_map_new(mapSize, mapSize)
- try:
- # pass no.1, write callback data to the tcodMap
- for x_, y_ in _itertools.product(range(mapSize), range(mapSize)):
- pos = (x_ + x - radius,
- y_ + y - radius)
- transparent = bool(callback(*pos))
- setProp(tcodMap, x_, y_, transparent, False)
-
- # pass no.2, compute fov and build a list of points
- _lib.TCOD_map_compute_fov(tcodMap, radius, radius, radius, lightWalls, fov)
- touched = set() # points touched by field of view
- for x_, y_ in _itertools.product(range(mapSize), range(mapSize)):
- if sphere and _math.hypot(x_ - radius, y_ - radius) > trueRadius:
- continue
- if inFOV(tcodMap, x_, y_):
- touched.add((x_ + x - radius, y_ + y - radius))
- finally:
- _lib.TCOD_map_delete(tcodMap)
- return touched
-
-def bresenham(x1, y1, x2, y2):
- """
- Return a list of points in a bresenham line.
-
- Implementation hastily copied from RogueBasin.
-
- Returns:
- List[Tuple[int, int]]: A list of (x, y) points,
- including both the start and end-points.
- """
- points = []
- issteep = abs(y2-y1) > abs(x2-x1)
- if issteep:
- x1, y1 = y1, x1
- x2, y2 = y2, x2
- rev = False
- if x1 > x2:
- x1, x2 = x2, x1
- y1, y2 = y2, y1
- rev = True
- deltax = x2 - x1
- deltay = abs(y2-y1)
- error = int(deltax / 2)
- y = y1
- ystep = None
- if y1 < y2:
- ystep = 1
- else:
- ystep = -1
- for x in range(x1, x2 + 1):
- if issteep:
- points.append((y, x))
- else:
- points.append((x, y))
- error -= deltay
- if error < 0:
- y += ystep
- error += deltax
- # Reverse the list if the coordinates were reversed
- if rev:
- points.reverse()
- return points
-
-
-quickFOV = _style.backport(quick_fov)
-AStar.getPath = _style.backport(AStar.get_path)
diff --git a/tdl/noise.py b/tdl/noise.py
deleted file mode 100755
index 66606a0a..00000000
--- a/tdl/noise.py
+++ /dev/null
@@ -1,169 +0,0 @@
-"""
- This module provides advanced noise generation.
-
- Noise is sometimes used for over-world generation, height-maps, and
- cloud/mist/smoke effects among other things.
-
- You can see examples of the available noise algorithms in the libtcod
- documentation `here
- `_.
-"""
-
-
-import random as _random
-
-from tcod import ffi as _ffi
-from tcod import lib as _lib
-
-import tdl as _tdl
-from . import style as _style
-
-_MERSENNE_TWISTER = 1
-_CARRY_WITH_MULTIPLY = 2
-
-_MAX_DIMENSIONS = 4
-_MAX_OCTAVES = 128
-
-_NOISE_TYPES = {'PERLIN': 1, 'SIMPLEX': 2, 'WAVELET': 4}
-_NOISE_MODES = {'FLAT': _lib.TCOD_noise_get,
- 'FBM': _lib.TCOD_noise_get_fbm,
- 'TURBULENCE': _lib.TCOD_noise_get_turbulence}
-
-class Noise(object):
- """An advanced noise generator.
-
- .. deprecated:: 3.2
- This class has been replaced by :any:`tcod.noise.Noise`.
-
- Args:
- algorithm (Text): The primary noise algorithm to be used.
-
- Can be one of 'PERLIN', 'SIMPLEX', 'WAVELET'
-
- - 'PERLIN' -
- A popular noise generator.
- - 'SIMPLEX' -
- In theory this is a slightly faster generator with
- less noticeable directional artifacts.
- - 'WAVELET' -
- A noise generator designed to reduce aliasing and
- not lose detail when summed into a fractal
- (as with the 'FBM' and 'TURBULENCE' modes.)
- This works faster at higher dimensions.
-
- mode (Text): A secondary parameter to determine how noise is generated.
-
- Can be one of 'FLAT', 'FBM', 'TURBULENCE'
-
- - 'FLAT' -
- Generates the simplest form of noise.
- This mode does not use the hurst, lacunarity,
- and octaves parameters.
- - 'FBM' -
- Generates fractal brownian motion.
- - 'TURBULENCE' -
- Generates detailed noise with smoother and more
- natural transitions.
-
- hurst (float): The hurst exponent.
-
- This describes the raggedness of the resultant noise,
- with a higher value leading to a smoother noise.
- It should be in the 0.0-1.0 range.
-
- This is only used in 'FBM' and 'TURBULENCE' modes.
-
- lacunarity (float): A multiplier that determines how quickly the
- frequency increases for each successive octave.
-
- The frequency of each successive octave is equal to
- the product of the previous octave's frequency and
- the lacunarity value.
-
- This is only used in 'FBM' and 'TURBULENCE' modes.
-
- octaves (float): Controls the amount of detail in the noise.
-
- This is only used in 'FBM' and 'TURBULENCE' modes.
-
- seed (Hashable): You can use any hashable object to be a seed for the
- noise generator.
-
- If None is used then a random seed will be generated.
- """
-
- def __init__(self, algorithm='PERLIN', mode='FLAT',
- hurst=0.5, lacunarity=2.0, octaves=4.0, seed=None, dimensions=4):
- if algorithm.upper() not in _NOISE_TYPES:
- raise _tdl.TDLError('No such noise algorithm as %s' % algorithm)
- self._algorithm = algorithm.upper()
-
- if mode.upper() not in _NOISE_MODES:
- raise _tdl.TDLError('No such mode as %s' % mode)
- self._mode = mode.upper()
-
- if seed is None:
- seed = _random.getrandbits(32)
- try:
- seed = int(seed)
- except TypeError:
- seed = hash(seed)
- self._seed = seed
- # convert values into ctypes to speed up later functions
- self._dimensions = min(_MAX_DIMENSIONS, int(dimensions))
- if self._algorithm == 'WAVELET':
- self._dimensions = min(self._dimensions, 3) # Wavelet only goes up to 3
- self._random = _lib.TCOD_random_new_from_seed(
- _MERSENNE_TWISTER,
- _ffi.cast('uint32_t', self._seed),
- )
- self._hurst = hurst
- self._lacunarity = lacunarity
- self._noise = _lib.TCOD_noise_new(self._dimensions, self._hurst,
- self._lacunarity, self._random)
- _lib.TCOD_noise_set_type(self._noise, _NOISE_TYPES[self._algorithm])
- self._noiseFunc = _NOISE_MODES[self._mode]
- self._octaves = octaves
- self._useOctaves = (self._mode != 'FLAT')
- self._arrayType = 'float[%i]' % self._dimensions
- #self._cFloatArray = _ctypes.c_float * self._dimensions
- #self._array = self._cFloatArray()
-
- def __copy__(self):
- # using the pickle method is a convenient way to clone this object
- return self.__class__(*self.__getstate__())
-
- def __getstate__(self):
- return (self._algorithm, self._mode,
- self._hurst, self._lacunarity, self._octaves,
- self._seed, self._dimensions)
-
- def __setstate__(self, state):
- self.__init__(*state)
-
- def get_point(self, *position):
- """Return the noise value of a specific position.
-
- Example usage: value = noise.getPoint(x, y, z)
-
- Args:
- position (Tuple[float, ...]): The point to sample at.
-
- Returns:
- float: The noise value at position.
-
- This will be a floating point in the 0.0-1.0 range.
- """
- #array = self._array
- #for d, pos in enumerate(position):
- # array[d] = pos
- #array = self._cFloatArray(*position)
- array = _ffi.new(self._arrayType, position)
- if self._useOctaves:
- return (self._noiseFunc(self._noise, array, self._octaves) + 1) * 0.5
- return (self._noiseFunc(self._noise, array) + 1) * 0.5
-
-
-__all__ = [_var for _var in locals().keys() if _var[0] != '_']
-
-Noise.getPoint = _style.backport(Noise.get_point)
diff --git a/tdl/style.py b/tdl/style.py
deleted file mode 100644
index cdf643b4..00000000
--- a/tdl/style.py
+++ /dev/null
@@ -1,31 +0,0 @@
-"""
- Used internally to handle style changes without breaking backwards
- compatibility.
-"""
-
-import warnings as _warnings
-import functools as _functools
-
-def backport(func):
- """
- Backport a function name into an old style for compatibility.
-
- The docstring is updated to reflect that the new function returned is
- deprecated and that the other function is preferred.
- A DeprecationWarning is also raised for using this function.
-
- If the script is run with an optimization flag then the real function
- will be returned without being wrapped.
- """
- if not __debug__:
- return func
-
- @_functools.wraps(func)
- def deprecated_function(*args, **kargs):
- _warnings.warn('This function name is deprecated',
- DeprecationWarning, 2)
- return func(*args, **kargs)
- deprecated_function.__doc__ = None
- return deprecated_function
-
-__all__ = []
diff --git a/tdl/terminal8x8.png b/tdl/terminal8x8.png
deleted file mode 100644
index b0392fc7..00000000
Binary files a/tdl/terminal8x8.png and /dev/null differ
diff --git a/tdl/version.py b/tdl/version.py
deleted file mode 100644
index 0e58e4ae..00000000
--- a/tdl/version.py
+++ /dev/null
@@ -1 +0,0 @@
-from tcod import __version__
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 00000000..38bb211b
--- /dev/null
+++ b/tests/__init__.py
@@ -0,0 +1 @@
+"""Test package."""
diff --git a/tests/common.py b/tests/common.py
deleted file mode 100644
index ce5f4066..00000000
--- a/tests/common.py
+++ /dev/null
@@ -1,13 +0,0 @@
-
-import pytest
-
-import tcod
-
-
-def raise_Exception(*args):
- raise Exception('testing exception')
-
-needs_window = pytest.mark.skipif(
- pytest.config.getoption("--no-window"),
- reason="This test needs a rendering context."
-)
\ No newline at end of file
diff --git a/tests/conftest.py b/tests/conftest.py
index 78859118..580f16bc 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,90 +1,103 @@
+"""Test directory configuration."""
+
+from __future__ import annotations
import random
+import warnings
+from typing import TYPE_CHECKING
import pytest
import tcod
+from tcod import libtcodpy
+
+if TYPE_CHECKING:
+ from collections.abc import Callable, Iterator
+
+
+def pytest_addoption(parser: pytest.Parser) -> None:
+ parser.addoption("--no-window", action="store_true", help="Skip tests which need a rendering context.")
-def pytest_addoption(parser):
- parser.addoption("--no-window", action="store_true",
- help="Skip tests which need a rendering context.")
-@pytest.fixture(scope="session", params=['SDL', 'OPENGL', 'GLSL'])
-def session_console(request):
- if(pytest.config.getoption("--no-window")):
+@pytest.fixture
+def uses_window(request: pytest.FixtureRequest) -> Iterator[None]:
+ """Marks tests which require a rendering context."""
+ if request.config.getoption("--no-window"):
pytest.skip("This test needs a rendering context.")
- FONT_FILE = 'libtcod/terminal.png'
- WIDTH = 12
- HEIGHT = 10
- TITLE = 'libtcod-cffi tests'
- FULLSCREEN = False
- RENDERER = getattr(tcod, 'RENDERER_' + request.param)
-
- tcod.console_set_custom_font(FONT_FILE)
- with tcod.console_init_root(WIDTH, HEIGHT,
- TITLE, FULLSCREEN, RENDERER) as con:
+ yield None
+ return
+
+
+@pytest.fixture(scope="session", params=["SDL", "SDL2"])
+def session_console(request: pytest.FixtureRequest) -> Iterator[tcod.console.Console]:
+ if request.config.getoption("--no-window"):
+ pytest.skip("This test needs a rendering context.")
+ font_file = "libtcod/terminal.png"
+ width = 12
+ height = 10
+ title = "libtcod-cffi tests"
+ fullscreen = False
+ renderer = getattr(libtcodpy, "RENDERER_" + request.param)
+
+ libtcodpy.console_set_custom_font(font_file)
+ with libtcodpy.console_init_root(width, height, title, fullscreen, renderer, vsync=False) as con:
yield con
-@pytest.fixture(scope="function")
-def console(session_console):
+
+@pytest.fixture
+def console(session_console: tcod.console.Console) -> tcod.console.Console:
console = session_console
- tcod.console_flush()
- console.default_fg = (255, 255, 255)
- console.default_bg = (0, 0, 0)
- console.default_blend = tcod.BKGND_SET
- console.default_alignment = tcod.LEFT
+ libtcodpy.console_flush()
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore")
+ console.default_fg = (255, 255, 255)
+ console.default_bg = (0, 0, 0)
+ console.default_bg_blend = libtcodpy.BKGND_SET
+ console.default_alignment = libtcodpy.LEFT
console.clear()
return console
-@pytest.fixture()
-def offscreen(console):
+
+@pytest.fixture
+def offscreen(console: tcod.console.Console) -> tcod.console.Console:
"""Return an off-screen console with the same size as the root console."""
return tcod.console.Console(console.width, console.height)
-@pytest.fixture()
-def fg():
- return tcod.Color(random.randint(0, 255), random.randint(0, 255),
- random.randint(0, 255))
-@pytest.fixture()
-def bg():
- return tcod.Color(random.randint(0, 255), random.randint(0, 255),
- random.randint(0, 255))
+@pytest.fixture
+def fg() -> libtcodpy.Color:
+ return libtcodpy.Color(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
-try:
- unichr
-except NameError:
- unichr = chr
-def ch_ascii_int():
- return random.randint(0x21, 0x7f)
+@pytest.fixture
+def bg() -> libtcodpy.Color:
+ return libtcodpy.Color(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
-def ch_ascii_str():
- return chr(ch_ascii_int())
-def ch_latin1_int():
- return random.randint(0x80, 0xff)
+def ch_ascii_int() -> int:
+ return random.randint(0x21, 0x7F)
-def ch_latin1_str():
- return chr(ch_latin1_int())
-def ch_bmp_int():
- # Basic Multilingual Plane, before surrogates
- return random.randint(0x100, 0xd7ff)
+def ch_ascii_str() -> str:
+ return chr(ch_ascii_int())
-def ch_bmp_str():
- return unichr(ch_bmp_int())
-def ch_smp_int():
- return random.randint(0x10000, 0x1f9ff)
+def ch_latin1_int() -> int:
+ return random.randint(0x80, 0xFF)
+
+
+def ch_latin1_str() -> str:
+ return chr(ch_latin1_int())
-def ch_smp_str():
- return unichr(ch_bmp_int())
-@pytest.fixture(params=['ascii_int', 'ascii_str',
- 'latin1_int', 'latin1_str',
- #'bmp_int', 'bmp_str', # causes crashes
- ])
-def ch(request):
- """Test with multiple types of ascii/latin1 characters"""
- return globals()['ch_%s' % request.param]()
+@pytest.fixture(
+ params=[
+ "ascii_int",
+ "ascii_str",
+ "latin1_int",
+ "latin1_str",
+ ]
+)
+def ch(request: pytest.FixtureRequest) -> Callable[[], int | str]:
+ """Test with multiple types of ascii/latin1 characters."""
+ return globals()[f"ch_{request.param}"]() # type: ignore[no-any-return]
diff --git a/tests/data/README.md b/tests/data/README.md
new file mode 100644
index 00000000..8a7a10d7
--- /dev/null
+++ b/tests/data/README.md
@@ -0,0 +1,3 @@
+Data files for tests, such as old pickle streams.
+
+Remember to add new file types to `MANIFEST.in`.
diff --git a/tests/data/random_v13.pkl b/tests/data/random_v13.pkl
new file mode 100644
index 00000000..03fb7bf2
Binary files /dev/null and b/tests/data/random_v13.pkl differ
diff --git a/tests/test_console.py b/tests/test_console.py
new file mode 100644
index 00000000..873a7691
--- /dev/null
+++ b/tests/test_console.py
@@ -0,0 +1,171 @@
+"""Tests for tcod.console."""
+
+import pickle
+from pathlib import Path
+
+import numpy as np
+import pytest
+
+import tcod
+import tcod.console
+
+
+def test_array_read_write() -> None:
+ console = tcod.console.Console(width=12, height=10)
+ FG = (255, 254, 253) # noqa: N806
+ BG = (1, 2, 3) # noqa: N806
+ CH = ord("&") # noqa: N806
+ with pytest.warns():
+ tcod.console_put_char_ex(console, 0, 0, CH, FG, BG)
+ assert console.ch[0, 0] == CH
+ assert tuple(console.fg[0, 0]) == FG
+ assert tuple(console.bg[0, 0]) == BG
+
+ with pytest.warns():
+ tcod.console_put_char_ex(console, 1, 2, CH, FG, BG)
+ assert console.ch[2, 1] == CH
+ assert tuple(console.fg[2, 1]) == FG
+ assert tuple(console.bg[2, 1]) == BG
+
+ console.clear()
+ assert console.ch[1, 1] == ord(" ")
+ assert tuple(console.fg[1, 1]) == (255, 255, 255)
+ assert tuple(console.bg[1, 1]) == (0, 0, 0)
+
+ ch_slice = console.ch[1, :]
+ ch_slice[2] = CH
+ console.fg[1, ::2] = FG
+ console.bg[...] = BG
+
+ with pytest.warns():
+ assert tcod.console_get_char(console, 2, 1) == CH
+ with pytest.warns():
+ assert tuple(tcod.console_get_char_foreground(console, 2, 1)) == FG
+ with pytest.warns():
+ assert tuple(tcod.console_get_char_background(console, 2, 1)) == BG
+
+
+@pytest.mark.filterwarnings("ignore")
+def test_console_defaults() -> None:
+ console = tcod.console.Console(width=12, height=10)
+
+ console.default_bg = [2, 3, 4] # type: ignore[assignment]
+ assert console.default_bg == (2, 3, 4)
+
+ console.default_fg = (4, 5, 6)
+ assert console.default_fg == (4, 5, 6)
+
+ console.default_bg_blend = tcod.BKGND_ADD
+ assert console.default_bg_blend == tcod.BKGND_ADD
+
+ console.default_alignment = tcod.RIGHT
+ assert console.default_alignment == tcod.RIGHT
+
+
+@pytest.mark.filterwarnings("ignore:Parameter names have been moved around,")
+@pytest.mark.filterwarnings("ignore:Pass the key color to Console.blit instead")
+@pytest.mark.filterwarnings("ignore:.*default values have been deprecated")
+def test_console_methods() -> None:
+ console = tcod.console.Console(width=12, height=10)
+ console.put_char(0, 0, ord("@"))
+ with pytest.deprecated_call():
+ console.print_(0, 0, "Test")
+ with pytest.deprecated_call():
+ console.print_rect(0, 0, 2, 8, "a b c d e f")
+ console.get_height_rect(0, 0, 2, 8, "a b c d e f")
+ with pytest.deprecated_call():
+ console.rect(0, 0, 2, 2, True) # noqa: FBT003
+ with pytest.deprecated_call():
+ console.hline(0, 1, 10)
+ with pytest.deprecated_call():
+ console.vline(1, 0, 10)
+ with pytest.deprecated_call():
+ console.print_frame(0, 0, 8, 8, "Frame")
+ console.blit(0, 0, 0, 0, console, 0, 0) # type: ignore[arg-type]
+ console.blit(0, 0, 0, 0, console, 0, 0, key_color=(0, 0, 0)) # type: ignore[arg-type]
+ with pytest.deprecated_call():
+ console.set_key_color((254, 0, 254))
+
+
+def test_console_pickle() -> None:
+ console = tcod.console.Console(width=12, height=10)
+ console.ch[...] = ord(".")
+ console.fg[...] = (10, 20, 30)
+ console.bg[...] = (1, 2, 3)
+ console2 = pickle.loads(pickle.dumps(console))
+ assert (console.ch == console2.ch).all()
+ assert (console.fg == console2.fg).all()
+ assert (console.bg == console2.bg).all()
+
+
+def test_console_pickle_fortran() -> None:
+ console = tcod.console.Console(2, 3, order="F")
+ console2 = pickle.loads(pickle.dumps(console))
+ assert console.ch.strides == console2.ch.strides
+ assert console.fg.strides == console2.fg.strides
+ assert console.bg.strides == console2.bg.strides
+
+
+def test_console_repr() -> None:
+ from numpy import array # Used for eval # noqa: F401, PLC0415
+
+ eval(repr(tcod.console.Console(10, 2))) # noqa: S307
+
+
+def test_console_str() -> None:
+ console = tcod.console.Console(10, 2)
+ console.ch[:] = ord(".")
+ with pytest.warns():
+ console.print_(0, 0, "Test")
+ assert str(console) == ("")
+
+
+def test_console_fortran_buffer() -> None:
+ tcod.console.Console(
+ width=1,
+ height=2,
+ order="F",
+ buffer=np.zeros((1, 2), order="F", dtype=tcod.console.Console.DTYPE),
+ )
+
+
+def test_console_clear() -> None:
+ console = tcod.console.Console(1, 1)
+ assert console.fg[0, 0].tolist() == [255, 255, 255]
+ assert console.bg[0, 0].tolist() == [0, 0, 0]
+ console.clear(fg=(7, 8, 9), bg=(10, 11, 12))
+ assert console.fg[0, 0].tolist() == [7, 8, 9]
+ assert console.bg[0, 0].tolist() == [10, 11, 12]
+
+
+def test_console_semigraphics() -> None:
+ console = tcod.console.Console(1, 1)
+ console.draw_semigraphics(
+ [[[255, 255, 255], [255, 255, 255]], [[255, 255, 255], [0, 0, 0]]],
+ )
+
+
+def test_rexpaint(tmp_path: Path) -> None:
+ xp_path = tmp_path / "test.xp"
+ consoles = tcod.console.Console(80, 24, order="F"), tcod.console.Console(8, 8, order="F")
+ tcod.console.save_xp(xp_path, consoles, compress_level=0)
+ loaded = tcod.console.load_xp(xp_path, order="F")
+ assert len(consoles) == len(loaded)
+ assert loaded[0].rgba.flags["F_CONTIGUOUS"]
+ assert consoles[0].rgb.shape == loaded[0].rgb.shape
+ assert consoles[1].rgb.shape == loaded[1].rgb.shape
+ with pytest.raises(FileNotFoundError):
+ tcod.console.load_xp(tmp_path / "non_existent")
+
+
+def test_draw_frame() -> None:
+ console = tcod.console.Console(3, 3, order="C")
+ with pytest.raises(TypeError):
+ console.draw_frame(0, 0, 3, 3, title="test", decoration="123456789")
+ with pytest.raises(TypeError):
+ console.draw_frame(0, 0, 3, 3, decoration="0123456789")
+
+ console.draw_frame(0, 0, 3, 3, decoration=(49, 50, 51, 52, 53, 54, 55, 56, 57))
+ assert console.ch.tolist() == [[49, 50, 51], [52, 53, 54], [55, 56, 57]]
+ with pytest.warns():
+ console.draw_frame(0, 0, 3, 3, title="T")
diff --git a/tests/test_deprecated.py b/tests/test_deprecated.py
new file mode 100644
index 00000000..4a960bba
--- /dev/null
+++ b/tests/test_deprecated.py
@@ -0,0 +1,70 @@
+"""Test deprecated features."""
+
+from __future__ import annotations
+
+import numpy as np
+import pytest
+
+import tcod
+import tcod.constants
+import tcod.event
+import tcod.libtcodpy
+import tcod.random
+
+with pytest.warns():
+ import libtcodpy
+
+
+def test_deprecate_color() -> None:
+ with pytest.warns(FutureWarning, match=r"\(0, 0, 0\)"):
+ assert tcod.black is tcod.constants.black
+ with pytest.warns(FutureWarning, match=r"\(0, 0, 0\)"):
+ assert tcod.libtcodpy.black is tcod.constants.black
+ with pytest.warns(FutureWarning, match=r"\(0, 0, 0\)"):
+ assert libtcodpy.black is tcod.constants.black
+
+
+def test_constants() -> None:
+ with pytest.warns(match=r"libtcodpy.RENDERER_SDL2"):
+ assert tcod.RENDERER_SDL2 is tcod.constants.RENDERER_SDL2
+ assert tcod.libtcodpy.RENDERER_SDL2 is tcod.constants.RENDERER_SDL2
+
+
+def test_implicit_libtcodpy() -> None:
+ with pytest.warns(match=r"libtcodpy.console_init_root"):
+ assert tcod.console_init_root is tcod.libtcodpy.console_init_root
+
+
+def test_deprecate_key_constants() -> None:
+ with pytest.warns(FutureWarning, match=r"KeySym.N1"):
+ _ = tcod.event.K_1
+ with pytest.warns(FutureWarning, match=r"Scancode.N1"):
+ _ = tcod.event.SCANCODE_1
+
+
+def test_deprecate_mouse_constants() -> None:
+ with pytest.warns(FutureWarning, match=r"MouseButton.LEFT"):
+ _ = tcod.event.BUTTON_LEFT
+ with pytest.warns(FutureWarning, match=r"MouseButtonMask.LEFT"):
+ _ = tcod.event.BUTTON_LMASK
+
+
+def test_deprecate_kmod_constants() -> None:
+ with pytest.warns(FutureWarning, match=r"Modifier.LSHIFT"):
+ _ = tcod.event.KMOD_LSHIFT
+ with pytest.warns(FutureWarning, match=r"Modifier.GUI"):
+ _ = tcod.event.KMOD_GUI
+
+
+def test_line_where() -> None:
+ with pytest.warns():
+ where = tcod.libtcodpy.line_where(1, 0, 3, 4)
+ np.testing.assert_array_equal(where, [[1, 1, 2, 2, 3], [0, 1, 2, 3, 4]])
+
+
+def test_gauss_typo() -> None:
+ rng = tcod.random.Random()
+ with pytest.warns(FutureWarning, match=r"gauss"):
+ rng.guass(1, 1)
+ with pytest.warns(FutureWarning, match=r"inverse_gauss"):
+ rng.inverse_guass(1, 1)
diff --git a/tests/test_event.py b/tests/test_event.py
new file mode 100644
index 00000000..9cc28243
--- /dev/null
+++ b/tests/test_event.py
@@ -0,0 +1,73 @@
+"""Tests for event parsing and handling."""
+
+from typing import Any, Final
+
+import pytest
+
+import tcod.event
+import tcod.sdl.sys
+from tcod._internal import _check
+from tcod.cffi import ffi, lib
+from tcod.event import KeySym, Modifier, Scancode
+
+EXPECTED_EVENTS: Final = (
+ tcod.event.Quit(),
+ tcod.event.KeyDown(scancode=Scancode.A, sym=KeySym.A, mod=Modifier(0), pressed=True),
+ tcod.event.KeyUp(scancode=Scancode.A, sym=KeySym.A, mod=Modifier(0), pressed=False),
+)
+"""Events to compare with after passing though the SDL event queue."""
+
+
+def as_sdl_event(event: tcod.event.Event) -> dict[str, dict[str, Any]]:
+ """Convert events into SDL_Event unions using cffi's union format."""
+ match event:
+ case tcod.event.Quit():
+ return {"quit": {"type": lib.SDL_EVENT_QUIT}}
+ case tcod.event.KeyboardEvent():
+ return {
+ "key": {
+ "type": (lib.SDL_EVENT_KEY_UP, lib.SDL_EVENT_KEY_DOWN)[event.pressed],
+ "scancode": event.scancode,
+ "key": event.sym,
+ "mod": event.mod,
+ "down": event.pressed,
+ "repeat": event.repeat,
+ }
+ }
+ raise AssertionError
+
+
+EVENT_PACK: Final = ffi.new("SDL_Event[]", [as_sdl_event(_e) for _e in EXPECTED_EVENTS])
+"""A custom C array of SDL_Event unions based on EXPECTED_EVENTS."""
+
+
+def push_events() -> None:
+ """Reset the SDL event queue to an expected list of events."""
+ tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.EVENTS) # Ensure SDL event queue is enabled
+
+ lib.SDL_PumpEvents() # Clear everything from the queue
+ lib.SDL_FlushEvents(lib.SDL_EVENT_FIRST, lib.SDL_EVENT_LAST)
+
+ assert _check( # Fill the queue with EVENT_PACK
+ lib.SDL_PeepEvents(EVENT_PACK, len(EVENT_PACK), lib.SDL_ADDEVENT, lib.SDL_EVENT_FIRST, lib.SDL_EVENT_LAST)
+ ) == len(EVENT_PACK)
+
+
+def test_get_events() -> None:
+ push_events()
+ assert tuple(tcod.event.get()) == EXPECTED_EVENTS
+
+ assert tuple(tcod.event.get()) == ()
+ assert tuple(tcod.event.wait(timeout=0)) == ()
+
+ push_events()
+ assert tuple(tcod.event.wait()) == EXPECTED_EVENTS
+
+
+def test_event_dispatch() -> None:
+ push_events()
+ with pytest.deprecated_call():
+ tcod.event.EventDispatch().event_wait(timeout=0)
+ push_events()
+ with pytest.deprecated_call():
+ tcod.event.EventDispatch().event_get()
diff --git a/tests/test_libtcodpy.py b/tests/test_libtcodpy.py
index 20776381..ca1ab1a2 100644
--- a/tests/test_libtcodpy.py
+++ b/tests/test_libtcodpy.py
@@ -1,188 +1,237 @@
-#!/usr/bin/env python
+"""Tests for the libtcodpy API."""
+from collections.abc import Callable, Iterator
+from pathlib import Path
+from typing import Any
+
+import numpy as np
import pytest
+from numpy.typing import NDArray
+
+import tcod
+from tcod import libtcodpy
-try:
- import numpy
-except ImportError:
- numpy = None
+pytestmark = [
+ pytest.mark.filterwarnings("ignore::DeprecationWarning"),
+ pytest.mark.filterwarnings("ignore::PendingDeprecationWarning"),
+]
-import libtcodpy
-def test_console_behaviour(console):
+def test_console_behavior(console: tcod.console.Console) -> None:
assert not console
-@pytest.mark.skip('takes too long')
-def test_credits_long(console):
+
+@pytest.mark.skip("takes too long")
+@pytest.mark.filterwarnings("ignore")
+def test_credits_long(console: tcod.console.Console) -> None: # noqa: ARG001
libtcodpy.console_credits()
-def test_credits(console):
- libtcodpy.console_credits_render(0, 0, True)
+
+def test_credits(console: tcod.console.Console) -> None: # noqa: ARG001
+ libtcodpy.console_credits_render(0, 0, True) # noqa: FBT003
libtcodpy.console_credits_reset()
-def assert_char(console, x, y, ch=None, fg=None, bg=None):
+
+def assert_char( # noqa: PLR0913
+ console: tcod.console.Console,
+ x: int,
+ y: int,
+ ch: str | int | None = None,
+ fg: tuple[int, int, int] | None = None,
+ bg: tuple[int, int, int] | None = None,
+) -> None:
if ch is not None:
- try:
+ if isinstance(ch, str):
ch = ord(ch)
- except TypeError:
- pass
assert console.ch[y, x] == ch
if fg is not None:
assert (console.fg[y, x] == fg).all()
if bg is not None:
assert (console.bg[y, x] == bg).all()
-def test_console_defaults(console, fg, bg):
+
+@pytest.mark.filterwarnings("ignore")
+def test_console_defaults(console: tcod.console.Console, fg: tuple[int, int, int], bg: tuple[int, int, int]) -> None:
libtcodpy.console_set_default_foreground(console, fg)
libtcodpy.console_set_default_background(console, bg)
libtcodpy.console_clear(console)
assert_char(console, 0, 0, None, fg, bg)
-@pytest.mark.filterwarnings("ignore:Directly access a consoles")
-def test_console_set_char_background(console, bg):
+
+@pytest.mark.filterwarnings("ignore")
+def test_console_set_char_background(console: tcod.console.Console, bg: tuple[int, int, int]) -> None:
libtcodpy.console_set_char_background(console, 0, 0, bg, libtcodpy.BKGND_SET)
assert_char(console, 0, 0, bg=bg)
-@pytest.mark.filterwarnings("ignore:Directly access a consoles")
-def test_console_set_char_foreground(console, fg):
+
+@pytest.mark.filterwarnings("ignore")
+def test_console_set_char_foreground(console: tcod.console.Console, fg: tuple[int, int, int]) -> None:
libtcodpy.console_set_char_foreground(console, 0, 0, fg)
assert_char(console, 0, 0, fg=fg)
-@pytest.mark.filterwarnings("ignore:Directly access a consoles")
-def test_console_set_char(console, ch):
+
+@pytest.mark.filterwarnings("ignore")
+def test_console_set_char(console: tcod.console.Console, ch: int) -> None:
libtcodpy.console_set_char(console, 0, 0, ch)
assert_char(console, 0, 0, ch=ch)
-def test_console_put_char(console, ch):
+
+@pytest.mark.filterwarnings("ignore")
+def test_console_put_char(console: tcod.console.Console, ch: int) -> None:
libtcodpy.console_put_char(console, 0, 0, ch, libtcodpy.BKGND_SET)
assert_char(console, 0, 0, ch=ch)
-def console_put_char_ex(console, ch, fg, bg):
+
+@pytest.mark.filterwarnings("ignore")
+def console_put_char_ex(
+ console: tcod.console.Console, ch: int, fg: tuple[int, int, int], bg: tuple[int, int, int]
+) -> None:
libtcodpy.console_put_char_ex(console, 0, 0, ch, fg, bg)
assert_char(console, 0, 0, ch=ch, fg=fg, bg=bg)
-def test_console_printing(console, fg, bg):
- libtcodpy.console_set_background_flag(console,
- libtcodpy.BKGND_SET)
- assert (libtcodpy.console_get_background_flag(console) ==
- libtcodpy.BKGND_SET)
+
+@pytest.mark.filterwarnings("ignore")
+def test_console_printing(console: tcod.console.Console, fg: tuple[int, int, int], bg: tuple[int, int, int]) -> None:
+ libtcodpy.console_set_background_flag(console, libtcodpy.BKGND_SET)
+ assert libtcodpy.console_get_background_flag(console) == libtcodpy.BKGND_SET
libtcodpy.console_set_alignment(console, libtcodpy.LEFT)
- assert (libtcodpy.console_get_alignment(console) ==
- libtcodpy.LEFT)
+ assert libtcodpy.console_get_alignment(console) == libtcodpy.LEFT
- libtcodpy.console_print(console, 0, 0, 'print')
- libtcodpy.console_print_ex(console, 0, 0, libtcodpy.BKGND_SET,
- libtcodpy.LEFT, 'print ex')
+ libtcodpy.console_print(console, 0, 0, "print")
+ libtcodpy.console_print_ex(console, 0, 0, libtcodpy.BKGND_SET, libtcodpy.LEFT, "print ex")
- assert (libtcodpy.console_print_rect(
- console, 0, 0, 8, 8, 'print rect') > 0
- )
- assert (libtcodpy.console_print_rect_ex(
- console, 0, 0, 8, 8, libtcodpy.BKGND_SET, libtcodpy.LEFT,
- 'print rect ex') > 0
- )
- assert (libtcodpy.console_get_height_rect(
- console, 0, 0, 8, 8, 'get height') > 0
- )
+ assert libtcodpy.console_print_rect(console, 0, 0, 8, 8, "print rect") > 0
+ assert (
+ libtcodpy.console_print_rect_ex(console, 0, 0, 8, 8, libtcodpy.BKGND_SET, libtcodpy.LEFT, "print rect ex") > 0
+ )
+ assert libtcodpy.console_get_height_rect(console, 0, 0, 8, 8, "get height") > 0
libtcodpy.console_set_color_control(libtcodpy.COLCTRL_1, fg, bg)
-def test_console_rect(console):
- libtcodpy.console_rect(console, 0, 0, 4, 4, False, libtcodpy.BKGND_SET)
-def test_console_lines(console):
+@pytest.mark.filterwarnings("ignore")
+def test_console_rect(console: tcod.console.Console) -> None:
+ libtcodpy.console_rect(console, 0, 0, 4, 4, False, libtcodpy.BKGND_SET) # noqa: FBT003
+
+
+@pytest.mark.filterwarnings("ignore")
+def test_console_lines(console: tcod.console.Console) -> None:
libtcodpy.console_hline(console, 0, 0, 4)
libtcodpy.console_vline(console, 0, 0, 4)
-def test_console_print_frame(console):
+
+@pytest.mark.filterwarnings("ignore")
+def test_console_print_frame(console: tcod.console.Console) -> None:
libtcodpy.console_print_frame(console, 0, 0, 9, 9)
-def test_console_fade(console):
- libtcodpy.console_set_fade(0, libtcodpy.Color(0, 0, 0))
+
+@pytest.mark.filterwarnings("ignore")
+def test_console_fade(console: tcod.console.Console) -> None: # noqa: ARG001
+ libtcodpy.console_set_fade(0, (0, 0, 0))
libtcodpy.console_get_fade()
libtcodpy.console_get_fading_color()
-def assertConsolesEqual(a, b):
- return ((a.fg[:] == b.fg[:]).all() and
- (a.bg[:] == b.bg[:]).all() and
- (a.ch[:] == b.ch[:]).all())
+def assert_consoles_equal(a: tcod.console.Console, b: tcod.console.Console, /) -> bool:
+ return bool((a.fg[:] == b.fg[:]).all() and (a.bg[:] == b.bg[:]).all() and (a.ch[:] == b.ch[:]).all())
-def test_console_blit(console, offscreen):
- libtcodpy.console_print(offscreen, 0, 0, 'test')
+@pytest.mark.filterwarnings("ignore")
+def test_console_blit(console: tcod.console.Console, offscreen: tcod.console.Console) -> None:
+ libtcodpy.console_print(offscreen, 0, 0, "test")
libtcodpy.console_blit(offscreen, 0, 0, 0, 0, console, 0, 0, 1, 1)
- assertConsolesEqual(console, offscreen)
- libtcodpy.console_set_key_color(offscreen, libtcodpy.black)
+ assert_consoles_equal(console, offscreen)
+ libtcodpy.console_set_key_color(offscreen, (0, 0, 0))
+
-def test_console_asc_read_write(console, offscreen, tmpdir):
- libtcodpy.console_print(console, 0, 0, 'test')
+@pytest.mark.filterwarnings("ignore")
+def test_console_asc_read_write(console: tcod.console.Console, offscreen: tcod.console.Console, tmp_path: Path) -> None:
+ libtcodpy.console_print(console, 0, 0, "test")
- asc_file = tmpdir.join('test.asc').strpath
+ asc_file = tmp_path / "test.asc"
assert libtcodpy.console_save_asc(console, asc_file)
assert libtcodpy.console_load_asc(offscreen, asc_file)
- assertConsolesEqual(console, offscreen)
+ assert_consoles_equal(console, offscreen)
-def test_console_apf_read_write(console, offscreen, tmpdir):
- libtcodpy.console_print(console, 0, 0, 'test')
- apf_file = tmpdir.join('test.apf').strpath
+@pytest.mark.filterwarnings("ignore")
+def test_console_apf_read_write(console: tcod.console.Console, offscreen: tcod.console.Console, tmp_path: Path) -> None:
+ libtcodpy.console_print(console, 0, 0, "test")
+
+ apf_file = tmp_path / "test.apf"
assert libtcodpy.console_save_apf(console, apf_file)
assert libtcodpy.console_load_apf(offscreen, apf_file)
- assertConsolesEqual(console, offscreen)
+ assert_consoles_equal(console, offscreen)
+
-@pytest.mark.filterwarnings("ignore:Directly access a consoles")
-def test_console_rexpaint_load_test_file(console):
- xp_console = libtcodpy.console_from_xp('libtcod/data/rexpaint/test.xp')
+@pytest.mark.filterwarnings("ignore")
+def test_console_rexpaint_load_test_file(console: tcod.console.Console) -> None: # noqa: ARG001
+ xp_console = libtcodpy.console_from_xp("libtcod/data/rexpaint/test.xp")
assert xp_console
- assert libtcodpy.console_get_char(xp_console, 0, 0) == ord('T')
- assert libtcodpy.console_get_char(xp_console, 1, 0) == ord('e')
- assert (libtcodpy.console_get_char_background(xp_console, 0, 1) ==
- libtcodpy.Color(255, 0, 0))
- assert (libtcodpy.console_get_char_background(xp_console, 1, 1) ==
- libtcodpy.Color(0, 255, 0))
- assert (libtcodpy.console_get_char_background(xp_console, 2, 1) ==
- libtcodpy.Color(0, 0, 255))
-
-def test_console_rexpaint_save_load(console, tmpdir, ch, fg, bg):
- libtcodpy.console_print(console, 0, 0, 'test')
+ assert libtcodpy.console_get_char(xp_console, 0, 0) == ord("T")
+ assert libtcodpy.console_get_char(xp_console, 1, 0) == ord("e")
+ assert libtcodpy.console_get_char_background(xp_console, 0, 1) == libtcodpy.Color(255, 0, 0)
+ assert libtcodpy.console_get_char_background(xp_console, 1, 1) == libtcodpy.Color(0, 255, 0)
+ assert libtcodpy.console_get_char_background(xp_console, 2, 1) == libtcodpy.Color(0, 0, 255)
+
+
+@pytest.mark.filterwarnings("ignore")
+def test_console_rexpaint_save_load(
+ console: tcod.console.Console,
+ tmp_path: Path,
+ ch: int,
+ fg: tuple[int, int, int],
+ bg: tuple[int, int, int],
+) -> None:
+ libtcodpy.console_print(console, 0, 0, "test")
libtcodpy.console_put_char_ex(console, 1, 1, ch, fg, bg)
- xp_file = tmpdir.join('test.xp').strpath
+ xp_file = tmp_path / "test.xp"
assert libtcodpy.console_save_xp(console, xp_file, 1)
xp_console = libtcodpy.console_from_xp(xp_file)
assert xp_console
- assertConsolesEqual(console, xp_console)
- assert libtcodpy.console_load_xp(None, xp_file)
- assertConsolesEqual(console, xp_console)
+ assert_consoles_equal(console, xp_console)
+ assert libtcodpy.console_load_xp(None, xp_file) # type: ignore[arg-type]
+ assert_consoles_equal(console, xp_console)
+
-def test_console_rexpaint_list_save_load(console, tmpdir):
+@pytest.mark.filterwarnings("ignore")
+def test_console_rexpaint_list_save_load(console: tcod.console.Console, tmp_path: Path) -> None: # noqa: ARG001
con1 = libtcodpy.console_new(8, 2)
con2 = libtcodpy.console_new(8, 2)
- libtcodpy.console_print(con1, 0, 0, 'hello')
- libtcodpy.console_print(con2, 0, 0, 'world')
- xp_file = tmpdir.join('test.xp').strpath
+ libtcodpy.console_print(con1, 0, 0, "hello")
+ libtcodpy.console_print(con2, 0, 0, "world")
+ xp_file = tmp_path / "test.xp"
assert libtcodpy.console_list_save_xp([con1, con2], xp_file, 1)
- for a, b in zip([con1, con2], libtcodpy.console_list_load_xp(xp_file)):
- assertConsolesEqual(a, b)
+ loaded_consoles = libtcodpy.console_list_load_xp(xp_file)
+ assert loaded_consoles
+ for a, b in zip([con1, con2], loaded_consoles, strict=True):
+ assert_consoles_equal(a, b)
libtcodpy.console_delete(a)
libtcodpy.console_delete(b)
-def test_console_fullscreen(console):
- libtcodpy.console_set_fullscreen(False)
-def test_console_key_input(console):
+@pytest.mark.filterwarnings("ignore")
+def test_console_fullscreen(console: tcod.console.Console) -> None: # noqa: ARG001
+ libtcodpy.console_set_fullscreen(False) # noqa: FBT003
+
+
+@pytest.mark.filterwarnings("ignore")
+def test_console_key_input(console: tcod.console.Console) -> None: # noqa: ARG001
libtcodpy.console_check_for_keypress()
libtcodpy.console_is_key_pressed(libtcodpy.KEY_ENTER)
-def test_console_fill_errors(console):
+
+@pytest.mark.filterwarnings("ignore")
+def test_console_fill_errors(console: tcod.console.Console) -> None:
with pytest.raises(TypeError):
libtcodpy.console_fill_background(console, [0], [], [])
with pytest.raises(TypeError):
libtcodpy.console_fill_foreground(console, [0], [], [])
-@pytest.mark.filterwarnings("ignore:Directly access a consoles")
-def test_console_fill(console):
+
+@pytest.mark.filterwarnings("ignore")
+def test_console_fill(console: tcod.console.Console) -> None:
width = libtcodpy.console_get_width(console)
height = libtcodpy.console_get_height(console)
fill = [i % 256 for i in range(width * height)]
@@ -201,65 +250,72 @@ def test_console_fill(console):
assert fill == fg
assert fill == ch
-@pytest.mark.skipif(not numpy, reason='requires numpy module')
-@pytest.mark.filterwarnings("ignore:Directly access a consoles")
-def test_console_fill_numpy(console):
+
+@pytest.mark.filterwarnings("ignore")
+def test_console_fill_numpy(console: tcod.console.Console) -> None:
width = libtcodpy.console_get_width(console)
height = libtcodpy.console_get_height(console)
- fill = numpy.zeros((height, width), dtype=numpy.intc)
+ fill: NDArray[np.intc] = np.zeros((height, width), dtype=np.intc)
for y in range(height):
fill[y, :] = y % 256
- libtcodpy.console_fill_background(console, fill, fill, fill)
- libtcodpy.console_fill_foreground(console, fill, fill, fill)
- libtcodpy.console_fill_char(console, fill)
+ libtcodpy.console_fill_background(console, fill, fill, fill) # type: ignore[arg-type]
+ libtcodpy.console_fill_foreground(console, fill, fill, fill) # type: ignore[arg-type]
+ libtcodpy.console_fill_char(console, fill) # type: ignore[arg-type]
# verify fill
- bg = numpy.zeros((height, width), dtype=numpy.intc)
- fg = numpy.zeros((height, width), dtype=numpy.intc)
- ch = numpy.zeros((height, width), dtype=numpy.intc)
+ bg: NDArray[np.intc] = np.zeros((height, width), dtype=np.intc)
+ fg: NDArray[np.intc] = np.zeros((height, width), dtype=np.intc)
+ ch: NDArray[np.intc] = np.zeros((height, width), dtype=np.intc)
for y in range(height):
for x in range(width):
bg[y, x] = libtcodpy.console_get_char_background(console, x, y)[0]
fg[y, x] = libtcodpy.console_get_char_foreground(console, x, y)[0]
ch[y, x] = libtcodpy.console_get_char(console, x, y)
- fill = fill.tolist()
- assert fill == bg.tolist()
- assert fill == fg.tolist()
- assert fill == ch.tolist()
+ fill_ = fill.tolist()
+ assert fill_ == bg.tolist()
+ assert fill_ == fg.tolist()
+ assert fill_ == ch.tolist()
-@pytest.mark.filterwarnings("ignore:Console array attributes perform better")
-def test_console_buffer(console):
+
+@pytest.mark.filterwarnings("ignore")
+def test_console_buffer(console: tcod.console.Console) -> None:
buffer = libtcodpy.ConsoleBuffer(
libtcodpy.console_get_width(console),
libtcodpy.console_get_height(console),
)
buffer = buffer.copy()
- buffer.set_fore(0, 0, 0, 0, 0, '@')
+ buffer.set_fore(0, 0, 0, 0, 0, "@")
buffer.set_back(0, 0, 0, 0, 0)
- buffer.set(0, 0, 0, 0, 0, 0, 0, 0, '@')
+ buffer.set(0, 0, 0, 0, 0, 0, 0, 0, "@")
buffer.blit(console)
+
@pytest.mark.filterwarnings("ignore:Console array attributes perform better")
-def test_console_buffer_error(console):
+def test_console_buffer_error(console: tcod.console.Console) -> None:
buffer = libtcodpy.ConsoleBuffer(0, 0)
- with pytest.raises(ValueError):
+ with pytest.raises(ValueError, match=r".*Destination console has an incorrect size."):
buffer.blit(console)
-def test_console_font_mapping(console):
- libtcodpy.console_map_ascii_code_to_font('@', 1, 1)
- libtcodpy.console_map_ascii_codes_to_font('@', 1, 0, 0)
- libtcodpy.console_map_string_to_font('@', 0, 0)
-def test_mouse(console):
- libtcodpy.mouse_show_cursor(True)
+@pytest.mark.filterwarnings("ignore")
+def test_console_font_mapping(console: tcod.console.Console) -> None: # noqa: ARG001
+ libtcodpy.console_map_ascii_code_to_font(ord("@"), 1, 1)
+ libtcodpy.console_map_ascii_codes_to_font(ord("@"), 1, 0, 0)
+ libtcodpy.console_map_string_to_font("@", 0, 0)
+
+
+@pytest.mark.filterwarnings("ignore")
+def test_mouse(console: tcod.console.Console) -> None: # noqa: ARG001
+ libtcodpy.mouse_show_cursor(True) # noqa: FBT003
libtcodpy.mouse_is_cursor_visible()
mouse = libtcodpy.mouse_get_status()
repr(mouse)
libtcodpy.mouse_move(0, 0)
-@pytest.mark.filterwarnings("ignore:Use Python's standard 'time' module")
-def test_sys_time(console):
+
+@pytest.mark.filterwarnings("ignore")
+def test_sys_time(console: tcod.console.Console) -> None: # noqa: ARG001
libtcodpy.sys_set_fps(0)
libtcodpy.sys_get_fps()
libtcodpy.sys_get_last_frame_length()
@@ -267,54 +323,60 @@ def test_sys_time(console):
libtcodpy.sys_elapsed_milli()
libtcodpy.sys_elapsed_seconds()
-def test_sys_screenshot(console, tmpdir):
- libtcodpy.sys_save_screenshot(tmpdir.join('test.png').strpath)
-def test_sys_custom_render(console):
+@pytest.mark.filterwarnings("ignore")
+def test_sys_screenshot(console: tcod.console.Console, tmp_path: Path) -> None: # noqa: ARG001
+ libtcodpy.sys_save_screenshot(tmp_path / "test.png")
+
+
+@pytest.mark.filterwarnings("ignore")
+def test_sys_custom_render(console: tcod.console.Console) -> None: # noqa: ARG001
if libtcodpy.sys_get_renderer() != libtcodpy.RENDERER_SDL:
- pytest.xfail(reason='Only supports SDL')
+ pytest.xfail(reason="Only supports SDL")
escape = []
- def sdl_callback(sdl_surface):
+
+ def sdl_callback(_sdl_surface: object) -> None:
escape.append(True)
- libtcodpy.console_set_dirty(0, 0, 0, 0)
+
libtcodpy.sys_register_SDL_renderer(sdl_callback)
libtcodpy.console_flush()
- assert escape, 'proof that sdl_callback was called'
+ assert escape, "proof that sdl_callback was called"
+
-def test_image(console, tmpdir):
+@pytest.mark.filterwarnings("ignore")
+def test_image(console: tcod.console.Console, tmp_path: Path) -> None:
img = libtcodpy.image_new(16, 16)
- libtcodpy.image_clear(img, libtcodpy.Color(0, 0, 0))
+ libtcodpy.image_clear(img, (0, 0, 0))
libtcodpy.image_invert(img)
libtcodpy.image_hflip(img)
libtcodpy.image_rotate90(img)
libtcodpy.image_vflip(img)
libtcodpy.image_scale(img, 24, 24)
- libtcodpy.image_set_key_color(img, libtcodpy.Color(255, 255, 255))
+ libtcodpy.image_set_key_color(img, (255, 255, 255))
libtcodpy.image_get_alpha(img, 0, 0)
libtcodpy.image_is_pixel_transparent(img, 0, 0)
libtcodpy.image_get_size(img)
libtcodpy.image_get_pixel(img, 0, 0)
libtcodpy.image_get_mipmap_pixel(img, 0, 0, 1, 1)
- libtcodpy.image_put_pixel(img, 0, 0, libtcodpy.Color(255, 255, 255))
- libtcodpy.image_blit(img, console, 0, 0,
- libtcodpy.BKGND_SET, 1, 1, 0)
- libtcodpy.image_blit_rect(img, console, 0, 0, 16, 16,
- libtcodpy.BKGND_SET)
+ libtcodpy.image_put_pixel(img, 0, 0, (255, 255, 255))
+ libtcodpy.image_blit(img, console, 0, 0, libtcodpy.BKGND_SET, 1, 1, 0)
+ libtcodpy.image_blit_rect(img, console, 0, 0, 16, 16, libtcodpy.BKGND_SET)
libtcodpy.image_blit_2x(img, console, 0, 0)
- libtcodpy.image_save(img, tmpdir.join('test.png').strpath)
+ libtcodpy.image_save(img, tmp_path / "test.png")
libtcodpy.image_delete(img)
img = libtcodpy.image_from_console(console)
libtcodpy.image_refresh_console(img, console)
libtcodpy.image_delete(img)
- libtcodpy.image_delete(libtcodpy.image_load('libtcod/data/img/circle.png'))
+ libtcodpy.image_delete(libtcodpy.image_load("libtcod/data/img/circle.png"))
+
-@pytest.mark.parametrize('sample', ['@', u'\u2603']) # Unicode snowman
-@pytest.mark.xfail(reason='Unreliable')
-@pytest.mark.filterwarnings("ignore:This function does not provide reliable")
-def test_clipboard(console, sample):
+@pytest.mark.parametrize("sample", ["@", "\u2603"]) # Unicode snowman
+@pytest.mark.xfail(reason="Unreliable")
+@pytest.mark.filterwarnings("ignore")
+def test_clipboard(console: tcod.console.Console, sample: str) -> None: # noqa: ARG001
saved = libtcodpy.sys_clipboard_get()
try:
libtcodpy.sys_clipboard_set(sample)
@@ -325,53 +387,55 @@ def test_clipboard(console, sample):
# arguments to test with and the results expected from these arguments
LINE_ARGS = (-5, 0, 5, 10)
-EXCLUSIVE_RESULTS = [(-4, 1), (-3, 2), (-2, 3), (-1, 4), (0, 5), (1, 6),
- (2, 7), (3, 8), (4, 9), (5, 10)]
-INCLUSIVE_RESULTS = [(-5, 0)] + EXCLUSIVE_RESULTS
-
-def test_line_step():
- """
- libtcodpy.line_init and libtcodpy.line_step
- """
+EXCLUSIVE_RESULTS = [(-4, 1), (-3, 2), (-2, 3), (-1, 4), (0, 5), (1, 6), (2, 7), (3, 8), (4, 9), (5, 10)]
+INCLUSIVE_RESULTS = [(-5, 0), *EXCLUSIVE_RESULTS]
+
+
+@pytest.mark.filterwarnings("ignore")
+def test_line_step() -> None:
+ """libtcodpy.line_init and libtcodpy.line_step."""
libtcodpy.line_init(*LINE_ARGS)
for expected_xy in EXCLUSIVE_RESULTS:
assert libtcodpy.line_step() == expected_xy
assert libtcodpy.line_step() == (None, None)
-def test_line():
- """
- tests normal use, lazy evaluation, and error propagation
- """
+
+@pytest.mark.filterwarnings("ignore")
+def test_line() -> None:
+ """Tests normal use, lazy evaluation, and error propagation."""
# test normal results
- test_result = []
- def line_test(*test_xy):
- test_result.append(test_xy)
- return 1
+ test_result: list[tuple[int, int]] = []
+
+ def line_test(x: int, y: int) -> bool:
+ test_result.append((x, y))
+ return True
+
assert libtcodpy.line(*LINE_ARGS, py_callback=line_test) == 1
assert test_result == INCLUSIVE_RESULTS
# test lazy evaluation
test_result = []
- def return_false(*test_xy):
- test_result.append(test_xy)
+
+ def return_false(x: int, y: int) -> bool:
+ test_result.append((x, y))
return False
+
assert libtcodpy.line(*LINE_ARGS, py_callback=return_false) == 0
assert test_result == INCLUSIVE_RESULTS[:1]
-def test_line_iter():
- """
- libtcodpy.line_iter
- """
+
+@pytest.mark.filterwarnings("ignore")
+def test_line_iter() -> None:
+ """libtcodpy.line_iter."""
assert list(libtcodpy.line_iter(*LINE_ARGS)) == INCLUSIVE_RESULTS
-def test_bsp():
- """
- commented out statements work in libtcod-cffi
- """
+
+@pytest.mark.filterwarnings("ignore")
+def test_bsp() -> None:
bsp = libtcodpy.bsp_new_with_size(0, 0, 64, 64)
- repr(bsp) # test __repr__ on leaf
+ repr(bsp) # test __repr__ on leaf
libtcodpy.bsp_resize(bsp, 0, 0, 32, 32)
- assert bsp != None
+ assert bsp is not None
# test getter/setters
bsp.x = bsp.x
@@ -383,34 +447,34 @@ def test_bsp():
bsp.level = bsp.level
# cover functions on leaf
- #self.assertFalse(libtcodpy.bsp_left(bsp))
- #self.assertFalse(libtcodpy.bsp_right(bsp))
- #self.assertFalse(libtcodpy.bsp_father(bsp))
+ assert not libtcodpy.bsp_left(bsp)
+ assert not libtcodpy.bsp_right(bsp)
+ assert not libtcodpy.bsp_father(bsp)
assert libtcodpy.bsp_is_leaf(bsp)
assert libtcodpy.bsp_contains(bsp, 1, 1)
- #self.assertFalse(libtcodpy.bsp_contains(bsp, -1, -1))
- #self.assertEqual(libtcodpy.bsp_find_node(bsp, 1, 1), bsp)
- #self.assertFalse(libtcodpy.bsp_find_node(bsp, -1, -1))
+ assert not libtcodpy.bsp_contains(bsp, -1, -1)
+ assert libtcodpy.bsp_find_node(bsp, 1, 1) == bsp
+ assert not libtcodpy.bsp_find_node(bsp, -1, -1)
- libtcodpy.bsp_split_once(bsp, False, 4)
- repr(bsp) # test __repr__ with parent
- libtcodpy.bsp_split_once(bsp, True, 4)
+ libtcodpy.bsp_split_once(bsp, False, 4) # noqa: FBT003
+ repr(bsp) # test __repr__ with parent
+ libtcodpy.bsp_split_once(bsp, True, 4) # noqa: FBT003
repr(bsp)
# cover functions on parent
assert libtcodpy.bsp_left(bsp)
assert libtcodpy.bsp_right(bsp)
- #self.assertFalse(libtcodpy.bsp_father(bsp))
+ assert not libtcodpy.bsp_father(bsp)
assert not libtcodpy.bsp_is_leaf(bsp)
- #self.assertEqual(libtcodpy.bsp_father(libtcodpy.bsp_left(bsp)), bsp)
- #self.assertEqual(libtcodpy.bsp_father(libtcodpy.bsp_right(bsp)), bsp)
+ assert libtcodpy.bsp_father(libtcodpy.bsp_left(bsp)) == bsp # type: ignore[arg-type]
+ assert libtcodpy.bsp_father(libtcodpy.bsp_right(bsp)) == bsp # type: ignore[arg-type]
libtcodpy.bsp_split_recursive(bsp, None, 4, 2, 2, 1.0, 1.0)
# cover bsp_traverse
- def traverse(node, user_data):
- return True
+ def traverse(_node: tcod.bsp.BSP, _user_data: object) -> None:
+ return None
libtcodpy.bsp_traverse_pre_order(bsp, traverse)
libtcodpy.bsp_traverse_in_order(bsp, traverse)
@@ -425,19 +489,24 @@ def traverse(node, user_data):
libtcodpy.bsp_delete(bsp)
-def test_map():
- map = libtcodpy.map_new(16, 16)
- assert libtcodpy.map_get_width(map) == 16
- assert libtcodpy.map_get_height(map) == 16
+
+@pytest.mark.filterwarnings("ignore")
+def test_map() -> None:
+ WIDTH, HEIGHT = 13, 17 # noqa: N806
+ map = libtcodpy.map_new(WIDTH, HEIGHT) # noqa: A001
+ assert libtcodpy.map_get_width(map) == WIDTH
+ assert libtcodpy.map_get_height(map) == HEIGHT
libtcodpy.map_copy(map, map)
libtcodpy.map_clear(map)
- libtcodpy.map_set_properties(map, 0, 0, True, True)
+ libtcodpy.map_set_properties(map, 0, 0, True, True) # noqa: FBT003
assert libtcodpy.map_is_transparent(map, 0, 0)
assert libtcodpy.map_is_walkable(map, 0, 0)
libtcodpy.map_is_in_fov(map, 0, 0)
libtcodpy.map_delete(map)
-def test_color():
+
+@pytest.mark.filterwarnings("ignore")
+def test_color() -> None:
color_a = libtcodpy.Color(0, 1, 2)
assert list(color_a) == [0, 1, 2]
assert color_a[0] == color_a.r
@@ -445,25 +514,28 @@ def test_color():
assert color_a[2] == color_a.b
color_a[1] = 3
- color_a['b'] = color_a['b']
+ color_a["b"] = color_a["b"]
assert list(color_a) == [0, 3, 2]
- assert color_a == color_a
+ assert color_a == color_a # noqa: PLR0124
color_b = libtcodpy.Color(255, 255, 255)
assert color_a != color_b
- color = libtcodpy.color_lerp(color_a, color_b, 0.5)
+ color = libtcodpy.color_lerp(color_a, color_b, 0.5) # type: ignore[arg-type]
libtcodpy.color_set_hsv(color, 0, 0, 0)
- libtcodpy.color_get_hsv(color)
+ libtcodpy.color_get_hsv(color) # type: ignore[arg-type]
libtcodpy.color_scale_HSV(color, 0, 0)
-def test_color_repr():
- Color = libtcodpy.Color
+
+def test_color_repr() -> None:
+ Color = libtcodpy.Color # noqa: N806
col = Color(0, 1, 2)
- assert eval(repr(col)) == col
+ assert eval(repr(col)) == col # noqa: S307
-def test_color_math():
+
+@pytest.mark.filterwarnings("ignore")
+def test_color_math() -> None:
color_a = libtcodpy.Color(0, 1, 2)
color_b = libtcodpy.Color(0, 10, 20)
@@ -472,18 +544,24 @@ def test_color_math():
assert libtcodpy.Color(255, 255, 255) * color_a == color_a
assert color_a * 100 == libtcodpy.Color(0, 100, 200)
-def test_color_gen_map():
+
+@pytest.mark.filterwarnings("ignore")
+def test_color_gen_map() -> None:
colors = libtcodpy.color_gen_map([(0, 0, 0), (255, 255, 255)], [0, 8])
assert colors[0] == libtcodpy.Color(0, 0, 0)
assert colors[-1] == libtcodpy.Color(255, 255, 255)
-def test_namegen_parse():
- libtcodpy.namegen_parse('libtcod/data/namegen/jice_celtic.cfg')
- assert libtcodpy.namegen_generate('Celtic female')
+
+@pytest.mark.filterwarnings("ignore")
+def test_namegen_parse() -> None:
+ libtcodpy.namegen_parse("libtcod/data/namegen/jice_celtic.cfg")
+ assert libtcodpy.namegen_generate("Celtic female")
assert libtcodpy.namegen_get_sets()
libtcodpy.namegen_destroy()
-def test_noise():
+
+@pytest.mark.filterwarnings("ignore")
+def test_noise() -> None:
noise = libtcodpy.noise_new(1)
libtcodpy.noise_set_type(noise, libtcodpy.NOISE_SIMPLEX)
libtcodpy.noise_get(noise, [0])
@@ -491,7 +569,9 @@ def test_noise():
libtcodpy.noise_get_turbulence(noise, [0], 4)
libtcodpy.noise_delete(noise)
-def test_random():
+
+@pytest.mark.filterwarnings("ignore")
+def test_random() -> None:
rand = libtcodpy.random_get_instance()
rand = libtcodpy.random_new()
libtcodpy.random_delete(rand)
@@ -510,96 +590,104 @@ def test_random():
libtcodpy.random_delete(rand)
libtcodpy.random_delete(backup)
-def test_heightmap():
- hmap = libtcodpy.heightmap_new(16, 16)
- repr(hmap)
+
+@pytest.mark.filterwarnings("ignore")
+def test_heightmap() -> None:
+ h_map = libtcodpy.heightmap_new(16, 16)
+ repr(h_map)
noise = libtcodpy.noise_new(2)
# basic operations
- libtcodpy.heightmap_set_value(hmap, 0, 0, 1)
- libtcodpy.heightmap_add(hmap, 1)
- libtcodpy.heightmap_scale(hmap, 1)
- libtcodpy.heightmap_clear(hmap)
- libtcodpy.heightmap_clamp(hmap, 0, 0)
- libtcodpy.heightmap_copy(hmap, hmap)
- libtcodpy.heightmap_normalize(hmap)
- libtcodpy.heightmap_lerp_hm(hmap, hmap, hmap, 0)
- libtcodpy.heightmap_add_hm(hmap, hmap, hmap)
- libtcodpy.heightmap_multiply_hm(hmap, hmap, hmap)
+ libtcodpy.heightmap_set_value(h_map, 0, 0, 1)
+ libtcodpy.heightmap_add(h_map, 1)
+ libtcodpy.heightmap_scale(h_map, 1)
+ libtcodpy.heightmap_clear(h_map)
+ libtcodpy.heightmap_clamp(h_map, 0, 0)
+ libtcodpy.heightmap_copy(h_map, h_map)
+ libtcodpy.heightmap_normalize(h_map)
+ libtcodpy.heightmap_lerp_hm(h_map, h_map, h_map, 0)
+ libtcodpy.heightmap_add_hm(h_map, h_map, h_map)
+ libtcodpy.heightmap_multiply_hm(h_map, h_map, h_map)
# modifying the heightmap
- libtcodpy.heightmap_add_hill(hmap, 0, 0, 4, 1)
- libtcodpy.heightmap_dig_hill(hmap, 0, 0, 4, 1)
- libtcodpy.heightmap_rain_erosion(hmap, 1, 1, 1)
- libtcodpy.heightmap_kernel_transform(hmap, 3, [-1, 1, 0], [0, 0, 0],
- [.33, .33, .33], 0, 1)
- libtcodpy.heightmap_add_voronoi(hmap, 10, 3, [1,3,5])
- libtcodpy.heightmap_add_fbm(hmap, noise, 1, 1, 1, 1, 4, 1, 1)
- libtcodpy.heightmap_scale_fbm(hmap, noise, 1, 1, 1, 1, 4, 1, 1)
- libtcodpy.heightmap_dig_bezier(hmap, [0, 16, 16, 0], [0, 0, 16, 16],
- 1, 1, 1, 1)
+ libtcodpy.heightmap_add_hill(h_map, 0, 0, 4, 1)
+ libtcodpy.heightmap_dig_hill(h_map, 0, 0, 4, 1)
+ libtcodpy.heightmap_rain_erosion(h_map, 1, 1, 1)
+ libtcodpy.heightmap_kernel_transform(h_map, 3, [-1, 1, 0], [0, 0, 0], [0.33, 0.33, 0.33], 0, 1)
+ libtcodpy.heightmap_add_voronoi(h_map, 10, 3, [1, 3, 5])
+ libtcodpy.heightmap_add_fbm(h_map, noise, 1, 1, 1, 1, 4, 1, 1)
+ libtcodpy.heightmap_scale_fbm(h_map, noise, 1, 1, 1, 1, 4, 1, 1)
+ libtcodpy.heightmap_dig_bezier(h_map, (0, 16, 16, 0), (0, 0, 16, 16), 1, 1, 1, 1)
# read data
- libtcodpy.heightmap_get_value(hmap, 0, 0)
- libtcodpy.heightmap_get_interpolated_value(hmap, 0, 0)
+ libtcodpy.heightmap_get_value(h_map, 0, 0)
+ libtcodpy.heightmap_get_interpolated_value(h_map, 0, 0)
- libtcodpy.heightmap_get_slope(hmap, 0, 0)
- libtcodpy.heightmap_get_normal(hmap, 0, 0, 0)
- libtcodpy.heightmap_count_cells(hmap, 0, 0)
- libtcodpy.heightmap_has_land_on_border(hmap, 0)
- libtcodpy.heightmap_get_minmax(hmap)
+ libtcodpy.heightmap_get_slope(h_map, 0, 0)
+ libtcodpy.heightmap_get_normal(h_map, 0, 0, 0)
+ libtcodpy.heightmap_count_cells(h_map, 0, 0)
+ libtcodpy.heightmap_has_land_on_border(h_map, 0)
+ libtcodpy.heightmap_get_minmax(h_map)
libtcodpy.noise_delete(noise)
- libtcodpy.heightmap_delete(hmap)
-
-MAP = (
- '############',
- '# ### #',
- '# ### #',
- '# ### ####',
- '## #### # ##',
- '## ####',
- '############',
- )
+ libtcodpy.heightmap_delete(h_map)
+
+
+MAP: NDArray[Any] = np.array(
+ [
+ list(line)
+ for line in (
+ "############",
+ "# ### #",
+ "# ### #",
+ "# ### ####",
+ "## #### # ##",
+ "## ####",
+ "############",
+ )
+ ]
+)
-MAP_WIDTH = len(MAP[0])
-MAP_HEIGHT = len(MAP)
+MAP_HEIGHT, MAP_WIDTH = MAP.shape
POINT_A = (2, 2)
POINT_B = (9, 2)
POINT_C = (9, 4)
-POINTS_AB = POINT_A + POINT_B # valid path
-POINTS_AC = POINT_A + POINT_C # invalid path
+POINTS_AB = POINT_A + POINT_B # valid path
+POINTS_AC = POINT_A + POINT_C # invalid path
+
-@pytest.fixture()
-def map_():
- map_ = libtcodpy.map_new(MAP_WIDTH, MAP_HEIGHT)
- for y, line in enumerate(MAP):
- for x, ch in enumerate(line):
- libtcodpy.map_set_properties(map_, x, y, ch == ' ', ch == ' ')
+@pytest.fixture
+def map_() -> Iterator[tcod.map.Map]:
+ map_ = tcod.map.Map(MAP_WIDTH, MAP_HEIGHT)
+ map_.walkable[...] = map_.transparent[...] = MAP[...] == " "
yield map_
libtcodpy.map_delete(map_)
-@pytest.fixture()
-def path_callback(map_):
- def callback(ox, oy, dx, dy, user_data):
- if libtcodpy.map_is_walkable(map_, dx, dy):
- return 1
- return 0
+
+@pytest.fixture
+def path_callback(map_: tcod.map.Map) -> Callable[[int, int, int, int, None], bool]:
+ def callback(_ox: int, _oy: int, dx: int, dy: int, _user_data: None) -> bool:
+ return bool(map_.walkable[dy, dx])
+
return callback
-def test_map_fov(map_):
+
+@pytest.mark.filterwarnings("ignore")
+def test_map_fov(map_: tcod.map.Map) -> None:
libtcodpy.map_compute_fov(map_, *POINT_A)
-def test_astar(map_):
+
+@pytest.mark.filterwarnings("ignore")
+def test_astar(map_: tcod.map.Map) -> None:
astar = libtcodpy.path_new_using_map(map_)
- assert not libtcodpy.path_compute(astar, *POINTS_AC)
- assert libtcodpy.path_size(astar) == 0
- assert libtcodpy.path_compute(astar, *POINTS_AB)
- assert libtcodpy.path_get_origin(astar) == POINT_A
- assert libtcodpy.path_get_destination(astar) == POINT_B
+ assert not libtcodpy.path_compute(astar, *POINTS_AC)
+ assert libtcodpy.path_size(astar) == 0
+ assert libtcodpy.path_compute(astar, *POINTS_AB)
+ assert libtcodpy.path_get_origin(astar) == POINT_A
+ assert libtcodpy.path_get_destination(astar) == POINT_B
libtcodpy.path_reverse(astar)
assert libtcodpy.path_get_origin(astar) == POINT_B
assert libtcodpy.path_get_destination(astar) == POINT_A
@@ -608,24 +696,31 @@ def test_astar(map_):
assert libtcodpy.path_size(astar) > 0
assert not libtcodpy.path_is_empty(astar)
+ x: int | None
+ y: int | None
+
for i in range(libtcodpy.path_size(astar)):
x, y = libtcodpy.path_get(astar, i)
while (x, y) != (None, None):
- x, y = libtcodpy.path_walk(astar, False)
+ x, y = libtcodpy.path_walk(astar, False) # noqa: FBT003
libtcodpy.path_delete(astar)
-def test_astar_callback(map_, path_callback):
+
+@pytest.mark.filterwarnings("ignore")
+def test_astar_callback(map_: tcod.map.Map, path_callback: Callable[[int, int, int, int, Any], bool]) -> None:
astar = libtcodpy.path_new_using_function(
libtcodpy.map_get_width(map_),
libtcodpy.map_get_height(map_),
path_callback,
- )
+ )
libtcodpy.path_compute(astar, *POINTS_AB)
libtcodpy.path_delete(astar)
-def test_dijkstra(map_):
+
+@pytest.mark.filterwarnings("ignore")
+def test_dijkstra(map_: tcod.map.Map) -> None:
path = libtcodpy.dijkstra_new(map_)
libtcodpy.dijkstra_compute(path, *POINT_A)
@@ -639,6 +734,9 @@ def test_dijkstra(map_):
libtcodpy.dijkstra_reverse(path)
+ x: int | None
+ y: int | None
+
for i in range(libtcodpy.dijkstra_size(path)):
x, y = libtcodpy.dijkstra_get(path, i)
@@ -647,23 +745,24 @@ def test_dijkstra(map_):
libtcodpy.dijkstra_delete(path)
-def test_dijkstra_callback(map_, path_callback):
+
+@pytest.mark.filterwarnings("ignore")
+def test_dijkstra_callback(map_: tcod.map.Map, path_callback: Callable[[int, int, int, int, Any], bool]) -> None:
path = libtcodpy.dijkstra_new_using_function(
libtcodpy.map_get_width(map_),
libtcodpy.map_get_height(map_),
path_callback,
- )
+ )
libtcodpy.dijkstra_compute(path, *POINT_A)
libtcodpy.dijkstra_delete(path)
-def test_alpha_blend(console):
+@pytest.mark.filterwarnings("ignore")
+def test_alpha_blend(console: tcod.console.Console) -> None:
for i in range(256):
- libtcodpy.console_put_char(console, 0, 0, 'x',
- libtcodpy.BKGND_ALPHA(i))
- libtcodpy.console_put_char(console, 0, 0, 'x',
- libtcodpy.BKGND_ADDALPHA(i))
+ libtcodpy.console_put_char(console, 0, 0, "x", libtcodpy.BKGND_ALPHA(i))
+ libtcodpy.console_put_char(console, 0, 0, "x", libtcodpy.BKGND_ADDALPHA(i))
-if __name__ == '__main__':
+if __name__ == "__main__":
pytest.main()
diff --git a/tests/test_noise.py b/tests/test_noise.py
index 9044de95..28825328 100644
--- a/tests/test_noise.py
+++ b/tests/test_noise.py
@@ -1,30 +1,105 @@
-#!/usr/bin/env python
+"""Tests for the tcod.noise module."""
import copy
import pickle
-import unittest
-import tdl
+import numpy as np
+import pytest
-class NoiseTests(unittest.TestCase):
+import tcod.noise
+import tcod.random
- def test_noise(self):
- n = tdl.noise.Noise()
- self.assertIsInstance(n.get_point(0, 0), float)
- n = tdl.noise.Noise('Wavelet', 'FBM', seed=-1)
- self.assertIsInstance(n.get_point(0, 0), float)
- def test_noise_exceptions(self):
- with self.assertRaises(tdl.TDLError):
- tdl.noise.Noise(algorithm='')
- with self.assertRaises(tdl.TDLError):
- tdl.noise.Noise(mode='')
+@pytest.mark.parametrize("implementation", tcod.noise.Implementation)
+@pytest.mark.parametrize("algorithm", tcod.noise.Algorithm)
+@pytest.mark.parametrize("hurst", [0.5, 0.75])
+@pytest.mark.parametrize("lacunarity", [2, 3])
+@pytest.mark.parametrize("octaves", [4, 6])
+def test_noise_class(
+ implementation: tcod.noise.Implementation,
+ algorithm: tcod.noise.Algorithm,
+ hurst: float,
+ lacunarity: float,
+ octaves: float,
+) -> None:
+ noise = tcod.noise.Noise(
+ 2,
+ algorithm=algorithm,
+ implementation=implementation,
+ hurst=hurst,
+ lacunarity=lacunarity,
+ octaves=octaves,
+ )
+ # cover attributes
+ assert noise.dimensions == 2 # noqa: PLR2004
+ noise.algorithm = noise.algorithm
+ noise.implementation = noise.implementation
+ noise.octaves = noise.octaves
+ assert noise.hurst
+ assert noise.lacunarity
- def test_noise_copy(self):
- self.assertIsInstance(copy.copy(tdl.noise.Noise()), tdl.noise.Noise)
+ assert noise.get_point(0, 0) == noise[0, 0]
+ assert noise[0] == noise[0, 0]
+ noise.sample_mgrid(np.mgrid[:2, :3])
+ noise.sample_ogrid(np.ogrid[:2, :3])
- def test_noise_pickle(self):
- self.assertIsInstance(pickle.loads(pickle.dumps(tdl.noise.Noise())),
- tdl.noise.Noise)
+ np.testing.assert_equal(
+ noise.sample_mgrid(np.mgrid[:2, :3]),
+ noise.sample_ogrid(np.ogrid[:2, :3]),
+ )
+ np.testing.assert_equal(noise.sample_mgrid(np.mgrid[:2, :3]), noise[tuple(np.mgrid[:2, :3])])
+ repr(noise)
+def test_noise_samples() -> None:
+ noise = tcod.noise.Noise(2, tcod.noise.Algorithm.SIMPLEX, tcod.noise.Implementation.SIMPLE)
+ np.testing.assert_equal(
+ noise.sample_mgrid(np.mgrid[:32, :24]),
+ noise.sample_ogrid(np.ogrid[:32, :24]),
+ )
+
+
+def test_noise_errors() -> None:
+ with pytest.raises(ValueError, match=r"dimensions must be in range"):
+ tcod.noise.Noise(0)
+ with pytest.raises(ValueError, match=r"-1 is not a valid implementation"):
+ tcod.noise.Noise(1, implementation=-1)
+ noise = tcod.noise.Noise(2)
+ with pytest.raises(ValueError, match=r"mgrid.shape\[0\] must equal self.dimensions"):
+ noise.sample_mgrid(np.mgrid[:2, :2, :2])
+ with pytest.raises(ValueError, match=r"len\(ogrid\) must equal self.dimensions"):
+ noise.sample_ogrid(np.ogrid[:2, :2, :2])
+ with pytest.raises(IndexError):
+ noise[0, 0, 0, 0, 0]
+ with pytest.raises(TypeError):
+ noise[object]
+
+
+@pytest.mark.parametrize("implementation", tcod.noise.Implementation)
+def test_noise_pickle(implementation: tcod.noise.Implementation) -> None:
+ rand = tcod.random.Random(tcod.random.MERSENNE_TWISTER, 42)
+ noise = tcod.noise.Noise(2, implementation, seed=rand)
+ noise2 = copy.copy(noise)
+ np.testing.assert_equal(
+ noise.sample_ogrid(np.ogrid[:3, :1]),
+ noise2.sample_ogrid(np.ogrid[:3, :1]),
+ )
+
+
+def test_noise_copy() -> None:
+ rand = tcod.random.Random(tcod.random.MERSENNE_TWISTER, 42)
+ noise = tcod.noise.Noise(2, seed=rand)
+ noise2 = pickle.loads(pickle.dumps(noise))
+ np.testing.assert_equal(
+ noise.sample_ogrid(np.ogrid[:3, :1]),
+ noise2.sample_ogrid(np.ogrid[:3, :1]),
+ )
+
+ noise3 = tcod.noise.Noise(2, seed=None)
+ assert repr(noise3) == repr(pickle.loads(pickle.dumps(noise3)))
+ noise4 = tcod.noise.Noise(2, seed=42)
+ assert repr(noise4) == repr(pickle.loads(pickle.dumps(noise4)))
+
+
+def test_noise_grid() -> None:
+ tcod.noise.grid((2, 2), scale=2) # Check int scale
diff --git a/tests/test_parser.py b/tests/test_parser.py
index c5a9a115..67cd10e3 100644
--- a/tests/test_parser.py
+++ b/tests/test_parser.py
@@ -1,106 +1,79 @@
-#!/usr/bin/env python
+"""Test old libtcodpy parser."""
-import os
+from pathlib import Path
+from typing import Any
import pytest
import tcod as libtcod
-@pytest.mark.filterwarnings("ignore:Using this class is not recommended.")
-def test_parser():
- print ('***** File Parser test *****')
- parser=libtcod.parser_new()
- struct=libtcod.parser_new_struct(parser, b'myStruct')
- libtcod.struct_add_property(struct, b'bool_field', libtcod.TYPE_BOOL, True)
- libtcod.struct_add_property(struct, b'char_field', libtcod.TYPE_CHAR, True)
- libtcod.struct_add_property(struct, b'int_field', libtcod.TYPE_INT, True)
- libtcod.struct_add_property(struct, b'float_field', libtcod.TYPE_FLOAT, True)
- libtcod.struct_add_property(struct, b'color_field', libtcod.TYPE_COLOR, True)
- libtcod.struct_add_property(struct, b'dice_field', libtcod.TYPE_DICE, True)
- libtcod.struct_add_property(struct, b'string_field', libtcod.TYPE_STRING,
- True)
- libtcod.struct_add_list_property(struct, b'bool_list', libtcod.TYPE_BOOL,
- True)
- libtcod.struct_add_list_property(struct, b'char_list', libtcod.TYPE_CHAR,
- True)
- libtcod.struct_add_list_property(struct, b'integer_list', libtcod.TYPE_INT,
- True)
- libtcod.struct_add_list_property(struct, b'float_list', libtcod.TYPE_FLOAT,
- True)
- libtcod.struct_add_list_property(struct, b'string_list', libtcod.TYPE_STRING,
- True)
- libtcod.struct_add_list_property(struct, b'color_list', libtcod.TYPE_COLOR,
- True)
-## # dice lists doesn't work yet
-## libtcod.struct_add_list_property(struct, b'dice_list', libtcod.TYPE_DICE,
-## True)
+
+@pytest.mark.filterwarnings("ignore")
+def test_parser() -> None:
+ print("***** File Parser test *****")
+ parser = libtcod.parser_new()
+ struct = libtcod.parser_new_struct(parser, "myStruct")
+ libtcod.struct_add_property(struct, "bool_field", libtcod.TYPE_BOOL, True) # noqa: FBT003
+ libtcod.struct_add_property(struct, "char_field", libtcod.TYPE_CHAR, True) # noqa: FBT003
+ libtcod.struct_add_property(struct, "int_field", libtcod.TYPE_INT, True) # noqa: FBT003
+ libtcod.struct_add_property(struct, "float_field", libtcod.TYPE_FLOAT, True) # noqa: FBT003
+ libtcod.struct_add_property(struct, "color_field", libtcod.TYPE_COLOR, True) # noqa: FBT003
+ libtcod.struct_add_property(struct, "dice_field", libtcod.TYPE_DICE, True) # noqa: FBT003
+ libtcod.struct_add_property(struct, "string_field", libtcod.TYPE_STRING, True) # noqa: FBT003
+ libtcod.struct_add_list_property(struct, "bool_list", libtcod.TYPE_BOOL, True) # noqa: FBT003
+ libtcod.struct_add_list_property(struct, "char_list", libtcod.TYPE_CHAR, True) # noqa: FBT003
+ libtcod.struct_add_list_property(struct, "integer_list", libtcod.TYPE_INT, True) # noqa: FBT003
+ libtcod.struct_add_list_property(struct, "float_list", libtcod.TYPE_FLOAT, True) # noqa: FBT003
+ libtcod.struct_add_list_property(struct, "string_list", libtcod.TYPE_STRING, True) # noqa: FBT003
+ libtcod.struct_add_list_property(struct, "color_list", libtcod.TYPE_COLOR, True) # noqa: FBT003
# default listener
- print ('***** Default listener *****')
- libtcod.parser_run(parser, os.path.join('libtcod', 'data', 'cfg', 'sample.cfg'))
- print ('bool_field : ', \
- libtcod.parser_get_bool_property(parser, b'myStruct.bool_field'))
- print ('char_field : ', \
- libtcod.parser_get_char_property(parser, b'myStruct.char_field'))
- print ('int_field : ', \
- libtcod.parser_get_int_property(parser, b'myStruct.int_field'))
- print ('float_field : ', \
- libtcod.parser_get_float_property(parser, b'myStruct.float_field'))
- print ('color_field : ', \
- libtcod.parser_get_color_property(parser, b'myStruct.color_field'))
- print ('dice_field : ', \
- libtcod.parser_get_dice_property(parser, b'myStruct.dice_field'))
- print ('string_field : ', \
- libtcod.parser_get_string_property(parser, b'myStruct.string_field'))
- print ('bool_list : ', \
- libtcod.parser_get_list_property(parser, b'myStruct.bool_list',
- libtcod.TYPE_BOOL))
- print ('char_list : ', \
- libtcod.parser_get_list_property(parser, b'myStruct.char_list',
- libtcod.TYPE_CHAR))
- print ('integer_list : ', \
- libtcod.parser_get_list_property(parser, b'myStruct.integer_list',
- libtcod.TYPE_INT))
- print ('float_list : ', \
- libtcod.parser_get_list_property(parser, b'myStruct.float_list',
- libtcod.TYPE_FLOAT))
- print ('string_list : ', \
- libtcod.parser_get_list_property(parser, b'myStruct.string_list',
- libtcod.TYPE_STRING))
- print ('color_list : ', \
- libtcod.parser_get_list_property(parser, b'myStruct.color_list',
- libtcod.TYPE_COLOR))
-## print ('dice_list : ', \
-## libtcod.parser_get_list_property(parser, b'myStruct.dice_list',
-## libtcod.TYPE_DICE))
+ print("***** Default listener *****")
+ libtcod.parser_run(parser, Path("libtcod/data/cfg/sample.cfg"))
+ print("bool_field : ", libtcod.parser_get_bool_property(parser, "myStruct.bool_field"))
+ print("char_field : ", libtcod.parser_get_char_property(parser, "myStruct.char_field"))
+ print("int_field : ", libtcod.parser_get_int_property(parser, "myStruct.int_field"))
+ print("float_field : ", libtcod.parser_get_float_property(parser, "myStruct.float_field"))
+ print("color_field : ", libtcod.parser_get_color_property(parser, "myStruct.color_field"))
+ print("dice_field : ", libtcod.parser_get_dice_property(parser, "myStruct.dice_field"))
+ print("string_field : ", libtcod.parser_get_string_property(parser, "myStruct.string_field"))
+ print("bool_list : ", libtcod.parser_get_list_property(parser, "myStruct.bool_list", libtcod.TYPE_BOOL))
+ print("char_list : ", libtcod.parser_get_list_property(parser, "myStruct.char_list", libtcod.TYPE_CHAR))
+ print("integer_list : ", libtcod.parser_get_list_property(parser, "myStruct.integer_list", libtcod.TYPE_INT))
+ print("float_list : ", libtcod.parser_get_list_property(parser, "myStruct.float_list", libtcod.TYPE_FLOAT))
+ print("string_list : ", libtcod.parser_get_list_property(parser, "myStruct.string_list", libtcod.TYPE_STRING))
+ print("color_list : ", libtcod.parser_get_list_property(parser, "myStruct.color_list", libtcod.TYPE_COLOR))
# custom listener
- print ('***** Custom listener *****')
+ print("***** Custom listener *****")
+
class MyListener:
- def new_struct(self, struct, name):
- print ('new structure type', libtcod.struct_get_name(struct), \
- ' named ', name )
+ def new_struct(self, struct: Any, name: str) -> bool: # noqa: ANN401
+ print("new structure type", libtcod.struct_get_name(struct), " named ", name)
return True
- def new_flag(self, name):
- print ('new flag named ', name)
+
+ def new_flag(self, name: str) -> bool:
+ print("new flag named ", name)
return True
- def new_property(self,name, typ, value):
- type_names = ['NONE', 'BOOL', 'CHAR', 'INT', 'FLOAT', 'STRING', \
- 'COLOR', 'DICE']
- type_name = type_names[typ & 0xff]
+
+ def new_property(self, name: str, typ: int, value: Any) -> bool: # noqa: ANN401
+ type_names = ["NONE", "BOOL", "CHAR", "INT", "FLOAT", "STRING", "COLOR", "DICE"]
+ type_name = type_names[typ & 0xFF]
if typ & libtcod.TYPE_LIST:
- type_name = 'LIST<%s>' % type_name
- print ('new property named ', name,' type ',type_name, \
- ' value ', value)
+ type_name = f"LIST<{type_name}>"
+ print("new property named ", name, " type ", type_name, " value ", value)
return True
- def end_struct(self, struct, name):
- print ('end structure type', libtcod.struct_get_name(struct), \
- ' named ', name)
+
+ def end_struct(self, struct: Any, name: str) -> bool: # noqa: ANN401
+ print("end structure type", libtcod.struct_get_name(struct), " named ", name)
return True
- def error(self,msg):
- print ('error : ', msg)
+
+ def error(self, msg: str) -> bool:
+ print("error : ", msg)
return True
- libtcod.parser_run(parser, os.path.join('libtcod','data','cfg','sample.cfg'), MyListener())
-if __name__ == '__main__':
+ libtcod.parser_run(parser, Path("libtcod/data/cfg/sample.cfg"), MyListener())
+
+
+if __name__ == "__main__":
test_parser()
diff --git a/tests/test_random.py b/tests/test_random.py
index afe16be0..764ae988 100644
--- a/tests/test_random.py
+++ b/tests/test_random.py
@@ -1,26 +1,43 @@
+"""Test random number generators."""
import copy
import pickle
+from pathlib import Path
-import tcod
+import pytest
-def test_tcod_random():
+import tcod.random
+
+SCRIPT_DIR = Path(__file__).parent
+
+
+def test_tcod_random() -> None:
rand = tcod.random.Random(tcod.random.COMPLEMENTARY_MULTIPLY_WITH_CARRY)
- assert 0 <= rand.randint(0, 100) <= 100
- assert 0 <= rand.uniform(0, 100) <= 100
- rand.guass(0, 1)
- rand.inverse_guass(0, 1)
+ assert 0 <= rand.randint(0, 100) <= 100 # noqa: PLR2004
+ assert 0 <= rand.uniform(0, 100) <= 100 # noqa: PLR2004
+ with pytest.warns(FutureWarning, match=r"typo"):
+ rand.guass(0, 1)
+ with pytest.warns(FutureWarning, match=r"typo"):
+ rand.inverse_guass(0, 1)
+
-def test_tcod_random_copy():
+def test_tcod_random_copy() -> None:
rand = tcod.random.Random(tcod.random.MERSENNE_TWISTER)
rand2 = copy.copy(rand)
assert rand.uniform(0, 1) == rand2.uniform(0, 1)
assert rand.uniform(0, 1) == rand2.uniform(0, 1)
assert rand.uniform(0, 1) == rand2.uniform(0, 1)
-def test_tcod_random_pickle():
+
+def test_tcod_random_pickle() -> None:
rand = tcod.random.Random(tcod.random.MERSENNE_TWISTER)
rand2 = pickle.loads(pickle.dumps(rand))
assert rand.uniform(0, 1) == rand2.uniform(0, 1)
assert rand.uniform(0, 1) == rand2.uniform(0, 1)
assert rand.uniform(0, 1) == rand2.uniform(0, 1)
+
+
+def test_load_rng_v13_1() -> None:
+ rand: tcod.random.Random = pickle.loads((SCRIPT_DIR / "data/random_v13.pkl").read_bytes())
+ assert rand.randint(0, 0xFFFF) == 56422 # noqa: PLR2004
+ assert rand.randint(0, 0xFFFF) == 15795 # noqa: PLR2004
diff --git a/tests/test_sdl.py b/tests/test_sdl.py
new file mode 100644
index 00000000..42433510
--- /dev/null
+++ b/tests/test_sdl.py
@@ -0,0 +1,118 @@
+"""Test SDL specific features."""
+
+import sys
+
+import numpy as np
+import pytest
+
+import tcod.sdl.render
+import tcod.sdl.sys
+import tcod.sdl.video
+
+
+def test_sdl_window(uses_window: None) -> None: # noqa: ARG001
+ assert tcod.sdl.video.get_grabbed_window() is None
+ window = tcod.sdl.video.new_window(1, 1)
+ window.raise_window()
+ window.maximize()
+ window.restore()
+ window.minimize()
+ window.hide()
+ window.show()
+ assert window.title == sys.argv[0]
+ window.title = "Title"
+ assert window.title == "Title"
+ assert window.opacity == 1.0
+ window.position = window.position
+ window.fullscreen = window.fullscreen
+ window.resizable = window.resizable
+ window.size = window.size
+ window.min_size = window.min_size
+ window.max_size = window.max_size
+ window.border_size # noqa: B018
+ window.set_icon(np.zeros((32, 32, 3), dtype=np.uint8))
+ with pytest.raises(TypeError):
+ window.set_icon(np.zeros((32, 32, 5), dtype=np.uint8))
+ with pytest.raises(TypeError):
+ window.set_icon(np.zeros((32, 32), dtype=np.uint8))
+ window.opacity = window.opacity
+ window.grab = window.grab
+
+ window.start_text_input(capitalization=tcod.sdl.video.Capitalization.NONE, multiline=False)
+ window.set_text_input_area((0, 0, 8, 8), 0)
+ window.stop_text_input()
+
+
+def test_sdl_window_bad_types() -> None:
+ with pytest.raises(TypeError):
+ tcod.sdl.video.Window(tcod.ffi.cast("SDL_Window*", tcod.ffi.NULL))
+ with pytest.raises(TypeError):
+ tcod.sdl.video.Window(tcod.ffi.new("SDL_Rect*"))
+
+
+def test_sdl_screen_saver(uses_window: None) -> None: # noqa: ARG001
+ tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.VIDEO)
+ assert tcod.sdl.video.screen_saver_allowed(False) is False # noqa: FBT003
+ assert tcod.sdl.video.screen_saver_allowed(True) is True # noqa: FBT003
+ assert tcod.sdl.video.screen_saver_allowed() is True
+
+
+def test_sdl_render(uses_window: None) -> None: # noqa: ARG001
+ window = tcod.sdl.video.new_window(4, 4)
+ render = tcod.sdl.render.new_renderer(window, driver="software", vsync=False)
+ render.clear()
+ render.present()
+ render.clear()
+ rgb = render.upload_texture(np.zeros((8, 8, 3), np.uint8))
+ assert (rgb.width, rgb.height) == (8, 8)
+ assert rgb.access == tcod.sdl.render.TextureAccess.STATIC
+ assert rgb.format == tcod.lib.SDL_PIXELFORMAT_RGB24
+ rgb.alpha_mod = rgb.alpha_mod
+ rgb.blend_mode = rgb.blend_mode
+ rgb.color_mod = rgb.color_mod
+ rgb.scale_mode = rgb.scale_mode
+ rgba = render.upload_texture(np.zeros((8, 8, 4), np.uint8), access=tcod.sdl.render.TextureAccess.TARGET)
+ with render.set_render_target(rgba):
+ render.copy(rgb)
+ with pytest.raises(TypeError):
+ render.upload_texture(np.zeros((8, 8, 5), np.uint8))
+
+ assert render.read_pixels(rect=(0, 0, 3, 4)).shape == (4, 3, 4)
+ assert (render.read_pixels(format="RGB") == (0, 0, 0)).all()
+ assert (render.read_pixels() == (0, 0, 0, 255)).all()
+
+ render.draw_point((0, 0))
+ render.draw_line((0, 0), (1, 1))
+ render.draw_rect((0, 0, 1, 1))
+ render.fill_rect((0, 0, 1, 1))
+
+ render.draw_points([(0, 0)])
+ render.draw_lines([(0, 0), (1, 1)])
+ render.draw_rects([(0, 0, 1, 1)])
+ render.fill_rects([(0, 0, 1, 1)])
+
+ for dtype in (np.intc, np.int8, np.uint8, np.float32, np.float16):
+ render.draw_points(np.ones((3, 2), dtype=dtype))
+ render.draw_lines(np.ones((3, 2), dtype=dtype))
+ render.draw_rects(np.ones((3, 4), dtype=dtype))
+ render.fill_rects(np.ones((3, 4), dtype=dtype))
+
+ with pytest.raises(TypeError, match=r"shape\[1\] must be 2"):
+ render.draw_points(np.ones((3, 1), dtype=dtype))
+ with pytest.raises(TypeError, match=r"must have 2 axes"):
+ render.draw_points(np.ones((3,), dtype=dtype))
+
+ render.geometry(
+ None,
+ np.zeros((1, 2), np.float32),
+ np.zeros((1, 4), np.float32),
+ np.zeros((1, 2), np.float32),
+ np.zeros((3,), np.uint8),
+ )
+
+
+def test_sdl_render_bad_types() -> None:
+ with pytest.raises(TypeError):
+ tcod.sdl.render.Renderer(tcod.ffi.cast("SDL_Renderer*", tcod.ffi.NULL))
+ with pytest.raises(TypeError):
+ tcod.sdl.render.Renderer(tcod.ffi.new("SDL_Rect*"))
diff --git a/tests/test_sdl_audio.py b/tests/test_sdl_audio.py
new file mode 100644
index 00000000..94bd151e
--- /dev/null
+++ b/tests/test_sdl_audio.py
@@ -0,0 +1,136 @@
+"""Test tcod.sdl.audio module."""
+
+import contextlib
+import sys
+import time
+from collections.abc import Callable
+from typing import Any
+
+import numpy as np
+import pytest
+from numpy.typing import NDArray
+
+import tcod.sdl.audio
+
+
+def device_works(device: Callable[[], tcod.sdl.audio.AudioDevice]) -> bool:
+ try:
+ device().open().close()
+ except RuntimeError:
+ return False
+ return True
+
+
+needs_audio_device = pytest.mark.xfail(
+ not device_works(tcod.sdl.audio.get_default_playback), reason="This test requires an audio device"
+)
+needs_audio_capture = pytest.mark.xfail(
+ not device_works(tcod.sdl.audio.get_default_recording), reason="This test requires an audio capture device"
+)
+
+
+def test_devices() -> None:
+ list(tcod.sdl.audio.get_devices())
+ list(tcod.sdl.audio.get_capture_devices())
+
+
+@needs_audio_device
+def test_audio_device() -> None:
+ with tcod.sdl.audio.open(frequency=44100, format=np.float32, channels=2, paused=True) as device:
+ assert not device.stopped
+ device.convert(np.zeros(4, dtype=np.float32), 22050)
+ assert device.convert(np.zeros((4, 4), dtype=np.float32)).shape[1] == device.channels
+ device.convert(np.zeros(4, dtype=np.int8)).shape[0]
+ assert device.paused is True
+ device.paused = False
+ assert device.paused is False
+ device.paused = True
+ with contextlib.closing(tcod.sdl.audio.BasicMixer(device, frequency=44100, channels=2)) as mixer:
+ assert mixer.play(np.zeros(4, np.float32)).busy
+ mixer.play(np.zeros(0, np.float32))
+ mixer.play(np.full(1, 0.01, np.float32), on_end=lambda _: None)
+ mixer.play(np.full(1, 0.01, np.float32), loops=2, on_end=lambda _: None)
+ mixer.play(np.full(4, 0.01, np.float32), loops=2).stop()
+ mixer.play(np.full(100000, 0.01, np.float32))
+ with pytest.raises(TypeError, match=r".*must be dtype=float32.*was dtype=int32"):
+ mixer.play(np.zeros(1, np.int32))
+ time.sleep(0.001)
+ mixer.stop()
+
+
+@needs_audio_capture
+def test_audio_capture() -> None:
+ with contextlib.closing(tcod.sdl.audio.get_default_recording().open()) as device:
+ device.new_stream(np.float32, 1, 11025).dequeue_audio()
+
+
+@needs_audio_device
+def test_audio_device_repr() -> None:
+ with contextlib.closing(tcod.sdl.audio.get_default_playback().open()) as device:
+ assert not device.stopped
+ assert "paused=False" in repr(device)
+
+
+def test_convert_bad_shape() -> None:
+ with pytest.raises(TypeError):
+ tcod.sdl.audio.convert_audio(
+ np.zeros((1, 1, 1), np.float32), 8000, out_rate=8000, out_format=np.float32, out_channels=1
+ )
+
+
+def test_convert_bad_type() -> None:
+ with pytest.raises(TypeError, match=r".*bool"):
+ tcod.sdl.audio.convert_audio(np.zeros(8, bool), 8000, out_rate=8000, out_format=np.float32, out_channels=1)
+ with pytest.raises(RuntimeError, match=r"Parameter 'src_spec->format' is invalid"):
+ tcod.sdl.audio.convert_audio(np.zeros(8, np.int64), 8000, out_rate=8000, out_format=np.float32, out_channels=1)
+
+
+def test_convert_float64() -> None:
+ np.testing.assert_array_equal(
+ tcod.sdl.audio.convert_audio(
+ np.ones(8, np.float64), 8000, out_rate=8000, out_format=np.float32, out_channels=1
+ ),
+ np.ones((8, 1), np.float32),
+ )
+
+
+@needs_audio_device
+def test_audio_callback() -> None:
+ class CheckCalled:
+ was_called: bool = False
+
+ def __call__(self, device: tcod.sdl.audio.AudioDevice, stream: NDArray[Any]) -> None:
+ self.was_called = True
+ assert isinstance(device, tcod.sdl.audio.AudioDevice)
+ assert isinstance(stream, np.ndarray)
+ assert len(stream.shape) == 2 # noqa: PLR2004
+
+ check_called = CheckCalled()
+ with tcod.sdl.audio.open(callback=check_called, paused=False) as device:
+ assert not device.stopped
+ while not check_called.was_called:
+ time.sleep(0.001)
+
+
+@pytest.mark.skipif(sys.version_info < (3, 8), reason="Needs sys.unraisablehook support")
+@pytest.mark.filterwarnings("ignore::pytest.PytestUnraisableExceptionWarning")
+@pytest.mark.skip(reason="Unsupported, causes too many issues")
+@needs_audio_device
+def test_audio_callback_unraisable() -> None:
+ """Test unraisable error in audio callback.
+
+ This can't be checked with pytest very well, so at least make sure this doesn't crash.
+ """
+
+ class CheckCalled:
+ was_called: bool = False
+
+ def __call__(self, _device: tcod.sdl.audio.AudioDevice, _stream: NDArray[Any]) -> None:
+ self.was_called = True
+ raise Exception("Test unraisable error") # noqa: EM101, TRY002, TRY003
+
+ check_called = CheckCalled()
+ with tcod.sdl.audio.open(callback=check_called, paused=False) as device:
+ assert not device.stopped
+ while not check_called.was_called:
+ time.sleep(0.001)
diff --git a/tests/test_tcod.py b/tests/test_tcod.py
index 9245d68f..3f8c1d9b 100644
--- a/tests/test_tcod.py
+++ b/tests/test_tcod.py
@@ -1,47 +1,56 @@
-#!/usr/bin/env python
+"""Tests for newer tcod API."""
import copy
import pickle
+from typing import Any, NoReturn
import numpy as np
import pytest
+from numpy.typing import DTypeLike, NDArray
-from common import tcod, raise_Exception
-import tcod.noise
+import tcod
+import tcod.bsp
+import tcod.console
+import tcod.context
+import tcod.map
import tcod.path
+import tcod.random
+from tcod import libtcodpy
-def test_line_error():
- """
- test exception propagation
- """
- with pytest.raises(Exception):
- tcod.line(*LINE_ARGS, py_callback=raise_Exception)
+def raise_exception(*_args: object) -> NoReturn:
+ raise RuntimeError("testing exception") # noqa: TRY003, EM101
-def test_tcod_bsp():
- """
- test tcod additions to BSP
- """
+def test_line_error() -> None:
+ """Test exception propagation."""
+ with pytest.raises(RuntimeError), pytest.warns():
+ libtcodpy.line(0, 0, 10, 10, py_callback=raise_exception)
+
+
+@pytest.mark.filterwarnings("ignore:Iterate over nodes using")
+@pytest.mark.filterwarnings("ignore:Use pre_order method instead of walk.")
+def test_tcod_bsp() -> None:
+ """Test tcod additions to BSP."""
bsp = tcod.bsp.BSP(0, 0, 32, 32)
assert bsp.level == 0
assert not bsp.horizontal
assert not bsp.children
- with pytest.raises(Exception):
- tcod.bsp_traverse_pre_order(bsp, raise_Exception)
+ with pytest.raises(RuntimeError):
+ libtcodpy.bsp_traverse_pre_order(bsp, raise_exception)
bsp.split_recursive(3, 4, 4, 1, 1)
for node in bsp.walk():
assert isinstance(node, tcod.bsp.BSP)
- assert bsp != 'asd'
+ assert bsp.children
# test that operations on deep BSP nodes preserve depth
sub_bsp = bsp.children[0]
- sub_bsp.split_recursive(3, 2, 2, 1, 1)
- assert sub_bsp.children[0].level == 2
+ sub_bsp.split_recursive(3, 2, 2, 1, 1, seed=tcod.random.Random(seed=42))
+ assert sub_bsp.children[0].level == 2 # noqa: PLR2004
# cover find_node method
assert bsp.find_node(0, 0)
@@ -51,181 +60,56 @@ def test_tcod_bsp():
str(bsp)
-@pytest.mark.filterwarnings("ignore:Directly access a consoles")
-def test_array_read_write(console):
- FG = (255, 254, 253)
- BG = (1, 2, 3)
- CH = ord('&')
- tcod.console_put_char_ex(console, 0, 0, CH, FG, BG)
- assert console.ch[0, 0] == CH
- assert tuple(console.fg[0, 0]) == FG
- assert tuple(console.bg[0, 0]) == BG
-
- tcod.console_put_char_ex(console, 1, 2, CH, FG, BG)
- assert console.ch[2, 1] == CH
- assert tuple(console.fg[2, 1]) == FG
- assert tuple(console.bg[2, 1]) == BG
-
- console.clear()
- assert console.ch[1, 1] == ord(' ')
- assert tuple(console.fg[1, 1]) == (255, 255, 255)
- assert tuple(console.bg[1, 1]) == (0, 0, 0)
-
- ch_slice = console.ch[1, :]
- ch_slice[2] = CH
- console.fg[1, ::2] = FG
- console.bg[...] = BG
-
- assert tcod.console_get_char(console, 2, 1) == CH
- assert tuple(tcod.console_get_char_foreground(console, 2, 1)) == FG
- assert tuple(tcod.console_get_char_background(console, 2, 1)) == BG
-
-
-def test_console_defaults(console):
- console.default_bg = [2, 3, 4]
- assert console.default_bg == (2, 3, 4)
-
- console.default_fg = (4, 5, 6)
- assert console.default_fg == (4, 5, 6)
-
- console.default_bg_blend = tcod.BKGND_ADD
- assert console.default_bg_blend == tcod.BKGND_ADD
-
- console.default_alignment = tcod.RIGHT
- assert console.default_alignment == tcod.RIGHT
-
-@pytest.mark.filterwarnings("ignore:Parameter names have been moved around,")
-def test_console_methods(console):
- console.put_char(0, 0, ord('@'))
- console.print_(0, 0, 'Test')
- console.print_rect(0, 0, 2, 8, 'a b c d e f')
- console.get_height_rect(0, 0, 2, 8, 'a b c d e f')
- console.rect(0, 0, 2, 2, True)
- console.hline(0, 1, 10)
- console.vline(1, 0, 10)
- console.print_frame(0, 0, 8, 8, 'Frame')
- console.blit(0, 0, 0, 0, console, 0, 0)
- console.blit(0, 0, 0, 0, console, 0, 0, key_color=(0, 0, 0))
- console.set_key_color((254, 0, 254))
-
-
-def test_console_pickle(console):
- console.ch[...] = ord('.')
- console.fg[...] = (10, 20, 30)
- console.bg[...] = (1, 2, 3)
- console2 = pickle.loads(pickle.dumps(console))
- assert (console.ch == console2.ch).all()
- assert (console.fg == console2.fg).all()
- assert (console.bg == console2.bg).all()
-
-
-def test_console_pickle_fortran():
- console = tcod.console.Console(2, 3, order='F')
- console2 = pickle.loads(pickle.dumps(console))
- assert console.ch.strides == console2.ch.strides
- assert console.fg.strides == console2.fg.strides
- assert console.bg.strides == console2.bg.strides
-
-
-def test_tcod_map_set_bits():
- map_ = tcod.map.Map(2,2)
-
- assert map_.transparent[:].any() == False
- assert map_.walkable[:].any() == False
- assert map_.fov[:].any() == False
+@pytest.mark.filterwarnings("ignore:Use map.+ to check for this")
+@pytest.mark.filterwarnings("ignore:This class may perform poorly")
+def test_tcod_map_set_bits() -> None:
+ map_ = tcod.map.Map(2, 2)
+
+ assert not map_.transparent[:].any()
+ assert not map_.walkable[:].any()
+ assert not map_.fov[:].any()
map_.transparent[1, 0] = True
- assert tcod.map_is_transparent(map_, 0, 1) == True
+ assert libtcodpy.map_is_transparent(map_, 0, 1)
map_.walkable[1, 0] = True
- assert tcod.map_is_walkable(map_, 0, 1) == True
+ assert libtcodpy.map_is_walkable(map_, 0, 1)
map_.fov[1, 0] = True
- assert tcod.map_is_in_fov(map_, 0, 1) == True
+ assert libtcodpy.map_is_in_fov(map_, 0, 1)
-def test_tcod_map_get_bits():
- map_ = tcod.map.Map(2,2)
+@pytest.mark.filterwarnings("ignore:This class may perform poorly")
+def test_tcod_map_get_bits() -> None:
+ map_ = tcod.map.Map(2, 2)
map_.transparent[0]
-def test_tcod_map_copy():
+@pytest.mark.filterwarnings("ignore:This class may perform poorly")
+def test_tcod_map_copy() -> None:
map_ = tcod.map.Map(3, 3)
map_.transparent[:] = True
- assert (map_.transparent.tolist() == copy.copy(map_).transparent.tolist())
+ assert map_.transparent.tolist() == copy.copy(map_).transparent.tolist()
-def test_tcod_map_pickle():
+@pytest.mark.filterwarnings("ignore:This class may perform poorly")
+def test_tcod_map_pickle() -> None:
map_ = tcod.map.Map(3, 3)
map_.transparent[:] = True
map2 = pickle.loads(pickle.dumps(copy.copy(map_)))
- assert (map_.transparent[:].tolist() == map2.transparent[:].tolist())
+ assert map_.transparent[:].tolist() == map2.transparent[:].tolist()
-def test_tcod_map_pickle_fortran():
- map_ = tcod.map.Map(2, 3, order='F')
- map2 = pickle.loads(pickle.dumps(copy.copy(map_)))
- assert map_._Map__buffer.strides == map2._Map__buffer.strides
+@pytest.mark.filterwarnings("ignore:This class may perform poorly")
+def test_tcod_map_pickle_fortran() -> None:
+ map_ = tcod.map.Map(2, 3, order="F")
+ map2: tcod.map.Map = pickle.loads(pickle.dumps(copy.copy(map_)))
+ assert map_._buffer.strides == map2._buffer.strides
assert map_.transparent.strides == map2.transparent.strides
assert map_.walkable.strides == map2.walkable.strides
assert map_.fov.strides == map2.fov.strides
-@pytest.mark.parametrize('implementation', [tcod.noise.SIMPLE,
- tcod.noise.FBM,
- tcod.noise.TURBULENCE])
-def test_noise_class(implementation):
- noise = tcod.noise.Noise(2, tcod.NOISE_SIMPLEX, implementation)
- # cover attributes
- assert noise.dimensions == 2
- noise.algorithm = noise.algorithm
- noise.implementation = noise.implementation
- noise.octaves = noise.octaves
- assert noise.hurst
- assert noise.lacunarity
-
- noise.get_point(0, 0)
- noise.sample_mgrid(np.mgrid[:2,:3])
- noise.sample_ogrid(np.ogrid[:2,:3])
-
-
-def test_noise_samples():
- noise = tcod.noise.Noise(2, tcod.NOISE_SIMPLEX, tcod.noise.SIMPLE)
- np.testing.assert_equal(
- noise.sample_mgrid(np.mgrid[:32,:24]),
- noise.sample_ogrid(np.ogrid[:32,:24]),
- )
-
-
-def test_noise_errors():
- with pytest.raises(ValueError):
- tcod.noise.Noise(0)
- with pytest.raises(ValueError):
- tcod.noise.Noise(1, implementation=-1)
- noise = tcod.noise.Noise(2)
- with pytest.raises(ValueError):
- noise.sample_mgrid(np.mgrid[:2,:2,:2])
- with pytest.raises(ValueError):
- noise.sample_ogrid(np.ogrid[:2,:2,:2])
-
-
-@pytest.mark.parametrize('implementation',
- [tcod.noise.SIMPLE, tcod.noise.FBM, tcod.noise.TURBULENCE])
-def test_noise_pickle(implementation):
- rand = tcod.random.Random(tcod.random.MERSENNE_TWISTER, 42)
- noise = tcod.noise.Noise(2, implementation, seed=rand)
- noise2 = copy.copy(noise)
- assert (noise.sample_ogrid(np.ogrid[:3,:1]) ==
- noise2.sample_ogrid(np.ogrid[:3,:1])).all()
-
-
-def test_noise_copy():
- rand = tcod.random.Random(tcod.random.MERSENNE_TWISTER, 42)
- noise = tcod.noise.Noise(2, seed=rand)
- noise2 = pickle.loads(pickle.dumps(noise))
- assert (noise.sample_ogrid(np.ogrid[:3,:1]) ==
- noise2.sample_ogrid(np.ogrid[:3,:1])).all()
-
-
-def test_color_class():
+@pytest.mark.filterwarnings("ignore")
+def test_color_class() -> None:
assert tcod.black == tcod.black
assert tcod.black == (0, 0, 0)
assert tcod.black == [0, 0, 0]
@@ -233,67 +117,103 @@ def test_color_class():
assert tcod.white * 1 == tcod.white
assert tcod.white * tcod.black == tcod.black
assert tcod.white - tcod.white == tcod.black
- assert tcod.black + (2, 2, 2) - (1, 1, 1) == (1, 1, 1)
- assert not tcod.black == None
+ assert tcod.black + (2, 2, 2) - (1, 1, 1) == (1, 1, 1) # noqa: RUF005
- color = tcod.Color()
+ color = libtcodpy.Color()
color.r = 1
color.g = 2
color.b = 3
assert color == (1, 2, 3)
-@pytest.mark.parametrize('dtype', [np.int8, np.int16, np.int32,
- np.uint8, np.uint16, np.uint32, np.float32])
-def test_path_numpy(dtype):
- map_np = np.ones((6, 6), dtype=dtype)
+@pytest.mark.parametrize("dtype", [np.int8, np.int16, np.int32, np.uint8, np.uint16, np.uint32, np.float32])
+def test_path_numpy(dtype: DTypeLike) -> None:
+ map_np: NDArray[Any] = np.ones((6, 6), dtype=dtype)
map_np[1:4, 1:4] = 0
astar = tcod.path.AStar(map_np, 0)
- astar = pickle.loads(pickle.dumps(astar)) # test pickle
- astar = tcod.path.AStar(astar.cost, 0) # use existing cost attribute
- assert len(astar.get_path(0, 0, 5, 5)) == 10
+ astar = pickle.loads(pickle.dumps(astar)) # test pickle
+ astar = tcod.path.AStar(astar.cost, 0) # use existing cost attribute
+ assert len(astar.get_path(0, 0, 5, 5)) == 10 # noqa: PLR2004
dijkstra = tcod.path.Dijkstra(map_np, 0)
dijkstra.set_goal(0, 0)
- assert len(dijkstra.get_path(5, 5)) == 10
- repr(dijkstra) # cover __repr__ methods
+ assert len(dijkstra.get_path(5, 5)) == 10 # noqa: PLR2004
+ repr(dijkstra) # cover __repr__ methods
# cover errors
- with pytest.raises(ValueError):
+ with pytest.raises(ValueError, match=r"Array must have a 2d shape, shape is \(3, 3, 3\)"):
tcod.path.AStar(np.ones((3, 3, 3), dtype=dtype))
- with pytest.raises(ValueError):
+ with pytest.raises(ValueError, match=r"dtype must be one of dict_keys"):
tcod.path.AStar(np.ones((2, 2), dtype=np.float64))
-def path_cost(this_x, this_y, dest_x, dest_y):
- return 1
+def path_cost(_this_x: int, _this_y: int, _dest_x: int, _dest_y: int) -> bool:
+ return True
-def test_path_callback():
- astar = tcod.path.AStar(
- tcod.path.EdgeCostCallback(path_cost, (10, 10))
- )
+
+def test_path_callback() -> None:
+ astar = tcod.path.AStar(tcod.path.EdgeCostCallback(path_cost, (10, 10)))
astar = pickle.loads(pickle.dumps(astar))
- assert astar.get_path(0, 0, 5, 0) == \
- [(1, 0), (2, 0), (3, 0), (4, 0), (5, 0)]
- repr(astar) # cover __repr__ methods
+ assert astar.get_path(0, 0, 5, 0) == [(1, 0), (2, 0), (3, 0), (4, 0), (5, 0)]
+ repr(astar) # cover __repr__ methods
-def test_key_repr():
- Key = tcod.Key
+def test_key_repr() -> None:
+ Key = libtcodpy.Key # noqa: N806
key = Key(vk=1, c=2, shift=True)
assert key.vk == 1
- assert key.c == 2
+ assert key.c == 2 # noqa: PLR2004
assert key.shift
- key_copy = eval(repr(key))
+ key_copy = eval(repr(key)) # noqa: S307
assert key.vk == key_copy.vk
assert key.c == key_copy.c
assert key.shift == key_copy.shift
-def test_mouse_repr():
- Mouse = tcod.Mouse
+def test_mouse_repr() -> None:
+ Mouse = libtcodpy.Mouse # noqa: N806
mouse = Mouse(x=1, lbutton=True)
- mouse_copy = eval(repr(mouse))
+ mouse_copy = eval(repr(mouse)) # noqa: S307
assert mouse.x == mouse_copy.x
assert mouse.lbutton == mouse_copy.lbutton
+
+
+def test_cffi_structs() -> None:
+ # Make sure cffi structures are the correct size.
+ tcod.ffi.new("SDL_Event*")
+
+
+@pytest.mark.filterwarnings("ignore")
+def test_recommended_size(console: tcod.console.Console) -> None: # noqa: ARG001
+ tcod.console.recommended_size()
+
+
+@pytest.mark.filterwarnings("ignore")
+def test_context(uses_window: None) -> None: # noqa: ARG001
+ with tcod.context.new_window(32, 32, renderer=libtcodpy.RENDERER_SDL2):
+ pass
+ width, height = 16, 4
+ with tcod.context.new_terminal(columns=width, rows=height, renderer=libtcodpy.RENDERER_SDL2) as context:
+ console = tcod.console.Console(*context.recommended_console_size())
+ context.present(console)
+ assert context.sdl_window_p is not None
+ assert context.renderer_type >= 0
+ context.change_tileset(tcod.tileset.Tileset(16, 16))
+ context.pixel_to_tile(0, 0)
+ context.pixel_to_subtile(0, 0)
+ with pytest.raises(RuntimeError, match=r".*context has been closed"):
+ context.present(console)
+
+
+def test_event_watch() -> None:
+ def handle_events(_event: tcod.event.Event) -> None:
+ pass
+
+ tcod.event.add_watch(handle_events)
+ with pytest.warns(RuntimeWarning, match=r"nothing was added"):
+ tcod.event.add_watch(handle_events)
+
+ tcod.event.remove_watch(handle_events)
+ with pytest.warns(RuntimeWarning, match=r"nothing was removed"):
+ tcod.event.remove_watch(handle_events)
diff --git a/tests/test_tdl.py b/tests/test_tdl.py
deleted file mode 100755
index 9ccc3aea..00000000
--- a/tests/test_tdl.py
+++ /dev/null
@@ -1,308 +0,0 @@
-#!/usr/bin/env python
-import sys
-import os
-
-import unittest
-import random
-import itertools
-import copy
-import pickle
-import gc
-
-import tdl
-
-#ERROR_RANGE = 100 # a number to test out of bound errors
-WIDTH, HEIGHT = 30, 20
-WINWIDTH, WINHEIGHT = 10, 10
-
-DEFAULT_CHAR = (0x20, (0, 0, 0), (0, 0, 0))
-
-IS_PYTHON2 = (sys.version_info[0] == 2)
-
-class TDLTemplate(unittest.TestCase):
- "Nearly all tests need tdl.init to be called"
-
- @classmethod
- def setUpClass(cls):
- cls.console = tdl.init(WIDTH, HEIGHT, 'TDL UnitTest', False, renderer='GLSL')
- # make a small window in the corner
- cls.window = tdl.Window(cls.console, 0, 0, WINWIDTH, WINHEIGHT)
-
- def setUp(self):
- tdl.event.get()
- self.console.set_colors((0,0,0), (0,0,0))
- self.console.clear()
-
- @classmethod
- def tearDownClass(cls):
- del cls.console
- gc.collect() # make sure console.__del__ is called quickly
-
- def in_window(self, x, y):
- "returns True if this point is in the Window"
- return 0 <= x < WINWIDTH and 0 <= y < WINHEIGHT
-
- def randomize_console(self):
- "Randomize the console returning the random data"
- noise = [((x, y), self.get_random_character()) for x,y in self.get_drawables()]
- for (x, y), graphic in noise:
- self.console.draw_char(x, y, *graphic)
- return noise # [((x, y), (cg, fg, bg)), ...]
-
- def flush(self):
- 'Pump events and refresh screen so show progress'
- #tdl.event.get() # no longer needed
- tdl.flush()
-
- def get_random_character(self):
- "returns a tuple with a random character and colors (ch, fg, bg)"
- return (random.getrandbits(8), self.get_random_color(), self.get_random_color())
-
- def get_random_color(self):
- "returns a single random color"
- return (random.getrandbits(8), random.getrandbits(8), random.getrandbits(8))
-
- def get_drawables(self, console=None):
- """return a list of all drawable (x,y) positions
- defaults to self.console
- """
- if console is None:
- console = self.console
- w, h = console.get_size()
- return itertools.product(range(w), range(h))
-
- def get_undrawables(self, console=None):
- """return a list of (x,y) positions that should raise errors when used
- positions are mostly random and will have at least one over the bounds of each side and each corner"""
- if console is None:
- console = self.console
- w, h = console.get_size()
- for y in range(-1, h+1):
- yield -w-1, y
- yield w, y
- for x in range(0, w):
- yield x, h
- yield x, -h-1
-
- def compare_consoles(self, consoleA, consoleB, errorMsg='colors should be the same'):
- "Compare two console assuming they match and failing if they don't"
- self.assertEqual(consoleA.get_size(), consoleB.get_size(), 'consoles should be the same size')
- for x, y in self.get_drawables(consoleA):
- self.assertEqual(consoleA.get_char(x, y),
- consoleB.get_char(x, y), '%s, position: (%i, %i)' % (errorMsg, x, y))
-
-class BasicTests(TDLTemplate):
-
- def test_clearConsole(self):
- self.randomize_console()
- _, fg, bg = self.get_random_character()
- ch = 0x20 # space
- self.console.clear(fg, bg)
- self.flush()
- for x,y in self.get_drawables():
- self.assertEqual((ch, fg, bg), self.console.get_char(x, y), 'color should be changed with clear')
- _, fg2, bg2 = self.get_random_character()
- self.window.clear(fg2, bg2)
- self.flush()
- for x,y in self.get_drawables():
- if self.in_window(x, y):
- self.assertEqual((ch, fg2, bg2), self.console.get_char(x, y), 'color in window should be changed')
- else:
- self.assertEqual((ch, fg, bg), self.console.get_char(x, y), 'color outside of window should persist')
-
- def test_cloneConsole(self):
- noiseData = self.randomize_console()
- clone = copy.copy(self.console)
- self.compare_consoles(self.console, clone, 'console clone should match root console')
-
- def test_pickleConsole(self):
- noiseData = self.randomize_console()
- pickled = pickle.dumps(self.console)
- clone = pickle.loads(pickled)
- self.compare_consoles(self.console, clone, 'pickled console should match root console')
-
-
-class DrawingTests(TDLTemplate):
-
- def test_draw_charTuples(self):
- "Test passing tuple colors and int characters to draw_char"
- record = {}
- for x,y in self.get_drawables():
- ch, fg, bg = self.get_random_character()
- record[x,y] = (ch, fg, bg)
- self.console.draw_char(x, y, ch, fg, bg)
- self.assertEqual(record[x,y], self.console.get_char(x, y), 'console data should be overwritten')
- self.flush() # show progress
-
- for (x,y), data in record.items():
- self.assertEqual(data, self.console.get_char(x, y), 'draw_char should not overwrite any other tiles')
-
- def test_draw_charWebcolor(self):
- "Test passing web style colors and string characters to draw_char"
- record = {}
- for x,y in self.get_drawables():
- ch, fg, bg = self.get_random_character()
- record[x,y] = (ch, fg, bg)
- ch = chr(ch)
- fg = fg[0] << 16 | fg[1] << 8 | fg[2] # convert to a 0xRRGGBB style number
- bg = bg[0] << 16 | bg[1] << 8 | bg[2]
- self.console.draw_char(x, y, ch, fg, bg)
- self.assertEqual(record[x,y], self.console.get_char(x, y), 'console data should be overwritten')
- self.flush() # show progress
- for (x,y), data in record.items():
- self.assertEqual(data, self.console.get_char(x, y), 'draw_char should not overwrite any other tiles')
-
- #@unittest.skipIf(not __debug__, 'python run with optimized flag, skipping an AssertionError test')
- #def test_draw_charErrors(self):
- # "test out of bounds assertion errors"
- # for x,y in self.get_undrawables():
- # with self.assertRaisesRegexp(AssertionError, r"\(%i, %i\)" % (x, y)):
- # self.console.draw_char(x, y, *(self.get_random_character()))
-
- def test_draw_str(self):
- """quick regression test for draw_str"""
- width, height = self.console.get_size()
- def str_check(array, string, desc):
- fg, bg = self.get_random_color(), self.get_random_color()
- self.console.clear()
- self.console.draw_str(0, 0, string, fg, bg)
- self.flush()
- i = 0
- for y in range(height):
- for x in range(width):
- self.assertEqual(self.console.get_char(x, y), (array[i], fg, bg),
- '%s should be written out' % desc)
- i += 1
-
- # array of numbers
- array = [random.getrandbits(8) for _ in range(width * height)]
- str_check(array, array, 'array of numbers')
-
- # array of strings
- #array = [random.getrandbits(8) for _ in range(width * height)]
- #array_str = [chr(c) for c in array]
- #str_check(array, array_str, 'array of characters')
-
- # standard string
- array = [random.getrandbits(8) for _ in range(width * height)]
- string = ''.join((chr(c) for c in array))
- str_check(array, string, 'standatd string')
-
- # Unicode string - Python 2
- if IS_PYTHON2:
- array = [random.getrandbits(7) for _ in range(width * height)]
- ucode = unicode().join((chr(c) for c in array))
- str_check(array, ucode, 'Unicode string')
-
-
- def test_draw_strArray(self):
- """strings will raise errors if they pass over the end of the console.
- The data will still be written however."""
- width, height = self.console.get_size()
- for x,y in self.get_drawables():
- string = [random.getrandbits(8) for _ in range(random.randint(2, 10))]
- fg, bg = self.get_random_color(), self.get_random_color()
- if len(string) > ((height - y) * width - x): # compare length of string to remaining space on the console
- with self.assertRaises(tdl.TDLError): # expect end of console error
- self.console.draw_str(x, y, string, fg, bg)
- else:
- self.console.draw_str(x, y, string, fg, bg)
- for ch in string: # inspect console for changes
- self.assertEqual(self.console.get_char(x, y), (ch, fg, bg), 'console data should be overwritten, even after an error')
- x += 1
- if x == width:
- x = 0
- y += 1
- if y == height:
- break # end of console
- self.flush() # show progress
-
- #@unittest.skipIf(not __debug__, 'python run with optimized flag, skipping an AssertionError test')
- #def test_draw_strErrors(self):
- # "test out of bounds assertion errors"
- # for x,y in self.get_undrawables():
- # with self.assertRaisesRegexp(AssertionError, r"\(%i, %i\)" % (x, y)):
- # self.console.draw_str(x, y, 'foo', self.get_random_color(), self.get_random_color())
-
- def test_draw_rect(self):
- consoleCopy = tdl.Console(*(self.console.get_size()))
- for x,y in random.sample(list(self.get_drawables()), 20):
- consoleCopy.blit(self.console) # copy the console to compare untouched areas
- ch, fg, bg = self.get_random_character()
- width, height = self.console.get_size()
- width, height = random.randint(1, width - x), random.randint(1, height - y)
- self.console.draw_rect(x, y, width, height, ch, fg, bg)
- self.flush() # show progress
- for testX,testY in self.get_drawables():
- if x <= testX < x + width and y <= testY < y + height:
- self.assertEqual(self.console.get_char(testX, testY), (ch, fg, bg), 'rectangle area should be overwritten')
- else:
- self.assertEqual(self.console.get_char(testX, testY), consoleCopy.get_char(testX, testY), 'this area should remain untouched')
-
- def test_draw_frame(self):
- consoleCopy = tdl.Console(*(self.console.get_size()))
- for x,y in random.sample(list(self.get_drawables()), 20):
- consoleCopy.blit(self.console) # copy the console to compare untouched areas
- ch, fg, bg = self.get_random_character()
- width, height = self.console.get_size()
- width, height = random.randint(1, width - x), random.randint(1, height - y)
- self.console.draw_frame(x, y, width, height, ch, fg, bg)
- self.flush() # show progress
- for testX,testY in self.get_drawables():
- if x + 1 <= testX < x + width - 1 and y + 1 <= testY < y + height - 1:
- self.assertEqual(self.console.get_char(testX, testY), consoleCopy.get_char(testX, testY), 'inner frame should remain untouched')
- elif x <= testX < x + width and y <= testY < y + height:
- self.assertEqual(self.console.get_char(testX, testY), (ch, fg, bg), 'frame area should be overwritten')
- else:
- self.assertEqual(self.console.get_char(testX, testY), consoleCopy.get_char(testX, testY), 'outer frame should remain untouched')
-
- #@unittest.skipIf(not __debug__, 'python run with optimized flag, skipping an AssertionError test')
- #def test_draw_rectFrameErrors(self):
- # for x,y in self.get_drawables():
- # ch, fg, bg = self.get_random_character()
- # width, height = self.console.get_size()
- # width, height = random.randint(x + width, x + width + ERROR_RANGE), random.randint(y + height, y + height + ERROR_RANGE)
- # with self.assertRaises(AssertionError):
- # self.console.draw_rect(x, y, width, height, ch, fg, bg)
- # with self.assertRaises(AssertionError):
- # self.console.draw_frame(x, y, width, height, ch, fg, bg)
-
- #@unittest.skip("Need this to be faster before unskipping")
- def test_scrolling(self):
- """marks a spot and then scrolls the console, checks to make sure no
- other spots are marked, test also knows if it's out of bounds.
-
- This test is a bit slow, it could be made more efficent by marking
- several areas and not clearing the console every loop.
- """
- scrollTests = set([(0, 0), (WIDTH, HEIGHT)]) # include zero and out of bounds
- while len(scrollTests) < 10: # add 3 more randoms
- scrollTests.add((random.randint(-WIDTH, WIDTH),
- random.randint(-HEIGHT, HEIGHT)))
- for sx, sy in scrollTests:
- noiseData = dict(self.randomize_console())
- self.console.set_colors((0, 0, 0), (0, 0, 0))
- self.console.scroll(sx, sy)
- self.flush() # show progress
- for x, y in self.get_drawables():
- nX = x - sx
- nY = y - sy
- if (nX, nY) in noiseData:
- self.assertEqual(self.console.get_char(x, y), noiseData[nX, nY], 'random noise should be scrolled')
- else:
- self.assertEqual(self.console.get_char(x, y), DEFAULT_CHAR, 'scrolled away positions should be clear')
-
-
-def test_fps():
- tdl.set_fps(0)
- tdl.get_fps()
-
-def suite():
- loader = unittest.TestLoader()
- load = loader.loadTestsFromTestCase
- return unittest.TestSuite([load(BasicTests), load(DrawingTests)])
-
-if __name__ == '__main__':
- suite = suite()
- unittest.TextTestRunner().run(suite)
-
diff --git a/tests/test_tdl_map.py b/tests/test_tdl_map.py
deleted file mode 100644
index bf99335c..00000000
--- a/tests/test_tdl_map.py
+++ /dev/null
@@ -1,104 +0,0 @@
-#!/usr/bin/env python
-
-import unittest
-import itertools
-import pytest
-
-import tdl
-
-class MapTests(unittest.TestCase):
-
- MAP = (
- '############',
- '# ### #',
- '# ### #',
- '# ### ####',
- '## #### # ##',
- '## ####',
- '############',
- )
-
- WIDTH = len(MAP[0])
- HEIGHT = len(MAP)
-
- POINT_A = (2, 2)
- POINT_B = (9, 2)
- POINT_C = (9, 4)
-
- POINTS_AB = POINT_A + POINT_B
- POINTS_AC = POINT_A + POINT_C
-
- @classmethod
- def map_is_transparant(cls, x, y):
- try:
- return cls.MAP[y][x] == ' '
- except IndexError:
- return False
-
- @classmethod
- def path_cost(cls, src_x, src_y, dest_x, dest_y):
- if cls.map_is_transparant(dest_x, dest_y):
- return 1
- return 0
-
-
- def setUp(self):
- self.map = tdl.map.Map(self.WIDTH, self.HEIGHT)
- for y, line in enumerate(self.MAP):
- for x, ch in enumerate(line):
- trans = ch == ' '
- self.map.transparent[x,y] = self.map.walkable[x,y] = trans
- assert self.map.transparent[x,y] == trans
- assert self.map.walkable[x,y] == trans
-
- def test_map_compute_fov(self):
- fov = self.map.compute_fov(*self.POINT_A)
- assert list(fov), 'should be non-empty'
- fov = self.map.compute_fov(*self.POINT_A, fov='PERMISSIVE8')
- assert list(fov), 'should be non-empty'
- with self.assertRaises(tdl.TDLError):
- self.map.compute_fov(*self.POINT_A, fov='invalid option')
-
- def test_map_compute_path(self):
- self.assertTrue(self.map.compute_path(*self.POINTS_AB),
- 'should be non-empty')
- self.assertFalse(self.map.compute_path(*self.POINTS_AC),
- 'invalid path should return an empty list')
-
- def test_map_specials(self):
- for x,y in self.map:
- self.assertTrue((x, y) in self.map)
- self.assertFalse((-1, -1) in self.map)
-
- @pytest.mark.filterwarnings("ignore:This function is very slow.")
- def test_quick_fov(self):
- fov = tdl.map.quick_fov(self.POINT_B[0], self.POINT_B[1],
- self.map_is_transparant, radius=2.5)
- self.assertTrue(fov, 'should be non-empty')
-
- def test_bresenham(self):
- for x1, x2, y1, y2 in itertools.permutations([-4, -2, 4, 4], 4):
- self.assertTrue(tdl.map.bresenham(x1, x2, y1, y2),
- 'should be non-empty')
-
- def test_astar(self):
- pathfinder = tdl.map.AStar(self.WIDTH, self.HEIGHT,
- self.map_is_transparant)
- self.assertTrue(pathfinder.get_path(*self.POINTS_AB))
-
- pathfinder = tdl.map.AStar(self.WIDTH, self.HEIGHT,
- self.path_cost, None, True)
- self.assertTrue(pathfinder.get_path(*self.POINTS_AB))
- self.assertFalse(pathfinder.get_path(*self.POINTS_AC),
- 'invalid path should return an empty list')
-
-
-def test_map_fov_cumulative():
- map_ = tdl.map.Map(3, 3)
- map_.transparent[::2,:] = True # Add map with small divider.
- assert not map_.fov.any()
- map_.compute_fov(1, 0, cumulative=True) # Light left side.
- assert map_.fov.any()
- assert not map_.fov.all()
- map_.compute_fov(1, 2, cumulative=True) # Light both sides.
- assert map_.fov.all()
diff --git a/tests/test_testing.py b/tests/test_testing.py
deleted file mode 100644
index 151d81fe..00000000
--- a/tests/test_testing.py
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/usr/bin/env python
-
-import os
-
-import tcod as libtcod
-
-curdir = os.path.dirname(__file__)
-
-FONT_FILE = os.path.join(curdir, 'data/fonts/consolas10x10_gs_tc.png')
-
-#def test_console():
-# libtcod.console_set_custom_font(FONT_FILE, libtcod.FONT_LAYOUT_TCOD)
-# libtcod.console_init_root(40, 30, 'test', False, libtcod.RENDERER_SDL)
-# libtcod.console_flush()
diff --git a/tests/test_tileset.py b/tests/test_tileset.py
new file mode 100644
index 00000000..78b458dd
--- /dev/null
+++ b/tests/test_tileset.py
@@ -0,0 +1,103 @@
+"""Test for tcod.tileset module."""
+
+from pathlib import Path
+
+import pytest
+
+import tcod.console
+import tcod.tileset
+
+PROJECT_DIR = Path(__file__).parent / ".."
+
+TERMINAL_FONT = PROJECT_DIR / "fonts/libtcod/terminal8x8_aa_ro.png"
+BDF_FONT = PROJECT_DIR / "libtcod/data/fonts/Tamzen5x9r.bdf"
+
+BAD_FILE = PROJECT_DIR / "CHANGELOG.md" # Any existing non-font file
+
+
+def test_proc_block_elements() -> None:
+ tileset = tcod.tileset.Tileset(0, 0)
+ with pytest.deprecated_call():
+ tcod.tileset.procedural_block_elements(tileset=tileset)
+ tileset += tcod.tileset.procedural_block_elements(shape=tileset.tile_shape)
+
+ tileset = tcod.tileset.Tileset(8, 8)
+ with pytest.deprecated_call():
+ tcod.tileset.procedural_block_elements(tileset=tileset)
+ tileset += tcod.tileset.procedural_block_elements(shape=tileset.tile_shape)
+
+
+def test_tileset_mix() -> None:
+ tileset1 = tcod.tileset.Tileset(1, 1)
+ tileset1[ord("a")] = [[0]]
+
+ tileset2 = tcod.tileset.Tileset(1, 1)
+ tileset2[ord("a")] = [[1]]
+ tileset2[ord("b")] = [[1]]
+
+ assert (tileset1 + tileset2)[ord("a")].tolist() == [[[255, 255, 255, 1]]] # Replaces tile
+ assert (tileset1 | tileset2)[ord("a")].tolist() == [[[255, 255, 255, 0]]] # Skips existing tile
+
+
+def test_tileset_contains() -> None:
+ tileset = tcod.tileset.Tileset(1, 1)
+
+ # Missing keys
+ assert None not in tileset
+ assert ord("x") not in tileset
+ assert -1 not in tileset
+ with pytest.raises(KeyError, match=rf"{ord('x')}"):
+ tileset[ord("x")]
+ with pytest.raises(KeyError, match=rf"{ord('x')}"):
+ del tileset[ord("x")]
+ assert len(tileset) == 0
+
+ # Assigned tile is found
+ tileset[ord("x")] = [[255]]
+ assert ord("x") in tileset
+ assert len(tileset) == 1
+
+ # Can be deleted and reassigned
+ del tileset[ord("x")]
+ assert ord("x") not in tileset
+ assert len(tileset) == 0
+ tileset[ord("x")] = [[255]]
+ assert ord("x") in tileset
+ assert len(tileset) == 1
+
+
+def test_tileset_assignment() -> None:
+ tileset = tcod.tileset.Tileset(1, 2)
+ tileset[ord("a")] = [[1], [1]]
+ tileset[ord("b")] = [[[255, 255, 255, 2]], [[255, 255, 255, 2]]]
+
+ with pytest.raises(ValueError, match=r".*must be \(2, 1, 4\) or \(2, 1\), got \(2, 1, 3\)"):
+ tileset[ord("c")] = [[[255, 255, 255]], [[255, 255, 255]]]
+
+ assert tileset.get_tile(ord("d")).shape == (2, 1, 4)
+
+
+def test_tileset_render() -> None:
+ tileset = tcod.tileset.Tileset(1, 2)
+ tileset[ord("x")] = [[255], [0]]
+ console = tcod.console.Console(3, 2)
+ console.rgb[0, 0] = (ord("x"), (255, 0, 0), (0, 255, 0))
+ output = tileset.render(console)
+ assert output.shape == (4, 3, 4)
+ assert output[0:2, 0].tolist() == [[255, 0, 0, 255], [0, 255, 0, 255]]
+
+
+def test_tileset_tilesheet() -> None:
+ tileset = tcod.tileset.load_tilesheet(TERMINAL_FONT, 16, 16, tcod.tileset.CHARMAP_CP437)
+ assert tileset.tile_shape == (8, 8)
+
+ with pytest.raises(RuntimeError):
+ tcod.tileset.load_tilesheet(BAD_FILE, 16, 16, tcod.tileset.CHARMAP_CP437)
+
+
+def test_tileset_bdf() -> None:
+ tileset = tcod.tileset.load_bdf(BDF_FONT)
+ assert tileset.tile_shape == (9, 5)
+
+ with pytest.raises(RuntimeError):
+ tileset = tcod.tileset.load_bdf(BAD_FILE)
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 00000000..784e5f57
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,17 @@
+[tox]
+isolated_build = True
+env_list =
+ py3
+minversion = 4.4.11
+
+[testenv]
+description = run the tests with pytest
+package = wheel
+wheel_build_env = .pkg
+deps =
+ pytest>=6
+ pytest-cov
+ pytest-benchmark
+ pytest-timeout
+commands =
+ pytest --no-window {tty:--color=yes} {posargs}
diff --git a/unittest.cfg b/unittest.cfg
deleted file mode 100644
index 8b014a48..00000000
--- a/unittest.cfg
+++ /dev/null
@@ -1,11 +0,0 @@
-[unittest]
-start-dir = tests
-
-[coverage]
-always-on = True
-coverage = tdl/ tcod/
-
-[output-buffer]
-always-on = True
-stderr = False
-stdout = True