django-word-battle
A reusable Django REST Framework + Channels backend for competitive multiplayer word-change games. Players take turns changing letters (FALL → CALL → TALL), and a player loses when they have no legal move, run out of time or resign.
The product name is not hard-coded. Set WORD_BATTLE["GAME_NAME"] or the
WORD_BATTLE_GAME_NAME environment variable and the API, admin, emails and API docs use it.
The Python package keeps the neutral import name word_battle.
pip install "django-word-battle[all]"
What's inside
| Area | What you get |
|---|---|
| Rules engine | Pure-Python authoritative engine: 1..k letter changes, scaling by word length, add/remove letters, lock-last-letter, power moves, repeat policy, move cap. Fast legal-move discovery through a word graph. Start-word rating that avoids dud games. |
| Modes | Classic (untimed/async), Blitz, Ranked, Long Words, Shape-shifter, Power Blitz, Arena (3–8 players), Daily Challenge and Practice. All are configured in the admin, not in code. |
| Matches | Private challenges with invite codes, rematches, bots (always labelled), spectators with privacy controls, resign/leave, server-side clocks with latency grace, timeouts, eliminations and placements. |
| Real time | WebSockets per match and per user, with JWT auth. Gapless, persisted event log with sequence numbers for resync. Idempotent moves (client_move_id), presence, reactions, per-socket rate limits. |
| Matchmaking | PostgreSQL queue using SKIP LOCKED, a rating window that widens with wait time, region hints, avoidance of recent opponents and blocked players, arena filling, and bot fallback. |
| Competition | Glicko-2 ratings per pool, both all-time and per season. Leaderboards, daily challenge leaderboard and streaks, achievements, XP and levels. |
| Economy | Idempotent reward ledger with a daily coin cap. Wallet separates earned and purchased coins and never allows cash-out. Cosmetic store, inventory and equipping. |
| Monetization | Google Play verification (Developer API, RTDN via Pub/Sub with OIDC check), Apple StoreKit 2 JWS verification (x5c chain up to Apple Root G3, Apple OIDs), App Store Server Notifications V2, refunds and revocations, subscriptions and entitlements. AdMob rewarded-ad SSV with ECDSA verification and replay protection. |
| Safety | Friends, blocks, reports, moderation actions with appeals, word reports, profanity filtering, fast-move and win-trading signals for human review. No automatic bans. |
| Ops | Feature flags with rollout percentages, audit log, admin with read-only match outcomes and audited wallet adjustments, metrics endpoint (DAU/MAU, retention, verified revenue), health checks, system checks, Celery tasks or a no-Celery worker loop. |
| Privacy | Personal data export and account deletion; match and accounting history are anonymised, not destroyed. |
Real-money wagering, deposits, cash-out and paid-entry cash prizes are not implemented.
Sponsored real-world prizes are blocked by a database constraint until legal approval,
an app-store policy review and official rules are recorded. They also need the
real_world_prizes flag, which can only be turned on in the admin.
Quick start (existing Django project)
# settings.py
INSTALLED_APPS += ["rest_framework", "channels", "word_battle"]
ASGI_APPLICATION = "config.asgi.application"
CHANNEL_LAYERS = {"default": {"BACKEND": "channels_redis.core.RedisChannelLayer", "CONFIG": {
"hosts": [{"address": "redis://localhost:6379/0", "socket_timeout": 30}]}}}
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": ["rest_framework_simplejwt.authentication.JWTAuthentication"],
"DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated"],
"EXCEPTION_HANDLER": "word_battle.api.exceptions.exception_handler",
}
WORD_BATTLE = {"GAME_NAME": "Word Clash"}
# urls.py
path("api/v1/", include("word_battle.api.urls")),
# asgi.py
from word_battle.realtime.middleware import OriginOrNativeValidator, WordBattleAuthMiddlewareStack
from word_battle.realtime.routing import websocket_urlpatterns
application = ProtocolTypeRouter({
"http": get_asgi_application(),
"websocket": OriginOrNativeValidator(WordBattleAuthMiddlewareStack(URLRouter(websocket_urlpatterns))),
})
python manage.py migrate
python manage.py wb_setup_defaults # dictionary (ENABLE), modes, rewards, achievements, cosmetics, flags, start words
celery -A config worker & celery -A config beat # or: python manage.py wb_worker
Set app.conf.beat_schedule = word_battle.tasks.BEAT_SCHEDULE in your Celery app. The
schedule covers turn timeouts every 1 s, matchmaking every 2 s, and the daily challenge,
housekeeping and anti-cheat scans.
To run the complete example stack with Docker:
cp .env.example .env
docker compose up --build # API on :8000, docs at /api/docs/, admin at /admin/
Configuration
All keys are optional. Nested dicts are merged with the defaults; see src/word_battle/conf.py for every key.
| Key | Default | Purpose |
|---|---|---|
GAME_NAME / GAME_SLUG |
Word Battle / word-battle |
Branding, shown via /meta/, admin and notifications |
ASYNC_TASKS |
False |
Push and other background work through Celery |
BOT_EXECUTION |
inline |
inline (in-request) or celery (with a human-like delay) |
LATENCY_GRACE_SECONDS |
1.5 |
Extra time after a deadline before a timeout is applied |
MATCHMAKING |
window 100 → 800, bots after 30 s | Rating window growth, bot fallback, arena fill wait |
RATING |
Glicko-2 1500/350/0.06, τ 0.5 | Initial rating values; minimum games for leaderboards |
REWARDS |
daily coin cap 500 | Reward codes used automatically and the XP curve |
ADS |
10 rewarded ads per day | AdMob key URL, reward code, daily cap |
GOOGLE_PLAY, APPLE |
— | Store verification credentials (see docs/monetization.md) |
NOTIFICATION_BACKEND |
logging | word_battle.services.notifications.FCMBackend for push |
THROTTLE_RATES |
moves 120/min … | Per-scope rate limits |
FEATURE_FLAGS |
— | Defaults; rows in the FeatureFlag table override them |
HOOKS |
— | Dotted paths called on match completion, verified purchases and reports |
The package also sends Django signals: match_completed, move_accepted,
purchase_verified, reward_granted and achievement_unlocked, all from
word_battle.signals.
Management commands
| Command | Purpose |
|---|---|
wb_setup_defaults |
Load the dictionary and default content (safe to re-run) |
wb_import_dictionary --file words.txt [--common common.txt] |
Import a new dictionary version |
wb_rate_start_words [mode ...] |
Pre-rate playable start words (rejects starts with an instant trap) |
wb_worker |
Scheduler loop when you don't run Celery beat |
Documentation
- docs/api.md: REST endpoints, error codes, idempotency
- docs/realtime.md: WebSocket protocol and event catalogue
- docs/flutter-integration.md: client integration guide
- docs/monetization.md: store, ads and sponsored-campaign setup and compliance limits
- docs/deployment.md: production setup, scaling, backups, runbooks
- docs/release-checklist.md: Google Play and App Store release checklist
docs/openapi.yml: generated OpenAPI 3 schema. With drf-spectacular,/api/docs/serves it live.
Development
pip install -e ".[dev]"
createdb word_battle # tests need PostgreSQL (row locks, SKIP LOCKED); set DATABASE_URL if different
pytest # 109 tests: engine, services, API, WebSockets, real concurrency
ruff check src tests example_project
python scripts/smoke_test.py --base http://localhost:8000 # end-to-end against a running stack
Licence
MIT. The bundled ENABLE word list is in the public domain. A "common words" list is
not bundled, because the popular frequency lists are not licensed for commercial use.
Supply your own with wb_import_dictionary --common.
Metadata
Release files for django-word-battle 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| django_word_battle-0.1.0.tar.gz | 559.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_word_battle-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.1 MB
Release files / django_word_battle-0.1.0.tar.gz
| Download URL | django_word_battle-0.1.0.tar.gz |
|---|---|
| Size | 559.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6fc9dffdf945d2c6d1b2f2ce46b0d319fbf7988a1d58bd262e3cea4f212f1714
|
|
BLAKE2b-256 checksum How to use checksums |
03d21d3ae2ff3dcd9f4d9f66078f5089bba2e9174e715e52c9a9609dfda2e4e1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.
Transparency logRelease files / django_word_battle-0.1.0-py3-none-any.whl
| Download URL | django_word_battle-0.1.0-py3-none-any.whl |
|---|---|
| Size | 582.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
32bd7fed6bbe8efb1e28dd86da5341985f216dce520dd7cccef0ac0982bbf113
|
|
BLAKE2b-256 checksum How to use checksums |
e1d8f0648834fd5e22912c535de69045187a4936e3588f298660e3bc814492ad
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.
Transparency log