この間の続き。
とりあえず書くだけ書いた。IEでは全く動かない。
一枚絵を描くだけじゃつまらない、と言われればなるほどごもっとも。やはり動きが見えてこそ、プログラムの甲斐があるという物でしょう。時間に沿った変化。それを実現する鍵になるのがタイマーです。
タイマーを使うには次の関数を使います。
setTimeoutはtミリ秒経過後に一回だけ関数fを呼び出すよう、タイマーをセットします。
setIntervalはtミリ秒間隔で関数fを繰り返し呼び出すよう、タイマーをセットします。
まずはsetTimeoutから。
function f1()
{
alert("f1呼ばれた");
}
setTimeout(f1, 1000);
1000ミリ秒後にメッセージを出します。実行は例によってインタラクティブコンソールで可能。
JavaScriptはコード中で自由に関数を作れるので、次のように書いても……
var f2 = function(){ alert("f2呼ばれた");}
setTimeout(f2, 1000);
次のように書いても……
setTimeout(function(){alert("f anonymous呼ばれた");}, 1000);
同じです。
これを使って絵を描いてみましょう。
var cv = document.createElement("canvas");
cv.setAttribute("width", "320");
cv.setAttribute("height", "240");
cv.style.cssText = "border: 1px solid;";
document.body.appendChild(cv);
var ctx = cv.getContext("2d");
setTimeout(drawLine, 50); //50ms後以降にdrawLineを呼び出すようセットする。
function drawLine()
{
ctx.strokeStyle = "#" + Math.round(Math.random() * 0xffffff).toString(16); //色をランダムで決める。
ctx.beginPath();
ctx.moveTo(Math.random() * 320, Math.random() * 240); //始点をランダムで決める。
ctx.lineTo(Math.random() * 320, Math.random() * 240); //終点をランダムで決める。
ctx.stroke();
setTimeout(drawLine, 50); //再度タイマーをセットする。
}
線が増えていく様子が確認できると思います。
setTimeoutは一回きりの呼び出しに使いますが、こうして呼び出されたときに再度タイマーを設定すれば、繰り返し呼び出すような用途にも使えます。
次はsetInterval。setIntervalは止めるまでずっと一定の間隔で関数を呼び出し続けようとします。
var cv = document.createElement("canvas");
cv.setAttribute("width", 120);
cv.setAttribute("height", 120);
cv.style.cssText = "border: 1px solid;";
document.body.appendChild(cv);
var ctx = cv.getContext("2d");
var second = 0;
setInterval(
function(){
// 秒をカウント。スコープの外のsecondを参照していることに注意。
++second;
// キャンバスを透明色で塗りつぶす。
ctx.clearRect(0, 0, 120, 120);
// 円を描く。
ctx.strokeStyle="#000";
ctx.fillStyle="#fff";
ctx.lineWidth = 4;
ctx.beginPath();
ctx.arc(60, 60, 50, 0, Math.PI*2, false);
ctx.fill();
ctx.stroke();
// 秒針を描く。
ctx.strokeStyle="#f00";
ctx.beginPath();
ctx.moveTo(60, 60);
ctx.lineTo(
60+Math.sin(Math.PI * second / 30)*40,
60-Math.cos(Math.PI * second / 30)*40);
ctx.stroke();
}
, 1000);
setTimeoutで繰り返すと少しずつ実際の経過秒数とずれていってしまうのですが、setIntervalなら大丈夫です。
個人的には普段考えたこともない意外な感じ。私、眼鏡を引け目に思ったなんてことは、今まで一度もない。私が眼鏡をかけ始めたのは小学校4~5年の頃だったと思うけど、それで人にからかわれたとかそういうことは全くなかった。すんなり周囲に受け入れられたと思う。友達にも眼鏡をかけている人はいたし、どちらかと言えばポジティブな印象を持っている。もちろん幼い頃からかけている人の中には、嫌な思いをした人もいるだろうが。運動能力? ナンセンス! 眼鏡をかけていても人並み以上に運動できたよ。それと、私にはコンタクトレンズを使いたいという人の気持ちが全く理解できない。目に異物を入れて何が楽しいんだろう。コンタクトレンズと悪戦苦闘していた姉を見ていると、コンタクトレンズがそれほど良いものとは思えない。使わなければならない状況になってしまった人は本当に気の毒だと思う。
と、そんなことはどうでも良いのだが、「メガネを外すと美少女」の逆がもっと欲しいという話を見て思ったこと。
でも単純な文字列置換じゃ満足できなくなって、結局式の評価が必要になるわけだ。やれやれ、たまには手書きの再帰下降型で丁寧に書くか。でもこれはsgfconv時代、あるいはもっと以前から欲しかった機能ではある。
Makefileライクな簡単な文字列置換が出来るようになった。
美味すぎ。カウンターに座ったら目の前でタマネギ切ってるんだもん。反則だよ。
式の変形途中で記号を間違えるので、Maximaの力を借りることにした。こういうのは本当は手を動かした方が良いんだろうけどね。
W32TeXとGhostScript、imaximaを導入。imaxima-image-typeがpng'だと画像は出るが何も見えなかったので'jpegにした。背景が黒いから見えないだけかなと思ったんだけど、imaxima-bg-color、imaxima-equation-color、imaxima-fg-colorを指定しても変わらず。'jpegにしたら黒地に白文字で表示されるようになった。
ああ、行列のかけ算はピリオドなのね。*だと思ってはまった。
迷路を文字列で記述して、それを読み込みたい。例えば、次のような記述。
...#####....... ...# #....... .### #######. .# @ #. .## ## # ###. ..# # #... ..# #### #... ..# #... ..##########...
'.'は部屋の外、'#'は壁、' 'は床、'@'はプレイヤー。
読み込み関数へ渡すとき、一つの文字列で渡すか、文字列の配列で渡すか。
一つの文字列で渡す場合、行毎の区切り文字を決めるか、別途迷路の幅(桁数)を指定する必要がある。
var maze = loadMaze("
|...#####.......
|...# #.......
|.### #######.
|.# @ #.
|.## ## # ###.
|..# # #...
|..# #### #...
|..# #...
|..##########...");
文字列の配列で渡す場合、ちょっと記述量が増える。メモリ効率も悪い。
var maze = loadMaze( ["...#####.......", "...# #.......", ".### #######.", ".# @ #.", ".## ## # ###.", "..# # #...", "..# #### #...", "..# #...", "..##########..."]);
古い人間なのでついつい上を考えてしまうのだけど、上の方法は区切り文字を入れ忘れたり、幅の指定を間違えたりといったミスを誘いやすい。効率が問題となる場面ではないのだから、後者を選んだ方が無難だと思う。
1行の文字数が一定ではない場合があり得る。例えば、次のように指定したって良いじゃないか、という話だ。
var maze = loadMaze( ["...#####", "...# #", ".### #######.", ".# @ #", ".## ## # ###", "..# # #", "..# #### #", "..# #", "..##########"]);
これについては、読み込むときに最大の幅で揃えても良いし、揃えずに読み込んで、アクセスするときにチェックしても良い。詳しくは後で考えることにする。
Cell = {
OUTSIDE: 0,
FLOOR: 1,
WALL: 2
};
function loadMaze(strs)
{
var assert = function(cond) { if(!cond){ throw "Failed to load maze.";}};
var player = null;
var cells = [];
for(var y in strs){
var cellsRow = [];
for(var x = 0; x < strs[y].length; ++x){ // for/inではダメだった。Firefoxは動いたけど。
switch(strs[y].charAt(x)){ //strs[y][x]と書いたらIEで動かなかった。
case '#': cellsRow.push(Cell.WALL); break;
case '@': cellsRow.push(Cell.FLOOR); assert(!player); player = {x:x, y:y}; break;
case ' ': cellsRow.push(Cell.FLOOR); break;
default: cellsRow.push(Cell.OUTSIDE); break;
}
}
cells.push(cellsRow);
}
return {cells:cells, player:player};
}
出来るだけ平易に書いてみた。以下言い訳。
とりあえず読んだ結果をテキストで出してみる。
// 読んでみるのコード
...
// どう指定するかの2番目のコード
...
document.open();
document.write("<pre>");
for(var y in maze.cells){
var line = "";
for(var x in maze.cells[y]){
switch(maze.cells[y][x]){
case Cell.OUTSIDE: line += '.'; break;
case Cell.FLOOR: line += ' '; break;
case Cell.WALL: line += '#'; break;
}
}
document.writeln(line);
}
document.writeln("player:(" + maze.player.x + "," + maze.player.y + ")");
document.write("</pre>");
document.close();
次はグラフィカルに表示してみたい。
今夜も月が綺麗である。
ああ、発売延期するのか。まあ、ゆっくり作ればいいんじゃね? くらいに思っていたのだが、なにやら変な話が流れているようだ。
個人的には、体験会で触ったくらいの出来であれば、多少不具合があっても発売すればいいんじゃね? と思っている。出たら買うけど、出なかったらそれはそれで困らないというのがなんとも。