【C#学習法】完成コードを上から写さない|ターン制バトルを大きなブロックから組み立てる写経術

広告

プログラミングの写経というと、完成コードを1行目から順番に入力する方法を思い浮かべるかもしれません。しかし、長いコードを上からそのまま写すだけでは、「今どの機能を作っているのか」が見えにくくなります。

ターン制バトルのように複数の処理が組み合わさるプログラムでは、まず大きなブロック(骨格)だけを作り、中身を後から埋める方法がおすすめです。この記事では、Characterクラスとバトルループを題材に、構造を理解するための写経手順を紹介します。

先に完成版を動かしたい方へ
今回使用するターン制バトルの完成コードと基本解説は、はじめての C# ミニ RPG ― クラス × ループで動かすターン制バトルで確認できます。完成版を一度動かしてから、この記事の手順で骨格から再構築する学習方法もおすすめです。
この記事のゴール

  • 完成コードを機能ごとのブロックに分解できる
  • クラスやメソッドの「外側」を先に書ける
  • 空の骨格を少しずつ動くプログラムへ育てられる
  • 写経をタイピング練習ではなく設計の練習に変えられる

結論:外側から内側へ書くと構造が見える

完成コードを上から順番に写す方法が必ず悪いわけではありません。文法や入力に慣れる段階では役立ちます。しかし、学習の目的が「自分で似たプログラムを作れるようになること」なら、次の順番が効果的です。

① 全体を機能ごとに分ける
② クラス・メソッド・ループの外枠を書く
③ 仮の表示で流れを確認する
④ 小さな機能を1つずつ実装する
⑤ 境界値や入力ミスを試す

最初に完成コードを5つのブロックへ分ける

今回のターン制バトルは、細かい行を見る前に、次の5つへ分けられます。

  1. キャラクターのデータ:名前、現在HP、最大HP、生存判定
  2. キャラクターの行動:攻撃、ダメージ、回復
  3. 登場人物の生成:プレイヤーと敵を作る
  4. バトルの進行:生きている間、交互に行動する
  5. 終了処理:勝利または敗北を表示する

この説明を自分の言葉で言えれば、コードを書く前にプログラムの設計図ができています。

ステップ1:空の骨格だけを書く

最初は攻撃力や回復量を書きません。名前空間、クラス、Main、必要なメソッドの外枠だけを作ります。

using System;

namespace TurnBasedGame
{
    class Character
    {
        // データは後で書く

        public int Attack()
        {
            // 後で実装する
            return 0;
        }

        public void TakeDamage(int damage)
        {
            // 後で実装する
        }

        public void Heal()
        {
            // 後で実装する
        }
    }

    class Program
    {
        static void Main()
        {
            // キャラクター生成

            // バトルループ

            // 勝敗表示
        }
    }
}

この段階ではゲームは動きません。それで構いません。まず、Characterが「行動を持つもの」、Program.Mainが「ゲーム全体を進行する場所」だと確認します。

大切な考え方:一度に完成させようとせず、「今は外枠だけ」と作業範囲を決めます。空のメソッドに仮のreturn 0;が必要なのは、戻り値を持つメソッドを一時的にコンパイル可能にするためです。

ステップ2:Characterのデータだけを入れる

class Character
{
    public string Name;
    public int HP;
    public int MaxHP;

    private static Random _rand = new Random();

    public bool IsAlive => HP > 0;

    // メソッドはまだ仮のまま
}

ここでは「キャラクターが何を持つか」だけに集中します。

  • Name:画面に表示する名前
  • HP:現在の体力
  • MaxHP:回復の上限
  • _rand:攻撃力や回復量を決める乱数
  • IsAlive:HPが0より大きいかを返す生存判定

ステップ3:Mainで登場人物を作る

Character player = new Character
{
    Name = "ヒロト",
    HP = 100,
    MaxHP = 100
};

Character enemy = new Character
{
    Name = "スライム",
    HP = 60,
    MaxHP = 60
};

Character[] participants = { player, enemy };
int turn = 0;

この段階で、次の確認用コードを一時的に追加して実行してみましょう。

Console.WriteLine($"{player.Name}: {player.HP}/{player.MaxHP}");
Console.WriteLine($"{enemy.Name}: {enemy.HP}/{enemy.MaxHP}");

ヒロトとスライムの情報が表示されれば、キャラクター生成は成功です。確認できたら、この仮表示は削除して構いません。

ステップ4:バトルループの外枠を作る

攻撃や入力処理をいきなり書かず、まず「戦闘を続ける条件」「現在の行動者」「攻撃される相手」「手番交代」だけを書きます。

while (player.IsAlive && enemy.IsAlive)
{
    Character current = participants[turn];
    Character target = participants[1 - turn];

    Console.WriteLine($"現在の行動者: {current.Name}");
    Console.WriteLine($"攻撃される相手: {target.Name}");

    turn = 1 - turn;
}

ただし、このまま実行すると無限に表示され続けます。骨格の確認時は、Console.ReadLine();を一時的に入れるか、回数を制限してください。

Console.ReadLine();
turn = 1 - turn;

なぜ 1 – turn で交代できる?

現在のturn 1 – turn 次の手番
0 1
1 0 プレイヤー

配列の添字を0と1に限定しているため、短い式で交互に切り替えられます。

ステップ5:Characterの行動を1つずつ完成させる

攻撃

public int Attack()
{
    int damage = _rand.Next(10, 21);
    Console.WriteLine($"{Name} の攻撃! {damage} ダメージ!");
    return damage;
}

Next(10, 21)の21は含まれないため、ダメージは10〜20です。まず攻撃だけ実装し、戻り値を表示して確認します。

被ダメージ

public void TakeDamage(int damage)
{
    HP = Math.Max(HP - damage, 0);
}

Math.Maxにより、HPが0未満になるのを防ぎます。HPが5のとき10ダメージを受ける、という境界値を試しましょう。

回復

public void Heal()
{
    int heal = _rand.Next(5, 16);
    HP = Math.Min(HP + heal, MaxHP);

    Console.WriteLine(
        $"{Name} は回復魔法! {heal} 回復(HP: {HP}/{MaxHP})"
    );
}

Math.Minにより、最大HPを超えて回復しません。HPが95、最大HPが100のときに試すと役割が分かります。

ステップ6:ループの中にプレイヤーと敵の行動を入れる

if (current == player)
{
    Console.WriteLine("\n=== あなたのターン ===");
    Console.WriteLine($"敵 HP: {target.HP}/{target.MaxHP}");
    Console.Write("1)攻撃  2)回復 > ");

    string input = Console.ReadLine();

    if (input == "2")
    {
        current.Heal();
    }
    else
    {
        target.TakeDamage(current.Attack());
    }
}
else
{
    Console.WriteLine("\n--- 敵のターン ---");
    target.TakeDamage(current.Attack());
}

ここで初めて、先ほど作った骨格の中へ具体的な行動を入れます。すでにcurrenttargetの意味が確認できているため、処理を読み解きやすくなります。

ステップ7:最後に勝敗表示を入れる

Console.WriteLine(
    player.IsAlive ? "\n★ 勝利!" : "\n★ 敗北…"
);

バトルループを抜けた時点で、プレイヤーか敵のどちらかが倒れています。条件演算子を使い、プレイヤーが生きていれば勝利、そうでなければ敗北と表示します。

ブロック学習で毎回行う3つの確認

  1. 予想する:実行前に何が表示されるか、変数がどう変わるかを書く
  2. 小さく実行する:1ブロック追加するたびに動作を確認する
  3. 説明する:そのブロックの役割をコードを見ずに日本語で説明する

「入力できた」だけで終わらず、予想と結果を比べることが学習につながります。

上から写す方法との使い分け

方法 向いている目的 注意点
上から順に写す 文法や開発環境の操作に慣れる 全体構造を見失いやすい
ブロックごとに外側から書く 設計・処理の流れ・役割分担を学ぶ 仮実装や小さな動作確認が必要

初学者には、最初に完成版を一度動かしてゴールを見せた後、白紙からブロック単位で再構築する二段階の授業も効果的です。

練習課題

  1. 設計図を作る:完成コードを見ずに、5つのブロック名だけ書いてください。
  2. 仮実装:Attackが常に10を返す状態でバトルを動かしてください。
  3. 境界値:HPが5のキャラクターに10ダメージを与え、HPが0で止まることを確認してください。
  4. 防御を追加:プレイヤーの選択肢に「3)防御」を追加する前に、必要なブロックを日本語で設計してください。
  5. 敵を増やす:敵を2体にする場合、現在の配列と1 - turnのどこを変更する必要があるか考えてください。
  6. 再構築:完成コードを閉じ、コメントで骨格を書いてから自力で中身を埋めてください。

まとめ

  • 長いコードは、最初に機能ごとの大きなブロックへ分ける
  • クラス、メソッド、ループなどの外枠を先に書く
  • 中身は攻撃、ダメージ、回復のように1機能ずつ実装する
  • ブロックを追加するたびに、予想・実行・説明を行う
  • 写経のゴールは、同じコードを入力することではなく、構造を理解して再構築できること

完成コードは答えではなく、設計を読み取るための教材です。「上から写す」から「骨格を作り、機能を組み立てる」へ変えると、次のゲームを自分で作る力につながります。

訪問数 22 回, 今日の訪問数 22回

広告