#!/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"; 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 mapWidth = 15; const spriteWidth = 17; const mapMatchingStrings = { 0x00_00_00: 'U8 0', // Empty 0xFF_00_00: 'U8 1', // Wall 0xFF_FF_00: 'U8 2', // Coin }; const spritesMatchingStrings = { 0x00_00_00: 'U8 0b000_000_00', 0x00_00_FF: 'U8 0b000_000_11', 0x00_FF_00: 'U8 0b000_111_00', 0x77_77_77: 'U8 0b010_011_10', 0xFF_00_00: 'U8 0b111_000_00', 0xFF_00_FF: 'U8 0b111_000_11', 0xFF_80_00: 'U8 0b111_100_00', 0xFF_FF_00: 'U8 0b111_110_00', 0xFF_FF_FF: 'U8 0b111_111_11', }; 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', }; /** * START OF BUILD */ for (const path of asmFilePaths) { const file = await Deno.open(path); const readable = file.readable.pipeThrough(new TextDecoderStream()) .pipeThrough(new TextLineStream()); console.log(` ;/******************************* ;* ${path} ;*******************************/ `); for await (const line of readable) { if (!line.startsWith("include ")) { console.log(line); } } } const asmRawRepresentations = []; const mapBytes = await getDataFromPPM(mapPath); asmRawRepresentations.push(byteToAsmRawData('map', mapBytes, mapMatchingStrings, mapWidth)); for (const [label, path] of Object.entries(spritesPath)) { const bytes = await getDataFromPPM(path); asmRawRepresentations.push(byteToAsmRawData(label, bytes, spritesMatchingStrings, spriteWidth)); } console.log(asmRawRepresentations.join("\n\n")); console.log(` ; ; RESERVED RAM SPACE ; `); console.log(await Deno.readTextFile(reservedSpacePath)) /** * Return an ASM representation of the given data * * @param label The label to write before the first raw data * @param data An array of bytes to interprete as 24 bits values * @param matchingStrings An associative array with a string representation for each possible 24 bit data value * @param lineLength Optional: Provide a way to wrap the result in multiple lines * * @returns The ASM representation of that raw data, pre- and post-fixed by the given label */ function byteToAsmRawData(label: string, data: Uint8Array, matchingStrings: Record, lineLength = 0): string { lineLength ??= Infinity; const stringValues = []; for (let i = 0; i < data.length - 2; i += 3) { const pixelValue = (data[i] << 16) + (data[i+1] << 8) + (data[i+2]) stringValues.push(matchingStrings[pixelValue]); } const lines = []; if (lineLength === 0) { lines.push(stringValues.join("\t")); } else { for (let i = 0; i < stringValues.length; i += lineLength) { lines.push(stringValues.slice(i, i+lineLength).join("\t")); } } return `${label}: ${lines.join("\n")} ${label}_end:` } /** * 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); }