前回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()メソッドとアバター処理を解説します。