/*

keymap:

	Anti-Clockwise Rotate : z
 	     Clockwise Rotate : x
	hard drop             : space
	left                  : <-
	right                 : ->
	hold                  : c
	down                  : anything else

bug:

	1.  some illegal z-spin and s-spin, but the code allow them.
	2.	pices not in c4w but top out.
	3.  ?
	4.  ?
	5.  ?
	
Author:

1. (Anonymous)
2. zKyGt1

Latest modify

2023/10/20

*/

#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;
	bool 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 LenX=40, DisX=21, LenY=10;//LenY=Width
int MovesLimit = 15;
int TileCount = 7;
int NormalTimeLimit=500, LockTimeLimit=500;
int NextDisplay = 5;
int Hold; bool HoldAvailable;
int TotalTiles, TotalLines, Combo, B2B;
bool B2BMessage;
int Board[100][100]; bool Active[100][100], DropTo[100][100];
int TileQueue[100], QueueLen;
long long BeginTime;
bool OnGround;
int MessageRemain; 
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;
}
bool CheckTile(int i, int j) {
	if (((i<0)||(i>=LenX))||((j<0)||(j>=LenY))) return true;
	return ((Board[i][j]!=0)&&(!Active[i][j]));
}
bool 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=0; i<LenX; ++i) {
		for (int j=0; j<LenY; ++j) {
			if (Active[i][j]) DropTo[i-DropLen][j]=true;
		}
	}
	for (int i=DisX; i>=0; --i) {
		curpos(DisX-i+5,12);
		color((DisX-i<2?8:7)<<4); printf("  ");
		for (int j=0; j<LenY; ++j) {
			if ((!Board[i][j])&&(DropTo[i][j])) {
				color(7); printf("□");
			}
			else if (!Active[i][j]) {
				color(Board[i][j]<<4); printf("  ");
			}
			else {
				color(Board[i][j]); printf("■");
			}
		}
		color((DisX-i<2?8:7)<<4); printf("  ");
		color(0);
	}
	color(((Hold==-1)||(!HoldAvailable))?8:15); curpos(2,1);
	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);
	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(20,1); printf("Pieces");
	color(10); curpos(21,1); printf("%d",TotalTiles);
	color(11); curpos(22,1); printf("%.2f / s",double(TotalTiles)/Seconds);
	color(15); curpos(24,1); printf("Lines");
	color(10); curpos(25,1); printf("%d",TotalLines);
	color(11); curpos(26,1); printf("%.2f / m",double(TotalLines*60)/Seconds);
}
bool 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;
}
bool 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;
}
bool 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;
}
bool 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;
}
bool 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;
}
bool 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;
}
bool 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;
}
bool Rotate(ClassTile OldTile, ClassTile NewTile) { // To be modified - 2023/10/20 11:04
	if(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)?0:OldTile.MidX-NewTile.MidX);
	int BaseY = FirstActiveY-FirstTileY+((NewTile.Color==14)?0:OldTile.MidY-NewTile.MidY);
	if (OnGround) {
		if (TryNewRotate(NewTile,BaseX-2,BaseY)) return true;
		if (TryNewRotate(NewTile,BaseX-1,BaseY)) return true;
		if (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;
	}
	return false;
}
set<pair<int,int> > NoSpinVisited;
bool NoSpinAvailable(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 false;
		}
	}
	return true;
}
bool CheckSpinDfs(ClassTile Tile, int x, int y) {
	if (x==LenX) return true;
	if (!NoSpinAvailable(Tile,x,y)) return false;
	if (NoSpinVisited.count(make_pair(x,y))) return false;
	NoSpinVisited.insert(make_pair(x,y));
	if (CheckSpinDfs(Tile,x+1,y)) return true;
	if (CheckSpinDfs(Tile,x,y-1)) return true;
	if (CheckSpinDfs(Tile,x,y+1)) return true;
	return false;
}
bool CheckSpin(ClassTile Tile) {
	NoSpinVisited.clear();
	return !CheckSpinDfs(Tile,0,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;
			}
		}
	}
}
bool 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);
	bool Spin = CheckSpin(Tile);
	for (int i=0; i<LenX; ++i) {
		for (int j=0; j<LenY; ++j) Active[i][j]=false;
	}
	return Spin;
}
bool 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=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;
}
bool CheckAllClear() {
	for (int i=0; i<LenX; ++i) {
		for (int j=0; j<LenY; ++j) {
			if (Board[i][j]) return false;
		}
	}
	return true;
}
char GetKeyInLimit(long long TimeLimit) {
	long long StartTime = clock();
	while (clock()-StartTime<TimeLimit) {
		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);
	++TileExist[NextTile];
	return NextTile;
}
int NewTile(int Certain=-1) {
	int NextTile = Certain;
	if (Certain==-1) {
		NextTile = TileQueue[0];
		for (int i=0; i+1<NextDisplay; ++i) TileQueue[i]=TileQueue[i+1];
		TileQueue[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) {
		bool LineClear = true;
		for (int j=0; j<LenY; ++j) {
			if (!Board[i][j]) {
				LineClear=false; break;
			}
		}
		if (LineClear) ++Lines;
		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;
}
int main() {
	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
	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);
	}
	//Tiles[4][2]=Tiles[4][0]; Tiles[4][1]=Tiles[4][3];
	//Tiles[6][2]=Tiles[6][0]; Tiles[6][3]=Tiles[6][1];
	//Board[0][0]=Board[1][0]=Board[1][1]=7; 
	int MovesLeft=MovesLimit; QueueLen=1;
	for (int i=0; i<NextDisplay; ++i) TileQueue[i]=RandomTile();
	int TileID=NewTile(), TileStatus=0; Hold=-1;
	HoldAvailable = true;
	TotalTiles=TotalLines=Combo=0;
	MessageRemain = 0;
	BeginTime = clock();
	Display();
	MessageA=MessageB="          ";
	for (;;) {
		char ch = GetKeyInLimit((long long)(CheckDown()?NormalTimeLimit:LockTimeLimit));
		bool NextRound = false;
		if (ch!='!') {
			if (!MovesLeft) {
				ch='!'; MovesLeft=MovesLimit; 
			}
			else --MovesLeft;
		}
		else MovesLeft=MovesLimit;
		if (isupper(ch)) ch=tolower(ch);
		//cout<<int(ch)<<endl; continue;
		bool HoldChange = false;
		OnGround = !CheckDown();
		int HoldTo = -1;
		if (ch==107) MoveLeft();
		else if (ch==109) MoveRight();
		else if (ch=='z') {
			if (Rotate(Tiles[TileID][TileStatus],Tiles[TileID][(TileStatus+1)&3])) TileStatus=(TileStatus+1)&3;
			else if (Rotate(Tiles[TileID][TileStatus],Tiles[TileID][(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;
			else if (Rotate(Tiles[TileID][TileStatus],Tiles[TileID][(TileStatus+2)&3])) TileStatus=(TileStatus+2)&3;
		}
		else if (ch==' ') {
			while (CheckDown()) MoveDown();
			NextRound = true;
		}
		else if (ch=='c') {
			if (HoldAvailable) {
				if (Hold>=0) {
					swap(Hold,TileID); HoldTo=TileID;
				}
				else Hold=TileID;
				NextRound=HoldChange=true; HoldAvailable=false;
				RemoveActive();
			}
		}
		else {
			if (CheckDown()) {
				MoveDown(); MovesLeft=MovesLimit;
			}
			else if (ch=='!') NextRound=true;
		}
		bool Over = false;
		if (MessageRemain) {
			if (--MessageRemain==0) MessageA=MessageB="          ";
		}
		if (NextRound) {
			if (!HoldChange) {
				MessageA=MessageB="          "; MessageColorB=15;
				bool 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";
				if (CheckAllClear()) {
					MessageColorB=12; MessageB="ALL CLEAR ";
				}
				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))) {
						++B2B; B2BMessage=(B2B>1);
					}
					else if (B2B>=2) {
						B2B=0; B2BMessage=true;
					}
					else {
						B2B=0; B2BMessage=false;
					}
				}
				else B2BMessage=false;
				MessageRemain = 15;
				HoldAvailable=true; ++TotalTiles;
			}
			TileID=NewTile(HoldTo); TileStatus=0;
			if (TileID==-1) Over=true;
		}
		Display();
		if (Over) for (;;);
	}
	return 0;
}