Original version: https://www.lessmilk.com/almost-pong/
I recently came across a funky version of Pong called “Almost Pong”. I really liked it and thought it would be fun to re-create it for the Commodore 64. I went about this entirely in BASIC, without writing any assembler routines — by using a fancy compiler that I recently came across: https://egonolsen71.github.io/basicv2/. (Before you get too excited, the physics in my game are quite different — I don’t think doing the same physics calculations would work in realtime on the C64, but using lookup tables it might be possible to produce very similar physics.)
So is this a usable game? I don’t know, when I play the above-mentioned Almost Pong it kind of feels more fun, maybe it’s the physics, maybe it’s because my game is even more bare-bones, or maybe I’m just biased against my own game? Anyway, without further ado:
- Works in VICE and on real hardware
- Press fire button on joystick #2 to jump
- The source code has lines that are longer than 80 lines and will therefore produce syntax errors if you type it into a C64 verbatim
- It’ll also be way too slow to play if you don’t compile it using Basicv2
Here’s the source code:
0 in=0
1 poke 53280,1:poke 53281,0:poke 646,1
2 v=53248:lv=1:co=1:c=0:fr=0:x=160:y=141:rem center
3 if in=0 then print chr$(147):print " press fire to play":wait 56320,16,16:wait 56320,16:in=1
4 for t=12288 to 12415 step 1:poke t,0:next
5 for t=12289 to 12350 step 3:poke t,255:next:for t=12373 to 12397 step 3:poke t,255:next
6 poke v+21,7:poke v+39,1:poke v+40,1:pokev+41,co
7 poke v,72:poke v+16,1:poke v+2,16:poke v+4,x:poke 53271,3
8 poke v+1,y:poke v+3,y:poke v+5,y
9 poke 2040,192:poke 2041,192:poke 2042,193
10 s=54272:w=17:poke s+5,97: poke s+6,200: poke s+4,w:poke s+24,15:rem sound
15 ox=4:oy=1:od=1:jm=4
20 sx=ox:sy=oy:dy=od
21 gosub 1500
30 for i=1 to 2 step 0:rem infinite loop
35 sc=peek(53278):rem sprite collision lag workaround
40 x=x+sx
50 y=y+0
60 if x>255 then poke v+16,5:poke v+4,x-256:goto 70
65 if x<=255 then poke v+16,1:poke v+4,x
70 y=y+sy:sy=sy+dy
73 if y>234 then y=234:poke v+5,y:gosub 1000:rem todo could add poke v+5,y to the subroutine
74 if y<43 then y=43:poke v+5,y:gosub 1010
75 poke v+5,y
80 rem joystick
90 f1=peek(56320) and 16
92 if f1<>0 then fr=0
93 poke 162,0:wait 162,2:rem wait 1/80s
99 rem only fire if fire wasn't pressed during last poll
100 if fr=0 and f1=0 then y=y-sy*jm:sy=oy:dy=od:fr=1:gosub 1700
110 rem bounce
120 if sx < 0 or x<=328 then goto 150
125 rem x=328:pokev+4,x-256
126 poke v+5,y
127 sc=peek(53278)
130 if sc <> 0 then goto 140:rem bounce if we collided
131 if x+sx < 344 then goto 180
135 gosub 1020:rem game over if no collision
140 gosub 1600:rem bounce
145 poke v+1,int(rnd(0)*158)+50
150 if sx > 0 or x>=32 then goto 180
155 rem x=32:pokev+4,x
156 poke v+5,y
159 sc=peek(53278)
160 if sc <> 0 then goto 170:rem bounce if we collided
161 if x+sx > 16 then goto 180
165 gosub 1030:rem game over if no collision
170 gosub 1600:rem bounce
175 poke v+3,int(rnd(0)*158)+50
180 gosub 1800:next:rem sound off
190 end
1000 print " you lose (don't fall into the abyss)":gosub 1100
1010 print " you lose (don't jump into the sky)":gosub 1100
1020 print " you lose (you need to bounce off the":print " right paddle)":gosub 1100
1030 print " you lose (you need to bounce off the":print " left paddle)":gosub 1100
1100 poke s,120:poke s+1,6:poke 162,0:wait 162,16
1110 wm=0
1120 gosub 1800
1130 print " press fire to play"
1140 wait 56320,16,16:wait 56320,16
1150 goto 1
1160 return:rem not reached
1500 rem print game status
1510 print chr$(147):print " level:", lv, "points:", c
1520 return
1600 rem bounce
1610 sx=-sx
1620 c=c+1
1630 if c/5 < lv then goto 1650
1640 lv=lv+1:co=co+1:poke v+41,co:ox=ox+1:if co=15 then co=1
1650 gosub 1500:rem print sc:poke 162,0:wait 162,8
1660 return
1700 rem fire button sound on
1710 poke s,133:poke s+1,11
1730 wm=3:rem num of sound off gosubs to wait before muting
1740 return
1800 rem sound off
1810 if wm>0 then wm=wm-1:goto 1830
1820 poke s,0:poke s+1,0
1830 return
Here’s the compiled .prg:
Here’s me playing the game.
How to load software from the internets on a real C64 using the Datasette drive
Now here’s a (zipped) .wav file that you can put on a tape (or much easier, stream through a cassette adapter into your datasette drive) and load on a real computer. I will describe how to generate .wav files from .prg/.tap/.d64 files in the next section.
Notes on using cassette adapters to load C64 software
When using a cassette adapter, you should make sure your playback device’s volume is neither too loud nor too quiet. On my computer, 50% volume appears to be the sweet spot. You may have to experiment a bit to find your own. When using a cassette adapter, you will have to pause streaming manually (after the C64 prints out “FOUND NAMEOFPROGRAM”, as whatever device you’re using to stream is completely unaware of whether the datasette drive’s motor is on or off and just continues streaming regardless. (I didn’t add a long enough pause between the header(?) and the actual data. You could maybe add a multiple-second pause yourself to automate this part.) It’s probably generally best to stream using Audacity (or similar software) and watch the datasette drive to see whether the motor is on or off. When it’s off, you press stop and when the motor starts running again, reposition the cursor into the nearest section of silence and press play again. I was able to load various kinds of software using this approach. Commercial software may have multiple bits of silence where the motor stops running for a bit.
Here’s an Audacity screenshot for Great Giana Sisters (the tape version):
Now that we have talked about how to load .wav files into the C64, here’s how to actually generate .wav files:
Generating .wav files from .tap/.prg/.d64 files
I spent a few hours evaluating multiple solutions, and have found that wav-prg (https://wav-prg.sourceforge.io/) did the best job.
Here’s how to build this program on Linux:
git clone https://git.code.sf.net/p/wav-prg/libtap
cd wav-prg-libtap
make libtapdecoder.so
make libtapencoder.so
cd ..
git clone https://git.code.sf.net/p/wav-prg/libaudiotap
cd wav-prg-libaudiotap
make clean # probably not needed but happened to be in my notes, possibly for a reason
make -j4 DEBUG=y LINUX64BIT=y libaudiotap.so
cd ..
git clone https://git.code.sf.net/p/wav-prg/code
cd wav-prg-code
make clean # probably not needed but happened to be in my notes, possibly for a reason
make -j4 cmdline/wav2prg DEBUG=y AUDIOTAP_HDR=../wav-prg-libaudiotap/ AUDIOTAP_LIB=../wav-prg-libaudiotap/
make -j4 cmdline/prg2wav DEBUG=y AUDIOTAP_HDR=../wav-prg-libaudiotap/ AUDIOTAP_LIB=../wav-prg-libaudiotap/
Here are some example invocations for an NTSC C64. You can also generate .wav files for use with the PET (full explanation here) or the VIC-20.
# (pwd is .../wav-prg-code)
LD_LIBRARY_PATH=../wav-prg-libaudiotap/:../wav-prg-libtap/ cmdline/prg2wav -m c64ntsc -t filename.tap /path/to/prg # convert prg to tap image for use in vice etc.
LD_LIBRARY_PATH=../wav-prg-libaudiotap/:../wav-prg-libtap/ cmdline/prg2wav -m c64ntsc -w filename.wav /path/to/prg # convert prg directly to wav file
Here’s how to extract a .prg file from a .d64 floppy image, which you can then convert to a .wav file. All you need is VICE, which comes with a tool to extract data from .d64 files:
./c1541 -attach ~/retro/c64/software/BLOCKNB1.D64 -list
0 "ass presents: "
66 "block'n'bubble" prg
6 "block'n'bub. dox" prg
1 "doc-maker.code" prg
1 "scores" prg
590 blocks free.
“ass”? :p Anyway, judging by the number of blocks, “block’n’bubble” seems like the program we’re most interested in. (You can try extracting the others and running them in an emulator.) To extract and to convert, run the following commands:
# (pwd is .../vice-3.5/src)
./c1541 -attach ~/retro/c64/software/BLOCKNB1.D64 -read "block'n'bubble" bnb.prg
reading file `block'n'bubble' from unit 8
# (change directory to .../wav-prg-code)
LD_LIBRARY_PATH=../wav-prg-libaudiotap/:../wav-prg-libtap/ cmdline/prg2wav -m c64ntsc -w bnb.wav bnb.prg # adjust input and output file paths
I don’t remember how to play, but I remember the intro screen music. Felt good to hear it played from a real Commodore 64 for the first time in 15-20 years! Here’s a clip. Sorry for the copyright violation:
The game’s playable as-is, but at least on my hardware the first time I started a game I got some weird colors. (The second time round the colors were normal.) Could be a PAL-vs.-NTSC issue, or could have something to do with the above conversion (for example, a program could assume that the C64’s tape buffer is available for temporary usage, but when loading from tape… it isn’t).
Note that large commercial software is often divided into multiple files, e.g., consisting of a loader and a main program, and perhaps a high score file. In that case, you will not be able to easily convert the software. (You would have to re-write the parts where the loader starts loading from the floppy, etc.)
For example, here’s what’s in the Sokoban .d64:
./c1541 -attach ~/retro/c64/SOKOBAN0.D64 -list
0 "ass presents: "
1 "soko-ban" prg
40 "soko-ban docs" prg
56 "sokoban" prg
41 "title" prg
2 "maze01" prg
2 "maze55" prg
9 "start" prg
3 "flip" prg
1 "test" prg
509 blocks free.
It would probably require some hacking to convert this to tape.
However, not all commercial software is this complex. For example, using this method, I was able to convert the main .prg file in “JupiterLander_1982_Commodore.d64” to .tap and load this in VICE (didn’t try on real hardware but should work) and play as normal.
~/src/vice-3.5/src/c1541 -attach JupiterLander_1982_Commodore.d64 -list
0 "1982 commodore " -136-
30 "j.lander +2 /n0" prg
5 "j.lander docs/n0" prg
629 blocks free.
~/src/vice-3.5/src/c1541 -attach JupiterLander_1982_Commodore.d64 -read "j.lander +2 /n0.prg" jupiterlander.prg
LD_LIBRARY_PATH=~/src/wav-prg-libaudiotap/:~/src/wav-prg-libtap/ ~/src/wav-prg-code/cmdline/prg2wav -t jupiterlander.tap jupiterlander.prg