#include <Turboc.h>
 
#define LEFT 75              // 각키값들을메크로정의
#define RIGHT 77
#define UP 72
#define DOWN 80
#define ESC 27
#define BX 5
#define BY 1
#define BW 10
#define BH 20
#define PGUP 73
#define PGDN 81
 
 
void DrawScreen();            // 화면전체를그림. 게임판과게임이름벽까지한번에그림
void DrawBoard();             // 게임판만그린다. 외부벽과문자열들은빼고쌓여있는벽돌만그린다.
BOOL ProcessKey();            // 키입력을처리하는데main 함수의부담을덜어주기위해
                              // 별도로분리되있다. 이동중인벽돌이바닥에놓이면TRUE를리턴.
void PrintBrick(BOOL Show);  
// 벽돌을출력하거나삭제함(이동중인벽돌을대상으로) - 전역변수brick, rot, nx, ny를참조
int GetAround(int x,int y,int b,int r);     
// 벽돌주변에무엇이있는지검사하여이동중인벽돌을대상으로하므로전역변수brick, rot, nx, ny값을참조한다.
BOOL MoveDown();       // 벽돌을한칸아래로이동시킨다. 만약바닥에닿았다면TestFull 함수를호출한후TRUE를리턴한다.
void TestFull();       // 수평으로다채워진줄을찾아삭제하고점수를계산한다.
void PrintInfo();      // 점수게시판을그린다.(TestFull안에서호출)
void DrawNext();       // 다음나올벽돌의모양을미리보여주는함수
 
struct Point {
        int x,y;
};

Point *Shape[][4][4]={  //      Shape[벽돌번호][회전번호][일련번호] - x, y
        { {0,0,1,0,2,0,-1,0}, {0,0,0,1,0,-1,0,-2}, {0,0,1,0,2,0,-1,0}, {0,0,0,1,0,-1,0,-2} },
        { {0,0,1,0,0,1,1,1}, {0,0,1,0,0,1,1,1}, {0,0,1,0,0,1,1,1}, {0,0,1,0,0,1,1,1} },
        { {0,0,-1,0,0,-1,1,-1}, {0,0,0,1,-1,0,-1,-1}, {0,0,-1,0,0,-1,1,-1}, {0,0,0,1,-1,0,-1,-1} },
        { {0,0,-1,-1,0,-1,1,0}, {0,0,-1,0,-1,1,0,-1}, {0,0,-1,-1,0,-1,1,0}, {0,0,-1,0,-1,1,0,-1} },
        { {0,0,-1,0,1,0,-1,-1}, {0,0,0,-1,0,1,-1,1}, {0,0,-1,0,1,0,1,1}, {0,0,0,-1,0,1,1,-1} },
        { {0,0,1,0,-1,0,1,-1}, {0,0,0,1,0,-1,-1,-1}, {0,0,1,0,-1,0,-1,1}, {0,0,0,-1,0,1,1,1} },
        { {0,0,-1,0,1,0,0,1}, {0,0,0,-1,0,1,1,0}, {0,0,-1,0,1,0,0,-1}, {0,0,-1,0,0,-1,0,1} },
        { {0,0,0,0,0,-1,1,0}, {0,0,0,0,-1,0,0,-1}, {0,0,0,0,0,1,-1,0}, {0,0,0,0,0,1,1,0} },
};
 
enum { EMPTY, BRICK, WALL };  // 게임판의세가지상태를말한다. EMPTY=0, BRICK=1, WALL=2
                              // 매크로선언의중복을피할수있으며가독성의효과가있다.
 
char *arTile[][3]={           // 빈공간및벽돌, 벽의상태를나타내는문자(2byte)
        {". ","■","□"},
              
        {"  ","■","□"},
       
        {"  ","##","II"},
       
        {"  ","●","▣"},
};
 
int ttype=0;                  // 현재사용중인타일모양을저장하기위한ttype 전역변수
int nbrick;                   // 다음나올벽돌을저장하기위한전역변수
 
int board[BW+2][BH+2];
int nx,ny;
int brick,rot;
 
int score;                    // 점수를계산하는전역변수
int bricknum;                // 총벽돌의갯수
 
 
void main()
{
        int nFrame, nStay;
        int x,y;
       
        setcursortype(NOCURSOR);      // 커서감추기
        randomize();                  // srand((unsigned)time(NULL));
       
        for( ;3; ){
              
               clrscr();                             // system("cls");
 
               for (x=0;x<BW+2;x++) {
                       for (y=0;y<BH+2;y++) {
                              board[x][y] = (y==0 || y==BH+1 || x==0 || x==BW+1) ? WALL:EMPTY;
                                                             // y축이게임판의끝과끝이면벽돌
                                                             // x축이게임판의끝과끝이면벽돌
                       }
               }
               DrawScreen();                         // 화면을그린다
               nFrame=20;                            // 게임의속도( 프레임)을가지는변수
 
               score=0;                              // 점수초기화
               bricknum=0;                           // 벽돌갯수초기화
              
               nbrick=random(sizeof(Shape)/sizeof(Shape[0]));       // nbrick값랜덤초기화
 
               for (;1;) {
                       bricknum++;                 // 벽돌이생성될때마다bricknum 변수를증가
 
                       brick=nbrick;               // 랜덤값뽑아놓은벽돌번호로brick 변수에대입
                       nbrick=random(sizeof(Shape)/sizeof(Shape[0]));      
                        // 8가지벽돌중에몇번째벽돌을출력할것인가?
                       // 다음에나올벽돌값이될것임
                       DrawNext();                 // 다음나올벽돌을미리보여줌
                       nx=BW/2;                    // 움직이는벽돌의위치- 새로운벽돌이나올위치
                       ny=3;
                       rot=0;                      // 회전번호0번으로초기화
                       PrintBrick(TRUE);        
                       // PrintBrick의인자값이TRUE, FALSE이냐에따라벽돌출력여부결정
                      
                       if (GetAround(nx,ny,brick,rot) != EMPTY) break;
                      
                       nStay=nFrame;
                      
                       for (;2;) {
                              if (--nStay == 0) {
                                     
                                      nStay = nFrame;
                                     
                                      if (MoveDown()) break;
                  // while( MoveDown() == FALSE );
                  // 벽돌을전부내릴때는MoveDown이TRUE를리턴할때까지계속호출해대기만하면된다.
                  // 그래서while문으로MoveDown이FALSE를리턴하는동안계속반복하는데
                  // 이반복문은반복자체가목적이며반복중에따로할일이없다
 
                              }
                              if (ProcessKey()) break;    
                              // 스페이스바가들어오면MoveDown을반복호출함
                      
                              delay(1000/20);
                              // 게임을유지하기위한딜레이( 벽돌의내리꼿는속도)
                       }
 
                       if (bricknum % 10 == 0 && nFrame > 5) {             
               nFrame--;             
              // 벽돌이10개떨어질때마다프레임수를감소시켜벽돌이떨어지는속도를증가시킨다
              // 즉, 게임의난이도증가
           }
 
               }
       
               clrscr();                            
               gotoxy(30,12);puts("G A M E  O V E R");
               gotoxy(25,14);puts("다시시작하려면Y를누르세요");
              
        if (tolower(getch()) != 'y') break;          // 재시작을 물어봄

    }
       
        setcursortype(NORMALCURSOR);                
}
 

// 화면전체를그림. 게임판과게임이름벽까지한번에그림
void DrawScreen()                         
{
        int x,y;
       
        for (x=0;x<BW+2;x++) {
               for (y=0;y<BH+2;y++) {
                       gotoxy(BX+x*2,BY+y);
                       puts(arTile[ttype][board[x][y]]);
               }
        }
       
        gotoxy(50,3);puts("Tetris Ver 1.1");
       
        gotoxy(50,6);puts("공백:전부내림, ESC:종료");
    gotoxy(50,7);puts("P:정지, PgUp,PgDn:모양바꿈");
 
        DrawNext();
}
 
void DrawBoard()                           
// 게임판만그린다. 외부벽과문자열들은빼고쌓여있는벽돌만그린다.
{
        int x,y;
       
        for (x=1;x<BW+1;x++) {
               for (y=1;y<BH+1;y++) {
                       gotoxy(BX+x*2,BY+y);
                       puts(arTile[ttype][board[x][y]]);
               }
        }
}
 
BOOL ProcessKey()                           
// 키입력을처리하는데main 함수의부담을덜어주기위해
{                                                           
// 별도로분리되있다. 이동중인벽돌이바닥에놓이면TRUE를리턴.
        int ch,trot;
       
        if (kbhit()) {
               ch=getch();
               if (ch == 0xE0 || ch == 0) {
                       ch=getch();
                      
                       switch (ch) {
                       case LEFT:
                              if (GetAround(nx-1,ny,brick,rot) == EMPTY) {
                                      PrintBrick(FALSE);
                                      nx--;
                                      PrintBrick(TRUE);
                              }
                              break;
                       case RIGHT:
                              if (GetAround(nx+1,ny,brick,rot) == EMPTY) {
                                      PrintBrick(FALSE);
                                      nx++;
                                      PrintBrick(TRUE);
                              }
                              break;
                       case UP:
                              trot=(rot == 3 ? 0:rot+1);
                              if (GetAround(nx,ny,brick,trot) == EMPTY) {
                                      PrintBrick(FALSE);
                                      rot=trot;
                                      PrintBrick(TRUE);
                              }
                              break;
                       case DOWN:
                              if (MoveDown()) {
                                      return TRUE;
                              }
                              break;
                             
                             
                       case PGDN:
                             
                              ttype++;
                              if (ttype == sizeof(arTile)/sizeof(arTile[0])) ttype=0;
                              DrawScreen();
                              PrintBrick(TRUE);
                              break;
                             
                       case PGUP:
                             
                              if (ttype == 0) ttype=sizeof(arTile)/sizeof(arTile[0]);
                              ttype--;
                              DrawScreen();
                               PrintBrick(TRUE);
                              break;
                       }
               } else {
                       switch (ch) {
                       case ' ':
                              while(MoveDown()==FALSE);
                              return TRUE;
                             
                       case ESC:
                              exit(0);
                             
                       case 'p':
                              clrscr();
                              gotoxy(15, 10);
                              puts("Tetris 잠시중지. 다시시작하려면아무키나누르세요,");
                              getch();
                              clrscr();
                              DrawScreen();
                              PrintBrick(TRUE);
                              break;
                             
                             
                       }
               }
        }
        return FALSE;
}
 
void PrintBrick(BOOL Show)   
// 벽돌을출력하거나삭제함(이동중인벽돌을대상으로) - 전역변수brick, rot, nx, ny를참조
{
        int i;
       
        for (i=0;i<4;i++) {
               gotoxy(BX+(Shape[brick][rot][i].x+nx)*2,BY+Shape[brick][rot][i].y+ny);
 
               puts(arTile[ttype][Show ? BRICK:EMPTY]);
        }
}
 
int GetAround(int x,int y,int b,int r)
{             
// 벽돌주변에무엇이있는지검사하여이동중인벽돌을대상으로하므로전역변수brick, rot, nx, ny값을
// 참조함

        int i,k=EMPTY;
       
        for (i=0;i<4;i++) {
               k=max(k,board[x+Shape[b][r][i].x][y+Shape[b][r][i].y]);
        }
        return k;
}
 
BOOL MoveDown()
{      
// 벽돌을한칸아래로이동시킨다. 만약바닥에닿았다면TestFull 함수를호출한후TRUE를리턴한다.
        if (GetAround(nx,ny+1,brick,rot) != EMPTY) {
        // y축다음한칸뒤가비어있지않으면이동하지않고TestFull함수호출
               TestFull();
               return TRUE;
        }
        PrintBrick(FALSE);                    // 다운키가활성화됐을시잠시1회블럭을감춘다
        ny++;                                 // y축으로한칸이동
        PrintBrick(TRUE);                     // 다시블럭표시!
       
        return FALSE;
}
 
// 수평으로 다채워진 줄을 찾아삭제하고 점수를 계산
void TestFull()       
{
        int i,x,y,ty;
        int count=0;                 
 
        static int arScoreInc[]={ 0, 1, 3, 8, 20 };
       
        for (i=0;i<4;i++) {
               board[nx+Shape[brick][rot][i].x][ny+Shape[brick][rot][i].y]=BRICK;
        }
       
        for (y=1;y<BH+1;y++) {
               for (x=1;x<BW+1;x++) {
                       if (board[x][y] != BRICK) break;     
// 게임판이벽돌이아니면멈춤!
               }
               if (x == BW+1) {                      // 벽돌이한줄꽉찾으면!
 
                       count++;                      // 점수변수인카운트증가
 
                       for (ty=y;ty>1;ty--) {
                              for (x=1;x<BW+1;x++) {
                                      board[x][ty]=board[x][ty-1];
                              }
                       }
                       DrawBoard();
                       delay(200);
               }
        }
        score += arScoreInc[count];           // 더많은줄을없앨수록점수가증가함
        PrintInfo();                          // 점수게시판출력
}
 
// 점수게시판출력
void PrintInfo()                                    
{
     gotoxy(50, 9);printf("점수: %d     ",score);
     gotoxy(50,10);printf("벽돌: %d 개 ",bricknum);
}
 
// 다음나올벽돌의모양을미리보여주는함수
void DrawNext()              
{
        int x,y,i;
              
        for (x=50;x<=70;x+=2) {
               for (y=12;y<=18;y++) {
                       gotoxy(x,y);
                       puts(arTile[ttype][(x==50 || x==70 || y==12 || y == 18) ? WALL:EMPTY]);     // 끝과끝이아니면빈공간출력
               }
        }
       
        for (i=0;i<4;i++) {   
               gotoxy(60+(Shape[nbrick][0][i].x)*2,15+Shape[nbrick][0][i].y);                             // 블럭그리기
               puts(arTile[ttype][BRICK]);
        }
}