Fork: Invaders

Added some controls to play around and some different generation options.

Log in to post a comment.

// Forked from "Invaders" by ge1doot
// https://turtletoy.net/turtle/46d50beb8a

// --- Print Configuration ---
const paperSize = 0; // min=0 max=1 step=1 (A4, A3)
const penWidthMM = 0.4; // min=0.1 max=2 step=0.05 (Actual width in mm)

// --- Design Controls ---
const gridCount = 12; // min=4 max=32 step=1 (Number of invaders)
const alienScale = 0.85; // min=0.1 max=1 step=0.05 (Invader scale)
const randomness = 0.5; // min=0.1 max=0.9 step=0.05 (Pixel density)

// --- Pixel Style ---
const pixelShape = 1; // min=0 max=1 step=1 (Circle, Square)
const fillEnabled = 1; // min=0 max=1 step=1 (Outline only, Fill/Hatch)
const hatchType = 2; // min=0 max=2 step=1 (Horizontal, Vertical, Diagonal)
const hatchDensity = 1.0; // min=0.5 max=3 step=0.1 (1 = Solid, >1 = More open)

// --- Internal Settings ---
const paperWidthMM = paperSize === 0 ? 210 : 297;
const scaleFactor = 200 / paperWidthMM; 
const penSize = penWidthMM * scaleFactor;

Canvas.setpenopacity(1);

// Grid setup
const margin = 10;
const availableSize = 200 - (margin * 2);
const cellSize = availableSize / gridCount; 
const pixelSize = (cellSize / 10) * alienScale; 

const turtle = new Turtle();

// Helper to draw a Square (with optional hatching)
function drawSquarePixel(t, x, y, size) {
    const r = size / 2;
    
    // 1. Draw Outline
    t.up();
    t.jump(x - r, y - r);
    t.down();
    t.goto(x + r, y - r);
    t.goto(x + r, y + r);
    t.goto(x - r, y + r);
    t.goto(x - r, y - r);
    
    // 2. Draw Hatching (if enabled)
    if (fillEnabled === 1) {
        // Calculate step based on pen size and density
        const step = penSize * hatchDensity;
        
        if (hatchType === 0) { // Horizontal ZigZag
            let currentY = -r + step;
            let dir = 1;
            t.up();
            t.jump(x - r, y + currentY);
            t.down();
            while (currentY < r) {
                t.goto(x + (dir * r), y + currentY);
                currentY += step;
                if (currentY < r) t.goto(x + (dir * r), y + currentY);
                dir *= -1;
            }
        } 
        else if (hatchType === 1) { // Vertical ZigZag
            let currentX = -r + step;
            let dir = 1;
            t.up();
            t.jump(x + currentX, y - r);
            t.down();
            while (currentX < r) {
                t.goto(x + currentX, y + (dir * r));
                currentX += step;
                if (currentX < r) t.goto(x + currentX, y + (dir * r));
                dir *= -1;
            }
        }
        else if (hatchType === 2) { // Diagonal (45 degrees)
             t.up();
             // Generate lines at 45 degrees (y = x + offset) and clip them to the square
             for(let offset = -2*size; offset <= 2*size; offset += step) {
                 let x1 = -r, y1 = -r + offset;
                 let x2 = r, y2 = r + offset;
                 
                 // Clip Y values to stay within -r and r
                 if (y1 < -r) { x1 += (-r - y1); y1 = -r; }
                 if (y1 > r)  { x1 -= (y1 - r);  y1 = r; }
                 if (y2 < -r) { x2 += (-r - y2); y2 = -r; }
                 if (y2 > r)  { x2 -= (y2 - r);  y2 = r; }
                 
                 // Draw line if valid
                 if (x1 < r && x2 > -r && x1 < x2) {
                     t.up(); t.jump(x + x1, y + y1);
                     t.down(); t.goto(x + x2, y + y2);
                 }
             }
        }
    }
}

// Helper to draw a Circle (concentric fill)
function drawCirclePixel(t, radius) {
     const step = Math.max(penSize, radius/3);
     let currentR = radius;
     if (fillEnabled === 1) {
         while(currentR > penSize/2) {
            t.circle(currentR);
            currentR -= step; 
         }
         t.circle(0.1); // Center point
     } else {
         t.circle(radius);
     }
}

// Main Loop
function walk(i) {
    const col = i % gridCount;
    const row = (i / gridCount) >> 0;
    
    // Calculate cell position
    const cellX = -100 + margin + (col * cellSize);
    const cellY = -100 + margin + (row * cellSize);
    
    // Center the 8x8 invader within the cell
    const centeringOffset = (cellSize - (8 * pixelSize)) / 2;
    const startX = cellX + centeringOffset;
    const startY = cellY + centeringOffset;

    // Generate and draw the 8x8 grid
    for (let y = 0; y < 8; y++) {
        let bits = [];
        // Generate half the row and mirror it for symmetry
        for(let b=0; b<4; b++) bits.push(Math.random() < randomness ? "1" : "0");
        let fullRow = bits.concat(bits.slice().reverse());

        fullRow.forEach((n, x) => {
            if (n === "1") {
                const px = startX + (x * pixelSize);
                const py = startY + (y * pixelSize);
                
                // 0.9 factor creates a small aesthetic gap between pixels
                const visualSize = pixelSize * 0.9; 

                turtle.up();
                turtle.goto(px, py);
                turtle.down();
                
                if (pixelShape === 0) {
                    drawCirclePixel(turtle, visualSize / 2);
                } else {
                    drawSquarePixel(turtle, px, py, visualSize);
                }
            }
        });
    }
    return i < gridCount * gridCount - 1;
}