Compare commits

...

257 Commits

Author SHA1 Message Date
8c95ecc547 feat: add profiling 2025-08-15 13:46:39 -05:00
02a98c9f32 chore: remove unnecessary log, simplify match to if let 2025-08-15 13:05:55 -05:00
7f95c0233e refactor: move position/movement related components into systems/movement 2025-08-15 13:05:03 -05:00
a531228b95 chore: update thiserror & phf crates 2025-08-15 13:04:39 -05:00
de86f383bf refactor: improve representation of movement system 2025-08-15 12:50:07 -05:00
bd811ee783 fix: initial next direction for pacman (mitigation) 2025-08-15 12:30:29 -05:00
57d7f75940 feat: implement generic optimized collision system 2025-08-15 12:21:29 -05:00
c5d6ea28e1 fix: discard PlayerControlled tag component 2025-08-15 11:28:08 -05:00
730daed20a feat: entity type for proper edge permission calculations 2025-08-15 10:06:09 -05:00
b9bae99a4c refactor: reorganize systems properly, move events to events.rs 2025-08-15 09:48:16 -05:00
2c65048fb0 refactor: rename 'ecs' submodule to 'systems' 2025-08-15 09:27:28 -05:00
3388d77ec5 refactor: remove all unused/broken tests, remove many now unused types/functions 2025-08-15 09:24:42 -05:00
242da2e263 refactor: reorganize ecs components 2025-08-15 09:17:43 -05:00
70fb2b9503 fix: working movement again with ecs 2025-08-14 18:35:23 -05:00
0aa056a0ae feat: ecs keyboard interactions 2025-08-14 18:17:58 -05:00
b270318640 feat: directional rendering, interactivity 2025-08-14 15:44:07 -05:00
bc759f1ed4 refactor!: begin switching to bevy ECS, all tests broken, all systems broken 2025-08-14 15:06:56 -05:00
2f1ff85d8f refactor: handle pausing within game, reduce input system allocations 2025-08-14 10:36:39 -05:00
b7429cd9ec chore: solve tests/ clippy warnings 2025-08-14 09:46:10 -05:00
12a63374a8 feat: avoid using spin sleep unless focused 2025-08-13 23:30:07 -05:00
d80d7061e7 refactor: build decoupled input processing & add event queue system 2025-08-13 20:45:56 -05:00
abdefe0af0 chore: add hidden note about why Coveralls.io is disappointing today 2025-08-13 19:52:58 -05:00
4f76de7c9f feat: enable vsync & hardware acceleration 2025-08-13 19:49:02 -05:00
db8cd6220a feat: cache dynamicly rendered map texture 2025-08-13 19:48:50 -05:00
ced4e87d41 feat: embed atlas.json via phf instead of runtime parsing 2025-08-13 00:37:37 -05:00
09e3d85821 feat!: dynamic map rendering from tiles 2025-08-13 00:25:34 -05:00
c1e421bbbb test: new graph tests 2025-08-12 19:58:37 -05:00
3a9381a56c chore: use NodeId explicitly in collision.rs types 2025-08-12 19:58:11 -05:00
90bdfbd2ae chore: remove emscripten.rs platform from coverage, add html generation task, hide absolute path with remap-path-prefix, organize gitignore 2025-08-12 19:57:52 -05:00
a230d15ffc test: setup common submodule, add text.rs tests, pattern exclude error.rs 2025-08-12 19:24:06 -05:00
60bbd1f5d6 ci: add retry mechanism for coverage reporting via Coveralls CLI 2025-08-12 18:31:07 -05:00
43ce8a4e01 ci: use justfile for coverage, separate report/generate coverage tasks 2025-08-12 18:00:57 -05:00
1529a64588 test: add asset path validity tests 2025-08-12 17:24:12 -05:00
be5eec64c9 Add justfile for handling multiple coverage steps, prevent early termination of coverage job 2025-08-12 17:24:12 -05:00
780a33f657 test: add coverage job to bacon.toml, coverage profile for nextest 2025-08-12 16:48:01 -05:00
c1c5dae6f2 refactor: restructure game logic and state management into separate modules
- Moved game logic from `game.rs` to `game/mod.rs` and `game/state.rs` for better organization.
- Updated `App` to utilize the new `Game` struct and its state management.
- Refactored error handling
- Removed unused audio subsystem references
2025-08-12 14:40:48 -05:00
c489f32908 fix: audio and other subsystems being dropped in App::new(), use Box::leak to ensure static ownership 2025-08-12 13:08:08 -05:00
b91f70cf2f ci: add concurrency group to 'wasm' job to prevent concurrent page deployments 2025-08-12 11:56:03 -05:00
24a207be01 chore: use steps.$.outputs in build workflow, document 1.86.0 toolchain version 2025-08-12 11:41:29 -05:00
44e31d9b21 chore: sync lockfile, add lcov.info to .gitignore 2025-08-12 10:31:10 -05:00
dependabot[bot]
b67234765a chore(deps): bump actions/checkout from 4 to 5 in the dependencies group (#1)
Bumps the dependencies group with 1 update: [actions/checkout](https://github.com/actions/checkout).


Updates `actions/checkout` from 4 to 5
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-12 09:27:54 -05:00
dependabot[bot]
d07498c30e chore(deps): bump the dependencies group with 5 updates (#2)
Bumps the dependencies group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [thiserror](https://github.com/dtolnay/thiserror) | `1.0.69` | `2.0.12` |
| [anyhow](https://github.com/dtolnay/anyhow) | `1.0.98` | `1.0.99` |
| [glam](https://github.com/bitshifter/glam-rs) | `0.30.4` | `0.30.5` |
| [serde_json](https://github.com/serde-rs/json) | `1.0.141` | `1.0.142` |
| [libc](https://github.com/rust-lang/libc) | `0.2.174` | `0.2.175` |


Updates `thiserror` from 1.0.69 to 2.0.12
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.69...2.0.12)

Updates `anyhow` from 1.0.98 to 1.0.99
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.98...1.0.99)

Updates `glam` from 0.30.4 to 0.30.5
- [Changelog](https://github.com/bitshifter/glam-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bitshifter/glam-rs/compare/0.30.4...0.30.5)

Updates `serde_json` from 1.0.141 to 1.0.142
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.141...v1.0.142)

Updates `libc` from 0.2.174 to 0.2.175
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.175/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.174...0.2.175)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-version: 2.0.12
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: dependencies
- dependency-name: anyhow
  dependency-version: 1.0.99
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dependencies
- dependency-name: glam
  dependency-version: 0.30.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dependencies
- dependency-name: serde_json
  dependency-version: 1.0.142
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dependencies
- dependency-name: libc
  dependency-version: 0.2.175
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Xevion <xevion@xevion.dev>
2025-08-12 09:26:46 -05:00
183a432116 test: add tests for collision, items, directional, sprite
enum macros for FruitKind
2025-08-12 09:18:53 -05:00
ead1466b2d chore: specify 'llvm-tools-preview' toolchain component for coverage in toolchain file 2025-08-12 00:22:27 -05:00
8ef09a4e3e test: drop minimal_test_board, use RAW_BOARD constant, item generation tests 2025-08-11 23:26:28 -05:00
33672d8d5a feat: implement collision detection system for entities 2025-08-11 23:24:23 -05:00
1dc8aca373 feat: item collection & collisions, pellet & energizer generation 2025-08-11 22:45:36 -05:00
02089a78da chore: downgrade toolchain to 1.86 on all versions
This is just because managing both 1.86 and 1.88 is really annoying, so
it's better to just be unified. There's no real point to using 1.88
besides more clippy warnings, which are already impeding my work right
now. So we're downgrading.
2025-08-11 22:10:41 -05:00
1f8e7c6d71 fix: resolve clippy warnings, inline format vars, use tracing to log warnings 2025-08-11 22:09:08 -05:00
27079e127d feat!: implement proper error handling, drop most expect() & unwrap() usages 2025-08-11 20:23:39 -05:00
5e9bb3535e ci: add dependabot config 2025-08-11 19:24:52 -05:00
250cf2fc89 fix: avoid rendering path lines between far apart cells 2025-08-11 18:39:01 -05:00
57975495a9 fix: calculate more static, stable offsets for path debug rendering 2025-08-11 16:00:23 -05:00
f3e7a780e2 fix: drop problematic ctrl-c keybind for bacon, reconfigure binds 2025-08-11 15:46:26 -05:00
ee6cb0a670 refactor: implement entity trait, common abstraction for movement & rendering 2025-08-11 15:46:04 -05:00
b3df34b405 fix: crash when entering right tunnel due to overflowing pixel position calculation 2025-08-11 15:44:04 -05:00
dbafa17670 chore: add bacon.toml config file 2025-08-11 15:25:53 -05:00
d9c8f97903 feat: pathfinding for ghosts, add debug rendering of paths 2025-08-11 15:25:39 -05:00
ad2ec35bfb chore: remove unused tracing debug invocations 2025-08-11 15:23:23 -05:00
6331ba0b2f refactor: move graph traversal code into traversal.rs 2025-08-11 14:05:28 -05:00
3d275b8e85 fix: clippy inline format args 2025-08-11 14:05:28 -05:00
bd61db9aae chore: remove unnecessary names, merge audit.yaml with tests.yaml, plural tests.yaml 2025-08-11 14:05:28 -05:00
ed8bd07518 fix: site rendering, fix SVG colors, remove header, viewport scaling, simplify 2025-08-11 12:20:52 -05:00
27705f1ba2 feat: implement ghost entities, movement & rendering 2025-08-11 11:54:05 -05:00
e964adc818 feat: enhance debug visuals with cursor-based effect 2025-08-11 11:54:05 -05:00
c5213320ac fix(emscripten): string pointer casting, fixup AssetError handling 2025-08-11 11:25:52 -05:00
e0f8443e75 refactor: replace HashMap with fixed-size arrays for textures in DirectionalAnimatedTexture 2025-08-11 11:13:46 -05:00
6702b3723a refactor: move DIRECTIONS constant into direction, add as_u8() const fn for array indexing 2025-08-11 11:03:46 -05:00
f6e7228f75 refactor: platform trait, platform-specific code handling into platform module 2025-08-11 10:49:58 -05:00
14cebe4462 chore: use logtape logger properly 2025-08-11 10:34:26 -05:00
c39fcaa7d7 feat: add timestamp-based tailwind cli downloading, add logtape logging, validate content-length 2025-08-11 10:31:54 -05:00
1d9499c4f8 docs: improve story & readme, simplify top header, expand experiment ideas, more build notes 2025-08-08 13:42:26 -05:00
61050a5585 chore: use fallback installer for bun run in build workflow 2025-08-08 13:34:54 -05:00
85420711df fix: preventDefault() context menu on canvas 2025-08-08 13:34:36 -05:00
2efa7a4df5 feat: manually download tailwindcss cli, only check for emsdkDir if not activated 2025-08-08 11:04:31 -05:00
1d018db5e9 ci: handle pre-activated emsdk 2025-08-08 10:15:43 -05:00
023697dcd7 fix: use bun and web.build.ts in build workflow, use minify & cwd args for tailwindcss cli 2025-08-08 10:10:57 -05:00
87ee12543e tests: revamp tests, remove more useless tests 2025-08-08 09:07:10 -05:00
b308bc0ef7 refactor: move all tests out of src/ into tests/, remove unnecessary tests 2025-08-08 08:50:52 -05:00
9d5ca54234 fix: improved frontend web interface, use tailwind cli 2025-08-08 00:20:38 -05:00
2ae73c3c58 fix: disable app quit on browser build 2025-08-07 23:44:26 -05:00
adfa2cc737 feat: edge traversal permissions system 2025-08-07 23:39:39 -05:00
7c937df002 docs: add build notes to README 2025-08-07 23:26:48 -05:00
9fb9c959a3 refactor: ensure emsdk dir exists before issuing commands 2025-08-07 23:20:12 -05:00
61ebc8f317 chore: fixup .gitignore 2025-08-07 23:00:03 -05:00
b7f668c58a feat: revamp web build script in bun + typescript, delete old scripts 2025-08-07 22:59:51 -05:00
b1021c28b5 chore: remove unused door/map.png/build.css 2025-08-07 22:58:38 -05:00
7d6f92283a docs: update README with badges, remove unnecessary install details, change workflow names 2025-07-28 21:09:42 -05:00
2a295b1daf test: small test lint fixes 2025-07-28 20:55:45 -05:00
4398ec2936 chore: fix clippy errors, add allow dead_code modifiers 2025-07-28 20:53:01 -05:00
324c358672 refactor: remove StartingPosition MapTile, track pacman start explicitly in parser 2025-07-28 20:49:17 -05:00
cda8c40195 test: allow mute state updates while audio subsystem is disabled 2025-07-28 20:48:13 -05:00
89b4ba125f feat: begin tracking nodes of entity starting positions 2025-07-28 20:41:26 -05:00
fcdbe62f99 feat: allow graceful disabling of audio subsystem in background 2025-07-28 20:38:16 -05:00
57c7afcdb4 ci: emit warnings on retry attempts in emscripten build 2025-07-28 20:25:13 -05:00
2e16c2d170 ci: add retry mechanism for emscripten builds due to dependency hash errors in sdk 2025-07-28 20:18:28 -05:00
f86c106593 test: switch to llvm-cov for coverage, switch to cargo-nextest as test runner 2025-07-28 19:59:40 -05:00
04cf8f217f test: add generic tests for coverage 2025-07-28 19:48:31 -05:00
7e0ca4ff3d test: disable fail-fast by default 2025-07-28 19:43:16 -05:00
fcc36c8a46 test: add tons of tests for all easy submodules 2025-07-28 19:26:36 -05:00
41affcd7ad test: add tests for centered_with_size 2025-07-28 18:52:34 -05:00
4ecfded4ac refactor: center Rect with centered_with_size helper 2025-07-28 18:47:24 -05:00
25d5121a28 ci: correct toolchain matrix args 2025-07-28 18:32:13 -05:00
91095ed2cc ci: switch tarpaulin output to lcov format 2025-07-28 18:28:43 -05:00
cbf52bb994 ci: add 'rustfmt' component for test workflow 2025-07-28 18:13:23 -05:00
d763b9646f chore: 'node' runner on emscripten target 2025-07-28 18:08:47 -05:00
d7a9e0a304 ci: update most toolchains to 1.88, keep emscripten on 1.86.0 2025-07-28 18:08:29 -05:00
db720edeef ci: move comment breaking up 'rustflags' for coverage linking 2025-07-28 18:05:54 -05:00
f241e85d8f ci: set rustflags for cargo-tarpaulin build linking 2025-07-28 17:32:53 -05:00
d18b414536 ci: add 'clippy' component to test workflow 2025-07-28 17:28:47 -05:00
c9bcf32381 chore: fix various clippy warnings, disable trivial warnings in some spot 2025-07-28 17:25:18 -05:00
b45980c172 ci: only deploy to pages on master pushes 2025-07-28 17:09:21 -05:00
b4e3f383ec ci: add audit, test & coverage workflows 2025-07-28 17:09:06 -05:00
532abd1e45 chore: remove unused params for debug_render_nodes func 2025-07-28 16:22:48 -05:00
70528b0dcc refactor: separate map struct into multiple files for building, rendering & parsing 2025-07-28 16:20:24 -05:00
c5ca7302c2 refactor: separate map parsing into MapTileParser, get tests working 2025-07-28 16:10:50 -05:00
a27f85279e feat: working perfect tunnels with offset house positioning nodes 2025-07-28 14:34:24 -05:00
bea915b5c7 docs: post-creation neighbor edges, no ignore result err 2025-07-28 13:25:51 -05:00
d743aee393 refactor: better graph connection functions & creation method, debug render connections 2025-07-28 13:23:12 -05:00
59aba9f691 fix: remove emscripten main_loop_callback targeted code 2025-07-28 12:48:10 -05:00
199b4dc939 refactor: static intersection struct for calculating edges instead of smallvec 2025-07-28 12:44:54 -05:00
2edd23cfbb docs: add all latest developments to STORY.md 2025-07-28 12:25:56 -05:00
464d6f9ca6 refactor: huge refactor into node/graph-based movement system 2025-07-28 12:23:57 -05:00
413f9f156f refactor: continue working on ghost house implementation, other stuff 2025-07-27 12:15:11 -05:00
4f87a116d5 chore: remove unused code, resolve simple stuff 2025-07-26 15:35:50 -05:00
86ffc931e8 fix: re-provide specific blue color to maze texture 2025-07-26 15:31:15 -05:00
d72f47d66c fix: fix tunneling logic 2025-07-26 15:27:17 -05:00
7a6182cb85 feat: re-add board offset logic, fixup text rendering 2025-07-26 15:26:37 -05:00
a1d37a1a0b feat: atlas tile color modulation 2025-07-26 15:06:27 -05:00
9066b2cdbc chore: allow deadcode in asset.rs due to emscripten builds 2025-07-26 14:43:43 -05:00
238b5aac6a feat: non-ttf text rendering using original sprite text, remove black bg from assets 2025-07-26 14:43:25 -05:00
8e5ec9fef0 refactor: huge refactor into atlas-based resources 2025-07-26 14:42:12 -05:00
6ca2e01fba feat: atlas decoding & frame acquisition 2025-07-25 12:27:19 -05:00
8cf30cd78d refactor: begin moving towards packed atlas configuration 2025-07-25 12:24:40 -05:00
9b441fa35c feat: shared blinking textures for power pellets 2025-07-24 16:23:09 -05:00
61ca537909 fix: continue removing lifetime annotations 2025-07-24 16:09:25 -05:00
0a82aea922 refactor: intentionally unsafe leak to drop annoying lifetimes, use IVec2 in all remaining spots 2025-07-24 16:04:47 -05:00
f41c550bb8 feat: enable basic binary size reduction options 2025-07-24 15:39:16 -05:00
829462d3b6 refactor: move direction & edible into entity submodule 2025-07-24 12:48:39 -05:00
002da46045 refactor: split up and move texture-related code into src/texture submodule 2025-07-24 12:48:39 -05:00
cfa73c58a8 refactor: move entity-related code into src/entity submodule 2025-07-24 12:36:48 -05:00
5728effcc6 chore: bump to v0.2.0 2025-07-24 12:14:26 -05:00
fa1a0175b0 ci: remove save-always, remove old vcpkg cache key, flush vcpkg caches 2025-07-24 03:29:22 -05:00
85edb18380 ci: drop linux dependencies: build-essential gettext zlib1g-dev 2025-07-24 03:26:05 -05:00
3a535ee04f fix: linux build linking arg, working build 2025-07-24 02:59:58 -05:00
9b31b392d2 fix(wasm): increase asyncify stack size, working wasm build 2025-07-24 02:39:57 -05:00
999fa14059 chore: remove unused config.toml comments 2025-07-24 02:37:49 -05:00
e925376b7a feat: setup emscripten module for api layer 2025-07-24 02:37:41 -05:00
2596034365 feat: use smallrng for emscripten compat 2025-07-24 02:37:27 -05:00
163855b6e7 fix(wasm): increase audio chunksize to 256 minimum for emscripten audio ctx 2025-07-24 01:14:03 -05:00
645d48aeae ci: use updated setup-emsdk from 'pyodide' v15, fixes emsdk caching, always save vcpkg cache 2025-07-24 01:12:45 -05:00
ec800a88fc fix: use sdl2 internal methods for loading resources for emscripten asset handling 2025-07-24 01:07:22 -05:00
abc37dee4e ci: fix vcpkg cache keys to use target for platforms with multiple targets, allow restore oldkey 2025-07-24 01:04:21 -05:00
1ae7839275 ci: install zlib for linux builds, correct deps/.data filepath 2025-07-24 01:04:21 -05:00
d976d1bc59 ci: specify vcpkg triplets for macos targets 2025-07-24 00:49:22 -05:00
531a5b5d05 ci: inline build script contents, shorten assembly process, separate build/assemble steps 2025-07-24 00:48:49 -05:00
67713fab06 ci: add aarch64-apple-drawin target 2025-07-24 00:46:06 -05:00
b572729e9d feat: reorganize assets/ folder into web/ and game/ 2025-07-24 00:46:06 -05:00
cdc6979458 ci: add cache vcpkg step, remove ineffective apt pkgs 2025-07-24 00:09:43 -05:00
564f88fee5 ci: use proper vcpkg triplet for linux, macos 2025-07-24 00:09:43 -05:00
00c99dc05f ci: remove 'stable' from vcpkg target triplet key 2025-07-23 23:55:15 -05:00
1e12940445 docs: remove unused markdown files in root 2025-07-23 23:24:30 -05:00
dc3c4a7580 ci: VCPKG_SYSTEM_LIBRARIES off, cache emsdk, raw jq output 2025-07-23 23:23:41 -05:00
434b62b036 ci: use cargo metadata with jq for acquiring workspace package version, drop binstall 2025-07-23 23:12:58 -05:00
2bd523e58a ci: add build-essential dependency, apt-get update before 2025-07-23 23:05:06 -05:00
7cd6e8005e ci: add names to workflow jobs, configure binstall to have lower timeout 2025-07-23 22:58:09 -05:00
a8a3745ca1 ci: disable fail-fast, ensure linux vcpkg dependencies are installed 2025-07-23 22:46:57 -05:00
cfa26bf146 ci: use build matrix for desktop builds 2025-07-23 22:45:56 -05:00
bfbbb71752 ci: verbose vcpkg builds on linux 2025-07-23 22:30:11 -05:00
979f736f54 ci: drop rust toolchain to 1.86.0 2025-07-23 22:26:08 -05:00
5a7f6a4c10 ci: remove archive assembly steps for statically linked builds 2025-07-23 22:22:55 -05:00
b66c9ce135 fix: emscripten assets 2025-07-23 22:18:54 -05:00
f5363516c3 chore: remove unused sdl2-image vcpkg features 2025-07-23 21:53:55 -05:00
320da36b83 feat: use cargo-vcpkg in build workflow 2025-07-23 21:53:03 -05:00
b68813cf5b ci: explicit toolchain version, fix build script path for wasm 2025-07-23 21:53:03 -05:00
0806fc744c chore: remove unused animation pausing methods 2025-07-23 21:34:27 -05:00
eead31d7fc refactor: add 'glam' for better positioning types, drop position types 2025-07-23 21:24:47 -05:00
eaa4ab37f9 feat: add audio muting button, mute by default in debug builds 2025-07-23 21:00:52 -05:00
076275158e chore: lower audio tracing to trace level 2025-07-23 20:36:56 -05:00
9f9ace0b16 chore: configure rustfmt, switch to LF line endings 2025-07-23 20:15:54 -05:00
2cc47d5904 feat: add pre-comit configuration 2025-07-23 20:12:11 -05:00
11e89a63d0 refactor: add thiserror/anyhow for asset error handling 2025-07-23 19:47:44 -05:00
50afd8c09f feat: improved emscripten-compatible asset loading api 2025-07-23 18:02:19 -05:00
06841fd0d7 refactor: resolve clippy warnings, resolve shared reference with once_cell 2025-07-23 17:38:27 -05:00
4365639a1d chore: lifetimes 2025-07-23 17:31:16 -05:00
7744c06046 chore: cargo fix 2025-07-23 17:27:22 -05:00
978752f0f3 chore: add index for FruitType sprite 2025-07-23 17:25:39 -05:00
f024ce7a54 refactor: fix unnecessary qualified imports 2025-07-23 17:25:28 -05:00
0196282a78 fix: reset code borrows 2025-07-23 17:20:12 -05:00
785a760343 feat: new edible type for pellet/powerpellet, fruits, separate static/moving entities 2025-07-23 17:16:15 -05:00
de1a89b9b0 refactor: move debug related code into debug.rs 2025-07-23 16:31:09 -05:00
66b6cdf01b feat: split animated texture away from atlas texture details 2025-07-23 16:25:40 -05:00
5a48e83b1a feat: flood-filled based playable position with cache, debug mode 2025-07-23 15:06:38 -05:00
df8f858651 refactor: continue improving MovableEntity shared implementation 2025-07-23 15:06:09 -05:00
1fa7a0807f refactor: abstract entity details into MovableEntity 2025-07-23 14:08:28 -05:00
6d3d3bf49c feat: tunnel implementation, pathfinding debug mode 2025-07-22 14:37:26 -05:00
0a46f64866 feat: pathfinding, ghost and blinky state, update dependencies 2025-07-22 13:18:09 -05:00
fd7eecf53e feat: allow instant direction reversal, improve cell position state tracking 2025-07-22 12:12:41 -05:00
f540dc5373 docs: minor documentation commentsa cross project 2025-07-22 12:12:41 -05:00
f51a3ddeb0 chore: web frontend files 2025-07-22 10:06:30 -05:00
9f7c460369 chore: ignore ephemeral submodules, build.css 2025-07-22 10:06:13 -05:00
2c0df99dea fix: emscripten libc, dynamic feature targets for emscripten, vcpkg triplets for linux 2025-07-22 10:05:07 -05:00
cf12a04c69 chore: use emscripten linker args direct from demo project 2025-07-22 09:19:46 -05:00
fa7e985c0d feat: embedded data files, audio effects processing 2025-07-21 22:30:40 -05:00
f5ff90cb11 feat: smarter winapi-based console window handling 2025-07-21 21:54:22 -05:00
a0f65b551c feat: board reset, store original map matrix 2025-07-18 20:21:16 -05:00
8808a1aa3b feat: pellet consumption, score & map mutation 2025-07-18 20:15:50 -05:00
62b2c607a9 feat: ttf score rendering, konami font 2025-07-18 20:07:50 -05:00
14b34db6de feat: enable sdl2 gfx feature, update sdl2, use vcpkg for library builds 2025-07-18 19:30:57 -05:00
9238b53c40 refactor: simplify/improve is_adjacent, add tests & docs 2025-07-18 19:28:39 -05:00
3e498ee5c3 feat: pull latest rust-sdl2-emscripten build workflow 2025-06-17 12:00:59 -05:00
715eeb6296 docs(story): sdl2 mixer + gfx extensions, cross platform 2025-06-17 11:54:13 -05:00
682ce059fa docs(story): SDL2-TTF 2025-06-17 11:54:13 -05:00
c8314b23dd docs(story): long break, emscripten callback main loop, asyncify, vsync 2025-06-17 11:54:13 -05:00
40acffafd1 fix: rebuild, try removing zero ms sleeps 2025-06-17 11:54:13 -05:00
2187212b7c chore: increase speed, no modulation, increase animation speed 2025-06-17 11:54:13 -05:00
229d2242ef fix: minor comments, disable accelerated, use std sleep on web builds 2025-06-17 11:54:13 -05:00
00c4c76299 chore: add ogg/vorbis comment for emscripten 2025-06-17 11:54:13 -05:00
8b30a602bf fix: remove idbfs.js inclusion linker arg 2025-06-17 11:54:13 -05:00
83a5ccdb8e chore: delete emscripten.rs 2025-06-17 11:54:13 -05:00
44d8184d8b feat: downloading in Windows build process, cleaning script 2025-06-17 11:54:13 -05:00
0630fc56ec fix: key stealing, disable Emscripten module, disable colored ANSI for emscripten builds 2025-06-17 11:54:13 -05:00
98d8960c57 docs(story): begin documenting project story/history 2025-06-17 11:54:13 -05:00
394344c11f docs: experimental scoreboard concept 2025-06-17 11:54:13 -05:00
93ba470ce9 fix: progress on reproducible Windows builds, disable script tracing 2025-06-17 11:54:13 -05:00
129aed0ffb docs: add more TODOs 2025-06-17 11:54:13 -05:00
e062ada301 chore: remove old windows build/serve scripts 2025-06-17 11:54:13 -05:00
af57199915 fix: center canvas, make background black 2025-06-17 11:54:13 -05:00
538cf1efb5 chore: copy .data file directly, remove locateFile step 2025-06-17 11:54:13 -05:00
03b2c5a659 ci: cleanup build script 2025-06-17 11:54:13 -05:00
64e226be70 ci: properly create deps folder for pacman.data during build 2025-06-17 11:54:13 -05:00
f998ddd344 docs: add build details 2025-06-17 11:54:13 -05:00
b2ad8e7afe ci: add permisions for deployment job 2025-06-17 11:54:13 -05:00
799d5d85e8 ci: add action-based deploy with artifacts 2025-06-17 11:54:13 -05:00
9730d02da5 ci: fix build-wasm execution permissions 2025-06-17 11:54:13 -05:00
f634beffee fix(wasm): remove unnecessary emscripten looping 2025-06-17 11:54:13 -05:00
d15dbe3982 ci: prepare proper build script, move script into /scripts, move index.html into /assets 2025-06-17 11:54:13 -05:00
de5cddd9b6 fix(wasm): wasm32-unknown-emscripten compiler flags 2025-06-17 11:54:13 -05:00
e3f37ab48e ci: target proper version of Emscripten (1.39.20) 2025-06-17 11:54:13 -05:00
3dd8d5aff7 chore: increase stat reporting period to 60 seconds 2025-06-17 11:54:13 -05:00
ad084d1cd8 feat: add pausing functionality, clean up statistic calculations 2025-06-17 11:54:13 -05:00
852e54f1bf chore: rust-fmt entire project 2025-06-17 11:54:13 -05:00
a62ddab9af fix: prevent changing direction into walls 2025-06-17 11:54:13 -05:00
50d0bc7d5f refactor: simplify keyboard event direction change logic in Game 2025-06-17 11:54:13 -05:00
2c6045aa1b feat: add map struct, overhaul stored map representation 2025-06-17 11:54:13 -05:00
bf8370ef35 feat: sprite frame pinning, conditional on stopped PacMan 2025-06-17 11:54:13 -05:00
c71b6d69ab fix: always use tracing, provide timing info 2s in + every 60s first 2025-06-17 11:54:13 -05:00
a7e87c18a3 feat: pacman next cell debug func 2025-06-17 11:54:13 -05:00
95298fbc00 feat: keycode to direction utility function 2025-06-17 11:54:13 -05:00
fe18eafbaf chore: doc & expose TickModulator trait, rename speed to tick 2025-06-17 11:54:13 -05:00
60eaa428ac reformat: improve AnimatedTexture API with paused animation abilities 2025-06-17 11:54:13 -05:00
18eaeee19e fix: compile time removal of tracing below WARN on release builds 2025-06-17 11:54:13 -05:00
b3c1a30a74 feat: tracing, sleep timing calculations, use spin_sleeper for accurate sleeps on Windows 2025-06-17 11:52:08 -05:00
0d76c6528b docs: add DLL instructions to README, expand .gitignore 2025-06-17 11:51:57 -05:00
da98b54216 feat: wall collisions 2025-06-17 11:51:49 -05:00
6ce3a5ce79 feat: speed modulation to implement precise speed decrease despite integers 2025-06-17 11:51:40 -05:00
b987599f10 reformat: general, target conditional module 2025-06-17 11:51:35 -05:00
285 changed files with 9561 additions and 693 deletions

View File

@@ -1,4 +1,17 @@
[target.wasm32-unknown-emscripten]
[target.'cfg(target_os = "emscripten")']
rustflags = [
"--use-preload-plugins --preload-file assets -s USE_SDL=2 -s USE_SDL_IMAGE=2 -s ASSERTIONS=1",
# Stack size is required for this project, it will crash otherwise.
"-C", "link-args=-sASYNCIFY=1 -sASYNCIFY_STACK_SIZE=8192 -sALLOW_MEMORY_GROWTH=1",
"-C", "link-args=-sUSE_SDL=2 -sUSE_SDL_IMAGE=2 -sUSE_SDL_MIXER=2 -sUSE_OGG=1 -sUSE_SDL_GFX=2 -sUSE_SDL_TTF=2 -sSDL2_IMAGE_FORMATS=['png']",
"-C", "link-args=--preload-file assets/game/",
]
runner = "node"
[target.'cfg(target_os = "linux")']
rustflags = [
# Manually link zlib.
# The `sdl2` crate's build script uses `libpng`, which requires `zlib`.
# By adding `-lz` here, we ensure it's passed to the linker after `libpng`,
# which is required for the linker to correctly resolve symbols.
"-C", "link-arg=-lz",
]

5
.config/nextest.toml Normal file
View File

@@ -0,0 +1,5 @@
[profile.default]
fail-fast = false
[profile.coverage]
status-level = "none"

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
* text=auto eol=lf

20
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "monthly"
groups:
dependencies:
patterns:
- "*"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
groups:
dependencies:
patterns:
- "*"

162
.github/workflows/build.yaml vendored Normal file
View File

@@ -0,0 +1,162 @@
name: Builds
on: ["push", "pull_request"]
permissions:
contents: write
jobs:
build:
name: Build (${{ matrix.target }})
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
artifact_name: pacman
toolchain: 1.86.0
- os: macos-13
target: x86_64-apple-darwin
artifact_name: pacman
toolchain: 1.86.0
- os: macos-latest
target: aarch64-apple-darwin
artifact_name: pacman
toolchain: 1.86.0
- os: windows-latest
target: x86_64-pc-windows-gnu
artifact_name: pacman.exe
toolchain: 1.86.0
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup Rust Toolchain
uses: dtolnay/rust-toolchain@master
with:
target: ${{ matrix.target }}
toolchain: ${{ matrix.toolchain }}
- name: Rust Cache
uses: Swatinem/rust-cache@v2
- name: Cache vcpkg
uses: actions/cache@v4
with:
path: target/vcpkg
key: A-vcpkg-${{ runner.os }}-${{ matrix.target }}-${{ hashFiles('Cargo.toml', 'Cargo.lock') }}
restore-keys: |
A-vcpkg-${{ runner.os }}-${{ matrix.target }}-
- name: Vcpkg Linux Dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y libltdl-dev
- name: Vcpkg
run: |
cargo install cargo-vcpkg
cargo vcpkg -v build
- name: Build
run: cargo build --release
- name: Acquire Package Version
id: get_version
shell: bash # required to prevent Windows runners from failing
run: |
set -euo pipefail # exit on error
echo "version=$(cargo metadata --format-version 1 --no-deps | jq '.packages[0].version' -r)" >> $GITHUB_OUTPUT
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: "pacman-${{ steps.get_version.outputs.version }}-${{ matrix.target }}"
path: ./target/release/${{ matrix.artifact_name }}
retention-days: 7
if-no-files-found: error
wasm:
name: Build (wasm32-unknown-emscripten)
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
# concurrency group is used to prevent multiple page deployments from being attempted at the same time
concurrency:
group: ${{ github.workflow }}-wasm
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup Emscripten SDK
uses: pyodide/setup-emsdk@v15
with:
version: 3.1.43
actions-cache-folder: "emsdk-cache-b"
- name: Setup Rust (WASM32 Emscripten)
uses: dtolnay/rust-toolchain@master
with:
target: wasm32-unknown-emscripten
toolchain: 1.86.0
- name: Rust Cache
uses: Swatinem/rust-cache@v2
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Build with Emscripten
shell: bash
run: |
# Retry mechanism for Emscripten build - only retry on specific hash errors
MAX_RETRIES=3
RETRY_DELAY=30
for attempt in $(seq 1 $MAX_RETRIES); do
echo "Build attempt $attempt of $MAX_RETRIES"
# Capture output and check for specific error while preserving real-time output
if bun run -i web.build.ts 2>&1 | tee /tmp/build_output.log; then
echo "Build successful on attempt $attempt"
break
else
echo "Build failed on attempt $attempt"
# Check if the failure was due to the specific hash error
if grep -q "emcc: error: Unexpected hash:" /tmp/build_output.log; then
echo "::warning::Detected 'emcc: error: Unexpected hash:' error - will retry (attempt $attempt of $MAX_RETRIES)"
if [ $attempt -eq $MAX_RETRIES ]; then
echo "::error::All retry attempts failed. Exiting with error."
exit 1
fi
echo "Waiting $RETRY_DELAY seconds before retry..."
sleep $RETRY_DELAY
# Exponential backoff: double the delay for next attempt
RETRY_DELAY=$((RETRY_DELAY * 2))
else
echo "Build failed but not due to hash error - not retrying"
exit 1
fi
fi
done
- name: Upload Artifact
uses: actions/upload-pages-artifact@v3
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
with:
path: "./dist/"
retention-days: 7
- name: Deploy
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
uses: actions/deploy-pages@v4

80
.github/workflows/coverage.yaml vendored Normal file
View File

@@ -0,0 +1,80 @@
name: Code Coverage
on: ["push", "pull_request"]
env:
CARGO_TERM_COLOR: always
RUST_TOOLCHAIN: 1.86.0
jobs:
coverage:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
components: llvm-tools-preview
- name: Rust Cache
uses: Swatinem/rust-cache@v2
- name: Cache vcpkg
uses: actions/cache@v4
with:
path: target/vcpkg
key: A-vcpkg-${{ runner.os }}-${{ hashFiles('Cargo.toml', 'Cargo.lock') }}
restore-keys: |
A-vcpkg-${{ runner.os }}-
- name: Vcpkg Linux Dependencies
run: |
sudo apt-get update
sudo apt-get install -y libltdl-dev
- name: Vcpkg
run: |
cargo install cargo-vcpkg
cargo vcpkg -v build
- uses: taiki-e/install-action@cargo-llvm-cov
- uses: taiki-e/install-action@nextest
- uses: taiki-e/install-action@just
- name: Generate coverage report
run: |
just coverage
- name: Download Coveralls CLI
run: |
# use GitHub Releases URL instead of coveralls.io because they can't maintain their own files; it 404s
curl -L https://github.com/coverallsapp/coverage-reporter/releases/download/v0.6.15/coveralls-linux-x86_64.tar.gz | tar -xz -C /usr/local/bin
- name: Upload coverage to Coveralls
env:
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
run: |
if [ ! -f "lcov.info" ]; then
echo "Error: lcov.info file not found. Coverage generation may have failed."
exit 1
fi
for i in {1..10}; do
echo "Attempt $i: Uploading coverage to Coveralls..."
if coveralls -n report lcov.info; then
echo "Successfully uploaded coverage report."
exit 0
fi
if [ $i -lt 10 ]; then
delay=$((2**i))
echo "Attempt $i failed. Retrying in $delay seconds..."
sleep $delay
fi
done
echo "Failed to upload coverage report after 10 attempts."
exit 1

View File

@@ -1,28 +0,0 @@
name: Github Pages
on: [push]
permissions:
contents: write
jobs:
build-github-pages:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2 # repo checkout
- uses: mymindstorm/setup-emsdk@v11 # setup emscripten toolchain
# with:
# version: 3.1.35
- uses: actions-rs/toolchain@v1 # get rust toolchain for wasm
with:
toolchain: stable
target: wasm32-unknown-emscripten
override: true
- name: Rust Cache # cache the rust build artefacts
uses: Swatinem/rust-cache@v1
- name: Build # build
run: ./build.sh
- name: Deploy
uses: JamesIves/github-pages-deploy-action@v4
with:
folder: dist

58
.github/workflows/tests.yaml vendored Normal file
View File

@@ -0,0 +1,58 @@
name: Tests & Checks
on: ["push", "pull_request"]
env:
CARGO_TERM_COLOR: always
RUST_TOOLCHAIN: 1.86.0
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
components: clippy, rustfmt
- name: Rust Cache
uses: Swatinem/rust-cache@v2
- name: Cache vcpkg
uses: actions/cache@v4
with:
path: target/vcpkg
key: A-vcpkg-${{ runner.os }}-${{ hashFiles('Cargo.toml', 'Cargo.lock') }}
restore-keys: |
A-vcpkg-${{ runner.os }}-
- name: Vcpkg Linux Dependencies
run: |
sudo apt-get update
sudo apt-get install -y libltdl-dev
- name: Vcpkg
run: |
cargo install cargo-vcpkg
cargo vcpkg -v build
- uses: taiki-e/install-action@nextest
- name: Run nextest
run: cargo nextest run --workspace
- name: Run clippy
run: cargo clippy -- -D warnings
- name: Check formatting
run: cargo fmt -- --check
- uses: taiki-e/install-action@cargo-audit
- name: Run security audit
run: cargo audit

17
.gitignore vendored
View File

@@ -1,2 +1,17 @@
/target
# IDE, Other files
.vscode
.idea
rust-sdl2-emscripten/
# Build files
target/
dist/
emsdk/
# Site build f iles
tailwindcss-*
assets/site/build.css
# Coverage reports
lcov.info
coverage.html

22
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,22 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-added-large-files
- id: check-merge-conflict
- id: check-case-conflict
- id: check-toml
- id: check-yaml
- id: forbid-submodules
- id: mixed-line-ending
- repo: local
hooks:
- id: cargo-fmt
name: cargo fmt
entry: cargo fmt --all --
language: system
types: [rust]
pass_filenames: false

1397
Cargo.lock generated
View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,68 @@
[package]
name = "pacman"
version = "0.1.0"
version = "0.2.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
lazy_static = "1.4.0"
sdl2 = { version = "0.35", features = ["image", "ttf", "mixer"] }
tracing = { version = "0.1.41", features = ["max_level_debug", "release_max_level_debug"]}
tracing-error = "0.2.0"
tracing-subscriber = {version = "0.3.17", features = ["env-filter"]}
lazy_static = "1.5.0"
sdl2 = { version = "0.38.0", features = ["image", "ttf"] }
spin_sleep = "1.3.2"
rand = { version = "0.9.2", default-features = false, features = ["small_rng", "os_rng"] }
pathfinding = "4.14"
once_cell = "1.21.3"
thiserror = "2.0.14"
anyhow = "1.0"
glam = "0.30.5"
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.142"
smallvec = "1.15.1"
strum = "0.27.2"
strum_macros = "0.27.2"
phf = { version = "0.12.1", features = ["macros"] }
bevy_ecs = "0.16.1"
bitflags = "2.9.1"
parking_lot = "0.12.3"
[profile.release]
lto = true
panic = "abort"
opt-level = "z"
[target.'cfg(target_os = "windows")'.dependencies.winapi]
version = "0.3"
features = ["consoleapi", "fileapi", "handleapi", "processenv", "winbase", "wincon", "winnt", "winuser", "windef", "minwindef"]
[target.'cfg(target_os = "emscripten")'.dependencies.sdl2]
version = "0.38"
default-features = false
features = ["ttf","image","gfx","mixer"]
[target.'cfg(not(target_os = "emscripten"))'.dependencies.sdl2]
version = "0.38"
default-features = false
features = ["ttf","image","gfx","mixer","static-link","use-vcpkg"]
[package.metadata.vcpkg]
dependencies = ["sdl2", "sdl2-image", "sdl2-ttf", "sdl2-gfx", "sdl2-mixer"]
git = "https://github.com/microsoft/vcpkg"
rev = "2024.05.24" # release 2024.05.24 # to check for a new one, check https://github.com/microsoft/vcpkg/releases
[package.metadata.vcpkg.target]
x86_64-pc-windows-msvc = { triplet = "x64-windows-static-md" }
x86_64-unknown-linux-gnu = { triplet = "x64-linux" }
x86_64-apple-darwin = { triplet = "x64-osx" }
aarch64-apple-darwin = { triplet = "arm64-osx" }
[target.'cfg(target_os = "emscripten")'.dependencies]
libc = "0.2.175"
[build-dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
phf = { version = "0.12.1", features = ["macros"] }

View File

@@ -1,35 +0,0 @@
# Implementation
A document detailing the implementation the project from rendering, to game logic, to build systems.
## Rendering
1. Map
- May require procedural text generation later on (cacheable?)
2. Pacman
3. Ghosts
- Requires colors
4. Items
5. Interface
- Requires fonts
## Grid System
1. How does the grid system work?
The grid is 28 x 36 (although, the map texture is 28 x 37), and each cell is 24x24 (pixels).
Many of the walls in the map texture only occupy a portion of the cell, so some items are able to render across multiple cells.
24x24 assets include pellets, the energizer, and the map itself ()
2. What constraints must be enforced on Ghosts and PacMan?
3. How do movement transitions work?
All entities store a precise position, and a direction. This position is only used for animation, rendering, and collision purposes. Otherwise, a separate 'cell position' (which is 24 times less precise, owing to the fact that it is based on the entity's position within the grid).
When an entity is transitioning between cells, movement directions are acknowledged, but won't take effect until the next cell has been entered completely.
4. Between transitions, how does collision detection work?
It appears the original implementation used cell-level detection.
I worry this may be prone to division errors. Make sure to use rounding (50% >=).

33
Justfile Normal file
View File

@@ -0,0 +1,33 @@
set shell := ["bash", "-c"]
set windows-shell := ["powershell.exe", "-NoLogo", "-Command"]
# Regex to exclude files from coverage report, double escapes for Justfile + CLI
# You can use src\\\\..., but the filename alone is acceptable too
coverage_exclude_pattern := "src\\\\app.rs|audio.rs|src\\\\error.rs|platform\\\\emscripten.rs"
# !!! --ignore-filename-regex should be used on both reports & coverage testing
# !!! --remap-path-prefix prevents the absolute path from being used in the generated report
# Generate HTML report (for humans, source line inspection)
html: coverage
cargo llvm-cov report \
--remap-path-prefix \
--ignore-filename-regex "{{ coverage_exclude_pattern }}" \
--html \
--open
# Display report (for humans)
report-coverage: coverage
cargo llvm-cov report \
--remap-path-prefix \
--ignore-filename-regex "{{ coverage_exclude_pattern }}"
# Run & generate report (for CI)
coverage:
cargo llvm-cov \
--lcov \
--remap-path-prefix \
--ignore-filename-regex "{{ coverage_exclude_pattern }}" \
--output-path lcov.info \
--profile coverage \
--no-fail-fast nextest

119
README.md
View File

@@ -1,66 +1,89 @@
# Pac-Man
If the title doesn't clue you in, I'm remaking Pac-Man with SDL and Rust.
[![Tests Status][badge-test]][test] [![Build Status][badge-build]][build] [![If you're seeing this, Coveralls.io is broken again and it's not my fault.][badge-coverage]][coverage] [![Online Demo][badge-online-demo]][demo] [![Last Commit][badge-last-commit]][commits]
The project is _extremely_ early in development, but check back in a week, and maybe I'll have something cool to look
at.
[badge-test]: https://github.com/Xevion/Pac-Man/actions/workflows/tests.yaml/badge.svg
[badge-build]: https://github.com/Xevion/Pac-Man/actions/workflows/build.yaml/badge.svg
[badge-coverage]: https://coveralls.io/repos/github/Xevion/Pac-Man/badge.svg?branch=master
[badge-demo]: https://img.shields.io/github/deployments/Xevion/Pac-Man/github-pages?label=GitHub%20Pages
[badge-online-demo]: https://img.shields.io/badge/GitHub%20Pages-Demo-brightgreen
[badge-last-commit]: https://img.shields.io/github/last-commit/Xevion/Pac-Man
[build]: https://github.com/Xevion/Pac-Man/actions/workflows/build.yaml
[test]: https://github.com/Xevion/Pac-Man/actions/workflows/tests.yaml
[coverage]: https://coveralls.io/github/Xevion/Pac-Man?branch=master
[demo]: https://xevion.github.io/Pac-Man/
[commits]: https://github.com/Xevion/Pac-Man/commits/master
## Feature Targets
A faithful recreation of the classic Pac-Man arcade game written in Rust. This project aims to replicate the original game's mechanics, graphics, sound, and behavior as accurately as possible while providing modern development features like cross-platform compatibility and WebAssembly support.
- Near-perfect replication of logic, scoring, graphics, sound, and behaviors.
- Written in Rust, buildable on Windows, Linux, Mac and WebAssembly.
The game includes all the original features you'd expect from Pac-Man:
- [x] Classic maze navigation and dot collection
- [ ] Four ghosts with their unique AI behaviors (Blinky, Pinky, Inky, and Clyde)
- [ ] Power pellets that allow Pac-Man to eat ghosts
- [ ] Fruit bonuses that appear periodically
- [ ] Progressive difficulty with faster ghosts and shorter power pellet duration
- [x] Authentic sound effects and sprites
This cross-platform implementation is built with SDL2 for graphics, audio, and input handling. It can run on Windows, Linux, macOS, and in web browsers via WebAssembly.
## Why?
Just because. And because I wanted to learn more about Rust, inter-operability with C, and compiling to WebAssembly.
I was inspired by a certain code review video on YouTube; [SOME UNIQUE C++ CODE // Pacman Clone Code Review](https://www.youtube.com/watch?v=OKs_JewEeOo) by The Cherno.
For some reason, I was inspired to try and replicate it in Rust, and it was uniquely challenging.
I wanted to hit a log of goals and features, making it a 'perfect' project that I could be proud of.
- Near-perfect replication of logic, scoring, graphics, sound, and behaviors. No hacks, workarounds, or poor designs.
- Written in Rust, buildable on Windows, Linux, Mac and WebAssembly. Statically linked, no runtime dependencies.
- Performant, low memory, CPU and GPU usage.
- Online demo, playable in a browser.
- Automatic build system, with releases for Windows, Linux, and Mac & Web-Assembly.
- Completely automatic build system with releases for all platforms.
- Well documented, well-tested, and maintainable.
## Experimental Ideas
- Debug tooling
- Game state visualization
- Game speed controls + pausing
- Log tracing
- Performance details
## Experimental Ideas
- Customized Themes & Colors
- Color-blind friendly
- Perfected Ghost Algorithms
- More than 4 ghosts
- Custom Level Generation
- Multi-map tunnelling
- Online Scoreboard
- An online axum server with a simple database and OAuth2 authentication.
- Integrates with GitHub, Discord, and Google OAuth2 to acquire an email identifier & avatar.
- Avatars are optional for score submission and can be disabled, instead using a blank avatar.
- Avatars are downscaled to a low resolution pixellated image to maintain the 8-bit aesthetic.
- A custom name is used for the score submission, which is checked for potential abusive language.
- A max length of 14 characters, and a min length of 3 characters.
- Names are checked for potential abusive language via an external API.
- The client implementation should require zero configuration, environment variables, or special secrets.
- It simply defaults to the pacman server API, or can be overriden manually.
## Installation
## Build Notes
Besides SDL2, the following extensions are required: Image, Mixer, and TTF.
Since this project is still in progress, I'm only going to cover non-obvious build details. By reading the code, build scripts, and copying the online build workflows, you should be able to replicate the build process.
### Ubuntu
On Ubuntu, you can install the required packages with the following command:
```
sudo apt install libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev
```
### Windows
On Windows, installation requires either building from source (not covered), or downloading the pre-built binaries.
The latest releases can be found here:
- [SDL2](https://github.com/libsdl-org/SDL/releases/latest/)
- [SDL2_image](https://github.com/libsdl-org/SDL_image/releases/latest/)
- [SDL2_mixer](https://github.com/libsdl-org/SDL_mixer/releases/latest/)
- [SDL2_ttf](https://github.com/libsdl-org/SDL_ttf/releases/latest/)
Download each for your architecture, and locate the appropriately named DLL within. Move said DLL to root of this project.
## Building
To build the project, run the following command:
```
cargo build
```
During development, you can easily run the project with:
```
cargo run
cargo run -q # Quiet mode, no logging
cargo run --release # Release mode, optimized
```
- We use rustc 1.86.0 for the build, due to bulk-memory-opt related issues on wasm32-unknown-emscripten.
- Technically, we could probably use stable or even nightly on desktop targets, but using different versions for different targets is a pain, mainly because of clippy warnings changing between versions.
- Install `cargo-vcpkg` with `cargo install cargo-vcpkg`, then run `cargo vcpkg build` to build the requisite dependencies via vcpkg.
- For the WASM build, you need to have the Emscripten SDK cloned; you can do so with `git clone https://github.com/emscripten-core/emsdk.git`
- The first time you clone, you'll need to install the appropriate SDK version with `./emsdk install 3.1.43` and then activate it with `./emsdk activate 3.1.43`. On Windows, use `./emsdk/emsdk.ps1` instead.
- I'm still not sure _why_ 3.1.43 is required, but it is. Perhaps in the future I will attempt to use a more modern version.
- Occasionally, the build will fail due to dependencies failing to download. I even have a retry mechanism in the build workflow due to this.
- You can then activate the Emscripten SDK with `source ./emsdk/emsdk_env.sh` or `./emsdk/emsdk_env.ps1` or `./emsdk/emsdk_env.bat` depending on your OS/terminal.
- While using the `web.build.ts` is not technically required, it simplifies the build process and is very helpful.
- It is intended to be run with `bun`, which you can acquire at [bun.sh](https://bun.sh/)
- Tip: You can launch a fileserver with `python` or `caddy` to serve the files in the `dist` folder.
- `python3 -m http.server 8080 -d dist`
- `caddy file-server --root dist` (install with `[sudo apt|brew|choco] install caddy` or [a dozen other ways](https://caddyserver.com/docs/install))
- `web.build.ts` auto installs dependencies, but you may need to pass `-i` or `--install=fallback|force` to install missing packages. My guess is that if you have some packages installed, it won't install any missing ones. If you have no packages installed, it will install all of them.
- If you want to have TypeScript resolution for development, you can manually install the dependencies with `bun install` in the `assets/site` folder.

420
STORY.md Normal file
View File

@@ -0,0 +1,420 @@
# Story
This is living document that describes the story of the project, from inspiration to solution.
When a website is available, this document will help curate it's content.
## Inspiration
I initially got the idea for this project after finding a video about another Pac-Man clone on YouTube.
[![Code Review Thumbnail][code-review-thumbnail]][code-review-video]
This implementation was written in C++, used SDL2 for graphics, and was kinda weird - but it worked.
- I think it was weird because the way it linked files together is extremely non-standard.
Essentially, it was a single file that included all the other files. This is not how C++ projects are typically structured.
- This implementation was also extremely dependent on OOP; Rust has no real counterpart for OOP code, so writing my own implementation would be a challenge.
## Lifetimes
Rust's SDL2 implementation is a wrapper around the C library, so it's not as nice as the C++ implementation.
Additionally, lifetimes in this library are a bit weird, making them quite difficult to deal with.
I found a whole blog post complaining about this ([1][fighting-lifetimes-1], [2][fighting-lifetimes-2], [3][fighting-lifetimes-3]), so I'm not alone in this.
## Emscripten & RuggRogue
One of the targets for this project is to build a web-accessible version of the game. If you were watching at all during
the Rust hype, one of it's primary selling points was a growing community of Rust-based web applications, thanks to
WebAssembly.
The problem is that much of this work was done for pure-Rust applications - and SDL is C++.
This requires a C++ WebAssembly compiler such as Emscripten; and it's a pain to get working.
Luckily though, someone else has done this before, and they fully documented it - [RuggRouge][ruggrouge].
- Built with Rust
- Uses SDL2
- Compiling for WebAssembly with Emscripten
- Also compiles for Windows & Linux
This repository has been massively helpful in getting my WebAssembly builds working.
## Key Capturing Extensions in WASM Build
Some extensions I had installed were capturing keys.
The issue presented with some keys never being sent to the application.
To confirm, enter safe mode or switch to a different browser without said extensions.
If the issue disappears, it's because of an extension in your browser stealing keys in a way that is incompatible with the batshit insanity of Emscripten.
## A Long Break
After hitting a wall with an issue with Emscripten where the tab would freeze after switching tabs (making it into a background tab), I decided to take a break from the project. A couple months went by without anything going on.
## Revisiting
I decided to revisit the project because I didn't want to see this project die. It's actually a lot of fun, and has a very interesting stack, with a simple premise, and a lot of potential for expansion.
Unfortunately, the issue above still lingered. I did a lot of testing, and concluded that I needed to create a simple example with as much stripped away as possible. All I learned from this was that the freeze occurred the moment that the 'Hidden' event (for the Window) was fired. After that, the rendered would take 0 nanoseconds to render, and some script for Asyncify would keep spinning in the background.
I tried to ask around but didn't get anywhere, but one reply on my post gave me the idea to back away from Emscripten 1.39.20 (several years old at this point).
## Emscripten Callback Main Loop
I looked into as many examples online (not that many), and came across an Emscripten callback loop exposed in C. Some were basic and all over the place, some were advanced, but also imbibed extremely annoying static lifetime requirements.
- I tried my best to satisfy and work with these lifetimes, but it was a nightmare.
- Instead, I tried to simplify and move away from this annoying Emscripten callback loop, but simpler ones had issues, crashing with `invalid renderer` errors.
- This guy named Greg Buchholz apparently was the creator of this special Emscripten bindings with the static lifetimes, and it was done to solve this issue with `invalid renderer`.
- [GitHub](https://github.com/gregbuchholz), [Repository](https://github.com/gregbuchholz/RuSDLem), [StackOverflow](https://stackoverflow.com/questions/69748049/rust-sdl2-emscripten-and-invalid-renderer-panic), [Forum Post](https://users.rust-lang.org/t/sdl2-emscripten-asmjs-and-invalid-renderer-panic/66567/2)
With this in mind, it seemed like I was at a dead end AGAIN; either I had to deal with the static lifetimes (I am not that good at Rust), or I had to deal with Asyncify.
But this did help me narrow my search even more for a good example. I needed to find a repository with Rust, SDL2, Emscripten, and `TextureCreator`.
`TextureCreator` was key, as the static lifetimes issue was most encumbering when dealing with borrows and lifetimes of `TextureCreator` inside the `main` loop closure.
## Return to Asyncify
I found [one such repository](https://github.com/KyleMiles/Rust-SDL-Emscripten-Template/), and interestingly, it used `latest` Emscripten (not a specific target like 1.39.20), and was new enough (2 years old, but still new enough) to be relevant.
Even more interesting, it didn't use the `main` loop closure, but instead used Emscripten's _Asyncify_ feature to handle the main loop.
But, unlike my original project which called `std::thread::sleep` directly, it used bindings into Emscripten's functions like `emscripten_sleep`.
Even better, it had an example of script execution (JavaScript) bindings, which I could use to handle all sorts of things. I tested it out, and it worked.
## Instant::now() 32-bit Byte Cutoff
Unfortunately while trying to get basic FPS timings working, I got divide by zero errors when trying to calculate the time difference between two `Instant` times.
This was weird, and honestly, I'm confused as to why the 2-year old sample code 'worked' at the time, but not now.
After a bit of time, I noted that the `Instant` times were printing with only the whole seconds changing, and the nanoseconds were always 0.
```
Instant { tv_sec: 0, tv_nsec: 0 }
Instant { tv_sec: 1, tv_nsec: 0 }
Instant { tv_sec: 2, tv_nsec: 0 }
Instant { tv_sec: 3, tv_nsec: 0 }
Instant { tv_sec: 4, tv_nsec: 0 }
...
```
This was super weird, but I stumbled upon [an issue on GitHub](https://github.com/rust-lang/rust/issues/113852) that mentioned the exact situation I was in, as well as providing a patch solution (`emscripten_get_now`).
## VSync Gotcha
After getting the timing working, I noticed that the rendering was extremely slow. I was getting 60 FPS, but I wasn't sleeping at all.
Normally when rendering occurs, you want to sleep for the remaining time so that your game calculations can occur at a consistent rate (60 FPS for example).
If your rendering time is less than the sleep time, you can just sleep for the remaining time. But if your rendering time is greater than the sleep time, you encounter lag, the FPS starts to drop.
This was a confusing issue as I knew it couldn't be a coincidence that the rendering time was exactly ~16ms (60 FPS) every time.
After a little bit though, I found the `present_vsync` function in the SDL2 render initialization. This was causing the rendering to try and time the canvas present() to the monitor's refresh rate (60 FPS).
Maybe I could have skipped my custom timing and just used this, but I don't know if it would be platform-independent, what would happen on 120 FPS displays, etc.
## Emscripten v.s. SDL2-TTF
While working on the next extension of SDL2 for my test repository, SDL2-TTF had some pretty annoying issues. It would build fine, but it would raise a runtime error: `indirect call to null`.
Luckily, I had a recently updated repository to copy off of, and the working fix was to lower the EMSDK version to `3.1.43`.
[Source](https://github.com/aelred/tetris/blob/0ad88153db1ca7962b42277504c0f7f9f3c675a9/tetris-sdl/src/main.rs#L34)
```rust
static FONT_DATA: &[u8] = include_bytes!("../assets/TerminalVector.ttf");
#[cfg(not(target_os = "emscripten"))]
fn ttf_context() -> ttf::Sdl2TtfContext {
ttf::init().unwrap()
}
#[cfg(target_os = "emscripten")]
fn ttf_context() -> &'static ttf::Sdl2TtfContext {
// Deliberately leak so we get a static lifetime
Box::leak(Box::new(ttf::init().unwrap()))
}
const FONT_MULTIPLE: u16 = 9;
// Funny division is done here to round to nearest multiple of FONT_MULTIPLE
const FONT_SIZE: u16 = (WINDOW_HEIGHT / 32) as u16 / FONT_MULTIPLE * FONT_MULTIPLE;
fn main() {
...
let font_data = RWops::from_bytes(FONT_DATA).unwrap();
let font_size = max(FONT_SIZE, FONT_MULTIPLE);
let font = ttf_context
.load_font_from_rwops(font_data, font_size)
.unwrap();
}
```
I don't particularly understand why loading from memory is used, but it's a neat trick. I tested normal font loading afterwards, and it seems to be totally fine.
On to the Mixer extension, then.
## Mixer and GFX
Mixer was relatively easy, I don't remember anything special about it.
As it happens, neither was SDL GFX, except for me finding that getting it compiling on Windows would soon be difficult; `SDL2_gfx` is not currently being updated, nor is it managed by the SDL team. This meant that no releases of development libraries including DLLs or LIB files were going to be available.
When I added in GFX, I wanted to add some mouse interaction since that currently wasn't being done anywhere in the demo, but I also wanted the ability for the mouse to be hidden until used.
Detecting whether the mouse was focusing the window or not wasn't super easy, and I'm still not sure that it's working perfectly, but at the very least Emscripten seems to support what I'm trying to do. I should look into asynchronous Javascript callbacks, see what Emscripten supports.
## Styling with PostCSS + Tailwind
I'm big on using Tailwind, and while this project probably could have done without it, I didn't want to forego my favorite tool.
But I also didn't want to include some big framework on this, like Astro, so I looked for the smallest way to include Tailwind.
After fiddling and failing to find Hugo suitable, I stuck to plain HTML & the PostCSS method, which worked great. It's definitely not that fast for rapid development, but it works well enough.
The only thing I'm unsatisfied with is why `postcss-cli` wasn't working when executed from `pnpm`. It works just fine from `pnpx`, but it has to download and setup the whole package on _every single invocation_, which is super slow. And probably expensive, in the long run.
## Cross-platform Builds
With the next step of the demo project, I needed to get builds for every OS running, that's one down out of the four targets I'm gunning for.
Linux was the easiest, as usual, with `apt` providing access to all the development libraries of SDL & the associated extensions, including `SDL2_gfx`.
There's also no requirement for providing sidecar DLLs like Windows needs, so that worked well.
The hardest part was figuring out the most satisfying way to zip and load all the assets together, but luckily the artifact uploader provides it's own zip implementation; albeit I may need to modify it to add further system hinting (`.tar.gz` for Linux, `.dmg` for MacOS, `.zip` for Windows).
## SDL2 on Windows
SDL2 on Windows has to be one of the least fun development cycles; setting up the environment is pretty painful as there's almost no guides for Rust users to figure out each requirement. You'll learn fast, and this knowledge is hands on experience that will probably be applicable later on in C++ development, but I'm sure a fair number of Rust users like myself have no idea why a DLL or LIB file is necessary at all.
To be honest, I still don't.
Regardless, SDL2 needs a LIB file for compliation to be available in the root directory, and each extension has there own.
Once the EXE is compiled, the working directory needs to contain a DLL file for execution, too. Each extension has it's own as well.
This sounds easy, but acquiring these DLLs and LIB files is not easy. At the very least, the SDL-supported extensions have releases available containing
![SDL2 Mixer GitHub Release Files](https://i.xevion.dev/ShareX/2024/04/firefox_6zAmbsD97n.png)
So I got to creating a build step involving the download of each of these libraries. I'm no expert with `curl`, but I had it figured out eventually.
```yaml
- name: Download SDL2 Libraries
run: |
curl -L "https://github.com/libsdl-org/SDL/releases/download/release-${{ env.SDL2 }}/SDL2-devel-${{ env.SDL2 }}-VC.zip" -o "sdl2_devel.zip"
curl -L "https://github.com/libsdl-org/SDL_mixer/releases/download/release-${{ env.SDL2_MIXER }}/SDL2_mixer-devel-${{ env.SDL2_MIXER }}-VC.zip" -o "sdl2_mixer_devel.zip"
curl -L "https://github.com/libsdl-org/SDL_ttf/releases/download/release-${{ env.SDL2_TTF }}/SDL2_ttf-devel-${{ env.SDL2_TTF }}-VC.zip" -o "sdl2_ttf_devel.zip"
curl -L "https://github.com/libsdl-org/SDL_image/releases/download/release-${{ env.SDL2_IMAGE }}/SDL2_image-devel-${{ env.SDL2_IMAGE }}-VC.zip" -o "sdl2_image_devel.zip"
```
I did take a lot of care in making sure that versions were specified externally in different variables, which took a couple tries while I learned how interpolation works with GitHub Actions.
Additionally, I realized that `LIB` files were required for compliation after this, so I had to painfully fix all the files to use the `-devel-` version. Speifically the one with `-VC` appended.
I still do not know what VC means here. Perhaps it is related to `vcpkg` somehow.
The next step was to extract the files I needed from the `.zip`s, but that proved quite hard. I'm a lover of precision and using tools to the best of my knowledge, so I wanted to finely take just the DLL and ZIP I needed from these archives, and nothing else.
While I was able to get working commands to do this on Linux, finely finding the exact DLL and placing it in `pwd`, I was not able to replicate it on the Windows-imaged GitHub Runner;
When specifying the `-o` flag meaning 'output directory here' like `-o./tmp` (yes, there is no space in between), it would always error with `Too short switch: -o`. I was unable to find meaningful discussions on Google.
My Linux machine did not complain, and I wasn't yet ready to switch OSes for an error like this, so I just extracted everything and then `mv`'d the items into `pwd`.
I knew what lay ahead with `SDL2_gfx`, so I tested whether the compilation error changed, and luckily, it was only erroring on the missing `SDL2_gfx.lib` at this point.
While reading discussions online, I came across [a reddit post](https://www.reddit.com/r/rust_gamedev/comments/am84q9/using_sdl2_gfx_on_windows/efk6uwq/) talking about `vcpkg`. I'd heard of it, but never used the program before. It seemed like it could provide `SDL2_gfx` for me without hassle.
And that was partly true.
The primary 'boon' of `vcpkg` here was that it setup and compiled `SDL2_gfx` without the hassle of messing with the compiler, options, or most importantly: dependencies.
I didn't know it at the time, but `SDL2_gfx` depended on `SDL2` directly, and so I'd have to setup and compile both projects, if I was hoping to do this 'manually'.
## VCPKG for SDL2_GFX
I tried to use the GitHub-provided environment variables relating to VCPKG's installation location, but nothing really worked here. I was on the correcti mage (`windows-latest` for Windows 2022 Enterprise on GitHub's Runner Images), but nothing seemed to work.
[This comment](https://github.com/actions/runner-images/issues/6376#issuecomment-1781269302) seemed to describe the exact same experience I was happening, several months ago.
Alas, I simply tried `C:\vcpkg\` and it worked, providing me the ability to install `SDL2_gfx`.
As it were though, the hard part wasn't going to be compiling, but locating the DLLs and LIB files for movement. No matter where I looked online or in the logs, nothing was obvious about the location of my files.
In retrospect, a recursive `Get-ChildItem` looking for `DLL` or `LIB` files probably would've worked well, but.. yeah...
After a couple attempts with various test commits, I couldn't find it, and just switched to Windows to install and compile it myself, so I could locate the file manually.
> Note: VCPKG is annoying to install, the executable provided by Visual Studio Community does not permit classic-mode usage, so you'll still need to clone and bootstrap VCPKG (instructions in the repository README).
As it happens, they were placed in
- `$VCPKG_ROOT\packages\sdl2-gfx_x64-windows-release\bin\SDL2_gfx.dll` and
- `$VCPKG_ROOT\packages\sdl2-gfx_x64-windows-release\lib\SDL2_gfx.lib` respectively.
This brings me to one issue, and one fix; while compiling you're required to specify that the build is for 64 bit systems manually, on each invocation of VCPKG (while in classic mode, which I am).
On top of that, they'll be built in debug mode (with extra symbols and such) by default, which I am not interested in.
To get the x64 Release build of a package, append `:x64-windows-release` to it, as in `sdl2-gfx:x64-windows-release` for `sdl2-gfx`.
After getting this sorted, I struggled a little bit in using the `mv` (`Move-Item`) command in Powershell, as I battled with the comma delimited files when moving multiple files to a given destination. Dumb.
This is also the point at which I renamed the executable from `pacman` to `spiritus` to differentiate the two projects. The name is just my play on the word 'sprite'.
## Console Window Hiding
When launching the demo app, I saw a console window pop up, even though I launched it from the File Explorer; this is not the behavior I was interested in.
I believe that apps launched from File Explorer shouldn't have a console window available unless...
- It's a CLI app by nature, and it uses the Console Window.
- It has a specific debugging flag passed into it, perhaps by a Shortcut file.
- The console window is required for the nature of the app, or it is the preferred method of log inspection.
- It's a debug build.
But if it's launched from the console, then it should either
- Detach and relinquish control of the console back to the user.
- Use the console actively in it's logging.
Most programs I know and use follow this general consensus. Naturally, mine must too.
But, when searching for a solution online, it seemed what I want doesn't really exist; I implemented the closest approximation.
If `stdout` is detected to be a `tty` (an active console), the console window won't be hidden. Otherwise, it will be hidden.
Unfortunately, this results in the millisecond flash of a black console window appearing.
## Updating Deprecated Actions
As it were, most of the actions I were using were deprecated in some way. It didn't feel like I was using super old actions, but I guess I was. Luckily, most of them were simply just updating the version (`2` or `3` to `4` or `5`).
`actions-rs@toolchain` was different though, and was officially deprecated, the GitHub repository archived. Couldn't find a good reason why, but the repository was untouched in 4 years, so maybe that's why...
I found `dtolnay@rust-toolchain` and switched, it more or less was perfect with no differences. I think it's sorta neat that the Rust version is specified using the version of the action. I'd be worried though of a changing feature set across different action versions...
I guess a well designed GitHub Action shouldn't change much, including a Rust toolchain action.
## Artifact Naming
Perhaps it's super unnecessary and won't be appreciated, but I wanted the artifact files produced by my script to have semantic meaning in it's version and target.
For each OS, I extracted the targets (`x86_64-unknown-linux-gnu`, `x86_64-apple-darwin`...) into an environment variable scoped at the job level (love that).
I looked into ways to get the package version, but nothing obvious jumped out at me. I did come across `toml-cli` though, a Rust-based CLI program analogous to `jq` (for JSON).
It worked great, but it was sorta slow; compiling `toml-cli` added an extra 20-60 seconds for each job. I'd heard of a Rust project to speed up builds by providing prebuilt executables though; it's called `binstall`.
Even cooler, it was had an action available to easily add it to my build script, and so I had `toml-cli` installing and available 10x faster!
![GitHub Actions output showing 2 cargo binstall process taking 4 seconds total](https://i.xevion.dev/ShareX/2024/04/firefox_5sOEDM7zkc.png)
Perhaps I could use some special bash commands to acquire the package version, but it'd be a lot of work and maintenance to get it working in both bash and Powershell, maintaining it across four jobs.
This is both cool, fast, and easy!
![GitHub Artifacts with Sizes Listed](https://i.xevion.dev/ShareX/2024/04/firefox_jDI8MDwWRc.png)
I was thinking of a github-pages artifact name that aligns with the others, but I think that'd be stupid AND overkill.
Perhaps at the least I'll look into a 32-bit build for Windows, just for demonstration purposes.
## My Return to Pac-Man
It's been 15 months since I last touched the demo codebase, and much longer since I've touched the core Pac-Man project, and I got inspired to look back into it recently. I'm finally touching up on the story document, so if this reads a bit disjointed, that's why.
- I switched the dependency linking to use the internal statically-linked `vcpkg` feature, which is a lot easier to maintain. It's not perfect, but it's much better than the manual downloads and the dynamically linked `.dll` files I was doing before. With caching, it also tends to be far quicker.
- I switched all of the commits to use conventional commit messages, which is easier to read and understand.
- I integrated the demo project's emscripten workflow, updated sdl2 and started poking around in the project. I got into adding fonts, adding a reset button, a debug mode, score tracking, pellet consumption, etc.
- I spent a lot of time working on the audio timing, getting it to work flawlessly and compare really well with the original Pac-Man; the sound is incredibly important to the game, so I wanted to get it right.
## Pathfinding and Tunnelling
Pathfinding was very easy to get working, although tunnelling was a bit more difficult, and unfortunately I never got it working with the way I was doing things at the time. A lot of issues were happening with trying to get the transition between the tunnels to work, I could only get Pac-Man to teleport from one tunnel to the other, but moving smoothly between them was nigh impossible.
I did however get pathfinding to work between the tunnels, which was very satisfying to see using the debug visuals.
I ended up using the `pathfinding` crate and it was a breeze to use.
## Atlas Tiles
When I was looking around for Pac-Man sprites, I kept coming across atlas images, and I had been noticing for some time how my sprites were not correctly sized, and some of them just didn't match the original Pac-Man. I had been spending a lot of time making this Pac-Man project as close to the original as possible, and I felt like if I didn't use the original sprites, I wasn't doing it justice.
This had me thinking about how asset loading was a real pain in this project, and how I wanted to look into atlas tiles.
The arguments for copying between a texture and a canvas/surface/texture were very obviously rigged to allow for this, given that you had to specify the source `Rect`, meaning you could target a specific area of the texture. Such as tiles on an atlas image.
It didn't take long for me to get it working, I chose an existing crate called `clutterd` which provided a CLI for building atlas images with an metadata file describing the positions and sizes of the tiles.
Doing so required a full re-work of the animation and texture system, and I ended up making a breakthrough on how I managed lifetimes: lifetime annotations were plaguing the codebase, literally everywhere, and it was super annoying to keep writing and dealing with them.
So, I ended up using `unsafe` to forcibly cast the lifetimes to `'static`, which was a bit of a gamble, but given that they essentially behave as `'static` in practice, there wasn't much risk as I see it. I might re-look into my understanding of lifetimes and this in the future, but for the time being, it's a good solution that makes the codebase far easier to work with.
## Cross-platform Builds
Since the original `rust-sdl2-emscripten` demo project had cross-platform builds, I was ready to get it working for this project. For the most part, it wasn't hard, things tended to click into place, but unfortunately, the `emscripten` os target and somehow, the `linux` os target were both failing.
I'm still not sure what exactly causes it, but `emscripten` strongly prefers to be built on 1.86 (1.88 does not work, 1.87 might though).
Changing the toolchain to 1.86 fixed the issue when it was failing.
It did turn out though, that despite me getting the `emscripten` target building, it did not mean the application was functioning properly.
- Upon launch, it was immediately crashing due to issues with the audio subsystem; this was fixed with a simple increase to the audio buffer chunksize, apparently it has a minimum size of 256.
- Then, it was failing due to issues with the main loop, referencing the `ASYNCIFY_STACK_SIZE` variable in `.cargo/config.toml`, asking for it to be increased. I really didn't like the idea of increasing it for whatever reason, so I ended up looking into the `emscripten_main_loop` method of looping again, but nothing worked all that well, just like the last time I tried. So I increased the variable, doubling it from the default of 4096 to 8192. Things immediately worked, and the browser build was working.
Linux however was a far more annoying task, as it was failing to due the `cargo-vcpkg` build step (which built the SDL2 libraries necessary for static linking and building the project). It was hard to pin down at first, but packages seemed to be failing due to system dependencies not being available, so after adding a couple `apt` packages to the steps, things started to work.
Eventually though, it kept failing at the `sdl2` package, which was failing to build due to the `libpng` package not being able to find a bunch of symbols related to `zlib`. Almost nothing was written about this online, except for one issue on GitHub which hadn't been updated in 2 years.
I won't lie, Gemini helped me out here, suggesting adding `"-C", "link-arg=-lz",` to the `rustflags` section of `.cargo/config.toml`.
It seems like it moved the `zlib` library to the front of the link order, and things started magically working both locally and on the GitHub Actions runner.
I also added an ARM64 build for MacOS, which worked without any issues. Surprisingly, MacOS is the only platform that I've been able to get working without any issues. At least, I hope it's working; I don't really have a way to test it myself.
## Caching
I spent a bit of time after this improving the build process to take advantage of caching so that most builds would fly. The `cargo-vcpkg` was by far the most expensive step, and it unfortunately, despite being in the `target` directory (which is supposed to be cached by the `Swatinem/rust-cache@v2` action), was not being cached.
I played with the parameters for a bit before giving up and just manually adding a cache step to the workflow. It's expensive, uploading 300MB of artifact data to GitHub, but it works well, and I'm really doubtful it will change that much.
I also ended up improving the build process to use `cargo metadata` to get the package version, which means I could drop the `toml-cli` dependency and just use the `cargo` command + `jq` (which is already installed on the runner).
## Atlas Text
At some point, I wanted to use the original text from the game, so I created a text texture type for rendering text using the existing sprite atlas, which means I wasn't using the `ttf` feature at all. I'm stil unsure whether or not I'll use it, I might keep it because it seems like more hassle to remove it at this point. Perhaps I'll still use normal ttf fonts like Arial for debug-related displays, or maybe I'll create/use a custom font.
## Node Graph Positioning
After getting all this working, I was really excited to finally get closer to actually finishing the project. I felt like I had finally started checking a bunch of important boxes, so I started actually working on the 'ghost house' part of Pac-Man.
The ghost house is very different from the rest of the game as it doesn't render the tiles in the same way, on a static grid.
It's actually offset by 8 pixels, and the ghosts exit the house between two tiles, requiring a lot more customization and flexibility in my
rendering system.
I spent a fair bit of time trying to implement hacks into this to get it working, but I eventually gave up after realizing that there's no solution here using my existing system.
I remembered how I was having trouble with the transition states between the two tunnels (still not resolved), and it felt quite similar to my current situation; the inflexibility of my integer grid system was the main cause of the issue.
I started thinking of different ways to approach movement, and realized that the Pac-Man and Ghost's movement is quite limited and simple like railroad tracks, like nodes on a graph. Both problems could be solved by switching to a graph - most of the maze would look like a grid, each cell connected to eachother.
By representing one's position as a distance from the start node towards an end node, I could achieve smooth linear movement between nodes
that, for the most part, appears to use a cell-based grid, which also allowing more customized offsets.
The bigger downside was that I had to toss out almost all the existing code for the game, only keeping the audio and most of the texturing system, as well as the initialization code. It also meant I was using floating points for a lot of internal state, which is not ideal.
This ended up being okay though, as I was able to clean up a lot of gross code, and the system ended up being easier to work with by comparison.
[code-review-video]: https://www.youtube.com/watch?v=OKs_JewEeOo
[code-review-thumbnail]: https://img.youtube.com/vi/OKs_JewEeOo/hqdefault.jpg
[fighting-lifetimes-1]: https://devcry.heiho.net/html/2022/20220709-rust-and-sdl2-fighting-with-lifetimes.html
[fighting-lifetimes-2]: https://devcry.heiho.net/html/2022/20220716-rust-and-sdl2-fighting-with-lifetimes-2.html
[fighting-lifetimes-3]: https://devcry.heiho.net/html/2022/20220724-rust-and-sdl2-fighting-with-lifetimes-3.html
[ruggrogue]: https://tung.github.io/ruggrogue/

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 528 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 394 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 228 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 370 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 B

1061
assets/game/atlas.json Normal file
View File

File diff suppressed because it is too large Load Diff

BIN
assets/game/atlas.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
assets/game/konami.ttf Normal file
View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

View File

Binary file not shown.

BIN
assets/site/favicon.ico Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

96
assets/site/index.html Normal file
View File

@@ -0,0 +1,96 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Pac-Man in Rust</title>
<link rel="stylesheet" href="build.css" />
<style>
/* Minimal fallback to prevent white flash and canvas pop-in before CSS loads */
html,
body {
background: #000;
color: #facc15;
margin: 0;
text-align: center;
}
#canvas {
display: block;
margin: 1.5rem auto;
background: #000;
}
</style>
</head>
<body class="bg-black text-yellow-400 text-center min-h-screen">
<a
href="https://github.com/Xevion/Pac-Man"
class="absolute top-0 right-0"
aria-label="View source on GitHub"
>
<svg
width="80"
height="80"
viewBox="0 0 250 250"
class="fill-yellow-400 [&>.octo-arm,.octo-body]:fill-black"
aria-hidden="true"
>
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
<path
d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
class="octo-arm"
></path>
<path
d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z"
class="octo-body"
></path>
</svg>
</a>
<div class="min-h-screen flex flex-col">
<main class="flex-1 flex items-center justify-center px-4">
<div class="w-full max-w-5xl">
<canvas
id="canvas"
oncontextmenu="event.preventDefault()"
class="block w-full h-full max-h-[90vh] aspect-square"
></canvas>
<div
class="mt-8 flex flex-wrap gap-3 justify-center items-center text-sm"
>
<span class="code">&larr; &uarr; &rarr; &darr;</span>
<span class="opacity-70">Move</span>
<span class="mx-2 opacity-30">|</span>
<span class="code">Space</span>
<span class="opacity-70">Toggle Debug</span>
<span class="mx-2 opacity-30">|</span>
<span class="code">P</span>
<span class="opacity-70">Pause / Unpause</span>
<span class="mx-2 opacity-30">|</span>
<span class="code">M</span>
<span class="opacity-70">Mute / Unmute</span>
</div>
</div>
</main>
</div>
<script type="text/javascript">
const canvas = document.getElementById("canvas");
var Module = {
canvas: canvas,
preRun: [
() => {
[...canvas.classList]
.filter((className) => className.includes("shadow-"))
.forEach((className) => canvas.classList.remove(className));
},
],
};
</script>
<script type="text/javascript" src="pacman.js"></script>
</body>
</html>

28
assets/site/styles.css Normal file
View File

@@ -0,0 +1,28 @@
@import "tailwindcss";
@font-face {
font-family: "TerminalVector";
src: url("TerminalVector.ttf");
font-weight: normal;
font-style: normal;
font-display: swap;
}
/* Key badge styling */
.code {
@apply px-3 py-1 rounded-md font-mono text-[0.9em] lowercase inline-block align-middle;
background: rgba(250, 204, 21, 0.08); /* yellow-400 at low opacity */
border: 1px solid rgba(250, 204, 21, 0.25);
color: #fde68a; /* lighter yellow for readability */
font-family: "TerminalVector", ui-monospace, Consolas, "Courier New",
monospace;
}
/* Title styling */
.arcade-title {
font-family: "TerminalVector", ui-monospace, Consolas, "Courier New",
monospace;
letter-spacing: 0.08em;
text-shadow: 0 0 18px rgba(250, 204, 21, 0.15),
0 0 2px rgba(255, 255, 255, 0.25);
}

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

Some files were not shown because too many files have changed in this diff Show More