AddButton(Str(19), "adjust 0_mod9 0 inc 0 2 -1", ++d, 31.5, 0.8, 0.8, 0.6, 0xBBBBBB, 0xCCCCCC, 0x000000, 0x000000);
	AddButton(Str(4), "adjust 0_mod9 0 def 0 2 -1", d, 32.5, 0.8, 0.8, 0.6, 0xBBBBBB, 0xCCCCCC, 0x000000, 0x000000);
	PrintStrL("Snowball", ++d, 20, 0.6, 0xBBBBBB);
	v = max(0,min(2,GetConfig(GlobalConfig,"0_ModS",0)));
	PrintStrR(vector<string>({"Off","On","Reverse"})[v], d, 33, 0.6, 0xCCCCCC);
	AddButton(Str(19), "adjust 0_mods 0 inc 0 2 -1", ++d, 31.5, 0.8, 0.8, 0.6, 0xBBBBBB, 0xCCCCCC, 0x000000, 0x000000);
	AddButton(Str(4), "adjust 0_mods 0 def 0 2 -1", d, 32.5, 0.8, 0.8, 0.6, 0xBBBBBB, 0xCCCCCC, 0x000000, 0x000000);
	PrintStrL("Pentomino", ++d, 20, 0.6, 0xBBBBBB);
	v = max(0,min(2,GetConfig(GlobalConfig,"0_ModP",0)));
	PrintStrR(vector<string>({"Off","On","Reverse"})[v], d, 33, 0.6, 0xCCCCCC);
	AddButton(Str(19), "adjust 0_modp 0 inc 0 2 -1", ++d, 31.5, 0.8, 0.8, 0.6, 0xBBBBBB, 0xCCCCCC, 0x000000, 0x000000);
	AddButton(Str(4), "adjust 0_modp 0 def 0 2 -1", d, 32.5, 0.8, 0.8, 0.6, 0xBBBBBB, 0xCCCCCC, 0x000000, 0x000000);
	PrintStrL("Mutation", ++d, 20, 0.6, 0xBBBBBB);
	v = max(0,min(2,GetConfig(GlobalConfig,"0_ModM",0)));
	PrintStrR(vector<string>({"Off","On","Reverse"})[v], d, 33, 0.6, 0xCCCCCC);
	AddButton(Str(19), "adjust 0_modm 0 inc 0 2 -1", ++d, 31.5, 0.8, 0.8, 0.6, 0xBBBBBB, 0xCCCCCC, 0x000000, 0x000000);
	AddButton(Str(4), "adjust 0_modm 0 def 0 2 -1", d, 32.5, 0.8, 0.8, 0.6, 0xBBBBBB, 0xCCCCCC, 0x000000, 0x000000);
}
string SelTab = "basic";
void PaintSelect() {
	PrintStrM("Custom Rules", 3, 35, 1.5, 0xDDDDDD, 0.15);
	AddButton(Str(12)+"Back", "home", 1, 5, 2, 8, 0.6, -1, -1, 0xBBBBBB, 0x66CCFF);
	AddButton("Settings", "settings", 1, 50, 2, 8, 0.6, -1, -1, 0xBBBBBB, 0x66CCFF);
	//AddButton("Start With Custom Rules", "", 7, 9, 2, 8, 0.6, -1, -1, 0xEEEEEE, 0xEEEEEE);
	AddButton("Start", "startcustom", 9, 9, 2, 8, 1, -1, -1, 0xCCCCCC, 0x66CCFF);
	AddButton("Game Presets", "", 11, 9, 2, 8, 0.6, -1, -1, 0xEEEEEE, 0xEEEEEE);
	AddButton("Marathon", "startcustom levline 10", 12.5, 9, 1, 8, 0.7, -1, -1, 0xCCCCCC, 0x66CCFF);
	AddButton("Sprint", "startcustom tarline 40", 14, 9, 1, 8, 0.7, -1, -1, 0xCCCCCC, 0x66CCFF);
	AddButton("Ultra", "startcustom tartime 120000 typetime 1", 15.5, 9, 1, 8, 0.7, -1, -1, 0xCCCCCC, 0x66CCFF);
	AddButton("Zen", "startcustom holdtype 2 useup 1 startdelay 0 clutchrule 2 usezone 1 stock -1 useflip 1 usewaste 1 useundo 1 uselock 1 useplace 1 autorespawn 1 resetlim 9999999 dropdelay -1 lockdelay -1", 17, 9, 1, 8, 0.7, -1, -1, 0xCCCCCC, 0x66CCFF);
	AddButton("Classic", "startcustom holdtype 0 use2rot 0 usehalf 0 useharddrop 0 comborule 13 spinrule 7 b2brule 6 allclearrule 7 rotatesystem 6 levline 10 shadowtype 0 randomtype 0 nextlen 1 resetlim 0", 18.5, 9, 1, 8, 0.7, -1, -1, 0xCCCCCC, 0x66CCFF);
	AddButton("Battle 2P", "startcustom player 2 qpmode 0", 20, 9, 1, 8, 0.7, -1, -1, 0xCCCCCC, 0x66CCFF);
	AddButton("Battle 10P", "startcustom player 10 qpmode 0", 21.5, 9, 1, 8, 0.7, -1, -1, 0xCCCCCC, 0x66CCFF);
	AddButton("Battle 30P", "startcustom player 30 qpmode 0", 23, 9, 1, 8, 0.7, -1, -1, 0xCCCCCC, 0x66CCFF);
	AddButton("Battle 100P", "startcustom player 100 qpmode 0", 24.5, 9, 1, 8, 0.7, -1, -1, 0xCCCCCC, 0x66CCFF);
	AddButton("Zenith", "startcustom player 16 qpmode 1 zenithbot 1 singlebonus 1 intrule 1", 26, 9, 1, 8, 0.7, -1, -1, 0xCCCCCC, 0x66CCFF);
	AddButton("Dig", "startcustom staticheight 10", 27.5, 9, 1, 8, 0.7, -1, -1, 0xCCCCCC, 0x66CCFF);
	AddButton("Clear Config", "clearconfig", 35, 9, 1, 8, 0.5, -1, -1, 0xFF6666, 0xEE0000);
	PrintStrL("d : "+IToS(AdjustDel), 6, 20, 0.6, 0xCCCCCC);
	AddButton("-", "adjustdel dec", 6, 26.5, 0.8, 0.8, 0.6, 0xBBBBBB, 0xCCCCCC, 0x000000, 0x000000);
	AddButton("+", "adjustdel inc", 6, 27.5, 0.8, 0.8, 0.6, 0xBBBBBB, 0xCCCCCC, 0x000000, 0x000000);
	AddButton("Basic", "seltab basic", 6, 32, 0.8, 4, 0.6, 0xBBBBBB, 0xCCCCCC, 0x000000, 0x000000);
	AddButton("Battle", "seltab battle", 6, 37, 0.8, 4, 0.6, 0xBBBBBB, 0xCCCCCC, 0x000000, 0x000000);
	AddButton("Level", "seltab level", 6, 42, 0.8, 4, 0.6, 0xBBBBBB, 0xCCCCCC, 0x000000, 0x000000);
	AddButton("Misc", "seltab misc", 6, 47, 0.8, 4, 0.6, 0xBBBBBB, 0xCCCCCC, 0x000000, 0x000000);
	AddButton("QPMod", "seltab qpmod", 6, 52, 0.8, 4, 0.6, 0xBBBBBB, 0xCCCCCC, 0x000000, 0x000000);
	if (SelTab=="basic") OptionBasic();
	if (SelTab=="battle") OptionBattle();
	if (SelTab=="level") OptionLevel();
	if (SelTab=="misc") OptionMisc();
	if (SelTab=="qpmod") OptionQPMod();
}
#define sq(xx,ss,clr) Square(x+sz*(xx),y+sz*(xx),sz*(ss),clr)
#define rec(xx,yy,sx,sy,clr) Rect(x+sz*(xx),y+sz*(yy),sz*(sx),sz*(sy),clr)
void PaintTile(double x, double y, int clr, double sz=1.0, EBlockStyle Style=S_Default) {
	Square(x, y, sz, clr);
	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 (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));
		}
	}
}
#undef sq
#undef rec
void PaintPiece(const Piece &p, double x, double y, double sz, int bck=BoardColor, int clr=-1, EBlockStyle Style=S_Active, int MinSize=4) {
	int LX=p.GetLX(); double ss=sz/double(LX);
	Square(x, y, sz, bck);
	if (LX<MinSize) {
		x+=0.5*sz*double(MinSize-LX)/double(MinSize); y+=0.5*sz*double(MinSize-LX)/double(MinSize);
		ss = sz/double(MinSize);
	}
	for (int i=0; i<LX; ++i) {
		for (int j=0; j<LX; ++j) {
			int val = p.GetGrid(LX-i-1,j);
			if (!val) continue;
			if (clr>=0) val=clr;
			PaintTile(x+i*ss, y+j*ss, abs(val), ss, Style);
		}
	}
}
void PaintPool() {
	AddButton(Str(12)+"Back", "select", 1, 5, 2, 8, 0.6, -1, -1, 0xBBBBBB, 0x66CCFF);
	int beg = max(0,int(EditPool.size())-200);
	PrintStrM("Delete "+Str(11), 15, 5, 0.8, 0xBBBBBB);
	for (int id=0; id<200; ++id) {
		int i=id/20, j=id%20;
		Piece p = P_Empty;
		if (EditPool.size()>beg+id) p=EditPool[beg+id];
		AddButton("", "pooldel "+IToS(beg+id), 3+1.5*(i+0.5), 10+1.5*(j+0.5), 1.5, 1.5, 0, -1, -1, 0x000000, 0x000000);
		PaintPiece(p, 3+1.5*i, 10+1.5*j, 1.5, (CheckButton(Buttons.back()))?0x555555:BoardColor);
	}
	AddButton("Piece Pool", "", 9, 50, 2, 8, 1.2, -1, -1, 0xDDDDDD, 0xDDDDDD);
	AddButton("Reset", "poolclear", 12, 50, 2, 8, 1, -1, -1, 0xBBBBBB, 0x66CCFF);
	AddButton("Cancel", "select", 15, 50, 2, 8, 1, -1, -1, 0xBBBBBB, 0x66CCFF);
	AddButton("Confirm", "poolconfirm", 18, 50, 2, 8, 1, -1, -1, 0xBBBBBB, 0x66CCFF);
	PrintStrM("Add "+Str(10), 23, 5, 0.8, 0xBBBBBB);
	for (int id=0; id<100; ++id) {
		Piece p = P_Empty;
		if (PieceList.size()>id) p=PieceList[id];
		int i=id/20, j=id%20;
		if (p.GetRealName().empty()) {
			PaintPiece(p,20+1.5*i,10+1.5*j,1.5); continue;
		}
		AddButton("", "poolins "+IToS(id), 20+1.5*(i+0.5), 10+1.5*(j+0.5), 1.5, 1.5, 0, -1, -1, 0x000000, 0x000000);
		PaintPiece(p, 20+1.5*i, 10+1.5*j, 1.5, (CheckButton(Buttons.back()))?0x555555:BoardColor);
	}
	AddButton("Custom Piece", "piece", 31, 5, 2, 8, 0.6, -1, -1, 0xBBBBBB, 0x66CCFF);
	beg = max(0,int(CustomPiece.size())-100);
	for (int id=0; id<100; ++id) {
		Piece p = P_Empty;
		if (CustomPiece.size()>id+beg) p=*CustomPiece[id+beg];
		int i=id/20, j=id%20;
		if (p.GetRealName().empty()) {
			PaintPiece(p,28+1.5*i,10+1.5*j,1.5); continue;
		}
		AddButton("", "custompoolins "+IToS(id+beg), 28+1.5*(i+0.5), 10+1.5*(j+0.5), 1.5, 1.5, 0, -1, -1, 0x000000, 0x000000);
		PaintPiece(p, 28+1.5*i, 10+1.5*j, 1.5, (CheckButton(Buttons.back()))?0x555555:BoardColor);
	}
}
bool EditPiece[99][99];
int EditPieceSize=3, EditPieceR=102, EditPieceG=204, EditPieceB=255;
void PaintCustomPiece() {
	AddButton(Str(12)+"Back", "poolback", 1, 5, 2, 8, 0.6, -1, -1, 0xBBBBBB, 0x66CCFF);
	AddButton("Reset", "clearcustompiece", 12, 50, 2, 8, 1, -1, -1, 0xBBBBBB, 0x66CCFF);
	AddButton("Cancel", "poolback", 15, 50, 2, 8, 1, -1, -1, 0xBBBBBB, 0x66CCFF);
	AddButton("Confirm", "addcustompiece", 18, 50, 2, 8, 1, -1, -1, 0xBBBBBB, 0x66CCFF);
	int Color = (EditPieceR<<16)|(EditPieceG<<8)|EditPieceB;
	for (int i=0; i<256; ++i) {
		AddButton("", "colorr "+IToS(i), 8, 10+i*0.1, (i%17)?0.3:0.45, 0.1, 0, i<<16, 0xFFFFFF, -1, -1);
		AddButton("", "colorg "+IToS(i), 9, 10+i*0.1, (i%17)?0.3:0.45, 0.1, 0, i<<8, 0xFFFFFF, -1, -1);
		AddButton("", "colorb "+IToS(i), 10, 10+i*0.1, (i%17)?0.3:0.45, 0.1, 0, i, 0xFFFFFF, -1, -1);
	}
	AddButton("", "", 8, 10+EditPieceR*0.1, 0.6, 0.1, 0, 0xFF6666, 0xFF6666, -1, -1);
	AddButton("", "", 9, 10+EditPieceG*0.1, 0.6, 0.1, 0, 0x66FF66, 0x66FF66, -1, -1);
	AddButton("", "", 10, 10+EditPieceB*0.1, 0.6, 0.1, 0, 0x6666FF, 0x6666FF, -1, -1);
	PrintStrM("R", 7.75, 9, 0.5, 0xAAAAAA);
	PrintStrM("G", 8.75, 9, 0.5, 0xAAAAAA);
	PrintStrM("B", 9.75, 9, 0.5, 0xAAAAAA);
	PrintStrM("Piece Color", 5.5, 22.8, 0.6, 0xAAAAAA);
	PrintStrM("#"+IToSH(Color), 6.5, 22.8, 0.7, Color);
	PrintStrL("Piece Size : "+IToS(EditPieceSize), 13, 15, 0.6, 0xAAAAAA);
	AddButton("-", "decpiecesize", 13, 25, 0.8, 0.8, 0.6, 0xBBBBBB, 0xCCCCCC, 0x000000, 0x000000);
	AddButton("+", "incpiecesize", 13, 26, 0.8, 0.8, 0.6, 0xBBBBBB, 0xCCCCCC, 0x000000, 0x000000);
	for (int i=0; i<EditPieceSize; ++i) {
		for (int j=0; j<EditPieceSize; ++j) {
			AddButton("", "piecegrid "+IToS(i)+" "+IToS(j), 15.5+i, 15.5+j, 1, 1, 0, -1, -1, -1, -1);
			if (EditPiece[i][j]) PaintTile(15+i,15+j,Color,1);
			else {
				Square(15+i, 15+j, 1, GridColor);
				Square(15+i+0.05, 15+j+0.05, 0.9, BoardColor);
			}
		}
	}
}
void PaintIngame() {
	AddButton(Str(12)+"Back", "select", 1, 2, 1, 3.5, 0.6, -1, -1, 0xBBBBBB, 0x66CCFF);
	AddButton((Pause)?"Resume":"Pause", "pause", 2, 2, 1, 3.5, 0.6, -1, -1, 0xBBBBBB, 0x66CCFF);
	AddButton("Retry", "retry", 3, 2, 1, 3.5, 0.6, -1, -1, 0xBBBBBB, 0x66CCFF);
}
queue<ll> FrameQueue;
void Paint() {
	if (Menu=="home") PaintHome();
	if (Menu=="settings") PaintSettings();
	if (Menu=="select") PaintSelect();
	if (Menu=="pool") PaintPool();
	if (Menu=="piece") PaintCustomPiece();
	if (Menu=="ingame") PaintIngame(); 
	PrintStrM("["+IToS(FrameQueue.size())+"]", 5, 2, 0.5, 0xEE82EE);
}
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;
}

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;
	}
}

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);
	}
}
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");
	}
}
#warning Execute
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");
}
#warning Frame
void Frame(int TimeDel, int RealTimeDel) {
	UpdateKeySta();
	int n=PlayerCount=Boards.size();
	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;
	Buttons.clear();
	POINT p;
	if (Focus) {
		GetCursorPos(&p); ScreenToClient(GetForegroundWindow(),&p);
	}
	else p.x=p.y=0;
	CurX = p.y*0.0412*500.0/double(Scale);
	CurY = p.x*0.04015*500.0/double(Scale);
	int clr = BackgroundColor;
	int B=clr&255; clr>>=8;
	int G=clr&255; clr>>=8;
	int R = clr&255;
	glClearColor(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)), 0);
	glClear(GL_COLOR_BUFFER_BIT);
	ArrangeBoards(Obs);
	Focus = (hWnd==GetFocus());
	ll cl = GetClock();
	while ((!FrameQueue.empty())&&((FrameQueue.front()+1000LL<cl)||(FrameQueue.size()>10000))) 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;
		}
	}
	Paint();
	int SelBoard = -1;
	for (Board *p : Boards) p->CheckCursor();
	LeftClick=RightClick=MidClick=false;
	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();
	}
	LeftUp=RightUp=0;
	if (CheckKey(VK_LBUTTON)) LeftHold+=RealTimeDel;
	else {
		LeftUp=LeftHold; LeftHold=0;
	}
	if (CheckKey(VK_RBUTTON)) RightHold+=RealTimeDel;
	else {
		RightUp=RightHold; RightHold=0;
	}
	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;
		}
	}
	for (Button &o : Buttons) {
		bool f = CheckButton(o);
		int clr = (f)?o.selclr:o.clr;
		if (clr>=0) Rect(o.x-o.lx*0.5,o.y-o.ly*0.5,o.lx,o.ly,clr);
		PrintStrM(o.Text, o.x-o.tsz*0.5, o.y-o.tsz*0.33, o.tsz, (f)?o.tselclr:o.tclr);
	}
	Poly(CurX, CurY, 0.1, 4, 0xFFFFFF, acos(-1.0)*0.25);
	glPushMatrix();
	glPopMatrix();
	SwapBuffers(hDC);
	for (string str : ForceCommand) Execute(str);
	ForceCommand.clear();
}
void MainLoop(int TimeDel) {
	int RealTimeDel = TimeDel;
	Frame(TimeDel, RealTimeDel);
	Time += TimeDel;
}
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;
	//Launch("qp",{{"Player",16},{"Bot",1},{"BotSpeed",521},\
	{"0_BotType",3},{"0_BotSpeed",200},{"SingleBonus",1},{"0_ModP",1},{"0_Mod9",0},\
	{"IntRule",IR_Accumulate},{"0_RotateSystem",R_ASCDX},{"SpinRule",SR_Default}});
	//Launch("ingame",{{"Bot",1},{"StartDelay",0},{"BotType",1},{"ModP",0},{"BotSpeed",0},\
	{"RotateSystem",R_SRSPlus},{"SpinRule",SR_AllMini}});
	//Launch("ingame",{{"Bot",1},{"Player",2},{"0_BotType",1},{"0_B2BRule",BR_Guildline},{"1_BotType",3},{"0_Stock",999},\
	{"BotSpeed",500},{"0_BotSpeed",600},{"0_SegMess",0},{"RotateSystem",R_SRSPlus},{"SpinRule",SR_AllMini}});
	//Launch("ingame",{{"Player",200},{"0_Stock",99999}});
	/*Launch("ingame", {{"Player",2},{"Bot",1},{"BotSpeed",300},{"GarbagePush",0},{"GarbagePass",1000},
	{"1_SpinRule",SR_AllMini},{"1_RotateSystem",R_SRSPlus},{"1_GarbageShape",GS_Board},{"1_GarbageActive",GA_Instant},
	{"1_GarbageDelay",0},{"1_B2BRule",BR_Guildline},
	{"0_BotType",6},{"0_Shadow",0},{"0_RandomType",G_Random},{"0_HoldLen",0},{"0_NextLen",1},
	{"0_RotateSystem",R_RNRS},{"0_LY",12},{"0_SX",23},{"0_RandHistory",0},{"0_UseHalf",0},{"0_Use2Rot",0},
	{"0_BaseRule",BSR_Classic},{"0_SpinRule",SR_None},{"0_ComboRule",CR_None},{"0_B2BRule",BR_None},{"0_AllClearRule",AR_None}});*/
	//Launch("ingame",{{"Player",1},{"HoldLen",0},{"NextLen",1},{"RandomType",G_Random},{"DefaultDir",2},{"DropDelay",01},\
	{"BotSpeed",0},{"Bot",1},{"BotType",6},{"RotateSystem",R_RNRS},{"UseHalf",0},{"Use2Rot",0}});
}
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);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int iCmdShow) {
	wc.style = CS_OWNDC;
	wc.lpfnWndProc = WndProc;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hInstance = hInstance;
	wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
	wc.hbrBackground = HBRUSH(GetStockObject(BLACK_BRUSH));
	wc.lpszMenuName = NULL;
	wc.lpszClassName = "GLSample";
	RegisterClass(&wc);
	//hWnd = GetConsoleWindow();
	int SystemWidth = GetSystemMetrics(SM_CXSCREEN);
	int SystemHeight = GetSystemMetrics(SM_CYSCREEN);
	Scale = min(SystemWidth/3,SystemHeight/2);
	hWnd = CreateWindow("GLSample", "Sample Text", WS_CAPTION|WS_POPUPWINDOW|WS_VISIBLE, 0, 0, Scale*3, Scale*2, NULL, NULL, hInstance, NULL);
	EnableOpenGL(hWnd, &hDC, &hRC);
	//midiOutOpen(&SHnd, 0, 0, 0, CALLBACK_NULL);
	ll CurTime=GetClock(), CurRealTime=clock();
	ReadConfig();
	Init();
	while (!bQuit) {
		if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) {
			if (msg.message==WM_QUIT) bQuit=true;
			else {
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
			continue;
		}
		else {
			ll NewTime=GetClock(), NewRealTime=clock();
			GlobalRealTimeDel = int(NewRealTime-CurRealTime);
			MainLoop(int(NewTime-CurTime));
			CurTime=NewTime; CurRealTime=NewRealTime;
		}
	}
	DisableOpenGL(hWnd, hDC, hRC);
	DestroyWindow(hWnd);
	WriteConfig();
	return msg.wParam;
}