Solve View

Brainfuck Printer

For each input string of printable ASCII (0x20-0x7e), output a brainfuck program which produces that string. At the end of your program, append a # followed by the brainfuck program that your code outputs when given the input string Hello, World!. For example, if your program is

abc

and it outputs

123

when given Hello, World! as an input, then your final submission should be

abc#123

(You don’t need a trailing newline on your submission, even if the output of your program contains one)

Judge

(async function*(context: Context): Challenge {
	// Split on last #
	const [program, helloWorldCode] = context.code.split(/#(?=[^#]*$)/);

	yield (await context.runCode(program, "Hello, World!")).assertEquals(helloWorldCode).setName("\"Hello, World!\" output appended");

	const evalBF = (bfCode:string):string => {
		const tape = new Array(0x8000).fill(0);
		let ptr = 0;
		let output = ``;
		for(let codeIdx = 0; codeIdx < bfCode.length; codeIdx++)
		{
			let command = bfCode.charAt(codeIdx);
			let jumpDirection = 0;
			if(command==`+`) tape[ptr]++;
			if(command==`-`) tape[ptr]--;
			if(command==`>`) ptr = ptr+1 & 0x7fff;
			if(command==`<`) ptr = ptr-1 & 0x7fff;
			if(command==`.`) output += String.fromCharCode(tape[ptr] & 0xff);
			if(command==`[` && !(tape[ptr]&0xff)) jumpDirection = 1;
			if(command==`]` &&  (tape[ptr]&0xff)) jumpDirection = -1;
			for(let balance = jumpDirection;balance;)
			{
			command = bfCode.charAt(codeIdx += jumpDirection);
			balance +=
				command==`[` ? 1 :
				command==`]` ? -1 : 0;
			}
		}
		return output;
	}

	// Runner adapted from runTestCases
	async function*runBFCases<T = string>(
    testCases: [string, T][],
    overrideOptions: Partial<TestCasesOptions<T>> = {}
	): AsyncIterableIterator<TestCase> {
    const options: TestCasesOptions<T> = {
      inputSeperator: "\n",
      outputSeperator: "\n",
      numberOfRuns: 2,
      shuffle: true,
      compareFunction: (a, b) => eqIgnoreTrailingWhitespace(a, "" + b),
      ...overrideOptions,
    };
	function shuffleAndDeal<T>(
		testCases: T[],
		options: { shuffle: boolean; numberOfRuns: number }
	): T[][] {
		if (options.shuffle) {
			shuffle(testCases);
		}

		// Ensure the runs are uneven
		// This is mostly to prevent people from hardcoding the length of the input
		const cardsPerHand = testCases.length / (options.numberOfRuns + 1);
		const hands = [testCases.slice(0, Math.ceil(cardsPerHand * 2))];
		for (let i = cardsPerHand * 2; i < testCases.length; i += cardsPerHand) {
			const hand = testCases.slice(Math.ceil(i), Math.ceil(i + cardsPerHand) + 1);
			if (hand.length != 0) {
				hands.push(hand);
			}
		}

		return hands;
	};
    const hands = shuffleAndDeal(testCases, options);


    // TODO: When running code becomes thread safe, this should run in paralel
    for (const hand of hands) {
      const input = hand.map((i) => i[0]).join(options.inputSeperator);
      yield (await context.runCode(program, input)).assert((d) => {
		const evaluated = d.trimEnd().split(options.outputSeperator).map(evalBF);
        const cases = [
          ...zipLongest(
            d.trimEnd().split(options.outputSeperator).map(evalBF),
            hand.map((i) => i[1])
          ),
        ].map(([a, b]) => ({
          output: a,
          expected: b,
          equal: a != undefined && options.compareFunction(a, b),
        }));

        return new TestCase(
          undefined,
          cases.every((i) => i.equal) ? "Pass" : "Fail",
          {
            Diff: {
              input: input,
              output: evaluated.join(options.outputSeperator),
              expected: cases
                .map((i) => (i.equal ? i.output : i.expected))
                .join(options.outputSeperator),
            },
          }
        );
      });
    }
  }

	  yield *runBFCases([
		"Hello, World!", 
		"Byte Heist is a site where you can test your coding skills by solving challenges in as few bytes as possible.",
		"The 8 valid brainfuck commands are: `+`,`-`,`>`,`<`,`[`,`]`,`.`, and `,`.",
		"a",
		" !\"\#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~",
		"[>q1h`2@Xv 95Be:-Fsj73py(P!T Bayk{{Wpy|L|$ 7*kLwE|",
		"eL34NfeOL454KdeJ44JOdefePK55gQ67ShfTL787KegJ77JTeghfUK88iV9:XjgYL:;:KfiJ::JYfij",
		"J,xU #={lM#+[`iwt9cRxdz[ACiNRok~H\\6I[|72KHo]k IN9jsB:WtoUq3%c|r>:QAb\":>-hYH\" k",
		"i0sAVNQ Ik!EV5/E&^Ym_DJfyD<$/JkG,-kN,AQ=CT[K+L\"n^^z=",
		"&^5B5BF&6b%^7B&*N7H76n876 n907HM*M9[.)>][)>.0O[{>]>{I9.0,u-m8"
	].map(s=>[s,s] as [string,string]));

	// Finally, the challenge is passed if no test cases failed
	return context.noFailures();
})

Example Code

input = require("fs").readFileSync(0) + "";
for (const line of input.split("\n")) {
  console.log(
    [...line].map(char => "[-]" + "+".repeat(char.charCodeAt(0)) + ".").join("")
  );
}#[-]++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.[-]+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.[-]++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.[-]++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.[-]+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.[-]++++++++++++++++++++++++++++++++++++++++++++.[-]++++++++++++++++++++++++++++++++.[-]+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.[-]+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.[-]++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.[-]++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.[-]++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.[-]+++++++++++++++++++++++++++++++++.

Comments