(续上文)
尽管横刀立马是经典的也是广为人知的一个华容道开局,这并不意味着,它是唯一的。笔者网搜了一下,发现其经典开局居然有很多。下面开局转自知乎。
48开局如下图所示:
布局输入
一开始笔者想采用类似VS界面输入控件的方法,发现这样做比较麻烦,就采用了由用户自己输入类型的方法进行布局的录入。录入时,只需要输入对应的类型的,系统自动完成棋子的其余属性的设定。棋子类型前文曾经提起过,为了方便描述再列举如下:
/// _type = 5 , the blank block on the board, size is (1,1) (width, height)
/// = 1, the soldier block , size is (1,1)
/// = 2, the vertical blcok, size is (2,1) 张飞,赵云,马超,黄忠
/// = 3, the horizaontal blcok , size is (1,2) 关羽
/// = 4 , the biggest block , size is (2,2) 曹操
/// = 0, null, this area is out of the board
为了保持一致性,布局时的底层输入控件也采用了一个5*4的控件数组,这样在映射实际的布局时,转换比较方便。
控件动态生成,并同时增加了按键的事件处理代码。设计器的界面如下:
区域1 中进行基本布局的录入,区域2 显示效果,并且可以录入每个棋子的名称。录入时,只需要录入棋子左上角的类型即可,其余部分,程序自动完成。输入部分棋子时的截图如下
布局保存
保存采用了现成的 三方库 Newtonsoft,它将对象的实例转化为Json格式的文件进行保存。我们仅需把一个对象的定义和实例交给它处理就可以了。
代码实现
包括两部分,文字版和头像版。头像版中,每个棋子将对应一个头像文件。
控件生成
为了便于后续的操作,控件是生成在容器里。为了支持各种容器,使用了模板。
private void InitDesignControls<T1,T2>(T2 containerCtrl, string preFix) where T1 : Control, new() where T2 : Control
{
T1[,] controls = new T1[4, 5];
int x = _txtSize.Width; // 横向间距
int y = _txtSize.Height; // 纵向间距
x = 2;
for (int i = 1; i <= 4; i++)
{
y = 16;
for (int j = 1; j <= 5; j++)
{
controls[i - 1, j - 1] = Activator.CreateInstance<T1>();
var control = controls[i - 1, j - 1];
control.Name = string.Format("{0}_{1}{2}",preFix, i, j); // 设置名称
control.Location = new Point(x, y); // 设置位置
control.Font = _txtFont; // 设置字体和大小
control.Size = _txtSize; // 设置适当的大小
if (control is TextBox textBox)
{
textBox.TextAlign = HorizontalAlignment.Center;
textBox.MaxLength = 1;
textBox.KeyDown += Txt_NN_KeyDown;
textBox.KeyUp += Txt_NN_TextChanged;
}
else if (control is Label label)
{
// Handle Label specific properties or events
label.AutoSize = false;
label.BorderStyle = BorderStyle.FixedSingle;
}
containerCtrl.Controls.Add(control); // 将TextBox添加到GroupBox中
control.KeyDown += new System.Windows.Forms.KeyEventHandler(this.Txt_NN_KeyDown);
control.KeyUp += new System.Windows.Forms.KeyEventHandler(this.Txt_NN_TextChanged);
control.TextChanged += new System.EventHandler(this.setTxtBkColor);
y += _txtSize.Height + 2; ; // 重置y位置以开始新的一行
}
x += _txtSize.Width + 2; // 更新x位置以添加下一个TextBox到同一行
}
}
代码中增加KeyDown ,KeyUp和TextChanged 事件代码, 代码的功能是完成录入的检测和后续Demo容器内的控件显示处理。
增加头像
为了使棋子更加形象生动,后期增加了头像的录入功能。这样,用户就可以使用自己喜欢的图片作为棋子的头像了。录入头像的界面如下
操作简述
点击下方的棋子,在上面会出现对应的头像(如果已经录入过)。然后可以再用粘贴的方式将需要的图片拷贝进去,再点击 Apply按钮就完成了一个棋子头像的录入。都完成之后,再点击下面的保存按钮,就完成了一个布局的录入。也可以选择磁盘上的文件,不再赘述。
录入后的布局会出现在主界面的下拉列表中,就可以游戏或者查找最优解了。如图
布局变换简单研究
布局很多,但是这些布局是真正的独立布局,还是可以转换的呢?为了探究这背后的真相,笔者增加了这方面的简单功能,就是检测两个布局是否可以通过移动棋子互相转换,也即检测它们是不是同一个布局。
看下面的界面
分别在1 和2 的列表中选择不同的布局,然后点击长条按钮,程序会尝试将1的布局转换为2,如果成功,说明它们的本质是一个布局的不同形式而已。
例如上面的布局的检查结果如下
检测结果标明 横刀立马和 指挥若定是相关的,实际上就是同一个布局的不同形式而已。
点击 右上方的 [>>] 按钮可以查看转换过程。使用这个功能,可以发现48个布局中有不少布局其实都是一个布局的变种。
例如横刀立马和 指挥若定,经过22步移动就可以相互转化
如图所示
再如:
这两个布局显然是不能转换的,尝试查找的结果如下:
至此,华容道最优步数查找的程序终于告一段落,了却了一个小心愿。后笔者可能会尝试写一个操作系统,目的是了解一下操作系统的工作原理以及挑战一下自己的能力。
marasun BJFWDQ
2024-03-12
I will go to Ninghai to take part in a marathon that will take place on 17 Mar. Just a log.