mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2026-02-04 11:07:59 -08:00
Merge pull request #911 from apeng2012/09-battle-rust
Port 09_Battle to Rust
This commit is contained in:
75
09_Battle/rust/Cargo.lock
generated
Normal file
75
09_Battle/rust/Cargo.lock
generated
Normal 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"
|
||||
9
09_Battle/rust/Cargo.toml
Normal file
9
09_Battle/rust/Cargo.toml
Normal 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
273
09_Battle/rust/src/main.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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 |
|
||||
|
||||
Reference in New Issue
Block a user