diff --git a/dist/main.js b/dist/main.js index cc57f95..b333e85 100644 --- a/dist/main.js +++ b/dist/main.js @@ -1,48 +1,16 @@ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; console.log("Starting Terrarium!"); -function reproduce(parentA, parentB, mutationChance = 0.1) { - let offspring = parentA.split(""); - let chars = "abcdefghijklmnopqrstuvxyz "; - for (let i = 0; i < offspring.length; i++) { - if (Math.random() > 0.5) { - offspring.splice(i, 1, parentB[i]); - } - if (Math.random() < mutationChance) { - let mutatedGene = chars.charAt(Math.floor(Math.random() * chars.length)); - offspring.splice(i, 1, mutatedGene); - } - } - return offspring.join(""); -} -function calculateFitness(str, targetString) { - let fitness = 0; - for (let i = 0; i < str.length; i++) { - if (str[i] == targetString[i]) { - fitness++; - } - } - return fitness; -} function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } -// (async () => { -// while (!population.includes(targetString)) { -// await sleep(25); -// generation++; -// // update interface -// updateInterface(); -// // find parents -// const parentA = population.sort((a, b) => calculateFitness(a, targetString) > calculateFitness(b, targetString) ? -1 : 1)[0]; -// const parentB = population.sort((a, b) => calculateFitness(a, targetString) > calculateFitness(b, targetString) ? -1 : 1)[1] -// // produce offspring based on fitness -// population = []; -// for (let i = 0; i < populationSize; i++) { -// population.push(reproduce(parentA, parentB)); -// } -// } -// updateInterface(); -// console.log("target matched!"); -// })(); class WordGeneticAlgorithmModel { constructor(populationSize = 50, targetString = "hello", generation = 1) { // instance variables @@ -52,17 +20,64 @@ class WordGeneticAlgorithmModel { this.targetString = ""; this.generation = 1; this.averageFitness = 0; + this.generationComplete = false; + this.fitnessRecord = []; // initialize standard values this.population = []; this.populationSize = populationSize; this.targetStringLength = targetString.length; this.targetString = targetString; this.generation = generation; + this.generationComplete = false; // initialize more complex values for (let i = 0; i < this.populationSize; i++) { this.population.push(WordGeneticAlgorithmModel.randomString(this.targetStringLength)); } } + step() { + this.generationComplete = true; + if (this.generationComplete) { + // create a new generation + this.progressToNextGeneration(); + } + } + progressToNextGeneration() { + this.generation++; + // metrics + this.averageFitness = this.population.map(str => WordGeneticAlgorithmModel.calculateFitness(str, this.targetString)).reduce((a, b) => a + b, 0) / this.populationSize; + this.fitnessRecord.push(this.averageFitness); + // find parents + const parentA = this.population.sort((a, b) => WordGeneticAlgorithmModel.calculateFitness(a, this.targetString) > WordGeneticAlgorithmModel.calculateFitness(b, this.targetString) ? -1 : 1)[0]; + const parentB = this.population.sort((a, b) => WordGeneticAlgorithmModel.calculateFitness(a, this.targetString) > WordGeneticAlgorithmModel.calculateFitness(b, this.targetString) ? -1 : 1)[1]; + // generate offspring + this.population = []; + for (let i = 0; i < this.populationSize; i++) { + this.population.push(WordGeneticAlgorithmModel.produceOffspring(parentA, parentB)); + } + } + static produceOffspring(parentA, parentB, mutationChance = 0.1) { + let offspring = parentA.split(""); + for (let i = 0; i < offspring.length; i++) { + if (Math.random() > 0.5) { + offspring.splice(i, 1, parentB[i]); + } + if (Math.random() < mutationChance) { + let mutatedGene = WordGeneticAlgorithmModel.possibleStringChars.charAt(Math.floor(Math.random() * WordGeneticAlgorithmModel.possibleStringChars.length)); + offspring.splice(i, 1, mutatedGene); + } + } + return offspring.join(""); + } + static calculateFitness(sampleString, targetString) { + let correctLetters = 0; + for (let i = 0; i < sampleString.length; i++) { + if (sampleString[i] == targetString[i]) { + correctLetters++; + } + } + let fitness = correctLetters / targetString.length; + return fitness; + } static randomString(targetLength) { let result = ""; for (let i = 0; i < targetLength; i++) { @@ -75,7 +90,7 @@ class WordGeneticAlgorithmModel { } } // static variables -WordGeneticAlgorithmModel.possibleStringChars = "abcdefghijklmnopqrstuvxyz "; +WordGeneticAlgorithmModel.possibleStringChars = "abcdefghijklmnopqrstuvwxyz "; class WordGeneticAlgorithmView { constructor(displayElement) { this.displayElement = displayElement; @@ -117,7 +132,7 @@ class WordGeneticAlgorithmView { metadataContainer.appendChild(generationParagraph); // display generation's average fitness let averageFitnessParagraph = document.createElement("p"); - averageFitnessParagraph.innerText = `Average fitness of this generation is ${model.averageFitness}.`; + averageFitnessParagraph.innerText = `Average fitness of this generation is ${model.averageFitness.toFixed(2)}.`; metadataContainer.appendChild(averageFitnessParagraph); } } @@ -126,8 +141,56 @@ class WordGeneticAlgorithmController { console.log("Created a new controller!"); } } -let model = new WordGeneticAlgorithmModel(20, "wobble", 1); +let model = new WordGeneticAlgorithmModel(20, "geronimo geoff", 1); let view = new WordGeneticAlgorithmView(document.querySelector("#view")); -console.log(model); -console.log(view); -view.update(model); +// chart for metrics +const ctx = document.getElementById("myChart"); +let fitnessGramPacerTest = new Chart(ctx, { + type: "line", + data: { + labels: Array.from({ length: model.population.length }, (_, i) => i + 1), + datasets: [{ + label: "Average Fitness", + data: [0, 1, 2, 3], + borderWidth: 1 + }] + }, + options: { + animation: false, + maintainAspectRatio: true, + scales: { + y: { + beginAtZero: true, + min: 0, + max: 1 + }, + x: { + type: "linear", + position: "bottom", + ticks: { + autoSkip: true, + maxTicksLimit: 500 + } + } + }, + responsive: true + } +}); +function gameLoop() { + return __awaiter(this, void 0, void 0, function* () { + model.step(); + // visuals + view.update(model); + fitnessGramPacerTest.data.labels = Array.from({ length: model.population.length }, (_, i) => i + 1); + fitnessGramPacerTest.data.datasets[0].data = model.fitnessRecord; + fitnessGramPacerTest.update(); + yield sleep(10); + if (!model.population.includes(model.targetString)) { + requestAnimationFrame(gameLoop); + } + else { + console.info("we're done!"); + } + }); +} +requestAnimationFrame(gameLoop); diff --git a/index.html b/index.html index 42baca1..437e812 100644 --- a/index.html +++ b/index.html @@ -3,9 +3,14 @@ Terrarium +
+ +
+ +
diff --git a/src/main.ts b/src/main.ts index 0fdeb7b..cf83755 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,70 +1,24 @@ console.log("Starting Terrarium!"); -function reproduce(parentA: string, parentB: string, mutationChance: number = 0.1): string { - let offspring: string[] = parentA.split(""); - let chars = "abcdefghijklmnopqrstuvxyz "; - - for (let i = 0; i < offspring.length; i++) { - if (Math.random() > 0.5) { - offspring.splice(i, 1, parentB[i]); - } - if (Math.random() < mutationChance) { - let mutatedGene: string = chars.charAt(Math.floor(Math.random() * chars.length)); - offspring.splice(i, 1, mutatedGene); - } - } - - return offspring.join(""); -} - -function calculateFitness(str: string, targetString: string): number { - let fitness: number = 0; - - for (let i = 0; i < str.length; i++) { - if (str[i] == targetString[i]) { - fitness++; - } - } - - return fitness; -} +declare var Chart: any; function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } -// (async () => { -// while (!population.includes(targetString)) { -// await sleep(25); -// generation++; -// // update interface -// updateInterface(); - -// // find parents -// const parentA = population.sort((a, b) => calculateFitness(a, targetString) > calculateFitness(b, targetString) ? -1 : 1)[0]; -// const parentB = population.sort((a, b) => calculateFitness(a, targetString) > calculateFitness(b, targetString) ? -1 : 1)[1] - -// // produce offspring based on fitness -// population = []; -// for (let i = 0; i < populationSize; i++) { -// population.push(reproduce(parentA, parentB)); -// } -// } -// updateInterface(); -// console.log("target matched!"); -// })(); - class WordGeneticAlgorithmModel { // instance variables - population: string[] = [] - populationSize: number = 50 - targetStringLength: number = 40 - targetString: string = "" - generation: number = 1 - averageFitness: number = 0 + population: string[] = []; + populationSize: number = 50; + targetStringLength: number = 40; + targetString: string = ""; + generation: number = 1; + averageFitness: number = 0; + generationComplete: boolean = false; + fitnessRecord: number[] = []; // static variables - static possibleStringChars: string = "abcdefghijklmnopqrstuvxyz " + static possibleStringChars: string = "abcdefghijklmnopqrstuvwxyz "; constructor(populationSize: number = 50, targetString: string = "hello", generation: number = 1) { // initialize standard values @@ -73,6 +27,7 @@ class WordGeneticAlgorithmModel { this.targetStringLength = targetString.length; this.targetString = targetString; this.generation = generation; + this.generationComplete = false; // initialize more complex values for (let i = 0; i < this.populationSize; i++) { @@ -80,6 +35,64 @@ class WordGeneticAlgorithmModel { } } + step(): void { + this.generationComplete = true; + + if (this.generationComplete) { + // create a new generation + this.progressToNextGeneration(); + } + } + + progressToNextGeneration():void { + this.generation++; + + // metrics + this.averageFitness = this.population.map(str => WordGeneticAlgorithmModel.calculateFitness(str, this.targetString)).reduce((a, b) => a + b, 0) / this.populationSize; + this.fitnessRecord.push(this.averageFitness); + + // find parents + const parentA = this.population.sort((a, b) => WordGeneticAlgorithmModel.calculateFitness(a, this.targetString) > WordGeneticAlgorithmModel.calculateFitness(b, this.targetString) ? -1 : 1)[0]; + const parentB = this.population.sort((a, b) => WordGeneticAlgorithmModel.calculateFitness(a, this.targetString) > WordGeneticAlgorithmModel.calculateFitness(b, this.targetString) ? -1 : 1)[1]; + + // generate offspring + this.population = []; + for (let i = 0; i < this.populationSize; i++) { + this.population.push(WordGeneticAlgorithmModel.produceOffspring(parentA, parentB)); + } + } + + static produceOffspring(parentA: string, parentB: string, mutationChance: number = 0.1): string { + let offspring: string[] = parentA.split(""); + + for (let i = 0; i < offspring.length; i++) { + if (Math.random() > 0.5) { + offspring.splice(i, 1, parentB[i]); + } + + if (Math.random() < mutationChance) { + let mutatedGene: string = WordGeneticAlgorithmModel.possibleStringChars.charAt(Math.floor(Math.random() * WordGeneticAlgorithmModel.possibleStringChars.length)); + offspring.splice(i, 1, mutatedGene); + } + } + + return offspring.join(""); + } + + static calculateFitness(sampleString: string, targetString: string): number { + let correctLetters: number = 0; + + for (let i = 0; i < sampleString.length; i++) { + if (sampleString[i] == targetString[i]) { + correctLetters++; + } + } + + let fitness: number = correctLetters / targetString.length; + + return fitness; + } + static randomString(targetLength: number): string { let result = ""; for (let i = 0; i < targetLength; i++) { @@ -141,7 +154,7 @@ class WordGeneticAlgorithmView { // display generation's average fitness let averageFitnessParagraph: HTMLParagraphElement = document.createElement("p"); - averageFitnessParagraph.innerText = `Average fitness of this generation is ${model.averageFitness}.`; + averageFitnessParagraph.innerText = `Average fitness of this generation is ${model.averageFitness.toFixed(2)}.`; metadataContainer.appendChild(averageFitnessParagraph); } } @@ -152,10 +165,59 @@ class WordGeneticAlgorithmController { } } -let model = new WordGeneticAlgorithmModel(20, "wobble", 1); +let model = new WordGeneticAlgorithmModel(20, "geronimo geoff", 1); let view = new WordGeneticAlgorithmView(document.querySelector("#view")); -console.log(model); -console.log(view); +// chart for metrics +const ctx = document.getElementById("myChart"); +let fitnessGramPacerTest = new Chart(ctx, { + type: "line", + data: { + labels: Array.from({ length: model.population.length }, (_, i) => i + 1), + datasets: [{ + label: "Average Fitness", + data: [0, 1, 2, 3], + borderWidth: 1 + }] + }, + options: { + animation: false, + maintainAspectRatio: true, + scales: { + y: { + beginAtZero: true, + min: 0, + max: 1 + }, + x: { + type: "linear", + position: "bottom", + ticks: { + autoSkip: true, + maxTicksLimit: 500 + } + } + }, + responsive: true + } +}) + +async function gameLoop(): Promise { + model.step(); + + // visuals + view.update(model); + fitnessGramPacerTest.data.labels = Array.from({ length: model.population.length }, (_, i) => i + 1); + fitnessGramPacerTest.data.datasets[0].data = model.fitnessRecord; + fitnessGramPacerTest.update(); + + await sleep(10); + + if (!model.population.includes(model.targetString)) { + requestAnimationFrame(gameLoop); + } else { + console.info("we're done!"); + } +} -view.update(model); +requestAnimationFrame(gameLoop);