Skip to content

Commit d9a4ff4

Browse files
committed
Improve Game Of Life plugin
1 parent 056ff6c commit d9a4ff4

File tree

7 files changed

+856
-715
lines changed

7 files changed

+856
-715
lines changed

frontend/src/app.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,13 @@ export const App: Component = () => {
8787
}
8888
};
8989

90+
const handleGOLDelayChange = (value: number, shouldSend = false) => {
91+
actions?.setGOLDelay(value);
92+
if (shouldSend) {
93+
wsMessage("goldelay", { delay: value });
94+
}
95+
};
96+
9097
const handlePersistPlugin = () => {
9198
wsMessage("persist-plugin");
9299
toast(`Current mode set as default`, 1500);
@@ -146,6 +153,7 @@ export const App: Component = () => {
146153
onPluginChange={handlePluginChange}
147154
onBrightnessChange={handleBrightnessChange}
148155
onArtnetChange={handleArtnetUniverseChange}
156+
onGOLDelayChange={handleGOLDelayChange}
149157
onPersistPlugin={handlePersistPlugin}
150158
/>
151159
}

frontend/src/components/layout/sidebar.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,26 @@ export const Sidebar: Component<SidebarProps> = (props) => {
128128
</SidebarSection>
129129
</Show>
130130

131+
<Show when={store?.plugin === 4 && !store?.isActiveScheduler}>
132+
<div class="my-6 border-t border-gray-200" />
133+
134+
<SidebarSection title="Time Step Delay">
135+
<div class="space-y-2">
136+
<input
137+
type="range"
138+
min="1"
139+
max="4000"
140+
value={store?.GOLDelay}
141+
class="w-full"
142+
onInput={(e) => props.onGOLDelayChange(parseInt(e.currentTarget.value))}
143+
onPointerUp={() => props.onGOLDelayChange(store.GOLDelay, true)}
144+
/>
145+
<div class="text-sm text-gray-600 text-right">{store?.GOLDelay}</div>
146+
</div>
147+
</SidebarSection>
148+
</Show>
149+
150+
131151
<Show when={store?.plugin === 1 && !store?.isActiveScheduler}>
132152
<div class="my-6 border-t border-gray-200" />
133153

frontend/src/contexts/store.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const [mainStore, setStore] = createStore<Store>({
2121
plugin: 1,
2222
brightness: 0,
2323
artnetUniverse: 1,
24+
GOLDelay: 150,
2425
indexMatrix: [...new Array(256)].map((_, i) => i),
2526
leds: [...new Array(256)].fill(0),
2627
systemStatus: SYSTEM_STATUS.NONE,
@@ -36,6 +37,7 @@ const actions: StoreActions = {
3637
setPlugin: (plugin) => setStore("plugin", plugin),
3738
setBrightness: (brightness) => setStore("brightness", brightness),
3839
setArtnetUniverse: (artnetUniverse) => setStore("artnetUniverse", artnetUniverse),
40+
setGOLDelay: (GOLDelay) => setStore("GOLDelay", GOLDelay),
3941
setIndexMatrix: (indexMatrix) => setStore("indexMatrix", indexMatrix),
4042
setLeds: (leds) => setStore("leds", leds),
4143
setSystemStatus: (systemStatus: SYSTEM_STATUS) => setStore("systemStatus", systemStatus),

frontend/src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export interface StoreActions {
2222
setSystemStatus: (systemStatus: SYSTEM_STATUS) => void;
2323
setSchedule: (items: ScheduleItem[]) => void;
2424
setArtnetUniverse: (artnetUniverse: number) => void;
25+
setGOLDelay: (GOLDelay: number) => void;
2526
send: (message: string | ArrayBuffer) => void;
2627
}
2728

@@ -34,6 +35,7 @@ export interface Store {
3435
plugins: { id: number; name: string }[];
3536
plugin: number;
3637
artnetUniverse: number;
38+
GOLDelay: number;
3739
systemStatus: SYSTEM_STATUS;
3840
connectionState: () => number;
3941
connectionStatus?: string;

include/plugins/GameOfLifePlugin.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,22 @@
55
class GameOfLifePlugin : public Plugin
66
{
77
private:
8+
static const uint8_t STATE_RUNNING = 1;
9+
static const uint8_t STATE_END = 2;
10+
uint8_t state;
11+
uint8_t previous2[ROWS * COLS];
812
uint8_t previous[ROWS * COLS];
913
uint8_t buffer[ROWS * COLS];
1014
uint8_t updateCell(int row, int col);
1115
uint8_t countNeighbours(int row, int col);
1216
void next();
17+
void init();
18+
void show();
19+
uint16_t gol_delay = 150;
1320

1421
public:
1522
void setup() override;
1623
void loop() override;
1724
const char *getName() const override;
25+
void websocketHook(DynamicJsonDocument &request) override;
1826
};

src/plugins/GameoflifePlugin.cpp

Lines changed: 117 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,45 +8,103 @@ uint8_t GameOfLifePlugin::countNeighbours(int row, int col)
88
{
99
for (j = col - 1; j <= col + 1; j++)
1010
{
11-
count += this->buffer[i * COLS + j];
11+
int r = i;
12+
int c = j;
13+
// OOB handling
14+
if (r > (ROWS-1)) r = 0;
15+
if (r < 0) r = ROWS-1;
16+
if (c > (COLS-1)) c = 0;
17+
if (c < 0) c = COLS-1;
18+
19+
count += this->previous[r * COLS + c];
1220
}
1321
}
14-
count -= this->buffer[row * COLS + col];
22+
count -= this->previous[row * COLS + col];
1523
return count;
1624
};
1725

1826
uint8_t GameOfLifePlugin::updateCell(int row, int col)
1927
{
2028
uint8_t total = this->countNeighbours(row, col);
21-
if (total > 4 || total < 3)
29+
if (total > 3 || total < 2)
2230
{
2331
return 0;
2432
}
25-
else if (this->buffer[row * COLS + col] == 0 && total == 3)
33+
else if (this->previous[row * COLS + col] == 0 && total == 3)
2634
{
2735
return 1;
2836
}
2937
else
3038
{
31-
return this->buffer[row * COLS + col];
39+
return this->previous[row * COLS + col];
3240
}
3341
};
3442

3543
void GameOfLifePlugin::setup()
3644
{
37-
Screen.clear();
45+
this->state = this->STATE_END;
46+
};
3847

39-
memset(previous, 0, ROWS * COLS);
48+
void GameOfLifePlugin::init()
49+
{
50+
memset(this->previous2, 0, ROWS * COLS);
51+
memset(this->previous, 0, ROWS * COLS);
52+
memset(this->buffer, 0, ROWS * COLS);
4053
for (int i = 0; i < ROWS * COLS; i++)
4154
{
42-
this->buffer[i] = (random(10)) ? 1 : 0;
55+
this->buffer[i] = (random(2)) ? 1 : 0;
4356
}
44-
this->next();
45-
};
57+
58+
// Simple glider to test rules and wrapping
59+
// uint8_t start[] = {
60+
// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
61+
// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
62+
// 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
63+
// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
64+
// 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0,
65+
// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
66+
// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
67+
// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
68+
// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
69+
// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
70+
// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
71+
// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
72+
// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
73+
// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
74+
// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
75+
// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
76+
// };
77+
// memcpy(this->buffer, start, 256);
78+
79+
for (int j = 0; j < 8; j++)
80+
{
81+
for (int i = 0; i < COLS; i++)
82+
{
83+
if (j < 4)
84+
{ // grayish cover
85+
Screen.setPixel((i), (j*4+0), 1, 25);
86+
Screen.setPixel((i), (j*4+1), 1, 25);
87+
Screen.setPixel((i), (j*4+2), 1, 25);
88+
Screen.setPixel((i), (j*4+3), 1, 25);
89+
} else { // fill in actual cells
90+
j -= 4;
91+
Screen.setPixel(i, (j*4+0), this->buffer[(j*4+0) * COLS + i]);
92+
Screen.setPixel(i, (j*4+1), this->buffer[(j*4+1) * COLS + i]);
93+
Screen.setPixel(i, (j*4+2), this->buffer[(j*4+2) * COLS + i]);
94+
Screen.setPixel(i, (j*4+3), this->buffer[(j*4+3) * COLS + i]);
95+
j += 4;
96+
}
97+
delay(50);
98+
}
99+
}
100+
101+
this->state = this->STATE_RUNNING;
102+
}
46103

47104
void GameOfLifePlugin::next()
48105
{
49-
Screen.clear();
106+
memcpy(this->previous2, this->previous, ROWS * COLS);
107+
memcpy(this->previous, this->buffer, ROWS * COLS);
50108
for (int i = 0; i < ROWS; i++)
51109
{
52110
for (int j = 0; j < COLS; j++)
@@ -56,29 +114,69 @@ void GameOfLifePlugin::next()
56114
}
57115
}
58116

59-
int generations = 30;
60-
void GameOfLifePlugin::loop()
117+
void GameOfLifePlugin::show()
61118
{
62-
generations--;
63-
this->next();
119+
Screen.clear();
64120

65121
for (int i = 0; i < ROWS; i++)
66122
{
67123
for (int j = 0; j < COLS; j++)
68124
{
69-
Screen.setPixelAtIndex(i * COLS + j, this->buffer[i * COLS + j]);
125+
Screen.setPixel(j, i, this->buffer[i * COLS + j]);
70126
}
71127
}
72-
delay(150);
128+
}
73129

74-
if (generations == 0)
130+
int generations = 120;
131+
bool updated;
132+
bool updated2;
133+
void GameOfLifePlugin::loop()
134+
{
135+
switch (this->state)
75136
{
76-
generations = 30;
137+
case this->STATE_RUNNING:
138+
this->show();
139+
140+
generations--;
141+
this->next();
142+
updated = memcmp(this->buffer, this->previous, ROWS * COLS);
143+
updated2 = memcmp(this->buffer, this->previous2, ROWS * COLS);
144+
145+
// is running in period of 2
146+
if (!updated2) generations -= 5;
147+
148+
if (generations < 0 || !updated)
149+
{
150+
generations = 120;
151+
delay(gol_delay*4);
152+
this->state = this->STATE_END;
153+
}
154+
break;
155+
case this->STATE_END:
77156
this->setup();
157+
break;
78158
}
159+
160+
delay(gol_delay);
79161
};
80162

81163
const char *GameOfLifePlugin::getName() const
82164
{
83165
return "GameOfLife";
84166
}
167+
168+
void GameOfLifePlugin::websocketHook(DynamicJsonDocument &request)
169+
{
170+
const char *event = request["event"];
171+
172+
if (currentStatus == NONE)
173+
{
174+
if (!strcmp(event, "goldelay"))
175+
{
176+
uint16_t new_delay = request["delay"].as<uint16_t>();
177+
Serial.print("Changing Game of life delay to ");
178+
Serial.println(new_delay);
179+
gol_delay = new_delay;
180+
}
181+
}
182+
}

0 commit comments

Comments
 (0)