# astra-engine

> game web-engine

Latest version **0.1.59** (published 2019-11-09) · MIT license · 0 weekly downloads

## Install

```sh
npm install astra-engine
pnpm add astra-engine
yarn add astra-engine
bun add astra-engine
```

## Health

**Score 15/100 (F)** — status: abandoned.

Positive: no vulnerabilities.

Warnings: low downloads; no types; no esm support; pre 1.0.

Negative: abandoned; low maintenance score.

## Facts

| | |
|---|---|
| Version | 0.1.59 |
| Published | 2019-11-09 |
| First published | 2019-10-14 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | none |
| Module format | CommonJS |
| Dependencies | 4 |
| Unpacked size | 63 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Author | D. |
| Maintainers | danixoon |

## Links

- npm: https://www.npmjs.com/package/astra-engine
- Repository: https://github.com/danixoon/astra-engine
- Homepage: https://github.com/danixoon/astra-engine#readme
- Issues: https://github.com/danixoon/astra-engine/issues
- npm.io page: https://npm.io/package/astra-engine

## Dependencies (4)

- [chalk](https://npm.io/package/chalk.md) ^2.4.2
- [lodash](https://npm.io/package/lodash.md) ^4.17.15
- [express](https://npm.io/package/express.md) ^4.17.1
- [socket.io](https://npm.io/package/socket.io.md) ^2.3.0

## Recent versions

- 0.1.59 (latest) — 2019-11-09
- 0.1.58 — 2019-11-09
- 0.1.57 — 2019-11-04
- 0.1.56 — 2019-11-04
- 0.1.55 — 2019-11-04
- 0.1.54 — 2019-11-02
- 0.1.52 — 2019-11-01
- 0.1.51 — 2019-11-01
- 0.1.5 — 2019-10-29
- 0.1.45 — 2019-10-19
- 0.1.44 — 2019-10-19
- 0.1.43 — 2019-10-19
- 0.1.42 — 2019-10-19
- 0.1.41-a — 2019-10-17
- 0.1.41 — 2019-10-17
- … 20 more at https://npm.io/package/astra-engine/versions

## README

# astra-engine

## Что это?

astra-engine - игровой веб-движок, написанный на NodeJS и использующий модуль socket.io

## Установка

```bash
npm install astra-engine
```

## Как использовать

Запуск сервера на TypeScript:
```ts
import SocketIO from "socket.io";
import { AstraEngine, Lobby, Player } from "astra-engine";

// Наследуемся от базового класса лобби
class GameLobby extends Lobby {
  // Метод вызывается когда команда прилетает от игрока в данном лобби
  onCommand(player: Player, action: string, payload: any) {
    // Если экшен команды "ping"
    if(action === "ping") {
      // Определяем задержку между игроком и сервером, которая является разницей во времени между ними
      const ping = Date.now() - payload;
      // И посылаем игроку задержку (пинг)
      this.command(player, "pong", ping);
    }
  }
}

// Создаём сервер (может быть любым фреймворком, поддерживающим socket.io)
const io = SocketIO(3000);
// И экземпляр движка, передавая созданный socket.io сервер и класс лобби, используемый по умолчанию
const engine = new AstraEngine(io, GameLobby);
```

Подключение клиента на JavaScript:
```js
// EventEmitter не обязателен, но очень полезен для упрощения обработки команд
const { EventEmitter } = require("events");
const SocketIO = require("socket.io-client");

// Создаём экземпляр EventEmitter'а
const server = new EventEmitter();

// Привязываем к нему все обрабатываемые команды
server
     // Когда игрок подключился (после аунтентификации)
    .on("player.connected", ({ playerId }) => {
      // Берём playerId из данных, пришедших от сервера (пейлода)
      console.log(`player with id ${playerId} connected!`);
      // И отправляем команду подсоединения к лобби, т.к. после подключения мы можем это сделать
      io.emit("command", "lobby.join");
     })
     // Когда мы подключились к лобби - мы можем отправлять команды непосредственно в него
    .on("lobby.joined", ({ playerId, lobbyId }) => {
      // Выводим, что мы подключились в лобби
      console.log(`player with id ${playerId} connected to lobby with id ${lobbyId}`);
      // И, с интервалом в 500 мс, посылаем текущие время серверу
      setInterval(() => io.emit("command", "ping", Date.now()), 500);
    })
    // Когда же сервер отвечает нам командой "pong"
    .on("pong", ping => {
      // Мы выводим пинг, полученный из аргумента, являющимся пришедшими данными от движка
      console.log("pong!", `ping: ${ping} ms`)
    })

// Создаём соединение, передавая в query строку username, являющейся ключём аутентификации в данный момент
// (поддержка токенов пока не реализована)
const io = SocketIO("http://localhost:3000", { query: { username: "1337player" } });
// И враппим данные о команде в наш эмиттер
io.on("command", (action, payload) => server.emit(action, payload));
```

## Классные фичи
### Состояние лобби
У каждого лобби существует два типа объекта состояния:
- ```lobbyState```, глобальное, единое для всех игроков
- ```playerState```, уникальное для каждого игрока

Оба состояния можно менять и изменения отправлять клиентам, обмениваясь данными и синхронизируя их.
Например, существует игра, цель которой нажмать на кнопку и получать очки, тогда лобби будет создано следующим образом:

```ts
class GameLobby extends Lobby {
  createPlayerState = () => ({ score: 0 })
  onCommand(player: Player, action: string) {
    if(action !== "player.score") return;
    const ps = this.getPlayerState(player);
    const changes = ps.modify(s => ({ score: s.score + 1 })).apply();
    this.command(player, "player.score", changes);
  }
}
```

Суть проста - функция ```createPlayerState``` возвращает объект, определяющий начальное состояние каждого подключенного в лобби игрока. Если она не объявлена, то состоянию присваивается пустой объект ```{}```.

Метод ```onCommand``` вызывается при команде, не имеющей отношения к отношения к системному взаимодействию с лобби, посылаемой игроком находящимся в данный момент в лобби. Например, игрок, посылая команду ```game.ping```, отправит её именно в то лобби, в котором он находится в данный момент и метод ```onCommand``` вызовется, передав игрока первым аргументом ```player```. Вторым и третьим аргументами выступают наименование команды ```action``` и дополнительные данные ```payload```, которые игрок мог передать. Если их нет - обработчику прилетает пустой объект ```{}```.

В методе проверяется, является ли обрабатываемая команда ```player.score```:

```ts
if(action !== "player.score") return;
```

В ином случае, обработка команды завершается. Метод базового класса ```Lobby``` именуемый ```getPlayerState``` позволяет получить состояние любого игрока, находящегося (или находившегося когда-либо) в этом лобби. Для этого первым аргументом передаётся ссылка на экземпляр игрока ```player```, в ответ возвращается экземпляр класса ```SyncState```, позволяющий манипулировать данными состояния.

```ts
const ps = this.getPlayerState(player);
```

Следующим шагом необходимо изменить состояние игрока, добавив ему очки. Данная операция осуществляется методом ```modify``` в объекте состояния. В его аргумент передаётся функция, в аргументах которой находятся:
- ```state```, текущее состояние;
- ```changes```, все совершённые изменения над этим состоянием после использования ```modify```;

Методом ```modify``` возвращается экземпляр объекта изменений, которые можно сохранить и получить, выполнив метод ```apply```, возвращающий объект изменений, совершённых над состоянием.

```ts
const changes = ps.modify(s => ({ score: s.score + 1 })).apply();
```

Изменения необходимо отправить клиенту и для этой цели используется метод базового класса ```Lobby``` именуемый ```command```, имеющий следующие аргументы:
- ```player```, игрок, который должен получить данные;
- ```action```, команда, которая придёт игроку с этими данными;
- ```payload```, Сам объект данных, отправляемый игроку;

Прописав метод:

```ts
this.command(player, "player.score", changes);
```

После вышеперечисленных операций игрок получит ответ с изменившимся состоянием, новым количеством очков, что и требовалось сделать.

## Тесты

```bash
yarn test
```

## Лицензия

[MIT](LICENSE)

---
_Source: https://npm.io/package/astra-engine · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
