Have you heard about the AWS Game Builder Challenge? This is a great competition where you need to create a game with AWS services of your choosing: all skill levels welcome!
In a recent post I talked about how I was able to quickly create a sideways scrolling arcade game with the help of Amazon Q Developer. I was talking to a few folk online who had got in touch to know more, and the subject turned to whether Amazon Q Developer could write a different kind of game, one that would run on the first computer I ever owned, the Commodore 64. Well there was only one way to find out, so I spent around an hour experimenting and this post is what I learned. Buckle up for some retro computing fun!
Writing my first C64 Basic program
After creating a new project workspace and starting VSCode, I ask Amazon Q Developer in the chat interface the following prompt:
Amazon Q Chat > Write a simple programming in Commodore 64 basic that displays a sprite moving across the screen smoothly. Make sure the screen is reset first and set to a random colour
I grab the code and start VICE, which is my preferred C64 emulator. I select the standard C64 from the available emulated machines, and then paste the code in. Oh dear, this does not look good.
I ask Amazon Q to suggest a fix, and it provides a bunch of ideas, but the one that immediately seems to make sense to me is this line:
Replace uppercase letters with lowercase (the C64 automatically capitalizes)
I ask Amazon Q to change the code to lowercase, and try again. Success this time, and now I have the code that Amazon Q generated in my virtual C64. Here is the code it produced.
10 print chr$(147): rem clear screen
20 poke 53280,int(rnd(1)*16): rem random border color
30 poke 53281,int(rnd(1)*16): rem random background color
40 rem initialize sprite data
50 for i = 0 to 62: read a: poke 832+i,a: next i
60 rem sprite data (simple square)
70 data 255,255,255,255,255,255,255,255,255,255,255,255
80 data 255,255,255,255,255,255,255,255,255,0,0,0,0,0,0
90 data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
100 data 0,0,0,0,0,0,0,0,0,0,0,0
110 rem set up sprite
120 poke 53248,0: rem x coordinate (low byte)
130 poke 53249,100: rem y coordinate
140 poke 2040,13: rem sprite 0 data from block 13 (832/64)
150 poke 53287,1: rem sprite 0 color (white)
160 poke 53269,1: rem enable sprite 0
170 rem main loop
180 for x = 0 to 320
190 poke 53248,x and 255: rem update x coordinate (low byte)
200 poke 53264,(x>255): rem update x coordinate (high bit)
210 for d = 1 to 10: next d: rem delay for smooth movement
220 next x
230 goto 180: rem repeat the movement
It runs.....well almost. I see a square sprite move smoothly across the screen and when it gets to the end it crashes with the following error: Illegal quantity error in 200
I turn to Amazon Q to help me fix this issue, asking it to resolve this error. It quickly provides me with updated code and an explanation of why I got the error.
Here's what this does: (x>255) is a boolean expression that evaluates to -1 when true and 0 when false in Commodore 64 BASIC. The minus sign (-) in front of (x>255) negates this, giving us 1 when x > 255 and 0 when x <= 255. This result (either 0 or 1) is then used as the value for the POKE statement. This correction ensures that the high bit of the sprite's X coordinate is set correctly when the sprite moves beyond the 255th pixel, allowing it to move across the entire width of the screen.
I run it again and success.
Making it more interesting
I want to make this a little more interesting so I ask Amazon Q to change the code:
Amazon Q Chat > can you update the code so that there are four sprites in different colours this time. can you make it so that they bounce around the screen
It provides me some updated code
10 print chr$(147): rem clear screen
20 poke 53280,0: poke 53281,0: rem black border and background
30 rem initialize sprite data
40 for i = 0 to 62: read a: poke 832+i,a: next i
50 rem sprite data (simple square)
60 data 255,255,255,255,255,255,255,255,255,255,255,255
70 data 255,255,255,255,255,255,255,255,255,0,0,0,0,0,0
80 data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
90 data 0,0,0,0,0,0,0,0,0,0,0,0
100 rem set up sprites
110 for s = 0 to 3
120 poke 2040+s,13: rem sprite data from block 13 (832/64)
130 poke 53287+s,s+2: rem sprite colors (red, cyan, purple, green)
140 x(s) = int(rnd(1)*320): rem random x position
150 y(s) = int(rnd(1)*200)+30: rem random y position
160 dx(s) = (rnd(1)>0.5)*2-1: rem random x direction
170 dy(s) = (rnd(1)>0.5)*2-1: rem random y direction
180 next s
190 poke 53269,15: rem enable first 4 sprites
200 rem main loop
210 for s = 0 to 3
220 x(s) = x(s) + dx(s)
230 y(s) = y(s) + dy(s)
240 if x(s) < 24 or x(s) > 320 then dx(s) = -dx(s)
250 if y(s) < 50 or y(s) > 229 then dy(s) = -dy(s)
260 poke 53248+s*2,x(s) and 255: rem x coordinate (low byte)
270 poke 53264,peek(53264) and (255-2^s) or (-(x(s)>255))*2^s
280 poke 53249+s*2,y(s): rem y coordinate
290 next s
300 for d = 1 to 10: next d: rem delay for smooth movement
310 goto 200: rem repeat the movement
Which runs first time. I know have four sprites bouncing around the screen.
Changing the Sprites
The next thing I want to do is change the Sprites to something more interesting.
Amazon Q Chat > can you tell me about any online C64 sprite editing tools that I can use to help me create my own sprites?
It provides some great resources, including a website called spritemate.com, which I head over to and quickly put together a more interesting looking sprite.
I can grab the data directly from the tool, and then update my code.
One of the neat features of these emulated computers is the ability to change the CPU speed. The above videos are all running at 800% of the native C64 6502 speed. When running at baseline, the sprites move much more slowly. This is to be expected - back in the day, you used Basic for simple programs where performance was not necessarily critical. If you needed that, you had to learn assembler.
Writing 6502 Assembler
I have no clue about how to approach this in VSCode on my Mac. After looking at some Redit groups, it is clear that the way forward is to use the Kick Assembler tool together with a VSCode plugin. Once I have those installed and configured I am ready.
I select all the code and Send to Prompt, then ask Amazon Q:
Amazon Q Chat > convert this commodore basic program to run 6502 assembler that will run on the Vice c64 emulator. I want to use the Kick Assembler tool.
I am super impressed that it quickly provides something I remember from my teenage years
// Constants
.const SPRITE_DATA = $0340 // 832 in decimal
.const SPRITE_POINTER = $07F8 // 2040 in decimal
.const VIC_SPRITE_X = $D000 // 53248 in decimal
.const VIC_SPRITE_Y = $D001 // 53249 in decimal
.const VIC_SPRITE_MSB_X = $D010 // 53264 in decimal
.const VIC_SPRITE_COLOR = $D027 // 53287 in decimal
.const VIC_SPRITE_ENABLE = $D015 // 53269 in decimal
.const VIC_SPRITE_EXPAND_Y = $D017 // 53271 in decimal
.const VIC_SPRITE_EXPAND_X = $D01D // 53277 in decimal
.const CHROUT = $FFD2
.const RASTER = $D012
.const RANDOM = $D41B
// Zero page variables
.const zp_x = $FB
.const zp_y = $FE
.const zp_dx = $101
.const zp_dy = $104
BasicUpstart2(start)
* = $0810 "Main Program"
start:
jsr clear_screen
jsr init_sprite_data
jsr setup_sprites
jsr init_variables
jmp main_loop
clear_screen:
lda #147 // Clear screen character
jsr CHROUT
rts
init_sprite_data:
ldx #0
load_sprite_data:
lda sprite_data,x
sta SPRITE_DATA,x
inx
cpx #63
bne load_sprite_data
rts
setup_sprites:
ldx #0
setup_sprite_loop:
txa
asl
tay
txa
asl
asl
asl
asl
asl
clc
adc #80
sta VIC_SPRITE_X,y // X coordinate (80, 112, 144)
txa
asl
asl
asl
asl
asl
clc
adc #80
sta VIC_SPRITE_Y,y // Y coordinate (80, 112, 144)
lda #13
sta SPRITE_POINTER,x // Sprite data pointer
txa
clc
adc #2
sta VIC_SPRITE_COLOR,x // Sprite color (different for each sprite)
inx
cpx #3
bne setup_sprite_loop
lda #7
sta VIC_SPRITE_ENABLE // Enable first 3 sprites
sta VIC_SPRITE_EXPAND_Y // Y-expand first 3 sprites
sta VIC_SPRITE_EXPAND_X // X-expand first 3 sprites
rts
init_variables:
ldx #0
init_var_loop:
lda #160
sta zp_x,x
lda #100
sta zp_y,x
jsr random_direction
sta zp_dx,x
jsr random_direction
sta zp_dy,x
inx
cpx #3
bne init_var_loop
rts
random_direction:
lda RANDOM
and #3 // 0-3 instead of 0-1
beq positive_slow
cmp #1
beq positive_fast
cmp #2
beq negative_slow
negative_fast:
lda #$FE // -2
rts
negative_slow:
lda #$FF // -1
rts
positive_slow:
lda #1
rts
positive_fast:
lda #2
rts
main_loop:
ldx #0
sprite_loop:
jsr update_position
jsr check_boundaries
jsr update_sprite_position
inx
cpx #3
bne sprite_loop
jsr delay
jmp main_loop
update_position:
lda zp_x,x
clc
adc zp_dx,x
sta zp_x,x
lda zp_y,x
clc
adc zp_dy,x
sta zp_y,x
rts
check_boundaries:
// Check X boundaries
lda zp_x,x
cmp #50
bcc bounce_x
cmp #250
bcs bounce_x
jmp check_y
bounce_x:
lda zp_dx,x
eor #$FF
clc
adc #1
sta zp_dx,x
check_y:
// Check Y boundaries
lda zp_y,x
cmp #70
bcc bounce_y
cmp #220
bcs bounce_y
rts
bounce_y:
lda zp_dy,x
eor #$FF
clc
adc #1
sta zp_dy,x
rts
update_sprite_position:
txa
asl
tay
lda zp_x,x
sta VIC_SPRITE_X,y
lda zp_x,x
cmp #255
bcc msb_clear
lda VIC_SPRITE_MSB_X
ora bit_masks,x
jmp msb_set
msb_clear:
lda VIC_SPRITE_MSB_X
and bit_masks_inv,x
msb_set:
sta VIC_SPRITE_MSB_X
lda zp_y,x
sta VIC_SPRITE_Y,y
rts
delay:
ldx #3
delay_loop:
ldy #255
inner_delay:
nop
nop
nop
nop
dey
bne inner_delay
dex
bne delay_loop
rts
bit_masks:
.byte 1, 2, 4
bit_masks_inv:
.byte $FE, $FD, $FB
sprite_data:
.byte 0,0,0,12,66,112,18,66,136,51,66,128,33,90,112,63
.byte 90,8,33,102,136,33,66,112,0,0,0,0,0,60,0,0
.byte 4,96,0,36,56,0,228,15,131,128,0,254,0,0,0,0
.byte 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4
I compile the code and then automatically run it via the VSCode plugin integration, and success, we have our sprite bouncing across the screen.
It is not perfect, as the updated code has lost a few things: the background colour and three sprites! However, this is now running at 100% speed, so you can see how dramatically better the performance is.
I ask Amazon Q to update the assembler to add additional sprites.
Amazon Q, Send to Prompt > Update the C64 assembler so that there are three sprites rather than one.
I have to have some back and forth, as Amazon Q gives me just snippets of the updated code. I follow up with the prompt:
Amazon Q Chat interface > provide complete code update
Which provides me with the updated code, which compiles and runs.
As you can see, running in assembler is much faster than when running this code in Commodore basic.
Wrapping up and next Steps
In this post I showed you how you can use Amazon Q Developer to help you write code, even if that code runs on a machine that is over 40 years old. Amazon Q Developer is simple an awesome companion that I am finding is able to help me across any task I throw at it - including creating Commodore 64 basic and assembler applications.
You might have some legacy code, perhaps some assembler or old code that you have been looking to modernise, update, or even figure out how to refactor. I hope this post will give you with ideas of the potential that tools like Amazon Q Developer provide.
You can try Amazon Q Developer for free today, by signing up for a Builder ID, and then using that to login in after installing the plugin in VSCode or Intelij.
Check out my other posts on community.aws, as I have been sharing a lot of tips and use cases of how I am using Amazon Q Developer. You can also keep up to date with all the new features and improvements of Amazon Q Developer by checking out the changelog.
Call to action
If you're ready to build your own game, AWS is running a hackathon through the month of November. From a simple tic tac toe game built with HTML/CSS/JS to a complex Unity game -- all skill levels and tech stacks are welcome. Join now on DevPost: https://awsdevchallenge.devpost.com! and I hope this post will inspire some of you to join too and maybe submit some retro games!