[so_long] 1. 맵 파싱하기
1. C언어 죽어
과제를 시작하며 일단 mlx라이브러리의 사용법부터 익혀보려고 하였으나...
대략 난감한 오류들이 뭉탱이로 쏟아져 나와서 나중에 다른 사람들의 도움을 받아보기로 하고 일단 맵 파싱을 담당하는 부분부터 완성하기로 했다.
먼저 맵에는 5가지의 요소들이 있는데,
1 == 벽. 플레이어는 벽으로 움직일 수 없다. 또한 맵은 반드시 벽으로 둘러싸여 있어야 한다.
0 == 빈 공간. 플레이어는 빈 공간을 자유롭게 움직일 수 있다.
C == 플레이어가 먹을 수 있는 수집품. 플레이어는 맵에 있는 수집품을 모두 수집하여야 한다.
E == 탈출구. 플레이어는 수집품을 모두 먹고 탈출구로 움직여 게임을 끝낼 수 있다.
P == 플레이어의 시작 위치. 벽에서 시작할 수 없음.
따라서 화면을 초기화 하기 전에 먼저 맵 파일을 읽어와 맵이 유효한가를 먼저 체크해야 할 것이다.
일단 맵 파일을 읽어오는 것부터 머리가 아파오기 시작했다.
처음에는
1. get_next_line 함수를 이용해 파일에서 한 줄씩 읽어옴
2. 읽어온 함수 2차원 배열에 저장
3. 해당 2차원 배열을 돌며 맵 유효성 체크
라는 식으로 접근하려고 했는데, 생각해보니 C언어에서는 2차원 배열을 malloc 하려면 [sizeof(char *) * 열의 개수] 만큼을 구해야 했고... 이는 곧 파일의 열의 개수를 알아야 한다는 뜻이었다... 그런데 그걸 알아오려면 어쨌든 한 번은 파일 전체를 읽어 열의 개수를 카운트해야 하므로 파일을 한번 열어서 끝까지 탐색하여 \n의 개수를 세고 어쩌고... 그리고 또 그걸 2차원 배열 malloc 한 후 파일을 다시 열어서 get_next_line으로 한 줄씩 읽어가며 유효성을 판단하고 잘못되면 싹 다 free 하고 어쩌고... 저쩌고...
생각이 왕창 복잡해지던 찰나에 그냥 연결리스트에 임시로 저장하는 방식으로 파싱을 하기로 했다. 그렇게 하면
1. get_next_line으로 한 줄 읽어오기
2. 해당 라인의 유효성 검사
3. 유효하다면 연결리스트에 저장, 아니라면 에러 출력 후 종료.
처럼 보다 직관적이고 머리가 덜 아픈 순서로 맵을 파싱 할 수 있었다.
맵을 받아온 후 추후에 보다 편리한 맵 조작을 위해 이걸 다시 2차원 배열로 옮겨주기만 하면 되지 않을까?라는 생각으로 접근해 보았다.
요렇게 해주기 위해 일단 맵을 파싱하며 정보를 담아줄 t_map_info 구조체를 만들어주었다.
typedef struct s_map_info
{
t_list *temp_map; //유효성 검사를 끝낸 맵이 담길 연결리스트
int height; // 맵의 높이
int width; // 맵의 너비
int number_of_collectibles; // 맵에 있는 수집품의 갯수
int number_of_exit; // 맵에 있는 출구의 갯수
int number_of_player; // 맵에 있는 플레이어의 갯수
} t_map_info;
2. 첫 째줄, 중간 줄, 마지막 줄로 나누기
맵이 유효한지 확인하기 위해서는 몇 가지 사항들을 체크해야 하는데,
1. 맵이 '1'로 완전히 둘러 싸여 있는가? -> 맵의 첫째 줄과 마지막 줄이 모두 1이며, 중간 줄의 시작과 마지막이 '1'인가?
2. 맵이 사각형인가? -> 맵의 첫째줄부터 마지막 줄 까지 너비가 모두 같은가?
3. 맵에 플레이어와 수집품과 탈출구가 모두 있는가? -> 'P'는 1개만, 'E'도 1개만, 'C'는 1개 이상 있는가?
와 같은 사항을 체크하여야 했다.
순간 뇌까리가 아찔했으나 곰곰이 생각해보니 첫째줄과 마지막 줄을 검사하는 함수와, 중간줄을 검사하는 함수를 나누어 작성한다면 직관적이고 쉽게 문제를 해결할 수 있겠다는 생각이 들었다.
따라서 다음과 같이 3개의 함수들을 작성해주었다.
int check_map_first_line(char *line, t_map_info *map_info)
{
int i;
i = 0;
while (line[i] != '\0')
{
if (line[i] != '1') // 하나라도 1이 아니면 바로 에러 출력 후 종료
error_handler("Wrong map format.");
i++;
}
if (i == 0) // 파일이 비어있을 경우도 에러 출력 후 종료
error_handler("Wrong map format.");
else
{
map_info->width = i; // 맵의 너비를 저장해주고
map_info->temp_map = ft_lstnew(line); // 연결리스트 초기화 하여 저장
if (map_info->temp_map == NULL)
error_handler("Failed to create temp_map.");
}
return (0);
}
int check_map_middle_line(char *line, t_map_info *map_info)
{
int i;
t_list *tmp;
i = 0;
while (line[i] != '\0')
{
if (i == 0 && line[i] != '1') // 중간라인에서는 첫번째가 '1'인지 확인
error_handler("Wrong map format.");
if (line[i] == 'E')
map_info->number_of_exit++;
else if (line[i] == 'C')
map_info->number_of_collectibles++;
else if (line[i] == 'P')
map_info->number_of_player++; // 그 외에는 각 경우에 따라 +1
else if (i != 0 && !(line[i] == '0' || line[i] == '1'))
error_handler("Wrong map format."); // 5가지 문자 외에 다른 문자가 있으면 에러출력 후 종료
i++;
}
if (line[i - 1] != '1' || i != map_info->width)
error_handler("Wrong map format."); // 마지막 글자가 1이 아니거나 맵의 너비가 다를경우 에러 출력 후 종료
tmp = ft_lstnew(line);
if (tmp == NULL)
error_handler("Failed to create temp_map.");
ft_lstadd_back(&map_info->temp_map, tmp); // line 연결리스트에 추가
return (0);
}
int check_map_last_line(char *line, t_map_info *map_info)
{
int i;
t_list *tmp;
i = 0;
while (line[i] != '\0')
{
if (line[i] != '1')
error_handler("Wrong map format.");
i++;
}
if (i != map_info->width)
error_handler("Wrong map format.");
tmp = ft_lstnew(line);
ft_lstadd_back(&map_info->temp_map, tmp);
return (0);
}
그리고 최종적으로 맵을 검사하여 p, c, e의 갯수를 체크하는 함수도 만들어주었다.
int check_for_last(t_map_info *map_info)
{
if (map_info->number_of_player > 1 || // 플레이어의 갯수가 1개보다 많거나
map_info->number_of_collectibles < 1 || //수집품이 0개거나
map_info->number_of_exit > 1) // 탈출구의 갯수가 1개보다 많으면 에러
error_handler("Wrong map format.");
map_info->height = ft_lstsize(map_info->temp_map); // 마지막으로 리스트의 사이즈 == 맵의 높이
// info에 저장
return (0);
}
뭔가 중복되는 라인이 많아 보이지만 나는 중복보다 복잡한 게 더 싫다... 가독성이 극악으로 변하고 나중에 설명할 때 너무 힘들어지기 때문에... ㅠㅠ
이렇게 작성된 함수들을 이용해 check_map_validity라는 함수를 만들어 최종적으로 완성된 맵을 연결 리스트에 저장할 수 있도록 해줬다.
static int check_map_validity(int map_fd, t_map_info *map_info)
{
char *line;
int gnl_check;
int first_ln_done;
first_ln_done = 0;
gnl_check = get_next_line(map_fd, &line);
while (gnl_check > 0)
{
if (!first_ln_done)
{
check_map_first_line(line, map_info);
first_ln_done = 1;
}
else
check_map_middle_line(line, map_info);
gnl_check = get_next_line(map_fd, &line);
}
if (gnl_check < 0)
error_handler("Failed to read from map file");
check_map_last_line(line, map_info);
check_for_last(map_info);
return (0);
}
함수 실행이 정상적으로 끝나면 map_info->tmp_map에 유효성 검사가 끝난 맵이 연결리스트 형태로 저장되게 된다. 추후에 화면에 맵을 그리기 위해 사용할때는 2차원 배열으로 옮겨서 사용하려고 계획중이다... 일단은... ㅠㅠ
요 다음에는 mlx라이브러리를 사용하는 법부터 차근차근 알아나가야겠다.