ChatGPTが作ったJavaScriptで動くライフゲームです。
ライフゲームは配置されたセルが時間経過により相互に作用することで、複雑な変化や生命の誕生、進化、淘汰などのプロセスを連想させるパターンなどが見られることもあり、研究対象やパズル、シミュレーションゲームとしての側面を持つことが特徴の数理モデルです。
要は「適当に置いたセルがわちゃわちゃ動く様子を楽しむプログラム」です。
遊び方
画面上にセル(黒いピクセル)が配置されている状態で [再生] ボタンをクリックすると、時間が進み、セルが周囲の状態に応じて増殖、減少します。
[パターン] ボックスから「cell」以外の項目を選択した状態でキャンバスをクリックすると、特殊なセルのパターンがキャンバスに配置されます。(初期状態ではランダムにセルが配置されているので、一度 [白紙] ボタンを押してから試すとわかりやすいです)
[白紙]、[ランダム配置] ボタンをクリックすることでキャンバスをリセットすることができ、[速度] スライダーで時間の進行スピードを調整することも可能です。
ソースコード
以下はChatGPTが作成したソースコードです。内容をそのままhtml形式のファイルに保存すればブラウザ上で動作します。
本ページのライフゲームはWordPressでの画面表示の関係上一部ボタンなどの表示調整を行っていますが、コアの動作となるJavaScriptに関しては100%、ChatGPTの生成したソースコードをそのまま利用しています。
Conway's Game of Life JavaScript - ライフゲーム.js
Conway's Game of Life JavaScript - ライフゲーム.js. GitHub Gist: instantly share code, notes, and snippets.
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ライフゲーム</title>
<style>
canvas {
border: 1px solid black;
display: block;
margin: 20px auto;
cursor: crosshair;
}
#controls {
text-align: center;
margin: 10px auto;
}
</style>
</head>
<body>
<canvas id="gameCanvas" width="800" height="800"></canvas>
<div id="controls">
<button id="startStopButton">再生</button>
<button id="clearButton">白紙</button>
<button id="randomButton">ランダム配置</button>
<label for="speedRange">速度:</label>
<input type="range" id="speedRange" min="10" max="1000" value="100">
<label for="patternSelect">パターン:</label>
<select id="patternSelect">
<option value="cell">cell</option>
<option value="random">random</option>
<option value="gliderGun">Glider Gun</option>
<option value="pufferTrain">Puffer Train</option>
<option value="glider">Glider</option>
<option value="lightweightSpaceship">Lightweight Spaceship</option>
</select>
</div>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const gridSize = 100;
const cellSize = canvas.width / gridSize;
let grid = createGrid(gridSize);
let isRunning = false;
let intervalId;
let speed = 100;
let isMouseDown = false;
// 初期のランダムな状態を生成
initializeRandomGrid();
// コントロール要素の取得
const startStopButton = document.getElementById('startStopButton');
const clearButton = document.getElementById('clearButton');
const randomButton = document.getElementById('randomButton');
const speedRange = document.getElementById('speedRange');
const patternSelect = document.getElementById('patternSelect');
// 再生・停止ボタンのイベントリスナー
startStopButton.addEventListener('click', () => {
if (isRunning) {
stopGame();
} else {
startGame();
}
});
// 白紙ボタンのイベントリスナー
clearButton.addEventListener('click', () => {
stopGame();
grid = createGrid(gridSize);
drawGrid(grid);
});
// ランダム配置ボタンのイベントリスナー
randomButton.addEventListener('click', () => {
stopGame();
initializeRandomGrid();
drawGrid(grid);
});
// 速度スライダーのイベントリスナー
speedRange.addEventListener('input', (event) => {
speed = event.target.value;
if (isRunning) {
stopGame();
startGame();
}
});
// ゲームを開始する関数
function startGame() {
isRunning = true;
startStopButton.textContent = '停止';
intervalId = setInterval(() => {
drawGrid(grid);
grid = nextGeneration(grid);
}, speed);
}
// ゲームを停止する関数
function stopGame() {
isRunning = false;
startStopButton.textContent = '再生';
clearInterval(intervalId);
}
// グリッドを描画する関数
function drawGrid(grid) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < gridSize; i++) {
for (let j = 0; j < gridSize; j++) {
if (grid[i][j] === 1) {
ctx.fillStyle = 'black';
ctx.fillRect(i * cellSize, j * cellSize, cellSize, cellSize);
}
}
}
}
// 次の世代を計算する関数
function nextGeneration(grid) {
let newGrid = createGrid(gridSize);
for (let i = 0; i < gridSize; i++) {
for (let j = 0; j < gridSize; j++) {
const aliveNeighbors = countAliveNeighbors(grid, i, j);
if (grid[i][j] === 1) {
newGrid[i][j] = (aliveNeighbors === 2 || aliveNeighbors === 3) ? 1 : 0;
} else {
newGrid[i][j] = (aliveNeighbors === 3) ? 1 : 0;
}
}
}
return newGrid;
}
// 隣接する生きているセルの数を数える関数
function countAliveNeighbors(grid, x, y) {
let count = 0;
for (let i = -1; i <= 1; i++) {
for (let j = -1; j <= 1; j++) {
if (i === 0 && j === 0) continue; // 自分自身は除外
const nx = (x + i + gridSize) % gridSize; // トーラス状の端の扱い
const ny = (y + j + gridSize) % gridSize; // トーラス状の端の扱い
count += grid[nx][ny];
}
}
return count;
}
// グリッドを初期化する関数
function createGrid(size) {
let grid = new Array(size);
for (let i = 0; i < size; i++) {
grid[i] = new Array(size).fill(0);
}
return grid;
}
// グリッドをランダムに初期化する関数
function initializeRandomGrid() {
grid = createGrid(gridSize);
for (let i = 0; i < gridSize; i++) {
for (let j = 0; j < gridSize; j++) {
grid[i][j] = Math.random() > 0.8 ? 1 : 0;
}
}
}
// 特定のパターンをグリッドに配置する関数
function placePattern(pattern, offsetX, offsetY) {
pattern.forEach(([x, y]) => {
const gridX = (x + offsetX) % gridSize;
const gridY = (y + offsetY) % gridSize;
grid[gridX][gridY] = 1;
});
}
// キャンバスをクリックしてセルを変更するイベントリスナー
canvas.addEventListener('mousedown', () => {
isMouseDown = true;
});
canvas.addEventListener('mouseup', () => {
isMouseDown = false;
});
canvas.addEventListener('mousemove', (event) => {
if (isMouseDown) {
handlePatternPlacement(event);
}
});
canvas.addEventListener('click', (event) => {
handlePatternPlacement(event);
});
// セルの状態をトグルする関数
function handlePatternPlacement(event) {
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
const x = Math.floor((event.clientX - rect.left) * scaleX / cellSize);
const y = Math.floor((event.clientY - rect.top) * scaleY / cellSize);
if (x >= 0 && x < gridSize && y >= 0 && y < gridSize) {
const selectedPattern = patternSelect.value;
if (selectedPattern === 'cell') {
grid[x][y] = grid[x][y] === 1 ? 0 : 1; // 通常のセル配置
} else if (selectedPattern === 'random') {
for (let i = -5; i <= 5; i++) {
for (let j = -5; j <= 5; j++) {
const nx = (x + i + gridSize) % gridSize;
const ny = (y + j + gridSize) % gridSize;
if (Math.random() > 0.5) {
grid[nx][ny] = 1;
}
}
}
} else if (selectedPattern === 'gliderGun') {
placePattern(gliderGunPattern, x, y);
} else if (selectedPattern === 'pufferTrain') {
placePattern(pufferTrainPattern, x, y);
} else if (selectedPattern === 'glider') {
placePattern(gliderPattern, x, y);
} else if (selectedPattern === 'lightweightSpaceship') {
placePattern(lightweightSpaceshipPattern, x, y);
}
drawGrid(grid);
}
}
// パターンの定義
const gliderGunPattern = [
[0, 4], [1, 4], [1, 5], [0, 5], [10, 4], [10, 5], [10, 6],
[11, 3], [11, 7], [12, 2], [12, 8], [13, 2], [13, 8], [14, 5],
[15, 3], [15, 7], [16, 4], [16, 5], [16, 6], [17, 5], [20, 2],
[20, 3], [20, 4], [21, 2], [21, 3], [21, 4], [22, 1], [22, 5],
[24, 0], [24, 1], [24, 5], [24, 6], [34, 2], [34, 3], [35, 2],
[35, 3]
];
const pufferTrainPattern = [
[0, 0], [1, 1], [1, 2], [2, 2], [2, 3], [3, 3], [4, 3],
[5, 2], [5, 1], [6, 1], [6, 0], [7, 0], [8, 0], [9, 0]
];
const gliderPattern = [
[0, 1], [1, 2], [2, 0], [2, 1], [2, 2]
];
const lightweightSpaceshipPattern = [
[0, 1], [0, 4], [1, 0], [2, 0], [3, 0], [3, 4], [4, 0],
[4, 1], [4, 2], [4, 3]
];
// 初期描画
drawGrid(grid);
</script>
</body>
</html>