-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.html
444 lines (387 loc) · 15.1 KB
/
index.html
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
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
<html>
<head></head>
<body>
<input type="text" value="food squares per tick" id="foodSquaresPerTick"></input>
<!-- <input type="text" value="tick10" onclick="buttonTickTen()"></input>
<input type="text" value="tick100" onclick="buttonTickHundred()"></input> -->
<input type="button" value="set" onclick="set()"></input>
<table>
<tr>
<td></td>
<td>
<select class="actionSelect" data-direction="0">
<option value="none">None</option>
</select>
<select class="matcherSelect" data-direction="0">
<option value="any">Any</option>
</select>
</td>
<td></td>
</tr>
<tr>
<td>
<select class="actionSelect" data-direction="3">
<option value="none">None</option>
</select>
<select class="matcherSelect" data-direction="3">
<option value="any">Any</option>
</select>
</td>
<td>
<input id="addGeneButton" type="button" value="Add Gene"></input>
<input id="spawnCellButton" type="button" value="Spawn Cell"></input>
<input id="newCellButton" type="button" value="New Cell"></input>
</td>
<td>
<select class="actionSelect" data-direction="1">
<option value="none">None</option>
</select>
<select class="matcherSelect" data-direction="1">
<option value="any">Any</option>
</select>
</td>
</tr>
<tr>
<td></td>
<td>
<select class="actionSelect" data-direction="2">
<option value="none">None</option>
</select>
<select class="matcherSelect" data-direction="2">
<option value="any">Any</option>
</select>
</td>
<td></td>
</tr>
</table>
<input id="genOrgFromGPButton" type="button" value="Gen Org From Gene Pool"></input>
</body>
<!-- U T I L S -->
<script src="js/utils/Utils.js"></script>
<script src="js/utils/jquery-3.3.1.min.js"></script>
<script src="js/utils/jquery.color.js"></script>
<script src="js/utils/Chart.bundle.js"></script>
<script src="js/utils/color_mixer.js"></script>
<!-- M O D E L -->
<script src="js/model/Posn.js"></script>
<script src="js/model/GameObject.js"></script>
<script src="js/model/Obstacle.js"></script>
<script src="js/model/Square.js"></script>
<script src="js/model/Behavior.js"></script>
<script src="js/model/Gene.js"></script>
<script src="js/model/Cell.js"></script>
<script src="js/CellBuilder.js"></script>
<script src="js/CellStats.js"></script>
<script src="js/model/World.js"></script>
<!-- C U S T O M I Z A T I O N -->
<script src="js/customization/EnvMatchers.js"></script>
<script src="js/customization/Actions.js"></script>
<script src="js/customization/WorldParameters.js"></script>
<script src="js/GeneGeneration.js"></script>
<script>
// -------- T O D O -------- //
// - Make sexual reproduction
// - Decouple drawing logic from the individual files... Orrr maybe not
// - make chromosome system
// - make customizable
// - optimize and cleanup this mess
// - terrain differences?
// - clean up all the misc stuff and rules i have
// - irregular food sources
// - have a behavior that lasts multiple ticks -- like first you do x, then you do y, then you do z, so action list can be over multiple ticks?
// - variable food function
// - irreversible changes to terrain, like how much movement costs
// - again no one guy should be able to use up all the resources
// - reproduce at food cap
// - make graphs once we're in a good place
// - ahh what if its easy to mutate and get dif env-matchers of your stuff, but really hard to mutate and get a different action? Right now there's no commitment to specialization
// - need a way to encourage the predator prey competition pressure
// - address the tendency to always go right
// - need a way to identify Actions so I could potentially print out what genes belong to a cell
// - MAKE A WAY TO VIEW A CELL'S GENOME
// - MAKE A WAY TO ENGINEER NEW GENES ORGANISMS
// - maybe don't do a complete shuffle for mutation?
// - more variety in the matchers and stuff
// - make mutation modify existing genes, like just change a matcher or a behavior/direction?
// - make a geneBuilder so we can have gene pool engineering game!
// - make a way to get live stats on what genes exist!
// - allow custom gene pools for a world, that can be modified in real time!e
// - decouple World from CellStats() maybe just make the World send out events or something, and cellStats can listen to the events
// - start abstracting out all the customizable specifics of the World logic so I it can be fully customizable
// - make chart colors line up with actual action colors
// - make an action that puts down obstacles!
// - mess with frequencies of the starting gene pool so that we get less dud runs
// - can you store food inside obstacles? That could be interesting
// - more intelligent initial org generation: everyone should have at least 1 reproduce gene
// - OOHHHH TRY AND IMPLEMENT WRAPAROUND!!!
// - would be nice if every action had some side effects that change the environment, not just death
// - one frame flash to mark food falling
// - pause mode where you can scroll and zoom and hover mouse for info about org
// - have some obstacles be permanent
// - need emergent benefit of being close together, not an explicit one with Transfer.
// - what if it was a lot harder to mutate your actions, so you had to just mutate the order and env-matching of your shit to maximize survival potential?
// - bring back variable genome size?
// - make obstacle generation not walk itself into a corner as easy
// - food sharing properties when orgs are close together? Leeching/eating can emerge from this. (remove transfer if i do this)
// - allow orgs to evolve as FSMs rather than gene decks? Input tape is the environment + their food level? Transitions are actions: see this environment -> do this. each state has its own gene-set, then we just associate genes with transitioning to a particular state.
// - ok so a more fundamental problem is maybe that the most efficient strategy really is just what has evolved. Like can I even think of a better more consistent one? Probably not. I need to actually create an environment where there can be more varied efficient strategies. FSM might help a bit by allowing more intelligent behavior, which could be gotten to by pressure from competitionn
// - there should be no perfect undos -- so digging shouldnt be able to perfectly undo cell death
// - new data type : GeneTransition? Combo of gene and pointer to new state
// - State is an array of GeneTransition
// - make cells have states, and implement transitioning, but make them all transation to the single state (for now leave gene deck behavior)
// - have a stateIndex, which is the index of the State that the cell is currently in.
// - make the cell code only use the current state as the GeneDeck
// - reduce genome size to 6 (6 per state) or maybe 5?
// - then we need different mutation types -- shuffle, just a transition pointer mutation, and then a full gene mutation
// -------- D E S I G N O V E R V I E W -------- //
// I am going to do this in a functional style.
// -------- M O D E L -------- //
// -------- T E S T I N G -------- //
var SPACEBAR = 32; //keycode
var CANVAS_WIDTH = WORLD_WIDTH * CELL_WIDTH;
var CANVAS_HEIGHT = WORLD_HEIGHT * CELL_HEIGHT;
//randomly generates a set of size genes from the given gene pool
//will guarantee that at least 1 of them is a reproduction gene
function genGenome(pool, size) {
var genome = [];
var reproductionGenes = pool.filter(function(gene) {
return gene.behavior.action == reproduce;
})
genome.push(choose(reproductionGenes));
for (var i = 1; i < size; i++) {
genome.push(choose(pool));
}
return genome;
}
function genGenePool(size) {
var genePool = [];
for (var i = 0; i < size; i++) {
genePool.push(genGene());
}
return genePool;
}
function genPopulation(pool, size) {
var population = []
for (var p = 0; p < size; p++) {
var genes = genGenome(pool, CELL_GENOME_SIZE)
population.push(new Cell(CELL_STARTING_FOOD, false, genes, 0, genomeToColor(genes)))
}
return population;
}
function nextPopulation(species) {
var newPop = [];
species.forEach(function(cell) {
newPop.push(cell.newChild(CELL_STARTING_FOOD / 2))
newPop.push(cell.newChild(CELL_STARTING_FOOD / 2))
})
return newPop;
}
//mutates the given world, initializing it with the starting population and obstacles
function populateWorld(world, population, genomeSize, foodSquares) {
//now spawn obstacles
for (var i = 0; i < STARTING_OBSTACLES; i++) {
var pos = world.randomPosn();
if (world.board[pos.x][pos.y].content) {
i--;
} else {
world.board[pos.x][pos.y].content = new Obstacle()
for (var e = 0; e < OBSTACLE_LENGTH; e++) {
var adjacents = pos.getAdjacentPosnsWithWraparound(world);
var openAdjacents = adjacents.filter(function(posn) {
if (world.get(posn) && !world.get(posn).content) {
return true
}
return false;
})
if (openAdjacents.length == 0) {
e = OBSTACLE_LENGTH; //break the loop
} else {
pos = choose(openAdjacents);
//TODO actually do this nicely designed, have a different type of obstacle that cells wont waste energy trying to dig through
var unbreakableObstacle = new Obstacle();
unbreakableObstacle.unbreakable = true;
world.board[pos.x][pos.y].content = unbreakableObstacle;
}
}
}
}
//now spawn cells
for (var i = 0; i < population.length; i++) {
var curCell = population[i];
var randomPosn = world.randomPosn();
if (world.get(randomPosn).content) {
i--;
} else {
world.spawnCell(randomPosn, curCell);
}
}
//now spawn food
for (var i = 0; i < foodSquares; i++) {
world.spawnFood(world.randomPosn(), FOOD_PER_SQUARE)
}
}
function init(world) {
//set up CellBuilder UI here?
var cellBuilder = new CellBuilder(ACTION_SPEC, MATCHER_SPEC)
var genePool = [];
var actionSelects = document.getElementsByClassName("actionSelect");
var matcherSelects = document.getElementsByClassName("matcherSelect");
var actionKeys = Object.keys(ACTION_SPEC);
var matcherKeys = Object.keys(MATCHER_SPEC);
Array.prototype.forEach.call(actionSelects, function(actionSelect) {
actionKeys.forEach(function(actionKey) {
var option = document.createElement("option");
option.text = actionKey;
option.value = actionKey;
actionSelect.add(option);
})
})
Array.prototype.forEach.call(matcherSelects, function(matcherSelect) {
matcherKeys.forEach(function(matcherKey) {
var option = document.createElement("option");
option.text = matcherKey;
option.value = matcherKey;
matcherSelect.add(option);
})
})
//setting up addGene functionality
document.getElementById("addGeneButton").onclick = function() {
for (i = 0; i < actionSelects.length; i++) {
var actionSelect = actionSelects[i];
var direction = parseInt(actionSelect.dataset.direction)
var actionName = actionSelect.value;
cellBuilder.addBehavior(actionName, direction)
//i = actionSelects.length; //break from the for loop, since we can only have 1 action per gene
}
Array.prototype.forEach.call(matcherSelects, function(matcherSelect) {
var direction = parseInt(matcherSelect.dataset.direction)
cellBuilder.addMatcher(matcherSelect.value, direction);
})
cellBuilder.addGene();
cellBuilder.clearGeneWorkspace();
alert("added the gene")
}
//setting up spawnCell functionality
document.getElementById("spawnCellButton").onclick = function() {
world.spawnCell(world.randomPosn(), cellBuilder.genCell());
//alert("spawned a cell!")
}
//setting up newCell functionality
document.getElementById("newCellButton").onclick = function() {
cellBuilder.clearEntireWorkspace();
alert("cleared workspace!")
}
document.getElementById("genOrgFromGPButton").onclick = function() {
var workingGenes = []
for (i = 0; i < CELL_GENOME_SIZE; i++) {
workingGenes.push(choose(genePool))
}
var cell = new Cell(CELL_STARTING_FOOD, false, workingGenes, 0, genomeToColor(workingGenes));
alert("Rn this does nothing, just is scaffolding for when GeneBuilder is finished and I wire up that stuff...")
}
}
// - ACTUAL FRAMES --------------------------------
function main() {
//canvas setup for main simulation
canvas = document.createElement("canvas");
canvas.width = CANVAS_WIDTH;
canvas.height = CANVAS_HEIGHT;
ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
//canvas setup for stats chart
var chartCanvas = document.createElement("canvas");
//chartCanvas.width = 800;
//chartCanvas.height = 800;
var chartCtx = chartCanvas.getContext("2d");
chartCtx.width = 400;
chartCtx.height = 400;
document.body.appendChild(chartCanvas)
//game state stuff
var world = new World(emptyBoard(WORLD_WIDTH, WORLD_HEIGHT), 0, {foodSquaresPerTick: FOOD_SQUARES_PER_TICK, foodPerSquare: FOOD_PER_SQUARE, decomposeThreshold: DECOMPOSE_THRESHOLD});
//population init stuff
var population = genPopulation(genGenePool(GENE_POOL_SIZE), STARTING_POPULATION)
populateWorld(world, population, 10, INITIAL_FOOD_SQUARES);
init(world);
// ------------------------ C H A R T S E T U P -------- //
var getGeneCountsByActionNames = function() {
return Object.keys(world.cellStats.geneCountsByAction);
}
var getGeneCountsByAction = function() {
return Object.keys(world.cellStats.geneCountsByAction).map(function(key) {
return world.cellStats.geneCountsByAction[key];
})
}
var geneChart = new Chart(chartCtx, {
type: 'pie',
data: {
labels: getGeneCountsByActionNames(),
datasets: [{
label: 'Frequency of Actions in Genes',
data: getGeneCountsByAction(),
backgroundColor: [
'rgba(255, 99, 132, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(255, 206, 86, 0.2)',
'rgba(75, 192, 192, 0.2)',
'rgba(153, 102, 255, 0.2)',
'rgba(255, 159, 64, 0.2)'
],
borderColor: [
'rgba(255,99,132,1)',
'rgba(54, 162, 235, 1)',
'rgba(255, 206, 86, 1)',
'rgba(75, 192, 192, 1)',
'rgba(153, 102, 255, 1)',
'rgba(255, 159, 64, 1)'
],
borderWidth: 1
}]
},
options: {
maintainAspectRatio: false,
responsive: false,
scales: {
yAxes: [{
ticks: {
beginAtZero:true
}
}]
}
}
});
//keypress and release handlers
keystate = {};
document.addEventListener("keydown", function(evt) {
keystate[evt.keyCode] = true;
if (evt.keyCode == SPACEBAR) {
world.togglePause()
//
}
});
document.addEventListener("keyup", function(evt) {
keystate[evt.keyCode] = false;
});
//main loop
var loop = function() {
if (!world.paused) {
world.tick();
world.draw(ctx);
//maybe only do this every 100 ticks or so?
if (world.tickNum % 100 == 0) {
geneChart.data.datasets[0].data = getGeneCountsByAction()
geneChart.update()
}
}
window.requestAnimationFrame(loop, canvas);
}
window.requestAnimationFrame(loop, canvas);
}
main();
// - SWITCH TO A NORMAL FRAME SYSTEM INSTEAD OF THIS WEIRD PROMISE SHIT
function set() {
var foodSquaresPerTick = parseInt(document.getElementById("foodSquaresPerTick").value)
alert(foodSquaresPerTick)
FOOD_SQUARES_PER_TICK = foodSquaresPerTick;
}
</script>
</html>