Atari FujiNet Programming Series: Roman Checkers - FujiNetWIFI/fujinet-firmware GitHub Wiki
This article was originally printed in ABBUC Magazine #148.
FujiNet Programming Series: Converting Roman Checkers to FujiNet
Abstract
With the N: device, it is extremely easy to convert existing two player games to be played over the network, via FujiNet. Turn-based, or round-based games like Reversi work well because the network transmission occurs at predictable times, and the minor delay caused by transmission does not adversely affect the game play. All we have to do is add a small bit of code to add all the necessary functionality.
A Note about assumptions
This article assumes that you have become comfortable in using your FujiNet to mount and create disk images. It also assumes that you are familiar with your favorite DOS, and are able to format disks and manipulate files, which is needed to copy the N: handler onto a development disk.
There will also be a section at the end of the article, with links to details of information presented.
Requirements
All you need to modify an existing BASIC game, is a copy of the N: handler, called NDEV.COM. This handler can easily be found on every single DOS disk in the DOS/ folder on the atari-apps.irata.online TNFS server (see Fig 1), automatically booting in as needed. This handler provides the integration with the Central Input Output (CIO) routines in the Atari OS ROM. This handler provides direct access to the network card functionality in the FujiNet. Because all of the networking functionality is on the FujiNet, the handler does not need to be updated every single time that a new network protocol is added, so it can be bundled with software without worry.
So in short, to do development, you need:
- An Atari 8-bit
- A FujiNet
- The N: Handler from one of the atari-apps DOS disks.
- A working wifi network connection.
- BASIC
Getting the game from the network.
The first thing we should do, is to select a DOS to load. As was mentioned previously, all of the DOS in the DOS/ folder on atari-apps.irata.online as seen in Fig. 1 have the required N: handler, so choose whichever is most convenient for you, and mount it read-only in drive 1.
You should also create a disk image on either your SD card slot, or local TNFS file server, and mount it as drive 2. This will need to be formatted for use by the DOS of your choice.
The game that we are modifying is available directly from the network, so it can be loaded directly into your computer via TNFS. So let's go grab it!
Boot your Atari, with the FujiNet attached. It should boot into CONFIG. If atari-apps.irata.online is not in a host slot, please add it using the [E]dit command. See Fig. 2 to see where to get the game.
Since this is a cassette image, the FuijNet software will automatically mount the image file into the cassette slot, and return to the Hosts and Devices menu.
So when we reach this point, you should have:
- A DOS disk in D1: mounted read only
- A Work disk in D2: mounted read/write
- The Roman Checkers in the cassette slot
We can then press the OPTION key to boot the computer.
The N: device
The N: device is now present in the computer, and is a gateway to the network beyond. With it, you can open connections of many different protocols to different hosts either on your local network, or to the internet beyond, while using standard Atari I/O idioms.
SMOKE TEST
For example, you can open an HTTP connection to icanhazip.com:
10 DIM A$(99)
20 OPEN #1,12,3,"N:HTTP://ICANHAZIP.COM/"
30 INPUT #1,A$
40 PRINT A$
50 CLOSE #1
60 END
Running this program, should display your publicly routable network address.
About the program
Line 10 dimensions a variable to hold the reply of the web server. Line 20 opens the connection to the web server that is hosting icanhazip.com. At this point, the protocol adapter does everything needed to open the connection and talk the HTTP protocol and GET the appropriate page. The 12 means to use HTTP GET, and the 3 in AUX2 specifies that CR/LF characters need to be translated to ATASCII EOL characters. The Filespec is N:HTTP://ICANHAZIP.COM/, and once this command is finished, the I/O channel should be open.
Line 30 immediately asks the channel for input. The N: Handler will then send the first line of the body of the requested content. Since ICANHAZIP.COM only sends a single line of plain text, a single INPUT statement is sufficent to retrieve it. We then print it to the screen on line 40.
Line 50 closes the I/O channel, and 60 ends the program.
Hopefully with this small example, you can see how easy it is to interact with network resources. This same consistency is used for all protocols, making it possible to write very powerful network applications with very little code.
An intermission to set up your work disk
You should now take the time to press OPTION to boot your system, and set up your work disk. When you are done, you should have a bootable work disk in D2: that contains:
- Your preferred DOS.
- the N: Handler as an AUTORUN
And at this point, you should reset your fujinet, remove the dos disk in D1, and replace it with the bootable work disk in D2:, mounted read-write.
On to the Game
Now that we've done a small test with the N: device, I will show how we modify the Roman Checkers game to allow for network play between two players. The changes made fall into two basic categories:
- Additional user interface code to set the network host
- Additional code to send and receive game state from the other player
No, really, that's it!
Okay, I don't like the colors either, so we'll change those too. :)
At the READY prompt, Load the game by typing
CLOAD
then
RUN
The title screen will display, then load the game automatically. A READY prompt should appear.
Typing RUN, again will start the game.
Once you've ensured the game has loaded properly, feel free to BREAK out, and get back to a usable state. The N: handler handles RESET properly, so you can use that key if you wish.
The Goal
The goal here, as stated previously, is to add network support for this game. We will do this as simple as possible by making the assumption that one player (the client) will need to connect to another player (the server). For simplicity, we will use the TCP protocol, and the server player will listen on port 6502 for the client.
A quick change, modifying the colors
Before we start adding network functionality, let's change the colors.
LIST 710,720
710 SE. 4,13,6:SE. 2,13,2
720 SE. 0,0,0:SE. 1,0,15
Aren't those better? :)
Adding variables we need
If we look at line 500, we'll see the following variables:
LIST 500
500 DIM A$(8),B$(8),F(1),S(1,9)
- A$ is the name of the first player.
- B$ is the name of the second player.
- F is used by a computer player to keep track of board position
- S is a set of the score of the last 9 games
We need to add two strings.
- H$ is the hostname, if this is blank, this player is the server, otherwise, the client.
- D$ is derived from H$ and is the device spec, imagine it as "N:TCP://H$:6502/"
Modify Line 500 to read:
500 DIM A$(8),B$(8),F(1),S(1,9),H$(64),D$(64)
We also need to add a numeric variable, LS to indicate whether we are listening for a connection (are we the server?)
501 LS=0
Adding the UI to set up the connection
With the new variables in place, we can add the code to set up the network connection.
530 POS. 10,1:? CHR$(125):?
531 ? "BE SURE TO FORWARD PORT 6502.":?
540 PRINT "HOSTNAME OR RETURN TO LISTEN"
550 INPUT #16,H$
560 IF H$="" THEN LS=1
570 D$="N:TCP://":D$(LEN(D$)+1)=H$:D$(LEN(D$)+1)=":6502/"
571 OPEN #2,12,0,D$
572 IF NOT LS THEN 600
580 STATUS #2,A:IF NOT PEEK(748) THEN 580
581 ? "ACCCEPTING CONNECTION.":XIO ASC("A"),#2,12,0,"N:":FOR DE=1 TO 200:NEXT DE
Line 530 clears the screen, while line 531 displays a message to the user informing them that port 6502 should be forwarded if they are the server. Lines 540 and 550 issue a prompt to the user to enter a hostname, which contains an input trick (using IOCB #16) to ensure that a question mark isn't printed. At this point, we have a hostname. If it's blank, then we set the LS flag to 1, to indicate that we are listening for a connection, rather than connecting to another system.
Line 570 subsequently builds the D$ devicespec, using the rules we mentioned above.
Line 571 opens the now constructed devicespec, and IOCB #2 is now the network connection.
Listening and accepting a connection
If a host name isn't specified, only a port, then the N: device assumes that you are setting up a server, or listening connection, and once opened, will sit listening for a client to connect. If a client connected at this point, it would be like a house guest knocking at the door, waiting to be let in. The client will simply sit there and wait for a while, before giving up and emitting a time out error. To deal with the waiting connection, we have to accept it.
Assuming we have asked to listen for a connection, we loop around line 580 asking for STATUS, and then checking memory location (748) also known as DVSTAT+2 to check for a 1 to indicate that a connection is waiting to be accepted. If this happens, then we immediately fall to line 581, which sends an XIO command to accept the connection. The AUX1 and AUX2 values of the connection should match the OPEN above, as they will overwrite those values in the IOCB.
Once the connection is accepted, we now can send and receive data to the connected client by writing to IOCB #2.
With this, the network connection is now established.
Initial communication, Who are you?
Once this is done, we need to communicate the player name to the opposing player. The existing code already inputs our player name, and as part of the changes we did above, we skip over using the keyboard to get the name of the opposing player. Doing this simply requires us to do one of two things:
- If we are listening then we send our player name over to the opposing player, then ask the opposing player for their namae.
- Else if we are not listening, then we do the opposite.
It is done with the following code:
601 IF NOT LS THEN 620
611 PRINT #2;A$:INPUT #2,B$
612 GOTO 700
621 PRINT #2;B$:INPUT #2,A$
Looking at the main game code
With this done, the game starts its drawing and setting up the board.
We can look at the main game code by doing the following:
LIST 1000,2000
There is a block of code for the "Computer" player which will no longer be used, and can be summarily ignored.
At this point, we need to think, what game state do we need to deal with?
There are two main game states:
- Us
- Opposing Player
and each of these are doing the following
- Reading the joystick
- Updating X and Y with new board position in response to joystick
- Checking for valid move when trigger is pressed
- Executing valid move when trigger is pressed
Given this, we need to only send three variables over the network:
- X (horizontal position)
- Y (vertical position)
- TRIG (was trigger pressed?)
All three of these are numeric variables, and are thus very easy to send and parse.
Dealing with Player Input
We need to process input appropriately. Now that we are no longer using the second joystick, we can simply read from STICK(0) and STRIG(0), while carefully jumping around the code to handle the opposing player, which is now at a remote system:
1510 N=PEEK(764):POKE 764,255:TRIG=STRIG(0):JS=STICK(0)
1511 IF LS=0 AND P=1 THEN 1500
1512 IF LS=1 AND P=2 THEN 1500
Dealing with the other player's input
This is accomplished with a single line:
61 STATUS #2,A:IF PEEK(746)>0 THEN INPUT #2,X,Y,TRIG:IF TRIG=0 THEN 1700
Because operations like INPUT and GET block the computer, We use STATUS to check for anything waiting input, and if there is input waiting (by checking memory location 746), we then use INPUT to get it. Since the input data is comma delimited, a single input statement can capture all of the information into the correct variables. If TRIG is 0, then the other player pressed their trigger, and we need to go to line 1700 to check for a valid move.
Sending our player state
We also need to send our player state to the other player when requested:
1530 POKE 77,0:IF N=33 OR N=12 OR TRIG=0 THEN 1699
1581 ? #2;X;",";Y;",";TRIG
1699 ? #2;X;",";Y;",";TRIG
Line 1530 checks for space, enter or a trigger from us, and sends the appropriate string. While the two lines look the same, they are at different parts of the code, but they accomplish the same function, sending our player state to the opposing player, and in an easy to digest comma delimited form that can be easily parsed by line 61 shown above.
That's it.
No really. That's it. That's all we need to do, to make this game work between two Atari computers over the network.
SAVE the file, and you can place the resulting disk image on a TNFS server to load between two Atari computers.
You can see an example of this game in action between two remote players, here: https://www.youtube.com/watch?v=8GyEFiTwHfo
A Note about testing
Since this is happening over a simple TCP socket, it is easy to use other programs to test the connection and see if things are working correctly. Programs such as netcat (nc) are invaluable for this.
To test, I change the AUX values in the OPEN and XIO lines in the program to be 12,2 instead of 12,0, so that FujiNet can change Line Feeds common on Linux systems, into ATASCII EOL characters automatically.
Linux using nc
571 OPEN #2,12,2,D$:REM TO LINUX SYSTEM USING NC
581 ? "ACCCEPTING CONNECTION.":XIO ASC("A"),#2,12,2,"N:":FOR DE=1 TO 200:NEXT DE
and you can have nc listen for a connection
$ nc -l 6502
or have netcat make a connection to the Atari
$ nc fujinet 6502
And you can test various parts of the program you are developing, even if you do not have another Atari handy to test the opposite end.
Conclusion
I hope you've found this first article (of what will hopefully be many) very informative, and I hope that it gives you an idea of just how easy it is to write networked games in Atari BASIC.
I can hear some of you asking, "Does it work with Turbo BASIC XL?" Yes. You will have to use the Relocatable version of Turbo BASIC XL as modified by dmsc, but it works just fine. Kay Savetz did a Connect Four game in this fashion, that is also on the atari-apps.irata.online in the games folder.
What it comes down to, is understanding exactly what bits of information is needed by the other player(s) and sending that information across the network at the right time. Spending the time and energy to make sure you send only what is needed, when it is needed, will pay off, as your network game takes shape.
So, until next time guys, Have fun!
Links
- The FujiNet WIKI: https://github.com/FujiNetWIFI/fujinet-platformio/wiki
- N: Game Developer Cheat Sheet: https://github.com/FujiNetWIFI/fujinet-platformio/wiki/N%3A-Game-Developer-Cheat-Sheet
Source Code
Here is the source, for the entire Reversi game:
8 TRAP 3000
9 GRAPHICS 0
10 GOTO 500
18 LOCATE X,Y-1,C:COLOR C:PLOT X,Y:PLOT X+1,Y:RETURN
19 LOCATE X-1,Y-1,C:GOTO 30
20 LOCATE X-1,Y,CC:IF C=CC THEN C=0
30 COLOR C:PLOT X,Y:PLOT X+1,Y:GOTO 60
40 LOCATE X-1,Y,Z:IF Z=C THEN RETURN
45 COLOR C:FOR I=X-1 TO X+2:FOR J=Y-1 TO Y+1
50 PLOT I,J:NEXT J:NEXT I:SOUND 0,C*25,10,8
60 FOR V=0 TO 10:NEXT V
61 STATUS #2,A:IF PEEK(746)>0 THEN GOSUB 18:INPUT #2,X,Y,TRIG:IF TRIG=0 THEN 1700
70 SOUND 0,0,0,0:RETURN
500 DIM A$(8),B$(8),F(1),S(1,9),H$(64),D$(64)
501 LS=0
520 OPEN #1,4,0,"K"
530 POSITION 10,1:? " #FUJINET REVERSI ":?
531 ? "BE SURE TO FORWARD TCP PORT 6502.":?
540 PRINT "HOSTNAME OR RETURN TO LISTEN"
550 INPUT #16,H$
560 IF H$="" THEN LS=1
570 D$="N:TCP://":D$(LEN(D$)+1)=H$:D$(LEN(D$)+1)=":6502/"
571 OPEN #2,12,0,D$
572 IF NOT LS THEN 600
580 STATUS #2,A:IF NOT PEEK(748) THEN 580
581 ? "ACCEPTING CONNECTION.":XIO 65,#2,12,0,"N:"
582 GOTO 600
600 GC=0:GRAPHICS O:POSITION 5,8:? "Type name, press RETURN"
601 IF NOT LS THEN 620
610 ? :? "Player 1";:INPUT A$:IF A$="" THEN 610
611 PRINT #2;A$:INPUT #2,B$
612 GOTO 700
620 ? :? "Player 2";:INPUT B$:IF B$="" THEN 620
621 PRINT #2;B$:INPUT #2,A$
622 GOTO 700
625 F(0)=0:F(1)=0
630 IF A$="COMPUTER" THEN F(0)=1
640 IF B$="COMPUTER" THEN F(1)=1
700 GRAPHICS 5:POKE 752,1
710 SETCOLOR 4,13,6:SETCOLOR 2,13,2
720 SETCOLOR 0,0,0:SETCOLOR 1,0,15
750 COLOR 3:FOR Y=1 TO 37
770 PLOT 17,Y:PLOT 18,Y:PLOT 19,Y
780 FOR X=20 TO 60 STEP 5:PLOT X,Y:NEXT X
790 PLOT 61,Y:PLOT 62,Y:PLOT 63,Y:NEXT Y
800 FOR X=17 TO 60
810 PLOT X,1:PLOT X,2
820 FOR Y=3 TO 35 STEP 4:PLOT X,Y:NEXT Y
830 PLOT X,36:PLOT X,37:NEXT X
850 FOR L=1 TO 6:READ X,Y,C:GOSUB 40:NEXT L:RESTORE
855 DATA 10,36,1,37,17,1,42,21,1,72,36,2,42,17,2,37,21,2
990 P=2
1000 P=3-P:POKE 764,255
1010 A=0:B=0:FOR M=22 TO 57 STEP 5:FOR N=5 TO 33 STEP 4
1020 LOCATE M,N,C:IF C=1 THEN A=A+1
1030 IF C=2 THEN B=B+1
1040 IF C=0 THEN X=M:Y=N
1050 NEXT N:NEXT M
1055 POKE 656,1:POKE 657,3:? A$;:POKE 657,28:? B$:? " ";A;" ";:POKE 657,30:? B;" "
1070 IF A+B=64 THEN 2000
1110 IF F(P-1)=0 THEN GOTO 1500
1120 GOSUB 5000
1130 IF Q>0 THEN 1730
1140 FOR L=0 TO 20:POKE 656,1:POKE 657,15
1143 ? "I PASS";:POKE 657,15:GOSUB 60:? " ";:NEXT L:GOTO 1000
1500 C=P:GOSUB 20:GOSUB 20:GOSUB 19
1510 N=PEEK(764):POKE 764,255:TRIG=STRIG(0):JS=STICK(0)
1511 IF LS=0 AND P=1 THEN 1500
1512 IF LS=1 AND P=2 THEN 1500
1520 IF N=255 AND TRIG=1 AND JS=15 THEN 1500
1530 POKE 77,0:IF N=33 OR N=12 OR TRIG=0 THEN 1698
1540 IF N=30 THEN C=2:GOSUB 40:GOTO 1500
1550 IF N=31 THEN C=1:GOSUB 40:GOTO 1500
1560 IF N=10 THEN 1630
1570 X=X+5*((N=7 OR JS=7) AND X<57)-5*((N=6 OR JS=11) AND X>22)
1580 Y=Y+4*((N=15 OR JS=13) AND Y<33)-4*((N=14 OR JS=14) AND Y>5)
1581 IF N=33 OR N=12 THEN TRIG=0
1582 ? #2;X;",";Y;",";TRIG
1590 SOUND 0,(P+1)*40,10,8:GOTO 1500
1630 GOSUB 19:GOSUB 5000:IF Q=0 THEN 1000
1655 FOR I=0 TO 20:POKE 656,1:POKE 657,12
1656 ? " INVALID MOVE";:SOUND 0,180,10,10:GOSUB 60:POKE 657,12:? " ";
1657 NEXT I:GOTO 1010
1698 IF N=33 OR N=12 THEN TRIG=0
1699 PRINT #2;X;",";Y;",";TRIG
1700 G=0:GOSUB 4000:IF R=0 THEN GOSUB 19:GOTO 1655
1730 G=1:GOSUB 4000
1780 GOTO 1000
2000 ? "To see scoreboard, press any key":GET #1,N
2040 S(0,GC)=A:S(1,GC)=B
2050 GRAPHICS O:POSITION 1,10:? "GAME":POSITION 1,12:? A$:POSITION 1,14:? B$
2060 FOR X=0 TO GC:N=X*3+10:POSITION N,10:? X+1;:POSITION N,12:? S(0,X);:POSITION N,14:? S(1,X);:NEXT X
2100 ? :? :? "Same players go again? (Y-N)";:GET #1,N
2120 IF GC>7 AND N=89 THEN ? :? "You have already played 9 games.";:FOR X=1 TO 999:NEXT X:GOTO 600
2130 IF N=89 THEN GC=GC+1:GOTO 700
2135 IF N<>78 THEN 2100
2140 GOTO 600
3000 GRAPHICS 0:? "DISCONNECTED.":CLOSE #1:CLOSE #2:END
4000 R=0:SX=X:SY=Y
4005 LOCATE X-1,Y,C:IF C<>0 THEN RETURN
4010 FOR DX=-1 TO 1:FOR DY=-1 TO 1
4020 IF DX=0 AND DY=0 THEN 4130
4030 TR=0:X=SX:Y=SY
4050 X=X+DX*5:Y=Y+DY*4:LOCATE X,Y,C
4060 IF C=3-P THEN TR=TR+1:GOTO 4050
4070 IF C=P THEN R=R+TR:IF G=1 THEN 4100
4080 GOTO 4130
4100 X=X-DX*5:Y=Y-DY*4:C=P:GOSUB 40
4110 IF NOT (X=SX AND Y=SY) THEN 4100
4130 NEXT DY:NEXT DX:X=SX:Y=SY:RETURN
5000 Q=0:QX=0:QY=0:FOR M=0 TO 7:FOR N=0 TO 7
5020 X=M*5+22:Y=N*4+5
5030 LOCATE X,Y,C:IF C<>0 THEN 5090
5035 C=P:GOSUB 20:GOSUB 19
5040 G=0:GOSUB 4000
5080 IF R>Q THEN Q=R:QX=X:QY=Y
5090 NEXT N:NEXT M:X=QX:Y=QY:RETURN
7005 FOR X=0 TO 8:S(0,X)=0:S(1,X)=0:NEXT X
9999 GOTO 9999