前回予告しました「TGEditクラス」「TGListクラス」「TGSet クラス」のコードを「ご参考」として掲載します。

コードの大部分はコメントだけで分かると思いますが、TGSetクラスのDoThis()(命令の実行)だけは解説:を入れます。

尚、ダイアログ部分は本編の解説が終わってからとなります。

 

///////////////////////////////////////////////////////////////////////////////
//                      TGEライブラリー (TGE.dll)
//                     Copyright (c) 2026 by Ysama
//
//                               【概要】
//LOGOベースの描画命令を纏めた「命令セット(TGSet)」、その一覧である「命令リス
//ト(TGList)」、「命令リスト」から指定した命令セットで、繰り返しを含め実行順を
//定める「実行リスト(TGList)」及び編集ダイアログをまとめたNakov.TurtleGraphics
//の命令編集ライブラリー
///////////////////////////////////////////////////////////////////////////////

using System;
using System.Windows.Forms;
using System.Drawing;
using System.Collections;
using System.Collections.Generic;        //Listを使う為
using System.IO;                        //FIle関係
using System.Text;                      //Encoding.GetEncoding を使用するのに必要
using Nakov.TurtleGraphics;                //TurtleGraphicsを使う為

namespace TGE
{
    /////////////////////////////////////////////
    //TurtleGraphics命令を編集するラッパークラス
    /////////////////////////////////////////////

    public class TGEdit
    {
        //クラスフィールド

        public TGSet CmdSet;            //命令セット({コマンド番号, 引数}の整数配列 )
        public TGList CmdList;            //命令リスト(命令セットの一覧)
        public TGList ExeList;            //実行リスト(指定する命令セットの実行順リスト)

        //コンストラクター
        public TGEdit(Form form = null)
        {
            if(form == null)
                Turtle.Init();
            else
                Turtle.Init(form);
            CmdSet = new TGSet();
            CmdList = new TGList();
            ExeList = new TGList();
        }

        //TurtleGraphicsのDispose/Reset
        public void Reset()
        {
            Turtle.Reset();
        }

        //亀の表示/非表示を確認する
        public bool IsTurtleShown()
        {
            return Turtle.ShowTurtle;
        }

        //亀を表示/非表示する
        public void ShowTurtle(bool flag)
        {
            Turtle.ShowTurtle = flag;
        }
    }

    ///////////////////////////////////////////
    //命令リストクラス-命令セットの集合クラス
    ///////////////////////////////////////////

    public class TGList
    {
        //クラスメンバー
        List<TGSet> CmdList;
        public string FileName {get; set;}        //ファイル入出力名

        //コンストラクター
        public TGList()
        {
            CmdList = new List<TGSet>();
            FileName = String.Empty;
        }

        //コピーコンストラクター
        public TGList(TGList tgl)
        {
            CmdList = new List<TGSet>(tgl.CmdList);
            FileName = tgl.FileName;
        }

        //TGListの初期化
        public void Clear()
        {
            CmdList.Clear();
            FileName = String.Empty;
        }

        //TGListの命令セット数
        public int CountSets()
        {
            return CmdList.Count;
        }

        //n番目の命令セットを取得
        public TGSet GetSet(int n)
        {
            if(n < 0 || n > CmdList.Count - 1)
                return new TGSet();                //空のTGSetを返す
            return CmdList[n];
        }

        //TGListへのTGSetの追加
        public void AddSet(TGSet tgs)
        {
            CmdList.Add(new TGSet(tgs));        //Deep copy
        }

        //n番目の命令セットの削除
        public bool RemoveAt(int n)
        {
            if(n < 0 || n > CmdList.Count - 1)
                return false;
            else
                CmdList.RemoveAt(n);
            return true;
        }

        //n番目に命令セットを挿入
        public bool InsertSet(int n, TGSet tgs)
        {
            if(n < 0 || n > CmdList.Count - 1)    //最後尾に追加するにはAddを使う
                return false;
            else
                CmdList.Insert(n, tgs);
            return true;
        }

        //CmdListへTGSetを介して命令セット入力
        public bool ReadList(string data)
        {
            //入力用TGSet
            TGSet tgs = new TGSet();
            //文字列の行分割
            string[] lines = data.Split(new[]{"\r\n","\n","\r"}, StringSplitOptions.None);
            //行の語分割と整数配列の作成
            bool Is1stTime = true;            //最初の命令セットの’-1'か否かのフラグ
            foreach(string line in lines)
            {
                if(line == String.Empty)    //(有る筈のない)空行に遭遇したら中止する
                    break;
                string[] dt = line.Split(',');
                if(dt[0] == "Name:")        //命令セットデータの最終行は"Name:,(Name),(Description)"とする
                {
                    tgs.Name = dt[1];
                    tgs.Description = dt[2];
                    continue;                //次行へスキップ
                }
                int cmd = 0, arg1 = 0, arg2 = 0, arg3 = 0;    //入力結果
                if(!int.TryParse(dt[0], out cmd))
                    return false;
                else
                {
                    if(cmd == -1)            //命令'-1'はセット開始(先行セット終了、セットの区切り)
                    {
                        if(!Is1stTime)        //最初の-1で先行セットがなければAddSet()は不要
                            AddSet(tgs);    //CmdListに今迄の命令セットを登録
                        else
                            Is1stTime = false;
                        tgs.Clear();        //次の命令セットの為に入力用tgsを初期化する
                    }
                }
                if(!int.TryParse(dt[1], out arg1))
                    return false;
                if(!int.TryParse(dt[2], out arg2))
                    return false;
                if(!int.TryParse(dt[3], out arg3))
                    return false;
                tgs.AddCmd(cmd, arg1, arg2, arg3);
            }
            AddSet(tgs);                    //最後の命令セットを登録
            return true;
        }

        //CmdListの命令セットの一覧出力
        public string WriteList()
        {
            string str = String.Empty;
            foreach(TGSet tgs in CmdList)
            {
                str += tgs.WriteSet();
            }
            return str;
        }

        //CmdListの命令セットの実行
        public void DoSet()
        {
            foreach(TGSet tgs in CmdList)
            {
                tgs.DoThis();
            }
        }

        ///////////////////////////////////////////////////////////////////////////////////////
        // >>> "*.tge"のファイルについて <<<
        //(1) LOGOベースのTurtleGraphicsの命令に対応した命令番号と引数一つのテキストcsvファイル
        //(2) 命令'-1'はセット開始(先行セット終了、セットの区切り)で引数は繰り返し回数
        //(3) 命令セットデータの最終行は"Name:,(Name),(Description)"とする
        ///////////////////////////////////////////////////////////////////////////////////////

        //tgdファイル書き込み(UTF-8:Encoding.UTF8)
        public bool WriteFile()
        {
            if(FileName == String.Empty)
                return false;
            using(StreamWriter file = new StreamWriter(FileName, false, Encoding.UTF8))
            {
                //ファイルを書く
                file.Write(WriteList());
                //ファイルを閉じる
                file.Close();
            }
            return true;
        }

        //tgdファイル読み込み(UTF-8:Encoding.UTF8)
        public bool ReadFile()
        {
            if(FileName == String.Empty)
                return false;
            string data = String.Empty;
            try
            {
                using(StreamReader file = new StreamReader(FileName, Encoding.UTF8))
                {
                    data = file.ReadToEnd();
                    // ファイルを閉じる
                    file.Close();
                }
            }
            catch(Exception e)    //FileNotFoundException e
            {
                //Debug用
                //MessageBox.Show("エラーコード:" + e.ToString(), "初期化ファイル読み込みエラー", MessageBoxButtons.OK, MessageBoxIcon.Error);

                MessageBox.Show("ファイルを読み込めませんでした。\r\n。エラーコード:" + e.ToString(), "確認", MessageBoxButtons.OK, MessageBoxIcon.Information);
                return false;
            }
            return ReadList(data);
        }
    }

    /////////////////////////////////////////////////
    //命令セットクラス(名称と説明付きの命令セット)
    /////////////////////////////////////////////////

    public class TGSet
    {    //クラスメンバー
        List<int[]> CmdSet;                        //命令配列
        public string Name {get; set;}            //命令セット名
        public string Description {get; set;}    //説明

        //コンストラクター1
        public TGSet()
        {
            CmdSet = new List<int[]>();
            Name = String.Empty;
            Description = String.Empty;
        }

        //コンストラクター1
        public TGSet(string name, string description)
        {
            CmdSet = new List<int[]>();
            Name = name;                        //再設定自由
            Description = description;            //再設定自由
        }

        //コピーコンストラクター
        public TGSet(TGSet copy)
        {
            CmdSet = new List<int[]>(copy.CmdSet);
            Name = copy.Name;
            Description = copy.Description;
        }

        //初期化
        public void Clear()
        {
            CmdSet.Clear();
            Name = String.Empty;
            Description = String.Empty;
        }

        //命令セットの命令数
        public int Count()
        {
            return CmdSet.Count;
        }

        //命令セットの設定(1)
        public void Set(TGSet tgs)
        {
            CmdSet.Clear();
            CmdSet = new List<int[]>(tgs.CmdSet);
            Name = tgs.Name;
            Description = tgs.Description;
        }

        //命令セットの設定(2)
        public void Set(List<int[]> list, string name, string description)
        {
            CmdSet.Clear();
            CmdSet = new List<int[]>(list);
            Name = name;
            Description = description;
        }

        //n番目の命令取得
        public int[] GetCmd(int n)
        {
            if(n < 0 || n > CmdSet.Count - 1)
                return new int[] {-1, -1, -1, -1};            //エラー
            else
                return CmdSet[n];
        }

        //n番目の命令設定(1)(命令番号cmdは必須、定数引数arg1は重要、arg2とarg3はオプション扱い)
        public bool SetCmd(int n, int cmd, int arg1, int arg2 = 0, int arg3 = 0)
        {
            if(n < 0 || n > CmdSet.Count - 1)
                return false;
            CmdSet[n] = new int[] {cmd, arg1, arg2, arg3};    //Deep copy
            return true;
        }

        //n番目の命令設定(2)
        public bool SetCmd(int n, int[] cmd)
        {
            if(n < 0 || n > CmdSet.Count - 1 || cmd.Length != 4)
                return false;
            CmdSet[n] = cmd;
            return true;
        }

        //命令の追加(命令番号cmdは必須、定数引数arg1は重要、arg2とarg3はオプション扱い)
        public void AddCmd(int cmd, int arg1, int arg2 = 0, int arg3 = 0)
        {
            CmdSet.Add(new int[] {cmd, arg1, arg2, arg3});    //Deep copy
        }

        //命令の追加(配列渡し)
        public bool AddCmd(int[] cmdarr)
        {
            if(cmdarr.Length != 4)
                return false;
            else
            {
                CmdSet.Add((int[])cmdarr.Clone());            //Deep copy
                return true;
            }
        }

        //命令の削除
        public bool RemoveAt(int n)
        {
            if(n < 0 || n > CmdSet.Count - 1)
                return false;
            else
                CmdSet.RemoveAt(n);
            return true;
        }

        //n番目に命令を挿入(命令番号cmdは必須、定数引数arg1は重要、arg2とarg3はオプション扱い)
        public bool Insert(int n, int cmd, int arg1, int arg2 = 0, int arg3 = 0)
        {
            if(n < 0 || n > CmdSet.Count - 1)                //最後尾に追加するにはAddを使う
                return false;
            else
                CmdSet.Insert(n, new int[] {cmd, arg1, arg2, arg3});
            return true;
        }

        //n番目に命令を挿入(配列渡し)
        public bool Insert(int n, int[] cmdarr)
        {
            if(n < 0 || n > CmdSet.Count - 1)                //最後尾に追加するにはAddを使う
                return false;
            else
                CmdSet.Insert(n, (int[])cmdarr.Clone());
            return true;
        }

        //命令の一覧出力
        public string WriteSet()
        {
            string str = String.Empty;
            foreach(int[] array in CmdSet)
            {
                //str += array[0].ToString() + "," + array[1].ToString() + ","
                //    + array[2].ToString() + "," + array[3].ToString() + Environment.NewLine;
                str += string.Join(",", array) + Environment.NewLine;
            }
            str += "Name:" + "," + Name + "," + Description + Environment.NewLine;
            return str;
        }

        //命令(int[4]-コマンド番号、引数1、引数2、引数3)の実行
        public void DoThis()    //解説:TurtleGraphicsの命令処理を行います。
        {
            int times = 1;    //解説:繰り返し回数です。既定値は「1回」です。
            for(int i = 0; i < times; i++)    //最低でも規定値1回は実行
            {    //cmdは命令(cmd[0])と引数(cmd[1~3])の整数配列
                foreach(int[] cmd in CmdSet)
                {    //引数cmd[1]は定数、cmd[2]は回数の倍数、cmd[3]は回数による分数(ともに規定値は0)
                    switch(cmd[0])            //引数cmd[0]は命令番号
                    {
                        case -1:            //命令セット開始(解説:これはTurtleGraphicsの命令ではなく、TGSet独自命令です。)
                            times = cmd[1];    //引数は繰り返し回数(解説:1回以上の場合、timesのループ回数が更新されます。)
                            cmd[0] = 0;        //繰り返しの為にNOPにする(解説:2回目からはNOPで「何もしません」。)
                            break;
                        case 0:                //NOP - No OPeration(解説:-1命令はループ中はNOPになります。)
                            break;
                        case 1:                //Forward(arg)(解説:基本的に引数は以下の式になります。)
                            Turtle.Forward(cmd[1] + cmd[2] * i + cmd[3] / (times - i));
                            break;
                        case 2:                //Backward(arg)
                            Turtle.Backward(cmd[1] + cmd[2] * i + cmd[3] / (times - i));
                            break;
                        case 3:                //MoveTo(arg1, arg2)(解説:引数の使い方が例外となります。)
                            int x = cmd[1] + cmd[3] * i;    //arg1(cmd[3]のループ倍数が加算)
                            int y = cmd[2] + cmd[3] * i;    //arg2(cmd[3]のループ倍数が加算)
                            Turtle.MoveTo(x, y);
                            break;
                        case 4:                //Rotate(arg)
                            Turtle.Rotate(cmd[1] + cmd[2] * i + cmd[3] / (times - i));
                            break;
                        case 5:                //RotateTo(arg)
                            Turtle.RotateTo(cmd[1] + cmd[2] * i + cmd[3] / (times - i));
                            break;
                        case 6:                //PenUp()
                            Turtle.PenUp();
                            break;
                        case 7:                //PenDown()
                            Turtle.PenDown();
                            break;
                        case 8:                //X
                            Turtle.X = cmd[1] + cmd[2] * i + cmd[3] / (times - i);
                            break;
                        case 9:                //Y
                            Turtle.Y = cmd[1] + cmd[2] * i + cmd[3] / (times - i);
                            break;
                        case 10:            //Angle
                            Turtle.Angle = cmd[1] + cmd[2] * i + cmd[3] / (times - i);
                            break;
                        case 11:            //PenColor(Color構造体←整数の変換-解説:Colorのメソッドです。)
                            Turtle.PenColor = Color.FromArgb(cmd[1] + cmd[2] * i + cmd[3] / (times - i));    //2026年04月22日変更
                            break;
                        case 12:            //PenSize
                            Turtle.PenSize = cmd[1];
                            break;
                        case 13:            //PenVisible (1 - true, 0 - false)
                            Turtle.PenVisible = (cmd[1] == 1);
                            break;
                        case 14:            //Delay
                            Turtle.Delay = cmd[1];
                            break;
                        default:
                            break;
                    }
                }
            }
            CmdSet[0][0] = -1;                //配列は参照引き渡しの為、値-1に復旧する
        }
    }

 

前回の「進め方」で書いた通り、今回はTGEditorの基盤となる旧TGEditクラスから現在のTGE.dllに至る迄、何度か繰り返した仕様変更について(プログラミングをされる方のご参考になればと)書いてみます。

 

仕様変更のポイントは、

 

(1)命令文の引数の数

(2)命令の繰り返し実行をどうするか

(3)UI(User Interface)との関係

(4)クラス化する際の構成

 

に纏められます。これらの観点で前期~中期~後期に分けて振り返ります。

 

1.前期

Visual Studio版を完成させ、C#5へ移植していた時期です。最終的にはTGEditorのようなものを作ることは意図していました。

 

(1)命令文の引数の数

元々は「ネタ探し」と「Visual Studio専用機」を活用する為に、WebとNuGetで面白いものを探していて「あっ、そうだ!TurtleGraphicsなんてあったね?」ということで始まった訳ですが、LOGO言語ベースのコマンドは↓のように比較的単純

 

『亀メソッド』(基本引数は一つで、MoveToのみ引数が二つ)
Init(arg1)-タートルグラフィックスシステムを初期化します。arg1は描画対象のFormsコントロールで、指定しない場合、アクティブなフォームが描画領域となります。
Dispose() / Reset()-タートルグラフィックスシステムを破棄し、関連するすべてのリソースを解放します。
Forward(arg1)-現在の方向に亀をarg1ピクセル前進させ、ペンが下がっている場合線を描きます。
Backward(arg1)-現在の方向に亀をarg1ピクセル後退させ、ペンが下がっている場合線を描きます。
MoveTo(arg1, arg2)-亀を指定した位置へ移動させます。ペンが下がっている場合線を描きます。
Rotate(arg1)-現在の向きからarg1(度数)亀を回転させます。
RotateTo(arg1)-亀を指定角度(度数)へ向けます。初期状態の亀の方向(角度)は0度(上向き)です。
PenUp()-ペンを描画面から離し、線は描かれません。
PenDown()-ペンを描画面に降ろし、線が描かれます。

『亀プロパティ』(引数は一つ)
X-亀の現在X座標を取得/設定します。初期状態の亀は {0, 0}(画面中央)です。
Y-亀の現在Y座標を取得/設定します。初期状態の亀は {0, 0}(画面中央)です。
Angle-亀の現在方向(度数)を取得/設定します。(上-0、右-90、下-180、左-270)
PenColor-描画色を取得/設定します。既定色はBlue(青)です。
PenSize-描画太さ(ピクセル単位)を取得/設定します。規定値は7です。
PenVisible-ペンが下がり描画可能であればtrue、ペンが上がればfalseとなります。
ShowTurtle-亀の表示可否を取得/設定します。規定値は表示可です。
Delay-移動/回転後の待機時間(ミリ秒)を取得/設定します。規定値は0です。

 

で、「毎回サンプルを作りコンパイルを繰り返すのってどーよ?」と感じたこともあり、先ず個々の命令文命令番号(オペコード)引数(オペランド)を与えて実行する形にしました。従って当初は引数2つの"MoveTo"命令があることから、

 

命令文 = new int[3]  {ijnt[0[(オペコード), int[1](オペランド1), int[2](オペランド2)};

 

という整数3つのint[](整数配列)とし、命令文Listで管理することにし、命令文の配列CmdSet(Command Set)、CmdSetの配列をCmdList(Command List)、としていました

 

        //クラスフィールド
        List<int[]> CmdSet = new List<int[]>();                    //描画設定の整数配列int[] {コマンド番号, 引数1、引数2}
        List<List<int[]>> CmdList = new List<List<int[]>>();    //構成設定の描画設定Listの配列
 

しかし"MoveTo"命令しか引数2を使わないことから、命令文をコンパクト化して整数2つ(命令番号, 引数}」)()にしました。(TurtleGraphics02

"MoveTo"命令の引数はHIWORDとLOWORDで整数一つに纏めました。(上位16bit値 = 16bit値 << 16; 下位16bit値 = 16bit値 & 0xFFFF;としてから(上位16bit値 | 下位16bit値)して作ります。分割する際はこの逆にビット処理します。)

 

(2)命令の繰り返し実行をどうするか

まだこの段階では気が付いていませんでした。

 

(3)UI(User Interface)との関係

まだこの段階ではダイアログ等のUIの在り方は具体化していませんでした。

 

(4)クラス化する際の構成

この段階では、上記の通り命令文の配列CmdSet)、CmdSetの配列であるCmdListを単純にTGEditクラスに入れればよいと考えていました。

 

2.中期

(1)命令文の引数の数(2)命令の繰り返し実行をどうするか

 

しかし、

 

一件落着と考えていた命令文について、ある日朝方に寝床で大失敗に気づきます。

 

複数の命令を繰り返し処理する場合、与える引数が定数だけじゃん!

 

例えばサンプルの赤渦巻き

を描くには、

 

            for (int i = 0; i < 25; i++)
            {
                CmdSet.Add(new int[] {1,
i * 5});    //Turtle.Forward(i * 5);
                CmdSet.Add(new int[] {4,
30 + i});    //Turtle.Rotate(30 + i);
            }

 

のように、ループカウンターを使った加算(減算)、乗算(除算)が必要だったのです。

 

では、どのように対応させたらよいのでしょうか?

 

この問題は更に「ループカウンターを使う対象は加減乗除のみでよいのか?」「ではいくつにすればよいのか?」「フラクタル関数等より複雑な式は使えないがそれでよいのか?」「不特定な計算処理まで考えると本格的なインタープリター機能が必要となり、際限なく複雑化してゆくぞ」と悩み、悩んだ末に(このアプリの性格から考え)私の回答は、引数を複数使い、「オペコード(命令番号-int[0])」に与える「オペランド(引数)」に、

 

引数=定数(int[1])+倍数(int[2] × 繰り返し数)+除数(int[3]÷(最大繰り返し数 - 繰り返し数))

 

という式を与えることでした。これを具体的に言うと、for(int i = 0; i < n; i++)というループの場合に引数120引数210引数30を与えると、最終的に「20 + 10 * i + 0 / (n - i)」という値が引数として与えられるので、「定数だけ、倍数、除数またはそれらの組み合わせ」が可能になる、ということです。(「フラクタル関数等より複雑な式」「不特定な計算処理」はGive upした、ということです。)

例:for(int i = 0; i < 12; i++)

        {

             deg1 = 0 + 30 * i + 0 / (12 - i);         //0, 30, 60, 90, 120, 150 ...

             deg2 = 360 - 30 * i + 0 / (12 - i);     //360, 330, 300, 270 ...

             deg3 = 0 - 0 * i + 360 / (12 - i);       //30, 32, 36, 40, 45 ... 

       }

 

(3)UI(User Interface)との関係

この段階ではUIダイアログの試作を始めており、整数4つの命令文に合わせ、オペコード(命令番号-int[0])をドロップダウンリストにし、3つのオペランド(引数)をテキストボックスで入力することを考えていました。

 

が、

 

ここでも「あ"っ!」が発生します。UIダイアログを試作していた段階で「人間が選択して使うには『名前』と『(それが何かと言う)説明』が必要」という人間の認知の為のUI問題から、「命令文の配列CmdSetに文字列の「名前」と「説明」を付加することを余儀なくされました。(↓の次項参照)

 

(4)クラス化する際の構成

最初はTGEditクラス

 

        //クラスフィールド
        List<int[]> CmdSet = new List<int[]>();                    //描画設定の整数配列int[] {コマンド番号, 引数1、引数2}
        List<List<int[]>> CmdList = new List<List<int[]>>();    //構成設定の描画設定Listの配列
 

をいれればいーや、と思っていた所、更にCmdSet t毎に

 

        public string Name {get; set;}            //命令セット名
        
public string Description {get; set;}    //説明

 

を入れなければならなくなった為に、"TGList"という「クラス内クラス」を急遽作ることにしました。

 

    public class TGEdit
    {
        
//命令リスト(クラス内)クラス-名称と説明付きの命令セット
        public class TGList
        {    
//クラスメンバー
            public List<int[]> intList;
            
public string Name {get; set;}
            
public string Description {get; set;}
          
 //コンストラクター
            public TGList(string name = "", string description = "")
            {
                intList =
new List<int[]>();
                Name = name;
                Description = description;
            }
            
//コピーコンストラクター
            public TGList(TGList copy)
            {
                intList = new List<int[]>(copy.intList);
                Name = copy.Name;
                Description = copy.Description;
            }
        }

 

        //RGEditのクラスフィールド
        TGList CmdSet = new TGList();                //命令セット({コマンド番号, 引数}の整数配列 )
        List<TGList> CmdList = new List<TGList>();    //命令リスト(命令セットの一覧)
        List<TGList> ExeList = new List<TGList>();    //実行リスト(指定する命令セットの実行順リスト)
(略)

}

 

ご覧の通り、この段階でもまだCmdSet は単なる整数配列のListにすぎず、「全体描画の部品」という性格や機能をもっていません。しかし、「CmdSet = 命令セット作成用」「CmdList = 作成した命令セットのライブラリー用命令リスト」「ExeList = ライブラリーから選択した命令セットで作る実行リスト」という概念が具体化してきました。

 

3.後期

 

(1)命令文の引数の数

(2)命令の繰り返し実行をどうするか

繰り返し対応の為に命令文を4つの整数配列(命令番号-int[0]と引数int[1]~int[3])にすることは決定しました。しかし、複数の命令文をどのように命令セットとするのか?また、繰り返し回数をどのようにデータ化するのか、という問題が残っていました。

これに対する私の回答は、命令番号-1で「(新しいの)当該命令セットの開始(同時に先行する命令セットがあればその終了」という定義を行い、その第1引数int[1]として「繰り返し回数を与える仕様にしました。

 

(3)UI(User Interface)との関係

名前」と「説明」を付加することから、命令セット命令リスト(命令番号-1で区切られる複数の命令セットを文字列化して(最終的に入出力用csvファイルに落とすためにも)読み書きするメソッドが必要となり、

 

命令セットの「文字列フォーマット」

-1,(繰り返し回数),0,0

   ・

   ・

   ・

int[0],int[1],int[2],int[3]

int[0],int[1],int[2],int[3]

int[0],int[1],int[2],int[3]

   ・

   ・

   ・

"Name:",(名前),(説明),0

 

命令セットの末尾に入れるようにしました。

 

(4)クラス化する際の構成

この最終段階で、

 

命令文int[4]の配列

 

命令番号(int[0])、引数1(int[1]-定数)、引数2(int[2]-倍数)、引数3(int[3]-除数)

 

繰り返しの対象となる命令セット

 

命令セット(TGSetクラス)

    /////////////////////////////////////////////////
    //命令セットクラス(名称と説明付きの命令セット)
    /////////////////////////////////////////////////
    
public class TGSet
    {    //クラスメンバー
        List<int[]> CmdSet;                        //命令配列
        
public string Name {get; set;}            //命令セット名
        
public string Description {get; set;}    //説明

(以下略)

 

そして元々は旧TGEditクラスの「クラス内クラス」であった命令リストも、命令セット(TGSetクラス)のクラス化に伴い、独立した「命令リスト(TGList)」クラスにしました。

 

命令リスト(TGListクラス)

    ///////////////////////////////////////////
    //命令リストクラス-命令セットの集合クラス
    ///////////////////////////////////////////
    
public class TGList
    {
        //クラスメンバー
        List<TGSet> CmdList;
        
public string FileName {get; set;}        //ファイル入出力名
(以下略)
 

最後に

  • 部品である「命令セット」を作成する為のCmdSet
  • また複数の「命令セット」をライブラリーとして管理する「命令リスト」であるCmdListと、
  • 特定の描画の為に「命令セット」を組み合わせる「命令リスト」であるExeList

を持つTGEditクラスを最上位クラスとしました。この再構成を行った時が「【TurtleGraphics】ちょ待ってね?です。

 

TGEditクラス

    /////////////////////////////////////////////

    //TurtleGraphics命令を編集するラッパークラス
    /////////////////////////////////////////////
    
public class TGEdit
    {
        //クラスフィールド
        
public TGSet CmdSet;            //命令セット({コマンド番号, 引数}の整数配列 )
        
public TGList CmdList;            //命令リスト(命令セットの一覧)
        
public TGList ExeList;            //実行リスト(指定する命令セットの実行順リスト)
(以下略)

 

このように、以下(↓)のように階層化されたクラスに合わせ、並行的に開発してきたUIダイアログ

 

TGEditクラス

      |

TGListクラス ・・・CmdListDlg(命令リスト作成、編集用の「命令リストダイアログ」)

      |

TGSet クラス・・・CmdSetDlg(命令セット作成、編集用の「命令セットダイアログ」)

                                                  |
                                         inputDlg(命令作成、編集用の「入力ダイアログ」)

 

最終的に追加して現在の「TGE.dll」というライブラリーになりました。それをサンプルに適用したのもがTurtleGraphics03です。(TGE.dllの最終テスト、という性格だった訳です。)

 

まぁ、今振り返ればいろんな紆余曲折を経ましたが、クラスの構造や構成は一番整合性がとれた良い形になったと思います。

 

次回は実際に「TGEditクラス」「TGListクラス」「TGSet クラス」のコードを「ご参考」として掲載し、その後に今回触れられなかったUIを担うCmdSetDlgCmdListDlgについて「【TurtleGraphics】TGEditor(4)-UIダイアログ」で説明します。

 

【TurtleGraphics】シリーズも中盤過ぎに差し掛かり、最終目的であるNovak.TurtleGraphics.dllを使って「様々なタートルグラフィックスを試すことができるアプリ」となりました。

 

 

前回の宿題

 

(1)(C#5のコードの紹介の前に)TGEditorの使い方を解説してからコード説明に入るか、

(2)先ずコード説明から入って、誰もがバッチファイルでコンパイルできるようにしてから、実際の使い方に入るか

 

については、

 

(1)コード量が2,000行を超えること(TGE.csが1440行、TGEditor.csが622行)

(2)TGEditorにリソース(ツールバービットマップ)を使っていて、簡単にバッチファイルでコンパイルできるプログラムではないこと

 

から、次のように進めさせていただきます。

 

(1)TGEライブラリー(クラス、ダイアログ)とTGEditor(UI)に分けて、

(2)(この間の「VisualStudio版」の時のように)ブログではプログラミングハイライトを書き、「ご参考」で(必要に 応じ)分割したコードを載せるだけにし、(大変なのでもさぼらせていただきます)

(3)ご自身でコンパイルして使ってみたい、という方にはBCCForm and BCCSkeltonパッケージの中の(MSCompAssでもバッチファイルでも直ぐ様にコンパイルですことができるように)↓を「SampleBCCSkelton」-「MSCompAss」-「Debug」-「Samples」-「TGEditor」フォールダーに入れておきます。

解説:ツールバービットマップの入ったリソースファイルと「亀」アイコン付きです。バッチファイルは(TGE.dllがなければ)TGE.csとTGEditor.csの二つともコンパイルします。

 

では、次回は「【TurtleGraphics】TGEditor(3)-TGEの仕様遷移」から始めさせていただくつもりです。

 

ps. ということで終了させていただこうと思いましたが、標題「進め方」に関連するので、予告しておくと、TGEditorで手仕舞にしようと思っていた所、TGEditorの限界も正しくご理解いただいたうえで、最後っ屁として"TG_Ala_Carte外伝"

 

 

で締めようかと考えております。どうぞ最後までお楽しみください。

 

前回で一応

 

(1)先ずは指示通りにVisual Studioで使ってみる。

(2)次にWindows 11標準装備のC#5(.NET Framework 4.8)で使えるかどうか、調査してみる。

(3)可能であれば、「誰でも使えるサンプル」から始め、

 

は お終いで、今回から

 

(4)様々なタートルグラフィックスを試すことができるアプリに挑戦してみる。

 

と書きました。この(4)Nakov.TurtleGraphics.dllをつかったタートルグラフィック命令の編集アプリで、"TGEEitor"と命名しました。開発にあたっては、

 

(1)本体はSDIウィンドウにし、骨格は前にやったRichEditorのドンガラを借りてきました。

(2)ダイアログは、選択肢が多くなることを想定し、ListViewを使うので、矢張り大昔に作った最初のアプリ、ResWriterから持ってきました。

(3)これにTurtleGraphics01TurtleGraphics02(命令のデータ駆動)TurtleGraphics03(クラス化)と、発展させてきたTGEライブラリーを落とし込む

 

という構想を立てました。そして実際、TGE.dllを仮組みした後は

 

あれよ、あれよ、とばかり、予想外のスピード

 

で開発が進み、昨日初版ができた後、実際に使ってみて(a few of)あっ、これ抜けてる!」とか「あ”-、ボーンヘッド!」とかをいくつか繰り返して、本日は一応の実用版となり、先ずはおなじみのNakov.TurtleGraphicsのサンプルを作ってみました。

 

 

その他、他のサンプルも試してみました。

 

 

開発者自身がまだ使い方に慣れていないのですが、大分コツと有効な開発作法を学習してきたので、次回以降は

 

(1)(C#5のコードの紹介の前に)TGEditorの使い方を解説してからコード説明に入るか、

(2)先ずコード説明から入って、誰もがバッチファイルでコンパイルできるようにしてから、実際の使い方に入るか、

 

思案中です。いずれにせよ、ちょっと根を詰めたので、

 

すこしゆっくりさせていただいてから再開しましょう。

 

TurtleGraphics02までお話しし、私のこだわりから前回回り道]」を取ることを告知しました。

 

本日Nakov.TurtleGraphicsのラッパーライブラリーTGE.dll前回お見せしたダイアログ

 

 

の単体テストを終了し、本体

 

 

に組み込み、総合プログラミングとテストへ向かえそうです。

 

が、

 

その前にTurtleGraphics02の時と同じように、新しいライブラリー(TGE.dll)をつかった標題サンプルプログラムの変更部分についてのみ、簡単に説明します。(

:旧TGEditクラスは現在、階層に合わせてTGSetクラスTGListクラスTGEditクラスへと分離させ、一応意図通りに動いています。今後さらにデバグ、細部修正があると思いますが、基本構造は確定と考えてよいでしょう。これは今後の修正の可能性や、ちょっと長めなので、TGEditor紹介の際に解説します。

 

以下コメント、解説を青字、新しいTGEditクラス関連、および追加部分赤字で書いています。(尚、このサンプルではDLL(TGE.dll)ではなく、ソース(TGSet、TGList、TDEditの3クラス)をそのまま使っています。)

 

【TurtleGraphics03.cs】

////////////////////////////
// TurtleGraphics03.cs
// Copyright 2026 by Y-Sama
////////////////////////////

(略)
using System.IO;                        //FIle関係
using System.Text;                      //Encoding.GetEncoding を使用するのに必要

(略)
        //クラスフィールド
        TGEdit tge;                        //Turtle Graphic Editor    //解説:Lis<T>変数関連をTGEditクラスに纏めています。
(略)
        //コントロールの設定
        private void InitializeControls()
        {

(略)
            tge = new TGEdit(this);        //解説:TGEditクラスインスタンスを生成します。
        }
(略)
        //Reset_Button処理
        private void Reset_Button_Click(object sender, EventArgs e)
        {
            tge.Reset();
            tge.CmdList.Clear();
        }

        //Hide_Button処理
        private void Hide_Button_Click(object sender, EventArgs e)
        {
            if(tge.IsTurtleShown())
            {
                tge.ShowTurtle(false);
                Hide_Button.Text = "亀表示";
            }
            else
            {
                tge.ShowTurtle(true);
                Hide_Button.Text = "亀非表示";
            }
        }

        //Design1_Button処理
        private void Design1_Button_Click(object sender, EventArgs e)
        {

           //解説:以下はRGEditクラスインスタンスtgeのメンバーである命令セット(TGSet-ひとまとまりの命令群で、「名称」と「説明文」を「人間様の認知」の為に持ちます)クラスを使って命令リスト(TGList)クラスに命令セットを登録しています。
            tge.CmdSet.Clear();                    //命令セットの初期化

            tge.CmdSet.Name = "Flower1";          //命令セット名

            tge.CmdSet.Description = "A blue flower";  //説明

            tge.CmdSet.AddCmd(-1, 4, 0, 0);        //4回繰り返す
            //Assign a delay to visualize the drawing process
            tge.CmdSet.AddCmd(14, 200, 0, 0);    //Turtle.Delay = 200;
            tge.CmdSet.AddCmd(12, 8, 0, 0);        //Turtle.PenSize = 8;
            //Draw a equilateral triangle
            tge.CmdSet.AddCmd(4, 30, 0, 0);        //Turtlet.Rotate(30);
            tge.CmdSet.AddCmd(1, 200, 0, 0);    //Turtle.Forward(200);
            tge.CmdSet.AddCmd(4, 120, 0, 0);    //Turtle.Rotate(120);
            tge.CmdSet.AddCmd(1, 200, 0, 0);    //Turtle.Forward(200);
            tge.CmdSet.AddCmd(4, 120, 0, 0);    //Turtle.Rotate(120);
            tge.CmdSet.AddCmd(1, 200, 0, 0);    //Turtle.Forward(200);
            //Draw a line in the triangle
            tge.CmdSet.AddCmd(4, -30, 0, 0);    //Turtle.Rotate(-30);
            tge.CmdSet.AddCmd(6, 0, 0, 0);        //Turtle.PenUp();
            tge.CmdSet.AddCmd(2, 50, 0, 0);        //Turtle.Backward(50);
            tge.CmdSet.AddCmd(7, 0, 0, 0);        //Turtle.PenDown();
            tge.CmdSet.AddCmd(2, 100, 0, 0);    //Turtle.Backward(100);
            tge.CmdSet.AddCmd(6, 0, 0, 0);        //Turtle.PenUp();
            tge.CmdSet.AddCmd(1, 150, 0, 0);    //Turtle.Forward(150);
            tge.CmdSet.AddCmd(7, 0, 0, 0);        //Turtle.PenDown();
            tge.CmdSet.AddCmd(4, 30, 0, 0);        //Turtle.Rotate(30);
            tge.CmdList.AddSet(tge.CmdSet);        //この描画セットの記録
        }

        //Design2_Button処理
        private void Design2_Button_Click(object sender, EventArgs e)
        {
            tge.CmdSet.Clear();                    //命令セットの初期化
            tge.CmdSet.Name = "Flower2";          //命令セット名

            tge.CmdSet.Description = "A green and blue flower";  //説明

            tge.CmdSet.AddCmd(-1, 4, 0, 0);        //4回繰り返す
            //Assign a delay to visualize the drawing process
            tge.CmdSet.AddCmd(14, 150, 0, 0);    //Turtle.Delay = 150;
            tge.CmdSet.AddCmd(12, 8, 0, 0);        //Turtle.PenSize = 8;
            //Draw a equilateral triangle
            tge.CmdSet.AddCmd(11, Color.Green.ToArgb(), 0, 0);  
 //Turtle.PenColor = Color.Green;(Color構造体→整数の変換)
            tge.CmdSet.AddCmd(4, 30, 0, 0);        //Turtle.Rotate(30);
            tge.CmdSet.AddCmd(1, 200, 0, 0);    //Turtle.Forward(200);
            tge.CmdSet.AddCmd(4, 120, 0, 0);    //Turtle.Rotate(120);
            tge.CmdSet.AddCmd(1, 200, 0, 0);    //Turtle.Forward(200);
            tge.CmdSet.AddCmd(4, 120, 0, 0);    //Turtle.Rotate(120);
            tge.CmdSet.AddCmd(1, 200, 0, 0);    //Turtle.Forward(200);
            //Draw a line in the triangle
            tge.CmdSet.AddCmd(11, Color.Blue.ToArgb(), 0, 0);  
 //Turtle.PenColor = Color.Blue;(Color構造体→整数の変換)
            tge.CmdSet.AddCmd(4, -30, 0, 0);    //Turtle.Rotate(-30);
            tge.CmdSet.AddCmd(6, 0, 0, 0);        //Turtle.PenUp();
            tge.CmdSet.AddCmd(2, 50, 0, 0);        //Turtle.Backward(50);
            tge.CmdSet.AddCmd(7, 0, 0, 0);        //Turtle.PenDown();
            tge.CmdSet.AddCmd(2, 100, 0, 0);    //Turtle.Backward(100);
            tge.CmdSet.AddCmd(6, 0, 0, 0);        //Turtle.PenUp();
            tge.CmdSet.AddCmd(1, 150, 0, 0);    //Turtle.Forward(150);
            tge.CmdSet.AddCmd(7, 0, 0, 0);        //Turtle.PenDown();
            tge.CmdSet.AddCmd(4, 30, 0, 0);        //Turtle.Rotate(30);
            tge.CmdList.AddSet(tge.CmdSet);        //この描画セットの記録
        }

        //Draw_Button処理
        private void Draw_Button_Click(object sender, EventArgs e)
        {
            tge.CmdSet.Clear();                    //命令セットの初期化
            tge.CmdSet.Name = "Swirl";          //命令セット名

            tge.CmdSet.Description = "A red swirl";  //説明

           tge.CmdSet.AddCmd(-1, 1, 0, 0);        //1回のみ
            tge.CmdSet.AddCmd(11, Color.Red.ToArgb(), 0, 0);    //Turtle.PenColor = Color.Red;(Color構造体→整数の変換)
            tge.CmdSet.AddCmd(14, 50, 0, 0);    //Turtle.Delay = 50;
            for (int i = 0; i < 25; i++)
            {
                tge.CmdSet.AddCmd(1, i * 5, 0, 0);    //Turtle.Forward(i * 5);
                tge.CmdSet.AddCmd(4, 30 + i, 0, 0);//Turtle.Rotate(30 + i);
            }
            tge.CmdList.AddSet(tge.CmdSet);        //この描画セットの記録

            //CmdListのファイル出力試験
            string cmdList = tge.CmdList.WriteList();    //命令リスト(全命令セット))の文字列化
            System.IO.File.WriteAllText(".\\CmdList.tge", cmdList);     //解説:命令リスト(CmdList)の命令全文をファイル出力しています。(現在は命令リストから直接ファイル出力をするメソッドも別途用意しています。)
            //CmdListのファイル入力試験
            tge.CmdList.Clear();                        //命令リストの初期化
            cmdList = System.IO.File.ReadAllText(".\\CmdList.tge");
            if(tge.CmdList.ReadList(cmdList))            //命令リストの読み込み
            {
                cmdList = tge.CmdList.WriteList();        //再度命令リストの文字列化、ファイル出力
                MessageBox.Show(cmdList, "CmdList", MessageBoxButtons.OK, MessageBoxIcon.Information);
                System.IO.File.WriteAllText(".\\CmdList.tge", cmdList);
            }
            int n = tge.CmdList.CountSets();            //命令セット数
            MessageBox.Show(n.ToString(), "CountSets - Tiems to repeat", MessageBoxButtons.OK, MessageBoxIcon.Information);
            DisableCtrls();                                //ボタン無効化
            tge.CmdList.DoSet();                        //命令セットの実行
            EnableCtrls();                                //ボタン有効化
        }

        private void DisableCtrls()     //解説:TurtleGraphicsの描画に時間がかかるので、その間は操作できないようにしています。
        {
            Reset_Button.Enabled = false;
            Hide_Button.Enabled = false;
            Design1_Button.Enabled = false;
            Design2_Button.Enabled = false;
            Draw_Button.Enabled = false;
        }


        private void EnableCtrls()     //解説:ボタン操作を可能として再開です。
        {
            Reset_Button.Enabled = true;
            Hide_Button.Enabled = true;
            Design1_Button.Enabled = true;
            Design2_Button.Enabled = true;
            Draw_Button.Enabled = true;
        }

    }

     //解説:以下は単にTGE.dllの中身のクラスの紹介だけです。

    ///////////////////////////////////////////////////////////////////////////////
    // TGSet、TGList、TGEditクラス (TGE.dll) Copyright (c) 2026 by Ysama
    //LOGOベースの描画命令を纏めた「命令セット」、その一覧である「命令リスト」及び
    //指定する「命令セット」で繰り返しを含め、実行順を定めた「実行リスト」を編集し、
    //「命令セット」単体や「実行リスト」を実行して、Nakov.TurtleGraphicsの出力Form
    //に描画するクラス
    ///////////////////////////////////////////////////////////////////////////////
    ///////////////////////////////////////////////
    //命令セットクラス-名称と説明付きの命令セット
    ///////////////////////////////////////////////

    public class TGSet
(略)
    ///////////////////////////////////////////
    //命令リストクラス-命令セットの集合クラス
    ///////////////////////////////////////////

    public class TGList
 (略)
        ///////////////////////////////////////////////////////////////////////////////////////
        // >>> "*.tgd"のファイルについて <<<
        //(1) LOGOベースのTurtleGraphicsの命令に対応した命令番号と引数一つのテキストcsvファイル
        //(2) 命令'-1'はセット開始(先行セット終了、セットの区切り)で引数は繰り返し回数
        //(3) 命令セットデータの最終行は"Name:,(Name),(Description)"とする
        ///////////////////////////////////////////////////////////////////////////////////////

 

    /////////////////////////////////////////////
    //TurtleGraphics命令を編集するラッパークラス
    /////////////////////////////////////////////

    public class TGEdit
(略)

 

(そうする為に改造しているので当たり前ですが)出力は前と同じです。(タイトルが違うだけ。)しかしこの描画の前にファイル出力や命令セット数の表示等クラスの動作確認も入れていることは↑のコードからお分かりになると思います。

 

 

さて、ここまでしてきたのは

 

(1)↑でもまだ煩瑣なデータ入力をコード量を減らすUIにすること(入力ダイアログ)

(2)作った部品(命令セット)を再利用するライブラリー(命令リスト)を持つこと、

(3)最後にそのライブラリーの部品を組み合わせて描画するシナリオ(実行リスト)を作ること

 

を可能とするTGEditorを作るためでした。後、もう少しお時間をいただいて本シリーズの最初(【TurtleGraphics】亀センニン、出発!)に建てた方針

 

(1)先ずは指示通りにVisual Studioで使ってみる。

(2)次にWindows 11標準装備のC#5(.NET Framework 4.8)で使えるかどうか、調査してみる。

(3)可能であれば、「誰でも使えるサンプル」から始め、

(4)様々なタートルグラフィックスを試すことができるアプリに挑戦してみる。

 

最終ステージに向かいたいと思います。

 

さてさて、Nakov.TurtleGraphicsライブラリーとそのサンプルプログラムを使ってVisual Studio版

 

 

C#5(.NET Framework)版であるTurtleGraphics01(以下ウィンドウの亀アイコン横のタイトルを参照してください)

 

 

とそれをデータで駆動する汎用の命令メソッドのプロトタイプに換えたTurtleGraphics02

 

 

を作ってきて、次はさらにそれをクラス化した「TurtleGraphics03をやりましょうか?」と確かに言いました。

 

が、
 
前文撤回させていただきます。ゴメンナサイ。

 

実は、TurtleGraphics03は既にできていて

 

TGE.dll

 

 

というクラス化されたライブラリーや

 

各種ダイアログ

 

 

TGEditor本体

 

までできているのですが、ここへ来て

 

クラス構造が美しくないっ!

 

という思いが強く、大きく手戻りさせて

 

再度ラッパーのクラス化からやり直したいっ!

 

と今朝1:00に目が覚めて、寝床の中で決心しました。(老人あるある、でえすね。)

 

再度、ゴメンナサイ。

 

前回ちょっと触れましたので、ここで私の懺悔を兼ねて記しておきます。

 

私はC/C++から来た者なので、変数構造体クラスインスタンスオブジェクトの概念、スタックヒープ等メモリー内の所在や「揮発性の有無」、「値渡し(数値データそのものを渡す)参照渡し(数値データの入っているメモリーのアドレスを渡す)」等のC/C++べースの概念を一応持っていて、それをベースにC#を理解しようとしてきました。しかし、C#とC/C++はビミョーに異なることも経験してきました。(【C#学習】びみょーな違い【C#学習】今日は疲れた...【C#学習】今日も疲れた...【C#学習】C#になれると...C++で困る

 

事の発端は整数配列のコピー問題から始まりました。C#では配列の複写は基本的に参照渡しになるので、「現実に存在する整数配列orgArrayの要素データをコピーしたい場合(↓でいうところの"Deep copy")、矢張り」愚直に

 

(1)ループでコピー元の値をコピー先に移転する

int[] toCopy = new int[orgArray.Length];

for(int i = 0; i < orgArray.Length; i++)

{

    toCopy[i] = toCopy[i];

}

 

しかないのか?とChat-GPTに訊いたら、

 

(2)Array.Copyを使う

int[] toCopy = new int[orgArray.Length];

Array.Copy(orgArray, toCopy, orgArray.Length);

 

(3)Cloneを使う

int[] toCopy = (int[])orgArray.Clone();

 

があるとのこと。従って私は「Copyメソッド」と「Cloneの代入」は「値渡し(orgArrayのアドレスではなく、その値をメモリーに確保して渡すという意味で"Deep copy")」だと思ったのですが、偶々見たwebの記事から「.Clone()メソッドは"Creates a shallow copy of the Array."と書かれていました。↑の説明ではdeep copyということでしたが、参照コピーだったのでしょうか?(原文ママ)と訊き返した際のChat-GPTの返答が、

 

結論

結論から言うと、そのドキュメントの記述が正しく、**Clone() は常に「shallow copy(浅いコピー)」**です。ただし、要素の型によって見え方が変わるため、混乱しやすい点です。(Copyメソッドも"shallow copy"とのこと。)

 

でした。しかしコピーされた配列の値は参照元を変更しても変わらず、値もコピー(Deep copy?)されています。その訳は、

 

結論

  • ドキュメント通り、Clone() は shallow copy
  • 「int配列で問題ない」のは 値型だから
そして止めに、
 

結論(先に要点)

  • Shallow copy(浅いコピー)
    →「1段階だけコピーする(フィールドをそのまま複写)」
  • Deep copy(深いコピー)
    →「参照の先まで再帰的に全部コピーする」

👉 重要:
これは「値型/参照型の区別」とは別の概念です

 

ということを言われたので、

 

「要素の型?値型、参照型のこと?」→これはその理解で正しい。

「見え方?現実には違うという意味?」→「値型」オブジェクトの場合には結果として値が複写される、という意味、意図のようです。値型オブジェクトの場合には「参照」がないので、そもそも"Deep copy"、"Shallow copy"の区別の意味がない訳です。

「『1段階』?、段階って何?フィールドって変数のことだよね?」→「そのオブジェクトだけ」の意味でした。

「再帰的って『自分自身を参照して「入れ子」状に同じことが繰り返される仕組み』だよね?どういうこと?」→英文のものでも"recursively"を使っていましたが、語の本来的な意味で正しいとは思えません。私が記述するなら「複写するオブジェクトが参照である場合、またはオブジェクトの参照を含む場合には、全ての参照先をすべて(別途メモリー領域を確保して)生成して複写すること」、の意味でした。

「えっ、『値型/参照型の区別』は関係ないの?」→関係なくはないです。「値型」のオブジェクトの場合、Shallow copyで「そのオブジェクトだけ」を複製すると、「別途メモリー領域を確保して」値が渡される結果になった、だけの話です。尤もChat-GPTも「関係ない」と言っているわけではなく、「別の(しかし深く関係する)概念」と言っているだけでしたね。

?????

 

青字部分のように混乱してしまいました。(結局その意味は緑字の通りだったのですが。)

 

まぁ、概念は分かっても言葉の使い方が違う話が食い違うという典型で、終いには、

 

Q: しつこいなぁ。 

例:

class A {

    public int X;

}

この場合:

shallow copy → Xコピー

deep copy → Xコピー

これは「例外(shallowとdeepの意味が同じint型の内包しかないクラスを、『intではないクラスだ』と強弁する安倍型論法」でしょう?また 「shallow / deep copy は 『参照関係を持つオブジェクト構造においてのみ意味の差が現れる区別』」 と書いていますが、 「copy は(全オブジェクトに対して適用される概念だが) 『参照関係を持つオブジェクト構造においてのみ意味に差(フィールドのコピーのみならず参照先の生成)が現れる区別』」 と全く同じ意味でしょう?と私は言っているだけです。別に「shallow/deepというcopyの概念に差を設けました」ということに文句を言っているわけではなく、「概念が分かり易い呼称ではないね」と言っているだけです。「オブジェクトそのもののコピーと、参照型オブジェクト(または参照型オブジェクトを含むオブジェクト)にあっては参照先の値(オブジェクト)をコピーをするか否かに処理の差異がある」ことを明確にすればそれでよいのですよ。又「再帰型」というのは「参照が連続する場合には最後の参照先まで処理を継続する」という意味であることを説明すればそれでよかったんです。 結論として「あなたの説明の仕方」が今回の論議のポイントだと思います。

 

と書いてしまいました。Chat-GPTは

 

A: おっしゃる通りです。今回の論点は「用語そのもの」ではなく、説明の切り分け方と前提の明示でした。そこが不十分だったために、余計な一般化や言い換えで混乱を招きました。

 

と返しましたが、

 

生成AI相手に何熱くなってんだよ!

 

と自分に突っ込みをいれてしまいました。お粗末。

 

Webで見つけたタートルグラフィックスライブラリー、Nakov.TurtleGraphicsVisual Studioで単にサンプルを動かすプログラム

 

 

を作り、次にそれをWIndows 11標準装備のC# 5(.NET Framework 4.8)で動く

 

 

ようにしました。

 

が、何か物足りない。

 

それは亀さんを使って何か描こうとすると毎回(LOGO言語ベースの描画)コードを書いて、コンパイルして動かさなくてはならないからです。

 

そこで、Nakov.TurtleGraphicsの描画命令コードをよく見ると、

 

『亀メソッド』(基本引数は一つで、MoveToのみ引数が二つ)
Init(arg1)-タートルグラフィックスシステムを初期化します。arg1は描画対象のFormsコントロールで、指定しない場合、アクティブなフォームが描画領域となります。
Dispose() / Reset()-タートルグラフィックスシステムを破棄し、関連するすべてのリソースを解放します。
Forward(arg1)-現在の方向に亀をarg1ピクセル前進させ、ペンが下がっている場合線を描きます。
Backward(arg1)-現在の方向に亀をarg1ピクセル後退させ、ペンが下がっている場合線を描きます。
MoveTo(arg1, arg2)-亀を指定した位置へ移動させます。ペンが下がっている場合線を描きます。
Rotate(arg1)-現在の向きからarg1(度数)亀を回転させます。
RotateTo(arg1)-亀を指定角度(度数)へ向けます。初期状態の亀の方向(角度)は0度(上向き)です。
PenUp()-ペンを描画面から離し、線は描かれません。
PenDown()-ペンを描画面に降ろし、線が描かれます。

 

『亀プロパティ』(引数は一つ)
X-亀の現在X座標を取得/設定します。初期状態の亀は {0, 0}(画面中央)です。
Y-亀の現在Y座標を取得/設定します。初期状態の亀は {0, 0}(画面中央)です。
Angle-亀の現在方向(度数)を取得/設定します。(上-0、右-90、下-180、左-270)
PenColor-描画色を取得/設定します。既定色はBlue(青)です。
PenSize-描画太さ(ピクセル単位)を取得/設定します。規定値は7です。
PenVisible-ペンが下がり描画可能であればtrue、ペンが上がればfalseとなります。
ShowTurtle-亀の表示可否を取得/設定します。規定値は表示可です。
Delay-移動/回転後の待機時間(ミリ秒)を取得/設定します。規定値は0です。

 

という9メソッド、8プロパティで、どれもほとんど引数は一つあることがわかり、

 

これって汎用の命令メソッドができないかな?

 

と思ったのがきっかけでした。そしてそのプロトタイプ()を次のようにしてみました。(以下はTurtleGraphics01のコードの変更部分のみを掲載しております。その他のコードはそちらを参照してください。)

:ありがちなことですが、初期仕様は大分変遷を重ね変化してゆきます。(現在も...です、です。)

 

【TurtleGraphics02.cs】

...(省略)

using System.Collections.Generic;        //Listを使う為

...(省略)

        //クラスフィールド(解説:追加)
        List<int[]> CmdSet = new List<int[]>();                    //描画設定の整数配列int[] {コマンド番号, 引数1、引数2}
        List<List<int[]>> CmdList = new List<List<int[]>>();    //構成設定の描画設定Listの配列
 

...(省略)

        //Design1_Button処理(解説:変更)
        private void Design1_Button_Click(object sender, EventArgs e)
        {
            CmdSet.Clear();                        //CmdSetの初期化
            //Assign a delay to visualize the drawing process
            CmdSet.Add(new int[] {15, 200});    //Turtle.Delay = 200;
            CmdSet.Add(new int[] {12, 8});        //Turtle.PenSize = 8;
            //Draw a equilateral triangle
            CmdSet.Add(new int[] {4, 30});        //Turtle.Rotate(30);
            CmdSet.Add(new int[] {1, 200});        //Turtle.Forward(200);
            CmdSet.Add(new int[] {4, 120});        //Turtle.Rotate(120);
            CmdSet.Add(new int[] {1, 200});        //Turtle.Forward(200);
            CmdSet.Add(new int[] {4, 120});        //Turtle.Rotate(120);
            CmdSet.Add(new int[] {1, 200});        //Turtle.Forward(200);
            //Draw a line in the triangle

            CmdSet.Add(new int[] {4, -30});        //Turtle.Rotate(-30);
            CmdSet.Add(new int[] {6, 0});        //Turtle.PenUp();
            CmdSet.Add(new int[] {2, 50});        //Turtle.Backward(50);
            CmdSet.Add(new int[] {7, 0});        //Turtle.PenDown();
            CmdSet.Add(new int[] {2, 100});        //Turtle.Backward(100);
            CmdSet.Add(new int[] {6, 0});        //Turtle.PenUp();
            CmdSet.Add(new int[] {1, 150});        //Turtle.Forward(150);
            CmdSet.Add(new int[] {7, 0});        //Turtle.PenDown();
            CmdSet.Add(new int[] {4, 30});        //Turtle.Rotate(30);
            CmdList.Add(new List<int[]>(CmdSet));    //この描画セットの記録
//            CmdList.Add(CmdSet);    //こうすると描画セットは参照だけとなる(CmdSetの事後の変更が反映してしまう)
        }

        //Design2_Button処理(解説:変更)
        private void Design2_Button_Click(object sender, EventArgs e)
        {
            CmdSet.Clear();                        //CmdSetの初期化
            //Assign a delay to visualize the drawing process
            CmdSet.Add(new int[] {15, 200});    //Turtle.Delay = 150;
            CmdSet.Add(new int[] {12, 8});        //Turtle.PenSize = 8;
            //Draw a equilateral triangle
            CmdSet.Add(new int[] {11, Color.Green.ToArgb()});    //Turtle.PenColor = Color.Green;(Color構造体→整数の変換)
            CmdSet.Add(new int[] {4, 30});        //Turtle.Rotate(30);
            CmdSet.Add(new int[] {1, 200});        //Turtle.Forward(200);
            CmdSet.Add(new int[] {4, 120});        //Turtle.Rotate(120);
            CmdSet.Add(new int[] {1, 200});        //Turtle.Forward(200);
            CmdSet.Add(new int[] {4, 120});        //Turtle.Rotate(120);
            CmdSet.Add(new int[] {1, 200});        //Turtle.Forward(200);
            //Draw a equilateral triangle
            CmdSet.Add(new int[] {11, Color.Blue.ToArgb()});    //Turtle.PenColor = Color.Green;(Color構造体→整数の変換)
            CmdSet.Add(new int[] {4, 30});        //Turtle.Rotate(-30);
            CmdSet.Add(new int[] {6, 0});        //Turtle.PenUp();
            CmdSet.Add(new int[] {2, 50});        //Turtle.Backward(50);
            CmdSet.Add(new int[] {7, 0});        //Turtle.PenDown();
            CmdSet.Add(new int[] {2, 100});        //Turtle.Backward(100);
            CmdSet.Add(new int[] {6, 0});        //Turtle.PenUp();
            CmdSet.Add(new int[] {1, 150});        //Turtle.Forward(150);
            CmdSet.Add(new int[] {7, 0});        //Turtle.PenDown();
            CmdSet.Add(new int[] {4, 30});        //Turtle.Rotate(30);
            CmdList.Add(new List<int[]>(CmdSet));    //この描画セットの記録
        }

        //Draw_Button処理(解説:変更)
        private void Draw_Button_Click(object sender, EventArgs e)
        {
            CmdSet.Clear();                        //CmdSetの初期化
            CmdSet.Add(new int[] {11, Color.Red.ToArgb()});    //Turtle.PenColor = Color.Green;(Color構造体→整数の変換)
            CmdSet.Add(new int[] {15, 50});    //Turtle.Delay = 50;
            for (int i = 0; i < 25; i++)
            {
                CmdSet.Add(new int[] {1, i * 5});    //Turtle.Forward(i * 5);
                CmdSet.Add(new int[] {4, 30 + i});    //Turtle.Rotate(30 + i);
            }
            CmdList.Add(new List<int[]>(CmdSet));    //この描画セットの記録
            int n = CmdList.Count - 1;
            //解説:ここからデータで描画命令を実行してゆきます。
            int count = 0;
            foreach(List<int[]> cmdset in CmdList)
            {

                /* Debug用
                foreach(int[] array in cmdset)
                {
                    string str = array[0].ToString() + ", " + array[1].ToString() + ", " + array[2].ToString() + "\r\n";
                    //string str = "[" + string.Join(", ", array) + "]";
                    MessageBox.Show(str, "cmdset", MessageBoxButtons.OK, MessageBoxIcon.Information);
                }
                */

                if(count == n)        //解説:この意味は「最後の赤い渦巻き命令の時」は、ということです。
                {
                    DisableCtrls();
                    DoThis(cmdset);    
//解説:一回だけ
                    EnableCtrls();
                }
                else
                {
                    DisableCtrls();
                    DoThis(cmdset, 4);    
//解説:それ以外は「四つ葉のクローバー」なので4回繰り返しです。
                    EnableCtrls();
                }
                count++;
            }

        }


...(省略)

        //引数cmdsはコマンド配列(int[2]-コマンド番号、引数1、引数2)で
        //引数times(規定値は1)だけコマンド配列の実行を繰り返す(解説:追加)

        private void DoThis(List<int[]> cmds, int times = 1)
        {
            for(int i = 0; i < times; i++) 
   //最低でも規定値1回は実行
            {
                foreach(int[] cmd in cmds)    //cmdはint[2]
                {
                    switch(cmd[0])
                    {
                        case 0:                
//NOP - No OPeration
                        break;
                        case 1:              
 //Forward(arg)
                            Turtle.Forward(cmd[1]);
                            break;
                        case 2:                
//Backward(arg)
                            Turtle.Backward(cmd[1]);
                            break;
                        case 3:                //MoveTo(arg1, arg2)
                            int x = cmd[1] & 0xFFFF;    //LOWORD
                            int y = cmd[1] >> 16;        //HIWORD
                            Turtle.MoveTo(x, y);
                            break;
                        case 4:              
 //Rotate(arg)
                            Turtle.Rotate(cmd[1]);
                            break;
                        case 5:              
 //RotateTo(arg)
                            Turtle.RotateTo(cmd[1]);
                            break;
                        case 6:                
//PenUp()
                            Turtle.PenUp();
                            break;
                        case 7:                
//PenDown()
                            Turtle.PenDown();
                            break;
                        case 8:              
 //X
                            Turtle.X = cmd[1];
                            break;
                        case 9:                
//Y
                            Turtle.Y = cmd[1];
                            break;
                        case 10:            
//Angle
                            Turtle.Angle = cmd[1];
                            break;
                        case 11:            
//PenColor(Color構造体←整数の変換)
                            Turtle.PenColor = Color.FromArgb(cmd[1]);
                            break;
                        case 12:            
//PenSize
                            Turtle.PenSize = cmd[1];
                            break;
                        case 13:            
//PenVisible (1 - true, 0 - false)
                            Turtle.PenVisible = (cmd[1] == 1);
                            break;
                        case 14:          
 //ShowTurtle (1 - true, 0 - false)
                            Turtle.ShowTurtle = (cmd[1] == 1);
                            break;
                        case 15:          
 //Delay
                            Turtle.Delay = cmd[1];
                            break;
                        default:
                            break;
                    }
                }
            }
        }

 

Prototypeでは試みにNakov.TurtleGraphicsの命令の内、描画に係る15の命令を抜き出し、これに「何もしない(NOP)」()命令を加えて、描画命令配列(List型式-冒頭の「クラスフィールド」参照)のデータ(cmds)を、繰り返し回数(times)と共に引数で与えて描画命令を連続実行してくれる「DoThis(「DoTheseじゃないの?」という突込みはやめてね。英語の慣用句"Do this, do that!(「これやれ、あれやれ!」)"命令から名付けました)というメソッドを作り、TurtleGraphics01.csで毎回命令文(3つのボタンClick処理のコメント文オリジナルの命令文)を書いていたものを、CmdSet.Add()文で作ったint[2]の整数のList配列を与えるだけで連続実行できるようにしたものです。

:実はこれが後々、仕様変更でヒット(良いアイデア)であったことがわかります。

 

当たり前ですが、これを実行すると、

 

 

という結果になります。

 

でも、まだ何か物足りない。

 

ということで、夜も寝ないで(年寄りは夜中に尿意を感じて起きちゃうのですが、その時にプログラミング課題を思い出すとアドレナリンが出て、眠れなくなっちゃうんです)あれこれ考えてしまいます。ということで、次回は取り敢えず、

 

TurtleGraphics03.cs

 

をやりましょうか?

 

ps. なお、↑で「 //こうすると描画セットは参照だけとなる(CmdSetの事後の変更が反映してしまう)」と書きましたが、C#のいわゆる"Deep copy"、"Shallow copy"を巡り、Chat-GPTが

 

① 結論(先に要点)

  • Shallow copy(浅いコピー)
    →「1段階だけコピーする(フィールドをそのまま複写)」
  • Deep copy(深いコピー)
    →「参照の先まで再帰的に全部コピーする」

👉 重要:
これは「値型/参照型の区別」とは別の概念です

 

と言ったことから、またまたChat-GPTと議論をしてしまいました。最後は

 

Q: 結論として「あなたの説明の仕方」が今回の論議のポイントだと思います。

A: おっしゃる通りです。今回の論点は「用語そのもの」ではなく、説明の切り分け方と前提の明示でした。そこが不十分だったために、余計な一般化や言い換えで混乱を招きました。

...

「説明の仕方がポイント」

その通りです。

  • 前提(参照型が関係する場合)を先に限定すべきでした
  • 「一般論」として広げたのが混乱の原因です

ということで手じまいしましたが、誤解を生みやすいのでこれについてもまた書きましょう。

 

サンプルプログラム第一弾の準備は前回で整ったのですが、こんなことをしていて四苦八苦し、ブログの方がおろそかになりましたので、今回はしっかりとやりましょう。

 

サンプルの第一弾である”TurtleGraphics01.cs”は、

 

(1)ここに載っていたサンプルをウィンドウデザインを変更して3つ描画するように変更してVisualStudio作ったプログラム3つのファイルを、

(2)Visual Studioのシステム管理用コードや参照が邪魔なのですべて取り除いてコンパクトに統合した

 

ものです。このサンプルは手っ取り早くLOGO言語ベースのタートルグラフィックスの描画等の命令に親しむことを目的にしています。以下ではコメントと「解説:」を青字TurtleGraphics関連のコードを赤字にしています。

 

【TurtleGraphics01.cs】

////////////////////////////
// TurtleGraphics01.cs
// Copyright 2026 by Y-Sama
////////////////////////////

using System;
using System.Windows.Forms;
using System.Drawing;
using System.ComponentModel;
using System.Reflection;                //Assemblyを使う為
using Nakov.TurtleGraphics;                //TurtleGraphicsを使う為
//解説:このプログラムが参照する"Nakov.TurtleGraphics.dll"を使うので、コンパイル時に"/reference:"を使うことは既に説明しました。


namespace TurtleGraphics01
{
    class Launcher
    {
        //エントリーポイント
        [STAThread]
        static void Main()
        {
            Application.Run(new TurtleGraphics01());

            //解説:TurtleGraphics01クラスのインスタンスを実行する、という意味です。
        }
    }

    public class TurtleGraphics01 : Form
    {
        //クラスコントロール(宣言-解説:「リセット」「亀を隠す」「描画1~3」のボタン5つです。)
        private Button Reset_Button;
        private Button Hide_Button;
        private Button Draw1_Button;
        private Button Draw2_Button;
        private Button Draw3_Button;

        //クラスコントロール(実装-解説:newでインスタンスを作り、その属性を各種プロパティで定めます。)
        private void InitializeControls()
        {
            this.Reset_Button = new Button();
            this.Hide_Button = new Button();
            this.Draw1_Button = new Button();
            this.Draw2_Button = new Button();
            this.Draw3_Button = new Button();
            this.SuspendLayout();
            //Reset_Button
            this.Reset_Button.Anchor = AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right;
            this.Reset_Button.Location = new Point(713, 12);
            this.Reset_Button.Name = "Reset_Button";
            this.Reset_Button.Size = new Size(75, 30);
            this.Reset_Button.TabIndex = 0;
            this.Reset_Button.Text = "リセット";
            this.Reset_Button.UseVisualStyleBackColor = true;
            this.Reset_Button.Click += new EventHandler(this.Reset_Button_Click);
            //Hide_Button
            this.Hide_Button.Anchor = AnchorStyles.Top | AnchorStyles.Right;
            this.Hide_Button.Location = new Point(713, 44);
            this.Hide_Button.Name = "Hide_Button";
            this.Hide_Button.Size = new Size(75, 30);
            this.Hide_Button.TabIndex = 1;
            this.Hide_Button.Text = "亀表示";
            this.Hide_Button.UseVisualStyleBackColor = true;
            this.Hide_Button.Click += new EventHandler(this.Hide_Button_Click);
            //Draw1_Button
            this.Draw1_Button.Anchor = AnchorStyles.Top | AnchorStyles.Right;
            this.Draw1_Button.Location = new Point(713, 74);
            this.Draw1_Button.Name = "Draw1_Button";
            this.Draw1_Button.Size = new Size(75, 30);
            this.Draw1_Button.TabIndex = 2;
            this.Draw1_Button.Text = "描画1";
            this.Draw1_Button.UseVisualStyleBackColor = true;
            this.Draw1_Button.Click += new EventHandler(this.Draw1_Button_Click);
            //Draw2_Button
            this.Draw2_Button.Anchor = AnchorStyles.Top | AnchorStyles.Right;
            this.Draw2_Button.Location = new Point(713, 104);
            this.Draw2_Button.Name = "Draw2_Button";
            this.Draw2_Button.Size = new Size(75, 30);
            this.Draw2_Button.TabIndex = 3;
            this.Draw2_Button.Text = "描画2";
            this.Draw2_Button.UseVisualStyleBackColor = true;
            this.Draw2_Button.Click += new EventHandler(this.Draw2_Button_Click);
            //Draw3_Button
            this.Draw3_Button.Anchor = AnchorStyles.Top | AnchorStyles.Right;
            this.Draw3_Button.Location = new Point(713, 134);
            this.Draw3_Button.Name = "Draw3_Button";
            this.Draw3_Button.Size = new Size(75, 30);
            this.Draw3_Button.TabIndex = 4;
            this.Draw3_Button.Text = "描画3";
            this.Draw3_Button.UseVisualStyleBackColor = true;
            this.Draw3_Button.Click += new EventHandler(this.Draw3_Button_Click);
            // TurtleGraphics01-解説:これはプログラムのフォーム自体で、↑のボタンを貼り付けています。
            Assembly myOwn = Assembly.GetEntryAssembly();
            this.Icon = Icon.ExtractAssociatedIcon(myOwn.Location);    //プログラムアイコンをフォームにつける
            this.AutoScaleDimensions = new SizeF(6F, 12F);
            this.AutoScaleMode = AutoScaleMode.Font;
            this.ClientSize = new Size(800, 450);
            this.Controls.Add(this.Hide_Button);
            this.Controls.Add(this.Reset_Button);
            this.Controls.Add(this.Draw1_Button);
            this.Controls.Add(this.Draw2_Button);
            this.Controls.Add(this.Draw3_Button);
            this.Name = "TurtleGraphics01";
            this.Text = "TurtleGraphics01";
            this.ResumeLayout(false);
        }

        public TurtleGraphics01()
        {
            InitializeControls();    //解説:これがコンストラクターでインスタンスを作成するときに呼ばれ、↑のメソッドを実行します。
        }

        private void Reset_Button_Click(object sender, EventArgs e)    //解説:初期化(Dispose()と同じ)はメソッドです。
        {
            Turtle.Reset();
        }

        private void Hide_Button_Click(object sender, EventArgs e)    //解説:亀の表示はプロパティです。
        {
            if (Turtle.ShowTurtle)
            {
                Turtle.ShowTurtle = false;
                this.Hide_Button.Text = "亀表示";
            }
            else
            {
                Turtle.ShowTurtle = true;
                this.Hide_Button.Text = "亀非表示";
            }
        }

        private void Draw1_Button_Click(object sender, EventArgs e)
        {
            // Assign a delay to visualize the drawing process
            Turtle.Delay = 200;    //解説:描画後の一旦停止(描画中断時間)はプロパティ。

            // Draw a equilateral triangle    //解説:回転、前進命令はメソッド。
            Turtle.Rotate(30);
            Turtle.Forward(200);
            Turtle.Rotate(120);
            Turtle.Forward(200);
            Turtle.Rotate(120);
            Turtle.Forward(200);


            // Draw a line in the triangle    //解説:PenUpは描画中止、PenDownは描画再開です。

            Turtle.Rotate(-30);
            Turtle.PenUp();
            Turtle.Backward(50);
            Turtle.PenDown();
            Turtle.Backward(100);
            Turtle.PenUp();
            Turtle.Forward(150);
            Turtle.PenDown();
            Turtle.Rotate(30);

        }

        private void Draw2_Button_Click(object sender, EventArgs e)
        {
            // Assign a delay to visualize the drawing process
            Turtle.Delay = 150;
            Turtle.PenSize = 8;
    //解説:描画幅はプロパティ。

            // Draw a equilateral triangle
            Turtle.PenColor = Color.Green;    //解説:描画色もプロパティ。
            Turtle.Rotate(30);
            Turtle.Forward(200);
            Turtle.Rotate(120);
            Turtle.Forward(200);
            Turtle.Rotate(120);
            Turtle.Forward(200);


            // Draw a line in the triangle
            Turtle.Rotate(-30);
            Turtle.PenUp();
            Turtle.Backward(50);
            Turtle.PenDown();
            Turtle.PenColor = Color.Blue;
            Turtle.Backward(100);
            Turtle.PenUp();
            Turtle.Forward(150);
            Turtle.PenDown();
            Turtle.Rotate(30);

        }

        private void Draw3_Button_Click(object sender, EventArgs e)
        {
            Turtle.PenColor = Color.Red;
            Turtle.Delay = 50;

            for (int i = 0; i < 25; i++)    //解説:このようにループを設け、繰り返しで引数を変化させることもできます。
            {
                Turtle.Forward(i * 5);
                Turtle.Rotate(30 + i);

            }
        }
    }
}

 

このファイルを前回述べたようにコンパイルしてください。こうなります。

 

TurtleGraphicsのより詳細な命令文の説明はここ(英文ですが...)をご覧ください。為念、私が書いた概要は↓の通り。

 

『亀メソッド』(基本引数は一つで、MoveToのみ引数が二つ)
Init(arg1)-タートルグラフィックスシステムを初期化します。arg1は描画対象のFormsコントロールで、指定しない場合、アクティブなフォームが描画領域となります。
Dispose() / Reset()-タートルグラフィックスシステムを破棄し、関連するすべてのリソースを解放します。
Forward(arg1)-現在の方向に亀をarg1ピクセル前進させ、ペンが下がっている場合線を描きます。
Backward(arg1)-現在の方向に亀をarg1ピクセル後退させ、ペンが下がっている場合線を描きます。
MoveTo(arg1, arg2)-亀を指定した位置へ移動させます。ペンが下がっている場合線を描きます。
Rotate(arg1)-現在の向きからarg1(度数)亀を回転させます。
RotateTo(arg1)-亀を指定角度(度数)へ向けます。初期状態の亀の方向(角度)は0度(上向き)です。
PenUp()-ペンを描画面から離し、線は描かれません。
PenDown()-ペンを描画面に降ろし、線が描かれます。

 

『亀プロパティ』(引数は一つ)
X-亀の現在X座標を取得/設定します。初期状態の亀は {0, 0}(画面中央)です。
Y-亀の現在Y座標を取得/設定します。初期状態の亀は {0, 0}(画面中央)です。
Angle-亀の現在方向(度数)を取得/設定します。(上-0、右-90、下-180、左-270)
PenColor-描画色を取得/設定します。既定色はBlue(青)です。
PenSize-描画太さ(ピクセル単位)を取得/設定します。規定値は7です。
PenVisible-ペンが下がり描画可能であればtrue、ペンが上がればfalseとなります。
ShowTurtle-亀の表示可否を取得/設定します。規定値は表示可です。
Delay-移動/回転後の待機時間(ミリ秒)を取得/設定します。規定値は0です。

 

如何だったでしょうか?私が初めに感じた印象は、

 

OOPのC#で書いた割には、クラスインスタンスを作らずにすべて静的メソッドを命令文のように使っているなぁ

:データと処理が一緒になったOOP作法では、例えば Turtleクラスのインスタンスを、"Turtle turtle = new Turtle();"のように作り、プロパティをturtle.Angle、メソッドをturtle.Forward(12)として使うのが一般的です。静的クラスについてはこちら参照。

 

というものです。故意にLOGO言語的に見せようとしたのかもしれません。

 

ここの解説サイトでは特に何も言っていないので、初めての人は描画1~3のボタンを押した(Click)処理を書き直し、他の図形の命令文をいれて再コンパイルすることになりますよね?でもそれって「メンドイッ!」と思われるでしょう?なので柔軟性はやや失われますが、

 

簡単に描画命令の組み合わせを作ることができないか?

 

と考え始めたわけですよ。次回はこのTurtleGraphics01の発展形としての

 

TurtleGraphics02

 

を紹介します。

 

現在進行形の【TurtleGraphics】。スタートの方針設定

 

(1)先ずは指示通りにVisual Studioで使ってみる。

(2)次にWindows 11標準装備のC#5(.NET Framework 4.8)で使えるかどうか、調査してみる。

(3)可能であれば、「誰でも使えるサンプル」から始め、

(4)様々なタートルグラフィックスを試すことができるアプリに挑戦してみる。

 

を経て、サンプルプログラムをVisualStudio版を作り、C#5版に移植する準備が整いましたが、(ブログ記事に先行する)現在の私の開発状況は上記の(4)に差し掛かっています。

 

現在のWEB環境のUIに比べると、ややレトロな印象を与えますが、私的にはこういうのがしっくり来ますね。これは改めて書きますが、

 

(1)サンプルのプログラムは完成したが、異なる図形は毎回手作業プログラミングとコンパイルが必要。

(2)一連の描画命令をデータを喰わせると図形を書く所謂"Data-driven"的インタープリターを志向し、

(3)その為の「Nakov.TurtleGraphics.dllのwrapper」的な「TG(TurtleGraphics)Editクラス」の開発を行い、

(4)基本機能はとりあえずできたので、それを入れる「箱」が↑のSDIウィンドウの"TGEditor"

 

まで来ています。

 

現在「画像描画データの編集」を行うUIを開発しているところで、今日は絶対に必要な「入力、編集UI」としての「FormListEditor」という「ListViewを使った汎用ダイアログのプロトタイプ

 

に取り組んでいました。今後はこのプロトタイプを応用して↑の「箱」に組み込み、動作確認とデバッグを行ってゆきます。(途中で機能強化や仕様変更が色々と出てくるでしょうが...)

 

取り急ぎ現況報告まで。