Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dc43e7aa0b | |||
| c7360be7fa | |||
| 356cf3fb94 | |||
| b7bc9956f9 | |||
| 5231f54c27 | |||
| aad1fa45f7 | |||
| 88f10f9f06 | |||
| 6cdb7304c2 | |||
| e6b2d8b086 | |||
| 5588d4c29a | |||
| e3cf5cdfa1 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
dist/
|
|
||||||
Binary file not shown.
Binary file not shown.
BIN
assets/sprites/ghost.ppm
Normal file
BIN
assets/sprites/ghost.ppm
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
assets/sprites/robot.ppm
Normal file
BIN
assets/sprites/robot.ppm
Normal file
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.
433
build.ts
433
build.ts
@@ -1,268 +1,229 @@
|
|||||||
#!/usr/bin/env -S deno run --allow-read --allow-write --allow-run
|
#!/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";
|
import { TextLineStream } from "jsr:@std/streams@1.1.0";
|
||||||
|
|
||||||
/**
|
const asmFilePaths = [
|
||||||
* CONFIG
|
"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';
|
||||||
|
|
||||||
const instructionLength = 4; // in bytes
|
const instructionLength = 4; // in bytes
|
||||||
const rawDataInstructionLengthPrefix = "U32";
|
const mapWidth = 15;
|
||||||
|
const spriteWidth = 17;
|
||||||
|
|
||||||
const ppmToAsmScriptPath = "./ppmToAsm.ts";
|
const mapMatchingStrings = {
|
||||||
|
0x00_00_00: '0', // Empty
|
||||||
|
0xFF_00_00: '1', // Wall
|
||||||
|
0xFF_FF_00: '2', // Coin
|
||||||
|
};
|
||||||
|
|
||||||
const mapPath = "assets/map.ppm";
|
const spritesMatchingStrings = {
|
||||||
|
0x00_00_00: '0b000_000_00',
|
||||||
|
0x00_00_FF: '0b000_000_11',
|
||||||
|
0x00_FF_00: '0b000_111_00',
|
||||||
|
0x99_50_00: `0b010_001_00`,
|
||||||
|
0x77_77_77: '0b011_011_10',
|
||||||
|
0xB3_B3_B3: '0b100_100_10',
|
||||||
|
0xFF_00_00: '0b111_000_00',
|
||||||
|
0xFF_00_FF: '0b111_000_11',
|
||||||
|
0xFF_80_00: '0b111_100_00',
|
||||||
|
0xFF_FF_00: '0b111_110_00',
|
||||||
|
0xFF_FF_FF: '0b111_111_11',
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapPath = 'assets/map.ppm';
|
||||||
const spritesPath = {
|
const spritesPath = {
|
||||||
"sprite_empty": "assets/sprites/empty.ppm",
|
'sprite_empty': 'assets/sprites/empty.ppm',
|
||||||
"sprite_unimplemented": "assets/sprites/placeholder.ppm",
|
'sprite_unimplemented': 'assets/sprites/placeholder.ppm',
|
||||||
"sprite_wall_end": "assets/sprites/wall_end.ppm",
|
'sprite_wall_end': 'assets/sprites/wall_end.ppm',
|
||||||
"sprite_wall_straight": "assets/sprites/wall_straight.ppm",
|
'sprite_wall_straight': 'assets/sprites/wall_straight.ppm',
|
||||||
"sprite_wall_corner": "assets/sprites/wall_corner.ppm",
|
'sprite_wall_corner': 'assets/sprites/wall_corner.ppm',
|
||||||
"sprite_robot_up": "assets/sprites/robot_up.ppm",
|
'sprite_robot': 'assets/sprites/robot.ppm',
|
||||||
"sprite_robot_right": "assets/sprites/robot_right.ppm",
|
'sprite_ghost': 'assets/sprites/ghost.ppm',
|
||||||
"sprite_robot_down": "assets/sprites/robot_down.ppm",
|
'sprite_coin': 'assets/sprites/coin.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",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* END OF CONFIG
|
* START OF BUILD
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class Minifier {
|
for (const path of asmFilePaths) {
|
||||||
private buffer = "";
|
const file = await Deno.open(path);
|
||||||
private lines: Array<string> = [];
|
|
||||||
private insideComment = false;
|
|
||||||
stream;
|
|
||||||
|
|
||||||
constructor(writableStream: WritableStream) {
|
|
||||||
this.stream = new TransformStream<string>({
|
|
||||||
transform: (chunk, controller) => {
|
|
||||||
this.buffer += chunk;
|
|
||||||
controller.enqueue(this.processChunk());
|
|
||||||
},
|
|
||||||
|
|
||||||
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 { code, stdout, stderr } = await command.output();
|
|
||||||
|
|
||||||
const decoder = new TextDecoder();
|
|
||||||
|
|
||||||
if (code !== 0) {
|
|
||||||
console.error(`ppmToAsm script returned non-zero value: ${code}`);
|
|
||||||
console.error(`STDERR: \n${decoder.decode(stderr)}`);
|
|
||||||
Deno.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
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())
|
const readable = file.readable.pipeThrough(new TextDecoderStream())
|
||||||
.pipeThrough(new TextLineStream());
|
.pipeThrough(new TextLineStream());
|
||||||
|
|
||||||
for await (const line of readable) {
|
console.log(`
|
||||||
const match = line.match(includeExpressionRegex);
|
;/*******************************
|
||||||
if (match === null) {
|
;* ${path}
|
||||||
await output.write(`${line}\n`);
|
;*******************************/
|
||||||
continue;
|
`);
|
||||||
}
|
|
||||||
|
|
||||||
await build(pwd, `${match[1]}.asm`, output);
|
for await (const line of readable) {
|
||||||
|
if (!line.startsWith("include ")) {
|
||||||
|
console.log(line);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
Options:
|
const asmRawRepresentations = [];
|
||||||
\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.`);
|
const mapBytes = await getDataFromPPM(mapPath);
|
||||||
|
asmRawRepresentations.push(bytesToAsmConstU8('map', mapBytes, mapMatchingStrings, mapWidth));
|
||||||
|
|
||||||
|
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)`);
|
||||||
|
Deno.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
stringValues.push(`U8 ${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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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`
|
||||||
|
|
||||||
|
return `${label}:
|
||||||
|
${lines.join("\n")}
|
||||||
|
${label}_end:${paddingString}`
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SCRIPT
|
* 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,
|
||||||
|
];
|
||||||
|
|
||||||
if (Deno.args.length < 1) {
|
const hashtag = 0x23;
|
||||||
printUsage(console.error);
|
|
||||||
Deno.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let minify = false;
|
const content = await Deno.readFile(path);
|
||||||
let convertPPM = false;
|
let offset = 0;
|
||||||
let path: string | undefined = undefined;
|
|
||||||
|
|
||||||
Deno.args.forEach((arg, index) => {
|
// magic number
|
||||||
switch (arg) {
|
while(!whitespaces.includes(content[offset])) {
|
||||||
case "--help":
|
offset++;
|
||||||
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;
|
offset++; // whitespace
|
||||||
|
|
||||||
|
// potential comment
|
||||||
|
if (content[offset] === hashtag) {
|
||||||
|
while(!endOfComment.includes(content[offset])) {
|
||||||
|
offset++;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
if (path === undefined) {
|
offset++; // end of comment
|
||||||
printUsage(console.error);
|
}
|
||||||
Deno.exit(1);
|
|
||||||
|
// 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 (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
184
ppmToAsm.ts
@@ -1,184 +0,0 @@
|
|||||||
#!/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);
|
|
||||||
38
readme.md
38
readme.md
@@ -9,45 +9,23 @@ It thus should be able to run on the architecture and ISA the players have built
|
|||||||
The `main` branch contains a minimal implementation of the game.
|
The `main` branch contains a minimal implementation of the game.
|
||||||
|
|
||||||
The following branches contains variations, allowing comparisons of performances, visuals, gameplay, ...
|
The following branches contains variations, allowing comparisons of performances, visuals, gameplay, ...
|
||||||
- `res-0-prerendered`:
|
- `res-2`: 256x192 screen resolution, 17x17 tile
|
||||||
- `80x60` screen resolution
|
|
||||||
- `5x5` tile resolution
|
|
||||||
- initial screen prerendered
|
|
||||||
- no sprite rotation
|
|
||||||
- `res-2`:
|
|
||||||
- `256x192` screen resolution
|
|
||||||
- `17x17` tile resolution
|
|
||||||
- `res-2-prerendered`:
|
|
||||||
- `256x192` screen resolution
|
|
||||||
- `17x17` tile resolution
|
|
||||||
- initial screen prerendered
|
|
||||||
- no sprite rotation
|
|
||||||
|
|
||||||
## Build script
|
## Build script
|
||||||
|
|
||||||
Both scripts uses Deno's API, so you'll need to have a `deno` binary in your PATH.
|
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.
|
||||||
|
|
||||||
### PPM to Symfony assembly
|
It also convert the PPM assets (map and sprites) into ASM raw values representation and append it after all the code.
|
||||||
|
|
||||||
Output the Symfony assembly representation of the given PPM file.
|
|
||||||
|
|
||||||
Usage example:
|
Usage example:
|
||||||
```sh
|
```sh
|
||||||
./ppmToAsm.ts map map ./assets/map.ppm > ./dist/map.asm
|
./build.ts > /path/to/the/game/schematics/architecture/Symfony/sandbox/main.asm
|
||||||
./ppmToAsm.ts sprite robot ./assets/sprites/robot.ppm > ./dist/robot.asm
|
|
||||||
|
# script output can easily be piped to strip it off all comments, leading whitespace and empty lines:
|
||||||
|
./build.ts | sed 's/\s*;.*//' | sed 's/^\s*//' | sed '/^\d*$/d' > result.asm
|
||||||
```
|
```
|
||||||
|
|
||||||
### Bundle and minify
|
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 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
|
## Documentation
|
||||||
|
|
||||||
|
|||||||
@@ -2,3 +2,16 @@ pub const game.direction_up = 0
|
|||||||
pub const game.direction_right = 1
|
pub const game.direction_right = 1
|
||||||
pub const game.direction_down = 2
|
pub const game.direction_down = 2
|
||||||
pub const game.direction_left = 3
|
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
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
; map size 16x12 (or 15x11 with margins)
|
|
||||||
pub const map.width = 15
|
pub const map.width = 15
|
||||||
pub const map.height = 11
|
pub const map.height = 11
|
||||||
pub const map.size = 165 ; 11*15
|
pub const map.size = 165 ; 11*15
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
; given:
|
; given:
|
||||||
; - 16x12 tiles map
|
; -> 256x192 pixels screen
|
||||||
; - 5x5 pixels tile
|
; -> 15x11 tiles map
|
||||||
|
; => 17x17 pixels tile
|
||||||
|
|
||||||
pub const scene.margin_top = 2
|
pub const scene.margin_top = 2
|
||||||
pub const scene.margin_bottom = 3
|
pub const scene.margin_bottom = 3
|
||||||
pub const scene.margin_left = 2
|
pub const scene.margin_left = 0
|
||||||
pub const scene.margin_right = 3
|
pub const scene.margin_right = 1
|
||||||
@@ -1,26 +1,26 @@
|
|||||||
/* Screen pixel mode (settings #0)
|
; Screen pixel mode (settings #0)
|
||||||
0: ASCII 8
|
; 0: ASCII 8
|
||||||
1: ASCII 24
|
; 1: ASCII 24
|
||||||
2: Pixel 8
|
; 2: Pixel 8
|
||||||
3: Pixel 24
|
; 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
|
||||||
|
|
||||||
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_index = 0
|
||||||
pub const screen.mode_value = 2
|
pub const screen.mode_value = 2
|
||||||
pub const screen.offset_index = 1
|
pub const screen.offset_index = 1
|
||||||
pub const screen.resolution_index = 2
|
pub const screen.resolution_index = 2
|
||||||
pub const screen.resolution_value = 0
|
pub const screen.resolution_value = 2
|
||||||
|
|
||||||
pub const screen.width = 80
|
pub const screen.width = 256
|
||||||
pub const screen.height = 60
|
pub const screen.height = 192
|
||||||
|
|||||||
@@ -1,29 +1,18 @@
|
|||||||
pub const sprites.width = 5
|
pub const sprites.width = 17
|
||||||
pub const sprites.height = 5 ; unused as sprite are all square
|
pub const sprites.height = 17 ; unused as sprite are all square
|
||||||
pub const sprites.size = 25 ; height x width
|
pub const sprites.size = 289 ; height x width
|
||||||
|
|
||||||
pub const sprites.rotation_0 = 0
|
pub const sprites.rotation_0 = 0
|
||||||
pub const sprites.rotation_90 = 1
|
pub const sprites.rotation_90 = 1
|
||||||
pub const sprites.rotation_180 = 2
|
pub const sprites.rotation_180 = 2
|
||||||
pub const sprites.rotation_270 = 3
|
pub const sprites.rotation_270 = 3
|
||||||
|
|
||||||
pub const sprites.empty = 0 ; must stay 0 (some code avoid addition when it knows the tile is empty)
|
pub const sprites.empty = 0
|
||||||
pub const sprites.unimplemented = 1
|
pub const sprites.unimplemented = 1
|
||||||
pub const sprites.wall_end = 2
|
pub const sprites.wall_end = 2
|
||||||
pub const sprites.wall_straight = 3
|
pub const sprites.wall_straight = 3
|
||||||
pub const sprites.wall_corner = 4
|
pub const sprites.wall_corner = 4
|
||||||
|
|
||||||
pub const sprites.robot = 5
|
pub const sprites.robot = 5
|
||||||
pub const sprites.robot_up = 5
|
pub const sprites.ghost = 6
|
||||||
pub const sprites.robot_right = 6
|
pub const sprites.coin = 7
|
||||||
pub const sprites.robot_down = 7
|
pub const sprites.max = 7 ; for lookup table
|
||||||
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
|
|
||||||
|
|||||||
70
src/init.asm
Normal file
70
src/init.asm
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
;
|
||||||
|
; 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
|
||||||
@@ -1,13 +1,24 @@
|
|||||||
/* Draw the whole map (fill the screen memory with the values of the sprites based on the map)
|
include ../consts/offsets
|
||||||
|
include ../consts/map
|
||||||
|
include ../consts/screen
|
||||||
|
include ../consts/scene
|
||||||
|
include ../consts/sprites
|
||||||
|
include sprite_rotation
|
||||||
|
|
||||||
Locals:
|
; /* Draw the whole (fill the screen memory with the values of the sprites based on the map)
|
||||||
r1: x map coord
|
|
||||||
r2: y map coord
|
; Inputs:
|
||||||
r3: sprite rotation
|
|
||||||
r4: sprite id
|
; Outputs:
|
||||||
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:
|
pub drawing.draw_whole_map:
|
||||||
push r1
|
push r1
|
||||||
push r2
|
push r2
|
||||||
@@ -41,71 +52,19 @@ pub drawing.draw_whole_map:
|
|||||||
|
|
||||||
ret
|
ret
|
||||||
|
|
||||||
/* Draw the tile corresponding to the given coordinate with it's map content
|
; /* Return the top-left pixel address of the tile at the given map coordinates
|
||||||
|
|
||||||
Inputs:
|
; Inputs:
|
||||||
r1, r2: Map coordinates x, y
|
; r1: map x coord
|
||||||
|
; r2: map y coord
|
||||||
|
|
||||||
Locals:
|
; Locals:
|
||||||
r3: tile value / sprite id / sprite address offset
|
; r3: counter
|
||||||
r9: pointer
|
; r4: offset per tile row (screen width * tile height)
|
||||||
r13: screen address
|
|
||||||
*/
|
|
||||||
pub drawing.clear_tile:
|
|
||||||
push r1
|
|
||||||
push r2
|
|
||||||
push r3
|
|
||||||
push r9
|
|
||||||
push r13
|
|
||||||
|
|
||||||
add r9, r1, map
|
; Ouputs:
|
||||||
push r2
|
; r13: the pixel address
|
||||||
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:
|
get_tile_address:
|
||||||
push r3
|
push r3
|
||||||
push r4
|
push r4
|
||||||
@@ -152,18 +111,17 @@ get_tile_address:
|
|||||||
pop r3
|
pop r3
|
||||||
ret
|
ret
|
||||||
|
|
||||||
|
; /* Get address of given sprite id
|
||||||
|
|
||||||
/* Get address of given sprite id
|
; Inputs:
|
||||||
|
; r1: sprite id
|
||||||
|
|
||||||
Inputs:
|
; Outputs:
|
||||||
r1: sprite id
|
; r13: address
|
||||||
|
|
||||||
Outputs:
|
; Locals:
|
||||||
r13: address
|
; r2: pointer address
|
||||||
|
; */
|
||||||
Locals:
|
|
||||||
r2: pointer address
|
|
||||||
*/
|
|
||||||
get_sprite_address:
|
get_sprite_address:
|
||||||
push r1
|
push r1
|
||||||
push r2
|
push r2
|
||||||
@@ -181,19 +139,21 @@ get_sprite_address:
|
|||||||
pop r1
|
pop r1
|
||||||
ret
|
ret
|
||||||
|
|
||||||
/* Draw given sprite on the screen at given map coordinates
|
; /* Draw given sprite on the screen at given map coordinates
|
||||||
|
|
||||||
Inputs:
|
; Inputs:
|
||||||
r1: map x coord
|
; r1: map x coord
|
||||||
r2: map y coord
|
; r2: map y coord
|
||||||
r3: sprite rotation
|
; r3: sprite rotation
|
||||||
r4: sprite id
|
; r4: sprite id
|
||||||
|
|
||||||
Locals:
|
; Outputs:
|
||||||
r1: screen address
|
|
||||||
r2: sprite address
|
; Locals:
|
||||||
r13: value returned by call to `get_tile_address` and `get_sprite_address`
|
; 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:
|
pub drawing.draw_sprite_at_map_coord:
|
||||||
push r1
|
push r1
|
||||||
push r2
|
push r2
|
||||||
@@ -219,22 +179,24 @@ pub drawing.draw_sprite_at_map_coord:
|
|||||||
pop r1
|
pop r1
|
||||||
ret
|
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:
|
; Inputs:
|
||||||
r1: screen address
|
; r1: screen address
|
||||||
r2: sprite address
|
; r2: sprite address
|
||||||
r3: rotation
|
; r3: rotation
|
||||||
|
|
||||||
Locals:
|
; Outputs:
|
||||||
r1: current screen pixel address
|
|
||||||
r4: x
|
; Locals:
|
||||||
r5: y
|
; r1: current screen pixel address
|
||||||
r6: current sprite pixel address
|
; r4: x
|
||||||
r7: color
|
; r5: y
|
||||||
r8: x increment
|
; r6: current sprite pixel address
|
||||||
r9: y increment
|
; r7: color
|
||||||
*/
|
; r8: x increment
|
||||||
|
; r9: y increment
|
||||||
|
; */
|
||||||
draw_sprite:
|
draw_sprite:
|
||||||
push r1
|
push r1
|
||||||
push r2
|
push r2
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
/* Return the tile address from the given map coordinate
|
|
||||||
|
|
||||||
Inputs:
|
; /* Return the tile address from the given map coordinate
|
||||||
r1: x map coordinate
|
|
||||||
r2: y map coordinate
|
|
||||||
|
|
||||||
Outputs:
|
; Inputs:
|
||||||
r13: tile address
|
; r1: x map coordinate
|
||||||
*/
|
; r2: y map coordinate
|
||||||
|
|
||||||
|
; Outputs:
|
||||||
|
; r13: tile address
|
||||||
|
; */
|
||||||
pub game.get_tile_address_from_map_coord:
|
pub game.get_tile_address_from_map_coord:
|
||||||
push r1
|
push r1
|
||||||
push r2
|
push r2
|
||||||
|
|||||||
@@ -1,22 +1,26 @@
|
|||||||
/* Return the sprite id and rotation for the given map coordinates
|
include ../consts/offsets
|
||||||
|
include ../consts/map
|
||||||
|
include ../consts/sprites
|
||||||
|
|
||||||
Inputs:
|
; /* Return the sprite id and rotation for the given map coordinates
|
||||||
r1: x map coord
|
|
||||||
r2: y map coord
|
|
||||||
|
|
||||||
Outputs:
|
; Inputs:
|
||||||
r12: sprite rotation
|
; r1: x map coord
|
||||||
r13: sprite id
|
; r2: y map coord
|
||||||
|
|
||||||
Locals:
|
; Outputs:
|
||||||
r3: tile id
|
; r12: sprite rotation
|
||||||
r4: map offset
|
; r13: sprite id
|
||||||
r5: counter
|
|
||||||
r6: left tile id
|
; Locals:
|
||||||
r7: up tile id
|
; r3: tile id
|
||||||
r8: right tile id
|
; r4: map offset
|
||||||
r9: down tile id
|
; 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:
|
pub sprite_rotation.get_sprite_id_and_rotation:
|
||||||
push r1
|
push r1
|
||||||
push r2
|
push r2
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
/* 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
|
|
||||||
387
src/main.asm
387
src/main.asm
@@ -1,69 +1,350 @@
|
|||||||
include consts/arch
|
|
||||||
include consts/game
|
|
||||||
include consts/keyboard
|
|
||||||
include consts/map
|
|
||||||
include consts/scene
|
|
||||||
include consts/screen
|
include consts/screen
|
||||||
include consts/sprites
|
|
||||||
|
|
||||||
include sections/init_sprite_lookup_table
|
include init
|
||||||
include sections/init_screen
|
|
||||||
|
|
||||||
call drawing.draw_whole_map
|
call drawing.draw_whole_map
|
||||||
|
|
||||||
include sections/init_robot_state
|
;
|
||||||
include sections/init_ghost_state
|
; State initialization
|
||||||
include sections/init_timers
|
;
|
||||||
|
; 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
|
||||||
|
|
||||||
game_loop:
|
game_loop:
|
||||||
include sections/handle_event
|
;
|
||||||
|
; 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
|
||||||
For all timers:
|
jne game_loop_event_handling_end
|
||||||
r1, r2: tick duration high, low
|
cmp r2, keyboard.key_min
|
||||||
r7, r8: now high, low
|
jb game_loop_event_handling_end
|
||||||
*/
|
cmp r2, keyboard.key_max
|
||||||
add r9, zr, ptr.tick_duration_high
|
ja game_loop_event_handling_end
|
||||||
load_32 r1, [r9]
|
|
||||||
add r9, zr, ptr.tick_duration_low
|
|
||||||
load_32 r2, [r9]
|
|
||||||
time_0 r8
|
|
||||||
time_1 r7
|
|
||||||
|
|
||||||
push r1
|
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 r2
|
push r2
|
||||||
push r7
|
mov r1, r5
|
||||||
push r8
|
mov r2, r6
|
||||||
include sections/timer_robot
|
call game.get_tile_address_from_map_coord
|
||||||
pop r8
|
mov r7, r13
|
||||||
pop r7
|
|
||||||
pop r2
|
pop r2
|
||||||
pop r1
|
|
||||||
|
|
||||||
include sections/timer_ghost
|
;
|
||||||
|
; 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:
|
||||||
|
|
||||||
jmp game_loop
|
jmp game_loop
|
||||||
|
|
||||||
include lib/u64
|
|
||||||
include lib/game
|
|
||||||
include lib/drawing
|
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
|
|
||||||
|
|||||||
@@ -5,14 +5,8 @@ U32 0 ; placeholder
|
|||||||
U32 0 ; wall_end
|
U32 0 ; wall_end
|
||||||
U32 0 ; wall_straight
|
U32 0 ; wall_straight
|
||||||
U32 0 ; wall_corner
|
U32 0 ; wall_corner
|
||||||
U32 0 ; robot up
|
U32 0 ; robot
|
||||||
U32 0 ; robot right
|
U32 0 ; ghost
|
||||||
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
|
U32 0 ; coin
|
||||||
|
|
||||||
; 0.5 second = 500_000_000 nano second = 0x0000_0000_1DCD_6500
|
; 0.5 second = 500_000_000 nano second = 0x0000_0000_1DCD_6500
|
||||||
@@ -23,11 +17,8 @@ pub ptr.tick_duration_low: U32 0x1DCD_6500
|
|||||||
; pub ptr.tick_duration_high: U32 0x0000_0000
|
; pub ptr.tick_duration_high: U32 0x0000_0000
|
||||||
; pub ptr.tick_duration_low: U32 0x3B9A_CA00
|
; pub ptr.tick_duration_low: U32 0x3B9A_CA00
|
||||||
|
|
||||||
pub ptr.timer_robot_next_tick_high: U32 0x0000_0000
|
pub ptr.next_tick_high: U32 0
|
||||||
pub ptr.timer_robot_next_tick_low: U32 0x1DCD_6500
|
pub ptr.next_tick_low: U32 0
|
||||||
|
|
||||||
pub ptr.timer_ghost_next_tick_high: U32 0
|
|
||||||
pub ptr.timer_ghost_next_tick_low: U32 0x0EE6_B280
|
|
||||||
|
|
||||||
pub ptr.state_robot:
|
pub ptr.state_robot:
|
||||||
U8 0 ; x coord
|
U8 0 ; x coord
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
/* 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:
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
; 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
|
|
||||||
@@ -1,94 +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_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
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
/*
|
|
||||||
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
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
/* 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:
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
/* 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:
|
|
||||||
@@ -23,4 +23,4 @@ Resolution ID | Tile size (in pixels) | Comment
|
|||||||
-- | -- | --
|
-- | -- | --
|
||||||
`0` | `5x5` | with a reminder of 5 pixels en width and 5 pixels in height.
|
`0` | `5x5` | with a reminder of 5 pixels en width and 5 pixels in height.
|
||||||
`1` | `10x10` | with a reminder of 10w and 10h
|
`1` | `10x10` | with a reminder of 10w and 10h
|
||||||
`2` | `17x17` | no reminder
|
`2` | `17x17` | with a reminder of 1w and 5h
|
||||||
|
|||||||
Reference in New Issue
Block a user