86 lines
3.1 KiB
Go
86 lines
3.1 KiB
Go
package westworldwithmessaging
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"sync"
|
|
)
|
|
|
|
// wifesGlobalState is Elsa's global state - its Execute runs on
|
|
// every single tick of her state machine, in addition to whatever
|
|
// her current state is doing (see StateMachine.Update). That's what
|
|
// makes it the right place for "does nature call right now" logic:
|
|
// the book says that can happen "during any state and at any time,"
|
|
// which is exactly what a global state is for.
|
|
type wifesGlobalState struct{}
|
|
|
|
var (
|
|
wifesGlobalStateInstance *wifesGlobalState
|
|
wifesGlobalStateOnce sync.Once
|
|
)
|
|
|
|
// WifesGlobalState is the Go stand-in for
|
|
// "WifesGlobalState::Instance()".
|
|
func WifesGlobalState() *wifesGlobalState {
|
|
wifesGlobalStateOnce.Do(func() {
|
|
wifesGlobalStateInstance = &wifesGlobalState{}
|
|
})
|
|
return wifesGlobalStateInstance
|
|
}
|
|
|
|
var _ State[Elsa] = (*wifesGlobalState)(nil)
|
|
|
|
// Enter and Exit do nothing - a global state is set once, in
|
|
// NewElsa, and never changed, so these never actually get called.
|
|
// They only exist to satisfy the State[Elsa] interface.
|
|
func (s *wifesGlobalState) Enter(e *Elsa) {}
|
|
func (s *wifesGlobalState) Exit(e *Elsa) {}
|
|
|
|
// bathroomCallChance is a 1-in-N chance, per the chapter's "1 in 10
|
|
// chance to change to VisitBathroom state."
|
|
const bathroomCallChance = 10
|
|
|
|
// Execute rolls the dice every tick. If it comes up, and Elsa isn't
|
|
// already in the bathroom (checked with IsInState, so this can't
|
|
// interrupt a bathroom trip that's already happening), she breaks
|
|
// off into VisitBathroom.
|
|
func (s *wifesGlobalState) Execute(e *Elsa) {
|
|
if !e.FSM().IsInState(VisitBathroom()) && rand.Intn(bathroomCallChance) == 0 {
|
|
e.ChangeState(VisitBathroom())
|
|
}
|
|
}
|
|
|
|
// OnMessage handles messages that should get through no matter what
|
|
// Elsa is currently doing - the book puts Msg_HiHoneyImHome here
|
|
// specifically because she should react to it "when in any state,"
|
|
// same reasoning as why the bathroom-call check above lives in the
|
|
// global state rather than in DoHousework.
|
|
//
|
|
// One guard we added that the book's snippet didn't show: if Bob
|
|
// comes home again while a stew is already in the oven (his fatigue
|
|
// or thirst can send him home more than once before her 1.5-second
|
|
// cooking timer fires), this used to unconditionally call
|
|
// ChangeState(CookStew()) even though she was already IN CookStew.
|
|
// That triggered a pointless Exit-then-Enter round trip on the same
|
|
// state - CookStew.Exit fired (printing "Puttin' the stew on the
|
|
// table" while the stew was still cooking!) and then Enter's
|
|
// "!e.Cooking()" check correctly skipped starting a second stew, so
|
|
// nothing useful happened, just a confusing extra print. Checking
|
|
// Cooking() first avoids the whole misfire.
|
|
func (s *wifesGlobalState) OnMessage(e *Elsa, msg Telegram) bool {
|
|
switch MessageType(msg.Msg) {
|
|
case MsgHiHoneyImHome:
|
|
if e.Cooking() {
|
|
// already got dinner covered - nothing new to do
|
|
return true
|
|
}
|
|
|
|
fmt.Printf("\nMessage handled by %s at time: %.5f", GetNameOfEntity(e.ID()), Clock.GetCurrentTime())
|
|
fmt.Printf("\n%s: Hi honey. Let me make you some of mah fine country stew", GetNameOfEntity(e.ID()))
|
|
e.FSM().ChangeState(CookStew())
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|