en.wikipedia.org/wiki/connect_four
This is a Turtletoyed port of Sebastian Macke's Connect4: simulationcorner.net/index.php?page=connectfour
#game
Log in to post a comment.
const seed = 'Change this text = change computer behaviour (do not change during a game!)'; //type=string const moves = ''; //type=string const moveArr = moves.match(/([1234567])/g) const legalMoves = moveArr? moveArr.join(''): ''; // You can find the Turtle API reference here: https://turtletoy.net/syntax Canvas.setpenopacity(.7); // Global code will be evaluated once. init(); loadHatcheryNamespace(); const turtle = new Turtle(); const polygons = new Polygons(); // Seedable random number generator by David Bau: http://davidbau.com/archives/2010/01/30/random_seeds_coded_hints_and_quintillions.html !function(a,b,c,d,e,f,g,h,i){function j(a){var b,c=a.length,e=this,f=0,g=e.i=e.j=0,h=e.S=[];for(c||(a=[c++]);d>f;)h[f]=f++;for(f=0;d>f;f++)h[f]=h[g=s&g+a[f%c]+(b=h[f])],h[g]=b;(e.g=function(a){for(var b,c=0,f=e.i,g=e.j,h=e.S;a--;)b=h[f=s&f+1],c=c*d+h[s&(h[f]=h[g=s&g+b])+(h[g]=b)];return e.i=f,e.j=g,c})(d)}function k(a,b){var c,d=[],e=typeof a;if(b&&"object"==e)for(c in a)try{d.push(k(a[c],b-1))}catch(f){}return d.length?d:"string"==e?a:a+"\0"}function l(a,b){for(var c,d=a+"",e=0;e<d.length;)b[s&e]=s&(c^=19*b[s&e])+d.charCodeAt(e++);return n(b)}function m(c){try{return o?n(o.randomBytes(d)):(a.crypto.getRandomValues(c=new Uint8Array(d)),n(c))}catch(e){return[+new Date,a,(c=a.navigator)&&c.plugins,a.screen,n(b)]}}function n(a){return String.fromCharCode.apply(0,a)}var o,p=c.pow(d,e),q=c.pow(2,f),r=2*q,s=d-1,t=c["seed"+i]=function(a,f,g){var h=[];f=1==f?{entropy:!0}:f||{};var o=l(k(f.entropy?[a,n(b)]:null==a?m():a,3),h),s=new j(h);return l(n(s.S),b),(f.pass||g||function(a,b,d){return d?(c[i]=a,b):a})(function(){for(var a=s.g(e),b=p,c=0;q>a;)a=(a+c)*d,b*=d,c=s.g(1);for(;a>=r;)a/=2,b/=2,c>>>=1;return(a+c)/b},o,"global"in f?f.global:this==c)};if(l(c[i](),b),g&&g.exports){g.exports=t;try{o=require("crypto")}catch(u){}}else h&&h.amd&&h(function(){return t})}(this,[],Math,256,6,52,"object"==typeof module&&module,"function"==typeof define&&define,"random"); Math.seedrandom(seed); const preLoadedHatches = Array.from({length: 7}, () => Array.from({length: 6}, () => [ new LineHatching(Math.PI / 8 + Math.random() * Math.PI * .5, .2, .2), new LineHatching(Math.PI / 8 + Math.random() * Math.PI * .5, .5, .3) ])); Math.seedrandom(seed); const texts = { logo: new Text({"name":"EMSSwiss","unitsPerEm":1000,"ascent":800,"descent":-200,"capHeight":500,"xHeight":300,"glyphs":"IADEDgDAIQDyEhoYjN3kDEb2AED2CWv9EAko\/jIK5P4OCyj+AEAAwCIA0gpcCFjasARy4wBAHg862pALcuMAQADAIwASKvoZXOCqFArnIhCQ7XILLvWABw78AEBEJVzg\/h8K52wbkO3kFi711BIO\/ABA3ghc6uQleuoAQOwE+vGYITbyAEAAwCQAfhjkFvjp3hdk5\/wXcOVcFzDkCBZU46oU+uJSEvri3A9s5KYO6OWsDQTo6A0W6gAPkusiECztFhJm73QTlvEKFLLzzhPs9dQS\/vciEBr6hA26+pALuvr8CNT5RAdE+IYGyPZoBqz0pAZY80QHfPLkB5bxAEBEFgjf\/AgXAgBAAMAlAPYniA5Q3TIKeOKAB8TmpAba6SYHzuvkB1DsVglu7MwLdOumDjrpFhLu5M4THuLIFIDfqhTI3QoUktxSEjTbohdM3Egcktw6IHTckCTy2\/4nNNvSKpTaAiaM3egcrOXIFCztTgxG9rwHlvv0AjsBAEDiHULwVBo09D4X2vgIFm78RBZn\/gIXov9gGKL\/ThuG\/sgeUPu8IGL49iIu9bQjuPK0Ix7xFCPA71YiAu96IWzuniAm7gBAAMAmAIQmXh8S8ygeZPYqHFz5PBmO\/BAT4f\/iDn4ANgvh\/54Ha\/3gBtj6YgeC9vYJ1vKsDULwfBCK7jIUUOyiFxbqchqC5wwcSOVIHBjj1hp+4d4XOOHMFTbjyBRw5fAUpOggFyDv4B8k\/24jUgPkJewEPShGBTQqkgRsLPQCwi78AJ8vQ\/8AQBwR+PigD2T23A+s9LwRdvPsE9bybBu48tQhEvMsJDDzzCQS8wBAAMAnAAgHOAle2SgFkOMAQADAKABYEc4iyNbQG8DYqhRM3MQOouAaCYjmbgVu7LEDuvBXAuD31QLo\/QoFvQCAB9gB2An4Aa4LWgEAQADAKQBYIHQT59aAFojWWhmF104bHNoMHCbfuBri5lwXIO80Eqb1rA22+d4IrfzQA2L\/JP\/cAJb7mgEAQADAKgAcIJgSXtkSDMDvAEDgBgjfYBg06gBAMwM06pQb6t4AQADAKwDwFNoRgOlaCvD7AEBMBNzxuhgY8gBAAMAsACAIFAPN\/NADhv6xA+H\/dgIXAp4AEAQAQADALQDcD3IDLvUwDEz1AEAAwC4AugkXAkv9OwHo\/TcCpv4zA+j9AEAAwC8A3hekH4DYQBBW69gBDP1D\/9wAAEAAwDAAhBfMC2rmngd66uwEAu9SA4701QKe+PIDMPwEBij+XAjk\/tIKR\/6EDY784BA++S4TiPXwFLrw6hXO62gVpOgKFMrlmBLu5JoQsuQeD7LkZg1w5cwLauYAQADAMQCgD4YGHOlkD\/Tj7gxM5koGrfxMBEf+ov\/F\/lQL5P4aCcX+hgZu\/ABAAMAyAMgUwAgQ65wJBuYMDU7kdhHW4woUbORoFejlCBZ86GgVVuvUEoruQg4S834JQPfcAOT+dgJH\/gQGKP4UCkf+hA1H\/r4PS\/1YEbT7VhO8+ABAAMAzAFAUgAcQ6+QH1Or8CAbmVAuy5KAPEuQWEpTkbhSs5fAUQOhuFFzqthJu7CIQCO5IDWbvfgn876wNfvBAEDzxvBG48vgRpvWaED75pg4w\/BIMCP5+CeT+LAZD\/1IDBP\/4Aej9WgHs\/FcCvPgAQADANACWGfwXPPEKFM71Wgoo9rUCRvZIDfLq2hFM5sgU7uTuDOz8nAlH\/mgGxf5SEuT+AA\/J\/SoN7PwAQADANQBoFYYVNOpCGNDkkAvQ5KQGJPDYCWbvhA1I7+AQYPCYEprycBJk9rgQ1PkkDm78bAwI\/jgJJP8EBkP\/sQME\/\/gBKP56Aez8VwI++QBAAMA2ACwVpBWk6KgWJOZiFtDkNBJy4wwNSOU4CXzowgb26+wEJPByA3D0tQKe+FIDzfwsBqb+GgnF\/k4M6P2gD1D7OhFc+VISKPZwEvTyOhG68KAPSO8qDcbuugmE7+QHnPBoBpbxbgX08nID7PUAQADANwBWE2gG+OmGBp7p3ggM5cwVSOU+F9DkthJ66moOZu\/YCaz0BAb8+fQCpv4AQADAOAAmFl4QVONODLLk\/AhA6J4HdOtcCETu2AmE76YOlPOgD871Rg+e+KwN0vu0Cij+mAgk\/6oFJP+SA+T+uQEs\/VoB2Pr0AkD3jAXK9D4I1vJkD2zucBKw61AUnunIFObn8BRI5ZITEuRSEpDjXhBU4wBAAMA5ACYWtQL8+fgBa\/2wBAT\/wAim\/ioNMPxAELz4mBIu9QoUPPFKFWzu6hV068wVfOiqFMrlLhNs5DoR9OPEDhLkTgzQ5D4IBOikBrbq5gWo7uAGWvGYCPTyNguy80IOMPPgEL7xmBJ+8DIUJu6kFdTqAEAAwDoAuglcCK7tgAdE7noIAu9WCUTuAEAQBKn9MwNH\/i4EBP8KBUf+AEAAwDsA2AkaCa7tPghE7lYJAu8yCkTuAECSA0f+CgVr\/ZIE3AC1Ai4EfgAEBgBAAMA8ABATvBEc6a4LaO0KBb7x9gmI9e4MgPgGDtT56A26+gBAAMA9AAgWEASK7lAUqO4AQFcCKPYuE0b2AEAAwD4A8hJKBhzpWgpK7U4MIO+CD\/rxegjs9WoEvPi1Ajj6FAO6+gBAAMA\/AAwchgap\/aoFR\/6kBgT\/gAdH\/gBA9gm44xgLgN8SDA7eyg0K3XYRLtyMFLbb5BZM3B4Z5t1UGiDg+hk244QXpuaqFKToWBF66ugNjOz0C0TuFAok8MAIVPJ6CKz0wAgo9hoJBPcAQADAQAC2MBoixOZMHabmfhiC50oVtupSEibuuBD68dwPEPVeEKr2WBFk9lAUiPXAFzbyeBlC8O4baO3IHl7opB8o59wZ1vL2GOz1Hhki9wYdBPeYIQr2QCcw8zQqou\/uK3TrLCy+51ArlOQ9KH7hMiNE34IeaN72GMzezhM+4IQNuON6CP7oBAYC74wF0PPCBvj4fgmO\/AYOBP9WE+H\/uhiC\/3Acpv68IOz8tCO6+oolnvgAQADAQQBoM34AvPii\/zL7wf\/J\/XoBYv+SA8H\/4AaC\/zYLS\/24EHr5Ehuc8NQhnulsJazlPSj64m8rPuCDLuremjDI3dYxbt0QM27dqjRQ3YI2Ltz8N5jb9TlS25431NtnNezcjjPM3towluKiLuLmiyyY6pMqAu++J7Lz5CXa+CYltPsmJej95CXk\/t4mBP97KCT\/NCqG\/mwsjvxkLpL6+TBA9wBAOAno9PwI0PMUCvrxEgwA8UYP3u80EoTvyBRm7\/YYhO+mHd7vmCHe764kwO8CJsDvgCfk7gBArBzi5pQbSOUqHFriRh4C4BoiaN67KIzdyiyq3TgxjN0AQADAQgCMI0wEBuYUA2zk1QIY49UCOOFSAyDgzgQI3wgHLN72CardZg1u3f4QMt3wFIzd3hdQ3cocjN1AH6TevCCi4DQhsuT+H4LnxB3a6bIbdOucGNLsRBZo7bgQbO5YEa7thBfq7RIbqO4oHn7wgh7u88Qd4PdsG\/b62Bgs\/aoUJP\/aEeH\/7gzh\/xQKQ\/+AB2f+SgZr\/YwFFPsuBOz8SgZr\/QBAYhbI3ZITxt8cEdbjsQPw+xcC6P0\/AIL\/Yv8fAGb+PwAAQADAQwBKJDogBub4IBLktiH24ZghXODgH6TeKB6q3U4bCt0IFozd3A8C4LQKTuSkBsLoTATS7LUCAPHYAez1VwL8+WoES\/3CBgT\/MgoAAKAPPwDwFEP\/lhnJ\/YIeuvqeIJ741CFA9wBAAMBEADgnkgPK5XYCcuNXAtjh9AIg4HIDYt\/IBYbePgjm3fQLbt0cEVDdIBcK3dAbUN3+H0rekiJi38wkuuFmJiTmAiba6YolbO6SInD0QB\/a+PQabvzGFmf+dBNi\/6APwf8SDMH\/nAmC\/4AHxf6GBij+RgWS+tADbvwsBij+AEBcFzLdsBPq3pQR\/OBuBZ74zgRW+vQCzfxaAQj+fgDk\/iT\/wf\/J\/QAAAEAAwEUANCGkFVDdcBLq3nYRIOAQBIb3dgJ0+lcCbvxSA6n9KAWG\/lQLYv\/+EEP\/8BSG\/iAXKP5aGc380Bu6+sgeQPcAQG4F6OXyA3jiTAQg4MgFCN8aCQ7ehA2M3bIbCt3aIIzdtiEI33ohwOCAIBjjAEAUCkrtPheu7fYYaO1UGm7sAEAAwEYAzCRKJFDdXh\/M3i4d\/OBgGBbqgg+6+oQNLP3wCgT\/eggAAOYFXgBSA8H\/2AHF\/n4ADP0\/ADL7HwBE+ABAyg3i5hIM7uRUC\/riNgsa4YQNpN5eECze7BPI3VQaMt2XKVDdjyuM3Sktqt3lLWje5S3k3wktWuLKLDbjAEA+F0rtbiOQ7ZAkzO1oJKjujCOc8DIjHvEAQADARwCQJOAfJOYWIfTjtiGc4RYh5N9eH+bdGBrs3LgQYt+WCvTjbgV66vQCfvB6AaT3lQIU+ygF6P3YCZ4AoA+9AIwU5P5yGvD7yhza+EAfLvUaIuTu6BzJ\/dwZhgaMFMQOiA7UEsAIUBQoBQoUegHaER8A3A8fAE4MAEAWEu7zdhF88nYR\/O8uE+TuqCUI7gBAAMBIACwzEgyO5bQKGOO0CsDgigwI3x4PLN7UEqrdCBaq3QoeUN3UIVDdKB7I3RIbGuH8FwbmkhOK7iIQpvWoDLr6Wgpr\/cIGYv+SAyT\/HAGG\/qb+bvxH\/pL6KP4i9wBAwjZw27Iy7Ny6MObdGSlW6\/YihvcaIpL6ViLN\/BQjhv7qJCT\/HSgk\/\/Eqyf2DLlb6ujBA9wBAtApm70gNqO4KHmbvMiNI72YmRO4AQADASQBcF9oRSOVeEJbiXhDG31ISht7IFMjdiiVu3cgeaN6UGzjhuhgG5jIU3u\/+EGr1rA10+pYKzfyAB4b+8gOG\/loByf3B\/w78xf4a+qb+HPgAQADASgCEDSwG4uZyA3LjkgOE4EoGpN6uC4zd5BYy3RATYt\/gEBrh0AyA6WIHpvUuBOz8AAD0AjD8wgZi+D4IiPWYCBjyJgd+8M4EJPDYAQBAAMBLACAmOAkG5oAHGONcCALg2Amk3jAMLN6gD4zd3Blu3eQWyN3yEt7gXhDu5JgImvJ2Amv9Q\/\/B\/\/wAZ\/6xAwj+UgNP\/ABAQC9E32ctyN09KA7eziLA4JQb7uRwEpjqNgvA7yoNbO52EQT3pBVu\/HgZZ\/6OHGf+fB9u\/LYh\/Pm0I0D3AEAAwEwAdCIGHSrlZB6W4qAext8GHQ7e+hns3IYVyN2YEsDgvg9w5agM9uvYCXzyngf+924FMvtyA478ngDo\/Uf+ov9H\/kf+sATo\/aAPKP5+GEf+jhxL\/cocT\/zoHDL7rBx0+pAawvcAQADATQAeN9wPpuZmDTDkSA0a4WoOqN+4EKTezhPI3UQWjN3aIFDdxB0I33gZ+uLGFljpLhMA8b4P5vYwDPb6eghH\/s4E5P4XAkf+BP+W+4b+uvpH\/oD4AEAEH0reph0a4WodpuZqHcDvTB1M9UwdpPesHD75Thsy+\/UxcuMcOOzctTlw2ys8vNrVObbbYzYI3xAzEuQpLYTv9imq9rsoUPvaKKn9kyok\/8os5P5bMG78czIa+qo0QPcAQADATgC+LSIQxOagD8TmhA2444QN3uBGDwjf1BLm3eQWjN22IW7dtiHI3cQdCN+yGz7gtBmW4t4XRudKFWjtthJY83wQpPcMDZb72AnJ\/WgGBP\/0AsX+Q\/+t\/Kb+uvoo\/mL4AECOHITg6Bww5FAjPvlsJWv9ziLB\/+QlevnOKzTqmjDk344zNNvGNeDZoja42Tw44NkYObzaGDlw2wBAAMBPAIolnBiw3FgRyN3wCn7hLAbi5jcC0uxeALjyPwBi+LkBMPywBGf+\/AjB\/+gNwf\/wFMn9Lh3g93QiuvDqJJ7pJiVO5LQjXODgH8jd6Bzs3JwYsNwAQADAUAA4IroYUN1KFYbedBMg4IwFGvoQBE\/81QLo\/TsBBP8E\/+H\/9ALo\/eYFa\/3mBZj5AEBqBKzlFAMY47EDxt+qBWjeFAqq3XASbt2WGTLdgh7I3Zgh3uDUIZTk2iAi6EYedOswG5DtwBfk7sgUZu\/cD4TvAEAAwFEAqCXcGbDcdBMy3XILwOBGBQrnuQEs7aL\/0PMAAPz51QJr\/eAGJP\/wCoL\/BBBD\/6oUqf1aGTL7oB4K9nQiJPDMJBzpkCS4484i5N+AICzeLh0y3dwZsNwAQJoQJP9uFOYF\/BeuC1QaxA7oHGQP\/h8AD\/AjZg0GJ9IKAEAAwFIA8CiSBKzlUgM24\/QCOOGxA+TfBAbM3lYJyN0GDm7dyhyM3RwgpN7UIR7i1CEM5TQh5udAH9TqBh0O7ZYZbO7MFUjvfBBm774P3u\/OE8L3AhdP\/HIaxf5kHgT\/+CCK\/fAj2PqEJkD3AECcGIzdyBSk3rYSGuHIBdT59ALo\/cH\/4f\/QA0v9yAVr\/YwFuvoAQADAUwBwHEgcDOXEHZzhCh4m37Ib7Nx+GEzcCBZM3FISDt4EEOTfxA6442QPBOiSE8ztwBcu9SAXdPrwFGv9FhJi\/8QOngAaCQAAkgQo\/pUCbvw7AXr5WgHO9bUCWPMAQADAVAD0GhQjRN\/KHBrhchq44xoYfOhoFYTvthLo9P4Q+PgqDWv9OAnB\/8gFXgAzA8H\/HAFH\/uH\/0vvB\/yD5AEDYAeLmov+U5OT+luLF\/vzgov\/G30wESt72CYzdoA\/m3W4jqN\/+J8bfVCqo348rLN4AQADAVQCQKb0AKuWC\/zziJP\/e4L0ApN4QBIzdYBgy3ZITLN68EVzgyg3C6JwJ3PFoBj75KAW0+84Exf6AB+H\/8Aok\/\/4QMvtEFqb19hjW8iocRO7eJ0retilM3GAnht7SI6jfGBpS9N4X8Pt+GKb+Thvh\/+IdQ\/+AIOj90iP2+iEnQPcAQADAVgAaIhwBcOWC\/1ri4f+o31cCDt4KBYzdfhiM3QBALhOM3Q4VrOVuFGjtthLg99wPmgEcEZ4Achru8\/YivOnlLUTfMDOY28I2dtq1OZTakjqY25I6UN3VOaLgAEAAwFcAuDigD4zdVhPS4lAUMuwuEy71QBAAAPgRHwBCGKr2\/h+w66ImuOMxKyDg4i6k3lgxLN7RMubdZy3G37Yp0uIGJyjnJiWo7q4kZPbSIxT78iGeAM4i3ADeJ5j5ZC7i8Ak1vOmyOrjjXj9E3yNGNNv3SHba0Ep22itM+NqJTEzcaUxK3sxLhOAAQDsBcOVeAFTjwf+E4L0AaN7yAw7e\/Aiq3fYYjN0AQADAWACuJAQQSt6gD+7kuBAs7XAS6PSkFdL7YBhn\/rIbR\/5kHjD8WCD8+TgiQPcAQI8rPOLKLIDfyiyq3RErsNzeJ3TcFCOk3o4cEuQsFdTqbAzK9PQCLP3Y+noBRPj4AYb3fgCk94b+\/vfo\/Zj5CP4AQL0AKuVi\/5biov\/k354A6t5XAizeRgWM3boYyN0AQADAWQCsHL4PDt5SEmDhsBPm5\/ISzO2UEcr0AECTKqLg0irI3bYpTNyiJkzcViJi38QdlOT2GBDrzhMS814QgPgqDc389gli\/0oGfgA3Ah8AHwCm\/qb+bvyG\/pj5AECSBHDlUgOW4hQDwOAuBCbfhgYs3ngKbt38F8jdAEAAwFoA7hveCAzlngeW4p4HIOB6CAjfNgss3iIQjN28IMjdegj08lcCnvhH\/qn9bvw\/AI78HAGK\/RwBAAAw\/PQCGvogCGL4Egxi+HwQ9vqoFtgBSBw+CFwhbAyQJAYOHSgkDnQqqAxvK\/AK7iuYCG8rwgbxKsgF+SiSAwBAAMBbAMYWyB7a2oQXOtqwE0zcHBHA4AAA7AQwDAoFJA6SBABAAMBcACIfov+42UgcvQAAQADAXQCcGKgMNNswG9raPgj0AsIGagRGBQoFmgFGBc38CgXw+0YFAEAAwF4AdBNyA+Tulgp66kAQ5O4AQADAXwBWCfwAyf1XAqb+cgOG\/nYCiv0AQADAYABqDjsBbOR6CFjpAEAAwGEA+BESDOTuRAdE7hcCHvHJ\/cj2bvuO\/DD8CP6G\/uj9KAVi+MIG5vYaCRLzWgoA8VoKZu+oDOTuwgae+G4FMPwKBSj+Sgam\/jgJa\/0MDRr6gg9A9wBAAMBiAIgOiv1A914A6PT0AjbygAc66VQLNuO4ELDc6hX+2boYmtmQGpTaEhsQ3ABAlQJ88oL\/vPjp\/W78CP5H\/mL\/BP8XAiT\/4Abw+\/YJwvcSDDT07gw88RIMZu+0CkjvpAZ88uwEjvT0Agr2AAB6+QBAAMBjAAYOMgr08rQKYPC6Caju5Acm7sgFqO5XAgDxJP9w9I78+Pi0+yz9a\/0k\/70ABP\/sBEv9\/Aga+rQKYvhyC0D3AEAAwGQAdhFAH\/jajhxe2fwXvNqSE0Tfvg8k5lQLiu6YCLLzSgYi93YC2PpeAI78qf0I\/vD7R\/5u+6n98Pv8+WL\/DPQ3ArrwagRI7wQGRO56CETuOAnG7jIKYPDmBZj57ATs\/G4Fhv68B0f+2AnN\/AwNtvkAD0D3AEAAwGUA4g4E\/yL30AOC9oAH6PS6CbjyFApg8LoJ5O4CCGzukgRm770AuPKp\/eb2MPwU+278S\/2p\/Wf+ngBD\/5IDhv7gBs382Al0+mwMQPcAQADAZgBUC04bENwSG5TaPBm42UQWuNlWExbbuBAy3QYOGuHwCkbnngeo7kYFcPT4Ac38gv\/YAY784Ab4+DIKZPbMCzDz7gwe8QwNiu5sDDLslgp0634J9ussBgBA0APk7i4E5O6oDMbuyg2K7gBAAMBnAIQNTgyo7hoJfPJoBkb2NwK6+n4Arfws\/Sj+0vtn\/hT7LP1P\/Fz5R\/4o9j8AsvNSA2DwjAXG7gIIiu4yCuTuNgvk7oYGuvq5AW4FDP3uDD75HBGO9M4TJPBQFBDrmBI66bgQpOhkD8LobAwAQADAaAC8EbgasNyQGhbbfhhA2YwUfNmaENrakAss3mIH0uIQBKTogv+a8rr6hv5Q+4b+JP+e+LUCUvQEBgDxXAii7\/AKqO7wCiTwuglU8m4FdPpqBE\/88gPo\/c4E5P6kBsX+OAnJ\/ZAL0vtGD0D3AEAAwGkAzAuK\/UD3\/AAS8\/IDQvBKBibu7AQk8OH\/tvmG\/gz9hv5n\/oL\/xf65AUf+bgUw\/IAHmPlWCUD3AED2CdDkOAlM5tIKTOY2C+7kAEAAwGoAXAhWCSbuSgaE7ygFYPA3Avj4S\/3QAz75cgvO9YIPlvGYEmjt7BPC6AoUTObyEjDkHBGQ4+IOuOOoDABAnAmU5N4IBuaWCiTmGAuy5ABAAMBrAD4XlBu225QblNq0GXzZaBU62uAQUN2EDfzgGgki6EYFQvDVAub2ngCW+yT\/hv4\/AEf+UgP4+AII0PNUC5zwQg4C7wQQZu98ECTwAEAmB1jzPgjU+VYJqf2QC6b+iA4I\/pQRMvvIFED3AEAAwGwArA2K\/UD3pAbk7jAMOum4EJbiEBPm3TIUmNsyFFjazhNe2S4TItlYESLZHg8021QLhODIBfLqegGU8wT\/+Pim\/s38HwCG\/rEDR\/5iB5b7NgtA9wBAAMBtAOIdiv1A9\/gB+vGwBEjv4AbG7pIElvGaAbz4Yv\/k\/nYCwvdiBxLzFAri8O4MAu9qDgLvxA7e79AM1vIaCRr6vAcs\/SAIyf0yCvj4Bg4Q9ToRNvJWE0LwpBUg7+QW5O6EFyTwShWy8xwRDP3aEeT+DhWp\/UIY2PpsG0D3AEAAwG4ADhWK\/UD3LgTk7m4Fxu7gBkTu7AQk8Gb+5P4E\/+T+cgNA9z4I+vEYC97vhA3k7uIOxu7iDsDvTgx285gIUPvkBwz95AeG\/jgJQ\/8YC4b+Zg3N\/JgSQPcAQADAbwC+D8gFxu7cAHzyqf3m9vD7MvsO\/Kn9S\/3F\/n4Apv5qBJb7RAcc+BoJyvRaCvrxeAr87\/YJ5O56CAjuYgcI7sgFxu4AQAoFUPuMBVD7wgbw+xQKVvpIDUD3AEAAwHAAhA3SCuLmWgrK5cAIjuXIBSjnVwJ66gAAwO9H\/qb1tPuW+9r42AEu9X4JvvEGDg7tFhKA6ewT0ORuFLrhdBMI3xwRaN6gD0reMAwAQEf+yPbVAlTypAaE73oI5O5+Cbrw5AeO9CgF+PjyAzL7+AFL\/eH\/hv6K\/WL\/tPsk\/zL7iv0AQADAcQDgEIQNqO6WCsbuYgdE7tUCJPAAAJTzyf3I9tL79vpQ+yz98Ptn\/kv9hv7F\/uj9\/AAw\/DMDdPoKBZ74\/AhY83gKYPB4CiDvzAsC7xAEhv5H\/vAKuvrOEw788hJeALQKCgXQA1QLMvtCDkD3AEAAwHIAJA6K\/UD3EATk7iwGqO5iB8ztCgVg8Ib+Q\/8k\/yT\/kgMK9pgIYPDSCkjvEgzG7kgNAu\/EDjzxag428ugNWPMAQADAcwCuC94I1vKcCZzwegiK7uYFCO7yAyDvdgJg8LkBfPIUA2r1qgWA+IYG1PmGBs38TAQk\/3oBPwCm\/oL\/rfyG\/m77zfxu+xT78Pu2+QBAiv1A9\/gBePEAQADAdAAMDYr9QPdaAVjzLgT870oGzO0aCebn9glq5nILSOWSBDbyOwGe+B8Abvzh\/yj+OwGm\/pIELP2ABzL7lgpA9wBAOwEC71cCqO5ODIruAEAAwHUAPBmK\/UD3uQF88nIDuvDIBUjvCAeo7oAHSO9oBtzxWgEU+34A6P2eAKb+NwLk\/kwEKP4IBzD8Vgl6+U4MZPYADxjyQBBI784TRO5YEUjvJA5S9K4L\/PmQC2v9Zg2G\/poQS\/10E1D7xhZA9wBAAMB2AAQQiv1A9\/gB3PGSA\/zvBAaK7vIDHvFXAmr1egEy+7kB6P2SAwT\/SgaK\/ZYK+PgqDaz0Qg6+8aYO3u+EDSbuAEAAwHcASByK\/UD3lQLe77UCZu+MBQLv8gN+8DcCgvY7AQ78HAEo\/hQDBP\/sBAj+3giS+jYLBPcqDbLzHg+i7zoRxu6+D+LwQg7o9GYNmPkqDc38Zg1n\/qYO5P52ESj+yBTw+yAXevmWGab1Thv68e4bYPDQG6jubBvM7bgaSu0AQADAeACSE+AGSO8oBULw7AQe8aoFRPhiB+z8mAhH\/rQK5P6QC4b+hA0M\/R4P9vocEUD3AEB0EwLvlBFI7yoNNvLeCGr19AKS+un9Q\/8AQADAeQDUEmv9aPd2ApbxKAUC7wIIIO8sBjbytQJi+D8Aiv1+AOT+LgRH\/vwIOPowDGT2Bg5Y80YPPPEiEGDwlBEg75ITqO6aEB7x0AyY+Z4HCgVqBPAKPwDcD0\/81BJA924UDPQyFCTwcBKo7r4PbO5sDABAAMB6AIgOsQMk8M4EZu9CDkjvAA\/e73oIyvS1Anr5ov9u\/Ef+6P0s\/cH\/a\/1+AOn9pv47ATD8qgUw\/HoIBP94CnoBSA0uBB4PqgVYESwGthLIBVAU7ASqFFIDbhTYAXQT3AAAQADAewBEFkYeTNwYGrDcYhYs3rATwOD4EU7k3A9A6MoNUOz2CeTuPgj878gFfvByA5zwPgjc8RQKdvP2CYL25gXB\/ygFtQKqBfIDCAeSBDgJKAWWCkYFcgtuBQBAAMB8AIQXWBFw2xQDJP8AQADAfQBUGogOktwQE5LczBVo3qQVeOLOE8rlFhJY6f4QUOxYEQLvkhNg8IQXPPHyEpbxoA8S8yoNiPV4Cvz5wAjk\/koGegFqBJID1QJqBIL\/CgUO\/AoFAEAAwH4A+BHVAtrp7ATg6KQGwujACJ7prgu26mYNeuoAD9rpvg866QBAAMA="}), default: new Text({"name":"HersheySans1","unitsPerEm":1000,"ascent":800,"descent":-200,"capHeight":500,"xHeight":300,"glyphs":"IADEDgDAIQBODE4MJOZODGj3AEBODIr9GAvF\/k4MAACEDcX+TgyK\/QBAAMAiALATmAgk5pgIxu4AQHASJOZwEsbuAEAAwCMA3Bk6ETjhmAiYCABAnBg44QQQmAgAQJgIPPHcGTzxAEBiB574nBie+ABAAMAkAJwYhA044YQN7AQAQHASOOFwEuwEAECcGNrpJhZk53ASJOaEDSTm2Alk52IH2uliB1DsmAjG7tgJ\/O9ODDzxsBOy8yYW6PRcFyj2nBie+JwYT\/wmFsX+cBIAAIQNAADYCcX+YgdP\/ABAAMAlAIgdiB0k5mIHAAAAQIQNJOYEEKToBBAQ68QOkO1ODMbu2AnG7mIHUOxiB9rpmAhk5xgLJOaEDSTmBBBk57ATpOhcF6ToEhtk54gdJOYAQJwYaPcmFp748BQU+\/AUiv1cFwAA3BkAAEgcxf6IHU\/8iB3U+RIbaPecGGj3AEAAwCYA\/h\/+Hzzx\/h\/878gexu6IHcbuSBz87xIbfPKcGJ74JhZP\/LATxf46EQAATgwAANgJxf6YCIr9YgcU+2IHnviYCCj22Ano9HAS\/O+wE8bu8BRQ7PAU2umwE2TnOhEk5sQOZOeEDdrphA1Q7MQO\/O86EbLzXBdP\/NwZxf5IHAAAyB4AAP4fxf7+H4r9AEAAwCcATgzYCaTomAhk59gJJOYYC2TnGAva6dgJUOyYCJDtAEAAwCgAOhE6ETjhxA64404MZOfYCVDsmAh88pgIaPfYCYr9Tgx2AsQOLAY6EZgIAEAAwCkAOhFiBzjh2Am4404MZOfEDlDsBBB88gQQaPfEDor9Tgx2AtgJLAZiB5gIAEAAwCoAsBOEDZDthA1P\/ABAYgc88bATnvgAQLATPPFiB574AEAAwCsA\/h+wE9rpsBMAAABAmAjo9Mge6PQAQADALADYCdgJFPuYCE\/8YgcU+5gI1PnYCRT72AmK\/WIHAAAAQADALQD+H5gI6PTIHuj0AEAAwC4A2AmYCNT5YgcU+5gIT\/zYCRT7mAjU+QBAAMAvABIbSBw44SwGmAgAQADAMACcGMQOJOYYC2TnmAgQ62IHPPFiB+j0mAgU+xgLxf7EDgAAOhEAAPAUxf5cFxT7nBjo9JwYPPFcFxDr8BRk5zoRJObEDiTmAEAAwDEAnBgYCxDrhA3a6ToRJOY6EQAAAEAAwDIAnBiYCFDsmAgQ69gJpOgYC2TnhA0k5nASJObwFGTnJhak6FwXEOtcF5DtJhb877ATsvNiBwAAnBgAAABAAMAzAJwY2Akk5lwXJOYEEPzvsBP87yYWPPFcF3zynBgo9pwYnvhcF0\/88BTF\/joRAACEDQAA2AnF\/pgIiv1iBxT7AEAAwDQAnBiwEyTmYgdo99wZaPcAQLATJOawEwAAAEAAwDUAnBgmFiTm2Akk5pgIPPHYCfzvhA3G7joRxu7wFPzvXBd88pwYKPacGJ74XBdP\/PAUxf46EQAAhA0AANgJxf6YCIr9YgcU+wBAAMA2AJwYXBfa6SYWZOdwEiTmBBAk5k4MZOfYCRDrmAg88ZgIaPfYCU\/8TgzF\/gQQAAA6EQAA8BTF\/lwXT\/ycGJ74nBho91wXsvPwFDzxOhH87wQQ\/O9ODDzx2Amy85gIaPcAQADANwCcGJwYJOZODAAAAEBiByTmnBgk5gBAAMA4AJwYhA0k5tgJZOeYCNrpmAhQ7NgJxu5ODPzvOhE88fAUfPJcF+j0nBho95wYFPtcF4r9JhbF\/nASAACEDQAA2AnF\/pgIiv1iBxT7Ygdo95gI6PQYC3zyxA488bAT\/O8mFsbuXBdQ7FwX2ukmFmTncBIk5oQNJOYAQADAOQCcGFwXxu4mFnzysBPo9AQQKPbEDij2GAvo9JgIfPJiB8buYgeQ7ZgI2ukYC2TnxA4k5gQQJOawE2TnJhba6VwXxu5cF+j0JhYU+7ATxf4EEAAAhA0AANgJxf6YCE\/8AEAAwDoA2AmYCDzxYgd88pgIsvPYCXzymAg88QBAmAjU+WIHFPuYCE\/82AkU+5gI1PkAQADAOwDYCZgIPPFiB3zymAiy89gJfPKYCDzxAEDYCRT7mAhP\/GIHFPuYCNT52AkU+9gJiv1iBwAAAEAAwDwAiB1IHNrpmAjo9EgcAAAAQADAPQD+H5gIPPHIHjzxAECYCJ74yB6e+ABAAMA+AIgdmAja6Ugc6PSYCAAAAEAAwD8AJhZiB1DsYgcQ65gIpOjYCWTnTgwk5joRJOawE2Tn8BSk6CYWEOsmFpDt8BT877ATPPHEDrLzxA5o9wBAxA6K\/YQNxf7EDgAABBDF\/sQOiv0AQADAQAA0IdwZ\/O+cGJDtJhZQ7HASUOwEEJDtxA7G7oQNfPKEDSj2xA6e+DoR1PnwFNT5XBee+JwYKPYAQHASUOwEEMbuxA588sQOKPYEEJ74OhHU+QBA3BlQ7JwYKPacGJ74EhvU+Ygd1Pn+H2j3NCGy8zQhPPH+H5DtyB4Q60gcpOjcGWTnJhYk5nASJObEDmTnTgyk6NgJEOuYCJDtYgc88WIH6PSYCJ742AkU+04Miv3EDsX+cBIAACYWAADcGcX+SByK\/YgdT\/wAQBIbUOzcGSj23Bme+BIb1PkAQADAQQAmFsQOJObsBAAAAEDEDiTmnBgAAABAmAho9\/AUaPcAQADAQgDcGZgIJOaYCAAAAECYCCTmsBMk5lwXZOecGKTo3BkQ69wZkO2cGPzvXBc88bATfPIAQJgIfPKwE3zyXBey85wY6PTcGWj33BkU+5wYiv1cF8X+sBMAAJgIAAAAQADAQwDcGdwZUOycGNrpJhZk57ATJObEDiTmTgxk59gJ2umYCFDsYgf872IHKPaYCNT52AlP\/E4Mxf7EDgAAsBMAACYWxf6cGE\/83BnU+QBAAMBEANwZmAgk5pgIAAAAQJgIJOY6ESTm8BRk51wX2umcGFDs3Bn879wZKPacGNT5XBdP\/PAUxf46EQAAmAgAAABAAMBFAFwXmAgk5pgIAAAAQJgIJOacGCTmAECYCHzycBJ88gBAmAgAAJwYAAAAQADARgAmFpgIJOaYCAAAAECYCCTmnBgk5gBAmAh88nASfPIAQADARwDcGdwZUOycGNrpJhZk57ATJObEDiTmTgxk59gJ2umYCFDsYgf872IHKPaYCNT52AlP\/E4Mxf7EDgAAsBMAACYWxf6cGE\/83BnU+dwZKPYAQLATKPbcGSj2AEAAwEgAEhuYCCTmmAgAAABA3Bkk5twZAAAAQJgIfPLcGXzyAEAAwEkA2AmYCCTmmAgAAABAAMBKALATcBIk5nAS1Pk6EYr9BBDF\/oQNAAAYCwAAmAjF\/mIHiv0sBtT5LAZo9wBAAMBLANwZmAgk5pgIAAAAQNwZJOaYCGj3AEDEDjzx3BkAAABAAMBMAPAUmAgk5pgIAAAAQJgIAABcFwAAAEAAwE0AiB2YCCTmmAgAAABAmAgk5nASAAAAQEgcJOZwEgAAAEBIHCTmSBwAAABAAMBOABIbmAgk5pgIAAAAQJgIJObcGQAAAEDcGSTm3BkAAABAAMBPABIbxA4k5k4MZOfYCdrpmAhQ7GIH\/O9iByj2mAjU+dgJT\/xODMX+xA4AALATAAAmFsX+nBhP\/NwZ1PkSGyj2Ehv879wZUOycGNrpJhZk57ATJObEDiTmAEAAwFAA3BmYCCTmmAgAAABAmAgk5rATJOZcF2TnnBik6NwZEOvcGcbunBg88VwXfPKwE7LzmAiy8wBAAMBRABIbxA4k5k4MZOfYCdrpmAhQ7GIH\/O9iByj2mAjU+dgJT\/xODMX+xA4AALATAAAmFsX+nBhP\/NwZ1PkSGyj2Ehv879wZUOycGNrpJhZk57ATJObEDiTmAEBwEhT73Bl2AgBAAMBSANwZmAgk5pgIAAAAQJgIJOawEyTmXBdk55wYpOjcGRDr3BmQ7ZwY\/O9cFzzxsBN88pgIfPIAQDoRfPLcGQAAAEAAwFMAnBicGNrpJhZk53ASJOaEDSTm2Alk52IH2uliB1DsmAjG7tgJ\/O9ODDzxsBOy8yYW6PRcFyj2nBie+JwYT\/wmFsX+cBIAAIQNAADYCcX+YgdP\/ABAAMBUALAThA0k5oQNAAAAQOwEJOYmFiTmAEAAwFUAEhuYCCTmmAie+NgJT\/xODMX+BBAAAHASAAAmFsX+nBhP\/NwZnvjcGSTmAEAAwFYAJhbsBCTmxA4AAABAnBgk5sQOAAAAQADAVwCIHSwGJOZODAAAAEBwEiTmTgwAAABAcBIk5pwYAAAAQMgeJOacGAAAAEAAwFgAnBhiByTmnBgAAABAnBgk5mIHAAAAQADAWQAmFuwEJObEDnzyxA4AAABAnBgk5sQOfPIAQADAWgCcGJwYJOZiBwAAAEBiByTmnBgk5gBAYgcAAJwYAAAAQADAWwA6EZgIOOGYCJgIAEDYCTjh2AmYCABAmAg44ToROOEAQJgImAg6EZgIAEAAwFwAOhGxAyTm8BSxAwBAAMBdADoRxA444cQOmAgAQAQQOOEEEJgIAEBiBzjhBBA44QBAYgeYCAQQmAgAQADAXgCwE4QNuOOxA+j0AECEDbjjXBfo9ABAAMBfACYWsQOYCNwZmAgAQADAYADYCdgJUOxiB8buYgc88ZgIfPLYCTzxmAj872IHPPEAQADAYQBcFyYWxu4mFgAAAEAmFnzysBP87zoRxu6EDcbuGAv875gIfPJiByj2Ygee+JgIT\/wYC8X+hA0AADoRAACwE8X+JhZP\/ABAAMBiAFwXmAgk5pgIAAAAQJgIfPIYC\/zvhA3G7joRxu6wE\/zvJhZ88lwXKPZcF574JhZP\/LATxf46EQAAhA0AABgLxf6YCE\/8AEAAwGMAJhYmFnzysBP87zoRxu6EDcbuGAv875gIfPJiByj2Ygee+JgIT\/wYC8X+hA0AADoRAACwE8X+JhZP\/ABAAMBkAFwXJhYk5iYWAAAAQCYWfPKwE\/zvOhHG7oQNxu4YC\/zvmAh88mIHKPZiB574mAhP\/BgLxf6EDQAAOhEAALATxf4mFk\/8AEAAwGUAJhZiByj2JhYo9iYWsvPwFDzxsBP87zoRxu6EDcbuGAv875gIfPJiByj2Ygee+JgIT\/wYC8X+hA0AADoRAACwE8X+JhZP\/ABAAMBmAMQOBBAk5oQNJOYYC2Tn2AkQ69gJAAAAQCwGxu7EDsbuAEAAwGcAXBcmFsbuJhZ2AvAULAawE2IHOhGYCIQNmAgYC2IHAEAmFnzysBP87zoRxu6EDcbuGAv875gIfPJiByj2Ygee+JgIT\/wYC8X+hA0AADoRAACwE8X+JhZP\/ABAAMBoAFwXmAgk5pgIAAAAQJgIsvNODPzvxA7G7nASxu7wFPzvJhay8yYWAAAAQADAaQDYCWIHJOaYCGTn2Akk5pgI7uRiByTmAECYCMbumAgAAABAAMBqAE4M2Akk5hgLZOdODCTmGAvu5NgJJOYAQBgLxu4YC7ED2AliB2IHmAjsBJgIAEAAwGsA8BSYCCTmmAgAAABA8BTG7pgIFPsAQIQNKPYmFgAAAEAAwGwA2AmYCCTmmAgAAABAAMBtAOokmAjG7pgIAAAAQJgIsvNODPzvxA7G7nASxu7wFPzvJhay8yYWAAAAQCYWsvPcGfzvSBzG7v4fxu50IvzvtCOy87QjAAAAQADAbgBcF5gIxu6YCAAAAECYCLLzTgz878QOxu5wEsbu8BT87yYWsvMmFgAAAEAAwG8AXBeEDcbuGAv875gIfPJiByj2Ygee+JgIT\/wYC8X+hA0AADoRAACwE8X+JhZP\/FwXnvhcFyj2JhZ88rAT\/O86EcbuhA3G7gBAAMBwAFwXmAjG7pgImAgAQJgIfPIYC\/zvhA3G7joRxu6wE\/zvJhZ88lwXKPZcF574JhZP\/LATxf46EQAAhA0AABgLxf6YCE\/8AEAAwHEAXBcmFsbuJhaYCABAJhZ88rAT\/O86EcbuhA3G7hgL\/O+YCHzyYgco9mIHnviYCE\/8GAvF\/oQNAAA6EQAAsBPF\/iYWT\/wAQADAcgAEEJgIxu6YCAAAAECYCCj22Al88k4M\/O\/EDsbucBLG7gBAAMBzAPAU8BR88rAT\/O8EEMbuTgzG7pgI\/O9iB3zymAjo9BgLKPY6EWj3sBOe+PAUFPvwFE\/8sBPF\/gQQAABODAAAmAjF\/mIHT\/wAQADAdADEDtgJJObYCRT7GAvF\/oQNAAAEEAAAAEAsBsbuxA7G7gBAAMB1AFwXmAjG7pgIFPvYCcX+TgwAAAQQAABwEsX+JhYU+wBAJhbG7iYWAAAAQADAdgCwEywGxu6EDQAAAEDwFMbuhA0AAABAAMB3ABIbYgfG7k4MAAAAQDoRxu5ODAAAAEA6EcbuJhYAAABAEhvG7iYWAAAAQADAeADwFGIHxu7wFAAAAEDwFMbuYgcAAABAAMB5ALATLAbG7oQNAAAAQPAUxu6EDQAAGAvsBJgIYgcsBpgI7ASYCABAAMB6APAU8BTG7mIHAAAAQGIHxu7wFMbuAEBiBwAA8BQAAABAAMB7ADoRxA444U4MeOIYC7jj2Akk5tgJpOgYCxDrTgxQ7IQNxu6EDTzxGAuy8wBATgx44hgL7uQYC2TnTgza6YQNEOvEDpDtxA7874QNfPKYCOj0hA1o98QO1PnEDk\/8hA3F\/k4MAAAYC3YCGAvsBE4MYgcAQBgLKPaEDZ74hA0U+04Miv0YC8X+2Ak7AdgJsQMYCywGTgxiB8QOmAgAQADAfADYCZgIOOGYCJgIAEAAwH0AOhHYCTjhTgx44oQNuOPEDiTmxA6k6IQNEOtODFDsGAvG7hgLPPGEDbLzAEBODHjihA3u5IQNZOdODNrpGAsQ69gJkO3YCfzvGAt88gQQ6PQYC2j32AnU+dgJT\/wYC8X+TgwAAIQNdgKEDewETgxiBwBAhA0o9hgLnvgYCxT7TgyK\/YQNxf7EDjsBxA6xA4QNLAZODGIH2AmYCABAAMB+AIgdYgee+GIHKPaYCHzyGAs88YQNPPEEEHzy8BQo9lwXaPfcGWj3SBwo9ogdsvMAQGIHKPaYCLLzGAt88oQNfPIEELLz8BRo91wXnvjcGZ74SBxo94gdsvOIHTzxAEAAwA=="}) } turtle.jump(-85, -80); texts.logo.print(turtle, 'Connect4', 1.5); for(let i = 1; i < 8; i++) { turtle.jump(-105 + i*25, 90); texts.default.print(turtle, ''+i, 1.2); } const gameState = Connect4(legalMoves); switch(gameState.status) { case gameState.GAMEON: turtle.jump(0, -90); texts.default.print(turtle, 'Add your moves by adding\ncolumn numbers to the\nmoves text box.', .7); break; case gameState.PLAYERWON: turtle.jump(18, -80); texts.default.print(turtle, 'You won!', 1.5); break; case gameState.COMPUTERWON: turtle.jump(-10, -80); texts.default.print(turtle, 'You lost! Try again.', 1); break; case gameState.DRAW: turtle.jump(30, -80); texts.default.print(turtle, 'It\'s a draw.', 1); break; case gameState.ERROR: turtle.jump(-10, -80); texts.default.print(turtle, 'No cheating!', 1.5); break; } const winningConnections = myownconnect4checker(gameState.board); if(winningConnections.length > 0) { winningConnections.forEach((e, i, a) => { for(let r = .6; r < 2; r+=.5) { const ts = PT.circlesTangents([e[0][0] * 25 - 75, e[0][1] * 25 - 60], r, [e[1][0] * 25 - 75, e[1][1] * 25 - 60], r); (()=>{ const p = polygons.create(); p.addPoints(ts[0][0], ts[0][1], ts[1][1], ts[1][0]); p.dp.push(ts[0][0], ts[1][0]); p.dp.push(ts[1][1], ts[0][1]); polygons.draw(turtle, p); })() e.forEach(cr => { const p = polygons.create(); p.addPoints(...PT.circle(r).map(pt => V.add(pt, [cr[0] * 25 - 75, cr[1] * 25 - 60]))); p.addOutline(); polygons.draw(turtle, p); }); } }) } gameState.board.forEach((column, col, columns) => column.forEach((cell, row, rows) => { const p = polygons.create(); p.addPoints(...PT.circle(10).map(pt => V.add(pt, [col * 25 - 75, row * 25 - 60]))); switch(cell) { case 1: p.addHatching(preLoadedHatches[col][row][0]); break; case 2: p.addHatching(preLoadedHatches[col][row][1]); default: } p.addOutline(); polygons.draw(turtle, p); })); function myownconnect4checker(board) { //all [col, row]s return gameState.board.flatMap( (c, col) => c.map((r, row) => [col, row]) ).filter(cr => gameState.board[cr[0]][cr[1]] == gameState.rot || gameState.board[cr[0]][cr[1]] == gameState.gelb).map(cr => //4 cells in directions right and down Array.from({length: 2}) .flatMap((e,c) => Array.from({length: 2}) .map((e,r) => [c, r])) .filter(c => !(c[0] == 0 && c[1] == 0)) .map(v => Array.from({length: 4}) .map((e,i,a) => V.scale(v, i))) //and map them relative to all [col, row]s .map(combo => combo.map(p => V.add(cr, p))) //filter out combos that are out of bound with the board .filter(combo => combo.every(cell => 0 <= cell[0] && cell[0] < 7 && 0 <= cell[1] && cell[1] < 6 )) //filter out combos of which all cell don't have the same value .filter(combo => combo.every(cell => gameState.board[combo[0][0]][combo[0][1]] == gameState.board[cell[0]][cell[1]])) ).filter(c => c.length > 0) .flatMap(c => c) .map(combo => combo.filter((c, i) => i == 0 || i == 3)); } // Port of connect4 implementation by Sebastian Macke // https://simulationcorner.net/index.php?page=connectfour function Connect4(moves) { const gameState = { GAMEON: 0, PLAYERWON: 1, COMPUTERWON: 2, DRAW: 3, ERROR: 9, status: 0, board: [], rot: 1, gelb: 2, ausserhalb: 3, leer: 0, } const feld = Array.from({length: 7}, () => Array.from({length: 6}, () => 0)); const hoehe = Array.from({length: 7}, () => 5); function get(spalte, zeile) { return (spalte < 0 || 6 < spalte || zeile < 0 || 5 < zeile)? gameState.ausserhalb: feld[spalte][zeile]; } function gesetzt(spalte) { if (hoehe[spalte] == -1) throw new Error('Column full'); feld[spalte][hoehe[spalte]] = gameState.rot; hoehe[spalte] = hoehe[spalte] - 1; //put(spalte,gameState.rot); if (pruefe(spalte,hoehe[spalte]+1,4,gameState.rot,false)) { gameState.status = gameState.PLAYERWON; } if (hoehe.every(h => h === -1)) { gameState.status = gameState.DRAW; } if (gameState.status != gameState.PLAYERWON) computer(); } /* * function pruefe * Beschreibung: sucht an Koordianten "x,y", ob "menge" Steine in der "farbe" waagrecht,diagonal oder senkrecht aufzufinden sind. * und ob noch die restlichen Stellen f�r die Erschaffung eines Vierers ausreichen * pruefe_bei_2: pr�ft ob 4. Stein gleich gesetzt werden kann(ansonsten k�nnte der Gegner den werdenden Vierer gleich zerst�ren) */ function pruefe(x, y, menge, farbe, pruefe_bei_2) { let ja = false; /* Gegnerfarbe bestimmen */ const farbe2 = (farbe === gameState.rot)? gameState.gelb: gameState.rot; for (let k = 0; k < 4; k++) { let summe1 = summe2 = summe3 = summe4 = summe12 = summe22 = summe32 = summe42 = 0; for(let j = 0; j < 4; j++){ if(get(x-k+j,y) == farbe) summe1++; if(get(x,y-k+j) == farbe) summe2++; if(get(x-k+j,y-k+j) == farbe) summe3++; if(get(x+k-j,y-k+j) == farbe) summe4++; if(get(x-k+j,y) == farbe2) summe12++; if(get(x,y-k+j) == farbe2) summe22++; if(get(x-k+j,y-k+j) == farbe2) summe32++; if(get(x+k-j,y-k+j) == farbe2) summe42++; if(get(x-k+j,y) == gameState.ausserhalb) summe12++; if(get(x,y-k+j) == gameState.ausserhalb) summe22++; if(get(x-k+j,y-k+j) == gameState.ausserhalb) summe32++; if(get(x+k-j,y-k+j) == gameState.ausserhalb) summe42++; } if ( ((summe1 >= menge) && (summe12 == 0)) || ((summe2 >= menge) && (summe22 == 0)) || ((summe3 >= menge) && (summe32 == 0)) || ((summe4 >= menge) && (summe42 == 0)) ) { ja = true; } if (ja && pruefe_bei_2) { summe12 = 0; summe22 = 0; summe32 = 0; summe42 = 0; feld[x][y] = farbe; hoehe[x]--; for(let j = 0; j< 4; j++) { if (summe1 >= menge && get(x-k+j,y) == gameState.leer && get(x-k+j,hoehe[x-k+j]+1) == gameState.leer) summe12++; if (summe2 >= menge && get(x,y-k+j) == gameState.leer && get(x,hoehe[x]+1) == gameState.leer) summe22++; if (summe3 >= menge && get(x-k+j,y-k+j) == gameState.leer && get(x-k+j,hoehe[x-k+j]+1) == gameState.leer) summe32++; if (summe4 >= menge && get(x+k-j,y-k+j) == gameState.leer && get(x+k-j,hoehe[x+k-j]+1) == gameState.leer) summe42++; } if (summe12 == 1 || summe22 == 1 || summe32 == 1 || summe42 == 1) ja = false; hoehe[x]++; feld[x][y] = gameState.leer; } } return ja; } function computer() { const chance = [13, 13, 16, 16, 16, 13, 13].map(v => v + Math.random() * 4); for(let i = 0; i < 7; i++) { if (hoehe[i] < 0) chance[i] = chance[i]-30000; } for(let i = 0; i < 7; i++) { //Gewonnen if (pruefe(i,hoehe[i],3,gameState.gelb,false) == true) chance[i] = chance[i] + 20000; //anderer versucht zu gewinnen if (pruefe(i,hoehe[i],3,gameState.rot,false) == true) chance[i] = chance[i] + 10000; //�ber einem 3 rot if (pruefe(i,hoehe[i]-1,3,gameState.rot,false) == true) chance[i] = chance[i] -4000; //�ber einem 3 gelb if (pruefe(i,hoehe[i]-1,3,gameState.gelb,false) == true) chance[i] = chance[i] -200; //2 auf 3 verhindern if (pruefe(i,hoehe[i],2,gameState.rot,false) == true) chance[i] = chance[i] +50+Math.random()*3; //2 auf 3 erm�glichen, aber nicht wenn anderer die 3 ausschalten kann if (pruefe(i,hoehe[i],2,gameState.gelb,true) && hoehe[i] > 0) { feld[i][hoehe[i]] = gameState.gelb; hoehe[i]--; let zaehler = 0; for(let j = 0; j < 7; j++) if(pruefe(j,hoehe[j],3,gameState.gelb,false)) zaehler++; chance[i] = chance[i] + (zaehler == 0? 60 + Math.random() * 2: -60); hoehe[i]++; feld[i][hoehe[i]] = gameState.leer; } //nein wenn rot dr�ber if (pruefe(i,hoehe[i]-1,2,gameState.rot,false)) chance[i] -= 10; //nein wenn gameState.gelb dr�ber if (pruefe(i,hoehe[i]-1,2,gameState.gelb,false)) chance[i] -= 8; //1 auf 2 verhindern if (pruefe(i,hoehe[i],1,gameState.rot,false)) chance[i] += 5 + Math.random() * 2; //1 auf 2 erm�glichen if (pruefe(i,hoehe[i],1,gameState.gelb,false) == true) chance[i] += 5 + Math.random() * 2; //nein wenn rot dr�ber if (pruefe(i,hoehe[i]-1,1,gameState.rot,false) == true) chance[i] -= 2; //ja wenn gelb dr�ber if (pruefe(i,hoehe[i]-1,1,gameState.gelb,false) == true) chance[i]++; //m�glichkeit zum austricksen suchen if (pruefe(i,hoehe[i],2,gameState.gelb,true) && hoehe[i] > 0) { feld[i][hoehe[i]] = gameState.gelb; hoehe[i]--; for(let k=0; k < 7; k++) { if (pruefe(k,hoehe[k],3,gameState.gelb,false) && hoehe[k] > 0) { feld[k][hoehe[k]] = gameState.rot; hoehe[k]--; for(let j = 0; j < 7; j++) { if (pruefe(j,hoehe[j],3,gameState.gelb,false) == true) chance[i] += 2000; } hoehe[k]++; feld[k][hoehe[k]] = gameState.leer; } } hoehe[i]++; feld[i][hoehe[i]] = gameState.leer; } //pr�fen ob anderer austricksen kann if(pruefe(i,hoehe[i],2,gameState.rot,true) && hoehe[i] > 0) { feld[i][hoehe[i]] = gameState.rot; hoehe[i]--; for(let k = 0; k < 7; k++) { if (pruefe(k,hoehe[k],3,gameState.rot,false) && hoehe[k] > 0) { feld[k][hoehe[k]] = gameState.gelb; hoehe[k]--; for(let j = 0; j < 7; j++) { if (pruefe(j,hoehe[j],3,gameState.rot,false)) chance[i] += 1000; } hoehe[k]++; feld[k][hoehe[k]] = gameState.leer; } } hoehe[i]++; feld[i][hoehe[i]] = gameState.leer; } //pr�fen ob anderer austricksen kann wenn ich ins feld reingehe if(pruefe(i,hoehe[i]-1,2,gameState.rot,true) && hoehe[i] > 1) { feld[i][hoehe[i]] = gameState.rot; hoehe[i]--; for(let k = 0; k < 7; k++) { if(pruefe(k,hoehe[k]-1,3,gameState.rot,false) && hoehe[k] > 0) { feld[k][hoehe[k]] = gameState.gelb; hoehe[k]--; for(let j = 0; j < 7; j++) { if (pruefe(j,hoehe[j]-1,3,gameState.rot,false)) chance[i] -= 500; } hoehe[k]++; feld[k][hoehe[k]] = gameState.leer; } } hoehe[i]++; feld[i][hoehe[i]] = gameState.leer; } } let spalte = 0; let x = -10000; for(let i = 0; i < 7; i++) { if (chance[i] > x) { x = chance[i]; spalte = i; } } feld[spalte][hoehe[spalte]] = gameState.gelb; hoehe[spalte]--; //put(spalte,gameState.gelb); if (pruefe(spalte,hoehe[spalte]+1,4,gameState.gelb,false)) { gameState.status = gameState.COMPUTERWON; } if (hoehe.every(v => v == -1)) { gameState.status = gameState.DRAW; } } moves.split('').forEach(v => { if(hoehe[v-1] < 0) { gameState.status = gameState.ERROR; } if(gameState.status == gameState.ERROR) { return; } if(gameState.status == gameState.GAMEON) { gesetzt(v - 1); } }); gameState.board = feld; return gameState; } function init() { /////////////////////////////////////////////////////// // Vector functions - Created by Jurgen Westerhof 2024 // https://turtletoy.net/turtle/d068ad6040 /////////////////////////////////////////////////////// class Vector { static add (a,b) { return a.map((v,i)=>v+b[i]); } static sub (a,b) { return a.map((v,i)=>v-b[i]); } static mul (a,b) { return a.map((v,i)=>v*b[i]); } static div (a,b) { return a.map((v,i)=>v/b[i]); } static scale(a,s) { return a.map(v=>v*s); } static det(m) { return m.length == 1? m[0][0]: m.length == 2 ? m[0][0]*m[1][1]-m[0][1]*m[1][0]: m[0].reduce((r,e,i) => r+(-1)**(i+2)*e*this.det(m.slice(1).map(c => c.filter((_,j) => i != j))),0); } static angle(a) { return Math.PI - Math.atan2(a[1], -a[0]); } //compatible with turtletoy heading static rot2d(angle) { return [[Math.cos(angle), -Math.sin(angle)], [Math.sin(angle), Math.cos(angle)]]; } static rot3d(yaw,pitch,roll) { return [[Math.cos(yaw)*Math.cos(pitch), Math.cos(yaw)*Math.sin(pitch)*Math.sin(roll)-Math.sin(yaw)*Math.cos(roll), Math.cos(yaw)*Math.sin(pitch)*Math.cos(roll)+Math.sin(yaw)*Math.sin(roll)],[Math.sin(yaw)*Math.cos(pitch), Math.sin(yaw)*Math.sin(pitch)*Math.sin(roll)+Math.cos(yaw)*Math.cos(roll), Math.sin(yaw)*Math.sin(pitch)*Math.cos(roll)-Math.cos(yaw)*Math.sin(roll)],[-Math.sin(pitch), Math.cos(pitch)*Math.sin(roll), Math.cos(pitch)*Math.cos(roll)]]; } static trans(matrix,a) { return a.map((v,i) => a.reduce((acc, cur, ci) => acc + cur * matrix[ci][i], 0)); } //Mirror vector a in a ray through [0,0] with direction mirror static mirror2d(a,mirror) { return [Math.atan2(...mirror)].map(angle => this.trans(this.rot2d(angle), this.mul([-1,1], this.trans(this.rot2d(-angle), a)))).pop(); } static approx(a,b,p) { return this.len(this.sub(a,b)) < (p === undefined? .001: p); } static norm (a) { return this.scale(a,1/this.len(a)); } static len (a) { return Math.hypot(...a); } static lenSq (a) { return a.reduce((a,c)=>a+c**2,0); } static lerp (a,b,t) { return a.map((v, i) => v*(1-t) + b[i]*t); } static dist (a,b) { return Math.hypot(...this.sub(a,b)); } static dot (a,b) { return a.reduce((a,c,i) => a+c*b[i], 0); } static cross(...ab) { return ab[0].map((e, i) => ab.map(v => v.filter((ee, ii) => ii != i))).map((m,i) => (i%2==0?-1:1)*this.det(m)); } } this.V = Vector; class Intersection2D { //a-start, a-direction, b-start, b-direction //returns false on no intersection or [[intersection:x,y], scalar a-direction, scalar b-direction static info(as, ad, bs, bd) { const d = V.sub(bs, as), det = -V.det([bd, ad]); if(det === 0) return false; const res = [V.det([d, bd]) / det, V.det([d, ad]) / det]; return [V.add(as, V.scale(ad, res[0])), ...res]; } static ray(a, b, c, d) { return this.info(a, b, c, d); } static segment(a,b,c,d, inclusiveStart = true, inclusiveEnd = true) { const i = this.info(a, V.sub(b, a), c, V.sub(d, c)); return i === false? false: ( (inclusiveStart? 0<=i[1] && 0<=i[2]: 0<i[1] && 0<i[2]) && (inclusiveEnd? i[1]<=1 && i[2]<=1: i[1]<1 && i[2]<1) )?i[0]:false;} static tour(tour, segmentStart, segmentDirection) { return tour.map((e, i, a) => [i, this.info(e, V.sub(a[(i+1)%a.length], e), segmentStart, segmentDirection)]).filter(e => e[1] !== false && 0 <= e[1][1] && e[1][1] <= 1).filter(e => 0 <= e[1][2]).map(e => ({position: e[1][0],tourIndex: e[0],tourSegmentPortion: e[1][1],segmentPortion: e[1][2],}));} static inside(tour, pt) { return tour.map((e,i,a) => this.segment(e, a[(i+1)%a.length], pt, [Number.MAX_SAFE_INTEGER, 0], true, false)).filter(e => e !== false).length % 2 == 1; } static circles(centerA, radiusA, centerB, radiusB) {const result = {intersect_count: 0,intersect_occurs: true,one_is_in_other: false,are_equal: false,point_1: [null, null],point_2: [null, null],};const dx = centerB[0] - centerA[0];const dy = centerB[1] - centerA[1];const dist = Math.hypot(dy, dx);if (dist > radiusA + radiusB) {result.intersect_occurs = false;}if (dist < Math.abs(radiusA - radiusB) && !N.approx(dist, Math.abs(radiusA - radiusB))) {result.intersect_occurs = false;result.one_is_in_other = true;}if (V.approx(centerA, centerB) && radiusA === radiusB) {result.are_equal = true;}if (result.intersect_occurs) {const centroid = (radiusA**2 - radiusB**2 + dist * dist) / (2.0 * dist);const x2 = centerA[0] + (dx * centroid) / dist;const y2 = centerA[1] + (dy * centroid) / dist;const prec = 10000;const h = (Math.round(radiusA**2 * prec)/prec - Math.round(centroid**2 * prec)/prec)**.5;const rx = -dy * (h / dist);const ry = dx * (h / dist);result.point_1 = [x2 + rx, y2 + ry];result.point_2 = [x2 - rx, y2 - ry];if (result.are_equal) {result.intersect_count = null;} else if (result.point_1.x === result.point_2.x && result.point_1.y === result.point_2.y) {result.intersect_count = 1;} else {result.intersect_count = 2;}}return result;} } this.Intersection = Intersection2D; class PathTools { static bezier(p1, cp1, cp2, p2, steps = null) {steps = (steps === null? (V.len(V.sub(cp1, p1)) + V.len(V.sub(cp2, cp1)) + V.len(V.sub(p2, cp2))) | 0: steps) - 1;return Array.from({length: steps + 1}).map((v, i, a, f = i/steps) => [[V.lerp(p1, cp1, f),V.lerp(cp1, cp2, f),V.lerp(cp2, p2, f)]].map(v => V.lerp(V.lerp(v[0], v[1], f), V.lerp(v[1], v[2], f), f))[0]);} // https://stackoverflow.com/questions/18655135/divide-bezier-curve-into-two-equal-halves#18681336 static splitBezier(p1, cp1, cp2, p2, t=.5) {const e = V.lerp(p1, cp1, t);const f = V.lerp(cp1, cp2, t);const g = V.lerp(cp2, p2, t);const h = V.lerp(e, f, t);const j = V.lerp(f, g, t);const k = V.lerp(h, j, t);return [[p1, e, h, k], [k, j, g, p2]];} static circular(radius,verticeCount,rotation=0) {return Array.from({length: verticeCount}).map((e,i,a,f=i*2*Math.PI/verticeCount+rotation) => [radius*Math.cos(f),radius*Math.sin(f)])} static circle(r){return this.circular(r,Math.max(12, r*2*Math.PI|0));} static arc(radius, extend = 2 * Math.PI, clockWiseStart = 0, steps = null, includeLast = false) { return [steps == null? (radius*extend+1)|0: steps].map(steps => Array.from({length: steps}).map((v, i, a) => [radius * Math.cos(clockWiseStart + extend*i/(a.length-(includeLast?1:0))), radius * Math.sin(clockWiseStart + extend*i/(a.length-(includeLast?1:0)))])).pop(); } static draw(turtle, path) {path.forEach((pt, i) => turtle[i==0?'jump':'goto'](pt));} static drawTour(turtle, path) {this.draw(turtle, path.concat([path[0]]));} static drawPoint(turtle, pt, r = .1) {this.drawTour(turtle, this.circle(r).map(e => V.add(e, pt)));} static drawArrow(turtle, s, d, width = 6, length = 3) {turtle.jump(s);const arrowHeadBase = V.add(s,d);turtle.goto(arrowHeadBase);turtle.goto(V.add(arrowHeadBase, V.trans(V.rot2d(-V.angle(d)), [-length, width/2])));turtle.jump(V.add(arrowHeadBase, V.trans(V.rot2d(-V.angle(d)), [-length, -width/2])));turtle.goto(arrowHeadBase);} static circlesTangents(c1_center, c1_radius, c2_center, c2_radius, internal = false) { let middle_circle = [V.scale(V.sub(c1_center, c2_center), .5)].map(hwp => [V.add(c2_center, hwp), V.len(hwp)]).pop(); if(!internal && c1_radius == c2_radius) { let target = V.sub(c2_center, c1_center); let scaledTarget = V.scale(target, c1_radius/V.len(target)); let partResult = [ V.add(c1_center, V.trans(V.rot2d(Math.PI/2), scaledTarget)), V.add(c1_center, V.trans(V.rot2d(Math.PI/-2), scaledTarget)) ]; return [ partResult, partResult.map(pt => V.add(pt, target)) ] } let swap = !internal && c2_radius > c1_radius; if(swap) { let t = [[...c1_center], c1_radius]; c1_center = c2_center; c1_radius = c2_radius; c2_center = t[0]; c2_radius = t[1]; } let internal_waypoints = intersectCircles2(c1_center, c1_radius + (internal?c2_radius:-c2_radius), ...middle_circle); if(internal_waypoints.length == 0) return []; const circlePointAtDirection2 = (circle_center, radius, direction) => V.add(circle_center, V.scale(direction, radius/V.len(direction))); const result = [ [ // circle 1, point 1 and 2 circlePointAtDirection2(c1_center, c1_radius, V.sub(internal_waypoints[0], c1_center)), circlePointAtDirection2(c1_center, c1_radius, V.sub(internal_waypoints[1], c1_center)) ], [ // circle 2, point 1 and 2 circlePointAtDirection2(c2_center, c2_radius, internal? V.sub(c1_center, internal_waypoints[0]): V.sub(internal_waypoints[0], c1_center)), circlePointAtDirection2(c2_center, c2_radius, internal? V.sub(c1_center, internal_waypoints[1]): V.sub(internal_waypoints[1], c1_center)) ] ]; return swap? [[result[1][1],result[1][0]],[result[0][1],result[0][0]]]: result; } } this.PT = PathTools; class Complex { static add(a,b) { return V.add(a,b); } static sub(a,b) { return V.sub(a,b); } static scale(a,s) { return V.scale(a,s); } static mult(a,b) { return [a[0]*b[0]-a[1]*b[1],a[0]*b[1]+a[1]*b[0]]; } static sqrt(a) { return [[Math.hypot(...a)**.5, Math.atan2(...a.reverse()) / 2]].map(ra => [ra[0]*Math.cos(ra[1]), ra[0]*Math.sin(ra[1])]).pop(); } } this.C = Complex; class Numbers { static approx(a,b,p) { return Math.abs(a-b) < (p === undefined? .001: p); } static clamp(a, min, max) { return Math.min(Math.max(a, min), max); } } this.N = Numbers; } //////////////////////////////////////////////////////////////// // Polygon Clipping utility code - Created by Reinder Nijhoff 2019 // (Polygon binning by Lionel Lemarie 2021) https://turtletoy.net/turtle/95f33bd383 // (Delegated Hatching by Jurgen Westerhof 2024) https://turtletoy.net/turtle/d068ad6040 // (Deferred Polygon Drawing by Jurgen Westerhof 2024) https://turtletoy.net/turtle/6f3d2bc0b5 // https://turtletoy.net/turtle/a5befa1f8d // // const polygons = new Polygons(); // const p = polygons.create(); // polygons.draw(turtle, p); // polygons.list(); // polygons.startDeferSession(); // polygons.stopDeferring(); // polygons.finalizeDeferSession(turtle); // // p.addPoints(...[[x,y],]); // p.addSegments(...[[x,y],]); // p.addOutline(); // p.addHatching(angle, distance); OR p.addHatching(HatchObject); where HatchObject has a method 'hatch(PolygonClass, thisPolygonInstance)' // p.inside([x,y]); // p.boolean(polygon, diff = true); // p.segment_intersect([x,y], [x,y], [x,y], [x,y]); //////////////////////////////////////////////////////////////// function Polygons(){const t=[],s=25,e=Array.from({length:s**2},t=>[]),n=class{constructor(){this.cp=[],this.dp=[],this.aabb=[]}addPoints(...t){let s=1e5,e=-1e5,n=1e5,h=-1e5;(this.cp=[...this.cp,...t]).forEach(t=>{s=Math.min(s,t[0]),e=Math.max(e,t[0]),n=Math.min(n,t[1]),h=Math.max(h,t[1])}),this.aabb=[s,n,e,h]}addSegments(...t){t.forEach(t=>this.dp.push(t))}addOutline(){for(let t=0,s=this.cp.length;t<s;t++)this.dp.push(this.cp[t],this.cp[(t+1)%s])}draw(t){for(let s=0,e=this.dp.length;s<e;s+=2)t.jump(this.dp[s]),t.goto(this.dp[s+1])}addHatching(t, s) {if(typeof t == 'object') return t.hatch(n, this);const e=new n;e.cp.push([-1e5,-1e5],[1e5,-1e5],[1e5,1e5],[-1e5,1e5]);const h=Math.sin(t)*s,o=Math.cos(t)*s,a=200*Math.sin(t),i=200*Math.cos(t);for(let t=.5;t<150/s;t++) {e.dp.push([h*t+i,o*t-a],[h*t-i,o*t+a]);e.dp.push([-h*t+i,-o*t-a],[-h*t-i,-o*t+a]);}e.boolean(this,!1);this.dp=[...this.dp,...e.dp]}inside(t){let s=0;for(let e=0,n=this.cp.length;e<n;e++)this.segment_intersect(t,[.1,-1e3],this.cp[e],this.cp[(e+1)%n])&&s++;return 1&s}boolean(t,s=!0){const e=[];for(let n=0,h=this.dp.length;n<h;n+=2){const h=this.dp[n],o=this.dp[n+1],a=[];for(let s=0,e=t.cp.length;s<e;s++){const n=this.segment_intersect(h,o,t.cp[s],t.cp[(s+1)%e]);!1!==n&&a.push(n)}if(0===a.length)s===!t.inside(h)&&e.push(h,o);else{a.push(h,o);const n=o[0]-h[0],i=o[1]-h[1];a.sort((t,s)=>(t[0]-h[0])*n+(t[1]-h[1])*i-(s[0]-h[0])*n-(s[1]-h[1])*i);for(let n=0;n<a.length-1;n++)(a[n][0]-a[n+1][0])**2+(a[n][1]-a[n+1][1])**2>=.001&&s===!t.inside([(a[n][0]+a[n+1][0])/2,(a[n][1]+a[n+1][1])/2])&&e.push(a[n],a[n+1])}}return(this.dp=e).length>0}segment_intersect(t,s,e,n){const h=(n[1]-e[1])*(s[0]-t[0])-(n[0]-e[0])*(s[1]-t[1]);if(0===h)return!1;const o=((n[0]-e[0])*(t[1]-e[1])-(n[1]-e[1])*(t[0]-e[0]))/h,a=((s[0]-t[0])*(t[1]-e[1])-(s[1]-t[1])*(t[0]-e[0]))/h;return o>=0&&o<=1&&a>=0&&a<=1&&[t[0]+o*(s[0]-t[0]),t[1]+o*(s[1]-t[1])]}};const y=function(n,j=[]){const h={},o=200/s;for(var a=0;a<s;a++){const c=a*o-100,r=[0,c,200,c+o];if(!(n[3]<r[1]||n[1]>r[3]))for(var i=0;i<s;i++){const c=i*o-100;r[0]=c,r[2]=c+o,n[0]>r[2]||n[2]<r[0]||e[i+a*s].forEach(s=>{const e=t[s];n[3]<e.aabb[1]||n[1]>e.aabb[3]||n[0]>e.aabb[2]||n[2]<e.aabb[0]||j.includes(s)||(h[s]=1)})}}return Array.from(Object.keys(h),s=>t[s])};return{list:()=>t,create:()=>new n,draw:(n,h,o=!0)=>{rpl=y(h.aabb, this.dei === undefined? []: Array.from({length: t.length - this.dei}).map((e, i) => this.dsi + i));for(let t=0;t<rpl.length&&h.boolean(rpl[t]);t++);const td=n.isdown();if(this.dsi!==undefined&&this.dei===undefined)n.pu();h.draw(n),o&&function(n){t.push(n);const h=t.length-1,o=200/s;e.forEach((t,e)=>{const a=e%s*o-100,i=(e/s|0)*o-100,c=[a,i,a+o,i+o];c[3]<n.aabb[1]||c[1]>n.aabb[3]||c[0]>n.aabb[2]||c[2]<n.aabb[0]||t.push(h)})}(h);if(td)n.pd();},startDeferSession:()=>{if(this.dei!==undefined)throw new Error('Finalize deferring before starting new session');this.dsi=t.length;},stopDeferring:()=>{if(this.dsi === undefined)throw new Error('Start deferring before stopping');this.dei=t.length;},finalizeDeferSession:(n)=>{if(this.dei===undefined)throw new Error('Stop deferring before finalizing');for(let i=this.dsi;i<this.dei;i++) {rpl = y(t[i].aabb,Array.from({length:this.dei-this.dsi+1}).map((e,j)=>i+j));for(let j=0;j<rpl.length&&t[i].boolean(rpl[j]);j++);t[i].draw(n);}this.dsi=undefined;this.dei=undefined;}}} /////////////////////////////////////////////////////////////////// // This is only the part of the Polygon Hatching utility code // required in this turtle. For the full hatchery code see // https://turtletoy.net/turtle/d068ad6040 // /////////////////////////////////////////////////////////////////// // Polygon Hatching utility code - Created by Jurgen Westerhof 2024 // https://turtletoy.net/turtle/d068ad6040 // //////////////////////////////////////////////////////////////// // // To be used with modified Polygon Clipping utility code // // Orginal: https://turtletoy.net/turtle/a5befa1f8d // // Polygon binning: https://turtletoy.net/turtle/95f33bd383 // // Delegated Hatching: https://turtletoy.net/turtle/d068ad6040 /////////////////////////////////////////////////////////////////// function loadHatcheryNamespace() { //for convenience on Turtletoy you can click the arrow to the right of the line number to collapse a class //////////////////////////////////////////////////// root class PolygonHatching { constructor() { if (this.constructor === PolygonHatching) { throw new Error("PolygonHatching is an abstract class and can't be instantiated."); } this.minX = -100; this.minY = -100; this.maxX = 100; this.maxY = 100; this.width = 200; this.height = 200; this.segments = []; this.init(); } hatch(polygonsClass, thePolygonToHatch) { const e = new polygonsClass; e.cp.push(...thePolygonToHatch.aabb);//[-1e5,-1e5],[1e5,-1e5],[1e5,1e5],[-1e5,1e5]); this.addHatchSegments(e.dp); e.boolean(thePolygonToHatch,!1); thePolygonToHatch.dp=[...thePolygonToHatch.dp,...e.dp] } addHatchSegments(segments) { this.getSegments().forEach(e => segments.push(e)); } getSegments() { return this.segments; } init() { /////////////////////////////////////////////////////// // Vector functions - Created by Jurgen Westerhof 2024 // https://turtletoy.net/turtle/d068ad6040 /////////////////////////////////////////////////////// class Vector { static add (a,b) { return a.map((v,i)=>v+b[i]); } static sub (a,b) { return a.map((v,i)=>v-b[i]); } static mul (a,b) { return a.map((v,i)=>v*b[i]); } static div (a,b) { return a.map((v,i)=>v/b[i]); } static scale(a,s) { return a.map(v=>v*s); } static det(m) { return m.length == 1? m[0][0]: m.length == 2 ? m[0][0]*m[1][1]-m[0][1]*m[1][0]: m[0].reduce((r,e,i) => r+(-1)**(i+2)*e*this.det(m.slice(1).map(c => c.filter((_,j) => i != j))),0); } static angle(a) { return Math.PI - Math.atan2(a[1], -a[0]); } //compatible with turtletoy heading static rot2d(angle) { return [[Math.cos(angle), -Math.sin(angle)], [Math.sin(angle), Math.cos(angle)]]; } static rot3d(yaw,pitch,roll) { return [[Math.cos(yaw)*Math.cos(pitch), Math.cos(yaw)*Math.sin(pitch)*Math.sin(roll)-Math.sin(yaw)*Math.cos(roll), Math.cos(yaw)*Math.sin(pitch)*Math.cos(roll)+Math.sin(yaw)*Math.sin(roll)],[Math.sin(yaw)*Math.cos(pitch), Math.sin(yaw)*Math.sin(pitch)*Math.sin(roll)+Math.cos(yaw)*Math.cos(roll), Math.sin(yaw)*Math.sin(pitch)*Math.cos(roll)-Math.cos(yaw)*Math.sin(roll)],[-Math.sin(pitch), Math.cos(pitch)*Math.sin(roll), Math.cos(pitch)*Math.cos(roll)]]; } static trans(matrix,a) { return a.map((v,i) => a.reduce((acc, cur, ci) => acc + cur * matrix[ci][i], 0)); } //Mirror vector a in a ray through [0,0] with direction mirror static mirror2d(a,mirror) { return [Math.atan2(...mirror)].map(angle => this.trans(this.rot2d(angle), this.mul([-1,1], this.trans(this.rot2d(-angle), a)))).pop(); } static approx(a,b,p) { return this.len(this.sub(a,b)) < (p === undefined? .001: p); } static norm (a) { return this.scale(a,1/this.len(a)); } static len (a) { return Math.hypot(...a); } static lenSq (a) { return a.reduce((a,c)=>a+c**2,0); } static lerp (a,b,t) { return a.map((v, i) => v*(1-t) + b[i]*t); } static dist (a,b) { return Math.hypot(...this.sub(a,b)); } static dot (a,b) { return a.reduce((a,c,i) => a+c*b[i], 0); } static cross(...ab) { return ab[0].map((e, i) => ab.map(v => v.filter((ee, ii) => ii != i))).map((m,i) => (i%2==0?-1:1)*this.det(m)); } } this.V = Vector; class Intersection2D { //a-start, a-direction, b-start, b-direction //returns false on no intersection or [[intersection:x,y], scalar a-direction, scalar b-direction static info(as, ad, bs, bd) { const d = V.sub(bs, as), det = -V.det([bd, ad]); if(det === 0) return false; const res = [V.det([d, bd]) / det, V.det([d, ad]) / det]; return [V.add(as, V.scale(ad, res[0])), ...res]; } static ray(a, b, c, d) { return this.info(a, b, c, d); } static segment(a,b,c,d, inclusiveStart = true, inclusiveEnd = true) { const i = this.info(a, V.sub(b, a), c, V.sub(d, c)); return i === false? false: ( (inclusiveStart? 0<=i[1] && 0<=i[2]: 0<i[1] && 0<i[2]) && (inclusiveEnd? i[1]<=1 && i[2]<=1: i[1]<1 && i[2]<1) )?i[0]:false; } } this.Intersection = Intersection2D; } } this.PolygonHatching = PolygonHatching; class LineHatching extends PolygonHatching { constructor(angle, distance, inp = 0) { super(); const h=Math.sin(angle)*distance,o=Math.cos(angle)*distance,a=200*Math.sin(angle),i=200*Math.cos(angle); this.segments = Array.from({length: 150/distance}).flatMap((x,y,z,t=.5+y) => [ [h*t+i+inp*(Math.random()-.5),o*t-a+inp*(Math.random()-.5)],[h*t-i+inp*(Math.random()-.5),o*t+a+inp*(Math.random()-.5)], [-h*t+i+inp*(Math.random()-.5),-o*t-a+inp*(Math.random()-.5)],[-h*t-i+inp*(Math.random()-.5),-o*t+a+inp*(Math.random()-.5)] ]) } } this.LineHatching = LineHatching; } //////////////////////////////////////////////////////////////// // Text utility code. Created by Reinder Nijhoff 2024 // https://turtletoy.net/turtle/0f84fd3ae4 // Modifications by Jurgen Westerhof 2024: https://turtletoy.net/turtle/9aef87b45e // - Does not force turtle to operate in radians // - Now respects turtle.isdown() and doesn't print if true // - Text.size(turtle, str, scale = 1) returns properties of text str to be printed // - Text.bbox(turtle, str, scale = 1) returns bounding box of text str to be printed // - Text.dimensions(scale = 1) returns properties of fontData for scale // - Text.bboxPrint(turtle, str, scale = 1) prints the text and returns the absolute bounding box //////////////////////////////////////////////////////////////// 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 RangeFinderTurtle extends Turtle { constructor(x, y) {super(x, y);this.reset();} goto(x, y) {super.goto(x, y);const [a, b] = this.pos();this.rangeX = [Math.min(a, this.rangeX? this.rangeX[0]: a), Math.max(a, this.rangeX? this.rangeX[1]: a)];this.rangeY = [Math.min(b, this.rangeY? this.rangeY[0]: b), Math.max(b, this.rangeY? this.rangeY[1]: b)];} reset() {const [a, b] = this.pos();this.rangeX = [a, a];this.rangeY = [b, b];} bbox() { return [[this.rangeX[0], this.rangeY[0]], [this.rangeX[1], this.rangeY[1]]]; } } class Text { print (t, str, scale = 1) { const isDown = t && t.isdown(); let pos = t ? [t.x(), t.y()] : [0,0], h = t ? t.h() * 2*Math.PI/t.fullCircle() : 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 && (!isDown || t.down()); } }); pos = rotAdd([glyph.x*s, 0], pos, h); }); if(isDown) t.down(); return rotAdd([0, 10*scale], pos, h); } bboxPrint(t, str, scale = 1) { const rft = new RangeFinderTurtle(t.pos()); rft.degrees(t.fullCircle()); rft.seth(t.h()); rft.up(); this.print(rft, str, scale); this.print(t, str, scale); return rft.bbox(); } size(t, str, scale = 1) { const rft = new RangeFinderTurtle(); rft.up(); this.print(rft, str, scale); return { width: rft.rangeX[1] - rft.rangeX[0], height: rft.rangeY[1] - rft.rangeY[0], bbox: t.h() === 0? rft.bbox(): this.bbox(t, str, scale) } } bbox(t, str, scale = 1) { const rft = new RangeFinderTurtle(); rft.degrees(t.fullCircle()); rft.seth(t.h()); rft.up(); this.print(rft, str, scale); return rft.bbox(); } dimensions(scale = 1, standardCharacter = 'x') { const t = new Turtle(); const x = this.size(t, standardCharacter, scale); const allLower = this.size(t, 'abcdefghijklmnopqrstuvwxyz', scale); const allUpper = this.size(t, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', scale); // property names from: https://www.onlineprinters.co.uk/magazine/font-sizes/ return { height: x.height, width: x.width, ascenders: Math.abs(allLower.bbox[0][1]) - x.height, descenders: Math.abs(allLower.bbox[1][1]), capitalHeight: Math.abs(allUpper.bbox[0][1]), capitalDescenders: Math.abs(allUpper.bbox[1][1]) }; } } return new Text(); }