Skip to content

Commit 869cf61

Browse files
Examples: Draw bpfsnake's terminal board exactly as the bpftrace script does
The terminal mode prints the original's 12x50 board of #, $ and @ from the top-left corner and exits when the snake dies. Both modes go back to the original's board size and BEGIN state, since the walls live in the kernel's tick.
1 parent bc56dd8 commit 869cf61

1 file changed

Lines changed: 26 additions & 57 deletions

File tree

‎examples/snake.py‎

Lines changed: 26 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
# sudo -E env PYTHONPATH=. /path/to/python examples/snake.py # pygame
2121
# sudo env PYTHONPATH=. /path/to/python examples/snake.py --terminal
2222
# The pygame window needs pygame (pip install pygame), and -E keeps your display
23-
# in the environment. The terminal needs 64 columns and 26 rows.
23+
# in the environment. The terminal draws the original's board, and like the
24+
# original the game ends when the snake dies.
2425
# Arrows or WASD steer, Space pauses, R restarts, Esc or Q quits.
2526
# x86_64 only (the syscall name in the kprobe).
2627

@@ -40,8 +41,8 @@
4041
from pythonbpf.maps import HashMap
4142

4243
MAX_LENGTH = 32
43-
WIDTH = 30 # columns, walls included
44-
HEIGHT = 20 # rows, walls included
44+
WIDTH = 50 # columns, walls included
45+
HEIGHT = 12 # rows, walls included
4546
TICK_MS = 120
4647

4748
# Slots in the `state` map. The eBPF function spells them as literals: module
@@ -103,7 +104,7 @@ def tick(ctx: c_void_p) -> c_int64:
103104
# Game over when the head hits a wall ...
104105
x = snakex.lookup(0)
105106
y = snakey.lookup(0)
106-
if x == 0 or x == 19 or y == 0 or y == 29:
107+
if x == 0 or x == 11 or y == 0 or y == 49:
107108
state.update(2, 1) # GAME_OVER
108109

109110
# ... or the snake's own body
@@ -115,8 +116,8 @@ def tick(ctx: c_void_p) -> c_int64:
115116

116117
# Eat: new food somewhere inside the walls, and the tail grows by one
117118
if x == state.lookup(0) and y == state.lookup(1): # FOODX, FOODY
118-
state.update(0, random() % 18 + 1)
119-
state.update(1, random() % 28 + 1)
119+
state.update(0, random() % 10 + 1)
120+
state.update(1, random() % 48 + 1)
120121
i = 2
121122
while i < 32 and snakex.lookup(i) != 0 and snakey.lookup(i) != 0:
122123
i += 1
@@ -156,11 +157,10 @@ def restart(self):
156157
m.delete_elem(i)
157158
except Exception:
158159
pass # the key was never set
159-
mid = HEIGHT // 2
160-
b["snakex"][0], b["snakey"][0] = mid, 4
161-
b["snakex"][1], b["snakey"][1] = mid, 3
162-
b["state"][FOODX] = mid
163-
b["state"][FOODY] = WIDTH // 2
160+
# BEGIN, as in the original
161+
b["snakex"][0], b["snakey"][0] = 1, 1
162+
b["snakex"][1], b["snakey"][1] = 1, 2
163+
b["state"][FOODX], b["state"][FOODY] = 4, 4
164164
b["state"][GAME_OVER] = 0
165165
b["state"][KEY] = RIGHT
166166
b["state"][PLAYER] = os.getpid()
@@ -215,58 +215,28 @@ def status(self):
215215
# ----------------------------------------------------------------- terminal
216216

217217

218-
def esc(code):
219-
return f"\033[{code}m"
220-
221-
222-
T_WALL = esc("48;5;240")
223-
T_TILES = (esc("48;5;234"), esc("48;5;235"))
224-
T_HEAD = esc("48;5;120") + esc("38;5;16")
225-
T_BODY = (esc("48;5;41"), esc("48;5;35"), esc("48;5;29"))
226-
T_FOOD = esc("38;5;203")
227-
T_TEXT, T_MUTED, T_ACCENT = esc("1;38;5;255"), esc("38;5;245"), esc("38;5;81")
228-
T_RESET = esc("0")
229-
230218
# Bytes a terminal sends: an arrow is ESC [ A..D (or ESC O A..D), and its last
231219
# byte happens to be the code the kernel expects.
232220
T_KEYS = {b"w": UP, b"s": DOWN, b"a": LEFT, b"d": RIGHT}
233221

234222

235223
def terminal_frame(game):
236-
body = {cell: i for i, cell in reversed(list(enumerate(game.body)))}
237-
n = max(len(game.body) - 1, 1)
238-
lines = [
239-
f" {T_TEXT}bpfsnake{T_RESET} {T_MUTED}game logic runs in eBPF{T_RESET}",
240-
"",
241-
]
224+
"""The board exactly as the bpftrace script prints it."""
225+
body = set(game.body)
226+
rows = []
242227
for x in range(HEIGHT):
243-
row = [" "]
228+
row = []
244229
for y in range(WIDTH):
245-
tile = T_TILES[(x + y) % 2]
246-
if x in (0, HEIGHT - 1) or y in (0, WIDTH - 1):
247-
row.append(T_WALL + " ")
230+
if x == 0 or y == 0 or x == HEIGHT - 1 or y == WIDTH - 1:
231+
row.append("#")
248232
elif (x, y) == game.food:
249-
row.append(tile + T_FOOD + "● ")
233+
row.append("$")
250234
elif (x, y) in body:
251-
i = body[(x, y)]
252-
if i == 0:
253-
row.append(T_HEAD + "••")
254-
else:
255-
row.append(T_BODY[min(i * 3 // (n + 1), 2)] + " ")
235+
row.append("@")
256236
else:
257-
row.append(tile + " ")
258-
lines.append("".join(row) + T_RESET)
259-
lines.append("")
260-
if game.over:
261-
note = f"{T_TEXT}game over{T_RESET} {T_MUTED}r restart · q quit{T_RESET}"
262-
elif game.paused:
263-
note = f"{T_TEXT}paused{T_RESET} {T_MUTED}space resume{T_RESET}"
264-
else:
265-
note = f"{T_MUTED}arrows/wasd steer · space pause · q quit{T_RESET}"
266-
lines.append(f" {T_ACCENT}{game.status()}{T_RESET}")
267-
lines.append(f" {note}")
268-
# Clear to the end of each line so a shorter status leaves nothing behind
269-
return "\033[H" + "\033[K\n".join(lines) + "\033[K"
237+
row.append(" ")
238+
rows.append("".join(row) + "\n")
239+
return "\033[H" + "".join(rows) # move cursor to top left
270240

271241

272242
def terminal_keys(data, game):
@@ -299,10 +269,12 @@ def run_terminal(game):
299269
out = sys.stdout
300270
try:
301271
tty.setcbreak(fd) # keys arrive at once and are not echoed
302-
out.write("\033[?1049h\033[?25l\033[2J") # alternate screen, no cursor
272+
out.write("\033[H\033[2J") # clear screen
303273
while True:
304274
out.write(terminal_frame(game))
305275
out.flush()
276+
if game.over:
277+
return # exit(), as in the original
306278
wait = max(0.0, TICK_MS / 1000 - (time.monotonic() - game.last_tick))
307279
if select.select([fd], [], [], wait)[0]:
308280
if not terminal_keys(os.read(fd, 64), game):
@@ -311,14 +283,11 @@ def run_terminal(game):
311283
game.tick()
312284
finally:
313285
termios.tcsetattr(fd, termios.TCSAFLUSH, saved)
314-
out.write("\033[?25h\033[?1049l")
315-
out.flush()
316-
print(f"bpfsnake: {game.status()}")
317286

318287

319288
# ------------------------------------------------------------------- pygame
320289

321-
CELL = 28
290+
CELL = 24
322291
HUD = 56
323292

324293
BG = (15, 23, 42)

0 commit comments

Comments
 (0)