Extra Definitions

It's easy to add new definitions to FIGnition's set of commands. Here's some common ones.

Pseudo-Random Numbers

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        

;

n rnd 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.

Constant Data

You can enter constant data values by defining this command:

: cdata <builds does> ;

For example:

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.

Similarly:

: 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

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:

: sortCards

  53 0 do

    53 i - 0 do

      i cards @ i 1+ cards @

      2dup < if swap then

      i 1+ cards ! i cards !

    loop

  loop

;

Sorts the entire pack in about 1.5s.

Udgs

FIGnition provides some user-definable graphics, handy for some simple games. Here's a simple bit of code to access the bit patterns:

: udg

  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 !

cdata udgs

00101000 c,

01111110 c,

11011011 c,

11111111 c,

10011001 c,

10100101 c,

00000000 c,

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 decimal 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, 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, 1 emit will display the pacman, but 0 emit 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: 256 emit will display UDG 0. Or we could use ic! 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 +

;

So, 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.

xy@ 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!

Time

Handling time is pretty easy in FIGnition Forth. A 50Hz clock is provided (60Hz for NTSC FIGnitions) and it's called clock.

clock i@

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 -- )

  setTimeout

  begin

    dup timeout

  until drop

;

Which simply waits for the given number of ticks, e.g. 50 pause waits for 1s.

Internal Peripherals

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:

: ec@

  dup 65 ic!

  8 >> 66 ic! ( little-endian)

  1 63 ic! ( start the eeprom read)

  64 ic@ ( read the eeprom)

;

Text Processing

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):

: ascii

  32 word here 1+ c@

  state @ if

    44 c, c,

  then

; immediate

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 --)

The initial 0 str2 ! is used to initialize the text and the text proper starts at str2 1+ . 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 ". ;