[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