mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2026-02-04 19:12:07 -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 |
|
| 06_Banner | x | x | x | | | x | x | x | x | x |
|
||||||
| 07_Basketball | 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 |
|
| 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 |
|
| 10_Blackjack | x | x | x | | | | x | x | x | x |
|
||||||
| 11_Bombardment | x | 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 |
|
| 12_Bombs_Away | x | x | x | | x | x | x | | | x |
|
||||||
|
|||||||
Reference in New Issue
Block a user