"Listen Willard, there's a kind of a little hootymajig thing in there... a wheel of some kind. Why don't you try turning the wheel, Willard?...
Oh, I don't know... turn it to the left and see what happens...
Yeh!...I can hear it, Willard, it's ticking a lot faster, isn't it?...
You'd better turn it back, Willard!"
Bob Newhart - Defusing a Bomb
This is a post I started about two years ago, and never got around to finishing it.
Now I have finally finished it.
It started after I went through a list of someones top 22 VIC20 games. There were a lot of odd choices on there, but one I quite liked was Timebomb.
I like maze games, and this was quite good, scrolling up and down a maze looking for a bomb.
If you don't find the bomb in time (about 80 seconds), it explodes, destroying the maze, so it has to build a new one so you can try again.
If you find the bomb in time, you restart the same maze, but each round you have 5 seconds less.
As the maze is the same, you should get increasingly fast at it, once you discount all the dead ends.
It decrements each round but always lets you have at least 15 seconds.
However, there were a few things I didn't like.
The main one was there was a full minute to wait whilst it was "Making (the) Maze".
It also takes another full minute to create a new maze if you didn't find the bomb in time.
At the top of the maze, it looked like you could see code on the screen
There was no on screen countdown to shown when the bomb was going to go off
There were no instructions to tell you what to do.
Although this was considered to be added to the Penultimate Cartridge, it didn't make the cut, mainly due to the glacial speed of the maze generation.
Later on, some space became free in the cartridge, before the next release, so I wondered if I could have a go at speeding up the maze generation, and maybe some of the other things and getting it into the cartridge.
I did some initial work and made good progress, but somewhere along the line I messed something up, and it was drawing the maze, but the movement was all wrong.
Ironically, the clock was ticking, and I ran out of time, and the bomb exploded.
This was put to one side and I moved onto finishing the cartridge and testing, testing and more testing of all the games within.
I went back once or twice to see if I could find what was wrong, but didn't see anything, so again it got put aside.
Back to today, and it's time for another refresh of the cartridge. I have managed to free up quite a bit of space, so let's finally have a proper look at Timebomb, and get it working.
The Original Version
I had a look around and found the original was actually a type-in game by Doug Smoak from Compute! magazine in July 1983.
It is a very short game, so any deficiencies are understandable.
There are no unnecessary spaces, it is very compressed.
To make it easier to follow, I added all the spaces in.
This was just to make the program more readable. It didn't actually work with all the spaces added to the program, the file size increased from 1843 bytes to 2574 bytes, which did not leave enough space in the unexpanded VIC20 to draw the maze.
Top Tip - PETCAT
PETCAT is a very handy program that is part of the VICE suite. It can be used to list PRG files as text, or turn text into PRG files.
petcat timebomb.prg > timebomb.basWill generate a text file with a listing of the program.
petcat -w2 -l 1001 -o timebomb.prg -- timebomb.basWill do the reverse and generate a PRG from the listing
-w2 = BASIC V2,
-l 1001 = Load address $1001 (unexpanded VIC)
I did considered rewriting it in C, but looking through the code it is mostly poke and loops, and the data statements contain some assembler routines.
So, I decided to write it all in assembler.
What could possibly go wrong.....
Actually, it wasn't that bad.
I worked through it line by line, starting with the spaced out line in comments and added assembler below to recreate the functionality.
Top Tip - Line Numbers
This program is a good example of speeding things up (a little) with careful use of line numbers.
The code is numbered from 2-42 (line 1 was probably a comment that got removed).
The line numbers themselves are stored as a two-byte value, so the actual number used does not, in itself, make a difference.
1 PRINT "HELLO"
10 PRINT "HELLO"
1000 PRINT "HELLO"
All take up the same space, as all the line numbers are two bytes.
However, when you have a GOTO or a GOSUB, the destination line number is stored as text characters, so the longer the line number, the more bytes it takes up in the file, and the longer it takes to parse.
2 GOTO 1
20 GOTO 10
2000 GOTO 1000
The GOTO 1 part is three bytes (the token for GOTO 1, the space and the character 1).
GOTO 10 is four bytes (GOTO, space, 1 and 0).
GOTO 1000 would be six bytes.
The space after the line number you get for free, that is not stored, it is always displayed in listings. The spaces within the line are stored as part of the program, so add bytes to the file and slow the parsing.
GOTO1 is even better (if less readable) at only two bytes.
This program also moves the least used functions (the initialisation) to line 19 onwards, so the main loop is at line 5, saving a few bytes in various GOTOs.
Development Environment
I had a question about this, so here is some more information on what I use for projects like this.
I use some ancient command line assemblers. These are the ones I have been using for years, they just work and do what I need, and I know how to work around any little issues they may have.
- For 6502 I use ACME.
- For Z80 I use TASM.
I run those on linux, ACME.EXE and TASM.EXE are DOS programs, so I use Wine to run those.
I am not a fan of Makefiles, they allways seem to make things overly complicated.
I use simple command line builds, sometimes in shell scrips.
All it usually takes is something like
wine TASM.EXE -t80 -b Lambda8300.asm Lambda8300.bin
or
wine ACME.exe timebomb.asm
or if you want a listing file
wine ACME.exe -r timebomb.lst timebomb.asm
To edit, I use various text editors.
I have been using Visual Studio recently, it is a nice editor which many useful features. But also some absolute howlers that I can't believe a dedicated code editor is so bad at.
I like the split views, being able to see multiple files at the same time, even safely edit the same file in multiple windows. That is very useful to have function in one window and the caller in another etc.
There is a powerful regular expression based search and replace that I used a lot for things like reformatting the Lambda 8300 ROM code.
e.g. sub_([0-9a-f]{4})h => SUB_\U$1
Would change all the labels like sub_da42h to they style of SUB_DA42
However, there are a couple of major issues I have.
Firstly, the menu keeps locking up so I can't open it. That is the only place there is a "save all" button. Luckily (and probably because they forgot to disable it) ctrl+S still works, but I have to do that regularly until the menu decides to start working again.
Autocomplete is very overzealous.
"Hey, I see you are typing a number. You have typed a number before, it was a different number, but I assume that must be the one you like. Let me change it to the other one for you."
No.
I want to type several different number, that's not unusual is it?
Also, I can't use things like LDA #255 for decimal numeric constants, which is annoying, I have to convert everything to hex LDA #$FF.
If I use # followed by any 3 digit number, even in an asm file, it thinks
"ah, that must be an HTML colour code. Let me add a little square in that colour to annoy you"
"oh, and if you click on it to try to get rid of it, I will change it to 225555"
What?
You just broke my code.
"Oh, and if you try to find the setting to disable it, I will fill you nice neat folders with more folders and XML files. And still not give you the option to disable it."
I ask you, what kind of person would let features like that through?
Who? Only a yoghurt.
Code Breakdown
The code is broken down as follows:
1 - missing, presumably a comment
2 - initialisation then gosub 29
29 - copy code to cassette buffer then return
30-36 data statements
3 - initialise some constants then goto 19
19 - initialise maze creation
20 - sys 861 then clear screen and print "MAKING MAZE"
21 - add spaces down the right (why?) and a line of spaces at the top
22 - 26 - create maze
27 - randomly place the timebomb
28 - sys 830 then set the start point then sys 923 and finally goto 4
4 - play the starting sounds
5 - main loop, if bomb found goto 40
6 - increment time, if timed out, goto 37
7 - ominous ticking sounds
8-14 - read joystick and move
15 - if you tried to move into a wall, go back where you were
16 - if you found the bomb, go back to 5 (which will goto 40)
17-18 - redraw the maze then go back to 5
37-39 - the bomb explodes, gosub 42 then run (full restart)
40-41 - bomb defused, reset for next round, gosub 42 then got 27
42 - print "ROUND r" and "PRESS F7" then wait for F7 and return
The values in the data statements in lines 30-36 are POKEd into RAM in the cassette buffer region, always a good place to store your assembler routines.
The functions within are called from the code, and are as follows:
SYS 830 - setup VIC and fill the colour memory
SYS 861 - fill maze with walls
SYS 887 - scroll screen up
SYS 905 - scroll screen down
SYS 923 - redraw screen
Oh, so all the data is code? There are no data statements for the graphics?
They are interesting, the red and blue gives a sort of "I should be wearing 3D glasses" type effect.
But it's normal PETSCII graphics and a clever trick I haven't seen used like that before.
The wall characters are all the inverse circle, character $B1, the inverse of character $51, as seen here in the font ROM.
; Character $51
.BYTE %00000000 ; ........
.BYTE %00111100 ; ..OOOO..
.BYTE %01111110 ; .OOOOOO.
.BYTE %01111110 ; .OOOOOO.
.BYTE %01111110 ; .OOOOOO.
.BYTE %01111110 ; .OOOOOO.
.BYTE %00111100 ; ..OOOO..
.BYTE %00000000 ; ........
If I set the colours of the first 8 characters to colours 0-7, you can see the circles.
What is happening here is colour 10 is being used. Any colour greater than 7 triggers the multicolour character mode. Here the 8 pixels of the character are used to generate 4 pixels, each of which can be one of four colours.
00 - uses the background colour
01 - uses the character colour
10 - uses the border colour
11 - uses the auxilliary colour (the top 4 bits of $900E)
So the middle lines of the font are split from
.BYTE %01111110 ; .OOOOOO.
to
.BYTE %01 11 11 10
The first pixel is 01, so uses the character colour, in this case set to red.
The second and third pixels are 11, so use the aux colour (light blue)
The final pixel is 10, so uses the border colour, blue
You can see that better if I add spaces and set the alternate squares to colours 8-15.
The time bomb looks more threatening in this mode, but it is just as asterisk.
The player characters are a little bit of a let down, V, <, > and ^, but adding UDGs would be tricky with the way the memory is arranged.
Top Tip - ON -(x) GOTO n
One little trick that is used a few times in the code is ON -(x) GOTO n, the function of which is not immediately obvious if you are not familiar with it.
ON x GOTO 10,20,30Will GOTO 10 if x=1, 20 if x=2, 30 if x=3 or continue for any other values.
It will even continue along the line, so you can have code for the "else" case.
ON x GOTO 10,20,30 : PRINT "none of the above"In the Timebomb code there are things like line 17
17 on -(me>7921) goto 18 : sys 887 : me = me+22 : goto 5This is saying if "-(me>7921)" = 1 then goto 18 else do the rest of the line.
The conditional (me>7921) returns -1 if true, 0 if false, so the - sign outside the brackets converts that to +1.
This makes more sense to me if I rewrite it as an IF THEN ELSE statement
17 IF (me>7921) THEN goto 18 ELSE sys 887 : me = me+22 : goto 5Note the -1 is no longer required as IF is only testing for non-zero.
Quite handy if you need to save space, or want to obfuscate your code, or both.
Numbers
There were a few interesting challenges with numbers.
The standard numbers in Commodore BASIC are floating point, so can happily go over 255.
Floating point numbers are a difficult to work with in assembler. Several of the variables will never go over 255, so I can replace those with single byte variables.
One thing I missed is that BASIC variable as initialised to zero, so I needed to add that to the start.
Top Tip - Always Leave Y=0
I have found it useful over the years to get into the habit of leaving the Y register in the 6502 set as 0.
That means I can initialise variables at the start
L1:
ldy #0 ; (leave Y as 0)
sty OM_LSB ; om = 0
sty OM_MSB
sty F ; f = 0
sty C ; c = 0
sty R_UNITS ; r = 0
sty R_TENS
sty K_UNITS ; k = 0
sty K_TENSAnd later on, anything that needs to be set to 0, I just use STY
; 41 poke v-1, 0 : f = 0 : k = x : ...
L41:
sty VIC_NOISE_FREQ ; v-1 = 0
sty F ; f = 0
lda X_TENS ; k = x, start K counter at X
sta K_TENS
sty K_UNITS ; X_UNITS is always 0A handy 6502 feature is being able to use two sequential bytes in zero page as a pointer, however the only addressing mode which supports it needs X or Y.
lda (ZP_PTR),yThat loads the accumulator with the value read from address make up from the values stored at those two bytes and Y, (ZP_PTR) + 256*(ZP_PTR+1) + Y.
Quite often I just want the value at that address, so I need Y to be 0 for those accesses, another reason to leave Y set to 0.
Some of the number are addresses of things like the VIC chip and the VIA, which make the BASIC program smaller, but in assembler, it is easier to just hard code the values (since they do not change).
3 d = 37154 : p1 = d-3 : p2 = d-2
8 poke d, 127 : p = peek(p2)
Can be replaced with the hard coded values
lda #$7F
sta $9122
lda $9120
Or, it is better to used named constants for things like that.
VIA1_PORTA = $911F ; p1
VIA2_PORTB = $9120 ; p2
VIA2_DDRB = $9122 ; d
lda #$7F ; set joystick 3 as input
sta VIA2_DDRB
lda VIA2_PORTB ; read the port
That makes it easier to follow.
There are some other numeric variables, which are used as counters which count to numbers in the hundreds, so cannot be stored in one byte.
Normally you would store these as a 16 bit value in two bytes, but any arithmetic involving 16-bit numbers is complicated as there are no 16-bit arithmetic instructions in the 6502. Any time you need to do any, it requires separate chunks of code to work on the LSB, and if required, the MSB.
Even comparison between two 16-bit numbers is complicated, and I think this is where I messed up on the first version of this code.
Top Tip - Comparing 16 bit numbers
I will start with comparing two 8 bit values.
LDA val1
CMP val2The zero flag is set if val1 = val2
The carry flag is set if val1 >= val2
So you can test with
BEQ numbers_are_equal
BCS val1_larger_or_equal
BCC val2_largerWith a 16 bit number, you compare the MSB first. If they are the same, then compare the LSB instead.
LDA val1_msb
CMD val2_msb
BNE go_compare ; MSBs are different, so compare those
; MSBs are the same, so compare LSB
LDA val1_lsb
CMD val2_lsb
go_compare:
BEQ numbers_are_equal
BCS val1_larger_or_equal
BCC val2_larger
To avoid that, I like to use a shortcut which saves some unnecessary multiplication and division (which is tricky enough with 8-bit numbers if it's not a power of 2 that you can relace with a shift operation).
Top Tip - Tens and Units
With several of these variables, e.g. K, the clocK, it counts from 0-600 at which point the bomb goes off.
If this was stored as a 16-bit value, to check if it has reached 600, I would need to load and check both bytes to see if they equaled $02 and $58.
Instead, I store the values as two bytes, X_TENS and X_UNITS. I increment X_UNITS and check to see if it has gone past 9. If it has, I set it to 0 (Y = 0) and increment X_TENS.
inc K_UNITS
lda K_UNITS
cmp #10
bcc +
sty K_UNITS
inc K_TENS
+When it comes to test for > 600, I only need to test the X_TENS value in a single byte comparison, is it over 60.
lda K_TENS
cmp #60
bcc L7
Another issue I hit a few times was adding an 8 bit signed number to a 16 bit number. There were a lot of "add 22 to the position to move down 1 row" or "subtract 22 to the position to move up 1 row".
Top Tip - Adding a signed 8 bit value to a 16 bit number
Something like me = me+c looks simple, but when me is a 16-bit location and c could be positive or negative, it took a bit of thought.
Positive numbers are relatively easy, you add the number to the LSB, and check if there is a carry. If there is, you increment the MSB.
lda C
clc
adc ME_LSB
sta ME_LSB
bcc +
inc ME_MSB
+Note you need to clear the carry before you start as the 6502's only addition instruction is adc (add with carry).
(and unlike other 8-bit processors I could name, the 6502 has two nice simple instructions to set and clear the carry flag. None of this "one instruction sets the carry flag, the other one inverts the carry flag")
Negative numbers are stored in two's compliment, so -1 is actually $100-$01 = $FF.
The range goes from -128 to + 127. In this case, C could be -22, which is stored as $EA.
Rather than subtracting $16 (which is 22 in hex), you add $EA (-22).
So $40 - $16 is implemented as $40 + $EA. The result is $012A, but if this is an 8-bit number, the carry is ignored, so you get the result $2A.
When looking at 16-bit values, you end up with an answer which is $0100 too high.
If $2040 - $16 is implemented as $2040 + $EA, the result is $212A, which is wrong.
You need to do $2040 + $EA - $0100, and you get $202A.
In practice, you decrememnt the MSB before you add the two's compliment of the number you want to subtract.
Yes, my head hurts too.
lda C
bpl +
dec ME_MSB
+
clc
adc ME_LSB
sta ME_LSB
bcc +
inc ME_MSB
+Here the bpl + skips over the decrement of the MSB if the value is positive. The remainder is an addition, which is the same for positive or negative numbers.
There was also an array of directions in the BASIC program.
If the array is very small, there is a trick you can use to simplify things.
Top Tip - Simple Arrays
A small array was setup as part of line 19
; 19 dim a(3) : a(0) = 2 : a(1) = -44 : a(2) = -2 : a(3) = 44A simple way to store that is to define some space in zero page for the 4 elements
; a = 2, -44, -2, 44
lda #2
sta ARRAY
lda #-44
sta ARRAY+1
lda #-2
sta ARRAY+2
lda #44
sta ARRAY+3
Then when it is used, you can use zero paged X-indexed addressing to read the value from the array.
; 23 b = a9 + a(j)
ldx J
lda ARRAY,x
However, another place it was used then divided by 2.
; poke a9 + a(j)/2, hl
My brain was already melted, and I think I could have handled the division by a rotate right, but it would need to decide to set carry and then doing a rotate right for a negative number, or a logical shift right if it was positive. It seemed like that would be a lot of extra steps for each access.
I decided to take a different approach.
Top Tip - Avoid Doing the Maths
Instead of doing the divide by 2 each time, I created a second array.
This contained pre-calculated values that were half of those in the normal array.
lda #1
sta ARRAY_HALF
lda #-22
sta ARRAY_HALF+1
lda #-1
sta ARRAY_HALF+2
lda #22
sta ARRAY_HALF+3I could then access those immediately using
lda ARRAY_HALF,x
The same thing happened in a few FOR loops, where the itterator was always divided or multiplied.
For example, part of line 4
for tt = 0 to 30 : poke v, tt/2 : next
I instead implemented that as
for tt= 0 to 15 : poke v, tt : next
It does the same thing, but faster, so it needs a longer delay.
Top Tip - Simple Delays
"you've got to speed it up, and then you've got to slow it down"
As assembler is a lot faster than BASIC, with FOR loops like this used to create sounds, I needed to add quite a lot of delay loops to match the speed of the BASIC.
My main delay loop of choice was a simple 256 cycle delay using the Y register.
- dey
bne -Y is always left as 0 (see above), so the first DEY takes it to $FF, that is not zero, so it loop. $FE is not zero, loop again, down to $00. That is zero, so it exits, and leaves Y as zero as it should be.
Where I needed more of those, I involved the X register as well.
ldx #$04
- dey
bne -
dex
bne -This loops around the 256 cycle loop 4 times.
What more can I tell you?
Well, in a few places a needed a random number.
Top Tip - Random Numbers
When I first looked at converting this program, George Beckett had recently been discussing Random Number Generators in out Patreon discord (hint, join the Patreon if you want to join the discord, a great place to find out about project updates and behind the scenes stuff, and discuss your own projects).
He found a routine on a page that has since disappeared, but was thanks to the wonderful archive.org "wayback machine", it is still available.
(Incidentally, that is an incredibly useful resource to have when you follow a link to a webpage that isn't there anymore. They need donations to keep going, so if you find it useful, and you are able to do so, please give them something.)
From that page:
"Xorshift is a fast pseudorandom generator algorithm originally developed by George Marsaglia. John Metcalf found a 16-bit version of the algorithm that is fast on 8-bit platforms with only single bit shifts available. It has a period of 65535 and passes reasonable tests for randomness. His pseudocode is reprinted here:
/* 16-bit xorshift PRNG */
unsigned x = 1;
unsigned xorshift( )
{
x ^= x << 7;
x ^= x >> 9;
x ^= x << 8;
return x;
}
The page then presented a version for the Commodore 64.
George used it on an assembler version of a 10 PRINT maze for the Commodore PET.
I have used that same code here.
RAND16:
lda SEED_MSB
lsr
lda SEED_LSB
ror
eor SEED_MSB
sta SEED_MSB
ror
eor SEED_LSB
sta SEED_LSB
eor SEED_MSB
sta SEED_MSB
rtsIt is seeded at the start of the program from the VIA1 timer, which is set counting at boot.
start:
lda VIA1_TIMER2_LSB
sta SEED_LSB
lda VIA1_TIMER2_MSB
sta SEED_MSB
When I started this process, I followed the path of the code, starting with the maze generation, and then movement and redraw (which is where I got the 16-bit comparison wrong).
Once I fixed the movement, I could progress to the "you won" and "you lost" stages.
There isn't much displayed on the screen, both cases display the same:
The number of the current round needs to be displayed, as part of that.
Top Tip - Displaying Simple Numbers
If you have a number from 0-9 to display, simply add $30 to it and store it in screen RAM.
lda R
clc
adc #$30 ; A = R+$30 (0->"0", 1->"1" etc.)
sta ROUND_DIGIT_1That works fine until you hit 10.
Here I again split into R_TENS and R_UNITS, to make it easier to display. If R_TENS was 0, then just display R_UNITS, if not, display R_TENS and then R_UNITS.
lda R_TENS
beq L42_1
; double digit
clc
adc #$30
sta ROUND_DIGIT_1
lda R_UNITS
clc
adc #$30
sta ROUND_DIGIT_2
bne L42_2 ; branch always
L42_1:
; single digit
lda R_UNITS
clc
adc #$30
sta ROUND_DIGIT_1That could probably be optimised a bit, but it only runs one at the end of each round, so it's not that critical.
The next step is to wait for F7 to be pressed. I was considering changing this to "press fire", as it is easier to check the joystick port, but when the keyboard is only being used for this, you can take a shortcut and use a very simple technique.
Top Tip - Simple "Press a Key"
There is a simple way to just check for a single key press, or a specific key.
$C5 in zero page holds the last key pressed. This is updated by the interrupt routine as it scans the keyboard.
If no key is pressed, it will return $64.
-
lda LAST_KEY ; check last key pressed
cmp #NO_KEY ; is a key pressed?
beq - ; no, keep checkingHere I want to check for F7, which is $63.
-
lda LAST_KEY ; check last key pressed
cmp #KEY_F7 ; was it F7?
bne - ; no, keep checkingYou might want to write your own keyboard routines if you are using the keyboard for directional movement, but it's fine for "press the any key".
With all that done, there was a lot of tweaking of delays to make the sounds right, and the loop timing.
The game has a clock, K, which counts up to 600. I am not sure if that is meant to represent 60 seconds, but in practice on a PAL VIC20, the original game gave you about 80 seconds.
I think I got the sounds and explosion effect reasonably close, and the ominous ticking sound.
The only real difference to the timing is the maze generation.
On the BASIC version, that takes about 55 seconds.
On the assembler version, it is pretty much instantaneous, so much so that I didn't even bother putting up the "making maze" screen as you would barely see it.
I found the same thing when I converted 3D Monster Maze, rather than a minute watching "the mists of time", I had to add a dissolve effect to make it look like something had happened.
Once all that was sorted out, I had a final pass through and was able to optimise out X_UNITS, as this was always zero (X was incremented by 50 each time).
I also rewrote the original assembler routines, with I think shorter and faster ones (he said with the arrogance of a programmer with a long grey beard who has been doing this for a long time).
e.g. part of SYS 830
This fills the colour RAM from $9600 to $97FF with $0A (the colour light red)
I don't think it is beneficial to use zero page here when the destination is always going to be fixed.
lda #$00 ; destination 9600
sta $FB
lda #$96
sta $FC
ldy #$00
L034D:
lda #$0A ; fill with 0A
L034F:
sta ($FB),y
iny
bne L034F
inc $FC ; keep going until 9800
lda $FC
cmp #$98
bne L034D
I went with a simpler approach
lda #COLOUR_LIGHTRED ; all text colour to light red
- sta COLOUR_RAM_PAGE0,y
sta COLOUR_RAM_PAGE1,y
iny
bne -
I also inline that function into line 28 to save a JSR and RTS.
Since line numbers no longer affect speed, I also shuffled things around so that the maze generation was at the start, where it logically comes in the sequence.
Other Things to Consider
The bottom of the maze is neatly hidden, the maze if stored in RAM right below the screen RAM, so when it hits the bottom of the maze, it continues copying the top of the screen.
You can see the top half is repeated below the solid line.
The top of the maze is not handled this way, instead you get a view of the BASIC heap I think.
I did consider tidying that up, but I haven't.*
I also considered a visible timer, but I think the ominous ticking of the clock is fine.
I did think about adding a title screen, even instructions, but I don't think it needs it.
I will call it done for now that.
I have also uploaded the source to github, with the original BASIC, spaced out BASIC and assembler versions.
There is also a PRG file if you want to play it on a real VIC or an emulator - remember to set remove or disable any memory expansions, or set the memory on the emulator to none (not 0, that is very different).
xvic -memory none timebomb.prg
The original was BASIC version was 1843 bytes.
The final tally of the assembler rewrite is 1067 bytes. It seems like I should try to get it down to below 1024 bytes, but I won't.*
* Part 2
OK, I did tidy things up a bit and fixed a few bugs (which were present in the original) and tried to get it under 1K.
I also wrote a version for the PET. That is all to follow in Part 2 next week.
All items are still available, and I can ship worldwide.
International shipping is a bit complicated at the moment, so if you want to order from outside the UK, please see this page for more information:
Sorry to have to do this, and I know this is going to affect sales, but please understand it is the only option I have right now.
Patreon
If you enjoy posts like this, you can support me via Patreon, and get access to advance previews of blog posts, and exclusive posts.
You also get progress updates on new projects and other behind the scenes updates, as well as access to my Patreon only Discord server for even more regular updates, and to discuss your own projects.
I currently have 50% off your fist month if you want to try it out.