일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- 셀
- 포인터
- 커서
- Windows API
- string
- 구현
- C
- 이진탐색#binary_search
- 함수
- 난수
- 3차원 배열
- 문자열
- C언어
- 콘솔
- 알고리즘
- Time
- 테트리스
- 삽입
- 연결리스트
- 배열
- 파일입출력
- scanf
- 오목#함수#gotoxy#금수#알고리즘#2차원#배열#실무#프로젝트
- 공백
- 선택
- 버블
- 구조체
- crud
- 정렬
Archives
- Today
- Total
C언어 알고리즘 정리 및 실무 프로젝트
C언어 콘솔 테트리스 프로그램(사용자 정의 함수 , 3차원 배열, 제어문 로직 활용) Windows API , 표준 라이브러리 활용 ( x86 버전) 본문
C언어 실무 프로젝트
C언어 콘솔 테트리스 프로그램(사용자 정의 함수 , 3차원 배열, 제어문 로직 활용) Windows API , 표준 라이브러리 활용 ( x86 버전)
C's everything! 2023. 6. 12. 18:23반응형
기존의 소스에서 로딩 화면, UI, 다음에 나올 블록의 모양, 점수, 레벨(난이도), 최고 기록 기능을 새롭게 추가하였습니다. 함수 단위로 기능을 세분화하였습니다. 함수에 있는 주석을 보시면서 혹시라도 콘솔 테트리스로 프로젝트를 하시는 분을 위해 기능을 이해할 수 있도록 써봤습니다.
#define _CRT_SECURE_NO_WARNINGS
#include "hed.h"
#define LEFT 75 // 왼쪽 방향키
#define RIGHT 77// 오른쪽 방향키
#define UP 72 // 위쪽 방향키
#define DOWN 80 // 아래 방향키
#define ESC 27
#define BX 5 // 외부 벽 X의 좌상단 좌표
#define BY 1 // 외부 벽 Y의 좌상단 좌표
#define BW 10 // 게임판의 폭
#define BH 20 // 게임판의 높이
void DrawScreen(); // 화면 전체를 그린다.
void DrawBoard(); // 벽돌 게임판을 그림. (벽 제외)
BOOL ProcessKey(); // 키 입력을 처리하는 함수 ( 방향키 및 옵션키 )
void PrintBrick(BOOL Show); // 벽돌을 출력하거나 삭제함. ( 1이면 출력, 0이면 삭제)
int GetAround(int x, int y, int b, int r); // 벽돌 주변의 상황에 따라서
// 벽돌의 이동 및 회전 가능성을 조사한다.
BOOL MoveDown(); // 벽돌을 한 칸 아래로 이동시킨다.
void TestFull(); // 채워진 줄이 있는지 검사한다.
void start(); // 게임 시작 함수
struct Point { // 좌표 구조체
int x, y;
};
struct Point Shape[][4][4] = { // 좌표 구조체형의 3차원 배열을 생성한다.
{ { 0,0,1,0,2,0,-1,0 },{ 0,0,0,1,0,-1,0,-2 },{ 0,0,1,0,2,0,-1,0 },{ 0,0,0,1,0,-1,0,-2 } },
{ { 0,0,1,0,0,1,1,1 },{ 0,0,1,0,0,1,1,1 },{ 0,0,1,0,0,1,1,1 },{ 0,0,1,0,0,1,1,1 } },
{ { 0,0,-1,0,0,-1,1,-1 },{ 0,0,0,1,-1,0,-1,-1 },{ 0,0,-1,0,0,-1,1,-1 },{ 0,0,0,1,-1,0,-1,-1 } },
{ { 0,0,-1,-1,0,-1,1,0 },{ 0,0,-1,0,-1,1,0,-1 },{ 0,0,-1,-1,0,-1,1,0 },{ 0,0,-1,0,-1,1,0,-1 } },
{ { 0,0,-1,0,1,0,-1,-1 },{ 0,0,0,-1,0,1,-1,1 },{ 0,0,-1,0,1,0,1,1 },{ 0,0,0,-1,0,1,1,-1 } },
{ { 0,0,1,0,-1,0,1,-1 },{ 0,0,0,1,0,-1,-1,-1 },{ 0,0,1,0,-1,0,-1,1 },{ 0,0,0,-1,0,1,1,1 } },
{ { 0,0,-1,0,1,0,0,1 },{ 0,0,0,-1,0,1,1,0 },{ 0,0,-1,0,1,0,0,-1 },{ 0,0,-1,0,0,-1,0,1 } },
};
enum { EMPTY, BRICK, WALL }; // 빈 공간: 0, 벽돌: 1, 벽: 2
char *arTile[] = { ". ","■","■" }; // 열거형의 값과 배열의 요소가 대응된다.
int board[BW + 2][BH + 2]; // 게임판의 폭에서 양쪽 벽의 폭을 더한 배열
int nx, ny; // 이동중인 벽돌의 현재 좌표 ( 배열상의 좌표값)
int brick[2] = { 0 }, rot; // 이동중인 벽돌의 번호, 회전 모양
int score = 0; // 점수 ( 지워지는 한 줄당 10점씩 증가)
int best_log= 0;// 최고 기록 (점수)
int level = 1; // 레벨 (점수가 50 증가할 때마다 1 증가)
void main()
{
time_t endCurrent; // 종료 시간
char input; // 게임 시작 키 입력 받음.
printf("\n\t게임 설명\n\n1. 다른 모양의 블럭 4개가 있습니다. 랜덤하게 이 4개 중에 1개가 천천히 내려옵니다.\n\n2. 좌 우 방향키를 누르면 누른 방향으로 블럭이 회전합니다.\n\n");
printf("3. 그렇게 내려오다가 블럭이 바닥에 닿으면 랜덤하게 또 4개 중 1개가 천천히 내려옵니다.\n\n4. 이러한 방법으로 바닥에 여러 블럭들을 쌓으면서 수평으로 된 블럭 줄을 만들면 그 줄이 지워집니다.\n\n");
printf("5. 줄을 잘 지우지 못해서 블럭이 쌓이다가 블럭이 내려올 곳이 더 이상 없을 때 게임은 끝이 납니다.\n\n\n");
printf("S를 누르면 게임을 시작합니다.\n\n");
for (;;) // S를 누를때까지 반복
{
input = _getch(); // 엔터키 없이 즉시 입력받는다.
if (input== 'S' || input== 's') break; // S를 입력받으면 for문을 탈출하면서 게임이 시작된다.
}
endCurrent = time(NULL) + 3; // 3초 후에 시작을 위함.
while (1) {
time_t startCurrent = time(NULL); // 현재 시간 저장
if (endCurrent - startCurrent <= 0) break; // 3초가 지나면 종료
printf("%lld초 후 게임을 시작합니다.\n", endCurrent - startCurrent);
Sleep(1000); // 1초 동안 작업 대기, 1초가 지나면 다음 startCurrent에 1초가 증가한 값이 들어감.
}
start(); // 본 게임을 시작한다.
}
void next() { // 다음에 나올 블록을 표시한다.
int Next[8][7]; // 다음 블록의 모양을 보관할 배열
int i;
gotoxy(34, 8); puts("다음 블록의 모양");
for (int x = 0; x < 8; x++) {
for (int y = 0; y < 7; y++) {
Next[x][y] = (y == 0 || y == 6 || x == 0 || x == 7) ? WALL : EMPTY; // 외부 벽을 칠함.
gotoxy(34 + x * 2, 10 + y); // 벽의 좌상단 좌표에서부터 커서를 x*2, y만큼 이동시킴
puts(arTile[Next[x][y]]); // board[x][y]에 저장된 열거형 값으로 테두리 생성
}
}
brick[1] = random(sizeof(Shape) / sizeof(Shape[0])); // 다음 블록의 번호를 저장함.
for (i = 0; i < 4; i++) { // 4개짜리 벽돌을 4번 반복해서 그림
gotoxy(40 + (Shape[brick[1]][rot][i].x) * 2, 13 + Shape[brick[1]][rot][i].y);
// 블록 모양의 좌표에 맞게 커서를 이동시킨다.
puts(arTile[1]); // 커서로 이동한 위치에 벽돌 출력
}
}
void start() { // 새 게임을 시작한다.
int nFrame, nStay, x, y; // 블록 속도, 속도 카운트, board 배열의 첨자 x, y
setcursortype(NOCURSOR); // 게임을 시작하기 전에 불필요한 커서를 지운다.
randomize(); // 난수 발생기 초기화
clrscr(); // 화면 청소
for (x = 0; x < BW + 2; x++) {
for (y = 0; y < BH + 2; y++)
board[x][y] = (y == 0 || y == BH + 1 || x == 0 || x == BW + 1) ? WALL : EMPTY;
// 게임판의 초기 상태를 지정한다. 외곽 부분은 벽(열거형 값 2), 나머지는 빈 공간(열거형 값 0)으로 비워둔다.
}
score = 0; // 게임 시작시 점수를 0으로 초기화
level = 1; // 게임 시작시 레벨을 1로 초기화
DrawScreen(); // 화면 전체를 그린다.
for (; 1;) {
nFrame = 20 - (3 * level); // 레벨이 증가할수록 블록이 떨어지는 속도 증가
if (level >= 6) { // 6레벨 이상일 때는 4으로 고정한다. (너무 빠르면 힘듭니다.)
nFrame = 4;
}
if (!brick[0]) // 맨 처음에만 생성
brick[0] = random(sizeof(Shape) / sizeof(Shape[0])); // 블록의 번호를 랜덤으로 지정한다.
else // 두 번째부턴 다음 블록의 모양을 대입한다.
brick[0] = brick[1]; // 다음 블록의 모양을 저장
next(); // 다음에 나올 블록을 표시한다.
nx = BW / 2; // 이동중인 벽돌의 x좌표
ny = 3; // 이동중인 벽돌의 y좌표
rot = 0; // 회전을 하지 않은 상태
PrintBrick(TRUE); // 벽돌을 출력
if (GetAround(nx, ny, brick[0], rot) != EMPTY) break;
// 현재 위치에서 비어있는 블록이 없으면 Game Over...
nStay = nFrame; // 변경된 속도를 대입
for (; 2;) {
if (--nStay == 0) { // 카운트하다가 0이 되면 MoveDown()을 호출한다.
nStay = nFrame; // 다음 블록을 위해 다시 대입
if (MoveDown()) break; // 이동이 끝나면 break;
}
if (ProcessKey()) break; // 이동중인 벽돌이 바닥에 닿으면 break
delay(1000 / 20); // 0.05초동안 프로그램을 지연시킴.
}
}
clrscr(); // 화면 청소
gotoxy(30, 7); printf("%d단계에서 돌아가셨습니다.\n\n", level);
gotoxy(30, 9); puts("G A M E O V E R");
Sleep(2000); // 2초후 프로그램 종료
setcursortype(NORMALCURSOR); // 게임 종료 후 커서를 표시
}
void DrawScreen() // 화면 전체를 그린다.
{
int x, y; // 이동할 거리
for (x = 0; x < BW + 2; x++) {
for (y = 0; y < BH + 2; y++) {
gotoxy(BX + x * 2, BY + y); // 외부 벽의 좌상단 좌표에서부터 커서를 x*2, y만큼 이동시킴
puts(arTile[board[x][y]]); // board[x][y]에 저장된 열거형 값으로 게임판을 구성한다.
}
}
// 게임에 대한 정보 출력
gotoxy(56, 3); puts("Version: Tetris Ver 1.0");
gotoxy(56, 6); puts("조작 키: <-, ->: 이동, ↑: 회전, ↓: 내림");
gotoxy(56, 8); puts("옵션 키: 공백(space): 전부 내림, @: 일시정지");
gotoxy(56, 9); puts(" Any Key: 일시정지 해제, !: 새 게임");
gotoxy(56, 12); printf("점수: %d", score);
gotoxy(56, 16); printf("레벨: %d", level);
gotoxy(56, 20); printf("최고 기록: %d", best_log);
}
void DrawBoard() // 블록을 그린다.
{
int x, y; // 이동할 거리
for (x = 1; x < BW + 1; x++) {
for (y = 1; y < BH + 1; y++) {
gotoxy(BX + x * 2, BY + y); // 외부 벽의 좌상단 좌표에서부터 커서를 x*2, y만큼 이동시킴
puts(arTile[board[x][y]]); // board[x][y]에 저장된 열거형 값으로 쌓여있는 벽돌을 그린다.
gotoxy(56, 12); printf("점수: %d", score); // 한 줄이 지워진 후 증가된 점수 출력
gotoxy(56, 16); printf("레벨: %d", level); // 50점이 증가할 때마다 레벨 1 증가
gotoxy(56, 20); printf("최고 기록: %d", best_log);
}
}
}
BOOL ProcessKey() // 키 입력 처리를 위한 함수
{
int ch, trot; // 키 입력, 회전 값 저장
if (_kbhit()) { // 키보드 입력이 있을 때까지 대기
ch = _getch();
if (ch == 0xE0 || ch == 0) { // 확장 키(Function) 의 경우에 키를 하나 더 입력 받는다.
// 방향키는 확장키에 포함된다.( 2바이트로 되어 있으므로 _getch()로 한번에 읽어들일 수 없다.)
// 따라서 _getch()를 2번 호출해야 한다.
ch = _getch();
switch (ch) { // 입력 값을 검사
case LEFT:
if (GetAround(nx - 1, ny, brick[0], rot) == EMPTY) {
PrintBrick(FALSE); // 이전 위치의 블록을 삭제한다.
nx--; // 왼쪽으로 한 칸 좌표 이동
PrintBrick(TRUE); // 왼쪽 방향키를 누른 후에, 블록을 새로 그린다.
}
break;
case RIGHT:
if (GetAround(nx + 1, ny, brick[0], rot) == EMPTY) {
PrintBrick(FALSE); // 이전 위치의 블록을 삭제한다.
nx++; // 오른쪽으로 한 칸 좌표 이동
PrintBrick(TRUE); // 오른쪽 방향키를 누른 후에, 블록을 새로 그린다.
}
break;
case UP:
trot = (rot == 3 ? 0 : rot + 1); // 다른 블록 모양으로 쌓을 수 있게 회전값을 지정
if (GetAround(nx, ny, brick[0], trot) == EMPTY) { // 벽돌 회전이 가능한 위치인지 검사
PrintBrick(FALSE); // 벽돌 삭제 (회전하기 전)
rot = trot; // 회전값을 대입
PrintBrick(TRUE); // 벽돌 출력 (회전이 적용된 모양)
}
break;
case DOWN:
if (MoveDown()) // 아래로 한 칸 내림.
return TRUE;
break;
}
}
else {
switch (ch) {
case ' ': // 스페이스 키를 누르면
while (MoveDown() == FALSE) {} // 블록을 바닥으로 즉시 내린다.
return TRUE;
break;
case '@': // @을 누르면
_kbhit(); // 일시 정지
break;
case '!': // !를 누르면
start(); // 새 게임 시작
break;
}
}// else
}// if(_kbhit())
return FALSE;
}
void PrintBrick(BOOL Show) // 벽돌을 출력하거나 삭제한다.
{ // 이동중인 벽돌을 대상으로 한다.
int i;
for (i = 0; i < 4; i++) { // 4개짜리 벽돌을 그려야 하므로 4번 반복
gotoxy(BX + (Shape[brick[0]][rot][i].x + nx) * 2, BY + Shape[brick[0]][rot][i].y + ny);
// 블록 모양의 좌표에 맞게 커서를 이동시킨다.
puts(arTile[Show ? BRICK : EMPTY]); // show가 true면 벽돌, false면 빈 공간 출력
}
}
int GetAround(int x, int y, int b, int r) // 벽돌 주변의 지형 검사
{
int i, k = EMPTY; // k를 빈 공간(0) 값으로 초기화
for (i = 0; i < 4; i++)
k = max(k, board[x + Shape[b][r][i].x][y + Shape[b][r][i].y]);
// 해당 지형의 좌표 값(0 or 1 or 2)을 k와 비교해서 큰 값을 k에 저장
return k; // 지형 값 반환
}
BOOL MoveDown() // 벽돌을 한칸 아래로 이동시킨다.
{
if (GetAround(nx, ny + 1, brick[0], rot) != EMPTY) { // 다음 ny좌표가 채워져 있으면, 이번 위치에서 이동을 끝내야 한다.
TestFull(); // 삭제할 것이 있는지 검사한다.
return TRUE; // 이동이 끝나면 true를 반환
}
PrintBrick(FALSE); // 현재 벽돌을 삭제
ny++; // 이동중인 현재 벽돌의 y좌표를 증가시킨다.
PrintBrick(TRUE); // 한 칸 아래에 벽돌을 출력
return FALSE; // 이동을 더 해야하므로 false를 반환
}
void TestFull() // 채워진 줄이 있는지 검사한다.
{
int i, x, y, ty;
for (i = 0; i < 4; i++) // 4개짜리 블록이 착륙했으므로 4번 반복해서 저장
board[nx + Shape[brick[0]][rot][i].x][ny + Shape[brick[0]][rot][i].y] = BRICK;
// 벽돌이 바닥에 착륙했으므로, 착륙한 위치에 벽돌값(1)을 저장한다.
for (y = 1; y < BH + 1; y++) { // 높이만큼 반복하면서 검사
for (x = 1; x < BW + 1; x++) // 너비만큼 반복하면서 검사
if (board[x][y] != BRICK) break; // 비어 있으면 break
if (x == BW + 1) { // 이전의 for문에서 x가 break를 만나지 않고
// BW + 1이 되어서 나왔다면 한 줄이 채워진 것이라고 볼 수 있다.
for (ty = y; ty > 1; ty--) {
for (x = 1; x < BW + 1; x++)
board[x][ty] = board[x][ty - 1];
// 채워진 줄의 값들을 윗 줄의 값으로 덮어쓴다.
// 나머지 윗 줄들도 차례로 덮어쓰기 하면서 한 줄을 지우는 효과를 볼 수 있다.
}
score += 10; // 한 줄당 10점 증가
if (score % 50 == 0) // 점수 50당
level++; // 레벨 증가
if (best_log < score)
best_log = score; // 최고기록 갱신
DrawBoard(); // 블럭을 다시 그린다.
delay(200); // 0.2초 지연
}// if
}// for(y)
}// TestFull()
그림 출처: 위키백과
사이트에 있는 원본 소스를 기반으로 프로그램을 만들었습니다.
반응형
'C언어 실무 프로젝트' 카테고리의 다른 글
C언어 컴소과 학생 관리[CRUD] 프로그램(구조체, 연결리스트(포인터), 함수 활용) x86 버전 (4) | 2022.05.28 |
---|---|
C언어 오목 프로그램(2인용, 2차원 배열과 심화된 제어문 로직 활용) Windows API 및, 표준 라이브러리 사용 (x86 버전) (4) | 2022.05.28 |
C언어 Up Down 콘솔 게임 (2) | 2022.05.18 |
C언어 성적 처리 프로그램 (파일 입출력, 함수, 구조체, 포인터 이용) (0) | 2022.04.23 |
C언어 숫자야구 프로그램 ( 배열, 제어문, %연산자 활용) 1 ~ 9자리까지 입력이 가능 (2) | 2022.04.22 |
Comments