From c378dc95faacfddae11b063ff356be0334d94dc2 Mon Sep 17 00:00:00 2001 From: Robin Chappatte Date: Sat, 25 Apr 2026 09:13:56 +0200 Subject: [PATCH 01/10] update branches description (cherry picked from commit 2344a2e874818cfca4c7853d2be14c01f891c4df) --- readme.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/readme.md b/readme.md index 80cab98..c004ece 100755 --- a/readme.md +++ b/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 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`: - `256x192` screen resolution - `17x17` tile resolution From 2b2e2a6d02989943dd005d88f8d76320db9948ad Mon Sep 17 00:00:00 2001 From: Robin Chappatte Date: Sat, 25 Apr 2026 09:13:05 +0200 Subject: [PATCH 02/10] use decimal instead of binary representation to save space (cherry picked from commit 54ba73c1772df0af055397e490618f31e068633b) --- build.ts | 22 +++++++++++----------- readme.md | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/build.ts b/build.ts index dc8cb41..cf8491b 100755 --- a/build.ts +++ b/build.ts @@ -38,17 +38,17 @@ const mapMatchingStrings = { }; 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', + 0x00_00_00: ' 0', // 0b000_000_00 + 0x00_00_FF: ' 3', // 0b000_000_11 + 0x00_FF_00: ' 28', // 0b000_111_00 + 0x99_50_00: ' 68', // 0b010_001_00 + 0x77_77_77: '110', // 0b011_011_10 + 0xB3_B3_B3: '146', // 0b100_100_10 + 0xFF_00_00: '224', // 0b111_000_00 + 0xFF_00_FF: '227', // 0b111_000_11 + 0xFF_80_00: '240', // 0b111_100_00 + 0xFF_FF_00: '248', // 0b111_110_00 + 0xFF_FF_FF: '255', // 0b111_111_11 }; const mapPath = 'assets/map.ppm'; diff --git a/readme.md b/readme.md index c004ece..402841e 100755 --- a/readme.md +++ b/readme.md @@ -33,8 +33,8 @@ Usage example: ```sh ./build.ts > /path/to/the/game/schematics/architecture/Symfony/sandbox/main.asm -# script output can easily be piped to strip it off all comments, leading whitespace and empty lines: -./build.ts | sed 's/\s*;.*//' | sed 's/^\s*//' | sed '/^\d*$/d' > result.asm +# script output can easily be piped to strip it off all comments, leading whitespace, double spaces and empty lines: +./build.ts | sed -r 's/\s*;.*//' | sed -r 's/^\s+//' | sed -r 's/ +/ /' | sed -r '/^\s*$/d' > result.asm ``` 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. From 42ea54550724899f49854c9f6637a3184a8998cc Mon Sep 17 00:00:00 2001 From: Robin Chappatte Date: Fri, 1 May 2026 14:57:11 +0200 Subject: [PATCH 03/10] add minify flag to build script (cherry picked from commit f0b43f7b7c8e6c7960000e57320656e5b21f3171) --- build.ts | 316 +++++++++++++++++++++++++++++++++++++----------------- readme.md | 5 +- 2 files changed, 221 insertions(+), 100 deletions(-) diff --git a/build.ts b/build.ts index cf8491b..01a50ca 100755 --- a/build.ts +++ b/build.ts @@ -1,14 +1,14 @@ #!/usr/bin/env -S deno run --allow-read -if (Deno.args.length !== 0) { - console.error( - "This script takes no argument (The source file list is hardcoded) and output the result on stdout.", - ); - Deno.exit(1); -} - import { TextLineStream } from "jsr:@std/streams@1.1.0"; +/** + * CONFIG + */ + +const instructionLength = 4; // in bytes +const rawDataInstructionLengthPrefix = "U32"; + const asmFilePaths = [ "src/consts/arch.asm", "src/consts/game.asm", @@ -25,136 +25,212 @@ const asmFilePaths = [ "src/lib/drawing.asm", "src/lib/sprite_rotation.asm", ]; -const reservedSpacePath = 'src/reserved_space.asm'; +const reservedSpacePath = "src/reserved_space.asm"; -const instructionLength = 4; // in bytes -const mapWidth = 15; -const spriteWidth = 5; - -const mapMatchingStrings = { - 0x00_00_00: '0', // Empty - 0xFF_00_00: '1', // Wall - 0xFF_FF_00: '2', // Coin +const mapMatchingValues = { + 0x00_00_00: 0, // Empty + 0xFF_00_00: 1, // Wall + 0xFF_FF_00: 2, // Coin }; -const spritesMatchingStrings = { - 0x00_00_00: ' 0', // 0b000_000_00 - 0x00_00_FF: ' 3', // 0b000_000_11 - 0x00_FF_00: ' 28', // 0b000_111_00 - 0x99_50_00: ' 68', // 0b010_001_00 - 0x77_77_77: '110', // 0b011_011_10 - 0xB3_B3_B3: '146', // 0b100_100_10 - 0xFF_00_00: '224', // 0b111_000_00 - 0xFF_00_FF: '227', // 0b111_000_11 - 0xFF_80_00: '240', // 0b111_100_00 - 0xFF_FF_00: '248', // 0b111_110_00 - 0xFF_FF_FF: '255', // 0b111_111_11 +const spritesMatchingValues = { + 0x00_00_00: 0, // 0b000_000_00 + 0x00_00_FF: 3, // 0b000_000_11 + 0x00_FF_00: 28, // 0b000_111_00 + 0x99_50_00: 68, // 0b010_001_00 + 0x77_77_77: 110, // 0b011_011_10 + 0xB3_B3_B3: 146, // 0b100_100_10 + 0xFF_00_00: 224, // 0b111_000_00 + 0xFF_00_FF: 227, // 0b111_000_11 + 0xFF_80_00: 240, // 0b111_100_00 + 0xFF_FF_00: 248, // 0b111_110_00 + 0xFF_FF_FF: 255, // 0b111_111_11 }; -const mapPath = 'assets/map.ppm'; +const mapPath = "assets/map.ppm"; const spritesPath = { - 'sprite_empty': 'assets/sprites/empty.ppm', - 'sprite_unimplemented': 'assets/sprites/placeholder.ppm', - 'sprite_wall_end': 'assets/sprites/wall_end.ppm', - 'sprite_wall_straight': 'assets/sprites/wall_straight.ppm', - 'sprite_wall_corner': 'assets/sprites/wall_corner.ppm', - 'sprite_robot': 'assets/sprites/robot.ppm', - 'sprite_ghost': 'assets/sprites/ghost.ppm', - 'sprite_coin': 'assets/sprites/coin.ppm', + "sprite_empty": "assets/sprites/empty.ppm", + "sprite_unimplemented": "assets/sprites/placeholder.ppm", + "sprite_wall_end": "assets/sprites/wall_end.ppm", + "sprite_wall_straight": "assets/sprites/wall_straight.ppm", + "sprite_wall_corner": "assets/sprites/wall_corner.ppm", + "sprite_robot": "assets/sprites/robot.ppm", + "sprite_ghost": "assets/sprites/ghost.ppm", + "sprite_coin": "assets/sprites/coin.ppm", }; /** - * START OF BUILD + * END OF CONFIG */ -for (const path of asmFilePaths) { - const file = await Deno.open(path); - const readable = file.readable.pipeThrough(new TextDecoderStream()) - .pipeThrough(new TextLineStream()); +class Minifier { + private buffer = ""; + private lines: Array = []; + stream; - console.log(` + constructor(writableStream: WritableStream) { + this.stream = new TransformStream({ + transform: (chunk, controller) => { + this.buffer += chunk; + controller.enqueue(this.processChunk()); + }, + + flush: (controller) => { + this.buffer += "\n"; + controller.enqueue(this.processChunk()); + }, + }); + + this.stream.readable.pipeTo(writableStream); + } + + private processChunk(): string { + 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 = []; + 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 build(output: WritableStreamDefaultWriter) { + for (const path of asmFilePaths) { + const file = await Deno.open(path); + const readable = file.readable.pipeThrough(new TextDecoderStream()) + .pipeThrough(new TextLineStream()); + + output.write(` ;/******************************* ;* ${path} ;*******************************/ `); - for await (const line of readable) { - if (!line.startsWith("include ")) { - console.log(line); + for await (const line of readable) { + if (!line.startsWith("include ")) { + output.write(`${line}\n`); + } } } -} + const asmRawRepresentations = []; -const asmRawRepresentations = []; + const mapBytes = await getDataFromPPM(mapPath); + asmRawRepresentations.push( + bytesToAsmConstU8("map", mapBytes, mapMatchingValues), + ); -const mapBytes = await getDataFromPPM(mapPath); -asmRawRepresentations.push(bytesToAsmConstU8('map', mapBytes, mapMatchingStrings, mapWidth)); + for (const [label, path] of Object.entries(spritesPath)) { + const bytes = await getDataFromPPM(path); + asmRawRepresentations.push( + bytesToAsmConstU8(label, bytes, spritesMatchingValues), + ); + } -for (const [label, path] of Object.entries(spritesPath)) { - const bytes = await getDataFromPPM(path); - asmRawRepresentations.push(bytesToAsmConstU8(label, bytes, spritesMatchingStrings, spriteWidth)); -} + output.write(asmRawRepresentations.join("\n\n")); -console.log(asmRawRepresentations.join("\n\n")); - -console.log(` + output.write(` ; ; RESERVED RAM SPACE ; `); -console.log(await Deno.readTextFile(reservedSpacePath)) + output.write(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 - * + * @param matchingValues An associative array with a string representation for each possible 24 bit data value + * * @returns The ASM representation of that raw data, pre- and post-fixed by the given label */ -function bytesToAsmConstU8(label: string, data: Uint8Array, matchingStrings: Record, lineLength = 0): string { - lineLength ??= Infinity; +function bytesToAsmConstU8( + label: string, + data: Uint8Array, + matchingValues: Record, +): 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 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)`); + 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); } - - stringValues.push(`U8 ${matchingStrings[pixelValue]}`); + + 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`, + ); } - 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 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 { @@ -189,7 +265,7 @@ async function getDataFromPPM(path: string): Promise { let offset = 0; // magic number - while(!whitespaces.includes(content[offset])) { + while (!whitespaces.includes(content[offset])) { offset++; } @@ -197,7 +273,7 @@ async function getDataFromPPM(path: string): Promise { // potential comment if (content[offset] === hashtag) { - while(!endOfComment.includes(content[offset])) { + while (!endOfComment.includes(content[offset])) { offset++; } @@ -205,25 +281,73 @@ async function getDataFromPPM(path: string): Promise { } // image width - while(numbers.includes(content[offset])) { + while (numbers.includes(content[offset])) { offset++; } offset++; // whitespace // image height - while(numbers.includes(content[offset])) { + while (numbers.includes(content[offset])) { offset++; } offset++; // whitespace // maximum color value - while(numbers.includes(content[offset])) { + while (numbers.includes(content[offset])) { offset++; } offset++; // whitespace return content.slice(offset, content.length); +} + +function printUsage(into: (message: string) => void) { + into(`Usage: +\tbuild.ts [--minify] +\t\tMerge the hardcoded files and output the result to stdout. The "--minify" option can be used to strip the result of all comments, leading/trailing double whitespaces, empty lines and use the more compact raw data representation. +\tbuild.ts --help +\t\tPrint this message`); +} + +/** + * SCRIPT + */ + +if (Deno.args.length > 1) { + printUsage(console.error); + Deno.exit(1); +} + +let minify = false; +if (Deno.args.length === 1) { + switch (Deno.args[0]) { + case "--help": + printUsage(console.log); + Deno.exit(0); + break; + case "--minify": + minify = true; + break; + default: + printUsage(console.error); + Deno.exit(1); + break; + } +} + +const encoder = new TextEncoder(); +const stdoutWritable = new WritableStream({ + async write(chunk) { + await Deno.stdout.write(encoder.encode(chunk)); + }, +}); + +if (minify) { + const minifier = new Minifier(stdoutWritable); + await build(minifier.stream.writable.getWriter()); +} else { + await build(stdoutWritable.getWriter()); } \ No newline at end of file diff --git a/readme.md b/readme.md index 402841e..d65be4f 100755 --- a/readme.md +++ b/readme.md @@ -31,10 +31,7 @@ It also convert the PPM assets (map and sprites) into ASM raw values representat Usage example: ```sh -./build.ts > /path/to/the/game/schematics/architecture/Symfony/sandbox/main.asm - -# script output can easily be piped to strip it off all comments, leading whitespace, double spaces and empty lines: -./build.ts | sed -r 's/\s*;.*//' | sed -r 's/^\s+//' | sed -r 's/ +/ /' | sed -r '/^\s*$/d' > result.asm +./build.ts --minify > /path/to/the/game/schematics/architecture/Symfony/sandbox/main.asm ``` The script is build over Deno's API, so you'll need to have a `deno` binary in your PATH to use it that way. From e90e792ebff220f149f29bb1404c2cad2a87b6b1 Mon Sep 17 00:00:00 2001 From: Robin Chappatte Date: Fri, 1 May 2026 15:08:22 +0200 Subject: [PATCH 04/10] fix build script ignoring last lines --- build.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/build.ts b/build.ts index 01a50ca..d5a7dff 100755 --- a/build.ts +++ b/build.ts @@ -78,6 +78,8 @@ class Minifier { flush: (controller) => { this.buffer += "\n"; controller.enqueue(this.processChunk()); + controller.enqueue(this.lines.join("\n") + "\n"); + this.lines = []; }, }); @@ -139,7 +141,7 @@ async function build(output: WritableStreamDefaultWriter) { const readable = file.readable.pipeThrough(new TextDecoderStream()) .pipeThrough(new TextLineStream()); - output.write(` + await output.write(` ;/******************************* ;* ${path} ;*******************************/ @@ -147,7 +149,7 @@ async function build(output: WritableStreamDefaultWriter) { for await (const line of readable) { if (!line.startsWith("include ")) { - output.write(`${line}\n`); + await output.write(`${line}\n`); } } } @@ -166,15 +168,16 @@ async function build(output: WritableStreamDefaultWriter) { ); } - output.write(asmRawRepresentations.join("\n\n")); + await output.write(asmRawRepresentations.join("\n\n")); - output.write(` + await output.write(` ; ; RESERVED RAM SPACE ; `); - output.write(await Deno.readTextFile(reservedSpacePath)); + await output.write(await Deno.readTextFile(reservedSpacePath)); + await output.close(); } /** From 6920d10089660846378d16b195d91db3d5b10e73 Mon Sep 17 00:00:00 2001 From: Robin Chappatte Date: Fri, 1 May 2026 15:56:05 +0200 Subject: [PATCH 05/10] move ppm to asm representation to its own script --- .gitignore | 1 + build.ts | 191 +++++++++------------------------------------------- ppmToAsm.ts | 184 ++++++++++++++++++++++++++++++++++++++++++++++++++ readme.md | 18 +++-- 4 files changed, 229 insertions(+), 165 deletions(-) create mode 100644 .gitignore create mode 100755 ppmToAsm.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7773828 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +dist/ \ No newline at end of file diff --git a/build.ts b/build.ts index d5a7dff..dc881db 100755 --- a/build.ts +++ b/build.ts @@ -1,4 +1,4 @@ -#!/usr/bin/env -S deno run --allow-read +#!/usr/bin/env -S deno run --allow-read --allow-run import { TextLineStream } from "jsr:@std/streams@1.1.0"; @@ -9,6 +9,8 @@ import { TextLineStream } from "jsr:@std/streams@1.1.0"; const instructionLength = 4; // in bytes const rawDataInstructionLengthPrefix = "U32"; +const ppmToAsmScriptPath = './ppmToAsm.ts'; + const asmFilePaths = [ "src/consts/arch.asm", "src/consts/game.asm", @@ -27,26 +29,6 @@ const asmFilePaths = [ ]; const reservedSpacePath = "src/reserved_space.asm"; -const mapMatchingValues = { - 0x00_00_00: 0, // Empty - 0xFF_00_00: 1, // Wall - 0xFF_FF_00: 2, // Coin -}; - -const spritesMatchingValues = { - 0x00_00_00: 0, // 0b000_000_00 - 0x00_00_FF: 3, // 0b000_000_11 - 0x00_FF_00: 28, // 0b000_111_00 - 0x99_50_00: 68, // 0b010_001_00 - 0x77_77_77: 110, // 0b011_011_10 - 0xB3_B3_B3: 146, // 0b100_100_10 - 0xFF_00_00: 224, // 0b111_000_00 - 0xFF_00_FF: 227, // 0b111_000_11 - 0xFF_80_00: 240, // 0b111_100_00 - 0xFF_FF_00: 248, // 0b111_110_00 - 0xFF_FF_FF: 255, // 0b111_111_11 -}; - const mapPath = "assets/map.ppm"; const spritesPath = { "sprite_empty": "assets/sprites/empty.ppm", @@ -135,6 +117,29 @@ class Minifier { } } +async function ppmToAsm(mode: "map" | "sprite", label: string, path: string): Promise { + 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(output: WritableStreamDefaultWriter) { for (const path of asmFilePaths) { const file = await Deno.open(path); @@ -154,19 +159,10 @@ async function build(output: WritableStreamDefaultWriter) { } } - const asmRawRepresentations = []; - - const mapBytes = await getDataFromPPM(mapPath); - asmRawRepresentations.push( - bytesToAsmConstU8("map", mapBytes, mapMatchingValues), - ); - - for (const [label, path] of Object.entries(spritesPath)) { - const bytes = await getDataFromPPM(path); - asmRawRepresentations.push( - bytesToAsmConstU8(label, bytes, spritesMatchingValues), - ); - } + const asmRawRepresentations = await Promise.all([ + ppmToAsm("map", "map", mapPath), + ...Object.entries(spritesPath).map(([label, path]) => ppmToAsm("sprite", label, path)), + ]); await output.write(asmRawRepresentations.join("\n\n")); @@ -180,133 +176,6 @@ async function build(output: WritableStreamDefaultWriter) { await output.close(); } -/** - * 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 matchingValues An associative array with a string representation for each possible 24 bit data value - * - * @returns The ASM representation of that raw data, pre- and post-fixed by the given label - */ -function bytesToAsmConstU8( - label: string, - data: Uint8Array, - matchingValues: Record, -): 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 { - 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: \tbuild.ts [--minify] diff --git a/ppmToAsm.ts b/ppmToAsm.ts new file mode 100755 index 0000000..376a514 --- /dev/null +++ b/ppmToAsm.ts @@ -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, + 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 { + 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); diff --git a/readme.md b/readme.md index d65be4f..d5a85d8 100755 --- a/readme.md +++ b/readme.md @@ -25,16 +25,26 @@ The following branches contains variations, allowing comparisons of performances ## Build script -File inclusion is currently not supported by the game, so a `build.ts` script it provided that will concatenate the content of the hard-coded `.asm` files and output the result on stdout. +Both scripts uses Deno's API, so you'll need to have a `deno` binary in your PATH. -It also convert the PPM assets (map and sprites) into ASM raw values representation and append it after all the code. +### PPM to Symfony assembly + +Output the Symfony assembly representation of the given PPM file. Usage example: ```sh -./build.ts --minify > /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 ``` -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 > pacman.asm +``` ## Documentation From 92ac475d58e6565ae832863428ac50032b2b0214 Mon Sep 17 00:00:00 2001 From: Robin Chappatte Date: Fri, 1 May 2026 17:22:22 +0200 Subject: [PATCH 06/10] make the build script use 'include' statement instead of a hard-coded '.asm' file list --- build.ts | 140 +++++++++++++++++++----------------- readme.md | 5 +- src/lib/drawing.asm | 9 +-- src/lib/game.asm | 1 - src/lib/sprite_rotation.asm | 4 -- src/main.asm | 21 ++++++ 6 files changed, 101 insertions(+), 79 deletions(-) diff --git a/build.ts b/build.ts index dc881db..cc8a7d9 100755 --- a/build.ts +++ b/build.ts @@ -1,4 +1,4 @@ -#!/usr/bin/env -S deno run --allow-read --allow-run +#!/usr/bin/env -S deno run --allow-read --allow-write --allow-run import { TextLineStream } from "jsr:@std/streams@1.1.0"; @@ -9,25 +9,7 @@ import { TextLineStream } from "jsr:@std/streams@1.1.0"; const instructionLength = 4; // in bytes const rawDataInstructionLengthPrefix = "U32"; -const ppmToAsmScriptPath = './ppmToAsm.ts'; - -const asmFilePaths = [ - "src/consts/arch.asm", - "src/consts/game.asm", - "src/consts/keyboard.asm", - "src/consts/map.asm", - "src/consts/scene.asm", - "src/consts/screen.asm", - "src/consts/sprites.asm", - - "src/init.asm", - "src/main.asm", - - "src/lib/game.asm", - "src/lib/drawing.asm", - "src/lib/sprite_rotation.asm", -]; -const reservedSpacePath = "src/reserved_space.asm"; +const ppmToAsmScriptPath = "./ppmToAsm.ts"; const mapPath = "assets/map.ppm"; const spritesPath = { @@ -117,7 +99,11 @@ class Minifier { } } -async function ppmToAsm(mode: "map" | "sprite", label: string, path: string): Promise { +async function ppmToAsm( + mode: "map" | "sprite", + label: string, + path: string, +): Promise { const command = new Deno.Command(ppmToAsmScriptPath, { args: [ instructionLength.toString(), @@ -140,62 +126,55 @@ async function ppmToAsm(mode: "map" | "sprite", label: string, path: string): Pr return decoder.decode(stdout); } -async function build(output: WritableStreamDefaultWriter) { - for (const path of asmFilePaths) { - const file = await Deno.open(path); - const readable = file.readable.pipeThrough(new TextDecoderStream()) - .pipeThrough(new TextLineStream()); +async function build( + pwd: string, + path: string, + output: WritableStreamDefaultWriter, +) { + const includeExpressionRegex = /^\s*include\s+([^\s;]+)/; - await output.write(` -;/******************************* -;* ${path} -;*******************************/ -`); + const file = await Deno.open(`${pwd}/${path}`); + const readable = file.readable.pipeThrough(new TextDecoderStream()) + .pipeThrough(new TextLineStream()); - for await (const line of readable) { - if (!line.startsWith("include ")) { - await output.write(`${line}\n`); - } + for await (const line of readable) { + const match = line.match(includeExpressionRegex); + if (match === null) { + await output.write(`${line}\n`); + continue; } + + await build(pwd, `${match[1]}.asm`, output); } - - const asmRawRepresentations = await Promise.all([ - ppmToAsm("map", "map", mapPath), - ...Object.entries(spritesPath).map(([label, path]) => ppmToAsm("sprite", label, path)), - ]); - - await output.write(asmRawRepresentations.join("\n\n")); - - await output.write(` -; -; RESERVED RAM SPACE -; -`); - - await output.write(await Deno.readTextFile(reservedSpacePath)); - await output.close(); } function printUsage(into: (message: string) => void) { into(`Usage: -\tbuild.ts [--minify] -\t\tMerge the hardcoded files and output the result to stdout. The "--minify" option can be used to strip the result of all comments, leading/trailing double whitespaces, empty lines and use the more compact raw data representation. +\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`); +\t\tPrint this message + +Options: +\t--minify\tWhen used, the result will be stripped of all comments, leading/trailing double whitespaces, empty lines and use the more compact raw data representation. +\t--ppm \tWhen used, the hardcoded ppm files will be translated to their matching Symfony assembly reprensentation into src/dist.`); } /** * SCRIPT */ -if (Deno.args.length > 1) { +if (Deno.args.length < 1) { printUsage(console.error); Deno.exit(1); } let minify = false; -if (Deno.args.length === 1) { - switch (Deno.args[0]) { +let convertPPM = false; +let path: string | undefined = undefined; + +Deno.args.forEach((arg, index) => { + switch (arg) { case "--help": printUsage(console.log); Deno.exit(0); @@ -203,11 +182,42 @@ if (Deno.args.length === 1) { case "--minify": minify = true; break; + case "--ppm": + convertPPM = true; + break; + default: - printUsage(console.error); - Deno.exit(1); + 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(); @@ -217,9 +227,9 @@ const stdoutWritable = new WritableStream({ }, }); -if (minify) { - const minifier = new Minifier(stdoutWritable); - await build(minifier.stream.writable.getWriter()); -} else { - await build(stdoutWritable.getWriter()); -} \ No newline at end of file +const writer = minify + ? (new Minifier(stdoutWritable)).stream.writable.getWriter() + : stdoutWritable.getWriter(); + +await build(`${Deno.cwd()}/src`, path, writer); +await writer.close(); diff --git a/readme.md b/readme.md index d5a85d8..226cf2d 100755 --- a/readme.md +++ b/readme.md @@ -43,7 +43,10 @@ Bundle all `.asm` and `.ppm` files and output the (optionaly minified) result. Usage example: ```sh -./build.ts --minify > pacman.asm +./build.ts --minify main.asm > pacman.asm + +# To beforehand convert all ppm files +./build.ts --ppm --minify main.asm > pacman.asm ``` ## Documentation diff --git a/src/lib/drawing.asm b/src/lib/drawing.asm index 2424274..ad4a7e5 100644 --- a/src/lib/drawing.asm +++ b/src/lib/drawing.asm @@ -1,11 +1,4 @@ -include ../consts/offsets -include ../consts/map -include ../consts/screen -include ../consts/scene -include ../consts/sprites -include sprite_rotation - -; /* Draw the whole (fill the screen memory with the values of the sprites based on the map) +; /* Draw the whole map (fill the screen memory with the values of the sprites based on the map) ; Inputs: diff --git a/src/lib/game.asm b/src/lib/game.asm index 64a86cb..a7f805b 100644 --- a/src/lib/game.asm +++ b/src/lib/game.asm @@ -1,4 +1,3 @@ - ; /* Return the tile address from the given map coordinate ; Inputs: diff --git a/src/lib/sprite_rotation.asm b/src/lib/sprite_rotation.asm index 2d3a1f0..3d3f70c 100644 --- a/src/lib/sprite_rotation.asm +++ b/src/lib/sprite_rotation.asm @@ -1,7 +1,3 @@ -include ../consts/offsets -include ../consts/map -include ../consts/sprites - ; /* Return the sprite id and rotation for the given map coordinates ; Inputs: diff --git a/src/main.asm b/src/main.asm index be88511..81de587 100644 --- a/src/main.asm +++ b/src/main.asm @@ -1,4 +1,10 @@ +include consts/arch +include consts/game +include consts/keyboard +include consts/map +include consts/scene include consts/screen +include consts/sprites include init @@ -347,4 +353,19 @@ game_loop: jmp game_loop +include lib/game include lib/drawing +include lib/sprite_rotation + +include dist/empty +include dist/placeholder +include dist/wall_end +include dist/wall_straight +include dist/wall_corner +include dist/robot +include dist/ghost +include dist/coin + +include dist/map + +include reserved_space From c78a2ff7a00c7cb461d1bf0242971731cc26f502 Mon Sep 17 00:00:00 2001 From: Robin Chappatte Date: Fri, 1 May 2026 18:27:51 +0200 Subject: [PATCH 07/10] handle multi-line comments --- build.ts | 27 +++++++++ src/consts/game.asm | 10 ++-- src/consts/screen.asm | 34 +++++------ src/init.asm | 18 +++--- src/lib/drawing.asm | 112 +++++++++++++++++------------------- src/lib/game.asm | 14 ++--- src/lib/sprite_rotation.asm | 32 +++++------ src/main.asm | 109 +++++++++++++++++------------------ 8 files changed, 188 insertions(+), 168 deletions(-) diff --git a/build.ts b/build.ts index cc8a7d9..fcb59e6 100755 --- a/build.ts +++ b/build.ts @@ -30,6 +30,7 @@ const spritesPath = { class Minifier { private buffer = ""; private lines: Array = []; + private insideComment = false; stream; constructor(writableStream: WritableStream) { @@ -51,6 +52,32 @@ class Minifier { } 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 diff --git a/src/consts/game.asm b/src/consts/game.asm index bbf69f2..9ef3ae8 100644 --- a/src/consts/game.asm +++ b/src/consts/game.asm @@ -4,11 +4,13 @@ pub const game.direction_down = 2 pub const game.direction_left = 3 pub const game.direction_still = 4 +/* ; 1_000_000_000 nano second = 0x0000_0000_3B9A_CA00 -; pub const game.tick_duration_0 = 0xCA00 -; pub const game.tick_duration_1 = 0x3B9A -; pub const game.tick_duration_2 = 0x0000 -; pub const game.tick_duration_3 = 0x0000 +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 diff --git a/src/consts/screen.asm b/src/consts/screen.asm index a4b8231..ce4ce52 100644 --- a/src/consts/screen.asm +++ b/src/consts/screen.asm @@ -1,21 +1,21 @@ -; Screen pixel mode (settings #0) -; 0: ASCII 8 -; 1: ASCII 24 -; 2: Pixel 8 -; 3: Pixel 24 - -; While in mode #2 (Pixel 8) - ; Screen memory offset (settings #1) - ; Screen resolutions (settings #2) - ; 0. 80x60 - ; 1. 160x120 - ; 2. 256x192 - ; 3. 320x240 - ; 4. 640x480 - ; 5. 800x600 - ; 6. 920x720 - ; 7. 1024x768 +/* Screen pixel mode (settings #0) + 0: ASCII 8 + 1: ASCII 24 + 2: Pixel 8 + 3: Pixel 24 + While in mode #2 (Pixel 8) + Screen memory offset (settings #1) + Screen resolutions (settings #2) + 0. 80x60 + 1. 160x120 + 2. 256x192 + 3. 320x240 + 4. 640x480 + 5. 800x600 + 6. 920x720 + 7. 1024x768 +*/ pub const screen.mode_index = 0 pub const screen.mode_value = 2 pub const screen.offset_index = 1 diff --git a/src/init.asm b/src/init.asm index c5688a2..fdda797 100644 --- a/src/init.asm +++ b/src/init.asm @@ -1,9 +1,9 @@ -; -; Jump table initialisation -; -; r1: sprite id -; r2: pointer address -; r3: pointer value +/* Jump table initialisation + + r1: sprite id + r2: pointer address + r3: pointer value +*/ ; sprite_empty add r1, zr, sprites.empty @@ -54,9 +54,9 @@ add r2, r2, ptr.textures_addresses add r3, zr, sprite_coin store_32 [r2], r3 -; -; Screen initialization -; +/* + Screen initialization +*/ ; r1 = screen parameter value mov r1, screen.mode_index diff --git a/src/lib/drawing.asm b/src/lib/drawing.asm index ad4a7e5..1f7b553 100644 --- a/src/lib/drawing.asm +++ b/src/lib/drawing.asm @@ -1,17 +1,13 @@ -; /* Draw the whole map (fill the screen memory with the values of the sprites based on the map) +/* Draw the whole map (fill the screen memory with the values of the sprites based on the map) -; Inputs: - -; Outputs: - -; Locals: -; r1: x map coord -; r2: y map coord -; r3: sprite rotation -; r4: sprite id -; r12: rotation (returned by `sprite and rotation`) -; r13: sprite (returned by `get_sprite_id_and_rotation`) -; */ + Locals: + r1: x map coord + r2: y map coord + r3: sprite rotation + r4: sprite id + r12: rotation (returned by `sprite and rotation`) + r13: sprite (returned by `get_sprite_id_and_rotation`) +*/ pub drawing.draw_whole_map: push r1 push r2 @@ -45,19 +41,19 @@ pub drawing.draw_whole_map: 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: -; r1: map x coord -; r2: map y coord + Inputs: + r1: map x coord + r2: map y coord -; Locals: -; r3: counter -; r4: offset per tile row (screen width * tile height) + Locals: + r3: counter + r4: offset per tile row (screen width * tile height) -; Ouputs: -; r13: the pixel address -; */ + Ouputs: + r13: the pixel address +*/ get_tile_address: push r3 push r4 @@ -104,17 +100,17 @@ get_tile_address: pop r3 ret -; /* Get address of given sprite id +/* Get address of given sprite id -; Inputs: -; r1: sprite id + Inputs: + r1: sprite id -; Outputs: -; r13: address + Outputs: + r13: address -; Locals: -; r2: pointer address -; */ + Locals: + r2: pointer address +*/ get_sprite_address: push r1 push r2 @@ -132,21 +128,19 @@ get_sprite_address: pop r1 ret -; /* Draw given sprite on the screen at given map coordinates +/* Draw given sprite on the screen at given map coordinates -; Inputs: -; r1: map x coord -; r2: map y coord -; r3: sprite rotation -; r4: sprite id + Inputs: + r1: map x coord + r2: map y coord + r3: sprite rotation + r4: sprite id -; Outputs: - -; Locals: -; r1: screen address -; r2: sprite address -; r13: value returned by call to `get_tile_address` and `get_sprite_address` -; */ + Locals: + r1: screen address + r2: sprite address + r13: value returned by call to `get_tile_address` and `get_sprite_address` +*/ pub drawing.draw_sprite_at_map_coord: push r1 push r2 @@ -172,24 +166,22 @@ pub drawing.draw_sprite_at_map_coord: pop r1 ret -; /* Draw given sprite (by address) at given screen position (by address) with given rotation +/* Draw given sprite (by address) at given screen position (by address) with given rotation -; Inputs: -; r1: screen address -; r2: sprite address -; r3: rotation + Inputs: + r1: screen address + r2: sprite address + r3: rotation -; Outputs: - -; Locals: -; r1: current screen pixel address -; r4: x -; r5: y -; r6: current sprite pixel address -; r7: color -; r8: x increment -; r9: y increment -; */ + Locals: + r1: current screen pixel address + r4: x + r5: y + r6: current sprite pixel address + r7: color + r8: x increment + r9: y increment +*/ draw_sprite: push r1 push r2 diff --git a/src/lib/game.asm b/src/lib/game.asm index a7f805b..e23967e 100644 --- a/src/lib/game.asm +++ b/src/lib/game.asm @@ -1,12 +1,12 @@ -; /* Return the tile address from the given map coordinate +/* Return the tile address from the given map coordinate -; Inputs: -; r1: x map coordinate -; r2: y map coordinate + Inputs: + r1: x map coordinate + r2: y map coordinate -; Outputs: -; r13: tile address -; */ + Outputs: + r13: tile address +*/ pub game.get_tile_address_from_map_coord: push r1 push r2 diff --git a/src/lib/sprite_rotation.asm b/src/lib/sprite_rotation.asm index 3d3f70c..4336162 100644 --- a/src/lib/sprite_rotation.asm +++ b/src/lib/sprite_rotation.asm @@ -1,22 +1,22 @@ -; /* Return the sprite id and rotation for the given map coordinates +/* Return the sprite id and rotation for the given map coordinates -; Inputs: -; r1: x map coord -; r2: y map coord + Inputs: + r1: x map coord + r2: y map coord -; Outputs: -; r12: sprite rotation -; r13: sprite id + Outputs: + r12: sprite rotation + r13: sprite id -; Locals: -; r3: tile id -; r4: map offset -; r5: counter -; r6: left tile id -; r7: up tile id -; r8: right tile id -; r9: down tile id -; */ + Locals: + r3: tile id + r4: map offset + r5: counter + r6: left tile id + r7: up tile id + r8: right tile id + r9: down tile id +*/ pub sprite_rotation.get_sprite_id_and_rotation: push r1 push r2 diff --git a/src/main.asm b/src/main.asm index 81de587..1465223 100644 --- a/src/main.asm +++ b/src/main.asm @@ -10,9 +10,9 @@ include init call drawing.draw_whole_map -; -; State initialization -; +/* + State initialization +*/ ; Robot const robot_initial_x = 1 const robot_initial_y = 1 @@ -51,9 +51,9 @@ mov r3, sprites.rotation_0 mov r4, sprites.ghost call drawing.draw_sprite_at_map_coord -; -; Time initialization -; +/* + Time initialization +*/ add r1, zr, ptr.tick_duration_low load_32 r2, [r1] add r1, zr, ptr.tick_duration_high @@ -74,13 +74,13 @@ add r1, zr, ptr.next_tick_high store_32 [r1], r5 game_loop: - ; - ; Event handling - ; - ; r1 = keyboard event+key / direction - ; r2 = keyboard key / direction address - ; r3 = keyboard event - ; r4 = jump address + /* 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 @@ -109,7 +109,7 @@ game_loop: jmp game_loop_event_handling_store_direction ; key_left: mov r1, game.direction_left -; jmp game_loop_event_handling_end + ;jmp game_loop_event_handling_end game_loop_event_handling_store_direction: add r2, zr, ptr.state_robot @@ -118,15 +118,14 @@ game_loop: 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 - ; + /* 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 @@ -159,18 +158,18 @@ game_loop: 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 + /* 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 @@ -209,9 +208,9 @@ game_loop: mov r7, r13 pop r2 - ; - ; Collision handling - ; + /* + Collision handling + */ load_8 r8, [r7] cmp r8, map.tiles.wall jne game_loop_robot_movement_some @@ -249,20 +248,20 @@ game_loop: 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 + /* 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 @@ -331,9 +330,9 @@ game_loop: mov r4, sprites.ghost call drawing.draw_sprite_at_map_coord - ; - ; Check ahead for direction change - ; + /* + Check ahead for direction change + */ add r1, r7, r5 add r2, r8, r6 call game.get_tile_address_from_map_coord From 425b83b1bb84a52f88d23bc90be4da748422bace Mon Sep 17 00:00:00 2001 From: Robin Chappatte Date: Fri, 1 May 2026 18:48:12 +0200 Subject: [PATCH 08/10] split code in sections --- src/main.asm | 345 +----------------- src/sections/characters_movement.asm | 231 ++++++++++++ src/sections/handle_event.asm | 43 +++ src/sections/init_ghost_state.asm | 17 + src/sections/init_robot_state.asm | 17 + src/sections/init_screen.asm | 10 + .../init_sprite_lookup_table.asm} | 15 - src/sections/init_timers.asm | 18 + 8 files changed, 343 insertions(+), 353 deletions(-) create mode 100644 src/sections/characters_movement.asm create mode 100644 src/sections/handle_event.asm create mode 100644 src/sections/init_ghost_state.asm create mode 100644 src/sections/init_robot_state.asm create mode 100644 src/sections/init_screen.asm rename src/{init.asm => sections/init_sprite_lookup_table.asm} (85%) create mode 100644 src/sections/init_timers.asm diff --git a/src/main.asm b/src/main.asm index 1465223..9282b9c 100644 --- a/src/main.asm +++ b/src/main.asm @@ -6,349 +6,18 @@ include consts/scene include consts/screen include consts/sprites -include init +include sections/init_sprite_lookup_table +include sections/init_screen call drawing.draw_whole_map -/* - State initialization -*/ -; Robot -const robot_initial_x = 1 -const robot_initial_y = 1 -const robot_initial_direction = game.direction_down - -mov r1, robot_initial_x -mov r2, robot_initial_y -mov r5, robot_initial_direction -add r6, zr, ptr.state_robot -store_8 [r6], r1 -add r6, r6, 1 -store_8 [r6], r2 -add r6, r6, 1 -store_8 [r6], r5 - -mov r3, sprites.rotation_0 -mov r4, sprites.robot -call drawing.draw_sprite_at_map_coord - -; Ghost -const ghost_initial_x = 13 -const ghost_initial_y = 1 -const ghost_initial_direction = game.direction_down - -mov r1, ghost_initial_x -mov r2, ghost_initial_y -mov r5, ghost_initial_direction -add r6, zr, ptr.state_ghost -store_8 [r6], r1 -add r6, r6, 1 -store_8 [r6], r2 -add r6, r6, 1 -store_8 [r6], r5 - -mov r3, sprites.rotation_0 -mov r4, sprites.ghost -call drawing.draw_sprite_at_map_coord - -/* - Time initialization -*/ -add r1, zr, ptr.tick_duration_low -load_32 r2, [r1] -add r1, zr, ptr.tick_duration_high -load_32 r3, [r1] - -time_0 r4 -time_1 r5 -add r4, r4, r2 -add r5, r5, r3 -cmp r4, r2 -jae init_store_next_tick -add r5, r5, 1 ; overflow - -init_store_next_tick: -add r1, zr, ptr.next_tick_low -store_32 [r1], r4 -add r1, zr, ptr.next_tick_high -store_32 [r1], r5 +include sections/init_robot_state +include sections/init_ghost_state +include sections/init_timers game_loop: - /* Event handling - - r1 = keyboard event+key / direction - r2 = keyboard key / direction address - r3 = keyboard event - r4 = jump address - */ - keyboard r1 - and r2, r1, keyboard.mask_key - and r3, r1, keyboard.mask_event - - cmp r3, keyboard.event_pressed - jne game_loop_event_handling_end - cmp r2, keyboard.key_min - jb game_loop_event_handling_end - cmp r2, keyboard.key_max - ja game_loop_event_handling_end - - sub r4, r2, keyboard.key_min ; so the first value is 0 - lsl r4, r4, 3 ; 2 * instruction length = 8 - add r4, r4, game_loop_key_jump_table_start - jmp r4 - - game_loop_key_jump_table_start: - ; key_up - mov r1, game.direction_up - jmp game_loop_event_handling_store_direction - ; key_right - mov r1, game.direction_right - jmp game_loop_event_handling_store_direction - ; key_down: - mov r1, game.direction_down - jmp game_loop_event_handling_store_direction - ; key_left: - mov r1, game.direction_left - ;jmp game_loop_event_handling_end - - game_loop_event_handling_store_direction: - add r2, zr, 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 - mov r1, r5 - mov r2, r6 - call game.get_tile_address_from_map_coord - mov r7, r13 - pop r2 - - /* - Collision handling - */ - load_8 r8, [r7] - cmp r8, map.tiles.wall - jne game_loop_robot_movement_some - ; hit wall - add r1, zr, ptr.state_robot - add r1, r1, 2 - mov r4, game.direction_still - store_8 [r1], r4 - jmp game_loop_robot_movement_none - - game_loop_robot_movement_some: - ; TODO: handle coin and empty differently - mov r8, map.tiles.empty - store_8 [r7], r8 - - add r1, zr, ptr.state_robot - store_8 [r1], r5 - add r1, r1, 1 - store_8 [r1], r6 - - ; clear old position - mov r1, r2 - mov r2, r3 - mov r3, sprites.rotation_0 - mov r4, sprites.empty - call drawing.draw_sprite_at_map_coord - - ; draw robot on new position - mov r1, r5 - mov r2, r6 - mov r3, sprites.rotation_0 - mov r4, sprites.robot - call drawing.draw_sprite_at_map_coord - - - game_loop_robot_movement_none: - - /* Ghost movement - - r1: ghost's state address - r2: x - r3: y - r4: direction - r5: x increment - r6: y increment - r7: new x - r8: new y - r9: new position address - r10: new position content - r11: jump address - */ - add r1, zr, ptr.state_ghost - load_8 r2, [r1] - add r1, r1, 1 - load_8 r3, [r1] - add r1, r1, 1 - load_8 r4, [r1] - - mov r5, 0 - mov r6, 0 - - lsl r11, r4, 3 ; 2 * instruction size = 8 - add r11, r11, game_loop_ghost_movement_jump_table_start - jmp r11 - - game_loop_ghost_movement_jump_table_start: - ; direction_up - sub r6, r6, 1 - jmp game_loop_ghost_movement_make_move - ; direction_right - add r5, r5, 1 - jmp game_loop_ghost_movement_make_move - ; direction_down - add r6, r6, 1 - jmp game_loop_ghost_movement_make_move - ; direction_left - sub r5, r5, 1 - ;jmp game_loop_ghost_movement_make_move - ; direction_still (should never happen) - - game_loop_ghost_movement_make_move: - add r7, r2, r5 - add r8, r3, r6 - add r1, zr, ptr.state_ghost - store_8 [r1], r7 - add r1, r1, 1 - store_8 [r1], r8 - - ; get tile at previous position - push r2 - mov r1, r2 - mov r2, r3 - call game.get_tile_address_from_map_coord - mov r9, r13 - pop r2 - - ; draw tile at previous position - load_8 r1, [r9] - cmp r1, map.tiles.coin - je game_loop_ghost_movement_old_tile_coin - ; old tile empty - mov r4, sprites.empty - jmp game_loop_ghost_movement_draw_old_tile - game_loop_ghost_movement_old_tile_coin: - mov r4, sprites.coin - - game_loop_ghost_movement_draw_old_tile: - mov r1, r2 - mov r2, r3 - mov r3, sprites.rotation_0 - call drawing.draw_sprite_at_map_coord - - ; draw ghost on new position - mov r1, r7 - mov r2, r8 - mov r3, sprites.rotation_0 - mov r4, sprites.ghost - call drawing.draw_sprite_at_map_coord - - /* - Check ahead for direction change - */ - add r1, r7, r5 - add r2, r8, r6 - call game.get_tile_address_from_map_coord - - load_8 r10, [r13] - cmp r10, map.tiles.wall - jne game_loop_ghost_movement_end - ; increment direction - add r1, zr, ptr.state_ghost - add r1, r1, 2 - load_8 r4, [r1] - add r4, r4, 1 - and r4, r4, 0b11 ; modulo 3 - store_8 [r1], r4 - - game_loop_ghost_movement_end: + include sections/handle_event + include sections/characters_movement jmp game_loop diff --git a/src/sections/characters_movement.asm b/src/sections/characters_movement.asm new file mode 100644 index 0000000..b5afced --- /dev/null +++ b/src/sections/characters_movement.asm @@ -0,0 +1,231 @@ +/* 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 +mov r1, r5 +mov r2, r6 +call game.get_tile_address_from_map_coord +mov r7, r13 +pop r2 + +/* + Collision handling +*/ +load_8 r8, [r7] +cmp r8, map.tiles.wall +jne game_loop_robot_movement_some +; hit wall +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: \ No newline at end of file diff --git a/src/sections/handle_event.asm b/src/sections/handle_event.asm new file mode 100644 index 0000000..f219eab --- /dev/null +++ b/src/sections/handle_event.asm @@ -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: \ No newline at end of file diff --git a/src/sections/init_ghost_state.asm b/src/sections/init_ghost_state.asm new file mode 100644 index 0000000..5bb41d0 --- /dev/null +++ b/src/sections/init_ghost_state.asm @@ -0,0 +1,17 @@ +const ghost_initial_x = 13 +const ghost_initial_y = 1 +const ghost_initial_direction = game.direction_down + +mov r1, ghost_initial_x +mov r2, ghost_initial_y +mov r5, ghost_initial_direction +add r6, zr, ptr.state_ghost +store_8 [r6], r1 +add r6, r6, 1 +store_8 [r6], r2 +add r6, r6, 1 +store_8 [r6], r5 + +mov r3, sprites.rotation_0 +mov r4, sprites.ghost +call drawing.draw_sprite_at_map_coord \ No newline at end of file diff --git a/src/sections/init_robot_state.asm b/src/sections/init_robot_state.asm new file mode 100644 index 0000000..383244a --- /dev/null +++ b/src/sections/init_robot_state.asm @@ -0,0 +1,17 @@ +const robot_initial_x = 1 +const robot_initial_y = 1 +const robot_initial_direction = game.direction_down + +mov r1, robot_initial_x +mov r2, robot_initial_y +mov r5, robot_initial_direction +add r6, zr, ptr.state_robot +store_8 [r6], r1 +add r6, r6, 1 +store_8 [r6], r2 +add r6, r6, 1 +store_8 [r6], r5 + +mov r3, sprites.rotation_0 +mov r4, sprites.robot +call drawing.draw_sprite_at_map_coord \ No newline at end of file diff --git a/src/sections/init_screen.asm b/src/sections/init_screen.asm new file mode 100644 index 0000000..aecff20 --- /dev/null +++ b/src/sections/init_screen.asm @@ -0,0 +1,10 @@ +; r1 = screen parameter value +mov r1, screen.mode_index +screen r1, screen.mode_value + +mov r1, screen.offset_index +add r2, zr, ptr.screen +screen r1, r2 + +mov r1, screen.resolution_index +screen r1, screen.resolution_value \ No newline at end of file diff --git a/src/init.asm b/src/sections/init_sprite_lookup_table.asm similarity index 85% rename from src/init.asm rename to src/sections/init_sprite_lookup_table.asm index fdda797..1960054 100644 --- a/src/init.asm +++ b/src/sections/init_sprite_lookup_table.asm @@ -53,18 +53,3 @@ 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 diff --git a/src/sections/init_timers.asm b/src/sections/init_timers.asm new file mode 100644 index 0000000..415be9a --- /dev/null +++ b/src/sections/init_timers.asm @@ -0,0 +1,18 @@ +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 \ No newline at end of file From 8b3b2aa20d15cbc2dc1c5503b93deaa7a2597579 Mon Sep 17 00:00:00 2001 From: Robin Chappatte Date: Fri, 1 May 2026 21:13:55 +0200 Subject: [PATCH 09/10] add directional characters display --- assets/sprites/{ghost.ppm => ghost_down.ppm} | Bin assets/sprites/ghost_left.ppm | Bin 0 -> 130 bytes assets/sprites/ghost_right.ppm | Bin 0 -> 130 bytes assets/sprites/ghost_up.ppm | Bin 0 -> 130 bytes assets/sprites/robot.ppm | Bin 130 -> 0 bytes assets/sprites/robot_down.ppm | Bin 0 -> 130 bytes assets/sprites/robot_left.ppm | Bin 0 -> 130 bytes assets/sprites/robot_right.ppm | Bin 0 -> 130 bytes assets/sprites/robot_up.ppm | Bin 0 -> 130 bytes build.ts | 10 +- src/consts/game.asm | 15 -- src/consts/sprites.asm | 19 +- src/lib/drawing.asm | 53 +++++ src/lib/u64.asm | 18 ++ src/main.asm | 36 ++- src/reserved_space.asm | 17 +- src/sections/characters_movement.asm | 231 ------------------- src/sections/init_ghost_state.asm | 2 +- src/sections/init_robot_state.asm | 2 +- src/sections/init_sprite_lookup_table.asm | 51 +++- src/sections/init_timers.asm | 48 ++-- src/sections/timer_ghost.asm | 99 ++++++++ src/sections/timer_robot.asm | 99 ++++++++ 23 files changed, 417 insertions(+), 283 deletions(-) rename assets/sprites/{ghost.ppm => ghost_down.ppm} (100%) create mode 100644 assets/sprites/ghost_left.ppm create mode 100644 assets/sprites/ghost_right.ppm create mode 100644 assets/sprites/ghost_up.ppm delete mode 100644 assets/sprites/robot.ppm create mode 100644 assets/sprites/robot_down.ppm create mode 100644 assets/sprites/robot_left.ppm create mode 100644 assets/sprites/robot_right.ppm create mode 100644 assets/sprites/robot_up.ppm create mode 100644 src/lib/u64.asm delete mode 100644 src/sections/characters_movement.asm create mode 100644 src/sections/timer_ghost.asm create mode 100644 src/sections/timer_robot.asm diff --git a/assets/sprites/ghost.ppm b/assets/sprites/ghost_down.ppm similarity index 100% rename from assets/sprites/ghost.ppm rename to assets/sprites/ghost_down.ppm diff --git a/assets/sprites/ghost_left.ppm b/assets/sprites/ghost_left.ppm new file mode 100644 index 0000000000000000000000000000000000000000..5e632614cd9c85d77e087c15d2bb69ea721745f2 GIT binary patch literal 130 zcmWGA<5E^|E=o--Nlj5ms#I|I^bJrbOD!tS%+FIW)-%#GQ3&w!RVc_QP1nuL<1$q+ g(^b literal 0 HcmV?d00001 diff --git a/assets/sprites/ghost_right.ppm b/assets/sprites/ghost_right.ppm new file mode 100644 index 0000000000000000000000000000000000000000..e72da9a532525cc33051f2288550b98766dc984c GIT binary patch literal 130 zcmWGA<5E^|E=o--Nlj5ms#I|I^bJrbOD!tS%+FIW)-%#GQ3&w!RVc_QP1nuL<1$q+ igAh;lDkXi%@0B5%&CIA2c literal 0 HcmV?d00001 diff --git a/assets/sprites/robot_left.ppm b/assets/sprites/robot_left.ppm new file mode 100644 index 0000000000000000000000000000000000000000..68b44fae03b22f734cb569d9c3437f98f14a2016 GIT binary patch literal 130 zcmWGA<5E^|E=o--Nlj5ms#I|I^bJrbOD!tS%+FIW)-%#GQ3&w!RVc_QP1nuL<1$q+ l cancel movement +sub r1, r1, r5 +sub r2, r2, r6 +jmp timer_robot.draw + +pub timer_robot.move: +; TODO: handle coin and empty differently +mov r10, map.tiles.empty +store_8 [r13], r10 + +add r9, zr, ptr.state_robot +store_8 [r9], r1 +add r9, r9, 1 +store_8 [r9], r2 + +pub timer_robot.draw: +; draw robot on new position +mov r3, sprites.rotation_0 +add r4, r4, sprites.robot +call drawing.draw_sprite_at_map_coord + +pub timer_robot.end: \ No newline at end of file From ba12492ebf53410d73808217262394d8fc72f666 Mon Sep 17 00:00:00 2001 From: Robin Chappatte Date: Fri, 1 May 2026 21:14:51 +0200 Subject: [PATCH 10/10] offset ghost first movement time --- src/reserved_space.asm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reserved_space.asm b/src/reserved_space.asm index 2540222..2747dec 100644 --- a/src/reserved_space.asm +++ b/src/reserved_space.asm @@ -27,7 +27,7 @@ pub ptr.timer_robot_next_tick_high: U32 0x0000_0000 pub ptr.timer_robot_next_tick_low: U32 0x1DCD_6500 pub ptr.timer_ghost_next_tick_high: U32 0 -pub ptr.timer_ghost_next_tick_low: U32 0 +pub ptr.timer_ghost_next_tick_low: U32 0x0EE6_B280 pub ptr.state_robot: U8 0 ; x coord