/// <summary> /// 处理position命令 /// 空闲状态的指令。 /// position {fen <FEN串> | startpos} [moves <后续着法列表>] /// 设置“内置棋盘”的局面,用fen来指定FEN格式串,moves后面跟的是随后走过的着法,例如: /// position fen rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - 0 1 moves h2e2 h9g7 /// FEN格式串的写法参阅《中国象棋电脑应用规范(三):FEN文件格式》一文。 /// moves选项是为了防止引擎着出长打着法而设的,UCCI界面传递局面时, /// 通常fen选项为最后一个吃过子的局面(或开始局面),然后moves选项列出该局面到当前局面的所有着法。 /// startpos表示开始局面,它等价于fen rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - 0 1 /// /// 执行position命令后,会设置引擎的fen和moves变量 /// </summary> /// <param name="cmd">Ucci命令</param> private void ExecutePositionCommand(UcciCommand cmd) { // 先看看是不是有moves字符串 const string STR_MOVES = "moves "; int idxMoves = cmd.Paras.IndexOf(STR_MOVES); string strFen = cmd.Paras; if (idxMoves >= 0) { this.moves = cmd.Paras.Substring(idxMoves + STR_MOVES.Length); strFen = cmd.Paras.Substring(0, idxMoves - 1); } else { this.moves = ""; } // 此时可能用startpos来表示开始局面 strFen = strFen.Replace("startpos", "fen rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - 0 1"); const string STR_FEN = "fen "; int start = strFen.IndexOf(STR_FEN); if (start < 0) { this.fen = ""; // position后面的FEN串错误 } this.fen = strFen.Substring(start + STR_FEN.Length); }
private void _ponderEngine_OutputDataReceived(object sender, DataReceivedEventArgs e) { string m_UcciInfo = e.Data; if (!string.IsNullOrEmpty(m_UcciInfo)) { m_LogHandler.logging(e.Data); m_engineInfos.Append(m_UcciInfo + "\n"); UcciCommand cmd = UcciCommand.Parse(m_UcciInfo); if (cmd.Name == "ucciok") { EngineIsOK(this, null); } else if (cmd.Name == "bestmove") { string bestmove = cmd.Paras.Substring(0, 4); BestMoveReceivedEventArgs newEvent = new BestMoveReceivedEventArgs(bestmove); BestMoveReceived(this, newEvent); } else if (cmd.Name == "position") { } } }
/// <summary> /// 处理setoption命令。 /// 空闲状态的指令。 /// /// setoption <选项> [<值>] /// 设置引擎参数,这些参数都应该是option反馈的参数,例如: /// setoption usebook false,不让引擎使用开局库; /// setoption selectivity large,把选择性设成最大; /// setoption style risky,指定冒进的走棋风格; /// setoption loadbook,初始化开局库。 /// 但是,设置option反馈没有给出的参数,并不会出错。 /// 例如UCCI界面《象棋巫师》就从不识别option反馈,而直接根据用户的设置发送setoption指令。 /// </summary> /// <param name="cmd">Ucci命令</param> private void ExecuteSetoptionCommand(UcciCommand cmd) { string[] options = cmd.Paras.Split(' '); if (options[0] == "debug") { if (!bool.TryParse(options[1], out this.isDebug)) { this.isDebug = false; } PrintDebugInfo("debug模式:" + isDebug); } else if (options[0] == "usebook") { // 是否用开局库 // setoption usebook true // setoption usebook false if (!bool.TryParse(options[1], out this.useBook)) { this.useBook = false; } PrintDebugInfo("usebook " + useBook); } else if (options[0] == "bookfiles") { // 设置开局库文件名,当前只支持一个开局库文件 openingBookFile = options[1]; PrintDebugInfo("开局库文件:" + openingBookFile); if (!File.Exists(openingBookFile)) { useBook = false; PrintDebugInfo("开局库文件不存在: " + openingBookFile); PrintDebugInfo("setoption usebook false"); } } else if (options[0] == "newgame") { // TODO: 在用置换表时,要将置换表清空 } }
public EngineStatus ExecuteCommand(string strCommand) { PrintDebugInfo("receive command:" + strCommand); UcciCommand cmd = UcciCommand.Parse(strCommand); if (status == EngineStatus.Boot) { if (cmd.Name == "ucci") { //引导状态的反馈。显示引擎的版本号、版权、作者和授权用户,例如: //id name ElephantEye 1.6 Beta,说明引擎的版本号 //id copyright 2004-2006 www.xqbase.com,说明引擎的版权 //id author Morning Yellow,说明引擎的作者 //id user ElephantEye Test Team,说明引擎授权给用户 info = "id name PonderEngine " + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version; m_LogHandler.logging(info); Output.WriteLine(info); info = "id copyright 2013 by slb"; m_LogHandler.logging(info); Output.WriteLine(info); info = "id author shen longbin"; m_LogHandler.logging(info); Output.WriteLine(info); info = "id user slb test"; m_LogHandler.logging(info); Output.WriteLine(info); // TODO: 给出支持option选项 // option <选项> type <类型> [min <最小值>] [max <最大值>] // [var <可选项> [var <可选项> [...]]] [default <默认值>] //引导状态的反馈。显示引擎所支持的选项,<option>指选项的名称, //选项的类型是label(标签,非选项)、button(指令)、check(是或非)、combo(多选项)、 //spin(整数)、string(字符串)中的一种。 //option usemillisec type check default false //option usebook type check default true //引导状态的反馈,此后引擎进入空闲状态。 info = "option usebook type check default true"; m_LogHandler.logging(info); Output.WriteLine(info); info = "option bookfiles type string default " + "opening.ob"; m_LogHandler.logging(info); Output.WriteLine(info); // 这里完成一些初始化的任务 transpositionTable = new TranspositionTable(); info = "ucciok"; m_LogHandler.logging(info); Output.WriteLine(info); status = EngineStatus.Idle; } else { //如果引导状态下UCCI引擎收到其他指令,则可以退出。 info = "quited directly"; m_LogHandler.logging(info); Output.WriteLine(info); return(EngineStatus.Quit); } } else if (status == EngineStatus.Idle) { //isready //空闲状态和思考状态的指令。检测引擎是否处于就绪状态,其反馈总是readyok,该指令仅仅用来检测引擎是否能够正常接收指令。 if (cmd.Name == "isready") { //空闲状态和思考状态的反馈。表明引擎处于就绪状态(可正常接收指令)。 Output.WriteLine("readyok"); } else if (cmd.Name == "setoption") { ExecuteSetoptionCommand(cmd); } else if (cmd.Name == "position") { ExecutePositionCommand(cmd); } else if (cmd.Name == "banmoves") { // banmoves <禁止着法列表> // 空闲状态的指令。为当前局面设置禁手,以解决引擎无法处理的长打问题。当出现长打局面时, // 棋手可以操控界面向引擎发出禁手指令。例如: // position fen 1r2kab1r/2c1a4/n1c1b1n2/4p2N1/p1p6/1C4P2/P1P1P4/2N1B3C/4A4/1RBAK2R1 w - - 0 1 moves h6i4 i9h9 i4h6 h9i9 // banmoves h6i4 // 本例取自《象棋竞赛规则》(1999年版)棋例图三,由于大多数象棋引擎无法识别红方这种方式的长捉, // 所以在采用中国象棋协会的比赛规则时,遇到这种局面就必须给引擎发出禁手指令。 // 下一次发送position指令后,前面设置过的禁止着法就取消了,需要重新设置禁止着法。 // 目前UCCI界面《象棋巫师》不识别长打禁手,所以不会向引擎发送banmoves指令。 /****************** 估计永远也不会实现了 ************************/ } else if (cmd.Name == "go") { // go [ponder | draw] <思考模式> // 空闲状态的指令,此后引擎进入思考状态。让引擎根据position指令设定的棋盘来思考, // 各选项为思考方式,有三种模式可供选择: // (1) depth <深度> | infinite: // 限定搜索深度,infinite表示无限制思考(直到找到杀棋或用stop指令中止)。 // 如果深度设定为0,那么引擎可以只列出当前局面静态评价的分数,并且反馈nobestmove。 // (2) nodes <结点数>:限定搜索结点数。 // (3) time <时间> [movestogo <剩余步数> | increment <每步加时>] // [opptime <对方时间> [oppmovestogo <对方剩余步数> | oppincrement <对方每步加时>]] // 限定时间,时间单位是秒(默认)或毫秒(启用毫秒制时),movestogo适用于时段制,increment适用于加时制。 // opptime、oppmovestogo和oppincrement可以让界面把对方的用时情况告诉引擎。 // 例如:go time 120 movestogo 120 opptime 120 oppmovestogo 120 // ------- // 如果指定ponder选项,则引擎思考时时钟不走,直到接受到ponderhit指令后才计时,该选项用于后台思考, // 它只对限定时间的思考模式有效。 // 指定draw选项表示向引擎提和,引擎以bestmove提供的选项作为反馈,参阅bestmove指令。 // 注意:ponder和draw选项不能同时使用,如果界面向正在后台思考中的引擎求和, // 则使用ponderhit draw指令。 status = EngineStatus.Thinking; Board board = new Board(fen); if (moves != null && moves != "") { board.MakeMoves(moves); } if (useBook) { Move bestInOpening = OpeningBook.BestMove(openingBookFile, board); if (bestInOpening.From != 0) { PrintDebugInfo("命中开局库!"); Output.WriteLine("bestmove " + bestInOpening); status = EngineStatus.Idle; return(status); } } int idxDepth = cmd.Paras.IndexOf("depth"); int searchDepth = 4; if (idxDepth >= 0) { searchDepth = int.Parse(cmd.Paras.Substring(idxDepth + 5)); } //SimpleSearch search = new SimpleSearch(board, this); //search.NegaAlphaBeta(0, searchDepth, Evaluator.MIN_EVAL_VALUE, Evaluator.MAX_EVAL_VALUE); SearchTT search = new SearchTT(board, transpositionTable, this); //search.NegaAlphaBetaTT_old(0, searchDepth, Evaluator.MIN_EVAL_VALUE, Evaluator.MAX_EVAL_VALUE); //search.StartPVS(searchDepth, Evaluator.MIN_EVAL_VALUE, Evaluator.MAX_EVAL_VALUE); search.IterativeDeepeningSearch(searchDepth); //search.StartAlphaBetaTT(searchDepth, Evaluator.MIN_EVAL_VALUE, Evaluator.MAX_EVAL_VALUE); /************* * bestmove <最佳着法> [ponder <后台思考的猜测着法>] [draw | resign] * 思考状态的反馈,此后引擎返回空闲状态。显示思考结果,即引擎认为在当前局面下的最佳着法, * 以及猜测在这个着法后对手会有怎样的应对(即后台思考的猜测着法)。 * 通常,最佳着法是思考路线(主要变例)中的第一个着法,而后台思考的猜测着法 * 则是第二个着法。 * 在对手尚未落子时,可以根据该着法来设定局面,并作后台思考。 * 当对手走出的着法和后台思考的猜测着法吻合时,称为“后台思考命中”。 * draw选项表示引擎提和或者接受界面向引擎发送的提和请求,参阅go draw和ponderhit draw指令。 * resign选项表示引擎认输。UCCI界面在人机对弈方式下,根据不同情况, * 可以对引擎的bestmove反馈中的draw和resign选项作出相应的处理: * (1) 如果用户提和,界面向引擎发出go draw或ponderhit draw指令,而引擎反馈带draw的bestmove, * 那么界面可终止对局并判议和; * (2) 如果用户没有提和,而引擎反馈带draw的bestmove,那么界面可向用户提和, * 用户接受提和则可终止对局并判议和; * (3) 如果引擎反馈带resign的bestmove,那么界面可终止对局并判引擎认输。 * 引擎应该根据当前局面的情况(由position指令给出),以及界面是否发送了带draw的go或ponderhit指令, * 来考虑是否反馈带draw或resign的bestmove。 *************/ //TODO: bestmove Move bestMove = search.BestMove; if (bestMove == null || bestMove.From == 0) { Output.WriteLine("bestmove resign"); } else { Output.WriteLine("bestmove " + bestMove); } status = EngineStatus.Idle; } else if (cmd.Name == "quit") { //接收到quit指令后的反馈"bye"。引擎完成了退出运转前的准备工作,通知界面, //引擎将在瞬间正常退出运转。界面收到该指令后,即可关闭输入输出通道。 info = "bye"; m_LogHandler.logging(info); Output.WriteLine(info); status = EngineStatus.Quit; } } else // Thinking状态时 { //isready //空闲状态和思考状态的指令。检测引擎是否处于就绪状态,其反馈总是readyok,该指令仅仅用来检测引擎是否能够正常接收指令。 if (cmd.Name == "isready") { //空闲状态和思考状态的反馈。表明引擎处于就绪状态(可正常接收指令)。 Output.WriteLine("readyok"); } else if (cmd.Name == "ponderhit") { // ponderhit [draw] // 思考状态的指令。告诉引擎后台思考命中,现在转入正常思考模式(引擎继续处于思考状态, // 此时go指令设定的时限开始起作用)。 // 指定draw选项表示向引擎提和,引擎以bestmove提供的选项作为反馈,参阅bestmove指令。 } else if (cmd.Name == "stop") { // stop // 思考状态的指令。中止引擎的思考。另外,后台思考没有命中时,就用该指令来中止思考,然后重新输入局面。 // 注意:发出该指令并不意味着引擎将立即回到空闲状态,而是要等到引擎反馈 // bestmove或nobestmove后才表示回到空闲状态,引擎应尽可能快地作出这样的反馈。 } else { } } return(status); }