(Click on the photos to see a video.)

NADA.PONG: A Graphic Demo of Apple II Network Gaming

Michael J. Mahon August 27, 2007
Revised - November 18, 2009

Why NadaPong?

At KFest 2007, I made a morning presentation about NadaNet and what it can do. There was considerable interest, and at lunch I was asked if I had written any demos that showcased NadaNet in a way that might appeal visually to an observer.

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.

NadaPong Design

NadaPong has a very simple structure, since there is only one ball in play, and it is always on only one machine's screen, each machine is in one of two states: animating the bouncing ball, or waiting for the ball to be passed back from the other machine (which is currently animating the ball). The game is implemented using the text screen and the Mousetext Open-Apple and Closed-Apple for the ball. Using the text screen allows even Applesoft to do fast enough animation. Lo-res graphics could also be used, and was, in fact, my first implementation.

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)

Before running NADA.PONG, make sure that the two machines have NadaNet adapters installed and are connected together. Then make sure the slave machine is booted, has NadaNet loaded, and is serving (by typing ]CALL 973 or *3CDG as appropriate). There will be no response when the slave is serving, but it will be waiting for a request.

To run NADA.PONG, first, boot the ProDOS NadaNet boot disk (NADAPRO.SDK). When asked for the machine ID, answer "1" if this is to be the master (left) machine, and answer "10" if this is to be the slave (right) machine. (Note that as of 8/24/07, the NADAPRO.SDK image on my website also contains the NADA.PONG program. I've left the date unchanged since everthing but NADA.PONG is exactly the same.)

With the slave machine serving, run NADA.PONG on the master machine. It will load NADA.PONG from disk and send it to the slave machine, then both machines will start running NADA.PONG. The ball will pass smoothly from the left machine to the right machine, and vice versa as long as it continues to run.

If the NadaNet connection is broken, or if a machine is stopped (ctl-C) while it is waiting for the ball, and the machine that currently has the ball continues to run, it will eventually try to sent the ball to the now disconnected or no longer serving machine. When that happens, it will continue to retry the NadaNet request for about 3 seconds (unless the default timeout has been changed), and then the machine attempting to send will terminate with a "DATA ERROR", as is appropriate when a communication cannot be completed and no soft recovery is provided by the programmer.

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).

Also note the gratuitous assumption that the slave's ID is 10! It can actually be any legal ID (except 1, which this program assumes is the "master"), but the constant "11" in line 150 must be changed to be the sum of the master's ID and the slave's ID. (It would be possible to take a "census" of serving machines to avoid specifying a slave ID, but NADA.PONG will usually be run with a particular slave in mind, whose monitor is set up appropriately.)

If we are the master machine, we will read the program from disk and &RUN it on the slave machine, which is assumed to be idle and serving. (Note that when the program is &RUN on the slave machine, it skips these steps by doing a GOTO to line 460.)
 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.

The vertical size of the playfield (measured from the top of the screen) can be changed as desired. SX was a good idea, but reading the code will show that I quietly assumed that SX=40 in a few places. If you want to really parameterize SX, you'll have to express all of my literal references to X positions as functions of SX. (You may even want to specify an offset from the left side of the screen.)
 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
 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 ball
This 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.)

The left (master) and right (slave) machines have separate code for the backwall (or paddle ;-) bounce because the backwall is at 1 on the left machine and at SX for the right machine.

The handoff to the other machine when the ball reaches the open end of the playfield is handled very similarly, but with opposite X positions, of course.
 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!

Here's where we "pack up" the state of the ball into the buffer BF and then &POKE it into the other machine's buffer, where it is waiting for it. Note that the X coordinate of the ball was set up just prior to these statements.
 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.

The default &SERVE(0) waits for about 5 seconds for a request to arrive, before timing out and returning. (Of course, if a request is processed, then it returns immediately, regardless of the timeout argument.) If you want to do some useful work while waiting, you can &SERVE(t), where t is the number of 1/50th second intervals that you are willing to wait for a request to arrive before going around the polling loop again. Values may be in the 1..255 range, with 0 being equivalent to 256.
 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 ball
When 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.

You'll have a lot of fun getting two or more of your Apple II computers talking! Of course, I'd be delighted to hear about your ideas and experiences!

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.)

NadaNet: A Native Network for the Apple II (A bottom-up design description)
A Network Extension for Applesoft BASIC (The ampersand extension to Applesoft)
Building a NadaNet Adapter (How to build the network game port adapter)
NadaNet (ProDOS version) (Merlin Pro listing of the ProDOS version of NadaNet)

Software Download Links

Here is the link to a ShrinkIt disk image of a bootable disk:

NADAPRO.SDK: A bootable ProDOS NadaNet startup disk, Applesoft programs, File Server, ATTACH source, and AppleCrate boot ROM source