さて、前回の最後に「『このMaze_DLL.dllとTestMaze.exeを機能改良する例』としてもう一回程度お付き合い下さい」と書いたので、追加仕様を実装しようとして結構苦戦しました。
すったもんだの挙句、何とか完成したので、変更点についてコメント、解説してゆきます。
【変更点-ファイル:Maze_DLL.cs】
<追加>
using System.IO; //StreamReader/Writerを使う為
using System.Text; //Encoderを使う為
//解説:これはファイルを扱う為に必須でした。
<追加>
/////////////////
//迷路関連データ
/////////////////
//迷路データの中断ファイル
const string mzFileName = "Maze.tmp";
//解説:C++の"define"による定数定義の代わりに、C#では変更できないconst変数を使います。
//迷路探索者の進行方向
public int mzWhereToGo; //上-0、右-1、下-2、左-3
//解説:最初「名無し(private)」にして、初期設定を1(右)にしていましたが、中断再開時には異なることもあることから、publicに変更し、初期値はコンストラクターで明示的に決定するようにしました。しかし、このメンバーに直接アクセスしたらコンパイラーに怒られたので別途アクセスメソッドを設けたため、privateのままでよかったように思います。
<追加>
////////////////////////////////////////////////////
//コンストラクタ(引数がない場合の初期値は41 x 41)
////////////////////////////////////////////////////
//迷路情報を初期化
if(!IniMaze()) //中断用設定ファイルがあればそれを使う
{ //なければオリジナル通りの設定となる
//迷路サイズの設定
mzWidth = width;
mzHeight = height;
MazeData = new int[mzWidth, mzHeight];
//迷路探索者の初期位置
mzWhereIam = new Cell();
mzWhereIam.X = 1;
mzWhereIam.Y = 1;
//迷路出口の初期位置
mzExit = new Cell();
mzExit.X = mzWidth - 2;
mzExit.Y = mzHeight - 2;
//迷路内の進行方向
mzWhereToGo = 1; //初期値は右(1)
}
//迷路作成用Cellリスト(解説:上記の条件式から外した)
StartCells = new List<Cell>();
//解説:IniMazeというプログラム中断時のパラメーターを記録したファイル(中断ファイル)を読みだして設定するメソッドを追加したので、それで設定できない場合のみオリジナル仕様の設定(↑)を行い、進行方向の初期設定も行うことにしました。なお、いずれもStartCellsリストは作るのでこれは外出ししました。
<追加>
///////////////////////////
//Mazeの進行方向を取得する
///////////////////////////
public int GetDir()
{
return mzWhereToGo;
}
//解説:もともとは常に白紙で、右向きで発進する仕様だったのですが、中断ファイルで右以外の方向もあり得るようになったので、↑で「別途アクセスメソッドを設けた」と書いたように、Mazeコントロールを使うアプリが進行方向のデータをMazeから取得できるようにこのメソッドを追加しました。
<追加>
///////////////
//迷路生成処理
///////////////
public int[,] GenerateMaze()
{
//既に壁データがあれば戻る(解説:中断ファイルを読み込んだ場合は、新たな迷路は作らない為)
if(MazeData[0, 0] == Wall)
return null;
//解説:オリジナル仕様では毎回Mazeコントロール(クラスインスタンス)が生成されるたびに一回限りの迷路を作っていたのですが、中断する場合は新規作成する必要がないので、「(常に存在する)左上の壁部分があれば何もしない」で戻る仕様にしました。
<追加>
/////////////////////////////
//Maze.tmpファイルを読み込み
//迷路を初期化する
/////////////////////////////
private bool IniMaze()
{
string data = "";
//ファイル(UTF-8:Encoding.UTF8)を読む
try
{
using(StreamReader file = new StreamReader(mzFileName, Encoding.UTF8))
{
data = file.ReadToEnd();
// ファイルを閉じる
file.Close();
}
}
catch(Exception e) //FileNotFoundException e
{
//Debug用-引数のeを使わないので、コンパイラーから警告が出されます。
//MessageBox.Show("エラーコード:" + e.ToString(), "初期化ファイル読み込みエラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
//文字列の行分割
string[] lines = data.Split(new[]{"\r\n","\n","\r"},StringSplitOptions.None);
//第1行(迷路の幅と高さ)の語分割
string[] widthheight = lines[0].Split(',');
if(!Int32.TryParse(widthheight[0], out mzWidth))
{
MessageBox.Show("mzWidthの初期化に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
if(!Int32.TryParse(widthheight[1], out mzHeight))
{
MessageBox.Show("mzHeightの初期化に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
//迷路の作成
MazeData = new int[mzWidth, mzHeight];
//第2行(UNIT、mzIs3D、mzWhereToGo)の語分割
string[] others = lines[1].Split(',');
if(!Int32.TryParse(others[0], out UNIT))
{
MessageBox.Show("UNITの初期化に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
if(!Boolean.TryParse(others[1], out mzIs3D))
{
MessageBox.Show("mzIs3Dの初期化に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
if(!Int32.TryParse(others[2], out mzWhereToGo))
{
MessageBox.Show("mzWhereToGoの初期化に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
//第3行(迷路探索者の位置)の語分割
string[] where = lines[2].Split(',');
mzWhereIam = new Cell();
int x, y; //TryParse用変数
if(Int32.TryParse(where[0], out x))
mzWhereIam.X = x;
else
{
MessageBox.Show("mzWhereIam.Xの初期化に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
if(Int32.TryParse(where[1], out y))
mzWhereIam.Y = y;
else
{
MessageBox.Show("mzWhereIam.Xの初期化に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
//第4行(迷路出口の位置)の語分割
string[] exitpos = lines[3].Split(',');
mzExit = new Cell();
if(Int32.TryParse(exitpos[0], out x))
mzExit.X = x;
else
{
MessageBox.Show("mzExit.Xの初期化に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
if(Int32.TryParse(exitpos[1], out y))
mzExit.Y = y;
else
{
MessageBox.Show("mzExit.Yの初期化に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
//第5行以降(迷路データ)の語分割
for(int i = 0; i < mzHeight; i++)
{
string[] dt = lines[4 + i].Split(',');
for(int j = 0; j < mzWidth; j++)
{
if(!Int32.TryParse(dt[j], out MazeData[i, j]))
{
MessageBox.Show("MazeDataの初期化に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
}
}
return true;
}
/////////////////////////////
//Maze.tmpファイルを書き込み
//中断する処理
/////////////////////////////
public void Abort()
{
//第1行(迷路の幅と高さ)
string data = mzWidth.ToString() + "," + mzHeight.ToString() + Environment.NewLine;
//第2行(UNIT、mzIs3D、mzWhereToGo)
data += UNIT.ToString() + "," + mzIs3D.ToString() + "," + mzWhereToGo.ToString() + Environment.NewLine;
//第3行(迷路探索者の位置)
data += mzWhereIam.X.ToString() + "," + mzWhereIam.Y.ToString() + Environment.NewLine;
//第4行(迷路出口の位置)
data += mzExit.X.ToString() + "," + mzExit.Y.ToString() + Environment.NewLine;
//第5行以降(迷路データ)
for(int i = 0; i < mzHeight; i++)
{
int j = 0;
while(j < mzWidth - 1)
{
data += MazeData[i, j].ToString() + ",";
j++;
}
data += MazeData[i, j].ToString() + Environment.NewLine;
}
//ファイル(UTF-8:Encoding.UTF8)を書く
using(StreamWriter file = new StreamWriter(mzFileName, false, Encoding.UTF8))
{
//ファイルを書く
file.Write(data);
// ファイルを閉じる
file.Close();
}
}
/////////////////////////////
//Maze.tmpファイルを削除する
/////////////////////////////
public void DeNovo()
{
//ファイルの削除
File.Delete(mzFileName);
}
//解説:この3つの中断ファイル操作メソッドは「中断ファイルを読む(IniMaze)」、「中断ファイルを作成する(Abort)」、「中断ファイルを削除する(DeNovo-注)」動作をします。やっている内容は、極めてプリミティブな「変数数値の文字列化/文字列の数値化とファイル読み込み/書き込み」だけで、(ファイルを扱う場合はエラーが生じることがあるのでusingやtry~catch~finallyを使うこと位が注意点で)使用されているメソッドも特殊なものはありませんので、コメントを読んでいただければわかると思います。
注:ラテン語で「新たに」「再び」という意味です。
なお、ファイルが書き込む内容は、
第1行:迷路幅、迷路高さ
第2行:UNIT値、表示方法(2D/3D)、進行方向
第3行:迷路探索者の位置
第4行:出口の位置
第5行~:迷路配列(MazeData)のWall(1)とPath(0)のデータ
です。以下の例を参照してください。(勿論暗号化したり、バイナリーで見えないように書いてもよいのですが、遊びのサンプルなので、これをいじくってチートできるようにテキストにしていること、前に書いた通りです。)
【初期状態のMaze.tmpの例】
41,41
16,True,1
1,1
39,39
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,1
1,0,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1,1,0,1,0,1
1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,1,0,1,0,1,0,0,0,0,0,0,0,0,0,1
1,1,1,0,1,0,1,0,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,1,1,0,1,0,1,0,1,1,1,1,1,1,1,1,1
1,0,0,0,1,0,1,0,0,0,1,0,0,0,0,0,1,0,0,0,1,0,1,0,0,0,0,0,1,0,1,0,0,0,0,0,1,0,0,0,1
1,0,1,1,1,0,1,1,1,0,1,1,1,0,1,0,1,1,1,0,1,0,1,1,1,0,1,1,1,0,1,1,1,1,1,0,1,1,1,0,1
1,0,1,0,1,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,1
1,0,1,0,1,0,1,0,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,0,1,1,1,0,1,0,1,1,1,0,1,0,1
1,0,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,1,0,1,0,1
1,1,1,1,1,1,1,1,1,0,1,0,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,0,1
1,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,1
1,0,1,1,1,1,1,0,1,1,1,0,1,1,1,1,1,1,1,0,1,1,1,0,1,0,1,1,1,1,1,1,1,0,1,0,1,1,1,0,1
1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,0,0,1,0,0,0,0,0,1,0,1,0,0,0,1
1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,1,1,0,1,0,1,1,1,1,1,1,1,0,1,1,1
1,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1
1,0,1,1,1,1,1,1,1,1,1,0,1,0,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1
1,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,1
1,1,1,1,1,0,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,0,1,0,1,1,1,0,1,0,1
1,0,0,0,1,0,1,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,1,0,1,0,1,0,1,0,1,0,1
1,0,1,0,1,1,1,0,1,0,1,0,1,1,1,1,1,1,1,0,1,1,1,0,1,0,1,1,1,0,1,0,1,0,1,0,1,0,1,0,1
1,0,1,0,0,0,0,0,1,0,1,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,1
1,0,1,1,1,1,1,1,1,0,1,0,1,0,1,1,1,0,1,1,1,1,1,1,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1
1,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,0,0,0,0,0,0,1,0,0,0,1
1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,0,1,1,1,1,1,1,1,0,1,0,1,0,1,0,1,1,1,1,1,0,1,0,1,1,1
1,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,0,1,0,1,0,1,0,1,0,0,0,1,0,1,0,0,0,1
1,0,1,0,1,0,1,0,1,1,1,1,1,0,1,1,1,1,1,1,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,1,1,1,1
1,0,0,0,1,0,1,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,1,0,1,0,1,0,1,0,0,0,0,0,1
1,1,1,1,1,0,1,1,1,0,1,1,1,1,1,0,1,0,1,0,1,1,1,1,1,1,1,0,1,0,1,1,1,0,1,1,1,1,1,0,1
1,0,0,0,1,0,1,0,0,0,1,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,0,1
1,0,1,0,1,0,1,1,1,1,1,0,1,1,1,1,1,0,1,1,1,0,1,1,1,0,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1
1,0,1,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,1,0,0,0,1,0,1,0,0,0,1,0,1
1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,1,1,0,1,0,1,1,1,0,1,0,1,1,1,0,1
1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,1,0,1,0,1,0,0,0,0,0,1,0,0,0,1,0,1
1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,0,1,1,1,0,1,0,1,0,1,1,1,1,1,1,1,0,1,1,1,0,1
1,0,1,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,1
1,0,1,1,1,1,1,1,1,0,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,1,1,1,1,0,1,0,1,1,1
1,0,0,0,0,0,1,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,1,0,0,0,0,0,1,0,1,0,1,0,1
1,1,1,1,1,0,1,0,1,1,1,1,1,0,1,1,1,0,1,0,1,0,1,1,1,0,1,0,1,0,1,1,1,0,1,0,1,0,1,0,1
1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,1,0,1,0,0,0,1
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
【変更点-ファイル:TestMaze.cs】
Maze_DLLの変更により、アプリケーション(以下アプリ)も変更が必要です。
<追加>
//迷路の初期化
private void InitMaze()
{
if(maze != null)
maze.Dispose();
maze = new Maze(); //初期値の41 x 41の迷路で、660 x 660のサイズとなる
maze.GenerateMaze(); //迷路を作る
maze.Display(Is3D, WhereToGo = maze.GetDir()); //Mazeコントロールの進行方向を継承して、迷路を表示する
//解説:Mazeコントロールから進行方向の値を承継します。Maze.tmpファイルがない場合は初期値(右-1)となります。
maze.Location = new Point(10, 10);
maze.Anchor = (AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Bottom | AnchorStyles.Right);
this.Controls.Add(maze);
}
//解説:先ず、オリジナルでは進行方向の初期値が右だったので、アプリの進行方向データも初期値を右にしていましたが、今回の変更により、毎回変わる可能性があるので、コントロールの初期値を取得する(WhereToGo = maze.GetDir();)仕様にしました。
<追加>
//終了処理
protected override void OnFormClosing(FormClosingEventArgs e)
{
base.OnFormClosing(e);
DialogResult dr = MessageBox.Show("終了しますか?", "確認", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
if(dr == DialogResult.Yes)
{
dr = MessageBox.Show("中断して、後で再開する予定ですか?", "確認", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
if(dr == DialogResult.Yes)
maze.Abort(); //中断ファイルを作成する
else
maze.DeNovo(); //中断ファイルを削除する
}
else
{
dr = MessageBox.Show("新しい迷路にしますか?", "確認", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
if(dr == DialogResult.Yes)
{
e.Cancel = true;
maze.DeNovo(); //中断ファイルを削除する
InitMaze();
}
else
e.Cancel = true;
}
}
//解説:最初は「はい(Yes)、いいえ(No)、中断(Abort)」のボタンを使って「三択」にしようと思っていたのですが、C#ではないのと、今までは「単純に今やっている迷路探索を続ける」選択肢が無かったので、「四択」にしました。
最初に「終了するか、否か」聞いてきますので、これに「はい」を押すと、次に「中断後再開するか、否か」着てきます。
実行終了(1)-中断すると中断ファイルが作られ、次のプログラム実行で読み込まれる。
実行終了(2)-完全終了すると存在する中断ファイルも削除され、次のプログラム実行では新規迷路となる。
終了しない(「いいえ」)を押すと、そのまま継続するか、新規迷路にするか聞いてきます。
実行継続(1)-中断ファイルを削除して、Mazeコントロールを更新するので、新規迷路でプログラムを継続することなる。
実行終了(2)-何もしないので現在の迷路を継続する。
以上でした。自分で動かしていると、41 x 41の迷路でも結構時間がかかるので、途中で中断せざるを得ない局面がままあったことから、この改造を行いました。まぁ、仕様を変更してこっちのプログラムを組みなおすと、あっちも手を入れなくてはならない、というように、DLL、アプリ共に見直しが必要で結構手がかかることが分かりました。(爆)
ではでは、
これにて一件落着。