[Labyrinthe Noir]>[Top]>[Computer Forum]>[実用JavaScript集]>[こども工作教室]>

すごろくのソースと解説


<html>
<head>
<title>すごろく</title>
</head>
<body>
<h3>すごろく</h3>
<hr>

</body>
</html>

すごろく用に「sugoroku.html」を作成して、保存します。


<html>
<head>
<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行あるのは、4人のプレイヤーで対戦できるようにするためです。
各列には、名前の入力欄、サイコロのボタン、サイコロの出目を表示、すごろくの位置を表示します。

それぞれフォームの部品には名前を付け、表示場所にはIDを付けておきます。
ボタンにはクリックのイベント処理として「getNum()」を呼び出すようにしておきましょう。

見た目のデザインに凝ると、プログラムも非常に複雑で難しくなりますので、今回は単純に1つの枠にコマの進み具合を表示します。
コマの進んだ数を比較して、他の人よりも先にゴールまで伸びた人が勝ちになります。

また、本当のすごろくのように止まった場所でイベントが発生するという仕組みは、次の段階で考えたいと思います。


<script language="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 language="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 language="javascript">
user_num = 0;
timer = 0;

sai_reset();

function getNum(num) {
clearTimeout(timer);
document.getElementById("sai"+num).innerHTML = "<font color='#FF0000'>" + r + "</font>";
}

(省略)

【ボタンを押した時の処理(1)】

ボタンを押して、サイコロの出目が決定される処理を作ります。

既にフォームでは、ボタンを押すとgetNum()が呼び出されるようになっていますので、ユーザー関数getNum()を作ります。

まずタイマーを止めます。ここで変数timerを使っています。
次に変数rを表示します。この時、判りやすいように赤い色を着けることにします。

変数rの内容は、sai_start()が繰り返し実行されている間に乱数で内容が変わっています。
サイコロの出目として表示されているものと同じです。


<script language="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();
}

(省略)

【ボタンを押した時の処理(2)】

一旦タイマーを止めて、サイコロの出目を止めました。
出目が決定したら、次のプレイヤーに順番を回す必要があります。

次の人に順番を替えるため、変数user_numに1を足します。
しかし、プレイヤーは0番から3番の4人なので、「4」番はいません。
そこで、変数user_numが「4」のときは「0」番にする必要がありますので、if文でその処理をしています。

最後にsai_reset()を呼び出して、次のプレイヤーのための初期化をし、サイコロが回ります。


<script language="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に入れ換えています。

【この時点の sugoroku.html を別枠で表示】


(省略)

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();
}
}

(省略)

【改良点:参加人に合わせた対応(1)】

参加人数は常に4人とは限らない。一人でやりたい場合もあるだろうし、人数に応じてサイコロを振れるようにしたいと思います。
そこで、名前欄を使って、ここに名前が書かれている場合だけ、サイコロの順番が回ってくるようにします。

新しくユーザー関数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();
}

(省略)

【改良点:参加人に合わせた対応(2)】

getNum()の次にsai_reset()を実行していましたが、これをsai_next()に変更します。
これで、一旦sai_next()で次のプレイヤーが正しいかどうか判断し、修正されてから、sai_reset()に流れが進みます。


(省略)

<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>

(省略)

【改良点:プレイヤー名を初期表示する】

最初プレイヤーの名前が入っていないため、この状態でサイコロボタンを押すと、「Out of Memory」というエラーが発生します。
これは、次のプレイヤーが見つからないために、sai_next()を永遠に続けようとして、ついにパンクしてしまうのです。

まずは単純な対策として、一人目の名前を自動的に入れておきます。
動作テストをするときも、一々名前を入力しなくて良いので楽になります。

でも、これでは根本的な解決ではありませんので、あとで解決しなくてはなりません。


(省略)

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);
}

(省略)

【問題点:プレイヤーが一人の場合の不具合】

現時点で2つの問題が発生しています。
まず、プレイヤーがいない場合に起こるエラー。そして、プレイヤーが一人の場合、サイコロの回転が止まらなくて、出目が判らなくなることです。

まずは、プレイヤーが一人の場合の問題点を修正しましょう。

プレイヤーが一人ということはどうやって判断できるのだろうか。現在のプレイヤーが変数user_numで、次のプレイヤーが変数pre_numに番号で入っていますので、これが同じならばプレイヤーは一人ということになります。
そこで、「if (user_num == pre_num)」という条件式が考えられます。

サイコロの出目を回しているのはsai_start()の中です。
ここでは、乱数を変数rに入れてから、画面に表示するということを繰り返しています。
プレイヤーが一人のときは表示しないということは、逆に言うと、プレイヤーが一人じゃないときに表示すれば良いということです。
そこで、表示をする処理に対して、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つ条件を付け加えて、最初はサイコロが回転するようにします。
1つその判断に使える変数があります。変数pre_rです。

変数pre_rは初期設定では「0」になっていますが、一度でもサイコロが振られると、出目である「1」〜「6」の数値が入ります。
そのため、最初にサイコロが振られるまでの間だけ「0」になっているのです。

2つ目の条件式は「pre_r == 0」になります。
2つの条件を同時に判断して、「どちらかが真ならば真」になるようにしたいので、「||」を2つの条件の間に置きます。
これで「どちらかが真ならば真」であると判断させることができます。

「||」は英語の「or」の意味で、「AかBかどちらか」という意味です。
「&&」を使うと、「and」の意味になり、「AとBの両方」という意味になります。

【この時点の sugoroku.html を別枠で表示】


(省略)

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_next()を呼び出しています。
if文を使って、現在のプレイヤーと前のプレイヤー番号が違うときだけ繰り返すように条件式を書きます。
また、elseを使って、番号が同じときはsai_reset()へと処理を続けます。これは、一周して他にプレイヤーがいなかったら、また同じ位置に戻っているということなので、プレイヤーが一人いるのと同じ状態となります。

elseの後の処理を忘れると、サイコロの目はいつまで経っても同じものしか出なくなってしまいます。

【この時点の sugoroku.html を別枠で表示】


(省略)

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();
}
}
}

(省略)

【改良点:同じ処理を整理する】

getNum()とsai_next()に同じ処理があります。

user_num++;
if (user_num == 4) user_num = 0;

この部分です。これを1つにして整理します。

getNum()にある緑の部分は削除して、sai_next()の中で実行することにします。
sai_next()も緑の部分を削除して、青の位置に移動します。

新しい位置で実行すれば、sai_reset()に行く前に1回は実行されることに違いはありません。
また、sai_next()をする場合も、飛ぶ前に2回目を実行していたものが、飛んでから実行されることになります。
流れが変わっていないことが確認できれば問題はありません。


(省略)

<script language="javascript">
img = new Array("★","◆","●","■");
user_num = 0;
pre_num = 0;
pre_r = 0;
timer = 0;

sai_reset();

(省略)

【すごろくのコマを用意】

サイコロの処理、プレイヤーの順番の処理ができたので、次はコマを動かす処理を作っていきます。

まずは、コマを用意する必要があります。
初期設定で、4種類のコマをあらかじめ配列変数imgに入れておきましょう。
もちろん、色を着けたり、画像を使っても良いでしょう。ただ、あまり大きな画像を使うと画面が見難くなるので、アイコン程度の大きさで4つ揃えましょう。

色の付け方:
<font color='#FF0000'>★</font>

画像の使い方:
<img src='filename'>


(省略)

<script language="javascript">
img = new Array("★","◆","●","■");

restart();

function restart() {
user_num = 0;
pre_num = 0;
pre_r = 0;
timer = 0;

sai_reset();
}

(省略)

【初期設定をユーザー関数に移し替え】

ゴールしたときに初期設定に戻す必要があるので、初期設定をユーザー関数「restart()」の中で行います。

restart()の中に初期設定の大部分を移し替えます。
元のところからrestart()を呼び出すように書き換えます。

これにより、起動時の流れは、restart()→sai_reset()→sai_start()となります。
ボタンを押したときは、getNum()→sai_next()→sai_reset()→sai_start()となります。
今後、ゴールをしたときにはrestart()を呼び出して、起動時の流れと同じことが起こります。


(省略)

<script language="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 language="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」を入れるようにすれば、後で初期化することができます。
そうしないと、コマの位置が最初の状態に戻らなくなってしまいます。
コマは4つありますから、この行程もfor文の中で行います。


(省略)

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に「.」を配列変数koma[num]の数だけ繰り返し継ぎ足しています。

「.」の部分は、「_」や空白、記号や文字を使ってもかまいません。
ただし、どのブラウザも半角スペースを2つ以上続けて表示しない決まりになっています。空白を使って移動跡が見えないようにしたい場合は、 「&nbsp;」という記号を使います。

変数sにコマ数だけの「.」が入ったら、後ろにコマを付けて、画面に表示しています。

【この時点の sugoroku.html を別枠で表示】

テスト用にコマの横にコマ数を表示しても良いでしょう。
その場合は、次のようにします。

document.getElementById("brd"+num).innerHTML = s + img[num] + "(" + koma[num] + ")";


(省略)

<script language="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 = 20」としておきます。

あとはgetNum()の最後にゴールに達したかどうかを判断させます。
ゴールをしたときは、ユーザー関数goalin()へ飛ぶことにします。
if文を使って、変数goalと配列変数koma[num]を比較して、次のプレイヤーに処理を渡すか、ゴールをしたときの処理に分けます。
if文の条件式「koma[num] < goal」は、変数goalよりも配列変数koma[num]が小さいときという意味です。そのときは、次のプレイヤーに、そうでないときはゴールをしたときになります。

このとき、変数goalと配列変数koma[num]が同じときにどうなるかがポイントです。
同じということはゴールしたことになりますね。

それでは、下のように条件を入れ換えてみるとどうでしょうか。

if (koma[num] > goal) {
goalin();
} else {
sai_next();
}

この場合、2つの変数が等しいときは、ゴールにならず、sai_next()を実行してしまいます。
「>=」だとただしくgoalin()が処理されます。

「>」や「<」で条件式を書く場合、2つの値が等しいときは、必ず偽となってしまうことに注意が必要です。

【比較演算のまとめ】

「==」等しい、「!=」等しくない。
「>」超える、「<」未満。(どちらも等しい場合は偽)
「>=」以上 、「<=」以下。(どちらも等しい場合は真)


(省略)

function goalin() {
alert("ゴール!!\n優勝は" + document.form1.elements["user"+user_num].value + "さんです。");
restart();
}

(省略)

【ゴールの表示】

一人がゴールしたら終わりとするのか、全員ゴールするまで続けるのかで仕組みは変わりますが、このゲームでは一人がゴールした時点で終わりとします。

ゴールをしたら、alert()を使ってダイアログボックスを表示します。
フォームからプレイヤーの名前を取ってきて、文章に付け加えています。

alertを使うと、画面の処理を止めてメッセージを表示します。
文章の中にある「\n」は改行です。

ダイアログはOKボタンが押されると消えます。
そのときになって、次の行の処理が続けて実行されます。
ここでは、restart()を実行して、設定を初期化します。

【この時点の sugoroku.html を別枠で表示】

上記のsugoroku.htmlでは、テスト用にコマの横にコマ数を表示しています。


【完成】

これでひとまず完成とする。
次には止まったマス目でイベント(ハプニング)が発生するところを作ってみたい。

その他、改良点として考えられるもの:
・ゴールの位置を判るようにする
・サイコロの目を画像で表示する
・コマを画像で表示する
・コマを選択できるようにする


戻る