diff --git a/Cargo.lock b/Cargo.lock index 7faa84e..d66b50f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,12 +17,202 @@ version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "assert_type_match" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f548ad2c4031f2902e3edc1f29c29e835829437de49562d8eb5dc5584d3a1043" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-executor" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +dependencies = [ + "portable-atomic", +] + [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "bevy_ecs" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c2bf6521aae57a0ec3487c4bfb59e36c4a378e834b626a4bea6a885af2fdfe7" +dependencies = [ + "arrayvec", + "bevy_ecs_macros", + "bevy_platform", + "bevy_ptr", + "bevy_reflect", + "bevy_tasks", + "bevy_utils", + "bitflags 2.9.1", + "bumpalo", + "concurrent-queue", + "derive_more", + "disqualified", + "fixedbitset", + "indexmap", + "log", + "nonmax", + "serde", + "smallvec", + "thiserror", + "variadics_please", +] + +[[package]] +name = "bevy_ecs_macros" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38748d6f3339175c582d751f410fb60a93baf2286c3deb7efebb0878dce7f413" +dependencies = [ + "bevy_macro_utils", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bevy_macro_utils" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052eeebcb8e7e072beea5031b227d9a290f8a7fbbb947573ab6ec81df0fb94be" +dependencies = [ + "parking_lot", + "proc-macro2", + "quote", + "syn", + "toml_edit", +] + +[[package]] +name = "bevy_platform" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7573dc824a1b08b4c93fdbe421c53e1e8188e9ca1dd74a414455fe571facb47" +dependencies = [ + "cfg-if", + "critical-section", + "foldhash", + "hashbrown", + "portable-atomic", + "portable-atomic-util", + "serde", + "spin", +] + +[[package]] +name = "bevy_ptr" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df7370d0e46b60e071917711d0860721f5347bc958bf325975ae6913a5dfcf01" + +[[package]] +name = "bevy_reflect" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daeb91a63a1a4df00aa58da8cc4ddbd4b9f16ab8bb647c5553eb156ce36fa8c2" +dependencies = [ + "assert_type_match", + "bevy_platform", + "bevy_ptr", + "bevy_reflect_derive", + "bevy_utils", + "derive_more", + "disqualified", + "downcast-rs", + "erased-serde", + "foldhash", + "glam 0.29.3", + "serde", + "smallvec", + "smol_str", + "thiserror", + "uuid", + "variadics_please", + "wgpu-types", +] + +[[package]] +name = "bevy_reflect_derive" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ddadc55fe16b45faaa54ab2f9cb00548013c74812e8b018aa172387103cce6" +dependencies = [ + "bevy_macro_utils", + "proc-macro2", + "quote", + "syn", + "uuid", +] + +[[package]] +name = "bevy_tasks" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b674242641cab680688fc3b850243b351c1af49d4f3417a576debd6cca8dcf5" +dependencies = [ + "async-executor", + "async-task", + "atomic-waker", + "bevy_platform", + "cfg-if", + "crossbeam-queue", + "derive_more", + "futures-lite", + "heapless", +] + +[[package]] +name = "bevy_utils" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f7a8905a125d2017e8561beefb7f2f5e67e93ff6324f072ad87c5fd6ec3b99" +dependencies = [ + "bevy_platform", + "thread_local", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -34,6 +224,21 @@ name = "bitflags" version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +dependencies = [ + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "c_vec" @@ -47,6 +252,37 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", + "portable-atomic", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "deprecate-until" version = "0.1.1" @@ -59,12 +295,98 @@ dependencies = [ "syn", ] +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + +[[package]] +name = "disqualified" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9c272297e804878a2a4b707cfcfc6d2328b5bb936944613b4fdf2b9269afdfd" + +[[package]] +name = "downcast-rs" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea8a8b81cacc08888170eef4d13b775126db426d0b348bee9d18c2c1eaf123cf" + [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "erased-serde" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7" +dependencies = [ + "serde", + "typeid", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "getrandom" version = "0.3.3" @@ -77,17 +399,50 @@ dependencies = [ "wasi", ] +[[package]] +name = "glam" +version = "0.29.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee" +dependencies = [ + "serde", +] + [[package]] name = "glam" version = "0.30.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2d1aab06663bdce00d6ca5e5ed586ec8d18033a771906c993a1e3755b368d85" +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + [[package]] name = "hashbrown" version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +dependencies = [ + "equivalent", + "serde", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "portable-atomic", + "stable_deref_trait", +] [[package]] name = "heck" @@ -120,6 +475,16 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -132,6 +497,16 @@ version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.20" @@ -153,6 +528,12 @@ version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +[[package]] +name = "nonmax" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51" + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -189,7 +570,8 @@ name = "pacman" version = "0.2.0" dependencies = [ "anyhow", - "glam", + "bevy_ecs", + "glam 0.30.5", "lazy_static", "libc", "once_cell", @@ -210,6 +592,35 @@ dependencies = [ "winapi", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + [[package]] name = "pathfinding" version = "4.14.0" @@ -272,6 +683,21 @@ version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "proc-macro2" version = "1.0.95" @@ -329,6 +755,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "redox_syscall" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +dependencies = [ + "bitflags 2.9.1", +] + [[package]] name = "regex" version = "1.11.1" @@ -379,12 +814,24 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + [[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "sdl2" version = "0.38.0" @@ -463,12 +910,36 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "portable-atomic", +] + [[package]] name = "spin_sleep" version = "1.3.2" @@ -478,6 +949,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "strum" version = "0.27.2" @@ -537,6 +1014,23 @@ dependencies = [ "once_cell", ] +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + [[package]] name = "tracing" version = "0.1.41" @@ -608,18 +1102,53 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + [[package]] name = "unicode-ident" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "uuid" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be" +dependencies = [ + "getrandom", + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "valuable" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "variadics_please" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b6d82be61465f97d42bd1d15bf20f3b0a3a0905018f38f9d6f6962055b0b5c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "vcpkg" version = "0.2.15" @@ -641,6 +1170,87 @@ dependencies = [ "wit-bindgen-rt", ] +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wgpu-types" +version = "24.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50ac044c0e76c03a0378e7786ac505d010a873665e2d51383dcff8dd227dc69c" +dependencies = [ + "bitflags 2.9.1", + "js-sys", + "log", + "serde", + "web-sys", +] + [[package]] name = "winapi" version = "0.3.9" @@ -669,7 +1279,23 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets", + "windows-targets 0.53.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -678,64 +1304,121 @@ version = "0.53.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + [[package]] name = "windows_aarch64_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + [[package]] name = "windows_aarch64_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + [[package]] name = "windows_i686_gnu" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + [[package]] name = "windows_i686_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + [[package]] name = "windows_i686_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + [[package]] name = "windows_x86_64_gnu" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + [[package]] name = "windows_x86_64_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "windows_x86_64_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +[[package]] +name = "winnow" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +dependencies = [ + "memchr", +] + [[package]] name = "wit-bindgen-rt" version = "0.39.0" diff --git a/Cargo.toml b/Cargo.toml index 35d3fed..bb4b470 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ smallvec = "1.15.1" strum = "0.27.2" strum_macros = "0.27.2" phf = { version = "0.11", features = ["macros"] } +bevy_ecs = "0.16.1" [profile.release] lto = true diff --git a/src/app.rs b/src/app.rs index 0f03791..826e1bf 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,28 +1,20 @@ use std::time::{Duration, Instant}; use glam::Vec2; -use sdl2::event::{Event, WindowEvent}; use sdl2::render::{Canvas, ScaleMode, Texture, TextureCreator}; use sdl2::ttf::Sdl2TtfContext; use sdl2::video::{Window, WindowContext}; use sdl2::{AudioSubsystem, EventPump, Sdl, VideoSubsystem}; -use tracing::{error, info, warn}; +use tracing::{error, warn}; use crate::error::{GameError, GameResult}; use crate::constants::{CANVAS_SIZE, LOOP_TIME, SCALE}; use crate::game::Game; -use crate::input::commands::GameCommand; -use crate::input::InputSystem; use crate::platform::get_platform; pub struct App { - game: Game, - input_system: InputSystem, - canvas: Canvas, - backbuffer: Texture<'static>, - event_pump: &'static mut EventPump, - + pub game: Game, last_tick: Instant, focused: bool, cursor_pos: Vec2, @@ -54,39 +46,32 @@ impl App { .build() .map_err(|e| GameError::Sdl(e.to_string()))?; - let mut canvas = window - .into_canvas() - .accelerated() - .present_vsync() - .build() - .map_err(|e| GameError::Sdl(e.to_string()))?; + let mut canvas = Box::leak(Box::new( + window + .into_canvas() + .accelerated() + .present_vsync() + .build() + .map_err(|e| GameError::Sdl(e.to_string()))?, + )); canvas .set_logical_size(CANVAS_SIZE.x, CANVAS_SIZE.y) .map_err(|e| GameError::Sdl(e.to_string()))?; - let texture_creator: &'static TextureCreator = Box::leak(Box::new(canvas.texture_creator())); + let texture_creator: &'static mut TextureCreator = Box::leak(Box::new(canvas.texture_creator())); - let mut game = Game::new(texture_creator)?; + let game = Game::new(canvas, texture_creator, event_pump)?; // game.audio.set_mute(cfg!(debug_assertions)); - let mut backbuffer = texture_creator - .create_texture_target(None, CANVAS_SIZE.x, CANVAS_SIZE.y) - .map_err(|e| GameError::Sdl(e.to_string()))?; - backbuffer.set_scale_mode(ScaleMode::Nearest); - // Initial draw - game.draw(&mut canvas, &mut backbuffer) - .map_err(|e| GameError::Sdl(e.to_string()))?; - game.present_backbuffer(&mut canvas, &backbuffer, glam::Vec2::ZERO) - .map_err(|e| GameError::Sdl(e.to_string()))?; + // game.draw(&mut canvas, &mut backbuffer) + // .map_err(|e| GameError::Sdl(e.to_string()))?; + // game.present_backbuffer(&mut canvas, &backbuffer, glam::Vec2::ZERO) + // .map_err(|e| GameError::Sdl(e.to_string()))?; Ok(App { game, - input_system: InputSystem::new(), - canvas, - event_pump, - backbuffer, focused: true, last_tick: Instant::now(), cursor_pos: Vec2::ZERO, @@ -97,34 +82,30 @@ impl App { { let start = Instant::now(); - for event in self.event_pump.poll_iter() { - match event { - Event::Window { win_event, .. } => match win_event { - WindowEvent::FocusGained => { - self.focused = true; - } - WindowEvent::FocusLost => { - self.focused = false; - } - _ => {} - }, - Event::MouseMotion { x, y, .. } => { - // Convert window coordinates to logical coordinates - self.cursor_pos = Vec2::new(x as f32, y as f32); - } - _ => {} - } - - if let Some(command) = self.input_system.handle_event(&event) { - match command { - GameCommand::Exit => { - info!("Exit requested. Exiting..."); - return false; - } - _ => self.game.post_event(command.into()), - } - } - } + // for event in self + // .game + // .world + // .get_non_send_resource_mut::<&'static mut EventPump>() + // .unwrap() + // .poll_iter() + // { + // match event { + // Event::Window { win_event, .. } => match win_event { + // WindowEvent::FocusGained => { + // self.focused = true; + // } + // WindowEvent::FocusLost => { + // self.focused = false; + // } + // _ => {} + // }, + // Event::MouseMotion { x, y, .. } => { + // // Convert window coordinates to logical coordinates + // self.cursor_pos = Vec2::new(x as f32, y as f32); + // } + // _ => {} + // } + // } let dt = self.last_tick.elapsed().as_secs_f32(); self.last_tick = Instant::now(); @@ -135,15 +116,9 @@ impl App { return false; } - if let Err(e) = self.game.draw(&mut self.canvas, &mut self.backbuffer) { - error!("Failed to draw game: {}", e); - } - if let Err(e) = self - .game - .present_backbuffer(&mut self.canvas, &self.backbuffer, self.cursor_pos) - { - error!("Failed to present backbuffer: {}", e); - } + // if let Err(e) = self.game.draw(&mut self.canvas, &mut self.backbuffer) { + // error!("Failed to draw game: {}", e); + // } if start.elapsed() < LOOP_TIME { let time = LOOP_TIME.saturating_sub(start.elapsed()); diff --git a/src/ecs/mod.rs b/src/ecs/mod.rs new file mode 100644 index 0000000..90f1d92 --- /dev/null +++ b/src/ecs/mod.rs @@ -0,0 +1,134 @@ +//! The Entity-Component-System (ECS) module. +//! +//! This module contains all the ECS-related logic, including components, systems, +//! and resources. + +use bevy_ecs::{bundle::Bundle, component::Component, resource::Resource}; +use glam::Vec2; + +use crate::{ + entity::{direction::Direction, graph::Graph, traversal}, + error::{EntityError, GameResult}, + texture::{directional::DirectionalAnimatedTexture, sprite::Sprite}, +}; + +/// A tag component for entities that are controlled by the player. +#[derive(Default, Component)] +pub struct PlayerControlled; + +/// A component for entities that have a sprite, with a layer for ordering. +#[derive(Component)] +pub struct Renderable { + pub sprite: Sprite, + pub layer: u8, +} + +/// A unique identifier for a node, represented by its index in the graph's storage. +pub type NodeId = usize; + +/// Represents the current position of an entity traversing the graph. +/// +/// This enum allows for precise tracking of whether an entity is exactly at a node +/// or moving along an edge between two nodes. +#[derive(Component, Debug, Copy, Clone, PartialEq)] +pub enum Position { + /// The traverser is located exactly at a node. + AtNode(NodeId), + /// The traverser is on an edge between two nodes. + BetweenNodes { + from: NodeId, + to: NodeId, + /// The floating-point distance traversed along the edge from the `from` node. + traversed: f32, + }, +} + +impl Position { + /// Calculates the current pixel position in the game world. + /// + /// Converts the graph position to screen coordinates, accounting for + /// the board offset and centering the sprite. + pub fn get_pixel_pos(&self, graph: &Graph) -> GameResult { + let pos = match self { + Position::AtNode(node_id) => { + let node = graph.get_node(*node_id).ok_or(EntityError::NodeNotFound(*node_id))?; + node.position + } + Position::BetweenNodes { from, to, traversed } => { + let from_node = graph.get_node(*from).ok_or(EntityError::NodeNotFound(*from))?; + let to_node = graph.get_node(*to).ok_or(EntityError::NodeNotFound(*to))?; + let edge = graph + .find_edge(*from, *to) + .ok_or(EntityError::EdgeNotFound { from: *from, to: *to })?; + from_node.position + (to_node.position - from_node.position) * (traversed / edge.distance) + } + }; + + Ok(Vec2::new( + pos.x + crate::constants::BOARD_PIXEL_OFFSET.x as f32, + pos.y + crate::constants::BOARD_PIXEL_OFFSET.y as f32, + )) + } +} + +impl Default for Position { + fn default() -> Self { + Position::AtNode(0) + } +} + +#[allow(dead_code)] +impl Position { + /// Returns `true` if the position is exactly at a node. + pub fn is_at_node(&self) -> bool { + matches!(self, Position::AtNode(_)) + } + + /// Returns the `NodeId` of the current or most recently departed node. + #[allow(clippy::wrong_self_convention)] + pub fn from_node_id(&self) -> NodeId { + match self { + Position::AtNode(id) => *id, + Position::BetweenNodes { from, .. } => *from, + } + } + + /// Returns the `NodeId` of the destination node, if currently on an edge. + #[allow(clippy::wrong_self_convention)] + pub fn to_node_id(&self) -> Option { + match self { + Position::AtNode(_) => None, + Position::BetweenNodes { to, .. } => Some(*to), + } + } + + /// Returns `true` if the traverser is stopped at a node. + pub fn is_stopped(&self) -> bool { + matches!(self, Position::AtNode(_)) + } +} + +/// A component for entities that have a velocity, with a direction and speed. +#[derive(Default, Component)] +pub struct Velocity { + pub direction: Direction, + pub speed: f32, +} + +#[derive(Bundle)] +pub struct PlayerBundle { + pub player: PlayerControlled, + pub position: Position, + pub velocity: Velocity, + pub sprite: Renderable, +} + +#[derive(Resource)] +pub struct GlobalState { + pub exit: bool, +} + +#[derive(Resource)] +pub struct DeltaTime(pub f32); + +pub mod render; diff --git a/src/ecs/render.rs b/src/ecs/render.rs new file mode 100644 index 0000000..a3d3e2d --- /dev/null +++ b/src/ecs/render.rs @@ -0,0 +1,69 @@ +use crate::ecs::{render, Position, Renderable}; +use crate::entity::graph::Graph; +use crate::error::{EntityError, GameError, TextureError}; +use crate::map::builder::Map; +use crate::texture::sprite::{Sprite, SpriteAtlas}; +use bevy_ecs::entity::Entity; +use bevy_ecs::event::EventWriter; +use bevy_ecs::query::With; +use bevy_ecs::system::{NonSendMut, Query, Res}; +use sdl2::render::{Canvas, Texture}; +use sdl2::video::Window; + +pub struct MapTextureResource(pub Texture<'static>); +pub struct BackbufferResource(pub Texture<'static>); + +pub fn render_system( + mut canvas: NonSendMut<&mut Canvas>, + map_texture: NonSendMut, + mut backbuffer: NonSendMut, + mut atlas: NonSendMut, + map: Res, + renderables: Query<(Entity, &Renderable, &Position)>, + mut errors: EventWriter, +) { + // Clear the main canvas first + canvas.set_draw_color(sdl2::pixels::Color::BLACK); + canvas.clear(); + + // Render to backbuffer + canvas + .with_texture_canvas(&mut backbuffer.0, |backbuffer_canvas| { + // Clear the backbuffer + backbuffer_canvas.set_draw_color(sdl2::pixels::Color::BLACK); + backbuffer_canvas.clear(); + + // Copy the pre-rendered map texture to the backbuffer + backbuffer_canvas + .copy(&map_texture.0, None, None) + .err() + .map(|e| errors.write(TextureError::RenderFailed(e.to_string()).into())); + + // Render all entities to the backbuffer + for (_, renderable, position) in renderables.iter() { + let pos = position.get_pixel_pos(&map.graph); + match pos { + Ok(pos) => { + renderable + .sprite + .render(backbuffer_canvas, &mut atlas, pos) + .err() + .map(|e| errors.write(TextureError::RenderFailed(e.to_string()).into())); + } + Err(e) => { + errors.write(e.into()); + } + } + } + }) + .err() + .map(|e| errors.write(TextureError::RenderFailed(e.to_string()).into())); + + // Copy backbuffer to main canvas and present + canvas + .copy(&backbuffer.0, None, None) + .err() + .map(|e| errors.write(TextureError::RenderFailed(e.to_string()).into())); + + canvas.present(); +} diff --git a/src/entity/collision.rs b/src/entity/collision.rs index 9ca4a28..0401751 100644 --- a/src/entity/collision.rs +++ b/src/entity/collision.rs @@ -1,128 +1,128 @@ -use smallvec::SmallVec; -use std::collections::HashMap; +// use smallvec::SmallVec; +// use std::collections::HashMap; -use crate::entity::{graph::NodeId, traversal::Position}; +// use crate::entity::{graph::NodeId, traversal::Position}; -/// Trait for entities that can participate in collision detection. -pub trait Collidable { - /// Returns the current position of this entity. - fn position(&self) -> Position; +// /// Trait for entities that can participate in collision detection. +// pub trait Collidable { +// /// Returns the current position of this entity. +// fn position(&self) -> Position; - /// Checks if this entity is colliding with another entity. - #[allow(dead_code)] - fn is_colliding_with(&self, other: &dyn Collidable) -> bool { - positions_overlap(&self.position(), &other.position()) - } -} +// /// Checks if this entity is colliding with another entity. +// #[allow(dead_code)] +// fn is_colliding_with(&self, other: &dyn Collidable) -> bool { +// positions_overlap(&self.position(), &other.position()) +// } +// } -/// System for tracking entities by their positions for efficient collision detection. -#[derive(Default)] -pub struct CollisionSystem { - /// Maps node IDs to lists of entity IDs that are at that node - node_entities: HashMap>, - /// Maps entity IDs to their current positions - entity_positions: HashMap, - /// Next available entity ID - next_id: EntityId, -} +// /// System for tracking entities by their positions for efficient collision detection. +// #[derive(Default)] +// pub struct CollisionSystem { +// /// Maps node IDs to lists of entity IDs that are at that node +// node_entities: HashMap>, +// /// Maps entity IDs to their current positions +// entity_positions: HashMap, +// /// Next available entity ID +// next_id: EntityId, +// } -/// Unique identifier for an entity in the collision system -pub type EntityId = u32; +// /// Unique identifier for an entity in the collision system +// pub type EntityId = u32; -impl CollisionSystem { - /// Registers an entity with the collision system and returns its ID - pub fn register_entity(&mut self, position: Position) -> EntityId { - let id = self.next_id; - self.next_id += 1; +// impl CollisionSystem { +// /// Registers an entity with the collision system and returns its ID +// pub fn register_entity(&mut self, position: Position) -> EntityId { +// let id = self.next_id; +// self.next_id += 1; - self.entity_positions.insert(id, position); - self.update_node_entities(id, position); +// self.entity_positions.insert(id, position); +// self.update_node_entities(id, position); - id - } +// id +// } - /// Updates an entity's position - pub fn update_position(&mut self, entity_id: EntityId, new_position: Position) { - if let Some(old_position) = self.entity_positions.get(&entity_id) { - // Remove from old nodes - self.remove_from_nodes(entity_id, *old_position); - } +// /// Updates an entity's position +// pub fn update_position(&mut self, entity_id: EntityId, new_position: Position) { +// if let Some(old_position) = self.entity_positions.get(&entity_id) { +// // Remove from old nodes +// self.remove_from_nodes(entity_id, *old_position); +// } - // Update position and add to new nodes - self.entity_positions.insert(entity_id, new_position); - self.update_node_entities(entity_id, new_position); - } +// // Update position and add to new nodes +// self.entity_positions.insert(entity_id, new_position); +// self.update_node_entities(entity_id, new_position); +// } - /// Removes an entity from the collision system - #[allow(dead_code)] - pub fn remove_entity(&mut self, entity_id: EntityId) { - if let Some(position) = self.entity_positions.remove(&entity_id) { - self.remove_from_nodes(entity_id, position); - } - } +// /// Removes an entity from the collision system +// #[allow(dead_code)] +// pub fn remove_entity(&mut self, entity_id: EntityId) { +// if let Some(position) = self.entity_positions.remove(&entity_id) { +// self.remove_from_nodes(entity_id, position); +// } +// } - /// Gets all entity IDs at a specific node - pub fn entities_at_node(&self, node: NodeId) -> &[EntityId] { - self.node_entities.get(&node).map(|v| v.as_slice()).unwrap_or(&[]) - } +// /// Gets all entity IDs at a specific node +// pub fn entities_at_node(&self, node: NodeId) -> &[EntityId] { +// self.node_entities.get(&node).map(|v| v.as_slice()).unwrap_or(&[]) +// } - /// Gets all entity IDs that could collide with an entity at the given position - pub fn potential_collisions(&self, position: &Position) -> Vec { - let mut collisions = Vec::new(); - let nodes = get_nodes(position); +// /// Gets all entity IDs that could collide with an entity at the given position +// pub fn potential_collisions(&self, position: &Position) -> Vec { +// let mut collisions = Vec::new(); +// let nodes = get_nodes(position); - for node in nodes { - collisions.extend(self.entities_at_node(node)); - } +// for node in nodes { +// collisions.extend(self.entities_at_node(node)); +// } - // Remove duplicates - collisions.sort_unstable(); - collisions.dedup(); - collisions - } +// // Remove duplicates +// collisions.sort_unstable(); +// collisions.dedup(); +// collisions +// } - /// Updates the node_entities map when an entity's position changes - fn update_node_entities(&mut self, entity_id: EntityId, position: Position) { - let nodes = get_nodes(&position); - for node in nodes { - self.node_entities.entry(node).or_default().push(entity_id); - } - } +// /// Updates the node_entities map when an entity's position changes +// fn update_node_entities(&mut self, entity_id: EntityId, position: Position) { +// let nodes = get_nodes(&position); +// for node in nodes { +// self.node_entities.entry(node).or_default().push(entity_id); +// } +// } - /// Removes an entity from all nodes it was previously at - fn remove_from_nodes(&mut self, entity_id: EntityId, position: Position) { - let nodes = get_nodes(&position); - for node in nodes { - if let Some(entities) = self.node_entities.get_mut(&node) { - entities.retain(|&id| id != entity_id); - if entities.is_empty() { - self.node_entities.remove(&node); - } - } - } - } -} +// /// Removes an entity from all nodes it was previously at +// fn remove_from_nodes(&mut self, entity_id: EntityId, position: Position) { +// let nodes = get_nodes(&position); +// for node in nodes { +// if let Some(entities) = self.node_entities.get_mut(&node) { +// entities.retain(|&id| id != entity_id); +// if entities.is_empty() { +// self.node_entities.remove(&node); +// } +// } +// } +// } +// } -/// Checks if two positions overlap (entities are at the same location). -fn positions_overlap(a: &Position, b: &Position) -> bool { - let a_nodes = get_nodes(a); - let b_nodes = get_nodes(b); +// /// Checks if two positions overlap (entities are at the same location). +// fn positions_overlap(a: &Position, b: &Position) -> bool { +// let a_nodes = get_nodes(a); +// let b_nodes = get_nodes(b); - // Check if any nodes overlap - a_nodes.iter().any(|a_node| b_nodes.contains(a_node)) +// // Check if any nodes overlap +// a_nodes.iter().any(|a_node| b_nodes.contains(a_node)) - // TODO: More complex overlap detection, the above is a simple check, but it could become an early filter for more precise calculations later -} +// // TODO: More complex overlap detection, the above is a simple check, but it could become an early filter for more precise calculations later +// } -/// Gets all nodes that an entity is currently at or between. -fn get_nodes(pos: &Position) -> SmallVec<[NodeId; 2]> { - let mut nodes = SmallVec::new(); - match pos { - Position::AtNode(node) => nodes.push(*node), - Position::BetweenNodes { from, to, .. } => { - nodes.push(*from); - nodes.push(*to); - } - } - nodes -} +// /// Gets all nodes that an entity is currently at or between. +// fn get_nodes(pos: &Position) -> SmallVec<[NodeId; 2]> { +// let mut nodes = SmallVec::new(); +// match pos { +// Position::AtNode(node) => nodes.push(*node), +// Position::BetweenNodes { from, to, .. } => { +// nodes.push(*from); +// nodes.push(*to); +// } +// } +// nodes +// } diff --git a/src/entity/direction.rs b/src/entity/direction.rs index b6466f9..f981076 100644 --- a/src/entity/direction.rs +++ b/src/entity/direction.rs @@ -1,12 +1,13 @@ use glam::IVec2; /// The four cardinal directions. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] #[repr(usize)] pub enum Direction { Up, Down, Left, + #[default] Right, } diff --git a/src/entity/ghost.rs b/src/entity/ghost.rs index 0b3ec0e..7638d11 100644 --- a/src/entity/ghost.rs +++ b/src/entity/ghost.rs @@ -1,254 +1,254 @@ -//! Ghost entity implementation. -//! -//! This module contains the ghost character logic, including movement, -//! animation, and rendering. Ghosts move through the game graph using -//! a traverser and display directional animated textures. +// //! Ghost entity implementation. +// //! +// //! This module contains the ghost character logic, including movement, +// //! animation, and rendering. Ghosts move through the game graph using +// //! a traverser and display directional animated textures. -use pathfinding::prelude::dijkstra; -use rand::prelude::*; -use smallvec::SmallVec; -use tracing::error; +// use pathfinding::prelude::dijkstra; +// use rand::prelude::*; +// use smallvec::SmallVec; +// use tracing::error; -use crate::entity::{ - collision::Collidable, - direction::Direction, - graph::{Edge, EdgePermissions, Graph, NodeId}, - r#trait::Entity, - traversal::Traverser, -}; -use crate::texture::animated::AnimatedTexture; -use crate::texture::directional::DirectionalAnimatedTexture; -use crate::texture::sprite::SpriteAtlas; +// use crate::entity::{ +// collision::Collidable, +// direction::Direction, +// graph::{Edge, EdgePermissions, Graph, NodeId}, +// r#trait::Entity, +// traversal::Traverser, +// }; +// use crate::texture::animated::AnimatedTexture; +// use crate::texture::directional::DirectionalAnimatedTexture; +// use crate::texture::sprite::SpriteAtlas; -use crate::error::{EntityError, GameError, GameResult, TextureError}; +// use crate::error::{EntityError, GameError, GameResult, TextureError}; -/// Determines if a ghost can traverse a given edge. -/// -/// Ghosts can move through edges that allow all entities or ghost-only edges. -fn can_ghost_traverse(edge: Edge) -> bool { - matches!(edge.permissions, EdgePermissions::All | EdgePermissions::GhostsOnly) -} +// /// Determines if a ghost can traverse a given edge. +// /// +// /// Ghosts can move through edges that allow all entities or ghost-only edges. +// fn can_ghost_traverse(edge: Edge) -> bool { +// matches!(edge.permissions, EdgePermissions::All | EdgePermissions::GhostsOnly) +// } -/// The four classic ghost types. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum GhostType { - Blinky, - Pinky, - Inky, - Clyde, -} +// /// The four classic ghost types. +// #[derive(Debug, Clone, Copy, PartialEq, Eq)] +// pub enum GhostType { +// Blinky, +// Pinky, +// Inky, +// Clyde, +// } -impl GhostType { - /// Returns the ghost type name for atlas lookups. - pub fn as_str(self) -> &'static str { - match self { - GhostType::Blinky => "blinky", - GhostType::Pinky => "pinky", - GhostType::Inky => "inky", - GhostType::Clyde => "clyde", - } - } +// impl GhostType { +// /// Returns the ghost type name for atlas lookups. +// pub fn as_str(self) -> &'static str { +// match self { +// GhostType::Blinky => "blinky", +// GhostType::Pinky => "pinky", +// GhostType::Inky => "inky", +// GhostType::Clyde => "clyde", +// } +// } - /// Returns the base movement speed for this ghost type. - pub fn base_speed(self) -> f32 { - match self { - GhostType::Blinky => 1.0, - GhostType::Pinky => 0.95, - GhostType::Inky => 0.9, - GhostType::Clyde => 0.85, - } - } -} +// /// Returns the base movement speed for this ghost type. +// pub fn base_speed(self) -> f32 { +// match self { +// GhostType::Blinky => 1.0, +// GhostType::Pinky => 0.95, +// GhostType::Inky => 0.9, +// GhostType::Clyde => 0.85, +// } +// } +// } -/// A ghost entity that roams the game world. -/// -/// Ghosts move through the game world using a graph-based navigation system -/// and display directional animated sprites. They randomly choose directions -/// at each intersection. -pub struct Ghost { - /// Handles movement through the game graph - pub traverser: Traverser, - /// The type of ghost (affects appearance and speed) - pub ghost_type: GhostType, - /// Manages directional animated textures for different movement states - texture: DirectionalAnimatedTexture, - /// Current movement speed - speed: f32, -} +// /// A ghost entity that roams the game world. +// /// +// /// Ghosts move through the game world using a graph-based navigation system +// /// and display directional animated sprites. They randomly choose directions +// /// at each intersection. +// pub struct Ghost { +// /// Handles movement through the game graph +// pub traverser: Traverser, +// /// The type of ghost (affects appearance and speed) +// pub ghost_type: GhostType, +// /// Manages directional animated textures for different movement states +// texture: DirectionalAnimatedTexture, +// /// Current movement speed +// speed: f32, +// } -impl Entity for Ghost { - fn traverser(&self) -> &Traverser { - &self.traverser - } +// impl Entity for Ghost { +// fn traverser(&self) -> &Traverser { +// &self.traverser +// } - fn traverser_mut(&mut self) -> &mut Traverser { - &mut self.traverser - } +// fn traverser_mut(&mut self) -> &mut Traverser { +// &mut self.traverser +// } - fn texture(&self) -> &DirectionalAnimatedTexture { - &self.texture - } +// fn texture(&self) -> &DirectionalAnimatedTexture { +// &self.texture +// } - fn texture_mut(&mut self) -> &mut DirectionalAnimatedTexture { - &mut self.texture - } +// fn texture_mut(&mut self) -> &mut DirectionalAnimatedTexture { +// &mut self.texture +// } - fn speed(&self) -> f32 { - self.speed - } +// fn speed(&self) -> f32 { +// self.speed +// } - fn can_traverse(&self, edge: Edge) -> bool { - can_ghost_traverse(edge) - } +// fn can_traverse(&self, edge: Edge) -> bool { +// can_ghost_traverse(edge) +// } - fn tick(&mut self, dt: f32, graph: &Graph) { - // Choose random direction when at a node - if self.traverser.position.is_at_node() { - self.choose_random_direction(graph); - } +// fn tick(&mut self, dt: f32, graph: &Graph) { +// // Choose random direction when at a node +// if self.traverser.position.is_at_node() { +// self.choose_random_direction(graph); +// } - if let Err(e) = self.traverser.advance(graph, dt * 60.0 * self.speed, &can_ghost_traverse) { - error!("Ghost movement error: {}", e); - } - self.texture.tick(dt); - } -} +// if let Err(e) = self.traverser.advance(graph, dt * 60.0 * self.speed, &can_ghost_traverse) { +// error!("Ghost movement error: {}", e); +// } +// self.texture.tick(dt); +// } +// } -impl Ghost { - /// Creates a new ghost instance at the specified starting node. - /// - /// Sets up animated textures for all four directions with moving and stopped states. - /// The moving animation cycles through two sprite variants. - pub fn new(graph: &Graph, start_node: NodeId, ghost_type: GhostType, atlas: &SpriteAtlas) -> GameResult { - let mut textures = [None, None, None, None]; - let mut stopped_textures = [None, None, None, None]; +// impl Ghost { +// /// Creates a new ghost instance at the specified starting node. +// /// +// /// Sets up animated textures for all four directions with moving and stopped states. +// /// The moving animation cycles through two sprite variants. +// pub fn new(graph: &Graph, start_node: NodeId, ghost_type: GhostType, atlas: &SpriteAtlas) -> GameResult { +// let mut textures = [None, None, None, None]; +// let mut stopped_textures = [None, None, None, None]; - for direction in Direction::DIRECTIONS { - let moving_prefix = match direction { - Direction::Up => "up", - Direction::Down => "down", - Direction::Left => "left", - Direction::Right => "right", - }; - let moving_tiles = vec![ - SpriteAtlas::get_tile(atlas, &format!("ghost/{}/{}_{}.png", ghost_type.as_str(), moving_prefix, "a")) - .ok_or_else(|| { - GameError::Texture(TextureError::AtlasTileNotFound(format!( - "ghost/{}/{}_{}.png", - ghost_type.as_str(), - moving_prefix, - "a" - ))) - })?, - SpriteAtlas::get_tile(atlas, &format!("ghost/{}/{}_{}.png", ghost_type.as_str(), moving_prefix, "b")) - .ok_or_else(|| { - GameError::Texture(TextureError::AtlasTileNotFound(format!( - "ghost/{}/{}_{}.png", - ghost_type.as_str(), - moving_prefix, - "b" - ))) - })?, - ]; +// for direction in Direction::DIRECTIONS { +// let moving_prefix = match direction { +// Direction::Up => "up", +// Direction::Down => "down", +// Direction::Left => "left", +// Direction::Right => "right", +// }; +// let moving_tiles = vec![ +// SpriteAtlas::get_tile(atlas, &format!("ghost/{}/{}_{}.png", ghost_type.as_str(), moving_prefix, "a")) +// .ok_or_else(|| { +// GameError::Texture(TextureError::AtlasTileNotFound(format!( +// "ghost/{}/{}_{}.png", +// ghost_type.as_str(), +// moving_prefix, +// "a" +// ))) +// })?, +// SpriteAtlas::get_tile(atlas, &format!("ghost/{}/{}_{}.png", ghost_type.as_str(), moving_prefix, "b")) +// .ok_or_else(|| { +// GameError::Texture(TextureError::AtlasTileNotFound(format!( +// "ghost/{}/{}_{}.png", +// ghost_type.as_str(), +// moving_prefix, +// "b" +// ))) +// })?, +// ]; - let stopped_tiles = - vec![ - SpriteAtlas::get_tile(atlas, &format!("ghost/{}/{}_{}.png", ghost_type.as_str(), moving_prefix, "a")) - .ok_or_else(|| { - GameError::Texture(TextureError::AtlasTileNotFound(format!( - "ghost/{}/{}_{}.png", - ghost_type.as_str(), - moving_prefix, - "a" - ))) - })?, - ]; +// let stopped_tiles = +// vec![ +// SpriteAtlas::get_tile(atlas, &format!("ghost/{}/{}_{}.png", ghost_type.as_str(), moving_prefix, "a")) +// .ok_or_else(|| { +// GameError::Texture(TextureError::AtlasTileNotFound(format!( +// "ghost/{}/{}_{}.png", +// ghost_type.as_str(), +// moving_prefix, +// "a" +// ))) +// })?, +// ]; - textures[direction.as_usize()] = Some(AnimatedTexture::new(moving_tiles, 0.2)?); - stopped_textures[direction.as_usize()] = Some(AnimatedTexture::new(stopped_tiles, 0.1)?); - } +// textures[direction.as_usize()] = Some(AnimatedTexture::new(moving_tiles, 0.2)?); +// stopped_textures[direction.as_usize()] = Some(AnimatedTexture::new(stopped_tiles, 0.1)?); +// } - Ok(Self { - traverser: Traverser::new(graph, start_node, Direction::Left, &can_ghost_traverse), - ghost_type, - texture: DirectionalAnimatedTexture::new(textures, stopped_textures), - speed: ghost_type.base_speed(), - }) - } +// Ok(Self { +// traverser: Traverser::new(graph, start_node, Direction::Left, &can_ghost_traverse), +// ghost_type, +// texture: DirectionalAnimatedTexture::new(textures, stopped_textures), +// speed: ghost_type.base_speed(), +// }) +// } - /// Chooses a random available direction at the current intersection. - fn choose_random_direction(&mut self, graph: &Graph) { - let current_node = self.traverser.position.from_node_id(); - let intersection = &graph.adjacency_list[current_node]; +// /// Chooses a random available direction at the current intersection. +// fn choose_random_direction(&mut self, graph: &Graph) { +// let current_node = self.traverser.position.from_node_id(); +// let intersection = &graph.adjacency_list[current_node]; - // Collect all available directions - let mut available_directions = SmallVec::<[_; 4]>::new(); - for direction in Direction::DIRECTIONS { - if let Some(edge) = intersection.get(direction) { - if can_ghost_traverse(edge) { - available_directions.push(direction); - } - } - } - // Choose a random direction (avoid reversing unless necessary) - if !available_directions.is_empty() { - let mut rng = SmallRng::from_os_rng(); +// // Collect all available directions +// let mut available_directions = SmallVec::<[_; 4]>::new(); +// for direction in Direction::DIRECTIONS { +// if let Some(edge) = intersection.get(direction) { +// if can_ghost_traverse(edge) { +// available_directions.push(direction); +// } +// } +// } +// // Choose a random direction (avoid reversing unless necessary) +// if !available_directions.is_empty() { +// let mut rng = SmallRng::from_os_rng(); - // Filter out the opposite direction if possible, but allow it if we have limited options - let opposite = self.traverser.direction.opposite(); - let filtered_directions: Vec<_> = available_directions - .iter() - .filter(|&&dir| dir != opposite || available_directions.len() <= 2) - .collect(); +// // Filter out the opposite direction if possible, but allow it if we have limited options +// let opposite = self.traverser.direction.opposite(); +// let filtered_directions: Vec<_> = available_directions +// .iter() +// .filter(|&&dir| dir != opposite || available_directions.len() <= 2) +// .collect(); - if let Some(&random_direction) = filtered_directions.choose(&mut rng) { - self.traverser.set_next_direction(*random_direction); - } - } - } +// if let Some(&random_direction) = filtered_directions.choose(&mut rng) { +// self.traverser.set_next_direction(*random_direction); +// } +// } +// } - /// Calculates the shortest path from the ghost's current position to a target node using Dijkstra's algorithm. - /// - /// Returns a vector of NodeIds representing the path, or an error if pathfinding fails. - /// The path includes the current node and the target node. - pub fn calculate_path_to_target(&self, graph: &Graph, target: NodeId) -> GameResult> { - let start_node = self.traverser.position.from_node_id(); +// /// Calculates the shortest path from the ghost's current position to a target node using Dijkstra's algorithm. +// /// +// /// Returns a vector of NodeIds representing the path, or an error if pathfinding fails. +// /// The path includes the current node and the target node. +// pub fn calculate_path_to_target(&self, graph: &Graph, target: NodeId) -> GameResult> { +// let start_node = self.traverser.position.from_node_id(); - // Use Dijkstra's algorithm to find the shortest path - let result = dijkstra( - &start_node, - |&node_id| { - // Get all edges from the current node - graph.adjacency_list[node_id] - .edges() - .filter(|edge| can_ghost_traverse(*edge)) - .map(|edge| (edge.target, (edge.distance * 100.0) as u32)) - .collect::>() - }, - |&node_id| node_id == target, - ); +// // Use Dijkstra's algorithm to find the shortest path +// let result = dijkstra( +// &start_node, +// |&node_id| { +// // Get all edges from the current node +// graph.adjacency_list[node_id] +// .edges() +// .filter(|edge| can_ghost_traverse(*edge)) +// .map(|edge| (edge.target, (edge.distance * 100.0) as u32)) +// .collect::>() +// }, +// |&node_id| node_id == target, +// ); - result.map(|(path, _cost)| path).ok_or_else(|| { - GameError::Entity(EntityError::PathfindingFailed(format!( - "No path found from node {} to target {}", - start_node, target - ))) - }) - } +// result.map(|(path, _cost)| path).ok_or_else(|| { +// GameError::Entity(EntityError::PathfindingFailed(format!( +// "No path found from node {} to target {}", +// start_node, target +// ))) +// }) +// } - /// Returns the ghost's color for debug rendering. - pub fn debug_color(&self) -> sdl2::pixels::Color { - match self.ghost_type { - GhostType::Blinky => sdl2::pixels::Color::RGB(255, 0, 0), // Red - GhostType::Pinky => sdl2::pixels::Color::RGB(255, 182, 255), // Pink - GhostType::Inky => sdl2::pixels::Color::RGB(0, 255, 255), // Cyan - GhostType::Clyde => sdl2::pixels::Color::RGB(255, 182, 85), // Orange - } - } -} +// /// Returns the ghost's color for debug rendering. +// pub fn debug_color(&self) -> sdl2::pixels::Color { +// match self.ghost_type { +// GhostType::Blinky => sdl2::pixels::Color::RGB(255, 0, 0), // Red +// GhostType::Pinky => sdl2::pixels::Color::RGB(255, 182, 255), // Pink +// GhostType::Inky => sdl2::pixels::Color::RGB(0, 255, 255), // Cyan +// GhostType::Clyde => sdl2::pixels::Color::RGB(255, 182, 85), // Orange +// } +// } +// } -impl Collidable for Ghost { - fn position(&self) -> crate::entity::traversal::Position { - self.traverser.position - } -} +// impl Collidable for Ghost { +// fn position(&self) -> crate::entity::traversal::Position { +// self.traverser.position +// } +// } diff --git a/src/entity/graph.rs b/src/entity/graph.rs index b5867d8..e19f60f 100644 --- a/src/entity/graph.rs +++ b/src/entity/graph.rs @@ -1,9 +1,8 @@ use glam::Vec2; -use super::direction::Direction; +use crate::ecs::NodeId; -/// A unique identifier for a node, represented by its index in the graph's storage. -pub type NodeId = usize; +use super::direction::Direction; /// Defines who can traverse a given edge. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] diff --git a/src/entity/item.rs b/src/entity/item.rs index 8d9788e..1d662a9 100644 --- a/src/entity/item.rs +++ b/src/entity/item.rs @@ -1,117 +1,117 @@ -use crate::{ - constants, - entity::{collision::Collidable, graph::Graph}, - error::{EntityError, GameResult}, - texture::sprite::{Sprite, SpriteAtlas}, -}; -use sdl2::render::{Canvas, RenderTarget}; -use strum_macros::{EnumCount, EnumIter}; +// use crate::{ +// constants, +// entity::{collision::Collidable, graph::Graph}, +// error::{EntityError, GameResult}, +// texture::sprite::{Sprite, SpriteAtlas}, +// }; +// use sdl2::render::{Canvas, RenderTarget}; +// use strum_macros::{EnumCount, EnumIter}; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ItemType { - Pellet, - Energizer, - #[allow(dead_code)] - Fruit { - kind: FruitKind, - }, -} +// #[derive(Debug, Clone, Copy, PartialEq, Eq)] +// pub enum ItemType { +// Pellet, +// Energizer, +// #[allow(dead_code)] +// Fruit { +// kind: FruitKind, +// }, +// } -impl ItemType { - pub fn get_score(self) -> u32 { - match self { - ItemType::Pellet => 10, - ItemType::Energizer => 50, - ItemType::Fruit { kind } => kind.get_score(), - } - } -} +// impl ItemType { +// pub fn get_score(self) -> u32 { +// match self { +// ItemType::Pellet => 10, +// ItemType::Energizer => 50, +// ItemType::Fruit { kind } => kind.get_score(), +// } +// } +// } -#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCount)] -#[allow(dead_code)] -pub enum FruitKind { - Apple, - Strawberry, - Orange, - Melon, - Bell, - Key, - Galaxian, -} +// #[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCount)] +// #[allow(dead_code)] +// pub enum FruitKind { +// Apple, +// Strawberry, +// Orange, +// Melon, +// Bell, +// Key, +// Galaxian, +// } -impl FruitKind { - #[allow(dead_code)] - pub fn index(self) -> u8 { - match self { - FruitKind::Apple => 0, - FruitKind::Strawberry => 1, - FruitKind::Orange => 2, - FruitKind::Melon => 3, - FruitKind::Bell => 4, - FruitKind::Key => 5, - FruitKind::Galaxian => 6, - } - } +// impl FruitKind { +// #[allow(dead_code)] +// pub fn index(self) -> u8 { +// match self { +// FruitKind::Apple => 0, +// FruitKind::Strawberry => 1, +// FruitKind::Orange => 2, +// FruitKind::Melon => 3, +// FruitKind::Bell => 4, +// FruitKind::Key => 5, +// FruitKind::Galaxian => 6, +// } +// } - pub fn get_score(self) -> u32 { - match self { - FruitKind::Apple => 100, - FruitKind::Strawberry => 300, - FruitKind::Orange => 500, - FruitKind::Melon => 700, - FruitKind::Bell => 1000, - FruitKind::Key => 2000, - FruitKind::Galaxian => 3000, - } - } -} +// pub fn get_score(self) -> u32 { +// match self { +// FruitKind::Apple => 100, +// FruitKind::Strawberry => 300, +// FruitKind::Orange => 500, +// FruitKind::Melon => 700, +// FruitKind::Bell => 1000, +// FruitKind::Key => 2000, +// FruitKind::Galaxian => 3000, +// } +// } +// } -pub struct Item { - pub node_index: usize, - pub item_type: ItemType, - pub sprite: Sprite, - pub collected: bool, -} +// pub struct Item { +// pub node_index: usize, +// pub item_type: ItemType, +// pub sprite: Sprite, +// pub collected: bool, +// } -impl Item { - pub fn new(node_index: usize, item_type: ItemType, sprite: Sprite) -> Self { - Self { - node_index, - item_type, - sprite, - collected: false, - } - } +// impl Item { +// pub fn new(node_index: usize, item_type: ItemType, sprite: Sprite) -> Self { +// Self { +// node_index, +// item_type, +// sprite, +// collected: false, +// } +// } - pub fn is_collected(&self) -> bool { - self.collected - } +// pub fn is_collected(&self) -> bool { +// self.collected +// } - pub fn collect(&mut self) { - self.collected = true; - } +// pub fn collect(&mut self) { +// self.collected = true; +// } - pub fn get_score(&self) -> u32 { - self.item_type.get_score() - } +// pub fn get_score(&self) -> u32 { +// self.item_type.get_score() +// } - pub fn render(&self, canvas: &mut Canvas, atlas: &mut SpriteAtlas, graph: &Graph) -> GameResult<()> { - if self.collected { - return Ok(()); - } +// pub fn render(&self, canvas: &mut Canvas, atlas: &mut SpriteAtlas, graph: &Graph) -> GameResult<()> { +// if self.collected { +// return Ok(()); +// } - let node = graph - .get_node(self.node_index) - .ok_or(EntityError::NodeNotFound(self.node_index))?; - let position = node.position + constants::BOARD_PIXEL_OFFSET.as_vec2(); +// let node = graph +// .get_node(self.node_index) +// .ok_or(EntityError::NodeNotFound(self.node_index))?; +// let position = node.position + constants::BOARD_PIXEL_OFFSET.as_vec2(); - self.sprite.render(canvas, atlas, position)?; - Ok(()) - } -} +// self.sprite.render(canvas, atlas, position)?; +// Ok(()) +// } +// } -impl Collidable for Item { - fn position(&self) -> crate::entity::traversal::Position { - crate::entity::traversal::Position::AtNode(self.node_index) - } -} +// impl Collidable for Item { +// fn position(&self) -> crate::entity::traversal::Position { +// crate::entity::traversal::Position::AtNode(self.node_index) +// } +// } diff --git a/src/entity/pacman.rs b/src/entity/pacman.rs index a1b2c8e..d881968 100644 --- a/src/entity/pacman.rs +++ b/src/entity/pacman.rs @@ -1,115 +1,115 @@ -//! Pac-Man entity implementation. -//! -//! This module contains the main player character logic, including movement, -//! animation, and rendering. Pac-Man moves through the game graph using -//! a traverser and displays directional animated textures. +// //! Pac-Man entity implementation. +// //! +// //! This module contains the main player character logic, including movement, +// //! animation, and rendering. Pac-Man moves through the game graph using +// //! a traverser and displays directional animated textures. -use crate::entity::{ - collision::Collidable, - direction::Direction, - graph::{Edge, EdgePermissions, Graph, NodeId}, - r#trait::Entity, - traversal::Traverser, -}; -use crate::texture::animated::AnimatedTexture; -use crate::texture::directional::DirectionalAnimatedTexture; -use crate::texture::sprite::SpriteAtlas; -use tracing::error; +// use crate::entity::{ +// collision::Collidable, +// direction::Direction, +// graph::{Edge, EdgePermissions, Graph, NodeId}, +// r#trait::Entity, +// traversal::Traverser, +// }; +// use crate::texture::animated::AnimatedTexture; +// use crate::texture::directional::DirectionalAnimatedTexture; +// use crate::texture::sprite::SpriteAtlas; +// use tracing::error; -use crate::error::{GameError, GameResult, TextureError}; +// use crate::error::{GameError, GameResult, TextureError}; -/// Determines if Pac-Man can traverse a given edge. -/// -/// Pac-Man can only move through edges that allow all entities. -fn can_pacman_traverse(edge: Edge) -> bool { - matches!(edge.permissions, EdgePermissions::All) -} +// /// Determines if Pac-Man can traverse a given edge. +// /// +// /// Pac-Man can only move through edges that allow all entities. +// fn can_pacman_traverse(edge: Edge) -> bool { +// matches!(edge.permissions, EdgePermissions::All) +// } -/// The main player character entity. -/// -/// Pac-Man moves through the game world using a graph-based navigation system -/// and displays directional animated sprites based on movement state. -pub struct Pacman { - /// Handles movement through the game graph - pub traverser: Traverser, - /// Manages directional animated textures for different movement states - texture: DirectionalAnimatedTexture, -} +// /// The main player character entity. +// /// +// /// Pac-Man moves through the game world using a graph-based navigation system +// /// and displays directional animated sprites based on movement state. +// pub struct Pacman { +// /// Handles movement through the game graph +// pub traverser: Traverser, +// /// Manages directional animated textures for different movement states +// texture: DirectionalAnimatedTexture, +// } -impl Entity for Pacman { - fn traverser(&self) -> &Traverser { - &self.traverser - } +// impl Entity for Pacman { +// fn traverser(&self) -> &Traverser { +// &self.traverser +// } - fn traverser_mut(&mut self) -> &mut Traverser { - &mut self.traverser - } +// fn traverser_mut(&mut self) -> &mut Traverser { +// &mut self.traverser +// } - fn texture(&self) -> &DirectionalAnimatedTexture { - &self.texture - } +// fn texture(&self) -> &DirectionalAnimatedTexture { +// &self.texture +// } - fn texture_mut(&mut self) -> &mut DirectionalAnimatedTexture { - &mut self.texture - } +// fn texture_mut(&mut self) -> &mut DirectionalAnimatedTexture { +// &mut self.texture +// } - fn speed(&self) -> f32 { - 1.125 - } +// fn speed(&self) -> f32 { +// 1.125 +// } - fn can_traverse(&self, edge: Edge) -> bool { - can_pacman_traverse(edge) - } +// fn can_traverse(&self, edge: Edge) -> bool { +// can_pacman_traverse(edge) +// } - fn tick(&mut self, dt: f32, graph: &Graph) { - if let Err(e) = self.traverser.advance(graph, dt * 60.0 * 1.125, &can_pacman_traverse) { - error!("Pac-Man movement error: {}", e); - } - self.texture.tick(dt); - } -} +// fn tick(&mut self, dt: f32, graph: &Graph) { +// if let Err(e) = self.traverser.advance(graph, dt * 60.0 * 1.125, &can_pacman_traverse) { +// error!("Pac-Man movement error: {}", e); +// } +// self.texture.tick(dt); +// } +// } -impl Pacman { - /// Creates a new Pac-Man instance at the specified starting node. - /// - /// Sets up animated textures for all four directions with moving and stopped states. - /// The moving animation cycles through open mouth, closed mouth, and full sprites. - pub fn new(graph: &Graph, start_node: NodeId, atlas: &SpriteAtlas) -> GameResult { - let mut textures = [None, None, None, None]; - let mut stopped_textures = [None, None, None, None]; +// impl Pacman { +// /// Creates a new Pac-Man instance at the specified starting node. +// /// +// /// Sets up animated textures for all four directions with moving and stopped states. +// /// The moving animation cycles through open mouth, closed mouth, and full sprites. +// pub fn new(graph: &Graph, start_node: NodeId, atlas: &SpriteAtlas) -> GameResult { +// let mut textures = [None, None, None, None]; +// let mut stopped_textures = [None, None, None, None]; - for direction in Direction::DIRECTIONS { - let moving_prefix = match direction { - Direction::Up => "pacman/up", - Direction::Down => "pacman/down", - Direction::Left => "pacman/left", - Direction::Right => "pacman/right", - }; - let moving_tiles = vec![ - SpriteAtlas::get_tile(atlas, &format!("{moving_prefix}_a.png")) - .ok_or_else(|| GameError::Texture(TextureError::AtlasTileNotFound(format!("{moving_prefix}_a.png"))))?, - SpriteAtlas::get_tile(atlas, &format!("{moving_prefix}_b.png")) - .ok_or_else(|| GameError::Texture(TextureError::AtlasTileNotFound(format!("{moving_prefix}_b.png"))))?, - SpriteAtlas::get_tile(atlas, "pacman/full.png") - .ok_or_else(|| GameError::Texture(TextureError::AtlasTileNotFound("pacman/full.png".to_string())))?, - ]; +// for direction in Direction::DIRECTIONS { +// let moving_prefix = match direction { +// Direction::Up => "pacman/up", +// Direction::Down => "pacman/down", +// Direction::Left => "pacman/left", +// Direction::Right => "pacman/right", +// }; +// let moving_tiles = vec![ +// SpriteAtlas::get_tile(atlas, &format!("{moving_prefix}_a.png")) +// .ok_or_else(|| GameError::Texture(TextureError::AtlasTileNotFound(format!("{moving_prefix}_a.png"))))?, +// SpriteAtlas::get_tile(atlas, &format!("{moving_prefix}_b.png")) +// .ok_or_else(|| GameError::Texture(TextureError::AtlasTileNotFound(format!("{moving_prefix}_b.png"))))?, +// SpriteAtlas::get_tile(atlas, "pacman/full.png") +// .ok_or_else(|| GameError::Texture(TextureError::AtlasTileNotFound("pacman/full.png".to_string())))?, +// ]; - let stopped_tiles = vec![SpriteAtlas::get_tile(atlas, &format!("{moving_prefix}_b.png")) - .ok_or_else(|| GameError::Texture(TextureError::AtlasTileNotFound(format!("{moving_prefix}_b.png"))))?]; +// let stopped_tiles = vec![SpriteAtlas::get_tile(atlas, &format!("{moving_prefix}_b.png")) +// .ok_or_else(|| GameError::Texture(TextureError::AtlasTileNotFound(format!("{moving_prefix}_b.png"))))?]; - textures[direction.as_usize()] = Some(AnimatedTexture::new(moving_tiles, 0.08)?); - stopped_textures[direction.as_usize()] = Some(AnimatedTexture::new(stopped_tiles, 0.1)?); - } +// textures[direction.as_usize()] = Some(AnimatedTexture::new(moving_tiles, 0.08)?); +// stopped_textures[direction.as_usize()] = Some(AnimatedTexture::new(stopped_tiles, 0.1)?); +// } - Ok(Self { - traverser: Traverser::new(graph, start_node, Direction::Left, &can_pacman_traverse), - texture: DirectionalAnimatedTexture::new(textures, stopped_textures), - }) - } -} +// Ok(Self { +// traverser: Traverser::new(graph, start_node, Direction::Left, &can_pacman_traverse), +// texture: DirectionalAnimatedTexture::new(textures, stopped_textures), +// }) +// } +// } -impl Collidable for Pacman { - fn position(&self) -> crate::entity::traversal::Position { - self.traverser.position - } -} +// impl Collidable for Pacman { +// fn position(&self) -> crate::entity::traversal::Position { +// self.traverser.position +// } +// } diff --git a/src/entity/trait.rs b/src/entity/trait.rs index e45495f..b0f07e8 100644 --- a/src/entity/trait.rs +++ b/src/entity/trait.rs @@ -1,114 +1,114 @@ -//! Entity trait for common movement and rendering functionality. -//! -//! This module defines a trait that captures the shared behavior between -//! different game entities like Ghosts and Pac-Man, including movement, -//! rendering, and position calculations. +// //! Entity trait for common movement and rendering functionality. +// //! +// //! This module defines a trait that captures the shared behavior between +// //! different game entities like Ghosts and Pac-Man, including movement, +// //! rendering, and position calculations. -use glam::Vec2; -use sdl2::render::{Canvas, RenderTarget}; +// use glam::Vec2; +// use sdl2::render::{Canvas, RenderTarget}; -use crate::entity::direction::Direction; -use crate::entity::graph::{Edge, Graph, NodeId}; -use crate::entity::traversal::{Position, Traverser}; -use crate::error::{EntityError, GameError, GameResult, TextureError}; -use crate::texture::directional::DirectionalAnimatedTexture; -use crate::texture::sprite::SpriteAtlas; +// use crate::entity::direction::Direction; +// use crate::entity::graph::{Edge, Graph, NodeId}; +// use crate::entity::traversal::{Position, Traverser}; +// use crate::error::{EntityError, GameError, GameResult, TextureError}; +// use crate::texture::directional::DirectionalAnimatedTexture; +// use crate::texture::sprite::SpriteAtlas; -/// Trait defining common functionality for game entities that move through the graph. -/// -/// This trait provides a unified interface for entities that: -/// - Move through the game graph using a traverser -/// - Render using directional animated textures -/// - Have position calculations and movement speed -#[allow(dead_code)] -pub trait Entity { - /// Returns a reference to the entity's traverser for movement control. - fn traverser(&self) -> &Traverser; +// /// Trait defining common functionality for game entities that move through the graph. +// /// +// /// This trait provides a unified interface for entities that: +// /// - Move through the game graph using a traverser +// /// - Render using directional animated textures +// /// - Have position calculations and movement speed +// #[allow(dead_code)] +// pub trait Entity { +// /// Returns a reference to the entity's traverser for movement control. +// fn traverser(&self) -> &Traverser; - /// Returns a mutable reference to the entity's traverser for movement control. - fn traverser_mut(&mut self) -> &mut Traverser; +// /// Returns a mutable reference to the entity's traverser for movement control. +// fn traverser_mut(&mut self) -> &mut Traverser; - /// Returns a reference to the entity's directional animated texture. - fn texture(&self) -> &DirectionalAnimatedTexture; +// /// Returns a reference to the entity's directional animated texture. +// fn texture(&self) -> &DirectionalAnimatedTexture; - /// Returns a mutable reference to the entity's directional animated texture. - fn texture_mut(&mut self) -> &mut DirectionalAnimatedTexture; +// /// Returns a mutable reference to the entity's directional animated texture. +// fn texture_mut(&mut self) -> &mut DirectionalAnimatedTexture; - /// Returns the movement speed multiplier for this entity. - fn speed(&self) -> f32; +// /// Returns the movement speed multiplier for this entity. +// fn speed(&self) -> f32; - /// Determines if this entity can traverse a given edge. - fn can_traverse(&self, edge: Edge) -> bool; +// /// Determines if this entity can traverse a given edge. +// fn can_traverse(&self, edge: Edge) -> bool; - /// Updates the entity's position and animation state. - /// - /// This method advances movement through the graph and updates texture animation. - fn tick(&mut self, dt: f32, graph: &Graph); +// /// Updates the entity's position and animation state. +// /// +// /// This method advances movement through the graph and updates texture animation. +// fn tick(&mut self, dt: f32, graph: &Graph); - /// Calculates the current pixel position in the game world. - /// - /// Converts the graph position to screen coordinates, accounting for - /// the board offset and centering the sprite. - fn get_pixel_pos(&self, graph: &Graph) -> GameResult { - let pos = match self.traverser().position { - Position::AtNode(node_id) => { - let node = graph.get_node(node_id).ok_or(EntityError::NodeNotFound(node_id))?; - node.position - } - Position::BetweenNodes { from, to, traversed } => { - let from_node = graph.get_node(from).ok_or(EntityError::NodeNotFound(from))?; - let to_node = graph.get_node(to).ok_or(EntityError::NodeNotFound(to))?; - let edge = graph.find_edge(from, to).ok_or(EntityError::EdgeNotFound { from, to })?; - from_node.position + (to_node.position - from_node.position) * (traversed / edge.distance) - } - }; +// /// Calculates the current pixel position in the game world. +// /// +// /// Converts the graph position to screen coordinates, accounting for +// /// the board offset and centering the sprite. +// fn get_pixel_pos(&self, graph: &Graph) -> GameResult { +// let pos = match self.traverser().position { +// Position::AtNode(node_id) => { +// let node = graph.get_node(node_id).ok_or(EntityError::NodeNotFound(node_id))?; +// node.position +// } +// Position::BetweenNodes { from, to, traversed } => { +// let from_node = graph.get_node(from).ok_or(EntityError::NodeNotFound(from))?; +// let to_node = graph.get_node(to).ok_or(EntityError::NodeNotFound(to))?; +// let edge = graph.find_edge(from, to).ok_or(EntityError::EdgeNotFound { from, to })?; +// from_node.position + (to_node.position - from_node.position) * (traversed / edge.distance) +// } +// }; - Ok(Vec2::new( - pos.x + crate::constants::BOARD_PIXEL_OFFSET.x as f32, - pos.y + crate::constants::BOARD_PIXEL_OFFSET.y as f32, - )) - } +// Ok(Vec2::new( +// pos.x + crate::constants::BOARD_PIXEL_OFFSET.x as f32, +// pos.y + crate::constants::BOARD_PIXEL_OFFSET.y as f32, +// )) +// } - /// Returns the current node ID that the entity is at or moving towards. - /// - /// If the entity is at a node, returns that node ID. - /// If the entity is between nodes, returns the node it's moving towards. - fn current_node_id(&self) -> NodeId { - match self.traverser().position { - Position::AtNode(node_id) => node_id, - Position::BetweenNodes { to, .. } => to, - } - } +// /// Returns the current node ID that the entity is at or moving towards. +// /// +// /// If the entity is at a node, returns that node ID. +// /// If the entity is between nodes, returns the node it's moving towards. +// fn current_node_id(&self) -> NodeId { +// match self.traverser().position { +// Position::AtNode(node_id) => node_id, +// Position::BetweenNodes { to, .. } => to, +// } +// } - /// Sets the next direction for the entity to take. - /// - /// The direction is buffered and will be applied at the next opportunity, - /// typically when the entity reaches a new node. - fn set_next_direction(&mut self, direction: Direction) { - self.traverser_mut().set_next_direction(direction); - } +// /// Sets the next direction for the entity to take. +// /// +// /// The direction is buffered and will be applied at the next opportunity, +// /// typically when the entity reaches a new node. +// fn set_next_direction(&mut self, direction: Direction) { +// self.traverser_mut().set_next_direction(direction); +// } - /// Renders the entity at its current position. - /// - /// Draws the appropriate directional sprite based on the entity's - /// current movement state and direction. - fn render(&self, canvas: &mut Canvas, atlas: &mut SpriteAtlas, graph: &Graph) -> GameResult<()> { - let pixel_pos = self.get_pixel_pos(graph)?; - let dest = crate::helpers::centered_with_size( - glam::IVec2::new(pixel_pos.x as i32, pixel_pos.y as i32), - glam::UVec2::new(16, 16), - ); +// /// Renders the entity at its current position. +// /// +// /// Draws the appropriate directional sprite based on the entity's +// /// current movement state and direction. +// fn render(&self, canvas: &mut Canvas, atlas: &mut SpriteAtlas, graph: &Graph) -> GameResult<()> { +// let pixel_pos = self.get_pixel_pos(graph)?; +// let dest = crate::helpers::centered_with_size( +// glam::IVec2::new(pixel_pos.x as i32, pixel_pos.y as i32), +// glam::UVec2::new(16, 16), +// ); - if self.traverser().position.is_stopped() { - self.texture() - .render_stopped(canvas, atlas, dest, self.traverser().direction) - .map_err(|e| GameError::Texture(TextureError::RenderFailed(e.to_string())))?; - } else { - self.texture() - .render(canvas, atlas, dest, self.traverser().direction) - .map_err(|e| GameError::Texture(TextureError::RenderFailed(e.to_string())))?; - } +// if self.traverser().position.is_stopped() { +// self.texture() +// .render_stopped(canvas, atlas, dest, self.traverser().direction) +// .map_err(|e| GameError::Texture(TextureError::RenderFailed(e.to_string())))?; +// } else { +// self.texture() +// .render(canvas, atlas, dest, self.traverser().direction) +// .map_err(|e| GameError::Texture(TextureError::RenderFailed(e.to_string())))?; +// } - Ok(()) - } -} +// Ok(()) +// } +// } diff --git a/src/entity/traversal.rs b/src/entity/traversal.rs index 372d37d..273303e 100644 --- a/src/entity/traversal.rs +++ b/src/entity/traversal.rs @@ -1,229 +1,181 @@ -use tracing::error; +// use tracing::error; -use crate::error::GameResult; +// use crate::error::GameResult; -use super::direction::Direction; -use super::graph::{Edge, Graph, NodeId}; +// use super::direction::Direction; +// use super::graph::{Edge, Graph, NodeId}; -/// Represents the current position of an entity traversing the graph. -/// -/// This enum allows for precise tracking of whether an entity is exactly at a node -/// or moving along an edge between two nodes. -#[derive(Debug, PartialEq, Clone, Copy)] -pub enum Position { - /// The traverser is located exactly at a node. - AtNode(NodeId), - /// The traverser is on an edge between two nodes. - BetweenNodes { - from: NodeId, - to: NodeId, - /// The floating-point distance traversed along the edge from the `from` node. - traversed: f32, - }, -} +// /// Manages an entity's movement through the graph. +// /// +// /// A `Traverser` encapsulates the state of an entity's position and direction, +// /// providing a way to advance along the graph's paths based on a given distance. +// /// It also handles direction changes, buffering the next intended direction. +// pub struct Traverser { +// /// The current position of the traverser in the graph. +// pub position: Position, +// /// The current direction of movement. +// pub direction: Direction, +// /// Buffered direction change with remaining frame count for timing. +// /// +// /// The `u8` value represents the number of frames remaining before +// /// the buffered direction expires. This allows for responsive controls +// /// by storing direction changes for a limited time. +// pub next_direction: Option<(Direction, u8)>, +// } -#[allow(dead_code)] -impl Position { - /// Returns `true` if the position is exactly at a node. - pub fn is_at_node(&self) -> bool { - matches!(self, Position::AtNode(_)) - } +// impl Traverser { +// /// Creates a new traverser starting at the given node ID. +// /// +// /// The traverser will immediately attempt to start moving in the initial direction. +// pub fn new(graph: &Graph, start_node: NodeId, initial_direction: Direction, can_traverse: &F) -> Self +// where +// F: Fn(Edge) -> bool, +// { +// let mut traverser = Traverser { +// position: Position::AtNode(start_node), +// direction: initial_direction, +// next_direction: Some((initial_direction, 1)), +// }; - /// Returns the `NodeId` of the current or most recently departed node. - #[allow(clippy::wrong_self_convention)] - pub fn from_node_id(&self) -> NodeId { - match self { - Position::AtNode(id) => *id, - Position::BetweenNodes { from, .. } => *from, - } - } +// // This will kickstart the traverser into motion +// if let Err(e) = traverser.advance(graph, 0.0, can_traverse) { +// error!("Traverser initialization error: {}", e); +// } - /// Returns the `NodeId` of the destination node, if currently on an edge. - #[allow(clippy::wrong_self_convention)] - pub fn to_node_id(&self) -> Option { - match self { - Position::AtNode(_) => None, - Position::BetweenNodes { to, .. } => Some(*to), - } - } +// traverser +// } - /// Returns `true` if the traverser is stopped at a node. - pub fn is_stopped(&self) -> bool { - matches!(self, Position::AtNode(_)) - } -} +// /// Sets the next direction for the traverser to take. +// /// +// /// The direction is buffered and will be applied at the next opportunity, +// /// typically when the traverser reaches a new node. This allows for responsive +// /// controls, as the new direction is stored for a limited time. +// pub fn set_next_direction(&mut self, new_direction: Direction) { +// if self.direction != new_direction { +// self.next_direction = Some((new_direction, 30)); +// } +// } -/// Manages an entity's movement through the graph. -/// -/// A `Traverser` encapsulates the state of an entity's position and direction, -/// providing a way to advance along the graph's paths based on a given distance. -/// It also handles direction changes, buffering the next intended direction. -pub struct Traverser { - /// The current position of the traverser in the graph. - pub position: Position, - /// The current direction of movement. - pub direction: Direction, - /// Buffered direction change with remaining frame count for timing. - /// - /// The `u8` value represents the number of frames remaining before - /// the buffered direction expires. This allows for responsive controls - /// by storing direction changes for a limited time. - pub next_direction: Option<(Direction, u8)>, -} +// /// Advances the traverser along the graph by a specified distance. +// /// +// /// This method updates the traverser's position based on its current state +// /// and the distance to travel. +// /// +// /// - If at a node, it checks for a buffered direction to start moving. +// /// - If between nodes, it moves along the current edge. +// /// - If it reaches a node, it attempts to transition to a new edge based on +// /// the buffered direction or by continuing straight. +// /// - If no valid move is possible, it stops at the node. +// /// +// /// Returns an error if the movement is invalid (e.g., trying to move in an impossible direction). +// pub fn advance(&mut self, graph: &Graph, distance: f32, can_traverse: &F) -> GameResult<()> +// where +// F: Fn(Edge) -> bool, +// { +// // Decrement the remaining frames for the next direction +// if let Some((direction, remaining)) = self.next_direction { +// if remaining > 0 { +// self.next_direction = Some((direction, remaining - 1)); +// } else { +// self.next_direction = None; +// } +// } -impl Traverser { - /// Creates a new traverser starting at the given node ID. - /// - /// The traverser will immediately attempt to start moving in the initial direction. - pub fn new(graph: &Graph, start_node: NodeId, initial_direction: Direction, can_traverse: &F) -> Self - where - F: Fn(Edge) -> bool, - { - let mut traverser = Traverser { - position: Position::AtNode(start_node), - direction: initial_direction, - next_direction: Some((initial_direction, 1)), - }; +// match self.position { +// Position::AtNode(node_id) => { +// // We're not moving, but a buffered direction is available. +// if let Some((next_direction, _)) = self.next_direction { +// if let Some(edge) = graph.find_edge_in_direction(node_id, next_direction) { +// if can_traverse(edge) { +// // Start moving in that direction +// self.position = Position::BetweenNodes { +// from: node_id, +// to: edge.target, +// traversed: distance.max(0.0), +// }; +// self.direction = next_direction; +// } else { +// return Err(crate::error::GameError::Entity(crate::error::EntityError::InvalidMovement( +// format!( +// "Cannot traverse edge from {} to {} in direction {:?}", +// node_id, edge.target, next_direction +// ), +// ))); +// } +// } else { +// return Err(crate::error::GameError::Entity(crate::error::EntityError::InvalidMovement( +// format!("No edge found in direction {:?} from node {}", next_direction, node_id), +// ))); +// } - // This will kickstart the traverser into motion - if let Err(e) = traverser.advance(graph, 0.0, can_traverse) { - error!("Traverser initialization error: {}", e); - } +// self.next_direction = None; // Consume the buffered direction regardless of whether we started moving with it +// } +// } +// Position::BetweenNodes { from, to, traversed } => { +// // There is no point in any of the next logic if we don't travel at all +// if distance <= 0.0 { +// return Ok(()); +// } - traverser - } +// let edge = graph.find_edge(from, to).ok_or_else(|| { +// crate::error::GameError::Entity(crate::error::EntityError::InvalidMovement(format!( +// "Inconsistent state: Traverser is on a non-existent edge from {} to {}.", +// from, to +// ))) +// })?; - /// Sets the next direction for the traverser to take. - /// - /// The direction is buffered and will be applied at the next opportunity, - /// typically when the traverser reaches a new node. This allows for responsive - /// controls, as the new direction is stored for a limited time. - pub fn set_next_direction(&mut self, new_direction: Direction) { - if self.direction != new_direction { - self.next_direction = Some((new_direction, 30)); - } - } +// let new_traversed = traversed + distance; - /// Advances the traverser along the graph by a specified distance. - /// - /// This method updates the traverser's position based on its current state - /// and the distance to travel. - /// - /// - If at a node, it checks for a buffered direction to start moving. - /// - If between nodes, it moves along the current edge. - /// - If it reaches a node, it attempts to transition to a new edge based on - /// the buffered direction or by continuing straight. - /// - If no valid move is possible, it stops at the node. - /// - /// Returns an error if the movement is invalid (e.g., trying to move in an impossible direction). - pub fn advance(&mut self, graph: &Graph, distance: f32, can_traverse: &F) -> GameResult<()> - where - F: Fn(Edge) -> bool, - { - // Decrement the remaining frames for the next direction - if let Some((direction, remaining)) = self.next_direction { - if remaining > 0 { - self.next_direction = Some((direction, remaining - 1)); - } else { - self.next_direction = None; - } - } +// if new_traversed < edge.distance { +// // Still on the same edge, just update the distance. +// self.position = Position::BetweenNodes { +// from, +// to, +// traversed: new_traversed, +// }; +// } else { +// let overflow = new_traversed - edge.distance; +// let mut moved = false; - match self.position { - Position::AtNode(node_id) => { - // We're not moving, but a buffered direction is available. - if let Some((next_direction, _)) = self.next_direction { - if let Some(edge) = graph.find_edge_in_direction(node_id, next_direction) { - if can_traverse(edge) { - // Start moving in that direction - self.position = Position::BetweenNodes { - from: node_id, - to: edge.target, - traversed: distance.max(0.0), - }; - self.direction = next_direction; - } else { - return Err(crate::error::GameError::Entity(crate::error::EntityError::InvalidMovement( - format!( - "Cannot traverse edge from {} to {} in direction {:?}", - node_id, edge.target, next_direction - ), - ))); - } - } else { - return Err(crate::error::GameError::Entity(crate::error::EntityError::InvalidMovement( - format!("No edge found in direction {:?} from node {}", next_direction, node_id), - ))); - } +// // If we buffered a direction, try to find an edge in that direction +// if let Some((next_dir, _)) = self.next_direction { +// if let Some(edge) = graph.find_edge_in_direction(to, next_dir) { +// if can_traverse(edge) { +// self.position = Position::BetweenNodes { +// from: to, +// to: edge.target, +// traversed: overflow, +// }; - self.next_direction = None; // Consume the buffered direction regardless of whether we started moving with it - } - } - Position::BetweenNodes { from, to, traversed } => { - // There is no point in any of the next logic if we don't travel at all - if distance <= 0.0 { - return Ok(()); - } +// self.direction = next_dir; // Remember our new direction +// self.next_direction = None; // Consume the buffered direction +// moved = true; +// } +// } +// } - let edge = graph.find_edge(from, to).ok_or_else(|| { - crate::error::GameError::Entity(crate::error::EntityError::InvalidMovement(format!( - "Inconsistent state: Traverser is on a non-existent edge from {} to {}.", - from, to - ))) - })?; +// // If we didn't move, try to continue in the current direction +// if !moved { +// if let Some(edge) = graph.find_edge_in_direction(to, self.direction) { +// if can_traverse(edge) { +// self.position = Position::BetweenNodes { +// from: to, +// to: edge.target, +// traversed: overflow, +// }; +// } else { +// self.position = Position::AtNode(to); +// self.next_direction = None; +// } +// } else { +// self.position = Position::AtNode(to); +// self.next_direction = None; +// } +// } +// } +// } +// } - let new_traversed = traversed + distance; - - if new_traversed < edge.distance { - // Still on the same edge, just update the distance. - self.position = Position::BetweenNodes { - from, - to, - traversed: new_traversed, - }; - } else { - let overflow = new_traversed - edge.distance; - let mut moved = false; - - // If we buffered a direction, try to find an edge in that direction - if let Some((next_dir, _)) = self.next_direction { - if let Some(edge) = graph.find_edge_in_direction(to, next_dir) { - if can_traverse(edge) { - self.position = Position::BetweenNodes { - from: to, - to: edge.target, - traversed: overflow, - }; - - self.direction = next_dir; // Remember our new direction - self.next_direction = None; // Consume the buffered direction - moved = true; - } - } - } - - // If we didn't move, try to continue in the current direction - if !moved { - if let Some(edge) = graph.find_edge_in_direction(to, self.direction) { - if can_traverse(edge) { - self.position = Position::BetweenNodes { - from: to, - to: edge.target, - traversed: overflow, - }; - } else { - self.position = Position::AtNode(to); - self.next_direction = None; - } - } else { - self.position = Position::AtNode(to); - self.next_direction = None; - } - } - } - } - } - - Ok(()) - } -} +// Ok(()) +// } +// } diff --git a/src/error.rs b/src/error.rs index 2a85b60..3c577d4 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,11 +5,13 @@ use std::io; +use bevy_ecs::event::Event; + /// Main error type for the Pac-Man game. /// /// This is the primary error type that should be used in public APIs. /// It can represent any error that can occur during game operation. -#[derive(thiserror::Error, Debug)] +#[derive(thiserror::Error, Debug, Event)] pub enum GameError { #[error("Asset error: {0}")] Asset(#[from] AssetError), diff --git a/src/game/events.rs b/src/game/events.rs index b0e5351..f8a41f0 100644 --- a/src/game/events.rs +++ b/src/game/events.rs @@ -1,6 +1,8 @@ +use bevy_ecs::event::Event; + use crate::input::commands::GameCommand; -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Event)] pub enum GameEvent { Command(GameCommand), } diff --git a/src/game/mod.rs b/src/game/mod.rs index d53467d..350e3ac 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -1,24 +1,37 @@ //! This module contains the main game logic and state. -use rand::{rngs::SmallRng, Rng, SeedableRng}; -use sdl2::pixels::Color; -use sdl2::render::{Canvas, Texture, TextureCreator}; -use sdl2::video::WindowContext; +include!(concat!(env!("OUT_DIR"), "/atlas_data.rs")); -use crate::entity::r#trait::Entity; -use crate::error::GameResult; +use crate::constants::CANVAS_SIZE; +use crate::ecs::render::{render_system, BackbufferResource, MapTextureResource}; +use crate::ecs::{DeltaTime, GlobalState, PlayerBundle, PlayerControlled, Position, Renderable, Velocity}; +use crate::entity::direction::Direction; +use crate::entity::{graph, traversal}; +use crate::error::{GameError, GameResult, TextureError}; +use crate::input::commands::GameCommand; +use crate::map::builder::Map; +use crate::texture::animated::AnimatedTexture; +use crate::texture::directional::DirectionalAnimatedTexture; +use crate::texture::sprite::Sprite; +use bevy_ecs::event::EventRegistry; +use bevy_ecs::observer::Trigger; +use bevy_ecs::schedule::IntoScheduleConfigs; +use bevy_ecs::system::{Commands, ResMut}; +use bevy_ecs::{schedule::Schedule, world::World}; +use sdl2::image::LoadTexture; +use sdl2::render::{Canvas, ScaleMode, Texture, TextureCreator}; +use sdl2::video::{Window, WindowContext}; +use sdl2::EventPump; -use crate::entity::{ - collision::{Collidable, CollisionSystem, EntityId}, - ghost::{Ghost, GhostType}, - pacman::Pacman, +use crate::asset::{get_asset_bytes, Asset}; +use crate::input::{handle_input, Bindings}; +use crate::map::render::MapRenderer; +use crate::{ + constants, + texture::sprite::{AtlasMapper, AtlasTile, SpriteAtlas}, }; -use crate::map::render::MapRenderer; -use crate::{constants, texture::sprite::SpriteAtlas}; - use self::events::GameEvent; -use self::state::GameState; pub mod events; pub mod state; @@ -28,361 +41,471 @@ pub mod state; /// It contains the game's state and logic, and is responsible for /// handling user input, updating the game state, and rendering the game. pub struct Game { - state: state::GameState, + pub world: World, + pub schedule: Schedule, } impl Game { - pub fn new(texture_creator: &'static TextureCreator) -> GameResult { - let state = GameState::new(texture_creator)?; + pub fn new( + canvas: &'static mut Canvas, + texture_creator: &'static mut TextureCreator, + event_pump: &'static mut EventPump, + ) -> GameResult { + let mut world = World::default(); + let mut schedule = Schedule::default(); - Ok(Game { state }) - } + EventRegistry::register_event::(&mut world); + EventRegistry::register_event::(&mut world); - pub fn post_event(&mut self, event: GameEvent) { - self.state.event_queue.push_back(event); - } + let mut backbuffer = texture_creator + .create_texture_target(None, CANVAS_SIZE.x, CANVAS_SIZE.y) + .map_err(|e| GameError::Sdl(e.to_string()))?; + backbuffer.set_scale_mode(ScaleMode::Nearest); - fn handle_command(&mut self, command: crate::input::commands::GameCommand) { - use crate::input::commands::GameCommand; - match command { - GameCommand::MovePlayer(direction) => { - self.state.pacman.set_next_direction(direction); + let mut map_texture = texture_creator + .create_texture_target(None, CANVAS_SIZE.x, CANVAS_SIZE.y) + .map_err(|e| GameError::Sdl(e.to_string()))?; + map_texture.set_scale_mode(ScaleMode::Nearest); + + // Load atlas and create map texture + let atlas_bytes = get_asset_bytes(Asset::Atlas)?; + let atlas_texture = texture_creator.load_texture_bytes(&atlas_bytes).map_err(|e| { + if e.to_string().contains("format") || e.to_string().contains("unsupported") { + GameError::Texture(crate::error::TextureError::InvalidFormat(format!( + "Unsupported texture format: {e}" + ))) + } else { + GameError::Texture(crate::error::TextureError::LoadFailed(e.to_string())) } - GameCommand::ToggleDebug => { - self.toggle_debug_mode(); - } - GameCommand::MuteAudio => { - let is_muted = self.state.audio.is_muted(); - self.state.audio.set_mute(!is_muted); - } - GameCommand::ResetLevel => { - if let Err(e) = self.reset_game_state() { - tracing::error!("Failed to reset game state: {}", e); + })?; + + let atlas_mapper = AtlasMapper { + frames: ATLAS_FRAMES.into_iter().map(|(k, v)| (k.to_string(), *v)).collect(), + }; + let mut atlas = SpriteAtlas::new(atlas_texture, atlas_mapper); + + // Create map tiles + let mut map_tiles = Vec::with_capacity(35); + for i in 0..35 { + let tile_name = format!("maze/tiles/{}.png", i); + let tile = atlas.get_tile(&tile_name).unwrap(); + map_tiles.push(tile); + } + + // Render map to texture + canvas + .with_texture_canvas(&mut map_texture, |map_canvas| { + MapRenderer::render_map(map_canvas, &mut atlas, &mut map_tiles); + }) + .map_err(|e| GameError::Sdl(e.to_string()))?; + + let map = Map::new(constants::RAW_BOARD)?; + let pacman_start_node = map.start_positions.pacman; + let player = PlayerBundle { + player: PlayerControlled, + position: Position::AtNode(pacman_start_node), + velocity: Velocity::default(), + sprite: Renderable { + sprite: Sprite::new( + SpriteAtlas::get_tile(&atlas, "pacman/full.png") + .ok_or_else(|| GameError::Texture(TextureError::AtlasTileNotFound("pacman/full.png".to_string())))?, + ), + layer: 0, + }, + }; + + let mut textures = [None, None, None, None]; + let mut stopped_textures = [None, None, None, None]; + + for direction in Direction::DIRECTIONS { + let moving_prefix = match direction { + Direction::Up => "pacman/up", + Direction::Down => "pacman/down", + Direction::Left => "pacman/left", + Direction::Right => "pacman/right", + }; + let moving_tiles = vec![ + SpriteAtlas::get_tile(&atlas, &format!("{moving_prefix}_a.png")) + .ok_or_else(|| GameError::Texture(TextureError::AtlasTileNotFound(format!("{moving_prefix}_a.png"))))?, + SpriteAtlas::get_tile(&atlas, &format!("{moving_prefix}_b.png")) + .ok_or_else(|| GameError::Texture(TextureError::AtlasTileNotFound(format!("{moving_prefix}_b.png"))))?, + SpriteAtlas::get_tile(&atlas, "pacman/full.png") + .ok_or_else(|| GameError::Texture(TextureError::AtlasTileNotFound("pacman/full.png".to_string())))?, + ]; + + let stopped_tiles = vec![SpriteAtlas::get_tile(&atlas, &format!("{moving_prefix}_b.png")) + .ok_or_else(|| GameError::Texture(TextureError::AtlasTileNotFound(format!("{moving_prefix}_b.png"))))?]; + + textures[direction.as_usize()] = Some(AnimatedTexture::new(moving_tiles, 0.08)?); + stopped_textures[direction.as_usize()] = Some(AnimatedTexture::new(stopped_tiles, 0.1)?); + } + + world.insert_non_send_resource(atlas); + world.insert_non_send_resource(event_pump); + world.insert_non_send_resource(canvas); + world.insert_non_send_resource(BackbufferResource(backbuffer)); + world.insert_non_send_resource(MapTextureResource(map_texture)); + + world.insert_resource(map); + world.insert_resource(GlobalState { exit: false }); + world.insert_resource(Bindings::default()); + world.insert_resource(DeltaTime(0f32)); + + world.add_observer(|event: Trigger, mut state: ResMut| match *event { + GameEvent::Command(command) => match command { + GameCommand::Exit => { + state.exit = true; } - } - GameCommand::TogglePause => { - self.state.paused = !self.state.paused; - } - GameCommand::Exit => {} - } + _ => {} + }, + }); + + schedule.add_systems((handle_input, render_system).chain()); + + // Spawn player + world.spawn(player); + + Ok(Game { world, schedule }) } - fn process_events(&mut self) { - while let Some(event) = self.state.event_queue.pop_front() { - match event { - GameEvent::Command(command) => self.handle_command(command), - } - } - } + // fn handle_command(&mut self, command: crate::input::commands::GameCommand) { + // use crate::input::commands::GameCommand; + // match command { + // GameCommand::MovePlayer(direction) => { + // self.state.pacman.set_next_direction(direction); + // } + // GameCommand::ToggleDebug => { + // self.toggle_debug_mode(); + // } + // GameCommand::MuteAudio => { + // let is_muted = self.state.audio.is_muted(); + // self.state.audio.set_mute(!is_muted); + // } + // GameCommand::ResetLevel => { + // if let Err(e) = self.reset_game_state() { + // tracing::error!("Failed to reset game state: {}", e); + // } + // } + // GameCommand::TogglePause => { + // self.state.paused = !self.state.paused; + // } + // GameCommand::Exit => {} + // } + // } - /// Resets the game state, randomizing ghost positions and resetting Pac-Man - fn reset_game_state(&mut self) -> GameResult<()> { - let pacman_start_node = self.state.map.start_positions.pacman; - self.state.pacman = Pacman::new(&self.state.map.graph, pacman_start_node, &self.state.atlas)?; + // fn process_events(&mut self) { + // while let Some(event) = self.state.event_queue.pop_front() { + // match event { + // GameEvent::Command(command) => self.handle_command(command), + // } + // } + // } - // Reset items - self.state.items = self.state.map.generate_items(&self.state.atlas)?; + // /// Resets the game state, randomizing ghost positions and resetting Pac-Man + // fn reset_game_state(&mut self) -> GameResult<()> { + // let pacman_start_node = self.state.map.start_positions.pacman; + // self.state.pacman = Pacman::new(&self.state.map.graph, pacman_start_node, &self.state.atlas)?; - // Randomize ghost positions - let ghost_types = [GhostType::Blinky, GhostType::Pinky, GhostType::Inky, GhostType::Clyde]; - let mut rng = SmallRng::from_os_rng(); + // // Reset items + // self.state.items = self.state.map.generate_items(&self.state.atlas)?; - for (i, ghost) in self.state.ghosts.iter_mut().enumerate() { - let random_node = rng.random_range(0..self.state.map.graph.node_count()); - *ghost = Ghost::new(&self.state.map.graph, random_node, ghost_types[i], &self.state.atlas)?; - } + // // Randomize ghost positions + // let ghost_types = [GhostType::Blinky, GhostType::Pinky, GhostType::Inky, GhostType::Clyde]; + // let mut rng = SmallRng::from_os_rng(); - // Reset collision system - self.state.collision_system = CollisionSystem::default(); + // for (i, ghost) in self.state.ghosts.iter_mut().enumerate() { + // let random_node = rng.random_range(0..self.state.map.graph.node_count()); + // *ghost = Ghost::new(&self.state.map.graph, random_node, ghost_types[i], &self.state.atlas)?; + // } - // Re-register Pac-Man - self.state.pacman_id = self.state.collision_system.register_entity(self.state.pacman.position()); + // // Reset collision system + // self.state.collision_system = CollisionSystem::default(); - // Re-register items - self.state.item_ids.clear(); - for item in &self.state.items { - let item_id = self.state.collision_system.register_entity(item.position()); - self.state.item_ids.push(item_id); - } + // // Re-register Pac-Man + // self.state.pacman_id = self.state.collision_system.register_entity(self.state.pacman.position()); - // Re-register ghosts - self.state.ghost_ids.clear(); - for ghost in &self.state.ghosts { - let ghost_id = self.state.collision_system.register_entity(ghost.position()); - self.state.ghost_ids.push(ghost_id); - } + // // Re-register items + // self.state.item_ids.clear(); + // for item in &self.state.items { + // let item_id = self.state.collision_system.register_entity(item.position()); + // self.state.item_ids.push(item_id); + // } - Ok(()) - } + // // Re-register ghosts + // self.state.ghost_ids.clear(); + // for ghost in &self.state.ghosts { + // let ghost_id = self.state.collision_system.register_entity(ghost.position()); + // self.state.ghost_ids.push(ghost_id); + // } + + // Ok(()) + // } /// Ticks the game state. /// /// Returns true if the game should exit. pub fn tick(&mut self, dt: f32) -> bool { - // Process any events that have been posted (such as unpausing) - self.process_events(); + self.world.insert_resource(DeltaTime(dt)); - // If the game is paused, we don't need to do anything beyond returning - if self.state.paused { - return false; - } + // Run all systems + self.schedule.run(&mut self.world); - self.state.pacman.tick(dt, &self.state.map.graph); + let state = self + .world + .get_resource::() + .expect("GlobalState could not be acquired"); - // Update all ghosts - for ghost in &mut self.state.ghosts { - ghost.tick(dt, &self.state.map.graph); - } + return state.exit; - // Update collision system positions - self.update_collision_positions(); + // // Process any events that have been posted (such as unpausing) + // self.process_events(); - // Check for collisions - self.check_collisions(); + // // If the game is paused, we don't need to do anything beyond returning + // if self.state.paused { + // return false; + // } - false + // self.schedule.run(&mut self.world); + + // self.state.pacman.tick(dt, &self.state.map.graph); + + // // Update all ghosts + // for ghost in &mut self.state.ghosts { + // ghost.tick(dt, &self.state.map.graph); + // } + + // // Update collision system positions + // self.update_collision_positions(); + + // // Check for collisions + // self.check_collisions(); } - /// Toggles the debug mode on and off. - /// - /// When debug mode is enabled, the game will render additional information - /// that is useful for debugging, such as the collision grid and entity paths. - pub fn toggle_debug_mode(&mut self) { - self.state.debug_mode = !self.state.debug_mode; - } + // /// Toggles the debug mode on and off. + // /// + // /// When debug mode is enabled, the game will render additional information + // /// that is useful for debugging, such as the collision grid and entity paths. + // pub fn toggle_debug_mode(&mut self) { + // self.state.debug_mode = !self.state.debug_mode; + // } - fn update_collision_positions(&mut self) { - // Update Pac-Man's position - self.state - .collision_system - .update_position(self.state.pacman_id, self.state.pacman.position()); + // fn update_collision_positions(&mut self) { + // // Update Pac-Man's position + // self.state + // .collision_system + // .update_position(self.state.pacman_id, self.state.pacman.position()); - // Update ghost positions - for (ghost, &ghost_id) in self.state.ghosts.iter().zip(&self.state.ghost_ids) { - self.state.collision_system.update_position(ghost_id, ghost.position()); - } - } + // // Update ghost positions + // for (ghost, &ghost_id) in self.state.ghosts.iter().zip(&self.state.ghost_ids) { + // self.state.collision_system.update_position(ghost_id, ghost.position()); + // } + // } - fn check_collisions(&mut self) { - // Check Pac-Man vs Items - let potential_collisions = self - .state - .collision_system - .potential_collisions(&self.state.pacman.position()); + // fn check_collisions(&mut self) { + // // Check Pac-Man vs Items + // let potential_collisions = self + // .state + // .collision_system + // .potential_collisions(&self.state.pacman.position()); - for entity_id in potential_collisions { - if entity_id != self.state.pacman_id { - // Check if this is an item collision - if let Some(item_index) = self.find_item_by_id(entity_id) { - let item = &mut self.state.items[item_index]; - if !item.is_collected() { - item.collect(); - self.state.score += item.get_score(); - self.state.audio.eat(); + // for entity_id in potential_collisions { + // if entity_id != self.state.pacman_id { + // // Check if this is an item collision + // if let Some(item_index) = self.find_item_by_id(entity_id) { + // let item = &mut self.state.items[item_index]; + // if !item.is_collected() { + // item.collect(); + // self.state.score += item.get_score(); + // self.state.audio.eat(); - // Handle energizer effects - if matches!(item.item_type, crate::entity::item::ItemType::Energizer) { - // TODO: Make ghosts frightened - tracing::info!("Energizer collected! Ghosts should become frightened."); - } - } - } + // // Handle energizer effects + // if matches!(item.item_type, crate::entity::item::ItemType::Energizer) { + // // TODO: Make ghosts frightened + // tracing::info!("Energizer collected! Ghosts should become frightened."); + // } + // } + // } - // Check if this is a ghost collision - if let Some(_ghost_index) = self.find_ghost_by_id(entity_id) { - // TODO: Handle Pac-Man being eaten by ghost - tracing::info!("Pac-Man collided with ghost!"); - } - } - } - } + // // Check if this is a ghost collision + // if let Some(_ghost_index) = self.find_ghost_by_id(entity_id) { + // // TODO: Handle Pac-Man being eaten by ghost + // tracing::info!("Pac-Man collided with ghost!"); + // } + // } + // } + // } - fn find_item_by_id(&self, entity_id: EntityId) -> Option { - self.state.item_ids.iter().position(|&id| id == entity_id) - } + // fn find_item_by_id(&self, entity_id: EntityId) -> Option { + // self.state.item_ids.iter().position(|&id| id == entity_id) + // } - fn find_ghost_by_id(&self, entity_id: EntityId) -> Option { - self.state.ghost_ids.iter().position(|&id| id == entity_id) - } + // fn find_ghost_by_id(&self, entity_id: EntityId) -> Option { + // self.state.ghost_ids.iter().position(|&id| id == entity_id) + // } - pub fn draw(&mut self, canvas: &mut Canvas, backbuffer: &mut Texture) -> GameResult<()> { - // Only render the map texture once and cache it - if !self.state.map_rendered { - let mut map_texture = self - .state - .texture_creator - .create_texture_target(None, constants::CANVAS_SIZE.x, constants::CANVAS_SIZE.y) - .map_err(|e| crate::error::GameError::Sdl(e.to_string()))?; + // pub fn draw(&mut self, canvas: &mut Canvas, backbuffer: &mut Texture) -> GameResult<()> { + // // Only render the map texture once and cache it + // if !self.state.map_rendered { + // let mut map_texture = self + // .state + // .texture_creator + // .create_texture_target(None, constants::CANVAS_SIZE.x, constants::CANVAS_SIZE.y) + // .map_err(|e| crate::error::GameError::Sdl(e.to_string()))?; - canvas - .with_texture_canvas(&mut map_texture, |map_canvas| { - let mut map_tiles = Vec::with_capacity(35); - for i in 0..35 { - let tile_name = format!("maze/tiles/{}.png", i); - let tile = SpriteAtlas::get_tile(&self.state.atlas, &tile_name).unwrap(); - map_tiles.push(tile); - } - MapRenderer::render_map(map_canvas, &mut self.state.atlas, &mut map_tiles); - }) - .map_err(|e| crate::error::GameError::Sdl(e.to_string()))?; - self.state.map_texture = Some(map_texture); - self.state.map_rendered = true; - } + // canvas + // .with_texture_canvas(&mut map_texture, |map_canvas| { + // let mut map_tiles = Vec::with_capacity(35); + // for i in 0..35 { + // let tile_name = format!("maze/tiles/{}.png", i); + // let tile = SpriteAtlas::get_tile(&self.state.atlas, &tile_name).unwrap(); + // map_tiles.push(tile); + // } + // MapRenderer::render_map(map_canvas, &mut self.state.atlas, &mut map_tiles); + // }) + // .map_err(|e| crate::error::GameError::Sdl(e.to_string()))?; + // self.state.map_texture = Some(map_texture); + // self.state.map_rendered = true; + // } - canvas - .with_texture_canvas(backbuffer, |canvas| { - canvas.set_draw_color(Color::BLACK); - canvas.clear(); - if let Some(ref map_texture) = self.state.map_texture { - canvas.copy(map_texture, None, None).unwrap(); - } + // canvas.set_draw_color(Color::BLACK); + // canvas.clear(); + // if let Some(ref map_texture) = self.state.map_texture { + // canvas.copy(map_texture, None, None).unwrap(); + // } - // Render all items - for item in &self.state.items { - if let Err(e) = item.render(canvas, &mut self.state.atlas, &self.state.map.graph) { - tracing::error!("Failed to render item: {}", e); - } - } + // // Render all items + // for item in &self.state.items { + // if let Err(e) = item.render(canvas, &mut self.state.atlas, &self.state.map.graph) { + // tracing::error!("Failed to render item: {}", e); + // } + // } - // Render all ghosts - for ghost in &self.state.ghosts { - if let Err(e) = ghost.render(canvas, &mut self.state.atlas, &self.state.map.graph) { - tracing::error!("Failed to render ghost: {}", e); - } - } + // // Render all ghosts + // for ghost in &self.state.ghosts { + // if let Err(e) = ghost.render(canvas, &mut self.state.atlas, &self.state.map.graph) { + // tracing::error!("Failed to render ghost: {}", e); + // } + // } - if let Err(e) = self.state.pacman.render(canvas, &mut self.state.atlas, &self.state.map.graph) { - tracing::error!("Failed to render pacman: {}", e); - } - }) - .map_err(|e| crate::error::GameError::Sdl(e.to_string()))?; + // if let Err(e) = self.state.pacman.render(canvas, &mut self.state.atlas, &self.state.map.graph) { + // tracing::error!("Failed to render pacman: {}", e); + // } - Ok(()) - } + // if self.state.debug_mode { + // if let Err(e) = + // self.state + // .map + // .debug_render_with_cursor(canvas, &mut self.state.text_texture, &mut self.state.atlas, cursor_pos) + // { + // tracing::error!("Failed to render debug cursor: {}", e); + // } + // self.render_pathfinding_debug(canvas)?; + // } + // self.draw_hud(canvas)?; + // canvas.present(); - pub fn present_backbuffer( - &mut self, - canvas: &mut Canvas, - backbuffer: &Texture, - cursor_pos: glam::Vec2, - ) -> GameResult<()> { - canvas - .copy(backbuffer, None, None) - .map_err(|e| crate::error::GameError::Sdl(e.to_string()))?; - if self.state.debug_mode { - if let Err(e) = - self.state - .map - .debug_render_with_cursor(canvas, &mut self.state.text_texture, &mut self.state.atlas, cursor_pos) - { - tracing::error!("Failed to render debug cursor: {}", e); - } - self.render_pathfinding_debug(canvas)?; - } - self.draw_hud(canvas)?; - canvas.present(); - Ok(()) - } + // Ok(()) + // } - /// Renders pathfinding debug lines from each ghost to Pac-Man. - /// - /// Each ghost's path is drawn in its respective color with a small offset - /// to prevent overlapping lines. - fn render_pathfinding_debug(&self, canvas: &mut Canvas) -> GameResult<()> { - let pacman_node = self.state.pacman.current_node_id(); + // /// Renders pathfinding debug lines from each ghost to Pac-Man. + // /// + // /// Each ghost's path is drawn in its respective color with a small offset + // /// to prevent overlapping lines. + // fn render_pathfinding_debug(&self, canvas: &mut Canvas) -> GameResult<()> { + // let pacman_node = self.state.pacman.current_node_id(); - for ghost in self.state.ghosts.iter() { - if let Ok(path) = ghost.calculate_path_to_target(&self.state.map.graph, pacman_node) { - if path.len() < 2 { - continue; // Skip if path is too short - } + // for ghost in self.state.ghosts.iter() { + // if let Ok(path) = ghost.calculate_path_to_target(&self.state.map.graph, pacman_node) { + // if path.len() < 2 { + // continue; // Skip if path is too short + // } - // Set the ghost's color - canvas.set_draw_color(ghost.debug_color()); + // // Set the ghost's color + // canvas.set_draw_color(ghost.debug_color()); - // Calculate offset based on ghost index to prevent overlapping lines - // let offset = (i as f32) * 2.0 - 3.0; // Offset range: -3.0 to 3.0 + // // Calculate offset based on ghost index to prevent overlapping lines + // // let offset = (i as f32) * 2.0 - 3.0; // Offset range: -3.0 to 3.0 - // Calculate a consistent offset direction for the entire path - // let first_node = self.map.graph.get_node(path[0]).unwrap(); - // let last_node = self.map.graph.get_node(path[path.len() - 1]).unwrap(); + // // Calculate a consistent offset direction for the entire path + // // let first_node = self.map.graph.get_node(path[0]).unwrap(); + // // let last_node = self.map.graph.get_node(path[path.len() - 1]).unwrap(); - // Use the overall direction from start to end to determine the perpendicular offset - let offset = match ghost.ghost_type { - GhostType::Blinky => glam::Vec2::new(0.25, 0.5), - GhostType::Pinky => glam::Vec2::new(-0.25, -0.25), - GhostType::Inky => glam::Vec2::new(0.5, -0.5), - GhostType::Clyde => glam::Vec2::new(-0.5, 0.25), - } * 5.0; + // // Use the overall direction from start to end to determine the perpendicular offset + // let offset = match ghost.ghost_type { + // GhostType::Blinky => glam::Vec2::new(0.25, 0.5), + // GhostType::Pinky => glam::Vec2::new(-0.25, -0.25), + // GhostType::Inky => glam::Vec2::new(0.5, -0.5), + // GhostType::Clyde => glam::Vec2::new(-0.5, 0.25), + // } * 5.0; - // Calculate offset positions for all nodes using the same perpendicular direction - let mut offset_positions = Vec::new(); - for &node_id in &path { - let node = self - .state - .map - .graph - .get_node(node_id) - .ok_or(crate::error::EntityError::NodeNotFound(node_id))?; - let pos = node.position + crate::constants::BOARD_PIXEL_OFFSET.as_vec2(); - offset_positions.push(pos + offset); - } + // // Calculate offset positions for all nodes using the same perpendicular direction + // let mut offset_positions = Vec::new(); + // for &node_id in &path { + // let node = self + // .state + // .map + // .graph + // .get_node(node_id) + // .ok_or(crate::error::EntityError::NodeNotFound(node_id))?; + // let pos = node.position + crate::constants::BOARD_PIXEL_OFFSET.as_vec2(); + // offset_positions.push(pos + offset); + // } - // Draw lines between the offset positions - for window in offset_positions.windows(2) { - if let (Some(from), Some(to)) = (window.first(), window.get(1)) { - // Skip if the distance is too far (used for preventing lines between tunnel portals) - if from.distance_squared(*to) > (crate::constants::CELL_SIZE * 16).pow(2) as f32 { - continue; - } + // // Draw lines between the offset positions + // for window in offset_positions.windows(2) { + // if let (Some(from), Some(to)) = (window.first(), window.get(1)) { + // // Skip if the distance is too far (used for preventing lines between tunnel portals) + // if from.distance_squared(*to) > (crate::constants::CELL_SIZE * 16).pow(2) as f32 { + // continue; + // } - // Draw the line - canvas - .draw_line((from.x as i32, from.y as i32), (to.x as i32, to.y as i32)) - .map_err(|e| crate::error::GameError::Sdl(e.to_string()))?; - } - } - } - } + // // Draw the line + // canvas + // .draw_line((from.x as i32, from.y as i32), (to.x as i32, to.y as i32)) + // .map_err(|e| crate::error::GameError::Sdl(e.to_string()))?; + // } + // } + // } + // } - Ok(()) - } + // Ok(()) + // } - fn draw_hud(&mut self, canvas: &mut Canvas) -> GameResult<()> { - let lives = 3; - let score_text = format!("{:02}", self.state.score); - let x_offset = 4; - let y_offset = 2; - let lives_offset = 3; - let score_offset = 7 - (score_text.len() as i32); - self.state.text_texture.set_scale(1.0); - if let Err(e) = self.state.text_texture.render( - canvas, - &mut self.state.atlas, - &format!("{lives}UP HIGH SCORE "), - glam::UVec2::new(8 * lives_offset as u32 + x_offset, y_offset), - ) { - tracing::error!("Failed to render HUD text: {}", e); - } - if let Err(e) = self.state.text_texture.render( - canvas, - &mut self.state.atlas, - &score_text, - glam::UVec2::new(8 * score_offset as u32 + x_offset, 8 + y_offset), - ) { - tracing::error!("Failed to render score text: {}", e); - } + // fn draw_hud(&mut self, canvas: &mut Canvas) -> GameResult<()> { + // let lives = 3; + // let score_text = format!("{:02}", self.state.score); + // let x_offset = 4; + // let y_offset = 2; + // let lives_offset = 3; + // let score_offset = 7 - (score_text.len() as i32); + // self.state.text_texture.set_scale(1.0); + // if let Err(e) = self.state.text_texture.render( + // canvas, + // &mut self.state.atlas, + // &format!("{lives}UP HIGH SCORE "), + // glam::UVec2::new(8 * lives_offset as u32 + x_offset, y_offset), + // ) { + // tracing::error!("Failed to render HUD text: {}", e); + // } + // if let Err(e) = self.state.text_texture.render( + // canvas, + // &mut self.state.atlas, + // &score_text, + // glam::UVec2::new(8 * score_offset as u32 + x_offset, 8 + y_offset), + // ) { + // tracing::error!("Failed to render score text: {}", e); + // } - // Display FPS information in top-left corner - // let fps_text = format!("FPS: {:.1} (1s) / {:.1} (10s)", self.fps_1s, self.fps_10s); - // self.render_text_on( - // canvas, - // &*texture_creator, - // &fps_text, - // IVec2::new(10, 10), - // Color::RGB(255, 255, 0), // Yellow color for FPS display - // ); + // // Display FPS information in top-left corner + // // let fps_text = format!("FPS: {:.1} (1s) / {:.1} (10s)", self.fps_1s, self.fps_10s); + // // self.render_text_on( + // // canvas, + // // &*texture_creator, + // // &fps_text, + // // IVec2::new(10, 10), + // // Color::RGB(255, 255, 0), // Yellow color for FPS display + // // ); - Ok(()) - } + // Ok(()) + // } } diff --git a/src/game/state.rs b/src/game/state.rs index 9c9da59..14013a5 100644 --- a/src/game/state.rs +++ b/src/game/state.rs @@ -1,153 +1,153 @@ -use std::collections::VecDeque; +// use std::collections::VecDeque; -use sdl2::{ - image::LoadTexture, - render::{Texture, TextureCreator}, - video::WindowContext, -}; -use smallvec::SmallVec; +// use sdl2::{ +// image::LoadTexture, +// render::{Texture, TextureCreator}, +// video::WindowContext, +// }; +// use smallvec::SmallVec; -use crate::{ - asset::{get_asset_bytes, Asset}, - audio::Audio, - constants::RAW_BOARD, - entity::{ - collision::{Collidable, CollisionSystem, EntityId}, - ghost::{Ghost, GhostType}, - item::Item, - pacman::Pacman, - }, - error::{GameError, GameResult, TextureError}, - game::events::GameEvent, - map::builder::Map, - texture::{ - sprite::{AtlasMapper, SpriteAtlas}, - text::TextTexture, - }, -}; +// use crate::{ +// asset::{get_asset_bytes, Asset}, +// audio::Audio, +// constants::RAW_BOARD, +// entity::{ +// collision::{Collidable, CollisionSystem, EntityId}, +// ghost::{Ghost, GhostType}, +// item::Item, +// pacman::Pacman, +// }, +// error::{GameError, GameResult, TextureError}, +// game::events::GameEvent, +// map::builder::Map, +// texture::{ +// sprite::{AtlasMapper, SpriteAtlas}, +// text::TextTexture, +// }, +// }; -include!(concat!(env!("OUT_DIR"), "/atlas_data.rs")); +// include!(concat!(env!("OUT_DIR"), "/atlas_data.rs")); -/// The `GameState` struct holds all the essential data for the game. -/// -/// This includes the score, map, entities (Pac-Man, ghosts, items), -/// collision system, and rendering resources. By centralizing the game's state, -/// we can cleanly separate it from the game's logic, making it easier to manage -/// and reason about. -pub struct GameState { - pub paused: bool, +// /// The `GameState` struct holds all the essential data for the game. +// /// +// /// This includes the score, map, entities (Pac-Man, ghosts, items), +// /// collision system, and rendering resources. By centralizing the game's state, +// /// we can cleanly separate it from the game's logic, making it easier to manage +// /// and reason about. +// pub struct GameState { +// pub paused: bool, - pub score: u32, - pub map: Map, - pub pacman: Pacman, - pub pacman_id: EntityId, - pub ghosts: SmallVec<[Ghost; 4]>, - pub ghost_ids: SmallVec<[EntityId; 4]>, - pub items: Vec, - pub item_ids: Vec, - pub debug_mode: bool, - pub event_queue: VecDeque, +// pub score: u32, +// pub map: Map, +// pub pacman: Pacman, +// pub pacman_id: EntityId, +// pub ghosts: SmallVec<[Ghost; 4]>, +// pub ghost_ids: SmallVec<[EntityId; 4]>, +// pub items: Vec, +// pub item_ids: Vec, +// pub debug_mode: bool, +// pub event_queue: VecDeque, - // Collision system - pub(crate) collision_system: CollisionSystem, +// // Collision system +// pub(crate) collision_system: CollisionSystem, - // Rendering resources - pub(crate) atlas: SpriteAtlas, - pub(crate) text_texture: TextTexture, +// // Rendering resources +// pub(crate) atlas: SpriteAtlas, +// pub(crate) text_texture: TextTexture, - // Audio - pub audio: Audio, +// // Audio +// pub audio: Audio, - // Map texture pre-rendering - pub(crate) map_texture: Option>, - pub(crate) map_rendered: bool, - pub(crate) texture_creator: &'static TextureCreator, -} +// // Map texture pre-rendering +// pub(crate) map_texture: Option>, +// pub(crate) map_rendered: bool, +// pub(crate) texture_creator: &'static TextureCreator, +// } -impl GameState { - /// Creates a new `GameState` by initializing all the game's data. - /// - /// This function sets up the map, Pac-Man, ghosts, items, collision system, - /// and all rendering resources required to start the game. It returns a `GameResult` - /// to handle any potential errors during initialization. - pub fn new(texture_creator: &'static TextureCreator) -> GameResult { - let map = Map::new(RAW_BOARD)?; +// impl GameState { +// /// Creates a new `GameState` by initializing all the game's data. +// /// +// /// This function sets up the map, Pac-Man, ghosts, items, collision system, +// /// and all rendering resources required to start the game. It returns a `GameResult` +// /// to handle any potential errors during initialization. +// pub fn new(texture_creator: &'static TextureCreator) -> GameResult { +// let map = Map::new(RAW_BOARD)?; - let start_node = map.start_positions.pacman; +// let start_node = map.start_positions.pacman; - let atlas_bytes = get_asset_bytes(Asset::Atlas)?; - let atlas_texture = texture_creator.load_texture_bytes(&atlas_bytes).map_err(|e| { - if e.to_string().contains("format") || e.to_string().contains("unsupported") { - GameError::Texture(TextureError::InvalidFormat(format!("Unsupported texture format: {e}"))) - } else { - GameError::Texture(TextureError::LoadFailed(e.to_string())) - } - })?; +// let atlas_bytes = get_asset_bytes(Asset::Atlas)?; +// let atlas_texture = texture_creator.load_texture_bytes(&atlas_bytes).map_err(|e| { +// if e.to_string().contains("format") || e.to_string().contains("unsupported") { +// GameError::Texture(TextureError::InvalidFormat(format!("Unsupported texture format: {e}"))) +// } else { +// GameError::Texture(TextureError::LoadFailed(e.to_string())) +// } +// })?; - let atlas_mapper = AtlasMapper { - frames: ATLAS_FRAMES.into_iter().map(|(k, v)| (k.to_string(), *v)).collect(), - }; - let atlas = SpriteAtlas::new(atlas_texture, atlas_mapper); +// let atlas_mapper = AtlasMapper { +// frames: ATLAS_FRAMES.into_iter().map(|(k, v)| (k.to_string(), *v)).collect(), +// }; +// let atlas = SpriteAtlas::new(atlas_texture, atlas_mapper); - let text_texture = TextTexture::new(1.0); - let audio = Audio::new(); - let pacman = Pacman::new(&map.graph, start_node, &atlas)?; +// let text_texture = TextTexture::new(1.0); +// let audio = Audio::new(); +// let pacman = Pacman::new(&map.graph, start_node, &atlas)?; - // Generate items (pellets and energizers) - let items = map.generate_items(&atlas)?; +// // Generate items (pellets and energizers) +// let items = map.generate_items(&atlas)?; - // Initialize collision system - let mut collision_system = CollisionSystem::default(); +// // Initialize collision system +// let mut collision_system = CollisionSystem::default(); - // Register Pac-Man - let pacman_id = collision_system.register_entity(pacman.position()); +// // Register Pac-Man +// let pacman_id = collision_system.register_entity(pacman.position()); - // Register items - let item_ids = items - .iter() - .map(|item| collision_system.register_entity(item.position())) - .collect(); +// // Register items +// let item_ids = items +// .iter() +// .map(|item| collision_system.register_entity(item.position())) +// .collect(); - // Create and register ghosts - let ghosts = [GhostType::Blinky, GhostType::Pinky, GhostType::Inky, GhostType::Clyde] - .iter() - .zip( - [ - map.start_positions.blinky, - map.start_positions.pinky, - map.start_positions.inky, - map.start_positions.clyde, - ] - .iter(), - ) - .map(|(ghost_type, start_node)| Ghost::new(&map.graph, *start_node, *ghost_type, &atlas)) - .collect::>>()?; +// // Create and register ghosts +// let ghosts = [GhostType::Blinky, GhostType::Pinky, GhostType::Inky, GhostType::Clyde] +// .iter() +// .zip( +// [ +// map.start_positions.blinky, +// map.start_positions.pinky, +// map.start_positions.inky, +// map.start_positions.clyde, +// ] +// .iter(), +// ) +// .map(|(ghost_type, start_node)| Ghost::new(&map.graph, *start_node, *ghost_type, &atlas)) +// .collect::>>()?; - // Register ghosts - let ghost_ids = ghosts - .iter() - .map(|ghost| collision_system.register_entity(ghost.position())) - .collect(); +// // Register ghosts +// let ghost_ids = ghosts +// .iter() +// .map(|ghost| collision_system.register_entity(ghost.position())) +// .collect(); - Ok(Self { - paused: false, - map, - atlas, - pacman, - pacman_id, - ghosts, - ghost_ids, - items, - item_ids, - text_texture, - audio, - score: 0, - debug_mode: false, - collision_system, - map_texture: None, - map_rendered: false, - texture_creator, - event_queue: VecDeque::new(), - }) - } -} +// Ok(Self { +// paused: false, +// map, +// atlas, +// pacman, +// pacman_id, +// ghosts, +// ghost_ids, +// items, +// item_ids, +// text_texture, +// audio, +// score: 0, +// debug_mode: false, +// collision_system, +// map_texture: None, +// map_rendered: false, +// texture_creator, +// event_queue: VecDeque::new(), +// }) +// } +// } diff --git a/src/input/commands.rs b/src/input/commands.rs index d125a0c..81f4757 100644 --- a/src/input/commands.rs +++ b/src/input/commands.rs @@ -3,9 +3,9 @@ use crate::entity::direction::Direction; #[derive(Debug, Clone, Copy)] pub enum GameCommand { MovePlayer(Direction), + Exit, TogglePause, ToggleDebug, MuteAudio, ResetLevel, - Exit, } diff --git a/src/input/mod.rs b/src/input/mod.rs index 5924876..91c414c 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -1,18 +1,22 @@ use std::collections::HashMap; -use sdl2::{event::Event, keyboard::Keycode}; +use bevy_ecs::{ + resource::Resource, + system::{Commands, NonSendMut, Res}, +}; +use sdl2::{event::Event, keyboard::Keycode, EventPump}; -use crate::{entity::direction::Direction, input::commands::GameCommand}; +use crate::{entity::direction::Direction, game::events::GameEvent, input::commands::GameCommand}; pub mod commands; -#[derive(Debug, Clone, Default)] -pub struct InputSystem { +#[derive(Debug, Clone, Resource)] +pub struct Bindings { key_bindings: HashMap, } -impl InputSystem { - pub fn new() -> Self { +impl Default for Bindings { + fn default() -> Self { let mut key_bindings = HashMap::new(); // Player movement @@ -35,13 +39,22 @@ impl InputSystem { Self { key_bindings } } +} - /// Handles an event and returns a command if one is bound to the event. - pub fn handle_event(&self, event: &Event) -> Option { +pub fn handle_input(bindings: Res, mut commands: Commands, mut pump: NonSendMut<&'static mut EventPump>) { + for event in pump.poll_iter() { match event { - Event::Quit { .. } => Some(GameCommand::Exit), - Event::KeyDown { keycode: Some(key), .. } => self.key_bindings.get(key).copied(), - _ => None, + Event::Quit { .. } => { + commands.trigger(GameEvent::Command(GameCommand::Exit)); + } + Event::KeyDown { keycode: Some(key), .. } => { + let command = bindings.key_bindings.get(&key).copied(); + if let Some(command) = command { + tracing::info!("triggering command: {:?}", command); + commands.trigger(GameEvent::Command(command)); + } + } + _ => {} } } } diff --git a/src/lib.rs b/src/lib.rs index d9374f7..a90eb5d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ pub mod app; pub mod asset; pub mod audio; pub mod constants; +pub mod ecs; pub mod entity; pub mod error; pub mod game; diff --git a/src/main.rs b/src/main.rs index f293b89..2117321 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ mod asset; mod audio; mod constants; +mod ecs; mod entity; mod error; mod game; diff --git a/src/map/builder.rs b/src/map/builder.rs index bf0b361..2a23e3d 100644 --- a/src/map/builder.rs +++ b/src/map/builder.rs @@ -1,12 +1,13 @@ //! Map construction and building functionality. use crate::constants::{MapTile, BOARD_CELL_SIZE, CELL_SIZE, RAW_BOARD}; +use crate::ecs::NodeId; use crate::entity::direction::Direction; -use crate::entity::graph::{EdgePermissions, Graph, Node, NodeId}; -use crate::entity::item::{Item, ItemType}; +use crate::entity::graph::{EdgePermissions, Graph, Node}; use crate::map::parser::MapTileParser; use crate::map::render::MapRenderer; use crate::texture::sprite::{Sprite, SpriteAtlas}; +use bevy_ecs::resource::Resource; use glam::{IVec2, Vec2}; use sdl2::render::{Canvas, RenderTarget}; use std::collections::{HashMap, VecDeque}; @@ -24,6 +25,7 @@ pub struct NodePositions { } /// The main map structure containing the game board and navigation graph. +#[derive(Resource)] pub struct Map { /// The node map for entity movement. pub graph: Graph, @@ -155,42 +157,42 @@ impl Map { } /// Generates Item entities for pellets and energizers from the parsed map. - pub fn generate_items(&self, atlas: &SpriteAtlas) -> GameResult> { - // Pre-load sprites to avoid repeated texture lookups - let pellet_sprite = SpriteAtlas::get_tile(atlas, "maze/pellet.png") - .ok_or_else(|| MapError::InvalidConfig("Pellet texture not found".to_string()))?; - let energizer_sprite = SpriteAtlas::get_tile(atlas, "maze/energizer.png") - .ok_or_else(|| MapError::InvalidConfig("Energizer texture not found".to_string()))?; + // pub fn generate_items(&self, atlas: &SpriteAtlas) -> GameResult> { + // // Pre-load sprites to avoid repeated texture lookups + // let pellet_sprite = SpriteAtlas::get_tile(atlas, "maze/pellet.png") + // .ok_or_else(|| MapError::InvalidConfig("Pellet texture not found".to_string()))?; + // let energizer_sprite = SpriteAtlas::get_tile(atlas, "maze/energizer.png") + // .ok_or_else(|| MapError::InvalidConfig("Energizer texture not found".to_string()))?; - // Pre-allocate with estimated capacity (typical Pac-Man maps have ~240 pellets + 4 energizers) - let mut items = Vec::with_capacity(250); + // // Pre-allocate with estimated capacity (typical Pac-Man maps have ~240 pellets + 4 energizers) + // let mut items = Vec::with_capacity(250); - // Parse the raw board once - let parsed_map = MapTileParser::parse_board(RAW_BOARD)?; - let map = parsed_map.tiles; + // // Parse the raw board once + // let parsed_map = MapTileParser::parse_board(RAW_BOARD)?; + // let map = parsed_map.tiles; - // Iterate through the map and collect items more efficiently - for (x, row) in map.iter().enumerate() { - for (y, tile) in row.iter().enumerate() { - match tile { - MapTile::Pellet | MapTile::PowerPellet => { - let grid_pos = IVec2::new(x as i32, y as i32); - if let Some(&node_id) = self.grid_to_node.get(&grid_pos) { - let (item_type, sprite) = match tile { - MapTile::Pellet => (ItemType::Pellet, Sprite::new(pellet_sprite)), - MapTile::PowerPellet => (ItemType::Energizer, Sprite::new(energizer_sprite)), - _ => unreachable!(), // We already filtered for these types - }; - items.push(Item::new(node_id, item_type, sprite)); - } - } - _ => {} - } - } - } + // // Iterate through the map and collect items more efficiently + // for (x, row) in map.iter().enumerate() { + // for (y, tile) in row.iter().enumerate() { + // match tile { + // MapTile::Pellet | MapTile::PowerPellet => { + // let grid_pos = IVec2::new(x as i32, y as i32); + // if let Some(&node_id) = self.grid_to_node.get(&grid_pos) { + // let (item_type, sprite) = match tile { + // MapTile::Pellet => (ItemType::Pellet, Sprite::new(pellet_sprite)), + // MapTile::PowerPellet => (ItemType::Energizer, Sprite::new(energizer_sprite)), + // _ => unreachable!(), // We already filtered for these types + // }; + // items.push(Item::new(node_id, item_type, sprite)); + // } + // } + // _ => {} + // } + // } + // } - Ok(items) - } + // Ok(items) + // } /// Renders a debug visualization with cursor-based highlighting. /// diff --git a/src/texture/sprite.rs b/src/texture/sprite.rs index b271c9e..41ebb69 100644 --- a/src/texture/sprite.rs +++ b/src/texture/sprite.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use bevy_ecs::resource::Resource; use glam::U16Vec2; use sdl2::pixels::Color; use sdl2::rect::Rect;