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
4041from pythonbpf .maps import HashMap
4142
4243MAX_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
4546TICK_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.
232220T_KEYS = {b"w" : UP , b"s" : DOWN , b"a" : LEFT , b"d" : RIGHT }
233221
234222
235223def 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
272242def 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
322291HUD = 56
323292
324293BG = (15 , 23 , 42 )
0 commit comments