C언어기반의 Tetris 게임을 구현했습니다.
기초 명세부터 잡기보다는 https://blog.naver.com/azure0777/220295388741([출처] [C언어 게임 만들기] 테트리스 게임 (Tetris)|작성자 azure0777) 참고해 부분 수정했습니다.
JAVA로 공부하다 업무상 C언어를 주언어로 해 테스트겸 구현해 보았습니다.
포인터가 익숙하지 않아 구조체 반환형식으로 했는데 효율성 좋은 코드인지는 잘 모르겠네요,,
사진
코드
#include<stdio.h> //표준 입출력 함수 헤더
#include<stdlib.h> //표준 유틸리티 함수 헤더
#include<windows.h> //윈도우 응용프로그램 헤더
#include<time.h> //시간 관련 함수 헤더
#include<conio.h> //콘솔 입출력 함수 헤더
#define SIZE_X 11 //게임 창 가로 크기
#define SIZE_Y 23 //게임 창 세로 크기
#define LEFT 75 //방향키 왼쪽 아스키코드
#define RIGHT 77 //방향키 오른쪽 아스키코드
#define UP 72 //방향키 위쪽 아스키코드
#define DOWN 80 //방향키 아래쪽 아스키코드
#define ENTER 13 //Enter키 아스키코드
#define ESC 27 //ESC키 아스키코드
#define SPACE 32 //스페이스바 아스키코드
struct Position {
int x;
int y;
int v;
int type;
};
int combo = 0;
int score = 0;
BOOL space_key_on = FALSE; //space가 눌렸는지 체크 플래그
BOOL crush_on = FALSE; //부딪혔는지 체크 플래그
BOOL new_on = FALSE; //새로운 블록이 생성되는지 체크 플래그
BOOL store_on = FALSE; //저장된 블록이 있는지 체크 플래그
int board[SIZE_X][SIZE_Y];
int blocks[7][4][4][4] = { //7가지 모양, 4가지 방향, 2차원 배열
{ { { 0, 0, 0, 0 }, { 0, 1, 1, 0 }, { 0, 1, 1, 0 }, { 0, 0, 0, 0 } }, //0000
{ { 0, 0, 0, 0 }, { 0, 1, 1, 0 }, { 0, 1, 1, 0 }, { 0, 0, 0, 0 } }, //0110
{ { 0, 0, 0, 0 }, { 0, 1, 1, 0 }, { 0, 1, 1, 0 }, { 0, 0, 0, 0 } }, //0110
{ { 0, 0, 0, 0 }, { 0, 1, 1, 0 }, { 0, 1, 1, 0 }, { 0, 0, 0, 0 } } }, //0000
{ { { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 1, 1, 1, 1 }, { 0, 0, 0, 0 } }, //0000
{ { 0, 1, 0, 0 }, { 0, 1, 0, 0 }, { 0, 1, 0, 0 }, { 0, 1, 0, 0 } }, //0000
{ { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 1, 1, 1, 1 }, { 0, 0, 0, 0 } }, //1111
{ { 0, 1, 0, 0 }, { 0, 1, 0, 0 }, { 0, 1, 0, 0 }, { 0, 1, 0, 0 } } }, //0000
{ { { 0, 0, 0, 0 }, { 1, 1, 0, 0 }, { 0, 1, 1, 0 }, { 0, 0, 0, 0 } }, //0000
{ { 0, 0, 0, 0 }, { 0, 0, 1, 0 }, { 0, 1, 1, 0 }, { 0, 1, 0, 0 } }, //1100
{ { 0, 0, 0, 0 }, { 1, 1, 0, 0 }, { 0, 1, 1, 0 }, { 0, 0, 0, 0 } }, //0110
{ { 0, 0, 0, 0 }, { 0, 0, 1, 0 }, { 0, 1, 1, 0 }, { 0, 1, 0, 0 } } }, //0000
{ { { 0, 0, 0, 0 }, { 0, 1, 1, 0 }, { 1, 1, 0, 0 }, { 0, 0, 0, 0 } }, //0000
{ { 0, 0, 0, 0 }, { 1, 0, 0, 0 }, { 1, 1, 0, 0 }, { 0, 1, 0, 0 } }, //0110
{ { 0, 0, 0, 0 }, { 0, 1, 1, 0 }, { 1, 1, 0, 0 }, { 0, 0, 0, 0 } }, //1100
{ { 0, 0, 0, 0 }, { 1, 0, 0, 0 }, { 1, 1, 0, 0 }, { 0, 1, 0, 0 } } }, //0000
{ { { 0, 0, 0, 0 }, { 0, 0, 1, 0 }, { 1, 1, 1, 0 }, { 0, 0, 0, 0 } }, //0000
{ { 0, 0, 0, 0 }, { 1, 1, 0, 0 }, { 0, 1, 0, 0 }, { 0, 1, 0, 0 } }, //0010
{ { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 1, 1, 1, 0 }, { 1, 0, 0, 0 } }, //1110
{ { 0, 0, 0, 0 }, { 0, 1, 0, 0 }, { 0, 1, 0, 0 }, { 0, 1, 1, 0 } } }, //0000
{ { { 0, 0, 0, 0 }, { 1, 0, 0, 0 }, { 1, 1, 1, 0 }, { 0, 0, 0, 0 } }, //0000
{ { 0, 0, 0, 0 }, { 0, 1, 0, 0 }, { 0, 1, 0, 0 }, { 1, 1, 0, 0 } }, //1000
{ { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 1, 1, 1, 0 }, { 0, 0, 1, 0 } }, //1110
{ { 0, 0, 0, 0 }, { 0, 1, 1, 0 }, { 0, 1, 0, 0 }, { 0, 1, 0, 0 } } }, //0000
{ { { 0, 0, 0, 0 }, { 0, 1, 0, 0 }, { 1, 1, 1, 0 }, { 0, 0, 0, 0 } }, //0000
{ { 0, 0, 0, 0 }, { 0, 1, 0, 0 }, { 0, 1, 1, 0 }, { 0, 1, 0, 0 } }, //0100
{ { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 1, 1, 1, 0 }, { 0, 1, 0, 0 } }, //1110
{ { 0, 0, 0, 0 }, { 0, 1, 0, 0 }, { 1, 1, 0, 0 }, { 0, 1, 0, 0 } } } //0000
};
void gotoxy(int x, int y);
void setting_board();
void make_block(int type, int v);
void print_board();
BOOL check_crush(int x, int y, int v, int type);
struct Position move_block(int value, struct Position p);
void stamp_block(int value, int x, int y, int v, int type);
void check_line();
void check_finish();
int main() {
//커서 숨기기
CONSOLE_CURSOR_INFO t_cursor;
HANDLE t_consloe = GetStdHandle(STD_OUTPUT_HANDLE);
t_cursor.dwSize = 1;
t_cursor.bVisible = FALSE;
SetConsoleCursorInfo(t_consloe, &t_cursor);
//화면 초기화
system("cls");
//선언 및 초기화
srand(time(NULL));
struct Position p; //위치값 저장할 구조체
p.x = SIZE_X / 2 - 1; //블록의 x좌표
p.y = 0; //블록의 y좌표
p.v = 0; //블록 방향
p.type = rand() % 7; //블록 종류
int ntype = rand() % 7; //다음 블록 종류
int s_type = 0; //저장 블록 종류
int s_v = 0; //저장 블록 방향
int key = 0; //입력 받은 키
int speed = 200; //속도
score = 0; //점수
combo = 0; //콤보
for (int i = 0; i < SIZE_Y; i++) { //보드 초기화
for (int j = 0; j < SIZE_X; j++) {
board[i][j] = 0;
}
}
//테두리 설정(벽은 -1로 세팅)
for (int i = 1; i < SIZE_Y - 1; i++) {
board[i][0] = -1;
board[i][SIZE_X - 1] = -1;
}
for (int i = 0; i < SIZE_X; i++) {
board[SIZE_Y - 1][i] = -1;
}
setting_board(); //보드 초기 세팅
make_block(p.type, 0); //새로운 블럭 생성
//NEXT 위치에 다음 블록 모양 찍기
for (int i = 1; i < 3; i++) {
for (int j = 0; j < 4; j++) {
if (blocks[ntype][0][i][j] == 1) {
gotoxy(SIZE_X + 6 + j, i + 4);
printf("■");
}
else {
gotoxy(SIZE_X + 6 + j, i + 4);
printf(" ");
}
}
}
while (1) {
for (int i = 0; i < 5; i++) {
if (new_on)
break;
if (kbhit()){ //키입력이 있는 경우
key = getch(); //키값을 받음
if (key == 224){ //방향키인경우
do{
key = getch();
} while (key == 224);//방향키 지시값을 버림
switch (key){
//왼쪽 방향키 눌렀을때 왼쪽으로 갈 수 있는지 체크 후 가능하면 이동
case LEFT:
if (check_crush(p.x - 1, p.y, p.v, p.type)){
p = move_block(LEFT, p);
}
break;
//오른쪽 방향키 눌렀을때 오른쪽으로 갈 수 있는지 체크 후 가능하면 이동
case RIGHT:
if (check_crush(p.x + 1, p.y, p.v, p.type)){
p = move_block(RIGHT, p);
}
break;
//아래쪽 방향키 눌렀을때 위쪽으로 갈 수 있는지 체크 후 가능하면 이동
case DOWN:
if (check_crush(p.x, p.y + 1, p.v, p.type)){
p = move_block(DOWN, p);
}
break;
//위쪽 방향키 눌렀을때 회전할 수 있는지 체크 후 가능하면 회전
case UP:
if (check_crush(p.x, p.y, (p.v + 1) % 4, p.type)){
p = move_block(UP, p);
}
break;
}
}
else{ //방향키가 아닌경우
switch (key){
case ENTER: //엔터키 눌렀을때
stamp_block(0, p.x, p.y, p.v, p.type); //현재 위치 지우고
if (store_on) { //저장된 블록이 있다면
int tmp = p.type; //저장된 블록과 타입, 방향 교환
p.type = s_type;
s_type = tmp;
tmp = p.v;
p.v = s_v;
s_v = tmp;
//현재 위치가 부딪히는지 체크
if (!check_crush(p.x, p.y, p.v, p.type)) {
tmp = p.x; //x좌표 백업
for (int i = 0; i < 4; i++) {
BOOL block_on = FALSE; //블록이 있는지 체크하는 플래그
BOOL move_on = FALSE; //움직이는지 체크하는 플래그
//현재 블록이 원래의 보드에서 부딪히는지 체크
for (int j = 3; j >= 0; j--) {
if (blocks[p.type][p.v][j][i] == 1 && (board[p.y + j][p.x + i] == 2 || board[p.y + j][p.x + i] == -1)){
block_on = TRUE;
break;
}
}
//부딪힌다면 원래 블록모양과 같은지 체크
if (block_on) {
move_on = TRUE;
for (int j = 0; j < 4; j++) {
if (blocks[s_type][s_v][j][i] == 1){
move_on = FALSE;
break;
}
}
}
//다르다면 움직여야되는 블록
if (move_on) {
//왼쪽이 부딪힌다면 오른쪽으로 이동
if (i < 2) {
p.x += 1;
}
//반대라면 왼쪽으로 이동
else {
p.x -= 1;
//아직 부딪힌다면 한칸 더 이동
if (!check_crush(p.x, p.y, p.v, p.type)){
p.x -= 1;
}
}
//그래도 부딪힌다면 변경 불가
if (!check_crush(p.x, p.y, p.v, p.type)){
p.x = tmp;
tmp = p.type;
p.type = s_type;
s_type = tmp;
tmp = p.v;
p.v = s_v;
s_v = tmp;
}
break;
}
}
//y좌표도 동일하지만 아래쪽만 체크
if (!check_crush(p.x, p.y, p.v, p.type)){
tmp = p.y;
for (int i = 0; i < 4; i++) {
if (blocks[p.type][p.v][3][i] == 1 && (board[p.y + 3][p.x + i] == 2 || board[p.y + 3][p.x + i] == -1)) {
p.y -= 1;
if (!check_crush(p.x, p.y, p.v, p.type)){
p.y = tmp;
tmp = p.type;
p.type = s_type;
s_type = tmp;
tmp = p.v;
p.v = s_v;
s_v = tmp;
}
}
}
}
}
//바뀐 모양 찍어주기
stamp_block(1, p.x, p.y, p.v, p.type);
}
//저장된 블록이 없으면 넣어주고 새로운 블록 생성
else {
store_on = TRUE;
new_on = TRUE;
s_type = p.type;
s_v = p.v;
}
//다음 블록 모양 찍기
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (blocks[s_type][s_v][i][j] == 1) {
gotoxy(SIZE_X + 6 + j, i + 12);
printf("■");
}
else {
gotoxy(SIZE_X + 6 + j, i + 12);
printf(" ");
}
}
}
break;
//스페이스키 눌렀을때
case SPACE:
space_key_on = TRUE;
//바닥에 닿을때까지 이동시킴
while (!crush_on){
if (check_crush(p.x, p.y + 1, p.v, p.type)){
p = move_block(DOWN, p);
}
else{
crush_on = TRUE;
}
gotoxy(SIZE_X + 4, 19);
printf(" COMBO :");
gotoxy(SIZE_X + 4, 20);
printf(" %10d", combo);
gotoxy(SIZE_X + 4, 22);
printf(" SCORE :");
gotoxy(SIZE_X + 4, 23);
printf(" %10d", score);
}
break;
//esc눌렀을때
case ESC:
system("cls"); //화면을 지우고
exit(0); //게임종료
}
}
}
//키버퍼를 비움
while (kbhit())
getch();
setting_board();
Sleep(speed);
//블록이 충돌중일때는 조금 더 시간이 추가됨
if (crush_on && !check_crush(p.x, p.y, p.v, p.type))
Sleep(100);
//스페이스바가 눌린경우 끝
if (space_key_on){
space_key_on = FALSE;
break;
}
}
//블록 내리기
//crush flag가 켜져있는데 밑이 비어있으면 crush flag 끔
if (crush_on && check_crush(p.x, p.y + 1, p.v, p.type)){
crush_on = FALSE;
}
//밑이 비어있지않고 crush flag가 켜저있으면 현재 블럭을 굳힘
if (crush_on && !check_crush(p.x, p.y + 1, p.v, p.type)){
for (int i = 0; i < SIZE_Y; i++){
for (int j = 0; j < SIZE_X; j++){
if (board[i][j] == 1){
board[i][j] = 2;
}
}
}
crush_on = FALSE;
check_line();
new_on = TRUE;
continue;
}
//밑이 비어있으면 밑으로 한칸 이동
if (check_crush(p.x, p.y + 1, p.v, p.type) && !new_on){
p = move_block(DOWN, p);
}
//밑으로 이동이 안되면 crush flag를 켬
if (!check_crush(p.x, p.y + 1, p.v, p.type)) {
crush_on = TRUE;
}
check_finish(); //천장 라인에 닿아서 게임이 끝나는지 체크
if (new_on){ //새로운 블록 생성
p.x = (SIZE_X / 2) - 1;
p.y = 0;
p.v = 0;
p.type = ntype;
ntype = rand() % 7;
crush_on = FALSE;
make_block(p.type, 0);
//다음 블록 모양 찍기
for (int i = 1; i < 3; i++) {
for (int j = 0; j < 4; j++) {
if (blocks[ntype][0][i][j] == 1) {
gotoxy(SIZE_X + 6 + j, i + 4);
printf("■");
}
else {
gotoxy(SIZE_X + 6 + j, i + 4);
printf(" ");
}
}
}
}
}
return 0;
}
void gotoxy(int x, int y) {
//유니코드 사용으로 좌우(가로)는 2칸씩 배치
COORD pos = { x * 2, y };
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);
}
void setting_board() {
//블록이 닿으면 끝나는 줄(천장) -2로 세팅
for (int i = 1; i < SIZE_X - 1; i++) {
if (board[3][i] == 0){
board[3][i] = -2;
}
}
print_board();
//사이드바(점수판)
gotoxy(SIZE_X + 4, 3); printf("┏ N E X T ┓ ");
gotoxy(SIZE_X + 4, 4); printf("┃");
gotoxy(SIZE_X + 10, 4); printf(" ┃ ");
gotoxy(SIZE_X + 4, 5); printf("┃");
gotoxy(SIZE_X + 10, 5); printf(" ┃ ");
gotoxy(SIZE_X + 4, 6); printf("┃");
gotoxy(SIZE_X + 10, 6); printf(" ┃ ");
gotoxy(SIZE_X + 4, 7); printf("┃");
gotoxy(SIZE_X + 10, 7); printf(" ┃ ");
gotoxy(SIZE_X + 4, 8); printf("┃");
gotoxy(SIZE_X + 10, 8); printf(" ┃ ");
gotoxy(SIZE_X + 4, 9); printf("┗━━━━━━━━━━━━┛ ");
gotoxy(SIZE_X + 4, 11); printf("┏ S T O R E ┓ ");
gotoxy(SIZE_X + 4, 12); printf("┃");
gotoxy(SIZE_X + 10, 12); printf(" ┃ ");
gotoxy(SIZE_X + 4, 13); printf("┃");
gotoxy(SIZE_X + 10, 13); printf(" ┃ ");
gotoxy(SIZE_X + 4, 14); printf("┃");
gotoxy(SIZE_X + 10, 14); printf(" ┃ ");
gotoxy(SIZE_X + 4, 15); printf("┃");
gotoxy(SIZE_X + 10, 15); printf(" ┃ ");
gotoxy(SIZE_X + 4, 16); printf("┃");
gotoxy(SIZE_X + 10, 16); printf(" ┃ ");
gotoxy(SIZE_X + 4, 17); printf("┗━━━━━━━━━━━━┛ ");
//10의 자리수까지 표시 가능한 점수판(int 범위 : 약 21억)
gotoxy(SIZE_X + 4, 19); printf(" COMBO :");
gotoxy(SIZE_X + 4, 20); printf(" %10d", combo);
gotoxy(SIZE_X + 4, 22); printf(" SCORE :");
gotoxy(SIZE_X + 4, 23); printf(" %10d", score);
}
void print_board() { //값에 따라 모양 찍기
for (int i = 0; i < SIZE_Y; i++) {
for (int j = 0; j < SIZE_X; j++) {
gotoxy(j + 3, i + 1);
switch (board[i][j]) {
case 0: //빈칸
printf(" ");
break;
case -2: //블록이 떨어지는 라인
printf("¸");
break;
case -1: //게임 테두리
printf("▩");
break;
case 1: // 다 내려온 블록
printf("■");
break;
case 2: //움직이고 있는 블록
printf("□");
break;
}
}
}
}
void make_block(int type, int v){ //새로운 블록 만들기
//맨위 중간 위치로 이동하고 방향 초기화 및 flag 끄기
int s_x = (SIZE_X / 2) - 1;
int s_y = 0;
new_on = FALSE;
//현재 블록대로 board에 값 세팅
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (blocks[type][v][i][j] == 1)
board[s_y + i][s_x + j] = 1;
}
}
}
//고정된 블록 또는 벽에 부딪히는지 체크
BOOL check_crush(int x, int y, int v, int type){
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (blocks[type][v][i][j] == 1 && (board[y + i][x + j] == 2 || board[y + i][x + j] == -1)){
return FALSE;
}
}
}
return TRUE;
}
struct Position move_block(int value, struct Position p) {
switch (value) {
case LEFT: // 왼쪽 방향
stamp_block(0, p.x, p.y, p.v, p.type); //현재 위치 지운 뒤
stamp_block(1, p.x - 1, p.y, p.v, p.type); //다음 위치에 찍고
return ((struct Position) { p.x - 1, p.y, p.v, p.type }); //옮긴 위치 반환
break;
case RIGHT: //오른쪽 방향
stamp_block(0, p.x, p.y, p.v, p.type);
stamp_block(1, p.x + 1, p.y, p.v, p.type);
return ((struct Position) { p.x + 1, p.y, p.v, p.type });
break;
case DOWN: //아래쪽 방향
stamp_block(0, p.x, p.y, p.v, p.type);
stamp_block(1, p.x, p.y + 1, p.v, p.type);
print_board();
return ((struct Position) { p.x, p.y + 1, p.v, p.type });
break;
case UP: //위쪽 방향
stamp_block(0, p.x, p.y, p.v, p.type);
stamp_block(1, p.x, p.y, (p.v + 1) % 4, p.type);
return ((struct Position) { p.x, p.y, (p.v + 1) % 4, p.type });
break;
}
}
//현재 블록 위치, 모양, 타입대로 값 찍기
void stamp_block(int value, int x, int y, int v, int type){
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (blocks[type][v][i][j] == 1){
board[y + i][x + j] = value;
}
}
}
}
//줄이 가득찼는지 확인
void check_line() {
BOOL full_on = TRUE;
int cnt = combo;
//블록 공간 바닥부터 끝까지 체크
for (int i = SIZE_Y - 2; i > 3;) {
full_on = TRUE;
for (int j = 1; j < SIZE_X - 1; j++) { //한줄 끝까지 체크
if (board[i][j] == 0){ //도중에 빈공간이 있으면 완성된 칸이 아님
full_on = FALSE;
break;
}
}
if (full_on) { //가득찼다면 개수 올리고
cnt++;
for (int y = i; y > 1; y--) { //천장이 아니라면 한줄씩 내리고 천장이라면 빈칸 내리기
for (int x = 1; x < SIZE_X - 1; x++) {
if (board[y - 1][x] != -2)
board[y][x] = board[y - 1][x];
else
board[y][x] = 0;
}
}
}
else{ //아니면 다음줄 체크
i--;
}
}
if (cnt != combo) { //줄이 더 생겼다면
combo = cnt; //콤보 올리고
score += (1 << cnt) * 10; //점수 추가 적용
}
else {//아니면 콤보 초기화
combo = 0;
}
}
void check_finish() { //종료화면
int x = 5;
int y = 5;
for (int i = 1; i < SIZE_X - 2; i++) {
if (board[3][i] == 2) {
system("cls");
gotoxy(x, y + 0); printf("▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤"); //게임오버 메세지
gotoxy(x, y + 1); printf("▤ ▤");
gotoxy(x, y + 2); printf("▤ +-----------------------+ ▤");
gotoxy(x, y + 3); printf("▤ | G A M E O V E R | ▤");
gotoxy(x, y + 4); printf("▤ +-----------------------+ ▤");
gotoxy(x, y + 5); printf("▤ ▤");
gotoxy(x, y + 6); printf("▤ YOUR SCORE: %10d ▤", score); //점수띄우기
gotoxy(x, y + 7); printf("▤ ▤");
gotoxy(x, y + 8); printf("▤ Press any key to restart... ▤");
gotoxy(x, y + 9); printf("▤ ▤");
gotoxy(x, y + 10); printf("▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤\n\n\n\n\n\n");
Sleep(1000);
while (kbhit()) getch();
exit(0);
}
}
}