23 Commits

Author SHA1 Message Date
dc42d7c5b6 Merge branch 'main' into res-0-prerendered 2026-05-01 21:53:28 +02:00
ba12492ebf offset ghost first movement time 2026-05-01 21:14:51 +02:00
8b3b2aa20d add directional characters display 2026-05-01 21:13:55 +02:00
425b83b1bb split code in sections 2026-05-01 18:48:12 +02:00
c78a2ff7a0 handle multi-line comments 2026-05-01 18:27:51 +02:00
92ac475d58 make the build script use 'include' statement instead of a hard-coded '.asm' file list 2026-05-01 17:22:22 +02:00
6920d10089 move ppm to asm representation to its own script 2026-05-01 15:56:05 +02:00
e90e792ebf fix build script ignoring last lines 2026-05-01 15:08:22 +02:00
42ea545507 add minify flag to build script
(cherry picked from commit f0b43f7b7c)
2026-05-01 14:57:11 +02:00
c88f67559d add minify flag to build script
(cherry picked from commit f0b43f7b7c)
2026-04-27 12:43:56 +02:00
2b2e2a6d02 use decimal instead of binary representation to save space
(cherry picked from commit 54ba73c177)
2026-04-25 09:22:21 +02:00
c378dc95fa update branches description
(cherry picked from commit 2344a2e874)
2026-04-25 09:21:00 +02:00
2344a2e874 update branches description 2026-04-25 09:13:56 +02:00
54ba73c177 use decimal instead of binary representation to save space 2026-04-25 09:13:05 +02:00
d5e9dde21d use prerendered screen and remove sprite rotation
(cherry picked from commit 3783a98f95)
2026-04-25 08:59:04 +02:00
02e1e4c6b3 update branches description
(cherry picked from commit 4e03aada20)
2026-04-25 08:37:57 +02:00
d413170098 add colors: light gray and brown
(cherry picked from commit 356cf3fb94)
2026-04-25 08:34:26 +02:00
2c37af8dd0 change which 8 bit color is used for dark gray
(cherry picked from commit b7bc9956f9)
2026-04-25 08:34:26 +02:00
f96e2420ef add way to reduce build output's size
(cherry picked from commit 5231f54c27)
2026-04-25 08:34:26 +02:00
17c6b07367 add automatic padding in build script
(cherry picked from commit aad1fa45f7)
2026-04-25 08:34:26 +02:00
f0656aca40 replace texture jump table with dispatch table
(cherry picked from commit 88f10f9f06)
2026-04-25 08:34:26 +02:00
b0758c6976 change the way static RAM variable are declared and used
(cherry picked from commit 6cdb7304c2)
2026-04-25 08:34:26 +02:00
3992863664 remove unused colors.asm const file
(cherry picked from commit e6b2d8b086)
2026-04-25 08:34:26 +02:00
42 changed files with 1069 additions and 998 deletions

1
.gitignore vendored Normal file
View File

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

BIN
assets/screen.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.

BIN
assets/sprites/ghost_up.ppm Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/sprites/robot_up.ppm Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

423
build.ts
View File

@@ -1,229 +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";
const asmFilePaths = [ /**
"src/consts/arch.asm", * CONFIG
"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 mapWidth = 15; const rawDataInstructionLengthPrefix = "U32";
const spriteWidth = 17;
const mapMatchingStrings = { const ppmToAsmScriptPath = "./ppmToAsm.ts";
0x00_00_00: '0', // Empty
0xFF_00_00: '1', // Wall
0xFF_FF_00: '2', // Coin
};
const spritesMatchingStrings = { const mapPath = "assets/map.ppm";
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 = "";
const readable = file.readable.pipeThrough(new TextDecoderStream()) private lines: Array<string> = [];
.pipeThrough(new TextLineStream()); private insideComment = false;
stream;
console.log(` constructor(writableStream: WritableStream) {
;/******************************* this.stream = new TransformStream<string>({
;* ${path} transform: (chunk, controller) => {
;*******************************/ this.buffer += chunk;
`); controller.enqueue(this.processChunk());
},
for await (const line of readable) { flush: (controller) => {
if (!line.startsWith("include ")) { this.buffer += "\n";
console.log(line); controller.enqueue(this.processChunk());
controller.enqueue(this.lines.join("\n") + "\n");
this.lines = [];
},
});
this.stream.readable.pipeTo(writableStream);
} }
private processChunk(): string {
const commentFullRegex = /\/\*.*\*\//;
const commentStartRegex = /(.*)\/\*(.*)/;
const commentEndRegex = /(.*)\*\/(.*)/;
if (this.insideComment) {
const matches = this.buffer.match(commentEndRegex);
if (matches === null) {
this.buffer = '';
return '';
}
this.buffer = matches[2];
this.insideComment = false;
}
if (!this.insideComment) {
this.buffer = this.buffer.replace(commentFullRegex, '');
const matches = this.buffer.match(commentStartRegex);
if (matches !== null) {
this.buffer = matches[1];
this.insideComment = true;
return '';
}
}
const newLines = this.buffer.split("\n");
this.buffer = newLines.pop() as string; // keep the last (potentially incomplete) line
const removeComment = (line: string) => line.replace(/\s*;.*/, "");
const removeLeadingWhitespaces = (line: string) => line.replace(/^\s+/, "");
const removeTrailingWhitespaces = (line: string) =>
line.replace(/\s+$/, "");
const removeDoubleSpaces = (line: string) => line.replaceAll(/ +/g, " ");
const isNotEmpty = (line: string) => line.length !== 0;
const newLinesStripped = newLines
.map(removeComment)
.map(removeLeadingWhitespaces)
.map(removeTrailingWhitespaces)
.map(removeDoubleSpaces)
.filter(isNotEmpty);
this.lines.push(...newLinesStripped);
return this.processLines();
}
private processLines() {
const processedLines: Array<string> = [];
while (this.lines.length >= instructionLength) {
const mergeableLines = this.lines.slice(0, instructionLength);
if (!mergeableLines.every((line) => line.startsWith("U8"))) {
processedLines.push(this.lines.shift() as string);
continue;
}
const bytesToMerge = mergeableLines.map((line) =>
parseInt((line.match(/U8\s(\d+)/) as RegExpMatchArray)[1])
);
let mergedBytes = 0;
for (let i = 0; i < bytesToMerge.length; i++) {
mergedBytes += bytesToMerge[i] << (instructionLength - 1 - i) * 8;
}
processedLines.push(
`${rawDataInstructionLengthPrefix} ${mergedBytes >>> 0}`,
);
this.lines.splice(0, instructionLength);
}
return processedLines.length === 0 ? "" : `${processedLines.join("\n")}\n`;
} }
} }
async function ppmToAsm(
mode: "map" | "sprite",
label: string,
path: string,
): Promise<string> {
const command = new Deno.Command(ppmToAsmScriptPath, {
args: [
instructionLength.toString(),
mode,
label,
path,
],
});
const asmRawRepresentations = []; const { code, stdout, stderr } = await command.output();
const mapBytes = await getDataFromPPM(mapPath); const decoder = new TextDecoder();
asmRawRepresentations.push(bytesToAsmConstU8('map', mapBytes, mapMatchingStrings, mapWidth));
for (const [label, path] of Object.entries(spritesPath)) { if (code !== 0) {
const bytes = await getDataFromPPM(path); console.error(`ppmToAsm script returned non-zero value: ${code}`);
asmRawRepresentations.push(bytesToAsmConstU8(label, bytes, spritesMatchingStrings, spriteWidth)); console.error(`STDERR: \n${decoder.decode(stderr)}`);
}
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); Deno.exit(1);
} }
stringValues.push(`U8 ${matchingStrings[pixelValue]}`); return decoder.decode(stdout);
}
async function build(
pwd: string,
path: string,
output: WritableStreamDefaultWriter<string>,
) {
const includeExpressionRegex = /^\s*include\s+([^\s;]+)/;
const file = await Deno.open(`${pwd}/${path}`);
const readable = file.readable.pipeThrough(new TextDecoderStream())
.pipeThrough(new TextLineStream());
for await (const line of readable) {
const match = line.match(includeExpressionRegex);
if (match === null) {
await output.write(`${line}\n`);
continue;
} }
const lines = []; await build(pwd, `${match[1]}.asm`, output);
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 = []; function printUsage(into: (message: string) => void) {
for (let i = 0; i < stringValues.length % instructionLength; i++) { into(`Usage:
paddingBytes.push('U8 0'); \tbuild.ts [options...] {file}
} \t\tRead the given file and recursively include other '.asm' files to produce a single output.
const paddingString = paddingBytes.length === 0 ? '' : `\n${paddingBytes.join('\t')} ; padding to preserve ${8 * instructionLength} bits alignment` \tbuild.ts --help
\t\tPrint this message
return `${label}: Options:
${lines.join("\n")} \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.
${label}_end:${paddingString}` \t--ppm \tWhen used, the hardcoded ppm files will be translated to their matching Symfony assembly reprensentation into src/dist.`);
} }
/** /**
* Return the image pixels data (excluding all other fields) from a given PPM file * 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);
const content = await Deno.readFile(path); Deno.exit(1);
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);
} }
let minify = false;
let convertPPM = false;
let path: string | undefined = undefined;
Deno.args.forEach((arg, index) => {
switch (arg) {
case "--help":
printUsage(console.log);
Deno.exit(0);
break;
case "--minify":
minify = true;
break;
case "--ppm":
convertPPM = true;
break;
default:
if (index !== Deno.args.length - 1) {
printUsage(console.error);
Deno.exit(1);
}
path = arg;
break;
}
});
if (path === undefined) {
printUsage(console.error);
Deno.exit(1);
}
if (convertPPM) {
await Deno.mkdir(`${Deno.cwd()}/src/dist`, { recursive: true });
const mapPromise = async () => {
const assembly = await ppmToAsm("map", "map", mapPath);
await Deno.writeTextFile(`${Deno.cwd()}/src/dist/map.asm`, assembly);
};
const spritePromises = Object.entries(spritesPath).map(async ([label, path]) => {
const assembly = await ppmToAsm("sprite", label, path);
const filename = path.split("/").pop()?.split('.')[0];
await Deno.writeTextFile(`${Deno.cwd()}/src/dist/${filename}.asm`, assembly);
});
await Promise.all([
mapPromise(),
...spritePromises,
]);
}
const encoder = new TextEncoder();
const stdoutWritable = new WritableStream<string>({
async write(chunk) {
await Deno.stdout.write(encoder.encode(chunk));
},
});
const writer = minify
? (new Minifier(stdoutWritable)).stream.writable.getWriter()
: stdoutWritable.getWriter();
await build(`${Deno.cwd()}/src`, path, writer);
await writer.close();

184
ppmToAsm.ts Executable file
View File

@@ -0,0 +1,184 @@
#!/usr/bin/env -S deno run --allow-read
const ppmCorrespondanceTables = {
map: {
0x00_00_00: 0, // Empty
0xFF_00_00: 1, // Wall
0xFF_FF_00: 2, // Coin
},
sprite: {
0x00_00_00: 0, // 0b000_000_00
0x00_00_FF: 3, // 0b000_000_11
0x00_FF_00: 28, // 0b000_111_00
0x99_50_00: 68, // 0b010_001_00
0x77_77_77: 110, // 0b011_011_10
0xB3_B3_B3: 146, // 0b100_100_10
0xFF_00_00: 224, // 0b111_000_00
0xFF_00_FF: 227, // 0b111_000_11
0xFF_80_00: 240, // 0b111_100_00
0xFF_FF_00: 248, // 0b111_110_00
0xFF_FF_FF: 255, // 0b111_111_11
},
};
/**
* Return an ASM constants (U8) representation of the given data
*
* If needed, the representation will be padded to ensure the following code stays aligned on the instruction's length
*
* @param label The label to write before the first raw data
* @param data An array of bytes to interprete as 24 bits values
* @param matchingValues An associative array with a string representation for each possible 24 bit data value
* @param instructionLength The size / length in byte of an instruction. Used to pad result to ensure data stays aligned
*
* @returns The ASM representation of that raw data, pre- and post-fixed by the given label
*/
function ppmBytesToCorrespondingAsmConstU8(
label: string,
data: Uint8Array,
matchingValues: Record<number, number>,
instructionLength: number,
): string {
if (data.length % 3 !== 0) {
console.error(
"Length of data passed to bytesToAsmConstU8 are not a multiple of 3 (it needs to, as each resulting byte need a Red, a Green and a Blue value)",
);
Deno.exit(1);
}
const lines = [`${label}:`];
for (let i = 0; i < data.length; i += 3) {
const pixelValue = (data[i] << 16) + (data[i + 1] << 8) + (data[i + 2]);
if (!Object.hasOwn(matchingValues, pixelValue)) {
console.error(
`No matching value defined for "${
pixelValue.toString(2)
}" (which is ${label}'s ${i} pixel)`,
);
Deno.exit(1);
}
lines.push(`U8 ${matchingValues[pixelValue]}`);
}
lines.push(`${label}_end:`);
const bytesNb = data.length / 3;
const unalignedBytesNb = bytesNb % instructionLength;
for (let i = 0; i < instructionLength - unalignedBytesNb; i++) {
lines.push(
`U8 0 ; padding to preserve ${8 * instructionLength} bits alignment`,
);
}
return lines.join("\n");
}
/**
* Return the image pixels data (excluding all other fields) from a given PPM file
*
* The implementation differ from the format definition (https://netpbm.sourceforge.net/doc/ppm.html) but match the file created by GIMP.
*/
async function getDataFromPPM(path: string): Promise<Uint8Array> {
const endOfComment = [
0x0a,
0x0d,
];
const whitespaces = [
0x09,
0x0a,
0x0b,
0x0c,
0x0d,
0x20,
];
const numbers = [
0x30,
0x31,
0x32,
0x33,
0x34,
0x35,
0x36,
0x37,
0x38,
0x39,
];
const hashtag = 0x23;
const content = await Deno.readFile(path);
let offset = 0;
// magic number
while (!whitespaces.includes(content[offset])) {
offset++;
}
offset++; // whitespace
// potential comment
if (content[offset] === hashtag) {
while (!endOfComment.includes(content[offset])) {
offset++;
}
offset++; // end of comment
}
// image width
while (numbers.includes(content[offset])) {
offset++;
}
offset++; // whitespace
// image height
while (numbers.includes(content[offset])) {
offset++;
}
offset++; // whitespace
// maximum color value
while (numbers.includes(content[offset])) {
offset++;
}
offset++; // whitespace
return content.slice(offset, content.length);
}
function printUsage(into: (message: string) => void) {
into(`Usage:
\tppmToAsm.ts {instruction length} {map|sprite} {label} {file}
\t\tConvert the given PPM file into a Symfony assembly representation using the given correspondance table. Result will be padded with "U8 0"s to ensure alignment to given instruction length.
\tppmToAsm.ts --help
\t\tPrint this message`);
}
/**
* SCRIPT
*/
if (Deno.args.length !== 4) {
printUsage(console.error);
Deno.exit(1);
}
if (!(Deno.args[1] in ppmCorrespondanceTables)) {
console.error(`"${Deno.args[1]}" is not a valid correspondance table.\n`);
printUsage(console.error);
Deno.exit(1);
}
const mode = Deno.args[1] as keyof typeof ppmCorrespondanceTables;
const correspondanceTable = ppmCorrespondanceTables[mode];
const label = Deno.args[2];
const path = Deno.args[3];
const instructionLength = parseInt(Deno.args[0]);
const ppmData = await getDataFromPPM(path);
const asm = ppmBytesToCorrespondingAsmConstU8(label, ppmData, correspondanceTable, instructionLength);
console.log(asm);

View File

@@ -9,23 +9,45 @@ 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-2`: 256x192 screen resolution, 17x17 tile - `res-0-prerendered`:
- `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
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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,18 +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.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 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.wall_end = 2
pub const sprites.wall_straight = 3 pub const sprites.robot = 2
pub const sprites.wall_corner = 4 pub const sprites.robot_up = 2
pub const sprites.robot = 5 pub const sprites.robot_right = 3
pub const sprites.robot_down = 4
pub const sprites.robot_left = 5
pub const sprites.ghost = 6 pub const sprites.ghost = 6
pub const sprites.coin = 7 pub const sprites.ghost_up = 6
pub const sprites.max = 7 ; for lookup table 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

View File

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

View File

@@ -1,70 +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
; /* Draw the whole (fill the screen memory with the values of the sprites based on the map) Inputs:
r1, r2: Map coordinates x, y
; Inputs: Locals:
r3: tile value / sprite id / sprite address offset
; Outputs: r9: pointer
r13: screen address
; Locals: */
; r1: x map coord pub drawing.clear_tile:
; r2: y map coord
; r3: sprite rotation
; r4: sprite id
; r12: rotation (returned by `sprite and rotation`)
; r13: sprite (returned by `get_sprite_id_and_rotation`)
; */
pub drawing.draw_whole_map:
push r1 push r1
push r2 push r2
push r3 push r3
push r4 push r9
push r12
push r13 push r13
mov r1, 0 add r9, r1, map
mov r2, 0 push r2
pub drawing.clear_tile_row_loop:
cmp r2, 0
je drawing.clear_tile_row_loop_end
sub r2, r2, 1
add r9, r9, map.width
jmp drawing.clear_tile_row_loop
pub drawing.clear_tile_row_loop_end:
pop r2
draw_whole_map_loop: load_8 r3, [r9]
call sprite_rotation.get_sprite_id_and_rotation cmp r3, map.tiles.coin
mov r3, r12 add r9, zr, ptr.textures_addresses
mov r4, r13 jne drawing.clear_tile_get_screen_address
call drawing.draw_sprite_at_map_coord ; coin
add r1, r1, 1 mov r3, sprites.coin
cmp r1, map.width lsl r3, r3, arch.instruction_increment_shift
jne draw_whole_map_loop add r9, r9, r3
mov r1, 0
add r2, r2, 1 pub drawing.clear_tile_get_screen_address:
cmp r2, map.height call get_tile_address
jne draw_whole_map_loop
mov r1, r13
load_32 r2, [r9]
mov r3, sprites.rotation_0
call draw_sprite
pop r13 pop r13
pop r12 pop r9
pop r4
pop r3 pop r3
pop r2 pop r2
pop r1 pop r1
ret ret
; /* Return the top-left pixel address of the tile at the given map coordinates /* Return the top-left pixel address of the tile at the given map coordinates
; Inputs: Inputs:
; r1: map x coord r1: map x coord
; r2: map y coord r2: map y coord
; Locals: Locals:
; r3: counter r3: counter
; r4: offset per tile row (screen width * tile height) r4: offset per tile row (screen width * tile height)
; Ouputs: Ouputs:
; r13: the pixel address r13: the pixel address
; */ */
get_tile_address: get_tile_address:
push r3 push r3
push r4 push r4
@@ -77,7 +75,7 @@ get_tile_address:
cmp r3, sprites.height cmp r3, sprites.height
jne get_tile_address_offset_per_row_loop jne get_tile_address_offset_per_row_loop
add r13, zr, ptr.screen add r13, zr, screen
mov r3, 0 mov r3, 0
get_tile_address_margin_top_loop: get_tile_address_margin_top_loop:
@@ -111,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
@@ -139,131 +138,78 @@ 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 id
; r4: 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
push r3 push r3
push r4
push r13 push r13
call get_tile_address call get_tile_address
mov r1, r13 push r13
push r1
mov r1, r4 mov r1, r3
call get_sprite_address call get_sprite_address
mov r2, r13
pop r1
mov r2, r13
pop r13
mov r1, r13
call draw_sprite call draw_sprite
pop r13 pop r13
pop r4
pop r3 pop r3
pop r2 pop r2
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: rotation r3: x
r4: color
; Outputs: r5: sprite last pixel address + 1
*/
; Locals:
; r1: current screen pixel address
; r4: x
; r5: y
; r6: current sprite pixel address
; r7: color
; r8: x increment
; r9: y increment
; */
draw_sprite: draw_sprite:
push r1 push r1
push r2 push r2
push r3 push r3
push r4 push r4
push r5 push r5
push r6
push r7
push r8
push r9
mov r6, r2 add r5, r2, sprites.size
; rotation mov r3, 0
draw_sprite_cmp_rotation_0:
cmp r3, sprites.rotation_0
jne draw_sprite_cmp_rotation_90
mov r8, 1
mov r9, 0
jmp draw_sprite_loop_init
draw_sprite_cmp_rotation_90:
cmp r3, sprites.rotation_90
jne draw_sprite_cmp_rotation_180
add r6, r6, sprites.size
sub r6, r6, sprites.width
sub r8, zr, sprites.width
mov r9, sprites.size
add r9, r9, 1
jmp draw_sprite_loop_init
draw_sprite_cmp_rotation_180:
cmp r3, sprites.rotation_180
jne draw_sprite_cmp_rotation_270
add r6, r6, sprites.size
sub r6, r6, 1
sub r8, zr, 1
mov r9, 0
jmp draw_sprite_loop_init
draw_sprite_cmp_rotation_270:
add r6, r6, sprites.width
sub r6, r6, 1
mov r8, sprites.width
sub r9, zr, sprites.size
sub r9, r9, 1
draw_sprite_loop_init:
mov r4, 0
mov r5, 0
draw_sprite_loop: draw_sprite_loop:
load_8 r7, [r6] load_8 r4, [r2]
store_8 [r1], r7 store_8 [r1], r4
add r4, r4, 1
add r1, r1, 1 add r1, r1, 1
add r6, r6, r8 add r2, r2, 1
cmp r4, sprites.width add r3, r3, 1
cmp r3, sprites.width
jne draw_sprite_loop jne draw_sprite_loop
mov r4, 0 mov r3, 0
add r6, r6, r9
add r1, r1, screen.width add r1, r1, screen.width
sub r1, r1, sprites.width sub r1, r1, sprites.width
add r5, r5, 1 cmp r5, r2
cmp r5, sprites.width
jne draw_sprite_loop jne draw_sprite_loop
pop r9
pop r8
pop r7
pop r6
pop r5 pop r5
pop r4 pop r4
pop r3 pop r3

View File

@@ -1,13 +1,12 @@
/* Return the tile address from the given map coordinate
; /* Return the tile address from the given map coordinate Inputs:
r1: x map coordinate
r2: y map coordinate
; Inputs: 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

View File

@@ -1,171 +0,0 @@
include ../consts/offsets
include ../consts/map
include ../consts/sprites
; /* Return the sprite id and rotation for the given map coordinates
; Inputs:
; r1: x map coord
; r2: y map coord
; Outputs:
; r12: sprite rotation
; r13: sprite id
; Locals:
; r3: tile id
; r4: map offset
; r5: counter
; r6: left tile id
; r7: up tile id
; r8: right tile id
; r9: down tile id
; */
pub sprite_rotation.get_sprite_id_and_rotation:
push r1
push r2
push r3
push r4
push r5
push r6
push r7
push r8
push r9
add r4, r1, map
mov r5, 0
get_sprite_id_and_rotation_map_row_loop:
cmp r5, r2
je get_sprite_id_and_rotation_map_row_loop_end
add r4, r4, map.width
add r5, r5, 1
jmp get_sprite_id_and_rotation_map_row_loop
get_sprite_id_and_rotation_map_row_loop_end:
mov r12, sprites.rotation_0 ; default rotation
load_8 r3, [r4]
lsl r3, r3, 3 ; 2*instruction_size = *8 = <<3
add r3, r3, get_sprite_id_and_rotation_jump_table
jmp r3
get_sprite_id_and_rotation_jump_table:
; tile_empty
mov r13, sprites.empty
jmp get_sprite_id_and_rotation_return
; tiles_wall
jmp get_sprite_id_and_rotation_wall
nop
; tile_coin
mov r13, sprites.coin
jmp get_sprite_id_and_rotation_return
get_sprite_id_and_rotation_wall:
mov r6, 0
mov r7, 0
mov r8, 0
mov r9, 0
get_sprite_id_and_rotation_cmp_left:
cmp r1, 0
je get_sprite_id_and_rotation_cmp_up
sub r4, r4, 1
load_8 r6, [r4]
add r4, r4, 1
get_sprite_id_and_rotation_cmp_up:
cmp r2, 0
je get_sprite_id_and_rotation_cmp_right
sub r4, r4, map.width
load_8 r7, [r4]
add r4, r4, map.width
get_sprite_id_and_rotation_cmp_right:
cmp r1, map.max_x
je get_sprite_id_and_rotation_cmp_down
add r4, r4, 1
load_8 r8, [r4]
sub r4, r4, 1
get_sprite_id_and_rotation_cmp_down:
cmp r2, map.max_y
je get_sprite_id_and_rotation_check_connections
add r4, r4, map.width
load_8 r9, [r4]
sub r4, r4, map.width
get_sprite_id_and_rotation_check_connections:
get_sprite_id_and_rotation_straight_0:
cmp r7, map.tiles.wall
jne get_sprite_id_and_rotation_straight_90
cmp r9, map.tiles.wall
jne get_sprite_id_and_rotation_straight_90
mov r12, sprites.rotation_0
mov r13, sprites.wall_straight
jmp get_sprite_id_and_rotation_return
get_sprite_id_and_rotation_straight_90:
cmp r6, map.tiles.wall
jne get_sprite_id_and_rotation_corner_0
cmp r8, map.tiles.wall
jne get_sprite_id_and_rotation_corner_0
mov r12, sprites.rotation_90
mov r13, sprites.wall_straight
jmp get_sprite_id_and_rotation_return
get_sprite_id_and_rotation_corner_0:
cmp r6, map.tiles.wall
jne get_sprite_id_and_rotation_corner_180
cmp r9, map.tiles.wall
jne get_sprite_id_and_rotation_corner_90
mov r12,sprites.rotation_0
mov r13, sprites.wall_corner
jmp get_sprite_id_and_rotation_return
get_sprite_id_and_rotation_corner_90:
cmp r7, map.tiles.wall
jne get_sprite_id_and_rotation_end_90
mov r12,sprites.rotation_90
mov r13, sprites.wall_corner
jmp get_sprite_id_and_rotation_return
get_sprite_id_and_rotation_corner_180:
cmp r8, map.tiles.wall
jne get_sprite_id_and_rotation_end_0
cmp r7, map.tiles.wall
jne get_sprite_id_and_rotation_corner_270
mov r12,sprites.rotation_180
mov r13, sprites.wall_corner
jmp get_sprite_id_and_rotation_return
get_sprite_id_and_rotation_corner_270:
cmp r9, map.tiles.wall
jne get_sprite_id_and_rotation_end_270
mov r12,sprites.rotation_270
mov r13, sprites.wall_corner
jmp get_sprite_id_and_rotation_return
get_sprite_id_and_rotation_end_0:
cmp r9, map.tiles.wall
jne get_sprite_id_and_rotation_end_180
mov r12,sprites.rotation_0
mov r13, sprites.wall_end
jmp get_sprite_id_and_rotation_return
get_sprite_id_and_rotation_end_90:
mov r12,sprites.rotation_90
mov r13, sprites.wall_end
jmp get_sprite_id_and_rotation_return
get_sprite_id_and_rotation_end_180:
mov r12,sprites.rotation_180
mov r13, sprites.wall_end
jmp get_sprite_id_and_rotation_return
get_sprite_id_and_rotation_end_270:
mov r12,sprites.rotation_270
mov r13, sprites.wall_end
;jmp get_sprite_id_and_rotation_return
get_sprite_id_and_rotation_return:
pop r9
pop r8
pop r7
pop r6
pop r5
pop r4
pop r3
pop r2
pop r1
ret

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

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

View File

@@ -1,350 +1,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
call drawing.draw_whole_map include sections/init_robot_state
include sections/init_ghost_state
; include sections/init_timers
; State initialization
;
; Robot
const robot_initial_x = 1
const robot_initial_y = 1
const robot_initial_direction = game.direction_down
mov r1, robot_initial_x
mov r2, robot_initial_y
mov r5, robot_initial_direction
add r6, zr, ptr.state_robot
store_8 [r6], r1
add r6, r6, 1
store_8 [r6], r2
add r6, r6, 1
store_8 [r6], r5
mov r3, sprites.rotation_0
mov r4, sprites.robot
call drawing.draw_sprite_at_map_coord
; Ghost
const ghost_initial_x = 13
const ghost_initial_y = 1
const ghost_initial_direction = game.direction_down
mov r1, ghost_initial_x
mov r2, ghost_initial_y
mov r5, ghost_initial_direction
add r6, zr, ptr.state_ghost
store_8 [r6], r1
add r6, r6, 1
store_8 [r6], r2
add r6, r6, 1
store_8 [r6], r5
mov r3, sprites.rotation_0
mov r4, sprites.ghost
call drawing.draw_sprite_at_map_coord
;
; Time initialization
;
add r1, zr, ptr.tick_duration_low
load_32 r2, [r1]
add r1, zr, ptr.tick_duration_high
load_32 r3, [r1]
time_0 r4
time_1 r5
add r4, r4, r2
add r5, r5, r3
cmp r4, r2
jae init_store_next_tick
add r5, r5, 1 ; overflow
init_store_next_tick:
add r1, zr, ptr.next_tick_low
store_32 [r1], r4
add r1, zr, ptr.next_tick_high
store_32 [r1], r5
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 For all timers:
cmp r2, keyboard.key_min r1, r2: tick duration high, low
jb game_loop_event_handling_end r7, r8: now high, low
cmp r2, keyboard.key_max */
ja game_loop_event_handling_end add r9, zr, ptr.tick_duration_high
load_32 r1, [r9]
add r9, zr, ptr.tick_duration_low
load_32 r2, [r9]
time_0 r8
time_1 r7
sub r4, r2, keyboard.key_min ; so the first value is 0 push r1
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
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.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 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

View File

@@ -2,11 +2,14 @@
pub ptr.textures_addresses: pub ptr.textures_addresses:
U32 0 ; empty U32 0 ; empty
U32 0 ; placeholder U32 0 ; placeholder
U32 0 ; wall_end U32 0 ; robot up
U32 0 ; wall_straight U32 0 ; robot right
U32 0 ; wall_corner U32 0 ; robot down
U32 0 ; robot U32 0 ; robot left
U32 0 ; ghost 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
@@ -17,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
@@ -31,5 +37,3 @@ U8 0 ; x coord
U8 0 ; y coord U8 0 ; y coord
U8 0 ; direction U8 0 ; direction
U8 0 ; [padding] U8 0 ; [padding]
pub ptr.screen:

View File

@@ -0,0 +1,43 @@
/* Event handling
r1 = keyboard event+key / direction
r2 = keyboard key / direction address
r3 = keyboard event
r4 = jump address
*/
keyboard r1
and r2, r1, keyboard.mask_key
and r3, r1, keyboard.mask_event
cmp r3, keyboard.event_pressed
jne handle_event_end
cmp r2, keyboard.key_min
jb handle_event_end
cmp r2, keyboard.key_max
ja handle_event_end
sub r4, r2, keyboard.key_min ; so the first value is 0
lsl r4, r4, 3 ; 2 * instruction length = 8
add r4, r4, handle_event_jump_table_start
jmp r4
handle_event_jump_table_start:
; key_up
mov r1, game.direction_up
jmp handle_event_store_direction
; key_right
mov r1, game.direction_right
jmp handle_event_store_direction
; key_down:
mov r1, game.direction_down
jmp handle_event_store_direction
; key_left:
mov r1, game.direction_left
;jmp handle_event_end
handle_event_store_direction:
add r2, zr, ptr.state_robot
add r2, r2, 2
store_8 [r2], r1
handle_event_end:

View File

@@ -0,0 +1,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

View 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

View File

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

View 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

View File

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

View File

@@ -0,0 +1,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:

View 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:

View File

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