From 0a46f6486622e8ff6857b240d02c3cd287a4be2f Mon Sep 17 00:00:00 2001 From: Xevion Date: Tue, 22 Jul 2025 13:18:02 -0500 Subject: [PATCH] feat: pathfinding, ghost and blinky state, update dependencies --- Cargo.lock | 369 +++++++++++++++++++++++++++++++++++++------ Cargo.toml | 16 +- src/animation.rs | 149 ++++++++--------- src/constants.rs | 2 +- src/game.rs | 69 ++++++-- src/ghost.rs | 367 ++++++++++++++++++++++++++++++++++++++++++ src/ghosts/blinky.rs | 88 +++++++++++ src/ghosts/mod.rs | 3 + src/main.rs | 2 + src/pacman.rs | 47 +++--- 10 files changed, 942 insertions(+), 170 deletions(-) create mode 100644 src/ghost.rs create mode 100644 src/ghosts/blinky.rs create mode 100644 src/ghosts/mod.rs diff --git a/Cargo.lock b/Cargo.lock index b9af997..896e0c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,22 +1,34 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aho-corasick" -version = "1.0.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + [[package]] name = "c_vec" version = "2.0.0" @@ -30,16 +42,71 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "lazy_static" -version = "1.4.0" +name = "deprecate-until" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "7a3767f826efbbe5a5ae093920b58b43b01734202be697e1354914e862e8e704" +dependencies = [ + "proc-macro2", + "quote", + "semver", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "integer-sqrt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" +dependencies = [ + "num-traits", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "log" @@ -58,9 +125,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.5.0" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "nu-ansi-term" @@ -72,6 +139,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.18.0" @@ -90,6 +166,8 @@ version = "0.1.0" dependencies = [ "lazy_static", "libc", + "pathfinding", + "rand", "sdl2", "spin_sleep", "tracing", @@ -98,6 +176,20 @@ dependencies = [ "winapi", ] +[[package]] +name = "pathfinding" +version = "4.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ac35caa284c08f3721fb33c2741b5f763decaf42d080c8a6a722154347017e" +dependencies = [ + "deprecate-until", + "indexmap", + "integer-sqrt", + "num-traits", + "rustc-hash", + "thiserror", +] + [[package]] name = "pin-project-lite" version = "0.2.13" @@ -105,33 +197,77 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] -name = "proc-macro2" -version = "1.0.66" +name = "ppv-lite86" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] -name = "regex" -version = "1.9.1" +name = "r-efi" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.3.3", - "regex-syntax 0.7.4", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", ] [[package]] @@ -145,13 +281,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.3" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.4", + "regex-syntax 0.8.5", ] [[package]] @@ -162,9 +298,15 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "sdl2" @@ -172,7 +314,7 @@ version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d42407afc6a8ab67e36f92e80b8ba34cbdc55aaeed05249efe9a2e8d0e9feef" dependencies = [ - "bitflags", + "bitflags 1.3.2", "c_vec", "lazy_static", "libc", @@ -191,6 +333,12 @@ dependencies = [ "version-compare", ] +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + [[package]] name = "sharded-slab" version = "0.1.4" @@ -208,25 +356,44 @@ checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" [[package]] name = "spin_sleep" -version = "1.1.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cafa7900db085f4354dbc7025e25d7a839a14360ea13b5fc4fd717f2d3b23134" +checksum = "14ac0e4b54d028c2000a13895bcd84cd02a1d63c4f78e08e4ec5ec8f53efd4b9" dependencies = [ - "once_cell", - "winapi", + "windows-sys", ] [[package]] name = "syn" -version = "2.0.31" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.1.7" @@ -239,11 +406,10 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ - "cfg-if", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -251,9 +417,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.26" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", @@ -262,9 +428,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.31" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", @@ -272,9 +438,9 @@ dependencies = [ [[package]] name = "tracing-error" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" +checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" dependencies = [ "tracing", "tracing-subscriber", @@ -282,20 +448,20 @@ dependencies = [ [[package]] name = "tracing-log" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ - "lazy_static", "log", + "once_cell", "tracing-core", ] [[package]] name = "tracing-subscriber" -version = "0.3.17" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "matchers", "nu-ansi-term", @@ -333,6 +499,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "winapi" version = "0.3.9" @@ -354,3 +529,105 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +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", +] + +[[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.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[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.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[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.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[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.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 83d775d..32fe497 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,12 +6,18 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -lazy_static = "1.4.0" -spin_sleep = "1.1.1" -tracing = { version = "0.1.37", features = ["max_level_debug", "release_max_level_debug"]} +tracing = { version = "0.1.40", features = ["max_level_debug", "release_max_level_debug"]} tracing-error = "0.2.0" tracing-subscriber = {version = "0.3.17", features = ["env-filter"]} -winapi = { version = "0.3", features = ["consoleapi", "fileapi", "handleapi", "processenv", "winbase", "wincon", "winnt", "winuser", "windef", "minwindef"] } +lazy_static = "1.5.0" +sdl2 = { version = "0.38.0", features = ["image", "ttf"] } +spin_sleep = "1.3.2" +rand = "0.9.2" +pathfinding = "4.14" + +[target.'cfg(target_os = "windows")'.dependencies.winapi] +version = "0.3" +features = ["consoleapi", "fileapi", "handleapi", "processenv", "winbase", "wincon", "winnt", "winuser", "windef", "minwindef"] [target.'cfg(target_os = "emscripten")'.dependencies.sdl2] @@ -34,4 +40,4 @@ x86_64-pc-windows-msvc = { triplet = "x64-windows-static-md" } stable-x86_64-unknown-linux-gnu = { triplet = "x86_64-unknown-linux-gnu" } [target.'cfg(target_os = "emscripten")'.dependencies] -libc = "0.2.16" \ No newline at end of file +libc = "0.2.16" diff --git a/src/animation.rs b/src/animation.rs index 9e6bdff..57ad1c8 100644 --- a/src/animation.rs +++ b/src/animation.rs @@ -9,41 +9,29 @@ use crate::direction::Direction; /// An animated texture, which is a texture that is rendered as a series of /// frames. +/// +/// This struct manages the state of an animated texture, including the current +/// frame and the number of frames in the animation. pub struct AnimatedTexture<'a> { + // Parameters raw_texture: Texture<'a>, - /// The current tick of the animation. - ticker: u32, - /// Whether the animation is currently playing in reverse. - reversed: bool, - /// The offset of the texture, in pixels. offset: (i32, i32), - /// The number of ticks per frame. ticks_per_frame: u32, - /// The number of frames in the animation. frame_count: u32, - /// The width of each frame, in pixels. - frame_width: u32, - /// The height of each frame, in pixels. - frame_height: u32, + width: u32, + height: u32, + // State + ticker: u32, + reversed: bool, } impl<'a> AnimatedTexture<'a> { - /// Creates a new `AnimatedTexture`. - /// - /// # Arguments - /// - /// * `texture` - The texture to animate. - /// * `ticks_per_frame` - The number of ticks to display each frame for. - /// * `frame_count` - The number of frames in the animation. - /// * `frame_width` - The width of each frame. - /// * `frame_height` - The height of each frame. - /// * `offset` - The offset of the texture, in pixels. pub fn new( texture: Texture<'a>, ticks_per_frame: u32, frame_count: u32, - frame_width: u32, - frame_height: u32, + width: u32, + height: u32, offset: Option<(i32, i32)>, ) -> Self { AnimatedTexture { @@ -52,20 +40,26 @@ impl<'a> AnimatedTexture<'a> { reversed: false, ticks_per_frame, frame_count, - frame_width, - frame_height, + width, + height, offset: offset.unwrap_or((0, 0)), } } - /// Returns the current frame number. fn current_frame(&self) -> u32 { self.ticker / self.ticks_per_frame } /// Advances the animation by one tick. /// - /// The animation will play forwards, then backwards, then forwards, and so on. + /// This method updates the internal ticker that tracks the current frame + /// of the animation. The animation automatically reverses direction when + /// it reaches the end, creating a ping-pong effect. + /// + /// When `reversed` is `false`, the ticker increments until it reaches + /// the total number of ticks for all frames, then reverses direction. + /// When `reversed` is `true`, the ticker decrements until it reaches 0, + /// then reverses direction again. pub fn tick(&mut self) { if self.reversed { self.ticker -= 1; @@ -82,27 +76,33 @@ impl<'a> AnimatedTexture<'a> { } } - /// Calculates the source rectangle for the given frame. - fn get_frame_rect(&self, frame: u32) -> Rect { - if frame >= self.frame_count { - panic!("Frame {} is out of bounds for this texture", frame); - } - - Rect::new( - frame as i32 * self.frame_width as i32, - 0, - self.frame_width, - self.frame_height, - ) - } - - /// Renders the animation to the canvas. + /// Gets the source rectangle for a specific frame of the animated texture. + /// + /// This method calculates the position and dimensions of a frame within the + /// texture atlas. Frames are arranged horizontally in a single row, so the + /// rectangle's x-coordinate is calculated by multiplying the frame index + /// by the frame width. /// /// # Arguments /// - /// * `canvas` - The canvas to render to. - /// * `position` - The position to render the animation at. - /// * `direction` - The direction the animation is facing. + /// * `frame` - The frame index to get the rectangle for (0-based) + /// + /// # Returns + /// + /// A `Rect` representing the source rectangle for the specified frame + fn get_frame_rect(&self, frame: u32) -> Option { + if frame >= self.frame_count { + return None; + } + + Some(Rect::new( + frame as i32 * self.width as i32, + 0, + self.width, + self.height, + )) + } + pub fn render( &mut self, canvas: &mut Canvas, @@ -113,41 +113,22 @@ impl<'a> AnimatedTexture<'a> { self.tick(); } - /// Renders the animation to the canvas, but only ticks the animation until - /// the given frame is reached. + /// Renders a specific frame of the animated texture to the canvas. + /// + /// This method renders a static frame without advancing the animation ticker. + /// It's useful for displaying a specific frame, such as when an entity is stopped + /// or when you want to manually control which frame is displayed. /// /// # Arguments /// - /// * `canvas` - The canvas to render to. - /// * `position` - The position to render the animation at. - /// * `direction` - The direction the animation is facing. - /// * `frame` - The frame to render until. - pub fn render_until( - &mut self, - canvas: &mut Canvas, - position: (i32, i32), - direction: Direction, - frame: u32, - ) { - // TODO: If the frame we're targeting is in the opposite direction (due - // to self.reverse), we should pre-emptively reverse. This would require - // a more complex ticking mechanism. - let current = self.current_frame(); - self.render_static(canvas, position, direction, Some(current)); - - if frame != current { - self.tick(); - } - } - - /// Renders a specific frame of the animation. + /// * `canvas` - The SDL canvas to render to + /// * `position` - The pixel position where the texture should be rendered + /// * `direction` - The direction to rotate the texture based on entity facing + /// * `frame` - Optional specific frame to render. If `None`, uses the current frame /// - /// # Arguments + /// # Panics /// - /// * `canvas` - The canvas to render to. - /// * `position` - The position to render the animation at. - /// * `direction` - The direction the animation is facing. - /// * `frame` - The frame to render. If `None`, the current frame is used. + /// Panics if the specified frame is out of bounds for this texture. pub fn render_static( &mut self, canvas: &mut Canvas, @@ -155,19 +136,20 @@ impl<'a> AnimatedTexture<'a> { direction: Direction, frame: Option, ) { - let frame_rect = self.get_frame_rect(frame.unwrap_or(self.current_frame())); - let position_rect = Rect::new( + let texture_source_frame_rect = + self.get_frame_rect(frame.unwrap_or_else(|| self.current_frame())); + let canvas_destination_rect = Rect::new( position.0 + self.offset.0, position.1 + self.offset.1, - self.frame_width, - self.frame_height, + self.width, + self.height, ); canvas .copy_ex( &self.raw_texture, - Some(frame_rect), - Some(position_rect), + texture_source_frame_rect, + Some(canvas_destination_rect), direction.angle(), None, false, @@ -175,4 +157,9 @@ impl<'a> AnimatedTexture<'a> { ) .expect("Could not render texture on canvas"); } + + /// Sets the color modulation for the texture. + pub fn set_color_modulation(&mut self, r: u8, g: u8, b: u8) { + self.raw_texture.set_color_mod(r, g, b); + } } diff --git a/src/constants.rs b/src/constants.rs index 1426824..0778877 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -20,7 +20,7 @@ pub const WINDOW_WIDTH: u32 = CELL_SIZE * BOARD_WIDTH; pub const WINDOW_HEIGHT: u32 = CELL_SIZE * (BOARD_HEIGHT + 6); /// An enum representing the different types of tiles on the map. -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq)] pub enum MapTile { /// An empty tile. Empty, diff --git a/src/game.rs b/src/game.rs index 91d7136..5ff2744 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1,4 +1,5 @@ //! This module contains the main game logic and state. +use std::cell::RefCell; use std::rc::Rc; use sdl2::image::LoadTexture; @@ -14,6 +15,7 @@ use crate::audio::Audio; use crate::constants::{MapTile, BOARD_HEIGHT, BOARD_WIDTH, RAW_BOARD}; use crate::direction::Direction; use crate::entity::Entity; +use crate::ghosts::Blinky; use crate::map::Map; use crate::pacman::Pacman; @@ -24,6 +26,10 @@ static POWER_PELLET_TEXTURE_DATA: &[u8] = include_bytes!("../assets/24/energizer static MAP_TEXTURE_DATA: &[u8] = include_bytes!("../assets/map.png"); static FONT_DATA: &[u8] = include_bytes!("../assets/font/konami.ttf"); +// Add ghost texture data +static GHOST_BODY_TEXTURE_DATA: &[u8] = include_bytes!("../assets/32/ghost_body.png"); +static GHOST_EYES_TEXTURE_DATA: &[u8] = include_bytes!("../assets/32/ghost_eyes.png"); + /// The main game state. /// /// This struct contains all the information necessary to run the game, including @@ -34,11 +40,13 @@ pub struct Game<'a> { pellet_texture: Texture<'a>, power_pellet_texture: Texture<'a>, font: Font<'a, 'static>, - pacman: Pacman<'a>, + pacman: Rc>>, map: Rc>, debug: bool, score: u32, audio: Audio, + // Add ghost + blinky: Blinky<'a>, } impl Game<'_> { @@ -62,7 +70,28 @@ impl Game<'_> { let pacman_atlas = texture_creator .load_texture_bytes(PACMAN_TEXTURE_DATA) .expect("Could not load pacman texture from embedded data"); - let pacman = Pacman::new((1, 1), pacman_atlas, Rc::clone(&map)); + let pacman = Rc::new(std::cell::RefCell::new(Pacman::new( + (1, 1), + pacman_atlas, + Rc::clone(&map), + ))); + + // Load ghost textures + let ghost_body = texture_creator + .load_texture_bytes(GHOST_BODY_TEXTURE_DATA) + .expect("Could not load ghost body texture from embedded data"); + let ghost_eyes = texture_creator + .load_texture_bytes(GHOST_EYES_TEXTURE_DATA) + .expect("Could not load ghost eyes texture from embedded data"); + + // Create Blinky + let blinky = Blinky::new( + (13, 11), // Starting position just above ghost house + ghost_body, + ghost_eyes, + Rc::clone(&map), + Rc::clone(&pacman), + ); // Load pellet texture from embedded data let pellet_texture = texture_creator @@ -90,15 +119,16 @@ impl Game<'_> { Game { canvas, - pacman: pacman, + pacman, debug: false, - map: map, + map, map_texture, pellet_texture, power_pellet_texture, font, score: 0, audio, + blinky, } } @@ -110,7 +140,7 @@ impl Game<'_> { pub fn keyboard_event(&mut self, keycode: Keycode) { // Change direction let direction = Direction::from_keycode(keycode); - self.pacman.next_direction = direction; + self.pacman.borrow_mut().next_direction = direction; // Toggle debug mode if keycode == Keycode::Space { @@ -143,9 +173,16 @@ impl Game<'_> { // Reset the score self.score = 0; - // Reset Pacman position (you might want to customize this) - // For now, we'll keep Pacman where he is, but you could add: - // self.pacman.position = Map::cell_to_pixel((1, 1)); + // Reset Pac-Man position + let mut pacman = self.pacman.borrow_mut(); + pacman.pixel_position = Map::cell_to_pixel((1, 1)); + pacman.cell_position = (1, 1); + + // Reset ghost positions + self.blinky.set_mode(crate::ghost::GhostMode::House); + self.blinky.pixel_position = Map::cell_to_pixel((13, 11)); + self.blinky.cell_position = (13, 11); + self.blinky.direction = Direction::Left; event!(tracing::Level::INFO, "Game reset - map and score cleared"); } @@ -153,13 +190,14 @@ impl Game<'_> { /// Advances the game by one tick. pub fn tick(&mut self) { self.check_pellet_eating(); - self.pacman.tick(); + self.pacman.borrow_mut().tick(); + self.blinky.tick(); } /// Checks if Pac-Man is currently eating a pellet and updates the game state /// accordingly. fn check_pellet_eating(&mut self) { - let cell_pos = self.pacman.cell_position(); + let cell_pos = self.pacman.borrow().cell_position(); // Check if there's a pellet at the current position let tile = { @@ -227,8 +265,11 @@ impl Game<'_> { } } - // Render the pacman - self.pacman.render(self.canvas); + // Render Pac-Man + self.pacman.borrow_mut().render(self.canvas); + + // Render ghost + self.blinky.render(self.canvas); // Render score self.render_ui(); @@ -244,7 +285,7 @@ impl Game<'_> { .unwrap_or(MapTile::Empty); let mut color = None; - if (x, y) == self.pacman.cell_position() { + if (x, y) == self.pacman.borrow().cell_position() { self.draw_cell((x, y), Color::CYAN); } else { color = match tile { @@ -263,7 +304,7 @@ impl Game<'_> { } // Draw the next cell - let next_cell = self.pacman.next_cell(None); + let next_cell = self.pacman.borrow().next_cell(None); self.draw_cell((next_cell.0 as u32, next_cell.1 as u32), Color::YELLOW); } diff --git a/src/ghost.rs b/src/ghost.rs new file mode 100644 index 0000000..3e8c6fe --- /dev/null +++ b/src/ghost.rs @@ -0,0 +1,367 @@ +use pathfinding::prelude::astar; +use sdl2::{ + pixels::Color, + render::{Canvas, Texture}, + video::Window, +}; +use std::cell::RefCell; +use std::rc::Rc; + +use rand::Rng; + +use crate::{ + animation::AnimatedTexture, + constants::{MapTile, BOARD_OFFSET, CELL_SIZE}, + direction::Direction, + entity::Entity, + map::Map, + modulation::{SimpleTickModulator, TickModulator}, + pacman::Pacman, +}; + +/// The different modes a ghost can be in +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum GhostMode { + /// Chase mode - ghost actively pursues Pac-Man using its unique strategy + Chase, + /// Scatter mode - ghost heads to its home corner + Scatter, + /// Frightened mode - ghost moves randomly and can be eaten + Frightened, + /// Eyes mode - ghost returns to the ghost house after being eaten + Eyes, + /// House mode - ghost is in the ghost house, waiting to exit + House, +} + +/// The different ghost personalities +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum GhostType { + Blinky, // Red - Shadow + Pinky, // Pink - Speedy + Inky, // Cyan - Bashful + Clyde, // Orange - Pokey +} + +impl GhostType { + /// Returns the color of the ghost. + pub fn color(&self) -> Color { + match self { + GhostType::Blinky => Color::RGB(255, 0, 0), + GhostType::Pinky => Color::RGB(255, 184, 255), + GhostType::Inky => Color::RGB(0, 255, 255), + GhostType::Clyde => Color::RGB(255, 184, 82), + } + } +} + +/// Base ghost struct that contains common functionality +pub struct Ghost<'a> { + /// The absolute position of the ghost on the board, in pixels + pub pixel_position: (i32, i32), + /// The position of the ghost on the board, in grid coordinates + pub cell_position: (u32, u32), + /// The current direction of the ghost + pub direction: Direction, + /// The current mode of the ghost + pub mode: GhostMode, + /// The type/personality of this ghost + pub ghost_type: GhostType, + /// Whether the ghost is currently blue (frightened) + pub is_blue: bool, + /// Reference to the game map + pub map: Rc>, + /// Reference to Pac-Man for targeting + pub pacman: Rc>>, + /// Movement speed + speed: u32, + /// Movement modulator + modulation: SimpleTickModulator, + /// Ghost body sprite + body_sprite: AnimatedTexture<'a>, + /// Ghost eyes sprite + eyes_sprite: AnimatedTexture<'a>, +} + +impl Ghost<'_> { + /// Creates a new ghost instance + pub fn new<'a>( + ghost_type: GhostType, + starting_position: (u32, u32), + body_texture: Texture<'a>, + eyes_texture: Texture<'a>, + map: Rc>, + pacman: Rc>>, + ) -> Ghost<'a> { + let color = ghost_type.color(); + let mut body_sprite = AnimatedTexture::new(body_texture, 8, 2, 32, 32, Some((-4, -4))); + body_sprite.set_color_modulation(color.r, color.g, color.b); + + Ghost { + pixel_position: Map::cell_to_pixel(starting_position), + cell_position: starting_position, + direction: Direction::Left, + mode: GhostMode::Chase, + ghost_type, + is_blue: false, + map, + pacman, + speed: 3, + modulation: SimpleTickModulator::new(1.0), + body_sprite, + eyes_sprite: AnimatedTexture::new(eyes_texture, 1, 4, 32, 32, Some((-4, -4))), + } + } + + /// Renders the ghost to the canvas + pub fn render(&mut self, canvas: &mut Canvas) { + // Render body + if self.mode != GhostMode::Eyes { + let color = if self.mode == GhostMode::Frightened { + Color::RGB(0, 0, 255) + } else { + self.ghost_type.color() + }; + + self.body_sprite + .set_color_modulation(color.r, color.g, color.b); + self.body_sprite + .render(canvas, self.pixel_position, Direction::Right); + } + + // Always render eyes on top + let eye_frame = if self.mode == GhostMode::Frightened { + 4 // Frightened frame + } else { + match self.direction { + Direction::Right => 0, + Direction::Up => 1, + Direction::Left => 2, + Direction::Down => 3, + } + }; + + self.eyes_sprite.render_static( + canvas, + self.pixel_position, + Direction::Right, + Some(eye_frame), + ); + } + + /// Calculates the path to the target tile using the A* algorithm. + fn get_path_to_target(&self, target: (u32, u32)) -> Option<(Vec<(u32, u32)>, u32)> { + let start = self.cell_position; + let map = self.map.borrow(); + + astar( + &start, + |&p| { + let mut successors = vec![]; + for dir in &[ + Direction::Up, + Direction::Down, + Direction::Left, + Direction::Right, + ] { + let (dx, dy) = dir.offset(); + let next_p = (p.0 as i32 + dx, p.1 as i32 + dy); + if let Some(tile) = map.get_tile(next_p) { + if tile != MapTile::Wall { + successors.push(((next_p.0 as u32, next_p.1 as u32), 1)); + } + } + } + successors + }, + |&p| { + ((p.0 as i32 - target.0 as i32).abs() + (p.1 as i32 - target.1 as i32).abs()) as u32 + }, + |&p| p == target, + ) + } + + /// Gets the target tile for this ghost based on its current mode + pub fn get_target_tile(&self) -> (i32, i32) { + match self.mode { + GhostMode::Scatter => self.get_scatter_target(), + GhostMode::Chase => self.get_chase_target(), + GhostMode::Frightened => self.get_random_target(), + GhostMode::Eyes => self.get_house_target(), + GhostMode::House => self.get_house_exit_target(), + } + } + + /// Gets this ghost's home corner target for scatter mode + fn get_scatter_target(&self) -> (i32, i32) { + match self.ghost_type { + GhostType::Blinky => (25, 0), // Top right + GhostType::Pinky => (2, 0), // Top left + GhostType::Inky => (27, 35), // Bottom right + GhostType::Clyde => (0, 35), // Bottom left + } + } + + /// Gets a random adjacent tile for frightened mode + fn get_random_target(&self) -> (i32, i32) { + let mut rng = rand::thread_rng(); + let (x, y) = self.cell_position; + let mut possible_moves = Vec::new(); + + // Check all four directions + for dir in &[ + Direction::Up, + Direction::Down, + Direction::Left, + Direction::Right, + ] { + // Don't allow reversing direction + if *dir == self.direction.opposite() { + continue; + } + + let (dx, dy) = dir.offset(); + let next_cell = (x as i32 + dx, y as i32 + dy); + let tile = self.map.borrow().get_tile(next_cell); + if let Some(MapTile::Wall) = tile { + // It's a wall, not a valid move + } else { + possible_moves.push(next_cell); + } + } + + if possible_moves.is_empty() { + // No valid moves, must reverse + let (dx, dy) = self.direction.opposite().offset(); + return (x as i32 + dx, y as i32 + dy); + } + + // Choose a random valid move + possible_moves[rng.gen_range(0..possible_moves.len())] + } + + /// Gets the ghost house target for returning eyes + fn get_house_target(&self) -> (i32, i32) { + (13, 14) // Center of ghost house + } + + /// Gets the exit point target when leaving house + fn get_house_exit_target(&self) -> (i32, i32) { + (13, 11) // Just above ghost house + } + + /// Gets this ghost's chase mode target (to be implemented by each ghost type) + fn get_chase_target(&self) -> (i32, i32) { + // Default implementation just targets Pac-Man directly + let pacman = self.pacman.borrow(); + (pacman.cell_position.0 as i32, pacman.cell_position.1 as i32) + } + + /// Changes the ghost's mode and handles direction reversal + pub fn set_mode(&mut self, new_mode: GhostMode) { + // Don't reverse if going to/from frightened or if in house + let should_reverse = self.mode != GhostMode::House + && new_mode != GhostMode::Frightened + && self.mode != GhostMode::Frightened; + + self.mode = new_mode; + + if should_reverse { + self.direction = self.direction.opposite(); + } + } +} + +impl Entity for Ghost<'_> { + fn position(&self) -> (i32, i32) { + self.pixel_position + } + + fn cell_position(&self) -> (u32, u32) { + self.cell_position + } + + fn internal_position(&self) -> (u32, u32) { + let (x, y) = self.position(); + (x as u32 % CELL_SIZE, y as u32 % CELL_SIZE) + } + + fn is_colliding(&self, other: &dyn Entity) -> bool { + let (x, y) = self.position(); + let (other_x, other_y) = other.position(); + x == other_x && y == other_y + } + + fn tick(&mut self) { + if self.mode == GhostMode::House { + // For now, do nothing in the house + return; + } + + if self.internal_position() == (0, 0) { + self.cell_position = ( + (self.pixel_position.0 as u32 / CELL_SIZE) - BOARD_OFFSET.0, + (self.pixel_position.1 as u32 / CELL_SIZE) - BOARD_OFFSET.1, + ); + + // Pathfinding logic + let target_tile = self.get_target_tile(); + if let Some((path, _)) = + self.get_path_to_target((target_tile.0 as u32, target_tile.1 as u32)) + { + if path.len() > 1 { + let next_move = path[1]; + let (x, y) = self.cell_position; + let dx = next_move.0 as i32 - x as i32; + let dy = next_move.1 as i32 - y as i32; + self.direction = if dx > 0 { + Direction::Right + } else if dx < 0 { + Direction::Left + } else if dy > 0 { + Direction::Down + } else { + Direction::Up + }; + } + } + + // Check if the next tile in the current direction is a wall + let (dx, dy) = self.direction.offset(); + let next_cell = ( + self.cell_position.0 as i32 + dx, + self.cell_position.1 as i32 + dy, + ); + let next_tile = self + .map + .borrow() + .get_tile(next_cell) + .unwrap_or(MapTile::Empty); + if next_tile == MapTile::Wall { + // Don't move if the next tile is a wall + return; + } + } + + if !self.modulation.next() { + return; + } + + // Update position based on current direction and speed + let speed = self.speed as i32; + match self.direction { + Direction::Right => self.pixel_position.0 += speed, + Direction::Left => self.pixel_position.0 -= speed, + Direction::Up => self.pixel_position.1 -= speed, + Direction::Down => self.pixel_position.1 += speed, + } + + // Update cell position when aligned with grid + if self.internal_position() == (0, 0) { + self.cell_position = ( + (self.pixel_position.0 as u32 / CELL_SIZE) - BOARD_OFFSET.0, + (self.pixel_position.1 as u32 / CELL_SIZE) - BOARD_OFFSET.1, + ); + } + } +} diff --git a/src/ghosts/blinky.rs b/src/ghosts/blinky.rs new file mode 100644 index 0000000..38b03a3 --- /dev/null +++ b/src/ghosts/blinky.rs @@ -0,0 +1,88 @@ +use std::cell::RefCell; +use std::rc::Rc; + +use sdl2::render::{Canvas, Texture}; +use sdl2::video::Window; + +use crate::{ + entity::Entity, + ghost::{Ghost, GhostMode, GhostType}, + map::Map, + pacman::Pacman, +}; + +pub struct Blinky<'a> { + ghost: Ghost<'a>, +} + +impl<'a> Blinky<'a> { + pub fn new( + starting_position: (u32, u32), + body_texture: Texture<'a>, + eyes_texture: Texture<'a>, + map: Rc>, + pacman: Rc>>, + ) -> Blinky<'a> { + Blinky { + ghost: Ghost::new( + GhostType::Blinky, + starting_position, + body_texture, + eyes_texture, + map, + pacman, + ), + } + } + + /// Gets Blinky's chase target - directly targets Pac-Man's current position + fn get_chase_target(&self) -> (i32, i32) { + let pacman = self.ghost.pacman.borrow(); + (pacman.cell_position.0 as i32, pacman.cell_position.1 as i32) + } + + pub fn set_mode(&mut self, mode: GhostMode) { + self.ghost.set_mode(mode); + } + + pub fn render(&mut self, canvas: &mut Canvas) { + self.ghost.render(canvas); + } +} + +impl<'a> Entity for Blinky<'a> { + fn position(&self) -> (i32, i32) { + self.ghost.position() + } + + fn cell_position(&self) -> (u32, u32) { + self.ghost.cell_position() + } + + fn internal_position(&self) -> (u32, u32) { + self.ghost.internal_position() + } + + fn is_colliding(&self, other: &dyn Entity) -> bool { + self.ghost.is_colliding(other) + } + + fn tick(&mut self) { + self.ghost.tick() + } +} + +// Allow direct access to ghost fields +impl<'a> std::ops::Deref for Blinky<'a> { + type Target = Ghost<'a>; + + fn deref(&self) -> &Self::Target { + &self.ghost + } +} + +impl<'a> std::ops::DerefMut for Blinky<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.ghost + } +} diff --git a/src/ghosts/mod.rs b/src/ghosts/mod.rs new file mode 100644 index 0000000..88c9ce6 --- /dev/null +++ b/src/ghosts/mod.rs @@ -0,0 +1,3 @@ +mod blinky; + +pub use blinky::Blinky; diff --git a/src/main.rs b/src/main.rs index 8915072..28d3615 100644 --- a/src/main.rs +++ b/src/main.rs @@ -58,6 +58,8 @@ mod constants; mod direction; mod entity; mod game; +mod ghost; +mod ghosts; mod helper; mod map; mod modulation; diff --git a/src/pacman.rs b/src/pacman.rs index 7657ace..b8b8b07 100644 --- a/src/pacman.rs +++ b/src/pacman.rs @@ -69,10 +69,9 @@ impl Pacman<'_> { /// /// * `canvas` - The SDL canvas to render to. pub fn render(&mut self, canvas: &mut Canvas) { - // When stopped, render the last frame of the animation if self.stopped { self.sprite - .render_until(canvas, self.pixel_position, self.direction, 2); + .render_static(canvas, self.pixel_position, self.direction, Some(2)); } else { self.sprite .render(canvas, self.pixel_position, self.direction); @@ -208,29 +207,31 @@ impl Entity for Pacman<'_> { } } - if !self.stopped && self.modulation.next() { - let speed = self.speed as i32; - match self.direction { - Direction::Right => { - self.pixel_position.0 += speed; + if !self.stopped { + if self.modulation.next() { + let speed = self.speed as i32; + match self.direction { + Direction::Right => { + self.pixel_position.0 += speed; + } + Direction::Left => { + self.pixel_position.0 -= speed; + } + Direction::Up => { + self.pixel_position.1 -= speed; + } + Direction::Down => { + self.pixel_position.1 += speed; + } } - Direction::Left => { - self.pixel_position.0 -= speed; - } - Direction::Up => { - self.pixel_position.1 -= speed; - } - Direction::Down => { - self.pixel_position.1 += speed; - } - } - // Update the cell position if Pac-Man is aligned with the grid. - if self.internal_position_even() == (0, 0) { - self.cell_position = ( - (self.pixel_position.0 as u32 / CELL_SIZE) - BOARD_OFFSET.0, - (self.pixel_position.1 as u32 / CELL_SIZE) - BOARD_OFFSET.1, - ); + // Update the cell position if Pac-Man is aligned with the grid. + if self.internal_position_even() == (0, 0) { + self.cell_position = ( + (self.pixel_position.0 as u32 / CELL_SIZE) - BOARD_OFFSET.0, + (self.pixel_position.1 as u32 / CELL_SIZE) - BOARD_OFFSET.1, + ); + } } } }