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();
}
// 打乱一个List
void 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);
}
}
}
}