65.9K
CodeProject 正在变化。 阅读更多。
Home

Windows Phone 7 Calcoolation

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (46投票s)

2011 年 5 月 15 日

CPOL

7分钟阅读

viewsIcon

102011

downloadIcon

1170

适用于 Windows Phone 7 的数学类数独游戏

目录

引言

似乎世界上所有人都知道什么是数独游戏。这是一款很棒且非常流行的游戏。尽管如此,几年前也发明了一些非常有趣的变体。

本文介绍其中一个变体在Windows Phone 7上的实现,为了避免商标问题,我将其命名为Calcoolation,我认为这个名字很符合游戏的本质:就像数独一样,您必须在 N x N 的棋盘中填入 1 到 N 的数字,且每行每列都不能重复。但还有更多:您还必须满足单元格所属区域的算术运算。

这是我关于Windows Phone 7的第一篇文章。希望您喜欢阅读和代码。除了乐趣之外,该应用程序还处理了一些有趣的计算问题。

系统要求

要使用本文提供的 Calcoolation 游戏,如果您已经有了 Visual Studio 2010,那就没问题。如果没有,您可以直接从 Microsoft 下载以下 100% 免费的开发工具。

  • 适用于 Windows Phone 的 Visual Web Developer 2010 Express
  • 拉丁方阵

    Calcoolation,与其他数独类游戏一样,基于 N x N 的网格,每个单元格都必须填入 1 到 N 的数字。此外,这些数字必须以这样的方式填充,即每个数字在其所在的行和列中只能出现一次。

    这种特殊的布局被称为拉丁方阵,由 18 世纪瑞士数学家 莱昂哈德·欧拉 发明。有趣的是,拉丁方阵的概念不仅适用于数字,也适用于任何不同的符号。例如,我们可以有一个基于颜色的拉丁方阵,如下所示。

                      
                      
                      
                      
                      
                      

    看到了吗?给定的颜色在行或列中永远不能出现两次。

    不同拉丁方阵的数量随着 N 的变化而急剧变化。这里有一些例子。

    N拉丁方阵的数量
    11
    22
    312
    4576
    5161280
    6812851200

    笼子概念

    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:初始版本。
    © . All rights reserved.