From 75b498156667fb7569d3e08dbb00a9b60e69c679 Mon Sep 17 00:00:00 2001 From: Henrik Hautakoski Date: Sun, 21 Sep 2025 12:15:17 +0200 Subject: [PATCH] feat: add basic state machine implementation --- game/state/handler.go | 8 +++++ game/state/machine/machine.go | 68 +++++++++++++++++++++++++++++++++++ game/state/transitioner.go | 6 ++++ 3 files changed, 82 insertions(+) create mode 100644 game/state/handler.go create mode 100644 game/state/machine/machine.go create mode 100644 game/state/transitioner.go diff --git a/game/state/handler.go b/game/state/handler.go new file mode 100644 index 0000000..86fb3e3 --- /dev/null +++ b/game/state/handler.go @@ -0,0 +1,8 @@ +package state + +type Handler interface { + Enter() + Exit() + Update(transitioner Transitioner, delta float32) + Render() +} diff --git a/game/state/machine/machine.go b/game/state/machine/machine.go new file mode 100644 index 0000000..06e8639 --- /dev/null +++ b/game/state/machine/machine.go @@ -0,0 +1,68 @@ +package machine + +import "tetris/game/state" + +type Machine struct { + current state.Handler + pending string + registry map[string]state.Handler +} + +// New creates an empty machine. +func New() *Machine { + return &Machine{registry: make(map[string]state.Handler)} +} + +// Register binds a name to a factory that returns a fresh state.Handler. +func (m *Machine) Register(name string, handler state.Handler) { + m.registry[name] = handler +} + +// Start switches to the named initial state immediately (calls Enter()). +func (m *Machine) Start(name string) { + m.switchNow(name) +} + +// Switch queues a transition to be applied after the current Update finishes. +func (m *Machine) Switch(name string) { + if name == "" || name == m.pending { + return + } + m.pending = name +} + +// Update ticks the current state, then applies any queued transition. +func (m *Machine) Update(delta float32) { + if m.current == nil { + return + } + m.current.Update(m, delta) + + if m.pending != "" { + next := m.pending + m.pending = "" + m.switchNow(next) + } +} + +// Render proxies to current state. +func (m *Machine) Render() { + if m.current != nil { + m.current.Render() + } +} + +func (m *Machine) switchNow(name string) { + handler, ok := m.registry[name] + if !ok { + // Unknown state: dont switch. + return + } + + if m.current != nil { + m.current.Exit() + } + + m.current = handler + m.current.Enter() +} diff --git a/game/state/transitioner.go b/game/state/transitioner.go new file mode 100644 index 0000000..95af875 --- /dev/null +++ b/game/state/transitioner.go @@ -0,0 +1,6 @@ +package state + +// Transitioner is the only thing a state needs to know about the machine. +type Transitioner interface { + Switch(name string) // request transition by state name +}