commit bf46a3e7c3fee72eda3193f91a0548702fb09132 Author: CrispyPin Date: Thu Oct 3 22:59:49 2024 +0200 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..fdeadbb --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,625 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bindgen" +version = "0.69.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.79", + "which", +] + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "cc" +version = "1.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" +dependencies = [ + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "cmake" +version = "0.1.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb1e43aa7fd152b1f968787f7dbcdeb306d1867ff373c69955211876c053f91a" +dependencies = [ + "cc", +] + +[[package]] +name = "crossbeam-queue" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" +dependencies = [ + "cfg-if 0.1.10", + "crossbeam-utils", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +dependencies = [ + "autocfg", + "cfg-if 0.1.10", + "lazy_static", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "hashbrown" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96282e96bfcd3da0d3aa9938bedf1e50df3269b6db08b4876d2da0bb1a0841cf" +dependencies = [ + "ahash", + "autocfg", +] + +[[package]] +name = "hibitset" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3ede5cfa60c958e60330d65163adbc4211e15a2653ad80eb0cce878de120121" + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" + +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if 1.0.0", + "windows-targets", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "marble2" +version = "0.1.0" +dependencies = [ + "raylib", +] + +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mopa" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a785740271256c230f57462d3b83e52f998433a7062fc18f96d5999474a9f915" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "once_cell" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "portable-atomic" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" + +[[package]] +name = "prettyplease" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" +dependencies = [ + "proc-macro2", + "syn 2.0.79", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "raylib" +version = "5.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2a7a6734329d7b872a418fe4cb08ca282eb66a6f4a3430bd4ee4e6a8cac6632" +dependencies = [ + "cfg-if 1.0.0", + "lazy_static", + "libc", + "parking_lot", + "raylib-sys", + "specs", + "specs-derive", +] + +[[package]] +name = "raylib-sys" +version = "5.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db5c6001cfaeec17210713227d11f3b1ba4b723bb12cff47d1b93c4060e10ad0" +dependencies = [ + "bindgen", + "cc", + "cmake", + "fs_extra", +] + +[[package]] +name = "redox_syscall" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.38.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "shred" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f08237e667ac94ad20f8878b5943d91a93ccb231428446c57c21c57779016d" +dependencies = [ + "arrayvec", + "hashbrown", + "mopa", + "smallvec", + "tynm", +] + +[[package]] +name = "shrev" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ea33232fdcf1bf691ca33450e5a94dde13e1a8cbb8caabc5e4f9d761e10b1a" + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "specs" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff28a29366aff703d5da8a7e2c8875dc8453ac1118f842cbc0fa70c7db51240" +dependencies = [ + "crossbeam-queue", + "hashbrown", + "hibitset", + "log", + "shred", + "shrev", + "tuple_utils", +] + +[[package]] +name = "specs-derive" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e23e09360f3d2190fec4222cd9e19d3158d5da948c0d1ea362df617dd103511" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tuple_utils" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44834418e2c5b16f47bedf35c28e148db099187dd5feee6367fb2525863af4f1" + +[[package]] +name = "tynm" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd30d05e69d1478e13fe3e7a853409cfec82cebc2cf9b8d613b3c6b0081781ed" +dependencies = [ + "nom", +] + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[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_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[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_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..1c4bd62 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "marble2" +version = "0.1.0" +edition = "2021" + +[dependencies] +raylib = "5.0.2" diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..218e203 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +hard_tabs = true diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..f8634fb --- /dev/null +++ b/src/main.rs @@ -0,0 +1,35 @@ +use std::fs::read_to_string; + +use marble_engine::parse; +use raylib::prelude::*; + +mod marble_engine; + +fn main() { + let (mut rl, thread) = raylib::init().resizable().title("Hello, World").build(); + rl.set_target_fps(60); + + let board = parse(&read_to_string("boards/flow.mbl").unwrap()); + let mut pos_offset = Vector2::zero(); + let mut machine = marble_engine::Machine::new(board, "Vec::new()".bytes().collect()); + + while !rl.window_should_close() { + if rl.is_key_pressed(KeyboardKey::KEY_SPACE) { + machine.step(); + } + if rl.is_mouse_button_down(MouseButton::MOUSE_BUTTON_MIDDLE) { + pos_offset += rl.get_mouse_delta() + } + if rl.is_mouse_button_pressed(MouseButton::MOUSE_BUTTON_RIGHT) { + pos_offset = Vector2::zero(); + } + + let mut d = rl.begin_drawing(&thread); + d.clear_background(Color::new(64, 64, 64, 255)); + + machine.draw(&mut d, pos_offset); + d.draw_text("Hello, world!", 12, 12, 20, Color::WHITE); + + d.draw_fps(2, 2); + } +} diff --git a/src/marble_engine.rs b/src/marble_engine.rs new file mode 100644 index 0000000..ceb9f87 --- /dev/null +++ b/src/marble_engine.rs @@ -0,0 +1,364 @@ +use raylib::{drawing::RaylibDrawHandle, math::Vector2}; + +mod board; +mod tile; +use board::{Board, Pos}; +use tile::*; + +#[derive(Debug)] +pub struct Machine { + board: Board, + marbles: Vec, + + input: Vec, + input_index: usize, + output: Vec, + steps: usize, +} + +impl Machine { + pub fn new_empty(width: usize) -> Self { + Self { + board: Board::new_empty(width, width), + marbles: Vec::new(), + input: Vec::new(), + input_index: 0, + output: Vec::new(), + steps: 0, + } + } + + pub fn new(grid: Board, input: Vec) -> Self { + // let (grid, marbles) = parse(source); + let mut marbles = Vec::new(); + for y in 0..grid.height() { + for x in 0..grid.width() { + if let Some(Tile::Marble { value: _, dir: _ }) = grid.get((x, y).into()) { + marbles.push((x, y).into()); + } + } + } + Self { + board: grid, + marbles, + input, + input_index: 0, + output: Vec::new(), + steps: 0, + } + } + + pub fn draw(&self, d: &mut RaylibDrawHandle, offset: Vector2) { + let tile_size = 32; + for x in 0..self.board.width() { + for y in 0..self.board.height() { + if let Some(tile) = self.board.get((x, y).into()) { + let px = x as i32 * tile_size + offset.x as i32 + tile_size / 2; + let py = y as i32 * tile_size + offset.y as i32 + tile_size / 2; + tile.draw(d, px, py, tile_size); + } + } + } + } + + pub fn step(&mut self) { + self.steps += 1; + // reset wires + for y in 0..self.board.height() { + for x in 0..self.board.width() { + match self.board.get_mut((x, y).into()) { + Tile::Wire(_, state) => *state = false, + Tile::Trigger(state) => *state = false, + _ => (), + } + } + } + let mut to_remove = Vec::new(); + let mut triggers = Vec::new(); + for i in 0..self.marbles.len() { + let marble_pos = self.marbles[i]; + let tile = self.board.get_or_blank(marble_pos); + + if let Tile::Marble { value, dir } = tile { + let next_pos = dir.step(marble_pos); + if !self.board.in_bounds(next_pos) { + continue; + } + let mut new_tile = None; + let target = self.board.get_mut(next_pos); + match target { + Tile::Blank => { + *target = tile; + self.marbles[i] = next_pos; + new_tile = Some(Tile::Blank); + } + Tile::Digit(d) => { + let new_val = value + .wrapping_mul(10) + .wrapping_add((*d - b'0') as MarbleValue); + *target = Tile::Marble { + value: new_val, + dir, + }; + self.marbles[i] = next_pos; + new_tile = Some(Tile::Blank); + } + Tile::Bag => { + to_remove.push(i); + new_tile = Some(Tile::Blank); + } + Tile::Marble { + value: _other_value, + dir: other_dir, + } => { + // bounce off other marbles + if *other_dir != dir { + new_tile = Some(Tile::Marble { + value, + dir: dir.opposite(), + }); + *other_dir = dir; + } + } + Tile::Trigger(state) => { + *state = true; + triggers.push(next_pos); + let far_pos = dir.step(next_pos); + if let Some(Tile::Blank) = self.board.get(far_pos) { + self.board.set(far_pos, Tile::Marble { value, dir }); + self.marbles[i] = far_pos; + new_tile = Some(Tile::Blank); + } + } + Tile::Arrow(arrow_dir) => { + let far_pos = arrow_dir.step(next_pos); + let arrow_dir = *arrow_dir; + if let Some(Tile::Blank) = self.board.get(far_pos) { + self.marbles[i] = far_pos; + self.board.set( + far_pos, + Tile::Marble { + value, + dir: arrow_dir, + }, + ); + new_tile = Some(Tile::Blank); + } else if far_pos == marble_pos { + // bounce on reverse arrow + new_tile = Some(Tile::Marble { + value, + dir: dir.opposite(), + }); + } + } + Tile::Mirror(mirror) => { + let new_dir = mirror.new_dir(dir); + let far_pos = new_dir.step(next_pos); + let far_target = self.board.get_mut(far_pos); + if let Tile::Blank = far_target { + *far_target = Tile::Marble { + value, + dir: new_dir, + }; + self.marbles[i] = far_pos; + new_tile = Some(Tile::Blank); + } + } + _ => (), + } + + if let Some(t) = new_tile { + *self.board.get_mut(marble_pos) = t; + } + } + } + let mut offset = 0; + for i in to_remove { + self.marbles.remove(i - offset); + offset += 1; + } + for pos in triggers { + for dir in Direction::ALL { + self.propagate_power(dir, dir.step(pos)); + } + } + } + + fn propagate_power(&mut self, dir: Direction, pos: Pos) { + if !self.board.in_bounds(pos) { + return; + } + let tile = self.board.get_mut(pos); + let front_pos = dir.step(pos); + match tile { + Tile::Wire(wiretype, state) => { + if *state { + return; + } + *state = true; + let dirs = wiretype.directions(); + for d in dirs { + self.propagate_power(*d, d.step(pos)); + } + } + Tile::Print => { + let sample = self.board.get_or_blank(front_pos); + if let Tile::Marble { value, dir: _ } = sample { + self.output.push(value as u8); + } + } + Tile::Bag => { + if let Some(Tile::Blank) = self.board.get(front_pos) { + *self.board.get_mut(front_pos) = Tile::Marble { value: 0, dir }; + self.marbles.push(front_pos); + } + } + Tile::Input => { + if self.input_index < self.input.len() + && self.board.get_or_blank(front_pos).is_blank() + { + let value = self.input[self.input_index] as MarbleValue; + *self.board.get_mut(front_pos) = Tile::Marble { value, dir }; + self.marbles.push(front_pos); + self.input_index += 1; + } + } + Tile::Math(op) => { + let op = *op; + let pos_a = dir.left().step(pos); + let pos_b = dir.right().step(pos); + let val_a = self.board.get_or_blank(pos_a).read_value(); + let val_b = self.board.get_or_blank(pos_b).read_value(); + if (!self.board.get_or_blank(pos_a).is_blank() + || !self.board.get_or_blank(pos_b).is_blank()) + && self.board.get_or_blank(front_pos).is_blank() + { + let result = match op { + MathOp::Add => val_a.wrapping_add(val_b), + MathOp::Sub => val_a.wrapping_sub(val_b), + MathOp::Mul => val_a.wrapping_mul(val_b), + MathOp::Div => val_a.checked_div(val_b).unwrap_or_default(), + MathOp::Rem => val_a.checked_rem(val_b).unwrap_or_default(), + }; + // println!("{op:?} a:{val_a} b:{val_b}"); + *self.board.get_mut(front_pos) = Tile::Marble { value: result, dir }; + self.marbles.push(front_pos); + } + } + Tile::Flip => { + let m = self.board.get_mut(front_pos); + match m { + Tile::Wire(wire_type, _) => { + *wire_type = match *wire_type { + WireType::Vertical => WireType::Horizontal, + WireType::Horizontal => WireType::Vertical, + WireType::Cross => WireType::Cross, + }; + } + Tile::Mirror(mirror) => { + *mirror = match *mirror { + MirrorType::Forward => MirrorType::Back, + MirrorType::Back => MirrorType::Forward, + }; + } + Tile::Arrow(dir) => { + *dir = dir.opposite(); + } + _ => (), + }; + } + Tile::Gate(gate) => { + let gate = *gate; + let pos_a = dir.left().step(pos); + let pos_b = dir.right().step(pos); + let val_a = self.board.get_or_blank(pos_a).read_value(); + let val_b = self.board.get_or_blank(pos_b).read_value(); + + let result = match gate { + GateType::LessThan => val_a < val_b, + GateType::GreaterThan => val_a > val_b, + GateType::Equal => val_a == val_b, + GateType::NotEqual => val_a != val_b, + }; + if result { + self.propagate_power(dir, dir.step(pos)); + } + } + Tile::Marble { value: _, dir: _ } + | Tile::Trigger(_) + | Tile::Digit(_) + | Tile::Mirror(_) + | Tile::Arrow(_) + | Tile::Comment(_) + | Tile::Blank => (), + } + } +} + +pub fn parse(source: &str) -> Board { + let mut rows = Vec::new(); + + let mut width = 0; + for line in source.lines() { + width = width.max(line.len()); + let mut tiles = Vec::new(); + let mut in_comment = false; + for char in line.chars() { + if in_comment { + if char == ')' { + in_comment = false; + } + if char == ' ' { + // allow marbles to pass through gaps in comments + tiles.push(Tile::Blank); + } else { + tiles.push(Tile::Comment(char as u8)); + } + continue; + } + tiles.push(match char { + 'o' => Tile::Marble { + value: 0, + dir: Direction::Down, + }, + '*' => Tile::Trigger(false), + '-' => Tile::Wire(WireType::Horizontal, false), + '|' => Tile::Wire(WireType::Vertical, false), + '+' => Tile::Wire(WireType::Cross, false), + '/' => Tile::Mirror(MirrorType::Forward), + '\\' => Tile::Mirror(MirrorType::Back), + '^' => Tile::Arrow(Direction::Up), + 'v' => Tile::Arrow(Direction::Down), + '<' => Tile::Arrow(Direction::Left), + '>' => Tile::Arrow(Direction::Right), + '=' => Tile::Gate(GateType::Equal), + '!' => Tile::Gate(GateType::NotEqual), + 'L' => Tile::Gate(GateType::LessThan), + 'G' => Tile::Gate(GateType::GreaterThan), + 'P' => Tile::Print, + 'I' => Tile::Input, + 'F' => Tile::Flip, + 'A' => Tile::Math(MathOp::Add), + 'S' => Tile::Math(MathOp::Sub), + 'M' => Tile::Math(MathOp::Mul), + 'D' => Tile::Math(MathOp::Div), + 'R' => Tile::Math(MathOp::Rem), + 'B' => Tile::Bag, + d @ '0'..='9' => Tile::Digit(d as u8), + '#' => Tile::Comment(b'#'), + ' ' => Tile::Blank, + '(' => { + in_comment = true; + Tile::Comment(b'(') + } + _ => Tile::Blank, + }); + } + rows.push(tiles); + } + for line in &mut rows { + line.resize(width, Tile::Blank); + } + + Board::new(rows) +} diff --git a/src/marble_engine/board.rs b/src/marble_engine/board.rs new file mode 100644 index 0000000..d0a71f7 --- /dev/null +++ b/src/marble_engine/board.rs @@ -0,0 +1,87 @@ +use super::tile::*; + +#[derive(Debug, Default, Clone, Copy, PartialEq)] +pub struct Pos { + pub x: isize, + pub y: isize, +} + +impl From<(usize, usize)> for Pos { + fn from(value: (usize, usize)) -> Self { + Self { + x: value.0 as isize, + y: value.1 as isize, + } + } +} + +#[derive(Debug)] +pub struct Board { + rows: Vec>, + width: usize, + height: usize, +} + +impl Board { + pub fn new_empty(width: usize, height: usize) -> Self { + let rows = vec![vec![Tile::Blank; width]; height]; + Self { + rows, + width, + height, + } + } + + pub fn new(rows: Vec>) -> Self { + Self { + width: rows[0].len(), + height: rows.len(), + rows, + } + } + + pub fn in_bounds(&self, p: Pos) -> bool { + p.x >= 0 && p.y >= 0 && p.x < self.width as isize && p.y < self.height as isize + } + + pub fn get(&self, p: Pos) -> Option { + if self.in_bounds(p) { + Some(self.rows[p.y as usize][p.x as usize]) + } else { + None + } + } + + pub fn get_or_blank(&self, p: Pos) -> Tile { + if self.in_bounds(p) { + self.rows[p.y as usize][p.x as usize] + } else { + Tile::default() + } + } + + pub fn get_mut(&mut self, p: Pos) -> &mut Tile { + if self.in_bounds(p) { + &mut self.rows[p.y as usize][p.x as usize] + } else { + panic!( + "position {p:?} out of bounds, size is {}x{}", + self.width, self.height + ); + } + } + + pub fn set(&mut self, p: Pos, tile: Tile) { + if self.in_bounds(p) { + self.rows[p.y as usize][p.x as usize] = tile; + } + } + + pub fn width(&self) -> usize { + self.width + } + + pub fn height(&self) -> usize { + self.height + } +} diff --git a/src/marble_engine/tile.rs b/src/marble_engine/tile.rs new file mode 100644 index 0000000..9928361 --- /dev/null +++ b/src/marble_engine/tile.rs @@ -0,0 +1,232 @@ +use raylib::{ + color::Color, + drawing::{RaylibDraw, RaylibDrawHandle}, + ffi::Rectangle, + math::Vector2, +}; + +use super::board::Pos; + +pub type MarbleValue = u32; + +#[derive(Debug, Default, Clone, Copy)] +pub enum Tile { + #[default] + Blank, + Comment(u8), + Bag, + Marble { + value: MarbleValue, + dir: Direction, + }, + Trigger(bool), + Digit(u8), + Wire(WireType, bool), + Gate(GateType), + Print, + Input, + Flip, + Math(MathOp), + Mirror(MirrorType), + Arrow(Direction), +} + +#[derive(Debug, Clone, Copy)] +pub enum MirrorType { + Forward, + Back, +} + +#[derive(Debug, Clone, Copy)] +pub enum MathOp { + Add, + Sub, + Mul, + Div, + Rem, +} + +#[derive(Debug, Clone, Copy)] +pub enum GateType { + LessThan, + GreaterThan, + Equal, + NotEqual, +} + +#[derive(Debug, Clone, Copy)] +pub enum WireType { + Vertical, + Horizontal, + Cross, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Direction { + Up, + Down, + Left, + Right, +} + +impl Tile { + pub fn is_blank(&self) -> bool { + matches!(self, Tile::Blank) + } + + pub fn read_value(&self) -> MarbleValue { + if let Tile::Marble { value, dir: _ } = self { + *value + } else { + 0 + } + } + + pub fn draw(&self, d: &mut RaylibDrawHandle, x: i32, y: i32, size: i32) { + let up = y - size / 2 + 1; + let down = y + size / 2 - 1; + let left = x - size / 2 + 1; + let right = x + size / 2 - 1; + match self { + Tile::Blank => (), + Tile::Comment(c) => { + d.draw_text(&format!("{}", *c as char), x - 10, y - 10, 20, Color::RED) + } + Tile::Bag => d.draw_circle_lines(x, y, size as f32 * 0.4, Color::CYAN), + Tile::Marble { value, dir } => { + d.draw_circle(x, y, size as f32 * 0.35, Color::new(15, 15, 15, 255)); + d.draw_text( + &format!("{value}"), + x - size / 2, + y - size / 2, + 20, + Color::MAGENTA, + ); + } + Tile::Trigger(state) => { + let color = if *state { Color::RED } else { Color::LIGHTGRAY }; + d.draw_rectangle(x - size / 4, y - size / 4, size / 2, size / 2, color) + } + Tile::Digit(n) => { + d.draw_text(&String::from(*n as char), x - 10, y - 10, 20, Color::ORANGE) + } + Tile::Wire(wire, state) => { + let color = if *state { Color::RED } else { Color::WHITE }; + let vertical = !matches!(wire, WireType::Horizontal); + let horizontal = !matches!(wire, WireType::Vertical); + if vertical { + // d.draw_line(x, up, x, down, color); + d.draw_rectangle(x - size / 8, y - size / 2, size / 4, size, color) + } + if horizontal { + // d.draw_line(left, y, right, y, color); + d.draw_rectangle(x - size / 2, y - size / 8, size, size / 4, color) + } + } + // Tile::Gate(_) => todo!(), + // Tile::Print => todo!(), + // Tile::Input => todo!(), + // Tile::Flip => todo!(), + // Tile::Math(_) => todo!(), + Tile::Mirror(mirror) => { + let height = size as f32 * 1.25; + let width = (size / 4) as f32; + let rec = Rectangle { + x: x as f32, + y: y as f32, + width, + height, + }; + let rot = match mirror { + MirrorType::Forward => 45.0, + MirrorType::Back => -45.0, + }; + d.draw_rectangle_pro(rec, Vector2::new(width, height) * 0.5, rot, Color::CYAN); + } + Tile::Arrow(dir) => { + let up = Vector2::from((x as f32, up as f32)); + let down = Vector2::from((x as f32, down as f32)); + let left = Vector2::from((left as f32, y as f32)); + let right = Vector2::from((right as f32, y as f32)); + let (v1, v2, v3) = match dir { + Direction::Up => (up, left, right), + Direction::Down => (down, right, left), + Direction::Left => (left, down, up), + Direction::Right => (right, up, down), + }; + d.draw_triangle(v1, v2, v3, Color::CYAN); + } + _ => d.draw_rectangle(x - size / 2, y - size / 2, size, size, Color::YELLOW), + } + } +} + +impl Direction { + pub const ALL: [Direction; 4] = [ + Direction::Up, + Direction::Down, + Direction::Left, + Direction::Right, + ]; + + pub fn opposite(&self) -> Direction { + match self { + Direction::Up => Direction::Down, + Direction::Down => Direction::Up, + Direction::Left => Direction::Right, + Direction::Right => Direction::Left, + } + } + + pub fn right(&self) -> Direction { + match self { + Direction::Up => Direction::Right, + Direction::Down => Direction::Left, + Direction::Left => Direction::Up, + Direction::Right => Direction::Down, + } + } + + pub fn left(&self) -> Direction { + self.right().opposite() + } + + pub fn step(&self, mut pos: Pos) -> Pos { + match self { + Direction::Up => pos.y -= 1, + Direction::Down => pos.y += 1, + Direction::Left => pos.x -= 1, + Direction::Right => pos.x += 1, + } + pos + } +} + +impl WireType { + pub fn directions(self) -> &'static [Direction] { + match self { + WireType::Vertical => &[Direction::Up, Direction::Down], + WireType::Horizontal => &[Direction::Left, Direction::Right], + WireType::Cross => &Direction::ALL, + } + } +} + +impl MirrorType { + pub fn new_dir(self, dir: Direction) -> Direction { + match self { + MirrorType::Forward => match dir { + Direction::Up => Direction::Right, + Direction::Down => Direction::Left, + Direction::Left => Direction::Down, + Direction::Right => Direction::Up, + }, + MirrorType::Back => match dir { + Direction::Up => Direction::Left, + Direction::Down => Direction::Right, + Direction::Left => Direction::Up, + Direction::Right => Direction::Down, + }, + } + } +}