aboutsummaryrefslogtreecommitdiff
path: root/README.md
blob: a372434b230a0905447ac0d0341f69821dafe7a3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# Downstroke

A 2D tile-driven game engine for Chicken Scheme, built on SDL2. Targets old-school platformer and arcade games — NES-style and beyond. The API is inspired by Phaser 2: one call to start, lifecycle hooks to fill in. A minimal game is ~20 lines of Scheme.

    (define my-game
      (make-game
        title: "My Game" width: 320 height: 240
        preload: (lambda (game) ...)      ; load assets
        create:  (lambda (game) ...)      ; set up scene
        update:  (lambda (game dt) ...))) ; per-frame logic (physics runs first)

    (game-run! my-game)

**[šŸ“– Full Documentation](https://docs.etenil.net/downstroke/guide.html)**


## Installation

Clone and install from source:

    git clone https://git.etenil.net/downstroke
    cd downstroke
    chicken-install

This will install the `downstroke` egg and all its dependencies. Ensure you have the required system libraries installed first (see [Dependencies](#dependencies) below).


## Features

-   Tweens with easing helpers
-   Tile-based physics: gravity, velocity, AABB collision against TMX tilemaps
-   Built-in update pipeline (before your `update:` hook): tweens → acceleration → gravity → velocity → tile collisions → ground detection → entity collisions → group sync → animation; input is polled each frame and available to hooks and your own logic
-   Entities as association lists (alists) — purely functional, no classes; prefab data is often written as plists and converted when loaded
-   Data-driven prefab/mixin system for composing entity types
-   Configurable input: keyboard, joystick, and controller
-   Asset registry with `preload:` lifecycle hook
-   Scene and camera management
-   Sprite animation with frame/tick tracking
-   SDL2<sub>mixer</sub> audio via a thin FFI binding


## Dependencies

System libraries: `SDL2`, `SDL2_image`, `SDL2_mixer`, `SDL2_ttf`

Chicken eggs (mirrors `downstroke.egg`; install what you do not already have):

    chicken-install sdl2 sdl2-image sdl2-ttf expat matchable defstruct list-utils \
                    srfi-1 srfi-13 srfi-69 srfi-197 simple-logger

From a git checkout, run tests with the `test` egg (`make test` runs `csi -s` on each suite).

Games that ship their own FSM logic (for example with the `states` egg) declare that egg themselves; Downstroke does not depend on it.


## Building

    make        # compile all modules to bin/
    make test   # run unit tests (=test= egg)
    make demos  # build demo games (verifies they compile)
    make clean  # remove bin/

Single test module (from repo root; space after each `-I` is required by `csi`):

    csi -I "$(pwd)" -I "$(pwd)/bin" -s tests/physics-test.scm

The canonical version string is `VERSION` at the top of the `Makefile`; it must match `((version "…")` in `downstroke.egg` and the `v…` marker in the milestone table. Bump all three with `make bump-version NEW=1.2.3` (POSIX `sed`; `NEW` must not contain `&`, backslash, or `#`).


## Architecture

Modules live at the project root. Each compiles as a Chicken unit (`csc -c -J -unit`). Compile order follows the dependency graph:

    entity → tween → tilemap → world → input → physics → renderer → assets → animation → engine → mixer → sound → prefabs → scene-loader

**Modules:**

-   `entity` — Alist accessors: `entity-ref`, `entity-set`
-   `tilemap` — TMX/TSX parsing (expat), tileset loading
-   `world` — Scene struct, entity list ops, camera
-   `physics` — Gravity, velocity, AABB tile + entity collisions
-   `input` — SDL2 events → action mapping, configurable binds
-   `animation` — Frame/tick tracking, sprite ID mapping
-   `prefabs` — Mixin composition, entity instantiation
-   `renderer` — `draw-sprite`, `draw-tilemap-layer`, `draw-text`
-   `assets` — Asset registry for the `preload:` hook
-   `scene-loader` — `game-load-scene!`, `instantiate-prefab`
-   `mixer` — SDL<sub>mixer</sub> FFI (no Scheme deps)
-   `sound` — Sound registry, music playback
-   `engine` — `make-game`, `game-run!`, lifecycle orchestration

Entities are alists at runtime — no classes, purely functional:

    '((#:type . player) (#:x . 100) (#:y . 200) (#:width . 16) (#:height . 16)
      (#:vx . 0) (#:vy . 0) (#:gravity? . #t) (#:on-ground? . #f)
      (#:tile-id . 29) (#:tags . (player))
      (#:anim-name . walk)
      (#:animations . (((#:name . walk) (#:frames . (27 28)) (#:duration . 10))
                      ((#:name . idle) (#:frames . (28)) (#:duration . 10)))))

Entity types are composed from mixins declared in a data file:

    (mixins
      (physics-body #:vx 0 #:vy 0 #:gravity? #t #:on-ground? #f)
      (has-facing   #:facing 1))

    (prefabs
      (player physics-body has-facing #:type player #:tile-id 29))


## LLM (AI) disclosure

Some engine code was written or refactored with an LLM (aka AI) prior
to the initial release. I don't intend to keep using AI for engine
code after that.

The `docs/` tree is LLM-generated, and that is expected to continue.


## Credits

Engine extracted from [macroknight](https://genepasquet.itch.io/macroknight) (Spring Lisp Game Jam 2025).

-   Code: Gene Pasquet
-   Demo Levels: Owen Pasquet
-   Demo Art: [Kenney](https://kenney.nl) (1-bit pack)