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 20:24:39 +00:00
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 ( [
" 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. " ) ;
}
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 20:24:39 +00:00
#[ derive (Clone, Default) ]
2021-10-24 20:03:17 +00:00
struct State {
quitting : bool ,
room_1 : StateRoom1 ,
}
2021-10-24 20:24:39 +00:00
fn room_1 < I : Io > ( io : & mut I , state : & mut State ) -> Result < ( ) > {
let input = io . read_input ( ) ? ;
2021-10-24 20:03:17 +00:00
let action = parse_input ( & input ) ;
match action {
PlayerAction ::Quit = > {
2021-10-24 20:24:39 +00:00
io . print ( " Bye. " ) ;
2021-10-24 20:03:17 +00:00
state . quitting = true ;
}
PlayerAction ::Help = > {
2021-10-24 20:24:39 +00:00
print_help ( io ) ;
2021-10-24 20:03:17 +00:00
} ,
PlayerAction ::Hint = > {
2021-10-24 20:24:39 +00:00
io . print ( " Hint for this room: Try using the `help` command. " ) ;
2021-10-24 20:03:17 +00:00
} ,
PlayerAction ::Nonsense = > {
2021-10-24 20:24:39 +00:00
io . print ( " I couldn't understand that. Try `help` or `hint`. " ) ;
io . print ( " `hint` may contain spoilers. `help` will not. " ) ;
2021-10-24 20:03:17 +00:00
} ,
PlayerAction ::Wait = > {
2021-10-24 20:24:39 +00:00
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. " ) ;
2021-10-24 20:03:17 +00:00
} ,
PlayerAction ::LookAround = > {
2021-10-24 20:24:39 +00:00
io . print ( " You are in a small room. In one corner is a TABLE. Obvious exits are a locked DOOR, and an EMERGENCY EXIT. " ) ;
2021-10-24 20:03:17 +00:00
}
PlayerAction ::Look ( item_name ) = > {
match item_name {
ItemName ::Door = > {
2021-10-24 20:24:39 +00:00
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. " ) ;
2021-10-24 20:03:17 +00:00
state . room_1 . detected_keypad = true ;
} ,
ItemName ::EmergencyExit = > {
2021-10-24 20:24:39 +00:00
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. " ) ;
2021-10-24 20:03:17 +00:00
} ,
ItemName ::Keypad = > {
2021-10-24 20:24:39 +00:00
if ! state . room_1 . detected_keypad {
print_undetected_item ( io ) ;
return Ok ( ( ) ) ;
2021-10-24 20:03:17 +00:00
}
2021-10-24 20:24:39 +00:00
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. " ) ;
2021-10-24 20:03:17 +00:00
} ,
ItemName ::Note = > {
2021-10-24 20:24:39 +00:00
if ! state . room_1 . detected_note {
print_undetected_item ( io ) ;
return Ok ( ( ) ) ;
2021-10-24 20:03:17 +00:00
}
2021-10-24 20:24:39 +00:00
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. " ,
] )
2021-10-24 20:03:17 +00:00
} ,
ItemName ::Table = > {
2021-10-24 20:24:39 +00:00
io . print ( " You look at the TABLE. Your instincts tell you that it is period-accurate. Upon the TABLE sits a NOTE. " ) ;
2021-10-24 20:03:17 +00:00
state . room_1 . detected_note = true ;
} ,
2021-10-24 20:24:39 +00:00
_ = > {
print_undetected_item ( io ) ;
} ,
}
} ,
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 ) ;
} ,
2021-10-24 20:03:17 +00:00
}
} ,
}
Ok ( ( ) )
}
2021-10-24 19:04:10 +00:00
fn main ( ) -> Result < ( ) > {
2021-10-24 20:24:39 +00:00
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. " ,
] ) ;
2021-10-24 19:04:10 +00:00
2021-10-24 20:24:39 +00:00
let input = io . read_input ( ) ? ;
2021-10-24 19:04:10 +00:00
if ! input . is_empty ( ) {
2021-10-24 20:24:39 +00:00
io . print ( " That was more than just ENTER but OKAY, overachiever. " ) ;
2021-10-24 19:04:10 +00:00
}
2021-10-24 20:24:39 +00:00
io . 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 20:24:39 +00:00
io . print ( " You are in a small room. In one corner is a TABLE. " ) ;
2021-10-24 20:03:17 +00:00
while ! state . quitting {
2021-10-24 20:24:39 +00:00
room_1 ( & mut io , & mut state ) ? ;
2021-10-24 20:03:17 +00:00
}
2021-10-24 19:04:10 +00:00
Ok ( ( ) )
}
#[ 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 ) ;
}
}
}