MSBOOKS

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

【C言語入門】part11 : 構造体

前回は文字,文字列についてやりました.
今回は構造体についてやっていきます.

構造体とは?

今回は新しく構造体という概念を紹介します.配列では同じ型のものを複数集めて1パックにしたようなものと紹介しましたが,型が異なる場合でも1パックにしたいときもあります.ゲームでは,キャラクターの名前(char),HP(int),MP(int),攻撃力(float)などはバラバラに変数で用意すると,どれが誰の属性なのかわからなくなってきます.
例えば戦隊もののキャラクターを変数にしようとすると,主人公の名前(char),体力(int),色(char)…などやはり異なる型を使うことになりそうです.
f:id:msteacher:20190505202825p:plain
そんなときに構造体を用意することでこれらの異なる型の属性をまとめてわかりやすくすることができます.以下のように構造体には書き方は2通り存在します.

//書き方1
struct character{
    char *name;
    int HP;
    int MP;
    float Power;
};
 
//書き方2
typedef struct character{
    char *name;
    int HP;
    int MP;
    float Power;
} character;

*はポインタですが,ポインタについては別の回で詳しくやるので今回は割愛します.
それでは書き方1の方法で構造体を実体化させる方法をみていきましょう.
・test11-1.c

#include <stdio.h>
//書き方1
struct character{
    char *name;
    int HP;
    int MP;
    float Power;
};
 
int main(void){
   float attack = 4;
   struct character hero; //構造体の定義
   hero.name = "主人公";
   hero.HP = 10;
   hero.MP = 10;
   hero.Power = 1.5;

   printf("===========\n");
   printf("NAME : %s\n",hero.name);
   printf("HP   : %d\n",hero.HP);
   printf("MP   : %d\n",hero.MP);
   printf("===========\n");
   printf("%sの通常攻撃は%.2fあります。\n",hero.name,hero.Power*attack);

   return 0;
}

・実行結果

>test11-1
===========
NAME : 主人公
HP   : 10
MP   : 10
===========
主人公の通常攻撃は6.00あります。

続いて書き方2の場合は以下のようになります.
・test11-2.c

#include <stdio.h>
//書き方2
typedef struct character{
    char *name;
    int HP;
    int MP;
    float Power;
} character;
 
int main(void){
   float attack = 4;
   character hero; //構造体の定義
   hero.name = "主人公";
   hero.HP = 10;
   hero.MP = 10;
   hero.Power = 1.5;

   printf("===========\n");
   printf("NAME : %s\n",hero.name);
   printf("HP   : %d\n",hero.HP);
   printf("MP   : %d\n",hero.MP);
   printf("===========\n");
   printf("%sの通常攻撃は%.2fあります。\n",hero.name,hero.Power*attack);

   return 0;
}

・実行結果

>test11-2
===========
NAME : 主人公
HP   : 10
MP   : 10
===========
主人公の通常攻撃は6.00あります。

本稿ではこれから構造体は書き方2を使っていきます.

構造体のメリット

文字でずらずら書くと何がなんだかわからないと思うのでゲームのキャラクターの設定を構造体で書いてどのように使いやすいかをお見せします.
・test11-3.c

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <time.h>
#define MAX_SIZE 10
#define ENEMY 3
#define MAX_STEP 60
#define NO 0
#define YES 1
/*グローバル変数の定義*/
int TIME = 0;

/*構造体の定義*/
typedef struct character{
   int x;
   int y;
   char mark;
} character;

/*キャラクターの移動先の定義*/
character changeStep(char choose,character c,int map[MAX_SIZE][MAX_SIZE]){
   switch(choose){
      case 'w':
         //次移動するところが壁でなければ移動する
         if(map[c.x-1][c.y] != 1)
            c.x -= 1;
         break;
      case 'd':
         if(map[c.x][c.y+1] != 1)
            c.y += 1;
         break;
      case 's':
         if(map[c.x+1][c.y] != 1)
            c.x += 1;
         break;
      case 'a':
         if(map[c.x][c.y-1] != 1)
            c.y -= 1;
         break;
      default:
         printf("不正な入力です\n");
   }
   return c;
}

/*マップを表示する関数*/
void drawMap(int map[MAX_SIZE][MAX_SIZE],character hero,character enemy[ENEMY]){
   int paintChara;
   int i,j,k;
   printf("******操作方法******\n↑:w →:d ↓:s ←:a\n");
   printf("********************\n残りのステップ数:%d\n",60-TIME);
   printf("********************\n");
   for(i = 0 ;i < MAX_SIZE ;i++){
      for(j = 0 ;j < MAX_SIZE ;j++){
         paintChara = NO;
         //キャラクターを表示する場合
         for(k = 0 ;k < ENEMY ;k++){
            if(enemy[k].x == i && enemy[k].y == j){
               printf("%c",enemy[k].mark);
               paintChara = YES;
               break;
            }
         }
         if(hero.x == i && hero.y == j && paintChara == NO){
            printf("%c",hero.mark);
            paintChara = YES;
         }
         //キャラクターの表示がない場合
         if(map[i][j] == 0 && paintChara == NO)
            printf(" ");
         else if(map[i][j] == 1 && paintChara == NO)
            printf("+");
      }
      printf("\n");
   }
}

/*メイン関数*/
int main(void){
   int end = NO;
   int i,j,num;
   int map[MAX_SIZE][MAX_SIZE];  //マップの定義
   char choose;
   character hero; //構造体の定義
   character enemy[ENEMY]; //構造体配列の定義
   srand((unsigned)time(NULL));

   //構造体の中身の定義
   hero.x = 1;
   hero.y = 1;
   hero.mark = '@';
   for(i = 0 ;i < ENEMY ;i++){
      enemy[i].x = rand()%7+2;
      enemy[i].y = rand()%7+2;
      enemy[i].mark = '\\';
   }

   //マップの初期化(0:移動可能,1:壁(移動不可))
   for(i = 0 ;i < MAX_SIZE ;i++){
      for(j = 0 ;j < MAX_SIZE ;j++){
         if(i == 0 || i == (MAX_SIZE - 1) || j == 0 || j == (MAX_SIZE - 1) )
            map[i][j] = 1;
         else
            map[i][j] = 0;
      }
   }
   system("cls"); //画面をいったん綺麗にする

   /*ゲームメインループ処理*/
   while(end == NO){
      TIME++;
      drawMap(map,hero,enemy);
      //終了判定
      for(i = 0 ;i < ENEMY ;i++){
         if(enemy[i].x == hero.x && enemy[i].y == hero.y){ //敵と主人公が同じ位置
            printf("GAME OVER!\n");
            end = YES;
         }
      }
      if(TIME == MAX_STEP){ //ステップが規定回数を超えたら
         printf("CLEAR!\n");
         end = YES;
      }
      //移動処理
      if(end == NO){
         choose = _getch(); //キーボードから入力を受け付ける
         system("cls"); //画面を綺麗にする
         hero = changeStep(choose,hero,map); //主人公の移動
         //敵キャラクターの移動処理
         for(i = 0 ;i < ENEMY ;i++){
            num = rand() % 4;
            if(num == 0)
               enemy[i] = changeStep('w',enemy[i],map);
            else if(num == 1)
               enemy[i] = changeStep('d',enemy[i],map);
            else if(num == 2)
               enemy[i] = changeStep('s',enemy[i],map);
            else if(num == 3)
               enemy[i] = changeStep('a',enemy[i],map);
         }
      }
   }
   return 0;
}

・実行結果

******操作方法******
↑:w →:d ↓:s ←:a
********************
残りのステップ数:58
********************
++++++++++
+ @      +
+        +
+ \   \  +
+        +
+  \     +
+        +
+        +
+        +
++++++++++

このようにゲームでは関数が多くなってくるため,構造体でまとめておくことでキャラクター1人の属性をまとめて1パックとして関数への明け渡しができ,記述が簡単になります.test9-4.cのときではx,yは配列でしたが,構造体にすることで,それ以外の要素(例えば今回はchar型の表示するマーク)を持たせることができ,よりまとまりのあるプログラムになります.また要素もhero.xという風に書けるため,見た目もわかりやすく後から見たときに何をしている処理か判別しやすいといったメリットもあります.
ちなみにこのゲームはキーボードで操作をして主人公を動かすごとに敵キャラクターも同時に動き,敵キャラクターと主人公が同じ位置にくるとゲームオーバーになり,規定されたステップ回数内に敵と被らなかったらクリアというゲームになります.ちなみに敵の数はdefineのENEMYの値を変えることで自由に増やすことができます.
構造体を使うことでよりまとまりのあるプログラムを作成できますので,ぜひいろいろなところで使っていきましょう.
お疲れさまでした.

練習問題

・問題1
 test9-3.cの自分とコンピュータの指などの情報を構造体にまとめてわかりやすく書き換えてみよう.(test11-4.c)

練習問題解答例

・問題1
 ・11-4.c

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
/*構造体の定義*/
typedef struct finger{
   int th;
   int reTh; 
} finger;

/*関数のプロトタイプ宣言*/
int inputFinger(int);
int battle(int, int, int, int, char *);

/*定数定義*/
#define MY_TURN 0
#define ENEMY_TURN 1

/*メイン関数*/
int main(void){
   int num;
   int turn;
   finger my;
   finger pc;
   my.reTh = 2;
   pc.reTh = 2;
   srand((unsigned)time(NULL));
   printf("*****指スマゲームを開始します*****\n");
   //先行を決める
   turn = rand() % 2;
   if(turn == MY_TURN)
      printf("先行はあなたです\n");
   else if(turn == ENEMY_TURN)
      printf("先行はPCです\n");

   //自分のターンとPCのターンの動きを定義
   while(my.reTh != 0 && pc.reTh != 0){
      printf("---------------------------------\n");
      printf("|自分の残りの数:%d PCの残りの数:%d|\n",my.reTh,pc.reTh);
      printf("---------------------------------\n");
      //自分のターン
      if(turn == 0){
         printf("あなたのターン!\n");
         do{
            printf("立つ親指の数を予測してね:");
            scanf("%d",&num);
         }while(num < 0 || num > (my.reTh + pc.reTh));
         my.th = inputFinger(my.reTh);
         pc.th = rand() % (pc.reTh + 1);
         my.reTh = battle(my.reTh, my.th, pc.th, num, "あなた");
         turn = ENEMY_TURN;
      }
      //PCのターン
      else{
         printf("pcのターン!\n");
         my.th = inputFinger(my.reTh);
         pc.th = rand() % (pc.reTh + 1);
         num = rand() % (my.reTh + 1) + pc.th;
         pc.reTh = battle(pc.reTh, my.th, pc.th, num, "PC");
         turn = MY_TURN;
      }
   }

   //どちらが勝ったか表示
   if(my.reTh == 0){
      printf("あなたの勝ちです!\n");
   }
   else if(pc.reTh == 0){
      printf("PCの勝ちです!\n");
   }
   return 0;
}

/*バトル関数*/
int battle(int reTh, int myTh, int pcTh, int num, char name[]){
   printf("*****バトル開始!*****\n");
   printf("自分の立てた指の数:%d\nPC立てた指の数:%d\n",myTh,pcTh);
   printf("2人の合計:%d\n",myTh+pcTh);
   printf("%sの予測した数:%d\n",name,num);
   if((myTh + pcTh) == num){
      printf("%sの予測はあってます!\n",name);
      reTh = reTh - 1;
   }
   else{
      printf("%sの予測は間違えていた!\n",name);
    }
    printf("**********************\n");
    return reTh;
}

/*立てる指の数を入力させる関数*/
int inputFinger(int myReTh){
   int finger;
   do{
      printf("自分の立てる親指の数を入力してね:");
      scanf("%d",&finger);
   }while(finger < 0 || finger > myReTh);
   return finger;
}