2023年06月10日くいなちゃん


6さいからのプログラミング」第13話は、基本編の最終回です。ゲームを作ってみましょう!
第12話までで、どのプログラミング言語を使う場合にも役に立つ基礎知識を一通り解説してきました。 今回は最後に基本編の締めくくりとして、C言語でゲームを作ります。

1RPG

題材にするのは、RPG(ロールプレイングゲーム)です。 とはいえテキストだけのRPGで、しかもラスボス戦のみとなります(図1-1)。
テキストRPG
図1-1: テキストRPG
勇者「Kuina」がラスボス「Demon」に挑むストーリーです。 「0」を入力すると攻撃し、「1」で力を溜めて攻撃力アップ(戦闘が終わるまで持続します)、「2」で回復です。
なぜ今回RPGを題材にしたかと言いますと、プログラムの流れがシンプルで解りやすく改造もしやすいため、プログラミングの学習に適していると考えたからです。
少し長いですが、ソースコードは図1-2の通りです。
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3.  
  4. struct param
  5. {
  6.   char name[8];
  7.   int life, attack, defense;
  8. };
  9.  
  10. void act(struct param* actor, struct param* target, int command)
  11. {
  12.   int point;
  13.   switch (command)
  14.   {
  15.     case 0:
  16.       point = actor->attack - target->defense;
  17.       target->life -= point;
  18.       printf("%s attacks! %s takes %d damage points!\n", actor->name, target->name, point);
  19.       break;
  20.     case 1:
  21.       point = 20;
  22.       actor->attack += point;
  23.       printf("%s casts Boost! %s's Attack increases by %d!\n", actor->name, actor->name, point);
  24.       break;
  25.     case 2:
  26.       point = 50;
  27.       actor->life += point;
  28.       printf("%s casts Heal! %s's Life increases by %d!\n", actor->name, actor->name, point);
  29.       break;
  30.   }
  31. }
  32.  
  33. int main(void)
  34. {
  35.   struct param you = { "Kuina", 100, 100, 100 };
  36.   struct param enemy = { "Demon", 1000, 100, 50 };
  37.   printf("%s appears!\n", enemy.name);
  38.   for (; ; )
  39.   {
  40.     int command;
  41.     printf("%s: Life=%d, Attack=%d, Defense=%d\n", you.name, you.life, you.attack, you.defense);
  42.     printf("%s: Life=%d, Attack=%d, Defense=%d\n", enemy.name, enemy.life, enemy.attack, enemy.defense);
  43.     printf("(0) Attack\n(1) Boost\n(2) Heal\n");
  44.     printf("Command:");
  45.     scanf("%d", &command);
  46.     act(&you, &enemy, command);
  47.     if (enemy.life <= 0)
  48.     {
  49.       printf("%s is defeated!\n", enemy.name);
  50.       return 0;
  51.     }
  52.     act(&enemy, &you, rand() % 3);
  53.     if (you.life <= 0)
  54.     {
  55.       printf("%s has been killed...\n", you.name);
  56.       return 0;
  57.     }
  58.   }
  59. }
図1-2: rpg.c
このソースコードは大まかには、4~8行目の構造体「struct param」と、10~31行目の関数「act」と、33~59行目の関数「main」から成ります。
以下、部分的に再掲しながら細かく解説していきます。

2struct param

  1. struct param
  2. {
  3.   char name[8];
  4.   int life, attack, defense;
  5. };
図2-1: rpg.c(2)
4~8行目の「struct param」は、味方と敵のパラメータを管理する構造体となっています。 メンバのnameはキャラクターの名前、lifeはライフ、attackは攻撃力、defenseは守備力です。
  1. int main(void)
  2. {
  3.   struct param you = { "Kuina", 100, 100, 100 };
  4.   struct param enemy = { "Demon", 1000, 100, 50 };
図2-2: rpg.c(3)
このstruct param型の変数として、関数mainの開始直後の35~36行目で、味方「you」と敵「enemy」を作成しています。 各メンバには列挙した値が順番に代入されるので、例えばenemyのメンバは、nameが"Demon"、lifeが1000、attackが100、defenseが50となります。

3メインループ

  1.   for (; ; )
  2.   {
  3.     int command;
  4.     printf("%s: Life=%d, Attack=%d, Defense=%d\n", you.name, you.life, you.attack, you.defense);
  5.     printf("%s: Life=%d, Attack=%d, Defense=%d\n", enemy.name, enemy.life, enemy.attack, enemy.defense);
  6.     printf("(0) Attack\n(1) Boost\n(2) Heal\n");
  7.     printf("Command:");
  8.     scanf("%d", &command);
  9.     act(&you, &enemy, command);
  10.     if (enemy.life <= 0)
  11.     {
  12.       printf("%s is defeated!\n", enemy.name);
  13.       return 0;
  14.     }
  15.     act(&enemy, &you, rand() % 3);
  16.     if (you.life <= 0)
  17.     {
  18.       printf("%s has been killed...\n", you.name);
  19.       return 0;
  20.     }
  21.   }
図3-1: rpg.c(4)
その直後、38~58行目では、for文により半永久的に処理が繰り返されます。 ゲームのプログラムでは、ゲームオーバーになるまでこのように同じ処理を繰り返すことが多く、この繰り返しのことは「メインループ」と呼ばれます。
今回のプログラムでは、47~51行目で敵のライフが0以下になれば勝利とし、53~57行目で味方のライフが0以下になれば負けとし、そのどちらでもなければメインループを回り続ける流れとなっています。
メインループの中では、45行目でまずプレイヤーからの入力を受け取って46行目のact関数で味方側の行動を行い、52行目のact関数で敵側の行動を行います。

4act関数

  1. void act(struct param* actor, struct param* target, int command)
  2. {
  3.   int point;
  4.   switch (command)
  5.   {
  6.     case 0:
  7.       point = actor->attack - target->defense;
  8.       target->life -= point;
  9.       printf("%s attacks! %s takes %d damage points!\n", actor->name, target->name, point);
  10.       break;
  11.     case 1:
  12.       point = 20;
  13.       actor->attack += point;
  14.       printf("%s casts Boost! %s's Attack increases by %d!\n", actor->name, actor->name, point);
  15.       break;
  16.     case 2:
  17.       point = 50;
  18.       actor->life += point;
  19.       printf("%s casts Heal! %s's Life increases by %d!\n", actor->name, actor->name, point);
  20.       break;
  21.   }
  22. }
図4-1: rpg.c(5)
味方や敵の行動を行うact関数は、10~31行目に書かれています。 関数の引数は「actor」「target」「command」ですが、それぞれ「行動するキャラ」「行動の対象となるキャラ」「行動の種類」です。
13行目のswitch文で、行動の種類であるcommandによって処理を分けます。 0なら攻撃、1なら力溜め、2なら回復を行います。

4.1攻撃



  1.     case 0:
  2.       point = actor->attack - target->defense;
  3.       target->life -= point;
  4.       printf("%s attacks! %s takes %d damage points!\n", actor->name, target->name, point);
  5.       break;
図4-2: rpg.c(6)
攻撃の処理は15~19行目です。 16行目で攻撃によって与えるダメージを計算し、変数pointに入れます。 ダメージの計算方法は、行動するキャラ「actor」の攻撃力「attack」から、行動の対象となるキャラ「target」の守備力「defense」を引いた値となっています。
17行目で対象者「target」のライフ「life」から、ダメージであるpointの値を減らし、その状況を18行目で画面に出力しています。

4.2力溜め



  1.     case 1:
  2.       point = 20;
  3.       actor->attack += point;
  4.       printf("%s casts Boost! %s's Attack increases by %d!\n", actor->name, actor->name, point);
  5.       break;
図4-3: rpg.c(7)
力溜めは、単純に攻撃力を上げるだけの処理で、20~24行目です。 変数pointに20を入れ、その値を22行目で行動者「actor」の攻撃力「attack」に加算しています。

4.3回復



  1.     case 2:
  2.       point = 50;
  3.       actor->life += point;
  4.       printf("%s casts Heal! %s's Life increases by %d!\n", actor->name, actor->name, point);
  5.       break;
図4-4: rpg.c(8)
回復は、単純にライフを増やすだけの処理で、25~29行目です。 通常のRPGのライフには最大値があり、それ以上の回復はできないようになっていますが、このプログラムでは省略しています。 つまり、いくらでも回復ができます。

5act呼び出し

  1.     act(&you, &enemy, command);
  2.     if (enemy.life <= 0)
  3.     {
  4.       printf("%s is defeated!\n", enemy.name);
  5.       return 0;
  6.     }
  7.     act(&enemy, &you, rand() % 3);
図5-1: rpg.c(9)
さて、act関数を理解したところで、再度46行目と52行目の呼び出し側を見てみましょう。
46行目ではプレイヤーが入力した行動を味方に行わせるために、行動者actorには味方「you」を、対象者targetには敵「enemy」を、行動の種類commandにはプレイヤーが入力した値を入れてact関数を呼び出しています。
対して52行目では敵を行動させるために、行動者actorには敵「enemy」を、対象者targetには味方「you」を、行動の種類commandには「rand() % 3」という値を入れてact関数を呼び出しています。 「rand() % 3」とは、「0~2の乱数を生成する」という意味です。 これにより敵は、攻撃、力溜め、回復の行動をランダムに選んで行います。
補足すると、rand関数は「0~非常に大きい数」の整数の乱数を生成します。 これを3で割った余りを求めることで、「0~2」の範囲の乱数にしています。

6プログラムの改造

以上、流れとしては「プレイヤーの入力」→「味方の行動」→「敵の行動」→「プレイヤーの入力」→…を繰り返しているだけのプログラムです。 市販のゲームはこれより遙かに複雑に見えるかもしれませんが、やっていることはこのプログラムと大差ありません。
さて、このRPGを実際に実行してみると、敵があまりに強すぎてなかなか勝てないと思います。 そこで、このゲームをより面白くするために、プログラムを改造することを考えてみてください。 何をどう書き換えれば勝てるようになるでしょうか。 既存のプログラムを改造することは、プログラミングに慣れる重要な一歩となるでしょう。
以上で、全13回にわたる「6さいからのプログラミング」の基本編はおしまいです。 ここで得た知識は、これからどのような言語を使ってどのようなプログラムを書くときにも直接役立つはずです!
1686380243jaf