C) 하노이의 탑 실행창에서 게임으로 즐기기 (exe, 소스 코드 첨부)

2025. 3. 1. 06:24·기타

재귀함수의 예시로 매번 등장하는 하노이의 탑

간단한 게임으로 만들어 보았습니다.

 

명령어 입력 후 엔터를 눌러줘야 합니다.

예) 탑 높이 3을 입력하고 엔터

자동으로 답 맞춰주기

 

직접 플레이

 

 

<< 게임 파일 >>

hanoi.exe
0.07MB

 

 

아래는 게임의 코드입니다.

//피보나치 수열, 하노이의 탑

#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
'기타' 카테고리의 다른 글
  • 시프트+딜리트로 실수로 파일을 삭제했을 때 무료 복구 방법
  • 티스토리 다크 모드 무료 스킨 추천 및 적용
  • C/C++ 비전공자의 눈으로 보는 포인터 (Pointer)
  • gif 배너 만들 때 사용한 툴 정리
ybbro
ybbro
대부분의 포스팅은 pc에서 작성되었습니다. 모바일에서 볼 때 설명이 잘리면 데스크탑 모드를 사용해보길 바랍니다.
  • ybbro
    어떻게든 굴리는 게임 공방
    ybbro
  • 전체
    오늘
    어제
    • 전체
      • 스파르타코딩클럽_Unity개발과정
      • Unity 2D
        • 카드게임
        • 플랫포머 게임
        • 뱀서라이크
      • Unity 3D
        • 닷지
        • 유니티 짱
        • 디펜스 게임
      • Unity 에러 노트
      • 기능 구현 방법 정리
      • 셰이더 그래프
        • 2D
        • 3D
      • 프로그래머스
      • 자료구조
      • 기타
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    텍스트매시프로
    유니티 애니메이터 파라미터 초기화
    직렬화
    대시
    세이브
    잔상
    unity
    유니티
    갤럭시 S24
    삭제
    마스크
    무료스킨
    룰렛
    64비트
    다크모드
    sprite mask
    앱이 휴대전화와 호환되지 않아 설치되지 않았습니다
    UI
    hello
    스파인
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
ybbro
C) 하노이의 탑 실행창에서 게임으로 즐기기 (exe, 소스 코드 첨부)
상단으로

티스토리툴바