前回Cellクラスをやったので、今回からWorldクラスをやります。

Worldクラスも以前にオリジナルのC++のプログラムのCworldクラスで解説しましたので、単に移植しただけ前半アバターを入れた為に大きく変えたGoes_Aroundメソッド関連中盤と、PictureBox派生コントロール化した描画部分後半ということで3回でやりましょうか?

 

正直「単に移植しただけ前半」は基本C++版のCworldクラスの構成や機能とあまり変わりはありません。以下はWorldクラスの基本的なデータ(フィールド)とメソッドになります。コメントを細かく書いているので内容は分かると思いますし、追加で「解説:」を入れておきました。

 

【Worldクラス前半】

    //////////////////
    //Worldクラス定義
    //////////////////

    public class World : PictureBox
    {
        ///////////////////////
        //定数定義(変更不可)
        ///////////////////////

        const int Wld_Width = 80;                //Worldの幅(セル数)
        const int Wld_Height = 40;                //Worldの高さ(セル数)
        const int Max_Population = Wld_Width * Wld_Height / 2;    //「世界」に棲めるセルの最大人口
        const int Max_Tribe = 4;                //現在はCell-P, Cell-B, Cell-G, Cell-D(死亡)
        const int Max_Dir = 8;                    //↑:0、↗:1、→:2、↘:3、↓:4、↙:5、←:6、↖:7
        const int Not_Found = -1;                //Find_***処理用
        /////////////////////////////////
        //ゲーム関係フィールド
        /////////////////////////////////

        public static Random rand;                //乱数(World、Cell共通)-解説:例のプログラムで一本化した乱数発生器です。
        private bool Gameover = false;            //ゲーム終了サイン
        private string Message;                    //ゲームオーバーの際に、呼び出しユーザーに返すメッセージ
        private int Ini_pop = 0;                //開始時のセル数(0で初期化)
        private List<Cell> CellList = new List<Cell>();                //セルリスト
        private int[] Tribe_Status = new int[4];                    //Cell-P, Cell-B, Cell-G, Cell-Dの状況を記録(0で初期化)
        private char[] Map = new char[Wld_Width * Wld_Height];        //「文字」地形図(野原:' '、山:'^'、河川:'~'、食物:'O')
        private int[] PrevMap = new int[Wld_Width * Wld_Height];    //直前の「論理」地形図(野原:0、山:1、河川:2、食物:3、セル:4 + ID)
        public int MyID = Not_Found;            //アバターCellのID
        //【調整項目】定数定義(変更可)
        int Max_Param = 100;                    //Cellの属性の最大値
        int Increase = 1;                        //パラメーターの増加幅
        int Decrease = -1;                        //パラメーターの減少幅
        ///////////////////////
        //描画関係フィールド
        ///////////////////////
        //地形描画用Bitmap「野原:0」、「山:1」、「河川:2」、「食物:3」

        Bitmap[] Terrain_Bmp = new Bitmap[Max_Tribe];
        //Cell描画用Bitmap「ピンク:0」、「ブルー:1」、「グリーン:2」、「モノ(死亡):3」+ Cell-Me(Avatar)
        Bitmap[] Cell_Bmp = new Bitmap[Max_Tribe + 1];
        //PictrueBox描画関係プロパティ
        public Bitmap Canvas {set; get;}        //仮想画面ビットマップ
        public Graphics gHandle {set; get;}        //仮想画面のグラフィック

        //コンストラクター
        public World()
        {
            //乱数の初期化
            rand = new Random((int)DateTime.Now.Ticks);
            //World(PictureBoxから派生)コントロール
            this.Width = 16 * Wld_Width;        //16 x 16ビットマップ x 80
            this.Height = 16 * Wld_Height;        //16 x 16ビットマップ x 40
            //本プログラムのリソースマネージャー作成
            ResourceManager rm = new ResourceManager("Cell_World", Assembly.GetAssembly(typeof(World)));
            //ビットマップの読み込みー解説:埋め込みリソースに入っています。
            //地形は「野原:0」、「山:1」、「河川:2」、「食物:3」
            Terrain_Bmp[0] = (Bitmap)rm.GetObject("Field");
            Terrain_Bmp[1] = (Bitmap)rm.GetObject("Mountain");
            Terrain_Bmp[2] = (Bitmap)rm.GetObject("River");
            Terrain_Bmp[3] = (Bitmap)rm.GetObject("Food");
            //Cellは「ピンク:0」、「ブルー:1」、「グリーン:2」、「モノ(死亡):3」
            Cell_Bmp[0] = (Bitmap)rm.GetObject("Cell-P");
            Cell_Bmp[1] = (Bitmap)rm.GetObject("Cell-B");
            Cell_Bmp[2] = (Bitmap)rm.GetObject("Cell-G");
            Cell_Bmp[3] = (Bitmap)rm.GetObject("Cell-D");
            //アバターCell用-解説:アバターセルは濃いピンクのセルになります。
            Cell_Bmp[Max_Tribe] = (Bitmap)rm.GetObject("Cell-Me");
            //「世界」の初期化
            Init();
        }

        //デストラクター
        ~World()
        {
            //「世界」の初期化
            Init();                                    //「世界」の初期化
            //ビットマップの廃棄
            for(int i = 0; i < Max_Tribe; i++)
            {
                Terrain_Bmp[i].Dispose();
                Cell_Bmp[i].Dispose();
            }
            //アバターCell用
            Cell_Bmp[Max_Tribe].Dispose();
            //描画関係終了処理
            if(gHandle != null)
                gHandle.Dispose();                    //グラフィックリソースの開放
            if(Canvas != null)
                Canvas.Dispose();                    //ビットマップリソースの開放
        }

        /////////////////////
        //ゲーム関係メソッド
        /////////////////////

        public void Init()                            //「世界」の初期化
        {
            //MapとPrevMapの初期化(新しいC#ではArray.Fill)-Windows 11標準装備のC#はVer 5.0
            for(int i = 0; i < Map.Length; i++)
            {
                Map[i] = '\0';        //オリジナルの「文字」マップ
                PrevMap[i] = -1;    //「文字」マップに基づく論理マップ
            }
            //再ゲームも考えられるのでGCに任せる
            CellList.Clear();                        //Cellのリストを初期化
            Gameover = false;                        //ゲーム開始
            Message = String.Empty;                    //メッセージの初期化
            //既存の仮想画面、グラフィックハンドルがあれば廃棄する
            if(gHandle != null)
                gHandle.Dispose();
            if(Canvas != null)
                Canvas.Dispose();
            //指定サイズ(16 x 16)のビットマップ仮想画面(80 x 40)の作成
            Canvas = new Bitmap(16 * Wld_Width, 16 * Wld_Height);
            //Graphicsクラス(アンチエイリアス)
            gHandle = Graphics.FromImage(Canvas);
            gHandle.SmoothingMode = SmoothingMode.AntiAlias;
            gHandle.PixelOffsetMode = PixelOffsetMode.HighQuality;
            //背景表示
            if(Read_Map("world_terrain.trn"))    //解説:この"world_terrain.trn"がシステムマップです。
                ShowMap();
        }

        public void GameOver()                        //強制終了
        {
            //ゲーム終了処理
            Gameover = true;
        }

        public bool Is_GameOver()                    //ゲーム終了サインの取得
        {
            return Gameover;
        }

        public string Get_Message()                    //ゲーム終了時のメッセージを取得
        {
            return Message;
        }

        public bool Read_Map(string fn)                //「世界」の地図を読み込む
        {
            string data = "";
            try
            {
                using(StreamReader file = new StreamReader(fn, Encoding.UTF8))    //入力UTF-8
                {
                    data = file.ReadToEnd();        //ファイルを読む
                    file.Close();                    //ファイルを閉じる
                }
            }
            catch(FileNotFoundException e)            //FileNotFoundException用
            {
                string errMessage = "地形ファイル(" + fn + ")を読み込めませんでした" + Environment.NewLine;
                MessageBox.Show(errMessage + e.Message , "ファイル読込エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
                Message += errMessage;
                return false;
            }
            catch(Exception e)                        //FileNotFoundException以外用
            {
                MessageBox.Show("その他エラーが発生しました。\r\n" + e.Message , "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
            //文字列の行分割
            string[] lines = data.Split(new[]{"\r\n","\n","\r"}, StringSplitOptions.None);
            //エラーチェック
            if(lines.Length != Wld_Height)
            {
                MessageBox.Show("正しい地形ファイルではありません。" , "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
            else
            {
                data = String.Empty;
                foreach(string line in lines)
                    data += line;
                Map = data.ToCharArray();
            }
            return true;
        }

        public int Check_What(int x, int y)            //Mapの桝目をチェックし、何があるかを返す(野原:0、山:1、河川:2、食物:3、セル:4 + m_id、それ以外:-1)
        {
            int val = Not_Found;    //戻り値
            //エラーチェック
            if(x >= Wld_Width || x < 0 || y >= Wld_Height || y < 0)
                return val;
            Cell c;                    //周囲にCellがいる場合のID取得用
            switch(Map[y * Wld_Width + x])
            {
                case '\0':            //「NULL(0X0)」 - 0
                case ' ':            //「野原(0X20)」- 0
                    val = 0;
                    break;
                case '^':            //「山(0X5E)」  - 1
                    val = 1;
                    break;
                case '~':            //「河川(0X7E)」- 2
                    val = 2;
                    break;
                case 'O':            //「食物(0X4F)」- 3
                    val = 3;
                    break;
                case 'C':            //「cell(0X43)」- 4 + ID
                    c = Get_Cell(x, y);    //その位置にいるセルのポインターを取得
                    val = 4 + c.ID;        //セルであることとIDを通知する
                    break;
                default:            //エラー処理
                    Message += "地形地図に規定外のもの(" + Map[y * Wld_Width + x].ToString() + ")が発見されました" + Environment.NewLine;
                    break;
            }
            return val;
        }

        public bool Create_Cells(int n, bool intmarg)    //指定個数のcellを作る
        {
            //開始時のセルの数を記録
            if(Ini_pop == 0)    Ini_pop = n;
            //Cellの生成
            for(int i = 0; i < n; i++)
            {
                Cell c = new Cell(intmarg);                //Cellインスタンスを生成
                //誕生位置決定
                while(true)
                {
                    //新たなCellのX、Y座標を乱数で求め、
                    Point loc = new Point(rand.Next() % Wld_Width, rand.Next() % Wld_Height);
                    //そこが「野原(0)」か否かチェック
                    if(Check_What(loc.X, loc.Y) == 0)
                    {
                        c.Born_At(CellList.Count, loc.X, loc.Y);    //空白であれば誕生位置として登録
                        Map[loc.Y * Wld_Width + loc.X] = 'C';        //Cellをマップに書き込む
                        Check_Around(c);                            //Cellの周囲情報を取得させる
                        break;
                    }
                }
                CellList.Add(c);                        //セルリストへ登録
            }
            return true;
        }

        public int How_Many_Cells()                        //m_celllistの登録されたcellの数を返す
        {
            return CellList.Count;
        }

        public void Check_Around(Cell c)                //周囲の状況をチェックし、指定cellのAround[8]を更新する
        {
            int[] around = c.Around;                    //Cell周囲の存在物(野原:0、山:1、河川:2、食物:3、セル:4 + ID)
            Point loc = c.Where();                        //CellのLocationを取得
            //エラーチェック(圏外は-1)
            if(loc.Y == 0)                                //上チェック不要
            {
                if(loc.X % Wld_Width == 0)                //左チェック不要
                {
                    //around[0, 1, 5, 6, 7]は不要
                    around[0] = around[1] = around[5] = around[6] = around[7] = Not_Found;
                    around[2] = Check_What(loc.X + 1, loc.Y);
                    around[3] = Check_What(loc.X + 1, loc.Y + 1);
                    around[4] = Check_What(loc.X, loc.Y + 1);
                }
                else if(loc.X % Wld_Width == Wld_Width - 1)    //右チェック不要
                {
                    //around[0, 1, 2, 3, 7]は不要
                    around[0] = around[1] = around[2] = around[3] = around[7] = Not_Found;
                    around[4] = Check_What(loc.X, loc.Y + 1);
                    around[5] = Check_What(loc.X - 1, loc.Y + 1);
                    around[6] = Check_What(loc.X - 1, loc.Y);
                }
                else                                    //左右チェック要
                {
                    //around[0, 1, 7]は不要
                    around[0] = around[1] = around[7] = Not_Found;
                    around[2] = Check_What(loc.X + 1, loc.Y);
                    around[3] = Check_What(loc.X + 1, loc.Y + 1);
                    around[4] = Check_What(loc.X, loc.Y + 1);
                    around[5] = Check_What(loc.X - 1, loc.Y + 1);
                    around[6] = Check_What(loc.X - 1, loc.Y);
                }
            }
            else if(loc.Y == Wld_Height - 1)            //下チェック不要
            {
                if(loc.X % Wld_Width == 0)                //左チェック不要
                {
                    //around[3, 4, 5, 6, 7]は不要
                    around[3] = around[4] = around[5] = around[6] = around[7] = Not_Found;
                    around[0] = Check_What(loc.X, loc.Y - 1);
                    around[1] = Check_What(loc.X + 1, loc.Y - 1);
                    around[2] = Check_What(loc.X + 1, loc.Y);
                }
                else if(loc.X % Wld_Width == Wld_Width - 1)    //右チェック不要
                {
                    //around[1, 2, 3, 4, 5]は不要
                    around[1] = around[2] = around[3] = around[4] = around[5] = Not_Found;
                    around[0] = Check_What(loc.X, loc.Y - 1);
                    around[6] = Check_What(loc.X - 1, loc.Y);
                    around[7] = Check_What(loc.X - 1, loc.Y - 1);
                }
                else                                    //左右チェック要
                {
                    //around[3, 4, 5]は不要
                    around[3] = around[4] = around[5] = Not_Found;
                    around[0] = Check_What(loc.X, loc.Y - 1);
                    around[1] = Check_What(loc.X + 1, loc.Y - 1);
                    around[2] = Check_What(loc.X + 1, loc.Y);
                    around[6] = Check_What(loc.X - 1, loc.Y);
                    around[7] = Check_What(loc.X - 1, loc.Y - 1);
                }
            }
            else                                        //上下チェック要
            {
                if(loc.X % Wld_Width == 0) {            //左チェック不要
                    //around[5, 6, 7]は不要
                    around[5] = around[6] = around[7] = Not_Found;
                    around[0] = Check_What(loc.X, loc.Y - 1);
                    around[1] = Check_What(loc.X + 1, loc.Y - 1);
                    around[2] = Check_What(loc.X + 1, loc.Y);
                    around[3] = Check_What(loc.X + 1, loc.Y + 1);
                    around[4] = Check_What(loc.X, loc.Y + 1);
                }
                else if(loc.X % Wld_Width == Wld_Width - 1) {    //右チェック不要
                    //around[1, 2, 3]は不要
                    around[1] = around[2] = around[3] = Not_Found;
                    around[0] = Check_What(loc.X, loc.Y - 1);
                    around[4] = Check_What(loc.X, loc.Y + 1);
                    around[5] = Check_What(loc.X - 1, loc.Y + 1);
                    around[6] = Check_What(loc.X - 1, loc.Y);
                    around[7] = Check_What(loc.X - 1, loc.Y - 1);
                }
                else {                                    //左右チェック要
                    //全て必要
                    around[0] = Check_What(loc.X, loc.Y - 1);
                    around[1] = Check_What(loc.X + 1, loc.Y - 1);
                    around[2] = Check_What(loc.X + 1, loc.Y);
                    around[3] = Check_What(loc.X + 1, loc.Y + 1);
                    around[4] = Check_What(loc.X, loc.Y + 1);
                    around[5] = Check_What(loc.X - 1, loc.Y + 1);
                    around[6] = Check_What(loc.X - 1, loc.Y);
                    around[7] = Check_What(loc.X - 1, loc.Y - 1);
                }
            }
        }

        public Cell Get_Cell(int n)                        //CellListの指定番目のセルを返す
        {
            return CellList[n];
        }

        public Cell Get_Cell(int x, int y)                //座標位置にあるセルを返す
        {
            int LastCell = CellList.Count;
            for(int i = 0; i < LastCell; i++)
            {
                Point loc = CellList[i].Where();
                if(loc.X == x && loc.Y == y)
                    return CellList[i];
            }
            return null;
        }

        public string Get_Distribution()                //ピンク、ブルー、グリーンの生死状況を返す
        {    //解説:C++の時はデータのみ整数配列で戻していましたが、C#では最初から文字列にしています。
            //Tribe_Statusの初期化
            for(int i = 0; i < Tribe_Status.Length; i++)
                Tribe_Status[i] = 0;
            int LastCell = CellList.Count;
            for(int i = 0; i < LastCell; i++)
            {
                switch(CellList[i].Tribe)
                {
                    case 0:        //ピンク
                        Tribe_Status[0]++;
                        break;
                    case 1:        //ブルー
                        Tribe_Status[1]++;
                        break;
                    case 2:        //グリーン
                        Tribe_Status[2]++;
                        break;
                    default:    //死亡(モノ)
                        Tribe_Status[3]++;
                        break;
                }
            }
            string Distribution = "----------" + Environment.NewLine
                                + "ピンク セル:" + Tribe_Status[0].ToString() + Environment.NewLine
                                + "ブルー セル:" + Tribe_Status[1].ToString() + Environment.NewLine
                                + "グリーンセル:" + Tribe_Status[2].ToString() + Environment.NewLine
                                + "死亡  セル:" + Tribe_Status[3].ToString();
            return     Distribution;
        }

        public string Show_Cell_Param()                    //総てのcell属性を表示する
        {
            string parameters = String.Empty;
            int LastCell = CellList.Count;
            for(int i = 0; i < LastCell; i++)
            {
                parameters += CellList[i].Show_Param();
                if(i == MyID)    //アバターを指定している場合にアバターのCellについて修正する
                {   //解説:以下は文字列末尾の改行コードを外し、" - My avatar"を付けてからまた改行コードを付けています。
                    parameters = parameters.TrimEnd('\r', '\n') + " - My avatar" + Environment.NewLine; 
                }
            }
            return parameters;
        }

 

では、次回はWorldクラスのGows_Around()メソッドとアバター処理を解説します。