Blending Forth, Lisp and SmallTalk produced first results.
I mixed late binding, reflection, concatenation and dynamics in a small python file. 17 lines of VM. 18 lines of compiler.
Simple ping-pong program below.
Grab fo.py and run
python fo.py
Cut&paste code to it.
You need pygame installed! Tested under python 2.3.5/Linux. Probably will not work under windows because interactive interpreter uses weird non-blocking polling IO.
First of all let’s make a window.
from pygame init 0 ginit from pygame.locals DOUBLEBUF 0 doublebuf from pygame.display set_mode 2 mode | # 800 , # 600 , ! display : dinit ginit display doublebuf mode ! sc dinit
Oops, window opened!
Importing functions and values from python looks like from <package> <func> <arity> <word>.. Word from imports function or value and binds it to name word. On execution of word arity values will be popped from stack and passed as arguments to imported function.
Another strange thing is a | # 800 , # 600 , ! display. You could write just a py display (800, 600) (see below) but i write it in this way to show how numbers and tuples constructed.
Word | puts empty tuple () on top of stack.
Word # reads next word and parses it as number and puts result on top of stack.
Word , appends value on top of stack to next value on stack (i.e. () + (800,)).
Word ! pops top of stack and binds this value to variable with a name in a next word (i.e. display = (800,600)).
Next, let’s draw something. Say, a square ball.
py white (255, 255, 255) py shape (10, 10) py ball-x 200 py ball-y 200 method fill 2 fill : ball sc white | | ball-x , ball-y , , shape , fill drop from pygame.display update 0 update : loop input ball update drop loop loop
Word py binds result of pythonic evaluation of rest of string to a name in a next word (i.e. white = eval(’(255, 255, 255)’)).
After evaluation of method x a y, y will execute stack.pop().x(*args). Parameter a is an arity just like in word from.
Look at word loop. Yes, we have tail call optimization.
Word drop just throws out top of stack.
Word input is an non-blocking interactive interpreter you are typing into.
And as you could see changes are instant! See the power of reflection!
Now, move the ball!
from operator add 2 + : move ball-x # 5 + ! ball-x ball-y # 5 + ! ball-y : loop input ball update drop move loop w
Word w will show you current bindings.
Run word w several times and look at ball-x and ball-y. They are changing!
Let’s trap the ball into a screen box!
py ball-dx 5 py ball-dy 5 : move ball-x ball-dx + ! ball-x ball-y ball-dy + ! ball-y py ball-ddy 5 py ball-ddx 5 from operator gt 2 > from operator neg 1 neg : top # 0 ball-y > ? ball-ddy ! ball-dy : left # 0 ball-x > ? ball-ddx ! ball-dx : right ball-x # 800 > ? ball-ddx neg ! ball-dx : bottom ball-y # 600 > ? ball-ddy neg ! ball-dy : box top left right bottom : loop input ball update drop move box loop
Word ? acts as if not stack.pop(): return.
I really like definition of box. And we have changed definition of loop on the fly again.
But, ohh, the ball is far away from the screen box. Put it back with bare hands:
py ball-x 100 py ball-y 100
What a weird trail! Let’s wipe it.
py black (0, 0, 0) method fill 1 erase : clear sc black erase drop : loop input clear ball update drop move box loop
Nothing new just an old magic. Adding a bat!
py bat-x 50 py bat-length 50 : bat sc white | bat-x , bat-y , # 10 , bat-length , fill drop py bat-y 100 : loop input clear ball bat update drop move box loop
Mouse control…
from pygame.event pump 0 pump from operator getitem 2 [] from pygame.mouse get_pos 0 mouse : bat-y mouse # 1 [] : loop input clear move ball box bat update drop pump drop loop
Ugly ghost bat! Make it real!
: ping ball-ddx ! ball-dx : pong bat-x ball-x > ? ball-y bat-y > ? bat-y bat-length + ball-y > ? ping : loop input clear move ball box pong bat update drop pump drop loop
Now type:
: init dinit loop exit
And run fo.py again. Whooa, it starts from where it was before! Even a ball position is preserved.
As a home work you could implement a second bat, AI and scores.
Cya!