Volatile memory

物覚えの悪いわたしの、備忘録的な技術系ブログです。

みんなまとめて

ヨットの続きです。

1だけ作って寝て起きたら、 2から6も同じだと気づきますよね。

なんで寝る前に気づかないんでしょうね?

1のとこをこうしたらもう明らかですよね。

        public void SelectNumber(Score score, int n)
        {
            score.Select(Dice.Sum(die => die.Value == n ? n : 0));

            Reset();
        }

        public void SelectOne()
        {
            SelectNumber(OneScore, 1);
        }

こんな感じで他も書けます。

        public void SelectTwo()
        {
            SelectNumber(TwoScore, 2);
        }

こういうものを定義したら、

            NumberScores = new Score[]
            {
                OneScore,
                TwoScore,
                ThreeScore,
                FourScore,
                FiveScore,
                SixScore,
            };

こういうのが簡単にかけます。

        public int NumberTotalScore
        {
            get
            {
                return NumberScores.Sum(s => s.Value);
            }
        }

        public int BonusScore
        {
            get
            {
                return NumberTotalScore >= 63 ? 35 : 0;
            }
        }

        public int TotalScore 
        {
            get
            {
                return NumberTotalScore + BonusScore;
            } 
        }

        public void End()
        {
            Reset();
            foreach (var s in NumberScores)
            {
                s.Reset();
            }
        }

Formの方もこういうのを定義して、

            NumberScoreLabels = new Label[]
            {
                OneScore,
                TwoScore,
                ThreeScore,
                FourScore,
                FiveScore,
                SixScore,
            };

表示の更新をこんなふうに書けます。

        private void UpdateView()
        {
//中略
            RestLabel.Text = Yacht.Rest.ToString();

            for (int i = 0; i < Yacht.NumberScores.Length; ++i)
            {
                NumberScoreLabels[i].Text = ToScoreString(Yacht.NumberScores[i]);
            }
            BonusScore.Text = Yacht.BonusScore.ToString();
            TotalScore.Text = Yacht.TotalScore.ToString();
        }

1から6まで選んでみました。 ちゃんとボーナス入ってます。

これで1から6までできました。 ボーナスも表示されます。

点数を数えよう。

一通り並べた。

並べたよ。以上。

なんか左下のあたりがスカスカでどうしたもんだろとか思いますが、

見た目じゃないんだ!

たぶん。

ゲームやる上では大して問題にならないに違いない。 きっとそうだ。問題になってから考えよう。

これをMNKDD(問題になってから考えよう駆動開発)と名付けます。 思いつきです。 流行るといいですね。

で、終わりにしとこうかと思ったんですけど、 ボタンを見たらコールバックを実装したくなるじゃないですか。 これも病気ですね。

とりあえず1ボタンを実装してみましょう。

1で計上するということは、その時点で出ている1の目の合計を1のスコアとするということです。

もうまったく初めての人に説明する気がないですね。

ちゃーん
ちゃかちゃかちゃーん
ちゃっちゃちゃん
ぽわんぽわんぽわん

説明しよう!
ヨットとは!
5つのサイコロに願いを込め、
その数を力に変えた戦いだ!

1のボタンは
1の目の合計で攻撃する、熱い技だぞ!

戦隊風にしてもわかんないもんはわかんないですね。早めに気づけばよかったです。

ビジネスロジック()的にはこうですね。

    public class Score
    {
        public int Value = 0;
        public bool Selected = false;

        public void Select(int value)
        {
            Value = value;
            Selected = true;
        }
        public void Reset()
        {
            Value = 0;
            Selected = false;
        }
    }

    public class Yacht
    {
        public Score OneScore = new Score();

        public void SelectOne()
        {
            OneScore.Select(Dice.Sum(die => die.Value == 1 ? 1 : 0));

            Reset();
        }
    }

Scoreは点数です。 選ばれた時に点数が決定されます。選ばれるまでは0点です。

Yachtは追加したとこだけ書いてます。 1の点数OneScoreと1を選ぶメソッドSelectOne()を実装します。 1になってるDiceの数だけ1を追加する感じで点数を設定して、リセットで残り回数を戻します。

ついでにこういうプロパティ(だっけ?)TotalScoreを定義します。 とりあえずOneScoreですが最終的には全部足します。

        public int TotalScore
        {
            get
            {
                return OneScore.Value;
            } 
        }

Formの方はOneButton押された時にこれを呼んでから画面更新します。 ResetButtonとだいたい同じですね。

    public partial class YachtForm : Form
    {
        private void OneButton_Click(object sender, EventArgs e)
        {
            Yacht.SelectOne();
            UpdateView();
            ResetView();
        }
    }

これで、3回サイコロを振って1をキープするとこうなって、

ここで1を押すと1に点数が入って合計も増えます。

うん、あと12個だ。

点数書いて合計するまでが遠足。

じゃなくてヨットです。

よく考えてみたら、

ヨットやったことないから点数の数え方わかんない!

当たり前ですね。やれないから作ったわけで。 しかし「よくか」まで打ったら「よく考えてみたら」が出るというのは 普段からどんだけよく考えてないんだということをGoogle日本語入力さんに指摘された気分で正月から不快ですね。

あ、もう正月終わってますね。 けっきょく、正月にはヨットできませんでした。

さて、本題に戻って点数ですが、 最初に紹介したこちらのサイトに親切な表が載ってます。 kododigi.com

これのとおりにやればいいわけです。

めんどいですね。

ここで紙と鉛筆片手にウキウキとゲームを始めるのがパリピ。 プログラムに点数計算させようとするのがプログラマー

変態ですね。

あ、ちなみにいまどき一般家庭に鉛筆があるのかとか 紙と鉛筆で両手じゃないのかとか細かいことが気になるのも理系特有の病気です。 治らないのであきらめましょう。わたしはそうしました。

ルール的にはサイコロの目の合計的なやつとか役とかあって、 出た目をどれで計上するかを決めるとその回のスコアが決まります。 計上する枠は13個あるので13回やって点数の合計で勝負です。

例によってわかってる人しかわかりませんね。 上の説明のサイト見てみてください。ずっとわかりやすい説明が載ってます。

プログラム的には枠13個作って、どれで計上するかのボタンくっつけて、 点数を計算してついでに合計を表示しておいてあげるといいように思えます。

こんなノリですね。

リセットじゃなくて1から6のボタンを押すと右にスコアが確定されるみたいにすればいいんだと思います。 あと7つあります。 はい、途中で飽きてますね。

気が向いたら続きやります。

ここで悔い改める2024。

ところでこのブログ、markdownで書けるんだって。知らんかった。 というわけで今回からmarkdownで書いていこうと思います。 技術系っぽくなってきた!

うまくいかなかったらもとに戻るんですけどね。

技術系っぽいついでに、きのう書いたコード、リファクタリングしてみましょう。 たぶん、リファクタリングのとこに下線がついて勝手に説明とかいけるようになるんですよね。 ブログって便利ですね。

きのう書いたヨットですが、たったあれだけで一応必要なことはできるような気がして、あー単純なのに楽しいゲームだな―ということで安心してほっとくのが世の慣わしという気もします。

でもでもでも、そこは新年。悔い改めるとかにちょうどいいじゃないですか。ここはリファクタリングして、美しいコードを目指しましょう。そうしましょう。今年からはちゃんとした技術系ブログです。縞パンがどうとか過去の話です。

問題点を上げてみましょう。

  • モデルとビューが分離されてない。
  • コピペくさい繰り返しが多い。
  • いらんとこまでプラットフォームに依存し過ぎ。

まずはモデルというかビジネスじゃないけどビジネスロジックを分離します。

    public class Dice
    {
        public int Value;
        public bool Keep;
        Random Random = new Random();

        public void Reset()
        {
            Value = 0;
            Keep = false;
        }

        public void Roll()
        {
            if (!Keep)
            {
                Value = Random.Next(1, 7);
            }
        }
    }

    public class Yacht
    {
        public int Rest;
        public Dice[] Dice = new Dice[5];

        public Yacht()
        {
            for (int i = 0; i < 5; i++)
            {
                Dice[i] = new Dice();
            }
            Reset();
        }

        public void Reset()
        {
            Rest = 3;
            foreach(var die in Dice)
            {
                die.Reset();
            }
        }

        public void Roll()
        {
            foreach(var die in Dice)
            {
                die.Roll();
            }

            -- Rest;
        }
    }

サイコロクラスとヨットクラスです。 サイコロとは、値を持ち、キープすることができて、転がすと1から6のランダムな整数値を取る。キープしてると値は更新されないと、わりと宣言的に書いてあるつもりです。 ヨットとは、残り回数とサイコロ5個を持ち、リセットすると初期状態に、転がすと5個のサイコロを振り、残り回数を減らすもの。と定義します。 先頭にusingがありません。つまりプラットフォーム依存もほぼないということです。

あとはこれの中身を表示するビューを、Formをいじって定義すればいいですね。MVC的にいうとVとCのとこになるのかと思います。

    public partial class YachtForm : Form
    {
        public YachtForm()
        {
            InitializeComponent();
        }

        private Yacht Yacht = new Yacht();
        private Label[] DiceLabels;
        private CheckBox[] KeepCheckBoxes; 

        private string ToDiceString(int i)
        {
            return i == 0 ? "-" : i.ToString();
        }

        private void UpdateView()
        {
            for (int i = 0; i < Yacht.Dice.Length; ++i)
            {
                DiceLabels[i].Text = ToDiceString(Yacht.Dice[i].Value);
            }
            RestLabel.Text = Yacht.Rest.ToString();
        }

        private void RollButton_Click(object sender, EventArgs e)
        {
            for (int i = 0; i < KeepCheckBoxes.Length; ++i)
            {
                Yacht.Dice[i].Keep = KeepCheckBoxes[i].Checked;
            }
            Yacht.Roll();
            UpdateView();
            if (Yacht.Rest == 0)
            {
                RollButton.Enabled = false;
            }
        }

        private void ResetButton_Click(object sender, EventArgs e)
        {
            Yacht.Reset();
            UpdateView();
            foreach (var box in KeepCheckBoxes)
            {
                box.Checked = false;
            }
            RollButton.Enabled = true;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            DiceLabels = new Label[]
            {
                DiceLabel0,
                DiceLabel1,
                DiceLabel2,
                DiceLabel3,
                DiceLabel4,
            };
            KeepCheckBoxes = new CheckBox[]
            {
                KeepCheckBox0,
                KeepCheckBox1,
                KeepCheckBox2,
                KeepCheckBox3,
                KeepCheckBox4,
            };
            UpdateView();
        }
    }

基本的にはビューの状態管理とヨットの呼び出ししかしてません。 えらく長くなってしまうのでなんかきれいになったのかどうなのかいまいち疑問ですね。

はい、今年もこんなもんですが、よろしければお付き合いのほどを…( )

なんかこれ貼り付けるとランキングに参加できるらしいので貼ってみます↓

ひとまずヨット!

たった今飲んだ薬の数さえ

すぐに忘れてしまう彼女は しかし

と、さだまさしが歌ったのも今は昔。

 

サイコロ何回振ったか覚えてますか―っ!?

1、2、3、…

2回だっけ?

 

たった今振ったサイコロの回数さえ

すぐに忘れてしまうわたしは、

 

アプリに数えさせることにしました。

 

ほれ。

 

ほれ。

 

ほれ。

 

ほれ。

 

…ざっこ。

 

残り回数の表示とリセットボタンを用意しました。

コード的にはこんだけ。

        int rest = 3;


        private void RollButton_Click(object sender, EventArgs e)
        {
            var r = new Random();
            if (!KeepCheckBox0.Checked)
            {
                DiceLabel0.Text = r.Next(1, 7).ToString();
            }
            if (!KeepCheckBox1.Checked)
            {
                DiceLabel1.Text = r.Next(1, 7).ToString();
            }
            if (!KeepCheckBox2.Checked)
            {
                DiceLabel2.Text = r.Next(1, 7).ToString();
            }
            if (!KeepCheckBox3.Checked)
            {
                DiceLabel3.Text = r.Next(1, 7).ToString();
            }
            if (!KeepCheckBox4.Checked)
            {
                DiceLabel4.Text = r.Next(1, 7).ToString();
            }
            --rest;
            RestLabel.Text = rest.ToString();

            if (rest == 0)
            {
                RollButton.Enabled = false;
            }
        }

        private void ResetButton_Click(object sender, EventArgs e)
        {
            rest = 3;
            RestLabel.Text = rest.ToString();
            KeepCheckBox0.Checked = false;
            KeepCheckBox1.Checked = false;
            KeepCheckBox2.Checked = false;
            KeepCheckBox3.Checked = false;
            KeepCheckBox4.Checked = false;
            DiceLabel0.Text = "-";
            DiceLabel1.Text = "-";
            DiceLabel2.Text = "-";
            DiceLabel3.Text = "-";
            DiceLabel4.Text = "-";
            RollButton.Enabled = true;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            RestLabel.Text = rest.ToString();
        }

 

リセットでもとに戻ります。あとは紙と鉛筆だな。

キープ!

説明せねばなるまい。キープとは?

いい感じの目が出たのでそのままにしとくやつだ。うん、わかってる人しかわからん説明だ。

 

ほれ。

 

ね、5が3つ出たのでそのままキープしたいでしょ?

 

そこで、キープ。

 

ほれ。

 

やた!4つそろった!

コード的にはこんなんですね。チェックボックス置いて、チェックされたたら振らない。

      private void RollButton_Click(object sender, EventArgs e)
        {
            var r = new Random();
            if (! KeepCheckBox0.Checked)
            {
                DiceLabel0.Text = r.Next(1, 7).ToString();
            }
            if (!KeepCheckBox1.Checked)
            {
                DiceLabel1.Text = r.Next(1, 7).ToString();
            }
            if (!KeepCheckBox2.Checked)
            {
                DiceLabel2.Text = r.Next(1, 7).ToString();
            }
            if (!KeepCheckBox3.Checked)
            {
                DiceLabel3.Text = r.Next(1, 7).ToString();
            }
            if (!KeepCheckBox4.Checked)
            {
                DiceLabel4.Text = r.Next(1, 7).ToString();
            }
        }

 

これもキープして

 

ほれ。ほれ。ほれ。

 

…何回振ったっけ?

よし、アイコン変えよう。

そろそろ正月も平常運転に戻るころですが、年も変わったのでアイコンを変えてやろうと思いました。いつぞや入れといたstable diffusionを取り出してこんな感じの呪文を突っ込んでみます。

 

A girl smelling the scent of shepherd's purse, anime, green hair, high quality.

ぺんぺん草を嗅ぐ少女ですね。

 

変態じゃないぞ―

 

まあ実は細かいとこ修正しながら何枚か描かせたですが

いいね!

 

なんかこう、可愛いしちょっと賢そうだし、それっぽいもん持ってるし。

 

ただ、ほら。

 

手が変。

 

なのでクリップしてついでにコントラスト上げて見ました。

 

どう?さっそくアイコン変えてまわろう。

ひゃっはー。