42 June - 5. So_Long (구현)

수현·2023년 6월 28일
0

42Seoul

목록 보기
7/8

📘 구현

📌 순서

  • (1) Window 생성
  • (2) map 생성
    • window에 이미지 넣기
    • map.ber 파일 읽기
    • map 정보에 맞게 이미지 변환
    • img 크기만큼 window 조정
  • (3) 키 입력 이벤트
    • 키 입력 받고 출력
    • 키 입력 이벤트 받고 동작
    • 키 이동 횟수 출력
    • map 규칙 적용
    • 종료 버튼 누르면 신호 수신 후 동작
  • (4) map 규칙 체크 (에러 처리)
    • 지도 직사각형 아닌 경우
    • 지도 벽에 둘러싸여 있지 않은 경우
    • 지도 출구/시작지점/수집품 없는 경우
    • 지도 지정되지 않은 문자가 포함된 경우
    • 비어있는 맵이 포함된 경우
    • 출구 뒤에 아이템이 있는 경우
    • map.ber 파일 확장자 틀린 경우
  • (5) map 경로 확인

📌 컴파일 주의

  • iMac : cc -lmlx -framework OpenGL -framework AppKit *.c

  • MacBook (M2) : cc -L./mlx -lmlx -framework OpenGL -framework AppKit *.c

📌 사전 준비

  • subject의 minilibx_opengl.tgz 폴더 다운로드

  • 과제 폴더에 mlx 이름으로 변경하여 추가

  • Makefile 수정

%.o:%.c so_long.h
	cc -Wall -Wextra -Werror -c $< -o $@

$(NAME) : 	$(OBJS)
			make -C ./mlx/
			cc -o so_long $(OBJS) -L./mlx -lmlx -framework OpenGL -framework AppKit

📌 1. Window 생성

사용 함수

  • void* mlx_init(void)
    • SW와 OS의 디스플레이 연결
  • void* mlx_new_window (mlx 포인터, x 사이즈, y 사이즈, window title명)
    • 디스플레이에 새로운 window 띄우는 함수
  • int mlx_loop(void* mlx 포인터)
    • 새로운 window에서 키보드와 마우스 입력 대기
#include "./mlx/mlx.h"

int	main(void)
{
	void *mlx;
	void *win;

	mlx = mlx_init();
	win = mlx_new_window(mlx, 500, 500, "so_long");
	mlx_loop(mlx);
}

📌 2. map 생성

(1) window에 이미지 넣기

사용 함수

  • void* mlx_xpm_file_to_image(mlx 포인터, 파일이름, 너비, 높이)
    • 이미지 가져와서 메모리에 올리고, 메모리 주소 반환
  • int mlx_put_image_to_window(mlx 포인터, window 포인터, image 포인터, x좌표, y좌표)
    • 이미지 포인터 받아서, 윈도우 안에 좌표를 지정하여 이미지 띄움
      -png이미지 파일 변환시 64픽셀로 고정함
#include "so_long.h"

void	init(t_param *p)
{
	p->play_x = 3;
	p->play_y = 4;
	p->mlx = mlx_init();
	p->win = mlx_new_window(p->mlx, 500, 500, "so_long");
	p->img_coin = mlx_xpm_file_to_image(p->mlx, "./imgs/coin.xpm", &p->img_x, &p->img_y);
	p->img_exit = mlx_xpm_file_to_image(p->mlx, "./imgs/exit.xpm", &p->img_x, &p->img_y);
	p->img_map = mlx_xpm_file_to_image(p->mlx, "./imgs/map.xpm", &p->img_x, &p->img_y);
	p->img_mario = mlx_xpm_file_to_image(p->mlx, "./imgs/mario.xpm", &p->img_x, &p->img_y);
	p->img_wall = mlx_xpm_file_to_image(p->mlx, "./imgs/wall.xpm", &p->img_x, &p->img_y);
}

int	main(void)
{
	t_param		p;

	init(&p);
	mlx_put_image_to_window(p.mlx, p.win, p.img_wall, 0, 0);
	mlx_put_image_to_window(p.mlx, p.win, p.img_map, 64, 0);
	mlx_put_image_to_window(p.mlx, p.win, p.img_coin, 128, 0);
	mlx_put_image_to_window(p.mlx, p.win, p.img_exit, 0, 128);
	mlx_put_image_to_window(p.mlx, p.win, p.img_mario, 0, 64);
	mlx_loop(p.mlx);
}

(2) map.ber 파일 읽기

  • map_read
    • open 함수 이용하여 파일 열기
    • gnl 이용하여 1줄씩 line에 저장
    • 해당 부분에서 line의 수를 return 하려고 했으나 main에서 받으면 계속 segmentation fault 오류 떠서 실패
  • map_strjoin
    • new를 malloc해서 연결리스트에 저장
int	map_strjoin(t_param *p, char *str)
{
	t_list	*cur;
	t_list	*new;

	cur = p->map;
	new = malloc(sizeof(t_list));
	if (!new) {
		map_free(p);
        return (1);
	}
	new->data = ft_strdup(str);
	new->next = NULL;
	if (cur == NULL)
		p->map = new;
	else {
		while (cur->next != NULL)
			cur = cur->next;
		cur->next = new;
	}
	return (0);
}

int	map_read(t_param *p, char *file)
{
	int		fd;
	char	*line;

	fd = open(file, O_RDONLY);
	if (fd <= 0)
		return (1);
	line = get_next_line(fd);
	ft_printf("line = %s\n", line);
	while (line != NULL)
	{
		if (map_strjoin(p, line))
			return (1);
		free(line);
		line = get_next_line(fd);
		ft_printf("line = %s\n", line);
	}
	free(line);
	return (0);
}

void    map_free(t_param *p)
{
    t_list  *cur;
    t_list  *new;

    cur = p->map;
    while (cur)
    {
        new = cur->next;
        free (cur->data);
        free (cur);
        cur = new;
    }
    free (cur);
}

int	main(void)
{
	t_param		p;

	init(&p);
	map_read(&p, "map/map.ber");
	mlx_put_image_to_window(p.mlx, p.win, p.img_wall, 0, 0);
	mlx_put_image_to_window(p.mlx, p.win, p.img_map, 64, 0);
	mlx_put_image_to_window(p.mlx, p.win, p.img_coin, 128, 0);
	mlx_put_image_to_window(p.mlx, p.win, p.img_exit, 0, 128);
	mlx_put_image_to_window(p.mlx, p.win, p.img_mario, 0, 64);
	mlx_loop(p.mlx);
}

(3) map 정보에 맞게 이미지 변환

void    map_set(t_param *p)
{
    t_list  *cur;
    char    *line;
    int     x;
    int     y;

    x = 0;
    y = 0;
    cur = p->map;
    init_player(p);
    mlx_clear_window(p->mlx, p->win);
    while (cur)
    {
        line = cur->data;
        while (*line)
        {
            map_imgs(p, *line, x, y);
            line++;
            x += 64;
        }
        x = 0;
        y += 64;
        cur = cur->next;
    }
}

void    map_imgs(t_param *p, char c, int x, int y)
{
    mlx_put_image_to_window(p->mlx, p->win, p->img_map, x, y);
    if (c == '1')
        mlx_put_image_to_window(p->mlx, p->win, p->img_wall, x, y);
    else if (c == '0')
        mlx_put_image_to_window(p->mlx, p->win, p->img_map, x, y);
    else if (c == 'C')
        mlx_put_image_to_window(p->mlx, p->win, p->img_coin, x, y);
    else if (c == 'E')
        mlx_put_image_to_window(p->mlx, p->win, p->img_exit, x, y);
    else if (c == 'P')
        mlx_put_image_to_window(p->mlx, p->win, p->img_mario, x, y);
}

void    init_player(t_param *p)
{
    t_list  *cur;
    char    *line;

    cur = p->map;
    while (cur)
    {
        line = cur->data;
        while (*line)
        {
            if (*line++ == 'P')
                return ;
            p->play_x++;
        }
        p->play_x = 0;
        p->play_y++;
        cur = cur->next;
    }
}


(수정) 뒤에 map 배경 모두 설정한 후 이미지 출력

(4) img 크기만큼 window 조정

int	main(void)
{
	t_param		p;

	init(&p);
	if (map_read(&p, "map/map.ber"))
	{
		ft_printf("Error : Map Read Fail");
		map_free(&p);
		exit(0);
		return (0);
	}
	map_check(&p);
	p.win = mlx_new_window(p.mlx, p.win_x, p.win_y, "so_long");
	map_set(&p);
	mlx_key_hook(p.win, &key_press, &p);
	mlx_hook(p.win, RED, 0, &map_red, &p);
	mlx_loop(p.mlx);
	return (0);
}

📌 3. 키 입력 이벤트

사용 함수

  • int mlx_hook(win 포인터, event, mask, 호출할 함수 포인터, 함수에 전달할 파라미터)
    • event : X11 event
    • mask : mac에서는 미사용으로 0 입력

(1) 키 입력 받고 출력

  • WASD : 상하좌우
  • ESC좌측 빨간 버튼 : 종료
#include "./mlx/mlx.h"
#include "./ft_printf/ft_printf.h"

# define	KEY_PRESS	2
# define	KEY_RELEASE	3
# define	KEY_W		13
# define	KEY_A		0
# define	KEY_S		1
# define	KEY_D		2
# define	KEY_ESC		53
# define	KEY_RED		17

typedef struct s_param
{
	int		x;
	int		y;
} t_param;

void	init(t_param *p)
{
	p->x = 3;
	p->y = 4;
}

int	key_press(int key, t_param *p)
{
	if (key == KEY_W)
		key_W(p);
	else if (key == KEY_A)
		key_A(p);
	else if (key == KEY_S)
		key_S(p);
	else if (key == KEY_D)
		key_D(p);
	else if (key == KEY_ESC)
		exit(0);
	else if (key == KEY_RED)
		exit(0);
	ft_printf("x : %d, y : %d\n", p->x, p->y);
	return (0);
}
void	key_W(t_param *p)
{
	p->y++;
}

void	key_A(t_param *p)
{
	p->x--;
}

void	key_S(t_param *p)
{
	p->y--;
}

void	key_D(t_param *p)
{
	p->x++;
}

int	main(void)
{
	void 		*mlx;
	void 		*win;
	t_param		p;

	init(&p);
	mlx = mlx_init();
	win = mlx_new_window(mlx, 500, 500, "so_long");
	mlx_hook(win, KEY_RELEASE, 0, &key_press, &p);
	mlx_loop(mlx);
	if (win == 0)
		return(0);
}

(2) 키 입력 이벤트 받고 동작 / 키 이동 횟수 출력

  • 현재 : line의 'P' ➡ '0'으로 변경
  • 이동 : line의 그 외 ➡ 'P'으로 변경
  • 이동 횟수 : move 저장
// 마리오(P)가 지나가면 벽(1), 코인(C), 출구(E) 모두 맵(0)으로 바꿈 
#include "so_long.h"

int	key_press(int key, t_param *p)
{
	if (key == W)
		key_W(p);
	else if (key == A)
        key_A(p);
	else if (key == S)
        key_S(p);
	else if (key == D)
        key_D(p);
	else if (key == ESC)
        map_esc(p);
    ft_printf("move = %d\n", p->move);
    map_set(p);
	return (0);
}

void	key_A(t_param *p)
{
    int     y;
    char    *line;
    t_list  *cur;

    cur = p->map;
    y = p->play_y;
    while (y--)
        cur = cur->next;
    line = cur->data;
    line[p->play_x - 1] = 'P';
    line[p->play_x] = '0';
    init_player(p);
    p->move++;
}

(3) map 규칙 적용

  • wall일 경우 : 이동 금지
  • exit일 경우 : coin = 0이면 종료
  • coin일 경우 : coin-- 후 이동
int	key_press(int key, t_param *p)
{
	if (key == W)
		key_up(p);
	else if (key == A)
		key_left(p);
	else if (key == S)
		key_down(p);
	else if (key == D)
		key_right(p);
	else if (key == ESC)
		map_esc(p);
	else
		return (1);
	ft_printf("move = %d\n", p->move);
	map_set(p);
	return (0);
}

void	key_up(t_param *p)
{
	int			y;
	char		*line;
	t_list		*cur;
	char		*cur_prev;

	cur = p->map;
	y = p->play_y;
	while (y-- != 1)
		cur = cur->next;
	cur_prev = cur->data;
	cur = cur->next;
	line = cur->data;
	if (cur_prev[p->play_x] == '1')
		return ;
	else if (cur_prev[p->play_x] == 'E')
		if (map_exit(p))
			return ;
	if (cur_prev[p->play_x] == 'C')
		p->coin--;
	cur_prev[p->play_x] = 'P';
	line[p->play_x] = '0';
	init_player(p);
	p->move++;
}

void	key_left(t_param *p)
{
	int			y;
	char		*line;
	t_list		*cur;

	cur = p->map;
	y = p->play_y;
	while (y--)
		cur = cur->next;
	line = cur->data;
	if (line[p->play_x - 1] == '1')
		return ;
	else if (line[p->play_x - 1] == 'E')
		if (map_exit(p))
			return ;
	if (line[p->play_x - 1] == 'C')
		p->coin--;
	line[p->play_x - 1] = 'P';
	line[p->play_x] = '0';
	init_player(p);
	p->move++;
}

void	key_down(t_param *p)
{
	int			y;
	char		*line;
	t_list		*cur;
	char		*cur_next;

	cur = p->map;
	y = p->play_y;
	while (y--)
		cur = cur->next;
	line = cur->data;
	cur = cur->next;
	cur_next = cur->data;
	if (cur_next[p->play_x] == '1')
		return ;
	else if (cur_next[p->play_x] == 'E')
		if (map_exit(p))
			return ;
	if (cur_next[p->play_x] == 'C')
		p->coin--;
	line[p->play_x] = '0';
	cur_next[p->play_x] = 'P';
	init_player(p);
	p->move++;
}

void	key_right(t_param *p)
{
	int			y;
	char		*line;
	t_list		*cur;

	cur = p->map;
	y = p->play_y;
	while (y--)
		cur = cur->next;
	line = cur->data;
	if (line[p->play_x + 1] == '1')
		return ;
	else if (line[p->play_x + 1] == 'E')
		if (map_exit(p))
			return ;
	if (line[p->play_x + 1] == 'C')
		p->coin--;
	line[p->play_x + 1] = 'P';
	line[p->play_x] = '0';
	init_player(p);
	p->move++;
}

(4) 종료 버튼 누르면 신호 수신 후 동작

// RED 버튼 누르면 segmentation fault 오류 발생
mlx_hook(p.win, RED, 0, &map_exit, &p);

int	map_exit(int key, t_param *p)
{
	if (key == ESC)
		ft_printf("Exit : ESC");
	else
		ft_printf("Exit : Red Button");
	map_free(p);
	exit(0);
	return (0);
}

// 수정 완료
mlx_hook(p.win, RED, 0, &map_red, &p);

int	map_esc(t_param *p)
{
	ft_printf("Exit : ESC");
	map_free(p);
	exit(0);
	return (0);
}

int	map_red(t_param *p)
{
	ft_printf("Exit : Red Button");
	map_free(p);
	exit(0);
	return (0);
}

📌 4. map 규칙 체크

(1) 지도 직사각형 아닌 경우

  • map.ber 파일에 비어있는 줄이 포함된 경우
int	map_check(t_param *p)
{
	t_list	*cur;
	int		count;

	cur = p->map;
	count = map_check_line(p);
	if (count == -1)
		return (1);
	p->win_x = p->img_x * (ft_strlen(cur->data) - 1);
	p->win_y = p->img_y * count;
	if (map_check_wall(p, count) == -1)
	{
		ft_printf("Error : (No Wall Enclosed) ");
		return (1);
	}
	if (map_check_count(p) == -1)
	{
		ft_printf("Error : (Invalid Start / Exit / Collect) ");
		return (1);
	}
	return (0);
}

int	map_check_line(t_param *p)
{
	t_list	*cur;
	int		len;
	int		len_next;
	int		count;

	cur = p->map;
	len = ft_strlen(cur->data);
	count = 0;
	while (cur)
	{
		len_next = ft_strlen(cur->data);
		if (len != len_next)
		{
			ft_printf("Error : (Different Line Lengths) ");
			return (-1);
		}
		cur = cur->next;
		count++;
	}
	if (count < 3)
	{
		ft_printf("Error : (Less than 3 Lines) ");
		return (-1);
	}
	return (count);
}

(2) 지도 벽에 둘러싸여있지 않은 경우

int	map_check_wall(t_param *p, int end)
{
	int			count;
	t_list		*cur;

	count = 0;
	cur = p->map;
	while (cur)
	{
		if (check_wall(cur->data, count, end) == -1)
			return (-1);
		cur = cur->next;
		count++;
	}
	return (0);
}

int	check_wall(char *str, int count, int end)
{
	char	c;

	if (count == 0 || count == end - 1)
	{
		while (*str != '\n')
		{
			if (*str != '1')
				return (-1);
			str++;
		}
	}
	else
	{
		if (*str != '1')
			return (-1);
		while (*str != '\n')
		{
			c = *str;
			str++;
		}
		if (c != '1')
			return (-1);
	}
	return (0);
}

(3) 지도 출구/시작지점/수집품 없는 경우

  • 맞을 경우 : 플레이어 시작 위치 저장
  • 틀릴 경우 : 에러 메시지 표출 + 할당된 메모리 free + 종료
int	map_check_count(t_param *p)
{
	t_list		*cur;
	char		*line;

	cur = p->map;
	while (cur)
	{
		line = cur->data;
		while (*line)
		{
			if (*line == 'C')
				p->coin++;
			else if (*line == 'E')
				p->exit++;
			else if (*line == 'P')
				p->player++;
			line++;
		}
		cur = cur->next;
	}
	if (p->player != 1 || p->exit != 1 || p->coin < 1)
		return (-1);
	return (0);
}

(4) 지도 지정되지 않은 문자가 포함된 경우

  • 특수문자 및 map 규칙 이외의 문자 입력할 경우
// map_imgs 에서 확인 후 에러 처리 
void	map_imgs(t_param *p, char c)
{
	if (c != '1' && c != '0' && c != 'C' && c != 'E' && c != 'P' && c != '\n' && c != '\0')
	{
		printf("%c\n", c);
		ft_printf("Error : Invalid Char\n");
		map_free(p);
		exit(0);
	}
    
	...
}

(5) 비어있는 맵이 포함된 경우

  • 에러 처리 안할경우 Segmentation Fault 오류 발생
int	map_read(t_param *p, char *file)
{
	...
    
	fd = open(file, O_RDONLY);
	if (fd < 0)
		return (1);
	line = get_next_line(fd);
	if (line == NULL) // 비어있어서 line = 0일 경우 에러 발생 
	{
		free(line);
		ft_printf("Empty Map\n");
		return (1);
	}
}

(6) 출구 뒤에 아이템이 있는 경우

int	map_dfs(t_param *p, int x, int y, char visit[2000][2000])
{
	...
    
	else if (visit[y][x] == 'E') // 출구 만나면 재귀 빠져나오게 변경 
	{
		p->exit++;
		return (0);
	}
	
    ... 
}

(7) map.ber 파일 확장자 틀린 경우

  • 프로그램 인자로 .ber 만을 받아야 함
int	file_name(char *file)
{
	int		i;
	char	*ext;

	i = 0;
	ext = ".ber";
	while (*(file + i) != '.')
		i++;
	while (*(file + i) || *ext)
	{
		if (*(file + i) != *ext)
		{
			ft_printf_s("Error : Invalid File name or File Extenstion\n");
			return (1);
		}
		ext++;
		i++;
	}
	return (0);
}

📌 5. map 경로 확인

  • dfs(깊이우선탐색) : dfs란 전체 탐색을 하는 알고리즘 중 하나로 하나의 경로로 쭉 파고들어 끝까지 확인한 후 다시 이전으로 돌아와서 다음경로를 끝까지 파고든다 이러한 동작은 모든 장소를 확인할때 까지 반복해서 동작한다
int	dfs(t_param *p, int coin, int exit)
{
	t_list		*cur;
	char		*line;
	char		d[2000][2000];
	int			x;
	int			y;

	cur = p->map;
	y = 0;
	while (cur)
	{
		x = 0;
		line = cur->data;
		while (*line)
			d[y][x++] = *line++;
		y++;
		cur = cur->next;
	}
	map_dfs(p, p->play_x, p->play_y, d);
	if (p->exit != 2 * exit || p->coin != 2 * coin)
	{
		ft_printf("Error : (No Map Route) ");
		return (1);
	}
	p->coin = coin;
	return (0);
}

int	map_dfs(t_param *p, int x, int y, char visit[2000][2000])
{
	if (x < 0 || x >= p->win_x || y < 0 || y >= p->win_y)
		return (0);
	if (visit[y][x] == '1')
		return (0);
	if (visit[y][x] != 'V')
	{
		if (visit[y][x] == 'E')
			p->exit++;
		if (visit[y][x] == 'C')
			p->coin++;
		visit[y][x] = 'V';
		map_dfs(p, x - 1, y, visit);
		map_dfs(p, x + 1, y, visit);
		map_dfs(p, x, y - 1, visit);
		map_dfs(p, x, y + 1, visit);
		return (1);
	}
	return (0);
}

📘 코드

📌 so_long.c

  • init : 구조체 변수 초기화
  • init_player : 플레이어 위치를 play_x, play_y에 저장
  • map_exit : coin 개수가 0일 경우에 종료
  • map_red_exit : 좌측 상단 빨간 버튼 눌렀을 경우 종료
  • main : map_read ➡ map_check ➡ map_set ➡ dfs
#include "so_long.h"

int	init(t_param *p)
{
	p->mlx = mlx_init();
	p->img_coin = mlx_xpm_file_to_image(\
				p->mlx, "imgs/coin.xpm", &p->x, &p->y);
	p->img_exit = mlx_xpm_file_to_image(\
				p->mlx, "imgs/exit.xpm", &p->x, &p->y);
	p->img_map = mlx_xpm_file_to_image(\
				p->mlx, "imgs/map.xpm", &p->x, &p->y);
	p->img_mario = mlx_xpm_file_to_image(\
				p->mlx, "imgs/mario.xpm", &p->x, &p->y);
	p->img_wall = mlx_xpm_file_to_image(\
				p->mlx, "imgs/wall.xpm", &p->x, &p->y);
	p->map = NULL;
	p->move = 1;
	p->count = 0;
	p->coin = 0;
	p->coin_check = 0;
	p->exit = 0;
	p->player = 0;
	return (1);
}

void	init_player(t_param *p)
{
	t_list		*cur;
	char		*line;

	cur = p->map;
	p->play_x = 0;
	p->play_y = 0;
	while (cur)
	{
		line = cur->data;
		while (*line)
		{
			if (*line++ == 'P')
				return ;
			p->play_x++;
		}
		p->play_x = 0;
		p->play_y++;
		cur = cur->next;
	}
}

int	map_exit(t_param *p)
{
	if (p->coin == 0)
	{
		p->move++;
		ft_printf_d(p->move);
		ft_printf_s("Exit : GAME SUCCESS\n");
		map_free(p);
		exit(0);
		return (0);
	}
	return (1);
}

int	map_red_exit(t_param *p)
{
	ft_printf_s("Exit : Red Button");
	map_free(p);
	exit(0);
	return (1);
}

int	main(int argc, char **argv)
{
	t_param		p;

	if (argc != 2 || file_name(argv[1]))
	{
		ft_printf_s("Error : Few argument or Invalid File Extenstion\n");
		return (-1);
	}
	if (init(&p) && map_read(&p, argv[1]))
		return (-1);
	if (map_check(&p))
	{
		map_free(&p);
		return (-1);
	}
	p.win = mlx_new_window(p.mlx, p.win_x, p.win_y, "so_long");
	map_set(&p);
	if (dfs(&p))
	{
		map_free(&p);
		return (-1);
	}
	mlx_key_hook(p.win, &move, &p);
	mlx_hook(p.win, RED, 0, &map_red_exit, &p);
	mlx_loop(p.mlx);
	return (0);
}

📌 so_long_map.c

  • map_read : 파일 한 줄씩 읽기
  • map_strjoin : 읽은 line을 연결리스트 마지막에 연결
  • map_set : line을 한개씩 map_imgs 인자로 넘긴 후 64픽셀씩 x, y에 더하기
  • map_imgs : 인자에 맞는 이미지를 x, y의 위치에 표출 (정해지지 않은 인자 에러처리)
  • map_free : 동적할당된 연결리스트 안의 내용 해제
#include "so_long.h"

int	map_read(t_param *p, char *file)
{
	int		fd;
	char	*line;

	fd = open(file, O_RDONLY);
	if (fd < 0)
	{
		ft_printf_s("Error : Read Fail or Invalid File name\n");
		return (1);
	}
	line = get_next_line(fd);
	if (line == NULL)
	{
		free(line);
		ft_printf_s("Error : Invalid Map (Empty Map)\n");
		return (1);
	}
	while (line != NULL)
	{
		map_strjoin(p, line);
		free(line);
		line = get_next_line(fd);
	}
	free(line);
	return (0);
}

int	map_strjoin(t_param *p, char *line)
{
	t_list	*cur;
	t_list	*new;

	cur = p->map;
	new = malloc(sizeof(t_list));
	if (!new)
	{
		map_free(p);
		return (0);
	}
	new->data = ft_strdup(line);
	new->next = NULL;
	if (cur == NULL)
		p->map = new;
	else
	{
		while (cur->next != NULL)
			cur = cur->next;
		cur->next = new;
	}
	return (1);
}

void	map_set(t_param *p)
{
	t_list		*cur;
	char		*line;

	p->x = 0;
	p->y = 0;
	cur = p->map;
	init_player(p);
	mlx_clear_window(p->mlx, p->win);
	while (cur)
	{
		line = cur->data;
		while (*line)
		{
			map_imgs(p, *line);
			line++;
			p->x += 64;
		}
		p->x = 0;
		p->y += 64;
		cur = cur->next;
	}
}

void	map_imgs(t_param *p, char c)
{
	if (c != '1' && c != '0' && c != 'C' && c != 'E' && c != 'P' \
		&& c != '\n' && c != '\0')
	{
		ft_printf_s("Error : Invalid Char\n");
		map_free(p);
		exit(0);
	}
	mlx_put_image_to_window(\
				p->mlx, p->win, p->img_map, p->x, p->y);
	if (c == '1')
		mlx_put_image_to_window(\
				p->mlx, p->win, p->img_wall, p->x, p->y);
	else if (c == '0')
		mlx_put_image_to_window(\
				p->mlx, p->win, p->img_map, p->x, p->y);
	else if (c == 'C')
		mlx_put_image_to_window(\
				p->mlx, p->win, p->img_coin, p->x, p->y);
	else if (c == 'E')
		mlx_put_image_to_window(\
				p->mlx, p->win, p->img_exit, p->x, p->y);
	else if (c == 'P')
		mlx_put_image_to_window(\
				p->mlx, p->win, p->img_mario, p->x, p->y);
}

void	map_free(t_param *p)
{
	t_list		*cur;
	t_list		*new;

	cur = p->map;
	while (cur)
	{
		new = cur->next;
		free (cur->data);
		free (cur);
		cur = new;
	}
	free (cur);
	exit(0);
}

📌 so_long_move.c

  • move : 키가 눌렸을 때 알맞게 분기 후 map 다시 그리기
  • move_up
  • move_left
  • move_down
  • move_right
#include "so_long.h"

int	move(int key, t_param *p)
{
	if (key == W)
		move_up(p);
	else if (key == A)
		move_left(p);
	else if (key == S)
		move_down(p);
	else if (key == D)
		move_right(p);
	else if (key == ESC)
	{
		ft_printf_s("Exit : ESC");
		map_free(p);
		exit(0);
	}
	else
		return (1);
	map_set(p);
	return (0);
}

void	move_up(t_param *p)
{
	int			y;
	char		*line;
	t_list		*cur;
	char		*cur_prev;

	cur = p->map;
	y = p->play_y;
	while (y-- != 1)
		cur = cur->next;
	cur_prev = cur->data;
	cur = cur->next;
	line = cur->data;
	if (cur_prev[p->play_x] == '1')
		return ;
	else if (cur_prev[p->play_x] == 'E')
		if (map_exit(p))
			return ;
	if (cur_prev[p->play_x] == 'C')
		p->coin--;
	cur_prev[p->play_x] = 'P';
	line[p->play_x] = '0';
	init_player(p);
	ft_printf_d(p->move);
	p->move++;
}

void	move_left(t_param *p)
{
	int			y;
	char		*line;
	t_list		*cur;

	cur = p->map;
	y = p->play_y;
	while (y--)
		cur = cur->next;
	line = cur->data;
	if (line[p->play_x - 1] == '1')
		return ;
	else if (line[p->play_x - 1] == 'E')
		if (map_exit(p))
			return ;
	if (line[p->play_x - 1] == 'C')
		p->coin--;
	line[p->play_x - 1] = 'P';
	line[p->play_x] = '0';
	init_player(p);
	ft_printf_d(p->move);
	p->move++;
}

void	move_down(t_param *p)
{
	int			y;
	char		*line;
	t_list		*cur;
	char		*cur_next;

	cur = p->map;
	y = p->play_y;
	while (y--)
		cur = cur->next;
	line = cur->data;
	cur = cur->next;
	cur_next = cur->data;
	if (cur_next[p->play_x] == '1')
		return ;
	else if (cur_next[p->play_x] == 'E')
		if (map_exit(p))
			return ;
	if (cur_next[p->play_x] == 'C')
		p->coin--;
	line[p->play_x] = '0';
	cur_next[p->play_x] = 'P';
	init_player(p);
	ft_printf_d(p->move);
	p->move++;
}

void	move_right(t_param *p)
{
	int			y;
	char		*line;
	t_list		*cur;

	cur = p->map;
	y = p->play_y;
	while (y--)
		cur = cur->next;
	line = cur->data;
	if (line[p->play_x + 1] == '1')
		return ;
	else if (line[p->play_x + 1] == 'E')
		if (map_exit(p))
			return ;
	if (line[p->play_x + 1] == 'C')
		p->coin--;
	line[p->play_x + 1] = 'P';
	line[p->play_x] = '0';
	init_player(p);
	ft_printf_d(p->move);
	p->move++;
}

📌 so_long_check.c

  • map_check : check_line ➡ check_wall ➡ check_count
  • map_check_line : 이전 및 현재 line의 길이 체크
  • map_check_wall : 벽에 둘러싸여 있는지 체크
  • map_check_count : P(플레이어) & E(탈출구)가 1개인지 + C(수집품)이 1개 이상인지 체크
#include "so_long.h"

int	map_check(t_param *p)
{
	t_list	*cur;

	cur = p->map;
	if (map_check_line(p) == -1)
		return (1);
	p->win_x = p->x * (ft_strlen(cur->data) - 1);
	p->win_y = p->y * p->count;
	if (map_check_wall(p, p->count) == -1)
	{
		ft_printf_s("Error : Invalid Map (No Wall Enclosed) \n");
		return (1);
	}
	if (map_check_count(p) == -1)
	{
		ft_printf_s("Error : Invalid Map (Invalid Start / Exit / Collect) \n");
		return (1);
	}
	return (0);
}

int	map_check_line(t_param *p)
{
	t_list	*cur;
	int		len;
	int		len_next;

	cur = p->map;
	len = ft_strlen(cur->data);
	while (cur)
	{
		len_next = ft_strlen(cur->data);
		if (len != len_next)
		{
			ft_printf_s("Error : Invalid Map (Different Line Lengths) \n");
			return (-1);
		}
		cur = cur->next;
		p->count++;
	}
	if (p->count < 3)
	{
		ft_printf_s("Error : Invalid Map (Less than 3 Lines) \n");
		return (-1);
	}
	return (1);
}

int	map_check_wall(t_param *p, int end)
{
	int			count;
	t_list		*cur;

	count = 0;
	cur = p->map;
	while (cur)
	{
		if (check_wall(cur->data, count, end) == -1)
			return (-1);
		cur = cur->next;
		count++;
	}
	return (0);
}

int	check_wall(char *str, int count, int end)
{
	char	c;

	if (count == 0 || count == end - 1)
	{
		while (*str != '\n')
			if (*str++ != '1')
				return (-1);
	}
	else
	{
		if (*str != '1')
			return (-1);
		while (*str != '\n')
			c = *str++;
		if (c != '1')
			return (-1);
	}
	return (0);
}

int	map_check_count(t_param *p)
{
	t_list		*cur;
	char		*line;

	cur = p->map;
	while (cur)
	{
		line = cur->data;
		while (*line)
		{
			if (*line == 'C')
				p->coin++;
			else if (*line == 'E')
				p->exit++;
			else if (*line == 'P')
				p->player++;
			line++;
		}
		cur = cur->next;
	}
	if (p->player != 1 || p->exit != 1 || p->coin < 1)
		return (-1);
	return (0);
}

📌 so_long_dfs.c

  • dfs : visit 2차원 배열 할당받아 map 복사 후 dfs된 coin 개수가 일치하는지 확인
  • dfs_recur : 출구 및 벽일 경우 재귀 탈출조건 세우고 상하좌우 재귀
  • file_name : .ber 파일이 들어오는지 확인
#include "so_long.h"

int	dfs(t_param *p)
{
	t_list		*cur;
	char		*line;
	char		**visit;

	cur = p->map;
	p->y = 0;
	visit = (char **)malloc(sizeof(char *) * (p->count + 1));
	while (cur)
	{
		p->x = 0;
		line = cur->data;
		visit[p->y] = (char *)malloc(sizeof(char) * (ft_strlen(line) + 1));
		while (*line)
			visit[p->y][p->x++] = *line++;
		p->y++;
		cur = cur->next;
	}
	dfs_recur(p, p->play_x, p->play_y, visit);
	if (p->coin != p->coin_check)
	{
		ft_printf_s("Error : Invalid Map Route\n");
		return (1);
	}
	free(visit);
	return (0);
}

int	dfs_recur(t_param *p, int x, int y, char **visit)
{
	if (x < 0 || x >= p->win_x || y < 0 || y >= p->win_y)
		return (0);
	if (visit[y][x] == '1')
		return (0);
	else if (visit[y][x] == 'E')
		return (0);
	else if (visit[y][x] != 'V')
	{
		if (visit[y][x] == 'C')
			p->coin_check++;
		visit[y][x] = 'V';
		dfs_recur(p, x - 1, y, visit);
		dfs_recur(p, x + 1, y, visit);
		dfs_recur(p, x, y - 1, visit);
		dfs_recur(p, x, y + 1, visit);
		return (1);
	}
	return (0);
}

int	file_name(char *file)
{
	int		i;
	char	*ext;

	i = 0;
	ext = ".ber";
	while (*(file + i) != '.')
		i++;
	while (*(file + i) || *ext)
	{
		if (*(file + i) != *ext)
			return (1);
		ext++;
		i++;
	}
	return (0);
}

void	ft_printf_s(char *s)
{
	size_t	i;
	char	c;

	i = 0;
	if (!s)
	{
		if (write(1, "(null)", 6) == -1)
			return ;
	}
	while (*(s + i))
	{
		c = *(s + i);
		write(1, &c, 1);
		i++;
	}
}

void	ft_printf_d(long long n)
{
	int		i;
	char	c[12];

	i = 0;
	if (n < 0)
	{
		c[0] = '-';
		write(1, &c, 1);
		n *= -1;
	}
	while (n >= 10)
	{
		c[i] = (n % 10) + 48;
		n /= 10;
		i++;
	}
	c[i] = (n % 10) + 48;
	while (i >= 0)
	{
		write(1, &c[i], 1);
		i--;
	}
	c[0] = '\n';
	write(1, &c, 1);
}

📌 so_long.h

#ifndef SO_LONG_H
# define SO_LONG_H

# define W			13
# define A			0
# define S			1
# define D			2
# define ESC		53
# define RED		17

# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <fcntl.h>

# include "mlx/mlx.h"
# include "libft/get_next_line.h"

typedef struct s_list
{
	char			*data;
	struct s_list	*next;
}	t_list;

typedef struct s_param
{
	void		*mlx;
	void		*win;
	void		*img_mario;
	void		*img_map;
	void		*img_coin;
	void		*img_wall;
	void		*img_exit;
	t_list		*map;
	int			win_x;
	int			win_y;
	int			x;
	int			y;
	int			play_x;
	int			play_y;
	int			move;
	int			count;
	int			coin;
	int			coin_check;
	int			exit;
	int			player;
}	t_param;

void	ft_printf_s(char *s);
void	ft_printf_d(long long n);
int		file_name(char *file);
int		init(t_param *p);
void	init_player(t_param *p);
int		move(int key, t_param *p);
void	move_up(t_param *p);
void	move_left(t_param *p);
void	move_down(t_param *p);
void	move_right(t_param *p);
int		map_read(t_param *p, char *file);
int		map_strjoin(t_param *p, char *line);
void	map_set(t_param *p);
void	map_imgs(t_param *p, char c);
void	map_free(t_param *p);
int		map_exit(t_param *p);
int		map_red_exit(t_param *p);
int		map_check(t_param *p);
int		map_check_line(t_param *p);
int		map_check_wall(t_param *p, int end);
int		map_check_count(t_param *p);
int		check_wall(char *str, int count, int end);
int		dfs(t_param *p);
int		dfs_recur(t_param *p, int x, int y, char **visit);

#endif

📌 Makefile

NAME = so_long

SRCS = libft/get_next_line.c\
		libft/get_next_line_utils.c\
		so_long.c\
		so_long_move.c\
		so_long_map.c\
		so_long_check.c\
		so_long_dfs.c

OBJS = $(SRCS:.c=.o)

%.o:%.c so_long.h
	cc -Wall -Wextra -Werror -c $< -o $@

$(NAME) : 	$(OBJS)
			make -C ./mlx/
			cc -o so_long $(OBJS) -L./mlx -lmlx -framework OpenGL -framework AppKit

all : $(NAME)

clean :
	make -C ./mlx clean
	rm -rf $(OBJS)

fclean : clean
	rm -rf $(NAME)

re :
	make fclean
	make all

.PHONY : all clean fclean re
profile
Notion으로 이동 (https://24tngus.notion.site/3a6883f0f47041fe8045ef330a147da3?v=973a0b5ec78a4462bac8010e3b4cd5c0&pvs=4)

0개의 댓글