feat: finalize with step 5
This commit is contained in:
1 parent
129dd5d8b8
commit
aab128b55c
10 files changed
+332
-37
No files matched your search
@@ -0,0 +1,37 @@
|
|||||||
|
// Command westworldwithmessaging runs the full messaging demo: Miner
|
||||||
|
// Bob and Elsa tick forward together, and each iteration also flushes
|
||||||
|
// any delayed telegrams that have come due (Dispatch.DispatchDelayedMessages).
|
||||||
|
//
|
||||||
|
// Our Clock measures real wall-clock time, same as the book's, and
|
||||||
|
// Elsa's CookStew reminder uses a real 1.5 second delay - so this
|
||||||
|
// loop sleeps a little each tick and runs for several seconds of
|
||||||
|
// real time, giving that delayed message an actual chance to fire
|
||||||
|
// before the program ends. A tight, sleep-free loop would finish in
|
||||||
|
// microseconds and the stew would never get made.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.jjstudio.me/jonas/Programming-Game-AI-by-Example-Go/chapter-2-state-driven-agent-design/westworldwithmessaging"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
bob := westworldwithmessaging.NewMiner(1)
|
||||||
|
elsa := westworldwithmessaging.NewElsa(2)
|
||||||
|
|
||||||
|
const (
|
||||||
|
numUpdateSteps = 40
|
||||||
|
tickDelay = 100 * time.Millisecond
|
||||||
|
)
|
||||||
|
|
||||||
|
for range numUpdateSteps {
|
||||||
|
westworldwithmessaging.Dispatch.DispatchDelayedMessages()
|
||||||
|
bob.Update()
|
||||||
|
elsa.Update()
|
||||||
|
time.Sleep(tickDelay)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
@@ -10,6 +10,11 @@ type Elsa struct {
|
|||||||
*BaseGameEntity
|
*BaseGameEntity
|
||||||
|
|
||||||
stateMachine *StateMachine[Elsa]
|
stateMachine *StateMachine[Elsa]
|
||||||
|
|
||||||
|
// cooking tracks whether there's already a stew in the oven, so
|
||||||
|
// CookStew.Enter doesn't put a second one in or send a second
|
||||||
|
// reminder message to herself.
|
||||||
|
cooking bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewElsa sets up Elsa's state machine: DoHousework is her ordinary,
|
// NewElsa sets up Elsa's state machine: DoHousework is her ordinary,
|
||||||
@@ -40,6 +45,16 @@ func (e *Elsa) FSM() *StateMachine[Elsa] {
|
|||||||
return e.stateMachine
|
return e.stateMachine
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cooking reports whether there's already a stew in the oven.
|
||||||
|
func (e *Elsa) Cooking() bool {
|
||||||
|
return e.cooking
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCooking updates whether there's a stew in the oven.
|
||||||
|
func (e *Elsa) SetCooking(v bool) {
|
||||||
|
e.cooking = v
|
||||||
|
}
|
||||||
|
|
||||||
// Update satisfies the Entity interface (alongside the ID() method
|
// Update satisfies the Entity interface (alongside the ID() method
|
||||||
// borrowed from BaseGameEntity) and just delegates to the state
|
// borrowed from BaseGameEntity) and just delegates to the state
|
||||||
// machine, same shape as Miner.Update.
|
// machine, same shape as Miner.Update.
|
||||||
|
|||||||
@@ -1,10 +1,21 @@
|
|||||||
package westworldwithmessaging
|
package westworldwithmessaging
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// SendMsgImmediately and NoAdditionalInfo are the Go versions of the
|
||||||
|
// book's "SEND_MSG_IMMEDIATELY" and "NO_ADDITIONAL_INFO" - both
|
||||||
|
// defined as 0 in the book's MessageDispatcher.h purely so call
|
||||||
|
// sites read clearly instead of showing a bare 0/NULL. NoAdditionalInfo
|
||||||
|
// is nil rather than 0, since Go's nil is what an empty "any" (our
|
||||||
|
// ExtraInfo field's type) actually looks like.
|
||||||
|
const SendMsgImmediately = 0.0
|
||||||
|
|
||||||
|
var NoAdditionalInfo any = nil
|
||||||
|
|
||||||
// messageDispatcher is the Go version of the singleton class
|
// messageDispatcher is the Go version of the singleton class
|
||||||
// MessageDispatcher. Same singleton pattern as everything else in
|
// MessageDispatcher. Same singleton pattern as everything else in
|
||||||
// this package: unexported type, one shared instance, reached only
|
// this package: unexported type, one shared instance, reached only
|
||||||
@@ -109,6 +120,9 @@ func (d *messageDispatcher) DispatchMessage(delay float64, sender int, receiver
|
|||||||
|
|
||||||
// if there is no delay, route the telegram immediately
|
// if there is no delay, route the telegram immediately
|
||||||
if delay <= 0.0 {
|
if delay <= 0.0 {
|
||||||
|
fmt.Printf("\nInstant telegram dispatched at time: %.5f by %s for %s. Msg is %s",
|
||||||
|
Clock.GetCurrentTime(), GetNameOfEntity(sender), GetNameOfEntity(receiver), GetNameOfMessage(MessageType(msg)))
|
||||||
|
|
||||||
// send the telegram to the recipient
|
// send the telegram to the recipient
|
||||||
d.discharge(pReceiver, telegram)
|
d.discharge(pReceiver, telegram)
|
||||||
} else {
|
} else {
|
||||||
@@ -118,6 +132,9 @@ func (d *messageDispatcher) DispatchMessage(delay float64, sender int, receiver
|
|||||||
|
|
||||||
// and put it in the queue
|
// and put it in the queue
|
||||||
d.insertIntoPriorityQueue(telegram)
|
d.insertIntoPriorityQueue(telegram)
|
||||||
|
|
||||||
|
fmt.Printf("\nDelayed telegram from %s recorded at time %.5f for %s. Msg is %s",
|
||||||
|
GetNameOfEntity(sender), currentTime, GetNameOfEntity(receiver), GetNameOfMessage(MessageType(msg)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,6 +163,9 @@ func (d *messageDispatcher) DispatchDelayedMessages() {
|
|||||||
// read the telegram from the front of the queue
|
// read the telegram from the front of the queue
|
||||||
telegram := d.priorityQueue[0]
|
telegram := d.priorityQueue[0]
|
||||||
|
|
||||||
|
fmt.Printf("\nQueued telegram ready for dispatch: Sent to %s. Msg is %s",
|
||||||
|
GetNameOfEntity(telegram.Receiver), GetNameOfMessage(MessageType(telegram.Msg)))
|
||||||
|
|
||||||
// find the recipient
|
// find the recipient
|
||||||
pReceiver := EntityMgr.GetEntityFromID(telegram.Receiver)
|
pReceiver := EntityMgr.GetEntityFromID(telegram.Receiver)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package westworldwithmessaging
|
package westworldwithmessaging
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
// MessageType is the Go version of the C++ enum:
|
// MessageType is the Go version of the C++ enum:
|
||||||
//
|
//
|
||||||
// enum message_type
|
// enum message_type
|
||||||
@@ -22,3 +24,24 @@ const (
|
|||||||
MsgHiHoneyImHome MessageType = iota
|
MsgHiHoneyImHome MessageType = iota
|
||||||
MsgStewReady
|
MsgStewReady
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// messageNames is our own stand-in for however the book actually
|
||||||
|
// renders a message type in its dispatcher log lines (the sample
|
||||||
|
// output shows names like "HiHoneyImHome" and "StewReady" - the enum
|
||||||
|
// constant with the "Msg_" prefix dropped). We haven't been shown
|
||||||
|
// the book's real helper for this, so this recreates exactly that
|
||||||
|
// naming.
|
||||||
|
var messageNames = map[MessageType]string{
|
||||||
|
MsgHiHoneyImHome: "HiHoneyImHome",
|
||||||
|
MsgStewReady: "StewReady",
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNameOfMessage looks up a friendly name for a message type, for
|
||||||
|
// use in log output. Anything not in messageNames falls back to
|
||||||
|
// printing its raw number.
|
||||||
|
func GetNameOfMessage(msg MessageType) string {
|
||||||
|
if name, ok := messageNames[msg]; ok {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Msg %d", int(msg))
|
||||||
|
}
|
||||||
@@ -2,13 +2,23 @@ package westworldwithmessaging
|
|||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
|
// entMinerBob and entElsa are our own stand-ins for the book's
|
||||||
|
// enumerated entity IDs (referenced as "ent_Miner_Bob" and "ent_Elsa"
|
||||||
|
// - presumably from an enum in a header we haven't been shown). Since
|
||||||
|
// this demo only ever has these two fixed characters, plain named
|
||||||
|
// constants do the same job: giving call sites a readable name
|
||||||
|
// instead of a bare number.
|
||||||
|
const (
|
||||||
|
entMinerBob = 1
|
||||||
|
entElsa = 2
|
||||||
|
)
|
||||||
|
|
||||||
// entityNames is a temporary stand-in for the book's real name
|
// entityNames is a temporary stand-in for the book's real name
|
||||||
// lookup table. We still haven't been shown that table, so this is
|
// lookup table. We still haven't been shown that table, so this is
|
||||||
// just a small map we're filling in by hand as we meet entities -
|
// just a small map we're filling in by hand as we meet entities.
|
||||||
// the miner (ID 1) and now his wife Elsa (ID 2).
|
|
||||||
var entityNames = map[int]string{
|
var entityNames = map[int]string{
|
||||||
1: "Miner Bob",
|
entMinerBob: "Miner Bob",
|
||||||
2: "Elsa",
|
entElsa: "Elsa",
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNameOfEntity looks up a friendly name for a given entity ID.
|
// GetNameOfEntity looks up a friendly name for a given entity ID.
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package westworldwithmessaging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// cookStew is the Go version of the (not yet pasted) singleton class
|
||||||
|
// CookStew from the book. Same pattern as every other state:
|
||||||
|
// unexported type, one shared instance, reached only through the
|
||||||
|
// function below.
|
||||||
|
type cookStew struct{}
|
||||||
|
|
||||||
|
var (
|
||||||
|
cookStewInstance *cookStew
|
||||||
|
cookStewOnce sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
// CookStew is the Go stand-in for "CookStew::Instance()".
|
||||||
|
func CookStew() *cookStew {
|
||||||
|
cookStewOnce.Do(func() {
|
||||||
|
cookStewInstance = &cookStew{}
|
||||||
|
})
|
||||||
|
return cookStewInstance
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ State[Elsa] = (*cookStew)(nil)
|
||||||
|
|
||||||
|
// Execute and Exit weren't pasted to us directly either, but a
|
||||||
|
// sample run of the book's program makes their behavior clear
|
||||||
|
// enough to fill in: Execute prints "Fussin' over food" once per
|
||||||
|
// tick while the stew is in the oven (waiting for the delayed
|
||||||
|
// Msg_StewReady reminder to arrive), and Exit announces taking the
|
||||||
|
// stew out and putting it on the table, right before switching back
|
||||||
|
// to DoHousework.
|
||||||
|
|
||||||
|
// Enter puts the stew in the oven (if one isn't already cooking) and
|
||||||
|
// sends a delayed message to herself, reminding her to take it out
|
||||||
|
// once it's ready.
|
||||||
|
func (s *cookStew) Enter(e *Elsa) {
|
||||||
|
if !e.Cooking() {
|
||||||
|
fmt.Printf("\n%s: Puttin' the stew in the oven", GetNameOfEntity(e.ID()))
|
||||||
|
|
||||||
|
// send a delayed message to myself so that I know when to take
|
||||||
|
// the stew out of the oven
|
||||||
|
Dispatch.DispatchMessage(
|
||||||
|
1.5, // time delay
|
||||||
|
e.ID(), // sender ID
|
||||||
|
e.ID(), // receiver ID
|
||||||
|
int(MsgStewReady), // the message
|
||||||
|
NoAdditionalInfo, // no extra info attached
|
||||||
|
)
|
||||||
|
e.SetCooking(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *cookStew) Execute(e *Elsa) {
|
||||||
|
fmt.Printf("\n%s: Fussin' over food", GetNameOfEntity(e.ID()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *cookStew) Exit(e *Elsa) {
|
||||||
|
fmt.Printf("\n%s: Puttin' the stew on the table", GetNameOfEntity(e.ID()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnMessage handles the delayed reminder Enter sent to herself: once
|
||||||
|
// the stew's ready, take it out, let Bob know dinner's on the table,
|
||||||
|
// and go back to the ordinary housework state.
|
||||||
|
func (s *cookStew) OnMessage(e *Elsa, msg Telegram) bool {
|
||||||
|
switch MessageType(msg.Msg) {
|
||||||
|
case MsgStewReady:
|
||||||
|
fmt.Printf("\nMessage received by %s at time: %.5f", GetNameOfEntity(e.ID()), Clock.GetCurrentTime())
|
||||||
|
fmt.Printf("\n%s: Stew ready! Let's eat", GetNameOfEntity(e.ID()))
|
||||||
|
|
||||||
|
// let hubby know the stew is ready
|
||||||
|
Dispatch.DispatchMessage(
|
||||||
|
SendMsgImmediately,
|
||||||
|
e.ID(),
|
||||||
|
entMinerBob,
|
||||||
|
int(MsgStewReady),
|
||||||
|
NoAdditionalInfo,
|
||||||
|
)
|
||||||
|
|
||||||
|
e.SetCooking(false)
|
||||||
|
e.FSM().ChangeState(DoHousework())
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -26,25 +26,33 @@ func DoHousework() *doHousework {
|
|||||||
var _ State[Elsa] = (*doHousework)(nil)
|
var _ State[Elsa] = (*doHousework)(nil)
|
||||||
|
|
||||||
// chores is our own small stand-in for the book's actual chore list
|
// chores is our own small stand-in for the book's actual chore list
|
||||||
// - it only ever showed two examples in its sample output ("Moppin'
|
// - its sample output shows three examples ("Moppin' the floor",
|
||||||
// the floor", "Makin' the bed"), so that's exactly what's reused
|
// "Makin' the bed", "Washin' the dishes"), so that's exactly what's
|
||||||
// here. One gets picked at random each tick.
|
// reused here. One gets picked at random each tick.
|
||||||
var chores = []string{
|
var chores = []string{
|
||||||
"Moppin' the floor",
|
"Moppin' the floor",
|
||||||
"Makin' the bed",
|
"Makin' the bed",
|
||||||
|
"Washin' the dishes",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enter does nothing - the book's sample output never shows a
|
// Enter announces that Elsa is back to her ordinary routine. A
|
||||||
// message when Elsa starts doing housework, only while she's doing
|
// sample run of the book's program shows this line printed right
|
||||||
// it (Execute) and when she leaves the bathroom back into it.
|
// after she finishes cooking and switches back to this state - an
|
||||||
func (s *doHousework) Enter(e *Elsa) {}
|
// earlier, more abridged sample output we saw didn't show this line
|
||||||
|
// after a bathroom-break revert, but that sample was explicitly
|
||||||
|
// described as just "a sample," not a complete transcript, so this
|
||||||
|
// message firing every time Enter runs (bathroom revert included)
|
||||||
|
// isn't a contradiction.
|
||||||
|
func (s *doHousework) Enter(e *Elsa) {
|
||||||
|
fmt.Printf("\n%s: Time to do some more housework!", GetNameOfEntity(e.ID()))
|
||||||
|
}
|
||||||
|
|
||||||
// Execute announces one randomly chosen chore per tick.
|
// Execute announces one randomly chosen chore per tick.
|
||||||
func (s *doHousework) Execute(e *Elsa) {
|
func (s *doHousework) Execute(e *Elsa) {
|
||||||
fmt.Printf("\n%s: %s", GetNameOfEntity(e.ID()), chores[rand.Intn(len(chores))])
|
fmt.Printf("\n%s: %s", GetNameOfEntity(e.ID()), chores[rand.Intn(len(chores))])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exit does nothing, for the same reason as Enter.
|
// Exit does nothing - no sample output ever shows a message here.
|
||||||
func (s *doHousework) Exit(e *Elsa) {}
|
func (s *doHousework) Exit(e *Elsa) {}
|
||||||
|
|
||||||
// OnMessage is required now that State[T] includes it, so that every
|
// OnMessage is required now that State[T] includes it, so that every
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package westworldwithmessaging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// eatStew is the Go version of the (not yet pasted) singleton class
|
||||||
|
// EatStew from the book. Same pattern as every other state:
|
||||||
|
// unexported type, one shared instance, reached only through the
|
||||||
|
// function below.
|
||||||
|
type eatStew struct{}
|
||||||
|
|
||||||
|
var (
|
||||||
|
eatStewInstance *eatStew
|
||||||
|
eatStewOnce sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
// EatStew is the Go stand-in for "EatStew::Instance()".
|
||||||
|
func EatStew() *eatStew {
|
||||||
|
eatStewOnce.Do(func() {
|
||||||
|
eatStewInstance = &eatStew{}
|
||||||
|
})
|
||||||
|
return eatStewInstance
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ State[Miner] = (*eatStew)(nil)
|
||||||
|
|
||||||
|
// The book never pasted this state's actual source - it only ever
|
||||||
|
// referenced EatStew::Instance() as a switch target. This whole
|
||||||
|
// implementation is reverse-engineered from a sample run of the
|
||||||
|
// finished program, which shows this exact sequence: Bob enters
|
||||||
|
// (one line), eats (one line, then immediately heads back to
|
||||||
|
// whatever he was doing - a single-tick state, same shape as
|
||||||
|
// QuenchThirst), and leaving prints one more line before he's back
|
||||||
|
// in GoHomeAndSleepTilRested.
|
||||||
|
|
||||||
|
// Enter announces that dinner smells good.
|
||||||
|
func (s *eatStew) Enter(m *Miner) {
|
||||||
|
fmt.Printf("\n%s: Smells reaaal goood, Elsa!", GetNameOfEntity(m.ID()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute is the entire meal: announce it, then head back to
|
||||||
|
// whatever state he was in before dinner interrupted him. The sample
|
||||||
|
// output never shows more than one "Tastes real good too!" line, so
|
||||||
|
// - like QuenchThirst - this doesn't loop across multiple ticks.
|
||||||
|
func (s *eatStew) Execute(m *Miner) {
|
||||||
|
fmt.Printf("\n%s: Tastes real good too!", GetNameOfEntity(m.ID()))
|
||||||
|
m.ChangeState(GoHomeAndSleepTilRested())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit thanks Elsa for the meal on the way out.
|
||||||
|
func (s *eatStew) Exit(m *Miner) {
|
||||||
|
fmt.Printf("\n%s: Thank ya li'l lady. Ah better get back to whatever ah wuz doin'", GetNameOfEntity(m.ID()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnMessage is required now that State[T] includes it. The sample
|
||||||
|
// output doesn't show Bob reacting to any message while eating, so
|
||||||
|
// false ("not handled") is the correct default.
|
||||||
|
func (s *eatStew) OnMessage(m *Miner, msg Telegram) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
+41
-18
@@ -28,41 +28,64 @@ func GoHomeAndSleepTilRested() *goHomeAndSleepTilRested {
|
|||||||
|
|
||||||
var _ State[Miner] = (*goHomeAndSleepTilRested)(nil)
|
var _ State[Miner] = (*goHomeAndSleepTilRested)(nil)
|
||||||
|
|
||||||
// The book hasn't shown us this state's real code, so this is our
|
// Enter sends the miner home, if he isn't there already, and lets
|
||||||
// own implementation based purely on its written description: "the
|
// Elsa know he's arrived by dispatching a message to her.
|
||||||
// miner will return to his shack and sleep until his fatigue level
|
|
||||||
// drops below an acceptable level. He will then change state to
|
|
||||||
// EnterMineAndDigForNugget."
|
|
||||||
|
|
||||||
// Enter sends the miner home, if he isn't there already.
|
|
||||||
func (s *goHomeAndSleepTilRested) Enter(m *Miner) {
|
func (s *goHomeAndSleepTilRested) Enter(m *Miner) {
|
||||||
if m.Location() != shack {
|
if m.Location() != shack {
|
||||||
fmt.Printf("\n%s: Headin' on home", GetNameOfEntity(m.ID()))
|
fmt.Printf("\n%s: Walkin' home", GetNameOfEntity(m.ID()))
|
||||||
m.ChangeLocation(shack)
|
m.ChangeLocation(shack)
|
||||||
|
|
||||||
|
// let the wife know I'm home
|
||||||
|
Dispatch.DispatchMessage(
|
||||||
|
SendMsgImmediately, // time delay
|
||||||
|
m.ID(), // ID of sender
|
||||||
|
entElsa, // ID/name of recipient
|
||||||
|
int(MsgHiHoneyImHome), // the message
|
||||||
|
NoAdditionalInfo, // no extra info attached
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute sleeps off a little fatigue each tick, and once the miner
|
// Execute sleeps off a little fatigue each tick, and once the miner
|
||||||
// is rested enough, sends him back to work.
|
// is rested enough, announces it and sends him back to work. A
|
||||||
|
// sample run of the book's program shows this exact message
|
||||||
|
// ("All mah fatigue has drained away...") printed right before the
|
||||||
|
// switch to digging - and no message at all when this state is
|
||||||
|
// instead exited early via a Msg_StewReady interruption (see
|
||||||
|
// OnMessage below) - which is why this line lives here in Execute's
|
||||||
|
// else-branch rather than in Exit: Exit runs either way, but this
|
||||||
|
// text is specific to waking up rested, not to leaving for any
|
||||||
|
// reason.
|
||||||
func (s *goHomeAndSleepTilRested) Execute(m *Miner) {
|
func (s *goHomeAndSleepTilRested) Execute(m *Miner) {
|
||||||
if m.Fatigued() {
|
if m.Fatigued() {
|
||||||
fmt.Printf("\n%s: ZZZZ... ", GetNameOfEntity(m.ID()))
|
fmt.Printf("\n%s: ZZZZ... ", GetNameOfEntity(m.ID()))
|
||||||
m.DecreaseFatigue()
|
m.DecreaseFatigue()
|
||||||
} else {
|
} else {
|
||||||
|
fmt.Printf("\n%s: All mah fatigue has drained away. Time to find more gold!", GetNameOfEntity(m.ID()))
|
||||||
m.ChangeState(EnterMineAndDigForNugget())
|
m.ChangeState(EnterMineAndDigForNugget())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exit announces that the miner is up and about again.
|
// Exit does nothing - a sample run of the book's program shows no
|
||||||
func (s *goHomeAndSleepTilRested) Exit(m *Miner) {
|
// message printed here, whether the miner leaves because he's
|
||||||
fmt.Printf("\n%s: What a great sleep! Time to find more gold", GetNameOfEntity(m.ID()))
|
// rested or because dinner's ready.
|
||||||
|
func (s *goHomeAndSleepTilRested) Exit(m *Miner) {}
|
||||||
|
|
||||||
|
// OnMessage is what makes Bob only respond to Elsa's dinner call
|
||||||
|
// while he's actually home to hear it: this state only exists while
|
||||||
|
// he's in the shack, so putting the Msg_StewReady handling here
|
||||||
|
// (rather than in MinerGlobalState) is exactly how the book
|
||||||
|
// guarantees "he will only respond to this message if he is in the
|
||||||
|
// GoHomeAndSleepTilRested state."
|
||||||
|
func (s *goHomeAndSleepTilRested) OnMessage(m *Miner, msg Telegram) bool {
|
||||||
|
switch MessageType(msg.Msg) {
|
||||||
|
case MsgStewReady:
|
||||||
|
fmt.Printf("\nMessage handled by %s at time: %.5f", GetNameOfEntity(m.ID()), Clock.GetCurrentTime())
|
||||||
|
fmt.Printf("\n%s: Okay hun, ahm a-comin'!", GetNameOfEntity(m.ID()))
|
||||||
|
m.ChangeState(EatStew())
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnMessage is required now that State[T] includes it. This state
|
// send message to global message handler
|
||||||
// doesn't handle any messages yet (the book hasn't shown us that a
|
|
||||||
// sleeping miner reacts to anything, like a "stew's ready" message,
|
|
||||||
// though that seems like a likely candidate later), so false ("not
|
|
||||||
// handled") is the correct default for now.
|
|
||||||
func (s *goHomeAndSleepTilRested) OnMessage(m *Miner, msg Telegram) bool {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
+14
-7
@@ -1,6 +1,7 @@
|
|||||||
package westworldwithmessaging
|
package westworldwithmessaging
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
@@ -49,13 +50,19 @@ func (s *wifesGlobalState) Execute(e *Elsa) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnMessage is required now that State[T] includes it. This state
|
// OnMessage handles messages that should get through no matter what
|
||||||
// doesn't handle any messages yet - false ("not handled") is the
|
// Elsa is currently doing - the book puts Msg_HiHoneyImHome here
|
||||||
// correct default until the book shows otherwise. (Note: our Elsa
|
// specifically because she should react to it "when in any state,"
|
||||||
// only has DoHousework and VisitBathroom, not a cooking state, so
|
// same reasoning as why the bathroom-call check above lives in the
|
||||||
// the book's stew-related messages between Miner and Elsa may not
|
// global state rather than in DoHousework.
|
||||||
// map cleanly onto this simplified version - worth revisiting if
|
|
||||||
// that comes up.)
|
|
||||||
func (s *wifesGlobalState) OnMessage(e *Elsa, msg Telegram) bool {
|
func (s *wifesGlobalState) OnMessage(e *Elsa, msg Telegram) bool {
|
||||||
|
switch MessageType(msg.Msg) {
|
||||||
|
case MsgHiHoneyImHome:
|
||||||
|
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
|
return false
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user