DEV Community

Ricardo Sueiras for AWS

Posted on • Originally published at community.aws

Back to the future: Writing 6502 assembler with Amazon Q Developer

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

Image description

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.

Image description

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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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.

Image description

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

Enter fullscreen mode Exit fullscreen mode

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!

Top comments (1)

Collapse
 
sickpuppysoftware profile image
Matt King

Very cool