using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;public class MainMenuPanel : MonoBehaviour
{public Button btnPlay; // 开始按钮public Slider sldDifficulty; // 难度滑动条private void Awake(){// 监听滑动条滑动事件sldDifficulty.onValueChanged.AddListener(OnSliderChange);// 开始按钮点击事件btnPlay.onClick.AddListener(OnPlayGame);}void Start(){// 启动的时候保存难度值SudokuGameManager.Instance.difficulty = (int)sldDifficulty.value;}public void OnSliderChange(float value){// 滑动条变化的时候保存难度值SudokuGameManager.Instance.difficulty = (int)sldDifficulty.value;}public void OnPlayGame(){// 调用游戏管理器中的方法来开始一局新的数独游戏SudokuGameManager.Instance.OnPlayNewGame();}
}
主要功能在该脚本中
在创建正确答案,扣除里面的数字并,把两个比较
重开游戏
算法用到了递归
using System.Collections.Generic;
using UnityEngine;public class SudokuBoard : MonoBehaviour
{// 正确答案int[,] gridNumber = new int[SudokuGameManager.girdLength, SudokuGameManager.girdLength];// 谜题int[,] puzzleNumber = new int[SudokuGameManager.girdLength, SudokuGameManager.girdLength];// 谜题备份,用于重开本局游戏int[,] puzzleBak = new int[SudokuGameManager.girdLength, SudokuGameManager.girdLength];public SudokuGrid grid;// 网格体public void Init(){// 创建一个有解的数独CreateGrid();// 根据已经创建的答案 来创建 谜题CreatePuzzle();// 根据谜题来初始化按钮InitButtons();}// 清理所有资源public void Clear(){// 清理谜题和答案数据gridNumber = new int[SudokuGameManager.girdLength, SudokuGameManager.girdLength];puzzleNumber = new int[SudokuGameManager.girdLength, SudokuGameManager.girdLength];puzzleBak = new int[SudokuGameManager.girdLength, SudokuGameManager.girdLength];}// 销毁某个物体下面的所有子物体void ClearChildren(Transform trans){for (int i = 0; i < trans.childCount; i++){Destroy(trans.GetChild(i).gameObject);}}// 某列是否包含某个数据bool ColumnContainsValue(int col, int value){for (int i = 0; i < SudokuGameManager.girdLength; i++){if (gridNumber[i, col] == value){return true;}}return false;}// 某一行是否包含某个数字bool RowContainsValue(int row, int value){for (int i = 0; i < SudokuGameManager.girdLength; i++){if (gridNumber[row, i] == value){return true;}}return false;}// 某一个子网格是否包含某个数字bool SquareContainsValue(int row, int col, int value){// 遍历子单元格(3X3)for (int i = 0; i < SudokuGameManager.subGirdLength; i++){for (int j = 0; j < SudokuGameManager.subGirdLength; j++){// 通过计算坐标 确定是属于哪个子网格if (gridNumber[(row / SudokuGameManager.subGirdLength) * SudokuGameManager.cellLength + i, (col / SudokuGameManager.subGirdLength) * SudokuGameManager.cellLength + j] == value){return true;}}}return false;}// 检查某个数是否已经存在于 横 列以及某个3X3的网格里bool CheckAll(int row, int col, int value){if (ColumnContainsValue(col, value)) // 列是否已经包含该数字{return false;}if (RowContainsValue(row, value)) // 行是否已经包含该数字{return false;}if (SquareContainsValue(row, col, value)) // 当前子九宫是否包含该数字{return false;}return true;}// 网格数字是否有效bool IsValid(){for (int i = 0; i < SudokuGameManager.girdLength; i++){for (int j = 0; j < SudokuGameManager.girdLength; j++){if (gridNumber[i, j] == 0){return false;}}}return true;}// 创建一个有效的数独网格void CreateGrid(){List<int> rowList = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9 };List<int> colList = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9 };// 先在00位置随机一个数据int value = rowList[Random.Range(0, rowList.Count)];gridNumber[0, 0] = value;rowList.Remove(value);colList.Remove(value);// 将其他8个数字随机到第一行中for (int i = 1; i < SudokuGameManager.girdLength; i++){value = rowList[Random.Range(0, rowList.Count)];gridNumber[i, 0] = value;rowList.Remove(value);}// 将其他几个数字随机到第一列中for (int i = 1; i < SudokuGameManager.girdLength; i++){value = colList[Random.Range(0, colList.Count)];// 需要判断是否会和第一个子网格有重复if (i < 3){while (SquareContainsValue(0, 0, value)){value = colList[Random.Range(0, colList.Count)]; // reroll}}gridNumber[0, i] = value;colList.Remove(value);}// 再随机对最后一个子网格添加三个合法的数字for (int i = 6; i < 9; i++){value = Random.Range(1, 10);while (SquareContainsValue(0, 8, value) || SquareContainsValue(8, 0, value) ||SquareContainsValue(8, 8, value)){value = Random.Range(1, 10);}gridNumber[i, i] = value;}// 先随机生成一个数独的底子,然后对它求解,解出的答案就是完整数独SolveSudoku();}// 对数独求解bool SolveSudoku(){int row = 0;int col = 0;// 如果已经生成完毕 就返回结果if (IsValid()){return true;}// 找到还没有生成数字的位置for (int i = 0; i < SudokuGameManager.girdLength; i++){for (int j = 0; j < SudokuGameManager.girdLength; j++){if (gridNumber[i, j] == 0){row = i;col = j;break;}}}// 循环 找到合适的数字,满足所有的规则for (int i = 1; i <= SudokuGameManager.girdLength; i++){if (CheckAll(row, col, i)){gridNumber[row, col] = i;// 递归找解 这里很重要,因为是对自身的递归,如果随机的数字正好全部都满足就结束了if (SolveSudoku()){return true;}else //如果某次递归找不到解 就会将该位置之 {gridNumber[row, col] = 0;}}}return false;}void CreatePuzzle(){// 根据事先完成的答案 创建谜题// 先将答案复制一份出来System.Array.Copy(gridNumber, puzzleNumber, gridNumber.Length);//移除数字,制造难度for (int i = 0; i < SudokuGameManager.Instance.difficulty; i++){int row = Random.Range(0, SudokuGameManager.girdLength);int col = Random.Range(0, SudokuGameManager.girdLength);// 循环随机,直到随到一个没有处理过的位置while (puzzleNumber[row, col] == 0){row = Random.Range(0, SudokuGameManager.girdLength);col = Random.Range(0, SudokuGameManager.girdLength);}puzzleNumber[row, col] = 0;}// 确保最少要出现8个不同的数字 才能保证唯一解List<int> onBoard = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9 };RandomizeList(onBoard);for (int i = 0; i < SudokuGameManager.girdLength; i++){for (int j = 0; j < SudokuGameManager.girdLength; j++){for (int k = 0; k < onBoard.Count - 1; k++){if (onBoard[k] == puzzleNumber[i, j]){onBoard.RemoveAt(k);}}}}// 如果剩余的数量大于1 说明没有8个不同的数字 那么就还原几个数字回来while (onBoard.Count - 1 > 1){int row = Random.Range(0, SudokuGameManager.girdLength);int col = Random.Range(0, SudokuGameManager.girdLength);if (gridNumber[row, col] == onBoard[0]){puzzleNumber[row, col] = gridNumber[row, col];onBoard.RemoveAt(0);}}// 将谜题备份用于重开本局游戏System.Array.Copy(puzzleNumber, puzzleBak, gridNumber.Length);}// 初始化可填写的按钮布局void InitButtons(){for (int i = 0; i < SudokuGameManager.girdLength; i++){for (int j = 0; j < SudokuGameManager.girdLength; j++){var cell = grid.GetCellByPosition(i,j);if (cell != null){cell.InitValues(puzzleNumber[i,j]);}}}}//重开本局游戏public void RestartGame(){System.Array.Copy(puzzleBak, puzzleNumber, gridNumber.Length);InitButtons();}// 打乱一个Listvoid RandomizeList(List<int> l){for (var i = 0; i < l.Count - 1; i++){// 随机交换两个位置int rand = Random.Range(i, l.Count);(l[i], l[rand]) = (l[rand], l[i]);}}// 将玩家输入更新到谜题中public void UpdatePuzzle(int row, int col, int value){puzzleNumber[row, col] = value;}/// <summary>/// 判定游戏是否完成/// </summary>/// <returns></returns>public bool CheckComplete(){// 检查填入的内容和谜底内容是否吻合for (int i = 0; i < SudokuGameManager.girdLength; i++){for (int j = 0; j < SudokuGameManager.girdLength; j++){if (puzzleNumber[i, j] != gridNumber[i, j]){return false;}}}return true;}}
using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.UI;public class SudokuCell : MonoBehaviour
{public Vector2Int coordinate; // 在大网格里坐标public SudokuSubGrid subGrid;int value = 0; // 当前格子的值public TextMeshProUGUI txtNumber;// 数字控件public Button btnNum;// 按钮控件void Awake(){btnNum = GetComponent<Button>();txtNumber = GetComponentInChildren<TextMeshProUGUI>();btnNum.onClick.AddListener(ButtonClicked);}// 给网格设置数字public void InitValues(int value){// 初始化的时候,不为0表示该位置是系统提供的数字,否则就是玩家应该输入的数字if (value != 0){txtNumber.text = value.ToString();txtNumber.color = new Color32(119, 110, 101, 255);btnNum.enabled = false;}else{btnNum.enabled = true;txtNumber.text = " ";txtNumber.color = new Color32(0, 102, 187, 255);}}// 设置行列坐标public void SetCoordinate(int row, int col){coordinate = new Vector2Int(row, col);name = row.ToString() + col.ToString();}// 设置其归属的子网格public void SetSubGridParent(SudokuSubGrid sub){subGrid = sub;}/// <summary>/// 按钮事件/// 将当前的数字输入和指定的Cell进行绑定/// </summary>public void ButtonClicked(){SudokuGameManager.Instance.ActivateInputButton(this);}/// <summary>/// 更新单元格内的数字/// </summary>/// <param name="newValue"></param>public void UpdateValue(int newValue){value = newValue;if (value != 0){txtNumber.text = value.ToString();}else{txtNumber.text = "";}}
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.UI;/// <summary>
/// 数独游戏的管理器
/// </summary>
public class SudokuGameManager : MonoBehaviour
{public static int girdLength = 9;// 网格的宽高public static int subGirdLength = 3;// 子网格的宽高// 子网格中 Cell的宽高public static int cellLength = SudokuGameManager.girdLength / SudokuGameManager.subGirdLength;public SudokuCell SudokuCell_Prefab; // 单元格的预制体// get; private set; 的写法是一种语法糖,表示这个值别的类可以读取和使用,但只有自己才能设置它public static SudokuGameManager Instance { get; private set; }public MainMenuPanel mainMenu;// 主界面相关逻辑public SudokuPlayPanel sudokuPlay; // 数独的游戏界面// 按照9X9的格子来算,一共是81个有效数字,如果已知的数字越多,那么未知的数字就越好推导// 所以难度的表达方式就是,设定一个数字,在数独创建完成之后,隐藏掉这些数字来增加难度public int difficulty = 20; // 默认的难度值public Image ipButtonsPanel; // 游戏输入按钮public List<Button>btnNums = new List<Button>(); // 数字输入// 记录上一次点击的格子 以便数字进行输入SudokuCell lastCell;private void Awake(){//单例模式Instance = this;for (int i = 0; i < btnNums.Count; i++){// 需要将i的地址传递出来 int index = i;// 为当前遍历到的按钮添加点击监听器// 当按钮被点击时,调用OnNumBtnClicked方法,并将index(即按钮的索引)作为参数传递btnNums[i].onClick.AddListener(delegate(){ OnNumBtnClicked(index);});}}private void Start(){// 程序启动的时候,默认显示开始界面OnBackToMenu();}// 返回到开始菜单public void OnBackToMenu(){// 程序启动的时候,默认显示开始界面mainMenu.gameObject.SetActive(true);sudokuPlay.gameObject.SetActive(false);// 执行游戏清理sudokuPlay.Clear();}// 开始游戏public void OnPlayNewGame(){// 隐藏开始界面 显示游戏界面mainMenu.gameObject.SetActive(false);sudokuPlay.gameObject.SetActive(true);// 隐藏数字输入面板ipButtonsPanel.gameObject.SetActive(false);// 执行游戏初始化sudokuPlay.Init();}/// <summary>/// 将当前的数字输入和指定的Cell进行绑定/// </summary>/// <param name="cell"></param>public void ActivateInputButton(SudokuCell cell){ipButtonsPanel.gameObject.SetActive(true);lastCell = cell;}/// <summary>/// 点击了某个数字按钮/// </summary>/// <param name="num"></param>public void OnNumBtnClicked(int num){// 更新上一次选中的格子的数值为当前被点击的数字按钮的值lastCell.UpdateValue(num);// 在数独游戏的逻辑板上更新相应的格子数值,使用lastCell的坐标和被点击的数字sudokuPlay.board.UpdatePuzzle(lastCell.coordinate.x, lastCell.coordinate.y, num);// 隐藏数字输入面板ipButtonsPanel.gameObject.SetActive(false);}}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 数独的大网格
/// </summary>
public class SudokuGrid : MonoBehaviour
{// 所有的子网格 需要处理成二维数组public SudokuSubGrid[,] subGrids { get; private set; }// 子网格public SudokuCell[] cells;// 所有的单元格void Awake(){// 获取所有子网格体var grid = GetComponentsInChildren<SudokuSubGrid>();// 建立子网格的二维数组subGrids = new SudokuSubGrid[SudokuGameManager.subGirdLength, SudokuGameManager.subGirdLength];// 通过循环将二维数组分配到指定位置int index = 0;for (int i = 0; i < SudokuGameManager.subGirdLength; i++){for (int j = 0; j < SudokuGameManager.subGirdLength; j++){subGrids[i, j] = grid[index++];subGrids[i, j].SetCoordinate(i,j);// 设置坐标subGrids[i, j].InitCells();// 初始化网格}}cells = GetComponentsInChildren<SudokuCell>();}/// <summary>/// 根据坐标获取Cell/// </summary>/// <param name="row"></param>/// <param name="col"></param>/// <returns></returns>public SudokuCell GetCellByPosition(int row,int col){foreach (var cell in cells){if (cell.coordinate.x == row && cell.coordinate.y == col){return cell;}}return null;}}
using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.PlayerLoop;
using UnityEngine.Serialization;
using UnityEngine.UI;public class SudokuPlayPanel : MonoBehaviour
{public Button btnBackToMenu;// 返回主界面按钮public Button btnBackToMenuInWinPanel;// 获胜面板上的返回主界面按钮public Button btnNewLevel;// 新建一个关卡public Button btnReplay; // 重新开始本次关卡public Button btnComplete; // 完成关卡public TextMeshProUGUI txtWrongTips; // 结果错误提示public Image imgWinPanel; // 获胜界面public TextMeshProUGUI txtTimer;// 计时器float levelStartTime = 0f; // 关卡开始的时间,用于计时器的计算public SudokuBoard board;// 游戏核心逻辑private void Awake(){// 返回主菜单btnBackToMenu.onClick.AddListener(OnBtnBackToMenuClicked);btnBackToMenuInWinPanel.onClick.AddListener(OnBtnBackToMenuClicked);// 新建关卡btnNewLevel.onClick.AddListener(OnBtnNewClicked);// 重新开始本关卡btnReplay.onClick.AddListener(OnbtnReplayClicked);// 完成关卡btnComplete.onClick.AddListener(OnbtnCompleteClicked);}private void Update(){// 计时器CountTimer();}/// <summary>/// 初始化游戏/// </summary>public void Init(){// 隐藏错误提示txtWrongTips.gameObject.SetActive(false);// 隐藏获胜面板imgWinPanel.gameObject.SetActive(false);// 记录当前的时间戳levelStartTime = Time.realtimeSinceStartup;// 核心逻辑初始化board.Init();}public void Clear(){levelStartTime = 0;// 清除计时器开始时间// 核心逻辑清理board.Clear();}/// <summary>/// 计时器逻辑/// </summary>void CountTimer(){float t = Time.realtimeSinceStartup - levelStartTime;int seconds = (int)(t % 60);t /= 60;int minutes = (int)(t % 60);txtTimer.text = string.Format("{0}:{1}", minutes.ToString("00"), seconds.ToString("00"));}/// <summary>/// 返回主菜单/// </summary>public void OnBtnBackToMenuClicked(){// 清理游戏Clear();// 返回主菜单SudokuGameManager.Instance.OnBackToMenu();}/// <summary>/// 开始新关卡/// </summary>public void OnBtnNewClicked(){// 先清理再初始化Clear();Init();}/// <summary>/// 重玩本关卡/// </summary>public void OnbtnReplayClicked(){// 调用核心逻辑的重玩本局board.RestartGame();}/// <summary>/// 完成游戏/// </summary>public void OnbtnCompleteClicked(){// 检查是否完成游戏if (board.CheckComplete()){imgWinPanel.gameObject.SetActive(true);}else{txtWrongTips.gameObject.SetActive(true);// 3秒后 错误提示消失StartCoroutine(HideWrongText());}}IEnumerator HideWrongText(){yield return new WaitForSeconds(3.0f);txtWrongTips.gameObject.SetActive(false);}}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;/// <summary>
/// 大网格下的小网格
/// </summary>
public class SudokuSubGrid : MonoBehaviour
{public Vector2Int coordinate; //子网格所属的坐标public SudokuCell[,] cells { get; private set; }// 创建所属的单元格private void Awake(){cells = new SudokuCell[SudokuGameManager.cellLength, SudokuGameManager.cellLength];}// 设置行列坐标public void SetCoordinate(int row, int col){coordinate = new Vector2Int(row, col);}// 初始化网格public void InitCells(){for (int i = 0; i < SudokuGameManager.cellLength; i++){for (int j = 0; j < SudokuGameManager.cellLength; j++){cells[i,j] = Instantiate(SudokuGameManager.Instance.SudokuCell_Prefab,transform);cells[i,j].SetCoordinate(coordinate.x * SudokuGameManager.cellLength + i,coordinate.y * SudokuGameManager.cellLength +j);cells[i,j].SetSubGridParent(this);cells[i,j].InitValues(1);}}}}