Oxo Flavoured Forth Richard Armstrong of Ayrshire wrote this program in Abersoft Forth and we decided to print it for all addicts of this language! This program was written using a 48K Spectrum and the FORTH implementation created by Abersoft. It uses around 8K of memory but could probably be used with FORTH systems having fewer than 8 screens (e.g. Artic FORTH) if the screens in the listing were entered and compiled one at a time. All the FORTH is fairly standard except for the graphics routines in screens 1, 3, 4 and 5 bit these could easily be replaced with standard graphics available on any system. [In fact, with a bit of effort it proved even more port- able than that. Having recently finished entering a Forth implementation by David Millington, published in 1984 by Your Computer, I thought I'd see if it was powerful enough to deal with this program. It was, but not without some additions. Luckily additions to the system are Forth's selling point. In order to run the resulting program, you don't need anything but a 48K Spectrum (or emulator). Mr. Millington's Forth allows you to save the compiled program in a state in which it can be called from basic - unlike, say, Aberforth. Just LOAD "OXO" and enjoy playing the game. If, however, you want to load and examine the program code, you will need the Forth system (which should be available from World of Spectrum, and possibly elsewhere as well); the toolset dictionary I wrote to port this program, which comes with the TZX of the compiler I have uploaded but which, just to be sure, I've also appended to the TZX of OXO; and, of course, OXO's source code, which is also on the TZX. There are two things to note about loading this source. First, you must load the toolset before you load the OXO source. More trickily, the source is larger than the normal Forth code size. This causes some problems if you want to edit the code, but viewing, printing and running it should work. You need to change the Forth Basic program to deal with it, though. Break in before loading the toolset, then change the 3004 in line 140 to 4500, and the 3001 in lines 3060 and 3065 to 4497. Then RUN, and load the toolset, then the code. It will take a long time to compile (this is normal for the system, not a result of the changes), but you can now have a rummage around the program. Don't forget to re-load the Forth system from scratch if you want to use another Forth program afterwards! The rest of this text file is the original text of the article, which presumes that you're using Abersoft Forth. Some of this is not applicable to the current implementa- tion (for example, because Mr. Millington has mercifully dispensed with the daft idea of "screens"), but most of it is, more or less. As it would be impossible to separate the two cleanly, I'll just give the entire text.] The function of the program is to play a game of noughts and crosses with the player trying to place three X's in a row and the computer trying to do likewise with 0's. The reasonably uncomplicated nature of this game has enabled me to concentrate on good programming style rather than on designing ungainly program routines to perform difficult tasks. To use the program with a Spectrum running Abersoft FORTH, simply type in screens 1-9 as shown in the listing then enter 9 LOAD. After the 'ok' message is displayed, enter the word LOADER and the text of the program will be compiled to machine code in about 35 seconds after which the message 'READY' will be displayed. When entering the program you should note the following points: Screen one is used to define graphic characters and so should only be used with Abersoft FORTH. In lines 1, 2, 4 and 5 of screen 3, lines 1, 2 and 3 of screen 4 and lines 1, 2 and 3 of screen 5 the capital letters inside string quotes should be entered from graphics mode since these are the U.D.G.'s defined in screen 1. Screen 9 should contain nothing but the definition for the word LOADER. After the message READY is displayed, the game can be started by entering RUN. A 3x3 grid is then drawn and scores for the player and Spectrum are displayed. A random function is used to determine whether the computer or player moves first. When prompted by the message 'Your Move', the player should make a move by pressing the number key on the Spectrum's keyboard corresponding to the number in the square he wishes to move into. For example, to place an X in the square at the centre of the grid press key '5'. The computer will ignore keys outside the range 1-9, or keys corresponding to squares that are already occupied by an X or a 0. The game ends when either the player or com- puter has won or the grid is full. When this happens the player will be asked if he would like another game and should respond by pressing Y (for Yes) or N (for No). The computer makes its moves by using the following algorithm: * 1 Look for two 0's in a row with a space beside them. If you find this situation then place another 0 in the space and so win the game. * 2 If you can't satisfy the above requirements then look for two X's in a row with a space between them. If you come across this situation place a 0 in the space, so preventing your opponent from winning the game. * 3 If neither of the above two steps can be carried out then pick a random, empty corner (squares 1, 3, 7 and 9) and put a 0 in it. * 4 If no steps have been carried out then search through all the corners and put a 0 in the first empty corner you come across. * 5 If none of steps 1-4 can be completed then pick a random square anywhere on the board and put a 0 in it if it is empty. * 6 If all the above steps fail, search through each square on the grid and place a 0 in the first empty one you find. FORTH is a language which is becoming increasingly popular and widely used, especially in industry and schools, so I feel that more FORTH programs should be featured in the popular computing press. I hope that this program will encourage other FORTH enthusiasts to have more of their work published. As well as being able to use SAVET to save screens to tape, it is also possible to save screens to the ZX Micro- drive by using the following commands from FORTH: MON (to re-enter BASIC) NEW CLEAR 50000 SAVE *"m";1;"Disc"CODE 53248,11263 The above commands result in the obliteration of the FORTH compiler from memory and so should only be used at the end of a programming session. To reload FORTH text screens from Microdrive, switch on the Spectrum and enter CLEAR 50000 LOAD *"m";1;"Disc"CODE 53248 NEW Then load the FORTH compiler from tape as usual. ___________________________________________________________ Variables [Millington's Forth does not allow variable names of more than one letter; this table gives the original name for those who want to read the official source code, and the name I've used instead.] LIN, COL [l,c] Store line and column values used when printing an X or a 0. BOARD [b@ ] An array of 9 bytes, each byte corres- ponding to a square on the grid. A byte contains 0 if the corresponding square contains a 0, 1 if the square contains an X and 2 if the square is empty. BOX [o ] Used by various words to store the value of a square. CORNERS [r@ ] An array of 4 bytes used to store off- sets to be added to the value of BOARD [b@] to give the address of a corner. CHAR [h ] Used by words dealing with both X's and 0's (e.g. WIN). If this variable has value 1 then the word is dealing with an X. If the value is 0 then the word is dealing with a 0. PLAYER, COMP [p,m] Used to keep the score for the player and the computer. ___________________________________________________________ Forth Words The words used in the program have the following effects when executed: DEFINE Used to produce User Defined Graphics. PICTURE Draws the grid onto the television screen, prints the scores for the player and com- puter and sets all bytes in the array BOARD to 2 (i.e. empty). CO-ORDS Finds the values of LIN and COL correspon- ding to the square whose number is on top of the stack. DRAWX Draws an X on screen in the square whose top left hand corner is specified by the values of LIN and COL. CLEAN Checks to see if the square indicated by BOX is empty. HIGH, LOW Check that the value of BOX is in the range 1-9. XIN Accepts a number from the keyboard and prints an X in the corresponding square. DRAW-O Draws a 0 in the square specified by BOX. [No, I don't know why it's DRAWX without hyphen and DRAW-O with, either.] 2ROW Checks if there are two X's or 0's in a row. [Millington's Forth does not allow words starting with a digit, so I've called this tworow.] CORNER Searches for an empty corner. RANDOM Produces a random number between 0 and the number on the stack. This word could be of use in other programs. [Barely. Much better RNGs can easily be written, even in Forth. It's good enough for this program, though.] ANYSQUARE Chooses a random, empty square. SYSTEMATIC Searches the entire grid to find an empty square. RANDCORNR Chooses a random, empty corner. [Why not RANDCORNER? I don't know. Mine is called randcorner.] LOOK Checks to see if a square is empty. 2INROW Checks the whole grid to find two X's or 0's in a row. [Called twoinrow in the port, for the same reason as 2ROW.] FIND-O Finds an empty square for 0. NEWGAME? Checks to see if Y or N is being pressed in response to the prompt given at the end of a game. 3LINE Used to check for three X's or 0's in a row. [Called threeline in my version, as above.] WIN Checks to see if the player or computer has won the game. FULL Checks to see if there are no empty squares in the grid. XWINS Prints a message indicating that the player has won the game. OWINS Prints a message indicating that the computer has won the game. XMOVE, OMOVE Lets the player or the computer make a move. TIE Indicates a draw. XSTART, Lets the player or computer make the first OSTART move. PLAY Draws the grid and then decided whether the player or computer will move first by using the word RANDOM. RUN Starts the game after compiling. [And then there are two words I had to add, because in the original version their functions were performed at compile- time, and one word only described in the main text: udgs Sets up the UDGs by calling define 10 times. initvars Initialises all used variables, and sets up the allotted memory for b (BOARD) and r (CORNER). loader Calls udgs and initvars, sets caps lock (something Abersoft Forth apparently does by default) and runs the game.] ___________________________________________________________ [And finally the original Abersoft Forth source code. The only change is that, for ease of reading, I've unwrapped long lines where feasible, and left out empty last lines.] SCR # 1 0 ( DEFINE GRAPHICS) 1 DEFINE 8 * UDG + DUP 8 + SWAP DO I C! LOOP ; 2 HEX 3 3C 3C 3C 3C 3C 3C 3C 3C 0 DEFINE 4 0 0 FF FF FF FF 0 0 1 DEFINE 5 3C 3C FF FF FF FF 3C 3C 2 DEFINE 6 FF FF FF FF FF E7 C3 81 3 DEFINE 7 81 C3 E7 FF FF FF FF FF 4 DEFINE 8 FF 7F 3F 1F 1F 3F 7F FF 5 DEFINE 9 FF FE FC F8 F8 FC FE FF 6 DEFINE 10 FF FE FC F8 F0 E0 C0 80 7 DEFINE 11 1 3 7 F 1F 3F 7F FF 8 DEFINE 12 80 C0 E0 F0 F8 FC FE FF 9 DEFINE 13 FF 7F 3F 1F 0F 07 03 01 A DEFINE 14 DECIMAL ________________________________ SCR # 2 0 ( VARIABLES) 1 2 0 VARIABLE LIN 0 VARIABLE COL 3 0 VARIABLE BOARD 7 ALLOT 0 VARIABLE BOX 4 0 VARIABLE CORNERS 2 ALLOT 0 2 6 8 CORNERS 3 + C! 5 CORNERS 2 + C! CORNERS 1 + C! 6 CORNERS C! 7 0 VARIABLE CHAR 0 VARIABLE PLAYER 8 0 VARIABLE COMP 9 0 VARIABLE XVAL [These two are never used in the game. 0 VARIABLE YVAL I've called them x and y, just to be 10 complete.] ________________________________ SCR # 3 0 { SCREEN INITIALISATION) 1 : SLAT 6 AT ." A A" ; 2 : STRIPE 6 AT ." BBBBBCBBBBBCBBBBB" ; 3 : SETUP CLS 18 1 DO I SLAT LOOP 6 STRIPE 12 STRIPE ; 4 : NUMS 3 8 AT ." 1 A 2 A 3" 9 8 AT ." 4 A 5 A 6" 5 15 8 AT ." 7 A 8 A 9" ; 6 : PICTURE SETUP NUMS BOARD 9 2 FILL 18 0 AT ." Spectrum:" 7 COMP @ . 18 22 AT ." Player:" PLAYER @ . ; 8 9 ( PROGRAM ROUTINES) 10 : CO-ORDS CASE 1 OF 1 6 ENDOF 11 2 OF 1 12 ENDOF 3 OF 1 18 ENDOF 12 4 OF 7 6 ENDOF 5 OF 7 12 ENDOF 13 6 OF 7 18 ENDOF 7 OF 13 6 ENDOF 14 8 OF 13 12 ENDOF 9 OF 13 18 ENDOF 15 ENDCASE COL ! LIN ! ; ________________________________ SCR # 4 0 { PROGRAM ROUTINES CONTINUED) 1 : DRAWX CO-ORDS LIN @ COL @ ." #H K#" [All #'s in these lines 2 LIN @ 1+ COL @ ." I#D#J" should be read as Graphics LIN @ 2+ COL @ ." F#G" Shift-8, i.e. solid blocks; 3 LIN @ 3 + COL @ ." K#E#H" ASCII doesn't have one.] LIN @ 4 + COL @ ." #J I#" 4 1 BOX @ 1 - BOARD + C! ; 5 ( BOARD CHECKING ROUTINES) 6 : CLEAN BOX @ 1 - BOARD + C@ 2 = ; 7 : HIGH BOX @ 9 > NOT ; : LOW BOX @ 1 < NOT ; 8 : XIN BEGIN INKEY 48 - BOX ! LOW HIGH CLEAN AND AND UNTIL 9 BOX @ DRAWX ; ________________________________ SCR # 5 0 ( FIND A SPACE FOR O) 1 : DRAW-O BOX @ CO-ORDS LIN @ COL @ ." K#H" [Same note as for DRAWX.] 2 LIN @ 1+ COL @ ." K#E#H" LIN @ 2+ COL @ ." #G F#" 3 LIN @ 3 + COL @ ." I#D#J" LIN @ 4 + COL @ ." I#J" 4 0 BOX @ 1 - BOARD + C! ; 5 : 2ROW BOARD + C@ CHAR @ = SWAP BOARD + C@ CHAR @ = AND ; 6 : CORNER BOARD 8 + C@ 2 1 - IF 9 BOX ! THEN 7 BOARD 6 + C@ 2 1 - IF 7 BOX ! THEN 8 BOARD 2 + C@ 2 1 - IF 3 BOX ! THEN 9 BOARD 0 + C@ 2 1 - IF 1 BOX ! THEN ; 10 : RANDOM 23672 @ ABS 16000 MOD C@ * 255 / ; 11 : ANYSQUARE 500 0 DO 8 RANDOM DUP BOARD + C@ 2 = IF 1+ 12 BOX ! LEAVE ELSE DROP THEN LOOP ; 13 : SYSTEMATIC 9 0 DO I BOARD + C@ 2 = IF I 1+ BOX ! THEN LOOP ; 14 RANDCORNR 99 0 DO 3 RANDOM CORNERS + C@ DUP BOARD + C@ 2 = 15 IF 1+ BOX ! LEAVE ELSE DROP THEN LOOP ; ________________________________ SCR # 6 0 ( BOARD SEARCHING ROUTINES) 1 : LOOK BOARD + C@ 2 = ; 2 : 2INROW 0 LOOK IF 1 2 2ROW 4 8 2ROW 3 3 6 2ROW OR OR IF 1 BOX ! THEN THEN 4 1 LOOK IF 0 2 2ROW 4 7 2ROW OR IF 2 BOX ! THEN THEN 5 2 LOOK IF 0 1 2ROW 4 6 2ROW 5 8 2ROW 6 OR OR IF 3 BOX ! THEN THEN 3 LOOK IF 0 6 2ROW 4 5 2ROW 7 OR IF 4 BOX ! THEN THEN 4 LOOK IF 0 8 2ROW 2 6 2ROW 8 1 7 2ROW 3 5 2ROW OR OR OR IF 5 BOX ! THEN THEN 9 5 LOOK IF 2 8 2ROW 3 4 2ROW OR IF 6 BOX ! THEN THEN 10 6 LOOK IF 0 3 2ROW 2 4 2ROW 7 8 2ROW 11 OR OR IF 7 BOX ! THEN THEN 7 LOOK IF 1 4 2ROW 6 8 2ROW 12 OR IF 8 BOX ! THEN THEN 8 LOOK IF 2 5 2ROW 0 4 2ROW 13 6 7 2ROW OR OR IF 9 BOX ! THEN THEN ; ________________________________ SCR # 7 0 ( FINDING SPACE FOR O) 1 : FIND-O 0 BOX ! 0 CHAR ! 2INROW BOX @ 0= IF 1 CHAR ! 2INROW 2 THEN BOX @ 0= IF RANDCORNR THEN BOX @ 0= IF CORNER THEN 3 BOX @ 0= IF ANYSQUARE THEN BOX @ 0= IF SYSTEMATIC THEN ; 4 : NEWGAME? BEGIN INKEY DUP 78 = SWAP 89 = OR UNTIL INKEY ; 5 : 3LINE DUP C@ 2 = IF DROP DROP DROP 0 ELSE C@ CHAR @ = SWAP C@ 6 CHAR @ = ROT C@ CHAR @ = AND AND THEN ; 7 : WIN 3 0 DO I BOARD + DUP 3 + DUP 3 + 3LINE LOOP OR OR 8 7 0 DO I BOARD + DUP 1+ DUP 1+ 3LINE 3 +LOOP OR OR 9 BOARD DUP 4 + DUP 4 + 3LINE BOARD 2 + DUP 2 + DUP 2 + 3LINE 10 OR OR OR ; 11 : FULL 1 9 0 DO I BOARD + C@ 2 = IF DROP 0 THEN LOOP ; 12 : XWINS PLAYER @ 1+ PLAYER ! 21 2 AT 13 ." You Win. Another game ? (Y/N)" ; ________________________________ SCR # 8 1 : OWINS 21 3 AT ." I Win. Another game (Y/N)" COMP @ 1+ COMP ! ; 2 : XMOVE 20 10 AT ." Your Move" XIN 20 10 AT ." " 3 1 CHAR ! WIN FULL OR NOT ; 4 : OMOVE FIND-O DRAW-O 0 CHAR ! WIN FULL OR NOT ; 5 : TIE 21 2 AT ." We Draw. Another game ? (Y/N)" ; 6 : X-START BEGIN XMOVE DUP IF DROP OMOVE THEN NOT UNTIL ; 7 : O-START BEGIN OMOVE DUP IF DROP XMOVE THEN NOT UNTIL ; 8 : PLAY PICTURE 10 RANDOM 5 > IF X-START ELSE O-START THEN 9 FULL IF TIE THEN 0 CHAR ! WIN IF OWINS THEN 1 CHAR ! WIN 10 IF XWINS THEN NEWGAME? ; 11 : RUN 0 PLAYER ! 0 COMP ! BEGIN PLAY 89 = NOT UNTIL ; ________________________________ SCR # 9 1 : LOADER CLS 10 12 AT ." COMPILING" 9 1 DO I LOAD LOOP 2 10 12 AT . " READY " ; ___________________________________________________________ [RLB, October 2012]