NADA.PONG: A Graphic Demo of Apple II Network Gaming
Michael J. Mahon – August 27, 2007
Revised - November 18, 2009
Why NadaPong? NadaPong Design Running NADA.PONG
Though I had written many NadaNet programs that do funtional tests, performance
tests, provide services, and demonstrate coordinated parallel programming,
I had not written any programs that would dramatically demonstrate the capabilities
of NadaNet!
But once the question was posed, I realized that I could write a striking
visual demo very quickly.
In an homage to Woz's Pong demo of the Apple ][, I decided
to do a two-computer "Pong-like" demo of NadaNet in which each machine's screen would have
half of the playing field, and as the ball bounced off the "walls", it would be passed back
and forth between machines via NadaNet. This would be a graphic demo that people could
easily run immediately after installing a pair of NadaNet adapters in two Apple II's!
I went back to my room and wrote the first iteration of this demo in a little
over an hour. As with most demos, most of the time was spent coding up the non-network
part--adding the network ball-passing only took a few minutes (and less than a dozen lines
of Applesoft out of about 90 total). The first version needed to be run on both machines,
but I later added code that runs on the "master" machine (ID=1) and loads the program into
the "slave" machine using NadaNet. The only requirement on the slave machine is that it
have NadaNet loaded and be serving (See "Running NADA.PONG" below if you're not sure about
how to get a machine "serving".)
The result is not a game, since there are no user controls, but it does make it
evident how such a multi-player game can be written using NadaNet, so hopefully others
with more game-oriented backgrounds can take it from here to write the first multicomputer
Apple II game!
If you would like to view a short video of the demo, just click on this link
or click on the photos at the top of this page.
Though the program is simplified by having only to deal with a "left" and a "right"
machine, it would not be difficult to extend it to handle playing fields
consisting of more machines. Of course, one would have to decide how the screens
were arranged geometrically and deal with ball bounces appropriately. The
handoff of the ball to the proper receiving machine would be essentially unchanged.
The logic for bouncing the ball off the walls is straightforward, but something
should be said about how the ball is passed to another machine. When the ball
leaves one machine's playing field and is passed to another, the complete state
of the ball is sent to the receiving machine. In this case, that is the X,Y
position of the ball and its X,Y velocities, plus the character currently
representing the ball. (This allows the ball to change from an Open-Apple to
a Closed-Apple or vice versa with each bounce. It could as easily be
a color or a shape number if a graphics display were used.)
Each of these quantities is represented as an unsigned byte that is
POKEd into a message buffer that is then (network) &POKEd to the receiving machine.
The X,Y coordinates are passed as screen coordinates of the receiving
machine, so each machine works only in its local coordinate system.
Since the value of X can never be $FF, after a machine passes
the ball off, it sets the buffer value of X to $FF, then waits in a service
loop for the value to change to a different value.
When it's time for the ball to re-enter the machine's playfield, it will serve
a &POKE request from the ball's sender, which causes the buffer contents to change and
causes the receiving machine to escape its buffer polling loop to begin animating
the newly received ball.
The roles of the two computers |
|
The left computer |
The right computer |
"Master" machine NadaNet ID =1 Runs NADA.PONG (after the slave machine is serving) |
"Slave" machine NadaNet ID=10 Serves as a result of CALL 973 (before running NADA.PONG on the master) |
Annotated NADA.PONG Listing
100 REM Bouncing Apple NadaNet demo 110 REM KansasFest 2007 HackFest 120 REM MJM - July 20, 2007 130 : 140 SELF = PEEK (972): REM Who am I? 150 OTHER = 11 - SELF: REM Other machine ID (10 or 1)Note that NadaNet initialization sets 972 ($3CC) to the ID of this machine. The program reads the "self" ID and uses it to determine whether it should behave as the master (ID=1) or the slave (ID=10).
160 IF SELF < > 1 GOTO 460 : REM Skip this if not master machine 170 : 200 REM Run this program on slave 210 P$ = "nada.pong" 220 PBUF = 2 * 4096: REM Program buffer $2000 230 BUF = PBUF - 10: REM Utility buffer 240 PRINT CHR$ (4)"bload "P$",a"PBUF",TBAS" 250 PLNG = PEEK (48840) + 256 * PEEK (48841): REM Prog Length 260 PSRT = 8 * 256 + 1 : REM Prog LOAD address ($801) 270 : 280 &RUN (OTHER,PSRT,PLNG,PBUF): REM RUN the program on OTHER machine 420 : 430 PRINT "NADA.PONG running on "OTHER 440 :This is where the actual program starts. We declare the (5-byte) buffer for sending and receiving the ball state, as well as the size of the playing field: here the size of the X dimension is 40 characters, and the size of the Y dimension is 21 lines.
450 REM ============= Nada Pong ============= 460 BF = 768: REM Ball buffer 470 SX = 40:SY = 21: REM Screen size 480 ZM = 3 490 : 500 BOOP = 780: REM BOOP routine 510 BT = BOOP + 6: REM BOOP tone 520 FOR I = BOOP TO BOOP + 13: READ X: POKE I,X: NEXT 530 DATA 162,32,173,48,192,160,200,136,208,253,202,208,245,96 540 : 550 REM Draw border 558 REM Start 80-col firmware, then go to 40-col leaving firmware active. 560 PRINT CHR$ (4)"PR#3": PRINT CHR$ (27) CHR$ (17): HOME 570 HTAB 1: VTAB 1 580 INVERSE : PRINT SPC( SX - 1); 590 HTAB 1: VTAB SY 600 PRINT SPC( SX - 1); 610 IF SELF = 1 THEN FOR I = 1 TO SY: HTAB 1: VTAB I: PRINT " ";: NEXT : NORMAL : FOR I = 0 TO ZM - 1:P$(I) = "PING": NEXT :P$(ZM) = "HACK" 620 IF SELF < > 1 THEN FOR I = 1 TO SY: HTAB 40: VTAB I: PRINT " ";: NEXT : NORMAL : FOR I = 0 TO ZM - 1:P$(I) = "PONG": NEXT :P$(ZM) = "FEST" 630 B$(0) = "@":B$(1) = "A": REM Ball characters 640 BI = 0 650 :Now we have initialized the playfield and are ready to animate the ball (if we are machine ID 1) or to wait for a ball to arrive (if we are not machine ID 1).
660 IF SELF < > 1 GOTO 960: REM Receive 670 : 680 VTAB 23: HTAB 18: PRINT P$(Z) 690 X = 2:Y = INT (SY / 2):VX = 1:VY = 1 700 : 710 REM Move ball 720 HTAB X: VTAB Y: INVERSE : PRINT CHR$ (27)B$(BI); CHR$ (24);: NORMAL :OX = X:OY = Y 730 X = X + VX:Y = Y + VY 740 IF Y < 2 THEN VY = - VY:Y = 3 - Y: POKE BT,200: CALL BOOP:BI = 1 - BI 750 IF Y > SY - 1 THEN VY = - VY:Y = SY - 1 - (Y - SY): POKE BT,200: CALL BOOP:BI = 1 - BI 760 HTAB OX: VTAB OY: PRINT " ";: REM Erase ballThis is actually a little early to be erasing the ball that we just drew. It would be better to move this "erase" statement to just before the "draw" statement at line 720. (You don't even need to skip over it on entry to the loop, since the background is all blanks anyway.)
770 IF SELF = 1 GOTO 850 780 : 790 REM Right machine 800 IF X > SX - 1 THEN VX = - VX:X = SX - 1 - (X - SX): POKE BT,250: CALL BOOP:BI = 1 - BI 810 IF X > = 1 GOTO 710: REM Keep moving... 820 POKE BF,X + SX: REM Entry x for left machine 830 GOTO 910 840 : 850 REM Left machine 860 IF X < 2 THEN VX = - VX:X = 3 - X: POKE BT,250: CALL BOOP:BI = 1 - BI 870 IF X < = SX GOTO 710: REM Keep moving... 880 POKE BF,X - SX: REM Entry x for right machine 890 :Finally--we get to some code related to the network!
900 REM Send ball to other machine 910 POKE BF + 1,Y: POKE BF + 2,VX + 128: POKE BF + 3,VY + 128: POKE BF + 4,BI 920 & POKE (OTHER,BF,5,BF): REM Send ball to other machine 930 VTAB 23: HTAB 18: PRINT " "; 940 Z = Z + 1: IF Z > ZM THEN Z = 0 950 :After sending the ball state to the other machine, we enter the "wait" state in which we &SERVE(0) until some request is served, then check a value in the buffer that we set to an impossible value (255) to see if the ball state has been &POKEd into our buffer.
960 POKE BF,255: REM Mark buffer empty 970 REM Receive ball from other machine 980 & SERVE(0): REM Receive ball 990 IF PEEK (BF) = 255 GOTO 980: REM Wait for ballWhen the ball has arrived, we "unpack" the buffer contents into our program variables and go to the animation loop. That's it!
1000 VTAB 23: HTAB 18: PRINT P$(Z); 1010 X = PEEK (BF):Y = PEEK (BF + 1) 1020 VX = PEEK (BF + 2) - 128:VY = PEEK (BF + 3) - 128 1030 BI = PEEK (BF + 4) 1040 GOTO 710: REM Move...
How to Run It Yourself
Take a look at this little demo, and, if you find it intriguing and would like to play with it, download the disk archive, build a couple of little adapters on 16-pin DIP sockets, and do your own experiments. If you would prefer to purchase adapters pre-built on printed circuit boards, you can purchase them from me for $15 each, including both the DOS and ProDOS boot disks. Complete adapter kits are also available for $10 each.Documentation Links
(Don't worry too much about the theory at first, but you may want to take a look at "A Network Extension for Applesoft BASIC" to see the NadaNet extensions to Applesoft.)Software Download Links
Here is the link to a ShrinkIt disk image of a bootable disk: