canvasと多重繰り返し

目標

ここでは多重繰り返しを身につける。
多重繰り返しは単なる単独の繰り返しの入れ子構造であり、繰り返しの基本がわかっていれば新しく学ぶことは特にない。

また、HTMLのcanvas要素に対してJavaScriptで描画する方法を学習する。

  • while文の二重繰り返しの挙動を理解できる。また、while文の二重繰り返しを書くことができる。
  • for文の二重繰り返しの挙動を理解できる。また、for文の二重繰り返しを書くことができる。
  • for文の二重繰り返しにおいて、変数の変化を正しく追うことができる。
  • 三重以上の繰り返しを正しく使うことができる。
  • canvas要素に円や矩形などの基本的な図形の描画ができる。
  • canvas要素に複雑な図形の描画ができる。

canvasと描画

多重繰り返しについて説明する前に、HTMLの要素の1つであるcanvas要素について説明する。

canvas要素

canvas要素は、Webページにプログラムによる描画領域を確保するための要素である。
次のように記述する。
この記述では200×200の描画領域を確保している。
タグで囲まれた「四角形を描画する領域」は、もしもcanvas要素をサポートしていないブラウザでこのページが表示された場合に代替として表示される文字列である。
最新のFirefoxやSafariなどのブラウザを利用している限りは、このような文字列が表示されることはない。

HTML
<canvas id="canvas" width="200" height="200">四角形を描画する領域</canvas>

キャンバス要素をHTML文書の中に挿入するだけでは、単にページに空間が空くだけである。
この文章の直後に空間が空いているが、これは上記の記述をこのページに挿入しているためである。
実際にcanvas要素を活用するためには、JavaScriptによって描画をすることが必要である。
次節以降ではJavaScriptによる描画方法について説明する。

四角形を描画する領域

JavaScriptによる描画の基本

canvas要素で確保した描画空間にJavaScriptを使って描画をするためには、canvas要素をプログラムの中で取得し、そこから更に描画コンテクストを取得する必要がある。

HTML
<canvas id="canvas" width="200" height="200">四角形を描画する領域</canvas>
JavaScript
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
ctx.strokeRect(50, 50, 100, 100);

上記はcanvas要素を使った四角を描画するプログラムである。
JavaScriptの1行目でcanvas要素を取得している。
canvas要素を取得することによって、プログラムでcanvas要素に対して描画などの処理ができるようになる。
2行目では、二次元描画用のコンテクストを取得している。
コンテクストというのは解りにくいかもしれないが、描画を行うにあたり、どのような座標を使うのか、四角や円などどのような図形を指定することが出来るのかなどを定義したものである。
つまり、ここでは「’2d’」と指定することによって、二次元描画コンテクストを取得している。
二次元があるということは「’3d’」と書くと三次元描画もできるのかと思うが、残念ながら現在のところ唯一使える描画コンテクストは「’2d’」のみである。
将来的には三次元描画コンテクストも使えるようになるかも知れない。

キャンバスの描画コンテクストを取得すると、そこに存在している幾つかのプロパティを変更することができる。
例えば、線の色や太さなどである。
JavaScriptで線の太さを変更するためには、描画コンテクストctxを取得した後に(変数名は「ctx」でなくとも問題ないが、ここでは便宜上、上記の続きでとみなして「ctx」を使う)、
lineWidthプロパティを変更する。
また、線の色を変更するためにはstrokeStyleプロパティを変更する。
更に、塗りつぶしを行う場合の色は、fillStyleプロパティを変更することによって指定が可能である。

次のプログラムを見てほしい。
JavaScriptの4行目から6行目までで線の太さ、色、塗りつぶしの色を指定している。
また、8行目と9行目でそれぞれ、塗りつぶした四角を描画し、四角の枠を描いている。

このプログラムのHTML部を見ると、普段はヘッダに入っているscript要素がbodyに入っている。
これはcanvas要素を作った後に、プログラムを読み込むようにするためである。
通常はこのような操作はせず、onloadイベントを使うが、今回はこのようにしてプログラムを実行してみてほしい。

HTML *
<!DOCTYPE html>
<html>

  <head>
    <meta charset="utf-8">
    <title>描画の例</title>
  </head>

  <body>
    <h1>描画の練習</h1>
    <canvas id="canvas" width="200" height="200">四角形を描画する領域</canvas>
    <script src="draw.js"></script>
  </body>

</html>
JavaScript(draw.js)
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

ctx.lineWidth = 10.0;
ctx.strokeStyle = 'black';
ctx.fillStyle = 'lightgrey';

ctx.fillRect(50, 50, 100, 100);
ctx.strokeRect(50, 50, 100, 100);

JavaScriptによる描画の基本(パス)

また、描画においては、より複雑な図形を描画するためにパスという概念がある。
これは、いろいろな描画メソッドを組み合わせて図形を描く時に使われるものである。
例えば、正六角形を描画するメソッドは二次元描画コンテクストには存在しないが、次のようにすることによって正六角形を描くことができる。
ここで、beginPath()メソッドからclosePath()メソッドまでがパスを定義している部分となる。
その中でmoveTo()メソッド(ペン先を上げた状態で座標に動かす)や、lineTo()メソッド(ペン先を下ろした状態で指定した座標に動かす。つまり、現在ペン先がある所から指定した座標まで直線を引く。)を使って正六角形を描き、その上でfill()メソッドによって塗りつぶし、また、stroke()メソッドによって直線を引いた部分に色をつけている。
fill()メソッドやstroke()メソッドによって色をつけるまでは、実際には目に見える形にはならないことに気をつけてほしい。

HTML
<canvas id="canvas" width="200" height="200">正六角形を描画する領域</canvas>
JavaScript
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

var x = 100;  // 中心点のX座標
var y = 100;  // 中心点のY座標
var r = 80;   // 半径

ctx.lineWidth = 3.0;
ctx.strokeStyle = 'black';
ctx.fillStyle = 'lightgrey';

ctx.beginPath();
ctx.moveTo(x + Math.sin(0 * 2 * Math.PI / 6) * r, y - Math.cos(0 * 2 * Math.PI / 6) * r);
ctx.lineTo(x + Math.sin(1 * 2 * Math.PI / 6) * r, y - Math.cos(1 * 2 * Math.PI / 6) * r);
ctx.lineTo(x + Math.sin(2 * 2 * Math.PI / 6) * r, y - Math.cos(2 * 2 * Math.PI / 6) * r);
ctx.lineTo(x + Math.sin(3 * 2 * Math.PI / 6) * r, y - Math.cos(3 * 2 * Math.PI / 6) * r);
ctx.lineTo(x + Math.sin(4 * 2 * Math.PI / 6) * r, y - Math.cos(4 * 2 * Math.PI / 6) * r);
ctx.lineTo(x + Math.sin(5 * 2 * Math.PI / 6) * r, y - Math.cos(5 * 2 * Math.PI / 6) * r);
ctx.lineTo(x + Math.sin(6 * 2 * Math.PI / 6) * r, y - Math.cos(6 * 2 * Math.PI / 6) * r);
ctx.closePath();
ctx.fill();
ctx.stroke();

また、ここで気をつけるべきことがもう一つある。
通常のグラフでは、X軸は左から右、Y軸は下から上に向かって数字が増えていくが、canvas要素で描画をする場合にはY軸は上から下に数字が大きくなる。
座標を計算する場合はこの点に注意してほしい。

> canvasの座標系

描画のためのメソッド

それでは、実際の描画方法についてみていこう。
以下に描画に使われる代表的なメソッドをまとめる。

メソッド 説明
fillRect(x, y, w, h) (x, y)で指定された座標を起点として、幅w、高さhの塗りつぶされた枠の無い四角形を描画する。lineTo()などと異なり、stroke()メソッドやfill()メソッドを用いなくとも実際に四角形が描画される。
strokeRect(x, y, w, h) (x, y)で指定された座標を起点として、幅w、高さhの塗りつぶされていない枠のみの四角形を描画する。lineTo()などと異なり、stroke()メソッドやfill()メソッドを用いなくとも実際に四角形が描画される。
clearRect(x, y, w, h) (x, y)で指定された座標を起点として、幅w、高さhの範囲の図形を消去する。
moveTo(x, y) ペン先を上げた状態で、(x, y)で指定された座標にペン先を動かす。
lineTo(x, y) 現在ペン先がある位置から、(x, y)で指定された座標まで直線を引く。
rect(x, y, w, h) (x, y)で指定された座標を起点として、幅w、高さhの四角形を描画する。
arc(x, y, r, sAng, eAng, anticlockwise) (x, y)を中心とするsAngで指定された角度からeAngで指定された角度までの半径rの弧を描く。また、anticlockwiseがtrueの場合は反時計回り、falseの場合は時計回りとなる。
bezierCurveTo(x0, y0, x1, y1, x2, y2) 3次ベジェ曲線を描く。現在の座標を始点、(x2, y2)を終点、(x0, y0)を始点に対する制御点、(x1, y1)を終点に対する制御点とするベジェ曲線を描く。
fillText(text, x, y) textで指定された文字列を座標(x,y)に書く。「ctx.font=”20pt 明朝”;」のように描画コンテクストのfontプロパティを変更することによって、フォントを変更することが出来る。

キャンバス全体を消去したい場合は、clearRect()を使って「ctx.clearRect(0, 0, canvas.width, canvas.height);」の様にすると良い。
ここでctxは描画コンテキストである。
また、canvasはキャンバス要素が入った変数である。
canvas.widthとcanvas.heightはそれぞれ、キャンバスの幅およびキャンバスの高さが自動的に設定される。

多重繰り返し

二重ループ

繰り返しについては、while文やfor文などが存在することは既に説明した。
しかし、whileループやforループを単独で使うだけでは、機能として不足することもある。
先ほどのcanvas要素を使って、次のようなチェッカーフラグの様な模様を描くことを考えてみよう。

四角形を描画する領域

このような模様を描くためには、もちろん根気づよく次のようにプログラミングする方法もある。

HTML
<canvas id="canvas" width="200" height="200">四角形を描画する領域</canvas>
JavaScript
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

ctx.fillRect(  0,   0, 20, 20);
ctx.fillRect( 40,   0, 20, 20);
ctx.fillRect( 80,   0, 20, 20);
ctx.fillRect(120,   0, 20, 20);
ctx.fillRect(160,   0, 20, 20);
 
ctx.fillRect( 20,  20, 20, 20);
ctx.fillRect( 60,  20, 20, 20);
ctx.fillRect(100,  20, 20, 20);
ctx.fillRect(140,  20, 20, 20);
ctx.fillRect(180,  20, 20, 20);
 
ctx.fillRect(  0,  40, 20, 20);
ctx.fillRect( 40,  40, 20, 20);
ctx.fillRect( 80,  40, 20, 20);
ctx.fillRect(120,  40, 20, 20);
ctx.fillRect(160,  40, 20, 20);

...

しかし、これでは人間が頑張って、コンピュータに楽をさせているようなものである。
もう少し人間が楽になる方法は無いだろうか?
繰り返しを使えばもう少し楽ができるだろう。
次のプログラムを見てほしい。

HTML
<canvas id="canvas" width="200" height="200">四角形を描画する領域</canvas>
JavaScript
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

var y;
for (y=0; y<200; y = y + 20) {
  if (y % 40 == 0) {
    ctx.fillRect(  0, y, 20, 20);
    ctx.fillRect( 40, y, 20, 20);
    ctx.fillRect( 80, y, 20, 20);
    ctx.fillRect(120, y, 20, 20);
    ctx.fillRect(160, y, 20, 20);
  } else {
    ctx.fillRect( 20, y, 20, 20);
    ctx.fillRect( 60, y, 20, 20);
    ctx.fillRect(100, y, 20, 20);
    ctx.fillRect(140, y, 20, 20);
    ctx.fillRect(180, y, 20, 20);
  }
}

しかし、これでも同じことを何度も書かなければならない。
そこで登場するのが二重ループである。
ループの中にもう一つループを追加することによってもう少し簡単に書くことができる。

HTML
<canvas id="canvas" width="200" height="200">四角形を描画する領域</canvas>
JavaScript
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

var x, y;
for (y=0; y<200; y+=20) {
  for (x=0; x<200; x+=20) {
    if ((x+y)%40 == 0) {
      ctx.fillRect(x, y, 20, 20);
    }
  }
}

この例では、変数yによって制御されるループの中に、変数xによって制御されるループが入っている。
これが二重ループである。
次に二重ループのフローチャートを示す。
一見複雑そうだが、流れを一つ一つ確認していけば理解できるはずである。

二重ループのフローチャート

練習問題7-

  1. *印のついたHTMLとdraw.jsを作成し、プログラムを実行してみなさい。
  2. ctx.fillRect()の行とctx.strokeRect()の行を入れ替えたプログラムを作成し、何が起きるかを確認しなさい。

練習問題7-

下記のような、nを入力して正n角形を描画するようなプログラムを作成しなさい。


正n角形を描画する領域

練習問題7-

上のプログラムを使って、辺の長さからπを求めるプログラムを作成しなさい。

π = ???

正n角形を描画する領域

練習問題7-

下記のような星印を描きなさい。

四角形を描画する領域

練習問題7-

次のような図形を描画しなさい。

四角形を描画する領域

練習問題7-

次のような図形を描画しなさい。

四角形を描画する領域

練習問題7-

次のような図形を描画しなさい。

四角形を描画する領域

練習問題7-

下記のように、点を表示しながらモンテカルロ法によってπを求めるプログラムを作成しなさい。
また、ボタンを押すたびに精度が上がっていくようにしなさい。
モンテカルロ法でπを計算するためには、正方形に内接する円を考え、適当な点を選んだ上で、その点が円の中に入るかどうかを確かめる。
その上で、円に入る点の割合からπを算出する。

参考:練習問題4-3

π = ???

モンテカルロ法によるπの計算