Merge pull request #911 from apeng2012/09-battle-rust

Port 09_Battle to Rust
This commit is contained in:
Jeff Atwood
2023-12-28 15:03:33 -08:00
committed by GitHub
4 changed files with 358 additions and 1 deletions

75
09_Battle/rust/Cargo.lock generated Normal file
View File

@@ -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"

View File

@@ -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"

273
09_Battle/rust/src/main.rs Normal file
View File

@@ -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<Point>);
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<i8>]) -> bool {
self.0.iter().any(|p| see[p.0 as usize][p.1 as usize] != 0)
}
pub fn place(self, see: &mut [Vec<i8>], 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<Vec<i8>>,
}
impl See {
pub const WIDTH: usize = 6;
fn place_ship(data: &mut [Vec<i8>], 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<Point, ()> {
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::<i8>().map_err(|_| ())?;
let y = point_str[1].parse::<i8>().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;
}
}

View File

@@ -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 |