From e0e6b68a5bc9a588c3cda359b08052d27a5cefcc Mon Sep 17 00:00:00 2001 From: mehbark Date: Mon, 29 Dec 2025 02:27:24 -0500 Subject: [PATCH] mvp you can do like 2 3 6q --- .gitignore | 1 + Cargo.lock | 432 ++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 7 + src/dir.rs | 101 ++++++++++++ src/input.rs | 66 ++++++++ src/main.rs | 28 ++++ src/timing.rs | 3 + 7 files changed, 638 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/dir.rs create mode 100644 src/input.rs create mode 100644 src/main.rs create mode 100644 src/timing.rs 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..7b4cf7f --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,432 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" +dependencies = [ + "bitflags", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags", + "core-foundation", + "libc", +] + +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags", + "objc2", +] + +[[package]] +name = "enigo" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71c6c56e50f7acae2906a0dcbb34529ca647e40421119ad5d12e7f8ba6e50010" +dependencies = [ + "core-foundation", + "core-graphics", + "foreign-types-shared", + "libc", + "log", + "nom", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "windows", + "x11rb", + "xkbcommon", + "xkeysym", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "gethostname" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +dependencies = [ + "rustix", + "windows-link 0.2.1", +] + +[[package]] +name = "libc" +version = "0.2.178" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "memmap2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" +dependencies = [ + "libc", +] + +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "objc2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "optimal-combo-search" +version = "0.1.0" +dependencies = [ + "enigo", +] + +[[package]] +name = "proc-macro2" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "syn" +version = "2.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "x11rb" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" +dependencies = [ + "gethostname", + "rustix", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + +[[package]] +name = "xkbcommon" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a974f48060a14e95705c01f24ad9c3345022f4d97441b8a36beb7ed5c4a02d" +dependencies = [ + "libc", + "memmap2", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f8bc781 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "optimal-combo-search" +version = "0.1.0" +edition = "2024" + +[dependencies] +enigo = "0.6.1" diff --git a/src/dir.rs b/src/dir.rs new file mode 100644 index 0000000..e0e56ac --- /dev/null +++ b/src/dir.rs @@ -0,0 +1,101 @@ +use std::{fmt, str::FromStr}; + +use enigo::{Enigo, Key, Keyboard}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)] +pub enum Dir { + D1 = 1, + D2 = 2, + D3 = 3, + D4 = 4, + #[default] + D5 = 5, + D6 = 6, + D7 = 7, + D8 = 8, + D9 = 9, +} + +#[derive(Debug, Clone, Copy)] +pub enum DirFromStrError { + Empty, + InvalidChar(char), +} + +impl FromStr for Dir { + type Err = DirFromStrError; + + fn from_str(s: &str) -> Result { + let Some(first) = s.chars().next() else { + return Err(DirFromStrError::Empty); + }; + Ok(match first { + '1' => Self::D1, + '2' => Self::D2, + '3' => Self::D3, + '4' => Self::D4, + '5' => Self::D5, + '6' => Self::D6, + '7' => Self::D7, + '8' => Self::D8, + '9' => Self::D9, + c => return Err(DirFromStrError::InvalidChar(c)), + }) + } +} + +impl fmt::Display for Dir { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let c = match self { + Self::D1 => '1', + Self::D2 => '2', + Self::D3 => '3', + Self::D4 => '4', + Self::D5 => '5', + Self::D6 => '6', + Self::D7 => '7', + Self::D8 => '8', + Self::D9 => '9', + }; + write!(f, "{c}") + } +} + +const UP: Key = Key::UpArrow; +const DOWN: Key = Key::DownArrow; +const LEFT: Key = Key::LeftArrow; +const RIGHT: Key = Key::RightArrow; + +impl Dir { + pub fn press(self, enigo: &mut Enigo, direction: enigo::Direction) -> enigo::InputResult<()> { + if self.is_up() { + enigo.key(UP, direction)?; + } + if self.is_down() { + enigo.key(DOWN, direction)?; + } + if self.is_left() { + enigo.key(LEFT, direction)?; + } + if self.is_right() { + enigo.key(RIGHT, direction)?; + } + Ok(()) + } + + fn is_up(self) -> bool { + matches!(self, Self::D7 | Self::D8 | Self::D9) + } + + fn is_down(self) -> bool { + matches!(self, Self::D1 | Self::D2 | Self::D3) + } + + fn is_left(self) -> bool { + matches!(self, Self::D7 | Self::D4 | Self::D1) + } + + fn is_right(self) -> bool { + matches!(self, Self::D9 | Self::D6 | Self::D3) + } +} diff --git a/src/input.rs b/src/input.rs new file mode 100644 index 0000000..acfd554 --- /dev/null +++ b/src/input.rs @@ -0,0 +1,66 @@ +use std::{collections::HashSet, fmt, str::FromStr}; + +use enigo::{Enigo, InputResult, Key, Keyboard}; + +use crate::dir::Dir; + +#[derive(Debug, Clone, Default)] +pub struct Input { + pub dir: Dir, + pub keys: HashSet, + /// Additional frames to hold this for + pub held_frames: u32, +} + +#[derive(Debug)] +pub enum InputFromStrError {} + +impl FromStr for Input { + type Err = InputFromStrError; + + fn from_str(s: &str) -> Result { + if let Some(Ok(dir)) = s.chars().next().map(|c| String::from(c).parse()) { + Ok(Self { + dir, + keys: s.chars().skip(1).collect(), + held_frames: 0, + }) + } else { + Ok(Self { + dir: Dir::default(), + keys: s.chars().collect(), + held_frames: 0, + }) + } + } +} + +impl fmt::Display for Input { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut keys: Vec<_> = self.keys.iter().collect(); + keys.sort_unstable(); + write!(f, "{}", self.dir)?; + for key in keys { + write!(f, "{key}")?; + } + Ok(()) + } +} + +impl Input { + pub fn press(&self, enigo: &mut Enigo) -> InputResult<()> { + self.dir.press(enigo, enigo::Direction::Press)?; + for key in &self.keys { + enigo.key(Key::Unicode(*key), enigo::Direction::Press)?; + } + Ok(()) + } + + pub fn release(&self, enigo: &mut Enigo) -> InputResult<()> { + self.dir.press(enigo, enigo::Direction::Release)?; + for key in &self.keys { + enigo.key(Key::Unicode(*key), enigo::Direction::Release)?; + } + Ok(()) + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..7cd52cb --- /dev/null +++ b/src/main.rs @@ -0,0 +1,28 @@ +use std::{env, error::Error, thread}; + +use enigo::Enigo; + +mod dir; +mod input; +mod timing; + +use crate::input::Input; +use timing::FRAME; + +fn main() -> Result<(), Box> { + let mut enigo = Enigo::new(&enigo::Settings::default())?; + let mut inputs: Vec = env::args().skip(1).map(|s| s.parse().unwrap()).collect(); + inputs.insert(0, Input::default()); + + for input in inputs.windows(2) { + if let [a, b] = input { + println!("Release: {a}, Press: {b}"); + a.release(&mut enigo)?; + b.press(&mut enigo)?; + thread::sleep(FRAME * b.held_frames); + thread::sleep(FRAME); + } + } + + Ok(()) +} diff --git a/src/timing.rs b/src/timing.rs new file mode 100644 index 0000000..d25b217 --- /dev/null +++ b/src/timing.rs @@ -0,0 +1,3 @@ +use std::time::Duration; + +pub const FRAME: Duration = Duration::from_millis(16);