Inspired by notes I made while solving a puzzle.
Log in to post a comment.
// You can find the Turtle API reference here: https://turtletoy.net/syntax
Canvas.setpenopacity(1);
let scale = 2.19;// min=0.1 max=20 step=0.01
let columns = 8;// min = 1 max=100 step=1
let rows = 9;// min = 1 max=100 step=1
let spacing = 18; // min = 1 max=200 step=1
let lines = 1; // min=0, max=1, step=1, (Main, All)
let distPow = 1.9; // min = 0.5 max=5 step=0.01
let bend = 3.42;// min=0 max=10 step=0.01
let bendLimit = 3.93;// min=0 max=10 step=0.01
let lineDist = 2.5; // min=0 max=10 step=0.01
// Global code will be evaluated once.
const turtle = new Turtle();
const positions = [[0, 0], [1, 0], [2, 0],
[0, 1], [1, 1], [2, 1],
[0, 2], [1, 2], [2, 2],
[1, 3]
];
// 0 1 2
// 3 4 5
// 6 7 8
// 9
const groups = [
[3, 0, 4, 6],
[4, 1, 5, 7, 3],
[5, 2, 4, 8],
[7, 4, 8, 9, 6]
];
const NB = [
[[1, 3], [4]],
[[2,4,0],[5,3]],
[[5,1],[4]],
[[0,4,6],[1, 7]],
[[1,5,7,3],[2,8,0,6]],
[[2,4,8],[1,7]],
[[3,7],[4,9]],
[[4,8,9,6],[3,5]],
[[5,7],[4,9]],
[[7],[6,8]],
];
function i_to_dpos(i, p0)
{
const p = positions[i];
return [(p[0]-1) * scale + p0[0], (p[1]-1) * scale + p0[1]];
}
function edge_id(a, b) {
return a < b ? 10 * a + b : 10 * b + a;
}
function draw_shape(shape)
{
const p = turtle.pos();
for (var i=0; i<10; i++) {
if (shape & (1<<i)) {
let p1 = i_to_dpos(i, p);
const nbi = NB[i];
let used = [];
let hasEdge = false;
for (const i2 of nbi[0]) {
let p2 = i_to_dpos(i2, p);
if (shape & (1 << i2)) {
let edgeID = edge_id(i, i2);
if (edgeID in used) {
continue;
}
used[edgeID] = true;
turtle.jump(p1);
turtle.pendown();
turtle.goto(p2);
hasEdge = true;
}
}
if (!hasEdge) {
for (const i2 of nbi[1]) {
let p2 = i_to_dpos(i2, p);
if (shape & (1 << i2)) {
let edgeID = edge_id(i, i2);
if (edgeID in used) {
continue;
}
used[edgeID] = true;
turtle.jump(p1);
turtle.pendown();
turtle.goto(p2);
hasEdge = true;
}
}
}
if(!hasEdge) {
let r = scale * 0.2;
turtle.setheading(0);
turtle.jump(p1[0], p1[1]-r);
turtle.pendown();
turtle.circle(r);
//turtle.forward(scale * 0.3);
//turtle.circle(scale * 0.2);
}
}
if (lines > 0 && ((shape & (1<<i)) == 0)) {
let p1 = i_to_dpos(i, p);
const nbi = NB[i];
let used = [];
let hasEdge = false;
for (const i2 of nbi[0]) {
let p2 = i_to_dpos(i2, p);
if ((shape & (1 << i2)) == 0) {
let edgeID = edge_id(i, i2);
if (edgeID in used) {
continue;
}
used[edgeID] = true;
turtle.jump(p1);
turtle.pendown();
turtle.goto(p2);
hasEdge = true;
}
}
}
}
}
function next_states(state) {
let result = [];
for (const action of groups) {
if (state & (1 << action[0])) {
let s2 = state;
let tmpBits=[];
for (let i=1; i<action.length; i++) {
if (state & (1 << action[i])) {
tmpBits.push(1);
} else {
tmpBits.push(0);
}
s2 &= ~(1<<action[i]);
}
tmpBits.push(tmpBits.shift());
for (let i=1; i<action.length; i++) {
if (tmpBits[i-1] > 0) {
s2 |= (1<<action[i]);
}
}
result.push(s2);
}
}
return Array.from(new Set(result));
}
let visited = [];
let q = [0x77];
let rc = [0, 0];
let states = [];
let states2 = [];
function dist(a, b, order) {
let i1 = order.indexOf(a);
let i2 = order.indexOf(b);
let x1 = i1 % columns;
let y1 = Math.floor(i1 / columns);
let x2 = i2 % columns;
let y2 = Math.floor(i2 / columns);
let dx = Math.abs(x1-x2);
let dy = Math.abs(y1-y2);
//return dx+dy;
//return dx*dx+dy*dy;
let d= Math.sqrt(dx*dx+dy*dy)
return Math.pow(d, distPow);
}
function dist_v(a, order) {
let res = 0;
//
for (const n of states[a]) {
//console.log(`sd ${a} ${n}`)
res += dist(a, n, order);
}
for (const n of states2[a]) {
//console.log(`sd ${a} ${n}`)
res += dist(a, n, order);
}
if (a == 0x77) {
// console.log(`zzzzzzdd 0x77 ${res} ${order.indexOf(a)}`);
}
//console.log(`tmp ${res}`);
return res;
}
function slerp(a, b, t) {
return t*b + (1-t)*a;
}
function xydist(a, b) {
let dx = a[0]-b[0];
let dy = a[1]-b[1];
return Math.sqrt(dx*dx+dy*dy);
}
// The walk function will be called until it returns false.
function walk(i) {
let order = [];
visited[0x77] = true;
while (q.length > 0) {
let s1 = q.shift();
let neighbours = next_states(s1);
states[s1] = neighbours;
for (const state of neighbours){
if (!(state in states2)) {
states2[state]=[];
}
states2[state].push(s1);
if (visited[state]) {
continue;
}
visited[state] = true;
q.push(state);
}
order.push(s1);
}
let fillers = -1;
while (order.length < rows*columns) {
order.push(fillers);
states[fillers] = [];
states2[fillers] = [];
fillers -= 1;
}
/*for (let i = order.length - 1; i >= 0; i--) {
let j = Math.floor(Math.random() * (i + 1));
if (j >= i) {
j = 0;
}
let temp = order[i];
order[i] = order[j];
order[j] = temp;
}*/
let dst =0;
for (let i=0; i<order.length; i++) {
//console.log(`wtf ${i} ${order[i]}`);
dst += dist_v(order[i], order);
}
console.log("optimizing --------------")
for (let x=0; x<15; x++) {
//console.log(`>>>>>>>>> ${x}`);
for (let i=0; i<order.length; i++) {
for (let j=0; j<order.length; j++) {
//console.log(`pos |${i} ${j}| v:${order[i]} ${order[j]}`);
let d1 = dist_v(order[i], order) + dist_v(order[j], order);
let temp = order[i];
order[i] = order[j];
order[j] = temp;
let d2 = dist_v(order[i], order) + dist_v(order[j], order);
// console.log(`d1: ${d1} d2:${d2}`);
if (d2 >= d1) {
let temp = order[i];
order[i] = order[j];
order[j] = temp;
} else {
// console.log(`tmpswpa ${d1}->${d2} ${i} ${j} ${order[i]} ${order[j]}`);
}
}
}
/*let dst2 =0;
for (let i=0; i<order.length; i++) {
dst2 += dist_v(order[i], order);
}*/
//console.log(`opt ${x} ${dst2}`);
}
console.log("AAAAAAAAAA optim")
let index = 0;
for (let r=0; r<rows; r++) {
for (let c=0; c<columns; c++) {
if (index >= order.length) {
continue;
}
let p = [c * spacing - (columns-1)*spacing*0.5,
r * spacing - (rows-1)*spacing*0.5];
turtle.jump(p);
if (order [index] > 0){
draw_shape(order[index]);
//console.log(`N: ${states[order[index]]}`);
for (let j=0; j<order.length; j++) {
if (states[order[index]].includes(order[j])) {
//console.log(`${order[index]} -> ${order[j]}`)
if (index == j) {
continue;
}
let c2 = j % columns;
let r2 = Math.floor(j/ columns);
//console.log(`${order[j]} ${c2} ${r2}`);
let p1 = p;
let p2 = [c2 * spacing - (columns-1)*spacing*0.5,
r2 * spacing - (rows-1)*spacing*0.5];
let dx = p2[0] -p1[0];
let dy = p2[1] -p1[1];
if (Math.abs(r-r2) ==1 && Math.abs(c-c2) == 1) {
if (p1[1] < p2[1]) {
p1[1] += 2 * scale;
} else {
p2[1] += 2 * scale;
}
}
turtle.jump(p);
for (let t=0; t<1; t += (1.0/128.0)) {
let px =slerp(p1[0], p2[0], t);
let py =slerp(p1[1], p2[1], t);
let shift = bend*scale * (1 - 4 * ((t-0.5)*(t-0.5))) * Math.min(bendLimit, (((Math.abs(dx)+Math.abs(dy)) / spacing)-1));
if (Math.abs(dx) < Math.abs(dy)) {
px += shift;
} else {
py -= shift;
}
if (xydist(p, [px, py]) < scale*lineDist ||
xydist(p2, [px, py]) < scale*lineDist ) {
turtle.penup();
} else {
turtle.pendown();
}
turtle.goto(px, py);
}
turtle.goto(p2);
}
}
}
index++;
}
}
return false;
}
/*function walk(i) {
if (q.length <= 0) {
return false;
}
if (rc[1] >= columns) {
rc[1] = 0;
rc[0] += 1;
}
if (rc[0] >= rows) {
return false;
}
let s1 = q.shift();
turtle.jump([rc[1] * spacing - (columns-1)*spacing*0.5,
rc[0] * spacing - (rows-1)*spacing*0.5]);
draw_shape(s1);
let neighbours = next_states(s1);
for (const state of neighbours){
if (state in visited) {
continue;
}
visited[state] = true;
q.push(state);
}
rc[1] += 1;
//turtle.jump(0, 0);
//draw_shape(0x77);
return true;
}*/