๐ŸŽ“ 12์ฃผ์ฐจ: ์…€๋ฃฐ๋Ÿฌ ์˜คํ† ๋งˆํƒ€(Cellular Automata)

BamgasiJMยท2025๋…„ 11์›” 6์ผ

p5.js Art

๋ชฉ๋ก ๋ณด๊ธฐ
23/67
post-thumbnail

๋‹จ์ˆœ ๊ทœ์น™์œผ๋กœ ๋ณต์žกํ•œ ์ƒ๋ช… ํ˜„์ƒ ๊ตฌํ˜„

์…€๋ฃฐ๋Ÿฌ ์˜คํ† ๋งˆํƒ€(CA)๋Š” ๊ฒฉ์ž ํ˜•ํƒœ๋กœ ๋ฐฐ์—ด๋œ ์…€(Cell)๋“ค์ด ์ฃผ๋ณ€ ์…€์˜ ์ƒํƒœ์™€ ๋ฏธ๋ฆฌ ์ •ํ•ด์ง„ ๋‹จ์ˆœํ•œ ๊ทœ์น™์— ๋”ฐ๋ผ ์ž์‹ ์˜ ์ƒํƒœ๋ฅผ ๋ณ€ํ™”์‹œํ‚ค๋ฉฐ ์‹œ๊ฐ„์„ ์ง„ํ–‰ํ•˜๋Š” ์‹œ์Šคํ…œ์ž…๋‹ˆ๋‹ค. ์ด ์‹œ์Šคํ…œ์˜ ๊ฐ€์žฅ ์œ ๋ช…ํ•œ ์˜ˆ๊ฐ€ ๋ฐ”๋กœ ์ฝ˜์›จ์ด์˜ ์ƒ๋ช… ๊ฒŒ์ž„(Conwayโ€™s Game of Life, GOL)์ž…๋‹ˆ๋‹ค.

GOL์€ ํƒ„์ƒ(Birth)๊ณผ ์ฃฝ์Œ(Death)์ด๋ผ๋Š” ๋‹จ ๋„ค ๊ฐ€์ง€์˜ ๋‹จ์ˆœํ•œ ๊ทœ์น™๋งŒ์œผ๋กœ ์˜ˆ์ธก ๋ถˆ๊ฐ€๋Šฅํ•˜๊ณ  ๋ณต์žกํ•˜๋ฉฐ, ์‹ฌ์ง€์–ด ์ด๋™ํ•˜๊ฑฐ๋‚˜ ์ง„๋™ํ•˜๋Š” ๋“ฏํ•œ ๋ณต์žกํ•œ ์ƒ๋ช… ํ˜„์ƒ์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๋‹จ์ˆœ ๊ทœ์น™์ด ์–ด๋–ป๊ฒŒ ๋ณต์žก์„ฑ์„ ์ฐฝ๋ฐœ(Emergence)์‹œํ‚ค๋Š”์ง€ ๋ณด์—ฌ์ฃผ๋Š” ํ›Œ๋ฅญํ•œ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค.

1. ์ฝ˜์›จ์ด์˜ ์ƒ๋ช… ๊ฒŒ์ž„ (Conwayโ€™s Game of Life)

์ด ์ฝ”๋“œ๋Š” ๊ฒฉ์ž(Grid)๋ฅผ ๊ตฌ์„ฑํ•˜๊ณ , draw() ํ•จ์ˆ˜๊ฐ€ ๋ฐ˜๋ณต๋  ๋•Œ๋งˆ๋‹ค ์ฃผ๋ณ€ ์…€์˜ ์ƒ์‚ฌ(็”Ÿๆฎบ) ์—ฌ๋ถ€์— ๋”ฐ๋ผ ๋‹ค์Œ ์„ธ๋Œ€๋ฅผ ๊ณ„์‚ฐํ•˜๊ณ  ํ™”๋ฉด์— ๊ทธ๋ฆฝ๋‹ˆ๋‹ค.

// ====== ์ „์—ญ ๋ณ€์ˆ˜ ์„ค์ • ======
let grid; // ํ˜„์žฌ ์„ธ๋Œ€์˜ ์ƒํƒœ (0: Dead, 1: Alive)
let cols;
let rows;
let resolution = 5; // ์…€ ํ•˜๋‚˜์˜ ํฌ๊ธฐ (ํ”ฝ์…€)

function setup() {
  // ์บ”๋ฒ„์Šค๋ฅผ 600x600์œผ๋กœ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
  createCanvas(600, 600);

  // ์…€์˜ ๊ฐœ์ˆ˜ ๊ณ„์‚ฐ
  cols = floor(width / resolution);
  rows = floor(height / resolution);

  // ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์†๋„๋ฅผ ์กฐ์ ˆํ•ฉ๋‹ˆ๋‹ค. (์ดˆ๋‹น 5 ํ”„๋ ˆ์ž„)
  // ๋„ˆ๋ฌด ๋น ๋ฅด๋ฉด ํŒจํ„ด ๋ณ€ํ™”๋ฅผ ๊ด€์ฐฐํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.
  frameRate(5);

  // ์ดˆ๊ธฐ ๊ฒฉ์ž ์ƒํƒœ๋ฅผ ์„ค์ •ํ•˜๊ณ  ๋ฌด์ž‘์œ„๋กœ ์ƒ๋ช…์ฒด๋ฅผ ๋ฐฐ์น˜ํ•ฉ๋‹ˆ๋‹ค.
  grid = initializeRandomGrid(cols, rows);

  // ๋ฐฐ๊ฒฝ์€ ์–ด๋‘์šด ์ƒ‰์œผ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
  background(20);
}

function draw() {
  // 1. ํ˜„์žฌ ์„ธ๋Œ€ ๊ทธ๋ฆฌ๊ธฐ
  background(20); // ๋งค ํ”„๋ ˆ์ž„๋งˆ๋‹ค ๋ฐฐ๊ฒฝ์„ ๋‹ค์‹œ ์น ํ•ด ์ž”์ƒ์„ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.
  drawGrid(grid);

  // 2. ๋‹ค์Œ ์„ธ๋Œ€ ๊ณ„์‚ฐ
  // CA์˜ ํ•ต์‹ฌ: ํ˜„์žฌ ์ƒํƒœ(grid)๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋‹ค์Œ ์ƒํƒœ(nextGrid)๋ฅผ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
  let nextGrid = computeNextGeneration(grid);

  // 3. ์ƒํƒœ ์—…๋ฐ์ดํŠธ
  // ๊ณ„์‚ฐ๋œ ๋‹ค์Œ ์„ธ๋Œ€๋ฅผ ํ˜„์žฌ ์„ธ๋Œ€๋กœ ๊ต์ฒดํ•ฉ๋‹ˆ๋‹ค.
  grid = nextGrid;
}

// ====== ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜ ======

/**
 * 2์ฐจ์› ๋ฐฐ์—ด์„ ์ƒ์„ฑํ•˜๊ณ  ๋ฌด์ž‘์œ„ ์ดˆ๊ธฐ๊ฐ’์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
 */
function initializeRandomGrid(cols, rows) {
  let arr = new Array(cols);
  for (let i = 0; i < cols; i++) {
    arr[i] = new Array(rows);
    for (let j = 0; j < rows; j++) {
      // 50% ํ™•๋ฅ ๋กœ 0(์ฃฝ์Œ) ๋˜๋Š” 1(์ƒ์กด)์„ ๋ถ€์—ฌํ•ฉ๋‹ˆ๋‹ค.
      arr[i][j] = floor(random(2));
    }
  }
  return arr;
}

/**
 * ํ˜„์žฌ ๊ฒฉ์ž์˜ ์ƒํƒœ๋ฅผ ์บ”๋ฒ„์Šค์— ๊ทธ๋ฆฝ๋‹ˆ๋‹ค.
 */
function drawGrid(currentGrid) {
  for (let i = 0; i < cols; i++) {
    for (let j = 0; j < rows; j++) {
      let x = i * resolution;
      let y = j * resolution;

      if (currentGrid[i][j] == 1) {
        // ์ƒ์กดํ•œ ์…€ (Alive): ํฐ์ƒ‰
        fill(200, 170, 80);
        stroke(20); // ์…€ ๊ฒฝ๊ณ„์„ ์€ ๋ฐฐ๊ฒฝ์ƒ‰๊ณผ ๋น„์Šทํ•˜๊ฒŒ ์„ค์ •
        rect(x, y, resolution, resolution);
      } else {
        // ์ฃฝ์€ ์…€ (Dead): ๊ทธ๋ฆฌ์ง€ ์•Š์Œ (๋ฐฐ๊ฒฝ์ƒ‰ ์œ ์ง€)
        // noStroke();
        // fill(20);
        // rect(x, y, resolution, resolution);
      }
    }
  }
}

/**
 * ์ฝ˜์›จ์ด์˜ ์ƒ๋ช… ๊ฒŒ์ž„ ๊ทœ์น™์„ ์ ์šฉํ•˜์—ฌ ๋‹ค์Œ ์„ธ๋Œ€๋ฅผ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
 */
function computeNextGeneration(currentGrid) {
  // ๋‹ค์Œ ์„ธ๋Œ€๋ฅผ ๋‹ด์„ ์ƒˆ๋กœ์šด ๊ฒฉ์ž๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค. (์ค‘์š”: ํ˜„์žฌ ๊ฒฉ์ž๋ฅผ ์ง์ ‘ ์ˆ˜์ •ํ•˜๋ฉด ์•ˆ ๋ฉ๋‹ˆ๋‹ค!)
  let newGrid = initializeEmptyGrid(cols, rows);

  for (let i = 0; i < cols; i++) {
    for (let j = 0; j < rows; j++) {
      // 1. ํ˜„์žฌ ์…€์˜ ์ƒํƒœ
      let state = currentGrid[i][j];

      // 2. ์ด์›ƒ ์…€ (8๋ฐฉํ–ฅ) ์ค‘ ์‚ด์•„์žˆ๋Š” ์…€์˜ ๊ฐœ์ˆ˜๋ฅผ ์…‰๋‹ˆ๋‹ค.
      let liveNeighbors = countLiveNeighbors(currentGrid, i, j);

      // 3. ์ฝ˜์›จ์ด์˜ 4๊ฐ€์ง€ ๊ทœ์น™ ์ ์šฉ
      if (state == 0 && liveNeighbors == 3) {
        // ๐ŸŒŸ ๊ทœ์น™ 4: ํƒ„์ƒ (Birth)
        // ์ฃฝ์€ ์…€์ด ์ด์›ƒ 3๊ฐœ๋ฅผ ๊ฐ€์ง€๋ฉด ์‚ด์•„๋‚ฉ๋‹ˆ๋‹ค.
        newGrid[i][j] = 1;
      } else if (state == 1) {
        if (liveNeighbors < 2 || liveNeighbors > 3) {
          // ๐Ÿ’€ ๊ทœ์น™ 1 & 3: ๊ณผ์†Œ/๊ณผ๋ฐ€์— ์˜ํ•œ ์ฃฝ์Œ (Death)
          // ์ด์›ƒ์ด 2๊ฐœ ๋ฏธ๋งŒ์ด๊ฑฐ๋‚˜ 3๊ฐœ๋ฅผ ์ดˆ๊ณผํ•˜๋ฉด ๊ณ ๋… ๋˜๋Š” ๊ณผ๋ฐ€๋กœ ์ฃฝ์Šต๋‹ˆ๋‹ค.
          newGrid[i][j] = 0;
        } else {
          // ๐Ÿ’– ๊ทœ์น™ 2: ์ƒ์กด (Survival)
          // ์ด์›ƒ์ด 2๊ฐœ ๋˜๋Š” 3๊ฐœ๋ฉด ๋‹ค์Œ ์„ธ๋Œ€์—๋„ ์ƒ์กดํ•ฉ๋‹ˆ๋‹ค.
          newGrid[i][j] = 1;
        }
      }
      // ๊ทธ ์™ธ์˜ ๊ฒฝ์šฐ (state=0, liveNeighbors!=3), ์…€์€ 0(์ฃฝ์€ ์ƒํƒœ)์„ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค.
    }
  }
  return newGrid;
}

/**
 * ์ด์›ƒ ์…€์˜ ์ƒ์กด ๊ฐœ์ˆ˜๋ฅผ ์…‰๋‹ˆ๋‹ค. (ํ† ๋Ÿฌ์Šค ๊ฒฝ๊ณ„ ์กฐ๊ฑด ์ ์šฉ)
 */
function countLiveNeighbors(grid, x, y) {
  let sum = 0;
  // ํ˜„์žฌ ์…€์˜ ์ฃผ๋ณ€ 8๊ฐœ ์…€์„ ์ˆœํšŒ (-1, 0, 1)
  for (let i = -1; i <= 1; i++) {
    for (let j = -1; j <= 1; j++) {
      // โš ๏ธ ๊ฒฝ๊ณ„ ์กฐ๊ฑด ์ฒ˜๋ฆฌ (Toroidal Boundary - ํ† ๋Ÿฌ์Šค/๋„๋„› ๋ชจ์–‘)
      // ๊ฒฉ์ž ๋์„ ๋„˜์–ด๊ฐ€๋ฉด ๋ฐ˜๋Œ€ํŽธ์œผ๋กœ ์ด์–ด์ง€๊ฒŒ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
      let col = (x + i + cols) % cols;
      let row = (y + j + rows) % rows;

      // ์ด์›ƒ ์…€์˜ ์ƒํƒœ๋ฅผ ํ•ฉ์‚ฐํ•ฉ๋‹ˆ๋‹ค.
      sum += grid[col][row];
    }
  }

  // ์ž๊ธฐ ์ž์‹ ์€ ์ด์›ƒ์— ํฌํ•จ๋˜์ง€ ์•Š์œผ๋ฏ€๋กœ, ํ•ฉ์‚ฐ์—์„œ ์ œ์™ธํ•ฉ๋‹ˆ๋‹ค.
  sum -= grid[x][y];

  return sum;
}

/**
 * ๋นˆ 2์ฐจ์› ๋ฐฐ์—ด(๋ชจ๋“  ๊ฐ’ 0)์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
 */
function initializeEmptyGrid(cols, rows) {
  let arr = new Array(cols);
  for (let i = 0; i < cols; i++) {
    arr[i] = new Array(rows).fill(0);
  }
  return arr;
}

1. ๐Ÿ–ผ๏ธ ๋‘ ๊ฐœ์˜ ๊ฒฉ์ž(Grid) ์‚ฌ์šฉ (๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ์ ์ธ ์ƒํƒœ ์ „์ด)

CA ์‹œ๋ฎฌ๋ ˆ์ด์…˜์—์„œ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๋ถ€๋ถ„์€ ๋™์‹œ ์—…๋ฐ์ดํŠธ์ž…๋‹ˆ๋‹ค.

  • grid: ํ˜„์žฌ ์„ธ๋Œ€์˜ ์ƒํƒœ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
  • nextGrid: ํ˜„์žฌ grid์˜ ์ƒํƒœ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋‹ค์Œ ์„ธ๋Œ€์— ์…€์ด ์‚ด์•„์žˆ์„์ง€ ์ฃฝ์„์ง€๋ฅผ ๊ณ„์‚ฐํ•˜์—ฌ ์ž„์‹œ๋กœ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.

๋งŒ์•ฝ ํ•˜๋‚˜์˜ ๋ฐฐ์—ด๋งŒ ์‚ฌ์šฉํ•˜๊ณ  ๊ณ„์‚ฐ๊ณผ ๋™์‹œ์— ์—…๋ฐ์ดํŠธํ•˜๋ฉด, ๋จผ์ € ์—…๋ฐ์ดํŠธ๋œ ์…€์ด ๋‚˜์ค‘์— ๊ณ„์‚ฐ๋˜๋Š” ์…€์— ์˜ํ–ฅ์„ ์ฃผ์–ด ๊ทœ์น™์ด ์˜ค์—ผ๋ฉ๋‹ˆ๋‹ค. ๋‘ ๊ฐœ์˜ ๊ฒฉ์ž๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ชจ๋“  ์…€์˜ ์—…๋ฐ์ดํŠธ๊ฐ€ ๋™์‹œ์— ์ด๋ฃจ์–ด์ง€๋Š” CA์˜ ๋ณธ์งˆ์„ ์ •ํ™•ํ•˜๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

2. ๐Ÿ”ข ์ฝ˜์›จ์ด์˜ 4๊ฐ€์ง€ ๊ทœ์น™ (computeNextGeneration ํ•จ์ˆ˜)

์ด ํ•จ์ˆ˜๋Š” ๋ชจ๋“  ์…€์„ ์ˆœํšŒํ•˜๋ฉฐ ๋‹ค์Œ ๋„ค ๊ฐ€์ง€ ๊ทœ์น™์„ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.

  • ๊ณผ์†Œ(Underpopulation)์— ์˜ํ•œ ์ฃฝ์Œ: ์‚ด์•„์žˆ๋Š” ์…€์ด ์ด์›ƒ์ด 2๊ฐœ ๋ฏธ๋งŒ์ด๋ฉด ์ฃฝ์Šต๋‹ˆ๋‹ค. (๊ณ ๋…)
  • ์ƒ์กด (Survival): ์‚ด์•„์žˆ๋Š” ์…€์ด ์ด์›ƒ์ด 2๊ฐœ ๋˜๋Š” 3๊ฐœ์ด๋ฉด ๋‹ค์Œ ์„ธ๋Œ€์—๋„ ์‚ด์•„๋‚จ์Šต๋‹ˆ๋‹ค.
  • ๊ณผ๋ฐ€(Overpopulation)์— ์˜ํ•œ ์ฃฝ์Œ: ์‚ด์•„์žˆ๋Š” ์…€์ด ์ด์›ƒ์ด 3๊ฐœ ์ดˆ๊ณผ์ด๋ฉด ์ฃฝ์Šต๋‹ˆ๋‹ค. (์งˆ์‹/๊ณผ๋ฐ€)
  • ํƒ„์ƒ (Reproduction): ์ฃฝ์–ด์žˆ๋Š” ์…€์ด ์ด์›ƒ์ด ์ •ํ™•ํžˆ 3๊ฐœ์ด๋ฉด ์‚ด์•„๋‚ฉ๋‹ˆ๋‹ค.

3. ๐Ÿ”„ ํ† ๋Ÿฌ์Šค ๊ฒฝ๊ณ„ ์กฐ๊ฑด (countLiveNeighbors ํ•จ์ˆ˜)

  • ๊ฒฝ๊ณ„ ์กฐ๊ฑด (Boundary Condition): ๊ฒฉ์ž์˜ ๊ฐ€์žฅ์ž๋ฆฌ์— ์žˆ๋Š” ์…€์˜ ์ด์›ƒ์„ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ• ์ง€๊ฐ€ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ์ด ์˜ˆ์ œ์—์„œ๋Š” ํ† ๋Ÿฌ์Šค(Toroidal) ๊ฒฝ๊ณ„๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

  • ๊ตฌํ˜„: ๋ฐฐ์—ด ์ธ๋ฑ์Šค๋ฅผ ๊ณ„์‚ฐํ•  ๋•Œ (x + i + cols) % cols ์™€ ๊ฐ™์ด ๋ชจ๋“ˆ๋กœ ์—ฐ์‚ฐ(%)์„ ์‚ฌ์šฉํ•˜์—ฌ, ์˜ค๋ฅธ์ชฝ ๋์„ ๋„˜์–ด๊ฐ€๋ฉด ์ž๋™์œผ๋กœ ์™ผ์ชฝ ๋์œผ๋กœ, ์œ„์ชฝ ๋์„ ๋„˜์–ด๊ฐ€๋ฉด ์•„๋ž˜์ชฝ ๋์œผ๋กœ ์—ฐ๊ฒฐ๋˜๋„๋ก ๋งŒ๋“ญ๋‹ˆ๋‹ค. ๋งˆ์น˜ ๊ฒฉ์ž๊ฐ€ ๋„๋„›์ฒ˜๋Ÿผ ๋‘ฅ๊ธ€๊ฒŒ ์ด์–ด์ง„ ๊ฒƒ์ฒ˜๋Ÿผ ๋™์ž‘ํ•˜์—ฌ ๊ฒฝ๊ณ„๋ฉด ์—†์ด ์ž์—ฐ์Šค๋Ÿฌ์šด ํŒจํ„ด ์ด๋™์„ ๊ด€์ฐฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


2. ๊ทœ์น™ ๊ธฐ๋ฐ˜ ํŒจํ„ด ์ƒ์„ฑ๊ธฐ

๊ฐ„๋‹จํ•œ 1์ฐจ์› ๊ทœ์น™์˜ 2์ฐจ์› ํ™•์žฅ
์ด ์˜ˆ์ œ๋Š” 1์ฐจ์› ์…€๋ฃฐ๋Ÿฌ ์˜คํ† ๋งˆํƒ€(Elementary Cellular Automata, ECA)์˜ ์•„์ด๋””์–ด๋ฅผ 2์ฐจ์›์œผ๋กœ ํ™•์žฅํ•˜์—ฌ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๊ฐ ์…€์€ ์˜ค์ง ์ž์‹ ๊ณผ ๋ฐ”๋กœ ์œ„์ชฝ ์ด์›ƒ๋“ค์˜ ์ƒํƒœ์—๋งŒ ์˜์กดํ•˜์—ฌ ๋‹ค์Œ ์ƒํƒœ๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” GOL์ฒ˜๋Ÿผ 8๋ฐฉํ–ฅ ์ด์›ƒ์„ ๋ณด๋Š” ๊ฒƒ๋ณด๋‹ค ํ›จ์”ฌ ๊ฐ„๋‹จํ•œ ๊ทœ์น™์ด์ง€๋งŒ, ๋ฐ˜๋ณต๋˜๋ฉด ๋งค์šฐ ๋ณต์žกํ•˜๊ณ  ํฅ๋ฏธ๋กœ์šด ํ”„๋ž™ํƒˆ์  ๊ตฌ์กฐ๋ฅผ ๋งŒ๋“ค์–ด๋ƒ…๋‹ˆ๋‹ค.

// ====== ์ „์—ญ ๋ณ€์ˆ˜ ์„ค์ • ======
let grid; // ์…€ ์ƒํƒœ๋ฅผ ์ €์žฅํ•  2์ฐจ์› ๋ฐฐ์—ด
let cols;
let rows;
let resolution = 8; // ์…€ ํ•˜๋‚˜์˜ ํฌ๊ธฐ (ํ”ฝ์…€)
let currentRow = 0; // ํ˜„์žฌ ํŒจํ„ด์„ ๊ณ„์‚ฐํ•˜๊ณ  ์žˆ๋Š” ํ–‰
let rule = 90; // ์‚ฌ์šฉํ•  1์ฐจ์› CA ๊ทœ์น™ ๋ฒˆํ˜ธ (Rule 90)
let ruleset; // ๊ทœ์น™ ๋ฒˆํ˜ธ์— ํ•ด๋‹นํ•˜๋Š” 8๋น„ํŠธ ๊ทœ์น™ ๋ฐฐ์—ด

function setup() {
  // ์บ”๋ฒ„์Šค ์ƒ์„ฑ ๋ฐ ๊ธฐ๋ณธ ์„ค์ •
  createCanvas(800, 800);

  // ์…€์˜ ๊ฐœ์ˆ˜ ๊ณ„์‚ฐ
  cols = floor(width / resolution);
  rows = floor(height / resolution);

  // ์ดˆ๊ธฐ ๊ฒฉ์ž๋ฅผ ๋ชจ๋‘ '์ฃฝ์€ ์ƒํƒœ(0)'๋กœ ์ดˆ๊ธฐํ™”
  grid = initializeEmptyGrid(cols, rows);

  // ์ดˆ๊ธฐ ์‹œ๋“œ (Seed) ์„ค์ •: ๊ฐ€์žฅ ์œ—์ค„ ์ค‘์•™ ์…€๋งŒ '์‚ด์•„์žˆ๋Š” ์ƒํƒœ(1)'๋กœ ์‹œ์ž‘
  grid[floor(cols / 2)][0] = 1;

  // ๊ทœ์น™ ๋ฒˆํ˜ธ๋ฅผ 8๋น„ํŠธ ๋ฐฐ์—ด๋กœ ๋ณ€ํ™˜
  ruleset = decimalToBinaryArray(rule);

  // ๋ฐฐ๊ฒฝ์€ ๊ฒ€์€์ƒ‰์œผ๋กœ ์„ค์ •
  background(0);
}

function draw() {
  // ํ˜„์žฌ ํ–‰์„ ๊ทธ๋ฆฝ๋‹ˆ๋‹ค.
  drawRow(currentRow);

  // ๋‹ค์Œ ํ–‰์„ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
  if (currentRow < rows - 1) {
    computeNextRow(currentRow);
    currentRow++;
  } else {
    // ๋ชจ๋“  ํ–‰์„ ๋‹ค ์ฑ„์šฐ๋ฉด ๋ฉˆ์ถฅ๋‹ˆ๋‹ค.
    noLoop();
    console.log("ํŒจํ„ด ์ƒ์„ฑ์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.");
  }
}

// ====== ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜ ======

/**
 * ๋นˆ 2์ฐจ์› ๋ฐฐ์—ด(๋ชจ๋“  ๊ฐ’ 0)์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
 */
function initializeEmptyGrid(cols, rows) {
  let arr = new Array(cols);
  for (let i = 0; i < cols; i++) {
    arr[i] = new Array(rows).fill(0);
  }
  return arr;
}

/**
 * ๊ทœ์น™ ๋ฒˆํ˜ธ (Decimal)๋ฅผ 8๋น„ํŠธ ๋ฐฐ์—ด (Binary)๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
 * ์˜ˆ: Rule 90 -> [0, 1, 0, 1, 1, 0, 1, 0]
 */
function decimalToBinaryArray(decimal) {
  // 10์ง„์ˆ˜๋ฅผ 8์ž๋ฆฌ 2์ง„์ˆ˜ ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•˜๊ณ  ๋ฐฐ์—ด๋กœ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
  let binaryString = decimal.toString(2).padStart(8, "0");
  // ๋ฐฐ์—ด์„ ๋’ค์ง‘๋Š” ์ด์œ : 1์ฐจ์› CA ๊ทœ์น™์€ ๋ณดํ†ต ์šฐ์ธก ๋น„ํŠธ(์ธ๋ฑ์Šค 0)๋ถ€ํ„ฐ ์ค‘์š”๋„๊ฐ€ ๋‚ฎ์Œ
  // ์ปจํ…์ŠคํŠธ: [111, 110, 101, 100, 011, 010, 001, 000] ์— ๋Œ€์‘
  return Array.from(binaryString).reverse().map(Number);
}

/**
 * ํ˜„์žฌ ํ–‰์˜ ์…€ ์ƒํƒœ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋‹ค์Œ ํ–‰์˜ ์ƒํƒœ๋ฅผ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
 * ๊ทœ์น™: ํ˜„์žฌ ์…€์˜ ์ƒํƒœ๋Š” ๋ฐ”๋กœ ์œ„ 3๊ฐœ ์ด์›ƒ (์™ผ์ชฝ ์œ„, ์œ„, ์˜ค๋ฅธ์ชฝ ์œ„)์— ์˜ํ•ด ๊ฒฐ์ •๋ฉ๋‹ˆ๋‹ค.
 */
function computeNextRow(y) {
  let nextY = y + 1;
  // ๊ฐ ์—ด์„ ์ˆœํšŒํ•˜๋ฉฐ ์ƒˆ๋กœ์šด ํ–‰์˜ ์ƒํƒœ๋ฅผ ๊ฒฐ์ •
  for (let x = 1; x < cols - 1; x++) {
    // 1. ์œ„์ชฝ 3๊ฐœ์˜ ์ด์›ƒ ์ƒํƒœ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
    // ๊ฒฝ๊ณ„ ์กฐ๊ฑด ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•ด (x=0, x=cols-1) ์—ด์€ ๋ฌด์‹œํ•ฉ๋‹ˆ๋‹ค.
    let left = grid[x - 1][y]; // ์™ผ์ชฝ ์œ„
    let center = grid[x][y]; // ๋ฐ”๋กœ ์œ„
    let right = grid[x + 1][y]; // ์˜ค๋ฅธ์ชฝ ์œ„

    // 2. 3๋น„ํŠธ ํŒจํ„ด์„ 10์ง„์ˆ˜ ์ธ๋ฑ์Šค๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
    // ์˜ˆ: [1, 0, 1] -> 1*2^2 + 0*2^1 + 1*2^0 = 5
    let index = left * 4 + center * 2 + right * 1;

    // 3. ๊ทœ์น™ ๋ฐฐ์—ด(ruleset)์—์„œ ํ•ด๋‹น ์ธ๋ฑ์Šค์˜ ๊ฐ’์„ ๊ฐ€์ ธ์™€ ๋‹ค์Œ ์ƒํƒœ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
    // ruleset[index]๋Š” 0 ๋˜๋Š” 1 (๋‹ค์Œ ์„ธ๋Œ€์˜ ์ƒํƒœ)
    grid[x][nextY] = ruleset[index];
  }
}

/**
 * ํ˜„์žฌ ํ–‰์˜ ์…€ ์ƒํƒœ๋ฅผ ์บ”๋ฒ„์Šค์— ๊ทธ๋ฆฝ๋‹ˆ๋‹ค.
 */
function drawRow(y) {
  for (let x = 0; x < cols; x++) {
    let cellState = grid[x][y];
    let drawX = x * resolution;
    let drawY = y * resolution;

    if (cellState == 1) {
      // ์ƒ์กดํ•œ ์…€ (Alive): ํฐ์ƒ‰
      fill(70, 200, 180);
      noStroke();
      rect(drawX, drawY, resolution, resolution);
    }
    // ์ฃฝ์€ ์…€ (0): ๊ทธ๋ฆฌ์ง€ ์•Š์Œ (๋ฐฐ๊ฒฝ์ƒ‰ ์œ ์ง€)
  }
}

๐Ÿ’ก ์ฝ”๋“œ ์ƒ์„ธ ์„ค๋ช…: 1์ฐจ์› CA ๊ทœ์น™์˜ 2D ์ ์šฉ

1. ๐Ÿ”ข ๊ทœ์น™ ์„ธํŠธ(ruleset)์™€ ๋ฒˆํ˜ธ(rule)

  • ๊ทœ์น™ ๋ฒˆํ˜ธ: rule = 90์€ ์ด ์‹œ๋ฎฌ๋ ˆ์ด์…˜์˜ ์ž‘๋™ ๋ฐฉ์‹์„ ๊ฒฐ์ •ํ•˜๋Š” ํ•ต์‹ฌ์ž…๋‹ˆ๋‹ค. 1์ฐจ์› CA์—์„œ๋Š” ์ฃผ๋ณ€ 3๊ฐœ ์…€(์™ผ์ชฝ, ์ค‘์•™, ์˜ค๋ฅธ์ชฝ)์˜ 8๊ฐ€์ง€ ๊ฐ€๋Šฅํ•œ ์กฐํ•ฉ(23=82^3=8) ๊ฐ๊ฐ์— ๋Œ€ํ•ด ๋‹ค์Œ ์ƒํƒœ(0 ๋˜๋Š” 1)๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.
  • decimalToBinaryArray(rule): 90๊ณผ ๊ฐ™์€ 10์ง„์ˆ˜ ๊ทœ์น™ ๋ฒˆํ˜ธ๋ฅผ 8๋น„ํŠธ์˜ 2์ง„์ˆ˜ ๋ฐฐ์—ด๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ฐฐ์—ด(ruleset)์ด ๋ฐ”๋กœ 8๊ฐ€์ง€ ์ƒํ™ฉ์— ๋Œ€ํ•œ 8๊ฐ€์ง€ ๊ฒฐ๊ณผ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
    • ์˜ˆ: Rule 90์€ 2์ง„์ˆ˜๋กœ 01011010์ด๋ฉฐ, ์ด๋ฅผ ๋ฐฐ์—ด๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ํŒจํ„ด ๊ฒฐ์ •์— ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

2. โ†•๏ธ ์‹œ๊ฐ„์„ฑ (Temporal Systems)์˜ ๊ตฌํ˜„

  • draw() ๋ฃจํ”„: GOL์ฒ˜๋Ÿผ ์ „์ฒด ๊ฒฉ์ž๋ฅผ ๋™์‹œ์— ๊ณ„์‚ฐํ•˜๋Š” ๋Œ€์‹ , ์ด ์˜ˆ์ œ๋Š” draw() ํ•จ์ˆ˜๊ฐ€ ๋ฐ˜๋ณต๋  ๋•Œ๋งˆ๋‹ค ํ•˜๋‚˜์˜ ํ–‰(row)์”ฉ ํŒจํ„ด์„ ์•„๋ž˜๋กœ ์ƒ์„ฑํ•ด ๋‚˜๊ฐ‘๋‹ˆ๋‹ค.

  • currentRow: ์‹œ๊ฐ„์ด ์ง€๋‚จ์— ๋”ฐ๋ผ currentRow๊ฐ€ 0๋ถ€ํ„ฐ rows-1๊นŒ์ง€ ์ฆ๊ฐ€ํ•˜๋ฉฐ, ์ด๋Š” ํŒจํ„ด์ด ์‹œ๊ฐ„์˜ ํ๋ฆ„์— ๋”ฐ๋ผ ์œ„์—์„œ ์•„๋ž˜๋กœ ์Œ“์ด๋Š” ๊ฒƒ์„ ์‹œ๊ฐํ™”ํ•ฉ๋‹ˆ๋‹ค.

3. ๐Ÿง  ๋‹ค์Œ ์ƒํƒœ ๊ฒฐ์ • (computeNextRow ํ•จ์ˆ˜)

์ด ํ•จ์ˆ˜๋Š” GOL์˜ computeNextGeneration๋ณด๋‹ค ํ›จ์”ฌ ๋‹จ์ˆœํ•œ ๊ทœ์น™์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

  • ์ด์›ƒ ์ •์˜: ์˜ค์ง ๋ฐ”๋กœ ์œ— ํ–‰์˜ 3๊ฐœ ์…€(์™ผ์ชฝ, ์ค‘์•™, ์˜ค๋ฅธ์ชฝ)๋งŒ์„ ์ด์›ƒ์œผ๋กœ ๊ฐ„์ฃผํ•ฉ๋‹ˆ๋‹ค.

  • ์ธ๋ฑ์Šค ๋ณ€ํ™˜: left 4 + center 2 + right * 1 ๊ณต์‹์€ 3๊ฐœ์˜ 1(Alive) ๋˜๋Š” 0(Dead) ์ƒํƒœ๋ฅผ 0๋ถ€ํ„ฐ 7๊นŒ์ง€์˜ ๊ณ ์œ ํ•œ 10์ง„์ˆ˜ ์ธ๋ฑ์Šค๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

    • ์ด ์ธ๋ฑ์Šค๋Š” ํ˜„์žฌ 3๊ฐœ ์ด์›ƒ์˜ ํŒจํ„ด์ด ruleset ๋ฐฐ์—ด์˜ ๋ช‡ ๋ฒˆ์งธ ๊ทœ์น™๊ณผ ์ผ์น˜ํ•˜๋Š”์ง€๋ฅผ ์•Œ๋ ค์ค๋‹ˆ๋‹ค.
  • ์ƒํƒœ ๊ฒฐ์ •: newGrid[x][nextY] = ruleset[index]๋ฅผ ํ†ตํ•ด, ํ•ด๋‹น ํŒจํ„ด์— ๋ฏธ๋ฆฌ ์ •์˜๋œ ๊ทœ์น™(0 ๋˜๋Š” 1)์— ๋”ฐ๋ผ ๋‹ค์Œ ์…€์˜ ์ƒํƒœ๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.

๐ŸŽจ ์ด ์˜ˆ์‹œ์˜ ํฅ๋ฏธ๋กœ์šด ์ 

  • ํ”„๋ž™ํƒˆ ํŒจํ„ด: Rule 90์€ ์‹คํ–‰ํ•˜๋ฉด ์‹œ์—๋ฅดํ•€์Šคํ‚ค ๊ฐœ์Šคํ‚ท๊ณผ ์œ ์‚ฌํ•œ ๋Œ€์นญ์ ์ด๊ณ  ํ”„๋ž™ํƒˆ์ ์ธ ๊ตฌ์กฐ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

  • ํŒจํ„ด ๋‹ค์–‘์„ฑ: let rule = 90 ๊ฐ’์„ ๋‹ค๋ฅธ ๊ทœ์น™ ๋ฒˆํ˜ธ(์˜ˆ: Rule 30, Rule 110, Rule 184)๋กœ ๋ฐ”๊พธ๋ฉด ์™„์ „ํžˆ ๋‹ค๋ฅธ ์‹œ๊ฐ์  ํŒจํ„ด์„ ๊ด€์ฐฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ Rule 110์€ ํŠœ๋ง ์™„์ „์„ฑ(Turing Completeness)์„ ๊ฐ€์ง„ ๊ฒƒ์œผ๋กœ ์•Œ๋ ค์ ธ ์žˆ์–ด ๋ณต์žก์„ฑ์ด ๋งค์šฐ ๋†’์Šต๋‹ˆ๋‹ค.

  • ๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ: GOL๊ณผ ๋‹ฌ๋ฆฌ ์ด์›ƒ ๊ณ„์‚ฐ์ด 8๊ฐœ๊ฐ€ ์•„๋‹Œ 3๊ฐœ๋กœ ๋‹จ์ˆœ

profile
Coding Art with Blender / oF / Processing / p5.js / nannou

0๊ฐœ์˜ ๋Œ“๊ธ€