谜之谜(译文)
By robot-v1.0
本文链接 https://www.kyfws.com/games/enigmapuzzle-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 20 分钟阅读 - 9666 个词 阅读量 0谜之谜(译文)
原文地址:https://www.codeproject.com/Articles/281954/EnigmaPuzzle
原文作者:Michael Hodel
译文由本站 robot-v1.0 翻译
前言
Enigma Puzzle – a game as difficult as the Rubik’s cube
谜之谜–与魔方一样难的游戏
介绍(Introduction)
欢迎来到谜之谜(Enigma Puzzle),这款游戏就像魔方一样困难.(Welcome to the Enigma Puzzle – a game as difficult as the Rubik’s cube.) **谜之谜(EnigmaPuzzle)**看起来无害,但非常棘手.播放区域由两个相互交叉的圆盘组成.每张光盘上有六个(looks harmless but is very tricky. The playing area consists of two circular discs that are intersecting each other. On each disc, there are six)**石头(stones)**与六轮换(alternating with six)骨头(bones).的(. The)**石头(stones)**看起来像超重三角形,(look like overweight triangles, the)**骨头(bones)**如营养不良的矩形.(as malnourished rectangles.) 由于光盘相交,因此它们共享两个(Since the discs are intersecting, they share two)**石头(stones)**和一个(and a)骨(bone).如果将上一张光盘旋转60度,则再旋转一张(. If a disc, let’s say the upper one, is rotated by 60 degrees, then one)**结石(stone)**还有一个(and one)**骨(bone)**以前也属于较低光盘的光盘被新的(that had previously also belonged to the lower disc are replaced by a new)**结石(stone)**和新的(and new)骨(bone).(.)
游戏概念(Game Concept)
Enigma的游戏概念很简单.如上所述,游戏区由两个部分重叠的转盘组成.光盘被框架包围,该框架采用相邻光盘组件的形状和颜色.(The game concept of Enigma is simple. The playing area consists, as mentioned, of two rotating discs, which partly overlap. The discs are surrounded by a frame that takes of the shape and color of the adjacent disc component.) 这两张光盘分成小段((The two discs are broken into small sections ()**石头(stones)**和(and)骨头(bones)),可以使用不同的颜色.的(), which can be colored differently. The)**石头(stones)**和(and)**骨头(bones)**进一步细分.这些零件也可以具有不同的颜色.拼图级别越高,使用的颜色就越多.(are further subdivided. These parts can have different colors as well. The higher the level of the puzzle, the more colors are used.) 通过将两个光盘沿随机方向交替旋转60度的倍数,(By alternating rotations of the two discs by multiples of 60 degrees in random directions, the)**石头(stones)**和(and)**骨头(bones)搞乱.(mess up.) 在游戏过程中,计算机将两个光盘随机向左或向右旋转,以使零件位于其他位置.可以在配置中指定计算机旋转的频率以及旋转的显示方式.游戏的目标当然是通过旋转光盘将光盘恢复到原始位置.可以通过单击箭头按钮或通过鼠标手势来打开它们.(During a game, the computer rotates the two discs randomly to the left or to the right so that the parts come to lie at other positions. How often the computer should turn and if the rotations are displayed can be specified in the configuration. The goal of the game is of course to bring the discs back to the original positions by turning them. They can be turned by clicking on the arrow buttons or by mouse gestures.) 有详细的手册((There is a detailed manual ()EnigmaPuzzle.pdf(EnigmaPuzzle.pdf))下载.英文版可以在() in the download. The English version can be found in the)\ EnigmaPuzzle \ en(\EnigmaPuzzle\en)**夹.(folder.)
背景(Background)
这个难题是由道格拉斯`恩格尔(Douglas A. Engel)发明的,它由一个塑料支架中的两个相交圆盘组成.这是对运行Windows OS的计算机的适应.(This puzzle was invented by Douglas A. Engel and it consisted of two intersecting discs in a plastic holder. This is an adaptation for computers running a windows OS.) 几年前,当我在Spektrum der Wissenschaft(<科学美国人>的德语版)一书中第一次看到这个难题时,我想为计算机实现它.由于出色的性能,我对Turbo C的首次尝试失败了.现在,我再次尝试过,该程序应在不太旧的计算机上快速运行.这是我编写的第一款游戏,因为我通常是为建筑行业开发(使用C ++).该程序是使用C#在Visual Studio 2010中开发的,并使用.NET 4.0.顺便说一句,它没有集成的解决方案算法(我也不知道).(As I saw the puzzle for the first time several years ago in an issue of Spektrum der Wissenschaft (German version of Scientific American), I wanted to implement it for the computer. My first attempt with Turbo C failed because of the terrific performance. Now I’ve tried again and the program should run quickly on a not too old computer. It is the first game I wrote, since I usually develop (with C++) for the construction industry. The program was developed in Visual Studio 2010 with C# and uses .NET 4.0. Incidentally, it has no integrated solution algorithm (I don’t know none).) 有一些问题需要解决.首先,我必须能够将木板画在屏幕上.因此,我首先在一张纸上进行了尝试,并使用了一些固定的坐标作为不同圆的中心.然后,我必须对相交的圆和线进行大量计算,才能确定一个圆的坐标(There were a few problems that would be solved. First I had to be able to draw the board onto the screen. Therefore I tried it first on a piece of paper and used some fixed coordinates for the centers of the different circles. Then I had to do a lot of calculations of intersecting circles and lines, to determine the coordinates for one)**结石(stone)**还有一个(and one)骨(bone).然后,我可以通过旋转和平移将这些对象相乘,以填满整个木板.当时的框架只是勤奋工作.(. I could then multiply these objects by means of rotation and translation, to fill the entire board. The frame was then only diligence work.) 下一个挑战是确定当前所有职位的职位(The next challenge was determining the positions of all the current)**石头(stones)**和(and)**骨头(bones)**为了确定难题是否已经解决.困难源于以下事实:(in order to determine whether the puzzle has been solved. The difficulty arose from the fact that the)**石头(stones)**和(and)**骨头(bones)**当它们从上光盘迁移到下光盘时,可以改变方向,反之亦然.此外,经常有相同的(can change the orientation when they migrate from the upper to the lower disc and vice versa. Further, there are often identical)**石头(stones)**要么(or)**骨头(bones)**可能位于不同的位置.我用光盘上各个块的位置矢量以及光盘旋转时对阵列进行的一些复杂操作来解决该问题.最复杂的部分是处理两个光盘的交叉区域.要详细解释这一点,将需要大量的措辞.找出其工作方式的最佳方法是调试代码并观察数组.(which may be located on different positions. I solved the problem with a vector of the positions of the individual blocks on a disc and some complicated manipulations on the arrays when the discs are turned. The most complex part is the handling of the intersection area of the two discs. It would need a lot of words to explain that in detail. The best way to find out how it works is to debug the code and watch the arrays.)
代码(The Code)
该程序的大多数相对简单,仅由几个类组成.最重要的是(Most of the program is relatively simple and it consists of just a few classes. The most important are) Block
,(,) Figure
和(and) Board
.(.)
的(The)**石头(stones)**和(and)**骨头(bones)**由不同的组成(are composed of different) Block
对象.板上有所有的阵列(objects. On the board, there are arrays for all the) Figure
光盘和框架的对象.的(objects for the discs and the frame. The) Board
对象还控制整个游戏.(object also controls the whole game.)
该程序包含几个注释,因此应该易于理解代码.(The program contains several comments, so it should be easy to understand the code.)
类块(The Class Block)
板上最小的单元是(The smallest unit on the board is a) Block
(例如下面两张图片中的红色区域).一个块由一个((e.g. the red area in the two pictures below). A block consists of a) GraphicsPath
对于形状,填充颜色的颜色代码和框架颜色的数字.的(for the shape, a color code for the fillcolor and a number for the framecolor. The) Paint()
方法将块绘制到给定(method draws the block into the given) Graphics
目的.(object.)
public class Block
{
public GraphicsPath GP { get; set; }
public int Col { get; set; }
public int Edge { get; set; }
public Block()
{
GP = new GraphicsPath();
Col = -1;
Edge = -1;
}
public void Paint(Graphics g)
{
if (Col >= 0 && Col < m_colors.Count())
{
g.FillPath(m_colors[Col], GP);
}
if (Edge >= 0 && Edge < m_pens.Count())
{
g.DrawPath(m_pens[Edge], GP);
g.DrawPath(m_pens[1], GP);
}
}
}
模块(The module) Blocks
有几个(has a few) static
创建所有必要方法的方法(methods that create all the necessary) Block
对象,并用特定游戏级别的原始颜色填充它们.的(objects and fill them with the original colors for a particular game level. The) GraphicsPath
这些块中的每一个都使用计算出的坐标构建,然后通过转换屏幕尺寸进行缩放.块始终固定在板上,仅为当前图案设置颜色.(of the blocks are built with the calculated coordinates and subsequently scaled by a transformation for the screen size. The blocks are always fixed on the board, only the colors of them are set for the current pattern.)
仅一个块的坐标(Only the coordinates of the blocks of one)**结石(stone)**还有一个(and one)**骨(bone)**在源代码中固定.所有其他块都是通过熟练旋转和平移给定块来创建的.可以在(are fixed in the source code. All the other blocks are created by skilled rotation and translation of the given one. This can be found in the) static
方法(method) Init(...)
在模块中(in the module) Blocks
.(.)
Cclass图(The Cclass Figure)
此类构成(This class forms the)**石头(stones)**和(and)**骨头(bones)**董事会.一种(of the board. A)**结石(stone)**由三个块组成,三个块围绕中心旋转120度(is composed of three blocks that are rotated by 120 degrees around the center of the)结石(stone).下图显示了(. The following image shows a)结石(stone),由红色,黄色和绿色块组成.(, which are composed of a red, a yellow and a green block.)
一种(A)**骨(bone)**由两个围绕中间翻转180度的块组成.下图显示了这样一个(consists of two blocks that are flipped by 180 degrees around the middle. The following illustration shows such a)骨(bone).(.)
除块外,(In addition to the blocks, a) Figure
对象还有一个用于对象方向的计数器.计数器指示是否以及如何(object has also a counter for the orientation of the object. The counter indicates whether and how the) Figure
已相对于原始状态旋转.该值可以是0(=原始),1(旋转120度)或2(旋转240度).当(has been rotated relative to the original state. The value can be 0 (= original), 1 (rotated by 120 degrees) or 2 (rotated by 240 degrees). A change of orientation can occur when a) Figure
对象从上光盘移动到下光盘,反之亦然.(object is moved from the upper to the lower disc, or vice versa.)
当然,这样的对象必须能够绘制自身.这是由(Such an object must, of course, be able to draw itself. This Is done by the) Paint()
方法,仅执行(method, which simply executes the) Paint()
全部加法(method of all added) Block
对象.块的当前颜色由电路板控制.(objects. The current color of a block is controlled by the board.)
班级委员会(The Class Board)
此类用于控制整个游戏.它提供包含板子当前状态的成员和方法,并可以旋转两个光盘.每个光盘由十二个组成(This class is used to control the whole game. It provides members and methods that contain the current state of the board and make it possible that the two discs can be rotated. Each disc consists of twelve) Figure
对象(六个(objects (six)**石头(stones)**和六个(and six)骨头(bones)).().)
如果游戏处于活动状态,那么移动也将被存储,因此可以保存并重新加载游戏.(If a game is active, ??the moves are stored as well, so the game can be saved and loaded again.)
因为并非总是需要重绘所有内容,所以将三个部分(上光盘,下光盘和框架)绘制到位图中.仅当发生某些变化(例如转动光盘)时,才创建并再次绘制该位图.当然,该框架只能创建一次,因为它始终保持固定.(For not everything needs always to be redrawn, the three parts (upper disc, lower disc and the frame) are drawn into bitmaps. Only if something changes (e.g. turning a disc) this bitmap is created and drawn again. The frame must of course be created only once, because it always remains fixed.)
这里是为上方光盘创建位图的示例.方法调用(Here an example of creating the bitmap for the upper disc. The method calls) PaintDisc(...)
画了所有的(which paints all the) Figure
使用那里的光盘对象(objects of the disc using there) Paint()
方法.(method.)
private void CreateUpperdisc()
{
// Create a bitmap
if (m_upperdisc != null)
{
m_upperdisc.Dispose();
}
m_upperdisc = new Bitmap(m_w, m_h);
Graphics g = Graphics.FromImage(m_upperdisc);
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
// Paint the disc
Paintdisc(g, eDisc.eUpperDisc);
// Clean up
g.Dispose();
}
在课堂里(In the class) Board
您还将找到光盘旋转所需的方法.有一些方法可以完成图形旋转((you will also find the methods that are needed for the rotation of the discs. There are methods that accomplish the graphical rotation () RotateDisc()
),并且该方法具有始终保持最新状态的逻辑状态(() and it has a method that holds the logical state always up to date () Rotate()
).这种方法非常棘手,因为(). This method was quite tricky, as all the possible transitions of the)**石头(stones)**和(and)**骨头(bones)**必须检查和处理.(have to be checked and handled.)
一个重要的方法是(An important method is) GetColorString
.此方法提供了(. This method provides a) string
以及光盘当前状态下板上所有块的颜色索引.这个(with the color indices of all blocks on the board for the current state of the discs. This) string
可以与颜色比较(can be compared with the color) string
在原始板上.如果两者(in the original board. If both) string
s相等,难题就解决了.这个复杂的过程是必需的,因为总是有多种解决方案,并且不可能仅依赖于块的原始位置和当前位置.(s are equal, the puzzle is solved. This rather complicated procedure is necessary because there are always several solutions and it is not possible to rely only on the original and current position of the blocks.)
public string GetColorString()
{
StringBuilder sb = new StringBuilder();
// First the stones and bones of the upper disc
for (int i = 0; i < 5; i++)
{
sb.Append(m_stones[m_upperStones[i]].GetColorString());
sb.Append(m_bones[m_upperBones[i + 1]].GetColorString());
}
sb.Append(m_stones[m_upperStones[5]].GetColorString());
sb.Append(m_bones[m_upperBones[0]].GetColorString());
// .. and now the lower disc
sb.Append(m_bones[m_lowerBones[1]].GetColorString());
for (int i = 2; i < 6; i++)
{
sb.Append(m_stones[m_lowerStones[i]].GetColorString());
sb.Append(m_bones[m_lowerBones[i]].GetColorString());
}
return sb.ToString();
}
数组"(The arrays “) m_stones
“和”(” and “) m_bones
包含(” contain the) Figure
整个板的对象.数组"(objects for the whole board. The arrays “) m_upperStones
“和”(” and “) m_upperBones
“中包含的索引(” contain the indices for the) Figure
物体位于上方光盘的特定位置.这些阵列也将用于绘制光盘(请参见(object on a specific location in the upper disc. These arrays will also be used to paint the discs (see) PaintDisc()
方法).(method).)
使用图块(Using Blocks and Figures)
首先,(First of all, the) static
方法(method) Init()
在模块中(in the module) Blocks
将创建所有(will create all the) Block
游戏所需的对象.这些(objects needed for the game. These) Block
对象将被保存在(objects will be held in a) static
数组”(array “) m_blocks
“,然后固定它们,直到板上的电平改变为止.(” and they are fixed until the level on the board changes.)
以下代码将创建一个(The following code would create a)**结石(stone)**首先创建一个块,然后旋转该块以获得其他两个块.数组"“中的块0\1和2(by first creating a block and then rotating this block to get the other two blocks. The blocks 0, 1 and 2 in the array “) m_blocks
“首先(” build together the first)结石(stone).(.)
// Create the first sub-part of the first stone
m_blocks[0].GP.AddArc(new RectangleF(6.60254F, 20, 160, 160), 180, 21.31781F);
m_blocks[0].GP.AddArc(new RectangleF(-80, 70, 160, 160), 278.68219F, 21.31781F);
m_blocks[0].GP.AddLine(new PointF(40.00000F, 80.71797F), new PointF(28.86751F, 100));
m_blocks[0].GP.AddLine(new PointF(28.86751F, 100), new PointF(6.60254F, 100F));
Matrix mat120 = new Matrix();
mat120.RotateAt(120.0F, new PointF(28.86751F, 100));
// The second sub-part of the first stone (rotate the first by 120 degrees)
m_blocks[1].GP.AddPath(m_blocks[0].GP, false);
m_blocks[1].GP.Transform(mat120);
// The third sub-part of the first stone (rotate the second part by 120 degrees)
m_blocks[2].GP.AddPath(m_blocks[1].GP, false);
m_blocks[2].GP.Transform(mat120);
前四行将创建(The first four lines would create the) GraphicsPath
为了第一(for the first) Block
“(object in the “) m_blocks
由两个弧线(A和B)和两条线(C和D)组成的数组.如果绘制了该块,则它看起来就像下面的图像(当然没有字母).(” array consisting of two arcs (A and B) and two lines (C and D). This block would look like the following image if painted (without the letters of course).)
创建第一个的其他两个块(To create the other two blocks of the first)**结石(stone)**该基本块绕着中心旋转120度(this base block is rotated by 120 degrees around the center of the)**结石(stone)**这是两条线交叉的位置(上图中的点M).(which is where the two lines cross (point M in the image above).)
以类似的方式,第一个(In a similar way, the blocks for the first)**骨(bone)**将创建并存储在”(will be created and stored in “) m_blocks
“在第3和第4位.(” on positions 3 and 4.)
// The first sub-part of the first bone
m_blocks[3].GP.AddArc(new RectangleF(6.60254F, 20, 160, 160), 218.68218F, -17.36437F);
m_blocks[3].GP.AddArc(new RectangleF(-80, 70, 160, 160), 278.68219F, 21.31781F);
m_blocks[3].GP.AddLine(new PointF(40.00000F, 80.71797F), new PointF(46.60254F, 69.28203F));
m_blocks[3].GP.AddArc(new RectangleF(6.60254F, -80, 160, 160), 120, 21.31781F);
Matrix mat180 = new Matrix();
mat180.RotateAt(180.0F, new PointF(43.30127F, 75F));
// The second sub-part of the first stone (rotate the first part by 180 degrees)
m_blocks[4].GP.AddPath(m_blocks[3].GP, false);
m_blocks[4].GP.Transform(mat180);
然后,这五个基本块将被复制六次,并且每次绕上盘的中心旋转60度(顺时针)以创建上盘的所有块.下盘的块将通过围绕下盘的中心逆时针旋转上盘的块来创建.完成所有复制和旋转之后,框架需要更多的块,然后准备好板的所有对象.它们看起来像下图:(These five basic blocks will then be copied six times and each time rotated by 60 degrees around the center of the upper disc (clockwise) to create all blocks for the upper disc. The blocks for the lower disc will be created by rotating the blocks of the upper disc anticlockwise around the center of the lower disc. After all that copying and rotating, there are some more blocks needed for the frame and then all objects for the board are ready. They would look like the following image:)
在课堂里(In the class) Board
有一些数组可以容纳10(there are a few arrays that hold the 10)石头(stones),11(, the 11)**骨头(bones)**以及板子的18个框架零件.(and the 18 frame parts for the board.)
/// <summary>
/// Figures in the board (stones and bones)
/// </summary />
private Figure[] m_stones = new Figure[10];
private Figure[] m_bones = new Figure[11];
private Figure[] m_frames = new Figure[18];
在方法中(In the method) InitBoard()
这些数组将充满(these arrays will be filled with the) Figure
对象,以便可以对其进行绘画.的(objects so that they can be painted. The) Figure
对象与初始位置的块链接.数组”(objects are linked with the blocks for the initial positions. The arrays “) m_stones
“和”(” and “) m_bones
“将用于创建光盘的旋转图像.它们始终代表游戏的当前图形状态.(” will be used to create rotated images of the discs. They always represent the current graphical state of the game.)
public void InitBoard(int level)
{
...
// Init the stones and bones
m_stones = new Figure[10];
m_bones = new Figure[11];
m_frames = new Figure[18];
// Build the blocks and color them
Block.Init(level);
// Build the stones and bones with the blocks
m_bones[0] = new Figure();
m_bones[0].AddBlock(28);
m_bones[0].AddBlock(29);
for (int i = 1; i < 6; i++)
{
m_bones[i] = new Figure();
m_bones[i].AddBlock(5 * (i - 1) + 3);
m_bones[i].AddBlock(5 * (i - 1) + 4);
}
for (int i = 0; i < 6; i++)
{
m_stones[i] = new Figure();
m_stones[i].AddBlock(5 * i);
m_stones[i].AddBlock(5 * i + 1);
m_stones[i].AddBlock(5 * i + 2);
}
for (int i = 6; i < 11; i++)
{
m_bones[i] = new Figure();
m_bones[i].AddBlock(5 * i);
m_bones[i].AddBlock(5 * i + 1);
}
for (int i = 6; i < 10; i++)
{
m_stones[i] = new Figure();
m_stones[i].AddBlock(5 * i + 2);
m_stones[i].AddBlock(5 * i + 3);
m_stones[i].AddBlock(5 * i + 4);
}
...
}
数组中的编号可以忽略,也可以是其他任何顺序.但是这样,第一个(The numbering in the arrays can be ignored and it could be any other order as well. But this way, the first)**骨(bone)**数组中的一个是刚好位于两个圆的交点上方的数组,数组中的其他石头和骨头遵循一条直线,就好像您要在板上画一个数字8一样.(in the array is the one just left above the intersection of the two circles and the other stones and bones in the arrays follow a line as if you would draw a number 8 over the board.)
还有更多的数组填充在(There are some more arrays which are filled in the) InitBoard()
方法 ("(method (") m_upperBones
“,"(”, “) m_upperStones
“,"(”, “) m_lowerBones
“,"(”, “) m_lowerStones
“).这些数组保存当前的逻辑位置("). These arrays hold the current logical position of the)**石头(stones)**和(and the)**骨头(bones)**它们用于检查难题是否已解决.(and they are used to check if the puzzle has been solved.)
更深入地了解这幅画(A deeper look into the painting)
在上一段中介绍了所有这些对象和阵列之后,该程序现在可以绘制图形并处理鼠标手势以转动光盘.在里面(With all these objects and arrays introduced in the last paragraph, the program can now paint the graphics and handle mouse gestures to turn the discs. In the) OnPaint()
主要形式的方法((method of the main form () EnigmaPuzzleDlg
),只有准备好的(), only the prepared) Bitmap
对象("(objects (") m_b.Background
“,"(”, “) m_b.LowerDisk
“和”(” and “) m_b.upperDisk
“)将在屏幕上绘制.要查看正确的光盘旋转动画,最后绘制哪张光盘很重要.总是最后移动的光盘将最后绘制.(") will be painted on the screen. To see the correct animation of disc turning it is important which disc is painted last. Always the last moved disc will be painted last.)
protected override void OnPaint(PaintEventArgs e)
{
...
e.Graphics.DrawImageUnscaled(m_b.Background, 0, 0);
if (m_b.RotDisk == Board.eDisc.eUpperDisc)
{
e.Graphics.DrawImageUnscaled(m_b.LowerDisk, 0, 0);
e.Graphics.DrawImageUnscaled(m_b.UpperDisk, 0, 0);
}
else
{
e.Graphics.DrawImageUnscaled(m_b.UpperDisk, 0, 0);
e.Graphics.DrawImageUnscaled(m_b.LowerDisk, 0, 0);
}
}
但是在使用位图之前,必须先创建它们.这是在方法中完成的(But before the Bitmaps may be used, they have to be created. That is done in the methods) CreateBackground()
,(,) CreateUppderDisk()
和(and) CreateLowerDisk()
.这些功能的第一个调用通常来自(. The first call of these functions will normally come from the) OnResize()
方法,因为该程序将始终占据整个屏幕.之后,续约的电话(method because the program will always fill the whole screen. After that, the calls to renew the) Bitmaps
将由旋转功能启动(will be initiated by the rotation functions) RotateDisk()
并由(and by the) Rotate()
以图形和逻辑方式处理所有旋转的方法.(method which handles all rotations - graphically and logically.)
public bool Rotate(eDisc disc, eDirection dir, bool bShow, EnigmaPuzzleDlg form)
{
...
// Graphically rotation
RotateDisk(60.0f, disc, dir, bShow, form);
// Logically rotation - adjust the stones and bones on the disks and take incount
// that there are two stone and one bone that overlap
...
// Create the bitmaps newly and show them
if (bShow)
{
CreateUpperDisk();
CreateLowerDisk();
form.Refresh();
}
// Check if the game has been solved
if (GameActive)
{
...
}
return false;
}
每当用户启动游戏或应根据输入反应转动光盘时,(Whenever the user starts a game or when a disc should be turned as a reaction of an input, the) Rotate()
方法将被调用.然后,此方法调用(method will be called. This method then calls the) RotateDisk()
调用另一个更复杂的方法(method which calls another, more complex) RotateDisk()
显示转盘动画的方法.这两个函数看起来相当复杂,但这仅是因为要处理旋转光盘的动画.没有第二个也可以做到(method to show the animation of the turning disk. These two functions look quite complex, but that’s only because the handling of the animation of a swinging disc. It could be done as well without the second) RotateDisk()
功能.(function.)
很大一部分(A big part of) Rotate()
是逻辑状态的处理.这看起来并不那么复杂,但是必须处理所有可能的旋转.在……的最后(is the handling of the logical state. That is not so complex as it looks like but all the possible rotations has to be handled. At the end of) Rotate()
,则可能会重新创建位图,并检查拼图是否已在最后一轮解决.(, the bitmaps may be created newly and there is a check if the puzzle has been solved with the last turn.)
处理鼠标手势(Handling Mouse Gestures)
可以通过单击四个带有箭头的小按钮之一来旋转光盘.但是也可以通过鼠标手势来实现.的(The discs may be turned by clicking on one of the four little buttons with the arrows on it. But it is also possible to do it by a mouse gesture. The) MouseDown
和(and) MouseUp
主窗体的事件用于获取两个点的坐标.下图显示了两种可能的手势-一种从A到B的手势,另一种从C到D的手势.AB应导致顺时针方向转动上盘,而CD应与下盘相同.(event of the main form are used to get the coordinates of two points. The following picture shows two possible gestures - one from A to B and one from C to D. AB should lead to clockwise turn of the upper disc and CD should the same with the lower disc.)
功能(The function) TurnDisk()
班上的(of the class) Board
处理两个给定点”(x1,y1)“和”(x2,y2)“的坐标,并确定圆盘和旋转方向.在上图中,例如有点A和B,它们的坐标可能为(ax,ay)和(bx,by).(handles the coordinates of two given points “(x1,y1)” and “(x2,y2)” and determines the disc and the direction of the rotation. In the picture above, there are for example the points A and B and they may have the coordinates (ax,ay) and (bx,by).)
先入(First in) TurnDisk()
有检查应该转动哪个磁盘.这是通过非常简单的方式完成的.从板的中线(上图的M)上方开始的每个移动都属于上光盘,而该行下方的所有内容都属于下光盘.垂直起点是”(there is check for which disk should be turned. That’s done in a very simple way. Every move that starts above the middle line of the board (M on the picture above) belongs to the upper disc and everything below that line to the lower disc. The vertical starting point is “) y1
“.有关此代码,请参见(”. The code for this can be found on the first few lines of) TurnDisc()
.将要旋转的光盘还定义了旋转的垂直中心”(. The disc which will be turned defines also the vertical center for the turn “) cy
“.(”.)
之后,两个给定点的坐标会有一些偏移.坐标以这样一种方式移动:必须转动的光盘中心在坐标(0,0)处.这里的变量”(After that, there are some shifts of the coordinates for the two given points. The coordinates are moved in such a way that the center of the disc which has to be turned is at the coordinates (0,0). Here the variable “) cy
““用于计算垂直偏移.在同一操作中,屏幕坐标(自上而下)被转换为实坐标(自下而上).(” is used to calculate the vertical shift. In the same operation, the screen coordinates (top-down) are converted to real coordinates (bottom-up).)
下一步是检查移动是否足够长.简单的单击(鼠标向下-鼠标向上)或非常短的拖动都不会转动光盘.移动的长度可以计算为两点”(x1,y1)“和”(x2,y2)“之间的矢量长度.最小长度应为20像素.(The next step is to check if the move was long enough. A simple click (mouse down - mouse up) or a very short drag should not turn a disc. The length of the move can be calculated as the length of the vector between the two points “(x1,y1)” and “(x2,y2)”. A minimum length of 20 pixels is expected.)
public bool TurnDisk(float x1, float y1, float x2, float y2, EnigmaPuzzleDlg form)
{
eDisc disc = eDisc.eUpperDisc;
eDirection dir = eDirection.eLeft;
float cy;
// Determine the disk - just look at the hor. middle of the board
if (y1 < MiddleY)
{
disc = eDisc.eUpperDisc;
cy = m_upperCenter.Y;
}
else
{
disc = eDisc.eLowerDisc;
cy = m_lowerCenter.Y;
}
// Move the coordinates so that the y-axle is in the middle of the board
x1 -= MiddleX;
x2 -= MiddleX;
// Because 0/0 is upper left corner, we have to inverse the y-coordinate
y1 = -(y1 - cy);
y2 = -(y2 - cy);
// If the drag is too short (length of the turning vector) - do nothing
// Get the turning vector
float vx = x2 - x1;
float vy = y2 - y1;
if (Math.Sqrt(vx * vx + vy * vy) < 20)
{
return false;
}
// Calc vector product to get the orientation
double orient = x1 * y2 - y1 * x2;
if (orient > 0)
{
dir = eDirection.eLeft;
}
else
{
dir = eDirection.eRight;
}
// Do the rotations
Rotate(disc, dir, true, form);
return true;
}
最后的动作(The last action in) TurnDisk()
是确定转弯的方向.有一个非常简单的方法可以做到这一点.需要的是两个向量”(x1,y1)“和”(x2,y2)“的向量积.如果该值大于零,则必须逆时针旋转光盘,否则顺时针旋转.(is to determine the direction of the turn. There is a very simple way to do that. What is needed is the vector product of the two vectors “(x1,y1)” and “(x2,y2)”. If this value is greater than zero, the disc has to be turned anticlockwise else clockwise.)
兴趣点(Points of Interest)
对我来说,几乎所有事情都很有趣,因为我从未与(For me, almost everything was interesting, since I’ve never worked with) GraphicsPath
,(,) Bitmap
以及与(and the transformation with the) Matrix
类.由于我以前从未做过游戏编程,因此我相信有很多事情可以做得更好或更容易.(class. Since I’ve never done game programming before, I’m sure there are a lot of things that could be done better or easier.)
多种语言(Multiple Languages)
该程序以两种语言实现? -德语和英语.在Visual Studio解决方案中,表单将德语作为默认语言.在运行时,将检查计算机上的区域性设置.如果它与"德语”(CH,DE,AUT)有关,则该程序以德语用户界面启动,如果没有,则以英语用户界面启动.(The program is implemented for two languages?? - German and English. In the Visual Studio solution, the forms have German as the default language. At runtime, the culture settings on the computer will be checked. If it has anything to do with “German” (CH, DE, AUT), then the program starts with a German user interface, if not, then it starts with an English one.)
建立(Setup)
安装程序已使用Inno-Setup编译器创建.安装程序的源文件可以在顶层文件夹中找到((The setup program has been created with Inno-Setup compiler. The source file for the setup program can be found on the toplevel folder ()谜题拼图(EnigmaPuzzle.iss)).().)
历史(History)
- 版本1.0-2011年11月11日(Version 1.0 - 11.11.2011)
- 版本1.1-2011年11月17日-添加了从全屏更改为较大窗口的设置(Version 1.1 - 17.11.2011 - Added setting to change from fullscreen to sizable window)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
C#4.0 C# Vista Windows WinXP GDI Dev application forms game 新闻 翻译