パルサー

ペンタデカスロン

銀河

八角形

8の字

タンブラー

床屋の看板

時計

グライダー

軽量級宇宙船

中量級宇宙船

重量級宇宙船

シャトル

パファーフィッシュ

グライダー銃

マックス

サンダーバード

ハーシェル

Bヘプトミノ

ダイハード

Loading editor...

"use strict";

let generationFigure = 0;
let isDragging = false;
let dragMode = 0; // 1: 生きたセルにする, 0: 死んだセルにする
let isPlacingTemplate = false;
let patternShape = [];
let patternHeight = 0;
let patternWidth = 0;
let previewCells = [];

//盤面の大きさ
let boardSize = 20;
const cellSize = 450 / boardSize;

//セルの色
const aliveCellColor = "black";
const deadCellColor = "white";

//セルの誕生/生存条件
const birthCounts = [3];
const survivalCounts = [2, 3];

// around: 周囲の生きたセル数 self: 自身が生きているかどうか
function isNextAlive(around, self) {
  // 自身が死んでいる & 周囲が birthCounts で誕生
  if (!self && birthCounts.includes(around)) {
    return 1;
  }
  // 自身が生きている & 周囲が survivalCounts で生存
  if (self && survivalCounts.includes(around)) {
    return self;
  }
  return 0;
}

// cellの状態に応じた色を返す関数
function getStyle(cell) {
  if (cell === 0) return deadCellColor;
  // cellの値に応じて色を返す場合はここに追加
  return aliveCellColor; // デフォルトは黒
}

//Boardの初期化
let board = Array.from({ length: boardSize }, () => Array.from({ length: boardSize }, () => 0));
const table = document.getElementById("game-board");

//盤面をBoardに従って変更する関数達(Boardを変更したら実行する)
function renderBoard() {
  // bodyを中央配置に設定
  document.body.style.display = "flex";
  document.body.style.justifyContent = "center";
  document.body.style.alignItems = "center";
  document.body.style.minHeight = "100vh";
  document.body.style.margin = "0";
  document.body.style.padding = "0";

  // 初回の盤面生成
  table.innerHTML = "";
  for (let i = 0; i < boardSize; i++) {
    const tr = document.createElement("tr");
    tr.style.padding = "0";
    for (let j = 0; j < boardSize; j++) {
      const td = document.createElement("td");
      td.style.padding = "0";
      const button = document.createElement("button");
      button.style.backgroundColor = board[i][j] ? aliveCellColor : deadCellColor; //Boardの対応する値によって色を変更
      // ボードが大きいときは border をつけない
      if (boardSize >= 50) {
        button.style.border = "none";
        table.style.border = "1px solid black";
      } else {
        button.style.border = "0.5px solid black";
      }
      button.style.width = `${cellSize}px`;
      button.style.height = `${cellSize}px`;
      button.style.padding = "0";
      button.style.display = "block";
      button.onclick = () => {
        if (isPlacingTemplate) {
          clearPreview();
          isPlacingTemplate = false;
          if (i + patternHeight < boardSize + 1 && j + patternWidth < boardSize + 1) {
            for (let r = 0; r < patternHeight; r++) {
              for (let c = 0; c < patternWidth; c++) {
                const boardRow = i + r;
                const boardCol = j + c;
                board[boardRow][boardCol] = patternShape[r][c];
              }
            }
            rerender();
            stop();
          } else {
            window.parent.postMessage(
              {
                type: "Size shortage",
                data: {},
              },
              "*",
            );
          }
        }
      };
      button.onmousedown = (e) => {
        e.preventDefault();
        if (!isPlacingTemplate) {
          isDragging = true;
          board[i][j] = board[i][j] === 1 ? 0 : 1;
          dragMode = board[i][j];
          button.style.backgroundColor = board[i][j] ? aliveCellColor : deadCellColor;
        }
      };
      button.onmouseenter = () => {
        if (isDragging && board[i][j] !== dragMode && !isPlacingTemplate) {
          board[i][j] = dragMode;
          button.style.backgroundColor = board[i][j] ? aliveCellColor : deadCellColor;
        }
        if (isPlacingTemplate) {
          drawPreview(i, j);
        }
      };
      td.appendChild(button);
      tr.appendChild(td);
    }
    table.appendChild(tr);
  }
}

table.onmouseleave = () => {
  if (isPlacingTemplate) {
    clearPreview();
  }
};

function drawPreview(row, col) {
  clearPreview();
  for (let r = 0; r < patternHeight; r++) {
    for (let c = 0; c < patternWidth; c++) {
      if (patternShape[r][c] === 1) {
        const boardRow = row + r;
        const boardCol = col + c;
        if (boardRow < boardSize && boardCol < boardSize) {
          const cell = table.rows[boardRow].cells[boardCol].firstChild;
          cell.style.backgroundColor = "gray";
          previewCells.push({ row: boardRow, col: boardCol });
        }
      }
    }
  }
}

function clearPreview() {
  previewCells.forEach((cellPos) => {
    const cell = table.rows[cellPos.row].cells[cellPos.col].firstChild;
    cell.style.backgroundColor = board[cellPos.row][cellPos.col] ? aliveCellColor : deadCellColor;
  });
  previewCells = [];
}

function rerender() {
  // 2回目以降の盤面生成
  for (let i = 0; i < boardSize; i++) {
    for (let j = 0; j < boardSize; j++) {
      const button = table.children[i].children[j].children[0];

      // 色の更新
      const currentCellColor = button.style.backgroundColor;
      const expectedCellColor = getStyle(board[i][j]);
      if (currentCellColor !== expectedCellColor) {
        button.style.backgroundColor = expectedCellColor;
      }

      // セルサイズの更新
      const currentCellsize = button.style.width;
      const expectedCellsize = `${cellSize}px`;
      if (currentCellsize !== expectedCellsize) {
        button.style.width = expectedCellsize;
        button.style.height = expectedCellsize;
      }
    }
  }
}

document.addEventListener("mouseup", () => {
  isDragging = false;
});

renderBoard();

function generationChange(num) {
  //現在の世代を表すgenerationFigureを変更し、文章も変更
  generationFigure = num;
  window.parent.postMessage(
    {
      type: "generation_change",
      data: generationFigure,
    },
    "*",
  );
}

function progressBoard() {
  const newBoard = structuredClone(board);
  for (let i = 0; i < boardSize; i++) {
    for (let j = 0; j < boardSize; j++) {
      //周囲のマスに生きたセルが何個あるかを計算(aroundに格納)↓
      let around = 0;
      let tate, yoko;
      if (i === 0) {
        tate = [0, 1];
      } else if (i === boardSize - 1) {
        tate = [0, -1];
      } else {
        tate = [-1, 0, 1];
      }
      if (j === 0) {
        yoko = [0, 1];
      } else if (j === boardSize - 1) {
        yoko = [0, -1];
      } else {
        yoko = [-1, 0, 1];
      }
      for (let ii = 0; ii < tate.length; ii++) {
        for (let jj = 0; jj < yoko.length; jj++) {
          if (tate[ii] !== 0 || yoko[jj] !== 0) {
            around += board[i + tate[ii]][j + yoko[jj]] !== 0 ? 1 : 0;
          }
        }
      }
      //↑周囲のマスに生きたセルが何個あるかを計算(aroundに格納)
      newBoard[i][j] = isNextAlive(around, board[i][j]);
    }
  }
  board = newBoard;
  generationChange(generationFigure + 1);
  rerender();
}

//イベント

on.progress = () => {
  progressBoard();
};

on.board_reset = () => {
  //すべて死んだセルにBoardを変更
  board = Array.from({ length: boardSize }, () => Array.from({ length: boardSize }, () => 0));
  renderBoard();
  generationChange(0);
};

on.board_randomize = () => {
  //生きたセル死んだセルランダムにBoardを変更
  board = Array.from({ length: boardSize }, () =>
    Array.from({ length: boardSize }, () => (Math.random() > 0.5 ? 1 : 0)),
  );
  renderBoard();
  generationChange(0);
};

on.get_boardsize = () => {
  window.parent.postMessage({ type: "get_boardsize", data: boardSize }, "*");
};

on.place_template = (template) => {
  patternShape = template;
  patternHeight = patternShape.length;
  patternWidth = patternShape[0].length;
  isPlacingTemplate = true;
  table.style.cursor = "crosshair";
};

on.save_board = async () => {
  window.parent.postMessage({ type: "save_board", data: board }, "*");
};

on.apply_board = (newBoard) => {
  board = newBoard;
  renderBoard();
  generationChange(0);
};
世代数:0
現在の速度: x1
盤面:
コード: