/*
        Push Push Ver 1.0
*/
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <conio.h>
#include <iostream>
using namespace std;

const int WIDTH = 10;
const int HEIGHT = 10;
const int TOTAL_STAGE = 10;
const int SZ_HISTORY = 999;

enum e_MoveDirection { UP, DOWN, RIGHT, LEFT };
enum e_Object { EMPTY, WALL, HART, BOX, PLAYER = 5, GOAL };

struct _Environment {
        COORD currentPlayerPos;
        int stage;
        int step;
        int tmSec; // 스테이지 경과 시간
        int totalScore; // 총 점수   
        DWORD tmStart; // 스테이지 시작 시간. 경과 시간을 구하는데만 사용.
        BOOL pause; // 스레드 동작을 멈출 때 사용.
        BOOL restart; // 스레드를 다시 시작할 때 사용.
        BOOL quit; // 스레드를 종료할 때 사용.
} Env;

struct _GoalInfo{
        int numOfGoal;
        COORD arrLocateOfGoal[99];
} GoalInfo;

typedef struct {
        int stage;
        int score;
} ScoreSet;

HANDLE hMutex; // Mutex 핸들
HANDLE hEvent; // Event 핸들
HANDLE hThread; // Thread 핸들
DWORD dwThreadID; // Thread 아이디
int g_TableOfMap[HEIGHT][WIDTH]; // 맵 테이블
char *aTile[] = {"  ", "▨", "♡", "⊙", "☆", ". "};
/*
        "  " = EMPTY
        "▨" = WALL
        "♡" = HART
        "⊙" = BOX
        "☆" = PLAYER
        ". " = GOAL
*/

void gotoxy(int, int);


//-------------------------------------
//              main.h
//-------------------------------------
class CHighScore
{
public:
        CHighScore();

        void GetHighScore(ScoreSet);
        void SetHighScore(ScoreSet);
        void ShowHighScore();
private:
        char *filename;
        ScoreSet S;
};

class CDrawScreen
{
public:
        CDrawScreen(int, int);

        void DrawMenu();
        void DrawBackground();
        void DrawBoard(BOOL visible = TRUE);
        void DrawScoreTable();
        void ShowClearEvent();
        COORD GetBoardPos() { return boardPos; };

private:
        int defDrawPosX;
        int defDrawPosY;
        RECT backgroundRect;
        COORD menuPos;
        COORD boardPos;
        COORD scoreTablePos;
};

class CStage
{
public:
        void StageLoad();
        void StageEnd();
        BOOL NewStage();
        BOOL CheckStageClear();
};

class CMoveRec
{
public:
        CMoveRec();

        void SavePath(int, int, BOOL);
        void Undo(LPVOID);

private:
        struct {
                COORD pos;
                BOOL bWithBox;
        } MoveInfo[SZ_HISTORY];
        int MoveIdx;
};

class CMoveMap
{
public:
        BOOL Move(e_MoveDirection);
        void MarkObject(int, int, e_Object);
        void RemoveObject(int, int);
        BOOL isEmpty(int, int);
        BOOL isGoal(int, int);
        void Undo();

private:
        CMoveRec MoveRec;
};

class CPushPush
{
public:
        CPushPush(
                int x = 3,
                int y = 3,
                int stage = 1
        ) : DrawScreen(x, y)
        {
                Env.stage = stage;
                hMutex = CreateMutex(NULL, FALSE, NULL); // 뮤텍스를 생성한다.
        }
        ~CPushPush()
        {
                ReleaseMutex(hMutex); // 뮤텍스를 반환한다.
        }

        void InitializeGame();
        void StartMenu();
        void StartGame();
        void PauseGame(int, int);
        BOOL QuitGame(int, int);

private:
        CStage Stage;
        CDrawScreen DrawScreen;
        CMoveMap MoveMap;
        CHighScore HighScore;

        BOOL KeyProcess();
        friend DWORD WINAPI ThreadProc(LPVOID lpParam);
};

//---------------------------------------------
//              CPushPush.cpp
//---------------------------------------------
void CPushPush::InitializeGame()
{
        // 스레드를 생성한다.
        hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
        hThread = CreateThread(NULL, 0, ThreadProc, (LPVOID)this, 0, &dwThreadID);
        if(hThread == NULL)
        {
                gotoxy(1, 1);
                cout << "CreateThread failed." << endl;
                exit(1);
        }

        // STD_OUT의 커서를 숨긴다.
        CONSOLE_CURSOR_INFO CurInfo;
        CurInfo.dwSize = 1;
        CurInfo.bVisible = FALSE;
        SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &CurInfo);
}

void CPushPush::StartMenu()
{
        char slct = 0;

        while(slct != 3)
        {
                DrawScreen.DrawMenu();

                if(slct = _getch())
                {
                        switch(slct)
                        {
                        case '1':
                                StartGame();
                                break;
                        case '2':
                                HighScore.ShowHighScore();
                                break;
                        case '3':
                                Env.pause = TRUE;
                                Env.quit = TRUE;
                                SetEvent(hEvent); // 스레드에 종료 시그널을 보낸다.
                                WaitForSingleObject(hThread, INFINITE); // 스레드가 종료될 때까지 기다린다.
                                CloseHandle(hThread); // 핸들을 닫는다.
                                CPushPush::~CPushPush(); // 클래스 파괴자 임의 호출
                                gotoxy(1, 1);
                                exit(1);
                        default:
                                break;
                        }
                }
        }
}

void CPushPush::StartGame()
{
        // 배경 화면을 그린다.
        DrawScreen.DrawBackground();

        // 모든 스테이지를 클리어 할 때까지 새 스테이지를 읽어들인다.
        while(Stage.NewStage())
        {
                while(Stage.CheckStageClear() != TRUE)
                {
                        DrawScreen.DrawBoard();
                        DrawScreen.DrawScoreTable();

                        if(KeyProcess())
                        {
                                // QuitGame()에서 '1. 메인 메뉴로'가 선택되면.
                                // 메인 메뉴로 돌아간다.
                                return;
                        }
                        if(Env.pause)
                        {
                                Env.pause = FALSE;
                                Stage.NewStage();
                        }
                }
                DrawScreen.DrawBoard();
                DrawScreen.DrawScoreTable();
                DrawScreen.ShowClearEvent();
                ++(Env.stage); // 다음 스테이지
        }
}

BOOL CPushPush::KeyProcess()
{
        int ch = 0;

        if(ch = _getch())
        {
                //ch = ((97 <= ch) && (ch <= 122))?ch-=32:ch;
                switch(ch)
                {
                        case 72:        // UP
                                MoveMap.Move(UP);
                                break;
                        case 80:        // DOWN
                                MoveMap.Move(DOWN);
                                break;
                        case 75:        // LEFT
                                MoveMap.Move(LEFT);
                                break;
                        case 77:        // RIGHT
                                MoveMap.Move(RIGHT);
                                break;
                        case 32:        // SPACE
                                break;
                        case 'p':       // P
                                Env.pause = TRUE;
                                DrawScreen.DrawBoard(FALSE);
                                PauseGame(static_cast<int>((DrawScreen.GetBoardPos().X + WIDTH)/2) + 4,
                                        static_cast<int>((DrawScreen.GetBoardPos().Y + HEIGHT)/2) + 1);
                                Env.pause = FALSE;
                                break;
                        case 'q':       // Q
                                Env.pause = TRUE;
                                DrawScreen.DrawBoard(FALSE);
                                if(QuitGame(static_cast<int>((DrawScreen.GetBoardPos().X + WIDTH)/2) + 9,
                                        static_cast<int>((DrawScreen.GetBoardPos().Y + HEIGHT)/2) - 2))
                                {
                                        // QuitGame() 에서 '1. 메인 메뉴로'가 선택되었다.
                                        // TRUE 를 리턴한다.
                                        return TRUE;
                                }
                                Env.pause = FALSE;
                                break;
                        case 'r':       // R
                                MoveMap.Undo();
                                break;
                        case 's':       // S
                                Env.pause = TRUE;
                                cin >> Env.stage;
                                break;
                        case 'z':       // Z
                                Env.pause = TRUE;
                                DrawScreen.DrawBoard(FALSE);
                                cin >> Env.stage;
                                break;
                        default:
                                break;
                }
        }
        return FALSE;
}

void CPushPush::PauseGame(int nx, int ny)
{
        gotoxy(nx, ny);
        cout << "P A U S E" << endl;

        _getch();
}

BOOL CPushPush::QuitGame(int nx, int ny)
{
        char ch = 0;

        gotoxy(static_cast<int>(nx / 2), 4 + static_cast<int>(ny / 2));
        cout << "1. 메인 메뉴로" << endl;
        gotoxy(static_cast<int>(nx / 2), 5 + static_cast<int>(ny / 2));
        cout << "2. 게임 종료 " << endl;
        gotoxy(static_cast<int>(nx / 2), 6 + static_cast<int>(ny / 2));
        cout << "3. 취소" << endl;

        ch = _getch();

        switch(ch)
        {
        case '1':
                // 1. 메인 메뉴로
                Env.restart = TRUE; // 스레드에 다시 시작 시그널을 보낸다.
                Env.stage = 1;
                return TRUE;
        case '2':
                // 2. 게임 종료
                Env.quit = TRUE; // 스레드에 종료 시그널을 보낸다.
                WaitForSingleObject(hThread, INFINITE); // 스레드가 종료될 때까지 기다린다.
                CloseHandle(hThread); // 핸들을 닫는다.
                CPushPush::~CPushPush(); // 클래스 파괴자 임의 호출
                gotoxy(1, 1);
                exit(1);
        case '3':
                // 3. 취소
                return FALSE;
        }
        return FALSE;
}

//--------------------------------------------
//              CHighScore.cpp
//---------------------------------------------

/*
  Space-Separated Values files 기반의 푸시푸시 최고점수 파일 사양에 대한 스펙

  1. 파일의 정의

        각 레코드는 ' ' 화이트 스페이스에 의해 구분된다.
        라인의 경계는 캐리지 리턴 라인 피드로 결정된다.

        For example:

                  111 222 333 CRLF
                  777 888 999 CRLF

  2. 각 레코드의 의미

        첫 10줄은 최고 점수와 완료 스테이지를 
        1위부터 10위까지 기억한다.

            rank(1-10) totalScore stage

    다음 줄은 TOTAL_STAGE 에 정의된 스테이지의 개수 만큼 입력되며
        각 스테이지의 최소 스텝과 최소 경과시간을 기억한다.
        
                stage(1-TOTAL_STAGE) step time

  For example:

      1 23604 10
      2 24000 10
      3 17050 7
         ...
      10 607 3
          11 23 5
      12 53 20
             ...
          20 231 120
*/
CHighScore::CHighScore()
{
        int i;
        filename = "highscore.txt";

        FILE *fp = fopen(filename, "r"); // highscore.txt 파일을 읽기 모드로 연다.
        if(fp == NULL) // 만약 파일이 없으면 초기화된 최고 점수 파일을 만든다.
        {
                fp = fopen(filename, "w");

                for(i = 1; i <= 10 + TOTAL_STAGE; ++i)
                {
                        fprintf(fp, "%d 0 0\n", i);
                }
        }
        fclose(fp); // 파일을 닫는다.
}

void CHighScore::ShowHighScore()
{
        int nx = 8;
        int ny = 3;
        int n1, n2, n3; // 파일 라인 임시 저장 변수.
        int i;

        FILE *fp = fopen(filename, "r"); // highscore.txt 파일을 읽기 모드로 연다.

        system("cls");

        gotoxy(nx,ny++);
        cout << "▤▤▤▤▤▤▤▤▤▤▤▤▤▤";
        gotoxy(nx,ny++);
        cout << "▤       HIGH SCORE       ▤";
        gotoxy(nx,ny++);
        cout << "▤▤▤▤▤▤▤▤▤▤▤▤▤▤";
        gotoxy(nx,ny++);
        cout << "▤ RANK    SCORE   STAGE  ▤";
        for(i = 0; i < 10; ++i)
        {
                gotoxy(nx,ny);
                cout << "▤                        ▤";

                fscanf(fp, "%d %d %d", &n1, &n2, &n3);

                gotoxy(nx+5,ny++);
                printf("%-2d %8d %5d\n", n1, n2, n3);
        }
        gotoxy(nx,ny++);
        cout << "▤▤▤▤▤▤▤▤▤▤▤▤▤▤";

        _getch();
        fclose(fp); // 파일을 닫는다.
}

//---------------------------------------------
//              CDrawScreen.cpp
//---------------------------------------------
CDrawScreen::CDrawScreen(int x, int y) : defDrawPosX(x), defDrawPosY(y)
{
        // 배경화면의 초기위치
        backgroundRect.left = x;
        backgroundRect.top = y;
        backgroundRect.right = 25;
        backgroundRect.bottom = 16;

        // 메뉴의 초기위치
        menuPos.X = x + 7;
        menuPos.Y = y + 5;

        // 게임 화면의 초기위치
        boardPos.X = x + 2;
        boardPos.Y = y + 1;

        // 스코어 테이블의 초기위치
        scoreTablePos.X = x + 25;
        scoreTablePos.Y = y + 1;
}

void CDrawScreen::DrawMenu()
{
        // 메뉴의 초기 위치를 입력한다.
        int nx = menuPos.X;
        int ny = menuPos.Y;

        // 전체 화면을 지운다.
        system("cls");

        // 메뉴를 출력한다.
        gotoxy(nx, ny++);
        cout << "1. 게임 시작";
        gotoxy(nx , ny++);
        cout << "2. 최고 점수";
        gotoxy(nx , ny++);
        cout << "3. 게임 종료";
}

void CDrawScreen::DrawBackground()
{
        // 배경화면의 초기위치를 입력한다.
        int nx = backgroundRect.left;
        int ny = backgroundRect.top;
        int i,j;

        // 전체 화면을 지운다.
        system("cls");

        // 배경화면을 그린다.
        for(i = 0; i < backgroundRect.bottom; ++i)
        {
                gotoxy(nx, ny++);
                for(j = 0; j < backgroundRect.right; ++j)
                {
                        cout << "▒";
                }
        }
}

void CDrawScreen::DrawBoard(BOOL visible)
{
        // Mutex 를 얻는다.
        WaitForSingleObject(hMutex, INFINITE);

        // 게임 화면이 그려질 위치를 입력한다.
        int nx = boardPos.X;
        int ny = boardPos.Y;
        int i, j;

        // 게임 화면을 그린다.
        for(i = 0; i < HEIGHT; ++i)
        {
                gotoxy(nx, ny++);
                for(j = 0; j < WIDTH ; ++j)
                {
                        if(visible == FALSE)
                        {
                                cout << aTile[0];
                                continue;
                        }

                        if(g_TableOfMap[i][j] == EMPTY)
                                cout << aTile[0];
                        else if(g_TableOfMap[i][j] == WALL)
                                cout << aTile[1];
                        else if(g_TableOfMap[i][j] == BOX)
                                cout << aTile[3];
                        else if(g_TableOfMap[i][j] == PLAYER)
                                cout << aTile[4];
                        else if(g_TableOfMap[i][j] == GOAL)
                                cout << aTile[5];
                        else
                                cout << aTile[2];
                }
                cout << endl;

        }
        gotoxy(1, 1);

        // Mutex 를 반환한다.
        ReleaseMutex(hMutex);
}

void CDrawScreen::DrawScoreTable()
{
        // Mutex 를 얻는다.
        WaitForSingleObject(hMutex, INFINITE);

        // 스코어 테이블이 그려질 위치를 입력한다.
        int nx = scoreTablePos.X;
        int ny = scoreTablePos.Y;

        gotoxy(nx , ny++);
        cout << "P u s h P u s h Ver1.0 ";
        ++ny;

        gotoxy(nx + 6, ny);
        cout << "   "; // 갱신되는 부분을 지운다.
        gotoxy(nx , ny);
        cout << "Stage " << Env.stage;

        gotoxy(nx + 14 , ny);
        cout << "최고점수";
        ++ny;
        ++ny;

        gotoxy(nx + 6, ny);
        cout << "   "; // 갱신되는 부분을 지운다.
        gotoxy(nx , ny);
        cout << "Step : " << Env.step;

        gotoxy(nx + 18 , ny);
        cout << "   "; // 갱신되는 부분을 지운다.
        gotoxy(nx + 14 , ny);
        cout << "Step: 0";
        ++ny;

        gotoxy(nx + 6 , ny);
        cout << "  "; // 갱신되는 부분을 지운다.
        gotoxy(nx , ny);
        cout << "Time : " << Env.tmSec;

        gotoxy(nx + 18 , ny);
        cout << "   "; // 갱신되는 부분을 지운다.
        gotoxy(nx + 14 , ny);
        cout << "Time: 0";
        ny+=2;

        gotoxy(nx , ny++);
        cout << "방향키 : 이동         ";
        gotoxy(nx , ny++);
        cout << "R : 되돌리기, P : 멈춤";
        gotoxy(nx , ny++);
        cout << "Z : 다시시작, Q : 종료 ";

        gotoxy(1, 1);

        // Mutex 를 반환한다.
        ReleaseMutex(hMutex);
}

void CDrawScreen::ShowClearEvent()
{
        // 클리어 화면을 보여준다.
        int nx = static_cast<int>((defDrawPosX + WIDTH)/2) + 2;
        int ny = static_cast<int>((defDrawPosY + HEIGHT)/2);
        int i, j;

        Env.pause = TRUE;
        DrawScoreTable();
        for(i = 1; i <= HEIGHT; ++i)
        {
                gotoxy(defDrawPosX + 2, defDrawPosY + i);
                for(j = 0; j < WIDTH; ++j)
                {
                        cout << ". ";
                        Sleep(25);
                }
                cout << endl;
        }
        gotoxy(nx, ny);
        cout << "Stage " << Env.stage << " clear!";
        ny+=3;
        gotoxy(nx, ny++);
        cout << "Step " << Env.step;
        gotoxy(nx, ny++);
        cout << "Time " << Env.tmSec;
        gotoxy(nx, ny++);
        cout << "Point " << Env.tmSec * Env.step;

        _getch();
}

//---------------------------------------------
//              CStage.cpp
//---------------------------------------------
BOOL CStage::NewStage()
{
        if(10 < Env.stage)
        {
                return FALSE;
        }
        Env.restart = TRUE;
        StageLoad();
        return TRUE;
}

void CStage::StageLoad()
{
        Env.step = 0; // STEP 초기화
        Env.pause = FALSE; // PAUSE 초기화
        Env.restart = FALSE; // RESTART 초기화
        Env.quit = FALSE; // QUIT 초기화
        Env.tmSec = 0; // 경과시간 초기화
        GoalInfo.numOfGoal = 0; // GOAL 개수 초기화
        int i, j;
        char fLine[WIDTH+1] = { '0' }; // stageN.txt 파일의 한 라인을 입력받기 위한 임시 변수.
        char str[15] = { '0' }; // sprintf 를 위한 임시 변수.

        sprintf(str, "stage%d.txt", Env.stage); // buff[] 에 stageN.txt 를 입력한다.
        FILE *fp = fopen(str, "r"); // 파일을 연다.

        if(fp == NULL) // 만약 파일 열기가 실패하면 종료한다.
        {
                cout << "! File open ERROR." << endl;;
                exit(1);
        }

        for(i = 0; i < HEIGHT; ++i)
        {
                fscanf(fp, "%s", fLine); // 파일에서 한 라인을 스트링으로 읽어들인다.
                for(j = 0; j < WIDTH; ++j)
                {
                        g_TableOfMap[i][j] = static_cast<int>(fLine[j]) - 48; // 스트링을 정수 토큰으로 분리한다.
                        if(g_TableOfMap[i][j] == PLAYER)
                        {       /* 로딩한 스테이지 토큰이 플레이어라면 현재 좌표를 플레이어의 초기 좌표로 입력한다. */
                                Env.currentPlayerPos.X = j;
                                Env.currentPlayerPos.Y = i;
                        }
                        else if(g_TableOfMap[i][j] == GOAL)
                        {       /* 로딩한 스테이지 토큰이 GOAL 이라면 arrLocateOfGoal 배열에 좌표를 저장한다. */
                                GoalInfo.arrLocateOfGoal[GoalInfo.numOfGoal].X = j;
                                GoalInfo.arrLocateOfGoal[GoalInfo.numOfGoal++].Y = i;
                        }
                }
        }

        fclose(fp); // 파일을 닫는다.

        SetEvent(hEvent);
}

BOOL CStage::CheckStageClear()
{
        // 클리어 조건을 만족하는지 확인하고 아니면 FALSE 를 리턴한다.
        for(int i = 0; i < GoalInfo.numOfGoal; ++i)
        {
                if(g_TableOfMap[GoalInfo.arrLocateOfGoal[i].Y][GoalInfo.arrLocateOfGoal[i].X] != BOX)
                        return FALSE;
        }

        return TRUE;
}

//---------------------------------------------
//              CMoveMap.cpp
//---------------------------------------------
BOOL CMoveMap::Move(e_MoveDirection t)
{
        int mx = 0;
        int my = 0;
        BOOL bWithBox = FALSE;

        // 이동 할 방향으로의 X, Y 증감값을 입력한다.
        switch(t)
        {
        case UP:
                mx = 0;
                my = -1;
                break;
        case DOWN:
                mx = 0;
                my = 1;
                break;
        case LEFT:
                mx = -1;
                my = 0;
                break;
        case RIGHT:
                mx = 1;
                my = 0;
                break;
        default:
                cout << "Wrong Move Type." << endl;
                return FALSE;
        }

        // 이동 할 방향이 박스일 경우 박스 다음 타일이 빈칸인지 판정한다.
        if(g_TableOfMap[Env.currentPlayerPos.Y + my][Env.currentPlayerPos.X + mx] == BOX)
        {
                if(isEmpty(Env.currentPlayerPos.X + (mx * 2), Env.currentPlayerPos.Y + (my * 2)))
                {
                        // 박스 다음이 빈칸이면 현재 박스를 지우고 그 앞에 그린다.
                        RemoveObject(Env.currentPlayerPos.X + mx, Env.currentPlayerPos.Y + my);
                        MarkObject(Env.currentPlayerPos.X + (mx * 2), Env.currentPlayerPos.Y + (my * 2), BOX);
                        bWithBox = TRUE;
                }
                else
                {
                        return FALSE;
                }
        }

        // 이동 할 방향이 박스가 아닐 경우 빈칸인지 판정한다.
        else
        {
                if(!isEmpty(Env.currentPlayerPos.X + mx, Env.currentPlayerPos.Y + my))
                {
                        // 빈칸이 아니면 아무것도 하지 않고 리턴한다.
                        return FALSE;
                }
        }

        // 현재 플레이어를 지우고 위치를 이동시켜 그린다.
        RemoveObject(Env.currentPlayerPos.X, Env.currentPlayerPos.Y);
        Env.currentPlayerPos.X += mx;
        Env.currentPlayerPos.Y += my;
        ++(Env.step);
        MarkObject(Env.currentPlayerPos.X, Env.currentPlayerPos.Y, PLAYER);
        MoveRec.SavePath(mx, my, bWithBox);
        return TRUE;
}

void CMoveMap::Undo()
{
        MoveRec.Undo(this);
}

BOOL CMoveMap::isEmpty(int x, int y)
{
        if(g_TableOfMap[y][x] == EMPTY || g_TableOfMap[y][x] == GOAL)
                return TRUE; // 빈칸 혹은 GOAL 이면 TRUE 를 리턴
        else
                return FALSE; // 빈칸이 아니면 FALSE 를 리턴
}

void CMoveMap::RemoveObject(int x, int y)
{
        // 맵핑 테이블 특정 위치를 빈칸으로 만든다.
        g_TableOfMap[y][x] = EMPTY;
        for(int i = 0; i < GoalInfo.numOfGoal; ++i)
        {
                // 빈칸이 GOAL 이면 GOAL 을 입력한다.
                if(GoalInfo.arrLocateOfGoal[i].X == x && GoalInfo.arrLocateOfGoal[i].Y == y)
                {
                        g_TableOfMap[y][x] = GOAL;
                        break;
                }
        }
}

void CMoveMap::MarkObject(int x, int y, e_Object t)
{
        // 맵핑 테이블에 전달받은 타입의 오브젝트를 입력한다.
        switch(t)
        {
        case EMPTY:
                g_TableOfMap[y][x] = EMPTY;
                break;
        case BOX:
                g_TableOfMap[y][x] = BOX;
                break;
        case PLAYER:
                g_TableOfMap[y][x] = PLAYER;
                break;
        case WALL:
                g_TableOfMap[y][x] = WALL;
                break;
        case GOAL:
                g_TableOfMap[y][x] = GOAL;
                break;
        default:
                break;
        }
}


//---------------------------------------------
//              CMoveRec.cpp
//---------------------------------------------
CMoveRec::CMoveRec()
{
        // 이동 정보를 초기화 한다.
        MoveIdx = 0;
        for(int i = 0; i < SZ_HISTORY; ++i)
        {
                MoveInfo[i].pos.X = 0;
                MoveInfo[i].pos.Y = 0;
                MoveInfo[i].bWithBox = FALSE;
        }
}

void CMoveRec::SavePath(int x, int y, BOOL bStatus)
{
        MoveInfo[MoveIdx].pos.X = x; // 이동한 X 값.
        MoveInfo[MoveIdx].pos.Y = y; // 이동한 Y 값.
        MoveInfo[MoveIdx].bWithBox = bStatus; // 박스를 밀어서 이동했는지 여부.
        ++MoveIdx; // 인덱스 증가.
}

void CMoveRec::Undo(LPVOID lpParam)
{
        CMoveMap *pThis = (CMoveMap*)lpParam; // CPushPush 의 this 포인터를 받는다.

        if(MoveIdx == 0)
        {
                // 기억된 이동 정보가 없으면 아무것도 하지 않고 리턴한다.
                return ;
        }

        // 박스와 같이 이동했으면 현재 박스를 지우고 이전 위치에 그린다.
        if(MoveInfo[--MoveIdx].bWithBox == TRUE)
        {
                pThis->RemoveObject(Env.currentPlayerPos.X + MoveInfo[MoveIdx].pos.X, Env.currentPlayerPos.Y + MoveInfo[MoveIdx].pos.Y);
                pThis->MarkObject(Env.currentPlayerPos.X, Env.currentPlayerPos.Y, BOX);
        }
        // 플레이어만 이동했으면 플레이어를 지운다. 
        else
        {
                pThis->RemoveObject(Env.currentPlayerPos.X, Env.currentPlayerPos.Y);
        }

        Env.currentPlayerPos.X -= MoveInfo[MoveIdx].pos.X; // 플레이어의 위치를 이동시킨다.
        Env.currentPlayerPos.Y -= MoveInfo[MoveIdx].pos.Y; // 플레이어의 위치를 이동시킨다.
        ++(Env.step); // 스텝 증가. 
        pThis->MarkObject(Env.currentPlayerPos.X, Env.currentPlayerPos.Y, PLAYER);
}



//---------------------------------------------
//              ThreadProc.cpp
//---------------------------------------------
DWORD WINAPI ThreadProc(LPVOID lpParam)
{
        CPushPush *pThis = (CPushPush*)lpParam; // CPushPush 의 this 포인터를 받는다.
        int onTime = 0;
        int pauseStartTm = 0;
        int pauseOnTm = 0;
        int pauseTm = 0;


        WaitForSingleObject(hEvent, INFINITE); // 게임이 시작될 때 까지 기다린다.

        /*      경과시간을 초 단위로 저장하며,
                1초에 한 번씩 DrawScoreTable()을 호출한다.
        */
        Env.tmStart = GetTickCount(); // 시작 시간을 저장한다.
        while(1)
        {
                // 만약 현재 상태가 PAUSE 라면 PAUSE 시간을 저장하고 동작을 멈춘다.
                if(Env.pause)
                {
                        pauseStartTm = GetTickCount(); // PAUSE 가 시작한 시간을 저장한다.
                        while(Env.pause) // PAUSE 가 해제될 때까지 루프한다.
                        {
                                if(Env.restart)
                                {
                                        // 다시 시작할 때까지 기다린다.
                                        ResetEvent(hEvent);

                                        // 시간설정을 초기화하고 다시 시작한다.
                                        onTime = 0;
                                        pauseStartTm = 0;
                                        pauseOnTm = 0;
                                        pauseTm = 0;

                                        Env.tmStart = GetTickCount();
                                        Env.restart = FALSE;
                                        break;
                                }
                                if(Env.quit)
                                {
                                        // 이벤트를 보낸다.
                                        SetEvent(hThread);
                                        // 스레드를 종료한다.
                                        ExitThread(0);
                                }
                                pauseOnTm = GetTickCount() - pauseStartTm; // PAUSE 의 경과시간을 구한다.
                        }
                        pauseTm += pauseOnTm; // 현재 멈춘 시간을 총 멈춘 시간에 더한다.
                }

                // 1초 단위로 경과 시간을 올린다.
                Env.tmSec = static_cast<int>((GetTickCount() - pauseTm - Env.tmStart) / 1000.0);
                if(Env.tmSec > onTime){ // 시간에 변화가 생기면
                        pThis->DrawScreen.DrawScoreTable(); // 스코어 테이블을 다시 그린다.
                        ++onTime; // 다음 1초를 설정한다.
                }
        }

        return 0;
}

//---------------------------------------------
//              main.cpp
//---------------------------------------------
int main()
{
        CPushPush PushPush;

        PushPush.InitializeGame();
        PushPush.StartMenu();

        return 0;
}

void gotoxy(int x, int y)
{
        COORD Pos = {x-1, y-1};
        SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), Pos);
}