8 Commits

31 changed files with 1090 additions and 736 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
dist/

Binary file not shown.

Binary file not shown.

BIN
assets/sprites/ghost_up.ppm Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/sprites/robot_up.ppm Normal file

Binary file not shown.

425
build.ts
View File

@@ -1,229 +1,268 @@
#!/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);
}
#!/usr/bin/env -S deno run --allow-read --allow-write --allow-run
import { TextLineStream } from "jsr:@std/streams@1.1.0";
const asmFilePaths = [
"src/consts/arch.asm",
"src/consts/game.asm",
"src/consts/keyboard.asm",
"src/consts/map.asm",
"src/consts/scene.asm",
"src/consts/screen.asm",
"src/consts/sprites.asm",
"src/init.asm",
"src/main.asm",
"src/lib/game.asm",
"src/lib/drawing.asm",
"src/lib/sprite_rotation.asm",
];
const reservedSpacePath = 'src/reserved_space.asm';
/**
* CONFIG
*/
const instructionLength = 4; // in bytes
const mapWidth = 15;
const spriteWidth = 5;
const rawDataInstructionLengthPrefix = "U32";
const mapMatchingStrings = {
0x00_00_00: '0', // Empty
0xFF_00_00: '1', // Wall
0xFF_FF_00: '2', // Coin
};
const ppmToAsmScriptPath = "./ppmToAsm.ts";
const spritesMatchingStrings = {
0x00_00_00: ' 0', // 0b000_000_00
0x00_00_FF: ' 3', // 0b000_000_11
0x00_FF_00: ' 28', // 0b000_111_00
0x99_50_00: ' 68', // 0b010_001_00
0x77_77_77: '110', // 0b011_011_10
0xB3_B3_B3: '146', // 0b100_100_10
0xFF_00_00: '224', // 0b111_000_00
0xFF_00_FF: '227', // 0b111_000_11
0xFF_80_00: '240', // 0b111_100_00
0xFF_FF_00: '248', // 0b111_110_00
0xFF_FF_FF: '255', // 0b111_111_11
};
const mapPath = 'assets/map.ppm';
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',
"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_up": "assets/sprites/robot_up.ppm",
"sprite_robot_right": "assets/sprites/robot_right.ppm",
"sprite_robot_down": "assets/sprites/robot_down.ppm",
"sprite_robot_left": "assets/sprites/robot_left.ppm",
"sprite_ghost_up": "assets/sprites/ghost_up.ppm",
"sprite_ghost_right": "assets/sprites/ghost_right.ppm",
"sprite_ghost_down": "assets/sprites/ghost_down.ppm",
"sprite_ghost_left": "assets/sprites/ghost_left.ppm",
"sprite_coin": "assets/sprites/coin.ppm",
};
/**
* START OF BUILD
* END OF CONFIG
*/
for (const path of asmFilePaths) {
const file = await Deno.open(path);
const readable = file.readable.pipeThrough(new TextDecoderStream())
.pipeThrough(new TextLineStream());
class Minifier {
private buffer = "";
private lines: Array<string> = [];
private insideComment = false;
stream;
console.log(`
;/*******************************
;* ${path}
;*******************************/
`);
constructor(writableStream: WritableStream) {
this.stream = new TransformStream<string>({
transform: (chunk, controller) => {
this.buffer += chunk;
controller.enqueue(this.processChunk());
},
for await (const line of readable) {
if (!line.startsWith("include ")) {
console.log(line);
flush: (controller) => {
this.buffer += "\n";
controller.enqueue(this.processChunk());
controller.enqueue(this.lines.join("\n") + "\n");
this.lines = [];
},
});
this.stream.readable.pipeTo(writableStream);
}
private processChunk(): string {
const commentFullRegex = /\/\*.*\*\//;
const commentStartRegex = /(.*)\/\*(.*)/;
const commentEndRegex = /(.*)\*\/(.*)/;
if (this.insideComment) {
const matches = this.buffer.match(commentEndRegex);
if (matches === null) {
this.buffer = '';
return '';
}
this.buffer = matches[2];
this.insideComment = false;
}
if (!this.insideComment) {
this.buffer = this.buffer.replace(commentFullRegex, '');
const matches = this.buffer.match(commentStartRegex);
if (matches !== null) {
this.buffer = matches[1];
this.insideComment = true;
return '';
}
}
const newLines = this.buffer.split("\n");
this.buffer = newLines.pop() as string; // keep the last (potentially incomplete) line
const removeComment = (line: string) => line.replace(/\s*;.*/, "");
const removeLeadingWhitespaces = (line: string) => line.replace(/^\s+/, "");
const removeTrailingWhitespaces = (line: string) =>
line.replace(/\s+$/, "");
const removeDoubleSpaces = (line: string) => line.replaceAll(/ +/g, " ");
const isNotEmpty = (line: string) => line.length !== 0;
const newLinesStripped = newLines
.map(removeComment)
.map(removeLeadingWhitespaces)
.map(removeTrailingWhitespaces)
.map(removeDoubleSpaces)
.filter(isNotEmpty);
this.lines.push(...newLinesStripped);
return this.processLines();
}
private processLines() {
const processedLines: Array<string> = [];
while (this.lines.length >= instructionLength) {
const mergeableLines = this.lines.slice(0, instructionLength);
if (!mergeableLines.every((line) => line.startsWith("U8"))) {
processedLines.push(this.lines.shift() as string);
continue;
}
const bytesToMerge = mergeableLines.map((line) =>
parseInt((line.match(/U8\s(\d+)/) as RegExpMatchArray)[1])
);
let mergedBytes = 0;
for (let i = 0; i < bytesToMerge.length; i++) {
mergedBytes += bytesToMerge[i] << (instructionLength - 1 - i) * 8;
}
processedLines.push(
`${rawDataInstructionLengthPrefix} ${mergedBytes >>> 0}`,
);
this.lines.splice(0, instructionLength);
}
return processedLines.length === 0 ? "" : `${processedLines.join("\n")}\n`;
}
}
async function ppmToAsm(
mode: "map" | "sprite",
label: string,
path: string,
): Promise<string> {
const command = new Deno.Command(ppmToAsmScriptPath, {
args: [
instructionLength.toString(),
mode,
label,
path,
],
});
const asmRawRepresentations = [];
const { code, stdout, stderr } = await command.output();
const mapBytes = await getDataFromPPM(mapPath);
asmRawRepresentations.push(bytesToAsmConstU8('map', mapBytes, mapMatchingStrings, mapWidth));
const decoder = new TextDecoder();
for (const [label, path] of Object.entries(spritesPath)) {
const bytes = await getDataFromPPM(path);
asmRawRepresentations.push(bytesToAsmConstU8(label, bytes, spritesMatchingStrings, spriteWidth));
}
console.log(asmRawRepresentations.join("\n\n"));
console.log(`
;
; RESERVED RAM SPACE
;
`);
console.log(await Deno.readTextFile(reservedSpacePath))
/**
* Return an ASM constant (U8) representation of the given data
*
* If needed, the representation will be padded to ensure the following code stays aligned on the instruction's length
*
* @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 bytesToAsmConstU8(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])
if (!Object.hasOwn(matchingStrings, pixelValue)) {
console.error(`No matching value defined for "${pixelValue.toString(2)}" (which is ${label}'s ${i} pixel)`);
if (code !== 0) {
console.error(`ppmToAsm script returned non-zero value: ${code}`);
console.error(`STDERR: \n${decoder.decode(stderr)}`);
Deno.exit(1);
}
stringValues.push(`U8 ${matchingStrings[pixelValue]}`);
return decoder.decode(stdout);
}
async function build(
pwd: string,
path: string,
output: WritableStreamDefaultWriter<string>,
) {
const includeExpressionRegex = /^\s*include\s+([^\s;]+)/;
const file = await Deno.open(`${pwd}/${path}`);
const readable = file.readable.pipeThrough(new TextDecoderStream())
.pipeThrough(new TextLineStream());
for await (const line of readable) {
const match = line.match(includeExpressionRegex);
if (match === null) {
await output.write(`${line}\n`);
continue;
}
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"));
}
await build(pwd, `${match[1]}.asm`, output);
}
}
const paddingBytes = [];
for (let i = 0; i < stringValues.length % instructionLength; i++) {
paddingBytes.push('U8 0');
}
const paddingString = paddingBytes.length === 0 ? '' : `\n${paddingBytes.join('\t')} ; padding to preserve ${8 * instructionLength} bits alignment`
function printUsage(into: (message: string) => void) {
into(`Usage:
\tbuild.ts [options...] {file}
\t\tRead the given file and recursively include other '.asm' files to produce a single output.
\tbuild.ts --help
\t\tPrint this message
return `${label}:
${lines.join("\n")}
${label}_end:${paddingString}`
Options:
\t--minify\tWhen used, the result will be stripped of all comments, leading/trailing double whitespaces, empty lines and use the more compact raw data representation.
\t--ppm \tWhen used, the hardcoded ppm files will be translated to their matching Symfony assembly reprensentation into src/dist.`);
}
/**
* 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.
* SCRIPT
*/
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);
if (Deno.args.length < 1) {
printUsage(console.error);
Deno.exit(1);
}
let minify = false;
let convertPPM = false;
let path: string | undefined = undefined;
Deno.args.forEach((arg, index) => {
switch (arg) {
case "--help":
printUsage(console.log);
Deno.exit(0);
break;
case "--minify":
minify = true;
break;
case "--ppm":
convertPPM = true;
break;
default:
if (index !== Deno.args.length - 1) {
printUsage(console.error);
Deno.exit(1);
}
path = arg;
break;
}
});
if (path === undefined) {
printUsage(console.error);
Deno.exit(1);
}
if (convertPPM) {
await Deno.mkdir(`${Deno.cwd()}/src/dist`, { recursive: true });
const mapPromise = async () => {
const assembly = await ppmToAsm("map", "map", mapPath);
await Deno.writeTextFile(`${Deno.cwd()}/src/dist/map.asm`, assembly);
};
const spritePromises = Object.entries(spritesPath).map(async ([label, path]) => {
const assembly = await ppmToAsm("sprite", label, path);
const filename = path.split("/").pop()?.split('.')[0];
await Deno.writeTextFile(`${Deno.cwd()}/src/dist/${filename}.asm`, assembly);
});
await Promise.all([
mapPromise(),
...spritePromises,
]);
}
const encoder = new TextEncoder();
const stdoutWritable = new WritableStream<string>({
async write(chunk) {
await Deno.stdout.write(encoder.encode(chunk));
},
});
const writer = minify
? (new Minifier(stdoutWritable)).stream.writable.getWriter()
: stdoutWritable.getWriter();
await build(`${Deno.cwd()}/src`, path, writer);
await writer.close();

184
ppmToAsm.ts Executable file
View File

@@ -0,0 +1,184 @@
#!/usr/bin/env -S deno run --allow-read
const ppmCorrespondanceTables = {
map: {
0x00_00_00: 0, // Empty
0xFF_00_00: 1, // Wall
0xFF_FF_00: 2, // Coin
},
sprite: {
0x00_00_00: 0, // 0b000_000_00
0x00_00_FF: 3, // 0b000_000_11
0x00_FF_00: 28, // 0b000_111_00
0x99_50_00: 68, // 0b010_001_00
0x77_77_77: 110, // 0b011_011_10
0xB3_B3_B3: 146, // 0b100_100_10
0xFF_00_00: 224, // 0b111_000_00
0xFF_00_FF: 227, // 0b111_000_11
0xFF_80_00: 240, // 0b111_100_00
0xFF_FF_00: 248, // 0b111_110_00
0xFF_FF_FF: 255, // 0b111_111_11
},
};
/**
* Return an ASM constants (U8) representation of the given data
*
* If needed, the representation will be padded to ensure the following code stays aligned on the instruction's length
*
* @param label The label to write before the first raw data
* @param data An array of bytes to interprete as 24 bits values
* @param matchingValues An associative array with a string representation for each possible 24 bit data value
* @param instructionLength The size / length in byte of an instruction. Used to pad result to ensure data stays aligned
*
* @returns The ASM representation of that raw data, pre- and post-fixed by the given label
*/
function ppmBytesToCorrespondingAsmConstU8(
label: string,
data: Uint8Array,
matchingValues: Record<number, number>,
instructionLength: number,
): string {
if (data.length % 3 !== 0) {
console.error(
"Length of data passed to bytesToAsmConstU8 are not a multiple of 3 (it needs to, as each resulting byte need a Red, a Green and a Blue value)",
);
Deno.exit(1);
}
const lines = [`${label}:`];
for (let i = 0; i < data.length; i += 3) {
const pixelValue = (data[i] << 16) + (data[i + 1] << 8) + (data[i + 2]);
if (!Object.hasOwn(matchingValues, pixelValue)) {
console.error(
`No matching value defined for "${
pixelValue.toString(2)
}" (which is ${label}'s ${i} pixel)`,
);
Deno.exit(1);
}
lines.push(`U8 ${matchingValues[pixelValue]}`);
}
lines.push(`${label}_end:`);
const bytesNb = data.length / 3;
const unalignedBytesNb = bytesNb % instructionLength;
for (let i = 0; i < instructionLength - unalignedBytesNb; i++) {
lines.push(
`U8 0 ; padding to preserve ${8 * instructionLength} bits alignment`,
);
}
return lines.join("\n");
}
/**
* 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);
}
function printUsage(into: (message: string) => void) {
into(`Usage:
\tppmToAsm.ts {instruction length} {map|sprite} {label} {file}
\t\tConvert the given PPM file into a Symfony assembly representation using the given correspondance table. Result will be padded with "U8 0"s to ensure alignment to given instruction length.
\tppmToAsm.ts --help
\t\tPrint this message`);
}
/**
* SCRIPT
*/
if (Deno.args.length !== 4) {
printUsage(console.error);
Deno.exit(1);
}
if (!(Deno.args[1] in ppmCorrespondanceTables)) {
console.error(`"${Deno.args[1]}" is not a valid correspondance table.\n`);
printUsage(console.error);
Deno.exit(1);
}
const mode = Deno.args[1] as keyof typeof ppmCorrespondanceTables;
const correspondanceTable = ppmCorrespondanceTables[mode];
const label = Deno.args[2];
const path = Deno.args[3];
const instructionLength = parseInt(Deno.args[0]);
const ppmData = await getDataFromPPM(path);
const asm = ppmBytesToCorrespondingAsmConstU8(label, ppmData, correspondanceTable, instructionLength);
console.log(asm);

View File

@@ -25,19 +25,29 @@ The following branches contains variations, allowing comparisons of performances
## 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.
Both scripts uses Deno's API, so you'll need to have a `deno` binary in your PATH.
It also convert the PPM assets (map and sprites) into ASM raw values representation and append it after all the code.
### PPM to Symfony assembly
Output the Symfony assembly representation of the given PPM file.
Usage example:
```sh
./build.ts > /path/to/the/game/schematics/architecture/Symfony/sandbox/main.asm
# script output can easily be piped to strip it off all comments, leading whitespace, double spaces and empty lines:
./build.ts | sed -r 's/\s*;.*//' | sed -r 's/^\s+//' | sed -r 's/ +/ /' | sed -r '/^\s*$/d' > result.asm
./ppmToAsm.ts map map ./assets/map.ppm > ./dist/map.asm
./ppmToAsm.ts sprite robot ./assets/sprites/robot.ppm > ./dist/robot.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.
### Bundle and minify
Bundle all `.asm` and `.ppm` files and output the (optionaly minified) result.
Usage example:
```sh
./build.ts --minify main.asm > pacman.asm
# To beforehand convert all ppm files
./build.ts --ppm --minify main.asm > pacman.asm
```
## Documentation

View File

@@ -2,16 +2,3 @@ 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

View File

@@ -1,21 +1,21 @@
; 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
/* 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

View File

@@ -7,12 +7,23 @@ 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.empty = 0 ; must stay 0 (some code avoid addition when it knows the tile is empty)
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
pub const sprites.robot_up = 5
pub const sprites.robot_right = 6
pub const sprites.robot_down = 7
pub const sprites.robot_left = 8
pub const sprites.ghost = 9
pub const sprites.ghost_up = 9
pub const sprites.ghost_right = 10
pub const sprites.ghost_down = 11
pub const sprites.ghost_left = 12
pub const sprites.coin = 13
pub const sprites.max = 13 ; for lookup table

View File

@@ -1,70 +0,0 @@
;
; Jump table initialisation
;
; r1: sprite id
; r2: pointer address
; r3: pointer value
; sprite_empty
add r1, zr, sprites.empty
lsl r2, r1, arch.instruction_increment_shift
add r2, r2, ptr.textures_addresses
add r3, zr, sprite_empty
store_32 [r2], r3
; sprite_unimplemented
add r1, zr, sprites.unimplemented
lsl r2, r1, arch.instruction_increment_shift
add r2, r2, ptr.textures_addresses
add r3, zr, sprite_unimplemented
store_32 [r2], r3
; sprite_wall_end
add r1, zr, sprites.wall_end
lsl r2, r1, arch.instruction_increment_shift
add r2, r2, ptr.textures_addresses
add r3, zr, sprite_wall_end
store_32 [r2], r3
; sprite_wall_straight
add r1, zr, sprites.wall_straight
lsl r2, r1, arch.instruction_increment_shift
add r2, r2, ptr.textures_addresses
add r3, zr, sprite_wall_straight
store_32 [r2], r3
; sprite_wall_corner
add r1, zr, sprites.wall_corner
lsl r2, r1, arch.instruction_increment_shift
add r2, r2, ptr.textures_addresses
add r3, zr, sprite_wall_corner
store_32 [r2], r3
; sprite_robot
add r1, zr, sprites.robot
lsl r2, r1, arch.instruction_increment_shift
add r2, r2, ptr.textures_addresses
add r3, zr, sprite_robot
store_32 [r2], r3
; sprite_ghost
add r1, zr, sprites.ghost
lsl r2, r1, arch.instruction_increment_shift
add r2, r2, ptr.textures_addresses
add r3, zr, sprite_ghost
store_32 [r2], r3
; sprite_coin
add r1, zr, sprites.coin
lsl r2, r1, arch.instruction_increment_shift
add r2, r2, ptr.textures_addresses
add r3, zr, sprite_coin
store_32 [r2], r3
;
; Screen initialization
;
; r1 = screen parameter value
mov r1, screen.mode_index
screen r1, screen.mode_value
mov r1, screen.offset_index
add r2, zr, ptr.screen
screen r1, r2
mov r1, screen.resolution_index
screen r1, screen.resolution_value

View File

@@ -1,24 +1,13 @@
include ../consts/offsets
include ../consts/map
include ../consts/screen
include ../consts/scene
include ../consts/sprites
include sprite_rotation
/* Draw the whole map (fill the screen memory with the values of the sprites based on the map)
; /* 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`)
; */
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
@@ -52,19 +41,71 @@ pub drawing.draw_whole_map:
ret
; /* Return the top-left pixel address of the tile at the given map coordinates
/* Draw the tile corresponding to the given coordinate with it's map content
; Inputs:
; r1: map x coord
; r2: map y coord
Inputs:
r1, r2: Map coordinates x, y
; Locals:
; r3: counter
; r4: offset per tile row (screen width * tile height)
Locals:
r3: tile value / sprite id / sprite address offset
r9: pointer
r13: screen address
*/
pub drawing.clear_tile:
push r1
push r2
push r3
push r9
push r13
; Ouputs:
; r13: the pixel address
; */
add r9, r1, map
push r2
pub drawing.clear_tile_row_loop:
cmp r2, 0
je drawing.clear_tile_row_loop_end
sub r2, r2, 1
add r9, r9, map.width
jmp drawing.clear_tile_row_loop
pub drawing.clear_tile_row_loop_end:
pop r2
load_8 r3, [r9]
cmp r3, map.tiles.coin
add r9, zr, ptr.textures_addresses
jne drawing.clear_tile_get_screen_address
; coin
mov r3, sprites.coin
lsl r3, r3, arch.instruction_increment_shift
add r9, r9, r3
pub drawing.clear_tile_get_screen_address:
call get_tile_address
mov r1, r13
load_32 r2, [r9]
mov r3, sprites.rotation_0
call draw_sprite
pop r13
pop r9
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
@@ -111,17 +152,18 @@ get_tile_address:
pop r3
ret
; /* Get address of given sprite id
; Inputs:
; r1: sprite id
/* Get address of given sprite id
; Outputs:
; r13: address
Inputs:
r1: sprite id
; Locals:
; r2: pointer address
; */
Outputs:
r13: address
Locals:
r2: pointer address
*/
get_sprite_address:
push r1
push r2
@@ -139,21 +181,19 @@ get_sprite_address:
pop r1
ret
; /* Draw given sprite on the screen at given map coordinates
/* 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
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`
; */
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
@@ -179,24 +219,22 @@ pub drawing.draw_sprite_at_map_coord:
pop r1
ret
; /* Draw given sprite (by address) at given screen position (by address) with given rotation
/* Draw given sprite (by address) at given screen position (by address) with given rotation
; Inputs:
; r1: screen address
; r2: sprite address
; r3: 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
; */
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

View File

@@ -1,13 +1,12 @@
/* Return the tile address from the given map coordinate
; /* Return the tile address from the given map coordinate
Inputs:
r1: x map coordinate
r2: y map coordinate
; Inputs:
; r1: x map coordinate
; r2: y map coordinate
; Outputs:
; r13: tile address
; */
Outputs:
r13: tile address
*/
pub game.get_tile_address_from_map_coord:
push r1
push r2

View File

@@ -1,26 +1,22 @@
include ../consts/offsets
include ../consts/map
include ../consts/sprites
/* Return the sprite id and rotation for the given map coordinates
; /* Return the sprite id and rotation for the given map coordinates
Inputs:
r1: x map coord
r2: y map coord
; Inputs:
; r1: x map coord
; r2: y map coord
Outputs:
r12: sprite rotation
r13: sprite id
; 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
; */
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

18
src/lib/u64.asm Normal file
View File

@@ -0,0 +1,18 @@
/* Add two unsigned 64 bit
Input:
r1, r2: A.high, A.low
r3, r4: B.high, B.low
Output:
r5, r6: C.high, C.low
*/
pub u64.add:
add r5, r1, r3
add r6, r2, r4
cmp r6, r2
jae u64.add_no_low_overflow
add r5, r5, 1 ; overflow
pub u64.add_no_low_overflow:
ret

View File

@@ -1,350 +1,69 @@
include consts/arch
include consts/game
include consts/keyboard
include consts/map
include consts/scene
include consts/screen
include consts/sprites
include init
include sections/init_sprite_lookup_table
include sections/init_screen
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
add r6, zr, ptr.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
add r6, zr, ptr.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
;
add r1, zr, ptr.tick_duration_low
load_32 r2, [r1]
add r1, zr, ptr.tick_duration_high
load_32 r3, [r1]
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:
add r1, zr, ptr.next_tick_low
store_32 [r1], r4
add r1, zr, ptr.next_tick_high
store_32 [r1], r5
include sections/init_robot_state
include sections/init_ghost_state
include sections/init_timers
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
include sections/handle_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
/*
For all timers:
r1, r2: tick duration high, low
r7, r8: now high, low
*/
add r9, zr, ptr.tick_duration_high
load_32 r1, [r9]
add r9, zr, ptr.tick_duration_low
load_32 r2, [r9]
time_0 r8
time_1 r7
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, ptr.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
add r1, zr, ptr.next_tick_low
load_32 r4, [r1]
add r1, zr, ptr.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:
add r1, zr, ptr.tick_duration_low
load_32 r2, [r1]
add r1, zr, ptr.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:
add r1, zr, ptr.next_tick_low
store_32 [r1], r4
add r1, zr, ptr.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
add r1, zr, ptr.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 r1
push r2
mov r1, r5
mov r2, r6
call game.get_tile_address_from_map_coord
mov r7, r13
push r7
push r8
include sections/timer_robot
pop r8
pop r7
pop r2
pop r1
;
; Collision handling
;
load_8 r8, [r7]
cmp r8, map.tiles.wall
jne game_loop_robot_movement_some
; hit wall
add r1, zr, ptr.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
add r1, zr, ptr.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
add r1, zr, ptr.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
add r1, zr, ptr.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
add r1, zr, ptr.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:
include sections/timer_ghost
jmp game_loop
include lib/u64
include lib/game
include lib/drawing
include lib/sprite_rotation
include dist/empty
include dist/placeholder
include dist/wall_end
include dist/wall_straight
include dist/wall_corner
include dist/robot_up
include dist/robot_right
include dist/robot_down
include dist/robot_left
include dist/ghost_up
include dist/ghost_right
include dist/ghost_down
include dist/ghost_left
include dist/coin
include dist/map
include reserved_space

View File

@@ -5,8 +5,14 @@ U32 0 ; placeholder
U32 0 ; wall_end
U32 0 ; wall_straight
U32 0 ; wall_corner
U32 0 ; robot
U32 0 ; ghost
U32 0 ; robot up
U32 0 ; robot right
U32 0 ; robot down
U32 0 ; robot left
U32 0 ; ghost up
U32 0 ; ghost right
U32 0 ; ghost down
U32 0 ; ghost left
U32 0 ; coin
; 0.5 second = 500_000_000 nano second = 0x0000_0000_1DCD_6500
@@ -17,8 +23,11 @@ pub ptr.tick_duration_low: U32 0x1DCD_6500
; pub ptr.tick_duration_high: U32 0x0000_0000
; pub ptr.tick_duration_low: U32 0x3B9A_CA00
pub ptr.next_tick_high: U32 0
pub ptr.next_tick_low: U32 0
pub ptr.timer_robot_next_tick_high: U32 0x0000_0000
pub ptr.timer_robot_next_tick_low: U32 0x1DCD_6500
pub ptr.timer_ghost_next_tick_high: U32 0
pub ptr.timer_ghost_next_tick_low: U32 0x0EE6_B280
pub ptr.state_robot:
U8 0 ; x coord

View File

@@ -0,0 +1,43 @@
/* 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 handle_event_end
cmp r2, keyboard.key_min
jb handle_event_end
cmp r2, keyboard.key_max
ja handle_event_end
sub r4, r2, keyboard.key_min ; so the first value is 0
lsl r4, r4, 3 ; 2 * instruction length = 8
add r4, r4, handle_event_jump_table_start
jmp r4
handle_event_jump_table_start:
; key_up
mov r1, game.direction_up
jmp handle_event_store_direction
; key_right
mov r1, game.direction_right
jmp handle_event_store_direction
; key_down:
mov r1, game.direction_down
jmp handle_event_store_direction
; key_left:
mov r1, game.direction_left
;jmp handle_event_end
handle_event_store_direction:
add r2, zr, ptr.state_robot
add r2, r2, 2
store_8 [r2], r1
handle_event_end:

View File

@@ -0,0 +1,17 @@
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
add r6, zr, ptr.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_down
call drawing.draw_sprite_at_map_coord

View File

@@ -0,0 +1,17 @@
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
add r6, zr, ptr.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_down
call drawing.draw_sprite_at_map_coord

View File

@@ -0,0 +1,10 @@
; r1 = screen parameter value
mov r1, screen.mode_index
screen r1, screen.mode_value
mov r1, screen.offset_index
add r2, zr, ptr.screen
screen r1, r2
mov r1, screen.resolution_index
screen r1, screen.resolution_value

View File

@@ -0,0 +1,94 @@
/* Jump table initialisation
r1: sprite id
r2: pointer address
r3: pointer value
*/
; sprite_empty
add r1, zr, sprites.empty
lsl r2, r1, arch.instruction_increment_shift
add r2, r2, ptr.textures_addresses
add r3, zr, sprite_empty
store_32 [r2], r3
; sprite_unimplemented
add r1, zr, sprites.unimplemented
lsl r2, r1, arch.instruction_increment_shift
add r2, r2, ptr.textures_addresses
add r3, zr, sprite_unimplemented
store_32 [r2], r3
; sprite_wall_end
add r1, zr, sprites.wall_end
lsl r2, r1, arch.instruction_increment_shift
add r2, r2, ptr.textures_addresses
add r3, zr, sprite_wall_end
store_32 [r2], r3
; sprite_wall_straight
add r1, zr, sprites.wall_straight
lsl r2, r1, arch.instruction_increment_shift
add r2, r2, ptr.textures_addresses
add r3, zr, sprite_wall_straight
store_32 [r2], r3
; sprite_wall_corner
add r1, zr, sprites.wall_corner
lsl r2, r1, arch.instruction_increment_shift
add r2, r2, ptr.textures_addresses
add r3, zr, sprite_wall_corner
store_32 [r2], r3
; sprite_robot_up
add r1, zr, sprites.robot_up
lsl r2, r1, arch.instruction_increment_shift
add r2, r2, ptr.textures_addresses
add r3, zr, sprite_robot_up
store_32 [r2], r3
; sprite_robot_right
add r1, zr, sprites.robot_right
lsl r2, r1, arch.instruction_increment_shift
add r2, r2, ptr.textures_addresses
add r3, zr, sprite_robot_right
store_32 [r2], r3
; sprite_robot_down
add r1, zr, sprites.robot_down
lsl r2, r1, arch.instruction_increment_shift
add r2, r2, ptr.textures_addresses
add r3, zr, sprite_robot_down
store_32 [r2], r3
; sprite_robot_left
add r1, zr, sprites.robot_left
lsl r2, r1, arch.instruction_increment_shift
add r2, r2, ptr.textures_addresses
add r3, zr, sprite_robot_left
store_32 [r2], r3
; sprite_ghost_up
add r1, zr, sprites.ghost_up
lsl r2, r1, arch.instruction_increment_shift
add r2, r2, ptr.textures_addresses
add r3, zr, sprite_ghost_up
store_32 [r2], r3
; sprite_ghost_right
add r1, zr, sprites.ghost_right
lsl r2, r1, arch.instruction_increment_shift
add r2, r2, ptr.textures_addresses
add r3, zr, sprite_ghost_right
store_32 [r2], r3
; sprite_ghost_down
add r1, zr, sprites.ghost_down
lsl r2, r1, arch.instruction_increment_shift
add r2, r2, ptr.textures_addresses
add r3, zr, sprite_ghost_down
store_32 [r2], r3
; sprite_ghost_left
add r1, zr, sprites.ghost_left
lsl r2, r1, arch.instruction_increment_shift
add r2, r2, ptr.textures_addresses
add r3, zr, sprite_ghost_left
store_32 [r2], r3
; sprite_coin
add r1, zr, sprites.coin
lsl r2, r1, arch.instruction_increment_shift
add r2, r2, ptr.textures_addresses
add r3, zr, sprite_coin
store_32 [r2], r3

View File

@@ -0,0 +1,34 @@
/*
r1, r2: now high, low
r3, r4: initial delay high, low
r5, r6: next tick high, low
r7: pointer
*/
time_0 r2
time_1 r1
; Robot
add r7, zr, ptr.timer_robot_next_tick_high
load_32 r3, [r7]
add r7, zr, ptr.timer_robot_next_tick_low
load_32 r4, [r7]
call u64.add
add r7, zr, ptr.timer_robot_next_tick_high
store_32 [r7], r5
add r7, zr, ptr.timer_robot_next_tick_low
store_32 [r7], r6
; Ghost
add r7, zr, ptr.timer_ghost_next_tick_high
load_32 r3, [r7]
add r7, zr, ptr.timer_ghost_next_tick_low
load_32 r4, [r7]
call u64.add
add r7, zr, ptr.timer_ghost_next_tick_high
store_32 [r7], r5
add r7, zr, ptr.timer_ghost_next_tick_low
store_32 [r7], r6

View File

@@ -0,0 +1,99 @@
/* Timer comparison / update
r1, r2: tick duration high, low [IN]
r3, r4: next tick high, low
r5, r6: tick after this one high, low
r7, r8: now high, low [IN]
r9: pointer
*/
add r9, zr, ptr.timer_ghost_next_tick_high
load_32 r3, [r9]
add r9, zr, ptr.timer_ghost_next_tick_low
load_32 r4, [r9]
cmp r7, r3
jb timer_ghost.end
ja timer_ghost.trigger
cmp r8, r4
jb timer_ghost.end
pub timer_ghost.trigger:
; udate next_tick
call u64.add
add r9, zr, ptr.timer_ghost_next_tick_high
store_32 [r9], r5
add r9, zr, ptr.timer_ghost_next_tick_low
store_32 [r9], r6
/* Movement
r1, r2: x, y
r3: rotation
r4: direction / sprite id
r5, r6: increments x, y
r7, r8: [RESERVED]
r9: pointer
r10: tile content
r13: tile address
*/
add r9, zr, ptr.state_ghost
load_8 r1, [r9]
add r9, r9, 1
load_8 r2, [r9]
add r9, r9, 1
load_8 r4, [r9]
call drawing.clear_tile
mov r5, 0
mov r6, 0
lsl r9, r4, 3 ; 2 * instruction size = 8
add r9, r9, timer_ghost.direction_jump_table
jmp r9
pub timer_ghost.direction_jump_table:
; direction_up
sub r6, r6, 1
jmp timer_ghost.move
; direction_right
add r5, r5, 1
jmp timer_ghost.move
; direction_down
add r6, r6, 1
jmp timer_ghost.move
; direction_left
sub r5, r5, 1
;jmp timer_ghost.move
pub timer_ghost.move:
add r1, r1, r5
add r2, r2, r6
add r9, zr, ptr.state_ghost
store_8 [r9], r1
add r9, r9, 1
store_8 [r9], r2
; draw ghost on new position
mov r3, sprites.rotation_0
add r4, r4, sprites.ghost
call drawing.draw_sprite_at_map_coord
/*
Check ahead for direction change
*/
add r1, r1, r5
add r2, r2, r6
call game.get_tile_address_from_map_coord
load_8 r10, [r13]
cmp r10, map.tiles.wall
jne timer_ghost.end
; increment direction
add r9, zr, ptr.state_ghost
add r9, r9, 2
load_8 r4, [r9]
add r4, r4, 1
and r4, r4, 0b11 ; modulo 3
store_8 [r9], r4
pub timer_ghost.end:

View File

@@ -0,0 +1,99 @@
/* Timer comparison / update
r1, r2: tick duration high, low [IN]
r3, r4: next tick high, low
r5, r6: tick after this one high, low
r7, r8: now high, low [IN]
r9: pointer
*/
add r9, zr, ptr.timer_robot_next_tick_high
load_32 r3, [r9]
add r9, zr, ptr.timer_robot_next_tick_low
load_32 r4, [r9]
cmp r7, r3
jb timer_robot.end
ja timer_robot.trigger
cmp r8, r4
jb timer_robot.end
pub timer_robot.trigger:
; udate next_tick
call u64.add
add r9, zr, ptr.timer_robot_next_tick_high
store_32 [r9], r5
add r9, zr, ptr.timer_robot_next_tick_low
store_32 [r9], r6
/* Movement
r1, r2: x, y
r3: rotation
r4: direction / sprite id
r5, r6: increments x, y
r7, r8: [RESERVED]
r9: pointer
r10: tile content
r13: tile address
*/
add r9, zr, ptr.state_robot
load_8 r1, [r9]
add r9, r9, 1
load_8 r2, [r9]
add r9, r9, 1
load_8 r4, [r9]
call drawing.clear_tile
mov r5, 0
mov r6, 0
lsl r9, r4, 3 ; 2 * instruction size = 8
add r9, r9, timer_robot.direction_jump_table
jmp r9
pub timer_robot.direction_jump_table:
; direction_up
sub r6, r6, 1
jmp timer_robot.try_move
; direction_right
add r5, r5, 1
jmp timer_robot.try_move
; direction_down
add r6, r6, 1
jmp timer_robot.try_move
; direction_left
sub r5, r5, 1
;jmp timer_robot.try_move
/*
Collision handling
*/
pub timer_robot.try_move:
add r1, r1, r5
add r2, r2, r6
call game.get_tile_address_from_map_coord
load_8 r10, [r13]
cmp r10, map.tiles.wall
jne timer_robot.move
; hit wall -> cancel movement
sub r1, r1, r5
sub r2, r2, r6
jmp timer_robot.draw
pub timer_robot.move:
; TODO: handle coin and empty differently
mov r10, map.tiles.empty
store_8 [r13], r10
add r9, zr, ptr.state_robot
store_8 [r9], r1
add r9, r9, 1
store_8 [r9], r2
pub timer_robot.draw:
; draw robot on new position
mov r3, sprites.rotation_0
add r4, r4, sprites.robot
call drawing.draw_sprite_at_map_coord
pub timer_robot.end: