Linux-native arrow-shading puzzle game that runs in Chrome OS Linux (Crostini) and other Linux desktops.
- Sign in with a display name, Guest, or Google (Chrome account) if OAuth is configured.
- Pick a level (Beginner → Expert). A puzzle is generated automatically.
- The board shows a recognizable shape (apple, tree, house, heart, geometry, …) filled with arrow lines that point toward the nearest side (head only on the outer tip).
- Hover an arrow to highlight it, then click. If its path out is clear, it slides out in
green and shades cells as it goes (
10 × lengthpoints). If blocked, it blinks red and plays an error sound. - Clear blockers first to unlock deeper arrows. When every arrow is gone, you win.
- A time bonus (up to 500 pts) rewards faster clears. Scores are saved per user/level.
- Enable Linux (Beta) if needed.
- In the Linux terminal:
cd ~/arrow-game
sudo apt update
sudo apt install -y python3-pygame
chmod +x run.sh
./run.shcd ~/arrow-game
chmod +x install-desktop.sh
./install-desktop.shThis installs:
~/.local/share/applications/arrow-game.desktopwithIcon=arrow-game- multi-size icons under
~/.local/share/icons/hicolor/*/apps/arrow-game.png - refreshes the desktop/icon caches
The icon is a gold arrow in a rounded box (assets/arrow-game-icon.png). It is
used by the launcher and the running window / shelf (via pygame set_icon
and StartupWMClass=arrow-game).
If Chrome OS still shows the default penguin after install, restart the Linux
container once (crosh → vmc stop termina or reboot) so garcon re-reads icons.
Then open Arrow Game from the Chrome OS launcher (Linux apps).
Scores always work with a local name or Guest. For Google sign-in:
- Create an OAuth 2.0 Desktop client in Google Cloud Console.
- Enable the userinfo scopes (OpenID email/profile).
- Export credentials before launch:
export ARROW_GAME_GOOGLE_CLIENT_ID="….apps.googleusercontent.com"
export ARROW_GAME_GOOGLE_CLIENT_SECRET="…"
./run.shThe app opens Chrome on a loopback redirect (http://127.0.0.1:8765/callback). Your profile is stored in ~/.local/share/arrow-game/.
arrow-game/
main.py # entry
run.sh # launcher
arrow-game.desktop # Chrome OS / desktop menu
assets/
arrow-game-icon.png # app icon (arrow in a box)
arrow_game/
app.py # UI screens & game loop
puzzle.py # generation + play rules
levels.py # difficulty presets
auth.py # Google OAuth + guest/local
storage.py # scores & profile JSON
ui.py / config.py
data/ # optional runtime data
Puzzles are solvable by construction:
- Stamp a template silhouette (object or geometric form) onto the level grid.
- Fill free cells with winding arrows: random segment length (capped by
segment_size), then a turn, repeat until the path hits the edge of the free region (endpoint). Paths never self-overlap. - Carve in solution order so each arrow can exit when placed; reverse order is a valid solve. Generation retries until move count is in the level band.
Higher difficulty uses smaller cells inside a fixed play area (720×540
px every level). Grid dimensions are derived from the cell size so each level
fills that area exactly (e.g. 45px → 16×12, 15px → 48×36). Glyph line weight
and heads scale from a design BASE_CELL. Paths are biased long and bendy.
| Level | Cell | Grid | Moves (solve) | Max segment (cells) |
|---|---|---|---|---|
| Beginner | 45px | 16×12 | 3–7 | 10 |
| Easy | 36px | 20×15 | 6–12 | 12 |
| Medium | 30px | 24×18 | 10–18 | 14 |
| Hard | 20px | 36×27 | 16–28 | 16 |
| Expert | 15px | 48×36 | 22–40 | 18 |
Holes in silhouettes (e.g. Ring) are filled with playable purple arrows. They follow the same exit rules: blocked until the path out is clear, and they block other arrows whose exit ray crosses them.
Calibrate with: PYTHONPATH=. python3 -m arrow_game.analyze_levels
| Event | Points |
|---|---|
| Remove arrow of length L | L × 10 |
| Click a blocked arrow | −10 |
| Time under target | 5 per whole second under target |
| Target time | 5 × number of arrows (seconds) |
Data files: ~/.local/share/arrow-game/profile.json, scores.json.
python3 -c "from arrow_game.puzzle import generate_puzzle, verify_solvable; from arrow_game.levels import LEVELS
for lv in LEVELS:
p=generate_puzzle(lv, seed=42)
assert verify_solvable(p), lv.name
print(lv.name, 'ok', len(p.arrows), 'arrows', len(p.shape), 'cells')"