ボタンを押すとサイコロの目が出て、その数だけコマを進めます。ゴールの位置を最初に超えると勝ちです。
盤面を簡略化するため、画面上にはゴールは表示しません。距離を伸ばすことでコマが進んでいるように見せます。
6 ......★
すごろく用に「sugoroku.html」を作成して、保存します。
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>すごろく</title> </head> <body> <h3>すごろく</h3> <hr> </body> </html>
4人のプレイヤーで対戦できるように、表示用のテーブルを書きます。
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>すごろく</title> </head> <body> <h3>すごろく</h3> <hr> <form name="form1"> <table border="0" cellspacing="5" cellpadding="5"> <tr> <td><input name="user0" type="text" size="20"></td> <td><input name="btn0" type="button" value="サイコロ" onClick="getNum(0)"></td> <td><div id="sai0"></div></td> <td><div id="brd0"></div></td> </tr> <tr> <td><input name="user1" type="text" size="20"></td> <td><input name="btn1" type="button" value="サイコロ" onClick="getNum(1)"></td> <td><div id="sai1"></div></td> <td><div id="brd1"></div></td> </tr> <tr> <td><input name="user2" type="text" size="20"></td> <td><input name="btn2" type="button" value="サイコロ" onClick="getNum(2)"></td> <td><div id="sai2"></div></td> <td><div id="brd2"></div></td> </tr> <tr> <td><input name="user3" type="text" size="20"></td> <td><input name="btn3" type="button" value="サイコロ" onClick="getNum(3)"></td> <td><div id="sai3"></div></td> <td><div id="brd3"></div></td> </tr> </table> </form> </body> </html>
4×4枠のテーブルを作成します。
4人分の行と、各列には、名前の入力欄、サイコロのボタン、サイコロの出目を表示、すごろくの位置を表示します。
それぞれフォームの部品には名前を付け、表示場所にはIDを付けておきます。
ボタンにはクリックのイベント処理としてユーザー関数getNum()を呼び出すようにしておきましょう。
見た目のデザインに凝ると、プログラムも非常に複雑で難しくなりますので、今回は単純に1つの枠にコマの進み具合を表示します。
コマの進んだ数を比較して、他の人よりも先にゴールまで伸びた人が勝ちになります。
また、本当のすごろくのように止まった場所でイベントが発生するという仕組みは、次の段階で考えたいと思います。
スクリプトでサイコロを作りましょう。
<script type="text/javascript"> user_num = 0; timer = 0; sai_start(); function sai_start() { r = Math.floor(Math.random() * 6) + 1; document.getElementById("sai"+user_num).innerHTML = r; timer = setTimeout("sai_start()",100); } </script>
プレイヤーの順番を管理するために変数user_numを用意します。
タイマーを動かしたり止めたりと、制御するために変数timerも用意しておきます。
初期設定の最後で「sai_start()」を呼び出します。
ユーザー関数sai_start()では、サイコロの出目がクルクル回ります。
そのために、乱数を変数rに入れて、変数user_numに対応した場所("sai"+user_num)に出目を表示しています。
最後に、乱数の表示を繰り返すためにsetTimeoutを使って、sai_start()を0.1秒後に呼び出しています。このとき変数timerには、制御用のタイマーIDが入っています。
sai_start()は自分自身で呼び出しをするため、永遠にループ(繰り返し)します。それを止めるために、タイマーIDが必要になります。
ボタンが押されると、サイコロが振られ、次の人に順番が変わるようにします。
まずは、順番が変わったときの処理を作りましょう。
<script type="text/javascript">
user_num = 0;
timer = 0;
sai_reset();
function sai_reset() {
document.form1.btn0.disabled = true;
document.form1.btn1.disabled = true;
document.form1.btn2.disabled = true;
document.form1.btn3.disabled = true;
document.form1.elements["btn"+user_num].disabled = false;
sai_start();
}
function sai_start() {
r = Math.floor(Math.random() * 6) + 1;
document.getElementById("sai"+user_num).innerHTML = r;
timer = setTimeout("sai_start()",100);
}
</script>
順番が変わったとき、次の順番の人だけがボタンだけを押せるようにしたいと思います。
一旦全部のボタンを使えなくしてから、変数user_numを参照して、ボタンを1つだけ使えるようにしています。
フォームの部品を呼び出すには、部品名を直接書くか、elementsを使って、部品番号または部品名を指定します。
ボタンを使用禁止(true)にしている部分では、部品名を直接指定しています。
使用禁止を解除する部分(false)では、部品名を変数user_numと組み合わせているので、elementsを使っています。
sai_start()とsai_reset()を分けているのは、タイマーの影響を受ける部分を切り分けるためです。
初期化は、次の人に順番が回ったときに1回だけやります。その後、サイコロが回るのは、次のサイコロが振られるまでの間、ずっとです。
何度もやらなくて良い仕事と、タイマーで繰り返す仕事は分けておきます。
初期設定の呼び出しもsai_reset()に変更し、その中からsai_start()を呼び出すようにしています。
ボタンを押して、サイコロの出目が決定される処理を作ります。
<script type="text/javascript">
user_num = 0;
timer = 0;
sai_reset();
function getNum(num) {
clearTimeout(timer);
document.getElementById("sai"+num).innerHTML = "<font color='#FF0000'>" + r + "</font>";
}
既にフォームでは、ボタンを押すとユーザー関数getNum()が呼び出されるようになっていますので、ユーザー関数getNum()を作ります。
まずタイマーを止めます。ここで変数timerを使っています。
次に変数rを表示します。この時、判りやすいように赤い色を着けることにします。
変数rの内容は、sai_start()が繰り返し実行されている間に乱数で内容が変わっています。
サイコロの出目として表示されているものと同じです。
次のプレイヤーに順番を回す処理を作ります。
<script type="text/javascript">
user_num = 0;
timer = 0;
sai_reset();
function getNum(num) {
clearTimeout(timer);
document.getElementById("sai"+num).innerHTML = "<font color='#FF0000'>" + r + "</font>";
user_num++;
if (user_num == 4) user_num = 0;
sai_reset();
}
一旦タイマーを止めて、サイコロの出目を止めました。
出目が決定したら、次のプレイヤーに順番を回す必要があります。
次の人に順番を替えるため、変数user_numに1を足します。
しかし、プレイヤーは0番から3番の4人なので、「4」番はいません。
そこで、変数user_numが「4」のときは「0」番にする必要がありますので、if文でその処理をしています。
最後にsai_reset()を呼び出して、次のプレイヤーのための初期化をし、サイコロが回ります。
サイコロの出目が決まったときは赤くしました。次々にサイコロを振るとどれも数字は赤くなります。
そこで、順番が過ぎた人の出目は黒く変えたいと思います。
<script type="text/javascript"> user_num = 0; pre_num = 0; pre_r = 0; timer = 0; sai_reset(); function getNum(num) { clearTimeout(timer); document.getElementById("sai"+pre_num).innerHTML = pre_r; pre_num = num; pre_r = r; document.getElementById("sai"+num).innerHTML = "<font color='#FF0000'>" + r + "</font>"; user_num++; if (user_num == 4) user_num = 0; sai_reset(); } function sai_reset() { document.form1.btn0.disabled = true; document.form1.btn1.disabled = true; document.form1.btn2.disabled = true; document.form1.btn3.disabled = true; document.form1.elements["btn"+user_num].disabled = false; sai_start(); } function sai_start() { r = Math.floor(Math.random() * 6) + 1; document.getElementById("sai"+user_num).innerHTML = r; timer = setTimeout("sai_start()",100); } </script>
現在の出目とは別に、1つ前の人とその出目を覚えておく必要がありますので、初期設定に2つの関数を使います。
変数pre_numは1つ前の人の番号を記録することにします。最初は「0」にしておきます。
変数pre_rは1つ前の出目を記録します。これも「0」にしておきます。
getNum()でタイマーが止まった直後に、1つ前の出目(変数pre_r)を書き換えます。
「"sai"+pre_num」が1つ前の場所を示しています。
変数pre_rの出力時に黒色を指定するための<font>タグがありません。
これは、文字を赤くするために<div>の中に<font>タグを書いているために、出目を書き換えると標準の色である黒に戻るので、改めて<font>タグを使う必要がないからです。
表示の次には、現在のプレイヤーの番号(変数num)を変数pre_numに移し替えます。
更にその次では、現在の出目(変数r)を変数pre_rに入れ換えています。
参加人数は常に4人とは限りません。人数に応じてサイコロを振れるようにします。
そこで、名前欄を使って、ここに名前が書かれている場合だけ、サイコロの順番が回ってくるようにしましょう。
function sai_next() { var s = document.form1.elements["user"+user_num].value; if (s != "") { sai_reset(); } else { user_num++; if (user_num == 4) user_num = 0; sai_next(); } }
新しくユーザー関数sai_nexst()を作ってみましょう。
ここでは、ユーザー関数getNum()でサイコロの出目を表示した後、ユーザー関数sai_reset()を実行する前の段階で、次のプレイヤーの番号が正しいかどうかを判断することになります。
1行目ではローカル変数sに、次の人の名前を入れます。変数user_numは、この前の段階であるgetNum()でとりあえず次の番号に替えられていますから、予定のプレイヤー名が変数sに入ります。
2行目のif文は最後の行まで続いていて、間に「else」が入っています。書式としては「if (条件式) {真} else {偽}」となっています。
この場合は、
条件式が真(しん)のときと偽(ぎ)のときで処理を2つに分けています。「else」の前にある{}が真、それ以外は後の{}内を実行します。
真とは、条件が成り立つことを指します。条件が正しい、満たされているということです。
偽は真の逆の状態を示し、条件が間違っていたり、満たされない、成り立たないということになります。
このif文の条件式では「!=」という比較式が使われています。これは「==」の反対です。「s == ""」ならば、変数sが「""」(空っぽ)のときは真です。「s != ""」ですから、変数sが「""」でないときに真となります。
もし、条件式が「s == ""」だったら、真と偽が逆になるので、elseの前の処理と後の処理を入れ換えれば、同じことになります。
どちらも同じなので、どちらでも良いということです。
変数sはプレイヤーの名前ですから、これが空っぽでないときというのは、名前があるときということです。そのときは、処理を次のsai_reset()に進めます。
変数sが空っぽのときは、条件式が偽となりますので、else以降の処理が行われます。
5行目と6行目で、次のプレイヤーに番号を変更しています。これはgetNum()でやったことと同じです。
そして、
7行目でもう一度sai_next()を実行して、また次の人の名前があるかどうかを調べています。
if文の中から他のユーザー関数を呼び出した場合、先方の処理が終わると、if文の次に処理が戻ってきます。
ここでは、if文の後に処理がないため、何も起こりません。
function getNum(num) {
clearTimeout(timer);
if (pre_r > 0) document.getElementById("sai"+pre_num).innerHTML = pre_r;
pre_num = num;
pre_r = r;
document.getElementById("sai"+num).innerHTML = "<font color='#FF0000'>" + r + "</font>";
user_num++;
if (user_num == 4) user_num = 0;
sai_next();
}
ユーザー関数getNum()の次にユーザー関数sai_reset()を実行していましたが、これをユーザー関数sai_next()に変更します。
これで、一旦ユーザー関数sai_next()で次のプレイヤーが正しいかどうか判断し、修正されてから、ユーザー関数sai_reset()に流れが進みます。
最初プレイヤーの名前が入っていないため、この状態でサイコロボタンを押すと、「Out of Memory」というエラーが発生します。
これは、次のプレイヤーが見つからないために、ユーザー関数sai_next()を永遠に続けようとして、ついにパンクしてしまうのです。
<table border="0" cellspacing="5" cellpadding="5">
<tr>
<td><input name="user0" type="text" size="20" value="あなた"></td>
<td><input name="btn0" type="button" value="サイコロ" onClick="getNum(0)"></td>
<td><div id="sai0"></div></td>
<td><div id="brd0"></div></td>
</tr>
まずは単純な対策として、一人目の名前を自動的に入れておきます。
動作テストをするときも、一々名前を入力しなくて良いので楽になります。
でも、これでは根本的な解決ではありませんので、あとで解決しなくてはなりません。
現時点で2つの問題が発生しています。
まず、プレイヤーがいない場合に起こるエラー。そして、プレイヤーが一人の場合、サイコロの回転が止まらなくて、出目が判らなくなることです。
まずは、プレイヤーが一人の場合の問題点を修正しましょう。
function sai_start() {
r = Math.floor(Math.random() * 6) + 1;
if (user_num != pre_num) document.getElementById("sai"+user_num).innerHTML = r;
timer = setTimeout("sai_start()",100);
}
プレイヤーが一人ということはどうやって判断できるのだろうか。現在のプレイヤーが変数user_numで、次のプレイヤーが変数pre_numに番号で入っていますので、これが同じならばプレイヤーは一人ということになります。
そこで、「if (user_num == pre_num)」という条件式が考えられます。
その条件を逆にして、プレイヤーが一人でないときは、今まで通りサイコロを表示します。
条件式は否定文となり「!=」が使われます。
ユーザー関数sai_start()の2行目でサイコロの出目を表示していますので、ここにif文を加えます。
これで、プレイヤーが一人の場合、サイコロの出目は表示されません。
また新たな問題が起きてしまいました。
今度は、最初からサイコロが回転しなくなってしまったのです。
先ほど、プレイヤーが一人の場合にサイコロを止めたことが原因です。
まだ誰もサイコロを振っていないときにサイコロが自動的に振られるようにしなければなりません。
function sai_start() {
r = Math.floor(Math.random() * 6) + 1;
if (user_num != pre_num || pre_r == 0) document.getElementById("sai"+user_num).innerHTML = r;
timer = setTimeout("sai_start()",100);
}
もう1つ条件を付け加えて、最初はサイコロがある条件で回転するようにします。
ある条件とは、起動後、初めてユーザー関数sai_start()が実行されたときです。
その判断のために変数を用意しても良いのですが、丁度使える変数があります。前回のサイコロの出目である変数pre_rです。
変数pre_rは初期設定によって「0」になっていますが、一度でもサイコロが振られると、出目である「1」~「6」の数値が入ります。
そのため、最初にサイコロが振られるまでの間だけ「0」になっているのです。
2つ目の条件式は「pre_r == 0」になります。
2つの条件を同時に判断して、「どちらかが真ならば真」になるようにしたいので、「||」を2つの条件の間に置きます。
これで「どちらかが真ならば真」であると判断させることができます。
「||」は英語の「or」の意味で、「AかBかどちらか」という意味です。
「&&」を使うと、「and」の意味になり、「AとBの両方」という意味になります。
一人しかいないプレイヤーの名前を消してしまった場合を想定して対処を考えます。
function sai_next() { var s = document.form1.elements["user"+user_num].value; if (s != "") { sai_reset(); } else { if (user_num != pre_num) { user_num++; if (user_num == 4) user_num = 0; sai_next(); } else { sai_reset(); } } }
プレイヤーの名前が全てなくなるとサイコロが動かなくなります。
そこで、プレイヤーがいなくなった場合、最低でも一人いることにして、サイコロが動くようにしましょう。
ユーザー関数sai_next()の中では、プレイヤーの名前がない場合に順送りをしています。ここで永久に順送りが発生して、いつまで経ってもユーザー関数sai_reset()に進まないことが原因です。
変数user_nameは順送りでプレイヤーを探しますので、前プレイヤーである変数pre_numと同じプレイヤーまで送られると誰もいないということになります。
すなわち「user_num == pre_num」の場合は、この無限ループから抜け出さなくてはなりません。
ではまた逆に、現在の条件を実行するのは、その否定の場合ですので、if文では否定条件を書きます。
そして、elseを使って、if文から除外された場合、すなわち偽の場合の実行文を書きます。
ユーザー関数getNum()とユーザー関数sai_next()の中に同じ処理がありますので、これを1つにまとめます。
function getNum(num) { clearTimeout(timer); if (pre_r > 0) document.getElementById("sai"+pre_num).innerHTML = pre_r; pre_num = num; pre_r = r; document.getElementById("sai"+num).innerHTML = "<font color='#FF0000'>" + r + "</font>";user_num++;if (user_num == 4) user_num = 0;sai_next(); } function sai_next() { user_num++; if (user_num == 4) user_num = 0; var s = document.form1.elements["user"+user_num].value; if (s != "") { sai_reset(); } else { if (user_num != pre_num) {user_num++;if (user_num == 4) user_num = 0;sai_next(); } else { sai_reset(); } } }
どちらもユーザー関数sai_next()を実行する前に同じ処理をしています。そこで、これをユーザー関数sai_next()の中に移動して、最初に処理を行えば流れは全く同じです。
念のため他からユーザー関数sai_next()を呼び出す場合がないことを確認しましょう。もし他にあると、そちらの流れが変わってしまうからです。
青字の取り消し部分を削除して、赤字の部分に新しく追記します。
サイコロの処理、プレイヤーの順番の処理ができたので、次はコマを動かす処理を作っていきます。
コマがなければすごろくとは言えません。
<script type="text/javascript">
img = new Array("★","◆","●","■");
user_num = 0;
pre_num = 0;
pre_r = 0;
timer = 0;
sai_reset();
初期設定で、配列変数imgを置いて、4種類のコマを登録しておきましょう。
もちろん、色を着けたり、画像を使ってもかまいません。ただ、あまり大きな画像を使うと画面が見難くなるので、アイコン程度の大きさで4つ揃えましょう。
色の付け方:<font color='#FF0000'>★</font>
画像の使い方:<img src='filename'>
ゴールしたときに初期設定をやり直す必要があるので、ユーザー関数にまとめます。
<script type="text/javascript"> img = new Array("★","◆","●","■"); restart(); function restart() { user_num = 0; pre_num = 0; pre_r = 0; timer = 0; sai_reset(); }
初期設定にある5つの行をユーザー関数restart()の中に移し替え、元のところから呼び出します。
これにより、起動時の流れは、restart()→sai_reset()→sai_start()となります。
ボタンを押したときは、getNum()→sai_next()→sai_reset()→sai_start()となります。
今後、ゴールをしたときの処理を作り、起動時と同じ流れに戻るようにします。
コマを初期の状態で表示させましょう。
<script type="text/javascript">
img = new Array("★","◆","●","■");
restart();
function restart() {
user_num = 0;
pre_num = 0;
pre_r = 0;
timer = 0;
for (i=0; i<4; i++) {
document.getElementById("brd"+i).innerHTML = img[i];
}
sai_reset();
}
ユーザー関数restart()の中でコマの表示を初期化します。
for文を使って、配列変数imgで用意したコマを順番に取り出して表示しています。
コマの進み具合を記録するために変数を用意します。
<script type="text/javascript"> img = new Array("★","◆","●","■"); koma = new Array(); restart(); function restart() { user_num = 0; pre_num = 0; pre_r = 0; timer = 0; for (i=0; i<4; i++) { document.getElementById("brd"+i).innerHTML = img[i]; koma[i] = 0; } sai_reset(); }
初期設定で、配列変数komaの宣言だけ行います。
ユーザー関数restart()の中で、配列変数komaに「0」を入れて初期化します。コマは人数分必要ですので、for文の中で実行します。
配列変数の宣言と初期化の位置が違うのには理由があります。
配列の宣言を実行すると、データの中身がない配列変数ができあがります。そのため、for文の中では実行できません。また、繰り返し実行する必要もないので、ユーザー関数の中にも入れません。
ユーザー関数restart()の中では初期値を与える必要があるので、宣言の時点で初期値を与えても意味がないので、それぞれ適切なところで処理を分けています。
サイコロの出目を表示した後に、コマを進める部分を作ります。
function getNum(num) {
clearTimeout(timer);
document.getElementById("sai"+pre_num).innerHTML = pre_r;
pre_num = num;
pre_r = r;
document.getElementById("sai"+num).innerHTML = "<font color='#FF0000'>" + r + "</font>";
koma[num] += r;
var s = "";
for (i=0; i<koma[num]; i++) {
s += ".";
}
document.getElementById("brd"+num).innerHTML = s + img[num];
sai_next();
}
ユーザー関数getNum()にスクリプトを追加します。
出目を表示した後、次のプレイヤーに引き継ぐ前の処理です。
まず、配列変数komaのnum番目に出目を足しています。変数numは押されたボタンの番号ですから、そのままプレイヤーの番号にもなります。配列変数komaに出目を足して行くことで、どのプレイヤーが何コマ目にいるのかを記録しています。
次にローカル宣言した変数sを空にして用意しています。
その次のfor文を使って、コマの進んだ回数だけ{}内の処理を繰り返します。
繰り返される処理は、変数sに「.」を継ぎ足すという内容です。そのため、進んだ数の「.」となります。これを表示することで、進んだ距離をグラフのように表します。
「.」の部分は、他の記号や文字を使ってもかまいません。
空白を使いたいときは「 」という記号を使います。半角スペースでは連続して表示されないため、コード化された実体参照を使います。
for文を抜けたら、変数sを画面に表示します。
テスト用にコマの横にコマ数を表示したい場合は次のように記述します。
document.getElementById("brd"+num).innerHTML = s + img[num] + "(" + koma[num] + ")";
ゴールをしたかどうか決定するには、ゴールの位置(長さ)を決める必要があります。
その後、ゴールの判定を行います。
<script type="text/javascript"> img = new Array("★","◆","●","■"); koma = new Array(); goal = 20; restart(); function getNum(num) { clearTimeout(timer); document.getElementById("sai"+pre_num).innerHTML = pre_r; pre_num = num; pre_r = r; document.getElementById("sai"+num).innerHTML = "<font color='#FF0000'>" + r + "</font>"; koma[num] += r; var s = ""; for (i=0; i<koma[num]; i++) { s += "."; } document.getElementById("brd"+num).innerHTML = s + img[num]; if (koma[num] < goal) { sai_next(); } else { goalin(); } }
まず、ゴールを20コマ目と決めて、初期設定の段階で、変数goalを指定します。ここに書いて置くと後で修正したいときも見やすくなります。
次にユーザー関数getNum()の中で、コマを進めた後にゴールに達したかどうかの判定を行います。
コマの位置がゴールより大きくなったらゴールに達したと判定し、そのときは、ユーザー関数goalin()へ飛ぶことにします。
if文を使って、変数goalと配列変数koma[num]を比較します。ゴールに達していない場合、ユーザー関数sai_next()を今まで通り実行します。if文の条件は「koma[num] < goal」とします。
そして、elseで、ゴールした場合の処理を書いています。
書き方を逆にした場合は次のようになります。
if (koma[num] > goal) { goalin(); } else { sai_next(); }
いや、実はこれは間違っています。判るでしょうか。
正しい条件式は「koma[num] >= goal」にしないと、20コマ目でゴールすることができません。
このように条件に「=」を忘れることがあるため、以上、以下を使うよりも、超過、未満を使う方が良いと思います。
【条件式のまとめ】
条件式 | 真/偽 | 条件式 | 真/偽 | 条件式 | 真/偽 |
---|---|---|---|---|---|
A == B | AとBは等しいとき真 | A > B | AがBより大きいとは真 (AとBが等しいときは偽) |
A >= B | AはBと同じか大きいときに真 |
A != B | AとBが等しくないときに真 | A < B | AがBより小さいときは真 (AとBが等しいときは偽) |
A <= B | AはBと同じか小さいときに真 |
一人がゴールしたら終わりとするのか、全員ゴールするまで続けるのかで仕組みは変わりますが、このゲームでは一人がゴールした時点で終わりとします。
function goalin() {
alert("ゴール!!\n優勝は" + document.form1.elements["user"+user_num].value + "さんです。");
restart();
}
ゴールをしたら、alert()を使ってダイアログボックスでメッセージを表示します。
フォームからプレイヤーの名前を取ってきて、文章に付け加えています。
alertを使うと、画面の処理を止めてメッセージを表示します。
文章の中にある「\n」は改行です。
ダイアログボックスはOKボタンが押されると消えます。
その後、次の処理が実行され、ユーザー関数restart()で設定が初期化されゲームの終了です。
上記のsugoroku.htmlでは、テスト用にコマの横にコマ数を表示しています。
これでひとまず完成としますが、次にいくつかの改良をしてみたいと思います。
止まったマス目でイベント(ハプニング)が発生するなどアイデアを出してみましょう。
・ゴールの位置を判るようにする
・サイコロの目を画像で表示する
・コマを画像で表示する
・コマを選択できるようにする