Hangman

This is my entry in the non-existent game competition on Turtletoy.

Please select the text input field (guess) and start typing.

#hangman #game #handdrawing

Log in to post a comment.

// Hangman. Created by Reinder Nijhoff 2024 - @reindernijhoff
//
// https://turtletoy.net/turtle/7742a7308b
//

const turtle = new Turtle();

const guess = ''; // type=string, What's your guess?
const text = new Text(getFontData());
let seed = 15;
const hangman = [
    () => drawLine([-30,50], [-30,-50]),
    () => drawLine([-30,-50], [30,-50]),
    () => drawLine([-30,-30], [-10,-50]),
    () => drawLine([-45,50], [-15,50]),
    () => drawLine([20,-50], [20,-37]),
    () => drawCircle([20,-25], 10),
    () => drawLine([20,-13], [20,15]),
    () => drawLine([20,-5], [0,-10]),
    () => drawLine([20,-5], [40,-10]),
    () => drawLine([20,15], [5,35]),
    () => drawLine([20,15], [35,35]),
];

drawGuess();

if (guess.length > 0) {
    const g = guess.toUpperCase();
    const w = getWord().toUpperCase();

    const incorrect = g.split('').reduce((a,c) => a + (w.includes(c)?0:1), 0);
    for (let i=0; i<Math.min(hangman.length, incorrect); i++) {
        hangman[i]();
    }
    
    if (incorrect >= 11) {
        drawCenteredText('YOU LOSE!', [0, -80], 1.5);
    } else if (w.split('').every(c => g.includes(c))) {
        drawCenteredText('YOU WIN!', [0, -80], 1.5);
    }
} else {
    drawCenteredText('Select the', [0,-20], 1);
    drawCenteredText('text input field', [0,0], 1);
    drawCenteredText('and start typing', [0,20], 1);
}

function drawGuess() {
    const word = getWord().toUpperCase();
    const w = 120 / word.length;
    const x = - word.length/2 * w;
    for (let i=0; i<word.length; i++) {
        drawLine([x + (i+0.1)*w,80], [x+(i+0.9)*w, 80], .1);
        if (guess.toUpperCase().includes(word[i])) {
            drawCenteredText( word[i], [x + (i+0.5)*w, 72], 1.25);
        }
    }
}

function drawCenteredText(str, pos, size) {
    const dim = text.print(null, str, size);
    turtle.jump([pos[0]-dim[0]/2, pos[1]+dim[1]/2]);
    text.print(turtle, str, size);
}

function drawCircle(p, r) {
    draw_smiley( p[0], p[1], turtle,r);
    turtle.setheading(0);
}

function drawLine(p0, p1, curve=2) {
    const p0m = add(p0, [rand(-curve,curve), rand(-curve,curve)]);
    const p1m = add(p1, [rand(-curve,curve), rand(-curve,curve)]);
    const c0m = add(lerp(p0m, p1m, 0.3), [rand(-curve,curve), rand(-curve,curve)]);
    const c1m = add(lerp(p0m, p1m, 0.7), [rand(-curve,curve), rand(-curve,curve)]);
    const steps = 10;
    turtle.jump(p0m);
    for (let i=0; i<steps; i++) {
        turtle.goto(bezier(p0m, c0m, c1m, p1m, (i+1)/steps));
    }
}

function random() {
    let r = 1103515245 * ((++seed >> 1) ^ seed);
    r = 1103515245 * (r ^ (r>>3));
    r = r ^ (r >> 16);
    return r / 32768 % 1;	
}

function rand(min, max) { return min + (max-min)*random(); }
function scale(a,b) { return [a[0]*b,a[1]*b]; }
function add(a,b) { return [a[0]+b[0],a[1]+b[1]]; }
function lerp(a,b,t) { return [a[0]*(1-t)+b[0]*t,a[1]*(1-t)+b[1]*t]; }

function bezier(p0, p1, p2, p3, t) {
    const k = 1 - t;
    return [    k*k*k*p0[0] + 3*k*k*t*p1[0] + 3*k*t*t*p2[0] + t*t*t*p3[0],
                k*k*k*p0[1] + 3*k*k*t*p1[1] + 3*k*t*t*p2[1] + t*t*t*p3[1] ];
}

function getFontData() {
    return {"name":"EMSTech","unitsPerEm":1000,"ascent":800,"descent":-200,"capHeight":500,"xHeight":300,"glyphs":"IADEDgDAIQBwA8QESOWcBBz4AEBaBQT\/fgSi\/wBAAMAiAMAIjAXY4W4F\/ujIBRbqAEB+CdjhVgk66ZwJ+OkAQADAIwByGroJXugUCvD7AEB2EWTncBK0+wBAkgOo7qYOkO36GQ7tAEDOBGT2oA\/o9PoZ7vMAQADAJADkFuoV4OjoDTrpnAk06uYFFOxMBAjuLgTe70YFvvECCHzyAA988uwT+vHGFrjyXBdS9MYWKPawE\/73XhC2+ZALkvoEBtj6AEBsDCDgHg8XAgBAAMAlAPYYwAi+5ywGXuiMBbbqyAUU7OAG8Oy6CdLs9AuS6zAMOukYC0DowAi+5wBAPhfK5fwIYv8AQG4UGvqSE478bhSG\/kQWpv4aGGf+VBqt\/FQauvp4Gbb5XBd6+W4UGvoAQADAJgBwHEwd7vP0Gub2oheY+RATjvymDsX+lgrh\/0QHwf+MBeT+qgVu\/KQGVvoMDdzxoA8s7UAQmOqaEObnBBAG5uIOKuUqDXDl8Aqm5lYJpOiYCFzqegjM7RoJfvDSChLzTgzo9EYPpPd0EzL7hBdr\/dwZZ\/7WGmf+AEAAwCcAjAXIBSLoLgRK7QBAAMAoAK4Lrgs+4PwIVOPCBqDnRgXO62oEYPAQBFL0zgSA+EoGa\/16CFoBAEAAwCkAyg1qBDjhIAhO5HgKIuiKDA7tZg0e8YQNyvTuDGL4cguW+1oKqf2eB70AAEAAwCoA8BSoDKLvBBDo5YgOSO9iFvDsQg5+8EAQ\/PlODPrx4AZ6+VQLJPBGBVbrqAyo7gBAAMArAIYV9AtK7WwMDPQqDRT7hA3o\/QBA8gMK9oAHTPXMC470fBDu88wV0PMAQADALACGBiwGCP5qBPgBUgNyAwBAAMAtAGwMTATm9t4IKPYSDM71AEAAwC4ATATQA6L\/agSC\/wBAAMAvABATyBSs5QQQLO0MDTbylgrO9Z4HVvoQBMH\/AEAAwDAAPBmgD+jlNBIk5mgVgufeF3rqHhnM7bQZlvEeGWT2XBc4+jIUqf24EEP\/cgui\/wgHZ\/6wBPD7sQPa+EwEWPPIBWzuXAja6ZYKgucqDQbmoA\/o5QBAAMAxAFYJwgbK5QQGYv8AQADAMgCGFZIENOpEB+bnFApM5oQNcOWCD3DldhHE5jQSwujaEZLr4BBs7qYOuPKuC4b3IAjS+woFQ\/\/8CEP\/6A3F\/hYS6P3qFez8AEAAwDMAbhTyA0DobgUE6HgKTOYkDqzlOhGO5RAT6OUKFEbn7BNY6bYSVutGD0Tu8Ao88UQHMPMSDDzxQBCc8PISlvEKFDDz7BOm9doR2viIDvD7GAso\/uAGov9uBeH\/AEAAwDQAMhTgEAAAuBA4614QCudkD6zlqAy+52gGwO\/yAzDz3A+48g4V3PEAQADANQBoFagW6OV2EYLnbAzg6PwIOunsBJ7pKAWO5UwEEvMIB9byrgv68SIQ3PFWEzbypBWO9AgWhvdKFRr6UhJu\/IgOKP4yCiP\/agSi\/2oEhv4AQADANgA2GhwR6OXoDeLm8Aoc6T4IUOxKBiDv7AT08s4E5vZuBbb5ngfN\/NIK5P4qDaL\/3A\/B\/3QTYv8+F2v93BlW+pAagPhyGkb2eBnK9FwXrPQKFAr2mhC8+GoOkvpsDI78GAso\/gBAAMA3ACYW0AMo55wJCud8EMrlgBYq5VAUvudYEZDtAA+48soN4PcqDdj67gwo\/gBAAMA4ALQZgg\/o5XILvucaCRbqPgjw7MAIIO94Cpzw0Aw88V4QJPCYEoTvCBYg71oZfvCUG7LzThtA97oYMvtKFej9WBFi\/4oMov\/eCOT+5gUO\/G4FPvloBqr2eggM9BgL3PGoDB7xyBQg794XDu36GVbrNhoc6TwZRufAF0zm7BNw5YIP6OUAQADAOQDIFIAWRO68EYTvyg0k8LoJYPCGBsDvsASK7pIEjOyqBTTqVglk50gNJOb+EMrlVhPi5iwVwuhiFpLrRBZg8EoVNPSwE\/73WBHS+6YOKP5sDEP\/AEAAwDoASgawBJryjAW48gBAaAZu+yYHbvsAQADAOwCOA7EDEvNMBBLzAECSBKn9kgM3AtUCTAT4AeYFAEAAwDwAsBMWEsbuWgro9J4HQPd4Cnr5Rg9u+xwRL\/yYEuz8AEAAwD0AdBMoBVL0HBHW8gBAbgXU+QwNvPj4EeD3AEAAwD4AdBMsBqLveAo88YQNmvL4EQz0BBDs9e4MRPh4Chr64AbN\/ABAAMA\/ADIUsQPE5p4H6OX0C3DlAA9w5TQSauZQFL7n8BSA6aoUdOvyEiztuBDk7ooMvvFcCLLzOAms9K4LLvXKDS71AECKDEP\/hA1D\/wBAAMBAAGosDByQ7fYYDu0KFIru2hG68DoRfPJ2EXD0LhNq9SYWjvQ8GXzy9Bri8AwcAu+IHTzxpB988lYiGPKQJOLwJiVm7+okbuwyI1jpvCBk55QbcOWGFUjlXhDE5ooM\/ui6CZLrYgcg74wF1vLsBED3hgZu+1oKKP4eD4L\/ChQfANwZov86IIb+bCXs\/HgpuvpsLP73AEAAwEEAVBouBOH\/3giU88wLdOvoDcrliA7o5eAQvOlQFLrwxhbm9vYYT\/zWGqL\/AECeB4L2MAyI9XYR7vPqFRLzGBoY8gBAAMBCAIgdRgVq5m4FlvHmBYD4wgbB\/wwNQ\/+wE8n99hi0+6wcXPlMHQT3cByO9PYYmvJKFRjyHBGW8U4M+vEIB3zyiA5+8FYTbO6oFm7swBd66oQXXuhEFmTnLhMG5sQOSOVaCirlsQOO5QBAAMBDAC4dqBbE5iwVcOV2Ee7k0Axq5pgIWOkEBg7tsARC8BAE0PPyA4b35gX2+lwIiv1UC+T+Rg9i\/woUpv7eF638MBsa+ogdHPgAQADARAC4Gs4ErOXsBAAAegiC\/9AMKP5YEdL7DhU++fwXRvZUGtbyEhv877ga0uwaGLzpzhNk56APTOZUCwbmIAhq5uwEKOcAQADARQBCGA4V6OUKBWrm7ATu82gVGPIKBe7zjAUj\/34Y6P0AQADARgCsHKYdJOYsFabmZA+g5xQKIugEBv7oRgVw5YYGVuueB3jxngfo9CgFCvbYCTT01BK48hIbWvG6CTT0ngcu9YAHov8AQADARwAuHWgVcOXaEcrlBg6g5zYLFur8CG7spAac8AoFEPXOBCD5jAUO\/GIHKP7wCqL\/pg4E\/xAT7PzqFdj6Ghi8+JwYwvfYGELw7gxU8gYdou\/2GGDw9hi5AQBAAMBIAIQXBAaO5YwFmvKMBZ74aAYAAKoFMPOSA+7zmAj08kIOVPJoFfrxaBWs5UoVEPXMFfb6YhYI\/iAXgv8AQADASQAuBAoFyuXsBPzvzgQu9ewEmPnsBCP\/AEAAwEoAaBXIFMrlDhXu88gUPvnsE478thJH\/kAQQ\/9IDSP\/eAqm\/iYHiv3sBNL7cgNW+jMDPvkAQADASwCiF4wFaubsBITvzgSI9c4Eov8AQD4XBua4EBDrTgxs7rwH4vCqBXzy7ASm9VYJaPceD\/z5dBMO\/OQWR\/5+GEP\/AEAAwEwAGhjIBcrlyAUG5uYFuvDmBSL3jAWt\/G4F5P5IDeT+fhgj\/wBAAMBNANogagTk\/qoFfPKkBp7pgAck5j4I6OX2CSjn7gws7QQQEvO2Ep74bhQo9vYYru0GHcrl4h1q5sge+OmkH\/zvniBk9pghgv8AQADATgCWGbAE5P4KBYL2KAVI7ygFgufIBUzm9gnU6uIOPPFKFXr5uhgo\/tgYmPnYGLrwuhiO5QBAAMBPABIbvg+O5YoMJOYaCV7oaAaw60wE\/O8QBBD1yAVW+noI7PxyC4b+oA\/k\/sgUZ\/72GJb7lBsE97IbVPISG8ztnBj46SAX5ufIFIjm+BGO5b4PjuUAQADAUAByGqoF5P6qBf73BAZs7koGeupiB77n0goG5iIQKuVoFUjl+hkK504bfOgwG3Trlhnq7QgWJPCUEXjxZg0Y8uYFVPIAQADAUQDEHboY0vtyGtT50Bum9UgcQvBsG5Lrfhgi6OwT6OW+Dwbm9Aug51YJNOrCBurtbgU28moEZPaqBW77eggI\/swLwf++D14AEBPh\/+oVxf4eGS\/8Lh3F\/uQWdPo6EUz1fBAu9QBAAMBSABIbzgQG5igFNvJuBSP\/yAWE7woFXuj8CAbmrA1I5XYRyuVuFOjlqBZq5vwXvudgGFjpYhby6tQSUOyCD8ztzAsC76oF4vBuBdzx9glS9IQNyPbyEvz5ThuC\/yocov8AQADAUwBUGpwYBuaSE+Lmag5A6LoJnum8BzjrwgZu7GgGkO1iB0jvigwA8VgRuPJEFqz09hgo9pAahvdyGtr4YBhQ+5ITyf3oDaL\/ugnh\/8IGgv8KBab+agRr\/QBAAMBUAHgZ9ALK5XgKJOYcEYjmoheI5rgayuUAQO4MiOYSDNbyrgtc+a4L5P4AQADAVQCoFsgFauZMBMbukgMu9ZIDPvkKBa384AbF\/lQLI\/+gDyz9EBP8+SwVDPQmFuTu6hUk5uoVBP8AQADAVgC6GFIDJOZCDkf+vg+G\/owUVPIYGsrlAEAAwFcAuidMBMrliA7F\/pQRwvdcF3rq9hgU7LIbYPD+HyD59iKm\/uokIPmiJpbxmygk5gBAAMBYAPYYEATo5RgLhO\/sE7z4+hlH\/gBAWhlI5V4QuvAyChz4KAWm\/s4Exf4AQADAWQDwFEwEyuUIB0DotApu7EAQfPLwFMrlJhay5A4VyuW+D1L0TgxD\/wBAAMBaALQZzgTo5bgayuWEF7bqAA\/o9IAHyf3CBqb+tAqi\/wAPwf+yGwT\/AEAAwFsAlBGmDurezgQ44W4FMPOkBnYCHBHdAABAAMBcADIUUgP+6NIKsvNAENj6bhSC\/wBAAMBdAIAW8gMe4roJ\/OB8ECDgthLk3xATFOx0E3D0zhPJ\/QoU+AFAENUCrgtSAwIIkgMAQADAXgByCxAEuPIKBbrweghE7rQKou9IDXzyAEAAwF8A6ByMBeH\/9AvF\/rYSR\/6OHGv9AEAAwGAAjAUQBF7opAZ66gBAAMBhAMgU8BQu9bYSGvoeD2v9igzk\/hQKwf+kBmL\/zgQo\/kwEVvosBoL2mAjW8ooMJPCCD8buvBHG7m4USO\/wFB7xaBXh\/wBAAMBiAAIXqgXK5eYFZ\/68ByP\/8AqG\/r4PjvykFeD3\/BfQ89gYnPAaGOTuRBYm7vgRiu4kDh7x9gkM9CwGevkAQADAYwCEF\/AUwO+wE6juBBBE7hIMwO\/ACDbyKAXm9hAE0vtoBqb+NgsfAF4Q4f8KFAT\/\/Bfs\/ABAAMBkAMwVpBWI5g4V4vDIFIb3DhXs\/CwVQ\/\/wFNr4WBGt\/JALQ\/+AB4L\/sATo\/dADtvnmBab18Ao28oIPePHyEvrxLBVS9ABAAMBlAMgUbgVw9FoKUvQEEDT0sBOU81AUNvLOE+LwmhAI7ooMCO7eCGbvSga+8WoEgvbQA1z5sQNQ+8gFCP44CcH\/Kg2C\/1gRhv5QFAz9aBW0+wBAAMBmAIQX3hfU6m4UKOdeEEjlrA0M5XgKKOf8CHrq\/AgI7pwJWvHwCtr4cgsE\/3gKCvYzAyL3\/hBM9VwXcPQAQADAZwCGFaQVzvUQEzj6ag7o\/VYJwf\/IBcH\/sATo\/SwGPvnYCbLzBg5C8LgQ5O50E0jvpBUA8UQWdgLMFVwIbhQSDLYSiA4cEdwPAA9AEGYNXhD8CEAQAEAAwGgAMhRuBejlCgUY8m4Fwf9oBmr1fgl48QwNSO9AEGbvcBI88aoUavUOFXr5LBXk\/gBAAMBpANgJCAco57wHKOcAQAIIiu5cCEf+GgkE\/wBAAMBqAIYG1QJG53IDauYAQLED0uyxA9DzLgTw+5IEngcuBDAM9AIGDnoBiA5eAIgOLP3KDTL7MAwAQADAawCSE5IDrOVMBI70TASC\/5IEZPYCCHbz4BCQ7ewE7PWcCVz5MhQj\/wBAAMBsAOQHhgYM5eAGRvYmB2f+AEAAwG0AFiHOBOrtCgXh\/4YGlPP8CN7vSA0I7twP6u2UEQLv8hJU8nAS4f\/OE1jzxhb87\/QaCO6CHgjuvCAC7zQhGPJcIeD3mCGC\/wBAAMBuAPISkgNs7rEDQ\/+SA1jzIAhg8IoMRO5kDwjudhFE7tQSwO\/OEwz0sBOi\/wBAAMBvAKQVhA2u7ZgI3u8EBpTzTARA9ygFL\/zCBuT+9AvB\/0AQpv6qFNL7IBdA90QWlvFQFGbvFhII7oQNru0AQADAcAAyFC4Exu5qBMQOTAQ88cAIxu7KDa7tdBOK7koVPPHwFGr1LhNE+KAPUPv0Cyz9kgQE\/wBAAMBxADIUjBQk8HQTRO64EOrtzAve7z4IMPPIBSL3kgS6+uwECP4gCOH\/rgui\/9wPyf0WEpb7khP4+KoUNvJKFdwPAEAAwHIAbhQKBa7tkgRi\/8gFavXACJbxigxm76APJu7+EAjuDhVs7oYVxu4AQADAcwA0EpITSO9SEgjupg4I7ngK5O6kBuLwCgV28woFRvakBob3MgqG9wYOyPZ8EOb2cBKk97ATvPhWE5L6gg+K\/ZYK5P68BwAAhgbh\/wBAAMB0AF4Q0grK5XILwf8YCzzxkgM88ZQRHvEAQADAdQCSE0YFCO5MBLLz8gM4+m4FCP56CKL\/rA1D\/5oQa\/00Eq38zhN0+vAUHPgKFJDtShWC\/wBAAMB2AA4VzgTM7SoNyf0kDmL\/mhB6+eoVbO4AQADAdwBGHkwECO4IB8j2ugkv\/JALBP9ODIb+vg\/Q814QePH4EZryQhgo\/twZI\/98HybuAEAAwHgA8hIQBIruigyk924Ugv9ODKT38hKK7ooMwveeB0v97ATh\/wBAAMB5AAIXagRm7xwRegEaGALvVAuaEABAAMB6ALATsARE7igFRO5QFGzuDhUC7xwRdvP2CdT5qgUI\/kYFQ\/9WCUP\/yBRD\/8wVov8AQADAewCsDV4Q5N9qDgLgMAw44fAKcuOWCmrm0gp065YKMuxiB+TuSgZC8CwG3PHgBpTz9gns9a4LaPfMC0\/8igx+AEIOtQJAENADFhIQBABAAMB8AG4FbgUS5MgFegEAQADAfQAiEAQG5N8aCaLgGAuW4q4Lvuc2C4zsag6K7poQYPAcERjy3A8S8xgLyPY2C5L60goE\/1YJmgGAB\/QCTATyAwBAAMB+AJQRTATo9MgFmvKAB1rx3gha8fAK1vKEDej0gg+O9BwR7vMuE+LwAEAAwA=="};
}

function getWord() {
    // hehe
    return [116, 117, 114, 116, 108, 101, 116, 111, 121].map(c => String.fromCharCode(c)).join('');
}

////////////////////////////////////////////////////////////////
// Text utility code. Created by Reinder Nijhoff 2024
// https://turtletoy.net/turtle/0f84fd3ae4
////////////////////////////////////////////////////////////////

function Text(fontData) {
    const decode = (data) => {
        const b = atob(data), a = new Int16Array(b.length / 2), g = {};
        for (let i = 0; i < b.length; i+=2) {
            a[i / 2 | 0] = b.charCodeAt(i) | (b.charCodeAt(i + 1) << 8);
        }
        for (let i = 0;i < a.length; i++) {
            let u = String.fromCharCode(a[i++]), x = a[i++], d = [], p = [];
            for (;a[i] !== -16384; i++) a[i] === 16384 ? (d.push(p), p = []) : p.push(a[i]);
            g[u] = { x, d };
        }
        return g;
    }
    const rotAdd = (a, b, h) => [Math.cos(h)*a[0] - Math.sin(h)*a[1] + b[0],
                                 Math.cos(h)*a[1] + Math.sin(h)*a[0] + b[1]];

    const data = Object.fromEntries(Object.entries(fontData).map(([key, value]) =>
        [key, key === 'glyphs' ? decode(value) : value]));

    class Text {
        print (t, str, scale = 1) {
            t && t.radians();
            let pos = t ? [t.x(), t.y()] : [0,0], h = t ? t.h() : 0, o = pos, s = scale / data.unitsPerEm;
            str.split('').map(c => {
                if (c == '\n') {
                    pos = o = rotAdd([0, 10 * scale], o, h);
                    return;
                }
                const glyph = (data.glyphs[c] || data.glyphs[' ']), d = glyph.d;
                d.forEach( (p, k) => {
                    t && t.up();
                    for (let i=0; i<p.length; i+=2) {
                        t && t.goto(rotAdd([p[i]*s, p[i+1]*s], pos, h));
                        t && t.down();
                    }
                });
                pos = rotAdd([glyph.x*s, 0], pos, h);
            });
            return rotAdd([0, 10*scale], pos, h);
        }
    }

    return new Text();
}

//
// copy paste from https://turtletoy.net/turtle/f51d24a066
//

function draw_smiley(x,y,t, r) {
    draw_imperfect_poly(x,y,t,30,r);
    if (r > 2) { // draw eyes and mouth
        t.right(Math.PI/2);
        t.penup();
        t.forward(r/2);
        t.pendown();
        t.forward(r/6)
        t.left(Math.PI/2);
        t.penup();
        t.forward(r/(1.5+random()));
        t.pendown();
        t.right(Math.PI/2);
        t.forward(r/6)
        t.penup();
        t.forward(r/(1.2+random()));
        t.pendown();
        t.right(Math.PI/3);
        
        const steps = Math.ceil(r*10);
        draw_imperfect_line(t, r, steps, 0.05, 2);
    }
}

function draw_imperfect_poly(x,y,t, c, r) {
    const side = 2*Math.sin(Math.PI/c) * r - 3/c;
    const side_div = 10;
    const rand_angle = Math.max(0.02,0.1/c);
    const steps = Math.ceil(side*side_div);

    t.radians();   
    t.penup();
    t.goto(x,y-r);
    t.setheading(0);
    t.circle(r,(-random())*Math.PI*.3);
    t.pendown();
    
    t.right(Math.PI/c);
    for (let i=0; i<c; i++) {
        draw_imperfect_line(t, side, steps, rand_angle, 0);
        t.circle(3/c,Math.PI*2/c);
    }
    t.forward(random() * r/10);
}

function draw_imperfect_line(t, side, steps, rand_angle, add_angle) {
    for(let j=0; j<steps;j++) {
        t.forward(side/steps);
        t.right((random()-.5)*rand_angle + add_angle/steps);
    }    
}