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