STEP7では、レベルによって強さが変わるコンピュータを作るんですけど、まずは「最強のコンピュータの思考ルーチン」を作ってみますにゃ。でも、その前に、神経衰弱で考えられる最善の選択は何かってことを考えてみますにゃ。
(1)知っているカードの中でおなじ数字のカードが2枚以上あれば、その2枚をめくりますにゃ。
(2)おなじ数字のカードを2枚以上知らにゃい時は、まだ知らにゃいカードを1枚選んでめくりますにゃ。にゃんで知らにゃいカードにゃのかって言うと、仮に知ってるカードをめくった場合、2枚目のカードを選ぶ時にそれとおなじ数字のカードはもう知らにゃいってことになって、結局そこで運まかせの選択をするしかにゃいからですにゃ。逆に、まだ知らにゃいカードをめくった場合、それがたまたま知っているカードとおなじ数字という可能性がありますにゃ。その場合はもちろん、2枚目としてそれとおなじ数字のカードをめくりますにゃ。
(3)1枚目にめくったカードとおなじ数字のカードを知らにゃい場合は、2枚目としてまだ知らにゃいカードを1枚選んでめくりますにゃ。にゃんでまだ知らにゃいカードにゃのかって言うと、その時点でもう知ってるカードは絶対に揃わにゃいカードだからですにゃ。まだ知らにゃいカードをめくると、運が良ければ揃いますにゃ。
次に、これを「1枚目のカードを選ぶ時の行動」と「2枚目のカードを選ぶ時の行動」に分けて考えてみますにゃ。
○1枚目のカードを選ぶ時の行動
・まず知っているカードを数字ごとに分けて、何の数字のカードを何枚知っているかをはっきりさせますにゃ。
・その結果、ある数字のカードを2枚以上知っているということがわかったら、その中の1枚を「1枚目のカードとして」めくりますにゃ。状況によっては、2枚以上知っている数字のカードが2組あるという可能性もあるんですけど、その場合は、どっちを先に取っても2枚のカードが揃う→次もコンピュータの手番→もう1組の2枚が揃うってことになるだけで、にゃにも問題ありませんにゃ。
・2枚以上知っている数字がにゃい時は、まだ知らにゃいカードを選んで「1枚目のカードとして」めくりますにゃ。
○2枚目のカードを選ぶ時の行動
・1枚目のカードとおなじ数字のカードを知っている時は、そのカードを「2枚目のカードとして」めくりますにゃ。これは「最初から2枚以上知っていた場合」と、1枚目に知らにゃいカードの中から選んだカードと「おなじ数字のカードをたまたま知っていた場合」の両方が含まれますにゃ。
・1枚目のカードとおなじ数字のカードを知らにゃい時は、まだ知らにゃいカードの中から1枚選んで、たまたま揃うことに期待しますにゃ。
ここまでわかれば、あとはそういうプログラムをコンピュータの思考ルーチンに書き加えるだけですにゃ。「だけ」って、それが難しいんじゃにゃいかって思われるかもしれにゃいんですけど、そんなに難しいことではありませんにゃ。こういうものができると思いますにゃ。
// コンピュータ思考(1枚目)
function complay1()
{
for (i=0;i<=9;i++) {cpp[i]=0;}
for (i=0;i<=29;i++)
{
if (cp[i+teban*30]>0 && p[i]>0)
{
cpp[cp[i+teban*30]]=cpp[cp[i+teban*30]]+1;
}
}
cardnum1=-1;tgn=-1;
for (i=1;i<=9;i++)
{
if (cpp[i]>=2)
{
tgn=i;break;
}
}
if (tgn>=1)
{
for (i=0;i<=29;i++)
{
if (cp[i+teban*30]==tgn)
{
cardnum1=i;break;
}
}
}
if (cardnum1<0)
{
for (i=0;i<=2;i++)
{
r=Math.floor(Math.random()*30);
if (p[r]==0 || cp[r+teban*30]>0) {i=0;}
else {cardnum1=r;break;}
}
}
h=charaput(cardnum1,p[cardnum1]);
flg=2;wait=10;
}
// コンピュータ思考(2枚目)
function complay2()
{
cardnum2=-1;
for (i=0;i<=29;i++)
{
if (cp[i+teban*30]==p[cardnum1] && p[i]>0 && i!=cardnum1)
{
cardnum2=i;break;
}
}
if (cardnum2<0)
{
for (i=0;i<=2;i++)
{
r=Math.floor(Math.random()*30);
if (p[r]==0 || r==cardnum1 || cp[r+teban*30]>0) {i=0;}
else {cardnum2=r;break;}
}
}
h=charaput(cardnum2,p[cardnum2]);
h=hantei();
}
実際に、コンピュータと対戦してみてくださいにゃ。本当に最強だってことがわかると思いますにゃ。書き加えた部分の解説をしますにゃ。読み飛ばしても大丈夫ですにゃ。
・「for (i=0;i<=9;i++) {cpp[i]=0;}」
繰り返し処理で、配列cpの0〜9番目の要素の値を0にしますにゃ。配列cpは、コンピュータが1〜9(数字の1〜7、0、くるくるカード)をそれぞれ何枚ずつ知っているかを数えるためのもので、コンピュータが1枚目をめくる前にリセットしておきますにゃ。配列の0番目の要素は、実際には使いませんにゃ。この配列は、記憶じゃにゃくてただのメモみたいなものですから、2人分作る必要はありませんにゃ。
・「for (i=0;i<=29;i++)」「{」「}」
30回の繰り返し処理で、コンピュータが記憶しているカードを全部調べてみますにゃ。
・「cp[i+teban*30]>0 && p[i]>0」「{」「}」
2つの条件に関して、両方ともそれにあてはまる時、{ }の中の命令を実行しますにゃ。最初の条件は「配列cpの『変数iに変数tebanの30倍を加えた値」番目の要素が0より大きい時』ですにゃ。配列cpはコンピュータの記憶で、先攻は配列番号0から、後攻は配列番号30から始まるんでしたにゃ。「teban*30」はそういう意味ですにゃ。「i+teban*30」の値は、30回の繰り返しで先攻なら0〜29、後攻なら30〜59になりますにゃ。配列cpの「その値」番目の要素っていうのは、ようするに「先攻または後攻のコンピュータがそのカードを知っているかどうか」で、知っている時はそのカードの数字、知らにゃい時は0ですにゃ。つまり、最初の条件「cp[i+teban*30]>0」は、コンピュータがそのカードを知っている時ってことになりますにゃ。2番目の条件は簡単で、配列pのi番目の要素の値が0より大きい時、言い換えるとそのカードがまだ場にある時ってことですにゃ。
・「cpp[cp[i+teban*30]]=cpp[cp[i+teban*30]]+1;」
最初にリセットした配列cppの要素に1を加えるんですけど、[ ]が2重になっていますにゃ。こういう場合は、内側から順番に見ていくとわかりやすいですにゃ。まず、[ ]の中の「cp[i+teban*30]」これはさっき説明しましたにゃ。ようするに、その時の手番のコンピュータの記憶のi番目ってことですにゃ。仮にそのカードが「1」だったとしたら、[ ]の中の値は1になりますにゃ。cpp[cp[i+teban*30]]は、回りくどい言い方をすると「配列cppの『配列cpのi+teban*30番目の要素の値』番目の要素の値」ってことになるんですけど、そのカードが「1」ならそれは「cpp[1]」ってことで、そのカードが他の数字でもおなじことですにゃ。その値に1を加えるってことは、ようするにコンピュータがその数字のカードを1枚知ってるからメモの数字を1増やすってことですにゃ。これを30回繰り返すことで、コンピュータが何の数字のカードを何枚ずつ知ってるかってことが配列cpの各要素の値としてわかることになるんですにゃ。
・「tgn=-1;」
変数tgnの値を-1にしますにゃ。tgnは「targetnumber」の略で、わたしが勝手に付けた名前ですにゃ。これから狙って取るカードがあるかにゃいかを表すのに使いますにゃ。
・「for (i=1;i<=9;i++)」「{」「}」
繰り返し処理にゃんですけど、変数iの初期値が1になっていますにゃ。これは、「数字の1(1番)〜くるくるカード(9番)まで」っていうことを表すために、こうしていますにゃ。今のところ、繰り返しの回数は最大9回ですにゃ。「今のところ」っていうのは、繰り返しの中で「break;」(強制終了)が実行される可能性があるからですにゃ。
・「if (cpp[i]>=2)」「{」「}」
配列cppのi番目の値が2以上、わかりやすく言うとコンピュータが数字のiのカードを2枚以上知っている時、{ }の中の命令を実行しますにゃ。
・「tgn=i;break;」
変数tgnの値を変数iの値にして、この繰り返し処理を強制終了しますにゃ。わかりやすく言うとこれから数字のiのカードを狙って取るってことですにゃ。この命令が実行されずに9回の繰り返しが終わってしまうこともありますにゃ。それはコンピュータがおなじ数字のカードを2枚以上知らにゃいってことで、その場合変数tgnの値は-1のまま、言い換えると狙って取りたいカードはにゃいってことですにゃ。
・「if (tgn>=1)」「{」「}」
変数tgnの値が1以上、つまり、狙って取るカードが決まっている時、{ }の中の命令を実行しますにゃ。
・「for (i=0;i<=29;i++)」「{」「}」
また30回の繰り返しで、コンピュータの記憶を調べてみますにゃ。
・「if (cp[i+teban*30]==tgn)」「{」「}」
「cp[i+teban*30]」はさっきとおなじにゃんですけど、簡単におさらいしてみますにゃ。先攻なら0、後攻なら30から始まる配列の要素(0〜29番目または30〜59番目)を順番に調べていきますにゃ。そして、その値が変数tgn(狙って取る数字)とおなじなら{ }の中の命令を実行しますにゃ。
・「cardnum1=i;break;」
変数cardnum1の値を変数iの値にする、わかりやすく言うと1枚目のカードとしてi番目のカードをめくることに決めるってことですにゃ。めくるカードが決まったから、この繰り返し処理を強制終了しますにゃ。実際にめくるのは、もうちょっと後になりますにゃ。
・「if (cardnum1<0)」「{」「}」
青い文字になってにゃい(STEP6の時にはもうあった)命令にゃんですけど、大事なことですから説明しますにゃ。変数cardnum1の値が0未満ってことは、この時点で1枚目としてめくるカードがまだ決まってにゃいってことですにゃ。言い換えると2枚以上知っていて狙って取るカードがにゃかったってことで、その場合に{ }の中の命令を実行しますにゃ。{ }の中はSTEP6とほとんどおなじにゃんですけど、1行だけ違うところがありますにゃ。
・「if (p[r]==0 || cp[r+teban*30]>0) {i=0;}」
条件が2つになりましたにゃ。最初の条件は「配列pのr番目の値が0である」つまりそのカードはもうにゃいってことですにゃ。新しく追加された2番目の条件は「配列cpのr+teban*30番目の要素が0よえい大きい」で、わかりやすく言うと、コンピュータがr番目のカードを知っているってことですにゃ。この2つの条件の少なくともどちらか一方にあてはまる時、変数iの値を0にすることでこの繰り返し処理を続けさせますにゃ。STEP6では「そのカードが場にあるかどうか」だけ判断してたんですけど、ここでは「そのカードを知っている」っていう条件を付けくわえることで、結果として場にまだ残っていて、コンピュータが知らにゃいカードが選ばれることになりますにゃ。
・「for (i=0;i<=29;i++)」「{」「}」
2枚目としてめくるカードを決める時も、おなじように30回の繰り返しで、コンピュータの記憶を調べてみますにゃ。
・「if (cp[i+teban*30]==p[cardnum1] && p[i]>0 && i!=cardnum1)」「{」「}」
条件の数が3つになりましたにゃ。こういう形で「&&」でつながった条件が3つある場合は、3つの条件すべてに関してそれにあてはまる時、全体として条件にあてはまることになって、{ }の中を実行しますにゃ。3つの条件を順番に見ていきますにゃ。
・「cp[i+teban*30]==p[cardnum1]」 配列cp…の説明は今までとおなじで、コンピュータの記憶のi番目ってことですにゃ。その値が配列pのcardnum1番目の要素の値とおなじ、わかりやすく言うと1枚目にめくったカードの数字とおなじ数字のカードをコンピュータが知っているってことですにゃ。
・「p[i]>0」 そのカードが場にまだあるならってことですにゃ。
・「i!=cardnum1」 変数iの値が変数cardnumの値と違う、言い換えるとそのカードは1枚目のカードではにゃいってことですにゃ。これを付けにゃいと、コンピュータはおなじカードを2回めくって(見かけ上は1枚しかめくらにゃいで)カードが揃ったと判断してしまいますにゃ。
・「cardnum1=i;break;」
変数cardnum2の値を変数iの値にする、わかりやすく言うと2枚目のカードとしてi番目のカードをめくることに決めるってことですにゃ。めくるカードが決まったから、この繰り返し処理を強制終了しますにゃ。実際にめくるのは、もうちょっと後になりますにゃ。
・「if (cardnum2<0)」「{」「}」
1枚目の時とおなじで、この時点で2枚目としてめくるカードがまだ決まってにゃい時、{ }の中の命令を実行しますにゃ。{ }の中はSTEP6とほとんどおなじにゃんですけど、1行だけ違うところがありますにゃ。
・「if (p[r]==0 || r==cardnum1 || cp[r+teban*30]>0) {i=0;}」
また条件が3つになりましたにゃ。こういう形で「||」でつながった条件が3つある場合は、3つの条件に関して少なくともどれかひとつにあてはまる時、全体として条件にあてはまることになりますにゃ。「少なくとも」ですから、3つのうちの2つにあてはまっても、3つ全部にあてはまっても、それは「全体としてあてはまる」ですにゃ。その場合に変数iの値を0にすることでこの繰り返し処理を続けさせますにゃ。結果として場にまだ残っているカードで、1枚目としてめくったカードとは別のカードで、コンピュータが知らにゃいカードが選ばれることになりますにゃ。
※参考 この講座では扱いませんけど、条件を4つ以上にしたり、それぞれの条件をさらに( )で囲んで書いたりすることもできますにゃ。例えば「((a1==0 && a2==0) || a3==0)」の場合は、「a1==0 && a2==0」または「a3==0」ということになって、a1とa2が両方とも0ならa3に関係にゃく成立、逆にa3が0ならa1とa2に関係にゃく成立、a1とa2とa3が3つとも0でももちろん成立ってことになりますにゃ。もっと複雑な条件も作ろうと思えば作れますし、今後そういうことも必要になってくるかもしれませんから、ご自身で研究してみてくださいにゃ。
長々と続けてきたこの講座にゃんですけど、ゲームとして作る部分はこれが最後ですにゃ。レベルによって強さが変わるコンピュータを作りますにゃ。ここでは、思考ルーチン(関数complay1、関数complay2)の中は変えずに、コンピュータがカードを覚える確率とコンピュータが忘れるかもしれにゃいカードの最大数を変えることで、コンピュータの強さが変わるようにしますにゃ。書き換えるところは2ヶ所だけですにゃ。こういうものができると思いますにゃ。
// コンピュータ記憶
function commemory()
{
for (i=0;i<=1;i++)
{
if (player[i]>0)
{
a=player[i]*20-10;
r=Math.floor(Math.random()*100);
if (r<a) {cp[cardnum1+i*30]=p[cardnum1];}
r=Math.floor(Math.random()*100);
if (r<a) {cp[cardnum2+i*30]=p[cardnum2];}
}
}
}
// コンピュータ忘却
function comforget()
{
for (i=0;i<=1;i++)
{
if (player[i]>0)
{
a=10-player[i]*2;
for (j=0;j<a;j++)
{
r=Math.floor(Math.random()*30);
cp[r+i*30]=0;
}
}
}
}
STEP6で仮の値にしてておいた変数aを本当の値に変えるだけですにゃ。2行だけですけど解説しますにゃ。読み飛ばしても大丈夫ですにゃ。
・「a=player[i]*20-10;」
変数aの値を配列playerのi番目の要素の値の20倍-10の数値にしますにゃ。player[i]は、先攻またはコンピュータのレベルの数値で、この値が0の時はコンピュータじゃにゃくて人間ですにゃ。レベルの数値を20倍して10を引いた数を変数aの値にしますにゃ。それで、どうなるのかっていうと…
・「COM Lv1」 変数aの値は1×20−10=10になりますにゃ。
・「COM Lv2」 変数aの値は2×20−10=30になりますにゃ。ちょっと飛ばして…
・「COM Lv5」 変数aの値は5×20−10=90になりますにゃ。
そして、「0〜99までのでたらめな整数の値がa未満」の時、コンピュータがそのカードを覚える(その命令を2枚のカードに対してそれぞれ実行する)ことになりますにゃ。わかりやすく言うと、レベル1は10%、レベル2は30%、ちょっと飛ばしてレベル5は90%の確率でカードを覚えるってことですにゃ。
・「a=10-player[i]*2;」
配列playerの説明は、さっきとおなじですから省略しますにゃ。変数aの値を「10からコンピュータのレベルの2倍の数を引いた数値」にしますにゃ。それで、どうなるのかっていうと…
・「COM Lv1」 変数aの値は10−1×2=8になりますにゃ。
・「COM Lv2」 変数aの値は10−2×2=6になりますにゃ。ちょっと飛ばして…
・「COM Lv5」 変数aの値は10−5×2=0になりますにゃ。
そして、2重ループの内側の繰り返しをa回実行することになりますにゃ。この繰り返し処理の中で、コンピュータがカードを最大a枚忘れる可能性が出てきますにゃ。わかりやすく言うと、レベル1は最大8枚のカードを忘れる可能性がある、レベル2は最大6枚のカードを忘れる可能性がある、ちょっと飛ばしてレベル5はカードを忘れにゃいってことですにゃ。
最後に、今までお世話になった灰色の部分の表示を消しますにゃ。実際に神経衰弱で遊ぶ時に、これが見えてしまうとゲームになりませんからにゃ。でも、あなたご自身がもっと自由に改造してみたいって思った時に、今のカードの状態がどうなってるのか、わかるほうがいいですにゃ。そこで、「関数carddataputは消さにゃいでそのまま残して、プログラム内で呼び出さにゃいようにする」「灰色のところにあるtextarea(白い四角)はルールを表示する場所として再利用する」ことにしますにゃ。この講座で作ってきたプログラムの完成版ってことで、こういうものができると思いますにゃ。
// メイン
function main()
{
※関係にゃい部分はちょっと省略しますにゃ
h=cardmix();
// h=carddataput();
h=ruleinfo();
if (lcrule>=1)
{
luckycard=Math.floor(Math.random()*7)+1;
}
※関係にゃい部分はちょっと省略しますにゃ
}
if (flg==4)
{
※関係にゃい部分はちょっと省略しますにゃ
if (kuru==1) {h=cardmix();}
// h=carddataput();
if (player[teban]>0) {wait=11;} else {wait=0;}
flg=5;
}
}
// コンピュータ忘却
function comforget()
{
※関係にゃい部分はちょっと省略しますにゃ
}
// ルール説明
function ruleinfo()
{
if (gametype==0)
{
c="【GAME:A】\n";
c=c+"・数字の「1」〜「7」が各4枚、数字の「0」が2枚ありますにゃ。\n";
}
if (gametype==1)
{
c="【GAME:B】\n";
c=c+"・数字の「1」〜「7」が各4枚、「くるくるカード」が2枚ありますにゃ。\n";
}
if (gametype==2)
{
c="【GAME:C】\n";
c=c+"・数字の「1」〜「7」が各4枚、数字の「0」と「くるくるカード」が1枚ずつありますにゃ。\n";
}
if (gametype==1 || gametype==2)
{
c=c+"・くるくるカードをめくると、手番が終わった時に場のカードがシャッフルされますにゃ。\n";
}
if (gametype==3)
{
c="【GAME:D】\n";
c=c+"・数字の「1」〜「7」のうち、どれか2つの数字が5枚ずつ、その他の数字が4枚ずつありますにゃ。\n";
}
if (lcrule>0)
{
c=c+"・ラッキーカードが揃った時は、";
if (lcrule==1) {c=c+"+1点";} else {c=c+"+2点";}
c=c+"のボーナスポイントが加算されますにゃ。\n";
}
if (gametype>=2)
{
c=c+"・最後まで揃わにゃいカードが場に2枚残ることになりますにゃ。\n";
}
c=c+"・その他のルールは、普通の神経衰弱とおなじですにゃ。\n";
document.hyoji.text1.value=c;
}
// タイマー処理
※関係にゃい部分はちょっと省略しますにゃ
<body bgcolor="A0FFA0" onLoad='setInterval("timerX()",100);'>
※関係にゃい部分はちょっと省略しますにゃ
<td colspan=2 bgcolor="C0C0C0" align=center>
※ここは、本当は必要にゃいんですけど、作ってる途中で<br>いろいろ表示する場所ですにゃ<br>
<textarea cols=80 rows=10 name="text1"></textarea>
※この後はおなじですから省略しますにゃ
飽きるまで遊んでみてもいいですし、お友達を呼んで「こんなの作ったぞ〜」って自慢してもいいですし、「ここからさらに、こんな改造をしてみよう」って考えてもいいですにゃ。この後は、この講座で最後になるプログラム解説ですにゃ。最後くらいは読んでやるかにゃんて思わにゃくても大丈夫ですにゃ。
・「// h=carddataput();」
「//」は注釈行っていう意味で、おなじ行にあるこれより後(右側)の文字は無視されますにゃ。こういう風に、今ある命令文の頭に「//」を付けることで、一時的にその命令を実行させにゃいようにすることができますにゃ。これは、プログラミングのテクニックのひとつで、「一時的に」ってことは、言い換えると「また必要になったら『//』を削除するだけでいつでも使えるようになる」ってことですにゃ。
・「h=ruleinfo();」
関数ruleinfoを呼び出して実行しますにゃ。ゲームが始まる時に、選択したゲームタイプとラッキーカードのルールに関する説明文が表示されますにゃ。
・「// h=carddataput();」
さっきとおなじですにゃ。「//」の後のスペースは、いくつ入れてもおなじですにゃ。
・「function ruleinfo()」「{」「}」
関数ruleinfoの始まりと終わりですにゃ。関数の中でにゃにをやってるのかは、見ればわかると思いますから説明は省略しますにゃ。簡単に言うと、ルールごとに違う説明文と、いくつかのゲームタイプで共通の説明文、そしてルールに関係にゃく共通の説明文をくっつけて、まとまった文字列cを作って表示していますにゃ。
・「align=center」
これは、htmlのbodyの中にありますにゃ。見た目を良くするために、灰色の小部屋の中央に白い四角を表示するようにしていますにゃ。
・「※ここは、本当は…」
この表示が必要にゃいっていうか、ルール説明の上にこんなことが書いてあると、神経衰弱で遊ぶ人が見ると混乱してしまいますから、忘れずに消しておきますにゃ。