2021-10-24 19:04:10 +00:00
/// As a dare to myself, I won't use any error-handling crates.
#[ derive (Debug) ]
enum GameError {
IoError ,
}
impl From < std ::io ::Error > for GameError {
fn from ( _ : std ::io ::Error ) -> Self {
Self ::IoError
}
}
type Result < T > = std ::result ::Result < T , GameError > ;
/// Prints a string, naively wrapping at 80-character boundaries
/// This means a period might be wrapped separate from the sentence it ends
/// , and frankly I think that's part of the charm.
fn print ( mut s : & str ) {
const COLS : usize = 80 ;
while s . len ( ) > COLS {
println! ( " {} " , & s [ 0 .. 80 ] ) ;
s = & s [ 80 .. ] ;
}
println! ( " {} " , s ) ;
}
fn read_input ( ) -> Result < String > {
{
2021-10-24 20:24:39 +00:00
use std ::io ::Write ;
let mut stdout = std ::io ::stdout ( ) ;
2021-10-24 19:04:10 +00:00
stdout . write_all ( b " > " ) ? ;
stdout . flush ( ) ? ;
}
let mut buffer = String ::new ( ) ;
2021-10-24 20:24:39 +00:00
std ::io ::stdin ( ) . read_line ( & mut buffer ) ? ;
2021-10-24 19:04:10 +00:00
// I don't know why I need the type annotation here, but I do.
let x : & [ _ ] = & [ '\r' , '\n' ] ;
let buffer = buffer . trim_end_matches ( x ) . to_string ( ) ;
Ok ( buffer )
}
2021-10-24 22:08:39 +00:00
fn print_help ( ) -> Response {
Response ::PrintMany ( vec! [
2021-10-24 20:24:39 +00:00
" 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`. " ,
2021-10-24 22:08:39 +00:00
] )
}
fn line_response < S : Into < String > > ( line : S ) -> Response {
Response ::Print ( line . into ( ) )
2021-10-24 20:24:39 +00:00
}
2021-10-24 22:08:39 +00:00
fn undetected_item ( ) -> Response {
2021-10-24 22:22:08 +00:00
line_response ( " That ITEM does not exist in this ROOM, or you have not noticed it. " )
2021-10-24 22:08:39 +00:00
}
fn just < T > ( t : T ) -> Vec < T > {
vec! [ t ]
2021-10-24 20:24:39 +00:00
}
2021-10-24 19:04:10 +00:00
#[ derive (Debug, PartialEq) ]
enum PlayerAction {
2021-10-24 20:03:17 +00:00
Quit ,
Help ,
Hint ,
2021-10-24 19:04:10 +00:00
Nonsense ,
2021-10-24 20:03:17 +00:00
Wait ,
Look ( ItemName ) ,
LookAround ,
2021-10-24 20:24:39 +00:00
Use ( ItemName ) ,
2021-10-24 20:03:17 +00:00
}
#[ derive (Clone, Copy, Debug, PartialEq) ]
enum ItemName {
Nonsense ,
Door ,
EmergencyExit ,
Keypad ,
Note ,
Table ,
2021-10-24 19:04:10 +00:00
}
2021-10-24 20:24:39 +00:00
fn _item_name_display ( x : ItemName ) -> & 'static str {
2021-10-24 20:03:17 +00:00
match x {
2021-10-24 20:24:39 +00:00
ItemName ::Nonsense = > " NONSENSE " ,
2021-10-24 20:03:17 +00:00
2021-10-24 20:24:39 +00:00
ItemName ::Door = > " DOOR " ,
ItemName ::EmergencyExit = > " EMERGENCY EXIT " ,
ItemName ::Keypad = > " KEYPAD " ,
ItemName ::Note = > " NOTE " ,
ItemName ::Table = > " TABLE " ,
2021-10-24 20:03:17 +00:00
}
}
fn parse_input ( s : & str ) -> PlayerAction {
let s = s . to_lowercase ( ) ;
if s = = " quit " | | s = = " quit game " {
return PlayerAction ::Quit ;
}
if s = = " look " | | s = = " look around " {
return PlayerAction ::LookAround ;
}
if let Some ( rest ) = s . strip_prefix ( " look at the " ) {
return PlayerAction ::Look ( parse_item_name ( rest ) ) ;
}
if let Some ( rest ) = s . strip_prefix ( " look at " ) {
return PlayerAction ::Look ( parse_item_name ( rest ) ) ;
}
if let Some ( rest ) = s . strip_prefix ( " look " ) {
return PlayerAction ::Look ( parse_item_name ( rest ) ) ;
}
if let Some ( rest ) = s . strip_prefix ( " examine " ) {
return PlayerAction ::Look ( parse_item_name ( rest ) ) ;
}
2021-10-24 20:24:39 +00:00
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 ) ) ;
}
2021-10-24 20:03:17 +00:00
if s = = " do nothing " {
return PlayerAction ::Wait ;
}
if s = = " wait " {
return PlayerAction ::Wait ;
}
if s = = " help " {
return PlayerAction ::Help ;
}
if s = = " hint " {
return PlayerAction ::Hint ;
}
2021-10-24 19:04:10 +00:00
PlayerAction ::Nonsense
}
2021-10-24 20:03:17 +00:00
fn parse_item_name ( s : & str ) -> ItemName {
if s = = " door " {
return ItemName ::Door ;
}
if s = = " emergency exit " {
return ItemName ::EmergencyExit ;
}
if s = = " keypad " {
return ItemName ::Keypad ;
}
if s = = " note " {
return ItemName ::Note ;
}
if s = = " table " {
return ItemName ::Table ;
}
ItemName ::Nonsense
}
2021-10-24 20:24:39 +00:00
#[ derive (Clone, Default) ]
2021-10-24 20:03:17 +00:00
struct StateRoom1 {
detected_keypad : bool ,
detected_note : bool ,
}
2021-10-24 22:16:14 +00:00
#[ derive (PartialEq) ]
2021-10-24 22:08:39 +00:00
enum Response {
Print ( String ) ,
PrintMany ( Vec < & 'static str > ) ,
Sleep ( u32 ) ,
Quit ,
JokeEnding ,
}
2021-10-24 20:24:39 +00:00
#[ derive (Clone, Default) ]
2021-10-24 20:03:17 +00:00
struct State {
room_1 : StateRoom1 ,
}
2021-10-24 22:08:39 +00:00
impl State {
2021-10-24 22:16:14 +00:00
fn room_1 ( & mut self , input : & str ) -> Vec < Response > {
2021-10-24 22:08:39 +00:00
use Response ::* ;
2021-10-24 22:16:14 +00:00
let action = parse_input ( input ) ;
2021-10-24 22:08:39 +00:00
2021-10-24 22:16:14 +00:00
match action {
2021-10-24 22:08:39 +00:00
PlayerAction ::Quit = > {
vec! [
line_response ( " Bye. " ) ,
Quit ,
]
2021-10-24 20:24:39 +00:00
}
2021-10-24 22:08:39 +00:00
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 = > {
2021-10-24 22:22:08 +00:00
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
2021-10-24 20:03:17 +00:00
}
2021-10-24 22:08:39 +00:00
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 {
2021-10-24 22:16:14 +00:00
return just ( undetected_item ( ) ) ;
2021-10-24 22:08:39 +00:00
}
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 {
2021-10-24 22:16:14 +00:00
return vec! [
2021-10-24 22:08:39 +00:00
undetected_item ( ) ,
2021-10-24 22:16:14 +00:00
] ;
2021-10-24 22:08:39 +00:00
}
just ( Response ::PrintMany ( vec! [
2021-10-24 22:22:08 +00:00
" You read the NOTE. " ,
2021-10-24 22:08:39 +00:00
" " ,
" 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 ( ) ,
]
} ,
}
} ,
2021-10-24 22:16:14 +00:00
}
2021-10-24 20:03:17 +00:00
}
}
2021-10-24 22:08:39 +00:00
fn game ( ) -> Result < ( ) > {
for line in [
2021-10-24 20:24:39 +00:00
" 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. " ,
2021-10-24 22:08:39 +00:00
] {
print ( line ) ;
}
2021-10-24 19:04:10 +00:00
2021-10-24 22:08:39 +00:00
let input = read_input ( ) ? ;
2021-10-24 19:04:10 +00:00
if ! input . is_empty ( ) {
2021-10-24 22:08:39 +00:00
print ( " That was more than just ENTER but OKAY, overachiever. " ) ;
2021-10-24 19:04:10 +00:00
}
2021-10-24 22:08:39 +00:00
print ( " " ) ;
2021-10-24 19:04:10 +00:00
2021-10-24 20:03:17 +00:00
let mut state = State ::default ( ) ;
2021-10-24 22:08:39 +00:00
print ( " You are in a small room. In one corner is a TABLE. " ) ;
2021-10-24 20:03:17 +00:00
2021-10-24 22:08:39 +00:00
' main_loop : loop {
let input = read_input ( ) ? ;
2021-10-24 22:16:14 +00:00
let responses = state . room_1 ( & input ) ;
2021-10-24 22:08:39 +00:00
for response in responses . into_iter ( ) {
match response {
Response ::Print ( line ) = > print ( & line ) ,
Response ::PrintMany ( lines ) = > {
for line in lines {
print ( line ) ;
}
} ,
Response ::Sleep ( x ) = > std ::thread ::sleep ( std ::time ::Duration ::from_millis ( x . into ( ) ) ) ,
Response ::Quit = > break 'main_loop ,
_ = > ( ) ,
}
}
2021-10-24 20:03:17 +00:00
}
2021-10-24 19:04:10 +00:00
Ok ( ( ) )
}
2021-10-24 22:08:39 +00:00
fn main ( ) -> Result < ( ) > {
game ( ) ? ;
Ok ( ( ) )
}
2021-10-24 19:04:10 +00:00
#[ cfg (test) ]
mod test {
#[ test ]
fn parse_input ( ) {
2021-10-24 20:03:17 +00:00
use super ::{
ItemName ,
PlayerAction ::* ,
} ;
2021-10-24 19:04:10 +00:00
for ( input , expected ) in [
( " " , Nonsense ) ,
2021-10-24 20:03:17 +00:00
( " look at the table " , Look ( ItemName ::Table ) ) ,
( " look at table " , Look ( ItemName ::Table ) ) ,
( " look table " , Look ( ItemName ::Table ) ) ,
( " LOOK TABLE " , Look ( ItemName ::Table ) ) ,
( " wait " , Wait ) ,
( " help " , Help ) ,
( " hint " , Hint ) ,
2021-10-24 19:04:10 +00:00
] . into_iter ( ) {
let actual = super ::parse_input ( input ) ;
assert_eq! ( actual , expected ) ;
}
}
2021-10-24 22:08:39 +00:00
#[ test ]
fn joke_ending ( ) {
2021-10-24 22:16:14 +00:00
use super ::{
Response ,
State ,
} ;
let mut state = State ::default ( ) ;
let responses = state . room_1 ( " use emergency exit " ) ;
2021-10-24 22:08:39 +00:00
2021-10-24 22:16:14 +00:00
assert! ( responses . contains ( & Response ::Quit ) ) ;
assert! ( responses . contains ( & Response ::JokeEnding ) ) ;
2021-10-24 22:08:39 +00:00
}
2021-10-24 19:04:10 +00:00
}