授業/C言語基礎/じゃんけんゲーム のバックアップの現在との差分(No.2)


  • 追加された行はこの色です。
  • 削除された行はこの色です。
*じゃんけんゲーム [#i2a2805d]

#freeze
これまでに勉強したことを使って、次のような簡単なじゃんけんゲームを作ります。
 プレイヤーがコンピューターと勝負がつくまでじゃんけんをします。
 プレイヤーがコンピューターと勝負がつくまでじゃんけんをする。


*実行結果をイメージする [#p272a86c]

[[数当てゲーム>授業/C言語基礎/数当てゲーム]]のところでも書いたように、プログラムを作り始める前に、まず、プログラムの実行結果をイメージします。

じゃんけんでは、グーかチョキかパーを出しますが、プレイヤーが選ぶときには、グーを1、チョキを2、パーを3として入力させることにしましょう。
じゃんけんでは、グーかチョキかパーを出しますが、プレイヤーが選ぶときには、グーを 1、チョキを 2、パーを 3 として入力させることにしましょう。

プレイヤーが入力した後にコンピューターが自分の手を選ぶのはズルいので、プレイヤーに入力を要求する前にコンピューターが自分の手を選ぶことにしましょう。
とりあえず、グーを選んだとして話を進めます。
とりあえず、コンピューターがグーを選んだとして話を進めます。

プレイヤーに入力を要求するメッセージは、じゃんけんらしく、次のような感じにしましょう。
 じゃんけんぽん!(1: グー、2: チョキ、3: パー):

プレイヤーが手を選んで入力すると、プレイヤーが選んだ手とコンピューターが選んだ手を出力します。
ここでは、プレイヤーがパーを選んだとします。
ここでは、プレイヤーがパーを選んで 3 を入力したとします。
 じゃんけんぽん! (1: グー、2: チョキ、3: パー):
 3
 あなた: 3、コンピューター: 1(1: グー、2: チョキ、3: パー)

プレイヤーがパーを選んだ場合は、プレイヤーの勝ちなので、「あなたの勝ち」というメッセージを表示し、プログラムを終了します。
 じゃんけんぽん! (1: グー、2: チョキ、3: パー):
 3
 あなた: 3、コンピューター: 1(1: グー、2: チョキ、3: パー)
 あなたの勝ち!

プレイヤーがチョキを選んで2と入力したとすると、プレイヤーの負けなので、「あなたの負け」というメッセージを表示し、プログラムを終了します。
プレイヤーがチョキを選んで 2 を入力したとすると、プレイヤーの負けなので、「あなたの負け」というメッセージを表示し、プログラムを終了します。
 じゃんけんぽん! (1: グー、2: チョキ、3: パー):
 2
 あなた: 2、コンピューター: 1(1: グー、2: チョキ、3: パー)
 あなたの負け!

プレイヤーがグーを選んで1と入力したとすると、あいこなので、もう一度コンピューターが自分の手を選び、プレイヤーに入力を要求するメッセージを表示します。
プレイヤーがグーを選んで 1 を入力したとすると、あいこなので、もう一度コンピューターが自分の手を選び、プレイヤーに入力を要求するメッセージを表示します。
 じゃんけんぽん! (1: グー、2: チョキ、3: パー):
 1
 あなた: 1、コンピューター: 1(1: グー、2: チョキ、3: パー)
 あいこでしょ! (1: グー、2: チョキ、3: パー):

これを、勝負がつくまで繰り返します。

このように、実行結果をイメージすることで、プログラムの中で、どんな処理が行われるのか、どんな処理が繰り返されるのかが、ある程度わかります。


*フローチャートを作る [#p5d52f9c]

次に、イメージした実行結果になるように、プログラムのフローチャート(またはアルゴリズム)を考えます。

このプログラムも、[[数当てゲーム>授業/C言語基礎/数当てゲーム]]と同じように、二つの部分に分けることができます。
+コンピューターが自分の手を選ぶ
+プレイヤーが自分の手を選ぶ

ただし、[[数当てゲーム>授業/C言語基礎/数当てゲーム]]とは違って、1と2の両方を(勝負がつくまで)繰り返し実行します。

プレイヤーの手はキーボードから入力してもらうので、繰り返し行われる処理は次のようになります。
+「じゃんけんぽん」と出力する
+rand関数で自分の手を選ぶ
+scanf関数でキーボードから入力する
--引き分けなら、「あいこでしょ」と出力し、2へ戻る
--そうでないなら、結果を出力し、プログラムを終了する

この流れを、そのままフローチャートにすると、次のようになります(フローチャート1)。
#ref(./janken1.png,50%)
#ref(./janken1.png,nolink,50%)

繰り返し条件の前置判定を行う方がwhile文を作りやすいので、これを次のように書き換えます(フローチャート2)。
#ref(./janken2.png,50%)
ただし、こうすると、繰り返し処理の前に重複する処理ができてしまいます。


*プログラムを作る [#fa42e20f]

フローチャートができたら、プログラムを作ります。


**無限ループとbreak文の組み合わせ [#e2ac4d0c]

まず初めに、「じゃんけんぽん」と出力して、無限ループを用意します。
#geshi(c){{
#include <stdio.h>

int main(void) {
  printf("じゃんけんぽん!(1: グー、2: チョキ、3: パー):\n");
  while (1) {

  }


  return 0;
}
}}

無限ループに入ると、乱数を使って自分の手を選びます。
#geshi(c){{
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void) {
  int r;

  srand((unsigned int) time(NULL));
  srand((unsigned int) time(NULL));  // 乱数のシード

  printf("じゃんけんぽん!(1: グー、2: チョキ、3: パー):\n");
  while (1) {
    r = rand() % 3 + 1;

  }


  return 0;
}
}}
ここでは、1から3までの整数を乱数で生成し、1ならグー、2ならチョキ、3ならパーとします。
rand関数、srand関数、time関数の使い方については、[[計算ゲーム>授業/C言語基礎/計算ゲーム]]を復習しましょう。

その次に、キーボードから入力する部分を加えます。
#geshi(c){{
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void) {
  int r, h;

  srand((unsigned int) time(NULL));
  srand((unsigned int) time(NULL));  // 乱数のシード

  printf("じゃんけんぽん!(1: グー、2: チョキ、3: パー):\n");
  while (1) {
    r = rand() % 3 + 1;
    scanf("%d", &h);
    printf("あなた: %d、コンピューター: %d(1: グー、2: チョキ、3: パー)\n");
    printf("あなた: %d、コンピューター: %d(1: グー、2: チョキ、3: パー)\n", h, r);

  }


  return 0;
}
}}
ここで、h はプレイヤーが入力した数を記憶しておくための変数です。
(同時に、新しく登場した変数 a の宣言も追加しています。)
また、入力された手とコンピューターが選んだ手を画面に出力しています。

ここで、引き分けでない場合は、無限ループを中断します。
そうでない場合(引き分けの場合)は、「あいこでしょ」と出力して、無限ループの先頭に戻ります。
#geshi(c){{
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void) {
  int r, h;

  srand((unsigned int) time(NULL));
  srand((unsigned int) time(NULL));  // 乱数のシード

  printf("じゃんけんぽん!(1: グー、2: チョキ、3: パー):\n");
  while (1) {
    r = rand() % 3 + 1;
    scanf("%d", &h);
    printf("あなた: %d、コンピューター: %d(1: グー、2: チョキ、3: パー)\n");
    printf("あなた: %d、コンピューター: %d(1: グー、2: チョキ、3: パー)\n", h, r);
    if (h != r) { break; }
    printf("あいこでしょ!(1: グー、2: チョキ、3: パー):\n");
  }


  return 0;
}
}}

無限ループが中断されたら(勝負がついたら)、結果を判定して、出力します。
#geshi(c){{
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void) {
  int r, h;

  srand((unsigned int) time(NULL));
  srand((unsigned int) time(NULL));  // 乱数のシード

  printf("じゃんけんぽん!(1: グー、2: チョキ、3: パー):\n");
  while (1) {
    r = rand() % 3 + 1;
    scanf("%d", &h);
    printf("あなた: %d、コンピューター: %d(1: グー、2: チョキ、3: パー)\n");
    printf("あなた: %d、コンピューター: %d(1: グー、2: チョキ、3: パー)\n", h, r);
    if (h != r) { break; }
    printf("あいこでしょ!(1: グー、2: チョキ、3: パー):\n");
  }

  if (h == r - 1 || (h == 3 && r == 1)) {
  if (r == 1 && h == 3 || r == 2 && h == 1 || r == 3 && h == 2) {
    printf("あなたの勝ち!\n");
  } else {
    printf("あなたの負け!\n");
  }

  return 0;
}
}}
結果を判定するif文の条件は、勝ちのパターン3通りをすべて列挙して論理和 || でつないでも作ることができます。

これで完成です(プログラム1)。


**演習1 [#g7b4befb]
プログラム1を作成し、実行結果を確認せよ。



**前置判定while文 [#pcfb4868]
*前置判定while文でプログラムを作る [#pcfb4868]

繰り返し(for文、while文、do-while文)でbreakやcontinueを濫用すると、プログラムの流れを理解することが難しくなります。
そのため、できる限りbreakやcontinueを使わずにプログラムを書くようにしましょう。

そこで、フローチャートを次のように書き換えます(フローチャート2)。
#ref(./janken2.png,nolink,50%)

これをプログラムにします。

まず初めに、「じゃんけんぽん」と出力して、乱数を使って自分の手を選び、キーボードからプレイヤーの手を入力します。
#geshi(c){{
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void) {
  int r, h;

  srand((unsigned int) time(NULL));  // 乱数のシード

  printf("じゃんけんぽん!(1: グー、2: チョキ、3: パー):\n");
  r = rand() % 3 + 1;
  scanf("%d", &h);
  printf("あなた: %d、コンピューター: %d(1: グー、2: チョキ、3: パー)\n", h, r);


  return 0;
}
}}

次に、繰り返し処理を行う前置判定のwhile文の形を用意します。
#geshi(c){{
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void) {
  int r, h;

  srand((unsigned int) time(NULL));  // 乱数のシード

  printf("じゃんけんぽん!(1: グー、2: チョキ、3: パー):\n");
  r = rand() % 3 + 1;
  scanf("%d", &h);
  printf("あなた: %d、コンピューター: %d(1: グー、2: チョキ、3: パー)\n", h, r);

  while (h == r) {

  }


  return 0;
}
}}
ここでは、繰り返し条件を先に考えることができるので、先に入れてあります。
繰り返し条件は、引き分け、つまり、コンピューターの手とプレイヤーの手が等しいことです。

つぎに、繰り返し処理の中だけを考えると、次のようになります。
#geshi(c){{
  printf("あいこでしょ!(1: グー、2: チョキ、3: パー):\n");
  r = rand() % 3 + 1;
  scanf("%d", &h);
  printf("あなた: %d、コンピューター: %d(1: グー、2: チョキ、3: パー)\n", h, r);
}}

そこで、これをwhile文の波括弧 { } の中に入れます。
#geshi(c){{
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void) {
  int r, h;

  srand((unsigned int) time(NULL));  // 乱数のシード

  printf("じゃんけんぽん!(1: グー、2: チョキ、3: パー):\n");
  r = rand() % 3 + 1;
  scanf("%d", &h);
  printf("あなた: %d、コンピューター: %d(1: グー、2: チョキ、3: パー)\n", h, r);

  while (h == r) {
    printf("あいこでしょ!(1: グー、2: チョキ、3: パー):\n");
    r = rand() % 3 + 1;
    scanf("%d", &h);
    printf("あなた: %d、コンピューター: %d(1: グー、2: チョキ、3: パー)\n", h, r);
  }


  return 0;
}
}}

繰り返しが終了したら(勝負がついたら)、結果を判定して、出力します。
#geshi(c){{
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void) {
  int r, h;

  srand((unsigned int) time(NULL));  // 乱数のシード

  printf("じゃんけんぽん!(1: グー、2: チョキ、3: パー):\n");
  r = rand() % 3 + 1;
  scanf("%d", &h);
  printf("あなた: %d、コンピューター: %d(1: グー、2: チョキ、3: パー)\n", h, r);

  while (h == r) {
    printf("あいこでしょ!(1: グー、2: チョキ、3: パー):\n");
    r = rand() % 3 + 1;
    scanf("%d", &h);
    printf("あなた: %d、コンピューター: %d(1: グー、2: チョキ、3: パー)\n", h, r);
  }

  if (r == 1 && h == 3 || r == 2 && h == 1 || r == 3 && h == 2) {
    printf("あなたの勝ち!\n");
  } else {
    printf("あなたの負け!\n");
  }

  return 0;
}
}}
これで完成です(プログラム2)。


**演習2 [#n6066e70]
プログラム1をプログラム2に変更し、実行結果を確認せよ。



*プログラムをテストする(おまけ) [#fa2130c8]

[[数当てゲーム>授業/C言語基礎/数当てゲーム]]と同様に、まず、プログラムをモジュールに分割し、単体テストを行います。

プログラム2は、次の四つのモジュールに分割できます。
+コンピューターが自分の手を選ぶ
+プレイヤーが手を入力する
+あいこのときにもう一度勝負する
+勝敗を判定する

そこで、まず、コンピューターが自分の手を正しく選べているかどうかをテストします。
[[数当てゲーム>授業/C言語基礎/数当てゲーム]]と同様に、乱数を生成した後にprintf関数を置いて生成された数を表示し、これを何回も繰り返し実行して生成された数が1から3までの数であることを確認します。
#geshi(c){{
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void) {
  int r, h;
  int i;  // テスト用

  srand((unsigned int) time(NULL));  // 乱数のシード

  // テスト用
  for (i = 1; i <= 1000; i++) {
    r = rand() % 3 + 1;
    printf("# r = %d\n", r);
    if (r < 1 || r > 3) { exit(1); }
  }

  /*
  printf("じゃんけんぽん!(1: グー、2: チョキ、3: パー):\n");
  r = rand() % 3 + 1;
  scanf("%d", &h);
  printf("あなた: %d、コンピューター: %d(1: グー、2: チョキ、3: パー)\n", h, r);
  */

  while (h == r) {
    printf("あいこでしょ!(1: グー、2: チョキ、3: パー):\n");
    r = rand() % 3 + 1;
    scanf("%d", &h);
    printf("あなた: %d、コンピューター: %d(1: グー、2: チョキ、3: パー)\n", h, r);
  }

  if (r == 1 && h == 3 || r == 2 && h == 1 || r == 3 && h == 2) {
    printf("あなたの勝ち!\n");
  } else {
    printf("あなたの負け!\n");
  }

  return 0;
}
}}


つぎに、コンピューターの手をグー、チョキ、パーのそれぞれに固定して、残りのモジュールのテストを行います。

ここでは、テスト・ケースが限定されているので、ホワイト・ボックス・テストを用いて、すべてのケースをカバーするようにテスト・ケースを作成します。
つまり、r が 1, 2, 3 のそれぞれについて、h が 1, 2, 3 のときの計9通りについて、実行結果を確認します。

コンピューターの手をグーに固定するには、乱数を生成した直後に、r に 1 を代入します。
#geshi(c){{
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void) {
  int r, h;

  srand((unsigned int) time(NULL));  // 乱数のシード

  printf("じゃんけんぽん!(1: グー、2: チョキ、3: パー):\n");
  r = rand() % 3 + 1;
  r = 1;  // テスト用
  scanf("%d", &h);
  printf("あなた: %d、コンピューター: %d(1: グー、2: チョキ、3: パー)\n", h, r);

  while (h == r) {
    printf("あいこでしょ!(1: グー、2: チョキ、3: パー):\n");
    r = rand() % 3 + 1;
    r = 1;  // テスト用
    scanf("%d", &h);
    printf("あなた: %d、コンピューター: %d(1: グー、2: チョキ、3: パー)\n", h, r);
  }

  if (r == 1 && h == 3 || r == 2 && h == 1 || r == 3 && h == 2) {
    printf("あなたの勝ち!\n");
  } else {
    printf("あなたの負け!\n");
  }

  return 0;
}
}}

このテスト用プログラムに対して、1, 2, 3 とすべてのパターンを入力して、実行結果を確認します。
#geshi(sh){{
luna% a.out
じゃんけんぽん!(1: グー、2: チョキ、3: パー):
1
あなた: 1、コンピューター: 1(1: グー、2: チョキ、3: パー)
あいこでしょ!(1: グー、2: チョキ、3: パー):
2
あなた: 2、コンピューター: 1(1: グー、2: チョキ、3: パー)
あなたの負け!
luna% a.out
じゃんけんぽん!(1: グー、2: チョキ、3: パー):
3
あなた: 3、コンピューター: 1(1: グー、2: チョキ、3: パー)
あなたの勝ち!
}}


**演習3 [#fee024dc]
プログラム2のテストを行え。


----
*まとめ [#d3e87cb3]

プログラム作成は、設計、コーディング、テストの順に行います。

まず、出力結果のイメージを作成し、プログラムの流れをフローチャートで表します(設計)。

フローチャートができたら、フローチャートに基づいてプログラムを作成します(コーディング)。

プログラムができたら、正しく動くことを確認します(テスト)。

トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS