Metadata-Version: 2.5
Name: pcg-console-engine
Version: 2.0.0
Summary: Console-first game engine and UI toolkit for Python
Project-URL: Homepage, https://github.com/Umar151515/PCG
Project-URL: Documentation, https://umar151515.github.io/PCG/
Project-URL: Repository, https://github.com/Umar151515/PCG
Project-URL: Issues, https://github.com/Umar151515/PCG/issues
Author: Umar
License-Expression: MIT
License-File: LICENSE
Keywords: ansi,ascii,console,ecs,game-engine,roguelike,terminal,truecolor,tui
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: OS Independent
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Games/Entertainment
Classifier: Topic :: Games/Entertainment :: Arcade
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Classifier: Typing :: Typed
Requires-Python: >=3.10
Provides-Extra: all
Requires-Dist: box2d-py>=2.3.8; extra == 'all'
Requires-Dist: mkdocs-material>=9.5; extra == 'all'
Requires-Dist: mkdocs<2,>=1.6; extra == 'all'
Requires-Dist: mkdocstrings[python]>=0.24; extra == 'all'
Requires-Dist: simpleaudio>=1.0.4; extra == 'all'
Provides-Extra: audio
Requires-Dist: simpleaudio>=1.0.4; extra == 'audio'
Provides-Extra: box2d
Requires-Dist: box2d-py>=2.3.8; extra == 'box2d'
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest-cov>=4.1; extra == 'dev'
Requires-Dist: pytest>=7.4; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Requires-Dist: twine>=5.1; extra == 'dev'
Provides-Extra: docs
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
Requires-Dist: mkdocs<2,>=1.6; extra == 'docs'
Requires-Dist: mkdocstrings[python]>=0.24; extra == 'docs'
Description-Content-Type: text/markdown

# pcg

`pcg` is a Python toolkit for games that live in a terminal. Its main API is a
single `Game` class: state goes in `start`, logic goes in `update`, and drawing
goes in `draw`. Input, colors, UI, sound, timers and a flicker-free game loop are
already connected.

Build anything from a small arcade game to a roguelike: Unicode cell rendering,
scrollable forms, weighted navigation, shared enemy flow fields, live asset
reloading, durable saves and deterministic replays are included.

[Русский быстрый старт](docs/ru.md) · [Examples](examples/README.md) ·
[API reference](https://umar151515.github.io/PCG/api/)

```python
from pcg import Game


class Hello(Game):
    def start(self):
        self.x, self.y = self.width // 2, self.height // 2

    def update(self, dt):
        move = self.input.direction()
        self.x = max(1, min(self.width - 2, self.x + move.x * 15 * dt))
        self.y = max(2, min(self.height - 2, self.y + move.y * 15 * dt))

    def draw(self):
        self.screen.box(0, 0, self.width, self.height, fg="dark_gray")
        self.screen.put(round(self.x), round(self.y), "@", fg="yellow")
        self.screen.center_text("Move with arrows or WASD", 1, fg="cyan")


Hello(60, 20, title="Hello").run()
```

## Install

```bash
python -m pip install pcg-console-engine
```

Python 3.10 or newer is required. The engine has no required runtime
dependencies. Optional sound playback and rigid-body physics are installed with:

```bash
python -m pip install "pcg-console-engine[audio]"
python -m pip install "pcg-console-engine[box2d]"
```

## Why the API is small

A basic game does not need a renderer object, an event bus, a scene manager or a
manual terminal context. `Game` owns those details and exposes these useful
things:

- `self.screen`: cells, text, lines, boxes, fills, circles, bars and cached sprites;
- `self.input`: direct keys, named actions, axes and normalized direction;
- `self.ui`: themeable widgets with focus, mouse support and automatic drawing;
- `self.audio`: tones, presets and optional sampled audio;
- `self.console`: an optional in-game developer command prompt;
- `after()`, `every()` and `every_frame()`: tagged game-time scheduling.

Escape exits by default. Terminal setup and restoration are automatic.

## Input

Poll a key directly:

```python
if self.input.pressed("space"):
    self.jump()

if self.input.down("left"):
    self.x -= 1
```

Or give controls semantic names:

```python
def start(self):
    self.input.bind("fire", "space", "f")

def update(self, dt):
    if self.input.pressed("fire"):
        self.fire()
```

Or register a callback:

```python
@self.on_key("space")
def jump():
    self.jump()
```

The default actions are `move_up`, `move_down`, `move_left`, `move_right`,
`confirm`, and `cancel`. `self.input.direction()` combines the movement actions
and normalizes diagonals.

## Grid games and roguelikes

The dependency-free grid toolkit provides deterministic connected dungeon
generation, field of view with exploration memory, coloured point lights with
shadows, rich text, and a bounded message log:

```python
level = generate_dungeon(60, 24, seed=42)
fov = FieldOfView(level.is_opaque, level.width, level.height)
fov.compute(level.start, radius=10)

lights = LightMap(level.width, level.height, level.is_opaque)
lights.rebuild([LightSource(level.start, "gold", radius=8)])
level.draw(self.screen, fov=fov, lights=lights)
```

Generate a complete starter with `pcg init my-game --template roguelike`.

Weighted navigation supports roads, mud, one-way edges and turn-based movement
budgets. A shared flow field steers enemies toward one or more goals without a
search for each enemy:

```python
from pcg import GridPathfinder

navigation = GridPathfinder.from_tilemap(level)
field = navigation.flow_field([player_cell])
next_cell = field.next_step(enemy_cell)
reachable = navigation.distance_map(player_cell, max_cost=5)
```

See [navigation](docs/guide/navigation.md) and the runnable
[navigation example](examples/navigation/main.py).

## Drawing

Colors can be names, hex strings, RGB strings, or `Color` values:

```python
def draw(self):
    s = self.screen
    s.clear(bg="#0d1117")
    s.text("HP", 2, 1, fg="white")
    s.bar(5, 1, 20, self.hp, 100, fg="lime", empty_fg="dark_gray")
    s.fill(4, 5, 12, 4, char="·", fg="gray")
    s.box(3, 4, 14, 6, border="double", fg="#ffcc00")
    s.sprite(" /\\\n<  >\n \\/", 30, 8, fg="cyan")
    s.markup("[bold][gold]Boss[/gold][/bold]: [red]12 HP[/red]", 2, 2)
```

Every draw starts on a clean frame. The engine compares it with the previous
frame and writes only changed terminal cells.

Unicode is measured in terminal cells rather than Python string length. CJK,
emoji, flags, skin tones, combining accents and ZWJ sequences remain atomic
through drawing, wrapping, markup, sprites, diff output and text editing:

```python
from pcg import display_width, graphemes

display_width("A界B")  # 4
list(graphemes("e\u0301👩‍💻"))  # two visible graphemes
```

Wide glyphs reserve two cells and are safely rejected when they would cross a
clip boundary. `TextInput` moves and deletes whole graphemes.
Sprites and tile layers support `opacity=0.5` for blending colours. A failed or
partial terminal write forces a complete repaint on the next frame. See
[rendering guarantees](docs/guide/rendering-safety.md).

## UI

UI is part of every `Game`; events, resize, update and drawing are automatic.
Focus navigation, mouse input, modal dialogs, bracketed paste and menu stacks are
built in.

```python
from pcg import Button, Checkbox, Panel, Slider, TextInput, Theme, VBox


def start(self):
    self.ui.theme = Theme.midnight()
    panel = Panel((10, 3, 40, 14), title="Settings", padding=1)
    form = VBox((1, 1, 36, 10), gap=1, stretch=True)
    form.add(
        TextInput((0, 0, 20, 3), placeholder="player name", tag="name"),
        Checkbox((0, 0), "Sound", checked=True),
        Slider((0, 0, 20, 1), value=70, show_value=True),
        Button((0, 0, 20, 3), "Play", on_click=lambda _: self.begin()),
    )
    panel.add(form)
    self.ui.add(panel)
    self.ui.focus(self.ui.get("name"))
```

Available controls include `Label`, `Panel`, `Button`, `Menu`, `ListBox`,
`ProgressBar`, `TextInput`, `Checkbox`, `RadioGroup`, `Select`, `Slider`,
`Separator`, `Spinner`, `Tabs`, and `Dialog`. `VBox`, `HBox`, `Stack`, `Grid`,
and `Center` handle layout. Themes can be applied to the whole UI or one subtree.

`ScrollView` clips larger widget trees, supports wheel and keyboard scrolling,
and reveals focused children. `TextInput` supports grapheme-safe selection,
mouse dragging, Shift navigation and clipboard callbacks. See the [UI guide](docs/guide/ui.md).

## Timers and deterministic tests

```python
def start(self):
    self.after(2.0, self.open_door)
    self.spawn_timer = self.every(0.5, self.spawn_enemy)
```

Headless execution uses the same input, update, UI and render pipeline:

```python
game = MyGame(headless=True)
game.run_for(120, dt=1 / 60)
assert "score" in "".join(game.engine.canvas_text())
game.close()
```

Pass `seed=` to `Game` and use `self.random` for deterministic randomness.
`run_until(seconds)` is available when duration reads better than frame count.
Repeating timers bound catch-up work with `max_catch_up=`. Keyboard input handled
by a focused widget is removed from gameplay actions, so typing does not move the
player. See [runtime behavior](docs/guide/runtime.md).

Call `pause()` / `resume()`, inspect `paused`, or set `time_scale=0.5` for slow
motion. Pausing freezes timers and physics while events and drawing continue.
Actions can bind combinations such as `ctrl+s` and `shift+left`.

## Assets and saves

```python
from pcg import ResourceManager, load_json, save_json

assets = ResourceManager("assets")
hero = assets.sprite("hero", "hero.ans")
changed = assets.reload_changed()

save_json("saves/settings.json", {"volume": 0.5})
settings = load_json("saves/settings.json", default={})
```

Resource paths stay inside the configured root by default. Reloading a broken
asset keeps the previous cached object. JSON and replay saves use atomic
replacement; malformed world saves are validated before existing entities are
cleared. World snapshots preserve sprite colours/styles, component enabled
flags, and animation state. See [assets and saves](docs/guide/assets-and-saves.md).

## Deterministic replays

Record a real session as frame timing, input events and visual checksums:

```python
game = MyGame(seed=42)
game.start_recording("replays/run.pcgr", metadata={"level": 3})
game.run()
```

Play it back with live input disabled. Strict mode raises `ReplayDesyncError` on
the first frame whose glyphs, colours or styles differ:

```python
MyGame(headless=True).play_replay("replays/run.pcgr")
```

`pcg replay replays/run.pcgr` validates and summarizes a recording for bug
reports and CI. See the [replay guide](https://umar151515.github.io/PCG/guide/replays/).

## In-game developer console

Enable the terminal-native overlay with `console=True`, then press F2 or `` ` ``
while the game is running. Commands get argument conversion from normal Python
annotations and may return text or an iterable of lines:

```python
class Arena(Game):
    def __init__(self):
        super().__init__(80, 24, console=True)

    def start(self):
        @self.console.command("give", help="Add health", aliases=("hp",))
        def give(amount: int = 25):
            self.health = min(100, self.health + amount)
            return f"health = {self.health}"
```

The overlay includes quoted arguments, booleans, enums, `Literal`, optional
values, history, Tab completion, bracketed paste, Unicode-safe editing, mouse
wheel scrollback and built-in `help`, `clear`, `debug`, `fps`, `scene`, and
`quit` commands. While open it captures input, so command text cannot move the
player or activate UI. See the [developer console guide](https://umar151515.github.io/PCG/guide/developer-console/).

## Project commands

```bash
pcg init my-game       # create a runnable project
cd my-game
pcg run                # run main.py
pcg check              # syntax check
pcg info               # terminal and backend diagnostics
pcg replay run.pcgr    # validate and inspect a deterministic replay
pcg build --dry-run    # validate and preview the backend command
pcg build              # build from pcg-build.toml
```

`pcg build` auto-detects PyInstaller or Nuitka and supports one-file/one-folder
output, assets, icons, hidden imports, optimization and backend-specific flags.
Successful builds include a JSON manifest with platform details, size and a
SHA-256 checksum. New projects contain a ready-to-edit `pcg-build.toml`.

## Advanced toolkit

The compact API covers most console games. Larger projects can use the modules
underneath it: scene stacks and transitions, an ECS, collision and physics,
tilemaps, pathfinding, particles, tweens, animation, camera effects, save files,
resource loading and a debug overlay. They use the same renderer and input
pipeline, so a project can introduce them gradually.

ECS queries return immutable cached tuples; `World.select()` filters by required
and excluded components, tag and active state. Physics ray queries can return
every hit with true surface normals and sensor filtering. `Audio.wait(timeout=)`
finishes queued effects without discarding them.

Read the [documentation](https://umar151515.github.io/PCG/) or start with the
[quickstart](https://umar151515.github.io/PCG/quickstart/).

## Development

```bash
python -m pip install -e ".[dev]"
python -m pytest
python -m ruff check .
python -m mypy pcg
python tools/release.py
```

The release command runs source checks, builds wheel and sdist, verifies every
runtime file and metadata, then tests isolated wheel and rebuilt-sdist
installations, CLI entry points and generated templates. It writes
artifacts and checksums into an empty `release-dist/` directory.
See [releasing](RELEASING.md) and [development](docs/contributing.md).

The package is typed (`py.typed`), platform-independent, and distributed under
the MIT license.
