- Zig 94.1%
- Nix 5.7%
- Shell 0.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
All checks were successful
test / test (push) Successful in 19m29s
The Android shell is a shared library loaded by NativeActivity. Its callbacks hand messages to the game's own looper thread and wait for them to be acted on. Frames are paced by the choreographer and drawn by the same Vulkan renderer as Linux, into a swapchain on the window. JNI asks Java for the system accent, the insets, and to hide the bars. zig build apk links the NDK's system libraries for arm64-v8a and x86_64, puts them in a package that aapt2 made, using tools/apk.zig, which stores them aligned to 16 KiB, and signs it with apksigner. install-apk and run-android install it with adb; nix run .#emulator starts an emulator. Along the way: - the renderer takes its format, and gains a render pass that leaves a swapchain image ready to present; - the board can be fitted inside a safe area; - the level store moves out of the Wayland shell; - OpSource's language is made Unknown when the shaders are embedded, since SwiftShader rejects Zig's. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QKsfJ57x3ZAv83X1SccVTQ |
||
| .forgejo/workflows | ||
| android | ||
| dbus | ||
| dist | ||
| docs/adr | ||
| LICENSES | ||
| nix | ||
| shaders | ||
| src | ||
| tools | ||
| .gitignore | ||
| build.zig | ||
| build.zig.zon | ||
| build.zig.zon.nix | ||
| CONTRIBUTING.md | ||
| flake.lock | ||
| flake.nix | ||
| GLOSSARY.md | ||
| package.nix | ||
| README.md | ||
| REUSE.toml | ||
pfeilstorch
A puzzle game in Zig. A board of dots is covered by bent arrows, and tapping an arrow sends it off the board in the direction it points — if the straight line from its head to the edge is clear. If another arrow is in the way, the arrow stays put and a life is lost; three lives are allowed per level. Clear the board to win the level. It is in the style of Arrow Puzzle Game.
The name is German for "arrow stork": the white stork that turned up in Mecklenburg in 1822 with an African spear through its neck, and with it the answer to where storks go in winter.
Where it stands
It plays on Linux, in a Wayland window drawn with Vulkan, and on Android. On Linux:
$ nix develop
$ zig build run
Click or touch an arrow to send it off, and drag anywhere off the board to
move the window. r starts the level over, and q or Escape quits. The
level reached is kept in $XDG_STATE_HOME/pfeilstorch
(~/.local/state/pfeilstorch), and --level N plays another. An iPhone
and iPad version may follow.
An arrow is black until it is tapped. One that leaves turns the accent color
on its way out, which is the desktop's own (from the XDG desktop portal,
followed as it changes) unless the settings file names one; one that is
blocked turns red, and stays red until it leaves. The settings file is
$XDG_CONFIG_HOME/pfeilstorch/config.struthio
(~/.config/pfeilstorch/config.struthio), in struthio, and
everything in it may be left out:
.{
// An arrow on its way out; the desktop's accent color if left out.
.accent_color = "#3584e4",
// A blocked arrow, and a life lost.
.block_color = "#e01b24",
}
The code is in three layers, of which only the last knows what it runs on:
src/rules: the game, with no platform in it — no I/O, no clock:-
the board, its arrows, and what a tap does: an exit, or a block naming the arrow in the way and how far off it is;
-
lives, and winning and losing a level;
-
a level generator that builds solvable boards from a seed. A level's seed comes from its number alone, so level 37 is the same board for everyone. Arrows are placed one at a time, each with a clear line to the edge past the ones placed before it, so removing them in reverse always works;
-
a solver. Since an arrow leaving only ever frees dots, the order of exits never matters, and taking any arrow that can leave until none can decides whether a board can be cleared;
-
boards as text, for levels written by hand and for tests:
# Arrow 0 points right at arrow 1, which points up and out. 4x2 0,1 1,1 3,1 3,0
-
src/view: the game on a screen, still with no platform in it: where the board goes in a window, which arrow a tap is on, exits sliding off with the body following the head like a snake, blocks bumping against the arrow in the way, and each frame as a list of circles, lines and triangles.- The Android shell, in
src/android, is a library that Android's ownNativeActivityloads, so the app has no Java in it at all; what only Java can say, the accent color and where the camera cutout is, is asked through JNI. It draws with the same Vulkan code as Linux, into a swapchain on the window. - The Linux shell. The window and its input are Wayland, through zig-wayland-native and zig-xkb, with no libwayland or libxkbcommon. The shapes are drawn by Vulkan, through vulkan-zig, with shaders written in Zig and compiled to SPIR-V, each shape covered by its signed distance so that edges are smooth at any size. The desktop's accent color comes over D-Bus through mdbus and zig-dbus-service. Frames reach the compositor as dma-bufs it reads directly, or through shared memory where it and the GPU cannot share them; that part follows harrier, which does the same for a terminal.
--snapshot FILE draws one frame into a PNG with no window, for looking at a
change to how the game draws without a screen:
$ zig build run -- --snapshot level30.png --level 30
$ zig build run -- --snapshot exit.png --tap 3,0 --at 80
The words used for the parts of the game are defined in GLOSSARY.md, and the decisions that shape the code are in docs/adr.
Getting it
$ git clone https://git.jcollie.dev/jeff/pfeilstorch.git
The same repository is mirrored on Tangled at https://tangled.org/jcollie.dev/pfeilstorch.
Installing
The flake has the game as a package, with a desktop entry and an icon so that GNOME Shell and other desktops list it:
$ nix run git+https://git.jcollie.dev/jeff/pfeilstorch.git
$ nix profile install git+https://git.jcollie.dev/jeff/pfeilstorch.git
Android
The game needs Android 10 or later and a device with Vulkan 1.2, which is most made since 2020. It fills the screen, in either orientation, with the system bars hidden until a swipe from the edge brings them back. Tap an arrow to send it off; back sends the game to the background, as it does any app. An arrow on its way out takes the accent color Android 12 and later draw from the wallpaper. The level reached is kept in the app's own storage.
There is no download yet. To build the package and install it on a phone with USB debugging turned on:
$ nix develop
$ zig build apk -Doptimize=ReleaseSafe
$ zig build run-android -Doptimize=ReleaseSafe
zig build apk leaves the package in zig-out/pfeilstorch.apk, and
run-android installs it with adb, starts it, and follows its log. With
no phone, nix run .#emulator starts an Android emulator for adb to
find.
Building
Everything comes from the Nix flake:
$ nix develop
$ zig build test --summary all
$ zig build test --fuzz=1M
$ zig build run
It needs Zig 0.17.0, and to play, a Wayland compositor and a Vulkan driver. The Android build also needs the Android SDK and NDK, which the flake provides. Linking a C library is a decision taken one library at a time, and so far there are none beyond what the operating system is reached through: the C library the Vulkan loader needs on Linux, and Android's own system libraries; see ADR 0001.
Issues and pull requests
Report bugs and send pull requests on the Tangled mirror: https://tangled.org/jcollie.dev/pfeilstorch. See CONTRIBUTING.md.
License
MIT. The project follows the REUSE specification.
References cited
- Arrow Puzzle Game. https://arrowpuzzlegame.com/. The game whose rules pfeilstorch follows.
- Behavior changes: Apps targeting Android 16 or higher. Android Developers. https://developer.android.com/about/versions/16/behavior-changes-16. Why back is left to the system.
- Inigo Quilez. "2D distance functions". https://iquilezles.org/articles/distfunctions2d/. The signed distance to a triangle and to a line segment that the shaders use.
- Java Native Interface Specification: JNI Functions. Oracle. https://docs.oracle.com/en/java/javase/21/docs/specs/jni/functions.html. The layout of the JNI function table that the Android shell calls into.
- Native Activity. Android NDK API reference.
https://developer.android.com/ndk/reference/group/native-activity.
ANativeActivityand its callbacks. - Support 16 KB page sizes. Android Developers. https://developer.android.com/guide/practices/page-sizes. Why native libraries are stored aligned to 16 KiB in the package.
- XDG Desktop Portal: Settings.
https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Settings.html.
The
org.freedesktop.appearanceaccent-colorsetting, and theSettingChangedsignal. - Guy L. Steele Jr., Doug Lea and Christine H. Flood. "Fast splittable pseudorandom number generators". ACM SIGPLAN Notices 49(10), 2014. https://doi.org/10.1145/2714064.2660195. SplitMix64, the generator levels are built from.