# `$.grid` — сеточные помощники

Инструменты для работы с двумерными данными: «какой тайл под курсором»,
«залить комнату», «провести линию», «обойти соседей». Это **не** навигация:
`$.nav` ищет путь по препятствиям, а `$.grid` — просто арифметика над
плоским массивом. Аналог `TileMap`-утилит и `GridContainer`-математики из
Godot, но без привязки к тайлсету.

Сетка — обычный объект с полем `data` (плоский массив значений), поэтому её
можно заполнить чем угодно (числа, строки, объекты), сохранить в JSON,
нарисовать или передать в `$.nav`.

```js
const g = $.grid.make({ x: 0, y: 0, cell: 16, cols: 40, rows: 30, fill: 0 });

const c = $.grid.toCell(g, mouse.x, mouse.y);
if ($.grid.inBounds(g, c.cx, c.cy)) {
    $.grid.set(g, c.cx, c.cy, 1);              // поставить блок
    $.grid.flood(g, c.cx, c.cy, 2);            // залить комнату
}
```

Проверка без движка:

```bash
build/_deps/quickjs-build/qjs tests/js/grid_test.mjs
```

---

## 1. Система координат

Она задана явно, чтобы не было «полклетки» на глаз:

| Величина | Смысл |
|---|---|
| `x`, `y` | Левый верхний угол сетки в **мировых пикселях** |
| `cell` | Сторона клетки в пикселях |
| `cx`, `cy` | Номер клетки: `0..cols-1`, `0..rows-1` |
| `toCell(g, wx, wy)` | Мировая точка → клетка (может быть за границей) |
| `toWorld(g, cx, cy)` | Клетка → **центр** клетки в мире |
| `cellRect(g, cx, cy)` | Клетка → прямоугольник `{x, y, w, h}` для отрисовки |
| `bounds(g)` | Вся сетка в мире: `{x, y, w: cols*cell, h: rows*cell}` |

Клетка `(cx, cy)` занимает мир `[x + cx*cell, x + (cx+1)*cell)` по X и так же
по Y. Точка ровно на левой границе попадает в левую клетку.

```js
const r = $.grid.bounds(g);                     // куда поставить камеру-ограничитель
for (const { cx, cy, value } of $.grid.neighbors(g, 4, 4, true)) { ... }
```

## 2. Создание

### `$.grid.make(opts) → grid | null`

| Поле | Тип | По умолчанию | Смысл |
|---|---|---|---|
| `x`, `y` | число | `0` | Левый верхний угол в мире |
| `cell` | число | `32` | Сторона клетки; мусор и `0` заменяются на `32` |
| `cols`, `rows` | число | — | Размер в клетках |
| `w`, `h` | число | — | Размер в пикселях: `cols = ceil(w / cell)` |
| `fill` | любое | `0` | Значение всех клеток при создании |

Размер можно задать либо в клетках (`cols`/`rows`), либо в пикселях
(`w`/`h`). Если не задано ни то, ни другое — возвращается `null`: молча
создавать сетку 0×0 опаснее, чем сообщить об ошибке.

Поля готовой сетки: `x`, `y`, `cell`, `cols`, `rows`, `fill`, `data`
(плоский массив длиной `cols * rows`, индекс — `cy * cols + cx`), `version`
(растёт при каждом изменении — удобно для кэшей отрисовки).

## 3. Чтение и запись

| Функция | Назначение |
|---|---|
| `inBounds(g, cx, cy) → bool` | Клетка внутри сетки |
| `at(g, cx, cy, fallback?) → any` | Значение клетки; за границей — `fallback` (`undefined`) |
| `set(g, cx, cy, value) → g` | Записать; за границей — тихо игнорируется |
| `fill(g, value) → g` | Залить всю сетку |
| `clear(g, value?) → g` | Вернуть к `g.fill` или к указанному значению |
| `count(g, value) → number` | Сколько клеток равны значению |
| `rect(g, cx, cy, w, h, value) → g` | Прямоугольник `w×h` **в клетках**, обрезается по границе |

`set` меняет `version` только когда значение действительно изменилось.

```js
$.grid.rect(g, 2, 2, 5, 3, 'стена');       // 5 клеток в ширину, 3 в высоту
$.grid.count(g, 'стена');                  // → 15
```

## 4. Линии и заливка

| Функция | Назначение |
|---|---|
| `bresenham(x0, y0, x1, y1) → [{cx,cy}, …]` | Клетки отрезка (чистая функция, без сетки) |
| `line(g, x0, y0, x1, y1, value) → g` | Провести линию по сетке |
| `flood(g, cx, cy, value, opts?) → number` | Заливка «ведром»; возвращает число изменённых клеток |

`flood` заменяет все соседние клетки со значением, как в стартовой:

* `opts.diagonal` — заливать и по диагонали (по умолчанию только 4 стороны);
* `opts.limit` — предохранитель на размер заливки.

Реализация итеративная (без рекурсии), поэтому заливка большого поля не
переполняет стек. Если стартовая клетка уже равна `value`, возвращается `0`.

```js
const room = $.grid.flood(g, 10, 10, 'пол');        // 4-связная комната
if (room > 400) $.log('комната большая');
$.grid.line(g, 0, 0, 39, 29, 'стена');              // диагональ через всю карту
```

## 5. Обход

| Функция | Назначение |
|---|---|
| `forEach(g, fn) → number` | `fn(value, cx, cy)` по всем клеткам, в порядке строк; возвращает число вызовов |
| `neighbors(g, cx, cy, diagonal?) → [{cx, cy, value}, …]` | Соседи: 4 (вправо, влево, вниз, вверх) или 8 |

Соседи за границей сетки не возвращаются, поэтому цикл по ним не требует
проверок.

```js
$.grid.forEach(g, (value, cx, cy) => {
    if (value === 'вода') drawWater($.grid.cellRect(g, cx, cy));
});

let open = 0;
for (const n of $.grid.neighbors(g, cx, cy)) if (n.value === 0) open++;
```

## 6. Установка и связь с `$.nav`

```js
import { installGrid, makeGrid } from './grid.js';
installGrid($);      // $.grid = { make, toCell, toWorld, … }
```

Все методы — те же чистые функции: сетка передаётся первым аргументом,
поэтому две и более сетки в игре не мешают друг другу.

`$.grid` не подменяет `$.nav` и не знает про препятствия. Если нужен путь,
сетку навигации создавайте отдельно (`$.nav.grid`), а `$.grid` используйте
для данных. Переносить значения между ними можно вручную:

```js
const nav = $.nav.grid({ x: 0, y: 0, w: 640, h: 480, cell: 16 });
const g = $.grid.make({ x: 0, y: 0, cell: 16, cols: nav.cols, rows: nav.rows });
$.grid.forEach(g, (value, cx, cy) => { if (value === 'стена') nav.setBlocked(cx, cy, true); });
```

## 7. Ограничения

| Чего нет | Почему / что делать |
|---|---|
| Отрисовки | Сетка — данные; рисуйте через `$.gfx.push` или `$.tilemap`, координаты даёт `cellRect`/`bounds` |
| Тайлсета и слоёв | Это `$.tilemap`; `$.grid` про значения, а не про картинки |
| `flood` по своему условию (например, «по всем тайлам воды») | Фильтра нет; сделайте свой обход через `neighbors` + `set` |
| Хранения сетки в сохранении «из коробки» | `g.data` — обычный массив, `$.fs.write`/`$.store` сериализуют его как есть (для больших карт лучше RLE) |
| Разреженных и бесконечных сеток | Модель плотная: `cols * rows` ячеек в памяти |
| Копирования/сравнения сеток | `copy`/`equals` нет: `g.data.slice()` и сравнение массивов вручную |
