Compare commits

..

No commits in common. "741fc8171a95817621af9c40177570cda9d04626" and "28f11e74c598b8a0df3c3868d1f6e25f7c31b9c3" have entirely different histories.

6 changed files with 225 additions and 282 deletions

View File

@ -1,13 +1,52 @@
mod player_actions; fn print_help () -> Response {
mod responses; Response::PrintMany (vec! [
mod room_1; "All commands are ASCII and case-insensitive.",
mod state; "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`.",
])
}
use player_actions::*; fn line_response <S: Into <String>> (line: S) -> Response {
use responses::*; Response::Print (line.into ())
}
pub use responses::Response; fn undetected_item () -> Response {
pub use state::State; line_response ("That ITEM does not exist in this ROOM, or you have not noticed it.")
}
fn just <T> (t: T) -> Vec <T> {
vec! [t]
}
#[derive (Debug, PartialEq)]
enum PlayerAction {
Quit,
Help,
Nonsense,
RoomSpecific (PlayerActionRoomSpecific),
}
#[derive (Debug, PartialEq)]
enum PlayerActionRoomSpecific {
Hint,
Wait,
Look (ItemName),
LookAround,
Use (ItemName),
}
#[derive (Clone, Copy, Debug, PartialEq)]
enum ItemName {
Nonsense,
Door,
EmergencyExit,
Keypad,
Note,
Table,
}
fn _item_name_display (x: ItemName) -> &'static str { fn _item_name_display (x: ItemName) -> &'static str {
match x { match x {
@ -94,7 +133,6 @@ fn parse_item_name (s: &str) -> ItemName {
} }
} }
#[macro_export]
macro_rules! require_detection { macro_rules! require_detection {
($condition:expr $(,)?) => { ($condition:expr $(,)?) => {
if ! $condition { if ! $condition {
@ -106,20 +144,79 @@ macro_rules! require_detection {
}; };
} }
impl State { #[derive (Clone)]
pub fn cheat (&mut self) { enum RoomName {
self.current_room = state::RoomName::SortingRoom; /// Starting room with the dead-simple note and keypad puzzle.
Room1,
/// Duplicate of starting room so I can change things around a little.
_Room2,
}
impl Default for RoomName {
fn default () -> Self {
Self::Room1
} }
}
#[derive (Clone, Default)]
struct StateRoom1 {
detected_keypad: bool,
detected_note: bool,
read_note: bool,
}
/// Commands that the game will ask the runtime to execute.
#[derive (PartialEq)]
pub enum Response {
/// Print a line of text
Print (String),
/// Print many lines of unformatted text
PrintMany (Vec <&'static str>),
/// Sleep for x milliseconds
Sleep (u32),
/// Quit the game
Quit,
// These are just useful markers for the automated tests
JokeEnding,
FailedDetectionCheck,
PlayerVictory,
// These are hints for spam detection on the Telnet frontend
Nonsense,
}
#[derive (Clone, Copy)]
enum IntroState {
Stage1,
Stage2,
Stage3,
}
impl Default for IntroState {
fn default () -> Self {
Self::Stage1
}
}
/// The entire game state
#[derive (Clone, Default)]
pub struct State {
intro_state: IntroState,
current_room: RoomName,
room_1: StateRoom1,
}
impl State {
/// Send a line of player input (e.g. "look table") into the game and return /// 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. /// a Vec of Responses. The runtime should process these responses in order.
pub fn step (&mut self, input: &str) -> Vec <Response> { pub fn step (&mut self, input: &str) -> Vec <Response> {
use state::{
IntroState,
RoomName::*,
};
match self.intro_state { match self.intro_state {
IntroState::Stage1 => { IntroState::Stage1 => {
self.intro_state = IntroState::Stage2; self.intro_state = IntroState::Stage2;
@ -162,35 +259,129 @@ impl State {
line_response ("`hint` may contain spoilers. `help` will not."), line_response ("`hint` may contain spoilers. `help` will not."),
Response::Nonsense, Response::Nonsense,
], ],
PlayerAction::RoomSpecific (x) => match self.current_room { PlayerAction::RoomSpecific (x) => self.room_1 (x),
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> fn room_1 (&mut self, action: PlayerActionRoomSpecific) -> Vec <Response> {
{
use PlayerActionRoomSpecific::*; use PlayerActionRoomSpecific::*;
match action { match action {
Hint => { Hint => {
just (line_response ("The books are out of order")) just (line_response ("Hint for this room: Try using the `help` command."))
}, },
Wait => { Wait => {
just (line_response ("You wait. Nothing is moving. The room smells like 5 books.")) 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."))
}, },
LookAround => { LookAround => {
vec! [ let mut output = 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."), line_response ("You are in a small room. In one corner is a TABLE. Obvious exits are a DOOR, and an EMERGENCY EXIT."),
] ];
},
if self.room_1.detected_note {
output.push (line_response ("You have noticed a NOTE on the TABLE."));
}
if self.room_1.detected_keypad {
output.push (line_response ("You have noticed the DOOR is locked by an electronic KEYPAD."));
}
output
}
Look (item_name) => { Look (item_name) => {
just (undetected_item ()) 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 => {
require_detection! (self.room_1.detected_keypad);
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 => {
require_detection! (self.room_1.detected_note);
self.room_1.read_note = true;
just (Response::PrintMany (vec! [
"You read the NOTE.",
"",
"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;
just (line_response ("You look at the TABLE. Your instincts tell you that it is period-accurate. Upon the TABLE sits a NOTE."))
},
_ => {
just (undetected_item ())
},
}
}, },
Use (item_name) => { Use (item_name) => {
just (undetected_item ()) 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 (line_response ("You notice an electronic KEYPAD on the DOOR."));
}
output
},
ItemName::EmergencyExit => {
vec! [
line_response ("You push on the emergency exit. An alarm starts sounding. Your ADVENTURE GAME ENTHUSIAST friend is going to be very mad at you."),
Response::Sleep (5000),
line_response ("The alarm is still sounding. You are getting embarrassed, but you have committed to this path of action."),
Response::Sleep (5000),
line_response ("The emergency exit unlocks, and you walk out of the game. Bye."),
Response::JokeEnding,
Response::Quit,
]
},
ItemName::Keypad => {
require_detection! (self.room_1.detected_keypad);
if ! self.room_1.read_note {
return just (line_response ("You can't USE the KEYPAD, you don't know the code for it. You would normally try guessing, but it would take the programmer all day to implement that level of interaction."));
}
vec! [
line_response ("You USE the code on the KEYPAD. The door opens, and the game immediately crashes."),
Response::PlayerVictory,
Response::Quit,
]
},
ItemName::Note => {
require_detection! (self.room_1.detected_note);
just (line_response ("You can't think of any way to USE the NOTE that would be better than LOOKing at it to read it."))
},
ItemName::Table => {
just (line_response ("You can't think of any way to USE the TABLE that would be better than LOOKing at it."))
},
_ => {
just (undetected_item ())
},
}
}, },
} }
} }

View File

@ -1,27 +0,0 @@
#[derive (Debug, PartialEq)]
pub enum PlayerAction {
Quit,
Help,
Nonsense,
RoomSpecific (PlayerActionRoomSpecific),
}
#[derive (Debug, PartialEq)]
pub enum PlayerActionRoomSpecific {
Hint,
Wait,
Look (ItemName),
LookAround,
Use (ItemName),
}
#[derive (Clone, Copy, Debug, PartialEq)]
pub enum ItemName {
Nonsense,
Door,
EmergencyExit,
Keypad,
Note,
Table,
}

View File

@ -1,46 +0,0 @@
/// Commands that the game will ask the runtime to execute.
#[derive (PartialEq)]
pub enum Response {
/// Print a line of text
Print (String),
/// Print many lines of unformatted text
PrintMany (Vec <&'static str>),
/// Sleep for x milliseconds
Sleep (u32),
/// Quit the game
Quit,
// These are just useful markers for the automated tests
JokeEnding,
FailedDetectionCheck,
PlayerVictory,
// These are hints for spam detection on the Telnet frontend
Nonsense,
}
pub 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`.",
])
}
pub fn line_response <S: Into <String>> (line: S) -> Response {
Response::Print (line.into ())
}
pub fn undetected_item () -> Response {
line_response ("That ITEM does not exist in this ROOM, or you have not noticed it.")
}
pub fn just <T> (t: T) -> Vec <T> {
vec! [t]
}

View File

@ -1,121 +0,0 @@
use crate::{
player_actions::*,
responses::*,
require_detection,
state::State,
};
impl State {
pub fn room_1 (&mut self, action: PlayerActionRoomSpecific) -> Vec <Response> {
use PlayerActionRoomSpecific::*;
match action {
Hint => {
just (line_response ("Hint for this room: Try using the `help` command."))
},
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."))
},
LookAround => {
let mut output = vec! [
line_response ("You are in a small room. In one corner is a TABLE. Obvious exits are a DOOR, and an EMERGENCY EXIT."),
];
if self.room_1.detected_note {
output.push (line_response ("You have noticed a NOTE on the TABLE."));
}
if self.room_1.detected_keypad {
output.push (line_response ("You have noticed the DOOR is locked by an electronic KEYPAD."));
}
output
}
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 => {
require_detection! (self.room_1.detected_keypad);
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 => {
require_detection! (self.room_1.detected_note);
self.room_1.read_note = true;
just (Response::PrintMany (vec! [
"You read the NOTE.",
"",
"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;
just (line_response ("You look at the TABLE. Your instincts tell you that it is period-accurate. Upon the TABLE sits a NOTE."))
},
_ => just (undetected_item ()),
},
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 (line_response ("You notice an electronic KEYPAD on the DOOR."));
}
output
},
ItemName::EmergencyExit => vec! [
line_response ("You push on the emergency exit. An alarm starts sounding. Your ADVENTURE GAME ENTHUSIAST friend is going to be very mad at you."),
Response::Sleep (5000),
line_response ("The alarm is still sounding. You are getting embarrassed, but you have committed to this path of action."),
Response::Sleep (5000),
line_response ("The emergency exit unlocks, and you walk out of the game. Bye."),
Response::JokeEnding,
Response::Quit,
],
ItemName::Keypad => {
require_detection! (self.room_1.detected_keypad);
if ! self.room_1.read_note {
return just (line_response ("You can't USE the KEYPAD, you don't know the code for it. You would normally try guessing, but it would take the programmer all day to implement that level of interaction."));
}
vec! [
line_response ("You USE the code on the KEYPAD. The door opens, and the game immediately crashes."),
Response::PlayerVictory,
Response::Quit,
]
},
ItemName::Note => {
require_detection! (self.room_1.detected_note);
just (line_response ("You can't think of any way to USE the NOTE that would be better than LOOKing at it to read it."))
},
ItemName::Table => just (line_response (
"You can't think of any way to USE the TABLE that would be better than LOOKing at it."
)),
_ => just (undetected_item ()),
},
}
}
}

View File

@ -1,49 +0,0 @@
/// The entire game state
#[derive (Clone, Default)]
pub struct State {
pub intro_state: IntroState,
pub current_room: RoomName,
pub room_1: StateRoom1,
pub room_sorting: StateSortingRoom,
}
#[derive (Clone, Copy)]
pub enum IntroState {
Stage1,
Stage2,
Stage3,
}
impl Default for IntroState {
fn default () -> Self {
Self::Stage1
}
}
#[derive (Clone)]
pub enum RoomName {
/// Starting room with the dead-simple note and keypad puzzle.
Room1,
/// Duplicate of starting room so I can change things around a little.
_Room2,
SortingRoom,
}
impl Default for RoomName {
fn default () -> Self {
Self::Room1
}
}
#[derive (Clone, Default)]
pub struct StateRoom1 {
pub detected_keypad: bool,
pub detected_note: bool,
pub read_note: bool,
}
#[derive (Clone, Default)]
pub struct StateSortingRoom {
}

View File

@ -51,12 +51,8 @@ fn read_input () -> Result <String> {
Ok (buffer) Ok (buffer)
} }
fn game (args: &[String]) -> Result <()> { fn game () -> Result <()> {
let mut state = State::default (); let mut state = State::default ();
if args.get (1) == Some (&"cheat".to_string ()) {
state.cheat ();
}
let responses = state.step (""); let responses = state.step ("");
for response in responses.into_iter () { for response in responses.into_iter () {
@ -95,8 +91,7 @@ fn game (args: &[String]) -> Result <()> {
} }
fn main () -> Result <()> { fn main () -> Result <()> {
let args: Vec <_> = std::env::args ().collect (); game ()?;
game (&args [..])?;
Ok (()) Ok (())
} }