Hacker's Edge Game Guide: Programming/Example program: showcase.s

Example program: showcase.s
Jan. 26, 2019

This example program works both as a unittest during development of the game to ensure the in-game devices don't break, and is also a great example program to see just what you can do within the Hacker's Edge engine when creating your own code. This is an evolved version of keyboard.s, which is also provided in the help center, but runs without an OS. This example requires HackerOS to run, and is included in every HackerOS installation as showcase.bin.

.import __IOBASE__ ; This is very important, as our memory mapped devices start from this base address.
.export _main ; This is required for HackerOS binary programs to run correctly.

.include "kernel.inc" ; This includes many useful resources from the kernel.

KBDFLAGS = __IOBASE__+$dc ; Keyboard flags register.
COUT = __IOBASE__+$d0 ; Console out
CIN = __IOBASE__+$e0 ; Console in
MIRQ = __IOBASE__+$dd ; Mouse IRQ vector
MBUT = __IOBASE__+$d9 ; Which mouse button was last pressed.
MCOL = __IOBASE__+$da ; The column in the terminal which the mouse was last pressed.
MROW = __IOBASE__+$db ; The row in the terminal which the mouse was last pressed.
COL = __IOBASE__+$d7 ; The column to set or get the cursor position.
ROW = __IOBASE__+$d6 ; The row to set or get the cursor position.

.rodata ; Set our segment to RODATA

welcome:
    .byte "Keyboard test program!", $a,$0 ; A nice friendly welcome message with a linefeed.

mask_input:
    .byte "Masked input: ", $0 ; A prompt for the user.

getc_input:
    .byte "Char input test!",$a
    .byte "Do you like this [Y/N]?",$0 ; Some text for the user to test out getc.

mouse_test:
    .byte "Mouse test!",$a
    .byte "Click anywhere to see where the mouse is!",$a
    .byte "Press ESC to stop.",$a,$0 ; A message to let the user know what to do and how to exit.

getc_yes:
    .byte $a,"Great to hear!",$a,$0 ; A response from getc

getc_no:
    .byte $a,"Please provide feedback on the forums.",$a,$0 ; A response from getc

.zeropage ; Set our segment to ZEROPAGE

ptr: .res 2, $00 ; Create a basic pointer we can use later.

.code ; Switch over to the CODE segment.

.proc _mask_test: near ; Our first assembly procedure!
  lda KBDFLAGS ; Load our current Keyboard flags
  ora #$10 ; OR the current flags with $10 to set mask on.
  sta KBDFLAGS ; Set our flag back to enable it.
  lda #mask_input
  jsr _print ; Call the Kernel function to print a null-terminated string.
: lda CIN ; Grab some non-essential input from the user.
  cmp #$a ; Did the user hit Return?
  beq :+ ; Yes? Then we're done.
  bne :- ; No? Keep grabbing non-essential input from the user.
: jmp _return ; We're done, but we need to clean up the keyboard flags.
.endproc ; Procedure and scope done.

.proc _getc_test: near
  lda KBDFLAGS ; Load our current Keyboard flags
  ora #$20 ; OR the current flags with $20 to set RAW mode.
  sta KBDFLAGS ; Set our flag back to enable it.
  lda #getc_input
  jsr _print ; Call the Kernel function to print a null-terminated string.
: lda CIN; Grab a single character from the user.
  cmp #'Y' ; Did the user press 'Y'?
  beq :+ ; Yes? Then lets handle that.
  cmp #'N' ; Did the user press 'N'?
  beq :+++ ; Yes? Then lets handle that one too.
  bne :- ; Otherwise, go back up and wait for a proper letter.
: lda #getc_yes
: jsr _print ; Call the Kernel function to print a null-terminated string.
  jmp _return ; We're done, but we need to clean up the keyboard flags.
: lda #getc_no
  bne :-- ; Jump up to where the Kernel print is called.
.endproc

.proc _mouse_test: near; Now for the most interesting example...
  lda #mouse_irq
  sta MIRQ+1
  lda KBDFLAGS ; Load our current Keyboard flags
  ora #($20 | $40); OR the current flags with $20 and $40.  $40 enables the mouse.
  sta KBDFLAGS ; Set our flag back to enable it.
  lda #mouse_test
  jsr _print ; Call the Kernel function to print a null-terminated string.
: lda CIN; Grab a character from the user, as we're in RAW mode.
  cmp #$1b; Did the user press ESC?
  beq :+; Yes? Then lets exit this routine.
  bne :- ; No? Keep waiting in a loop.
: jmp _return ; We're done, but we need to clean up the keyboard flags.
.endproc

.proc mouse_irq: near ; The Mouse IRQ routine
  sei ; Set the interrupt flag in the CPU to prevent double calls.
  lda MROW ; Get the ROW the mouse was clicked on.
  sta ROW ; Set the cursor position to that ROW.
  lda MCOL ; Get the column the mouse was clicked on.
  sta COL ; Set the cursor position to that column.
  lda #'X' ; Load an 'X' into the A register.
  sta COUT ; Use the console out to display our A register.
  cli ; Clear the interrupt flag on the CPU to resume interrupt handling.
  rti ; Return from interrupt.
.endproc

.proc _welcome: near ; Here's a welcome routine for the user.
  lda #'H' ; Load 'H' into the A register to home the cursor.
  sta $ffd3 ; Set the A register into the terminal ANSI handler.
  lda #'J' ; Load 'J' into the A register to clear every line below the cursor.
  sta $ffd3 ; Set the A register into the terminal ANSI handler.
  lda #welcome
  jsr _print ; Call the Kernel function to print a null-terminated string.
  rts ; Since we did not do anything with the keyboard flags, just do a normal RTS.
.endproc

.proc _return: near ; A helper function to clear keyboard flags and return.
  lda #0 ; Set $0 into the A register.
  sta KBDFLAGS ; Set the keyboard register from A
  rts ; Return.
.endproc

_main: jsr _welcome ; This routine just calls all the other ones provided above.
       jsr _mask_test
       jsr _getc_test
       jsr _mouse_test
       brk ; BRK returns back to the Shell program in HackerOS.