PieceOpt.push_back(p.GetHash());
			++tot;
		}
		sort(PieceOpt.begin(), PieceOpt.end());
		for (llu o : PieceOpt) res=res*37LLU+o;
		/*for (Piece p : Next) {
			if (tot) --tot; 
			else res=res*47LL+p.GetHash();
		}*/
		for (int i=1; i<=LX; ++i) {
			for (int j=1; j<=LY; ++j) {
				res*=47LLU; if (B[i][j]) ++res;
			}
		}
		return res;
	}
	~Status() {
		for (int i=0; i<=LX+1; ++i) delete[] B[i];
		delete[] B;
	}
};
struct Particle {
	double x, y, vx, vy, ax, ay;
	double sz, dsz, th, dth; int n, clr;
	void Update(int TimeDel) {
		vx += double(TimeDel)*0.001*ax;
		vy += double(TimeDel)*0.001*ay;
		x += double(TimeDel)*0.001*vx;
		y += double(TimeDel)*0.001*vy;
		sz = max(0.0,sz-double(TimeDel)*0.001*dsz);
		th += double(TimeDel)*0.001*dth;
	}
};
struct FloatText {
	double x, y, vx, vy, ax, ay, sz, dsz, bd, dbd;
	double th, dth, w, dw;
	int clr; string Text;
	void Update(int TimeDel) {
		vx += double(TimeDel)*0.001*ax;
		vy += double(TimeDel)*0.001*ay;
		x += double(TimeDel)*0.001*vx;
		y += double(TimeDel)*0.001*vy;
		sz = max(0.0,sz-double(TimeDel)*0.001*dsz);
		bd = max(0.0,bd-double(TimeDel)*0.001*dbd);
		th += double(TimeDel)*0.001*dth;
		w += double(TimeDel)*0.001*dw;
	}
};
struct GarbageLayer {
	vector<int> B;
	int Delay;
	GarbageLayer(int LY=0, int _Delay=0) {
		B.resize(LY+2,0); Delay=_Delay;
	}
};
string Menu = "home";
bool Pause = false;
bool ShowAttack = false;
const int Max = 9999999;
int FrameAILimit = 1;
double CurX, CurY;
int PlayerCount, AliveCount;
set<llu> InList;
struct BoardHistory {
	int SX, LX, LY, TargetSta; vector<vector<int> > B, U;
	PieceGenerator Gen; Piece Cur; vector<Piece> Hold, Next;
	vector<GarbageLayer> AttackIn;
	int TPiece, TLine, TDig, TAttack, TAC, Combo, B2B, C2ComboRem;
	bool CurClutch;
	ll Time, TScore;
	int QPLevel, QPRank, QPExpDecCD; ll QPExp, QPAddExp, TQPScore; bool QPBlighted;
	int CurZone; int ZoneProgress, ZoneAttack, ZoneRem;
	BoardHistory() {
		SX=LX=LY=0;
	}
	void MarkIn() {
		for (Piece &o : Hold) InList.insert(o.GetHash());
		for (Piece &o : Next) InList.insert(o.GetHash());
		for (Piece &o : Gen.Pool) InList.insert(o.GetHash());
		for (Piece &o : Gen.RemPool) InList.insert(o.GetHash());
	}
};
string GetBotName(int x) {
	if (x<0) return "Default";
	return vector<string>({"Nanami T0",
	"Silver T2",
	"Nanami T2",
	"Silver T3",
	"Nanami T3",
	"Nanami T1",
	"Denia T2",
	"Denia T3",
	"Silver T1",
	"Denia T1"})[x];
	return vector<string>({"D0",
	"S2",
	"D2",
	"S3",
	"D3",
	"D1",
	"Q2",
	"Q3",
	"S1",
	"Q1"})[x];
}
const double TimeMul = 0.0;
double TimeAcc=0.0; ll LstClock, CurClock, FinalClock;
int GlobalRealTimeDel = 0;
ll GetClock() {
	if (!TimeMul) return clock();
	CurClock = clock();
	TimeAcc += double(CurClock-LstClock)*TimeMul;
	ll d = ll(floor(TimeAcc));
	FinalClock+=d; TimeAcc-=double(d); LstClock=CurClock;
	return FinalClock;
}
bool QPMode = false;
mt19937 GlobalGen; int BeginTime=time(0);
#warning Board
int Obs = -1;
class Board {
	int **B, **U, **Age, *FallDest; bool **C;
	vector<Monitor> Keys;
	Piece Cur, Lst; vector<Piece> Hold, Next;
	int PosX, PosY, UID=1, PlaceUID=0;
	int BoardID = 0;
	PieceGenerator Gen;
	int InactiveTime, InactiveTot, FallTime;
	int ResetRem, HoldRem;
	int LastLock, GravityCD;
	int LineClearText, LineClearColor, LineClearAge;
	string SpinText; int SpinColor, SpinGrade, SpinAge;
	int ComboText, ComboAge, B2BText, B2BAge, B2BBlinkRem, AllClearAge;
	int Combo, B2B, Chain; bool SpinSta, RotSta, CurClutch; pair<int,int> LastKick;
	bool HalfDown;
	int TPiece, TLine, TDig, TAttack, TAC, ActionTime, ActionCD; ll TScore, TQPScore, DScore, DQPScore, Time;
	vector<pair<int,pair<ll,ll> > > QPSeg; vector<ll> QPSegTime;
	ll QPLevTime; double QPAttackWeightInc;
	int QPWeightDec = 0;
	int QPLevel=0, QPRank=0, QPExpDecCD=0; ll QPExp=0LL, QPAddExp=0LL; 
	int LineAfterPC, MasterBegin, QPRevMod2GarbSide;
	vector<pair<Action,int> > PendingActions;
	vector<Particle> Particles; vector<pair<Particle,int> > DelayParticles;
	vector<FloatText> FloatTexts;
	vector<Piece*> CustomPiece;
	pair<string,int> LastClearType;
	vector<GarbageLayer> AttackIn; int HolePos;
	//GarbageLayer LatestReceive;
	vector<int> AttackOut;
	bool ShowParticle;
	int DropDelay, LockDelay, GravityDelay, ARE, LCD, CCD, StartDelay=3000, StartMsg, Level;
	bool SideGravity = false;
	double PDth, PDx, PDy, DNext;
	int C2ComboRem, PushCD, HoldPosInc, PreInc;
	int GarbCount; double AttackAcc=0.0;
	int MutateID=0, AddID=0, ZoneRem=0;
	int MutatePos = 0;
	bool Over = false;
	double BotPos = 0.0;
	int LastAttacker=-1, KOMessage=-2;
	int StackHeight=0, WindUpCD=0, QPRisePos=0, StockRem=0;
	bool QPBlighted = false;
	int RoundHeight = 0;
	int VanishHeight = 0;
	
	vector<Piece> Pool; EGenerationType GenType;
	int LX, LY, SX;
	double PX, PY, K;
	double tPX, tPY, tK; bool FirstSetScale=true;
	int HoldLen=1, NextLen=5, HoldType=1;
	int DefaultRot=3, DefaultDir=0;
	int DAS=200, ARR=0, SDDAS=200, SDARR=20, HCD=100, DCD=0;
	int BaseDropDelay=500, BaseLockDelay=500, BaseGravityDelay=0, BaseARE=0, BaseLCD=0, BaseCCD=200;
	int ResetLim = 15;
	bool Bot, SpinMode; int BotType=1, ActType=1;
	Piece Tg; int TgD, TgX, TgY;
	int BotSpeed=1000, SpeedCap=0;
	int GarbageGap=8, GarbageDelay=500, GarbageSeg=0, GarbagePass=0, GarbagePush=100, GarbagePreview=10;
	bool ForceShowAttack = false;
	ELevScaleMode LevScaleMode = LS_Default;
	int LevPiece=0, LevLine=0, LevTime=0, LevAttack=0;
	int TarPiece=0, TarLine=0, TarDig=0, TarAttack=0; ll TarTime=0, TarScore=0;
	bool TypePiece=false, TypeLine=false, TypeDig=false, TypeTime=false, TypeAttack=false, TypeScore=false;
	int TargetSta = 0;
	int LevPieceInc=0, LevLineInc=0, LevTimeInc=0, LevAttackInc=0;
	int LevStart=0, LevBase=0, LevMax=0;
	int LevScaleRate = 7;
	int MasterThreshold = 0;
	int Stock = 0;
	bool AutoRespawn = false;
	bool HideStat = false;
	bool UseZone = false;
	int ZoneReq=8, ZoneSeg=4, ZoneTime=5000;
	int ZoneProgress=0, ZoneAttack=0;
	EBaseRule BaseRule;
	EComboRule ComboRule;
	ESpinRule SpinRule;
	EB2BRule B2BRule;
	EAllClearRule AllClearRule;
	ERotateSystem RotateSystem;
	EGarbageShape GarbageShape;
	EGarbageType GarbageType;
	EIntRule IntRule;
	EGarbageActive GarbageActive;
	EGravityType GravityType;
	EClutchRule ClutchRule;
	int DigBonus = 2;
	bool SingleBonus = false;
	int OpenerProtect = 14;
	int StaticGarbHeight, StartHeight;
	int InnerMess=0, SegMess=1000;
	int ShadowType = 1;
	bool Mutate = false;
	bool UseMove=true, UseRotate=true, Use2Rot=true, UseHalf=true,
	UseSoftDrop=true, UseHardDrop=true, UseSonicDrop=true,
	UseFlip=false, UseUp=false, UseWaste=false, UseLock=false,
	UsePlace=false, UseClick=true, UseUndo=false, UseDeep=false;
	bool UseTip = false;
	deque<BoardHistory> History; int HistoryPos=0;
	int SmoothLevel = 3;
	bool RandomRot = false;
	ETargetType TargetType = TT_Even;
	int PaintType = 0;
	
	void ClearSearchProcess() {
		if (SearchStep!=-1) {
			bool f = false;
			for (Status *p : CurLayer) {
				if (p==Current) f=true;
				delete p;
			}
			delete Map; delete Tmp;
			if (!f) delete Current;
			SearchStep = -1;
		}
		for (Status *o : Recycle) delete o;
		Recycle.clear();
	}
	int NewUID() {
		++UID; if (UID>1000000000) UID=1;
		return UID;
	}
	int LatestClock = 0;
	void ResetBoard(int Seed=-1) {
		Tg=P_Empty; TgX=TgY=StackHeight=ZoneRem=PreInc=0; TgD=-1; Rank=999; Chain=0;
		FallTime=LastAttack=TargetSta=0; InCascade=IsZoneClear=CurClutch=GiveQPScoreMsg=false; DNext=0.0;
		DropDelay=BaseDropDelay; LockDelay=BaseLockDelay; GravityDelay=BaseGravityDelay;
		ARE=BaseARE; LCD=BaseLCD; CCD=BaseCCD; QPSeg.clear(); QPSeg.push_back(make_pair(LevelColor[0],make_pair(0LL,0LL)));
		QPSegTime.clear();
		KOCount=Badge=ActionCD=ZoneProgress=ZoneAttack=CurZone=0;
		LastAttacker=-1; //KOMessage=-2;
		//LatestReceive = GarbageLayer(0);
		ClearSearchProcess();
		if (Seed==-1) Seed=LatestClock;
		ClearBoard(); Level=-1;
		LastClearType = make_pair("",-1);
		Hold.clear(); Next.clear();
		int H = max(0,min(100,GetConfig("RandHistory",1)));
		int R = max(0,min(100,GetConfig("RandRoll",1)));
		int E = max(0,min(100,GetConfig("BagExtra",0)));
		MutatePool.clear();
		if (ModM==1) {
			for (int i=2; i<9; ++i) MutatePool.push_back(i);
		}
		MutateID=AddID=MutatePos=0;
		if (QPMode) Seed+=BoardID-1;
		if (Mod2) HoldLen=0;
		if (Mod2==2) {
			NextLen=min(NextLen,1); GenType=G_Random;
			QPRevMod2GarbSide=rand()&1; H=R=ShadowType=0;
		}
		if (Mod3==2) BaseLCD=max(BaseLCD,1150);
		if (Mod5==2) GarbagePreview=max(GarbagePreview,10);
		if (Mod6==2) {
			StaticGarbHeight=max(StaticGarbHeight,4); GarbageGap=min(GarbageGap,2);
		}
		if (Mod9) GarbagePush=0;
		if (Mutate) {
			vector<Piece> MutatePool;
			for (Piece p : Pool) MutatePool.push_back(p);
			Gen = PieceGenerator(MutatePool,GenType,Seed,H,R,E);
			AddID = Pool.size();
		}
		else Gen=PieceGenerator(Pool,GenType,Seed,H,R,E);
		HoldLen=min(HoldLen,10); NextLen=min(NextLen,100);
		Lst=Cur=P_Empty; InactiveTime=InactiveTot=FallTime=0; HoldRem=HoldLen;
		LineClearText=-1; LineClearAge=5000; LineClearColor=0;
		SpinText=""; SpinAge=5000; SpinColor=SpinGrade=0; SpinSta=RotSta=false;
		Combo=B2B=B2BText=ComboText=0; ComboAge=B2BAge=5000; B2BBlinkRem=0;
		TScore=DScore=TQPScore=DQPScore=0LL; QPLevel=QPRank=QPExpDecCD=0; QPExp=QPAddExp=0LL;
		TPiece=TLine=TDig=TAttack=TAC=0; Time=0LL;
		ActionTime=int(-BotPos*double(BotSpeed)); AllClearAge=5000; SearchStep=-1;
		GarbCount=0; QPBlighted=false; QPLevTime=0LL;
		LineAfterPC=LastLock=HoldPosInc=0;
		PendingActions.clear();
		AttackIn.clear(); AttackOut.clear();
		HalfDown = false;
		SpinMode = false;
		Over = false;
		HolePos = rand()%LY+1;
		FillNext();
		C2ComboRem=PushCD=0;
		AttackAcc=QPAttackWeightInc=0.0;
		RerollHole(); StockRem=max(0,Stock);
		for (int i=0; i<min(LX,max(StaticGarbHeight,StartHeight)); ++i) {
			if (randf()*1000.0<SegMess) RerollHole();
			AddGarbage(RollGarbage());
		}
		if (ModS) ChangeSize(LX,4+max(ModP,ModM),6+max(ModP,ModM));
		if (Mod3==2) {
			for (int i=1; i<=SX-8; ++i) {
				for (int j=1; j<=LY; ++j) {
					int pi=(i-1)%5, pj=(j-1)%5;
					if (max(abs(pi-2),abs(pj-2))==1) B[i][j]=Garb;
				}
			}
		}
		if (Mod6==2) {
			for (int i=1; i<=SX-8; ++i) {
				for (int j=1; j<=LY; ++j) {
					if ((i+j)&1) {
						B[i][j]=0; continue;
					}
					if ((j==1)||(j==LY)) B[i][j]=Garb;
					else B[i][j]=RollColor();
				}
			}
		}
		if (Mod8==2) {
			for (int i=1; i<=((SX-1)>>1); ++i) {
				int p = rand()%LY+1;
				for (int j=1; j<=LY; ++j) {
					if (j!=p) B[i][j]=Garb;
					else B[i][j]=0;
				}
			}
		}
		else if (Mod7==2) {
			for (int i=1; i<=min(SX,3); ++i) {
				int p = rand()%LY+1;
				for (int j=1; j<=LY; ++j) {
					if (j!=p) B[i][j]=Garb;
					else B[i][j]=0;
				}
			}
		}
		for (int i=1; i<=LX; ++i) {
			for (int j=1; j<=LY; ++j) {
				if ((B[i][j])&&(!U[i][j])) U[i][j]=NewUID();
			}
		}
		InactiveTime=InactiveTot=StartDelay;
		StartMsg = StartDelay/1000;
		Time = -ll(StartDelay);
		if (!StartDelay) StartMsg=-1;
		for (int i=1; i<=LX; ++i) FallDest[i]=i;
		UpdateLevel();
		WindUpCD=5000; QPRisePos=0; QPWeightDec=0; ResetRem=ResetLim;
	}
	void AddParticle(int Delay, int n, int clr, double sz, double dsz, double x, double y, double vx, double vy, double ax, double ay, double th=0.0, double dth=0.0) {
		if (!ShowParticle) return;
		Particle o = (Particle){x,y,vx,vy,ax,ay,sz,dsz,th,dth,n,clr};
		if (Delay) DelayParticles.push_back(make_pair(o,Delay));
		else Particles.push_back(o);
	}
	void AddFloatText(string str, int clr, double sz, double dsz, double bd, double dbd, double x, double y, double vx, double vy, double ax, double ay, double th=0.0, double dth=0.0, double w=1.0, double dw=0.0) {
		if (!ShowParticle) return;
		FloatTexts.push_back((FloatText){x,y,vx,vy,ax,ay,sz,dsz,bd,dbd,th,dth,w,dw,clr,str});
	}
	vector<int> MutatePool;
	void FillNext(int val=0) {
		int lim = max(max(12,val),NextLen+5);
		while (Next.size()<lim) {
			Piece p = Gen.Roll();
			/*#warning Test
			if ((!BoardID)&&(p.GetName()=="I")) {
				int u = rand()%6+1;
				if (u==1) p=P_A;
				if (u==2) p=P_B;
				if (u==3) p=P_D;
				if (u==4) p=P_I;
				if (u==5) p=P_I5;
				if (u==6) p=P_30;
			}*/
			if (ModM==1) {
				if (MutatePool.empty()) {
					for (int i=0; i<9; ++i) MutatePool.push_back(i);
					shuffle(MutatePool.begin(), MutatePool.end(), GlobalGen);
				}
				int u=MutatePool.back(); MutatePool.pop_back();
				//u = rand()&1;
				if (u<2) {
					vector<Piece> lst;
					if (p.GetName()=="I") lst=QPModM_I;
					else if (p.GetName()=="J") lst=QPModM_J;
					else if (p.GetName()=="L") lst=QPModM_L;
					else if (p.GetName()=="O") lst=QPModM_O;
					else if (p.GetName()=="S") lst=QPModM_S;
					else if (p.GetName()=="T") lst=QPModM_T;
					else if (p.GetName()=="Z") lst=QPModM_Z;
					else {
						int r = rand()%7;
						if (r==0) lst=QPModM_I;
						if (r==1) lst=QPModM_J;
						if (r==2) lst=QPModM_L;
						if (r==3) lst=QPModM_O;
						if (r==4) lst=QPModM_S;
						if (r==5) lst=QPModM_T;
						if (r==6) lst=QPModM_Z;
					}
					if (!lst.empty()) {
						int pos = max(0,min(Level-1,int(lst.size())-1));
						if ((!pos)||(u==1)) p=lst[pos];
						else p=lst[rand()%pos];
					}
				}
			}
			if (RandomRot) p=GetRotate(p,rand()&3);
			Next.push_back(p); Next.back().SetDir(DefaultDir);
		}
	}
	pair<int,int> SpawnPlace(const Piece &_p) {
		Piece p=_p; p.SetDir(DefaultDir);
		int PLX = p.GetLX();
		int LeftMost=-1, RightMost=PLX, UpMost=-1, DownMost=PLX;
		for (int i=0; i<PLX; ++i) {
			for (int j=0; j<PLX; ++j) {
				if (!p.GetGrid(i,j)) continue;
				LeftMost = min(LeftMost,j);
				RightMost = max(RightMost,j);
				UpMost = max(UpMost,i);
				DownMost = min(DownMost,i);
			}
		}
		if (UpMost<0) return make_pair(0,0);
		int PieceMid=(LeftMost+RightMost)>>1, BoardMid=(LY+1)>>1;
		return make_pair(min(LX-(UpMost-DownMost),max(SX-UpMost,SX-DownMost-1)),BoardMid-PieceMid);
	}
	bool CheckOut(int x, int y) {
		return (((x<1)||(y<1))||((x>LX)||(y>LY)));
	}
	bool CheckGrid(int x, int y) {
		if (((x<1)||(y<1))||((x>LX)||(y>LY))) return true;
		return ((B[x][y])&&(!C[x][y])); 
	}
	bool CheckPlace(Piece &p, int x, int y) {
		int LX = p.GetLX();
		for (int i=0; i<LX; ++i) {
			for (int j=0; j<LX; ++j) {
				if ((p.GetGrid(i,j))&&(CheckGrid(x+i,y+j))) return false;
			}
		}
		return true;
	}
	bool PlacePiece(Piece &p, int x, int y) {
		if (!CheckPlace(p,x,y)) return false;
		for (int i=1; i<=LX; ++i) {
			for (int j=1; j<=LY; ++j) {
				if (C[i][j]) {
					B[i][j]=0; C[i][j]=false;
				}
			}
		}
		int LX = p.GetLX();
		for (int i=0; i<LX; ++i) {
			for (int j=0; j<LX; ++j) {
				int val = p.GetGrid(i,j);
				if (val) {
					C[x+i][y+j]=true; B[x+i][y+j]=val;
				}
			}
		}
		Cur = p;
		PosX=x; PosY=y;
		return true;
	}
	bool CheckSpawn(Piece &_p, int PosInc=0, bool Clutch=false) {
		Piece p=_p; p.SetDir(DefaultDir);
		pair<int,int> Place = SpawnPlace(p);
		if (!Clutch) return CheckPlace(p,Place.first+PosInc,Place.second);
		for (int i=0; i<=min(LX,LX-SX+6); ++i) {
			if (CheckPlace(p,Place.first+PosInc+i,Place.second)) return true;
		}
		return false;
	}
	bool Spawn(Piece &_p, int PosInc=0, int Dir=-1, bool Clutch=false) {
		Piece p=_p; p.SetDir((Dir<0)?DefaultDir:Dir);
		pair<int,int> Place = SpawnPlace(p);
		Place.first += PosInc;
		bool f = PlacePiece(p,Place.first,Place.second);
		if ((!f)&&(Clutch)) {
			for (int i=1; i<=min(LX,LX-SX+6); ++i) {
				f = PlacePiece(p,Place.first+i,Place.second);
				if ((f)&&(CurClutch)) {
					AddNotice("Clutch +"+IToS(i),0xFDD000,0.7,0); break;
				}
			}
		}
		if (f) {
			Chain = 0;
			UpdateLevel(); GravityCD=GravityDelay;
			RotSta=false; LastKick=make_pair(0,0); HalfDown=false;
			PDth=PDx=PDy=0.0; FallTime=0; ResetRem=ResetLim;
		}
		return f;
	}
	bool CheckActive() {
		for (int i=1; i<=LX; ++i) {
			for (int j=1; j<=LY; ++j) {
				if (C[i][j]) return true;
			}
		}
		return false;
	}
	bool CheckUp() {
		bool f = false;
		for (int i=1; i<=LX; ++i) {
			for (int j=1; j<=LY; ++j) {
				if (!C[i][j]) continue;
				if (CheckGrid(i+1,j)) return false;
				f = true;
			}
		}
		return f;
	}
	bool CheckDown() {
		bool f = false;
		for (int i=1; i<=LX; ++i) {
			for (int j=1; j<=LY; ++j) {
				if (!C[i][j]) continue;
				if (CheckGrid(i-1,j)) return false;
				f = true;
			}
		}
		return f;
	}
	bool CheckLeft() {
		bool f = false;
		for (int i=1; i<=LX; ++i) {
			for (int j=1; j<=LY; ++j) {
				if (!C[i][j]) continue;
				if (CheckGrid(i,j-1)) return false;
				f = true;
			}
		}
		return f;
	}
	bool CheckRight() {
		bool f = false;
		for (int i=1; i<=LX; ++i) {
			for (int j=1; j<=LY; ++j) {
				if (!C[i][j]) continue;
				if (CheckGrid(i,j+1)) return false;
				f = true;
			}
		}
		return f;
	}
	bool MoveUp() {
		if (!CheckUp()) return false;
		bool f = true;
		for (int i=LX; i>1; --i) {
			for (int j=1; j<=LY; ++j) {
				if (C[i-1][j]) {
					f = true;
					B[i][j]=B[i-1][j]; C[i][j]=true;
					B[i-1][j]=0; C[i-1][j]=false;
				}
			}
		}
		if (SmoothLevel>=3) --PDx;
		++PosX; RotSta=false; return f;
	}
	bool MoveDown() {
		if (!CheckDown()) return false;
		bool f = true;
		for (int i=1; i<LX; ++i) {
			for (int j=1; j<=LY; ++j) {
				if (C[i+1][j]) {
					f = true;
					B[i][j]=B[i+1][j]; C[i][j]=true;
					B[i+1][j]=0; C[i+1][j]=false;
				}
			}
		}
		--PosX;
		RotSta=false; return f;
	}
	int DeepDrop() {
		int p = 0;
		for (int d=1; d<=LX; ++d) {
			bool f = true;
			for (int i=1; i<=LX; ++i) {
				for (int j=1; j<=LY; ++j) {
					if (!C[i][j]) continue;
					if (CheckGrid(i-d,j)) {
						f=false; break;
					}
				}
				if (!f) break;
			}
			if (f) {
				p=d; break;
			}
		}
		if (!p) return 0;
		for (int i=1; i<=LX; ++i) {
			for (int j=1; j<=LY; ++j) {
				if (!C[i][j]) continue;
				B[i-p][j]=B[i][j]; B[i][j]=0;
				C[i-p][j]=true; C[i][j]=false;
			}
		}
		if (!TargetSta) TScore+=ll(p);
		PosX-=p; if (SmoothLevel>=3) PDx+=p;
		HalfDown=true; RotSta=false;
		return p;
	}
	bool MoveLeft() {
		if (!CheckLeft()) return false;
		AddMoveParticle(-1);
		bool f = true;
		for (int i=1; i<=LX; ++i) {
			for (int j=1; j<LY; ++j) {
				if (C[i][j+1]) {
					f = true;
					B[i][j]=B[i][j+1]; C[i][j]=true;
					B[i][j+1]=0; C[i][j+1]=false;
				}
			}
		}
		if (SmoothLevel>=3) ++PDy;
		--PosY; RotSta=false; return f;
	}
	bool MoveRight() {
		if (!CheckRight()) return false;
		bool f = true;
		AddMoveParticle(1);
		for (int i=1; i<=LX; ++i) {
			for (int j=LY; j>1; --j) {
				if (C[i][j-1]) {
					f = true;
					B[i][j]=B[i][j-1]; C[i][j]=true;
					B[i][j-1]=0; C[i][j-1]=false;
				}
			}
		}
		if (SmoothLevel>=3) --PDy;
		++PosY; RotSta=false; return f;
	}
	int RotateToX = -10;
	bool CheckRotate(int Rot) {
		if (!CheckActive()) return false;
		int Dir=Cur.GetDir(), Dest=(Dir+Rot)&3;
		if (Dir==Dest) return true;
		const vector<pair<int,int> > &Kick = GetKickTable(Dir,Dest,Cur.GetRealName(),RotateSystem);
		Piece NewP=Cur; NewP.SetDir(Dest);
		RotateToX = -10;
		for (pair<int,int> o : Kick) {
			if (CheckPlace(NewP,PosX+o.second,PosY+o.first)) {
				RotateToX=PosX+o.second; return true;
			}
		}
		return false;
	}
	void AddB2BParticle(int clr, int Lev=1) {
		if (!ShowParticle) return;
		for (int k=0; k<Lev; ++k) {
			for (int i=1; i<=SX; ++i) {
				int j = 0;
				double sz=0.2+randf()*0.1, th=randf()*2.0*acos(-1.0);
				double d=randf(), x=SX-i+0.5+cos(th)*d, y=j-0.5+sin(th)*d;
				AddParticle(0, 4, clr, sz*0.5, sz*1.5,
				x, y, cos(th), sin(th), 0, 0, randf()*acos(-1.0), 0.3);
				j = LY+1;
				sz=0.2+randf()*0.1; th=randf()*2.0*acos(-1.0);
				d=randf(); x=SX-i+0.5+cos(th)*d; y=j-0.5+sin(th)*d;
				AddParticle(0, 4, clr, sz*0.5, sz*1.5,
				x, y, cos(th), sin(th), 0, 0, randf()*acos(-1.0), 0.3);
			}
			for (int j=1; j<=LY; ++j) {
				int i = 0;
				double sz=0.2+randf()*0.1, th=randf()*2.0*acos(-1.0);
				double d=randf(), x=SX-i+0.5+cos(th)*d, y=j-0.5+sin(th)*d;
				AddParticle(0, 4, clr, sz*0.5, sz*1.5,
				x, y, cos(th), sin(th), 0, 0, randf()*acos(-1.0), 0.3);
				i = SX+1;
				sz=0.2+randf()*0.1; th=randf()*2.0*acos(-1.0);
				d=randf(); x=SX-i+0.5+cos(th)*d; y=j-0.5+sin(th)*d;
				AddParticle(0, 4, clr, sz*0.5, sz*1.5,
				x, y, cos(th), sin(th), 0, 0, randf()*acos(-1.0), 0.3);
			}
		}
	}
	void AddSpinParticle(int Lev) {
		if (!ShowParticle) return;
		for (int i=1; i<=LX; ++i) {
			for (int j=1; j<=LY; ++j) {
				if (!C[i][j]) continue;
				for (int k=0; k<Lev; ++k) {
					double sz=0.2+randf()*0.1, th=randf()*2.0*acos(-1.0);
					double d=randf(), x=SX-i+0.5+cos(th)*d, y=j-0.5+sin(th)*d;
					AddParticle(0, 4, Mix(0xFFFFFF,Cur.GetColor(),0.3), sz, sz*3.0,
					x, y, cos(th), sin(th), 0, 0, randf()*acos(-1.0), 0.3);
				}
			}
		}
	}
	void AddMoveParticle(int dir) {
		if (!ShowParticle) return;
		if ((Bot)&&(DropDelay>0)) {
			bool f = false;
			for (int i=1; i<=LX; ++i) {
				for (int j=1; j<=LY; ++j) {
					if (C[i][j]) f|=CheckGrid(i+1,j+dir)|CheckGrid(i+1,j);
				}
			}
			if (!f) return;
		}
		for (int i=1; i<=LX; ++i) {
			for (int j=1; j<=LY; ++j) {
				if ((!C[i][j])||((!CheckGrid(i-1,j))&&(!CheckGrid(i-1,j+dir)))||
				((C[i][j-dir])&&((CheckGrid(i-1,j-dir))||(CheckGrid(i-1,j))))) continue;
				for (int k=0; k<3; ++k) {
					double sz = 0.15;
					double th=(randf()-0.5), x=SX-i+1, y=j-1+randf();
					AddParticle(0, 3, Mix(0xFFFFFF,Cur.GetColor(),0.5), sz, sz*1.5,
					x, y, -sin(th)*8, -double(dir)*cos(th)*8, -sin(th)*4, -double(dir)*cos(th)*4, randf()*acos(-1.0), 0.3);
				}
			}
		}
	}
	void AddDropParticle() {
		if (!ShowParticle) return;
		for (int i=1; i<=LX; ++i) {
			for (int j=1; j<=LY; ++j) {
				if (!C[i][j]) continue;
				int cnt = rand()&1;
				for (int k=0; k<cnt; ++k) {
					double sz=0.1+randf()*0.05, th=randf()*2.0*acos(-1.0);
					double d=randf(), x=SX-i+0.5+cos(th)*d, y=j-0.5+sin(th)*d;
					AddParticle(0, 3, Mix(0xFFFFFF,Cur.GetColor(),0.3), sz, sz*2,
					x, y, -3, 0, 0, 0, randf()*acos(-1.0), 0.3);
				}
			}
		}
	}
	bool Rotate(int Rot, const Piece Tar=P_Empty) {
		if (!CheckActive()) return false;
		int Dir=Cur.GetDir(), Dest=(Dir+Rot)&3;
		const vector<pair<int,int> > &Kick = GetKickTable(Dir,Dest,Cur.GetRealName(),RotateSystem);
		Piece NewP = Cur;
		if (Tar.GetHash()!=P_Empty.GetHash()) NewP=Tar;
		NewP.SetDir(Dest);
		for (pair<int,int> o : Kick) {
			if (CheckPlace(NewP,PosX+o.second,PosY+o.first)) {
				int del = o.second-GetShift(Dest,Cur.GetRealName(),RotateSystem).second
				+GetShift(Dir,Cur.GetRealName(),RotateSystem).second;
				if ((!Bot)&&(del>0)) {
					if (ResetRem) {
						if (ResetLim<Max) --ResetRem;
					}
					else return false;
				}
				PlacePiece(NewP, PosX+o.second, PosY+o.first);
				if (!RotSta) HalfDown=false;
				RotSta=true; LastKick=make_pair(o.second,o.first);
				if (((o.second<0)||((!o.second)&&(o.first)&&(!Dir)))&&(Rot==2)&&((Dir!=2)||(IsOType(Cur.GetRealName()))||(IsStrictHalfSame(Cur.GetRealName())))) HalfDown=true;
				else if ((Rot!=2)&&(!IsOType(Cur.GetRealName()))) HalfDown=false;
				int Lev = GetSpinLevel(Cur);
				if (Lev) AddSpinParticle(Lev);
				if (SmoothLevel>=2) {
					PDx-=o.second; PDy-=o.first;
					if (((Dir+1)&3)==Dest) PDth+=acos(-1.0)*0.5;
					if (((Dir+3)&3)==Dest) PDth-=acos(-1.0)*0.5;
					if (((Dir+2)&3)==Dest) {
						if (DefaultRot==1) PDth+=acos(-1.0);
						else PDth-=acos(-1.0);
					}
				}
				return true;
			}
		}
		return false;
	}
	inline bool IsSpinBonus(const string &Name) {
		return (Name=="T");
	}
	vector<pair<int,int> > CPos;
	bool GarbFuse = false;
	void Lock() {
		GarbFuse=false; TgD=-1; TgX=0;
		int d = Cur.GetDir();
		Lst=Cur; LastLock=0;
		CPos.clear();
		SpinSta = ((!CheckUp())&&(!CheckDown())&&(!CheckLeft())&&(!CheckRight()));
		vector<int> BombLines;
		int cU = NewUID(); 
		for (int i=1; i<=LX; ++i) {
			for (int j=1; j<=LY; ++j) {
				if (C[i][j]) {
					CPos.push_back(make_pair(i,j));
					C[i][j]=false; Age[i][j]=500; U[i][j]=cU;
					int p = i-1;
					while ((p>0)&&((B[p][j]==GarbLock)||(B[p][j]==GarbBomb))) {
						if (B[p][j]==GarbLock) {
							B[p][j] = Garb;
							for (int k=0; k<2; ++k) {
								double sz=0.2+randf()*0.1, th=randf()*2.0*acos(-1.0);
								double d=randf(), x=SX-p+0.5+cos(th)*d, y=j-0.5+sin(th)*d;
								AddParticle(0, 4, abs(GarbLock), sz, sz*3.0,
								x, y, cos(th), sin(th), 0, 0, randf()*acos(-1.0), 0.3);
							}
						}
						else BombLines.push_back(p);
						--p; GarbFuse=true;
					}
				}
			}
		}
		sort(BombLines.begin(),BombLines.end()); int pre=-1;
		for (int i : BombLines) {
			if (pre==i) continue;
			pre = i;
			for (int j=1; j<=LY; ++j) {
				if (B[i][j]!=Garb) {
					B[i][j] = Garb;
					for (int k=0; k<2; ++k) {
						double sz=0.2+randf()*0.1, th=randf()*2.0*acos(-1.0);
						double d=randf(), x=SX-i+0.5+cos(th)*d, y=j-0.5+sin(th)*d;
						AddParticle(0, 4, abs(GarbBomb), sz, sz*3.0,
						x, y, cos(th), sin(th), 0, 0, randf()*acos(-1.0), 0.3);
					}
				}
			}
		}
		return;
	}
	void Waste() {
		SpinSta = false;
		CPos.clear();
		for (int i=1; i<=LX; ++i) {
			for (int j=1; j<=LY; ++j) {
				if (C[i][j]) {
					C[i][j]=false; B[i][j]=0;
				}
			}
		}
		Cur = P_Empty;
		return;
	}
	void HardDrop() {
		while (CheckDown()) {
			AddDropParticle(); MoveDown(); if (!TargetSta) TScore+=2LL;
		}
		Lock();
	}
	int SonicDrop(bool HaveScore=false) {
		int res = 0;
		while (CheckDown()) {
			if ((!Bot)&&(HaveScore)) AddDropParticle();
			MoveDown(); ++res; if ((HaveScore)&&(!TargetSta)) ++TScore;
		}
		return res;
	}
	void ClearBoard(bool Eff=false) {
		for (int i=1; i<=LX; ++i) {
			for (int j=1; j<=LY; ++j) {
				//if (!C[i][j]) continue;
				if ((B[i][j])&&(!C[i][j])&&(Eff)) {
					double th = (randf()*2.0-1.0)*acos(-1.0)*0.1;
					double v = 15.0;
					int clr = Mix(0xFFFFFF,abs(B[i][j]),0.2);
					AddParticle(0, 8, clr, 0.15, 0,
					SX-i+0.5, j-0.5, -v*cos(th), v*sin(th), 60, 0,
					randf()*2.0*acos(-1.0), 0.1);
				}
				B[i][j]=Age[i][j]=U[i][j]=0; C[i][j]=false;
			}
		}
	}
	bool IsUnclearable(int x) {
		return ((x==Solid)||(x==GarbZone)||(x==GarbRound)||(x==GarbLock)||(x==GarbBomb)||((x>=GarbQuest)&&(x<=GarbQuest+20)));
	}
	int DigVal, CurZone=0;
	int ZonePreClearLines() {
		int Lines = 0;
		for (int i=1; i<=LX; ++i) {
			bool f=true, g=false;
			for (int j=1; j<=LY; ++j) {
				if (!B[i][j]) f=false;
				if (IsUnclearable(B[i][j])) f=false;
				if (B[i][j]<0) g=true;
			}
			if (f) ++Lines;
		}
		int FullPos=0, EmptyPos=Lines;
		for (int i=1; i<=LX; ++i) {
			bool f=true, g=false;
			for (int j=1; j<=LY; ++j) {
				if (!B[i][j]) f=false;
				if (IsUnclearable(B[i][j])) f=false;
				if (B[i][j]<0) g=true;
			}
			if (f) FallDest[i]=++FullPos;
			else FallDest[i]=++EmptyPos;
		}
		return Lines;
	}
	int PreClearLines() {
		if (CurZone) return ZonePreClearLines();
		int res=0, p=0;
		int Dig = 0;
		vector<int> Place;
		for (int i=1; i<=LX; ++i) {
			bool f=true, g=false;
			for (int j=1; j<=LY; ++j) {
				if (!B[i][j]) f=false;
				if (IsUnclearable(B[i][j])) f=false;
				if (B[i][j]<0) g=true;
			}
			if ((f)&&(i>RoundHeight)) {
				++res; if (g) ++Dig;
				Place.push_back(i);
				FallDest[i] = 0;
				if (GravityType!=GV_Default) ++p;
			}
			else {
				if (f) ++res;
				FallDest[i] = ++p;
			}
		}
		for (int i : Place) {
			for (int j=1; j<=LY; ++j) {
				double th = (randf()*2.0-1.0)*acos(-1.0)*0.1;
				double v = 15.0;
				int clr = Mix(0xFFFFFF,abs(B[i][j]),0.2);
				AddParticle(LCD*j/LY, 8, clr, 0.15, 0,
				SX-i+0.5, j-0.5, -v*cos(th), v*sin(th), 60, 0,
				randf()*2.0*acos(-1.0), 0.1);
			}
		}
		if (!TargetSta) TDig+=Dig;
		DigVal = Dig;
		return res;
	}
	int ZoneClearLines() {
		int res = 0;
		ReallocateUID();
		int cU = NewUID();
		for (int i=1; i<=LX; ++i) {
			bool f = true;
			for (int j=1; j<=LY; ++j) {
				if (!B[i][j]) f=false;
				else U[i][j]+=res*1000000;
				if (IsUnclearable(B[i][j])) f=false;
			}
			if (f) {
				++res;
				for (int j=1; j<=LY; ++j) {
					B[i][j]=GarbZone; Age[i][j]=500; U[i][j]=cU;
				}
				if (i==res) continue;
				for (int j=1; j<=LY; ++j) {
					B[0][j]=B[i][j]; Age[0][j]=Age[i][j]; U[0][j]=U[i][j];
				}
				for (int ii=i; ii>res; --ii) {
					for (int j=1; j<=LY; ++j) {
						B[ii][j]=B[ii-1][j]; Age[ii][j]=Age[ii-1][j]; U[ii][j]=U[ii-1][j];
					}
				}
				for (int j=1; j<=LY; ++j) {
					B[res][j]=B[0][j]; Age[res][j]=Age[0][j]; U[res][j]=U[0][j];
					B[0][j]=Age[0][j]=U[0][j]=0;
				}
			}
		}
		ReallocateUID();
		return res;
	}
	void UDfs(int x, int y, int u, int nw) {
		if (((x<1)||(y<1))||((x>LX)||(y>LY))) return;
		if ((!B[x][y])||(C[x][y])||(U[x][y]!=u)) return;
		U[x][y] = nw;
		UDfs(x-1,y,u,nw); UDfs(x+1,y,u,nw);
		UDfs(x,y-1,u,nw); UDfs(x,y+1,u,nw);
	}
	void ReallocateUID(int beg=0) {
		const int D = 1000000000;
		PlaceUID = 0;
		for (int i=beg+1; i<=LX; ++i) {
			for (int j=1; j<=LY; ++j) {
				if (U[i][j]) U[i][j]+=D;
			}
		}
		if (!beg) UID=1;
		for (int i=beg+1; i<=LX; ++i) {
			for (int j=1; j<=LY; ++j) {
				if (U[i][j]<D) continue;
				UDfs(i, j, U[i][j], NewUID());
			}
		}
	}
	int ClearLines() {
		if (CurZone) return ZoneClearLines();
		ReallocateUID();
		int res=0, p=0;
		for (int i=1; i<=LX; ++i) {
			bool f = true;
			for (int j=1; j<=LY; ++j) {
				if (!B[i][j]) f=false;
				else U[i][j]+=res*1000000;
				if (IsUnclearable(B[i][j])) f=false;
			}
			if ((f)&&(i>RoundHeight)) {
				++res;
				if (GravityType!=GV_Default) {
					++p;
					for (int j=1; j<=LY; ++j) B[p][j]=Age[p][j]=U[p][j]=0;
				}
			}
			else {
				++p;
				if (p!=i) {
					for (int j=1; j<=LY; ++j) {
						B[p][j]=B[i][j]; B[i][j]=0;
						Age[p][j]=Age[i][j]; U[p][j]=U[i][j];
						Age[i][j]=U[i][j]=0;
					}
				}
				if (f) {
					++res;
					for (int j=1; j<=LY; ++j) B[i][j]=GarbRound;
				}
			}
		}
		for (int i=p+1; i<=LX; ++i) {
			for (int j=1; j<=LY; ++j) B[i][j]=Age[i][j]=U[i][j]=0;
		}
		ReallocateUID();
		return res;
	}
	bool CheckAllClear() {
		for (int i=1; i<=LX; ++i) {
			bool f=true, g=false;
			for (int j=1; j<=LY; ++j) {
				if (B[i][j]) g=true;
				else f=false;
			}
			if ((g)&&(!f)) return false;
		}
		return true;
	}
	void AttackSeq(int x) {
		if (x<1) return;
		AttackOut.push_back(x);
	}
	int Adj4C(int x, int y) {
		int r = 0;
		for (int ii=-1; ii<=1; ++ii) {
			for (int jj=-1; jj<=1; ++jj) {
				if (abs(ii)+abs(jj)!=1) continue;
				if (C[x+ii][y+jj]) ++r;
			}
		}
		return r;
	}
	int Adj4PC(int x, int y) {
		int r = 0;
		for (int ii=-1; ii<=1; ii+=2) {
			for (int jj=-1; jj<=1; jj+=2) {
				if (C[x+ii][y+jj]) ++r;
			}
		}
		return r;
	}
	int Adj8C(int x, int y) {
		int r = 0;
		for (int ii=-1; ii<=1; ++ii) {
			for (int jj=-1; jj<=1; ++jj) {
				if ((!ii)&&(!jj)) continue;
				if (C[x+ii][y+jj]) ++r;
			}
		}
		return r;
	}
	int Adj4B(int x, int y) {
		int r = 0;
		for (int ii=-1; ii<=1; ++ii) {
			for (int jj=-1; jj<=1; ++jj) {
				if (abs(ii)+abs(jj)!=1) continue;
				if ((CheckGrid(x+ii,y+jj))||(C[x+ii][y+jj])) ++r;
			}
		}
		return r;
	}
	int Adj4PB(int x, int y) {
		int r = 0;
		for (int ii=-1; ii<=1; ii+=2) {
			for (int jj=-1; jj<=1; jj+=2) {
				if ((CheckGrid(x+ii,y+jj))||(C[x+ii][y+jj])) ++r;
			}
		}
		return r;
	}
	int Adj8B(int x, int y) {
		int r = 0;
		for (int ii=-1; ii<=1; ++ii) {
			for (int jj=-1; jj<=1; ++jj) {
				if ((!ii)&&(!jj)) continue;
				if ((CheckGrid(x+ii,y+jj))||(C[x+ii][y+jj])) ++r;
			}
		}
		return r;
	}
	int DefaultSpinLevel(Piece p) {
		if (SpinSta) {
			if (IsOType(p.GetName())) {
				int tot = 0;
				for (int i=1; i<LX; ++i) {
					for (int j=1; j<=LY; ++j) {
						if ((C[i][j])&&(B[i+1][j])&&(!C[i+1][j])) ++tot;
					}
				}
				return (tot>=2)?3:2;
			}
			if (HalfDown) return 2;
			bool ex=false, sw=false, ew=false;
			for (int i=1; i<=LX; ++i) {
				for (int j=1; j<=LY; ++j) {
					if ((C[i][j])&&(Adj4B(i,j)<=1)) return 2;
					if ((C[i][j])&&(Adj4B(i,j)<=2)) ex=true;
					if ((!B[i][j])&&(Adj4C(i,j)>=2)) sw=true;
					if ((!C[i][j])&&(Adj4C(i,j)>=2)) ew=true;
				}
			}
			if ((ex)&&((sw)||(!ew))) return 2;
			return 3;
		}
		if (RotSta) {
			int tot=0, cnt=0;
			for (int i=1; i<=LX; ++i) {
				for (int j=1; j<=LY; ++j) {
					if ((C[i][j])&&(Adj4C(i,j)>=2)&&(Adj8C(i,j)>=3)) {
						int v = Adj8B(i,j);
						if (v<6) return 0;
						++tot;
						if (Adj8B(i,j)>=7) ++cnt;
					}
				}
			}
			if ((cnt)&&(cnt>=tot-1)) return 1;
			else if ((IsSpinBonus(p.GetName()))&&(CornerCheck()>=3)) return 1;
		}
		return 0;
	}
	int CornerCheck() {
		int res=0, mx=0;
		for (int i=1; i<=LX; ++i) {
			for (int j=1; j<=LY; ++j) {
				if (!C[i][j]) continue;
				mx = max(mx,Adj8C(i,j));
			}
		}
		for (int i=1; i<=LX; ++i) {
			for (int j=1; j<=LY; ++j) {
				if (!C[i][j]) continue;
				if (Adj8C(i,j)!=mx) continue;
				res = max(res,Adj4PB(i,j));
			}
		}
		return res;
	}
	int GetSpinLevelP(Piece p) {
		if (p.GetHash()==P_Empty.GetHash()) return 0;
		SpinSta = ((!CheckUp())&&(!CheckDown())&&(!CheckLeft())&&(!CheckRight()));
		if (CheckDown()) return 0;
		if ((SpinSta)&&(Mod8)) return 3;
		if ((!RotSta)&&(!SpinSta)) return 0;
		if (SpinRule==SR_Easy) return 3;
		string Name = p.GetRealName();
		if (SpinRule==SR_None) return 0;
		if (SpinRule==SR_Default) {
			int r = DefaultSpinLevel(p);
			if (!IsSpinBonus(p.GetName())) {
				if (r==1) r=0;
			}
			return r;
		}
		if (SpinRule==SR_AllSpin) {
			int r = DefaultSpinLevel(p);
			if (r>1) r=3;
			return r;
		}
		if (SpinRule==SR_AllSemi) {
			int r = DefaultSpinLevel(p);
			if (!IsSpinBonus(p.GetName())) {
				if (r>1) r=2;
				else r=0;
			}
			return r;
		}
		if (SpinRule==SR_AllMini) {
			int r = DefaultSpinLevel(p);
			if (!IsSpinBonus(p.GetName())) {
				if (r>1) r=1;
				else r=0;
			}
			return r;
		}
		if (SpinRule==SR_IO) {
			int r = DefaultSpinLevel(p);
			if (!IsSpinBonus(p.GetName())) {
				if (r>1) r=1;
				else r=0;
			}
			else if (r==2) r=1;
			return r;
		}
		if (SpinRule==SR_Guildline) {
			if (!RotSta) return 0;
			if (!IsSpinBonus(p.GetName())) return 0;
			bool f = true;
			for (int i=1; i<=LX; ++i) {
				for (int j=1; j<=LY; ++j) {
					if ((C[i][j])&&(Adj4C(i,j)>2)) {
						int d = Adj4PB(i,j);
						if (d<3) return 0;
					}
					if ((!B[i][j])&&(Adj4C(i,j)>1)) f=false;
				}
			}
			return ((f)||((abs(LastKick.first)>=2)&&(abs(LastKick.second)>=1)))?3:1;
		}
		return 0;
	}
	int GetSpinLevel(Piece p) {
		int r = GetSpinLevelP(p);
		if (Mod2==2) r=min(r,1);
		return r;
	}
	void AddAttackText(int Attack, int clro, int clri, double cpx, double cpy, bool Spin, double bd=1.0) {
		if ((!ShowAttack)&&(!ForceShowAttack)) return;
		double sz = sqrt(double(min(Attack,15)+1));
		double x=SX-cpx+1-(randf()-0.8)*6.0, y=-sz;
		double v=sz*0.6, th=randf()*0.5;
		if (TPiece&1) y=LY+sz;
		double rot = (randf()-0.5)*0.2;
		double rr = (Spin)?((y<0)?-0.05:0.05):0;
		double vx=v*sin(th), vy=v*cos(th)*((y<0)?-1:1);
		AddFloatText(IToS(Attack), clro, sz, 0, 0.3*bd, 0.075,
		x, y, vx, vy, -vx*0.25, -vy*0.25, rot, rr);
		AddFloatText(IToS(Attack), clri, sz, 0, 0.2*bd, 0.05,
		x, y, vx, vy, -vx*0.25, -vy*0.25, rot, rr);
	}
	int BaseScore(int Lines, int SpinLev) {
		vector<int> List = {0,100,300,500,800,1600,3000};
		if (SpinLev==1) List={100,200,600,1000,1800,3200};
		if (SpinLev==2) List={200,400,1000,1600,2600,4000};
		if (SpinLev==3) List={400,800,1200,1600,2600,4000};
		if (Lines<0) return 0;
		if (Lines>=List.size()) return List.back()+1000*(Lines-List.size()+1);
		return List[Lines];
	}
	int GetBaseAttack(int Lines) {
		if (!Lines) return 0;
		if (BaseRule==BSR_Default) return Lines-(Lines<4);
		if (BaseRule==BSR_Equal) return Lines;
		if (BaseRule==BSR_Classic) return (Lines<3)?0:Lines-1;
		if (BaseRule==BSR_ASC) return max(0,Lines-1);
		if (BaseRule==BSR_Arcade) return (Lines<2)?0:Lines;
		return 0;
	}
	double GetComboAttack(int Combo) {
		if (ComboRule==CR_Default) {
			if (Combo<2) return 0;
			return log(1.0+1.25*Combo);
		}
		vector<int> lst;
		if (ComboRule==CR_Guildline) lst={2,4,6,8,11};
		if (ComboRule==CR_Friends) lst={2,5,5,7,12};
		if (ComboRule==CR_Expert) lst={2,5,7,9,12};
		if (ComboRule==CR_Battle) lst={1,3,5,7};
		if (ComboRule==CR_99) lst={1,3,5,7,10};
		if (ComboRule==CR_PPT) lst={2,4,6,8,13};
		if (ComboRule==CR_Effect) lst={2,4,7,13};
		if (ComboRule==CR_Zone) lst={2,4,12};
		if (ComboRule==CR_C2) lst={1,4,6,7,7,8,8,9,9,10};
		if (ComboRule==CR_Techmino) lst={3};
		if (ComboRule==CR_Classic) lst={1};
		double res = 0.0;
		for (int x : lst) {
			if (Combo>=x) ++res;
		}
		return res;
	}
	double GetB2BAttack(int B2B) {
		if (B2B<1) return 0;
		if (B2BRule==BR_Chain) {
			if (B2B==1) return 1;
			double p = 1.0+log(0.8*B2B+1);
			return floor(p)+(1.0+p-floor(p))/3.0;
		}
		return 1;
	}
	double GetMultiplerComboAttack(int Combo, double Attack, int Lines) {
		if (ComboRule==CR_Default) Attack*=(1.0+max(0,Combo)*0.25);
		if (ComboRule==CR_Techmino) {
			Combo = min(12,Combo);
			if (Lines>1) Attack*=(1.0+max(0,Combo)*0.25);
			else Attack*=(1.0+max(0,Combo)*0.15);
			if (Combo>=3) ++Attack;
		}
		return Attack;
	}
	void ChangeSize(int _LX, int _LY, int _SX, int FillStart=1) {
		ClearSearchProcess();
		vector<vector<pair<int,int> > > o(LX+2,vector<pair<int,int> >(LY+2));
		int OLX=LX, OLY=LY; 
		for (int i=1; i<=LX; ++i) {
			for (int j=1; j<=LY; ++j) {
				if ((B[i][j])&&(!C[i][j])) o[i][j]=make_pair(B[i][j],U[i][j]);
			}
		}
		for (int i=0; i<=LX+1; ++i) {
			delete[] B[i]; delete[] C[i]; delete[] Age[i]; delete[] U[i];
		}
		delete[] B; delete[] C;
		delete[] Age; delete[] FallDest; delete[] U;
		LX=_LX; LY=_LY; SX=_SX;
		B = new int*[LX+2];
		C = new bool*[LX+2];
		Age = new int*[LX+2];
		U = new int*[LX+2];
		FallDest = new int[LX+2];
		for (int i=0; i<=LX+1; ++i) {
			FallDest[i] = 0;
			B[i] = new int[LY+2];
			C[i] = new bool[LY+2];
			Age[i] = new int[LY+2];
			U[i] = new int[LY+2];
			for (int j=0; j<=LY+1; ++j) {
				Age[i][j]=B[i][j]=U[i][j]=0; C[i][j]=false;
			}
		}
		for (int i=1; i<=min(LX,OLX); ++i) {
			int Fill = 0;
			for (int j=1; j<=OLY; ++j) {
				if (o[i][j].first==Solid) {
					Fill=Solid; break;
				}
				if (o[i][j].first==Garb) Fill=Garb;
			}
			if (Fill) {
				for (int j=1; j<=LY; ++j) {
					B[i][j]=Fill; U[i][j]=NewUID();
				}
			}
			for (int j=1; j<=OLY; ++j) {
				int nj=FillStart+j-1; if ((nj<1)||(nj>LY)) continue;
				if (o[i][j].first) {
					B[i][nj]=o[i][j].first; U[i][nj]=o[i][j].second;
					Age[i][nj] = 500;
				}
				else B[i][nj]=U[i][nj]=0;
			}
		}
		for (GarbageLayer &o : AttackIn) {
			vector<int> New(LY+2);
			bool f = false;
			for (int x : o.B) {
				if (x==Solid) f=true;
			}
			for (int j=1; j<=LY; ++j) New[j]=(f)?Solid:Garb;
			for (int j=1; j<=OLY; ++j) {
				int nj=FillStart+j-1; if ((nj<1)||(nj>LY)) continue;
				New[nj] = o.B[j];
			}
			o.B = New;
		}
	}
	bool ReadHistoryMsg = false;
	#define WriteIn(x) o.x=x
	BoardHistory WriteHistory() {
		BoardHistory o;
		WriteIn(SX); WriteIn(LX); WriteIn(LY);
		o.B = vector<vector<int> >(LX+2,vector<int>(LY+2));
		o.U = vector<vector<int> >(LX+2,vector<int>(LY+2));
		for (int i=1; i<=LX; ++i) {
			for (int j=1; j<=LY; ++j) {
				o.B[i][j]=B[i][j]; o.U[i][j]=U[i][j]; if (C[i][j]) o.B[i][j]=o.U[i][j]=0;
			}
		}
		WriteIn(Gen); WriteIn(Hold); WriteIn(Next); WriteIn(TargetSta);
		WriteIn(AttackIn); WriteIn(TPiece);
		WriteIn(TLine); WriteIn(TDig); WriteIn(TAttack);
		WriteIn(TAC); WriteIn(Combo); WriteIn(B2B); WriteIn(CurClutch);
		WriteIn(TScore); WriteIn(Time); WriteIn(C2ComboRem);
		WriteIn(TQPScore); WriteIn(QPExp); WriteIn(QPAddExp);
		WriteIn(QPLevel); WriteIn(QPRank); WriteIn(QPExpDecCD); WriteIn(QPBlighted);
		WriteIn(CurZone); WriteIn(ZoneProgress); WriteIn(ZoneAttack); WriteIn(ZoneRem);
		return o;
	}
	#undef WriteIn
	#define WriteIn(x) x=o.x;
	void ReadHistory(BoardHistory o) {
		if (!o.LX) return;
		if ((o.SX!=SX)||(o.LX!=LX)||(o.LY!=LY)) ChangeSize(o.LX,o.LY,o.SX);
		for (int i=1; i<=LX; ++i) {
			FallDest[i] = i;
			for (int j=1; j<=LY; ++j) {
				B[i][j]=o.B[i][j]; C[i][j]=false; Age[i][j]=U[i][j]=0;
				if (B[i][j]) U[i][j]=o.U[i][j];
			}
		}
		WriteIn(Gen); WriteIn(Hold); WriteIn(Next); WriteIn(TargetSta);
		WriteIn(AttackIn); WriteIn(TPiece);
		WriteIn(TLine); WriteIn(TDig); WriteIn(TAttack);
		WriteIn(TAC); WriteIn(Combo); WriteIn(B2B); WriteIn(CurClutch);
		WriteIn(TScore); WriteIn(Time); WriteIn(C2ComboRem);
		WriteIn(TQPScore); WriteIn(QPExp); WriteIn(QPAddExp);
		WriteIn(QPLevel); WriteIn(QPRank); WriteIn(QPExpDecCD); WriteIn(QPBlighted);
		WriteIn(CurZone); WriteIn(ZoneProgress); WriteIn(ZoneAttack); WriteIn(ZoneRem);
		if ((Combo>1)||((Combo)&&(ComboRule==CR_C2))) {
			ComboAge=0; ComboText=(ComboRule==CR_C2)?Combo:Combo-1;
		}
		else {
			ComboAge=5000; ComboText=0;
		}
		if ((B2B>1)||((B2B)&&(B2BRule==BR_Techmino))) {
			B2BAge=0; B2BText=(B2BRule==BR_Techmino)?B2B:B2B-1;
		}
		else {
			B2BAge=5000; B2BText=0;
		}
		Cur = P_Empty;
		InactiveTime=InactiveTot=FallTime=0; Over=false;
		ReadHistoryMsg = true;
		TgD=-1; ClearSearchProcess();
	}
	#undef WriteIn
	#warning Board::AREProcess
	int LastAttack = 0;
	void AddNotice(string str, int clr, double sz=1.0, double xp=0.25) {
		AddFloatText(str, clr, sz, 0, 0.15, 0.05, SX*xp, LY*0.5, 0, 0, 0, 0, 0, 0, 1.5, 0.5);
	}
	void ClearVanish() {
		if (VanishHeight<1) return;
		for (int i=VanishHeight; i<=LX; ++i) {
			for (int j=1; j<=LY; ++j) {
				if (!B[i][j]) continue;
				double th = (randf()*2.0-1.0)*acos(-1.0)*0.1;
				double v = 15.0;
				int clr = Mix(0xFFFFFF,abs(B[i][j]),0.2);
				AddParticle(LCD*j/LY, 8, clr, 0.15, 0,
				SX-i+0.5, j-0.5, -v*cos(th), v*sin(th), 60, 0,
				randf()*2.0*acos(-1.0), 0.1);
				B[i][j]=Age[i][j]=U[i][j]=0; C[i][j]=false;
			}
		}
	}
	bool InCascade = false;
	static int V[110][110], SegTreeM[20010], SegTreeP[20010], Dis[10010];
	static vector<pair<int,int> > Graph[10010]; static bool Vis[10010];
	void CDfs(int x, int y, int u, int b, int val) {
		if (((x<1)||(y<1))||((x>LX)||(y>LY))) return;
		if ((!B[x][y])||(V[x][y])) return;
		if (((GravityType==GV_Cascade)||(GravityType==GV_Separate)||(GravityType==GV_Avalanche))&&(U[x][y]!=u)) return;
		if ((GravityType==GV_Color)&&(B[x][y]!=b)) return;
		V[x][y] = val;
		CDfs(x-1,y,u,b,val); CDfs(x+1,y,u,b,val);
		CDfs(x,y-1,u,b,val); CDfs(x,y+1,u,b,val);
	}
	void SegTreeUpdate(int x, int val, int id) {
		int u = x+id-1;
		SegTreeM[u]=val; SegTreeP[u]=x; u>>=1;
		while (u) {
			if (SegTreeM[u<<1]<=SegTreeM[u<<1|1]) {
				SegTreeM[u]=SegTreeM[u<<1]; SegTreeP[u]=SegTreeP[u<<1];
			}
			else {
				SegTreeM[u]=SegTreeM[u<<1|1]; SegTreeP[u]=SegTreeP[u<<1|1];
			}
			u >>= 1;
		}
	}
	bool CheckCascade() {
		if ((GravityType==GV_None)||(GravityType==GV_Default)) return false;
		for (int i=0; i<=LX+1; ++i) {
			for (int j=0; j<=LY+1; ++j) V[i][j]=0; 
		}
		int id = 0;
		if (GravityType==GV_Fall) {
			for (int i=1; i<=LX; ++i) {
				for (int j=1; j<=LY; ++j) {
					if (B[i][j]) V[i][j]=++id;
				}
			}
		}
		else {
			if ((GravityType==GV_Separate)||(GravityType==GV_Avalanche)) {
				bool f = false;
				for (int i=1; i<=LX; ++i) {
					bool ff = true;
					for (int j=1; j<=LY; ++j) {
						if (B[i][j]) {
							ff = false;
							if (f) U[i][j]=NewUID();
						}
					}
					if (ff) f=true;
				}
			}
			for (int i=1; i<=LX; ++i) {
				for (int j=1; j<=LY; ++j) {
					if ((B[i][j])&&(!V[i][j])) CDfs(i,j,U[i][j],B[i][j],++id);
				}
			}
		}
		++id;
		for (int i=1; i<=id; ++i) {
			Graph[i].clear(); Dis[i]=LX; Vis[i]=false;
		}
		for (int i=1; i<=(id<<1); ++i) {
			SegTreeM[i]=LX; SegTreeP[i]=0;
		}
		for (int j=1; j<=LY; ++j) {
			int pre=id, del=0;
			for (int i=1; i<=LX; ++i) {
				++del; if (!V[i][j]) continue;
				if (IsUnclearable(B[i][j])) Graph[id].push_back(make_pair(V[i][j],0));
				if (V[i][j]!=pre) Graph[pre].push_back(make_pair(V[i][j],del-1));
				pre=V[i][j]; del=0;
			}
		}
		if ((GravityType!=GV_Separate)&&(GravityType!=GV_Fall)&&(GravityType!=GV_Avalanche)) {
			for (int i=1; i<=LX; ++i) {
				int lst = -1;
				for (int j=1; j<=LY; ++j) {
					if (B[i][j]<0) {
						if ((lst!=-1)&&(V[i][j]!=lst)) {
							Graph[lst].push_back(make_pair(V[i][j],0));
							Graph[V[i][j]].push_back(make_pair(lst,0));
						}
						lst = V[i][j];
					}
				}
			}
		}
		Dis[id]=0; SegTreeUpdate(id,0,id);
		while (SegTreeM[1]<LX) {
			int u=SegTreeP[1]; Vis[u]=true; SegTreeUpdate(u,LX,id);
			for (pair<int,int> o : Graph[u]) {
				int v=o.first, w=Dis[u]+o.second;
				if ((!Vis[v])&&(w<Dis[v])) {
					Dis[v]=w; SegTreeUpdate(v,Dis[v],id);
				}
			}
		}
		bool flag = false;
		for (int i=1; i<=id; ++i) {
			if ((Dis[i])&&(Dis[i]<LX)) {
				flag=true; break;
			}
		}
		if (!flag) return false;
		ReallocateUID();
		int pos = LX+1;
		for (int i=1; i<=LX; ++i) {
			FallDest[i] = i;
			for (int j=1; j<=LY; ++j) {
				if ((V[i][j])&&(Dis[V[i][j]])) {
					Age[i][j]=-Dis[V[i][j]]; U[i][j]+=1000000*Dis[V[i][j]];
					pos = min(pos,i+Age[i][j]);
				}
			}
		}
		if (GravityType==GV_Avalanche) {
			for (int i=max(pos,1); i<=LX; ++i) {
				for (int j=1; j<=LY; ++j) {
					if (B[i][j]) U[i][j]=NewUID();
				}
			}
		}
		ReallocateUID();
		return true;
	}
	void CascadeProcess() {
		for (int i=1; i<=LX; ++i) {
			for (int j=1; j<=LY; ++j) {
				if ((!B[i][j])||(Age[i][j]>=0)) continue;
				int d=i+Age[i][j]; if (d<1) continue;
				B[d][j]=B[i][j]; Age[d][j]=500; U[d][j]=U[i][j];
				B[i][j]=Age[i][j]=U[i][j]=0;
			}
		}
	}
	void PreAREProcess(bool IsPlace=true, bool IsCascade=false) {
		ClearVanish();
		//if (IsPlace) CurZone=EnableZone; 
		int PreTLine = TLine;
		Piece p = Cur;
		Cur = P_Empty;
		int RealLines=PreClearLines(), Lines=RealLines;
		CurClutch = false;
		if ((ClutchRule==CLR_Default)&&(RealLines)) CurClutch=true;
		if (ClutchRule==CLR_Always) CurClutch=true;
		if (RealLines) ++Chain;
		bool AllClear = ((RealLines)&&(CheckAllClear()));
		if (AllClear) AllClearAge=0;
		if ((!ReadHistoryMsg)&&(IsPlace)) {
			if (!TargetSta) ++TPiece;
		}
		if ((!TargetSta)&&(!CurZone)) TLine+=RealLines;
		InactiveTot = (Lines)?LCD:ARE;
		if (SpeedCap) InactiveTot=max(InactiveTot,int(min(100000000LL,max(0LL,ll(TPiece)*ll(SpeedCap)-Time))));
		InactiveTime = InactiveTot;
		PreInc = HoldPosInc+BeginInc;
		HoldPosInc = 0;
		double cpx=0, cpy=0; int cpt=0;
		for (pair<int,int> o : CPos) {
			++cpt; cpx+=o.first; cpy+=o.second;
			if (((o.first>0)&&(o.second>0))&&((o.first<=LX)&&(o.second<=LY))) C[o.first][o.second]=true;
		}
		if (cpt) {
			cpx/=double(cpt); cpy/=double(cpt);
		}
		int SpinLev = GetSpinLevel(p);
		if ((Mod8)&&(!SpinSta)) SpinLev=min((Bot)?0:1,SpinLev);
		for (pair<int,int> o : CPos) {
			if (((o.first>0)&&(o.second>0))&&((o.first<=LX)&&(o.second<=LY))) C[o.first][o.second]=false;
		}
		if ((Mod8)&&(SpinSta)) SpinLev=3;
		if ((Mod8==2)&&(!SpinLev)) Lines=0;
		if (SpinLev) {
			SpinText = Lst.GetRealName();
			SpinAge=0; SpinColor=Lst.GetColor(); SpinGrade=SpinLev;
			if (Mod8==2) {
				SpinColor=0x9999FF; SpinText="X";
			}
		}
		if ((Chain>1)&&(RealLines)) {
			SpinText=IToS(Chain)+" Chain"; SpinColor=0xCCCC00; SpinGrade=SpinAge=0;
		}
		if ((SpinLev)||(RealLines)) {
			pair<string,int> NewClearType = make_pair((((Mod8)&&(Bot))?SpinSta:SpinLev)?p.GetRealName():"",Lines);
			if (Mod8==2) {
				if (SpinLev) NewClearType.first="X";
				else NewClearType.second=0;
			}
			if (!RealLines) NewClearType.second=-1;
			if ((Mod8)&&(NewClearType==LastClearType)&&(Chain<2)) {
				for (int k=1; k<=((Mod8==2)?SX:1); ++k) {
					GarbageLayer o(LY);
					for (int i=1; i<=LY; ++i) o.B[i]=Garb;
					int tp = GarbQuest+max(1,min(20,Level+5));
					o.B[rand()%LY+1] = tp;
					if ((Mod6)&&(rand()&1)) o.B[rand()%LY+1]=tp;
					AddGarbage(o, 1);
				}
			}
			else if (Mod8) {
				for (int i=1; i<=LX; ++i) {
					for (int j=1; j<=LY; ++j) {
						if (C[i][j]) continue;
						if ((B[i][j]>=GarbQuest)&&(B[i][j]<=GarbQuest+20)) {
							--B[i][j]; if (B[i][j]<=GarbQuest) B[i][j]=Age[i][j]=U[i][j]=0;
						}
					}
				}
			}
			LastClearType = NewClearType;
		}
		if ((Lines)&&(!SpinLev)&&(Lines<4)) {
			if (((BotType==1)||(BotType==3))&&(SpinRule==SR_Guildline)&&(!Mod8)) SpinMode=false;
		}
		double CalcAttack=0.0, BaseAttack=0.0; int Surge=0;
		if (RealLines) {
			int val = Lines;
			if ((Mod6==2)&&(!QPBlighted)&&(!DigVal)) val=0;
			LineClearText = min(int(LineClearWord.size()-1),max(0,val));
			LineClearAge=0; LineClearColor=0xFFFFFF;
			if (Mod8<2) BaseAttack=GetBaseAttack(Lines);
			if (SpinLev) {
				if (SpinLev==3) BaseAttack=Lines*2;
				else if (SpinLev==2) ++BaseAttack;
			}
		}
		int ComboSta = (RealLines)?1:-1;
		if ((ComboRule==CR_Arcade)&&(RealLines<2)) ComboSta=min(ComboSta,0);
		if (ComboRule==CR_None) ComboSta=-1;
		if ((ReadHistoryMsg)||(IsZoneClear)) ComboSta=0;
		if (IsCascade) ComboSta=max(ComboSta,0);
		if (ComboSta>0) {
			C2ComboRem = max(0,C2ComboRem);
			if (ComboRule==CR_C2)
				C2ComboRem=max(0,C2ComboRem+C2ComboBase[min(int(C2ComboBase.size())-1,Combo)]+Lines*C2ComboBonus[min(int(C2ComboBonus.size())-1,Combo)]);
			Combo += ComboSta;
			if (ComboRule==CR_C2) {
				ComboText=Combo; ComboAge=0;
			}
			else if (Combo>1) {
				ComboText=Combo-1; ComboAge=0;
			}
		}
		else if (ComboSta<0) {
			if (ComboRule==CR_C2) C2ComboRem=max(0,C2ComboRem-390);
			else {
				if (Combo>1) ComboText=ComboAge=0;
				Combo = 0;
			}
		}
		if (!RealLines) AllClear=false;
		if (AllClearRule==AR_None) AllClear=false;
		int B2BSta = 0;
		if (RealLines) B2BSta=(((Lines>3)&&(Mod8<2))||(SpinLev))?1:-1;
		if (AllClear) {
			if ((AllClearRule==AR_IO)||(AllClearRule==AR_Default)) B2BSta=max(B2BSta,1);
			if (AllClearRule==AR_QP) B2BSta=max(B2BSta,2);
		}
		if (Chain>1) B2BSta=max(B2BSta,0);
		if (IsZoneClear) B2BSta=0;
		int PrevB2B = B2B;
		if (B2BRule==BR_Techmino) {
			if (RealLines) {
				if (SpinLev) B2BSta=max(20,50*Lines+30*(Lines>2))*2/max(2,5-SpinLev);
				else if (Lines>3) B2BSta=(Lines-1)*50;
				else if ((AllClear)&&(TLine>4)) B2BSta=800;
				else if (AllClear) B2BSta=0;
				else B2BSta=-250;
			}
			else {
				if (SpinLev) B2BSta=20;
				else B2BSta=-min(max(0,B2B-800),40);
			}
		}
		if (GarbFuse) B2BSta=max(B2BSta,0);
		if ((Mod8==2)&&(SpinLev)&&(!Lines)&&(B2B>4)) B2BSta=max(B2BSta,1);
		if ((Mod6==2)&&(!QPBlighted)&&(!DigVal)) B2BSta=min(B2BSta,0);
		if (Mod6==2) B2BSta=min(B2BSta,0);
		if (B2BRule==BR_None) B2BSta=-1;
		//if (Spin) B2BSta=1;
		int CurScore = BaseScore(Lines,SpinLev);
		int Defense = 0;
		bool B2BDig = false;
		if (ReadHistoryMsg) B2BSta=0;
		if (B2BRule==BR_Techmino) {
			if (B2BSta>0) {
				if (SpinLev) {
					if (PrevB2B>=50) {
						BaseAttack += 1+(Lines>2);
						if (PrevB2B>800) {
							BaseAttack += 0.5*Lines;
							++Defense;
						}
					}
				}
				else if (PrevB2B>800) {
					BaseAttack*=1.5; ++Defense;
				}
				else if (PrevB2B>=50) ++BaseAttack;
				B2B = min(1000,B2B+B2BSta);
			}
			else B2B=max(0,B2B+B2BSta);
			if (B2B!=PrevB2B) {
				B2BText=B2B; B2BAge=0;
			}
		}
		else if (B2BSta>0) {
			B2B += B2BSta;
			if (B2BRule==BR_Chain) {
				if (int(floor(GetB2BAttack(PrevB2B-1)))<int(floor(GetB2BAttack(B2B-1)))) {
					B2BBlinkRem=600; AddB2BParticle(0xFFE211);
				}
			}
			if (DigVal) B2BDig=true;
			if ((PrevB2B>0)&&((SpinLev)||(Lines>3))) {
				CurScore = CurScore*3/2;
				if (RealLines) BaseAttack+=GetB2BAttack(B2B-1);
				if (Mod8==2) {
					if (Lines) ++BaseAttack;
					else if (B2B>4) BaseAttack+=2;
				}
			}
			if (B2B>1) {
				B2BText=B2B-1; B2BAge=0;
			}
			if ((B2B>4)&&((B2BRule==BR_Surge)||(B2BRule==BR_QP))) AddB2BParticle(B2BColor(B2B-1));
		}
		else if (B2BSta<0) {
			if (B2B>1) {
				if (B2B>4) {
					if (B2BRule==BR_Surge) {
						Surge=B2B-1; AddB2BParticle(0xFFFFFF,2);
					}
					if (B2BRule==BR_QP) {
						Surge=B2B-4; AddB2BParticle(0xFFFFFF,2);
					}
				}
				B2BText=0; B2BAge=0;
				if (B2BRule==BR_Chain) {
					B2BBlinkRem=600; AddB2BParticle(0x888888);
				}
			}
			B2B = 0;
		}
		if (AllClear) CurScore+=3500;
		if ((Combo>1)&&(RealLines)) CurScore+=(Combo-1)*50;
		double MultiplerRes = (RealLines)?GetMultiplerComboAttack(Combo-1,BaseAttack,Lines):BaseAttack;
		double FlatRes = BaseAttack+((RealLines)?GetComboAttack(Combo-1):0);
		if ((ComboRule==CR_Techmino)||(ComboRule==CR_Default)) FlatRes=max(FlatRes-BaseAttack,BaseAttack);
		CalcAttack = max(MultiplerRes,FlatRes);
		CalcAttack += Surge;
		if (B2BDig) {
			if (DigBonus==2) ++CalcAttack;
			if (DigBonus==1) ++Defense;
		}
		if ((SingleBonus)&&(Lines==1)&&(Combo<=1)&&(B2B<1)) {
			if (!QPHardmode()) ++CalcAttack;
		}
		if (AllClear) {
			++TAC;
			if (AllClearRule==AR_Default) {
				CalcAttack+=(LY>>1); Defense+=LY-(LY>>1);
			}
			if (AllClearRule==AR_IO) CalcAttack+=5;
			if (AllClearRule==AR_QP) CalcAttack+=3;
			if (AllClearRule==AR_Guildline) CalcAttack+=10;
			if (AllClearRule==AR_Arcade) CalcAttack*=2;
			if (AllClearRule==AR_Techmino) CalcAttack=max(CalcAttack,double(min(16,6+2*TAC)));
		}
		if ((Mod6==2)&&(RealLines)) {
			if ((!QPBlighted)&&(!DigVal)) CalcAttack=Surge;
			if (QPBlighted) {
				CalcAttack*=1.75;
				if (!DigVal) {
					++CalcAttack; QPBlighted=false; AddB2BParticle(0xFFFFFF,1);
				}
			}
			if (DigVal) {
				QPBlighted=true; AddB2BParticle(0xFFE211,1);
			}
		}
		int Attack = max(0,int(floor(CalcAttack+0.001)));
		if (IntRule==IR_Ceil) Attack=max(0,int(ceil(CalcAttack-0.001)));
		double rem = CalcAttack-Attack;
		if (IntRule==IR_Random) {
			if (randf()<rem) ++Attack;
		}
		if (IntRule==IR_Accumulate) {
			AttackAcc += rem;
			if (AttackAcc>=0.9999) {
				++Attack; AttackAcc-=1.0;
			}
		}
		Attack = max(Attack,0);
		if (CurZone) {
			ZoneAttack+=Attack; Attack=0;
		}
		else if (ZoneAttack) {
			Surge=Attack=Surge+ZoneAttack; ZoneAttack=0;
		}
		if (!TargetSta) TAttack+=Attack;
		LastAttack = Attack;
		if (CurScore) {
			double sz = 0.8*(1.0+0.1*sqrt(max(0.0,CurScore*0.005)));
			if (!TargetSta) TScore+=ll(CurScore)*ll(max(1,Level+1));
			if (!QPMode)
				AddFloatText("+"+IToS(ll(CurScore)*ll(max(1,Level+1))), p.GetColor(), sz, 0.08, 0.15, 0.1,
				SX+1, LY*0.5-0.4, 0.3, 0, 0, 0);
		}
		int CancelMax = 0;
		for (int i=0; i<AttackIn.size(); ++i) {
			if ((AttackIn[i].Delay>GarbageDelay-GarbagePass)&&((!QPMode)||(!WindUpCD))) break;
			CancelMax = i+1;
		}
		if (TPiece<=OpenerProtect) Defense+=max(0,min(Attack,CancelMax-Defense-Attack));
		if (Mod5==1) Defense+=Attack;
		int Cancel = min(Attack+Defense,int(CancelMax));
		if (Defense>=Cancel) Defense-=Cancel;
		else {
			Attack-=Cancel-Defense; Defense=0;
		}
		for (int i=0; i<min(Cancel,GarbagePreview); ++i) {
			for (int j=1; j<=LY; ++j) {
				if (!AttackIn[i].B[j]) continue;
				int clr = Mix(0xFFFFFF,abs(AttackIn[i].B[j]),0.2);
				clr = Mix(clr,BackgroundColor,max(0.2,0.8-0.1*i));
				double sz=0.2+randf()*0.1, th=randf()*2.0*acos(-1.0);
				double d=randf(), x=SX-(-i-0.5)+0.5+cos(th)*d, y=j-0.5+sin(th)*d;
				AddParticle(0, 4, clr, sz, sz*3.0,
				x, y, cos(th), sin(th), 0, 0, randf()*acos(-1.0), 0.3);
			}
		}
		for (int i=Cancel; i<AttackIn.size(); ++i) AttackIn[i-Cancel]=AttackIn[i];
		for (int i=0; i<Cancel; ++i) AttackIn.pop_back();
		int clr = 0x888888;
		if (Surge) clr=0x00FFCC;
		else if (SpinLev) clr=p.GetColor();
		else if (Combo>1) clr=0x39C5BB;
		if (Defense) AddAttackText(Defense,0,0x888888,cpx,cpy,SpinLev,0.5);
		if (Cancel) AddAttackText(Cancel,clr,0xDDDDDD,cpx,cpy,SpinLev);
		if (Attack) {
			int Split = (Surge)?min(30,max(3,Surge/10)):1;
			//if (QPMode) Split*=2;
			AddAttackText(Attack, 0xDDDDDD, clr, cpx, cpy, SpinLev);
			/*if ((QPMode)&&(Attack>=2)) {
				int d = rand()%6;
				if (d<2) Split=max(Split,4);
				if (d<4) Split=max(Split,2);
			}*/
			GiveQPScore(Attack*1000LL, true, true, p.GetColor());
			if (TargetType==TT_Backfire) Attack=max(0,Attack-Surge);
			for (int i=0; i<Split; ++i) {
				int val = Attack/Split+(i<Attack%Split);
				AttackSeq(val);
			}
		}
		if (QPMode) {
			if ((Attack)||(Cancel)||(Lines)) {
				if (QPNextLevelDistance()<=2000LL) GiveQPScore(3000LL,true,false,p.GetColor());
			}
			if ((Lines)&&(Mod9<2)) GiveQPExp(500+10000*(((Mod8==2)&&(!SpinLev))?1:Lines));
			if (Cancel) GiveQPExp(500+10000*(((Mod5==2)||(Mod9==2)||(ModD==2))?0:Cancel));
			if (Attack) GiveQPExp(500+10000*min(Attack,10000));
		}
		SpinSta=RotSta=false;
		if (Mutate) {
			for (int i=PreTLine+1; i<=TLine; ++i) {
				if (i>=AddID*AddID/2+AddID*3) {
					++AddID;
					if (Gen.Pool.size()<1000) Gen.Pool.push_back(GetMutate(P_A));
				}
				else if ((Gen.Pool.size())&&(i%1==0)) {
					int p = MutatePos%Gen.Pool.size();
					Gen.Pool[p] = GetMutate(Gen.Pool[p]);
					++MutatePos; if (MutatePos>=Gen.Pool.size()) MutatePos=0;
				}
			}
		}
	}
	void ActivateGarbage() {
		int tot = 0;
		if (GarbagePush) {
			for (GarbageLayer &o : AttackIn) {
				if ((GarbageGap>=0)&&(tot>=GarbageGap)) break;
				if (!o.Delay) o.Delay=-1;
				++tot;
			}
		}
		else {
			while ((!AttackIn.empty())&&(AttackIn[0].Delay<=0)&&((!CurZone)||(!ZoneReq))) {
				if ((GarbageGap>=0)&&(tot>=GarbageGap)) break;
				++tot;
				GarbageLayer o = AttackIn[0];
				AddGarbage(o);
				for (int i=1; i<AttackIn.size(); ++i) AttackIn[i-1]=AttackIn[i];
				AttackIn.pop_back();
			}
		}
	}
	void PostAREProcess() {
		int Lines = ClearLines();
		FallTime=0; HoldRem=HoldLen;
		for (int i=1; i<=LX; ++i) FallDest[i]=0;
		int Height = 0;
		for (int i=1; i<=LX; ++i) {
			for (int j=1; j<=LY; ++j) {
				if (B[i][j]<0) Height=max(Height,i);
			}
		}
		if (Height) LineAfterPC+=Lines;
		else LineAfterPC=0;
		bool f = false;
		if (GarbageActive==GA_Default) f=(!Lines);
		if (GarbageActive==GA_Attack) f=(!LastAttack);
		if ((GarbageActive==GA_None)||(GarbageActive==GA_Instant)) f=true;
		if (ModS) {
			int TargetLY = min(20,max(4,4+TLine/20));
			while (LY<TargetLY) {
				ChangeSize(LX,LY+1,SX+1,(LY&1)+1); AddNotice(IToS(LY)+"*"+IToS(LY),0x66CCFF);
			}
		}
		if (f) ActivateGarbage();
		if ((CurZone)||(IsZoneClear)) ZoneProgress=0;
		else {
			ZoneAttack=0; ZoneProgress+=Lines;
		}
		IsZoneClear = false; 
	}
	void PrintStrL(string str, double x, double y, double sz, int clr, double bd=0.12, double dis=1.0, string eff="", double th=0.0, double sc=0.0) {
		PaintManager::PrintStrL(str, PX+K*x, PY+K*y, K*sz*0.66, clr, bd, dis, eff, th, sc);
	}
	void PrintStrR(string str, double x, double y, double sz, int clr, double bd=0.12, double dis=1.0, string eff="", double th=0.0, double sc=0.0) {
		PaintManager::PrintStrR(str, PX+K*x, PY+K*y, K*sz*0.66, clr, bd, dis, eff, th, sc);
	}
	void PrintAction(string str, double x, double y, double sz, int clr, double bd=0.12, double dis=1.0, string eff="", double th=0.0, double sc=0.0) {
		if (HideStat) PrintStrM(str, x, LY*0.5-0.1, sz, clr, bd, dis, eff, th, sc);
		else PrintStrR(str, x, y, sz, clr, bd, dis, eff, th, sc);
	}
	void PrintStrM(string str, double x, double y, double sz, int clr, double bd=0.12, double dis=1.0, string eff="", double th=0.0, double sc=0.0) {
		PaintManager::PrintStrM(str, PX+K*x, PY+K*y, K*sz*0.66, clr, bd, dis, eff, th, sc);
	}
	void Rect(double x, double y, double dx, double dy, int clr, double th=0.0, double rx=0.0, double ry=0.0) {
		PaintManager::Rect(PX+K*x, PY+K*y, dx*K, dy*K, clr, th, PX+K*rx, PY+K*ry);
	}
	void Square(double x, double y, double d, int clr, double th=0.0, double rx=0.0, double ry=0.0) {
		Rect(x, y, d, d, clr, th, rx, ry);
	}
	void Poly(double x, double y, double sz, int n, int clr, double th=0.0) {
		PaintManager::Poly(PX+K*x, PY+K*y, sz*K, n, clr, th);
	}
	#define sq(xx,ss,clr) Square(x+sz*(xx),y+sz*(xx),sz*(ss),clr,th,rx,ry)
	#define rec(xx,yy,sx,sy,clr) Rect(x+sz*(xx),y+sz*(yy),sz*(sx),sz*(sy),clr,th,rx,ry)
	void PaintTile(double x, double y, int clr, double sz=1.0, EBlockStyle Style=S_Default, double th=0.0, double rx=0.0, double ry=0.0, bool u=false, bool d=false, bool l=false, bool r=false) {
		Square(x, y, sz, clr, th, rx, ry);
		if ((K*double(GetSize())<8.0)) return;
		if (1) {
			if ((Style==S_Default)||(Style==S_Active)) {
				sq(0, 1, Mix(0xFFFFFF,clr,0.1));
				sq(0.1, 0.9, Mix(0x000000,clr,0.2));
				sq(0.1, 0.8, clr);
				sq(0.2, 0.65, Mix(0xFFFFFF,clr,0.2));
				sq(0.2, 0.6, Mix(0x000000,clr,0.05));
				sq(0.3, 0.4, Mix(0xFFFFFF,clr,0.2));
				sq(0.35, 0.3, clr);
				if ((u)&&(!r)) rec(0,0.9,0.1,0.1,Mix(0x000000,clr,0.2));
				if ((l)&&(!d)) rec(0.9,0,0.1,0.1,Mix(0x000000,clr,0.2));
			}
			if (Style==S_Garbage) {
				sq(0, 1, Mix(0xFFFFFF,clr,0.1));
				sq(0.1, 0.9, Mix(0x000000,clr,0.2));
				sq(0.1, 0.8, clr);
				sq(0.2, 0.6, Mix(0x000000,clr,0.05));
				sq(0.25, 0.5, Mix(0x000000,clr,0.1));
				sq(0.3, 0.4, Mix(0x000000,clr,0.15));
			}
		}
		if (u) rec(0,0.1,0.1,0.8,clr);