https://retrocoders.phatcode.net/index.php?topic=868.0 (https://retrocoders.phatcode.net/index.php?topic=868.0)
the rust implementation:
main.rs:
use std::fs;
use std::io::{self, Write};
use std::thread;
use std::time::Duration;
use std::ffi::CString;
use std::env;
use rand::Rng;
use anyhow::{Result, Context};
use bass_sys::*;
// File paths
const DESC1: &str = "data/des1.txt";
const OPENING_BANNER: &str = "banners/opening1.txt";
const OPENING_POINT: &str = "data/opening1.txt";
const MENU: &str = "data/mainmenu.txt";
const MONEY_MENU: &str = "data/moneyMenu.txt";
const FRIENDS_MENU: &str = "data/friendsmenu.txt";
const OVER: &str = "data/suicide.txt";
const GAME_END: &str = "data/theend.txt";
const FAMILY1: &str = "data/family1.txt";
const SCHOOL1: &str = "data/school1.txt";
// Arrays of file paths
const MONTHS: [&str; 12] = [
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
];
const SONGS_RAND: [&str; 12] = [
"sound/walrus1.ogg", "sound/walrus2.ogg", "sound/walrus3.ogg",
"sound/walrus4.ogg", "sound/walrus5.ogg", "sound/walrus6.ogg",
"sound/walrus7.ogg", "sound/walrus8.ogg", "sound/walrus9.ogg",
"sound/walrus10.ogg", "sound/walrus11.ogg", "sound/walrus12.ogg"
];
const MARY: [&str; 5] = [
"data/mary1.txt", "data/mary2rehab.txt", "data/mary3pregnant.txt",
"data/mary4jail.txt", "data/mary5dead.txt"
];
const JOE: [&str; 5] = [
"data/joe1.txt", "data/joe2rehab.txt", "data/joe3jail.txt",
"data/joe4.txt", "data/joe5prison.txt"
];
const FELIX: [&str; 5] = [
"data/felix1.txt", "data/felix2.txt", "data/felix3jail.txt",
"data/felix4rehab.txt", "data/felix5crazy.txt"
];
struct GameState {
health: i32,
money: i32,
turns: usize,
year: i32,
is_over: bool,
is_mary: bool,
is_joe: bool,
is_felix: bool,
is_suicide: bool,
stream: u32,
}
impl GameState {
fn new() -> Self {
Self {
health: 100,
money: 5000,
turns: 0,
year: 1991,
is_over: false,
is_mary: true,
is_joe: true,
is_felix: true,
is_suicide: false,
stream: 0,
}
}
fn play_music(&mut self, path: &str) -> Result<()> {
let mut full_path = env::current_dir()?;
full_path.push(path);
let path_str = full_path.to_str().unwrap();
println!("Attempting to play: {}", path_str);
if self.stream != 0 {
BASS_StreamFree(self.stream);
self.stream = 0;
}
let path_c = CString::new(path_str)?;
self.stream = BASS_StreamCreateFile(
0,
path_c.as_ptr() as *const _,
0,
0,
BASS_SAMPLE_LOOP
);
if self.stream == 0 {
println!("BASS Error Code: {}", BASS_ErrorGetCode());
return Ok(());
}
if BASS_ChannelPlay(self.stream, 0) == 0 {
println!("Failed to play: Error code {}", BASS_ErrorGetCode());
return Ok(());
}
Ok(())
}
fn stop_music(&mut self) {
if self.stream != 0 {
BASS_ChannelStop(self.stream);
BASS_StreamFree(self.stream);
self.stream = 0;
}
}
fn read_file(&self, path: &str) -> Result<String> {
let mut full_path = env::current_dir()?;
full_path.push(path);
fs::read_to_string(&full_path)
.with_context(|| format!("Failed to read file: {}", full_path.display()))
}
fn clear_screen(&self) {
print!("\x1B[2J\x1B[1;1H");
io::stdout().flush().unwrap();
}
fn get_input(&self) -> Result<i32> {
let mut input = String::new();
io::stdin().read_line(&mut input)?;
Ok(input.trim().parse()?)
}
fn wait_for_enter(&self) -> Result<()> {
let mut input = String::new();
io::stdin().read_line(&mut input)?;
Ok(())
}
fn friends(&mut self) -> Result<()> {
self.clear_screen();
println!(
"date: {}, {} health: {} money: {}\n\n{}",
MONTHS[self.turns], self.year, self.health, self.money,
self.read_file(FRIENDS_MENU)?
);
match self.get_input()? {
1 => self.visit_mary()?,
2 => self.visit_joe()?,
3 => self.visit_felix()?,
4 => self.visit_school()?,
5 => self.visit_family()?,
_ => {
println!("INVALID INPUT!");
thread::sleep(Duration::from_secs(5));
}
}
Ok(())
}
fn visit_mary(&mut self) -> Result<()> {
self.clear_screen();
if !self.is_mary {
println!("{}", self.read_file(MARY[4])?);
} else {
let mut rng = rand::thread_rng();
let r = rng.gen_range(0..5);
if r == 4 {
self.is_mary = false;
}
println!("{}", self.read_file(MARY[r])?);
}
self.wait_for_enter()?;
Ok(())
}
fn visit_joe(&mut self) -> Result<()> {
self.clear_screen();
if !self.is_joe {
println!("{}", self.read_file(JOE[4])?);
} else {
let mut rng = rand::thread_rng();
let r = rng.gen_range(0..5);
if r == 4 {
self.is_joe = false;
}
println!("{}", self.read_file(JOE[r])?);
}
self.wait_for_enter()?;
Ok(())
}
fn visit_felix(&mut self) -> Result<()> {
self.clear_screen();
if !self.is_felix {
println!("{}", self.read_file(FELIX[4])?);
} else {
let mut rng = rand::thread_rng();
let r = rng.gen_range(0..5);
if r == 4 {
self.is_felix = false;
}
println!("{}", self.read_file(FELIX[r])?);
}
self.wait_for_enter()?;
Ok(())
}
fn visit_school(&mut self) -> Result<()> {
self.clear_screen();
println!("{}", self.read_file(SCHOOL1)?);
self.wait_for_enter()?;
Ok(())
}
fn visit_family(&mut self) -> Result<()> {
self.clear_screen();
println!("{}", self.read_file(FAMILY1)?);
self.wait_for_enter()?;
Ok(())
}
fn drugs(&mut self) -> Result<()> {
self.clear_screen();
let mut rng = rand::thread_rng();
let index = rng.gen_range(0..5);
println!("{}", self.read_file(&format!("data/drug{}.txt", index + 1))?);
match index {
0 => self.money -= 170,
3 => {
self.health -= 5;
self.money -= 350;
}
4 => {
self.health -= 5;
self.money -= 250;
}
_ => {}
}
self.wait_for_enter()?;
Ok(())
}
fn shoplist(&mut self) -> Result<()> {
self.clear_screen();
let mut rng = rand::thread_rng();
let index = rng.gen_range(0..5);
println!("{}", self.read_file(&format!("data/shop{}.txt", index + 1))?);
match index {
0 => self.money += 200,
1 => self.money += 150,
2 => self.health -= 3,
3 => self.money += 200,
4 => self.health -= 3,
_ => {}
}
self.wait_for_enter()?;
Ok(())
}
fn car(&mut self) -> Result<()> {
self.clear_screen();
let mut rng = rand::thread_rng();
let index = rng.gen_range(0..5);
println!("{}", self.read_file(&format!("data/car{}.txt", index + 1))?);
match index {
0 => self.money += 1000,
1 => self.money += 2000,
2 => self.health -= 5,
3 => self.money += 1500,
4 => self.health -= 5,
_ => {}
}
self.wait_for_enter()?;
Ok(())
}
fn burglary(&mut self) -> Result<()> {
self.clear_screen();
let mut rng = rand::thread_rng();
let index = rng.gen_range(0..5);
println!("{}", self.read_file(&format!("data/burglur{}.txt", index + 1))?);
match index {
0 => self.money += 2500,
1 => self.health -= 7,
2 => self.health -= 7,
3 => self.money += 2500,
4 => self.health -= 7,
_ => {}
}
self.wait_for_enter()?;
Ok(())
}
fn get_money(&mut self) -> Result<()> {
self.clear_screen();
println!(
"date: {}, {} health: {} money: {}\n\n{}",
MONTHS[self.turns], self.year, self.health, self.money,
self.read_file(MONEY_MENU)?
);
match self.get_input()? {
1 => self.shoplist()?,
2 => self.car()?,
3 => self.burglary()?,
_ => {}
}
Ok(())
}
fn rehab(&mut self) -> Result<()> {
if self.money < 2800 {
self.clear_screen();
println!(
"\nYOU DON'T HAVE ENOUGH MONEY FOR REHAB...\nIT COSTS 2800 AND YOU ONLY HAVE {}\n",
self.money
);
self.wait_for_enter()?;
return Ok(());
}
self.money -= 2800;
for i in 0..6 {
self.clear_screen();
println!("{}", self.read_file(&format!("data/rehab{}.txt", i + 1))?);
self.wait_for_enter()?;
}
self.health += 65;
if self.health > 100 {
self.health = 100;
}
Ok(())
}
fn mirror(&mut self) -> Result<()> {
self.clear_screen();
let mirror_index = match self.health {
h if h >= 90 => 0,
h if h >= 80 => 1,
h if h >= 70 => 2,
h if h >= 60 => 3,
h if h >= 50 => 4,
h if h >= 40 => 5,
h if h >= 30 => 6,
h if h >= 20 => 7,
h if h >= 10 => 8,
_ => 9,
};
println!("{}", self.read_file(&format!("data/mirror{}.txt", mirror_index + 1))?);
if self.health < 35 {
self.is_suicide = true;
}
self.wait_for_enter()?;
Ok(())
}
fn suicide(&mut self) -> Result<()> {
self.clear_screen();
println!("{}", self.read_file(OVER)?);
self.wait_for_enter()?;
self.is_over = true;
Ok(())
}
}
fn main() -> Result<()> {
// Initialize BASS
if BASS_Init(-1, 44100, 0, std::ptr::null_mut(), std::ptr::null_mut()) == 0 {
let error_code = BASS_ErrorGetCode();
eprintln!("Error initializing BASS library: {}", error_code);
return Ok(());
}
let mut game = GameState::new();
let mut rng = rand::thread_rng();
// Show intro
println!("{}", game.read_file(DESC1)?);
game.wait_for_enter()?;
game.clear_screen();
// Start background music
let song_index = rng.gen_range(0..12);
if let Err(e) = game.play_music(SONGS_RAND[song_index]) {
eprintln!("Failed to play music: {}", e);
}
println!("{}", game.read_file(OPENING_BANNER)?);
game.wait_for_enter()?;
game.clear_screen();
println!("{}", game.read_file(OPENING_POINT)?);
game.wait_for_enter()?;
game.clear_screen();
// Main game loop
while game.health > 0 && !game.is_over {
game.clear_screen();
if game.turns > 11 {
game.turns = 0;
game.year += 1;
}
if game.money < 0 {
game.money = 0;
}
println!(
"date: {}, {} health: {} money: {}\n\n{}",
MONTHS[game.turns], game.year, game.health, game.money,
game.read_file(MENU)?
);
if game.is_suicide {
println!("PRESS 8 TO TRY AND SUICIDE");
}
match game.get_input()? {
1 => {
game.stop_music();
let song_index = rng.gen_range(0..12);
if let Err(e) = game.play_music(SONGS_RAND[song_index]) {
eprintln!("Failed to change music: {}", e);
}
},
2 => game.friends()?,
3 => game.drugs()?,
4 => game.get_money()?,
5 => game.rehab()?,
6 => game.mirror()?,
7 => game.is_over = true,
8 if game.is_suicide => game.suicide()?,
_ => {}
}
game.turns += 1;
}
// Show ending
game.clear_screen();
println!("{}", game.read_file(GAME_END)?);
game.wait_for_enter()?;
// Cleanup
game.stop_music();
BASS_Free();
Ok(())
}
Cargo.toml:
[package]
name = "rust_trainspotting_II_game"
version = "0.1.0"
edition = "2021"
[dependencies]
rand = "0.8"
bass-sys = "2.1"
anyhow = "1.0"