diff --git a/08_Batnum/rust/Cargo.lock b/08_Batnum/rust/Cargo.lock new file mode 100644 index 000000000..b21cc6a2d --- /dev/null +++ b/08_Batnum/rust/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "rust" +version = "0.1.0" diff --git a/08_Batnum/rust/Cargo.toml b/08_Batnum/rust/Cargo.toml new file mode 100644 index 000000000..1ec696335 --- /dev/null +++ b/08_Batnum/rust/Cargo.toml @@ -0,0 +1,8 @@ +[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] diff --git a/08_Batnum/rust/src/main.rs b/08_Batnum/rust/src/main.rs new file mode 100644 index 000000000..761e1939b --- /dev/null +++ b/08_Batnum/rust/src/main.rs @@ -0,0 +1,239 @@ +use std::io::{self, Write}; + +/// Print out the introduction and rules for the game. +fn print_intro() { + println!(); + println!(); + println!("{:>33}", "BATNUM"); + println!("{:>15}", "CREATIVE COMPUTING MORRISSTOWN, NEW JERSEY"); + println!(); + println!(); + println!(); + println!("THIS PROGRAM IS A 'BATTLE OF NUMBERS' GAME, WHERE THE"); + println!("COMPUTER IS YOUR OPPONENT."); + println!(); + println!("THE GAME STARTS WITH AN ASSUMED PILE OF OBJECTS. YOU"); + println!("AND YOUR OPPONENT ALTERNATELY REMOVE OBJECTS FROM THE PILE."); + println!("WINNING IS DEFINED IN ADVANCE AS TAKING THE LAST OBJECT OR"); + println!("NOT. YOU CAN ALSO SPECIFY SOME OTHER BEGINNING CONDITIONS."); + println!("DON'T USE ZERO, HOWEVER, IN PLAYING THE GAME."); + println!("ENTER A NEGATIVE NUMBER FOR NEW PILE SIZE TO STOP PLAYING."); + println!(); +} + +/// This requests the necessary parameters to play the game. +/// five game parameters: +/// * pile_size - the starting size of the object pile +/// * min_select - minimum selection that can be made on each turn +/// * max_select - maximum selection that can be made on each turn +/// * start_option - computer first or player first +/// * win_option - goal is to take the last object +/// or the goal is to not take the last object +struct Params { + pub pile_size: usize, + pub min_select: usize, + pub max_select: usize, + pub start_option: StartOption, + pub win_option: WinOption, +} + +#[derive(PartialEq, Eq)] +enum StartOption { + ComputerFirst, + PlayerFirst, +} + +#[derive(PartialEq, Eq)] +enum WinOption { + TakeLast, + AvoidLast, +} + +impl Params { + pub fn get_params() -> Self { + let pile_size = Self::get_pile_size(); + let (min_select, max_select) = Self::get_min_max(); + let start_option = Self::get_start_option(); + let win_option = Self::get_win_option(); + + Self { + pile_size, + min_select, + max_select, + start_option, + win_option, + } + } + + fn get_pile_size() -> usize { + print!("ENTER PILE SIZE "); + let _ = io::stdout().flush(); + read_input_integer() + } + + fn get_win_option() -> WinOption { + print!("ENTER WIN OPTION: 1 TO TAKE LAST, 2 TO AVOID LAST: "); + let _ = io::stdout().flush(); + + loop { + match read_input_integer() { + 1 => { + return WinOption::TakeLast; + } + 2 => { + return WinOption::AvoidLast; + } + _ => { + print!("Please enter 1 or 2 "); + let _ = io::stdout().flush(); + continue; + } + } + } + } + + fn get_start_option() -> StartOption { + print!("ENTER START OPTION: 1 COMPUTER FIRST, 2 YOU FIRST "); + let _ = io::stdout().flush(); + + loop { + match read_input_integer() { + 1 => { + return StartOption::ComputerFirst; + } + 2 => { + return StartOption::PlayerFirst; + } + _ => { + print!("Please enter 1 or 2 "); + let _ = io::stdout().flush(); + continue; + } + } + } + } + + fn get_min_max() -> (usize, usize) { + print!("ENTER MIN "); + let _ = io::stdout().flush(); + let min = read_input_integer(); + + print!("ENTER MAX "); + let _ = io::stdout().flush(); + let max = read_input_integer(); + + (min, max) + } +} + +fn read_input_integer() -> usize { + loop { + let mut input = String::new(); + io::stdin() + .read_line(&mut input) + .expect("Failed to read line"); + match input.trim().parse::() { + Ok(num) => { + if num == 0 { + print!("Must be greater than zero "); + let _ = io::stdout().flush(); + continue; + } + return num; + } + Err(_err) => { + print!("Please enter a number greater than zero "); + let _ = io::stdout().flush(); + continue; + } + } + } +} + +fn player_move(pile_size: &mut usize, params: &Params) -> bool { + loop { + print!("YOUR MOVE "); + let _ = io::stdout().flush(); + + let player_move = read_input_integer(); + if player_move == 0 { + println!("I TOLD YOU NOT TO USE ZERO! COMPUTER WINS BY FORFEIT."); + return true; + } + if player_move > params.max_select || player_move < params.min_select { + println!("ILLEGAL MOVE, REENTER IT"); + continue; + } + *pile_size -= player_move; + if *pile_size == 0 { + if params.win_option == WinOption::AvoidLast { + println!("TOUGH LUCK, YOU LOSE."); + } else { + println!("CONGRATULATIONS, YOU WIN.") + } + return true; + } + return false; + } +} + +fn computer_pick(pile_size: usize, params: &Params) -> usize { + let q = if params.win_option == WinOption::AvoidLast { + pile_size - 1 + } else { + pile_size + }; + let c = params.min_select + params.max_select; + let computer_pick = q - (c * (q / c)); + let computer_pick = if computer_pick < params.min_select { + params.min_select + } else { + computer_pick + }; + if computer_pick > params.max_select { + params.max_select + } else { + computer_pick + } +} + +fn computer_move(pile_size: &mut usize, params: &Params) -> bool { + if params.win_option == WinOption::TakeLast && *pile_size <= params.max_select { + println!("COMPUTER TAKES {pile_size} AND WINS."); + return true; + } + if params.win_option == WinOption::AvoidLast && *pile_size >= params.min_select { + println!("COMPUTER TAKES {} AND LOSES.", params.min_select); + return true; + } + + let curr_sel = computer_pick(*pile_size, params); + *pile_size -= curr_sel; + println!("COMPUTER TAKES {curr_sel} AND LEAVES {pile_size}"); + false +} + +fn play_game(params: &Params) { + let mut pile_size = params.pile_size; + + if params.start_option == StartOption::ComputerFirst && computer_move(&mut pile_size, params) { + return; + } + + loop { + if player_move(&mut pile_size, params) { + return; + } + if computer_move(&mut pile_size, params) { + return; + } + } +} + +fn main() -> ! { + loop { + print_intro(); + let params = Params::get_params(); + play_game(¶ms); + } +} diff --git a/09_Battle/rust/Cargo.lock b/09_Battle/rust/Cargo.lock new file mode 100644 index 000000000..10a595996 --- /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 000000000..3b1d02f52 --- /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 000000000..f9e8d36ec --- /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/43_Hammurabi/perl/lib/BasicComputerGames/Hammurabi.pm b/43_Hammurabi/perl/lib/BasicComputerGames/Hammurabi.pm new file mode 100644 index 000000000..cfcffb343 --- /dev/null +++ b/43_Hammurabi/perl/lib/BasicComputerGames/Hammurabi.pm @@ -0,0 +1,472 @@ +#!/usr/bin/env perl +package BasicComputerGames::Hammurabi; + +use v5.24; +use warnings; +use experimental 'signatures'; + +{ + # Quick and dirty accessors + no strict 'refs'; + for my $feature ( + qw< year population store rats_toll had_plague planted + production_per_acre acres new_arrivals starved status max_year fed + percent_starved total_starved cost_per_acre > + ) + { + *{__PACKAGE__ . '::' . $feature} = sub ($self, @new) { + $self->{$feature} = $new[0] if @new; + return $self->{$feature}; + }; + } ## end for my $feature (...) +} + +sub new ($package, %args) { + my $self = bless { + + # These defaults can be overridden by %args + population => 100, + store => 2800, + rats_toll => 200, + production_per_acre => 3, + acres => 1000, + new_arrivals => 5, + fed => 0, + max_year => 10, + + %args, + + # These starting values cannot be overridden by %args + status => 'start', + year => 1, + starved => 0, + total_starved => 0, + percent_starved => 0, + had_plague => 0, + planted => 0, + cost_per_acre => 0, + }, $package; + + return $self; +} ## end sub new + +sub step ($self, $input) { + my $method = $self->can('_handle_' . $self->status); + $self->$method($input); +} + +######################################################################## +# +# All _handle_* methods below represents handlers for different states +# of the game, e.g. state `start` is managed by _handle_start(). Each +# handler receives two input arguments: an instance to the game object and +# the input that was collected by the UI for that particular state (if +# any). + +# start of the game +sub _handle_start ($self, $input) { + $self->status('start_of_year'); +} + +# start of each year +sub _handle_start_of_year ($self, $input) { + $self->cost_per_acre(int(rand(10)) + 17); + $self->status('advertise_cost_per_acre'); +} + +# intermediate state to allow for printing the cost per acre, moves +# directly to following state +sub _handle_advertise_cost_per_acre ($self, $input) { + $self->status('buy_acres'); +} + +# buy acres of land, making sure to be able to cover for the cost +sub _handle_buy_acres ($self, $input) { + return $self->status('bail_out') if $input < 0; + return $self->status('sell_acres') if $input == 0; + my $cpa = $self->cost_per_acre; + my $cost = $cpa * $input; + return $self->status('buy_acres_again') + if $cost > $self->store; + $self->acres($self->acres + $input); + $self->store($self->store - $cost); + return $self->status('feeding'); +} ## end sub _handle_buy_acres + +# intermediate state to allow for notifying that the request for new +# acres of land could not be covered, moves directly to the following +# state +sub _handle_buy_acres_again ($self, $input) { + $self->status('buy_acres'); +} + +# sell acres of land, making sure to sell only what can be sold. +sub _handle_sell_acres ($self, $input) { + return $self->status('bail_out') if $input < 0; + return $self->status('sell_acres_again') if $input >= $self->acres; + $self->acres($self->acres - $input); + $self->store($self->store + $self->cost_per_acre * $input); + return $self->status('feeding'); +} ## end sub _handle_sell_acres + +# intermediate state to allow for notifying that the request to sell +# acres of land could not be covered, moves directly to the following +# state +sub _handle_sell_acres_again ($self, $input) { + $self->status('sell_acres'); +} + +# feed people, making sure we have the necessary resources +sub _handle_feeding ($self, $input) { + return $self->status('bail_out') if $input < 0; + return $self->status('feeding_again') if $input >= $self->store; + $self->store($self->store - $input); + $self->fed($input); + $self->status('planting'); +} ## end sub _handle_feeding + +# intermediate state to allow for notifying that the request to use +# bushels of grain could not be covered, moves directly to the following +# state +sub _handle_feeding_again ($self, $input) { + $self->status('feeding'); +} + +# plant crops, making sure we have the land, the seeds and the workers. +sub _handle_planting ($self, $input) { + return $self->status('bail_out') if $input < 0; + + return $self->status('planting_fail_acres') if $input > $self->acres; + + my $store = $self->store; + return $self->status('planting_fail_seeds') + if $store < int($input / 2); + + return $self->status('planting_fail_people') + if $input >= $self->population * 10; + + $self->planted($input); + $self->store($store - int($input / 2)); + $self->status('simulate_year'); +} ## end sub _handle_planting + +# complain about lack of land to cover the planting request +sub _handle_planting_fail_acres ($self, $input) { + $self->status('planting'); +} + +# complain about lack of seeds to cover the planting request +sub _handle_planting_fail_seeds ($self, $input) { + $self->status('planting'); +} + +# complain about lack of workers to cover the planting request +sub _handle_planting_fail_people ($self, $input) { + $self->status('planting'); +} + +# simulate the rest of the year after all inputs, i.e. rats, crops, etc. +sub _handle_simulate_year ($self, $input) { + my $store = $self->store; + + # rats might take a toll during the year + my $c = 1 + int(rand(5)); + my $rats_toll = $c % 2 ? 0 : int($store / $c); + $self->rats_toll($rats_toll); + + # planting also gains us grain after the harvest + my $ppa = $self->production_per_acre(1 + int(rand(5))); + my $harvest = $ppa * $self->planted; + + # let's update the stored seeds finally + $self->store($store += $harvest - $rats_toll); + + # let's see how population evolved + my $population = $self->population; + + # how many people had full tummies + my $fed_people = int($self->fed / 20); + my $starved = $population - $fed_people; + $starved = 0 if $starved < 0; # cannot create people from seeds + $self->starved($starved); + + # check preliminary exit condition for a very bad year + return $self->status('impeach_year') + if $starved > $population * 0.45; + + # update statistics + $self->total_starved($self->total_starved + $starved); + my $perc = $self->percent_starved; + my $year = $self->year; + $perc = (($year - 1) * $perc + $starved * 100 / $population) / $year; + $self->percent_starved($perc); + + # babies + my $acres = $self->acres; + my $rand = 1 + int(rand(5)); + my $arrivals = $self->new_arrivals( + int(1 + $rand * (20 * $acres + $store) / $population / 100)); + + $population += $arrivals - $starved; + + # HORROS, A 15% CHANCE OF PLAGUE + my $had_plague = $self->had_plague(rand(1) < 0.15); + $population = int($population / 2) if $had_plague; + + # save population for next round + $self->population($population); + + # advance to next year + $self->year(++$year); + if ($year > $self->max_year) { + $self->status('summary'); + } + else { + $self->status('start_of_year'); + } +} ## end sub _handle_simulate_year + +# this is a transition after the impeachment message +sub _handle_impeach_year ($self, $input) { + $self->status('goodbye'); +} + +# this is a transition after printing the summary +sub _handle_summary ($self, $input) { + $self->status('goodbye'); +} + +# this is a transition after printing the final salutation message +sub _handle_goodbye ($self, $input) { + $self->status('game_over'); +} + +# this is a transition after asking the king to hire someone else! +sub _handle_bail_out ($self, $input) { + $self->status('game_over'); +} + +# The following package implements all the User Interface, using the +# game state (as exposed by $game->status) to figure out what to print +# and if an input is needed from the user. It all happens on the +# standard input and output. +package BasicComputerGames::Hammurabi::DefaultIO; + +# All __io_* functions take a $game object as input, in case of need for +# some specific data (e.g. population amount or amassed grain bushels). +# They usually print something out and collect input from standard +# input for states that require a user input. All functions are named +# after the available states in BasicComputerGames::Hammurabi. + +sub __io_start ($game) { + say ' ' x 32, 'HAMURABI'; + say ' ' x 15, 'CREATIVE COMPUTING MORRISTOWN, NEW JERSEY'; + print "\n\n\n"; + say 'TRY YOUR HAND AT GOVERNING ANCIENT SUMERIA'; + say 'FOR A TEN-YEAR TERM OF OFFICE'; + print "\n"; + return; +} ## end sub __io_start + +sub __io_start_of_year ($game) { + print "\n\n"; + say "HAMURABI: I BEG TO REPORT TO YOU,"; + printf "IN YEAR %d , %d PEOPLE STARVED, %d CAME TO THE CITY,\n", + $game->year, $game->starved, $game->new_arrivals; + say 'A HORRIBLE PLAGUE STRUCK! HALF THE PEOPLE DIED.' + if $game->had_plague; + say 'POPULATION IS NOW ', $game->population; + say 'THE CITY NOW OWNS ', $game->acres, ' ACRES.'; + say 'YOU HARVESTED ', $game->production_per_acre, ' BUSHELS PER ACRE.'; + say 'THE RATS ATE ', $game->rats_toll, ' BUSHELS.'; + say 'YOU NOW HAVE ', $game->store, ' BUSHELS IN STORE.'; + print "\n"; + return; +} ## end sub __io_start_of_year + +sub get_input ($game = undef) { + while () { + chomp(my $input = $_); + return 0 unless $input; + return $input if $input =~ m{\A -? \d+ \z}mxs; + print "REENTER?\n?? "; + } ## end while () + die "\n"; +} ## end sub get_input + +sub __io_bail_out ($game) { + say "\nHAMURABI: I CANNOT DO WHAT YOU WISH."; + say 'GET YOURSELF ANOTHER STEWARD!!!!!'; + return; +} + +sub __not_enough_bushels ($game) { + say 'HAMURABI: THINK AGAIN. YOU HAVE ONLY'; + say $game->store, ' BUSHELS OF GRAIN. NOW, THEN,'; +} + +sub __not_enough_acres ($game) { + say 'HAMURABI: THINK AGAIN. YOU OWN ONLY ', + $game->acres, ' ACRES. NOW, THEN,'; +} + +sub __io_buy_acres ($game) { + print 'HOW MANY ACRES DO YOU WISH TO BUY?? '; + return get_input(); +} + +sub __io_advertise_cost_per_acre ($game) { + say 'LAND IS TRADING AT ', $game->cost_per_acre, ' BUSHELS PER ACRE.'; + return; +} + +sub __io_sell_acres ($game) { + print 'HOW MANY ACRES DO YOU WISH TO SELL?? '; + return get_input(); +} + +sub __io_feeding ($game) { + print "\nHOW MANY BUSHELS DO YOU WISH TO FEED YOUR PEOPLE?? "; + return get_input(); +} + +sub __io_planting ($game) { + print "\nHOW MANY ACRES DO YOU WISH TO PLANT WITH SEED?? "; + return get_input(); +} + +sub __io_buy_acres_again ($game) { __not_enough_bushels($game) } + +sub __io_sell_acres_again ($game) { __not_enough_acres($game) } + +sub __io_feeding_again ($game) { __not_enough_bushels($game) } + +sub __io_planting_fail_acres ($game) { __not_enough_acres($game) } + +sub __io_planting_fail_seeds ($game) { __not_enough_bushels($game) } + +sub __io_planting_fail_people ($game) { + say 'BUT YOU HAVE ONLY ', $game->population, + ' PEOPLE TO TEND THE FIELDS! NOW, THEN,'; +} + +sub __impeachment { + say 'DUE TO THIS EXTREME MISMANAGEMENT YOU HAVE NOT ONLY'; + say 'BEEN IMPEACHED AND THROWN OUT OF OFFICE BUT YOU HAVE'; + say 'ALSO BEEN DECLARED NATIONAL FINK!!!!'; +} + +sub __io_impeach_year ($game) { + printf "\nYOU STARVED %d PEOPLE IN ONE YEAR!!!\n", $game->starved; + return __impeachment(); +} + +sub __io_goodbye ($game) { + say "\nSO LONG FOR NOW.\n"; + return; +} + +# Final summary for the game, print statistics and evaluation +sub __io_summary ($game) { + my $starved = $game->total_starved; + my $years = $game->max_years; + my $p1 = 100 * $starved / $years; + my $l = $game->acres / $game->population; + printf "IN YOUR %d-YEAR TERM OF OFFICE, %d PERCENT OF THE\n", + $years, $p1; + say 'POPULATION STARVED PER YEAR ON THE AVERAGE, I.E. A TOTAL OF'; + printf "%d PEOPLE DIED!!\n", $starved; + say 'YOU STARTED WITH 10 ACRES PER PERSON AND ENDED WITH'; + printf "%.2f ACRES PER PERSON.\n\n", $l; + + if ($p1 > 33 || $l < 7) { + __impeachment(); + } + elsif ($p1 > 10 || $l < 9) { + say 'YOUR HEAVY-HANDED PERFORMANCE SMACKS OF NERO AND IVAN IV.'; + say 'THE PEOPLE (REMAINING) FIND YOU AN UNPLEASANT RULER, AND,'; + say 'FRANKLY, HATE YOUR GUTS!!'; + } + elsif ($p1 > 3 || $l < 10) { + my $haters = int($game->population * rand(0.8)); + say 'YOUR PERFORMANCE COULD HAVE BEEN SOMEWHAT BETTER, BUT'; + say "REALLY WASN'T TOO BAD AT ALL. $haters PEOPLE"; + say 'WOULD DEARLY LIKE TO SEE YOU ASSASSINATED BUT WE ALL HAVE OUR'; + say 'TRIVIAL PROBLEMS.'; + } ## end elsif ($p1 > 3 || $l < 10) + else { + say 'A FANTASTIC PERFORMANCE!!! CHARLEMANGE, DISRAELI, AND'; + say 'JEFFERSON COMBINED COULD NOT HAVE DONE BETTER!'; + } + + return; +} ## end sub __io_summary + +# this class method allows using this module... easily. Call with +# arguments to be fed to the BasicComputerGames::Hammurabi constructor. +sub run ($package, @args) { + my $game = BasicComputerGames::Hammurabi->new(@args); + while ((my $status = $game->status) ne 'game_over') { + eval { + my $retval; + if (my $cb = $package->can('__io_' . $status)) { + $retval = $cb->($game); + } + $game->step($retval); + 1; + } or last; + } ## end while ((my $status = $game...)) + say ''; + return 0; +} ## end sub run + +# Modulino (https://gitlab.com/polettix/notechs/-/snippets/1868370) +exit __PACKAGE__->run(@ARGV) unless caller; + +1; +__END__ + +=pod + +=encoding UTF-8 + +=head1 NAME + +BasicComputerGames::Hammurabi - the Hammurabi game from BASIC + +=head1 SYNOPSIS + + use BasicComputerGames::Hammurabi; + + # if you have a way to manage the UI yourself, then you can get the + # game logic handler + my $game_handler = BasicComputerGames::Hammurabi->new; + while ((my $status = $game_handler->status) ne 'game_over') { + # figure out what to print out with $status, this is totally + # up to the interface implementation, which also has to collect + # the inputs + my $retval = manage_ui_for($game_handler); + + # now we feed whatever came from the interface back to the handler + $game_handler->step($retval); + } + + # Want the plain terminal experience? No problem: + BasicComputerGames::Hammurabi::DefaultIO->run; + +=head1 IMPLEMENTATION DETAILS + +The code tries to behave like the original BASIC, including some dubious +conditions checks that e.g. do not allow using the full potential of +available resources for lack of an equal sign. + +The calculation of the final average of starved people per year is +differnet from the original and avoids what is considered (by me) a bug +that kicks in when there are years in which nobody starves. + +=head1 AUTHOR + +Adapted by Flavio Poletti from the BASIC version by David Ahl. Game text +copied verbatim from the original BASIC implementation, including typos. + +=cut diff --git a/69_Pizza/rust/Cargo.lock b/69_Pizza/rust/Cargo.lock new file mode 100644 index 000000000..b4abe0dca --- /dev/null +++ b/69_Pizza/rust/Cargo.lock @@ -0,0 +1,23 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "rust" +version = "0.1.0" +dependencies = [ + "anyhow", + "fastrand", +] diff --git a/69_Pizza/rust/Cargo.toml b/69_Pizza/rust/Cargo.toml new file mode 100644 index 000000000..5224dbab6 --- /dev/null +++ b/69_Pizza/rust/Cargo.toml @@ -0,0 +1,10 @@ +[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] +anyhow = "1.0.75" +fastrand = "2.0.1" diff --git a/69_Pizza/rust/README.md b/69_Pizza/rust/README.md new file mode 100644 index 000000000..ce0d38dc2 --- /dev/null +++ b/69_Pizza/rust/README.md @@ -0,0 +1,3 @@ +Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html) + +Conversion to [Rust](https://www.rust-lang.org/). diff --git a/69_Pizza/rust/src/main.rs b/69_Pizza/rust/src/main.rs new file mode 100644 index 000000000..4bfe4f226 --- /dev/null +++ b/69_Pizza/rust/src/main.rs @@ -0,0 +1,178 @@ +use anyhow::Result; +use std::io; + +fn print_centered(text: &str) { + println!("{:^72}", text); +} + +fn read_line() -> Result { + let mut input = String::new(); + io::stdin().read_line(&mut input)?; + input.truncate(input.trim_end().len()); + Ok(input) +} + +fn read_number() -> Result { + let line = read_line()?; + let num = line.parse()?; + Ok(num) +} + +fn print_banner() { + print_centered("PIZZA"); + print_centered("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY"); + println!(); + println!(); + println!(); +} + +fn print_instructions() -> Result { + println!("PIZZA DELIVERY GAME"); + println!(); + println!("WHAT IS YOUR FIRST NAME"); + + let name = read_line()?; + println!("HI, {name}. IN THIS GAME YOU ARE TO TAKE ORDERS"); + println!("FOR PIZZAS. THEN YOU ARE TO TELL A DELIVERY BOY"); + println!("WHERE TO DELIVER THE ORDERED PIZZAS."); + println!(); + println!(); + + print_map(); + + println!("THE OUTPUT IS A MAP OF THE HOMES WHERE"); + println!("YOU ARE TO SEND PIZZAS."); + println!(); + println!("YOUR JOB IS TO GIVE A TRUCK DRIVER"); + println!("THE LOCATION OR COORDINATES OF THE"); + println!("HOME ORDERING THE PIZZA."); + println!(); + + Ok(name) +} + +fn print_ruler() { + println!(" -----1-----2-----3-----4-----"); +} + +fn print_ticks() { + println!("-"); + println!("-"); + println!("-"); + println!("-"); +} + +fn print_street(i: u32) { + let street_number = 3 - i; + let street_name = (street_number + 1).to_string(); + + let mut line = street_name.clone(); + let space = " "; + for customer_idx in 0..4 { + line.push_str(space); + let customer = 4 * street_number + customer_idx; + line.push(char::from_u32(65 + customer).unwrap()); + } + line.push_str(space); + line.push_str(&street_name); + println!("{line}"); +} + +fn print_map() { + println!("MAP OF THE CITY OF HYATTSVILLE"); + println!(); + print_ruler(); + for i in 0..4 { + print_ticks(); + print_street(i); + } + print_ticks(); + print_ruler(); + println!(); +} + +fn yes_no_prompt(text: &str) -> Result> { + println!("{text}"); + let input = read_line()?; + match input.as_str() { + "YES" => Ok(Some(true)), + "NO" => Ok(Some(false)), + _ => Ok(None), + } +} + +fn play_game(turns: u32, player_name: &str) -> Result<()> { + for _ in 0..turns { + let customer = fastrand::char('A'..='P'); + println!("HELLO {player_name}'S PIZZA. THIS IS {customer}."); + println!(" PLEASE SEND A PIZZA."); + loop { + println!(" DRIVER TO {player_name}: WHERE DOES {customer} LIVE"); + + let x = read_number()?; + let y = read_number()?; + + let input = x - 1 + (y - 1) * 4; + match char::from_u32(65 + input) { + Some(c) if c == customer => { + println!("HELLO {player_name}. THIS IS {customer}, THANKS FOR THE PIZZA."); + break; + } + Some(c @ 'A'..='P') => { + println!("THIS IS {c}. I DID NOT ORDER A PIZZA."); + println!("I LIVE AT {x},{y}"); + } + // this is out of bounds in the original game + _ => (), + } + } + } + + Ok(()) +} + +fn main() -> Result<()> { + print_banner(); + let player_name = print_instructions()?; + let more_directions = loop { + if let Some(x) = yes_no_prompt("DO YOU NEED MORE DIRECTIONS")? { + break x; + } else { + println!("'YES' OR 'NO' PLEASE, NOW THEN,"); + } + }; + + if more_directions { + println!(); + println!("SOMEBODY WILL ASK FOR A PIZZA TO BE"); + println!("DELIVERED. THEN A DELIVERY BOY WILL"); + println!("ASK YOU FOR THE LOCATION."); + println!(" EXAMPLE:"); + println!("THIS IS J. PLEASE SEND A PIZZA."); + println!("DRIVER TO {player_name}. WHERE DOES J LIVE?"); + println!("YOUR ANSWER WOULD BE 2,3"); + println!(); + + if let Some(true) = yes_no_prompt("UNDERSTAND")? { + println!("GOOD. YOU ARE NOW READY TO START TAKING ORDERS."); + println!(); + println!("GOOD LUCK!!"); + println!(); + } else { + println!("THIS JOB IS DEFINITELY TOO DIFFICULT FOR YOU. THANKS ANYWAY"); + return Ok(()); + } + } + + loop { + play_game(5, &player_name)?; + println!(); + + if let Some(false) | None = yes_no_prompt("DO YOU WANT TO DELIVER MORE PIZZAS")? { + println!(); + println!("O.K. {player_name}, SEE YOU LATER!"); + println!(); + return Ok(()); + } + } +} diff --git a/84_Super_Star_Trek/README.md b/84_Super_Star_Trek/README.md index 07da653f2..32f827c67 100644 --- a/84_Super_Star_Trek/README.md +++ b/84_Super_Star_Trek/README.md @@ -3,12 +3,12 @@ #### Brief History Many versions of Star Trek have been kicking around various college campuses since the late sixties. I recall playing one at Carnegie-Mellon Univ. in 1967 or 68, and a very different one at Berkeley. However, these were a far cry from the one written by Mike Mayfield of Centerline Engineering and/or Custom Data. This was written for an HP2000C and completed in October 1972. It became the “standard” Star Trek in February 1973 when it was put in the HP contributed program library and onto a number of HP Data Center machines. -In the summer of 1973, I converted the HP version to BASIC-PLUS for DEC’s RSTS-11 compiler and added a few bits and pieces while I was at it. Mary Cole at DEC contributed enormously to this task too. Later that year I published it under the name SPACWE (Space War — in retrospect, an incorrect name) in my book _101 Basic Computer Games_.It is difficult today to find an interactive computer installation that does not have one of these versions of Star Trek available. +In the summer of 1973, I converted the HP version to BASIC-PLUS for DEC’s RSTS-11 compiler and added a few bits and pieces while I was at it. Mary Cole at DEC contributed enormously to this task too. Later that year I published it under the name SPACWR (Space War — in retrospect, an incorrect name) in my book _101 Basic Computer Games_. It is difficult today to find an interactive computer installation that does not have one of these versions of Star Trek available. #### Quadrant Nomenclature Recently, certain critics have professed confusion as to the origin on the “quadrant” nomenclature used on all standard CG (Cartesian Galactic) maps. Naturally, for anyone with the remotest knowledge of history, no explanation is necessary; however, the following synopsis should suffice for the critics: -As everybody schoolboy knows, most of the intelligent civilizations in the Milky Way had originated galactic designations of their own choosing well before the Third Magellanic Conference, at which the so-called “2⁶ Agreement” was reached. In that historic document, the participant cultures agreed, in all two-dimensional representations of the galaxy, to specify 64 major subdivisions, ordered as an 8 x 8 matrix. This was partially in deference to the Earth culture (which had done much in the initial organization of the Federation), whose century-old galactic maps had landmarks divided into four “quadrants,” designated by ancient “Roman Numerals” (the origin of which has been lost). +As every schoolboy knows, most of the intelligent civilizations in the Milky Way had originated galactic designations of their own choosing well before the Third Magellanic Conference, at which the so-called “2⁶ Agreement” was reached. In that historic document, the participant cultures agreed, in all two-dimensional representations of the galaxy, to specify 64 major subdivisions, ordered as an 8 x 8 matrix. This was partially in deference to the Earth culture (which had done much in the initial organization of the Federation), whose century-old galactic maps had always shown 16 major regions named after celestial landmarks divided into four “quadrants,” designated by ancient “Roman Numerals” (the origin of which has been lost). To this day, the official logs of starships originating on near-Earth starbases still refer to the major galactic areas as “quadrants.” @@ -17,7 +17,7 @@ The relation between the Historical and Standard nomenclatures is shown in the s | | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |---|--------------|----|-----|----|------------|----|-----|----| | 1 | ANTARES | | | | SIRIUS | | | | -| | I | II | III | IV | I | | III | IV | +| | I | II | III | IV | I | II | III | IV | | 2 | RIGEL | | | | DENEB | | | | | | I | II | III | IV | I | II | III | IV | | 3 | PROCYON | | | | CAPELLA | | | | @@ -88,11 +88,13 @@ The relation between the Historical and Standard nomenclatures is shown in the s 15. This version of Star Trek was created for a Data General Nova 800 system with 32K or core. So that it would fit, the instructions are separated from the main program via a CHAIN. For conversion to DEC BASIC-PLUS, Statement 160 (Randomize) should be moved after the return from the chained instructions, say to Statement 245. For Altair BASIC, Randomize and the chain instructions should be eliminated. +† Designates trademark of Paramount Pictures Corporation. Used by permission of Paramount Pictures Corporation. + --- As published in Basic Computer Games (1978): - [Atari Archives](https://www.atariarchives.org/basicgames/showpage.php?page=157) -- [Annarchive](https://annarchive.com/files/Basic_Computer_Games_Microcomputer_Edition.pdf#page=166) +- [Annarchive](https://annarchive.com/files/Basic_Computer_Games_Microcomputer_Edition.pdf#page=172) Downloaded from Vintage Basic at http://www.vintage-basic.net/games.html @@ -110,6 +112,5 @@ Many of the programs in this book and this collection have bugs in the original - lines `8310`,`8330`,`8430`,`8450` : Division by zero is possible - line `440` : `B9` should be initialised to 0, not 2 - #### External Links - C++: https://www.codeproject.com/Articles/28399/The-Object-Oriented-Text-Star-Trek-Game-in-C diff --git a/README.md b/README.md index 0ddd2748f..f714bc89b 100644 --- a/README.md +++ b/README.md @@ -83,10 +83,10 @@ NOTE: per [the official blog post announcement](https://blog.codinghorror.com/up | 03_Animal | x | x | x | x | x | x | x | x | x | x | | 04_Awari | x | x | x | | | x | x | x | x | x | | 05_Bagels | x | x | x | x | x | x | x | x | x | x | -| 06_Banner | 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 | -| 08_Batnum | x | x | x | | | x | x | x | | x | -| 09_Battle | x | x | x | | | | x | | | x | +| 08_Batnum | x | x | x | | | 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 |