Lucas Kent e39465ad2f Changes to be committed:
new file:   Files/flashplayer_32_sa.exe
	new file:   favicon.ico
	new file:   globe.gif
	new file:   imgs/download.png
	new file:   imgs/zuck.jpg
	new file:   index.html
	new file:   other.ico
	new file:   script.js
	new file:   site.webmanifest
	new file:   sitemap.html
	new file:   styles/backround.css
	new file:   styles/border.css
	new file:   styles/fonts/Titillium_Web/OFL.txt
	new file:   styles/fonts/Titillium_Web/TitilliumWeb-Black.ttf
	new file:   styles/fonts/Titillium_Web/TitilliumWeb-Bold.ttf
	new file:   styles/fonts/Titillium_Web/TitilliumWeb-BoldItalic.ttf
	new file:   styles/fonts/Titillium_Web/TitilliumWeb-ExtraLight.ttf
	new file:   styles/fonts/Titillium_Web/TitilliumWeb-ExtraLightItalic.ttf
	new file:   styles/fonts/Titillium_Web/TitilliumWeb-Italic.ttf
	new file:   styles/fonts/Titillium_Web/TitilliumWeb-Light.ttf
	new file:   styles/fonts/Titillium_Web/TitilliumWeb-LightItalic.ttf
	new file:   styles/fonts/Titillium_Web/TitilliumWeb-Regular.ttf
	new file:   styles/fonts/Titillium_Web/TitilliumWeb-SemiBold.ttf
	new file:   styles/fonts/Titillium_Web/TitilliumWeb-SemiBoldItalic.ttf
	new file:   styles/fonts/webfontkit-20221027-163353/generator_config.txt
	new file:   styles/fonts/webfontkit-20221027-163353/specimen_files/grid_12-825-55-15.css
	new file:   styles/fonts/webfontkit-20221027-163353/specimen_files/specimen_stylesheet.css
	new file:   styles/fonts/webfontkit-20221027-163353/stylesheet.css
	new file:   styles/fonts/webfontkit-20221027-163353/titilliumweb-extralight-demo.html
	new file:   styles/fonts/webfontkit-20221027-163353/titilliumweb-extralight-webfont.woff
	new file:   styles/fonts/webfontkit-20221027-163353/titilliumweb-extralight-webfont.woff2
	new file:   styles/fonts/webfontkit-20221027-165950/generator_config.txt
	new file:   styles/fonts/webfontkit-20221027-165950/specimen_files/grid_12-825-55-15.css
	new file:   styles/fonts/webfontkit-20221027-165950/specimen_files/specimen_stylesheet.css
	new file:   styles/fonts/webfontkit-20221027-165950/stylesheet.css
	new file:   styles/fonts/webfontkit-20221027-165950/titilliumweb-bold-demo.html
	new file:   styles/fonts/webfontkit-20221027-165950/titilliumweb-bold-webfont.woff
	new file:   styles/fonts/webfontkit-20221027-165950/titilliumweb-bold-webfont.woff2
	new file:   styles/style.css
	new file:   tools/2048/.gitignore
	new file:   tools/2048/.jshintrc
	new file:   tools/2048/CONTRIBUTING.md
	new file:   tools/2048/LICENSE.txt
	new file:   tools/2048/README.md
	new file:   tools/2048/Rakefile
	new file:   tools/2048/favicon.ico
	new file:   tools/2048/index.html
	new file:   tools/2048/js/animframe_polyfill.js
	new file:   tools/2048/js/application.js
	new file:   tools/2048/js/bind_polyfill.js
	new file:   tools/2048/js/classlist_polyfill.js
	new file:   tools/2048/js/game_manager.js
	new file:   tools/2048/js/grid.js
	new file:   tools/2048/js/html_actuator.js
	new file:   tools/2048/js/keyboard_input_manager.js
	new file:   tools/2048/js/local_storage_manager.js
	new file:   tools/2048/js/tile.js
    new file:   tools/2048/meta/apple-touch-icon.png
	new file:   tools/webretro/cores/neocd_libretro.js
	new file:   tools/webretro/cores/neocd_libretro.wasm
	new file:   tools/webretro/cores/nestopia_libretro.js
	new file:   tools/webretro/cores/nestopia_libretro.wasm
	new file:   tools/webretro/cores/o2em_libretro.js
	new file:   tools/webretro/cores/o2em_libretro.wasm
	new file:   tools/webretro/cores/opera_libretro.js
	new file:   tools/webretro/cores/opera_libretro.wasm
2022-11-02 08:40:01 -04:00

508 lines
18 KiB
JavaScript

let my={}
function init(wd,ht){let version='0.35'
my.wd=typeof wd!=='undefined'?wd:360
my.ht=typeof ht!=='undefined'?ht:(my.wd*7)/4
let s=''
s+='<div id="main" style="position:relative; width:'+my.wd+'px; min-height:'+my.ht+'px; border: none; background-color: white; margin:auto; display:block; box-shadow: 0px 0px 19px 10px rgba(0,0,68,0.46); border-radius: 10px;">'
s+='<canvas id="canvas1" style="position: absolute; left: 0; top:0; border: none;"></canvas>'
s+='<canvas id="canvas2" style="position: absolute; left: 0; top:0; border: none;"></canvas>'
s+='<div id="optpop" style="position:absolute; left:-450px; bottom:10px; width:460px; padding: 5px; border-radius: 9px; background-color: #88aaff; box-shadow: 10px 10px 5px 0px rgba(40,40,40,0.75); transition: all linear 0.3s; opacity:0; text-align: center; "></div>'
let props=[['mass','Mass','onMassChg','#6600cc','kg',0.5],['rad','Radius','onRadChg','#0000ff','m',0.14],['grav','Gravity','onGravChg','orangered','m/s2',0],['dense','Air/Water/Oil','onDenseChg','yellowgreen','kg/m3',0],]
s+='<div style="visibility:hidden;">'
for(let i=0;i<props.length;i++){let p=props[i]
s+='<div style="position:absolute; top:'+(40+i*30)+'px; right:5px; width: 390px; border-radius: 9px; font: 20px Arial; box-shadow: 0px 0px 6px 0px '+p[3]+'; ">'
s+='<div style="display: inline-block; font: 15px Arial; color:'+p[3]+'; width:100px; text-align: right;">'+p[1]+':</div>'
s+='<input type="range" id="'+p[0]+'r" value="0" min="0" max="1" step=".01" style="z-index:2; width:200px; height:10px; border: none; " oninput="'+p[2]+'(0,this.value)" onchange="'+p[2]+'(1,this.value)" />'
s+='<div id="'+p[0]+'" style="display: inline-block; width:50px;font: 20px Arial; color:'+p[3]+'; text-align: center; ">1</div>'
s+='</div>'}
s+='</div>'
s+='<button id="restart" style="position: absolute; bottom:2px; right:40px; font: 14px Arial; height:30px; vertical-align:middle; z-index: 10;" class="btn" onclick="newGame()" >New</button>'
s+='<button id="playBtn" style="position: absolute; top:35px; left:53px; font: 14px Arial; height:30px; vertical-align:middle; z-index: 10; visibility:hidden; " class="btn" onclick="doPlay(-1)" >Pause</button>'
s+='<div id="fact" style="position: absolute; left:'+(my.wd-200)/2+'px; top: 4px; font: 30px Verdana; width:200px; text-align:center; color:white;">0</div>'
s+='<div id="score" style="position: absolute; left:'+(my.wd-200)/2+'px; top:38px; font: 30px Verdana; width:200px; text-align:center; color:gold;">0</div>'
s+=wrap({cls:'copyrt',pos:'abs',style:'left:35px; bottom:3px'},`&copy; 2022 Rod Pierce v${version}`)
s+='</div>'
docInsert(s)
my.can=new Can('canvas1',my.wd,my.ht,2)
my.can2=new Can('canvas2',my.wd,my.ht,2)
my.clrs=[['Blue','#0000FF'],['Red','#FF0000'],['Black','#000000'],['Green','#00cc00'],['Orange','#FFA500'],['Slate Blue','#6A5ACD'],['Lime','#00FF00'],['Spring Green','#00FF7F'],['Teal','#008080'],['Gold','#ffd700'],['Med Purple','#aa00aa'],['Light Blue','#ADD8E6'],['Navy','#000080'],['Purple','#800080'],['Dark SeaGreen','#8FBC8F'],]
clrNum=0
let el2=my.can2.el
el2.addEventListener('touchstart',ontouchstart,false)
el2.addEventListener('touchmove',ontouchmove,false)
el2.addEventListener('touchend',ontouchend,false)
el2.addEventListener('mousedown',onMouseDown,false)
window.addEventListener('mousemove',onMouseMove,false)
window.addEventListener('mouseup',onMouseUp,false)
my.maxBalln=100
my.holeRad=36
let gap=7
let tHt=20
let midMult=3
my.pockets=[{x:-gap,y:-gap,xt:0,yt:tHt},{x:my.wd+gap,y:-gap,xt:my.wd-tHt*0.8,yt:tHt},{x:-gap*midMult,y:my.ht/2,xt:0,yt:my.ht/2+tHt/2},{x:my.wd+gap*midMult,y:my.ht/2,xt:my.wd-tHt*0.8,yt:my.ht/2+tHt/2},{x:-gap,y:my.ht+gap,xt:0,yt:my.ht},{x:my.wd+gap,y:my.ht+gap,xt:my.wd-tHt*0.8,yt:my.ht},]
midY=my.ht/2+30
timeIncr=24*3600
univTime=0
gFact=6.673e-11
extents=[0e10,0e10,2e10,2e10]
newExtents=[0,0,1e10,1e10]
clrNum=0
playQ=false
m=1e24
massNum=10
radNum=10
my.elasticity=0.95
grav=9.8
dense=1
my.frameRate=1/40
my.balls=[]
my.newball=new Ball()
my.drag={n:0,onQ:false,holdX:0,holdY:0}
my.mouse={x:0,y:0,isDown:false}
let g=my.can.g
g.fillStyle='red'
g.strokeStyle='#000000'
for(let i=0;i<props.length;i++){let p=props[i]
document.getElementById(p[0]+'r').value=p[5]
window[p[2]](0,p[5])}
newGame()
doPlay(1)}
function loop(){if(my.veln==0)return
if(!my.mouse.isDown){my.veln=doVels()
my.can.clear()
drawTable()
doCollisions()
for(let i=0;i<my.balls.length;i++){let ball=my.balls[i]
ball.vx=applyDrag(ball.vx,ball.radius,ball.mass)
ball.vy=applyDrag(ball.vy,ball.radius,ball.mass)
ball.vy+=grav*my.frameRate
let dx=ball.vx*my.frameRate*100
let dy=ball.vy*my.frameRate*100
ball.x+=dx
ball.y+=dy
let wallQ=false
if(ball.y>my.ht-ball.radius){ball.vx*=0.995
ball.vy*=-my.elasticity
ball.y=my.ht-ball.radius
wallQ=true}
if(ball.y<ball.radius){ball.vx*=0.995
ball.vy*=-my.elasticity
ball.y=ball.radius
wallQ=true}
if(ball.x>my.wd-ball.radius){ball.vy*=0.995
ball.vx*=-my.elasticity
ball.x=my.wd-ball.radius
wallQ=true}
if(ball.x<ball.radius){ball.vy*=0.995
ball.vx*=-my.elasticity
ball.x=ball.radius
wallQ=true}
if(wallQ)doPocket(ball)
drawBall(ball)}}}
function doVels(){let veln=0
for(let i=0;i<my.balls.length;i++){let a=my.balls[i]
let aVel=dist(a.vx,a.vy)
if(aVel==0)continue
veln++
if(1==1){a.vx*=0.993
a.vy*=0.993}
if(aVel<0.5){a.vx*=0.99
a.vy*=0.99}
if(aVel<0.04){a.vx=0
a.vy=0}}
return veln}
function doPocket(ball){for(let i=0;i<my.pockets.length;i++){let pock=my.pockets[i]
if(dist(ball.x-pock.x,ball.y-pock.y)<my.holeRad*1.25){if(ball.collQ){if(my.hita==-1){my.hita=i
let a=my.pockets[my.hita].num
let s=a+' x '+'_'+' = '
document.getElementById('fact').innerHTML=s}else{my.hitb=i
doQuest()}}
delBall(ball)
break}}}
function doQuest(){let a=my.pockets[my.hita].num
let b=my.pockets[my.hitb].num
let s=a+' x '+b+' = '
console.log('doQuest',s)
my.hita=-1
my.hitb=-1
let ans=a*b
s+=ans
document.getElementById('fact').innerHTML=s
my.score+=ans
document.getElementById('score').innerHTML=my.score}
function delBall(ball){for(let i=0;i<my.balls.length;i++){if(my.balls[i]===ball){my.balls.splice(i,1)}}}
function drawTable(){let g=my.can.g
g.fillStyle='rgba(0,222,0,0.3)'
g.beginPath()
g.rect(0,0,my.wd,my.ht)
g.fill()
for(let i=0;i<my.pockets.length;i++){let pock=my.pockets[i]
g.fillStyle='rgba(0,222,0,0.3)'
for(let j=0;j<7;j++){g.beginPath()
g.arc(pock.x,pock.y,my.holeRad-j,0,2*Math.PI)
g.fill()}
if(i==my.hita||i==my.hitb){g.fillStyle='black'
g.font='24px Arial'}else{g.fillStyle='white'
g.font='24px Arial'}
g.fillText(pock.num,pock.xt,pock.yt)}}
function gameOver(){console.log('gameOver')}
class Pt{constructor(xnew,ynew){this.x=xnew
this.y=ynew}
toString(){return this.x+','+this.y}
rotate(angle){let cosa=Math.cos(angle)
let sina=Math.sin(angle)
let xPos=this.x*cosa+this.y*sina
let yPos=-this.x*sina+this.y*cosa
return new Pt(xPos,yPos)}
rotateMe(angle){let t=new Pt(this.x,this.y).rotate(angle)
this.x=t.x
this.y=t.y}}
function doCollisions(){for(let i=0;i<my.balls.length-1;i++){let a=my.balls[i]
for(let j=i+1;j<my.balls.length;j++){let b=my.balls[j]
let dx=a.x-b.x
let dy=a.y-b.y
let dSqr=dx*dx+dy*dy
let radTot=a.radius+b.radius
let radSqr=radTot*radTot
if(radSqr-dSqr>0){let norm=new Pt(b.x-a.x,b.y-a.y)
let ang=Math.atan2(norm.y,norm.x)
if(1==0){my.can2.clear()
let g2=my.can2.g
g2.beginPath()
g2.lineWidth=1
g2.strokeStyle=a.clr
g2.arc(a.x,a.y,a.radius,0,Math.PI*2,true)
g2.stroke()
g2.closePath()
g2.beginPath()
g2.strokeStyle=b.clr
g2.arc(b.x,b.y,b.radius,0,Math.PI*2,true)
g2.stroke()
g2.closePath()
g2.beginPath()
g2.strokeStyle='black'
g2.moveTo(a.x,a.y)
g2.lineTo(b.x,b.y)
g2.stroke()
g2.closePath()
g2.fillText(ang.toPrecision(4),a.x,a.y)}
let aVel=new Pt(a.vx,a.vy)
aVel.rotateMe(ang)
let bVel=new Pt(b.vx,b.vy)
bVel.rotateMe(ang)
let va=(my.elasticity*b.mass*(bVel.x-aVel.x)+a.mass*aVel.x+b.mass*bVel.x)/(a.mass+b.mass)
let vb=(my.elasticity*a.mass*(aVel.x-bVel.x)+a.mass*aVel.x+b.mass*bVel.x)/(a.mass+b.mass)
aVel.x=va
bVel.x=vb
aVel.rotateMe(-ang)
a.vx=aVel.x
a.vy=aVel.y
bVel.rotateMe(-ang)
b.vx=bVel.x
b.vy=bVel.y
let olap=radTot-Math.sqrt(dSqr)
olap*=0.5/2
let aPos=new Pt(a.x,a.y)
aPos.rotateMe(ang)
aPos.x-=olap
let bPos=new Pt(b.x,b.y)
bPos.rotateMe(ang)
bPos.x+=olap
aPos.rotateMe(-ang)
a.x=aPos.x
a.y=aPos.y
bPos.rotateMe(-ang)
b.x=bPos.x
b.y=bPos.y
a.collQ=true
b.collQ=true}}}}
function drawBall(ball){let g=my.can.g
g.beginPath()
let alpha=Math.min(1,Math.log(ball.mass)*0.16-0.1)
if(1==0){g.fillStyle=hex2rgba(ball.clr,alpha)
g.arc(ball.x,ball.y,ball.radius,0,Math.PI*2,true)
g.fill()
g.closePath()}else{g.ball(ball,ball.x,ball.y)}}
function applyDrag(v,r,mass){let Cd=0.47
let A=(Math.PI*r*r)/10000
let F=0.5*Cd*A*dense*v*v
let a=F/mass
let vd=a*my.frameRate
vd=Math.min(Math.abs(vd),Math.abs(v))
vd=Math.abs(vd)*sign(v)
v-=vd
return v}
function sign(n){return n==0?0:n/Math.abs(n)}
function ontouchstart(ev){let touch=ev.targetTouches[0]
ev.clientX=touch.clientX
ev.clientY=touch.clientY
ev.touchQ=true
onMouseDown(ev)}
function ontouchmove(ev){let touch=ev.targetTouches[0]
ev.clientX=touch.clientX
ev.clientY=touch.clientY
ev.touchQ=true
onMouseMove(ev)}
function ontouchend(){my.drag.onQ=false}
function onMouseDown(ev){let[mouseX,mouseY]=my.can.mousePos(ev)
my.mouse.x=mouseX
my.mouse.y=mouseY
my.mouse.isDown=true
my.newball=new Ball()
my.newball.x=mouseX
my.newball.y=mouseY
my.newball.mass=massNum
my.newball.radius=radNum
clrNum=++clrNum%my.clrs.length
my.newball.clr=my.clrs[clrNum][1]
drawBall(my.newball)
my.drag.onQ=true
if(ev.preventDefault){ev.preventDefault()}
return false}
function onMouseMove(ev){let[mouseX,mouseY]=my.can.mousePos(ev)
if(my.drag.onQ){let g2=my.can2.g
my.can2.clear()
g2.lineWidth=1
g2.strokeStyle='blue'
let len=dist(my.mouse.x-mouseX,my.mouse.y-mouseY)
let ang=Math.atan2(-(my.mouse.y-mouseY),my.mouse.x-mouseX)
g2.drawCue(my.mouse.x,my.mouse.y,len,ang)}
if(ev.preventDefault){ev.preventDefault()}
return false}
function onMouseUp(ev){let[mouseX,mouseY]=my.can.mousePos(ev)
my.drag.onQ=false
my.can2.clear()
let xm=mouseX
let ym=mouseY
my.veln=1
my.mouse.isDown=false
my.newball.vx=(my.newball.x-mouseX)*0.05
my.newball.vy=(my.newball.y-mouseY)*0.05
my.newball.mass=massNum
my.newball.radius=radNum
my.balls.push(my.newball)
for(let i=0;i<my.balls.length;i++){my.balls[i].collQ=false}}
function wallChg(){let div=document.getElementById('wallType')
wallType=div.options[div.selectedIndex].text
wallType=wallType.toLowerCase()
console.log('wallChg',wallType)}
function onMassChg(n,v){v=Number(v)*2+1
v=Number(Math.pow(10,v).toPrecision(2))
document.getElementById('mass').innerHTML=v
massNum=v
my.newball.mass=v}
function onRadChg(n,v){v=Number(v)*1+1
v=Number(Math.pow(10,v).toPrecision(2))
document.getElementById('rad').innerHTML=v
radNum=v
my.newball.radius=v}
function onDenseChg(n,v){v=Number(v)*5
v=Number(Math.pow(10,v).toPrecision(2))
document.getElementById('dense').innerHTML=v
dense=v}
function onGravChg(n,v){v=Number(v)*2
v=Number((Math.pow(10,v)-1).toPrecision(2))
document.getElementById('grav').innerHTML=v
grav=v}
function onElastChg(n,v){console.log('onElastChg='+n,v)
document.getElementById('elast').innerHTML=v
my.elasticity=v}
function newGame(){my.can.clear()
my.can2.clear()
my.hita=-1
my.hitb=-1
my.balls=[]
let nums=[2,3,4,5,6,7,8,9]
for(let i=0;i<my.pockets.length;i++){let pock=my.pockets[i]
let idx=getRandomInt(0,nums.length-1)
let num=nums[idx]
nums.splice(idx,1)
pock.num=num}
drawTable()
setupBalls()
my.score=0
document.getElementById('score').innerHTML=my.score
document.getElementById('fact').innerHTML='0 x 0 = 0'
this.playQ=true
this.frame=0}
function setupBalls(){let mid=my.wd/2
let gap=35
let lvln=3
let tp=100+gap*lvln
let pts=[]
for(let i=0;i<lvln;i++){let wd=i*gap
for(let j=0;j<=i;j++){pts.push({x:mid-wd/2+j*gap,y:tp-i*gap})}}
for(let i=0;i<pts.length;i++){let pt=pts[i]
let ball=new Ball()
ball.x=pt.x
ball.y=pt.y
ball.mass=massNum
ball.radius=radNum
clrNum=++clrNum%my.clrs.length
ball.clr=my.clrs[clrNum][1]
drawBall(ball)
my.balls.push(ball)}}
function animate(){this.frame++
loop()
if(this.playQ)requestAnimationFrame(animate)}
function doPlay(action){if(action==-1){this.playQ=!this.playQ}else{if(action==0){this.playQ=false}else{this.playQ=true}}
if(this.playQ){document.getElementById('playBtn').innerHTML='Pause'}else{document.getElementById('playBtn').innerHTML='Play'}
if(this.playQ){animate()}}
function toggleBtn(btn,onq){if(onq){document.getElementById(btn).classList.add('hi')
document.getElementById(btn).classList.remove('lo')}else{document.getElementById(btn).classList.add('lo')
document.getElementById(btn).classList.remove('hi')}}
function dist(dx,dy){return Math.sqrt(dx*dx+dy*dy)}
function getRandomInt(min,max){return Math.floor(Math.random()*(max-min+1))+min}
function hex2rgba(hex,opacity){hex=hex.replace('#','')
let r=parseInt(hex.substring(0,2),16)
let g=parseInt(hex.substring(2,4),16)
let b=parseInt(hex.substring(4,6),16)
let result='rgba('+r+','+g+','+b+','+opacity+')'
return result}
class Ball{constructor(){this.x=0
this.y=0
this.vx=0
this.vy=0
this.mass=1
this.radius=15
this.collQ=false
this.clr='blue'
this.clr2='white'
this.sleep=false}}
CanvasRenderingContext2D.prototype.ball=function(ball,x,y){let size=ball.radius
this.beginPath()
this.fillStyle=ball.color
this.arc(x,y,size,0,Math.PI*2,true)
let gradient=this.createRadialGradient(x-size/2,y-size/2,0,x,y,size)
gradient.addColorStop(0,ball.clr2)
gradient.addColorStop(1,ball.clr)
this.fillStyle=gradient
this.fill()
this.closePath()
this.beginPath()
this.arc(x,y,size*0.85,(Math.PI/180)*270,(Math.PI/180)*200,true)
gradient=this.createRadialGradient(x-size*0.99,y-size*0.99,0,x,y,size)
gradient.addColorStop(0,ball.clr2)
gradient.addColorStop(0.99,'transparent')
this.fillStyle=gradient
this.fill()}
CanvasRenderingContext2D.prototype.drawArrow=function(x0,y0,totLen,shaftHt,headLen,headHt,angle,sweep,invertQ){let g=this
let pts=[[0,0],[-headLen,-headHt/2],[-headLen+sweep,-shaftHt/2],[-totLen,-shaftHt/2],[-totLen,shaftHt/2],[-headLen+sweep,shaftHt/2],[-headLen,headHt/2],[0,0],]
if(invertQ){pts.push([0,-headHt/2],[-totLen,-headHt/2],[-totLen,headHt/2],[0,headHt/2])}
for(let i=0;i<pts.length;i++){let cosa=Math.cos(-angle)
let sina=Math.sin(-angle)
let xPos=pts[i][0]*cosa+pts[i][1]*sina
let yPos=pts[i][0]*sina-pts[i][1]*cosa
if(i==0){g.moveTo(x0+xPos,y0+yPos)}else{g.lineTo(x0+xPos,y0+yPos)}}}
CanvasRenderingContext2D.prototype.drawCue=function(x0,y0,fillLen,angle){let g=this
let gapLen=20
let headLen=15
let headHt=4
let shaftHt=10
let handLen=70
let totLen=200
let shaftLen=totLen-handLen
let slope=(shaftHt/2-headHt/2)/shaftLen
let cuePts=[[-gapLen,headHt/2],[-(gapLen+headLen),headHt/2+slope*headLen],[-(gapLen+shaftLen),shaftHt/2],[-(gapLen+totLen),shaftHt/2],]
let pts=[]
for(let i=0;i<4;i++){pts.push(cuePts[i])}
drawRot(g,x0,y0,pts,angle,true)
g.strokeStyle='white'
g.stroke()
g.fillStyle='#fc9'
g.fill()
pts=[]
for(let i=0;i<2;i++){pts.push(cuePts[i])}
drawRot(g,x0,y0,pts,angle,true)
g.fillStyle='black'
g.fill()
pts=[]
for(let i=2;i<4;i++){pts.push(cuePts[i])}
drawRot(g,x0,y0,pts,angle,true)
g.fillStyle='#630'
g.fill()
pts=[[-gapLen,1],[-fillLen,3],]
drawRot(g,x0,y0,pts,angle,true)
g.fillStyle='rgba(0,0,255,0.3)'
g.fill()}
function drawRot(g,x0,y0,pts,angle,mirrorQ){if(mirrorQ){let len=pts.length
for(let i=len-1;i>=0;i--){pts.push([pts[i][0],-pts[i][1]])}}
g.beginPath()
for(let i=0;i<pts.length;i++){let cosa=Math.cos(-angle)
let sina=Math.sin(-angle)
let xPos=pts[i][0]*cosa+pts[i][1]*sina
let yPos=pts[i][0]*sina-pts[i][1]*cosa
if(i==0){g.moveTo(x0+xPos,y0+yPos)}else{g.lineTo(x0+xPos,y0+yPos)}}
g.closePath()}
function docInsert(s){let div=document.createElement('div')
div.innerHTML=s
let script=document.currentScript
script.parentElement.insertBefore(div,script)}
class Can{constructor(id,wd,ht,ratio){this.id=id
this.wd=wd
this.ht=ht
this.ratio=ratio
let el=document.getElementById(id)
el.width=wd*ratio
el.style.width=wd+'px'
el.height=ht*ratio
el.style.height=ht+'px'
this.g=el.getContext('2d')
this.g.setTransform(ratio,0,0,ratio,0,0)
this.el=el
return this}
clear(){this.g.clearRect(0,0,this.wd,this.ht)}
mousePos(ev){let bRect=this.el.getBoundingClientRect()
let mouseX=(ev.clientX-bRect.left)*(this.el.width/this.ratio/bRect.width)
let mouseY=(ev.clientY-bRect.top)*(this.el.height/this.ratio/bRect.height)
return[mouseX,mouseY]}}
function wrap({id='',cls='',pos='rel',style='',txt='',tag='div',lbl='',fn='',opts=[]},...mores){let s=''
s+='\n'
txt+=mores.join('')
s+={btn:()=>{if(cls.length==0)cls='btn'
return '<button onclick="'+fn+'"'},can:()=>'<canvas',div:()=>'<div',edit:()=>{let s=''
s+=lbl.length>0?'<label class="label">'+lbl+' ':''
s+='<textarea onkeyup="'+fn+'" onchange="'+fn+'"'
return s},inp:()=>{if(cls.length==0)cls='input'
let s=''
s+=lbl.length>0?'<label class="label">'+lbl+' ':''
s+='<input value="'+txt+'"'
s+=fn.length>0?' oninput="'+fn+'" onchange="'+fn+'"':''
return s},out:()=>{pos='dib'
if(cls.length==0)cls='output'
let s=''
s+=lbl.length>0?'<label class="label">'+lbl+' ':''
s+='<span '
return s},radio:()=>{if(cls.length==0)cls='radio'
return '<div '},sel:()=>{if(cls.length==0)cls='select'
let s=''
s+=lbl.length>0?'<label class="label">'+lbl+' ':''
s+='<select '
s+=fn.length>0?' onchange="'+fn+'"':''
return s},sld:()=>'<input type="range" '+txt+' oninput="'+fn+'" onchange="'+fn+'"',}[tag]()||''
if(id.length>0)s+=' id="'+id+'"'
if(cls.length>0)s+=' class="'+cls+'"'
if(pos=='dib')s+=' style="position:relative; display:inline-block;'+style+'"'
if(pos=='rel')s+=' style="position:relative; '+style+'"'
if(pos=='abs')s+=' style="position:absolute; '+style+'"'
s+={btn:()=>'>'+txt+'</button>',can:()=>'></canvas>',div:()=>' >'+txt+'</div>',edit:()=>' >'+txt+'</textarea>',inp:()=>'>'+(lbl.length>0?'</label>':''),out:()=>' >'+txt+'</span>'+(lbl.length>0?'</label>':''),radio:()=>{let s=''
s+='>\n'
for(let i=0;i<opts.length;i++){let chk=''
if(i==0)chk='checked'
let idi=id+i
let lbl=opts[i]
s+='<input id="'+idi+'" type="radio" name="'+id+'" value="'+lbl.name+'" onclick="'+fn+'('+i+');" '+chk+' >'
s+='<label for="'+idi+'">'+lbl.name+' </label>'}
s+='</div>'
return s},sel:()=>{let s=''
s+='>\n'
for(let i=0;i<opts.length;i++){let opt=opts[i]
let idStr=id+i
let chkStr=opt.descr==txt?' selected ':''
s+='<option id="'+idStr+'" value="'+opt.name+'"'+chkStr+'>'+opt.descr+'</option>\n'}
s+='</select>'
if(lbl.length>0)s+='</label>'
return s},sld:()=>'>',}[tag]()||''
s+='\n'
return s.trim()}
init()