実践、CLIのじゃんけんゲームを作ろう!

[C#] 実践、CLIのじゃんけんゲームを作ろう!

※ 当サイトは広告を含みます。

今回はこれまでの学習を活かして「じゃんけんゲーム」を作りましょう。
僕が学生の頃は課題として提出しろって言われました。今は知りません。

じゃんけんゲームの仕様

最低限の仕様は以下です。

じゃんけんゲームの仕様

ランダムな値を発生させる方法

CPU側の手を決めるためにランダムな値が必要になります。ちなみにプログラムではランダムな値を乱数と呼びます。
この乱数ですが、若干クラスの知識を必要とするため、今回は記述だけ伝えます。深く考えず、このまま使ってください。


Random random = new System.Random();
int v = random.Next(0, 3); // 0から2の乱数を作る

0以上で3未満の乱数を作る部分に注意してください。
この記述で0, 1, 2の乱数が得られます。これでグー、チョキ、パーが表現できますよ。

実践練習

管理人

という事で、早速作ってください。Let's try!

りさ

時間が掛かってもいいから完成させよう!

解答例

数学と同じで答えに辿り着くプログラムは無限にあります。なので、これは一例です。

クリックして解答例を見る。

namespace Sample
{
  internal class Program
  {
    // じゃんけんの手を表現するenum型
    enum RockPaperScissors
    {
      Rock,
      Scissors,
      Paper,
    }

    static void Main(string[] args)
    {
      // じゃんけんの勝敗テーブル
      //   p1がp2に対して勝ったか負けたかを表す
      //   [p1, p2] = 1(勝) or 0(分) or -1(負)
      //
      //   RockPaperScissors型と連動する。
      //    [p1, p2] = [p1のRockPaperScissors型, p2のRockPaperScissors型]
      //
      //   ※ グー、チョキ、パーの組み合わせを多次元配列で表現
      //
      int[,] winLoseTable = new int[,]
      {
        {  0,  1, -1 },
        { -1,  0,  1 },
        {  1, -1,  0 },
      };

      // 勝利数
      int playerWin = 0;
      int cpuWin = 0;

      // ゲームループ
      while (true)
      {
        Console.WriteLine("");
        Console.WriteLine("============================================================");
        Console.WriteLine($"[成績] Player: {playerWin}勝 vs CPU: {cpuWin}勝");
        Console.WriteLine("じゃんけん! ('G':グー | 'C':チョキ | 'P':パー | 'E':終了)");

        // 入力受付
        string input = Console.ReadLine();

        // 1文字以外はやり直し
        if (input.Length != 1)
        {
          Console.WriteLine("ちゃんと入力して!");
          continue;
        }

        // プレイヤー側の手をRockPaperScissors型にする。
        RockPaperScissors playerInput;
        switch (input[0])
        {
          // グー
          case 'G': // 大文字
          case 'g': // 小文字
            playerInput = RockPaperScissors.Rock;
            break;

          // チョキ
          case 'C': // 大文字
          case 'c': // 小文字
            playerInput = RockPaperScissors.Scissors;
            break;

          // パー
          case 'P': // 大文字
          case 'p': // 小文字
            playerInput = RockPaperScissors.Paper;
            break;

          // 終了
          case 'E': // 大文字
          case 'e': // 小文字
            goto GAME_END;

          // 上記以外はNG
          default:
            Console.WriteLine("ちゃんと入力して!");
            continue;
        }

        // 乱数でCPUの手を決定
        Random random = new System.Random();
        RockPaperScissors cpuInput = (RockPaperScissors)random.Next(0, 3);

        switch (cpuInput)
        {
          case RockPaperScissors.Rock:
            Console.WriteLine("ぽん! グー!");
            break;

          case RockPaperScissors.Scissors:
            Console.WriteLine("ぽん! チョキ!");
            break;

          case RockPaperScissors.Paper:
            Console.WriteLine("ぽん! パー!");
            break;
        }

        // 勝敗の判定
        int judgement = winLoseTable[(int)playerInput, (int)cpuInput];
        if (judgement > 0)
        {
          playerWin++;

          Console.WriteLine("あたなの勝ち!");
        }
        else if (judgement < 0)
        {
          cpuWin++;

          Console.WriteLine("あたなの負け!");
        }
        else
        {
          Console.WriteLine("あいこ!");
        }
      }

GAME_END: ;

      // 終了表示
      Console.WriteLine("============================================================");
      Console.WriteLine($"ゲームを終了しました。対戦結果は以下です。");
      Console.WriteLine($"Player: {playerWin}勝");
      Console.WriteLine($"CPU   : {cpuWin}勝");

      // 入力待ち
      Console.WriteLine($"ゲームを終了しました。何か入力すると画面を閉じます。");
      Console.ReadLine();
    }
  }
}
管理人

そんなに長くないと思いませんか? 実処理だけなら100行くらいかな。
これで解説が終わってしまうとキレられそうなので、僕ならどうやって作るかを話しますか。

りさ

普通の人は簡単にキレませんよ。

全体の設計

まず最初にやるのは全体の設計です。
僕は勝手にコメントプログラミングとか呼んでますが、コメントで大枠を作ります。


namespace Sample
{
  internal class Program
  {
    static void Main(string[] args)
    {
      // ゲームループ
      while (true)
      {
        // 入力受付

        // どうやってループを脱出するか考える

        // プレイヤー側の手

        // 乱数でCPUの手

        // 勝敗の判定
      }

      // 終了表示
    }
  }
}
管理人

これ凄く大切です。ジグソーパズルを外枠から作る人、きっとプログラミング向いてますよ。
最初は各種機能なんてどうでもいいので全体を考えましょう。

必要そうな変数や型を用意する

ここで独自型が必要なら作ります。今回はグー、チョキ、パーを表現するenum型を作りました。
後は分かってる限りの変数を記述しておきます。この時に変数のスコープが意識できるとベストです。


namespace Sample
{
  internal class Program
  {
    // じゃんけんの手を表現するenum型
    enum RockPaperScissors
    {
      Rock,
      Scissors,
      Paper,
    }

    static void Main(string[] args)
    {
      // 勝利数
      int playerWin = 0;
      int cpuWin = 0;

      // ゲームループ
      while (true)
      {
        // 入力受付
        string input = Console.ReadLine();

        // プレイヤー側の手
        RockPaperScissors playerInput;

        // 乱数でCPUの手を決定
        Random random = new System.Random();
        RockPaperScissors cpuInput = (RockPaperScissors)random.Next(0, 3);

        // 勝敗の判定
      }
    }
  }
}
管理人

英語でじゃんけんはRockPaperScissorsって言うんだって。でも、日本語の順番だとグーチョキパーじゃん。
最初はenum型Rock, Paper, Scissorsの順番にしたんだけど、使いずれぇぇぇって感じにキレた。

りさ

長年染み付いた癖は簡単に抜けない。

可能な限り実装する

お互いの入力処理とか、実装できる部分は全部実装します。
ループを抜ける処理は考えてないので、ループを継続するように変えておきます。


namespace Sample
{
  internal class Program
  {
    // じゃんけんの手を表現するenum型
    enum RockPaperScissors
    {
      Rock,
      Scissors,
      Paper,
    }

    static void Main(string[] args)
    {
      // 勝利数
      int playerWin = 0;
      int cpuWin = 0;

      // ゲームループ
      while (true)
      {
        // 入力受付
        string input = Console.ReadLine();

        // 1文字以外はやり直し
        if (input.Length != 1)
        {
          Console.WriteLine("ちゃんと入力して!");
          continue;
        }

        // プレイヤー側の手をRockPaperScissors型にする。
        RockPaperScissors playerInput;
        switch (input[0])
        {
          // グー
          case 'G': // 大文字
          case 'g': // 小文字
            playerInput = RockPaperScissors.Rock;
            break;

          // チョキ
          case 'C': // 大文字
          case 'c': // 小文字
            playerInput = RockPaperScissors.Scissors;
            break;

          // パー
          case 'P': // 大文字
          case 'p': // 小文字
            playerInput = RockPaperScissors.Paper;
            break;

          // 終了
          case 'E': // 大文字
          case 'e': // 小文字
            continue; // 暫定処置

          // 上記以外はNG
          default:
            Console.WriteLine("ちゃんと入力して!");
            continue;
        }

        // 乱数でCPUの手を決定
        Random random = new System.Random();
        RockPaperScissors cpuInput = (RockPaperScissors)random.Next(0, 3);

        switch (cpuInput)
        {
          case RockPaperScissors.Rock:
            Console.WriteLine("ぽん! グー!");
            break;

          case RockPaperScissors.Scissors:
            Console.WriteLine("ぽん! チョキ!");
            break;

          case RockPaperScissors.Paper:
            Console.WriteLine("ぽん! パー!");
            break;
        }

        // 勝敗の判定
      }
    }
  }
}

無限ループを抜ける方法を考える

次はどうやったら無限ループを抜けてゲームを終了できるか考えます。
一般的にはゲームフラグを使って真偽値で判定することもありますが、それだと変数が増えるからgoto文を使いました。


namespace Sample
{
  internal class Program
  {
    // じゃんけんの手を表現するenum型
    enum RockPaperScissors
    {
      Rock,
      Scissors,
      Paper,
    }

    static void Main(string[] args)
    {
      // 勝利数
      int playerWin = 0;
      int cpuWin = 0;

      // ゲームループ
      while (true)
      {
        // 入力受付
        string input = Console.ReadLine();

        // 1文字以外はやり直し
        if (input.Length != 1)
        {
          Console.WriteLine("ちゃんと入力して!");
          continue;
        }

        // プレイヤー側の手をRockPaperScissors型にする。
        RockPaperScissors playerInput;
        switch (input[0])
        {
          // グー
          case 'G': // 大文字
          case 'g': // 小文字
            playerInput = RockPaperScissors.Rock;
            break;

          // チョキ
          case 'C': // 大文字
          case 'c': // 小文字
            playerInput = RockPaperScissors.Scissors;
            break;

          // パー
          case 'P': // 大文字
          case 'p': // 小文字
            playerInput = RockPaperScissors.Paper;
            break;

          // 終了
          case 'E': // 大文字
          case 'e': // 小文字
            goto GAME_END;

          // 上記以外はNG
          default:
            Console.WriteLine("ちゃんと入力して!");
            continue;
        }

        // 乱数でCPUの手を決定
        Random random = new System.Random();
        RockPaperScissors cpuInput = (RockPaperScissors)random.Next(0, 3);

        switch (cpuInput)
        {
          case RockPaperScissors.Rock:
            Console.WriteLine("ぽん! グー!");
            break;

          case RockPaperScissors.Scissors:
            Console.WriteLine("ぽん! チョキ!");
            break;

          case RockPaperScissors.Paper:
            Console.WriteLine("ぽん! パー!");
            break;
        }

        // 勝敗の判定
      }

GAME_END:;

      // 入力待ち
      Console.WriteLine($"ゲームを終了しました。何か入力すると画面を閉じます。");
      Console.ReadLine();
    }
  }
}
管理人

こういう感じに利用するgoto文は全く問題ないと思います。もし文句つけるやつ居たらアンチなので中指立ててください。

りさ

やめて!

勝敗の判定をどうするか考える

このプログラムで1番考えるところですね。正直、気合のif文で全パターンを書いてもいいです。
僕が勝敗を配列でまとめたのは、よくあるパターンで仕組みを知ってたから。


namespace Sample
{
  internal class Program
  {
    // じゃんけんの手を表現するenum型
    enum RockPaperScissors
    {
      Rock,
      Scissors,
      Paper,
    }

    static void Main(string[] args)
    {
      // じゃんけんの勝敗テーブル
      //   p1がp2に対して勝ったか負けたかを表す
      //   [p1, p2] = 1(勝) or 0(分) or -1(負)
      //
      //   RockPaperScissors型と連動する。
      //    [p1, p2] = [p1のRockPaperScissors型, p2のRockPaperScissors型]
      //
      //   ※ グー、チョキ、パーの組み合わせを多次元配列で表現
      //
      int[,] winLoseTable = new int[,]
      {
        {  0,  1, -1 },
        { -1,  0,  1 },
        {  1, -1,  0 },
      };

      // 勝利数
      int playerWin = 0;
      int cpuWin = 0;

      // ゲームループ
      while (true)
      {
        // 入力受付
        string input = Console.ReadLine();

        // 1文字以外はやり直し
        if (input.Length != 1)
        {
          Console.WriteLine("ちゃんと入力して!");
          continue;
        }

        // プレイヤー側の手をRockPaperScissors型にする。
        RockPaperScissors playerInput;
        switch (input[0])
        {
          // グー
          case 'G': // 大文字
          case 'g': // 小文字
            playerInput = RockPaperScissors.Rock;
            break;

          // チョキ
          case 'C': // 大文字
          case 'c': // 小文字
            playerInput = RockPaperScissors.Scissors;
            break;

          // パー
          case 'P': // 大文字
          case 'p': // 小文字
            playerInput = RockPaperScissors.Paper;
            break;

          // 終了
          case 'E': // 大文字
          case 'e': // 小文字
            goto GAME_END;

          // 上記以外はNG
          default:
            Console.WriteLine("ちゃんと入力して!");
            continue;
        }

        // 乱数でCPUの手を決定
        Random random = new System.Random();
        RockPaperScissors cpuInput = (RockPaperScissors)random.Next(0, 3);

        switch (cpuInput)
        {
          case RockPaperScissors.Rock:
            Console.WriteLine("ぽん! グー!");
            break;

          case RockPaperScissors.Scissors:
            Console.WriteLine("ぽん! チョキ!");
            break;

          case RockPaperScissors.Paper:
            Console.WriteLine("ぽん! パー!");
            break;
        }

        // 勝敗の判定
        int judgement = winLoseTable[(int)playerInput, (int)cpuInput];
        if (judgement > 0)
        {
          playerWin++;

          Console.WriteLine("あたなの勝ち!");
        }
        else if (judgement < 0)
        {
          cpuWin++;

          Console.WriteLine("あたなの負け!");
        }
        else
        {
          Console.WriteLine("あいこ!");
        }
      }

GAME_END:;

      // 入力待ち
      Console.WriteLine($"ゲームを終了しました。何か入力すると画面を閉じます。");
      Console.ReadLine();
    }
  }
}
管理人

プログラミングって結構覚えゲーです。たくさん経験すると、その時に使いたいコードが浮かびます。
たぶん、大抵の仕事やスポーツもそうでしょう。知識は財産。頑張りましょう。

表示を整える

最後は表示を整えて完成です。内容は最初と同じですが載せておきます。


namespace Sample
{
  internal class Program
  {
    // じゃんけんの手を表現するenum型
    enum RockPaperScissors
    {
      Rock,
      Scissors,
      Paper,
    }

    static void Main(string[] args)
    {
      // じゃんけんの勝敗テーブル
      //   p1がp2に対して勝ったか負けたかを表す
      //   [p1, p2] = 1(勝) or 0(分) or -1(負)
      //
      //   RockPaperScissors型と連動する。
      //    [p1, p2] = [p1のRockPaperScissors型, p2のRockPaperScissors型]
      //
      //   ※ グー、チョキ、パーの組み合わせを多次元配列で表現
      //
      int[,] winLoseTable = new int[,]
      {
        {  0,  1, -1 },
        { -1,  0,  1 },
        {  1, -1,  0 },
      };

      // 勝利数
      int playerWin = 0;
      int cpuWin = 0;

      // ゲームループ
      while (true)
      {
        Console.WriteLine("");
        Console.WriteLine("============================================================");
        Console.WriteLine($"[成績] Player: {playerWin}勝 vs CPU: {cpuWin}勝");
        Console.WriteLine("じゃんけん! ('G':グー | 'C':チョキ | 'P':パー | 'E':終了)");

        // 入力受付
        string input = Console.ReadLine();

        // 1文字以外はやり直し
        if (input.Length != 1)
        {
          Console.WriteLine("ちゃんと入力して!");
          continue;
        }

        // プレイヤー側の手をRockPaperScissors型にする。
        RockPaperScissors playerInput;
        switch (input[0])
        {
          // グー
          case 'G': // 大文字
          case 'g': // 小文字
            playerInput = RockPaperScissors.Rock;
            break;

          // チョキ
          case 'C': // 大文字
          case 'c': // 小文字
            playerInput = RockPaperScissors.Scissors;
            break;

          // パー
          case 'P': // 大文字
          case 'p': // 小文字
            playerInput = RockPaperScissors.Paper;
            break;

          // 終了
          case 'E': // 大文字
          case 'e': // 小文字
            goto GAME_END;

          // 上記以外はNG
          default:
            Console.WriteLine("ちゃんと入力して!");
            continue;
        }

        // 乱数でCPUの手を決定
        Random random = new System.Random();
        RockPaperScissors cpuInput = (RockPaperScissors)random.Next(0, 3);

        switch (cpuInput)
        {
          case RockPaperScissors.Rock:
            Console.WriteLine("ぽん! グー!");
            break;

          case RockPaperScissors.Scissors:
            Console.WriteLine("ぽん! チョキ!");
            break;

          case RockPaperScissors.Paper:
            Console.WriteLine("ぽん! パー!");
            break;
        }

        // 勝敗の判定
        int judgement = winLoseTable[(int)playerInput, (int)cpuInput];
        if (judgement > 0)
        {
          playerWin++;

          Console.WriteLine("あたなの勝ち!");
        }
        else if (judgement < 0)
        {
          cpuWin++;

          Console.WriteLine("あたなの負け!");
        }
        else
        {
          Console.WriteLine("あいこ!");
        }
      }

GAME_END:;

      // 終了表示
      Console.WriteLine("============================================================");
      Console.WriteLine($"ゲームを終了しました。対戦結果は以下です。");
      Console.WriteLine($"Player: {playerWin}勝");
      Console.WriteLine($"CPU   : {cpuWin}勝");

      // 入力待ち
      Console.WriteLine($"ゲームを終了しました。何か入力すると画面を閉じます。");
      Console.ReadLine();
    }
  }
}

あとがき

どうでしたか? 実はここまでの知識で簡単なプログラムは作れます。
しかし、本当の意味でC#を扱うにはオブジェクト指向とクラスの理解が必要になります。

これまでの知識に加えてクラスを学べば概ね終わりですが、C#は歴史が長いため色々と覚えることがあります。
簡易記述や便利機能など、それらは実務レベルだと実質的に必須とも言えますが、最悪なくても戦えますよ。

◆ C#に関する学習コンテンツ

この記事は参考になりましたか?

👆このブログを支援する

関連記事

コメント

この記事へのコメントはありません。