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+=12345) >> 1) ^ (seed)); r = 1103515245 * (r ^ (r >> 3)); r = r ^ (r >> 16); const mod = 1 << 20; return (r % mod) / mod; } 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); } }