Pubnix-local turn-based multiplayer TUI for tic-tac-toe, chess, checkers, Reversi, Battleship, Connect 4, Crazy Eights, Royal Game of Ur, and peg solitaire.
Project description
tuimanji
A pubnix-local turn-based multiplayer TUI. No server. No accounts. Just log in to the same box as your friend and play something.
Tuimanji bundles nine turn-based games — Tic-Tac-Toe, Connect 4, Reversi, Chess, Checkers, Battleship, Crazy Eights, Peg Solitaire, and the Royal Game of Ur — behind a single Textual terminal UI. Matches live in a shared SQLite database; clients coordinate via file locks and append-only writes. It's designed for pubnix boxes, shared servers, and old-school SSH shenanigans.
Why no server?
Because everything you need to synchronize turn-based games is already on the
box: a filesystem, a SQLite binary, and flock(2). Running a daemon to
marshal moves would be more infrastructure to break. Instead:
- State lives in
tuimanji.db— one file under$TUIMANJI_DB, WAL mode,PRAGMA busy_timeout=5000. - Writes are append-only. Each turn is a new row; nothing is ever updated. Replay, spectating, and crash-resume fall out for free.
- Concurrency is layered. The app checks "is it your turn?"; SQLite's
BEGIN IMMEDIATEserializes writers; a(match_id, turn)unique constraint catches anything that slips through. - Identity is a session slot.
flocking$TUIMANJI_DB/.sessions/<user>/N.locklets two terminals for the same unix user claim distinct player ids (user,user#2, …) — great for local testing, still sane for shared hosts.
Install
# one-shot run
uv tool run tuimanji
# persistent install
uv tool install tuimanji
# or
pipx install tuimanji
# or
pip install --user tuimanji
Requires Python 3.13+ and a terminal that speaks 256 colors.
Run
tuimanji # open the lobby
tuimanji new chess # create a new match + jump to its waiting room
tuimanji join a1b2c3d4 # join (or rejoin) an existing match
tuimanji resume # open your most recent unfinished match
tuimanji games # list available games
tuimanji where # print the resolved database directory
tuimanji --version
The shared database defaults to /var/games/tuimanji/. On a machine where
you don't own that path, override it:
export TUIMANJI_DB=~/.local/share/tuimanji
# or per-command
tuimanji --db ~/.local/share/tuimanji
Everyone playing against each other has to point at the same directory.
Pubnix / shared host setup
For multiple users to share /var/games/tuimanji/, the directory needs to
be writable by all of them. The easiest way is to mark it sticky +
world-writable, the same way /tmp is, once at install time (as root):
sudo install -d -m 1777 /var/games/tuimanji
When tuimanji sees the data dir set up that way, it propagates the same
intent to everything it creates inside: .sessions/ becomes 1777 so
every user can mkdir their own subdir, and tuimanji.db plus its WAL
sidecars get 0o666 so writes from any user succeed. If the data dir
isn't sticky+world-writable, tuimanji leaves permissions alone — your
personal ~/.local/share/tuimanji stays private.
If you'd rather scope sharing to a unix group:
sudo groupadd tuimanji
sudo install -d -m 2775 -g tuimanji /var/games/tuimanji
sudo usermod -aG tuimanji alice # repeat per user
…and have each user run with a permissive umask (umask 0002) so files
they create stay group-writable. The auto-propagation above only kicks in
for the sticky+world-writable layout; for groups, you set the policy and
tuimanji honors what it finds.
Games
| id | players | name |
|---|---|---|
tic-tac-toe |
2 | Tic-Tac-Toe |
connect-4 |
2 | Connect 4 |
battleship |
2 | Battleship |
reversi |
2 | Reversi |
chess |
2 | Chess |
checkers |
2 | Checkers |
peg-solitaire |
1 | Peg Solitaire |
crazy-eights |
2–4 | Crazy Eights |
royal-ur |
2 | Royal Game of Ur |
Controls
| key | action |
|---|---|
↑ ↓ ← → |
move cursor |
enter space |
commit cursor action |
r |
rotate ship (Battleship placement phase) |
tab |
cycle stage / suit / piece (multi-phase games) |
q |
quit to lobby |
Per-game quirks are surfaced in the status strip at the bottom of each match.
Adding a game
A game is one module under src/tuimanji/games/ implementing the
tuimanji.engine.Game protocol — pure functions over state dicts. No I/O,
no database access. See docs/adding-a-game.md for
a walkthrough, or read src/tuimanji/games/peg_solitaire.py for the minimal
single-player shape.
from tuimanji import Game, REGISTRY, IllegalAction
class MyGame: # satisfies the Game protocol structurally
id = "my-game"
name = "My Game"
min_players = 2
max_players = 2
# ... initial_state, apply_action, current_player, winner, is_terminal,
# ... render, initial_cursor, move_cursor, cursor_action, animation_for
Development
git clone https://github.com/alanbato/tuimanji
cd tuimanji
uv sync
uv run pytest -q
prek install # pre-commit hooks (ruff, ty)
uv run tuimanji
See CONTRIBUTING.md for the full contributor guide.
License
LGPL-3.0-or-later. See COPYING.LESSER (which extends the GPLv3 in COPYING).
Modifications to Tuimanji itself must be released under the LGPL, but larger
works that merely use the library (e.g. a game module wired into the
REGISTRY) are not required to adopt the same license.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tuimanji-0.1.5.tar.gz.
File metadata
- Download URL: tuimanji-0.1.5.tar.gz
- Upload date:
- Size: 70.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
299970d14fc247e1ef074681a597a00903766716ca6108d85431a9c75dbc01a6
|
|
| MD5 |
17be1099c73f650869d32705930b486c
|
|
| BLAKE2b-256 |
04d694fa43f7e25eab34eff0f19621638cca00e348635b1c0d267ed0571c9b60
|
Provenance
The following attestation bundles were made for tuimanji-0.1.5.tar.gz:
Publisher:
publish.yml on alanbato/tuimanji
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tuimanji-0.1.5.tar.gz -
Subject digest:
299970d14fc247e1ef074681a597a00903766716ca6108d85431a9c75dbc01a6 - Sigstore transparency entry: 1389492146
- Sigstore integration time:
-
Permalink:
alanbato/tuimanji@5527af8e340aeff6dfff61d88885c8d0d20350bc -
Branch / Tag:
refs/tags/v0.1.5 - Owner: https://github.com/alanbato
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@5527af8e340aeff6dfff61d88885c8d0d20350bc -
Trigger Event:
push
-
Statement type:
File details
Details for the file tuimanji-0.1.5-py3-none-any.whl.
File metadata
- Download URL: tuimanji-0.1.5-py3-none-any.whl
- Upload date:
- Size: 86.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d934c6f0ddf16b3ebc255890c5e07b2f968e2a083d21a8c62340ef75264a4517
|
|
| MD5 |
4431337291e2928e2ee2609a6103a703
|
|
| BLAKE2b-256 |
bca5613a3ccca92fbd7f5ef3d3fcccc101efaf77401d371af9bccfc7e698758d
|
Provenance
The following attestation bundles were made for tuimanji-0.1.5-py3-none-any.whl:
Publisher:
publish.yml on alanbato/tuimanji
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tuimanji-0.1.5-py3-none-any.whl -
Subject digest:
d934c6f0ddf16b3ebc255890c5e07b2f968e2a083d21a8c62340ef75264a4517 - Sigstore transparency entry: 1389492246
- Sigstore integration time:
-
Permalink:
alanbato/tuimanji@5527af8e340aeff6dfff61d88885c8d0d20350bc -
Branch / Tag:
refs/tags/v0.1.5 - Owner: https://github.com/alanbato
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@5527af8e340aeff6dfff61d88885c8d0d20350bc -
Trigger Event:
push
-
Statement type: