Skip to main content

gamekit

The game-logic half of a game engine, with no window required.

gamekit is a small, pure-Python toolkit for the parts of a game that aren't graphics: an entity-component-system, tile grids with A* pathfinding, a fixed-timestep game loop with timers, and action-based input mapping. It has zero dependencies and never opens a display, so your game logic runs the same in a terminal, a test suite, a server, or under whatever renderer you bolt on later (curses, pygame, a web front end...).

Features

  • ECS (World): integer entity ids, components are plain objects (dataclasses work well), typed query() over component combinations, ordered systems, and deferred destroy() so it's safe to remove entities while iterating.
  • Grids & pathfinding (Grid): build maps from ASCII art, 4-way neighbours, flood-fill reachability, A* shortest paths, and text rendering with overlays.
  • Fixed-timestep loop (GameLoop): frame-rate-independent updates, an accumulator with a "spiral of death" cap, render interpolation alpha, headless run(ticks) for tests, and run_realtime() with an injectable clock.
  • Timers (Timer, GameLoop.every/after): one-shot and repeating timers in simulated time, with exact catch-up when a big step overshoots.
  • Input mapping (InputMap): bind any hashable key to named actions; query held / just-pressed / just-released and two-action axes. Backend-agnostic.
  • Vec2: immutable, hashable 2D vector usable as a dict key.
  • Fully type-hinted (py.typed), standard library only, Python 3.10+.

Install

From a checkout:

python3 -m venv .venv && . .venv/bin/activate
pip install -e .

Quickstart

from dataclasses import dataclass
from gamekit import GameLoop, Grid, Vec2, World

@dataclass
class Pos:
    at: Vec2

@dataclass
class Seeker:
    goal: Vec2

grid = Grid.from_ascii("""
#######
#.....#
#.###.#
#.....#
#######
""")

world = World()
world.spawn(Pos(Vec2(1, 1)), Seeker(goal=Vec2(5, 3)))

@world.add_system
def seek(world: World, dt: float) -> None:
    for eid, pos, seeker in world.query(Pos, Seeker):
        path = grid.find_path(pos.at, seeker.goal)
        if path and len(path) > 1:
            pos.at = path[1]          # one step per tick
        else:
            world.destroy(eid)        # arrived (removed at end of step)

loop = GameLoop(world.step, tick_rate=10)
loop.every(0.5, lambda: print(f"t={loop.time:.1f}s entities={len(world)}"))
loop.run(10)

A complete example, a tiny terminal chase game that uses every module, lives in examples/chase.py:

python3 examples/chase.py              # coin-collecting player escapes the ghost
python3 examples/chase.py --idle       # player stands still; the ghost catches it
python3 examples/chase.py --every 1 --delay 0.1   # watch it frame by frame

API overview

Everything below is importable from the top-level gamekit package.

Vec2(x=0, y=0) (frozen dataclass)

Member Description
+, -, * k, k *, unary - Vector arithmetic
iter(v) Unpack: x, y = v
length() -> float Euclidean length
manhattan(other) Taxicab distance
normalized() -> Vec2 Unit vector (zero stays zero)

CARDINALS: tuple of the four unit directions (1,0), (-1,0), (0,1), (0,-1); y grows downward.

World(), the ECS (Entity is an alias for int)

Method Description
spawn(*components) -> Entity Create an entity
destroy(entity) Mark for removal; applied at the end of step() or by flush()
flush() Apply pending destroys now
alive(entity) -> bool Exists and is not pending destruction
len(world) Number of live entities
add(entity, component) Attach/replace a component (keyed by its type)
remove(entity, ctype) Detach a component type (no-op if absent)
get(entity, ctype) Component, or KeyError
try_get(entity, ctype) Component or None
has(entity, *ctypes) -> bool Has every listed type
query(*ctypes) Yields (entity, comp1, comp2, ...) in creation order
add_system(fn) Register fn(world, dt); usable as a decorator
step(dt) Run all systems in order, then flush destroys

Grid(width, height, walls=())

Method Description
Grid.from_ascii(text, wall="#") Build from ASCII art
width, height Dimensions
in_bounds(p), is_wall(p), passable(p) Cell tests (out of bounds counts as wall)
set_wall(p, wall=True) Add/remove a wall (IndexError if out of bounds)
cells() All cells, row-major
neighbors(p) -> list[Vec2] Passable 4-connected neighbours
reachable(start) -> set[Vec2] Flood fill
find_path(start, goal) -> list[Vec2] | None A* shortest path including both endpoints
render(marks=None, wall="#", floor=".") -> str Text rendering with per-cell overrides

GameLoop(update, tick_rate=60.0, max_steps_per_advance=8)

Member Description
update(dt) Your callback, called once per fixed step
dt, ticks, time, running Step size, steps run, simulated seconds, run state
alpha Leftover fraction of a step, for render interpolation
step() Run exactly one step: update, advance ticks/time, then timers
advance(elapsed) -> int Feed real elapsed seconds; returns steps run
run(ticks) Run N steps immediately (headless)
run_realtime(duration=None, clock=time.perf_counter, sleep=time.sleep) Real-time loop
stop() Exit run/run_realtime after the current step
every(seconds, cb) -> Timer / after(seconds, cb) -> Timer Schedule in simulated time

Timer(duration, callback=None, repeat=False)

tick(dt) -> int (times fired), reset(), progress (0..1), remaining, finished.

InputMap(bindings=None)

bindings is {"action": [key, ...]}; keys are any hashable.

Method Description
bind(action, key) / unbind(action, key=None) Edit bindings
keys_for(action) -> frozenset Keys bound to an action
press(key) / release(key) Feed raw events from your backend
is_held(action), just_pressed(action), just_released(action) Action state
axis(negative, positive) -> int -1, 0 or +1
end_frame() Clear edge state; call once per frame

Development

python3 -m unittest discover -s tests -t .    # or: python3 -m pytest

License

MIT

Metadata

Release files for gamekit 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for gamekit 0.1.0
File Size Uploaded Provenance
gamekit-0.1.0.tar.gz 17.3 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for gamekit 0.1.0
File Interpreter ABI Platform
gamekit-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 30.6 kB

Release files / gamekit-0.1.0.tar.gz

Download URL gamekit-0.1.0.tar.gz
Size 17.3 kB
Tags Source
SHA-256 checksum
How to use checksums
8bea5e307fd9acc852d6baacf5b1fc5631b9d4c28f9b12da090de948ff8e2a43
BLAKE2b-256 checksum
How to use checksums
10f0ec7f1ab7412fd8361aed83bc499ff32ff267ae1452b2a73c68cb42344e8c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.5

Release files / gamekit-0.1.0-py3-none-any.whl

Download URL gamekit-0.1.0-py3-none-any.whl
Size 13.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f05cc3c8cc6e79beb8acf9fb7dfe24247b24ccca41dcbca2d8a3ccad9a949d14
BLAKE2b-256 checksum
How to use checksums
13948a920cca68bbee557cd7070cfc346d0f1049f035859baddfc2150aa331bb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.5

Security for gamekit 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

0.0.1

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page