新手魔方 (2.0 版)。 包含 3D 图形和动画的 C# 应用程序。 VS2022 .NET6






4.98/5 (68投票s)
本程序将使用面向初学者的算法来解魔方。它是一个用 C# 编写的 WPF 开源应用程序,用于 VS 2022 和 .NET6,使用 3D 图形和动画。
引言
最新版本 2.0.0 已升级到 VS 2022 和 .NET6。
用于解魔方算法的灵感来自 RubiksPlace.com 网站 《新手魔方解法指南》。以及 Ruwix.com 网站 《魔方解法》。
本文和应用程序可以通过三种不同的方式查看。
- 一个演示程序,帮助新手魔方爱好者理解解法过程。
- 一个关于用 C# 解魔方的源代码示例。
- 一个关于 WPF C# 3D 图形和动画的源代码示例。
您可以在屏幕上玩魔方,也可以打乱您自己的真实魔方,然后将屏幕上的魔方颜色与之匹配。接着,按下“Solve Step”按钮,您将看到一步一步的转动过程,以实现进度。指令同时提供相对转动代码 U、F、R、B、L、D(上、前、右、后、左、下)和颜色代码 Y、R、G、O、B、W(黄、红、绿、橙、蓝、白)。
魔方是逐层从白色面解到黄色面。每一层都分为角块和棱块。主要组别是
- 白色棱块
- 白色角块
- 中间层棱块
- 黄色十字
- 黄色角块位置
- 黄色角块方向
- 黄色棱块
每个组别又细分为若干步骤。每个步骤通常会移动一个块到其正确的位置。
当您在 Visual Studio 环境中调试程序时,程序会生成一个名为 RubiksCubeTrace.txt 的跟踪文件。该文件存储在当前目录中。如果您保持“下载源代码”zip 文件(RubiksCube_src.zip)中的目录结构,当前目录将是“..\RubiksCube\Work”。如果您想要 Visual Studio 的默认行为,请删除 Work 文件夹。该文件将保存在“..\RubiksCube\bin\Debug”。如果您想要一个不同的工作目录,请转到项目属性中的“调试”选项卡并更改为您想要的目录。与版本 1.0.0 相比的变化是,项目属性中的工作目录现在是空白的。
术语
可以合理地假定,阅读本文的读者都熟悉魔方。互联网上有许多关于如何解魔方的文章。这里的术语是为那些想理解代码的人准备的。
魔方由 27 个小块组成。其中 20 个小块是可移动的。6 个是固定的,它们的表面颜色代表魔方的面色。1 个小块是不可见的。20 个可移动小块分为 8 个角块和 12 个棱块。角块有 3 个可见面,棱块有 2 个可见面。魔方有六个面,每个面有 8 个可移动的面。总共有 48 个可移动的面。代码的 3D 图形部分显示并动画化这些小块和块面。代码的解法部分则操作块面的编号。
源代码中的类可以分为处理屏幕上魔方显示和处理谜题解法的类。显示类名称中包含 3D。所有其他类都处理面旋转和解魔方的逻辑。主要的显示类是 Cube3D,主要的解法类是 Cube。
演示程序
要测试演示程序,请创建一个新文件夹,将附带的 RubiksCube.exe 复制到该文件夹中,然后运行程序。
控制按钮
侧面旋转:有 18 个按钮,用于以三种方式(顺时针 90 度、180 度或逆时针 90 度)旋转魔方六个面中的每一个。
顶面:六个颜色按钮,用于旋转整个魔方,使顶面与选定的颜色匹配。
前表面:六个颜色按钮,用于旋转整个魔方,使前表面与选定的颜色匹配。
用户魔方选定颜色:六个颜色按钮,用于为屏幕上的魔方选择颜色,以匹配您的真实魔方。在按下“User Cube”按钮后,这些按钮才可用。请参阅下面的“User Cube”说明。
Solve Step (解法步骤):按下“Solve Step”按钮将使程序执行一个算法,将解法推进一个步骤。
Auto Solve (自动解法):按下“Auto Solve”按钮将使程序通过重复执行解法步骤操作来解魔方,直到魔方处于已解状态。
Save Solution (保存解法):按下“Save Solution”按钮会将魔方的定义和解法步骤保存在一个文本文件中。本节末尾提供了此类文件的示例。程序将在默认目录中创建 SolutionSteps.txt 文件。如果已存在同名文件,则会被覆盖。文件创建后,将调用您计算机中处理 .txt 文件的默认程序。通常是记事本。如果您想永久保存文件,请在记事本中选择“另存为”并将其另存为其他名称。魔方定义由六行组成,每行代表一个面。每行有八个颜色,代表可移动的面。第一行是左上角。其余按顺时针顺序排列。解法是每行一个解法步骤。解法提供两种方式:相对于顶面和前表面的相对代码,以及颜色代码。
Load Solution (加载解法):按下“Load Solution”将加载一个魔方定义文本文件。它可以是“Save Solution”按钮保存的文件。或者,它可以是任何以方括号 [ 和 ] 分隔的六行文本文件。每行包含八个字母或单词。程序读取每个单词的第一个字母。该字母必须是 W、B、R、G、O、Y 中的大写或小写字母。
Reset (重置):将魔方重置为已解状态。
Scramble (打乱):执行 10 次随机旋转来打乱魔方。
Undo (撤销):撤销上一次旋转。
User Cube (用户魔方):按下“User Cube”按钮允许您在屏幕上为魔方涂色,使其与您的真实魔方匹配。确保两个魔方具有相同的方向。从“User Cube Selected Color”六个按钮中选择颜色。使用顶面和前表面按钮旋转屏幕上的魔方。完成后,按下“End Coloring”按钮。程序将检查您输入的魔方是否有效。如果魔方无效,将显示错误消息。纠正错误并再次按下“End Coloring”。如果您不想纠正错误,唯一的选择是“Reset”。
鼠标操作
整体魔方旋转:左键单击魔方图像以外的屏幕区域。按住左键并移动。整个魔方图像将旋转。
单面旋转:单击要旋转的面的任意一个面。如果您单击了一个角块的面,请再次单击同一面上的其他三个角块面之一。棱块面也是如此。该面将旋转,将第一个面带到第二个面的位置。
用户魔方着色。如果您处于用户魔方着色模式,鼠标只能用于为面着色。要旋转魔方,您必须使用“Up Face”和“Front Face”按钮。
2017/07/31 20:08:30 Rubik’s Cube definition Front Up White Red [White Blue White Orange Red Green Green Blue ] Blue Yellow [Orange White Green Orange Red Red Orange Green ] Red Yellow [White Yellow White Red Blue Orange Green White ] Green Yellow [Blue Yellow Green Green Blue Yellow Red White ] Orange Yellow [Yellow Green Yellow Orange Yellow White Yellow Red ] Yellow Orange [Blue Yellow Red Red Orange Blue Orange Blue ] Rubik’s Cube solution steps White Edges. White Red Edge. Move to position. White, Red, F, Color: R White Edges. White Green Edge. Move to position. White, Green, L F, Color: O G White Edges. White Orange Edge. Move to position. White, Orange, D L’ F, Color: Y B’ O White Edges. White Blue Edge. Move to position. White, Blue, F’, Color: B’ White Corners. White Blue Red Corner. Rotate and move to position. White, Red, D’ F D - F’, Color: Y’ R Y - R’ White Corners. White Blue Orange Corner. Move to position. White, Blue, R’ D’ R, Color: O’ Y’ O White Corners. White Green Orange Corner. Rotate and move to position. White, Orange, D’ R’ D’ - R, Color: Y’ G’ Y’ - G White Corners. White Red Green Corner. Rotate and move to position. White, Green, D2 R’ D’ - R, Color: Y2 R’ Y’ - R Mid Layer Edges. Green Orange Edge. Move to position (remove first step). Yellow, Green, R U’ R’ - U’ F’ U - F, Color: O Y’ O’ - Y’ G’ Y - G Mid Layer Edges. Blue Red Edge. Move to position (adjust first step). Yellow, Red, U L’ U - L U F - U’ F’, Color: Y B’ Y - B Y R - Y’ R’ Mid Layer Edges. Blue Orange Edge. Move to position (remove first step). Yellow, Blue, L’ U L - U F U’ - F’, Color: O’ Y O - Y B Y’ - B’ Mid Layer Edges. Red Green Edge. Move to position (adjust first step). Yellow, Red, U2 R U’ - R’ U’ F’ - U F, Color: Y2 G Y’ - G’ Y’ R’ - Y R Yellow Cross. Yellow Green Edge. Move from L shape to cross. Yellow, Green, F U R - U’ R’ F’, Color: G Y O - Y’ O’ G’ Yellow Corners Position. Yellow Red Green Corner. Rotate yellow face to position. Yellow, Red, U2, Color: Y2 Yellow Corners. Yellow Green Orange Corner. Shuffle three yellow corners. Yellow, Blue, L U L’ - U L U2 - L’ U2, Color: O Y O’ - Y O Y2 - O’ Y2 Yellow Corners. Yellow Green Orange Corner. Rotate 3 yellow corners into their place. Yellow, Blue, R’ U’ R - U’ R’ U2 - R U2, Color: R’ Y’ R - Y’ R’ Y2 - R Y2 Yellow Edges. Yellow Blue Edge. Rotate 3 edges to position. Yellow, Orange, R2 U R - U R’ U’ - R’ U’ R’ - U R’, Color: B2 Y B - Y B’ Y’ - B’ Y’ B’ - Y B’ Cube is Solved Total number of steps: 86
解魔方
Cube 类是控制解法过程的主要类。魔方有六个面。每个面有八个可移动的面。总共有 48 个可移动的面。Cube 类有一个包含 48 个整数的整数数组(FaceArray),代表可移动的面。Cube 类以及与 Cube 类相关的其他类会操作这个面数组。有重置数组、加载数组、保存数组、旋转数组、测试数组和解魔方的方法。
面编号为 0 到 47。下图显示了所有面编号。它们具有以下属性。
- 与角块相关的面编号是偶数。
- 与棱块相关的面编号是奇数。
- 面的颜色(0-白色,1-蓝色,2-红色,3-绿色,4-橙色,5-黄色)由面编号 FaceColor = FaceNo / 8 获得。
- 面在魔方侧面的位置是 FacePos = FaceNo % 8。每个侧面上的面位置从左上角开始,按顺时针方向绕面排列。
- 当所有面编号都等于它们在 FaceArray 中的位置时,魔方就解好了。FaceArray[FaceNo] == FaceNo。
重置。将 FaceArray 重置为已解状态。
public void Reset()
获取颜色数组。返回一个表示面颜色的整数数组。
public int[] ColorArray {get}
设置颜色数组。软件会测试颜色数组以验证其是否是有效的魔方。如果测试成功,则将颜色数组转换为面数组。如果测试失败,将引发 ApplicationException。
public int[] ColorArray {set}
魔方状态测试属性。有 8 个属性用于测试魔方的解法级别。
public bool AllBlocksInPlace
public bool AllWhiteEdgesInPlace
public bool AllWhiteCornersInPlace
public bool AllMidLayerEdgesInPlace
public bool YellowEdgesInCrossShape
public bool AllYellowEdgesInPlace
public bool AllYellowCornersInPosition
public bool AllYellowCornersInPlace
旋转数组。按一次旋转或按一组旋转来旋转 FaceArray。
public void RotateArray(int RotationCode)
public void RotateArray(int[] RotationSteps)
解法步骤。要解魔方,必须反复执行下一个解法步骤方法,直到魔方解好为止。
// create cube
Cube FullCube = new Cube();
try
{
// Load the cube with scrambled color array
FullCube.ColorArray = ScrambledArray;
// loop until solved
For(;;)
{
// get next step
SolutionStep SolveStep = FullCube.NextSolutionStep();
// cube is solved
if(SolveStep.StepCode == StepCode.CubeIsSolved) break;
// perform the rotation steps
FullCube.RotateArray(SolveStep.Steps);
}
}
// exception
catch (Exception Ex)
{
// report exception
}
魔方的 3D 视图和相机初始化
下面的初始化代码创建了一个 3D 视口、一个 3D 魔方模型和一个透视相机。我们魔方的 3D 模型由 Cube3D 类表示。Cube3D 类创建 27 个小块,由 Block3D 类表示。每个小块创建 6 个块面,由 BlockFace3D 表示。每个块面又被分成由 MashGeometry3D 表示的三角形。不可见块面为黑色。可见块面有一个灰色边缘和魔方六种颜色中的一种。3D 对象的源代码是本文附带的源代码的一部分。为了在屏幕上显示魔方,我们创建了一个看向魔方的相机。相机位于魔方的上方和右侧。结果是我们能看到前、上、右三个面。
<span id="ArticleContent">
// create Viewport3D and add it to CubeGrid parent
CubeViewPort3D = new Viewport3D()
{
Name = "mainViewport",
ClipToBounds = true
};
CubeGrid.Children.Clear();
CubeGrid.Children.Add(CubeViewPort3D);
// create ModelVisual3D and add it to Viewport3D
ModelVisual3D ModelVisual = new ModelVisual3D();
CubeViewPort3D.Children.Add(ModelVisual);
// create Model3DGroup with white ambiant light and attach it to ModelViual
Model3DGroup ModelGroup = new Model3DGroup();
ModelGroup.Children.Add(new AmbientLight(Colors.White));
ModelVisual.Content = ModelGroup;
// create our rubik's cube and attach it to ViewPort
RubiksCube3D = new Cube3D();
CubeViewPort3D.Children.Add(RubiksCube3D);
// camera position relative to the cube
// camera is looking directly to the cube
double PosZ = Cube3D.CameraDistance * Math.Sin(Cube3D.CameraUpAngle);
double Temp = Cube3D.CameraDistance * Math.Cos(Cube3D.CameraUpAngle);
double PosX = Temp * Math.Sin(Cube3D.CameraRightAngle);
double PosY = Temp * Math.Cos(Cube3D.CameraRightAngle);
// create camera and attach it to ViewPort
CubeViewPort3D.Camera = new PerspectiveCamera(new Point3D(PosX, -PosY, PosZ),
new Vector3D(-PosX, PosY, -PosZ), new Vector3D(0, 0, 1), Cube3D.CameraViewAngle);
// full cube motion transformation group allows the program to
// rotate the whole cube in any direction
Transform3DGroup FullCubeMotion = new Transform3DGroup();
RubiksCube3D.Transform = FullCubeMotion;
FullCubeZRotation = new AxisAngleRotation3D(new Vector3D(0, 0, 1), 0);
FullCubeMotion.Children.Add(new RotateTransform3D(FullCubeZRotation));
FullCubeXRotation = new AxisAngleRotation3D(new Vector3D(1, 0, 0), 0);
FullCubeMotion.Children.Add(new RotateTransform3D(FullCubeXRotation));
FullCubeYRotation = new AxisAngleRotation3D(new Vector3D(0, 1, 0), 0);
FullCubeMotion.Children.Add(new RotateTransform3D(FullCubeYRotation));
</span>
旋转整个魔方
旋转整个魔方非常简单。在初始化过程中,我们创建了 3 个 AxisAngleRotation3D 对象。每个轴一个对象。要旋转魔方,请设置这三个对象的 Angle 属性。角度以度为单位。程序将角度范围保持在 -180 到 +180 度。
// rotate the full cube
FullCubeXRotation.Angle = XAngle;
FullCubeYRotation.Angle = YAngle;
FullCubeZRotation.Angle = ZAngle;
带动画地旋转魔方的一面
旋转魔方的一面是通过将该面上的所有 9 个小块组合在一起并旋转该组来实现的。旋转由 DoubleAnimation 对象完成。旋转是从零度到旋转角度。动画持续时间为 RotateDuration。90 度为 0.25 秒,180 度为 0.5 秒。
// there are 18 rotation codes 3 for each cube face
// RotateFace 0=white, 1=blue, 2=red, 3=green, 4=orange, 5=yellow
int RotateFace = RotateCode / 3;
// create a transform group
RotateTransformGroup = new Transform3DGroup();
// attach the transform group object to all 9 blocks of the face to be rotated
for(int Index = 0; Index < Cube.BlocksPerFace; Index++)
{
RubiksCube3D.CubeFaceBlockArray[RotateFace][Index].Transform = RotateTransformGroup;
}
// create axis rotation for the transform group
AxisAngleRotation3D AxisRot = new AxisAngleRotation3D(Cube3D.RotationAxis[RotateFace], 0);
RotateTransformGroup.Children.Add(new RotateTransform3D(AxisRot));
// start the animation
// note: AnimationArray is an array of 3 DoubleAnnimation objects.
// The three objects are 90Deg 0.25Sec, 180Deg 0.5Sec, -90Deg 0.25Sec
AxisRot.BeginAnimation(AxisAngleRotation3D.AngleProperty, AnimationArray[RotateCode % 3]);
动画完成后,我们清除上面创建的所有对象。
// clear all block children of the side that was rotated
RotateTransformGroup.Children.Clear();
// rotate the current face array of the cube object
RubiksCube3D.FullCube.RotateArray(RotateCode);
// set the color of all block faces of the cube
RubiksCube3D.SetColorOfAllFaces();
// reset the transform fields of each face that was part of the group
for(int Index = 0; Index < Cube.BlocksPerFace; Index++)
RubiksCube3D.CubeFaceBlockArray[RotateCode / 3][Index].Transform = null;
作者发布的其他开源软件
-
PDF 文件写入器 C# 类库
2013 年 4 月“最佳整体文章”竞赛获奖作品
2013 年 4 月“最佳 C# 文章”竞赛获奖作品
PDF 文件写入器是一个 C# 类库,允许 .NET 应用程序创建 PDF 文件。 -
PDF 文件分析器(含 C# 解析类)
PDF 文件分析器旨在读取、解析和显示 PDF 文件的内部结构。 -
使用 C# 压缩/解压缩类处理标准 Zip 文件
本项目将提供使用 Deflate 压缩方法进行文件压缩和解压缩的工具,以及读写标准 Zip 文件。 -
支持隐式 SSL 安全套接字层,并支持 OAuth2 或密码授权的 SMTP 客户端
附带的开源 C# .NET 库是一个 SMTP 客户端,实现了隐式 SSL 和 OAuth2 协议。该库回答了以下问题:如何使用 Gmail 作为服务器发送电子邮件?或者,如何使用支持隐式 SSL 和 465 端口的电子邮件服务器发送电子邮件?
历史
- 2017/08/01 版本 1.0 原始版本。
- 2017/08/30 版本 1.0.1 调试工作目录未保存为项目的一部分
- 2022/02/20 版本 2.0.0 升级到 VS 2022 .Net6。