재귀함수의 예시로 매번 등장하는 하노이의 탑
간단한 게임으로 만들어 보았습니다.
명령어 입력 후 엔터를 눌러줘야 합니다.
예) 탑 높이 3을 입력하고 엔터
<< 게임 파일 >>
아래는 게임의 코드입니다.
//피보나치 수열, 하노이의 탑
#include<stdio.h>
#include<windows.h>
// 해당 게임에서 음악을 사용했으나
// 경로가 바뀌면 사용 불가능하고
// 음까지 같이 배포하기에 저작권의 문제가 있을지 모르니
// 현재는 주석 처리
//#include<mmsystem.h>
//#pragma comment(lib, "winmm.lib")
//#define whoareyou "C:\\workspace_vs\\whoareyou.wav"
//#define ddack "C:\\workspace_vs\\ddack.wav"
//#define frustration "C:\\workspace_vs\\frustration.wav"
//#define tetris "C:\\workspace_vs\\tetris.wav"
//#define stupid "C:\\workspace_vs\\stupid.wav"
#define _CRT_SECURE_NO_WARNINGS // scanf 보안 경고로 인한 컴파일 에러 방지
#define height_MAX 12 // 3층부터 12층까지 가능 -> 12 초과는 cmd창에서 표현불가, 3 미만은 의미없음
char coordinate_of_tower[(height_MAX + 3)][(6 * height_MAX + 3)]; // 2차원 배열을 통해 하노이 탑 전체 모형을 좌표로 설정
int flr[3] = { 0,0,0 }; // flr[n] : (n+1) 번째 막대에 꽂힌 층의 수 -> 이를 게임의 끝(3번째 막대에 7개의 블럭)을 판정 + 구속 조건과 결합하여 각 막대에 쌓이 각 층의 크기도 나타냄
void initial_state();
void print_tower();
void move_of_block(int from, int to);
void ClearEnterBuffer();
void victory();
void error(); // 잘못된 입력을 알리고 잠시 후 게임으로 다시 복귀
void com(int from, int tmp, int to, int floor);
int i = 0, j = 0, fail = 0;
int i1, j1, count1; // 첫번째 막대의 제일 윗층 탑의 좌표 + 크기
int i2, j2, count2; // 두번째 막대의 제일 윗층 탑의 좌표 + 크기
int height = 0;
int sleepTime = 0; // 원반이 움직였음을 보여주기 위한 다음 움직임까지의 대기 시간(1/1000초)
void main()
{
// 피보나치 수열
//num은 반복 횟수를 입력받는 변수
/*int num, pib1=1, pib2=1, pib3; // pib1,pib2는 피보나치 수열 다음 수 계산에 사용될 두 숫자, pib3은 pib1, pib2를 계산해서 다음수로 넘겨줄때 중간에 값을 저장하는 용도
printf("피보나치 수열\n\n");
printf("몇번째까지 만들지 입력하시오 : ");
scanf("%d", &num);
if (num == 1)
printf("%d\n\n",pib1);
else if (num ==2)
printf("%d %d\n\n", pib1, pib2);
else if (num > 2)
{
printf("%d %d ", pib1, pib2);
for (int i = 2; i < num; i++)
{
pib3 = pib2;
pib2 = pib1 + pib2;
printf("%d ", pib2);
pib1 = pib3;
}
printf("\n\n");
}*/
HANDLE consoleHandle = GetStdHandle(STD_OUTPUT_HANDLE); // 콘솔 핸들 가져오기
CONSOLE_CURSOR_INFO consoleCursor; // 콘솔 커서
consoleCursor.bVisible = 0; // false 혹은 0으로 커서 숨기기
consoleCursor.dwSize = 1;
SetConsoleCursorInfo(consoleHandle, &consoleCursor); // 설정값 적용
Start: // 해당 중단지점에서 goto로 재시작
// 재시작 시 게임 초기화 용도
flr[2] = 0;
height = 0;
// 하노이의 탑
int from, to, tmp;
while (height < 3 || height>12)
{
printf("하노이의 탑의 높이를 입력하세요(3 ~ 12) : ");
scanf_s("%d", &height);
}
ClearEnterBuffer();
sleepTime = 49 - (4 * height); // 12층까지 하면 블럭 이동 시간이 지나치게 길어지기에 층수에 따라 조절. 12층은 블럭 1칸 이동에 1밀리초(최소 단위)
flr[0] = height; // 초기에는 1번 막대에 모든 층이 꽂혀있다.
char sel = 'a';
while (1)
{
printf("직접 플레이하시겠습니까? (y / n) : ");
scanf_s("%c", &sel, sizeof(sel)); // scanf가 경고를 띄우고 말썽이구나.. 구동에는 지장이 없음
if (sel == 'y' || sel == 'Y' || sel == 'n' || sel == 'N')
break;
else
ClearEnterBuffer();
}
ClearEnterBuffer();
system("cls"); // 위의 게임 설정 부분을 지우기
initial_state(); // 탑이 최초에 쌓인 상태를 2d 좌표로 배열에 할당함
print_tower(); // 탑이 최초에 쌓인 상태를 출력
//PlaySound(TEXT(tetris), NULL, SND_FILENAME | SND_ASYNC | SND_LOOP);
if (sel == 'y' || sel == 'Y') // 유저가 직접 탑을 옮김
{
while (flr[2] < height) // 승리조건: 제일 오른쪽 막대에 height만큼 원반이 쌓이면 루프를 빠져나감
{
printf("\n제일 위의 층을 옮길 막대를 선택하시오(1 ~ 3) \n몇번 막대로부터 옮기나요? : ");
scanf_s("%d", &from);
ClearEnterBuffer();
if (from < 1 || from>3)
{
error();
continue;
}
printf("몇번 막대로 옮기나요? : ");
scanf_s("%d", &to);
ClearEnterBuffer();
if (from == to || to < 1 || to>3)
{
error();
continue;
}
// 입력이 끝나고
system("cls"); // 타워+안내 메시지 전체를 지우고
print_tower(); // 타워를 다시 그려줘서 안내 메시지만 날려주기
move_of_block(from, to);
}
Sleep(1000);
victory(); // 승리
}
else // 탑이 자동으로 이동
{
int floor = height;
from = 1, to = 3, tmp = 2;
com(from, tmp, to, floor);
Sleep(1000);
victory();
}
Sleep(1000);
COORD pos = { 0 ,(short)(height + 4) }; // 안내문을 시작할 커서 위치
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos); // 커서 이동
printf("아무 키나 입력하여 재시작\n");
int anyKey;
scanf_s("%d", &anyKey);
ClearEnterBuffer();
system("cls"); // 기존 내용을 지우고
goto Start; // main()의 처음부터 재시작
}
void initial_state() // 제일 처음 하노이탑이 첫 막대에 쌓인것을 표시 -> 2차원 배열로 이후에 각 좌표에 대한 값을 변경 가능
{
for (i = 0; i < (height + 3); i++) // num+2 만큼 시행 [막대의 길이(탑의 높이+1)이고 그 위에 빈 공간 2칸 존재]
{
if (i == 0 || i == 1) // 원반의 이동을 보여주기 위해 막대 위의 2줄은 비워둔다
{
for (j = 0; j < (6 * height + 3); j++)
coordinate_of_tower[i][j] = ' ';
}
else if (i > 1)
{
for (j = 0; j < (height - i + 2); j++) // 탑의 맨 아랫층 높이의 2배만큼 막대와 막대 사이를 띄우면 탑의 제일 아랫층과 막대가 겹치지 않는다.
{
coordinate_of_tower[i][j] = ' ';
}
for (j; j < (height + i - 1); j++) // 하노이의 탑을 별로 표현
{
if (j == (height - i + 2) && i == 2)
coordinate_of_tower[i][j] = '|';
else
coordinate_of_tower[i][j] = '*';
}
for (j; j < (3 * height + 1); j++) // 1->2 막대 사이의 간격을 띄워줌
{
coordinate_of_tower[i][j] = ' ';
}
coordinate_of_tower[i][j] = '|';// 2막대의 점을 찍어줌
j++; // 이 구문이 없으면 막대가 뒤이어 올 공백으로 덮어씌워짐
for (j; j < (5 * height + 2); j++) // 2->3 막대 사이의 간격을 띄워줌
{
coordinate_of_tower[i][j] = ' ';
}
coordinate_of_tower[i][j] = '|'; // 3막대의 점을 찍어줌
j++;
for (j; j < (6 * height + 3); j++) // 3막대 이후의 모든 공백에 대해 ' '을 지정해줌
{
coordinate_of_tower[i][j] = ' ';
}
}
}
}
void print_tower() // 새로운 하노이의 탑 출력(플레이하는 과정을 시각적으로 표현)
{
for (int i = 0; i < (height + 3); i++)
{
for (int j = 0; j < (6 * height + 3); j++)
{
COORD pos = { (short)j, (short)i }; // 커서 위치
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos); // 설정 위치로 커서 이동
printf("%c", coordinate_of_tower[i][j]); // 매번 전체를 지우기 않고 바뀌는 부분만 변경하기에 깜빡거림을 완화하면서 원반 이동 속도를 더 빠르게 할 수 있다
}
printf("\n");
}
}
void move_of_block(int from, int to) // 입력한 결과에 따라 제일 위의 블럭을 움직임
{
if (from == 1) // from번 막대의 제일 윗층 타워의 축으로부터 1칸 왼쪽의 가로축 좌표를 잡음 (height에 따라 막대 간격이 다름)
j1 = height - 1;
else if (from == 2)
j1 = 3 * height;
else if (from == 3)
j1 = 5 * height + 1;
i1 = height + 3 - flr[from - 1]; // from번 막대의 제일 윗층 타워의 세로축 좌표를 잡음
count1 = 0;
if (flr[from - 1] != 0)
{
while (coordinate_of_tower[i1][j1] == '*')
{
count1++; // count1으로 from번 막대의 제일 윗층의 크기를 판정
j1--; // 타워의 층의 제일 왼쪽 좌표의 -1이 나옴
}
}
if (count1 != 0)
j1++; //타워의 층의 제일 왼쪽 좌표가 나옴
if (to == 1) // from번 막대의 제일 윗층 타워의 축으로부터 1칸 왼쪽의 가로축 좌표를 잡음
j2 = height - 1;
else if (to == 2)
j2 = 3 * height;
else if (to == 3)
j2 = 5 * height + 1;
i2 = height + 3 - flr[to - 1]; // sel번 막대의 제일 윗층 타워의 세로축 좌표를 잡음 -> (i2-1) = 이동한 블럭이 쌓이는 세로축 좌표
count2 = 0;
if (flr[to - 1] != 0)
{
while (coordinate_of_tower[i2][j2] == '*')
{
count2++; // count1으로 from번 막대의 제일 윗층의 크기를 판정
j2--;
}
}
if (count2 != 0)
j2 += count2;
if (count1 > count2 && count2 != 0 || count1 == 0) // 이동하려는 막대의 최상층 블록보다 큰 블럭은 위로 옮길 수 없음, 아무층도 놓이지 않은 경우 이동 가능
{
error();
}
else
{
for (i = i1; i > 0; i--) // 옮기고자 하는 원반을 위로 올림
{
for (int j = 0; j < (count1 * 2 + 1); j++)
{
coordinate_of_tower[i][j1 + j] = ' '; // 옮기려는 원반 부분을 지움
if (((j1 + j) == 3 * height + 1 || (j1 + j) == 5 * height + 2 || (j1 + j) == height) && i != 1) // 막대가 지나는 부분은 *을 표시
coordinate_of_tower[i][j1 + j] = '|';
coordinate_of_tower[i - 1][j1 + j] = '*'; // 1칸 위에서 원반을 그림
}
Sleep(sleepTime);
print_tower();
}
if (j1 < j2) // 우측으로 이동
{
for (int j = j1; j < (j2 - count1 + 1); j++)
{
coordinate_of_tower[0][j] = ' ';
coordinate_of_tower[0][j + i + 2 * count1 + 1] = '*';
Sleep(sleepTime);
print_tower();
}
}
else // (j1>j2) 좌측으로 이동
{
for (int j = j1; j > j2 - count1 + 1; j--)
{
coordinate_of_tower[0][j + 2 * count1] = ' ';
coordinate_of_tower[0][j - 1] = '*';
Sleep(sleepTime);
print_tower();
}
}
for (int i = 0; i < i2 - 1; i++) // 목표 막대에 내려줌
{
for (int j = 0; j < (count1 * 2 + 1); j++)
{
coordinate_of_tower[i][j2 - (count1 - 1) + j] = ' '; // 옮기려는 원반 부분을 지움
if (((j2 + j - (count1 - 1)) == 3 * height + 1 || (j2 + j - (count1 - 1)) == 5 * height + 2 || (j2 + j - (count1 - 1)) == height) && i != 1 && i != 0) // 막대가 지나는 부분은 |을 표시
coordinate_of_tower[i][j2 + j - (count1 - 1)] = '|';
coordinate_of_tower[i + 1][j2 + j - (count1 - 1)] = '*'; // 원반을 1칸 아래로 내림
}
Sleep(sleepTime);
print_tower();
}
flr[to - 1]++; // 원반이 이동한 막대들에 대해 쌓인 층수를 더하고 빼줌
flr[from - 1]--;
}
}
void ClearEnterBuffer() // 잘못된 값을 입력할 경우 키보드 버퍼를 날리기 위해 함수
{
while (getchar() != '\n');
}
void victory()
{
//PlaySound(TEXT(frustration), NULL, SND_FILENAME | SND_ASYNC | SND_LOOP);
// 왼쪽부터 막대 2개 지우기
for (int i = 0; i < height + 3; i++)
{
for (int j = 0; j < 4 * height; j++)
{
coordinate_of_tower[i][j] = ' ';
}
}
system("cls");
print_tower();
// 타위 사이즈 3층부터 가능
// 게임 클리어 문구 출력
char clearChars[11] = "COMPLETE!!";
for (size_t i = 0; i < 10; i++)
{
coordinate_of_tower[(height + 3) / 2][2 * height - 5 + i] = clearChars[i];
print_tower();
Sleep(300);
}
}
void error()
{
system("cls");
printf("\n\n\n\n\n");
printf(" ********************\n");
printf(" ********************\n");
printf(" ** **\n");
printf(" ** 잘못된 이동 **\n");
printf(" ** **\n");
printf(" ********************\n");
printf(" ********************\n");
Sleep(1000);
/* 에러음은 더 이상 사용하지 않음
//PlaySound(NULL, 0, 0);
if (fail == 0) // 실패 횟수에 따라 각각 다른 음 재생
{
fail++;
//PlaySound(TEXT(whoareyou), NULL, SND_FILENAME | SND_ASYNC | SND_LOOP);
Sleep(1000);
}
else if (fail == 1)
{
fail++;
//PlaySound(TEXT(ddack), NULL, SND_FILENAME | SND_ASYNC | SND_LOOP);
Sleep(2000);
}
else
{
fail = 0;
//PlaySound(TEXT(stupid), NULL, SND_FILENAME | SND_ASYNC | SND_LOOP);
Sleep(1500);
}
//PlaySound(NULL, 0, 0);
//PlaySound(TEXT(tetris), NULL, SND_FILENAME | SND_ASYNC | SND_LOOP);
*/
system("cls");
print_tower();
}
void com(int from, int tmp, int to, int floor)
{
if (floor == 1)
move_of_block(from, to);
else
{
com(from, to, tmp, floor - 1);
move_of_block(from, to);
com(tmp, from, to, floor - 1);
}
}
'기타' 카테고리의 다른 글
시프트+딜리트로 실수로 파일을 삭제했을 때 무료 복구 방법 (0) | 2025.03.29 |
---|---|
티스토리 다크 모드 무료 스킨 추천 및 적용 (0) | 2025.03.02 |
C/C++ 비전공자의 눈으로 보는 포인터 (Pointer) (2) | 2025.03.02 |
gif 배너 만들 때 사용한 툴 정리 (0) | 2024.03.14 |