It's easy to add new definitions to FIGnition's set of commands. Here's some common ones.
We can define a simple 16-bit pseudo-random number generator as follows:
5 var seed
: rnd ( range -- random )
seed @ 1+ 75 * dup seed
! u* swap drop
generates a number in the range 0 to n -1. For example,
: dice 10 0 do 6 rnd 1+ . loop ;
will throw a die 10 times.
You can enter constant data values by defining this command:
: cdata <builds does> ;
cdata days/months 31 c, 28 c, 31 c, 30 c, 31 c, 30 c, 31 c, 31 c, 30 c, 31 c, 30 c, 31 c,
defines a list of days/month, so days/month 9 + c@ would give you the number of days in October ( 10-1) and days/month 1 + c@ would give you the number of days in February.
: data <builds does> over + + ;
Does the same for 16-bit values. E.g.
data pins 1234 , 9999 , 7777 , 2011 , 2010 , 2009 ,
defines a list of the most ingenious pin numbers people have devised for cash machines to date.
Arrays are a simple data structure, like a single column in a spreadsheet. We can define a simple array type with:
: var <builds dup +
allot does> over + + ;
Then use it to create arrays e.g.
54 var cards
And access each row with e.g.
5 cards @
to obtain the 6th row,
312 10 cards !
to store 312 in the 11th row etc.
Although simple, arrays are probably the most fundamental concept in data structures, because they allow you to access data based on a calculation rather than explicitly by name. For example, this bit of code:
53 0 do
53 i - 0 do
i cards @ i 1+ cards @
2dup < if swap then
i 1+ cards ! i cards !
Sorts the entire pack in about 1.5s.
FIGnition provides some user-definable graphics, handy for some simple games. Here's a simple bit of code to access the bit patterns:
8 * vram + 600 +
For example, 0 udg returns the address of the first UDG and 15 udg the address of the last one. We can define udg patterns quite easily:
2 base !
00000000 c, ( invader)
This takes us into binary mode and then it's quite easy to type out the bit patterns. At the end you'll need to type
to go back to normal numbers. If you're really clever you could pre-calculate them all and just enter the decimal values, e.g.
60 c, 126 c, 254 c,
248 c, 254 c,
126 c, 60 c,
Which is a Pacman UDG. You can make it even more efficient by encoding the UDGs in hexadecimal. If you pair up the binary bit patterns and convert them to hex (by using .hex) the UDG is even more compact. We'd start by converting them:
0010100001111110 .hex 0x287E OK
And then repeat the process for rows 2..7 and finally define the UDG as:
hex 287E var udgs 0DBFF ,
99A5 , 0 , decimal
Would take only two lines - but it's not as easy to read that way!
: initUdgs ( src dst num -- )
8 * cmove
Will copy the udg data to actual udgs and you'd use it e.g. as
udgs 0 udg 1 initUdgs
You can't easily display all the udgs,
will display the pacman, but
will display nothing, because emit treats some character codes (such as 9 and 13) in a special way. In later FIGnition firmware we can display the full set of characters by adding 256:
will display UDG 0. Or we could use
to store the values directly into video ram (the equivalent of poke if you were used to basic in the good-old days ;-) )
: xy@ ( x y -- VRamAddress)
25 * + vram +
0 12 8 xy@ ic!
would put the invader half-way across the top third of the screen - plenty of time to shoot him down ;-) !
12 8 at 256 emit
would do the same thing.
can also be used to read the screen, so you can test for collisions.
12 8 xy@ ic@
would now return 0, the character code of the invader!
Handling time is pretty easy in FIGnition Forth. A 50Hz clock is provided (60Hz for NTSC FIGnitions) and it's called clock.
It displays the number of 50ths of a second since FIGnition was turned on. Often you want to be able to do some processing for a given time period and this is simple to implement.
: setTimeout ( ticks -- timeout)
clock i@ +
: timeout ( timeout -- boolean)
clock i@ - 0<
For example, setTimeout and timeout can be used to create a simple pause command.
: pause ( ticks -- )
Which simply waits for the given number of ticks, e.g.
waits for 1s.
In theory, it's easy to access the internal peripherals on FIGnition. @TODO
For example, the eeprom can be read using the following simple routine:
dup 65 ic!
8 >> 66 ic! ( little-endian)
1 63 ic! ( start the eeprom read)
64 ic@ ( read the eeprom)
With Firmware 0.9.7 FIGnition has now become much better at handling text, through the editor which uses 'C'-style '\0' terminated strings.
So, here's how to do C's strlen, strcat, strcpy (or BASIC's MID$, LEFT$, RIGHT$, or Sinclair Basic x$([start] TO [end] powerful string slicing syntax).
Firstly it's often useful to be able to enter ascii characters, e.g. for emit or delimiters. This little command (which can also be found on a Jupiter-Ace):
32 word here 1+ c@
state @ if
44 c, c,
Converts an ascii character to its character code. You can use it directly, for example:
ascii $ . 36 OK
And you can use it inside another definition:
: x ascii B . ;
Which is the same as doing
: x 66 . ;
The new firmware implements:
"len ( str -- len)
which takes as input a zero-terminated string and returns its length. Thus we can copy a string with the command:
: "! ( str1 str2 -- ) over "len cmove ;
str "len +
returns the end of a string, thus:
: "+ ( str1 str2 -- ) "len + "! ;
Concatenates strings. Other string functions are similarly simple, truncating a string to n characters is:
: "cut ( str1 n --) + 0 swap c! ;
Note, this will work even if the original string was shorter than n chars.
A string starting at the nth character is: simply
str n +
(which is also a string). But since that won't work if n > the length of the string it's better to implement:
: "from ( str1 n -- str ) over "len min + ;
You can (with a bit of thought combine them):
str 5 "from 3 "cut str "!
(which will extract the 5th to 8th characters of str and copy it back to the string).
Displaying a zero-terminated string can be done with:
: ". ( str --) "len type ;
This is OK, but until now it was hard to actually input strings from FIGnition Forth, thus with games it was hard to have high scores and do other kinds of input, for example generating forms that involved text entry. Now it's EASY!!
Firstly, we can input strings directly into the terminal input buffer using query, since query now just uses the new editor. For example:
query tib @ 1+ "len .
Allows you to input some text and when you've finished (using Shift, Enter) it'll return the length.
Query is simple, you can easily use it to input text and copy it to your own strings:
0 var myStr 80 allot
query tib @ 1+ myStr "!
(assuming you've defined "!). Of course, you can treat the tib as a temp 79 char string pretty much as you like too from within your own code.
Although query is simple, it's a bit restrictive; text input is always at the bottom of the screen and is always copied to the 79 character tib. So, boxed has been defined which allows you to define a text edit box anywhere on the screen and input text into any string of your own:
0 var str2 20 allot
0 str2 ! ( init)
5 5 at ( set the origin for the text box)
str2 18 8 2 boxed ( buff maxLen width height --)
0 str2 !
is used to initialize the text and the text proper starts at
. If you don't initialize the text it'll use the previous text already in the string; thus you can use it to modify a string with user input as well as input new strings (which is better than BASIC generally allowed).
One caveat: edited text always starts at str 1+ , that's because the text editor uses '\0' to terminate strings at both ends - the paragraph searching algorithm expects this.
FIGnition Forth now also supports a primitive string searching function
cIn" ( dir buff ch -- buff' )
which searches for ch in the direction dir (-1 or +1) and returns the address where it was found, or where '\0' was found. "len is defined in terms of cIn", it's : "len 1 swap 0 cIn" ;
Constant strings can be entered into programs. For the moment, this command will work (it's more complex than I'd like it to be, because it has to convert from Forths traditional strings to zero-terminated strings) but in the near future,
will be built-in:
: ," 34 word here dup c@ dup >r 1+ allot dup 1+ swap r> 1+ cmove 0 here 1- c! ;
Allows you to enter constant strings by combining it with cdata. For example:
cdata myMsg1 ," Hello you!"
cdata myMsg2 ," Goodbye!"
cdata myMsg3 ," Buy a FIGnition for a friend!"
You can then combine it with data to create an array of references to messages:
data myMsgs myMsg1 , myMsg2 , myMsg3 ,
: rndMsg 3 rnd myMsgs ". ;