5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

githubのcalendarでライフゲーム

Posted at

先日、宅配便が届くまで暇だったので、github calendarで動くライフゲームをchrome extensionsで動作するように書きました。

github calendar

image.png

githubのユーザーページにある 「** contributions in the last year」 と表記されているカレンダーです。
このスカスカなcalendarでも数十世代分は動いてくれます。

cells.gif

ライフゲーム

基本的には、(conwayの)ライフゲーム(wikipedia)のルールを踏襲しています。
一点違う点としては、セルにライフが付いている点です。
githubのcalendarのセル(カレンダーの一日分)にはcontributionの頻度によって、以下の5つの状態があります。

  • #ebedf0
  • #c6e48b
  • #7bc96f
  • #239a3b
  • #196127

これらの状態をそれぞれとして

  • #ebedf0 → ライフ0(死)
  • #c6e48b → ライフ1(生)
  • #7bc96f → ライフ2(生、死ステータスへの変更後はライフ1状態へ移行)
  • #239a3b → ライフ3(        〃     ライフ2状態へ移行)
  • #196127 → ライフ4(        〃     ライフ3状態へ移行)

セルの死亡判定時には、ライフが減っていく処理にしました。

動かし方

uitspitss/github-calendar-lifegame

上記のリポジトリをクローンします。
そして、 Google Chromeで chrome://extensions/ のページに行き、 Load unpacked(パッケージ化されていない拡張機能を読み込む) をクリックして、クローンしてきたディレクトリ(フォルダ)を選択。
あとは、github上の任意のユーザーページに飛べば動くと思います。

コード

window.addEventListener("DOMContentLoaded", function(){
  if(!document.querySelector('.js-calendar-graph-svg')) return;
  const nickname = document.querySelector('.p-nickname').innerHTML;

  const LIFE = ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127'];
  let board = [];

  const push_banpei = () => {
    let l = [];
    for(let i=0; i < 9; i++){
      l.push({
        life: 0,
      });
    }
    board.push(l);
  };

  // initialize board
  push_banpei();
  document.querySelectorAll('g > g').forEach(g => {
    let week_rects = [];
    week_rects.push({life: 0});
    g.querySelectorAll('rect').forEach(rect => {
      week_rects.push({
        life: LIFE.findIndex(life => life === rect.getAttribute('fill')),
        date: rect.dataset.date
      });
    });
    week_rects.push({life:0});
    board.push(week_rects);
  });
  for(let i=0; i < 6 - new Date().getDay(); i++){
    board[board.length-1].push({
      life: 0
    });
  }
  push_banpei();

  // console.log(board);

  const loop = () => {
    let past_board = JSON.parse(JSON.stringify(board));
    let alive_num = 0;

    for(let x=1; x < 54; x++){
      for(let y=1; y < 8; y++){
        const past_life = past_board[x][y].life;
        const date = past_board[x][y].date;

        let cnt = 0;
        if(past_board[x-1][y].life >= 1) cnt++;
        if(past_board[x+1][y].life >= 1) cnt++;
        if(past_board[x][y-1].life >= 1) cnt++;
        if(past_board[x][y+1].life >= 1) cnt++;
        if(past_board[x-1][y-1].life >= 1) cnt++;
        if(past_board[x+1][y-1].life >= 1) cnt++;
        if(past_board[x-1][y+1].life >= 1) cnt++;
        if(past_board[x+1][y+1].life >= 1) cnt++;

        if(past_life >= 1){
          alive_num++;
          if(cnt <= 1){
            // dies by under population
            board[x][y].life -= 1;
          }else if(cnt === 2 || cnt === 3){
            // lives
          }else{
            // dies by overpopulation
            board[x][y].life -= 1;
          }
        }else{
          if(cnt === 3){
            // reproduce
            board[x][y].life = 1;
          }
        }

        // console.log(past_board[x][y].life, board[x][y].life);
        if(board[x][y].date && past_board[x][y].life !== board[x][y].life){
          document.querySelector(`rect[data-date="${date}"]`).setAttribute('fill', LIFE[board[x][y].life]);
        }
      }
    }
    let is_changed = true;
    if(JSON.stringify(board) === JSON.stringify(past_board)){
      is_changed = false;
    }
    return [is_changed, alive_num];
  }

  let gen = 0;
  const timer = setInterval(() => {
    _ret = loop();
    const is_changed = _ret[0];
    const alive_num = _ret[1];
    if(!is_changed){
      clearInterval(timer);
      if(alive_num === 0){
        alert(`congturations! ${nickname}'s calendar is alive to ${gen} generations.`);
      }else{
        alert(`congturations! ${nickname}'s calendar is alive to ${gen} generations, and ${alive_num} cells are alive forever.`);
      }
    }
    gen++;
  }, 1000);
});

カレンダー内にボックス等の固定物体パターンが生成されて、全体に変化がなくなった場合は、

  • congratulations! ${nickname}'s calendar is alive to ${gen} generations.
  • congratulations! ${nickname}'s calendar is alive to ${gen} generations, and ${alive_num} cells are alive forever.

というalertが出ます。
そして、以下のパルサーのような周期的なパターンが生まれた場合は、残念ながらずっと動き続けます。

image.png

感想

ライフゲーム自体は以前から知ってはいましたが、作成したのは初めてでした。
漠然と全セル死滅して終わり、というイメージがあったのですが、
意外と生存期間が長く、永遠に続くパターンもあったりして楽しかったです。

生存期間としては、このライフゲームのリポジトリを作る前が20世代ほどで全滅。
このリポジトリを作ってプッシュしたら、パルサーパターンが出てきてforever。
他、数人を試した感じではほどほどにcontributeしてる人で70世代くらいでforeverという感じでした。

前述のコード内で、 // lives の箇所は生きている限りはセルは死に向かうだろうということでライフが2以上ある場合は1削るなどの処理を入れたりして遊んでました。いろいろとバリエーションができそうで楽しいですね。

参考ページ等

遊び終わったらgithub-calendar-lifegameをRemoveして片付けましょう。

おおまかなデバッグはしましたが、取り切れていないバグが多分にあると思います…。

5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?