Compare commits
23 Commits
0.1.0
...
res-0-prer
| Author | SHA1 | Date | |
|---|---|---|---|
| dc42d7c5b6 | |||
| ba12492ebf | |||
| 8b3b2aa20d | |||
| 425b83b1bb | |||
| c78a2ff7a0 | |||
| 92ac475d58 | |||
| 6920d10089 | |||
| e90e792ebf | |||
| 42ea545507 | |||
| c88f67559d | |||
| 2b2e2a6d02 | |||
| c378dc95fa | |||
| 2344a2e874 | |||
| 54ba73c177 | |||
| d5e9dde21d | |||
| 02e1e4c6b3 | |||
| d413170098 | |||
| 2c37af8dd0 | |||
| f96e2420ef | |||
| 17c6b07367 | |||
| f0656aca40 | |||
| b0758c6976 | |||
| 3992863664 |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
dist/
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
assets/sprites/ghost_down.ppm
Normal file
BIN
assets/sprites/ghost_down.ppm
Normal file
Binary file not shown.
BIN
assets/sprites/ghost_left.ppm
Normal file
BIN
assets/sprites/ghost_left.ppm
Normal file
Binary file not shown.
BIN
assets/sprites/ghost_right.ppm
Normal file
BIN
assets/sprites/ghost_right.ppm
Normal file
Binary file not shown.
BIN
assets/sprites/ghost_up.ppm
Normal file
BIN
assets/sprites/ghost_up.ppm
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
assets/sprites/robot_down.ppm
Normal file
BIN
assets/sprites/robot_down.ppm
Normal file
Binary file not shown.
BIN
assets/sprites/robot_left.ppm
Normal file
BIN
assets/sprites/robot_left.ppm
Normal file
Binary file not shown.
BIN
assets/sprites/robot_right.ppm
Normal file
BIN
assets/sprites/robot_right.ppm
Normal file
Binary file not shown.
BIN
assets/sprites/robot_up.ppm
Normal file
BIN
assets/sprites/robot_up.ppm
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
426
build.ts
426
build.ts
@@ -1,232 +1,266 @@
|
|||||||
#!/usr/bin/env -S deno run --allow-read
|
#!/usr/bin/env -S deno run --allow-read --allow-write --allow-run
|
||||||
|
|
||||||
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";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CONFIG
|
||||||
|
*/
|
||||||
|
|
||||||
const instructionLength = 4; // in bytes
|
const instructionLength = 4; // in bytes
|
||||||
const mapWidth = 15;
|
const rawDataInstructionLengthPrefix = "U32";
|
||||||
const spriteWidth = 17;
|
|
||||||
|
|
||||||
const asmFilePaths = [
|
const ppmToAsmScriptPath = "./ppmToAsm.ts";
|
||||||
"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",
|
const mapPath = "assets/map.ppm";
|
||||||
"src/main.asm",
|
|
||||||
|
|
||||||
"src/lib/game.asm",
|
|
||||||
"src/lib/drawing.asm",
|
|
||||||
];
|
|
||||||
const initialScreenPath = 'assets/screen.ppm';
|
|
||||||
const reservedSpacePath = 'src/reserved_space.asm';
|
|
||||||
|
|
||||||
const mapMatchingStrings = {
|
|
||||||
0x00_00_00: '0', // Empty
|
|
||||||
0xFF_00_00: '1', // Wall
|
|
||||||
0xFF_FF_00: '2', // Coin
|
|
||||||
};
|
|
||||||
|
|
||||||
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_robot_up": "assets/sprites/robot_up.ppm",
|
||||||
'sprite_wall_straight': 'assets/sprites/wall_straight.ppm',
|
"sprite_robot_right": "assets/sprites/robot_right.ppm",
|
||||||
'sprite_wall_corner': 'assets/sprites/wall_corner.ppm',
|
"sprite_robot_down": "assets/sprites/robot_down.ppm",
|
||||||
'sprite_robot': 'assets/sprites/robot.ppm',
|
"sprite_robot_left": "assets/sprites/robot_left.ppm",
|
||||||
'sprite_ghost': 'assets/sprites/ghost.ppm',
|
"sprite_ghost_up": "assets/sprites/ghost_up.ppm",
|
||||||
'sprite_coin': 'assets/sprites/coin.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",
|
||||||
|
"screen": "assets/screen.ppm",
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* START OF BUILD
|
* END OF CONFIG
|
||||||
*/
|
*/
|
||||||
|
|
||||||
for (const path of asmFilePaths) {
|
class Minifier {
|
||||||
const file = await Deno.open(path);
|
private buffer = "";
|
||||||
|
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());
|
||||||
|
|
||||||
console.log(`
|
|
||||||
;/*******************************
|
|
||||||
;* ${path}
|
|
||||||
;*******************************/
|
|
||||||
`);
|
|
||||||
|
|
||||||
for await (const line of readable) {
|
for await (const line of readable) {
|
||||||
if (!line.startsWith("include ")) {
|
const match = line.match(includeExpressionRegex);
|
||||||
console.log(line);
|
if (match === null) {
|
||||||
|
await output.write(`${line}\n`);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await build(pwd, `${match[1]}.asm`, output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
const asmRawRepresentations = [];
|
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.
|
||||||
const mapBytes = await getDataFromPPM(mapPath);
|
\t--ppm \tWhen used, the hardcoded ppm files will be translated to their matching Symfony assembly reprensentation into src/dist.`);
|
||||||
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
|
|
||||||
;
|
|
||||||
`);
|
|
||||||
|
|
||||||
const screenBytes = await getDataFromPPM(initialScreenPath);
|
|
||||||
console.log(bytesToAsmConstU8('screen', screenBytes, spritesMatchingStrings, 1));
|
|
||||||
|
|
||||||
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}`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the image pixels data (excluding all other fields) from a given PPM file
|
* SCRIPT
|
||||||
*
|
|
||||||
* 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;
|
if (Deno.args.length < 1) {
|
||||||
|
printUsage(console.error);
|
||||||
|
Deno.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
const content = await Deno.readFile(path);
|
let minify = false;
|
||||||
let offset = 0;
|
let convertPPM = false;
|
||||||
|
let path: string | undefined = undefined;
|
||||||
|
|
||||||
// magic number
|
Deno.args.forEach((arg, index) => {
|
||||||
while(!whitespaces.includes(content[offset])) {
|
switch (arg) {
|
||||||
offset++;
|
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;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
offset++; // whitespace
|
if (path === undefined) {
|
||||||
|
printUsage(console.error);
|
||||||
|
Deno.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
// potential comment
|
if (convertPPM) {
|
||||||
if (content[offset] === hashtag) {
|
await Deno.mkdir(`${Deno.cwd()}/src/dist`, { recursive: true });
|
||||||
while(!endOfComment.includes(content[offset])) {
|
const mapPromise = async () => {
|
||||||
offset++;
|
const assembly = await ppmToAsm("map", "map", mapPath);
|
||||||
}
|
await Deno.writeTextFile(`${Deno.cwd()}/src/dist/map.asm`, assembly);
|
||||||
|
};
|
||||||
|
|
||||||
offset++; // end of comment
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
// image width
|
await Promise.all([
|
||||||
while(numbers.includes(content[offset])) {
|
mapPromise(),
|
||||||
offset++;
|
...spritePromises,
|
||||||
}
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
offset++; // whitespace
|
const encoder = new TextEncoder();
|
||||||
|
const stdoutWritable = new WritableStream<string>({
|
||||||
|
async write(chunk) {
|
||||||
|
await Deno.stdout.write(encoder.encode(chunk));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// image height
|
const writer = minify
|
||||||
while(numbers.includes(content[offset])) {
|
? (new Minifier(stdoutWritable)).stream.writable.getWriter()
|
||||||
offset++;
|
: stdoutWritable.getWriter();
|
||||||
}
|
|
||||||
|
|
||||||
offset++; // whitespace
|
await build(`${Deno.cwd()}/src`, path, writer);
|
||||||
|
await writer.close();
|
||||||
// maximum color value
|
|
||||||
while(numbers.includes(content[offset])) {
|
|
||||||
offset++;
|
|
||||||
}
|
|
||||||
|
|
||||||
offset++; // whitespace
|
|
||||||
|
|
||||||
return content.slice(offset, content.length);
|
|
||||||
}
|
|
||||||
|
|||||||
184
ppmToAsm.ts
Executable file
184
ppmToAsm.ts
Executable 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);
|
||||||
29
readme.md
29
readme.md
@@ -9,6 +9,11 @@ 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`:
|
||||||
|
- `80x60` screen resolution
|
||||||
|
- `5x5` tile resolution
|
||||||
|
- initial screen prerendered
|
||||||
|
- no sprite rotation
|
||||||
- `res-2`:
|
- `res-2`:
|
||||||
- `256x192` screen resolution
|
- `256x192` screen resolution
|
||||||
- `17x17` tile resolution
|
- `17x17` tile resolution
|
||||||
@@ -20,19 +25,29 @@ The following branches contains variations, allowing comparisons of performances
|
|||||||
|
|
||||||
## Build script
|
## 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:
|
Usage example:
|
||||||
```sh
|
```sh
|
||||||
./build.ts > /path/to/the/game/schematics/architecture/Symfony/sandbox/main.asm
|
./ppmToAsm.ts map map ./assets/map.ppm > ./dist/map.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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
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
|
## Documentation
|
||||||
|
|
||||||
|
|||||||
@@ -2,16 +2,3 @@ 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,3 +1,4 @@
|
|||||||
|
; 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,9 +1,8 @@
|
|||||||
; given:
|
; given:
|
||||||
; -> 256x192 pixels screen
|
; - 16x12 tiles map
|
||||||
; -> 15x11 tiles map
|
; - 5x5 pixels tile
|
||||||
; => 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 = 0
|
pub const scene.margin_left = 2
|
||||||
pub const scene.margin_right = 1
|
pub const scene.margin_right = 3
|
||||||
@@ -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 = 2
|
pub const screen.resolution_value = 0
|
||||||
|
|
||||||
pub const screen.width = 256
|
pub const screen.width = 80
|
||||||
pub const screen.height = 192
|
pub const screen.height = 60
|
||||||
|
|||||||
@@ -1,10 +1,26 @@
|
|||||||
pub const sprites.width = 17
|
pub const sprites.width = 5
|
||||||
pub const sprites.height = 17 ; unused as sprite are all square
|
pub const sprites.height = 5 ; unused as sprite are all square
|
||||||
pub const sprites.size = 289 ; height x width
|
pub const sprites.size = 25 ; height x width
|
||||||
|
|
||||||
pub const sprites.empty = 0
|
pub const sprites.rotation_0 = 0
|
||||||
|
pub const sprites.rotation_90 = 1
|
||||||
|
pub const sprites.rotation_180 = 2
|
||||||
|
pub const sprites.rotation_270 = 3
|
||||||
|
|
||||||
|
pub const sprites.empty = 0 ; must stay 0 (some code avoid addition when it knows the tile is empty)
|
||||||
pub const sprites.unimplemented = 1
|
pub const sprites.unimplemented = 1
|
||||||
|
|
||||||
pub const sprites.robot = 2
|
pub const sprites.robot = 2
|
||||||
pub const sprites.ghost = 3
|
pub const sprites.robot_up = 2
|
||||||
pub const sprites.coin = 4
|
pub const sprites.robot_right = 3
|
||||||
pub const sprites.max = 4 ; for lookup table
|
pub const sprites.robot_down = 4
|
||||||
|
pub const sprites.robot_left = 5
|
||||||
|
|
||||||
|
pub const sprites.ghost = 6
|
||||||
|
pub const sprites.ghost_up = 6
|
||||||
|
pub const sprites.ghost_right = 7
|
||||||
|
pub const sprites.ghost_down = 8
|
||||||
|
pub const sprites.ghost_left = 9
|
||||||
|
|
||||||
|
pub const sprites.coin = 10
|
||||||
|
pub const sprites.max = 10 ; for lookup table
|
||||||
52
src/init.asm
52
src/init.asm
@@ -1,52 +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_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, screen
|
|
||||||
screen r1, r2
|
|
||||||
|
|
||||||
mov r1, screen.resolution_index
|
|
||||||
screen r1, screen.resolution_value
|
|
||||||
@@ -1,23 +1,68 @@
|
|||||||
include ../consts/offsets
|
/* Draw the tile corresponding to the given coordinate with it's map content
|
||||||
include ../consts/map
|
|
||||||
include ../consts/screen
|
|
||||||
include ../consts/scene
|
|
||||||
include ../consts/sprites
|
|
||||||
include sprite_rotation
|
|
||||||
|
|
||||||
; /* Return the top-left pixel address of the tile at the given map coordinates
|
Inputs:
|
||||||
|
r1, r2: Map coordinates x, y
|
||||||
|
|
||||||
; Inputs:
|
Locals:
|
||||||
; r1: map x coord
|
r3: tile value / sprite id / sprite address offset
|
||||||
; r2: map y coord
|
r9: pointer
|
||||||
|
r13: screen address
|
||||||
|
*/
|
||||||
|
pub drawing.clear_tile:
|
||||||
|
push r1
|
||||||
|
push r2
|
||||||
|
push r3
|
||||||
|
push r9
|
||||||
|
push r13
|
||||||
|
|
||||||
; Locals:
|
add r9, r1, map
|
||||||
; r3: counter
|
push r2
|
||||||
; r4: offset per tile row (screen width * tile height)
|
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
|
||||||
|
|
||||||
; Ouputs:
|
load_8 r3, [r9]
|
||||||
; r13: the pixel address
|
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
|
||||||
@@ -64,17 +109,18 @@ get_tile_address:
|
|||||||
pop r3
|
pop r3
|
||||||
ret
|
ret
|
||||||
|
|
||||||
; /* Get address of given sprite id
|
|
||||||
|
|
||||||
; Inputs:
|
/* Get address of given sprite id
|
||||||
; r1: sprite id
|
|
||||||
|
|
||||||
; Outputs:
|
Inputs:
|
||||||
; r13: address
|
r1: sprite id
|
||||||
|
|
||||||
; Locals:
|
Outputs:
|
||||||
; r2: pointer address
|
r13: address
|
||||||
; */
|
|
||||||
|
Locals:
|
||||||
|
r2: pointer address
|
||||||
|
*/
|
||||||
get_sprite_address:
|
get_sprite_address:
|
||||||
push r1
|
push r1
|
||||||
push r2
|
push r2
|
||||||
@@ -92,20 +138,18 @@ 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 id
|
r3: sprite id
|
||||||
|
|
||||||
; Outputs:
|
Locals:
|
||||||
|
r1: screen address
|
||||||
; Locals:
|
r2: sprite address
|
||||||
; r1: screen address
|
r13: value returned by call to `get_tile_address` and `get_sprite_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
|
||||||
@@ -129,21 +173,18 @@ 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:
|
||||||
|
r1: screen address
|
||||||
|
r2: sprite address
|
||||||
|
|
||||||
; Inputs:
|
Locals:
|
||||||
; r1: screen address
|
r1: current screen pixel address
|
||||||
; r2: sprite address
|
r2: current sprite pixel address
|
||||||
|
r3: x
|
||||||
; Outputs:
|
r4: color
|
||||||
|
r5: sprite last pixel address + 1
|
||||||
; Locals:
|
*/
|
||||||
; r1: current screen pixel address
|
|
||||||
; r2: current sprite pixel address
|
|
||||||
; r3: x
|
|
||||||
; r4: color
|
|
||||||
; r5: sprite last pixel address + 1
|
|
||||||
; */
|
|
||||||
draw_sprite:
|
draw_sprite:
|
||||||
push r1
|
push r1
|
||||||
push r2
|
push r2
|
||||||
|
|||||||
@@ -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:
|
Outputs:
|
||||||
; r1: x map coordinate
|
r13: tile address
|
||||||
; 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
|
||||||
|
|||||||
18
src/lib/u64.asm
Normal file
18
src/lib/u64.asm
Normal 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
|
||||||
374
src/main.asm
374
src/main.asm
@@ -1,337 +1,65 @@
|
|||||||
|
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 init
|
include sections/init_sprite_lookup_table
|
||||||
|
include sections/init_screen
|
||||||
|
|
||||||
;
|
include sections/init_robot_state
|
||||||
; State initialization
|
include sections/init_ghost_state
|
||||||
;
|
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.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.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
|
|
||||||
jne game_loop_event_handling_end
|
|
||||||
cmp r2, keyboard.key_min
|
|
||||||
jb game_loop_event_handling_end
|
|
||||||
cmp r2, keyboard.key_max
|
|
||||||
ja game_loop_event_handling_end
|
|
||||||
|
|
||||||
sub r4, r2, keyboard.key_min ; so the first value is 0
|
/*
|
||||||
lsl r4, r4, 3 ; 2 * instruction length = 8
|
For all timers:
|
||||||
add r4, r4, game_loop_key_jump_table_start
|
r1, r2: tick duration high, low
|
||||||
jmp r4
|
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
|
||||||
|
|
||||||
game_loop_key_jump_table_start:
|
push r1
|
||||||
; 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
|
||||||
mov r1, r5
|
push r7
|
||||||
mov r2, r6
|
push r8
|
||||||
call game.get_tile_address_from_map_coord
|
include sections/timer_robot
|
||||||
mov r7, r13
|
pop r8
|
||||||
|
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.empty
|
|
||||||
call drawing.draw_sprite_at_map_coord
|
|
||||||
|
|
||||||
; draw robot on new position
|
|
||||||
mov r1, r5
|
|
||||||
mov r2, r6
|
|
||||||
mov r3, 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
|
|
||||||
mov r1, r2
|
|
||||||
mov r2, r3
|
|
||||||
call game.get_tile_address_from_map_coord
|
|
||||||
load_8 r13, [r13]
|
|
||||||
|
|
||||||
; draw tile at previous position
|
|
||||||
cmp r13, map.tiles.coin
|
|
||||||
je game_loop_ghost_movement_old_tile_coin
|
|
||||||
; old tile empty
|
|
||||||
mov r3, sprites.empty
|
|
||||||
jmp game_loop_ghost_movement_draw_old_tile
|
|
||||||
game_loop_ghost_movement_old_tile_coin:
|
|
||||||
mov r3, sprites.coin
|
|
||||||
|
|
||||||
game_loop_ghost_movement_draw_old_tile:
|
|
||||||
call drawing.draw_sprite_at_map_coord
|
|
||||||
|
|
||||||
; draw ghost on new position
|
|
||||||
mov r1, r7
|
|
||||||
mov r2, r8
|
|
||||||
mov r3, 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 dist/empty
|
||||||
|
include dist/placeholder
|
||||||
|
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
|
||||||
|
|
||||||
|
include dist/screen
|
||||||
|
|||||||
@@ -2,8 +2,14 @@
|
|||||||
pub ptr.textures_addresses:
|
pub ptr.textures_addresses:
|
||||||
U32 0 ; empty
|
U32 0 ; empty
|
||||||
U32 0 ; placeholder
|
U32 0 ; placeholder
|
||||||
U32 0 ; robot
|
U32 0 ; robot up
|
||||||
U32 0 ; ghost
|
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
|
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
|
||||||
@@ -14,8 +20,11 @@ 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.next_tick_high: U32 0
|
pub ptr.timer_robot_next_tick_high: U32 0x0000_0000
|
||||||
pub ptr.next_tick_low: U32 0
|
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:
|
pub ptr.state_robot:
|
||||||
U8 0 ; x coord
|
U8 0 ; x coord
|
||||||
|
|||||||
43
src/sections/handle_event.asm
Normal file
43
src/sections/handle_event.asm
Normal 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:
|
||||||
16
src/sections/init_ghost_state.asm
Normal file
16
src/sections/init_ghost_state.asm
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
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.ghost_down
|
||||||
|
call drawing.draw_sprite_at_map_coord
|
||||||
16
src/sections/init_robot_state.asm
Normal file
16
src/sections/init_robot_state.asm
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
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.robot_down
|
||||||
|
call drawing.draw_sprite_at_map_coord
|
||||||
10
src/sections/init_screen.asm
Normal file
10
src/sections/init_screen.asm
Normal 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, screen
|
||||||
|
screen r1, r2
|
||||||
|
|
||||||
|
mov r1, screen.resolution_index
|
||||||
|
screen r1, screen.resolution_value
|
||||||
76
src/sections/init_sprite_lookup_table.asm
Normal file
76
src/sections/init_sprite_lookup_table.asm
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
/* 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_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
|
||||||
34
src/sections/init_timers.asm
Normal file
34
src/sections/init_timers.asm
Normal 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
|
||||||
98
src/sections/timer_ghost.asm
Normal file
98
src/sections/timer_ghost.asm
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
/* 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: sprite id
|
||||||
|
r4: direction
|
||||||
|
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
|
||||||
|
add r3, 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:
|
||||||
98
src/sections/timer_robot.asm
Normal file
98
src/sections/timer_robot.asm
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
/* 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: sprite id
|
||||||
|
r4: direction
|
||||||
|
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
|
||||||
|
add r3, 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` | with a reminder of 1w and 5h
|
`2` | `17x17` | no reminder
|
||||||
|
|||||||
Reference in New Issue
Block a user