UNPKG

textadv

Version:

Text Adventures generator from Markdown files

224 lines 8.19 kB
import { Type } from './types.js'; import { generateInputVariations, hyphenate, removeDiacritics } from './utils.js'; class BasicWriter { constructor() { this.lines = []; } escape(s) { return '"' + removeDiacritics(s). replace(/"/g, '"CHR$(34)"').replace(/\n/g, '"CHR$(10)CHR$(13)"') + '"'; } print(s) { const endsWithEnter = s.endsWith("\n"); const paragraphs = s.split(/\n/g); for (let i = 0; i < paragraphs.length; i++) { if (i > 0) { this.write('?'); } const paragraph = paragraphs[i]; const hyphenatedLines = hyphenate(paragraph); for (const line of hyphenatedLines) { if (line.endsWith("\n")) { this.write(`?${this.escape(line.trimEnd())}`); } else { this.write(`?${this.escape(line)};`); } } if (paragraphs.length === 1 && endsWithEnter) { this.write('?'); } // const hyphenated = hyphenatedLines.join("") // for (let i=0; i<hyphenated.length; i++) { // const char = hyphenated.charAt(i) // if (char === "\n") { // this.write(`?${this.escape(current)}`) // current = "" // continue // } // current += char // if (current.length > 100) { // this.write(`?${this.escape(current)};`) // current = "" // } // } // if (current.length > 0) { // this.write(`?${this.escape(current)}`) // } } } printLn(s) { return this.print(`${s}\n`); } write(s) { this.lines.push(s); } getLineRef() { return this.lines.length + 1; } setLine(ref, s) { this.lines[ref - 1] = s; } replaceLineToken(ref, token, value) { this.lines[ref - 1] = this.lines[ref - 1].replace(token, String(value)); } render() { const output = []; for (let i = 0; i < this.lines.length; i++) { output.push(`${i + 1} ${this.lines[i]}`); } return output.join("\r\n"); } } class Flags { constructor() { this.flags = []; this.dict = {}; } get(name) { if (this.dict[name] === undefined) { this.dict[name] = this.flags.length; this.flags.push(name); } return `F(${this.dict[name]})`; } getCount() { return this.flags.length; } } export function generateBasic(project) { const flags = new Flags(); const basic = new BasicWriter(); if (typeof (project.meta) === 'object') { for (const key in project.meta) { basic.write(`' ${key}: ${project.meta[key]}`); } } basic.write("DEFINTA-Z:COLOR15,1,1:SCREEN0,,0:KEYOFF:WIDTH40"); const varSetupRef = basic.getLineRef(); basic.write("DIM ..."); basic.write(`RM=${project.initialRoomIndex}`); basic.printLn(project.name); basic.printLn(""); basic.printLn(project.intro.join("\n\n")); basic.printLn(""); basic.printLn(""); basic.write("LINE INPUT\"[Tecle ENTER]\";I$:CLS"); const roomsOnGosubRef = basic.getLineRef(); basic.write("?:ON RM+1 GOSUB ..."); const parseInputGosubRef = basic.getLineRef(); basic.write("GOSUB ..."); const roomCmdOnGosubRef = basic.getLineRef(); basic.write("ON RM+1 GOSUB ..."); basic.write(`IF RZ<>RM THEN RZ=RM:GOTO ${roomsOnGosubRef}`); basic.write(`GOTO ${parseInputGosubRef}`); // input parser basic.setLine(parseInputGosubRef, `GOSUB ${basic.getLineRef()}`); basic.write("PRINT:INPUT I$:PRINT"); basic.write("RETURN"); // project codes basic.write("REM project codes"); const projectCodesRef = basic.getLineRef(); generateCodes(project.onInput, basic, project, roomsOnGosubRef, flags); basic.write("RETURN"); // rooms intros const introLines = []; for (const node of project.children) { introLines.push(basic.getLineRef()); if (node.intro.length === 0) { basic.write("RETURN"); continue; } if (node.type === Type.location) { basic.printLn(`[${node.name}]`); basic.printLn(""); } basic.printLn(node.intro.join("\n\n")); basic.write("RETURN"); } basic.setLine(roomsOnGosubRef, "?:ON RM+1 GOSUB " + introLines.join(",")); // rooms commands const roomsCommandLines = []; for (const room of project.children) { if (room.type !== Type.location) continue; roomsCommandLines.push(basic.getLineRef()); basic.write(`REM room ${room.id} codes`); generateCodes(room.onInput, basic, project, roomsOnGosubRef, flags); basic.write(`GOTO ${projectCodesRef}`); } basic.setLine(roomCmdOnGosubRef, "ON RM+1 GOSUB " + roomsCommandLines.join(",")); basic.setLine(varSetupRef, `DIM F(${flags.getCount()})`); return basic.render(); } function generateCodes(codes, basic, project, checkRoomLine, flags) { var _a; for (const code of codes) { const lineRef = basic.getLineRef(); const linesWithSkipping = []; basic.write("IF ..."); // ops for (const op of code.ops) { switch (op.cmd) { case "print": basic.printLn(String(op.params[0])); break; case "check-room": basic.write(`RETURN ${checkRoomLine}`); break; case "goto": const roomIndex = (_a = project.getChildById(String(op.params[0]))) === null || _a === void 0 ? void 0 : _a.index; if (roomIndex === undefined) { throw new Error(`could not find room "${op.params[0]}"`); } basic.write(`RM=${roomIndex}`); break; case "set": const flSet = flags.get(op.params[0]); basic.write(`${flSet}=1`); break; case "clear": const flClear = flags.get(op.params[0]); basic.write(`${flClear}=0`); break; case "zero": const flZero = flags.get(op.params[0]); linesWithSkipping.push(basic.getLineRef()); basic.write(`IF ${flZero}<>0 THEN @`); break; case "notzero": const flNotZero = flags.get(op.params[0]); linesWithSkipping.push(basic.getLineRef()); basic.write(`IF ${flNotZero}=0 THEN @`); break; case "continue": linesWithSkipping.push(basic.getLineRef()); basic.write(`GOTO @`); break; default: basic.write(`REM TODO: ${JSON.stringify(op)}`); } } // end of ops basic.write("RETURN"); // TODO: allow skip when declared for (const lineToReplace of linesWithSkipping) { basic.replaceLineToken(lineToReplace, '@', basic.getLineRef()); } if (code.on.endsWith('*')) { if (code.on === '*') { // default fallback for any input basic.setLine(lineRef, 'REM fallback'); } else { const codeOnStart = code.on.replace('*', '').trim().split(/ +/g).join(' '); basic.setLine(lineRef, `IF LEFT$(I$, ${codeOnStart.length})<>${basic.escape(codeOnStart)} THEN ${basic.getLineRef()}`); } } else { const inputs = generateInputVariations(code.on).map((input) => `I$<>${basic.escape(input)}`); basic.setLine(lineRef, `IF ${inputs.join(" AND ")} THEN ${basic.getLineRef()}`); } } } //# sourceMappingURL=gen-basic.js.map