BaseGravityDelay = max(0,min(Max,GetConfig("GravityDelay",0)));
		if (ResetLim<0) ResetLim=9999999;
		BotSpeed = max(0,min(Max,GetConfig("BotSpeed",1000)));
		GenType = (EGenerationType)max(0,min(2,GetConfig("RandomType",G_Bag)));
		ActType = max(0,min(9,GetConfig("BotType",1)));
		Mod8 = max(0,min(2,GetConfig("Mod8",0)));
		BaseRule = (EBaseRule)max(0,min(5,GetConfig("BaseRule",BSR_Default)));
		ComboRule = (EComboRule)max(0,min(13,GetConfig("ComboRule",CR_Default)));
		SpinRule = (ESpinRule)max(0,min(7,GetConfig("SpinRule",SR_Default)));
		B2BRule = (EB2BRule)max(0,min(5,GetConfig("B2BRule",BR_Surge)));
		AllClearRule = (EAllClearRule)max(0,min(6,GetConfig("AllClearRule",AR_Default)));
		ClutchRule = (EClutchRule)max(0,min(2,GetConfig("ClutchRule",CLR_Default)));
		GarbageDelay = max(0,min(Max,GetConfig("GarbageDelay",500)));
		GarbageSeg = max(0,min(Max,GetConfig("GarbageSeg",0)));
		GarbagePass = max(0,min(Max,GetConfig("GarbagePass",0)));
		GarbagePush = max(0,min(Max,GetConfig("GarbagePush",100)));
		GarbageGap = max(-1,min(Max,GetConfig("GarbageGap",8)));
		GarbagePreview = max(0,min(30,GetConfig("GarbagePreview",10)));
		ForceShowAttack = max(0,min(1,GetConfig("ForceShowAttack",0)));
		AutoRespawn = max(0,min(1,GetConfig("AutoRespawn",0)));
		HideStat = max(0,min(1,GetConfig("HideStat",0)));
		RotateSystem = (ERotateSystem)max(0,min(15,GetConfig("RotateSystem",R_Default)));
		TarPiece = max(0,min(Max,GetConfig("TarPiece",0)));
		TarLine = max(0,min(Max,GetConfig("TarLine",0)));
		TarDig = max(0,min(Max,GetConfig("TarDig",0)));
		TarAttack = max(0,min(Max,GetConfig("TarAttack",0)));
		TarTime = max(0,min(Max,GetConfig("TarTime",0)));
		TarScore = max(0,min(Max,GetConfig("TarScore",0)));
		TypePiece = max(0,min(1,GetConfig("TypePiece",0)));
		TypeLine = max(0,min(1,GetConfig("TypeLine",0)));
		TypeDig = max(0,min(1,GetConfig("TypeDig",0)));
		TypeAttack = max(0,min(1,GetConfig("TypeAttack",0)));
		TypeTime = max(0,min(1,GetConfig("TypeTime",0)));
		TypeScore = max(0,min(1,GetConfig("TypeScore",0)));
		LevTime = max(0,min(Max,GetConfig("LevTime",0)));
		LevPiece = max(0,min(Max,GetConfig("LevPiece",0)));
		LevLine = max(0,min(Max,GetConfig("LevLine",0)));
		LevAttack = max(0,min(Max,GetConfig("LevAttack",0)));
		LevTimeInc = max(0,min(Max,GetConfig("LevTimeInc",0)));
		LevPieceInc = max(0,min(Max,GetConfig("LevPieceInc",0)));
		LevLineInc = max(0,min(Max,GetConfig("LevLineInc",0)));
		LevAttackInc = max(0,min(Max,GetConfig("LevAttackInc",0)));
		LevBase = max(0,min(1000,GetConfig("LevBase",0)));
		LevStart = max(0,min(1000,GetConfig("LevStart",0)));
		LevMax = max(0,min(100,GetConfig("LevMax",50)));
		LevScaleMode = (ELevScaleMode)max(0,min(3,GetConfig("LevScaleMode",LS_Default)));
		LevScaleRate = max(0,min(1000,GetConfig("LevScaleRate",7)));
		MasterThreshold = max(0,min(Max,GetConfig("MasterThreshold",0)));
		GarbageShape = (EGarbageShape)max(0,min(7,GetConfig("GarbageShape",GS_Default)));
		GarbageType = (EGarbageType)max(0,min(4,GetConfig("GarbageType",GT_Default)));
		StaticGarbHeight = max(0,min(100,GetConfig("StaticHeight",0)));
		StartHeight = max(0,min(100,GetConfig("StartHeight",0)));
		InnerMess = max(0,min(1000,GetConfig("InnerMess",0)));
		SegMess = max(0,min(1000,GetConfig("SegMess",1000)));
		IntRule = (EIntRule)max(0,min(3,GetConfig("IntRule",IR_Floor)));
		GarbageActive = (EGarbageActive)max(0,min(3,GetConfig("GarbageActive",GA_Default)));
		UseMove = max(0,min(1,GetConfig("UseMove",1)));
		UseRotate = max(0,min(1,GetConfig("UseRotate",1)));
		Use2Rot = max(0,min(1,GetConfig("Use2Rot",1)));
		UseZone = max(0,min(1,GetConfig("UseZone",0)));
		ZoneReq = max(0,min(Max,GetConfig("ZoneReq",8)));
		ZoneSeg = max(1,min(Max,GetConfig("ZoneSeg",4)));
		ZoneTime = max(0,min(Max,GetConfig("ZoneTime",5000)));
		UseHalf = max(0,min(1,GetConfig("UseHalf",1)));
		UseSoftDrop = max(0,min(1,GetConfig("UseSoftDrop",1)));
		UseHardDrop = max(0,min(1,GetConfig("UseHardDrop",1)));
		UseSonicDrop = max(0,min(1,GetConfig("UseSonicDrop",1)));
		UseFlip = max(0,min(1,GetConfig("UseFlip",0)));
		UseUp = max(0,min(1,GetConfig("UseUp",0)));
		UseWaste = max(0,min(1,GetConfig("UseWaste",0)));
		UseTip = max(0,min(1,GetConfig("UseTip",0)));
		UseUndo = max(0,min(1,GetConfig("UseUndo",0)));
		if (BoardID>1) UseUndo=false;
		UseLock = max(0,min(1,GetConfig("UseLock",0)));
		UseDeep = max(0,min(1,GetConfig("UseDeep",0)));
		UsePlace = max(0,min(1,GetConfig("UsePlace",0)));
		UseClick = max(0,min(1,GetConfig("UseClick",1)));
		RandomRot = max(0,min(1,GetConfig("RandomRot",0)));
		ShadowType = max(0,min(2,GetConfig("ShadowType",1)));
		StartDelay = max(0,min(Max,GetConfig("StartDelay",3000)));
		VanishHeight = max(0,min(100,GetConfig("VanishHeight",0)));
		GravityType = (EGravityType)max(0,min(7,GetConfig("GravityType",0)));
		Stock = max(-1,min(Max,GetConfig("Stock",0)));
		Mutate = max(0,min(1,GetConfig("Mutate",0)));
		DigBonus = max(0,min(2,GetConfig("DigBonus",2)));
		SingleBonus = max(0,min(1,GetConfig("SingleBonus",0)));
		OpenerProtect = max(0,min(Max,GetConfig("OpenerProtect",14)));
		Pool = (_Pool.empty())?DefaultPool:_Pool;
		int tmp = max(-1,min(8,GetConfig("TargetType",-1)));
		if (tmp<0) {
			if (Bot) tmp=rand()%6+2;
			else tmp=2;
		}
		TargetType = (ETargetType)tmp;
		Mod2 = max(0,min(2,GetConfig("Mod2",0)));
		Mod3 = max(0,min(2,GetConfig("Mod3",0)));
		Mod4 = max(0,min(2,GetConfig("Mod4",0)));
		Mod5 = max(0,min(2,GetConfig("Mod5",0)));
		Mod6 = max(0,min(2,GetConfig("Mod6",0)));
		Mod7 = max(0,min(2,GetConfig("Mod7",0)));
		Mod8 = max(0,min(2,GetConfig("Mod8",0)));
		Mod9 = max(0,min(2,GetConfig("Mod9",0)));
		ModP = max(0,min(2,GetConfig("ModP",0)));
		ModM = max(0,min(2,GetConfig("ModM",0)));
		ModS = max(0,min(2,GetConfig("ModS",0)));
		if (ModM==2) Mutate=true;
		if (ModP==1) Pool=PentominoPool;
		if (ModP==2) Pool=HexaminoPool;
		//if (!BoardID&&1) Pool={P_I,P_J,P_L,P_O,P_S,P_T,P_Z,P_A,P_B,P_C};
		MasterBegin = -1;
		CursorMode=((UsePlace)&&(!HideStat)); CursorMoveLock=true;
		for (int i=0; i<=1000; ++i) {
			int val = BaseDropDelay*GetLevelMultipler(i,0.001*double(LevScaleRate));
			if (val<=MasterThreshold) {
				MasterBegin=i; break;
			}
		}
		if (BaseDropDelay<0) MasterBegin=-1;
		if (_SX==-1) SX=LX;
		else SX=max(2,min(LX,_SX));
		if (Mod5==2) SX=max(2,SX-6);
		//if (ModM) SX=min(LX,SX+4);
		if (ModS) {
			SX=6+max(ModP,ModM); LY=4+max(ModP,ModM); LX=max(LX,40);
		}
		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;
			}
		}
		SearchStep = -1;
		ResetBoard(Seed);
		PX=PY=0.0; K=28.0;
		KeyPause = AskKey(GetConfig("KeyPause","o",KeyList));
		if (ModS==2) DAS=ARR=SDDAS=SDARR=0;
		Keys = {
			Monitor(A_SoftDrop, AskKey(GetConfig("KeySoft","down",KeyList)), UseSoftDrop, SDDAS, min(SDARR,0), DCD),
			Monitor(A_SonicDrop, AskKey(GetConfig("KeySonic","s",KeyList)), UseSonicDrop, SDDAS, 0),
			Monitor(A_Left, AskKey(GetConfig("KeyLeft","left",KeyList)), UseMove, DAS, ARR, DCD),
			Monitor(A_Right, AskKey(GetConfig("KeyRight","right",KeyList)), UseMove, DAS, ARR, DCD),
			Monitor(A_Rotate, AskKey(GetConfig("KeyRotate","up",KeyList)), UseRotate),
			Monitor(A_CW, AskKey(GetConfig("KeyCW","x",KeyList)), Use2Rot),
			Monitor(A_CCW, AskKey(GetConfig("KeyCCW","z",KeyList)), Use2Rot),
			Monitor(A_Half, AskKey(GetConfig("KeyHalf","a",KeyList)), UseHalf),
			Monitor(A_HardDrop, AskKey(GetConfig("KeyHard","space",KeyList)), UseHardDrop),
			Monitor(A_Hold, AskKey(GetConfig("KeyHold","c",KeyList))),
			Monitor(A_Retry, AskKey(GetConfig("KeyRetry","u",KeyList))),
			Monitor(A_Reset, AskKey(GetConfig("KeyReset","e",KeyList)), HoldType>1),
			Monitor(A_Up, AskKey(GetConfig("KeyUp","w",KeyList)), UseUp, DAS, SDARR, DCD),
			Monitor(A_Waste, AskKey(GetConfig("KeyWaste","v",KeyList)), UseWaste),
			Monitor(A_Flip, AskKey(GetConfig("KeyFlip","q",KeyList)), UseFlip),
			Monitor(A_Lock, AskKey(GetConfig("KeyLock","r",KeyList)), UseLock),
			Monitor(A_CursorMode, AskKey(GetConfig("KeyCursorMode","i",KeyList))),
			Monitor(A_TargetMode, AskKey(GetConfig("KeyTargetMode","f",KeyList))),
			Monitor(A_Undo, AskKey(GetConfig("KeyUndo","t",KeyList)), ((!Bot)&&(UseUndo))),
			Monitor(A_Redo, AskKey(GetConfig("KeyRedo","y",KeyList)), ((!Bot)&&(UseUndo))),
			Monitor(A_Action, AskKey(GetConfig("KeyAction","g",KeyList)), !Bot),
		};
		KeyRetry = AskKey(GetConfig("KeyRetry","u",KeyList));
		Monitor *ml=FindMonitor(A_Left), *mr=FindMonitor(A_Right);
		if (ml!=nullptr) ml->Opposite=mr;
		if (mr!=nullptr) mr->Opposite=ml;
		ml=FindMonitor(A_SoftDrop); mr=FindMonitor(A_Up);
		if (ml!=nullptr) ml->Opposite=mr;
		if (mr!=nullptr) mr->Opposite=ml;
		//if ((Bot)&&(PlayerCount>2)) LX=min(LX,SX+2);
	}
	~Board() {
		ClearSearchProcess();
		for (Piece *p : CustomPiece) delete p;
		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[] U; delete[] FallDest;
	}
	bool IsOver() {
		return Over;
	}
	ETargetType GetTargetType() {
		return TargetType;
	}
	inline double GetQPScoreF() {
		return double(TQPScore)*0.001;
	}
	void SetQPScore(ll x) {
		TQPScore = x;
	}
	void SetTime(ll x) {
		Time = x;
	}
	void SetScale(double _PX, double _PY, double _K) {
		int Lev = GetSize();
		tK = 0.8*_K/double(Lev);
		tPX = _PX+_K*(0.1+0.4*double(Lev-SX)/double(Lev));
		tPY = _PY+_K*(0.1+0.4*double(Lev-LY)/double(Lev));
		if ((Menu=="home")||(Menu=="settings")) {
			tPX+=3.0; tPY-=16.0;
		}
		if (FirstSetScale) {
			PX=tPX; PY=tPY; K=tK;
			FirstSetScale = false;
		}
	}
	bool ClickHoldCD=false, CursorMoveLock=true;
	void Frame(int TimeDel, int _LatestClock, bool PermitSearch=false) {
		if ((Bot)&&(Over)&&(tK<0.001)&&(!CheckKey(KeyRetry))&&(!QPMode)) return;
		LatestClock = _LatestClock;
		int RealTimeDel = TimeDel;
		if (Pause) TimeDel=0;
		while (AttackIn.size()>5000) AttackIn.pop_back();
		while (AttackOut.size()>5000) AttackOut.pop_back();
		if (Over) {
			AttackIn.clear(); AttackOut.clear();
		}
		ShowParticle = ((!Bot)||((K*double(GetSize())>=8.0)&&(BotSpeed>=100)));
		if (!Over) {
			if (!TargetSta) Time+=ll(TimeDel);
		}
		FillNext();
		bool Active=CheckActive(); PreActive=false;
		if ((!Bot)&&(!Active)&&(!Over)&&(InactiveTime<=TimeDel)) PreActive=true;
		bool G20 = ((!Bot)&&((!DropDelay)&&(GravityCD<=0)));
		if (Bot) {
			FallTime=0; ResetRem=99999999;
		}
		bool ForceLock = false;
		if (Bot) UseUndo=false;
		if (!UseUndo) History.clear();
		StackHeight = 0;
		for (int i=LX; i; --i) {
			for (int j=1; j<=LY; ++j) {
				if (CheckGrid(i,j)) {
					StackHeight=i; break;
				}
			}
			if (StackHeight) break;
		}
		if ((!Over)&&(!TargetSta)) {
			int t0=0, c0=0, t1=0, c1=0;
			if (TarPiece) {
				++((TypePiece)?t1:t0);
				if (TPiece>=TarPiece) ++((TypePiece)?c1:c0);
			}
			if (TarLine) {
				++((TypeLine)?t1:t0);
				if (TLine>=TarLine) ++((TypeLine)?c1:c0);
			}
			if (TarDig) {
				++((TypeDig)?t1:t0);
				if (TDig>=TarDig) ++((TypeDig)?c1:c0);
			}
			if (TarAttack) {
				++((TypeAttack)?t1:t0);
				if (TAttack>=TarAttack) ++((TypeAttack)?c1:c0);
			}
			if (TarTime) {
				++((TypeTime)?t1:t0);
				if (Time>=TarTime) ++((TypeTime)?c1:c0);
			}
			if (TarScore) {
				++((TypeScore)?t1:t0);
				if (((QPMode)?TQPScore:TScore)>=TarScore) ++((TypeScore)?c1:c0);
			}
			if ((c0==t0)&&(t0)) TargetSta=1;
			else if (c1) TargetSta=-1;
		}
		if (QPMode) {
			ll req = QPExpReq();
			if ((!Over)&&(Time>=0LL)&&(!TargetSta)) {
				if (Mod9<2) GiveQPScore(min(TimeDel,min(QPNextLevelDistance()/100,QPNextLevelDistance()-1)),false);
				else {
					ll Bottom = QPLevelScore[max(0,min(int(QPLevelScore.size())-1,Level-1))]*1000LL;
					int lv = min(Level,10);
					TQPScore = max(Bottom,TQPScore-(ll(lv)*ll(lv+1)+10LL)*ll(TimeDel)/20LL);
				}
			}
			QPAddExp = max(QPAddExp,0LL);
			if (QPAddExp) {
				ll val = max(1LL,min(QPAddExp,QPAddExp*ll(TimeDel)/300LL));
				QPAddExp-=val; QPExp+=val;
			}
			if (Over) ;
			else if (QPExpDecCD>0) QPExpDecCD=max(0,QPExpDecCD-TimeDel);
			else if (TimeDel) {
				ll dec = ll(TimeDel)*ll(QPRank+1)*ll(QPRank+2)*((Mod9)?5LL:3LL)/6LL;
				QPExp -= dec;
				if (QPExp<0LL) {
					if (QPRank) {
						--QPRank; QPExp=QPExpReq()-1LL;
					}
					else QPExp=0LL;
				}
			}
			if (QPExp>=req) {
				QPExp-=req; ++QPRank;
				QPExpDecCD = 5000;
				req = QPExpReq();
				if (QPExp+QPAddExp>=req) {
					QPExp += QPAddExp;
					QPRank+=min(1000LL,QPExp/max(req,1LL)); QPExp-=req;
					ll val = min(QPExp,QPAddExp);
					QPExp-=val; QPAddExp=val;
				}
			}
			WindUpCD = max(0,WindUpCD-TimeDel);
			int ProtectDec = QPProtectDec[min(int(QPProtectDec.size())-1,Level)];
			if (Mod9==2) ProtectDec=max(100,1100-Level*100);
			QPWeightDec = max(0,QPWeightDec-TimeDel*10000/ProtectDec);
			if ((Time>=0LL)&&(!Over)) {
				ll pre=QPLevTime; QPLevTime+=TimeDel;
				if (Mod9==2) {
					if ((pre<60000LL)&&(QPLevTime>=60000LL)) AddNotice("REGRET",0xFF6666);
					if (pre>=60000LL) QPAttackWeightInc+=double(TimeDel)*0.001*0.005;
				}
			}
			if ((!QPSeg.empty())&&(K*double(GetSize())>=8.0)) {
				int clr = LevelColor[min(int(LevelColor.size())-1,QPRank)];
				if ((GiveQPScoreMsg)&&(QPSeg.size()<9000)) {
					if (Level<int(QPLevelScore.size())) clr=-clr;
					GiveQPScoreMsg = false;
				}
				if (QPSeg.back().first==clr) QPSeg.back().second.second=TQPScore;
				else if (QPSeg.size()<10000) QPSeg.push_back(make_pair(clr,make_pair(QPSeg.back().second.second,TQPScore)));
			}
		}
		if (Active) {
			if ((!Bot)&&(UseTip)&&(TgD<0)) SearchNext(true);
			bool CDown = CheckDown();
			InactiveTime = 0;
			if (Bot) FallTime=0;
			else if (((!CDown)||(DropDelay<Max))&&(GravityCD<=0)) FallTime+=TimeDel;
			if (CDown) {
				if ((!Bot)&&(FallTime>=DropDelay)) {
					FallTime=min(FallTime-DropDelay,DropDelay+5000); MoveDown();
					if (G20) SonicDrop();
				}
			}
			else {
				if ((!Bot)&&(LockDelay>=0)&&(FallTime>=LockDelay)) {
					FallTime=0; ForceLock=true;
				}
			}
			GravityCD = max(0,GravityCD-TimeDel);
			if (SideGravity) {
				int tot = 110;
				while ((tot>0)&&(CheckLeft())) {
					--tot; MoveLeft();
				}
			}
		}
		else {
			InactiveTime = max(0,InactiveTime-TimeDel);
			int flag = 0;
			if ((Over)||(InactiveTime>0)) ;
			else if ((CurZone)&&(ZoneRem<0)) flag=4;
			else {
				if (InCascade) {
					CascadeProcess(); InCascade=false; flag=3;
				}
				else {
					PostAREProcess();
					if (CheckCascade()) flag=2;
					else flag=1;
				}
			}
			if (flag==4) {
				ClearZone(); CurZone=0; PreAREProcess(false,true);
			}
			if (flag==1) {
				bool UseHis=((!Bot)&&(UseUndo)&&(!ReadHistoryMsg)); ReadHistoryMsg=false;
				BoardHistory His;
				if (UseHis) His=WriteHistory();
				Piece p = Next[0];
				if (Spawn(p,0,-1,((CurClutch)||((Bot)&&((PreInc)||(HoldPosInc+BeginInc)))))) {
					for (int i=0; i+1<Next.size(); ++i) Next[i]=Next[i+1];
					Next.pop_back(); ++DNext;
					if ((!Bot)&&(UseTip)) {
						ClearSearchProcess(); SearchNext(true);
					}
					if ((SmoothLevel>=4)&&(NextLen>0)&&((!Bot)||(BotSpeed>=100))) {
						PDx+=1-(SX-PosX); PDy+=(LY-1)-PosY;
					}
				}
				else if (!Bot) {
					int Rot=0; bool UseHold=false;
					vector<Action> NewActions;
					for (Action o : Actions) {
						if (o==A_Rotate) Rot+=DefaultRot;
						else if (o==A_CW) Rot+=1;
						else if (o==A_CCW) Rot+=3;
						else if (o==A_Half) Rot+=2;
						else if ((o==A_Hold)&&(HoldLen)&&(HoldType)) UseHold=true;
						else NewActions.push_back(o); 
					}
					Piece old = p;
					if (UseHold) {
						p = (Hold.size()==HoldLen)?Hold[0]:Next[1];
					}
					if (Spawn(p,0,(DefaultDir+Rot)&3,CurClutch)) {
						Actions = NewActions;
						for (int i=0; i+1<Next.size(); ++i) Next[i]=Next[i+1];
						Next.pop_back(); ++DNext;
						bool ff = false;
						if (UseHold) {
							if (Hold.size()==HoldLen) {
								for (int i=0; i+1<HoldLen; ++i) Hold[i]=Hold[i+1];
								Hold[HoldLen-1] = old;
								ff = true;
							}
							else {
								Hold.push_back(old);
								for (int i=0; i+1<Next.size(); ++i) Next[i]=Next[i+1];
								Next.pop_back();
							}
							HoldRem = max(0,HoldRem-1);
						}
						if ((SmoothLevel>=4)&&(NextLen>0)&&((!Bot)||(BotSpeed>=100))) {
							if (ff) {
								PDx+=1-(SX-PosX); PDy+=(-4)-PosY;
							}
							else {
								PDx+=1-(SX-PosX); PDy+=(LY-1)-PosY;
							}
						}
					}
					else TopOut();
				}
				else TopOut();
				FillNext();
				if ((UseHis)&&(CheckActive())) {
					while ((HistoryPos>0)&&(!History.empty())) {
						History.pop_front(); --HistoryPos;
					}
					HistoryPos=0; History.push_front(His);
					while (History.size()>101) History.pop_back();
				}
			}
			else if (flag==2) {
				InCascade = true; 
				InactiveTime=InactiveTot=CCD;
			}
			else if (flag==3) PreAREProcess(false,true);
		}
		if ((QPMode)&&(PlayerCount>1)&&(!Over)) {
			vector<int> lst = (Mod9<2)?QPRiseTime:QPRevMod9RiseTime;
			if ((QPRisePos<lst.size())&&(Time>=1000LL*ll(lst[QPRisePos]))) {
				++QPRisePos; GarbageLayer o(LY);
				for (int i=1; i<=LY; ++i) o.B[i]=Solid;
				AddGarbage(o, 2);
			}
		}
		Active = CheckActive();
		bool Reset=false, L20=false, R20=false, SDInDAS=false;
		if (SideGravity) L20=true;
		if (Time<0) ActionTime=int(-BotPos*double(BotSpeed));
		if (Bot) {
			int d = max(1,GetSearchSeg());
			int SegTime = BotSpeed/2/(d+2);
			ActionTime += TimeDel;
			ActionCD = max(0,ActionCD-TimeDel);
			vector<Action> tmp=Actions; Actions.clear();
			if ((!Pause)&&(!Over)&&(!DropDelay)&&(CheckActive())) SonicDrop();
			if ((Pause)||(Over)) ;
			else if (!PendingActions.empty()) {
				for (pair<Action,int> &o : PendingActions) o.second=min(o.second,BotSpeed);
				int dd=TimeDel, tot=0;
				while ((tot<PendingActions.size())&&(dd>0)) {
					int val = min(dd,PendingActions[tot].second);
					dd-=val; PendingActions[tot].second-=val;
					if (PendingActions[tot].second) break;
					++tot;
				}
				for (int i=0; i<tot; ++i) Actions.push_back(PendingActions[i].first);
				for (int i=0; i+tot<PendingActions.size(); ++i) PendingActions[i]=PendingActions[i+tot];
				PendingActions.resize(PendingActions.size()-tot);
			}
			else if ((ActionTime>=BotSpeed)&&(PermitSearch)&&(ActionCD<=0)) {
				if (BotSpeed) {
					vector<Action> Nxt = SearchNext(!((!BotType)||((AliveCount>5)&&(ActType==9)&&(PlayerCount>1))));
					//SearchNext(!((!BotType)||(BotType==8)||(BotType==9)));
					//printf("%lld step=%d\n", this, SearchStep);
					int tt = Nxt.size();
					int ts=BotSpeed/2, d=ts/max(1,tt);
					for (Action o : Nxt) {
						int cur = 1;
						PendingActions.push_back(make_pair(o,cur*d));
					}
					if (!Nxt.empty()) PendingActions[0].second+=ts-d*tt;
					//printf("[%d,%d]\n",BotType,SearchStep);
					if (!Nxt.empty()) ActionTime=min(BotSpeed+5000,max(0,ActionTime-BotSpeed));
					else ActionCD=SegTime;
				}
				else {
					Actions=SearchNext(); ActionTime=min(BotSpeed+5000,max(0,ActionTime-BotSpeed));
				}
			}
			for (Action o : tmp) {
				if (AvailBotAction(o)) Actions.push_back(o);
			}
			if ((UseZone)&&(!CurZone)&&(ZoneProgress>=ZoneReq*((GetHeight()+AttackIn.size()<=SX)?min(ZoneSeg,2):1))) Actions.push_back(A_Action);
			G20 = false;
		}
		else {
			ActionTime=ActionCD=0; if ((!UseTip)||(TgD>=0)) SearchStep=-1;
			for (Monitor &o : Keys) {
				if (!o.Enabled) continue;
				if (o.Result==A_SoftDrop) {
					if ((!SDARR)&&(o.CheckDAS())) G20=true;
					if (o.CheckDAS()) SDInDAS=true;
				}
				if (o.Result==A_Left) {
					if ((!o.ARR)&&(o.CheckDAS())&&(!o.DCDRem)) L20=true;
				}
				if (o.Result==A_Right) {
					if ((!o.ARR)&&(o.CheckDAS())&&(!o.DCDRem)) R20=true;
				}
				if (o.Result==A_SonicDrop) {
					if ((CheckKey(o.Key))&&(o.CheckDAS())) G20=true;
				}
			}
		}
		if (((AutoRespawn)||((QPMode)&&(Bot)&&(PlayerCount>1)&&(Obs!=BoardID)))&&(Over)) Actions.push_back(A_Retry);
		if (RetryMsg) {
			Actions.push_back(A_Retry); RetryMsg=false;
		}
		if ((!Bot)&&(!CursorMode)&&(!Pause)&&(UseClick)&&(SelX)&&(CheckActive())) {
			bool PreCDown = CheckDown();
			bool ResetOper = false;
			int HalfLen = Cur.GetShapeMid();
			while ((UseMove)&&(PosY+HalfLen<SelY)&&(!CursorMoveLock)) {
				bool f=MoveRight(); ResetOper|=f;
				if (!f) break;
			}
			while ((UseMove)&&(PosY+HalfLen>SelY)&&(!CursorMoveLock)) {
				bool f=MoveLeft(); ResetOper|=f;
				if (!f) break;
			}
			if (RightUp) {
				CursorMoveLock = false;
				if (RightUp<200) {
					int UseRotate=DefaultRot, mndel=10;
					if (!CheckDown()) {
						for (int ii : {1,3,2}) {
							int i = ii;
							if ((i&1)&&(DefaultRot==3)) i^=2;
							if ((i==2)&&(!Use2Rot)) continue;
							CheckRotate(i); if (RotateToX<=-10) continue;
							int del = RotateToX-PosX;
							if (del<mndel) {
								mndel=del; UseRotate=i;
							}
						}
					}
					if ((UseRotate)||(Use2Rot)) {
						ResetOper |= Rotate(UseRotate);
						if (G20) {
							if (SonicDrop()) FallTime=0;
						}
					}
				}
			}
			if ((RightHold>=200)&&(!ClickHoldCD)) {
				ClickHoldCD=true; Actions.push_back(A_Hold);
			}
			if (!RightHold) ClickHoldCD=false;
			if (LeftUp) {
				if (CursorMoveLock) {
					if (Time>100LL) CursorMoveLock=false;
				}
				else if (LeftUp<200) ForceLock=true;
			}
			if (LeftHold>=200) {
				if (SDARR<1) {
					int f = SonicDrop();
					if (f) {
						ResetOper=true; FallTime=0;
					}
				}
				else if (CheckDown()) {
					FallTime += int(min(ll(DropDelay),ll(DropDelay)*ll(TimeDel)/ll(SDARR)));
					if (FallTime>=DropDelay) {
						MoveDown();
						if (!TargetSta) ++TScore;
						FallTime=0;
					}
				}
			}
			if ((ResetOper)&&((!CheckDown())||(!PreCDown))) {
				if (ResetRem>0) FallTime=0;
				if (ResetLim<Max) ResetRem=max(ResetRem-1, 0);
			}
			if (MidClick) Actions.push_back(A_Hold);
		}
		for (Action o : Actions) {
			bool ResetOper=false, PreCDown=CheckDown();
			if (o==A_Left) {
				CursorMoveLock = true;
				for (;;) {
					bool f = MoveLeft();
					if (!f) break;
					ResetOper = true;
					if (G20) {
						if (SonicDrop()) FallTime=0;
					}
					if (!L20) break;
				}
			}
			if (o==A_Right) {
				CursorMoveLock = true;
				for (;;) {
					bool f = MoveRight();
					if (!f) break;
					ResetOper = true;
					if (G20) {
						if (SonicDrop()) FallTime=0;
					}
					if (!R20) break;
				}
			}
			if (o==A_SoftDrop) {
				if ((!Bot)&&((G20)||((SDInDAS)&&(!SDARR)))) {
					if (SonicDrop(true)) FallTime=0;
				}
				else if ((Bot)||(!SDInDAS)||(SDARR<0)) {
					if (MoveDown()) {
						if (SmoothLevel>=3) {
							double FallRate = 1.0;
							if (DropDelay) FallRate=max(0.0,min(1.0,double(FallTime)/double(DropDelay)));
							PDx += 1.0-FallRate;
						}
						FallTime=0; if (!TargetSta) ++TScore;
					}
					else if ((UseDeep)&&(!Bot)) {
						if (DeepDrop()) FallTime=0;
					}
				}
				else {
					if (CheckDown()) {
						FallTime += int(min(ll(DropDelay),ll(DropDelay)*ll(TimeDel)/ll(SDARR)));
						if (FallTime>=DropDelay) {
							MoveDown();
							FallTime=0; if (!TargetSta) ++TScore;
						}
					}
				}
			}
			if (o==A_Rotate) {
				ResetOper = Rotate(DefaultRot);
				if (G20) {
					if (SonicDrop()) FallTime=0;
				}
			}
			if (o==A_CW) {
				ResetOper = Rotate(1);
				if (G20) {
					if (SonicDrop()) FallTime=0;
				}
			}
			if (o==A_Half) {
				ResetOper = Rotate(2);
				if (G20) {
					if (SonicDrop()) FallTime=0;
				}
			}
			if (o==A_CCW) {
				ResetOper = Rotate(3);
				if (G20) {
					if (SonicDrop()) FallTime=0;
				}
			}
			if (o==A_HardDrop) {
				if ((Bot)||(LastLock>=HCD)) HardDrop();
			}
			if (o==A_SonicDrop) {
				if (SonicDrop(true)) FallTime=0;
			}
			if (o==A_Hold) {
				if (!CheckActive()) continue;
				if (!Bot) HoldPosInc=0;
				if ((HoldType)&&(HoldLen)&&((HoldType==2)||(HoldRem>0))) {
					FillNext(); Piece NewP=Next[0];
					while (Hold.size()>HoldLen) Hold.pop_back();
					if (Hold.size()==HoldLen) NewP=Hold[0];
					if (CheckSpawn(NewP,HoldPosInc,CurClutch)) {
						Cur.SetDir(DefaultDir);
						HoldRem = max(0,HoldRem-1);
						bool ff = false;
						if (Hold.size()==HoldLen) {
							for (int i=0; i+1<HoldLen; ++i) Hold[i]=Hold[i+1];
							Hold[HoldLen-1] = Cur;
							ff = true;
						}
						else {
							Hold.push_back(Cur);
							for (int i=0; i+1<Next.size(); ++i) Next[i]=Next[i+1];
							Next.pop_back(); ++DNext;
						}
						Spawn(NewP,HoldPosInc,-1,CurClutch); ResetRem=ResetLim; FallTime=0;
						FillNext();
						if (((SmoothLevel>=4)&&((!Bot)||(BotSpeed>=100)))) {
							if (ff) {
								PDx+=1-(SX-PosX); PDy+=(-4)-PosY;
							}
							else {
								PDx+=1-(SX-PosX); PDy+=(LY-1)-PosY;
							}
						}
					}
				}
			}
			if (o==A_Retry) Reset=true;
			if (o==A_Up) {
				if (ResetRem) {
					if (MoveUp()) {
						if (ResetLim<Max) --ResetRem;
					}
				}
			}
			if (o==A_Reset) {
				if (CheckSpawn(Cur)) {
					Spawn(Cur); ResetRem=ResetLim; FallTime=0;
				}
			}
			if (o==A_Flip) {
				ResetOper = Rotate(0,GetFlip(Cur));
				if (G20) {
					if (SonicDrop()) FallTime=0;
				}
			}
			if (o==A_Waste) Waste();
			if (o==A_Lock) Lock();
			if (o==A_Action) {
				if ((!Over)&&(Time>=0LL)&&(ZoneProgress>=ZoneReq)) {
					bool f = true;
					for (int i=1; i<=LX; ++i) {
						for (int j=1; j<=LY; ++j) {
							if ((B[i][j]==GarbZone)||(B[i][j]==-GarbZone)) f=false;
						}
					}
					if (f) {
						CurZone=max(1,min(ZoneSeg,(ZoneReq)?ZoneProgress/ZoneReq:ZoneSeg)); ZoneRem=ZoneTime*CurZone;
					}
				}
			}
			if (o==A_Undo) {
				if (HistoryPos+1<History.size()) ReadHistory(History[++HistoryPos]);
			}
			if (o==A_Redo) {
				if (HistoryPos>0) ReadHistory(History[--HistoryPos]);
			}
			if (o==A_CursorMode) {
				if (UsePlace) CursorMode^=1;
				else {
					CursorMode=false; CursorMoveLock^=1;
				}
			}
			if (o==A_TargetMode) {
				if (PlayerCount>1) {
					int v = int(TargetType);
					if ((v>=1)&&(v<=7)) {
						++v; if (v>7) v=1;
						TargetType = ETargetType(v);
					}
				}
				else PaintType=(PaintType+1)%9;
			}
			if ((ResetOper)&&((!CheckDown())||(!PreCDown))) {
				if (ResetRem>0) FallTime=0;
				if (ResetLim<Max) ResetRem=max(ResetRem-1, 0);
			}
		}
		if ((UsePlace)&&(CursorMode)&&(!Bot)&&(!Over)&&(SelX)&&(Time>=0LL)) {
			SelX = max(1,min(SX,SelX));
			SelY = max(1,min(LY,SelY));
			if (CheckKey(VK_LBUTTON)) {
				if (!PlaceUID) PlaceUID=NewUID();
				if (!C[SelX][SelY]) {
					B[SelX][SelY]=GetPaintColor(); Age[SelX][SelY]=500; U[SelX][SelY]=PlaceUID; 
				}
				else {
					bool f = false;
					if ((!C[SelX-1][SelY])&&(MoveUp())) f=true;
					else if ((!C[SelX][SelY+1])&&(MoveLeft())) f=true;
					else if ((!C[SelX][SelY-1])&&(MoveRight())) f=true;
					else if ((!C[SelX+1][SelY])&&(MoveDown())) f=true;
					if (f) {
						B[SelX][SelY]=GetPaintColor(); Age[SelX][SelY]=500; U[SelX][SelY]=PlaceUID;
					}
				}
			}
			else PlaceUID=0;
			if (CheckKey(VK_RBUTTON)) {
				if (!C[SelX][SelY]) B[SelX][SelY]=Age[SelX][SelY]=U[SelX][SelY]=0;
			}
		}
		if ((ForceLock)&&(!Pause)) HardDrop();
		if ((Active)&&(!CheckActive())&&(!Over)) PreAREProcess();
		for (int i=1; i<=LX; ++i) {
			for (int j=1; j<=LY; ++j) {
				if (Age[i][j]<0) {
					if (InCascade) continue;
					else Age[i][j]=500;
				}
				Age[i][j] = max(Age[i][j]-TimeDel,0);
			}
		}
		if (Reset) ResetBoard();
		LineClearAge += TimeDel*(((LastClearType.second==LineClearText)||(!Mod8))?1:5);
		SpinAge += TimeDel*(((((SpinText==LastClearType.first)||((Mod8==2)&&(LastClearType.first=="X"))))||(!SpinGrade))?1:5);
		ComboAge += TimeDel*((ComboText)?1:5);
		B2BAge += TimeDel*((B2BText)?1:5);
		if (CurZone) ZoneRem=max(-1,ZoneRem-TimeDel);
		B2BBlinkRem = max(0,B2BBlinkRem-TimeDel);
		AllClearAge += TimeDel;
		LastLock += TimeDel;
		if (ComboRule==CR_C2) {
			C2ComboRem = max(0,C2ComboRem-TimeDel);
			if (!C2ComboRem) {
				if (Combo) Combo=ComboText=ComboAge=0;
			}
			else if (ComboText) ComboAge=max(0,3000-C2ComboRem/2);
		}
		else {
			C2ComboRem = 0;
			if ((Combo>=ComboText+1)&&(ComboText)) ComboAge=min(ComboAge,1500);
		}
		if (B2BText) B2BAge=min(B2BAge,1000);
		if (ShowParticle) {
			int tot = 0;
			vector<Particle> NewParticles;
			for (Particle o : Particles) {
				o.Update(TimeDel);
				if ((o.sz>=0.001)&&(abs(o.x)<=LX*2.0)&&(abs(o.y)<=LY*2.0)) NewParticles.push_back(o);
				if (++tot>=1000) break;
			}
			Particles = NewParticles;
			vector<pair<Particle,int> > NewDelayParticles;
			tot = 0;
			for (pair<Particle,int> &o : DelayParticles) {
				o.second = max(0,o.second-TimeDel);
				if (o.second<=0) Particles.push_back(o.first);
				else NewDelayParticles.push_back(o);
				if (++tot>=1000) break;
			}
			DelayParticles = NewDelayParticles;
			vector<FloatText> NewFloatTexts;
			tot = 0;
			for (FloatText &o : FloatTexts) {
				o.Update(TimeDel);
				o.Update(TimeDel);
				if ((o.sz>=0.001)&&(o.bd>=0.001)&&(abs(o.x)<=LX*2.0)&&(abs(o.y)<=LY*2.0)) NewFloatTexts.push_back(o);
				if (++tot>=1000) break;
			}
			FloatTexts = NewFloatTexts;
		}
		else {
			Particles.clear(); DelayParticles.clear(); FloatTexts.clear();
		}
		if (TScore<=DScore) DScore=TScore;
		else {
			ll del = max(200LL,TScore-DScore)*ll(TimeDel)/200LL;
			DScore = min(TScore, DScore+del);
		}
		if (TQPScore<=DQPScore) DQPScore=TQPScore;
		else {
			ll del = max(200LL,TQPScore-DQPScore)*ll(TimeDel)/200LL;
			DQPScore = min(TQPScore, DQPScore+del);
		}
		DecD(PDth,TimeDel); DecD(PDx,TimeDel); DecD(PDy,TimeDel); DecD(DNext,TimeDel,0.5);
		K-=tK; PX-=tPX; PY-=tPY;
		DecD(K,RealTimeDel); DecD(PX,RealTimeDel); DecD(PY,RealTimeDel);
		K+=tK; PX+=tPX; PY+=tPY;
		if (GarbageActive==GA_Instant) ActivateGarbage();
		if (PushCD) {
			int val = min(TimeDel,PushCD);
			PushCD -= val;
			if ((!Bot)&&(CheckDown())&&(DropDelay>0)&&(GarbagePush)&&(SmoothLevel>=1))
				FallTime+=int(min(ll(DropDelay),ll(val)*ll(DropDelay)/ll(GarbagePush)));
		}
		if ((AttackIn.size())&&(AttackIn[0].Delay<0)&&((!CurZone)||(!ZoneReq))&&(!PushCD)) {
			AddGarbage(AttackIn[0]);
			for (int i=1; i<AttackIn.size(); ++i) AttackIn[i-1]=AttackIn[i];
			PushCD=GarbagePush; AttackIn.pop_back();
		}
		if ((!PushCD)&&(StaticGarbHeight)) {
			int h = ((GarbageType==GT_Bricks)?GetHeight():GetGarbHeight())-((GarbageType==GT_Solid)?0:GetRiseHeight());
			if (h<StaticGarbHeight) {
				if (randf()*1000.0<SegMess) RerollHole();
				AddGarbage(RollGarbage()); PushCD=GarbagePush;
			}
		}
		for (GarbageLayer &o : AttackIn) {
			if (o.Delay>=0) o.Delay=max(0,o.Delay-TimeDel);
		}
		InList.clear(); InList.insert(Cur.GetHash());
		for (Piece &o : Next) InList.insert(o.GetHash());
		for (Piece &o : Hold) InList.insert(o.GetHash());
		for (Piece &o : Pool) InList.insert(o.GetHash());
		for (Piece &o : Gen.Pool) InList.insert(o.GetHash());
		for (Piece &o : Gen.RemPool) InList.insert(o.GetHash());
		if (UseUndo) {
			for (BoardHistory &o : History) o.MarkIn();
		}
		vector<Piece*> NewCustomPiece;
		for (Piece *p : CustomPiece) {
			if (InList.count(p->GetHash())) NewCustomPiece.push_back(p);
			else delete p;
		}
		CustomPiece = NewCustomPiece;
		if ((!Pause)&&(StartMsg>=0)&&(InactiveTime<=1000*StartMsg)) {
			AddFloatText((StartMsg)?IToS(StartMsg):"GO", 0xDDDDDD, 3.5,
			0.2, 0.15, 0.3, SX*0.5-2, LY*0.5-0.7, 0, 0, 0, 0);
			AddFloatText((StartMsg)?IToS(StartMsg):"GO", 0xFFE211, 3.5,
			0.2, 0.14, 0.3, SX*0.5-2, LY*0.5-0.7, 0, 0, 0, 0);
			--StartMsg;
		}
	}
	int GetAIUrgency(int TimeDel) {
		if ((!Bot)||(Over)||(Pause)||(!PendingActions.empty())) return -1;
		if (ActionTime+TimeDel<BotSpeed) return -1;
		if (InactiveTime>0) return -1;
		if (ActionCD>0) return -1;
		if ((!BoardID)&&(QPMode)) return 100000002;
		if (PlayerCount<=2) return 100000002;
		if (SearchStep!=-1) return 100000001;
		return min(10000000,ActionTime+TimeDel-BotSpeed);
	}
};
vector<pair<int,int> > Board::TmpKick[4][4];
vector<Board*> Boards;
vector<vector<Board*> > Targets, TargetFrom;
void ArrangeBoards(int Obs=-1) {
	int n=Boards.size(), an=0;
	if (n==1) {
		for (Board *p : Boards) p->SetScale(0.0,DefaultScale*0.5,DefaultScale);
		return;
	}
	if ((QPMode)&&((Obs>=0)&&(Obs<n))) {
		for (int i=0; i<n; ++i) {
			if (i==Obs) Boards[i]->SetScale(0.0,DefaultScale*0.5,DefaultScale);
			else Boards[i]->SetScale(0.0,0.0,0.0001);
		}
		return;
	}
	vector<Board*> SelectBoards; int sn=0;
	for (Board *p : Boards) {
		if (!p->IsOver()) ++an;
		if ((p->IsOver())&&(p->GetBoardID()!=Obs)&&(p->IsBot())) p->SetScale(0.0,0.0,0.0001);
		else {
			SelectBoards.push_back(p); ++sn;
		}
	}
	if (an<2) {
		SelectBoards=Boards; sn=Boards.size();
	}
	if ((Obs>=0)&&(Obs<n)) {
		int m=1, p=0;
		while (m*m<sn-1) ++m;
		Boards[Obs]->SetScale(0.0,3.0,DefaultScale);
		double GridScale = DefaultScale/double(m);
		for (int i=0; i<m; ++i) {
			for (int j=0; j<m; ++j) {
				if (p>=sn) break;
				if (SelectBoards[p]->GetBoardID()==Obs) ++p;
				if (p<sn) SelectBoards[p]->SetScale(GridScale*i,DefaultScale+GridScale*j,GridScale);
				++p;
			}
		}
	}
	else {
		int m=1, p=0;
		while (m*(m*2-1)<sn) ++m;
		double GridScale = DefaultScale/double(m);
		for (int i=0; i<m; ++i) {
			for (int j=0; j<m*2-1; ++j) {
				if (p<sn) SelectBoards[p]->SetScale(GridScale*i,GridScale*(j+1),GridScale);
				++p;
			}
		}
	}
}
int Board::V[110][110], Board::SegTreeM[20010], Board::SegTreeP[20010], Board::Dis[10010];
vector<pair<int,int> > Board::Graph[10010]; bool Board::Vis[10010];
vector<string> ForceCommand;

struct Button {
	double x, y, lx, ly, tsz; int clr, selclr, tclr, tselclr;
	string Text, Result;
};
vector<Button> Buttons;
vector<ConfigContent> GlobalConfig;
int GetConfig(vector<ConfigContent> &From, string Name, int Default) {
	for (ConfigContent o : From) {
		if (ToLower(o.first)==ToLower(Name)) {
			int tmp = SToI(o.second);
			if (tmp!=SToIErr) return tmp;
		}
	}
	return Default;
}
string GetConfig(vector<ConfigContent> &From, string Name, string Default, vector<string> List) {
	for (ConfigContent o : From) {
		if (ToLower(o.first)==ToLower(Name)) {
			for (string x : List) {
				if (ToLower(x)==ToLower(o.second)) return ToLower(x);
			}
		}
	}
	return ToLower(Default);
}
bool IsHandling(string Name) {
	Name = ToLower(Name);
	if ((Name.size()>3)&&(Name.substr(0,3)=="key")) return true;
	return ((Name=="das")||(Name=="arr")||(Name=="sddas")||(Name=="sdarr")||
	(Name=="hcd")||(Name=="dcd")||(Name=="defaultrot")||(Name=="smoothlevel"));
}
vector<ConfigContent> GetHandling() {
	vector<ConfigContent> res;
	for (ConfigContent o : GlobalConfig) {
		if (IsHandling(o.first)) res.push_back(o);
	}
	return res;
}
void SetConfig(string Name, string val) {
	Name = ToLower(Name);
	for (ConfigContent &o : GlobalConfig) {
		if (ToLower(o.first)==Name) {
			o.second=ToLower(val); return;
		}
	}
	GlobalConfig.push_back(make_pair(Name,ToLower(val)));
}
bool CheckButton(Button &o) {
	return ((fabs(CurX-o.x)<=o.lx*0.5)&&(fabs(CurY-o.y)<=o.ly*0.5));
}
void AddButton(string Text, string Result, double x, double y, double lx, double ly, double tsz, int clr, int selclr, int tclr, int tselclr) {
	if (selclr<0) selclr=clr;
	Buttons.push_back((Button){x+tsz*0.5,y+tsz*0.33,lx,ly,tsz,clr,selclr,tclr,tselclr,Text,Result});
}
int AdjustDel = 100;
const vector<int> AdjustDels = {1,2,5,10,20,50,100,200,500,1000};

queue<ll> FrameQueue, RenderQueue;
vector<ConfigContent> NewGameConfig;
void AddConfig(vector<ConfigContent> &Dest, vector<ConfigContent> From) {
	for (ConfigContent o : From) Dest.push_back(o);
}
vector<ConfigContent> GetHandlingConfig(vector<ConfigContent> From) {
	vector<ConfigContent> res;
	for (ConfigContent o : From) {
		string Name = ToLower(o.first);
		if ((Name=="das")||(Name=="arr")||(Name=="sddas")||(Name=="sdarr")||(Name=="defaultrot")||
		(Name=="hcd")||(Name=="dcd")||(Name=="smoothlevel")||((Name.size()>3)&&(Name.substr(0,3)=="key")))
			res.push_back(o);
	}
	return res;
}
vector<Piece> SelectPool=DefaultPool, EditPool;
string SelKeyOpt;
bool ZenithBot = false;
void Launch(string _Menu, vector<ConfigContent> Config={}, vector<ConfigContent> ExtraConfig={}) {
	//printf("!%d\n",QPMode);
	QPMode=ZenithBot=false;
	if (_Menu=="qp") {
		_Menu="ingame"; QPMode=true;
	}
	Pause = false;
	Menu = ToLower(_Menu);
	if (Menu=="home") AddConfig(Config,HomeConfig);
	if (Menu=="settings") AddConfig(Config,SettingConfig);
	if ((Menu=="select")||(Menu=="pool")) AddConfig(Config,SelectConfig);
	AddConfig(Config, ExtraConfig);
	if (Menu=="pool") EditPool=SelectPool;
	if (Menu=="poolback") Menu="pool";
	if (Menu=="custom") {
		AddConfig(Config,GlobalConfig); Menu="ingame";
	}
	else AddConfig(Config,GetHandlingConfig(GlobalConfig));
	SelKeyOpt = "";
	for (Board *p : Boards) delete p;
	Boards.clear(); Targets.clear();
	NewGameConfig = Config;
	vector<Piece> Pool = (Menu=="ingame")?SelectPool:DefaultPool;
	if (Menu=="home") Pool=HomePool;
	int Players=PlayerCount=max(0,min(1001,GetConfig(NewGameConfig,"Player",1)));
	if ((Menu=="pool")||(Menu=="piece")) Players=PlayerCount=0;
	if (!QPMode) QPMode=GetConfig(NewGameConfig,"QPMode",0);
	int Seed = int(GetClock())+BeginTime;
	bool AutoBotType = (GetConfig(NewGameConfig,"BotType",-1)<0);
	bool AllBot = GetConfig(NewGameConfig,"AllBot",0)||GetConfig(NewGameConfig,"Bot",0);
	int bs0 = GetConfig(NewGameConfig,"0BotSpeed",-1);
	int bt0 = GetConfig(NewGameConfig,"0BotType",-1);
	ZenithBot = GetConfig(NewGameConfig,"ZenithBot",0);
	for (int i=0; i<Players; ++i) {
		vector<ConfigContent> BoardConfig;
		for (ConfigContent o : Config) {
			string str=o.first, pre=IToS(i)+"_";
			if ((str.size()>=pre.size())&&(str.substr(0,pre.size())==pre))
				BoardConfig.push_back(ConfigContent(str.substr(pre.size(),str.size()-pre.size()),o.second));
		}
		if ((bs0>=0)&&(!i)) BoardConfig.push_back((ConfigContent){"BotSpeed",bs0});
		if ((bt0>=0)&&(!i)) BoardConfig.push_back((ConfigContent){"BotType",bt0});
		else if (AutoBotType) {
			int tp=0, d=i%3;
			if (!i) tp=1;
			else if (!d) tp=5;
			else if (d==1) tp=9;
			else tp=8;
			if (Players<=5) tp=1;
			if ((Players>=5)&&(QPMode)&&(ZenithBot)&&(i)) tp=9;
			BoardConfig.push_back((ConfigContent){"BotType",tp});
		}
		if ((ZenithBot)&&(i)) {
			BoardConfig.push_back((ConfigContent){"BotSpeed",521});
			BoardConfig.push_back((ConfigContent){"BotType",9});
		}
		BoardConfig.push_back((ConfigContent){"Bot",((i)||(AllBot))});
		if ((!ZenithBot)||(!i)) {
			for (ConfigContent o : Config) BoardConfig.push_back(o);
		}
		Boards.push_back(new Board(i,BoardConfig,Pool,Seed,(Players>2)?double(i)/double(Players):0.0));
	}
	if ((ZenithBot)&&(Players>=10)) {
		for (int i=1; i<Players; ++i) {
			if (Boards[i]->IsBot()) {
				double rat = double(i)/double(Players);
				rat = pow(rat,1.2);
				Boards[i]->SetTime(420000*rat);
				Boards[i]->SetQPScore(800000*rat);
			}
		}
	}
	Targets.resize(Players);
	Obs = -1;
	for (int i=0; i<Players; ++i) {
		if (!Boards[i]->IsBot()) Obs=i;
	}
	ArrangeBoards(); 
}

vector<Monitor> Keys = {Monitor(A_Pause,'O'),Monitor(A_Click,VK_LBUTTON),Monitor(A_RightClick,VK_RBUTTON),
Monitor(A_MidClick,VK_MBUTTON),Monitor(A_Quit,VK_ESCAPE)};
vector<string> Split(string Input) {
	vector<string> res; string cur;
	for (char ch : Input) {
		if ((!ch)||(ch==' ')) {
			if (!cur.empty()) {
				res.push_back(cur); cur="";
			}
		}
		else cur+=ch;
	}
	if (!cur.empty()) res.push_back(cur);
	return res;
}
void KeyBind(string Type, string val) {
	Type=ToLower(Type); val=ToLower(val);
	Action Tar; bool f=false;
	for (pair<string,Action> o : KeyToAct) {
		if (ToLower(o.first)==Type) {
			f=true; Tar=o.second; break;
		}
	}
	if (!f) return;
	for (Monitor &o : Keys) {
		if (o.Result==Tar) o.Key=AskKey(val);
	}
}
string SelTab="basic", KeyTab="basic";
bool EditPiece[99][99];
int EditPieceSize=3, EditPieceR=102, EditPieceG=204, EditPieceB=255;
void Execute(string Input) {
	vector<string> Args, spl=Split(Input);
	if (spl.empty()) return;
	string Command = spl[0];
	for (int i=1; i<spl.size(); ++i) Args.push_back(spl[i]);
	while (Args.size()<100) Args.push_back("");
	if (Command=="quit") {
		PostQuitMessage(0); return;
	}
	if (Command=="start") Launch("ingame");
	if (Command=="startcustom") {
		vector<ConfigContent> ExtraConfig;
		for (int i=0; i<90; i+=2) {
			string s=Args[i], t=Args[i+1];
			if (t.empty()) break;
			ExtraConfig.push_back(make_pair(ToLower(s),ToLower(t)));
		}
		Launch("custom", {}, ExtraConfig);
	}
	if (Command=="settings") Launch("settings");
	if (Command=="home") Launch("home");
	if (Command=="select") Launch("select");
	if (Command=="selkey") {
		if (Menu=="settings") {
			if (!Args[0].empty()) SelKeyOpt=(SelKeyOpt==Args[0])?"":Args[0];
		}
	}
	if (Command=="choosekey") {
		if ((!SelKeyOpt.empty())&&(!Args[0].empty())) {
			SetConfig(SelKeyOpt, Args[0]);
			if (SelKeyOpt=="keypause") KeyBind(SelKeyOpt,Args[0]);
			for (Board *p : Boards) p->KeyBind(SelKeyOpt,Args[0]);
		}
		SelKeyOpt = "";
	}
	if (Command=="adjustdel") {
		int p = -1;
		for (int i=0; i<AdjustDels.size(); ++i) {
			if (AdjustDels[i]==AdjustDel) p=i;
		}
		if (p<0) return;
		if (Args[0]=="inc") AdjustDel=AdjustDels[min(int(AdjustDels.size()-1),p+1)];
		if (Args[0]=="dec") AdjustDel=AdjustDels[max(0,p-1)];
	}
	if (Command=="adjust") {
		int Default = SToI(Args[1]);
		if (Default==SToIErr) return;
		string Name = ToLower(Args[0]);
		int val = GetConfig(GlobalConfig,Name,Default);
		int Lower=0, Upper=Max;
		int del = AdjustDel;
		if (!Args[3].empty()) {
			Lower=SToI(Args[3]); if (Lower==SToIErr) Lower=0;
		}
		if (!Args[4].empty()) {
			Upper=SToI(Args[4]); if (Upper==SToIErr) Upper=Max;
		}
		int mod = 0;
		if (!Args[5].empty()) {
			del = SToI(Args[5]);
			if (del==SToIErr) del=AdjustDel;
			else if (del<0) {
				mod=max(0,Upper+1); del=-del;
			}
		}
		if (((val)&&(Args[2]!="def"))||(mod)) Lower=max(0,Lower);
		if (val<0) Upper=min(0,Upper);
		if (Args[2]=="inc") val+=del;
		if (Args[2]=="dec") val-=del;
		if (Args[2]=="def") val=Default;
		if (mod) val=(val%mod+mod)%mod;
		val = max(Lower,min(Upper,val));
		SetConfig(Name, IToS(val));
		for (Board *p : Boards) p->ChangeHandling(Name,val);
	}
	if (Command=="adjustdefrot") {
		int val = GetConfig(GlobalConfig,"DefaultRot",3);
		val = (val==1)?3:1;
		SetConfig("DefaultRot", IToS(val));
		for (Board *p : Boards) p->SetDefaultRot(val);
	}
	if (Command=="adjustsmooth") {
		int val = GetConfig(GlobalConfig,"SmoothLevel",3);
		val = (val+4)%5;
		SetConfig("SmoothLevel", IToS(val));
		for (Board *p : Boards) p->ChangeHandling("smoothlevel",val);
	}
	if (Command=="poolins") {
		if (EditPool.size()>=10000) return;
		Piece p = P_Empty;
		int i = max(0,SToI(Args[0]));
		if (PieceList.size()>i) p=PieceList[i];
		vector<Piece> ins = {p};
		if (p.GetRealName()=="Bag3") ins={P_C,P_D};
		if (p.GetRealName()=="Bag4") ins=TetraminoPool;
		if (p.GetRealName()=="Bag5") ins=PentominoPool;
		if (p.GetRealName()=="Bag6") ins=HexaminoPool;
		for (Piece pp : ins) EditPool.push_back(pp);
	}
	if (Command=="custompoolins") {
		if (EditPool.size()>=10000) return;
		int i = max(0,SToI(Args[0]));
		if (i<CustomPiece.size()) EditPool.push_back(*CustomPiece[i]);
	}
	if (Command=="clearconfig") {
		GlobalConfig=GetHandling(); SelectPool={P_I,P_J,P_L,P_O,P_S,P_T,P_Z};
	}
	if (Command=="pool") Launch("pool");
	if (Command=="poolback") Launch("poolback");
	if (Command=="pooldel") {
		int i = max(0,SToI(Args[0]));
		if (EditPool.size()<=i) return;
		for (int j=i; j+1<EditPool.size(); ++j) EditPool[j]=EditPool[j+1];
		EditPool.pop_back();
	}
	if (Command=="poolclear") EditPool.clear();
	if (Command=="poolconfirm") {
		SelectPool = (EditPool.empty())?DefaultPool:EditPool;
		Launch("select");
	}
	if (Command=="pooldefault") SelectPool=DefaultPool;
	if (Command=="seltab") SelTab=Args[0];
	if (Command=="selkeytab") {
		KeyTab=Args[0]; SelKeyOpt="";
	}
	if (Command=="pause") Pause^=1;
	if (Command=="retry") {
		for (Board *p : Boards) p->ForceRetry();
	}
	if (Command=="piece") Launch("piece");
	if (Command=="colorr") EditPieceR=max(0,min(255,SToI(Args[0])));
	if (Command=="colorg") EditPieceG=max(0,min(255,SToI(Args[0])));
	if (Command=="colorb") EditPieceB=max(0,min(255,SToI(Args[0])));
	if (Command=="piecegrid") {
		int x=SToI(Args[0]), y=SToI(Args[1]);
		if ((x>=0)&&(y>=0)&&(x<EditPieceSize)&&(y<EditPieceSize)) EditPiece[x][y]^=1;
	}
	if (Command=="clearcustompiece") memset(EditPiece,0,sizeof(EditPiece));
	if (Command=="incpiecesize") EditPieceSize=min(10,EditPieceSize+1);
	if (Command=="decpiecesize") EditPieceSize=max(1,EditPieceSize-1);
	if (Command=="addcustompiece") {
		if (CustomPiece.size()>=10000) return;
		int n = EditPieceSize;
		vector<vector<int> > B(n,vector<int>(n));
		bool f = false;
		bool HalfSame=true, OType=true;
		for (int i=0; i<n; ++i) {
			for (int j=0; j<n; ++j) {
				if (EditPiece[i][j]!=EditPiece[j][n-i-1]) OType=false;
				if (EditPiece[i][j]!=EditPiece[n-i-1][n-j-1]) HalfSame=false;
				if (EditPiece[i][j]) {
					B[i][j]=1; f=true;
				}
			}
		}
		if (f) {
			string Name = ":"+IToS(CustomPiece.size());
			if (OType) Name=((n>3)?'O':'o')+Name;
			else if (HalfSame) Name='h'+Name;
			int Color = max(1,(EditPieceR<<16)|(EditPieceG<<8)|EditPieceB);
			CustomPiece.push_back(new Piece(n,B,Color,Name));
		}
		Launch("poolback");
	}
}
void EscapeCur() {
	if (Menu=="ingame") ForceCommand.push_back("select");
	if (Menu=="settings") {
		if (SelKeyOpt.empty()) ForceCommand.push_back("home");
		else SelKeyOpt="";
	}
	if (Menu=="select") ForceCommand.push_back("home");
	if (Menu=="pool") ForceCommand.push_back("select");
	if (Menu=="piece") ForceCommand.push_back("poolback");
}
void Frame_Model(int TimeDel) {
	int n=PlayerCount=Boards.size(), SelBoard=-1;
	for (Monitor &o : Keys) {
		int r = o.Output(TimeDel);
		if (!r) continue;
		if (o.Result==A_Pause) Pause^=1;
		if (o.Result==A_Click) {
			for (int i=0; i<n; ++i) {
				if (Boards[i]->CheckCursor()) SelBoard=i;
			}
			for (Button &o : Buttons) {
				if (CheckButton(o)) {
					ForceCommand.push_back(o.Result); SelBoard=-1;
				}
			}
			LeftClick = true;
		}
		if (o.Result==A_RightClick) RightClick=true;
		if (o.Result==A_MidClick) MidClick=true;
		if (o.Result==A_Quit) EscapeCur();
	}
	if (SelBoard>=0) {
		bool f = false;
		if (Obs==-1) f=true;
		else if (SelBoard==Obs) {
			if (!Boards[Obs]->ObserveLevel()) Obs=-1;
		}
		else if (Boards[Obs]->ObserveLevel()<=Boards[SelBoard]->ObserveLevel()) f=true;
		if (f) Obs=SelBoard;
	}
	if ((SelBoard>=0)&&(Menu=="home")&&(Boards.size())) Boards[0]->ToggleBot();
	if ((Obs==-1)||(!Boards[Obs]->ObserveLevel())) {
		for (int i=0; i<n; ++i) {
			if (Boards[i]->ObserveLevel()) Obs=i;
		}
	}
	AliveCount = 0;
	for (Board *p : Boards) {
		if (!p->IsOver()) ++AliveCount;
	}
	for (Board *p : Boards) {
		if (!p->IsOver()) p->Rank=min(p->Rank,AliveCount);
	}
	if ((ZenithBot)&&(PlayerCount>1)) Obs=0;
	vector<pair<Board*,int> > UseList;
	for (Board *p : Boards) {
		int k = p->GetAIUrgency(TimeDel);
		if (k>=0) UseList.push_back(make_pair(p,k));
	}
	shuffle(UseList.begin(), UseList.end(), GlobalGen);
	stable_sort(UseList.begin(), UseList.end(), [](pair<Board*,int> p, pair<Board*,int> q) {
		return p.second>q.second;
	});
	while ((!UseList.empty())&&(UseList.size()>FrameAILimit)) UseList.pop_back();
	vector<Board*> AlivePlayers;
	ll cl = GetClock();
	while ((!FrameQueue.empty())&&((FrameQueue.front()+1000LL<=cl)||(FrameQueue.size()>11000))) FrameQueue.pop();
	FrameQueue.push(cl);
	int LatestClock = int(GetClock());
	for (Board *p : Boards) {
		bool PermitSearch = false;
		for (pair<Board*,int> q : UseList) {
			if (p==q.first) PermitSearch=true;
		}
		p->Frame(TimeDel,LatestClock,PermitSearch); if (!p->IsOver()) AlivePlayers.push_back(p);
	}
	ShowAttack = (n>1);
	TargetFrom = vector<vector<Board*> >(n);
	for (int i=0; i<n; ++i) {
		if (Boards[i]->IsOver()) continue;
		for (Board* p : Targets[i]) {
			int id = p->GetBoardID();
			if ((i!=id)&&(id>=0)&&(id<n)) TargetFrom[id].push_back(Boards[i]);
		}
	}
	vector<int> QPTargetList;
	if (QPMode) {
		for (int i=0; i<n; ++i) {
			int w = Boards[i]->GetQPTargetWeight();
			for (int j=0; j<w; ++j) QPTargetList.push_back(i);
		}
		shuffle(QPTargetList.begin(), QPTargetList.end(), GlobalGen);
	}
	for (int i=0; i<n; ++i) {
		Board *p=Boards[i]; if (p->IsOver()) continue;
		ETargetType tp = p->GetTargetType();
		vector<Board*> NewTarget;
		if (tp==TT_None) ;
		else if (tp==TT_Backfire) NewTarget.push_back(p);
		else if (QPMode) {
			int p = -1;
			for (int j=0; j<QPTargetList.size(); ++j) {
				if (QPTargetList[j]!=i) {
					p=j; break;
				}
			}
			if (p!=-1) {
				swap(QPTargetList.back(), QPTargetList[p]);
				NewTarget.push_back(Boards[QPTargetList.back()]);
				QPTargetList.pop_back();
			}
		}
		else {
			vector<pair<Board*,int> > Candidates;
			for (Board *q : AlivePlayers) {
				if (p!=q) Candidates.push_back(make_pair(q,0)); 
			}
			int m = Candidates.size();
			bool UseRandom = false;
			if (tp==TT_All) {
				for (pair<Board*,int> q : Candidates) NewTarget.push_back(q.first);
			}
			if (tp==TT_Random) UseRandom=true;
			if ((tp==TT_Even)||(tp==TT_Elimination)||(tp==TT_Badges)) {
				for (pair<Board*,int> &q : Candidates) {
					if (tp==TT_Even) q.second=-q.first->GetStackHeight();
					if (tp==TT_Elimination) q.second=q.first->GetStackHeight();
					if (tp==TT_Badges) q.second=q.first->GetBadge();
				}
				shuffle(Candidates.begin(), Candidates.end(), GlobalGen);
				stable_sort(Candidates.begin(), Candidates.end(), [](pair<Board*,int> p, pair<Board*,int> q) {
					return p.second>q.second;
				});
				int w=10, tot=0;
				for (pair<Board*,int> &o : Candidates) {
					o.second=max(0,w); --w; tot+=o.second;
				}
				if (tot) {
					int sel = rand()%tot;
					for (pair<Board*,int> &o : Candidates) {
						sel -= o.second;
						if (sel<0) {
							NewTarget.push_back(o.first); break;
						}
					}
				}
				else UseRandom=true;
			}
			if (tp==TT_Payback) {
				int id = p->GetLastAttacker();
				if ((id>=0)&&(id<n)) {
					if (!Boards[id]->IsOver()) NewTarget.push_back(Boards[id]);
					else UseRandom=true;
				}
				else UseRandom=true;
			}
			if (tp==TT_Attackers) {
				NewTarget = TargetFrom[i];
				if (NewTarget.empty()) UseRandom=true;
			}
			if ((UseRandom)&&(m)) NewTarget.push_back(Candidates[rand()%m].first);
		}
		Targets[i] = NewTarget;
	}
	for (int i=0; i<n; ++i) {
		Board *p = Boards[i];
		vector<int> Attack = p->SendAttack();
		int m=Targets[i].size(); if (!m) continue;
		int tot = 0;
		for (int x : Attack) {
			shuffle(Targets[i].begin(), Targets[i].end(), GlobalGen);
			int val=min(x,5000-tot); tot+=val;
			for (int j=0; j<m; ++j) {
				int d = val/m+(j<val%m);
				if (!d) continue;
				if (QPMode) {
					double FromH=Boards[i]->GetQPScoreF(), ToH=Targets[i][j]->GetQPScoreF();
					FromH = max(FromH,min(ToH*max(0.2,1.0-ToH/5000.0)*(1.0-0.2*double(Targets[i][j]->Mod9)),max(1650.0,ToH*0.5)));
					double pos = max(min(3500.0,FromH),min(ToH-1000.0,2000.0));
					double mul = 1.0;
					ll tm = Targets[i][j]->GetTime();
					if (Targets[i][j]->Mod9==2) tm-=2*20000LL;
					if (tm>=9*60000LL) mul=1.5;
					else if (tm>=7*60000LL) mul=1.25;
					mul += Targets[i][j]->GetQPAttackWeightInc();
					if (ToH>=pos) mul*=(1.0+0.002*(ToH-pos)*(ToH-pos)/max(1.0,ToH+pos));
					if (Targets[i][j]->Mod5==1) mul*=2.0;
					if (Targets[i][j]->Mod5==2) mul*=3.0;
					//if (Targets[i][j]->Mod6==2) mul*=0.5;
					if (abs(mul-1.0)>0.0001) {
						double nd=d*mul; int fl=int(floor(nd));
						d = max(d,fl+(randf()<nd-fl));
					}
				}
				if (d) Targets[i][j]->ReceiveAttack(d,i);
			}
		}
	}
	for (int i=0; i<n; ++i) {
		int From=Boards[i]->GetKOMessage(); if (From<-1) continue;
		if (From>=0) {
			++Boards[From]->KOCount; Boards[From]->Badge+=Boards[i]->Badge+1;
			if (QPMode) Boards[From]->GiveQPScore(((Boards[From]->Mod9==2)||(Boards[i]->ModS))?8000LL:15000LL);
		}
		if ((i==Obs)&&(!QPMode)) {
			int p = Obs;
			for (int k=1; k<=100; ++k) {
				if (p<0) break;
				if (Boards[p]->IsOver()) p=Boards[p]->GetLastAttacker();
				else break;
			}
			if ((p<0)||(Boards[p]->IsOver())) Obs=-1;
			else Obs=p;
		}
	}
	for (string str : ForceCommand) Execute(str);
	ForceCommand.clear();
}
void Init() {
	/* Battle Config
	Players : 100,
	BotType : Default,
	BotSpeed : 300 (3.33pps) */
	/* Zenith Config
	Players : 16,
	BotType : D_T1,
	BotSpeed : 521 (1.92pps) */
	srand(time(0));
	InitKick(); InitText(); InitChar();
	Launch("home");
	SelectPool = {P_I,P_J,P_L,P_O,P_S,P_T,P_Z};
	FrameAILimit = 1;
}
void ReadCustomPiece() {
	FILE *f = fopen("custompiece.zkygt","r");
	if (f==nullptr) return;
	int tot; fscanf(f,"%d",&tot); tot=max(tot,0);
	while (tot--) {
		int n, Color; fscanf(f,"%d%d",&n,&Color);
		vector<vector<int> > B(n,vector<int>(n));
		for (int i=0; i<n; ++i) {
			for (int j=0; j<n; ++j) {
				int x; fscanf(f,"%d",&x); B[i][j]=(x)?1:0;
			}
		}
		bool HalfSame=true, OType=true;
		for (int i=0; i<n; ++i) {
			for (int j=0; j<n; ++j) {
				if (B[i][j]!=B[j][n-i-1]) OType=false;
				if (B[i][j]!=B[n-i-1][n-j-1]) HalfSame=false;
			}
		}
		string Name = ":"+IToS(CustomPiece.size());
		if (OType) Name=((n>3)?'O':'o')+Name;
		else if (HalfSame) Name='h'+Name;
		CustomPiece.push_back(new Piece(n,B,Color,Name));
	}
	fclose(f);
}
void ReadConfig() {
	ReadCustomPiece();
	FILE *f = fopen("config.zkygt","r");
	if (f==nullptr) return;
	vector<string> lst;
	while (fgets(PlaceholderString,5033,f)) {
		string tmp(PlaceholderString), str;
		for (char ch : tmp) {
			if ((ch>=33)&&(ch<127)) str.push_back(ch);
		}
		if (!str.empty()) lst.push_back(str);
	}
	for (int i=0; i+1<lst.size(); i+=2) GlobalConfig.push_back(ConfigContent(lst[i],lst[i+1]));
	fclose(f);
}
void WriteCustomPiece() {
	FILE *f = fopen("custompiece.zkygt","w");
	if (f==nullptr) return;
	fprintf(f, "%d\n", min(100,int(CustomPiece.size())));
	int beg = max(0,int(CustomPiece.size())-100);
	for (int i=beg; i<CustomPiece.size(); ++i) {
		Piece p=*CustomPiece[i]; int n=p.GetLX();
		fprintf(f, "%d %d\n", n, p.GetColor());
		for (int ii=0; ii<n; ++ii) {
			for (int jj=0; jj<n; ++jj) fprintf(f,"%d ",(p.GetGrid(n-ii-1,jj))?1:0);
			fprintf(f, "\n");
		}
	}
	fclose(f);
}
void WriteConfig() {
	WriteCustomPiece();
	FILE *f = fopen("config.zkygt","w");
	if (f==nullptr) return;
	for (ConfigContent o : GlobalConfig) fprintf(f,"%s\n%s\n",o.first.c_str(),o.second.c_str());
	fclose(f);
}

#warning View_Part

namespace PaintManager {
	const int PS = 20;
	void Plot(double x, double y, int clr) {
		int B=clr&255; clr>>=8;
		int G=clr&255; clr>>=8;
		int R = clr&255;
		double px = 1.0-x/double(PS);
		double py = y/double(PS);
		glColor3f(min(1.0,max(0.0,double(R)/255.0)), min(1.0,max(0.0,double(G)/255.0)), min(1.0,max(0.0,double(B)/255.0)));
		glVertex2f(py*2.0/3.0-1.0, px);
	}
	pair<double,double> RotatePos(double x, double y, double th, double rx, double ry) {
		if (fabs(th)<0.001) return make_pair(x,y);
		double d = sqrt((x-rx)*(x-rx)+(y-ry)*(y-ry));
		if (d<=0.001) return make_pair(x,y);
		th += atan2(y-ry,x-rx);
		return make_pair(rx+d*cos(th),ry+d*sin(th));
	}
	void Rect(double x, double y, double dx, double dy, int clr, double th=0.0, double rx=0.0, double ry=0.0) {
		glBegin(GL_POLYGON);
		pair<double,double> o = RotatePos(x,y,th,rx,ry);
		Plot(o.first, o.second, clr);
		o = RotatePos(x+dx,y,th,rx,ry);
		Plot(o.first, o.second, clr);
		o = RotatePos(x+dx,y+dy,th,rx,ry);
		Plot(o.first, o.second, clr);
		o = RotatePos(x,y+dy,th,rx,ry);
		Plot(o.first, o.second, clr);
		glEnd();
	}
	void Square(double x, double y, double d, int clr) {
		Rect(x, y, d, d, clr);
	}
	void Poly(double x, double y, double sz, int n, int clr, double th=0.0) {
		glBegin(GL_POLYGON);
		double dth = acos(-1.0)*2.0/double(n);
		th += 0.5*dth;
		for (int i=0; i<=n; ++i) {
			Plot(x+sz*cos(th), y+sz*sin(th), clr);
			th += dth;
		}
		glEnd();
	}
	void PrintChar(int ch, double x, double y, double sz, int clr, double bd=0.12, double th=0.0, double sc=0.0) {
		ch%=256; if (ch<0) ch+=256;
		bd*=sz; sz/=6.0;
		double mx=x+sz*3, my=y+sz*2.5;
		for (string str : Char[ch]) {
			if (str.size()==1) {
				int val = ((str[0]>='a')&&(str[0]<='z'))?str[0]-'a':26+str[0]-'0';
				double sx=x+sz*double(val/5), sy=y+sz*double(val%5);
				if ((th)||(sc)) {
					double dis = sqrt((sx-mx)*(sx-mx)+(sy-my)*(sy-my));
					dis = dis*(1.0+sc);
					if (dis>0.01) {
						double tt = atan2(sy-my,sx-mx);
						sx=mx+dis*cos(tt+th); sy=my+dis*sin(tt+th);
					}
				}
				double dx=bd*0.5, dy=bd*0.5;
				glBegin(GL_POLYGON);
				Plot(sx+dx, sy+dy, clr);
				Plot(sx+dx, sy-dy, clr);
				Plot(sx-dx, sy-dy, clr);
				Plot(sx-dx, sy+dy, clr);
				glEnd();
				continue;
			}
			for (int i=1; i<str.size(); ++i) {
				int val = ((str[i-1]>='a')&&(str[i-1]<='z'))?str[i-1]-'a':26+str[i-1]-'0';
				double sx=x+sz*double(val/5), sy=y+sz*double(val%5);
				val = ((str[i]>='a')&&(str[i]<='z'))?str[i]-'a':26+str[i]-'0';
				double ex=x+sz*double(val/5), ey=y+sz*double(val%5);
				if ((th)||(sc)) {
					double dis = sqrt((sx-mx)*(sx-mx)+(sy-my)*(sy-my));
					dis = dis*(1.0+sc);
					if (dis>0.01) {
						double tt = atan2(sy-my,sx-mx);
						sx=mx+dis*cos(tt+th); sy=my+dis*sin(tt+th);
					}
					swap(sx,ex); swap(sy,ey);
					dis = sqrt((sx-mx)*(sx-mx)+(sy-my)*(sy-my));
					dis = dis*(1.0+sc);
					if (dis>0.01) {
						double tt = atan2(sy-my,sx-mx);
						sx=mx+dis*cos(tt+th); sy=my+dis*sin(tt+th);
					}
					swap(sx,ex); swap(sy,ey);
				}
				double dx=ex-sx, dy=ey-sy, len=sqrt(dx*dx+dy*dy);
				double rat = bd/len*0.2;
				sx-=rat*dx; sy-=rat*dy;
				ex+=rat*dx; ey+=rat*dy;
				dx=sy-ey; dy=ex-sx;
				rat = 0.5*bd/sqrt((sx-ex)*(sx-ex)+(sy-ey)*(sy-ey));
				dx*=rat; dy*=rat;
				glBegin(GL_POLYGON);
				Plot(sx+dx, sy+dy, clr);
				Plot(ex+dx, ey+dy, clr);
				Plot(ex-dx, ey-dy, clr);
				Plot(sx-dx, sy-dy, clr);
				glEnd();
			}
		}
	}
	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) {
		dis *= sz;
		reverse(str.begin(), str.end());
		for (char ch : str) {
			//if (sp) Rect(x+oo*sz*0.1-sz*0.2,y-sz*0.2,sz*1.5,sz,0xFF3333); 
			PrintChar(int(ch), x, y, sz, clr, bd, th, sc);
			y -= dis;
		}
	}
	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) {
		PrintStrR(str, x, y+dis*sz*double(str.size()-1), 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) {
		PrintStrR(str, x, y+dis*sz*double(str.size()-1)*0.5, sz, clr, bd, dis, eff, th, sc);
	}
}
void Board::PrintStrL(string str, double x, double y, double sz, int clr, double bd, double dis, string eff, double th, double sc) {
	PaintManager::PrintStrL(str, PX+K*x, PY+K*y, K*sz*0.66, clr, bd, dis, eff, th, sc);
}
void Board::PrintStrR(string str, double x, double y, double sz, int clr, double bd, double dis, string eff, double th, double sc) {
	PaintManager::PrintStrR(str, PX+K*x, PY+K*y, K*sz*0.66, clr, bd, dis, eff, th, sc);
}
void Board::PrintStrM(string str, double x, double y, double sz, int clr, double bd, double dis, string eff, double th, double sc) {
	PaintManager::PrintStrM(str, PX+K*x, PY+K*y, K*sz*0.66, clr, bd, dis, eff, th, sc);
}
void Board::Rect(double x, double y, double dx, double dy, int clr, double th, double rx, double ry) {
	PaintManager::Rect(PX+K*x, PY+K*y, dx*K, dy*K, clr, th, PX+K*rx, PY+K*ry);
}
void Board::Square(double x, double y, double d, int clr, double th, double rx, double ry) {
	Rect(x, y, d, d, clr, th, rx, ry);
}
void Board::Poly(double x, double y, double sz, int n, int clr, double th) {
	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 Board::PaintTile(double x, double y, int clr, double sz, EStyle Style, double th, double rx, double ry, bool u, bool d, bool l, bool r, double lrat) {
	Square(x, y, sz, clr, th, rx, ry);
	if ((K*double(GetSize())<8.0)) return;
	if (BlockStyle==BS_Simple) return;
	if (BlockStyle==BS_Arcade) {