User Tools

Site Tools


sd:getting_stuff_done_in_assembly_language

This is an old revision of the document!


Getting Stuff Done in Assembly Language

Introduction

When you start out progrmaming for the SD-8516 it's basically going to be in Assembly if you want maximum power because any language running on the system is going to pare down to assembly eventually. So unless we get a good version of C up, it's assembly. Try this first for now.

But how do you get stuff done? How do you do real stuff? Well you're in luck son, because this is the guide for you!

The Golden Rule of Assembly

If you don't have a library for something, write it yourself first. Writing low level stuff where you put characters into the memory mapped screen area is possible, but if you did that you would just end up writing your own library to do it anyways. Use the provided libraries or make them better. Look for a library function that can help you first.

Always remember: The libraries are your friend.

Using an external editor

If you're in the C version, you can have an editor open in .vc4/files and that makes it easier to edit files. For the WASM version you will need to INCOPY your file into the system if you want to use an external editor.

Dealing with Text

; Example 1: Print using INT 5h
.address $C000
    LDBLX @msg
    LDAH $66    ; BASIC IO_PUTSTRING
    INT 0x05    ; INT 5 is the BASIC services library.
    RET         ; return control back to system.
msg:
    .bytes "Hello Potato Chips!", 13, 10, 0

Let's follow along at home:

  MKDIR A
  CD A
  ED a.asm
  a            <--- this will enter append mode
  <paste the above program>
  .
  w
  q
  AS a.asm a
  DLOAD a
  DUMP C000     <---- you can see it's been loaded
  SYS 49152

The above sequence of commands will allow you to enter a program. Alternately you can use INCOPY for the WASM version or just copy the files into the directory .vc4/files on the C version.

Run the program by typing SYS 49152. What do you see?

Obviously it will say hello potato chips.

INT 10h Print String

The same as above works with INT 10h.

  ; ============================================================================
  ; AH=18h - Print String at Cursor
  ; Input:  ELM = pointer to null-terminated string
  ; Output: Cursor advances
  ; Notes:  Works with current video mode
  ; ============================================================================

; Example 2: Print using INT 10h

.address $C000
    LDELM @msg
    LDAH $18    ; INT 10h Write String
    INT 0x10    ; INT 10h is the Terminal Services library.
    RET         ; return control back to system.
msg:
    .bytes "Hello from INT $10!", 13, 10, 0

Input

You might want to ask the user for input. For string input, we repurpose BASIC's INPUT command. IO_INPUT can be used as follows:

  ; ----------------------------------------------------------------------------
  ; AH=$68: IO_INPUT - Read line of input from user
  ; Input:  None
  ; Output: ELM = pointer to input string (null-terminated, in @PATB_TBUF)
  ;         B = numeric value (parsed via atoi)
  ;         CF = 0 on success, 1 on empty input
  ; Notes:  Reads characters until ENTER (13).
  ;         Supports backspace for editing.
  ;         Echoes characters to screen.
  ;         ENTER is not echoed.
  ;         Max input length limited by PATB_TBUF size.
  ;         If ESC is detected it sets the B flag.
  ; ----------------------------------------------------------------------------

; Example 3: IO_INPUT

.address $C000
    LDBLX @prompt
    LDAH $66    ; IO_PRINT
    INT 0x05
    
    LDAH $68    ; IO_INPUT
    INT 0x05
    ; ELM now contains the user-entered string.

    LDBLX @hellomr
    LDAH $66    ; IO_PRINT
    INT 0x05
    
    MOV BLX, ELM ; get the name in BLX
    INT 0x05    ; print the name

    LDAL #'!'   ; load a '!' character.
    LDAH $61    ; IO_PUTCHAR
    INT 0x05    
    
    LDAH $64    ; IO_NEWLINE
    INT 0x05
    
    RET         ; return control back to system.

prompt:
    .bytes "What is your name? ", 0

hellomr:
    .bytes "Hello Mr. ", 0

Random Numbers

Random numbers are really useful. Here, we demonstrate the xorshift generator that comes standard in the INT $13 math services library.

.address $C000

start:
    LDBLX @msg
    LDAH $66        ; IO_PRINT
    INT 0x05
    
loop:
    LDAH $00    ; 16 bit random number in B
    INT $13
    MOD B, #10 ; 0-9
    INC B      ; 1-10

    LDAH $63    ; IO_PUTNUM (prints number in B)
    INT 0x05

    LDAH $64    ; IO_NEWLINE
    INT 0x05
    
    LDAH $02    ; blocking getkey
    INT 0x10
    CMP AL, #'q'
    JZ @exit
    CMP AL, #'Q'
    JZ @exit
    
    JMP @loop
    
exit:
    RET
     
msg:
    .bytes "Random number program.", 13, 10
    .bytes Press any key or Q to quit.", 13, 10, 0

Now you see how to get a random number and how to do blocking getkey! Non-blocking getkey is AH=00.

About INT $13

As an aside to the above, here is the function header from INT $13, AH=$00.

  ; ============================================================================
  ; AH=00h - Random Number
  ; Input:  None
  ; Output: A, B = random 16-bit numbers (1-65535)
  ; Notes:  Uses xorshift
  ; ============================================================================

Getkey

  ; ============================================================================
  ; AH=02h - Read Character (Blocking)
  ; Input:  None
  ; Output: AL  = ASCII character
  ;         B  = number of keys that were in buffer before this call
  ;         K  = keyboard flags at time of press
  ; Note:   X, Y preserved for cursor position
  ;         This function BLOCKS until a key is pressed
  ; ============================================================================

Also there is a non-blocking version:

  ; ============================================================================
  ; AH=00h - Read Character (Non-blocking)
  ; Input:  None
  ; Output: AL = ASCII character (0 if no key)
  ;         B  = number of keys that //were// in buffer before this call (0 = no key)
  ;         K  = keyboard flags at time of press (1 = shift, 8 = caps, etc)
  ;         ZF = 1 if no key (B value is zero), ZF = 0 has key (non-zero B value)
  ; *** NOTE: calls int10_apply_shift
  ; ============================================================================

Putting It All Together: Menus

The engine for all these games we love such as Bard's Tale, Ulima, Nethack, King's quest, Alice in wonderland, Maniac Mansion, all were based on text engines. If the engine is good, a modicum of graphics will suffice. The big deal with retro games is that it's all about the gameplay, and the graphics serve the gameplay. What people miss in today's games is that the graphics attempt to look so good that they can replace gameplay. But it goes without saying, that even so, King's Quest was a “better game” than Tomb Raider, even though Tomb Raider is such an amazing game in and of itself. It fell into that trap, replacing gameplay with graphics. I mean, Tomb Raider pulled it off because it was so awesome, but King's Quest, OMG, hard to say which one is better really.

The first thing you need to do is write some menus. Fundamentally printing the menus is easy, but you may wish to color the title of the menus, so you will need to learn how to do colored text. Also, you want to do input validation, so let's work on that too.

First, colored text. it's exactly the same as writing normal text, except you write a color byte. Here, i've added a special function to change the default color text is written in TTY mode. You can also set the color of text at an X and Y location, but that can become tedious because you have to do it character by character.

.address $C000

start:
    ; Clear screen.
    LDAH $10
    INT $10

    ; Set cursor position to X=0, Y=10.
    LDX #0
    LDY #10
    LDAH $15    ; set cursor position
    INT $10

    LDAH $50
    LDAL $2F    ; color data in AL: BG=2, FG=F (here, orange)
    INT $10

    LDBLX @str_menu_title
    LDAH $66    ; BASIC IO_PUTSTRING
    INT 0x05

    LDAH $50
    LDAL $29    ; color data default for mode 1
    INT $10

    LDBLX @str_menu
    LDAH $66    ; BASIC IO_PUTSTRING
    INT 0x05

    LDAH $02    ; blocking getkey
    INT 0x10

    RET


str_menu_title:
   .bytes "  Choose your class:", 13, 10, 13, 10, 0

str_menu:
   .bytes "    1) Human", 13, 10
   .bytes "    2) Elf", 13, 10
   .bytes "    3) Half-elf", 13, 10
   .bytes "    4) Dwarf", 13, 10
   .bytes "    5) Orc", 13, 10, 13, 10
   .bytes "  Your choice [1-5, q]? ", 0 

Major Concept: The Cursor

Before we proceed, it's nice to be able to completely control the terminal. That means, instead of relying solely on PRINT and INPUT we have character level control.

Let's have a quick introduction to four important INT $10 (terminal services) routines.

  • AH = 10h – Clear Screen
  • AH = 16h – Get Cursor Position (X, Y)
  • AH = 15h – Set Cursor Position (X, Y)
  • AH = 17h – Print Character at Cursor (Teletype, AL=ascii code)
    • Note that with print character at cursor, it's a teletype mode, meaning the cursor will auto-advance for you.

Now let's make a simple demo program that clears the screen and writes ten random letters to the screen. This demo means we can now draw (using charaters) any text we want to the screen in any place – meaning, we can draw a typical 'game board' or 'game screen' in text. This is the first step before graphics programming, since we can edit the in-memory character set.

.address $C000

start:
    ; Set up variables, clear screen, etc.
    LDAH $10
    INT $10

    LDT #10     ; ten times for the loop    
loop:
    ; 1. Choose random X and Y location on screen
    LDAH $00    ; 16 bit random number in B (and in A)
    INT $13
    MOV X, A
    MOV Y, B

    MOD X, #40 ; clamp to 0-39
    MOD Y, #24 ; clamp to 0-23

    ; 2. Get random letter in AL
    LDAH $00
    INT $13
    MOD A, #26 ; 0-25
    ADD A, #65 ; AL is now a random letter.

    ; 3. Set cursor position to X and Y
    ; X and Y already set
    LDAH $15
    INT $10

    ; 4. Print character.
    LDAH $17    ; write char (teletype)
    ; AL already has char
    INT $10

    DEC T
    JNZ @loop

done:
    ; 5. Set cursor position to X=0, Y=20.
    LDX #0
    LDY #20
    LDAH $15    ; set cursor position
    INT $10

    RET     ; return control to system.

Keeping Track Yourself

In a game, you don't really need the cursor. So instead of using a teletyle print function and calling INT $10, AH=$16, you can just give the values you want for your game screen. Here's an example of putting a character anywhere you want and using any color. Although it looks a bit trivial this ends up proving to be a very useful pattern.

.address $C000

start:
    LDX #0
    LDY #10
    LDC $03
    LDB #'A'

    CALL @put_ccxy
    RET

; put_ccxy(B, X, Y, C)
; Put Character and Color at X, T
; INPUT:
;     B -- ascii code
;     X -- x location on screen
;     Y -- y location on screen
;     C -- hi nibble (BG) and low niblle (FG)
;
put_ccxy:
    ; Write character in AL at X, Y
    LDAH $11
    INT $10

    ; Set color at X, Y
    ; Input:  CL = color data already sey
    LDAH $13
    INT $10

    RET

DEFCHAR Graphics

Mode 1 Graphics is the first step in our graphics adventure. Mode 1 Graphics has two limitations. One, since you are redefining the character set to do graphics, everything must be drawn using 8×8 monocolor stamps (like characters). Two, you can only have 256 of them.

First, use int10h_font_set_char and int10h_glyph_pack to define the character:

  ; ----------------------------------------------------------------------------
  ; AH=49h - glyph_pack -- pack an 8x8 grid and install it as a font glyph
  ;   Input:    AL  = character code (0..255)
  ;             ELM = pointer to 64 source chars (8 rows x 8 cols, row-major)
  ;   Encoding: ' ' (space) or 0 -> bit clear; any other byte -> bit set
  ;             MSB = leftmost column
  ;   Writes:   8 bytes at petscii_data + charcode*8
  ; ----------------------------------------------------------------------------

You can also use AH=48h if you have the pre-compiled bytes:

  ; ----------------------------------------------------------------------------
  ; AH=48h - font_set_char -- install an 8-byte glyph into the font
  ;   Input:    AL  = character code (0..255)
  ;             ELM = pointer to 8 source bytes
  ;   Writes:   8 bytes at petscii_data + charcode*8
  ;   Destroys: A   (BLX/FLD/C/K preserved)
  ; ----------------------------------------------------------------------------

Here's how it works:

.address $C000

start:
    LDELM @smiley
    LDAH $49
    LDAL #'!'  ; replace the ! character
    INT $10
    RET   
    ; Now the ! is replaced by a smiley.
    
    
smiley:
    .bytes "        "
    .bytes "  #  #  "
    .bytes "        "
    .bytes " #    # "
    .bytes " #    # "
    .bytes "  ####  "
    .bytes "        "
    .bytes "        "

Unfortunately, the conversion to a smiley is permanent! At least, until you reboot the system (or change it back).

put_ccxy

Let's make a combo function that puts a character and a color on the screen at the same time. We'll use the non-cursor version of these functions:

.address $C000

start:

  LDB #'A'
  LDC $03
  LDX #39
  LDY #24
  CALL @put_ccxy
  RET

; put_ccxy(B, X, Y, C) ; Put Character and Color at X, T ; INPUT: ; B – ascii code ; X – x location on screen ; Y – y location on screen ; C – hi nibble (BG) and low niblle (FG) ; put_ccxy:

  LDAH $11    ; Write character in B at X, Y
  INT $10
  LDAH $13    ; Set color in C at X, Y
  INT $10
  RET

</codify>

It's really a toss-up if you want to make two INT calls or just call put_ccxy. This is more about learning how those two functions can work.

Using this concept in Mode 3

Now, interestingly enough, you can translate this graphics technique directly into Mode 3. First, let's adapt BASIC's DRAWCHAR (INT $18 AH=$60 and $61) so we can blit characters in Mode 3.

  ; ----------------------------------------------------------------------------
  ; AH=61h -- stamp_char -- render a glyph onto mode-3, transparent background
  ;   Input:  AL = character code (0..255)
  ;           X  = base X (pixel)
  ;           Y  = base Y (pixel)
  ;           CL = foreground color (only looks at low nibble)
  ;   Uses:   INT 18h AH=01h (plot pixel);
  ;           glyph source = VM1_CHAR_ROM ($01E000)
  ; ----------------------------------------------------------------------------

We'll toss in some fancy arrow key movement and a q for quit key, to spice things up:

.address $C000

start:
    ; Switch to mode 3
    LDA $4003   ; 40h = set mode, 3 = mode 3
    INT $10

    ; Clear screen
    LDAH $20
    INT $10

    LDX #0
    LDY #0

loop:
    ; draw an 'x' at X, Y
    LDAL #'x'
    LDAH $61    ; stamp char
    LDC #9      ; dark blue
    INT $18

    LDAH $02    ; blocking getkey
    INT 0x10

    LDAH $60    ; erase char
    LDC #0      ; black background
    INT $18

    CMP AL, #'q'
    JZ @exit
    CMP AL, #'Q'
    JZ @exit

    CMP AL, #128
    JZ @move_left
    CMP AL, #129
    JZ @move_down
    CMP AL, #130
    JZ @move_up
    CMP AL, #131
    JZ @move_right

    JMP @loop

move_left:
    DEC X
    DEC X
    JNN @loop   ; if X didn't go negative, just continue.

    LDX #0      ; Don't allow X to go below 0.
    JMP @loop

move_right:
    INC X
    INC X
    CMP X, #312
    JNC @loop

    ; C is set if X was >= 312
    LDX #311
    JMP @loop


move_up:
    DEC Y
    DEC Y
    JNN @loop   ; if Y didn't go negative, just continue.

    LDY #0
    JMP @loop

move_down:
    INC Y
    INC Y
    CMP Y, #191
    JNC @loop

    LDY #191
    JMP @loop

exit:
    ; Switch back to mode 1
    LDA $4001
    INT $10
    RET

Multi-Color Character Graphics in Mode 3

The way to do multicolor graphics is to use different tiles for each color component. For example, you can have one tile with the blue pixels and the other tile with the yellow pixels. When you draw the tiles on top of each other, it will produce a multicolor effect.

sd/getting_stuff_done_in_assembly_language.1780292758.txt.gz · Last modified: by appledog

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki