**/__pycache__
.mypy_cache
.ruff_cache
+dist/
-MAIN=pacman
-PYTHON=python3
-CONFIG=config.json
+PACMAN_CONFIG ?= config.json
-
-.PHONY: install run venv debug clean lint lint--strict
+.PHONY: install run venv debug clean lint lint-strict
install:
- @uv sync
+ uv sync
run:
- @uv run $(MAIN) $(CONFIG)
-
+ uv run -m pacman $(PACMAN_CONFIG)
debug:
- @$(PYTHON) -m pdb $(MAIN) $(CONFIG)
+ uv run -m pdb -m pacman $(PACMAN_CONFIG)
clean:
- @find . -type d -name "__pycache__" -exec rm -r {} +
- @find . -type d -name ".mypy_cache" -exec rm -r {} +
- @find . -type d -name "*.pyc" -delete
+ find . -type d -name "__pycache__" -exec rm -r {} +
+ find . -type d -name ".mypy_cache" -exec rm -r {} +
+ find . -type d -name ".ruff_cache" -exec rm -r {} +
+ find . -type d -name "*.pyc" -delete
# @rm -rf .venv
lint:
- @uv run flake8 src
- @uv run mypy src --warn-return-any \
+ uv run flake8 src
+ uv run mypy src --warn-return-any \
--warn-unused-ignores --ignore-missing-imports \
--disallow-untyped-defs
+ uv run ruff check
+ uv run ty check
lint-strict:
- @uv run flake8 src
- @uv run mypy src --strict
\ No newline at end of file
+ uv run flake8 src
+ uv run mypy src --strict
+ uv run ruff check
+ uv run ty check
+
+check:
+ uv run ruff check
+ uv run ty check
+
+build:
+ uv build
"flake8>=7.3.0",
"mypy>=2.3.1",
"ruff>=0.16.4",
+ "shiv>=1.0.8",
"ty>=0.0.75",
]
+"""The pacman project."""
+
from pacman.game import run_game
def main() -> None:
"""Run the game."""
- run_game()
- return None
try:
run_game()
except Exception as e:
--- /dev/null
+"""The pacman executable file."""
+
+from pacman import main
+
+if __name__ == "__main__":
+ main()
from abc import ABC, abstractmethod
from collections.abc import Callable
-from os import PathLike
-from os.path import abspath
from typing import Self, get_args, override
from pacman.ecs.world import Resource, World
@classmethod
@abstractmethod
- def load(cls, ty: type, path: str) -> Self:
+ def load(cls, ty: type, ident: str) -> Self:
"""Load the type from the specified identifier.
Args:
ty:
The type, including generics, of cls.
- path:
- The identifier of the asset, usually a file path.
+ ident:
+ The identifier of the asset.
"""
self._lookup: dict[str, T] = {}
self._asset_ty: type[T] = get_args(resolve_type_aliases(ty))[0]
- def load(self, s: str | PathLike[str]) -> T:
+ def load(self, s: str) -> T:
"""Load the given asset or fetch it from cache."""
- s = abspath(s)
if s not in self._lookup:
- self._lookup[s] = self._asset_ty.load(self._asset_ty, s)
+ self._lookup[s] = self._asset_ty.load(
+ self._asset_ty, "assets/" + s
+ )
return self._lookup[s]
@override
from collections.abc import Iterator
from dataclasses import dataclass
+from importlib.resources import files
from time import time
from typing import cast, override
import pygame
from pygame import K_RETURN, K_a, K_d, K_s, K_w, Vector2
+import pacman
from pacman.ecs.assets import Asset
from pacman.ecs.coord import Coord2D, CoordAbs2D, PropagateTransform
from pacman.ecs.schedule import StartupSchedule, SystemSet, UpdateSchedule
@override
@classmethod
- def load(cls, ty: type, path: str) -> "Sprite2D":
+ def load(cls, ty: type, ident: str) -> "Sprite2D":
del ty
- return Sprite2D(pygame.image.load(path))
+
+ return Sprite2D(
+ pygame.image.load(files(pacman).joinpath(ident).open("rb"))
+ )
def copy(self) -> "Sprite2D":
"""Make a shallow copy of this sprite."""
from collections.abc import Callable
from dataclasses import dataclass, field
from enum import Enum, auto
+from importlib.resources import files
from typing import cast, override
import pygame
from pygame import Color, Vector2
+import pacman
from pacman.ecs.assets import Asset
from pacman.ecs.coord import (
CoordAbs2D,
@override
@classmethod
- def load(cls, ty: type, path: str) -> "Font":
+ def load(cls, ty: type, ident: str) -> "Font":
del ty
- path, size = path.split(":")
- return Font(pygame.font.Font(path, int(size)))
+ ident, size = ident.split(":")
+ return Font(
+ pygame.font.Font(
+ files(pacman).joinpath(ident).open("rb"), int(size)
+ )
+ )
def center_text_for(self, text: str, dims: Vector2) -> CoordBundle:
"""Compute how to center the text in the given rectangle."""
+"""All the gameplay logic of pacman."""
+
import sys
from collections.abc import Callable
from dataclasses import dataclass
from random import random, shuffle
from typing import cast, override
-from mazegenerator import MazeGenerator # type: ignore[import-untyped]
+from mazegenerator import ( # type: ignore[import-untyped, unused-ignore]
+ MazeGenerator,
+)
from pydantic import BaseModel, Field
from pygame import K_BACKSPACE, K_ESCAPE, K_RETURN, K_a, K_c, K_n, K_z, Vector2
from pygame.math import clamp
CoordBundle(*(Vector2(x, y).elementwise() * cell_dims).xy),
Cell(cell, (x, y)),
ShouldRender(),
- assets.load(f"./asset/walls/{cell}.png"),
+ assets.load(f"walls/{cell}.png"),
)
for y, line in enumerate(maze.maze)
for x, cell in enumerate(line)
(
ChildOf(cell),
CoordBundle(),
- assets.load("./asset/misc/superpacgum.png"),
+ assets.load("misc/superpacgum.png"),
ShouldRender(),
SuperPacGum(),
)
(
ChildOf(ent),
CoordBundle(),
- assets.load("./asset/misc/pacgum.png"),
+ assets.load("misc/pacgum.png"),
ShouldRender(),
PacGum(),
)
AnimationSet(
[
[
- assets.load(
- f"./asset/player/player_{direction}{frame}.png"
- )
+ assets.load(f"player/player_{direction}{frame}.png")
for frame in "ABCB"
]
for direction in "URDL"
[
[
assets.load(
- "./asset/enemy/ghost"
+ "enemy/ghost"
+ f"{variant.name}_{direction}.png"
)
]
for direction in "URDL"
]
+ [
- [assets.load("./asset/enemy/ghost_blue.png")],
- [assets.load("./asset/enemy/ghost_dead.png")],
+ [assets.load("enemy/ghost_blue.png")],
+ [assets.load("enemy/ghost_dead.png")],
[
- assets.load("./asset/enemy/ghost_flash.png"),
- assets.load("./asset/enemy/ghost_blue.png"),
+ assets.load("enemy/ghost_flash.png"),
+ assets.load("enemy/ghost_blue.png"),
],
],
6,
GameOver(),
),
)
- font = fonts.load("./asset/fonts/JetBrainsMonoNL-Regular.ttf:200")
+ font = fonts.load("fonts/JetBrainsMonoNL-Regular.ttf:200")
text = state.state
world.spawn(
tuple[ChildOf, ShouldRender, CoordBundle, TextDisplay],
),
)
- font = fonts.load("./asset/fonts/JetBrainsMonoNL-Regular.ttf:40")
+ font = fonts.load("fonts/JetBrainsMonoNL-Regular.ttf:40")
world.spawn(
tuple[ChildOf, ShouldRender, CoordBundle, ComputedText, TextDisplay],
(
),
)
- font = fonts.load("./asset/fonts/JetBrainsMonoNL-Regular.ttf:50")
+ font = fonts.load("fonts/JetBrainsMonoNL-Regular.ttf:50")
world.spawn(
tuple[ChildOf, ShouldRender, CoordBundle, ComputedText, TextDisplay],
(
ComputedText(cast(System[str], fn)),
TextDisplay(
"",
- fonts.load("./asset/fonts/JetBrainsMonoNL-Regular.ttf:30"),
+ fonts.load("fonts/JetBrainsMonoNL-Regular.ttf:30"),
),
GameGui(),
)
ShouldRender(),
AnimationSet(
[
- [sprites.load("./asset/gui/button_unselected.png")],
- [sprites.load("./asset/gui/button_selected.png")],
- [sprites.load("./asset/gui/button_selected.png")],
+ [sprites.load("gui/button_unselected.png")],
+ [sprites.load("gui/button_selected.png")],
+ [sprites.load("gui/button_selected.png")],
],
1,
),
Button(cb),
),
)
- font = fonts.load("./asset/fonts/JetBrainsMonoNL-Regular.ttf:30")
+ font = fonts.load("fonts/JetBrainsMonoNL-Regular.ttf:30")
world.spawn(
tuple[CoordBundle, ShouldRender, TextDisplay, ChildOf],
(
ShouldRender(),
TextDisplay(
"CHEAT MODE ENABLED",
- fonts.load("./asset/fonts/JetBrainsMonoNL-Regular.ttf:100"),
+ fonts.load("fonts/JetBrainsMonoNL-Regular.ttf:100"),
),
CheatMode(),
),
ShouldRender(),
TextDisplay(
s,
- fonts.load("./asset/fonts/JetBrainsMonoNL-Regular.ttf:22"),
+ fonts.load("fonts/JetBrainsMonoNL-Regular.ttf:22"),
),
CoordBundle(depth=-20),
CheatMode(),
ShouldRender(),
TextDisplay(
"Instructions",
- fonts.load("./asset/fonts/JetBrainsMonoNL-Regular.ttf:50"),
+ fonts.load("fonts/JetBrainsMonoNL-Regular.ttf:50"),
),
),
)
ShouldRender(),
TextDisplay(
s,
- fonts.load(
- "./asset/fonts/JetBrainsMonoNL-Regular.ttf:20"
- ),
+ fonts.load("fonts/JetBrainsMonoNL-Regular.ttf:20"),
),
)
for i, s in enumerate(text.split("\n"))
ShouldRender(),
TextDisplay(
"Highscores",
- fonts.load("./asset/fonts/JetBrainsMonoNL-Regular.ttf:50"),
+ fonts.load("fonts/JetBrainsMonoNL-Regular.ttf:50"),
),
),
)
ShouldRender(),
TextDisplay(
f"{i + 1}. {highscores[i][0]} - {highscores[i][1]}",
- fonts.load(
- "./asset/fonts/JetBrainsMonoNL-Regular.ttf:30"
- ),
+ fonts.load("fonts/JetBrainsMonoNL-Regular.ttf:30"),
),
)
for i in range(min(len(highscores), 10))
{ url = "https://files.pythonhosted.org/packages/25/6c/b400476d3ceba681ab929787edc9554f6d88fcc69435eb681b00fc0457a5/ast_serialize-0.8.0-cp39-abi3-win_arm64.whl", hash = "sha256:b2a5978662fd4db463dfb4b974d2b10ac6430b98f5333aabc7051909df3561d0", size = 1083655, upload-time = "2026-08-07T11:29:00.349Z" },
]
+[[package]]
+name = "click"
+version = "8.5.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/c7/0e/7fa0ef50764b67090eca4114772a2abf8b6148198475e54c660b97caeee6/click-8.5.0.tar.gz", hash = "sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34", size = 382235, upload-time = "2026-08-26T13:33:14.56Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl", hash = "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360", size = 125251, upload-time = "2026-08-26T13:33:12.928Z" },
+]
+
[[package]]
name = "flake8"
version = "7.3.0"
{ name = "flake8" },
{ name = "mypy" },
{ name = "ruff" },
+ { name = "shiv" },
{ name = "ty" },
]
{ name = "flake8", specifier = ">=7.3.0" },
{ name = "mypy", specifier = ">=2.3.1" },
{ name = "ruff", specifier = ">=0.16.4" },
+ { name = "shiv", specifier = ">=1.0.8" },
{ name = "ty", specifier = ">=0.0.75" },
]
{ url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" },
]
+[[package]]
+name = "pip"
+version = "26.2.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/fa/e8/0842d757e483e895e670f655084cfe8b8c72580c6769a76b30b2789a0608/pip-26.2.2.tar.gz", hash = "sha256:3a8e5c800492787f33a4114ef54444546c340df428974ebba17e0fd47e11af2f", size = 1863336, upload-time = "2026-10-20T16:36:29.008Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3f/06/3e4420302363fbb71be3c7f35ce042f54bfca330e3e66b3a2e3c4cca579f/pip-26.2.2-py3-none-any.whl", hash = "sha256:fd7f4dfa8190e014013b9f08f3e973f4e4c26ffd179a204895533a7254813a70", size = 1819850, upload-time = "2026-10-20T16:36:27.046Z" },
+]
+
[[package]]
name = "pycodestyle"
version = "2.14.0"
{ url = "https://files.pythonhosted.org/packages/73/e3/7df5a396e445b9ba49ce9a9437439a4d80042c61c0ade199abf8d16de1ac/ruff-0.16.5-py3-none-win_arm64.whl", hash = "sha256:a64abe90968719b851bb7cedffaa8753fbdbdadab483089682db623f3edc587e", size = 10391564, upload-time = "2026-08-27T16:34:16.064Z" },
]
+[[package]]
+name = "setuptools"
+version = "84.2.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/70/b3/c19426f783614d625699a4b247faf628ea9ab1db228674491767b417f9e1/setuptools-84.2.1.tar.gz", hash = "sha256:3ff8b3b5492a0aa66960f4419365a86b84e013070d4313e4d4a6894f5d64d6bc", size = 1230289, upload-time = "2026-10-20T19:01:37.742Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ef/55/6dc6d6bb953bdfc52519a0059592c52b6e3b8ad51cf8256dffceb55b0610/setuptools-84.2.1-py3-none-any.whl", hash = "sha256:ca140b45d5325a9478f880e351484f62fef15f336462e449ca0c03c873d589c2", size = 876263, upload-time = "2026-10-20T19:01:35.678Z" },
+]
+
+[[package]]
+name = "shiv"
+version = "1.0.8"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "click" },
+ { name = "pip" },
+ { name = "setuptools" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/06/85/004e7123b4821c64be6d9bfed27f63147b4dde929a1cd848f137befeada4/shiv-1.0.8.tar.gz", hash = "sha256:2a68d69e98ce81cb5b8fdafbfc1e27efa93e6d89ca14bfae33482e4176f561d6", size = 32806, upload-time = "2024-11-01T19:47:46.189Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e7/ec/afbb46f7c1ab071a50d92424daf149420ca1f0e02dc51239485747151d6c/shiv-1.0.8-py2.py3-none-any.whl", hash = "sha256:a60e4b05a2d2f8b820d567b1d89ee59af731759771c32c282d03c4ceae6aba24", size = 20516, upload-time = "2024-11-01T19:47:45.061Z" },
+]
+
[[package]]
name = "ty"
version = "0.0.75"