Copyright (C) 2000 DaiichiGakushusha Corporation. All Rights Reserved.
連載 JavaScript実習講座
エデュカーレNo.15 より
筑波大学大学院教授 久野 靖
第1回 シミュレューション
第2回ゲーム作成
 <INDEX>
第3回ウェブページの工夫
 先生方のなかには,「ゲーム作成」と聞いて抵抗を感じ られる方もおいでかもしれない。確かに「テレビゲーム」 は学習というより娯楽の一種であり,長時間ゲームに熱 中してしまい,勉強がおろそかになる高校生も少なくな い。
 しかし,プログラミング学習の題材としてゲームを取 り上げることは,上記のようなマイナスイメージとはま た別の,次のような利点をもっている。
・高校生たちは「ゲーム」にプラスのイメージをもってお り,その高いモチベーションによって,多少ハードル の高い題材でもこなすことができる。
・「ゲームプログラマ」はわが国において確立された職業 の1つであり,それがどのようなものかを多少なりと も考える機会が得られる。
・ほかの分野では生徒がつくったプログラムが「実用」に なることはあまりないが,ゲームであれば簡単なもの でも「遊ぶ」ことができ,実用のプログラムをつくった という満足感が得られる。
・ユーザの記憶能力や反応速度を試すようなゲームは, 人間の知覚・運動能力に関するさまざまな洞察を与え てくれるとともに,ユーザインタフェースに関する理 解を深める題材となり得る。
 今回はいくつかの異な るパターンのゲームを実 際に作成することにしよ う。
 まず最初は「数当てゲーム」をやってみよう。このゲー ムは次のようなルールになっている。
・出題者(ここではプログラム)は4桁の数字を思いうか べる(4桁の数字はすべて互いに異なる※1)。
・回答者が4桁の数字を入力すると,出題者はそれを自 分の思いうかべた数字と照合し,次の数を答える。
ヒット:同じ数字が同じ位置にある個数
ブロー:同じ数字があるが位置が違うものの個数
・それを聞いた回答者は再度数字を言いなおす。決まっ た回数(たとえば20回)以内に正解すれば回答者の勝ち となる。
 このようなクイズ型のゲームでは,「ゲーム開始」「回答 チェック」などの動作を指示する「ボタン」と,回答を入力 する「入力欄」が必要になる。ボタンは次のような HTML のタグを出力することで生成できる。
<button onclick="押した時の動作">ラベル</button>
 「押した時の動作」は,読みやすさのためにJavaScript の関数を定義して,それを呼び出すのが普通である。次 に,入力欄は次のようなHTML のタグで生成できる。
<input id="ID 名">
 これでdocument.getElementByID()によりこのオ ブジェクトをJavaScript に取り出してきて,そのvalue プロパティを読み出すことで入力欄の値が取り出せる。 これらのHTML タグはdocument.write()でHTML を書き出すことで生成できる。
 このゲームをJavaScript でプログラムしたものをプ ログラム[1]に示す。
プログラム[1]

<script>
document.write('<h1 id="disp1">数当てゲーム<\/h1>');			//(1)
document.write('4桁の違った数字からなる数を当てます。');			// :
document.write('<button onclick="start()">Start<\/button><br>');	// :
document.write('回答欄:<input id="ans1">');				// :
document.write('<button onclick="check()">Check<\/button><br>');	// :
disp = document.getElementById('disp1');				//(2)

ans = document.getElementById('ans1');					// :
a = ['0','1','2','3','4','5','6','7','8','9'];				//(3)
function start(){							//(4)
  for(i=0; i<9 ;++i){							// :
    j = i + Math.floor((10-i)*Math.random());				// :
    z = a[i]; a[i] = a[j]; a[j] = z;					// :
  }									// :
  disp.innerHTML = '私が選んだ4桁の数字を当ててください。';count = 0;	// :
}									// :
function check(){							//(5)
 if(ans.value.length!=4){						// :
  disp.innerHTML='4桁の数字ですよ!';					// :
 }else{									// :
  hit=0;blow=0;								// :
  for(var i=0;i<4;++i){							// :
   for(var j=0;j<4;++j){						// :
    if(ans.value.charAt(i)==a[j]){					// :
     if(i==j)++hit; else++blow;						// :
    }									// :
   }									// :
  }									// :
  ++count;								// :
  if(hit==4){								// :
    disp.innerHTML=count+'回目:正解です!!!';				// :
  }else if(count<20){							// :
    disp.innerHTML=count+'回目:ヒット='+hit+'、ブロー='+blow;		// :
  }else{								// :
    disp.innerHTML='残念!正解は:'+a[0]+a[1]+a[2]+a[3];			// :
  }									// :
 }									// :
}									// :
</script>


(1)document.writeで,表示に使う見出し,説明文,開始 ボタン,回答欄,判定ボタンを出力する。
(2)見出しを変数disp に,回答欄を変数ansに取り出す。
(3)'0'〜'9'の文字を入れた配列を変数aに用意する。
(4)関数start()は開始ボタンの動作をあらわす。配列a のi =0〜9番目の要素のそれぞれに,その右側の要 素の番号をランダムに1つjとして選び, i 番目とj 番目を交換することで配列をシャッフルする(切る, 「シャッフルとは」のコラム参照)。その先頭の4個の数字が「問題」 となる。メッセージを出力し,回答回数を0にする。
(5)関数check()は判定ボタンの動作をあらわす。まず回 答欄の文字数が4文字でなければ警告を出す。次に変 数hit,blowを0にしてから,i=0..3,j=0..3のす べての組合せについて,入力文字列のi 番目と問題文 字列のj番目が等しいとき,i とjが同じならhit,そ うでなければblowを1増やす。hit が4なら正解。不 正解でカウントが20未満ならhit とblowの値を表示。 カウントオーバーなら正解を表示する。
 クイズ型のゲームをプログラムで実現することの意味 を考えてみよう。人間が出題する場合,規則にしたがっ てヒットやブローの数を回答していくのは間違えやすく, 出題が面倒になりやすい。プログラムであれば,いくつ でも疲れることなく問題を生成してくれるし,ヒットや ブローの数を間違えることもない。

演習1
もっと単純な「数当て」ゲームをつくろう。出題 者(プログラム)は2桁の数を思い浮かべ,回答者は2 桁の数を入力する。出題者は回答が「正解」「大きすぎ」 「小さすぎ」のどれであるかを答える。

シャッフルとは

 シャッフルとは,配列に入った要素の順番をランダムに 並べ替えることをいう。その方法だが,たとえば,配列a の大きさが10 (a[0]〜a[9]の要素がある) だったとして,次 のように考えればよい。
(1)まず0〜9から整数をランダムに選ぶ。それを j とする。
(2)a[0]とa[j]を交換する。それには次のようにする。
a[0];a[0]= a[j];a[j]= z;
これにより,a[0]はもとのa[0]〜a[9]のどれか1つ がランダムに入ったことになる。
(3)今度は1〜9から整数をランダムに選ぶ(実際には0 〜8からランダムに選び,それに1を足して1〜9にす る)。それを j として,a[1]とa[j]を交換する。これに より,a[1]にはa[0]を選んだ残りのもののどれか1つ がランダムに入ったことになる。
(4)同様にしてa[2],a[3],…とランダムに決めていくこと で,配列全体をシャッフルできる。
 プログラム[1]のゲームは時間制約がない,つまりどれ だけ考えてから回答してもかまわないものだったが,そ のようなゲームは当然ながら「じっくり考える」形になる。
 これに対し,時間制約を設けることでゲームは反射神 経や反応を競うものになる。そこで次のパターンとして, 時間制約のあるゲームを考えてみる。
 次の例題ゲームはやはり4桁の数字が使われるが,次 のようなルールになっている。
・短い一定の時間間隔で4桁以下の数字が表示される。
・最後の数字もその同じ時間だけ提示されて消える。
・最後にあらわれた数字を思い出して回答する。
プログラム[2]

<script>
document.write('<h1 id="disp1">記憶ゲーム<\/h1>');			//(1)
document.write('Start を押し、最後の数字を記憶してください。');		// :
document.write('<button onclick="start()">Start<\/button><br>');	// :
document.write('回答をどうぞ:<input id="ans1">');			// :
document.write('<button onclick="check()">Check<\/button><br>');	// :
delay = 200;
count = 50;
num = 0;
disp = document.getElementById('disp1');	
ans = document.getElementById('ans1');
function start(){show=count; setTimeout(step,delay);}			//(2)
function step(){							//(3)
 --show;								// :
 if(show>=0){								// :
   num=Math.floor(10000*Math.random());					// :
   disp.innerHTML = num; setTimeout(step,delay);			// :
 }else{									// :
   disp.innerHTML='????';						// :
 }									// :
}									// :
function check(){							//(4)
 if(ans.value==num){							// :
   disp.innerHTML='正解です!!!';						// :
 }else{									// :
   disp.innerHTML='不正解!正解は:'+num;					// :
 }									// :
}									// :
</script>


(1)最初に表示欄となる見出し,説明文,スタートボタン, 回答欄,回答ボタンを出力する。
(2)変数showにあと何個表示するかを入れるものとし, start では最初はcount を入れて,delayミリ秒後に関 数step を呼び出すように依頼する。
(3)関数step ではshowを減らし,まだ数が最後でなけれ ば,0以上1未満の乱数を1万倍して整数に切り捨て ることで最大4桁の数を表示し,delayミリ秒後の関 数step を呼び出すように依頼する。最後なら数の変わ りに「????」を表示する。
(4)回答ボタンが押されたら,最後に表示した文字列(変数 numに残っている)と入力を比べ,同じかどうかで正 解/不正解メッセージをのいずれかを表示する。
 このようなタイミング型のゲームは,コンピュータで なければ出題は難しい。まず人間では大量のランダムデ ータを用意するのも難しいし,たとえばトランプのカー ドをよく切って用意したとしても,それを一定の短いタ イミングで順次提示して最後に隠すのも難しい。プログ ラムであれば,遊び手の反射神経に応じて時間を調節す るのも,桁数を調節するのも簡単である。
 また,どれくらいの時間間隔であれば覚えられるか, 何桁くらい覚えられるかは人間の神経系や脳の基本的な 情報処理能力によって決まってくることが知られている。 このような研究をおこなう専門家もいて,その研究成果 はさまざまな製品やソフトウェアの使いやすさを改良す るのに活かされてきている。

演習2
短い時間間隔で1桁の数字が10個表示され,最 後に合計を答えるようなゲームをつくろう。何個くら いであれば簡単に答えられるかも調べてみるとよい。
 すでにこれまでに学んできたように,JavaScript では さまざまな要素をブラウザ画面の任意位置に配置したり, それを時間とともに動かしたりすることができる。要素 として画像を使えば,たとえば「将棋盤」などを表示して 動かすこともできる。将棋や囲碁のような,ゆっくり考 えるゲームであれば,表示部分以外の原理は「クイズ型」 のゲームということになるし,一定時間見せて記憶させ たり,一定時間内にボタンを押すなどの形にすれば,「タ イミング型」のゲームになる。
 ここでは,前回学んだ物理シミュレーションを応用す る形で,「リフティング」「蹴掬」ゲームをつくってみるこ とにする ※2。その概要は次のようなものである。
・「ボール」も「自分の足」も20×20ピクセルの矩形の領域 であらわす。「自分の足」はマウスで左右に動かすこと ができる。
・Start ボタンを押すとボールが落ちてくるので,足を ボールの下に移動する。ボールは足に当たるとはね返 る。当たる位置によって左右方向への速度も変化する。
・ゲームとして時間とともに難しくなるように,1回蹴 るごとに重力加速度が増して,ボールが落ちてくる時 間が短くなるようにする。
・ボールを蹴りそこねて落ちてしまうまでの回数を表示 し,この回数の多さを交互に競う。
プログラム[3]

<script>
document.write('<h1 id="h1">Lift It!<\/h1>');				//(1)
h = document.getElementById('h1');					// :
document.write('<div id="d0"><button onclick="start()">Start<\/div>');	// :
d = document.getElementById('d0')					// :
d.style.position='absolute';						// :
d.style.left='0px'; d.style.top='50px';					// :
d.style.width='300px'; d.style.height='300px';				// :
d.style.backgroundColor='yellow';					// :
document.write('<div id="a1">A<\/div>');				//(2)
a = document.getElementById('a1');					// :
a.style.backgroundColor='pink';						// :
a.style.position='absolute';						// :
a.style.width='20px'; a.style.height='20px';				// :
a.style.left='100px'; a.style.top='300px';				// :
function apos(e){							// :
  if(window.event)ax=window.event.clientX;				// :
  else            ax=e.pageX;						// :
  a.style.left=(ax-10)+'px';						// :
}									// :
document.onmousemove=apos;						//(3)
document.write('<div id="b1">B<\/div>');				//(4)
b = document.getElementById('b1');					// :
b.style.backgroundColor='purple';					// :
b.style.position='absolute';						// :
b.style.width='20px'; b.style.height='20px';				// :
b.style.left='100px'; b.style.top='50px';				// :
stop=true;								// :
function start(){							//(5)
 bx=110; by=60; vx=0; vy=10; stop=false; g=12;				// :
 count=0; h.innerHTML='start...';					// :
}									// :
function bpos(){							//	  (6)
 if(stop) return;							//(7)	   :
 bx = bx + 0.1 * vx;							//(8)	   :
 if(bx<10&&vx<0 || bx>290&&vx>0) vx=-vx;				// :	   :
 vy = vy + g;								//(9)	   :
 by = by + 0.1 * vy;							// :	   :
 if(ax-20<=bx&&bx<=ax+20&&by>290){					//  (10) :
   vy=-vy; by=290-(by-290);						//(11) :   :
   vx=vx+10*(bx-ax);							//(12) :   :
   ++count; h.innerHTML=count; g=1.1*g;					//(13) :   :
 }else if(by>350){							//(14)	   :
   stop=true;								// :	  :
 }									//	  :
 b.style.left=(bx-10)+'px'; b.style.top=(by-10)+'px';			//(15)	   :
}
setInterval(bpos, 100)
</script>


(1)最初に表示用の見出しと開始ボタンを出力する。見出 しは変数hに取り出し,後で書き換えられるようにす る。表示ボタンを含むdiv要素は変数dに取り出し, 背景を黄色にして大きさと位置を調整し,ボールの動 く左右範囲を示すようにする。
(2)マウスの動きにつれて左右に動くようにするため,関 数apos()でマウスイベントのX座標をもとにstyle. left を変更する。
(3)関数aposはdocument のonmousemoveハンドラに 設定することで,(Aの上だけでなく)ページ中のどこに マウスがあってもその位置変化を受け取れるようにする。
(4)変数stop がtrueの時は止まっていて,falseだと動い ているようにする。
(5)関数start()はスタートボタンで呼ばれ,X/Y座標, X/Y方向の速度,重力加速度を初期設定し,stop を falseにする。
(6)あとの処理はすべてbpos()の中でおこなわれる。 bposは100ミリ秒に1回ごとに呼び出される。
(7)bposの中では,もしstop がtrueなら何もせずに戻る。
(8)X座標はこれまでのX座標とX方向の速度で計算する。 ただし10〜190の範囲を出そうになったら,X方向の速 度を反転させる。
(9)Y方向の速度は重力加速度により増える。 Y座標はX座標と同様に計算する。
(10)もし,X座標をAのX座標と比較して前後20以内なら,ボールは「足の上に」ある。 そこでさらにY座標が足の位置(290)より下なら,「蹴る」処理をおこなう。
(11)具体的には,Y方向の速度を反転し,Y座標は290を越 えた分だけ「跳ね返った」位置とする。
(12)また,X方向の速度をAとBのX座標の差に応じて増 減する(足の端で蹴ったら斜めに飛ぶようにする)。
(13)回数を増やし,回数を表示する。また重力加速度を1.1 倍する(次第に難しくするため)。
(14)蹴らなかった場合で,位置が350を越えたら「下に落ち た」としてstop をtrueにする。
(15)いずれにせよ最後にX/Y座標に応じて要素Bを動かす。
 やや長かったが,物理シミュレーションを題材にして「次第に難しくなる」「点数をつける」などの味つけを入れ ることで,ゲームを作成できることが分かると思う。

演習3
ボールを増やしてみよう。そのほか,ゲームと しておもしろくなるような工夫を試してみよう。