♻️ redo everything to get rid of the dependency injection.
It wasn't gonna work out the way I hoped.main
parent
574300cdad
commit
2a171a8e27
368
src/main.rs
368
src/main.rs
|
@ -46,52 +46,26 @@ fn read_input () -> Result <String> {
|
|||
Ok (buffer)
|
||||
}
|
||||
|
||||
trait Io {
|
||||
fn print (&mut self, s: &str);
|
||||
fn read_input (&mut self) -> Result <String>;
|
||||
fn sleep (&mut self, milliseconds: u32);
|
||||
|
||||
fn print_many <'a, I: IntoIterator <Item=&'a str>> (&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 <String> {
|
||||
read_input ()
|
||||
}
|
||||
|
||||
fn sleep (&mut self, milliseconds: u32) {
|
||||
use std::{
|
||||
thread::sleep,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
sleep (Duration::from_millis (milliseconds.into ()));
|
||||
}
|
||||
}
|
||||
|
||||
fn print_help <I: Io> (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 <I: Io> (io: &mut I) {
|
||||
io.print ("That ITEM does not exist in this ROOM, or you have not detected it.");
|
||||
fn line_response <S: Into <String>> (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: T) -> Vec <T> {
|
||||
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 <I: Io> (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 <Vec <Response>> {
|
||||
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 <String>,
|
||||
input_idx: usize,
|
||||
}
|
||||
|
||||
impl Io for TestIo {
|
||||
fn print (&mut self, _s: &str) {}
|
||||
|
||||
fn read_input (&mut self) -> Result <String> {
|
||||
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 () {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue