I made a snake #182
Replies: 5 comments 3 replies
-
PandoraHissp version (EDN). 0 ; from garden_of_edn import _this_file_as_main_; """#"
(hissp/_macro_.prelude)
(define WIDTH 40)
(define HEIGHT 20)
(define FRAMERATE 0.1)
(define ESC #b"\\x1b")
(define BINDS {#b"H" (complex 0 -1), #b"P" (complex 0 1), #b"K" -1, #b"M" +1})
(define new-food (lambda . (complex (random/randint 1 #hissp/."WIDTH-2")
(random/randint 1 #hissp/."HEIGHT-2"))))
(define wall? (lambda z (#hissp/$"||" (contains #{#hissp/."WIDTH-1" 0} z.real)
(contains #{#hissp/."HEIGHT-1" 0} z.imag))))
(define score 0)
(define snake (list [(complex 3 2) (complex 2 2)]))
(define food (new-food))
(define direction +1)
(define hit (iter msvcrt/getch #b""))
(loop-from () ()
(define board (list))
(any-map z (starmap complex (map reversed (product (range HEIGHT) (range WIDTH))))
(cond (contains snake z) (.append board "O")
(eq z food) (.append board "@")
(eq #hissp/."WIDTH-1" z.real) (.append board "#\n")
(wall? z) (.append board "#")
:else (.append board " ")))
(let (out (.format "Score: {}\n{}" score (.join "" board)))
(os/system "cls")
(print out))
(when (msvcrt/kbhit)
(let (key (next hit))
(when (eq key ESC)
(sys/exit))
(when (eq key #b"\\xe0")
(let (arrow (.get BINDS (next hit) direction))
(when (ne arrow (neg direction))
(define direction arrow))))))
(let (head (add (#get 0 snake) direction))
(if-else (eq head food)
(progn (zap! add (globals) (quote score) 1)
(define food (new-food)))
(.pop snake))
(.insert snake 0 head)
(when (#hissp/$"||" (wall? head) (contains (#X #hissp/."X[1:]" snake) head))
(print "Game over!")
(sys/exit)))
(time/sleep FRAMERATE)
(recur-from ()))
;; """#"
It's a pretty direct translation for comparison, so it's not taking full advantage of the pyrsistent data structures. It looks really similar, but there are a few things that were harder than in Lissp. What passes for the current EDN spec is actually quite a bit stricter than what Clojure's actual EDN parser will accept, e.g. By the way, this is also a valid Python file. It's why a PandoraHissp main module can be run with the 0 ; from garden_of_edn import _this_file_as_main_; """#"
(hissp/_macro_.prelude)
...
;; """#" |
Beta Was this translation helpful? Give feedback.
-
Readerless: from hissp import Compiler, munge as m
WIDTH = 40
HEIGHT = 20
FRAMERATE = 0.1
ESC = b'\x1b'
BINDS = {b'H': -1j, b'P': +1j, b'K': -1, b'M': +1}
def q(x):
return 'quote',x
program = [
('hissp.._macro_.prelude',)
,('define','new_food'
,('lambda',()
,(complex,('random..randint',1,WIDTH-2,)
,('random..randint',1,HEIGHT-2,),),),)
,('define','is_wall'
,('lambda','z'
,(m('||'),('contains',{WIDTH-1,0},'z.real',)
,('contains',{HEIGHT-1,0},'z.imag',),),),)
,('define','score',0,)
,('define','snake',[3+2j,2+2j],)
,('define','food',('new_food',),)
,('define','direction',+1,)
,('define','hit',(iter,'msvcrt..getch',b'',),)
,(m('loop-from'),(),()
,('define','board',[],)
,(m('any-map'),'z',('starmap',complex
,(map,reversed
,('product',range(HEIGHT)
,range(WIDTH),),),)
,('cond','(z in snake)',('.append','board',q('O'),)
,'(z == food)',('.append','board',q('@'),)
,'(WIDTH-1 == z.real)',('.append','board',q('#\n'),)
,('is_wall','z',),('.append','board',q('#'),)
,':else',('.append','board',q(' '),),),)
,('let',('out',('.format',q("Score: {}\n{}"),'score',('.join',q(''),'board',),),)
,('os..system',q('cls'),)
,('print','out',),)
,('when',('msvcrt..kbhit',)
,('let',('key',(next,'hit',),)
,('when',('eq','key',b'\xe0',)
,('let',('arrow',('.get','BINDS',(next,'hit',),'direction',),)
,('when','(arrow != -direction)'
,('define','direction','arrow',),),),),),)
,('let',('head','(snake[0] + direction)',)
,(m('if-else'),'(head == food)'
,('progn'
,(m('zap!'),'add',(globals,),q('score'),1,)
,('define','food',('new_food',),),)
,('.pop','snake',),)
,('.insert','snake',0,'head',)
,('when',(m('||'),('is_wall','head',),'(head in snake[1:])',)
,(print,q('Game over!'),)
,('sys..exit',),),)
,('time..sleep','FRAMERATE',)
,(m('recur-from'),(),),)
]
if __name__ == '__main__':
Compiler(ns=globals()).compile(program) I'm noticing a tendency to push computation to "read" time. (I.e., into the Python before compilation happens. Readerless mode lacks a reader, so it uses Python itself for parsing the text into the Hissp data structures instead.) Notice the All the commas make readerless mode awkward. But starting the line with them helps. I'm going to update the style guide for this. I feel like readerless mode is meant for short snippets, not for spelling out programs this long. None of the readerless mode code in e.g. Hebigo's bootstrap was this long, although the Hissp it programmatically generates could be. Parentheses and indents are hard to manage without editor support. |
Beta Was this translation helpful? Give feedback.
-
Hebigo version: from: itertools :import product starmap
def: WIDTH 40
def: HEIGHT 20
def: FRAMERATE 0.1
def: ESC b'\x1b'
def: BINDS {b'H': -1j, b'P': +1j, b'K': -1, b'M': +1}
def: new_food:
complex:
random..randint: 1 (WIDTH-2)
random..randint: 1 (HEIGHT-2)
def: is_wall: z
or: (z.real in {WIDTH-1, 0}) (z.imag in {HEIGHT-1, 0})
def: score 0
def: snake [3+2j, 2+2j]
def: food new_food:
def: direction +1
def: hit iter: msvcrt..getch b''
!loop: recur:
def: board []
for: z :in starmap: complex map: reversed product: range:HEIGHT range:WIDTH
if: (z in snake)
:then:
.append: board "O"
:elif: (z == food)
.append: board "@"
:elif: (WIDTH-1 == z.real)
.append: board "#\n"
:elif: is_wall:z
.append: board "#"
:else:
.append: board " "
!let: out :be .format: "Score: {}\n{}" score .join: "" board
os..system: "cls"
print: out
if: msvcrt..kbhit:
:then:!let: key :be next: hit
if: (key == ESC)
:then:sys..exit:
if: (key == b'\xe0')
:then:!let: arrow :be .get: BINDS next:hit direction
if: (arrow != -direction)
:then:def: direction arrow
!let: head :be (snake[0] + direction)
if: (head == food)
:then:
def: score (score+1)
def: food new_food:
:else:.pop:snake
.insert: snake 0 head
if: or: is_wall:head (head in snake[1:])
:then:
print: "Game over!"
sys..exit:
time..sleep: FRAMERATE
recur: I found and corrected a couple of serious bugs in Hebigo (missed by its limited tests) in order to get this one working. I need to dogfood more. Writing the code itself was much easier in Hebigo than in readerless. Hebigo doesn't currently have a good way to run a main module, only to transpile, but it will execute the code as a side effect. The command I used to run it was something like |
Beta Was this translation helpful? Give feedback.
-
Cross-platform Tkinter version (in Lissp). No flickering! (hissp.._macro_.prelude)
(define TICK 100)
(define WIDTH 40)
(define HEIGHT 20)
(define root
(doto (tkinter..Tk)
(.resizable 0 0)
(.bind "<Key>" X#(.extend arrow (-> (dict (zip "wasd" (zip '(-1j -1 +1j +1))))
(.get X.char ()))))))
(define label (doto (tkinter..Label)
.pack
(.configure : font "TkFixedFont"
justify "left"
height (add 1 HEIGHT)
width WIDTH)))
(define new-food O#(complex (random..randint 0 (sub WIDTH 1))
(random..randint 0 (sub HEIGHT 1))))
(define wall? (lambda z (|| (contains (# WIDTH -1) z.real)
(contains (# HEIGHT -1) z.imag))))
(define arrow (@))
(define score 0)
(define snake (@ 3+2j 2+2j))
(define food (new-food))
(define direction +1)
(define update
(lambda :
(define board (@))
(any-map z (starmap ^^#complex^@ (product (range HEIGHT) (range WIDTH)))
(when (eq 0 z.real)
(.append board #"\n"))
(cond (contains snake z) (.append board "O")
(eq z food) (.append board "@")
:else (.append board " ")))
(.configure label : text (.format #"Score: {}{}" score (.join "" board)))
(when arrow
(when (ne (get#0 arrow) (neg direction))
(define direction (get#0 arrow)))
(.pop arrow 0))
(let (head (add (get#0 snake) direction))
(if-else (eq head food)
(progn (zap! add (globals) 'score 1)
(define food (new-food)))
(.pop snake))
(.insert snake 0 head)
(if-else (|| (wall? head) (contains ([#1:] snake) head))
(.configure label : text (.format #"Score: {} GAME OVER!{}"
score
(.join "" board)))
(label.after TICK update)))))
(when (eq __name__ '__main__)
(update)
(.mainloop root)) |
Beta Was this translation helpful? Give feedback.
-
Functional Tkinter version (in PandoraHissp). 0 ; from garden_of_edn import _this_file_as_main_; """#"
(hissp/_macro_.prelude)
(attach _macro_ . ors #hissp/$"_macro_.||", ands #hissp/$"_macro_.&&")
(defmacro #hissp/$"m#" t (tuple (.extend [(quote pyrsistent/m) (quote .)] t)))
(defmacro #hissp/$"j#" j (complex 0 j))
(define TICK 100)
(define WIDTH 40)
(define HEIGHT 20)
(define SNAKE (pyrsistent/dq (complex 3 2) (complex 2 2)))
(define BINDS #m(w [#j -1], a [-1], s [#j 1], d [1]))
(define arrow (collections/deque))
(define root (doto (tkinter/Tk)
(.resizable 0 0)
(.bind "<Key>" #X(.extendleft arrow (.get BINDS X.char ())))))
(define label (doto (tkinter/Label) .pack (.configure . font "TkFixedFont"
justify "left"
height (add 1 HEIGHT)
width WIDTH)))
(define wall? (lambda z (ors (contains #{WIDTH -1} z.real)
(contains #{HEIGHT -1} z.imag))))
(define food! #O(complex (random/randint 0 (sub WIDTH 1))
(random/randint 0 (sub HEIGHT 1))))
(define frame (lambda (state)
(-<>> (product (range HEIGHT) (range WIDTH))
(starmap #XY(complex Y X))
(map (lambda z (concat (cond (contains state.snake z) "O"
(eq z state.food) "@"
:else " ")
(if-else (eq 0 z.real) "\n" ""))))
(.join ""))))
(define move (lambda (state new-food arrow)
(let (direction (if-else (ands arrow (ne arrow (neg state.direction)))
arrow state.direction))
(let (head (add (#get 0 state.snake) direction))
(-> state
(.update (if-else (eq head state.food)
#m(score (add 1 state.score)
food new-food)
#m(snake (.pop state.snake)))
#m(direction direction))
(.transform [(quote snake)] #X(.appendleft X head)))))))
(define lost? (lambda (state)
(let (head (#get 0 state.snake))
(ors (wall? head)
(contains (#get(slice 1 None) state.snake)
head)))))
(define update!
(lambda (state)
(-<>> (if-else (lost? state)
" GAME OVER!"
(prog1 "" (.after root TICK update! (move state (food!) (when arrow
(.pop arrow))))))
(.format "Score: {}{}{}" state.score :<> (frame state))
(.configure label . text))))
(when (eq __name__ "__main__")
(update! #m(score 0, direction 1, snake SNAKE, food (food!)))
(.mainloop root))
;; """#" Turns out pmaps work like namespaces as well, if you use identifier keys. Just don't collide with method names. Unfortunately, that means keyword keys don't work. No problem, just use symbols. This is a better fit for Python anyway. Except there's no quotes in EDN. Everything's presumed to be already quoted, so there would be no point. That means falling back to the I tried a Now I'm using the I also renamed Doing this the functional way made it significantly longer. The overhead maybe isn't worth it in a program this small. |
Beta Was this translation helpful? Give feedback.
-
Another example of what Lissp code can look like.
Super basic flickering terminal game in about 50 lines of Lissp. Also somewhat imperative in style and with mutable global fields, patterns which don't scale too well. Python doesn't seem to have an OS-independent way to clear the screen or read keyboard input (without enter) with a terminal in the standard library, so this is the Windows version. I expect the POSIX version to be similar though.
Beta Was this translation helpful? Give feedback.
All reactions