게임 제작

(C언어) 테트리스 만들기 2단계 - 블록 이동, 회전 및 키보드 입력받기

komizke 2023. 2. 21. 15:00

 

이번 페이지에서는 블록 이동, 회전과 키보드 입력을 받는 함수까지 소개해드리겠습니다.

1. DeleteBlock(char block [4][4])

콘솔창에 그려진 블록을 지워주는 함수로 블록 이동을 구현하는데 중요한 함수 중 하나입니다.

void DeleteBlock(char block[4][4]) {
    COORD pos = GetCurrentCursorPos();
    for (int y = 0; y < 4; y++) {
        for (int x = 0; x < 4; x++) {
            SetCurrentCursorPos(pos.X + x * 2, pos.Y + y);	//커서 위치 설정
            if (block[y][x] == 1) {
                printf("  ");	//블록이 있는 곳에 "  "출력으로 지워준다.
            }
        }
    }
    SetCurrentCursorPos(pos.X, pos.Y);	//초기 커서 위치로 돌아가기 
}

 

<예시 코드 - 출력된 블록 지우기>

#include <stdio.h>
#include <Windows.h>

int block_id = 0;
int pos_x = 10, pos_y = 5;
char blockModel[][4][4];	//길이상 생략
void SetCurrentCursorPos(int x, int y) {
    COORD pos = { x,y };
    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);
}
void RemoveCursor(void) {
    CONSOLE_CURSOR_INFO curInfo;
    GetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &curInfo);
    curInfo.bVisible = 0;
    SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &curInfo);
}
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 ShowBlock(char block[4][4]) {
    int y, x;
    COORD pos = GetCurrentCursorPos();
    for (y = 0; y < 4; y++) {
        for (x = 0; x < 4; x++) {
            SetCurrentCursorPos(pos.X + x * 2, pos.Y + y);
            if (block[y][x] == 1) {
                printf("■");
            }
        }
    }
    SetCurrentCursorPos(pos.X, pos.Y);
}
void DeleteBlock(char block[4][4]) {
    COORD pos = GetCurrentCursorPos();
    for (int y = 0; y < 4; y++) {
        for (int x = 0; x < 4; x++) {
            SetCurrentCursorPos(pos.X + x * 2, pos.Y + y);
            if (block[y][x] == 1) {
                printf("  ");
            }
        }
    }
    SetCurrentCursorPos(pos.X, pos.Y);
}
int main() {
    RemoveCursor();
    SetCurrentCursorPos(pos_x, pos_y);	//커서 위치 설정
    ShowBlock(blockModel[block_id]);	//블록 보여주기
    Sleep(1000);	//시각적으로 확인 가능하기 위해 1초 동안 sleep
    DeleteBlock(blockModel[block_id]);	//1초후에 블록 사라짐
    getchar();
    return 0;
}

<실행 영상>

 

 

2. ShiftDown(), shiftLeft(), ShiftRight()

블록을 이동시켜 주는 원리는 간단합니다.

 

바로 현재 그려진 블록을 지워주고 커서의 위치를 원하는 위치로 옮겨 다시 블록을 출력해 주면 됩니다.

 

사용자 입장에서는 블록이 이동하는 거처럼 보이게 되는 거죠.

 

-ShiftDown()-

void ShiftDown() {
    DeleteBlock(blockModel[block_id]);
    pos_y += 1;
    SetCurrentCursorPos(pos_x, pos_y);
    ShowBlock(blockModel[block_id]);
}

-ShiftLeft()-

void ShiftLeft() {
    DeleteBlock(blockModel[block_id]);
    pos_x -= 2;
    SetCurrentCursorPos(pos_x, pos_y);
    ShowBlock(blockModel[block_id]);
}

-ShiftRight()-

void ShiftRight() {
    DeleteBlock(blockModel[block_id]);
    pos_x += 2;
    SetCurrentCursorPos(pos_x, pos_y);
    ShowBlock(blockModel[block_id]);
}

테트리스에서는 블록이 주로 왼쪽, 오른쪽, 아래로만 이동하므로 위의 세 가지 함수를 구현하였습니다.

 

 

<예시 코드 - 블록 오른쪽으로 두 번, 왼쪽으로 두 번, 아래로 두 번 이동시키기>

#include <stdio.h>
#include <Windows.h>

int block_id = 0;
int pos_x = 10, pos_y = 5;
char blockModel[][4][4]; //길이상 생략
void SetCurrentCursorPos(int x, int y) {
    COORD pos = { x,y };
    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);
}
void RemoveCursor(void) {
    CONSOLE_CURSOR_INFO curInfo;
    GetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &curInfo);
    curInfo.bVisible = 0;
    SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &curInfo);
}
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 ShowBlock(char block[4][4]) {
    int y, x;
    COORD pos = GetCurrentCursorPos();
    for (y = 0; y < 4; y++) {
        for (x = 0; x < 4; x++) {
            SetCurrentCursorPos(pos.X + x * 2, pos.Y + y);
            if (block[y][x] == 1) {
                printf("■");
            }
        }
    }
    SetCurrentCursorPos(pos.X, pos.Y);
}
void DeleteBlock(char block[4][4]) {
    COORD pos = GetCurrentCursorPos();
    for (int y = 0; y < 4; y++) {
        for (int x = 0; x < 4; x++) {
            SetCurrentCursorPos(pos.X + x * 2, pos.Y + y);
            if (block[y][x] == 1) {
                printf("  ");
            }
        }
    }
    SetCurrentCursorPos(pos.X, pos.Y);
}
void ShiftDown() {
    DeleteBlock(blockModel[block_id]);
    pos_y += 1;
    SetCurrentCursorPos(pos_x, pos_y);
    ShowBlock(blockModel[block_id]);
}
void ShiftLeft() {
    DeleteBlock(blockModel[block_id]);
    pos_x -= 2;
    SetCurrentCursorPos(pos_x, pos_y);
    ShowBlock(blockModel[block_id]);
}
void ShiftRight() {
    DeleteBlock(blockModel[block_id]);
    pos_x += 2;
    SetCurrentCursorPos(pos_x, pos_y);
    ShowBlock(blockModel[block_id]);
}
int main() {
    RemoveCursor();
    SetCurrentCursorPos(pos_x, pos_y);	//커서 위치 설정
    ShowBlock(blockModel[block_id]);	//블록 보여주기
    for (int i = 0; i < 6; i++) {
        Sleep(200);	//블록 이동을 눈으로 확인가능하기 위해 사용
        if (i < 2) ShiftRight();
        else if (i < 4) ShiftLeft();
        else ShiftDown();
    }
    getchar();
    return 0;
}

<실행 영상>

 

 

3. RotateBlock()

테트리스에서는 일반적으로 블록을 회전시키면서 플레이를 하는데요.

 

블록을 회전시키는 함수를 소개하겠습니다.

void RotateBlock() {
    int temp = block_id / 4;    //블럭 종류 저장
    int tempid = block_id + 1;  //다음으로 보여줄 블록 아이디
    if (temp + 1 == tempid / 4) { //다른 종류의 블록의 경우 인덱스 초기화 
        tempid -= 4;
    }
    DeleteBlock(blockModel[block_id]);	//현재 블록 지우기
    block_id = tempid;
    ShowBlock(blockModel[block_id]);	//다음 블록 그리기
}

 

<예시 코드 - blockModel에 있는 블록 중 하나에 대해 회전>

#include <stdio.h>
#include <Windows.h>
int block_id = 8;
int pos_x = 10, pos_y = 5;
char blockModel[][4][4];
void SetCurrentCursorPos(int x, int y) {
    COORD pos = { x,y };
    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);
}
void RemoveCursor(void) {
    CONSOLE_CURSOR_INFO curInfo;
    GetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &curInfo);
    curInfo.bVisible = 0;
    SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &curInfo);
}
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 ShowBlock(char block[4][4]) {
    int y, x;
    COORD pos = GetCurrentCursorPos();
    for (y = 0; y < 4; y++) {
        for (x = 0; x < 4; x++) {
            SetCurrentCursorPos(pos.X + x * 2, pos.Y + y);
            if (block[y][x] == 1) {
                printf("■");
            }
        }
    }
    SetCurrentCursorPos(pos.X, pos.Y);
}
void DeleteBlock(char block[4][4]) {
    COORD pos = GetCurrentCursorPos();
    for (int y = 0; y < 4; y++) {
        for (int x = 0; x < 4; x++) {
            SetCurrentCursorPos(pos.X + x * 2, pos.Y + y);
            if (block[y][x] == 1) {
                printf("  ");
            }
        }
    }
    SetCurrentCursorPos(pos.X, pos.Y);
}
void ShiftDown() {
    DeleteBlock(blockModel[block_id]);
    pos_y += 1;
    SetCurrentCursorPos(pos_x, pos_y);
    ShowBlock(blockModel[block_id]);
}
void ShiftLeft() {
    DeleteBlock(blockModel[block_id]);
    pos_x -= 2;
    SetCurrentCursorPos(pos_x, pos_y);
    ShowBlock(blockModel[block_id]);
}
void ShiftRight() {
    DeleteBlock(blockModel[block_id]);
    pos_x += 2;
    SetCurrentCursorPos(pos_x, pos_y);
    ShowBlock(blockModel[block_id]);
}
void RotateBlock() {
    int temp = block_id / 4;    
    int tempid = block_id + 1;  
    if (temp + 1 == tempid / 4) {  
        tempid -= 4;
    }
    DeleteBlock(blockModel[block_id]);
    block_id = tempid;
    ShowBlock(blockModel[block_id]);
}
int main() {
    RemoveCursor();
    SetCurrentCursorPos(pos_x, pos_y);
    ShowBlock(blockModel[block_id]);
    while (TRUE) {
        Sleep(200);
        RotateBlock();
    }
    getchar();
    return 0;
}

<실행 영상>

 

 

4. ProcessKeyInput()

사용자가 키보드 입력을 할 경우 키보드 입력을 받는 함수입니다.

#include <conio.h>

int speed = 20;
void ProcessKeyInput() {
    int key;
    for (int i = 0; i < 20; i++) {
        if (_kbhit() != 0) {
            key = _getch();
            switch (key) {
            	//내용
            }
        }
        Sleep(speed);
    }
}

먼저 위 코드에서 처음 보이는 함수인 _kbhit(), _getch() 함수가 보입니다.

 

이 함수를 사용하기 전에 <conio.h>를 include 해줍니다.

 

_kbhit()키보드의 입력 여부를 판단하며 입력받을 경우에 1을 반환합니다.

 

_getch()입력받은 키값을 받아줍니다. 

 

위 함수는 좌, 우, 위 방향키 입력에 따라 블록을 조종할 수 있도록 정의하였습니다.

 

 

<예시 코드 - 키보드 방향키로 블록 조종하기>

#include <stdio.h>
#include <Windows.h>
#include <conio.h>

#define LEFT 75
#define RIGHT 77
#define UP 72

int speed = 20;
int block_id = 0;
int pos_x = 10, pos_y = 5;
char blockModel[][4][4];
void SetCurrentCursorPos(int x, int y) {
    COORD pos = { x,y };
    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);
}
void RemoveCursor(void) {
    CONSOLE_CURSOR_INFO curInfo;
    GetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &curInfo);
    curInfo.bVisible = 0;
    SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &curInfo);
}
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 ShowBlock(char block[4][4]) {
    int y, x;
    COORD pos = GetCurrentCursorPos();
    for (y = 0; y < 4; y++) {
        for (x = 0; x < 4; x++) {
            SetCurrentCursorPos(pos.X + x * 2, pos.Y + y);
            if (block[y][x] == 1) {
                printf("■");
            }
        }
    }
    SetCurrentCursorPos(pos.X, pos.Y);
}
void DeleteBlock(char block[4][4]) {
    COORD pos = GetCurrentCursorPos();
    for (int y = 0; y < 4; y++) {
        for (int x = 0; x < 4; x++) {
            SetCurrentCursorPos(pos.X + x * 2, pos.Y + y);
            if (block[y][x] == 1) {
                printf("  ");
            }
        }
    }
    SetCurrentCursorPos(pos.X, pos.Y);
}
void ShiftDown() {
    DeleteBlock(blockModel[block_id]);
    pos_y += 1;
    SetCurrentCursorPos(pos_x, pos_y);
    ShowBlock(blockModel[block_id]);
}
void ShiftLeft() {
    DeleteBlock(blockModel[block_id]);
    pos_x -= 2;
    SetCurrentCursorPos(pos_x, pos_y);
    ShowBlock(blockModel[block_id]);
}
void ShiftRight() {
    DeleteBlock(blockModel[block_id]);
    pos_x += 2;
    SetCurrentCursorPos(pos_x, pos_y);
    ShowBlock(blockModel[block_id]);
}
void RotateBlock() {
    int temp = block_id / 4;    //블럭 종류 저장
    int tempid = block_id + 1;  //다음으로 보여줄 블록 아이디
    if (temp + 1 == tempid / 4) { //다른 종류의 블록의 경우 인덱스 초기화 
        tempid -= 4;
    }
    DeleteBlock(blockModel[block_id]);
    block_id = tempid;
    ShowBlock(blockModel[block_id]);
}
void ProcessKeyInput() {
    int key;
    for (int i = 0; i < 20; i++) {
        if (_kbhit() != 0) {
            key = _getch();
            switch (key) {
            case LEFT:	//왼쪽 방향키
                ShiftLeft();	
                break;
            case RIGHT:	//오른쪽 방향키
                ShiftRight();
                break;
            case UP:	//위쪽 방향키
                RotateBlock();
                break;
            }
        }
        Sleep(speed);
    }
}
int main() {
    RemoveCursor();
    SetCurrentCursorPos(pos_x, pos_y);
    ShowBlock(blockModel[block_id]);
    while (TRUE) {
        ProcessKeyInput();
    }
    getchar();
    return 0;
}

<실행 영상>

 

 

 

 

지금까지 블록의 이동 및 회전, 키보드의 값을 입력받는 함수를 구현하였습니다.

 

테트리스 게임의 기반은 어느 정도 갖춰진 느낌인데요.

 

다음 장에서는 테트리스 게임 디자인과 블록끼리 충돌할 때 어떻게 처리할지 살펴보겠습니다.

 

감사합니다 :)