(C언어) 테트리스 만들기 2단계 - 블록 이동, 회전 및 키보드 입력받기
이번 페이지에서는 블록 이동, 회전과 키보드 입력을 받는 함수까지 소개해드리겠습니다.
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;
}
<실행 영상>
지금까지 블록의 이동 및 회전, 키보드의 값을 입력받는 함수를 구현하였습니다.
테트리스 게임의 기반은 어느 정도 갖춰진 느낌인데요.
다음 장에서는 테트리스 게임 디자인과 블록끼리 충돌할 때 어떻게 처리할지 살펴보겠습니다.
감사합니다 :)