JavaScriptを学んで楽しいことの一つに、ブラウザ上で好きな図形や絵を描画できるという点にあります。ゲームを作ったり、グラフを描画したり、見た目が楽しいと楽しくプログラミングを学べます。今回は、ブラウザ描画を行うCanvas APIを使って、個体群生態学をシミュレーションするライフゲームを作ってみましょう。
Canvas APIとは?
2023年3月の世界のWebブラウザのシェアは、statcounterによれば、Google Chromeが65%、Apple Safariが20%、Microsoft Edgeが5%、Mozilla Firefoxが3%となっています。これら主要なWebブラウザには約10年以上前から「Canvas API」と呼ばれる高度な描画機能が利用できます。
このCanvas APIを使う事で図形や画像を描画することができます。ブラウザで動くゲームや、グラフ、写真など画像の加工ツールなどは、この機能を利用しています。そのため、ブラウザでツールを作る上で欠かせない機能と言えます。
Canvas APIの使い方
Canvas APIは、HTMLの要素である<canvas>に対してグラフィック描画を行うものとなっています。そのため、Canvas APIを使うためには、まず、HTMLのコードに、図形を描画するためのキャンバスを表す<canvas>要素を書き加えます。そして、JavaScriptからこの<canvas>要素を取得して描画を行います。
一番簡単なCanvas APIを使った例を確認してみましょう。次HTMLのコードは、Canvas APIを使って画面に赤い長方形を描画するものです。下記のHTMLを「hello.html」という名前で保存しましょう。
<!DOCTYPE html>
<html><meta charset="UTF-8">
<body>
<!-- HTMLにcanvasを配置する --- (*1) -->
<canvas id="cv" width="640" height="640"></canvas>
<!-- Canvasを操作するJavaScriptを記述 --- (*2) -->
<script>
// 描画先のキャンバスを取得 --- (*3)
const canvas = document.getElementById('cv')
const ctx = canvas.getContext('2d')
// 描画のための設定を行う --- (*4)
ctx.fillStyle = 'red' // 塗り色を赤に設定
// 描画を行う --- (*5)
ctx.fillRect(30, 30, 300, 200)
</script>
</body>
</html>
そして、ファイル「hello.html」をWebブラウザにドラッグ&ドロップしましょう。すると、次のように表示されます。
HTMLを確認してみましょう。(*1)ではHTMLに<canvas>要素を配置しています。ここにJavaScriptで描画を行います。そのため、幅を表すwidth属性、高さを表すheight属性を指定して十分な広さのキャンバスを用意します。(*2)以降ではキャンバスに描画を行うためのJavaScriptを記述します。
(*3)では描画先のキャンバス要素を取得します。<canvas id="cv">のようにid属性を指定しておくと、JavaScriptからその要素を参照できる仕組みとなっています。そして、キャンバスを取得したら、コンテキストと呼ばれる描画のためのオブジェクトを取得します。
そのため、HTMLから描画先のキャンバスを取得して、そこから描画用のコンテキストを取得するまでがセットとなっています。
(*4)では描画用の設定を行います。fillStyleを利用して図形の塗り色を指定します。そして、(*5)のfillRectメソッドを使って長方形を描画します。
ライフゲームとは?
Canvas APIの基本が分かったところで、もう少し高度な描画に挑戦してみましょう。ここでは、「ライフゲーム」を作ってみましょう。
ライフゲームとは、1970年にイギリスの数学者ジョン・ホートン・コンウェイによって考案されたもので、生物集団の栄枯盛衰をシミュレーションする環境ゲームです。
この環境ゲームでは、二次元のグリッドを使って生物(セル)の生死を表現します。グリッドに赤い円があれば、そこには生物が生きていることを表します。そして、生物集団は、過疎でも過密でも生きてはいけないという基本的なルールに沿ってシミュレーションを行います。
ライフゲームはターン制となっており、1回のターンにおいて、以下のルールに沿って生物のシミュレーションが行われます。
- 生物誕生 --- 死んでいるセルの周囲に、生きているセルが3つあれば、次の世代に生物が誕生します。
- 生物生存 --- 生きているセルの周囲に、生きているセルが2つ以上3つ以下あれば、次の世代に生物は継続して生存します。
- 過疎状態 --- 周囲に生きているセルが1つ以下しかなければ、次の世代には死滅します。
- 過密状態 --- 生きているセルの周囲に、生きているセルが4つ以上あれば、過密により死滅します。
このルールのために、1つのターンにおいて、生物の周囲(8方向)を調べます。そして、生存するセルがいくつあるかによって、次の世代の生死が決定します。なお、以前、別の連載でPythonを使ってライフゲームを作成しているので、JavaScriptとPythonのプログラムを見比べるのも楽しいでしょう。
ライフゲームを作ってみよう
それでは、さっそくライフゲームを作ってみましょう。以下を「lifegame.html」という名前で保存しましょう。
<!DOCTYPE html>
<html><meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<body><canvas id="cv" width="640" height="640"></canvas>
<script>
// 描画先のキャンバスを取得 --- (*1)
const canvas = document.getElementById('cv')
const ctx = canvas.getContext('2d')
// グリッドの設定 --- (*2)
const COLS = 40, ROWS = 30
const w = (canvas.width / COLS)
let grid = initGrid()
// 二次元配列に対して全ての処理を行う関数 --- (*3)
function map2d(fn, a2) {
return a2.map((row, y, ary) => {
return row.map((v, x, ary) => fn(v, x, y, ary))
})
}
// ランダムにグリッドを初期化(ランダムに生物を配置) --- (*4)
function initGrid() {
const g = new Array(ROWS)
for (let y = 0; y < ROWS; y++) {
g[y] = (new Array(COLS)).fill(0)
}
return map2d(_ => (Math.random() > 0.7) ? 1 : 0, g)
}
// 生物を描画 --- (*5)
function drawGrid() {
map2d((v, x, y, cells) => {
ctx.fillStyle = (v) ? 'red' : 'white' // 描画設定
ctx.strokeStyle = 'silver'
ctx.lineWidth = 1
ctx.fillRect(x*w, y*w, w, w) // 塗りつぶす
ctx.strokeRect(x*w, y*w, w, w) // 枠線を描画
}, grid)
}
// 栄枯盛衰のシミュレーション(次の世代) --- (*6)
function nextTurn() {
// 8方向の座標を定義 --- (*7)
const XY = [[0,-1], [0,1], [-1,0], [1,0], [-1,-1], [1,-1], [-1,1], [1,1]]
grid = map2d((v, x, y) => {
// 周囲のセルを調べる --- (*8)
let count = 0;
for (const xy of XY) {
const xx = x + xy[0], yy = y + xy[1]
if (xx < 0 || xx >= COLS || yy < 0 || yy >= ROWS) { continue }
count += grid[yy][xx]
}
// 次のターンの判定 --- (*9)
let alive = (grid[y][x] > 0)
if (!alive && count == 3) { alive = true } // 誕生
if (count <= 1 || count >= 4) { alive = false } // 過疎と過密
return alive ? 1 : 0
}, grid)
drawGrid() // --- (*10)
setTimeout(nextTurn, 300)
}
nextTurn()
</script>
</body>
</html>
先ほどと同じように、ブラウザにドラッグ&ドロップすればプログラムが実行されます。生物のシミュレーションが始まります。ブラウザのリロードボタンを押すことで、改めて、生物をランダムに配置します。
プログラムを確認してみましょう。(*1)では描画先のキャンバスを取得し、キャンバスから描画用のコンテキストを取得します。(*2)ではグリッドの列数(COLS)と行数(ROWS)や描画サイズ(w)の設定を行います。