All files game.js

100% Statements 124/124
100% Branches 13/13
83.33% Functions 10/12
100% Lines 124/124

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 1251x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 7x 7x 7x 7x 7x 3x 3x 3x 7x 7x 3x 3x 7x 7x 7x 7x 7x 3x 3x 7x 1x 1x  
import {
  asyncScheduler,
  BehaviorSubject,
  distinctUntilChanged,
  interval,
  Observable,
  Subject,
  Subscription,
  switchMap,
  takeUntil,
  tap,
} from 'rxjs';
 
/**
 * Represents a Game, consisting of a grid and an initial state,
 * that can be started and stopped.
 */
class Game {
  /**
   * Whether the game is currently running.
   * @type {boolean}
   */
  #running = false;
 
  /**
   * Signal used to stop the running game loop.
   * @type {Subject<void>}
   */
  #stopSignal = new Subject();
 
  /**
   * Grid for the current game.
   * @type {Grid}
   */
  #grid;
 
  /**
   * Contains the currently-set tick interval in millisecond.
   * @type {BehaviorSubject<number>}
   */
  #intervalMillis;
 
  /**
   * Subscriptions held by this game instance.
   * @type {Subscription}
   */
  subscriptions = new Subscription();
 
  /**
   * @param grid {Grid} - initialized grid to be managed by this game.
   * @param cellToggled {Observable<[number, number, State]>} - emits [x,y,active] on interaction
   * @param {number} tickInterval - ms between each tick in auto mode
   */
  constructor(grid, cellToggled, tickInterval = 1000) {
    this.#grid = grid;
    this.#intervalMillis = new BehaviorSubject(tickInterval);
    this.subscriptions.add(this.handleIncomingCellEvent(cellToggled));
  }
 
  /**
   *
   * @param cellToggled {Observable<[number, number, State]>}
   * @returns {Subscription}
   */
  handleIncomingCellEvent(cellToggled) {
    return cellToggled
      .pipe(tap((a) => console.log(`Got this in game: ${JSON.stringify(a)}`)))
      .subscribe({
        next: ([x, y, active]) => this.#grid.setCellState(x, y, active),
        error: (err) =>
          console.error(`Error in Game.cellToggled listener: ${err}`),
        complete: () => console.log('Game received complete signal'),
      });
  }
 
  /**
   * Progresses the game by one tick.
   */
  tick() {
    this.#grid.tick();
  }
 
  /**
   * Starts the game with the current tick interval.
   *
   * @param {import('rxjs').SchedulerLike} scheduler - optional scheduler
   * @returns {Subscription}
   */
  start(scheduler = asyncScheduler) {
    this.#running = true;
 
    return this.#intervalMillis
      .asObservable()
      .pipe(
        distinctUntilChanged(),
        switchMap((ms) => interval(ms, scheduler)),
        takeUntil(this.#stopSignal),
      )
      .subscribe({
        next: () => this.tick(),
      });
  }
 
  /**
   * Stops the game.
   */
  stop() {
    this.#stopSignal.next(void 0);
    this.#running = false;
  }
 
  get isRunning() {
    return this.#running;
  }
 
  /**
   * @param {number} ms
   */
  set tickInterval(ms) {
    this.#intervalMillis.next(ms);
  }
}
 
export { Game };