Skip to content

Commit

Permalink
docs: add example "mandelbrot-animated"
Browse files Browse the repository at this point in the history
  • Loading branch information
TimPietrusky committed Apr 5, 2023
1 parent 31aae4c commit fc5b7a1
Show file tree
Hide file tree
Showing 11 changed files with 1,496 additions and 0 deletions.
158 changes: 158 additions & 0 deletions examples/mandelbrot-animated/base.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import process from "node:process";
import fs from "node:fs/promises";
import path from "node:path";
import { config } from "dotenv";
import meow from "meow";
import { Configuration, OpenAIApi } from "openai";
import ora from "ora";
import pkg from "./package.json" assert { type: "json" };

config();

const { flags } = meow("", {
importMeta: import.meta,
flags: {
goal: {
type: "string",
alias: "G",
default: "extend the code",
},
generations: {
type: "number",
alias: "g",
default: 1,
},
persona: {
type: "string",
alias: "p",
default: "expert node.js developer, creative, interaction expert, cli expert",
},
temperature: {
type: "number",
alias: "t",
default: 0.2,
},
},
});

const spinner = ora("Evolving");

const configuration = new Configuration({
apiKey: process.env.OPENAI_SECRET,
});

export const openai = new OpenAIApi(configuration);

const instructions = `
Extend the code but the RULES can NEVER be changed and must be respected AT ALL TIMES.
The code should INCREASE in logic.
The GOAL must be completed.
GOAL: ${flags.goal}
ALLOWED NPM PACKAGES: ${Object.entries(pkg.dependencies)
.map(([name, version]) => `${name}@${version}`)
.join(", ")}
RULES:
- Pay special attention TO ALL UPPERCASE words
- KEEP the existing code, only ADD new code or improve the code you added since "Generation 0"
- increment the generation constant ONCE per generation
- Keep track of changes, EXTEND the CHANGELOG
- Use es module syntax (with imports)
- NEVER use "require"
- NEVER explain anything
- VERY IMPORTANT: output the javaScript only (this is the most important rule, the entire answer has to be valid javascript)
`;

const persona = `programmer with the following characteristics: ${flags.persona}`;

export const generations = flags.generations;
const maxSpawns = 3;
let spawns = maxSpawns;

export async function evolve(generation) {
if (spawns <= 0) {
spinner.fail("Maximum retries reached");
return;
}
const nextGeneration = generation + 1;

try {
const filename = buildFilename(generation);
const code = await fs.readFile(filename, "utf-8");
spinner.start(`Generation ${generation} | ${spawns} spawns left`);

const completion = await openai.createChatCompletion({
// model: "gpt-4",
model: "gpt-3.5-turbo",
messages: [
{
role: "system",
content: `You are a: ${persona} extend or fix my code based on these instructions: ${instructions}`,
},
{
role: "user",
content: code,
},
],
max_tokens: 2048,
temperature: flags.temperature,
});
spinner.stop();
const { content } = completion.data.choices[0].message;
const nextFilename = buildFilename(nextGeneration);
await fs.writeFile(nextFilename, content);
spinner.succeed(`Generation ${generation} | ${spawns} spawns left`);
await import(`./${nextFilename}`);
} catch (error) {
spawns--;
spinner.fail(`Generation ${generation} | ${spawns} spawns left`);

await handleError(error, generation);
}
}

export function pad(n) {
return n.toString().padStart(3, "0");
}

export function buildFilename(currentGeneration) {
return path.join(".", `generation-${pad(currentGeneration)}.js`);
}

export async function handleError(error, generation) {
const message = (
error.response?.data?.error.message ??
error.message ??
"unknown error"
).trim();

const code = error.response?.status ?? error.code ?? "UNKOWN_CODE";

if (code === "ERR_MODULE_NOT_FOUND") {
console.error(message);
return;
}

// The AI might increment too often
if (message.startsWith("ENOENT") && generation > 1) {
await evolve(generation - 1);
}

// Errors in the API
if (error.response && code !== 200) {
console.error(`${code}: ${message}`);

if (code === 401) {
console.error(
"Please make sure to use a valid API key and that you have set OPENAI_SECRET in .env"
);
}

return;
}

console.error(error);
throw error;
}
20 changes: 20 additions & 0 deletions examples/mandelbrot-animated/generation-000.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* CHANGELOG:
* Generation 0: implement base script
*/
import readline from "node:readline";
import logUpdate from "log-update";
import fs from "node:fs/promises";
import { generations, evolve } from "./base.js";

const generation = 0;

async function run() {
// App logic
}

if (generation < generations) {
await evolve(generation);
} else {
await run();
}
67 changes: 67 additions & 0 deletions examples/mandelbrot-animated/generation-001.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* CHANGELOG:
* Generation 0: implement base script
* Generation 1: implement mandelbrot algorithm and ascii console output
*/

import readline from "node:readline";
import logUpdate from "log-update";
import fs from "node:fs/promises";
import { generations, evolve } from "./base.js";

const generation = 1;

async function run() {
const width = process.stdout.columns;
const height = process.stdout.rows - 2;

const xMin = -2;
const xMax = 1;
const yMin = -1;
const yMax = 1;

const maxIterations = 100;

let output = "";

for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const a = xMin + (xMax - xMin) * (x / width);
const b = yMin + (yMax - yMin) * (y / height);

let real = a;
let imaginary = b;

let iterations = 0;

while (iterations < maxIterations) {
const tempReal = real * real - imaginary * imaginary + a;
const tempImaginary = 2 * real * imaginary + b;

real = tempReal;
imaginary = tempImaginary;

if (real * real + imaginary * imaginary > 4) {
break;
}

iterations++;
}

const char =
iterations === maxIterations ? " " : String.fromCharCode(97 + (iterations % 26));

output += char;
}

output += "\n";
}

logUpdate(output);
}

if (generation < generations) {
await evolve(generation);
} else {
await run();
}
83 changes: 83 additions & 0 deletions examples/mandelbrot-animated/generation-002.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* CHANGELOG:
* Generation 0: implement base script
* Generation 1: implement mandelbrot algorithm and ascii console output
* Generation 2: add ability to save output to file
*/

import readline from "node:readline";
import logUpdate from "log-update";
import fs from "node:fs/promises";
import { generations, evolve } from "./base.js";

const generation = 2;

async function run() {
const width = process.stdout.columns;
const height = process.stdout.rows - 2;

const xMin = -2;
const xMax = 1;
const yMin = -1;
const yMax = 1;

const maxIterations = 100;

let output = "";

for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const a = xMin + (xMax - xMin) * (x / width);
const b = yMin + (yMax - yMin) * (y / height);

let real = a;
let imaginary = b;

let iterations = 0;

while (iterations < maxIterations) {
const tempReal = real * real - imaginary * imaginary + a;
const tempImaginary = 2 * real * imaginary + b;

real = tempReal;
imaginary = tempImaginary;

if (real * real + imaginary * imaginary > 4) {
break;
}

iterations++;
}

const char =
iterations === maxIterations ? " " : String.fromCharCode(97 + (iterations % 26));

output += char;
}

output += "\n";
}

logUpdate(output);

const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});

rl.question("Do you want to save the output to a file? (y/n) ", async answer => {
if (answer === "y") {
const filename = `mandelbrot_${Date.now()}.txt`;
await fs.writeFile(filename, output);
console.log(`Output saved to ${filename}`);
}

rl.close();
});
}

if (generation < generations) {
await evolve(generation);
} else {
await run();
}
Loading

0 comments on commit fc5b7a1

Please sign in to comment.