#include <bits/stdc++.h>
#include <windows.h>
#include <conio.h>

#define color(x) SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),x)
#define curpos(x,y) SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),{y,x})
using namespace std;

struct RandomGenerator {
    int StartSeed;
    int CurSeed;
    int CurCount;
    RandomGenerator() {
        CurCount = 0;
        CurSeed = int(time(0));
        StartSeed = CurSeed;
        srand(CurSeed);
    }
    RandomGenerator(int Seed) {
        CurCount = 0;
        CurSeed = Seed;
        StartSeed = CurSeed;
        srand(CurSeed);
    }
    void Srand(int Seed) {
        CurCount = 0;
        CurSeed = Seed;
        StartSeed = CurSeed;
        srand(CurSeed);
    }
    int Rand() {
        ++CurCount;
        if (CurCount==0x8000) {
            CurCount = 0;
            CurSeed += (StartSeed^0x66CCFF)+1;
            srand(CurSeed);
        }
        return rand();
    }
    int Rand32() {
        return ((Rand()>>1)<<16)|Rand()|((Rand()&1)<<15);
    }
    int Rand(int Mod) {
        return Rand32()%Mod;
    }
} Rnd;
struct ClassTile {
	int LenX, LenY, MidX, MidY, Color;
	int Block[100][100];
	ClassTile() {
		LenX=LenY=MidX=MidY=0; Color=7;
		memset(Block, 0, sizeof(Block));
	}
	void Init(string str, int clr) {
		LenX = LenY = 0;
		Color = clr;
		int CurY = 0;
		for (unsigned int i=0; i<str.size(); ++i) {
			char ch = str[i];
			if (ch=='|') {
				++LenX; CurY=0;
			}
			else if (ch=='*') Block[LenX][CurY++]=true;
			else if (ch=='#') {
				MidX=LenX; MidY=CurY;
				Block[LenX][CurY++] = true;
			}
			else if (ch=='.') Block[LenX][CurY++]=false;
			LenY = max(LenY,CurY);
		}
	}
} BasicTiles[7], Tiles[7][4];
int SaveInFile = true;
int LenX=40, DisX=21, LenY=10;
int MovesLimit = 15;
int TileCount = 7;
int NormalTimeLimit=500, LockTimeLimit=500;
int NextDisplay = 5;
int AttackEnabled = false;
int AttackLayerLimit = 8;
int SingleAttackLimit = 20;
int RandomByBag = true;
int EnableHold = true;
int Gravity = 1;
int AttackByTime = 0;
int EnableKickWall = true;
int Enable180Spin = true;
int DisplayDropLocation = true;
int AllSpin = true;
int OSpin = true;
int B2BForAllSpin = false;
int ImmeadiateLock = false;
int ShowDisappearEffect = true;
int GoalLines = 0;

ifstream fin; int Options=0, LimL[100], LimR[100], DisplayType[100], Multiplier[100];
int *Pointers[100]; string Name[100];
void TryRead(string _Name, int &x, int _LimL, int _LimR, int _DisplayType=0, int _Multiplier=1) {
	int r; ++Options;
	Pointers[Options]=&x; Name[Options]=_Name;
	LimL[Options]=_LimL; LimR[Options]=_LimR;
	DisplayType[Options]=_DisplayType; Multiplier[Options]=_Multiplier;
	if (fin>>r) x=r;
}
char GetKey() {
	if (GetKeyState(VK_SHIFT)&32768) return 1;
	char r=getch(); while (kbhit()) r=getch();
	return r;
}
void SettingsBoard() {
	fin.open("settings.txt");
	fin.clear(); fin.clear();
	++DisX;
	TryRead("Save Options at Local", SaveInFile, 0, 1, 2);
	TryRead("Height of Board", DisX, 2, 80);
	TryRead("Width of Board", LenY, 4, 80);
	TryRead("Real Height of Board", LenX, 2, 80);
	TryRead("Drop Time (ms)", NormalTimeLimit, 0, 10000000, 1, 50);
	TryRead("Lock Time (ms)", LockTimeLimit, 0, 10000000, 1, 50);
	TryRead("Show Next Pieces", NextDisplay, 0, 5, 1);
	TryRead("Moves Limit Per Drop", MovesLimit, 0, 1000, 1);
	TryRead("Attack Myself", AttackEnabled, 0, 1, 2);
	TryRead("Max Attack Layer Per Round", AttackLayerLimit, 0, 50, 1);
	TryRead("Single Attack Limit", SingleAttackLimit, 0, 50, 1);
	TryRead("Use 7-Bag Piece Random", RandomByBag, 0, 1, 2);
	TryRead("Enable Hold Function", EnableHold, 0, 1, 2);
	TryRead("Gravity", Gravity, 0, 80, 1);
	TryRead("Send Garbage By Time", AttackByTime, 0, 10000000, 1, 50);
	TryRead("Enable Kick Wall", EnableKickWall, 0, 1, 2);
	TryRead("Enable 180-Degree Rotate", Enable180Spin, 0, 1, 2);
	TryRead("Show Ghost Piece", DisplayDropLocation, 0, 1, 2);
	TryRead("Show All Spins", AllSpin, 0, 1, 2);
	TryRead("Enable O-Spin", OSpin, 0, 1, 2);
	TryRead("Bonus For All Spins", B2BForAllSpin, 0, 1, 2);
	TryRead("Lock Immeadiately on Ground", ImmeadiateLock, 0, 1, 2);
	TryRead("Disappear Effect", ShowDisappearEffect, 0, 1, 2);
	TryRead("Goal Lines", GoalLines, 0, 10000, 1);
	fin.clear(); fin.close();
	int Selected = 1;
	for (;;) {
		for (int i=1; i<=Options; ++i) {
			curpos(i, 35);
			color((Selected==i)?15:7);
			if (!DisplayType[i]) printf("%d   ",*Pointers[i]);
			else if (DisplayType[i]==1) {
				if (*Pointers[i]) printf("%d   ",*Pointers[i]);
				else {
					color((Selected==i)?12:4);
					printf("NO ");
				}
			}
			else if (DisplayType[i]==2) {
				if (*Pointers[i]) {
					color((Selected==i)?10:2);
					printf("YES");
				}
				else {
					color((Selected==i)?12:4);
					printf("NO ");
				}
			}
			curpos(i, 3);
			color((Selected==i)?7:8);
			cout << Name[i];
			printf("    ");
		}
		char ch=GetKey(); if (isupper(ch)) ch=tolower(ch);
		if ((ch=='\r')||(ch=='\n')||(ch==' ')) break;
		if (ch==104) Selected=max(1,Selected-1);
		if (ch==112) Selected=min(Options,Selected+1);
		if (((ch==107)||(ch==109))&&(DisplayType[Selected]==2)) *Pointers[Selected]^=1;
		else if (ch==107) *Pointers[Selected]=max(*Pointers[Selected]-Multiplier[Selected],LimL[Selected]);
		else if (ch==109) *Pointers[Selected]=min(*Pointers[Selected]+Multiplier[Selected],LimR[Selected]);
		DisX = min(DisX,LenX);
	}
	if (SaveInFile) {
		ofstream fout("settings.txt");
		for (int i=1; i<=Options; ++i) fout<<*Pointers[i]<<" ";
		fout.clear(); fout.close();
	}
	--DisX;
	system("cls");
}

int Hold; int HoldAvailable;
int TotalTiles, TotalLines, TotalAttack, Combo, B2B;
int AttackRemain, AttackQueue[1000];
int B2BMessage;
int Board[100][100]; int Active[100][100], DropTo[100][100], AttackColor[100];
int DisappearEffect[100][100];
int TileQueue[100], QueueLen;
long long BeginTime;
int OnGround;
int MessageRemain;
int ResetBar;
int Record = 2000000000;
string MessageA; int MessageColorA;
string MessageB; int MessageColorB;

ClassTile RotateTile(ClassTile Tile, int K=1) {
	K = (K%4+4)%4;
	while (K--) {
		ClassTile NewTile; NewTile.Color=Tile.Color;
		NewTile.LenX=Tile.LenY; NewTile.LenY=Tile.LenX;
		NewTile.MidY=Tile.LenX-Tile.MidX-1; NewTile.MidX=Tile.MidY;
		for (int i=0; i<Tile.LenX; ++i) {
			for (int j=0; j<Tile.LenY; ++j) NewTile.Block[j][Tile.LenX-i-1]=Tile.Block[i][j];
		}
		Tile = NewTile;
	}
	return Tile;
}
int CheckTile(int i, int j) {
	if (((i<0)||(i>=LenX))||((j<0)||(j>=LenY))) return true;
	return ((Board[i][j]!=0)&&(!Active[i][j]));
}
int CheckTileNoUpper(int i, int j) {
	if ((i<0)||((j<0)||(j>=LenY))) return true;
	return ((Board[i][j]!=0)&&(!Active[i][j]));
}
void Display() {
	int DropLen = LenX;
	for (int i=0; i<LenX; ++i) {
		for (int j=0; j<LenY; ++j) {
			DropTo[i][j] = false;
			if (Active[i][j]) {
				int Len=0; while (!CheckTile(i-Len-1,j)) ++Len;
				DropLen = min(DropLen,Len);
			}
		}
	}
	for (int i=1; i<min(AttackRemain,DisX); ++i) {
		if (AttackQueue[i]!=AttackQueue[i-1]) AttackColor[i]=!AttackColor[i-1];
		else AttackColor[i]=AttackColor[i-1]; 
	}
	for (int i=0; i<LenX; ++i) {
		for (int j=0; j<LenY; ++j) {
			if (Active[i][j]) DropTo[i-DropLen][j]=true;
		}
	}
	int GoalBar = 0;
	if (GoalLines) GoalBar=int(floor(double(DisX)*double(TotalLines)/double(GoalLines)));
	for (int i=DisX; i>=0; --i) {
		curpos(DisX-i+5,12);
		color(((AttackEnabled)||(DisX-i<2)?8:7)<<4); if (AttackRemain>i) color((AttackColor[i]?4:12)<<4);
		printf(" ");
		color((DisX-i<2?8:7)<<4); printf(" ");
		for (int j=0; j<LenY; ++j) {
			if ((!Board[i][j])&&(DropTo[i][j])&&(DisplayDropLocation)) {
				color(7); printf("□");
			}
			else if (!Active[i][j]) {
				color(Board[i][j]<<4|8); 
				if ((DisappearEffect[i][j])&&(!Board[i][j])&&(ShowDisappearEffect)) printf("※");
				else printf("  ");
			}
			else {
				color(Board[i][j]); printf("■");
			}
		}
		color((i<GoalBar)?(2<<4):((DisX-i<2?8:7)<<4)); printf(" ");
		color(((ResetBar>i)?12:(DisX-i<2?8:7))<<4); printf(" ");
		color(0);
	}
	color(((Hold==-1)||(!HoldAvailable))?8:15); curpos(2,1);
	if (EnableHold) printf("H O L D");
	for (int i=0; i<5; ++i) {
		curpos(4+i, 1);
		for (int j=0; j<5; ++j) {
			if ((Hold==-1)||(!BasicTiles[Hold].Block[4-i][j])) color(0);
			else color((HoldAvailable)?BasicTiles[Hold].Color:8);
			printf("■");
		}
	}
	color(15); curpos(2,18+LenY*2);
	if (NextDisplay) printf("N E X T");
	for (int d=0; d<NextDisplay; ++d) {
		for (int i=0; i<5; ++i) {
			curpos(3+d*5+i, 18+LenY*2);
			for (int j=0; j<5; ++j) {
				if (!BasicTiles[TileQueue[d]].Block[4-i][j]) color(0);
				else color(BasicTiles[TileQueue[d]].Color);
				printf("■");
			}
		}
	}
	curpos(DisX+6, 12);
	for (int j=-1; j<=LenY; ++j) {
		color(7<<4); printf("  ");
	}
	double Seconds = double(clock()-BeginTime)*0.001+0.001;
	int SecondsInt = int(floor(Seconds));
	color(MessageColorA); curpos(10,1); cout<<MessageA;
	color(MessageColorB); curpos(11,1); cout<<MessageB;
	curpos(12,1); color(15);
	if ((Combo<2)||(!MessageRemain)) printf("          ");
	else printf("%d Combo",Combo-1);
	curpos(13,1); color((B2B)?14:12);
	if ((!B2BMessage)||((!B2B)&&(!MessageRemain))) printf("          ");
	else printf("B2B x %d",max(0,B2B-1));
	color(15); curpos(17,1); printf("Time");
	color(11); curpos(18,1); printf("%02d:%02d",SecondsInt/60,SecondsInt%60);
	color(15); curpos(19,1); printf("Pieces");
	color(10); curpos(20,1); printf("%d ",TotalTiles);
	color(11); curpos(21,1); printf("%.2f /s ",double(TotalTiles)/Seconds);
	color(15); curpos(22,1); printf("Lines");
	color(10); curpos(23,1); printf("%d ",TotalLines);
	color(11); curpos(24,1); printf("%.2f /m ",double(TotalLines*60)/Seconds);
	color(15); curpos(25,1); printf("Attack");
	color(10); curpos(26,1); printf("%d ",TotalAttack);
	color(11); curpos(27,1); printf("%.2f /m ",double(TotalAttack*60)/Seconds);
	if (GoalLines) {
		curpos(27, 1);
		if (TotalLines>=GoalLines) {
			color(10); printf("COMPLETED");
		}
		else {
			color(14); printf("(%.2f%%)",double(TotalLines)/double(GoalLines)*100.0);
		}
	}
}
int CheckDown() {
	for (int i=0; i<LenX; ++i) {
		for (int j=0; j<LenY; ++j) {
			if ((Active[i][j])&&(CheckTile(i-1,j))) return false;
		}
	}
	return true;
}
int CheckLeft() {
	for (int i=0; i<LenX; ++i) {
		for (int j=0; j<LenY; ++j) {
			if ((Active[i][j])&&(CheckTile(i,j-1))) return false;
		}
	}
	return true;
}
int CheckRight() {
	for (int i=0; i<LenX; ++i) {
		for (int j=0; j<LenY; ++j) {
			if ((Active[i][j])&&(CheckTile(i,j+1))) return false;
		}
	}
	return true;
}
int MoveDown() {
	if (!CheckDown()) return false;
	for (int i=0; i+1<LenX; ++i) {
		for (int j=0; j<LenY; ++j) {
			if (Active[i+1][j]) {
				Active[i][j]=true; Board[i][j]=Board[i+1][j];
			}
			else if (Active[i][j]) {
				Active[i][j]=false; Board[i][j]=0;
			}
		}
	}
	for (int j=0; j<LenY; ++j) {
		if (Active[LenX-1][j]) {
			Active[LenX-1][j]=false; Board[LenX-1][j]=0;
		}
	}
	return true;
}
int MoveLeft() {
	if (!CheckLeft()) return false;
	for (int j=0; j+1<LenY; ++j) {
		for (int i=0; i<LenX; ++i) {
			if (Active[i][j+1]) {
				Active[i][j]=true; Board[i][j]=Board[i][j+1];
			}
			else if (Active[i][j]) {
				Active[i][j]=false; Board[i][j]=0;
			}
		}
	}
	for (int i=0; i<LenX; ++i) {
		if (Active[i][LenY-1]) {
			Active[i][LenY-1]=false; Board[i][LenY-1]=0;
		}
	}
	return true;
}
int MoveRight() {
	if (!CheckRight()) return false;
	for (int j=LenY-1; j>=0; --j) {
		for (int i=0; i<LenX; ++i) {
			if (Active[i][j-1]) {
				Active[i][j]=true; Board[i][j]=Board[i][j-1];
			}
			else if (Active[i][j]) {
				Active[i][j]=false; Board[i][j]=0;
			}
		}
	}
	for (int i=0; i<LenX; ++i) {
		if (Active[i][0]) {
			Active[i][0]=false; Board[i][0]=0;
		}
	}
	return true;
}
int TryNewRotate(ClassTile Tile, int x, int y) {
	for (int i=0; i<Tile.LenX; ++i) {
		for (int j=0; j<Tile.LenY; ++j) {
			if ((Tile.Block[i][j])&&(CheckTile(x+i,y+j))) return false;
		}
	}
	for (int i=0; i<LenX; ++i) {
		for (int j=0; j<LenY; ++j) {
			if (Active[i][j]) {
				Active[i][j]=false; Board[i][j]=0;
			}
		}
	}
	for (int i=0; i<Tile.LenX; ++i) {
		for (int j=0; j<Tile.LenY; ++j) {
			if (Tile.Block[i][j]) {
				Active[x+i][y+j]=true; Board[x+i][y+j]=Tile.Color;
			}
		}
	}
	return true;
}
vector<pair<int,int> > KickTable[4][4], IKickTable[4][4];
int Rotate(ClassTile OldTile, ClassTile NewTile, int From, int To) { // To be modified - 2023/10/20 11:04
	if ((!OSpin)&&(NewTile.Color==14)) return false;
	int FirstActiveX=-1, FirstActiveY=-1;
	for (int i=0; i<LenX; ++i) {
		for (int j=0; j<LenY; ++j) {
			if (Active[i][j]) {
				FirstActiveY=j; break;
			}
		}
		if (FirstActiveY>=0) {
			FirstActiveX=i; break;
		}
	}
	int FirstTileX=-1, FirstTileY=-1;
	for (int i=0; i<OldTile.LenX; ++i) {
		for (int j=0; j<OldTile.LenY; ++j) {
			if (OldTile.Block[i][j]) {
				FirstTileY=j; break;
			}
		}
		if (FirstTileY>=0) {
			FirstTileX=i; break;
		}
	}
	int BaseX = FirstActiveX-FirstTileX+((NewTile.Color==14||NewTile.Color==11)?0:OldTile.MidX-NewTile.MidX);
	int BaseY = FirstActiveY-FirstTileY+((NewTile.Color==14||NewTile.Color==11)?0:OldTile.MidY-NewTile.MidY);
	if (!EnableKickWall) {
		if (TryNewRotate(NewTile,BaseX,BaseY)) return true;
		return false;
	}
	if (NewTile.Color==14) {
		if (!OnGround) return true; 
		int Shift = 0;
		if ((To-From+4)%4==1) Shift=-1;
		if ((To-From+4)%4==3) Shift=1;
		if (TryNewRotate(NewTile,BaseX-3,BaseY+Shift)) return true;
		if (TryNewRotate(NewTile,BaseX-2,BaseY+Shift)) return true;
		if (TryNewRotate(NewTile,BaseX-1,BaseY+Shift)) return true;
	}
	else if (KickTable[From][To].empty()) {
		if (OnGround) {
			if (TryNewRotate(NewTile,BaseX-2,BaseY)) return true;
			if (TryNewRotate(NewTile,BaseX-1,BaseY)) return true;
			if ((NewTile.Color!=14)&&(TryNewRotate(NewTile,BaseX,BaseY))) return true;
			int d = Rnd.Rand(2)?1:-1;
			if (TryNewRotate(NewTile,BaseX-2,BaseY+d)) return true;
			if (TryNewRotate(NewTile,BaseX-2,BaseY-d)) return true;
			if (TryNewRotate(NewTile,BaseX-1,BaseY+d)) return true;
			if (TryNewRotate(NewTile,BaseX-1,BaseY-d)) return true;
			if (TryNewRotate(NewTile,BaseX,BaseY+d)) return true;
			if (TryNewRotate(NewTile,BaseX,BaseY-d)) return true;
		}
		else {
			if (TryNewRotate(NewTile,BaseX,BaseY)) return true;
			if (TryNewRotate(NewTile,BaseX-1,BaseY)) return true;
			if (TryNewRotate(NewTile,BaseX-2,BaseY)) return true;
			int d = Rnd.Rand(2)?1:-1;
			if (TryNewRotate(NewTile,BaseX,BaseY+d)) return true;
			if (TryNewRotate(NewTile,BaseX,BaseY-d)) return true;
			if (TryNewRotate(NewTile,BaseX-1,BaseY+d)) return true;
			if (TryNewRotate(NewTile,BaseX-1,BaseY-d)) return true;
			if (TryNewRotate(NewTile,BaseX-2,BaseY+d)) return true;
			if (TryNewRotate(NewTile,BaseX-2,BaseY-d)) return true;
		}
	}
	else if (NewTile.Color==11) {
		for (pair<int,int> p : IKickTable[From][To]) {
			if (TryNewRotate(NewTile,BaseX+p.second,BaseY+p.first)) return true;
		}
	}
	else {
		for (pair<int,int> p : KickTable[From][To]) {
			if (TryNewRotate(NewTile,BaseX+p.second,BaseY+p.first)) return true;
		}
	}
	return false;
}
int NoPlacement(ClassTile Tile, int x, int y) {
	for (int i=0; i<Tile.LenX; ++i) {
		for (int j=0; j<Tile.LenY; ++j) {
			if ((Tile.Block[i][j])&&(CheckTileNoUpper(x+i,y+j))) return true;
		}
	}
	return false;
}
int CheckSpin(ClassTile Tile) {
	return NoPlacement(Tile,0,1)&&NoPlacement(Tile,0,-1)
		&&NoPlacement(Tile,1,0)&&NoPlacement(Tile,-1,0);
}
void RemoveActive() {
	for (int i=0; i<LenX; ++i) {
		for (int j=0; j<LenY; ++j) {
			if (Active[i][j]) {
				Active[i][j]=false; Board[i][j]=0;
			}
		}
	}
}
int FinalDrop() {
	string Construct = "";
	for (int i=0; i<LenX; ++i) {
		for (int j=0; j<LenY; ++j) {
			if (Active[i][j]) Construct+="*";
			else Construct+=".";
		}
		Construct += "|";
	}
	ClassTile Tile; Tile.Init(Construct,0);
	int Spin = CheckSpin(Tile);
	for (int i=0; i<LenX; ++i) {
		for (int j=0; j<LenY; ++j) Active[i][j]=false;
	}
	return Spin;
}
int SpawnTile(ClassTile Tile) {
	int PosY=(LenY>>1)+(Tile.LenY>>1)-1, PosX=DisX;
	PosY = max(PosY,Tile.LenY-1);
	PosY = min(PosY,LenY-1);
	PosX-=Tile.LenX-1; PosY-=Tile.LenY-1;
	for (int i=Tile.LenX-1; i>=0; --i) {
		bool flag = true;
		for (int j=0; j<Tile.LenY; ++j) {
			if (Tile.Block[i][j]) {
				flag=false; break;
			}
		}
		if (flag) ++PosX;
		else break; 
	}
	for (int i=0; i<Tile.LenX; ++i) {
		for (int j=0; j<Tile.LenY; ++j) {
			if ((Tile.Block[i][j])&&(CheckTile(PosX+i,PosY+j))) return false;
		}
	}
	for (int i=0; i<Tile.LenX; ++i) {
		for (int j=0; j<Tile.LenY; ++j) {
			if (Tile.Block[i][j]) {
				Board[PosX+i][PosY+j] = Tile.Color;
				Active[PosX+i][PosY+j] = true;
			}
		}
	}
	return true;
}
int CheckAllClear() {
	for (int i=0; i<LenX; ++i) {
		for (int j=0; j<LenY; ++j) {
			if (Board[i][j]) return false;
		}
	}
	return true;
}
long long StartTimeForKey;
char GetKeyInLimit(long long TimeLimit) {
	if (GetKeyState(VK_SHIFT)&32768) return 1;
	if (kbhit()) {
		char res='!'; while (kbhit()) res=getch();
		return res;
	}
	while ((!TimeLimit)||(clock()-StartTimeForKey<TimeLimit)) {
		if (GetKeyState(VK_SHIFT)&32768) return 1;
		if (kbhit()) {
			char res='!'; while (kbhit()) res=getch();
			return res;
		}
	}
	return '!';
}
int TileExist[100];
int RandomTile() {
	int MinCount=TileExist[0]; for (int i=1; i<TileCount; ++i) MinCount=min(TileExist[i],MinCount);
	int NextTile = Rnd.Rand(TileCount);
	while (TileExist[NextTile]!=MinCount) NextTile=Rnd.Rand(TileCount);
	if (RandomByBag) ++TileExist[NextTile];
	return NextTile;
}
int NewTile(int Certain=-1) {
	int NextTile = Certain;
	if (Certain==-1) {
		NextTile = TileQueue[0];
		for (int i=0; i+1<max(1,NextDisplay); ++i) TileQueue[i]=TileQueue[i+1];
		TileQueue[max(1,NextDisplay)-1] = RandomTile();
	}
	if (!SpawnTile(Tiles[NextTile][0])) return -1;
	else return NextTile;
}
int RemoveFullLines() {
	int NewLine=0, Lines=0;
	for (int i=0; i<LenX; ++i) {
		int LineClear = true;
		for (int j=0; j<LenY; ++j) {
			if (!Board[i][j]) {
				LineClear=false; break;
			}
		}
		if (LineClear) {
			++Lines;
			for (int j=0; j<LenY; ++j) DisappearEffect[i][j]=true;
		}
		else {
			if (NewLine<i) {
				for (int j=0; j<LenY; ++j) Board[NewLine][j]=Board[i][j];
			}
			++NewLine;
		}
	}
	for (int i=NewLine; i<LenX; ++i) {
		for (int j=0; j<LenY; ++j) Board[i][j]=0;
	}
	return Lines;
}
void AddAttackLayer(int y) {
	for (int i=LenX-2; i>=0; --i) {
		for (int j=0; j<LenY; ++j) {
			Board[i+1][j]=Board[i][j]; Active[i+1][j]=Active[i][j];
		}
	}
	for (int i=0; i<LenY; ++i) {
		Board[0][i]=(i==y)?0:8; Active[0][i]=false;
	}
	MoveDown();
}
int RemoveAttackLayer() {
	if (!AttackRemain) return false;
	for (int i=0; i+1<AttackRemain; ++i) AttackQueue[i]=AttackQueue[i+1];
	--AttackRemain;
	return true;
}
int EnableAttackLayer() {
	if (!AttackRemain) return false;
	AddAttackLayer(AttackQueue[0]);
	for (int i=0; i+1<AttackRemain; ++i) AttackQueue[i]=AttackQueue[i+1];
	--AttackRemain;
	return true;
}
int main() {
	SettingsBoard();
	if (!AttackLayerLimit) AttackLayerLimit=10000;
	if (!SingleAttackLimit) SingleAttackLimit=10000; 
	if (!MovesLimit) MovesLimit=1000000000;
	BasicTiles[0].Init("....|....|*#**|....|",11); // I
	BasicTiles[1].Init("*#*|*..|",1); // J
	BasicTiles[2].Init("*#*|..*|",6); // L
	BasicTiles[3].Init("**|#*|",14); // O
	BasicTiles[4].Init("*#.|.**|",10); // S
	BasicTiles[5].Init("*#*|.*.|",13); // T
	BasicTiles[6].Init(".#*|**.|",12); // Z
	KickTable[0][3] = {{ 0, 0}, {-1, 0}, {-1,+1}, { 0,-2}, {-1,-2}};
	KickTable[3][0] = {{ 0, 0}, {+1, 0}, {+1,-1}, { 0,+2}, {+1,+2}};
	KickTable[3][2] = {{ 0, 0}, {+1, 0}, {+1,-1}, { 0,+2}, {+1,+2}};
	KickTable[2][3] = {{ 0, 0}, {-1, 0}, {-1,+1}, { 0,-2}, {-1,-2}};
	KickTable[2][1] = {{ 0, 0}, {+1, 0}, {+1,+1}, { 0,-2}, {+1,-2}};
	KickTable[1][2] = {{ 0, 0}, {-1, 0}, {-1,-1}, { 0,+2}, {-1,+2}};
	KickTable[1][0] = {{ 0, 0}, {-1, 0}, {-1,-1}, { 0,+2}, {-1,+2}};
	KickTable[0][1] = {{ 0, 0}, {+1, 0}, {+1,+1}, { 0,-2}, {+1,-2}};
	IKickTable[0][3] = {{ 0, 0}, {-2, 0}, {+1, 0}, {-2,-1}, {+1,+2}};
	IKickTable[3][0] = {{ 0, 0}, {+2, 0}, {-1, 0}, {+2,+1}, {-1,-2}};
	IKickTable[3][2] = {{ 0, 0}, {-1, 0}, {+2, 0}, {-1,+2}, {+2,-1}};
	IKickTable[2][3] = {{ 0, 0}, {+1, 0}, {-2, 0}, {+1,-2}, {-2,+1}};
	IKickTable[2][1] = {{ 0, 0}, {+2, 0}, {-1, 0}, {+2,+1}, {-1,-2}};
	IKickTable[1][2] = {{ 0, 0}, {-2, 0}, {+1, 0}, {-2,-1}, {+1,+2}};
	IKickTable[1][0] = {{ 0, 0}, {+1, 0}, {-2, 0}, {+1,-2}, {-2,+1}};
	IKickTable[0][1] = {{ 0, 0}, {-1, 0}, {+2, 0}, {-1,+2}, {+2,-1}};
	for (int i=0; i<TileCount; ++i) {
		Tiles[i][0] = BasicTiles[i];
		for (int j=1; j<4; ++j) Tiles[i][j]=RotateTile(Tiles[i][j-1],1);
	}
	int MovesLeft=MovesLimit; QueueLen=1;
	for (int i=0; i<max(1,NextDisplay); ++i) TileQueue[i]=RandomTile();
	int TileID=NewTile(), TileStatus=0; Hold=-1;
	HoldAvailable = true;
	TotalTiles=TotalLines=Combo=0;
	MessageRemain = 0;
	long long LastAttack=BeginTime=StartTimeForKey=clock();
	Display();
	MessageA=MessageB="          ";
	AttackRemain = 0;
	int ResetCooldown = 0;
	int Win = false;
	for (;;) {
		char ch = GetKeyInLimit((long long)(CheckDown()?NormalTimeLimit:LockTimeLimit));
		int NextRound = false;
		if ((ch!='!')&&(ch!='q')&&(ch!='c')) {
			if (!MovesLeft) {
				ch='!'; MovesLeft=MovesLimit; 
			}
			else --MovesLeft;
		}
		else MovesLeft=MovesLimit;
		if (isupper(ch)) ch=tolower(ch);
		int HoldChange = false;
		OnGround = !CheckDown();
		int HoldTo = -1;
		if (ch!='r') {
			if (ResetCooldown) --ResetCooldown;
			else ResetBar=max(0,ResetBar-10);
		}
		if (ch==107) MoveLeft();
		else if (ch==109) MoveRight();
		else if (ch=='r') {
			++ResetBar; ResetCooldown=5; 
		}
		else if ((ch==104)||(ch=='z')) {
			if (Rotate(Tiles[TileID][TileStatus],Tiles[TileID][(TileStatus+1)&3],TileStatus,(TileStatus+1)&3)) TileStatus=(TileStatus+1)&3;
			//else if ((Enable180Spin)&&(Rotate(Tiles[TileID][TileStatus],Tiles[TileID][(TileStatus+2)&3],TileStatus,(TileStatus+2)&3))) TileStatus=(TileStatus+2)&3;
		}
		else if (ch=='x') {
			if (Rotate(Tiles[TileID][TileStatus],Tiles[TileID][(TileStatus+3)&3],TileStatus,(TileStatus+3)&3)) TileStatus=(TileStatus+3)&3;
			//else if ((Enable180Spin)&&(Rotate(Tiles[TileID][TileStatus],Tiles[TileID][(TileStatus+2)&3],TileStatus,(TileStatus+2)&3))) TileStatus=(TileStatus+2)&3;
		}
		else if ((ch=='a')&&(Enable180Spin)) {
			if ((Enable180Spin)&&(Rotate(Tiles[TileID][TileStatus],Tiles[TileID][(TileStatus+2)&3],TileStatus,(TileStatus+2)&3))) TileStatus=(TileStatus+2)&3;
		}
		else if (ch==' ') {
			while (CheckDown()) MoveDown();
			NextRound = true;
		}
		else if (ch=='c') {
			if ((EnableHold)&&(HoldAvailable)) {
				if (Hold>=0) {
					swap(Hold,TileID); HoldTo=TileID;
				}
				else Hold=TileID;
				NextRound=HoldChange=true; HoldAvailable=false;
				RemoveActive();
			}
		}
		else if ((ch==112)||(ch=='!')) {
			for (int i=0; i<((ch=='!')?Gravity:max(1,Gravity)); ++i) {
				if (CheckDown()) {
					MoveDown(); MovesLeft=MovesLimit;
					if ((ImmeadiateLock)&&(!CheckDown())) {
						NextRound=true; break;
					}
				}
				else if (ch=='!') {
					NextRound=true; break;
				}
			}
		}
		else if (ch=='q') {
			while (kbhit()) getch();
			Sleep(50); getch();
		}
		int Over = false;
		if (MessageRemain) {
			if (--MessageRemain==0) MessageA=MessageB="          ";
		}
		if (NextRound) {
			for (int i=0; i<LenX; ++i) {
				for (int j=0; j<LenY; ++j) DisappearEffect[i][j]=false;
			}
			if (!HoldChange) {
				MessageA=MessageB="          "; MessageColorB=15;
				int Spin=FinalDrop(); int NewLines=RemoveFullLines();
				if (NewLines) ++Combo;
				else Combo=0;
				TotalLines += NewLines;
				if (NewLines==1) MessageB="    SINGLE";
				if (NewLines==2) MessageB="    DOUBLE";
				if (NewLines==3) MessageB="    TRIPLE";
				if (NewLines==4) MessageB="   Q U A D";
				int NewAttack = 0;
				if (NewLines) {
					if (NewLines>1) NewAttack=1<<(NewLines-2);
					if ((Spin)&&((TileID==5)||(B2BForAllSpin))) NewAttack=NewLines<<1;
					--Combo;
					if (Combo>=2) ++NewAttack;
					if (Combo>=4) ++NewAttack;
					if (Combo>=6) ++NewAttack;
					if (Combo>=8) ++NewAttack;
					if (Combo>=11) ++NewAttack;
					++Combo;
				}
				if (CheckAllClear()) {
					MessageColorB=12; MessageB="ALL CLEAR ";
					NewAttack += 10;
				}
				if ((!AllSpin)&&(TileID!=5)) Spin=false;
				if (Spin) {
					MessageColorA = BasicTiles[TileID].Color;
					if (TileID==0) MessageA="    I-SPIN";
					if (TileID==1) MessageA="    J-SPIN";
					if (TileID==2) MessageA="    L-SPIN";
					if (TileID==3) MessageA="    O-SPIN";
					if (TileID==4) MessageA="    S-SPIN";
					if (TileID==5) MessageA="    T-SPIN";
					if (TileID==6) MessageA="    Z-SPIN";
				}
				if (NewLines) {
					if ((NewLines==4)||((Spin)&&((TileID==5)||(B2BForAllSpin)))) {
						++B2B; B2BMessage=(B2B>1);
						if (B2B>1) NewAttack+=int(floor(sqrt(double(B2B-1))));
					}
					else if (B2B>=2) {
						B2B=0; B2BMessage=true;
					}
					else {
						B2B=0; B2BMessage=false;
					}
					TotalAttack += NewAttack;
					while ((NewAttack)&&(AttackRemain)) {
						RemoveAttackLayer(); --NewAttack;
					}
					NewAttack = min(NewAttack,SingleAttackLimit);
					if (!AttackEnabled) NewAttack=0;
					int y = Rnd.Rand(LenY);
					for (int i=0; i<NewAttack; ++i) AttackQueue[AttackRemain++]=y; 
				}
				else {
					B2BMessage = false;
					int AttackReceive = min(AttackLayerLimit,AttackRemain);
					while (AttackReceive--) EnableAttackLayer();
				}
				MessageRemain = 15;
				HoldAvailable=true; ++TotalTiles;
			}
			TileID=NewTile(HoldTo); TileStatus=0;
			if (TileID==-1) Over=true;
		}
		if ((!Over)&&(CheckDown())&&(AttackByTime)&&(clock()-LastAttack>(long long)(AttackByTime))) {
			LastAttack=clock(); AddAttackLayer(Rnd.Rand(LenY));
		}
		StartTimeForKey = clock();
		if ((GoalLines)&&(TotalLines>=GoalLines)&&(!Win)) {
			color(14); system("cls");
			while (kbhit()) getch();
			printf("          TETRIS HOT NEWS!\n\n");
			color(15); printf("          WOW! Gamer xxx competed in %d Line(s)\n",GoalLines);
			printf("          challenge taking time ");
			double Seconds = double(clock()-BeginTime)*0.001+0.001;
			int SecondsInt = int(floor(Seconds));
			color(11); printf("%02d:%02d.\n", SecondsInt/60, SecondsInt%60);
			Record = min(Record, SecondsInt);
			color(7);
			if (Record>=SecondsInt) {
				Record=SecondsInt; color(10); printf("          NEW RECORD!\n");
			}
			else printf("          (Record : %02d:%02d.)\n", Record/60, Record%60);
			color(15);
			printf("\n          SHARE IT!\n          (press any key to countinue)\n");
			getch();
			system("cls");
			Win = true;
		}
		Display();
		if (ResetBar>=DisX) Over=true;
		if (Over) {
			for (int i=0; i<LenX; ++i) {
				for (int j=0; j<LenY; ++j) Active[i][j]=false;
			}
			TileID = -1;
			Win = false;
			int BlockCount=Combo=B2B=0;
			int BaseAttackRemain = AttackRemain;
			int BaseResetBar = ResetBar;
			int BaseTotalLines = TotalLines;
			int BaseTotalTiles = TotalTiles;
			int BaseTotalAttack = TotalAttack;
			for (int i=0; i<LenX; ++i) {
				for (int j=0; j<LenY; ++j) {
					if (Board[i][j]) ++BlockCount;
				}
			}
			int BlockRemain = BlockCount;
			B2BMessage=false; Hold=-1; HoldAvailable=true;
			MessageA="          "; MessageB="GAME OVER ";
			MessageRemain=1; MessageColorB=12;
			int lp = -1;
			for (int i=LenX-1; i>=0; --i) {
				for (int j=0; j<LenY; ++j) {
					if (!Board[i][j]) continue;
					--BlockRemain;
					TotalLines = int(floor(double(BaseTotalLines)/double(BlockCount)*double(BlockRemain)));
					TotalTiles = int(floor(double(BaseTotalTiles)/double(BlockCount)*double(BlockRemain)));
					TotalAttack = int(floor(double(BaseTotalAttack)/double(BlockCount)*double(BlockRemain)));
					AttackRemain = int(floor(double(BaseAttackRemain)/double(BlockCount)*double(BlockRemain)));
					ResetBar = int(floor(double(BaseResetBar)/double(BlockCount)*double(BlockRemain)));
					Board[i][j]=0; DisappearEffect[i][j]=true;
					if (lp!=BlockRemain*15/BlockCount) {
						Display(); lp=BlockRemain*15/BlockCount;
					}
				}
			}
			MovesLeft=MovesLimit; QueueLen=1;
			for (int i=0; i<TileCount; ++i) TileExist[i]=0;
			for (int i=0; i<max(1,NextDisplay); ++i) TileQueue[i]=RandomTile();
			LastAttack=StartTimeForKey=BeginTime=clock();
			TileID=NewTile(); TileStatus=0;
			while (kbhit()) getch();
			MessageRemain = 0;
			MessageA=MessageB="          ";
			system("cls"); Display();
		}
	}
	return 0;
}