User Tools

Site Tools


sd:part_ii_writing_games_in_assembly_language

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
sd:part_ii_writing_games_in_assembly_language [2026/04/02 12:55] appledogsd:part_ii_writing_games_in_assembly_language [2026/04/03 05:30] (current) appledog
Line 3: Line 3:
  
 == Introduction == Introduction
-At the end of [[SD-8516 Stellar BASIC]] we wrote a game called [[sdb:robots|ROBOTS.BAS]]. This was a fun little take on the classic CHASE or 'robots' game from the bsd games collection. Now, for our study of Assembly Language, let's write a similar game: 'robots.asm'.+At the end of [[SD-8516 Stellar BASIC]] we wrote a game called [[sdb:robots|ROBOTS.BAS]]. This was a fun little take on the classic CHASE or 'robots' game from the bsd games collection. Now, for our study of Assembly Language, let's write a similar game: 'robots.asm'. Don't worry if you're not familiar with the BASIC version of this program; everything will be explained here.
  
 == Part 1: Assembling and Running a Program == Part 1: Assembling and Running a Program
-An assembly language program has an .address the bytes will be compiled from at that address. Our program therefore begins:+Just like BASIC programs have line numbers, or C programs have #include statements, an assembly language program should start with an ''.address'' statement. This will tell the assembler where to start assembling the program. This enables us to use labels like ''start:'' and ''message:''. The assembler can calculate the exact address of the label in relative to the ''.address'' when assembling the program.
  
 <codify armasm> <codify armasm>
Line 25: Line 25:
     as robots.asm robots     as robots.asm robots
  
-To run the program, just DLOAD (or LOAD) it and type "sys 256". This can be a bit confusing; why 256? Without instructions, people might not know what to do. There are three options.+To run the program, just DLOAD (or LOAD) it and type "SYS 256". This can be a bit confusing; why 256? Without instructions, people might not know what to do. There are three options.
  
-* 1. Tell people they have to type SYS and a number. +* 1. Tell people they have to type SYS and a number. Mysterious
-* 2. .address it at $030100. This is the default "SYSlocation. People can run it with SYS. +* 2. use ''.address $030100'' (the default ''SYS'' location). People can run it with ''SYS''
-* 3. Include a BASIC stub.+* 3. Include a BASIC stub so people can type ''RUN''.
  
 For this program I will demonstrate option #3, although #2 is probably just as common. Our program now becomes: For this program I will demonstrate option #3, although #2 is probably just as common. Our program now becomes:
Line 52: Line 52:
 </codify> </codify>
  
-Enter this into ed and then save it; then assemble:+=== Step 1: ED 
 +Enter this into ed by typing: 
 + 
 +    f robots.asm 
 +    a 
 + 
 +Enter the program then type ''.'' on a line by itself. Then type ''w'' to save and ''q'' to quit. Now you can assemble the program. 
 + 
 +=== Step 2: Assembly 
 +To assemble a program named ''robots.asm'' in d-tank, type:
  
     as robots.asm r     as robots.asm r
  
-when you dload r, now you can type LIST. It will show:+ 
 +=== Step 3: LOAD and RUN 
 +Finally, enter 
 + 
 +    dload r 
 + 
 +After you ''dload r'' you can type LIST. It will show:
  
     10 SYS 269     10 SYS 269
Line 63: Line 78:
  
 == Part 2: Program Structure == Part 2: Program Structure
-Now that we understand how to assemble and run a program, we can talk about our game -- robots. Just like in BASIC, we will have:+Now that we understand how to assemble and run a program, we can talk about our game ''robots''. Just like in BASIC, we will have:
  
 * A game loop * A game loop
Line 71: Line 86:
 * Collision detection * Collision detection
  
 +=== Initialization
 Just like in the BASIC version, we can start by drawing the map. First, let's define our variables and then the map drawing function. We will include a simple main loop whose only job is to call draw_map once. Just like in the BASIC version, we can start by drawing the map. First, let's define our variables and then the map drawing function. We will include a simple main loop whose only job is to call draw_map once.
- 
-Our complete listing now becomes: 
  
 <codify armasm> <codify armasm>
 +; Define variables.
 +; This section uses memory-location variables. $00 is memory location $00.
 .equ PX $00        ; player x .equ PX $00        ; player x
 .equ PY $01        ; player y .equ PY $01        ; player y
Line 101: Line 117:
     CALL @draw_map     CALL @draw_map
     RET     RET
 +</codify>
 +
 +Now that we have initialized our main variables and set up a program loop we are ready for the ''draw_map'' subroutine.
 +
 +=== draw_map
 +Like in the BASIC version, the ''draw_map'' subroutine will begin by clearing the screen. So the first two things we do are we replicate VSTOP and CLS from BASIC:
 +
 +<codify armasm>
 +    .equ VIDEO_MODE $01EF00 ; This defines VIDEO_MODE as a memory location variable.
  
 draw_map: draw_map:
Line 106: Line 131:
     LDAL $81     LDAL $81
     STAL [@VIDEO_MODE]     STAL [@VIDEO_MODE]
 +    
     ; CLS     ; CLS
     LDAH $10        ; CLS     LDAH $10        ; CLS
     INT $10     INT $10
 +</codify>
  
 +In assembly, we can't easily call a BASIC command, so we do things slightly differently. Setting video mode to $81 sets us up in mode 1 but stops video updates (because bit 7 is set). So $81 means "mode 1, but set bit 7 so we don't update the screen". This is normally done to stop screen tearing caused by drawing during an update. But here we're just doing it for academic reasons since assembly is so fast the screen won't have time to update until we're done.
 +
 +Secondly, INT $10 AH=$10 is the clear screen function. We can call that as an interrupt helper here.
 +
 +==== Drawing the border
 +To draw the border we need to set up a loop that draws stars on the top and bottom, left and right, just like in BASIC. Here we will examine the top and bottom loop:
 +
 +<codify armasm>
     ; Draw top/bottom border: '*' at (0..39, 0) and (0..39, 24)     ; Draw top/bottom border: '*' at (0..39, 0) and (0..39, 24)
-    LDBL #'*' +    LDBL #'*'       ; Load character into BL 
-    LDXL #0+    LDXL #0         ; set X location
  
 dm_top_bottom: dm_top_bottom:
-    LDYL #0+    LDYL #0         ; set Y location
     LDAH $11        ; write char AL at XL, YL     LDAH $11        ; write char AL at XL, YL
     INT $10     INT $10
 +</codify>
  
 +This simply loads the character, sets the x and y locations, and calls write_char_at_xy via INT $10.
 +
 +<codify armasm>
     LDYL #24     LDYL #24
-    LDAH $11        ; write char+    LDAH $11        ; write char (on the bottom of the screen this time)
     INT $10     INT $10
     INC XL     INC XL
     CMP XL, #40     CMP XL, #40
     JNC @dm_top_bottom     JNC @dm_top_bottom
 +</codify>
  
 +Here's the loop: After the top and bottom characters are drawn, we INC XL (which is the column marker). Then we CMP XL, #40. This means "check if it's under 40"-- i.e. 0-39. If we are on 39, we need to draw again. If we just drew on column 39 and then we INC XL to 40, that means don't continue the loop, and we fall-through to the next function; drawing on the left and right sides.
 +
 +<codify armasm>
     ; Draw left/right border: '*' at (0, 0..24) and (39, 0..24)     ; Draw left/right border: '*' at (0, 0..24) and (39, 0..24)
     LDBL #'*'     LDBL #'*'
Line 142: Line 184:
     CMP YL, #25     CMP YL, #25
     JNC @dm_left_right     JNC @dm_left_right
 +</codify>
  
 +This code works just like the top and bottom but on the left and right sides. Instead of looping from 0 to 39 on XL, we will loop from 0 to 24 on YL. As before, we CMP to see if YL is 25. If it is, we fall through. A simple loop, in essence the exact same loop technique we used to draw the map in BASIC.
 +
 +==== Draw the player and robots
 +<codify armasm>
     ; Draw player '@'     ; Draw player '@'
     LDBL #'@'     LDBL #'@'
Line 160: Line 207:
     LDAL #1     LDAL #1
     STAL [@VIDEO_MODE]     STAL [@VIDEO_MODE]
 +    
     RET     RET
 </codify> </codify>
  
-Let's go over the draw_map subroutine.+Finally we draw the player and the robot, and turn video updates back on (Set mode to 1, but without bit 7 set). As we are done drawing the map, we can now RET. If you save, assemble and run the program now you will see it draws the border, player and robot before exiting.
  
-The first two things we do are we replicate VSTOP and CLS from BASIC.+It's working!
  
-    ; VSTOP +more to come...
-    LDAL $81 +
-    STAL [@VIDEO_MODE] +
-     +
-    ; CLS +
-    LDAH $10        ; CLS +
-    INT $10+
  
-In assembly, we can't easily call a BASIC command, so we do things slightly differently. Setting video mode to $81 sets us up in mode 1 but stops video updates (because bit 7 is set). So $81 means "mode 1, but set bit 7 so we don't update the screen". This is normally done to stop screen tearing caused by drawing during an update. But here we're just doing it for academic reasons since assembly is so fast the screen won't have time to update until we're done. +== Part 3: Player Input 
- +Let's begin part 3 by adding a new subroutine to our main loop. Our main loop now becomes:
-Secondly, INT $10 AH=$10 is the clear screen function. We can call that as an interrupt helper here. +
- +
-=== Drawing the border +
-To draw the border we need to set up a loop that draws stars on the top and bottom, left and right, just like in BASICHere we will examine the top and bottom loop:+
  
 <codify armasm> <codify armasm>
-    ; Draw top/bottom border'*' at (0..39, 0) and (0..39, 24) +main_loop
-    LDBL #'*'       ; Load character into BL +    CALL @draw_map 
-    LDXL #0         ; set X location +    CALL @get_input 
- +    RET
-dm_top_bottom: +
-    LDYL #0         ; set Y location +
-    LDAH $11        ; write char AL at XL, YL +
-    INT $10+
 </codify> </codify>
  
-This simply loads the character, sets the x and y locations, and calls write_char_at_xy via INT $10.+=== get_input 
 +The primary goal of this section is to get input from the player and deal with itWe can do this by using the getkey function:
  
 <codify armasm> <codify armasm>
-    LDYL #24 +get_input: 
-    LDAH $11        ; write char (on the bottom of the screen this time)+    LDAH $02        ; getkey()
     INT $10     INT $10
-    INC XL 
-    CMP XL, #40 
-    JNC @dm_top_bottom 
 </codify> </codify>
  
-Here'the loop: After the top and bottom characters are drawnwe INC XL (which is the column marker). Then we CMP XL, #40. This means "check if it's under 40"-- i.e. 0-39. If we are on 39, we need to draw again. If we just drew on column 39 and then we INC XL to 40, that means don't continue the loop, and we fall-through to the next function; drawing on the left and right sides:+In this case, the function specification for AH=$02INT 0x10 is:
  
-<codify armasm> +    ; ============================================================================ 
-</codify>+    ; AH=02h - Read Character (Blocking) 
 +    ; Input:  None 
 +    ; Output: AL  = ASCII character 
 +    ;          = number of keys that were in buffer before this call 
 +    ;          = keyboard flags at time of press 
 +    ; Note:   X, Y preserved for cursor position 
 +    ;         This function BLOCKS until a key is pressed 
 +    ; ============================================================================ 
 + 
 +As we can see, this function returns the ASCII code of the key the player will press in ''AL''. Thus, we can deal with the player's commands. 
 + 
 +=== uppercase or lowercase? 
 +The player might type an uppercase command or a lowercase command. Let's normalize the keys to uppercase before processing them:
  
 <codify armasm> <codify armasm>
 +
 +    ; Uppercase: if AL >= 'a', it must be an uppercase character (or an invalid command).
 +    CMP AL, #97
 +    JNC @gi_check_keys      ; AL < 'a', skip
 + 
 +    CMP AL, #123
 +    JC @gi_check_keys       ; AL >= '{', skip
 +    
 +    SUB AL, #32
 +    
 +gi_check_keys:
 +    ...
 </codify> </codify>
 +
 +CMP works as follows:
 +
 +    CMP A, B        ; if A >= B, then set carry
 +
 +Therefore,
 +
 +    CMP AL, #97     ; if AL is greater or equal to #97 ('a') then set carry
 +
 +So therefore if carry is NOT set, skip past the SUB command. Or else (if it's a lowercase ASCII) subtract 32 to convert from lowercase to uppercase.
 +
 +The second compare has a protective function.
 +
 +    CMP AL, #123             ; if AL >= 123, set carry.
 +    
 +    ; If it's '{' or anything after, skip:
 +    JC @gi_check_keys        ; Jump if Carry = Jump if AL > 'z' (al >= '}')
 +
 +
 +So after these two checks, the only values that reach SUB AL, #32 are exactly the bytes from 97 to 122 inclusive; i.e. 'a' to 'z'.
 +
 +Visual summary:
 +^ AL Value ^ Meaning ^ What Happens ^
 +| < 97 | before ''a'' | Skipped. |
 +| 97-122 | ''a'' to ''z'' | SUB #32 becomes 'A' to 'Z'. |
 +| >= 123 | after 'z'\\ //'}' and up// | Skipped. |
 +
 +=== Processing player input
 +We will use the HJKL convention:
  
 <codify armasm> <codify armasm>
 +gi_check_keys:
 +    CMP AL, #'H'
 +    JZ @move_left
 +
 +    CMP AL, #'J'
 +    JZ @move_down
 +
 +    CMP AL, #'K'
 +    JZ @move_up
 +
 +    CMP AL, #'L'
 +    JZ @move_right
 +
 +    CMP AL, #'Q'
 +    JZ @quit_game
 +
 +    RET
 </codify> </codify>
  
 +This is easy enough; after a CMP, if the values are equal then the zero flag is set. We will use JZ (jump if zero flag is set) to go to the correct routine.
 +
 +=== processing each command
 <codify armasm> <codify armasm>
 +move_left:
 +    LDAL [@PX]        ; load variable PX into register AL.
 +    DEC AL            ; X = X - 1 (move to the left!)
 +    
 +    CMP AL, #1        ; bounds check. Is the player on the border?
 +    JC @ml_ok         ; AL >= 1, so it's ok. skip to ml_ok,
 +    
 +    LDAL #1           ; If  we reach here the player was on the border; set position to 1.
 +    
 +ml_ok:
 +    STAL [@PX]        ; pass! Save the player's location.
 +    RET
 </codify> </codify>
 +
 +This is the move_left command. We begin by loading the player's X location into AL and executing the move. After a bounds check, we save the variable. All of the other moves operate this way.
sd/part_ii_writing_games_in_assembly_language.1775134550.txt.gz · Last modified: by appledog

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki