All Articles

【100sites #011】LifeGame,生命遊戲模擬無限的可能性

LifeGame,生命遊戲模擬無限的可能性

點我玩生命遊戲線上demo

點我查看Github原始碼

  • ENTER:暫停
  • 滑鼠左鍵:放置或刪除細胞

螢幕快照 2016-03-27 上午12.35.39.png

什麼是生命遊戲?

生命遊戲,是由英國數學家John Horton Conway研發的一套細胞自動機模型,地圖中每個細胞會根據周圍8格細胞的狀態來決定自身的存亡,詳細規則如下:(摘錄自維基百科

  1. 當前細胞為存活狀態時,當周圍低於 2 個(不包含 2 個)存活細胞時,該細胞變成死亡狀態。(模擬生命數量稀少)
  2. 當前細胞為存活狀態時,當周圍有 2 個或 3 個存活細胞時,該細胞保持原樣。
  3. 當前細胞為存活狀態時,當周圍有 3 個以上(不包含 3 個)的存活細胞時,該細胞變成死亡狀態。(模擬生命數量過多)
  4. 當前細胞為死亡狀態時,當周圍有 3 個存活細胞時,該細胞變成存活狀態。 (模擬繁殖)

僅僅這4條規則,就可以建構出一個複雜而美妙的生物遊戲世界。我們可以找到一些特殊形狀的生物單位,他們會表現出一些有趣的行為模式。

三種經典的特殊生物單位: 螢幕快照 2016-03-28 上午11.36.59.png

  • 左邊的Blinker(信號燈)會以特定週期變化
  • 中間的Spaceship(太空船)會往右下角移動(你也可以設計出往其他方向移動的船)
  • 右邊的Beehive(蜂窩)則會保持靜止

今天的LifeGame

而在今天的LifeGame裡頭,我已經為你在左上角放置一個Blinker了,剩下的空間,就交給你來創造無限的可能性了!

今天的程式碼:

<!DOCTYPE html>
<html>
<head>
  <meta charset-"UTF-8">
  <title>LifeGame</title>
  <link rel="stylesheet" type="text/css" href="style.css">
  <script src="http://cdnjs.cloudflare.com/ajax/libs/p5.js/0.4.23/p5.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.4.23/addons/p5.dom.min.js"></script>
  <script src="lifegame.js"></script>
</head>

<body>
</body>
</html>
* {
  margin: 0;
  padding: 0;
}

body {
  overflow: hidden;
}
var SIZE = 20;
var oldMap = [],
    newMap = [],
    num_x = 0,
    num_y = 0,
    paused = false,
    frameCount = 0;
var pauseButton;



function setup() {
  createCanvas(windowWidth, windowHeight);
  frameRate(30);
  
  // init map
  num_x = windowWidth / SIZE;
  num_y = windowHeight / SIZE;
  for (var i = 0; i < num_x; ++i) {
    oldMap.push([]);
    newMap.push([]);
    for (var j = 0; j < num_y; ++j) {
      oldMap[i].push(false);
      newMap[i].push(false);
    }
  }
  loadDefaultMap();

  // init pause button
  pauseButton = createButton('Pause');
  pauseButton.size(80, 30);
  pauseButton.position(windowWidth/2-40, windowHeight-40);
  pauseButton.mousePressed(togglePauseSimulate);
}


function draw() {
  // fresh map every 5 frames
  if (!paused) {
    if (frameCount%5 == 0) {
      freshMap();
    }
  }
  // increase frame count
  ++frameCount;
  if (frameCount >= 30) {
    frameCount = 0;
  }

  drawMap();
}


// default map: a blink unit at top left corner
function loadDefaultMap() {
  oldMap[1][1] = true;
  oldMap[1][2] = true;
  oldMap[1][3] = true;
}


function drawMap() {
  background(0);
  // draw grid
  stroke(30);
  for (var i = 0; i < num_x; ++i) {
    line(i*SIZE, 0, i*SIZE, windowHeight);
  }
  for (var i = 0; i < num_y; ++i) {
    line(0, i*SIZE, windowWidth, i*SIZE);
  }
  fill(255);
  // draw cells
  for (var i = 0; i < num_x; ++i) {
    for (var j = 0; j < num_y; ++j) {
      if (oldMap[i][j]) {
        rect(i*SIZE, j*SIZE, SIZE, SIZE);
      }
    }
  }
  // draw mouse cell
  fill(color('rgba(100,100,100,0.5)'));
  var mouseCellX = int(mouseX / SIZE);
  var mouseCellY = int(mouseY / SIZE);
  rect(mouseCellX*SIZE, mouseCellY*SIZE, SIZE, SIZE);
}


// press ENTER to pause simulate
function keyPressed() {
  if (keyCode == ENTER) {
    togglePauseSimulate();
  }
}


// press mouse to add or remove cell
function mousePressed() {
  var mouseCellX = int(mouseX / SIZE);
  var mouseCellY = int(mouseY / SIZE);
  oldMap[mouseCellX][mouseCellY] = !oldMap[mouseCellX][mouseCellY]
}


function togglePauseSimulate() {
  if (paused) {
    paused = false;
  } else {
    paused = true;
  }
}



// count how many neighbors there are of a cell
function neighbors(xpos, ypos) {
  var total = 0;
  // four corners
  if (xpos != 0 && ypos != 0) {
    if (oldMap[xpos-1][ypos-1]) {
      ++total;
    }
  }
  if (xpos != 0 && ypos != num_y-1) {
    if (oldMap[xpos-1][ypos+1]) {
      ++total;
    }
  }
  if (xpos != num_x-1 && ypos != 0) {
    if (oldMap[xpos+1][ypos-1]) {
      ++total;
    }
  }
  if (xpos != num_x-1 && ypos != num_y-1) {
    if (oldMap[xpos+1][ypos+1]) {
      ++total;
    }
  }
  // left and right
  if (xpos != 0) {
    if (oldMap[xpos-1][ypos]) {
      ++total;
    }
  }
  if (xpos != num_x-1) {
    if (oldMap[xpos+1][ypos]) {
      ++total;
    }
  }
  if (ypos != 0) {
    if (oldMap[xpos][ypos-1]) {
      ++total;
    }
  }
  if (ypos != num_y-1) {
    if (oldMap[xpos][ypos+1]) {
      ++total;
    }
  }
  return total;
}


// calculate the next map according to the Life Game rule
function freshMap() {
  for (var i = 0; i < num_x; ++i) {
    for (var j = 0; j < num_y; ++j) {
      var neighbor_count = neighbors(i, j);
      if (neighbor_count <= 1 || neighbor_count >= 4) {
        newMap[i][j] = false;
      } else if (neighbor_count === 2) {
        newMap[i][j] = oldMap[i][j];
      } else { // neighbor_count === 3
        newMap[i][j] = true;
      }
    }
  }

  for (var i = 0; i < num_x; ++i) {
    for (var j = 0; j < num_y; ++j) {
      oldMap[i][j] = newMap[i][j];
    }
  }
}