cascading-reel 0.0.8 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.es.js +36 -28
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/index.umd.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const V=6,g=3,f=3,Z=34,j=.05,y=1800,K=.1,x=720,_=34,H=20,T=12,J=[255,235,110],tt=[255,255,255,.78],it=190,R=650,et=220,nt=200;function m(t,i,e){return t<i?i:t>e?e:t}function q(t){return 1-(1-t)**3}function st(t){return Math.floor(Math.random()*t)}function I(t,i){return(t%i+i)%i}function b(t){return m(Math.round(t),0,255)}function lt(t){return m(t,0,1)}function G(t){const i=[];for(let e=0;e<g;e+=1){const n=[];for(let l=0;l<f;l+=1)n.push(st(t));i.push(n)}return i}function N(t){const i=new Map;for(let s=0;s<g;s+=1)for(let r=0;r<f;r+=1){const u=t[s][r];i.set(u,(i.get(u)??0)+1)}let e=t[0][0],n=-1;for(const[s,r]of i.entries())r>n&&(n=r,e=s);const l=[];for(let s=0;s<g;s+=1)for(let r=0;r<f;r+=1)t[s][r]===e&&l.push({col:s,row:r});return l}function Y(){return Array.from({length:g},()=>Array.from({length:f},()=>0))}function W(t,i){for(let e=0;e<g;e+=1)for(let n=0;n<f;n+=1)t[e][n]=i}class rt{rafId=null;step=null;start(i){this.rafId===null&&(this.step=i,this.rafId=requestAnimationFrame(this.tick))}stop(){this.rafId!==null&&(cancelAnimationFrame(this.rafId),this.rafId=null),this.step=null}isRunning(){return this.rafId!==null}tick=i=>{if(!this.step){this.stop();return}if(this.step(i)===!1){this.stop();return}this.rafId!==null&&(this.rafId=requestAnimationFrame(this.tick))}}function ot(t,i,e){const n=[0,0,0];let l=0;for(let s=f-1;s>=0;s-=1){if(t[s]===0){n[s]=0;continue}n[s]=l;const r=Math.floor(i*j);l+=r+e}return n}function ht(t){let i=!0,e=!0;const l=t.height-t.boardY+t.cellH+2,s=[l,l,l],r=[-t.cellH,-t.cellH*2,-t.cellH*3],u=ot(s,R,Z),o=R-et;for(let h=0;h<t.scriptedOutgoingOffsets.length;h+=1){const a=t.now-(t.scriptedOutroStartedAt+h*it);for(let c=0;c<f;c+=1){const d=a-u[c];if(d<=0){t.scriptedOutgoingOffsets[h][c]=0,t.scriptedIncomingOffsets[h][c]=Number.NaN,i=!1,e=!1;continue}const p=m(d/R,0,1),S=q(p);t.scriptedOutgoingOffsets[h][c]=l*S,p<1&&(i=!1);const E=d-o;if(E<=0){t.scriptedIncomingOffsets[h][c]=Number.NaN,e=!1;continue}const C=m(E/R,0,1),O=q(C);t.scriptedIncomingOffsets[h][c]=r[c]*(1-O),C<1&&(e=!1)}}return{allOutgoingDone:i,allIncomingDone:e}}function ct(t){return t?[b(t[0]),b(t[1]),b(t[2])]:J}function ut(t){return t==="rainbow"?"rainbow":"solid"}function at(t){return t==="high"||t==="balanced"||t==="low"||t==="auto"?t:"auto"}function dt(t){return t?[b(t[0]),b(t[1]),b(t[2]),lt(t[3])]:tt}function F(t){if(t.length!==f)throw new Error(`rows must contain ${f} rows`);for(let i=0;i<f;i+=1)if(!Array.isArray(t[i])||t[i].length!==g)throw new Error(`rows[${i}] must contain ${g} columns`);return[[t[0][0],t[1][0],t[2][0]],[t[0][1],t[1][1],t[2][1]],[t[0][2],t[1][2],t[2][2]]]}function v(t,i){if(t.length!==g)throw new Error(`stopGrid must contain ${g} columns`);const e=[];for(let n=0;n<g;n+=1){const l=t[n];if(!Array.isArray(l)||l.length!==f)throw new Error(`stopGrid[${n}] must contain ${f} rows`);e[n]=[I(l[0],i),I(l[1],i),I(l[2],i)]}return e}function ft(t,i){return v(F(t),i)}function gt(t){return{stopGrid:t.stopGrid?.map(i=>[...i]),stopRows:t.stopRows?.map(i=>[...i]),finaleSequence:t.finaleSequence?.map(i=>i.map(e=>[...e])),finaleSequenceRows:t.finaleSequenceRows?.map(i=>i.map(e=>[...e])),callback:t.callback}}class pt{queue;constructor(i){this.queue=(i??[]).map(e=>gt(e))}hasPending(){return this.queue.length>0}consume(){return this.queue.length===0?null:this.queue.shift()??null}}function St(){return{isSpinning:!1,hasStartedFirstSpin:!1,queueFinished:!1,shouldHighlightCurrentSpin:!1,activeSpinState:null,phase:"idle",winFlashStartedAt:0,outroStartedAt:0,idleStartedAt:0,preSpinStartedAt:0,swayEnvelope:1,winEffectsEnvelope:1}}function Ct(t,i){t.hasStartedFirstSpin=!0,t.isSpinning=!0,t.phase="outro",t.outroStartedAt=i.startedAt,t.activeSpinState=i.activeSpinState,t.shouldHighlightCurrentSpin=i.shouldHighlightCurrentSpin}function wt(t,i,e){t.phase="idle",t.idleStartedAt=e,t.isSpinning=!1,t.shouldHighlightCurrentSpin=!1,t.queueFinished=!i,t.activeSpinState=null}function D(t,i){t.winFlashStartedAt=i,t.phase="winFlash"}function Et(t){t.isSpinning=!1,t.queueFinished=!0}const Ot=3,bt=.002;function At(t){const i=t.columnSway??[0,0,0],e=Ot*(Math.PI/180),n=Math.max(0,Math.min(1,t.swayEnvelope)),l=t.skipWinningCells??!1;for(let s=0;s<g;s+=1){const r=t.boardX+s*t.cellW,u=i[s]??0;for(let o=0;o<f;o+=1){if((l||t.phase==="winFlash"||t.phase==="preSpin")&&t.isWinningCell(s,o))continue;const h=t.offsets?t.offsets[s][o]:0;if(!Number.isFinite(h))continue;const a=t.boardY+o*t.cellH+h+u;if(a>t.height||a+t.cellH<0)continue;const d=Math.sin(t.now*bt+s*1.1+o*1.3)*e*n,p=r+t.cellW*.5,S=a+t.cellH*.5;t.ctx.save(),t.ctx.translate(p,S),t.ctx.rotate(d),t.ctx.translate(-t.cellW*.5,-t.cellH*.5),t.drawSpriteCell(t.grid[s][o],0,0,t.cellW,t.cellH),t.ctx.restore()}}}function A(t,i,e,n){const l=Math.sin(t*127.1+i*311.7+e*74.7+n*19.3)*43758.5453;return l-Math.floor(l)}const U=new Map;function mt(t,i){const e=`${t},${i}`;let n=U.get(e);if(n)return n;n=[];for(let l=0;l<_;l+=1)n.push({seedA:A(t,i,l,1),seedB:A(t,i,l,2),seedC:A(t,i,l,3),phaseOffset:A(t,i,l,4),twinkleSeed:A(t,i,l,5)});return U.set(e,n),n}function k(t){if(t.winningCells.length===0||t.phase!=="winFlash"&&t.phase!=="preSpin")return;const i=Math.max(0,Math.min(1,t.winEffectsEnvelope)),e=Math.max(0,t.now-t.winFlashStartedAt),n=e%y/y,l=1+Math.sin(n*Math.PI*2)*K;for(const s of t.winningCells){const r=t.boardX+s.col*t.cellW,u=t.boardY+s.row*t.cellH;Mt(t.ctx,r,u,t.cellW,t.cellH,s.col,s.row,t.winningCellBorderRgba,e,i);const o=t.getCell(s.col,s.row),h=1+(l-1)*i,a=t.cellW*h,c=t.cellH*h,d=(t.cellW-a)*.5,p=(t.cellH-c)*.5;t.ctx.save(),t.drawSpriteCell(o,r+d,u+p,a,c),t.ctx.restore()}if(!(t.particlesPerCell<=0)){t.ctx.save(),t.ctx.beginPath(),t.ctx.rect(t.boardX,t.boardY,t.cellW*g,t.cellH*f),t.ctx.clip();for(const s of t.winningCells){const r=t.boardX+s.col*t.cellW,u=t.boardY+s.row*t.cellH;Rt({ctx:t.ctx,cell:s,x:r,y:u,elapsed:e,envelope:i,cellW:t.cellW,cellH:t.cellH,particlesPerCell:t.particlesPerCell,particleColorMode:t.particleColorMode,particleColorRgb:t.particleColorRgb})}t.ctx.restore()}}function Mt(t,i,e,n,l,s,r,u,o,h){const a=Math.max(1,Math.floor(Math.min(n,l)*.04)),c=Math.max(1,Math.floor(Math.min(n,l)*.025)),[,,,d]=u,p=(s+.5)/g,S=(r+.5)/f,E=(o*.15+p*80+S*45)%360,C=Math.min(n,l),O=Math.max(4,Math.floor(C*.04)),L=Math.max(3,Math.floor(C*.03));t.save(),t.lineWidth=c,t.strokeStyle=`hsla(${E}, 90%, 70%, ${d*h})`,t.setLineDash([O,L]),t.lineDashOffset=-o*.03;const M=c*.5;t.strokeRect(i+a+M,e+a+M,n-a*2-c,l-a*2-c),t.setLineDash([]),t.restore()}const B=24;function Rt(t){const i=t.x+t.cellW*.5,e=t.y+t.cellH*.5,n=Math.min(t.cellW,t.cellH)*.72,l=Math.min(t.cellW,t.cellH)*.028,s=.96*t.envelope,[r,u,o]=t.particleColorRgb,h=t.particleColorMode==="solid";h&&(t.ctx.fillStyle=`rgb(${r},${u},${o})`);const a=mt(t.cell.col,t.cell.row);for(let c=0;c<t.particlesPerCell;c+=1){const d=a[c],p=d.phaseOffset*x,S=t.elapsed-p;if(S<0)continue;const E=S%x/x,C=d.seedA*Math.PI*2,O=n*E*(.35+d.seedB*.65),L=i+Math.cos(C)*O,M=e+Math.sin(C)*O,z=.7+.9*Math.max(0,Math.sin((t.elapsed*.012+d.twinkleSeed*2)*Math.PI*2)),X=Math.max(1,l*(.55+d.seedC*.6)*(1-E*.5)),P=m((.9+z*.2)*s,.9,1);if(!(P<=0)){if(h)t.ctx.globalAlpha=P;else{const $=(d.seedA*360+t.elapsed*.24+t.cell.col*38+t.cell.row*22)%360,Q=Math.floor($/(360/B))*(360/B);t.ctx.fillStyle=`hsla(${Q},98%,64%,${P})`}t.ctx.beginPath(),t.ctx.arc(L,M,X,0,Math.PI*2),t.ctx.fill()}}t.ctx.globalAlpha=1}class w{canvas;container;button;ctx;spinQueueController;spriteUrl;spriteElementsCount;highlightInitialWinningCells;particleColorRgb;particleColorMode;winningCellBorderRgba;quality;spriteImage=null;rafLoop=new rt;runtime=St();dpr=1;width=0;height=0;cellW=0;cellH=0;boardX=0;boardY=0;scriptedCascadeQueue=[];scriptedOutgoingGrid=null;scriptedPendingGrid=null;scriptedOutroStartedAt=0;scriptedOutgoingOffsets=Y();scriptedIncomingOffsets=Y();winningCells=[];grid;lastRafTime=0;particlesPerCell=_;initialHighlightRequestedAt=0;static SWAY_ENVELOPE_TAU_MS=220;static PRE_SPIN_MS=150;static WIN_EFFECTS_ENVELOPE_TAU_MS=120;constructor(i){this.canvas=i.canvas,this.container=i.container,this.button=i.button,this.spriteUrl=i.sprite,this.spriteElementsCount=Math.max(1,i.spriteElementsCount??V),this.highlightInitialWinningCells=i.highlightInitialWinningCells!==!1,this.spinQueueController=new pt(i.queuedSpinStates),this.particleColorRgb=ct(i.particleColorRgb),this.particleColorMode=ut(i.particleColorMode),this.winningCellBorderRgba=dt(i.winningCellBorderRgba),this.quality=at(i.quality);const e=this.canvas.getContext("2d");if(!e)throw new Error("2D canvas context is not available");this.ctx=e,this.grid=i.initialSegments?ft(i.initialSegments,this.spriteElementsCount):G(this.spriteElementsCount)}async init(){this.bindEvents(),this.resize(),await this.loadSpriteIfProvided(),this.applyInitialHighlightIfNeeded(),this.prewarmWinEffects(),requestAnimationFrame(i=>{this.render(i),this.startLoop()})}destroy(){this.unbindEvents(),this.rafLoop.stop(),Et(this.runtime),this.clearWinningCells()}spin(){if(this.runtime.isSpinning||this.runtime.queueFinished)return;if(!this.spinQueueController.hasPending()){this.runtime.queueFinished=!0,this.button&&(this.button.disabled=!0);return}const i=this.spinQueueController.consume(),e=!this.spinQueueController.hasPending();this.runtime.isSpinning=!0,this.runtime.phase="preSpin",this.runtime.preSpinStartedAt=performance.now(),this.runtime.activeSpinState=i,this.runtime.shouldHighlightCurrentSpin=e,this.runtime.hasStartedFirstSpin=!0,this.button&&(this.button.disabled=!0)}bindEvents(){window.addEventListener("resize",this.resize),this.button?.addEventListener("click",this.onSpinClick)}unbindEvents(){window.removeEventListener("resize",this.resize),this.button?.removeEventListener("click",this.onSpinClick)}onSpinClick=()=>{this.runtime.isSpinning||this.spin()};getNextGrid(i){return i?v(i,this.spriteElementsCount):G(this.spriteElementsCount)}update(i){if(this.runtime.isSpinning){if(this.runtime.phase==="preSpin"){if(i-this.runtime.preSpinStartedAt<w.PRE_SPIN_MS)return;Ct(this.runtime,{activeSpinState:this.runtime.activeSpinState,shouldHighlightCurrentSpin:this.runtime.shouldHighlightCurrentSpin,startedAt:i}),this.runtime.preSpinStartedAt=0;const e=this.runtime.activeSpinState?.finaleSequenceRows?this.runtime.activeSpinState.finaleSequenceRows.map(s=>F(s)):this.runtime.activeSpinState?.finaleSequence??[];this.scriptedCascadeQueue=e.map(s=>s.map(r=>[...r])),this.clearWinningCells();const n=this.runtime.activeSpinState?.stopRows?F(this.runtime.activeSpinState.stopRows):this.runtime.activeSpinState?.stopGrid,l=this.getNextGrid(n);this.startOutroTransition(l,i)}if(this.runtime.phase==="outro"){this.updateScriptedOutro(i);return}this.runtime.phase}}updateScriptedOutro(i){if(!this.scriptedOutgoingGrid||!this.scriptedPendingGrid){this.finishSpinWithUi();return}const{allOutgoingDone:e,allIncomingDone:n}=ht({now:i,height:this.height,boardY:this.boardY,cellH:this.cellH,scriptedOutroStartedAt:this.scriptedOutroStartedAt,scriptedOutgoingOffsets:this.scriptedOutgoingOffsets,scriptedIncomingOffsets:this.scriptedIncomingOffsets});if(!(!e||!n)){if(!this.scriptedPendingGrid){this.finishSpinWithUi();return}if(this.grid=this.scriptedPendingGrid,this.scriptedOutgoingGrid=null,this.scriptedPendingGrid=null,this.clearWinningCells(),W(this.scriptedOutgoingOffsets,0),W(this.scriptedIncomingOffsets,0),!this.tryStartScriptedCascade(i)){if(!this.runtime.shouldHighlightCurrentSpin){this.finishSpinWithUi();return}this.winningCells=N(this.grid),D(this.runtime,i)}}}tryStartScriptedCascade(i){if(this.scriptedCascadeQueue.length===0)return!1;const e=this.scriptedCascadeQueue.shift();return e?(this.startOutroTransition(v(e,this.spriteElementsCount),i),!0):!1}startOutroTransition(i,e){this.scriptedOutgoingGrid=this.grid.map(n=>[...n]),this.scriptedPendingGrid=i.map(n=>[...n]),W(this.scriptedIncomingOffsets,Number.NaN),W(this.scriptedOutgoingOffsets,0),this.clearWinningCells(),this.runtime.phase="outro",this.scriptedOutroStartedAt=e}finishSpinWithUi(){const i=this.runtime.activeSpinState?.callback;wt(this.runtime,this.spinQueueController.hasPending(),performance.now()),this.button&&(this.button.disabled=this.runtime.queueFinished),i?.()}applyInitialHighlightIfNeeded(){this.highlightInitialWinningCells&&(this.winningCells=N(this.grid),this.initialHighlightRequestedAt=performance.now())}static ZERO_SWAY=[0,0,0];render(i){this.initialHighlightRequestedAt>0&&i-this.initialHighlightRequestedAt>=nt&&(D(this.runtime,this.initialHighlightRequestedAt),this.runtime.winEffectsEnvelope=1,this.initialHighlightRequestedAt=0);const e=this.runtime.phase==="outro"||this.runtime.phase==="preSpin"?0:1,n=this.runtime.phase==="preSpin"?0:this.runtime.phase==="winFlash"?1:0;if(this.lastRafTime>0){const u=Math.min(i-this.lastRafTime,50),o=1-Math.exp(-u/w.SWAY_ENVELOPE_TAU_MS);this.runtime.swayEnvelope+=(e-this.runtime.swayEnvelope)*o;const h=1-Math.exp(-u/w.WIN_EFFECTS_ENVELOPE_TAU_MS);this.runtime.winEffectsEnvelope+=(n-this.runtime.winEffectsEnvelope)*h}this.lastRafTime=i,this.ctx.clearRect(0,0,this.width,this.height),this.runtime.phase==="outro"&&this.scriptedOutgoingGrid&&this.scriptedPendingGrid?(this.drawLayer(this.scriptedOutgoingGrid,this.scriptedOutgoingOffsets,w.ZERO_SWAY,i),this.drawLayer(this.scriptedPendingGrid,this.scriptedIncomingOffsets,w.ZERO_SWAY,i)):this.drawBaseGrid(i);const l=this.initialHighlightRequestedAt>0?"winFlash":this.runtime.phase,s=this.initialHighlightRequestedAt>0?this.initialHighlightRequestedAt:this.runtime.winFlashStartedAt,r=this.initialHighlightRequestedAt>0?1:this.runtime.winEffectsEnvelope;k({ctx:this.ctx,now:i,phase:l,winFlashStartedAt:s,winEffectsEnvelope:r,winningCells:this.winningCells,boardX:this.boardX,boardY:this.boardY,cellW:this.cellW,cellH:this.cellH,particlesPerCell:this.runtime.phase==="winFlash"&&this.runtime.shouldHighlightCurrentSpin&&this.runtime.hasStartedFirstSpin?this.particlesPerCell:0,particleColorMode:this.particleColorMode,particleColorRgb:this.particleColorRgb,winningCellBorderRgba:this.winningCellBorderRgba,getCell:this.getCell,drawSpriteCell:this.drawSpriteCell})}drawBaseGrid(i){this.drawLayer(this.grid,null,w.ZERO_SWAY,i)}drawLayer(i,e,n,l){const s=this.runtime.phase==="winFlash"||this.runtime.phase==="preSpin"||this.initialHighlightRequestedAt>0&&this.winningCells.length>0;At({ctx:this.ctx,phase:this.runtime.phase,now:l,swayEnvelope:this.runtime.swayEnvelope,grid:i,offsets:e,columnSway:n,boardX:this.boardX,boardY:this.boardY,cellW:this.cellW,cellH:this.cellH,width:this.width,height:this.height,skipWinningCells:s,isWinningCell:this.isWinningCell,drawSpriteCell:this.drawSpriteCell})}getCell=(i,e)=>this.grid[i][e];isWinningCell=(i,e)=>this.winningCells.some(n=>n.col===i&&n.row===e);drawSpriteCellToCtx(i,e,n,l,s,r){const u=this.spriteImage;if(!u)return;const o=I(e,this.spriteElementsCount),h=u.height/this.spriteElementsCount,a=Math.floor(o*h),c=Math.floor(h);i.drawImage(u,0,a,u.width,c,n,l,s,r)}drawSpriteCell=(i,e,n,l,s)=>{this.drawSpriteCellToCtx(this.ctx,i,e,n,l,s)};clearWinningCells(){this.winningCells=[]}resize=()=>{const i=this.container.getBoundingClientRect();this.dpr=Math.max(1,Math.min(window.devicePixelRatio||1,2));const e=Math.max(300,Math.floor(i.width*this.dpr));this.width=e,this.height=e;const n=Math.floor(Math.min(this.width/g,this.height/f));this.cellW=n,this.cellH=n,this.boardX=Math.floor((this.width-this.cellW*g)/2),this.boardY=Math.floor((this.height-this.cellH*f)/2),this.quality==="high"?this.particlesPerCell=_:this.quality==="balanced"?this.particlesPerCell=H:this.quality==="low"?this.particlesPerCell=T:this.particlesPerCell=this.dpr>=2?H:_,this.canvas.width=this.width,this.canvas.height=this.height};async loadSpriteIfProvided(){if(!this.spriteUrl)return;const i=new Image;i.decoding="async",i.src=this.spriteUrl;try{await i.decode(),this.spriteImage=i}catch{this.spriteImage=null}}prewarmWinEffects(){if(this.winningCells.length===0)return;const i=document.createElement("canvas");i.width=this.width,i.height=this.height;const e=i.getContext("2d");if(!e)return;const n=this.runtime.phase,l=this.runtime.winFlashStartedAt;this.runtime.phase="winFlash",this.runtime.winFlashStartedAt=performance.now()-300;const s=performance.now(),r=Math.min(this.particlesPerCell,T),u=(o,h,a,c,d)=>{this.drawSpriteCellToCtx(e,o,h,a,c,d)};for(let o=0;o<2;o+=1)k({ctx:e,now:s+o*16,phase:"winFlash",winFlashStartedAt:this.runtime.winFlashStartedAt,winEffectsEnvelope:1,winningCells:this.winningCells,boardX:this.boardX,boardY:this.boardY,cellW:this.cellW,cellH:this.cellH,particlesPerCell:r,particleColorMode:this.particleColorMode,particleColorRgb:this.particleColorRgb,winningCellBorderRgba:this.winningCellBorderRgba,getCell:this.getCell,drawSpriteCell:u});this.runtime.phase=n,this.runtime.winFlashStartedAt=l}startLoop(){this.rafLoop.isRunning()||this.rafLoop.start(i=>(this.update(i),this.render(i),!0))}}exports.CascadingReel=w;
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const V=6,g=3,f=3,Z=34,j=.05,K=5e3,v=1800,J=.1,F=720,_=34,y=20,T=12,tt=[255,235,110],it=[255,255,255,.78],et=190,M=650,nt=220,st=200;function m(t,i,e){return t<i?i:t>e?e:t}function q(t){return 1-(1-t)**3}function lt(t){return Math.floor(Math.random()*t)}function I(t,i){return(t%i+i)%i}function O(t){return m(Math.round(t),0,255)}function rt(t){return m(t,0,1)}function N(t){const i=[];for(let e=0;e<g;e+=1){const n=[];for(let l=0;l<f;l+=1)n.push(lt(t));i.push(n)}return i}function G(t){const i=new Map;for(let s=0;s<g;s+=1)for(let r=0;r<f;r+=1){const u=t[s][r];i.set(u,(i.get(u)??0)+1)}let e=t[0][0],n=-1;for(const[s,r]of i.entries())r>n&&(n=r,e=s);const l=[];for(let s=0;s<g;s+=1)for(let r=0;r<f;r+=1)t[s][r]===e&&l.push({col:s,row:r});return l}function Y(){return Array.from({length:g},()=>Array.from({length:f},()=>0))}function R(t,i){for(let e=0;e<g;e+=1)for(let n=0;n<f;n+=1)t[e][n]=i}class ot{rafId=null;step=null;start(i){this.rafId===null&&(this.step=i,this.rafId=requestAnimationFrame(this.tick))}stop(){this.rafId!==null&&(cancelAnimationFrame(this.rafId),this.rafId=null),this.step=null}isRunning(){return this.rafId!==null}tick=i=>{if(!this.step){this.stop();return}if(this.step(i)===!1){this.stop();return}this.rafId!==null&&(this.rafId=requestAnimationFrame(this.tick))}}function ht(t,i,e){const n=[0,0,0];let l=0;for(let s=f-1;s>=0;s-=1){if(t[s]===0){n[s]=0;continue}n[s]=l;const r=Math.floor(i*j);l+=r+e}return n}function ct(t){let i=!0,e=!0;const l=t.height-t.boardY+t.cellH+2,s=[l,l,l],r=[-t.cellH,-t.cellH*2,-t.cellH*3],u=ht(s,M,Z),o=M-nt;for(let h=0;h<t.scriptedOutgoingOffsets.length;h+=1){const a=t.now-(t.scriptedOutroStartedAt+h*et);for(let c=0;c<f;c+=1){const d=a-u[c];if(d<=0){t.scriptedOutgoingOffsets[h][c]=0,t.scriptedIncomingOffsets[h][c]=Number.NaN,i=!1,e=!1;continue}const p=m(d/M,0,1),S=q(p);t.scriptedOutgoingOffsets[h][c]=l*S,p<1&&(i=!1);const E=d-o;if(E<=0){t.scriptedIncomingOffsets[h][c]=Number.NaN,e=!1;continue}const C=m(E/M,0,1),b=q(C);t.scriptedIncomingOffsets[h][c]=r[c]*(1-b),C<1&&(e=!1)}}return{allOutgoingDone:i,allIncomingDone:e}}function ut(t){return t?[O(t[0]),O(t[1]),O(t[2])]:tt}function at(t){return t==="rainbow"?"rainbow":"solid"}function dt(t){return t==="high"||t==="balanced"||t==="low"||t==="auto"?t:"auto"}function ft(t){return t?[O(t[0]),O(t[1]),O(t[2]),rt(t[3])]:it}function x(t){if(t.length!==f)throw new Error(`rows must contain ${f} rows`);for(let i=0;i<f;i+=1)if(!Array.isArray(t[i])||t[i].length!==g)throw new Error(`rows[${i}] must contain ${g} columns`);return[[t[0][0],t[1][0],t[2][0]],[t[0][1],t[1][1],t[2][1]],[t[0][2],t[1][2],t[2][2]]]}function H(t,i){if(t.length!==g)throw new Error(`stopGrid must contain ${g} columns`);const e=[];for(let n=0;n<g;n+=1){const l=t[n];if(!Array.isArray(l)||l.length!==f)throw new Error(`stopGrid[${n}] must contain ${f} rows`);e[n]=[I(l[0],i),I(l[1],i),I(l[2],i)]}return e}function gt(t,i){return H(x(t),i)}function pt(t){return{stopGrid:t.stopGrid?.map(i=>[...i]),stopRows:t.stopRows?.map(i=>[...i]),finaleSequence:t.finaleSequence?.map(i=>i.map(e=>[...e])),finaleSequenceRows:t.finaleSequenceRows?.map(i=>i.map(e=>[...e])),highlightWin:t.highlightWin,callback:t.callback}}class St{queue;constructor(i){this.queue=(i??[]).map(e=>pt(e))}hasPending(){return this.queue.length>0}consume(){return this.queue.length===0?null:this.queue.shift()??null}}function Ct(){return{isSpinning:!1,hasStartedFirstSpin:!1,queueFinished:!1,shouldHighlightCurrentSpin:!1,activeSpinState:null,phase:"idle",winFlashStartedAt:0,outroStartedAt:0,idleStartedAt:0,preSpinStartedAt:0,swayEnvelope:1,winEffectsEnvelope:1}}function wt(t,i){t.hasStartedFirstSpin=!0,t.isSpinning=!0,t.phase="outro",t.outroStartedAt=i.startedAt,t.activeSpinState=i.activeSpinState,t.shouldHighlightCurrentSpin=i.shouldHighlightCurrentSpin}function Et(t,i,e){t.phase="idle",t.idleStartedAt=e,t.isSpinning=!1,t.shouldHighlightCurrentSpin=!1,t.queueFinished=!i,t.activeSpinState=null}function U(t,i){t.winFlashStartedAt=i,t.phase="winFlash"}function bt(t){t.isSpinning=!1,t.queueFinished=!0}const Ot=3,At=.002;function mt(t){const i=t.columnSway??[0,0,0],e=Ot*(Math.PI/180),n=Math.max(0,Math.min(1,t.swayEnvelope)),l=t.skipWinningCells??!1;for(let s=0;s<g;s+=1){const r=t.boardX+s*t.cellW,u=i[s]??0;for(let o=0;o<f;o+=1){if((l||t.phase==="winFlash"||t.phase==="preSpin")&&t.isWinningCell(s,o))continue;const h=t.offsets?t.offsets[s][o]:0;if(!Number.isFinite(h))continue;const a=t.boardY+o*t.cellH+h+u;if(a>t.height||a+t.cellH<0)continue;const d=Math.sin(t.now*At+s*1.1+o*1.3)*e*n,p=r+t.cellW*.5,S=a+t.cellH*.5;t.ctx.save(),t.ctx.translate(p,S),t.ctx.rotate(d),t.ctx.translate(-t.cellW*.5,-t.cellH*.5),t.drawSpriteCell(t.grid[s][o],0,0,t.cellW,t.cellH),t.ctx.restore()}}}function A(t,i,e,n){const l=Math.sin(t*127.1+i*311.7+e*74.7+n*19.3)*43758.5453;return l-Math.floor(l)}const D=new Map;function Wt(t,i){const e=`${t},${i}`;let n=D.get(e);if(n)return n;n=[];for(let l=0;l<_;l+=1)n.push({seedA:A(t,i,l,1),seedB:A(t,i,l,2),seedC:A(t,i,l,3),phaseOffset:A(t,i,l,4),twinkleSeed:A(t,i,l,5)});return D.set(e,n),n}function k(t){if(t.winningCells.length===0||t.phase!=="winFlash"&&t.phase!=="preSpin")return;const i=Math.max(0,Math.min(1,t.winEffectsEnvelope)),e=Math.max(0,t.now-t.winFlashStartedAt),n=e%v/v,l=1+Math.sin(n*Math.PI*2)*J;for(const s of t.winningCells){const r=t.boardX+s.col*t.cellW,u=t.boardY+s.row*t.cellH;Mt(t.ctx,r,u,t.cellW,t.cellH,s.col,s.row,t.winningCellBorderRgba,e,i);const o=t.getCell(s.col,s.row),h=1+(l-1)*i,a=t.cellW*h,c=t.cellH*h,d=(t.cellW-a)*.5,p=(t.cellH-c)*.5;t.ctx.save(),t.drawSpriteCell(o,r+d,u+p,a,c),t.ctx.restore()}if(!(t.particlesPerCell<=0)){t.ctx.save(),t.ctx.beginPath(),t.ctx.rect(t.boardX,t.boardY,t.cellW*g,t.cellH*f),t.ctx.clip();for(const s of t.winningCells){const r=t.boardX+s.col*t.cellW,u=t.boardY+s.row*t.cellH;Rt({ctx:t.ctx,cell:s,x:r,y:u,elapsed:e,envelope:i,cellW:t.cellW,cellH:t.cellH,particlesPerCell:t.particlesPerCell,particleColorMode:t.particleColorMode,particleColorRgb:t.particleColorRgb})}t.ctx.restore()}}function Mt(t,i,e,n,l,s,r,u,o,h){const a=Math.max(1,Math.floor(Math.min(n,l)*.04)),c=Math.max(1,Math.floor(Math.min(n,l)*.03)),[,,,d]=u,p=(s+.5)/g,S=(r+.5)/f,E=(o*.2+p*80+S*40)%360,C=Math.min(n,l),b=Math.max(4,Math.floor(C*.04)),L=Math.max(3,Math.floor(C*.03));t.save(),t.lineWidth=c,t.strokeStyle=`hsla(${E}, 90%, 70%, ${d*h})`,t.setLineDash([b,L]),t.lineDashOffset=-o*.03;const W=c*.5;t.strokeRect(i+a+W,e+a+W,n-a*2-c,l-a*2-c),t.setLineDash([]),t.restore()}const B=24;function Rt(t){const i=t.x+t.cellW*.5,e=t.y+t.cellH*.5,n=Math.min(t.cellW,t.cellH)*.72,l=Math.min(t.cellW,t.cellH)*.028,s=.96*t.envelope,[r,u,o]=t.particleColorRgb,h=t.particleColorMode==="solid";h&&(t.ctx.fillStyle=`rgb(${r},${u},${o})`);const a=Wt(t.cell.col,t.cell.row);for(let c=0;c<t.particlesPerCell;c+=1){const d=a[c],p=d.phaseOffset*F,S=t.elapsed-p;if(S<0)continue;const E=S%F/F,C=d.seedA*Math.PI*2,b=n*E*(.35+d.seedB*.65),L=i+Math.cos(C)*b,W=e+Math.sin(C)*b,z=.7+.9*Math.max(0,Math.sin((t.elapsed*.012+d.twinkleSeed*2)*Math.PI*2)),X=Math.max(1,l*(.55+d.seedC*.6)*(1-E*.5)),P=m((.9+z*.2)*s,.9,1);if(!(P<=0)){if(h)t.ctx.globalAlpha=P;else{const $=(d.seedA*360+t.elapsed*.24+t.cell.col*38+t.cell.row*22)%360,Q=Math.floor($/(360/B))*(360/B);t.ctx.fillStyle=`hsla(${Q},98%,64%,${P})`}t.ctx.beginPath(),t.ctx.arc(L,W,X,0,Math.PI*2),t.ctx.fill()}}t.ctx.globalAlpha=1}class w{canvas;container;button;ctx;spinQueueController;spriteUrl;spriteElementsCount;highlightInitialWinningCells;particleColorRgb;particleColorMode;winningCellBorderRgba;quality;spriteImage=null;rafLoop=new ot;runtime=Ct();dpr=1;width=0;height=0;cellW=0;cellH=0;boardX=0;boardY=0;scriptedCascadeQueue=[];scriptedOutgoingGrid=null;scriptedPendingGrid=null;scriptedOutroStartedAt=0;scriptedOutgoingOffsets=Y();scriptedIncomingOffsets=Y();winningCells=[];grid;lastRafTime=0;particlesPerCell=_;initialHighlightRequestedAt=0;static SWAY_ENVELOPE_TAU_MS=220;static PRE_SPIN_MS=150;static WIN_EFFECTS_ENVELOPE_TAU_MS=120;constructor(i){this.canvas=i.canvas,this.container=i.container,this.button=i.button,this.spriteUrl=i.sprite,this.spriteElementsCount=Math.max(1,i.spriteElementsCount??V),this.highlightInitialWinningCells=i.highlightInitialWinningCells!==!1,this.spinQueueController=new St(i.queuedSpinStates),this.particleColorRgb=ut(i.particleColorRgb),this.particleColorMode=at(i.particleColorMode),this.winningCellBorderRgba=ft(i.winningCellBorderRgba),this.quality=dt(i.quality);const e=this.canvas.getContext("2d");if(!e)throw new Error("2D canvas context is not available");this.ctx=e,this.grid=i.initialSegments?gt(i.initialSegments,this.spriteElementsCount):N(this.spriteElementsCount)}async init(){this.bindEvents(),this.resize(),await this.loadSpriteIfProvided(),this.applyInitialHighlightIfNeeded(),this.prewarmWinEffects(),requestAnimationFrame(i=>{this.render(i),this.startLoop()})}destroy(){this.unbindEvents(),this.rafLoop.stop(),bt(this.runtime),this.clearWinningCells()}spin(){if(this.runtime.isSpinning||this.runtime.queueFinished)return;if(!this.spinQueueController.hasPending()){this.runtime.queueFinished=!0,this.button&&(this.button.disabled=!0);return}const i=this.spinQueueController.consume(),e=i?.highlightWin===!0;this.runtime.isSpinning=!0,this.runtime.phase="preSpin",this.runtime.preSpinStartedAt=performance.now(),this.runtime.activeSpinState=i,this.runtime.shouldHighlightCurrentSpin=e,this.runtime.hasStartedFirstSpin=!0,this.button&&(this.button.disabled=!0)}bindEvents(){window.addEventListener("resize",this.resize),this.button?.addEventListener("click",this.onSpinClick)}unbindEvents(){window.removeEventListener("resize",this.resize),this.button?.removeEventListener("click",this.onSpinClick)}onSpinClick=()=>{if(this.runtime.phase==="winFlash"&&(this.runtime.shouldHighlightCurrentSpin||!this.runtime.hasStartedFirstSpin)){this.finishSpinWithUi(),this.spinQueueController.hasPending()&&this.spin();return}this.runtime.isSpinning||this.spin()};getNextGrid(i){return i?H(i,this.spriteElementsCount):N(this.spriteElementsCount)}update(i){if(this.runtime.isSpinning){if(this.runtime.phase==="preSpin"){if(i-this.runtime.preSpinStartedAt<w.PRE_SPIN_MS)return;wt(this.runtime,{activeSpinState:this.runtime.activeSpinState,shouldHighlightCurrentSpin:this.runtime.shouldHighlightCurrentSpin,startedAt:i}),this.runtime.preSpinStartedAt=0;const e=this.runtime.activeSpinState?.finaleSequenceRows?this.runtime.activeSpinState.finaleSequenceRows.map(s=>x(s)):this.runtime.activeSpinState?.finaleSequence??[];this.scriptedCascadeQueue=e.map(s=>s.map(r=>[...r])),this.clearWinningCells();const n=this.runtime.activeSpinState?.stopRows?x(this.runtime.activeSpinState.stopRows):this.runtime.activeSpinState?.stopGrid,l=this.getNextGrid(n);this.startOutroTransition(l,i)}if(this.runtime.phase==="outro"){this.updateScriptedOutro(i);return}if(this.runtime.phase==="winFlash"){!this.runtime.shouldHighlightCurrentSpin&&i-this.runtime.winFlashStartedAt>=K&&this.finishSpinWithUi();return}}}updateScriptedOutro(i){if(!this.scriptedOutgoingGrid||!this.scriptedPendingGrid){this.finishSpinWithUi();return}const{allOutgoingDone:e,allIncomingDone:n}=ct({now:i,height:this.height,boardY:this.boardY,cellH:this.cellH,scriptedOutroStartedAt:this.scriptedOutroStartedAt,scriptedOutgoingOffsets:this.scriptedOutgoingOffsets,scriptedIncomingOffsets:this.scriptedIncomingOffsets});if(!(!e||!n)){if(!this.scriptedPendingGrid){this.finishSpinWithUi();return}if(this.grid=this.scriptedPendingGrid,this.scriptedOutgoingGrid=null,this.scriptedPendingGrid=null,this.clearWinningCells(),R(this.scriptedOutgoingOffsets,0),R(this.scriptedIncomingOffsets,0),!this.tryStartScriptedCascade(i)){if(!this.runtime.shouldHighlightCurrentSpin){this.finishSpinWithUi();return}this.winningCells=G(this.grid),U(this.runtime,i),this.button&&this.runtime.shouldHighlightCurrentSpin&&this.spinQueueController.hasPending()&&(this.button.disabled=!1)}}}tryStartScriptedCascade(i){if(this.scriptedCascadeQueue.length===0)return!1;const e=this.scriptedCascadeQueue.shift();return e?(this.startOutroTransition(H(e,this.spriteElementsCount),i),!0):!1}startOutroTransition(i,e){this.scriptedOutgoingGrid=this.grid.map(n=>[...n]),this.scriptedPendingGrid=i.map(n=>[...n]),R(this.scriptedIncomingOffsets,Number.NaN),R(this.scriptedOutgoingOffsets,0),this.clearWinningCells(),this.runtime.phase="outro",this.scriptedOutroStartedAt=e}finishSpinWithUi(){const i=this.runtime.activeSpinState?.callback;Et(this.runtime,this.spinQueueController.hasPending(),performance.now()),this.button&&(this.button.disabled=this.runtime.queueFinished),i?.()}applyInitialHighlightIfNeeded(){this.highlightInitialWinningCells&&(this.winningCells=G(this.grid),this.initialHighlightRequestedAt=performance.now())}static ZERO_SWAY=[0,0,0];render(i){this.initialHighlightRequestedAt>0&&i-this.initialHighlightRequestedAt>=st&&(U(this.runtime,this.initialHighlightRequestedAt),this.runtime.winEffectsEnvelope=1,this.initialHighlightRequestedAt=0,this.button&&(this.button.disabled=!1));const e=this.runtime.phase==="outro"||this.runtime.phase==="preSpin"?0:1,n=this.runtime.phase==="preSpin"?0:this.runtime.phase==="winFlash"?1:0;if(this.lastRafTime>0){const u=Math.min(i-this.lastRafTime,50),o=1-Math.exp(-u/w.SWAY_ENVELOPE_TAU_MS);this.runtime.swayEnvelope+=(e-this.runtime.swayEnvelope)*o;const h=1-Math.exp(-u/w.WIN_EFFECTS_ENVELOPE_TAU_MS);this.runtime.winEffectsEnvelope+=(n-this.runtime.winEffectsEnvelope)*h}this.lastRafTime=i,this.ctx.clearRect(0,0,this.width,this.height),this.runtime.phase==="outro"&&this.scriptedOutgoingGrid&&this.scriptedPendingGrid?(this.drawLayer(this.scriptedOutgoingGrid,this.scriptedOutgoingOffsets,w.ZERO_SWAY,i),this.drawLayer(this.scriptedPendingGrid,this.scriptedIncomingOffsets,w.ZERO_SWAY,i)):this.drawBaseGrid(i);const l=this.initialHighlightRequestedAt>0?"winFlash":this.runtime.phase,s=this.initialHighlightRequestedAt>0?this.initialHighlightRequestedAt:this.runtime.winFlashStartedAt,r=this.initialHighlightRequestedAt>0?1:this.runtime.winEffectsEnvelope;k({ctx:this.ctx,now:i,phase:l,winFlashStartedAt:s,winEffectsEnvelope:r,winningCells:this.winningCells,boardX:this.boardX,boardY:this.boardY,cellW:this.cellW,cellH:this.cellH,particlesPerCell:this.runtime.phase==="winFlash"&&this.runtime.shouldHighlightCurrentSpin&&this.runtime.hasStartedFirstSpin?this.particlesPerCell:0,particleColorMode:this.particleColorMode,particleColorRgb:this.particleColorRgb,winningCellBorderRgba:this.winningCellBorderRgba,getCell:this.getCell,drawSpriteCell:this.drawSpriteCell})}drawBaseGrid(i){this.drawLayer(this.grid,null,w.ZERO_SWAY,i)}drawLayer(i,e,n,l){const s=this.runtime.phase==="winFlash"||this.runtime.phase==="preSpin"||this.initialHighlightRequestedAt>0&&this.winningCells.length>0;mt({ctx:this.ctx,phase:this.runtime.phase,now:l,swayEnvelope:this.runtime.swayEnvelope,grid:i,offsets:e,columnSway:n,boardX:this.boardX,boardY:this.boardY,cellW:this.cellW,cellH:this.cellH,width:this.width,height:this.height,skipWinningCells:s,isWinningCell:this.isWinningCell,drawSpriteCell:this.drawSpriteCell})}getCell=(i,e)=>this.grid[i][e];isWinningCell=(i,e)=>this.winningCells.some(n=>n.col===i&&n.row===e);drawSpriteCellToCtx(i,e,n,l,s,r){const u=this.spriteImage;if(!u)return;const o=I(e,this.spriteElementsCount),h=u.height/this.spriteElementsCount,a=Math.floor(o*h),c=Math.floor(h);i.drawImage(u,0,a,u.width,c,n,l,s,r)}drawSpriteCell=(i,e,n,l,s)=>{this.drawSpriteCellToCtx(this.ctx,i,e,n,l,s)};clearWinningCells(){this.winningCells=[]}resize=()=>{const i=this.container.getBoundingClientRect();this.dpr=Math.max(1,Math.min(window.devicePixelRatio||1,2));const e=Math.max(300,Math.floor(i.width*this.dpr));this.width=e,this.height=e;const n=Math.floor(Math.min(this.width/g,this.height/f));this.cellW=n,this.cellH=n,this.boardX=Math.floor((this.width-this.cellW*g)/2),this.boardY=Math.floor((this.height-this.cellH*f)/2),this.quality==="high"?this.particlesPerCell=_:this.quality==="balanced"?this.particlesPerCell=y:this.quality==="low"?this.particlesPerCell=T:this.particlesPerCell=this.dpr>=2?y:_,this.canvas.width=this.width,this.canvas.height=this.height};async loadSpriteIfProvided(){if(!this.spriteUrl)return;const i=new Image;i.decoding="async",i.src=this.spriteUrl;try{await i.decode(),this.spriteImage=i}catch{this.spriteImage=null}}prewarmWinEffects(){if(this.winningCells.length===0)return;const i=document.createElement("canvas");i.width=this.width,i.height=this.height;const e=i.getContext("2d");if(!e)return;const n=this.runtime.phase,l=this.runtime.winFlashStartedAt;this.runtime.phase="winFlash",this.runtime.winFlashStartedAt=performance.now()-300;const s=performance.now(),r=Math.min(this.particlesPerCell,T),u=(o,h,a,c,d)=>{this.drawSpriteCellToCtx(e,o,h,a,c,d)};for(let o=0;o<2;o+=1)k({ctx:e,now:s+o*16,phase:"winFlash",winFlashStartedAt:this.runtime.winFlashStartedAt,winEffectsEnvelope:1,winningCells:this.winningCells,boardX:this.boardX,boardY:this.boardY,cellW:this.cellW,cellH:this.cellH,particlesPerCell:r,particleColorMode:this.particleColorMode,particleColorRgb:this.particleColorRgb,winningCellBorderRgba:this.winningCellBorderRgba,getCell:this.getCell,drawSpriteCell:u});this.runtime.phase=n,this.runtime.winFlashStartedAt=l}startLoop(){this.rafLoop.isRunning()||this.rafLoop.start(i=>(this.update(i),this.render(i),!0))}}exports.CascadingReel=w;
|
|
2
2
|
//# sourceMappingURL=index.cjs.js.map
|
package/dist/index.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs.js","sources":["../src/constants.ts","../src/utils/math.ts","../src/core/grid.ts","../src/core/loop.ts","../src/core/outro.ts","../src/normalize.ts","../src/core/spinQueue.ts","../src/core/state.ts","../src/render/canvasRenderer.ts","../src/CascadingReel.ts"],"sourcesContent":["export const DEFAULT_SPRITE_ELEMENTS_COUNT = 6;\nexport const GRID_COLS = 3;\nexport const GRID_ROWS = 3;\nexport const FLOW_OUTRO_ROW_GAP_MS = 34;\nexport const FLOW_ROW_BASE_SPACING_RATIO = 0.05;\nexport const FLOW_WIN_FLASH_MS = 1200;\nexport const FLOW_WIN_PULSE_PERIOD_MS = 1800;\nexport const FLOW_WIN_PULSE_AMPLITUDE = 0.1;\n/** Duration for one particle to fly from center to edge (continuous spray). */\nexport const PARTICLE_FLY_DURATION_MS = 720;\nexport const FLOW_WIN_PARTICLES_PER_CELL_HIGH = 34;\nexport const FLOW_WIN_PARTICLES_PER_CELL_BALANCED = 20;\nexport const FLOW_WIN_PARTICLES_PER_CELL_LOW = 12;\nexport const DEFAULT_PARTICLE_COLOR_RGB: [number, number, number] = [255, 235, 110];\nexport const DEFAULT_WINNING_CELL_BORDER_RGBA: [number, number, number, number] = [\n 255, 255, 255, 0.78,\n];\nexport const FLOW_COLUMN_STAGGER_MS = 190;\nexport const FLOW_FALL_MS = 650;\nexport const FLOW_OUTRO_OVERLAP_MS = 220;\n/** Delay before starting initial win-effect so canvas/JIT can warm up. */\nexport const INITIAL_WIN_FLASH_DELAY_MS = 200;\n","export function clamp(value: number, min: number, max: number): number {\n if (value < min) return min;\n if (value > max) return max;\n return value;\n}\n\nexport function easeOutCubic(t: number): number {\n return 1 - (1 - t) ** 3;\n}\n\nexport function randomInt(maxExclusive: number): number {\n return Math.floor(Math.random() * maxExclusive);\n}\n\nexport function normalizeSegment(segment: number, elementsCount: number): number {\n return ((segment % elementsCount) + elementsCount) % elementsCount;\n}\n\nexport function normalizeRgbChannel(value: number): number {\n return clamp(Math.round(value), 0, 255);\n}\n\nexport function normalizeAlphaChannel(value: number): number {\n return clamp(value, 0, 1);\n}\n","import { GRID_COLS, GRID_ROWS } from '../constants';\nimport type { CellPosition, SymbolId } from '../types';\nimport { randomInt } from '../utils/math';\n\nexport function createRandomGrid(spriteElementsCount: number): SymbolId[][] {\n const grid: SymbolId[][] = [];\n for (let col = 0; col < GRID_COLS; col += 1) {\n const column: SymbolId[] = [];\n for (let row = 0; row < GRID_ROWS; row += 1) {\n column.push(randomInt(spriteElementsCount));\n }\n grid.push(column);\n }\n return grid;\n}\n\nexport function findMostFrequentCells(grid: SymbolId[][]): CellPosition[] {\n const counts = new Map<SymbolId, number>();\n for (let col = 0; col < GRID_COLS; col += 1) {\n for (let row = 0; row < GRID_ROWS; row += 1) {\n const symbol = grid[col][row];\n counts.set(symbol, (counts.get(symbol) ?? 0) + 1);\n }\n }\n\n let selectedSymbol: SymbolId = grid[0][0];\n let maxCount = -1;\n for (const [symbol, count] of counts.entries()) {\n if (count > maxCount) {\n maxCount = count;\n selectedSymbol = symbol;\n }\n }\n\n const cells: CellPosition[] = [];\n for (let col = 0; col < GRID_COLS; col += 1) {\n for (let row = 0; row < GRID_ROWS; row += 1) {\n if (grid[col][row] === selectedSymbol) {\n cells.push({ col, row });\n }\n }\n }\n return cells;\n}\n\nexport function createZeroOffsets(): number[][] {\n return Array.from({ length: GRID_COLS }, () => Array.from({ length: GRID_ROWS }, () => 0));\n}\n\nexport function fillOffsets(offsets: number[][], value: number): void {\n for (let col = 0; col < GRID_COLS; col += 1) {\n for (let row = 0; row < GRID_ROWS; row += 1) {\n offsets[col][row] = value;\n }\n }\n}\n","export type RafStep = (now: number) => boolean;\n\nexport class RafLoop {\n private rafId: number | null = null;\n private step: RafStep | null = null;\n\n public start(step: RafStep): void {\n if (this.rafId !== null) return;\n this.step = step;\n this.rafId = requestAnimationFrame(this.tick);\n }\n\n public stop(): void {\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n this.step = null;\n }\n\n public isRunning(): boolean {\n return this.rafId !== null;\n }\n\n private readonly tick = (now: number): void => {\n if (!this.step) {\n this.stop();\n return;\n }\n\n const shouldContinue = this.step(now);\n if (shouldContinue === false) {\n this.stop();\n return;\n }\n\n if (this.rafId === null) return;\n this.rafId = requestAnimationFrame(this.tick);\n };\n}\n","import {\n FLOW_COLUMN_STAGGER_MS,\n FLOW_FALL_MS,\n FLOW_OUTRO_OVERLAP_MS,\n FLOW_OUTRO_ROW_GAP_MS,\n FLOW_ROW_BASE_SPACING_RATIO,\n GRID_ROWS,\n} from '../constants';\nimport { clamp, easeOutCubic } from '../utils/math';\n\nexport function buildSequentialRowStartDelays(\n fromRowOffsets: [number, number, number],\n duration: number,\n gapMs: number,\n): [number, number, number] {\n const delays: [number, number, number] = [0, 0, 0];\n let nextDelay = 0;\n for (let row = GRID_ROWS - 1; row >= 0; row -= 1) {\n if (fromRowOffsets[row] === 0) {\n delays[row] = 0;\n continue;\n }\n delays[row] = nextDelay;\n const baseSpacing = Math.floor(duration * FLOW_ROW_BASE_SPACING_RATIO);\n nextDelay += baseSpacing + gapMs;\n }\n return delays;\n}\n\nexport function updateOutroOffsets(params: {\n now: number;\n height: number;\n boardY: number;\n cellH: number;\n scriptedOutroStartedAt: number;\n scriptedOutgoingOffsets: number[][];\n scriptedIncomingOffsets: number[][];\n}): { allOutgoingDone: boolean; allIncomingDone: boolean } {\n let allOutgoingDone = true;\n let allIncomingDone = true;\n\n const exitEpsilon = 2;\n const outgoingDistance = params.height - params.boardY + params.cellH + exitEpsilon;\n const outgoingOffsetsForOrder: [number, number, number] = [\n outgoingDistance,\n outgoingDistance,\n outgoingDistance,\n ];\n const incomingFromOffsets: [number, number, number] = [\n -params.cellH,\n -params.cellH * 2,\n -params.cellH * 3,\n ];\n const rowStartDelays = buildSequentialRowStartDelays(\n outgoingOffsetsForOrder,\n FLOW_FALL_MS,\n FLOW_OUTRO_ROW_GAP_MS,\n );\n const incomingStartShift = FLOW_FALL_MS - FLOW_OUTRO_OVERLAP_MS;\n\n for (let col = 0; col < params.scriptedOutgoingOffsets.length; col += 1) {\n const columnElapsed =\n params.now - (params.scriptedOutroStartedAt + col * FLOW_COLUMN_STAGGER_MS);\n for (let row = 0; row < GRID_ROWS; row += 1) {\n const rowElapsed = columnElapsed - rowStartDelays[row];\n\n if (rowElapsed <= 0) {\n params.scriptedOutgoingOffsets[col][row] = 0;\n params.scriptedIncomingOffsets[col][row] = Number.NaN;\n allOutgoingDone = false;\n allIncomingDone = false;\n continue;\n }\n\n const tOut = clamp(rowElapsed / FLOW_FALL_MS, 0, 1);\n const easedOut = easeOutCubic(tOut);\n params.scriptedOutgoingOffsets[col][row] = outgoingDistance * easedOut;\n if (tOut < 1) allOutgoingDone = false;\n\n const incomingElapsed = rowElapsed - incomingStartShift;\n if (incomingElapsed <= 0) {\n params.scriptedIncomingOffsets[col][row] = Number.NaN;\n allIncomingDone = false;\n continue;\n }\n\n const tIn = clamp(incomingElapsed / FLOW_FALL_MS, 0, 1);\n const easedIn = easeOutCubic(tIn);\n params.scriptedIncomingOffsets[col][row] = incomingFromOffsets[row] * (1 - easedIn);\n if (tIn < 1) allIncomingDone = false;\n }\n }\n\n return { allOutgoingDone, allIncomingDone };\n}\n","import {\n DEFAULT_PARTICLE_COLOR_RGB,\n DEFAULT_WINNING_CELL_BORDER_RGBA,\n GRID_COLS,\n GRID_ROWS,\n} from './constants';\nimport type { QualityPreset, SpinState, SymbolId } from './types';\nimport { normalizeAlphaChannel, normalizeRgbChannel, normalizeSegment } from './utils/math';\n\nexport function normalizeParticleColor(color?: [number, number, number]): [number, number, number] {\n if (!color) return DEFAULT_PARTICLE_COLOR_RGB;\n return [\n normalizeRgbChannel(color[0]),\n normalizeRgbChannel(color[1]),\n normalizeRgbChannel(color[2]),\n ];\n}\n\nexport function normalizeParticleColorMode(mode?: 'solid' | 'rainbow'): 'solid' | 'rainbow' {\n if (mode === 'rainbow') return 'rainbow';\n return 'solid';\n}\n\nexport function normalizeQuality(preset?: QualityPreset): QualityPreset {\n if (preset === 'high' || preset === 'balanced' || preset === 'low' || preset === 'auto') {\n return preset;\n }\n return 'auto';\n}\n\nexport function normalizeWinningCellBorderColor(\n color?: [number, number, number, number],\n): [number, number, number, number] {\n if (!color) return DEFAULT_WINNING_CELL_BORDER_RGBA;\n return [\n normalizeRgbChannel(color[0]),\n normalizeRgbChannel(color[1]),\n normalizeRgbChannel(color[2]),\n normalizeAlphaChannel(color[3]),\n ];\n}\n\nexport function rowsToStopGrid(rows: number[][]): number[][] {\n if (rows.length !== GRID_ROWS) {\n throw new Error(`rows must contain ${GRID_ROWS} rows`);\n }\n for (let row = 0; row < GRID_ROWS; row += 1) {\n if (!Array.isArray(rows[row]) || rows[row].length !== GRID_COLS) {\n throw new Error(`rows[${row}] must contain ${GRID_COLS} columns`);\n }\n }\n\n return [\n [rows[0][0], rows[1][0], rows[2][0]],\n [rows[0][1], rows[1][1], rows[2][1]],\n [rows[0][2], rows[1][2], rows[2][2]],\n ];\n}\n\nexport function normalizeStopGrid(stopGrid: number[][], elementsCount: number): SymbolId[][] {\n if (stopGrid.length !== GRID_COLS) {\n throw new Error(`stopGrid must contain ${GRID_COLS} columns`);\n }\n\n const next: SymbolId[][] = [];\n for (let col = 0; col < GRID_COLS; col += 1) {\n const column = stopGrid[col];\n if (!Array.isArray(column) || column.length !== GRID_ROWS) {\n throw new Error(`stopGrid[${col}] must contain ${GRID_ROWS} rows`);\n }\n next[col] = [\n normalizeSegment(column[0], elementsCount),\n normalizeSegment(column[1], elementsCount),\n normalizeSegment(column[2], elementsCount),\n ];\n }\n return next;\n}\n\nexport function normalizeInitialSegments(\n initialSegments: number[][],\n elementsCount: number,\n): SymbolId[][] {\n return normalizeStopGrid(rowsToStopGrid(initialSegments), elementsCount);\n}\n\nexport function cloneSpinState(state: SpinState): SpinState {\n return {\n stopGrid: state.stopGrid?.map((column) => [...column]),\n stopRows: state.stopRows?.map((row) => [...row]),\n finaleSequence: state.finaleSequence?.map((grid) => grid.map((column) => [...column])),\n finaleSequenceRows: state.finaleSequenceRows?.map((grid) => grid.map((row) => [...row])),\n callback: state.callback,\n };\n}\n","import { cloneSpinState } from '../normalize';\nimport type { SpinState } from '../types';\n\nexport class SpinQueueController {\n private queue: SpinState[];\n\n public constructor(initialQueue?: SpinState[]) {\n this.queue = (initialQueue ?? []).map((entry) => cloneSpinState(entry));\n }\n\n public hasPending(): boolean {\n return this.queue.length > 0;\n }\n\n public consume(): SpinState | null {\n if (this.queue.length === 0) return null;\n return this.queue.shift() ?? null;\n }\n}\n","import type { SpinPhase, SpinState } from '../types';\n\nexport type RuntimeState = {\n isSpinning: boolean;\n hasStartedFirstSpin: boolean;\n queueFinished: boolean;\n shouldHighlightCurrentSpin: boolean;\n activeSpinState: SpinState | null;\n phase: SpinPhase;\n winFlashStartedAt: number;\n outroStartedAt: number;\n idleStartedAt: number;\n preSpinStartedAt: number;\n swayEnvelope: number;\n winEffectsEnvelope: number;\n};\n\nexport function createRuntimeState(): RuntimeState {\n return {\n isSpinning: false,\n hasStartedFirstSpin: false,\n queueFinished: false,\n shouldHighlightCurrentSpin: false,\n activeSpinState: null,\n phase: 'idle',\n winFlashStartedAt: 0,\n outroStartedAt: 0,\n idleStartedAt: 0,\n preSpinStartedAt: 0,\n swayEnvelope: 1,\n winEffectsEnvelope: 1,\n };\n}\n\nexport function beginSpin(\n state: RuntimeState,\n params: {\n activeSpinState: SpinState | null;\n shouldHighlightCurrentSpin: boolean;\n startedAt: number;\n },\n): void {\n state.hasStartedFirstSpin = true;\n state.isSpinning = true;\n state.phase = 'outro';\n state.outroStartedAt = params.startedAt;\n state.activeSpinState = params.activeSpinState;\n state.shouldHighlightCurrentSpin = params.shouldHighlightCurrentSpin;\n}\n\nexport function finishSpin(state: RuntimeState, hasPendingInQueue: boolean, now: number): void {\n state.phase = 'idle';\n state.idleStartedAt = now;\n state.isSpinning = false;\n state.shouldHighlightCurrentSpin = false;\n state.queueFinished = !hasPendingInQueue;\n state.activeSpinState = null;\n}\n\nexport function startWinFlash(state: RuntimeState, startedAt: number): void {\n state.winFlashStartedAt = startedAt;\n state.phase = 'winFlash';\n}\n\nexport function destroyState(state: RuntimeState): void {\n state.isSpinning = false;\n state.queueFinished = true;\n}\n","import {\n FLOW_WIN_PARTICLES_PER_CELL_HIGH,\n FLOW_WIN_PULSE_AMPLITUDE,\n FLOW_WIN_PULSE_PERIOD_MS,\n GRID_COLS,\n GRID_ROWS,\n PARTICLE_FLY_DURATION_MS,\n} from '../constants';\nimport type { CellPosition, SpinPhase, SymbolId } from '../types';\nimport { clamp } from '../utils/math';\n\ntype DrawSpriteCell = (\n symbolId: SymbolId,\n x: number,\n y: number,\n width: number,\n height: number,\n) => void;\n\nconst SWAY_AMPLITUDE_DEG = 3;\nconst SWAY_OMEGA = 0.002;\n\nexport function drawGridLayer(params: {\n ctx: CanvasRenderingContext2D;\n phase: SpinPhase;\n now: number;\n swayEnvelope: number;\n grid: SymbolId[][];\n offsets: number[][] | null;\n columnSway?: [number, number, number];\n boardX: number;\n boardY: number;\n cellW: number;\n cellH: number;\n width: number;\n height: number;\n /** When true, winning cells are not drawn here (they are drawn by win effects). */\n skipWinningCells?: boolean;\n isWinningCell: (col: number, row: number) => boolean;\n drawSpriteCell: DrawSpriteCell;\n}): void {\n const sway = params.columnSway ?? [0, 0, 0];\n const swayAmplitudeRad = SWAY_AMPLITUDE_DEG * (Math.PI / 180);\n const envelope = Math.max(0, Math.min(1, params.swayEnvelope));\n const skipWinning = params.skipWinningCells ?? false;\n\n for (let col = 0; col < GRID_COLS; col += 1) {\n const x = params.boardX + col * params.cellW;\n const swayY = sway[col] ?? 0;\n for (let row = 0; row < GRID_ROWS; row += 1) {\n if (\n (skipWinning || params.phase === 'winFlash' || params.phase === 'preSpin') &&\n params.isWinningCell(col, row)\n )\n continue;\n const offsetY = params.offsets ? params.offsets[col][row] : 0;\n if (!Number.isFinite(offsetY)) continue;\n\n const y = params.boardY + row * params.cellH + offsetY + swayY;\n if (y > params.height || y + params.cellH < 0) continue;\n\n const rawAngle = Math.sin(params.now * SWAY_OMEGA + col * 1.1 + row * 1.3) * swayAmplitudeRad;\n const angle = rawAngle * envelope;\n\n const centerX = x + params.cellW * 0.5;\n const centerY = y + params.cellH * 0.5;\n\n params.ctx.save();\n params.ctx.translate(centerX, centerY);\n params.ctx.rotate(angle);\n params.ctx.translate(-params.cellW * 0.5, -params.cellH * 0.5);\n params.drawSpriteCell(params.grid[col][row], 0, 0, params.cellW, params.cellH);\n params.ctx.restore();\n }\n }\n}\n\nfunction hash01(a: number, b: number, c: number, d: number): number {\n const value = Math.sin(a * 127.1 + b * 311.7 + c * 74.7 + d * 19.3) * 43758.5453;\n return value - Math.floor(value);\n}\n\ntype ParticleSeeds = {\n seedA: number;\n seedB: number;\n seedC: number;\n phaseOffset: number;\n twinkleSeed: number;\n};\n\nconst particleSeedsCache = new Map<string, ParticleSeeds[]>();\n\nfunction getParticleSeeds(col: number, row: number): ParticleSeeds[] {\n const key = `${col},${row}`;\n let seeds = particleSeedsCache.get(key);\n if (seeds) return seeds;\n\n seeds = [];\n for (let i = 0; i < FLOW_WIN_PARTICLES_PER_CELL_HIGH; i += 1) {\n seeds.push({\n seedA: hash01(col, row, i, 1),\n seedB: hash01(col, row, i, 2),\n seedC: hash01(col, row, i, 3),\n phaseOffset: hash01(col, row, i, 4),\n twinkleSeed: hash01(col, row, i, 5),\n });\n }\n particleSeedsCache.set(key, seeds);\n return seeds;\n}\n\nexport function drawWinningEffects(params: {\n ctx: CanvasRenderingContext2D;\n now: number;\n phase: SpinPhase;\n winFlashStartedAt: number;\n winEffectsEnvelope: number;\n winningCells: CellPosition[];\n boardX: number;\n boardY: number;\n cellW: number;\n cellH: number;\n particlesPerCell: number;\n particleColorMode: 'solid' | 'rainbow';\n particleColorRgb: [number, number, number];\n winningCellBorderRgba: [number, number, number, number];\n getCell: (col: number, row: number) => SymbolId;\n drawSpriteCell: DrawSpriteCell;\n}): void {\n if (params.winningCells.length === 0) return;\n if (params.phase !== 'winFlash' && params.phase !== 'preSpin') return;\n\n const envelope = Math.max(0, Math.min(1, params.winEffectsEnvelope));\n const elapsed = Math.max(0, params.now - params.winFlashStartedAt);\n const pulseProgress = (elapsed % FLOW_WIN_PULSE_PERIOD_MS) / FLOW_WIN_PULSE_PERIOD_MS;\n const pulse = 1 + Math.sin(pulseProgress * Math.PI * 2) * FLOW_WIN_PULSE_AMPLITUDE;\n\n for (const cell of params.winningCells) {\n const x = params.boardX + cell.col * params.cellW;\n const y = params.boardY + cell.row * params.cellH;\n drawWinningCellBorder(\n params.ctx,\n x,\n y,\n params.cellW,\n params.cellH,\n cell.col,\n cell.row,\n params.winningCellBorderRgba,\n elapsed,\n envelope,\n );\n\n const symbol = params.getCell(cell.col, cell.row);\n const scale = 1 + (pulse - 1) * envelope;\n const scaledW = params.cellW * scale;\n const scaledH = params.cellH * scale;\n const offsetX = (params.cellW - scaledW) * 0.5;\n const offsetY = (params.cellH - scaledH) * 0.5;\n\n params.ctx.save();\n params.drawSpriteCell(symbol, x + offsetX, y + offsetY, scaledW, scaledH);\n params.ctx.restore();\n }\n\n if (params.particlesPerCell <= 0) return;\n\n params.ctx.save();\n params.ctx.beginPath();\n params.ctx.rect(params.boardX, params.boardY, params.cellW * GRID_COLS, params.cellH * GRID_ROWS);\n params.ctx.clip();\n\n for (const cell of params.winningCells) {\n const x = params.boardX + cell.col * params.cellW;\n const y = params.boardY + cell.row * params.cellH;\n drawCellParticleBurst({\n ctx: params.ctx,\n cell,\n x,\n y,\n elapsed,\n envelope,\n cellW: params.cellW,\n cellH: params.cellH,\n particlesPerCell: params.particlesPerCell,\n particleColorMode: params.particleColorMode,\n particleColorRgb: params.particleColorRgb,\n });\n }\n\n params.ctx.restore();\n}\n\nfunction drawWinningCellBorder(\n ctx: CanvasRenderingContext2D,\n x: number,\n y: number,\n cellW: number,\n cellH: number,\n cellCol: number,\n cellRow: number,\n color: [number, number, number, number],\n elapsed: number,\n envelope: number,\n): void {\n const inset = Math.max(1, Math.floor(Math.min(cellW, cellH) * 0.04));\n const lineWidth = Math.max(1, Math.floor(Math.min(cellW, cellH) * 0.025));\n const [, , , a] = color;\n\n const localX = (cellCol + 0.5) / GRID_COLS;\n const localY = (cellRow + 0.5) / GRID_ROWS;\n const hue = (elapsed * 0.15 + localX * 80 + localY * 45) % 360;\n\n const minSide = Math.min(cellW, cellH);\n const dashLength = Math.max(4, Math.floor(minSide * 0.04));\n const gapLength = Math.max(3, Math.floor(minSide * 0.03));\n\n ctx.save();\n ctx.lineWidth = lineWidth;\n ctx.strokeStyle = `hsla(${hue}, 90%, 70%, ${a * envelope})`;\n ctx.setLineDash([dashLength, gapLength]);\n ctx.lineDashOffset = -elapsed * 0.03;\n const halfLine = lineWidth * 0.5;\n ctx.strokeRect(\n x + inset + halfLine,\n y + inset + halfLine,\n cellW - inset * 2 - lineWidth,\n cellH - inset * 2 - lineWidth,\n );\n ctx.setLineDash([]);\n ctx.restore();\n}\n\nconst RAINBOW_HUE_BUCKETS = 24;\n\nfunction drawCellParticleBurst(params: {\n ctx: CanvasRenderingContext2D;\n cell: CellPosition;\n x: number;\n y: number;\n elapsed: number;\n envelope: number;\n cellW: number;\n cellH: number;\n particlesPerCell: number;\n particleColorMode: 'solid' | 'rainbow';\n particleColorRgb: [number, number, number];\n}): void {\n const centerX = params.x + params.cellW * 0.5;\n const centerY = params.y + params.cellH * 0.5;\n const maxDistance = Math.min(params.cellW, params.cellH) * 0.72;\n const baseRadius = Math.min(params.cellW, params.cellH) * 0.028;\n const globalFade = 0.96 * params.envelope;\n\n const [r, g, b] = params.particleColorRgb;\n const isSolid = params.particleColorMode === 'solid';\n if (isSolid) {\n params.ctx.fillStyle = `rgb(${r},${g},${b})`;\n }\n\n const seedsList = getParticleSeeds(params.cell.col, params.cell.row);\n\n for (let i = 0; i < params.particlesPerCell; i += 1) {\n const s = seedsList[i];\n const startTime = s.phaseOffset * PARTICLE_FLY_DURATION_MS;\n const age = params.elapsed - startTime;\n if (age < 0) continue;\n const particleT = (age % PARTICLE_FLY_DURATION_MS) / PARTICLE_FLY_DURATION_MS;\n\n const direction = s.seedA * Math.PI * 2;\n const distance = maxDistance * particleT * (0.35 + s.seedB * 0.65);\n const px = centerX + Math.cos(direction) * distance;\n const py = centerY + Math.sin(direction) * distance;\n const twinkle =\n 0.7 + 0.9 * Math.max(0, Math.sin((params.elapsed * 0.012 + s.twinkleSeed * 2) * Math.PI * 2));\n const radius = Math.max(1, baseRadius * (0.55 + s.seedC * 0.6) * (1 - particleT * 0.5));\n const alpha = clamp((0.9 + twinkle * 0.2) * globalFade, 0.9, 1);\n if (alpha <= 0) continue;\n\n if (isSolid) {\n params.ctx.globalAlpha = alpha;\n } else {\n const hueRaw =\n (s.seedA * 360 + params.elapsed * 0.24 + params.cell.col * 38 + params.cell.row * 22) % 360;\n const hue = Math.floor(hueRaw / (360 / RAINBOW_HUE_BUCKETS)) * (360 / RAINBOW_HUE_BUCKETS);\n params.ctx.fillStyle = `hsla(${hue},98%,64%,${alpha})`;\n }\n\n params.ctx.beginPath();\n params.ctx.arc(px, py, radius, 0, Math.PI * 2);\n params.ctx.fill();\n }\n\n params.ctx.globalAlpha = 1;\n}\n","import {\n DEFAULT_SPRITE_ELEMENTS_COUNT,\n FLOW_WIN_PARTICLES_PER_CELL_BALANCED,\n FLOW_WIN_PARTICLES_PER_CELL_HIGH,\n FLOW_WIN_PARTICLES_PER_CELL_LOW,\n GRID_COLS,\n GRID_ROWS,\n INITIAL_WIN_FLASH_DELAY_MS,\n} from './constants';\nimport {\n createRandomGrid,\n createZeroOffsets,\n fillOffsets,\n findMostFrequentCells,\n} from './core/grid';\nimport { RafLoop } from './core/loop';\nimport { updateOutroOffsets } from './core/outro';\nimport { SpinQueueController } from './core/spinQueue';\nimport {\n beginSpin,\n createRuntimeState,\n destroyState,\n finishSpin,\n startWinFlash,\n} from './core/state';\nimport {\n normalizeInitialSegments,\n normalizeParticleColor,\n normalizeParticleColorMode,\n normalizeQuality,\n normalizeStopGrid,\n normalizeWinningCellBorderColor,\n rowsToStopGrid,\n} from './normalize';\nimport { drawGridLayer, drawWinningEffects } from './render/canvasRenderer';\nimport type { CascadingReelConfig, CellPosition, QualityPreset, SymbolId } from './types';\nimport { normalizeSegment } from './utils/math';\n\nexport class CascadingReel {\n private readonly canvas: HTMLCanvasElement;\n private readonly container: HTMLElement;\n private readonly button?: HTMLButtonElement;\n private readonly ctx: CanvasRenderingContext2D;\n private readonly spinQueueController: SpinQueueController;\n private readonly spriteUrl?: string;\n private readonly spriteElementsCount: number;\n private readonly highlightInitialWinningCells: boolean;\n private readonly particleColorRgb: [number, number, number];\n private readonly particleColorMode: 'solid' | 'rainbow';\n private readonly winningCellBorderRgba: [number, number, number, number];\n private readonly quality: QualityPreset;\n\n private spriteImage: HTMLImageElement | null = null;\n private readonly rafLoop = new RafLoop();\n private readonly runtime = createRuntimeState();\n private dpr = 1;\n private width = 0;\n private height = 0;\n private cellW = 0;\n private cellH = 0;\n private boardX = 0;\n private boardY = 0;\n private scriptedCascadeQueue: number[][][] = [];\n private scriptedOutgoingGrid: SymbolId[][] | null = null;\n private scriptedPendingGrid: SymbolId[][] | null = null;\n private scriptedOutroStartedAt = 0;\n private scriptedOutgoingOffsets: number[][] = createZeroOffsets();\n private scriptedIncomingOffsets: number[][] = createZeroOffsets();\n private winningCells: CellPosition[] = [];\n private grid: SymbolId[][];\n private lastRafTime = 0;\n private particlesPerCell = FLOW_WIN_PARTICLES_PER_CELL_HIGH;\n private initialHighlightRequestedAt = 0;\n private static readonly SWAY_ENVELOPE_TAU_MS = 220;\n private static readonly PRE_SPIN_MS = 150;\n private static readonly WIN_EFFECTS_ENVELOPE_TAU_MS = 120;\n\n public constructor(config: CascadingReelConfig) {\n this.canvas = config.canvas;\n this.container = config.container;\n this.button = config.button;\n this.spriteUrl = config.sprite;\n this.spriteElementsCount = Math.max(\n 1,\n config.spriteElementsCount ?? DEFAULT_SPRITE_ELEMENTS_COUNT,\n );\n this.highlightInitialWinningCells = config.highlightInitialWinningCells !== false;\n this.spinQueueController = new SpinQueueController(config.queuedSpinStates);\n this.particleColorRgb = normalizeParticleColor(config.particleColorRgb);\n this.particleColorMode = normalizeParticleColorMode(config.particleColorMode);\n this.winningCellBorderRgba = normalizeWinningCellBorderColor(config.winningCellBorderRgba);\n this.quality = normalizeQuality(config.quality);\n\n const context = this.canvas.getContext('2d');\n if (!context) {\n throw new Error('2D canvas context is not available');\n }\n this.ctx = context;\n this.grid = config.initialSegments\n ? normalizeInitialSegments(config.initialSegments, this.spriteElementsCount)\n : createRandomGrid(this.spriteElementsCount);\n }\n\n public async init(): Promise<void> {\n this.bindEvents();\n this.resize();\n await this.loadSpriteIfProvided();\n this.applyInitialHighlightIfNeeded();\n this.prewarmWinEffects();\n requestAnimationFrame((warmNow) => {\n this.render(warmNow);\n this.startLoop();\n });\n }\n\n public destroy(): void {\n this.unbindEvents();\n this.rafLoop.stop();\n destroyState(this.runtime);\n this.clearWinningCells();\n }\n\n public spin(): void {\n if (this.runtime.isSpinning) return;\n if (this.runtime.queueFinished) return;\n if (!this.spinQueueController.hasPending()) {\n this.runtime.queueFinished = true;\n if (this.button) this.button.disabled = true;\n return;\n }\n\n const activeSpinState = this.spinQueueController.consume();\n const shouldHighlightCurrentSpin = !this.spinQueueController.hasPending();\n this.runtime.isSpinning = true;\n this.runtime.phase = 'preSpin';\n this.runtime.preSpinStartedAt = performance.now();\n this.runtime.activeSpinState = activeSpinState;\n this.runtime.shouldHighlightCurrentSpin = shouldHighlightCurrentSpin;\n this.runtime.hasStartedFirstSpin = true;\n\n if (this.button) this.button.disabled = true;\n }\n\n private bindEvents(): void {\n window.addEventListener('resize', this.resize);\n this.button?.addEventListener('click', this.onSpinClick);\n }\n\n private unbindEvents(): void {\n window.removeEventListener('resize', this.resize);\n this.button?.removeEventListener('click', this.onSpinClick);\n }\n\n private readonly onSpinClick = (): void => {\n if (this.runtime.isSpinning) return;\n this.spin();\n };\n\n private getNextGrid(stopGrid?: number[][]): SymbolId[][] {\n if (!stopGrid) return createRandomGrid(this.spriteElementsCount);\n return normalizeStopGrid(stopGrid, this.spriteElementsCount);\n }\n\n private update(now: number): void {\n if (!this.runtime.isSpinning) return;\n\n if (this.runtime.phase === 'preSpin') {\n if (now - this.runtime.preSpinStartedAt < CascadingReel.PRE_SPIN_MS) {\n return;\n }\n beginSpin(this.runtime, {\n activeSpinState: this.runtime.activeSpinState,\n shouldHighlightCurrentSpin: this.runtime.shouldHighlightCurrentSpin,\n startedAt: now,\n });\n this.runtime.preSpinStartedAt = 0;\n\n const scriptedSource = this.runtime.activeSpinState?.finaleSequenceRows\n ? this.runtime.activeSpinState.finaleSequenceRows.map((rows) => rowsToStopGrid(rows))\n : (this.runtime.activeSpinState?.finaleSequence ?? []);\n this.scriptedCascadeQueue = scriptedSource.map((grid) => grid.map((column) => [...column]));\n this.clearWinningCells();\n\n const stopGridSource = this.runtime.activeSpinState?.stopRows\n ? rowsToStopGrid(this.runtime.activeSpinState.stopRows)\n : this.runtime.activeSpinState?.stopGrid;\n const nextGrid = this.getNextGrid(stopGridSource);\n this.startOutroTransition(nextGrid, now);\n }\n\n if (this.runtime.phase === 'outro') {\n this.updateScriptedOutro(now);\n return;\n }\n if (this.runtime.phase === 'winFlash') return;\n }\n\n private updateScriptedOutro(now: number): void {\n if (!this.scriptedOutgoingGrid || !this.scriptedPendingGrid) {\n this.finishSpinWithUi();\n return;\n }\n\n const { allOutgoingDone, allIncomingDone } = updateOutroOffsets({\n now,\n height: this.height,\n boardY: this.boardY,\n cellH: this.cellH,\n scriptedOutroStartedAt: this.scriptedOutroStartedAt,\n scriptedOutgoingOffsets: this.scriptedOutgoingOffsets,\n scriptedIncomingOffsets: this.scriptedIncomingOffsets,\n });\n\n if (!allOutgoingDone || !allIncomingDone) return;\n if (!this.scriptedPendingGrid) {\n this.finishSpinWithUi();\n return;\n }\n\n this.grid = this.scriptedPendingGrid;\n this.scriptedOutgoingGrid = null;\n this.scriptedPendingGrid = null;\n this.clearWinningCells();\n fillOffsets(this.scriptedOutgoingOffsets, 0);\n fillOffsets(this.scriptedIncomingOffsets, 0);\n\n if (this.tryStartScriptedCascade(now)) return;\n if (!this.runtime.shouldHighlightCurrentSpin) {\n this.finishSpinWithUi();\n return;\n }\n\n this.winningCells = findMostFrequentCells(this.grid);\n startWinFlash(this.runtime, now);\n }\n\n private tryStartScriptedCascade(now: number): boolean {\n if (this.scriptedCascadeQueue.length === 0) return false;\n const nextGrid = this.scriptedCascadeQueue.shift();\n if (!nextGrid) return false;\n this.startOutroTransition(normalizeStopGrid(nextGrid, this.spriteElementsCount), now);\n return true;\n }\n\n private startOutroTransition(nextGrid: SymbolId[][], now: number): void {\n this.scriptedOutgoingGrid = this.grid.map((column) => [...column]);\n this.scriptedPendingGrid = nextGrid.map((column) => [...column]);\n fillOffsets(this.scriptedIncomingOffsets, Number.NaN);\n fillOffsets(this.scriptedOutgoingOffsets, 0);\n this.clearWinningCells();\n this.runtime.phase = 'outro';\n this.scriptedOutroStartedAt = now;\n }\n\n private finishSpinWithUi(): void {\n const callback = this.runtime.activeSpinState?.callback;\n finishSpin(this.runtime, this.spinQueueController.hasPending(), performance.now());\n if (this.button) this.button.disabled = this.runtime.queueFinished;\n callback?.();\n }\n\n private applyInitialHighlightIfNeeded(): void {\n if (!this.highlightInitialWinningCells) return;\n this.winningCells = findMostFrequentCells(this.grid);\n this.initialHighlightRequestedAt = performance.now();\n }\n\n private static readonly ZERO_SWAY: [number, number, number] = [0, 0, 0];\n\n private render(now: number): void {\n if (\n this.initialHighlightRequestedAt > 0 &&\n now - this.initialHighlightRequestedAt >= INITIAL_WIN_FLASH_DELAY_MS\n ) {\n startWinFlash(this.runtime, this.initialHighlightRequestedAt);\n this.runtime.winEffectsEnvelope = 1;\n this.initialHighlightRequestedAt = 0;\n }\n\n const swayTarget = this.runtime.phase === 'outro' || this.runtime.phase === 'preSpin' ? 0 : 1;\n const winEffectsTarget =\n this.runtime.phase === 'preSpin' ? 0 : this.runtime.phase === 'winFlash' ? 1 : 0;\n if (this.lastRafTime > 0) {\n const dt = Math.min(now - this.lastRafTime, 50);\n const kSway = 1 - Math.exp(-dt / CascadingReel.SWAY_ENVELOPE_TAU_MS);\n this.runtime.swayEnvelope += (swayTarget - this.runtime.swayEnvelope) * kSway;\n const kWin = 1 - Math.exp(-dt / CascadingReel.WIN_EFFECTS_ENVELOPE_TAU_MS);\n this.runtime.winEffectsEnvelope +=\n (winEffectsTarget - this.runtime.winEffectsEnvelope) * kWin;\n }\n this.lastRafTime = now;\n\n this.ctx.clearRect(0, 0, this.width, this.height);\n if (this.runtime.phase === 'outro' && this.scriptedOutgoingGrid && this.scriptedPendingGrid) {\n this.drawLayer(\n this.scriptedOutgoingGrid,\n this.scriptedOutgoingOffsets,\n CascadingReel.ZERO_SWAY,\n now,\n );\n this.drawLayer(\n this.scriptedPendingGrid,\n this.scriptedIncomingOffsets,\n CascadingReel.ZERO_SWAY,\n now,\n );\n } else {\n this.drawBaseGrid(now);\n }\n\n const winPhase = this.initialHighlightRequestedAt > 0 ? 'winFlash' : this.runtime.phase;\n const winFlashStartedAt =\n this.initialHighlightRequestedAt > 0\n ? this.initialHighlightRequestedAt\n : this.runtime.winFlashStartedAt;\n const winEffectsEnvelope =\n this.initialHighlightRequestedAt > 0 ? 1 : this.runtime.winEffectsEnvelope;\n\n drawWinningEffects({\n ctx: this.ctx,\n now,\n phase: winPhase,\n winFlashStartedAt,\n winEffectsEnvelope,\n winningCells: this.winningCells,\n boardX: this.boardX,\n boardY: this.boardY,\n cellW: this.cellW,\n cellH: this.cellH,\n particlesPerCell:\n this.runtime.phase === 'winFlash' &&\n this.runtime.shouldHighlightCurrentSpin &&\n this.runtime.hasStartedFirstSpin\n ? this.particlesPerCell\n : 0,\n particleColorMode: this.particleColorMode,\n particleColorRgb: this.particleColorRgb,\n winningCellBorderRgba: this.winningCellBorderRgba,\n getCell: this.getCell,\n drawSpriteCell: this.drawSpriteCell,\n });\n }\n\n private drawBaseGrid(now: number): void {\n this.drawLayer(this.grid, null, CascadingReel.ZERO_SWAY, now);\n }\n\n private drawLayer(\n grid: SymbolId[][],\n offsets: number[][] | null,\n columnSway: [number, number, number],\n now: number,\n ): void {\n const skipWinningCells =\n this.runtime.phase === 'winFlash' ||\n this.runtime.phase === 'preSpin' ||\n (this.initialHighlightRequestedAt > 0 && this.winningCells.length > 0);\n\n drawGridLayer({\n ctx: this.ctx,\n phase: this.runtime.phase,\n now,\n swayEnvelope: this.runtime.swayEnvelope,\n grid,\n offsets,\n columnSway,\n boardX: this.boardX,\n boardY: this.boardY,\n cellW: this.cellW,\n cellH: this.cellH,\n width: this.width,\n height: this.height,\n skipWinningCells,\n isWinningCell: this.isWinningCell,\n drawSpriteCell: this.drawSpriteCell,\n });\n }\n\n private readonly getCell = (col: number, row: number): SymbolId => {\n return this.grid[col][row];\n };\n\n private readonly isWinningCell = (col: number, row: number): boolean => {\n return this.winningCells.some((cell) => cell.col === col && cell.row === row);\n };\n\n private drawSpriteCellToCtx(\n ctx: CanvasRenderingContext2D,\n symbolId: SymbolId,\n x: number,\n y: number,\n width: number,\n height: number,\n ): void {\n const image = this.spriteImage;\n if (!image) return;\n\n const segmentIndex = normalizeSegment(symbolId, this.spriteElementsCount);\n const segmentHeight = image.height / this.spriteElementsCount;\n const sourceY = Math.floor(segmentIndex * segmentHeight);\n const sourceHeight = Math.floor(segmentHeight);\n\n ctx.drawImage(image, 0, sourceY, image.width, sourceHeight, x, y, width, height);\n }\n\n private readonly drawSpriteCell = (\n symbolId: SymbolId,\n x: number,\n y: number,\n width: number,\n height: number,\n ): void => {\n this.drawSpriteCellToCtx(this.ctx, symbolId, x, y, width, height);\n };\n\n private clearWinningCells(): void {\n this.winningCells = [];\n }\n\n private readonly resize = (): void => {\n const bounds = this.container.getBoundingClientRect();\n this.dpr = Math.max(1, Math.min(window.devicePixelRatio || 1, 2));\n const side = Math.max(300, Math.floor(bounds.width * this.dpr));\n this.width = side;\n this.height = side;\n const squareSize = Math.floor(Math.min(this.width / GRID_COLS, this.height / GRID_ROWS));\n this.cellW = squareSize;\n this.cellH = squareSize;\n this.boardX = Math.floor((this.width - this.cellW * GRID_COLS) / 2);\n this.boardY = Math.floor((this.height - this.cellH * GRID_ROWS) / 2);\n\n if (this.quality === 'high') this.particlesPerCell = FLOW_WIN_PARTICLES_PER_CELL_HIGH;\n else if (this.quality === 'balanced')\n this.particlesPerCell = FLOW_WIN_PARTICLES_PER_CELL_BALANCED;\n else if (this.quality === 'low') this.particlesPerCell = FLOW_WIN_PARTICLES_PER_CELL_LOW;\n else\n this.particlesPerCell =\n this.dpr >= 2 ? FLOW_WIN_PARTICLES_PER_CELL_BALANCED : FLOW_WIN_PARTICLES_PER_CELL_HIGH;\n\n this.canvas.width = this.width;\n this.canvas.height = this.height;\n };\n\n private async loadSpriteIfProvided(): Promise<void> {\n if (!this.spriteUrl) return;\n const image = new Image();\n image.decoding = 'async';\n image.src = this.spriteUrl;\n try {\n await image.decode();\n this.spriteImage = image;\n } catch {\n this.spriteImage = null;\n }\n }\n\n private prewarmWinEffects(): void {\n if (this.winningCells.length === 0) return;\n\n const offscreen = document.createElement('canvas');\n offscreen.width = this.width;\n offscreen.height = this.height;\n const offscreenCtx = offscreen.getContext('2d');\n if (!offscreenCtx) return;\n\n const savedPhase = this.runtime.phase;\n const savedWinFlashStartedAt = this.runtime.winFlashStartedAt;\n\n this.runtime.phase = 'winFlash';\n this.runtime.winFlashStartedAt = performance.now() - 300;\n\n const prewarmNow = performance.now();\n const particlesPrewarm = Math.min(this.particlesPerCell, FLOW_WIN_PARTICLES_PER_CELL_LOW);\n\n const drawSpriteToOffscreen = (\n symbolId: SymbolId,\n x: number,\n y: number,\n w: number,\n h: number,\n ): void => {\n this.drawSpriteCellToCtx(offscreenCtx, symbolId, x, y, w, h);\n };\n\n for (let frame = 0; frame < 2; frame += 1) {\n drawWinningEffects({\n ctx: offscreenCtx,\n now: prewarmNow + frame * 16,\n phase: 'winFlash',\n winFlashStartedAt: this.runtime.winFlashStartedAt,\n winEffectsEnvelope: 1,\n winningCells: this.winningCells,\n boardX: this.boardX,\n boardY: this.boardY,\n cellW: this.cellW,\n cellH: this.cellH,\n particlesPerCell: particlesPrewarm,\n particleColorMode: this.particleColorMode,\n particleColorRgb: this.particleColorRgb,\n winningCellBorderRgba: this.winningCellBorderRgba,\n getCell: this.getCell,\n drawSpriteCell: drawSpriteToOffscreen,\n });\n }\n\n this.runtime.phase = savedPhase;\n this.runtime.winFlashStartedAt = savedWinFlashStartedAt;\n }\n\n private startLoop(): void {\n if (this.rafLoop.isRunning()) return;\n this.rafLoop.start((time: number): boolean => {\n this.update(time);\n this.render(time);\n return true;\n });\n }\n}\n"],"names":["DEFAULT_SPRITE_ELEMENTS_COUNT","GRID_COLS","GRID_ROWS","FLOW_OUTRO_ROW_GAP_MS","FLOW_ROW_BASE_SPACING_RATIO","FLOW_WIN_PULSE_PERIOD_MS","FLOW_WIN_PULSE_AMPLITUDE","PARTICLE_FLY_DURATION_MS","FLOW_WIN_PARTICLES_PER_CELL_HIGH","FLOW_WIN_PARTICLES_PER_CELL_BALANCED","FLOW_WIN_PARTICLES_PER_CELL_LOW","DEFAULT_PARTICLE_COLOR_RGB","DEFAULT_WINNING_CELL_BORDER_RGBA","FLOW_COLUMN_STAGGER_MS","FLOW_FALL_MS","FLOW_OUTRO_OVERLAP_MS","INITIAL_WIN_FLASH_DELAY_MS","clamp","value","min","max","easeOutCubic","randomInt","maxExclusive","normalizeSegment","segment","elementsCount","normalizeRgbChannel","normalizeAlphaChannel","createRandomGrid","spriteElementsCount","grid","col","column","row","findMostFrequentCells","counts","symbol","selectedSymbol","maxCount","count","cells","createZeroOffsets","fillOffsets","offsets","RafLoop","step","now","buildSequentialRowStartDelays","fromRowOffsets","duration","gapMs","delays","nextDelay","baseSpacing","updateOutroOffsets","params","allOutgoingDone","allIncomingDone","outgoingDistance","outgoingOffsetsForOrder","incomingFromOffsets","rowStartDelays","incomingStartShift","columnElapsed","rowElapsed","tOut","easedOut","incomingElapsed","tIn","easedIn","normalizeParticleColor","color","normalizeParticleColorMode","mode","normalizeQuality","preset","normalizeWinningCellBorderColor","rowsToStopGrid","rows","normalizeStopGrid","stopGrid","next","normalizeInitialSegments","initialSegments","cloneSpinState","state","SpinQueueController","initialQueue","entry","createRuntimeState","beginSpin","finishSpin","hasPendingInQueue","startWinFlash","startedAt","destroyState","SWAY_AMPLITUDE_DEG","SWAY_OMEGA","drawGridLayer","sway","swayAmplitudeRad","envelope","skipWinning","x","swayY","offsetY","y","angle","centerX","centerY","hash01","a","b","c","d","particleSeedsCache","getParticleSeeds","key","seeds","i","drawWinningEffects","elapsed","pulseProgress","pulse","cell","drawWinningCellBorder","scale","scaledW","scaledH","offsetX","drawCellParticleBurst","ctx","cellW","cellH","cellCol","cellRow","inset","lineWidth","localX","localY","hue","minSide","dashLength","gapLength","halfLine","RAINBOW_HUE_BUCKETS","maxDistance","baseRadius","globalFade","g","isSolid","seedsList","s","startTime","age","particleT","direction","distance","px","py","twinkle","radius","alpha","hueRaw","CascadingReel","config","context","warmNow","activeSpinState","shouldHighlightCurrentSpin","scriptedSource","stopGridSource","nextGrid","callback","swayTarget","winEffectsTarget","dt","kSway","kWin","winPhase","winFlashStartedAt","winEffectsEnvelope","columnSway","skipWinningCells","symbolId","width","height","image","segmentIndex","segmentHeight","sourceY","sourceHeight","bounds","side","squareSize","offscreen","offscreenCtx","savedPhase","savedWinFlashStartedAt","prewarmNow","particlesPrewarm","drawSpriteToOffscreen","w","h","frame","time"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":["../src/constants.ts","../src/utils/math.ts","../src/core/grid.ts","../src/core/loop.ts","../src/core/outro.ts","../src/normalize.ts","../src/core/spinQueue.ts","../src/core/state.ts","../src/render/canvasRenderer.ts","../src/CascadingReel.ts"],"sourcesContent":["export const DEFAULT_SPRITE_ELEMENTS_COUNT = 6;\nexport const GRID_COLS = 3;\nexport const GRID_ROWS = 3;\nexport const FLOW_OUTRO_ROW_GAP_MS = 34;\nexport const FLOW_ROW_BASE_SPACING_RATIO = 0.05;\n/** Длительность фазы подсветки выигрыша (мс). Подсветка отключается по таймауту. */\nexport const FLOW_WIN_FLASH_MS = 5000;\nexport const FLOW_WIN_PULSE_PERIOD_MS = 1800;\nexport const FLOW_WIN_PULSE_AMPLITUDE = 0.1;\n/** Duration for one particle to fly from center to edge (continuous spray). */\nexport const PARTICLE_FLY_DURATION_MS = 720;\nexport const FLOW_WIN_PARTICLES_PER_CELL_HIGH = 34;\nexport const FLOW_WIN_PARTICLES_PER_CELL_BALANCED = 20;\nexport const FLOW_WIN_PARTICLES_PER_CELL_LOW = 12;\nexport const DEFAULT_PARTICLE_COLOR_RGB: [number, number, number] = [255, 235, 110];\nexport const DEFAULT_WINNING_CELL_BORDER_RGBA: [number, number, number, number] = [\n 255, 255, 255, 0.78,\n];\nexport const FLOW_COLUMN_STAGGER_MS = 190;\nexport const FLOW_FALL_MS = 650;\nexport const FLOW_OUTRO_OVERLAP_MS = 220;\n/** Delay before starting initial win-effect so canvas/JIT can warm up. */\nexport const INITIAL_WIN_FLASH_DELAY_MS = 200;\n","export function clamp(value: number, min: number, max: number): number {\n if (value < min) return min;\n if (value > max) return max;\n return value;\n}\n\nexport function easeOutCubic(t: number): number {\n return 1 - (1 - t) ** 3;\n}\n\nexport function randomInt(maxExclusive: number): number {\n return Math.floor(Math.random() * maxExclusive);\n}\n\nexport function normalizeSegment(segment: number, elementsCount: number): number {\n return ((segment % elementsCount) + elementsCount) % elementsCount;\n}\n\nexport function normalizeRgbChannel(value: number): number {\n return clamp(Math.round(value), 0, 255);\n}\n\nexport function normalizeAlphaChannel(value: number): number {\n return clamp(value, 0, 1);\n}\n","import { GRID_COLS, GRID_ROWS } from '../constants';\nimport type { CellPosition, SymbolId } from '../types';\nimport { randomInt } from '../utils/math';\n\nexport function createRandomGrid(spriteElementsCount: number): SymbolId[][] {\n const grid: SymbolId[][] = [];\n for (let col = 0; col < GRID_COLS; col += 1) {\n const column: SymbolId[] = [];\n for (let row = 0; row < GRID_ROWS; row += 1) {\n column.push(randomInt(spriteElementsCount));\n }\n grid.push(column);\n }\n return grid;\n}\n\nexport function findMostFrequentCells(grid: SymbolId[][]): CellPosition[] {\n const counts = new Map<SymbolId, number>();\n for (let col = 0; col < GRID_COLS; col += 1) {\n for (let row = 0; row < GRID_ROWS; row += 1) {\n const symbol = grid[col][row];\n counts.set(symbol, (counts.get(symbol) ?? 0) + 1);\n }\n }\n\n let selectedSymbol: SymbolId = grid[0][0];\n let maxCount = -1;\n for (const [symbol, count] of counts.entries()) {\n if (count > maxCount) {\n maxCount = count;\n selectedSymbol = symbol;\n }\n }\n\n const cells: CellPosition[] = [];\n for (let col = 0; col < GRID_COLS; col += 1) {\n for (let row = 0; row < GRID_ROWS; row += 1) {\n if (grid[col][row] === selectedSymbol) {\n cells.push({ col, row });\n }\n }\n }\n return cells;\n}\n\nexport function createZeroOffsets(): number[][] {\n return Array.from({ length: GRID_COLS }, () => Array.from({ length: GRID_ROWS }, () => 0));\n}\n\nexport function fillOffsets(offsets: number[][], value: number): void {\n for (let col = 0; col < GRID_COLS; col += 1) {\n for (let row = 0; row < GRID_ROWS; row += 1) {\n offsets[col][row] = value;\n }\n }\n}\n","export type RafStep = (now: number) => boolean;\n\nexport class RafLoop {\n private rafId: number | null = null;\n private step: RafStep | null = null;\n\n public start(step: RafStep): void {\n if (this.rafId !== null) return;\n this.step = step;\n this.rafId = requestAnimationFrame(this.tick);\n }\n\n public stop(): void {\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n this.step = null;\n }\n\n public isRunning(): boolean {\n return this.rafId !== null;\n }\n\n private readonly tick = (now: number): void => {\n if (!this.step) {\n this.stop();\n return;\n }\n\n const shouldContinue = this.step(now);\n if (shouldContinue === false) {\n this.stop();\n return;\n }\n\n if (this.rafId === null) return;\n this.rafId = requestAnimationFrame(this.tick);\n };\n}\n","import {\n FLOW_COLUMN_STAGGER_MS,\n FLOW_FALL_MS,\n FLOW_OUTRO_OVERLAP_MS,\n FLOW_OUTRO_ROW_GAP_MS,\n FLOW_ROW_BASE_SPACING_RATIO,\n GRID_ROWS,\n} from '../constants';\nimport { clamp, easeOutCubic } from '../utils/math';\n\nexport function buildSequentialRowStartDelays(\n fromRowOffsets: [number, number, number],\n duration: number,\n gapMs: number,\n): [number, number, number] {\n const delays: [number, number, number] = [0, 0, 0];\n let nextDelay = 0;\n for (let row = GRID_ROWS - 1; row >= 0; row -= 1) {\n if (fromRowOffsets[row] === 0) {\n delays[row] = 0;\n continue;\n }\n delays[row] = nextDelay;\n const baseSpacing = Math.floor(duration * FLOW_ROW_BASE_SPACING_RATIO);\n nextDelay += baseSpacing + gapMs;\n }\n return delays;\n}\n\nexport function updateOutroOffsets(params: {\n now: number;\n height: number;\n boardY: number;\n cellH: number;\n scriptedOutroStartedAt: number;\n scriptedOutgoingOffsets: number[][];\n scriptedIncomingOffsets: number[][];\n}): { allOutgoingDone: boolean; allIncomingDone: boolean } {\n let allOutgoingDone = true;\n let allIncomingDone = true;\n\n const exitEpsilon = 2;\n const outgoingDistance = params.height - params.boardY + params.cellH + exitEpsilon;\n const outgoingOffsetsForOrder: [number, number, number] = [\n outgoingDistance,\n outgoingDistance,\n outgoingDistance,\n ];\n const incomingFromOffsets: [number, number, number] = [\n -params.cellH,\n -params.cellH * 2,\n -params.cellH * 3,\n ];\n const rowStartDelays = buildSequentialRowStartDelays(\n outgoingOffsetsForOrder,\n FLOW_FALL_MS,\n FLOW_OUTRO_ROW_GAP_MS,\n );\n const incomingStartShift = FLOW_FALL_MS - FLOW_OUTRO_OVERLAP_MS;\n\n for (let col = 0; col < params.scriptedOutgoingOffsets.length; col += 1) {\n const columnElapsed =\n params.now - (params.scriptedOutroStartedAt + col * FLOW_COLUMN_STAGGER_MS);\n for (let row = 0; row < GRID_ROWS; row += 1) {\n const rowElapsed = columnElapsed - rowStartDelays[row];\n\n if (rowElapsed <= 0) {\n params.scriptedOutgoingOffsets[col][row] = 0;\n params.scriptedIncomingOffsets[col][row] = Number.NaN;\n allOutgoingDone = false;\n allIncomingDone = false;\n continue;\n }\n\n const tOut = clamp(rowElapsed / FLOW_FALL_MS, 0, 1);\n const easedOut = easeOutCubic(tOut);\n params.scriptedOutgoingOffsets[col][row] = outgoingDistance * easedOut;\n if (tOut < 1) allOutgoingDone = false;\n\n const incomingElapsed = rowElapsed - incomingStartShift;\n if (incomingElapsed <= 0) {\n params.scriptedIncomingOffsets[col][row] = Number.NaN;\n allIncomingDone = false;\n continue;\n }\n\n const tIn = clamp(incomingElapsed / FLOW_FALL_MS, 0, 1);\n const easedIn = easeOutCubic(tIn);\n params.scriptedIncomingOffsets[col][row] = incomingFromOffsets[row] * (1 - easedIn);\n if (tIn < 1) allIncomingDone = false;\n }\n }\n\n return { allOutgoingDone, allIncomingDone };\n}\n","import {\n DEFAULT_PARTICLE_COLOR_RGB,\n DEFAULT_WINNING_CELL_BORDER_RGBA,\n GRID_COLS,\n GRID_ROWS,\n} from './constants';\nimport type { QualityPreset, SpinState, SymbolId } from './types';\nimport { normalizeAlphaChannel, normalizeRgbChannel, normalizeSegment } from './utils/math';\n\nexport function normalizeParticleColor(color?: [number, number, number]): [number, number, number] {\n if (!color) return DEFAULT_PARTICLE_COLOR_RGB;\n return [\n normalizeRgbChannel(color[0]),\n normalizeRgbChannel(color[1]),\n normalizeRgbChannel(color[2]),\n ];\n}\n\nexport function normalizeParticleColorMode(mode?: 'solid' | 'rainbow'): 'solid' | 'rainbow' {\n if (mode === 'rainbow') return 'rainbow';\n return 'solid';\n}\n\nexport function normalizeQuality(preset?: QualityPreset): QualityPreset {\n if (preset === 'high' || preset === 'balanced' || preset === 'low' || preset === 'auto') {\n return preset;\n }\n return 'auto';\n}\n\nexport function normalizeWinningCellBorderColor(\n color?: [number, number, number, number],\n): [number, number, number, number] {\n if (!color) return DEFAULT_WINNING_CELL_BORDER_RGBA;\n return [\n normalizeRgbChannel(color[0]),\n normalizeRgbChannel(color[1]),\n normalizeRgbChannel(color[2]),\n normalizeAlphaChannel(color[3]),\n ];\n}\n\nexport function rowsToStopGrid(rows: number[][]): number[][] {\n if (rows.length !== GRID_ROWS) {\n throw new Error(`rows must contain ${GRID_ROWS} rows`);\n }\n for (let row = 0; row < GRID_ROWS; row += 1) {\n if (!Array.isArray(rows[row]) || rows[row].length !== GRID_COLS) {\n throw new Error(`rows[${row}] must contain ${GRID_COLS} columns`);\n }\n }\n\n return [\n [rows[0][0], rows[1][0], rows[2][0]],\n [rows[0][1], rows[1][1], rows[2][1]],\n [rows[0][2], rows[1][2], rows[2][2]],\n ];\n}\n\nexport function normalizeStopGrid(stopGrid: number[][], elementsCount: number): SymbolId[][] {\n if (stopGrid.length !== GRID_COLS) {\n throw new Error(`stopGrid must contain ${GRID_COLS} columns`);\n }\n\n const next: SymbolId[][] = [];\n for (let col = 0; col < GRID_COLS; col += 1) {\n const column = stopGrid[col];\n if (!Array.isArray(column) || column.length !== GRID_ROWS) {\n throw new Error(`stopGrid[${col}] must contain ${GRID_ROWS} rows`);\n }\n next[col] = [\n normalizeSegment(column[0], elementsCount),\n normalizeSegment(column[1], elementsCount),\n normalizeSegment(column[2], elementsCount),\n ];\n }\n return next;\n}\n\nexport function normalizeInitialSegments(\n initialSegments: number[][],\n elementsCount: number,\n): SymbolId[][] {\n return normalizeStopGrid(rowsToStopGrid(initialSegments), elementsCount);\n}\n\nexport function cloneSpinState(state: SpinState): SpinState {\n return {\n stopGrid: state.stopGrid?.map((column) => [...column]),\n stopRows: state.stopRows?.map((row) => [...row]),\n finaleSequence: state.finaleSequence?.map((grid) => grid.map((column) => [...column])),\n finaleSequenceRows: state.finaleSequenceRows?.map((grid) => grid.map((row) => [...row])),\n highlightWin: state.highlightWin,\n callback: state.callback,\n };\n}\n","import { cloneSpinState } from '../normalize';\nimport type { SpinState } from '../types';\n\nexport class SpinQueueController {\n private queue: SpinState[];\n\n public constructor(initialQueue?: SpinState[]) {\n this.queue = (initialQueue ?? []).map((entry) => cloneSpinState(entry));\n }\n\n public hasPending(): boolean {\n return this.queue.length > 0;\n }\n\n public consume(): SpinState | null {\n if (this.queue.length === 0) return null;\n return this.queue.shift() ?? null;\n }\n}\n","import type { SpinPhase, SpinState } from '../types';\n\nexport type RuntimeState = {\n isSpinning: boolean;\n hasStartedFirstSpin: boolean;\n queueFinished: boolean;\n shouldHighlightCurrentSpin: boolean;\n activeSpinState: SpinState | null;\n phase: SpinPhase;\n winFlashStartedAt: number;\n outroStartedAt: number;\n idleStartedAt: number;\n preSpinStartedAt: number;\n swayEnvelope: number;\n winEffectsEnvelope: number;\n};\n\nexport function createRuntimeState(): RuntimeState {\n return {\n isSpinning: false,\n hasStartedFirstSpin: false,\n queueFinished: false,\n shouldHighlightCurrentSpin: false,\n activeSpinState: null,\n phase: 'idle',\n winFlashStartedAt: 0,\n outroStartedAt: 0,\n idleStartedAt: 0,\n preSpinStartedAt: 0,\n swayEnvelope: 1,\n winEffectsEnvelope: 1,\n };\n}\n\nexport function beginSpin(\n state: RuntimeState,\n params: {\n activeSpinState: SpinState | null;\n shouldHighlightCurrentSpin: boolean;\n startedAt: number;\n },\n): void {\n state.hasStartedFirstSpin = true;\n state.isSpinning = true;\n state.phase = 'outro';\n state.outroStartedAt = params.startedAt;\n state.activeSpinState = params.activeSpinState;\n state.shouldHighlightCurrentSpin = params.shouldHighlightCurrentSpin;\n}\n\nexport function finishSpin(state: RuntimeState, hasPendingInQueue: boolean, now: number): void {\n state.phase = 'idle';\n state.idleStartedAt = now;\n state.isSpinning = false;\n state.shouldHighlightCurrentSpin = false;\n state.queueFinished = !hasPendingInQueue;\n state.activeSpinState = null;\n}\n\nexport function startWinFlash(state: RuntimeState, startedAt: number): void {\n state.winFlashStartedAt = startedAt;\n state.phase = 'winFlash';\n}\n\nexport function destroyState(state: RuntimeState): void {\n state.isSpinning = false;\n state.queueFinished = true;\n}\n","import {\n FLOW_WIN_PARTICLES_PER_CELL_HIGH,\n FLOW_WIN_PULSE_AMPLITUDE,\n FLOW_WIN_PULSE_PERIOD_MS,\n GRID_COLS,\n GRID_ROWS,\n PARTICLE_FLY_DURATION_MS,\n} from '../constants';\nimport type { CellPosition, SpinPhase, SymbolId } from '../types';\nimport { clamp } from '../utils/math';\n\ntype DrawSpriteCell = (\n symbolId: SymbolId,\n x: number,\n y: number,\n width: number,\n height: number,\n) => void;\n\nconst SWAY_AMPLITUDE_DEG = 3;\nconst SWAY_OMEGA = 0.002;\n\nexport function drawGridLayer(params: {\n ctx: CanvasRenderingContext2D;\n phase: SpinPhase;\n now: number;\n swayEnvelope: number;\n grid: SymbolId[][];\n offsets: number[][] | null;\n columnSway?: [number, number, number];\n boardX: number;\n boardY: number;\n cellW: number;\n cellH: number;\n width: number;\n height: number;\n /** When true, winning cells are not drawn here (they are drawn by win effects). */\n skipWinningCells?: boolean;\n isWinningCell: (col: number, row: number) => boolean;\n drawSpriteCell: DrawSpriteCell;\n}): void {\n const sway = params.columnSway ?? [0, 0, 0];\n const swayAmplitudeRad = SWAY_AMPLITUDE_DEG * (Math.PI / 180);\n const envelope = Math.max(0, Math.min(1, params.swayEnvelope));\n const skipWinning = params.skipWinningCells ?? false;\n\n for (let col = 0; col < GRID_COLS; col += 1) {\n const x = params.boardX + col * params.cellW;\n const swayY = sway[col] ?? 0;\n for (let row = 0; row < GRID_ROWS; row += 1) {\n if (\n (skipWinning || params.phase === 'winFlash' || params.phase === 'preSpin') &&\n params.isWinningCell(col, row)\n )\n continue;\n const offsetY = params.offsets ? params.offsets[col][row] : 0;\n if (!Number.isFinite(offsetY)) continue;\n\n const y = params.boardY + row * params.cellH + offsetY + swayY;\n if (y > params.height || y + params.cellH < 0) continue;\n\n const rawAngle = Math.sin(params.now * SWAY_OMEGA + col * 1.1 + row * 1.3) * swayAmplitudeRad;\n const angle = rawAngle * envelope;\n\n const centerX = x + params.cellW * 0.5;\n const centerY = y + params.cellH * 0.5;\n\n params.ctx.save();\n params.ctx.translate(centerX, centerY);\n params.ctx.rotate(angle);\n params.ctx.translate(-params.cellW * 0.5, -params.cellH * 0.5);\n params.drawSpriteCell(params.grid[col][row], 0, 0, params.cellW, params.cellH);\n params.ctx.restore();\n }\n }\n}\n\nfunction hash01(a: number, b: number, c: number, d: number): number {\n const value = Math.sin(a * 127.1 + b * 311.7 + c * 74.7 + d * 19.3) * 43758.5453;\n return value - Math.floor(value);\n}\n\ntype ParticleSeeds = {\n seedA: number;\n seedB: number;\n seedC: number;\n phaseOffset: number;\n twinkleSeed: number;\n};\n\nconst particleSeedsCache = new Map<string, ParticleSeeds[]>();\n\nfunction getParticleSeeds(col: number, row: number): ParticleSeeds[] {\n const key = `${col},${row}`;\n let seeds = particleSeedsCache.get(key);\n if (seeds) return seeds;\n\n seeds = [];\n for (let i = 0; i < FLOW_WIN_PARTICLES_PER_CELL_HIGH; i += 1) {\n seeds.push({\n seedA: hash01(col, row, i, 1),\n seedB: hash01(col, row, i, 2),\n seedC: hash01(col, row, i, 3),\n phaseOffset: hash01(col, row, i, 4),\n twinkleSeed: hash01(col, row, i, 5),\n });\n }\n particleSeedsCache.set(key, seeds);\n return seeds;\n}\n\nexport function drawWinningEffects(params: {\n ctx: CanvasRenderingContext2D;\n now: number;\n phase: SpinPhase;\n winFlashStartedAt: number;\n winEffectsEnvelope: number;\n winningCells: CellPosition[];\n boardX: number;\n boardY: number;\n cellW: number;\n cellH: number;\n particlesPerCell: number;\n particleColorMode: 'solid' | 'rainbow';\n particleColorRgb: [number, number, number];\n winningCellBorderRgba: [number, number, number, number];\n getCell: (col: number, row: number) => SymbolId;\n drawSpriteCell: DrawSpriteCell;\n}): void {\n if (params.winningCells.length === 0) return;\n if (params.phase !== 'winFlash' && params.phase !== 'preSpin') return;\n\n const envelope = Math.max(0, Math.min(1, params.winEffectsEnvelope));\n const elapsed = Math.max(0, params.now - params.winFlashStartedAt);\n const pulseProgress = (elapsed % FLOW_WIN_PULSE_PERIOD_MS) / FLOW_WIN_PULSE_PERIOD_MS;\n const pulse = 1 + Math.sin(pulseProgress * Math.PI * 2) * FLOW_WIN_PULSE_AMPLITUDE;\n\n for (const cell of params.winningCells) {\n const x = params.boardX + cell.col * params.cellW;\n const y = params.boardY + cell.row * params.cellH;\n drawWinningCellBorder(\n params.ctx,\n x,\n y,\n params.cellW,\n params.cellH,\n cell.col,\n cell.row,\n params.winningCellBorderRgba,\n elapsed,\n envelope,\n );\n\n const symbol = params.getCell(cell.col, cell.row);\n const scale = 1 + (pulse - 1) * envelope;\n const scaledW = params.cellW * scale;\n const scaledH = params.cellH * scale;\n const offsetX = (params.cellW - scaledW) * 0.5;\n const offsetY = (params.cellH - scaledH) * 0.5;\n\n params.ctx.save();\n params.drawSpriteCell(symbol, x + offsetX, y + offsetY, scaledW, scaledH);\n params.ctx.restore();\n }\n\n if (params.particlesPerCell <= 0) return;\n\n params.ctx.save();\n params.ctx.beginPath();\n params.ctx.rect(params.boardX, params.boardY, params.cellW * GRID_COLS, params.cellH * GRID_ROWS);\n params.ctx.clip();\n\n for (const cell of params.winningCells) {\n const x = params.boardX + cell.col * params.cellW;\n const y = params.boardY + cell.row * params.cellH;\n drawCellParticleBurst({\n ctx: params.ctx,\n cell,\n x,\n y,\n elapsed,\n envelope,\n cellW: params.cellW,\n cellH: params.cellH,\n particlesPerCell: params.particlesPerCell,\n particleColorMode: params.particleColorMode,\n particleColorRgb: params.particleColorRgb,\n });\n }\n\n params.ctx.restore();\n}\n\nfunction drawWinningCellBorder(\n ctx: CanvasRenderingContext2D,\n x: number,\n y: number,\n cellW: number,\n cellH: number,\n cellCol: number,\n cellRow: number,\n color: [number, number, number, number],\n elapsed: number,\n envelope: number,\n): void {\n const inset = Math.max(1, Math.floor(Math.min(cellW, cellH) * 0.04));\n const lineWidth = Math.max(1, Math.floor(Math.min(cellW, cellH) * 0.03));\n const [, , , a] = color;\n\n const localX = (cellCol + 0.5) / GRID_COLS;\n const localY = (cellRow + 0.5) / GRID_ROWS;\n const hue = (elapsed * 0.2 + localX * 80 + localY * 40) % 360;\n\n const minSide = Math.min(cellW, cellH);\n const dashLength = Math.max(4, Math.floor(minSide * 0.04));\n const gapLength = Math.max(3, Math.floor(minSide * 0.03));\n\n ctx.save();\n ctx.lineWidth = lineWidth;\n ctx.strokeStyle = `hsla(${hue}, 90%, 70%, ${a * envelope})`;\n ctx.setLineDash([dashLength, gapLength]);\n ctx.lineDashOffset = -elapsed * 0.03;\n const halfLine = lineWidth * 0.5;\n ctx.strokeRect(\n x + inset + halfLine,\n y + inset + halfLine,\n cellW - inset * 2 - lineWidth,\n cellH - inset * 2 - lineWidth,\n );\n ctx.setLineDash([]);\n ctx.restore();\n}\n\nconst RAINBOW_HUE_BUCKETS = 24;\n\nfunction drawCellParticleBurst(params: {\n ctx: CanvasRenderingContext2D;\n cell: CellPosition;\n x: number;\n y: number;\n elapsed: number;\n envelope: number;\n cellW: number;\n cellH: number;\n particlesPerCell: number;\n particleColorMode: 'solid' | 'rainbow';\n particleColorRgb: [number, number, number];\n}): void {\n const centerX = params.x + params.cellW * 0.5;\n const centerY = params.y + params.cellH * 0.5;\n const maxDistance = Math.min(params.cellW, params.cellH) * 0.72;\n const baseRadius = Math.min(params.cellW, params.cellH) * 0.028;\n const globalFade = 0.96 * params.envelope;\n\n const [r, g, b] = params.particleColorRgb;\n const isSolid = params.particleColorMode === 'solid';\n if (isSolid) {\n params.ctx.fillStyle = `rgb(${r},${g},${b})`;\n }\n\n const seedsList = getParticleSeeds(params.cell.col, params.cell.row);\n\n for (let i = 0; i < params.particlesPerCell; i += 1) {\n const s = seedsList[i];\n const startTime = s.phaseOffset * PARTICLE_FLY_DURATION_MS;\n const age = params.elapsed - startTime;\n if (age < 0) continue;\n const particleT = (age % PARTICLE_FLY_DURATION_MS) / PARTICLE_FLY_DURATION_MS;\n\n const direction = s.seedA * Math.PI * 2;\n const distance = maxDistance * particleT * (0.35 + s.seedB * 0.65);\n const px = centerX + Math.cos(direction) * distance;\n const py = centerY + Math.sin(direction) * distance;\n const twinkle =\n 0.7 + 0.9 * Math.max(0, Math.sin((params.elapsed * 0.012 + s.twinkleSeed * 2) * Math.PI * 2));\n const radius = Math.max(1, baseRadius * (0.55 + s.seedC * 0.6) * (1 - particleT * 0.5));\n const alpha = clamp((0.9 + twinkle * 0.2) * globalFade, 0.9, 1);\n if (alpha <= 0) continue;\n\n if (isSolid) {\n params.ctx.globalAlpha = alpha;\n } else {\n const hueRaw =\n (s.seedA * 360 + params.elapsed * 0.24 + params.cell.col * 38 + params.cell.row * 22) % 360;\n const hue = Math.floor(hueRaw / (360 / RAINBOW_HUE_BUCKETS)) * (360 / RAINBOW_HUE_BUCKETS);\n params.ctx.fillStyle = `hsla(${hue},98%,64%,${alpha})`;\n }\n\n params.ctx.beginPath();\n params.ctx.arc(px, py, radius, 0, Math.PI * 2);\n params.ctx.fill();\n }\n\n params.ctx.globalAlpha = 1;\n}\n","import {\n DEFAULT_SPRITE_ELEMENTS_COUNT,\n FLOW_WIN_FLASH_MS,\n FLOW_WIN_PARTICLES_PER_CELL_BALANCED,\n FLOW_WIN_PARTICLES_PER_CELL_HIGH,\n FLOW_WIN_PARTICLES_PER_CELL_LOW,\n GRID_COLS,\n GRID_ROWS,\n INITIAL_WIN_FLASH_DELAY_MS,\n} from './constants';\nimport {\n createRandomGrid,\n createZeroOffsets,\n fillOffsets,\n findMostFrequentCells,\n} from './core/grid';\nimport { RafLoop } from './core/loop';\nimport { updateOutroOffsets } from './core/outro';\nimport { SpinQueueController } from './core/spinQueue';\nimport {\n beginSpin,\n createRuntimeState,\n destroyState,\n finishSpin,\n startWinFlash,\n} from './core/state';\nimport {\n normalizeInitialSegments,\n normalizeParticleColor,\n normalizeParticleColorMode,\n normalizeQuality,\n normalizeStopGrid,\n normalizeWinningCellBorderColor,\n rowsToStopGrid,\n} from './normalize';\nimport { drawGridLayer, drawWinningEffects } from './render/canvasRenderer';\nimport type { CascadingReelConfig, CellPosition, QualityPreset, SymbolId } from './types';\nimport { normalizeSegment } from './utils/math';\n\nexport class CascadingReel {\n private readonly canvas: HTMLCanvasElement;\n private readonly container: HTMLElement;\n private readonly button?: HTMLButtonElement;\n private readonly ctx: CanvasRenderingContext2D;\n private readonly spinQueueController: SpinQueueController;\n private readonly spriteUrl?: string;\n private readonly spriteElementsCount: number;\n private readonly highlightInitialWinningCells: boolean;\n private readonly particleColorRgb: [number, number, number];\n private readonly particleColorMode: 'solid' | 'rainbow';\n private readonly winningCellBorderRgba: [number, number, number, number];\n private readonly quality: QualityPreset;\n\n private spriteImage: HTMLImageElement | null = null;\n private readonly rafLoop = new RafLoop();\n private readonly runtime = createRuntimeState();\n private dpr = 1;\n private width = 0;\n private height = 0;\n private cellW = 0;\n private cellH = 0;\n private boardX = 0;\n private boardY = 0;\n private scriptedCascadeQueue: number[][][] = [];\n private scriptedOutgoingGrid: SymbolId[][] | null = null;\n private scriptedPendingGrid: SymbolId[][] | null = null;\n private scriptedOutroStartedAt = 0;\n private scriptedOutgoingOffsets: number[][] = createZeroOffsets();\n private scriptedIncomingOffsets: number[][] = createZeroOffsets();\n private winningCells: CellPosition[] = [];\n private grid: SymbolId[][];\n private lastRafTime = 0;\n private particlesPerCell = FLOW_WIN_PARTICLES_PER_CELL_HIGH;\n private initialHighlightRequestedAt = 0;\n private static readonly SWAY_ENVELOPE_TAU_MS = 220;\n private static readonly PRE_SPIN_MS = 150;\n private static readonly WIN_EFFECTS_ENVELOPE_TAU_MS = 120;\n\n public constructor(config: CascadingReelConfig) {\n this.canvas = config.canvas;\n this.container = config.container;\n this.button = config.button;\n this.spriteUrl = config.sprite;\n this.spriteElementsCount = Math.max(\n 1,\n config.spriteElementsCount ?? DEFAULT_SPRITE_ELEMENTS_COUNT,\n );\n this.highlightInitialWinningCells = config.highlightInitialWinningCells !== false;\n this.spinQueueController = new SpinQueueController(config.queuedSpinStates);\n this.particleColorRgb = normalizeParticleColor(config.particleColorRgb);\n this.particleColorMode = normalizeParticleColorMode(config.particleColorMode);\n this.winningCellBorderRgba = normalizeWinningCellBorderColor(config.winningCellBorderRgba);\n this.quality = normalizeQuality(config.quality);\n\n const context = this.canvas.getContext('2d');\n if (!context) {\n throw new Error('2D canvas context is not available');\n }\n this.ctx = context;\n this.grid = config.initialSegments\n ? normalizeInitialSegments(config.initialSegments, this.spriteElementsCount)\n : createRandomGrid(this.spriteElementsCount);\n }\n\n public async init(): Promise<void> {\n this.bindEvents();\n this.resize();\n await this.loadSpriteIfProvided();\n this.applyInitialHighlightIfNeeded();\n this.prewarmWinEffects();\n requestAnimationFrame((warmNow) => {\n this.render(warmNow);\n this.startLoop();\n });\n }\n\n public destroy(): void {\n this.unbindEvents();\n this.rafLoop.stop();\n destroyState(this.runtime);\n this.clearWinningCells();\n }\n\n public spin(): void {\n if (this.runtime.isSpinning) return;\n if (this.runtime.queueFinished) return;\n if (!this.spinQueueController.hasPending()) {\n this.runtime.queueFinished = true;\n if (this.button) this.button.disabled = true;\n return;\n }\n\n const activeSpinState = this.spinQueueController.consume();\n const shouldHighlightCurrentSpin = activeSpinState?.highlightWin === true;\n this.runtime.isSpinning = true;\n this.runtime.phase = 'preSpin';\n this.runtime.preSpinStartedAt = performance.now();\n this.runtime.activeSpinState = activeSpinState;\n this.runtime.shouldHighlightCurrentSpin = shouldHighlightCurrentSpin;\n this.runtime.hasStartedFirstSpin = true;\n\n if (this.button) this.button.disabled = true;\n }\n\n private bindEvents(): void {\n window.addEventListener('resize', this.resize);\n this.button?.addEventListener('click', this.onSpinClick);\n }\n\n private unbindEvents(): void {\n window.removeEventListener('resize', this.resize);\n this.button?.removeEventListener('click', this.onSpinClick);\n }\n\n private readonly onSpinClick = (): void => {\n // Подсветка до клика: в winFlash с подсветкой кнопка включена, по клику завершаем и при наличии очереди запускаем следующий спин\n if (\n this.runtime.phase === 'winFlash' &&\n (this.runtime.shouldHighlightCurrentSpin || !this.runtime.hasStartedFirstSpin)\n ) {\n this.finishSpinWithUi();\n if (this.spinQueueController.hasPending()) this.spin();\n return;\n }\n if (this.runtime.isSpinning) return;\n this.spin();\n };\n\n private getNextGrid(stopGrid?: number[][]): SymbolId[][] {\n if (!stopGrid) return createRandomGrid(this.spriteElementsCount);\n return normalizeStopGrid(stopGrid, this.spriteElementsCount);\n }\n\n private update(now: number): void {\n if (!this.runtime.isSpinning) return;\n\n if (this.runtime.phase === 'preSpin') {\n if (now - this.runtime.preSpinStartedAt < CascadingReel.PRE_SPIN_MS) {\n return;\n }\n beginSpin(this.runtime, {\n activeSpinState: this.runtime.activeSpinState,\n shouldHighlightCurrentSpin: this.runtime.shouldHighlightCurrentSpin,\n startedAt: now,\n });\n this.runtime.preSpinStartedAt = 0;\n\n const scriptedSource = this.runtime.activeSpinState?.finaleSequenceRows\n ? this.runtime.activeSpinState.finaleSequenceRows.map((rows) => rowsToStopGrid(rows))\n : (this.runtime.activeSpinState?.finaleSequence ?? []);\n this.scriptedCascadeQueue = scriptedSource.map((grid) => grid.map((column) => [...column]));\n this.clearWinningCells();\n\n const stopGridSource = this.runtime.activeSpinState?.stopRows\n ? rowsToStopGrid(this.runtime.activeSpinState.stopRows)\n : this.runtime.activeSpinState?.stopGrid;\n const nextGrid = this.getNextGrid(stopGridSource);\n this.startOutroTransition(nextGrid, now);\n }\n\n if (this.runtime.phase === 'outro') {\n this.updateScriptedOutro(now);\n return;\n }\n if (this.runtime.phase === 'winFlash') {\n // Подсветка без ограничения по времени: выход только по клику «Spin»\n if (\n !this.runtime.shouldHighlightCurrentSpin &&\n now - this.runtime.winFlashStartedAt >= FLOW_WIN_FLASH_MS\n ) {\n this.finishSpinWithUi();\n }\n return;\n }\n }\n\n private updateScriptedOutro(now: number): void {\n if (!this.scriptedOutgoingGrid || !this.scriptedPendingGrid) {\n this.finishSpinWithUi();\n return;\n }\n\n const { allOutgoingDone, allIncomingDone } = updateOutroOffsets({\n now,\n height: this.height,\n boardY: this.boardY,\n cellH: this.cellH,\n scriptedOutroStartedAt: this.scriptedOutroStartedAt,\n scriptedOutgoingOffsets: this.scriptedOutgoingOffsets,\n scriptedIncomingOffsets: this.scriptedIncomingOffsets,\n });\n\n if (!allOutgoingDone || !allIncomingDone) return;\n if (!this.scriptedPendingGrid) {\n this.finishSpinWithUi();\n return;\n }\n\n this.grid = this.scriptedPendingGrid;\n this.scriptedOutgoingGrid = null;\n this.scriptedPendingGrid = null;\n this.clearWinningCells();\n fillOffsets(this.scriptedOutgoingOffsets, 0);\n fillOffsets(this.scriptedIncomingOffsets, 0);\n\n if (this.tryStartScriptedCascade(now)) return;\n if (!this.runtime.shouldHighlightCurrentSpin) {\n this.finishSpinWithUi();\n return;\n }\n\n this.winningCells = findMostFrequentCells(this.grid);\n startWinFlash(this.runtime, now);\n // Включаем кнопку только если в очереди есть следующий спин; после последнего — оставляем disabled\n if (\n this.button &&\n this.runtime.shouldHighlightCurrentSpin &&\n this.spinQueueController.hasPending()\n )\n this.button.disabled = false;\n }\n\n private tryStartScriptedCascade(now: number): boolean {\n if (this.scriptedCascadeQueue.length === 0) return false;\n const nextGrid = this.scriptedCascadeQueue.shift();\n if (!nextGrid) return false;\n this.startOutroTransition(normalizeStopGrid(nextGrid, this.spriteElementsCount), now);\n return true;\n }\n\n private startOutroTransition(nextGrid: SymbolId[][], now: number): void {\n this.scriptedOutgoingGrid = this.grid.map((column) => [...column]);\n this.scriptedPendingGrid = nextGrid.map((column) => [...column]);\n fillOffsets(this.scriptedIncomingOffsets, Number.NaN);\n fillOffsets(this.scriptedOutgoingOffsets, 0);\n this.clearWinningCells();\n this.runtime.phase = 'outro';\n this.scriptedOutroStartedAt = now;\n }\n\n private finishSpinWithUi(): void {\n const callback = this.runtime.activeSpinState?.callback;\n finishSpin(this.runtime, this.spinQueueController.hasPending(), performance.now());\n if (this.button) this.button.disabled = this.runtime.queueFinished;\n callback?.();\n }\n\n private applyInitialHighlightIfNeeded(): void {\n if (!this.highlightInitialWinningCells) return;\n this.winningCells = findMostFrequentCells(this.grid);\n this.initialHighlightRequestedAt = performance.now();\n }\n\n private static readonly ZERO_SWAY: [number, number, number] = [0, 0, 0];\n\n private render(now: number): void {\n if (\n this.initialHighlightRequestedAt > 0 &&\n now - this.initialHighlightRequestedAt >= INITIAL_WIN_FLASH_DELAY_MS\n ) {\n startWinFlash(this.runtime, this.initialHighlightRequestedAt);\n this.runtime.winEffectsEnvelope = 1;\n this.initialHighlightRequestedAt = 0;\n if (this.button) this.button.disabled = false;\n }\n\n const swayTarget = this.runtime.phase === 'outro' || this.runtime.phase === 'preSpin' ? 0 : 1;\n const winEffectsTarget =\n this.runtime.phase === 'preSpin' ? 0 : this.runtime.phase === 'winFlash' ? 1 : 0;\n if (this.lastRafTime > 0) {\n const dt = Math.min(now - this.lastRafTime, 50);\n const kSway = 1 - Math.exp(-dt / CascadingReel.SWAY_ENVELOPE_TAU_MS);\n this.runtime.swayEnvelope += (swayTarget - this.runtime.swayEnvelope) * kSway;\n const kWin = 1 - Math.exp(-dt / CascadingReel.WIN_EFFECTS_ENVELOPE_TAU_MS);\n this.runtime.winEffectsEnvelope +=\n (winEffectsTarget - this.runtime.winEffectsEnvelope) * kWin;\n }\n this.lastRafTime = now;\n\n this.ctx.clearRect(0, 0, this.width, this.height);\n if (this.runtime.phase === 'outro' && this.scriptedOutgoingGrid && this.scriptedPendingGrid) {\n this.drawLayer(\n this.scriptedOutgoingGrid,\n this.scriptedOutgoingOffsets,\n CascadingReel.ZERO_SWAY,\n now,\n );\n this.drawLayer(\n this.scriptedPendingGrid,\n this.scriptedIncomingOffsets,\n CascadingReel.ZERO_SWAY,\n now,\n );\n } else {\n this.drawBaseGrid(now);\n }\n\n const winPhase = this.initialHighlightRequestedAt > 0 ? 'winFlash' : this.runtime.phase;\n const winFlashStartedAt =\n this.initialHighlightRequestedAt > 0\n ? this.initialHighlightRequestedAt\n : this.runtime.winFlashStartedAt;\n const winEffectsEnvelope =\n this.initialHighlightRequestedAt > 0 ? 1 : this.runtime.winEffectsEnvelope;\n\n drawWinningEffects({\n ctx: this.ctx,\n now,\n phase: winPhase,\n winFlashStartedAt,\n winEffectsEnvelope,\n winningCells: this.winningCells,\n boardX: this.boardX,\n boardY: this.boardY,\n cellW: this.cellW,\n cellH: this.cellH,\n particlesPerCell:\n this.runtime.phase === 'winFlash' &&\n this.runtime.shouldHighlightCurrentSpin &&\n this.runtime.hasStartedFirstSpin\n ? this.particlesPerCell\n : 0,\n particleColorMode: this.particleColorMode,\n particleColorRgb: this.particleColorRgb,\n winningCellBorderRgba: this.winningCellBorderRgba,\n getCell: this.getCell,\n drawSpriteCell: this.drawSpriteCell,\n });\n }\n\n private drawBaseGrid(now: number): void {\n this.drawLayer(this.grid, null, CascadingReel.ZERO_SWAY, now);\n }\n\n private drawLayer(\n grid: SymbolId[][],\n offsets: number[][] | null,\n columnSway: [number, number, number],\n now: number,\n ): void {\n const skipWinningCells =\n this.runtime.phase === 'winFlash' ||\n this.runtime.phase === 'preSpin' ||\n (this.initialHighlightRequestedAt > 0 && this.winningCells.length > 0);\n\n drawGridLayer({\n ctx: this.ctx,\n phase: this.runtime.phase,\n now,\n swayEnvelope: this.runtime.swayEnvelope,\n grid,\n offsets,\n columnSway,\n boardX: this.boardX,\n boardY: this.boardY,\n cellW: this.cellW,\n cellH: this.cellH,\n width: this.width,\n height: this.height,\n skipWinningCells,\n isWinningCell: this.isWinningCell,\n drawSpriteCell: this.drawSpriteCell,\n });\n }\n\n private readonly getCell = (col: number, row: number): SymbolId => {\n return this.grid[col][row];\n };\n\n private readonly isWinningCell = (col: number, row: number): boolean => {\n return this.winningCells.some((cell) => cell.col === col && cell.row === row);\n };\n\n private drawSpriteCellToCtx(\n ctx: CanvasRenderingContext2D,\n symbolId: SymbolId,\n x: number,\n y: number,\n width: number,\n height: number,\n ): void {\n const image = this.spriteImage;\n if (!image) return;\n\n const segmentIndex = normalizeSegment(symbolId, this.spriteElementsCount);\n const segmentHeight = image.height / this.spriteElementsCount;\n const sourceY = Math.floor(segmentIndex * segmentHeight);\n const sourceHeight = Math.floor(segmentHeight);\n\n ctx.drawImage(image, 0, sourceY, image.width, sourceHeight, x, y, width, height);\n }\n\n private readonly drawSpriteCell = (\n symbolId: SymbolId,\n x: number,\n y: number,\n width: number,\n height: number,\n ): void => {\n this.drawSpriteCellToCtx(this.ctx, symbolId, x, y, width, height);\n };\n\n private clearWinningCells(): void {\n this.winningCells = [];\n }\n\n private readonly resize = (): void => {\n const bounds = this.container.getBoundingClientRect();\n this.dpr = Math.max(1, Math.min(window.devicePixelRatio || 1, 2));\n const side = Math.max(300, Math.floor(bounds.width * this.dpr));\n this.width = side;\n this.height = side;\n const squareSize = Math.floor(Math.min(this.width / GRID_COLS, this.height / GRID_ROWS));\n this.cellW = squareSize;\n this.cellH = squareSize;\n this.boardX = Math.floor((this.width - this.cellW * GRID_COLS) / 2);\n this.boardY = Math.floor((this.height - this.cellH * GRID_ROWS) / 2);\n\n if (this.quality === 'high') this.particlesPerCell = FLOW_WIN_PARTICLES_PER_CELL_HIGH;\n else if (this.quality === 'balanced')\n this.particlesPerCell = FLOW_WIN_PARTICLES_PER_CELL_BALANCED;\n else if (this.quality === 'low') this.particlesPerCell = FLOW_WIN_PARTICLES_PER_CELL_LOW;\n else\n this.particlesPerCell =\n this.dpr >= 2 ? FLOW_WIN_PARTICLES_PER_CELL_BALANCED : FLOW_WIN_PARTICLES_PER_CELL_HIGH;\n\n this.canvas.width = this.width;\n this.canvas.height = this.height;\n };\n\n private async loadSpriteIfProvided(): Promise<void> {\n if (!this.spriteUrl) return;\n const image = new Image();\n image.decoding = 'async';\n image.src = this.spriteUrl;\n try {\n await image.decode();\n this.spriteImage = image;\n } catch {\n this.spriteImage = null;\n }\n }\n\n private prewarmWinEffects(): void {\n if (this.winningCells.length === 0) return;\n\n const offscreen = document.createElement('canvas');\n offscreen.width = this.width;\n offscreen.height = this.height;\n const offscreenCtx = offscreen.getContext('2d');\n if (!offscreenCtx) return;\n\n const savedPhase = this.runtime.phase;\n const savedWinFlashStartedAt = this.runtime.winFlashStartedAt;\n\n this.runtime.phase = 'winFlash';\n this.runtime.winFlashStartedAt = performance.now() - 300;\n\n const prewarmNow = performance.now();\n const particlesPrewarm = Math.min(this.particlesPerCell, FLOW_WIN_PARTICLES_PER_CELL_LOW);\n\n const drawSpriteToOffscreen = (\n symbolId: SymbolId,\n x: number,\n y: number,\n w: number,\n h: number,\n ): void => {\n this.drawSpriteCellToCtx(offscreenCtx, symbolId, x, y, w, h);\n };\n\n for (let frame = 0; frame < 2; frame += 1) {\n drawWinningEffects({\n ctx: offscreenCtx,\n now: prewarmNow + frame * 16,\n phase: 'winFlash',\n winFlashStartedAt: this.runtime.winFlashStartedAt,\n winEffectsEnvelope: 1,\n winningCells: this.winningCells,\n boardX: this.boardX,\n boardY: this.boardY,\n cellW: this.cellW,\n cellH: this.cellH,\n particlesPerCell: particlesPrewarm,\n particleColorMode: this.particleColorMode,\n particleColorRgb: this.particleColorRgb,\n winningCellBorderRgba: this.winningCellBorderRgba,\n getCell: this.getCell,\n drawSpriteCell: drawSpriteToOffscreen,\n });\n }\n\n this.runtime.phase = savedPhase;\n this.runtime.winFlashStartedAt = savedWinFlashStartedAt;\n }\n\n private startLoop(): void {\n if (this.rafLoop.isRunning()) return;\n this.rafLoop.start((time: number): boolean => {\n this.update(time);\n this.render(time);\n return true;\n });\n }\n}\n"],"names":["DEFAULT_SPRITE_ELEMENTS_COUNT","GRID_COLS","GRID_ROWS","FLOW_OUTRO_ROW_GAP_MS","FLOW_ROW_BASE_SPACING_RATIO","FLOW_WIN_FLASH_MS","FLOW_WIN_PULSE_PERIOD_MS","FLOW_WIN_PULSE_AMPLITUDE","PARTICLE_FLY_DURATION_MS","FLOW_WIN_PARTICLES_PER_CELL_HIGH","FLOW_WIN_PARTICLES_PER_CELL_BALANCED","FLOW_WIN_PARTICLES_PER_CELL_LOW","DEFAULT_PARTICLE_COLOR_RGB","DEFAULT_WINNING_CELL_BORDER_RGBA","FLOW_COLUMN_STAGGER_MS","FLOW_FALL_MS","FLOW_OUTRO_OVERLAP_MS","INITIAL_WIN_FLASH_DELAY_MS","clamp","value","min","max","easeOutCubic","randomInt","maxExclusive","normalizeSegment","segment","elementsCount","normalizeRgbChannel","normalizeAlphaChannel","createRandomGrid","spriteElementsCount","grid","col","column","row","findMostFrequentCells","counts","symbol","selectedSymbol","maxCount","count","cells","createZeroOffsets","fillOffsets","offsets","RafLoop","step","now","buildSequentialRowStartDelays","fromRowOffsets","duration","gapMs","delays","nextDelay","baseSpacing","updateOutroOffsets","params","allOutgoingDone","allIncomingDone","outgoingDistance","outgoingOffsetsForOrder","incomingFromOffsets","rowStartDelays","incomingStartShift","columnElapsed","rowElapsed","tOut","easedOut","incomingElapsed","tIn","easedIn","normalizeParticleColor","color","normalizeParticleColorMode","mode","normalizeQuality","preset","normalizeWinningCellBorderColor","rowsToStopGrid","rows","normalizeStopGrid","stopGrid","next","normalizeInitialSegments","initialSegments","cloneSpinState","state","SpinQueueController","initialQueue","entry","createRuntimeState","beginSpin","finishSpin","hasPendingInQueue","startWinFlash","startedAt","destroyState","SWAY_AMPLITUDE_DEG","SWAY_OMEGA","drawGridLayer","sway","swayAmplitudeRad","envelope","skipWinning","x","swayY","offsetY","y","angle","centerX","centerY","hash01","a","b","c","d","particleSeedsCache","getParticleSeeds","key","seeds","i","drawWinningEffects","elapsed","pulseProgress","pulse","cell","drawWinningCellBorder","scale","scaledW","scaledH","offsetX","drawCellParticleBurst","ctx","cellW","cellH","cellCol","cellRow","inset","lineWidth","localX","localY","hue","minSide","dashLength","gapLength","halfLine","RAINBOW_HUE_BUCKETS","maxDistance","baseRadius","globalFade","g","isSolid","seedsList","s","startTime","age","particleT","direction","distance","px","py","twinkle","radius","alpha","hueRaw","CascadingReel","config","context","warmNow","activeSpinState","shouldHighlightCurrentSpin","scriptedSource","stopGridSource","nextGrid","callback","swayTarget","winEffectsTarget","dt","kSway","kWin","winPhase","winFlashStartedAt","winEffectsEnvelope","columnSway","skipWinningCells","symbolId","width","height","image","segmentIndex","segmentHeight","sourceY","sourceHeight","bounds","side","squareSize","offscreen","offscreenCtx","savedPhase","savedWinFlashStartedAt","prewarmNow","particlesPrewarm","drawSpriteToOffscreen","w","h","frame","time"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -88,6 +88,8 @@ export declare type SpinState = {
|
|
|
88
88
|
stopRows?: number[][];
|
|
89
89
|
finaleSequence?: number[][][];
|
|
90
90
|
finaleSequenceRows?: number[][][];
|
|
91
|
+
/** When true, show win highlight after this spin. When false, never. When undefined, show only if this is the last spin in the queue. */
|
|
92
|
+
highlightWin?: boolean;
|
|
91
93
|
callback?: () => void;
|
|
92
94
|
};
|
|
93
95
|
|
package/dist/index.es.js
CHANGED
|
@@ -4,7 +4,7 @@ const Y = [255, 235, 110], B = [
|
|
|
4
4
|
255,
|
|
5
5
|
0.78
|
|
6
6
|
], k = 190, I = 650, z = 220, X = 200;
|
|
7
|
-
function
|
|
7
|
+
function w(t, i, e) {
|
|
8
8
|
return t < i ? i : t > e ? e : t;
|
|
9
9
|
}
|
|
10
10
|
function m(t) {
|
|
@@ -17,10 +17,10 @@ function W(t, i) {
|
|
|
17
17
|
return (t % i + i) % i;
|
|
18
18
|
}
|
|
19
19
|
function R(t) {
|
|
20
|
-
return
|
|
20
|
+
return w(Math.round(t), 0, 255);
|
|
21
21
|
}
|
|
22
22
|
function Q(t) {
|
|
23
|
-
return
|
|
23
|
+
return w(t, 0, 1);
|
|
24
24
|
}
|
|
25
25
|
function F(t) {
|
|
26
26
|
const i = [];
|
|
@@ -48,7 +48,7 @@ function G(t) {
|
|
|
48
48
|
t[s][r] === e && l.push({ col: s, row: r });
|
|
49
49
|
return l;
|
|
50
50
|
}
|
|
51
|
-
function
|
|
51
|
+
function H() {
|
|
52
52
|
return Array.from({ length: 3 }, () => Array.from({ length: 3 }, () => 0));
|
|
53
53
|
}
|
|
54
54
|
function L(t, i) {
|
|
@@ -117,14 +117,14 @@ function K(t) {
|
|
|
117
117
|
t.scriptedOutgoingOffsets[h][c] = 0, t.scriptedIncomingOffsets[h][c] = Number.NaN, i = !1, e = !1;
|
|
118
118
|
continue;
|
|
119
119
|
}
|
|
120
|
-
const f =
|
|
120
|
+
const f = w(d / I, 0, 1), g = m(f);
|
|
121
121
|
t.scriptedOutgoingOffsets[h][c] = l * g, f < 1 && (i = !1);
|
|
122
122
|
const p = d - o;
|
|
123
123
|
if (p <= 0) {
|
|
124
124
|
t.scriptedIncomingOffsets[h][c] = Number.NaN, e = !1;
|
|
125
125
|
continue;
|
|
126
126
|
}
|
|
127
|
-
const S =
|
|
127
|
+
const S = w(p / I, 0, 1), _ = m(S);
|
|
128
128
|
t.scriptedIncomingOffsets[h][c] = r[c] * (1 - _), S < 1 && (e = !1);
|
|
129
129
|
}
|
|
130
130
|
}
|
|
@@ -151,7 +151,7 @@ function it(t) {
|
|
|
151
151
|
Q(t[3])
|
|
152
152
|
] : B;
|
|
153
153
|
}
|
|
154
|
-
function
|
|
154
|
+
function P(t) {
|
|
155
155
|
if (t.length !== 3)
|
|
156
156
|
throw new Error("rows must contain 3 rows");
|
|
157
157
|
for (let i = 0; i < 3; i += 1)
|
|
@@ -180,7 +180,7 @@ function M(t, i) {
|
|
|
180
180
|
return e;
|
|
181
181
|
}
|
|
182
182
|
function et(t, i) {
|
|
183
|
-
return M(
|
|
183
|
+
return M(P(t), i);
|
|
184
184
|
}
|
|
185
185
|
function nt(t) {
|
|
186
186
|
return {
|
|
@@ -188,6 +188,7 @@ function nt(t) {
|
|
|
188
188
|
stopRows: t.stopRows?.map((i) => [...i]),
|
|
189
189
|
finaleSequence: t.finaleSequence?.map((i) => i.map((e) => [...e])),
|
|
190
190
|
finaleSequenceRows: t.finaleSequenceRows?.map((i) => i.map((e) => [...e])),
|
|
191
|
+
highlightWin: t.highlightWin,
|
|
191
192
|
callback: t.callback
|
|
192
193
|
};
|
|
193
194
|
}
|
|
@@ -225,7 +226,7 @@ function rt(t, i) {
|
|
|
225
226
|
function ot(t, i, e) {
|
|
226
227
|
t.phase = "idle", t.idleStartedAt = e, t.isSpinning = !1, t.shouldHighlightCurrentSpin = !1, t.queueFinished = !i, t.activeSpinState = null;
|
|
227
228
|
}
|
|
228
|
-
function
|
|
229
|
+
function D(t, i) {
|
|
229
230
|
t.winFlashStartedAt = i, t.phase = "winFlash";
|
|
230
231
|
}
|
|
231
232
|
function ht(t) {
|
|
@@ -310,17 +311,17 @@ function T(t) {
|
|
|
310
311
|
}
|
|
311
312
|
}
|
|
312
313
|
function ft(t, i, e, n, l, s, r, u, o, h) {
|
|
313
|
-
const a = Math.max(1, Math.floor(Math.min(n, l) * 0.04)), c = Math.max(1, Math.floor(Math.min(n, l) * 0.
|
|
314
|
+
const a = Math.max(1, Math.floor(Math.min(n, l) * 0.04)), c = Math.max(1, Math.floor(Math.min(n, l) * 0.03)), [, , , d] = u, f = (s + 0.5) / 3, g = (r + 0.5) / 3, p = (o * 0.2 + f * 80 + g * 40) % 360, S = Math.min(n, l), _ = Math.max(4, Math.floor(S * 0.04)), A = Math.max(3, Math.floor(S * 0.03));
|
|
314
315
|
t.save(), t.lineWidth = c, t.strokeStyle = `hsla(${p}, 90%, 70%, ${d * h})`, t.setLineDash([_, A]), t.lineDashOffset = -o * 0.03;
|
|
315
|
-
const
|
|
316
|
+
const E = c * 0.5;
|
|
316
317
|
t.strokeRect(
|
|
317
|
-
i + a +
|
|
318
|
-
e + a +
|
|
318
|
+
i + a + E,
|
|
319
|
+
e + a + E,
|
|
319
320
|
n - a * 2 - c,
|
|
320
321
|
l - a * 2 - c
|
|
321
322
|
), t.setLineDash([]), t.restore();
|
|
322
323
|
}
|
|
323
|
-
const
|
|
324
|
+
const N = 24;
|
|
324
325
|
function gt(t) {
|
|
325
326
|
const i = t.x + t.cellW * 0.5, e = t.y + t.cellH * 0.5, n = Math.min(t.cellW, t.cellH) * 0.72, l = Math.min(t.cellW, t.cellH) * 0.028, s = 0.96 * t.envelope, [r, u, o] = t.particleColorRgb, h = t.particleColorMode === "solid";
|
|
326
327
|
h && (t.ctx.fillStyle = `rgb(${r},${u},${o})`);
|
|
@@ -328,15 +329,15 @@ function gt(t) {
|
|
|
328
329
|
for (let c = 0; c < t.particlesPerCell; c += 1) {
|
|
329
330
|
const d = a[c], f = d.phaseOffset * 720, g = t.elapsed - f;
|
|
330
331
|
if (g < 0) continue;
|
|
331
|
-
const p = g % 720 / 720, S = d.seedA * Math.PI * 2, _ = n * p * (0.35 + d.seedB * 0.65), A = i + Math.cos(S) * _,
|
|
332
|
-
if (!(
|
|
332
|
+
const p = g % 720 / 720, S = d.seedA * Math.PI * 2, _ = n * p * (0.35 + d.seedB * 0.65), A = i + Math.cos(S) * _, E = e + Math.sin(S) * _, v = 0.7 + 0.9 * Math.max(0, Math.sin((t.elapsed * 0.012 + d.twinkleSeed * 2) * Math.PI * 2)), y = Math.max(1, l * (0.55 + d.seedC * 0.6) * (1 - p * 0.5)), b = w((0.9 + v * 0.2) * s, 0.9, 1);
|
|
333
|
+
if (!(b <= 0)) {
|
|
333
334
|
if (h)
|
|
334
|
-
t.ctx.globalAlpha =
|
|
335
|
+
t.ctx.globalAlpha = b;
|
|
335
336
|
else {
|
|
336
|
-
const q = (d.seedA * 360 + t.elapsed * 0.24 + t.cell.col * 38 + t.cell.row * 22) % 360, U = Math.floor(q / (360 /
|
|
337
|
-
t.ctx.fillStyle = `hsla(${U},98%,64%,${
|
|
337
|
+
const q = (d.seedA * 360 + t.elapsed * 0.24 + t.cell.col * 38 + t.cell.row * 22) % 360, U = Math.floor(q / (360 / N)) * (360 / N);
|
|
338
|
+
t.ctx.fillStyle = `hsla(${U},98%,64%,${b})`;
|
|
338
339
|
}
|
|
339
|
-
t.ctx.beginPath(), t.ctx.arc(A,
|
|
340
|
+
t.ctx.beginPath(), t.ctx.arc(A, E, y, 0, Math.PI * 2), t.ctx.fill();
|
|
340
341
|
}
|
|
341
342
|
}
|
|
342
343
|
t.ctx.globalAlpha = 1;
|
|
@@ -368,8 +369,8 @@ class C {
|
|
|
368
369
|
scriptedOutgoingGrid = null;
|
|
369
370
|
scriptedPendingGrid = null;
|
|
370
371
|
scriptedOutroStartedAt = 0;
|
|
371
|
-
scriptedOutgoingOffsets =
|
|
372
|
-
scriptedIncomingOffsets =
|
|
372
|
+
scriptedOutgoingOffsets = H();
|
|
373
|
+
scriptedIncomingOffsets = H();
|
|
373
374
|
winningCells = [];
|
|
374
375
|
grid;
|
|
375
376
|
lastRafTime = 0;
|
|
@@ -402,7 +403,7 @@ class C {
|
|
|
402
403
|
this.runtime.queueFinished = !0, this.button && (this.button.disabled = !0);
|
|
403
404
|
return;
|
|
404
405
|
}
|
|
405
|
-
const i = this.spinQueueController.consume(), e = !
|
|
406
|
+
const i = this.spinQueueController.consume(), e = i?.highlightWin === !0;
|
|
406
407
|
this.runtime.isSpinning = !0, this.runtime.phase = "preSpin", this.runtime.preSpinStartedAt = performance.now(), this.runtime.activeSpinState = i, this.runtime.shouldHighlightCurrentSpin = e, this.runtime.hasStartedFirstSpin = !0, this.button && (this.button.disabled = !0);
|
|
407
408
|
}
|
|
408
409
|
bindEvents() {
|
|
@@ -412,6 +413,10 @@ class C {
|
|
|
412
413
|
window.removeEventListener("resize", this.resize), this.button?.removeEventListener("click", this.onSpinClick);
|
|
413
414
|
}
|
|
414
415
|
onSpinClick = () => {
|
|
416
|
+
if (this.runtime.phase === "winFlash" && (this.runtime.shouldHighlightCurrentSpin || !this.runtime.hasStartedFirstSpin)) {
|
|
417
|
+
this.finishSpinWithUi(), this.spinQueueController.hasPending() && this.spin();
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
415
420
|
this.runtime.isSpinning || this.spin();
|
|
416
421
|
};
|
|
417
422
|
getNextGrid(i) {
|
|
@@ -427,16 +432,19 @@ class C {
|
|
|
427
432
|
shouldHighlightCurrentSpin: this.runtime.shouldHighlightCurrentSpin,
|
|
428
433
|
startedAt: i
|
|
429
434
|
}), this.runtime.preSpinStartedAt = 0;
|
|
430
|
-
const e = this.runtime.activeSpinState?.finaleSequenceRows ? this.runtime.activeSpinState.finaleSequenceRows.map((s) =>
|
|
435
|
+
const e = this.runtime.activeSpinState?.finaleSequenceRows ? this.runtime.activeSpinState.finaleSequenceRows.map((s) => P(s)) : this.runtime.activeSpinState?.finaleSequence ?? [];
|
|
431
436
|
this.scriptedCascadeQueue = e.map((s) => s.map((r) => [...r])), this.clearWinningCells();
|
|
432
|
-
const n = this.runtime.activeSpinState?.stopRows ?
|
|
437
|
+
const n = this.runtime.activeSpinState?.stopRows ? P(this.runtime.activeSpinState.stopRows) : this.runtime.activeSpinState?.stopGrid, l = this.getNextGrid(n);
|
|
433
438
|
this.startOutroTransition(l, i);
|
|
434
439
|
}
|
|
435
440
|
if (this.runtime.phase === "outro") {
|
|
436
441
|
this.updateScriptedOutro(i);
|
|
437
442
|
return;
|
|
438
443
|
}
|
|
439
|
-
this.runtime.phase
|
|
444
|
+
if (this.runtime.phase === "winFlash") {
|
|
445
|
+
!this.runtime.shouldHighlightCurrentSpin && i - this.runtime.winFlashStartedAt >= 5e3 && this.finishSpinWithUi();
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
440
448
|
}
|
|
441
449
|
}
|
|
442
450
|
updateScriptedOutro(i) {
|
|
@@ -463,7 +471,7 @@ class C {
|
|
|
463
471
|
this.finishSpinWithUi();
|
|
464
472
|
return;
|
|
465
473
|
}
|
|
466
|
-
this.winningCells = G(this.grid),
|
|
474
|
+
this.winningCells = G(this.grid), D(this.runtime, i), this.button && this.runtime.shouldHighlightCurrentSpin && this.spinQueueController.hasPending() && (this.button.disabled = !1);
|
|
467
475
|
}
|
|
468
476
|
}
|
|
469
477
|
}
|
|
@@ -484,7 +492,7 @@ class C {
|
|
|
484
492
|
}
|
|
485
493
|
static ZERO_SWAY = [0, 0, 0];
|
|
486
494
|
render(i) {
|
|
487
|
-
this.initialHighlightRequestedAt > 0 && i - this.initialHighlightRequestedAt >= X && (
|
|
495
|
+
this.initialHighlightRequestedAt > 0 && i - this.initialHighlightRequestedAt >= X && (D(this.runtime, this.initialHighlightRequestedAt), this.runtime.winEffectsEnvelope = 1, this.initialHighlightRequestedAt = 0, this.button && (this.button.disabled = !1));
|
|
488
496
|
const e = this.runtime.phase === "outro" || this.runtime.phase === "preSpin" ? 0 : 1, n = this.runtime.phase === "preSpin" ? 0 : this.runtime.phase === "winFlash" ? 1 : 0;
|
|
489
497
|
if (this.lastRafTime > 0) {
|
|
490
498
|
const u = Math.min(i - this.lastRafTime, 50), o = 1 - Math.exp(-u / C.SWAY_ENVELOPE_TAU_MS);
|