Windows Phone 7 Calcoolation






4.96/5 (46投票s)
适用于 Windows Phone 7 的数学类数独游戏
目录
引言
似乎世界上所有人都知道什么是数独游戏。这是一款很棒且非常流行的游戏。尽管如此,几年前也发明了一些非常有趣的变体。
本文介绍其中一个变体在Windows Phone 7上的实现,为了避免商标问题,我将其命名为Calcoolation,我认为这个名字很符合游戏的本质:就像数独一样,您必须在 N x N 的棋盘中填入 1 到 N 的数字,且每行每列都不能重复。但还有更多:您还必须满足单元格所属区域的算术运算。
这是我关于Windows Phone 7的第一篇文章。希望您喜欢阅读和代码。除了乐趣之外,该应用程序还处理了一些有趣的计算问题。
系统要求
要使用本文提供的 Calcoolation 游戏,如果您已经有了 Visual Studio 2010,那就没问题。如果没有,您可以直接从 Microsoft 下载以下 100% 免费的开发工具。
拉丁方阵
Calcoolation,与其他数独类游戏一样,基于 N x N 的网格,每个单元格都必须填入 1 到 N 的数字。此外,这些数字必须以这样的方式填充,即每个数字在其所在的行和列中只能出现一次。
这种特殊的布局被称为拉丁方阵,由 18 世纪瑞士数学家 莱昂哈德·欧拉 发明。有趣的是,拉丁方阵的概念不仅适用于数字,也适用于任何不同的符号。例如,我们可以有一个基于颜色的拉丁方阵,如下所示。
看到了吗?给定的颜色在行或列中永远不能出现两次。
不同拉丁方阵的数量随着 N 的变化而急剧变化。这里有一些例子。
N | 拉丁方阵的数量 |
1 | 1 |
2 | 2 |
3 | 12 |
4 | 576 |
5 | 161280 |
6 | 812851200 |
笼子概念
2004 年,数学老师宫本哲也发明了一种新的数独变体,其中单元格被分组为粗边框区域,称为“笼子”。
每个笼子有三个数据:
- 一组 n 个连续单元格
- 目标数字
- 基本运算(加、减、乘、除)
在宫本的游戏中,每个笼子都必须以这样一种方式填充,即对笼中的数字应用笼子运算,正好得到目标数字。
需要注意的是,Calcoolation 是一个“无提示”游戏,也就是说,在传统的数独游戏中,有些单元格已经预先填好,而这个游戏的所有单元格最初都是空的,因此是“无提示”的。
代码
代码分为两个层:WP7Calcoolation.Core 和 WP7Calcoolation。在 WP7Calcoolation 层,我们有 Windows Phone 的 Silverlight 表示逻辑。界面非常简单,我认为没有必要添加复杂的动画,这在传统的 Silverlight 项目中我会尝试的。

Core 层完成了大部分繁重的工作。它由代表游戏中三个主要实体的类组成:Board(棋盘)、Cage(笼子)和 Cell(单元格)。游戏中只能有一个 Board(这就是我决定使用单例模式的原因)。默认的 Board 具有预定义的 4x4 尺寸(用户稍后可以更改)。棋盘上的每个位置由一个单元格持有(即,单元格数量 = 尺寸²)。在棋盘内部,单元格也排列在称为“笼子”的块中(很像传统的谜题)。
我认为值得提及的代码部分是与随机数选取、随机笼子生成和游戏完整性测试相关的部分。
随机数选取
最具挑战性的部分是找到一种正确的策略来选取随机数,避免在列和行中出现重复。我决定使用裸对/裸三元组策略,这是我从一些致力于数独解题的网站上借鉴来的。稍后我将在本文中讨论裸对。
有关随机数选取,请参阅 GenerateNumbers()
方法。
private void GenerateNumbers()
{
ResetBoard();
Random rnd = new Random();
string number = "0";
int minSize = size;
int maxSize = 0;
bool someSolved = true;
while (someSolved)
{
someSolved = false;
//Search for naked pairs in rows
if (!someSolved)
{
//code removed for better visibility
}
//Search for naked pairs in columns
if (!someSolved)
{
//code removed for better visibility
}
//Search for naked triplets in rows
for (int row = 0; row < size; row++)
{
//code removed for better visibility
}
//Search for cells with a unique solution possible
for (int row = 0; row < size; row++)
{
//code removed for better visibility
}
//Random selection
if (!someSolved)
{
//code removed for better visibility
}
}
}
请注意,根据上面的代码,裸对首先被解决。然后是裸三元组,然后是只有唯一解的单元格,最后是随机选择。这样做是为了避免回溯。
结果是,我们现在有了一个有效的棋盘,可以使用了。

随机笼子生成
下一个重要步骤是随机创建笼子,这是 GenerateCages
方法。
private void GenerateCages()
{
cages = new List<Cage>();
bool success = false;
int orientation = 0;
int c2 = 0;
int r2 = 0;
Random rnd = new Random();
for (int r = 0; r < size; r++)
{
for (int c = 0; c < size; c++)
{
if (matrix[c, r].Cage == null)
{
success = false;
while (!success)
{
orientation = rnd.Next(1, 5);
switch (orientation)
{
case 1: // W
c2 = c - 1;
r2 = r;
break;
case 2: // E
c2 = c + 1;
r2 = r;
break;
case 3: // N
c2 = c;
r2 = r - 1;
break;
case 4: // S
c2 = c;
r2 = r + 1;
break;
}
if (c2 >= 0 && c2 < size && r2 >= 0 && r2 < size)
{
Cage cage = matrix[c2, r2].Cage;
if (cage == null)
{
cage = new Cage();
cage.CellList.Add(matrix[c2, r2]);
matrix[c2, r2].Cage = cage;
}
else
{
if (cage.CellList.Count > 3 && (c != size - 1 || r != size - 1))
{
continue;
}
}
cage.CellList.Add(matrix[c, r]);
matrix[c, r].Cage = cage;
cages.Add(cage);
success = true;
}
}
}
}
}
该函数从棋盘上的 {0,0} 位置开始,向右和向下移动,以随机方向放置两个单元格的块,并测试是否存在与现有笼子的冲突。在这种情况下,笼子会被合并;否则,会创建一个新笼子。
之后,PickOperation()
方法选择一个可能的随机运算(从 +、-、x 和 ÷ 中选取),使用笼子内的数字。
public void PickOperation(Cage cage)
{
bool success = false;
while (!success)
{
if (currentOperation == 5)
currentOperation = 1;
switch (currentOperation)
{
case 1:
cage.Operation = Operations.Plus;
int sum = 0;
foreach (Cell cell in cage.CellList)
{
sum += Convert.ToInt32(cell.CellValue);
}
cage.Result = sum;
success = true;
break;
case 2:
cage.Operation = Operations.Minus;
if (cage.CellList.Count == 2)
{
int sub = Convert.ToInt32(cage.CellList[0].CellValue) -
Convert.ToInt32(cage.CellList[1].CellValue);
if (sub > 0)
{
cage.Result = sub;
success = true;
}
else
{
sub = Convert.ToInt32(cage.CellList[1].CellValue) -
Convert.ToInt32(cage.CellList[0].CellValue);
if (sub > 0)
{
cage.Result = sub;
success = true;
}
}
}
break;
case 3:
cage.Operation = Operations.Multiply;
int mult = 1;
foreach (Cell cell in cage.CellList)
{
mult *= Convert.ToInt32(cell.CellValue);
}
cage.Result = mult;
success = true;
break;
case 4:
cage.Operation = Operations.Divide;
if (cage.CellList.Count == 2)
{
int quo = Convert.ToInt32(cage.CellList[0].CellValue) /
Convert.ToInt32(cage.CellList[1].CellValue);
int rem = Convert.ToInt32(cage.CellList[0].CellValue) - quo *
Convert.ToInt32(cage.CellList[1].CellValue);
if (rem == 0)
{
cage.Result = quo;
success = true;
}
else
{
quo = Convert.ToInt32(cage.CellList[1].CellValue) /
Convert.ToInt32(cage.CellList[0].CellValue);
rem = Convert.ToInt32(cage.CellList[1].CellValue) - quo *
Convert.ToInt32(cage.CellList[0].CellValue);
if (rem == 0)
{
cage.Result = quo;
success = true;
}
}
}
break;
}
currentOperation++;
}
}
结果是,我们现在拥有了所有随机笼子,以及它们各自的随机运算和随机目标数字。

裸对
在为单元格选取随机数之前,您应该始终查找“裸对”。裸对意味着在某个行或列中,有两个单元格有两个可能的数值。下图中,我们可以注意到底部左侧笼子中的裸对,只有两个可能值 [1,3]。

发现裸对的原因很简单:由于这两个单元格只能容纳这两个数字,因此该行中的任何其他单元格都不会包含“1”或“3”。因此,我们可以将它们从可能的数字中移除。

显示正确答案
您可以通过点击“结束游戏”按钮查看正确答案。这样做,您将放弃游戏,让应用程序(希望如此)动态生成一个更简单的问题。

重要提示: 尽管每个新的游戏棋盘都预设了结果,但玩家也可能得到另一个解决方案。但这也没关系:如果您找到了一个与原始解决方案不同的解决方案,您就赢了。棋盘上的每一次移动都会触发一个函数,检查是否产生了令人满意的解决方案。
public bool TestResult()
{
bool success = false;
if (cellList.Count > 0)
{
switch (operation)
{
case Operations.Plus:
int sum = 0;
foreach (Cell cell in cellList)
{
sum += Convert.ToInt32(cell.UserValue);
}
if (sum == result)
success = true;
break;
case Operations.Minus:
int sub = 0;
sub = Convert.ToInt32(cellList[0].UserValue) - Convert.ToInt32(cellList[1].UserValue);
if (sub == result)
success = true;
else
{
sub = Convert.ToInt32(cellList[1].UserValue) - Convert.ToInt32(cellList[0].UserValue);
if (sub == result)
success = true;
}
break;
case Operations.Multiply:
int mult = 1;
foreach (Cell cell in cellList)
{
mult *= Convert.ToInt32(cell.UserValue);
}
if (mult == result)
success = true;
break;
case Operations.Divide:
int div = 0;
int rem = 0;
div = Convert.ToInt32(cellList[0].UserValue) / Convert.ToInt32(cellList[1].UserValue);
rem = Convert.ToInt32(cellList[0].UserValue) - (div * Convert.ToInt32(cellList[1].UserValue));
if (div == result && rem == 0)
success = true;
else
{
div = Convert.ToInt32(cellList[1].UserValue) / Convert.ToInt32(cellList[0].UserValue);
rem = Convert.ToInt32(cellList[1].UserValue) - (div * Convert.ToInt32(cellList[0].UserValue));
if (div == result && rem == 0)
success = true;
}
break;
}
}
return success;
}
游戏界面
游戏 UI 基于 Windows Phone 7 的 Silverlight。幸运的是,有一个很棒的工具叫做Microsoft Expression Blend 4,它被创建用来方便地开发使用 WPF、Silverlight 和 Silverlight for Windows Phone 7 构建的应用程序的界面。事实上,我没有使用 Expression Blend 来设计 Calcoolation 的游戏 UI(也许是因为我仍然是一个喜欢直接在 XAML 或代码后台构建用户界面的顽固者),但我保证在接下来的文章中会尝试使用 Expression Blend 4。
回到 Expression Blend 4,这是该强大工具打开的游戏 UI(点击放大)。
上图显示,游戏大部分是由原生的 Silverlight 控件组成的,例如游戏棋盘的网格,用于选取数字的按钮,以及用于“新游戏”、“结束游戏”和“退出游戏”用户操作的按钮。
唯一值得注意的例外是 CellBox
用户控件,它用于填充 4 x 4 的游戏网格。该控件负责控制每个单元格内的用户操作(例如选择/清除数字),显示笼子运算和目标数字,以及显示正确数字。

最终思考
正如您所见,本文并未涵盖 Windows Phone 7 的 Silverlight 开发的复杂方面。我认为任何 Silverlight 开发者都能轻松理解这里展示的简单 UI。我认为简单是伟大的,当目标得以实现时。我还认为算法的陷阱使文章更有趣。
如果您有任何投诉、建议或意见,请在页面底部留言。反馈很重要,我很想知道您的想法。
历史
- 2011-05-15:初始版本。