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>
|