From 590995f9695870008acee8531cbb4c3b4f31dae2 Mon Sep 17 00:00:00 2001 From: iris Date: Fri, 15 May 2026 19:55:37 -0400 Subject: [PATCH] initial commit --- .gitignore | 1 + Cargo.lock | 7 +++ Cargo.toml | 6 +++ src/data.rs | 64 +++++++++++++++++++++++ src/main.rs | 141 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/solve.rs | 53 +++++++++++++++++++ 6 files changed, 272 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/data.rs create mode 100644 src/main.rs create mode 100644 src/solve.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..00ba394 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "wynn-mounts" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0856736 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "wynn-mounts" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/src/data.rs b/src/data.rs new file mode 100644 index 0000000..14937de --- /dev/null +++ b/src/data.rs @@ -0,0 +1,64 @@ +pub type Int = i16; + +#[derive(Clone, Copy, Default, PartialEq, Debug)] +pub struct Mount { + pub speed: Int, + pub accel: Int, + pub altitude: Int, + pub energy: Int, + pub handling: Int, + pub toughness: Int, + pub boost: Int, + pub training: Int, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum Mat { + Ingot, + Gem, + Wood, + Paper, + String, + Grains, + Oil, + Meat, +} + +macro_rules! mtch { + ($struct:ident $($mat:ident {$($field:ident $amt:expr)*},)*) => { + impl $struct { + pub fn apply(&mut self, mat: Mat) { + match mat { + $(Mat::$mat => { + $(self.$field -= $amt;)* + },)* + } + } + pub fn undo(&mut self, mat: Mat) { + match mat { + $(Mat::$mat => { + $(self.$field += $amt;)* + },)* + } + } + pub fn useless(&self, mat: Mat) -> bool { + match mat { + $(Mat::$mat => { + $(self.$field <= 0 &&)* true + },)* + } + } + } + }; +} + +mtch! { Mount + Ingot { energy 4 toughness 8 }, + Gem { speed 4 energy 2 training 6 }, + Wood { speed 2 accel 6 toughness 4 }, + Paper { altitude 8 boost 4 }, + String { accel 2 handling 4 boost 6 }, + Grains { speed 8 altitude 4 }, + Oil { altitude 2 handling 6 training 4 }, + Meat { accel 4 energy 8 }, +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..0c037d7 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,141 @@ +mod data; +mod solve; +pub use data::*; +pub use solve::*; + +fn main() { + let mut priority = vec![ + Mat::Wood, + Mat::Paper, + Mat::Grains, + Mat::String, + Mat::Oil, + Mat::Meat, + Mat::Ingot, + Mat::Gem, + ]; + print_priority(&priority); + let input = std::io::stdin(); + for line in input.lines() { + let Ok(line) = line else { + println!("error reading line"); + continue; + }; + let Some((cmd, args)) = line.split_once(" ") else { + println!("space expected after command"); + continue; + }; + match cmd { + "solve" => run_solve(args, &priority), + "priority" => run_priority(args, &mut priority), + _ => { + println!("unknown command"); + } + } + } +} + +fn run_solve(args: &str, priority: &Priority) { + let parts = args.split(" "); + let mut attrs = [0; 8]; + let mut i = 0; + for part in parts { + if part.is_empty() { + continue; + } + if i == 8 { + i += 1; + break; + } + let Some((limit, max)) = part.split_once("/") else { + println!("bad stat input"); + return; + }; + let Ok(limit): Result = limit.parse() else { + println!("bad stat input"); + return; + }; + let Ok(max): Result = max.parse() else { + println!("bad stat input"); + return; + }; + attrs[i] = max - limit; + i += 1; + } + if i != 8 { + println!("wrong number of parts ({i}, need 8)"); + return; + } + let mount = Mount { + speed: attrs[0], + accel: attrs[1], + altitude: attrs[2], + energy: attrs[3], + handling: attrs[4], + toughness: attrs[5], + boost: attrs[6], + training: attrs[7], + }; + println!("requirements: {mount:?}"); + let res = solve(mount, priority); + println!("{}: {res:?}", res.len()); +} + +fn run_priority(args: &str, priority: &mut Priority) { + let parts = args.split(" "); + let mut new = Vec::new(); + for (i, part) in parts.enumerate() { + if part.is_empty() { + continue; + } + if i == 8 { + println!("too many materials provided"); + return; + } + new.push(match part.to_lowercase().as_str() { + "ingot" => Mat::Ingot, + "gem" => Mat::Gem, + "wood" => Mat::Wood, + "paper" => Mat::Paper, + "string" => Mat::String, + "grains" => Mat::Grains, + "oil" => Mat::Oil, + "meat" => Mat::Meat, + _ => { + println!("unknown material {part}"); + continue; + } + }); + } + if validate_priority(&new) { + *priority = new; + print_priority(priority); + } else { + println!("invalid priority (2+ of a material)"); + } +} + +fn print_priority(priority: &Priority) { + println!("priority:"); + for (i, mat) in priority.iter().enumerate() { + println!(" {}: {mat:?}", i + 1); + } +} + +fn validate_priority(priority: &Priority) -> bool { + let mut used = std::collections::HashSet::new(); + for p in priority { + if !used.insert(p) { + return false; + } + } + true +} + +fn usage() { + println!("Usage:"); + println!(" > solve 41/63 50/60 55/55 43/64 60/66 51/55 48/59 41/63"); + println!(" solves for the least number of materials needed given a priority"); + println!(" > priority wood paper grains string oil meat"); + println!(" sets the material priority for solving (what to use and what to try first)"); +} diff --git a/src/solve.rs b/src/solve.rs new file mode 100644 index 0000000..d0721fd --- /dev/null +++ b/src/solve.rs @@ -0,0 +1,53 @@ +pub use crate::data::*; + +pub fn solve(mut mount: Mount, priority: &Priority) -> Vec { + let mut best = Vec::new(); + let mut stack = vec![0]; + loop { + let mut mat_i = *stack.last().unwrap(); + let mat = priority[mat_i]; + if !mount.useless(mat) { + mount.apply(mat); + if mount.finished() { + if best.is_empty() || stack.len() < best.len() { + best = stack.clone(); + } + } else if best.is_empty() || stack.len() + 1 < best.len() { + stack.push(mat_i); + continue; + } + mount.undo(mat); + } + while mat_i == priority.len() - 1 { + stack.pop(); + if let Some(&prev) = stack.last() { + mat_i = prev; + } else { + return best.into_iter().map(|i| priority[i]).collect(); + } + mount.undo(priority[mat_i]); + } + *stack.last_mut().unwrap() += 1; + } +} + +impl Mat { + pub fn first() -> Self { + Self::Wood + } +} + +impl Mount { + pub fn finished(&self) -> bool { + self.speed <= 0 + && self.accel <= 0 + && self.altitude <= 0 + && self.energy <= 0 + && self.handling <= 0 + && self.toughness <= 0 + && self.boost <= 0 + && self.training <= 0 + } +} + +pub type Priority = Vec;