# 2D Game Grid (square)
A simple square grid made for games with built-in features like:
- get the shortest path between cells
- list reachable cells (pathfinding)
- list cell edges
- list cell neighbors
- get distance between cells
- list cells in distance

Missing a feature? [Create an issue](https://github.com/mstuercke/2d-game-grid/issues)!

## Examples & Demos
- [example code](https://codesandbox.io/s/y3xt8s)  
  a basic implementation with code examples
- [interactive demo](https://3ms9ky.csb.app/)  
  a customizable playground to visualize all the features

## Usage
### Installation
Install [@2d-game-grid/square](https://www.npmjs.com/package/@2d-game-grid/square) to your project with one of the following commands: 
#### npm
```
npm install @2d-game-grid/square
``` 
#### yarn
```
yarn add @2d-game-grid/square
```
#### bun
```
bun add @2d-game-grid/square
```

### Create a grid
Both examples will create the exact same grid
#### Use pre-generated cells
```ts
import {SquareGrid} from '@2d-game-grid/square';

const grid = new SquareGrid({
  grid: [
    ['0-0', '0-1', '0-2', '0-3'],
    ['1-0', '1-1', '1-2', '1-3'],
    ['2-0', '2-1', '2-2', '2-3']
  ]
});
```

#### Generate cells on initialization
```ts
import {SquareGrid} from '@2d-game-grid/square';

const grid = new SquareGrid({
  width: 4,
  height: 3,
  initializeCellValue: ({row, col}) => `${row}-${col}`,
});
```

### Get cell
```ts
const cell = grid.getCell({row: 1, col: 2});
console.log(cell.value) // '1-2'
```

### Get cells of a row
```ts
const row = grid.getRow(1);
console.log(row.cells.map(cell => cell.value)) // ['1-0', '1-1', '1-2', '1-3']
```

### Get cells of a column
```ts
const column = grid.getColumn(1);
console.log(column.cells.map(cell => cell.value)) // ['0-1', '1-1', '2-1']
```

### Get neighbors of a cell
```ts
const cell = grid.getCell({row: 1, col: 2});
const leftNeighbor = cell.neighbors.get('LEFT');
console.log(leftNeighbor.value); // '1-1'
```

### Get the distance between cells
```ts
const cell = grid.getCell({row: 1, col: 2});
const distance = cell.getDistance({row: 1, col: 0});
console.log(distance); // 2
```

### List all cells in distance
```ts
const cell = grid.getCell({row: 0, col: 3});
const cells = cell.listCellsInDistance(2);
console.log(cells.map(cell => cell.value)); // ['1-2', '2-3', '0-1', '0-2', '1-3']
```

#### Algorithms
- [Manhattan](https://en.wikipedia.org/wiki/Taxicab_geometry)
- [Euclidean](https://en.wikipedia.org/wiki/Euclidean_distance)

### Get the shortest path between cells
The pathfinding uses the [pathfinding](https://www.npmjs.com/package/pathfinding) package.  

```ts
const cell = grid.getCell({row: 1, col: 2});
const path = cell.getPath({row: 0, col: 0});
console.log(path.map(cell => cell.value)); // ['1-2', '0-1', '0-0']
```
Hint: The returned path will always include the start and the end.

#### Algorithms
- [A* (A Star)](https://en.wikipedia.org/wiki/A*_search_algorithm)
- [Best First](https://en.wikipedia.org/wiki/Best-first_search)
- [Breadth First](https://en.wikipedia.org/wiki/Breadth-first_search)
- [Dijkstra](https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm)

#### Diagonal movement
You can decide which diagonal movements should be allowed: 
- Always  
  ![Always](https://github.com/mstuercke/2d-game-grid/blob/master/apps/square/images/always.jpg?raw=true)
- Never  
  ![Never](https://github.com/mstuercke/2d-game-grid/blob/master/apps/square/images/never.jpg?raw=true)
- If at most one obstacle  
  ![If at most one obstacle](https://github.com/mstuercke/2d-game-grid/blob/master/apps/square/images/if-at-most-one-obstacle.jpg?raw=true)
- Only when no obstacles  
  ![Only when no obstacles](https://github.com/mstuercke/2d-game-grid/blob/master/apps/square/images/only-when-no-obstacles.jpg?raw=true)

#### Heuristic
You can either choose between the following heuristic algorithms:
- [Manhattan](https://en.wikipedia.org/wiki/Taxicab_geometry)
- [Chebyshev](https://en.wikipedia.org/wiki/Chebyshev_distance)
- [Euclidean](https://en.wikipedia.org/wiki/Euclidean_distance)
- Octile

Or pass your own heuristic function:
```ts
cell.getPath({row: 1, col: 0}, {
  heuristic: (cell) => {/* your implementation */}
});
```

### List all reachable cells (pathfinding)
```ts
const cell = grid.getCell({row: 0, col: 3});
const cells = cell.listReachableCells(3);
console.log(cells.map(cell => cell.value)); // ['1-2', '2-3', '2-1', '0-1', '1-1', '2-2', '0-2', '1-3']
```

### Extend grid with another grid
```ts
const gridA = new SquareGrid({grid: [["A"]]})
const gridB = new SquareGrid({grid: [["B"]]})

gridA.extend(gridB, 'LEFT')  // B A
gridA.extend(gridB, 'RIGHT') // A B
```

### Crop grid
```ts
const cells = grid.crop({row: 0, col: 1}, {row: 1, col: 2}).grid;
console.log(cells.map(cell => cell.value)); // [['0-1', '0-2'], ['1-1', '1-2']]
```


### Listen for cell value changes
#### Register
```ts
grid.onCellValueChanged((event) => {/* your implementation */});
grid.getRow(1).onCellValueChanged((event) => {/* your implementation */});
grid.getColumn(2).onCellValueChanged((event) => {/* your implementation */});
grid.getCell({row: 1, col: 2}).onValueChanged((event) => {/* your implementation */});
```

#### Trigger
```ts
const cell = grid.getCell({row: 1, col: 2});
cell.value = 'new value'; // updating this value will trigger all callbacks of the "Register" example
```

#### Unregister
The `onCellValueChanged()` and `onValueChanged()` functions return a function to unregister. 
When you execute this unregister function, the callback will not be called anymore.

```ts
const unregister = grid.onCellValueChanged((event) => {/* your implementation */});
unregister();
```

## Collaboration
Feel free to create issues or merge requests

### Release package
Modify the version in `package.json`. When running the pipeline on `master` a new package will be deployed with that version. 


