84psce ball from Kerm Martian

I'm not an asm guru, I will try to understand the code and explain it.
So I could say some stupid things, depending my skills and sometimes the quality of the code which is reviewed (mostly its level of obfuscation :D)!

I will review a nice program today : the pcseball from Kerm Martian !
I will try to understand what he wrote and how he made this program.

Visit Cemetech for more informations !

Let's start with the classic header :

; Program Name: PCSE Ball ; Author: Christopher Mitchell ; Version: .1 ; Date: February 2013 .nolist ;#include "ti83plus.inc" ;#include "dcs7.inc" ;------------------------------- #define equ .equ #define EQU .equ #define end .end #define END .end userMemC .equ $A60B lfsrseed1 .equ $8000 balls .equ $8001 nballs .equ 10 _cphlde .equ 400Ch ;Banked Call Equates/Macros ;----------------------------------------------- rBR_CALL equ 28h BRT_JUMP0 equ 50h #define bcall(xxxx) rst 28h \ .dw xxxx #define b_call(xxxx) rst 28h \ .dw xxxx #define bjump(xxxx) call 50h \ .dw xxxx .list

Ok no suprise here, we have the equates, a place to store the balls (8001), the number of balls (10) a place for the seed (8000) and some equates for the bcall.

If you already made some assembly, there's no problem with that :D

The final .list is telling us that the real job start here ahah !

.org userMemC-2 .db $EF,$69 Init: ld a,r ld (lfsrseed1),a ld a,1 out ($20),a ;set 15MHz ld b,nballs ld hl,balls ld de,8 ;starting x ;ld c,5 ;starting y

Ok now we have the .org statement which as you can see is not the usual 9D93h.

Then we have the AsmCmp (usually it's $BB,$6D) : .db $EF,$69
Then we load te random register in a and put it into 8000 as a seed.

After that, Kerm send 1 to the port 20, load 10 into b, and the balls adress into hl.
Then he loads 8 in de as x starting point.

Let's see what Kerm is preparing :

InitSetupLoop: ld (hl),e ;x inc hl ld (hl),d ;x inc hl push hl call LFSR_PRNG pop hl and $02 dec a ld (hl),a ;x velocity inc hl push hl call LFSR_PRNG pop hl cp 240-16 jr c,InitSetupLoop_XOK and $7f

Ok next step is putting x into 8001 and y into 8002.

At this point, x contains 8 and y contains 0 (d e = 00000000 10000000).
Then hl is equal to 8003 and kerm saves this value and call LFSR_PRNG.
This seems to be soemthing like this.

To have a smooth move, kermM simply keep the two lower bits (and $02).
Then he store the xvelocity into 8003 and start looking for the new xvalue.
After that, Kerm compares the value to 240 - 16 (16 is the width of the ball).
If it's Ok, jump to InitSetupLoop_XOK else keep only a part of a regarding $7f (011111111).
Let's study the mysterious LFSR_PRNG :

LFSR_PRNG: ld hl,lfsrseed1 ld a,(hl) rrca ld (hl),a jp nc,+_ xor %00111000 ld (hl),a _: set 6,a ret

This routines is a bit mysterious... Kerm put the seed into a, then rotate the register a using the carry...

Then save a into the seed. If the lower bit was 0, then set the bit 6 to 1 into a.
Keep only the bit 3, 4, 5 and put it into the seed.

Restart now where we were previously:
InitSetupLoop_XOK: ld (hl),a ;y inc hl push hl call LFSR_PRNG pop hl and $02 dec a ld (hl),a ;y velocity inc hl push hl ld hl,20 add hl,de pop de ex de,hl ld a,10 add a,c ld c,a djnz InitSetupLoop

To conclude about it, we could see that this will be done 10 times (djnz InitSetupLoop) so at the end there are 10 times :
x, xvel, y, yvel (4 bytes) starting at the adress 8001. So we're OK to start moving the balls.

Before starting this, Kerm init the screen and clear the screen/

InitClearScreen: ld hl,$10B8 ; BGR mode, increment order ld a,$03 call SetLCDRegister call FullScreenWindow ld e,$0f

At this point, Kerm inits the LCD (some magic stuff you will know when 84plys cse will be released).
Take a look at the routines defined in the end of the file :

FullScreenWindow: ld hl,$0000 ld a,$50 call SetLCDRegister ;Horizontal Start ld hl,239 inc a ;ld a,$51 call SetLCDRegister ;Horizontal End ld hl,$0000 inc a ;ld a,$52 call SetLCDRegister ;Vertical Start ld hl,319 inc a ;ld a,$53 call SetLCDRegister ;Vertical End ld hl,0 ld a,$20 call SetLCDRegister ;Y pos inc a ;ld a,$21 ;jp SetLCDRegister ;X pos SetLCDRegister: out ($10),a \ out ($10),a ld c,$11 out (c),h out (c),l ret

These are some specific stuff for t84plus color here.

As you probably know, you can update only a part of the lcd (which is pretty big for this calc).
That's what this piece of code does : setting which part of the lcd will be updated.

As you can see, Kerm refresh from x = 0000 to x = 239 (width = 240 pixels).
Adn from y = 0000 to y = 319 (height = 320 pixels). So in fact that's a full refresh !
SetLCDRegister simply sends the values to the ports.

FullScreenColor: ;takes color in e ld hl,0 ld a,$20 call SetLCDRegister ;Y pos ld a,$21 call SetLCDRegister ;X pos ld a,$22 out ($10),a \ out ($10),a ld c,(320*240/2)/256 ClearLoop: ld b,0 InnerClearLoop: ld a,e out ($11),a \ out ($11),a out ($11),a \ out ($11),a djnz InnerClearLoop dec c jr nz,InnerClearLoop ret

This code seems to put the same color on the entire background (the color in e).

The c contains the number of bytes to write.
OuterColorLoop: call FullScreenColor ;takes color in e ld a,$10 add a,e ld e,a cp $0f jr nz,OuterColorLoop

And this is the loop wher it's called.
BallTime: ld b,nballs ld hl,balls BallTimeLoop: push hl ld e,(hl) inc hl ld d,(hl) inc hl push hl ex de,hl ld a,$52 ;"Vertical" = X for us call SetLCDRegister ld a,$21 ;"Vertical" = X for us call SetLCDRegister push hl ld de,15 add hl,de ld a,$53 ;"Vertical" = X for us call SetLCDRegister pop hl pop de ld a,(de) ld e,a ld d,0 cp $ff jr nz,BallTimeLoop_CheckReverse ld d,$ff

Ok let's start moving.

In BallTime, the number of balls goes in b (for looping) and the balls (coords + velocity) goes in hl).

In BallTimeLoop, Kerm takes each ball and clear the place of the ball with the SetLCDRegister called with hl = x...
Then hl = x + 15 (for the width which is 16 pixels).
Then Kerm checks if the ball is bouncing (and eventually jump to BallTimeLoop_CheckReverse)
BallTimeLoop_CheckReverse: add hl,de pop de ex de,hl ld (hl),e inc hl ld (hl),d inc hl push hl ld hl,320-16 or a ;cphlde sbc hl,de add hl,de jr z,BallTimeLoop_DoXFlip ld a,e or d jr nz,BallTimeLoop_NoXFlip

In this part, the x position is checked to see if the ball needs to bounce !

The new x coord is generated here by adding hl to de at the second line.
The comparaison is done against 320 - 16 which is the biggest x pos (due to the x value of the sprite which is the left top corner).
If the x coords are inferior to 320-16 and superior to 0 that's Ok, jump to BallTimeLoop_NoXFlip.

BallTimeLoop_DoXFlip: pop hl ld c,(hl) xor a sub c ld (hl),a push hl

Here, xor a is to put 0 inside a, then the sub c simply reverse the value of the xvel (as hl contains the adress of xvel).
Then the value is saved (ld (hl), a).

BallTimeLoop_NoXFlip: pop hl inc hl push hl ld e,(hl) inc hl push hl ex de,hl ld h,0 ; ld a,$50 ;"Horizontal" = Y for us ; call SetLCDRegister ld a,$20 ;"Horizontal" = Y for us call SetLCDRegister ; push hl ; ld de,15 ; add hl,de ; ld a,$51 ;"Horizontal" = Y for us ; call SetLCDRegister ; pop hl pop de ld a,(de) add a,l pop de ex de,hl ld (hl),a inc hl push hl cp 240-16 jr z,BallTimeLoop_DoYFlip or a jr nz,BallTimeLoop_NoYFlip BallTimeLoop_DoYFlip: pop hl ld c,(hl) xor a sub c ld (hl),a push hl

Ok in this piece of code, this is exactly the same task but applied to y coords.

BallTimeLoop_NoYFlip: pop hl inc hl push hl ld a,$22 ld c,$10 \ out (c),0 \ out (c),a push bc ld bc,$0011 ; 16*16 ld hl,RedBall BallRender1: outi jp nz,BallRender1 BallRender2: outi jp nz,BallRender2 pop bc pop hl dec b ld a,b jp nz,BallTimeLoop

In this part, the sprites are written to the LCD.
The RedBall contains the picture of the ball.
OUTI : Reads from (HL) and writes to the (C) port. HL is then incremented, and B is decremented.

So in fact it takes a byte from the picture, write it to the LCD, then takes next byte, write it, while B is superior to 0.

ld a,$ff out (1),a nop \ nop ld a,$fd out (1),a nop \ nop in a,(1) inc a jp z,BallTime call FullScreenWindow ld e,$ff call FullScreenColor ld hl,$1038 ; BGR mode, increment order ld a,$03 call SetLCDRegister xor a out ($20),a ;set 6MHz bcall(54F4h) ;redraw the status area bcall(454Fh) ;homeup ld hl,credit_text bcall(4501h) ;puts bcall(4525h) ;newline ret

Here the things done are testing the keyboard using direct input.
If clear is pressed, then clear the screen, restore to 6Mhz the speed of the proc, redraw some stuff, and prints "Kerm Martian".

credit_text: .db "By Kerm Martian",0 RedBall: .db $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff .db $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff .db $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$00,$00,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff .db $ff,$ff,$ff,$ff,$ff,$ff,$00,$00,$e8,$e4,$ff,$ff,$ff,$ff,$ff,$ff,$e8,$e4,$e8,$e4,$e8,$e4,$00,$00,$00,$00,$ff,$ff,$ff,$ff,$ff,$ff .db $ff,$ff,$ff,$ff,$00,$00,$e8,$e4,$ff,$ff,$ff,$ff,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$00,$00,$ff,$ff,$ff,$ff .db $ff,$ff,$00,$00,$e8,$e4,$e8,$e4,$ff,$ff,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$00,$00,$ff,$ff,$ff,$ff .db $ff,$ff,$00,$00,$e8,$e4,$e8,$e4,$ff,$ff,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$88,$02,$00,$00,$ff,$ff .db $ff,$ff,$00,$00,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$88,$02,$00,$00,$ff,$ff .db $ff,$ff,$00,$00,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$88,$02,$88,$02,$00,$00,$ff,$ff .db $ff,$ff,$00,$00,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$88,$02,$88,$02,$88,$02,$00,$00,$ff,$ff .db $ff,$ff,$00,$00,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$e8,$e4,$88,$02,$88,$02,$88,$02,$00,$00,$ff,$ff,$ff,$ff .db $ff,$ff,$ff,$ff,$ff,$ff,$00,$00,$e8,$e4,$e8,$e4,$e8,$e4,$88,$02,$88,$02,$88,$02,$88,$02,$88,$02,$00,$00,$ff,$ff,$ff,$ff,$ff,$ff .db $ff,$ff,$ff,$ff,$ff,$ff,$00,$00,$88,$02,$88,$02,$88,$02,$88,$02,$88,$02,$88,$02,$88,$02,$00,$00,$00,$00,$ff,$ff,$ff,$ff,$ff,$ff .db $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$00,$00,$00,$00,$88,$02,$88,$02,$88,$02,$88,$02,$00,$00,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff .db $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$00,$00,$00,$00,$00,$00,$00,$00,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff .db $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff
This final part is only data (the picture of the ball).
The colors are 16 bits.

Thanks to Kerm Martian for this great demo. I'm not sure to really completely understand this code, I hope I said not too much stupidities :D

Visit Cemetech for more informations !