xxxxxxxxxx
273
// visualiser for
// https://www.codingame.com/training/medium/escaping-the-cat
var mouse;
var cat;
var R = 100;
var startX = 0;
var startY = 0;
var disableMouse = false;
var userClickedWrongError = false;
var catReach = (80 / 500) * 100;
var catReachRads;
var mouseSpeed = 2;
const dumbMouse = 'right edge (100,0)';
const moveToOppositeCat = 'opposite side of cat'
const tryToOutSmartTheCat = 'outsmart cat'
const moveToEdgeASAP = 'closest edge'
const MAX_TRACE_PATH_LENGTH = 150;
const showMousePathTrace = true;
function setup() {
createCanvas(400, 400);
frameRate(10);
mouse = new Mouse(startX, startY);
cat = new Cat(mouse);
var catReachDeg = catReach / (TWO_PI * R);
catReachRads = radians(catReachDeg * 360);
strategySel = createSelect();
strategySel.position(50, 380);
strategySel.option(moveToOppositeCat);
strategySel.option(dumbMouse);
strategySel.option(tryToOutSmartTheCat);
strategySel.disable(tryToOutSmartTheCat);
strategySel.option(moveToEdgeASAP);
strategySel.disable(moveToEdgeASAP);
}
function mouseClicked() {
startX = mouseX;
startY = mouseY;
disableMouse = !disableMouse;
userClickedWrongError =
mouseX - width / 2 < -R ||
mouseX - width / 2 > R ||
mouseY - height / 2 < -R ||
mouseY - height / 2 > R;
mouse = new Mouse(startX, startY);
cat = new Cat(mouse);
mouse.setCat(cat);
}
function shortestArc(v1, v2) {
// find midpoint
var mx = (v1.x + v2.x) / 2;
var my = (v1.y + v2.y) / 2;
// calc midpoint location on arc + margin
var Ax = (mx / dist(mx, my, 0, 0)) * (R + 45);
var Ay = (my / dist(mx, my, 0, 0)) * (R + 45);
stroke(200, 0, 0, 150);
textAlign(CENTER);
noFill();
strokeWeight(3);
if (v2.angleBetween(v1) > v1.angleBetween(v2)) {
arc(0, 0, 250, 250, v2.heading(), v1.heading());
} else {
arc(0, 0, 250, 250, v1.heading(), v2.heading());
}
strokeWeight(1);
text(`${int(degrees(v2.angleBetween(v1)))}`, Ax, Ay);
}
class Mouse {
constructor(startx, starty) {
this.startx = startx;
this.starty = starty;
this.x = startx;
this.y = starty;
this.path = [];
this.pos = createVector(this.x - width / 2, this.y - height / 2);
}
setCat(c) {
this.cat = c;
}
update() {
const p = this.pos;
if (disableMouse) {
switch (strategySel.value()) {
case tryToOutSmartTheCat:
break;
case moveToOppositeCat:
// mouse tries to move to the side of the circle that is exactly at the opposite side of the cat
const opp = createVector(-this.cat.pos.x, -this.cat.pos.y);
circle(opp.x, opp.y, 15);
opp.sub(p);
opp.normalize();
opp.mult(mouseSpeed);
p.add(opp);
if (showMousePathTrace) {
this.path.push([p.x, p.y]);
}
break;
case moveToEdgeASAP:
text("unimplemented",-100, 100);
case dumbMouse:
default:
// mouse adds static mouse speed to a side of the circle
this.pos.add(mouseSpeed);
if (dist(0, 0, this.pos.x, this.pos.y) > R + 1) {
p.x = this.startx - width / 2;
p.y = this.starty - height / 2;
this.cat.random();
}
}
} else {
// if Mouse is enabled, mouse stays on top of cursor.
p.x = mouseX - width / 2
p.y = mouseY - height / 2;
}
}
draw() {
fill(255, 255, 255, 200);
const x = this.pos.x;
const y = this.pos.y;
stroke(0, 0, 0, 10);
circle(x, y, 5);
curve(x + 10, y + 10, x - 5, y, x, y, x + 10, y + 30);
curve(x - 10, y + 10, x + 5, y, x, y, x - 10, y + 30);
fill("rgba(22,214,19,0.71)");
stroke("rgba(15,16,15,0.88)");
point(x + 2, y - 1);
point(x - 2, y - 1);
point(x, y + 1);
strokeWeight(1);
stroke("#F45BEA89");
fill(0);
if (showMousePathTrace) {
for (const [x, y] of this.path) {
point(x, y);
}
}
if (this.path.length > MAX_TRACE_PATH_LENGTH) {
this.path.shift();
print("shifted");
}
}
}
class Cat {
constructor(mouse, speed = 10) {
this.m = mouse;
this.pos = createVector(0, -R);
}
random() {
//set cat to random pos on the circle
this.pos = p5.Vector.random2D();
this.pos.setMag(R);
}
update() {
const m = this.m.pos;
const p = this.pos;
if (m.x == 0 && m.y == 0) {
circle(p.x, p.y, 15);
line(0, 0, p.x, p.y);
// shortestArc(p,m);
return;
}
var Ax = (m.x / dist(m.x, m.y, 0, 0)) * R;
var Ay = (m.y / dist(m.x, m.y, 0, 0)) * R;
// debuglines
circle(Ax, Ay, 15);
stroke(0, 0, 0, 40);
line(m.x, m.y, Ax, Ay);
// more debug
var v = createVector(Ax, Ay);
shortestArc(p, v);
if (dist(p.x, p.y, Ax, Ay) > catReach) {
// decide which way the cat should walk to get there fastest
if (p.angleBetween(v) > v.angleBetween(p)) {
p.rotate(catReachRads);
line(p.x, p.y, Ax, Ay);
} else {
p.rotate(-catReachRads);
}
} else {
p.x = Ax;
p.y = Ay;
}
}
draw() {
fill("#795548");
const x = this.pos.x;
const y = this.pos.y;
noStroke();
circle(x, y, 8);
triangle(x, y - 4, x + 4, y, x + 6, y - 6);
triangle(x, y - 4, x - 4, y, x - 6, y - 6);
fill("rgba(22,214,19,0.71)");
circle(x - 2, y - 1, 2);
circle(x + 2, y - 1, 2);
stroke("rgba(15,16,15,0.88)");
point(x + 2, y - 1);
point(x - 2, y - 1);
noFill();
stroke(0, 0, 0, 30);
circle(x, y, catReach);
}
}
function draw() {
background(220);
text('strategy', 0, 390)
translate(width / 2, height / 2);
mouse.update();
cat.update();
mouse.draw();
cat.draw();
noFill();
circle(0, 0, 200);
stroke("green");
strokeWeight(1);
noFill();
textAlign(LEFT);
if (disableMouse) {
text("click to make mouse follow the mouse", -190, -190);
stroke("red");
if (userClickedWrongError) {
text("click somewhere inside the circle, lol", -170, -170);
}
} else {
text("click to lock mouse ", -190, -190);
}
point(0, 0);
}