From 7014692d71019f34bfc6ad2201e80fcf135ac812 Mon Sep 17 00:00:00 2001 From: Robin Chappatte Date: Fri, 24 Apr 2026 07:13:59 +0200 Subject: [PATCH] create repository with functional basic implementation --- assets/map.ppm | Bin 0 -> 552 bytes assets/sprites/coin.ppm | Bin 0 -> 130 bytes assets/sprites/empty.ppm | Bin 0 -> 130 bytes assets/sprites/ghost.ppm | Bin 0 -> 130 bytes assets/sprites/placeholder.ppm | Bin 0 -> 130 bytes assets/sprites/robot.ppm | Bin 0 -> 130 bytes assets/sprites/wall_corner.ppm | Bin 0 -> 130 bytes assets/sprites/wall_end.ppm | Bin 0 -> 130 bytes assets/sprites/wall_straight.ppm | Bin 0 -> 130 bytes build.ts | 205 +++++++++++++++++ readme.md | 56 +++++ src/consts/colors.asm | 9 + src/consts/game.asm | 17 ++ src/consts/keyboard.asm | 12 + src/consts/map.asm | 10 + src/consts/offsets.asm | 16 ++ src/consts/scene.asm | 8 + src/consts/screen.asm | 26 +++ src/consts/sprites.asm | 18 ++ src/lib/drawing.asm | 299 +++++++++++++++++++++++++ src/lib/game.asm | 26 +++ src/lib/sprite_rotation.asm | 171 ++++++++++++++ src/main.asm | 369 +++++++++++++++++++++++++++++++ visuals.md | 23 ++ 24 files changed, 1265 insertions(+) create mode 100644 assets/map.ppm create mode 100644 assets/sprites/coin.ppm create mode 100644 assets/sprites/empty.ppm create mode 100644 assets/sprites/ghost.ppm create mode 100644 assets/sprites/placeholder.ppm create mode 100644 assets/sprites/robot.ppm create mode 100644 assets/sprites/wall_corner.ppm create mode 100644 assets/sprites/wall_end.ppm create mode 100644 assets/sprites/wall_straight.ppm create mode 100755 build.ts create mode 100755 readme.md create mode 100644 src/consts/colors.asm create mode 100644 src/consts/game.asm create mode 100644 src/consts/keyboard.asm create mode 100644 src/consts/map.asm create mode 100644 src/consts/offsets.asm create mode 100644 src/consts/scene.asm create mode 100644 src/consts/screen.asm create mode 100644 src/consts/sprites.asm create mode 100644 src/lib/drawing.asm create mode 100644 src/lib/game.asm create mode 100644 src/lib/sprite_rotation.asm create mode 100644 src/main.asm create mode 100644 visuals.md diff --git a/assets/map.ppm b/assets/map.ppm new file mode 100644 index 0000000000000000000000000000000000000000..f2d61ebc07e9d7f27da88a69833d279ae4b76113 GIT binary patch literal 552 zcmWGA<5E^|E=o--Nlj5ms#I|I^bJrbOD!tS%+FIW)-%#GQ3&w!RVc_QP1nuL<1#c= zFf`;cGBxG;&%i($`p@v67z8mEs1j~IK{ni2pcXhA!ax>5HW_Xvgh7xCF%7O5jZ2=% Y5JQk%h$(_>GLdY!+we0WrV&j706Ag-v;Y7A literal 0 HcmV?d00001 diff --git a/assets/sprites/coin.ppm b/assets/sprites/coin.ppm new file mode 100644 index 0000000000000000000000000000000000000000..70d89cb7316962900724e904ef3479573dcf5cd9 GIT binary patch literal 130 zcmWGA<5E^|E=o--Nlj5ms#I|I^bJrbOD!tS%+FIW)-%#GQ3&w!RVc_QP1nuL<1$q+ T70020=4>|w< literal 0 HcmV?d00001 diff --git a/assets/sprites/ghost.ppm b/assets/sprites/ghost.ppm new file mode 100644 index 0000000000000000000000000000000000000000..7296c19c1b0f7c2132b50aa2162be9d17536e6bf GIT binary patch literal 130 zcmWGA<5E^|E=o--Nlj5ms#I|I^bJrbOD!tS%+FIW)-%#GQ3&w!RVc_QP1nuL<1$q+ fWV$3J literal 0 HcmV?d00001 diff --git a/assets/sprites/placeholder.ppm b/assets/sprites/placeholder.ppm new file mode 100644 index 0000000000000000000000000000000000000000..2b76098314466eee33989bdce9e0ccebe72ec481 GIT binary patch literal 130 zcmWGA<5E^|E=o--Nlj5ms#I|I^bJrbOD!tS%+FIW)-%#GQ3&w!RVc_QP1nuL<1$q+ c1Z65`l98^A8;? literal 0 HcmV?d00001 diff --git a/assets/sprites/wall_end.ppm b/assets/sprites/wall_end.ppm new file mode 100644 index 0000000000000000000000000000000000000000..3210f7644b6c31c93d1815a10ed12ab232c7ec48 GIT binary patch literal 130 zcmWGA<5E^|E=o--Nlj5ms#I|I^bJrbOD!tS%+FIW)-%#GQ3&w!RVc_QP1nuL<1$q+ W, lineLength = 0): string { + lineLength ??= Infinity; + + const stringValues = []; + for (let i = 0; i < data.length - 2; i += 3) { + const pixelValue = (data[i] << 16) + (data[i+1] << 8) + (data[i+2]) + + stringValues.push(matchingStrings[pixelValue]); + } + + const lines = []; + if (lineLength === 0) { + lines.push(stringValues.join("\t")); + } else { + for (let i = 0; i < stringValues.length; i += lineLength) { + lines.push(stringValues.slice(i, i+lineLength).join("\t")); + } + } + + return `${label}: +${lines.join("\n")} +${label}_end:` +} + +/** + * Return the image pixels data (excluding all other fields) from a given PPM file + * + * The implementation differ from the format definition (https://netpbm.sourceforge.net/doc/ppm.html) but match the file created by GIMP. + */ +async function getDataFromPPM(path: string): Promise { + const endOfComment = [ + 0x0a, + 0x0d, + ]; + const whitespaces = [ + 0x09, + 0x0a, + 0x0b, + 0x0c, + 0x0d, + 0x20, + ]; + const numbers = [ + 0x30, + 0x31, + 0x32, + 0x33, + 0x34, + 0x35, + 0x36, + 0x37, + 0x38, + 0x39, + ]; + + const hashtag = 0x23; + + const content = await Deno.readFile(path); + let offset = 0; + + // magic number + while(!whitespaces.includes(content[offset])) { + offset++; + } + + offset++; // whitespace + + // potential comment + if (content[offset] === hashtag) { + while(!endOfComment.includes(content[offset])) { + offset++; + } + + offset++; // end of comment + } + + // image width + while(numbers.includes(content[offset])) { + offset++; + } + + offset++; // whitespace + + // image height + while(numbers.includes(content[offset])) { + offset++; + } + + offset++; // whitespace + + // maximum color value + while(numbers.includes(content[offset])) { + offset++; + } + + offset++; // whitespace + + return content.slice(offset, content.length); +} \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100755 index 0000000..5fe95ae --- /dev/null +++ b/readme.md @@ -0,0 +1,56 @@ +# Turing Complete PacMan + +This program is intended to be used as "level validation" for the `IO` level (in which player add support for keyboard, timer and screen display). + +It thus should be able to run on the architecture and ISA the players have built at that point, and must be able to run smoothly on a simulation speed of `1 MHz` or above. + +## About this repository + +The `main` branch contains a minimal implementation of the game. + +The following branches contains variations, allowing comparisons of performances, visuals, gameplay, ... +- `res-2`: 256x192 screen resolution, 17x17 tile + +## Build script + +File inclusion is currently not supported by the game, so a `build.ts` script it provided that will concatenate the content of the hard-coded `.asm` files and output the result on stdout. + +It also convert the PPM assets (map and sprites) into ASM raw values representation and append it after all the code. + +Usage example: +```sh +./build.ts > /path/to/the/game/schematics/architecture/Symfony/sandbox/main.asm +``` + +The script is build over Deno's API, so you'll need to have a `deno` binary in your PATH to use it that way. + +## Assets + +PPM files that may be converted into the game's raw data format. + +### Map + +The `assets/map.ppm` file contains a representation of the initial state of the map. +The corresponding tile values are: + +PPM 8bits channels | Tile +-- | -- +`0x00_00_00` | `0` (Empty) +`0xFF_00_00` | `1` (Wall) +`0xFF_FF_00` | `2` (Coin) + +### Sprites + +The corresponding in-game color values are: + +PPM 8bits channels | In-game `RRRGGGBB` value | Name +-- | -- | -- +`0x00_00_00` | `000_000_00` | Black +`0x00_00_FF` | `000_000_11` | Blue +`0x00_FF_00` | `000_111_00` | Green +`0x77_77_77` | `010_011_10` | Gray +`0xFF_00_00` | `111_000_00` | Red +`0xFF_00_FF` | `111_000_11` | Pink +`0xFF_80_00` | `111_100_00` | Orange +`0xFF_FF_00` | `111_110_00` | Yellow +`0xFF_FF_FF` | `111_111_11` | White \ No newline at end of file diff --git a/src/consts/colors.asm b/src/consts/colors.asm new file mode 100644 index 0000000..a40c4af --- /dev/null +++ b/src/consts/colors.asm @@ -0,0 +1,9 @@ +pub const colors.black = 0b000_000_00 +pub const colors.blue = 0b000_000_11 +pub const colors.green = 0b000_111_00 +pub const colors.gray = 0b010_011_10 +pub const colors.red = 0b111_000_00 +pub const colors.pink = 0b111_000_11 +pub const colors.orange = 0b111_100_00 +pub const colors.yellow = 0b111_110_00 +pub const colors.white = 0b111_111_11 diff --git a/src/consts/game.asm b/src/consts/game.asm new file mode 100644 index 0000000..bbf69f2 --- /dev/null +++ b/src/consts/game.asm @@ -0,0 +1,17 @@ +pub const game.direction_up = 0 +pub const game.direction_right = 1 +pub const game.direction_down = 2 +pub const game.direction_left = 3 +pub const game.direction_still = 4 + +; 1_000_000_000 nano second = 0x0000_0000_3B9A_CA00 +; pub const game.tick_duration_0 = 0xCA00 +; pub const game.tick_duration_1 = 0x3B9A +; pub const game.tick_duration_2 = 0x0000 +; pub const game.tick_duration_3 = 0x0000 + +; 0.5 second = 500_000_000 nano second = 0x0000_0000_1DCD_6500 +pub const game.tick_duration_0 = 0x6500 +pub const game.tick_duration_1 = 0x1DCD +pub const game.tick_duration_2 = 0x0000 +pub const game.tick_duration_3 = 0x0000 \ No newline at end of file diff --git a/src/consts/keyboard.asm b/src/consts/keyboard.asm new file mode 100644 index 0000000..6abea3e --- /dev/null +++ b/src/consts/keyboard.asm @@ -0,0 +1,12 @@ +pub const keyboard.mask_key = 0x00FF +pub const keyboard.mask_event = 0x0100 + +pub const keyboard.event_released = 0x0000 +pub const keyboard.event_pressed = 0x0100 + +pub const keyboard.key_min = 1 +pub const keyboard.key_up = 1 +pub const keyboard.key_right = 2 +pub const keyboard.key_down = 3 +pub const keyboard.key_left = 4 +pub const keyboard.key_max = 4 \ No newline at end of file diff --git a/src/consts/map.asm b/src/consts/map.asm new file mode 100644 index 0000000..07bdf6a --- /dev/null +++ b/src/consts/map.asm @@ -0,0 +1,10 @@ +; map size 16x12 (or 15x11 with margins) +pub const map.width = 15 +pub const map.height = 11 +pub const map.size = 165 ; 11*15 +pub const map.max_x = 14 +pub const map.max_y = 10 + +pub const map.tiles.empty = 0 +pub const map.tiles.wall = 1 +pub const map.tiles.coin = 2 diff --git a/src/consts/offsets.asm b/src/consts/offsets.asm new file mode 100644 index 0000000..53d2e4f --- /dev/null +++ b/src/consts/offsets.asm @@ -0,0 +1,16 @@ +;const free_ram_start = 8000 + +pub const offsets.screen = 8000 ; screen size: 80x60 = 4800 + +pub const offsets.tick_duration_high = 12800 +pub const offsets.tick_duration_low = 12804 +pub const offsets.next_tick_high = 12808 +pub const offsets.next_tick_low = 128012 + + +; State: +; U8 x coord +; U8 y coord +; U8 direction +pub const offsets.state_robot = 12816 +pub const offsets.state_ghost = 12819 \ No newline at end of file diff --git a/src/consts/scene.asm b/src/consts/scene.asm new file mode 100644 index 0000000..37c160e --- /dev/null +++ b/src/consts/scene.asm @@ -0,0 +1,8 @@ +; given: +; - 16x12 tiles map +; - 5x5 pixels tile + +pub const scene.margin_top = 2 +pub const scene.margin_bottom = 3 +pub const scene.margin_left = 2 +pub const scene.margin_right = 3 \ No newline at end of file diff --git a/src/consts/screen.asm b/src/consts/screen.asm new file mode 100644 index 0000000..a4b8231 --- /dev/null +++ b/src/consts/screen.asm @@ -0,0 +1,26 @@ +; Screen pixel mode (settings #0) +; 0: ASCII 8 +; 1: ASCII 24 +; 2: Pixel 8 +; 3: Pixel 24 + +; While in mode #2 (Pixel 8) + ; Screen memory offset (settings #1) + ; Screen resolutions (settings #2) + ; 0. 80x60 + ; 1. 160x120 + ; 2. 256x192 + ; 3. 320x240 + ; 4. 640x480 + ; 5. 800x600 + ; 6. 920x720 + ; 7. 1024x768 + +pub const screen.mode_index = 0 +pub const screen.mode_value = 2 +pub const screen.offset_index = 1 +pub const screen.resolution_index = 2 +pub const screen.resolution_value = 0 + +pub const screen.width = 80 +pub const screen.height = 60 diff --git a/src/consts/sprites.asm b/src/consts/sprites.asm new file mode 100644 index 0000000..8c6f485 --- /dev/null +++ b/src/consts/sprites.asm @@ -0,0 +1,18 @@ +pub const sprites.width = 5 +pub const sprites.height = 5 ; unused as sprite are all square +pub const sprites.size = 25 ; height x width + +pub const sprites.rotation_0 = 0 +pub const sprites.rotation_90 = 1 +pub const sprites.rotation_180 = 2 +pub const sprites.rotation_270 = 3 + +pub const sprites.empty = 0 +pub const sprites.unimplemented = 1 +pub const sprites.wall_end = 2 +pub const sprites.wall_straight = 3 +pub const sprites.wall_corner = 4 +pub const sprites.robot = 5 +pub const sprites.ghost = 6 +pub const sprites.coin = 7 +pub const sprites.max = 7 ; for lookup table diff --git a/src/lib/drawing.asm b/src/lib/drawing.asm new file mode 100644 index 0000000..1bfdf0c --- /dev/null +++ b/src/lib/drawing.asm @@ -0,0 +1,299 @@ +include ../consts/offsets +include ../consts/map +include ../consts/screen +include ../consts/scene +include ../consts/sprites +include sprite_rotation + +; /* Draw the whole (fill the screen memory with the values of the sprites based on the map) + +; Inputs: + +; Outputs: + +; Locals: +; r1: x map coord +; r2: y map coord +; r3: sprite rotation +; r4: sprite id +; r12: rotation (returned by `sprite and rotation`) +; r13: sprite (returned by `get_sprite_id_and_rotation`) +; */ +pub drawing.draw_whole_map: + push r1 + push r2 + push r3 + push r4 + push r12 + push r13 + + mov r1, 0 + mov r2, 0 + + draw_whole_map_loop: + call sprite_rotation.get_sprite_id_and_rotation + mov r3, r12 + mov r4, r13 + call drawing.draw_sprite_at_map_coord + add r1, r1, 1 + cmp r1, map.width + jne draw_whole_map_loop + mov r1, 0 + add r2, r2, 1 + cmp r2, map.height + jne draw_whole_map_loop + + pop r13 + pop r12 + pop r4 + pop r3 + pop r2 + pop r1 + + ret + +; /* Return the top-left pixel address of the tile at the given map coordinates + +; Inputs: +; r1: map x coord +; r2: map y coord + +; Locals: +; r3: counter +; r4: offset per tile row (screen width * tile height) + +; Ouputs: +; r13: the pixel address +; */ +get_tile_address: + push r3 + push r4 + + mov r3, 0 + mov r4, 0 + get_tile_address_offset_per_row_loop: + add r4, r4, screen.width + add r3, r3, 1 + cmp r3, sprites.height + jne get_tile_address_offset_per_row_loop + + mov r13, offsets.screen + + mov r3, 0 + get_tile_address_margin_top_loop: + cmp r3, scene.margin_top + je get_tile_address_margin_top_loop_end + add r13, r13, screen.width + add r3, r3, 1 + jmp get_tile_address_margin_top_loop + get_tile_address_margin_top_loop_end: + + mov r3, 0 + get_tile_address_tile_row_loop: + cmp r3, r2 + je get_tile_address_tile_row_loop_end + add r13, r13, r4 + add r3, r3, 1 + jmp get_tile_address_tile_row_loop + get_tile_address_tile_row_loop_end: + + add r13, r13, scene.margin_left + mov r3, 0 + get_tile_address_tile_column_loop: + cmp r3, r1 + je get_tile_address_tile_column_loop_end + add r13, r13, sprites.width + add r3, r3, 1 + jmp get_tile_address_tile_column_loop + get_tile_address_tile_column_loop_end: + + pop r4 + pop r3 + ret + +; /* Get address of given sprite id + +; Inputs: +; r1: sprite id + +; Outputs: +; r13: address + +; Locals: +; r2: jump address +; */ +get_sprite_address: + push r1 + push r2 + + cmp r1, sprites.max + jbe get_sprite_address_jmp_table_init + mov r1, sprites.unimplemented + + get_sprite_address_jmp_table_init: + lsl r2, r1, 3 ; 2*instruction_size = *8 = <<3 + add r2, r2, get_sprite_address_jmp_table_start + jmp r2 + + get_sprite_address_jmp_table_start: + ; sprite_empty + add r13, zr, sprite_empty + jmp get_sprite_address_return + ; sprite_unimplemented: + add r13, zr, sprite_unimplemented + jmp get_sprite_address_return + ; sprite_wall_end + add r13, zr, sprite_wall_end + jmp get_sprite_address_return + ; sprite_wall_straight + add r13, zr, sprite_wall_straight + jmp get_sprite_address_return + ; sprite_wall_corner + add r13, zr, sprite_wall_corner + jmp get_sprite_address_return + ; sprite_robot + add r13, zr, sprite_robot + jmp get_sprite_address_return + ; sprite_ghost + add r13, zr, sprite_ghost + jmp get_sprite_address_return + ; sprite_coin: + add r13, zr, sprite_coin + ;jmp get_sprite_address_return + + get_sprite_address_return: + pop r2 + pop r1 + ret + +; /* Draw given sprite on the screen at given map coordinates + +; Inputs: +; r1: map x coord +; r2: map y coord +; r3: sprite rotation +; r4: sprite id + +; Outputs: + +; Locals: +; r1: screen address +; r2: sprite address +; r13: value returned by call to `get_tile_address` and `get_sprite_address` +; */ +pub drawing.draw_sprite_at_map_coord: + push r1 + push r2 + push r3 + push r4 + push r13 + + call get_tile_address + mov r1, r13 + push r1 + + mov r1, r4 + call get_sprite_address + mov r2, r13 + pop r1 + + call draw_sprite + + pop r13 + pop r4 + pop r3 + pop r2 + pop r1 + ret + +; /* Draw given sprite (by address) at given screen position (by address) with given rotation + +; Inputs: +; r1: screen address +; r2: sprite address +; r3: rotation + +; Outputs: + +; Locals: +; r1: current screen pixel address +; r4: x +; r5: y +; r6: current sprite pixel address +; r7: color +; r8: x increment +; r9: y increment +; */ +draw_sprite: + push r1 + push r2 + push r3 + push r4 + push r5 + push r6 + push r7 + push r8 + push r9 + + mov r6, r2 + + ; rotation + draw_sprite_cmp_rotation_0: + cmp r3, sprites.rotation_0 + jne draw_sprite_cmp_rotation_90 + mov r8, 1 + mov r9, 0 + jmp draw_sprite_loop_init + draw_sprite_cmp_rotation_90: + cmp r3, sprites.rotation_90 + jne draw_sprite_cmp_rotation_180 + add r6, r6, sprites.size + sub r6, r6, sprites.width + sub r8, zr, sprites.width + mov r9, sprites.size + add r9, r9, 1 + jmp draw_sprite_loop_init + draw_sprite_cmp_rotation_180: + cmp r3, sprites.rotation_180 + jne draw_sprite_cmp_rotation_270 + add r6, r6, sprites.size + sub r6, r6, 1 + sub r8, zr, 1 + mov r9, 0 + jmp draw_sprite_loop_init + draw_sprite_cmp_rotation_270: + add r6, r6, sprites.width + sub r6, r6, 1 + mov r8, sprites.width + sub r9, zr, sprites.size + sub r9, r9, 1 + + draw_sprite_loop_init: + mov r4, 0 + mov r5, 0 + draw_sprite_loop: + load_8 r7, [r6] + store_8 [r1], r7 + add r4, r4, 1 + add r1, r1, 1 + add r6, r6, r8 + cmp r4, sprites.width + jne draw_sprite_loop + mov r4, 0 + add r6, r6, r9 + add r1, r1, screen.width + sub r1, r1, sprites.width + add r5, r5, 1 + cmp r5, sprites.width + jne draw_sprite_loop + + pop r9 + pop r8 + pop r7 + pop r6 + pop r5 + pop r4 + pop r3 + pop r2 + pop r1 + ret diff --git a/src/lib/game.asm b/src/lib/game.asm new file mode 100644 index 0000000..64a86cb --- /dev/null +++ b/src/lib/game.asm @@ -0,0 +1,26 @@ + +; /* Return the tile address from the given map coordinate + +; Inputs: +; r1: x map coordinate +; r2: y map coordinate + +; Outputs: +; r13: tile address +; */ +pub game.get_tile_address_from_map_coord: + push r1 + push r2 + + add r13, r1, map + get_tile_address_from_map_coord_loop: + cmp r2, 0 + je get_tile_address_from_map_coord_loop_end + add r13, r13, map.width + sub r2, r2, 1 + jmp get_tile_address_from_map_coord_loop + get_tile_address_from_map_coord_loop_end: + + pop r2 + pop r1 + ret \ No newline at end of file diff --git a/src/lib/sprite_rotation.asm b/src/lib/sprite_rotation.asm new file mode 100644 index 0000000..2d3a1f0 --- /dev/null +++ b/src/lib/sprite_rotation.asm @@ -0,0 +1,171 @@ +include ../consts/offsets +include ../consts/map +include ../consts/sprites + +; /* Return the sprite id and rotation for the given map coordinates + +; Inputs: +; r1: x map coord +; r2: y map coord + +; Outputs: +; r12: sprite rotation +; r13: sprite id + +; Locals: +; r3: tile id +; r4: map offset +; r5: counter +; r6: left tile id +; r7: up tile id +; r8: right tile id +; r9: down tile id +; */ +pub sprite_rotation.get_sprite_id_and_rotation: + push r1 + push r2 + push r3 + push r4 + push r5 + push r6 + push r7 + push r8 + push r9 + + add r4, r1, map + mov r5, 0 + get_sprite_id_and_rotation_map_row_loop: + cmp r5, r2 + je get_sprite_id_and_rotation_map_row_loop_end + add r4, r4, map.width + add r5, r5, 1 + jmp get_sprite_id_and_rotation_map_row_loop + get_sprite_id_and_rotation_map_row_loop_end: + + mov r12, sprites.rotation_0 ; default rotation + + load_8 r3, [r4] + lsl r3, r3, 3 ; 2*instruction_size = *8 = <<3 + add r3, r3, get_sprite_id_and_rotation_jump_table + jmp r3 + + get_sprite_id_and_rotation_jump_table: + ; tile_empty + mov r13, sprites.empty + jmp get_sprite_id_and_rotation_return + ; tiles_wall + jmp get_sprite_id_and_rotation_wall + nop + ; tile_coin + mov r13, sprites.coin + jmp get_sprite_id_and_rotation_return + + get_sprite_id_and_rotation_wall: + mov r6, 0 + mov r7, 0 + mov r8, 0 + mov r9, 0 + + get_sprite_id_and_rotation_cmp_left: + cmp r1, 0 + je get_sprite_id_and_rotation_cmp_up + sub r4, r4, 1 + load_8 r6, [r4] + add r4, r4, 1 + get_sprite_id_and_rotation_cmp_up: + cmp r2, 0 + je get_sprite_id_and_rotation_cmp_right + sub r4, r4, map.width + load_8 r7, [r4] + add r4, r4, map.width + get_sprite_id_and_rotation_cmp_right: + cmp r1, map.max_x + je get_sprite_id_and_rotation_cmp_down + add r4, r4, 1 + load_8 r8, [r4] + sub r4, r4, 1 + get_sprite_id_and_rotation_cmp_down: + cmp r2, map.max_y + je get_sprite_id_and_rotation_check_connections + add r4, r4, map.width + load_8 r9, [r4] + sub r4, r4, map.width + + get_sprite_id_and_rotation_check_connections: + get_sprite_id_and_rotation_straight_0: + cmp r7, map.tiles.wall + jne get_sprite_id_and_rotation_straight_90 + cmp r9, map.tiles.wall + jne get_sprite_id_and_rotation_straight_90 + mov r12, sprites.rotation_0 + mov r13, sprites.wall_straight + jmp get_sprite_id_and_rotation_return + get_sprite_id_and_rotation_straight_90: + cmp r6, map.tiles.wall + jne get_sprite_id_and_rotation_corner_0 + cmp r8, map.tiles.wall + jne get_sprite_id_and_rotation_corner_0 + mov r12, sprites.rotation_90 + mov r13, sprites.wall_straight + jmp get_sprite_id_and_rotation_return + + get_sprite_id_and_rotation_corner_0: + cmp r6, map.tiles.wall + jne get_sprite_id_and_rotation_corner_180 + cmp r9, map.tiles.wall + jne get_sprite_id_and_rotation_corner_90 + mov r12,sprites.rotation_0 + mov r13, sprites.wall_corner + jmp get_sprite_id_and_rotation_return + get_sprite_id_and_rotation_corner_90: + cmp r7, map.tiles.wall + jne get_sprite_id_and_rotation_end_90 + mov r12,sprites.rotation_90 + mov r13, sprites.wall_corner + jmp get_sprite_id_and_rotation_return + get_sprite_id_and_rotation_corner_180: + cmp r8, map.tiles.wall + jne get_sprite_id_and_rotation_end_0 + cmp r7, map.tiles.wall + jne get_sprite_id_and_rotation_corner_270 + mov r12,sprites.rotation_180 + mov r13, sprites.wall_corner + jmp get_sprite_id_and_rotation_return + get_sprite_id_and_rotation_corner_270: + cmp r9, map.tiles.wall + jne get_sprite_id_and_rotation_end_270 + mov r12,sprites.rotation_270 + mov r13, sprites.wall_corner + jmp get_sprite_id_and_rotation_return + + get_sprite_id_and_rotation_end_0: + cmp r9, map.tiles.wall + jne get_sprite_id_and_rotation_end_180 + mov r12,sprites.rotation_0 + mov r13, sprites.wall_end + jmp get_sprite_id_and_rotation_return + get_sprite_id_and_rotation_end_90: + mov r12,sprites.rotation_90 + mov r13, sprites.wall_end + jmp get_sprite_id_and_rotation_return + get_sprite_id_and_rotation_end_180: + mov r12,sprites.rotation_180 + mov r13, sprites.wall_end + jmp get_sprite_id_and_rotation_return + get_sprite_id_and_rotation_end_270: + mov r12,sprites.rotation_270 + mov r13, sprites.wall_end + ;jmp get_sprite_id_and_rotation_return + + get_sprite_id_and_rotation_return: + pop r9 + pop r8 + pop r7 + pop r6 + pop r5 + pop r4 + pop r3 + pop r2 + pop r1 + + ret diff --git a/src/main.asm b/src/main.asm new file mode 100644 index 0000000..03923c4 --- /dev/null +++ b/src/main.asm @@ -0,0 +1,369 @@ +include consts/screen + +; +; Screen initialization +; + +; r1 = screen parameter value +mov r1, screen.mode_index +screen r1, screen.mode_value + +mov r1, screen.offset_index +screen r1, offsets.screen + +mov r1, screen.resolution_index +screen r1, screen.resolution_value + +call drawing.draw_whole_map + +; +; State initialization +; +; Robot +const robot_initial_x = 1 +const robot_initial_y = 1 +const robot_initial_direction = game.direction_down + +mov r1, robot_initial_x +mov r2, robot_initial_y +mov r5, robot_initial_direction +mov r6, offsets.state_robot +store_8 [r6], r1 +add r6, r6, 1 +store_8 [r6], r2 +add r6, r6, 1 +store_8 [r6], r5 + +mov r3, sprites.rotation_0 +mov r4, sprites.robot +call drawing.draw_sprite_at_map_coord + +; Ghost +const ghost_initial_x = 13 +const ghost_initial_y = 1 +const ghost_initial_direction = game.direction_down + +mov r1, ghost_initial_x +mov r2, ghost_initial_y +mov r5, ghost_initial_direction +mov r6, offsets.state_ghost +store_8 [r6], r1 +add r6, r6, 1 +store_8 [r6], r2 +add r6, r6, 1 +store_8 [r6], r5 + +mov r3, sprites.rotation_0 +mov r4, sprites.ghost +call drawing.draw_sprite_at_map_coord + +; +; Time initialization +; +mov r2, game.tick_duration_1 +movt r2, r2 +movb r2, game.tick_duration_0 +mov r1, offsets.tick_duration_low +store_32 [r1], r2 + +mov r3, game.tick_duration_3 +movt r3, r3 +movb r3, game.tick_duration_2 +mov r1, offsets.tick_duration_high +store_32 [r1], r3 + +time_0 r4 +time_1 r5 +add r4, r4, r2 +add r5, r5, r3 +cmp r4, r2 +jae init_store_next_tick +add r5, r5, 1 ; overflow + +init_store_next_tick: +mov r1, offsets.next_tick_low +store_32 [r1], r4 +mov r1, offsets.next_tick_high +store_32 [r1], r5 + +game_loop: + ; + ; Event handling + ; + ; r1 = keyboard event+key / direction + ; r2 = keyboard key / direction address + ; r3 = keyboard event + ; r4 = jump address + keyboard r1 + and r2, r1, keyboard.mask_key + and r3, r1, keyboard.mask_event + + cmp r3, keyboard.event_pressed + jne game_loop_event_handling_end + cmp r2, keyboard.key_min + jb game_loop_event_handling_end + cmp r2, keyboard.key_max + ja game_loop_event_handling_end + + sub r4, r2, keyboard.key_min ; so the first value is 0 + lsl r4, r4, 3 ; 2 * instruction length = 8 + add r4, r4, game_loop_key_jump_table_start + jmp r4 + + game_loop_key_jump_table_start: + ; key_up + mov r1, game.direction_up + jmp game_loop_event_handling_store_direction + ; key_right + mov r1, game.direction_right + jmp game_loop_event_handling_store_direction + ; key_down: + mov r1, game.direction_down + jmp game_loop_event_handling_store_direction + ; key_left: + mov r1, game.direction_left +; jmp game_loop_event_handling_end + + game_loop_event_handling_store_direction: + add r2, zr, offsets.state_robot + add r2, r2, 2 + store_8 [r2], r1 + + game_loop_event_handling_end: + + ; + ; Elapsed time check + ; + ; r1: address + ; r2: now low / delay low + ; r3: now high / delay high + ; r4: next tick low + ; r5: next tick high + ; + time_0 r2 + time_1 r3 + + mov r1, offsets.next_tick_low + load_32 r4, [r1] + mov r1, offsets.next_tick_high + load_32 r5, [r1] + + cmp r3, r5 + jb game_loop + ja update_next_tick + cmp r2, r4 + jb game_loop + + update_next_tick: + mov r1, offsets.tick_duration_low + load_32 r2, [r1] + mov r1, offsets.tick_duration_high + load_32 r3, [r1] + add r4, r4, r2 + add r5, r5, r3 + cmp r4, r2 + jae store_next_tick + add r5, r5, 1 + + store_next_tick: + mov r1, offsets.next_tick_low + store_32 [r1], r4 + mov r1, offsets.next_tick_high + store_32 [r1], r5 + + + ; + ; Robot movement + ; + ; r1 = robot's state address + ; r2 = x + ; r3 = y + ; r4 = direction + ; r5 = new x + ; r6 = new y + ; r7 = new position address + ; r8 = new position content + ; r9 = jump address + mov r1, offsets.state_robot + load_8 r2, [r1] + add r1, r1, 1 + load_8 r3, [r1] + add r1, r1, 1 + load_8 r4, [r1] + + mov r5, r2 + mov r6, r3 + + lsl r9, r4, 3 ; 2 * instruction size = 8 + add r9, r9, game_loop_robot_movement_jump_table_start + jmp r9 + + game_loop_robot_movement_jump_table_start: + ; direction_up + sub r6, r6, 1 + jmp game_loop_robot_movement_try_move + ; direction_right + add r5, r5, 1 + jmp game_loop_robot_movement_try_move + ; direction_down + add r6, r6, 1 + jmp game_loop_robot_movement_try_move + ; direction_left + sub r5, r5, 1 + jmp game_loop_robot_movement_try_move + ; direction_still + jmp game_loop_robot_movement_none + + game_loop_robot_movement_try_move: + push r2 + mov r1, r5 + mov r2, r6 + call game.get_tile_address_from_map_coord + mov r7, r13 + pop r2 + + ; + ; Collision handling + ; + load_8 r8, [r7] + cmp r8, map.tiles.wall + jne game_loop_robot_movement_some + ; hit wall + mov r1, offsets.state_robot + add r1, r1, 2 + mov r4, game.direction_still + store_8 [r1], r4 + jmp game_loop_robot_movement_none + + game_loop_robot_movement_some: + ; TODO: handle coin and empty differently + mov r8, map.tiles.empty + store_8 [r7], r8 + + mov r1, offsets.state_robot + store_8 [r1], r5 + add r1, r1, 1 + store_8 [r1], r6 + + ; clear old position + mov r1, r2 + mov r2, r3 + mov r3, sprites.rotation_0 + mov r4, sprites.empty + call drawing.draw_sprite_at_map_coord + + ; draw robot on new position + mov r1, r5 + mov r2, r6 + mov r3, sprites.rotation_0 + mov r4, sprites.robot + call drawing.draw_sprite_at_map_coord + + + game_loop_robot_movement_none: + + ; + ; Ghost movement + ; + ; r1: ghost's state address + ; r2: x + ; r3: y + ; r4: direction + ; r5: x increment + ; r6: y increment + ; r7: new x + ; r8: new y + ; r9: new position address + ; r10: new position content + ; r11: jump address + mov r1, offsets.state_ghost + load_8 r2, [r1] + add r1, r1, 1 + load_8 r3, [r1] + add r1, r1, 1 + load_8 r4, [r1] + + mov r5, 0 + mov r6, 0 + + lsl r11, r4, 3 ; 2 * instruction size = 8 + add r11, r11, game_loop_ghost_movement_jump_table_start + jmp r11 + + game_loop_ghost_movement_jump_table_start: + ; direction_up + sub r6, r6, 1 + jmp game_loop_ghost_movement_make_move + ; direction_right + add r5, r5, 1 + jmp game_loop_ghost_movement_make_move + ; direction_down + add r6, r6, 1 + jmp game_loop_ghost_movement_make_move + ; direction_left + sub r5, r5, 1 + ;jmp game_loop_ghost_movement_make_move + ; direction_still (should never happen) + + game_loop_ghost_movement_make_move: + add r7, r2, r5 + add r8, r3, r6 + mov r1, offsets.state_ghost + store_8 [r1], r7 + add r1, r1, 1 + store_8 [r1], r8 + + ; get tile at previous position + push r2 + mov r1, r2 + mov r2, r3 + call game.get_tile_address_from_map_coord + mov r9, r13 + pop r2 + + ; draw tile at previous position + load_8 r1, [r9] + cmp r1, map.tiles.coin + je game_loop_ghost_movement_old_tile_coin + ; old tile empty + mov r4, sprites.empty + jmp game_loop_ghost_movement_draw_old_tile + game_loop_ghost_movement_old_tile_coin: + mov r4, sprites.coin + + game_loop_ghost_movement_draw_old_tile: + mov r1, r2 + mov r2, r3 + mov r3, sprites.rotation_0 + call drawing.draw_sprite_at_map_coord + + ; draw ghost on new position + mov r1, r7 + mov r2, r8 + mov r3, sprites.rotation_0 + mov r4, sprites.ghost + call drawing.draw_sprite_at_map_coord + + ; + ; Check ahead for direction change + ; + add r1, r7, r5 + add r2, r8, r6 + call game.get_tile_address_from_map_coord + + load_8 r10, [r13] + cmp r10, map.tiles.wall + jne game_loop_ghost_movement_end + ; increment direction + mov r1, offsets.state_ghost + add r1, r1, 2 + load_8 r4, [r1] + add r4, r4, 1 + and r4, r4, 0b11 ; modulo 3 + store_8 [r1], r4 + + game_loop_ghost_movement_end: + +jmp game_loop + +include lib/drawing diff --git a/visuals.md b/visuals.md new file mode 100644 index 0000000..7e54d03 --- /dev/null +++ b/visuals.md @@ -0,0 +1,23 @@ +# Visuals + +## Pixel mode resolutions + +0. 80x60 +1. 160x120 +2. 256x192 +3. 320x240 +4. 640x480 +5. 800x600 +6. 920x720 +7. 1024x768 + +## Map size + +### 15x11 + +Pro: The width and hight are odd, allowing the map to have a central column and row. + +Tile size per resolution: +0. 5x5 pixels, with a reminder of 5 pixels en width and 5 pixels in height. +1. 10x10 pixels, with a reminder of 10w and 10h +2. 17x17 pixels, no reminder