珍しく、以前の記事から4ヶ月で戻ってきました。
今回、C言語でオセロを作ってみました。
ネットで作り方などは見ないで独力で作ったためもあり、面倒くさい&効率の悪い仕組みとなってしまいました。
ここでは、自分の石=黒、相手の石=白とします。
まず黒を置ける位置は、白をひっくり返せる場所のみとします。
黒を置くと、その周囲8マスに白が無いか調べます。

もし白があるならば、置いた黒から白の方向に進んでいきます。
その方向に進んでいき、やがて黒にぶつかったら・・・

今まで通ってきた白を黒に上塗りしながら後ろに戻っていきます。
もし黒にぶつからない(=空白にぶつかった)場合は、白を白のまま上塗りしながら後ろに戻っていきます。
これを実現するために、再帰をうまく使えないかと考えました。
というわけでできたソースコードは下になります。
#include<stdio.h>
int field[8][8]={0}; //盤
int flag=0; //ひっくり返すためのフラグ
//表示用関数
void show(){
int i,j;
printf("\n 0 1 2 3 4 5 6 7\n");
for(i=0;i<8;i++){
printf("%d",i);
for(j=0;j<8;j++){
switch(field[i][j]){
case 0:
printf("□");
break;
case 1:
printf("●");
break;
case 2:
printf("○");
break;
default:
printf("NN");
}
}
printf("\n");
}
}
//石の状態を調べ、ひっくり返す関数
int check(int ro,int co,int i,int j,int turn){
if(field[ro][co]==turn){ //自分の石と同じ色
flag=1;
return turn;
}else if(field[ro][co]==0){ //空白
return (turn==1 ? 2 : 1);
}else{ //相手の石と同じ色
field[ro][co]=check(ro+i,ro+j,i,j,turn);
if(flag=1)
return turn;
else
return (turn==1 ? 2 : 1);
}
return -1;
}
//置いた石の周囲8マスを探索する関数
int search(int ro,int co,int turn){
int i,j;
int success=0;
//(ro,co)の周囲8マスを調べる
for(i=-1;i<=1;i++){
for(j=-1;j<=1;j++){
if(!(i==0 && j==0) &&
//ro+i>=0 && ro+i<8 &&
//co+j>=0 && co+j<8 &&
ro+2*i>=0 && ro+2*i<8 &&
co+2*j>=0 && ro+2*i<8 &&
field[ro+i][co+j]!=0 && field[ro+i][co+j]!=turn)
{
field[ro+i][co+j]=check(ro+2*i,co+2*j,i,j,turn);
if(flag==1 && success==0)
success=1;
}
}
}
return success;
}
int main(){
int turn=1; //1:○,2:●
int ro,co;//行(row),列(column)
int success; //探索に失敗したかどうかを判定するフラグ
//初期化
field[3][3]=1;
field[3][4]=2;
field[4][3]=2;
field[4][4]=1;
//-------
while(1)
show();
do{
printf("turn %s:",(turn==1 ? "●":"○"));
scanf("%d %d",&ro,&co);
}while(field[ro][co]!=0);
field[ro][co]=turn;
success=search(ro,co,turn);
if(success==0){ //探索したが、ひっくり返せる場所が無かった場合
printf("cannot put.\n");
field[ro][co]=0;
continue;
}
success=0;
turn=(turn==1 ? 2 : 1);
}
return 0;
}
search関数中のif文の条件中に
//ro+i>=0 && ro+i<8 &&
//co+j>=0 && co+j<8 &&
ro+2*i>=0 && ro+2*i<8 &&
co+2*j>=0 && ro+2*i<8 &&
という記述があります。 この条件によって、端っこのマスを調べたときに配列の範囲外の読み書きをしてしまうことを防ぎます。
初めの2行がコメントアウトされているのは、後の2行でその条件が満たされるからです。
なぜ2*i,2*jなのかは、if文のブロック内で
check(ro+2*i , co+2*j , i , j ,turn);
を呼び出していることが理由です。
if文ブロック内の処理を示した図を下に示します。全体として、「周囲8マスに相手の石があったら、さらにその先のマスを調べろ」という命令になっています。
ソースコードをエディタにコピペして、適当な名前のCファイルで保存する。それをコンパイルして実行してください。
今回、Windows上のgccで動作確認をしました。具体的な方法を以下に示します。
(1)メモ帳を開いて上のソースコードをコピペ
(2)適当なディレクトリに適当な名前のCファイルで保存。
ここではC:\othello\ に作るものとし、名前はothello.cで保存。
(3)コマンドプロンプトを開いて、(2)で作ったCファイルのあるディレクトリに移動する。
ここでは以下のコマンドを実行してC:\othello\に移動する。
cd C:\othello\
(4)gccでコンパイルすると、a.exeが同じディレクトリに作られるので、実行する。
ここでは以下の2つのコマンドを実行する
gcc othello.c
a.exe
起動すると、まず●のターンから始まります。
置く位置は入力状態になったとき、
行の数字 列の数字
のように入力します。
例えば下図の例では、5行3列に石を置きたいので、
5 3
と入力しています。
0以上7以下でない数字や、ひっくり返せないような位置を入力すると、入力のやり直しになるように作りました。
勝敗判定はつけていませんので、終了はCtrl+Cで強制終了するしかないです。
以上です。
初めにも述べましたが、今回のオセロはプログラムとしては効率は悪いと思います。再帰がうまくできたら面白いかなと思って作りました。あくまで「こんなプログラムもあるんだな」程度に思ってください。他の人のオセロのソースコードを見比べてみるのも面白いですね。
※8/12追記:パスのルールをつけ忘れていました…。なのでどちらかが置けなくなったらゲームが進まなくなるというひどい仕様になっています。気が向いたら付け足します。








