[so_long] so_long 구현

J_JEON·2022년 6월 15일
1

so_long

목록 보기
2/2

miniLibX를 활용하여 맵을 구성하고, 직접 조작이 가능한 캐릭터로 수집품을 획득하여 지정된 탈출구로 탈출하는 구조의 미니게임을 만들어보는 프로젝트
만약 과제를 이제 시작한다면 MLX를 활용하는 예제들을 미리 해보는것을 추천
(https://velog.io/@jen133/miniLibX)

Main함수

int	main(void)
{
	t_param		par;
	int			mapstat;

	set_param(&par);
	if (par.fd <= 0)
	{
		printf("Error\nInvalidMapFile\n");
		return (0);
	}
	copymap(&par);
	mapstat = check_map(&par);
	if (mapstat == 0)
	{
		printf("Error\n");
		freemap(&par);
		exit (0);
		return (0);
	}
	set_p(&par);
	par.win = mlx_new_window(par.mlx, par.win_width, par.win_height, "so_long");
	drawmap(&par);
	mlx_key_hook(par.win, &key_press, &par);
	mlx_hook(par.win, PRESS_RED_BUTTON, 0, &redbut, &par);
	mlx_loop(par.mlx);
	return (0);
}

구현 순서

  1. 게임 실행에 필요한 각종 값들의 초기설정을 해줌 (set_param(t_param))
  2. map.ber 파일에 저장되어있는 맵정보를 한줄씩 불러읽어 연결리스트에 저장해줌 (copymap(t_param))
  3. 맵정보가 맵 구현규칙에 맞는지 검증해줌 (check_map(t_param))
  4. 맵정보가 맵 구현 규칙에 맞지않다면 오류메세지를 띄우고 할당한 메모리를 free 해준 뒤 종료함
  5. 맵정보에 문제가 없다면 플레이를 위해 초기 플레이어의 시작위치를 저장해줌 (set_p(t_param))
  6. 게임화면을 띄우기위해 mlx를 사용하여 적정크기의 새 창을 띄워줌 (mlx_new_window())
  7. 저장해둔 맵 정보를 기반으로 윈도우에 이미지를 그려줌 (drawmap(t_param))
  8. 키 입력을 받고 받은 키에따른 동작(key_press())을 할 수있도록 키입력 이벤트를 수신하도록 함 (mlx_key_hook())
  9. 좌측 상단의 프로그램 종료버튼이 눌리는것(redbut())을 수신하고 동작할 수 있도록 함(mlx_hook())
  10. 프로그램이 종료되지않고 지속적으로 이벤트를 받을 수 있게 무한 루프를 돌도록 함(mlx_loop())

구현

1. set_param(t_param)

void	set_param(t_param *par)
{
	par->fd = open("maps/map.ber", O_RDONLY);
	if (par->fd <= 0)
		return ;
	par->map = NULL; // map을 불러와서 저장할 공간
	par->count_e = 0; // 탈출구의 갯수
	par->count_p = 0; // 시작지점의 갯수
	par->count_c = 0; // 수집품의 갯수
	par->win_width = 0; // 윈도우의 너비
	par->win_height = 0; // 윈도우의 높이
	par->x = 0; // 이미지의 x축 크기
	par->y = 0; // 이미지의 y축 크기
	par->move = 0; // 이동 횟수
	par->p_x = 0; // 플레이어의 x좌표
	par->p_y = 0; // 플레이어의 y좌표
	par->mlx = mlx_init(); // mlx
	par->g = mlx_xpm_file_to_image(par->mlx, "imgs/g.xpm", &par->wi, &par->he);// 땅 이미지
	par->w = mlx_xpm_file_to_image(par->mlx, "imgs/r.xpm", &par->wi, &par->he);// 벽 이미지
	par->c = mlx_xpm_file_to_image(par->mlx, "imgs/c.xpm", &par->wi, &par->he);// 수집품 이미지
	par->e = mlx_xpm_file_to_image(par->mlx, "imgs/e.xpm", &par->wi, &par->he);// 탈출구 이미지
	par->p = mlx_xpm_file_to_image(par->mlx, "imgs/p.xpm", &par->wi, &par->he);// 플레이어 이미지
	par->win = NULL;// 윈도우
}

필요한 값들을 저장해둔 구조체의 여러 값들을 초기화 하고, 이미지파일을 세팅해줌(mlx_xpm_file_to_image()) 또한 사용할 mlx도 초기화 해줌

2. copymap(t_param)

int	copymap(t_param *par)
{
	char	*str;

	str = get_next_line(par->fd);
    //get_next_line 사용해 맵파일을 한줄씩 읽어줌
	while (str != NULL) //읽어온 문자열이 NULL일때까지 (다 읽을때 까지)
	{
		copyline(str, par); //해당 줄을 복사하고 연결리스트에 넣어줌
		free(str); // 읽어온 문자열의 메모리할당 해제
		str = get_next_line(par->fd); // 다음 문자열을 읽어옴
	}
	free (str); // 읽어온 문자열의 메모리 할당 해제
	return (1);
}

int	copyline(char *str, t_param *par)
{
	t_mapline	*line;
	t_mapline	*currline;

	currline = par->map; // 문자열을 저장할 연결리스트의 시작을 가져옴
		line = malloc(sizeof(t_mapline)); //새 문자열을 저장할 저장공간 할당
	if (!line) // 할당 실패시
	{
		freemap(par); //여태 할당한 저장공간을 모두 해제
		return (0);
	}
	line->next = NULL; // 초기값 설정
	line->line = ft_strdup(str); //읽어온 문자열을 새로운 저장공간을 할당해서 넣어줌
	if (par->map == NULL) // 연결리스트의 첫 부분이 NULL이라면
		par->map = line; // 시작의 line에 바로 넣어줌
	else
	{
		while (currline->next != NULL) // 연결리스트의 끝까지 이동
			currline = currline->next;
		currline->next = line; // 문자열을 넣어줌
	}
	return (1);
}

void	freemap(t_param *par) // 여태 할당한 저장공간을 모두 해제해줌
{
	t_mapline	*currline;
	t_mapline	*nextline;

	currline = par->map;
	while (currline) // 연결리스트의 끝까지 순회
	{
		nextline = currline->next; // 할당 해제 전 다음 연결리스트를 미리 가져옴
		free (currline->line); // 내부에 들어있는 line부터 해제
		free (currline); // 현재 공간을 해제
		currline = nextline; // 다음 연결리스트로 이동
	}
	free (currline); // 마지막에 보고있던 저장공간 해제
}

3. check_map(t_param)

int	check_map(t_param *par)
{
	int	map_line; //맵이 총 몇라인으로 이뤄져있는지 저장

	map_line = check_line_len(par); // 맵 줄수를 확인하고 저장
	par->win_height = map_line * par->he; // 윈도우 높이를 계산하고 저장
	par->win_width = (ft_strlen(par->map->line) - 1) * par->wi; // 윈도우 너비를 계산하고 저장
	if (map_line) // 줄수가 맞는지 확인
		if (check_wall(par, map_line)) // 벽이 온전한지 확인
			if (check_map_char(par)) // 구성요소가 조건에 맞는지 확인
				return (1); // 맞다면 1 반환
	return (0); // 하나의 조건이라도 맞지않다면 0 반환
}

int	check_line_len(t_param *par)
{
	t_mapline	*curline;
	int			before_line_len;
	int			curr_line_len;
	int			line_count;

	before_line_len = 0;
	line_count = 0;
	curline = par->map;
	before_line_len = ft_strlen(curline->line); // 첫줄의 글자 수를 확인
	while (curline) // 마지막 라인까지 반복
	{
		line_count++; // 줄수 + 1
		curr_line_len = ft_strlen(curline->line); // 현재 보고있는 라인의 글자수 확인
		if (before_line_len != curr_line_len) // 이전 글자수와 다르다면
			return (print_err("줄 길이가 다름")); // 줄길이가 다르기때문에 에러반환
		before_line_len = curr_line_len; // 글자수가 같다면 현재 글자수를 이전 글자수로 변경
		curline = curline->next; // 다음줄 확인
	}
	if (line_count >= 3) // 라인이 3줄 이하라면
		return (line_count); // 구성요소가 다 들어갈 수 없으므로 에러 반환
	return (print_err("줄이 3줄 이하임"));
}

int	check_wall(t_param *par, int linenum) // 벽을 확인
{
	int			line_count;
	int			end_line;
	t_mapline	*curline;

	line_count = 0;
	end_line = linenum;
	curline = par->map;
	while (curline) // 마지막 라인까지 반복
	{
		if (line_count == 0 || line_count == end_line - 1) // 첫줄 또는 마지막줄 일때
		{
			if (!check_wall_end(curline->line)) // 전부 1이 아니면 에러 반환
				return (print_err("벽이 다 둘러져있지않습니다"));
		}
		else
		{
			if (!check_wall_middle(curline->line)) // 시작과 끝이 1이 아니라면 에러 반환
				return (print_err("벽이 다 둘러져있지않습니다"));
		}
		line_count++;
		curline = curline->next;
	}
	return (1);
}

int	check_map_char(t_param *par) // 맵의 구성요소를 확인
{
	t_mapline	*curline;
	char		*str;

	curline = par->map;
	while (curline)
	{
		str = curline->line;
		while (*str != '\n') // 라인의 끝까지 반환
		{
			if (*str == 'E') // E가 있다면 e갯수 증가
				par->count_e++;
			else if (*str == 'P') // P가 있다면 p갯수 증가
				par->count_p++;
			else if (*str == 'C') // C가 있다면 c갯수 증가
				par->count_c++;
			else if (*str != '1' && *str != '0') // 1 또는 0이 아니라면 에러
				return (0);
			str++;
		}
		curline = curline->next;
	}
	if (par->count_e != 1 || par->count_p != 1 || par->count_c < 1) 
    // 각 구성요소의 갯수가 1이 아닐시 에러
		return (print_err("탈출구, 시작지점, 수집품의 시작조건이 맞지않습니다"));
	return (1);
}

int	check_wall_end(char *str) // 시작과 끝 벽 확인
{
	while (*str != '\n')
	{
		if (*str != '1') // 1이아닌 다른게 있다면 에러
			return (print_err("끝에 1아닌게 있음"));
		str++;
	}
	return (1);
}

int	check_wall_middle(char *str)
{
	char	a;

	if (*str != '1') // 처음이 1이 아니라면 에러
		return (print_err("중간 시작이 1 아님"));
	while (*str != '\n') // 끝까지 이동
	{
		a = *str;
		str++;
	}
	if (a != '1') // 끝이 1이 아니라면 에러
		return (print_err("중간 끝이 1 아님"));
	return (1);
}

int	print_err(char *str) // 에러메시지를 출력해줌
{
	printf("%s\n", str);
	return (0);
}

4. set_p(t_param)

void	set_p(t_param *par) // 플레이어의 현재 좌표를 찾는 함수
{
	t_mapline	*curline;
	char		*str;

	curline = par->map;
	par->p_x = 0;
	par->p_y = 0;
	while (curline) //맵 문자열을 순회
	{
		str = curline->line;
		while (*str != '\n') // 문자열의 끝까지 순회
		{
			if (*str == 'P') // 플레이어의 위치인 P가 나올때까지
				return ;
			str++;
			par->p_x++; // x좌표값을 증가시킴
		}
		curline = curline->next;
		par->p_x = 0; // 새로운 문자열로 가야하니 x좌표값 초기화
		par->p_y++; // 문자열을 바꿀때마다 y좌표값 증가
	}
}

5. drawmap(t_param)

int	drawmap(t_param *par)
{
	t_mapline	*currline;
	char		*str;
	int			x;
	int			y;

	x = 0;
	y = 0;
	mlx_clear_window(par->mlx, par->win);
    // 윈도우에 그려진 이미지들을 모두 지워줌
	currline = par->map;
	while (currline) // 맵정보를 순회
	{
		str = currline->line;
		while (*str != '\n') // 문자열을 순회
		{
			draw_img(*str, par, x, y);
            // 현재 문자에 해당하는 이미지를 그려줌
			str++;
			x += par->wi;
            // 이미지가 그려질 x좌표값을 이미지의 너비만큼 증가시켜줌
		}
		currline = currline->next; // 다음 문자열로
		x = 0;
		y += par->he;
        // 이미지가 그려질 y좌표값을 이미지의 높이만큼 증가시켜줌
	}
	return (0);
}

void	draw_img(char a, t_param *par, int x, int y)
{
	if (a == '0') // 문자가 0이라면
		mlx_put_image_to_window(\
		par->mlx, par->win, par->g, x, y);
        //땅 이미지를 그려줌
	else if (a == '1') // 문자가 1이라면
	{
		mlx_put_image_to_window(\
		par->mlx, par->win, par->g, x, y);
        //땅을 먼저 그리고
		mlx_put_image_to_window(\
		par->mlx, par->win, par->w, x, y);
        //그 위에 벽을 그려줌
	}
	else if (a == 'P' || a == 'C' || a == 'E')
    // P,C,E중 하나라면
	{
		mlx_put_image_to_window(\
		par->mlx, par->win, par->g, x, y);
        //땅을 우선 그려주고
		if (a == 'P')
			mlx_put_image_to_window(\
			par->mlx, par->win, par->p, x, y);
            // P라면 캐릭터를 그려줌
		else if (a == 'C')
			mlx_put_image_to_window(\
			par->mlx, par->win, par->c, x, y);
            // C라면 수집품을 그려줌
		else if (a == 'E')
			mlx_put_image_to_window(\
			par->mlx, par->win, par->e, x, y);
            // E라면 탈출구를 그려줌
	}
}

6. keypress()

int	key_press(int keycode, t_param *par)
{
	if (keycode == KEY_W)
	{
		move_up(par, par->p_y);
        // W를 누르면 up 호출
	}
	else if (keycode == KEY_S)
	{
		move_down(par, par->p_y);
        //S누르면 down 호출
	}
	else if (keycode == KEY_A)
	{
		move_left(par, par->p_y);
        //A누르면 left호출
	}
	else if (keycode == KEY_D)
	{
		move_right(par, par->p_y);
        //D누르면 right 호출
	}
	else if (keycode == KEY_ESC)
		esc_press(keycode, par);
        //esc 누르면 종료 호출
	printf("step: %d\n", par->move);
    //움직인 횟수 출력
	drawmap(par);
    //맵을 다시 그려줌
	return (0);
}

void	move_left(t_param *par, int y) // 왼쪽으로 이동
{
	t_mapline	*curline;
	char		*str;

	curline = par->map;
	while (y-- != 0)
		curline = curline->next;
        //플레이어의 y좌표에 해당하는 문자열로 이동
	str = curline->line;
	if (str[par->p_x - 1] == '1')
		return ;
        // 만약 이동할 위치가 1이라면 벽이므로 그냥 종료
	else if (str[par->p_x - 1] == 'E')
		if (get_e(par))
			return ;
           	// 이동할 위치가 탈출구이고 조건이 맞다면 게임종료, 안맞다면 함수를 종료
	if (str[par->p_x - 1] == 'C')
		par->count_c--;
        // 이동할 위치가 수집품이라면 수집품 카운트를 줄여줌
	str[par->p_x - 1] = 'P';
    // 이동할 위치를 P로 바꿔주고
	str[par->p_x] = '0';
    // 원래 위치를 0으로 바꿔줌
	par->move++;
    // 이동횟수 1 증가
	set_p(par);
    // 플레이어의 좌표값을 수정해줌
}

void	move_right(t_param *par, int y) // 오른쪽으로 이동
{
// (left와 동일하게 작동함)
	t_mapline	*curline;
	char		*str;

	curline = par->map;
	while (y-- != 0)
		curline = curline->next;
	str = curline->line;
	if (str[par->p_x + 1] == '1')
		return ;
	else if (str[par->p_x + 1] == 'E')
		if (get_e(par))
			return ;
	if (str[par->p_x + 1] == 'C')
		par->count_c--;
	str[par->p_x + 1] = 'P';
	str[par->p_x] = '0';
	par->move++;
	set_p(par);
}

void	move_up(t_param *par, int y) // 위로 이동하는 함수
{
	t_mapline	*curline;
	char		*str;

	curline = par->map;
	str = curline->line;
	while (y != 0)
	{
		if (y == 1) // y가 1일때 즉 이동할 y좌표를 보고있을 때
		{
			if (str[par->p_x] == '1')
				return ;
                // 갈 위치가 1이라면 함수 종료
			else if (str[par->p_x] == 'E')
				if (get_e(par))
					return ;
                    // 갈 위치가 E이고 탈출조건이 맞으면 게임종료 아니면 함수 종료
			if (str[par->p_x] == 'C')
				par->count_c--;
                // 갈 위치가 C라면 수집품 카운트 -1
			str[par->p_x] = 'P';
            // 갈 위치를 P로 변경해줌
		}
		curline = curline->next; // 다음 문자열로 (한줄 아래)
		str = curline->line;
		y--; // 반복마다 y값 감소
	}
	str[par->p_x] = '0'; // 현재문자열의  x좌표위치를 0으로 바꿈
	par->move++; // 이동횟수 1 증가
	set_p(par); // 플레이어 좌표 수정
}

void	move_down(t_param *par, int y) // 아래로 이동하는 함수
{
	t_mapline	*curline;
	char		*str;
	char		*savestr;

	curline = par->map;
	while (y != 0) // 현재 플레이어가 있는 y좌표까지 문자열 이동
	{
		curline = curline->next;
		y--;
	}
	savestr = curline->line; // 현재y좌표 문자열을 저장해둠
	curline = curline->next; // 다음 문자열로
	str = curline->line;
	if (str[par->p_x] == '1')
		return ;
        // 갈곳이 1이라면 함수 종료
	else if (str[par->p_x] == 'E')
		if (get_e(par))
			return ;
            // 이동할 위치가 탈출구이고 조건이 맞다면 게임종료, 안맞다면 함수를 종료
	if (str[par->p_x] == 'C')
		par->count_c--;
        // 이동할 위치가 수집품이라면 수집품 카운트를 줄여줌
	str[par->p_x] = 'P';
    //이동할 위치를 P로 바꿔주고
	savestr[par->p_x] = '0';
    // 아까 저장해둔 현재문자열의  현재위치를 0으로 변경
	par->move++;
    // 이동횟수 증가
	set_p(par);
    // 플레이어 좌표 수정
}
profile
늅늅

1개의 댓글

comment-user-thumbnail
2023년 9월 30일

안녕하세요, par->g = mlx_xpm_file_to_image(par->mlx, "imgs/g.xpm", &par->wi, &par->he); 이부분이 나중에 어떻게 쓰이는지 잘 이해가 안되는데 설명 부탁드려도 될까요? 제가 궁금한 부분은 &par->wi, &par->he의 wi나 he이 값이 다른 그림의 값으로 덮어씌워지지 않는가거든요.

답글 달기