User Tools

Site Tools


sd:rogueima_i_mvp-4

This is an old revision of the document!


Rogueima I MPV-4

  • rogueima.asm: 384
  • draw.asm: 247
  • map.asm: 51
  • mon.asm: 99
  • msg.asm: 166
  • obj.asm: 171
  • talk.asm: 166
  • combat.asm: 149

Total: 1,385 SLOC. According to SLOCCOUNT, a program by David A. Wheeler, using traditional software development metrics this program would have taken four months and cost $38,033 to develop. The four months is about right, then again I did this in about 2 months; and frankly, I would have done it for half the cost!

rogueima.sda

// Rogueima (C) 2026 Appledog Hu
// rogueima.sda
// rogueima 'main'

; Variables
.equ PX $00    ; player X
.equ PY $01    ; player Y
.equ RX $02    ; robot X
.equ RY $03    ; robot Y

.equ player_str    $04       ; 2 bytes
.equ player_hp     $06       ; 2 bytes
.equ player_name   $08       ; 16 bytes + 1 zero starting at $02
.equ pname_zero    $18       ; this must always be a zero even if there are earlier zeroes.
.equ game_time     $19       ; 2 bytes
.equ player_score  $1b       ; 2 bytes

.equ VIDEO_MODE           $01EF00   ; Current video mode (1 byte)

.address $C000

    ; BASIC stub: 10 SYS 269
    ;.bytes $FB, $0A, $00
    ;.bytes "SYS 269", $00
    ;.bytes $00, $00

; --- Entry point at $00010D (decimal 269) ---
start:
    ; Set mode 6.
    LDAH $40    ; Set video mode
    LDAL #6     ; to 6
    INT 0x10

    CALL @title_screen
    CALL @init_game

    CALL @get_player_name
    LDA #0
    STAL [@pname_zero]      ; ensure player name length

    JMP @main_loop    ; start game

init_game:
    ; Initialize variables
    LDAL #19
    STAL [@PX]
    LDAL #11
    STAL [@PY]
    LDAL #40
    STAL [@RX]
    LDAL #22
    STAL [@RY]

    LDA #10
    STA [@player_str]
    STA [@player_hp]

    LDA #0
    STA [@game_time]
    STA [@player_score]

    CALL @init_objects      ; Initialize the game objects list.

    RET

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Main Loop
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
main_loop:
    CALL @draw_map
    CALL @get_input
    CALL @inc_game_time

    JMP @main_loop ; Player can press Q to quit.

inc_game_time:
    LDA [@game_time]
    INC A
    STA [@game_time]
    RET


get_player_name:
    LDELM @what_is_your_name
    LDAH $18      ; write string
    INT 0x10

    LDAH $68      ; IO_INPUT
    INT 0x05
    ; player name is now in a system buffer (pointed too by ELM).

    ; Ensure player name is no longer than 16 characters.
    MOV FLD, ELM
    ADD FLD, #17
    LDAL #0
    STAL [FLD]

    ; Copy player name into variable space.
    LDFLD @player_name
name_copy_loop:
    LDAL [ELM, +]
    STAL [FLD, +]
    JNZ @name_copy_loop

    RET ;; return.

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

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Get input from the player.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
get_input:
    ; Blocking GETKEY -> AL
    LDAH $02
    INT $10

    ;; Add the player's key as entropy.
    LDAH $02
    INT 0x13

    ; Uppercase: if AL >= 'a' and AL < 'z'+1, subtract 32
    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:
    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, #128
    JZ @move_left
    CMP AL, #129
    JZ @move_down
    CMP AL, #130
    JZ @move_up
    CMP AL, #131
    JZ @move_right

    CMP AL, #'M'
    JZ @spawn_monster
    CMP AL, #'T'
    JZ @do_talk
    CMP AL, #'O'
    JZ @open_door
    CMP AL, #'G'
    JZ @create_gold
    CMP AL, #'Q'
    JZ @quit_game
    CMP AL, #','        // pick things up using comma
    JZ @pick_up_items

    RET

move_left:
    LDAL [@PX]
    JZ @ml_done
    DEC AL                  ; AL = new PX
    LDX #0
    LDY #0
    MOV XL, AL              ; XL = new PX (survives CALL)
    LDYL [@PY]
    CALL @is_walkable
    JNC @ml_done
    STXL [@PX]
ml_done:
    RET

move_right:
    LDAL [@PX]
    INC AL                  ; AL = new PX
    LDB [@map1_dim]         ; BL = map_width
    CMP AL, BL
    JC @mr_done             ; AL >= map_width, out of bounds, abort
    LDX #0
    LDY #0
    MOV XL, AL              ; XL = new PX
    LDYL [@PY]
    CALL @is_walkable
    JNC @mr_done
    STXL [@PX]
mr_done:
    RET

move_up:
    LDAL [@PY]
    JZ @mu_done
    DEC AL                  ; AL = new PY
    LDX #0
    LDY #0
    LDXL [@PX]
    MOV YL, AL              ; YL = new PY (survives CALL)
    CALL @is_walkable
    JNC @mu_done
    STYL [@PY]
mu_done:
    RET

move_down:
    LDAL [@PY]
    INC AL                  ; AL = new PY
    LDB [@map1_dim]         ; BH = map_height
    CMP AL, BH
    JC @md_done             ; AL >= map_height, out of bounds, abort
    LDX #0
    LDY #0
    LDXL [@PX]
    MOV YL, AL              ; YL = new PY
    CALL @is_walkable
    JNC @md_done
    STYL [@PY]
md_done:
    RET


;;;;;;;;;;;;;;;;;;;;;;;;
;; Game Over.
;;;;;;;;;;;;;;;;;;;;;;;;
game_over:
    ; CLS
    LDAH $10
    INT $10

    ; Print "OH NO! THE ROBOT CATCHES YOU."
    LDBLX @msg_game_over1
    LDAH $66
    INT $05
    LDAH $64                ; newline
    INT $05

    ; Print "GAME OVER."
    LDBLX @msg_game_over2
    LDAH $66
    INT $05
    LDAH $64
    INT $05

    RET

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Player quit game
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
quit_game:
    ; CLS
    LDAH $10
    INT $10

    ; Print "QUIT GAME"
    LDBLX @msg_quit
    LDAH $66
    INT $05

    POP ELM ; destroy return address of CALL from main loop
    RET     ; exit program.

; ============================================================================
; String data
; ============================================================================
msg_game_over1:
    .bytes "OH NO! THE ROBOT CATCHES YOU.", 0
msg_game_over2:
    .bytes "GAME OVER.", 0
msg_quit:
    .bytes "QUIT GAME", 10,13,0
msg_died:
    .bytes "You died.", 10,13,0
robot_sfx:
    .bytes "T120 W1 V5 O2 L24 B", 0


; =============================================
; Title Screen
; =============================================
title_screen:
    ; Clear screen
    ; (This is done on mode switch, but we do it here anyways.)
    LDAH $10
    INT $10

    LDA $1500          ; Set cursor
    LDX #1
    LDY #1
    INT 0x10

    LDELM @str_title1
    LDA $1800
    INT 0x10
    RET

str_title1:
    .bytes "Rogueima I: The First Dungeon",10,13
    .bytes " Copyright 2026 by Appledog Hu.",10,10,13,0


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; get_glyph(X, Y) -> AL
;;
;; Returns the map glyph at column X, row Y.
;; Caller is responsible for X, Y being within map bounds.
;;
;; Inputs:   X = column, Y = row
;; Output:   AL = glyph byte (e.g. '#', '.', '+', ' ')
;;
get_glyph:
    PUSH ELM
    PUSH FLD
    PUSH K
    PUSH Z
    PUSH I
    PUSH B

    ;  Default: map tile glyph
    LDK [@map1_dim]
    MOV Z, Y
    MUL Z, KL
    ADD Z, X
    LDELM @map1_data
    ADD ELM, Z
    LDAL [ELM]              ; AL = map glyph (default return)

    ; Override if any object sits at (X, Y)
    LDFLD @OBJS_BASE
get_glyph_obj_loop:
    LDFLD [FLD]
    CMP FLD, @OBJS_BASE
    JZ @get_glyph_done      ; wrapped to head, no match, keep map glyph

    LDI @OBJ_ID
    LDB [FLD+I]
    JZ @get_glyph_obj_loop  ; uninitialized slot

    LDI @OBJ_X
    LDBL [FLD+I]
    CMP BL, XL
    JNZ @get_glyph_obj_loop

    LDI @OBJ_Y
    LDBL [FLD+I]
    CMP BL, YL
    JNZ @get_glyph_obj_loop

    LDI @OBJ_VIS
    LDAL [FLD+I]

get_glyph_done:
    POP B
    POP I
    POP Z
    POP K
    POP FLD
    POP ELM
    RET

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; put_glyph(X, Y, AL)
;;
;; Places the glyph AL at x, y
;; Caller is responsible for X, Y being within map bounds.
;;
;; Inputs:   X = column, Y = row, AL = chr (ex. 'A')
;;
put_glyph:
    PUSH ELM
    PUSH K
    PUSH Z

    LDK [@map1_dim]      ; KL = map_width
    MOV Z, Y             ; Z = Y
    MUL Z, KL            ; Z = Y * map_width
    ADD Z, X             ; Z = Y * map_width + X
    LDELM @map1_data
    ADD ELM, Z           ; ELM -> map[Y][X]
    STAL [ELM]           ; AL = glyph

    POP Z
    POP K
    POP ELM
    RET

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; is_walkable(X, Y)
;;
;; Sets CARRY if this tile is walkable.
;;
;; Non-walkable tiles: = and +
;;

is_walkable:
    PUSHA
    CALL @get_glyph     ; AL now has glyph.
    CMP AL, #'#'
    JZ @is_walkable_no
    CMP AL, #'+'
    JZ @is_walkable_no

    ;; default: yes.
is_walkable_yes:
    POPA
    SEC
    RET

is_walkable_no:
    POPA
    CLC
    RET

;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; open_door()
;; This replicates the gi_get_keys pattern
;; but is used to directionally change a door's status.
;;
open_door:
    ; Show "Open where?"
    LDELM @str_open_where
    CALL @print_msg
    CALL @msg_display

    ; Ask player for key
    LDAH $02    ; Blocking GETKEY -> AL
    INT $10

    ;; Add the player's key as entropy.
    LDAH $02
    INT 0x13

    ; Uppercase: if AL >= 'a' and AL < 'z'+1, subtract 32
    CMP AL, #97
    JNC @od_check_keys      ; AL < 'a', skip
    CMP AL, #123
    JC @od_check_keys       ; AL >= '{', skip
    SUB AL, #32

od_check_keys:
    ; Check direction given
    CMP AL, #'H'
    JZ @open_door_left
    CMP AL, #'J'
    JZ @open_door_down
    CMP AL, #'K'
    JZ @open_door_up
    CMP AL, #'L'
    JZ @open_door_right

    CMP AL, #128
    JZ @open_door_left
    CMP AL, #129
    JZ @open_door_down
    CMP AL, #130
    JZ @open_door_up
    CMP AL, #131
    JZ @open_door_right

    ; Bad direction -- ignore.

    RET

open_door_up:
    ; Show "open door north"
    LDELM @str_OPEN_DOOR
    CALL @print_msg
    LDELM @str_NORTH
    CALL @print_msg
    LDELM @str_crlf
    CALL @print_msg

    LDI #0
    LDJ #0
    LDK #0
    LDL #1
    JMP @open_door_go

open_door_down:
    LDELM @str_OPEN_DOOR
    CALL @print_msg
    LDELM @str_SOUTH
    CALL @print_msg
    LDELM @str_crlf
    CALL @print_msg

    LDI #0
    LDJ #1
    LDK #0
    LDL #0
    JMP @open_door_go

open_door_left:
    LDELM @str_OPEN_DOOR
    CALL @print_msg
    LDELM @str_WEST
    CALL @print_msg
    LDELM @str_crlf
    CALL @print_msg

    LDI #0
    LDJ #0
    LDK #1
    LDL #0
    JMP @open_door_go

open_door_right:
    LDELM @str_OPEN_DOOR
    CALL @print_msg
    LDELM @str_EAST
    CALL @print_msg
    LDELM @str_crlf
    CALL @print_msg

    LDI #1
    LDJ #0
    LDK #0
    LDL #0
    JMP @open_door_go

open_door_go:
    LDX #0
    LDY #0
    LDXL [@PX]
    LDYL [@PY]
    ADD XL, IL
    ADD YL, JL
    SUB XL, KL
    SUB YL, LL
    CALL @get_glyph
    CMP AL, #'+'
    JZ @open_door_open_it
    CMP AL, #'-'
    JZ @open_door_close_it

    ;; bad command? ignore.
    RET

open_door_open_it:
    LDAL #'-'
    CALL @put_glyph
    RET

open_door_close_it:
    LDAL #'+'
    CALL @put_glyph
    RET

draw.sda

// rogueima (C) 2026 Appledog Hu
// draw.asm
// Draw the display screen; borders, status, chat, wold map, etc.

cam_origin_x:
    .bytes 0,0      ; displacement value for drawing on the projected map

cam_origin_y:
    .bytes 0,0      ; displacement value for drawing on the projected map

draw_map:
    LDAH $51        ; VSTOP
    INT 0x18

    CALL @draw_borders
    CALL @draw_world
    CALL @draw_items
    CALL @move_mon
    CALL @draw_mobs
    CALL @draw_player
    CALL @draw_stats
    CALL @draw_msgs
    CALL @msg_display

    LDAH $50        ; VSTART
    INT 0x18

    LDA [@player_hp]
    CMP A, 0
    JZ @kill_player

    RET

;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Draw borders.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;
draw_borders:
    ; CLS
    LDAH $10        ; CLS
    INT $10

    LDX #0  ;; clear XH and YH.
    LDY #0

    ; Draw top/bottom border: '*' at (0..79, 0) and (0..79, 24)
    LDBL #'*'
    LDXL #0

dm_top_bottom:
    LDYL #0
    LDAH $11        ; write char AL at XL, YL
    INT $10

    LDYL #24
    LDAH $11        ; write char
    INT $10

    CMP XL, #59      ; if XL >= 10, then set carry.
    JNC @dm_skip_ms    ; So if it's not, then don't draw the middle separator char.

    LDYL #10        ; status/text window separator
    LDAH $11        ; write char
    INT $10

dm_skip_ms:
    INC XL
    CMP XL, #80     ; Mode 6 is 80 chars wide.
    JNC @dm_top_bottom

    ; Draw left/right borders: '*' at (0, 0..24) and (59, 0..24) and (79, 0..24)
    LDBL #'*'
    LDYL #0
dm_left_right:
    LDXL #0         ; left border
    LDAH $11        ; write char
    INT $10

    LDXL #59        ; middle border
    LDAH $11        ; write char
    INT $10

    LDXL #79        ; right side border
    LDAH $11        ; write char
    INT $10

    INC YL
    CMP YL, #25
    JNC @dm_left_right
    RET

draw_player:
    LDBL #'@'
    LDXL [@PX]
    LDYL [@PY]
    LDIL [@cam_origin_x]
    LDJL [@cam_origin_y]
    SUB XL, IL              ; XL = PX - I_start
    ADD XL, #1              ; play_x_offset (mirror draw_world)
    SUB YL, JL              ; YL = PY - J_start
    ADD YL, #1              ; play_y_offset
    LDAH $11
    INT $10
    RET

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Draw all the monsters that are placed on the map.
;; Filters on OBJ_TYPE == 2 (monster), skips items.
;;
draw_mobs:
    LDD [@cam_origin_x]         ; DL = I_start, DH = J_start
    LDT [@disp_dim]             ; TL = play_width, TH = play_height
    LDELM @OBJS_BASE
draw_mobs_loop:
    LDELM [ELM]
    CMP ELM, @OBJS_BASE
    JZ @draw_mobs_done

    LDI @OBJ_ID
    LDA [ELM+I]
    JZ @draw_mobs_loop          ; uninitialized slot

    LDI @OBJ_TYPE
    LDA [ELM+I]
    CMP A, #2                    ; OBJ_TYPE_MONSTER
    JNZ @draw_mobs_loop         ; not a mob → skip

    LDI @OBJ_X
    LDX [ELM+I]
    LDI @OBJ_Y
    LDY [ELM+I]

    ; --- Visibility: is (XL, YL) inside the viewport? ---
    CMP XL, DL
    JNC @draw_mobs_loop          ; XL < I_start → off-screen left
    MOV AL, DL
    ADD AL, TL
    CMP XL, AL
    JC  @draw_mobs_loop          ; XL >= I_start + TL → off-screen right

    CMP YL, DH
    JNC @draw_mobs_loop
    MOV AL, DH
    ADD AL, TH
    CMP YL, AL
    JC  @draw_mobs_loop

    ; --- Map → screen ---
    SUB XL, DL
    ADD XL, #1                   ; play_x_offset
    SUB YL, DH
    ADD YL, #1                   ; play_y_offset

    ; --- Draw ---
    LDI @OBJ_VIS
    LDBL [ELM+I]
    LDAH $11
    INT $10
    JMP @draw_mobs_loop
draw_mobs_done:
    RET

draw_stats:
    ; Set cursor
    LDA $1500
    LDX #61
    LDY #2
    INT 0x10

    LDELM @str_name  ; Draw 'Name: '
    LDA $1800        ; write string
    INT 0x10

    ; Draw player's name.
    ; This will draw right after the 'Name: ' above.
    ; That's why we added spaces to the strings (below).
    LDELM @player_name
    LDA $1800   ; write string
    INT 0x10

;;;;;;;;;;;;;;;;;;;; Strength
    LDA $1500        ; set cursor
    LDX #61
    LDY #4
    INT 0x10

    LDELM @str_str     ; Draw 'Str: '
    LDA $1800          ; write string
    INT 0x10

    ; Draw player's strength score
    LDA $6300          ; print unsigned word (will print after string above).
    LDB [@player_str]
    INT 0x05

;;;;;;;;;;;;;;;;;;;; Health
    LDA $1500          ; set cursor
    LDX #61
    LDY #5
    INT 0x10

    LDELM @str_hp      ; Draw 'Health: '
    LDA $1800          ; Write string
    INT 0x10

    LDA $6300          ; Write number (unsigned)
    LDB [@player_hp]
    INT 0x05


;;;;;;;;;;;;;;;;;;;; Score
    LDA $1500          ; Set cursor
    LDX #61
    LDY #7
    INT 0x10

    LDELM @str_score   ; Draw 'Score: '
    LDA $1800          ; write string
    INT 0x10

    LDA $6300          ; print number
    LDB [@player_score]
    INT 0x05


;;;;;;;;;;;;;;;;;;;; Time
    LDA $1500          ; set cursor
    LDX #61
    LDY #8
    INT 0x10

    LDELM @str_time    ; Draw 'Time: '
    LDA $1800          ; write string
    INT 0x10

    LDA $6300          ; print number
    LDB [@game_time]
    INT 0x05

    RET

str_name:
    .bytes "Name: ", 0

str_str:
    .bytes "STR: ", 0

str_hp:
    .bytes "HP: ", 0

str_time:
    .bytes "T: ",0

str_score:
    .bytes "Score: ", 0


draw_msgs:
    ; We will work on this after.
    RET

draw_world:
    LDT [@disp_dim]    ; TL = play_width,  TH = play_height
    LDK [@map1_dim]    ; KL = map_width,   KH = map_height

    LDX #0
    LDY #0
    LDXL [@PX]
    LDYL [@PY]
    MOV I, X
    MOV J, Y

    ; Clamp I to [0, KL - TL]
    MOV AL, TL
    DEC AL
    SHR AL, #1          ; AL = (TL-1)/2 = centered offset
    CMP IL, AL
    JC @dw_i_lo_ok
    MOV IL, AL
dw_i_lo_ok:
    SUB IL, AL

    MOV AL, KL
    SUB AL, TL          ; AL = max_I_start
    CMP IL, AL
    JNC @dw_i_hi_ok
    MOV IL, AL
dw_i_hi_ok:

    ; Clamp J to [0, KH - TH]
    MOV AL, TH
    DEC AL
    SHR AL, #1
    CMP JL, AL
    JC @dw_j_lo_ok
    MOV JL, AL
dw_j_lo_ok:
    SUB JL, AL

    MOV AL, KH
    SUB AL, TH
    CMP JL, AL
    JNC @dw_j_hi_ok
    MOV JL, AL

dw_j_hi_ok:
    STIL [@cam_origin_x]        ; Save computed camera origin
    STJL [@cam_origin_y]

    ; Render loop
    LDX #1
    LDY #1
    MOV G, I

dw_x_loop:
    LDELM @map1_data
    ADD ELM, I
    MOV Z, J
    MUL Z, KL
    ADD ELM, Z
    LDBL [ELM]

    LDAH $11
    INT 0x10

    INC I
    INC X
    CMP TL, XL          ; continue while TL >= X
    JC @dw_x_loop

    LDX #1
    MOV I, G
    INC J
    INC Y
    CMP TH, YL
    JC @dw_x_loop       ; same trick on the outer

    RET

disp_dim:
    .bytes #58, #23

map.sda

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; map data
;;
map1_id:
    .bytes 1
map1_name:
    .bytes "START   ", 0       ; 8 characters and zero terminated
map1_up:
    .bytes 5, 5
map1_down:
    .bytes 25, 15
map1_dim:
    .bytes 80, 40
map1_data:
    .bytes "################################################################################" ; 0
    .bytes "#         #                                                                    #" ; 1
    .bytes "#         #                                                                    #" ; 2
    .bytes "#         #                                                                    #" ; 3
    .bytes "#         #                                                                    #" ; 4
    .bytes "#         +                                                                    #" ; 5
    .bytes "#         #                                                                    #" ; 6
    .bytes "#         #                        #########                                   #" ; 7
    .bytes "#         #                        #       #                                   #" ; 8
    .bytes "#         #                        #       +                                   #" ; 9
    .bytes "#####+#####                        #       #                                   #" ; 10
    .bytes "#                                  #       #                                   #" ; 11
    .bytes "#                                  #########                                   #" ; 12
    .bytes "#                                                                              #" ; 13
    .bytes "#                                                                              #" ; 14
    .bytes "#                                                                              #" ; 15
    .bytes "#                                                                              #" ; 16
    .bytes "#                                                                              #" ; 17
    .bytes "#                                                                              #" ; 18
    .bytes "#                                                                              #" ; 19
    .bytes "#                                                                              #" ; 20
    .bytes "#                                                                              #" ; 21
    .bytes "#                                                                              #" ; 22
    .bytes "#                                                                              #" ; 23
    .bytes "#                                                                              #" ; 24
    .bytes "#                                                                              #" ; 25
    .bytes "#                                                                              #" ; 26
    .bytes "#                                                                              #" ; 27
    .bytes "#                                                                              #" ; 28
    .bytes "#                                                                              #" ; 29
    .bytes "#                                                                              #" ; 30
    .bytes "#                                    #######+#############+#############+#######" ; 31
    .bytes "#                                    #             #             #             #" ; 32
    .bytes "#                                    #             #             #             #" ; 33
    .bytes "#                                    #             #             #             #" ; 34
    .bytes "#                                    #             #             #             #" ; 35
    .bytes "#                                    #             #             #             #" ; 36
    .bytes "#                                    #             #             #             #" ; 37
    .bytes "#                                    #             #             #             #" ; 38
    .bytes "################################################################################" ; 39

mon.sda

;; mon.sda
;; spawning monsters with the m command (as a test of the system, of course!)

spawn_monster:
    ; Find a free space in the object list
    LDA #0
    LDELM [@OBJS_BASE]  ; start scanning at HEAD.FLINK (i.e. the first object)
    LDI @OBJ_ID
    SCANQUE ELM, A, I
    JNZ @spawn_monster_oom

    ; Since we're using a sentinel node, if the sentinel is a match it means there was no valid node.
    CMP ELM, @OBJS_BASE
    JZ @spawn_monster_oom

    LDA [@obj_ids]
    LDI @OBJ_ID
    STA [ELM+I]
    INC A
    STA [@obj_ids]

    LDA #2              ; obj type 1 = gold
    LDI @OBJ_TYPE
    STA [ELM+I]

    CALL @random_monster    ; set up a random monster

    ; get a random x,y location that is walkable
    ; this could be refactored out maybe
    ; it's in create_gold too
    LDK [@map1_dim]
spawn_mon_xy:
    LDAH $00     ; 16 bit random number
    INT 0x13
    MOD B, KL
    MOV X, B

    LDAH $00     ; 16 bit random number
    INT 0x13
    MOD B, KH
    MOV Y, B
    CALL @is_walkable
    JNC @spawn_mon_xy

    LDI @OBJ_X
    STX [ELM+I]
    LDI @OBJ_Y
    STY [ELM+I]

    CLC         ; no error
    RET         ; return

spawn_monster_oom:
    SEC     ; set carry on error
    RET

random_monster:
    ;; What kind of monster do we want?
    ;; Pick a random number from 1 to 3.
randmon_montype:
    LDAH $00     ; 16 bit random number
    INT 0x13     ; math services
    MOD B, #3
    INC B       ; amount is now 1 to 3.
    CMP B, #1
    JZ @make_rat
    CMP B, #2
    JZ @make_snake
    CMP B, #3
    JZ @make_spider

    ;; it has to be one of the above 3.
    ;; But for safety..
    JMP @randmon_montype

make_rat:
    ;; Monster Hit Points
    LDAH $00     ; 16 bit random number
    INT 0x13     ; math services
    MOD B, #2
    INC B       ; amount is now 1 or 2 for a rat.
    LDI @OBJ_DATA1
    STB [ELM+I]

    ;; Monster strength (does how much damage)
    LDB #1
    LDI @OBJ_DATA2
    STB [ELM+I]

    ; Add GLYPH and VIS.
    LDAL #'r'       ; r for rat
    LDI @OBJ_GLYPH
    STAL [ELM+I]
    LDI @OBJ_VIS
    STAL [ELM+I]
    RET

make_snake:
    ;; Monster Hit Points
    LDAH $00     ; 16 bit random number
    INT 0x13     ; math services
    MOD B, #3
    INC B       ; amount is now 1 to 3 for a snake.
    LDI @OBJ_DATA1
    STB [ELM+I]

    ;; Monster strength (does how much damage)
    LDB #2
    LDI @OBJ_DATA2
    STB [ELM+I]

    ; Add GLYPH and VIS.
    LDAL #'s'       ; s for snake
    LDI @OBJ_GLYPH
    STAL [ELM+I]
    LDI @OBJ_VIS
    STAL [ELM+I]
    RET

make_spider:
    ;; Monster Hit Points
    LDAH $00     ; 16 bit random number
    INT 0x13     ; math services
    MOD B, #5
    INC B       ; amount is now 1 to 5 for a big fat spider
    LDI @OBJ_DATA1
    STB [ELM+I]

    ;; Monster strength (does how much damage)
    LDB #3
    LDI @OBJ_DATA2
    STB [ELM+I]

    ; Add GLYPH and VIS.
    LDAL #'x'       ; x for bugs, insects, spiders, etc.
    LDI @OBJ_GLYPH
    STAL [ELM+I]
    LDI @OBJ_VIS
    STAL [ELM+I]
    RET


;;;;;;;;;;;;;;;;;;
;; has_mon(x,y)
;;
;; Returns CARRY SET if monster.
;; Returns monster pointer in FLD.
;;
has_mon:
    PUSH I
    PUSH B

    LDFLD @OBJS_BASE
has_mon_loop:
    LDFLD [FLD]
    CMP FLD, @OBJS_BASE
    JZ @has_mon_no

    LDI @OBJ_ID
    LDB [FLD+I]
    JZ @has_mon_loop           ; uninitialized slot

    LDI @OBJ_TYPE
    LDB [FLD+I]
    CMP B, #2                   ; OBJ_TYPE_MONSTER
    JNZ @has_mon_loop

    LDI @OBJ_X
    LDBL [FLD+I]
    CMP BL, XL
    JNZ @has_mon_loop

    LDI @OBJ_Y
    LDBL [FLD+I]
    CMP BL, YL
    JNZ @has_mon_loop

    ; Match returns pointer in FLD, CF=1
    SEC
    JMP @has_mon_done

has_mon_no:
    CLC                          ; no monster; FLD unchanged.

has_mon_done:
    POP B
    POP I
    RET

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; move_mon
;;
;; Attempt to move every live monster
;; towards the player
;;
move_mon:
    LDELM @OBJS_BASE
move_mon_loop:
    LDELM [ELM]
    CMP ELM, @OBJS_BASE
    JZ @move_mon_done

    ; live monsters only
    LDI @OBJ_ID
    LDA [ELM+I]
    JZ @move_mon_loop
    LDI @OBJ_TYPE
    LDA [ELM+I]
    CMP A, #2                  ; OBJ_TYPE_MONSTER
    JNZ @move_mon_loop

    LDI @OBJ_X
    LDXL [ELM+I]
    LDI @OBJ_Y
    LDYL [ELM+I]

    ; sign(PX - mx) to CL
    LDAL [@PX]
    CMP AL, XL
    JZ  @mm_dx_zero
    JC  @mm_dx_pos             ; PX > mx
    LDCL #$FF                  ; PX < mx, step left
    JMP @mm_dx_done

mm_dx_pos:
    LDCL #$01
    JMP @mm_dx_done

mm_dx_zero:
    LDCL #$00

mm_dx_done:
    LDAL [@PY]
    CMP AL, YL
    JZ  @mm_dy_zero
    JC  @mm_dy_pos
    LDDL #$FF
    JMP @mm_dy_done

mm_dy_pos:
    LDDL #$01
    JMP @mm_dy_done

mm_dy_zero:
    LDDL #$00

mm_dy_done:
    ; Pick axis
    CMP CL, #0
    JNZ @mm_dx_nz
    CMP DL, #0
    JZ  @move_mon_loop         ; both zero, monster sits on player, skip
    JMP @mm_use_y

mm_dx_nz:
    CMP DL, #0
    JZ  @mm_use_x

    ; Both non-zero; coin flip
    LDAH $00
    INT 0x13
    MOD B, #2
    CMP B, #0
    JZ  @mm_use_x
    ; fall through to mm_use_y

mm_use_y:
    ADD YL, DL                 ; YL = my + sign(dy)
    JMP @mm_check

mm_use_x:
    ADD XL, CL                 ; XL = mx + sign(dx)

mm_check:
    LDAL [@PX]
    CMP AL, XL
    JNZ @mm_check_obj
    LDAL [@PY]
    CMP AL, YL
    JNZ @mm_check_obj

    ; Target tile IS the player, so attack instead of move
    LDFLD @player_obj           ; ELM attacks FLD

    CALL @do_combat
    JMP @move_mon_loop          ; monster's turn is spent attacking

mm_check_obj:
    PUSH ELM
    CALL @has_mon
    JC  @mm_pop_skip            ; another monster occupies target
    CALL @is_walkable
    JNC @mm_pop_skip            ; wall or other non-walkable

    ; commit the move
    POP ELM
    SED
    LDI @OBJ_X
    STXL [ELM+I]
    LDI @OBJ_Y
    STYL [ELM+I]
    CLD
    JMP @move_mon_loop

mm_pop_skip:
    POP ELM
    JMP @move_mon_loop

move_mon_done:
    RET

msg.sda

; msg.sda -- manage the message window.

msg_dim_x:
    .bytes #19, #0
msg_dim_y:
    .bytes #13, #0  ; width and height

msg_home_x:
    .bytes #60, #0
msg_home_y:
    .bytes #11, #0  ; X and Y where to start drawing

msg_cursor_x:
    .bytes #0, #0    ; X internal cursor location (start: left side)
msg_cursor_y:
    .bytes #12, #0   ; Y internal cursor location (start: lower row)

msg_data:
    ; 13 rows of 19 bytes (must match msg_dim)
    .bytes 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
    .bytes 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
    .bytes 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
    .bytes 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
    .bytes 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
    .bytes 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
    .bytes 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
    .bytes 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
    .bytes 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
    .bytes 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
    .bytes 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
    .bytes 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
    .bytes 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; print_msg(ELM)
;;
;; ELM points to a string. This function
;; simplifies a "puts" type function by
;; calling the "putc" type function.

print_msg:
    PUSH A                  ; We over-write AL so save register A.
    PUSH ELM                ; Save pointer to string

print_msg_loop:
    LDAL [ELM, +]           ; Load char and advance ELM.
    JZ @print_msg_done      ; If we hit a zero, (end of string), then exit.
    CALL @msg_putchar       ; Writes char al at position in msg_cursor
    JMP @print_msg_loop

print_msg_done:
    POP ELM                 ; restore original string pointer
    POP A
    RET


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; msg_putchar(AL)
;; looks at msg_cursor, msg_dim, etc. and writes the character.
;;
msg_putchar:
    ;; We are called with character in AL.
    ;; Find the current cursor position:
    LDX [@msg_cursor_x]
    LDY [@msg_cursor_y]

    ; Is AL a special character?
    CMP AL, #10     ; line feed
    JZ @msg_putchar_lf
    CMP AL, #13     ; carriage return
    JZ @msg_putchar_cr

mpc_check_done:
    ; Not a special character. Print it.
    LDFLD @msg_data
    ADD FLD, X
    LDI [@msg_dim_x]
    MUL Y, I        ; multiply Y by row length to get ptr
    ADD FLD, Y
    STAL [FLD]      ; Store this character in the buffer.

    ; Advance the cursor properly.
    CALL @msg_putchar_advance
    LDX [@msg_cursor_x]

msg_putchar_done:
    RET

msg_putchar_lf:
    CALL @msg_do_lf
    JMP @msg_putchar_done

msg_putchar_cr:
    CALL @msg_do_cr
    JMP @msg_putchar_done

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Advance the cursor properly.
;; This is a big responsibility!
;; We may need to linefeed the msg window.
;;
msg_putchar_advance:
    PUSHA
    LDX [@msg_cursor_x]
    LDY [@msg_cursor_y]

    INC X     ; Attempt to move cursor.

    ;; Check if we moved past the X-width
    LDI [@msg_dim_x]
    CMP X, I        ; Since X is 0 based, if it is equal to I it's out of bounds.
    JNZ @mpc_done   ; X is fine, so we're done.

    ; We've moved past the right side, do a CR/LF.
    CALL @msg_do_cr        ; Carriage return
    CALL @msg_do_lf        ; Do a linefeed, will scroll if necessary.

mpc_done:
    STX [@msg_cursor_x]
    STY [@msg_cursor_y]
    POPA
    RET


;;;;;;;;;;;;;;;;;
;; msg_do_cr
;; this doesn't affect the buffer in any way, just the cursor.
;;
msg_do_cr:
    LDX #0
    STX [@msg_cursor_x]
    RET

;;;;;;;;;;;;;;
;; msg_do_lf
;; Just lf.
;; Adds a line only if we need to.
msg_do_lf:
    PUSH J
    INC Y
    LDJ [@msg_dim_y]
    CMP Y, J
    JNC @msg_do_lf_done
    CALL @msg_scroll    ; manually scroll window.

msg_do_lf_done:
    STY [@msg_cursor_y]
    POP J
    RET

;;;;;;;;;;;;;;;;;;;;;;;;;
;; msg_scroll
;; Called by msg_do_lf.
;; You probably don't want to call this by itself.
;; Note that it modifies Y, and the caller may depend on this.
msg_scroll:
    PUSH ELM
    PUSH FLD
    CMP Y, 0                ; if Y is zero,
    JZ @msg_scroll_nodec     ; don't dec Y.

    DEC Y       ; We keep the cursor at the same position it was at in the buffer.
    STY [@msg_cursor_y]

msg_scroll_nodec:
    LDI [@msg_dim_x]    ; get row size
    LDJ [@msg_dim_y]    ; get height
    LDELM @msg_data     ; get start of msg area
    LDFLD @msg_data     ; copy start into FLD
    ADD ELM, I          ; skip one row in ELM (start)
    DEC J               ; don't copy last row (# of bytes)
    MUL J, I            ; find total bytes to move except last row

msg_scroll_loop:
    LDAL [ELM, +]
    STAL [FLD, +]
    DEC J
    CMP J, #0
    JNZ @msg_scroll_loop

    LDFLD @msg_data
    LDI [@msg_dim_x]
    LDJ [@msg_dim_y]
    DEC J                  ; AL = last row index
    MUL J, I               ; AL = byte offset of last row
    ADD FLD, J

    LDAL $20               ; space
msg_scroll_clear:
    STAL [FLD, +]
    DEC I
    JNZ @msg_scroll_clear  ; write a blank line

    POP FLD
    POP ELM
    RET                    ; Buffer has been updated!

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; msg_display
;; Copies the message buffer to the screen at (msg_home_x, msg_home_y).
;; Treats zero bytes as spaces so the all-zeros init looks blank.
;;
msg_display:
    PUSHA
    LDELM @msg_data         ; ELM walks through the buffer

    LDY #0
    LDYL [@msg_home_y]      ; Y = current screen row
    LDJL [@msg_dim_y]       ; JL = rows remaining

mdpy_row:
    LDX #0
    LDXL [@msg_home_x]      ; X = current screen col (start of row)
    LDIL [@msg_dim_x]       ; IL = cols remaining in this row

mdpy_col:
    LDBL [ELM, +]           ; BL = char, advance ELM
    JNZ @mdpy_print
    LDBL #' '               ; map 0 -> space for the renderer
mdpy_print:
    LDAH $11
    INT $10                 ; write char BL at XL, YL

    INC X
    DEC IL
    JNZ @mdpy_col

    INC Y
    DEC JL
    JNZ @mdpy_row

    POPA
    RET


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; String data                                    ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
str_open_where:
    .bytes "> Open where?", 13, 10, 0
str_NORTH:
    .bytes "NORTH", 0
str_EAST:
    .bytes "EAST", 0
str_SOUTH:
    .bytes "SOUTH", 0
str_WEST:
    .bytes "WEST", 0
str_OPEN_DOOR:
    .bytes "  OPEN DOOR ", 0
str_crlf:
    .bytes #13, #10, #0

obj.sda

; obj.sda
; create and such for game objects (items and monsters).

; Object structure (offset includes 6 byte INSQUE header)
.equ OBJ_FLINK  0        ; forward-link (managed by INSQUE)
.equ OBJ_BLINK  3        ; back-link (managed by INSQUE)
.equ OBJ_ID     6        ; 2 bytes, max 64k objects
.equ OBJ_TYPE   8        ; 2 bytes; ex. 0 = nothing, 1 = wall, 2=gold, etc.
.equ OBJ_X      10       ; 2 bytes; x position on map
.equ OBJ_Y      12       ; 2 bytes; x position on map
.equ OBJ_GLYPH  14       ; 1 byte; what it looks like (char)
.equ OBJ_VIS    15       ; 1 byte; what it looks like on the map (char)
.equ OBJ_NAME   16       ; 16 bytes + 1 zero (16 max len str)
.equ OBJ_DATA1  33       ; 2 bytes (data 1, ex. gold value)
.equ OBJ_DATA2  35       ; 2 bytes (data 1, ex. gold value)

.equ OBJS_BASE  $00F000  ; objects data starts here
.equ OBJS_END   $00FFFF  ; last byte for space
.equ OBJ_LEN    37       ; record length for an OBJ including 6 byte header

objs_ptr:
    .bytes 0, 0, 0        ; PTR to last valid object
objs_max:
    .bytes 0, 0           ; max number of objects possible to have

str_gold:
    .bytes "gold", 0

obj_ids:
    .bytes 1, 0 ; Start at ID = 1 (0 means, not an object).

init_objects:
    ; Write blank HEAD node at @OBJS_BASE (this clears the whole list)
    LDELM @OBJS_BASE        ; ELM = node ptr
    LDFLD @OBJS_BASE        ; FLD = FLINK/BLINK ptr
    STELM [FLD, +]          ; write FLINK and advance to BLINK
    STELM [FLD]             ; write BLINK (and leave FLD pointed at HEAD.BLINK)

    ADD ELM, #6             ; advance past head to first data node space.

    LDGLK @OBJS_END
    SUB GLK, @OBJ_LEN
    INC GLK                 ; GLK now equals first invalid address

init_obj_loop:
    ; 1. test if there's enough memory for another object
    CMP GLK, ELM            ; Will we overflow memory if we initialize an object here?
    JNC @init_obj_done      ; Yes, so exit (ELM >= GLK == CF).

    ; 2. initialize this object space
    LDI @OBJ_ID
    STB [ELM + I]             ; Write id = 0 for this record
    INSQUE ELM, [FLD]         ; FLD is a ptr to HEAD.BLINK; Append to list

    ; 3. Record number of objects we've initialized
    LDA [@objs_max]
    INC A
    STA [@objs_max]

    ; 4. loop
    ADD ELM, @OBJ_LEN       ; point to next object
    JMP @init_obj_loop      ; loop

init_obj_done:
    LDELM [@OBJS_BASE]      ; Load HEAD.FLINK (Point to first object on list)
    RET

create_gold:
    ; Find a free space in the object list
    LDA #0
    LDELM [@OBJS_BASE]  ; start scanning at HEAD.FLINK (i.e. the first object)
    LDI @OBJ_ID
    SCANQUE ELM, A, I
    JNZ @create_gold_oom

    ; Since we're using a sentinel node, if the sentinel is a match it means there was no valid node.
    CMP ELM, @OBJS_BASE
    JZ @create_gold_oom

    LDA [@obj_ids]
    LDI @OBJ_ID
    STA [ELM+I]
    INC A
    STA [@obj_ids]

    LDA #1              ; obj type 1 = gold
    LDI @OBJ_TYPE
    STA [ELM+I]

    ;; Now that we'e saved type, lets get its X, Y and amount data.
    LDAH $00     ; 16 bit random number
    INT 0x13     ; math servics
    MOD B, #100
    INC B       ; amount is now 1 to 100.
    LDI @OBJ_DATA1
    STB [ELM+I]

    LDK [@map1_dim]
create_gold_xy:
    LDAH $00     ; 16 bit random number
    INT 0x13
    MOD B, KL
    MOV X, B

    LDAH $00     ; 16 bit random number
    INT 0x13
    MOD B, KH
    MOV Y, B

    CALL @is_walkable
    JNC @create_gold_xy

    ; store final x,y location
    LDI @OBJ_X
    STX [ELM+I]

    LDI @OBJ_Y
    STY [ELM+I]

    ; Add GLYPH and VIS.
    LDAL #'$'
    LDI @OBJ_GLYPH
    STAL [ELM+I]
    LDI @OBJ_VIS
    STAL [ELM+I]

    CLC         ; no error
    RET         ; return

create_gold_oom:
    SEC     ; set carry on error
    RET

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Draw all the items that are placed on the map.
;;
draw_items:
    LDDL [@cam_origin_x]       ; DL = I_start, DH = J_start
    LDDH [@cam_origin_y]       ; DL = I_start, DH = J_start

    LDT [@disp_dim]         ; TL, TH for upper-bound additions
    LDELM @OBJS_BASE
draw_items_loop:
    LDELM [ELM]
    CMP ELM, @OBJS_BASE
    JZ @draw_items_done

    LDI @OBJ_ID
    LDA [ELM+I]
    JZ @draw_items_loop

    LDI @OBJ_TYPE
    LDA [ELM+I]
    CMP A, #1                  ; OBJ_TYPE_GOLD — this filter is the fix
    JNZ @draw_items_loop

    LDI @OBJ_X
    LDX [ELM+I]
    JZ @draw_items_loop
    LDI @OBJ_Y
    LDY [ELM+I]
    JZ @draw_items_loop

    ; is (XL, YL) inside the viewport?
    CMP XL, DL
    JNC @draw_items_loop    ; XL < I_start (off-screen left)
    MOV AL, DL
    ADD AL, TL
    CMP XL, AL
    JC  @draw_items_loop    ; XL >= I_start + TL (off-screen right)

    CMP YL, DH
    JNC @draw_items_loop    ; YL < J_start (off-screen up)
    MOV AL, DH
    ADD AL, TH
    CMP YL, AL
    JC  @draw_items_loop    ; YL >= J_start + TH (off-screen down)

    ; xlate map to screen
    SUB XL, DL
    ADD XL, #1              ; play_x_offset (mirror draw_world's screen_x)
    SUB YL, DH
    ADD YL, #1              ; play_y_offset (use whatever draw_world uses)

    ; draw
    LDI @OBJ_VIS
    LDBL [ELM+I]
    LDAH $11
    INT $10
    JMP @draw_items_loop
draw_items_done:
    RET

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Pick up items
;; Picks up the first pile of gold where the player is standing.
;;
pick_up_items:
    LDELM @OBJS_BASE         ; Start at first object

pick_up_items_loop:
    LDELM [ELM]              ; Traverse to next object
    CMP ELM, @OBJS_BASE
    JZ @pick_up_items_done   ; End loop if we returned to HEAD node.

    LDI @OBJ_ID
    LDA [ELM+I]              ; get object ID.
    JZ @pick_up_items_loop   ; Skip non-initialized/empty objects

    ; Does this item match the player's location?
    LDI @OBJ_X
    LDX [ELM+I]              ; object x

    LDIL [@PX]               ; player x
    CMP IL, XL
    JNZ @pick_up_items_loop  ; No match, keep looking.

    LDI @OBJ_Y
    LDY [ELM+I]              ; objecy y

    LDJL [@PY]               ; player y
    CMP JL, YL
    JNZ @pick_up_items_loop  ; No match, keep looking.

    ; Oh, there's an item here!
    LDI @OBJ_TYPE
    LDA [ELM+I]
    CMP A, #1                ; is it gold?
    JNZ @pick_up_items_loop  ; No, keep looking.

    ; How much gold is it?
    LDI @OBJ_DATA1
    LDA [ELM+I]
    LDB [@player_score]
    ADD B, A                 ; Add one point for every gold.
    STB [@player_score]      ; Save score.

    LDA #0
    LDI @OBJ_ID              ; Remove the gold object from the game
    STA [ELM+I]              ; by zeroing it's ID (thats all!)

pick_up_items_done:
    RET

talk.sda

; Talk.sda
; handle talking.
; Right now you can only talk to a monster, unfortunately.
; Or an item?

;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; do_talk()
;; This started as a copy of open_door
;;

do_talk:
    ; Show "Talk where?"
    LDELM @str_talk_where
    CALL @print_msg
    CALL @msg_display

    ; Ask player for key
    LDAH $02    ; Blocking GETKEY -> AL
    INT $10

    ;; Add the player's key as entropy.
    LDAH $02
    INT 0x13

    ; Uppercase: if AL >= 'a' and AL < 'z'+1, subtract 32
    CMP AL, #97
    JNC @talk_check_keys      ; AL < 'a', skip
    CMP AL, #123
    JC @talk_check_keys       ; AL >= '{', skip
    SUB AL, #32

talk_check_keys:
    ; Check direction given
    CMP AL, #'H'
    JZ @talk_west
    CMP AL, #'J'
    JZ @talk_south
    CMP AL, #'K'
    JZ @talk_north
    CMP AL, #'L'
    JZ @talk_east

    CMP AL, #128
    JZ @talk_west
    CMP AL, #129
    JZ @talk_south
    CMP AL, #130
    JZ @talk_north
    CMP AL, #131
    JZ @talk_east

    ; Bad direction -- ignore.

    RET

talk_north:
    ; Show "talk north"
    LDELM @str_TALK
    CALL @print_msg
    LDELM @str_NORTH
    CALL @print_msg
    LDELM @str_crlf
    CALL @print_msg

    LDI #0
    LDJ #0
    LDK #0
    LDL #1
    JMP @talk_go

talk_south:
    LDELM @str_TALK
    CALL @print_msg
    LDELM @str_SOUTH
    CALL @print_msg
    LDELM @str_crlf
    CALL @print_msg

    LDI #0
    LDJ #1
    LDK #0
    LDL #0
    JMP @talk_go

talk_west:
    LDELM @str_TALK
    CALL @print_msg
    LDELM @str_WEST
    CALL @print_msg
    LDELM @str_crlf
    CALL @print_msg

    LDI #0
    LDJ #0
    LDK #1
    LDL #0
    JMP @talk_go

talk_east:
    LDELM @str_TALK
    CALL @print_msg
    LDELM @str_EAST
    CALL @print_msg
    LDELM @str_crlf
    CALL @print_msg

    LDI #1
    LDJ #0
    LDK #0
    LDL #0
    JMP @talk_go

talk_go:
    LDX #0
    LDY #0
    LDXL [@PX]
    LDYL [@PY]
    ADD XL, IL
    ADD YL, JL
    SUB XL, KL
    SUB YL, LL
    CALL @get_glyph

    CMP AL, #' '
    JZ @talk_air
    CMP AL, #'#'
    JZ @talk_wall
    CMP AL, #'+'
    JZ @talk_cdoor
    CMP AL, #'-'
    JZ @talk_odoor
    CMP AL, #'r'
    JZ @talk_rat
    CMP AL, #'s'
    JZ @talk_snake
    CMP AL, #'x'
    JZ @talk_spider

    ;; bad command? ignore.
    RET

talk_air:
    LDELM @str_ytt
    CALL @print_msg
    LDELM @str_talk_air
    CALL @print_msg
    RET

talk_wall:
    LDELM @str_ytt
    CALL @print_msg
    LDELM @str_talk_wall
    CALL @print_msg
    RET

talk_cdoor:
    LDELM @str_ytt
    CALL @print_msg
    LDELM @str_talk_cdoor
    CALL @print_msg
    RET

talk_odoor:
    LDELM @str_ytt
    CALL @print_msg
    LDELM @str_talk_odoor
    CALL @print_msg
    RET

talk_rat:
    LDELM @str_ytt
    CALL @print_msg
    LDELM @str_talk_rat
    CALL @print_msg
    RET

talk_snake:
    LDELM @str_ytt
    CALL @print_msg
    LDELM @str_talk_snake
    CALL @print_msg
    RET

talk_spider:
    LDELM @str_ytt
    CALL @print_msg
    LDELM @str_talk_spider
    CALL @print_msg
    RET


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; talk strings

str_talk_where:
    .bytes " > Talk where? ", 13, 10, 0

str_TALK:
    .bytes "  TALK ", 0

str_ytt:
    .bytes " You talk to ", 0

str_talk_air:
    .bytes "the ", 13, 10, " air. But no-one", 13, 10, " is there!", 13, 10, 0

str_talk_wall:
    .bytes "the ", 13, 10, " wall. It's not ", 13, 10, " listening.", 13, 10, 0

str_talk_cdoor:
    .bytes "the ", 13, 10, " closed door. ", 13, 10, 32, 34,"Open sesame!", 34, 13, 10, 0

str_talk_odoor:
    .bytes "the ", 13, 10, " open door. Nothing happens.... YET!", 13, 10, 0

str_talk_rat:
    .bytes "the ", 13, 10, " rat. It squeaks", 13, 10, " in anger!", 13, 10, 0

str_talk_snake:
    .bytes "the ", 13, 10, " snake. It hisses", 13, 10, " dangerously!", 13, 10, 0

str_talk_spider:
    .bytes "the ", 13, 10, " spider. It moves", 13, 10, " closer!", 13, 10, 0

combat.sda

player_obj:
    ; 40 bytes, about sizeof(OBJ)
    .bytes 0,0,0,0,0,0,0,0,0,0
    .bytes 0,0,0,0,0,0,0,0,0,0
    .bytes 0,0,0,0,0,0,0,0,0,0
    .bytes 0,0,0,0,0,0,0,0,0,0

str_you:
    .bytes "You ", 0
str_the:
    .bytes "The ", 0
str_attack:
    .bytes "attack for 1 damage!", 13, 10, 0   ; player verb
str_attacks:
    .bytes "attacks for 1 damage!", 13, 10, 0  ; monster verb
str_rat:
    .bytes "rat ", 0
str_snake:
    .bytes "snake ", 0
str_spider:
    .bytes "spider ", 0
str_unknown:
    .bytes "thing ", 0
str_dies:
    .bytes "dies!", 13, 10, 0
str_you_die:
    .bytes "You die!", 13, 10, 0

copy_player_to_obj:
    PUSH ELM
    PUSH A
    PUSH I

    LDELM @player_obj

    LDI @OBJ_DATA1
    LDA [@player_hp]
    STA [ELM+I]

    LDI @OBJ_DATA2
    LDA [@player_str]
    STA [ELM+I]

    POP I
    POP A
    POP ELM
    RET

copy_obj_to_player:
    PUSH ELM
    PUSH A
    PUSH I
    LDELM @player_obj

    LDI @OBJ_DATA1
    LDA [ELM+I]
    STA [@player_hp]

    LDI @OBJ_DATA2
    LDA [ELM+I]
    STA [@player_str]
    POP I
    POP A
    POP ELM
    RET

print_mon_name:
    PUSH ELM
    PUSH A
    PUSH I
    LDI @OBJ_VIS
    LDAL [ELM+I]
    CMP AL, #'r'
    JZ  @pmn_rat
    CMP AL, #'s'
    JZ  @pmn_snake
    CMP AL, #'x'
    JZ  @pmn_spider
    LDELM @str_unknown
    JMP @pmn_emit
pmn_rat:
    LDELM @str_rat
    JMP @pmn_emit
pmn_snake:
    LDELM @str_snake
    JMP @pmn_emit
pmn_spider:
    LDELM @str_spider
pmn_emit:
    CALL @print_msg
    POP I
    POP A
    POP ELM
    RET



;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Call with:
;; ELM is the attacker
;; FLD is the defender
;; Note: If player is attacking or defending,
;;       pass @player_obj in ELM or FLD as appropriate.
;;       If the player is not involved, syncing doesn't hurt.
;;
do_combat:
    ; sync
    CALL @copy_player_to_obj

combat_damage:
    ; part 2: damage
    LDI @OBJ_DATA1
    LDA [FLD+I]
    JZ  @combat_msg                   ; defender already 0 (defensive)
    DEC AL
    STAL [FLD+I]                      ; HP -= 1, stored (may be 0)

combat_msg:
    CMP ELM, @player_obj
    JZ  @combat_msg_player
    PUSH ELM
    LDELM @str_the
    CALL @print_msg
    POP ELM

    CALL @print_mon_name              ; ELM = attacker
    PUSH ELM
    LDELM @str_attacks
    CALL @print_msg
    POP ELM
    JMP @combat_check_dead

combat_msg_player:
    PUSH ELM
    LDELM @str_you
    CALL @print_msg
    LDELM @str_attack
    CALL @print_msg
    POP ELM

combat_check_dead:
    LDI @OBJ_DATA1
    LDA [FLD+I]
    JNZ @combat_post
    CALL @kill_obj

combat_post:
    CALL @copy_obj_to_player

combat_done:
    RET


kill_obj:
    ; Monster dies
    ; player will be handled in main loop
    PUSH ELM                         ; save caller's attacker
    MOV ELM, FLD                    ; ELM = victim for everything below

    PUSH ELM
    LDELM @str_the
    CALL @print_msg
    POP ELM
    CALL @print_mon_name              ; reads OBJ_VIS via ELM = victim
    PUSH ELM
    LDELM @str_dies
    CALL @print_msg
    POP ELM

    LDI @OBJ_ID
    LDA #0
    STA [ELM+I]                       ; free the slot

    POP ELM                           ; restore attacker
    RET


kill_player:
    PUSH ELM
    LDELM @str_you_die
    CALL @print_msg
    POP ELM

    ; CLS
    LDAH $10
    INT $10

    ; Print "You died."
    LDBLX @msg_died
    LDAH $66
    INT $05

    POP ELM ; get rid of return address from CALL @draw_map game loop
    RET     ; exit program.

sd/rogueima_i_mvp-4.1778431627.txt.gz · Last modified: by appledog

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki