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/09 13:29] 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 2004: Line 2009:
 The above Java code uses an //iterative// approach. The function we will use below is a //closed calculation//. They execute on the same basic idea and should produce identical results. I am undecided which algorithm I like better. The above Java code uses an //iterative// approach. The function we will use below is a //closed calculation//. They execute on the same basic idea and should produce identical results. I am undecided which algorithm I like better.
  
-=== draw_world+==== draw_world
 <codify armasm> <codify armasm>
 draw_world: draw_world:
Line 2101: Line 2106:
 </codify> </codify>
  
-=== draw_map+==== draw_map
 Finally let's change draw_map: to this: Finally let's change draw_map: to this:
  
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 2416: Line 2421:
 Next, we need the ''open_door:'' function: Next, we need the ''open_door:'' function:
  
-=== open_door+==== open_door
 <codify armasm> <codify armasm>
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Line 2520: Line 2525:
 </codify> </codify>
  
-Note that this function relies on ''set_glyph:'' -- this function is exactly the same as get_glyph except you change the LDAL to a STAL:+Note that this function relies on ''put_glyph:'' -- this function is exactly the same as get_glyph except you change the LDAL to a STAL.
  
 +==== put_glyph
 <codify armasm> <codify armasm>
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Line 2617: Line 2623:
 Replace ''CALL @draw_msgs'' with ''CALL @msg_display'' in ''draw_map:' in ''draw.sda''. That will print the messages; now we just have to get from //print_message// to //msg_display//! Replace ''CALL @draw_msgs'' with ''CALL @msg_display'' in ''draw_map:' in ''draw.sda''. That will print the messages; now we just have to get from //print_message// to //msg_display//!
  
-=== print_msg:+==== print_msg:
 Our first stop is the intended use case: print_msg. Our first stop is the intended use case: print_msg.
  
Line 2646: Line 2652:
  
 This function delegates actually printing to a msg_putchar function. This is a fair divvy of work. This function delegates actually printing to a msg_putchar function. This is a fair divvy of work.
-=== msg_putchar:+ 
 +==== msg_putchar:
 The putchar function simply calculates the correct position in the char-buffer and writes the ASCII code. That would be the end of it, but we need to handle special characters. Here we handle CR and LF; you could add other special characters here too. The putchar function simply calculates the correct position in the char-buffer and writes the ASCII code. That would be the end of it, but we need to handle special characters. Here we handle CR and LF; you could add other special characters here too.
  
Line 2697: Line 2704:
 </codify> </codify>
  
-=== msg_putchar_advance:+==== msg_putchar_advance:
 This is a cursor advance function but it is (mis)-named //putchar_advance// because it is intended only to be called by ''msg_putchar''. This is a cursor advance function but it is (mis)-named //putchar_advance// because it is intended only to be called by ''msg_putchar''.
  
Line 2731: Line 2738:
 </codify> </codify>
  
-=== msg_do_cr: and msg_do_lf:+==== msg_do_cr: and msg_do_lf:
 These are the carriage_return and line_feed functions. Essentially, we just increment the cursor location; but in the case of line_feed we may need to scroll the screen. These are the carriage_return and line_feed functions. Essentially, we just increment the cursor location; but in the case of line_feed we may need to scroll the screen.
  
Line 2762: Line 2769:
 </codify> </codify>
  
-=== msg_scroll:+==== msg_scroll:
 If the message buffer needs to be fed a new line, this is the function you would call. If the message buffer needs to be fed a new line, this is the function you would call.
  
Line 2817: Line 2824:
 </codify> </codify>
  
-=== msg_display:+==== msg_display:
 This is the round trip now, we're done! This is the function that will copy the message buffer onto the screen. This is the round trip now, we're done! This is the function that will copy the message buffer onto the screen.
  
Line 2873: Line 2880:
 Each of these things presents an interesting challenge to the programmer, but, these are not strictly game design concerns that depend on SDA-8516 Assembly Language, they are mechanical algorithmic processes that build almost entirely on what you already know. The one interesting thing would be random levels; I will include some level generation functions below taken from Java NetWhack; you can feel free to use them, if you can translate them into assembly! Each of these things presents an interesting challenge to the programmer, but, these are not strictly game design concerns that depend on SDA-8516 Assembly Language, they are mechanical algorithmic processes that build almost entirely on what you already know. The one interesting thing would be random levels; I will include some level generation functions below taken from Java NetWhack; you can feel free to use them, if you can translate them into assembly!
  
-Part of the challenge of writing a game is being creative and coming up with your own stuffCopyingalso, is a great form of art. If you enjoy this kind of gamego and play NetHack, or the original rogue or hack games, and modify Rogueima into something uniquely yours!+=== Improvements and optimizations 
 +Looking at the direction the code took, I would rather have some way of accessing an inventory (a list) on a per-tile basis to determine things about that tile. However, no matter how you try and arrange the data, you will never be able to have an index into a large map; it just requires too much memory. A head node for a world map (256x256) would require 6 banks of memory. That just isn't going to work on a machine with less memory! 
 + 
 +The first solution is to only store the visible tiles in memory. Since the play window is 58x23, what if we only stored the current screen (and a small buffer zone) in memory? What if all we did was store a glyph and two bytes that pointed to an in-bank HEAD node? If we used 64*28, that's 5,376 bytes. On top of this, 100 objects and 100 monsters would equal 7,400 bytes. That seems about right for such a game. If you can get the data to load and save and can spare the 12k RAM, you could have maps of unusual size. 
 + 
 +There's also the zelda technique, where you just load a new map chunk and scrol into it when you move past the edge; this is a fun technique too, and allows us to deal solely with the play area. 
 + 
 +Another technique is shrinking the play area. Compared to rogueetc. this is the Bard's Tale and Ultima 4 solution; The Bard's tale had a small play window in the upper left of the screen; Ultima 4 had an 11x11 play window. 11x11! Even if you stored the 33x33 tiles around the player with HEAD pointers, that's just 3,267 bytes! Storing maps in 11x11 chunks is very interesting. When you think back to how Ultima 4 and 5 workedisn't it true that monsters more than 2 screens away would tend to disappear? That is a clue to the method they used to cache the world map. Additionally, most dungeon combat and dungeon rooms were one-screen. This saved a lot of memory, and greatly contributed to the game's look and feel. You didn't notice it so much, since the towns and world map were much larger. 
 + 
 +A third solution is splitting the difference. if the top 16 tiles on the map account for ~95% of the data, you can save space by bit packing those tiles into half a byte, and then including a list of special objects (ex. a bridge, a moongate, a town/abbey) in a separate list of objects to be drawn on the map. This allows you to store a huge world map (ex. 256*256) in just 32k. You could even get it down to 24k if you only had 8 main terrain types. How many terrain types did Ultima 4 use? Glancing at a //shapes// file there appeaer to be only 22-23 overworld tiles, possibly a few more; It would be easy to split some tiles off into a special list (like towns, bridges and docks, shrines, and such). In fact, it seems as if Ultima 4's world map was designed with this as a fallback: 
 + 
 +* Basic terrain (tiles 0–8): Deep Water, Medium Water, Shallow Water, Swamp, Grasslands, Scrubland/Brush, Forest, Hills, Mountains. 
 +* Special location/overlay tiles (commonly placed on the map): Dungeon Entrance (9), Town (10), Castle (11), Village (12), Ruins (29), Shrine (30), various moongates, bridges, Lord British’s castle pieces, etc. 
 + 
 +Having 16 basic tiles means you can bit pack a 256x256 map into 32k. Next, you can have a small list of extra overlay tiles. If you cut the map into 4x4 (16 chunks), each chunk would fit into 2k memory. Then an additional list of over 200 monsters and items could exist in this chunk, using //just// another 2k of memory. The object struct for this would be 10 bytes; 2 bytes for ID2 bytes for NEXT_ID (for containers), 2 bytes for X and Y in the chunk, 1 byte type, 3 bytes data.  
 + 
 +=== Large, active worlds 
 +If you absolutely want to make a very large, active world (civilization clone?) think about what you must absolutely do with each thing. For example if you have to keep track of a crowd of 20,000 people, and they have four behavior states (ex. chopping wood, mining gold, farming, bulding) then each person's behavior strategy can be stored in 2 bits. This means you need 40,000 bits, or 5k. If you only have 16 types of object in your game (or 8) consider bit packing for that as well. Bit packing is cheap; computing it (decoding it) is also cheap, in general. 
 + 
 +How far can you push the machine to make it play the kind of games you want to make? 
 + 
 +==== Moving the monsters 
 +It would be very easy to move the monsters. Simplyjust calculate the difference between each monster's position and the player's position and have the monster try to move towards the player. That's all. 
 + 
 +Combat occurrs when a monster or player tries to occupy the other's space. Monsters will naturally attack players by trying to move onto their square. It's a simple function, just check if the monster is moving onto the player's co-ordinates, or if there is a monster where the player is moving. A function like ''tile_hasmon'' would be very useful here; if the tile has a monster it returns a pointer to the monster (ex. in FLD). Then you can write a combat routine. 
 + 
 +==== Levels 
 +In the same way you would save data for larger maps you can save and load entire levels. To do this you would want to rewrite parts of the game to use map_ map1_ and then make map_ a pointer to an area where you load and save maps. Or do this with map1 and then make a map2 that you can load from disk. Either way, adding a load and save map function is vital for traversing up and down dungeon levels. 
 + 
 +A save game function is not far away from a save level/save map function. 
 + 
 +=== Go forth and code great games
 +//Good artists copygreat artists steal. This means if you want to be good, write a game similar to an existing popular game. But if you want to be great, you must take the ideas you get from that game and make them your own ideas -- creatively! Part of the challenge of writing a game is being creative and coming up with your own stuff. But to do this you need inspiration. Play the games of old; hack, rogue, adventure, zork, The Bard's Tale, Ultima -- The Legend of Zelda -- and you will understand what you must do!// 
 + 
 +== Rogueima I MVP-4 Source Code 
 +Here is the complete source code for the tutorial (including the extensions in Appendix I). 
 + 
 +To compile this code, concatenate the files in any order (rogueima.sda must be first). Then INCOPY the file and type: 
 + 
 +    as allfiles.sda rogueima 
 + 
 +This will assemble the game. Then you can play it by typing: 
 + 
 +    SYS 49152 
 + 
 +I may return to soup it up a little with some easy monster AI and a quest ("Thou must kill... a viper!") but this is really only intended as an instructional game to show you how to program in assembly. If you'd like to continue from here, you may wish to check out another game in the "Writing Games in Assembly Language" series: [[The Rogue's Tale I]]. Coming soon! 
 + 
 +* [[Rogueima I MVP-4]]
  
 == Appendix I Extending the Game == Appendix I Extending the Game
Line 2948: Line 3002:
 </codify> </codify>
  
-== Extending and refactoring+=== Extending and refactoring
 Notice that draw_mobs is essentially a cut and paste of draw_items. This suggests that it is possible to refactor some of the code into helper functions. Same thing with create_items vs. spawn_monsters. Same idea. Notice that draw_mobs is essentially a cut and paste of draw_items. This suggests that it is possible to refactor some of the code into helper functions. Same thing with create_items vs. spawn_monsters. Same idea.
  
 Next up we will do the talk command. The same idea will apply; talk is essentially a copy of the open command. Next up we will do the talk command. The same idea will apply; talk is essentially a copy of the open command.
  
-== The 'Talk' command+=== The 'Talk' command
 Did you add the talk command to the key dispatcher yet? Did you add the talk command to the key dispatcher yet?
  
Line 3220: Line 3274:
 </codify> </codify>
  
-== Fixing a bug+=== Fixing a bug
 Notice how when you try to talk to a monster, it doesn't work? That's because get_glyph only looks at the map. Let's add a check for any item or monster. Notice how when you try to talk to a monster, it doesn't work? That's because get_glyph only looks at the map. Let's add a check for any item or monster.
  
Line 3232: 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 3241: 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 3262: 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 3276: Line 3330:
 </codify> </codify>
  
-== Appendix II: L-Cave algorithm +== Appendix II: Monster Combat 
-From Netwhack v0.7.2.+Just one more thing! Here's how to make monsters move around and attack you.
  
-* //Note: The L-Cave algorithm (also known as the "Lazy Cave" algorithm) for roguelike procedural map generation was popularized by Herbert Wolverson.//+First we need to know if a square has a monster or not. This means, when we try to move onto a square, we test if that square has a monster. If it does, we attack that monster.
  
-<codify Java> +==== has_mon 
-/* +This function tells us if there is a monster on the tile.
- * LCave.java +
- * +
- * Created on December 27, 2005 +
- * +
- */+
  
-// +<codify armasm> 
-// LCave.java +;;;;;;;;;;;;;;;;;; 
-// Create random caves using L shaped cookie cutters. +;; has_mon(x,y) 
-// +;; 
-package netwhack.world.mapgen;+;; Returns CARRY SET if monster. 
 +;; Returns monster pointer in FLD. 
 +;
 +has_mon: 
 +    PUSH I 
 +    PUSH B
  
-import usrlib.Dice;+    LDFLD @OBJS_BASE 
 +has_mon_loop: 
 +    LDFLD [FLD] 
 +    CMP FLD, @OBJS_BASE 
 +    JZ @has_mon_no
  
-public class LCave extends DungeonMaker +    LDI @OBJ_ID 
-{ +    LDB [FLD+I] 
-    public boolean generate() +    JZ @has_mon_loop           uninitialized slot
-    +
-        popfreq = 200; +
-        xmax = Dice.roll(60,79); +
-        ymax = Dice.roll(18,21);+
  
-        fill_with_rock(); +    LDI @OBJ_TYPE 
-        bound_with_undiggable_walls();+    LDB [FLD+I] 
 +    CMP B, #2                   OBJ_TYPE_MONSTER 
 +    JNZ @has_mon_loop
  
-        int loop_est = ((xmax*ymax)/5); +    LDI @OBJ_X 
-        for (int i = 0; i < loop_est; i++) +    LDBL [FLD+I] 
-        { +    CMP BLXL 
-            int x = Dice.roll(6,xmax-6);    // INT(RND * 59) + 6 +    JNZ @has_mon_loop
-            int y = Dice.roll(6,ymax-6);    // INT(RND * 9) + 6 +
-            int d = Dice.roll(0,3);     // INT(RND * 4)+
  
-            amap[x][y] = ' '; +    LDI @OBJ_Y 
-             +    LDBL [FLD+I
-            int jmax = Dice.roll(0,3); +    CMP BLYL 
-            switch(d) +    JNZ @has_mon_loop
-            { +
-                case 0: +
-                    for (int j = 1; j <= jmax; j++)// to FOR i = 1 TO INT(RND * 5) +
-                    { +
-                        if (isWall(x+j,y)) amap[j][y] = ' '; +
- if (isWall(x,y-j)) amap[x][y - j] = ' '; +
-                    } +
-                    break; +
-  +
-                case 1: +
-                    for (int j = 1; j <= jmax; j++)// to FOR i = 1 TO INT(RND * 5) +
-                    { +
-                        if (isWall(x+j,y)) amap[x + j][y] = ' '; +
- if (isWall(x,y+j)) amap[x][y + j] = ' '; +
-                    } +
-                    break;+
  
-                case 2: +    Match returns pointer in FLD, CF=1 
-                    for (int j = 1j <= jmax; j++)// to FOR i = 1 TO INT(RND * 5) +    SEC 
-                    { +    JMP @has_mon_done
-                        if (isWall(x-j,y)) amap[x - j][y] = ' '; +
- if (isWall(x,y-j)) amap[x][y - j] = ' '; +
-                    } +
-                    break;+
  
-                case 3+has_mon_no
-                    for (int j = 1j <= jmaxj++)// to FOR i = 1 TO INT(RND * 5) +    CLC                          no monsterFLD unchanged.
-                    { +
-                        if (isWall(x-j,y)) amap[x - j][y] = ' '; +
- if (isWall(x,y+j)) amap[x][y + j] = ' '; +
-                    } +
-                    break; +
-            }//switch +
-        } // 150 times+
  
-        amap_to_level(); +has_mon_done: 
-   +    POP B 
-        return true; +    POP I 
-    } +    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.1778333365.txt.gz · Last modified: by appledog

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki