lab03—unity制作简易数独

        身为数独发烧友的我在接触到了unity之后,产生的第一个念头就是用unity制作一个数独。但是,在跟着网上一大堆教程搭了半天还失败之后,我决定改用纯C#代码的形式搭建我的数独游戏。前置步骤为在unity中新建项目并打开,创建C# script并将其拖动至MainCamera中;接下来就是对C# script的正式编写。

                //游玩视频放在文末,其中涉及到放置数字、撤回、重新生成题目等活动

一,生成9*9棋盘

        为了在屏幕上显示9*9的数独表格,需要一个同样为9*9尺寸的二维数组,与OnGUI函数中循环读取-显示相配合。

        首先,我们需要在整个public class中定义好二维数组。格式如下:

private int[,] sudokuBoard = new int[9, 9];
int count;

        其次,同样在public class中,我们需要定义OnGUI函数,这个函数会在整个项目运行时的每帧执行一次,负责(数独表格的)实时显示。

        //在其中提到的函数都会在后续进行补充

        //num_str这个一维数组仅用于按钮上文本的显示,即数独每个小格子的内容

    void OnGUI()
    {
        GUI.Box(new Rect(50, 25, 750, 750), "");//整个棋盘
        if (GUI.Button(new Rect(60, 270, 100, 30), "Restart")) Init();//如果点了restrat按钮就会重新初始化
        if (!GameOver())
        {//在格子填满前,循环判断每个格子应该显示什么内容
            string[] num_str = {"0","1","2","3","4","5","6","7","8","9" };
            for (int i = 0; i < 9; i++) 
            {
                for (int j = 0; j < 9; j++)
                {
                    if (sudokuBoard[i, j] == 0 && 
                        GUI.Button(new Rect(170+j*50+j/3*10,50+i*50+i/3*10,50,50), ""))
                    {
                        PutChess(i, j);//点击空白区域时调用PutChess函数
                    }
                    else if(sudokuBoard[i, j] !=0)//遍历到了非空白区域,显示数字
                    {
                        //显示的内容
                        GUI.Button(new Rect(170+j*50+j/3*10,50+i*50+i/3*10,50,50), num_str[sudokuBoard[i,j] ]);

                        //如果在撤回状态下(number==0)被点击:
                        if ((number == 0) && (last_i==i)&&(last_j==j))
                        {
                            sudokuBoard[i, j] = 0;
                            if(count>0)
                                count--;
                            number = 1;
                        }
                    }
                }

            }

            for (int i = 0; i < 10; i++)//显示界面下方待填入的数字(0-9)
            {
                if (GUI.Button(new Rect(155 + i * 50, 550, 50, 50), num_str[i]))
                {
                    FixNumber(i);
                }
            }
        }
    }

为了更好地让大家理解,我先放出最终的运行图:

在我的OnGUI函数中,前一个嵌套循环对应着:每一帧都遍历数独表格上所有格子,分别进行显示所有格子和修改空格两件事;后一个循环对应着:若点击下方按钮,则调用FixNumber函数,准备修改数独表格上某些格子的内容。下方十个按钮中,0代表撤回前一步。

二,函数 Init() 

        顾名思义,Init()这个函数的主要功能就是初始化整个数独表格。在这里我放出两版Init()函数,分别是初始化空数独 / 初始化含题目数独的Init()。

        没有任何数字的空数独表格Init():

for(int i=0;i<9;i++)
{
    for(int j=0;j<9;j++)
   {
        sudokuBoard[i, j] = 0;
    }
}
count = 0;

        //理论上来说,初始化自带题目的Init()有更好的做法(每次都随机生成新题目),但我在编写时发现我的做法会导致unity运行卡死,暂时不知道怎么解决。因此,我先手动放入了一个数独的完整解,再将能够推理得到的格子挖空,最终得到一份数独题目:

void Init()//此处完成各个参数和数独题目的初始化
    {
        count = 0;
        int[,] copytable = new int[9, 9]{
            {1,2,3,4,5,6,7,8,9 },
            {8,6,9,7,3,1,5,4,2 },
            {7,4,5,9,2,8,6,1,3 },
            {5,9,8,1,7,2,3,6,4 },
            {6,3,4,8,9,5,1,2,7 },
            {2,7,1,6,4,3,9,5,8 },
            {9,5,7,2,6,4,8,3,1 },
            {4,8,6,3,1,7,2,9,5 },
            {3,1,2,5,8,9,4,7,6 }
        };

        //挖空,使得数独题目有唯一解,且最简。实际上就是用解数独策略反推哪些格子可以挖掉
        //如果能从行+列+宫三种约束条件中推导出一格的内容,则这一格可以挖掉
        //注:仍然可以有更多策略,如用4个同种数字排除一个数字等。

        int row;
        int column;
        int my_count = 0;
        for (int time = 600; time > 0; time--)//这么写是当时找卡死的原因,可以改成while循环
        {
            row = Random.Range(0, 9);
            column = Random.Range(0, 9);//随机生成行列的数字
            for (int g = 0; g < 9; g++)//恢复 geweishu,用于反推该格子能否挖掉
            {
                geweishu[g] = g + 1;
            }
            sum = 0;
            for (int p = 0; p < 9; p++)
            {
                if ((copytable[column, p] > 0) && (p != row))//同行有数字,且不是本人
                {
                    geweishu[copytable[column, p] - 1] = 0;//这个数字不可能被填入这个格子
                }
                if ((copytable[p, row] > 0) && (p != column))//同列有数字,不是本人
                {
                    geweishu[copytable[p, row] - 1] = 0;
                }
                if (copytable[column / 3 * 3 + p % 3, row / 3 * 3 + p / 3] > 0)//同九宫格
                {
                    if ((column / 3 * 3 + p%3 != column) || (row / 3 * 3 + p/3 != row))
                    {
                        geweishu[copytable[column /3*3 + p%3, row /3*3 + p/3] - 1] = 0;
                    }
                }
            }
            //利用邻近格子完全筛除了所有不可能的数字,再看是否可能填别的数字)
            for (int q = 0; q < 9; q++)
            {
                sum += geweishu[q];
            }
            if (copytable[column, row] == sum)//本行中只能填原来这个数字,不能填别的数字
            {
                //这个格子能推理出,直接擦掉
                copytable[column, row] = 0;
                my_count++;
            }
        }
        for (int s = 0; s < 9; s++)
        {
            for (int d = 0; d < 9; d++)
            {
                sudokuBoard[s, d] = copytable[s, d];//将副本覆盖到棋盘上
            }
        }
        count = 81-my_count;//count即为当前数独表格中已有的数字个数
    }

三,函数PutChess

        函数PutChess的功能是将数字放入数独表格。为此,我们需要有10个按键(1-9,以及撤回前一步),以及完成“点击即填入数字”的设计。

        PutChess函数的设计如下:

        //需要在public class的开头定义last_i和last_j

void PutChess(int i, int j)
    {
        sudokuBoard[i, j] = number;//标记

        //记录上一次放置数字的位置,用于撤回
        last_i = i;
        last_j = j;

        //记录本局放了多少数字,看游戏是否结束
        count++;
    }

        10个按键的设计如下:

        //要将这段代码放在OnGUI的if(!GameOver){  }中

        //FixNumber函数会在后续补充定义,功能为记录“即将放入空格的数字”。

for (int i = 0; i < 10; i++)//显示界面下方待填入的数字(0-9)
{
    if (GUI.Button(new Rect(155 + i * 50, 550, 50, 50), num_str[i]))
    {
        FixNumber(i);
    }
}

四,GameOver函数

功能为判定游戏是否结束、以什么方式结束(胜/负)

//仅当数独表格填满、count为81时才有可能胜利,也有可能失败,但都返回true意味着游戏结束

//未填满时都返回false意味着未结束

bool GameOver()//判断游戏结束(不够严谨)
    {
        if(count>=81)//填满棋盘时才判定
        {
            for (int i = 0; i < 9; i++)//判断每行每列
            {
                int sum1 = 0;//统计每行
                int sum2 = 0;//统计每列
                int sum3 = 0;//统计每个九宫格
                for (int j = 0; j < 9; j++)
                {
                    sum1 += sudokuBoard[i, j];
                    sum2 += sudokuBoard[j, i];
                    //此处是统计第i个九宫格里的所有九个数字
                    sum3 += sudokuBoard[(i * 3) % 9 + j % 3, j / 3 + (i / 3) * 3];
                }
                if ((sum1 == 45) || (sum2 == 45) || (sum3==45))
                {
                    continue;
                }
                //发现和不为45的,提示数独失败
                GUI.Box(new Rect(520, 100, 400, 400), "\n\n\n\n\nI'm Sorry\n You has lost.");
                return true;//结束了,但是输了
            }

            //能离开循环说明填对了
            GUI.Box(new Rect(520, 100, 400, 400), "\n\n\n\n\nCongratulations!\n You has won.");
            return true;//结束且胜利
        }
        return false;//没填满棋盘就是没结束
        
    }

五,FixNumber函数

顾名思义,其实就是修改number这个变量,用于每次鼠标点击时点击填入数字

    void FixNumber(int i)
    {
        number = i;
    }

六,Start函数

        Start函数是unity初始化游戏的唯一入口。在此函数中,我们只需将Init函数放入,实现初始化

void Start()
    {
        Init();
    }

改进方向

        至此,一个“预设好答案并随机挖空生成题目”的数独游戏就完成啦!

        b站视频链接:

只是一个还没长开的unity数独游戏罢了_哔哩哔哩bilibili

Unity数独视频

        当然,还可以从以下几个方面进行改进:

        1.用更多方式进行挖空,提升游戏难度;

        2.随机生成答案,而不仅仅是对固定的答案进行随机挖空;

        3.让撤回的范围从“只能撤回前一步”变为“撤回更多步”,或者“擦除固定一格的数据”,提升玩家的游玩体验;

        4.美化界面;

        5.加入点击音效/点击动画等操作的反馈;

        6.加入规则介绍。

  • 11
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值