• ベストアンサー
※ ChatGPTを利用し、要約された質問です(原文:オセロを作成 助言お願いします・・・)

オセロ作成に関する助言をお願いします

このQ&Aのポイント
  • 初心者向けのC言語を使用したオセロ作成の助言をお願いします。
  • 関数を使わない方法でのオセロ作成に困っています。
  • 偶数か奇数かの判定で石の色を変える方法についても教えてください。

質問者が選んだベストアンサー

  • ベストアンサー
  • 7o8
  • ベストアンサー率55% (5/9)
回答No.5

> 関数はやはり使ったほうがいいですよね。 Cだけでなく、他の言語でもそうですが、なるべく簡素に、単機能にを 目指して関数化すべきです。 一つ一つが小さくなればなるほどデバッグも簡易になっていきます。 ご存じだとは思いますが、main()関数の外にて変数宣言すると それはグローバル変数になります。 グローバル変数は一見、非常に便利に見えますが、プログラムが 大きくなるとどこでどう値が変化しているか検討がつかなく なる可能性があります。 たいていの場合、グローバル変数はプログラムの核となるものだけに 設定します。 今回の場合はbordですね。 私のサンプルはbordもmain()内で宣言しましたが、この変数は グローバル変数としても問題はないです。 #その場合、関数の引数、及び、関数内での使用方法の変更が #必要ですが。 ただし、iやjはテンポラリ的な役割でしかないものですから、 それをグローバル変数にするのは好ましくないです。 わかりやすくいうと、main()関数内でiやjを使用したforループを実行する ようになっていますが、その中で やはり iやjを使用したforループを持つ 別の関数の呼び出しを行うと思わぬ結果になることとなるということです。 > さすがにmain関数だけじゃわかりにくいですよね。 > コメントの書き方なども参考にさせていただきます。 基本的にmain()関数はコンパクトにまとめるべきです。 私のサンプルでも長いくらいで、ゲーム本体は別関数にまとめるべきだったなぁと 反省してます。 サンプルは一旦提示させて頂きましたが、より可読性の高いプログラムの 作成のために、以下ポイントで修正していただければ、と思います。 1.bord[][]をグローバル変数化する。 2.縦・横それぞれ8ですが、「#define XMAX 8」「#define YMAX 8」   等としてdefine文による定数を使用、直接数値を使用しないようにする。 3.ゲーム実行部分の関数化 4.ゲーム盤表示部分の関数化 5.行き詰まり自動判定 6.両者行き詰まり判定 7.勝敗表示 5~7は改善、というより、足りない機能の追加ですが。(^^; 5と6はcheck()関数をうまく使えば可能だと思います。 7は集計して表示だけですよね。 きっとできるかと思います。 私自身は仕事上でのプログラム開発から離れて10年たっており、 時々趣味程度に、忘れないようにCを触っています。 今回は ちょっとだけ頭の体操って感じで楽しませていただきました。 お楽しみを奪ってしまったかもしれませんが、課題はあるということで.....(^^;;;;; 因みにreverse_sub()内の「int c」は不要な宣言ですので消してください(^_^;;;;;

kokakoara
質問者

お礼

またまた回答ありがとうございます。 >グローバル変数は一見、非常に便利に見えますが、プログラムが >大きくなるとどこでどう値が変化しているか検討がつかなく >なる可能性があります。 なんだって!そんなこと本には書いてなかったぞ! そうなんですか・・・勉強になりました。 iやjなどはローカル変数にするべきなんですね。 >基本的にmain()関数はコンパクトにまとめるべきです。 >私のサンプルでも長いくらいで、ゲーム本体は別関数にまとめるべき>だったなぁと反省してます。 main関数はコンパクトにですか。 これも頭に刻んでおきます。 ではmain関数はほかの関数の呼び出しとちょこっとの機能だけでいいんですね。勉強になります・・・ >1.bord[][]をグローバル変数化する。 >2.縦・横それぞれ8ですが、「#define XMAX 8」「#define YMAX >8」等としてdefine文による定数を使用、直接数値を使用しないよう>する。 >3.ゲーム実行部分の関数化 >4.ゲーム盤表示部分の関数化 >5.行き詰まり自動判定 >6.両者行き詰まり判定 >7.勝敗表示  2.ですが私も最初はマクロを使おうと思ったんですが「マクロ」と聞くだけでどうしても苦手意識が・・・ 5,6,7は時間をかけてじっくりしていきます。 これ以上世話になると将来プログラマを目指している私として何か大事なものが欠けそうな気がするので、オセロの件での質問は終了とさせていただきます。ソースを書いてくださったり初心者相手にとてもわかりやすく回答してくれました。 本当にありがとうございました。 またお世話になるかもしれませんがそのときはよろしくお願いします。 ※サンプルソースは、大事な参考として使わせていただきます。 ありがとうございました。

その他の回答 (4)

  • 7o8
  • ベストアンサー率55% (5/9)
回答No.4

すみません。ソースは添付できないんですね.....(^^; #include<stdio.h> // 概要 // check()関数から呼び出しされるサブ関数 // 指定されたbord[y][x]から隣接するxx,yy方向の駒を // ひっくり返せるかチェック。 // xx,yyはそれぞれ -1,0,1の何れかであることが大前提 // // 返値 0:存在無し、1:存在する int check_sub(int *bord,int x,int y,int xx,int yy,int player) { int c,flag; flag=0; //ひっくり返したかどうかフラグ(^-^; for (;;) { //x,yのxx,yy方向に移動 x+=xx; y+=yy; //x,yが盤の範囲外に出たらそのxxyy方向にはひっくり返せない if (x<0 || y <0 || x>7 || y>7) return 0; c=bord[y*8+x]; //x,y上に駒がなければひっくり返せない。 if(c==0) return 0; //自分の駒を見つけた時 if( c == player+1 ) { if(flag==0) return 0; // 隣は自分の駒でした。 else return 1; // ひっくり返せる駒があった! }else flag=1; // 相手の駒を見つけたときはフラグを立てる } } // // 概要 // check_sub()関数を使用して隣接する8方向にひっくり返せる // パターンがあるかどうかをチェック // 左上から時計回りにチェックし、下位ビットからそのパターン有無 // 結果をセットする。 // // 返値 0~255(0:置けない、255:全方向ひっくり返る) int check(int *bord,int x,int y,int player) { int houkou; //houkouをリセット houkou=0; if(check_sub(bord,x,y,-1,-1,player)) houkou |= 1; // 1.左上方向チェック if(check_sub(bord,x,y, 0,-1,player)) houkou |= 2; // 2.上方向チェック if(check_sub(bord,x,y, 1,-1,player)) houkou |= 4; // 3.右上方向チェック if(check_sub(bord,x,y, 1, 0,player)) houkou |= 8; // 4.右方向チェック if(check_sub(bord,x,y, 1, 1,player)) houkou |= 16; // 5.右下方向チェック if(check_sub(bord,x,y, 0, 1,player)) houkou |= 32; // 6.下方向チェック if(check_sub(bord,x,y,-1, 1,player)) houkou |= 64; // 7.左下方向チェック if(check_sub(bord,x,y,-1, 0,player)) houkou |=128; // 8.左上方向チェック return houkou; } // // 概要 // xx,yy方向にて指定された方向をひっくり返す。 // 自分の駒を見つけるまで繰り返し実行する。 // // 返値 無し void reverse_sub(int *bord,int x,int y,int xx,int yy,int player) { int c; for (;;) { //x,yのxx,yy方向に移動 x += xx; y += yy; //自分の駒を見つけた時 if( bord[y*8+x] == player+1 ) return; // ひっくり返し完了 //ひっくり返し実行 bord[y*8+x] = player+1; } } // // 概要 // reverse_sub()関数を使用してフラグにて指定された方向をひっくり返す。 // フラグはcheck()関数で得られたものを使用することが大前提。 // そのため、この関数の中ではエラーチェックは行わない。 // // 返値 無し void reverse(int *bord,int x,int y,int player,int flag) { if(flag & 1) reverse_sub(bord, x, y,-1,-1, player); // 1.左上方向ひっくり返し if(flag & 2) reverse_sub(bord, x, y, 0,-1, player); // 2.上方向ひっくり返し if(flag & 4) reverse_sub(bord, x, y, 1,-1, player); // 3.右上方向ひっくり返し if(flag & 8) reverse_sub(bord, x, y, 1, 0, player); // 4.右方向ひっくり返し if(flag & 16) reverse_sub(bord, x, y, 1, 1, player); // 5.右下方向ひっくり返し if(flag & 32) reverse_sub(bord, x, y, 0, 1, player); // 6.下方向ひっくり返し if(flag & 64) reverse_sub(bord, x, y,-1, 1, player); // 7.左下方向ひっくり返し if(flag & 128) reverse_sub(bord, x, y,-1, 0, player); // 8.左上方向ひっくり返し } int main (void) { char *tate[8]={"0","1","2","3","4","5","6","7"}; char end; int bord[8][8]; int x,y; int i,j,flag,cnt; int player; // 0.ボードの初期化 for(i=0;i<8;i++) for(j=0;j<8;j++) bord[i][j]=0; bord[3][3]=1; bord[4][4]=1; bord[3][4]=2; bord[4][3]=2; cnt=4; //既に4つ置かれている player=0; for(;cnt <64;){ // 1.盤の表示 printf(" 01234567\n"); for(i=0;i<8;i++){ printf(tate[i]); for(j=0;j<8;j++){ if(bord[i][j] == 0) printf("*"); else if(bord[i][j] == 1) printf("●"); else printf("○"); } printf("\n"); } // 2.メッセージ出力&値入力 if(player%2==0) printf("白の番です。\n"); else printf("黒の番です。\n"); printf("y座標とx座標どちらも100と入力すると相手のターンにします。(-1で終了)\n"); printf("y座標を入力してください。(縦軸)"); scanf_s("%d", &y); printf("x座標を入力してください。(横軸)"); scanf_s("%d", &x); // 3.終了処理 if(x==-1&&y==-1) { printf("終了コマンドが入力されました。本当に終了しますか?(Y or N)"); for(;;) { end=getchar(); if(end == 'y' || end == 'Y' || end == 'n' || end == 'N') break; } if(end=='y' || end=='Y') { printf("処理を終了します。\n"); break; } if(end=='n' || end=='N') { printf("処理を続行します。\n"); continue; } } // 4.x=100,y=100の場合、石を置く権利を相手に移す if(x==100 && y==100) { printf("\n石を置く権利を相手に渡します。\n\n"); player ^= 1; // 相手変更 0←→1 continue; } // 5.エラー処理(入力された数字が許容範囲外) if(x<0 || x>=8 || y<0 || y>=8 ) { printf("\n許容範囲外です。\n"); printf("もう一度入力してください。\n\n"); } // 6.エラー処理(同じところには置けない) if(bord[y][x]!=0) { printf("\n置かれてるよ!\n"); continue; } // 7.ひっくり返せるかチェック flag=check(&bord[0][0], x, y, player); // flagには方向が入る if(flag==0){ // 6-1.ひっくり返せない場合はメッセージ printf("\nそこには置けないんだな\n\n"); continue; }else { bord[y][x]=player+1; reverse(&bord[0][0], x, y, player, flag); player ^= 1; // 相手変更 0←→1 cnt++; // 置いた駒の数カウントアップ } } //loop終了地点 printf("終わり!\n"); return 0; }

kokakoara
質問者

お礼

なんとソースまで書いてくれましたか! 本当にありがとうございました! ここまで親切な回答者がいるとは思いませんでした。 関数はやはり使ったほうがいいですよね。 さすがにmain関数だけじゃわかりにくいですよね。 コメントの書き方なども参考にさせていただきます。 後ほどじっくり拝見させていただきます。もしかしたらまたお世話になるかもしれませんwwそうならないよう努力します。 本当にお世話になりました。 心から感謝します。

  • 7o8
  • ベストアンサー率55% (5/9)
回答No.3

VC++であればまずはデバッガを使用できるようにしなければ もったいないですよ。 F11を押せば1行1行変数の値を見ながら実行できます。 プログラムが長い場合でもブレークポイントまでは一気に実行、 その後1行1行実行とか..... とりあえず、私の方でも作ってみました。 ある程度動作確認はしているので参考にしてみてはどうでしょうか?

kokakoara
質問者

お礼

何度も回答ありがとうございます。 どの回答もご丁寧で、私の知識がなくて手間を取らせてしまいました。  デバッガの存在は知っていましたがどのように使うかなどはしならなかったのでためになりました。

  • 7o8
  • ベストアンサー率55% (5/9)
回答No.2

ID変えました。 う~む.... とりあえずは今回はコードを示さず、言葉だけで。 2が設定されないのは 設定される条件が bord[y][x]が0であり、且つ、1であること、 だからです。 これでは2を設定できるはずがないです。 他に気付いた点を.... ・//黒か白か判定// のif文の中で どちらのプレーヤーでも  強制的にbord[i][j]に対し0か1を代入している  因みに「i」と「j」はこの時点でそれぞれ 共に 8になってるので  bord[8][8]に1を入れていることになります。  bordはMAXで [7][7]ですので、実はセグメンテーションエラー(の類)が  発生してもおかしくないし、少なくともメモリ破壊は実行しています。(^^;  ※iとjが8を指しているのはその前のfor文の終了条件だからです。 ・許容範囲外の数値が入力された場合は continueしてあげましょう。 ・細かいところで無駄がたくさん.....(^^; > >デバッグで値の変化ぐらいは確認しましょう..... > この一番肝心なところがわかりません。。。 Cコンパイラは何を使用していますでしょうか? VC++であればデバッグというメニューがあります。 linux系だとそういう環境はない(私は知らない)ので printf()文を途中に挟んで、どこを実行しているのか? 変数にどのような値が設定されているのかが確認できます。 フラグの件は覚えればプログラムはすっきりします。 それはまたおいおい....ということで。

kokakoara
質問者

補足

回答ありがとうございます。 自分にはまだ早い課題なのでしょうか? よく理解できません。 ですができる範囲のことはしました。 あきらめずに頑張ります!(べんきょうしながら) ちなみに使用しているIDEはVC++です。 以下ソースです。 #include<stdio.h> int bord[8][8]; int x,y; int i,j,l,a; int player; char end; int main (void) { //ボードの初期化// for(i=0;i<8;i++){ for(j=0;j<8;j++){ bord[i][j]=0; } } bord[3][3]=1; bord[4][4]=1; bord[3][4]=2; bord[4][3]=2; //bordの表示// player=0; for(;;){ //何度も表示 printf("01234567\n"); for(i=0;i<8;i++){ for(j=0;j<8;j++){ if(bord[i][j] == 0){ printf("*"); } else if(bord[i][j] == 1) { printf("●"); } else { printf("○"); } } printf("\n"); } //黒か白か判定// if(player%2==0){ bord[i][j]=1; printf("y座標とx座標に-1と入力すると処理を終了します。\n"); printf("黒の番です。\n"); printf("y座標を入力してください。(縦軸)"); scanf_s("%d", &y); printf("x座標を入力してください。(横軸)"); scanf_s("%d", &x); } if(player%2==1){ bord[i][j]=0; printf("y座標とx座標に-1と入力すると処理を終了します。\n"); printf("白の番です。\n"); printf("y座標を入力してください。(縦軸)"); scanf_s("%d", &y); printf("x座標を入力してください。(横軸)"); scanf_s("%d", &x); } //ひっくりかえす// //終了処理 if(x==-1&&y==-1) { printf("終了コマンドが入力されました。本当に終了しますか?YまたはNを入力してください。\n"); scanf("%s",&end); if(end=='y' || end=='Y') { printf("処理を終了します。\n"); break; } if(end=='n' || end=='N') { printf("処理を続行します。\n"); continue; } } //エラー処理(入力された数字が許容範囲外) if(x<0 || x>=8 || y<0 || y>=8 ) { printf("許容範囲外です。\n"); printf("もう一度入力してください。\n"); continue; } //エラー処理(同じところには置けない) if(bord[y][x]!=0) { printf("\n"); printf("置かれてるよ!もういちど入力してください。\n"); continue; } //ひっくり返す if(x>=0 && x<8 && y>=0 && y<8 && bord[y][x]==0){ if(player%2==0){ //偶数なら黒い石を置く bord[y][x]=1; } if(player%2==1){ //奇数なら白い石を置く bord[y][x]=2; } printf("\n"); printf("石が置かれました。\n"); player++; } } //無限loop終了地点 return 0; }

回答No.1

そもそもおかしいところは1カ所ありますね。 bord[y][x]に1を設定した場合、その直後で必ず2にしています。 ---------------------------------------------------------------- //ひっくり返す// if(x>=0 && x<8 && y>=0 && y<8 && bord[y][x]==0){ if(bord[y][x] == 0){ bord[y][x]=1; } ← ここに elseがないのが問題 if(bord[y][x]==1) { bord[y][x]=2; } } ---------------------------------------------------------------- デバッグで値の変化ぐらいは確認しましょう..... あと、オセロのルールに則れば、 1.必ずひっくり返せる場所にしかおけない   →おけるかどうかの判定が必要。    入力エラーは入力直後に判定、間違っていても    おける以上、相手に権利を移させない。    ギブアップで-1,-1を入力すると手動で権利を移す 2.置いたらひっくり返す。   →ひっくり返せる方向は8方向    私ならどの方向にひっくり返せるかのチェック関数を作成。    1バイトフラグ(8bit)にて返す。(1.でも呼ぶ)    ひっくり返す際はフラグに従い、設置位置隣接位置から目的の    駒までひっくり返す。 ついでにいくつか気になった点を挙げておきます。 ・プログラムですが、X,Y座標入力において  以下の判定実行は scanf()の直後で実行すべきです。  ------------------------------------------  //エラー処理//  if(x<0 || x>=8 || y<0 || y>=8 )    :  ------------------------------------------    下記判定は上記判定の前段階で行っているのですが、  同一if文内の 判定順序は C言語の処理系によっては  後ろから....なんてことが多々あります。  bord[y][x]のように、配列にx,yを使用するのは必ず  値チェックを先に行いましょう。  (xに10000とか入力すると例外エラーの可能性が....)  ------------------------------------------  if(x>=0 && x<8 && y>=0 && y<8 && bord[y][x]==0){  ------------------------------------------  そもそも、scanf()直後にエラー処理を行っていれば、  「x>=0 && x<8 && y>=0 && y<8 &&」は不要ですよね。 ・入力に失敗しただけで相手に権利が移ってしまうのは厳しくないですか?  (そのように見えます。違っていたらすみません。) ・playerがカウントアップしていくのですが、うまく使えば終了条件に  使用できますよね。  今は駒を置くのに失敗してもカウントアップされてしまうようなので  64になっても必ず終了しているとは限りませんが、そこをうまく  調整すると終了条件に使用できるようになるかと思います。  (今は結局 CTRL+Cで終了させるように見えます.....)

kokakoara
質問者

補足

こんなに詳細に回答してくれるかたがいるとは思いませんでした。 ありがとうございます。 >必ずひっくり返せる場所にしかおけない >→おけるかどうかの判定が必要 これはクリアしました。 >入力エラーは入力直後に判定、間違っていても >おける以上、相手に権利を移させない。 >ギブアップで-1,-1を入力すると手動で権利を移す これもクリアしました。 >1バイトフラグ(8bit)にて返す。(1.でも呼ぶ) >ひっくり返す際はフラグに従い、設置位置隣接位置から目的の >駒までひっくり返す。 お手上げです。 >そもそもおかしいところは1カ所ありますね。 >bord[y][x]に1を設定した場合、その直後で必ず2にしています。 >-------------------------------------------------------------- >//ひっくり返す// >if(x>=0 && x<8 && y>=0 && y<8 && bord[y][x]==0){ >if(bord[y][x] == 0){ >bord[y][x]=1; >} ← ここに elseがないのが問題 >if(bord[y][x]==1) >{ >bord[y][x]=2; >} >} >-------------------------------------------------------------- >デバッグで値の変化ぐらいは確認しましょう..... この一番肝心なところがわかりません。。。 ------------------------------------------------------------ //ひっくり返す if(x>=0 && x<8 && y>=0 && y<8 && bord[y][x]==0){ if(bord[y][x] == 1){ bord[y][x]=2; }else{ if(bord[y][x]==0) bord[y][x]=1; } printf("\n"); printf("石が置かれました。\n"); player++; } ------------------------------------------------------------ こんな感じになったんですけどやはりずっと同じ石しか置けません。 終了条件ですかそれはできそうですね。 --------------------------以下すべてのソース------------------- #include<stdio.h> int bord[8][8]; int x,y; int i,j,l,a; int player; int main (void) { //ボードの初期化// for(i=0;i<8;i++){ for(j=0;j<8;j++){ bord[i][j]=0; } } bord[3][3]=1; bord[4][4]=1; bord[3][4]=2; bord[4][3]=2; //bordの表示// player=0; for(;;){ //何度も表示 printf("01234567\n"); for(i=0;i<8;i++){ for(j=0;j<8;j++){ if(bord[i][j] == 0){ printf("*"); } else if(bord[i][j] == 1){ printf("●"); } else{ printf("○"); } } printf("\n"); } //黒か白か判定// if(player%2==0){ bord[i][j]=1; printf("黒の番です。\n"); printf("y座標を入力してください。(縦軸)"); scanf_s("%d", &y); printf("x座標を入力してください。(横軸)"); scanf_s("%d", &x); } if(player%2==1){ bord[i][j]=0; printf("白の番です。\n"); printf("y座標とx座標どちらも100と入力すると相手のターンにします。\n"); printf("y座標を入力してください。(縦軸)"); scanf_s("%d", &y); printf("x座標を入力してください。(横軸)"); scanf_s("%d", &x); } //ひっくりかえす// //石を置く権利を相手に移す if(x==100 && y==100){ printf("\n"); printf("石を置く権利を相手に渡します。\n"); player++; continue; } //エラー処理(入力された数字が許容範囲外) if(x<0 || x>=8 || y<0 || y>=8 ) { printf("許容範囲外です。\n"); printf("もう一度入力してください。\n"); } //エラー処理(同じところには置けない) if(bord[y][x]!=0) { printf("\n"); printf("置かれてるよ!\n"); } //ひっくり返す if(x>=0 && x<8 && y>=0 && y<8 && bord[y][x]==0){ if(bord[y][x] == 1){ bord[y][x]=2; }else{ if(bord[y][x]==0) bord[y][x]=1; } printf("\n"); printf("石が置かれました。\n"); player++; } } //無限loop終了地点 return 0; } ---------------------------------ソース終了---------------------

関連するQ&A