Grid with small classic generative pieces
Log in to post a comment.
const grid = 6; // min=2 max=20 step=1
const gap = 4.5; // min=0 max=10 step=0.1
const space = 6; // min=0 max=20 step=0.1
const size = 180; // min=50, max=200, step=5
const turtle = new Turtle();
function shuffler(length) {
let arr = [];
return () => {
if (arr.length === 0) {
arr = Array.from({length}, (_,i) => i);
arr.sort(() => -0.5 + Math.random());
}
return arr.pop();
}
}
const totalTypes = 17;
const getRandomType = shuffler(totalTypes);
function walk(i) {
const row = (i % grid);
const col = (i / grid | 0);
const gs = gap + space;
const tileSize = size / grid - gs;
const x = -size/2 + (row / grid) * size + gs / 2;
const y = -size/2 + (col / grid) * size + gs / 2;
const tile = {
x,y,
w: tileSize, h: tileSize,
cx: x + tileSize / 2, cy: y + tileSize / 2,
type: getRandomType(),
col,
row,
}
drawTile(tile);
return i < grid*grid - 1;
}
// Liang-Barsky line clipping to ensure we never draw outside the tile
const clipLine = (x1, y1, x2, y2, bounds) => {
const {minX,minY,maxX,maxY} = bounds;
let t0 = 0, t1 = 1;
const dx = x2 - x1, dy = y2 - y1;
const p = [-dx, dx, -dy, dy];
const q = [x1 - minX, maxX - x1, y1 - minY, maxY - y1];
for (let k = 0; k < 4; k++) {
if (p[k] === 0) { if (q[k] < 0) return; }
else {
const r = q[k] / p[k];
if (p[k] < 0) { if (r > t1) return; else if (r > t0) t0 = r; }
else { if (r < t0) return; else if (r < t1) t1 = r; }
}
}
if (t0 <= t1) {
turtle.jump(x1 + t0 * dx, y1 + t0 * dy);
turtle.goto(x1 + t1 * dx, y1 + t1 * dy);
}
};
const globalAngle = (Math.random() * 36 | 0) * 10;
function drawTile(t) {
const minX = t.x, maxX = t.x + t.w;
const minY = t.y, maxY = t.y + t.h;
const bounds = { minX, maxX, minY, maxY };
if ([2,5,6,7,10,13].includes(t.type) || Math.random() < 0.2) {
let s1 = space/2;
turtle.jump(t.x-s1, t.y-s1);
turtle.goto(t.x+t.w+s1, t.y-s1);
turtle.goto(t.x+t.w+s1, t.y+t.h+s1);
turtle.goto(t.x-s1, t.y+t.h+s1);
turtle.goto(t.x-s1, t.y-s1);
if (Math.random()>0.5) {
s1+=1;
turtle.jump(t.x-s1, t.y-s1);
turtle.goto(t.x+t.w+s1, t.y-s1);
turtle.goto(t.x+t.w+s1, t.y+t.h+s1);
turtle.goto(t.x-s1, t.y+t.h+s1);
turtle.goto(t.x-s1, t.y-s1);
}
}
// Draw pattern based on type
switch (t.type) {
case 0: { // Circles (offset on odd/even)
const s = 5+Math.random()*4|0;
const r = t.w/40;
for(let y=0;y<s;y++) for(let x=y%2?0.5:0;x<s;x++) {
if(Math.random() > 0.1) {
turtle.jump(t.x+ x/s * t.w + r, t.y + y/s*t.h + r*1.5);
turtle.circle(r);
}
}
break;
}
case 1: {// Stripes (hashed lines)
const dist = Math.random()>0.5 ? 2 : 4;
const rad = globalAngle * Math.PI / 180;
const dirX = Math.cos(rad), dirY = Math.sin(rad);
const perpX = -dirY, perpY = dirX;
const diag = Math.hypot(t.w, t.h);
for (let d = -diag; d < diag; d += dist) {
const px = t.cx + d * perpX, py = t.cy + d * perpY;
clipLine(px - diag * dirX, py - diag * dirY, px + diag * dirX, py + diag * dirY, bounds);
}
break;
}
case 2: {
// Connected (dots + lines)
const d = 0.25;
const s = 7;
const pts = Array.from({ length: s*s }, (_,i) => [minX + ((i%s)/(s-1)) * t.w + (-1+Math.random()*2)*t.w/s, minY +(i/s|0)/(s-1) * t.h + (-1+Math.random()*2)*t.h/s]);
//pts.forEach(p => { turtle.jump(p[0], p[1] - 0.5); turtle.circle(0.5); });
for (let a of pts) for (let b of pts) {
if (a !== b && Math.hypot(a[0] - b[0], a[1] - b[1]) < t.w * d)
clipLine(a[0], a[1], b[0], b[1], bounds);
}
break;
}
case 3: // Plus grid
const s = t.w / 8;
for (let px = minX + s / 2; px < maxX; px += s)
for (let py = minY + s / 2; py < maxY; py += s) {
if(Math.random() > 0.2)clipLine(px - s / 3, py, px + s / 3, py, bounds);
if(Math.random() > 0.2)clipLine(px, py - s / 3, px, py + s / 3, bounds);
}
break;
case 4: // Round (arcs from left)
const dist = Math.random()>0.5 ? 2 : 4;
const startX = minX + (maxX-minX)*Math.random()
const startY = minY + (maxY-minY)*[-0.5, 0.5, 1.5][Math.floor(Math.random() * 3)];
for (let rad = 0; rad < t.w * 2.5; rad += dist) {
let lx = null, ly = null;
for (let a = 0; a <= Math.PI*2; a += 0.2) {
const nx = startX + rad * Math.cos(a), ny = startY + rad * Math.sin(a);
if (lx !== null) clipLine(lx, ly, nx, ny, bounds);
lx = nx; ly = ny;
}
const nx = startX + rad * Math.cos(0), ny = startY + rad * Math.sin(0);
clipLine(lx, ly, nx, ny, bounds);
}
break;
case 5: // Outside-to-circle
const circleRadius = t.h/[10,5][Math.random()*2|0];
const totalLines = 6;
for (let side = 0; side < 4; side++) {
for (let k = 0; k < totalLines; k++) {
const tVal = k / (totalLines-1);
let sx, sy;
if (side === 0) {
sx = minX + tVal * t.w;
sy = minY;
} else if (side === 1) {
sx = maxX;
sy = minY + tVal * t.h;
} else if (side === 2) {
sx = maxX - tVal * t.w;
sy = maxY;
} else {
sx = minX;
sy = maxY - tVal * t.h;
}
const theta = Math.atan2(sy - t.cy, sx - t.cx);
clipLine(sx, sy, t.cx + circleRadius * Math.cos(theta), t.cy + circleRadius * Math.sin(theta), bounds);
}
}
break;
case 6: // Isometric (triangular grid)
[30, 90, 150].forEach(deg => {
const rad = deg * Math.PI / 180;
const dX = Math.cos(rad), dY = Math.sin(rad);
const pX = -dY, pY = dX;
const dia = Math.hypot(t.w, t.h);
for (let d = -dia; d < dia; d += 6) {
const cx = t.cx + d * pX, cy = t.cy + d * pY;
clipLine(cx - dia * dX, cy - dia * dY, cx + dia * dX, cy + dia * dY, bounds);
}
});
break;
case 7: { // 10 PRINT (Random Diagonals)
const step = t.w / 8;
for (let x = minX; x < maxX - 1; x += step) {
for (let y = minY; y < maxY - 1; y += step) {
if (Math.random() > 0.5) clipLine(x, y, x + step, y + step, bounds);
else clipLine(x + step, y, x, y + step, bounds);
}
}
break;
}
case 8: { // Concentric Squares (Tunnel)
const centerX = t.cx + (Math.random() - 0.5) * t.w * 0.5;
const centerY = t.cy + (Math.random() - 0.5) * t.h * 0.5;
for (let i = 0; i < t.w / 2; i += 2) {
const f = i / (t.w / 2); // 0 to 1
// Lerp from bounds to center point
const x1 = minX * (1 - f) + centerX * f;
const x2 = maxX * (1 - f) + centerX * f;
const y1 = minY * (1 - f) + centerY * f;
const y2 = maxY * (1 - f) + centerY * f;
// Draw manual rect to ensure clipping
clipLine(x1, y1, x2, y1, bounds);
clipLine(x2, y1, x2, y2, bounds);
clipLine(x2, y2, x1, y2, bounds);
clipLine(x1, y2, x1, y1, bounds);
}
break;
}
case 9: { // Sine Waves
const amp = t.h / 10;
const freq = (Math.random() * 0.25) + 0.25;
const steps = Math.random()>0.5 ? 2 : 4;
const offset = Math.random() * t.w/10;
for (let y = minY-offset; y < maxY + offset; y += steps) {
let lx = minX;
let ly = y + Math.sin((lx - minX) * freq) * amp;
for (let x = minX + 2; x <= maxX; x += 2) {
const ny = y + Math.sin((x - minX) * freq) * amp;
clipLine(lx, ly, x, ny, bounds);
lx = x; ly = ny;
}
}
break;
}
case 10: { // empty box
break;
}
case 11: { // Truchet Arcs
const step = t.w / 6;
for (let x = minX; x < maxX - 0.1; x += step) {
for (let y = minY; y < maxY - 0.1; y += step) {
const cx = x + step/2, cy = y + step/2;
const r = step/2;
// Randomly choose orientation
if (Math.random() > 0.5) {
// Top-Left & Bottom-Right
drawArc(x, y, r, 0, Math.PI/2, bounds); // approximate via small lines
drawArc(x+step, y+step, r, Math.PI, 1.5*Math.PI, bounds);
} else {
// Top-Right & Bottom-Left
drawArc(x+step, y, r, Math.PI/2, Math.PI, bounds);
drawArc(x, y+step, r, 1.5*Math.PI, 2*Math.PI, bounds);
}
}
}
break;
}
case 12: { // ZigZags (Fixed)
const d = 6; // Width of each zig/zag
const a = t.h / 12; // Amplitude (height)
const space = a * 2.5; // Spacing between rows
for (let y = minY + a; y < maxY; y += space) {
let lx = minX;
let ly = y + a; // Start at the "High" point
// Use a counter 'i' to guarantee perfect alternation
for (let i = 0; lx < maxX; i++) {
const nx = lx + d;
// Toggle: Even = Low, Odd = High
const ny = y + (i % 2 === 0 ? -a : a);
clipLine(lx, ly, nx, ny, bounds);
lx = nx;
ly = ny;
}
}
break;
}
case 13: { // Sunburst / Radial Lines
const rays = 12 + Math.random() * 10;
const originX = t.cx + (Math.random() - 0.5) * t.w * 0.5;
const originY = t.cy + (Math.random() - 0.5) * t.h * 0.5;
const radius = Math.hypot(t.w, t.h); // Ensure it covers corners
for (let i = 0; i < rays; i++) {
const theta = (i / rays) * Math.PI * 2;
const ex = originX + Math.cos(theta) * radius;
const ey = originY + Math.sin(theta) * radius;
clipLine(originX, originY, ex, ey, bounds);
}
break;
}
case 14: { // Brick Wall
const rowH = t.h / 8-0.001;
const brickW = t.w / 4;
for (let y = minY; y < maxY; y += rowH) {
// Horizontal mortar
clipLine(minX, y, maxX, y, bounds);
// Vertical joints (offset every other row)
const offset = ((y ) / rowH | 0) % 2 === 0 ? 0 : brickW / 2;
for (let x = minX + offset; x < maxX; x += brickW) {
clipLine(x - 1, y, x-1, y + rowH, bounds);
}
}
break;
}
case 15: { // Rectangular Spiral
let x = t.cx, y = t.cy;
let len = 2;
const gap = 1;
// 0: up, 1: right, 2: down, 3: left
let dir = 0;
// Limit loop to prevent infinite run if bounds are huge
for(let i=0; i<50; i++) {
let nx = x, ny = y;
if (dir === 0) ny -= len;
if (dir === 1) nx += len;
if (dir === 2) ny += len;
if (dir === 3) nx -= len;
clipLine(x, y, nx, ny, bounds);
x = nx; y = ny;
len += gap; // Spiral gets wider
dir = (dir + 1) % 4;
// Stop if we are fully outside bounds (optimization)
if(x < minX - gap && nx < minX - gap) break;
if(x > maxX + gap && nx > maxX + gap) break;
if(y < minY - gap && ny < minY - gap) break;
if(y > maxY + gap && ny > maxY + gap) break;
}
break;
}
case 16: { // Nested Rotated Squares
let size = Math.min(t.w, t.h);
let cx = t.cx, cy = t.cy;
let angle = 0;
// Recursively draw squares shrinking in size
while (size > 2) {
const rad = angle;
const c = Math.cos(rad) * size / 2;
const s = Math.sin(rad) * size / 2;
const p1x = cx - c - s, p1y = cy + s - c; // Top-Left-ish
const p2x = cx + c - s, p2y = cy - s - c; // Top-Right-ish
const p3x = cx + c + s, p3y = cy - s + c; // Bottom-Right-ish
const p4x = cx - c + s, p4y = cy + s + c; // Bottom-Left-ish
clipLine(p1x, p1y, p2x, p2y, bounds);
clipLine(p2x, p2y, p3x, p3y, bounds);
clipLine(p3x, p3y, p4x, p4y, bounds);
clipLine(p4x, p4y, p1x, p1y, bounds);
size *= 0.8; // Shrink
angle += 0.15; // Rotate
}
break;
}
default:
break;
}
}
// Helper for Case 11 to draw clipped arcs
function drawArc(cx, cy, r, startAngle, endAngle, bounds) {
const steps = 6;
let la = startAngle;
for(let i=1; i<=steps; i++) {
const na = startAngle + (endAngle - startAngle) * (i/steps);
clipLine(
cx + Math.cos(la)*r, cy + Math.sin(la)*r,
cx + Math.cos(na)*r, cy + Math.sin(na)*r,
bounds
);
la = na;
}
}