diff --git a/09_Battle/rust/Cargo.lock b/09_Battle/rust/Cargo.lock new file mode 100644 index 00000000..10a59599 --- /dev/null +++ b/09_Battle/rust/Cargo.lock @@ -0,0 +1,75 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "getrandom" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "libc" +version = "0.2.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rust" +version = "0.1.0" +dependencies = [ + "rand", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" diff --git a/09_Battle/rust/Cargo.toml b/09_Battle/rust/Cargo.toml new file mode 100644 index 00000000..3b1d02f5 --- /dev/null +++ b/09_Battle/rust/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "rust" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rand = "0.8.5" diff --git a/09_Battle/rust/src/main.rs b/09_Battle/rust/src/main.rs new file mode 100644 index 00000000..f9e8d36e --- /dev/null +++ b/09_Battle/rust/src/main.rs @@ -0,0 +1,273 @@ +use rand::Rng; +use std::{ + cmp::Ordering, + fmt, + io::{self, Write}, +}; + +#[derive(Clone, Copy)] +#[repr(C)] +enum ShipLength { + Destroyer = 2, + Cruiser = 3, + AircraftCarrier = 4, +} + +#[derive(Clone, Copy, PartialEq)] +struct Point(i8, i8); + +impl core::ops::Add for Point { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self(self.0 + rhs.0, self.1 + rhs.1) + } +} + +impl Point { + pub fn is_outside(&self, width: usize) -> bool { + let w = width as i8; + (!(0..w).contains(&self.0)) || (!(0..w).contains(&self.1)) + } + + pub fn userinput2coordinate(self) -> Self { + Self(self.0 - 1, See::WIDTH as i8 - self.1) + } +} + +struct Ship(Vec); + +impl Ship { + pub fn new(length: ShipLength) -> Self { + 'try_again: loop { + let start = Point( + rand::thread_rng().gen_range(0..See::WIDTH) as i8, + rand::thread_rng().gen_range(0..See::WIDTH) as i8, + ); + let vector = Self::random_vector(); + + let mut ship = vec![start]; + for _ in 1..length as usize { + let last = ship.last().unwrap(); + let new_part = *last + vector; + if new_part.is_outside(See::WIDTH) { + continue 'try_again; + } + ship.push(new_part); + } + + return Self(ship); + } + } + + fn random_vector() -> Point { + loop { + let vector = Point( + rand::thread_rng().gen_range(-1..2), + rand::thread_rng().gen_range(-1..2), + ); + if vector != Point(0, 0) { + return vector; + } + } + } + + pub fn collide(&self, see: &[Vec]) -> bool { + self.0.iter().any(|p| see[p.0 as usize][p.1 as usize] != 0) + } + + pub fn place(self, see: &mut [Vec], code: i8) { + for p in self.0.iter() { + see[p.0 as usize][p.1 as usize] = code; + } + } +} + +enum Report { + Already(i8), + Splash, + Hit(i8), +} + +struct See { + data: Vec>, +} + +impl See { + pub const WIDTH: usize = 6; + + fn place_ship(data: &mut [Vec], length: ShipLength, code: i8) { + let ship = loop { + let ship = Ship::new(length); + if ship.collide(data) { + continue; + } + break ship; + }; + ship.place(data, code); + } + + pub fn new() -> Self { + let mut data = vec![vec![0; Self::WIDTH]; Self::WIDTH]; + + Self::place_ship(&mut data, ShipLength::Destroyer, 1); + Self::place_ship(&mut data, ShipLength::Destroyer, 2); + Self::place_ship(&mut data, ShipLength::Cruiser, 3); + Self::place_ship(&mut data, ShipLength::Cruiser, 4); + Self::place_ship(&mut data, ShipLength::AircraftCarrier, 5); + Self::place_ship(&mut data, ShipLength::AircraftCarrier, 6); + + Self { data } + } + + pub fn report(&mut self, point: Point) -> Report { + let (x, y) = (point.0 as usize, point.1 as usize); + let value = self.data[x][y]; + match value.cmp(&0) { + Ordering::Less => Report::Already(-value), + Ordering::Equal => Report::Splash, + Ordering::Greater => { + self.data[x][y] = -value; + Report::Hit(value) + } + } + } + + pub fn has_ship(&self, code: i8) -> bool { + self.data.iter().any(|v| v.contains(&code)) + } + + pub fn has_any_ship(&self) -> bool { + (1..=6).any(|c| self.has_ship(c)) + } + + pub fn count_sunk(&self, ship: ShipLength) -> i32 { + let codes = match ship { + ShipLength::Destroyer => (1, 2), + ShipLength::Cruiser => (3, 4), + ShipLength::AircraftCarrier => (5, 6), + }; + + let ret = if self.has_ship(codes.0) { 0 } else { 1 }; + ret + if self.has_ship(codes.1) { 0 } else { 1 } + } +} + +impl fmt::Display for See { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for row in &self.data { + write!(f, "\r\n")?; + for cell in row { + write!(f, "{:2} ", cell)?; + } + } + write!(f, "\r\n") + } +} + +fn input_point() -> Result { + let mut input = String::new(); + io::stdin() + .read_line(&mut input) + .expect("Failed to read line"); + let point_str: Vec<&str> = input.trim().split(',').collect(); + + if point_str.len() != 2 { + return Err(()); + } + + let x = point_str[0].parse::().map_err(|_| ())?; + let y = point_str[1].parse::().map_err(|_| ())?; + + Ok(Point(x, y)) +} + +fn get_next_target() -> Point { + loop { + print!("? "); + let _ = io::stdout().flush(); + + if let Ok(p) = input_point() { + let p = p.userinput2coordinate(); + if !p.is_outside(See::WIDTH) { + return p; + } + } + + println!( + "INVALID. SPECIFY TWO NUMBERS FROM 1 TO {}, SEPARATED BY A COMMA.", + See::WIDTH + ); + } +} + +fn main() { + let mut see = See::new(); + println!( + " + BATTLE +CREATIVE COMPUTING MORRISTOWN, NEW JERSEY + +THE FOLLOWING CODE OF THE BAD GUYS' FLEET DISPOSITION +HAS BEEN CAPTURED BUT NOT DECODED: + " + ); + + println!("{see}"); + + println!( + " + +DE-CODE IT AND USE IT IF YOU CAN +BUT KEEP THE DE-CODING METHOD A SECRET. + +START GAME + " + ); + + let mut splashes = 0; + let mut hits = 0; + + loop { + let target = get_next_target(); + + let r = see.report(target); + if let Report::Hit(c) = r { + println!("A DIRECT HIT ON SHIP NUMBER {c}"); + hits += 1; + + if !see.has_ship(c) { + println!("AND YOU SUNK IT. HURRAH FOR THE GOOD GUYS."); + println!("SO FAR, THE BAD GUYS HAVE LOST"); + println!("{} DESTROYER(S),", see.count_sunk(ShipLength::Destroyer)); + println!("{} CRUISER(S),", see.count_sunk(ShipLength::Cruiser)); + println!( + "AND {} AIRCRAFT CARRIER(S),", + see.count_sunk(ShipLength::AircraftCarrier) + ); + } + } else { + if let Report::Already(c) = r { + println!("YOU ALREADY PUT A HOLE IN SHIP NUMBER {c} AT THAT POINT."); + } + println!("SPLASH! TRY AGAIN."); + splashes += 1; + continue; + } + + if see.has_any_ship() { + println!("YOUR CURRENT SPLASH/HIT RATIO IS {splashes}/{hits}"); + continue; + } + + println!("YOU HAVE TOTALLY WIPED OUT THE BAD GUYS' FLEET "); + println!("WITH A FINAL SPLASH/HIT RATIO OF {splashes}/{hits}"); + + if splashes == 0 { + println!("CONGRATULATIONS -- A DIRECT HIT EVERY TIME."); + } + + println!("\n****************************"); + break; + } +} diff --git a/README.md b/README.md index 31a2ada8..f714bc89 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ NOTE: per [the official blog post announcement](https://blog.codinghorror.com/up | 06_Banner | x | x | x | | | x | x | x | x | x | | 07_Basketball | x | x | x | | | x | x | x | | x | | 08_Batnum | x | x | x | | | x | x | x | x | x | -| 09_Battle | x | x | x | | | | x | | | x | +| 09_Battle | x | x | x | | | | x | | x | x | | 10_Blackjack | x | x | x | | | | x | x | x | x | | 11_Bombardment | x | x | x | | | x | x | x | x | x | | 12_Bombs_Away | x | x | x | | x | x | x | | | x |