generated from michael/webpack-base
intermediate commit -- working on makeLab
This commit is contained in:
parent
c224f1a609
commit
c631383087
@ -4,6 +4,18 @@ import { clamp, Engine, Keys, randchoice, randint, UI, Vec2, vec2 } from './game
|
||||
import { SnakeBrain } from './snake-brain';
|
||||
import { SGSHashSet, Snake, SnakeGameState, SnakeGameStateWithHistory } from './types';
|
||||
import { PipeRef } from '.';
|
||||
import {
|
||||
assignSpecies,
|
||||
CompatibilityDistanceConfig,
|
||||
CompatibilityDistanceThreshold,
|
||||
CrossConfig,
|
||||
FertilityConfig,
|
||||
Genome,
|
||||
MateChoiceConfig,
|
||||
mutate,
|
||||
MutateConfig,
|
||||
} from './neat';
|
||||
import { BASE_GENOME_SNAKE_BRAIN_NEAT, NEATSnakeBrain } from './neat-snake-brain';
|
||||
|
||||
const BOARD_SIZE = 600; // px
|
||||
const SQUARE_SIZE = 30; // px
|
||||
@ -45,6 +57,8 @@ export interface TrainerSnapshot {
|
||||
|
||||
// labs and mutation -----------------------------------------------------------
|
||||
|
||||
// TODO: random colors for each species
|
||||
|
||||
function makeLabColors({ hue, sat, lig }: { hue: number; sat: number; lig: number }): LabColors {
|
||||
const head = `hsl(${hue},${sat}%,${lig}%)`;
|
||||
const tail = `hsl(${hue},${sat - 10}%,${lig - 20}%)`;
|
||||
@ -60,29 +74,12 @@ function makeRandomLabColors() {
|
||||
return makeLabColors({ hue, sat, lig });
|
||||
}
|
||||
|
||||
function mutateLabColors(prev: LabColors) {
|
||||
const hue = (prev.hue + randint(-10, 10) + 360) % 360;
|
||||
const sat = clamp(prev.sat + randint(-5, 5), 70, 100);
|
||||
const lig = clamp(prev.lig + randint(-4, 4), 50, 70);
|
||||
|
||||
return makeLabColors({ hue, sat, lig });
|
||||
}
|
||||
|
||||
function makeRandomLab({ id, hiddenLayerNodes }: { id: number; hiddenLayerNodes: number }) {
|
||||
function makeLab(id: number, genome: Genome) {
|
||||
return {
|
||||
id,
|
||||
colors: makeRandomLabColors(),
|
||||
state: getStartSnakeGameState(),
|
||||
brain: SnakeBrain.fromRandom({ hiddenLayerNodes }),
|
||||
};
|
||||
}
|
||||
|
||||
function mutateLab({ newId, ref, mc }: { newId: number; ref: SnakeGameTrainerLab; mc: MutationConfig }) {
|
||||
return {
|
||||
id: newId,
|
||||
colors: mutateLabColors(ref.colors),
|
||||
state: getStartSnakeGameState(),
|
||||
brain: SnakeBrain.mutate(ref.brain, mc),
|
||||
brain: NEATSnakeBrain.fromGenome(genome),
|
||||
};
|
||||
}
|
||||
|
||||
@ -133,53 +130,56 @@ export default function runCanvas(canvas: HTMLCanvasElement, pipeRef: MutableRef
|
||||
|
||||
// game logic --------------------------------------------------------------
|
||||
|
||||
const SNAKES = 5000;
|
||||
|
||||
const SNAKES = 150;
|
||||
const STARVE_STEPS = BOARD_SQUARES * 6;
|
||||
|
||||
const CULL_RATIO = 0.8;
|
||||
const CULL_N = Math.floor(SNAKES * CULL_RATIO);
|
||||
const MUTATE_RATE = 0.03;
|
||||
const MUTATE_MAG = 5.0;
|
||||
// neat configuration ------------------------------------------------------
|
||||
const FC: FertilityConfig = {
|
||||
fertile_threshold: 0.6,
|
||||
champion_min_species_size: 3,
|
||||
};
|
||||
const MCC: MateChoiceConfig = {
|
||||
asexual_rate: 0.5,
|
||||
interspecies_rate: 0.2,
|
||||
};
|
||||
const CC: CrossConfig = {
|
||||
reenable_rate: 0.25,
|
||||
};
|
||||
const MC: MutateConfig = {
|
||||
mutate_rate: 0.25,
|
||||
assign_rate: 0.1,
|
||||
assign_mag: 1.0,
|
||||
perturb_mag: 0.1,
|
||||
new_node_rate: 0.2,
|
||||
new_connection_rate: 0.1,
|
||||
};
|
||||
const CDC: CompatibilityDistanceConfig = {
|
||||
c1: 1,
|
||||
c2: 1,
|
||||
c3: 1,
|
||||
};
|
||||
const CDT: CompatibilityDistanceThreshold = 1.0;
|
||||
|
||||
// general simulation ------------------------------------------------------
|
||||
const generation = 1;
|
||||
|
||||
// labs & initial population -----------------------------------------------
|
||||
const initialGenomes = new Array(SNAKES).map(() => mutate(BASE_GENOME_SNAKE_BRAIN_NEAT, MC));
|
||||
|
||||
// assign initial species
|
||||
const population = new Map();
|
||||
const reps = new Map();
|
||||
for (const genome of initialGenomes) {
|
||||
const sid = assignSpecies(genome, reps, CDC, CDT);
|
||||
population.set(genome, sid);
|
||||
}
|
||||
|
||||
let iteration = 1;
|
||||
let nextLabId = 1;
|
||||
|
||||
const trainer: SnakeGameTrainerState = {
|
||||
labs: Array.from({ length: SNAKES }).map(_ => makeRandomLab({ id: nextLabId++, hiddenLayerNodes: 6 })),
|
||||
};
|
||||
|
||||
function cullWeakFearStrong() {
|
||||
// a snake is considered starved if on average, a snake traverses
|
||||
// the width of the board 6 times
|
||||
const STARVE_RATIO_CUTOFF = BOARD_SQUARES * 4;
|
||||
|
||||
function sortKey(lab: SnakeGameTrainerLab): number {
|
||||
const length = lab.state.snake.length;
|
||||
const duration = lab.state.history.length;
|
||||
|
||||
// const starve_ratio = length / duration;
|
||||
// if (starve_ratio < STARVE_RATIO_CUTOFF) return -1;
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
pipeRef.current.addTrainerSnap({ iteration, labs: createLabSnapshots(trainer.labs) });
|
||||
|
||||
const labsRanked = trainer.labs.sort((a, b) => sortKey(a) - sortKey(b));
|
||||
const labsSurvive = labsRanked.slice(CULL_N).map(l => ({ ...l, state: getStartSnakeGameState() }));
|
||||
const labsMutated = Array.from({ length: CULL_N }).map(_ => {
|
||||
const ref = randchoice(labsSurvive);
|
||||
return mutateLab({
|
||||
newId: nextLabId++,
|
||||
ref,
|
||||
mc: { rate: MUTATE_RATE, mag: MUTATE_MAG },
|
||||
});
|
||||
});
|
||||
const newLabs = [...labsSurvive, ...labsMutated];
|
||||
trainer.labs = newLabs;
|
||||
iteration += 1;
|
||||
}
|
||||
|
||||
function update() {
|
||||
// press spacebar to slow down time
|
||||
if (keys.isKeyPressed(' ')) {
|
||||
@ -191,10 +191,11 @@ export default function runCanvas(canvas: HTMLCanvasElement, pipeRef: MutableRef
|
||||
engine.setUpdateDelay(0);
|
||||
}
|
||||
|
||||
// TODO: compute next generation when all snakes are dead
|
||||
// cull weak when all snakes are dead
|
||||
const allDead = trainer.labs.findIndex(l => !l.state.dead) === -1;
|
||||
if (allDead) {
|
||||
cullWeakFearStrong();
|
||||
// cullWeakFearStrong();
|
||||
}
|
||||
|
||||
for (const lab of trainer.labs) {
|
||||
|
@ -301,7 +301,7 @@ export function alignGenomes(mom: Genome, dad: Genome): GenomeAlignment {
|
||||
return alignment;
|
||||
}
|
||||
|
||||
interface CompatibilityDistanceConfig {
|
||||
export interface CompatibilityDistanceConfig {
|
||||
c1: number;
|
||||
c2: number;
|
||||
c3: number;
|
||||
@ -316,7 +316,7 @@ export function compatibilityDistance(alignment: GenomeAlignment, { c1, c2, c3 }
|
||||
return distance;
|
||||
}
|
||||
|
||||
type CompatibilityDistanceThreshold = number;
|
||||
export type CompatibilityDistanceThreshold = number;
|
||||
|
||||
export function assignSpecies(
|
||||
genome: Genome,
|
||||
@ -336,7 +336,7 @@ export function assignSpecies(
|
||||
return sid;
|
||||
}
|
||||
|
||||
interface FertilityConfig {
|
||||
export interface FertilityConfig {
|
||||
fertile_threshold: number;
|
||||
champion_min_species_size: number;
|
||||
}
|
||||
@ -377,7 +377,7 @@ export function tournamentSelectionWithChampions(
|
||||
return { winners, champions };
|
||||
}
|
||||
|
||||
interface MateChoiceConfig {
|
||||
export interface MateChoiceConfig {
|
||||
asexual_rate: number;
|
||||
interspecies_rate: number;
|
||||
}
|
||||
@ -414,7 +414,7 @@ export function chooseMate(genome: Genome, population: Population, config: MateC
|
||||
return genome;
|
||||
}
|
||||
|
||||
interface CrossConfig {
|
||||
export interface CrossConfig {
|
||||
reenable_rate: number;
|
||||
}
|
||||
export function crossGenomes(mom: Genome, dad: Genome, fitness: Map<Genome, number>, config: CrossConfig) {
|
||||
|
Loading…
Reference in New Issue
Block a user