Files
jonas 2764849943 feat: add Chapter 2 gold miner FSM example in Go
Port the finite state machine from "Programming Game AI by Example"
chapter 2: BaseGameEntity, a generic State[T]/StateMachine[T] pair
(Go generics standing in for the book's C++ templates), and a Miner
that cycles through digging, banking, sleeping, and drinking states.
VisitBankAndDepositGold, GoHomeAndSleepTilRested, and QuenchThirst
are original implementations based on the book's written behavior
description, since their C++ source wasn't available. Includes a
runnable demo in main.go.
2026-09-03 11:36:05 +02:00

70 lines
2.6 KiB
Go

package westworld
// Entity is what every game object in the world must be able to do.
// The C++ code used a pure virtual function (Update()=0) to force
// every subclass to write its own Update. Go has no subclasses, so
// instead we say: "anything with an Update() and an ID() method
// counts as an Entity." A type only satisfies this interface once
// it has its own Update method - just like the C++ version couldn't
// be used until Update was implemented.
type Entity interface {
Update()
ID() int
}
// nextValidID plays the role of the C++ "static int m_iNextValidID".
// In C++, "static" on a class member means "one shared copy for the
// whole class, not one per object." Go doesn't have that keyword for
// structs, so we just use a normal package-level variable - it's
// shared by every BaseGameEntity for the same reason: there's only
// one of it.
var nextValidID int
// BaseGameEntity is the Go version of the C++ class BaseGameEntity.
// Go doesn't have "classes" or "inheritance" - instead, other types
// (like a Miner or a Wife later in the book) will embed this struct
// inside themselves to borrow its fields and methods, and then add
// their own Update method on top.
type BaseGameEntity struct {
id int // was "m_ID" - the entity's own unique number
}
// NewBaseGameEntity replaces the C++ constructor:
//
// BaseGameEntity(int id) { SetID(id); }
//
// Go structs don't run any code automatically when you create them,
// so we write a plain function that builds one correctly. Any code
// that wants a BaseGameEntity should call this instead of building
// the struct by hand.
func NewBaseGameEntity(id int) *BaseGameEntity {
e := &BaseGameEntity{}
e.setID(id)
return e
}
// setID is the Go version of the private C++ method SetID. It does
// exactly what the comment in the book says: check the requested ID
// isn't lower than the next free one, store it, then move the
// "next free ID" counter past it.
//
// The C++ code used assert() for the check, which only runs in
// debug builds and does nothing in a release build. Go has no
// assert, so we use panic - it always runs, and it stops the
// program immediately if this ever happens, which is exactly what
// a broken invariant like this deserves.
func (e *BaseGameEntity) setID(val int) {
if val < nextValidID {
panic("BaseGameEntity.setID: invalid ID")
}
e.id = val
nextValidID = e.id + 1
}
// ID returns the entity's unique number. This is the Go version of
// the C++ "int ID() const { return m_ID; }" - a small read-only
// accessor for the private field.
func (e *BaseGameEntity) ID() int {
return e.id
}