-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathelements.js
372 lines (350 loc) · 15.5 KB
/
elements.js
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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
/**@typedef {import("./structure.js").AbilityMetadata} AbilityMetadata */
/**@typedef {import("./structure.js").ElementalBoard} ElementalBoard */
"use strict";
import { Random } from "./modules/generators.js";
import { Point2D } from "./modules/measures.js";
import { Color } from "./modules/palette.js";
import { Ability, Elemental } from "./structure.js";
const random = Random.global;
const board = Elemental.Board.self;
//#region Dirt
class Dirt extends Elemental {
/** @type {string} */
static #name = `Dirt`;
/** @readonly @returns {string} */
static get name() { return Dirt.#name; }
/** @type {Readonly<Color>} */
static #color = Object.freeze(Color.viaRGB(150, 100, 80));
/** @readonly @returns {Readonly<Color>} */
static get color() { return Dirt.#color; }
/** @type {Readonly<AbilityMetadata[]>} */
static #metaset = Object.freeze([]);
/** @readonly @returns {Readonly<AbilityMetadata[]>} */
static get metaset() { return Dirt.#metaset; }
/** @type {Readonly<Ability[]>} */
#abilities = Object.freeze([]);
/** @readonly @returns {Readonly<Ability[]>} */
get abilities() { return this.#abilities; }
}
board.cases.set(Dirt, 90);
//#endregion
//#region Grass
class Grass extends Elemental {
/** @type {string} */
static #name = `Grass`;
/** @readonly @returns {string} */
static get name() { return Grass.#name; }
/** @type {Readonly<Color>} */
static #color = Object.freeze(Color.viaRGB(0, 128, 0));
/** @readonly @returns {Readonly<Color>} */
static get color() { return Grass.#color; }
/** @type {AbilityMetadata} */
static #metaGrow = new Ability.Metadata(`Grow`, `Grass can expand by converting a random adjacent Dirt element into a Grass element. The ability targets the eight neighboring cells (up, down, left, right, and diagonals) to find a Dirt element and transform it.`, 10);
/** @type {Readonly<AbilityMetadata[]>} */
static #metaset = Object.freeze([Grass.#metaGrow]);
/** @readonly @returns {Readonly<AbilityMetadata[]>} */
static get metaset() { return Grass.#metaset; }
/** @type {Ability} */
#grow = new Ability(Grass.#metaGrow, (invoker) => {
const positions = [
new Point2D(this.position.x - 1, this.position.y - 1),
new Point2D(this.position.x, this.position.y - 1),
new Point2D(this.position.x + 1, this.position.y - 1),
new Point2D(this.position.x - 1, this.position.y),
new Point2D(this.position.x + 1, this.position.y),
new Point2D(this.position.x - 1, this.position.y + 1),
new Point2D(this.position.x, this.position.y + 1),
new Point2D(this.position.x + 1, this.position.y + 1)
];
const targets = board.getElementsOfType(positions, Dirt);
if (targets.length > 0) {
const target = random.item(targets);
board.spawnElementOfType(target.position, Grass);
invoker.change();
}
});
/** @type {Readonly<Ability[]>} */
#abilities = Object.freeze([this.#grow]);
/** @readonly @returns {Readonly<Ability[]>} */
get abilities() { return this.#abilities; }
}
board.cases.set(Grass, 4);
//#endregion
//#region Fire
class Fire extends Elemental {
/** @type {string} */
static #name = `Fire`;
/** @readonly @returns {string} */
static get name() { return Fire.#name; }
/** @type {Readonly<Color>} */
static #color = Object.freeze(Color.viaRGB(255, 150, 0));
/** @readonly @returns {Readonly<Color>} */
static get color() { return Fire.#color; }
/** @type {AbilityMetadata} */
static #metaBurn = new Ability.Metadata(`Burn`, `Fire spreads by converting a random adjacent Grass element into a Fire element. It targets the four neighboring cells (up, down, left, right) to find a Grass element and ignite it. When Fire successfully spreads, it resets the progress of the Fade ability.`, 4);
/** @type {AbilityMetadata} */
static #metaFade = new Ability.Metadata(`Fade`, `When Fire burns out, it converts itself into a Dirt element. This ability is triggered when Fire has not spread to a new Grass element after a certain period, indicating the fire has exhausted its fuel and extinguishes itself.`, 16);
/** @type {Readonly<AbilityMetadata[]>} */
static #metaset = Object.freeze([Fire.#metaBurn, Fire.#metaFade]);
/** @readonly @returns {Readonly<AbilityMetadata[]>} */
static get metaset() { return Fire.#metaset; }
/** @type {Ability} */
#burn = new Ability(Fire.#metaBurn, (invoker) => {
const positions = [
new Point2D(this.position.x, this.position.y - 1),
new Point2D(this.position.x - 1, this.position.y),
new Point2D(this.position.x + 1, this.position.y),
new Point2D(this.position.x, this.position.y + 1),
];
const targets = board.getElementsOfType(positions, Grass);
if (targets.length > 0) {
const target = random.item(targets);
board.spawnElementOfType(target.position, Fire);
this.#fade.progress = 0;
invoker.change();
}
});
/** @type {Ability} */
#fade = new Ability(Fire.#metaFade, (invoker) => {
board.spawnElementOfType(this.position, Dirt);
});
/** @type {Readonly<Ability[]>} */
#abilities = Object.freeze([this.#burn, this.#fade]);
/** @readonly @returns {Readonly<Ability[]>} */
get abilities() { return this.#abilities; }
}
board.cases.set(Fire, 2);
//#endregion
//#region Water
class Water extends Elemental {
/** @type {string} */
static #name = `Water`;
/** @readonly @returns {string} */
static get name() { return Water.#name; }
/** @type {Readonly<Color>} */
static #color = Object.freeze(Color.viaRGB(0, 50, 255));
/** @readonly @returns {Readonly<Color>} */
static get color() { return Water.#color; }
/** @type {AbilityMetadata} */
static #metaFlow = new Ability.Metadata(`Flow`, `Water can spread by converting a random adjacent Dirt element into a Water element. This ability targets the eight neighboring cells to find a Dirt element and transform it into Water.`, 8);
/** @type {AbilityMetadata} */
static #metaEvaporate = new Ability.Metadata(`Evaporate`, ` Water evaporates when it encounters Fire. This ability converts a random adjacent Fire element and itself into Dirt elements. It targets the four neighboring cells to find a Fire element and then transforms both the Fire and Water into Dirt, simulating the evaporation process.`, 8);
/** @type {Readonly<AbilityMetadata[]>} */
static #metaset = Object.freeze([Water.#metaFlow, Water.#metaEvaporate]);
/** @readonly @returns {Readonly<AbilityMetadata[]>} */
static get metaset() { return Water.#metaset; }
/** @type {Ability} */
#flow = new Ability(Water.#metaFlow, (invoker) => {
const positions = [
new Point2D(this.position.x - 1, this.position.y - 1),
new Point2D(this.position.x, this.position.y - 1),
new Point2D(this.position.x + 1, this.position.y - 1),
new Point2D(this.position.x - 1, this.position.y),
new Point2D(this.position.x + 1, this.position.y),
new Point2D(this.position.x - 1, this.position.y + 1),
new Point2D(this.position.x, this.position.y + 1),
new Point2D(this.position.x + 1, this.position.y + 1)
];
const targets = board.getElementsOfType(positions, Dirt);
if (targets.length > 0) {
const target = random.item(targets);
board.spawnElementOfType(target.position, Water);
invoker.change();
}
});
/** @type {Ability} */
#evaporate = new Ability(Water.#metaEvaporate, (invoker) => {
const positions = [
new Point2D(this.position.x, this.position.y - 1),
new Point2D(this.position.x - 1, this.position.y),
new Point2D(this.position.x + 1, this.position.y),
new Point2D(this.position.x, this.position.y + 1),
];
const targets = board.getElementsOfType(positions, Fire);
if (targets.length > 0) {
const target = random.item(targets);
board.spawnElementOfType(target.position, Dirt);
board.spawnElementOfType(this.position, Dirt);
invoker.change();
}
});
/** @type {Readonly<Ability[]>} */
#abilities = Object.freeze([this.#flow, this.#evaporate]);
/** @readonly @returns {Readonly<Ability[]>} */
get abilities() { return this.#abilities; }
}
board.cases.set(Water, 2);
//#endregion
//#region Lava
class Lava extends Elemental {
/** @type {string} */
static #name = `Lava`;
/** @readonly @returns {string} */
static get name() { return Lava.#name; }
/** @type {Readonly<Color>} */
static #color = Object.freeze(Color.viaRGB(255, 0, 0));
/** @readonly @returns {Readonly<Color>} */
static get color() { return Lava.#color; }
/** @type {AbilityMetadata} */
static #metaFlow = new Ability.Metadata(`Flow`, `Lava can spread by converting a random adjacent Dirt element into a Lava element with lower density. This ability targets the four neighboring cells to find a Dirt element and transform it into Lava. The new Lava element will have a reduced density compared to the original.`, 15);
/** @type {AbilityMetadata} */
static #metaBurn = new Ability.Metadata(`Burn`, `Lava can ignite Grass by converting a random adjacent Grass element into a Fire element. It targets the four neighboring cells to find a Grass element and set it on fire.`, 8);
/** @type {AbilityMetadata} */
static #metaFade = new Ability.Metadata(`Fade`, `Lava cools down when it encounters Water. This ability converts a random adjacent Water element into a Dirt element and decreases the Lava's density. It targets the four neighboring cells to find a Water element. If the Lava's density reaches zero, it transforms itself into a Fire element, simulating the cooling process.`, 4);
/** @type {Readonly<AbilityMetadata[]>} */
static #metaset = Object.freeze([Lava.#metaFlow, Lava.#metaBurn, Lava.#metaFade]);
/** @readonly @returns {Readonly<AbilityMetadata[]>} */
static get metaset() { return Lava.#metaset; }
/** @type {number} */
static #maxDensity = 3;
/** @readonly @returns {Color} */
get color() {
return Color.mix(Fire.color, Lava.color, this.#density / Lava.#maxDensity);
}
/** @type {number} */
#density = Lava.#maxDensity;
/** @type {Ability} */
#flow = new Ability(Lava.#metaFlow, (invoker) => {
const positions = [
new Point2D(this.position.x, this.position.y - 1),
new Point2D(this.position.x - 1, this.position.y),
new Point2D(this.position.x + 1, this.position.y),
new Point2D(this.position.x, this.position.y + 1),
];
const targets = board.getElementsOfType(positions, Dirt);
if (targets.length > 0 && this.#density > 1) {
const target = random.item(targets);
const descendant = board.spawnElementOfType(target.position, Lava);
descendant.#density = this.#density - 1;
invoker.change();
}
});
/** @type {Ability} */
#burn = new Ability(Lava.#metaBurn, (invoker) => {
const positions = [
new Point2D(this.position.x, this.position.y - 1),
new Point2D(this.position.x - 1, this.position.y),
new Point2D(this.position.x + 1, this.position.y),
new Point2D(this.position.x, this.position.y + 1),
];
const targets = board.getElementsOfType(positions, Grass);
if (targets.length > 0) {
const target = random.item(targets);
board.spawnElementOfType(target.position, Fire);
invoker.change();
}
});
/** @type {Ability} */
#fade = new Ability(Lava.#metaFade, (invoker) => {
const positions = [
new Point2D(this.position.x, this.position.y - 1),
new Point2D(this.position.x - 1, this.position.y),
new Point2D(this.position.x + 1, this.position.y),
new Point2D(this.position.x, this.position.y + 1),
];
const targets = board.getElementsOfType(positions, Water);
if (targets.length > 0) {
const target = random.item(targets);
board.spawnElementOfType(target.position, Dirt);
if (this.#density > 0) {
this.#density--;
} else {
board.spawnElementOfType(this.position, Fire);
}
invoker.change();
}
});
/** @type {Readonly<Ability[]>} */
#abilities = Object.freeze([this.#flow, this.#burn, this.#fade]);
/** @readonly @returns {Readonly<Ability[]>} */
get abilities() { return this.#abilities; }
}
board.cases.set(Lava, 1);
//#endregion
//#region Ice
class Ice extends Elemental {
/** @type {string} */
static #name = `Ice`;
/** @readonly @returns {string} */
static get name() { return Ice.#name; }
/** @type {Readonly<Color>} */
static #color = Object.freeze(Color.viaRGB(0, 200, 255));
/** @readonly @returns {Readonly<Color>} */
static get color() { return Ice.#color; }
/** @type {AbilityMetadata} */
static #metaFlow = new Ability.Metadata(`Flow`, `Ice can spread by converting a random adjacent Dirt element into an Ice element with lower density. This ability targets the four neighboring cells to find a Dirt element and transform it into Ice. The new Ice element will have a reduced density compared to the original.`, 12);
/** @type {AbilityMetadata} */
static #metaMelt = new Ability.Metadata(`Melt`, `Ice melts when it encounters Fire. This ability converts a random adjacent Fire element into a Dirt element and decreases the Ice's density. It targets the four neighboring cells to find a Fire element. If the Ice's density reaches zero, it transforms itself into a Water element, simulating the melting process.`, 4);
/** @type {AbilityMetadata} */
static #metaEvaporate = new Ability.Metadata(`Evaporate`, `Ice evaporates when it encounters Lava. This ability converts a random adjacent Lava element and itself into Dirt elements. It targets the four neighboring cells to find a Lava element and then transforms both the Lava and Ice into Dirt, simulating the evaporation process.`, 4);
/** @type {Readonly<AbilityMetadata[]>} */
static #metaset = Object.freeze([Ice.#metaFlow, Ice.#metaMelt, Ice.#metaEvaporate]);
/** @readonly @returns {Readonly<AbilityMetadata[]>} */
static get metaset() { return Ice.#metaset; }
/** @type {number} */
static #maxDensity = 3;
/** @readonly @returns {Color} */
get color() {
return Color.mix(Water.color, Ice.color, this.#density / Ice.#maxDensity);
}
/** @type {number} */
#density = Ice.#maxDensity;
/** @type {Ability} */
#flow = new Ability(Ice.#metaFlow, (invoker) => {
const positions = [
new Point2D(this.position.x, this.position.y - 1),
new Point2D(this.position.x - 1, this.position.y),
new Point2D(this.position.x + 1, this.position.y),
new Point2D(this.position.x, this.position.y + 1),
];
const targets = board.getElementsOfType(positions, Dirt);
if (targets.length > 0 && this.#density > 1) {
const target = random.item(targets);
const descendant = board.spawnElementOfType(target.position, Ice);
descendant.#density = this.#density - 1;
invoker.change();
}
});
/** @type {Ability} */
#melt = new Ability(Ice.#metaMelt, (invoker) => {
const positions = [
new Point2D(this.position.x, this.position.y - 1),
new Point2D(this.position.x - 1, this.position.y),
new Point2D(this.position.x + 1, this.position.y),
new Point2D(this.position.x, this.position.y + 1),
];
const targets = board.getElementsOfType(positions, Fire);
if (targets.length > 0) {
const target = random.item(targets);
board.spawnElementOfType(target.position, Dirt);
if (this.#density > 0) {
this.#density--;
} else {
board.spawnElementOfType(this.position, Water);
}
invoker.change();
}
});
/** @type {Ability} */
#evaporate = new Ability(Ice.#metaEvaporate, (invoker) => {
const positions = [
new Point2D(this.position.x, this.position.y - 1),
new Point2D(this.position.x - 1, this.position.y),
new Point2D(this.position.x + 1, this.position.y),
new Point2D(this.position.x, this.position.y + 1),
];
const targets = board.getElementsOfType(positions, Lava);
if (targets.length > 0) {
const target = random.item(targets);
board.spawnElementOfType(target.position, Dirt);
board.spawnElementOfType(this.position, Dirt);
invoker.change();
}
});
/** @type {Readonly<Ability[]>} */
#abilities = Object.freeze([this.#flow, this.#melt, this.#evaporate]);
/** @readonly @returns {Readonly<Ability[]>} */
get abilities() { return this.#abilities; }
}
board.cases.set(Ice, 1);
//#endregion