'use strict';
* Artificial Chemistry / Life -simulaation MVP-pelimoottori
* Arkkitehtuuri: Data-Oriented Design (SoA), Double Buffering, 1D Typed
Arrays
* KentC$n koko: 32 x 32 (1024 solua)
*/
/ --- Vakiot ---
const GRID_WIDTH = 32;const GRID_HEIGHT = 32;const TOTAL_CELLS = GRID_WIDTH *
GRID_HEIGHT;
const CHAR_EMPTY = 46; // '.'const CHAR_WALL = 35; // '#'const CHAR_MOVER
= 77; // 'M'
const HEAT_INPUT_AMOUNT = 10;const MAX_HEAT = 255;
/ --- Double Buffering -puskurit ---
/ Rakenne: buffers[0] = read, buffers[1] = write (tai pC$invastoin riippuen
indeksistC$)
const buffers = [ { cellTypes: new Uint8Array(TOTAL_CELLS),
cellAlive: new Uint8Array(TOTAL_CELLS),
cellHeat: new Uint8Array(TOTAL_CELLS) }, {
cellTypes: new Uint8Array(TOTAL_CELLS),
cellAlive: new Uint8Array(TOTAL_CELLS),
cellHeat: new Uint8Array(TOTAL_CELLS) } ];
let readIndex = 0;let writeIndex = 1;
/ VC$liaikainen puskuri siirtoaikeille (ei tarvitse double bufferingia)
const intentBuffer = new Int16Array(TOTAL_CELLS);
/ --- Apufunktiot ---
function getReadBuffer() { return buffers[readIndex];}
function getWriteBuffer() { return buffers[writeIndex];}
function swapBuffers() { readIndex = 1 - readIndex; writeIndex = 1 -
writeIndex;}
/ --- Alustus ---
function init() { const read = getReadBuffer(); const write =
getWriteBuffer();
// TC$ytC$ koko kenttC$ tyhjC$llC$ ('.')
read.cellTypes.fill(CHAR_EMPTY); read.cellAlive.fill(0);
read.cellHeat.fill(0);
// Aseta esteitC$ (#) - "pari raskasta estettC$"
// Rivit 15 ja 16, sarakkeet 20-25
for (let row = 15; row <= 16; row++) {
for (let col = 20; col <= 25; col++) {
const idx = row * GRID_WIDTH + col;
read.cellTypes[idx] = CHAR_WALL;
read.cellAlive[idx] = 0; // kuollut } }
// Aseta elC$viC$ Moottori-soluja ('M')
// Kaksi M-solua riveillC$ 15 ja 16, sarakkeessa 10
const m1Idx = 15 * GRID_WIDTH + 10;
read.cellTypes[m1Idx] = CHAR_MOVER; read.cellAlive[m1Idx] = 1;
read.cellHeat[m1Idx] = 100;
const m2Idx = 16 * GRID_WIDTH + 10;
read.cellTypes[m2Idx] = CHAR_MOVER; read.cellAlive[m2Idx] = 1;
read.cellHeat[m2Idx] = 100;
// Kopioi alkutila myC6s write-puskuriin
write.cellTypes.set(read.cellTypes); write.cellAlive.set(read.cellAlive);
write.cellHeat.set(read.cellHeat);
}
/ --- Pelisilmukan vaiheet ---
* 1. Heat Input: LisC$C$ energiaa jokaiselle elC$vC$lle solulle.
*/
function heatInput() {
const read = getReadBuffer(); const write = getWriteBuffer();
for (let i = 0; i < TOTAL_CELLS; i++) { if (read.cellAlive[i] === 1)
{
const newHeat = Math.min(read.cellHeat[i] + HEAT_INPUT_AMOUNT,
MAX_HEAT);
write.cellHeat[i] = newHeat;
} else { write.cellHeat[i] = read.cellHeat[i]; } }
}
* 2. Intent: Luo siirtoaikeet liikkuville soluille ('M').
* Moottori tahtoo siirtyC$ askeleen oikealle (+1 x).
*/
function generateIntents() {
const read = getReadBuffer();
intentBuffer.fill(-1); // -1 tarkoittaa "ei aietta"
for (let i = 0; i < TOTAL_CELLS; i++) {
if (read.cellAlive[i] === 1 && read.cellTypes[i] === CHAR_MOVER) {
const col = i % GRID_WIDTH;
if (col < GRID_WIDTH - 1) { intentBuffer[i] = i + 1; //
kohdeindeksi oikealle
} } }
}
* 3. Conflict Resolution: Ratkaise konfliktit ja suorita hyvC$ksytyt siirrot.
*/
function resolveConflicts() {
const read = getReadBuffer(); const write = getWriteBuffer();
// Kopioi read-puskurin cellTypes ja cellAlive write-puskuriin.
// cellHeat on jo pC$ivitetty heatInput-vaiheessa.
write.cellTypes.set(read.cellTypes); write.cellAlive.set(read.cellAlive);
// PidetC$C$n kirjaa varatuista kohderuuduista tC$mC$n tickin aikana
const claimed = new Uint8Array(TOTAL_CELLS);
for (let i = 0; i < TOTAL_CELLS; i++) {
const target = intentBuffer[i];
if (target === -1) continue;
// Tarkista, onko kohderuutu tyhjC$ ('.') ja ei jo varattu
if (write.cellTypes[target] === CHAR_EMPTY && claimed[target] === 0) {
// Siirto hyvC$ksytty
const moverHeat = write.cellHeat[i]; // pC$ivitetty lC$mpC6
// TyhjennC$ lC$htC6ruutu
write.cellTypes[i] = CHAR_EMPTY; write.cellAlive[i] = 0;
write.cellHeat[i] = 0;
// Aseta kohderuutuun
write.cellTypes[target] = CHAR_MOVER; write.cellAlive[tar
get] = 1; write.cellHeat[target] = moverHeat;
claimed[target] = 1; }
// muuten: siirto peruuntuu (kineettinen shokki), solu pysyy
paikoillaan
}
}
* 4. Commit: vaihda read/write-puskurit.
*/
function commit() { swapBuffers();}
* Suorita yksi pelisilmukan tick.
*/
function tick() { heatInput(); generateIntents(); resolveConflicts();
commit();}
/ --- RenderC6inti ---
function render() {
const read = getReadBuffer(); const lines = [];
for (let row = 0; row < GRID_HEIGHT; row++) {
let line = ''; for (let col = 0; col < GRID_WIDTH; col++) {
const idx = row * GRID_WIDTH + col;
line += String.fromCharCode(read.cellTypes[idx]); }
lines.push(line); }
console.log(lines.join('
')); console.log('---');
}
/ --- KC$ynnistys ---
init();
/ PC$C$silmukka: 500 ms vC$lein tick ja renderC6i
setInterval(() => { tick(); render();}, 500);