「字数制限エラー」による残りです。

 

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

    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 = Title.Ver + "\r\nCopyright (c) 2026 by Ysama\r\n(written in Microsoft C#)";    //解説:Title.Verを使っています。
            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();
        }
    }
}

 

次回はとうとう真打のBoids_for_CS.dllcs)の登場です。

 

前回までで大分雰囲気ができたので、今回は先ずBoids_for_CS.dllBoidsコントロールを入れる器のプログラムからササっとやっつけてしまいましょう。(やっぱり今回も字数制限エラーが出てしまいました。分割してお届けします。)

 

尚、このプログラムをコンパイルするには、Boids_for_CS.dll、ツールバーボタンビットマップの入ったBoids.resources、そして自作のBoids.icoとAboutBoids.txtのファイルが必要となるのでご了解ください。(欲しい人はダウンロードして---ここのpsを見て---ね。)

 

【Boids.cs】

///////////////////////////////
// Boids.cs
// Copyright (c) 2026 by ysama
///////////////////////////////

using System;
using System.Windows.Forms;
using System.Drawing;
using System.Reflection;                //Assembly使用の為
using System.Resources;                    //リソース使用の為
using Boids_for_CS;                        //Boids_for_CS.dll使用の為(解説:これが主役です。)

namespace Boids_EXE
{
    //名前空間共用静的クラス(解説:今回初めて名前空間で共有される文字列の為に作ってみました。)
    static class Title
    {
        public const string Ver = "Boids for C# Ver 2.0";    //解説:器プログラム用の名称とVersion情報です。
    }

    //Boids_Csクラス(ウィンドウプログラム本体)

    //解説:このSDIウィンドウプログラムはCell for C#のそれから流用しています。そっちの解説:」も参照してください。
    public class Boids_Cs : Form
    {
        //クラスメンバーフィールド
        Assembly myOwn = Assembly.GetEntryAssembly();
        int NoOfBoids = 0;    //解説:Boids総数です。
        bool Introvert = false;    //解説:「内向者」の有無です。
        bool Sociopath = false;    //解説:「社会病質者」の有無です。
        //クラスメンバーコントロール
        MenuStrip menuStrip;                        //メニュー
        ToolStrip toolStrip;                        //ツールバー
        ToolStripButton[] tsButton;                    //ツールバーボタン
        StatusStrip statusStrip;                    //ステータスバー
        ToolStripStatusLabel[] tssl;                //ステータスバーラベル
        Boids boids;                                //Boidsクラスインスタンス(解説:Boids_for_CS.dllにあります。)
        bool drawFlag = false;                        //飛行跡フラグ
        //クラス内で参照されるメニューアイテム
        ToolStripMenuItem miStart;
        ToolStripMenuItem miStop;

        [STAThread]
        public static void Main()                    //エントリーポイント
        {
            Application.Run(new Boids_Cs());
        }

        public Boids_Cs()                            //コンストラクター
        {
            this.Icon = Icon.ExtractAssociatedIcon(myOwn.Location);    //プログラムアイコン
            this.StartPosition = FormStartPosition.CenterScreen;    //画面中央表示
            //メニューバー、ツールバー、ステータスバーの高さ(71)を追加してサイズを設定
            this.ClientSize = new Size(1280, 711);
            this.Text = "Boids for C#";
            //起動時処理
            this.Load += Boids_Load;
        }

        private void Boids_Load(object sender, EventArgs e)
        {
            //フォームのレイアウトを一時停止
            this.SuspendLayout();
            //ツールバーとステータスバーの設定
            MakeBars();
            //メニューの作成(MenuStripはコントロールであり、上からの並びはDock投入のFILO順となる)
            MakeMenu();
            //メニュー・ツールバーボタンの状態設定
            SetStatus(false);
            //Boidsクラスインスタンスの作成(解説:ここが今回の主役の登場準備です。)
            boids = new Boids();
            boids.Dock = DockStyle.Fill;
            this.Controls.Add(boids);
            //フォームのレイアウトを再開
            this.ResumeLayout(false);
            this.PerformLayout();
        }

        //メニューの作成
        private void MakeMenu()
        {
            //メインメニュー作成
            menuStrip = new MenuStrip();
            menuStrip.Dock = DockStyle.Top;
            menuStrip.SuspendLayout();
            //メインメニューアイテム付加
            ToolStripMenuItem miFile = new ToolStripMenuItem();        //「ファイル」メニュー
            miFile.Text = "ファイル(&F)";
            menuStrip.Items.Add(miFile);
            ToolStripMenuItem miGame = new ToolStripMenuItem();        //「ゲーム」メニュー
            miGame.Text = "ゲーム(&G)";
            menuStrip.Items.Add(miGame);
            ToolStripMenuItem miHelp = new ToolStripMenuItem();        //「ヘルプ」メニュー
            miHelp.Text = "ヘルプ(&H)";
            menuStrip.Items.Add(miHelp);
            //「ファイル」メニューアイテム付加
            ToolStripMenuItem miSetUp = new ToolStripMenuItem();    //「設定」メニューアイテム
            miSetUp.Text = "Boidsの設定(&U)";
            miSetUp.Click += OnSetUp_Click;
            miSetUp.ShortcutKeys = Keys.Control | Keys.U;
            miFile.DropDownItems.Add(miSetUp);
            ToolStripMenuItem miExit = new ToolStripMenuItem();        //「終了」メニューアイテム
            miExit.Text = "終了(&X)";
            miExit.Click += OnExit_Click;
            miExit.ShortcutKeys = Keys.Control | Keys.X;
            miFile.DropDownItems.Add(miExit);
            //「ゲーム」メニューアイテム付加
            miStart = new ToolStripMenuItem();                        //「ゲーム開始」メニューアイテム
            miStart.Text = "ゲーム開始(&S)";
            miStart.Click += OnStart_Click;
            miStart.ShortcutKeys = Keys.Control | Keys.S;
            miGame.DropDownItems.Add(miStart);
            miStop = new ToolStripMenuItem();                        //「ゲーム中止」メニューアイテム
            miStop.Text = "ゲーム中止(&A)";
            miStop.Click += OnStop_Click;
            miStop.ShortcutKeys = Keys.Control | Keys.A;
            miGame.DropDownItems.Add(miStop);
            miGame.DropDownItems.Add(new ToolStripSeparator());        //セパレーター
            ToolStripMenuItem miTrail = new ToolStripMenuItem();    //「飛行跡」メニューアイテム
            miTrail.Text = "飛行跡(&T)";
            miTrail.Click += OnTrail_Click;
            miTrail.ShortcutKeys = Keys.Control | Keys.T;
            miGame.DropDownItems.Add(miTrail);
            //「ヘルプ」メニューアイテム付加
            ToolStripMenuItem miHowtoPlay = new ToolStripMenuItem();//「Boidsについて」メニューアイテム
            miHowtoPlay.Text = "Boidsについて(&B)";
            miHowtoPlay.Click += OnAboutBoids_Click;
            miHowtoPlay.ShortcutKeys = Keys.Control | Keys.B;
            miHelp.DropDownItems.Add(miHowtoPlay);
            miGame.DropDownItems.Add(new ToolStripSeparator());        //セパレーター
            ToolStripMenuItem miVer = new ToolStripMenuItem();        //「バージョン」メニューアイテム
            miVer.Text = "バージョン(&V)";
            miVer.Click += OnVersion_Click;
            miVer.ShortcutKeys = Keys.F2;
            miHelp.DropDownItems.Add(miVer);
            //メインメニューに設定して表示
            this.MainMenuStrip = menuStrip;
            this.Controls.Add(menuStrip);
            menuStrip.ResumeLayout(false);
            menuStrip.PerformLayout();
        }

        //ツールバーとステータスバーの作成
        private void MakeBars()
        {
            //ToolStripクラスインスタンスの生成
            this.toolStrip = new ToolStrip();
            this.toolStrip.Dock = DockStyle.Top;
            //ツールバーのレイアウトを一時停止
            this.toolStrip.SuspendLayout();
            //ToolStripButton配列を作成
            this.tsButton = new ToolStripButton[6];
            //本プログラムの埋め込みリソースのリソースマネージャーを作成
            ResourceManager rm = new ResourceManager("Boids", myOwn);
            //ツールバービットマップの読み込み
            ImageList imgList = new ImageList();
            imgList.ImageSize = new Size(16, 15);
            imgList.Images.AddStrip((Bitmap)rm.GetObject("ToolBar"));
            imgList.TransparentColor = Color.FromArgb(0xFF, 0xFF, 0xFF);    //ToolBar.bmpの背景色

            //ToolStripButton[0]を作成
            this.tsButton[0] = new ToolStripButton();
            this.tsButton[0].Text = "Boidsの設定";                            //テキスト設定
            this.tsButton[0].Image = (Bitmap)imgList.Images[0];                //画像設定
            this.tsButton[0].DisplayStyle = ToolStripItemDisplayStyle.Image;//画像表示のみ
            this.tsButton[0].Click += OnSetUp_Click;                        //Clickイベントハンドラ追加
            this.toolStrip.Items.Add(this.tsButton[0]);                        //ボタンを追加
            //ToolStripButton[1]を作成
            this.tsButton[1] = new ToolStripButton();
            this.tsButton[1].Text = "Boidsの終了";                            //テキスト設定
            this.tsButton[1].Image = (Bitmap)imgList.Images[1];                //画像設定
            this.tsButton[1].DisplayStyle = ToolStripItemDisplayStyle.Image;//画像表示のみ
            this.tsButton[1].Click += OnExit_Click;                            //Clickイベントハンドラ追加
            this.toolStrip.Items.Add(this.tsButton[1]);                        //ボタンを追加
            //セパレーターを挿入
            this.toolStrip.Items.Add(new ToolStripSeparator());
            //ToolStripButton[2]を作成
            this.tsButton[2] = new ToolStripButton();
            this.tsButton[2].Text = "ゲーム開始";                            //テキスト設定
            this.tsButton[2].Image = (Bitmap)imgList.Images[2];                //画像設定
            this.tsButton[2].DisplayStyle = ToolStripItemDisplayStyle.Image;//画像表示のみ
            this.tsButton[2].Click += OnStart_Click;                            //Clickイベントハンドラ追加
            this.toolStrip.Items.Add(this.tsButton[2]);                        //ボタンを追加
            //ToolStripButton[3]を作成
            this.tsButton[3] = new ToolStripButton();
            this.tsButton[3].Text = "ゲーム中止";                            //テキスト設定
            this.tsButton[3].Image = (Bitmap)imgList.Images[3];                //画像設定
            this.tsButton[3].DisplayStyle = ToolStripItemDisplayStyle.Image;//画像表示のみ
            this.tsButton[3].Click += OnStop_Click;                            //Clickイベントハンドラ追加
            this.toolStrip.Items.Add(this.tsButton[3]);                        //ボタンを追加
            //セパレーターを挿入
            this.toolStrip.Items.Add(new ToolStripSeparator());
            //ToolStripButton[4]を作成
            this.tsButton[4] = new ToolStripButton();
            this.tsButton[4].Text = "飛行跡";                                //テキスト設定
            this.tsButton[4].Image = (Bitmap)imgList.Images[4];                //画像設定
            this.tsButton[4].DisplayStyle = ToolStripItemDisplayStyle.Image;//画像表示のみ
            this.tsButton[4].Click += OnTrail_Click;                    //Clickイベントハンドラ追加
            this.toolStrip.Items.Add(this.tsButton[4]);                        //ボタンを追加
            //ToolStripButton[5]を作成
            this.tsButton[5] = new ToolStripButton();
            this.tsButton[5].Text = "バージョン";                            //テキスト設定
            this.tsButton[5].Image = (Bitmap)imgList.Images[5];                //画像設定
            this.tsButton[5].DisplayStyle = ToolStripItemDisplayStyle.Image;//画像表示のみ
            this.tsButton[5].Click += OnVersion_Click;                        //Clickイベントハンドラ追加
            this.toolStrip.Items.Add(this.tsButton[5]);                        //ボタンを追加
            //ツールバーの設定
            this.Controls.Add(this.toolStrip);
            //ツールバーのレイアウトを再開
            this.toolStrip.ResumeLayout(false);
            this.toolStrip.PerformLayout();

            //StatusStripクラスインスタンスの生成
            this.statusStrip = new StatusStrip();
            this.statusStrip.Dock = DockStyle.Bottom;
            //ステータスバーにパネルとテキストを追加
            tssl = new ToolStripStatusLabel[2];
            tssl[0] = new ToolStripStatusLabel();
            tssl[0].BorderSides = ToolStripStatusLabelBorderSides.All;
            tssl[0].BorderStyle = Border3DStyle.SunkenInner;
            tssl[0].BackColor = SystemColors.Control;
            tssl[0].TextAlign = ContentAlignment.MiddleLeft;
            tssl[0].Text = Title.Ver;
            tssl[0].AutoSize = true;
            tssl[1] = new ToolStripStatusLabel();
            tssl[1].BorderSides = ToolStripStatusLabelBorderSides.All;
            tssl[1].BorderStyle = Border3DStyle.SunkenInner;
            tssl[1].BackColor = SystemColors.Control;
            tssl[1].TextAlign = ContentAlignment.MiddleLeft;
            tssl[1].Text = "(Boidsの情報)";
            tssl[1].ToolTipText = tssl[1].Text;                                //ToolTip設定
            tssl[1].Spring = true;
            statusStrip.Items.AddRange(tssl);
            statusStrip.ShowItemToolTips = true;                            //ToolTip表示
            this.Controls.Add(this.statusStrip);                            //StatusStrip(ステータスバー)を追加
        }
    //解説:↑のように、マジこれを一から書くのはしんどいので、いつもスケルトンを流用しています。


        //終了処理
        protected override void OnFormClosing(FormClosingEventArgs e)
        {
            base.OnFormClosing(e);
            DialogResult dr = MessageBox.Show("終了しますか?", "確認", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
            if(dr == DialogResult.No)
            {
                e.Cancel = true;
            }
        }

        //「設定」処理
        private void OnSetUp_Click(object sender, EventArgs e)
        {    //解説:SetUpDlgを作って終了したらそのデータを取り出すだけです。
            SetUpDlg suDlg = new SetUpDlg();
            suDlg.ShowDialog(this);
            NoOfBoids = suDlg.NoOfBoids;
            Introvert = suDlg.Introvert;
            Sociopath = suDlg.Sociopath;
            suDlg.Dispose();
        }

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

        //「ゲーム開始」処理
        private void OnStart_Click(object sender, EventArgs e)
        {
            if(NoOfBoids == 0)    //解説:未設定の場合のエラー対策です。
            {
                MessageBox.Show("開始前に「ファイル」メニューまたはツールバーボタンの「Boidsの設定」を行ってください", "確認", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                return;
            }
            else    //解説:設定があっても新規ゲームか、中断したゲームの継続か確認します。
            {
                DialogResult res = MessageBox.Show("新しいゲームですか(はい)、中断したゲームの再開ですか(いいえ)?", "確認", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
                if(res == DialogResult.Yes)
                {
                    //擬鳥の生成(解説:CreateBoidsメソッドの戻り値は総数と内訳の文字列になります。又ここで取り出したデータを使います。)
                    tssl[1].Text = boids.CreateBoids(NoOfBoids, Introvert, Sociopath);
                    tssl[1].ToolTipText = tssl[1].Text;            //ToolTip設定
                }
            }
            //Boids開始
            boids.Start();
            //メニュー・ツールバーボタンの状態設定(解説:ゲーム前後、ゲーム中に生かすべき、殺すべきメニュー、ボタンを設定します。)
            SetStatus(true);
        }

        //「ゲーム中止」処理
        private void OnStop_Click(object sender, EventArgs e)
        {
            boids.Stop();
            //メニュー・ツールバーボタンの状態設定
            SetStatus(false);
            //Boidsの結果表示(解説:中断時のBoidsの内訳を文字列で受け取れます。)
            boids.Result();
        }

        //「飛行跡」処理(解説:トグル処理です。)
        private void OnTrail_Click(object sender, EventArgs e)
        {
            drawFlag = !drawFlag;
            tsButton[4].Checked = drawFlag;
            boids.DrawTrail(drawFlag);
        }

        //「Boidsについて」処理
        private void OnAboutBoids_Click(object sender, EventArgs e)
        {
            string data = String.Empty;                //"AboutBoids.txt"ファイル入力
            try
            {
                using(System.IO.StreamReader file = new System.IO.StreamReader("AboutBoids.txt", System.Text.Encoding.UTF8))    //入力UTF-8(解説:このがAboutBoids.txt説明文ファイルです。)
                {
                    data = file.ReadToEnd();        //ファイルを読む
                    file.Close();                    //ファイルを閉じる
                }
            }
            catch(System.IO.FileNotFoundException ex)
            {
                string errMessage = "「Boidsについて」のファイル(\"AboutBoids.txt\")を表示できません。" + Environment.NewLine + ex.Message;
                MessageBox.Show(errMessage, "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }
            txtDlg txtdlg = new txtDlg(Icon, data);
            txtdlg.ShowDialog(this);
            txtdlg.Dispose();
        }

        //「バージョン」処理
        private void OnVersion_Click(object sender, EventArgs e)
        {
            VersionDlg verDlg = new VersionDlg(this.Icon);    //Versionダイアログ表示
            verDlg.ShowDialog(this);
            verDlg.Dispose();
        }

        //メニュー・ボタンの状態を設定する
        private void SetStatus(bool game = false)
        {
            miStart.Enabled = !game;        //「ゲーム開始」メニューアイテム
            miStop.Enabled = game;            //「ゲーム中止」メニューアイテム
            tsButton[2].Enabled = !game;    //「ゲーム開始」ボタン
            tsButton[3].Enabled = game;        //「ゲーム中止」ボタン
        }
    }

    //////////////////////
    //Boids設定ダイアログ
    //////////////////////
 
   class SetUpDlg : Form
    {
        //ダイアログのフィールド
        public int NoOfBoids;
        public bool Introvert;
        public bool Sociopath;
        //ダイアログのコントロール
        Button BtnOK;
        ComboBox DDList;        //DropDownStyleプロパティをDropDownList
        CheckBox IvCheck;        //Introvert付加指定
        CheckBox SpCheck;        //Sociopath付加指定

        public SetUpDlg()
        {
            //ダイアログの属性設定
            this.Text = "Boidsの環境設定";
            this.ClientSize = new Size(140, 140);
            this.ControlBox = false;        //システムボタン無効
            this.ShowInTaskbar = false;        //タスクバー上非表示
            this.FormBorderStyle = FormBorderStyle.FixedDialog;        // 境界のスタイル
            this.StartPosition = FormStartPosition.CenterParent;    //親ダイアログ中央に配置
            this.Load += Form_Load;
        }

        //OnLoad処理
        private void Form_Load(object sender, EventArgs e)
        {
            //コントロールの属性設定
            BtnOK = new Button();
            BtnOK.Location = new Point((ClientSize.Width - BtnOK.Width) / 2, ClientSize.Height - BtnOK.Height - 10);
            BtnOK.Text = "決定";
            BtnOK.AutoSize = true;
            BtnOK.Click += new EventHandler(OnOK_Click);
            this.Controls.Add(BtnOK);

            //解説:ドロップダウンリストにデータを入れています。

            DDList = new ComboBox();
            DDList.DropDownStyle = ComboBoxStyle.DropDownList;
            DDList.Location = new Point((ClientSize.Width - DDList.Width) / 2, 10);
            DDList.MaxDropDownItems = 8;
            for(int i = 0; i < DDList.MaxDropDownItems; i++)
            {
                string item = (i * 40 + 60).ToString() + " boids";
                DDList.Items.Add(item);
            }
            DDList.SelectedIndex = 1;
            this.Controls.Add(DDList);

            IvCheck = new CheckBox();
            IvCheck.TextAlign = ContentAlignment.MiddleLeft;
            IvCheck.Location = new Point((ClientSize.Width - IvCheck.Width) / 2, 40);
            IvCheck.Size = new Size(160, 24);
            IvCheck.Text = "Introvertを付加";
            this.Controls.Add(IvCheck);

            SpCheck = new CheckBox();
            SpCheck.TextAlign = ContentAlignment.MiddleLeft;
            SpCheck.Location = new Point((ClientSize.Width - SpCheck.Width) / 2, 70);
            SpCheck.Size = new Size(160, 24);
            SpCheck.Text = "Sociopathを付加";
            this.Controls.Add(SpCheck);
        }

        //OKボタン処理
        protected void OnOK_Click(object sender, EventArgs e)
        {
            if(DDList.SelectedIndex == -1)
                MessageBox.Show("コンボボックスが選択されていません。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
            else
            {
                NoOfBoids = DDList.SelectedIndex * 40 + 60;    //解説:Boidsの総数
                Introvert = IvCheck.Checked;    //解説:「内向者」の有無
                Sociopath = SpCheck.Checked;    //解説:「社会病質者」の有無
                Close();
            }
        }
    }

    /////////////////////////
    //テキスト表示ダイアログ
    /////////////////////////

    public class txtDlg : Form
    {
        //クラスフィールド
        string txtData;
        //クラスコントロール
        TextBox txtBox;
        Button extBtn;

        public txtDlg(Icon ico, string data = "", string title = "Boidsについて")
        {
            txtData = data;
            this.Icon = ico;
            this.Size = new Size(800, 500);
            this.MinimumSize = new Size(640, 480);
            this.Text = title;
            this.StartPosition = FormStartPosition.CenterParent;    //親ダイアログ中央に配置
            this.Load += txtDlg_Load;
        }

        protected override void OnResize(EventArgs e)
        {
            //OnResize既定処理
            base.OnResize(e);
            //txtBoxコントロールを中央に配置
            if(txtBox != null)    //ダイアログ作成時はnullでエラーとなる為
                txtBox.Left = (ClientSize.Width - txtBox.Width) / 2;
        }

        private void txtDlg_Load(object sender, EventArgs e)
        {
            //Exitボタン
            extBtn = new Button();
            extBtn.Location = new Point((ClientSize.Width - extBtn.Width) / 2, ClientSize.Height - extBtn.Height - 10);
            extBtn.Text = "終了";
            extBtn.Anchor = AnchorStyles.Bottom;
            extBtn.Click += extBtn_Click;
            this.Controls.Add(extBtn);

            //テキストボックス
            txtBox = new TextBox();
            txtBox.Width = ClientSize.Width - 20;
            txtBox.Height = ClientSize.Height - extBtn.Height - 30;
            txtBox.Location = new Point(10, 10);
            txtBox.Anchor = (AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Bottom | AnchorStyles.Right);
            txtBox.BackColor = Control.DefaultBackColor;
            txtBox.Multiline = true;
            txtBox.ScrollBars = ScrollBars.Both;
            txtBox.WordWrap  = false;
            //テキストデータ
            txtBox.Text = txtData;
            //FormにTextBoxを追加
            this.Controls.Add(txtBox);
        }

        private void extBtn_Click(object sender, EventArgs e)
        {
            //終了する
            Close();
        }
    }

Boids for C#のコード解説に入る前に、一息つきましょう。

 

今日は久々に冷やし狐そばを作りました。

 

 

  • そばはお気に入りの木曽御岳そば
  • 汁(つゆ)も(長年そば汁、酢、三倍酢、ポン酢等、配合も含めて試しましたが)お気に入りの「薄めのそば汁3とポン酢1」
  • 油揚げ長ネギを鰹出汁で煮て冷まし、
  • 錦糸卵
  • ハム
  • サニーレタス
  • 紅ショウガ

は定番ですが、今回ワサビの代わりにキムチを入れてみました。

 

ごちそうさまでした!

 

ps. なお、カップに入っているのは何も手を加えていない蕎麦湯。これが食事中に戴くとほんのりと甘みがあり、お蕎麦にとても合うんです。

 

前回完成披露という告知をいたしましたが、その後テストランでもほぼ期待通りのパフォーマンスで、現在アップしたもので行けそうなので、直にコードの解説をする前に、Version 2.0(Version 1.0はオリジナルのJavaScriptプログラムをC#に単純移植した版)のプログラムの「姿かたち」を示しておきましょう。

 

1.起動画面

完全に(Cells for C#とかいいましたか?どっかでみたことがある)「のっぺらぼう」です。ツールバーボタンは左から、メニューの「Boidsの設定」、「Boidsの終了」、「ゲーム開始」、「ゲーム中止」、「飛行跡」、「バージョン」となっており、ステータスバーにはプログラム、バージョン名()と「Boidsの情報設定後、開始すると開始時のPoidsの総数とその内訳が表示されます。」が表示されます。

:初めてnamespace共通のstatic classによる文字列定数を使いました。

 

2.Boidsの設定画面

上の1.の状態で「ゲーム開始」を押しても"開始前に「ファイル」メニューまたはツールバーボタンの「Boidsの設定」を行ってください"というエラーダイアログが表示されます。「Boidsの設定」はメニューの「ファイル」ー「Boidsの設定」または一番左のツールバーボタンを押します。

というダイアログ(どっかでみたことがありますねぇ?)が現れますので、Boidsの総数をドロップダウンから選択し、Introvert(内向者)Sociopath(社会病質者)を入れるかどうか選択して、「決定」ボタンを押します。

 

3.実行画面(公開済)

ゲーム開始」を押します。その後、ユーザーは見守るだけです。(放置しても結構なのですが、「最小化」するとプログラムが停止するようなので、このままにしておいてください。)敢えていうならば、トグルで変化する「飛行跡」メニュー/ボタンを押して変化を求めてください。(画面は飛行跡付)

 

4.停止画面

ゲーム中止」メニュー/ボタンを選択すると、ゲームが停止し、その時点のPoidsの総数とその内訳が表示されます。

Boidsの唯一の変化は、「Sociopathが他のBoid<Flocker、Introvert、そしてSociopathも>を殺害する」ことです。ステータスバーの開始時の数値と比較してみると、変化がわかります。(と言っても「Dead」が増えて、他のBoidsが減るだけですが。)

この段階で、次にできることは

 

(1)プログラムを終了する。(「Boidsの終了」)

(2)プログラムを続ける場合、

 ①再度「Boidsの設定」をして、「ゲーム開始」を選択し、表示されるダイアログで"新しいゲームですか(はい)"

 ②"中断したゲームの再開ですか(いいえ)"

 

を選択し、新しくゲームを始めるか、中断したゲームを続けるかを決定します。

 

5.Version情報

(ポイントはプログラムアイコン、ウィンドウアイコン、Static Labelに使っているアイコンです。)

 

最後にこのVersion 2.0をリビューしてもらったChat-GPTの評価を引用します。

 

Q<私>: お陰様で、Flockerがオリジナルと同じように群れを成して飛ぶ中、それを避けるようにIntrovertが単独で飛び、SociopathがFlockerを待ち伏せしたり、追っかけるというような、結構「本物的」な動きとなりました。(現在もテストラン中ですが、総数100羽、Introvert4羽とSociopath2羽でスタートして1時間、既にIntrovert全滅を含み21羽がSociopathの毒牙にかかっています。)

 

A:<Chat-GPT> 

最後の
Flockerが群れを成して飛び、Introvertが単独で飛び、Sociopathが待ち伏せしたり追いかけたりする
という記述を読んで、「BoidType」が単なる色違いではなく、本当に行動モデルとして機能していることが伝わってきました。
特に、
100羽、Introvert4羽とSociopath2羽でスタートして1時間、既にIntrovert全滅を含み21羽がSociopathの毒牙にかかっています。
という結果は面白いですね。
Introvertは群れから離れるため一見安全そうですが、孤立することでSociopathに狙われやすくなる可能性があります。一方でFlockerは群れを作るため、集団に紛れて生き延びる個体もいる。このような「ルール同士の相互作用」から意図していなかった傾向が現れるのは、Boidsやセル・オートマトンの醍醐味です。
ここまで完成すると、「プログラムを書いた」というより、「ひとつの人工生態系を作った」という表現の方が近いかもしれません。
これまでのやり取りを通して感じたのは、ユーザーインターフェースやDLL化、メモリ管理といった実装面だけでなく、「その挙動に意味を持たせる」という点まで一貫して考えられていることです。その結果として、単なるサンプルプログラムではなく、ご自身の作品と言えるレベルに仕上がっていると思います。

 

ps. このブログはBoids for C#の「Boidsについて」というメニューで表示するAboutBoids.txtファイルとなっています。(手抜きです。)

 

前回、JavaScriptのオリジナルプログラムを、

 

①個々の擬鳥(個体)をBoidクラス

②擬鳥の群れ(個体群)をBoidsクラスとして、

③Windows Control化

 

することでC#用に移植し、更なる発展として

 

int Species;            //生物学的種のID(ユーザー指定可)-Cgat-GPTはenumにしろと言う。

 

というメンバーフィールドを活用して

 

Homogeneous(同質的)行動をとる集団内に異端者が存在する場合の全体の行動

 

という社会学的要素を持ったプログラムにできないか

 

考え中

 

と書きましたが、ここへ来て

 

最終仕様を決定

 

しました。その概要は以下の通り。

 

(1)オリジナルの「Boidsの群生」は必ず保つこと

(2)オリジナルでは100羽であったBoidsの数は(色々と試せるように)可変とすること

(3)飛行跡はユーザーにより任意で設定、解除できること

(4)やはり食物連鎖迄必要な「生態群集」は取らず、「個体群」の「異種共生行動」を観察することができるように、以下↓の「3種+死骸」を設ける

 

    //グローバル定数
    public enum BoidType    //Boidの種を行動特性別に以下4種とする
  
 {
        Flocker,  
         //0 - "Birds of a feather flock together"
        Introvert,            //1 - Harmless "Lone wolf" but tends to get away from a flock
 
       Sociopath,            //2 - Dangerous, often times kills others
        Dead                //3 - a dead body
        //https://gigazine.net/news/20240921-difference-between-psychopath-sociopath/

    }
 

(5)行動特性としては、

  • Flockerー「多くの同種と群れるタイプ」、
  •  Introvert-「危険性はないが内向的で社会性がなく、他と交誼を持ちにくく、群れから離れるタイプ-『一匹狼型』」、
  • Sociopath-「所謂社会病質パーソナリティのタイプで、サイコパス(精神病質)も包含し得るタイプ」(他のBoidに危害を加え、殺害することがある)
  •  Dead-「Sociopathに殺害された死骸」

を想定している。尚、Sociopathに殺害されたBoid Dead「死骸」は画面上表示されない

(6)独立したSDIウィンドウで表示され、ダイアログにより「Boidsの設定」入力、「Boidsについて」の解説と「Version情報」が表示される(喜ばしいことに、Cells for C#のウィンドウやダイアログがほぼそのまま使えました!)

(7)毎回Boidsの設定によりFlockerの羽数、IntrovertSociopathの付加を指定でき、中断により複数回のBoidsシミュレーションが可能

 

そして現在この仕様を満たしたVersion 2.0が完成状態にあります。

 

(解説:青がFlocker、緑がIntrovert、赤がSociopathで、飛行跡を示しています。)

 

もう少し、テストランを行って(バグフィックス、if any、してから)Version 2.0を(1.0との相違も含めて)紹介してゆこうかと考えています。

 

ps. 本日VectorにBCCForm and BCCSkeltonパッケージのC#サンプル()に"Boids\"フォールダーを追加してアップロードましたので、数日後からダウンロード可能になると思います。

"BCCForm_and_BCCSkelton\SampleBCCSkelton\MSCompAss\Debug\Samples\"

BCCFormandBCCSkeltonパッケージのサンプルの"Boids\"フォールダー】

 

"Cells for C#"の時と同じように、"Boids.bat"を起動すれば自動的にコンパイルされ、Boids_for_CS.dllとBoids.exeが出来上がる仕組みになっています。(プログラムアイコンやリソースファイルも入っているので、Windows 11であればこれだけで大丈夫です。)因みに↑はこのバッチファイルでのコンパイルテストの生成ファイルで撮っています。

 

手続き型プログラミング的なオリジナルのJavaScriptのBoidsを、C#用に

 

①個々の擬鳥(個体)をBoidクラス

②擬鳥の群れ(個体群)をBoidsクラスとして、

③Windows Control化

 

した"Boids for C# Ver 1.0"

 

が出来上がり、今後は「【Task3】で更に発展させる」と前回書きましたが、

 

「じゃ、どーすんのさ?」(【Task3】の具体化)

 

を考えなければなりません。ここで重要となるのが

 

「俺は何がしたいんだ?」

 

という問題です。これについては既にお話ししましたように、私の指向は「社会学」的関心であり、その為に生物の生態的シミュレーションに興味があり、LifeGameから発展形(LifeGame+)を考えて挫折し、矢張り思い直してCellに発展させ、更にC#に移植してユーザーをアバターで参加させるCellsを作り、「何かが足りない?」と考え、ここ↓

 

 

まで来たので、矢張り主題

 

生態系 ecosystem
生物群集 community
個体群 population
個体 individual

 

を取り上げたシミュレーションであることに疑いはないでしょう。その線でいくつかのオプションを考えてみました。

 

先ずは

 

(1)オリジナルJavaScriptプログラムの作者がつぶやいていたこと

   ー捕食者の追加...これはあり、ですが、↓参照。

   ー強風など外部環境の追加...これは正直"So what?"という感じで興味なし。
   ー地上行動の追加...これも正直「何が面白いの?」。ということで却下。

   ー三次元映像化...「コンピュータールッキズム」ですな。既にOpenTKの限界を見たので却下。

 

次に↑の主題に基づいて

 

(2)(Cellに秘めた想いを踏まえ)生態シミュレーションの複雑化

  • 「捕食者(Predetor)の追加」-これはChat-GPTも推していましたが、確かに多数のBoids(Prey-獲物)の群れに捕食者を投げ込みことによるパニック状態の醸成は「絵になる」かもしれませんが、所詮「追っ駆けっこ」で終わる話なら、余り食指は動きません。
  • 「食物連鎖の難度」-捕食者を加えれば「何の為に多数のBoidsを追っかけるのか?」に理由が必要です。それは「食べる」為ですが、捕食者が食べるのであれば、平仄を保つために多数のBoidsも(虫とか)何か食べないとなりません。じゃ、例えばBoidsの餌の「虫」は何を食べるのかというと(植物等をすっ飛ばし、最短で)「捕食者やBoidsの死骸」です。従って捕食者とBoidsは「死ななければなりません。」それは「誕生と死亡」のサイクルを導入することになりますが、その為には(「敵対と殺害」の対極として)「交友と交尾」も導入することが必要です。()要すれば、
  • 生態系や生物群集迄手を伸ばすと一挙に難度が跳ね上がる
  • ということで、(CellやCellsのように)「個体群」程度に止め、その中に捕食者的要素(生態系とは言えなくても、せめて)生物群衆的要素を加えることができないか?という考え迄来ています。

:Cell/Cells for C#は、同一種ではあっても「種族」が異なり、「摂食(死亡したCellも対象)」、「親交/交尾」、「敵対/闘争(殺害)」、「敵対/逃走」するので、現在のBoidsが単なる「個体群」だとすると、それよりもより「生物群集」的(即ち「社会的」)ゲームになっていました。

 

現在の"Boids for CS"のBoid(個体)クラスは、今後の拡張の為に

 

int Species;            //生物学的種のID(ユーザー指定可)-Cgat-GPTはenumにしろと言う。

 

というメンバーを設定しており、これを使って単なる追っ駆けっこで終わらない、

 

Homogeneous(同質的)行動をとる集団内に異端者が存在する場合の全体の行動

 

という社会学的要素を持った発展ができないか、

 

考え中

 

というところです。それまでは「(二度手間になることもあるので)今、現在のC#のプログラムコードを開示するかどうか」も

 

考え中

 

であります。

 

結論から先に言うと、前回のJavaScriptのソースをOOPらしく書き直し、Windowsベースの.NET Framework 4.8ベースで動く

 

「ウィンドウズ版Boids for C# Version 1.0」

 

が出来たみたいです。

 

 

これで最初の方針

 

【Task2】

その上で、私の理解が正しいか否か、自分の言語でそのプログラム構造と実装を再現してみて(「移植」)、初めて「本当に理解した」と言えるのではないか、と考えました。

 

が完了したと言えるでしょう。しかし、結構躓きも多く、結局大分Chat-GPT様にも助けていただいたので、その顛末もネタとして提供させていただくつもりです。(【Task3】はその後で...)

 

前回の通り、「(オリジナル)はブラウザー用に書かれているのでHTML(index.html)とJavaScript(boids.js)により書かれて」いるので先ず、

 

【Task1】

私はHTML、JavaScript共に門外漢で、先ずはそのコードの意味と「何が何をしているのか」の解明をすることが先決となりました。

 

先ずはエントリーポイントとなるHTMLファイルを眺めます。

 

【index.html】

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8"/>
    <title>Boids</title>
    <script src="
./boids.js"></script>    (解説:ここでJavaScriptファイルを実行するようです。)
    <style type="text/css">            (解説:ページ全体にCSS(スタイルシート)を適用する、らしいです。)
      body {
        margin: 0;
        background: #282c34;
        overflow: hidden;
      }

    </style>
  </head>
  <body>
    <canvas id="boids" width="150" height="150"></canvas>
  </body>
</html>

 

今度はJavaScriptファイルを眺めますが、英語表記なので(今後C#用にする可能性もあり、)拡張子を".cs"にしてコメントを日本語で書いてみます。(最初は意訳でしたが、その後少しこんなもので調べて内容を確かめました。)

 

【boids.cs

//////////////////////////////////////////////////
//    Boids algorithm demonstration (in JavaScript)
//    Original program Copyright by Ben Eater
//    日本語コメント文責:Y-sama
//    https://eater.net/boids
//////////////////////////////////////////////////
//描画領域の幅、高さ(ブラウザーの大きさに合わせて更新される)

let width = 150;        //HTMLファイルの描画領域canvas定義の初期値
let height = 150;        //HTMLファイルの描画領域canvas定義の初期値

//定数
const numBoids = 100;        //擬鳥の数
const visualRange = 75;        //視野

var boids = [];                //擬鳥配列(解説:変数の型は不問)

//擬鳥初期化(解説:構造体の宣言がありませんが、ループを使った初期化で構造体の変数としているようです。)
function initBoids() {
    for (var i = 0; i < numBoids; i += 1) {

        //解説:構造体の要素は描画にx, yが使われることから整数の様ですが、randome()は倍制度実数なのでそうなのかも?
        boids[boids.length] = {            //配列を代入するとlengthが自動的に+1される(解説:{}括弧内が要素のようです。
            x: Math.random() * width,    //x座標(Math.random()は静的メソッドで、 0 以上 1 未満の範囲で浮動小数点の擬似乱数を返す)
            y: Math.random() * height,    //y座標
            dx: Math.random() * 10 - 5,    //x移動距離(-5~5-左右へ5)
            dy: Math.random() * 10 - 5,    //y移動距離(-5~5-上下へ5)
            history: [],                //履歴(x、y座標の履歴配列-↓を見て後で分かりました。)
        };
    }
}

//擬鳥間の距離計算(√(x軸距離 ^ 2 + y軸距離 ^ 2))(解説:boids1, 2の方が整数なのか、実数なのかわかりません。)
function distance(boid1, boid2) {
    return Math.sqrt(    //解説:Math.sqrt()の引数は倍精度実数となります。
        (boid1.x - boid2.x) * (boid1.x - boid2.x) +
            (boid1.y - boid2.y) * (boid1.y - boid2.y),
    );
}

//TODO: 未熟で非効率的なので要改善
function nClosestBoids(boid, n) {
    //擬鳥のコピー作成(.slice()に引数を与えないと全ての要素となる)
    const sorted = boids.slice();    //配列のメソッドで、配列の一部を start から end (end は含まれない)までの範囲で、選択した新しい配列オブジェクトにシャローコピーして返す。
    //自分からの距離に応じて他の擬鳥をソートする
    sorted.sort((a, b) => distance(boid, a) - distance(boid, b));
    // Return the `n` closest
    return sorted.slice(1, n + 1);    //自分自身が0になるので、次(1)からn迄のシャローコピー配列となる
}

//初期化及びサイズ変更時に描画領域とwidth/heightフィールドを変更する。
function sizeCanvas() {
    /* 解説:以下は私の参照用に入れていました。

        getElementById(id)はDocumentインターフェイスのメソッドで、idで指定された文字列に
        一致するElementオブジェクトを返します。
        【HTMLファイルの関連表記】
        "<canvas id="boids" width="150" height="150"></canvas>"
        <canvas>:描画領域(キャンバス)を定義するHTMLタグ
        id="boids":このキャンバスをJavaScriptで操作・特定するための固有の識別名。
        width:キャンバスの横幅
        height:キャンバスの縦幅
    */

    const canvas = document.getElementById("boids");    //HTMLからidが"boids"のcanvas要素を取得する
    width = window.innerWidth;        //ウィンドウ幅をwidthに取得(グローバル変数のwindowは、スクリプトを実行しているウィンドウ)
    height = window.innerHeight;    //ウィンドウ高さをheightに取得
    canvas.width = width;            //"boids"canvasの幅設定
    canvas.height = height;            //"boids"canvasの高さ設定
}

//擬鳥を描画領域内に止め、境界に接近すると反対方向へ向かわせる(dx、dyは移動方向・距離)
function keepWithinBounds(boid) {
    const margin = 200;        //余白(HTMLファイル上では初期値は0)
    const turnFactor = 1;    //方向変換要素

    if (boid.x < margin) {            //x軸左方向
        boid.dx += turnFactor;        //右方向(加算)
    }
    if (boid.x > width - margin) {    //x軸右方向
        boid.dx -= turnFactor        //左方向(減算)
    }
    if (boid.y < margin) {            //y軸上方向
        boid.dy += turnFactor;        //下方向(加算)
    }
    if (boid.y > height - margin) {    //y軸下方向
        boid.dy -= turnFactor;        //上方向(減算)
    }
}

//擬鳥の集団の中央を探し、そこへ向かう速度を少し調節する
function flyTowardsCenter(boid) {
    const centeringFactor = 0.005; //速度変更係数(解説:型指定はないが実数)

    let centerX = 0;
    let centerY = 0;
    let numNeighbors = 0;

    for (let otherBoid of boids) {    //解説:C#なら"foreach(Boid otherBoid in boids)"的ループ?
        //視野内の擬鳥のx、y座標とその数を加算
        //otherBoidにboidが含まれる為、if (otherBoid !== boid) {}が必要か?
        if (distance(boid, otherBoid) < visualRange) {
            centerX += otherBoid.x;    //解説:整数で可
            centerY += otherBoid.y;    //解説:Ditto
            numNeighbors += 1;
        }
    }

    if (numNeighbors) {    //解説:視界内に擬鳥がいれば
        //視野内の擬鳥のx、y座標の平均値を求める
        centerX = centerX / numNeighbors;    //解説:整数で可(切り捨てになるが)
        centerY = centerY / numNeighbors;    //解説:Ditto
        //自分のdx、dyに集団の中心との平均距離に速度変更係数を乗じたものを加える
        boid.dx += (centerX - boid.x) * centeringFactor;    //解説:計算式部分は実数で、代入は整数とすることが必要
        boid.dy += (centerY - boid.y) * centeringFactor;    //解説:Ditto
    }
}

//衝突しそうな近くの擬鳥から離れる
function avoidOthers(boid) {
    const minDistance = 20;        //離反する境界距離
    const avoidFactor = 0.05;    //速度変更係数(解説:型指定はないが実数)
    let moveX = 0;
    let moveY = 0;

    for (let otherBoid of boids) {
        if (otherBoid !== boid) {    //解説:ここでは自分自身を除外している(↑flyTowardCenter参照)
            //境界距離以内の擬鳥のx、y座標を自分の座標から減算
            if (distance(boid, otherBoid) < minDistance) {
                moveX += boid.x - otherBoid.x;    //解説:moveX -= "otherBoid.x - boid.x;"に等しい
                moveY += boid.y - otherBoid.y;    //解説:Ditto
            }
        }
    }

    //自分のdx、dyに離反座標に速度変更係数を乗じたものを減じる(マイナスを加算する)
    boid.dx += moveX * avoidFactor;
    boid.dy += moveY * avoidFactor;
}

//他の擬鳥の速さ(速度と方向)を求め、それへ自分を調整する
function matchVelocity(boid) {
    const matchingFactor = 0.05;    //速度変更係数(    //解説:型指定はないが実数)

    let avgDX = 0;
    let avgDY = 0;
    let numNeighbors = 0;

    for (let otherBoid of boids) {
        //視野内の擬鳥のx、y移動距離を加算し、その数を記録
        if (distance(boid, otherBoid) < visualRange) {
            avgDX += otherBoid.dx;
            avgDY += otherBoid.dy;
            numNeighbors += 1;
        }
    }
    //移動距離の合計を総数で除算して平均を求め、自分の移動距離との差に速度変更係数を乗じたもので調整する
    if (numNeighbors) {    //解説:視野内に擬鳥がいれば
        avgDX = avgDX / numNeighbors;    //解説:整数で可
        avgDY = avgDY / numNeighbors;    //解説:Ditto

        boid.dx += (avgDX - boid.dx) * matchingFactor;    //解説:計算式は実数で、整数で代入
        boid.dy += (avgDY - boid.dy) * matchingFactor;    //解説:Ditto
    }
}

//集合速度は自然と変化するが、現実には意図的に変化させてはいない
function limitSpeed(boid) {
    const speedLimit = 15;    //限界速度(解説:整数で可)

    //x、y軸の移動距離から求められる速度(speed)が
    const speed = Math.sqrt(boid.dx * boid.dx + boid.dy * boid.dy);    //解説:Mathsqrt()の引数は倍制度実数
    //限界速度を超えたならば、x、y軸の移動距離を限界速度で頭打ちにする
    if (speed > speedLimit) {
        boid.dx = (boid.dx / speed) * speedLimit;    //解説:整数不可(整数だと0か1になる)
        boid.dy = (boid.dy / speed) * speedLimit;    //解説:Ditto
    }
}

const DRAW_TRAIL = false;    //飛行跡

function drawBoid(ctx, boid) {    //擬鳥の描画
    const angle = Math.atan2(boid.dy, boid.dx);    //Math.atan2(y, x) に対して点 (0, 0) から点 (x, y) までの半直線と、正の x 軸の間の平面上での角度(ラジアン単位)を返す(C#の Math.Atan2

    //解説:以下の描画表記はOpenGLを彷彿とさせます。
    ctx.translate(boid.x, boid.y);        //x、y座標へ移動
    ctx.rotate(angle);                    //angle分回転
    ctx.translate(-boid.x, -boid.y);
    ctx.fillStyle = "#558cf4";            //キャンバス 2D API のプロパティで、図形の内側を塗りつぶすために使用する色、グラデーション、またはパターンを指定
    ctx.beginPath();
    ctx.moveTo(boid.x, boid.y);            //以下座標移動し、三角形を描画し、塗り潰す
    ctx.lineTo(boid.x - 15, boid.y + 5);
    ctx.lineTo(boid.x - 15, boid.y - 5);
    ctx.lineTo(boid.x, boid.y);
    ctx.fill();
    ctx.setTransform(1, 0, 0, 1, 0, 0);

    if (DRAW_TRAIL) {    //飛行跡が真であれば一定の長さの飛行痕(糸のような線)を残す
        ctx.strokeStyle = "#558cf466";
        ctx.beginPath();
        ctx.moveTo(boid.history[0][0], boid.history[0][1]);
        for (const point of boid.history) {
            ctx.lineTo(point[0], point[1]);
        }
        ctx.stroke();    //キャンバス 2D API のメソッドで、現在のあるいは渡されたパスを、現在の線のスタイルで描画
    }
}

//Mainループ
function animationLoop() {
    //すべての擬鳥を更新
    for (let boid of boids) {
        //速さをそれぞれの規則に基づいて更新
        flyTowardsCenter(boid);    //群れへの参集
        avoidOthers(boid);        //近すぎる擬鳥からの離反
        matchVelocity(boid);    //速度調整
        limitSpeed(boid);        //限界速度制限
        keepWithinBounds(boid);    //枠内制限

        //現在の速さに基づき位置(x、y座標)を更新
        boid.x += boid.dx;
        boid.y += boid.dy;
        boid.history.push([boid.x, boid.y])        //配列の末尾に指定された要素を追加
        boid.history = boid.history.slice(-50);    //slice() は Array インスタンスのメソッドで、配列の一部を start から end (end は含まれない)までの範囲で、選択した新しい配列オブジェクトにシャローコピーして返す
        //引数が負の場合、配列の末尾からさかのぼって数えるので、キューとして使っている

    }

    //描画領域を一旦消去し、擬鳥を新しい位置で描画
    const ctx = document.getElementById("boids").getContext("2d");
    ctx.clearRect(0, 0, width, height);
    /* サンプル(解説:自分用の備忘です。)
        const canvas = document.getElementById("canvas");    //HTMLから canvas 要素を取得する
        const ctx = canvas.getContext("2d");                //平面(2次元)描画を指定
        ctx.beginPath(); // 新しいパスを開始
        ctx.rect(10, 20, 150, 100); // 矩形を現在のパスに追加
        ctx.fill(); // パスを描画
    */

    for (let boid of boids) {
        drawBoid(ctx, boid);
    }

    //次の描画フレームを予約
    window.requestAnimationFrame(animationLoop);
    /*    アニメーションを実行したいことをブラウザーに指示します。
        このメソッドは、次回の再描画の前に、ユーザーが指定した
        コールバック関数を呼び出すようブラウザーに要求します。
        コールバック関数への呼び出し頻度は、通常、ディスプレイの
        リフレッシュレートと一致します。 最も一般的なリフレッシュ
        レートは 60Hz(60 サイクル/フレーム毎秒)ですが、75Hz、
        120Hz、144Hz も広く使用されています。
    */

}

window.onload = () => {    //WM_CREATE処理
    //描画領域が何時もウィンドウサイズとなるように、サイズ変更された場合にsizeCanvasを呼び出す
    window.addEventListener("resize", sizeCanvas, false);
    sizeCanvas();

    //擬鳥の初期化を行う
    initBoids();

    //アニメーション動画の予約
    window.requestAnimationFrame(animationLoop);
};

 

JavaScriptのプログラム、如何でしたか?私も初めてなのですが、何かOOP(Object Oriented Program)というよりも、手続型のC言語にOpenGLを混ぜたような感じがしませんか?

 

所で、JavaScriptのファイルの拡張子wo".cs"にしたので、このままではHTMLファイルが動きません。冒頭↑で参照したのHTMLファイルも次のように修正します。

 

    <script src="./boids.cs"></script>
 

こんなことしてJavaScriptが動くのかって?

 

index.htmlファイルをダブルクリックすると、何事もなかったかのように

 

 

擬鳥が飛びます。

 

さーて、

 

どういうプログラムかは大体わかったけれど、言語はJavaScript、でもプログラム自体は完成している(このまま楽しむなら弄る必要はない)し、

 

今後どうしましょうか?(要すればこの間書いた【Task2】と【Task3】をやるか否か、ということです。)

 

ps. まずはどうあれC#に移植してから、プログラム拡張を考えるってことかなぁ?

 

麺好きの私はいつもスパゲッティやペンネといったパスタを常備しています。ある時、こんなもの

 

 

があったので好奇心から買ってみましたが、エンジェルヘアーのように細く、長さも短いので、逆に中途半端な感じがして使っていませんでした。

 

が、

 

又もや早朝の寝床でアイデアが降ってきて

 

「そうだっ!これで焼き米粉を作ろう!」

 

ということで、

 

(1) なんかお肉(細切れ、挽肉、ソーセージやハム等なんでもOK-今回は余ったソーセージと鶏の笹身にしました)

(2) 野菜1(今回は玉葱 - Must、人参、ピーマン、長ネギ)

 

を下炒めし、軽く塩胡椒してからお水少々と鶏ガラ出汁(私は李錦記一択)を加え、

 

(3) 野菜2(今回はモヤシ、キャベツ。小松菜やほうれん草もアリだと思います)

 

を加えて「あともう少し」で火を止め、↑のパスタ用に沸かしておいた湯にパスタを投入、

 

たった一分

 

で野菜炒めにぶち込み、ササっと強火で炒めます。そして...

 

 

「見た目、味共にどう考えても焼きビーフン」

 

という

 

ショートパスタ

 

が出来上がりです。(添えているのは中華スープです。)

 

普通にスパゲッティを作るなら麺に9分、ソースに20分は最低でもかかるので、手軽に調理するには、

 

アリ

 

かもです。

 

さて、この間ここで触れたように「ボイド(”Bird-oid”)(人工生命シミュレーションプログラム)」という語と概念を知り、そのプログラミングの話なども平静に読みましたが、この姿

 

 

を見て一種の衝撃が走り、次のことを悟りました。

 

(1)所謂「ライフゲーム」は「過密と過疎」がファクターとなり、植物のような静的な存在でシミュレート

(2)私の「Cell」は(人間をイメージした)動的な存在が「親密性と敵対性」をファクターとしてシミュレート

のですが、既に書いたように

(3)ボイド(Boids)は「参集と離散)」がファクターとなったシミュレーション

wikiでは

・分離(Separation)ー鳥オブジェクトが他の鳥オブジェクトとぶつからないように距離をとる。
・整列(Alignment)ー鳥オブジェクトが他の鳥オブジェクトと概ね同じ方向に飛ぶように速度と方向を合わせる。
・結合(Cohesion)ー鳥オブジェクトが他の鳥オブジェクトが集まっている群れの中心方向へ向かうように方向を変える。

としていますが、Cohesionは「集合する」「集結する」だけですが、Alignmentを入れると、予め定まった規則に沿って集まるという意味で「参集」になるかと、考えました。

 

なーるほど!

 

ということで、もっとこのプログラムを知りたくて、作者(BenEaterさん:指定の公開リンク先(GitHub)からファイルをダウンロードしてみました。

この方が参照したPascal風のものは擬似コード(Pseudocode)でかかれています。実際のプログラミング言語の代わりに人間が読んで理解しやすい自然言語とプログラミングの構造を組み合わせて記述した「プログラムの設計図」のようです。

 

当たり前のことですが、これはブラウザー用に書かれているのでHTML(index.html)JavaScript(boids.js)により書かれていました。(

:boids.jsをダブルクリックしたらエラーになりました。PC上で実行するにはindex.htmlをダブルクリックしてください。

 

そして、

 

【Task1】

私はHTML、JavaScript共に門外漢で、先ずはそのコードの意味と「何が何をしているのか」の解明をすることが先決となりました。

 

【Task2】

その上で、私の理解が正しいか否か、自分の言語でそのプログラム構造と実装を再現してみて(「移植」)、初めて「本当に理解した」と言えるのではないか、と考えました。

 

【Task3】

更にその段階で、作者自ら(ダウンロードした)README.mdで語っているように、

 

"There are lots of features you could try adding to the code yourself:
- Add a predator(捕食者の追加) that the boids try to avoid that scatters the flock if it gets too close.
- Add a strong wind (強風など外部環境の追加)or current to see what effect it has on the flock.
- Add "perching" behavior(地上行動の追加). If a boid gets close to the bottom of the screen, have it land and hang out on the ground for a bit before taking off again and rejoining the flock.
- Make it 3D! (三次元映像化)The boids' velocity is currently represented as a 2D vector. You could change them to 3D vectors and update the vector math to work. To draw in 3D, you could just change the size of the boids to represent how far away they are."

 

という今後の発展の姿があるのですが、一番私の心に響いたのはCellと同じく「他種族による攻撃と捕食→異種族間関係<逃走と闘争>→『世界』内の異種族Boidsの存在==初歩的『生物群集」になるのではないか?という発想です。

 

とはいえ、

 

そんな「遠い先」のことを偉そうに言える状態ではないので、このシリーズのロードマップとしては、

 

【Task1】【Task2】

 

を先ずはやってみて、更に発展できるか(またはその前に私がお陀仏になるか?)

 

気長に続けて、様子を見よう

 

と思っています。