本文主要是介绍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.加入规则介绍。
这篇关于lab03—unity制作简易数独的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!