各位朋友大家好,這次要來分享在新公司的一些遊戲作品,數獨遊戲。是某朋友敲碗要小弟打這篇小弟才打的,本來有點懶得打XD
上一次小弟做遊戲的時候已經是大四(2008年)的時候了(凱特衝浪去遊戲專案請按此),壓根沒有想到過了13年,現在還有機會碰遊戲相關的專案。因為現在換到遊戲公司工作,才得以碰到做遊戲這塊,而且其實遊戲產業也是小弟夢寐以求想做的工作,所以做得算是開心~只是薪水....(´_ゝ`)
數獨小遊戲下載點:
https://drive.google.com/file/d/1zVGfm60gll8qRUM4RDKeMhoW8QwQHLbo
數獨小遊戲Unity原始檔:
https://github.com/hcyang1227/Proj/tree/main/001_Sudoku
這次先來分享遊戲畫面跟遊戲流程。畫面影片如上,流程很簡單,先用亂數產生一個盤面,再從盤面挖洞將滿滿的格子當中挖掉一部分的數字,剩下的空格就是玩家要填空的部分了。小弟也做了自動填空跟目測檢查有無兩種以上解答的功能。
因為這個專案當初公司只給了4天就要寫出來,小弟對Unity跟C#(Unity預設使用的程式語言)又不是很熟的狀態,所以寫得有點吃力,就算參考網路上的寫法,也寫不太出來,可說是差點就趕不出來的狀態,看著同事輕鬆解決這個專案,只能遠目(不過他幫了我很多忙,感恩同事,讚歎同事)。
在主程式演算法方面,下面有參考的C#程式碼供大家參考,應該是沒什麼bug才對,頂多UI的部分寫得不是很好而已。這邊描述一下如何產生一個亂數產生的數獨盤面。
- 首先先寫GetRandomArray()使程式產生一個1~9的有序陣列,接著用亂數將這個有序列逐一交換數字順序好幾次
- 分別產生1~9的有序陣列並填入左上、中間、右下的九宮格內
- 執行SolveSudoku(),先將盤面上的數字存進sudoku陣列,接著找出盤面上數值為零(未填數字)的地方並存入陣列n[]
- 接著執行forcefillsdk(1),用遞迴法搜尋可能解答(若此格isValid判定答案正確則執行下一格forcefillsdk(i+1),如果碰壁則自動回上一個動作),只要在solution1[]還是空白的狀況下就用正向搜尋(如果isValid判定答案不對則加1),solution1[]有數值時就用逆向搜尋(如果isValid判定答案不對則減1)
- 等到solution1[]與solution2[]都有答案時(或是搜索次數超過30000次時),程式就會自動終止並秀出最終結果
程式碼截圖如上、擷取如下,寫得也不是很好,僅供參考就好XD
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class Sudoku : MonoBehaviour { public GameObject uiText; public GameObject InputFld; public GameObject BtnDigHole; Color BtnColor = new Color(149, 169, 245, 255); int solveStep = 0; int[,] sudoku = new int[9, 9]; int[,] solution1 = new int[9, 9]; int[,] solution2 = new int[9, 9]; int[,] n = new int[81, 2]; int nlen = 0; static int[,] sudokuSample = new int[9, 9] { {0,0,0,7,2,8,0,0,0}, {0,9,0,0,5,1,6,0,0}, {0,0,0,0,6,0,0,8,2}, {3,0,0,8,0,2,7,0,4}, {1,7,4,0,3,0,0,2,0}, {2,8,0,5,0,0,0,3,0}, {0,1,0,3,0,0,2,0,0}, {0,0,7,0,4,6,0,0,5}, {0,0,6,1,0,0,0,4,9}}; public void SudokuSolution1() { for(int i=0;i().color = BtnColor; } } uiText.GetComponent().text = "答案1盤面"; Show(); } public void SudokuSolution2() { for(int i=0;i().color = BtnColor; } } uiText.GetComponent().text = "答案2盤面"; Show(); } public void SudokuSample() { BtnDigHole.SetActive(false); for(int i=0;i().color = Color.gray; } else { string str="Btn"+(i+1)+"-"+(j+1); GameObject Btn = GameObject.Find(str); Btn.GetComponent().color = BtnColor; } } } uiText.GetComponent().text = "使用數獨樣本"; Show(); } public void InitSudoku() { BtnDigHole.SetActive(false); for (int i=0;i().color = Color.gray; } } Show(); solveStep = 0; uiText.GetComponent().text = "清空盤面"; } int[] GetRandomArray() { int[] result = new int[] {1,2,3,4,5,6,7,8,9}; int loopNum = UnityEngine.Random.Range(10,100); for (int i = 0; i < loopNum; i++) { int a = UnityEngine.Random.Range(0,9); int b = UnityEngine.Random.Range(0,9); while(a == b) { b = UnityEngine.Random.Range(0,9); } int temp = 0; temp = result[a]; result[a] = result[b]; result[b] = temp; } return result; } public void DiggingHole() { BtnDigHole.SetActive(false); int digHoleNum = 15; string digHoleText = InputFld.GetComponent().text; if ((digHoleText == "") || (int.Parse(digHoleText) > 50) || (int.Parse(digHoleText) <= 0)) { InputFld.GetComponent().text = "15"; digHoleNum = 15; uiText.GetComponent().text = "進行挖洞,共挖15個洞"; } else digHoleNum = int.Parse(digHoleText); uiText.GetComponent().text = "進行挖洞,共挖"+digHoleNum.ToString()+"個洞"; for (int i = 0; i < digHoleNum; i++) { int a = UnityEngine.Random.Range(0,9); int b = UnityEngine.Random.Range(0,9); while(sudoku[a,b] == 0) { a = UnityEngine.Random.Range(0,9); b = UnityEngine.Random.Range(0,9); } sudoku[a,b] = 0; string str="Btn"+(a+1)+"-"+(b+1); GameObject Btn = GameObject.Find(str); Btn.GetComponent().color = Color.gray; } Show(); } void SolveSudoku() { nlen = 0; n = new int[81, 2]; solution1 = new int[9, 9]; solution2 = new int[9, 9]; //將盤面上的文字存入sudoku for (int i = 1; i <= 9; i++) { for (int j = 1; j <= 9; j++) { string str="Btn"+i+"-"+j+"/Text"; GameObject Btn = GameObject.Find(str); sudoku[i-1,j-1] = int.Parse(Btn.GetComponent().text); Btn.GetComponentInParent().color = BtnColor; } } //搜尋數獨區塊為零的地方 for (int i = 0; i < 9; i++) { for (int j = 0; j < 9; j++) { if(sudoku[i,j]==0) { n[nlen, 0] = i; n[nlen, 1] = j; nlen++; } } } //用遞迴法搜尋可能解答 forcefillsdk(1); } void forcefillsdk(int i) { if(solveStep>=30000) { uiText.GetComponent().text = "解題超過30000步,強制終止"; return; } if((i > nlen) && (solution1[0,0] == 0)) { Show(); for(int a=0;a().text = "完成解題,找到答案1"; //所有空格填回0 for (int k = 0; k < nlen; k++) { sudoku[n[k, 0],n[k, 1]] = 0; } forcefillsdk(1); return; } if((i > nlen) && (solution1[0,0] != 0) && (solution2[0,0] == 0)) { Show(); for(int a=0;a().text = "完成解題,找到答案1與答案2"; return; } if(i > nlen) { uiText.GetComponent().text = "完成解題,終止程序"; return; } for (int k = 1; k <= 9; k++) { if (solution1[0,0] == 0) sudoku[n[i-1, 0],n[i-1, 1]] += 1; //解答一用正向搜尋 else sudoku[n[i-1, 0],n[i-1, 1]] -= 1; //解答二用逆向搜尋 if (sudoku[n[i-1, 0],n[i-1, 1]] > 9) { sudoku[n[i-1, 0],n[i-1, 1]] = 1; } if (sudoku[n[i-1, 0],n[i-1, 1]] < 0) { sudoku[n[i-1, 0],n[i-1, 1]] = 9; } if(isValid(n[i-1, 0], n[i-1, 1])) { // print("第"+i+"("+n[i-1, 0]+","+n[i-1, 1]+")被填滿"+sudoku[n[i-1, 0],n[i-1, 1]]); solveStep++; forcefillsdk(i+1); } else { solveStep++; } } // print("第"+i+"("+n[i-1, 0]+","+n[i-1, 1]+")錯誤,數字歸零"); sudoku[n[i-1, 0],n[i-1, 1]] = 0; } //驗證函數 bool isValid(int i, int j) { int n = sudoku[i,j]; int[] query = new int[9] {0,0,0,3,3,3,6,6,6}; int t, u; //本身是否為零 if (sudoku[i,j] == 0) return false; //每一行每一列是否重複 for (t=0;t().text); } } bool Correctness = true; for (int i = 0; i < 9; i++) { for (int j = 0; j < 9; j++) { if(isValid(i,j) == false) { Correctness = false; string str="Btn"+(i+1)+"-"+(j+1); GameObject Btn = GameObject.Find(str); Btn.GetComponent().color = Color.red; } else { string str="Btn"+(i+1)+"-"+(j+1); GameObject Btn = GameObject.Find(str); Btn.GetComponent().color = BtnColor; } } } if (Correctness) { uiText.GetComponent().text = "數獨盤面正確無誤"; BtnDigHole.SetActive(true); } else uiText.GetComponent().text = "數獨盤面有以下紅色錯誤"; } void Show() { for (int i = 1; i <= 9; i++) { for (int j = 1; j <= 9; j++) { string str="Btn"+i+"-"+j+"/Text"; GameObject Btn = GameObject.Find(str); Btn.GetComponent().text = ""+sudoku[i-1,j-1]; } } } public void CreateGameFull() { uiText.GetComponent().text = "開始產題..."; solveStep = 0; InitSudoku(); for (int i = 0; i < 3; i++) { int[] temp = GetRandomArray(); int p = 0; for (int j =0+(i*3);j().text); } } solveStep = 0; SolveSudoku(); solveStep = 0; } void Start() { BtnDigHole.SetActive(false); } void Update() { } }
<9 btn.getcomponent="" btn="GameObject.Find(str);" for="" gameobject="" i="" int="" j="" mage="" solution1="" str="Btn" string="" sudoku=""><9 btn.getcomponent="" btn="GameObject.Find(str);" for="" gameobject="" i="" int="" j="" mage="" solution2="" str="Btn" string="" sudoku=""><9 0="" btn.getcomponent="" btn="GameObject.Find(str);" for="" gameobject="" i="" if="" int="" j="" mage="" str="Btn" string="" sudoku="" sudokusample=""><9 0="" btn.getcomponent="" btn="GameObject.Find(str);" for="" gameobject="" i="" int="" j="" mage="" str="Btn" string="" sudoku=""><9 a="" b="" ext="" for="" int="" solution1="" sudoku="" uitext.getcomponent=""><9 a="" b="" ext="" for="" int="" solution2="" sudoku="" uitext.getcomponent=""><9 3="" 9="" btn="GameObject.Find(str);" checksdkcorrectness="" ext="" false="" for="" gameobject="" i-1="" i="" if="" int.parse="" int="" j-1="" j="" n="" public="" query="" return="" str="Btn" string="" sudoku="" t="" tn.getcomponent="" true="" u="" void="">
9> 9>9> 9> 9> 9> 9>
這次的數獨撰寫,讓我對小時候寫遊戲程式的熱誠慢慢又找回來了,希望未來會有更多的作品可以跟大家分享,也期待大家能夠多多支持國產遊戲作品,因為設計一款好遊戲真的不容易~ヽ(∀゚ )人(゚∀゚)人( ゚∀)人(∀゚ )人(゚∀゚)人( ゚∀)ノ
沒有留言:
張貼留言