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