Part 2. User's Guide

Learning Forth

What is Forth?

The Forth language was written in the 1970's by Charles Moore and Elizabeth Rather, as a radio telescope control language. Due to the varying nature and wide spread differences of the machines used to meet the task at hand, Moore and Rather chose to create an independent language that would; a) require a minimum number of machine routines to place itself upon any given hardware, b) have a minimum amount of type checking to allow access to variable system resources, c) use a bare-bones interface for both program generation and testing, and d) be easily transportable and extensible to interface with other systems or hardware variants.

The result of their effort is the language that we call Forth, the combination of a compiler and interpreter within the same package with a machine independent interface. The Forth style of programming and implementation mimics the operations of a standard computing device while allowing the programmer to maintain an intimate communication with the target hardware, creating a "virtual machine" on the target system for all normal operations while containing functions for access to the system's features. Fig-Forth v2 shares this duality between the virtual machine and its operating hardware, becoming a variant of the basic Forth language particularly when calling those functions specific to the hardware machine.

Forth is also a thread based language, with each thread executed by the virtual machine processor in rapid succession. As such there are two active Program Counters within the Forth Kernel; one for machine based code and one for the threaded instructions. Each of these counters are discussed elsewhere in this manual.

Starting Forth

Fig-Forth v2 is executed from the MS-DOS command line in the same manner as any application for that system, or from a shortcut or program manager icon within the appropriate version of Windows for DOS applications. The package will make several tests of the operating hardware and software, then announce its version, date and status. For turn-key operation Fig-Forth will first attempt to execute any application code vectored through the deferred word of START, then will echo the tail of the command line to the compiler in an attempt to interpret commands following the executable's name. After processing the optional components Fig-Forth will drop to the command line interpreter unless the application code prevents it, where the package may be directed from the system console.

At this and most other times the compiler will be in the Execution or Run Mode of operation, attempting to carry out the commands as given it from the system keyboard. As stated in other sections these commands may or may not engage the compiler action of Fig-Forth; building, assigning or destroying subroutines located within the user space. Unless previously extended and saved in the dictionary, only the core words needed by the compiler and virtual machine, or those words protected by the FENCE value, will be actively searched. No files will be opened or defined in this event, and the mouse, modem and sound functions shall not be enabled.

For those unfamiliar with Forth the first function that should be performed is the VLIST operation, which will list the contents of the current vocabulary. Unless other modules have been saved within the executable this listing shall comprise the Forth Language vocabulary first, followed by the ROOT vocabulary. More will be explained about each as we move on. Simply type the word VLIST and follow it with the Enter key, and Fig-Forth will print out the first page of the functions in the search order. Pressing any key besides Enter will list another page of this dictionary, and so on until the dictionary is exhausted.

The first thing the beginner will notice is that some words are printed in Red ink, while all others are printed in the default color of Yellow. Those Red words listed are the operations that call upon the system compiler, or require the activation of the compiler in order to be used. All other words are operations that can be run by the interpreter, with the exception of those words surrounded by parentheses or brackets. Words surrounded by these characters should only be used in the manner shown by example, and used with care unless explicitly documented.

System Self-Destruction

Forth has traditionally been a very unforgiving language, and Fig-Forth v2 is no exception to this rule. As such there is no safety net by which the user is protected from doing the impossible or not recommended for system integrity. Forth makes the assumption that you are aware of all the results of your suggested actions to the compiler, mimicking the same activity as might be taken within a machine language assembler. Therefore, the user of Fig-Forth must expect and deal with the complete lock up or failure of the system in the event that a catastrophic error has occurred, and must undertake the necessary corrective actions within their source code. Review of any operation and their parameters is recommended before attempting any word function.

Forth Statements

The Forth language is a very different method of defining a program to the machine hardware, one which mimics the way the hardware really functions. This makes Forth resemble assembly language or operational machine code, where addresses, functions and data are intermixed in a structured though fluid style. In addition, Forth is also a single pass compiler operation, taking a bottom up approach to all program operations. This means that subroutines are defined before the inclusion of higher level functions, and that higher level functions are defined before the main program loop. This same approach is applied to 98% of all operations within Fig-Forth, such that any parameters must be placed upon the stack before an operation is called.

Because of the above points Forth appears much more cryptic to the naked, untrained eye than other languages; where data, tests and addresses come before the function call itself. To make this clear let's take a look at a typical system call, one you might find in a C environment;

          char name[256]; -- global variables

          HINSTANCE handle;

          int paddr;

          void get_proc() { -- the process

                  paddr = (int) GetProcAddress(handle, name)

               }

In Forth code this same function would appear as;

          256 array name

          0 variable handle

          0 variable paddr

          : get_proc name handle @ GetProcAddress paddr ! ;

Thus it is not unusual that Forth programs can be understood at the time of writing the source text, but perhaps not some weeks or months after the programmer has moved on to other concerns. This factor grows exponentially with the size of the project involved, and the time that has elapsed since the last review. Also, the above definition cannot be used for more than one value of the term handle above, without changing the contents of the variable itself. (E.g.; as in a function call.) To counter these conditions Forth utilizes a Parameter Stack, in which data may be passed from one function to another. Such a sample of the same function using this method is listed below;

          : get_proc ( name handle -- paddr ) GetProcAddress ;

While this definition "guts" the former line it serves as the equivalent "int get_proc(handle, name)" function as would be written in C, while at the same time describing the inputs and outputs the function requires. It is also common to use such parenthesized text to describe the function in common language, such as;

          : get_proc ( name handle -- paddr ) GetProcAddress ;

               ( get process address )

Note the space between the parenthesis and the rest of the text, it is an essential part of Forth programming. Forth separates words by spaces when not looking for a quotation mark, comment end or specialized entry, so the space must appear between the punctuation. The same holds true of most function names and operations, so it is common to balance such spaces as shown above.

Look closely at the line with the colon and semicolon. This is the function itself. The colon character instructs the compiler to build a new function thread inside the vocabulary, using the next space delimited word as the name to call the operation. When the parenthesis is encountered by the compiler it temporarily suspends operation, parsing over the input stream until a closing parenthesis is found. Note that comments such as these cannot be nested.

Now, the text within the parentheses is read from left to right, giving the order of the inputs required. The double or single hyphen represents the function in operation, while the final values represent the output if any. Note that in Forth there is no "stack balancing" that occurs in calling any function, although application programs may selectively discard or preserve the stack contents. Such an operation would be as shown below;

          : BIOS_10 ( MODE -- F ) 0 0 0 4 ROLL &10 INT# 4NIP ;

In this function a single parameter is required by the operation; in this case calling the BIOS ROM to (we assume) change video modes. Since this is a raw call to the PC Interrupt itself, 5 values are required before the interrupt call, and 5 values are returned again. (dx, cx, bx, ax and Interrupt # for input; dx, cx, bx, ax, and carry flag for output.) This example places a zero for the other registers on the stack and pulls the value of mode to the top, (ax) then calls the function and removes all returned values except for the carry state. (See other sections in this manual for the explanation of each word.) This is the only amount of stack balancing required by Forth.

Using the Command Interpreter

When Fig-Forth is started the program begins operation in the Execution mode, where command words are run in the order in which they are received. These commands may range from simple arithmetic to disk access or compiler functions, all entered from the keyboard for the virtual machine to process. Pressing Enter after the compiler starts will make the screen look as follows;

          80386 fig-FORTH v2.32

          Implemented 04/09/03 by MB

          _OK.

All this means is that the program has received the input and you didn't give any commands, so it prints the "OK." to tell you it's finished processing your input. If you entered a number however you might see the following, depending on what you entered;

          80386 fig-FORTH v2.32

          Implemented 04/09/03 by MB

          123 1K.

or;

          80386 fig-FORTH v2.32

          Implemented 04/09/03 by MB

          12.3 2K.

This display demonstrates the "tattle-tale" QUIT process imbedded inside Fig-Forth v2, which constantly reports the number of saved values located on the system stack. Thus the "1K." up above means the 123 value entered was successfully converted into an integer, taking up one location on the stack to await further processing. The decimal number however was converted into a double integer, resulting in the "2K." that represents a forth double number. Thus the "OK" sign you receive will always show the number of stack values, if any exist at all. Note however that the decimal value is not saved as a decimal, but as a Traveling Point or Non-Point integer discussed in Chapter 2 of The Programmer's Reference. This means that using the appropriate print words for each value will result in the following;

          1K.

          . 123 OK.

 or;

          2K.

          D. 123 OK.

In addition, the decimal point following the "OK" will appear only when the number base is 10, otherwise it is omitted;

          80386 fig-FORTH v2.32

          Implemented 04/09/03 by MB

          HEX OK

          DECIMAL OK.

For those using the multi-tasking version of the package this prompt is extended, including the thread number of the interpreter you are speaking to;

          OK.0 -- task zero, decimal mode, empty stack

          5K3 -- task three, non-decimal mode, five stack items

And so on. This display will always appear when the compiler is expecting input from the keyboard, showing the results of the last command and the current stack, number base and thread.

When the command process is running characters entered from the keyboard are stored into a Text Input Buffer, (TIB) which are interpreted upon the receipt of a carriage return or if the buffer becomes full. The buffer is defined as 256 characters in length, allowing approximately four lines of on screen input before the interpretation process takes place. The sole editing character allowed is that of ASCII Backspace, which is substituted for ASCII Delete internally by Fig-Forth. When at the start of the TIB and a delete character is entered, the console will beep in text modes or a Bell character will be printed in graphical modes.

The Stack

This section is provided in the event the user is unfamiliar with stack oriented programming, though by no means will make you an expert. All stacks operate in essentially the same way; as a last in, first out data buffer. For the purposes of Fig-Forth v2 and the IBM system hardware, this data buffer is organized as an Nth collection of 16 bit words. The stack is maintained by the automated functions controlled by a CPU register, (or two in this case, because Forth uses two stacks,) and occupies the upper regions of the user dictionary space. Data on this stack is freely intermixed in differing combinations of the 16 bit values, which may or may not contain control flags to indicate their type and size.

For the programmer of any Forth system the Parameter Stack is of primary importance, because all functions that require or generate data will pass the values through this buffer. The buffer itself can be viewed as a pile of cards or sheets of paper set aside for later use, in which when an entry is created it goes on top of any already in the stack.

values before adding 5 values after adding 5
25 5
14 25
7 14
7

In the reverse action, it is the top value on the stack that is used first by any function which requires data, followed by the next entry, and the next, and so on. The current data on the stack can be characters, integers, double numbers or larger quantities, and it is up to the programmer to insure that these values are in their proper order. See Chapter 4 of The Programmer's Reference for a list of the stack control words and operations.

Your First Program

To get you accustomed to Forth style we're going to start with a very simple game, using a text interface and very few options. The game is an implementation of BlackJack, which is well known and commonly understood by all. Since this game is so well understood we're going to start with some of the bookkeeping variables it requires, namely the Bet, the Computer's or Dealer's Hand, the User's Hand, and the Total of funds supposedly won or lost.

          0 VARIABLE HHAND -- human hand

          0 VARIABLE DHAND -- dealer's hand

          0 VARIABLE BET -- current bet

          0 VARIABLE TOTAL -- total wins or losses

Note that in Fig-Forth or Forth79 standards a starting value precedes the variable definition, a convention which was dropped in Forth83. (The assumption being that variables are set to starting values when the program is initialized anyway.) The word following the variable definition is the name of the variable itself, and upon execution of the symbol name Forth will return the address of the variable's contents. After we have such an address we can use the memory retrieve or store words of @ or ! to save values into the variables, following the same manner of reverse addressing common to Forth;

          HHAND @ -- get the contents of HHAND

          5 BET ! -- save 5 in BET

And so on...

Next, because this is a game we need a method to create "cards" that appear to be shuffled and pulled at random, hopefully bearing the same probability of occurrence that we'd find in real cards. Since a standard deck has 52 cards and 16 of those are worth 10 or higher, there is a 31% chance that any card drawn will be one of these 10-value items. So, our next function should be a random number generator which has an adjusted weight to this result, and the values that such a function requires;

          0 VARIABLE CARD -- the card drawn

          : DEAL ( -- N ) RND0 ABS -- fetch random number, positive only

               12 MOD 1+ -- divide by 12 and get remainder, add 1

               DUP 10 > -- make copy and see if it's greater then 10

               IF DROP 10 THEN -- if yes, make the value 10 only

               DUP CARD ! ; -- then put copy in CARD, return with value

This little routine will do nicely for our random generator, picking out values and turning them into the numbers of 1 to 10. Notice that the value is passed from operation to operation entirely on the parameter stack, with copies made at the time we are about to destroy it. (See STACKS.) A complete examination of this function is shown below;

word

stack

operation

: DEAL

empty

start of word function

RND0

n

fetch a random number from -32768 to +32767

ABS

[n]

get the absolute value of the random number

12

12 [n]

put 12 on the stack

MOD

n'

divide n by 12 and return remainder only

1+

n'+1

add one to the result

DUP

n'+1 n'+1

make a copy of the value for testing

10

10 n'+1 n'+1

place 10 on the stack

>

f n'+1

test if n'+1 is greater than 10, return truth flag

IF

n'+1

pull flag and execute words if true.

DROP

empty

if flag was true, discard value n'+1

10

10

if flag true, place 10 on stack

THEN

10 or n'+1

end of conditional section

DUP

10 10 or n'+1 n'+1

copy result thus far

CARD

a 10 10 or a n'+1 n'+1

place address of variable on stack

!

10 or n'+1

save copy into the variable

;

10 or n'+1

and return with value generated

Now, the next few functions are the win, lose and push strings used by the game, plus the obvious title and notices about converting an Ace to 11 and back again. The reason we're doing this now is because the actual method of converting Aces is a complex process, so it would be better to get the simple routines done first. First, the strings alone;

          : HELLO ." Welcome to Forth Blackjack. " CR ;

          : 1$ CR ." Ace converted to 11." CR ;

          : 2$ CR ." Ace converted to 1." CR ;

          : DRAW CR ." We have both busted!" CR ;

Each of these functions will print the string contained when they are called by later functions, along with a blank line or two as required by each notice. Now we enter the routines to manage wins or losses, in which case we'll take the two lose conditions first. As you'd expect these operations print the reason for the loss and then deduct the bet from the total, so may result in Forth code that looks like this;

          : LOSE ." Hand lost." CR BET @ TOTAL -! ;

          : PUSH ." Push! " CR BET @ TOTAL -! ;

Note that these two lines are in fact the same operation, that of subtracting the amount of the bet from the total while printing a string. Only the string itself is different within these two routines, meaning we could have written the operations as follows;

          : LOST CR BET @ TOTAL -! ;

          : LOSE ." Hand lost." LOST ;

          : PUSH ." Push! " LOST ;

This practice is known as "factoring" in Forth parlance, or the breaking apart of multiple definitions to share their common code portions. This is an extremely important aspect of Forth programming, in that it makes our source lines shorter and easier to comprehend once you know the lines that come before the one being examined. However, like anything else this can be taken to extreme measure, such that the code line which defines an operation may be many pages away from the function that calls it. So as a general guideline of such factoring it should not be performed on word lines that are 15 operations or less, and the factored operation should appear close to the routines that call it.

Next, we move on to the Win operation, in which the player is paid one and a half times their bet. This is done to balance out the fact the Dealer takes pushes or ties within the course of our game, and the code for this is shown below;

          : WIN ." Hand won." CR BET @ DUP TOTAL +! 2 / TOTAL +! ;

All these routines use the arithmetic variable operations, in that the value on the stack is added to or subtracted from the variable contents directly in memory, which save us having to retrieve both values and store the result. In addition, by copying the amount of the bet before adding to the total, we can divide the bet by two and add the half to the total as well. See Variables and Math.

Now we face the task of defining our Ace conversion routine, which is of course a series of decisions that must be made about the cards a player holds. Since the conversion of an Ace into 11 is also a reversible condition we'll need yet another variable, one that holds the memory of such a conversion being performed. For this operation we're going to take each player separately in making these choices, because this allows us to check our thinking and not be distracted by multiple stack values.

          0 VARIABLE HACE -- human had an Ace

          : ACE1 HACE @ -- has an Ace been held?

               IF 1 HACE -! -- yes, deduct the ace from the variable

               10 HHAND -! -- deduct the 10 advantage from the hand

               2$ THEN ; -- then print our string and exit.

This first routine is used to convert an 11 back into an Ace, after having checked that an Ace has been drawn. Because it is possible that more then one Ace can be found in any hand the HACE variable is decremented before the hand's total is adjusted, then we print the string "Ace converted to 1." Note that in this case we are taking advantage of Forth's unique False Flag, by simply retrieving the contents of HACE without testing it versus anything. This is because the IF statement will treat any value as True unless it happens to be a Zero, which is precisely what we need for this routine.

          : ACE2 CARD @ 1 = -- get card dealt, test for Ace

               IF 1 HACE +! -- mark Ace in variable

               10 HHAND +! -- assume Ace is 11, add advantage

               1$ THEN ; -- then print status and exit

This is the opposite function of the last routine, one which converts Aces into their higher value equivalent. In this case however we are examining the copy of the last card dealt to the human player, conveniently stored by the DEAL routine in the variable CARD. If the card drawn was an Ace we make note of it in the HACE variable, then add the 10 offset which makes a potential BlackJack.

          : ACEH1 HHAND @ 21 > -- is hand over 21?

               IF ACE1 ELSE -- yes, try converting an Ace back

               ACE2 THEN -- no, try converting an Ace up

               0 CARD ! ; -- then zero the card so we can try again.

Finally, this routine will convert an Ace into an 11 if the player's hand is less than 21, or do the reverse if the hand total is greater than 21. In addition, it resets the last card dealt variable after performing its function, so that ACE2 will not find an Ace on a second attempt. Because of this "cycling" method of testing the hand before Ace conversion we must make the same test a second time to be certain the hand total is at the greatest value not over 21, which is accomplished by the last routine below;

          : ACEH ACEH1 ACEH1 ; -- convert human aces.

Now our player will receive the greatest advantage out of the cards they are dealt, and we do the same process for the dealer as well;

          0 VARIABLE DACE

          : ACE3 DACE @ 0 > IF 1 DACE -! -10 DHAND +! 2$ THEN ;

          : ACE4 CARD @ 1 = IF 1 DACE +! 10 DHAND +! 1$ THEN ;

          : ACED1 DHAND @ 21 > IF ACE3 ELSE ACE4 THEN 0 CARD ! ;

          : ACED ACED1 ACED1 ;

Next we get to the meat and potatoes of the entire game; determining who wins. This is a complex process of comparisons against the limit of 21 and of both hands which must be taken one step at a time. However, since we are forced to write this process in the reverse order it is generally easier to check our thinking first, by starting with the most obvious result;

          : COMP HHAND @ 21 > IF COMP1 ELSE COMP2 THEN ;

This line tests to see if the player has busted and then calls two different routines in that event, for it is equally possible that the dealer has also gone over the limit. Note that if the human hand is precisely equal to 21 the "greater than" condition will not be met, while if 22 it will. So, our next test is to see if the dealer has busted, and to declare the outcome if this was the case;

          : COMP1 DHAND @ 21 > IF DRAW ELSE LOSE THEN ;

This routine manages the event of the dealer and player both going over 21 (a draw) or the player busting and the dealer staying under the limit. (lose) Since this routine was called only because the player has gone over, all we have to check is the dealer busting as well.

          : COMP2 DHAND @ 21 > IF WIN ELSE COMP3 THEN ;

This next routine faces the opposite condition, where the player has remained under 22 and now the dealer's hand must be checked. As above if the dealer has busted the routine after IF is called, though in this case it's a player win for they've beaten the dealer. And finally, this routine calls the next step in our process, where we must compare the hands themselves to determine the winner;

          : COMP3 DHAND @ HHAND @ > IF LOSE ELSE COMP4 THEN ;

Here we are testing for the dealer's hand being greater than the player's, then declaring a loss if this is the case. As a last test we are going to give the dealer a little more advantage, by accepting that if the two hands are equal the dealer takes the bet;

          : COMP4 DHAND @ HHAND @ = IF PUSH ELSE WIN THEN ;

Now our entire comparison routine has been fully thought out, starting from the top and working down to the lowest level. Unfortunately, this is not the way these routines can be defined in Forth because of the bottom-up approach, so we must take these lines and reverse their order, giving us the listing shown below;

          : COMP4 DHAND @ HHAND @ = IF PUSH ELSE WIN THEN ;

          : COMP3 DHAND @ HHAND @ > IF LOSE ELSE COMP4 THEN ;

          : COMP2 DHAND @ 21 > IF WIN ELSE COMP3 THEN ;

          : COMP1 DHAND @ 21 > IF DRAW ELSE LOSE THEN ;

          : COMP HHAND @ 21 > IF COMP1 ELSE COMP2 THEN ;

These lines we can enter into the compiler, and our entire win/loss system is complete.

Our next task is that of building the human interface, or how is the user going to tell the game to give them another card or stop doing so. Because this is a small game we're not going to spend a great deal of effort in this section, but are going to take advantage of the Forth input processor. We'll cover more on the input processor in a moment, but first, we need the command words themselves;

          : HIT CR DEAL DUP -- call random and make copy of the card

               ." Card drawn " . -- tell the user which card it is.

               HHAND +! ACEH -- add to the player's had and do aces.

               ." Total=" HHAND ? CR ; -- and tell player their total.

This routine manages the process of dealing cards to the player, automatically converting aces and reporting the result. Now we need a similar routine to manage the dealer's hand, though in this case it's a little more complex. Why? Because in addition to displaying what card the dealer already has the player has elected to stop drawing cards, so it is up to the dealer to attempt to meet or beat the player's score. For the purposes of this version of the game we tell the dealer to draw a single card when the round is beginning and then another when the player stops, which saves us having to "split" the dealer's hand into two variables.

          : SHOW CR ." Dealer holds " -- print the dealer's hole card

               DEAL DUP . DHAND +! -- even though we deal it now

               ACED ." Total=" -- adjust aces and print total

               DHAND ? CR ; -- and show the contents of the hand

This routine will replace the practice of giving the dealer two cards when the hand is begun, by forcing the dealer to take the next card and display it as though it was the first draw. Now we get to the point of the Dealer drawing more cards to beat the human hand, following the rules of El Monte;

          0 VARIABLE TESTV -- a status variable

          : STAND SHOW BEGIN -- show hand an begin a loop

               DHAND @ 16 < WHILE -- while hand is less than 17

               DEAL DUP ." Dealer draws " -- draw a new card

               . DHAND +! ACED ." Total=" CR -- adjust aces, add to hand and display

               REPEAT 1 TESTV ! ; -- then exit loop and set status value

For this routine we're using one of the more complex loops available in Fig-Forth, one which will continue drawing cards until the dealer's hand is 17 or greater. A special condition flag is also used by this process, to indicate that the dealer has made his selection. This will become more clear later, but we have a second use of this flag as show below;

          : STOP 2 TESTV ! ; -- tell game to exit

This short routine allows us to change the status variable to indicate an end of the game, which will become important in our main loop later on. Next, we need a way to call upon the input processor to get the user input, so we create a function to handle this;

          : LINE ." ? " QUERY -- print ? and get a keyboard line

               INTERPRET CR ; -- interpret the line and print a CR.

This is one of the many ways to handle user input while in Fig-Forth, one that makes a few assumptions about the input content. The first assumption is that the text is to be placed in the Text Input Buffer of Fig-Forth, and that this input will not be longer than 254 characters. This is managed by the word QUERY which requires no parameters, and will allow simple editing to take place during the input. The second assumption is that the current contents of the Text Input Buffer is something we can safely destroy without causing errors, something which is not always safe but workable in this context. And finally, the last assumption made in this process is that the line entered will be comprised of words or letters we can type into Forth directly, using the execution mode of the compiler.

For clarification of that last assumption let us look at a typical input line, say 4 5 + HHAND ! This line would be processed by the INTERPRET word just like it would be on the Forth command line, creating the values stated and saving the result directly in our variable. This also means that a knowledgeable user can "cheat" at the game we've just created, or even call upon functions outside the game itself. While there are ways around these items, we'll only address a single one of them later in the program.

Now that we have LINE defined we can get our first user input, that of getting the player's responses during the game. Because we are using the input processor all this function has to do is ask the user for the word of the operation they wish to perform, then test the loop status variable defined before;

          : PLAY1 BEGIN -- start the input loop

               ." Hit, Stand or Stop" -- Print the user prompt

               LINE TESTV @ 0 > -- get a line and test the status value

               UNTIL ; -- exit the loop if told to stand or stop.

This is rather handy because the user will most likely follow the prompt line by typing "hit", "stand" or "stop" as they desire. Because the INTERPRET word is part of the standard input handler the words typed by the user are converted to uppercase if the CASELOCK variable is zero, so the user doesn't have to match the routine name exactly. Note that the INTERPRET word also allows the player to exit the game by calling the words QUIT or BYE, as well as allowing them to crash the compiler, input a bad number, and so on. In the event of an error such as a bad number or poorly spelled word the whole game will exit, which for this example we are not going to correct.

Now we move on to the get bet function, which occurs only at the start of the game. Note that this routine also disallows that a negative bet may be made, just to keep the player honest or at least attempt to do so;

          : GETB BEGIN CR -- start the input loop, carriage return

               ." What is your bet (10 min)" -- print input prompt line.

               LINE DUP BET ! -- get value, save copy in bet

               9 > UNTIL ; -- make sure answer is larger the 9 and exit

Now comes the main routine that starts the play process, which sets up all the variables to their proper values and deals out the first cards. This forms a lengthy definition but fairly easy to comprehend, so we may or may not desire to factor it;

          : SETUP 0 TESTV ! 0 DACE ! 0 HACE ! -- zero everything

               DEAL DUP DHAND ! ACED -- deal first card to dealer

               ." Dealer showing " . CR -- show card to player

               DEAL DUP HHAND ! -- deal next card to player

               ." First card " . ACEH -- display it and convert any Ace

               DEAL DUP HHAND +! ACEH -- deal third card and adjust Ace

               ." Top card " . ." Total= " -- show card and prepare for total

               HHAND ? CR ; -- and show total to user

At long last we get to the final play process, which sets up the game and begins execution. Note the second use of the status variable to exit this loop, so the user can play hand after hand until they wish to stop.

          : PLAY CLS HELLO -- clear the screen and say hello

               GETB BEGIN CR -- get bet and start main loop

               CR SETUP PLAY1 -- set-up hand, then play it.

               TESTV @ 2 < IF -- if status is 1, hand is over

               COMP THEN -- find winner and display

               TOTAL @ -- get total

               ." Total of bets " . -- print total won or lost

               CR TESTV @ 2 = -- if status is 2, user wants to quit

               UNTIL ; -- then exit the main loop

Now all we have to do is type PLAY on the command line and the game will proceed, until we get tired enough to tell it to stop. To expand this program we could include the options of Insurance, Double-Down or Split Hands, or even more players as we consider appropriate.

The Block Editor

Obviously, for any program beyond the limits of the BlackJack game there should be a way to avoid entering each line one at a time. In addition, it would be nice to have this text contained in a file that we could edit as we desire, because Fig-Forth's input processor is limited and needs some assistance. However, Fig-Forth uses a block of data 1024 bytes long for each request from the file system, which does not require allocating memory or other resources during the process. These types of files are called Block Screens, or 16 lines of 64 characters without carriage return and line feed symbols or other terminators. This access type is simply a "long line" input to the Forth compiler and does not rely on ASCII or MS-DOS recognized controls for input, even though the file itself consists of ASCII characters. This type of file requires a special program to manipulate the text.

Such an editor function is included with the Fig-Forth package, the source of which is in the file named EDIT.4TH of the main directory. In addition to displaying and manipulating the target file, this editor also manages the blocking and de-blocking of program source lines without the terminators normally carried by DOS. It is recommended that the editor be loaded immediately upon starting a Forth session of program development, though the editor itself is not required in the final executable. A typical command line to cause the editor to load is as follows;

          1 GO EDIT.4TH

This line may also be included in any batch file which starts the Forth package, or typed from the command line as shown below;

          C:\> F80 1 GO EDIT.4TH

For those who may have earlier renditions of this package the Editor has gone through quite a few changes recently, several functions of which operate differently depending on the mode selected, and from previous versions of the package. The new package can perform all of the functions of the prior version plus many new operations, so the screen display has been altered significantly to identify the new program. A help menu is also provided, as is this text.

Starting an Edit

The Advanced Block Editor program is constructed as part of the ROOT vocabulary when it is loaded, comprising a word list that is hidden from the user and 3 entry points within the default search path. Each of these entry points may be typed at the keyboard to begin the editing function, providing a file has been opened for that purpose. To open a file use a line similar to the one below;

          20 OPEN MYTASK.4TH OK.

Please note that the file must already be present on the disk drive, or an error will be generated. In the event that a new file is required Fig-Forth can call the DOS shell to create it, as in the line which follows;

          CMD" COPY OLDFILE.EXT NEWFILE.4TH" OK.

This line will call DOS and make a copy of an old file that exists in the Forth directory, calling it by the new name. Then you can open the new file as shown in the above example, where Forth will assign it an address location and read the current length for subsequent review. Once the file is opened the editor can be started in a similar manner, giving the command of;

          20 EDIT

At this time the screen will be forced to a text mode if not currently in it, all text will be erased and the editor screen will appear;

Scr# 20 MYTASK.4TH
----------------------------------------------------------
                                                          |          cuts=0
                                                          |
                                                          |
            ( repeated 12 more times )                    |
                                                          |
                                                          |
----------------------------------------------------------
                                                          |     0
                                                          |
                                                          |
Press F1 for help....

The upper window in the above diagram is the text area contained in the file, while the lower window is a "wrap buffer" composed of 4 lines explained below. By convention, the first line of any block is intended to contain a Forth comment, typically the name of the task, the date of writing, the author's initials and a comment about what the block contains. The other 15 lines are used for source text.

One of the first discoveries you may make is that the editor unilaterally prints all ASCII values into the text area, even those that make up carriage return, line feed, backspace and others. In doing this the editor allows that files of other types may be edited as well as source screens, and text files may be edited to form source blocks. In addition, this allows the input of characters not available from the keyboard, such as foreign symbols and graphic characters. (See special editor functions below.)

Other Editor Starts

For the purposes of debugging a compilation the Editor has two additional entry points, the VIEW and REDIT words. REDIT is used to return to the last block viewed or edited by the user, while VIEW is used to point out the location of where an error occurred. For example;

        20 LOAD THEN? Check Pairs! 2K.

The above line and result indicates an error has occurred within the compiling process, in this case a THEN that is not properly nested with an IF word. To find this error we have two options available to us, the first of which is to call for a words listing using VLIST. The definition where the error occurred will be listed in violet ink, wherein we can start the Editor and hunt through the file for it. However, Fig-Forth has provided us with two values on the stack, the block number and location where the error was detected. Typing VIEW will then start the Editor and show us precisely where the compiler located the fault, which saves time.

In addition to the above entry points the Editor file also contains two shortcut routines, GG and HH. These are designed to make editing multiple files easier. The GG word takes the location of two block files then starts the editor, swapping between the two files by pressing <Esc> twice. HH does the same function but switches between 3 block files, or any 3 locations within a single file. Both HH and GG use values on the stack for this switching operation, and will remember the values as changed while editing. To exit either of these functions press <Esc> and <Enter>, which forms an Editor exit command and an end of edit signal to these words.

         20 25 GG  -- edit two locations in a file

Editor Navigation

The Editor has a number of keys that navigate over the source file, of which the keyboard arrows are an obvious collection. Page Up will move from the current screen to a lower numbered one, while Page Down will move to the next higher. Note that if any screen is changed it will be updated internally, causing Forth to write the block to disk the next time it attempts to use that buffer. Thus in the event of accidental change the editor should be exited immediately by pressing <Esc>, then the word EMPTY-BUFFERS should be executed. As Fig-Forth currently contains 4 disk buffers in the user space, this must be done within 3 page-up or page-down operations.

The keyboard End key functions as one would expect, moving the cursor to the end of the current line or to the far right of the display area. Home performs the opposite function of End, moving the cursor to the start of the current line. If Home is pressed while the cursor is already in the zero column, the cursor is moved to the top of the screen block.

Control Left Arrow and Control Right Arrow are used to skip from word to word on the current screen, in the direction of the arrow shown on the key top. This function will catch any non-space character even if it has no apparent screen image, such as the graphic block character which turns all pixels off.

Finally, the Editor contains a single bookmark and Home function, which can be used to return to any screen within the current file. Pressing Alt-M will mark the location to be saved and Alt-G will return to it, without passing through the screens contained in the interim. Pressing Alt-H is the file Home key, which moves to the first screen within the current file being edited. Note this function does not span files, simply returning to the first block in the current file of focus. (See also HH and GG above.)

Editor Modes

The editor program operates in one of three distinct modes, as displayed above the count of cuts made to the Line Buffer. The default mode is the Over-Write function, which accepts characters from the keyboard and replaces those on the Block with the input. This mode is signaled by having no notice above the "cuts=" entry, and does not use the Wrap Buffers. Character deletes will roll characters right of the cursor to the left, for the current screen line where the cursor appears.

The second edit mode is that of Insert, in which keyboarded text is inserted at the location of the cursor. All characters to the right of the cursor will be rolled towards the right edge, while any characters reaching the far right will be lost on the next roll. As above, character deletes will roll characters right of the cursor to the left on the current line.

The final edit mode is that of Wrap, which is a super-extended version of the Insert mode. As before characters will be inserted into the text at the point of the cursor location, but in Wrap mode the entire block of data will be rolled down one location for each keystroke. This mode does use the Wrap Buffer located at the bottom of the screen, as characters reaching the end of the last text line are transferred into the hold space. This same extended roll applies to character deletes, giving the work space an additional four lines for text editing.

The Wrap Mode also updates the number displayed beside the wrap window, which indicates how many characters are saved in the buffer. This value is used by the wrap command defined under the Alt-W key sequence described below, as well as any insert or delete operation that is performed. Note that characters from the text window will not be transferred to the wrap buffer unless the following conditions are valid;

        1) The Editor is operating in Wrap Mode and,

        2) the Wrap Buffer contains valid characters as indicated by the value,

        3) or the 15th line of the Text Window contains non-blank characters.

Special Functions

For all of the various modes the Editor contains several function keys, similar to the Alt-M, Alt-G and Alt-H functions listed above. Some of these functions change their definitions according to the edit mode selected.

<Enter>

In all modes this keystroke will break the current line at the location of the cursor, rolling any remaining characters down one line and placing the text to the right of the cursor in column zero. If the mode selected is over-write or insert lines reaching the bottom of the text area are lost, while in wrap mode they are transferred to the wrap buffer.

Function F1

This brings up the editor help menu, replacing the current text area for review. Any key pressed after this display will return to the editing function.

Function Alt-F1

This key combination will will scan the current file for the word symbol that the cursor sits on, allowing you to find any prior instances of the word in the file up to the screen you are editing. The search will stop and display "Found..." with the cursor after the word located, then will wait for an <Enter> key to return to the screen being edited or a <Space> which will search for the word again until the editing screen is reached. The search buffer data (described below) is lost.

Function F2

This is the Editor Search text function, locating a given string upon the current screen block. If the string is not found the cursor will be deposited at the end of the screen.

Function Alt-F2

This key asks for the string to be found using the F2 operation, which is placed into a separate buffer so the search can be repeated. Please note that all searches in the editor are case insensitive, to allow for searches covering wider file types.

Function F3

This is the Editor text Replace function, replacing the word found with F2 with the one saved below. Please note that this function will take place wherever the cursor currently resides, deleting the number of characters contained in the search buffer and inserting its replacement string. As in other functions calling delete or insert, this key will use the wrap buffer in the wrap mode.

Function Alt-F3

This key asks for the replacement string to the one found in the F2 operation, placing itself into a separate buffer so the operation can be repeated. Note that the string is replaced immediately after entry, at the current cursor location. (So search first!)

Function F5

The F5 key is used to delete the line the cursor currently sits on, rolling all other lines up one location. If in wrap mode lines from the wrap buffer are pulled as well, filling in the gap caused by the deleted line.

Function F6

This key has two similar but different definitions behind it, depending on the type of mode selected. The first of these functions is to "join lines" at the current cursor location, or roll characters from the next line to the current. In both over-write and insert modes this joining comprises only the line directly beneath the cursor, and the key cannot be used on line 15. In the wrap mode however this key functions as a "delete all spaces" operation, gobbling up white space at the cursor's location until a non-blank is found. Because this operation uses the delete character operation, lines are rolled out of the wrap buffer as needed to meet this function.

Functions Alt-F5 and Alt-F6

These two keys perform the opposite function of their non-Alt counterparts, inserting a line of 64 spaces when the editor is in wrap mode. While it is possible to create space between two lines by pressing ENTER in all of the editor modes, these functions will create a hole the length of one line automatically starting with the current cursor location. As with all wrap operations this function uses the wrap buffers. The cursor will be returned to the point where the insert was made, or the end of the current screen minus one line, whichever is less.

Function F7

This key will "cut" or copy the line the cursor is presently on into an Nth line buffer, advancing the "cuts=" count and moving down to the next line. All lines thus copied go into the free ram between PAD and SP@, minus the 512 characters reserved to preserve the system memory. If insufficient ram exists to make any cut, the system console will issue an error beep. Note: because the area between PAD and SP@ is used, lines cannot be preserved if a compilation operation takes place between the use of F7 and F8.

Function Alt-F7

This key combination will copy the entire screen of 16 editor lines into the Nth line buffer and advance to the next screen, updating the "cuts=" count as above. Note that if insufficient memory is present the system will issue an error beep for each line attempted yet the function will still perform a Page Down operation. As with all Nth cuts, lines saved cannot be preserved if a compilation operation takes place between the use of F7 and F8.

Function F8

This key performs the opposite function of F7, pulling out the saved lines in the order they were cut. Lines pulled in this fashion replace the line where the cursor resides.

Function Alt-F8

This key combination will "pull" an entire screen of 16 editor lines from the Nth line buffer and advance to the next screen, updating the "cuts=" count as above. Should the line buffer become empty this operation is equivalent to a Page Down operation. Note that both Alt-F7 and Alt-F8 assume the current editing position is at the top of the screen, such that improper placement may cause incorrect operation.

Function F9

This operation allows the setting of a maximum limit to the search operation, such as the end of the current file. While F2 searches a single screen using this key and F10 below allows the whole file to be searched, or any portion of it.

Function F10

This is the extended search function made available by F2. This key will repeat the last search until the block number entered by F9 is reached or the string is found, displaying the point at where the search ended. All searches begin at the current location of the cursor, and proceed to higher numbered blocks.

Function F12

This function of the Editor is a macro operation, echoing keystrokes to the editor as recorded down below. Most functions of the Editor are available for use within macros, with the exceptions of the search string input, the replace string input, the maximum search block number input and the Alt-P printer codes. Even GG and HH switches between screens can be executed within a macro, as well as the navigation and mode change keys.

Function Alt-F12

This function will record keystrokes made inside the Editor, up to the limit of 255 operations. Should the macro overflow the first characters saved will be over written. As stated above only those operations that call interpret are not supported in macro, namely text search and replace input operations, and that of printer codes. Recording is stopped by pressing this key combination a second time, and the display will indicate the number of characters saved in the buffer.

Alt-Z

This key combination will erase the wrap buffers and fill them with spaces, then update the screen.

Alt-X

This function will erase the current block and the wrap buffers, essentially starting out with a fresh block of the file. Note however that this function does not set the update flag of the block erased, so it maybe recovered before any editing is performed by exiting the editor, using EMPTY-BUFFERS and REDIT to restore the text. The contents of the wrap buffers cannot be recovered.

Alt-O

This is the Editor Open Block command. The function will scan forward in the file for the distance of 250 block entries, attempting to locate a screen that contains only ASCII spaces. If such a block is located all blocks between the current one and the empty location are rolled down one slot, leaving a copy of the current screen which can be modified without loss of the text. If no such block of spaces can be found the key generates a system error beep and displays the words, "Empty not found."

Alt-W

This is the Editor Wrap Blocks function, which moves the text contained in the Wrap Buffer onto the next screen. Like Alt-O this function pre-scans the file to locate an empty block before operation, then proceeds to roll text from the Wrap Buffer onto the next screen, and then the next, etc., until either the empty block is located or the Wrap Buffer becomes empty. (As in having 3 lines saved in the wrap buffer and two lines available on the next block, with one available line on the block after that. The function will halt on the third screen.) The insertion point for this operation is on program line #1 of the convention mentioned above, or the second line to appear in the text window. This allows the function to move program source without disturbing any title comments. This function operates only in the Wrap Mode, and inserts all characters contained in the wrap buffer even if they are spaces so long as a valid character remains in the buffer.

Alt-P

This function is used to enter Printer or other display codes into a screen block, by asking for the ASCII value of the character to be inserted at the cursor location. The number entered should be in decimal, to which base the editor defaults.

Alt-1 through Alt-8

These last key functions of the Editor set or reset individual bits of the character where the cursor now resides, bits numbered 1 to 8 as the keys themselves. Again, this is for entry of printer and other codes, but may be used for graphics or other functions.

Editor Exit & File Saves

The Editor is exited by pressing the <Esc> key, then optionally hitting <Enter> if the HH or GG word is run. At this point all changed blocks have been updated internally by Fig-Forth, which can be forced to the disk drive by saying FLUSH. If the edits are not desired say EMPTY-BUFFERS before any disk access is attempted, because Fig-Forth will write out the changes as each buffer is rotated in the use list. When the editor is exited a line stating the last screen edited will be printed at the bottom of the screen, because the title value rolls off.

Other Editor Functions

The current version of the Editor is 1.7, which includes four additional functions inside the default build of the source code. Two of these operations have long memory equivalents on the screen block following the Editor source text, plus a function to adapt older styled Fig-Forth source to the current environment. These functions are listed below;

.M

This operation will display 112 bytes of user RAM starting at the address given it, returning the address of the next location to be displayed on the stack. The numeric base is changed to Hexadecimal for both addresses and values in the display, and the screen characters represented by the bytes will appear to the right of the values.

.B

This word performs the same task as .M above, but returns the starting address of the displayed RAM block as unchanged. (I.e.; a DUP .M DROP sequence.)

L.M and L.B

These operations perform as .M and .B above but require both an Offset and Segment location for the portion of RAM displayed. Note that none of these functions will span segments, wrapping around to address 0000 from FFFF hex. The long memory versions must be loaded manually before use.

LOWER-BLOCKS

When this function is called with an Ending Block Number plus one and a Starting Block Number, it will scan the block range specified and change all uppercase ASCII characters to their lowercase equivalents. This function must be manually loaded from the screen where L.M and L.B appears.

?? and SEE

These two words form a very simple decompiler for Fig-Forth dictionary words, attempting to display the source text from which the definition was constructed. The process is begun by using the SEE word followed by the definition desired to be examined;

        SEE EDIT

        0 1561

        DUP 234

        ...etc.

As shown above, on each subsequent line the SEE word will attempt to display the Fig-Forth source word and the address it represents in decimal. The function will stop and wait for an input from the console before displaying the next value, for which the <Enter> key will halt the process and return the next address to be processed.

Because of the simplicity in the above routine the programmer must interpret the output for proper comprehension, as in the following sequence which forms the start of a DO-LOOP with the values of 10 to 15.

        LIT 238

        TVbG 15

        LIT 238

        *-?> 10

        (DO)

Note that the values after the LIT lines are the constants used by the code sequence, such that the SEE word cannot find a suitable dictionary entry for the value and a random string of characters appears. The same type of operation can occur when displaying string constants, as in a typical output of below;

        SEE EDD

        ...

        (.") 8234

        {_______ -23451

        *^{yU 767

        EDIT -34

As so on. While the interpretation of the contained string as numeric constants is the source of this error with the results as shown above, this particular error may be avoided by proper use of the other functions;

        SEE EDD

        ...

        (.") 8234 1K. -- user types Enter to exit

        .B -- string is displayed in memory dump

        COUNT + -- adjust address to skip over string

        ?? -- restart decompile function

        DUP 131 -- next word appears.

Note that most user defined dictionary functions end with the word (;S) which forms the Forth "return to caller" operation.

About the Editor

This Editor is based upon my own and Frank Sergeant's Pygmy Editor source code, using many ideas from Frank plus the extra functions of my Fig-Forth Editor written in the 80's. The source screens for the Editor are available to you in the file EDIT.4TH, but I'm not going to discuss this file because the screens are very busy and space compressed. (I tend to like the Editor to take up less than 20 blocks, so my program files can start at 20.) Write me if you feel you have any suggestions for the Editor.

Your Second Program

A glass teletype version of BlackJack is all well and good as it goes, but with only 7 variables and a few constants it's not exactly exciting. In fact the only interesting aspects were the use of the input processor for playing and the random card generator, otherwise the program was really quite boring. So for the next program we're going to implement a game most commonly written in BASIC during the 70's and early 80's, loosely called "Star Battle" or "Star Trek" after the popular television series by Paramount.

This section will not be as highly commented as the previous program, because the Star Trek game is the classic example of spaghetti code even in the BASIC language. This is mostly due to the number of people who worked upon the game, and while Forth makes us maintain a greater structure I'm not willing to break it all down. So I suggest you run the game a few times to see what's happening, then examine the source in detail as we did for the DEAL word in BlackJack. In addition, because Fig-Forth is an integer based program that does not allow internally scaled variables, we must make several corrections to the number system to give the illusion of the original game, which would be confusing and lengthy as shown under the Graphic Program example in the next chapter.

Also, this version of the game will be using the text windowing options that Fig-Forth contains, as well as data arrays, geometric functions, plus some unusual mathematic operations. So this is a good selection for our second program, even allowing for multiple methods of addressing and programming styles. As before in BlackJack, one of our initial concerns is that of getting a valid user input while not letting the internal machine generate an error that would stop the game, so we start with a variation of the get-line definition;

          : GET#$ ( -- F ) TIB @ 30 BLANKS -- blank text input buffer

               TIB @ 1+ 25 EXPECT -- get string from keyboard

               TIB @ 1+ C@ -- get 1st character

               DUP 45 = OVER 46 = OR SWAP -- test for period or minus sign

               48 58 WITHIN OR -- or a valid digit

               HLD @ 2- 0 ?DO -- start a testing loop

               TIB @ I + 2+ C@ -- get next character

               DUP 46 = SWAP 48 58 WITHIN OR -- valid number or period?

               AND LOOP ; -- add it to flag, loop & exit

               ( GET A STRING, MAKE SURE IT'S A NUMBER )

This is our first routine for retrieving a number input from the system console, one which tests that each character entered by the user is in fact a valid digit for the number processor. While this routine will allow multiple "decimal points" within a number, it does ensure that every character is a valid digit. Once this routine has returned its flag value we can move on to the next operation, that of converting the string to a stack value;

          : GET# ( -- N/D F ) CUR @ -- save cursor in case of error.

               BEGIN GET#$ DUP 0= -- get string and copy flag

               IF OVER CUR ! SPACE 8 EMIT THEN -- if error, back up cursor

               UNTIL DROP -- no error, exit and drop cursor value

               TIB @ NUMBER DPL @ -- get the number and DPL value.

               TIB @ 30 ERASE ; -- erase text buffer again

               ( GET NUMBER FROM KEYBOARD )

This routine completes the input process, locking the cursor on the screen until the user inputs a valid number. Then the string is converted and both the value and its decimal point locator is returned, which allows us to test the result in later operations. Note that because of the GET#$ operation the user cannot input the ampersand (&) sign, Forth commands or other characters (including spaces or commas) because the GET#$ disallows them.

Our next process is that of bumping the random number generator, or in other words selecting a point at where the generator starts its long loop of random values. For this we call upon the get number word and enter a simple loop, using the user input and the internal seed to decide a starting point;

          : SEED-IT ." INPUT ANY NUMBER: " -- user prompt

               GET# 2DROP ABS -- get number, absolute value

               0 DO RND0 DROP LOOP CR ; -- then loop until done.

               ( BUMP RND GEN AROUND A BIT )

This routine discards the DPL and high word values returned by the GET# routine, then insures that the result is a positive value. This not only allows that any number can be entered for the routine, but keeps the value relatively small for system response time. Now of course, because the Forth RND0 word returns a value that is unsigned and in the range of -32768 to 32767, we need a way to limit the random value to a range that we desire on each request. We define such a random function as the one below that will return the values between 1 and N inclusively.

          : RND ( n -- n ) 1+ BEGIN --start of the check loop, add one to value sought.

               RND0 ABS OVER MOD --mask off sign and then get the remainder if any

               -DUP UNTIL NIP ; --copy result if not zero and exit, else go back again.

Using this method the random sequence will appear still longer to the user of the game as they apply differing strategies, because we know the result from RND0 and the MOD will occasionally be zero and this routine "skips ahead" one location in that event to mess up the current sequence even more. A long standing problem with random number routines is that they loop after a while, so this process will increase the apparent length even though Fig-Forth's generator has a loop value that's pretty high.

Now, we need to print a title upon the standard text page, a "Hello World!" program as it were with a definite purpose. We are going to call this word simply the name of TITLE, which tells the user what's going on;

          : TITLE CLS HOME 27 SPACES ." FIG-FORTH BATTLE TREK V2.5" CR ;

This line is obvious, it clears the screen, homes the cursor, prints 27 spaces for centering and then prints the title followed by a carriage return. Now we get to the point of defining the text window, namely its size and position. For this routine we're going to use a simple helper, to define each window as we desire while we go along;

          : SETWIN ( X Y W H -- )

               VIDEO --switch to video controls

               VLENGTH ! VWIDTH ! --set #lines & #columns

               VLEFT ! VTOP ! --set upper corner

               FORTH --back to forth.

               HOME ; --set cursor on window

Though this function does not clear the window when it changes the screen display, it does prepare the cursor for printing upon the new page after changing the values. Note that in this case we had to change the current syntax vocabulary to access the control functions, then switch back to the Forth vocabulary to continue our program. (See Chapter 6.) Now we can define our first window, that is the normal one we use when talking to Forth;

          : NORM 0 0 80 25 SETWIN 3680 CUR ! ; -- set normal window

Because the screen could contain any data when we want to switch back to standard operation, this routine both defines the full window as the target and then sets the cursor on the final line minus one. This means that the cursor will be deposited at the bottom of the screen after the next carriage return, so we may view the resulting display without disturbing it.

          : SUBS 13 0 80 12 SETWIN 3680 CUR ! ; -- set game window

This routine performs a similar operation, though cuts the screen in half for the game to be processed. Because we intend to use the upper half of the text area for displays of ship status and the location of the ship and enemy, we need an area where we can type in commands and see their results. Thus this routine will set up that area.

Now we enter the area of defining the constants to control the game;

           40 CONSTANT MAX-KLINGS

             3 CONSTANT MIN-KLING

             1 CONSTANT MIN-BASES

             9 CONSTANT MAX-STARS

        4000 CONSTANT BATTERIES

            10 CONSTANT SHOTS

         6000 CONSTANT KE ( KLING POWER*10 )

These values control the game limits used by the program, though be aware that changing some can cause incorrect operation. Of particular concern is the manner in which the galaxy data is defined, something we'll get to in a few paragraphs.

Next, we need the variables that this version uses, minus the arrays that we'll define in a moment.

          0 VARIABLE #KLINGS

          0 VARIABLE #BASES

          0 VARIABLE XQUAD

          0 VARIABLE YQUAD

          0 VARIABLE XSECT

          0 VARIABLE YSECT

          0 VARIABLE COND

          0 VARIABLE ANG

          0 VARIABLE SDATE

        10 VARIABLE TORPS

     1000 VARIABLE SHIELDS

     3000 VARIABLE ENERGY

Note that variables and constants can be defined in any order within Fig-Forth, even allowing that they may be defined before the routine(s) that call them. However as a matter of standard practice it is recommended that they be grouped together, because their global use allows for better organization and understanding of the terms. For example, having the SHIELDS variable defined directly before the fire control section is all very well and good, but because it has been moved from the global variable page the word can be misunderstood when writing the docking sequence. (Or vice versa as the case may be.) Again, Forth is geared to a more structured approach of programming, just like C, Pascal or others.

Next we get to the arrays used by the game, starting with the most simple and advancing to the complex one;

          4 4 * ARRAY KK ( MAX OF 4 KS IN QUAD )

     8 8 * 2 * ARRAY GALAXY ( GALAXY DATA )

       64 64 * ARRAY SECTORS ( SECTOR DATA: QUAD, QUAD, QUAD... )

          8 2 * ARRAY DAMAGE ( DAMAGE ARRAY )

               0 +OFF .LRS

               2 +OFF .SRS

               2 +OFF .PHSRS

               2 +OFF .TORPS

               2 +OFF .ENG

               2 +OFF .DMG

               2 +OFF .SHLDS

Again, these are grouped together simply for ease of reading the program after its creation, so the program can be modified or maintained later. Note that the final array uses the +OFF function of the Fig-Forth compiler, to allow access to any element in the array. While it is equally possible to define these numbers as individual variables, saving them in an array allows for sequential access;

          : ANY-DAMAGE? ( -- F ) 0 8 0 DO I 2 * DAMAGE + @ + LOOP 0= 0= ;

This routine for example will return a true flag if any device has been indicated as damaged by having a value stored in its variable, something we can do quickly and easily through loop addressing and adding up the values found. Though even this routine is clumsy by the method being applied, which can be streamlined to operate as follows;

          : ANY-DAMAGE? ( -- F ) DAMAGE 0 8 0 DO OVER @ + 2 +UNDER LOOP NIP 0 <> ;

This version uses the stack to store both the array address and the flag being constructed, something we'll do later for building the galaxy. With a slight modification this routine can check any table of integers, as shown below;

          : ANY-THING? ( A N -- F ) 0 SWAP 0 DO OVER @ + 2 +UNDER LOOP NIP 0 <> ;

Now all we have to do is give the address and array size to test for any value not being a zero. E.g., DAMAGE 8 ANY-THING? will return true if any device is marked as damaged.

Next, because this is an updated copy of the game we come to the final custom array, the colors to be used for printing on the screen;

          14 VARIABLE CLRS -1 ALLOT 15 C, 10 C, 12 C, 14 C, 14 C,

This definition uses the ALLOT word to change the effective end of the dictionary after we've defined the variable, "sliding backwards" one memory location to make the table contiguous. This is very similar to the original construction of arrays in Fig-Forth, in that you'd say;

          0 VARIABLE MYVAR 25 ALLOT

for a 27 byte space. Fig-Forth v2's ARRAY word does this automatically, as well as zeroing the bytes covered which ALLOT does not. Now comes our table function, to allow the game to display various components with a different ink so it's not quite so boring as the original version;

          : C? ( N -- ) CLRS + C@ ATTR ! ;

This little routine will pick out one of the bytes in the CLRS table and place it into the screen attribute system variable, allowing us to change colors of ink and background easily. (Even though a change of background color has not been defined in the table.) A zero sent to this function will produce a Yellow text color, while 1 is White, 2 Red, 3 Green and so on.

Now comes some strings the game will use, informing the player of damage information;

          : $8 ." DAMAGED***" CR ;

          : $9 ." LONG RANGE SENSORS" ;

          : $10 ." SHORT RANGE SENSORS" ;

          : $11 ." PHASERS" ;

          : $12 ." PHOTON TUBES" ;

          : $13 ." ENGINES" ;

          : $14 ." DAMAGE CONTROL" ;

          : $15 ." SHIELDS" ;

          ( DAMAGE PRINT STRINGS... )

          SWITCH DEV1 0 $9 1 $10 2 $11 3 $12 4 $13 5 $14 6 $15 -1

          ( TABLE OF PRINT VECTORS )

This aspect of the game routine uses the SWITCH operation of Fig-Forth 2.26 and up, in that each string in the listed table under DEV1 will be printed according to the number value given DEV1. This is another reason for making the DAMAGE variables an array of integers, for the result is if we know which numbered value is disabled the SWITCH statement will automatically print the correct string for us. This is rather handy given all the specialized printing we need to do, though the switch structure could also be used for commands, data types, all sorts of operations.

And finally, at least for this phase of the setup operation, we come to the final array used for printing;

          : $1 ( -- ADR ) ," . * B K E " 1+ ; ( THE PRINT DATA FOR DISPLAY )

This little array is best defined in text mode rather than in characters, because each entry in the table should be precisely 3 characters wide. This is fine given that we are using letters for the operation, while graphics would require a much larger data area. This array word like any other will return its starting address when run, skipping over the internal length byte contained in the string. (See STRINGS.)

Since our data has now entered the realm of game printing, it's a good time to deal with the other display routines. The first is that of printing a horizontal line on the screen that makes up our master display area frame, a simple matter of outputting the correct character the correct number of times;

          : HLINE 23 0 DO 196 EMIT LOOP ; ( A HORIZONTAL BOX LINE )

This will draw a line on the screen to make the top and bottom of our display window, using one of the 28 box characters originally developed. Now we get to the number printing section, which takes into account our odd math;

          : .Y ( N -- ) 1+ . ; ( PRINT Y COORD )

          : .X ( N -- ) 1+ . ; ( PRINT X COORD )

These first two are obvious, printing an X and Y coordinate in the manner of 1 to 8. Because the original game used the values of 1 to 8 for any coordinate in the galaxy while our internal computations are based on 0 to 7, we simply add one to the number and print it as normal. This is due to the fact that in Fig-Forth arrays are addressed from element zero and up, instead of the usage in Basic which started at one.

          : #-# ( N -- ) S->D DUP -ROT DABS <# # # # 46 HOLD #S SIGN #>

               TYPE ; ( NUMBER PRINT X.XXX )

This next routine takes any integer and prints it as though comprised of 3 decimal places, meaning that 12345 will be printed as 12.345. This gives the game the illusion of operating in floating point, even if the values are saved as non-point integers. (See VARIABLES & MATH, Number Formatting Functions.)

           : .## ( N -- ) S->D DUP -ROT DABS <# # # 46 HOLD #S SIGN #>

               TYPE ; ( NUMBER PRINT X.XX )

This next routine does the same operation as above, though only includes two apparent decimal digits. Again, this is used for the specialized printing we require, and we assume that we will be giving it values that meet the requirement of the operation.

          : .# ( N -- ) S->D DUP -ROT DABS <# # 46 HOLD #S SIGN #> TYPE ;

          ( PRINT NUMBER XXX.X )

This final number routine will print only 1 decimal digit, to complete our picture formatting of the expected values. While it is possible to create a universal word to print any value as any collection of decimal digits, as in using the DPL value to decide how to print the number, this is not required for this version of the game.

          : DOTS ( -- ) 19 0 DO ." :" LOOP CR ; ( PRINT DOTS FOR FRAME )

Lastly, we finish up the display helper area with a frame of all colons, needed to emulate the final window from the original game. Now we enter the area of our number helpers, for computing locations and fixing current values;

          : ->Q ( X Y -- ADR ) 8 * + 2 * GALAXY + ; ( ANY QUAD ADR )

          : >QUAD ( -- ADR ) XQUAD @ YQUAD @ ->Q ; ( CURRENT QUAD )

These are the first two helpers in the game addressing, that of pointing into the galaxy array for any location or the current one. These are useful in that we'll be using them often, as can be said of the next routine;

          : SECTOR ( -- ADR ) XQUAD @ YQUAD @ 8 * + 64 * SECTORS + ;

           ( POINT TO CURRENT QUAD RECORD IN SECTOR DATA )

This definition is a "smart variable" operation like >QUAD above, in that it returns the current base address of the present sector data. Such an operation can be defined in many ways within Fig-Forth, as in the following which performs the operation blindly;

          : BSECT <BUILDS 2048 0 DO 0 , LOOP DOES> XQUAD @ YQUAD @ 8 * + 64 * + ;

          BSECT SECTOR ( BUILD IT! )

This will build the same array as SECTORS up above and automatically associate the operations as contained in SECTOR to the data area. The exact method to be employed in such matters is both a matter of preference, style and expected number of uses, so use your best judgment as to which is required by your program.

Now we get to some all important tests, making certain that different values are within legal range;

          : 0-9 ( N -- N ) 0 MAX 9 MIN ; ( LIMIT DIGIT )

          : 0-7 ( N -- N ) 0 MAX 7 MIN ; ( LIMIT Q/S )

          : ?XY ( X Y -- X Y F ) OVER 0 8 WITHIN OVER 0 8 WITHIN AND 0= ;

          ( IS X & Y VALID? )

The first two routines here make sure that the value is within the legal range of a valid digit and a coordinate address respectively, by forcing either a zero or the maximum that is allowed. The final routine however doesn't change the values as the 0-9 and 0-7 sequences do, it only returns a flag to indicate if the entries are within range. Note the WITHIN operation is inclusive on one end and exclusive on the other, meaning that 0 8 WITHIN will return a true for all values of 0 to 7, but 8 and higher or a negative number will be false.

Next we get to the formulas needed by the game, which involve both rectangular and polar coordinates. This is because the game expects to be given commands in degrees and vectors, while the game area is defined in Cartesian style.

          : P->R ( 2N -- X Y ) DUP ANG @ COS 20000 */ SWAP ANG @ SIN

               20000 */ ; ( COMPUTE COORDS FROM VECTOR )

          : P->R1K ( N -- Y X ) DUP ANG @ SIN 200 */ YSECT @ 1+ 100 * +

               SWAP ANG @ COS 200 */ XSECT @ 1+ 100 * + ;

               ( COMPUTE COORDS *100 FROM SHIP )

          : W->R ( N -- X Y F ) P->R SWAP XSECT @ + SWAP YSECT @ + ?XY ;

          ( CONVERT VECTOR TO X,Y & TEST LIMITS )

          : DIST ( X1 Y1 X2 Y2 -- N ) ROT - 10 * DUP * -ROT SWAP - 10 *

               DUP * + SQR ; ( DISTANCE BETWEEN POINTS*10 )

These are fairly standard math operations, minus the adjustments made for accuracy within the game. Since we are on this screen we also include the final formula, that of a declining effectiveness of energy weapons. The original game used an exponent value of .4 for this operation, which works out to be a 2.5 root;

          : ^.4 ( N -- V ) 10 * 40 1 DO I DUP DUP 2 / * * OVER >= IF DROP

               I LEAVE THEN LOOP ; ( USED LATER, 2.5 ROOT )

This section makes an approximation of the 2.5 root by actually computing its inverse, then comparing the result versus the value whose root we're trying to calculate. Because we are computing the value from an integer basis, the maximum root of 40 will cover all expected values in the game. And now we get to one of the oddest operations contained within the game, the "easy digits" operation or DIGS as defined;

          : DIGS ( N C -- N D ) PLEAT 1 -ROT 0 -ROT 0 DO 10 /MOD ROT DROP

               ROT 10 * -ROT LOOP DROP SWAP 10 / OVER * ROT SWAP - SWAP ;

               ( RETURN COLUMN VALUE C OF N )

This routine is a variation of the "easy bits" operation commonly defined within programs to save space and compress data areas. It also uses the concept of "flags" contained within a number area, such as the setting, resetting or testing of each bit within a variable's address space. However, unlike its Binary counterpart the values operated on are in Decimal, because the Star Trek game saves the number of stars in the 1's digit, the number of bases in the 10's digit and the number of Klingons in the 100's digit. So this routine will extract one base Ten digit worth of data from the number given it, meaning that 1234 and 2 will return 1204 and 3, or the number as modified with the digit removed, and the digit itself on the top of the stack. An unusual operation to be sure, but useful for this implementation.

Now at long last, we're ready to begin building the game galaxy.

          : GALAXY-DONE? ( -- F ) 1 128 0 DO I GALAXY + @ 0 > AND 2 +LOOP ;

          ( FIND OUT IF ALL OF THE GALAXY HAS BEEN SEEDED )

Our first operation is a simple one, finding out if the galaxy has been completed. This is the same operation as the ANY-THING word up above, or, if every location in the array contains something then the galaxy is complete. In this case we place a true flag on the stack and then test each galaxy location for being non-zero, ANDing the flags together to make the result. If any galaxy location is empty the flag will become zero, thus a false flag will be the result.

          : STARS ( -- N ) MAX-STARS RND ; ( GET THE NUMBER OF STARS )

          : KLINGS ( N -- N ) #KLINGS @ MAX-KLINGS < IF MIN-KLING RND DUP

               #KLINGS +! 100 * + THEN ; ( ADD IN THE KLINGON MENACE )

          : BASE% ( -- % ) #BASES @ 0= IF 43 ELSE 500 #KLINGS @ 10 / 1+ /

               THEN ; ( GET THE PERCENTAGE OF BASES )

          : BASES ( N -- N ) BASE% RND 1 = IF 10 + 1 #BASES +! THEN ;

               ( ADD BASES TO CURRENT VALUE )

These four lines are the reverse operation, building a number to be used by the game's galaxy definition. Though the order is not important two operations are self limiting; the klingon menace and the number of bases. Klingons are added until the total is within the limit defined in the game constant, while the number of bases is computed against the current number of klingons defined thus far. This means that any galaxy entry will fall within certain limits, with more difficult games adding in extra bases to relieve the tension.

          : -VALUE ( N -- ) 3 DIGS #KLINGS -! 2 DIGS #BASES -! DROP ;

               ( CORRECTION FOR VALUES THAT MUST BE REMOVED )

This next routine is designed to remove the klingon and base calculations in the event the random number generator feels the need to change any galaxy location, which we can expect will happen frequently as the random coordinates are selected.

          : >GAL ( N -- ) 8 RND 1- 8 RND 1-

               BEGIN 2DUP ->Q @ 0= IF ->Q ! 1

               ELSE 1+ 8 /MOD +UNDER SWAP 8 MOD SWAP 2DUP ->Q @

               0= IF ->Q ! 1 ELSE

               2DROP 8 RND 1- 8 RND 1- 2DUP ->Q @ -VALUE

               2DUP 0 -ROT ->Q ! 0 THEN THEN ?TERMINAL OR UNTIL ;

               ( SEND DATA TO GALAXY ARRAY AT RANDOM )

Next we get to a monster routine that is designed to take the values built by the randomizer and place them into the galaxy, first by selecting an X and Y coordinate at random and seeing if it is empty. If that location in the galaxy is not empty the routine advances to the next galaxy location and repeats the test, then gives up and selects another X and Y value to use. In doing this it forces frequently called quadrants to be emptied, thus increasing the "cheesy-ness" of the resulting galaxy.

          : BUILD-GALAXY ( -- ) 0 #KLINGS ! 0 #BASES ! GALAXY 128 ERASE

               BEGIN STARS KLINGS BASES >GAL #KLINGS @ MAX-KLINGS 4 / DUP

               3 * SWAP RND + > #BASES @ MIN-BASES > AND GALAXY-DONE? AND

               ?TERMINAL OR UNTIL ; ( BUILD THE WHOLE THING )

And now we get to the last routine of the galaxy build operation, which empties the array and then fills it with values. This routine will continue to create the galaxy until the number of bases are at one or more as defined by MIN-BASES, and at least three quarters of the klingons listed in MAX-KLINGS are present. As a final test it also insures that all of the galaxy contains something, and does a keyboard detect in the event of a lock-up. (As in the RNG has been seeded with all zeros, which would take a very long time to produce any useful values for the program.)

          : REPORT1 ( -- ) BEGIN BUILD-GALAXY 480 CUR ! ." KLINGS: "

               #KLINGS ? ." BASES: " #BASES ? CR ." CHANGE? " KEY 95 AND

               89 <> UNTIL ; ( REPORT IT ALL )

Now comes our report function to tell us how many klingons and star bases we have in the game, then allows us to request another configuration. This is repeated over and over until we agree to the limits, when the game can proceed.

          : QQ1 8 0 DO CR 8 0 DO I J ->Q @ 4 .R LOOP LOOP ;

          ( PROGRAMMERS CHEAT TO PRINT THE GALAXY DATA )

Next is our first debug or cheat word to insure that things are going smoothly, namely a routine to print out the values created by the galaxy build operation. This allows that we can create level after level and adjust the constants used by the game, to make it as easy or difficult as we choose.

          : PUT-E 8 RND 1- XQUAD ! 8 RND 1- YQUAD ! ;

          ( SET SHIP DOWN SOMEWHERE... )

And of course now we must select a location for the Enterprise, just a random starting point from where the game will begin.

Our next routines must be the Damage Control System, for any device contained on board ship may be disabled by enemy fire. At the same time we must inform the player of this using our switch print statement up above;

          : OUCH! 1 C? 7 RND 1- 10000 RND 1- OVER 2 * DAMAGE + +! ." ***"

               DEV1 DROP $8 7 EMIT 0 C? ; ( DISABLE A DEVICE )

This routine selects one of the six devices at random and disables it for up to 10 move increments, then calls the switch statement to tell the user that's something's wrong.

          : DAMR DAMAGE.DMG @ IF ." ***" $14 $8 ELSE ." DAMAGE CONTROL "

               ." REPORT" CR 7 0 DO I DEV1 42 CUR @ 160 MOD - 2 / SPACES

               2 * DAMAGE + @ #-# CR LOOP THEN ; ( DAMAGE REPORT... )

This next routine will list the status of all devices unless itself is disabled, a rather odd quirk of the original program. Now we get to the repair operation that takes place on each use of the engines, to selectively repair the devices as the game goes on;

          : FIXIT DAMAGE.DMG DUP @ IF DUP @ 1000 - 0 MAX SWAP ! ELSE 10 -

               7 0 DO DUP @ IF DUP @ 1000 - 0 MAX OVER ! THEN 2+ LOOP DROP

               THEN ; ( FIX DAMAGE IN MOVE INCREMENTS )

Again, this test first detects if the repair system is disabled, then adds a one to each disabled device for every movement the player makes if not.

          : STORM? 25 RND 1 = IF 7 EMIT 1 C? ." ION STORM -- " OUCH! THEN ;

          ( BE NASTY TO THE FOLKS! )

This routine on the repair screen performs the opposite function of the FIXIT word, randomly disabling devices as the player moves around. This disabling is called "ion storms" by the original program, so we keep it for ours.

Now we get to our custom display operations, starting with the Long Range Sensors display. Since we are using the text window operation of Fig-Forth this display is a little different than the original, in that we don't have to issue a command in order to display it;

          : DLRS ( X Y -- ) ?XY 0= IF ->Q @ 1000 MOD 4 .R ." :" ELSE

               2DROP ." --- :" THEN ; ( PRINT ONE ENTRY OF SCAN )

This first word of the LRS display is used to print out a single galaxy entry, using precisely three decimal digits and a final colon character. At the same time the routine calls the coordinate validation word to "blot out" those parts of the "galaxy" not allowed in the array, replacing the display with 3 hyphens.

          : LRS 1 60 20 8 SETWIN DAMAGE.LRS @ IF CR CR CR ." ** LRS "

               ." DAMAGED **" CR CR

               ELSE 4 1 DO DOTS ." :" 4 1 DO

               I 2- XQUAD @ + J 2- YQUAD @ + DLRS

               LOOP CR LOOP DOTS THEN

               ." LRS FOR " XQUAD @ .X ." - " YQUAD @ .Y ;

               ( PRINT WHOLE SCAN )

This second word actually performs the process of making the display, or of declaring that the LRS system is a damaged device. Note that this routine uses two DO loops to select the data sent to the DLRS function, offsetting the ship's current position by -1, 0 and +1 in both the X and Y direction. Once this word is defined we can test the operation by the process below, remembering to restore the text window to that of normal as defined above;

          LRS NORM -- the window will be printed

Now we come to the major display within the Star Trek Battle game, that of the Short Range Sensor Scan. While the original Basic version printed this display after every action made by the player, our updated version will recreate this display as a central window upon the monitor area.

However, the first task is to build the data this display requires, a function of how we want the game to proceed. There are two methods that can be used to create this information, a small data version and a large data version;

          8 8 * ARRAY SECTOR -- define a small array

This method of creating the sector array allows for a single quadrant worth of data inside the field, the method used by the original program. In this process the galaxy data is extracted from the galaxy array, then the byte data needed for the sector scan is built as the player enters. The advantage and drawback to this method is that the sector is not fixed by the game, so a player may leave a quadrant and re-enter it with a different arrangement of the objects contained in the area.

For our version we're going to use the large data model, one in which we create each quadrant worth of sector data the first time the player enters the area. The advantage from a player standpoint is that each quadrant will always look the same after the player first sees it, meaning that the universe is fixed and stable. The disadvantage is of course that navigating around objects will be a required, because you can't exit and re-enter the quadrant to change the arrangement. This is fine given the nature of the game.

          : >SEC ( N -- ) BEGIN 64 RND 1- SECTOR + DUP C@ 0= IF C! 0 ELSE

               DROP THEN -DUP 0= UNTIL ;

               ( STORE DATA IN A SECTOR LOCATION THAT IS ZERO )

This first routine stores a value into the proper sector array space, using the "smart variable" process as defined previously. Note that in this case however the routine doesn't call an X and Y component, using an offset value from 0 to 63 instead of the coordinates. In this case the routine tries location after location until it finds an empty space, saving the data and exiting at the end. For the purposes of this version of the game a 1 is a star, a 2 is a base, etc., as shown in the sample print string constructed earlier.

          : SEC? ( -- F ) SECTOR 0 64 0 DO OVER I + C@ OR DUP IF LEAVE

               THEN LOOP NIP ;

               ( HAS THIS QUAD BEEN DRAWN BEFORE? )

This routine is similar to the galaxy test function, checking to see if this sector has been drawn previously. Again this uses the fact that any value not zero will be treated as a true flag by Fig-Forth, so it simply ORs all the values together.

          : SECTS SEC? 0= IF >QUAD @ BEGIN DUP 1000 / IF 4 >SEC 1000 -

               ELSE DUP 100 / IF 3 >SEC 100 - ELSE DUP 10 / IF 2 >SEC 10 -

               ELSE DUP IF 1 >SEC 1- THEN THEN THEN THEN -DUP 0= UNTIL

               THEN ;     ( SET UP ONE QUADRANT )

Now this routine is the main operation of setting up the sector data, which retrieves the quadrant data and builds the sector arithmetically. It does this by testing each column of the decimal number, until the value becomes zero to indicate it's finished.

          : SET-E SECTS BEGIN 64 RND 1- DUP SECTOR + C@ 0= IF 8 /MOD

               YSECT ! XSECT ! 0 THEN 0= UNTIL ; ( PUT SHIP SOMEWHERE! )

This routine does the same process as the >SEC routine but doesn't save any data, instead placing the location's coordinate equivalent into the current sector locations for the player's ship. In this way the sector data does not have to be updated for each movement of the vessel, though we have allowed that this process may be used if we define the proper routines.

          : DOCKED? ( X Y N -- ) 2 = COND @ 3 <> AND IF YSECT @ - ABS

               SWAP XSECT @ - ABS 2 < SWAP 2 < AND IF 3 COND ! THEN ELSE

               2DROP THEN ; ( ARE WE DOCKED? )

Next we come to the routine that detects when the ship is sitting next to a star base, so that the vessel may be refueled and restocked with weapons. By defining this routine as part of the Short Range Sensor Scan, and providing the data and location of sector data, we can update the condition variable at the end of every operation taken by the game, which is just what we need here.

          : RED? ( N -- ) 3 = COND @ 3 <> AND IF 1 COND ! THEN ;

          ( ENEMY PRESENT? )

Next in the process is doing the same for any enemy contained in the quadrant, when the condition of "red alert" should be set unless the ship is docked.

          : E? ( X Y -- N ) 2DUP YSECT @ = SWAP XSECT @ = AND IF 2DROP 4

               ELSE 8 * + SECTOR + C@ THEN ; ( GET KIND OF OBJECT )

And next we test the location being examined versus the ship location, or retrieve the sector data as constructed previously.

          : DSRS 8 0 DO CR 179 EMIT 8 0 DO I J 2DUP E? DUP RED? DOCKED? 3

               SPACES LOOP 8 EMIT 179 EMIT LOOP CUR @ 864 CUR !

                ." ** SRS DAMAGED **" CUR ! ; ( DAMAGED DISPLAY )

Now we are ready for the first type of Short Range Sensor Scan display, the version that appears when the SRS is damaged. while this routine doesn't actually display any sector data it makes the checks as mentioned above, so the condition value will always be properly set.

          : RSRS 8 0 DO CR 179 EMIT 8 0 DO I J 2DUP E? DUP DUP C? 3 * $1

               + 3 TYPE 0 C? DUP RED? DOCKED? LOOP 8 EMIT 179 EMIT LOOP ;

               ( NORMAL SHORT RANGE DISPLAY )

Here is the real short range sensor scan, which extracts three characters from the print string definition and places them into the frame, sets the characters in color and shows the ship's location. This routine also adds the vertical sides to the display to make our display square, again using the proper characters.

          : SRS 0 COND ! 1 28 27 13 SETWIN DAMAGE.SRS @ SECTS 218 EMIT

               HLINE 191 EMIT IF DSRS ELSE RSRS THEN CR 192 EMIT HLINE 217

               EMIT CR ." QUAD: " XQUAD @ .X ." - " YQUAD @ .Y ." SECT: "

               XSECT @ .X ." - " YSECT @ .Y ; ( DO IT ALL )

Finally, we get to the routine that completes the SRS, detecting damage and calling the appropriate word as needed. This word also adds the corners of the box image, and the report of where the ship is located.

Now we get to the final part of the heads up display, that of reporting the total ship's condition. This will form the final window display.

          : YELLOW? ENERGY @ 500 < COND @ 0 = AND IF 2 COND ! THEN COND @

               3 = IF 3000 ENERGY ! 10 TORPS ! THEN ;

               ( IS SHIP LOW ON ENERGY? OR DOCKED )

This routine tests the current ship's condition and refuels it if docked, or changes the condition of Green to Yellow in the event the ship is low on energy.

          : $2 ." CONDITION: " COND @ DUP 0= IF 2 C? ." GREEN " 0 C?

               THEN DUP 1 = IF 3 C? ." * RED *" 0 C? THEN DUP 2 = IF 0 C?

               ." YELLOW " THEN 3 = IF 1 C? ." DOCKED " 0 C? THEN CR ;

               ( CONDITION REPORT )

This routine prints out the ships condition, using the appropriate ink color at the time of calling.

          : $3 ." ENERGY @: " ENERGY @ 7 .R CR ; ( PRINT ENERGY LEVEL )

          : $4 ." TORPEDOES: " TORPS @ 7 .R CR ; ( TORP COUNT )

          : $5 ." SHIELDS @: " SHIELDS @ 7 .R CR ; ( SHIELD STRENGTH )

          : $6 ." STAR DATE: " SDATE @ 7 .R CR ; ( GAME TIME )

          : $7 ." KLINGONS : " #KLINGS @ 7 .R CR ; ( ENEMY LEFT )

These routines of course print both the contents of the game variables and the titles they require, so that the final routine below can pull the whole display together;

          : .STATS CUR @ NORM SRS LRS YELLOW? 1 0 20 9 SETWIN $2 $3 $4 $5

               CR $6 CR $7 SUBS CUR ! ; ( PRINT ALL STATS )

Now that our 3 major displays are complete we get to the next collection of utilities, followed by the game win and lose checking system.

          : GETD ." DIRECTION? " GET# DUP 1 < IF 2DROP ELSE 0 DO 10 M/

               LOOP DROP THEN ANG ! CR ; ( GET A DIRECTION )

This first routine gets a direction from the player for aiming the ship and weapons, using our get number routine defined at the start. Because our get number routine can allow that decimal points may be entered by the user, this routine calls upon a do loop to divide the result by 10 for every decimal point provided. In this way the do loop operation mimics the FIX or INT routine in other languages, returning only those digits to the left of the decimal point.

          : @K ( ADR -- Y X ) 8 /MOD SWAP ; ( REL ADR CHANGER )

This next routine is the relative addressing changer, used to convert a sector offset into an X and Y component.

          : ?KLINGS ( -- N ) 0 4 0 DO I 4 * KK + @ IF 1+ THEN LOOP ;

          ( HOW MANY KLINGS IN QUADRANT? )

Now we access the Klingon array to set up the battle computer inside the game, the routine above returning how many enemy ships are present in the quadrant. Since this value will change as the player engages the commands of the game, this array saves the energy level and relative offset location of each enemy ship.

          : FNDK ( REL -- REL F ) 1+ 0 OVER 64 SWAP DO OVER SECTOR + C@ 3

               = IF 1+ LEAVE ELSE SWAP 1+ 64 MIN SWAP THEN LOOP ;

               ( FIND A BAD-GUY )

This routine accepts a relative sector address and searches for the next occurrence of an enemy vessel, covering the maximum distance of one quadrant's sector size in the array and returning a flag to indicate the result. Of particular concern in this routine is the inclusion of a default False flag on the starting stack, followed by the limit of 64 units forward to scan the sector data. This means the routine will never search beyond the limit of one sector worth of data, and will always return a flag value when executed.

          : UP-KLINGS KK 4 4 * ERASE -1 4 0 DO FNDK IF KE I 4 * KK + >R

               R ! DUP R> 2+ ! THEN LOOP DROP ; ( SET-UP ENEMY ARRAY )

This routine sets up the Klingon data array for the battle system to access, first emptying the array and then scanning for 4 enemy ships within the quadrant. For each ship found the relative address and Klingon Energy state is moved to the array value, so the battle computer can use them during the game.

          : -KL ( A N -- A N ) 4 0 DO OVER I 4 * KK + 2+ @ = IF I 4 * KK

               + DUP 2+ @ SWAP -64 0 ROT 2! ." ZAP!!" >QUAD @ 3 DIGS 1-

               0-9 100 * + >QUAD ! 1 #KLINGS -! SECTOR + 0 SWAP C! THEN

               LOOP ; ( REMOVE A KLING )

This next routine performs the opposite function of the UP-KLINGS definition, accepting a relative sector address and reporting that a Klingon Ship has been destroyed. Although the value of N is not used by the routine, it does expect that N exists on the stack. The routine compares A to each Klingon's address position, then declares a kill if a match occurs and updates the game data.

          : SECT? ( N -- A F ) W->R -ROT 8 * + SECTOR + DUP C@ ROT IF

               DROP -1 THEN ;

               ( TRAVEL TO X,Y & RETRIEVE OBJECT IN SECTOR.

                  ERROR IF OUT OF RANGE. )

This routine accepts a vector length value for the current angle stored in the ANG variable, then converts the vector to a relative sector address to retrieve any object found at that location. Because the call made uses the W->R definition of the formula section, the vector is computed using the player ship's location as the origin.

          : ?CR CUR @ 160 MOD 145 > IF CR THEN ; ( IS CR NEEDED? )

And finally in the utility section before the battle area, we have the condition test for inserting carriage returns into the battle data. It does this by simply testing for which column the cursor resides after a display of any data, then inserts the return code when it reaches beyond this limit.

          : CK1 #KLINGS @ IF NORM CR ." IT IS STARDATE " SDATE ? ." AND "

               ." THERE ARE STILL " #KLINGS @ . ." KLINGON" IF ." S" THEN

               ." IN THE GALAXY." CR ." YOUR MISSION HAS FAILED, THE FED"

               ." ERATION WILL BE DESTROYED." CR ." YOU ARE DEAD." SP! RP!

               QUIT THEN ;

This routine is obvious, if any of the enemy ships remain in the game it prints an ending message, then discards both the return and parameter stacks and quits the program.

          : CK2 SDATE @ 100 MOD 98 > IF CK1 THEN ;

The second routine in this section is the "out of time" indicator, which tests the "star date" in the game versus the allowed 98 moves within the game. While the original game allowed only 40 "star dates" to be passed before declaring the end of the game, using the value of 99 moves allows us to test the functions of the program with greater leeway.

          : CK3 ENERGY @ 1 < IF NORM ." OUT OF SHIP'S ENERGY!" CR CK1

               THEN ;

This routine tests to see if the ship has run out of energy, meaning the vessel has no way to protect itself or to initiate a move from place to place. As before, this routine makes the test for any remaining enemy, then quits the program if this is the case.

          : CK4 #KLINGS @ 0= IF NORM ." CONGRATULATIONS ADMIRAL! YOU "

               ." HAVE SAVED THE FEDERATION!" CR SP! RP! QUIT THEN ;

Finally in the Win-Loss section this word tests for all enemy having been destroyed, declaring the player as the winner.

          : KILL? CK2 CK3 CK4 ;

And to make the reference to the above routines more simple we tie all the last tests together, making a single word out of them to add to our time-keeper later.

Now that we have the win-loss section written we can turn our attention to the game time-keeper, and in particular the section that deals with the Klingons attacking. This is because the enemy can attack on each movement of the vessel in addition to responding to the player's attacks. So, for this aspect of the game we address the attack of the competing vessels before building the time-keeper, starting with the infliction of damage to the player's ship;

          : KFIRE2 ( A KE -- A ) 10 / DAMAGE.SHLDS @ IF ." SHIELDS ARE "

               ." DOWN! " OUCH! ENERGY -! ELSE SHIELDS @ OVER < IF OUCH!

               THEN SHIELDS @ SWAP - DUP 0< IF DUP ENERGY +! THEN 0 MAX

               SHIELDS ! THEN ;

               ( IF SHIELDS OVER-POWERED, DISABLE DEVICE )

This routine takes the energy applied by an attacking Klingon and divides it by Ten, then tests for the Shields having been damaged. If they are disabled or the energy applied is greater than the shields as operating, a device is disabled and the ship's energy is deducted.

          : KFIRE1 ( A KE' -- A ) COND @ 3 = IF ." STAR BASE SHIELDS "

               ." PROTECT THE ENTERPRISE." DROP CR ELSE KFIRE2 THEN ;

               ( ALLOW STARBASE TO PROTECT US )

This portion tests the condition flag to see if the Enterprise is located next to a Star base, thus protecting the player from the Klingon attack.

          : KFIRE ( -- ) KK 4 0 DO DUP @ IF DUP @ 2 / 1 MAX RND OVER 2+ @

               >R R @K SWAP XSECT @ YSECT @ DIST ^.4 2 / / DUP 3 C? .#

               ." HIT FROM K@ " R> @K .X ." - " .Y ." LEFT:" OVER @ PLEAT

               SWAP - DUP .# 0 C? CR SWAP >R OVER ! R> KFIRE1 THEN 4+ LOOP

               DROP ; ( REPORT KLING ATTACK )

And lastly, this routine allows that all Klingons in a quadrant may attack the Enterprise, using up to one-half their available energy to fire their weapon. This complex routine also measures the distance between each Klingon and the Player's vessel, to compute the strength of the hit and print a report of the result. One of the interesting points for the player is that a Klingon's energy reserve is displayed after the attack is completed, giving the player yet another advantage. (Remove the ." LEFT:" and DUP .# sequences to hide this information to make the game more difficult.)

          : +TURN FIXIT 1 SDATE +! .STATS KFIRE KILL? ;

          ( ADVANCE GAME TIME & PRINT )

And here we get to the game time-keeper, which calls the repair routine, the enemy attack sequence, the status printing and the win-loss process while updating the "Star Date." Now that the process is complete we can turn to the game options, namely fighting back and moving the vessel;

          : EFIRE ( E -- ) ?KLINGS / 4 0 DO KK I 4 * + DUP @ IF 2+ >R DUP

               R @ @K YSECT @ XSECT @ DIST ^.4 OVER 30 * SWAP / DUP .#

               ." HIT ON K@ " R @ @K .X ." - " .Y R 2- @ SWAP - DUP

               ." LEFT: " .# DUP 0< IF R> @ SWAP -KL 2DROP ELSE R> 2- !

               THEN CR THEN DROP LOOP DROP ;

               ( DIVIDE ENERGY AMOUNG KLINGS AND FIRE )

This routine forms the first of two words used to allow the player to shoot back at the enemy, ostensibly using the ship's "Phasers" to strike a blow for justice. In this case the amount of energy to be applied to the beam weapon is divided among the attacking vessels, then the energy beams are launched in the appropriate directions. In reality of course the routine simply computes the distances between the Klingons and the Enterprise, then applies the divided energy factor and the 2.5 square root to the result. As before, remove the DUP ." LEFT: " .# sequence to make the game more difficult, which will hide the remaining energy of the attacked ships from the player.

          : PHASERS DAMAGE.PHSRS @ IF ." PHASERS ARE OUT." ELSE ." ENERG"

               ." Y TO FIRE: " GET# CR DUP 1 < IF 2DROP ELSE 0 DO 10 M/

               LOOP DROP THEN ENERGY @ OVER < ?KLINGS 0= OR IF ." SORRY."

               DROP ELSE EFIRE KFIRE THEN THEN .STATS ;

               ( SHOOT DAMMIT! )

This is the second routine required of the Phaser Fire Control process, which checks for Phaser damage and then requests the amount of energy desired. As you'd expect, the value is checked versus the amount of energy the ship contains, and allows that the enemy may shoot back afterwards. If an invalid value is entered or the phasers are damaged, this routine returns the appropriate response and treats the command as a no-operation.

The next few routines manage the "Photon Torpedo" operation provided in the game, which is a more complex process due to the tracking report and effectiveness of this weapon. That is to say, such a torpedo is capable of destroying most anything within its flight path, and that path is reported periodically as the flight progresses.

          : TORP1 ( A N -- ? N ) DUP 1 = IF ." STAR DESTROYED." CR 10 RND

               1 = IF 5 3 PICK C! >QUAD @ 4 DIGS 1+ 0-9 1000 * + >QUAD !

               ELSE 0 3 PICK C! >QUAD @ 1 DIGS 1- 0-9 + >QUAD ! THEN THEN ;

               ( STAR HIT? )

This first routine of the section is the one that determines if a star has been hit by the weapon, as defined by the value of N on the top of the stack. If a star is struck the relative address of A is used to remove the object from the sector data, then the galaxy array information is altered to reflect the change. Finally, if a random value of 1 to 10 comes up as 1 the routine alters the star's characteristic, creating a "black hole" both in the sector and galaxy data.

          : TORP2 ( A N -- ? N ) DUP 2 = IF CR ." GOOD SHOOTING, STAR "

               ." BASE DESTROYED!" CR 0 3 PICK C! 1 #BASES -! >QUAD @ 2

               DIGS 1- 0-9 10 * + >QUAD ! THEN ;

               ( BASE HIT? )

This second routine performs the same check for hitting a star base, removing a refueling station from the game's galaxy.

          : TORP3 ( A N -- ? N ) DUP 5 = IF 2 RND 1 = IF ." TORP OFF "

               ." SENSORS." ELSE ." CONTACT LOST." THEN CR THEN ;

               ( OOPS )

This version of the routine checks for a torpedo striking one of the altered stars in the first process, declaring that the missile has vanished into the event horizon. While the original game did not contain this option we've added it for more interest, plus provided that the game can respond in one of two ways in this event.

          : TORP4 ( A N -- ? N ) DUP 3 = IF SECTOR MINUS +UNDER -KL THEN ;

          ( KLING HIT? )

And finally in the detection section, this routine manages the striking of an enemy vessel to remove the attacking ship from the data arrays.

          : FIRET 0 16 0 DO DROP I P->R1K .## ." -" .## 3 SPACES ?CR I

               SECT? DUP 0< IF 1+ LEAVE ELSE TORP1 TORP2 TORP3 TORP4 THEN

               NIP IF LEAVE 1 ELSE 0 THEN LOOP 0= IF ." MISSED." THEN 1

               TORPS -! CR KFIRE .STATS ;

               ( FIRE TORP )

This portion of the fire routine actually does the tracking of the torpedo, by computing its location via a do loop process. For the body of this routine the formula for distance is scaled to use twice the value as provided by the 8 by 8 grid of the galaxy and its sectors, thus allowing that the report may print twice the number of points and use a finer approximation of the missile's position.

          : TORP DAMAGE.TORPS @ IF ." SORRY, PHOTON TUBES DAMAGED."

               ELSE TORPS @ IF GETD ." TRACKING:" FIRET ELSE

               ." NO TORPEDOES LEFT." THEN THEN CR ;

And of course, now we come to the highest function of the process; that of declaring the missile launcher as empty or damaged and getting the trajectory and tracking the weapon.

Next, we follow a similar process in regard to the ship movement, particularly the movement that takes place within the current quadrant. This is because the ship can move until it detects an object, where it stops in front of it.

          : IN-SECT? ( N -- A F ) 0. ROT 0 DO 2DROP I SECT? NIP DUP 0< IF

               LEAVE -1 SWAP ELSE I SWAP DUP IF LEAVE THEN THEN LOOP ;

               ( SCAN VECTOR FOR OBJECT, RETURN FLAG & LENGTH )

Like the movement of the photon torpedoes this routine accepts a vector length and scans the vector along the assigned angle for an object in the quadrant area. Unlike the previous routine it does not alter the data in any way, only returning a flag value and the address of where such vector ended. Finally, it returns the address of the new position in relative sector units or a minus flag if nothing was detected.

          : LCK ( A -- ) DUP SECT? DROP SECTOR - @K ." OBJECT @" .X ." -"

               .Y 1- SECT? DROP SECTOR - @K XSECT ! YSECT ! CR +TURN ;

               ( OBJECT REPORT )

This routine takes the address of the previous word and reports the presence of an object, then advances the ship's position to the location just ahead of the ending location in the vector. A more complex and higher difficulty option would be to allow this routine to detect the "black holes" created by collapsed stars in the torpedo section, thus ending the game by letting the player sink into these death traps.

          : WARP1 ( X Y -- ) YQUAD @ + 0-7 YQUAD ! XQUAD @ + 0-7 XQUAD !

               SET-E UP-KLINGS +TURN ;

               ( INTRA-QUADRANT MOVEMENT )

Next we come to the routine that moves the player from one quadrant to another, using the offset to X & Y to set the new position. After this is done the new location is scanned and constructed by the previous routines, the ship is placed, and the enemy is allowed to fire.

          : WARP ( N -- ) 16 IN-SECT? DUP 1 < IF 2DROP 2+ 4 / P->R WARP1

               ELSE IF LCK DROP ELSE 2DROP THEN THEN ;

               ( WARP TO NEXT QUAD )

This routine is the one called when the player engages the engines for intra-quadrant travel, or any "Warp Factor" greater than one. As stated earlier the routine first scans the vector chosen to detect any object when attempting to leave the current quadrant, then halts the ship's movement if such an object is detected. As before, this routine actually operates at twice the precision of the 8 by 8 array, adjusting the value given it to correct for the math.

          : SGN ( N -- D') DUP 0< IF DROP -1 ELSE 7 > IF 1 ELSE 0 THEN

               THEN ; ( RETURN OFFSET VECTOR FOR WARP )

This routine is designed to limit the offset factor for the X & Y values applied to the ship's location at sub-light speed, by returning the mathematical sign for any valid value. For those values out of range or greater than 7 in this case, the routine returns a zero to ignore the value.

          : SUBLIGHT ( N -- ) DUP 16 10 */ IN-SECT? DUP 0< IF 2DROP W->R

               DROP SGN SWAP SGN SWAP WARP1 ELSE IF LCK ELSE SECT? DROP

               SECTOR - @K XSECT ! YSECT ! +TURN THEN DROP THEN ;

               ( TRAVEL IN QUAD )

Next, this routine performs the WARP operation for the sub-light speeds of the vessel, applying the same vectors to the sector positions of the ship instead of the quadrant values.

          : CHKS ( N -- ) DUP 9 > DAMAGE.ENG @ 0= 0= AND IF ." ENGINES "

               ." ARE OFF-LINE." CR DROP ELSE DUP 9 > DAMAGE.ENG @ 0= AND

               ENERGY 149 > AND IF WARP ELSE 1+ SUBLIGHT THEN THEN ;

               ( CHECK SPEED AND CALL CORRECT FUNCTION )

This routine calls the appropriate vector application after checking that the engines are operational, and that the ship has the ability to move from place to place.

          : GETS ( -- N ) ." WARP? " GET# DUP 1 < IF 2DROP 10 * ELSE 1-

               DUP IF 0 DO 10 M/ LOOP ELSE DROP THEN DROP THEN CR ;

               ( GET SPEED*10 )

This helper word gets the speed value as entered by the user, returning a value ten times that entered.

          : NAVIGATE ENERGY @ 150 < IF ." INSUFFICIENT POWER -- CANNOT "

               ." ESTABLISH A WARP FIELD." CR ELSE DAMAGE.ENG @ IF

               ." WARP DRIVE DAMAGED, .2 MAX." CR THEN THEN GETD GETS CHKS ;

               ( MOVE THE SHIP )

And finally, this word checks if the engines are damaged or under-powered, then gets a heading and warp factor from the player for the movement command.

          : SHEI DAMAGE.SHLDS @ IF ." SHIELD GENERATOR IS OFF-LINE. " ELSE

               SHIELDS @ ENERGY @ + ." AVAILABLE POWER: " DUP . CUR @ >R

               BEGIN R CUR ! ." ENERGY TO SHIELDS? " GET# DUP 1 < IF 2DROP

               ELSE 0 DO 10 M/ LOOP DROP THEN 2DUP > UNTIL DUP SHIELDS ! -

               ENERGY ! RDROP THEN CR .STATS ;

               ( GET SHIELDS )

Now we come to the Shield Generator section where the player defines the strength of the ship's defenses, siphoning off the battery power to protect the vessel. In this version of the game none of this energy is lost except by strikes made by the enemy, though a minimum level is required to engage the engines. In more difficult games the ship loses energy both by firing weapons or making movements, which you may add as you feel is appropriate. (Though you will have to adjust the battery limits to do so.)

Now we get to the main command console used by the game, a dual input type of routine designed to limit the player's options;

          : CC1 ," COMMAND(NTPSDHQ)?" DUP COUNT TYPE 9 + BEGIN KEY DUP 58

               < IF 48 - OVER + C@ THEN 95 AND 2DUP 7 0 DO OVER C@ = IF

               DROP 0 LEAVE ELSE 1+ THEN OVER LOOP SWAP 0= IF EMIT CR 1

               ELSE 2DROP 0 THEN UNTIL NIP ;

               ( GET COMMAND CHARACTER )

This routine's first process is to print the command prompt for the user, then to accept a keystroke for the next operation. In doing this the key is checked versus the number keys of 0 to 6, which serve to extract the letters of NTPSDHQ from the command prompt itself. If the key given by the user is not a number the letter typed is raised to its upper case, then compared against the same letters in the command prompt. This allows that the player may use either the keypad or the main keyboard to enter commands for the game, providing a convenient interface to the game's operation.

          : CC2 ( C -- C ) 78 CASE: NAVIGATE :END 84 CASE: TORP :END 83 CASE:

               SHEI :END 68 CASE: DAMR :END 80 CASE: PHASERS :END ;

               ( FIRST HALF OF KEY PROCESS )

This routine uses the CASE: statement to test the character returned by the CC1 word versus the most common game operations. Only two command operations remain now, the Help and Quit.

          : SET-UP CLS HOME SEED-IT SECTORS 64 DUP * ERASE GALAXY 8 DUP *

               2 * ERASE DAMAGE 8 2 * ERASE 3000 ENERGY ! 1000 SHIELDS !

               10 TORPS ! 16 RND 20 + 100 * SDATE ! 0 #KLINGS ! 0 #BASES !

               REPORT1 PUT-E SET-E UP-KLINGS NORM CLS TITLE SRS LRS .STATS

               SUBS ;

Now we come to the start-up process, which empties the arrays, sets up the starting position, creates the galaxy and then sets up the display page.

          : H1 NORM CR ." YOUR MISSION: TO RID THE GALAXY OF THE KLINGON "

               ." MENACE." CR ." YOUR COMMANDS ARE NAVIGATION, TORPEDO LAUN"

               ." CH, PHASER CONTROL, SHIELD ENERGY, DAMAGE CONTROL, HELP "

               ." AND QUIT." CR KEY DROP CLS TITLE SRS LRS .STATS SUBS ;

               ( A HELP SCREEN, BUT I'M TOO LAZY TO WRITE IT ALL )

Finally, we come to the help loop, explaining the game and the purpose.

          : REMAIN BEGIN CC1 CC2 72 = IF H1 THEN 81 = UNTIL SP! ;

          : MAIN SET-UP KFIRE REMAIN NORM ;

And at long last, we come to the main routine, which has been factored to allow us to stop the game and restart it for testing.

          : // NORM CLS TITLE SRS LRS .STATS 13 0 80 12 SETWIN ;

          ( TESTING FUNCTION )

And here is the last entry in the listing, a test word to restore the game display before we call REMAIN to continue testing. Though this version of the game does not include all options available, or any on-screen animation as developed after the original was written, this game is sufficient for giving you a feel of how Forth operates.

Additional Reading

If you require additional material for becoming familiar with the Forth style and manner of programming, I suggest my Pygmy Forth Tutor (pftutr22.zip) on SimTel net, or the book Starting Forth by Brodie. Though I believe this manual is sufficient to introduce you to Forth programming, only hours of working with the language will make you comfortable with it. The above Tutor also contains a full compiler and its ASM source, plus a debugger so you can witness how each operation takes place. Pygmy Forth was written by Frank C. Sergeant.

Return to Contents.   Next Chapter.   Previous Chapter.