第三章 系統架構
本系統基於吳光哲的“Chess Spy",起始畫面如圖 3-1 所示。為了使程式可
以達到套譜的功能,我們修改 Chess Spy 程式,加入將開局庫內的盤面轉換成 FEN
字串
1
、放入系統剪貼薄的功能、和取得其它棋軟按鈕位置的功能。程式中所使用的相關技術大部份是由吳光哲學長所完成,特此提出,並表達感謝之意。
1
我們將在附錄 A 討論 FEN 記號法。
圖 3-1 套譜系統運行畫面
整個系統流程如圖 3-2:
取得棋軟按鍵位置
仍有待查盤 面?
程式開始
載入盤面
設定電腦思考 是
程式結束
電腦出步時間
>1秒?
取得由開局庫所得 的走步 取得由搜尋所得的
走步
是 否
否
盤面狀態是否 改變?
電腦已出步
仍在思考
是
否 等待電腦出步
掃描盤面
圖 3-2 套譜系統流程圖
第 一 節 滑 鼠 自 動 點 選
因為我們不止要套取一次盤面,如果只套取少量的次數,可以使用人工點選
的方式。要套取大量的盤面走步,必須讓棋軟幫我們自動化的完成,我們的目的
希望可以用批次的方次來進行整個工作,為了達到此目的,我們需要事先取得這
些棋軟的按鈕位置,以便讓系統在取得一個盤面的走步後,再自動套取下一個盤
面。
我們要取得其它棋軟的按鈕位置,位於我們程式視窗的外部,並不是在同一
個視窗之內的,所以我們必需使用 HOOK
2
來取得按鈕的位置。Windows 是一套以訊息驅動(Message Driven)的作業系統。所謂訊息是由 Windows 作業系統送
往程式的事件。它是系統中各個物件溝通的方式,當滑鼠移動、按下滑鼠按鈕、
或是改變視窗大小時,Windows 都以訊息的方式通知程式。
我們在程式碼中使用 GetCursorPos()函數
3
來取得座標,其函式雛型如下。所以,當我們按下程式中“新局"、“電腦當紅方"、“電腦當黑方"、“貼
上盤面"這些按鈕時,我們再去點選其它棋軟這些按鈕的位置,函式會取得螢幕
上游標的座標儲存於相對應的變數之中。
2
Hook 是用來與作業系統掛勾進而攔截並處理某些訊息之用。
3
參考 MSDN 網站,網址:http://msdn.microsoft.com
BOOL GetCursorPos(
LPPOINT lpPoint );
當我們需要進行滑鼠點選工作時,我們可以使用下列程式片段來完成一次滑
鼠點選:
第二節 載 入 盤 面
現在我們要準備給測試棋軟幫我們思考走步的盤面,我們需要從現有的開局
庫取得盤面,然後存成棋軟可以接受的 FEN 字串。以 book21 為例,我們可以從
起始盤面的節點開始,藉由該盤面的走步,用深度優先的方式,一層一層的拜訪,
取出盤面資訊,逐步調整開局庫的走步,如圖 3-3 所示。
void click(POINT pt){
//pt 是我們要進行模擬滑鼠按下的螢幕位置 // 移到目標上面
SetCursorPos(pt.x, pt.y);
//模擬滑鼠按下
mouse_event(MOUSEEVENTF_LEFTDOWN,pt.x,pt.y,0,GetMessageExtraInfo());
//模擬滑鼠放開
mouse_event(MOUSEEVENTF_LEFTUP ,pt.x,pt.y,0,GetMessageExtraInfo());
return ; }
對於沒有開局庫可以產生盤面的人,我們建議可以採用 Thomas R. Linke 自
動建造開局庫的方法[2],建立起始的開局庫,再來藉由其它的象棋軟體的幫助,
修正其中可能的錯誤,建立更完整的開局庫。利用其它象棋軟體的幫助也可以改
善原來自動建造開局庫的缺點,改掉只使用單一搜尋程式的盲點,我們融合了更
多的象棋程式的搜尋,在比賽時會大大的降低脫譜的可能性,且下出正確走步的
機率會更大。
根節點
圖 3-3 開局庫節點拜訪順序
第三節 監 控 盤 面 變 化
這部份是引用自吳光哲學長“Chess Spy"的功能,當程式一開始時,我們
會出現一個視窗讓使用者可以去取得測試棋軟的棋盤大小及位置,以便取得要監
控的視窗位置,如圖 3-4 所示。
當 Chess Spy 取得測試棋軟大小及位置後,它會將棋盤切割成九十個監控方
格,每一格代表棋盤上的一個位置,如圖 3-5 所示。
圖 3-4 Chess Spy 抓取棋盤畫面
為了避免系統負荷太大,在取得棋盤像素時,Chess Spy 並不是完全的儲存
下來,而是在棋盤上每個小方格,僅取 9 個監控點。使用 GetPixel 函式
4
來記錄監控點的 RGB 值。
4
參考 MSDN 網站,網址:http://msdn.microsoft.com
圖 3-5 Chess Spy 切割棋盤圖
COLORREF GetPixel(
HDC hdc, // handle to DC int nXPos, // x-coordinate of pixel int nYPos // y-coordinate of pixel )
為了計算方便,我們會宣告一個陣列,將九十個方格的左上角座標位置記錄
下來,如下。
當我們要取得整個盤面的狀態時,以下面程式碼來完成記錄這九十個方格的
資料。
所以當棋盤上有某個棋子移動的時候,我們可以藉由畫面的變化,得知棋盤
上哪兩個位置有變動,再配合盤面的 FEN 字串,我們就可以知道是哪一個子力
移動了。如圖 3-6 所示,由起始盤面,走了『炮二平五』這步,我們在掃描盤面
時,會發現編號 67 和編號 70 這兩個位置有發生變動。配合 FEN 字串,我們進
行檢查,發現是由位置 70 移動到位置 67 的。
int boardxy[90][2];
//其中 boardxy[n][0] 記錄第 n 個方格左上角的 x 座標 //其中 boardxy[n][1] 記錄第 n 個方格左上角的 y 座標
//我們用一陣列 c 來儲存此時的盤面狀態 //N=9 為每個格子的監控點數目
//COLORREF c[90][N]
for(i=0;i<90;i++) {
for(j=-N/2;j<=N/2;j++)
c[i][j+N/2]=GetPixel(hdc, boardxy[i][0]+j,boardxy[i][1]+j);
}
圖 3-6 盤面變化的情況