7.○×ゲームを作ってみよう

7-4. ○×ゲームの細分化

◇ソースプログラム

まず、前に作った○×ゲームのソースです。無駄が多いというか(わざとですが)これを複数の関数に分けることでバグフィックス・バージョンアップしやすいように改造します。

#include<stdio.h>
main()
{
    int err;
    char c,
    line1[9]="    789\n",
    line2[9]="    456\n",
    line3[9]="    123\n";
    printf("○×ゲーム\n");
    printf("%s%s%s\n",line1,line2,line3);

  while(1){
    while(1){
      err=0;
      printf("○先手:数字キーで指定");
      scanf("%c%*[^\n]",&c);getchar();

      switch(c){
        case '1': if(line3[0]==' ')line3[0]='o';else err=1;break;
        case '2': if(line3[1]==' ')line3[1]='o';else err=1;break;
        case '3': if(line3[2]==' ')line3[2]='o';else err=1;break;
        case '4': if(line2[0]==' ')line2[0]='o';else err=1;break;
        case '5': if(line2[1]==' ')line2[1]='o';else err=1;break;
        case '6': if(line2[2]==' ')line2[2]='o';else err=1;break;
        case '7': if(line1[0]==' ')line1[0]='o';else err=1;break;
        case '8': if(line1[1]==' ')line1[1]='o';else err=1;break;
        case '9': if(line1[2]==' ')line1[2]='o';else err=1;break;
        default : err=1;
      }
      if(err==1)printf("もう一度入力してください");
      else break;
    }
    printf("%s%s%s\n",line1,line2,line3);

    if(line3[0]=='o'&&line3[1]=='o'&&line3[2]=='o' ||
       line2[0]=='o'&&line2[1]=='o'&&line2[2]=='o' ||
       line1[0]=='o'&&line1[1]=='o'&&line1[2]=='o' ||
       line3[0]=='o'&&line2[0]=='o'&&line1[0]=='o'){
       printf("○ 先手の勝ち\n");return 0;}
    else if(
       line3[1]=='o'&&line2[1]=='o'&&line1[1]=='o' ||
       line3[2]=='o'&&line2[2]=='o'&&line1[2]=='o' ||
       line3[0]=='o'&&line2[1]=='o'&&line1[2]=='o' ||
       line3[2]=='o'&&line2[1]=='o'&&line1[0]=='o'){
       printf("○ 先手の勝ち\n");return 0;}
    else if(line1[0]!=' '&&line1[1]!=' '&&line1[2]!=' '&&
            line2[0]!=' '&&line2[1]!=' '&&line2[2]!=' '&&
            line3[0]!=' '&&line3[1]!=' '&&line3[2]!=' '){
       printf("引き分け\n");return 0;}

    while(1){
      err=0;
      printf("×後手:数字キーで指定");
      scanf("%c%*[^\n]",&c);getchar();

      switch(c){
        case '1': if(line3[0]==' ')line3[0]='x';else err=1;break;
        case '2': if(line3[1]==' ')line3[1]='x';else err=1;break;
        case '3': if(line3[2]==' ')line3[2]='x';else err=1;break;
        case '4': if(line2[0]==' ')line2[0]='x';else err=1;break;
        case '5': if(line2[1]==' ')line2[1]='x';else err=1;break;
        case '6': if(line2[2]==' ')line2[2]='x';else err=1;break;
        case '7': if(line1[0]==' ')line1[0]='x';else err=1;break;
        case '8': if(line1[1]==' ')line1[1]='x';else err=1;break;
        case '9': if(line1[2]==' ')line1[2]='x';else err=1;break;
        default : err=1;
      }
      if(err==1)printf("もう一度入力してください");
      else break;
    }
    printf("%s%s%s\n",line1,line2,line3);

    if(line3[0]=='x'&&line3[1]=='x'&&line3[2]=='x' ||
       line2[0]=='x'&&line2[1]=='x'&&line2[2]=='x' ||
       line1[0]=='x'&&line1[1]=='x'&&line1[2]=='x' ||
       line3[0]=='x'&&line2[0]=='x'&&line1[0]=='x'){
       printf("× 後手の勝ち\n");return 0;}
    else if(
       line3[1]=='x'&&line2[1]=='x'&&line1[1]=='x' ||
       line3[2]=='x'&&line2[2]=='x'&&line1[2]=='x' ||
       line3[0]=='x'&&line2[1]=='x'&&line1[2]=='x' ||
       line3[2]=='x'&&line2[1]=='x'&&line1[0]=='x'){
       printf("× 後手の勝ち\n");return 0;}
    else if(line1[0]!=' '&&line1[1]!=' '&&line1[2]!=' '&&
            line2[0]!=' '&&line2[1]!=' '&&line2[2]!=' '&&
            line3[0]!=' '&&line3[1]!=' '&&line3[2]!=' '){
       printf("引き分け\n");return 0;}
  }
}

◇同じような処理を一つの機能としてまとめる

いやーホントにコピー&ペーストで引き伸ばしたようなプログラムで長いですね(全体像が把握しにくいので印刷したらA4で2ページ←紙の無駄)

それで殆ど同じようなことをしているのは2つあります。

・キーボード入力
・勝敗判定

です。

◇改造プログラム

以下は前回のソースを機能別に関数化して全体を短くしてみました。
これで修正などコードを変更する時、変更箇所が少なくて済みます。
あとこのプログラムで改良するとしたら・・・
 ・デファイン等を使って特殊用途の定数を分かり安くする
 ・デバッグモードの追加
 ・適度なコメント(関数仕様の簡単なマニュアル等)
 ・構造体&ポインタの利用
があげられます。

#include<stdio.h>

int input(char, char);
int judge(char);

char line1[9]="    789\n",
     line2[9]="    456\n",
     line3[9]="    123\n";

main()
{
    char c,turn='o';
    printf("○×ゲーム\n");
    printf("%s%s%s\n",line1,line2,line3);

  while(1){
    while(1){
      if(turn=='o')printf("○先手:数字キーで指定");
      else printf("×後手:数字キーで指定");
      scanf("%c%*[^\n]",&c);getchar();
      if(input(c,turn))printf("もう一度入力してください");
      else break;
    }
    printf("%s%s%s\n",line1,line2,line3);
    if(judge(turn))return;
    if(turn=='o')turn='x';
    else turn='o';
  }
}

int input(char c, char piece)
{
  int flg=0;
  switch(c){
    case '1': if(line3[0]==' ')line3[0]=piece ;else flg=1;break;
    case '2': if(line3[1]==' ')line3[1]=piece ;else flg=1;break;
    case '3': if(line3[2]==' ')line3[2]=piece ;else flg=1;break;
    case '4': if(line2[0]==' ')line2[0]=piece ;else flg=1;break;
    case '5': if(line2[1]==' ')line2[1]=piece ;else flg=1;break;
    case '6': if(line2[2]==' ')line2[2]=piece ;else flg=1;break;
    case '7': if(line1[0]==' ')line1[0]=piece ;else flg=1;break;
    case '8': if(line1[1]==' ')line1[1]=piece ;else flg=1;break;
    case '9': if(line1[2]==' ')line1[2]=piece ;else flg=1;break;
    default : flg=1;
  }
  return flg;
}

int judge(char piece)
{
  if(line3[0]==piece &&line3[1]==piece &&line3[2]==piece ||
     line2[0]==piece &&line2[1]==piece &&line2[2]==piece ||
     line1[0]==piece &&line1[1]==piece &&line1[2]==piece ||
     line3[0]==piece &&line2[0]==piece &&line1[0]==piece ){
     if(piece=='o'){printf("○ 先手の勝ち\n");return 1;}
     else{printf("○ 後手の勝ち\n");return 1;}
  }
  else if(
     line3[1]==piece &&line2[1]==piece &&line1[1]==piece ||
     line3[2]==piece &&line2[2]==piece &&line1[2]==piece ||
     line3[0]==piece &&line2[1]==piece &&line1[2]==piece ||
     line3[2]==piece &&line2[1]==piece &&line1[0]==piece ){
     if(piece=='o'){printf("○ 先手の勝ち\n");return 1;}
     else{printf("× 後手の勝ち\n");return 1;}
  }
  else if(line1[0]!=' '&&line1[1]!=' '&&line1[2]!=' '&&
          line2[0]!=' '&&line2[1]!=' '&&line2[2]!=' '&&
          line3[0]!=' '&&line3[1]!=' '&&line3[2]!=' '){
     printf("引き分け\n");return 1;}
  return 0;
}

◇解説

改造した部分だけ解説します。
まず変更箇所として配列をメイン関数内から外に出しグローバル変数配列にしました。これはinput()やjudge()でも使うためです。(スコープ問題)
あまりグローバル領域に書き換えする変数を宣言するのはよろしくないのですがまだポインターを使わない段階なので全ての関数からアクセスできるようにしています。
次に入力処理のinput()、判定処理のjudge()を作りメインから呼び出すようにしています。これで随分コンパクトになりました。
最後に、先手・後手の変数turnを用意しif文を使って判別する事によりループ内も約半分のコード量にしました。今回のif文はあまり良い例ではありません。呼び出した関数の戻り値でエラー・勝敗判定しているのですが統一性も無く仕様も書いていないので関数定義を見ないと分からないです。
ここの部分はぜひ関数定義でデファインを使って戻り値に意味を付加してif文の部分にはコメントを入れるべきです。

scanf(“%c%*[^\n]”,&c);getchar(); /* バッファのお掃除(^-^;) */

も初めて見る人には意味わからないかもしれないのでコメント付けておいた方が良いですね。
・・・しかし偶然とは言えこんな処理法を発見するとは思わなかった(^^;;

次章
8.関数