Espamática
ZX SpectrumRetroZ80 Assembly

ZX Spectrum Assembly, Pong – 0x04 We start moving the ball

In this ZX Spectrum Assembly chapter, we will make the first tests to move the ball.

Translation by Felipe Monge Corbalán

Table of contents

We start moving the ball

Create a folder called Step04, create a file called main.asm in it and copy sprite.asm and video.asm from Step03.

We start by editing the sprite.asm file to define the necessary ball data:

ASM
BALL_BOTTOM:   EQU $ba
BALL_TOP:      EQU $00

As with the paddles, we define lower and upper limits for the ball, in the format TTLLLSSS.

ASM
ballPos:       dw $4870
ballSetting:   db $00
ballRotation:  db $f8

As with the paddles, we will use a variable to hold the position of the ball at each moment, ballPos.

In ballSetting we store the X-speed in bits 0 to 3, the Y-speed in bits 4 and 5, the X-direction (0 right / 1 left) in bit 6 and the Y-direction (0 up / 1 down) in bit 7.

We store the rotation of the ball in ballRotation, where rotation to the right is represented by positive values and rotation to the left by negative values.

The rotation is necessary because of the way we are going to do the horizontal movement.

The ball will consist of one empty scanline, four scanlines with the visible part and another empty scanline. The blank scanlines ensure that the ball leaves no trace when it moves.

We will define two bytes to paint the ball and define each movement pixel by pixel:

ASM
; Ball sprite:  1 line at 0, 4 lines visible, 1 line at 0
ballRight:     ; Right         Sprite         Left
db $3c, $00    ; +0/$00 00111100    00000000 -8/$f8
db $1e, $00    ; +1/$01 00011110    00000000 -7/$f9
db $0f, $00    ; +2/$02 00001111    00000000 -6/$fa
db $07, $80    ; +3/$03 00000111    10000000 -5/$fb
db $03, $c0    ; +4/$04 00000011    11000000 -4/$fc
db $01, $e0    ; +5/$05 00000001    11100000 -3/$fd
db $00, $f0    ; +6/$06 00000000    11110000 -2/$fe
db $00, $78    ; +7/$07 00000000    01111000 -1/$ff
ballLeft:
db $00, $3c    ; +8/$08 00000000    00111100 +0/$00

Each line defines the visible part of the sphere, depending on how the pixels are positioned. We define two bytes per position. In the comment you can see the rotation when the ball goes to the right, the bytes we are going to paint, and the rotation when the ball goes to the left.

The start ball is drawn as shown in the first sprite:

00111100 00000000

If it moves one pixel to the right, we don’t change the position of the ball, we change the rotation and draw the second sprite:

00011110 00000000

When we reach the last rotation, we change the position of the ball, specifically the column. The last aspect of the code is:

ASM
; Limits of the objects on the screen
BALL_BOTTOM:   EQU $ba     ; TTLLLSSS
BALL_TOP:      EQU $00     ; TTLLLLLSSSSS

; Ball sprite:
;     1 line at 0, 4 lines 3c, 1 line at 0
ballRight:      ; Right        Sprite         Left
db $3c, $00    ; +0/$00 00111100    00000000 -8/$f8
db $1e, $00    ; +1/$01 00011110    00000000 -7/$f9
db $0f, $00    ; +2/$02 00001111    00000000 -6/$fa
db $07, $80    ; +3/$03 00000111    10000000 -5/$fb
db $03, $c0    ; +4/$04 00000011    11000000 -4/$fc
db $01, $e0    ; +5/$05 00000001    11100000 -3/$fd
db $00, $f0    ; +6/$06 00000000    11110000 -2/$fe
db $00, $78    ; +7/$07 00000000    01111000 -1/$ff
ballLeft:
db $00, $3c    ; +8/$08 00000000    00111100 +0/$00

; Ball position
ballPos:      dw $4870     ; 010T TSSS LLLC CCCC

; Ball speed and direction.
; bits 0 to 3: speed X: 1 to 4
; bits 4 to 5: speed Y: 0 to 3
; bit 6:       X direction: 0 right / 1 left
; bit 7:       Y direction: 0 up / 1 down
ballSetting:  db $00
; Ball rotation
; Positive values right, negative values left
ballRotation: db $f8

Now we are going to implement the routine that paints the ball in video.asm; we are going to put it after the PreviousScan routine:

ASM
PrintBall:
ld   b, $00
ld   a, (ballRotation)
ld   c, a
cp   $00
ld   a, $00
jp   p, printBall_right

First we find out the horizontal direction of the ball. Then we add or subtract the rotation to the base sprite of the ball to get the sprite to paint. The base direction of the sprite is stored in HL and we subtract or add the rotation that we will have in BC, so the first thing to do is set B to zero, LD B, $00.

The next step is to load the rotation of the ball into A, LD A, (ballRotation), and from there into C, LD C, A. We could load the value directly into C, after passing through HL, but depending on the value we get, it will go to the right or to the left. To get this, we compare the value with zero, and since the comparisons are always made against the A register, it is necessary to load the rotation into this register.

We compare the value of A with zero, CP A, $00, if the result is positive, the ball moves to the right and we jump, JP P, printBall_right. Before, we set A = 0 for the following calculations, LD A, $00.

We continue to implement the movement to the left:

ASM
printBall_left:
ld   hl, ballLeft
sub  c
add  a, a
ld   c, a
sbc  hl, bc
jr   printBall_continue

If the ball moves to the left, we have to load the direction of the left base sprite in HL, LD HL, ballLeft.

At this point A is zero, so we subtract the rotation we have in C to get the value we need to subtract to get the correct sprite, SUB C:

Example: C = $FF, A = $00    A – C = $01

As each sprite occupies two bytes, we have to duplicate the value to subtract from HL, ADD A, A, and then load it into C, LD C, A.

Now we can calculate the address of the sprite to print, SBC HL, BC, and print the ball, JR printBall_continue.

We implement the movement to the right:

ASM
printBall_right:
ld   hl, ballRight
add  a, c	
add  a, a	
ld   c, a
add  hl, bc

If the ball moves to the right, the routine is slightly different. We again load the direction of the base sprite into HL, LD HL, ballRight, in this case it goes to the right, we add the rotation to A, ADD A, C, multiply by two, ADD A, A, and load the result into C, LD C, A, and then add it to HL, ADD HL, BC, to get the direction of the sprite to print.

And now we print the ball:

ASM
printBall_continue:
ex   de, hl
ld   hl, (ballPos)

Since the NextScan routine receives the current address in HL and returns, also in HL, the new address, the first thing to do is to load the value of HL in DE, EX DE, HL. With EX we exchange the value of the registers and save four clock cycles and one byte compared to LD (LD D, H and LD E, L). We load the position of the ball in HL, LD HL, (ballPos).

ASM
ld   (hl), ZERO
inc  l
ld   (hl), ZERO
dec  l
call NextScan

We paint the first byte of the first scanline empty, LD (HL), ZERO, increment the column to go to the next byte, INC L, paint the second byte, LD (HL), ZERO, leave the column as it was, DEC L, and get the next scanline, CALL NextScan.

The next step is to draw the four scanlines that are actually visible on the ball:

ASM
ld   b, $04
printBall_loop:
ld   a, (de)
ld   (hl), a
inc  de
inc  l
ld   a, (de)
ld   (hl), a
dec  de
dec  l
call NextScan
djnz printBall_loop

We load the scanlines to be painted into B, LD B, $04, the first byte of the sprite into A, LD A, (DE), and paint it, LD (HL), A.

We point DE to the next byte of the sprite, INC DE, point HL to the next column, INC L, load the sprite into A, LD A, (DE), and paint it, LD (HL), A.

We repoint DE to the first byte of the sprite, DEC DE, HL, to the previous column, DEC L, and calculate the next scanline, CALL NextScan.

Repeat all operations until B is zero, DJNZ printBall_loop.

ASM
ld   (hl), ZERO
inc  l
ld   (hl), ZERO

ret

We draw the first byte of the last empty ball scanline, LD (HL), ZERO, increment L to point to the next column, INC L, and draw the second byte, LD (HL), ZERO.

The final code of the routine is as follows:

ASM
;--------------------------------------------------------------------
; Paint the ball.
; Alters the value of the AF, BC, DE and HL registers.
;--------------------------------------------------------------------
PrintBall:
ld   b, $00                ; B = 0
ld   a, (ballRotation)     ; A = ball rotation, what to paint?
ld   c, a                  ; C = A
cp   $00                   ; Compare with 0, see where it rotates to
ld   a, $00                ; A = 0
jp   p, printBall_right    ; If positive jumps, rotates to right

printBall_left:
; The rotation of the ball is to the left
ld   hl, ballLeft          ; HL = address bytes ball
sub  c                     ; A = A-C, ball rotation
add  a, a                  ; A = A+A, ball = two bytes
ld   c, a                  ; C = A
sbc  hl, bc                ; HL = HL-BC (ball offset)
jr   printBall_continue

printBall_right:
; Ball rotation is clockwise
ld   hl, ballRight         ; HL = address bytes ball
add  a, c                  ; A = A+C, ball rotation
add  a, a                  ; A = A+A, ball = two bytes
ld   c, a                  ; C = A
add  hl, bc                ; HL = HL+BC (ball offset)

printBall_continue:
; The address of the ball definition is loaded in DE.
ex   de, hl
ld   hl, (ballPos)         ; HL = ball position

; Paint the first line in white
ld   (hl), ZERO            ; Moves target to screen position
inc  l                     ; L = next column
ld   (hl), ZERO            ; Moves target to screen position
dec  l                     ; L = previous column
call NextScan              ; Next scanline

ld   b, $04                ; Paint ball in next 4 scanlines
printBall_loop:
ld   a, (de)               ; A = byte 1 definition ball
ld   (hl), a               ; Load ball definition on screen
inc  de                    ; DE = next byte definition ball
inc  l                     ; L = next column
ld   a, (de)               ; A = byte 2 definition ball
ld   (hl), a               ; Load ball definition on screen
dec  de                    ; DE = first byte definition ball
dec  l                     ; L = previous column
call NextScan              ; Next scanline
djnz printBall_loop        ; Until B = 0

; Paint the last blank line
ld   (hl), ZERO            ; Moves target to screen position
inc  l                     ; L = next column
ld   (hl), ZERO            ; Moves target to screen position

ret

Now we just need to see if everything we implemented works, for which we will edit main.asm:

ASM
org  $8000

ld   a, $02
out  ($fe), a

ld   a, $00
ld   (ballRotation), a

We specify the address to load the program, ORG $8000, set A = 2, LD A, $02, to set the border to red, OUT ($FE), A, and then set A = 0, LD A, $00, to initialise the ball rotation, LD (ballRotation), A.

We implement an infinite loop so that the ball moves indefinitely:

ASM
Loop:
call PrintBall

We are going to print the ball, CALL PrintBall, in the home position:

ASM
loop_cont:
ld   b, $08
loopRight:
exx
ld   a, (ballRotation)
inc  a
ld   (ballRotation), a
call PrintBall
exx
halt
djnz loopRight

Let’s move, rotate, the ball eight pixels to the right, LD B, $08, swapping the values with the alternate registers to preserve the value of B, EXX.

EXX swaps the value of the general purpose registers with the value of the alternate registers:

BC <-> ‘BC
DE <-> ‘DE
HL <-> ‘HL

We have chosen EXX in this case because it takes four clock cycles and occupies one byte, while PUSH BC takes eleven clock cycles and the value of the registers, except for B, is not critical for any operation to be performed in the loop, and we see this instruction.

We load the rotation of the ball into A, LD A, (ballRotation), increment the rotation, INC A, and load the resulting value into memory, LD (ballRotation), A.

We psint the ball, CALL PrintBall, exchange the values of the registers, EXX, and retrieve the value of B and pause to see how the ball moves, HALT.

Repeat until B is zero, DJNZ loopRight.

We set the rotation of the ball to zero, this time without painting it, to start rotating the pixels to the left (see the definition of the ball sprite):

ASM
ld   a, $00
ld   (ballRotation), a

We are going to move the ball eight pixels to the left. Only one instruction and one label change in relation to the shift to the right, so the routine will not be explained, we will mark the changes so you can see the difference:

ASM
ld   b, $08
loopLeft:                    ; Change!
exx
ld   a, (ballRotation)
dec  a                       ; Change!
ld   (ballRotation), a
call PrintBall
exx
halt
djnz loopLeft                ; Change!

To finish, we reset the rotation to zero. We load the value into memory and repeat the loop:

ASM
ld   a, $00
ld   (ballRotation), a

jr   loop_cont

Don’t forget to include the files sprite.asm and video.asm. Tell PASMO where to call them when loading the program:

ASM
include "sprite.asm"
include "video.asm"

end  $8000

The ball does not move, on the contrary, we always draw it in the same two columns, moving the pixels eight times to the right and then eight times to the left, and we repeat this over and over again.

The final appearance of the main.asm file is as follows:

ASM
; Move the ball from left to right between two columns.
org  $8000

ld   a, $02                ; A = 2
out  ($fe), a              ; Red border
ld   a, $00                ; A = 0
ld   (ballRotation), a     ; Rotation ball = 0

Loop:
call PrintBall             ; Print ball

loop_cont:
ld   b, $08                ; Move ball 8 pixels to the right
loopRight:
exx                        ; Exchanges records, preserves B
ld   a, (ballRotation)     ; A = ball rotation
inc  a                     ; Increases turnover
ld   (ballRotation), a     ; Store rotation value
call PrintBall             ; Print ball
exx                        ; Exchanges records, retrieves B
halt                       ; Synchronise with screen refresh
djnz loopRight             ; Until B = 0

ld   a, $00                ; A = 0
ld   (ballRotation), a     ; Rotation ball = 0

ld   b, $08                ; Move ball 8 pixels to the right
loopLeft:
exx                        ; Exchanges records, preserves B
ld   a, (ballRotation)     ; A = ball rotation
dec  a                     ; Decreases the rotation
ld   (ballRotation), a     ; Store rotation value
call PrintBall             ; Print ball
exx                        ; Exchanges records, retrieves B
halt                       ; Synchronise with screen refresh
djnz loopLeft              ; Until B = 0

ld   a, $00                ; A = 0
ld   (ballRotation), a     ; Rotation ball = 0

jr   loop_cont             ; Infinite loop

include "sprite.asm"
include "video.asm"

end  $8000

All that’s left is to compile and see the results in the emulator:

ZX Spectrum Assembly, Pong

In the next ZX Spectrum Assembly chapter, we will move the ball around the screen.

Download the source code from here.

ZX Spectrum Assembly, Pong by Juan Antonio Rubio García.
Translation by Felipe Monge Corbalán.
This work is licensed to Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0).
Any comments are always welcome.



Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Este sitio web utiliza cookies para que usted tenga la mejor experiencia de usuario. Si continúa navegando está dando su consentimiento para la aceptación de las mencionadas cookies y la aceptación de nuestra política de cookies, pinche el enlace para mayor información.plugin cookies

ACEPTAR
Aviso de cookies

Descubre más desde Espamática

Suscríbete ahora para seguir leyendo y obtener acceso al archivo completo.

Seguir leyendo