본문 바로가기
코스웨어/16년 스마트컨트롤러

2016-10-04_조재찬_ 프로젝트 일지_테트리스 게임

by 알 수 없는 사용자 2016. 10. 4.
728x90
반응형

<테트리스 게임> 프로젝트

- 블록을 만들고 움직이기


작업 환경 : 윈도우 10, Visual Studio, C++ (Win32 Console)

참고 서적 : C프로그래밍 파워 업그레이드 (윤성우 저)



- 커서 위치 이동

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <Windows.h>
using namespace std;
int main(void)
{
    COORD pos1 = { 02 };    // x,y 좌표
    COORD pos2 = { 66 };
    COORD pos3 = { 154 };
    HANDLE hConsoleOut = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleCursorPosition(hConsoleOut, pos1);
    cout << "첫 번째 인사 : 안녕하세요.";
    getchar();
    SetConsoleCursorPosition(hConsoleOut, pos2);
    cout << "두 번째 인사 : 안녕하세요.";
    getchar();
    SetConsoleCursorPosition(hConsoleOut, pos3);
    cout << "세 번째 인사 : 안녕하세요.";
    getchar();
    return 0;
}
cs



2행 : include는 ms가 제공해주는 구조체나 함수를 사용하기 위함


6-8행 : COORD는 구조체로서 다음과 같이 선언 및 정의되어 있음


1
2
3
4
5
typedef struct _COORD
{
    int x;
    int y;
} COORD;
cs

COORD 구조체 : 콘솔창의 x, y 좌표 정보를 담을 수 있도록 정의된 구조체


10행 : GetStdHandle이란 함수를 호출하면서 인자로 STD_OUTPUT_HANDLE를 전달하면 콘솔 출력 창을 조절 가능하다.

        HANDLE은 MS가 정의한 자료형으로 이 자료형의 변수는 hConsoleOut이 된다. 여기에 GetStdHandle 함수의 반환값을 저장하는 것이다.


12행 : SetConsoleCursorPosition 함수는 콘솔의 커서 위치를 변경하는 기능의 함수

이 함수를 호출하면서 첫번째 인자로 hConsoleOut, 두 번째 인자로 커서 좌표 경로를 전달하고 있다.

커서 위치는 좌상단 x,y좌표가 0,0 이며 x값이 증가할 수록 오른쪽으로, y값이 증가할수록 아래쪽으로 이동한다.



현재 커서 위치 정보 얻어오기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <windows.h>
 
using namespace std;
 
int main(void)
{
    CONSOLE_SCREEN_BUFFER_INFO curInfo;
 
    GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &curInfo);    
    cout << "[" << curInfo.dwCursorPosition.X << ", " << curInfo.dwCursorPosition.Y << "]" << endl;
    cout << "First Hello world" << endl;
 
    GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &curInfo);
    cout << "[" << curInfo.dwCursorPosition.X << ", " << curInfo.dwCursorPosition.Y << "]" << endl;
    cout << "Second Hello world" << endl;
 
    GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &curInfo);
    cout << "[" << curInfo.dwCursorPosition.X << ", " << curInfo.dwCursorPosition.Y << "]" << endl;
 
    return 0;
}
cs


8행 : CONSOLE_SCREEN_BUFFER_INFO는 MS에서 콘솔출력창의 정보를 담기 위해 정의한 구조체


10행 : GetConsoleScreenBufferInfo 함수는 콘솔 출력창의 정보를 얻기위해 호출하는 함수

첫번째 인자는 위의 예제에서 호출한 SetConsoleCursorPosition 함수의 첫번째 인자와 동일하다.

두번째 인자에는 8행에 선언된 구조체 변수의 주소 값이 전달된다.


11,15,19행 : curInfo에 저장된 여러 정보중 현재 커서의 x,y 좌표값을 보여주고 있다.



- 벽돌을 그리는 원리


블럭 모델은 다음과 같이 배열에 정의하며, 블럭이 있는 공간은 1, 없는 공간은 0

원 위치와 회전 모델까지 포함하여 총 4개를 정의한다.


 □ □ □ 

 ■ □ □ 

 ■ ■ ■ 

 □ □ □ 


배열을 조회하여 index의 값이 1이면 ■ 출력 (특문은 2칸을 잡아먹는다)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include <iostream>
#include <Windows.h>
using namespace std;
 
void ShowBlock(char blockInfo[][4]);        // ShowBlock 함수는 인자로 전달되는 정보를 토대로 콘솔 출력 창에 블록을 그림
void SetCurrentCursorPos(int x, int y);        // 이 함수는 x,y 좌표를 입력받아 커서 위치를 이동시키는 기능
COORD GetCurrentCursorPos(void);            // 이 함수는 현재 커서 위치 정보를 반환, 반환은 구조체 COORD를 기반으로 이뤄짐
char blockModel[][4][4=
{
    /* ■
       ■ ■ ■ */
    {                        // 원 위치
        { 0000 },
        { 1000 },
        { 1110 },
        { 0000 }    },
    {                        // 회전
        {0100},
        {0100},
        {1100},
        {0000}     },
    {                        // 회전
        { 0000 },
        { 1110 },
        { 0010 },
        { 0000 }     },
    {                        // 회전
        { 0000 },
        { 1100 },
        { 1000 },
        { 1000 }     }
};
int main(void)
{
    SetCurrentCursorPos(00);    // 커서 위치를 [0, 0] 으로 이동
    ShowBlock(blockModel[0]);    // 커서 위치를 기준으로 블록을 그림
    SetCurrentCursorPos(08);
    ShowBlock(blockModel[1]);
    SetCurrentCursorPos(120);
    ShowBlock(blockModel[2]);
    SetCurrentCursorPos(128);
    ShowBlock(blockModel[3]);
    getchar();
    return 0;
}
void ShowBlock(char blockInfo[][4])
{
    int x, y;
    COORD curPos = GetCurrentCursorPos();
    for (y = 0; y < 4; y++)        // 중첩된 for문은 인자로 전달된 배열 전체 정보를 참조하기 위함이다.
    {
        for (x = 0; x < 4; x++)
        {
            SetCurrentCursorPos(curPos.X + (x * 2), curPos.Y + y);    // 블록을 그리기 위해 커서위치 이동. ■는 특문이기 때문에 x좌표에 *2
            if (blockInfo[y][x] == 1)
                cout << "■" ;
        }
    }
    SetCurrentCursorPos(curPos.X, curPos.Y);    // 커서 위치 원래대로
}
void SetCurrentCursorPos(int x, int y)
{
    COORD pos = {x, y};
    HANDLE hConsoleOut = GetStdHandle(STD_OUTPUT_HANDLE);    // 콘솔 출력창의 제어
    SetConsoleCursorPosition(hConsoleOut, pos);
}
COORD GetCurrentCursorPos(void)        // 커서 위치 정보를 반환하는 함수
{
    COORD curPoint;
    CONSOLE_SCREEN_BUFFER_INFO curInfo;
    GetConsoleScreenBufferInfo(                        // 콘솔 출력창의 정보를 얻음
        GetStdHandle(STD_OUTPUT_HANDLE), &curInfo);
    curPoint.X = curInfo.dwCursorPosition.X;
    curPoint.Y = curInfo.dwCursorPosition.Y;
    return curPoint;
}
cs


61행 SetCurrentCursorPos 함수에서 63-65행 코드는 운영체제에 의존적이다. 또한 기능은 가능하면 함수로 정의하고 호출하는 것이 좋다.


Output :



- 커서 깜박임 제거

위의 출력 결과를 보면 커서가 깜박이는 것을 볼 수 있다.

그래서 커서의 깜빡임을 제거하는 함수가 필요하다. (깜박임만 제거하기에 커서 위치에서의 출력은 여전히 가능)


1
2
3
4
5
6
7
8
void RemoveCursor(void)
{
    CONSOLE_CURSOR_INFO curInfo;    
    HANDLE hConsoleOut = GetStdHandle(STD_OUTPUT_HANDLE);
    GetConsoleCursorInfo(hConsoleOut, &curInfo);
    curInfo.bVisible = FALSE;
    SetConsoleCursorInfo(hConsoleOut, &curInfo);
}
cs

위 함수를 정의하고 main함수에서 호출 // RemoveCursor();


3행 : CONSOLE_CURSOR_INFO는 콘솔의 커서 정보를 얻기위해 사용되는 구조체


5행 : GetConsoleCursorInfo 함수는 콘솔출력창의 정보를 반환하는 함수. 

3행에서 선언한 변수의 주소가 두 번째 인자로 전달되고, curInfo변수에 커서 정보가 채워진다.


6행 : CONSOLE_CURSOR_INFO구조체의 bVisible 멤버는 표준 출력 커서 표시 유무를 담당. 숨기려면 FALSE


7행 : 함수 호출을 통해 변경된 특성을 저장



 [ 테트리스의 28가지 블록 모델 ]


이 모델들은 blockInfo.h에 배열로 정의해 include하기로 한다.


총 28가지 모델이 되므로(회전시 모양이 변하지않는 것도 포함)

결과적으로 char blockModel[7*4][4][4]이 된다.



블록의 이동을 위한 Sleep 함수

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <Windows.h>
 
int main(void)
{
    RemoveCursor();
    puts("One");
    Sleep(1000);
 
    puts("Two");
    Sleep(2000);
 
    puts("Three");
    Sleep(3000);
 
    puts("Four");
    Sleep(3000);
 
    return 0;
}
cs

Sleep 함수는 프로그램의 실행을 잠시 멈추게 한다.
0.001 X N초 이므로 1000은 1초가 된다.


블록을 랜덤 생성후 아래로 이동

Sleep 함수를 이용해 블럭을 내려가도록 구현해보았다.

블럭을 지우기 위한 함수인 DeleteBlock을 따로 정의하여 main함수에서 호출
DeleteBlock함수는 ShowBlock 함수의 원리와 동일하다. 다만 특문  출력이 아닌 빈칸 2칸을 출력하여 지워지도록 보이게 하는 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#include <iostream>
#include <Windows.h>
#include <time.h>
#include "blockInfo.h"    // 블럭 정보 (4x7가지 블럭 모델)
 
using namespace std;
void ShowBlock(char blockInfo[][4]);        // ShowBlock 함수는 인자로 전달되는 정보를 토대로 콘솔 출력 창에 블록을 그림
void DeleteBlock(char blockInfo[][4]);        // 블럭을 지우기 위한 함수
void BlockDown(void);
void RandomBlock(void);
 
void SetCurrentCursorPos(int x, int y);        // 이 함수는 x,y 좌표를 입력받아 커서 위치를 이동시키는 기능
COORD GetCurrentCursorPos(void);           // 이 함수는 현재 커서 위치 정보를 반환, 반환은 구조체 COORD를 기반으로 이뤄짐
void RemoveCursor(void);
int GetCurBlockIdx(void);
 
int CurBlockModel;
int curPosX, curPosY;
 
void Init(int x, int y)
{
    if (x < 0 || y < 0)
        return;
 
    curPosX = x;
    curPosY = y;
}
 
int main(void)
{
    RemoveCursor();
    SetCurrentCursorPos(curPosX=10, curPosY=0);    // 커서 위치를 [ , ] 으로 초기화
    RandomBlock();
 
    while (TRUE)
    {
        BlockDown();
        Sleep(1000);
    }
    return 0;
}
 
 
void ShowBlock(char blockInfo[][4])
{
    int x, y;
    COORD curPos = GetCurrentCursorPos();
    for (y = 0; y < 4; y++)        // 중첩된 for문은 인자로 전달된 배열 전체 정보를 참조하기 위함이다.
    {
        for (x = 0; x < 4; x++)
        {
            SetCurrentCursorPos(curPos.X + (x * 2), curPos.Y + y);    // 블록을 그리기 위해 커서위치 이동. ■는 특문이기 때문에 x좌표에 *2
            if (blockInfo[y][x] == 1)
                cout << "■";
        }
    }
    SetCurrentCursorPos(curPos.X, curPos.Y);    // 커서 위치 원래대로
}
void DeleteBlock(char blockInfo[][4])
{
    int x, y;
    COORD curPos = GetCurrentCursorPos();
    for (y = 0; y < 4; y++)        // 중첩된 for문은 인자로 전달된 배열 전체 정보를 참조하기 위함이다.
    {
        for (x = 0; x < 4; x++)
        {
            SetCurrentCursorPos(curPos.X + (x * 2), curPos.Y + y);    // 블록을 그리기 위해 커서위치 이동. ■는 특문이기 때문에 x좌표에 *2
            if (blockInfo[y][x] == 1)
                cout << "  ";
        }
    }
    SetCurrentCursorPos(curPos.X, curPos.Y);    // 커서 위치 원래대로
}
 
void SetCurrentCursorPos(int x, int y)
{
    COORD pos = { x, y };
    HANDLE hConsoleOut = GetStdHandle(STD_OUTPUT_HANDLE);    // 콘솔 출력창의 제어
    SetConsoleCursorPosition(hConsoleOut, pos);
}
COORD GetCurrentCursorPos(void)        // 커서 위치 정보를 반환하는 함수
{
    COORD curPoint;
    CONSOLE_SCREEN_BUFFER_INFO curInfo;
 
    GetConsoleScreenBufferInfo(                        // 콘솔 출력창의 정보를 얻음
        GetStdHandle(STD_OUTPUT_HANDLE), &curInfo);
    curPoint.X = curInfo.dwCursorPosition.X;
    curPoint.Y = curInfo.dwCursorPosition.Y;
 
    return curPoint;
}
 
void RemoveCursor(void)
{
    CONSOLE_CURSOR_INFO curInfo;
    HANDLE hConsoleOut = GetStdHandle(STD_OUTPUT_HANDLE);
    GetConsoleCursorInfo(hConsoleOut, &curInfo);
    curInfo.bVisible = FALSE;
    SetConsoleCursorInfo(hConsoleOut, &curInfo);
}
 
void BlockDown(void)
{
    DeleteBlock(blockModel[GetCurBlockIdx()]);
    curPosY += 1;
 
    SetCurrentCursorPos(curPosX, curPosY);
    ShowBlock(blockModel[GetCurBlockIdx()]);
}
 
void RandomBlock(void)
{
    srand((unsigned int)time(NULL));
    CurBlockModel = (rand() % 7* 4;
}
 
int GetCurBlockIdx(void)
{
    return CurBlockModel;
}
cs

Output :


■■■


정의된 블록모델수 중 (위의 예시에서는 7x4) 랜덤함수에 의해 선택된 블록이 1초 간격으로 아래로 이동





블록(특수문자)의 한 칸이 콘솔 출력창 두 칸을 잡아먹는 것에  유의


112행의 RandomBlock 함수는 배열의 인덱스 값 중 하나를 무작위로 선택하기 위함이다.

이 함수는 0,4,8,12,16,20,24 중 하나의 숫자를 랜덤함수를 통해 생성하고 변수 CurBlockModel에 저장한다.

이렇게 저장된 값은 배열 blockModel에 저장된 기존 블럭의 index 값으로 사용된다.


118행 GetCurBlockIdx 함수가 변수 CurBlockModel에 저장된 값을 반환한다.






728x90