279 lines
6.3 KiB
Rust
279 lines
6.3 KiB
Rust
mod player_actions;
|
|
mod responses;
|
|
mod room_1;
|
|
mod state;
|
|
|
|
use player_actions::*;
|
|
use responses::*;
|
|
|
|
pub use responses::Response;
|
|
pub use state::State;
|
|
|
|
fn _item_name_display (x: ItemName) -> &'static str {
|
|
match x {
|
|
ItemName::Nonsense => "NONSENSE",
|
|
|
|
ItemName::Door => "DOOR",
|
|
ItemName::EmergencyExit => "EMERGENCY EXIT",
|
|
ItemName::Keypad => "KEYPAD",
|
|
ItemName::Note => "NOTE",
|
|
ItemName::Table => "TABLE",
|
|
}
|
|
}
|
|
|
|
fn parse_input (s: &str) -> PlayerAction {
|
|
use PlayerAction::*;
|
|
use PlayerActionRoomSpecific::*;
|
|
|
|
let s = s.trim ().to_lowercase ();
|
|
|
|
let look = |rest| RoomSpecific (Look (parse_item_name (rest)));
|
|
let activate = |rest| RoomSpecific (Use (parse_item_name (rest)));
|
|
|
|
if s == "quit" || s == "quit game" {
|
|
Quit
|
|
}
|
|
else if s == "help" || s == "help me" {
|
|
Help
|
|
}
|
|
else if s == "look" || s == "look around" {
|
|
RoomSpecific (LookAround)
|
|
}
|
|
else if let Some (rest) = s.strip_prefix ("look at the ") {
|
|
look (rest)
|
|
}
|
|
else if let Some (rest) = s.strip_prefix ("look at ") {
|
|
look (rest)
|
|
}
|
|
else if let Some (rest) = s.strip_prefix ("look ") {
|
|
look (rest)
|
|
}
|
|
else if let Some (rest) = s.strip_prefix ("examine ") {
|
|
look (rest)
|
|
}
|
|
else if let Some (rest) = s.strip_prefix ("use the ") {
|
|
activate (rest)
|
|
}
|
|
else if let Some (rest) = s.strip_prefix ("use ") {
|
|
activate (rest)
|
|
}
|
|
else if
|
|
s == "do nothing" ||
|
|
s == "wait"
|
|
{
|
|
RoomSpecific (Wait)
|
|
}
|
|
else if s == "hint" {
|
|
RoomSpecific (Hint)
|
|
}
|
|
else {
|
|
Nonsense
|
|
}
|
|
}
|
|
|
|
fn parse_item_name (s: &str) -> ItemName {
|
|
let s = s.trim ();
|
|
|
|
if s == "door" {
|
|
ItemName::Door
|
|
}
|
|
else if s == "emergency exit" {
|
|
ItemName::EmergencyExit
|
|
}
|
|
else if s == "keypad" {
|
|
ItemName::Keypad
|
|
}
|
|
else if s == "note" {
|
|
ItemName::Note
|
|
}
|
|
else if s == "table" {
|
|
ItemName::Table
|
|
}
|
|
else {
|
|
ItemName::Nonsense
|
|
}
|
|
}
|
|
|
|
#[macro_export]
|
|
macro_rules! require_detection {
|
|
($condition:expr $(,)?) => {
|
|
if ! $condition {
|
|
return vec! [
|
|
Response::FailedDetectionCheck,
|
|
undetected_item (),
|
|
];
|
|
}
|
|
};
|
|
}
|
|
|
|
impl State {
|
|
pub fn cheat (&mut self) {
|
|
self.current_room = state::RoomName::SortingRoom;
|
|
}
|
|
|
|
/// Send a line of player input (e.g. "look table") into the game and return
|
|
/// a Vec of Responses. The runtime should process these responses in order.
|
|
|
|
pub fn step (&mut self, input: &str) -> Vec <Response> {
|
|
use state::{
|
|
IntroState,
|
|
RoomName::*,
|
|
};
|
|
|
|
match self.intro_state {
|
|
IntroState::Stage1 => {
|
|
self.intro_state = IntroState::Stage2;
|
|
|
|
return just (Response::PrintMany (vec! [
|
|
"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.",
|
|
]));
|
|
},
|
|
IntroState::Stage2 => {
|
|
self.intro_state = IntroState::Stage3;
|
|
|
|
let mut output = vec! [];
|
|
|
|
if ! input.is_empty () {
|
|
output.push (line_response ("That was more than just ENTER but OKAY, overachiever."));
|
|
}
|
|
|
|
output.push (line_response (""));
|
|
output.push (line_response ("You are in a small room. In one corner is a TABLE."));
|
|
|
|
return output;
|
|
},
|
|
_ => (),
|
|
}
|
|
|
|
let action = parse_input (input);
|
|
|
|
match action {
|
|
PlayerAction::Quit => vec! [
|
|
line_response ("Bye."),
|
|
Response::Quit,
|
|
],
|
|
PlayerAction::Help => just (print_help ()),
|
|
PlayerAction::Nonsense => vec! [
|
|
line_response ("I couldn't understand that. Try `help` or `hint`."),
|
|
line_response ("`hint` may contain spoilers. `help` will not."),
|
|
Response::Nonsense,
|
|
],
|
|
PlayerAction::RoomSpecific (x) => match self.current_room {
|
|
Room1 => self.room_1 (x),
|
|
SortingRoom => self.sorting_room (x),
|
|
_ => just (line_response ("ERR: Invalid current room")),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn sorting_room (&mut self, action: PlayerActionRoomSpecific) -> Vec <Response>
|
|
{
|
|
use PlayerActionRoomSpecific::*;
|
|
|
|
match action {
|
|
Hint => {
|
|
just (line_response ("The books are out of order"))
|
|
},
|
|
Wait => {
|
|
just (line_response ("You wait. Nothing is moving. The room smells like 5 books."))
|
|
},
|
|
LookAround => {
|
|
vec! [
|
|
line_response ("You see a small bookshelf with 5 books on it, and a MACHINE. It looks like the MACHINE is able to grab books and swap them around with a robotic arm. The bookshelf is behind a pane of reinforced glass, but a control panel near you has buttons labelled LEFT, RIGHT, and SWAP."),
|
|
]
|
|
},
|
|
Look (item_name) => {
|
|
just (undetected_item ())
|
|
},
|
|
Use (item_name) => {
|
|
just (undetected_item ())
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg (test)]
|
|
mod test {
|
|
use super::{
|
|
Response,
|
|
State,
|
|
};
|
|
|
|
fn skip_intro () -> State {
|
|
let mut x = State::default ();
|
|
x.step ("");
|
|
x.step ("");
|
|
|
|
x
|
|
}
|
|
|
|
#[test]
|
|
fn parse_input () {
|
|
use super::{
|
|
ItemName,
|
|
PlayerAction::*,
|
|
PlayerActionRoomSpecific::*,
|
|
};
|
|
|
|
for (input, expected) in [
|
|
("", Nonsense),
|
|
("help", Help),
|
|
("look at the table", RoomSpecific (Look (ItemName::Table))),
|
|
("look at table", RoomSpecific (Look (ItemName::Table))),
|
|
("look table", RoomSpecific (Look (ItemName::Table))),
|
|
("look note ", RoomSpecific (Look (ItemName::Note))),
|
|
("LOOK TABLE", RoomSpecific (Look (ItemName::Table))),
|
|
("look ", RoomSpecific (LookAround)),
|
|
("wait", RoomSpecific (Wait)),
|
|
("hint", RoomSpecific (Hint)),
|
|
].into_iter () {
|
|
let actual = super::parse_input (input);
|
|
assert_eq! (actual, expected);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn joke_ending () {
|
|
let mut state = skip_intro ();
|
|
|
|
let responses = state.step ("use emergency exit");
|
|
|
|
assert! (responses.contains (&Response::Quit));
|
|
assert! (responses.contains (&Response::JokeEnding));
|
|
}
|
|
|
|
#[test]
|
|
fn detection_check () {
|
|
let mut state = skip_intro ();
|
|
|
|
let responses = state.step ("look keypad");
|
|
assert! (responses.contains (&Response::FailedDetectionCheck));
|
|
|
|
state.step ("look door");
|
|
|
|
let responses = state.step ("look keypad");
|
|
assert! (! responses.contains (&Response::FailedDetectionCheck));
|
|
}
|
|
|
|
#[test]
|
|
fn happy_path () {
|
|
let mut state = skip_intro ();
|
|
|
|
for line in [
|
|
"look table",
|
|
"look note",
|
|
"look door",
|
|
] {
|
|
let responses = state.step (line);
|
|
assert! (! responses.contains (&Response::PlayerVictory));
|
|
}
|
|
|
|
let responses = state.step ("use keypad");
|
|
assert! (responses.contains (&Response::PlayerVictory));
|
|
}
|
|
}
|