[cub3d](11)(.cub)파일 파싱하기
1️⃣ 목표
- .cub형식의 파일의 내용에 따라 맵을 파싱하기 이전에
내용을 변수에 담는 코드를 구현할 예정입니다. 적절한 .cub파일 은 다음의 내용을 포함해야 합니다. (보너스 구현은 일단 제외)- NO(북), SO(남), WE(서), EA(동)의
xpm파일 경로 - F(바닥), C(천장)의
색깔 - 0, 1, N, S, E, W로 구성된
지도 배열
- NO(북), SO(남), WE(서), EA(동)의
.cub파일 는 다음과 같은 규칙으로 적혀 있어야합니다.
SO texture/SO.xpm
WE texture/WE.xpm
EA texture/EA.xpm
F 0,100,222
C 97,144,0
1111111111111111111111111
1000000111110000000000001
1011000001110000010000001
1001001100000000000000001
1110001110110000011100001
100000N000110000011101111
1000000000000000110101001
1100000111010101111101111
1111111111111111111111111
2️⃣ 파싱관련 구조체 선언
typedef struct s_texture
{
char *tex_path;
int *texture;
double width;
double height;
} t_texture;
typedef struct s_parse
{
t_texture tex[TEXTURE_COUNT];
int floor_color;
int ceiling_color;
char *temp_map;
char **map;
int row;
int col;
} t_parse;
t_parse
구조체에 맵 좌표(2차원 배열형식), 천장, 바닥색, texture관련 구조체변수들을 담았습니다.t_texture
구조체는 xpm파일을 mlx내장함수로 로딩하는데 필요한 변수들이 담겨져있습니다.
3️⃣ 메인 파싱 함수
- 파싱함수는
메모리 누수 관리도 해주어야하며 다소 복잡합니다. - 그렇기 때문에 세부적인 함수부터 구현해나가는 것보다 어떤식으로 .cub파일을 읽어올지
큰틀을 잡고 중심이되는 파싱함수 부터 구현하는 것이 좋습니다.
{
if문(cub파일 경로명이 올바른지 확인)
if문(open함수를 이용해서 경로의파일을 불러오는 코드)
while문(get_next_line함수를 이용하여 한줄씩 읽어옴)
{
if문(빈줄발견시 스킵)
if문(각줄의 정보의 종류를 판별)
if문(판별한 정보의 종류에 따라 변수에 저장)
}
return (SUCCESS);
}
- 위의
메인 파싱 함수 의 설계방향에 따라 다음 함수들을 구현해 나갈 계획입니다.is_cub_file()
: .cub파일명 체크check_parse_type()
: 각줄의 정보의 종류를 판별do_parsing()
: 판별 정보를 토대로 변수에 대입
4️⃣ is_cub_file()함수 구현
static int is_cub_file(const char *cub_file_path)
{
int file_len;
int is_same;
file_len = ft_strlen(cub_file_path);
if (ft_strncmp(cub_file_path + file_len - 4, ".cub", 4) == 0)
is_same = TRUE;
else
is_same = FALSE;
return (is_same);
}
- 이전 과제인
libft
때 구현한 함수들을 사용하면 쉽게 구현이 가능합니다. - 끝의 4자리가
".cub" 이 맞는지 확인합니다. - 만약
FALSE
가 반환될 경우 서브젝트에 나온 내용대로 `ERROR\n”를 출력해주어야합니다. 이러한 오류출력은 앞으로 여러번 쓰일 것이기 때문에 다음과 같이 따로 함수로 구현하였습니다.
int exit_error(t_god *god, int exit_code, const char *message)
{
write(2, "ERROR\n", 6);
if (message)
{
write(2, message, ft_strlen(message));
write(2, "\n", 1);
}
exit_cub3d(god, exit_code);
return (exit_code);
}
5️⃣ check_parse_type()함수 구현
static int check_parse_type(char *line)
{
if (line[0] == 'N' && line[1] == 'O')
return (T_NO);
else if (line[0] == 'S' && line[1] == 'O')
return (T_SO);
else if (line[0] == 'W' && line[1] == 'E')
return (T_WE);
else if (line[0] == 'E' && line[1] == 'A')
return (T_EA);
else if (line[0] == 'F' && line[1] == ' ')
return (T_FLOOR);
else if (line[0] == 'C' && line[1] == ' ')
return (T_CEIL);
else if (is_map_valid(line))
return (T_MAP);
return (T_ERROR);
}
strcmp
함수를 이용할까했지만 두글자만 체크하기 때문에 위와같이 코드를 구현하였습니다.- 각각의 return(반환값)들은 다음과 같이 헤더파일에 정의해두었습니다.
/* texture */
# define T_ERROR (-1)
# define T_NO (0)
# define T_SO (1)
# define T_WE (2)
# define T_EA (3)
# define T_FLOOR (4)
# define T_CEIL (5)
# define T_MAP (6)
# define TEXTURE_COUNT (4)
- map(지도)의 정보같은 경우 각각의 요소를 체크해주었습니다.
int is_map_valid(char *line)
{
int i;
i = 0;
while (line[i])
{
if (find_char("01NSEW ", line[i]) == FALSE)
return (FALSE);
i++;
}
return (TRUE);
}
0, 1, N, S, E, W
값으로만 이루어져있는지 체크하는 함수입니다.
6️⃣ do_parsing()함수 구현
- 이전까지는 단순히 읽기만 하는 것이기 때문에 간단했습니다.
- 하지만 읽어온정보를 토대로 쓰기를 하는
do_parsing()
함수를 구현하는 것은 만만치 않았습니다. (사실 노가다..)
(1) do_parsing() 최종구현
int do_parsing(t_parse *parse, int g_ret, int type, char *line)
{
if (type >= T_NO && type <= T_EA)
{
if (parse->tex[type].tex_path || !(parse->tex[type].tex_path = parse_path_malloc(line)))
return (free_memory_return(line, ERROR));
}
else if (type == T_CEIL || type == T_FLOOR)
{
if ((type == T_CEIL && (parse->ceiling_color = parse_color(line)) == T_ERROR)
|| (type == T_FLOOR && (parse->floor_color = parse_color(line)) == T_ERROR))
return (free_memory_return(line, ERROR));
}
else
{
parse->temp_map = update_map_malloc(parse->temp_map, line);
if (g_ret == 0 && parse_map(parse) == ERROR)
return (free_memory_return(line, ERROR));
}
return (free_memory_return(line, SUCCESS));
}
- 위의 코드는
do_parsing함수
의 최종구현형태입니다. - 크게 3가지로 분류하여 처리합니다.
- 동, 서, 남, 북 정보저장
- 천장, 바닥 색깔저장
- 맵 저장
(2) 동, 서, 남, 북 정보저장함수 구현
char *parse_path_malloc(char *line)
{
char *temp_malloc;
while (is_upper(*line) == TRUE)
line++;
while (is_space(*line) == TRUE)
line++;
if((temp_malloc = ft_strdup(line)) == NULL)
return (NULL);
return (temp_malloc);
}
- xpm파일경로에 대한 정보는
대문자 로 된 타입명과 공백다음에 온다는 것을 이용하여 구현했습니다. - 함수호출 뒤에
메모리해제 가 필요하므로 함수명 뒤에malloc
을 붙여줬습니다.
(3) 천장, 바닥 색깔저장함수 구현
int parse_color(char *line)
{
int i;
int x;
int result;
int color;
char **color_malloc;
while (is_upper(*line) == TRUE)
line++;
while (is_space(*line) == TRUE)
line++;
if ((color_malloc = ft_split(line, ',')) == NULL)
return (T_ERROR);
result = 0;
i = -1;
while (++i < 3)
{
result *= 256;
x = -1;
color = 0;
while (color_malloc[i][++x] != '\0')
{
if (ft_isdigit(color_malloc[i][x]) == FALSE)
return (T_ERROR);
color = color * 10 + color_malloc[i][x] - '0';
}
result += color;
}
free_array_memory((void**)color_malloc, 3);
return (result);
}
- 색깔 정보 또한 처음 대문자들과 공백을 넘겨준 뒤 입력을 받게 했습니다.
- 먼저
ft_split함수
를 이용하여r(빨강) , g(초록), b(파랑)을 분류하여 임시 2차원 포인터에 정보를 저장합니다. - 그 뒤 각각의
r,g,b
값이 int형 메모리중8비트 씩의 메모리를 차지함을 이용하여 연산을 통해 보정한 뒤 반환해주도록 했습니다. split함수
는 2차원으로 동적메모리를 할당시켜주므로 메모리해제할때 주의해서 해야합니다.
(4) 맵 저장함수 구현
int parse_map(t_parse *parse)
{
int row;
int col;
if((parse->map = ft_split(parse->temp_map, '\n')) == NULL)
return (free_memory_return(parse->temp_map, ERROR));
row = -1;
col = 0;
while (parse->map[++row] != NULL)
if (ft_strlen(parse->map[row]) > col)
col = ft_strlen(parse->map[row]);
parse->row = row;
parse->col = col;
return (free_memory_return(parse->temp_map, SUCCESS));
}
ft_split함수
를 이용하여 맵을 저장해줍니다.- 그 뒤 저장된 맵을
while문
을 통해 탐색하면서row, col
값도 저장해줍니다. - 하지만
지도를 한줄씩 저장하는 것은 비효율적 입니다. 우선적으로 모든 지도를 읽어온 뒤 저장을 시작하면 됩니다.get_next_line함수
는 파일을 모두 읽었을 경우0(EOF)
값을 반환함을 이용하여 지도 저장함수를 호출하면 됩니다. - 먼저
0(EOF)
함수가 반환될때까지맵의 정보 를 임시적으로 저장하는 함수를 구현해야 합니다.
char *update_map_malloc(char *s1, char *s2)
{
char *temp;
char *result;
if (s1 == NULL)
s1 = ft_strdup("");
temp = ft_strjoin(s1, "\n");
result = ft_strjoin(temp, s2);
free_memory(s1);
free_memory(temp);
return (result);
}
ft_strdup함수
를 이용하여 읽어온 지도 한줄씩을 누적해서 저장해나가는 함수입니다.- 이 함수를 호출한 뒤
메모리 해제 가 필요하기 때문에 함수명에_malloc
을 붙여줬습니다. ft_strdup함수
특성상 매개변수로NULL
값이 들어오면 오류를 반환하기 때문에
s1 = ft_strdup("");
로 빈 공백값을 넣어줬습니다. 만약
s1 = "";
와 같이 지정하지않고 굳이 메모리할당(ft_strdup)을 해서 ""
값을 지정한 이유는 뒤쪽에 구현한 free함수
에서 오류가 남을 방지해주기 위해서 입니다. (free함수를 호출하기 이전에 NULL
을 확인하는 조건문은 도움이 되지 않았습니다.)
- 이 함수를 이용하여 지도를 한줄씩 차곡차곡 쌓아간 뒤 마지막에
지도 저장함수
를 호출하면 됩니다.
7️⃣ 최종 본 parse함수 구현
int parse_file(t_god *god, const char *cub_file_path)
{
int fd;
char *line;
int gnl_ret;
int parse_type;
if (!(is_cub_file(cub_file_path)))
return (exit_error(god, ERROR, "NOT \".cub\" FILE"));
if ((fd = open(cub_file_path, O_RDONLY)) < 0)
return (exit_error(god, ERROR, "WRONG FILE PATH OR CAN'T OPEN!"));
gnl_ret = 1;
while ((gnl_ret = get_next_line(fd, &line)) > 0)
{
if (line[0] == '\0')
continue;
if ((parse_type = check_parse_type(line)) == T_ERROR)
return (exit_error(god, ERROR, "WRONG PARSE TYPE"));
if (do_parsing(&god->parse, gnl_ret, parse_type, line) == ERROR)
return (ERROR);
}
do_parsing(&god->parse, gnl_ret, parse_type, line);
close(fd);
return (SUCCESS);
}
- 이번에 구현한 파싱할 데이터를 저장하는 함수의 중요한점은
메모리해제 와적절한 오류가 출력 입니다. - 여러가지 선배들의 코드들을 참고하여 구현하였지만 42seoul문법을 맞추기 위해서는 다음과 같이 기능이 합쳐진 함수를 만들 수 밖에 없는 것 같습니다.
- 에러처리 + 반환값
- 메모리해제 + 반환값
- 또한 에러처리와 메모리해제를 구현을 맞춘 뒤 작성해주는 것보다 각각세부기능을 구현해 나가면서 처리해주며
main함수 로 테스트해가면서 구현해 나가는 것이 효율적일 것 같습니다.
8️⃣ 정상적으로 데이터가 저장됐는지 확인(main 함수)
int main(void)
{
t_god god;
ft_memset(&god, 0, sizeof(t_god));
parse_file(&god, "./map/test.cub");
for (int i = 0; i < TEXTURE_COUNT; i++)
printf("%s\n", god.parse.tex[i].tex_path);
printf("\n");
printf("ceilling color: %#x\n", god.parse.ceiling_color);
printf("floor color: %#x\n", god.parse.floor_color);
printf("row: %d\ncol: %d\n", god.parse.row, god.parse.col);
for (int y = 0; y < god.parse.row; y++)
{
for (int x = 0; x < god.parse.col; x++)
printf("%c ", god.parse.map[y][x]);
printf("\n");
}
}
texture/NO.xpm
texture/SO.xpm
texture/WE.xpm
texture/EA.xpm
ceilling color: 0x619000
floor color: 0x64de
row: 9
col: 25
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 0 0 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 1
1 0 1 1 0 0 0 0 0 1 1 1 0 0 0 0 0 1 0 0 0 0 0 0 1
1 0 0 1 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
1 1 1 0 0 0 1 1 1 0 1 1 0 0 0 0 0 1 1 1 0 0 0 0 1
1 0 0 0 0 0 N 0 0 0 1 1 0 0 0 0 0 1 1 1 0 1 1 1 1
1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 1 0 1 0 0 1
1 1 0 0 0 0 0 1 1 1 0 1 0 1 0 1 1 1 1 1 0 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
- 정상적으로 필요한 값만 저장됐습니다.