unity数独游戏

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);
            }
        }
        
        
       
        
       
       
    }
    
}

  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值