Since FIGnition is a real (though simple) computer, it can run real
software and there are a number of example programs that can be run! Contents:
Luna: A Lunar LanderTo begin with, here's a simple interactive version of Lunar Lander, called Luna. It's a bitmapped version of the game which was supposed to have been delivered to the BBC along with kit, but wasn't ready in time. It demonstrates that you can write a simple interactive bitmapped - i.e. sprite-based game and that the Forth system is capable of supporting 'real' development. Since this is the first available program for FIGnition I've supplied it as a listing too:
Your Lunar lander is heading for the moon as it drifts along; guide it down gently to the landing pad using space (SW7) to provide thrust if necessary. Your Lunar lander will crash if you hit the regolith or hit the landing pad at a velocity over 200! Be careful not to send the craft out of orbit!!!!!!!!!! Graphics Demo
FIGnition is fast enough to handle a large number of moving graphic characters entirely in Forth. This simple demonstration moves a number of UDGs randomly around the screen while displaying the frame rate. 20 UDGs can be animated at 36 frames per second and up to 81 at least 10 frames per second. You run the demonstration with e.g. |
( Block 1)5 var seed: rnd ( range -- random ) seed @ 1+ 75 * dup seed ! u* swap drop;( graphic demo UDGs: Invader, mini pacman, mini-ghost, man, dog, quaver, Invader2, 8. ): cdata <builds does> ;: var[] <builds dup + allot does> over + + ;2 base !cdata udgs00101000 c,01111110 c,11011011 c,11111111 c,10011001 c,10100101 c,00000000 c,00000000 c, ( invader)00111100 c,01111110 c,11111110 c,11111000 c,11110000 c,11111110 c,01111110 c,00111100 c, ( pacman)00111100 c,11111111 c,10010011 c,10110111 c,11111111 c,11111111 c,11011011 c,01001001 c, ( ghost) |
00111000 c,00111000 c,00010010 c,11111110 c,10010000 c,00101000 c,00101000 c,00101000 c,00000000 c,00000000 c,00000111 c,10000111 c,01111100 c,01111000 c,10000100 c,01000100 c,00000011 c,00011111 c,01111101 c,01100001 c,01000011 c,01000011 c,11000000 c,11000000 c,00011000 c,00111100 c,00011000 c,00111100 c,01111110 c,11011011 c,11111111 c,00011000 c,10000000 c,11100000 c,11110000 c,11111100 c,10110000 c,00011000 c,00001100 c,00000110 c, |
decimal: udg 8 * vram + 600 +;: initudg ( src dst num ) 8 * 0 do over c@ over ic! swap 1+ swap 1+ loop drop drop;: tudgs ( addr -- ) 8 0 do i over ic! 2 + loop;: scraddr ( x y ) 25 * + vram +; ( 6 w )100 const maxAnimmaxAnim var[] poz: initpos ( lim ) 0 do vram 600 rnd + i poz ! loop ;: range ( vramaddr -- ) dup vram < if 600 + then dup vram 599 + > if 600 - then ; |
: vcalc ( vramaddr -- ) 3 rnd 1 - 25 * + 3 rnd 1 - + range ;: udgmove ( lim -- ) 0 do i poz @ dup vcalc ( old new ) swap 32 swap ic! i 7 and over ic! i poz ! loop ;: fps ( frms timo lim) >r swap 1+ swap dup clock i@ - 0< if 0 0 at ." fps " drop . 0 ." #" r . clock i@ 49 + then r> ;: doKey dup 9 = if drop 1+ maxAnim min 0 then dup 8 = if drop 1 - 1 max 0 then 32 = ;: gdem ( initialUdgs ) udgs 0 udg 8 initudg maxAnim initpos 0 clock i@ 49 + rot begin dup udgmove fps inkey doKey until drop drop drop ;
|
A simple timeout command (note, we do a timeout by calculating timeout-clock<0, not timeout<clock since the subtraction followed by the 0< will in fact take care of overflow and wrap-around. The maximum timeout ticks are limited to 32767 (about 10 minutes).
: setTimeout ( ticks -- timeout)
clock i@ +
;
: timeout ( timeout -- boolean) dup clock i@ - 0<;Accessing the eeprom:
$42 const eearh |
|
Drawing Circles!
The following few blocks uses a simple Bresenham Circle drawing algorithm to draw circles. You only really need the last 2 columns. It's quite quick, using roughly 100µs per point (a radius 23 circle can be drawn in <20ms).( FIGnition Circles |
: nextp ( x y diff ) |
: octplot ( cx cy dx dy) |
: gTest1 ( circles) |
Here's both a low resolution and high resolution demonstration of circ, the hi-res one being generated by running hiCirc.
Noughts And Crosses!
Here's a simple 'unbeatable' noughts and crosses game in only 5 screens and 554 bytes!
( Simple oxo block 0) |
( block 2 ) |
( block 3) |
( block 4) |
For the Computing History Museum's Hackers' Delight event I wrote a better version of Noughts and Crosses ( see below). It's 7.5Kb of source, and compiles into 2.9Kb of Forth (though 841b are just the definition headers, so the actual, compiled code is only 2.1Kb).
Line Drawing
Here's a simple block that provides line drawing!: qLine ( DX DY X Y n) |
|
Using the standard Bresenham line-drawing algorithm would be slower on FIGnition so instead line gradients are represented as 16:16 bit fixed point values which are added to coordinates also represented as 16:16 fixed-point values. This replaces the earlier algorithm which used 8:8 bit fixed point values and now requires Firmware 1.0.0 or later to run. This line drawing algorithm plots lines at up to 8000 pixels per second, faster than most 80s 8-bit computers!
Life
I wrote a version of Life for the Jupiter-Ace in 2006. I wanted to see how easy it was to convert to FIGnition. It's not so hard and the result is listed here
The FIGnition version is only 1497b of compiled code (+4600b of data); is about 10x faster than the Ace version and generates a new generation every 2.95s. But the motivation wasn't just to prove FIGnition would be faster, but to encourage is other conversions of Jupiter-Ace programs by doing one myself.
Simple Character Graphics Engine
FIGnition is fast enough to support simple tiled graphics directly in Forth. Here's an easy double-buffered tiled graphics engine to make simple games easier to write. ( overhead: 1200b data) |
: xy@: fTile ( t -- ) |
: clg: backTiles>: tilesUp ( tileMap ): tilesDown
|
: vfill ( dst -- ): tilesLeft ( tileMap ) |
The code is currently untested, so you should expect to see changes before anything is written using it, so for the moment this is a description of how it works. The display engine consists of a background image and a foreground image. The basic technique is to first draw the background contents using tile then use backTiles> to copy it to the foreground image. You then start drawing the foreground graphics using fore. When you've finished you'd use foreTiles> to copy the entire screen to video and then call backTiles> to restore the background image before drawing your new foreground graphics Foreground graphics are simple cdata tile maps, so if you have, say a 2x2 graphic image to be displayed at 10, 12 you'd define it with:
cdata myBlob 2 c, 3 c, 4 c, 5 c,Then you can copy it using
10 12 >xy myBlob 2 2 fore .The basic game is loop is thus:
clg ( clears the background)
generate-background-using-tile
backTiles>
begin
generate-foreground-using-fore
foreTiles>
update-background-if-needed
backTiles>
inkey handle-keypresses
gameOver @ until
Because the background is restored on every loop the engine automatically provides flicker-free updates for your moving foreground images without you having to keep track of where the graphics had been and this works even if your foreground objects overlap. The definition xy@ provides some basic support for object detection.
The engine provides some niceties for scrolling games which you'd use as:
backTiles> tilesUp or tilesDown or backTiles> tilesLeft or backTiles> tilesRight . Scrolling and copying uses cmove for the updates which currently operates at roughly 19Kb/s for SRAM to SRAM copies and 31Kb/s for SRAM to internal RAM copies. Therefore foreTiles> takes 19.5ms and backTiles> 31ms => 50ms or 1/20th of a second = a maximum of 20 frames per second which is certainly playable though not stunningly fast. Scrolling up takes 19ms, left and right 21ms and down takes 39ms. Thus a horizontal scrolling game would take at least 71ms, and run at no more than 14 fps. By comparison, a Jupiter ace could shift RAM at over 150Kb/s (in machine code). Future firmware updates will improve cmove and fill by at least 5x. ( relative loader. |
: p@ dup 0< if @ else
|
: ovc@ over c@ ;
|
( block 8: 54)
|
Forth is powerful enough to support a debugger written in Forth itself! To use the debugger you'd type debug CommandToDebug and it must be a command you've written. The display shows the Debugging stack, followed by the Data stack, then the return stack and finally the memory stack. Pressing SW3 (Right) steps over each command; SW1 (Left) allows you to see the run screen; SW6 (Down) Steps into a definition and SW8 (Enter) Continues until the next breakpoint (which usually means it steps out of a definition).
The debugger can be downloaded from this page and includes both the .fth file and the .hex files created by T2E2. It occupies about 1.5Kb.
Mini Car Race
How small can a FIGnition game be? This extremely crude game, minirace is only 338 bytes long.5 var seed : rnd ( range -- random ) seed @ 1+ 75 *
dup seed ! u* swap drop ;( the graphic image for the
car)
hex 0BAFE var udg 0BA38 , 28AA , 0FE82 , decimal( copy it to
the UDG area)udg vram 608 + 8 cmove( pause for delay/50ths
of a second): pause ( delay -- ) clock i@ + begin
dup clock i@ -
0< until drop;
|
( choose the start position for the
next bit of road): nupath 3 rnd 1- + 0 max 20 min;0
var hit( has the car hit the side of the road?): hit?
2dup 25 * + vram + ic@ hit !
;
|
( create the next strip of road): path 24 23 at cr
25 0 do i over < over 4 + i < or 128 and 32 +
emit loop;( handle moving the car, left or right):
mv
swap dup inkey dup 8 = swap 9 = 1 and + + 0 max 24 min dup
12 hit? at 1 emit swap 11 at 32 emit swap;
|
0 var sc(
update the score): score 1 sc +! 0 0 at sc @ . ;( put
everything together)
: race cls 12 10 0 sc ! begin nupath path mv score
>r over pause r> hit @ 32 = 0= until;
|
Minirace is simple, but it has all the elements of a game you need. It supports different speeds down to -1; it correctly handles the movement of the road so that it stays within bounds; it supports a UDG; it handles user input via inkey; it manages collision detection and a running score. The minirace.zip (which includes the .fth and .hex files) is in the attachments.
Chess
Here's the beginning of a simple chess game, a 2 player version. It generates a set of chess pieces; draws a board and allows 2 players to move pieces around the board.
( chessica )
|
( board layout) |
( gen piece movement)
|
( ui ) |
To use it, load the program and then type
chessica. Type a pair of coordinates e.g. if you want to move F1 to F5 (which is technically illegal as a first move) then type F, then 1, then <space>, then F then 5, then <enter> . Typing a letter or digit always changes the value in the correct column, <space> always switches between the from and to coordinates.As you can see, it's pretty simple and only requires 614b, including graphics. It doesn't check for any illegal moves, nor can the computer play chess itself - that's for the future. 2Kb chess anyone ;-) ?
Brikky
Brikky is a simple breakout clone in only 8 pages of code, it uses the Audio Mod that's recently been developed to provide sound!
hex 0FF80 var udgs |
: emits 0 do dup emit
|
: 40sfx 40 sfx ;( ox oy nx ny): hitSides? >r dup maxBall 1 - > if dup maxBall - - dx bounce 40sfx else dup 2 < if dup 1 - - 40sfx dx bounce then then r> dup 3 < if dup 2 - - dy bounce 40sfx then ;: hitFloor? ( o:xy n:xy) dup baty > if 1- loselife playstate ! then ;( ox oy nx ny): hitBrick? 2dup vpos ic@ ( .. c ) dup 0 > over 9 < and if score over 1- 1 or over at space space bricks @ 1- dup 0= if newround playstate ! then bricks ! dy bounce else drop then ;: hitBat? ( ox oy nx ny) over batx @ - 4 u< over baty 1- = and if over batx @ - 2 - dup 0< + 1+ dx ! dy bounce 45 sfx then ;
|
: ball x @ y @ over dx @ |
I also created a simple YouTube video for it (note: there's some strobing effects on it!).
Mazes!
I took Compute Magazine's! Maze generator and converted it for FIGnition. It's just a little 3 screen program.
128 const wall
|
513 var dxy 258 , |
: init
|
|
FIGnition can generate 23x21 mazes in about 3s per maze which isn't
bad.
With a little bit of 3d code we could generate a program for you
to walk through a 3D maze, rather like the Jupiter Ace version.
How does it work? Well it's a pretty simple brute-force random maze generator. We have the maze array which stores 128's in empty
locations and any other value represents a filled location in the
maze. At each step it just picks a random direction to go in and if
it's empty it calculates the new location and stores the direction in
the new location in the maze. If it was filled it tries for the next
direction in a clockwise sequence and if it gets back to the first
direction it backtracks.
Backtracking is pretty simply too: by storing the direction in each
new location we visit, it provides backtracking information, so to
backtrack we simply retrieve the direction in the current maze
location and move in the opposite direction (by subtracting rather
than adding its displacement). Eventually we'll fill the entire screen
with maze walls.
To set it up, we need to make sure the outer edges of the maze are
no-go areas, which we do by filling them with 0s (not empty).
The clever part about FIGnition's algorithm is that we don't need
special checks for when we backtrack to the start, instead we simply
define the direction at our starting position (which is (1,1) ) as 1
(=right) and then set maze[0] to empty. Then when we backtrack from
(1,1) we move left to (0,1) and do a search. The algorithm finds that
to the right it's filled, as is the bottom, as is the left (which
corresponds to the top, right corner of the maze). But then it finds
that (0,0) is empty so it moves there and since the terminating
condition happens before the move is displayed, the maze generation
stops.
The maze is plotted as though walls were the same width as the paths,
but in fact since the walls aren't used in the maze calculation they
can be replaced with any width walls, which is why a conversion to 3D
is pretty trivial (you can convince yourself of this by considering
the fact that all the walls are on even coordinates; which means you
could change their thicknesses without changing the maze itself).
( Alternative commands for a thin walled maze )
|
( also ) |
( and finally)
|
|
Maze generation is good for chase games, e.g. the classic ZX81 3D
Monster Maze. The algorithm here only uses 1030b including 575b for
the maze array itself. Here's the code itself: (wall would be better
termed as 'empty').
As you can see, it's pretty simple and only requires 614b, including graphics. It doesn't check for any illegal moves, nor can the computer play chess itself - that's for the future, 2Kb chess anyone ;-) ?














