mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-12 15:50:20 -08:00
Merge pull request #895 from GKnirps/implement_king
Implement King in rust
This commit is contained in:
16
53_King/rust/Cargo.lock
generated
Normal file
16
53_King/rust/Cargo.lock
generated
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fastrand"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "king"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"fastrand",
|
||||||
|
]
|
||||||
9
53_King/rust/Cargo.toml
Normal file
9
53_King/rust/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "king"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
fastrand = "^2.0.0"
|
||||||
28
53_King/rust/README.md
Normal file
28
53_King/rust/README.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
King
|
||||||
|
====
|
||||||
|
|
||||||
|
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
|
||||||
|
|
||||||
|
Conversion to [rust](https://www.rust-lang.org/).
|
||||||
|
|
||||||
|
Porting Notes
|
||||||
|
-------------
|
||||||
|
|
||||||
|
### Floats
|
||||||
|
|
||||||
|
The original code implicitly uses floating point numbers in many places which are explicitly cast to integers. In this port, I avoided using floats and tried to replicate the behaviour using just integers. It is possible that I missed some places where rounding a value would have made a difference. If you find such a bug, please notify me or make implement a fix yourself.
|
||||||
|
|
||||||
|
### Signed Numbers
|
||||||
|
|
||||||
|
I used unsigned integers for most of the program because it was easier than to check for negative values all the time. Unfortunately, that made the code a bit whacky in one or two places.
|
||||||
|
|
||||||
|
Since I only allow input of positive numbers, it is not possible to exit the game when entering the stats to resume a game, which would be possible by entering negative numbers in the original game.
|
||||||
|
|
||||||
|
### Bugs
|
||||||
|
|
||||||
|
I tried to fix all bugs listed in the [main README for King](../README.md). I have tested this implementation a bit but not extensively, so there may be some portation bugs. If you find them, you are free to fix them.
|
||||||
|
|
||||||
|
Future Development
|
||||||
|
------------------
|
||||||
|
|
||||||
|
I plan to add some tests and tidy up the code a bit, but this version should be feature-complete.
|
||||||
577
53_King/rust/src/main.rs
Normal file
577
53_King/rust/src/main.rs
Normal file
@@ -0,0 +1,577 @@
|
|||||||
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
|
use fastrand::Rng;
|
||||||
|
use std::io;
|
||||||
|
use std::io::{stdin, stdout, BufRead, Write};
|
||||||
|
|
||||||
|
// global variable `N5` in the original game
|
||||||
|
const TERM_LENGTH: u32 = 8;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut rng = Rng::new();
|
||||||
|
let mut input = stdin().lock();
|
||||||
|
let mut state = intro(&mut input, &mut rng).expect("input error");
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let land_price = 95 + rng.u32(0..10);
|
||||||
|
let plant_price = 10 + rng.u32(0..5);
|
||||||
|
print_state(&state, land_price, plant_price);
|
||||||
|
state = match next_round(&mut input, &mut rng, &state, land_price, plant_price)
|
||||||
|
.expect("input error")
|
||||||
|
{
|
||||||
|
RoundEnd::Next(s) => s,
|
||||||
|
RoundEnd::GameOver(msg) => {
|
||||||
|
println!("{}", msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The game is round based (one round per in-game year).
|
||||||
|
// This struct represents the state before each round
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
struct State {
|
||||||
|
// global variable `A` in the original game (currency: "rallods")
|
||||||
|
money: u32,
|
||||||
|
// global variable `B` in the original game
|
||||||
|
countrymen: u32,
|
||||||
|
// global variable `C` in the original game
|
||||||
|
foreign_workers: u32,
|
||||||
|
// global variable `D` in the original game
|
||||||
|
land: u32,
|
||||||
|
// global variable `X5` in original game
|
||||||
|
year_in_office: u32,
|
||||||
|
// global variable `X` in original game
|
||||||
|
show_land_hint: bool,
|
||||||
|
// global variable `V3` in original game
|
||||||
|
previous_tourist_trade: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
enum RoundEnd {
|
||||||
|
GameOver(String),
|
||||||
|
Next(State),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_state(rng: &mut Rng) -> State {
|
||||||
|
State {
|
||||||
|
// the original formula for random values used floating point numbers.
|
||||||
|
// e.g. `INT(60000+(1000*RND(1))-(1000*RND(1)))`
|
||||||
|
// I want to avoid floats unless necessary. These values generated here should be close
|
||||||
|
// enough to the original distribution
|
||||||
|
money: 60000 + rng.u32(0..1000) - rng.u32(0..1000),
|
||||||
|
countrymen: 500 + rng.u32(0..10) - rng.u32(0..10),
|
||||||
|
foreign_workers: 0,
|
||||||
|
land: 2000,
|
||||||
|
year_in_office: 0,
|
||||||
|
show_land_hint: true,
|
||||||
|
previous_tourist_trade: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_state(state: &State, land_price: u32, plant_price: u32) {
|
||||||
|
print!(
|
||||||
|
r"
|
||||||
|
YOU NOW HAVE {} RALLODS IN THE TREASURY.
|
||||||
|
{} COUNTRYMEN, ",
|
||||||
|
state.money, state.countrymen
|
||||||
|
);
|
||||||
|
if state.foreign_workers > 0 {
|
||||||
|
print!("{} FOREIGN WORKERS, ", state.foreign_workers)
|
||||||
|
}
|
||||||
|
println!(
|
||||||
|
r"AND {} SQ. MILES OF LAND.
|
||||||
|
THIS YEAR INDUSTRY WILL BUY LAND FOR {} RALLODS PER SQUARE MILE.
|
||||||
|
LAND CURRENTLY COSTS {} RALLODS PER SQUARE MILE TO PLANT.
|
||||||
|
",
|
||||||
|
state.land, land_price, plant_price
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// print the intro, optional instructions or a previous savegame
|
||||||
|
fn intro<R: BufRead>(mut input: R, rng: &mut Rng) -> io::Result<State> {
|
||||||
|
println!("⚠️ This game includes references to suicide or self-harm.");
|
||||||
|
println!(" KING");
|
||||||
|
println!(" CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n");
|
||||||
|
print!("DO YOU WANT INSTRUCTIONS?? ");
|
||||||
|
// In the original game, all inputs were made in the same line as the previous output.
|
||||||
|
// I try to replicate this behaviour here, but if I do not print a line break, the stdout buffer
|
||||||
|
// will not be flushed and the user may not see the input prompt before the input.
|
||||||
|
// this means in these cases I have to explicitly flush stdout.
|
||||||
|
stdout().flush()?;
|
||||||
|
let mut buf = String::with_capacity(16);
|
||||||
|
input.read_line(&mut buf)?;
|
||||||
|
buf.make_ascii_lowercase();
|
||||||
|
match buf.trim() {
|
||||||
|
"n" => Ok(init_state(rng)),
|
||||||
|
"again" => {
|
||||||
|
let year_in_office = read_and_verify_int(
|
||||||
|
&mut input,
|
||||||
|
&mut buf,
|
||||||
|
"HOW MANY YEARS HAD YOU BEEN IN OFFICE WHEN INTERRUPTED?? ",
|
||||||
|
|v| {
|
||||||
|
if v < 8 {
|
||||||
|
Ok(v)
|
||||||
|
} else {
|
||||||
|
Err(format!(
|
||||||
|
" COME ON, YOUR TERM IN OFFICE IS ONLY {} YEARS.",
|
||||||
|
TERM_LENGTH
|
||||||
|
))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
// The original game exits here when you enter a negative number for any of
|
||||||
|
// the following values. This looks like intentional behaviour. However, to replicate that
|
||||||
|
// I would have to change everything to signed number which I do not want right now.
|
||||||
|
print!("HOW MUCH DID YOU HAVE IN THE TREASURY?? ");
|
||||||
|
stdout().flush()?;
|
||||||
|
let money = read_int(&mut input, &mut buf)?;
|
||||||
|
print!("HOW MANY COUNTRYMEN?? ");
|
||||||
|
stdout().flush()?;
|
||||||
|
let countrymen = read_int(&mut input, &mut buf)?;
|
||||||
|
print!("HOW MANY WORKERS?? ");
|
||||||
|
stdout().flush()?;
|
||||||
|
let foreign_workers = read_int(&mut input, &mut buf)?;
|
||||||
|
let land = read_and_verify_int(
|
||||||
|
&mut input,
|
||||||
|
&mut buf,
|
||||||
|
"HOW MANY SQUARE MILES OF LAND?? ",
|
||||||
|
|v| {
|
||||||
|
if !(1000..=2000).contains(&v) {
|
||||||
|
// Note: the original says "10,000 SQ. MILES OF FOREST LAND", but this is
|
||||||
|
// inconsistent and listed as Bug 3 in the README.md
|
||||||
|
Err(" COME ON, YOU STARTED WITH 1000 SQ. MILES OF FARM LAND\n AND 1000 SQ. MILES OF FOREST LAND.".to_owned())
|
||||||
|
} else {
|
||||||
|
Ok(v)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
Ok(State {
|
||||||
|
money,
|
||||||
|
countrymen,
|
||||||
|
foreign_workers,
|
||||||
|
land,
|
||||||
|
year_in_office,
|
||||||
|
show_land_hint: true,
|
||||||
|
previous_tourist_trade: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
println!(
|
||||||
|
r"
|
||||||
|
CONGRATULATIONS! YOU'VE JUST BEEN ELECTED PREMIER OF SETATS
|
||||||
|
DETINU, A SMALL COMMUNIST ISLAND 30 BY 70 MILES LONG. YOUR
|
||||||
|
JOB IS TO DECIDE UPON THE CONTRY'S BUDGET AND DISTRIBUTE
|
||||||
|
MONEY TO YOUR COUNTRYMEN FROM THE COMMUNAL TREASURY.
|
||||||
|
THE MONEY SYSTEM IS RALLODS, AND EACH PERSON NEEDS 100
|
||||||
|
RALLODS PER YEAR TO SURVIVE. YOUR COUNTRY'S INCOME COMES
|
||||||
|
FROM FARM PRODUCE AND TOURISTS VISITING YOUR MAGNIFICENT
|
||||||
|
FORESTS, HUNTING, FISHING, ETC. HALF YOUR LAND IS FARM LAND
|
||||||
|
WHICH ALSO HAS AN EXCELLENT MINERAL CONTENT AND MAY BE SOLD
|
||||||
|
TO FOREIGN INDUSTRY (STRIP MINING) WHO IMPORT AND SUPPORT
|
||||||
|
THEIR OWN WORKERS. CROPS COST BETWEEN 10 AND 15 RALLODS PER
|
||||||
|
SQUARE MILE TO PLANT.
|
||||||
|
YOUR GOAL IS TO COMPLETE YOUR {} YEAR TERM OF OFFICE.
|
||||||
|
GOOD LUCK!
|
||||||
|
",
|
||||||
|
TERM_LENGTH
|
||||||
|
);
|
||||||
|
Ok(init_state(rng))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static POLLUTION: &[&str] = &[
|
||||||
|
"FISH POPULATION HAS DWINDLED DUE TO WATER POLLUTION.",
|
||||||
|
"AIR POLLUTION IS KILLING GAME BIRD POPULATION.",
|
||||||
|
"MINERAL BATHS ARE BEING RUINED BY WATER POLLUTION.",
|
||||||
|
"UNPLEASANT SMOG IS DISCOURAGING SUN BATHERS.",
|
||||||
|
"HOTELS ARE LOOKING SHABBY DUE TO SMOG GRIT.",
|
||||||
|
];
|
||||||
|
|
||||||
|
fn next_round<R: BufRead>(
|
||||||
|
mut input: R,
|
||||||
|
rng: &mut Rng,
|
||||||
|
state: &State,
|
||||||
|
land_price: u32,
|
||||||
|
plant_price: u32,
|
||||||
|
) -> io::Result<RoundEnd> {
|
||||||
|
let mut buf = String::with_capacity(16);
|
||||||
|
let mut show_land_hint = state.show_land_hint;
|
||||||
|
// global variable `H` in the original game
|
||||||
|
let land_sold = read_and_verify_int(
|
||||||
|
&mut input,
|
||||||
|
&mut buf,
|
||||||
|
"HOW MANY SQUARE MILES DO YOU WISH TO SELL TO INDUSTRY?? ",
|
||||||
|
|v| {
|
||||||
|
if v + 1000 <= state.land {
|
||||||
|
Ok(v)
|
||||||
|
} else if show_land_hint {
|
||||||
|
show_land_hint = false;
|
||||||
|
Err(format!(
|
||||||
|
r"*** THINK AGAIN. YOU ONLY HAVE {} SQUARE MILES OF FARM LAND.
|
||||||
|
(FOREIGN INDUSTRY WILL ONLY BUY FARM LAND BECAUSE
|
||||||
|
FOREST LAND IS UNECONOMICAL TO STRIP MINE DUE TO TREES,
|
||||||
|
THICKER TOP SOIL, ETC.)",
|
||||||
|
state.land - 1000
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Err(format!(
|
||||||
|
r"*** THINK AGAIN. YOU ONLY HAVE {} SQUARE MILES OF FARM LAND.\n",
|
||||||
|
state.land - 1000
|
||||||
|
))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let land = state.land - land_sold;
|
||||||
|
let money = state.money + land_sold * land_price;
|
||||||
|
|
||||||
|
// global variable `I` in the original game
|
||||||
|
let money_distributed = read_and_verify_int(
|
||||||
|
&mut input,
|
||||||
|
&mut buf,
|
||||||
|
"HOW MANY RALLODS WILL YOU DISTRIBUTE AMONG YOUR COUNTRYMEN?? ",
|
||||||
|
|v| {
|
||||||
|
if v <= money {
|
||||||
|
Ok(v)
|
||||||
|
} else {
|
||||||
|
Err(format!(
|
||||||
|
" THINK AGAIN. YOU'VE ONLY {} RALLODS IN THE TREASURY",
|
||||||
|
money
|
||||||
|
))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
let money = money - money_distributed;
|
||||||
|
|
||||||
|
// global variable `J` in the original game
|
||||||
|
let land_planted = if money > 0 {
|
||||||
|
read_and_verify_int(
|
||||||
|
&mut input,
|
||||||
|
&mut buf,
|
||||||
|
"HOW MANY SQUARE MILES DO YOU WISH TO PLANT?? ",
|
||||||
|
|v| {
|
||||||
|
if v > 2 * state.countrymen {
|
||||||
|
Err(" SORRY, BUT EACH COUNTRYMAN CAN ONLY PLANT 2 SQ. MILES.".to_owned())
|
||||||
|
} else if v + 1000 > land {
|
||||||
|
Err(format!(
|
||||||
|
" SORRY, BUT YOU'VE ONLY {} SQ. MILES OF FARM LAND.",
|
||||||
|
land - 1000
|
||||||
|
))
|
||||||
|
} else if v * plant_price > money {
|
||||||
|
Err(format!(
|
||||||
|
" THINK AGAIN. YOU'VE ONLY {} RALLODS LEFT IN THE TREASURY.",
|
||||||
|
money
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(v)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)?
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
let money = money - land_planted * plant_price;
|
||||||
|
|
||||||
|
// global variable `K` in the original game
|
||||||
|
let pollution_control = if money > 0 {
|
||||||
|
read_and_verify_int(
|
||||||
|
&mut input,
|
||||||
|
&mut buf,
|
||||||
|
"HOW MANY RALLODS DO YOU WISH TO SPEND ON POLLUTION CONTROL?? ",
|
||||||
|
|v| {
|
||||||
|
if v <= money {
|
||||||
|
Ok(v)
|
||||||
|
} else {
|
||||||
|
Err(format!(
|
||||||
|
" THINK AGAIN. YOU ONLY HAVE {} RALLODS REMAINING.",
|
||||||
|
money
|
||||||
|
))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)?
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
let money = money - pollution_control;
|
||||||
|
|
||||||
|
if land_sold == 0 && money_distributed == 0 && land_planted == 0 && pollution_control == 0 {
|
||||||
|
return Ok(RoundEnd::GameOver(
|
||||||
|
r"
|
||||||
|
GOODBYE.
|
||||||
|
(IF YOU WISH TO CONTINUE THIS GAME AT A LATER DATE, ANSWER
|
||||||
|
'AGAIN' WHEN ASKED IF YOU WANT INSTRUCTIONS AT THE START
|
||||||
|
OF THE GAME)."
|
||||||
|
.to_owned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("\n\n");
|
||||||
|
|
||||||
|
let money_after_expenses = money;
|
||||||
|
|
||||||
|
let starvation_deaths = state.countrymen.saturating_sub(money_distributed / 100);
|
||||||
|
if starvation_deaths > 0 {
|
||||||
|
println!("{starvation_deaths} COUNTRYMEN DIED OF STARVATION");
|
||||||
|
}
|
||||||
|
|
||||||
|
// the original was using `RND(1)` as factor, but I do not want to deal with floats.
|
||||||
|
// this solution should do the same in the range of numbers we expect
|
||||||
|
let pollution = ((2000 - land) * rng.u32(0..=2000)) / 2000;
|
||||||
|
let pollution_deaths = if pollution_control >= 25 {
|
||||||
|
pollution / (pollution_control / 25)
|
||||||
|
} else {
|
||||||
|
pollution
|
||||||
|
};
|
||||||
|
|
||||||
|
if pollution_deaths > 0 {
|
||||||
|
println!(
|
||||||
|
"{} COUNTRYMEN DIED OF CARBON-MONOXIDE AND DUST INHALATION",
|
||||||
|
pollution_deaths
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (money, land) = if pollution_deaths + starvation_deaths > 0 {
|
||||||
|
let funeral_costs = (pollution_deaths + starvation_deaths) * 9;
|
||||||
|
println!(" YOU WERE FORCED TO SPEND {funeral_costs} RALLODS ON FUNERAL EXPENSES");
|
||||||
|
if funeral_costs > money {
|
||||||
|
println!(" INSUFFICIENT RESERVES TO COVER COST - LAND WAS SOLD");
|
||||||
|
(
|
||||||
|
0,
|
||||||
|
// I only handle integers here, but I think the basic code implicitly turns integers
|
||||||
|
// to floats on division. So in order to round up to the next full land unit, I have
|
||||||
|
// to do this weird modulo stuff here
|
||||||
|
land - funeral_costs / land_price
|
||||||
|
+ if funeral_costs % land_price == 0 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(money - funeral_costs, land)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(money, land)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut countrymen = state
|
||||||
|
.countrymen
|
||||||
|
.saturating_sub(starvation_deaths)
|
||||||
|
.saturating_sub(pollution_deaths);
|
||||||
|
|
||||||
|
let tourist_trade_positive = countrymen * 22 + rng.u32(0..500);
|
||||||
|
let tourist_trade_negative = (2000 - land) * 15;
|
||||||
|
|
||||||
|
let new_foreign_workers: i32 = if land_sold > 0 {
|
||||||
|
land_sold as i32 + rng.i32(0..10) - rng.i32(0..20)
|
||||||
|
+ if state.foreign_workers == 0 { 20 } else { 0 }
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
// In theory, we could come up with negative foreign workers here (and in the original game)
|
||||||
|
// This does not seem to be right, so let's cap them at 0
|
||||||
|
let foreign_workers = if new_foreign_workers < 0 {
|
||||||
|
state
|
||||||
|
.foreign_workers
|
||||||
|
.saturating_sub(new_foreign_workers.unsigned_abs())
|
||||||
|
} else {
|
||||||
|
state.foreign_workers + new_foreign_workers as u32
|
||||||
|
};
|
||||||
|
print!(" {new_foreign_workers} WORKERS CAME TO THE COUNTRY AND ");
|
||||||
|
|
||||||
|
let countryman_migration = (money_distributed as i32 / 100 - countrymen as i32) / 10
|
||||||
|
+ pollution_control as i32 / 25
|
||||||
|
- (2000 - land as i32) / 50
|
||||||
|
- pollution_deaths as i32 / 2;
|
||||||
|
|
||||||
|
if countryman_migration < 0 {
|
||||||
|
println!("{} COUNTRYMEN LEFT THE ISLAND", countryman_migration.abs());
|
||||||
|
countrymen = countrymen.saturating_sub(countryman_migration.unsigned_abs())
|
||||||
|
} else {
|
||||||
|
println!("{countryman_migration} COUNTRYMEN CAME TO THE ISLAND");
|
||||||
|
countrymen += countryman_migration as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
let harvest_lost = ((2000 - land) * (rng.u32(0..2000) + 3000)) / 4000;
|
||||||
|
// in the original game, this checked for foreign_workers == 0 instead of land_planted == 0
|
||||||
|
// this is documented as Bug 4 in the README.md
|
||||||
|
if land_planted == 0 {
|
||||||
|
print!("OF {land_planted} SQ. MILES PLANTED,");
|
||||||
|
}
|
||||||
|
println!(
|
||||||
|
" YOU HARVESTED {} SQ. MILES OF CROPS.",
|
||||||
|
land_planted.saturating_sub(harvest_lost)
|
||||||
|
);
|
||||||
|
|
||||||
|
if harvest_lost > 0 {
|
||||||
|
// There was a bug here in the original code (Bug 2 in README.md).
|
||||||
|
// Based on the variable `V1`, the word `INCREASED` was inserted.
|
||||||
|
// However, no value was ever assigned to `V1`. Since the pollution comes from land that has
|
||||||
|
// been sold, I used the land difference to check whether the pollution increased.
|
||||||
|
if state.land < land {
|
||||||
|
println!(" (DUE TO INCREASED AIR AND WATER POLLUTION FROM FOREIGN INDUSTRY.)");
|
||||||
|
} else {
|
||||||
|
println!(" (DUE TO AIR AND WATER POLLUTION FROM FOREIGN INDUSTRY.)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let crop_winnings = land_planted.saturating_sub(harvest_lost) * land_price / 2;
|
||||||
|
println!("MAKING {crop_winnings} RALLODS.");
|
||||||
|
let money = money + crop_winnings;
|
||||||
|
|
||||||
|
// In the original game, there are two bugs here (documented as Bug 1 and Bug 5 in the README.md)
|
||||||
|
// The first one made the game ignore the income from tourists and duplicated the money that was
|
||||||
|
// left at this point (the "DECREASE BECAUSE"-message would never have been shown).
|
||||||
|
// The second bug made the tourist trade profitable again is the pollution was too high (i.e.
|
||||||
|
// if `tourist_trade_positive` was less than `tourist_trade_negative`
|
||||||
|
let tourist_trade = tourist_trade_positive.saturating_sub(tourist_trade_negative);
|
||||||
|
println!(" YOU MADE {tourist_trade} RALLODS FROM TOURIST TRADE.");
|
||||||
|
if tourist_trade < state.previous_tourist_trade {
|
||||||
|
println!(
|
||||||
|
" DECREASE BECAUSE {}",
|
||||||
|
POLLUTION[rng.usize(0..POLLUTION.len())]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let money = money + tourist_trade;
|
||||||
|
|
||||||
|
if starvation_deaths + pollution_deaths > 200 {
|
||||||
|
let reason = rng.u8(0..10);
|
||||||
|
return Ok(RoundEnd::GameOver(format!(
|
||||||
|
r"
|
||||||
|
|
||||||
|
{} COUNTRYMEN DIED IN ONE YEAR!!!!!
|
||||||
|
DUE TO THIS EXTREME MISMANAGEMENT, YOU HAVE NOT ONLY
|
||||||
|
BEEN IMPEACHED AND THROWN OUT OF OFFICE, BUT YOU
|
||||||
|
{}
|
||||||
|
",
|
||||||
|
starvation_deaths + pollution_deaths,
|
||||||
|
// The reasons are not equally probable in the original game.
|
||||||
|
// I wonder if this was intentional.
|
||||||
|
if reason <= 3 {
|
||||||
|
"ALSO HAD YOUR LEFT EYE GOUGED OUT!"
|
||||||
|
} else if reason <= 6 {
|
||||||
|
"HAVE ALSO GAINED A VERY BAD REPUTATION."
|
||||||
|
} else {
|
||||||
|
"HAVE ALSO BEEN DECLARED NATIONAL FINK."
|
||||||
|
}
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
if countrymen < 343 {
|
||||||
|
// This is not entirely fair, it is possible that some of them just left, not died.
|
||||||
|
// Also: the initial number of countrymen varies a bit, but this boundary is fix, so it is
|
||||||
|
// not always a third
|
||||||
|
return Ok(RoundEnd::GameOver(format!(
|
||||||
|
r"
|
||||||
|
|
||||||
|
OVER ONE THIRD OF THE POPULTATION HAS DIED SINCE YOU
|
||||||
|
WERE ELECTED TO OFFICE. THE PEOPLE (REMAINING)
|
||||||
|
HATE YOUR GUTS.
|
||||||
|
{}
|
||||||
|
",
|
||||||
|
departure_flavour(rng)
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
if money_after_expenses / 100 > 5 && starvation_deaths >= 2 {
|
||||||
|
return Ok(RoundEnd::GameOver(
|
||||||
|
r"
|
||||||
|
MONEY WAS LEFT OVER IN THE TREASURY WHICH YOU DID
|
||||||
|
NOT SPEND. AS A RESULT, SOME OF YOUR COUNTRYMEN DIED
|
||||||
|
OF STARVATION. THE PUBLIC IS ENRAGED AND YOU HAVE
|
||||||
|
BEEN FORCED TO EITHER RESIGN OR COMMIT SUICIDE.
|
||||||
|
THE CHOICE IS YOURS.
|
||||||
|
IF YOU CHOOSE THE LATTER, PLEASE TURN OFF YOUR COMPUTER
|
||||||
|
BEFORE PROCEEDING.
|
||||||
|
"
|
||||||
|
.to_owned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if foreign_workers > countrymen {
|
||||||
|
return Ok(RoundEnd::GameOver(format!(
|
||||||
|
r"
|
||||||
|
|
||||||
|
THE NUMBER OF FOREIGN WORKERS HAS EXCEEDED THE NUMBER
|
||||||
|
OF COUNTRYMEN. AS A MINORITY, THEY HAVE REVOLTED AND
|
||||||
|
TAKEN OVER THE COUNTRY.
|
||||||
|
{}
|
||||||
|
",
|
||||||
|
departure_flavour(rng)
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
if state.year_in_office + 1 >= TERM_LENGTH {
|
||||||
|
return Ok(RoundEnd::GameOver(format!(
|
||||||
|
r"
|
||||||
|
|
||||||
|
CONGRATULATIONS!!!!!!!!!!!!!!!!!!
|
||||||
|
YOU HAVE SUCCESFULLY COMPLETED YOUR {} YEAR TERM
|
||||||
|
OF OFFICE. YOU WERE, OF COURSE, EXTREMELY LUCKY, BUT
|
||||||
|
NEVERTHELESS, IT'S QUITE AN ACHIEVEMENT. GOODBYE AND GOOD
|
||||||
|
LUCK - YOU'LL PROBABLY NEED IT IF YOU'RE THE TYPE THAT
|
||||||
|
PLAYS THIS GAME.
|
||||||
|
",
|
||||||
|
TERM_LENGTH
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(RoundEnd::Next(State {
|
||||||
|
money,
|
||||||
|
countrymen,
|
||||||
|
foreign_workers,
|
||||||
|
land,
|
||||||
|
year_in_office: state.year_in_office + 1,
|
||||||
|
show_land_hint,
|
||||||
|
previous_tourist_trade: tourist_trade,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn departure_flavour(rng: &mut Rng) -> &'static str {
|
||||||
|
if rng.bool() {
|
||||||
|
"YOU HAVE BEEN THROWN OUT OF OFFICE AND ARE NOW\nRESIDING IN PRISON.\n"
|
||||||
|
} else {
|
||||||
|
"YOU HAVE BEEN ASSASSINATED.\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_int<R: BufRead>(mut input: R, buf: &mut String) -> io::Result<u32> {
|
||||||
|
loop {
|
||||||
|
buf.clear();
|
||||||
|
input.read_line(buf)?;
|
||||||
|
let line = buf.trim();
|
||||||
|
// This is implicit behaviour in the original code: empty input is equal to 0
|
||||||
|
if line.is_empty() {
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
|
if let Ok(n) = line.parse::<u32>() {
|
||||||
|
return Ok(n);
|
||||||
|
} else {
|
||||||
|
print!("??REENTER\n?? ");
|
||||||
|
stdout().flush()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_and_verify_int<R: BufRead, Verify>(
|
||||||
|
mut input: R,
|
||||||
|
buf: &mut String,
|
||||||
|
prompt: &str,
|
||||||
|
mut verify: Verify,
|
||||||
|
) -> io::Result<u32>
|
||||||
|
where
|
||||||
|
Verify: FnMut(u32) -> Result<u32, String>,
|
||||||
|
{
|
||||||
|
loop {
|
||||||
|
print!("{}", prompt);
|
||||||
|
stdout().flush()?;
|
||||||
|
let v = read_int(&mut input, buf)?;
|
||||||
|
match verify(v) {
|
||||||
|
Ok(v) => {
|
||||||
|
return Ok(v);
|
||||||
|
}
|
||||||
|
Err(msg) => {
|
||||||
|
println!("{}", msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -130,7 +130,7 @@ NOTE: per [the official blog post announcement](https://blog.codinghorror.com/up
|
|||||||
| 50_Horserace | x | | x | | | | | | x | x |
|
| 50_Horserace | x | | x | | | | | | x | x |
|
||||||
| 51_Hurkle | x | x | x | | | x | x | x | x | x |
|
| 51_Hurkle | x | x | x | | | x | x | x | x | x |
|
||||||
| 52_Kinema | x | x | x | | | x | x | x | | x |
|
| 52_Kinema | x | x | x | | | x | x | x | | x |
|
||||||
| 53_King | x | | x | | | | x | | | x |
|
| 53_King | x | | x | | | | x | | x | x |
|
||||||
| 54_Letter | x | x | x | | | x | x | x | x | x |
|
| 54_Letter | x | x | x | | | x | x | x | x | x |
|
||||||
| 55_Life | x | x | x | | | x | x | x | x | x |
|
| 55_Life | x | x | x | | | x | x | x | x | x |
|
||||||
| 56_Life_for_Two | x | x | x | | | x | x | | | x |
|
| 56_Life_for_Two | x | x | x | | | x | x | | | x |
|
||||||
|
|||||||
Reference in New Issue
Block a user