Alright, so I've taken up learning Assembly, now that I am more comfortable with middle-to-lower level languages like C and C++, and I've progressed to the stage that I can start to write crappy programs now.

Here's my first actual assembly program. It's pretty bad. You can move a pixel around with the arrow keys. Mateo, PT_, and Runer. helped me a bit with this one (Mateo helped me realize that the stack exists, and PT_ helped inspire the drawing code. PT_, in his 'P_T' form, and Runer showed me some a lot of useful optimization tips and made me realize how many simple optimizations could have been done.)


Code:
.nolist
#include "ti84pce.inc"
.list

  .org UserMem-2
  .db tExtTok, tAsm84CeCmp
 
  call _RunIndicOff
  call _boot_ClearVRAM
  ld de, lcdWidth/2; de holds x coord
  ld c, lcdHeight/2; y coord stored in c
mainLoop:
  ; constrain de and c so they are always on the screen.
  ld a, c
  cp 240d ; remember: it is 0-indexed, so we check if a < 240 (if a = 240 it is out of bounds)
  jr c, chk_de
  ld c, lcdHeight - 1
chk_de:
  ld a, e
  push de
    ex de, hl
    ld de, $ffffff - lcdWidth + 1 ; if de is >= lcdWidth it will overflow.
    adc hl, de
  pop de
  jr nc, cont
  ld de, lcdWidth - 1
cont:
  xor a, a ; draw
  call draw
keyWait: ; waits for a key, keycode returned in a
  call _GetCSC ; get key codes
  or a, a; check scan codes by comparing a with itself (I got this "hack" from various routines, it is super clever)
  jr z, keyWait ; if a is not zero, we are done
keyEnd:
  ld b, a
  ld a, $ff ; erase
  call draw
      ; Movement code.
      ; input in a (getCSC code)
      ; moves the pixel by updating
      ; de (x coord) and c (y coord)
      ; nothing overly arcane happens
      ; I used to check if a was >= 5 or equal to skDel
      ; but my code caused bugs so I killed it (and the bugs)
chk_down:
  djnz chk_left
  inc c
  jr mainLoop
chk_left:
  djnz chk_right
  dec de
  ; this is my crappy routine to check if de overflowed.
  ; it just works. Ew.
  ex de, hl ; hl is now de
  ld de, $ffffff ; de is saved in hl
  adc hl, de ; logic explained below
  ex de, hl ; de is now hl
  jr c, mainLoop
  inc de
  jr mainLoop
  ; I feel clever writing the above few lines of code,
  ; but please burst my ego if you have a better one.
chk_right:
  djnz chk_up
  inc de
  jr mainLoop
chk_up:
  djnz chk_quit
  ld a, c
  adc a, $ff; (n + $ff) % $100 + ((n + $ff)>$100) = n-1 (n!=0)
  ld c, a
  jr c, mainLoop
  inc c
  jr mainLoop
chk_quit:
  ld a, b
  cp skDel - 4
  jr nz, mainLoop
  call _RunIndicOn
  ret ; quit
draw: ; draws our singular pixel (I set high goals)
      ; (whew this is a bit of work, it gave me lots of appreciation for the sprite routines :P)
      ; we are using 16bpp mode here, so my routines
      ; (which are gently modified 8bpp routines) can
      ; probably be optimized
      ; takes input in a: 1 = draw the pixel, 0 = erase
  push bc
    ld l, c
    ld h, lcdWidth/2
    mlt hl ; this messes with c
    add hl, hl ; hl * 2
    add hl, hl ; hl * 4 (2 bytes/pixel)
    add hl, de
    add hl, de ; hl += de * 2
    ld bc, vRAM ; this also messes with c
    add hl, bc
    ld (hl), $ff ; byte 1, color info
    inc hl
    ld (hl), a ; byte 2, color info stored in a
  pop bc
  ret


As you can probably tell, it's as optimized as I can get it, which is to say not very optimized. It's optimized (I think? I'm obviously not a good judge of these things. Yet.), not obfuscated. I realize these things come with time. When will the Dunning-Kruger effect kick in? I like feeling mildly competent... I'm trying to keep my code optimized and readable so I can look back on it later.

My current assembly setup is SC3 and the Project Builder. This is a temporary setup, and I'm switching to better tools soon. Smile
A lot of fun progress has happened since that first post! I've decided on a very simple game where you (a square) walk around a screen collecting coins (also squares).




Features:

  • Slow!
  • Large! Ok, this one is a lie, it's only like 300 bytes
  • Unoriginal!
  • Nut-free!
  • Low-quality graphics!
  • Documentation not included!
  • 80% All-natural!
  • Not a scam!


This game is clearly the pinnacle of ez80 programming and should be worshipped as such.
Before some person tells me that my program is excellent or that I'm still learning or something, I know. This post is a bit of a joke, I'm still learning and I'm actually super proud of what I have done so far.

Edit:
I thought I'd post my source code in case anyone had any tips. PT_ suggested that I should be reading directly from the port instead of using GetCSC (and I can see why, looking at CEmu's disassembler, it is an absolutely massive routine, larger than my entire program!), and I tried to, but I think I'm going to do that one particular optimization later.

Code:
.nolist
#include "ti84pce.inc"

square_size     .equ 8d ; I'm making this an equate for readability, changing it without modifying other parts of the program will cause you problems.
coin_size       .equ 6d ; don't change this either

score_location  .equ pixelShadow

coin_y_location .equ pixelShadow + 2
coin_x_location .equ pixelShadow + 1 ; yummy little-endian format
coin_location   .equ pixelShadow + 1 ; for 24 byte registers.

rng_seed             .equ $71dc ; this can be anything non-zero
rng_seed_location    .equ pixelShadow + 4 ; +3 is upper byte of 24 bit register after ld <reg24>, coin_location
.list
 
  .org UserMem - 2
  .db tExtTok, tAsm84CeCmp
 
  call _RunIndicOff
  call _boot_ClearVRAM
 
; <init palette>, code came from C toolchain.
  ld   de,mpLcdPalette ; address of mmio palette
  ld   b,e ; b = 0
_paletteLoop:
  ld   a,b
  rrca
  xor   a,b
  and   a,224
  xor   a,b
  ld   (de),a
  inc   de
  ld   a,b
  rla
  rla
  rla
  ld   a,b
  rra
  ld   (de),a
  inc   de
  inc   b
  jr   nz,_paletteLoop ; loop for 256 times to fill palette
; </init palette>
 
  ld a, lcdBpp8
  ld (mpLcdCtrl), a
 
; seed rng
  ld hl, rng_seed
  ld (rng_seed_location), hl
 
  ld de, 0 ; clear upper byte
 
  ld a, 0
  ld (score_location), a
  ld (coin_location + 2), a
 
  ld d, (lcdHeight/square_size)/2 ; d holds y coord
  ld e, (lcdWidth/square_size)/2 ; e holds x coord
 
  jr _move_coin ; just for simplicity.
 
main_loop:
  ld hl, (coin_location)
  cp a, a ; thank you, jcgter <3
  sbc hl, de
  jr nz, _
  ld hl, score_location
  inc (hl)
_move_coin:
  call rng
  cp lcdWidth/square_size + 1
  jr c, __
  sub lcdWidth/square_size
__:
  ld (coin_y_location), a
  call rng
  ld a, r
  and %00000111 ; bitmask, limit to 0-7.
  add a, c
  ld (coin_x_location), a
  call draw_coin
_:
  xor a, a ; draw
  call draw
kbd_read:
  call _GetCSC
  or a
  jr z, kbd_read
  ld b, a
  cp skDel
  jr nz, kbd_end
  ld a, lcdBpp16 ; quit
  ld (mpLcdCtrl), a
  call _RunIndicOn
  ret ; quit
kbd_end:
  ld a, $ff ; erase
  call draw
; Movement code.
; input in b (getCSC code)
; moves the pixel by updating
; de (x coord) and c (y coord)
; nothing overly arcane happens
; I used to check if a was >= 5 or equal to skDel
; but my code caused bugs so I killed it (and the bugs)
chk_down:
  djnz chk_left
  inc d
  ld a, d
  cp lcdHeight/square_size
  jr c, main_loop
  ld d, lcdHeight/square_size - 1
  jr main_loop
chk_left:
  djnz chk_right
  dec e
  ld a, e
  inc a ; check for overflow (this is a pretty sweet optimization)
  jr nz, main_loop
  ld e, 0
  jr main_loop
chk_right:
  djnz chk_up
  inc e
  ld a, e
  cp lcdWidth/square_size
  jr c, main_loop
  ld e, lcdWidth/square_size - 1
  jr main_loop
chk_up:
  djnz main_loop
  dec d
  ld a, d
  inc a ; check for overflow
  jr nz, main_loop
  ld d, 0
  jr main_loop
draw:
; (whew this is a bit of work, it gave me lots of appreciation for the sprite routines :P)
; takes input in a: 00 = draw the pixel, ff = erase
  ld c, b
  ld (colorSet+1), a ; writes the color byte into the program.
  push de
    push de ; thanks, dTal, PT_, Runer and zeda for making me realize that I should never do assembly programming while extremely tired.
      ld e, lcdWidth/2
      mlt de
      ex de, hl
      add hl, hl ; this gets rid of the /2 part above
      add hl, hl ; the below stuff multiplies hl by square_size, which is 8.
      add hl, hl
      add hl, hl
      ld de, vRAM ; while we have corrupted de, let's use it to make hl point into vRAM.
      add hl, de
    pop de
    ld d, square_size
    mlt de
    add hl, de
    ld de, lcdWidth - square_size
    ld a, square_size
_loopOuter:
    ld b, square_size
_loopInner:
colorSet:
    ld (hl), $00 ; The color byte gets written in here.
    inc hl
    djnz _loopInner
   
    add hl, de
    dec a
    jr nz, _loopOuter
  pop de
  ld b, c
  ret
draw_coin:
; draws the coin! (some of this is repeated code, but I'm really concerned about speed.)
; this routine is like a tank, it destroys everything.
  push de
    ld de, $0
    ld hl, coin_y_location
    ld e, lcdWidth/2
    ld d, (hl)
    mlt de
    ex de, hl
    add hl, hl ; this gets rid of the /2 part above
    add hl, hl ; the below stuff multiplies hl by square_size, which is 8.
    add hl, hl
    add hl, hl
    ld de, vRAM+lcdWidth ; the '+lcdWidth' is to center it on the grid. The coin is 6x6 on an 8x8 grid, so we need 1 pixel of padding all around.
    add hl, de
    ld b, square_size
    ld a, (coin_x_location)
    ld c, a
    mlt bc
    add hl, bc
    inc hl
    ld de, lcdWidth - coin_size
    ld a, coin_size
_loopOuter2:
    ld b, coin_size
_loopInner2:
    ld (hl), $a4 ; gold
    inc hl
    djnz _loopInner2
    add hl, de
    dec a
    jr nz, _loopOuter2
  pop de
  ret
rng:
; generates a random number, 1-32, and stores the result in a
; destroys hl, bc
; to seed the rng (16-bit seed in hl):
; ld (rng_seed_location), hl
  ld b, 5 ; max output of rng = 2^b (0 <= b <= 7)
  ld hl, (rng_seed_location) ; seed rng with previous output
  ld c, $00 ; we will be using c to hold the output, because we need a for other things.
_rng:
; A highly modified Galois LFSR. Nicely, the current
; example source code on wikipedia pretty much directly ports over.
  srl h ; shifts h, fills gap with 0. sets carry flag to what "fell off".
  rr l ; shifts l, fills gap with what fell off h.
  rl c ; output here, this is what is different from the wikipedia code. Carry flag (what fell off l) is pushed into bit 0 of c. I love this.
  bit 0, c
  jr nz, _
  ld a, h
  xor $B4 ; magic # from wiki article.
  ld h, a
_:
  djnz _rng
  ld (rng_seed_location), hl
  ld a, c
  ret

If you want to play the "game" yourself, you can download it from here.
You use the arrow keys to move around, and [del] to quit.

After quitting, the program will leave a few artifacts on the screen. I'm not sure how to fix that yet, because I haven't spent the time looking into fixing it. If they bother you too much, just press [2nd][quit] after running the program and the TI-OS will remove them by redrawing the screen.

I'm still using SC3 to compile my code, but I'm going to be switching really soon.
  
Register to Join the Conversation
Have your own thoughts to add to this or any other topic? Want to ask a question, offer a suggestion, share your own programs and projects, upload a file to the file archives, get help with calculator and computer programming, or simply chat with like-minded coders and tech and calculator enthusiasts via the site-wide AJAX SAX widget? Registration for a free Cemetech account only takes a minute.

» Go to Registration page
Page 1 of 1
» All times are UTC - 5 Hours
 
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum

 

Advertisement