前回、2次元配列を使って9 マスのどこに○が書かれているか記憶できるようにしました。今回は、コンピュータ側のプログラムを作成しましょう。
コンピュータ側(敵側)のプログラムは、「思考ルーチン」と呼ばれます。ゲームの面白さはこの思考ルーチンの質で大きく変わります。思考ルーチンが強すぎても弱すぎてもつまらくなってしまいます。そのため、難易度をどのように設定するか、どのような戦略を立てるかが難しく、そして面白くもあるところです。高度なプログラムでなくても、プレイヤーが予測できないような動きをさせることで、ゲームが面白くなることもあります。
さて、今回の○×ゲームでは、敵側が後攻で×印をつけるようにしてみます。その前に、このゲームはターン制(複数人が順番にプレイする)なので、ターンができるようにします。変数 turn を用意し、初期値として 0 を設定しておきます。
int turn = 0;
プレイヤーはturnが 0 を含む偶数のときだけ印をつけることができ、付け終わったらturnに 1 を足します。(以下のコードの”//”は、同じ行の”//”以降がコメントであることを示しています。コメントはプログラム上で無視されるため、プログラムの動作には影響しません。)ここでは、
// プレイヤー側
if(mousePressed){
X = mouseX / 200;
Y = mouseY / 200;
if(((turn % 2) == 0) && (map[Y][X] == 1)){
fill(255);
ellipse(X*200+100, Y*200+100, 150, 150);
map[Y][X] = 2;
turn++; // "++" は、1を足すという意味です
}
}
同様にコンピュータ側は、turnが奇数のときだけ印をつけることができるようにしましょう。ですがその前に、コンピュータ側の戦略を考えなくてはいけません。例えば、以下のような戦略が考えられます。
- はじめは角を取り、その後はリーチがかかったところを優先する
- 空いていれば真ん中を取り、その後はランダム、リーチがかかればその個所を優先する
- ランダム(どこでもよいので空いているところを探す)
プログラムで最も簡単につくることができるのは、3つめのランダムです。ランダムに×を付けるプログラムを作成しましょう。関数とは、いくつかの命令をまとめたものです。作成者が自由に名前をつけることができます。ただし、すでにProcessingで役割が決まっている言葉は予約語と呼ばれ、すでに予約語として登録されているものは自分の関数の名前として使うことができません。ちなみにすでにわたし達が使っている、setup()もdraw()も関数です。なお、”random” は、乱数を生成する関数名の予約語ですので、今回の目的(ランダムな場所を選び × をつける)で使うことはできません。今回はrandomXという名前の関数を作りましょう。
関数は、関数の呼び出しと定義の両方が必要です。draw()関数の中で関数名を記述して関数を呼び出し、draw()関数の後に関数を定義します(draw()関数内ではなく関数の中に関数を書くこともありますが、これは後からにしましょう)。例えば以下のように記述します。
void draw(){
関数名();
}
void 関数名(){
命令1
命令2
・・・
}
では、実際に書いてみましょう。× をつけた場所には、二次元配列上で 3 を上書きさせています。また、turn の値を 1 増やしておくのを忘れないようにしましょう。これがなければ、次のターンでプレイヤーが○を書くことができません。
void draw(){
if(mousePressed){省略}
randomX(); // randomX()関数の呼び出し
}
void randomX(){ // randomX()関数の定義
if((turn % 2 == 1) && (turn < 9)){
do{
X = int(random(3));
Y = int(random(3));
}while(map[Y][X] != 1);
line(X*200, Y*200, X*200+200, Y*200+200);
line(X*200, Y*200+200, X*200+200, Y*200);
map[Y][X] = 3;
turn ++;
}
}
ついでですが、×を書く部分はこの後も何回か登場します。同じことを何回も書くのはわずらわしいですし、読みにくく間違いのもとです。ですので、この個所も関数化してしまいましょう。関数名はdrawX()とします。上記のline関数が使われている2行を、drawX(); と置き換えます。そして、randomX() 関数の定義の後に新しく drawX() 関数の定義を書きます。中身はさきほど削除した2行の line() 関数のみです。
void drawX(){
line(X*200, Y*200, X*200+200, Y*200+200);
line(X*200, Y*200+200, X*200+200, Y*200);
}
ここまでのコードをまとめると以下のようになります。
int X, Y;
int turn = 0;
int map[][] = {{1,1,1}, {1,1,1}, {1,1,1}};
void setup(){
size(600, 600);
background(255);
line(0, 200, 600, 200);
line(0, 400, 600, 400);
line(200, 0, 200, 600);
line(400, 0, 400, 600);
}
void draw(){
if(mousePressed){
X = mouseX / 200;
Y = mouseY / 200;
if(((turn % 2) == 0) && (map[Y][X] == 1)){
fill(255);
ellipse(X*200+100, Y*200+100, 150, 150);
map[Y][X] = 2;
turn++;
}
}
randomX();
}
void randomX(){
if((turn % 2 == 1) && (turn < 9)){
do{
X = int(random(3));
Y = int(random(3));
}while(map[Y][X] != 1);
drawX();
map[Y][X] = 3;
turn ++;
}
}
void drawX(){
line(X*200, Y*200, X*200+200, Y*200+200);
line(X*200, Y*200+200, X*200+200, Y*200);
}
コードが少し長くなってきました。ここで、Processingのタブの機能を使って、タブごとにコードを書き分けましょう。こうすることで全体のコードがとても見やすくなります。”randomX” と “drawX” の2つのタブを作成します(なお、タブ名は必ずしも関数名と同じである必要はありません)。

タブを作成したら、関数のまとまりをタブの方へ移動させます。

ここまでで、とりあえずはプレイヤーとコンピュータが順番に○と×をつけあうことができるようになりました。続きは次回~。