Download Game! Currently 61 players and visitors. Last logged in:LmtHaikeaAlistarBauglir

Blitzer's Blog >> 71685

Back to blogs index
Posted: 06 Aug 2026 16:40 [ permalink ]
'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);