xxxxxxxxxx
448
const colors = {
frog: [0, 1, 0.5],
bird: [1, 1, 0],
snake: [0.5, 0, 1],
tortoise: [1, 0, 0],
fly: [0, 0.5, 1],
fish: [1, 0.5, 0],
squid: [1, 0, 0.5],
end: [0.5, 0.5, 0.5],
}
const emojis = {
frog: "🐸",
bird: "🐤",
snake: "🐍",
tortoise: "🐢",
fly: "🐛",
fish: "🐠",
squid: "🦑",
end: "⚫",
}
let bg;
function setup() {
createCanvas(1080, 1080);
colorMode(RGB, 1);
stroke(1);
bg = createGraphics(width, height);
bg.colorMode(RGB, 1);
generate();
init();
}
let initial_characters;
let state = {};
function generate() {
// initial_characters = [
// { type: "frog", x: 2, y: 2, selected: true, vel: {x: 0, y: 0}},
// // { type: "frog", x: 2, y: 1, selected: true, vel: {x: 0, y: 0}},
// { type: "bird", x: 2, y: 3, selected: false, vel: {x: 0, y: 0}},
// { type: "frog", x: 3, y: 3, selected: false, vel: {x: 0, y: 0}},
// { type: "snake", x: 3, y: 2, selected: false, vel: {x: 0, y: 0}},
// { type: "tortoise", x: 1, y: 4, selected: false, vel: {x: 0, y: 0}},
// { type: "fly", x: 4, y: 1, selected: false, vel: {x: 0, y: 0}},
// { type: "fish", x: 0, y: 1, selected: false, vel: {x: 0, y: 0}},
// { type: "squid", x: 2, y: 4, selected: false, vel: {x: 0, y: 0}},
// { type: "fish", x: 2, y: 0, selected: false, vel: {x: 0, y: 0}},
// { type: "end", x: 4, y: 4, selected: false, vel: {x: 0, y: 0}},
// ]
const W = 5
const H = 5;
const DIFFICULTY = 0.5;
const TARGET_COUNT = floor(W * H * DIFFICULTY);
const MAX_TOTAL_STEPS = H;
const MAX_TRIES = 10000;
state.w = W;
state.h = H;
const { w, h } = state;
let pos = {
x: floor(random(w)),
y: floor(random(h))
}
initial_characters = [
{ type: "end", pos, selected: false, vel: {x: 0, y: 0}}
]
const types = [
"frog",
"bird",
"snake",
"tortoise",
"fly",
"squid",
"fish",
]
for (let i=0; i<MAX_TRIES; i++) {
if (initial_characters.length >= TARGET_COUNT) break;
const type = random(types);
const total_steps = ceil(random(MAX_TOTAL_STEPS));
const dir = random([0, 1]);
if (canStep(pos, type, total_steps, dir)) {
const newPos = getCharacterPos(pos.x, pos.y, type, total_steps, total_steps, dir);
initial_characters.push({
type,
newPos,
selected: false,
vel: { x: 0, y: 0 },
})
pos = newPos;
}
}
initial_characters.at(-1).selected = true;
}
function canStep(pos, type, total_steps, dir) {
for (let i=1; i<=total_steps; i++) {
const newPos = getCharacterPos(pos.x, pos.y, type, i, total_steps, dir);
if (newPos.x < 0 || newPos.y < 0 || newPos.x >= state.w || newPos.y >= state.h) return false;
if (initial_characters.some(c => abs(c.x - newPos.x) < 0.5 && abs(c.y - newPos.y) < 0.5))
return false;
}
return true;
}
function getCharacterPos(x, y, type, step, total_steps, dir) {
switch (type) {
case "frog": {
const step_dif = total_steps - step;
if (step_dif == 0)
return { x, y: y - (total_steps - 2) }
return { x, y: y - step }
}
case "fish": {
const step_dif = total_steps - step;
if (total_steps === 1)
return {
x: dir === 0 ? (x - 1) : (x + 1),
y: y + 1
}
if (step_dif === 0)
return {
x: dir === 0 ? (x - 2) : (x + 2),
y: y - step + 2
}
if (step_dif === 1)
return {
x: dir === 0 ? (x - 1) : (x + 1),
y: y - step
}
return { x, y: y - step }
}
case "bird": {
if (dir === 0)
return { x: x - step, y }
if (dir === 1)
return { x: x + step, y }
}
case "snake": {
if (dir === 0)
return { x: x - step, y: y - step }
if (dir === 1)
return { x: x + step, y: y - step }
}
case "tortoise": {
if (dir === 0)
return { x: x - step, y: y + step }
if (dir === 1)
return { x: x + step, y: y + step }
}
case "fly": {
if (dir === 0)
return { x: x - step * 2, y }
if (dir === 1)
return { x: x + step * 2, y }
}
case "squid": {
if (dir === 0)
return { x, y: y - step * 2 }
if (dir === 1)
return { x, y: y + step * 2 }
}
}
}
let characters;
let history;
function init() {
characters = structuredClone(initial_characters);
history = [];
saveState();
bg.background(0);
}
function saveState() {
history.push(structuredClone(characters));
// console.log(history.map(cs => cs.filter(c => !c.dead).length));
}
function undo() {
if (history.length > 1 && !characters.some(c => c.active && !c.dead))
history.pop();
characters = structuredClone(history.at(-1));
// console.log(history.map(cs => cs.filter(c => !c.dead).length));
}
function draw() {
update();
characters
.filter(c => c.active && !c.dead)
.forEach(drawCharacterLine);
background(0);
image(bg, 0, 0);
characters
.filter(c => !c.dead)
.forEach(drawCharacter);
}
function drawCharacter(character) {
const tile_size = getTileSize();
const { x, y, selected, type, active, prev_x, prev_y } = character;
const pos = getPos(x, y);
const clr = colors[type];
noStroke();
fill(clr);
circle(pos.x, pos.y, tile_size / (selected ? 1.5 : 2));
const emoji = emojis[type];
textSize(tile_size / 4);
text(emoji, pos.x - textSize() * 0.68, pos.y + textSize() * 0.35);
if (selected) {
noFill();
stroke(0);
strokeWeight(5);
circle(pos.x, pos.y, tile_size / 2);
}
}
function drawCharacterLine(character) {
const { x, y, selected, type, active, prev_x, prev_y } = character;
const clr = colors[type];
const pos = getPos(x, y);
const prev_pos = getPos(prev_x, prev_y);
bg.strokeWeight(10);
bg.noFill();
bg.stroke(clr);
bg.line(prev_pos.x, prev_pos.y, pos.x, pos.y);
}
function getTileSize() {
return min(width / (state.w + 2), height / (state.h + 2));
}
function getPos(x, y) {
const tile_size = getTileSize();
return {
x: (x + 1 + 0.5) * tile_size,
y: (y + 1 + 0.5) * tile_size,
}
}
function update() {
characters.forEach(updateCharacter);
}
function updateCharacter(character) {
if (!character.active || character.dead) return;
character.prev_x = character.x;
character.prev_y = character.y;
character.x += character.vel.x;
character.y += character.vel.y;
for (const character2 of characters) {
if (character === character2 || character2.dead) continue;
const dist = sqrt(
pow(character2.x - character.x, 2) +
pow(character2.y - character.y, 2)
)
if (dist < 0.1) {
character.dead = true;
character.x = character2.x;
character.y = character2.y;
drawCharacterLine(character);
character2.selected = true;
if (!characters.some(c => c.active && !c.dead))
saveState();
}
}
const { type } = character;
switch(type) {
case "frog":
character.vel.y = min(character.vel.y + 0.01, 0.2);
break;
case "fly": {
const dist = character.x - character.start_x;
character.y = character.start_y + sin(dist * TAU * 0.25) * 0.5;
} break;
case "squid": {
const dist = character.y - character.start_y;
character.x = character.start_x + (tri(dist * 0.25 + 0.25) * 2 - 1) * 0.5;
} break;
case "fish":
if (character.a < 0.5) {
character.a = min(character.a + character.a_vel, 0.5);
const offset = character.dir > 0 ? -0.5 : 0;
character.x = character.pivot_x + cos((character.a + offset) * character.dir * TAU) * 1;
character.y = character.pivot_y + sin((character.a + offset) * character.dir * TAU) * 1;
if (character.a === 0.5) {
character.vel.y = 0.15;
}
}
break;
}
}
function keyPressed() {
console.log(key);
characters
.filter(c => c.selected && !c.active)
.forEach(c => activateCharacter(c, key));
switch(key) {
case "r":
init();
break;
case "z":
undo();
break;
case " ":
generate();
init();
break;
case "Enter":
bg.save();
}
}
function activateCharacter(character, key) {
const {type} = character;
switch(type) {
case "frog":
if (key === "ArrowUp") {
character.active = true;
character.vel.y = -0.135;
}
break;
case "bird":
if (key === "ArrowRight") {
character.active = true;
character.vel.x = 0.1;
}
if (key === "ArrowLeft") {
character.active = true;
character.vel.x = -0.1;
}
break;
case "snake":
if (key === "ArrowRight") {
character.active = true;
character.vel = { x: 0.075, y: 0.075 }
}
if (key === "ArrowLeft") {
character.active = true;
character.vel = { x: -0.075, y: 0.075 }
}
break;
case "tortoise":
if (key === "ArrowRight") {
character.active = true;
character.vel = { x: 0.075, y: -0.075 }
}
if (key === "ArrowLeft") {
character.active = true;
character.vel = { x: -0.075, y: -0.075 }
}
break;
case "fly":
if (key === "ArrowRight") {
character.active = true;
character.vel.x = 0.08;
character.start_x = character.x;
character.start_y = character.y;
}
if (key === "ArrowLeft") {
character.active = true;
character.vel.x = -0.08;
character.start_x = character.x;
character.start_y = character.y;
}
break;
case "squid":
if (key === "ArrowUp") {
character.active = true;
character.vel.y = -0.0725;
character.start_x = character.x;
character.start_y = character.y;
}
if (key === "ArrowDown") {
character.active = true;
character.vel.y = 0.0725;
character.start_x = character.x;
character.start_y = character.y;
}
break;
case "fish":
if (key === "ArrowRight") {
character.active = true;
character.a = 0;
character.a_vel = 0.015;
character.dir = 1;
character.pivot_x = character.x + 1;
character.pivot_y = character.y;
}
if (key === "ArrowLeft") {
character.active = true;
character.a = 0;
character.a_vel = 0.015;
character.dir = -1;
character.pivot_x = character.x - 1;
character.pivot_y = character.y;
}
break;
}
}
function tri(v) {
return 1 - abs(1 - fract(v) * 2);
}