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/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 0000000..5e63261 Binary files /dev/null and b/assets/sprites/ghost_left.ppm differ diff --git a/assets/sprites/ghost_right.ppm b/assets/sprites/ghost_right.ppm new file mode 100644 index 0000000..e72da9a Binary files /dev/null and b/assets/sprites/ghost_right.ppm differ diff --git a/assets/sprites/ghost_up.ppm b/assets/sprites/ghost_up.ppm new file mode 100644 index 0000000..c0f2bf7 Binary files /dev/null and b/assets/sprites/ghost_up.ppm differ diff --git a/assets/sprites/robot.ppm b/assets/sprites/robot.ppm deleted file mode 100644 index 3060135..0000000 Binary files a/assets/sprites/robot.ppm and /dev/null differ diff --git a/assets/sprites/robot_down.ppm b/assets/sprites/robot_down.ppm new file mode 100644 index 0000000..20b694a Binary files /dev/null and b/assets/sprites/robot_down.ppm differ diff --git a/assets/sprites/robot_left.ppm b/assets/sprites/robot_left.ppm new file mode 100644 index 0000000..68b44fa Binary files /dev/null and b/assets/sprites/robot_left.ppm differ diff --git a/assets/sprites/robot_right.ppm b/assets/sprites/robot_right.ppm new file mode 100644 index 0000000..9470f98 Binary files /dev/null and b/assets/sprites/robot_right.ppm differ diff --git a/assets/sprites/robot_up.ppm b/assets/sprites/robot_up.ppm new file mode 100644 index 0000000..dc649d1 Binary files /dev/null and b/assets/sprites/robot_up.ppm differ diff --git a/build.ts b/build.ts index 3f1b4b5..0e129f0 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-write --allow-run import { TextLineStream } from "jsr:@std/streams@1.1.0"; @@ -9,54 +9,22 @@ import { TextLineStream } from "jsr:@std/streams@1.1.0"; const instructionLength = 4; // in bytes const rawDataInstructionLengthPrefix = "U32"; -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", -]; -const initialScreenPath = "assets/screen.ppm"; -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 ppmToAsmScriptPath = "./ppmToAsm.ts"; 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_robot_up": "assets/sprites/robot_up.ppm", + "sprite_robot_right": "assets/sprites/robot_right.ppm", + "sprite_robot_down": "assets/sprites/robot_down.ppm", + "sprite_robot_left": "assets/sprites/robot_left.ppm", + "sprite_ghost_up": "assets/sprites/ghost_up.ppm", + "sprite_ghost_right": "assets/sprites/ghost_right.ppm", + "sprite_ghost_down": "assets/sprites/ghost_down.ppm", + "sprite_ghost_left": "assets/sprites/ghost_left.ppm", "sprite_coin": "assets/sprites/coin.ppm", + "screen": "assets/screen.ppm", }; /** @@ -66,6 +34,7 @@ const spritesPath = { class Minifier { private buffer = ""; private lines: Array = []; + private insideComment = false; stream; constructor(writableStream: WritableStream) { @@ -78,6 +47,8 @@ class Minifier { flush: (controller) => { this.buffer += "\n"; controller.enqueue(this.processChunk()); + controller.enqueue(this.lines.join("\n") + "\n"); + this.lines = []; }, }); @@ -85,6 +56,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 @@ -133,200 +130,82 @@ class Minifier { } } -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 ")) { - output.write(`${line}\n`); - } - } - } - - 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), - ); - } - - output.write(asmRawRepresentations.join("\n\n")); - - output.write(` -; -; RESERVED RAM SPACE -; -`); - - const screenBytes = await getDataFromPPM(initialScreenPath); - output.write(bytesToAsmConstU8("screen", screenBytes, spritesMatchingValues)); - - 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 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( +async function ppmToAsm( + mode: "map" | "sprite", 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)", - ); + 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); } - 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 decoder.decode(stdout); } -/** - * 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, - ]; +async function build( + pwd: string, + path: string, + output: WritableStreamDefaultWriter, +) { + const includeExpressionRegex = /^\s*include\s+([^\s;]+)/; - const hashtag = 0x23; + const file = await Deno.open(`${pwd}/${path}`); + const readable = file.readable.pipeThrough(new TextDecoderStream()) + .pipeThrough(new TextLineStream()); - 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++; + for await (const line of readable) { + const match = line.match(includeExpressionRegex); + if (match === null) { + await output.write(`${line}\n`); + continue; } - offset++; // end of comment + await build(pwd, `${match[1]}.asm`, output); } - - // 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] -\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); @@ -334,11 +213,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(); @@ -348,9 +258,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/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..226cf2d 100755 --- a/readme.md +++ b/readme.md @@ -25,16 +25,29 @@ The following branches contains variations, allowing comparisons of performances ## Build script -File inclusion is currently not supported by the game, so a `build.ts` script it provided that will concatenate the content of the hard-coded `.asm` files and output the result on stdout. +Both scripts uses Deno's API, so you'll need to have a `deno` binary in your PATH. -It also convert the PPM assets (map and sprites) into ASM raw values representation and append it after all the code. +### PPM to Symfony assembly + +Output the Symfony assembly representation of the given PPM file. Usage example: ```sh -./build.ts --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 main.asm > pacman.asm + +# To beforehand convert all ppm files +./build.ts --ppm --minify main.asm > pacman.asm +``` ## Documentation diff --git a/src/consts/game.asm b/src/consts/game.asm index bbf69f2..f73bb21 100644 --- a/src/consts/game.asm +++ b/src/consts/game.asm @@ -2,16 +2,3 @@ pub const game.direction_up = 0 pub const game.direction_right = 1 pub const game.direction_down = 2 pub const game.direction_left = 3 -pub const game.direction_still = 4 - -; 1_000_000_000 nano second = 0x0000_0000_3B9A_CA00 -; pub const game.tick_duration_0 = 0xCA00 -; pub const game.tick_duration_1 = 0x3B9A -; pub const game.tick_duration_2 = 0x0000 -; pub const game.tick_duration_3 = 0x0000 - -; 0.5 second = 500_000_000 nano second = 0x0000_0000_1DCD_6500 -pub const game.tick_duration_0 = 0x6500 -pub const game.tick_duration_1 = 0x1DCD -pub const game.tick_duration_2 = 0x0000 -pub const game.tick_duration_3 = 0x0000 \ No newline at end of file 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/consts/sprites.asm b/src/consts/sprites.asm index 9f6f12b..e61cb2f 100644 --- a/src/consts/sprites.asm +++ b/src/consts/sprites.asm @@ -2,9 +2,25 @@ pub const sprites.width = 5 pub const sprites.height = 5 ; unused as sprite are all square pub const sprites.size = 25 ; height x width -pub const sprites.empty = 0 +pub const sprites.rotation_0 = 0 +pub const sprites.rotation_90 = 1 +pub const sprites.rotation_180 = 2 +pub const sprites.rotation_270 = 3 + +pub const sprites.empty = 0 ; must stay 0 (some code avoid addition when it knows the tile is empty) pub const sprites.unimplemented = 1 + pub const sprites.robot = 2 -pub const sprites.ghost = 3 -pub const sprites.coin = 4 -pub const sprites.max = 4 ; for lookup table +pub const sprites.robot_up = 2 +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_up = 6 +pub const sprites.ghost_right = 7 +pub const sprites.ghost_down = 8 +pub const sprites.ghost_left = 9 + +pub const sprites.coin = 10 +pub const sprites.max = 10 ; for lookup table \ No newline at end of file diff --git a/src/init.asm b/src/init.asm deleted file mode 100644 index 06ed09b..0000000 --- a/src/init.asm +++ /dev/null @@ -1,52 +0,0 @@ -; -; Jump table initialisation -; -; r1: sprite id -; r2: pointer address -; r3: pointer value - -; sprite_empty -add r1, zr, sprites.empty -lsl r2, r1, arch.instruction_increment_shift -add r2, r2, ptr.textures_addresses -add r3, zr, sprite_empty -store_32 [r2], r3 -; sprite_unimplemented -add r1, zr, sprites.unimplemented -lsl r2, r1, arch.instruction_increment_shift -add r2, r2, ptr.textures_addresses -add r3, zr, sprite_unimplemented -store_32 [r2], r3 -; sprite_robot -add r1, zr, sprites.robot -lsl r2, r1, arch.instruction_increment_shift -add r2, r2, ptr.textures_addresses -add r3, zr, sprite_robot -store_32 [r2], r3 -; sprite_ghost -add r1, zr, sprites.ghost -lsl r2, r1, arch.instruction_increment_shift -add r2, r2, ptr.textures_addresses -add r3, zr, sprite_ghost -store_32 [r2], r3 -; sprite_coin -add r1, zr, sprites.coin -lsl r2, r1, arch.instruction_increment_shift -add r2, r2, ptr.textures_addresses -add r3, zr, sprite_coin -store_32 [r2], r3 - -; -; Screen initialization -; - -; r1 = screen parameter value -mov r1, screen.mode_index -screen r1, screen.mode_value - -mov r1, screen.offset_index -add r2, zr, screen -screen r1, r2 - -mov r1, screen.resolution_index -screen r1, screen.resolution_value diff --git a/src/lib/drawing.asm b/src/lib/drawing.asm index 03cbf44..14659f8 100644 --- a/src/lib/drawing.asm +++ b/src/lib/drawing.asm @@ -1,23 +1,68 @@ -include ../consts/offsets -include ../consts/map -include ../consts/screen -include ../consts/scene -include ../consts/sprites -include sprite_rotation +/* Draw the tile corresponding to the given coordinate with it's map content -; /* Return the top-left pixel address of the tile at the given map coordinates + Inputs: + r1, r2: Map coordinates x, y -; Inputs: -; r1: map x coord -; r2: map y coord + Locals: + r3: tile value / sprite id / sprite address offset + r9: pointer + r13: screen address +*/ +pub drawing.clear_tile: + push r1 + push r2 + push r3 + push r9 + push r13 -; Locals: -; r3: counter -; r4: offset per tile row (screen width * tile height) + add r9, r1, map + push r2 + pub drawing.clear_tile_row_loop: + cmp r2, 0 + je drawing.clear_tile_row_loop_end + sub r2, r2, 1 + add r9, r9, map.width + jmp drawing.clear_tile_row_loop + pub drawing.clear_tile_row_loop_end: + pop r2 -; Ouputs: -; r13: the pixel address -; */ + load_8 r3, [r9] + cmp r3, map.tiles.coin + add r9, zr, ptr.textures_addresses + jne drawing.clear_tile_get_screen_address + ; coin + mov r3, sprites.coin + lsl r3, r3, arch.instruction_increment_shift + add r9, r9, r3 + + pub drawing.clear_tile_get_screen_address: + call get_tile_address + + mov r1, r13 + load_32 r2, [r9] + mov r3, sprites.rotation_0 + call draw_sprite + + pop r13 + pop r9 + pop r3 + pop r2 + pop r1 + ret + +/* Return the top-left pixel address of the tile at the given map coordinates + + Inputs: + r1: map x coord + r2: map y coord + + Locals: + r3: counter + r4: offset per tile row (screen width * tile height) + + Ouputs: + r13: the pixel address +*/ get_tile_address: push r3 push r4 @@ -64,17 +109,18 @@ get_tile_address: pop r3 ret -; /* Get address of given sprite id -; Inputs: -; r1: sprite id +/* Get address of given sprite id -; Outputs: -; r13: address + Inputs: + r1: sprite id -; Locals: -; r2: pointer address -; */ + Outputs: + r13: address + + Locals: + r2: pointer address +*/ get_sprite_address: push r1 push r2 @@ -92,20 +138,18 @@ 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 id + Inputs: + r1: map x coord + r2: map y coord + r3: 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 @@ -129,21 +173,18 @@ 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 -; Inputs: -; r1: screen address -; r2: sprite address - -; Outputs: - -; Locals: -; r1: current screen pixel address -; r2: current sprite pixel address -; r3: x -; r4: color -; r5: sprite last pixel address + 1 -; */ + Locals: + r1: current screen pixel address + r2: current sprite pixel address + r3: x + r4: color + r5: sprite last pixel address + 1 +*/ draw_sprite: push r1 push r2 diff --git a/src/lib/game.asm b/src/lib/game.asm index 64a86cb..e23967e 100644 --- a/src/lib/game.asm +++ b/src/lib/game.asm @@ -1,13 +1,12 @@ +/* Return the tile address from the given map coordinate -; /* Return the tile address from the given map coordinate + Inputs: + r1: x map coordinate + r2: y map coordinate -; Inputs: -; r1: x map coordinate -; r2: y map coordinate - -; Outputs: -; r13: tile address -; */ + Outputs: + r13: tile address +*/ pub game.get_tile_address_from_map_coord: push r1 push r2 diff --git a/src/lib/u64.asm b/src/lib/u64.asm new file mode 100644 index 0000000..a0d8e6b --- /dev/null +++ b/src/lib/u64.asm @@ -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 \ No newline at end of file diff --git a/src/main.asm b/src/main.asm index 6118fc4..f2200d0 100644 --- a/src/main.asm +++ b/src/main.asm @@ -1,337 +1,65 @@ +include consts/arch +include consts/game +include consts/keyboard +include consts/map +include consts/scene include consts/screen +include consts/sprites -include init +include sections/init_sprite_lookup_table +include sections/init_screen -; -; 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.robot -call drawing.draw_sprite_at_map_coord - -; Ghost -const ghost_initial_x = 13 -const ghost_initial_y = 1 -const ghost_initial_direction = game.direction_down - -mov r1, ghost_initial_x -mov r2, ghost_initial_y -mov r5, ghost_initial_direction -add r6, zr, ptr.state_ghost -store_8 [r6], r1 -add r6, r6, 1 -store_8 [r6], r2 -add r6, r6, 1 -store_8 [r6], r5 - -mov r3, sprites.ghost -call drawing.draw_sprite_at_map_coord - -; -; Time initialization -; -add r1, zr, ptr.tick_duration_low -load_32 r2, [r1] -add r1, zr, ptr.tick_duration_high -load_32 r3, [r1] - -time_0 r4 -time_1 r5 -add r4, r4, r2 -add r5, r5, r3 -cmp r4, r2 -jae init_store_next_tick -add r5, r5, 1 ; overflow - -init_store_next_tick: -add r1, zr, ptr.next_tick_low -store_32 [r1], r4 -add r1, zr, ptr.next_tick_high -store_32 [r1], r5 +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 + include sections/handle_event - 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 + /* + For all timers: + r1, r2: tick duration high, low + r7, r8: now high, low + */ + add r9, zr, ptr.tick_duration_high + load_32 r1, [r9] + add r9, zr, ptr.tick_duration_low + load_32 r2, [r9] + time_0 r8 + time_1 r7 - game_loop_key_jump_table_start: - ; key_up - mov r1, game.direction_up - jmp game_loop_event_handling_store_direction - ; key_right - mov r1, game.direction_right - jmp game_loop_event_handling_store_direction - ; key_down: - mov r1, game.direction_down - jmp game_loop_event_handling_store_direction - ; key_left: - mov r1, game.direction_left -; jmp game_loop_event_handling_end - - game_loop_event_handling_store_direction: - add r2, zr, ptr.state_robot - add r2, r2, 2 - store_8 [r2], r1 - - game_loop_event_handling_end: - - ; - ; Elapsed time check - ; - ; r1: address - ; r2: now low / delay low - ; r3: now high / delay high - ; r4: next tick low - ; r5: next tick high - ; - time_0 r2 - time_1 r3 - - add r1, zr, ptr.next_tick_low - load_32 r4, [r1] - add r1, zr, ptr.next_tick_high - load_32 r5, [r1] - - cmp r3, r5 - jb game_loop - ja update_next_tick - cmp r2, r4 - jb game_loop - - update_next_tick: - add r1, zr, ptr.tick_duration_low - load_32 r2, [r1] - add r1, zr, ptr.tick_duration_high - load_32 r3, [r1] - add r4, r4, r2 - add r5, r5, r3 - cmp r4, r2 - jae store_next_tick - add r5, r5, 1 - - store_next_tick: - add r1, zr, ptr.next_tick_low - store_32 [r1], r4 - add r1, zr, ptr.next_tick_high - store_32 [r1], r5 - - - ; - ; Robot movement - ; - ; r1 = robot's state address - ; r2 = x - ; r3 = y - ; r4 = direction - ; r5 = new x - ; r6 = new y - ; r7 = new position address - ; r8 = new position content - ; r9 = jump address - add r1, zr, ptr.state_robot - load_8 r2, [r1] - add r1, r1, 1 - load_8 r3, [r1] - add r1, r1, 1 - load_8 r4, [r1] - - mov r5, r2 - mov r6, r3 - - lsl r9, r4, 3 ; 2 * instruction size = 8 - add r9, r9, game_loop_robot_movement_jump_table_start - jmp r9 - - game_loop_robot_movement_jump_table_start: - ; direction_up - sub r6, r6, 1 - jmp game_loop_robot_movement_try_move - ; direction_right - add r5, r5, 1 - jmp game_loop_robot_movement_try_move - ; direction_down - add r6, r6, 1 - jmp game_loop_robot_movement_try_move - ; direction_left - sub r5, r5, 1 - jmp game_loop_robot_movement_try_move - ; direction_still - jmp game_loop_robot_movement_none - - game_loop_robot_movement_try_move: + push r1 push r2 - mov r1, r5 - mov r2, r6 - call game.get_tile_address_from_map_coord - mov r7, r13 + push r7 + push r8 + include sections/timer_robot + pop r8 + pop r7 pop r2 + pop r1 - ; - ; Collision handling - ; - load_8 r8, [r7] - cmp r8, map.tiles.wall - jne game_loop_robot_movement_some - ; hit wall - add r1, zr, ptr.state_robot - add r1, r1, 2 - mov r4, game.direction_still - store_8 [r1], r4 - jmp game_loop_robot_movement_none - - game_loop_robot_movement_some: - ; TODO: handle coin and empty differently - mov r8, map.tiles.empty - store_8 [r7], r8 - - add r1, zr, ptr.state_robot - store_8 [r1], r5 - add r1, r1, 1 - store_8 [r1], r6 - - ; clear old position - mov r1, r2 - mov r2, r3 - mov r3, sprites.empty - call drawing.draw_sprite_at_map_coord - - ; draw robot on new position - mov r1, r5 - mov r2, r6 - mov r3, sprites.robot - call drawing.draw_sprite_at_map_coord - - - game_loop_robot_movement_none: - - ; - ; Ghost movement - ; - ; r1: ghost's state address - ; r2: x - ; r3: y - ; r4: direction - ; r5: x increment - ; r6: y increment - ; r7: new x - ; r8: new y - ; r9: new position address - ; r10: new position content - ; r11: jump address - add r1, zr, ptr.state_ghost - load_8 r2, [r1] - add r1, r1, 1 - load_8 r3, [r1] - add r1, r1, 1 - load_8 r4, [r1] - - mov r5, 0 - mov r6, 0 - - lsl r11, r4, 3 ; 2 * instruction size = 8 - add r11, r11, game_loop_ghost_movement_jump_table_start - jmp r11 - - game_loop_ghost_movement_jump_table_start: - ; direction_up - sub r6, r6, 1 - jmp game_loop_ghost_movement_make_move - ; direction_right - add r5, r5, 1 - jmp game_loop_ghost_movement_make_move - ; direction_down - add r6, r6, 1 - jmp game_loop_ghost_movement_make_move - ; direction_left - sub r5, r5, 1 - ;jmp game_loop_ghost_movement_make_move - ; direction_still (should never happen) - - game_loop_ghost_movement_make_move: - add r7, r2, r5 - add r8, r3, r6 - add r1, zr, ptr.state_ghost - store_8 [r1], r7 - add r1, r1, 1 - store_8 [r1], r8 - - ; get tile at previous position - mov r1, r2 - mov r2, r3 - call game.get_tile_address_from_map_coord - load_8 r13, [r13] - - ; draw tile at previous position - cmp r13, map.tiles.coin - je game_loop_ghost_movement_old_tile_coin - ; old tile empty - mov r3, sprites.empty - jmp game_loop_ghost_movement_draw_old_tile - game_loop_ghost_movement_old_tile_coin: - mov r3, sprites.coin - - game_loop_ghost_movement_draw_old_tile: - call drawing.draw_sprite_at_map_coord - - ; draw ghost on new position - mov r1, r7 - mov r2, r8 - mov r3, sprites.ghost - call drawing.draw_sprite_at_map_coord - - ; - ; Check ahead for direction change - ; - add r1, r7, r5 - add r2, r8, r6 - call game.get_tile_address_from_map_coord - - load_8 r10, [r13] - cmp r10, map.tiles.wall - jne game_loop_ghost_movement_end - ; increment direction - add r1, zr, ptr.state_ghost - add r1, r1, 2 - load_8 r4, [r1] - add r4, r4, 1 - and r4, r4, 0b11 ; modulo 3 - store_8 [r1], r4 - - game_loop_ghost_movement_end: + include sections/timer_ghost jmp game_loop +include lib/u64 +include lib/game 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 diff --git a/src/reserved_space.asm b/src/reserved_space.asm index cda0e83..17b7397 100644 --- a/src/reserved_space.asm +++ b/src/reserved_space.asm @@ -2,8 +2,14 @@ pub ptr.textures_addresses: U32 0 ; empty U32 0 ; placeholder -U32 0 ; robot -U32 0 ; ghost +U32 0 ; robot up +U32 0 ; robot right +U32 0 ; robot down +U32 0 ; robot left +U32 0 ; ghost up +U32 0 ; ghost right +U32 0 ; ghost down +U32 0 ; ghost left U32 0 ; coin ; 0.5 second = 500_000_000 nano second = 0x0000_0000_1DCD_6500 @@ -14,8 +20,11 @@ pub ptr.tick_duration_low: U32 0x1DCD_6500 ; pub ptr.tick_duration_high: U32 0x0000_0000 ; pub ptr.tick_duration_low: U32 0x3B9A_CA00 -pub ptr.next_tick_high: U32 0 -pub ptr.next_tick_low: U32 0 +pub ptr.timer_robot_next_tick_high: U32 0x0000_0000 +pub ptr.timer_robot_next_tick_low: U32 0x1DCD_6500 + +pub ptr.timer_ghost_next_tick_high: U32 0 +pub ptr.timer_ghost_next_tick_low: U32 0x0EE6_B280 pub ptr.state_robot: U8 0 ; x coord 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..b0ddf5a --- /dev/null +++ b/src/sections/init_ghost_state.asm @@ -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 \ 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..e39177e --- /dev/null +++ b/src/sections/init_robot_state.asm @@ -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 \ 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..324c018 --- /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, screen +screen r1, r2 + +mov r1, screen.resolution_index +screen r1, screen.resolution_value \ No newline at end of file diff --git a/src/sections/init_sprite_lookup_table.asm b/src/sections/init_sprite_lookup_table.asm new file mode 100644 index 0000000..694730e --- /dev/null +++ b/src/sections/init_sprite_lookup_table.asm @@ -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 diff --git a/src/sections/init_timers.asm b/src/sections/init_timers.asm new file mode 100644 index 0000000..0835cc5 --- /dev/null +++ b/src/sections/init_timers.asm @@ -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 \ No newline at end of file diff --git a/src/sections/timer_ghost.asm b/src/sections/timer_ghost.asm new file mode 100644 index 0000000..2630cc3 --- /dev/null +++ b/src/sections/timer_ghost.asm @@ -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: \ No newline at end of file diff --git a/src/sections/timer_robot.asm b/src/sections/timer_robot.asm new file mode 100644 index 0000000..34d9920 --- /dev/null +++ b/src/sections/timer_robot.asm @@ -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: \ No newline at end of file