create repository with functional basic implementation
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,205 @@
|
||||
#!/usr/bin/env -S deno run --allow-read
|
||||
|
||||
if (Deno.args.length !== 0) {
|
||||
console.error(
|
||||
"This script takes no argument (The source file list is hardcoded) and output the result on stdout.",
|
||||
);
|
||||
Deno.exit(1);
|
||||
}
|
||||
|
||||
import { TextLineStream } from "jsr:@std/streams@1.1.0";
|
||||
|
||||
const asmFilePaths = [
|
||||
"src/consts/colors.asm",
|
||||
"src/consts/game.asm",
|
||||
"src/consts/keyboard.asm",
|
||||
"src/consts/map.asm",
|
||||
"src/consts/offsets.asm",
|
||||
"src/consts/scene.asm",
|
||||
"src/consts/screen.asm",
|
||||
"src/consts/sprites.asm",
|
||||
|
||||
"src/main.asm",
|
||||
|
||||
"src/lib/game.asm",
|
||||
"src/lib/drawing.asm",
|
||||
"src/lib/sprite_rotation.asm",
|
||||
];
|
||||
|
||||
const mapWidth = 15;
|
||||
const spriteWidth = 5;
|
||||
|
||||
const mapMatchingStrings = {
|
||||
0x00_00_00: 'U8 0', // Empty
|
||||
0xFF_00_00: 'U8 1', // Wall
|
||||
0xFF_FF_00: 'U8 2', // Coin
|
||||
};
|
||||
|
||||
const spritesMatchingStrings = {
|
||||
0x00_00_00: 'U8 0b000_000_00',
|
||||
0x00_00_FF: 'U8 0b000_000_11',
|
||||
0x00_FF_00: 'U8 0b000_111_00',
|
||||
0x77_77_77: 'U8 0b010_011_10',
|
||||
0xFF_00_00: 'U8 0b111_000_00',
|
||||
0xFF_00_FF: 'U8 0b111_000_11',
|
||||
0xFF_80_00: 'U8 0b111_100_00',
|
||||
0xFF_FF_00: 'U8 0b111_110_00',
|
||||
0xFF_FF_FF: 'U8 0b111_111_11',
|
||||
};
|
||||
|
||||
const mapPath = 'assets/map.ppm';
|
||||
const spritesPath = {
|
||||
'sprite_empty': 'assets/sprites/empty.ppm',
|
||||
'sprite_unimplemented': 'assets/sprites/placeholder.ppm',
|
||||
'sprite_wall_end': 'assets/sprites/wall_end.ppm',
|
||||
'sprite_wall_straight': 'assets/sprites/wall_straight.ppm',
|
||||
'sprite_wall_corner': 'assets/sprites/wall_corner.ppm',
|
||||
'sprite_robot': 'assets/sprites/robot.ppm',
|
||||
'sprite_ghost': 'assets/sprites/ghost.ppm',
|
||||
'sprite_coin': 'assets/sprites/coin.ppm',
|
||||
};
|
||||
|
||||
/**
|
||||
* START OF BUILD
|
||||
*/
|
||||
|
||||
for (const path of asmFilePaths) {
|
||||
const file = await Deno.open(path);
|
||||
const readable = file.readable.pipeThrough(new TextDecoderStream())
|
||||
.pipeThrough(new TextLineStream());
|
||||
|
||||
console.log(`
|
||||
;/*******************************
|
||||
;* ${path}
|
||||
;*******************************/
|
||||
`);
|
||||
|
||||
for await (const line of readable) {
|
||||
if (!line.startsWith("include ")) {
|
||||
console.log(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const asmRawRepresentations = [];
|
||||
|
||||
const mapBytes = await getDataFromPPM(mapPath);
|
||||
asmRawRepresentations.push(byteToAsmRawData('map', mapBytes, mapMatchingStrings, mapWidth));
|
||||
|
||||
for (const [label, path] of Object.entries(spritesPath)) {
|
||||
const bytes = await getDataFromPPM(path);
|
||||
asmRawRepresentations.push(byteToAsmRawData(label, bytes, spritesMatchingStrings, spriteWidth));
|
||||
}
|
||||
|
||||
console.log(asmRawRepresentations.join("\n\n"));
|
||||
|
||||
/**
|
||||
* Return an ASM representation of the given data
|
||||
*
|
||||
* @param label The label to write before the first raw data
|
||||
* @param data An array of bytes to interprete as 24 bits values
|
||||
* @param matchingStrings An associative array with a string representation for each possible 24 bit data value
|
||||
* @param lineLength Optional: Provide a way to wrap the result in multiple lines
|
||||
*
|
||||
* @returns The ASM representation of that raw data, pre- and post-fixed by the given label
|
||||
*/
|
||||
function byteToAsmRawData(label: string, data: Uint8Array, matchingStrings: Record<number, string>, 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<Uint8Array> {
|
||||
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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
+369
@@ -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
|
||||
+23
@@ -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
|
||||
Reference in New Issue
Block a user