Básico Sobre A MiniLibX
Tags: tutorial, beginners, c, coding
Published: 2024-01-26 22:00:00

## Introdução

Olá, pessoal, hoje eu gostaria de fazer um pequeno tutorial sobre usar a [_MLX francesa_](https://github.com/42Paris/minilibx-linux).
Por mais que ela seja ruim, ela é bem básica, mas mesmo assim pega gente de surpresa.
PS. Esse texto é para dar um pequeno guia para quem tentou fazer algo e recebeu um segfault na cara.

## Tabela de conteúdo

1. [Começando](#start)
2. [Imagens](#imagem)
3. [Teclado](#teclas)
4. [Mouse](#rata)
5. [XPM](#png)
6. [Problemas Com A Produção](#problem)
7. [Vídeo](#video)

<a id="start"></a>
### Começando

Bom todo projeto começa com uma preparação e a minha foi de fazer um simples Makefile:

```Makefile
TARGET=Pong.exec
LIB_MLX=minilibx/libmlx.a
FLAGS=-Wall -Wextra -Werror
MLX_FLAGS=-lXext -lX11 -lm -lz
SOURCES=main.c

all: ${TARGET}

${LIB_MLX}:
	@make -C minilibx

${TARGET}: ${LIB_MLX} ${SOURCES}
	cc ${FLAGS} -g3 -Iminilibx ${SOURCES} minilibx/libmlx.a -o ${TARGET} ${MLX_FLAGS}

clean:
	rm -f ${TARGET}
.PHONY: clean

re: clean all
.PHONY: re
```

Depois disso seguimos com uma main bem simples e direta:

```c
#include "minilibx/mlx.h"
#include <stdlib.h>

typedef struct ClassMlx
{
  void* mlx;
  void* win;
} ClassMlx;

int main(void)
{
  ClassMlx* engine = calloc(1, sizeof(ClassMlx));
  int ret = 0;

  ret = initEngine(engine);
  if (ret < 0) {
    free(engine);
    return (ret);
  }
  mlx_loop(engine->mlx);
  destroyEngine(engine);
  free(engine);
  return (0);
}

int initEngine(ClassMlx* engine)
{
  engine->mlx = mlx_init();
  if (engine->mlx == NULL) {
    return (-1);
  }
  engine->win = mlx_new_window(engine->mlx, 800, 600, "PONG");
  if (engine->win == NULL) {
    mlx_destroy_display(engine->mlx);
    free(engine->mlx);
    return (-2);
  }
  return (0);
}

void destroyEngine(ClassMlx* engine)
{
  mlx_destroy_window(engine->mlx, engine->win);
  mlx_destroy_display(engine->mlx);
  free(engine->mlx);
}
```

Não vou entrar em muitos detalhes sobre como as coisas funcionam debaixo dos panos, mas citei um pouco na minha [documentação](https://github.com/andreyvdl/MiniLibX_my_docs), vamos focar na produção. Nesse momento ao rodar o código temos uma tela preta que não fecha, por isso faça Ctrl+C no terminal.

![Espero que isso renderize uma janela com tela preta](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tfmq3zji6w27b7zow5oh.png)

<a id="imagem"></a>
### Imagens

Bom vamos alterar um pouco o código para desenhar um quadrado branco na tela

```c
#include "minilibx/mlx.h"
#include <stdlib.h>

typedef struct ClassMlx
{
  void* mlx;
  void* win;
  void* img;
} ClassMlx;

int main(void)
{
  ClassMlx* engine = calloc(1, sizeof(ClassMlx));
  int ret = 0;

  ret = initEngine(engine);
  if (ret < 0) {
    free(engine);
    return (ret);
  }
  mlx_loop_hook(engine->mlx, renderGame, engine);
  mlx_loop(engine->mlx);
  destroyEngine(engine);
  free(engine);
  return (0);
}


int initEngine(ClassMlx* engine)
{
  engine->mlx = mlx_init();
  if (engine->mlx == NULL) {
    return (-1);
  }
  engine->win = mlx_new_window(engine->mlx, 800, 600, "PONG");
  if (engine->win == NULL) {
    mlx_destroy_display(engine->mlx);
    free(engine->mlx);
    return (-2);
  }
  engine->img = mlx_new_image(engine->mlx, 800, 600);
  if (engine->img == NULL) {
    mlx_destroy_window(engine->mlx, engine->win);
    mlx_destroy_display(engine->mlx);
    free(engine->mlx);
    return (-3);
  }
  return (0);
}

int renderGame(void* param)
{
  ClassMlx* engine = (ClassMlx*)param;

  renderMap(engine);
  mlx_put_image_to_window(engine->mlx, engine->win, engine->img, 0, 0);
  return (0);
}

void renderMap(ClassMlx* engine)
{
  for (int y = 0; y < 300; y++) {
    for (int x = 0; x < 400; x++) {
      drawPixel(engine->img, x, y, 0xFFFFFF);
    }
  }
}

void drawPixel(void* img, int x, int y, int color)
{
  int bpp = 0;
  int size_line = 0;
  int endian = 0;
  char* data = mlx_get_data_addr(img, &bpp, &size_line, &endian);
  char* pixel = NULL;

  /*
   * explicacao da formula:
   * (y * size_line) = posicao vertical com base na largura da imagem.
   * (bpp / 8) = converte os bytes da imagem em uma linha de pixels
   * (x * (bpp / 8)) = escolhe o pixel da linha
   */
  pixel = data + (y * size_line) + (x * (bpp / 8));
  *(int*)pixel = color;
}

void destroyEngine(ClassMlx* engine)
{
  mlx_clear_window(engine->mlx, engine->win);
  mlx_destroy_image(engine->mlx, engine->img);
  mlx_destroy_window(engine->mlx, engine->win);
  mlx_destroy_display(engine->mlx);
  free(engine->mlx);
}
```

Com isso você deve ter uma tela preta com um retângulo branco no canto superior esquerdo.

![Tipo esse](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/71252w3b5e3z6dkfj3d1.png)

Vou só fazer uma pequena edição de uma função para nunca mais mexer nela (se você fizer a mesma alteração vai ter uma linha no meio da tela).

```c
void renderMap(ClassMlx* engine)
{
  for (int y = 0; y < 600; y++) {
    drawPixel(engine->img, 400, y, 0xFFFFFF);
  }
}
```

<a id="teclas"></a>
### Teclado

Vamos mexer de novo com a main para poder controlar a raquete de pong (também aproveitei e adicionei um hook na função `setEngine` que permite fechar a janela com o botão X):

```c
#include "minilibx/mlx.h"
#include <X11/X.h>
#include <X11/keysym.h>
#include <stdlib.h>

typedef struct ClassPlayer
{
  int x;
  int y;
  int moveStyle;
} ClassPlayer;

typedef struct ClassMlx
{
  void* mlx;
  void* win;
  void* img;
  ClassPlayer player;
} ClassMlx;

int main(void)
{
  ClassMlx* engine = calloc(1, sizeof(ClassMlx));
  int ret = 0;

  ret = initEngine(engine);
  if (ret < 0) {
    free(engine);
    return (ret);
  }
  setEngine(engine);
  mlx_loop(engine->mlx);
  destroyEngine(engine);
  free(engine);
  return (0);
}

int initEngine(ClassMlx* engine)
{
  engine->player.x = 0;
  engine->player.y = 300;
  engine->player.moveStyle = 0;
  engine->mlx = mlx_init();
  if (engine->mlx == NULL) {
    return (-1);
  }
  engine->win = mlx_new_window(engine->mlx, 800, 600, "PONG");
  if (engine->win == NULL) {
    mlx_destroy_display(engine->mlx);
    free(engine->mlx);
    return (-2);
  }
  engine->img = mlx_new_image(engine->mlx, 800, 600);
  if (engine->img == NULL) {
    mlx_destroy_window(engine->mlx, engine->win);
    mlx_destroy_display(engine->mlx);
    free(engine->mlx);
    return (-3);
  }
  return (0);
}

void setEngine(ClassMlx* engine)
{
  mlx_key_hook(engine->win, keyEvents, engine);
  mlx_hook(engine->win, DestroyNotify, NoEventMask, mlx_loop_end, engine->mlx);
  mlx_loop_hook(engine->mlx, renderGame, engine);
}

int keyEvents(int keycode, void* param)
{
  ClassMlx* engine = (ClassMlx*)param;

  if (keycode == XK_Up && engine->player.y > 10) {
    engine->player.y -= 10;
  }
  if (keycode == XK_Down && engine->player.y < 590) {
    engine->player.y += 10;
  }
  if (keycode == XK_space) {
    engine->player.moveStyle = !engine->player.moveStyle;
  }
  if (keycode == XK_Escape) {
    mlx_loop_end(engine->mlx);
    return (0);
  }
  return (0);
}

int renderGame(void* param)
{
  ClassMlx* engine = (ClassMlx*)param;

  // esse for e para limpar a imagem antes de pintar ela de novo.
  for (int y = 0; y < 600; y++) {
    for (int x = 0; x < 800; x++) {
      drawPixel(engine->img, x, y, 0);
    }
  }
  renderMap(engine);
  renderPlayer(engine);
  mlx_put_image_to_window(engine->mlx, engine->win, engine->img, 0, 0);
  return (0);
}

void renderPlayer(ClassMlx* engine)
{
  for (int pos_x = 0; pos_x < 5; pos_x++) {
    for (int pos_y = engine->player.y - 10; pos_y < engine->player.y + 10; pos_y++) {
      if (pos_y < 0 || pos_y >= 600) {
        continue;
      }
      drawPixel(engine->img, pos_x, pos_y, 0xFF0000);
    }
  }
}
```

![Tá começando a parecer um pong](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ag7s31iu4tlxma3uvq9p.png)

Só de extra vamos adicionar os placares:

```c
int renderGame(void* param)
{
  ClassMlx* engine = (ClassMlx*)param;

  for (int y = 0; y < 600; y++) {
    for (int x = 0; x < 800; x++) {
      drawPixel(engine->img, x, y, 0);
    }
  }
  renderMap(engine);
  renderPlayer(engine);
  mlx_put_image_to_window(engine->mlx, engine->win, engine->img, 0, 0);
  mlx_string_put(engine->mlx, engine->win, 200, 42, 0xFF00, "0");
  mlx_string_put(engine->mlx, engine->win, 600, 42, 0xFF00, "0");
  return (0);
}
```

![0x0](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ej8ghojws987st0lssob.png)

<a id="rata"></a>
### Mouse

Vocês devem ter notado que configurei o espaço para alterar uma variável, mas ele não é utilizada em lugar nenhum, pois agora vamos utiliza-la:

```c
int renderGame(void* param)
{
  ClassMlx* engine = (ClassMlx*)param;

  for (int y = 0; y < 600; y++) {
    for (int x = 0; x < 800; x++) {
      drawPixel(engine->img, x, y, 0);
    }
  }
  movePlayerMouse(engine);
  renderMap(engine);
  renderPlayer(engine);
  mlx_put_image_to_window(engine->mlx, engine->win, engine->img, 0, 0);
  mlx_string_put(engine->mlx, engine->win, 200, 42, 0xFF00, "0");
  mlx_string_put(engine->mlx, engine->win, 600, 42, 0xFF00, "0");
  return (0);
}

void movePlayerMouse(ClassMlx* engine)
{
  int x = 0;
  int y = 0;

  if (engine->player.moveStyle == 0) {
    return;
  }
  mlx_mouse_get_pos(engine->mlx, engine->win, &x, &y);
  (void)x;
  if (y > 10 && y < 590) {
    engine->player.y = y;
  }
}
```

Sei que para algumas pessoas parece certo utilizar a `mlx_mouse_hook` para pegar o movimento do mouse, mas, na verdade do metódo que fiz agora, podemos pegar a posição do mouse antes de renderizar a raquete (sem contar que o mouse_hook só é ativado quando o mouse tem um clique ou scroll).

![Ué não mudou?](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rmnov2uh0z9e0cu87wjv.png)

<a id="png"></a>
### XPM

[XPM](https://en.wikipedia.org/wiki/X_PixMap) é um formato de imagem bem desconhecido, mas que conquiste em um array de string do C.

```txt
/* XMP3 */
static char *place_holder[] = {
"5 30 2 1",
"+ c #FFFFFF",
"- c #0000FF",
"+----",
"++---",
"+++--",
"++++-",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"++++-",
"+++--",
"++---",
"+----"
};
```

Pra facilitar as coisas a MiniLibX só aceita XPM e ela carrega como uma imagem igual a `mlx_new_image`:

```c
int renderGame(void* param)
{
  ClassMlx* engine = (ClassMlx*)param;

  for (int y = 0; y < 600; y++) {
    for (int x = 0; x < 800; x++) {
      drawPixel(engine->img, x, y, 0);
    }
  }
  movePlayerMouse(engine);
  renderMap(engine);
  // renderPlayer(engine);
  mlx_put_image_to_window(engine->mlx, engine->win, engine->img, 0, 0);
  mlx_string_put(engine->mlx, engine->win, 200, 42, 0xFF00, "0");
  mlx_string_put(engine->mlx, engine->win, 600, 42, 0xFF00, "0");
  renderPlayer2(engine);
  return (0);
}

void renderPlayer2(ClassMlx* engine)
{
  int width = 0;
  int height = 0;
  void* texture = mlx_xpm_file_to_image(engine->mlx, "./raquete.xpm", &width, &height);

  if (engine->player.y > 0 && engine->player.y < 600) {
    mlx_put_image_to_window(engine->mlx, engine->win, texture, engine->player.x, engine->player.y);
  }
}
```

Preste atenção na ordem das put_image_to_window se não você vai ter apenas uma tela preta com uma linha no meio e dois números.

![Juro que era pra ser uma raquete](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f9h17386cuqahvj36qcm.png)

E pronto, acho que isso é realmente o básico que você precisa pra fazer algo com a MiniLibX, claro existem muitas outras funções, mas como citei você pode achar elas na minha [documentação](https://github.com/andreyvdl/MiniLibX_my_docs).

<a id="problem"></a>
#### Problemas Com A Produção

> Se você não falha em pelo menos 90% das vezes, seus objetivos não foram ambiciosos o suficiente. - KAY, Alan

Que ótima frase para começar essa seção (honestamente peguei ela quando comecei a escrever isso), isso, pois montar esse tutorial foi uma grande falha.
Para começar, a primeira falha foi do primeiro vídeo, que ficou parecendo uma reunião de PowerPoint (essa parte foi cortada do vídeo final).
A segunda foi eu ter que lidar com o Vim depois de 1 ano sem tocar nele, e ainda ter o GitHub Copilot me recomendando código errado que me fez pensar que eu não lembrava mais como codar com a MiniLibX.
Já a terceira falha foi o fato de tentar gravar áudio no calor com o ventilador desligado.
A quarta falha foi ver que eu leio um tele prompt mais rápido do que o texto sobe.
E a quinta falha foi por não conseguir gravar áudio porque o microfone do meu notebook não é sensível, o que me obrigaria a gritar.
Com isso, tudo o que sobrou foi um roteiro e um vídeo que se tornaram esse post.

<a id="video"></a>
#### Vídeo

{% embed https://www.youtube.com/watch?v=UPTAYrVtSi8 %}