sd:writing_games_in_assembly_language
Differences
This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
| sd:writing_games_in_assembly_language [2026/05/09 14:34] – appledog | sd: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 | JZ @draw_items_loop | ||
| + | LDI @OBJ_TYPE | ||
| + | LDA [ELM+I] | ||
| + | CMP A, #1 | ||
| + | JNZ @draw_items_loop | ||
| + | | ||
| ; 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 // | The above Java code uses an // | ||
| - | === draw_world | + | ==== draw_world |
| <codify armasm> | <codify armasm> | ||
| draw_world: | draw_world: | ||
| Line 2101: | Line 2106: | ||
| </ | </ | ||
| - | === 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 '' | Next, we need the '' | ||
| - | === open_door | + | ==== open_door |
| <codify armasm> | <codify armasm> | ||
| ;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| Line 2520: | Line 2525: | ||
| </ | </ | ||
| - | Note that this function relies on '' | + | Note that this function relies on '' |
| + | ==== put_glyph | ||
| <codify armasm> | <codify armasm> | ||
| ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| Line 2875: | Line 2881: | ||
| === Improvements and optimizations | === 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 data. A head node for a world map (256x256) would require 6 banks of memory. | + | 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. |
| - | Even if you pare down the object data, and only include | + | The first solution is to only store the visible |
| - | The first solution is to only store the visible tiles in memory. Since the play window is 58x23, what if we only stored this data in memory? What if all we did was store a glyph and two bytes that pointed to an in-bank HEAD node? And, those bytes were zero if there was nothing on the tile? That' | + | There' |
| - | There' | + | Another technique is shrinking the play area. Compared to rogue, etc. this is the Bard' |
| + | |||
| + | 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' | ||
| + | |||
| + | * Basic terrain (tiles 0–8): Deep Water, Medium Water, Shallow Water, Swamp, Grasslands, Scrubland/ | ||
| + | * Special location/ | ||
| + | |||
| + | 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 | ||
| + | |||
| + | === 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' | ||
| + | |||
| + | How far can you push the machine to make it play the kind of games you want to make? | ||
| ==== Moving the monsters | ==== Moving the monsters | ||
| Line 2895: | Line 2913: | ||
| === Go forth and code great games! | === Go forth and code great games! | ||
| //Good artists copy, great 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!// | //Good artists copy, great 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!" | ||
| + | |||
| + | * [[Rogueima I MVP-4]] | ||
| == Appendix I Extending the Game | == Appendix I Extending the Game | ||
| Line 3253: | Line 3286: | ||
| PUSH B | PUSH B | ||
| - | ; --- Default: | + | ; map tile glyph |
| LDK [@map1_dim] | LDK [@map1_dim] | ||
| MOV Z, Y | MOV Z, Y | ||
| Line 3262: | 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 | + | JZ @get_glyph_done |
| LDI @OBJ_ID | LDI @OBJ_ID | ||
| Line 3283: | Line 3316: | ||
| JNZ @get_glyph_obj_loop | JNZ @get_glyph_obj_loop | ||
| - | ; Match — override AL with the object' | + | ; Match will override AL with the object' |
| LDI @OBJ_VIS | LDI @OBJ_VIS | ||
| LDAL [FLD+I] | LDAL [FLD+I] | ||
| Line 3297: | Line 3330: | ||
| </ | </ | ||
| - | == 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, |
| - | // | + | ;; |
| - | 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 |
| - | { | + | |
| - | | + | |
| - | | + | |
| - | popfreq = 200; | + | |
| - | xmax = Dice.roll(60, | + | |
| - | ymax = Dice.roll(18, | + | |
| - | fill_with_rock(); | + | LDI @OBJ_TYPE |
| - | | + | LDB [FLD+I] |
| + | CMP B, #2 ; OBJ_TYPE_MONSTER | ||
| + | JNZ @has_mon_loop | ||
| - | int loop_est = ((xmax*ymax)/ | + | LDI @OBJ_X |
| - | for (int i = 0; i < loop_est; i++) | + | LDBL [FLD+I] |
| - | { | + | CMP BL, XL |
| - | int x = Dice.roll(6,xmax-6); | + | |
| - | int y = Dice.roll(6, | + | |
| - | int d = Dice.roll(0, | + | |
| - | amap[x][y] = ' '; | + | LDI @OBJ_Y |
| - | + | | |
| - | int jmax = Dice.roll(0, | + | CMP BL, YL |
| - | 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, | + | |
| - | 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, | + | |
| - | if (isWall(x, | + | |
| - | } | + | |
| - | | + | |
| - | case 2: | + | |
| - | for (int j = 1; j <= jmax; j++)// to FOR i = 1 TO INT(RND * 5) | + | SEC |
| - | { | + | JMP @has_mon_done |
| - | if (isWall(x-j, | + | |
| - | if (isWall(x, | + | |
| - | } | + | |
| - | break; | + | |
| - | case 3: | + | has_mon_no: |
| - | for (int j = 1; j <= jmax; j++)// to FOR i = 1 TO INT(RND * 5) | + | |
| - | { | + | |
| - | if (isWall(x-j, | + | |
| - | if (isWall(x, | + | |
| - | } | + | |
| - | break; | + | |
| - | }//switch | + | |
| - | } // 150 times | + | |
| - | amap_to_level(); | + | has_mon_done: |
| - | + | POP B | |
| - | return true; | + | |
| - | | + | RET |
| - | } | + | |
| </ | </ | ||
| + | |||
| + | ==== 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 '' | ||
| + | |||
| + | <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 | ||
| + | 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 | ||
| + | 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 | ||
| + | CALL @is_walkable | ||
| + | JNC @mm_pop_skip | ||
| + | |||
| + | ; 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 | ||
| + | </ | ||
| + | |||
| + | === 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/ | ||
| + | |||
| + | 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 | ||
| + | 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 | ||
| + | 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 | ||
| + | |||
| + | CALL @do_combat | ||
| + | JMP @move_mon_loop | ||
| + | |||
| + | mm_check_obj: | ||
| + | PUSH ELM | ||
| + | CALL @has_mon | ||
| + | JC @mm_pop_skip | ||
| + | CALL @is_walkable | ||
| + | JNC @mm_pop_skip | ||
| + | |||
| + | ; 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 | ||
| + | </ | ||
| + | |||
| + | 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, | ||
| + | .bytes 0, | ||
| + | .bytes 0, | ||
| + | .bytes 0, | ||
| + | |||
| + | str_you: | ||
| + | .bytes "You ", 0 | ||
| + | str_the: | ||
| + | .bytes "The ", 0 | ||
| + | str_attack: | ||
| + | .bytes " | ||
| + | str_attacks: | ||
| + | .bytes " | ||
| + | str_rat: | ||
| + | .bytes "rat ", 0 | ||
| + | str_snake: | ||
| + | .bytes "snake ", 0 | ||
| + | str_spider: | ||
| + | .bytes " | ||
| + | str_unknown: | ||
| + | .bytes "thing ", 0 | ||
| + | str_dies: | ||
| + | .bytes " | ||
| + | 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, #' | ||
| + | JZ @pmn_rat | ||
| + | CMP AL, #' | ||
| + | JZ @pmn_snake | ||
| + | CMP AL, #' | ||
| + | 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' | ||
| + | ;; | ||
| + | do_combat: | ||
| + | ; sync | ||
| + | CALL @copy_player_to_obj | ||
| + | |||
| + | combat_damage: | ||
| + | ; part 2: damage | ||
| + | LDI @OBJ_DATA1 | ||
| + | LDA [FLD+I] | ||
| + | JZ @combat_msg | ||
| + | DEC AL | ||
| + | STAL [FLD+I] | ||
| + | |||
| + | combat_msg: | ||
| + | CMP ELM, @player_obj | ||
| + | JZ @combat_msg_player | ||
| + | PUSH ELM | ||
| + | LDELM @str_the | ||
| + | CALL @print_msg | ||
| + | POP ELM | ||
| + | |||
| + | CALL @print_mon_name | ||
| + | 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' | ||
| + | MOV ELM, FLD ; ELM = victim for everything below | ||
| + | |||
| + | PUSH ELM | ||
| + | LDELM @str_the | ||
| + | CALL @print_msg | ||
| + | POP ELM | ||
| + | CALL @print_mon_name | ||
| + | PUSH ELM | ||
| + | LDELM @str_dies | ||
| + | CALL @print_msg | ||
| + | POP ELM | ||
| + | |||
| + | LDI @OBJ_ID | ||
| + | LDA #0 | ||
| + | STA [ELM+I] | ||
| + | |||
| + | 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. | ||
| + | </ | ||
| + | |||
| + | Finally, if the player is dead, we end the game. in '' | ||
| + | |||
| + | ... | ||
| + | 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.1778337275.txt.gz · Last modified: by appledog
