프로그래밍/C
지가 미로를 탐색하는 프로그램입니다.
jhansol
2014. 4. 18. 12:56
설명운 다음에 달도록 하겠습니다.
아래 링크를 클릭하여 파일을 다운로드 받아 리눅스 상에서 컴파일하고 실행해보세요.
다른 것은 문제가 아닌데 시스템 명령이 리눅스용이라 그렇습니다.
아래 내용은 소스파일입니다.
#include#include #include #include #define MAZE_SIZE 10 // 미로의 가로/세로 폭 #define GOAL 'G' // 골인 지점의 상수 #define FOOD 'F' // 임식ㅇ 있는 지점의 상수 #define SYMBWALL '#' // 벽의 상수 #define SYMBROAD ' ' // 통로 상수 #define SYMBFOOTPRINT '-' // 지나간 경로를 의미하는 상수 #define SYMBTRACEPRINT '@' // 최단 경로 상수 typedef struct { // 우선순위를 지정하기 위한 구조체 정의 int row; // 행 int col; // 열 int weight; // 해당 좌표의 가중치 } weight; typedef struct point { // 좌표를 스택에 저장하기 위한 요소의 구조체 int row; // 행 int col; // 열 struct point *prev; // 이전 요소가 저장된 메모리 위치 } point; typedef struct { // 스택 정보를 담기 위한 구조체 struct point *top; // 맨 마지막 저장된 자료의 위치 } StackType; // 스택을 초기화하는 함수 // 전달받은 스택의 정보를 이용하여 저장된 스택에 저장된 자료를 모두 제거한다. void init_stack( StackType *s ) { point *temp; // top이 가리키는 위치의 자료의 이치를 저장하기 위한 변수 // s->top이 NULL이 아닌동안(즉, 자료가 있는 동안) 아래 문장을 반복 while( s->top ) { temp = s->top; // top이 가리키는 스택 자료의 번지를 임시 변수에 저장 s->top = s->top->prev; // top 이전의 스택 자료의 번지를 top에 지정 free( temp ); // 임시 변수에 저장된 스택자료가 들어 있는 메모리를 할당 해제함 } } // 스택에 자료를 삽입하는 함수 // 스택 정보와 좌표를 입력받아 동적으로 메모리를 할당받아 스택에 삽입한다. // 만일 메모리를 할당받지 못한 경우 0을, 할당받아 스택에 삽입되면 1을 되돌린다. int push( StackType *s, int r, int c ) { // 스택에 저장되는 요소의 크기만큼 메모리를 할당받아 변수 p에 저장 point *p = (point *)malloc( sizeof( point ) ); if( !p ) { // 만약 p의 값이 NULL이면 printf( "스택 포함 에러\n" ); // 스택 포함 에러를 출력하고 return 0; // 0을 되돌린다. } else { // 그렇지 않으면 p->row = r; // 전달된 좌표를 할당된 요소에 저장한다. p->col = c; if( !s->top ) p->prev = NULL; // 만일 탑이 NULL이면 이전자료 위치(prev)를 NULL로 지정 else p->prev = s->top; // 그렇지 않으면 이전 위치를 top의 값을 지정한다. s->top = p; // 그리고 top에 할당 받은 메모리의 번지를 지정한다. return 1; // 성상적으로 삽입되었으므로 1을 되돌리고 종료한다. } } // 스택에서 top이 가리키는 위치의 자료를 꺼내는 함수 // top이 가리키는 요소에서 좌표를 r, c가 가리키는 번지에 지정한다. // top이 NULL이면 오류를 표시하고 0을 되돌리고, 정상적으로 꺼낸 경우 1을 되돌린다. int pop( StackType *s, int *r, int *c ) { point *temp; // 자료를 삭제할 때 사용할 임시 변수 if( !s->top ) { // top이 NULL이면 printf( "스택 공백 에러\n" ); // 오류를 출력하고 return 0; // 0을 되돌린다. } else { // 그렇지 않으면 temp = s->top; // top의 값을 temp에 지정하고 *r = s->top->row; // r이 가리키는 번지에 top이 가리키는 자료의 row값을 *c = s->top->col; // c가 가리키는 번지에 top이 가리키는 자료의 col의 값을 지정 s->top = s->top->prev; // 스택의 top에 이전 자료의 번지를 지정한다. free( temp ); // temp(마지막 자료)의 메모리를 해제한다. return 1; // 정상적으로 꺼냈으므로 1을 되돌린다. } } // 스택에 들어 있는 자료를 출력하는 함수 // 전달된 스택 정보를 이용하여 top이 가리키는 메모리의 자료를 출력하고 // 각 요소의 prev를 이용하여 탐색하며 자료를 출력한다. void print_stack( StackType *s ) { point *temp; // 자료 출력을 위한 임시 변수 printf( "| |\n" ); temp = s->top; // top이 가리키는 자료의 번지를 temp에 지정 while( temp ) { // temp가 NULL 아닌 동안(자료가 있는 동안) 반복 printf( "|(%01d,%01d)|\n", temp->row, temp->col ); // 요소(자료)의 r, 의 값 출력 temp = temp->prev; // temp의 prev를 temp에 지정 } printf( "|-----|\n" ); // 자료의 끝임을 출력 } // 미로를 초기화하는 함수 // 조기화용 이차원 배열에 미로로 데이터를 설정하고 전달된 미로 데이터 번지에 복사한다. void init_maze( char (*m)[MAZE_SIZE] ) { // 초기화용 미로 데이터 char maze_init[MAZE_SIZE][MAZE_SIZE] = { {'#', 'F', ' ', 'F', '#', '#', '#', ' ', '#', '#'}, {'#', ' ', ' ', ' ', ' ', ' ', '#', 'F', '#', '#'}, {'#', ' ', '#', ' ', '#', ' ', ' ', ' ', ' ', ' '}, {' ', ' ', '#', ' ', '#', ' ', '#', '#', '#', ' '}, {'#', ' ', '#', ' ', ' ', 'G', '#', ' ', ' ', ' '}, {'#', '#', '#', ' ', '#', '#', '#', '#', '#', ' '}, {' ', ' ', ' ', ' ', 'F', ' ', ' ', '#', '#', ' '}, {' ', '#', '#', '#', '#', '#', ' ', ' ', '#', ' '}, {' ', '#', '#', ' ', '#', '#', ' ', '#', '#', '#'}, {' ', ' ', ' ', ' ', '#', '#', ' ', '#', '#', '#'} }; int i, j; // 이중 for문을 이용하여 데이터를 복사한다. for( i = 0 ; i < MAZE_SIZE ; i++ ) for( j = 0 ; j < MAZE_SIZE ; j++ ) m[i][j] = maze_init[i][j]; } // 미로 데이터를 출력하는 함수 // 반복문을 이용하여 미로 데이터를 화면에 출력한다. void print_maze( char (*m)[MAZE_SIZE] ) { int i, j; for( i = 0 ; i < MAZE_SIZE ; i++ ) { for( j = 0 ; j < MAZE_SIZE ; j++ ) printf( " %c", m[i][j] ); printf( "\n" ); } } // 게임이 시작될 때 쥐의 초기 위치를 입력받는 함수 // r, c의 번지, 미로 데이터가 들어 있는 배열의 번지를 전달받고 // 사용자로부터 쥐의 초기 좌표를 입력받아 올바른 좌표인지, 통로인지 등을 검사하여 통로일 경우에만 // 게임이 진행되도록 한다. // 이 함수에서 되돌리는 결과는 다음과 같다. // 0 : 정상 입력이며 게임을 진행한다. // 1 : 잘못된 좌표 입력 // 2 : 통로가 아님 int input_start_position(int *r, int *c, char (*m)[MAZE_SIZE]) { if(scanf("%d,%d",r,c)!=2) // r과 c에 좌표를 입력받고 입력된 자료가 2개가 아니면 { fflush(stdin); // 입력 버퍼를 비우고 return 1; // 1을 되돌린다. } else{ // 그렇지 않고 // 좌표과 배열의 인덱스 범위 않에 있지 않으면 1을 되돌린다. if( *r < 0 || *r > 9 || *c < 0 || *c > 9 ) return 1; else if( m[*r][*c] != SYMBROAD ) return 2; // 지정된 좌페가 통로가 아니면 2를 되돌린다. else return 0; // 정상적으로 입력되었으므로 0을 되돌려 게임이 진행되도록 한다. } } // 게임이 종료된 경우 다시 할지 확인하는 함수 // temp에 정수를 입력받아 1이면 진행 그렇지 않으면 끝내도록 한다. int endsign(){ int tmp = 0; // 임시 변수 초기화 printf("계속 하시겠습니까? 예(1) 아니오(0) "); // 메시지 출력 if(scanf("%d", &tmp)==0)fflush(stdin); // 입력되지 않으면 입력버퍼를 지운다. return (tmp==1); // 입력된 값이 1과 같으면 1을 그렇지 않으면 0을 되돌린다. } // 스택에 우선순위를 지정하여 우선순위가 낮은 요소가 먼저 삽입되도록 정렬하는 함수 // 좌표와 가중치 정보가 들어있는 구조체 자료의 배열의 번지와 자료의 개수를 입력받아 거품 정렬한다. void sort( weight *w, int cnt ) { int i, j; weight temp; // 자료를 교환하기 위한 임시 변수 // 이중 for문을 이용하여 정렬한다. for( i = 0 ; i < cnt - 1 ; i++ ) for( j = i + 1 ; j < cnt ; j++ ) if( w[i].weight > w[j].weight ) { // i번째 자료의 가중치가 j번째 자료의 가중치보다 크면 temp = w[i]; // temp를 이용하여 서로 교환한다. w[i] = w[j]; // w[j] = temp; // } } // 현재 위치에서 골인지점, 음식, 통로 등을 검색하여 우선순위를 정해 스택에 삽입하는 함수 // 스택정보, 미로 데이터, 현재 위치(r,c)를 전달받아 이동하는 가능한 위치를 우선순위를 정해 우선순위가 가장 낮은 // 좌표부터 스택에 삽입한다. // 우선순위 계산울 위한 가중치는 아래와 같다. // 6 : 골인지점 // 5 : 음식 // 4 : 오른쪽 통로 // 3 : 아래쪽 통로 // 2 : 왼쪽 통로 // 1 : 위쪽 통로 // // 검색 후 되돌리는 결과 값은 아래와 같다. // 0 : 이동 가능 경로 없음 // 1 : 이동 가능 경로 존재 // 2 : 스택 오류 int search( StackType *s, char (*m)[MAZE_SIZE], int r, int c ) { weight w[4]; // 이동 가능한 방향은 4곳이므로 4개의 가중치 정보를 저장하기 위한 배열 정의 int cnt = 0; // 이동 가능한 방향의 개수 저장용 변수 int i; // 반복용 if( c+1 <= 9 ) { // 현재 위치의 오른쪽이 배열의 열 인덱스보다 작거나 같으면 // 미로에서 현재 위치 오른쪽 칸(c+1)의 값이 백이 아니거나 이동했던 자표가 아니면 if( m[r][c+1] != SYMBWALL && m[r][c+1] != SYMBFOOTPRINT ) { w[cnt].row = r; // cnt가 가리키는 배열에 w[cnt].col = c+1; // 오른쪽 좌표 지정 // 가중치 계산 // 오른쪽 좌표가 콜인지점이면 cnt가 가리키는 배열의 가중치값을 6으로 지정 if( m[r][c+1] == GOAL ) w[cnt].weight = 6; else if( m[r][c+1]== FOOD ) w[cnt].weight = 5; // 음식이면 5를 지정 else if( m[r][c+1]== SYMBROAD ) w[cnt].weight = 4; // 통로이면 4를 지정 cnt++; // 배열에 저장된 자료의 개수에 1을 더함 } } // 현재 위치의 아래 칸이 이동 가능한 경로인지 검색하는 부분. 위 알고리즘과 동일하므로 이후 주석 생략함 if( r+1 <= 9 ) { if( m[r+1][c] != SYMBWALL && m[r+1][c] != SYMBFOOTPRINT ) { w[cnt].row = r+1; w[cnt].col = c; if( m[r+1][c] == GOAL ) w[cnt].weight = 6; else if( m[r+1][c]== FOOD ) w[cnt].weight = 5; else if( m[r+1][c]== SYMBROAD ) w[cnt].weight = 3; cnt++; } } // 현재 위치의 온쪽 칸이 이동 가능한 경로인지 검색하는 부분 if( c-1 >= 0 ) { if( m[r][c-1] != SYMBWALL && m[r][c-1] != SYMBFOOTPRINT ) { w[cnt].row = r; w[cnt].col = c-1; if( m[r][c-1] == GOAL ) w[cnt].weight = 6; else if( m[r][c-1]== FOOD ) w[cnt].weight = 5; else if( m[r][c-1]== SYMBROAD ) w[cnt].weight = 2; cnt++; } } // 현재 위치의 위쪽 칸이 이동 가능한 경로인지 검색하는 부분 if( r-1 >= 0 ) { if( m[r-1][c] != SYMBWALL && m[r-1][c] != SYMBFOOTPRINT ) { w[cnt].row = r-1; w[cnt].col = c; if( m[r-1][c] == GOAL ) w[cnt].weight = 6; else if( m[r-1][c]== FOOD ) w[cnt].weight = 5; else if( m[r-1][c]== SYMBROAD ) w[cnt].weight = 1; cnt++; } } // cnt가 0이면 이동 가능한 경로가 없다는 것이므로 0를 되돌리고 종료함 if( !cnt ) return 0; // 그렇지 않고 가능 경로가 있다면 가중치를 기준으로 오름차순 정렬 sort( w, cnt ); // 정렬된 자료를 순서되로 스택에 삽입함 for( i = 0 ; i < cnt ; i++ ) { // 스택에 좌표를 삽입하고 오류가 발생한 경우 2를 되돌리고 종료함 if( !push( s, w[i].row, w[i].col ) ) return 2; } // 스택에 성공적으로 자료를 삽입하고 1을 되돌림 return 1; } // 미로를 탐색하던 중 더이상 이동 가능한 경로가 없을 때 왔던 길을 되돌아가는 함수 // 미로데이터 이동 가능한 경로 저장용 스택 정보, 왔던 길을 되돌아가기 위한 스택정보, 총 이동 경로 저장용 스택정보, 현재 위치의 자표를 입력받ㄷ아 // 이동 가능한 경로의 좌표와 왔던 길 좌표를 스택에서 꺼내 거리를 계산한 결과 1이 될 때까지 진행한다. (되돌아 간다.) int go_back( char (*m)[MAZE_SIZE], StackType *s, StackType *r, StackType *t, int *cr, int *cc ) { int near_flag = 0; // 이동 가능한 경로와 현재 되돌아가는 위치가 인접(거리 1)해 있는지를 나타내는 변수 point *temp; // 스택에 저장된 자료를 임시로 저장하기 위한 변수 int d; // 거리 저장용 변수 // 되돌아 갈 좌표를 스택(r)으로부터 꺼냄, 만약 오류이면 0을 되돌림 if( !pop( r, cr, cc ) ) return 0; // 가능 경로좌표와 현재 위치가 인접하지 않은 동안 반복 while( !near_flag ) { // 거리 계산 : |이동 가능한 좌표의 row - 현재 좌표의 cr| * {이동 가능한 좌표의 col = 현재 좌표의 cc} d = abs( s->top->row - *cr ) + abs( s->top->col - *cc ); if( d == 1 ) { // 만약 거리가 1이면 near_flag = 1; // 인접 여부 변수 = 1로 설정 if( !push( r, *cr, *cc ) ) return 0; // 되돌가기 위한 스택 정보에 다시 현재 위치 삽입 } else { // 그렇지 않은 경우 system("clear"); // 화면을 지우고 print_maze( m ); // 미로를 출력한 후 // "생쥐가 현제 위치(좌표)에서 목적지까지 거리 x이므로 목적지(좌표)로 돌아갑니다"를 출력한다. printf("생쥐가 (%01d,%01d)에서 목적지까지의 거리 %d이므로(%01d,%01d)로 돌아갑니다\n", *cr, *cc, d, r->top->row, r->top->col ); getchar(); fflush( stdin ); if( !pop( r, cr, cc ) ) return 0; // 현재 위치를 이전 이동 좌표르 이동하기 위해 스택(r)에서 꺼냄 if( !push( t, *cr, *cc ) ) return 0; // 총 이동 경로를 저장하기 위해 현재위치를 스택(t)에 삽입 } } return 1; // 인접 위치로 되돌아 왔음을 알리기 위해 1을 되돌림 } // 스택의 두 자료 사이의 자료를 삭제하는 함수 void remove_inner( point *start, point *end ) { point *temp; // 임시 변수 temp = start->prev; // 시작 위치의 스택 자료를 임시 변수에 저장 while( temp != end ) { // 임시 변수가 가리키는 번지가 end와 같지 않은 동안 아래 문장 반복 start->prev = temp->prev; // start의 prev에 temp의 prev의 값을 지정하고 free( temp ); // temp가 가리키는 요소를 메모리에서 해제한다. temp = start->prev; // 그리고 temp에 start의 prev값을 지정하여 반복을 계속한다. } } // 불필요한 우회 경로를 검색하고 삭제하여 최단 경로를 지정하는 함수 // 전달된 스택 정보를 이용하여 top이 가리키는 자료에서 시작하여 스택에 저장된 모든 자료들간 거리를 계산하고 거리가 1인 자료가 검색되면 // 두 자료 사이의 자료(불필요한 자료)를 삭제한다. void search_shortest( StackType *r ) { point *key, *dest; // 비교를 위한 스택 자료의 번지를 지정하기 위한 변수 정의 int d; // 거리 계산용 key = r->top; // key에 top이 가리키는 자료의 번지 지정 while( key->prev ) { // key의 prev값이 NULL이 아닌 동안 아래 문장 반복 dest = key->prev; // dest에 key의 prev의 값을 지정 while( dest ) { // dest가 NULL 아닌 동안 아래 문장 반복 // key가 가리키는 자료의 좌표와 dest가 가리키는 자료의 좌표를 이용하여 거리를 계산함 d = abs( key->row - dest->row ) + abs( key->col - dest->col ); // 거리가 1과 같으면 key와 dest 사이의 모든 자료를 제거함 if( d == 1 ) remove_inner( key, dest ); dest = dest->prev; // dest에 dest의 priv값을 지정하여 반복을 계속함 } key = key->prev; // key에 key의 prev의 값을 지정하여 반복을 계속함 } } // 최단 경로를 미로 데이터에 표시하는 함수 // 전달받은 미로 데이터와 스택정보를 이용하여 스택에 저장된 좌표들을 꺼내 해당 좌표에 최단 경로임을 표시한다. void put_trace( char (*m)[MAZE_SIZE], StackType *r ) { point *temp; // 임시 변수 // 불필요한 우회경로를 삭제하여 최단경로 설정 search_shortest( r ); temp = r->top; // 스택의 top이 가리키는 번지를 temp에 지정 while( temp ) { // temp의 값이 NULL이 아닌 동안 반복 // temp의 row, col의 정보를 이용하여 미로 데이터 베열에 최단 경로 표시 m[temp->row][temp->col] = SYMBTRACEPRINT; temp = temp->prev; // temp에 temp의 prev의 값을 지정 } } // 화면에 스택의 자료를 이용하여 경로를 좌표의 쌍으로 표시하는 함수 void print_route( StackType *s ) { point *temp; int cnt = 0; printf( "골인\n" ); // 스택의 top위치의 자료가 먼저 표시되며 최근 자표가 골인 지점이므로 temp = s->top; // temp가 NULL 아닌 동안 temp의 row, col 좌표를 출력하고 temp의 prev의 값을 지정하여 좌표를 출락함 while( temp ) { printf( "(%01d,%01d) ", temp->row, temp->col ); if( ++cnt % 10 == 0 ) printf( "\n" ); temp = temp->prev; } // 모두 출력한 후 마지막 좌표가 출발점이므로 출발 지점을 표시 printf( "출발\n" ); } void main() { // 쥐의 초기 좌표와 현재 좌표를 지정하기 위한 변수 int init_mouse_r, init_mouse_c, current_mouse_r, current_mouse_c; // 프로그램 상태를 표시하기 위한 플래그 변수 int stop_flag=1, error_flag=0, search_flag; // 미로 데이터용 배열 char maze[MAZE_SIZE][MAZE_SIZE]; // 스택정보 저장용 변수 s : 가능 경로 저장용, r : 되돌아가기 위한 목족과 최단 경로 지정용, t : 총 이동 경로 저장용 StackType s = { NULL }, r = { NULL }, t= { NULL }; // 스톱 플래가가 0이 아닌 동안 반복 (게임 실행) while(stop_flag){ // 스택 초기화 init_stack( &s ); init_stack( &r ); init_stack( &t ); // 미로 데이터 초기화 init_maze(maze); // 미로 출력 system("clear"); print_maze(maze); // 생쥐의 초기 위치를 사용자로부터 입력받음 printf("생쥐의 초기 위치를 입력하세요 (ex:1,1) :"); error_flag=input_start_position(&init_mouse_r,&init_mouse_c,maze); // 입력 데이터가 정상이면 (게임 진행) if(error_flag==0) { // 생쥐의 초기 위치를 현재 위치로 지정 current_mouse_r = init_mouse_r; current_mouse_c = init_mouse_c; // 현재 위치를 스택 r, 스택 t에 삽입, 오류가 발생하면 프로그램 종료 if( !push( &r, current_mouse_r, current_mouse_c ) ) break; if( !push( &t, current_mouse_r, current_mouse_c ) ) break; // 현재 위치를 생쥐가 지나간 위치로 표시 maze[current_mouse_r][current_mouse_c]=SYMBFOOTPRINT; // 생쥐가 미로를 탐색하여 골인지점을 찾거나 오류가 발생할 때까지 반복 while(1) { // 현재 위치에서 이동 가능 경로 검색 search_flag = search( &s, maze, current_mouse_r, current_mouse_c); // 만약 현재 위치에서 이동 가능 경로가 더 이상 없다면 왔던 길로 되돌아 감 if( !search_flag ) { // 되돌아 가던 중 스택 오류가 발생하면 프로그램을 종료하도록 함 if( !go_back( maze, &s, &r, &t, ¤t_mouse_r, ¤t_mouse_c ) ) { stop_flag = 0; break; } } else if( search_flag == 2 ) { // 검색 도중 오류가 발생했다면 stop_flag = 0; // stop_flag를 0로 지정하여 프로그램을 종료하도록 함 break; } // 미로를 출력하고 다음 이동 가능 좌표로 이동하는 내용 출력 system("clear"); print_maze( maze ); print_stack( &s ); printf("생쥐가 (%01d,%01d)로 이동합니다\n", s.top->row, s.top->col ); getchar(); fflush( stdin ); // 다음 이동 가능 경로가 골인지점이면 if(GOAL==maze[s.top->row][s.top->col]) { // 스택 r, t에 현재 위치를 삽입하고 if( !push( &r, s.top->row, s.top->col ) ) break; if( !push( &t, s.top->row, s.top->col ) ) break; // 탐색 성공 메시지를 출력고 내부 반복문을 탈출함 printf("탐색 성공!!\n"); break; } else { // 골인지점이 아니라면 현재 위치를 다음 이동 가능한 좌표로 이동하고 if( !pop( &s, ¤t_mouse_r, ¤t_mouse_c ) ) break; // 이동된 현재 위치를 r, t에 삽입하고 if( !push( &r, current_mouse_r, current_mouse_c ) ) break; if( !push( &t, current_mouse_r, current_mouse_c ) ) break; // 현재 위치를 생쥐가 지나간 위치로 표시 maze[current_mouse_r][current_mouse_c]=SYMBFOOTPRINT; } } // stop_flag가 0이면 오류가 발생한 것이므로 프로그램 종료하도록 함 if( !stop_flag ) break; // 골인지점 탐색이 완료된 후 미로 데이터와 총 이동 경로를 출력함 system("clear"); printf( "\n쥐가 이동한 총 경로입니다.\n" ); print_route( &t ); print_maze( maze ); // 최단 경로를 미로 데이터에 표시하고 미로 데이터와 최단 경로를 출력함 put_trace( maze, &r ); printf( "\n쥐가 찾은 최단 경로입니다.\n" ); print_route( &r ); print_maze( maze ); } else if(error_flag==1) // 좌표를 잘 못 입력한 경우 메시지를 출력하고 종료함 printf("잘못된 입력입니다.\n"); else if(error_flag==2) // 입력된 자표가 미로에서 길이 아닌 경우 메시지를 출력하고 종료함 printf("길이 아닙니다.\n"); stop_flag=endsign(); } }