熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> .NET編程 >> 正文

利用Visual C#實現Reversi游戲開發

2022-06-13   來源: .NET編程 

   簡介
  
  本文將細致地介紹用C#來實現游戲Reversi的完整過程游戲界面如下圖所示
  
 

   背景
  
  我最開始寫這個程序是為了作為學習C#編程的一個練習Reversi或Othello一是一個相當有趣且相當流行的游戲它僅要求幾個基本元素和簡單的游戲規則所以它是學習一個新的編程環境的良好選擇
  
  該程序的第一個版本是一個可玩的游戲但是缺乏一些計算機平板游戲的常規特性例如撤消移動的能力因此在又學習了NET編程的一些技巧後我又對該游戲進行了改進修改後的游戲在原先的圖形和人工智能方面增加了一些新特性並作了性能上的改進
  
   使用代碼
  
  你只要編譯源文件並運行結果可執行文件Reversiexe即可開始玩這個游戲使用菜單或工具欄你可以進行多方面的選擇和設置你不妨試著在游戲中間縮放窗戶改變顏色或交換邊界來觀察所發生的情況
  
  你可能注意在你退出該游戲時該程序將創造一文件ReversiXML這個文件被使用於保存多方面設置例如游戲選項窗戶大小和位置以及進行玩家統計它們在游戲重新開始時被重載
  
   幫助文件
  
  本文還提供一個Windows幫助文件包括完整的源代碼你可以在文檔的help files子目錄找到它為了使這個幫助文件可應用於該程序只需簡單地把文件Reversichm復制到可執行文件Reversiexe所在位置即可當然沒有它你也可以運行此游戲但是如果點擊Help Topics選項將顯示一個錯誤的話說明該游戲主程序不能發現幫助文件
  
  所有用於創建這個幫助文件的源文件包括在那個子目錄下你可以使用微軟的HTML Help Workshop對之進行編輯並且重新編譯它
  
   興趣點
  
  相應的源文件已經被很好的注釋過了讀者可以很容易的看懂現在讓我們分析一下本游戲中幾個有趣的方面
  
  (一) 游戲AI
  
  本游戲的一個很有意思的地方是計算游戲玩家的移動所以值得討論本游戲使用一標准的最小最大向前看算法來確定玩家的最佳移動Alphabeta pruning被使用於改進向前搜索的效率如果你不熟悉最小最大向前看算法和/或alphabeta pruning你可以用Google搜索來找到大量的相關信息和示例
  
  當然在游戲中可能存在的太多的移動順序將導致一個相當費時的向前搜索要生成所有可能的移動組合需要花太長的時間這裡的例外是在游戲的結束時此時僅剩下很少的幾個方格大約十或二十個此時可以進行全部的搜索並且這時可能找到玩家的最佳移動結果
  
  但是在大多數情況中向前搜索深度必須被限定到一個數目(這基於游戲的難度設置)因此對於每一系列可能的行動和反向移動搜索必須計算最後的游戲平板以決定哪個玩家最有機會贏得游戲該計算是通過使用下列標准來計算一個等級
  
  ·輸掉讓你的對手沒有合法的移動可以迫使他輸掉這一回合從而使你更有利於能夠在一行中再次(或多次)移動
  
  ·可移動性這是一種測算你可以做出多少次合法的行動從而留給你的對手多少次合法的行動類似於輸掉其思想是減少你的對手的選擇從而最大化你自己的選擇
  
  ·邊界一個邊界圓盤是鄰近一個空的方格的地方一般地擁有大量的邊界圓盤會給你的對手在隨後的回合中更多的可移動性相反擁有較少的邊界圓盤意味著你的對手將在後面有較少的可移動性這種得分反應了你的邊界圓盤相對於你的對手的邊界圓盤數
  
  ·穩定性角圓盤是穩定的它們永不會被翼側包圍隨著游戲的進展另外的圓盤也將變為穩定的這種得分反應了你的穩定圓盤數相對於你的對手的穩定圓盤數
  
  ·得分這是在平板上你的圓盤數相對於你的對手的圓盤數之差
  
  不同的權值分別被賦給這裡的每一種得分(這再次依賴於游戲的當前難度設置)通過每一種標准得分乘以它的相應權值然後把這些值加在一起一個平板即被賦予一個等級一個大的負數等級代表一個平板有利於黑棋而一個大的正數等級代表一個平板有利於白棋因此對於一個可能的移動集合計算機將為當前選定顏色一方選擇最可能導致最高等級的那個移動
  
  一個常數maxRank被用於一場游戲的結束它被設置為SystemIntMaxValue這可以確保任何會導致游戲結束的移動將總是比其它移動具有更高的等級(負數或正數)
  
  從系統的最大整數值減去允許我們把最後的得分添加到等級上這樣贏得個圓盤將比贏得個圓盤具有高的等級這可以使得計算機玩家在贏了時最大化自己的得分(或在輸了時最小化對手玩家的得分)
  
  當前的實現還不能匹配更好的AI玩家但是如果它和一個小人對手(至少這個小人對手)玩得話已經比較難了同樣如果你用Google搜索一下會找到許多描述此游戲策略和AI方法的資源
  
  (二) 游戲部件
  
   平板類
  
  Board類描述了一個游戲平板它使用一個二維數組來跟蹤每個平板方格的內容它可以是定義在類中的下列的常數值之一
  
  ·Black=
  ·Empty=
  ·White=
  
  該類提供了兩個構造器一個用於創建一個新的空的平板而另一個創建一個已存在平板的拷貝
  
  它提供象MakeMove()這樣的公共方法這個方法把一個圓盤添加到平板上並能翻動任何可翼側包圍的對手圓盤例如IsValidMove()可被用來確定是否一個給定的移動對於一個給定玩家是有效的如果該給定玩家不能作任何合法的移動HasAnyValidMove()將返回false
  
  另外它還為每一個玩家跟蹤圓盤的數目該數目被用於機器的移動AI例程這些數目包括圓盤總數邊界圓盤數和每種顏色的安全圓盤(或未翻動的圓盤)數
  
   移動結構
  
  在主要的ReversiForm類中定義了一對結構用於存儲游戲移動這兩個結構都包含了一個行與列索引對以相應於一個特別的平板方格
  
  ComputerMove結構用於計算機AI除了移動位置之外它還有一個等級成員這是被用於跟蹤一次移動的好或壞這是在向前搜索過程中決定的
  
  MoveRecord結構用於存儲在游戲中每次移動的信息為了允許移動的撤消/重做特性建立了一個數組來跟蹤每一輪游戲中的該平板一次移動記錄包含一個描述這次特定移動之前的游戲平板還有用來指示哪一個玩家將做下次移動的值針對每個玩家的每次移動建立一個相應的數組以允許游戲復位到在移動過程中的任一點的狀態
  
  RestoreGameAt()方法實現把游戲復位到一個特定的移動數字盡管它潛在地允許游戲可以恢復到當前移動歷史中的任何移動但是主表單程序中的菜單和工具條選項目前僅提供了一次移動的撤消/重做或所有移動的撤消/重做一種將來的增加可能是允許用戶點擊移動列表中的項來把游戲恢復相應的移動數字
  
  (三) 圖形和用戶接口
  
   游戲平板
  
  平板上的方格被一個叫SquareControl的用戶控件所描述對於每個方格都有一個這種控件顯示於游戲平板上該控件包含信息用於顯示方格和它的內容(空的或一個黑的或白的圓盤)包括圓盤動畫和任何高亮
  
   顯示圓盤
  
  每一個圓盤被動態繪制其基本形狀是一個圓具有某種高亮和一個陰影來給它一個偽裝的D外觀這些形狀被按比例縮放依賴於方形控制的當前尺寸通過以這種方式對其著色代之使用靜態的圖像平板可以被動態地調整大小以匹配表單窗口的大小
  
  在ReversiForm內部控制的方格控件的Click事件允許用戶一次移動到一個特定的方格(假定它是一個合法的移動)同樣當這些選項激活時MouseMove和MouseLeave事件被控制通過有效的移動或預覽一次移動來更新平板顯示
  
   移動動畫
  
  圓盤反轉動畫是通過使用一個定義在SquareControl類中的計數器並伴隨一個SystemWindowsFormsTimer定時器實現的基本上這是一個被操作系統所控制的線程它周期性地引發一個你的表單應用程序能夠響應的事件
  
  在做一次移動後如果移動動畫選項處於活動狀態每個受影響的方格控制把它的計數器初始化並且激活定時器在每次時鐘滴答響時主表單的AnimateMove()方法被調用(見下面)這個方法更新方格計數器並且重畫它們的顯示該動畫基本上包含把圓盤形狀從一個圓改變成一個更扁的橢圓然後又變回到一個完整的圓只是以相反的顏色罷了這個動畫的光滑度和速度依賴於初始的計數值的大小(由常數SquareControlAnimationStart所設置)和時鐘多長時間滴答響一次(由主表單中的常數animationTimerInterval所設置)
  
  (四) 玩游戲
  
  下列變量用於控制一次游戲過程:
  
  //游戲參數
  private GameState gameState;
  private int currentColor;
  private int moveNumber;
  
  moveNumber應該是顯然的currentColor顯示現在輪著哪一個玩家移動(黑色或白色)gameState被設置為下列枚舉值之一
  
  //定義游戲的狀態
  private enum GameState{
  GameOver //游戲完了(也適合於起始的狀態)
  InMoveAnimation //產生一次移動並且該動畫是活動的
  InPlayerMove //等待用戶移動
  InComputerMove //等待計算機移動
  MoveCompleted //一次移動完成
  //(包括動畫如果是活動的)
  }
  
  大多數游戲都是在事件驅動下玩的因此gameState的使用允許各種事件處理器來決定要采取的適當行動例如當用戶點擊平板方格SquareControl_Click()被調用如果游戲狀態是InPlayerMove則在那個方格上作一次移動但是如果游戲在其它狀態則說明還沒輪到用戶移動所以這次點擊將被忽略
  
  同樣如果用戶點擊工具條Undo Move按鈕我們想要檢查該游戲狀態來看一下是否需要做任何事情在把游戲復位到前一次移動之前例如如果狀態是InMoveAnimation那麼動畫定時器需要停下來而該方格控制需要它們的計時器並且顯示重置如果狀態是InComputerMove那麼該程序現在在用一個獨立的線程進行一次向前搜索(見下面)它將需要停下來
  
   程序流程
  
  下圖說明了在一個游戲過程中的通用程序流程
  
 

  StartTurn()在每次游戲的開始當任何一個玩家做一次移動後以及無論何時執行一次撤消/重做之後被調用它負責評估游戲狀況並且為下次移動作准備
  
  它首先檢查是否當前玩家能夠作一合法的移動如果不能它切換到其它玩家並且檢查是否那個玩家有任何合法的移動當兩個玩家都不能移動時根據規則游戲結束並且它將結束該游戲
  
  否則該函數將為當前玩家作出移動作好准備如果當前玩家在用戶控制下它簡單地退出然後用戶通過在一個有效的方形上點按鼠標指針或通過輸入一個有效的列字母和行數字可以作一次移動這將導致一次對MakePlayerMove()的調用它完成一些清理工作然後調用MakeMove()來實現移動如果當前玩家在計算機控制下它就啟動向前搜索以找到最佳移動
  
   使用一個工作者線程
  
  因為向前搜索是深度優先計算的所以它被用一個工作者線程來專門實現否則主表單屏幕將被凍結並且在計算最佳移動時響應滯後因此StartTurn()創建一個工作者線程來執行CalculateComputerMove()方法並且啟動它
  
  鎖機制被用在主游戲平板對象上以防止不滿足游戲條件作為一個例子MakeComputerMove()和UndoMove()方法都因游戲平板而改變這兩個方法首先試圖把一個鎖放在它上面因此如果一個方法碰巧被調用而另一個正在更新該平板時它將被強迫等待直到那些變化完成並且該鎖被釋放為止
  
  一旦發現一個移動CalculateComputerMove()方法完成一次回調以在主表單屏幕上運行MakeComputerMove()這個方法鎖定平板並調用MakeMove()來實現移動
  
   實現移動
  
  MakeMove()完成實際的平板更新把新的圓盤放置到指定的位置它也實現一些維護如撤消/重做移動歷史和高亮搬遷任何方形等
  
  然後如果移動動畫選項置為Off狀態它簡單地調用EndMove()它將切換當前顏色並以一個對StartTurn()的調用啟動下一個回合
  
  但是如果動畫選項置為On狀態它將代之來初始化圓盤使其動起來並且啟動動畫定時器如以前所討論的該定時器將會使AnimateMove()每幾個毫秒運行一次更新顯示並且相應地每次減少動畫計數器最後該計數器將到點而AnimateMove()將調用EndMove()來完成移動
  
  (五) 未來的增強
  
  在玩家AI方面還有很大的改進余地向前搜索算法可以被擴充用打開的移動或一系列被預先定級別的角和邊模式可以使用選擇性深度這樣查找深度可以針對對游戲有較強影響的移動(例如在角落附近)而加以擴展另外的改進將是存儲向前搜索樹這將使它被搜索到一個更深的層次因為該程序不會在每次重新生成相同的移動
From:http://tw.wingwit.com/Article/program/net/201311/11945.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.