feat: westworldwithwoman. add elsa
This commit is contained in:
1 parent
2764849943
commit
0e7482e04c
28 files changed
+1946
No files matched your search
@@ -0,0 +1,131 @@
|
||||
package westworldwithwoman
|
||||
|
||||
// StateMachine is the Go version of the C++ class template:
|
||||
//
|
||||
// template <class entity_type>
|
||||
// class StateMachine { ... };
|
||||
//
|
||||
// Same trick as State[T]: [T any] is a blank slot filled in with
|
||||
// whichever entity type owns this machine, e.g. StateMachine[Miner].
|
||||
// Instead of an agent managing its own currentState field and
|
||||
// ChangeState method by hand (like Miner did before), the agent now
|
||||
// just owns one of these and hands it all the work.
|
||||
type StateMachine[T any] struct {
|
||||
// owner is the agent this state machine belongs to and acts on.
|
||||
// Go version of "entity_type* m_pOwner".
|
||||
owner *T
|
||||
|
||||
// currentState is whatever mode the agent is in right now.
|
||||
currentState State[T]
|
||||
|
||||
// previousState is a record of the last state the agent was in,
|
||||
// so RevertToPreviousState can jump back to it.
|
||||
previousState State[T]
|
||||
|
||||
// globalState's Execute runs on every single Update, no matter
|
||||
// what currentState is - for behavior that should always be
|
||||
// checked (the book will show a real use for this later).
|
||||
globalState State[T]
|
||||
}
|
||||
|
||||
// NewStateMachine is the Go stand-in for the C++ constructor
|
||||
// "StateMachine(entity_type* owner)". Go doesn't run constructor
|
||||
// initializer lists automatically, so this function does that job
|
||||
// by hand: remember the owner, leave every state slot empty. An
|
||||
// empty interface value in Go is nil - that's the direct equivalent
|
||||
// of the book's NULL.
|
||||
func NewStateMachine[T any](owner *T) *StateMachine[T] {
|
||||
return &StateMachine[T]{owner: owner}
|
||||
}
|
||||
|
||||
// SetCurrentState, SetGlobalState and SetPreviousState are for
|
||||
// initializing the FSM directly - for example, giving an agent its
|
||||
// very first state without running it through the Enter/Exit dance
|
||||
// that ChangeState does.
|
||||
func (sm *StateMachine[T]) SetCurrentState(s State[T]) {
|
||||
sm.currentState = s
|
||||
}
|
||||
|
||||
func (sm *StateMachine[T]) SetGlobalState(s State[T]) {
|
||||
sm.globalState = s
|
||||
}
|
||||
|
||||
func (sm *StateMachine[T]) SetPreviousState(s State[T]) {
|
||||
sm.previousState = s
|
||||
}
|
||||
|
||||
// Update runs every tick. The C++ version marked this method const
|
||||
// (a promise that it won't change the StateMachine's own fields) -
|
||||
// Go has no equivalent keyword, and doesn't need one; nothing here
|
||||
// requires special marking. If a global state is set, its Execute
|
||||
// always runs; then, separately, whatever the current state is also
|
||||
// runs. Both can fire in the same tick - that's the point of having
|
||||
// two slots instead of one.
|
||||
func (sm *StateMachine[T]) Update() {
|
||||
// if a global state exists, call its execute method
|
||||
if sm.globalState != nil {
|
||||
sm.globalState.Execute(sm.owner)
|
||||
}
|
||||
|
||||
// same for the current state
|
||||
if sm.currentState != nil {
|
||||
sm.currentState.Execute(sm.owner)
|
||||
}
|
||||
}
|
||||
|
||||
// ChangeState switches the agent into a new state properly: it
|
||||
// remembers where it came from, tells the old state it's being
|
||||
// left, swaps over, then tells the new state it's being entered.
|
||||
func (sm *StateMachine[T]) ChangeState(newState State[T]) {
|
||||
// The C++ code used assert() to make sure the new state isn't a
|
||||
// null pointer. Go's equivalent is panic() - nil is Go's version
|
||||
// of a null pointer/null value.
|
||||
if newState == nil {
|
||||
panic("StateMachine.ChangeState: trying to change to a nil state")
|
||||
}
|
||||
|
||||
// keep a record of the previous state
|
||||
sm.previousState = sm.currentState
|
||||
|
||||
// call the exit method of the existing state
|
||||
sm.currentState.Exit(sm.owner)
|
||||
|
||||
// change state to the new state
|
||||
sm.currentState = newState
|
||||
|
||||
// call the entry method of the new state
|
||||
sm.currentState.Enter(sm.owner)
|
||||
}
|
||||
|
||||
// RevertToPreviousState switches back to whatever state the agent
|
||||
// was in right before its current one.
|
||||
func (sm *StateMachine[T]) RevertToPreviousState() {
|
||||
sm.ChangeState(sm.previousState)
|
||||
}
|
||||
|
||||
// CurrentState, GlobalState and PreviousState are plain read-only
|
||||
// accessors for the three state slots.
|
||||
func (sm *StateMachine[T]) CurrentState() State[T] {
|
||||
return sm.currentState
|
||||
}
|
||||
|
||||
func (sm *StateMachine[T]) GlobalState() State[T] {
|
||||
return sm.globalState
|
||||
}
|
||||
|
||||
func (sm *StateMachine[T]) PreviousState() State[T] {
|
||||
return sm.previousState
|
||||
}
|
||||
|
||||
// IsInState reports whether the agent's current state is the same
|
||||
// state as the one passed in. The book describes this as comparing
|
||||
// state *types* (it takes "const State<entity_type>&", not a
|
||||
// pointer, and presumably checks typeid under the hood) - but every
|
||||
// concrete state in this codebase is a singleton (there's only ever
|
||||
// one *doHousework, one *visitBathroom, and so on), so comparing the
|
||||
// two interface values directly for equality gets the same answer:
|
||||
// they're equal exactly when they're both that one shared instance
|
||||
// of the same state type.
|
||||
func (sm *StateMachine[T]) IsInState(st State[T]) bool {
|
||||
return sm.currentState == st
|
||||
}
|
||||
Reference in new issue
Block a user