何とかチンチロリンの最終回に漕ぎ付けました。では、最後の部分を見て行きましょう。

 

(解説(3)からの続き) 

    /////////////////////
    // PlayerViewクラス(解説:PlayerListと似たものですが、これは博徒ファイルを処理します。)
    /////////////////////

    public class PlayerView : Form
    {
        //クラスメンバー変数
        ListView Player_View;                    //プレーヤーリスト用リストビュー
        ContextMenu contextMenu;                //コンテキストメニュー
        MenuItem mi0, mi1, mi2, mi3;            //コンテキストメニューアイテム
        Form dlg;                                //博徒名入力ダイアログ
        TextBox txb;                            //同上ダイアログの入力テキストボックス
        Button btn;                                //同上ダイアログの「決定」ボタン

        public PlayerView()
        {
            this.ClientSize = new Size(640, 137);
            this.BackColor = SystemColors.Window;
            this.ShowInTaskbar = false;            //タスクバー上表示
            this.Left = 580;
            this.Top = 110;
            this.StartPosition = FormStartPosition.Manual;
            this.Text = "Player View";
            this.Load += new EventHandler(PlayerView_Load);
        }

        //WM_CREATE時処理
        private void PlayerView_Load(object sender, EventArgs e)
        {
            //リストビューの生成とプロパティの設定
            Player_View = new ListView();
            Player_View.Size = new Size(ClientSize.Width - 20, ClientSize.Height - 20);    //サイズ
            Player_View.Location = new Point(10,  10);            //位置
            Player_View.Anchor = (AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Bottom | AnchorStyles.Right);
            Player_View.FullRowSelect = true;                    //一行選択
            Player_View.GridLines = true;                        //グリッドラインの表示
            Player_View.MultiSelect = true;                        //複数選択を許す
            Player_View.Activation = ItemActivation.OneClick;    //シングルクリック選択
            Player_View.CheckBoxes = false;                        //チェックボックス無効化
            Player_View.View = View.Details;                    //リストビューの表示方
            Player_View.Scrollable = true;                        //スクロールバーを表示
            //イベントハンドラの追加
            Player_View.ColumnClick += new ColumnClickEventHandler(OnLV_ColumnClick);
            //ヘッダー定義
            Player_View.Columns.Add("博徒ファイル名", 152, HorizontalAlignment.Left);
            Player_View.Columns.Add("破産", 48, HorizontalAlignment.Center);
            Player_View.Columns.Add("所持金", 120, HorizontalAlignment.Center);
            Player_View.Columns.Add("運", 48, HorizontalAlignment.Center);
            Player_View.Columns.Add("強気", 48, HorizontalAlignment.Center);
            Player_View.Columns.Add("累計勝負数", 120, HorizontalAlignment.Center);
            Player_View.Columns.Add("累計勝数", 80, HorizontalAlignment.Center);
            Player_View.Columns.Add("累計負数", 80, HorizontalAlignment.Center);
            Player_View.Columns.Add("累計勝率", 80, HorizontalAlignment.Center);
            //コンテキストメニュー関連
            mi0 = new MenuItem();
            mi0.Index = 0;
            mi0.Text = "博徒ファイルの新規作成";
            mi1 = new MenuItem();
            mi1.Index = 0;
            mi1.Text = "博徒ファイルの読み込み";
            mi2 = new MenuItem();
            mi2.Index = 0;
            mi2.Text = "博徒ファイルの削除";
             mi3 = new MenuItem();
            mi3.Index = 0;
            mi3.Text = "終了";
             contextMenu = new ContextMenu();
            contextMenu.MenuItems.Add(mi0);
            contextMenu.MenuItems.Add(mi1);
            contextMenu.MenuItems.Add(mi2);
            contextMenu.MenuItems.Add("-");        //セパレーター
            contextMenu.MenuItems.Add(mi3);
             Player_View.ContextMenu = contextMenu;
            mi0.Click += new EventHandler(Make_Player);
            mi1.Click += new EventHandler(Read_Players);
            mi2.Click += new EventHandler(Del_Player);
            mi3.Click += new EventHandler(Done);
            //FormにListViewを追加
            this.Controls.Add(Player_View);
            //初期的に博徒ファイルを読み込む
            Read_Players(this, new EventArgs());
        }

        //Player_Viewのコラムがクリックされた時
        private void OnLV_ColumnClick(object sender, ColumnClickEventArgs e)
        {
            //ListViewItemSorterを指定する
            Player_View.ListViewItemSorter = new ListViewItemComparer(e.Column);
            //正順、逆順交互に並び替える(ListViewItemSorterを設定するとSortが自動的に呼び出される)
        }

        //「博徒ファイルの新規作成」処理
        public void Make_Player(object sender, EventArgs e)
        {
            //入力ダイアログを作る
            dlg = new Form();
            dlg.FormBorderStyle = FormBorderStyle.FixedSingle;    //サイズの変更を不可にする
            dlg.Size = new Size(240, 120);
            dlg.StartPosition = FormStartPosition.CenterParent;
            dlg.Text = "博徒名の入力";
            Label lbl = new Label();
            lbl.Size = new Size(200, 18);
            lbl.Location = new Point(10, 10);
            lbl.Text = "博徒名を入力してください。";
            dlg.Controls.Add(lbl);
            txb = new TextBox();
            txb.Size = new Size(200, 24);
            txb.Location = new Point(10, lbl.Height + 10);
            txb.Text = "(博徒名)";
            dlg.Controls.Add(txb);
            btn = new Button();
            btn.Size = new Size(48, 24);
            btn.Location = new Point(dlg.Width / 2 - 24, lbl.Height + txb.Height + 20);
            btn.Text = "決定";
            btn.Click += new EventHandler(btn_Click);
            dlg.Controls.Add(btn);
            dlg.ShowDialog(this);    //解説:ダイアログをShowDialogで表示したら最後にDIspose()しなくてはなりません。
        }

        //「博徒ファイルの新規作成」の名前入力ダイアログの処理
        public void btn_Click(object sender, EventArgs e)
        {
            if(txb.Text == "" || txb.Text == "(博徒名)")
            {
                MessageBox.Show("博徒の名前を正しく入力してください。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            else
            {
                //Playerクラスインスタンスを作成、datファイルを書き込む
                Player pl = new Player(txb.Text);
                pl.FinalRecord();
                //博徒ファイルを再度読み込む
                GetDataFiles();
                //博徒名入力ダイアログを開放する
                dlg.Dispose();
            }
        }

        //「博徒ファイルの読み込み」処理
        public void Read_Players(object sender, EventArgs e)
        {
            //Playersフォールダーのデータファイルを読む
            GetDataFiles();
        }

        //「博徒ファイルの削除」処理
        public void Del_Player(object sender, EventArgs e)
        {
            if(Player_View.SelectedItems.Count < 1)
            {
                MessageBox.Show("削除する博徒名ファイルが未選択です。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            else
            {
                foreach(ListViewItem item in Player_View.SelectedItems)
                {
                    DialogResult dr = MessageBox.Show("博徒" + item.Text + "ファイルを削除しますか?", "確認", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
                    if(dr == DialogResult.Yes)
                    {
                        //選択された博徒ファイルを削除
                        File.Delete(".\\Players\\" + item.Text);    //ファイル削除
                        item.Remove();                        //リストビューから削除
                    }
                }
            }
        }

        //「終了」処理
        public void Done(object sender, EventArgs e)
        {
            Close();
        }

        //Playersフォールダーの*.datファイルを取得
        public void GetDataFiles()
        {
            //ListViewのデータをクリアする
            Player_View.Items.Clear();
            //Playersフォールダーの*.datファイルを表示
            IEnumerable<string> files = null;
            files = Directory.EnumerateFiles(".\\Players", "*.dat", SearchOption.TopDirectoryOnly);
            foreach (string file in files)
            {
                string fn = file.Replace(".\\Players\\", "");
                Player pl = new Player(fn.Replace(".dat", ""));
                ListViewItem item = Player_View.Items.Add(fn);
                item.SubItems.Add(pl.IsBankrupt.ToString());
                item.SubItems.Add(String.Format("{0:C}", pl.Capital));
                item.SubItems.Add(pl.Luck.ToString());
                item.SubItems.Add(pl.BullBear.ToString());
                item.SubItems.Add(String.Format("{0:#,0}", pl.TotalGames));
                item.SubItems.Add(String.Format("{0:#,0}", pl.TotalWins));
                item.SubItems.Add(String.Format("{0:#,0}", pl.TotalLoses));
                if(pl.TotalGames > 0)
                {
                    double rate = (double)pl.TotalWins / (double)pl.TotalGames;
                    item.SubItems.Add(String.Format("{0:P3}", rate));
                }
                else
                    item.SubItems.Add("N/A");
            }
        }
    }

    ///////////////////////////////////////////
    //ListViewの項目の並び替えに使用するクラス(解説:PlayerList、PlayerView共通)
    ///////////////////////////////////////////

    public class ListViewItemComparer : IComparer
    {
        private int m_column;
        private static int m_order = 1;    //毎回呼ばれるたびに正順、逆順を交代させるフラグ
        //ListViewItemComparerクラスのコンストラクタ
        public ListViewItemComparer(int col)
        {
            m_column = col;
            m_order *= -1;    //ListViewItemComparerが呼ばれる度に正順、逆順が変更される
        }
        //xがyより小さいときはマイナスの数、大きいときはプラスの数、同じときは0を返す
        public int Compare(object x, object y)
        {
            //ListViewItemの取得
            ListViewItem itemx = (ListViewItem) x;
            ListViewItem itemy = (ListViewItem) y;
            //xとyを文字列として比較する
            return string.Compare(itemx.SubItems[m_column].Text, itemy.SubItems[m_column].Text) * m_order;
        }
    }

    //////////////////////
    //InputBet ダイアログ
    //////////////////////

    public class InputBetDlg : Form
    {
        //クラスメンバープロパティ・変数
        public int Bet {get; private set;}    //賭金
        int Max;                            //Playerの所持金残高
        ComboBox BetList;

        public InputBetDlg(Icon ico, Player pl)    //icoは親のアイコンを承継する。plはPlayerクラス
        {
            //ダイアログの属性設定
            this.Text = "賭金設定";
            this.ClientSize = new Size(320, 214);
            this.MaximizeBox = false;        // 最大化ボタン
            this.MinimizeBox = false;        // 最小化ボタン
            this.ShowInTaskbar = false;        //タスクバー上表示
            this.FormBorderStyle = FormBorderStyle.FixedDialog;        // 境界のスタイル
            this.StartPosition = FormStartPosition.CenterParent;    // 親フォームの中央に配置
            //所持金確認と賭金初期設定
            Max = pl.Capital;
            Bet = pl.Bet();
            //コントロール設定
            //アイコン表示用ラベル
            Label imglabel = new Label();
            imglabel.Size = new Size(40, 40);
            imglabel.Location = new Point(10, 10);
            imglabel.BorderStyle = BorderStyle.Fixed3D;
            imglabel.Image = ico.ToBitmap();    //親のシステムアイコン
            this.Controls.Add(imglabel);
            //スタッツ表示用ラベル
            Label label1 = new Label();
            label1.Size = new Size(ClientSize.Width - imglabel.Width - 30, 18);
            label1.Location = new Point(imglabel.Width + 20, 10);
            label1.BorderStyle = BorderStyle.Fixed3D;
            label1.Text = "今回の勝負数\t:" + pl.ThisGames.ToString();
            label1.TextAlign = ContentAlignment.MiddleLeft;
            Label label2 = new Label();
            label2.Size = new Size(ClientSize.Width - imglabel.Width - 30, 18);
            label2.Location = new Point(imglabel.Width + 20, label1.Size.Height + 20);
            label2.BorderStyle = BorderStyle.Fixed3D;
            label2.Text = "今回の勝数\t:" + pl.ThisWins.ToString();
            label2.TextAlign = ContentAlignment.MiddleLeft;
            Label label3 = new Label();
            label3.Size = new Size(ClientSize.Width - imglabel.Width - 30, 18);
            label3.Location = new Point(imglabel.Width + 20, label1.Size.Height + label2.Size.Height + 30);
            label3.BorderStyle = BorderStyle.Fixed3D;
            label3.Text = "今回の負数\t:" + pl.ThisLoses.ToString();
            label3.TextAlign = ContentAlignment.MiddleLeft;
            Label label4 = new Label();
            label4.Size = new Size(ClientSize.Width - imglabel.Width - 30, 18);
            label4.Location = new Point(imglabel.Width + 20, label1.Size.Height + label2.Size.Height + label3.Size.Height + 40);
            label4.BorderStyle = BorderStyle.Fixed3D;
            label4.Text = "現在の所持金\t:" + String.Format("{0:C}", pl.Capital);
            label4.ForeColor = Color.Red;    //注意の為に赤字にする
            label4.TextAlign = ContentAlignment.MiddleLeft;
            Label label5 = new Label();
            label5.Size = new Size(ClientSize.Width - imglabel.Width - 30, 18);
            label5.Location = new Point(imglabel.Width + 20, label1.Size.Height + label2.Size.Height + label3.Size.Height + label3.Size.Height + 50);
            label5.BorderStyle = BorderStyle.Fixed3D;
            label5.Text = "PCの推奨賭金\t:" + String.Format("{0:C}", Bet);
            label5.ForeColor = Color.Blue;    //注意の為に青字にする
            label5.TextAlign = ContentAlignment.MiddleLeft;
            this.Controls.Add(label1);
            this.Controls.Add(label2);
            this.Controls.Add(label3);
            this.Controls.Add(label4);
            this.Controls.Add(label5);
            //賭金入力コンボボックス
            BetList = new ComboBox();
            BetList.Size = new Size(ClientSize.Width - imglabel.Width - 30, 24);
            BetList.Location = new Point(imglabel.Width + 20, label1.Size.Height + label2.Size.Height  + label3.Size.Height + label4.Size.Height + label5.Size.Height + 60);
            BetList.DropDownStyle = ComboBoxStyle.DropDownList;    //勝手な金額の入力はできない
            BetList.Text = String.Format("{0:C}", Bet);
            for(int i = 1; i <= 20; i++)    //1000円単位賭金リスト
            {
                BetList.Items.Add(String.Format("{0:C}", i * 1000));
            }
            this.Controls.Add(BetList);
            //決定ボタン
            Button btnOK = new Button();
            btnOK.Size = new Size(54, 28);
            btnOK.Location = new Point(ClientSize.Width - btnOK.Width - 10, ClientSize.Height - btnOK.Height - 10);
            btnOK.Text = "決定";
            btnOK.Click += new EventHandler(OnOK_Click);
            this.Controls.Add(btnOK);
        }

        private void OnOK_Click(object sender, EventArgs e)
        {
            //賭金コンボボックスが選択されたならその金額、そうでなければpl.Bet()のままとなる
            if(BetList.SelectedIndex > -1)
            {
                Bet = (BetList.SelectedIndex + 1) * 1000;
                if(Bet > Max)
                {
                    MessageBox.Show("選択金額が所持金を超えたので所持金以内とします。", "警告", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                    Bet = (Max / 1000) * 1000;    //千円単位端数切り捨て
                }
            }
            else
                MessageBox.Show("金額が選択されなかったので、賭金はPCの推奨する" + String.Format("{0:C}", Bet) + "とします。", "確認", MessageBoxButtons.OK, MessageBoxIcon.Question);
            Close();
        }
    }

    /////////////////////
    //Version ダイアログ
    /////////////////////

    public class VersionDlg : Form
    {
        public VersionDlg(Icon ico)
        {
            //ダイアログの属性設定
            this.Text = "バーション情報";
            this.ClientSize = new Size(320, 100);
            this.MaximizeBox = false;        // 最大化ボタン
            this.MinimizeBox = false;        // 最小化ボタン
            this.ShowInTaskbar = false;        //タスクバー上表示
            this.FormBorderStyle = FormBorderStyle.FixedDialog;        // 境界のスタイル
            this.StartPosition = FormStartPosition.CenterParent;    // 親フォームの中央に配置
            //コントロールの属性設定
            Button btnOK = new Button();
            btnOK.Size = new Size(40, 28);
            btnOK.Location = new Point(ClientSize.Width - btnOK.Width - 10, (ClientSize.Height - btnOK.Height) / 2);
            btnOK.Text = "OK";
            btnOK.Click += new EventHandler(OnOK_Click);
            Label imglabel = new Label();
            imglabel.Size = new Size(40, 40);
            imglabel.Location = new Point(10, (ClientSize.Height - imglabel.Height) / 2);
            imglabel.BorderStyle = BorderStyle.Fixed3D;
            imglabel.Image = ico.ToBitmap();    //親のシステムアイコン
            Label label = new Label();
            label.Size = new Size(ClientSize.Width - imglabel.Width - btnOK.Width - 40, ClientSize.Height- 20);
            label.Location = new Point(imglabel.Width + 20, (ClientSize.Height - label.Height) / 2);
            label.BorderStyle = BorderStyle.Fixed3D;
            label.Text = "Chinchirorin Version 1.0\r\nCopyright (c) 2023 by Ysama\r\n";
            label.TextAlign = ContentAlignment.MiddleCenter;
            label.Font = new Font("Times New Roman", 10, FontStyle.Bold);
            this.Controls.Add(btnOK);
            this.Controls.Add(imglabel);
            this.Controls.Add(label);
        }

        private void OnOK_Click(object sender, EventArgs e)
        {
            Close();
        }
    }
}

 

以上です。あともう一回だけ、コンパイルと使い勝手等について解説をしようかと思います。

あ"~、疲れた。

 

なお、WPFとの悪戦苦闘も終わりました。次のネタにしますので、ご期待ください。

また今日もWPFと格闘してしまいましたが、チンチロリンを先に済ませておきましょう。

 

(解説(2)からの続き) 

       /////////////////////
        //チンチロリン一勝負
        /////////////////////

        private bool Match(int roller)    //親Bakuto[Banker]と子Bakuto[roller]の対局
        {
            //戻り値(true-親落ち、false-続投)
            bool change = false;
            //賽子を表示する(解説:ここでDiceの登場です。)
            dice1.Visible = true;
            dice2.Visible = true;
            dice3.Visible = true;
            int result = 0;        //賽子を振った結果(WinOrNotメソッドの戻り値)
            //親(Banker)の投賽
            for(int i = 0; i < 3; i++)     //目無し(0)の場合、3回迄続けられる
            {
                //投賽処理
                dice1.DiceSound(false);        //代表して投賽音を鳴らす
                result = WinOrNot(dice1.Roll(), dice2.Roll(), dice3.Roll());
                if(result != 0)    //解説:「目無し」でなければ処理を続けます。
                    break;
            }
            //結果処理
            string str = "";
            if(result < 4)
            {    //resultは勝ち(正)または負け(負)で掛金の倍数
                if(result == 0)    //解説:親の「目無し(負け)」で「親落ち」です。
                {
                    str = "親の負け:目無しでした。";
                    Bakuto[Banker].Update(-LastBet[roller]);
                    playerList.SetTextInSubItems(Banker, 3, String.Format("{0:C}", -LastBet[roller]));
                    Bakuto[roller].Update(LastBet[roller]);
                    playerList.SetTextInSubItems(roller, 3, String.Format("{0:C}", LastBet[roller]));
                    change = true;
                }
                else if(result > 0)
                {
                    str = "親の勝ち:親の" + result.ToString() + "倍取りです。";
                    Bakuto[Banker].Update(LastBet[roller] * result);
                    playerList.SetTextInSubItems(Banker, 3, String.Format("{0:C}", LastBet[roller] * result));
                    Bakuto[roller].Update(-LastBet[roller] * result);
                    playerList.SetTextInSubItems(roller, 3, String.Format("{0:C}", -LastBet[roller] * result));
                 }
                else
                {
                    str = "親の負け:親の" + (-result).ToString() + "倍付けです。";
                    Bakuto[Banker].Update(LastBet[roller] * result);
                    playerList.SetTextInSubItems(Banker, 3, String.Format("{0:C}", LastBet[roller] * result));
                    Bakuto[roller].Update(LastBet[roller] * -result);
                    playerList.SetTextInSubItems(roller, 3, String.Format("{0:C}", LastBet[roller] * -result));
                    change = true;
                }
            }
            else
            {
                //親の出目 = result -2;
                int banker_roll = result -2;    //2~5の場合の親の出目
                MessageBox.Show("親の出目は" + banker_roll.ToString() + "でした。", "親の出目", MessageBoxButtons.OK, MessageBoxIcon.Information);
                //子(roller)の投賽(解説:親の出目が2~5の時に初めて子の投賽が許されます。)
                for(int i = 0; i < 3; i++)     //目無し(0)の場合、3回迄続けられる
                {
                    //投賽処理(解説:親と同じ処理です。)
                    dice1.DiceSound(false);        //代表して投賽音を鳴らす
                    result = WinOrNot(dice1.Roll(), dice2.Roll(), dice3.Roll());
                    if(result != 0)
                        break;
                }
                //結果処理
                if(result < 4)
                {    //resultは勝ち(正)または負け(負)で掛金の倍数
                    if(result == 0)
                    {
                        str = "子の負け:目無しでした。";
                        Bakuto[Banker].Update(LastBet[roller]);
                        playerList.SetTextInSubItems(Banker, 3, String.Format("{0:C}", LastBet[roller]));
                        Bakuto[roller].Update(-LastBet[roller]);
                        playerList.SetTextInSubItems(roller, 3, String.Format("{0:C}", -LastBet[roller]));
                    }
                    else if(result > 0)
                    {
                        str = "子の勝ち:子の" + result.ToString() + "倍取りです。";
                        Bakuto[Banker].Update(-LastBet[roller] * result);
                        playerList.SetTextInSubItems(Banker, 3, String.Format("{0:C}", -LastBet[roller] * result));
                        Bakuto[roller].Update(LastBet[roller] * result);
                        playerList.SetTextInSubItems(roller, 3, String.Format("{0:C}", LastBet[roller] * result));
                    }
                    else
                    {
                        str = "子の負け:子の" + (-result).ToString() + "倍付けです。";
                        Bakuto[Banker].Update(LastBet[roller] * -result);
                        playerList.SetTextInSubItems(Banker, 3, String.Format("{0:C}", LastBet[roller] * -result));
                        Bakuto[roller].Update(LastBet[roller] * result);
                        playerList.SetTextInSubItems(roller, 3, String.Format("{0:C}", LastBet[roller] * result));
                    }
                }
                else    //解説:ここで初めて親の出目と子の出目が比較されます。
                {
                    //子の出目 = result -2;
                    if(banker_roll > result - 2)
                    {
                        str = "子の負け:親(" + banker_roll.ToString() + ")対子(" + (result - 2).ToString() + ")で負け。";
                        Bakuto[Banker].Update(LastBet[roller]);
                        playerList.SetTextInSubItems(Banker, 3, String.Format("{0:C}", LastBet[roller]));
                        Bakuto[roller].Update(-LastBet[roller]);
                        playerList.SetTextInSubItems(roller, 3, String.Format("{0:C}", -LastBet[roller]));
                    }
                    else if(banker_roll == result - 2)
                    {
                        Bakuto[Banker].Update();
                        Bakuto[roller].Update();
                        str = "引き分け:親(" + banker_roll.ToString() + ")対子(" + (result - 2).ToString() + ")で分け。";
                    }
                    else
                    {
                        str = "子の勝ち:親(" + banker_roll.ToString() + ")対子(" + (result - 2).ToString() + ")で勝ち。";
                        Bakuto[Banker].Update(-LastBet[roller]);
                        playerList.SetTextInSubItems(Banker, 3, String.Format("{0:C}", -LastBet[roller]));
                        Bakuto[roller].Update(LastBet[roller]);
                        playerList.SetTextInSubItems(roller, 3, String.Format("{0:C}", LastBet[roller]));
                    }
                }
            }
            MessageBox.Show(str, "チンチロリン勝負", MessageBoxButtons.OK, MessageBoxIcon.Information);
            //賽子を非表示にする
            dice1.Visible = false;
            dice2.Visible = false;
            dice3.Visible = false;
            //戻り値がtrueなら親落ち
            return change;
        }

        //////////////////////////////////////////
        //チンチロリン勝敗判定(解説:以下は「戻り値」の解説です。)
        //戻り値:0(出目無し-3回まで投賽可能)
        //正-勝  1-1倍取り(出目6)
        //負-負  2-2倍取り(シゴロ-456)
        //        3-3倍取り(アラシ-ゾロ目)
        //       -1-1倍付け(出目1)
        //       -2-2倍付け(ヒフミ-123)
        //     4~7-出目の数(2~5の目)
        //////////////////////////////////////////

        private int WinOrNot(int D1, int D2, int D3)
        {
            int Num_OR = D1 | D2 | D3;
            int Num_AND = D1 & D2 & D3;
            int Num_XOR = D1 ^ D2 ^ D3;
            bool Num_EQ = (D1 == D2) | (D2 == D3) | (D3 == D1);
            if(Num_OR == Num_AND)                    //アラシの場合
            {
                return 3;
            }
            else if(Num_XOR == 7 && Num_AND == 4)    //456の場合
            {
                return 2;
            }
            else if(Num_XOR == 0 && Num_OR == 3)    //123の場合
            {
                return -2;
            }
            else if(Num_EQ)                            //目が出た場合
            {
                if(Num_XOR == 6)                    //6の目の場合
                {
                    return 1;
                }
                else if(Num_XOR == 1)                //1の目の場合
                {
                    return -1;
                }
                else                                //その他の目が出た場合
                {
                    return Num_XOR + 2;
                }
            }
            else                                    //役も目も出ない場合
                return 0;
        }

        ///////////////
        //親落ち・変更
        ///////////////

        private void ChangeBanker()
        {
            //旧親の〇印を消す(解説:親が落ちたので。)
            playerList.SetTextInSubItems(Banker, 1, "");
            //廻り胴なので次の親候補を仮親とする
            Banker++;
            if(Banker == NoOfBakuto)    //既に最後の博徒([NoOfBakuto - 1])まで来ていたら
                Banker -= NoOfBakuto;    //最初の博徒(0)に戻る
            //仮親が破産者の場合、その次にする
            for(int i = 0; i < NoOfBakuto - 1; i++)    //前の親は対象外なので(博徒数- 1)繰り返す
            {
                if(Bakuto[Banker].IsBankrupt)        //破産チェック
                {
                    Banker++;
                    if(Banker == NoOfBakuto)        //既に最後の博徒(NoOfBakuto - 1)まで来ていたら
                        Banker -= NoOfBakuto;        //最初の博徒(0)に戻る
                }
                else                                //破産者ではない
                    break;                            //その博徒に決定する
            }
            //新親に〇印をつける
            playerList.SetTextInSubItems(Banker, 1, "〇");
            //親の表示(解説:ステータスバーに表示します。)
            tssl[1].Text = Bakuto[Banker].Name;
        }
    }


    /////////////////////
    // PlayerListクラス
    /////////////////////

    public class PlayerList : Form
    {
        //フォームの「閉じるボタン」を無効化する(解説:これをC#では用意していませんでした。)
        //出典:https://www.codeproject.com/Articles/20379/Disabling-Close-Button-on-Forms

        private const int CP_NOCLOSE_BUTTON = 0x200;
        protected override CreateParams CreateParams
        {
            get
            {
                CreateParams myCp = base.CreateParams;
                myCp.ClassStyle = myCp.ClassStyle | CP_NOCLOSE_BUTTON ;
                return myCp;
            }
        }

        //クラスメンバー変数
        public string Players_List = "";        //カンマ区切りプレーヤーリスト
        ListView player_List;                    //プレーヤーリスト用リストビュー
        ContextMenu contextMenu;                //コンテキストメニュー
        MenuItem mi1, mi2, mi3;                    //コンテキストメニューアイテム

        public PlayerList()
        {
            this.FormBorderStyle = FormBorderStyle.FixedSingle;    //サイズの変更を不可にする
            this.ClientSize = new Size(640, 137);    //親子合計6名
            this.BackColor = SystemColors.Window;
            this.MaximizeBox = false;                //最大化ボタンを非表示
            this.MinimizeBox = true;                //最小化ボタンを表示
            this.ShowInTaskbar = false;                //タスクバー上表示
            this.Left = 570;
            this.Top = 100;
            this.StartPosition = FormStartPosition.Manual;
            this.Text = "Player List";
            this.Load += new EventHandler(PlayerList_Load);
        }

        //WM_CREATE時処理
        private void PlayerList_Load(object sender, EventArgs e)
        {
            //リストビューの生成とプロパティの設定
            player_List = new ListView();
            player_List.Size = new Size(ClientSize.Width - 20, ClientSize.Height - 20);    //サイズ
            player_List.Location = new Point(10,  10);            //位置
            player_List.Anchor = (AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Bottom | AnchorStyles.Right);
            player_List.FullRowSelect = true;                    //一行選択
            player_List.GridLines = true;                        //グリッドラインの表示
            player_List.MultiSelect = true;                        //複数選択を許す
            player_List.HoverSelection = false;                    //ポイントで選択できるようにする
            player_List.Activation = ItemActivation.OneClick;    //シングルクリック選択
            player_List.CheckBoxes = false;                        //チェックボックス無効化
            player_List.View = View.Details;                    //リストビューの表示方法
            player_List.Scrollable = true;                        //スクロールバーを表示
            //イベントハンドラの追加
            player_List.ColumnClick += new ColumnClickEventHandler(OnLV_ColumnClick);
            //ヘッダー定義
            player_List.Columns.Add("博徒名", 160, HorizontalAlignment.Left);
            player_List.Columns.Add("親", 32, HorizontalAlignment.Center);
            player_List.Columns.Add("賭金", 80, HorizontalAlignment.Center);
            player_List.Columns.Add("勝負結果", 344, HorizontalAlignment.Center);
            player_List.Columns.Add("破産者", 80, HorizontalAlignment.Center);
            player_List.Columns.Add("累計勝負数", 120, HorizontalAlignment.Center);
            player_List.Columns.Add("累計勝数", 120, HorizontalAlignment.Center);
            player_List.Columns.Add("累計負数", 120, HorizontalAlignment.Center);
            player_List.Columns.Add("累計勝率", 120, HorizontalAlignment.Center);
            //コンテキストメニュー関連
            mi1 = new MenuItem();
            mi1.Index = 0;
            mi1.Text = "博徒の読み込み";
            mi2 = new MenuItem();
            mi2.Index = 0;
            mi2.Text = "博徒の削除";
            mi2.Enabled = false;                //博徒を読み込んだ時に有効にする
             mi3 = new MenuItem();
            mi3.Index = 0;
            mi3.Text = "確定";
            mi3.Enabled = false;                //定員が6名以内になった時に有効にする
            contextMenu = new ContextMenu();
            contextMenu.MenuItems.Add(mi1);
            contextMenu.MenuItems.Add(mi2);
            contextMenu.MenuItems.Add("-");        //セパレーター
            contextMenu.MenuItems.Add(mi3);
             player_List.ContextMenu = contextMenu;
            mi1.Click += new EventHandler(Read_Players);
            mi2.Click += new EventHandler(Del_Player);
            mi3.Click += new EventHandler(Done);
            //FormにListViewを追加
            this.Controls.Add(player_List);
        }

        //player_Listのコラムがクリックされた時
        private void OnLV_ColumnClick(object sender, ColumnClickEventArgs e)
        {
            //ListViewItemSorterを指定する
            player_List.ListViewItemSorter = new ListViewItemComparer(e.Column);
            //正順、逆順交互に並び替える(ListViewItemSorterを設定するとSortが自動的に呼び出される)
        }

        //「博徒の読み込み」処理
        public void Read_Players(object sender, EventArgs e)
        {
            //Playersフォールダーのデータファイルを読む(解説:ここではPlayerインスタンスは作りません。)
            GetDataFiles();
            if(player_List.Items.Count > 2)    //最低でも博徒は2名必要
                mi2.Enabled = true;
        }

        //「博徒の削除」処理(解説:「博徒ファイル」ではなく、リストビュー上の表示です。)
        public void Del_Player(object sender, EventArgs e)
        {
            int count = player_List.Items.Count;
            if(count > 2)
            {
                foreach(ListViewItem item in player_List.SelectedItems)
                {
                    DialogResult dr = MessageBox.Show("博徒" + item.Text + "を削除しますか?", "確認", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
                    if(dr == DialogResult.Yes)
                    {
                        //選択された博徒を削除する
                        item.Remove();
                        count--;
                        if(count == 2)
                        {
                            MessageBox.Show("これ以上博徒を削除できません。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                            mi2.Enabled = false;
                            break;
                        }
                    }
                }
                if(count > 6)
                    MessageBox.Show("まだ定員(6名)を" + (count - 6) + "名オーバーしています。除外する博徒をクリックしてください。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                else
                    mi3.Enabled = true;    //解説:「確定」メニューを有効にします。
            }
            else
            {
                MessageBox.Show("これ以上博徒を削除できません。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                mi2.Enabled = false;
            }
            return;
        }

        //「確定」処理
        public void Done(object sender, EventArgs e)
        {
            //博徒名のリストを作成する
            for(int i = 0; i < player_List.Items.Count; i++)
            {
                Players_List += player_List.Items[i].Text.TrimEnd();    //Textには"\r\n"が付いているので
                if(i < player_List.Items.Count - 1)
                    Players_List += ",";
            }
            //ポップアップメニューを無効にする(解説:一旦確定したら後は編集できません。)
            mi1.Enabled = false;
            mi2.Enabled = false;
            mi3.Enabled = false;
            MessageBox.Show("参加博徒が確定しました\r\nChinchirorin Casinoに戻って「開帳」メニューを選択してください。", "参加博徒確定", MessageBoxButtons.OK, MessageBoxIcon.Information);
            //親フォームにフォーカスを当てる(解説:これをしないと二度クリックする必要があります。)
            this.Owner.Activate();
        }

        //Playersフォールダーの*.datファイルを取得
        public void GetDataFiles()
        {
            //ListViewのデータをクリアする
            player_List.Items.Clear();
            //Playersフォールダーの*.datファイルを表示
            IEnumerable<string> files = null;
            files = Directory.EnumerateFiles(".\\Players", "*.dat", SearchOption.TopDirectoryOnly);
            int count = 0;
            foreach (string file in files)
            {
                string temp = file.Replace(".\\Players\\", "");
                Player pl = new Player(temp.Replace(".dat", ""));    //解説:読み込み用の使い捨てローカルインスタンスです。
                ListViewItem item = player_List.Items.Add(pl.Name);
                item.SubItems.Add("");    //解説:親か否かは未定です。
                item.SubItems.Add("");    //解説:賭金は未定です。
                item.SubItems.Add("");    //解説:勝負結果は未定です。
                if(pl.IsBankrupt)    //解説:破産者か否か、です。
                    item.SubItems.Add("Yes");
                else
                    item.SubItems.Add("-");
                //解説:以下勝負数、勝ち数、負け数の累計と勝率です。

                item.SubItems.Add(String.Format("{0:#,0}", pl.TotalGames));
                item.SubItems.Add(String.Format("{0:#,0}", pl.TotalWins));
                item.SubItems.Add(String.Format("{0:#,0}", pl.TotalLoses));
                if(pl.TotalGames > 0)
                {
                    double rate = (double)pl.TotalWins / (double)pl.TotalGames;
                    item.SubItems.Add(String.Format("{0:P3}", rate));
                }
                else
                    item.SubItems.Add("N/A");    //解説:未勝負なので。
                count++;
            }
            if(count > 6)
                MessageBox.Show("定員(6名)を" + (count - 6) + "名オーバーしています。除外する博徒をクリックしてください。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
            else
                mi3.Enabled = true;
        }

        //PlayerのSubItemsに文字列を設定する(解説:汎用メソッドとして作りました。)
        public bool SetTextInSubItems(int row, int column, string data)
        {
            if(column <= 0 || column > 3)        //エラーチェック
                return false;
            if(row >= player_List.Items.Count)
                return false;
            player_List.Items[row].SubItems[column].Text = data;
            player_List.Refresh();                //再描画
            return true;
        }
    }

(解説(4)へ続く)

 

(注意:ブログが長すぎてエラーになったので、(2)~(4)迄3回に分けました。)

スミマセン、ご無沙汰です。

Chinchirorinの解説を終えるよりも早く、次のネタ(所謂WPF)に遭遇し、嵌ってしまいました。(未だに泥沼から抜け出ていません。もう少し勉強しなくては!)

 

さて、

気を取り直して、チンチロの解説をしてゆきましょう。プログラムコメントの他、「//解説:」で追加説明を加えてゆきます。

 

【Chinchirorin.cs】

///////////////////////////////////////
// Chinchirorin.cs - Chinchirorin Game
// Copyright (c) 2023 by Ysama
///////////////////////////////////////

using System;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Imaging;
using System.Reflection;            //Assemblyの使用の為
using System.Resources;                //リソース関係クラス等の使用の為
using System.IO;                    //ファイル操作使用の為
using System.Linq;                    // EnumerateFiles の使用の為
using System.Collections;            //IComparerの使用の為
using System.Collections.Generic;    // List<T> クラス使用の為
using System.Diagnostics;            //ProcessStartInfoの使用の為
//カスタムDLL(解説:これらについては既に前回にやりましたね。)
using Dices;                        //Diceコントロールを使う為
using Players;                        //Playerクラスを使うy為

namespace Chinchirorin
{
    //エントリーポイントクラス
    class WinMain
    {
        [STAThread]
        public static void Main()
        {
            Application.Run(new Casino());
        }
    }

    ///////////////////////
    //Casinoフォームクラス(解説:Dice、Playerとの関係を含めこれを復習してください。)
    ///////////////////////

    public partial class Casino : Form
    {
        //ウィンドウフォーム関連変数(全てprivate)
        MenuItem miOpen, miGo;                        //有効無効化処理を行うメニューアイテム(解説:メソッドから見えるように)
        ToolStrip toolStrip;                        //ツールバー
        ToolStripButton[] toolStripButton;            //ツールバーボタン
        StatusStrip statusStrip;                    //ステータスバー
        ToolStripStatusLabel[] tssl;                //ステータスバーラベル
        Dice dice1, dice2, dice3;                    //Diceコントロール
        //子ウィンドウフォーム変数(private
        PlayerList playerList;                        //PlayerListフォーム
        //ゲーム関連変数(全てprivate)
        int NoOfBakuto;                                //Playerの数
        int NoOfBankrupt;                            //破産Playerの数
        Player[] Bakuto;                            //Player配列
        int[] LastBet;                                //PlayerがPlaceBetsを行った際に記録される
        int Banker;                                    //Player[]配列の添字

        //コンストラクター
        public Casino()
        {
            Assembly myOwn = Assembly.GetEntryAssembly();
            this.Icon = Icon.ExtractAssociatedIcon(myOwn.Location);    //プログラムアイコンをフォームにつける
            this.ClientSize = new Size(454, 407);    //メニューバー+ツールバー+ステータスバー = 67
            this.MinimumSize = new Size(470, 446);    //幅 +16(8 x 2)、高さ +39
            this.Left = 100;
            this.Top = 100;
            this.StartPosition = FormStartPosition.Manual;
            this.BackColor = SystemColors.Window;
            this.BackgroundImageLayout = ImageLayout.Zoom;
            this.Text = "Chinchirorin Casino";
            this.Load += new EventHandler(Casino_Load);
        }

        //デストラクター
        ~Casino()
        {
            //子フォームを廃棄する
            playerList.Close();
        }

        //WM_CREATE時処理
        private void Casino_Load(object sender, EventArgs e)
        {
            //メニュー作成
            SetMenu();
            //ツールバーとステータスバー作成
            SetBars();
            //賽子の作成
            SetDices();
            //PlayerListを子フォームとして表示する(解説:これは所謂「Modeless Dialog」です。)
            playerList = new PlayerList();
            playerList.Show(this);
        }

        //サイズが変更された場合
        protected override void OnSizeChanged(EventArgs e)
        {
            base.OnSizeChanged(e);
            //Diceコントロールが作成される前に呼び出される可能性があるので
            if(dice1 != null && dice2 != null && dice3 != null)
            {    //解説:Diceコントロールを三つフォームの中央に並べて表示します。
                dice1.Location = new Point(ClientSize.Width / 2 - 32 - 16 - 64, ClientSize.Height / 2 - 32);
                dice2.Location = new Point(ClientSize.Width / 2 - 32, ClientSize.Height / 2 - 32);
                dice3.Location = new Point(ClientSize.Width / 2 + 32 + 16, ClientSize.Height / 2 - 32);
            }
        }

        //終了時処理
        protected override void OnFormClosing(FormClosingEventArgs e)
        {
            base.OnFormClosing(e);
            DialogResult dr = MessageBox.Show("終了しますか?", "確認", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
            if(dr == DialogResult.No)
                e.Cancel = true;
            else
            {
                //勝負終了後、参加博徒の最終成績を更新する(解説:昔、Playerのデストラクターでやっていた処理です。)
                for(int i = 0; i < NoOfBakuto; i++)
                    Bakuto[i].FinalRecord();
            }
        }

        //メニューの設定
        protected void SetMenu()
        {
            //メニュー作成
            MainMenu menu = new MainMenu();
            Menu = menu;
            //メニューアイテム付加
            MenuItem miFile = new MenuItem();        //「ファイル」メニュー
            miFile.Text = "ファイル(&F)";
            miFile.Index = 0;
            menu.MenuItems.Add(miFile);
            MenuItem miHelp = new MenuItem();        //「ヘルプ」メニュー
            miHelp.Text = "ヘルプ(&H)";
            miHelp.Index = 1;
            menu.MenuItems.Add(miHelp);
            miOpen = new MenuItem();                //「開帳」メニューアイテム
            miOpen.Text = "開帳(&O)";
            miOpen.Index = 0;
            miOpen.Click += OnOpen_Click;
            miOpen.Shortcut = Shortcut.CtrlO;
            miFile.MenuItems.Add(miOpen);
            miGo = new MenuItem();                    //「開始」メニューアイテム
            miGo.Text = "開始(&G)";
            miGo.Index = 1;
            miGo.Click += OnGo_Click;
            miGo.Shortcut = Shortcut.CtrlG;
            miGo.Enabled = false;                    //起動時は無効化する
            miFile.MenuItems.Add(miGo);
            miFile.MenuItems.Add("-");                //セパレーター
            MenuItem miExit = new MenuItem();        //「終了」メニューアイテム
            miExit.Text = "終了(&X)";
            miExit.Index = 2;
            miExit.Click += OnExit_Click;
            miExit.Shortcut = Shortcut.CtrlX;
            miFile.MenuItems.Add(miExit);
            MenuItem miHowtoUse = new MenuItem();    //「使い方」メニューアイテム
            miHowtoUse.Text = "使い方(&U)";
            miHowtoUse.Index = 0;
            miHowtoUse.Click += OnHowtoUse_Click;
            miHelp.MenuItems.Add(miHowtoUse);
            MenuItem miPlayerView = new MenuItem();    //「博徒参照」メニューアイテム
            miPlayerView.Text = "博徒ファイル参照(&P)";
            miPlayerView.Index = 0;
            miPlayerView.Click += OnPlayerView_Click;
            miHelp.MenuItems.Add(miPlayerView);
            MenuItem miVer = new MenuItem();        //「バージョン」メニューアイテム
            miVer.Text = "バージョン(&V)";
            miVer.Index = 1;
            miVer.Click += OnVersion_Click;
            miVer.Shortcut = Shortcut.CtrlV;
            miHelp.MenuItems.Add(miVer);
        }

        //ツールバーとステータスバーの設定
        protected void SetBars()
        {
            //フォームのレイアウトを一時停止
            this.SuspendLayout();
            //ToolStripクラスインスタンスの生成
            this.toolStrip = new ToolStrip();
            //ツールバーのレイアウトを一時停止
            this.toolStrip.SuspendLayout();
            //ToolStripButton配列を作成
            this.toolStripButton = new ToolStripButton[5];
            //本プログラムの埋め込みリソース(Chinchirorin)のリソースマネージャーを作成
            Assembly asm = Assembly.GetExecutingAssembly();
            ResourceManager rm = new ResourceManager("Chinchirorin", asm);
            //フォームの背景を貼る(解説:フリーのチンチロリンの画像を貼っています。)
            this.BackgroundImage = (Bitmap)rm.GetObject("bmpBack");                    //サイズ 454 x 340
            //ToolStripButton[0]を作成
            this.toolStripButton[0] = new ToolStripButton();
            this.toolStripButton[0].Text = "開帳(&O)";                                //テキスト設定
            this.toolStripButton[0].Image = (Bitmap)rm.GetObject("bmpOpen");        //画像設定
            this.toolStripButton[0].DisplayStyle = ToolStripItemDisplayStyle.Image;    //画像表示のみ
            this.toolStripButton[0].Click += OnOpen_Click;                            //Clickイベントハンドラ追加
            this.toolStrip.Items.Add(this.toolStripButton[0]);                        //ボタンを追加
            //ToolStripButton[1]を作成
            this.toolStripButton[1] = new ToolStripButton();
            this.toolStripButton[1].Text = "開始(&G)";                                //テキスト設定
            this.toolStripButton[1].Image = (Bitmap)rm.GetObject("bmpGo");            //画像設定
            this.toolStripButton[1].DisplayStyle = ToolStripItemDisplayStyle.Image;    //画像表示のみ
            this.toolStripButton[1].Click += OnGo_Click;                            //Clickイベントハンドラ追加
            this.toolStripButton[1].Enabled = false;                                //起動時は無効化する(解説:開帳との関係に注意してください。)
            this.toolStrip.Items.Add(this.toolStripButton[1]);                        //ボタンを追加
            //セパレーターを挿入
            this.toolStrip.Items.Add(new ToolStripSeparator());
            //ToolStripButton[2]を作成
            this.toolStripButton[2] = new ToolStripButton();
            this.toolStripButton[2].Text = "終了(&X)";                                //テキスト設定
            this.toolStripButton[2].Image = (Bitmap)rm.GetObject("bmpExit");        //画像設定
            this.toolStripButton[2].DisplayStyle = ToolStripItemDisplayStyle.Image;    //画像表示のみ
            this.toolStripButton[2].Click += OnExit_Click;                            //Clickイベントハンドラ追加
            this.toolStrip.Items.Add(this.toolStripButton[2]);                        //ボタンを追加
            //セパレーターを挿入
            this.toolStrip.Items.Add(new ToolStripSeparator());
            //ToolStripButton[3]を作成
            this.toolStripButton[3] = new ToolStripButton();
            this.toolStripButton[3].Text = "使い方(&U)";                            //テキスト設定
            this.toolStripButton[3].Image = (Bitmap)rm.GetObject("bmpHelp");        //画像設定
            this.toolStripButton[3].DisplayStyle = ToolStripItemDisplayStyle.Image;    //画像表示のみ
            this.toolStripButton[3].Click += OnHowtoUse_Click;                        //Clickイベントハンドラ追加
            this.toolStrip.Items.Add(this.toolStripButton[3]);                        //ボタンを追加
            //ToolStripButton[4]を作成
            this.toolStripButton[4] = new ToolStripButton();
            this.toolStripButton[4].Text = "バージョン情報(&V)";                    //テキスト設定
            this.toolStripButton[4].Image = (Bitmap)rm.GetObject("bmpVersion");        //画像設定
            this.toolStripButton[4].DisplayStyle = ToolStripItemDisplayStyle.Image;    //画像表示のみ
            this.toolStripButton[4].Click += OnVersion_Click;                        //Clickイベントハンドラ追加
            this.toolStrip.Items.Add(this.toolStripButton[4]);                        //ボタンを追加
            //ツールバーの設定
            this.Controls.Add(this.toolStrip);
            //ツールバーのレイアウトを再開
            this.toolStrip.ResumeLayout(false);
            this.toolStrip.PerformLayout();

            //StatusStripクラスインスタンスの生成
            this.statusStrip = new StatusStrip();
            //ステータスバーにパネルとテキストを追加
            tssl = new ToolStripStatusLabel[3];
            tssl[0] = new ToolStripStatusLabel();
            tssl[0].BorderSides = ToolStripStatusLabelBorderSides.All;
            tssl[0].BorderStyle = Border3DStyle.SunkenInner;
            tssl[0].BackColor = SystemColors.Control;
            tssl[0].Text = "Chinchirorin Version 1.0";
            tssl[0].AutoSize = true;
            tssl[0].TextAlign = ContentAlignment.MiddleLeft;
            tssl[1] = new ToolStripStatusLabel();
            tssl[1].BorderSides = ToolStripStatusLabelBorderSides.All;
            tssl[1].BorderStyle = Border3DStyle.SunkenInner;
            tssl[1].BackColor = SystemColors.Control;
            tssl[1].Text = "(親)";
            tssl[1].AutoSize = true;
            tssl[1].TextAlign = ContentAlignment.MiddleLeft;
            tssl[2] = new ToolStripStatusLabel();
            tssl[2].BorderSides = ToolStripStatusLabelBorderSides.All;
            tssl[2].BorderStyle = Border3DStyle.SunkenInner;
            tssl[2].BackColor = SystemColors.Control;
            tssl[2].Text = "対(子)";
            tssl[2].Spring = true;
            tssl[2].TextAlign = ContentAlignment.MiddleLeft;
            statusStrip.Items.AddRange(tssl);
            statusStrip.ShowItemToolTips = true;    //ToolTip表示
            this.Controls.Add(this.statusStrip);    //StatusStrip(ステータスバー)を追加

            //メインフォームのレイアウトを再開
            this.ResumeLayout(false);
            this.PerformLayout();
        }

        //Diceコントロールの作成
        protected void SetDices()
        {
            //Dice1
            dice1 = new Dice();
            dice1.Size = new Size(64, 64);
            dice1.Location = new Point(ClientSize.Width / 2 - 32 - 16 - 64, ClientSize.Height / 2 - 32);
            dice1.Anchor = (AnchorStyles.Top | AnchorStyles.Left);
            dice1.Visible = false;    //解説:最初は隠しておきます。(以下同じ)
            this.Controls.Add(dice1);

            //Dice2
            dice2 = new Dice();
            dice2.Size = new Size(64, 64);
            dice2.Location = new Point(ClientSize.Width / 2 - 32, ClientSize.Height / 2 - 32);
            dice2.Anchor = AnchorStyles.Top;
            dice2.Visible = false;
            this.Controls.Add(dice2);

            //Dice3
            dice3 = new Dice();
            dice3.Size = new Size(64, 64);
            dice3.Location = new Point(ClientSize.Width / 2 + 32 + 16, ClientSize.Height / 2 - 32);
            dice3.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
            dice3.Visible = false;
            this.Controls.Add(dice3);
        }

        //「開帳」メニュー処理
        private void OnOpen_Click(object sender, EventArgs e)
        {
            if(playerList.Players_List != "")    //解説:playListフォームからPlayer_ListというPlayer情報文字列を受けとります。
            {
                miOpen.Enabled = false;   //解説:参加博徒が確定したならば「開帳」は終了なので無効にします。
                miGo.Enabled = true;    //解説:一方、賭博は開始できますのでこれを有効にします。
                toolStripButton[0].Enabled = false;    //解説:ボタンも同じです。
                toolStripButton[1].Enabled = true;
                string [] names = playerList.Players_List.Split(',');    //解説:博徒名のcvsファイルを分解します。
                NoOfBakuto = names.Count();            //博徒の数
                Bakuto = new Player[NoOfBakuto];    //解説:以下配列に参加博徒情報を入れてゆきます。
                LastBet = new int[NoOfBakuto];    //解説:それぞれの参加博徒の「最後に行った賭金」です。(博徒の掛け金はBet()メソッドで決まりますが、乱数を使用しているので、必ずしも同じになりません。)
                for(int i = 0; i < NoOfBakuto; i++)
                {
                    Bakuto[i] = new Player(names[i]);    //解説:博徒のPlayerインスタンスを作り、ファイルから情報を読み込みます。
                }
                MessageBox.Show(playerList.Players_List + "のメンバーでチンチロリンの賭場を開きます。\r\n親決めの後、「開始」メニューを選択してください。", "開帳", MessageBoxButtons.OK, MessageBoxIcon.Information);
                //親決め
                SelectBanker(NoOfBakuto);            //結果はBankerに代入される
            }
            else    //解説:Player Listで博徒を確定していないとこのエラーが出ます。
                MessageBox.Show("博徒が選択されていません。\r\n\"Player List\"を右クリックし、参加する博徒を確定してください。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }

        //「開始」メニュー処理
        private void OnGo_Click(object sender, EventArgs e)
        {
            //破産博徒数をチェックする
            NoOfBankrupt = 0;
            for(int i = 0; i < NoOfBakuto; i++)
            {
                if(Bakuto[i].IsBankrupt)
                    NoOfBankrupt++;
            }
            //有効博徒が一人以下になった場合
            if(NoOfBakuto - NoOfBankrupt < 2)
            {
                MessageBox.Show("破産者多数で終了します。", "破産者多数による終了", MessageBoxButtons.OK, MessageBoxIcon.Information);
                return;
            }
            //コマを張る(掛け金を設定する)
            Place_Bets();    //解説:以下のメソッドで解説します。
            //チンチロリン勝負
            for(int i = 0; i < NoOfBakuto; i++)
            {
                //対戦当事者の表示(すぐに反映させるためにツールバーを再描画する)
                tssl[2].Text = " 対 " + Bakuto[i].Name;
                this.Refresh();    //解説:データを入れるだけではなく、再描画を行います。
                if(i == Banker)                //親であればスキップ(「親対親」の賭博は無いですよね?)
                    continue;
                if(Bakuto[i].IsBankrupt)    //破産者であればスキップ(破産者はかけられません。)
                    continue;
                if(Match(i))                //引数はBakuto[]配列の引数、戻り値は親落ちか否か(解説:親は決まっているので、'i'はこのIDです。)
                {
                    ChangeBanker();    //解説:親落ち条件が成立するとMatch()メソッドがtrueになりますので、親落ち処理を行います。
                    MessageBox.Show("親落ちしました。\r\n新たに「開始」メニューを選択してください。", "親落ち終了", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    Place_Bets(false);        //賭金」「勝負結果」をブランクにする(解説:同じような処理なのでここで行いました。)
                    return;
                }
                playerList.SetTextInSubItems(Banker, 3, "");        //Bankerの「勝負結果」をクリアする
                playerList.SetTextInSubItems(i, 2, "");                //roller(対戦する小方)の「賭金」をクリアする
                playerList.SetTextInSubItems(i, 3, "");                //roller(対戦する小方)の「勝負結果」をクリアする
            }
            MessageBox.Show("子方が一巡しました。\r\n新たに「開始」メニューを選択してください。", "一巡終了", MessageBoxButtons.OK, MessageBoxIcon.Information);
            Place_Bets(false);                //賭金」「勝負結果」をブランクにする
        }

        //「終了」メニュー処理
        private void OnExit_Click(object sender, EventArgs e)
        {
            Close();    //修了確認はOnClosingメソッドで行う
        }

        //「使い方」メニュー処理
        private void OnHowtoUse_Click(object sender, EventArgs e)
        {
            //HTML HelpでChinchirorinHelp.chm(解説:BCCForm and BCCSkeltonのMSCompAssに入っています)を表示する。
            ProcessStartInfo pInfo = new ProcessStartInfo();
            pInfo.FileName = "hh";
            pInfo.Arguments = ".\\ChinchirorinHelp.chm";
            Process.Start(pInfo);
        }

        //「博徒参照」メニュー処理
        private void OnPlayerView_Click(object sender, EventArgs e)
        {
            PlayerView pv = new PlayerView();    //解説:Modal Dialogのローカルインスタンスとして開きます。
            pv.ShowDialog(this);
            pv.Dispose();
        }

        //「バージョン」メニュー処理
        private void OnVersion_Click(object sender, EventArgs e)
        {
            VersionDlg verDlg = new VersionDlg(this.Icon);
            verDlg.ShowDialog();
            verDlg.Dispose();
        }

        /////////////
        //初回親決め
        /////////////

        private void SelectBanker(int num)
        {
            MessageBox.Show("参加者全員がサイコロを振って出した数が最も大きい者を親にします。\r\n同じ数なら先に振ったものが親となります。", "親決め", MessageBoxButtons.OK, MessageBoxIcon.Information);
            dice2.Visible = true;    //解説:真ん中の賽子を一つ使います。
            int Max = 0;
            for(int i = 0; i < num; i++)
            {
                dice2.DiceSound();
                int roll = dice2.Roll();
                MessageBox.Show(Bakuto[i].Name + "の出目は" + roll.ToString() + "です。", Bakuto[i].Name, MessageBoxButtons.OK, MessageBoxIcon.Information);
                if(Max < roll)
                {
                    Banker = i;
                    Max = roll;
                }
            }
            dice2.Visible = false;
            MessageBox.Show(Bakuto[Banker].Name + "が親になりました。", "親決め", MessageBoxButtons.OK, MessageBoxIcon.Information);
            playerList.SetTextInSubItems(Banker, 1, "〇");    //Player Listに親の〇をつける
            tssl[1].Text = Bakuto[Banker].Name;                //親の表示(解説:ステータスバーです。)
        }

        /////////////////////
        //コマ(賭金)を張る
        /////////////////////

        private void Place_Bets(bool flag = true)    //解説:このflagを使って処理用途を変えています。
        {
            for(int i = 0; i < NoOfBakuto; i++)
            {
                if(flag)    //引数をtrueにすると「賭金」を表示する
                {
                    if(i == Banker)                    //親であればスキップ(解説:親は賭けません。)
                        continue;
                    if(Bakuto[i].IsBankrupt)        //破産者であればスキップ
                        continue;
                    if(Bakuto[i].Name == "あなた")    //ユーザープレイヤーであれば(解説:「あなた」だけ手動です。)
                    {                                //賭金入力ダイアログで設定する(初期値はBet()の戻り値)
                        InputBetDlg ibDlg = new InputBetDlg(this.Icon, Bakuto[0]);
                        ibDlg.ShowDialog();
                        LastBet[i] = ibDlg.Bet;    //解説:賭金を記録します。
                        ibDlg.Dispose();
                    }
                    else
                        LastBet[i] = Bakuto[i].Bet();    //博徒の最新賭金を記録する
                    playerList.SetTextInSubItems(i, 2, String.Format("{0:C}", LastBet[i]));    //通貨表示
                }
                else        //引数をfalseにすると「賭金」「勝負結果」をブランクにする(//解説:消去機能だけです。)
                {
                    playerList.SetTextInSubItems(i, 2, "");
                    playerList.SetTextInSubItems(i, 3, "");
                }
            }
        }
(解説(3)へ続く)

実は今日も実際に使ってチェックして、ちょっとバグを発見しました。

その1:親と子が引き分けの際に本日の勝負数が+1にならない。(Chinchirorin.cs)

その2:賭金設定ダイアログの「今回の負数」も「今回の勝負数」を入れていた。(Chinchirorin.cs-「今回の勝負数」のコードをコピーして使用する際、修正するのを忘れた!)

 

ということで、これ以上はもうバグが出てこないだろう(Let's the fingers crossed!)と本日からプログラムの解説に移りたいと思います。解説は「全体の鳥瞰から細部に至る」「細部から積み上げて全体の鳥瞰に至る」どっちにするか迷いましたが、先ずは使用するDLLから始めて、本体(Chinchirorin.cs)に行きたいと思います。

 

1.Dice.dll

これはすでにこの回この回では使い方も)で説明しているので,、特に付け加えたり、変更もないのでスキップしましょう。

 

2.Player.dll

これはまだコードは紹介していませんでしたね。では、今回はこれを解説しましょう。

このクラスはチンチロリンの博徒に共通する「運(やツキ)』によって勝負の結果である『経験』が異なり、またその『経験』によって次の勝負に対する『気性(強気、弱気→賭金の意思決定に影響する』が決定される」という前提(注)で、原型として作りました。以下のコードはそれを頭に入れてご覧ください。

注:私自身の経験によります。

 

//////////////////////
//    Player.cs
//チンチロ賭博者クラス
//////////////////////

using System;
using System.IO;                        //StreamReader/Writerを使う為
using System.Text;                        //Encoderを使う為

namespace Players
{
    public class Player
    {
        //定数(代わり)
        const int MinBet = 1000;                        //最低掛け金\1,000(解説:安すぎると思われる方は変更してください。)
        //個性系変数
        public bool IsNew = true;                        //新規か否かのフラグ
        public bool IsBankrupt = false;                    //破産したか否かのフラグ
        public string Name;                                //賭博者の名前(愛称)-解説:既成の博徒名は「麻雀放浪記」のそれらを引用しています。
        //賭博系変数
        public int Capital;                                //掛け金の元手(解説:いわゆる「タネ銭」です。これはMinBet以上でなければなりません。
        public int Luck;                                //運、強気(bullish)弱気(bearish)の原因
        public int BullBear;                            //強気弱気(掛け金の多寡に影響する)
        //統計系変数
        public int TotalGames;                            //累計勝負数
        public int TotalWins;                            //累計勝ち数
        public int TotalLoses;                            //累計負け数
        public int ThisGames;                            //今回勝負数
        public int ThisWins;                            //今回勝ち数
        public int ThisLoses;                            //今回負け数
//解説:これら三つが「経験」です。
        //コンストラクター
        public Player(string name = "")
        {
            if(name == "")                //引数がない場合
                Name = "名無し";        //別途"*.Name ="で名前を設定する(設定しないと「名無し」Playerに統一される)
            else
            {
                Name = name;            //引数の名前を記録
                IsNew = !ReadData();    //ファイルがあれば既存プレーヤー(ない場合は新規)
                if(IsBankrupt)            //破産者は新規プレーヤーとなる(破産者のファイルは残る)
                {
                    IsNew = true;
                    IsBankrupt = false;
                    Name = "名無し";
                    Capital = 0;
                    Luck = 0;
                    BullBear = 0;
                    TotalGames = 0;
                    TotalWins = 0;
                    TotalLoses = 0;
                }
            }
            if(IsNew)
            {
                //「種銭」は新規のみ\10,000~\500,000(最低掛け金\1,000)とする
                Random rnd = new Random();
                Capital = (int)Math.Round((double)rnd.Next(MinBet * 10, MinBet * 500) / (double)1000,  MidpointRounding.AwayFromZero) * 1000;

//解説:乱数で最低賭金の10倍~500倍の数を求め、それを四捨五入し、千円単位にしています。
            }
        }

        //Playerファイルを読む
        private bool ReadData()
        {
            string data = "";                //空文字に初期化する
            //Exeファイルのフォールダーに"Playersフォールダー"が無ければ作成する
            if(!Directory.Exists(".\\Players"))
                Directory.CreateDirectory(".\\Players");
            //ファイル(UTF-8:Encoding.UTF8)を読む
            try
            {
                using(StreamReader sw = new StreamReader(".\\Players\\" + Name + ".dat", Encoding.UTF8, false))
                {
                    //ファイルを読む
                    data = sw.ReadToEnd();
                    if(data == "")            //ファイルが無く読み込めない場合
                        return false;
                    //文字列の行分割
                    string[] lines = data.Split(new[]{"\r\n","\n","\r"},StringSplitOptions.None);
                    //第1行(賭博者名, IsBankrupt)
                    string[] specs = lines[0].Split(',');
                    Name = specs[0];
                    if(!Boolean.TryParse(specs[1], out IsBankrupt))
                        return false;
                    //第2行(Capital、Luck、BullBear)
                    string[] gamble = lines[1].Split(',');
                    if(!Int32.TryParse(gamble[0], out Capital))
                        return false;
                    if(!Int32.TryParse(gamble[1], out Luck))
                        return false;
                    if(!Int32.TryParse(gamble[2], out BullBear))
                        return false;
                    //第3行(TotalGames、TotalWins、TotalLoses)
                    string[] stats = lines[2].Split(',');
                    if(!Int32.TryParse(stats[0], out TotalGames))
                        return false;
                    if(!Int32.TryParse(stats[1], out TotalWins))
                        return false;
                    if(!Int32.TryParse(stats[2], out TotalLoses))
                        return false;
//解説:賭博者ファイルは↑のように3行単位となっています。

                    //ファイルを閉じる
                    sw.Close();
                }
            }
            catch(Exception e)
            {
                //エラーコード
                //Console.WriteLine("Generic Exception Handler: {0}", e);

//解説:ここはすべてのエラーを通過させていますが、catch(System.IO.FileNotFoundException e)だけ通過させ、その他はthrowする、という方がスマートかもしれませんね。
                return false;
            }
            return true;
        }

        //Playerファイルを書く
        private void WriteData()
        {
            //ファイル(UTF-8:Encoding.UTF8)を書く
            using(StreamWriter sw = new StreamWriter(".\\Players\\" + Name + ".dat", false, Encoding.UTF8))
            {
                //第1行(賭博者名)
                string data = Name + "," + IsBankrupt.ToString() + Environment.NewLine;
                //第2行(Capital、Luck、BullBear)
                data += Capital.ToString() + "," + Luck.ToString() + "," + BullBear.ToString() + Environment.NewLine;
                //第3行(TotalGames、TotalWins、TotalLoses)
                data += TotalGames.ToString() + "," + TotalWins.ToString() + "," + TotalLoses.ToString() + Environment.NewLine;
                //ファイルを書く
                sw.Write(data);
                //ファイルを閉じる
                sw.Close();
            }
        }

        //掛け金を決定する(//解説:ここが冒頭で書いた「前提」です。)
        public int Bet()
        {
            //「種銭」残高チェック
            if(Capital < MinBet)        //掛け金がない場合
            {
                IsBankrupt = true;        //破産宣告され、
                return 0;                //"掛け金 0"(破産を表す)を返す
            }
            //変数宣言
            int sum = 0;                //掛け金
            int Max = 0;                //最大掛け金
            //累計勝率(0.5が標準)
            double totalRate = 0;
            if(TotalGames > 0)
                totalRate = (double)TotalWins / (double)TotalGames;
            //今回勝率(0.5が標準)
            double thisRate = 0;
            if(ThisGames > 0)
                thisRate = (double)ThisWins / (double)ThisGames;
            //運・ツキは今回勝率が累計平均よりも大きくなっているか否かによって判断される
            if(thisRate > totalRate)
                Luck++;
            else if(thisRate < totalRate)
                Luck--;
            //強気弱気は運・ツキと今回のゲーム数(実績)により判断する
            if(Luck > 0)
            {
                if(ThisGames > 5)        //最初の5回までは調整しない
                    BullBear++;            //強気+1
                if(ThisGames > 10)
                    BullBear++;            //強気+2
                if(ThisGames > 20)
                    BullBear++;            //強気+3
                //BullBearのMaxは10
                if(BullBear > 10)
                    BullBear = 10;
            }
            else
            {
                if(ThisGames > 5)        //最初の5回までは調整しない
                    BullBear--;            //強気-1
                if(ThisGames > 10)
                    BullBear--;            //強気-2
                if(ThisGames > 20)
                    BullBear--;            //強気-3
                //BullBearのMinは-10
                if(BullBear < -10)
                    BullBear = -10;
            }
            //掛け金決定算式
            Random rnd = new Random();
            //最初の5回まで(序盤)
            if(ThisGames <= 5)
            {
                Max = Capital / 10;        //最大掛け金は種銭の1/10
                //ツキが無ければ2倍まで
                if(BullBear <= 0)
                    sum = rnd.Next(MinBet, MinBet * 2);
                //ツいてくれば4倍まで
                else
                    sum = rnd.Next(MinBet, MinBet * 4);
            }
            //6~10回まで(中盤)
            else if(ThisGames <= 10)
            {
                Max = Capital / 5;        //最大掛け金は種銭の1/5
                //ツキが無ければ2倍まで
                if(BullBear <= 0)
                    sum = rnd.Next(MinBet, MinBet * 2);
                //まぁまぁなら2から4倍まで
                else if(BullBear < 2)
                    sum = rnd.Next(MinBet * 2, MinBet * 4);
                //ツいてくれば4から10から4倍まで
                else if(BullBear >= 2)
                    sum = rnd.Next(MinBet * 4, MinBet * 10);
            }
            //10回以降まで(後盤)
            else if(ThisGames > 10)
            {
                Max = Capital / 2;        //最大掛け金は種銭の1/2
                //ツキが無ければ最低掛け金
                if(BullBear < 0)
                    sum = MinBet;
                //まぁまぁなら2から4倍まで
                else if(BullBear < 2)
                    sum = rnd.Next(MinBet * 2, MinBet * 4);
                //ツいてくれば4から10倍まで
                else if(BullBear >= 2)
                    sum = rnd.Next(MinBet * 4, MinBet * 10);
                //更にツけば30倍まで
                else if(BullBear >= 4)
                    sum = rnd.Next(MinBet * 10, MinBet * 30);
            }
            //Maxによるチェック
            if(sum > Max)
                sum = Max;
            //sumを千円単位に四捨五入する
            sum = (int)Math.Round((double)sum / (double)1000,  MidpointRounding.AwayFromZero) * 1000;
            return sum;
        }
//解説:実際に運用してみると、皆「運・ツキ」がマイナス、「強気・弱気」もマイナスかゼロになり、掛け金が\1,000~\2,000になって、勝率が30~50%代になるという状況です。これは皆さんにも考えてもらって改造してもらうところでしょう。
        //勝負結果によりPlayerの記録を更新する(引き分けの場合は引数不要)戻り値は「賭博継続可(Boolean)」
        public bool Update(int rewards = 0)
        {
            //今回の勝負数を1つ増やす
            ThisGames++;
            //勝負結果を勝ち数、負け数に反映
            if(rewards == 0)        //分け
                return !IsBankrupt;
            else if(rewards > 0)    //勝ち
                ThisWins++;
            else                    //負け
                ThisLoses++;
            //「種銭」残高の増減
            Capital += rewards;
            //破産チェック
            return !(IsBankrupt = (Capital < MinBet));
        }

        //勝負終了後、最終成績を更新する
        public void FinalRecord()
        {
            //今回のデータを統計データに落とす
            TotalGames += ThisGames;
            TotalWins += ThisWins;
            TotalLoses += ThisLoses;
            //プレーヤーファイルを書き出す
            WriteData();
//解説:ここがこの間書いた、当初は"~Player()"というデストラクターにして大間違いの部分です。

        }
    }
}

 

このファイルをMSCompAssでターゲットを"Library"(DLLのこと)にしてコンパイルすれば、Player.dllが出来上がります。

前回、ほとんどVersion 1.0の完成、と言っておきながら、その後仕様変更を含む改造をいくつか行いました。

 

1.「賭金設定」ダイアログ

先ず「賭金設定」の所ですが、累計勝敗統計を示しても肝心の「お財布事情(所持金)」が分からなければ決められません。又、コンボボックスの入力欄に不正な入力(注)があった場合にも対処していませんでした。

注:オリジナル仕様も整数に変換できない場合はPC推奨額になるのですが、「所持金を超えた入力をされても認めてしまう」貸し越しOKの状態でした。

そういうわけで、まずPlayer.Capitalという所持金とPlayer.Bet()メソッドによるPC推奨額をラベル表示し、ComboBoxをReadOnlyにして\1,000から\20,000の枠の中で「所持金額限度チェック」をする方法を採りました。

また、金額を選択しない場合はPC推奨額になるのですが、(今までは黙ってそうしていたのですが)それをメッセージボックスでユーザーに伝えるようにしました。

 

2.PlayViewダイアログ

もう一つは前回も書いた、

最後に宿題として残しているのは、「今回のチンチロリン賭博の勝負数、勝ち数、負け数を『あなた(ユーザープレイヤー)』だけが見れるようにすべきか(現在も『賭金の設定』ダイアログで見れます)、他の博徒のスタッツも見れるようにすべきか?(注)というものです。

注:現在は参加博徒の選任(Player Listフォームで実施する)の際に、ListViewに「今回迄の」「累計勝負数、累計勝数、累計負数、勝率」を表示していますが、現在進行形の今回のデータは「あなた」しか表示していません。私はその方が、「てめーで覚えて、判断しろ」的な実際の賭場の感じが出ており、よいと思うのですが...

ですが、結局「博徒データファイルの管理」の観点から"Player List"フォームを改造して、"Player View"なるフォームのダイアログを作り、すべての博徒データの内訳を参照できるようにし、更に「博徒ファイルの新規作成()」と「博徒ファイルの削除」を行えるようにしました。

しかし、このお陰(「博徒ファイルの削除」を実行すること)で、その後オリジナルの仕様の潜在的な問題が浮上し、発見することが出来ました。

注:オリジナルの仕様では、博徒インスタンス(Playerクラスインスタンス)を「名前(文字列)」を与えて生成すると、インスタンス消滅時にデストラクターが"Players"フォールダーにdatファイルを書き出すようにしていました。(なお、名前を与えないで作成してしまうと「名無し」という名の博徒が名無し.datファイルで残るようにしています。)

 

3.C++とC#の相違-GC(ガーベージコレクション)の動作

ご存じの通り、私はC、C++で育ち、1年前の68歳の時からC#を学習し始めましたが、その際に低級言語C++と高級言語C#の違いを思い知らされました。しかし、今回C#のガーベージコレクション(プログラムによって確保された未解放のメモリーをまとめて清掃<解放>する掃除人)の動作を思い知らされました。

先ずC++がどのように動くのかをおさらいしましょう。末尾の【C++の局所変数の生成と消滅のテスト】をご覧ください。C++では局所変数(昔Cの時代では自動(auto)変数と呼ばれていた)は有効範囲(スコープ)の中で生成(スタックにメモリー領域が積まれる)し、スコープから出ると消滅(スタックが戻される)しました。その動作は即時的であり、時間差はありません

一方、C#ではWin32時代のリソース等「アンマネージ(unmanaged by .NET)」のものは(C++と同様)ユーザーが手動でメモリー管理しますが、「マネージド(managed by .NET)」のものはCLI(共通言語基盤-Common Language Infrastructure)のガーベージコレクション機能で掃除してもらえます。これはとてもありがたいのですが、

時間差が生じる

ということを理解していないと(私のように)躓きます。

例えば今回の問題は、Playerクラスのデストラクターに博徒データのファイル書き込み機能を付けたので、

(1)Chinchirorinを起動し、博徒(Playerクラスインスタンス)を確定(生成)する。

(2)ChinchirorinのPlayerViewダイアログである博徒のデータファイル(e.g. 博徒.dat)を削除する。(プログラムからも、またExplorerで確認してもそのファイルは削除されている。)

(3)Chinchirorinを終了する。(この段階でガーベージコレクターが働き、終了後「ふっと」死んだはずの"博徒.dat"ファイルがフォールダーに現れる。(Chinchirorin終了後、ガーベージコレクターが働き、デストラクター処理がなされて、ファイルが書き込まれる。それもプログラムで使う「参加博徒のPlayerインスタンス配列変数」のみならず、プログラム処理で使う「使い捨てPlayerインスタンス」でも生じるようである。)

(4)私は(愚かにも)即時的にメモリー開放を行わせるために

①意図的に{}内(スコープを限定させる)にインスタンス宣言を入れたり→ガーベージコレクターはつまらん仕事はあとでまとめてやるようで、直ぐには開放しない。

②敢えてデストラクターを呼ぼうとしたり→C#ではデストラクターは呼べない!

等したのですが、結局

C#では特定のメモリーをプログラマーが意図的に開放することは難しい(.NETというCLIが管理しているので)

ということを思い知らされました。

この為、Playerクラスの仕様を変更し、"~Player"(デストラクター)で行っていたチンチロリン参加博徒(Playerクラスインスタンス)の情報のファイル書き込み処理を通常のメソッド(void FinalRecord())に変更し、ファイル書き込みはプログラム終了時にメイン(Chinchirorin.exe)の終了処理段階で実施するように変更しました。(

注:実はこれでも「幽霊現象」は発生し得ます。Chinchirorinを実行し、参加博徒を確定し(それら博徒の情報はChinchirorinのメモリー内にある)、PlayerViewダイアログでそれら博徒のファイルを削除します。この段階ではファイルは消えますが、Chinchirorinが終了する際に(新しい勝負のデータを含めて更新した)博徒データがファイルに書き込まれますので、これら博徒のファイルは復活します。→しかし、

現在遊んでいるチンチロリンの参加博徒を、勝負の最中にファイルごと殺してやろう、という変態ユーザーは少ないだろう?

と思われるので、新仕様で確定しました。

 

こうして我がChinchirorin.exeは今日も元気に動いております。

 

ps. 自画自賛になりますが、(座が一巡する、または親落ちする迄だけなので)空き時間が少しあれば遊べます。又、プログラマーの作為のない「乱数賽子」による勝負なので、現実のチンチロと同じような展開となり、結構はまります。私は好みのDeward's 12年のオンザロックをカラカラ言わせて数巡遊んでいますが、「自分ひとりだけなら『あなた』博徒を入れて『コマ張り』に勘と趨勢分析をいれて、PC推奨値に逆らって勝負」するのが面白く、(まだやったことはないですが)数人の友人と飲む際に「俺はドサ健」「私、オックスのママ」「僕は坊や哲かな?」などと「外馬(そとうま)」に乗って、みんなでチンチロ勝負を楽しむことが出来ます。(勿論、それで本当にチンチロ博打もできますが、くれぐれも賭博はご法度であること、このコンプライアンスの時代なので作者から申し上げておきます。)

 

【C++の局所変数の生成と消滅のテスト】

////////////////////////////////////////
// C++における局所変数の生成と消滅

// C++では変数やインスタンスは、そ

// の宣言のある括弧内が有効範囲(ス

// コープ)となり、その外に出ると消

// 滅する。

// この場合の「生成」とはスタックに

// メモリー領域が確保されることであ

// り、「消滅」とはそれが解放される

// ことである。
////////////////////////////////////////

#include    <stdlib>
#include    <iostream>    //cout、cin使用の為
#include    <conio.h>    //getch()使用の為

using namespace std;

class Test {
public:
    //関数
    Test();            //コンストラクター
    ~Test();        //デストラクター
};

//コンストラクター
Test::Test() {

    cout << "Testクラスインスタンスが生成されました。" << endl;
}

//デストラクター
Test::~Test() {

    cout << "Testクラスインスタンスが消滅します。" << endl;
}


//メイン関数
int main(int argc, char **argv) {

    int a = 0;
    cout << "一番外側のaは" << a << "です。" << endl;
    Test test;
    {
        int a = 1;
        cout << "一つ内側のaは" << a << "です。" << endl;
        Test test;
        {
            int a = 2;
            cout << "二つ内側のaは" << a << "です。" << endl;
            Test test;
            {
                int a = 3;
                cout << "三つ内側のaは" << a << "です。" << endl;
                Test test;
            }
        }
    }
    getch();
    return 0;
}
//一番外側のTestクラスインスタンスはプログラムが終了した段階で消滅する。

 

 

 

 

 

お詫び:最初の部分がフォントが大きくなっていますが、「段落」を変更し、「文字サイズ」をしても変わりません。ということで、このまま載せてしまいます。(泣;)

 

前回に引き続き、現在の状況など。

 

あの後、結構頑張ってほぼVersion 1.0としてよい状態になっており、今日はHTMLHelp Workshopでヘルプなどを書いていました。

一方、想定が出来なくて可笑しな動作になることも十分に予想され(注)、実際のリリースはこれで一通り遊んでみてからにしようと思っています。

 

ではでは、Previewなぞ。

 

1.参加博徒の確定(1勝負6名迄)

「本 ゲームでは、故阿佐田哲也著「麻雀放浪記 青春編」に準拠して、ユーザープレーヤーとその登場人物7名の博徒(総勢8名)から、親と子が最大5名(総勢6 名)迄の賭場を作り、親を廻り胴として運営する複数プレーヤーゲームになっています。詳しくは「使い方」をご覧ください。 」(ヘルプ「概要」から)で確定しました。

 

2.開帳

確定した博徒ですが、中には破産してコマ(掛金)を張れない者(従ってこいつらは親を取れない)が出てくるはずですが、それのチェックをどこでどう行うか、今日気づき、考えました。(こんなことが結構あります。結果、「開始」時点で破産者をチェックし、有効な賭博者が2名以上いれば開始することとしました。又、親落ちのメソッドもこれに合わせて、次の親候補が破産していないかチェックするようにし、有効な候補者まで進めることとしました。)

 

3.開始

「本プログラム(Chinchirorin)は、チンチロリンという複数の博徒が親を交代しながら、「資料」記載のルールで賽子3つを転がして親(胴元)と子で勝負を行う賭博をPC上でシミュレートしたゲームです。」(ヘルプから)なので、サイコロを転がすのは乱数で「ユーザープレーヤーが唯一任意の判断で行える賭博行為」は「コマ(賭金)を張る」ことになります。

要すればチンチロリンの奥義は「運やツキが上向きか、下向きかを的確にとらえ、上向きで勝ちを最大化し、下向きで負けを最小化する」ことにあります。これの簡単なアルゴリズムを"Player.dll"に入れているのですが、ユーザープレイヤーは今回の勝負経過を踏まえてPCの推奨する賭金の額を確認しつつ、ご自身の判断で賭金を決定する所に本ゲームの醍醐味があります。

 

そして親対子の勝負が続きます。

 

これを(あなたが好きなら)延々とやるわけです。

 

最後に宿題として残しているのは、「今回のチンチロリン賭博の勝負数、勝ち数、負け数を『あなた(ユーザープレイヤー)』だけが見れるようにすべきか(現在も『賭金の設定』ダイアログで見れます)、他の博徒のスタッツも見れるようにすべきか?(注)というものです。

注:現在は参加博徒の選任(Player Listフォームで実施する)の際に、ListViewに「今回迄の」「累計勝負数、累計勝数、累計負数、勝率」を表示していますが、現在進行形の今回のデータは「あなた」しか表示していません。私はその方が、「てめーで覚えて、判断しろ」的な実際の賭場の感じが出ており、よいと思うのですが...

 

いずれにせよ、一定期間置いてから最終形をお示しして、解説に入りましょうか。要すれば、

ボチボチ行きましょう。

チンチロリンゲームも大分進展してきて、メインプログラムがDiceとPlayerのDLLを読み、勝負に入る手前まで来ています。

 

一応完成時に解説を行おうかと考えていますが、現段階でのチンチロリンゲームのフロー案と何処までできているかの解説をします。

 

【チンチロリンゲーム フロー】
0.前提として8名の博徒データを用意している。(新規作成は不要?)

→Playerクラスの記録データ(博徒名.dat)は「麻雀放浪記(青春篇)」に準じて「あなた」を入れた8名の既成データを用意しています。
1.参加博徒の確定(1勝負6名迄)
→メインフォーム("Chinchirorin Casino")の「開帳」メニュー(ボタン)を選択すると、「"Player List"を右クリックして博徒を確定しろ」というメッセージボックスが出ます。"Player List"を右クリックするとポップアップメニューが出て、「博徒の読み込み」「博徒の削除」「確定」の選択肢が表示されます。「博徒の読み込み」を選ぶと「定員(6名)にはX名多いので削除してください。」というメッセージボックスが出ます。(現在の所、どの博徒も乱数で投賽し、勝負履歴以外に差はないので、ユーザーによる博徒の「新規作成」は考えていません。)これに従って参加博徒を6名以下(しかし、2名以上→最低2名以内と勝負ができない。)に絞り、ポップアップメニューメニューの「確定」を選択すると、以降の博徒の削除等はできなくなります。
2.開帳
→(博徒を選ぶ前にこれを押すと「先に博徒を選べ」と怒られます。)博徒を選んだ後、これを押すと「親決めの後、開始ボタンを押せ」と指示されます。
3.(初回)親決め(賽一つを振り、最大の目を先に出したものが親となる)
→これはPCが勝手にやってくれます。基本的に参加博徒が一つの賽を振り、最大の目を先に出したものが親になります。(その後、親落ちすれば「車座になった博徒の廻り胴」となるので、一遍しかやりません。)

↑(以上まではコーディングされました。)
4.(勝負)開始(以下これがループ処理される-終了条件は、他の博徒が破産して、博徒が唯一人になった場合)
→チンチロリンゲームで一番重要な勝負の部分である、ここからはまだコード化できていません。ウインドウプログラムの難しさ(注)もあり、一旦ここでフローを整理しようということで今日のブログを書いています。
 (1)親の投賽(3回まで-子も同じ)
→投賽と役等の判定メソッドは出来上がっています。チンチロリンの投賽時はダイナミックな賽の回転グラフィックなどを夢見たのですが、そんな技量もグラフィックリソースもないので、「背景画像の上に賽が三つ現れて10回転がって止まる仕様」にしています。

    ①親の役(アラシ、シゴロ)または出目=6で即勝ち→break;
    ②親の役(ヒムミ)または、出目=1、目無しで即負け(親落ち→現在の親を削除し、車座に座った次の親へ廻り胴とする)→break;
    ③親の出目A(2~5)→(2)へ
 (2)子の投賽(1~5名の子が順番に親の③に対してのみ勝負-ループ処理)
    ①子の役(アラシ、シゴロ)で即勝ち→break;
    ②子の役(ヒムミ)で即負け→break;
    ③親と同じ出目(2~5)なら分け→break;
    ④子の出目B < A、または目無しで負け→break;

→以上のフローがすべての子に対して自動的に行われる場合、マルチスレッドにする必要があると思われます。しかし、「あなた」(ユーザー)を含めて勝手にゲームを進められると、またまた興覚めかと思われ、PCも含めてゆっくり進むようにしようと思っています。
注:ウィンドウズプログラムの場合、長時間スレッドがCPUを占有するとタイムアウトエラーになるので、「投賽単位」で処理を区切る(例:MessageBoxを出して中断する)ことが望ましい。また、環境ゲームと異なり、他の博徒の勝負をPC処理すると目が追いついていかないので矢張りゆっくりと進めてよいと思われる。

 

こんな感じですかね?これを当面の設計仕様にして進めるつもりです。

 

「Project チンチロリン」は少しづつ進めています。

 

ご存じの通り、チンチロは賽子を三つ振るのですが、その為の「賽子(サイコロ)コントロール」は既に作り、

 

次に博打打ちをクラス化した「Playerクラス」を作って、テスト中です。

チューニングの結果、元手\70,000で負けたりしていますが、しっかり勝ってもいるので\243,000に増やしている例です。

 

そして本体("Chinchirorin.exe")となるCasinoクラスと関連メソッドの開発を開始しました。(以下はプロトタイプ画像-背景はフリー画像で、博徒のリストは別フォーム(Modeless Dialog)にする予定です。)

 

まだまだ先は長いです。

 

まー、ボチボチ行きましょうか。

 

 

 

 

これでした。

 

C#で表現すると、こんなかな?

namespace 「和食」

{     public class void「ご飯」 //には不可欠の     {       string 「具沢山の味噌汁」       //小松菜、油揚げ、豆腐、玉葱、榎と薬味の長葱       int 「焼き鮭+キャベツと塩昆布の浅漬」       int 「わさび漬け、烏賊の塩辛と焼き海苔」     } }

美味しく頂戴しました。

 

「えっ、何?」とお感じになられるといーな。

 

現在Playersクラスを試行錯誤で作成中です。これのコンソールベースのテストプログラムを走らせてみると賭博依存症の感じがよく出ています。

 

<チンチロリン開始>

<成れの果て>

 

取り合えずこれで走り出して、Casinoを作り、最後に調整を入れますか?