canvasでのeasing関数のまとめ。
javascriptのカードゲームで使いたくてなかなかまとまったものがなかったので作成しました。
modeとtypeでeasingのさせ方を切り替えられるようになってます
easing.js
//************************************************* */
//線形補間基本関数
function lerp(a,b,t) {
if(b==a) return a;
return a+t*(b-a);
}
//************************************************* */
//easing関数配列
function easing(mode,type,x){
//mode: in out in_out
//type: linear quad cubic quart quint sine expo circ elastic back bounce
//関数配列
let EaseModeType = new Array();
//以下easing関数をあらかじめ配列登録
EaseModeType.in_linear=function(x){
return x;
}
EaseModeType.out_linear=function(x){
return x;
}
EaseModeType.in_out_linear=function(x){
return x;
}
EaseModeType.in_quad=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
return c*(t/=d)*t+b;
}
EaseModeType.out_quad=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
return -c*(t/=d)*(t-2)+b;
}
EaseModeType.in_out_quad=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
if((t/=d/2)<1) return c/2*t*t+b;
return -c/2*((--t)*(t-2)-1)+b;
}
EaseModeType.in_cubic=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
return c*(t/=d)*t*t+b;
}
EaseModeType.out_cubic=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
return c*((t=t/d-1)*t*t+1)+b;
}
EaseModeType.in_out_cubic=function(x){
let t = x;
let b = 0;
let c = 1;
let d = 1;
if ((t/=d/2)<1) return c/2*t*t*t+b;
return c/2*((t-=2)*t*t+2)+b;
}
EaseModeType.in_quart=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
return c*(t/=d)*t*t*t+b;
}
EaseModeType.out_quart=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
return -c*((t=t/d-1)*t*t*t-1)+b;
}
EaseModeType.in_out_quart=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
if((t/=d/2)<1) return c/2*t*t*t*t+b;
return -c/2*((t-=2)*t*t*t-2)+b;
}
EaseModeType.in_quint=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
return c*(t/=d)*t*t*t*t+b;
}
EaseModeType.out_quint=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
return c*((t=t/d-1)*t*t*t*t+1)+b;
}
EaseModeType.in_out_quint=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
if ((t/=d/2)<1) return c/2*t*t*t*t*t+b;
return c/2*((t-=2)*t*t*t*t+2)+b;
}
EaseModeType.in_sine=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
return -c*Math.cos(t/d*(Math.PI/2))+c+b;
}
EaseModeType.out_sine=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
return c*Math.sin(t/d*(Math.PI/2))+b;
}
EaseModeType.in_out_sine=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
return -c/2*(Math.cos(Math.PI*t/d)-1)+b;
}
EaseModeType.in_expo=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
return (t==0) ? b : c*Math.pow(2,10*(t/d-1))+b;
}
EaseModeType.out_expo=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
return (t==d) ? b+c : c*(-Math.pow(2,-10*t/d)+1)+b;
}
EaseModeType.in_out_expo=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
if(t==0) return b;
if(t==d) return b+c;
if((t/=d/2)<1) return c/2*Math.pow(2,10*(t-1))+b;
return c/2*(-Math.pow(2,-10*--t)+2)+b;
}
EaseModeType.in_circ=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
return -c*(Math.sqrt(1-(t/=d)*t)-1)+b;
}
EaseModeType.out_circ=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
return c*Math.sqrt(1-(t=t/d-1)*t)+b;
}
EaseModeType.in_out_circ=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
if((t/=d/2)<1) return -c/2*(Math.sqrt(1-t*t)-1)+b;
return c/2*(Math.sqrt(1-(t-=2)*t)+1)+b;
}
EaseModeType.in_elastic=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
let s=1.70158;
let p=0;
let a=c;
if(t==0) return b;
if((t/=d)==1) return b+c;
if(p==0) p=d*.3;
if(a<Math.abs(c)) { a=c; s=p/4; }
else s=p/(2*Math.PI)*Math.asin(c/a);
return -(a*Math.pow(2,10*(t-=1))*Math.sin((t*d-s)*(2*Math.PI)/p ))+b;
}
EaseModeType.out_elastic=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
let s=1.70158;
let p=0;
let a=c;
if(t==0) return b;
if((t/=d)==1) return b+c;
if(p==0) p=d*.3;
if(a<Math.abs(c)) { a=c; s=p/4; }
else s=p/(2*Math.PI)*Math.asin(c/a);
return a*Math.pow(2,-10*t)*Math.sin((t*d-s)*(2*Math.PI)/p)+c+b;
}
EaseModeType.in_out_elastic=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
let s=1.70158;
let p=0;
let a=c;
if (t==0) return b;
if((t/=d/2)==2) return b+c;
if(p==0) p=d*(.3*1.5);
if(a<Math.abs(c)) {a=c; s=p/4;}
else s=p/(2*Math.PI)*Math.asin(c/a);
if(t<1) return -.5*(a*Math.pow(2,10*(t-=1))*Math.sin((t*d-s)*(2*Math.PI)/p))+b;
return a*Math.pow(2,-10*(t-=1))*Math.sin((t*d-s)*(2*Math.PI)/p)*.5+c+b;
}
EaseModeType.in_back=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
let s=1.70158;
return c*(t/=d)*t*((s+1)*t-s)+b;
}
EaseModeType.out_back=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
let s=1.70158;
return c*((t=t/d-1)*t*((s+1)*t+s)+1)+b;
}
EaseModeType.in_out_back=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
let s=1.70158;
if ((t/=d/2)<1) return c/2*(t*t*(((s*=(1.525))+1)*t-s))+b;
return c/2*((t-=2)*t*(((s*=(1.525))+1)*t+s)+2)+b;
}
EaseModeType.out_bounce=function(x){
const n1=7.5625;
const d1=2.75;
if (x<1/d1) {
return n1*x*x;
}else if(x<2/d1){
return n1*(x-=1.5/d1)*x+0.75;
}else if(x<2.5/d1){
return n1*(x-=2.25/d1)*x+0.9375;
}else{
return n1*(x-=2.625/d1)*x+0.984375;
}
}
EaseModeType.in_bounce=function(x){
return 1-EaseModeType.out_bounce(1-x);
}
EaseModeType.in_out_bounce=function(x){
if(x<1/2) return (1-EaseModeType.out_bounce(1-2*x))/2;
return (1+EaseModeType.out_bounce(2*x-1))/2;
}
//関数名変数作成
let FN=mode + "_" + type;
return EaseModeType[FN](x);
}
使い方の例
呼び出しの際に、easingで算出した時間引数をlerpに渡して座標を算出する
main.js
function UpdateCardCoord(){
//1枚ずつでよいのでループは無し。リスト先頭を対象とする
let TIMECurrent=Date.now();
let m=MvList[0];
if(m.st==0){
m.st=1;
TIMEStart=Date.now();
}
if(TIMEStart+m.timespan>TIMECurrent){
//経過時間に応じた座標に移動
let t=(TIMECurrent-TIMEStart)/m.timespan;
t=easing(EaseMode,EaseType,t);
C[m.adr].x=lerp(m.x1,m.x2,t);
C[m.adr].y=lerp(m.y1,m.y2,t);
}else{
//移動終了。念のため最終位置で更新しておく
C[m.adr].x=m.x2;
C[m.adr].y=m.y2;
//移動中カードリストから削除し次の移動中カードに進める
DelMvList(m.adr);
}
}
実際に作ったサンプルプログラム(シーヘブンタワーてゆうソリティアっぽいゲームです)
main.js
"use strict";
window.onload = function(){
//Canvas def
let CVS=document.querySelector('#canvas');
let CTX=CVS.getContext('2d');
let BB=CVS.getBoundingClientRect();
let OffsetX=BB.left;
let OffsetY=BB.top;
//Rects on deck
let BR=[]; //Box Rect
let FR=[]; //Free Rect
let TR=[]; //Tower Rect
let BTNR=[]; //Button Rect
let DAFR=[]; //DropAeaFrameRect
//max value
let BMax=4; //BoxMax
let FMax=4; //FreeMax
let TCMax=10; //Tower列最大値
let TRMax=13; //Tower行最大値
let AMax=3; //deck rect最大値 area0:tower area1:free area2:box
let BTNMax=3; //ボタン数最大値 new reset undo
//layout
let XMRG=10; //X座標マージン
let YMRG=10; //Y座標マージン
let CMRG=5; //カード間マージン
let BMRG=20; //ボタンエリア高さマージン
let BW=60; //ボタン幅
let BH=16; //ボタン高
let OVR=1/2; //Tower行配置重なり割合
//colors
let Clightgray="rgb(255,255,255)";
//let Clightgray="rgb(228,228,228)";
let Cgray ="rgb(222,222,222)";
let Cblack ="rgb(0,0,0)";
let Cwhite ="rgb(255,255,255)";
let Cshadow ="rgb(0,0,0,0.2)";
//cards and lists
let C=[]; //カード情報配列
let CMax=52; //カード枚数最大値
let CW=25; //表示サイズカード幅
let CH=40; //表示サイズカード高
let MvList=[]; //移動カードリスト
let DrgList=[]; //ドラッグ中カードアドレスリスト
let CC= ["rgb(255,255,255)","rgb(221,235,247)","rgb(255,242,204)","rgb(198,224,180)"];
let TC= ["rgb(255,0,0)","rgb(0,0,0)","rgb(255,0,0)","rgb(0,0,0)"];
let TCT=["rgb(255,0,0,0.3)","rgb(0,0,0,0.3)","rgb(255,0,0,0.3)","rgb(0,0,0,0.3)"];
let MK= ["♥","♠","♦","♣"]; //カードマーク
//Easing def
let TSDR=50; //ドロップ時ticks
let TSSF=50; //カード配布時ticks
let TSMV=200; //指定移動時ticks
let TSAM=100; //終了自動移動時ticks
let EaseType="sine"; //linear quad cubic quart quint sine expo circ elastic back bounce
let EaseMode="out"; //in out in_out
let TIMEStart;
let TIMECurrent;
//mouse events
let StartX; //ドラッグ中元位置X座標
let StartY; //ドラッグ中元位置Y座標
let DTime; //マウスダウン時刻
let UTime; //マウスアップ時刻
let MousePress=0; //マウスステータス 0:何もない 1:MousePress(左ボタン)
//other
let SVDATA=[]; //Undo/Restart用保存用データ
let STP=0; //手数
let STIME; //開始時刻
let ETIME; //現在時刻
//event listner
window.addEventListener("mousedown" ,myMouseDown);
window.addEventListener("mouseup" ,myMouseUp);
window.addEventListener("mousemove" ,myMouseMove);
main("New"); //新規モードで開始
//************************************************* */
//メイン処理
//cm 0:新規 1:restart 2:undo
//戻り値:無し
function main(btn){
switch(btn){
case "New":
InitGame();
DefDeck();
DefCard();
break;
case "Reset":
LoadGame(1);
break;
case "Undo":
LoadGame(2);
break;
default:
console.log("起動モードエラー");
break;
}
window.requestAnimationFrame(Update);
}
//****************************************************
//ゲーム初期化
//戻り値:無し
function InitGame(){
STP=0;
SVDATA=[];
STIME=new Date();
}
//************************************************* */
//デッキ上各矩形定義
//戻り値:無し
function DefDeck(){
//デッキ上の各Rectを定義
//Box定義
for(let i=0;i<2;i++){
BR[i]=({x:XMRG+i*(CW+CMRG),y:BMRG+YMRG});
}
for(let i=2;i<BMax;i++){
BR[i]=({x:XMRG+(i+6)*(CW+CMRG),y:BMRG+YMRG});
}
//Free定義
for(let i=0;i<FMax;i++){
FR[i]=({x:XMRG+(i+3)*(CW+CMRG),y:BMRG+YMRG});
}
//Tower定義
for(let i=0;i<TCMax;i++){
TR[i]=[];
for(let j=0;j<TRMax;j++){
TR[i][j]=({x:XMRG+i*(CW+CMRG),y:BMRG+YMRG+CH+YMRG+j*CH*(1-OVR)});
}
}
//Button定義
let cap=["New","Reset","Undo"];
for(let i=0;i<BTNMax;i++){
BTNR[i]=({x:XMRG+i*(BW+CMRG),y:YMRG,cap:cap[i],sel:false});
}
}
//************************************************* */
//カード定義
//戻り値:無し
function DefCard(){
C=new Array(CMax);
let Rnd=GetShuffledArray(0,CMax-1);
for(let i=0;i<C.length;i++){
let mrk=Math.trunc(Rnd[i]/13);
let num=Rnd[i]%13+1;
let col=i%10;
let row=Math.trunc(i/10);
C[i]=({ adr:i,
mrk:mrk,num:num,
col:col,row:row,
x:BR[mrk].x,y:BR[mrk].y,
isFin:false,
isDrg:0, //0:静止 1:ドラッグ中
isMov:0, //0:静止 1:移動中もしくは移動待ち
inArea:0 //0:tower 1:free 2:box
});
//カード初期配布のアニメーションのために移動カードリストに登録
AddMvList(C[i].adr,0,TSSF,TR[col][row].x,TR[col][row].y);
}
}
//****************************************************
//ゲームのロード
//戻り値:無し
function LoadGame(x){
let DN=0; //Data Number
if(x==1){ //Restart
DN=0;
}else{ //Undo
DN=SVDATA.length-2;
if(DN<0){
DN=0;
}
}
//SVDATAをCardに反映
C=new Array(CMax);
for(let i=0;i<C.length;i++){
C[i]=JSON.parse(JSON.stringify(SVDATA[DN][i]));
}
//SVDATA不要配列削除
let DND=SVDATA.length-1;
if(x==0){
SVDATA.splice(1,SVDATA.length-1); //restart
}else{
SVDATA.pop(); //undo
}
}
//****************************************************
//ゲームの状態保存(undo、reset用)
//戻り値:無し
function SaveGame(){
let swChange=false;
if(SVDATA.length>0){
let x=SVDATA.length-1;
for(let i=0;i<C.length;i++){
let SV=SVDATA[x];
//最新のセーブデータと現在のカード配置を比較し変更がないかを確認し変更がある場合、セーブ対象
if( C[i].col!=SV[i].col || C[i].row!=SV[i].row ||
C[i].isFin!=SV[i].isFin || C[i].inArea!=SV[i].inArea ){
swChange=true;
break;
}
}
}else{
swChange=true; //まだセーブデータがない場合、無条件にセーブ対象
}
if(swChange){
//現在のカード状態をそのまま保存
let SV=new Array(CMax);
for(let i=0;i<C.length;i++){
SV[i]=JSON.parse(JSON.stringify(C[i]));
}
SVDATA.push(SV);
STP=STP+1;
}
}
//************************************************* */
//画面更新処理ループ
//戻り値:無し
function Update(callback){
if(MvList.length>0){
UpdateCardCoord();
}else{
while(ChkAutoMove());
//自動移動可能なカードがなくなったらゲーム保存
if(MvList.length==0){
SaveGame();
}
}
ClearCanvas(); //キャンバスクリア
DrawBtn(); //ボタン描画
DrawDeck(); //デッキ描画
DrawCards(); //カード描画
//ゲーム完了チェック
if(ChkEnd()){
EndGame();
window.cancelAnimationFrame(Update);
}else{
window.requestAnimationFrame(Update);
}
}
//************************************************* */
//移動カードの座標を更新する
//戻り値:無し
function UpdateCardCoord(){
//1枚ずつでよいのでループは無し。リスト先頭を対象とする
let TIMECurrent=Date.now();
let m=MvList[0];
if(m.st==0){
m.st=1;
TIMEStart=Date.now();
}
if(TIMEStart+m.timespan>TIMECurrent){
//経過時間に応じた座標に移動
let t=(TIMECurrent-TIMEStart)/m.timespan;
t=easing(EaseMode,EaseType,t);
C[m.adr].x=lerp(m.x1,m.x2,t);
C[m.adr].y=lerp(m.y1,m.y2,t);
}else{
//移動終了。念のため最終位置で更新しておく
C[m.adr].x=m.x2;
C[m.adr].y=m.y2;
//移動中カードリストから削除し次の移動中カードに進める
DelMvList(m.adr);
}
}
//************************************************* */
//自動移動できるカードの存在チェック
//戻り値:自動移動できる場合、移動対象リストに追加してtrue 移動可能カードがない場合falseを返す
function ChkAutoMove() {
//各マークごとの終了番号リストを作成
let num=[0,0,0,0];
for(let i=0;i<BMax;i++){
let c1=C.slice();
c1=c1.filter(function(x){
return x.inArea==2 && x.isFin==true && x.mrk==i;
});
if(c1.length>0){
c1.sort((a,b)=> b.num-a.num);
num[i]=c1[0].num;
}else{
num[i]=0;
}
}
let m;
let swAutoMove=false;
if(!swAutoMove){
//Free上カードのチェック
let c1=C.slice();
c1=c1.filter(function(x){
return x.inArea==1 && x.isFin==false;
});
for(let i=0;i<c1.length;i++){
let j=c1[i].adr;
if(C[j].num==num[C[j].mrk]+1){
m=j;
swAutoMove=true;
break;
}
}
}
if(!swAutoMove){
//Tower上カードのチェック
for(let i=0;i<TCMax;i++){
let c1=C.slice();
c1=c1.filter(function(x){
return x.inArea==0 && x.col==i;
});
if(c1.length>0){
//各Tower末端カードをチェック
c1.sort((a,b)=> b.row-a.row);
let j=c1[0].adr;
if(C[j].num==num[C[j].mrk]+1){
m=j;
swAutoMove=true;
break;
}
}
}
}
//自動移動可能カードがあるなら、カード情報を更新
if(swAutoMove){
C[m].inArea=2;
C[m].isFin=true;
C[m].isDrg=0;
C[m].isMov=0;
AddMvList(m,0,TSAM,BR[C[m].mrk].x, BR[C[m].mrk].y);
}
return swAutoMove;
}
//**********************************************************
//キャンバスのクリア
//戻り値:無
function ClearCanvas(){
// canvasクリア
CTX.clearRect(0, 0, CVS.width, CVS.height);
fillrect(0,0,CVS.width,CVS.height,Clightgray);
}
//**********************************************************
//デッキの描画
//戻り値:無
function DrawDeck(){
//Box描画
for(let i=0;i<BR.length;i++){
rect(BR[i].x,BR[i].y,CW,CH,Cgray);
}
//Free描画
for(let i=0;i<FR.length;i++){
//rect(FR[i].x,FR[i].y,CW,CH,Cgray);
fillrect(FR[i].x,FR[i].y,CW,CH,Cgray);
}
//Tower描画
for(let i=0;i<TR.length;i++){
let color;
for(let j=0;j<TR[i].length;j++){
rect(TR[i][j].x,TR[i][j].y,CW,CH,Cgray);
}
}
//Boxデフォルト画像描画
for(let i=0;i<BR.length;i++){
DrawCard(0,i,BR[i].x,BR[i].y);
CTX.fillStyle=CC[i];
fillrect(BR[i].x,BR[i].y,CW,CH,CC[i]);
//文字
CTX.fillStyle=TC[i];
CTX.textAlign = "center";
CTX.font="18px 'MS 明朝', serif";
CTX.fillText(MK[i],BR[i].x+12,BR[i].y+24);
}
}
//**********************************************************
//ボタンの描画(ゲーム終了後Updateループを抜けた後も反応させるためにDraw()から切りだす)
//戻り値:無
function DrawBtn(){
let fillC;
let fontC;
for(let i=0;i<BTNR.length;i++){
if(BTNR[i].sel==false){
fillC=Cwhite;
fontC=Cblack;
}else{
fillC=Cblack;
fontC=Cwhite;
}
fillrect(BTNR[i].x,BTNR[i].y,BW,BH,fillC);
CTX.textAlign = "left";
CTX.font="8pt Arial";
CTX.fillStyle=fontC;
CTX.fillText(BTNR[i].cap,BTNR[i].x+10,BTNR[i].y+12);
}
}
//************************************************* */
//カードソートと描画呼び出し
//戻り値:無
function DrawCards(){
//終了移動済Card描画
let c1 = C.slice();
c1=c1.filter(function(x){
return x.isFin==true && x.isMov==0;
});
c1.sort((a,b)=> a.num-b.num);
for(let i=0;i<c1.length;i++){
DrawCard(c1[i],false);
}
//静止Card描画
c1 = C.slice();
c1=c1.filter(function(x){
return x.isFin==false && x.isMov==0 && x.isDrg==0;
});
c1.sort((a,b)=> a.row-b.row);
for(let i=0;i<c1.length;i++){
DrawCard(c1[i],false);
}
//移動中Card描画
c1 = C.slice();
c1=c1.filter(function(x){
return x.isFin==false && x.isMov==1 && x.isDrg==0;
});
c1.sort((a,b)=> a.row-b.row);
for(let i=0;i<c1.length;i++){
DrawCard(c1[i],false);
}
//終了移動中Card描画
c1 = C.slice();
c1=c1.filter(function(x){
return x.isFin==true && x.isMov==1;
});
c1.sort((a,b)=> a.row-b.row);
for(let i=0;i<c1.length;i++){
DrawCard(c1[i],false);
}
//ドラッグ中ドロップ可能強調枠
if(DAFR.area==0){ //Tower area
rect(TR[DAFR.col][DAFR.row].x,TR[DAFR.col][DAFR.row].y,CW,CH,Cblack);
}
if(DAFR.area==1){ //Free area
rect(FR[DAFR.col].x,FR[DAFR.col].y,CW,CH,Cblack);
}
//ドラッグ中Card描画
c1 = C.slice();
c1=c1.filter(function(x){
return x.isFin==false && x.isMov==0 && x.isDrg==1;
});
c1.sort((a,b)=> a.row-b.row);
for(let i=0;i<c1.length;i++){
DrawCard(c1[i],true);
}
}
//**********************************
//カードの描画
function DrawCard(card,shadow){
//Cshadow
if(shadow){
fillrect(card.x+4,card.y+4,CW,CH,Cshadow);
}
//カード地
CTX.fillStyle=CC[card.mrk];
fillrect(card.x,card.y,CW,CH,CC[card.mrk]);
//番号文字
let numt;
numt=card.num;
CTX.fillStyle=TC[card.mrk];
CTX.textAlign = "center";
CTX.font="14px 'MS 明朝', serif";
CTX.fillText(numt,card.x+12,card.y+14);
//マーク
numt=MK[card.mrk];
CTX.fillStyle=TCT[card.mrk];
CTX.textAlign = "center";
CTX.font="18px 'MS 明朝', serif";
CTX.fillText(numt,card.x+12,card.y+34);
}
//****************************************************
//完了チェック
//戻り値:全カード完了の場合true そうでない場合falseを返す
function ChkEnd(){
let c1=C.filter(function(x){
return x.isFin==false || x.isMov==1;
});
if(c1.length==0){
return true;
}else{
return false;
}
}
//****************************************************
//完了(結果表示)
//戻り値:無し
function EndGame(){
ETIME=new Date();
let ms=ETIME.getTime()-STIME.getTime();
let sec=Math.trunc(ms/1000);
//メッセージ表示
CTX.fillStyle=Cblack;
CTX.font="20pt Arial";
CTX.textAlign = "left";
CTX.fillText("Congratulations !",TR[2][2].x,TR[2][2].y+CH);
CTX.font="10pt Arial";
CTX.fillText("Successfully completed.",TR[3][4].x,TR[3][4].y+CH);
CTX.fillText("MOVE: "+STP,TR[3][5].x,TR[3][5].y+CH);
CTX.fillText("TIME: "+sec+" sec.",TR[3][6].x,TR[3][6].y+CH);
}
//************************************************************
//ドラッグリスト作成
//戻り値:選択対象カードのリストを返却する
function GetSelectedCard(mx,my){
let aList=[];
//クリックカード特定(前面カード優先)
let c1 = C.slice();
c1.sort((a,b)=> a.row-b.row);
c1=c1.filter(function(x){
return x.isFin==false;
});
let j=-1;
for(let i=c1.length-1;i>=0;i--){
if(mx>c1[i].x && mx<c1[i].x+CW && my>c1[i].y && my<c1[i].y+CH){
j=c1[i].adr;
break;
}
}
if(j>=0){
//対象カードおよび同一カラム下に続くカードがあればそれらを取得
let c1=C.filter(function(x){
return x.col==C[j].col &&
x.row>=C[j].row &&
x.inArea==C[j].inArea &&
x.isFin==false;
});
//rowでソート
c1.sort((a,b)=> a.row-b.row);
//フリーの空き数取得
let c2=C.filter(function(x){
return x.inArea==1;
});
let x=FMax-c2.length;
//ドラッグ対象カード数がフリーの空き数+1以下の場合
if(c1.length<=(x+1)){
//ドラッグOKなのでドラッグ対象カードリストの作成
for(let i=0;i<c1.length;i++){
C[c1[i].adr].isDrg=1;
aList.push(c1[i].adr);
}
}
}
return aList;
}
//************************************************* */
//ドロップ位置取得
//戻り値:ドロップ先のエリア、行、列を返却する
function GetDropCoord(mx,my){
let col=TCMax;
let row=TRMax;
let area=AMax;
//Free上
for(let i=0;i<FR.length;i++){
if(mx>FR[i].x && mx<FR[i].x+CW && my>FR[i].y && my<FR[i].y+CH){
col=i;
row=0;
area=1;
return {area,col,row};
}
}
//Tower上
for(let i=0;i<TR.length;i++){
for(let j=0;j<TR[i].length;j++){
if(mx>TR[i][j].x && mx<TR[i][j].x+CW && my>TR[i][j].y && my<TR[i][j].y+CH){
col=i;
row=j;
area=0;
return {area,col,row};
}
}
}
return {area,col,row};
}
//************************************************* */
//ドロップ可不可判定
//戻り値:ドロップ可能かどうかをbooleanで返却する
function ChkDropOk(area,col,row){
let swDropOk=false;
if(area==1){
//FreeRectにドロップ
let c1=C.filter(function(x){
return x.inArea==1 && x.isFin==false && x.isDrg==0;
});
if(DrgList.length<=(FMax-c1.length)){
//ドラッグ中枚数<=空きFreeRect数ならドロップOK
swDropOk=true;
}else{
//err:ドロップカード数がフリーエリアの空きスロット数を超えている"
}
}else if(area==0){
//TowerRectにドロップ
let swErr=false;
if(swErr==false){
let c1=C.filter(function(x){
return x.inArea==0 && x.col==col && x.isFin==false;
});
if(c1.length>0){
if(C[DrgList[0]].num==13){
//err:13はタワーの先頭にしか配置できない
swErr=true;
}
}else{
if(C[DrgList[0]].num!=13){
//err:13以外をタワー先頭に配置しようとした
swErr=true;
}
}
}
if(swErr==false){
if(IsInOrder(DrgList)==false){
//err:ドロップ対象のカード群が連続していない
swErr=true;
}
}
if(swErr==false){
let c1=C.filter(function(x){
return x.inArea==0 && x.col==col && x.isFin==false;
});
if(c1.length>0){
//末端カードを取得
c1.sort((a,b)=> b.row-a.row);
if(c1[0].num!=(C[DrgList[0]].num+1)||c1[0].mrk!=C[DrgList[0]].mrk){
//err:上のカードと連続していない
swErr=true;
}
}
}
if(swErr==false){
swDropOk=true;
}else{
swDropOk=false;
}
}else{
swDropOk=false; //無関係のエリアでdropされている
}
return swDropOk;
}
//************************************************* */
//アドレスリストのカードマークが同一、番号が連番かをチェックする
//戻り値 true:OK false:NGマーク不一致もしくは順番になってない
function IsInOrder(list){
let swOrder=true;
for(let i=0;i<list.length;i++){
if(i>0){
if(C[list[i-1]].mrk!=C[list[i]].mrk ||
C[list[i-1]].num!=C[list[i]].num+1){
swOrder=false;
break;
}
}
}
return swOrder;
}
//************************************************* */
//ドロップ実行・カード情報更新
//戻り値:無
function DoDrop(area,col,row){
//Drop実行
if(area==0){
//該当colの最終rowを取得
let c1=C.filter(function(x){
return x.inArea==0 && x.col==col && x.isFin==false;
});
if(c1.length>0){
//末端カードを取得
c1.sort((a,b)=> b.row-a.row);
row=c1[0].row+1;
}else{
row=0;
}
//Towerへのドロップ
for(let i=0;i<DrgList.length;i++){
let j=DrgList[i];
//カード情報更新
C[j].col=col;
C[j].row=row+i;
C[j].inArea=area;
C[j].isFin=false;
C[j].isDrg=0;
C[j].isMov=0;
AddMvList(j,0,TSDR,TR[C[j].col][C[j].row].x,TR[C[j].col][C[j].row].y);
}
}else{
//Freeへのドロップ
//既存カード状態を保存
let c1=C.filter(function(x){
return x.inArea==1 && x.isFin==false && x.isDrg==0;
});
let UseF=[false,false,false,false];
//ドロップカードを指定カラムから無条件にセット
for(let i=0;i<DrgList.length;i++){
let j=DrgList[i];
//カード情報更新
C[j].col=(col+i)%FMax;
C[j].row=row;
C[j].inArea=area;
C[j].isFin=false;
C[j].isDrg=0;
C[j].isMov=0;
//Freeカラム利用状態を保存
UseF[C[j].col]=true;
AddMvList(j,0,TSDR,FR[C[j].col].x,FR[C[j].col].y);
}
//保存しておいた既存カードを再セット
if(c1.length>0){
//カラム順に並べる
c1.sort((a,b)=> b.col-a.col);
for(let i=0;i<c1.length;i++){
let j=c1[i].adr;
let k=c1[i].col;
while(1){
if(UseF[k]==false){
C[j].col=k;
C[j].row=row;
C[j].inArea=1;
C[j].isFin=false;
C[j].isDrg=0;
C[j].isMov=0;
UseF[k]=true;
break;
}
k=k+1;
k=k%FMax;
}
AddMvList(j,0,TSDR,FR[C[j].col].x,FR[C[j].col].y);
}
}
}
}
//************************************************* */
//ドロップ取り消し
//戻り値:無
function CancelDrop(){
//ドロップできなかった場合、元の位置に戻す
//自動移動対象リストにカードを追加し、ドラッグリストをクリアする
let c1=C.filter(function(x){
return x.isDrg==1;
});
for(let i=0;i<c1.length;i++){
let j=c1[i].adr;
if(C[j].inArea==0){
AddMvList(j,0,TSDR,TR[C[j].col][C[j].row].x, TR[C[j].col][C[j].row].y);
}else if(C[j].inArea==1){
AddMvList(j,0,TSDR,FR[C[j].col].x, FR[C[j].col].y);
}
C[j].isDrg=0;
}
DrgList=[];
}
//*************************************************
//カード移動リスト(MvList)に追加する
//戻り値:無
function AddMvList(a,t,s,x,y){
//a:アドレス t:開始時間 s:easing上限時間 x:X座標 y:Y座標
//二重登録の抑制
let c1=MvList.filter(function(x){
return x.adr==a;
});
if(c1.length==0){
C[a].isMov=1;
MvList.push({adr:a,st:t,timespan:s,x1:C[a].x,y1:C[a].y,x2:x,y2:y});
}
}
//*************************************************
//カード移動リスト(MvList)から削除する
//戻り値:無
function DelMvList(a){
//a:アドレス
C[a].isMov=0;
MvList.shift();
}
//************************************************************
//マウスダウン
//戻り値:無
function myMouseDown(e){
e.preventDefault();
e.stopPropagation();
let mx=parseInt(e.clientX-OffsetX);
let my=parseInt(e.clientY-OffsetY);
DTime=Date.now();
MousePress=1;
//ドラッグ対象リスト作成
DrgList=[];
DrgList=GetSelectedCard(mx,my);
//最新のマウス位置を保存
StartX=mx;
StartY=my;
}
//************************************************************
//カーソル移動
//戻り値:無
function myMouseMove(e){
e.preventDefault();
e.stopPropagation();
let mx=parseInt(e.clientX-OffsetX);
let my=parseInt(e.clientY-OffsetY);
let dx=mx-StartX;
let dy=my-StartY;
if(MousePress==1){
// ドラッグ中の場合
if(DrgList.length>0){
//ドラッグ中カードの座標更新
for(let i=0;i<DrgList.length;i++){
C[DrgList[i]].x+=dx;
C[DrgList[i]].y+=dy;
}
//現カーソル位置の列行取得
let{area,col,row}=GetDropCoord(mx,my);
//ドロップ妥当性確認
if(ChkDropOk(area,col,row)==true){
//Rectをフレーム強調
DAFR={area,col,row};
}
}
}else{
//非ドラッグ中の場合
CancelDrop();
myMouseOver(mx,my);
}
//最新のマウス位置を保存
StartX=mx;
StartY=my;
}
//************************************************************
//マウスアップ
//戻り値:無
function myMouseUp(e){
e.preventDefault();
e.stopPropagation();
let mx=parseInt(e.clientX-OffsetX);
let my=parseInt(e.clientY-OffsetY);
MousePress=0;
DAFR=[];
UTime=Date.now();
if(UTime-DTime>200){
if(DrgList.length>0){
//ドロップ先の列行取得
let{area,col,row}=GetDropCoord(mx,my);
//ドロップ妥当性確認
if(ChkDropOk(area,col,row)==true){
//Drop実行
DoDrop(area,col,row);
}else{
//元のカード位置に戻す
CancelDrop();
}
}
}else{
myClick(mx,my); //マウスダウンから200ticks以内でのマウスアップをクリックとみなす
}
}
//************************************************************
//クリック
//戻り値:無
function myClick(mx,my){
//ボタンクリックの場合
for(let i=0;i<BTNR.length;i++){
if(mx>BTNR[i].x && mx<BTNR[i].x+BW && my>BTNR[i].y && my<BTNR[i].y+BH){
main(BTNR[i].cap);
return;
}
}
//カードクリックの場合
ClickCard(mx,my);
}
//*****************************************************
//カードクリック
//戻り値:無
function ClickCard(mx,my){
//タワー→フリーの順で移動できる場所に移動する
let aList=[];
//クリックされたカードアドレスリストを取得
aList=GetSelectedCard(mx,my);
if(aList.length>0){
//まずはFreeの空きスロットを列挙
let UseF=[];
let FreeCnt=0;
for(let i=0;i<FR.length;i++){
let c1=C.filter(function(x){
return x.inArea==1 && x.isFin==false && x.isDrg==0 && x.col==i;
});
if(c1.length>0){
UseF.push(true);
}else{
UseF.push(false);
FreeCnt=FreeCnt+1;
}
}
let swOk=false;
//選択枚数とFree空きスロット数を比較
if(FreeCnt+1 >= aList.length){
//移動可能枚数は空きスロット+1まで
//aList内で同一マーク・番号が順になっているかチェック
if(IsInOrder(aList)){
//先にTowerに移動できるかをチェック
//各タワーの末尾のカードのマーク・番号と比較
let c=TCMax;
let r=TRMax;
for(let i=0;i<TCMax;i++){
//Towerの末端をList化
let c1 = C.slice();
c1=c1.filter(function(x){
return x.inArea==0 && x.isFin==false && x.col==i;
});
if(c1.length>0){
//該当Tower末端カードを取得
c1.sort((a,b)=> b.row-a.row);
if(c1[0].mrk==C[aList[0]].mrk && c1[0].num-1==C[aList[0]].num){
swOk=true;
c=i;
r=c1[0].row+1;
break;
}
}else{
if(C[aList[0]].num==13){
swOk=true;
c=i;
r=0;
break;
}
}
}
if(swOk==true){
for(let j=0;j<aList.length;j++){
C[aList[j]].col=c;
C[aList[j]].row=r+j;
C[aList[j]].inArea=0;
C[aList[j]].isFin=false;
C[aList[j]].isDrg=0;
C[aList[j]].isMov=0;
AddMvList(aList[j],0,TSMV,TR[c][r+j].x,TR[c][r+j].y);
}
}
}
}
//Towerに置けない場合で、Freeに枚数分の空きがあれば、Freeに置く
if(swOk==false && FreeCnt>=aList.length){
swOk==true
for(let i=0;i<aList.length;i++){
for(let j=0;j<UseF.length;j++){
//Freeの空きに自動移動
if(UseF[j]==false){
C[aList[i]].col=j;
C[aList[i]].row=0;
C[aList[i]].inArea=1;
C[aList[i]].isFin=false;
C[aList[i]].isDrg=0;
C[aList[i]].isMov=0;
AddMvList(aList[i],0,TSMV,FR[j].x,FR[j].y);
UseF[j]=true;
break;
}
}
}
}
}
}
//***************************************************
//マウスオーバー(非ドラッグ中)
//戻り値:無
function myMouseOver(mx,my){
//ボタンマウスオーバー(判定)
for(let i=0;i<BTNR.length;i++){
if(mx>BTNR[i].x && mx<BTNR[i].x+BW && my>BTNR[i].y && my<BTNR[i].y+BH){
BTNR[i].sel=true;
}else{
BTNR[i].sel=false;
}
}
//カードマウスオーバー
//ボタンについては画面Updateループ外(ゲーム終了後)での単独画面更新できるようにしておく
if(ChkEnd()){
DrawBtn();
}
}
//****************************************************
//矩形描画(frame)
//戻り値:無
function rect(x,y,w,h,color){
CTX.beginPath();
CTX.rect(x,y,w,h);
CTX.closePath();
CTX.strokeStyle=color;
CTX.lineWidth=1;
CTX.stroke();
}
//************************************************* */
//矩形描画(fill)
function fillrect(x,y,w,h,color){
CTX.fillStyle = color;
CTX.fillRect(x, y, w, h);
}
//*******************************************
//ランダム配列作成(開始番号、終了番号指定)
function GetShuffledArray(st,ed){
let list1=[];
for(let i=st;i<=ed;i++){
list1.push(i);
}
let list2=[];
while(list1.length>0){
let x=Math.trunc(Math.random()*list1.length);
list2.push(Math.trunc(list1[x]));
list1.splice(x,1);
}
return list2;
}
//************************************************* */
//線形補間基本関数(Linear Interpolate)
function lerp(a,b,t) {
if(b==a) return a;
return a+t*(b-a);
}
//************************************************* */
//easing関数配列
function easing(mode,type,x){
//関数配列
let EaseModeType = new Array();
//以下easing関数をあらかじめ配列登録
EaseModeType.in_linear=function(x){
return x;
}
EaseModeType.out_linear=function(x){
return x;
}
EaseModeType.in_out_linear=function(x){
return x;
}
EaseModeType.in_quad=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
return c*(t/=d)*t+b;
}
EaseModeType.out_quad=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
return -c*(t/=d)*(t-2)+b;
}
EaseModeType.in_out_quad=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
if((t/=d/2)<1) return c/2*t*t+b;
return -c/2*((--t)*(t-2)-1)+b;
}
EaseModeType.in_cubic=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
return c*(t/=d)*t*t+b;
}
EaseModeType.out_cubic=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
return c*((t=t/d-1)*t*t+1)+b;
}
EaseModeType.in_out_cubic=function(x){
let t = x;
let b = 0;
let c = 1;
let d = 1;
if ((t/=d/2)<1) return c/2*t*t*t+b;
return c/2*((t-=2)*t*t+2)+b;
}
EaseModeType.in_quart=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
return c*(t/=d)*t*t*t+b;
}
EaseModeType.out_quart=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
return -c*((t=t/d-1)*t*t*t-1)+b;
}
EaseModeType.in_out_quart=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
if((t/=d/2)<1) return c/2*t*t*t*t+b;
return -c/2*((t-=2)*t*t*t-2)+b;
}
EaseModeType.in_quint=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
return c*(t/=d)*t*t*t*t+b;
}
EaseModeType.out_quint=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
return c*((t=t/d-1)*t*t*t*t+1)+b;
}
EaseModeType.in_out_quint=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
if ((t/=d/2)<1) return c/2*t*t*t*t*t+b;
return c/2*((t-=2)*t*t*t*t+2)+b;
}
EaseModeType.in_sine=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
return -c*Math.cos(t/d*(Math.PI/2))+c+b;
}
EaseModeType.out_sine=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
return c*Math.sin(t/d*(Math.PI/2))+b;
}
EaseModeType.in_out_sine=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
return -c/2*(Math.cos(Math.PI*t/d)-1)+b;
}
EaseModeType.in_expo=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
return (t==0) ? b : c*Math.pow(2,10*(t/d-1))+b;
}
EaseModeType.out_expo=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
return (t==d) ? b+c : c*(-Math.pow(2,-10*t/d)+1)+b;
}
EaseModeType.in_out_expo=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
if(t==0) return b;
if(t==d) return b+c;
if((t/=d/2)<1) return c/2*Math.pow(2,10*(t-1))+b;
return c/2*(-Math.pow(2,-10*--t)+2)+b;
}
EaseModeType.in_circ=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
return -c*(Math.sqrt(1-(t/=d)*t)-1)+b;
}
EaseModeType.out_circ=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
return c*Math.sqrt(1-(t=t/d-1)*t)+b;
}
EaseModeType.in_out_circ=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
if((t/=d/2)<1) return -c/2*(Math.sqrt(1-t*t)-1)+b;
return c/2*(Math.sqrt(1-(t-=2)*t)+1)+b;
}
EaseModeType.in_elastic=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
let s=1.70158;
let p=0;
let a=c;
if(t==0) return b;
if((t/=d)==1) return b+c;
if(p==0) p=d*.3;
if(a<Math.abs(c)) { a=c; s=p/4; }
else s=p/(2*Math.PI)*Math.asin(c/a);
return -(a*Math.pow(2,10*(t-=1))*Math.sin((t*d-s)*(2*Math.PI)/p ))+b;
}
EaseModeType.out_elastic=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
let s=1.70158;
let p=0;
let a=c;
if(t==0) return b;
if((t/=d)==1) return b+c;
if(p==0) p=d*.3;
if(a<Math.abs(c)) { a=c; s=p/4; }
else s=p/(2*Math.PI)*Math.asin(c/a);
return a*Math.pow(2,-10*t)*Math.sin((t*d-s)*(2*Math.PI)/p)+c+b;
}
EaseModeType.in_out_elastic=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
let s=1.70158;
let p=0;
let a=c;
if (t==0) return b;
if((t/=d/2)==2) return b+c;
if(p==0) p=d*(.3*1.5);
if(a<Math.abs(c)) {a=c; s=p/4;}
else s=p/(2*Math.PI)*Math.asin(c/a);
if(t<1) return -.5*(a*Math.pow(2,10*(t-=1))*Math.sin((t*d-s)*(2*Math.PI)/p))+b;
return a*Math.pow(2,-10*(t-=1))*Math.sin((t*d-s)*(2*Math.PI)/p)*.5+c+b;
}
EaseModeType.in_back=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
let s=1.70158;
return c*(t/=d)*t*((s+1)*t-s)+b;
}
EaseModeType.out_back=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
let s=1.70158;
return c*((t=t/d-1)*t*((s+1)*t+s)+1)+b;
}
EaseModeType.in_out_back=function(x){
let t=x;
let b=0;
let c=1;
let d=1;
let s=1.70158;
if ((t/=d/2)<1) return c/2*(t*t*(((s*=(1.525))+1)*t-s))+b;
return c/2*((t-=2)*t*(((s*=(1.525))+1)*t+s)+2)+b;
}
EaseModeType.out_bounce=function(x){
const n1=7.5625;
const d1=2.75;
if (x<1/d1) {
return n1*x*x;
}else if(x<2/d1){
return n1*(x-=1.5/d1)*x+0.75;
}else if(x<2.5/d1){
return n1*(x-=2.25/d1)*x+0.9375;
}else{
return n1*(x-=2.625/d1)*x+0.984375;
}
}
EaseModeType.in_bounce=function(x){
return 1-EaseModeType.out_bounce(1-x);
}
EaseModeType.in_out_bounce=function(x){
if(x<1/2) return (1-EaseModeType.out_bounce(1-2*x))/2;
return (1+EaseModeType.out_bounce(2*x-1))/2;
}
//関数名変数作成
let FN=mode + "_" + type;
return EaseModeType[FN](x);
}
}