mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2026-01-02 00:00:14 -08:00
Merge pull request #842 from ChrisPritchard/main
84_Super_Star_Trek in Rustlang
This commit is contained in:
10
84_Super_Star_Trek/rust/Cargo.toml
Normal file
10
84_Super_Star_Trek/rust/Cargo.toml
Normal file
@@ -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]
|
||||
ctrlc = "3.2.5"
|
||||
rand = "0.8.5"
|
||||
19
84_Super_Star_Trek/rust/readme.md
Normal file
19
84_Super_Star_Trek/rust/readme.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Super Star Trek - Rust version
|
||||
|
||||
Explanation of modules:
|
||||
|
||||
- [main.rs](./src/main.rs) - creates the galaxy (generation functions are in model.rs as impl methods) then loops listening for commands. after each command checks for victory or defeat condtions.
|
||||
- [model.rs](./src/model.rs) - all the structs and enums that represent the galaxy. key methods in here (as impl methods) are generation functions on galaxy and quadrant, and various comparison methods on the 'Pos' tuple type.
|
||||
- [commands.rs](./src/commands.rs) - most of the code that implements instructions given by the player (some code logic is in the model impls, and some in view.rs if its view only).
|
||||
- [view.rs](./src/view.rs) - all text printed to the output, mostly called by command.rs (like view::bad_nav for example). also contains the prompts printed to the user (e.g. view::prompts::COMMAND).
|
||||
- [input.rs](./src/input.rs) - utility methods for getting input from the user, including logic for parsing numbers, repeating prompts until a correct value is provided etc.
|
||||
|
||||
Basically the user is asked for the next command, this runs a function that usually checks if the command system is working, and if so will gather additional input (see next note for a slight change here), then either the model is read and info printed, or its mutated in some way (e.g. firing a torpedo, which reduces the torpedo count on the enterprise and can destroy klingons and star bases; finally the klingons fire back and can destroy the enterprise). Finally the win/lose conditions are checked before the loop repeats.
|
||||
|
||||
## Changes from the original
|
||||
|
||||
I have tried to keep it as close as possible. Notable changes are:
|
||||
|
||||
- commands can be given with parameters in line. e.g. while 'nav' will ask for course and then warp speed in the original, here you can *optionally* also do this as one line, e.g. `nav 1 0.1` to move one sector east. I'm sorry - it was driving me insane in its original form (which is still sorted, as is partial application e.g. nav 1 to preset direction and then provide speed).
|
||||
- text is mostly not uppercase, as text was in the basic version. this would be easy to change however as all text is in view.rs, but I chose not to.
|
||||
- the navigation system (plotting direction, paths and collision detection) is as close as I could make it to the basic version (by using other language conversions as specification sources) but I suspect is not perfect. seems to work well enough however.
|
||||
543
84_Super_Star_Trek/rust/src/commands.rs
Normal file
543
84_Super_Star_Trek/rust/src/commands.rs
Normal file
@@ -0,0 +1,543 @@
|
||||
use rand::Rng;
|
||||
|
||||
use crate::{model::*, view, input::{self, param_or_prompt_value, prompt_two_values}};
|
||||
|
||||
pub fn perform_short_range_scan(galaxy: &Galaxy) {
|
||||
if galaxy.enterprise.damaged.contains_key(systems::SHORT_RANGE_SCAN) {
|
||||
view::scanners_out();
|
||||
return;
|
||||
}
|
||||
|
||||
view::short_range_scan(&galaxy)
|
||||
}
|
||||
|
||||
pub fn get_amount_and_set_shields(galaxy: &mut Galaxy, provided: Vec<String>) {
|
||||
|
||||
if galaxy.enterprise.damaged.contains_key(systems::SHIELD_CONTROL) {
|
||||
view::inoperable(&systems::name_for(systems::SHIELD_CONTROL));
|
||||
return;
|
||||
}
|
||||
|
||||
view::energy_available(galaxy.enterprise.total_energy);
|
||||
let value = input::param_or_prompt_value(&provided, 0, view::prompts::SHIELDS, 0, i32::MAX);
|
||||
if value.is_none() {
|
||||
view::shields_unchanged();
|
||||
return;
|
||||
}
|
||||
|
||||
let value = value.unwrap() as u16;
|
||||
if value > galaxy.enterprise.total_energy {
|
||||
view::ridiculous();
|
||||
view::shields_unchanged();
|
||||
return;
|
||||
}
|
||||
|
||||
galaxy.enterprise.shields = value;
|
||||
view::shields_set(value);
|
||||
}
|
||||
|
||||
pub fn gather_dir_and_speed_then_move(galaxy: &mut Galaxy, provided: Vec<String>) {
|
||||
|
||||
let course = input::param_or_prompt_value(&provided, 0, view::prompts::COURSE, 1.0, 9.0);
|
||||
if course.is_none() {
|
||||
view::bad_course_data();
|
||||
return;
|
||||
}
|
||||
|
||||
let course = course.unwrap();
|
||||
|
||||
let mut max_warp = 8.0;
|
||||
if galaxy.enterprise.damaged.contains_key(systems::WARP_ENGINES) {
|
||||
max_warp = 0.2;
|
||||
}
|
||||
|
||||
let speed = input::param_or_prompt_value(&provided, 1, &view::prompts::warp_factor(max_warp), 0.0, 8.0);
|
||||
if speed.is_none() {
|
||||
view::bad_course_data();
|
||||
return;
|
||||
}
|
||||
|
||||
let speed = speed.unwrap();
|
||||
|
||||
if speed > max_warp {
|
||||
view::damaged_engines(max_warp, speed);
|
||||
return;
|
||||
}
|
||||
|
||||
klingons_move(galaxy);
|
||||
klingons_fire(galaxy);
|
||||
|
||||
if galaxy.enterprise.destroyed {
|
||||
return;
|
||||
}
|
||||
|
||||
repair_systems(&mut galaxy.enterprise, speed);
|
||||
repair_or_damage_random_system(&mut galaxy.enterprise);
|
||||
|
||||
move_enterprise(course, speed, galaxy);
|
||||
}
|
||||
|
||||
fn repair_systems(enterprise: &mut Enterprise, amount: f32) {
|
||||
|
||||
let keys: Vec<String> = enterprise.damaged.keys().map(|k| k.to_string()).collect();
|
||||
let mut repaired = Vec::new();
|
||||
for key in keys {
|
||||
let fully_fixed = enterprise.repair_system(&key, amount);
|
||||
if fully_fixed {
|
||||
repaired.push(systems::name_for(&key));
|
||||
}
|
||||
}
|
||||
|
||||
if repaired.len() <= 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
view::damage_control_report();
|
||||
for name in repaired {
|
||||
view::system_repair_completed(name);
|
||||
}
|
||||
}
|
||||
|
||||
fn repair_or_damage_random_system(enterprise: &mut Enterprise) {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
if rng.gen::<f32>() > 0.2 {
|
||||
return;
|
||||
}
|
||||
|
||||
if rng.gen::<f32>() >= 0.6 {
|
||||
if enterprise.damaged.len() == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let damaged: Vec<String> = enterprise.damaged.keys().map(|k| k.to_string()).collect();
|
||||
let system = damaged[rng.gen_range(0..damaged.len())].to_string();
|
||||
let system_name = &systems::name_for(&system);
|
||||
|
||||
enterprise.repair_system(&system, rng.gen::<f32>() * 3.0 + 1.0);
|
||||
view::random_repair_report_for(system_name, false);
|
||||
return;
|
||||
}
|
||||
|
||||
let system = systems::KEYS[rng.gen_range(0..systems::KEYS.len())].to_string();
|
||||
let system_name = &systems::name_for(&system);
|
||||
|
||||
enterprise.damage_system(&system, rng.gen::<f32>() * 5.0 + 1.0);
|
||||
view::random_repair_report_for(system_name, true);
|
||||
}
|
||||
|
||||
fn move_enterprise(course: f32, warp_speed: f32, galaxy: &mut Galaxy) {
|
||||
|
||||
let ship = &mut galaxy.enterprise;
|
||||
|
||||
let (mut path, mut hit_edge) = find_nav_path(ship.quadrant, ship.sector, course, warp_speed);
|
||||
for i in 0..path.len() {
|
||||
let (quadrant, sector) = path[i].to_local_quadrant_sector();
|
||||
if quadrant != ship.quadrant {
|
||||
break; // have left current quadrant, so collision checks removed. if there is a collision at the dest... /shrug?
|
||||
}
|
||||
let quadrant = &galaxy.quadrants[quadrant.as_index()];
|
||||
if quadrant.sector_status(sector) != SectorStatus::Empty {
|
||||
path = path[..i].into();
|
||||
hit_edge = false;
|
||||
if i > 0 {
|
||||
let (_, last_sector) = path[path.len() - 1].to_local_quadrant_sector();
|
||||
view::bad_nav(last_sector);
|
||||
} else {
|
||||
view::bad_nav(ship.sector);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if path.len() == 0 {
|
||||
if hit_edge {
|
||||
view::hit_edge(ship.quadrant, ship.sector);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let energy_cost = path.len() as u16 + 10;
|
||||
|
||||
if energy_cost > ship.total_energy {
|
||||
view::insuffient_warp_energy(warp_speed);
|
||||
return
|
||||
}
|
||||
|
||||
let (end_quadrant, end_sector) = path[path.len() - 1].to_local_quadrant_sector();
|
||||
if hit_edge {
|
||||
view::hit_edge(end_quadrant, end_sector);
|
||||
}
|
||||
|
||||
if ship.quadrant != end_quadrant {
|
||||
view::enter_quadrant(end_quadrant);
|
||||
galaxy.scanned.insert(end_quadrant);
|
||||
|
||||
if galaxy.quadrants[end_quadrant.as_index()].klingons.len() > 0 {
|
||||
view::condition_red();
|
||||
if ship.shields <= 200 {
|
||||
view::danger_shields();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ship.quadrant = end_quadrant;
|
||||
ship.sector = end_sector;
|
||||
|
||||
let quadrant = &galaxy.quadrants[end_quadrant.as_index()];
|
||||
if quadrant.docked_at_starbase(ship.sector) {
|
||||
ship.shields = 0;
|
||||
ship.photon_torpedoes = MAX_PHOTON_TORPEDOES;
|
||||
ship.total_energy = MAX_ENERGY;
|
||||
} else {
|
||||
ship.total_energy = ship.total_energy - energy_cost;
|
||||
if ship.shields > ship.total_energy {
|
||||
view::divert_energy_from_shields();
|
||||
ship.shields = ship.total_energy;
|
||||
}
|
||||
}
|
||||
|
||||
view::short_range_scan(&galaxy)
|
||||
}
|
||||
|
||||
fn find_nav_path(start_quadrant: Pos, start_sector: Pos, course: f32, warp_speed: f32) -> (Vec<Pos>, bool) {
|
||||
|
||||
let (dx, dy) = calculate_delta(course);
|
||||
|
||||
let mut distance = (warp_speed * 8.0) as i8;
|
||||
if distance == 0 {
|
||||
distance = 1;
|
||||
}
|
||||
|
||||
let mut last_sector = start_sector.as_galactic_sector(start_quadrant);
|
||||
let mut path = Vec::new();
|
||||
let mut hit_edge;
|
||||
|
||||
loop {
|
||||
let nx = (last_sector.0 as f32 + dx) as i8;
|
||||
let ny = (last_sector.1 as f32 + dy) as i8;
|
||||
hit_edge = nx < 0 || ny < 0 || nx >= 64 || ny >= 64;
|
||||
if hit_edge {
|
||||
break;
|
||||
}
|
||||
last_sector = Pos(nx as u8, ny as u8);
|
||||
path.push(last_sector);
|
||||
|
||||
distance -= 1;
|
||||
if distance == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
(path, hit_edge)
|
||||
}
|
||||
|
||||
fn calculate_delta(course: f32) -> (f32, f32) {
|
||||
// this course delta stuff is a translation (of a translation, of a translation...) of the original basic calcs
|
||||
let dir = (course - 1.0) % 8.0;
|
||||
let (dx1, dy1) = COURSES[dir as usize];
|
||||
let (dx2, dy2) = COURSES[(dir + 1.0) as usize];
|
||||
let frac = dir - (dir as i32) as f32;
|
||||
|
||||
let dx = dx1 + (dx2 - dx1) * frac;
|
||||
let dy = dy1 + (dy2 - dy1) * frac;
|
||||
|
||||
(dx, dy)
|
||||
}
|
||||
|
||||
fn klingons_move(galaxy: &mut Galaxy) {
|
||||
let quadrant = &mut galaxy.quadrants[galaxy.enterprise.quadrant.as_index()];
|
||||
for k in 0..quadrant.klingons.len() {
|
||||
let new_sector: Pos;
|
||||
loop {
|
||||
let candidate = quadrant.find_empty_sector();
|
||||
if candidate != galaxy.enterprise.sector {
|
||||
new_sector = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
quadrant.klingons[k].sector = new_sector;
|
||||
}
|
||||
}
|
||||
|
||||
fn klingons_fire(galaxy: &mut Galaxy) {
|
||||
let quadrant = &mut galaxy.quadrants[galaxy.enterprise.quadrant.as_index()];
|
||||
if quadrant.docked_at_starbase(galaxy.enterprise.sector) {
|
||||
view::starbase_shields();
|
||||
return;
|
||||
}
|
||||
|
||||
for k in 0..quadrant.klingons.len() {
|
||||
quadrant.klingons[k].fire_on(&mut galaxy.enterprise);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_damage_control(galaxy: &Galaxy) {
|
||||
if galaxy.enterprise.damaged.contains_key(systems::DAMAGE_CONTROL) {
|
||||
view::inoperable(&systems::name_for(systems::DAMAGE_CONTROL));
|
||||
return;
|
||||
}
|
||||
|
||||
view::damage_control(&galaxy.enterprise);
|
||||
}
|
||||
|
||||
pub fn try_starbase_ship_repair(galaxy: &mut Galaxy) {
|
||||
let ship = &mut galaxy.enterprise;
|
||||
let quadrant = &galaxy.quadrants[ship.quadrant.as_index()];
|
||||
if ship.damaged.len() == 0 || !quadrant.docked_at_starbase(ship.sector) {
|
||||
return;
|
||||
}
|
||||
|
||||
let repair_delay = quadrant.star_base.as_ref().unwrap().repair_delay;
|
||||
let repair_time = (ship.damaged.len() as f32 * 0.1 + repair_delay).max(0.9);
|
||||
|
||||
view::repair_estimate(repair_time);
|
||||
if !input::prompt_yes_no(view::prompts::REPAIR) {
|
||||
return;
|
||||
}
|
||||
|
||||
ship.damaged.clear();
|
||||
galaxy.stardate += repair_time;
|
||||
view::damage_control(&ship);
|
||||
}
|
||||
|
||||
pub fn perform_long_range_scan(galaxy: &mut Galaxy) {
|
||||
if galaxy.enterprise.damaged.contains_key(systems::LONG_RANGE_SCAN) {
|
||||
view::inoperable(&systems::name_for(systems::LONG_RANGE_SCAN));
|
||||
return;
|
||||
}
|
||||
|
||||
let seen = view::long_range_scan(galaxy);
|
||||
for pos in seen {
|
||||
galaxy.scanned.insert(pos);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn access_computer(galaxy: &Galaxy, provided: Vec<String>) {
|
||||
if galaxy.enterprise.damaged.contains_key(systems::COMPUTER) {
|
||||
view::inoperable(&systems::name_for(systems::COMPUTER));
|
||||
return;
|
||||
}
|
||||
|
||||
let operation : i32;
|
||||
loop {
|
||||
let entered = input::param_or_prompt_value(&provided, 0, view::prompts::COMPUTER, 0, 5);
|
||||
if entered.is_none() {
|
||||
view::computer_options();
|
||||
} else {
|
||||
operation = entered.unwrap();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
match operation {
|
||||
0 => view::galaxy_scanned_map(galaxy),
|
||||
1 => {
|
||||
view::status_report(galaxy);
|
||||
run_damage_control(galaxy);
|
||||
},
|
||||
2 => show_klingon_direction_data(galaxy),
|
||||
3 => show_starbase_direction_data(galaxy),
|
||||
4 => direction_dist_calculator(galaxy),
|
||||
5 => view::galaxy_region_map(),
|
||||
_ => () // unreachable
|
||||
}
|
||||
}
|
||||
|
||||
fn show_klingon_direction_data(galaxy: &Galaxy) {
|
||||
let quadrant = &galaxy.quadrants[galaxy.enterprise.quadrant.as_index()];
|
||||
if quadrant.klingons.len() == 0 {
|
||||
view::no_local_enemies();
|
||||
return;
|
||||
}
|
||||
|
||||
view::klingon_report(quadrant.klingons.len() > 1);
|
||||
let origin = galaxy.enterprise.sector;
|
||||
for k in &quadrant.klingons {
|
||||
let target = k.sector;
|
||||
view::direction_distance(origin.direction(target), origin.dist(target))
|
||||
}
|
||||
}
|
||||
|
||||
fn show_starbase_direction_data(galaxy: &Galaxy) {
|
||||
let quadrant = &galaxy.quadrants[galaxy.enterprise.quadrant.as_index()];
|
||||
match &quadrant.star_base {
|
||||
None => {
|
||||
view::no_local_starbase();
|
||||
return;
|
||||
},
|
||||
Some(s) => {
|
||||
view::starbase_report();
|
||||
let origin = galaxy.enterprise.sector;
|
||||
let target = s.sector;
|
||||
view::direction_distance(origin.direction(target), origin.dist(target))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn direction_dist_calculator(galaxy: &Galaxy) {
|
||||
view::direction_dist_intro(&galaxy.enterprise);
|
||||
loop {
|
||||
let coords1 = prompt_two_values(view::prompts::INITIAL_COORDS, 1, 8).map(|(x, y)| Pos(x, y));
|
||||
if coords1.is_none() {
|
||||
continue;
|
||||
}
|
||||
let coords2 = prompt_two_values(view::prompts::TARGET_COORDS, 1, 8).map(|(x, y)| Pos(x, y));
|
||||
if coords2.is_none() {
|
||||
continue;
|
||||
}
|
||||
let dir = coords1.unwrap().direction(coords2.unwrap());
|
||||
let dist = coords1.unwrap().dist(coords2.unwrap());
|
||||
view::direction_distance(dir, dist);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_power_and_fire_phasers(galaxy: &mut Galaxy, provided: Vec<String>) {
|
||||
if galaxy.enterprise.damaged.contains_key(systems::PHASERS) {
|
||||
view::inoperable(&systems::name_for(systems::PHASERS));
|
||||
return;
|
||||
}
|
||||
|
||||
let quadrant = &mut galaxy.quadrants[galaxy.enterprise.quadrant.as_index()];
|
||||
if quadrant.klingons.len() == 0 {
|
||||
view::no_local_enemies();
|
||||
return;
|
||||
}
|
||||
|
||||
let computer_damaged = galaxy.enterprise.damaged.contains_key(systems::COMPUTER);
|
||||
if computer_damaged {
|
||||
view::computer_accuracy_issue();
|
||||
}
|
||||
|
||||
let available_energy = galaxy.enterprise.total_energy - galaxy.enterprise.shields;
|
||||
view::phasers_locked(available_energy);
|
||||
let mut power: f32;
|
||||
loop {
|
||||
let setting = param_or_prompt_value(&provided, 0, view::prompts::PHASERS, 0, available_energy);
|
||||
if setting.is_some() {
|
||||
power = setting.unwrap() as f32;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if power == 0.0 {
|
||||
return;
|
||||
}
|
||||
|
||||
galaxy.enterprise.total_energy -= power as u16;
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
if computer_damaged {
|
||||
power *= rng.gen::<f32>();
|
||||
}
|
||||
|
||||
let per_enemy = power / quadrant.klingons.len() as f32;
|
||||
|
||||
for k in &mut quadrant.klingons {
|
||||
let dist = k.sector.abs_diff(galaxy.enterprise.sector) as f32;
|
||||
let hit_strength = per_enemy / dist * (2.0 + rng.gen::<f32>());
|
||||
if hit_strength < 0.15 * k.energy {
|
||||
view::no_damage(k.sector);
|
||||
} else {
|
||||
k.energy -= hit_strength;
|
||||
view::hit_on_klingon(hit_strength, k.sector);
|
||||
if k.energy > 0.0 {
|
||||
view::klingon_remaining_energy(k.energy);
|
||||
} else {
|
||||
view::klingon_destroyed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
quadrant.klingons.retain(|k| k.energy > 0.0);
|
||||
|
||||
klingons_fire(galaxy);
|
||||
}
|
||||
|
||||
pub fn gather_dir_and_launch_torpedo(galaxy: &mut Galaxy, provided: Vec<String>) {
|
||||
let star_bases = galaxy.remaining_starbases();
|
||||
let ship = &mut galaxy.enterprise;
|
||||
|
||||
if ship.damaged.contains_key(systems::TORPEDOES) {
|
||||
view::inoperable(&systems::name_for(systems::TORPEDOES));
|
||||
return;
|
||||
}
|
||||
|
||||
if ship.photon_torpedoes == 0 {
|
||||
view::no_torpedoes_remaining();
|
||||
return;
|
||||
}
|
||||
|
||||
let course = input::param_or_prompt_value(&provided, 0, view::prompts::TORPEDO_COURSE, 1.0, 9.0);
|
||||
if course.is_none() {
|
||||
view::bad_torpedo_course();
|
||||
return;
|
||||
}
|
||||
|
||||
ship.photon_torpedoes -= 1;
|
||||
view::torpedo_track();
|
||||
|
||||
let path = find_torpedo_path(ship.sector, course.unwrap());
|
||||
let quadrant = &mut galaxy.quadrants[ship.quadrant.as_index()];
|
||||
let mut hit = false;
|
||||
for p in path {
|
||||
view::torpedo_path(p);
|
||||
match quadrant.sector_status(p) {
|
||||
SectorStatus::Empty => continue,
|
||||
SectorStatus::Star => {
|
||||
hit = true;
|
||||
view::star_absorbed_torpedo(p);
|
||||
break;
|
||||
},
|
||||
SectorStatus::Klingon => {
|
||||
hit = true;
|
||||
quadrant.get_klingon(p).unwrap().energy = 0.0;
|
||||
quadrant.klingons.retain(|k| k.energy > 0.0);
|
||||
view::klingon_destroyed();
|
||||
break;
|
||||
},
|
||||
SectorStatus::StarBase => {
|
||||
hit = true;
|
||||
quadrant.star_base = None;
|
||||
let remaining = star_bases - 1;
|
||||
view::destroyed_starbase(remaining > 0);
|
||||
if remaining == 0 {
|
||||
ship.destroyed = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ship.destroyed { // if you wiped out the last starbase, trigger game over
|
||||
return;
|
||||
}
|
||||
|
||||
if !hit {
|
||||
view::torpedo_missed();
|
||||
}
|
||||
|
||||
klingons_fire(galaxy);
|
||||
}
|
||||
|
||||
fn find_torpedo_path(start_sector: Pos, course: f32) -> Vec<Pos> {
|
||||
|
||||
let (dx, dy) = calculate_delta(course);
|
||||
|
||||
let mut last_sector = start_sector;
|
||||
let mut path = Vec::new();
|
||||
|
||||
loop {
|
||||
let nx = (last_sector.0 as f32 + dx) as i8;
|
||||
let ny = (last_sector.1 as f32 + dy) as i8;
|
||||
if nx < 0 || ny < 0 || nx >= 8 || ny >= 8 {
|
||||
break;
|
||||
}
|
||||
last_sector = Pos(nx as u8, ny as u8);
|
||||
path.push(last_sector);
|
||||
}
|
||||
|
||||
path
|
||||
}
|
||||
73
84_Super_Star_Trek/rust/src/input.rs
Normal file
73
84_Super_Star_Trek/rust/src/input.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
use std::{io::{stdin, stdout, Write}, str::FromStr};
|
||||
|
||||
pub fn prompt(prompt_text: &str) -> Vec<String> {
|
||||
let stdin = stdin();
|
||||
let mut stdout = stdout();
|
||||
|
||||
print!("{prompt_text} ");
|
||||
let _ = stdout.flush();
|
||||
|
||||
let mut buffer = String::new();
|
||||
if let Ok(_) = stdin.read_line(&mut buffer) {
|
||||
return buffer.trim_end().split([' ', ',']).map(|s| s.to_string()).collect();
|
||||
}
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
pub fn prompt_yes_no(prompt_text: &str) -> bool {
|
||||
loop {
|
||||
let response = prompt(&format!("{prompt_text} (Y/N)"));
|
||||
if response.len() == 0 {
|
||||
continue;
|
||||
}
|
||||
let first_word = response[0].to_uppercase();
|
||||
if first_word.starts_with("Y") {
|
||||
return true;
|
||||
}
|
||||
if first_word.starts_with("N") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prompt_value<T: FromStr + PartialOrd>(prompt_text: &str, min: T, max: T) -> Option<T> {
|
||||
let passed = prompt(prompt_text);
|
||||
if passed.len() != 1 {
|
||||
return None
|
||||
}
|
||||
match passed[0].parse::<T>() {
|
||||
Ok(n) if (n >= min && n <= max) => Some(n),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn param_or_prompt_value<T: FromStr + PartialOrd>(params: &Vec<String>, param_pos: usize, prompt_text: &str, min: T, max: T) -> Option<T> {
|
||||
let mut res: Option<T> = None;
|
||||
if params.len() > param_pos {
|
||||
match params[param_pos].parse::<T>() {
|
||||
Ok(n) if (n >= min && n <= max) => res = Some(n),
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
if res.is_some() {
|
||||
return res;
|
||||
}
|
||||
return prompt_value::<T>(prompt_text, min, max);
|
||||
}
|
||||
|
||||
pub fn prompt_two_values<T: FromStr + PartialOrd>(prompt_text: &str, min: T, max: T) -> Option<(T, T)> {
|
||||
let passed = prompt(prompt_text);
|
||||
if passed.len() != 2 {
|
||||
return None
|
||||
}
|
||||
match passed[0].parse::<T>() {
|
||||
Ok(n1) if (n1 >= min && n1 <= max) => {
|
||||
match passed[1].parse::<T>() {
|
||||
Ok(n2) if (n2 >= min && n2 <= max) =>
|
||||
Some((n1, n2)),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
70
84_Super_Star_Trek/rust/src/main.rs
Normal file
70
84_Super_Star_Trek/rust/src/main.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
use std::process::exit;
|
||||
|
||||
use input::{prompt, prompt_yes_no};
|
||||
use model::{Galaxy, systems};
|
||||
|
||||
mod input;
|
||||
mod model;
|
||||
mod commands;
|
||||
mod view;
|
||||
|
||||
fn main() {
|
||||
ctrlc::set_handler(move || { exit(0) })
|
||||
.expect("Error setting Ctrl-C handler");
|
||||
|
||||
view::title();
|
||||
if prompt_yes_no(view::prompts::INSTRUCTIONS) {
|
||||
view::full_instructions();
|
||||
let _ = input::prompt(view::prompts::WHEN_READY);
|
||||
}
|
||||
|
||||
let mut galaxy = Galaxy::generate_new();
|
||||
let initial_klingons = galaxy.remaining_klingons();
|
||||
let initial_stardate = galaxy.stardate;
|
||||
|
||||
view::enterprise();
|
||||
view::intro(&galaxy);
|
||||
let _ = input::prompt(view::prompts::WHEN_READY);
|
||||
|
||||
view::starting_quadrant(galaxy.enterprise.quadrant);
|
||||
view::short_range_scan(&galaxy);
|
||||
|
||||
loop {
|
||||
let command = input::prompt(view::prompts::COMMAND);
|
||||
if command.len() == 0 {
|
||||
continue;
|
||||
}
|
||||
match command[0].to_uppercase().as_str() { // order is weird because i built it in this order :)
|
||||
systems::SHORT_RANGE_SCAN => commands::perform_short_range_scan(&galaxy),
|
||||
systems::WARP_ENGINES => commands::gather_dir_and_speed_then_move(&mut galaxy, command[1..].into()),
|
||||
systems::SHIELD_CONTROL => commands::get_amount_and_set_shields(&mut galaxy, command[1..].into()),
|
||||
systems::DAMAGE_CONTROL => {
|
||||
commands::run_damage_control(&galaxy);
|
||||
commands::try_starbase_ship_repair(&mut galaxy);
|
||||
}
|
||||
systems::LONG_RANGE_SCAN => commands::perform_long_range_scan(&mut galaxy),
|
||||
systems::COMPUTER => commands::access_computer(&galaxy, command[1..].into()),
|
||||
systems::PHASERS => commands::get_power_and_fire_phasers(&mut galaxy, command[1..].into()),
|
||||
systems::TORPEDOES => commands::gather_dir_and_launch_torpedo(&mut galaxy, command[1..].into()),
|
||||
systems::RESIGN => galaxy.enterprise.destroyed = true,
|
||||
_ => view::print_command_help()
|
||||
}
|
||||
|
||||
if galaxy.enterprise.destroyed || galaxy.enterprise.is_stranded() || galaxy.stardate >= galaxy.final_stardate {
|
||||
view::end_game_failure(&galaxy);
|
||||
if galaxy.remaining_klingons() > 0 && galaxy.remaining_starbases() > 0 && galaxy.stardate < galaxy.final_stardate {
|
||||
view::replay();
|
||||
let result = prompt("");
|
||||
if result.len() > 0 && result[0].to_uppercase() == "AYE" {
|
||||
galaxy.enterprise = Galaxy::new_captain(&galaxy.quadrants);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
} else if galaxy.remaining_klingons() == 0 {
|
||||
let efficiency = 1000.0 * f32::powi(initial_klingons as f32 / (galaxy.stardate - initial_stardate), 2);
|
||||
view::congratulations(efficiency);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
355
84_Super_Star_Trek/rust/src/model.rs
Normal file
355
84_Super_Star_Trek/rust/src/model.rs
Normal file
@@ -0,0 +1,355 @@
|
||||
use std::{ops::{Mul, Add}, fmt::Display, collections::{HashMap, HashSet}};
|
||||
|
||||
use rand::Rng;
|
||||
|
||||
use crate::view;
|
||||
|
||||
pub struct Galaxy {
|
||||
pub stardate: f32,
|
||||
pub final_stardate: f32,
|
||||
pub quadrants: Vec<Quadrant>,
|
||||
pub scanned: HashSet<Pos>,
|
||||
pub enterprise: Enterprise
|
||||
}
|
||||
|
||||
pub struct Quadrant {
|
||||
pub stars: Vec<Pos>,
|
||||
pub star_base: Option<StarBase>,
|
||||
pub klingons: Vec<Klingon>
|
||||
}
|
||||
|
||||
pub struct StarBase {
|
||||
pub sector: Pos,
|
||||
pub repair_delay: f32,
|
||||
}
|
||||
|
||||
pub struct Klingon {
|
||||
pub sector: Pos,
|
||||
pub energy: f32
|
||||
}
|
||||
|
||||
impl Klingon {
|
||||
pub fn fire_on(&mut self, enterprise: &mut Enterprise) {
|
||||
let mut rng = rand::thread_rng();
|
||||
let attack_strength = rng.gen::<f32>();
|
||||
let dist_to_enterprise = self.sector.abs_diff(enterprise.sector) as f32;
|
||||
let hit_strength = self.energy * (2.0 + attack_strength) / dist_to_enterprise;
|
||||
|
||||
self.energy /= 3.0 + attack_strength;
|
||||
|
||||
enterprise.take_hit(self.sector, hit_strength as u16);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Enterprise {
|
||||
pub destroyed: bool,
|
||||
pub damaged: HashMap<String, f32>,
|
||||
pub quadrant: Pos,
|
||||
pub sector: Pos,
|
||||
pub photon_torpedoes: u8,
|
||||
pub total_energy: u16,
|
||||
pub shields: u16,
|
||||
}
|
||||
impl Enterprise {
|
||||
fn take_hit(&mut self, sector: Pos, hit_strength: u16) {
|
||||
if self.destroyed {
|
||||
return;
|
||||
}
|
||||
|
||||
view::enterprise_hit(&hit_strength, sector);
|
||||
|
||||
if self.shields <= hit_strength {
|
||||
view::enterprise_destroyed();
|
||||
self.destroyed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
self.shields -= hit_strength;
|
||||
|
||||
view::shields_hit(self.shields);
|
||||
|
||||
if hit_strength >= 20 {
|
||||
self.take_damage(hit_strength)
|
||||
}
|
||||
}
|
||||
|
||||
fn take_damage(&mut self, hit_strength: u16) {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let hit_past_shield = hit_strength as f32 / self.shields as f32;
|
||||
if rng.gen::<f32>() > 0.6 || hit_past_shield < 0.02 {
|
||||
return
|
||||
}
|
||||
|
||||
let system = systems::KEYS[rng.gen_range(0..systems::KEYS.len())].to_string();
|
||||
let damage = hit_past_shield + rng.gen::<f32>() * 0.5;
|
||||
self.damage_system(&system, damage);
|
||||
}
|
||||
|
||||
pub fn damage_system(&mut self, system: &str, damage: f32) {
|
||||
self.damaged.entry(system.to_string()).and_modify(|d| *d -= damage).or_insert(-damage);
|
||||
}
|
||||
|
||||
pub fn repair_system(&mut self, system: &str, amount: f32) -> bool {
|
||||
let existing_damage = self.damaged[system];
|
||||
if existing_damage + amount >= -0.1 {
|
||||
self.damaged.remove(system);
|
||||
return true;
|
||||
}
|
||||
|
||||
self.damaged.entry(system.to_string()).and_modify(|d| *d += amount);
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn is_stranded(&self) -> bool {
|
||||
if self.total_energy < 10 || (self.shields + 10 > self.total_energy && self.damaged.contains_key(systems::SHIELD_CONTROL)) {
|
||||
view::stranded();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
pub mod systems {
|
||||
|
||||
pub const SHORT_RANGE_SCAN: &str = "SRS";
|
||||
pub const WARP_ENGINES: &str = "NAV";
|
||||
pub const SHIELD_CONTROL: &str = "SHE";
|
||||
pub const DAMAGE_CONTROL: &str = "DAM";
|
||||
pub const LONG_RANGE_SCAN: &str = "LRS";
|
||||
pub const COMPUTER: &str = "COM";
|
||||
pub const PHASERS: &str = "PHA";
|
||||
pub const TORPEDOES: &str = "TOR";
|
||||
|
||||
pub const RESIGN: &str = "XXX";
|
||||
|
||||
pub const KEYS: [&str; 8] = [
|
||||
SHORT_RANGE_SCAN, WARP_ENGINES, SHIELD_CONTROL, DAMAGE_CONTROL, LONG_RANGE_SCAN, COMPUTER, PHASERS, TORPEDOES
|
||||
];
|
||||
|
||||
pub fn name_for(key: &str) -> String {
|
||||
match key {
|
||||
SHORT_RANGE_SCAN => "Short Range Scanners".into(),
|
||||
WARP_ENGINES => "Warp Engines".into(),
|
||||
SHIELD_CONTROL => "Shield Control".into(),
|
||||
DAMAGE_CONTROL => "Damage Control".into(),
|
||||
LONG_RANGE_SCAN => "Long Range Scanners".into(),
|
||||
COMPUTER => "Library-Computer".into(),
|
||||
PHASERS => "Phaser Control".into(),
|
||||
TORPEDOES => "Photon Tubes".into(),
|
||||
_ => "Unknown".into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Copy, Debug, Hash, Eq)]
|
||||
pub struct Pos(pub u8, pub u8);
|
||||
|
||||
impl Pos {
|
||||
pub fn as_index(&self) -> usize {
|
||||
(self.0 * 8 + self.1).into()
|
||||
}
|
||||
|
||||
pub fn abs_diff(&self, other: Pos) -> u8 {
|
||||
self.0.abs_diff(other.0) + self.1.abs_diff(other.1)
|
||||
}
|
||||
|
||||
pub fn dist(&self, other: Pos) -> u8 {
|
||||
let dx = other.0 as f32 - self.0 as f32;
|
||||
let dy = other.1 as f32 - self.1 as f32;
|
||||
(f32::powi(dx, 2) + f32::powi(dy, 2)).sqrt() as u8
|
||||
}
|
||||
|
||||
pub fn direction(&self, other: Pos) -> f32 {
|
||||
// this is a replication of the original BASIC code
|
||||
let dx = other.0 as f32 - self.0 as f32;
|
||||
let dy = other.1 as f32 - self.1 as f32;
|
||||
let dx_dominant = dx.abs() > dy.abs();
|
||||
|
||||
let frac = if dx_dominant { dy / dx } else { -dx / dy };
|
||||
let nearest_cardinal =
|
||||
if dx_dominant {
|
||||
if dx > 0. { 7. } else { 3. }
|
||||
} else {
|
||||
if dy > 0. { 1. } else { 5. }
|
||||
};
|
||||
|
||||
let mut dir = nearest_cardinal + frac;
|
||||
if dir < 1. {
|
||||
dir += 8.
|
||||
}
|
||||
dir
|
||||
}
|
||||
|
||||
pub fn as_galactic_sector(&self, containing_quadrant: Pos) -> Self {
|
||||
Pos(containing_quadrant.0 * 8 + self.0, containing_quadrant.1 * 8 + self.1)
|
||||
}
|
||||
|
||||
pub fn to_local_quadrant_sector(&self) -> (Self, Self) {
|
||||
(Pos(self.0 / 8, self.1 / 8), Pos(self.0 % 8, self.1 % 8))
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<u8> for Pos {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, rhs: u8) -> Self::Output {
|
||||
Pos(self.0 * rhs, self.1 * rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Pos> for Pos {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: Pos) -> Self::Output {
|
||||
Pos(self.0 + rhs.0, self.1 + rhs.1)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Pos {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{} , {}", self.0 + 1, self.1 + 1)
|
||||
}
|
||||
}
|
||||
|
||||
pub const COURSES : [(f32, f32); 9] = [
|
||||
(0., 1.),
|
||||
(-1., 1.),
|
||||
(-1., 0.),
|
||||
(-1., -1.),
|
||||
(0., -1.),
|
||||
(1., -1.),
|
||||
(1., 0.),
|
||||
(1., 1.),
|
||||
(0., 1.), // course 9 is equal to course 1
|
||||
];
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum SectorStatus {
|
||||
Empty, Star, StarBase, Klingon
|
||||
}
|
||||
|
||||
pub const MAX_PHOTON_TORPEDOES: u8 = 28;
|
||||
pub const MAX_ENERGY: u16 = 3000;
|
||||
|
||||
impl Galaxy {
|
||||
pub fn remaining_klingons(&self) -> u8 {
|
||||
let quadrants = &self.quadrants;
|
||||
quadrants.into_iter().map(|q| { q.klingons.len() as u8 }).sum::<u8>()
|
||||
}
|
||||
|
||||
pub fn remaining_starbases(&self) -> u8 {
|
||||
let quadrants = &self.quadrants;
|
||||
quadrants.into_iter().filter(|q| q.star_base.is_some()).count() as u8
|
||||
}
|
||||
|
||||
pub fn generate_new() -> Self {
|
||||
let quadrants = Self::generate_quadrants();
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
let stardate = rng.gen_range(20..=40) as f32 * 100.0;
|
||||
|
||||
let enterprise = Self::new_captain(&quadrants);
|
||||
|
||||
let mut scanned = HashSet::new();
|
||||
scanned.insert(enterprise.quadrant);
|
||||
|
||||
Galaxy {
|
||||
stardate,
|
||||
final_stardate: stardate + rng.gen_range(25..=35) as f32,
|
||||
quadrants,
|
||||
scanned,
|
||||
enterprise
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_captain(quadrants: &Vec<Quadrant>) -> Enterprise {
|
||||
let mut rng = rand::thread_rng();
|
||||
let enterprise_quadrant = Pos(rng.gen_range(0..8), rng.gen_range(0..8));
|
||||
let enterprise_sector = quadrants[enterprise_quadrant.as_index()].find_empty_sector();
|
||||
Enterprise {
|
||||
destroyed: false,
|
||||
damaged: HashMap::new(),
|
||||
quadrant: enterprise_quadrant,
|
||||
sector: enterprise_sector,
|
||||
photon_torpedoes: MAX_PHOTON_TORPEDOES,
|
||||
total_energy: MAX_ENERGY,
|
||||
shields: 0 }
|
||||
}
|
||||
|
||||
fn generate_quadrants() -> Vec<Quadrant> {
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut result = Vec::new();
|
||||
for _ in 0..64 {
|
||||
|
||||
let mut quadrant = Quadrant { stars: Vec::new(), star_base: None, klingons: Vec::new() };
|
||||
let star_count = rng.gen_range(0..=7);
|
||||
for _ in 0..star_count {
|
||||
quadrant.stars.push(quadrant.find_empty_sector());
|
||||
}
|
||||
|
||||
if rng.gen::<f64>() > 0.96 {
|
||||
quadrant.star_base = Some(StarBase { sector: quadrant.find_empty_sector(), repair_delay: rng.gen::<f32>() * 0.5 });
|
||||
}
|
||||
|
||||
let klingon_count =
|
||||
match rng.gen::<f64>() {
|
||||
n if n > 0.98 => 3,
|
||||
n if n > 0.95 => 2,
|
||||
n if n > 0.8 => 1,
|
||||
_ => 0
|
||||
};
|
||||
for _ in 0..klingon_count {
|
||||
quadrant.klingons.push(Klingon { sector: quadrant.find_empty_sector(), energy: rng.gen_range(100..=300) as f32 });
|
||||
}
|
||||
|
||||
result.push(quadrant);
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl Quadrant {
|
||||
pub fn sector_status(&self, sector: Pos) -> SectorStatus {
|
||||
if self.stars.contains(§or) {
|
||||
SectorStatus::Star
|
||||
} else if self.is_starbase(sector) {
|
||||
SectorStatus::StarBase
|
||||
} else if self.has_klingon(sector) {
|
||||
SectorStatus::Klingon
|
||||
} else {
|
||||
SectorStatus::Empty
|
||||
}
|
||||
}
|
||||
|
||||
fn is_starbase(&self, sector: Pos) -> bool {
|
||||
match &self.star_base {
|
||||
None => false,
|
||||
Some(p) => p.sector == sector
|
||||
}
|
||||
}
|
||||
|
||||
fn has_klingon(&self, sector: Pos) -> bool {
|
||||
let klingons = &self.klingons;
|
||||
klingons.into_iter().find(|k| k.sector == sector).is_some()
|
||||
}
|
||||
|
||||
pub fn get_klingon(&mut self, sector: Pos) -> Option<&mut Klingon> {
|
||||
let klingons = &mut self.klingons;
|
||||
klingons.into_iter().find(|k| k.sector == sector)
|
||||
}
|
||||
|
||||
pub fn find_empty_sector(&self) -> Pos {
|
||||
let mut rng = rand::thread_rng();
|
||||
loop {
|
||||
let pos = Pos(rng.gen_range(0..8), rng.gen_range(0..8));
|
||||
if self.sector_status(pos) == SectorStatus::Empty {
|
||||
return pos
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn docked_at_starbase(&self, enterprise_sector: Pos) -> bool {
|
||||
self.star_base.is_some() && self.star_base.as_ref().unwrap().sector.abs_diff(enterprise_sector) == 1
|
||||
}
|
||||
}
|
||||
635
84_Super_Star_Trek/rust/src/view.rs
Normal file
635
84_Super_Star_Trek/rust/src/view.rs
Normal file
@@ -0,0 +1,635 @@
|
||||
use crate::model::{Galaxy, Pos, SectorStatus, Enterprise, systems};
|
||||
|
||||
pub mod prompts {
|
||||
pub const INSTRUCTIONS: &str = "Do you need instructions";
|
||||
pub const COURSE: &str = "Course (1-9)?";
|
||||
pub const TORPEDO_COURSE: &str = "Photon torpedo course (1-9)?";
|
||||
pub const SHIELDS: &str = "Number of units to shields";
|
||||
pub const REPAIR: &str = "Will you authorize the repair order";
|
||||
pub const COMPUTER: &str = "Computer active and waiting command?";
|
||||
pub const PHASERS: &str = "Number of units to fire";
|
||||
pub const WHEN_READY: &str = "Press Enter when ready to accept command";
|
||||
pub const COMMAND: &str = "Command?";
|
||||
pub const INITIAL_COORDS: &str = " Initial coordinates (X/Y)";
|
||||
pub const TARGET_COORDS: &str = " Final coordinates (X/Y)";
|
||||
|
||||
pub fn warp_factor(max_warp: f32) -> String {
|
||||
format!("Warp Factor (0-{})?", max_warp)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn title() {
|
||||
println!("
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
*************************************
|
||||
* *
|
||||
* *
|
||||
* * * SUPER STAR TREK * * *
|
||||
* *
|
||||
* *
|
||||
*************************************
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
");
|
||||
}
|
||||
|
||||
pub fn full_instructions() {
|
||||
println!(
|
||||
" INSTRUCTIONS FOR 'SUPER STAR TREK'
|
||||
|
||||
1. When you see \"Command ?\" printed, enter one of the legal
|
||||
commands (NAV, SRS, LRS, PHA, TOR, SHE, DAM, COM, OR XXX).
|
||||
2. If you should type in an illegal command, you'll get a short
|
||||
list of the legal commands printed out.
|
||||
3. Some commands require you to enter data (for example, the
|
||||
'NAV' command comes back with 'Course (1-9) ?'.) If you
|
||||
type in illegal data (like negative numbers), then command
|
||||
will be aborted.
|
||||
|
||||
The galaxy is divided into an 8 X 8 quadrant grid,
|
||||
and each quadrant is further divided into an 8 X 8 sector grid.
|
||||
|
||||
You will be assigned a starting point somewhere in the
|
||||
galaxy to begin a tour of duty as commander of the starship
|
||||
Enterprise; your mission: to seek and destroy the fleet of
|
||||
Klingon warships which are menacing the United Federation of
|
||||
Planets.
|
||||
|
||||
You have the following commands available to you as captain
|
||||
of the starship Enterprise:
|
||||
|
||||
NAV command = Warp Engine Control
|
||||
Course is in a circular numerical 4 3 2
|
||||
vector arrangement as shown . . .
|
||||
integer and real values may be ...
|
||||
used. (Thus course 1.5 is half- 5 ---*--- 1
|
||||
way between 1 and 2. ...
|
||||
. . .
|
||||
Values may approach 9.0, which 6 7 8
|
||||
itself is equivalent to 1.0
|
||||
COURSE
|
||||
One warp factor is the size of
|
||||
one quadrant. Therefore, to get
|
||||
from quadrant 6,5 to 5,5, you WOULD
|
||||
use course 3, warp factor 1.
|
||||
|
||||
SRS command = Short Range Sensor Scan
|
||||
Shows you a scan of your present quadrant.
|
||||
|
||||
Symbology on your sensor screen is as follows:
|
||||
<*> = Your starship's position
|
||||
+K+ = Klingon battle cruiser
|
||||
>!< = Federation starbase (refuel/repair/re-arm here!)
|
||||
* = Star
|
||||
|
||||
A condensed 'status report' will also be presented.
|
||||
|
||||
LRS command = Long Range Sensor Scan
|
||||
Shows conditions in space for one quadrant on each side
|
||||
of the Enterprise (which is in the middle of the scan).
|
||||
The scan is coded in the form ###, where the units digit
|
||||
is the number of stars, the tens digit is the number of
|
||||
starbases, and the hundreds digit is the number of
|
||||
Klingons.
|
||||
|
||||
Example - 207 = 2 Klingons, No starbases, & 7 stars.
|
||||
|
||||
PHA command = Phaser Control
|
||||
Allows you to destroy the Klingon battle cruisers by
|
||||
zapping them with suitably large units of energy to
|
||||
deplete their shield power. (Remember, Klingons have
|
||||
phasers, too!)
|
||||
|
||||
TOR command = Photon Torpedo Control
|
||||
Torpedo course is the same as used in warp engine control.
|
||||
If you hit the Klingon vessel, he is destroyed and
|
||||
cannot fire back at you. If you miss, you are subject to
|
||||
his phaser fire. In either case, you are also subject to
|
||||
the phaser fire of all other Klingons in the quadrant.
|
||||
|
||||
The library-computer (COM command) has an option to
|
||||
compute torpedo trajectory for you (Option 2).
|
||||
|
||||
SHE command = Shield Control
|
||||
Defines the number of energy units to be assigned to the
|
||||
shields. Energy is taken from total ship's energy. Note
|
||||
that the status display total energy includes shield energy.
|
||||
|
||||
DAM command = Damage Control Report
|
||||
Gives the state of repair of all devices. Where a negative
|
||||
'state of repair' shows that the device is temporarily
|
||||
damaged.
|
||||
|
||||
COM command = Library-Computer
|
||||
The library-computer contains six options:
|
||||
Option 0 = Cumulative Galactic Record
|
||||
This option shows computer memory of the results of all
|
||||
previous short and long range sensor scans.
|
||||
Option 1 = Status Report
|
||||
This option shows the number of Klingons, Stardates,
|
||||
and starbases remaining in the game.
|
||||
Option 2 = Photon Torpedo Data
|
||||
Which gives directions and distance from the Enterprise
|
||||
to all Klingons in your quadrant.
|
||||
Option 3 = Starbase Nav Data
|
||||
This option gives direction and distance to any
|
||||
starbase within your quadrant.
|
||||
Option 4 = Direction/Distance Calculator
|
||||
This option allows you to enter coordinates for
|
||||
direction/distance calculations.
|
||||
Option 5 = Galactic Region Name Map
|
||||
This option prints the names of the sixteen major
|
||||
galactic regions referred to in the game.
|
||||
|
||||
")
|
||||
}
|
||||
|
||||
pub fn enterprise() {
|
||||
println!("
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
,------*------,
|
||||
,------------- '--- ------'
|
||||
'-------- --' / /
|
||||
,---' '-------/ /--,
|
||||
'----------------'
|
||||
|
||||
THE USS ENTERPRISE --- NCC-1701
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
")
|
||||
}
|
||||
|
||||
pub fn intro(model: &Galaxy) {
|
||||
let star_bases = model.remaining_starbases();
|
||||
let mut star_base_message: String = "There is 1 starbase".into();
|
||||
if star_bases > 1 {
|
||||
star_base_message = format!("There are {} starbases", star_bases);
|
||||
}
|
||||
println!(
|
||||
"Your orders are as follows:
|
||||
Destroy the {} Klingon warships which have invaded
|
||||
the galaxy before they can attack federation headquarters
|
||||
on stardate {:.1}. This gives you {} days. {} in the galaxy for resupplying your ship.\n",
|
||||
model.remaining_klingons(), model.final_stardate, model.final_stardate - model.stardate, star_base_message)
|
||||
}
|
||||
|
||||
const REGION_NAMES: [&str; 16] = [
|
||||
"Antares",
|
||||
"Sirius",
|
||||
"Rigel",
|
||||
"Deneb",
|
||||
"Procyon",
|
||||
"Capella",
|
||||
"Vega",
|
||||
"Betelgeuse",
|
||||
"Canopus",
|
||||
"Aldebaran",
|
||||
"Altair",
|
||||
"Regulus",
|
||||
"Sagittarius",
|
||||
"Arcturus",
|
||||
"Pollux",
|
||||
"Spica"
|
||||
];
|
||||
|
||||
const SUB_REGION_NAMES: [&str; 4] = ["I", "II", "III", "IV"];
|
||||
|
||||
fn quadrant_name(quadrant: Pos) -> String {
|
||||
format!("{} {}",
|
||||
REGION_NAMES[((quadrant.0 << 1) + (quadrant.1 >> 2)) as usize],
|
||||
SUB_REGION_NAMES[(quadrant.1 % 4) as usize])
|
||||
}
|
||||
|
||||
pub fn starting_quadrant(quadrant: Pos) {
|
||||
println!(
|
||||
"\nYour mission begins with your starship located
|
||||
in the galactic quadrant, '{}'.\n", quadrant_name(quadrant))
|
||||
}
|
||||
|
||||
pub fn enter_quadrant(quadrant: Pos) {
|
||||
println!("\nNow entering {} quadrant . . .\n", quadrant_name(quadrant))
|
||||
}
|
||||
|
||||
pub fn short_range_scan(model: &Galaxy) {
|
||||
let quadrant = &model.quadrants[model.enterprise.quadrant.as_index()];
|
||||
let mut condition = "GREEN";
|
||||
if quadrant.docked_at_starbase(model.enterprise.sector) {
|
||||
println!("Shields dropped for docking purposes");
|
||||
condition = "DOCKED";
|
||||
} else if quadrant.klingons.len() > 0 {
|
||||
condition = "*RED*";
|
||||
} else if model.enterprise.damaged.len() > 0 {
|
||||
condition = "YELLOW";
|
||||
}
|
||||
|
||||
let data : [String; 8] = [
|
||||
format!("Stardate {:.1}", model.stardate),
|
||||
format!("Condition {}", condition),
|
||||
format!("Quadrant {}", model.enterprise.quadrant),
|
||||
format!("Sector {}", model.enterprise.sector),
|
||||
format!("Photon torpedoes {}", model.enterprise.photon_torpedoes),
|
||||
format!("Total energy {}", model.enterprise.total_energy),
|
||||
format!("Shields {}", model.enterprise.shields),
|
||||
format!("Klingons remaining {}", model.remaining_klingons()),
|
||||
];
|
||||
|
||||
println!("{:-^33}", "");
|
||||
for x in 0..=7 {
|
||||
for y in 0..=7 {
|
||||
let pos = Pos(x, y);
|
||||
if &pos == &model.enterprise.sector {
|
||||
print!("<*> ")
|
||||
} else {
|
||||
match quadrant.sector_status(pos) {
|
||||
SectorStatus::Star => print!(" * "),
|
||||
SectorStatus::StarBase => print!(">!< "),
|
||||
SectorStatus::Klingon => print!("+K+ "),
|
||||
_ => print!(" "),
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("{:>9}{}", "", data[x as usize])
|
||||
}
|
||||
println!("{:-^33}", "");
|
||||
}
|
||||
|
||||
pub fn print_command_help() {
|
||||
println!(
|
||||
"Enter one of the following:
|
||||
NAV (To set course)
|
||||
SRS (For short range sensor scan)
|
||||
LRS (For long range sensor scan)
|
||||
PHA (To fire phasers)
|
||||
TOR (To fire photon torpedoes)
|
||||
SHE (To raise or lower shields)
|
||||
DAM (For damage control reports)
|
||||
COM (To call on library-computer)
|
||||
XXX (To resign your command)
|
||||
")
|
||||
}
|
||||
|
||||
pub fn end_game_failure(galaxy: &Galaxy) {
|
||||
println!(
|
||||
"Is is stardate {:.1}.
|
||||
There were {} Klingon battle cruisers left at
|
||||
the end of your mission.
|
||||
", galaxy.stardate, galaxy.remaining_klingons());
|
||||
}
|
||||
|
||||
pub fn enterprise_destroyed() {
|
||||
println!("The Enterprise has been destroyed. The Federation will be conquered.");
|
||||
}
|
||||
|
||||
pub fn bad_course_data() {
|
||||
println!(" Lt. Sulu reports, 'Incorrect course data, sir!'")
|
||||
}
|
||||
|
||||
pub fn bad_nav(current_sector: Pos) {
|
||||
println!("Warp engines shut down at sector {current_sector} dues to bad navigation")
|
||||
}
|
||||
|
||||
pub fn bad_torpedo_course() {
|
||||
println!(" Ensign Chekov reports, 'Incorrect course data, sir!'")
|
||||
}
|
||||
|
||||
pub fn enterprise_hit(hit_strength: &u16, from_sector: Pos) {
|
||||
println!("{hit_strength} unit hit on Enterprise from sector {from_sector}");
|
||||
}
|
||||
|
||||
pub fn hit_edge(quadrant: Pos, sector: Pos) {
|
||||
println!(
|
||||
"Lt. Uhura report message from Starfleet Command:
|
||||
'Permission to attempt crossing of galactic perimeter
|
||||
is hereby *Denied*. Shut down your engines.'
|
||||
Chief Engineer Scott reports, 'Warp engines shut down
|
||||
at sector {} of quadrant {}.'", sector, quadrant);
|
||||
}
|
||||
|
||||
pub fn condition_red() {
|
||||
println!("COMBAT AREA CONDITION RED")
|
||||
}
|
||||
|
||||
pub fn danger_shields() {
|
||||
println!(" SHIELDS DANGEROUSLY LOW ")
|
||||
}
|
||||
|
||||
pub fn insuffient_warp_energy(warp_speed: f32) {
|
||||
println!(
|
||||
"Engineering reports, 'Insufficient energy available
|
||||
for maneuvering at warp {warp_speed} !'")
|
||||
}
|
||||
|
||||
pub fn divert_energy_from_shields() {
|
||||
println!("Shield Control supplies energy to complete the maneuver.")
|
||||
}
|
||||
|
||||
pub fn energy_available(total_energy: u16) {
|
||||
println!("Energy available = {{{total_energy}}}")
|
||||
}
|
||||
|
||||
pub fn shields_unchanged() {
|
||||
println!("<SHIELDS UNCHANGED>")
|
||||
}
|
||||
|
||||
pub fn ridiculous() {
|
||||
println!("Shield Control reports, 'This is not the Federation Treasury.'")
|
||||
}
|
||||
|
||||
pub fn shields_set(value: u16) {
|
||||
println!(
|
||||
"Deflector control room report:
|
||||
'Shields now at {value} units per your command.'")
|
||||
}
|
||||
|
||||
pub fn shields_hit(shields: u16) {
|
||||
println!(" <Shields down to {shields} units>")
|
||||
}
|
||||
|
||||
pub fn inoperable(arg: &str) {
|
||||
println!("{} inoperable", arg)
|
||||
}
|
||||
|
||||
pub fn scanners_out() {
|
||||
println!("*** Short Range Sensors are out ***")
|
||||
}
|
||||
|
||||
pub fn damaged_engines(max_warp: f32, warp_factor: f32) {
|
||||
println!(
|
||||
"Warp engines are damaged. Maximum speed = warp {max_warp}
|
||||
Chief Engineer Scott reports, 'The engines won't take warp {warp_factor} !'")
|
||||
}
|
||||
|
||||
pub fn damage_control_report() {
|
||||
println!("Damage Control report:")
|
||||
}
|
||||
|
||||
pub fn random_repair_report_for(name: &str, damaged: bool) {
|
||||
let mut message = "state of repair improved";
|
||||
if damaged {
|
||||
message = "damaged";
|
||||
}
|
||||
println!("Damage Control report: {name} {message}")
|
||||
}
|
||||
|
||||
pub fn system_repair_completed(name: String) {
|
||||
println!(" {name} repair completed.")
|
||||
}
|
||||
|
||||
pub fn damage_control(enterprise: &Enterprise) {
|
||||
println!("Device State of Repair");
|
||||
for key in systems::KEYS {
|
||||
let damage = enterprise.damaged.get(key).unwrap_or(&0.0);
|
||||
println!("{:<25}{}", systems::name_for(key), damage)
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
pub fn long_range_scan(galaxy: &Galaxy) -> Vec<Pos> {
|
||||
|
||||
let cx = galaxy.enterprise.quadrant.0 as i8;
|
||||
let cy = galaxy.enterprise.quadrant.1 as i8;
|
||||
|
||||
let mut seen = Vec::new();
|
||||
|
||||
println!("Long range scan for quadrant {}", galaxy.enterprise.quadrant);
|
||||
println!("{:-^19}", "");
|
||||
for x in cx - 1..=cx + 1 {
|
||||
for y in cy - 1..=cy + 1 {
|
||||
let mut klingons = "*".into();
|
||||
let mut star_bases = "*".into();
|
||||
let mut stars = "*".into();
|
||||
|
||||
if y >= 0 && y < 8 && x >= 0 && x < 8 {
|
||||
let pos = Pos(x as u8, y as u8);
|
||||
seen.push(pos);
|
||||
|
||||
let quadrant = &galaxy.quadrants[pos.as_index()];
|
||||
klingons = format!("{}", quadrant.klingons.len());
|
||||
star_bases = quadrant.star_base.as_ref().map_or("0", |_| "1");
|
||||
stars = format!("{}", quadrant.stars.len());
|
||||
}
|
||||
|
||||
print!(": {}{}{} ", klingons, star_bases, stars)
|
||||
}
|
||||
println!(":");
|
||||
println!("{:-^19}", "");
|
||||
}
|
||||
|
||||
seen
|
||||
}
|
||||
|
||||
pub fn stranded() {
|
||||
println!(
|
||||
"** FATAL ERROR ** You've just stranded your ship in space
|
||||
You have insufficient maneuvering energy, and shield control
|
||||
is presently incapable of cross-circuiting to engine room!!")
|
||||
}
|
||||
|
||||
pub fn computer_options() {
|
||||
println!(
|
||||
" 0 = Cumulative galactic record
|
||||
1 = Status report
|
||||
2 = Photon torpedo data
|
||||
3 = Starbase nav data
|
||||
4 = Direction/distance calculator
|
||||
5 = Galaxy 'region name' map")
|
||||
}
|
||||
|
||||
pub fn galaxy_region_map() {
|
||||
println!(
|
||||
" The Galaxy
|
||||
1 2 3 4 5 6 7 8
|
||||
----- ----- ----- ----- ----- ----- ----- -----");
|
||||
for i in (0..REGION_NAMES.len()-1).step_by(2) {
|
||||
println!(
|
||||
"{} {:^23} {:^23}
|
||||
----- ----- ----- ----- ----- ----- ----- -----", (i/2)+1, REGION_NAMES[i], REGION_NAMES[i+1]);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn galaxy_scanned_map(galaxy: &Galaxy) {
|
||||
println!(
|
||||
"Computer record of galaxy for quadrant {}
|
||||
1 2 3 4 5 6 7 8
|
||||
----- ----- ----- ----- ----- ----- ----- -----", galaxy.enterprise.quadrant);
|
||||
for x in 0..8 {
|
||||
print!("{} ", x+1);
|
||||
for y in 0..8 {
|
||||
let pos = Pos(x, y);
|
||||
if galaxy.scanned.contains(&pos) {
|
||||
let quadrant = &galaxy.quadrants[pos.as_index()];
|
||||
print!(" {}{}{} ", quadrant.klingons.len(), quadrant.star_base.as_ref().map_or("0", |_| "1"), quadrant.stars.len())
|
||||
} else {
|
||||
print!(" *** ");
|
||||
}
|
||||
}
|
||||
println!(
|
||||
"\n ----- ----- ----- ----- ----- ----- ----- -----")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn no_local_enemies() {
|
||||
println!(
|
||||
"Science Officer Spock reports, 'Sensors show no enemy ships
|
||||
in this quadrant'")
|
||||
}
|
||||
|
||||
pub fn computer_accuracy_issue() {
|
||||
println!("Computer failure hampers accuracy")
|
||||
}
|
||||
|
||||
pub fn phasers_locked(available_energy: u16) {
|
||||
println!("Phasers locked on target; Energy available = {available_energy} units")
|
||||
}
|
||||
|
||||
pub fn starbase_shields() {
|
||||
println!("Starbase shields protect the Enterprise")
|
||||
}
|
||||
|
||||
pub fn repair_estimate(repair_time: f32) {
|
||||
println!(
|
||||
"Technicians standing by to effect repairs to your ship;
|
||||
Estimated time to repair: {repair_time:.1} stardates.")
|
||||
}
|
||||
|
||||
pub fn no_damage(sector: Pos) {
|
||||
println!("Sensors show no damage to enemy at {sector}")
|
||||
}
|
||||
|
||||
pub fn hit_on_klingon(hit_strength: f32, sector: Pos) {
|
||||
println!("{hit_strength} unit hit on Klingon at sector {sector}")
|
||||
}
|
||||
|
||||
pub fn klingon_remaining_energy(energy: f32) {
|
||||
println!(" (sensors show {energy} units remaining)")
|
||||
}
|
||||
|
||||
pub fn klingon_destroyed() {
|
||||
println!("*** Klingon destroyed ***")
|
||||
}
|
||||
|
||||
pub fn congratulations(efficiency: f32) {
|
||||
println!("
|
||||
Congratulations, Captain! The last Klingon battle cruiser
|
||||
menacing the Federation has been destroyed.
|
||||
|
||||
Your efficiency rating is {efficiency}.
|
||||
")
|
||||
}
|
||||
|
||||
pub fn replay() {
|
||||
println!("
|
||||
The Federation is in need of a new starship commander
|
||||
for a similar mission -- if there is a volunteer
|
||||
let him step forward and enter 'Aye'")
|
||||
}
|
||||
|
||||
pub fn no_torpedoes_remaining() {
|
||||
println!("All photon torpedoes expended")
|
||||
}
|
||||
|
||||
pub fn torpedo_track() {
|
||||
println!("Torpedo track:")
|
||||
}
|
||||
|
||||
pub fn torpedo_path(sector: Pos) {
|
||||
println!("{:<16}{}", "", sector)
|
||||
}
|
||||
|
||||
pub fn torpedo_missed() {
|
||||
println!("Torpedo missed!")
|
||||
}
|
||||
|
||||
pub fn star_absorbed_torpedo(sector: Pos) {
|
||||
println!("Star at {sector} absorbed torpedo energy.")
|
||||
}
|
||||
|
||||
pub fn destroyed_starbase(not_the_last_starbase: bool) {
|
||||
println!("*** Starbase destroyed ***");
|
||||
if not_the_last_starbase {
|
||||
println!("
|
||||
Starfleet Command reviewing your record to consider
|
||||
court martial!")
|
||||
} else {
|
||||
println!("
|
||||
That does it, Captain!! You are hereby relieved of command
|
||||
and sentenced to 99 stardates at hard labor on Cygnus 12!!")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn no_local_starbase() {
|
||||
println!("Mr. Spock reports, 'Sensors show no starbases in this quadrant.'")
|
||||
}
|
||||
|
||||
pub fn starbase_report() {
|
||||
println!("From Enterprise to Starbase:'")
|
||||
}
|
||||
|
||||
pub fn direction_distance(dir: f32, dist: u8) {
|
||||
println!(
|
||||
"Direction = {dir}
|
||||
Distance = {dist}"
|
||||
)
|
||||
}
|
||||
|
||||
pub fn status_report(galaxy: &Galaxy) {
|
||||
let klingon_count = galaxy.remaining_klingons();
|
||||
let star_bases = galaxy.remaining_starbases();
|
||||
let time_remaining = galaxy.final_stardate - galaxy.stardate;
|
||||
let mut plural_starbase = "";
|
||||
if star_bases > 1 {
|
||||
plural_starbase = "s";
|
||||
}
|
||||
|
||||
println!(" Status report:
|
||||
Klingons left: {klingon_count}
|
||||
Mission must be completed in {time_remaining:.1} stardates.
|
||||
The Federation is maintaining {star_bases} starbase{plural_starbase} in the galaxy.
|
||||
")
|
||||
}
|
||||
|
||||
pub fn direction_dist_intro(enterprise: &Enterprise) {
|
||||
let quadrant = enterprise.quadrant;
|
||||
let sector = enterprise.sector;
|
||||
println!("Direction/distance calculator:
|
||||
You are at quadrant {quadrant} sector {sector}
|
||||
Please enter")
|
||||
}
|
||||
|
||||
pub fn klingon_report(more_than_one: bool) {
|
||||
let mut plural = "";
|
||||
if more_than_one {
|
||||
plural = "s";
|
||||
}
|
||||
println!("From Enterprise to Klingon battle cruiser{}", plural)
|
||||
}
|
||||
Reference in New Issue
Block a user