-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstate_machine.py
More file actions
282 lines (224 loc) · 9.98 KB
/
state_machine.py
File metadata and controls
282 lines (224 loc) · 9.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
"""
Branchless State Machine — State transitions without if/switch.
PATTERN: Table-Driven Transitions
Instead of a switch/case or if/elif chain to decide what happens
when an event occurs, we store ALL transitions in a 2D table:
next_state = TRANSITION[current_state][event]
action = ACTIONS[current_state][event]
This is a single array lookup — no branches, no matter how many
states or events exist.
Traditional (branching):
if state == IDLE:
if event == WALK:
state = WALKING
speed = 1
elif event == JUMP:
state = JUMPING
velocity = 10
...
Branchless (table-driven):
state = TRANSITION[state][event] # One lookup!
action = ACTIONS[state][event] # One lookup!
REAL-WORLD USES:
- Game character controllers (this example)
- Network protocol handlers (TCP state machine)
- Lexers and parsers (tokenization)
- UI navigation (screen flows)
- Hardware controllers (elevator logic, traffic lights)
"""
# ──────────────────────────────────────────────
# State and Event Definitions
# ──────────────────────────────────────────────
# States — each is just an integer index into our tables
IDLE = 0
WALKING = 1
RUNNING = 2
JUMPING = 3
FALLING = 4
NUM_STATES = 5
# Events — also integer indices
EVT_NONE = 0 # No event (do nothing)
EVT_WALK = 1 # Start walking
EVT_RUN = 2 # Start running
EVT_JUMP = 3 # Jump
EVT_LAND = 4 # Land on ground
EVT_STOP = 5 # Stop movement
NUM_EVENTS = 6
# Human-readable names for display
STATE_NAMES = ["IDLE", "WALKING", "RUNNING", "JUMPING", "FALLING"]
EVENT_NAMES = ["NONE", "WALK", "RUN", "JUMP", "LAND", "STOP"]
# ──────────────────────────────────────────────
# Transition Table
# ──────────────────────────────────────────────
#
# HOW TO READ THIS TABLE:
# Row = current state, Column = event
# Value = next state
#
# Example: TRANSITION[WALKING][EVT_JUMP] = JUMPING
# "If we're WALKING and a JUMP event occurs, go to JUMPING"
#
# Example: TRANSITION[JUMPING][EVT_WALK] = JUMPING
# "If we're JUMPING and a WALK event occurs, stay JUMPING"
# (can't start walking while in the air!)
#
# Event: NONE WALK RUN JUMP LAND STOP
TRANSITION = [
# IDLE:
[IDLE, WALKING, RUNNING, JUMPING, IDLE, IDLE],
# WALKING:
[WALKING, WALKING, RUNNING, JUMPING, WALKING, IDLE],
# RUNNING:
[RUNNING, WALKING, RUNNING, JUMPING, RUNNING, IDLE],
# JUMPING:
[JUMPING, JUMPING, JUMPING, JUMPING, IDLE, FALLING],
# FALLING:
[FALLING, FALLING, FALLING, FALLING, IDLE, FALLING],
]
# ──────────────────────────────────────────────
# Action Functions
# ──────────────────────────────────────────────
# Each function returns a velocity value representing what happens
# during this transition.
def action_none(state, event):
"""No-op — nothing changes."""
return 0
def action_start_walk(state, event):
"""Begin walking — set speed to 1."""
return 1
def action_start_run(state, event):
"""Begin running — set speed to 3."""
return 3
def action_jump(state, event):
"""Jump — set upward velocity to 10."""
return 10
def action_land(state, event):
"""Land — reset velocity to 0."""
return 0
def action_stop(state, event):
"""Stop — reset velocity to 0."""
return 0
# ──────────────────────────────────────────────
# Action Table
# ──────────────────────────────────────────────
#
# Same structure as TRANSITION table, but stores functions instead of states.
# ACTIONS[current_state][event] = function_to_call
#
# Event: NONE WALK RUN JUMP LAND STOP
ACTIONS = [
# IDLE:
[action_none, action_start_walk, action_start_run, action_jump, action_none, action_none],
# WALKING:
[action_none, action_none, action_start_run, action_jump, action_none, action_stop],
# RUNNING:
[action_none, action_start_walk, action_none, action_jump, action_none, action_stop],
# JUMPING:
[action_none, action_none, action_none, action_none, action_land, action_none],
# FALLING:
[action_none, action_none, action_none, action_none, action_land, action_none],
]
# ──────────────────────────────────────────────
# State Machine Class
# ──────────────────────────────────────────────
class BranchlessStateMachine:
"""
A state machine that uses table lookups for ALL decisions.
There are zero if/switch statements in the core logic.
Adding new states or events just means adding rows/columns to the tables.
"""
def __init__(self):
self.state = IDLE
self.velocity = 0
def process_event(self, event):
"""
Process an event: look up the action, execute it, transition state.
This is the core of the branchless pattern:
1. Validate input with arithmetic (no if)
2. Look up action in table (no if)
3. Look up next state in table (no if)
Returns:
(old_state, new_state, velocity) tuple
"""
# Branchless bounds check: if event is out of range, treat as EVT_NONE (0)
# valid = 1 if 0 <= event < NUM_EVENTS, else 0
# event * valid = event if valid, 0 if invalid
valid = int(0 <= event < NUM_EVENTS)
safe_event = event * valid
# Look up and execute the action — one table access, no branching
action = ACTIONS[self.state][safe_event]
result = action(self.state, safe_event)
# Update velocity
self.velocity = result
# Transition state — one table access, no branching
old_state = self.state
self.state = TRANSITION[self.state][safe_event]
return old_state, self.state, result
def get_state_name(self):
"""Get human-readable name of current state."""
return STATE_NAMES[self.state]
# ──────────────────────────────────────────────
# Helper: Branchless Event Priority Selection
# ──────────────────────────────────────────────
def branchless_event_priority(events):
"""
Select the highest-priority event from a list, without branching.
In games, multiple events can happen in one frame (e.g., player presses
WALK and JUMP simultaneously). We need to pick the most important one.
Priority order: JUMP (highest) > RUN > WALK > STOP > LAND > NONE (lowest)
TRICK: Use a priority lookup table and branchless max selection.
For each event, we check: is this event's priority higher than our best?
is_better = int(priority > best_priority)
Then use the 0/1 flag to conditionally update:
best = new * is_better + old * (1 - is_better)
"""
# NONE WALK RUN JUMP LAND STOP
priority_table = [0, 3, 4, 5, 1, 2]
best_event = EVT_NONE
best_priority = 0
for evt in events:
evt_priority = priority_table[evt]
# Branchless "if evt_priority > best_priority, update best"
is_better = int(evt_priority > best_priority)
best_event = evt * is_better + best_event * (1 - is_better)
best_priority = evt_priority * is_better + best_priority * (1 - is_better)
return best_event
# ──────────────────────────────────────────────
# Demo
# ──────────────────────────────────────────────
if __name__ == "__main__":
print("Branchless State Machine Demo")
print("=" * 55)
sm = BranchlessStateMachine()
# Simulate a sequence of game events
events = [
EVT_WALK, # IDLE → WALKING
EVT_RUN, # WALKING → RUNNING
EVT_JUMP, # RUNNING → JUMPING
EVT_LAND, # JUMPING → IDLE
EVT_WALK, # IDLE → WALKING
EVT_STOP, # WALKING → IDLE
]
print(f"\nInitial state: {sm.get_state_name()}")
print(f"\n{'Event':<10} {'From':<10} {'To':<10} {'Velocity'}")
print("-" * 45)
for event in events:
old, new, velocity = sm.process_event(event)
print(f" {EVENT_NAMES[event]:<8} {STATE_NAMES[old]:<10} → {STATE_NAMES[new]:<10} vel={velocity}")
# Test event priority selection
print(f"\n{'='*55}")
print("Event Priority Selection (pick most important event):")
test_sets = [
[EVT_WALK, EVT_RUN],
[EVT_STOP, EVT_JUMP, EVT_WALK],
[EVT_NONE, EVT_LAND],
]
for event_list in test_sets:
names = [EVENT_NAMES[e] for e in event_list]
selected = branchless_event_priority(event_list)
print(f" {names} → {EVENT_NAMES[selected]}")
print()
print("KEY TECHNIQUE: 2D lookup tables")
print(" TRANSITION[state][event] = next_state")
print(" ACTIONS[state][event] = what_to_do")
print(" → zero branches, easy to extend")