Spaces:
Running
Running
Update index.html
Browse files- index.html +131 -85
index.html
CHANGED
@@ -107,19 +107,22 @@
|
|
107 |
<div class="rules">
|
108 |
<h2>Simulation Rules</h2>
|
109 |
<ul>
|
110 |
-
<li>Cancer cells die when surrounded by 3+ cells within
|
|
|
111 |
<li>Cancer cells convert isolated healthy cells (no nearby healthy)</li>
|
112 |
<li>Cells reproduce based on type-specific rates</li>
|
113 |
-
<li>Cancer cells move faster than healthy cells</li>
|
114 |
<li>Neural networks control movement decisions</li>
|
115 |
-
<li>Adjust parameters below to influence outcomes</li>
|
116 |
</ul>
|
117 |
</div>
|
118 |
|
119 |
<div class="controls">
|
120 |
<h2>Simulation Parameters</h2>
|
121 |
<div class="param-group">
|
122 |
-
<label>
|
|
|
|
|
|
|
123 |
<label>Initial Healthy: <input type="number" id="initialHealthy" value="15" min="1"></label>
|
124 |
<label>Initial Cancer: <input type="number" id="initialCancer" value="8" min="1"></label>
|
125 |
<label>Mutation Rate: <input type="number" id="mutationRate" value="0.1" step="0.01" min="0"></label>
|
@@ -174,7 +177,9 @@
|
|
174 |
const cellRadius = 5;
|
175 |
let populationChart = null;
|
176 |
let generationsData = [];
|
177 |
-
let currentHiddenDim =
|
|
|
|
|
178 |
|
179 |
function getNormal(mean = 0, std = 1) {
|
180 |
let u, v, s;
|
@@ -277,6 +282,96 @@
|
|
277 |
}
|
278 |
}
|
279 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
280 |
function updateChart() {
|
281 |
const ctx = document.getElementById('populationChart').getContext('2d');
|
282 |
|
@@ -323,92 +418,40 @@
|
|
323 |
`).join('');
|
324 |
}
|
325 |
|
326 |
-
function
|
327 |
-
|
328 |
-
const
|
329 |
-
|
330 |
-
|
331 |
-
|
332 |
-
|
333 |
-
|
334 |
-
|
335 |
-
|
336 |
-
|
337 |
-
|
338 |
-
|
339 |
-
|
340 |
-
if(generationsData.length > 20) generationsData.shift();
|
341 |
-
updateChart();
|
342 |
-
updateTable();
|
343 |
-
}
|
344 |
-
}
|
345 |
-
|
346 |
-
function checkCollisions() {
|
347 |
-
cells.forEach((cell, i) => {
|
348 |
-
if(cell.type === 'cancer') {
|
349 |
-
const neighbors = cells.filter(c => c !== cell &&
|
350 |
-
Math.hypot(c.x - cell.x, c.y - cell.y) < 40);
|
351 |
-
if(neighbors.length >= 3) {
|
352 |
-
cells.splice(i, 1);
|
353 |
-
return;
|
354 |
}
|
355 |
|
356 |
-
cells.forEach(
|
357 |
-
|
358 |
-
|
359 |
-
|
360 |
-
|
361 |
-
Math.hypot(c.x - other.x, c.y - other.y) < 60
|
362 |
-
);
|
363 |
-
if(healthyNeighbors.length === 0) {
|
364 |
-
other.type = 'cancer';
|
365 |
-
}
|
366 |
-
}
|
367 |
-
});
|
368 |
}
|
369 |
-
|
370 |
-
|
371 |
-
|
372 |
-
|
373 |
-
|
374 |
-
const healthyCells = cells.filter(c => c.type === 'healthy');
|
375 |
-
const healthyCandidates = [...healthyCells].sort(() => Math.random() - 0.5).slice(0, healthyReproRate);
|
376 |
-
healthyCandidates.forEach(cell => cells.push(new Cell('healthy', cell)));
|
377 |
-
|
378 |
-
const cancerReproRate = parseInt(document.getElementById('cancerRepro').value);
|
379 |
-
const cancerCells = cells.filter(c => c.type === 'cancer');
|
380 |
-
const cancerCandidates = [...cancerCells].sort(() => Math.random() - 0.5).slice(0, cancerReproRate);
|
381 |
-
cancerCandidates.forEach(cell => cells.push(new Cell('cancer', cell)));
|
382 |
-
}
|
383 |
-
|
384 |
-
function updateStatus() {
|
385 |
-
document.getElementById('genCount').textContent = generation;
|
386 |
-
document.getElementById('healthyCount').textContent =
|
387 |
-
cells.filter(c => c.type === 'healthy').length;
|
388 |
-
document.getElementById('cancerCount').textContent =
|
389 |
-
cells.filter(c => c.type === 'cancer').length;
|
390 |
-
}
|
391 |
-
|
392 |
-
function animate() {
|
393 |
-
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
394 |
-
|
395 |
-
frameCount++;
|
396 |
-
if(frameCount % 60 === 0) {
|
397 |
-
generation++;
|
398 |
-
reproduceCells();
|
399 |
-
updateStatus();
|
400 |
-
saveGenerationData();
|
401 |
}
|
402 |
-
|
403 |
-
cells.forEach(cell => cell.update());
|
404 |
-
checkCollisions();
|
405 |
-
cells.forEach(cell => cell.draw());
|
406 |
-
|
407 |
-
animationId = requestAnimationFrame(animate);
|
408 |
}
|
409 |
|
410 |
document.getElementById('start').addEventListener('click', () => {
|
411 |
-
if(!animationId)
|
|
|
|
|
|
|
412 |
});
|
413 |
|
414 |
document.getElementById('reset').addEventListener('click', () => {
|
@@ -433,7 +476,10 @@
|
|
433 |
cells.forEach(cell => cell.draw());
|
434 |
});
|
435 |
|
436 |
-
|
|
|
|
|
|
|
437 |
document.getElementById('reset').click();
|
438 |
</script>
|
439 |
</body>
|
|
|
107 |
<div class="rules">
|
108 |
<h2>Simulation Rules</h2>
|
109 |
<ul>
|
110 |
+
<li>Cancer cells die when surrounded by 3+ cells within <span id="deathProxDisplay">20</span>px</li>
|
111 |
+
<li>Healthy cells die when near 2+ cancer cells within 30px</li>
|
112 |
<li>Cancer cells convert isolated healthy cells (no nearby healthy)</li>
|
113 |
<li>Cells reproduce based on type-specific rates</li>
|
114 |
+
<li>Cancer cells move 50% faster than healthy cells</li>
|
115 |
<li>Neural networks control movement decisions</li>
|
|
|
116 |
</ul>
|
117 |
</div>
|
118 |
|
119 |
<div class="controls">
|
120 |
<h2>Simulation Parameters</h2>
|
121 |
<div class="param-group">
|
122 |
+
<label>Death Proximity (px): <input type="number" id="deathProximity" value="20" min="1"></label>
|
123 |
+
<label>Healthy Death Threshold: <input type="number" id="healthyDeathThreshold" value="2" min="1"></label>
|
124 |
+
<label>Healthy Death Radius: <input type="number" id="healthyDeathRadius" value="30" min="1"></label>
|
125 |
+
<label>Hidden Dimension: <input type="number" id="hiddenDim" value="6" min="1"></label>
|
126 |
<label>Initial Healthy: <input type="number" id="initialHealthy" value="15" min="1"></label>
|
127 |
<label>Initial Cancer: <input type="number" id="initialCancer" value="8" min="1"></label>
|
128 |
<label>Mutation Rate: <input type="number" id="mutationRate" value="0.1" step="0.01" min="0"></label>
|
|
|
177 |
const cellRadius = 5;
|
178 |
let populationChart = null;
|
179 |
let generationsData = [];
|
180 |
+
let currentHiddenDim = 6;
|
181 |
+
const targetFPS = 60;
|
182 |
+
let lastFrame = 0;
|
183 |
|
184 |
function getNormal(mean = 0, std = 1) {
|
185 |
let u, v, s;
|
|
|
282 |
}
|
283 |
}
|
284 |
|
285 |
+
function checkCollisions() {
|
286 |
+
const deathProximity = parseInt(document.getElementById('deathProximity').value);
|
287 |
+
const healthyDeathThreshold = parseInt(document.getElementById('healthyDeathThreshold').value);
|
288 |
+
const healthyDeathRadius = parseInt(document.getElementById('healthyDeathRadius').value);
|
289 |
+
|
290 |
+
const cellsCopy = [...cells];
|
291 |
+
const cellsToRemove = new Set();
|
292 |
+
const cellsToConvert = new Set();
|
293 |
+
|
294 |
+
cellsCopy.forEach((cell) => {
|
295 |
+
if(cell.type === 'healthy') {
|
296 |
+
const nearbyCancer = cellsCopy.filter(c =>
|
297 |
+
c.type === 'cancer' &&
|
298 |
+
Math.hypot(c.x - cell.x, c.y - cell.y) < healthyDeathRadius
|
299 |
+
);
|
300 |
+
if(nearbyCancer.length >= healthyDeathThreshold) {
|
301 |
+
cellsToRemove.add(cell);
|
302 |
+
}
|
303 |
+
}
|
304 |
+
|
305 |
+
if(cell.type === 'cancer') {
|
306 |
+
const neighbors = cellsCopy.filter(c =>
|
307 |
+
c !== cell &&
|
308 |
+
Math.hypot(c.x - cell.x, c.y - cell.y) < deathProximity
|
309 |
+
);
|
310 |
+
if(neighbors.length >= 3) {
|
311 |
+
cellsToRemove.add(cell);
|
312 |
+
}
|
313 |
+
|
314 |
+
cellsCopy.forEach((other) => {
|
315 |
+
if(other.type === 'healthy' &&
|
316 |
+
Math.hypot(cell.x - other.x, cell.y - other.y) < cellRadius * 2 &&
|
317 |
+
!cellsToConvert.has(other)) {
|
318 |
+
const healthyNeighbors = cellsCopy.filter(c =>
|
319 |
+
c !== other &&
|
320 |
+
c.type === 'healthy' &&
|
321 |
+
Math.hypot(c.x - other.x, c.y - other.y) < 60
|
322 |
+
);
|
323 |
+
if(healthyNeighbors.length === 0) {
|
324 |
+
cellsToConvert.add(other);
|
325 |
+
}
|
326 |
+
}
|
327 |
+
});
|
328 |
+
}
|
329 |
+
});
|
330 |
+
|
331 |
+
cells = cells.filter(cell => !cellsToRemove.has(cell));
|
332 |
+
cellsToConvert.forEach(cell => cell.type = 'cancer');
|
333 |
+
}
|
334 |
+
|
335 |
+
function reproduceCells() {
|
336 |
+
const healthyReproRate = parseInt(document.getElementById('healthyRepro').value);
|
337 |
+
const healthyCells = cells.filter(c => c.type === 'healthy');
|
338 |
+
const healthyCandidates = [...healthyCells].sort(() => Math.random() - 0.5).slice(0, healthyReproRate);
|
339 |
+
healthyCandidates.forEach(cell => cells.push(new Cell('healthy', cell)));
|
340 |
+
|
341 |
+
const cancerReproRate = parseInt(document.getElementById('cancerRepro').value);
|
342 |
+
const cancerCells = cells.filter(c => c.type === 'cancer');
|
343 |
+
const cancerCandidates = [...cancerCells].sort(() => Math.random() - 0.5).slice(0, cancerReproRate);
|
344 |
+
cancerCandidates.forEach(cell => cells.push(new Cell('cancer', cell)));
|
345 |
+
}
|
346 |
+
|
347 |
+
function updateStatus() {
|
348 |
+
document.getElementById('genCount').textContent = generation;
|
349 |
+
document.getElementById('healthyCount').textContent =
|
350 |
+
cells.filter(c => c.type === 'healthy').length;
|
351 |
+
document.getElementById('cancerCount').textContent =
|
352 |
+
cells.filter(c => c.type === 'cancer').length;
|
353 |
+
}
|
354 |
+
|
355 |
+
function saveGenerationData() {
|
356 |
+
if(generation % 10 === 0) {
|
357 |
+
const healthy = cells.filter(c => c.type === 'healthy').length;
|
358 |
+
const cancer = cells.filter(c => c.type === 'cancer').length;
|
359 |
+
const speeds = cells.map(c => c.speed);
|
360 |
+
const avgSpeed = speeds.reduce((a,b) => a + b, 0) / speeds.length || 0;
|
361 |
+
|
362 |
+
generationsData.push({
|
363 |
+
generation,
|
364 |
+
healthy,
|
365 |
+
cancer,
|
366 |
+
avgSpeed
|
367 |
+
});
|
368 |
+
|
369 |
+
if(generationsData.length > 20) generationsData.shift();
|
370 |
+
updateChart();
|
371 |
+
updateTable();
|
372 |
+
}
|
373 |
+
}
|
374 |
+
|
375 |
function updateChart() {
|
376 |
const ctx = document.getElementById('populationChart').getContext('2d');
|
377 |
|
|
|
418 |
`).join('');
|
419 |
}
|
420 |
|
421 |
+
function safeAnimate(timestamp) {
|
422 |
+
try {
|
423 |
+
const delta = timestamp - lastFrame;
|
424 |
+
|
425 |
+
if (delta >= 1000/targetFPS) {
|
426 |
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
427 |
+
|
428 |
+
frameCount++;
|
429 |
+
if(frameCount % 60 === 0) {
|
430 |
+
generation++;
|
431 |
+
reproduceCells();
|
432 |
+
updateStatus();
|
433 |
+
saveGenerationData();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
434 |
}
|
435 |
|
436 |
+
cells.forEach(cell => cell.update());
|
437 |
+
checkCollisions();
|
438 |
+
cells.forEach(cell => cell.draw());
|
439 |
+
|
440 |
+
lastFrame = timestamp;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
441 |
}
|
442 |
+
animationId = requestAnimationFrame(safeAnimate);
|
443 |
+
} catch (error) {
|
444 |
+
console.error('Simulation error:', error);
|
445 |
+
document.getElementById('status').innerHTML += ' [PAUSED DUE TO ERROR]';
|
446 |
+
cancelAnimationFrame(animationId);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
447 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
448 |
}
|
449 |
|
450 |
document.getElementById('start').addEventListener('click', () => {
|
451 |
+
if(!animationId) {
|
452 |
+
lastFrame = performance.now();
|
453 |
+
animationId = requestAnimationFrame(safeAnimate);
|
454 |
+
}
|
455 |
});
|
456 |
|
457 |
document.getElementById('reset').addEventListener('click', () => {
|
|
|
476 |
cells.forEach(cell => cell.draw());
|
477 |
});
|
478 |
|
479 |
+
document.getElementById('deathProximity').addEventListener('input', function() {
|
480 |
+
document.getElementById('deathProxDisplay').textContent = this.value;
|
481 |
+
});
|
482 |
+
|
483 |
document.getElementById('reset').click();
|
484 |
</script>
|
485 |
</body>
|