kajam_10/game/src/lib.rs

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));
}
}