Skip to content

Commit 8754758

Browse files
feat: add rule30 automaton
1 parent ad5d628 commit 8754758

File tree

2 files changed

+66
-0
lines changed

2 files changed

+66
-0
lines changed

1d/rule30/rule30.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import type { RGB } from "../../types/RGB"
2+
import { Automaton1D } from "../automaton1d"
3+
4+
export class Rule30 extends Automaton1D {
5+
constructor(
6+
canvasEl: HTMLCanvasElement,
7+
width: number,
8+
height: number,
9+
paletteColors?: RGB[],
10+
) {
11+
// Rule 30 always uses 2 colors (binary states)
12+
super(canvasEl, width, height, 2, paletteColors)
13+
}
14+
15+
protected setRandomState = (): void => {
16+
// Instead of random state, start with a single black cell in the middle
17+
if (!this.state) this.state = []
18+
for (let x = 0; x < this.width; x++) {
19+
this.state[x] = this.colors[0] // white
20+
}
21+
// Place single black cell in the middle
22+
this.state[Math.floor(this.width / 2)] = this.colors[1] // black
23+
}
24+
25+
protected update = (line: number): void => {
26+
const newState = []
27+
for (let x = 0; x < this.width; x++) {
28+
// Get the three cells above (previous line)
29+
const left = this.getCellColor(x - 1)
30+
const center = this.getCellColor(x)
31+
const right = this.getCellColor(x + 1)
32+
33+
// Convert to binary pattern (0 = white, 1 = black)
34+
const pattern =
35+
(left.id === 1 ? 4 : 0) +
36+
(center.id === 1 ? 2 : 0) +
37+
(right.id === 1 ? 1 : 0)
38+
39+
// Apply Rule 30:
40+
// 111 -> 0 011 -> 0 101 -> 0 001 -> 1
41+
// 110 -> 0 010 -> 1 100 -> 1 000 -> 0
42+
const newStateId = [0, 1, 1, 1, 1, 0, 0, 0][pattern]
43+
newState[x] = this.colors[newStateId]
44+
45+
// Render directly to the canvas
46+
this.fillPixel(this.state[x].colorRgb, x, line)
47+
}
48+
this.state = newState
49+
}
50+
}

main.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as Sentry from "@sentry/browser"
22
import { Pane } from "tweakpane"
33
import { CCA1D } from "./1d/cca_1d/cca_1d"
4+
import { Rule30 } from "./1d/rule30/rule30"
45
import { CCA2D } from "./2d/cca_2d/cca_2d"
56
import { ConwayAutomaton } from "./2d/conway/conway"
67
import { gosperGliderGunPattern } from "./2d/conway/patterns/guns"
@@ -59,6 +60,7 @@ window.onload = () => {
5960
"quadlife",
6061
"langton",
6162
"entropy",
63+
"rule30",
6264
]
6365
return validAlgos.includes(path) ? path : "cca-2D"
6466
}
@@ -72,6 +74,7 @@ window.onload = () => {
7274
label: "Algorithm",
7375
options: {
7476
"1 dimension Cyclic Cellular Automaton": "cca-1D",
77+
"Elementary Cellular Automaton Rule 30": "rule30",
7578
"2 dimensions Cyclic Cellular Automaton": "cca-2D",
7679
"3 dimensions Cyclic Cellular Automaton": "cca-3D",
7780
"Conway's game of Life": "conway",
@@ -261,6 +264,11 @@ window.onload = () => {
261264
resolutionBlade.hidden = false
262265
}
263266

267+
const setRule30Blades = () => {
268+
for (const blade of blades) blade.hidden = true
269+
paletteSelector.hidden = false
270+
}
271+
264272
setCca2dBlades()
265273
void reset()
266274

@@ -273,6 +281,9 @@ window.onload = () => {
273281
case "cca-1D":
274282
setCca1dBlades()
275283
break
284+
case "rule30":
285+
setRule30Blades()
286+
break
276287
case "cca-2D":
277288
setCca2dBlades()
278289
break
@@ -349,6 +360,9 @@ window.onload = () => {
349360
case "cca-1D":
350361
automaton.start(10)
351362
break
363+
case "rule30":
364+
automaton.start(10)
365+
break
352366
case "cca-2D":
353367
automaton.start(25, 2500)
354368
break
@@ -425,6 +439,8 @@ const createAutomaton = async (
425439
settings.cca1dColorsCount || 4,
426440
paletteColors,
427441
)
442+
case "rule30":
443+
return new Rule30(canvasEl, width, height, paletteColors)
428444
case "cca-2D":
429445
return new CCA2D(
430446
settings.cca2dThreshold,

0 commit comments

Comments
 (0)