내가 짠 so_long 코드를 되새김질 해보자

정하나둘·2022년 12월 25일
0

so_long

목록 보기
2/2

필자는 사용할 이미지 파일들을 찾기도 귀찮고 .xpm 파일로 변환하기도 굉장히 귀찮아서 그냥 대충 이미지 파일을 썼으니 진짜 고퀄로 하고싶다면 어디 한 번 화이팅...!

사용하는 구조체의 종류들

typedef struct s_data {
	char	**ber_arr;	//map 정보가 담길 2차원 배열들
	void	*mlx;		//mlx_init()의 리턴값이 담길 주소 포인터
	void	*win;		//mlx_new_window()의 리턴값이 담길 주소 포인터
	void	*grass;		//ber_arr[][]의 값이 '0'일 경우 땅 이미지를 넣을 포인터
	void	*wall;		//"" '1'일 경우 벽 이미지를 넣을 포인터
	void	*player;	// "" 'P'일 경우 캐릭터 이미지를 넣을 포인터
	void	*chest;		// "" 'C'일 경우 아이템 이미지를 넣을 포인터
	void	*door;		// "" 'E'일 경우 문 이미지를 넣을 포인터
	char	*map;		
	int		items;		//캐릭터가 이동하며 아이템을 획득할 경우 하나씩 줄어들 아이템의 갯수 (0이 되고 E로 탈출해야 게임 종료)
	int		walk;		//걸음수를 terminal에 출력하기 위한 걸음수 체크 변수
	int		now_x;		//캐릭터의 현재 위치
	int		now_y;		//캐릭터의 현재 위치
	int		x;			//캐릭터의 다음 위치
	int		y;			//캐릭터의 다음 위치
	int		box_width;	//ber파일의 인자값 갯수
	int		box_height;	//ber파일의 인자값 갯수
	int		img_width;	//mlx_xpm_file_to_image의 3,4번째 인자값으로 사용할 이미지의 크기, 따로 설정하지 않아도 사진 크기에 맞게 조절됨 (주의, xpm으로 변환할 png파일들은 크기가 모두 같아야 함.)
	int		img_height;	//""
}				t_data;

typedef struct s_dfs {
	int	items;			//dfs에서 검사할 유효한 아이템
	int	door;			//dfs에서 검사할 유효한 문
	int	**arr;			//dfs에서 사용할 ber_arr과 똑같은 이중배열 포인터
}				t_dfs;

main함수

int	main(int ac, char **av)
{
	t_dfs	tdfs;
	t_data	data;
	int		fd;

	if (ac == 2)
	{
		fd = open(av[1], O_RDONLY);
		if (fd <= 0)
			print_err0("File open fail.\n");
		data.mlx = mlx_init();
		data.ber_arr = map_read(av[1], &data); //2차원 배열에 .ber파일의 내용들을 개행 기준으로 split해서 담는 함수.
		valid_test(&data);	//split해서 담은 내용들을 하나씩 흝어 P와 E가 하나만 있고 C가 1개 이상 있는지 검사한다.
		data = set_win_img(data);	//윈도우 창을 만들고, 사진을 넣는 함수.
		dfs(&data, &tdfs);	//dfs를 통해 이루어질 수 없는 맵(player가 아이템을 먹을 수 없거나 탈출구로 나갈 수 없는 맵) 검사
		mlx_hook(data.win, KEYPRESS, 0, &deal_key, &data);	//키보드로 w,a,s,d 입력시 캐릭터가 이동하도록 하는 함수(deal_key)의 주소를 담은 hook 함수
		mlx_hook(data.win, 17, 0, &deal_mouse, &data);	//마우스로 window의 왼쪽 상단 X를 눌러 종료 할 경우를 위한 deal_mouse 함수의 주소를 담은 hook 함수
		mlx_loop(data.mlx);
	}
	else
		print_err0("Map is missing.\n"); //argv의 인자값이 1개보다 더 들어왔을 경우를 위한 에러처리.
	return (0);
}

set_win_img함수

t_data	set_win_img(t_data data)
{
	int	i;

	i = 0;
	while (data.ber_arr[i])
		i++;
	data.box_height = i;
	data.items = 0;
	data.walk = 0;
	data.box_width = ft_strlen(data.ber_arr[0]);
	data.win = mlx_new_window(data.mlx, data.box_width * 64, \
										data.box_height * 64, "my_mlx");
	data.wall = mlx_xpm_file_to_image(data.mlx, "./image/walls.xpm", \
										&data.img_width, &data.img_height);
	data.grass = mlx_xpm_file_to_image(data.mlx, "./image/grass.xpm", \
										&data.img_width, &data.img_height);
	data.player = mlx_xpm_file_to_image(data.mlx, "./image/player.xpm", \
										&data.img_width, &data.img_height);
	data.chest = mlx_xpm_file_to_image(data.mlx, "./image/chest_01.xpm", \
										&data.img_width, &data.img_height);
	data.door = mlx_xpm_file_to_image(data.mlx, "./image/door.xpm", \
										&data.img_width, &data.img_height);
	put_image(&data);
	return (data);
}

각종 void 이미지 포인터에 .xpm 파일을 넣어주는 함수이다.


put_image함수

void	put_image(t_data *data)
{
	int	i;
	int	j;
	int	count;

	i = -1;
	data->y = 0;
	count = 0;
	while (data->ber_arr[++i])
	{
		j = -1;
		data->x = 0;
		while (data->ber_arr[i][++j])
		{
			check_map(data, i, j);
			data->x += 64;
			count++;
		}
		data->y += 64;
	}
	check_wall(data);
	if (count != data->box_height * data->box_width)
		print_err("Error\nMap must be rectangular.\n", data);
}

check_map을 통해 이미지를 window에 출력하고 check_wall을 통해 벽으로 둘러쌓여있는지 확인한 후 맵이 사각형이 아니면 에러문 출력 함수로 넘어간다. (free해줌)


check_map, check_wall 함수

void	check_map(t_data *data, int i, int j)
{
	if (data->ber_arr[i][j] == '1')
		mlx_put_image_to_window(data->mlx, data->win, \
					data->wall, data->x, data->y);
	else if (data->ber_arr[i][j] == '0')
		do_mlx_put_image_grass(data);
	else if (data->ber_arr[i][j] == 'P')
	{
		do_mlx_put_image_grass(data);
		mlx_put_image_to_window(data->mlx, data->win, \
					data->player, data->x, data->y);
		data->now_x = j;
		data->now_y = i;
	}
	else if (data->ber_arr[i][j] == 'C')
	{
		do_mlx_put_image_grass(data);
		do_mlx_put_image_chest(data);
		data->items++;
	}
	else if (data->ber_arr[i][j] == 'E')
		mlx_put_image_to_window(data->mlx, data->win, \
					data->door, data->x, data->y);
	else
		print_err("Error\nWrong Map\n", data);
}

void	check_wall(t_data *data)
{
	int	i;

	i = -1;
	while (++i < data->box_width)
	{
		if (data->ber_arr[0][i] != '1')
			print_err("Error\nMap must be closed/surrounded by walls\n", data);
		if ((data->ber_arr[data->box_height - 1][i]) != '1')
			print_err("Error\nMap must be closed/surrounded by walls\n", data);
	}
	i = -1;
	while (++i < data->box_height)
	{
		if ((data->ber_arr[i][0]) != '1')
			print_err("Error\nMap must be closed/surrounded by walls\n", data);
		if ((data->ber_arr[i][data->box_width - 1]) != '1')
			print_err("Error\nMap must be closed/surrounded by walls\n", data);
	}
}

check_map 함수는 이미지를 넣음과 동시에 1,0,P,C,E가 아닌 문자가 나오면 에러문을 출력한다.
check_wall 함수는 벽으로 둘러쌓여있지 않으면 에러문을 출력한다.


dfs 함수

void	dfs(t_data *data, t_dfs *tdfs)
{
	int	x;
	int	y;
	int	i;

	i = -1;
	x = data->now_x;	//처음 player의 위치
	y = data->now_y;	//처음 player의 위치
	tdfs->items = data->items;	//아이템 전체의 갯수
	tdfs->door = 0;		//문을 만나면 하나씩 증가하므로 0으로 초기화
	init_tdfs(data, tdfs);	//tdfs->arr[][]의 값을 모두 정수 0으로 초기화
	find_items(data, tdfs, x, y); 	//맵 전체 아이템 검사
	free_tdfs_arr(data, tdfs);	//tdfs->arr[][] free
	init_tdfs(data, tdfs);	//tdfs->arr[][]의 값을 모두 정수 0으로 초기화
	find_door(data, tdfs, x, y);	//맵 전체 문 검사
	free_tdfs_arr(data, tdfs);		//tdfs->arr[][] free
	if (tdfs->items != 0 || tdfs->door != 1)	//아이템이 0이 아니거나 문 갯수가 1이 아니면 에러문 출력
		print_err("Error\nInvalidMapFile\n", data);
}


find_items 함수

void	find_items(t_data *data, t_dfs *tdfs, int x, int y)
{
	//상하좌우 한칸씩 이동하며 재귀로 맵 전체 아이템 검사
	if (check_items_dfs(data, tdfs, x + 1, y))
		find_items(data, tdfs, x + 1, y);
	if (check_items_dfs(data, tdfs, x, y + 1))
		find_items(data, tdfs, x, y + 1);
	if (check_items_dfs(data, tdfs, x - 1, y))
		find_items(data, tdfs, x - 1, y);
	if (check_items_dfs(data, tdfs, x, y - 1))
		find_items(data, tdfs, x, y - 1);
}


check_items_dfs 함수

int	check_items_dfs(t_data *data, t_dfs *tdfs, int x, int y)
{
	if (data->ber_arr[y][x] == '1' || tdfs->arr[y][x] || \
		data->ber_arr[y][x] == 'E' || !tdfs->items)
		return (0);	//벽이거나, 이미 방문해서 1이 찍혀있거나, 출구거나 아이템 갯수가 0개면 리턴으로 종료
	if (data->ber_arr[y][x] == 'C')
		tdfs->items--;	//아이템을 만나면 아이템 수 하나씩 감소
	tdfs->arr[y][x] = 1;	//방문한 흔적을 남기기 위해 0->1로 값 변경
	return (1);
}


find_door 함수

find_items와 같음

void	find_door(t_data *data, t_dfs *tdfs, int x, int y)
{
	if (check_doors_dfs(data, tdfs, x + 1, y))
		find_door(data, tdfs, x + 1, y);
	if (check_doors_dfs(data, tdfs, x, y + 1))
		find_door(data, tdfs, x, y + 1);
	if (check_doors_dfs(data, tdfs, x - 1, y))
		find_door(data, tdfs, x - 1, y);
	if (check_doors_dfs(data, tdfs, x, y - 1))
		find_door(data, tdfs, x, y - 1);
}


check_doors_dfs 함수

int	check_doors_dfs(t_data *data, t_dfs *tdfs, int x, int y)
{
	if (data->ber_arr[y][x] == '1' || tdfs->arr[y][x])
		return (0);	//1이거나 이미 방문했으면 리턴 0으로 종료
	if (data->ber_arr[y][x] == 'E')	
		tdfs->door++;	//문을 만나면 1씩 +
	tdfs->arr[y][x] = 1;	//방문 흔적 남기기
	return (1);
}


deal_key 함수

int	deal_key(int key_code, t_data *data)
{
	//key_code 방향키에 따른 x, y좌표 변경 인자를 가지고 move_player 함수 실행
	if (key_code == ESC)
		good_bye(data);
	else if (key_code == UP)
		move_player(data, data->now_x, data->now_y - 1);
	else if (key_code == LEFT)
		move_player(data, data->now_x - 1, data->now_y);
	else if (key_code == RIGHT)
		move_player(data, data->now_x + 1, data->now_y);
	else if (key_code == DOWN)
		move_player(data, data->now_x, data->now_y + 1);
	return (0);
}


move_player 함수

void	move_player(t_data *data, int x, int y)
{
	if (data->ber_arr[y][x] == '1' || \
			(data->items && data->ber_arr[y][x] == 'E'))
		return ;	//벽이거나, 아이템이 남은 상태로 출구에 갈 수 없도록 리턴
	if (!data->items && data->ber_arr[y][x] == 'E')
		good_bye(data);		//아이템을 다 먹고 출구로 가면 게임 종료
	if (data->ber_arr[y][x] == 'C')
	{
		data->items--;		//아이템 먹어서 갯수 줄어듦
		data->ber_arr[y][x] = 0;	//땅을 의미하는 '0'으로 바꾸기
		mlx_put_image_to_window(data->mlx, data->win, \
					data->player, x * 64, y * 64);	//땅 이미지 넣기
	}
	mlx_put_image_to_window(data->mlx, data->win, \
			data->grass, data->now_x * 64, data->now_y * 64);	//원래 있던 곳에 풀 이미지 넣기
	data->walk++;	//걸음 수 증가
	ft_printf("%d\n", data->walk);	//걸음 수 터미널 출력
	mlx_put_image_to_window(data->mlx, data->win, \
					data->grass, x * 64, y * 64);	//이동하는 방향 풀 이미지 넣기
	mlx_put_image_to_window(data->mlx, data->win, \
					data->player, x * 64, y * 64);	//이동된 방향 플레이어 이미지 넣기
	data->now_x = x;	//현재 위치 조정
	data->now_y = y;	//현재 위치 조정
}


deal_mouse 함수

int	deal_mouse(t_data *data)
{
	good_bye(data);
	return (0);
}


good_bye 함수

void	good_bye(t_data *data)
{
	int	i;

	i = -1;
	while (data->ber_arr[++i])
		free(data->ber_arr[i]);
	free(data->ber_arr);	//ber_arr free
	mlx_destroy_window(data->mlx, data->win); //윈도우 종료
	exit(1);	//exit로 탈출
}

이상 모든 과정과 함수에 대한 설명이 끝났다. 이해가 안되는 부분이 있으면 질문 적어주시면 감사하겠습니다.

profile
내가 다시 보려고 만드는 42서울 본과정 블로그

0개의 댓글