MSBOOKS

プログラミングとか深層学習とか日常の出来事とか

【C言語入門】part7 : for文,乱数の生成

前回はwhile文,do-while文(繰り返し処理)についてやりました.
今回はfor文と乱数の生成についてやっていきます.

for文

前回,決めた条件を満たす間はずっとループするwhile文をやりました.しかしポケモンの技である"みだれひっかき"などを思い浮かべてください(わからなかったらすみません,連続で攻撃する技という意味です).乱数などで決まった回数だけ攻撃を繰り返すものですよね.
f:id:msteacher:20190113011201p:plain
while文で書くとどうなるでしょうか.例えば乱数で攻撃が3回に決められたものだとして書くと以下のようになります.
・test7-1.c

#include <stdio.h>
int main(void){
   int enemyHP;
   int i; //繰り返し回数を保存する変数
   printf("敵のHPを入力してね:");
   scanf("%d",&enemyHP);
   printf("今の敵のHP:%d\n",enemyHP);
   while(enemyHP>0){
      printf("敵は3回の連続攻撃をうけた!\n");
      i = 1; //最初は1回目の繰り返しなので1にする(初期化)
      while(i <= 3){ //3回繰り返したいのでiが3以下までループする(条件式)
         printf("%d回目 : 敵は3ダメージ受けた!\n",i);
         enemyHP = enemyHP - 3;
         i = i + 1; //ここまでで1回が終わったので+1する(変化式)
      }
      if(enemyHP < 0){
         enemyHP = 0;
      }
      printf("今の敵のHP:%d\n",enemyHP);
   }
   printf("敵を倒した!\n");
   return 0;
}

・実行結果

C:\Users\ユーザ名\Desktop>test7-1
敵のHPを入力してね:15
今の敵のHP:15
敵は3回の連続攻撃をうけた!
1回目 : 敵は3ダメージ受けた!
2回目 : 敵は3ダメージ受けた!
3回目 : 敵は3ダメージ受けた!
今の敵のHP:6
敵は3回の連続攻撃をうけた!
1回目 : 敵は3ダメージ受けた!
2回目 : 敵は3ダメージ受けた!
3回目 : 敵は3ダメージ受けた!
今の敵のHP:0
敵を倒した!

このように適当な変数iを用意し,カウントアップさせ,それを数えていき要求する回数分ループしたら終了するといった形になっています.しかしこの書き方だと,ループ用の仮の計算式があるのか,敵のHPの変化の計算式なのか,ごちゃごちゃになってしまいます.こういった決まった回数の処理をしたいときにループを見やすくするのがfor文になります.先ほどのプログラムを書き換えた例を見てみましょう.
・test7-2.c

#include <stdio.h>
int main(void){
   int enemyHP;
   int i; //繰り返し回数を保存する変数
   printf("敵のHPを入力してね:");
   scanf("%d",&enemyHP);
   printf("今の敵のHP:%d\n",enemyHP);
   while(enemyHP>0){
      printf("敵は3回の連続攻撃をうけた!\n");
      for(i = 1; i <= 3; i = i + 1){ //初期化,条件式,変化式が1行になる
         printf("%d回目 : 敵は3ダメージ受けた!\n",i);
         enemyHP = enemyHP - 3;
      }
      if(enemyHP < 0){
         enemyHP = 0;
      }
      printf("今の敵のHP:%d\n",enemyHP);
   }
   printf("敵を倒した!\n");
   return 0;
}

・実行結果

C:\Users\ユーザ名\Desktop>test7-2
敵のHPを入力してね:15
今の敵のHP:15
敵は3回の連続攻撃をうけた!
1回目 : 敵は3ダメージ受けた!
2回目 : 敵は3ダメージ受けた!
3回目 : 敵は3ダメージ受けた!
今の敵のHP:6
敵は3回の連続攻撃をうけた!
1回目 : 敵は3ダメージ受けた!
2回目 : 敵は3ダメージ受けた!
3回目 : 敵は3ダメージ受けた!
今の敵のHP:0
敵を倒した!

実行結果はまったく同じになりますね.しかも初期化,条件式,変化式が1行で書くことができるため,カウントアップ用の仮の計算式などを,実現したい繰り返し処理と被ることがないため,見やすく書き間違えとかもなくなりそうですね.for文の書き方は以下のようになります.
・for文

for(初期化式; 繰り返し条件式; 変化式){
   //処理
}

for文は決まった回数処理を繰り返したい時に使うのがメインなので,単純に3回繰り返したいときC言語ではよく以下のような書き方をします.

int i;
for(i = 0; i < 3; i++){
   //処理
}

初期値は1からではなく0から始めることが多いです(今後やる配列をやると意味がわかります).またi = i + 1のような1だけカウントアップさせる場合に省略形としてi++という書き方をする場合が多く,式の意味はほぼ同じで使われます.ひたすら2を掛け算していくプログラムの例で書き方を見てみましょう.
・test7-3.c

#include <stdio.h>
int main(void){
   int i;
   int count;
   int ans = 1;
   printf("ひたすら2を掛け算する計算をするよ\n");
   printf("何回2を掛けるか,繰り返したい数を入力してね:");
   scanf("%d",&count);
   for(i = 0; i < count; i++){ //初期化,条件式,変化式
      printf("%d回目 : %d × 2 = %d\n",i+1,ans,ans*2);
      ans = ans * 2;
   }
   return 0;
}

・実行結果

C:\Users\ユーザ名\Desktop>test7-3
ひたすら2を掛け算する計算をするよ
何回2を掛けるか,繰り返したい数を入力してね:6
1回目 : 1 × 2 = 2
2回目 : 2 × 2 = 4
3回目 : 4 × 2 = 8
4回目 : 8 × 2 = 16
5回目 : 16 × 2 = 32
6回目 : 32 × 2 = 64

for文で2を掛け算する計算を指定した回数だけ繰り返しています.このように指定した回数を繰り返す場合while文よりもfor文のほうが見やすくわかりやすいです.

乱数の生成

今回は+αとして,ゲームを作るうえでダメージのばらつきや攻撃が失敗する確率を求める計算で必須となる乱数(ランダムな値)の生成の仕方を見てみましょう.今回乱数を10個生成して,それらを画面表示するものを作りたかったので,指定した回数(10回)だけ繰り返すfor文を使った書き方をしています.
・test7-4.c

#include <stdio.h>
#include <stdlib.h> //追加
int main(void){
   int i;
   srand(10);
   for(i = 0; i < 10; i++){
      printf("%d\n",rand());
   }
   return 0;
}

・実行結果

C:\Users\ユーザ名\Desktop>test7-4
30957
10345
10368
8444
31802
13592
4097
7909
6420
6516

C:\Users\ユーザ名\Desktop>test7-4
30957
10345
10368
8444
31802
13592
4097
7909
6420
6516

まず#includeでstdlib.hを追加することで,乱数を作る関数を呼び出せるようになります.乱数を生成するには元となるシード値(乱数の種)が必要になります.C言語ではsrand(10)を書くことで,乱数のシード値を10に固定します.何度実行してもシード値が10ということはこのプログラムを複数回実行しても,毎回同じ場所には同じ乱数がでてくるのが実行結果からわかると思います.これがシード値を固定すると起こる現象です.10で生成される乱数が決まっているからですね.試しに10を別の値にして実行してみてください.別の乱数が生成されることがわかると思います.実際に乱数を生成しているのはrand()という関数です.ここで生成された乱数を変数などに入れることで攻撃時のダメージのばらつきを表現することができます.しかしこのシード値固定の方法だと,毎回同じタイミングで同じ乱数が生成されるため,ゲーム性に欠けてしまう問題があります.それを解決したのが以下のプログラムになります.
・test7-5.c

#include <stdio.h>
#include <time.h> //追加
#include <stdlib.h>
int main(void){
   int i;
   srand((unsigned)time(NULL));
   for(i=0; i < 10; i++){
      printf("%d\n",rand());
   }
   return 0;
}

・実行結果

C:\Users\ユーザ名\Desktop>test7-5
28503
20635
16243
15927
26190
20175
2414
32334
25814
32469

C:\Users\ユーザ名\Desktop>test7-5
28288
31487
6352
26493
21651
30653
24002
16099
1223
10347

time.hを追加することで現在のコンピュータ上の時間を得ることができます.その時間を(unsigned)time(NULL)という関数(詳細は省きます)で実際に呼ぶことができます.これをシード値とすることでプログラム実行のたびに時間が異なるため,異なった乱数が生成されるという訳です.よくゲームとかでやる乱数調整で「時間を動かして~」とかやってるのはこのシード値を固定させている訳ですね.しかしこのままだと乱数の値が大きすぎて使いづらいと思います.例えばサイコロ(1~6)の乱数を生成したいときはどうすればいいのかというと以下のように書けます.
・test7-6.c

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
int main(void){
   int i;
   srand((unsigned)time(NULL));
   for(i=0; i < 10; i++){
      printf("%d\n",rand()%6+1);
   }
   return 0;
}

・実行結果

C:\Users\ユーザ名\Desktop>test7-6
1
3
6
6
5
1
3
1
6
6

rand()で生成されたものに対し"%6"(6で割った余り)を求めることで,この計算結果は必ず0~5の範囲になります.これに+1をすることでサイコロの1~6の乱数が作られたということですね.このようにすることで,自由に乱数を作ることができます.
今回の練習問題は乱数をやったのでゲームとかも作る問題を用意してみました.
お疲れさまでした.

練習問題

・問題1
 test7-2.cを改良し,何回連続で攻撃するかをキーボード入力からその回数を受け付け,自分で指定できるよう変更してみよう(test7-7.c).
・実行結果例

C:\Users\ユーザ名\Desktop>test7-7
敵のHPを入力してね:20
敵に何回連続で攻撃をする?:4
今の敵のHP:20
敵は4回の連続攻撃をうけた!
1回目 : 敵は3ダメージ受けた!
2回目 : 敵は3ダメージ受けた!
3回目 : 敵は3ダメージ受けた!
4回目 : 敵は3ダメージ受けた!
今の敵のHP:8
敵は4回の連続攻撃をうけた!
1回目 : 敵は3ダメージ受けた!
2回目 : 敵は3ダメージ受けた!
3回目 : 敵は3ダメージ受けた!
4回目 : 敵は3ダメージ受けた!
今の敵のHP:0
敵を倒した!

・問題2
 前回のtest6-6.cを改良し,敵の攻撃で受けるダメージを1~4の範囲で乱数を生成し,それをダメージとするものへ変更してみよう(test7-8.c).
・実行結果例

C:\Users\ユーザ名\Desktop>test7-8
敵のHPを入力してね:10
主人公のHPを入力してね:10
今の敵のHP:10
今の主人公のHP:10
戦闘開始!
主人公のターン!主人公はどうする?
1.攻撃する
2.魔法で攻撃する
3.逃げる
1,2,3のいずれかを入力:1
攻撃をした!
3ダメージ!
敵のターン!
主人公に攻撃をした!
4ダメージ!
今の主人公のHP:6
今の敵のHP:7

主人公のターン!主人公はどうする?
1.攻撃する
2.魔法で攻撃する
3.逃げる
1,2,3のいずれかを入力:1
攻撃をした!
3ダメージ!
敵のターン!
主人公に攻撃をした!
3ダメージ!
今の主人公のHP:3
今の敵のHP:4

主人公のターン!主人公はどうする?
1.攻撃する
2.魔法で攻撃する
3.逃げる
1,2,3のいずれかを入力:1
攻撃をした!
3ダメージ!
敵のターン!
主人公に攻撃をした!
1ダメージ!
今の主人公のHP:2
今の敵のHP:1

主人公のターン!主人公はどうする?
1.攻撃する
2.魔法で攻撃する
3.逃げる
1,2,3のいずれかを入力:1
攻撃をした!
3ダメージ!
今の主人公のHP:2
今の敵のHP:0

主人公の勝利!

・問題3
f:id:msteacher:20190113011815p:plain
 "いっせーのせ"もしくは"指スマ"という遊びを御存じであろうか.親指を立てるゲームだが,みんなの親指が合計何個立つかを予測するゲームである.自分とPCの2人で指スマをするゲームを作ってみよう.プログラムは以下の作成手順で作成すること(test7-9.c).

①.変数はすべて整数型変数で以下を定義する.
・立つ親指の予測の数num
・自分の立てる親指の数myTh
・自分の残りの親指の数myReTh(初期値2)
・PCの立てる親指の数pcTh
・PCの残りの親指の数pcReTh(初期値2)
・今のターンがどちらであるかturn
②.どちらが先行かを0~1の範囲の乱数で決める.0ならば自分,1ならば相手とする.
③.自分のターンとPCのターンは以下のような動きをする.
(自分のターン)
1.numとmyThをキーボード入力から受け付ける(範囲以外の入力であれば再度入力を促す処理があるとより良い).
2.PC側のpcThを0~pcReThの範囲内で乱数を生成し代入する.
3.myTh+pcThがnumと同値であったならばmyReThを1マイナスする.
(PCのターン)
1.myThをキーボード入力から受け付ける.
2.PC側のpcThを0~pcReThの範囲内でnumを0~myReThの範囲内で乱数を生成し代入する.ただしnumは最後に+pcThすることで,人間が考えるような動作をするので考えてから追加してみよう.
3.myTh+pcThがnumと同値であったならばpcReThを1マイナスする.
④.③の処理をループ処理を用いて,myReThかpcReThのどちらかが0になったらループを抜ける.最後にどちらが勝ったかを画面表示して終了する.
・実行結果例

C:\Users\ユーザ名\Desktop>test7-9
*****指スマゲームを開始します*****
先行はあなたです
---------------------------------
|自分の残りの数:2 PCの残りの数:2|
---------------------------------
あなたのターン!
立つ親指の数を予測してね:4
自分の立てる親指の数を入力してね:2
*****バトル開始!*****
自分の立てた指の数:2
PC立てた指の数:1
2人の合計:3
あなたの予測した数:4
あなたの予測は間違えていた!
**********************
---------------------------------
|自分の残りの数:2 PCの残りの数:2|
---------------------------------
pcのターン!
自分の立てる親指の数を入力してね:2
*****バトル開始!*****
自分の立てた指の数:2
PC立てた指の数:1
2人の合計:3
pcの予測した数:3
pcの予測はあってます!
**********************
---------------------------------
|自分の残りの数:2 PCの残りの数:1|
---------------------------------
あなたのターン!
立つ親指の数を予測してね:2
自分の立てる親指の数を入力してね:1
*****バトル開始!*****
自分の立てた指の数:1
PC立てた指の数:1
2人の合計:2
あなたの予測した数:2
あなた予測はあってます!
**********************
---------------------------------
|自分の残りの数:1 PCの残りの数:1|
---------------------------------
pcのターン!
自分の立てる親指の数を入力してね:1
*****バトル開始!*****
自分の立てた指の数:1
PC立てた指の数:1
2人の合計:2
pcの予測した数:1
pcの予測は間違えていた!
**********************
---------------------------------
|自分の残りの数:1 PCの残りの数:1|
---------------------------------
あなたのターン!
立つ親指の数を予測してね:2
自分の立てる親指の数を入力してね:1
*****バトル開始!*****
自分の立てた指の数:1
PC立てた指の数:1
2人の合計:2
あなたの予測した数:2
あなた予測はあってます!
**********************
あなたの勝ちです!

練習問題の解答例

・問題1
 ・test7-7.c

#include <stdio.h>
int main(void){
   int enemyHP;
   int attack;
   int i; //繰り返し回数を保存する変数
   printf("敵のHPを入力してね:");
   scanf("%d",&enemyHP);
   printf("敵に何回連続で攻撃する?:");
   scanf("%d",&attack);
   printf("今の敵のHP:%d\n",enemyHP);
   while(enemyHP>0){
      printf("敵は%d回の連続攻撃をうけた!\n",attack);
      for(i = 1; i <= attack; i = i + 1){ //初期化,条件式,変化式が1行になる
         printf("%d回目 : 敵は3ダメージ受けた!\n",i);
         enemyHP = enemyHP - 3;
      }
      if(enemyHP < 0){
         enemyHP = 0;
      }
      printf("今の敵のHP:%d\n",enemyHP);
   }
   printf("敵を倒した!\n");
   return 0;
}

・問題2
 ・test7-8.c

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
int main(void){
   int enemyHP;
   int heroHP;
   int choose;
   int attack;
   srand((unsigned)time(NULL));
   do{
      printf("敵のHPを入力してね:");
      scanf("%d",&enemyHP);
      if(enemyHP <= 0){
	printf("HPは0より大きい値で入力してね\n");
      }
   }while(enemyHP <= 0);
   do{
      printf("主人公のHPを入力してね:");
      scanf("%d",&heroHP);
      if(heroHP <= 0){
	printf("HPは0より大きい値で入力してね\n");
      }
   }while(heroHP <= 0);
   printf("今の敵のHP:%d\n",enemyHP);
   printf("今の主人公のHP:%d\n",heroHP);
   printf("戦闘開始!\n");
   while(enemyHP > 0 && heroHP > 0){
      printf("主人公のターン!主人公はどうする?\n");
      printf("1.攻撃する\n2.魔法で攻撃する\n3.逃げる\n");
      printf("1,2,3のいずれかを入力:");
      scanf("%d",&choose);
      switch(choose){
         case 1:
            printf("攻撃をした!\n3ダメージ!\n");
            enemyHP = enemyHP - 3;
            break;
         case 2:
            printf("魔法で攻撃した!\n6ダメージ!\n");
            enemyHP = enemyHP - 6;
            break;
         case 3:
            printf("敵から逃げようとしたが,逃げられなかった!\n");
            break;
         default:
            printf("不正な入力です\n");
      }
      //HPがマイナスにならないように調整
      if(enemyHP < 0){
         enemyHP = 0;
      }
      if(enemyHP != 0){
	 printf("敵のターン!\n");
         attack = rand() % 4 + 1;
	 printf("主人公に攻撃をした!\n%dダメージ!\n",attack);
	 heroHP = heroHP - attack;
         //HPがマイナスにならないように調整
	 if(heroHP < 0){
	    heroHP = 0;
	 }
      }
      printf("今の主人公のHP:%d\n",heroHP);
      printf("今の敵のHP:%d\n\n",enemyHP);
   }
   if(heroHP==0){
      printf("敵の勝利!\n");
   }
   else if(enemyHP==0){
      printf("主人公の勝利!\n");
   }
   return 0;
}

・問題3
 ・test7-9.c

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
int main(void){
   //①変数定義
   int num;
   int myTh;
   int myReTh = 2;
   int pcTh;
   int pcReTh = 2;
   int turn;
   srand((unsigned)time(NULL));
   printf("*****指スマゲームを開始します*****\n");
   //②先行を決める
   turn = rand() % 2;
   if(turn == 0){
      printf("先行はあなたです\n");
   }
   else{
      printf("先行はPCです\n");
   }
   //③自分のターンとPCのターンの動きを定義
   while(myReTh != 0 && pcReTh != 0){
      printf("---------------------------------\n");
      printf("|自分の残りの数:%d PCの残りの数:%d|\n",myReTh,pcReTh);
      printf("---------------------------------\n");
      //自分のターン
      if(turn == 0){
         printf("あなたのターン!\n");
         do{
            printf("立つ親指の数を予測してね:");
            scanf("%d",&num);
         }while(num < 0 || num > (myReTh + pcReTh));
         do{
            printf("自分の立てる親指の数を入力してね:");
            scanf("%d",&myTh);
         }while(myTh < 0 || myTh > myReTh);
         pcTh = rand() % (pcReTh + 1);
         printf("*****バトル開始!*****\n");
         printf("自分の立てた指の数:%d\nPC立てた指の数:%d\n",myTh,pcTh);
         printf("2人の合計:%d\n",myTh+pcTh);
         printf("あなたの予測した数:%d\n",num);
         if((myTh + pcTh) == num){
            printf("あなた予測はあってます!\n");
            myReTh = myReTh - 1;
         }
         else{
            printf("あなたの予測は間違えていた!\n");
         }
         printf("**********************\n");
         turn = 1;
      }
      //PCのターン
      else{
         printf("pcのターン!\n");
         do{
            printf("自分の立てる親指の数を入力してね:");
            scanf("%d",&myTh);
         }while(myTh < 0 || myTh > myReTh);
         pcTh = rand() % (pcReTh + 1);
         //pcが立てた指の数を足すことで,pcが指を立ててるのに0を予測しないようにする
         num = rand() % (myReTh + 1) + pcTh;
         printf("*****バトル開始!*****\n");
         printf("自分の立てた指の数:%d\nPC立てた指の数:%d\n",myTh,pcTh);
         printf("2人の合計:%d\n",myTh+pcTh);
         printf("pcの予測した数:%d\n",num);
         if((myTh + pcTh) == num){
            printf("pcの予測はあってます!\n");
            pcReTh = pcReTh - 1;
         }
         else{
            printf("pcの予測は間違えていた!\n");
         }
         printf("**********************\n");
         turn = 0;
      }
   }
   //④どちらが勝ったか表示
   if(myReTh == 0){
      printf("あなたの勝ちです!\n");
   }
   else if(pcReTh == 0){
      printf("PCの勝ちです!\n");
   }
   return 0;
}