現(xiàn)國(guó)際象棋引擎:從算法原理到工程實(shí)踐)
1. 項(xiàng)目概述為什么用C寫(xiě)一個(gè)國(guó)際象棋程序如果你對(duì)C有一定了解又想找一個(gè)能綜合鍛煉編程、算法和工程思維的項(xiàng)目自己動(dòng)手實(shí)現(xiàn)一個(gè)國(guó)際象棋程序是個(gè)絕佳的選擇。這聽(tīng)起來(lái)可能有點(diǎn)“復(fù)古”畢竟現(xiàn)在各種成熟的游戲引擎和AI庫(kù)唾手可得。但恰恰是這種“復(fù)古”能讓你觸及計(jì)算機(jī)科學(xué)中一些最經(jīng)典、最核心的問(wèn)題狀態(tài)空間搜索、評(píng)估函數(shù)設(shè)計(jì)、人機(jī)交互邏輯以及如何用高效的代碼管理復(fù)雜的游戲規(guī)則。我最初寫(xiě)這個(gè)程序是為了解決一個(gè)很實(shí)際的問(wèn)題如何向?qū)W生直觀地展示算法比如極小化極大算法在博弈中的威力。市面上雖然有很多開(kāi)源的國(guó)際象棋引擎但代碼庫(kù)往往龐大而復(fù)雜初學(xué)者很難理清頭緒。一個(gè)從零開(kāi)始、結(jié)構(gòu)清晰的C實(shí)現(xiàn)就像一份活生生的教案每一步棋的生成、每一次局面的評(píng)估、每一次搜索的剪枝都清晰可見(jiàn)。這個(gè)項(xiàng)目絕不僅僅是“又一個(gè)棋盤(pán)游戲”。它要求你系統(tǒng)地思考如何用面向?qū)ο蟮乃枷雭?lái)建模棋盤(pán)、棋子和走法如何設(shè)計(jì)一個(gè)既準(zhǔn)確又高效的走法生成器如何讓電腦“思考”從數(shù)百萬(wàn)種可能中選出最優(yōu)的一步在這個(gè)過(guò)程中你會(huì)深入接觸到位運(yùn)算優(yōu)化、搜索算法優(yōu)化如Alpha-Beta剪枝、以及啟發(fā)式評(píng)估函數(shù)的設(shè)計(jì)。最終你將得到一個(gè)可以運(yùn)行在命令行、擁有基礎(chǔ)AI對(duì)戰(zhàn)能力的完整程序。這不僅是編程能力的證明更是對(duì)邏輯思維和系統(tǒng)設(shè)計(jì)能力的一次全面錘煉。2. 核心架構(gòu)設(shè)計(jì)與模塊拆解一個(gè)可運(yùn)行、可擴(kuò)展的國(guó)際象棋程序其核心架構(gòu)可以清晰地劃分為幾個(gè)松耦合的模塊。清晰的模塊劃分是項(xiàng)目成功的關(guān)鍵它能讓代碼易于維護(hù)、調(diào)試和升級(jí)。2.1 數(shù)據(jù)模型層棋盤(pán)與棋子的抽象一切始于如何表示棋盤(pán)。最直觀的方法是使用一個(gè)8x8的二維數(shù)組比如Piece board[8][8]用不同的字符或枚舉值代表棋子如‘K‘ ’Q‘ ’R‘ ’B‘ ’N‘ ’P‘和對(duì)應(yīng)的小寫(xiě)字母代表黑方。這種方法易于理解但在生成走法和判斷局面時(shí)效率不高因?yàn)樾枰罅垦h(huán)遍歷。在實(shí)際的高性能引擎中位棋盤(pán)Bitboard是更優(yōu)的選擇。位棋盤(pán)用一個(gè)64位的整數(shù)在C中通常是uint64_t來(lái)表示棋盤(pán)上某個(gè)子集例如所有白兵、所有黑車、或者所有被占領(lǐng)的格子。每一位對(duì)應(yīng)棋盤(pán)上的一個(gè)格子通常a1是第0位h8是第63位。這種表示的巨大優(yōu)勢(shì)在于許多棋盤(pán)操作如判斷子力攻擊范圍、計(jì)算棋子移動(dòng)可以通過(guò)極其快速的位運(yùn)算與、或、非、移位來(lái)完成這比遍歷數(shù)組快幾個(gè)數(shù)量級(jí)。對(duì)于初學(xué)者可以從二維數(shù)組開(kāi)始實(shí)現(xiàn)以理解邏輯但在規(guī)劃架構(gòu)時(shí)必須為將來(lái)向位棋盤(pán)遷移留出接口。棋子的設(shè)計(jì)需要一個(gè)Piece類或結(jié)構(gòu)體至少包含顏色白/黑、類型王、后、車、象、馬、兵以及位置信息。走法則可以用一個(gè)Move類來(lái)表示包含起始位置、目標(biāo)位置、移動(dòng)的棋子類型以及特殊標(biāo)志如是否為吃子、升變、王車易位等。2.2 規(guī)則引擎層走法生成與驗(yàn)證這是整個(gè)程序中最復(fù)雜、最需要嚴(yán)謹(jǐn)對(duì)待的部分。走法生成器必須完備且正確即能生成當(dāng)前局面下所有符合國(guó)際象棋規(guī)則的合法走法且不能生成任何非法走法?;A(chǔ)走法生成需要為每種棋子類型編寫(xiě)移動(dòng)規(guī)則。車的直線移動(dòng)、象的斜線移動(dòng)、后的直線加斜線、馬的“日”字跳、兵的特殊規(guī)則前進(jìn)一格、起始兩格、斜吃、過(guò)路兵以及王的移動(dòng)和王車易位。這里需要注意生成的走法在此時(shí)還只是“偽合法走法”即符合該棋子基本移動(dòng)規(guī)則但尚未考慮是否會(huì)導(dǎo)致己方王被將軍。合法性驗(yàn)證這是關(guān)鍵。生成所有偽合法走法后必須對(duì)每一個(gè)走法進(jìn)行模擬執(zhí)行然后檢查執(zhí)行后己方王是否處于被攻擊的狀態(tài)即“將軍”狀態(tài)。如果處于將軍狀態(tài)則該走法是非法的必須剔除。這一步計(jì)算開(kāi)銷很大因此催生了各種優(yōu)化技巧比如“將軍探測(cè)”時(shí)只檢查攻擊王的棋子射線上的格子。注意王車易位的合法性條件尤其繁瑣需要檢查王和車從未移動(dòng)過(guò)、王和車之間的格子為空、王沒(méi)有被將軍、王經(jīng)過(guò)和到達(dá)的格子不被對(duì)方攻擊。務(wù)必單獨(dú)編寫(xiě)函數(shù)仔細(xì)處理。2.3 決策大腦層搜索算法與局面評(píng)估這是賦予程序“智能”的部分。核心是搜索算法和評(píng)估函數(shù)。搜索算法最基礎(chǔ)的是極小化極大算法Minimax。它模擬雙方輪流走棋假設(shè)對(duì)方總是做出對(duì)己方最不利的應(yīng)對(duì)極小化我方收益而我方則選擇對(duì)自己最有利的走法最大化我方收益。算法通過(guò)遞歸遍歷一定深度的博弈樹(shù)來(lái)實(shí)現(xiàn)。然而純Minimax搜索的節(jié)點(diǎn)數(shù)隨深度指數(shù)級(jí)增長(zhǎng)完全不切實(shí)際。因此必須引入Alpha-Beta剪枝。它在Minimax的基礎(chǔ)上通過(guò)傳遞兩個(gè)值A(chǔ)lpha和Beta來(lái)記錄當(dāng)前路徑的收益上下界從而可以果斷剪掉那些不可能影響最終決策的分支在不影響結(jié)果的前提下極大提升搜索效率。這是博弈程序算法的基石。評(píng)估函數(shù)用于給一個(gè)靜止的棋盤(pán)局面打一個(gè)分?jǐn)?shù)分?jǐn)?shù)越高對(duì)白方越有利越低對(duì)黑方越有利。最簡(jiǎn)單的評(píng)估函數(shù)是子力價(jià)值王無(wú)限大、后9、車5、象3、馬3、兵1。但僅此遠(yuǎn)遠(yuǎn)不夠。好的評(píng)估函數(shù)還包括位置價(jià)值比如馬在中心比在邊角好、兵形結(jié)構(gòu)疊兵、孤兵是弱點(diǎn)、王的安全度、子力活動(dòng)性等。評(píng)估函數(shù)的設(shè)計(jì)是調(diào)整AI棋風(fēng)激進(jìn)或穩(wěn)健的主要手段。2.4 交互層用戶界面與協(xié)議最后我們需要一個(gè)方式與程序交互。對(duì)于初學(xué)者一個(gè)命令行界面CLI是最簡(jiǎn)單直接的選擇??梢燥@示ASCII字符畫(huà)的棋盤(pán)通過(guò)輸入坐標(biāo)如“e2e4”來(lái)走棋。同時(shí)為了實(shí)現(xiàn)更強(qiáng)大的功能比如與圖形界面前端連接支持通用象棋協(xié)議UCI是一個(gè)專業(yè)的選擇。UCI協(xié)議規(guī)定了引擎與圖形界面之間通過(guò)標(biāo)準(zhǔn)輸入輸出進(jìn)行通信的指令格式如“position startpos moves e2e4”、“go depth 6”。實(shí)現(xiàn)UCI協(xié)議能讓你的引擎接入像Arena、Cute Chess這樣的標(biāo)準(zhǔn)象棋GUI可玩性和實(shí)用性大大增強(qiáng)。3. 核心模塊的C實(shí)現(xiàn)細(xì)節(jié)理論架構(gòu)清晰后我們進(jìn)入具體的C實(shí)現(xiàn)環(huán)節(jié)。這里會(huì)涉及很多工程上的權(quán)衡和細(xì)節(jié)處理。3.1 棋盤(pán)表示類的實(shí)現(xiàn)我們從基于二維數(shù)組的棋盤(pán)開(kāi)始因?yàn)樗庇^。定義一個(gè)Board類。#include array #include string #include vector enum class PieceType { None, King, Queen, Rook, Bishop, Knight, Pawn }; enum class Color { White, Black }; struct Piece { PieceType type PieceType::None; Color color Color::White; // 可以添加更多信息如是否移動(dòng)過(guò)用于王車易位判斷 bool hasMoved false; }; class Board { private: // 8x8棋盤(pán)board[rank][file] rank是行(0-7)file是列(0-7) std::arraystd::arrayPiece, 8, 8 squares; Color sideToMove Color::White; // 當(dāng)前該誰(shuí)走 // 記錄王車易位權(quán)利、過(guò)路兵目標(biāo)格等狀態(tài)信息 bool whiteKingSideCastle true; bool whiteQueenSideCastle true; bool blackKingSideCastle true; bool blackQueenSideCastle true; int enPassantTarget -1; // 記錄過(guò)路兵可吃掉的兵身后的格子索引 public: Board(); void initializeStandardPosition(); // 初始化標(biāo)準(zhǔn)起始局面 Piece getPiece(int rank, int file) const; bool makeMove(const Move move); // 執(zhí)行一步走法返回是否成功 bool isSquareAttacked(int rank, int file, Color byColor) const; // 核心函數(shù)判斷某格是否被某方攻擊 std::vectorMove generateLegalMoves() const; // 生成所有合法走法 // ... 其他輔助函數(shù) };initializeStandardPosition函數(shù)負(fù)責(zé)擺好初始棋子。isSquareAttacked函數(shù)是合法性驗(yàn)證的基石它需要遍歷對(duì)方所有棋子根據(jù)其類型判斷是否能攻擊到目標(biāo)格。實(shí)現(xiàn)這個(gè)函數(shù)時(shí)對(duì)每種棋子都要小心處理其攻擊規(guī)則特別是兵的攻擊方向白兵斜向上吃黑兵斜向下吃。3.2 走法生成與驗(yàn)證的實(shí)現(xiàn)Move類需要包含足夠的信息。class Move { public: int fromRank, fromFile; // 起點(diǎn)坐標(biāo) int toRank, toFile; // 終點(diǎn)坐標(biāo) PieceType pieceMoved; PieceType pieceCaptured PieceType::None; // 被吃掉的棋子 PieceType promotion PieceType::None; // 升變?yōu)槭裁雌遄?bool isCastle false; // 是否是王車易位 bool isEnPassant false; // 是否是過(guò)路兵 // 重載運(yùn)算符便于比較 bool operator(const Move other) const; };在Board::generateLegalMoves()中邏輯分兩步生成偽合法走法遍歷己方所有棋子根據(jù)其類型和位置生成所有符合基本規(guī)則的終點(diǎn)格。注意處理兵的升變兵到底線可變?yōu)楹蟆④?、象、馬。過(guò)濾合法走法對(duì)每一個(gè)偽合法走法調(diào)用Board::makeMove嘗試執(zhí)行在臨時(shí)副本上操作然后調(diào)用isInCheck()函數(shù)通過(guò)isSquareAttacked檢查己方王的位置判斷是否導(dǎo)致己方被將軍。如果沒(méi)有則加入合法走法列表。這里有一個(gè)重要的性能優(yōu)化點(diǎn)在makeMove和生成走法時(shí)要維護(hù)一個(gè)“棋盤(pán)哈希值”Zobrist Hash。這是一個(gè)幾乎唯一的、代表當(dāng)前局面的64位整數(shù)通過(guò)異或操作隨走法快速更新。它可以用于檢測(cè)重復(fù)局面在搜索算法中實(shí)現(xiàn)置換表Transposition Table這是提升搜索深度和速度的關(guān)鍵高級(jí)技術(shù)。3.3 搜索算法與評(píng)估函數(shù)的實(shí)現(xiàn)實(shí)現(xiàn)一個(gè)帶Alpha-Beta剪枝的Negamax框架Negamax是Minimax的一種簡(jiǎn)化寫(xiě)法統(tǒng)一用負(fù)值表示對(duì)方分?jǐn)?shù)。// 評(píng)估函數(shù) int Board::evaluate() const { int score 0; // 1. 子力價(jià)值 for (int r 0; r 8; r) { for (int f 0; f 8; f) { Piece p getPiece(r, f); if (p.type ! PieceType::None) { int pieceValue getPieceValue(p.type); // 獲取子力基礎(chǔ)值 // 根據(jù)顏色加或減 score (p.color Color::White) ? pieceValue : -pieceValue; // 2. 可以在這里添加位置價(jià)值表查詢 // score (p.color White) ? positionTable[p.type][r][f] : -positionTable[p.type][r][f]; } } } // 3. 這里可以添加更多評(píng)估項(xiàng)雙象優(yōu)勢(shì)、兵形等 // score evaluatePawnStructure(); // score evaluateMobility(); // 子力活動(dòng)性 return score; } // 帶Alpha-Beta剪枝的Negamax搜索 int negamax(Board board, int depth, int alpha, int beta) { if (depth 0) { // 到達(dá)葉子節(jié)點(diǎn)返回局面評(píng)估值 // 注意Negamax中總是從當(dāng)前走棋方的視角評(píng)估 return board.evaluate() * (board.sideToMove Color::White ? 1 : -1); } std::vectorMove moves board.generateLegalMoves(); if (moves.empty()) { // 無(wú)棋可走判斷是將軍輸還是逼和 if (board.isInCheck(board.sideToMove)) { return -10000 depth; // 被將死返回負(fù)無(wú)窮大這里用一個(gè)大負(fù)數(shù)深度使更快的將死更好 } else { return 0; // 逼和 } } // 對(duì)走法進(jìn)行排序能極大提升Alpha-Beta剪枝效率 // 通常按“吃子價(jià)值-移動(dòng)棋子價(jià)值”的差值降序排序好的走法先搜索。 orderMoves(moves, board); int bestValue -100000; // 負(fù)無(wú)窮 for (const Move move : moves) { board.makeMove(move); int value -negamax(board, depth - 1, -beta, -alpha); // 關(guān)鍵遞歸時(shí)取負(fù)并交換alpha/beta角色 board.unmakeMove(move); // 必須撤銷走法 if (value bestValue) { bestValue value; } if (value alpha) { alpha value; } if (alpha beta) { break; // Beta剪枝發(fā)生 } } return bestValue; } // 根節(jié)點(diǎn)調(diào)用尋找最佳走法 Move findBestMove(Board board, int maxDepth) { std::vectorMove moves board.generateLegalMoves(); if (moves.empty()) return Move(); // 返回?zé)o效走法 Move bestMove; int bestValue -100000; int alpha -100000; int beta 100000; for (const Move move : moves) { board.makeMove(move); int value -negamax(board, maxDepth - 1, -beta, -alpha); board.unmakeMove(move); if (value bestValue) { bestValue value; bestMove move; } if (value alpha) { alpha value; } } return bestMove; }幾個(gè)關(guān)鍵點(diǎn)走法排序在negamax函數(shù)中對(duì)moves進(jìn)行排序至關(guān)重要。好的走法如吃后先搜索能更早地引發(fā)剪枝大幅減少搜索節(jié)點(diǎn)。這是提升Alpha-Beta效率最立竿見(jiàn)影的方法。撤銷走法Unmake Move遞歸調(diào)用后必須精確地撤銷棋盤(pán)狀態(tài)包括棋子位置、易位權(quán)利、過(guò)路兵目標(biāo)格等。實(shí)現(xiàn)一個(gè)unmakeMove函數(shù)通常需要Move對(duì)象記錄足夠的信息或者使用“?!眮?lái)保存歷史狀態(tài)。評(píng)估函數(shù)視角在Negamax中評(píng)估函數(shù)應(yīng)始終從當(dāng)前走棋方的視角返回分?jǐn)?shù)。我們?cè)谌~子節(jié)點(diǎn)調(diào)用board.evaluate()然后根據(jù)當(dāng)前走棋方乘以1或-1。更常見(jiàn)的做法是在evaluate()內(nèi)部就處理好返回一個(gè)對(duì)白方有利為正的分?jǐn)?shù)然后在Negamax中根據(jù)輪到誰(shuí)走來(lái)決定正負(fù)號(hào)。4. 性能優(yōu)化與高級(jí)技巧當(dāng)基礎(chǔ)版本運(yùn)行起來(lái)后你會(huì)立刻遇到性能瓶頸。搜索深度可能只能達(dá)到4-5層思考速度很慢。以下是一些必須考慮的優(yōu)化方向。4.1 置換表Transposition Table這是最重要的優(yōu)化之一。在搜索樹(shù)中不同的走法順序可能到達(dá)相同的棋盤(pán)局面稱為“置換局面”。置換表就是一個(gè)緩存存儲(chǔ)已經(jīng)搜索過(guò)的局面的結(jié)果分?jǐn)?shù)、最佳走法、搜索深度等。當(dāng)再次遇到相同局面時(shí)如果緩存中的搜索深度足夠就可以直接使用緩存的結(jié)果避免重復(fù)搜索。實(shí)現(xiàn)置換表通常需要一個(gè)哈希表鍵是局面的Zobrist哈希值值是一個(gè)包含分?jǐn)?shù)、深度、節(jié)點(diǎn)類型精確值、上界、下界和最佳走法的結(jié)構(gòu)。在negamax開(kāi)始時(shí)先查詢置換表。在negamax結(jié)束時(shí)將搜索結(jié)果存入置換表。4.2 走法排序策略更智能的走法排序能引發(fā)更多剪枝。吃子排序使用“MVV-LVA”Most Valuable Victim - Least Valuable Aggressor原則。優(yōu)先嘗試吃價(jià)值高的棋子后并且用價(jià)值低的棋子去吃用兵吃后。殺手啟發(fā)Killer Heuristic記錄在搜索樹(shù)其他分支中導(dǎo)致剪枝的走法“殺手走法”在當(dāng)前節(jié)點(diǎn)也優(yōu)先嘗試這些走法。歷史啟發(fā)History Heuristic維護(hù)一個(gè)全局的歷史表記錄每個(gè)走法從哪到哪在歷史上導(dǎo)致剪枝的良好程度優(yōu)先嘗試歷史得分高的走法。迭代加深I(lǐng)terative Deepening不從最大深度開(kāi)始搜索而是從深度1開(kāi)始逐步加深。這樣做的好處是每次加深搜索都可以利用上一次淺搜索的結(jié)果來(lái)優(yōu)化當(dāng)前深度的走法排序并且可以在時(shí)間限制內(nèi)隨時(shí)返回當(dāng)前最深度的最佳結(jié)果。4.3 開(kāi)局庫(kù)與殘局庫(kù)對(duì)于開(kāi)局前10-15步直接使用龐大的開(kāi)局庫(kù)Book來(lái)查詢經(jīng)過(guò)千錘百煉的譜著可以節(jié)省大量計(jì)算時(shí)間并保證開(kāi)局質(zhì)量。殘局庫(kù)Endgame Tablebase則存儲(chǔ)了子力極少的殘局如王兵對(duì)王的精確結(jié)果可以引導(dǎo)引擎走向必勝或必和局面。對(duì)于個(gè)人項(xiàng)目集成一個(gè)簡(jiǎn)單的開(kāi)局庫(kù)文件如PGN格式解析是可行的第一步。5. 常見(jiàn)問(wèn)題、調(diào)試技巧與心得在開(kāi)發(fā)過(guò)程中你一定會(huì)遇到各種詭異的問(wèn)題。以下是一些常見(jiàn)坑點(diǎn)和解決思路。5.1 走法生成錯(cuò)誤這是最頭疼的問(wèn)題。表現(xiàn)可能是AI走出自殺性的送王棋或者拒絕進(jìn)行合法的王車易位。調(diào)試方法編寫(xiě)一個(gè)“每步驗(yàn)證”模式。在AI每走一步前打印出它生成的所有合法走法列表。人工檢查是否有遺漏或多余。重點(diǎn)關(guān)注兵的升變、王車易位和過(guò)路兵。單元測(cè)試為走法生成函數(shù)編寫(xiě)單元測(cè)試。針對(duì)特定局面如各種將軍、逼和、易位條件滿足/不滿足的局面驗(yàn)證生成的走法列表是否與已知結(jié)果一致??梢允褂靡恍┰诰€國(guó)際象棋棋盤(pán)工具來(lái)輔助驗(yàn)證。isSquareAttacked函數(shù)這個(gè)函數(shù)的正確性是整個(gè)合法走法驗(yàn)證的基石。務(wù)必單獨(dú)、徹底地測(cè)試它。創(chuàng)建一個(gè)測(cè)試手動(dòng)擺放棋子驗(yàn)證它對(duì)每個(gè)格子是否被攻擊的判斷是否正確。5.2 搜索算法陷入死循環(huán)或結(jié)果荒謬可能原因是遞歸沒(méi)有正確終止或評(píng)估函數(shù)值域不合理。深度限制確保遞歸深度depth在每次遞歸時(shí)遞減并在為0時(shí)正確返回評(píng)估值。評(píng)估值范圍確保評(píng)估函數(shù)不會(huì)返回極端大的值除了將死分?jǐn)?shù)否則可能干擾Alpha-Beta的邏輯。將死分?jǐn)?shù)比如10000需要足夠大但也要避免溢出。走法撤銷最隱蔽的錯(cuò)誤之一。如果unmakeMove沒(méi)有完全、精確地恢復(fù)棋盤(pán)狀態(tài)特別是易位權(quán)利、過(guò)路兵目標(biāo)格這些“狀態(tài)位”會(huì)導(dǎo)致后續(xù)搜索基于錯(cuò)誤的局面進(jìn)行結(jié)果完全不可預(yù)測(cè)。建議實(shí)現(xiàn)一個(gè)狀態(tài)歷史棧每次makeMove時(shí)將改變前的關(guān)鍵狀態(tài)壓棧unmakeMove時(shí)彈?;謴?fù)。5.3 性能瓶頸定位程序跑得太慢深度上不去。性能剖析使用性能分析工具如gprof、Valgrind的Callgrind、Visual Studio Profiler。你會(huì)發(fā)現(xiàn)絕大部分時(shí)間都花在generateLegalMoves和isSquareAttacked上。這證實(shí)了轉(zhuǎn)向位棋盤(pán)和預(yù)計(jì)算攻擊表的必要性。預(yù)計(jì)算很多信息可以提前計(jì)算好。例如可以預(yù)計(jì)算每個(gè)棋子在每個(gè)格子上所有可能的移動(dòng)目標(biāo)位圖對(duì)于馬、王、兵。車的直線移動(dòng)和象的斜線移動(dòng)雖然目標(biāo)格依賴棋盤(pán)阻擋但可以預(yù)計(jì)算“射線掩碼”。這能極大減少運(yùn)行時(shí)計(jì)算量。5.4 個(gè)人實(shí)操心得循序漸進(jìn)不要一開(kāi)始就追求完美先從最簡(jiǎn)單的二維數(shù)組、無(wú)AI、純手動(dòng)對(duì)戰(zhàn)開(kāi)始。確保棋盤(pán)顯示、走棋輸入、基本規(guī)則正確。然后加入走法生成和合法性驗(yàn)證。最后才實(shí)現(xiàn)搜索AI。每完成一個(gè)階段都進(jìn)行充分測(cè)試。測(cè)試驅(qū)動(dòng)多寫(xiě)測(cè)試代碼。特別是針對(duì)國(guó)際象棋的特殊規(guī)則過(guò)路兵、升變、易位、逼和、長(zhǎng)將構(gòu)造特定測(cè)試局面驗(yàn)證你的程序行為是否正確。版本控制使用Git。在實(shí)現(xiàn)位棋盤(pán)、置換表等重大重構(gòu)前確保有一個(gè)可以回退的穩(wěn)定版本。參考優(yōu)秀開(kāi)源項(xiàng)目不要閉門(mén)造車。學(xué)習(xí)像Stockfish、Glaurung等開(kāi)源引擎的代碼注意它們非常復(fù)雜。你可以重點(diǎn)看它們?nèi)绾谓M織代碼結(jié)構(gòu)而不是一開(kāi)始就深究所有優(yōu)化細(xì)節(jié)??炊粋€(gè)簡(jiǎn)單的引擎如“Sunfish”P(pán)ython實(shí)現(xiàn)的架構(gòu)對(duì)理解整體流程也大有裨益。耐心與興趣這是一個(gè)涉及面很廣的項(xiàng)目調(diào)試過(guò)程可能枯燥。但當(dāng)你的AI第一次走出一步像樣的棋或者你成功優(yōu)化讓搜索深度增加了一層時(shí)帶來(lái)的成就感是巨大的。把它當(dāng)作一個(gè)長(zhǎng)期的學(xué)習(xí)項(xiàng)目享受從零構(gòu)建一個(gè)復(fù)雜系統(tǒng)的過(guò)程。