[42서울] so_long

tamagoyakii·2022년 7월 21일
0

42seoul

목록 보기
8/19
post-thumbnail

프로젝트를 끝내고 나니 블랙홀이 얼마 남지 않았다..🥲 9일 안에 과제를 뿌시고 살아남을 수 있을지... 정신차렷!

사실 42gg를 시작하기 전에 하루 정도 이 과제 발가락을 담갔다 뺐었다. 윈도우에 이미지를 띄우는 것까지 성공했지만 2달이나 지나버린 지금.. 아무것도 기억이 나지 않는다! 차근차근 기억을 살려 해내보자.

1️⃣ so_long

External functs.

open, close, read, write, printf, malloc, free, perror, strerror, exit

놀랍게도 printf가 허용! 처음 보는 함수가 몇 개 있으니 살펴보자.

1. perror

#include <stdio.h>

void perror(const char* str);

perror를 알기 위해서는 errno에 대한 이해가 필요하다. errno는 전역 변수로 라이브러리 함수에 의해 발생한 오류에 대한 정보를 가지고 있다. 즉, 오류의 원인을 알아내기 위해 사용되는 것이다. 하지만 다른 함수를 실행하면 errno의 값이 바뀌기 때문에 errno 변수를 설정한 함수 호출 직후에만 유효하다.

perrorerrno의 값을 해석하여 해당하는 오류 메세지를 출력한다. 시스템 오류 메세지는 플랫폼이나 컴파일러에 따라 달라질 수 있다. 인자로 들어가는 str는 사용자 정의 메시지로, 널 포인터가 아니라면 시스템 오류 메시지 앞에 출력된다. 관습적으로 프로그램의 이름이 주로 사용된다. 이 때 두 개의 메세지는 콜론(: )으로 구분되며, 맨 마지막에는 개행 문자(\n)가 출력된다.

2. strerror

#include <string.h>

char *strerror(int errnum);

오류 메세지 문자열을 가리키는 포인터를 얻어온다. 여기서 인자로 받는 errnum는 오류 번호인데, strerror는 해당 오류 번호에 맞는 오류 메세지를 가리키는 포인터를 리턴한다. 오류 메세지는 사용중인 컴파일러나 플랫폼에 따라 다를 수 있다.

printfstrerror를 적절히 넣어서 사용한다면, 위의 perror와 같은 형태로 사용할 수 있겠다.

Description

간단히 말하면 플레이어가 맵에 있는 모든 아이템을 얻어 문으로 탈출하는 게임을 만드는 것이다. 보너스 부분에는 움직이는 적이 추가된다. 추가로 서브젝트에서 요구하는 몇 가지 사항들이 있다.

  1. minilibx 라이브러리를 사용해야 한다.
  2. 맵은 "벽", "아이템", "빈 공간"의 세 요소로 구성되어야 한다.
  3. 플레이어는 맵에 표시된 모든 아이템을 획득한 후 탈출구로 나가야 한다.
  4. 플레이어가 움직일 때 마다 쉘에 움직인 횟수가 표시되어야 한다.
  5. 플레이어는 위(w), 아래(s), 오른쪽(d), 왼쪽(a)으로 움직일 수 있다.
  6. 플레이어는 벽을 넘을 수 없다.
  7. esc키 또는 윈도우의 x버튼을 누르면 프로그램이 종료된다.
  8. 프로그램은 인자로 .ber 형식의 맵을 받는다.
  9. 맵 파일은 다음의 요소들로만 구성된다.
    0 : 빈 공간
    1 : 벽
    C : 아이템
    E : 탈출구
    P : 플레이어의 시작 위치
  10. 맵은 벽으로 둘러싸여 있어야 한다.
  11. 맵에는 최소 하나의 탈출구와 아이템이 있어야 하며, 하나의 플레이어 시작점이 있어야 한다.
  12. 맵은 사각형이어야 한다.
  13. 프로그램에 오류가 생기면 프로그램을 종료시키고 "Error/n"라는 문구와 명시적인 오류 메시지를 출력해야 한다.

2️⃣ minilibx

42서울에서는 minilibx 라는 라이브러리를 사용하여 이미지를 처리한다. 라이브러리에 포함되어 있는 함수들 중 최소한을 사용했다.

1. mlx_init()

  • prototype:
void	*mlx_init();
  • description:
    mlx 사용을 위해 가장 먼저 사용하는 함수다. 내 소프트웨어와 디스플레이를 연결해준다.
  • return:
    성공 시 void*(이하 mlx_ptr)
    연결 실패 시 NULL

2. mlx_new_window

  • prototype:
void	*mlx_new_window(void *mlx_ptr, int size_x, int size_y, char *title);
  • description:
    새 창을 스크린에 띄운다. 실패 시 NULL 포인터를 반환한다.
  • parameter:
    size_x, size_y: 창 사이즈
    title: 창의 타이틀(바에 표시됨)
    mlx_ptr: mlx_init이 반환한 연결 식별자
  • return:
    성공 시 다른 void* 인 창 식별자(이하 win_ptr)
    창 생성 실패 시 NULL

MiniLibX는 n개의 각기 다른 창들을 제어할 수 있다. win_ptr은 각 창을 구분해주는 고유의 값이다.

3. mlx_clear_window

  • prototype:
int	mlx_clear_window(void *mlx_ptr, void *win_ptr);
  • description:
    윈도우 창을 검은색으로 초기화한다.

4. mlx_destroy_window

  • prototype:
int	mlx_destroy_window(void *mlx_ptr, void *win_ptr);
  • description:
    윈도우 창을 삭제한다.

5. mlx_xpm_file_to_image

  • prototype:
void	*mlx_xpm_file_to_image(void *mlx_ptr, char *filename, int *width, int *height);
  • description:
    말 그대로 xpm 형식의 파일을 mlx에서 사용할 수 있는 이미지로 변환 해주는 함수다.
  • parameter:
    filename: 사용할 xpm 파일의 이름
    width, height: 이미지의 너비, 이미지의 높이
  • return:
    성공 시 void*의 이미지 식별자(이하 img_ptr)
    실패 시 NULL

6. mlx_put_image_to_window

  • prototype:
int	mlx_put_image_to_window(void *mlx_ptr, void *win_ptr, void *img_ptr, int x, int y);
  • description:
    이미지를 윈도우에 그린다.
  • parameter:
    img_ptr: 사용할 이미지의 식별자
    x, y: 이미지가 그려질 x, y 좌표

7. mlx_hook

  • prototype:
int	mlx_hook(void *win_ptr, int x_event, int x_mask, int (*funct)(), void *param);
  • description:
    x_event에 지정된 이벤트가 발생할 때 마다 사용자 정의 함수를 호출한다.
  • parameter:
    x_event: 이벤트의 코드 값
    x_mask: 사용하지 않는 함수로, 일반적으로 0
    funct(): 이벤트가 발생했을 때 호출할 사용자 정의 함수
    param: 사용자 정의 함수로 보낼 인자

아래 링크에서 이벤트 코드와 이벤트에 따른 사용자 정의 함수의 형태를 자세히 볼 수 있다.
https://github.com/VBrazhnik/FdF/wiki/How-to-handle-mouse-buttons-and-key-presses%3F

8. mlx_loop

  • prototype:
int	mlx_loop (void *mlx_ptr);
  • description:
    이벤트를 기다리다가 특정 이벤트가 발생했을 때 사용자 정의 함수를 호출하는 무한 루프 함수다. 창을 계속적으로 랜더링 하기 위해서 사용된다.

9. mlx_loop_hook

  • prototype:
int	mlx_loop_hook (void *mlx_ptr, int (*funct_ptr)(), void *param);
  • description:
    이벤트가 발생하지 않을 때 호출되는 함수다. 게임에 애니메이션을 추가하기 위해 사용되므로, mandatory part만 풀 계획이라면 사용하지 않아도 되는 함수다.

10. mlx_string_put

  • prototype:
int	mlx_string_put(void *mlx_ptr, void *win_ptr, int x, int y, int color, char *string);
  • description:
    지정한 문자열을 화면에 그린다. 마찬가지로 bonus part를 위한 함수다.
  • parameter:
    x, y: 문자열이 그려질 x, y 좌표((0, 0)이 창의 좌측 상단)
    color: 문자열의 색(바이트 단위로 000-255 사이의 투명도|R|G|B|(0x00RRGGBB) 값)
    string: 화면에 그릴 문자열

3️⃣ Map parsing

윈도우에 맵을 띄우기 위해서는 map.ber의 형태로 들어오는 파일을 읽어내야만 한다. 당연히 open 함수를 이용해 fd 값을 받은 다음 파일을 읽어내는 형태가 되는데, 이 때 두 가지 선택지가 나온다.

  1. open(1) 하면서 개행이 몇 개인지 세어 줄 수를 알아낸다.
  2. 줄 수로 이중 배열을 (char **)calloc(줄 수 + 1, sizeof(char *))의 형태로 할당한다.
  3. open(2) 해서 할당한 배열에 맵을 한 줄씩 저장한다.
  1. open(1) 해서 get_next_line, strjoin으로 맵을 한 줄로 붙여버린다.
  2. 붙인 맵을 split으로 이중 배열에 저장한다.

오픈을 두 번 하고 싶지 않아서 후자를 선택하려 했으나, strjoin을 하면서 오히려 더 큰 메모리 낭비가 생길 것 같아서 전자를 선택했다.
맵을 다 받았다면, 한 칸 한 칸에 해당하는 이미지를 윈도우에 그려주어야 한다.

int	print_map(t_info *info)
{
	int	row;
	int	col;

	row = -1;
	while (++row < info->map.row)
	{
		col = -1;
		while (++col < info->map.col)
		{
			put_img(info, row, col);
		}
	}
	return (0);
}

info->map.row, info->map.col은 맵의 전체 세로, 가로 수를 저장한 변수이다. 즉, 모든 맵을 돌면서 각 요소에 해당하는 이미지를 put_img 함수로 그려주는 형태인 것이다.

4️⃣ Operate

map parsing이 끝났다면, 플레이어를 움직여야 한다. 위에 mlx_hook을 사용했기 때문에 키 이벤트에 대한 사용자 지정 함수를 만들어 주어야 한다.

mlx_hook(info->win.win_ptr, 2, 0, key_press, info);

int	key_press(int keycode, t_info *info)
{
	if (keycode == KEY_W)
		move(-1, 0);
	else if (keycode == KEY_S)
		move(1, 0);
	else if (keycode == KEY_A)
		move(0, -1);
	else if (keycode == KEY_D)
		move(0, 1);
	else if (keycode == KEY_ESC)
		close_win(&info->win);
	print_map(info);
	return (0);
}

나는 간단하게 위의 형태로 구현했다. mlx_hook의 x_event에서 2는 키보드를 누르는 이벤트를 뜻한다. 3은 키보드를 떼는 이벤트이다. 처음에는 3을 쓰려고 했다. 하지만 서브젝트에서 "플레이어가 부드럽게 움직여야 한다"고 언급한 부분이 키보드를 꾹 누르고 있을 때에도 플레이어가 움직여야 한다는 의미가 아닐까 싶어 이벤트2를 선택했다.
키 코드를 비교하여 wasd 중 하나라면 move 함수로 이동해 플레이어를 움직이고 esc라면 윈도우 창을 닫는다. move 함수는 이중 배열로 저장한 맵을 수정하는 함수인데, 수정이 끝나고 나면 맵에 그려진 이미지도 바뀌어야 하기 때문에 print_map을 다시 해준다.

참고

https://modoocode.com/53
https://modoocode.com/105

0개의 댓글