create repository with functional basic implementation

This commit is contained in:
2026-04-24 07:13:59 +02:00
commit 7014692d71
24 changed files with 1265 additions and 0 deletions

BIN
assets/map.ppm Normal file

Binary file not shown.

BIN
assets/sprites/coin.ppm Normal file

Binary file not shown.

BIN
assets/sprites/empty.ppm Normal file

Binary file not shown.

BIN
assets/sprites/ghost.ppm Normal file

Binary file not shown.

Binary file not shown.

BIN
assets/sprites/robot.ppm Normal file

Binary file not shown.

Binary file not shown.

BIN
assets/sprites/wall_end.ppm Normal file

Binary file not shown.

Binary file not shown.

205
build.ts Executable file
View File

@@ -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);
}

56
readme.md Executable file
View File

@@ -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

9
src/consts/colors.asm Normal file
View File

@@ -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

17
src/consts/game.asm Normal file
View File

@@ -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

12
src/consts/keyboard.asm Normal file
View File

@@ -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

10
src/consts/map.asm Normal file
View File

@@ -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

16
src/consts/offsets.asm Normal file
View File

@@ -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

8
src/consts/scene.asm Normal file
View File

@@ -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

26
src/consts/screen.asm Normal file
View File

@@ -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

18
src/consts/sprites.asm Normal file
View File

@@ -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

299
src/lib/drawing.asm Normal file
View File

@@ -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

26
src/lib/game.asm Normal file
View File

@@ -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

171
src/lib/sprite_rotation.asm Normal file
View File

@@ -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
src/main.asm Normal file
View File

@@ -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
visuals.md Normal file
View File

@@ -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