Skip to content

Commit 9d80779

Browse files
authored
Merge pull request #4 from ParkerM/merge-canvas-renderer
Merge canvas renderer
2 parents 76380fe + 2c3492a commit 9d80779

23 files changed

Lines changed: 3451 additions & 5477 deletions

.github/workflows/codeql-analysis.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ on:
2424
jobs:
2525
analyze:
2626
name: Analyze
27-
runs-on: ubuntu-latest
27+
runs-on: ubuntu-24.04
2828
permissions:
2929
actions: read
3030
contents: read
@@ -39,11 +39,11 @@ jobs:
3939

4040
steps:
4141
- name: Checkout repository
42-
uses: actions/checkout@v3
42+
uses: actions/checkout@v4
4343

4444
# Initializes the CodeQL tools for scanning.
4545
- name: Initialize CodeQL
46-
uses: github/codeql-action/init@v2
46+
uses: github/codeql-action/init@v3
4747
with:
4848
languages: ${{ matrix.language }}
4949
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -56,7 +56,7 @@ jobs:
5656
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
5757
# If this step fails, then you should remove it and run the build manually (see below)
5858
- name: Autobuild
59-
uses: github/codeql-action/autobuild@v2
59+
uses: github/codeql-action/autobuild@v3
6060

6161
# ℹ️ Command-line programs to run using the OS shell.
6262
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -69,6 +69,6 @@ jobs:
6969
# ./location_of_script_within_repo/buildscript.sh
7070

7171
- name: Perform CodeQL Analysis
72-
uses: github/codeql-action/analyze@v2
72+
uses: github/codeql-action/analyze@v3
7373
with:
7474
category: '/language:${{matrix.language}}'

.github/workflows/main.yml

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,27 @@ on: push
44

55
jobs:
66
lint:
7-
runs-on: ubuntu-20.04
7+
runs-on: ubuntu-24.04
88

99
steps:
10-
- uses: actions/checkout@v2
11-
- uses: actions/setup-node@v2
10+
- uses: actions/checkout@v4
11+
- uses: actions/setup-node@v4
1212
with:
13-
node-version: '16'
13+
node-version: '22'
1414
- name: Install dependencies
1515
run: npm ci
1616

1717
- name: check prettier
1818
run: npm run lint
1919

2020
build:
21-
runs-on: ubuntu-20.04
21+
runs-on: ubuntu-24.04
2222

2323
steps:
24-
- uses: actions/checkout@v2
25-
- uses: actions/setup-node@v2
24+
- uses: actions/checkout@v4
25+
- uses: actions/setup-node@v4
2626
with:
27-
node-version: '16'
27+
node-version: '22'
2828
- name: Install dependencies
2929
run: npm ci
3030

@@ -33,19 +33,19 @@ jobs:
3333

3434
- name: Upload build artifacts
3535
if: ${{ github.ref == 'refs/heads/main' }}
36-
uses: actions/upload-artifact@v2
36+
uses: actions/upload-artifact@v4
3737
with:
3838
name: build-artifacts
3939
path: dist
4040

4141
test:
42-
runs-on: ubuntu-20.04
42+
runs-on: ubuntu-24.04
4343

4444
steps:
45-
- uses: actions/checkout@v2
46-
- uses: actions/setup-node@v2
45+
- uses: actions/checkout@v4
46+
- uses: actions/setup-node@v4
4747
with:
48-
node-version: '16'
48+
node-version: '22'
4949
- name: Install dependencies
5050
run: npm ci
5151

@@ -57,7 +57,7 @@ jobs:
5757

5858
- name: Upload coverage artifacts
5959
if: ${{ github.ref == 'refs/heads/main' }}
60-
uses: actions/upload-artifact@v2
60+
uses: actions/upload-artifact@v4
6161
with:
6262
name: coverage-report
6363
path: coverage
@@ -67,16 +67,16 @@ jobs:
6767
needs:
6868
- build
6969
- test
70-
runs-on: ubuntu-20.04
70+
runs-on: ubuntu-24.04
7171

7272
steps:
7373
- name: Download build artifacts
74-
uses: actions/download-artifact@v2
74+
uses: actions/download-artifact@v4
7575
with:
7676
name: build-artifacts
7777
path: public
7878
- name: Download coverage artifacts
79-
uses: actions/download-artifact@v2
79+
uses: actions/download-artifact@v4
8080
with:
8181
name: coverage-report
8282
path: public/coverage

.husky/pre-commit

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1 @@
1-
#!/bin/sh
2-
. "$(dirname "$0")/_/husky.sh"
3-
41
npx lint-staged

index.html

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
1-
<!DOCTYPE html>
1+
<!doctype html>
22
<html lang="en">
33
<head>
44
<title>Game of Life RxJS</title>
55
<meta charset="UTF-8" />
66
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
7+
<link rel="preconnect" href="https://fonts.googleapis.com" />
8+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9+
<link
10+
href="https://fonts.googleapis.com/css2?family=Farsan&display=swap"
11+
rel="stylesheet"
12+
/>
13+
<!-- <style >-->
14+
<!-- @import url('https://fonts.googleapis.com/css2?family=BenchNine:wght@300&display=swap');-->
15+
<!-- @import url('https://fonts.googleapis.com/css2?family=BenchNine:wght@300&family=Stint+Ultra+Condensed&display=swap');-->
16+
<!-- </style>-->
717
<link rel="stylesheet" type="text/css" href="/style.css" />
818
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
919
<script type="module" src="/main.js"></script>
@@ -14,7 +24,10 @@
1424
<header class="header">I'm alive!</header>
1525

1626
<div class="game-container">
17-
<div id="game-div"></div>
27+
<div id="game-div">
28+
<canvas id="game-canvas" width="60" height="30"></canvas>
29+
<canvas id="grid-canvas"></canvas>
30+
</div>
1831
</div>
1932

2033
<div class="panel controls">

lib/cell.js

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import {
1010
takeUntil,
1111
zip,
1212
} from 'rxjs';
13-
import { State } from './util.js';
13+
import { Rule, WellKnownRules } from './rule.js';
14+
import { State } from './state.js';
1415

1516
class Cell {
1617
/**
@@ -49,19 +50,34 @@ class Cell {
4950
*/
5051
#posY;
5152

53+
/**
54+
* The neighbor rules for this cell.
55+
* @member {Rule}
56+
*/
57+
#rule;
58+
5259
/**
5360
* @param {Observable<void>} ticker - the game "clock" that emits whenever state should update
5461
* @param {number} x
5562
* @param {number} y
5663
* @param {Subject<[number, number, State]>} changeEmitter
5764
* @param {State} state - initial state, defaults to false
65+
* @param {Rule} rule - the neighbor rules for this cell (defaults to B3/S23 for Game of Life)
5866
*/
59-
constructor(ticker, x, y, changeEmitter = null, state = State.DEAD) {
67+
constructor(
68+
ticker,
69+
x,
70+
y,
71+
changeEmitter = null,
72+
state = State.DEAD,
73+
rule = WellKnownRules.GAME_OF_LIFE,
74+
) {
6075
this.#ticker = ticker;
6176
this.#posX = x;
6277
this.#posY = y;
6378
this.changeEmitter = changeEmitter;
6479
this.#stateSubject = new BehaviorSubject(state);
80+
this.#rule = rule;
6581
}
6682

6783
/** @returns {State} whether this cell is alive or dead */
@@ -122,9 +138,17 @@ class Cell {
122138
* @returns {State}
123139
*/
124140
#nextState(livingNeighborCount) {
125-
if (this.state.isAlive && [2, 3].includes(livingNeighborCount))
141+
if (
142+
this.state.isAlive &&
143+
this.#rule.survivalCounts.includes(livingNeighborCount)
144+
)
126145
return State.ALIVE;
127-
if (!(this.state.isDead && livingNeighborCount === 3)) {
146+
if (
147+
!(
148+
this.state.isDead &&
149+
this.#rule.birthCounts.includes(livingNeighborCount)
150+
)
151+
) {
128152
return State.DEAD;
129153
}
130154
return State.ALIVE;

lib/cell.spec.js

Lines changed: 62 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { Subject } from 'rxjs';
22
import { Cell } from './cell.js';
3-
import { State } from './util.js';
3+
4+
import { State } from './state.js';
45

56
describe('Cells', () => {
67
/** @type {Subject<void>} */
78
let ticker;
89

9-
/** @type {Subject<number, number, State>} */
10+
/** @type {Subject<[number, number, State]>} */
1011
let changeEmitter;
1112

1213
/** @type {Subject<void>} */
@@ -41,62 +42,82 @@ describe('Cells', () => {
4142
ticker = new Subject();
4243
changeEmitter = new Subject();
4344
stopSignal = new Subject();
44-
cell = new Cell(ticker.asObservable(), 1, 1, changeEmitter);
4545
});
4646

47-
it('Any live cell with fewer than two live neighbours dies, as if by underpopulation', (done) => {
48-
cell.state = State.ALIVE;
49-
setupNeighbors(cell, [State.ALIVE, State.DEAD]);
47+
describe('Game of Life', () => {
48+
beforeEach(() => {
49+
cell = new Cell(ticker.asObservable(), 1, 1, changeEmitter);
50+
});
51+
52+
it('Any live cell with fewer than two live neighbours dies, as if by underpopulation', (done) => {
53+
cell.state = State.ALIVE;
54+
setupNeighbors(cell, [State.ALIVE, State.DEAD]);
5055

51-
cell.listenUntil(stopSignal).subscribe({
52-
next: expectNext(State.DEAD, done),
56+
cell.listenUntil(stopSignal).subscribe({
57+
next: expectNext(State.DEAD, done),
58+
});
59+
ticker.next(void 0);
60+
stopSignal.next(void 0);
5361
});
54-
ticker.next(void 0);
55-
stopSignal.next(void 0);
56-
});
5762

58-
it('Any live cell with two live neighbours lives on to the next generation', (done) => {
59-
cell.state = State.ALIVE;
60-
setupNeighbors(cell, [State.ALIVE, State.ALIVE]);
63+
it('Any live cell with two live neighbours lives on to the next generation', (done) => {
64+
cell.state = State.ALIVE;
65+
setupNeighbors(cell, [State.ALIVE, State.ALIVE]);
6166

62-
cell.listenUntil(stopSignal).subscribe({
63-
next: expectNext(State.ALIVE, done),
67+
cell.listenUntil(stopSignal).subscribe({
68+
next: expectNext(State.ALIVE, done),
69+
});
70+
ticker.next(void 0);
71+
stopSignal.next(void 0);
6472
});
65-
ticker.next(void 0);
66-
stopSignal.next(void 0);
67-
});
6873

69-
it('Any live cell with three live neighbours lives on to the next generation', (done) => {
70-
cell.state = State.ALIVE;
71-
setupNeighbors(cell, [State.ALIVE, State.ALIVE, State.ALIVE]);
74+
it('Any live cell with three live neighbours lives on to the next generation', (done) => {
75+
cell.state = State.ALIVE;
76+
setupNeighbors(cell, [State.ALIVE, State.ALIVE, State.ALIVE]);
7277

73-
cell.listenUntil(stopSignal).subscribe({
74-
next: expectNext(State.ALIVE, done),
78+
cell.listenUntil(stopSignal).subscribe({
79+
next: expectNext(State.ALIVE, done),
80+
});
81+
ticker.next(void 0);
82+
stopSignal.next(void 0);
83+
});
84+
85+
it('Any live cell with more than three live neighbours dies, as if by overpopulation', (done) => {
86+
cell.state = State.ALIVE;
87+
setupNeighbors(cell, [
88+
State.ALIVE,
89+
State.ALIVE,
90+
State.ALIVE,
91+
State.ALIVE,
92+
]);
93+
94+
cell.listenUntil(stopSignal).subscribe({
95+
next: expectNext(State.DEAD, done),
96+
});
97+
ticker.next(void 0);
98+
stopSignal.next(void 0);
7599
});
76-
ticker.next(void 0);
77-
stopSignal.next(void 0);
78-
});
79100

80-
it('Any live cell with more than three live neighbours dies, as if by overpopulation', (done) => {
81-
cell.state = State.ALIVE;
82-
setupNeighbors(cell, [State.ALIVE, State.ALIVE, State.ALIVE, State.ALIVE]);
101+
it('Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction', (done) => {
102+
cell.state = State.DEAD;
103+
setupNeighbors(cell, [State.ALIVE, State.ALIVE, State.ALIVE]);
83104

84-
cell.listenUntil(stopSignal).subscribe({
85-
next: expectNext(State.DEAD, done),
105+
cell.listenUntil(stopSignal).subscribe({
106+
next: expectNext(State.ALIVE, done),
107+
});
108+
ticker.next(void 0);
109+
stopSignal.next(void 0);
86110
});
87-
ticker.next(void 0);
88-
stopSignal.next(void 0);
89111
});
90112

91-
it('Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction', (done) => {
92-
cell.state = State.DEAD;
93-
setupNeighbors(cell, [State.ALIVE, State.ALIVE, State.ALIVE]);
113+
describe('Overrides and defaults', () => {
114+
beforeEach(() => {
115+
cell = new Cell(ticker.asObservable(), 1, 1, changeEmitter);
116+
});
94117

95-
cell.listenUntil(stopSignal).subscribe({
96-
next: expectNext(State.ALIVE, done),
118+
test('Cell.toString', () => {
119+
expect(cell.toString()).toEqual('Cell(1,1): dead');
97120
});
98-
ticker.next(void 0);
99-
stopSignal.next(void 0);
100121
});
101122
});
102123

0 commit comments

Comments
 (0)