[cub3d](11)(.cub)파일 파싱하기



1️⃣ 목표

  • .cub형식의 파일의 내용에 따라 맵을 파싱하기 이전에 내용을 변수에 담는코드를 구현할 예정입니다.
  • 적절한 .cub파일은 다음의 내용을 포함해야 합니다. (보너스 구현은 일단 제외)
    1. NO(북), SO(남), WE(서), EA(동)의 xpm파일 경로
    2. F(바닥), C(천장)의 색깔
    3. 0, 1, N, S, E, W로 구성된 지도 배열
  • .cub파일는 다음과 같은 규칙으로 적혀 있어야합니다.
NO texture/NO.xpm
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파일을 읽어올지 큰틀을 잡고 중심이되는 파싱함수부터 구현하는 것이 좋습니다.
int parse_file(t_god *god, const char *cub_file_path)
{
    if문(cub파일 경로명이 올바른지 확인)
    if문(open함수를 이용해서 경로의파일을 불러오는 코드)

    while문(get_next_line함수를 이용하여 한줄씩 읽어옴)
    {
        if문(빈줄발견시 스킵)
        if문(각줄의 정보의 종류를 판별)
        if문(판별한 정보의 종류에 따라 변수에 저장)
    }
    return (SUCCESS);
}
  • 위의 메인 파싱 함수의 설계방향에 따라 다음 함수들을 구현해 나갈 계획입니다.
    1. is_cub_file(): .cub파일명 체크
    2. check_parse_type(): 각줄의 정보의 종류를 판별
    3. 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가지로 분류하여 처리합니다.
    1. 동, 서, 남, 북 정보저장
    2. 천장, 바닥 색깔저장
    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값이 들어오면 오류를 반환하기 때문에
if (s1 == NULL)
     s1 = ft_strdup("");

로 빈 공백값을 넣어줬습니다. 만약 if (s1 == NULL)
     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문법을 맞추기 위해서는 다음과 같이 기능이 합쳐진 함수를 만들 수 밖에 없는 것 같습니다.
    1. 에러처리 + 반환값
    2. 메모리해제 + 반환값
  • 또한 에러처리와 메모리해제를 구현을 맞춘 뒤 작성해주는 것보다 각각세부기능을 구현해 나가면서 처리해주며 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
  • 정상적으로 필요한 값만 저장됐습니다.




© 2021.02. by kirim

Powered by kkrim