diff --git a/src/main.rs b/src/main.rs index 24332be..ef0b439 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,3 @@ -use std::io::{ - Write, - stdin, - stdout, -}; - /// As a dare to myself, I won't use any error-handling crates. #[derive (Debug)] @@ -34,27 +28,16 @@ fn print (mut s: &str) { println! ("{}", s); } -fn print_help () { - print ("All commands are ASCII and case-insensitive."); - print ("Commands should start with a verb like LOOK."); - print ("e.g. `look table`"); - print ("Single-word verbs are better, e.g. prefer `hint` over `give me a hint`"); - print ("When in doubt, try generic verbs like `look` or `use` over specific verbs like `actuate`, `type`, or `consolidate`."); -} - -fn print_undetected_item () { - print ("That ITEM does not exist in this ROOM, or you have not detected it."); -} - fn read_input () -> Result { { - let mut stdout = stdout (); + use std::io::Write; + let mut stdout = std::io::stdout (); stdout.write_all (b"> ")?; stdout.flush ()?; } let mut buffer = String::new (); - stdin ().read_line (&mut buffer)?; + std::io::stdin ().read_line (&mut buffer)?; // I don't know why I need the type annotation here, but I do. let x: &[_] = &['\r', '\n']; @@ -63,6 +46,54 @@ 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 ([ + "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."); +} + #[derive (Debug, PartialEq)] enum PlayerAction { Quit, @@ -72,6 +103,7 @@ enum PlayerAction { Wait, Look (ItemName), LookAround, + Use (ItemName), } #[derive (Clone, Copy, Debug, PartialEq)] @@ -85,15 +117,15 @@ enum ItemName { Table, } -fn item_name_display (x: ItemName) -> &'static str { +fn _item_name_display (x: ItemName) -> &'static str { match x { - Nonsense => "NONSENSE", + ItemName::Nonsense => "NONSENSE", - Door => "DOOR", - EmergencyExit => "EMERGENCY EXIT", - Keypad => "KEYPAD", - Note => "NOTE", - Table => "TABLE", + ItemName::Door => "DOOR", + ItemName::EmergencyExit => "EMERGENCY EXIT", + ItemName::Keypad => "KEYPAD", + ItemName::Note => "NOTE", + ItemName::Table => "TABLE", } } @@ -124,6 +156,14 @@ fn parse_input (s: &str) -> PlayerAction { return PlayerAction::Look (parse_item_name (rest)); } + if let Some (rest) = s.strip_prefix ("use the ") { + return PlayerAction::Use (parse_item_name (rest)); + } + + if let Some (rest) = s.strip_prefix ("use ") { + return PlayerAction::Use (parse_item_name (rest)); + } + if s == "do nothing" { return PlayerAction::Wait; } @@ -165,92 +205,112 @@ fn parse_item_name (s: &str) -> ItemName { ItemName::Nonsense } -#[derive (Default)] +#[derive (Clone, Default)] struct StateRoom1 { detected_keypad: bool, detected_note: bool, } -#[derive (Default)] +#[derive (Clone, Default)] struct State { quitting: bool, room_1: StateRoom1, } -fn room_1 (state: &mut State) -> Result <()> { - let input = read_input ()?; +fn room_1 (io: &mut I, state: &mut State) -> Result <()> { + let input = io.read_input ()?; let action = parse_input (&input); match action { PlayerAction::Quit => { - print ("Bye."); + io.print ("Bye."); state.quitting = true; } PlayerAction::Help => { - print_help (); + print_help (io); }, PlayerAction::Hint => { - print ("Hint for this room: Try using the `help` command."); + io.print ("Hint for this room: Try using the `help` command."); }, PlayerAction::Nonsense => { - print ("I couldn't understand that. Try `help` or `hint`."); - print ("`hint` may contain spoilers. `help` will not."); + io.print ("I couldn't understand that. Try `help` or `hint`."); + io.print ("`hint` may contain spoilers. `help` will not."); }, PlayerAction::Wait => { - 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."); + 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 => { - print ("You are in a small room. In one corner is a TABLE. Obvious exits are a locked DOOR, and an EMERGENCY EXIT."); + 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::Nonsense => { - print_undetected_item (); - }, ItemName::Door => { - 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."); + 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 => { - print ("The EMERGENCY EXIT reads, \"Emergency exit. Push bar to open. Alarm will sound. Door will unlock in 15 seconds.\". The EMERGENCY EXIT is period-accurate for an American Wal-Mart c. 2020 C.E."); + 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 ("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."); - } - else { - print_undetected_item (); + if ! state.room_1.detected_keypad { + print_undetected_item (io); + return Ok (()); } + + 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 { - for x in [ - "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.", - ].into_iter () { - print (x); - } - } - else { - print_undetected_item (); + 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 => { - print ("You look at the TABLE. Your instincts tell you that it is period-accurate. Upon the TABLE sits a NOTE."); + 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); + }, + } + }, + 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); + }, } }, } @@ -259,26 +319,30 @@ fn room_1 (state: &mut State) -> Result <()> { } fn main () -> Result <()> { - print ("Welcome to SEROTONIN DEPOSITORY, the only adventure game ever made."); - print (""); - print ("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."); - print (""); - print ("Press ENTER if you dare to begin."); + let mut io = Stdio::default (); - let input = read_input ()?; + 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 () { - print ("That was more than just ENTER but OKAY, overachiever."); + io.print ("That was more than just ENTER but OKAY, overachiever."); } - print (""); + io.print (""); let mut state = State::default (); - print ("You are in a small room. In one corner is a TABLE."); + io.print ("You are in a small room. In one corner is a TABLE."); while ! state.quitting { - room_1 (&mut state)?; + room_1 (&mut io, &mut state)?; } Ok (())