From 2a171a8e27bf28fb702fbe33e42fe73f98a836ab Mon Sep 17 00:00:00 2001 From: _ <> Date: Sun, 24 Oct 2021 22:08:39 +0000 Subject: [PATCH] :recycle: redo everything to get rid of the dependency injection. It wasn't gonna work out the way I hoped. --- src/main.rs | 368 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 211 insertions(+), 157 deletions(-) diff --git a/src/main.rs b/src/main.rs index ef0b439..80b6a3b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -46,52 +46,26 @@ fn read_input () -> Result { Ok (buffer) } -trait Io { - fn print (&mut self, s: &str); - fn read_input (&mut self) -> Result ; - fn sleep (&mut self, milliseconds: u32); - - fn print_many <'a, I: IntoIterator > (&mut self, lines: I) { - for line in lines { - self.print (line); - } - } -} - -#[derive (Default)] -struct Stdio {} - -impl Io for Stdio { - fn print (&mut self, s: &str) { - print (s) - } - - fn read_input (&mut self) -> Result { - read_input () - } - - fn sleep (&mut self, milliseconds: u32) { - use std::{ - thread::sleep, - time::Duration, - }; - - sleep (Duration::from_millis (milliseconds.into ())); - } -} - -fn print_help (io: &mut I) { - io.print_many ([ +fn print_help () -> Response { + Response::PrintMany (vec! [ "All commands are ASCII and case-insensitive.", "Commands should start with a verb like LOOK.", "e.g. `look table`", "Single-word verbs are better, e.g. prefer `hint` over `give me a hint`", "When in doubt, try generic verbs like `look` or `use` over specific verbs like `actuate`, `type`, or `consolidate`.", - ]); + ]) } -fn print_undetected_item (io: &mut I) { - io.print ("That ITEM does not exist in this ROOM, or you have not detected it."); +fn line_response > (line: S) -> Response { + Response::Print (line.into ()) +} + +fn undetected_item () -> Response { + line_response ("That ITEM does not exist in this ROOM, or you have not detected it.") +} + +fn just (t: T) -> Vec { + vec! [t] } #[derive (Debug, PartialEq)] @@ -211,145 +185,220 @@ struct StateRoom1 { detected_note: bool, } +enum Response { + Print (String), + PrintMany (Vec <&'static str>), + Sleep (u32), + Quit, + JokeEnding, +} + #[derive (Clone, Default)] struct State { - quitting: bool, room_1: StateRoom1, } -fn room_1 (io: &mut I, state: &mut State) -> Result <()> { - let input = io.read_input ()?; - let action = parse_input (&input); +impl State { + fn room_1 (&mut self, input: String) -> Result > { + use Response::*; + + let action = parse_input (&input); + + Ok (match action { + PlayerAction::Quit => { + vec! [ + line_response ("Bye."), + Quit, + ] + } + PlayerAction::Help => { + just (print_help ()) + }, + PlayerAction::Hint => { + just (line_response ("Hint for this room: Try using the `help` command.")) + }, + PlayerAction::Nonsense => { + vec! [ + line_response ("I couldn't understand that. Try `help` or `hint`."), + line_response ("`hint` may contain spoilers. `help` will not."), + ] + }, + PlayerAction::Wait => { + just (line_response ("You wait around a bit. You can hear humming from the electrical lights, and the distant rumble of the building's HVAC system. The room smells faintly of fresh paint. Nothing has changed.")) + }, + PlayerAction::LookAround => { + just (line_response ("You are in a small room. In one corner is a TABLE. Obvious exits are a locked DOOR, and an EMERGENCY EXIT.")) + } + PlayerAction::Look (item_name) => { + match item_name { + ItemName::Door => { + self.room_1.detected_keypad = true; + just (line_response ("You examine the DOOR. It is firmly locked, and you don't have any lock-picking tools. On the DOOR is an electronic KEYPAD.")) + }, + ItemName::EmergencyExit => { + just (line_response ("The EMERGENCY EXIT reads, \"Emergency exit. Push bar to open. Alarm will sound. Door will unlock in 10 seconds.\". The EMERGENCY EXIT is period-accurate for an American Wal-Mart c. 2020 C.E.")) + }, + ItemName::Keypad => { + if ! self.room_1.detected_keypad { + return Ok (just (undetected_item ())); + } + + just (line_response ("The DOOR is locked by an electronic KEYPAD. A soft amber power light indicates that the KEYPAD is likely functional. The KEYPAD buttons are the digits 0-9, Enter, and Clear. Experience tells you that the key code is likely 4 or 5 digits long.")) + }, + ItemName::Note => { + if ! self.room_1.detected_note { + return Ok (vec! [ + undetected_item (), + ]); + } + + just (Response::PrintMany (vec! [ + "You pick up the NOTE and read it.", + "", + "Welcome to SEROTONIN DEPOSITORY.", + "As you play, keep in mind:", + "- LOOKing at ITEMS is not always safe", + "- TAKEing an item may be bad long-term", + "- WAITing counts as an action", + "- LOOKing AROUND is always safe", + "- Other NOTEs may contain non-truths", + "The code for this first KEYPAD is 1234.", + "", + " -- Phayle Sayf", + "", + "You notice that the NOTE is _not_ period-accurate.", + ])) + }, + ItemName::Table => { + self.room_1.detected_note = true; + vec! [ + Response::Print ("You look at the TABLE. Your instincts tell you that it is period-accurate. Upon the TABLE sits a NOTE.".into ()), + ] + }, + _ => { + vec! [ + undetected_item (), + ] + }, + } + }, + PlayerAction::Use (item_name) => { + match item_name { + ItemName::Door => { + let mut output = vec! [ + line_response ("You can't USE the DOOR, it is locked."), + ]; + + if ! self.room_1.detected_keypad { + self.room_1.detected_keypad = true; + output.push (Response::Print ("You notice an electronic KEYPAD on the DOOR.".into ())); + } + + output + }, + ItemName::EmergencyExit => { + vec! [ + Response::Print ("You push on the emergency exit. An alarm starts sounding. Your ADVENTURE GAME ENTHUSIAST friend is going to be very mad at you.".into ()), + Response::Sleep (5000), + Response::Print ("The alarm is still sounding. You are getting embarrassed, but you have committed to this path of action.".into ()), + Response::Sleep (5000), + Response::Print ("The emergency exit unlocks, and you walk out of the game. Bye.".into ()), + Response::JokeEnding, + Response::Quit, + ] + }, + _ => { + vec! [ + undetected_item (), + ] + }, + } + }, + }) + } +} + +fn game () -> Result <()> { + for line in [ + "Welcome to SEROTONIN DEPOSITORY, the only adventure game ever made.", + "", + "You have been consensually kidnapped by a diabolical ADVENTURE GAME ENTHUSIAST and encouraged to solve PUZZLES for their sick PLEASURE. The only winning move is to solve all the PUZZLES.", + "", + "Press ENTER if you dare to begin.", + ] { + print (line); + } - match action { - PlayerAction::Quit => { - io.print ("Bye."); - state.quitting = true; - } - PlayerAction::Help => { - print_help (io); - }, - PlayerAction::Hint => { - io.print ("Hint for this room: Try using the `help` command."); - }, - PlayerAction::Nonsense => { - io.print ("I couldn't understand that. Try `help` or `hint`."); - io.print ("`hint` may contain spoilers. `help` will not."); - }, - PlayerAction::Wait => { - io.print ("You wait around a bit. You can hear humming from the electrical lights, and the distant rumble of the building's HVAC system. The room smells faintly of fresh paint. Nothing has changed."); - }, - PlayerAction::LookAround => { - io.print ("You are in a small room. In one corner is a TABLE. Obvious exits are a locked DOOR, and an EMERGENCY EXIT."); - } - PlayerAction::Look (item_name) => { - match item_name { - ItemName::Door => { - io.print ("You examine the DOOR. It is firmly locked, and you don't have any lock-picking tools. On the DOOR is an electronic KEYPAD."); - state.room_1.detected_keypad = true; - }, - ItemName::EmergencyExit => { - io.print ("The EMERGENCY EXIT reads, \"Emergency exit. Push bar to open. Alarm will sound. Door will unlock in 10 seconds.\". The EMERGENCY EXIT is period-accurate for an American Wal-Mart c. 2020 C.E."); - }, - ItemName::Keypad => { - if ! state.room_1.detected_keypad { - print_undetected_item (io); - return Ok (()); + let input = read_input ()?; + + if ! input.is_empty () { + print ("That was more than just ENTER but OKAY, overachiever."); + } + + print (""); + + let mut state = State::default (); + + print ("You are in a small room. In one corner is a TABLE."); + + 'main_loop: loop { + let input = read_input ()?; + let responses = state.room_1 (input)?; + + for response in responses.into_iter () { + match response { + Response::Print (line) => print (&line), + Response::PrintMany (lines) => { + for line in lines { + print (line); } - - io.print ("The DOOR is locked by an electronic KEYPAD. A soft amber power light indicates that the KEYPAD is likely functional. The KEYPAD buttons are the digits 0-9, Enter, and Clear. Experience tells you that the key code is likely 4 or 5 digits long."); - }, - ItemName::Note => { - if ! state.room_1.detected_note { - print_undetected_item (io); - return Ok (()); - } - - io.print_many ([ - "You pick up the NOTE and read it.", - "", - "Welcome to SEROTONIN DEPOSITORY.", - "As you play, keep in mind:", - "- LOOKing at ITEMS is not always safe", - "- TAKEing an item may be bad long-term", - "- WAITing counts as an action", - "- LOOKing AROUND is always safe", - "- Other NOTEs may contain non-truths", - "The code for this first KEYPAD is 1234.", - "", - " -- Phayle Sayf", - "", - "You notice that the NOTE is _not_ period-accurate.", - ]) - }, - ItemName::Table => { - io.print ("You look at the TABLE. Your instincts tell you that it is period-accurate. Upon the TABLE sits a NOTE."); - state.room_1.detected_note = true; - }, - _ => { - print_undetected_item (io); }, + Response::Sleep (x) => std::thread::sleep (std::time::Duration::from_millis (x.into ())), + Response::Quit => break 'main_loop, + _ => (), } - }, - PlayerAction::Use (item_name) => { - match item_name { - ItemName::Door => { - io.print ("You can't USE the DOOR, it is locked."); - if ! state.room_1.detected_keypad { - io.print ("You notice an electronic KEYPAD on the DOOR."); - state.room_1.detected_keypad = true; - } - }, - ItemName ::EmergencyExit => { - io.print ("You push on the emergency exit. An alarm starts sounding. Your ADVENTURE GAME ENTHUSIAST friend is going to be very mad at you."); - io.sleep (5000); - io.print ("The alarm is still sounding. You are getting embarrassed, but you have committed to this path of action."); - io.sleep (5000); - io.print ("The emergency exit unlocks, and you walk out of the game. Bye."); - state.quitting = true; - }, - _ => { - print_undetected_item (io); - }, - } - }, + } } Ok (()) } fn main () -> Result <()> { - let mut io = Stdio::default (); - - io.print_many ([ - "Welcome to SEROTONIN DEPOSITORY, the only adventure game ever made.", - "", - "You have been consensually kidnapped by a diabolical ADVENTURE GAME ENTHUSIAST and encouraged to solve PUZZLES for their sick PLEASURE. The only winning move is to solve all the PUZZLES.", - "", - "Press ENTER if you dare to begin.", - ]); - - let input = io.read_input ()?; - - if ! input.is_empty () { - io.print ("That was more than just ENTER but OKAY, overachiever."); - } - - io.print (""); - - let mut state = State::default (); - - io.print ("You are in a small room. In one corner is a TABLE."); - - while ! state.quitting { - room_1 (&mut io, &mut state)?; - } + game ()?; Ok (()) } #[cfg (test)] mod test { + use super::{ + Io, + GameError, + Result, + State, + }; + + struct TestIo { + inputs: Vec , + input_idx: usize, + } + + impl Io for TestIo { + fn print (&mut self, _s: &str) {} + + fn read_input (&mut self) -> Result { + if self.input_idx >= self.inputs.len () { + return Err (GameError::IoError); + } + + let s = self.inputs [self.input_idx].clone (); + self.input_idx += 1; + Ok (s) + } + + fn sleep (&mut self, _milliseconds: u32) {} + } + #[test] fn parse_input () { use super::{ @@ -371,4 +420,9 @@ mod test { assert_eq! (actual, expected); } } + + #[test] + fn joke_ending () { + + } }