這篇文章主要介紹使用C#來編寫一個(gè)完整字謎游戲的方法是什么,文中介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們一定要看完!
公司主營業(yè)務(wù):網(wǎng)站設(shè)計(jì)、做網(wǎng)站、移動(dòng)網(wǎng)站開發(fā)等業(yè)務(wù)。幫助企業(yè)客戶真正實(shí)現(xiàn)互聯(lián)網(wǎng)宣傳,提高企業(yè)的競爭能力。創(chuàng)新互聯(lián)是一支青春激揚(yáng)、勤奮敬業(yè)、活力青春激揚(yáng)、勤奮敬業(yè)、活力澎湃、和諧高效的團(tuán)隊(duì)。公司秉承以“開放、自由、嚴(yán)謹(jǐn)、自律”為核心的企業(yè)文化,感謝他們對(duì)我們的高要求,感謝他們從不同領(lǐng)域給我們帶來的挑戰(zhàn),讓我們激情的團(tuán)隊(duì)有機(jī)會(huì)用頭腦與智慧不斷的給客戶帶來驚喜。創(chuàng)新互聯(lián)推出云溪免費(fèi)做網(wǎng)站回饋大家。
字謎游戲,可能你在許多益智書中都曾看到過。試著在電腦上用不同類別的內(nèi)容寫字謎游戲,并且有自定義字詞去玩也是很有意思的。
我很早以前使用Turbo C編碼游戲,但我丟失了代碼。我覺得用C#.NET讓它復(fù)活將是一件很偉大的事情。該語言在內(nèi)存、GC、圖形方面提供了很多靈活性,而這些是我在使用C語言的時(shí)候必須小心處理的。但是在C語言中的明確關(guān)注,會(huì)讓我們學(xué)到很多(這就是為什么C語言被稱為“上帝的編程語言”的原因)。另一方面,因?yàn)镃#.NET照顧到了這些,所以我可以專注于其他地方的增強(qiáng),例如字的方向,重疊,作弊碼,計(jì)分,加密等。所以在欣賞兩種語言的時(shí)候需要有一個(gè)平衡。
在題目中我之所以說它是“完整的”,原因如下:
1)它有一些類別的預(yù)設(shè)詞。
2)它在加密文件中保存單詞和分?jǐn)?shù),這樣就沒有人可以篡改文件。如果要篡改,那么它將恢復(fù)到預(yù)設(shè)并從頭開始計(jì)分。
3)它有作弊碼,但作弊會(huì)不利于得分,且顯然作弊一旦應(yīng)用會(huì)使分?jǐn)?shù)歸零。
4)它有一個(gè)計(jì)分機(jī)制。
游戲提供以下功能,具體我將在隨后的章節(jié)中討論:
1)載入類別和單詞:從程序中硬編碼的預(yù)設(shè)中加載單詞。然而,如果玩家提供自定義的單詞,那么游戲?qū)⒆詣?dòng)把所有這些(連同預(yù)設(shè))存儲(chǔ)在文件中并從那里讀取。
2)放在網(wǎng)格上:游戲?qū)⑺械膯卧~隨機(jī)地放在18×18的矩陣中。方向可以是水平,垂直,左下和右下,如上圖中所示。
3)計(jì)分:對(duì)于不同類別,分?jǐn)?shù)單獨(dú)存儲(chǔ)。分?jǐn)?shù)的計(jì)算方式是單詞的長度乘以乘法因子(這里為10)。與此同時(shí),在找到所有的單詞之后,剩余時(shí)間(乘以乘法因子)也會(huì)加到分?jǐn)?shù)中。
4)顯示隱藏的單詞:如果時(shí)間用完之后,玩家依然找不到所有的單詞,那么游戲會(huì)用不同的顏色顯示沒找到的單詞。
5)作弊碼:游戲在游戲板上提作弊碼(mambazamba)。作弊碼只簡單地設(shè)置了一整天的時(shí)間(86,400秒)。但是,應(yīng)用作弊碼也會(huì)應(yīng)用讓此次運(yùn)行的計(jì)分為零的懲罰。
載入預(yù)設(shè)
我們有一個(gè)簡單的用于持有類別和單詞的類:
class WordEntity { public string Category { get; set; } public string Word { get; set; } }
我們有一些預(yù)設(shè)的類別和單詞如下。預(yù)設(shè)都是管道分隔的,其中每第15個(gè)單詞是類別名稱,后面的單詞是該類別中的單詞。
private string PRESET_WORDS = "COUNTRIES|BANGLADESH|GAMBIA|AUSTRALIA|ENGLAND|NEPAL|INDIA|PAKISTAN|TANZANIA|SRILANKA|CHINA|CANADA|JAPAN|BRAZIL|ARGENTINA|" + "MUSIC|PINKFLOYD|METALLICA|IRONMAIDEN|NOVA|ARTCELL|FEEDBACK|ORTHOHIN|DEFLEPPARD|BEATLES|ADAMS|JACKSON|PARTON|HOUSTON|SHAKIRA|" + ...
我們使用加密在文件中寫這些單詞。所以沒有人可以篡改文件。對(duì)于加密我使用了一個(gè)從這里借鑒的類。使用簡單——你需要傳遞字符串和用于加密的加密密碼。對(duì)于解密,你需要傳遞加密的字符串和密碼。
如果文件存在,那么我們從那里讀取類別和單詞,否則我們保存預(yù)設(shè)(以及玩家自定義的單詞)并從預(yù)設(shè)那里讀取。這在下面的代碼中完成:
if (File.Exists(FILE_NAME_FOR_STORING_WORDS)) // If words file exists, then read it. ReadFromFile(); else { // Otherwise create the file and populate from there. string EncryptedWords = StringCipher.Encrypt(PRESET_WORDS, ENCRYPTION_PASSWORD); using (StreamWriter OutputFile = new StreamWriter(FILE_NAME_FOR_STORING_WORDS)) OutputFile.Write(EncryptedWords); ReadFromFile(); }
ReadFromFile()方法簡單地從存儲(chǔ)單詞的文件中讀取。它首先嘗試解密從文件讀取的字符串。如果失敗(由返回的空白字符串確定),它將顯示關(guān)于問題的一條消息,然后從內(nèi)置預(yù)設(shè)重新加載。否則它從字符串讀取并將它們分成類別和單詞,并把它們放在單詞列表中。每第15個(gè)詞是類別,后續(xù)詞是該類別下的單詞。
string Str = File.ReadAllText(FILE_NAME_FOR_STORING_WORDS); string[] DecryptedWords = StringCipher.Decrypt(Str, ENCRYPTION_PASSWORD).Split('|'); if (DecryptedWords[0].Equals("")) // This means the file was tampered. { MessageBox.Show("The words file was tampered. Any Categories/Words saved by the player will be lost."); File.Delete(FILE_NAME_FOR_STORING_WORDS); PopulateCategoriesAndWords(); // Circular reference. return; } string Category = ""; for (int i = 0; i <= DecryptedWords.GetUpperBound(0); i++) { if (i % (MAX_WORDS + 1) == 0) // Every 15th word is the category name. { Category = DecryptedWords[i]; Categories.Add(Category); } else { WordEntity Word = new WordEntity(); Word.Category = Category; Word.Word = DecryptedWords[i]; WordsList.Add(Word); } }
保存玩家的自定義詞
游戲可供應(yīng)由玩家提供的自定義詞。設(shè)備位于相同的加載窗口。單詞應(yīng)該最少3個(gè)字符長,最多10個(gè)字符長,并且需要14個(gè)單詞——不多也不能不少。指示在標(biāo)簽中。另外單詞不能是任何其他詞的子部分。例如:不能有如’JAPAN’和’JAPANESE’這樣兩個(gè)詞,因?yàn)榍罢甙诤笳咧小?/p>
我將簡要介紹一下有效性檢查。有3個(gè)關(guān)于最大長度、最小長度和SPACE輸入(不允許空格)的即時(shí)檢查。這通過將我們自定義的處理程序Control_KeyPress添加到單詞條目網(wǎng)格的EditingControlShowingevent中來完成。
private void WordsDataGridView_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e) { e.Control.KeyPress -= new KeyPressEventHandler(Control_KeyPress); e.Control.KeyPress += new KeyPressEventHandler(Control_KeyPress); }
每當(dāng)用戶輸入東西時(shí),處理程序就被調(diào)用并檢查有效性。完成如下:
TextBox tb = sender as TextBox; if (e.KeyChar == (char)Keys.Enter) { if (tb.Text.Length <= MIN_LENGTH) // Checking length { MessageBox.Show("Words should be at least " + MAX_LENGTH + " characters long."); e.Handled = true; return; } } if (tb.Text.Length >= MAX_LENGTH) // Checking length { MessageBox.Show("Word length cannot be more than " + MAX_LENGTH + "."); e.Handled = true; return; } if (e.KeyChar.Equals(' ')) // Checking space; no space allowed. Other invalid characters check can be put here instead of the final check on save button click. { MessageBox.Show("No space, please."); e.Handled = true; return; } e.KeyChar = char.ToUpper(e.KeyChar);
最后,在輸入所有單詞并且用戶選擇保存和使用自定義單詞之后存在有效性檢查。首先它檢查是否輸入了14個(gè)單詞。然后它遍歷所有的14個(gè)單詞,并檢查它們是否有無效字符。同時(shí)它也檢查重復(fù)的單詞。檢查成功就把單詞添加到列表中。最后,提交另一次迭代,以檢查單詞是否包含在另一個(gè)單詞中(例如,不能有如’JAPAN’和’JAPANESE’這樣的兩個(gè)單詞,因?yàn)榍罢甙诤笳咧校?。通過下面的代碼完成:
public bool CheckUserInputValidity(DataGridView WordsDataGridView, ListWordsByThePlayer) { if (WordsDataGridView.Rows.Count != MAX_WORDS + 1) { MessageBox.Show("You need to have " + MAX_WORDS + " words in the list. Please add more."); return false; } char[] NoLettersList = { ':', ';', '@', '\'', '"', '{', '}', '[', ']', '|', '\\', '<', '>', '?', ',', '.', '/', '`', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '~', '!', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+'}; //' foreach (DataGridViewRow Itm in WordsDataGridView.Rows) { if (Itm.Cells[0].Value == null) continue; if (Itm.Cells[0].Value.ToString().IndexOfAny(NoLettersList) >= 0) { MessageBox.Show("Should only contain letters. The word that contains something else other than letters is: '" + Itm.Cells[0].Value.ToString() + "'"); return false; } if (WordsByThePlayer.IndexOf(Itm.Cells[0].Value.ToString()) != -1) { MessageBox.Show("Can't have duplicate word in the list. The duplicate word is: '" + Itm.Cells[0].Value.ToString() + "'"); return false; } WordsByThePlayer.Add(Itm.Cells[0].Value.ToString()); } for (int i = 0; i < WordsByThePlayer.Count - 1; i++) // For every word in the list. { string str = WordsByThePlayer[i]; for (int j = i + 1; j < WordsByThePlayer.Count; j++) // Check existence with every other word starting from the next word if (str.IndexOf(WordsByThePlayer[j]) != -1) { MessageBox.Show("Can't have a word as a sub-part of another word. Such words are: '" + WordsByThePlayer[i] + "' and '" + WordsByThePlayer[j] + "'"); return false; } } return true; }
玩家的列表與現(xiàn)有單詞一起保存,然后游戲板與該類別中的那些單詞一起被打開。
在網(wǎng)格上放置單詞
單詞通過InitializeBoard()方法被放置在網(wǎng)格上。我們?cè)谧址仃嚕ǘS字符數(shù)組)WORDS_IN_BOARD中先放置單詞。然后我們?cè)诰W(wǎng)格中映射這個(gè)矩陣。遍歷所有的單詞。每個(gè)單詞獲取隨機(jī)方向(水平/垂直/左下/右下)下的隨機(jī)位置。此時(shí),如果我們可視化的話,單詞矩陣看起來會(huì)有點(diǎn)像下面這樣。
放置通過PlaceTheWords()方法完成,獲得4個(gè)參數(shù)——單詞方向,單詞本身,X坐標(biāo)和Y坐標(biāo)。這是一個(gè)關(guān)鍵方法,所以我要逐個(gè)解釋這四個(gè)方向。
水平方向
對(duì)于整個(gè)單詞,逐個(gè)字符地運(yùn)行循環(huán)。首先它檢查這個(gè)詞是否落在網(wǎng)格之外。如果這是真的,那么它返回到調(diào)用過程以生成新的隨機(jī)位置和方向。
然后,它檢查當(dāng)前字符是否可能與網(wǎng)格上的現(xiàn)有字符重疊。如果發(fā)生這種情況,那么檢查它是否是相同的字符。如果不是相同的字符,那就返回到調(diào)用方法,請(qǐng)求另一個(gè)隨機(jī)位置和方向。
在這兩個(gè)檢查之后,如果放置是一種可能,那么就把單詞放置在矩陣中,并且通過方法StoreWordPosition()將列表中的位置和方向存儲(chǔ)在WordPositions中。
for (int i = 0, j = PlacementIndex_X; i < Word.Length; i++, j++) // First we check if the word can be placed in the array. For this it needs blanks there. { if (j >= GridSize) return false; // Falling outside the grid. Hence placement unavailable. if (WORDS_IN_BOARD[j, PlacementIndex_Y] != '\0') if (WORDS_IN_BOARD[j, PlacementIndex_Y] != Word[i]) // If there is an overlap, then we see if the characters match. If matches, then it can still go there. { PlaceAvailable = false; break; } } if (PlaceAvailable) { // If all the cells are blank, or a non-conflicting overlap is available, then this word can be placed there. So place it. for (int i = 0, j = PlacementIndex_X; i < Word.Length; i++, j++) WORDS_IN_BOARD[j, PlacementIndex_Y] = Word[i]; StoreWordPosition(Word, PlacementIndex_X, PlacementIndex_Y, OrientationDecision); return true; } break;
垂直/左下/右下方向
相同的邏輯適用于為這3個(gè)方向找到單詞的良好布局。它們?cè)诰仃囄恢煤瓦吔鐧z查的增量/減量方面不同。
在所有的單詞被放置在矩陣中之后,F(xiàn)illInTheGaps()方法用隨機(jī)字母填充矩陣的其余部分。此時(shí)窗體打開并觸發(fā)Paint()事件。在這個(gè)事件上,我們繪制最終顯示為40×40像素矩形的線。然后我們將我們的字符矩陣映射到board上。
Pen pen = new Pen(Color.FromArgb(255, 0, 0, 0)); ColourCells(ColouredRectangles, Color.LightBlue); if (FailedRectangles.Count > 0) ColourCells(FailedRectangles, Color.ForestGreen); // Draw horizontal lines. for (int i = 0; i <= GridSize; i++) e.Graphics.DrawLine(pen, 40, (i + 1) * 40, GridSize * 40 + 40, (i + 1) * 40); // Draw vertical lines. for (int i = 0; i <= GridSize; i++) e.Graphics.DrawLine(pen, (i + 1) * 40, 40, (i + 1) * 40, GridSize * 40 + 40); MapArrayToGameBoard();
MapArrayToGameBoard()方法簡單地把我們的字符矩陣放在board上。我們使用來自MSDN的繪圖代碼。這遍歷矩陣中的所有字符,將它們放置在40×40矩形的中間,邊距調(diào)整為10像素。
Graphics formGraphics = CreateGraphics(); Font drawFont = new Font("Arial", 16); SolidBrush drawBrush = new SolidBrush(Color.Black); string CharacterToMap; for (int i = 0; i < GridSize; i++) for (int j = 0; j < GridSize; j++) { if (WORDS_IN_BOARD[i, j] != '\0') { CharacterToMap = "" + WORDS_IN_BOARD[i, j]; // "" is needed as a means for conversion of character to string. formGraphics.DrawString(CharacterToMap, drawFont, drawBrush, (i + 1) * 40 + 10, (j + 1) * 40 + 10); } }
單詞發(fā)現(xiàn)和有效性檢查
鼠標(biāo)點(diǎn)擊位置和釋放位置存儲(chǔ)在點(diǎn)列表中。對(duì)鼠標(biāo)按鈕釋放事件(GameBoard_MouseUp())調(diào)用CheckValidity()方法。同時(shí),當(dāng)用戶在左鍵按下的同時(shí)拖動(dòng)鼠標(biāo)時(shí),我們從起始位置繪制一條線到鼠標(biāo)指針。這在GameBoard_MouseMove()事件中完成。
if (Points.Count > 1) Points.Pop(); if (Points.Count > 0) Points.Push(e.Location); // Form top = X = Distance from top, left = Y = Distance from left. // However mouse location X = Distance from left, Y = Distance from top. // Need an adjustment to exact the location. Point TopLeft = new Point(Top, Left); Point DrawFrom = new Point(TopLeft.Y + Points.ToArray()[0].X + 10, TopLeft.X + Points.ToArray()[0].Y + 80); Point DrawTo = new Point(TopLeft.Y + Points.ToArray()[1].X + 10, TopLeft.X + Points.ToArray()[1].Y + 80); ControlPaint.DrawReversibleLine(DrawFrom, DrawTo, Color.Black); // draw new line
單詞的有效性在CheckValidity()方法中檢查。它通過抓取所有的字母來制定單詞,字母通過使用鼠標(biāo)查看相應(yīng)的字符矩陣來繪制。然后檢查是否真的匹配單詞列表中的單詞。如果匹配,則通過將單元格著色為淺藍(lán)色并使單詞列表中的單詞變灰來更新單元格。
以下是抓取行開始和結(jié)束位置的代碼片段。首先它檢查行是否落在邊界之外。然后它制定單詞并且存儲(chǔ)矩陣的坐標(biāo)。類似地,它檢查垂直,左下和右下單詞,并嘗試相應(yīng)地匹配。如果這真的匹配,那么我們通過AddCoordinates()方法將臨時(shí)矩形存儲(chǔ)在我們的ColouredRectangles點(diǎn)列表中。
if (Points.Count == 1) return; // This was a doble click, no dragging, hence return. int StartX = Points.ToArray()[1].X / 40; // Retrieve the starting position of the line. int StartY = Points.ToArray()[1].Y / 40; int EndX = Points.ToArray()[0].X / 40; // Retrieve the ending position of the line. int EndY = Points.ToArray()[0].Y / 40; if (StartX > GridSize || EndX > GridSize || StartY > GridSize || EndY > GridSize || // Boundary checks. StartX <= 0 || EndX <= 0 || StartY <= 0 || EndY <= 0) { StatusLabel.Text = "Nope!"; StatusTimer.Start(); return; } StringBuilder TheWordIntended = new StringBuilder(); ListTempRectangles = new List (); TheWordIntended.Clear(); if (StartY == EndY) // Horizontal line drawn. for (int i = StartX; i <= EndX; i++) { TheWordIntended.Append(WORDS_IN_BOARD[i - 1, StartY - 1].ToString()); TempRectangles.Add(new Point(i * 40, StartY * 40)); }
對(duì)于計(jì)分,我們有計(jì)分文件。如果缺少,則使用當(dāng)前分?jǐn)?shù)和類別創(chuàng)建一個(gè)。這里,再次,所有的分?jǐn)?shù)被組合在一個(gè)大的管道分隔的字符串中,然后該字符串被加密并放入文件。我們有四個(gè)實(shí)體。
class ScoreEntity { public string Category { get; set; } public string Scorer { get; set; } public int Score { get; set; } public DateTime ScoreTime { get; set; } .............. ..............
最多允許一個(gè)類別14個(gè)分?jǐn)?shù)。首先加載分?jǐn)?shù)列表中的所有分?jǐn)?shù),然后獲得當(dāng)前分類分?jǐn)?shù)的排序子集。在該子集中,檢查當(dāng)前分?jǐn)?shù)是否大于任何可用的分?jǐn)?shù)。如果是,則插入當(dāng)前分?jǐn)?shù)。之后,檢查子集數(shù)是否超過14,如果超過了,就消除最后一個(gè)。所以最后的得分消失了,列表總是有14個(gè)分?jǐn)?shù)。這在CheckAndSaveIfTopScore()方法中完成。
這里,再次,如果有人篡改得分文件,那么它只會(huì)開始一個(gè)新的得分。不允許篡改。
如果時(shí)間用完了,那么游戲用綠色顯示單詞。首先,獲取玩家找不到的單詞??梢允沁@樣的
ListFailedWords = new List (); foreach (string Word in WORD_ARRAY) if (WORDS_FOUND.IndexOf(Word) == -1) FailedWords.Add(Word);
然后,遍歷這些失敗的單詞位置并制定相應(yīng)的失敗的矩陣。最后,它通過無效來調(diào)用窗體的paint方法。
foreach (string Word in FailedWords) { WordPosition Pos = WordPositions.Find(p => p.Word.Equals(Word)); if (Pos.Direction == Direction.Horizontal) // Horizontal word. for (int i = Pos.PlacementIndex_X + 1, j = Pos.PlacementIndex_Y + 1, k = 0; k < Pos.Word.Length; i++, k++) FailedRectangles.Add(new Point(i * 40, j * 40)); else if (Pos.Direction == Direction.Vertical) // Vertical word. for (int i = Pos.PlacementIndex_X + 1, j = Pos.PlacementIndex_Y + 1, k = 0; k < Pos.Word.Length; j++, k++) FailedRectangles.Add(new Point(i * 40, j * 40)); else if (Pos.Direction == Direction.DownLeft) // Down left word. for (int i = Pos.PlacementIndex_Y + 1, j = Pos.PlacementIndex_X + 1, k = 0; k < Pos.Word.Length; i--, j++, k++) FailedRectangles.Add(new Point(i * 40, j * 40)); else if (Pos.Direction == Direction.DownRight) // Down right word. for (int i = Pos.PlacementIndex_X + 1, j = Pos.PlacementIndex_Y + 1, k = 0; k < Pos.Word.Length; i++, j++, k++) FailedRectangles.Add(new Point(i * 40, j * 40)); } Invalidate();
這是一件小事了。這工作在keyup事件上,這個(gè)事件抓取所有的擊鍵到CheatCode變量。實(shí)際上,我們合并玩家在游戲窗口上輸入的擊鍵,并看看代碼是否與我們的CHEAT_CODE(mambazamba)匹配。例如,如果玩家按下“m”和“a”,那么我們?cè)贑heatCode變量中將它們保持為’ma’(因?yàn)?,ma仍然匹配cheatcode模式)。類似地,如果它匹配CHEAT_CODE的模式,則添加連續(xù)變量。然而,一旦它不能匹配模式(例如,’mambi’),則重新開始。
最后,如果匹配,則激活作弊碼(將剩余時(shí)間提高到完整一天,即86,400秒),并應(yīng)用懲罰。
CheatCode += e.KeyCode.ToString().ToUpper(); if (CHEAT_CODE.IndexOf(CheatCode) == -1) // Cheat code didn't match with any part of the cheat code. CheatCode = ("" + e.KeyCode).ToUpper(); // Hence erase it to start over. else if (CheatCode.Equals(CHEAT_CODE) && WORDS_FOUND.Count != MAX_WORDS) { Clock.TimeLeft = 86400; // Cheat code applied, literally unlimited time. 86400 seconds equal 1 day. ScoreLabel.Text = "Score: 0"; StatusLabel.Text = "Cheated! Penalty applied!!"; StatusTimer.Start(); CurrentScore = 0; Invalidate();
這里有趣的是,我們必須使用WordsListView的KeyUp事件而不是窗體。這是因?yàn)樵诩虞d游戲窗口后,列表框有焦點(diǎn),而不是窗體。
以上是使用C#來編寫一個(gè)完整字謎游戲的方法是什么的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對(duì)大家有幫助,更多相關(guān)知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!