프로젝트를 끝내고 나니 블랙홀이 얼마 남지 않았다..🥲 9일 안에 과제를 뿌시고 살아남을 수 있을지... 정신차렷!
사실 42gg를 시작하기 전에 하루 정도 이 과제 발가락을 담갔다 뺐었다. 윈도우에 이미지를 띄우는 것까지 성공했지만 2달이나 지나버린 지금.. 아무것도 기억이 나지 않는다! 차근차근 기억을 살려 해내보자.
open, close, read, write, printf, malloc, free, perror, strerror, exit
놀랍게도 printf
가 허용! 처음 보는 함수가 몇 개 있으니 살펴보자.
#include <stdio.h>
void perror(const char* str);
perror
를 알기 위해서는 errno
에 대한 이해가 필요하다. errno
는 전역 변수로 라이브러리 함수에 의해 발생한 오류에 대한 정보를 가지고 있다. 즉, 오류의 원인을 알아내기 위해 사용되는 것이다. 하지만 다른 함수를 실행하면 errno
의 값이 바뀌기 때문에 errno
변수를 설정한 함수 호출 직후에만 유효하다.
perror
는 errno
의 값을 해석하여 해당하는 오류 메세지를 출력한다. 시스템 오류 메세지는 플랫폼이나 컴파일러에 따라 달라질 수 있다. 인자로 들어가는 str
는 사용자 정의 메시지로, 널 포인터가 아니라면 시스템 오류 메시지 앞에 출력된다. 관습적으로 프로그램의 이름이 주로 사용된다. 이 때 두 개의 메세지는 콜론(:
)으로 구분되며, 맨 마지막에는 개행 문자(\n
)가 출력된다.
#include <string.h>
char *strerror(int errnum);
오류 메세지 문자열을 가리키는 포인터를 얻어온다. 여기서 인자로 받는 errnum
는 오류 번호인데, strerror
는 해당 오류 번호에 맞는 오류 메세지를 가리키는 포인터를 리턴한다. 오류 메세지는 사용중인 컴파일러나 플랫폼에 따라 다를 수 있다.
printf
에 strerror
를 적절히 넣어서 사용한다면, 위의 perror
와 같은 형태로 사용할 수 있겠다.
간단히 말하면 플레이어가 맵에 있는 모든 아이템을 얻어 문으로 탈출하는 게임을 만드는 것이다. 보너스 부분에는 움직이는 적이 추가된다. 추가로 서브젝트에서 요구하는 몇 가지 사항들이 있다.
minilibx
라이브러리를 사용해야 한다.- 맵은 "벽", "아이템", "빈 공간"의 세 요소로 구성되어야 한다.
- 플레이어는 맵에 표시된 모든 아이템을 획득한 후 탈출구로 나가야 한다.
- 플레이어가 움직일 때 마다 쉘에 움직인 횟수가 표시되어야 한다.
- 플레이어는 위(
w
), 아래(s
), 오른쪽(d
), 왼쪽(a
)으로 움직일 수 있다.- 플레이어는 벽을 넘을 수 없다.
esc키
또는 윈도우의x버튼
을 누르면 프로그램이 종료된다.- 프로그램은 인자로
.ber
형식의 맵을 받는다.- 맵 파일은 다음의 요소들로만 구성된다.
0
: 빈 공간
1
: 벽
C
: 아이템
E
: 탈출구
P
: 플레이어의 시작 위치- 맵은 벽으로 둘러싸여 있어야 한다.
- 맵에는 최소 하나의 탈출구와 아이템이 있어야 하며, 하나의 플레이어 시작점이 있어야 한다.
- 맵은 사각형이어야 한다.
- 프로그램에 오류가 생기면 프로그램을 종료시키고
"Error/n"
라는 문구와 명시적인 오류 메시지를 출력해야 한다.
42서울에서는 minilibx
라는 라이브러리를 사용하여 이미지를 처리한다. 라이브러리에 포함되어 있는 함수들 중 최소한을 사용했다.
- prototype:
void *mlx_init();
- description:
mlx 사용을 위해 가장 먼저 사용하는 함수다. 내 소프트웨어와 디스플레이를 연결해준다.- return:
성공 시void*
(이하mlx_ptr
)
연결 실패 시NULL
- 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은 각 창을 구분해주는 고유의 값이다.
- prototype:
int mlx_clear_window(void *mlx_ptr, void *win_ptr);
- description:
윈도우 창을 검은색으로 초기화한다.
- prototype:
int mlx_destroy_window(void *mlx_ptr, void *win_ptr);
- description:
윈도우 창을 삭제한다.
- 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
- 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 좌표
- 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
- prototype:
int mlx_loop (void *mlx_ptr);
- description:
이벤트를 기다리다가 특정 이벤트가 발생했을 때 사용자 정의 함수를 호출하는 무한 루프 함수다. 창을 계속적으로 랜더링 하기 위해서 사용된다.
- prototype:
int mlx_loop_hook (void *mlx_ptr, int (*funct_ptr)(), void *param);
- description:
이벤트가 발생하지 않을 때 호출되는 함수다. 게임에 애니메이션을 추가하기 위해 사용되므로, mandatory part만 풀 계획이라면 사용하지 않아도 되는 함수다.
- 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
: 화면에 그릴 문자열
윈도우에 맵을 띄우기 위해서는 map.ber
의 형태로 들어오는 파일을 읽어내야만 한다. 당연히 open
함수를 이용해 fd
값을 받은 다음 파일을 읽어내는 형태가 되는데, 이 때 두 가지 선택지가 나온다.
- open(1) 하면서 개행이 몇 개인지 세어 줄 수를 알아낸다.
- 줄 수로 이중 배열을
(char **)calloc(줄 수 + 1, sizeof(char *))
의 형태로 할당한다.- open(2) 해서 할당한 배열에 맵을 한 줄씩 저장한다.
- open(1) 해서
get_next_line
,strjoin
으로 맵을 한 줄로 붙여버린다.- 붙인 맵을
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
함수로 그려주는 형태인 것이다.
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
을 다시 해준다.