【C#入門】第8回 関数について【じゃんけんゲームを作ろう その8】

2023年2月7日

こんにちは、ノムノムです。

前回は繰り返し文までやりました。

【C#入門】第7回 繰り返しについて【じゃんけんゲームを作ろう その7】
【C#入門】第7回 繰り返しについて【じゃんけんゲームを作ろう その7】
こんにちは。ノムノムです。 前回は条件文をやりました。 今回は繰り返し文をやります。 繰り返し文 繰り返しって…
http://nomux2.net/loop/

今回は関数をやりたいと思います。

まずはここまでのおさらい

前回の段階でじゃんけんゲームを作ることができます。
以下に表示します。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace ProjectJanken
{
    public partial class Form1 : Form
    {

        System.Random r = new System.Random();

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //初期化処理
            this.picCPU.Image = null;
            this.picYou.Image = null;
            this.lblResult.Text = "";
        }

        private void btnEnd_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void btnGoo_Click(object sender, EventArgs e)
        {
            //あなたの手
            this.picYou.Image = imgJankenList.Images[0];            //グーをあなたの手にします。

            //CPUの手
            int cpu_hand = r.Next(3);       //0~2の乱数を返します。

            switch(cpu_hand)
            {
                case 0:
                    this.picCPU.Image = imgJankenList.Images[0];    //グーをCPUの手にします。
                    break;
                case 1:
                    this.picCPU.Image = imgJankenList.Images[1];    //チョキをCPUの手にします。
                    break;
                case 2:
                    this.picCPU.Image = imgJankenList.Images[2];    //パーをCPUの手にします。
                    break;
                default:
                    this.picCPU.Image = null;
                    break;
            }

            //勝敗判定
            if (cpu_hand == 0)
            {
                this.lblResult.Text = "引き分け";
            }
            else if (cpu_hand == 1)
            {
                this.lblResult.Text = "勝ち";
            }
            else if (cpu_hand == 2)
            {
                this.lblResult.Text = "負け";
            }
        }

        private void btnChoki_Click(object sender, EventArgs e)
        {
            //あなたの手
            this.picYou.Image = imgJankenList.Images[1];            //チョキをあなたの手にします。

            //CPUの手
            int cpu_hand = r.Next(3);       //0~2の乱数を返します。

            switch (cpu_hand)
            {
                case 0:
                    this.picCPU.Image = imgJankenList.Images[0];    //グーをCPUの手にします。
                    break;
                case 1:
                    this.picCPU.Image = imgJankenList.Images[1];    //チョキをCPUの手にします。
                    break;
                case 2:
                    this.picCPU.Image = imgJankenList.Images[2];    //パーをCPUの手にします。
                    break;
                default:
                    this.picCPU.Image = null;
                    break;
            }


            //勝敗判定
            if (cpu_hand == 1)
            {
                this.lblResult.Text = "引き分け";
            }
            else if (cpu_hand == 2)
            {
                this.lblResult.Text = "勝ち";
            }
            else if (cpu_hand == 0)
            {
                this.lblResult.Text = "負け";
            }
        }

        private void btnPar_Click(object sender, EventArgs e)
        {
            //あなたの手
            this.picYou.Image = imgJankenList.Images[2];            //パーをあなたの手にします。

            //CPUの手
            int cpu_hand = r.Next(3);       //0~2の乱数を返します。

            switch (cpu_hand)
            {
                case 0:
                    this.picCPU.Image = imgJankenList.Images[0];    //グーをCPUの手にします。
                    break;
                case 1:
                    this.picCPU.Image = imgJankenList.Images[1];    //チョキをCPUの手にします。
                    break;
                case 2:
                    this.picCPU.Image = imgJankenList.Images[2];    //パーをCPUの手にします。
                    break;
                default:
                    this.picCPU.Image = null;
                    break;
            }

            if (cpu_hand == 2)
            {
                this.lblResult.Text = "引き分け";
            }
            else if (cpu_hand == 0)
            {
                this.lblResult.Text = "勝ち";
            }
            else if (cpu_hand == 1)
            {
                this.lblResult.Text = "負け";
            }
        }

    }
}

どうでしょうか?ここで一回動かしてみてはいかがでしょう。開始を押すことで実行できます。

動きましたでしょうか?ちゃんとじゃんけんできているでしょうか?

関数を導入する

ソースコードを見てください。「btnGoo_Click」「btnChoki_Click」「btnPar_Click」って内容が似ていると思いませんか?

同じまたは、似たような命令文を書くことはプログラミングでは悪手といえます。

なぜなら、もし修正が入った場合同じ文または似たような文のところ全て直さなければいけません。
例えば今回ならば、1がグー、2がチョキ、3がパーに変更が掛かったら修正する箇所が複数あることになります。

できれば修正箇所は少ないに越したことはありません。関数を使えばそれができるのです。

関数の記述

関数は以下のように書きます。

private 戻り値の型 関数名(引数1, 引数2, ・・・・)
{
    処理
    return 戻り値;
}

例を出します。じゃんけんのCPUの手を取得する関数です。

private int GetCpuHand()
{
    //cpuの手を取得
    int cpu_hand = r.Next(3);
    
    //不正な手になっていないかチェック、不正な値の場合-1を返す
    if (cpu_hand < 0 && cpu_hand > 2)
    {
        return -1;
    }

    //CPUの手を返す。
    return cpu_hand;
}

説明します。GetCpuHand関数という名前にしました。引数(パラメータともいう)はなくても大丈夫です。戻り値の型をintにしたので必ず戻り値を設定しなければいけません。

retrun で戻り値を呼び出し側に与えます。

関数の使い方

それでは先ほどのcpuの手を置き換えてみます。

//CPUの手
int cpu_hand = r.Next(3);       //0~2の乱数を返します。

これを関数を使うと

int cpu_hand = GetCpuHand();

となります。

なんだあんまり変わってないじゃんって思うかもしれませんが、まず関数により変数cpu_handの中身は0~2もしくは-1 であることが保証されています。

そしてCPUの手を取得する方法が変わった時、修正するのはGetCpuHand関数の中を直せばよいのです。

他にもやってみよう

他にもいろんなパターンでやってみようと思います。お次はこれ

switch (cpu_hand)
{
    case 0:
        this.picCPU.Image = imgJankenList.Images[0];    //グーをCPUの手にします。
        break;
    case 1:
        this.picCPU.Image = imgJankenList.Images[1];    //チョキをCPUの手にします。
        break;
    case 2:
        this.picCPU.Image = imgJankenList.Images[2];    //パーをCPUの手にします。
        break;
    default:
        this.picCPU.Image = null;
        break;
}

これを関数化する

private Image GetJankenImage(int index)
{
     //不正な値が引数になっていないかチェック、不正な値の場合nullを返す
    if (index< 0 && index> 2)
    {
        return null;
    }
    
    //Imageを返す
    return imgJankenList.Images[index];
}

簡略化してみました。スッキリしましたね。条件として imgJankenList のイメージのインデックスとじゃんけんの手が同じである必要があります。

さて置き換えましょう

this.picCPU.Image = GetJankenImage(cpu_hand);     //じゃんけんのイメージを取得する

勝敗判定を関数化

3すくみの勝敗判定も関数化してみましょう。

//勝敗判定
if (cpu_hand == 0)
{
    this.lblResult.Text = "引き分け";
}
else if (cpu_hand == 1)
{
    this.lblResult.Text = "勝ち";
}
else if (cpu_hand == 2)
{
    this.lblResult.Text = "負け";
}

3すくみは以下の計算式でできます。

  • (私の手 – CPUの手 + 3) % 3 = 0 ⇒ 相子
  • (私の手 – CPUの手 + 3 ) % 3 = 1 ⇒ 負け
  • (私の手 – CPUの手 + 3 ) % 3 = 2 ⇒  勝ち
private int JadgeJanken(int my_hand, int cpu_hand)
{
    return (my_hand - cpu_hand + 3) % 3;
}

勝敗結果を文字列に変換

上記の勝敗判定関数(JadgeJanken関数)だと数値で帰ってくるのでそれを文字に変換する関数を作ってみたいと思います。


private int ConvertVictoryMessage(int judge, ref string victory)
{
    //判定で結果文字列を取得
    switch(judge)
    {
        case 0:
            victory = "あいこ";
            break;
        case 1:
            victory = "負け";
            break;
        case 2:
            victory = "勝ち";
            break;
        default:
            victory = "";
            return -1;
    }

    return 0;
}

通常は関数の中でいくら変更しても渡した値は変わることはありません。(値渡し)
しかし、引数にrefをつけると引数として渡した値を変更することができます。(参照渡し)

呼び出す側にもrefが必要です。

string message = "";

//勝敗判定
int judge = JadgeJanken(my_hand, cpu_hand);

//文字列を取得
int ret = ConvertVictory(judge, ref message);

置き換えてみましょう

ここまで作成した関数を利用して、さらに関数を加えて変えてみます。

namespace ProjectJanken
{
    public partial class Form1 : Form
    {

        System.Random r = new System.Random();

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //初期化処理
            this.picCPU.Image = null;
            this.picYou.Image = null;
            this.lblResult.Text = "";
        }

        private void btnEnd_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void btnGoo_Click(object sender, EventArgs e)
        {
            //あなたの手
            MatchStart(0);
        }

        private void btnChoki_Click(object sender, EventArgs e)
        {
            //あなたの手
            MatchStart(1);
        }

        private void btnPar_Click(object sender, EventArgs e)
        {
            //あなたの手
            MatchStart(2);
        }

        //勝負スタート
        private void MatchStart(int my_hand)
        {
            string message = "";

            int cpu_hand = GetCpuHand();       //0~2の乱数をCPUの手にします。

            //イメージを設定
            this.picYou.Image = GetJankenImage(my_hand);     //あなたの手
            this.picCPU.Image = GetJankenImage(cpu_hand);    //CPUの手

            //勝敗判定
            int judge = JadgeJanken(my_hand, cpu_hand);

            //勝敗結果文字列を取得
            int ret = ConvertVictoryMessage(judge, ref message);

            //結果を表示
            if (ret == 0)
            {
                this.lblResult.Text = message;
            }
            else
            {
                this.lblResult.Text = "不正な結果が返ってきました。";
            }

        }

        //CPUの手を取得します。
        private int GetCpuHand()
        {
            //cpuの手を取得
            int cpu_hand = r.Next(3);

            //不正な手になっていないかチェック、不正な値の場合-1を返す
            if (cpu_hand < 0 && cpu_hand > 2)
            {
                return -1;
            }

            //CPUの手を返す。
            return cpu_hand;
        }

        //じゃんけんの手のイメージを取得します。
        private Image GetJankenImage(int index)
        {
            //不正な値が引数になっていないかチェック、不正な値の場合nullを返す
            if (index < 0 && index > 2)
            {
                return null;
            }

            //Imageを返す
            return imgJankenList.Images[index];
        }

        //じゃんけんの勝敗を取得します。0:あいこ、1:負け、2:勝ち
        private int JadgeJanken(int my_hand, int cpu_hand)
        {
            return (my_hand - cpu_hand + 3) % 3;
        }

        //じゃんけんの結果に対し勝敗メッセージに変換します。
        private int ConvertVictoryMessage(int judge, ref string victory)
        {
            //判定で結果文字列を取得
            switch(judge)
            {
                case 0:
                    victory = "あいこ";
                    break;
                case 1:
                    victory = "負け";
                    break;
                case 2:
                    victory = "勝ち";
                    break;
                default:
                    victory = "";
                    return -1;
            }

            return 0;
        }

    }
}

新しくMatchStart関数を作成しました。

プログラミングの基本は、同じ命令を2度書かないことです。
その理由は、プログラムの可読性の向上と修正箇所を少なくすることによる二次バグを減らすことにあります。

二次バグとは修正したことにより発生した新たなバグのことをさします。

まとめ

やっと関数まで終わりました。ひとまずここまでできればあとは慣れもありますが、大抵のものは作れるのではないでしょうか?

もちろん知らなければできないこともあります。

ここまでやってきてプログラムの基本を習得あなたなら、どうやれば自分のやりたいことができるのか調べる方法さえわかれば、 大がかりなものでなければ大抵作れると思います。

ここからは、このじゃんけんゲームをさらにパワーアップさせていきながら他のことも説明していきたいと思います。

ありがとうございました。

【C#入門】第9回 デバッグについて【じゃんけんゲームを作ろう その9】
【C#入門】第9回 デバッグについて【じゃんけんゲームを作ろう その9】
前回の関数まででとりあえずじゃんけんゲームは完成です。 そこでより一層理解を深めるために、今回はデバッグの話を…
http://nomux2.net/debug/