/**
* Typed publish/subscribe event bus used to decouple the game model from the
* view. Mirrors the Java Events singleton, simplified to plain callbacks.
*
* There are six event channels:
* game — game lifecycle (paused, resumed)
* player — player stats changed
* inventory — player bag changed
* flags — an agent's flags changed
* cell — a board cell's visual state changed
* message — messages displayed to the player
*
* All channels support multiple subscribers.
*/
/** @typedef {function(): void} VoidFn */
/** @typedef {function(Player): void} PlayerFn */
/** @typedef {function(Player): void} InventoryFn */
/** @typedef {function(Agent): void} FlagsFn */
/** @typedef {function(Cell): void} CellFn */
/** @typedef {function(Cell, string): void} MessageFn */
/** @typedef {function(string): void} ModalFn */
class Events {
constructor() {
/** @type {VoidFn[]} */
this._gamePaused = [];
/** @type {VoidFn[]} */
this._gameResumed = [];
/** @type {PlayerFn[]} */
this._playerChanged = [];
/** @type {InventoryFn[]} */
this._inventoryChanged = [];
/** @type {FlagsFn[]} */
this._flagsChanged = [];
/** @type {CellFn[]} */
this._cellChanged = [];
/** @type {MessageFn[]} */
this._message = [];
/** @type {ModalFn[]} */
this._modalMessage = [];
/** @type {CellFn[]} */
this._clearCell = [];
/** @type {VoidFn[]} */
this._clearCurrentCell = [];
/** @type {VoidFn[]} */
this._clearAllCells = [];
/** @type {VoidFn[]} */
this._handleInventoryMessaging = [];
/** @type {VoidFn[]} */
this._handleModalMessage = [];
/** @type {function(number, number): void[]} */
this._fallThrough = [];
}
// ── Registration ──────────────────────────────────────────────────────────
/** @param {VoidFn} fn */
onGamePaused(fn) {
this._gamePaused.push(fn);
}
/** @param {VoidFn} fn */
onGameResumed(fn) {
this._gameResumed.push(fn);
}
/** @param {PlayerFn} fn */
onPlayerChanged(fn) {
this._playerChanged.push(fn);
}
/** @param {InventoryFn} fn */
onInventoryChanged(fn) {
this._inventoryChanged.push(fn);
}
/** @param {FlagsFn} fn */
onFlagsChanged(fn) {
this._flagsChanged.push(fn);
}
/** @param {CellFn} fn */
onCellChanged(fn) {
this._cellChanged.push(fn);
}
/** @param {MessageFn} fn */
onMessage(fn) {
this._message.push(fn);
}
/** @param {ModalFn} fn */
onModalMessage(fn) {
this._modalMessage.push(fn);
}
/** @param {CellFn} fn */
onClearCell(fn) {
this._clearCell.push(fn);
}
/** @param {VoidFn} fn */
onClearCurrentCell(fn) {
this._clearCurrentCell.push(fn);
}
/** @param {VoidFn} fn */
onClearAllCells(fn) {
this._clearAllCells.push(fn);
}
/** @param {VoidFn} fn */
onHandleInventoryMessaging(fn) {
this._handleInventoryMessaging.push(fn);
}
/** @param {VoidFn} fn */
onHandleModalMessage(fn) {
this._handleModalMessage.push(fn);
}
/** @param {function(number, number): void} fn */
onFallThrough(fn) {
this._fallThrough.push(fn);
}
// ── Firing ────────────────────────────────────────────────────────────────
fireGamePaused() {
for (const fn of this._gamePaused) fn();
}
fireGameResumed() {
for (const fn of this._gameResumed) fn();
}
/** @param {Player} player */
firePlayerChanged(player) {
for (const fn of this._playerChanged) fn(player);
}
/** @param {Player} bag */
fireInventoryChanged(bag) {
for (const fn of this._inventoryChanged) fn(bag);
}
/** @param {Agent} agent */
fireFlagsChanged(agent) {
for (const fn of this._flagsChanged) fn(agent);
}
/** @param {Cell} cell */
fireCellChanged(cell) {
for (const fn of this._cellChanged) fn(cell);
}
/**
* @param {Cell} cell
* @param {string} message
*/
fireMessage(cell, message) {
for (const fn of this._message) fn(cell, message);
}
/** @param {string} message */
fireModalMessage(message) {
for (const fn of this._modalMessage) fn(message);
}
fireHandleInventoryMessaging() {
for (const fn of this._handleInventoryMessaging) fn();
}
fireClearCurrentCell() {
for (const fn of this._clearCurrentCell) fn();
}
/** @param {Cell} cell */
fireClearCell(cell) {
for (const fn of this._clearCell) fn(cell);
}
fireHideAllMessages() {
for (const fn of this._clearAllCells) fn();
}
fireHandleModalMessage() {
for (const fn of this._handleModalMessage) fn();
}
/**
* @param {string} boardId
* @param {number} x
* @param {number} y
*/
/**
* @param {number} x
* @param {number} y
*/
fireFallThrough(x, y) {
for (const fn of this._fallThrough) fn(x, y);
}
/** Remove all listeners. Useful in tests and on game teardown. */
reset() {
this._gamePaused = [];
this._gameResumed = [];
this._playerChanged = [];
this._inventoryChanged = [];
this._flagsChanged = [];
this._cellChanged = [];
this._message = [];
this._modalMessage = [];
this._clearCell = [];
this._clearCurrentCell = [];
this._clearAllCells = [];
this._handleInventoryMessaging = [];
this._handleModalMessage = [];
this._fallThrough = [];
}
}
/** Global event bus singleton. */
export const events = new Events();