User Tools

Site Tools


sd: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:writing_games_in_assembly_language [2026/05/10 10:33] appledogsd:writing_games_in_assembly_language [2026/05/10 16:36] (current) appledog
Line 1076: Line 1076:
     CALL @draw_borders     CALL @draw_borders
     CALL @draw_world     CALL @draw_world
-    CALL @draw_player 
     CALL @draw_mobs     CALL @draw_mobs
 +    CALL @draw_player
     CALL @draw_stats     CALL @draw_stats
     CALL @draw_msgs     CALL @draw_msgs
Line 1662: Line 1662:
     JZ @draw_items_loop    ; Skip non-initialized/empty objects     JZ @draw_items_loop    ; Skip non-initialized/empty objects
  
 +    LDI @OBJ_TYPE
 +    LDA [ELM+I]
 +    CMP A, #1
 +    JNZ @draw_items_loop         ; not amn item. skip
 +    
     ; Does this item have an X and Y position?     ; Does this item have an X and Y position?
     LDI @OBJ_X     LDI @OBJ_X
Line 2112: Line 2117:
     CALL @draw_world     CALL @draw_world
     ;CALL @draw_items     ;CALL @draw_items
-    ;CALL @draw_player 
     ;CALL @draw_mobs     ;CALL @draw_mobs
 +    ;CALL @draw_player
     CALL @draw_stats     CALL @draw_stats
     CALL @draw_msgs     CALL @draw_msgs
Line 3281: Line 3286:
     PUSH B     PUSH B
  
-    ; --- Default: map tile glyph ---+    ; map tile glyph
     LDK [@map1_dim]     LDK [@map1_dim]
     MOV Z, Y     MOV Z, Y
Line 3290: Line 3295:
     LDAL [ELM]              ; AL = map glyph (default return)     LDAL [ELM]              ; AL = map glyph (default return)
  
-    ; --- Override if any object sits at (X, Y) ---+    ; Override if any object sits at (X, Y)
     LDFLD @OBJS_BASE     LDFLD @OBJS_BASE
 get_glyph_obj_loop: get_glyph_obj_loop:
     LDFLD [FLD]     LDFLD [FLD]
     CMP FLD, @OBJS_BASE     CMP FLD, @OBJS_BASE
-    JZ @get_glyph_done      ; wrapped to head, no match → keep map glyph+    JZ @get_glyph_done      ; wrapped to head, no matchkeep map glyph
  
     LDI @OBJ_ID     LDI @OBJ_ID
Line 3311: Line 3316:
     JNZ @get_glyph_obj_loop     JNZ @get_glyph_obj_loop
  
-    ; Match — override AL with the object's VIS, then exit loop+    ; Match will override AL with the object's VIS, then exit loop
     LDI @OBJ_VIS     LDI @OBJ_VIS
     LDAL [FLD+I]     LDAL [FLD+I]
Line 3381: Line 3386:
     RET     RET
 </codify> </codify>
 +
 +==== move_mon
 +This is where we attempt to move the monsters. Don't forget to move them //before// you draw them in the game loop:
 +
 +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
 +
 +    RET
 +
 +Add this in, and add ''CALL @move_mon'' before ''CALL @draw_mon'' in ''draw_map:''.
 +
 +<codify armasm>
 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 +;; 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:
 +
 +    ; sign(PY - my) into DL
 +    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:
 +    ; Don't step on the player
 +    LDAL [@PX]
 +    CMP AL, XL
 +    JNZ @mm_check_obj
 +    LDAL [@PY]
 +    CMP AL, YL
 +    JZ  @move_mon_loop
 +
 +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
 +    LDI @OBJ_X
 +    STXL [ELM+I]
 +    LDI @OBJ_Y
 +    STYL [ELM+I]
 +    JMP @move_mon_loop
 +
 +mm_pop_skip:
 +    POP ELM
 +    JMP @move_mon_loop
 +
 +move_mon_done:
 +    RET
 +</codify>
 +
 +=== Combat
 +When a monster attempts to move onto a player, this is a combat situation.
 +
 +When a player attempts to move onto a monster, this is also a combat situation.
 +
 +Ideally we want to use the same code, but this will never work out in practice unless we add the player as an object in the item list (so it has data like a monster). This is actually one way of running the game, but for now we will have a special case to set up player vs monster or monster vs player. What we will do is set up a general data structure and have the code for player/monster at the start and end. That will be good enough for now.
 +
 +Let's change mon_move to call the combat function:
 +
 +<codify armasm>
 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 +;; 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
 +</codify>
 +
 +And now, the combat function. This is just an example; every attack does 1 damage to the defender. You could expand on this later.
 +
 +=== combat.sda
 +<codify armasm>
 +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.
 +</codify>
 +
 +Finally, if the player is dead, we end the game. in ''draw_map:''
 +
 +    ...
 +    CALL @draw_stats
 +    CALL @draw_msgs
 +    CALL @msg_display
 +    
 +    LDAH $50        ; VSTART
 +    INT 0x18
 +    
 +    LDA [@player_hp]
 +    CMP A, 0
 +    JZ @kill_player
 +
 +    RET
 +    
  
 == Appendix III: DungeonMaker == Appendix III: DungeonMaker
sd/writing_games_in_assembly_language.1778409193.txt.gz · Last modified: by appledog

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki