Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 3031x 3031x 242x 242x 242x 519x 519x 242x 242x 242x 242x 242x 242x 1248x 1248x 242x 242x 242x 242x 242x 242x 242x 242x 242x 242x 345x 345x 345x 345x 345x 345x 242x 242x 242x 242x 242x 242x 242x 242x 242x 350x 350x 350x 350x 350x 350x 350x 242x 242x 242x 242x 242x 242x 242x 242x 242x 450x 450x 129x 450x 450x 393x 393x 450x 321x 450x 450x 327x 327x 66x 450x 242x 242x 242x 242x 242x 242x 242x 242x 242x 450x 450x 450x 450x 138x 138x 450x 450x 242x 242x 1x 1x 242x 3x 3x | import {
BehaviorSubject,
map,
Observable,
of,
sample,
Subject,
Subscription,
switchMap,
takeUntil,
zip,
} from 'rxjs';
import { Rule, WellKnownRules } from './rule.js';
import { State } from './state.js';
class Cell {
/**
* Subject used hold and emit current state to subscribers
* @type {BehaviorSubject<State>}
*/
#stateSubject;
/**
* A list of notifiers from neighbor cells
* @type {Observable<State>[]}
*/
#neighbors = [];
/**
* Used to broadcast current position and state whenever a change occurs.
* @type {Subject<[number, number, State]>}
*/
changeEmitter;
/**
* Indicates a next step signal.
* @type {Observable<void>}
*/
#ticker;
/**
* X position within the grid.
* @type {number}
*/
#posX;
/**
* Y position within the grid.
* @type {number}
*/
#posY;
/**
* The neighbor rules for this cell.
* @member {Rule}
*/
#rule;
/**
* @param {Observable<void>} ticker - the game "clock" that emits whenever state should update
* @param {number} x
* @param {number} y
* @param {Subject<[number, number, State]>} changeEmitter
* @param {State} state - initial state, defaults to false
* @param {Rule} rule - the neighbor rules for this cell (defaults to B3/S23 for Game of Life)
*/
constructor(
ticker,
x,
y,
changeEmitter = null,
state = State.DEAD,
rule = WellKnownRules.GAME_OF_LIFE,
) {
this.#ticker = ticker;
this.#posX = x;
this.#posY = y;
this.changeEmitter = changeEmitter;
this.#stateSubject = new BehaviorSubject(state);
this.#rule = rule;
}
/** @returns {State} whether this cell is alive or dead */
get state() {
return this.#stateSubject.getValue();
}
/** @param {State} state - the new state to set and broadcast from this cell */
set state(state) {
this.#stateSubject.next(state);
}
/**
* Add a neighbor listener to this cell's registry.
* @param {Cell} cell
*/
addNeighbor(cell) {
this.#neighbors.push(cell.#stateSubject.asObservable());
}
/**
* Begins listening to neighbor notifications, unsubscribing once
* stopSignal emits.
* On each tick, this cell updates its state and notifies its neighbors.
*
* @param {Observable<void>} stopSignal - unsubscribe notifier
* @returns {Subscription}
*/
start(stopSignal) {
return this.listenUntil(stopSignal).subscribe({
next: (state) => this.#stateSubject.next(state),
error: (err) =>
console.error(`Error in cell ${this.#posX},${this.#posY}: ${err}`),
});
}
/**
* Returns an observable that updates its state on each tick, based on
* the states emitted from neighbor cells.
*
* @param {Observable<void>} stopSignal - unsubscribe notifier
* @return {Observable<State>} the next state of this cell
*/
listenUntil(stopSignal) {
return zip(this.#neighbors).pipe(
sample(this.#ticker),
map((states) => states.filter((state) => state === State.ALIVE).length),
switchMap((neighborCount) => this.#updateState(neighborCount)),
takeUntil(stopSignal),
);
}
/**
* Returns true (alive) or false (dead) for the next state based on number of
* living neighbor cells.
*
* @param livingNeighborCount {number} number of living neighbors
* @returns {State}
*/
#nextState(livingNeighborCount) {
if (
this.state.isAlive &&
this.#rule.survivalCounts.includes(livingNeighborCount)
)
return State.ALIVE;
if (
!(
this.state.isDead &&
this.#rule.birthCounts.includes(livingNeighborCount)
)
) {
return State.DEAD;
}
return State.ALIVE;
}
/**
* Updates this cell's state and returns it wrapped in an observable.
* Also publishes coords and state in the event of a change.
*
* @param livingNeighborCount {number}
* @return {Observable<State>}
*/
#updateState(livingNeighborCount) {
const prev = this.state;
this.state = this.#nextState(livingNeighborCount);
if (prev !== this.state) {
this.changeEmitter.next([this.#posX, this.#posY, this.state]);
}
return of(this.state);
}
toString() {
return `Cell(${this.#posX},${this.#posY}): ${this.state}`;
}
}
export { Cell };
|