8 Variables, Constants, and Arrays

As we have seen throughout the previous seven chapters, Forth programmers use the stack to store numbers temporarily while they perform calculations or to pass arguments from one word to another. When programmers need to store numbers more permanently, they use variables and constants.

In this chapter, we'll learn how Forth treats variables and constants, and in the process we'll see how to directly access locations in memory.


Let's start with an example of a situation in which you'd want to use a variable--to store the day's date. First we'll create a variable called DATE. We do this by saying


If today is the twelfth, we now say

	12 DATE !
that is, we put twelve on the stack, then give the name of the variable, then finally execute the word !, which is pronounced store. This phrase stores the number twelve into the variable DATE.

Conversely, we can say

that is, we can name the variable, then execute the word @, which is pronounced fetch. This phrase fetches the twelve and puts it on the stack. Thus the phrase
	DATE @ . 12 ok 
prints the date.

To make matters even easier, there is a Forth word whose definition is this:

	: ?   @ . ;

So instead of "DATE-fetch-dot," we can simply type

	DATE ? 12 ok 

The value of DATE will be twelve until we change it. To change it, we simply store a new number

	13 DATE ! ok 
	DATE ? 13 ok 

Conceivably we could define additional variables for the month and year:

then define a word called !DATE (for "store-the-date") like this:
	: !DATE  YEAR !  DATE !  MONTH ! ;
to be used like this:
	7 31 03 !DATE ok 
then define a word called .DATE (for "print-the-date") like this:
	: .DATE  MONTH ?  DATE ?  YEAR ? ;

Your Forth system already has a number of variables defined; one is called BASE. BASE contains the number base that you're currently working in. In fact, the definition of HEX and DECIMAL (and OCTAL, if your system has it) are simply

	: DECIMAL  10 BASE ! ;
	: HEX      16 BASE ! ;
	: OCTAL     8 BASE ! ;

You can work in any number base by simply storing it into BASE.

For Experts

A three-letter code such as an airport terminal name, can be stored as a single-length unsigned number in base 36. For example:
	: ALPHA  36 BASE ! ; ok 
	ALPHA ok 
	ZAP U. ZAP ok 

Somewhere in the definitions of the system words which perform input and output number conversions, you will find the phrase

because the current value of BASE is used in the conversion process. Thus a single routine can convert numbers in any base. This leads us to make a formal statement about the use of variables:
In Forth, variables are appropriate for any value that is used inside a definition which may need to change at any time after the definition has already been compiled.

A Closer Look at Variables

When you create a variable such as DATE by using the phrase

you are really compiling a new word, called DATE, into the dictionary. A simplified view would look like the view below.
4 D A T E
instruction code
appropriate for
space for the
actual value
to be stored

DATE is like any other word in your dictionary except that you defined it with the word VARIABLE instead of the word :. As a result, you didn't have to define what your definition would do, the word VARIABLE itself spells out what is supposed to happen. And here is what happens:

When you say

	12 DATE !
Twelve goes onto the stack, after which the text interpreter looks up DATE in the dictionary and, finding it, points it out to EXECUTE.

execute ! EXECUTE executes a variable by copying the address of the variable's "empty" cell (where the value will go) onto the stack.

! does it The word ! takes the address (on top) and the value (underneath), and stores the value into that location. Whatever number used to be at that address is replaced by the new number.

(To remember what order the arguments belong in, think of setting down your parcel, then sticking the address label on top.)

execute @ The word @ expects one argument only: an address, which in this case is supplied by the name of the variable, as in


Using the value on the stack as an address, the word @ pushes the contents of that location onto the stack, "dropping" the address. (The contents of the location remain intact.)

Using a Variable as a Counter

In Forth, a variable is ideal for keeping a count of something. To reuse our egg-packer example, we might keep track of how many eggs go down the conveyor belt in a single day. (This example will work at your terminal, so enter it as we go.)

First we can define

to keep the count in. To start with a clean slate every morning, we could store a zero into EGGS by executing a word whose definition looks like this:
	: RESET  0 EGGS ! ;

Then somewhere in our egg-packing application, we would define a word which executes the following phrase every time an egg passes an electric eye on the conveyor:

	1 EGGS +!
The word +! adds the given value to the contents of the given address. (It doesn't bother to tell you what the contents are.) Thus the phrase
	1 EGGS +!
increments the count of eggs by one. For purposes of illustration, let's put this phrase inside a definition like this:
	: EGG  1 EGGS +! ;

At the end of the day, we would say

to find out how many eggs went by since morning.

Let's try it:

	RESET ok 
	EGG ok 
	EGG ok 
	EGG ok 
	EGGS ? 3 ok 

Here's a review of the words we've covered in the chapter so far:

VARIABLE xxx ( -- )
xxx: ( -- addr )
Creates a variable named xxx; the word xxx returns its address when executed. variable
! ( n addr -- ) Stores a single-length number into the address. store
@ ( addr -- n ) Replaces the address with its contents. fetch
? ( addr -- ) Prints the contents of the address, followed by one space. question
+! ( n addr -- ) Adds a single-length number to the contents of the address. plus-store


5 L I M I T
instruction code
appropriate for

While variables are normally used for values that may change, constants are used for values that won't change. In Forth, we create a constant and set its value at the same time, like this:


Here we have defined a constant named LIMIT, and given it the value 220. Now we can use the word LIMIT in place of the value, like this:

	: ?TOO-HOT  LIMIT > IF  ." Danger -- reduce heat "  THEN ;

If the number on the stack is greater than 220, then the warning message will be printed.

Notice that when we say

we get the value, not the address. We don't need the "fetch."

This is an important difference between variables and constants. The reason for the difference is that with variables, we need the address to have the option of fetching or storing. With constants we always want the value; we absolutely never store. (If you really need to store a new value into a "constant", you should use a VALUE.)

One use for constants is to name a hardware address. For example, a microprocessor-controlled portable camera application might contain this definition:


Here the word SHUTTER has been defined as a constant so that execution of SHUTTER returns the hardware address of the camera's shutter. It might, for example, be defined:


The words OPEN and CLOSE might be defined simply as

	: OPEN  1 SWAP ! ;
	: CLOSE 0 SWAP ! ;

so that the phrase

writes a "1" to the shutter address, causing the shutter to open.

Here are some situations when it's good to define numbers as constants:

  1. When it's important that you make your application more readable. One of the elements of Forth style is that definitions should be self-documenting, as is the definition of PHOTOGRAPH above.
  2. When it's more convenient to use a name instead of the number. For example, if you think you may have to change the value (because, for instance, the hardware might get changed) you will only have to change the value once--in the file where the constant is defined--then recompile your application.
  3. (Only true for less sophisticated Forth compilers) When you are using the same value many times in your application. In the compiled form of a definition, reference to a constant requires less memory space.

CONSTANT xxx ( n -- )
xxx: ( -- n )
Creates a constant named xxx with the value n; the word xxx returns n when executed.

Double-length Variables and Constants

You can define a double-length variable by using the word 2VARIABLE. For example,


Now you can use the Forth words 2! (pronounced two-store) and 2@ (pronounced two-fetch) to access this double-length variable. You can store a double-length number into it by simply saying

	800,000 DATE 2!
and fetch it back with
	DATE 2@ D. 800000 ok 

Or you can store the full month/date/year into it, like this:

	7/17/03 DATE 2!
and fetch it back with
	DATE 2@ .DATE 7/17/03 ok 
assuming that you've loaded the version of .DATE we gave in the last chapter.

You can define a double-length constant by using the Forth word 2CONSTANT, like this:


Now the word APPLES will place the double-length number on the stack.

	APPLES D. 200000 ok 

Of course, we can do:

	: MUCH-MORE  200,000 D+  MUCH D+ ;
in order to be able to say
	APPLES MUCH-MORE D. 800000 ok 

As the prefix "2" reminds us, we can also use 2CONSTANT to define a pair of single-length numbers. The reason for putting two numbers under the same name is a matter of convenience and of saving space in the dictionary.

As an example, recall (from Chap. 5) that we can use the phrase

	355 113 */
to multiply a number by a crude approximation of p. We could store these two integers as a 2CONSTANT as follows:
	355 113 2CONSTANT PI
then simply use the phrase
	PI */
as in
	10000 PI */ . 31415 ok 

Here is a review of the double-length data-structure words:

2CONSTANT xxx ( d -- )
xxx: ( -- d )
Creates a double-length constant named xxx with the value d; the word xxx returns d when executed. two-constant
2VARIABLE xxx ( -- )
xxx: ( -- addr )
Creates a double-length variable named xxx; the word xxx returns its address when executed. two-variable
2! ( d addr -- ) Stores a double-length number into the address. two-store
2@ ( addr -- d ) Returns the double-length contents of the address. two-fetch


4 D A T E
room for a
single-length value

As you know, the phrase

creates a definition which conceptually looks like that at the right.

Now if you say

an additional cell is allotted in the definition, like this:

4 D A T E
room for a

The result is the same as if you had used 2VARIABLE. By changing the argument to ALLOT, however, you can define any number of variables under the same name. Such a group of variables is called an "array."

For example, let's say that in our laboratory, we have not just one, but five burners that heat various kinds of liquids.

We can make our word ?TOO-HOT check that all five burners have not exceeded their individual limit if we define LIMIT using an array rather than a constant.

Let's give the array the name LIMITS, like this:


The phrase "4 CELLS ALLOT" gives the array an extra four cells (five cells in all).
6 L I M I T S
room for burner-0's limit
room for burner-1's limit
room for burner-2's limit
room for burner-3's limit
room for burner-4's limit

Suppose we want the limit for burner 0 to be 220. We can store this value by simply saying

	220 LIMITS !
because LIMITS returns the address of the first cell in the array. Suppose we want the limit for burner 1 to be 340. We can store this value by adding 1 CELLS to the address of the original cell, like this:
	340 LIMITS 1 CELLS + !

We can store limits for burners 2, 3, and 4 by adding the "offsets" 2 CELLS, 3 CELLS, and 4 CELLS, respectively, to the original address. We can define the convenient word

	: LIMIT  ( burner# -- addr ) CELLS LIMITS + ;
to take a burner number on the stack and compute an address that reflects the appropriate offset.

Now if we want the value 170 to be the limit for burner 2, we simply say

	170 2 LIMIT !
or similarly, we can fetch the limit for burner 2 with the phrase
	2 LIMIT ? 170 ok 

This technique increases the usefulness of the word LIMIT, so that we can redefine ?TOO-HOT as follows:

	: ?TOO-HOT ( temp burner# -- )
	LIMIT @ > IF  ." Danger -- reduce heat "  THEN ;
which works like this:
	210 0 ?TOO-HOT ok 
	230 0 ?TOO-HOT Danger -- reduce heat ok 
	300 1 ?TOO-HOT ok 
	350 1 ?TOO-HOT Danger -- reduce heat ok 


Another Example -- Using an Array for Counting

Meanwhile, back at the egg ranch:

Here's another example of an array. In this example, each element of the array is used as a separate counter. Thus we can keep track of how many cartons of "extra large" eggs the machine has packed, how many "large," and so forth.

Recall from our previous definition of EGGSIZE (in Chap. 4) that we used four categories of acceptable eggs, plus two categories of "bad eggs."


So let's create an array that is six cells long:


The counts will be incremented using the word +!, so we must be able to set all the elements of the array to zero before we begin counting. The phrase

will fill 6 cells , starting at the address of COUNTS, with zeros. If your Forth system includes the word ERASE, it's better to use it in this situation. ERASE fills the given number of bytes with zeroes. Use it like this:

FILL ( addr n b -- ) Fills n bytes of memory, beginning at the address, with value b.
ERASE ( addr n -- ) Stores zeroes into n bytes of memory, beginning at the address.

For convenience, we can put the phrase inside a definition, like this:


Now let's define a word which will give us the address of one of the counters, depending on the category number it is given (0 through 5), like this:

and another word which will add one to the counter whose number is given, like this:

The "1" serves as the increment for +!, and SWAP puts the arguments for +! in the order they belong, i.e., ( n addr -- ).

Now, for instance, the phrase

will increment the counter that corresponds to large eggs.

Now let's define a word which converts the weight per dozen into a category number:

	: CATEGORY ( weight -- category )
	  DUP 18 < IF   REJECT      ELSE
	  DUP 21 < IF   SMALL       ELSE
	  DUP 24 < IF   MEDIUM      ELSE
	  DUP 27 < IF   LARGE       ELSE
	THEN THEN THEN THEN THEN  NIP ;We'll see a simpler definition soon.

(By the time we'll get to the NIP, we will have two values on the stack: the weight which we have been DUPping and the category number, which will be on top. We want only the category number; "NIP" eliminates the weight.)

For instance, the phrase

will leave the number 3 (LARGE) on the stack. The above definition of CATEGORY resembles our old definition of EGGSIZE, but, in the true Forth style of keeping words as short as possible, we have removed the output messages from the definition. Instead, we'll define an additional word which expects a category number and prints an output message, like this:
	: LABEL ( category -- )
	    REJECT      OF ." reject "      ENDOF
	    SMALL       OF ." small "       ENDOF
	    MEDIUM      OF ." medium "      ENDOF
	    LARGE       OF ." large "       ENDOF
	    EXTRA-LARGE OF ." extra large " ENDOF
	    ERROR       OF ." error "       ENDOF

For example:

	SMALL LABEL small ok 

Now we can define EGGSIZE using three of our own words:


Thus the phrase

will print
	medium ok 
at your terminal and update the counter for medium eggs.

How will we read the counters at the end of the day? We could check each cell in the array separately with a phrase such as

(which would tell us how many "large" cartons were packed). But let's get a little fancier and define our own word to print a table of the day's results in this format:
	    1			reject
	  112			small
	  132			medium
	  143			large
	  159			extra large
	    0			error

Since we have already devised category numbers, we can simply use a DO and index on the category number, like this:

	: REPORT ( -- )
		6 0 DO	 I COUNTER @ 5 U.R
			 7 SPACES
		  LOOP ;

(The phrase "I COUNTER @ 5 U.R" takes the category number given by I, indexes into the array, and prints the contents of the proper element in a five-column field.)

Factoring Definitions

This is a good time to talk about factoring as it applies to Forth definitions. We've just seen an example in which factoring simplified our problem.

Our first definition of EGGSIZE from Chap. 4, categorized eggs by weight and printed the name of the categories at the terminal. In our present version we factored out the "categorizing" and the "printing" into two separate words. We can use the word CATEGORY to provide the argument either for the printing word or the counter-tallying word (or both). And we can use the printing word, LABEL, in both EGGSIZE and REPORT.

As Charles Moore, the inventor of Forth, has written:

A good Forth vocabulary contains a large number of small words. It is not enough to break a problem into small pieces. The object is to isolate words that can be reused.

For example, in the recipe:

Get a can of tomato sauce.
Open can of tomato sauce.
Pour tomato sauce into pan.
Get can of mushrooms.
Open can of mushrooms.
Pour mushrooms into pan.

you can "factor out" the getting, opening, and pouring, since they are common to both cans. Then you can give the factored-out process a name and simply write:

and any chef who's graduated from the Postfix School of Cookery will know exactly what you mean.

Not only does factoring make a program easier to write (and fix!), it saves memory space, too. A reusable word such as ADD gets defined only once. The more complicated the application, the greater the savings.

Here is another thought about Forth style before we leave the egg ranch. Recall our definition of EGGSIZE


CATEGORY gave us a value which we wanted to pas on to both LABEL and TALLY, so we included the DUP. To make the definition "cleaner," we might have been tempted to take the DUP out and put it inside the definition of LABEL, at the beginning. Thus we might have written:


where CATEGORY passes the value to LABEL, and LABEL passes it on to TALLY. Certainly this approach would have worked. But then, when we defined REPORT, we would have had to say

instead of simply

Forth programmers tend to follow this convention: when possible, words should destroy their own parameters. In general, it's better to put the DUP inside the "calling definition" (EGGSIZE, here) than in the "called" definition (LABEL, here).

Another Example -- "Looping" through an Array

We'd like to introduce a little technique that is relevant to arrays. We can best illustrate this technique by writing our own definition of a Forth word called DUMP. DUMP is used to print out the contents of a series of memory addresses. The usage is

	addr count DUMP

For instance, we could enter

to print the contents of our egg-counting array called COUNTS. Since DUMP is primarily designed as a programming tool to print out the contents of memory locations, it prints either byte-by-byte or cell-by-cell, depending on the type of addressing our computer uses. Our version of DUMP will print cell-by-cell.

Obviously DUMP will involve a DO loop. The question is: what should we use for an index? Although we might use the count itself (0 - 6) as the loop index, it's better to use the address as the index.

The address of COUNTS will be the starting index for the loop, while the address plus the count will serve as the limit, like this:

	: DUMP ( addr cell-count -- )
		           DO   CR I @ 5 U.R  

The key phrase here is

which immediately precedes the DO. bounds

The ending and starting addresses are now on the stack, ready to serve as the limit and index for the DO loop. Since we are "indexing on the addresses," once we are inside the loop we merely have to say

	I @  5 U.R
to print the contents of each element of the array. Since we are examining cells (@ fetches a single-length, single cell value), we increment the index by one cell each time, by using

Byte Arrays

Forth lets you create an array in which each element consists of a single byte rather than a full cell. This is useful any time you are storing a series of numbers whose range fits into that which can be expressed within eight bits.

The range of an unsigned 8-bit number is 0 to 255. Byte arrays are also used to store ASCII character strings. The benefit of using a byte array instead of a cell array is that you can get the same amount of data in 25% (32-bit Forth) of the memory space.

The mechanics of using a byte array are the same as using a cell array except that

  1. you don't have to use CELLS to manipulate the offset, since each element corresponds to one address unit, and
  2. you must use the words C! and C@ instead of ! and @. These words, which operate on byte values only, have the prefix "C" because their typical use is accepting ASCII characters.

C! ( b addr -- ) Stores an 8-bit value into the address. c-store
C@ ( addr -- b ) Fetches an 8-bit value from the address. c-fetch

Initializing an Array

Many situations call for an array whose values never change during the operation of the application and which may as well be stored into the array at the same time that the array is created, just as CONSTANTs are. Forth provides the means to accomplish this through the two words CREATE and , (pronounced create and comma).

Suppose we want permanent values in our LIMITS array. Instead of saying

we can say
	CREATE LIMITS  220 , 340 , 170 , 100 , 190 ,

Usually the above line would be included from a disk file, but it also works interactively.

Like the word VARIABLE, CREATE puts a new name in the dictionary at compile time and returns the address of that definition when it is executed. But it does not "allot" any bytes for a value.

The word , takes a number off the stack and stores it into the array. So each time you express a number and follow it with ,, you add one cell to the array.

For Newcomers

Ingrained habits, learned from English writing, lead some newcomers to forget to type the final , in the line. Remember that , does not separate the numbers, it compiles them.

You can access the elements in a CREATE array just as you would the elements in a VARIABLE array. For example:

	LIMITS CELL+ @ . 340 ok 

You can even store new values into the array, just as you would into a VARIABLE array.

To initialize a byte-array that has been defined with CREATE, you can use the word C, (c-comma). For instance, we could store each of the values used in our egg-sorting definition CATEGORY as follows:

	CREATE SIZES 18 C, 21 C, 24 C, 27 C, 30 C, 255 C,

This would allow us to redefine CATEGORY using a DO loop rather than as a series of nested IF...THEN statements, as follows


Note that we have added a maximum (255) to the array to simplify our definition regarding category 5.

Including the initialization of the SIZES array, this version takes only three lines of source text as opposed to six and takes less space in the dictionary, too.

For People Who Don't Like Guessing How It Works

The idea here is this: since there are five possible categories, we can use the category numbers as our loop index. Each time around, we compare the number on the stack against the element in SIZES, offset by the current loop index. As soon as the weight on the stack is greater than one of the elements in the array, we leave the loop and use I to tell us how many times we had looped before we "left." Since this number is our offset into the array, it will also be our category number.

Here's a list of the Forth words we've covered in this chapter:

CONSTANT xxx ( n -- )
xxx: ( -- n )
Creates a constant named xxx with the value n; the word xxx returns n when executed.
VARIABLE xxx ( -- )
xxx: ( -- addr )
Creates a variable named xxx; the word xxx returns its address when executed.
CREATE xxx ( -- )
xxx: ( -- addr )
Creates a dictionary entry (head and code pointer only) named xxx; the word xxx returns its address when executed.
! ( n addr -- ) Stores a single-length number into the address.
@ ( addr -- n ) Replaces the address with its contents.
? ( addr -- ) Prints the contents of the address, followed by one space.
+! ( n addr -- ) Adds a single-length number to the contents of the address.
ALLOT ( n -- ) Adds n bytes to the body of the most recently defined word.
, ( n -- ) Compiles n into the next available cell in the dictionary.
C! ( b addr -- ) Stores an 8-bit value into the address.
C@ ( addr -- b ) Fetches an 8-bit value from the address.
FILL ( addr n b -- ) Fills n bytes of memory, beginning at the address, with value b.
BASE ( n -- ) A variable which contains the value of the number base being used by the system.
2CONSTANT xxx ( d -- )
xxx: ( -- d )
Creates a double-length constant named xxx with the value d; the word xxx returns d when executed.
2VARIABLE xxx ( -- )
xxx: ( -- addr )
Creates a double-length variable named xxx; the word xxx returns its address when executed.
2! ( d addr -- ) Stores a double-length number into the address.
2@ ( addr -- d ) Returns the double-length contents of the address.
C, ( b -- ) Compiles b into the next available byte in the dictionary.
DUMP ( addr u -- ) Displays u bytes of memory, starting at the address.
ERASE ( addr n -- ) Stores zeroes into n bytes of memory, beginning at the address.

n, n1, ... single-length signed
d, d1, ... double-length signed
u, u1, ... single-length unsigned
ud, ud1, ... double-length unsigned
addr address
c ASCII character value
b 8-bit byte
f Boolean flag

Review of Terms

Array a series of memory locations with a single name. Values can be stored and fetched into the individual locations by giving the name of the array and adding an offset to the address.
Constant a value which has a name. The value is stored in memory and usually never changes.
Factoring as it applies to programming in Forth, simplifying a large job by extracting those elements which might be reused and defining those elements as operations.
Fetch to retrieve a value from a given memory location.
Initialize to give a variable (or array) its initial value(s) before the rest of the program begins.
Offset a number which can be added to the address of the beginning of an array to produce the address of the desired location within the array.
Store to place a value in a given memory location.
Variable a location in memory which has a name and in which values are frequently stored and fetched.

Problems -- Chapter 8

    1. Write two words called BAKE-PIE and EAT-PIE. The first word increases the number of available PIES by one. The second decreases the number by one and thanks you for the pie. But if there are no pies, it types "What pie?" (make sure you start out with no pies.)
      	EAT-PIE What pie? 
      	BAKE-PIE ok 
      	EAT-PIE Thank you! ok 
    2. Write a word called FREEZE-PIES which takes all the available pies and adds them to the number of pies in the freezer. Remember that frozen pies cannot be eaten.
      	PIES ? 0 ok 
      	FROZEN-PIES ? 2 ok 
  1. Define a word called .BASE which prints the current value of the variable BASE in decimal. Test it by first changing BASE to some value other than ten. (This one is trickier than it may seem.)
    	DECIMAL .BASE 10 ok 
    	HEX .BASE 16 ok 
  2. Define a number-formatting word called M. which prints a double-length number with a decimal point. The position of the decimal point witin the number is movable and depends on the value of a variable that you will define as PLACES. For example, if you store a "1" into PLACES, you will get
    	200,000 M. 20000.0 ok 
    that is, with the decimal point one place from the right. A zero in PLACES should produce no decimal point at all. [answer]
  3. In order to keep track of the inventory of colored pencils in your office, create an array, each cell of which contains the count of a different colored pencil. Define a set of words so that, for example, the phrase
    returns the address of the cell that contains the count of red pencils, etc. Then set these variables to indicate the following counts:
    	23 red pencils
    	15 blue pencils
    	12 green pencils
    	0 orange pencils
  4. A histogram is a graphic representation of a series of values. Each value is shown by the height or length of a bar. In this exercise you will create an array of values and print a histogram which displays a line of "*"s for each value. First create an array with about ten cells. Initialize each element of the array with a value in the range of zero to seventy. Then define a word PLOT which will print a line for each value. On each line print the number of the cell followed by a number of "*"s equal to the contents of that cell.

    For example, if the array has four cells and contains the values 1, 2, 3 and 4, then PLOT would produce:

    	1 *
    	2 **
    	3 ***
    	4 ****
  5. Create an application that displays a tic-tac-toe board, so that two human players can make their moves by entering them from the keyboard. For example, the phrase
    	4 X!
    puts an "X" in box 4 (counting starts with 1) and produces this display:
    	  |   |
    	X |   | 
    	  |   |
    Then the phrase
    	3 O!
    puts an "O" in box 3 and prints the display:
    	  |   | O
    	X |   | 
    	  |   |
    Use a byte array to remember the contents of the board, with the value 1 to signify "X," a -1 to signify a "O," and a 0 to signify an empty box. [answer]
Valid HTML 3.5