{"id":51274426,"url":"https://github.com/nanot1m/project-einz","last_synced_at":"2026-06-29T20:01:31.923Z","repository":{"id":360900904,"uuid":"1251730814","full_name":"nanot1m/project-einz","owner":"nanot1m","description":"Terminal ECS shooter in C++ — friction-based movement, BFS pathfinding, cell-grid reconciler render","archived":false,"fork":false,"pushed_at":"2026-05-28T09:47:42.000Z","size":34,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-05-28T11:21:21.763Z","etag":null,"topics":["ascii-game","cpp","ecs","learning-project","pathfinding","roguelike","terminal-game"],"latest_commit_sha":null,"homepage":null,"language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nanot1m.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-27T21:30:54.000Z","updated_at":"2026-05-28T09:47:46.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/nanot1m/project-einz","commit_stats":null,"previous_names":["nanot1m/project-einz"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/nanot1m/project-einz","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nanot1m%2Fproject-einz","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nanot1m%2Fproject-einz/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nanot1m%2Fproject-einz/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nanot1m%2Fproject-einz/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nanot1m","download_url":"https://codeload.github.com/nanot1m/project-einz/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nanot1m%2Fproject-einz/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34941027,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-29T02:00:05.398Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ascii-game","cpp","ecs","learning-project","pathfinding","roguelike","terminal-game"],"created_at":"2026-06-29T20:01:30.694Z","updated_at":"2026-06-29T20:01:31.917Z","avatar_url":"https://github.com/nanot1m.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# project-einz\n\nTerminal-based ECS shooter written in C++ as a learning project.\n\n**▶ Play in browser:** \u003chttps://nanot1m.github.io/project-einz/\u003e\n\nAlso runs in a POSIX terminal (macOS / Linux). Single codebase — only the backend (terminal vs wasm) differs.\n\n## Features\n\n- **ECS-style architecture** — `Entity` is identity (`id`, `type`, `in_use`); all properties (`positions`, `velocities`, `hps`, `speeds`) are parallel component arrays indexed by entity id.\n- **Friction-based player movement** — impulse on keypress, exponential damping, max-speed clamp.\n- **Tile-sweep bullet collision** — bullets check every tile along their path each frame; no teleporting through enemies.\n- **Three enemy variants** — regular (`\u0026`), strong (`M`, 2 HP), swift (`s`, faster); each randomly spawned.\n- **Dijkstra/BFS pathfinding** — single distance field computed from the player every 200ms; enemies follow the gradient.\n- **Terrain generation** — random lakes (ellipses), two rivers (random-walk drift) with bridges that connect both sides.\n- **Trees** — 2×3 ASCII Christmas trees, block movement and bullets.\n- **Pickups** — grass (`\"`, +1-2 ammo, common), ammo packs (`=`, +5, rare).\n- **Player health + invincibility frames** — 3 HP, 1s i-frames after hit.\n- **Game-over screen** with score and restart prompt.\n- **Cell-grid IR + reconciler render** — the frame is built into an intermediate cell grid, then only diffs vs. the previous frame are emitted to the terminal. Static walls/floor/HUD labels render once.\n- **Run-length color encoding** — color escape sequences emitted only on color change; cells of the same color form long runs.\n- **Hybrid sleep + spin-wait** for accurate 120fps.\n- **Alternate screen buffer** — terminal restored cleanly on exit (q, Ctrl+C, atexit).\n\n## Controls\n\n| Key            | Action                         |\n| -------------- | ------------------------------ |\n| Arrow keys     | Move player                    |\n| `Space` or `J` | Shoot in facing direction      |\n| `R`            | Restart (only on Game Over)    |\n| `Q` or Ctrl+C  | Quit                           |\n\n## Build \u0026 Run\n\nVia the Makefile:\n\n```bash\nmake            # build terminal binary (./einz)\nmake run        # build + run terminal binary\nmake pages      # build the browser bundle into ./docs\nmake web        # build + serve at http://localhost:8000/\nmake clean      # nuke all build artifacts\n```\n\nManual commands if you want them:\n\n```bash\n# Terminal (POSIX — macOS / Linux)\nclang++ -std=c++17 -O2 project-einz.cpp -o einz\n\n# Web (emscripten) — output goes wherever you point -o\nemcc wasm-einz.cpp -o docs/index.html --shell-file shell.html -O2 \\\n     -s ALLOW_MEMORY_GROWTH=1 -s EXPORTED_RUNTIME_METHODS=HEAP8\n```\n\nBoth builds share `engine.hpp` + `platform.hpp` — only the backend (`terminal.hpp` vs `wasm.hpp`) and the entry-point `.cpp` differ.\n\nGitHub Pages is updated automatically on push to `master` by `.github/workflows/pages.yml`, which runs `make pages` in CI and uploads `docs/` as the Pages artifact.\n\n## Tuning\n\nAll gameplay constants live in `namespace cfg` at the top of [project-einz.cpp](project-einz.cpp). Edit a value, recompile, observe.\n\n```cpp\nconstexpr float PLAYER_IMPULSE         = 16.0f;\nconstexpr float PLAYER_MAX_SPEED       = 16.0f;\nconstexpr float BULLET_SPEED           = 40.0f;\nconstexpr float ENEMY_REGULAR_SPEED    = 2.5f;\nconstexpr int   SCORE_PER_KILL         = 10;\nconstexpr float DAMPING                = 20.0f;\n// ... etc\n```\n\n## Glyphs\n\n| Symbol | Entity         | Color        |\n| ------ | -------------- | ------------ |\n| `@`    | Player         | bold blue    |\n| `\u0026`    | Enemy regular  | bold red     |\n| `M`    | Enemy strong   | bold magenta |\n| `s`    | Enemy swift    | bold yellow  |\n| `\u003e` `\u003c` `^` `v` | Bullet (directional) | bold white |\n| `*`    | Hit effect     | bold yellow  |\n| `\"`    | Grass pickup   | bold green   |\n| `=`    | Ammo pack      | bold yellow  |\n| `/\\` `||` | Tree (2×3)  | bold green   |\n| `▓`    | Wall           | dim gray     |\n| `~`    | Water          | bold cyan    |\n| `_`    | Bridge         | brown        |\n| `.`    | Floor          | dim gray     |\n| `♥` `♡` | HP heart full/empty | red / gray |\n\n## Files\n\n| File | Role |\n| ---- | ---- |\n| `platform.hpp` | Shared ABI: `DynamicArray`, `Key`, `Cell`, `RenderFrame`, abstract `Renderer` / `InputSource`. |\n| `engine.hpp` | All game logic: `cfg`, enums, `Entity`, `World`. Depends only on `platform.hpp`. |\n| `terminal.hpp` | POSIX backend + `run_terminal()` main loop (sleep_until, SIGINT). |\n| `project-einz.cpp` | POSIX entry: builds `World`, calls `run_terminal`. |\n| `wasm.hpp` | WebAssembly backend + `run_wasm()` (registers a `requestAnimationFrame` callback). |\n| `wasm-einz.cpp` | Wasm entry: builds `World`, calls `run_wasm`. |\n| `shell.html` | HTML shell used by emcc — owns the DOM grid + JS-side diff cache + keyboard listener. |\n| `Makefile` | Build targets (`terminal`, `pages`, `web`, `run`, `clean`). |\n| `.github/workflows/pages.yml` | CI: rebuilds wasm and redeploys GitHub Pages on every push. |\n\n## Architecture overview\n\nEach frame, the backend (`run_terminal` / `run_wasm`) drives the same engine pipeline:\n\n```\nbackend loop\n ├─ input.poll()              (TerminalInput: read() + ESC parse;\n │                             WasmInput: drain JS keyQueue)\n ├─ world.handle_input(key)\n ├─ world.update(dt)\n │   ├─ compute_pathfinding   (BFS, every 200ms)\n │   ├─ spawn_system          (timers)\n │   ├─ enemy_ai_system       (gradient descent on dist_field)\n │   ├─ speed_limit_system\n │   ├─ movement_system       (per-axis, wall slide)\n │   ├─ bullet_system         (tile sweep, wall/enemy hit)\n │   ├─ pickup_system\n │   ├─ enemy_damage_system   (i-frames)\n │   ├─ effects_system        (hit flash decay)\n │   └─ friction_system       (linear damping)\n ├─ world.prepare_frame(fps)  (game state -\u003e Cell grid IR)\n └─ renderer.present(world.get_frame())\n     ├─ TerminalRenderer: diff vs prev_cells -\u003e ANSI escape stream\n     └─ WasmRenderer:     pass cell ptr to JS, which diffs and patches DOM\n```\n\nThe engine never calls into the renderer or input source — it just produces IR and consumes `Key` values. Swapping backends is a constructor change in the entry-point `.cpp`.\n\n## License\n\nNo license declared — public for learning/reference. Adapt as you wish.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnanot1m%2Fproject-einz","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnanot1m%2Fproject-einz","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnanot1m%2Fproject-einz/lists"}