/// <summary> /// 在棋盘内查找三个兵或卒出现在同一列时的列号 /// 在纵列表示法中会出现“中兵平二”这样的记录 /// 要全盘查找哪一列上有3个兵,才可以准确定位这个兵 /// </summary> /// <param name="board"></param> /// <param name="piece"></param> /// <returns>找到3兵或卒在同一列时,返回0-8的列号;否则,返回-1</returns> public static int Find3PawnFileNum(Board board, int piece) { // 这里用了一个比较懒的办法,全部找一遍,统计每一列上兵的出现个数 // 哪一列上兵出现的次数最多,就返回哪个列号了 int countMax = 0; int file = -1; for (int i = 0; i < 9; i++) { int numFound = 0; for (int rank = 0; rank < 10; rank++) { int pos = BoardUtil.Pos(i, rank); if (board.pieceData[pos] == piece) { numFound++; } if (numFound > countMax) { file = i; countMax = numFound; } } } return(file); }
/// <summary> /// 用字符串表示的着法 /// </summary> /// <param name="strMove">例如:"a2c2"</param> /// <returns></returns> public static Move CreateMoveFromString(Board board, string strMove) { int from = BoardUtil.Pos(strMove.Substring(0, 2)); int to = BoardUtil.Pos(strMove.Substring(2, 2)); return(board.CreateMove(from, to)); }
/// <summary> /// 根据行号(0-8)、列号(0-9)得到内部的位置编号 /// </summary> /// <param name="file">列号,0-8</param> /// <param name="rank">行号,0-9</param> /// <returns>返回内部的编号,从0-238,如果不满足条件,则抛出异常</returns> public static int Pos(int file, int rank) { if (file >= 0 && file < 9 && rank >= 0 && rank < 10) { return(BoardUtil.Pos(rank * 9 + file)); } throw new Exception("行号、列号越界"); }
public void MakeMoves(string strMoves) { string[] s = strMoves.Split(' '); foreach (string move in s) { MakeMove(BoardUtil.CreateMoveFromString(this, move)); } }
/// <summary> /// 在棋盘的某一列上查找某个棋子的准确位置,从上边(黑方)到下边(红方)的方向搜索 /// </summary> /// <param name="board">棋盘</param> /// <param name="piece">棋子编号</param> /// <param name="file">列号</param> /// <returns>返回找到的棋子位置的编号,找不到时返回0</returns> public static int FindPositionFromBlackToRed(Board board, int piece, int file) { for (int rank = 0; rank <= 9; rank++) { int pos = BoardUtil.Pos(file, rank); if (board.pieceData[pos] == piece) { return(pos); } } return(0); }
/// <summary> /// 在棋盘的某一列上查找某个棋子的准确位置,从下边(红方)到上边(黑方)搜索 /// </summary> /// <param name="board">棋盘</param> /// <param name="piece">棋子编号</param> /// <returns>返回找到的棋子位置的编号,找不到时返回0</returns> public static int FindPositionFromRedToBlack(Board board, int piece) { for (int file = 0; file <= 8; file++) { for (int rank = 9; rank >= 0; rank--) { int pos = BoardUtil.Pos(file, rank); if (board.pieceData[pos] == piece) { return(pos); } } } return(0); }
/// <summary> /// 根据FEN字符串初始化棋盘 /// http://chessprogramming.wikispaces.com/Forsyth-Edwards+Notation /// FEN表示法从第9行到第0行扫描棋子,在每一行中,从第0列(第A列)到第8列(第I列)扫描棋子,每一行以字符'/'结束。 /// 例如:初始状态的FEN字符串是 "rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w" /// 在描述完棋子位置后,根一个空格,然后是一个表示轮到哪方走棋的字符,如果是b,表示轮黑方走棋,r或其它字符都表示红方走棋 /// </summary> /// <param name="fen">FEN格式字符串</param> public static void InitFromFEN(Board board, string fen) { int piece; int index = 0; int pos90 = 0; while (fen[index] != ' ') { piece = BoardUtil.PieceID(fen[index]); //把字符转换为棋子的编号; if (piece > 0) { int pos238 = BoardUtil.Pos(pos90); AddPiece(board, piece, pos238); ++pos90; } else if (fen[index] >= '1' && fen[index] <= '9') { pos90 += fen[index] - '0'; // skip empty squares } else if (fen[index] != '/') { // if there another char than '/' throw exception // throw new ArgumentException("fen"); // 如果程序执行到这里表示FEN字符串有错 throw new Exception("Fen代码出错,缺少终止符'/'"); } ++index; } ++index; // 由于国际象棋中是白方先行,这里会记录一个w字母,但在中国象棋中通常会记录r,代表红方 // 这里把不是b字母的都认为是轮红方走棋 board.IsRedTurn = (fen[index] != 'b'); // 最后更新另外几个特殊的位棋盘的状态 board.TotalRedPiece = board.RedPieceNum[1] + board.RedPieceNum[2] + board.RedPieceNum[3] + board.RedPieceNum[4] + board.RedPieceNum[5] + board.RedPieceNum[6] + 1; // 最后的1代表红帅 board.TotalBlackPiece = board.BlackPieceNum[1] + board.BlackPieceNum[2] + board.BlackPieceNum[3] + board.BlackPieceNum[4] + board.BlackPieceNum[5] + board.BlackPieceNum[6] + 1; board.ZobristKey = Zobrist.ZoristHash(board); }
/// <summary> /// 为方便调试,用一个简洁的方式来显示棋盘情况 /// TODO: 不知道是否影响性能 /// </summary> /// <returns> /// 盘面字符串 /// </returns> public override string ToString() { StringBuilder sb = new StringBuilder(); for (int j = 0; j < 10; j++) { for (int i = 0; i < 9; i++) { int piece = pieceData[BoardUtil.Pos(i, j)]; if (piece != 0) { sb.Append(BoardUtil.PieceChineseNameForPrint(piece)); } else { sb.Append(boardChar[j * 9 + i]); } } sb.Append(System.Environment.NewLine); } return(sb.ToString()); }
///<summary> /// 在某一列上找中兵或中卒的位置编号 ///</summary> ///<param name="piece"></param> ///<param name="file">列号</param> ///<returns></returns> public static int FindMiddlePawn(Board board, int piece, int file) { int foundPos = -1; int numFound = 0; for (int rank = 0; rank < 10; rank++) { int pos = BoardUtil.Pos(file, rank); if (board.pieceData[pos] == piece) { foundPos = pos; // 如果之前发现过一个兵,又在同一列上找到一个兵,就是“中兵”了 if (numFound == 1) { break; } ++numFound; } } return(foundPos); }
/* * static void Main(string[] args) * { * OpeningBook.CreateOpeningBook(); * * Board board = Board.CreateStartingBoard(); * Move m = OpeningBook.BestMove(board); * * int ttt = 0; * while(m!=null) * { * m.Make(board); * Console.WriteLine("---- " + (++ttt) + ": Best Move: " + m + "\n" + board); * m = OpeningBook.BestMove(board); * } * * // AddPgnFileToOpeningBook(@"..\..\test.pgn"); * * DirectoryInfo dir = new DirectoryInfo("e:\\11万精品真藏"); * FileInfo[] files = dir.GetFiles("*.pgn",SearchOption.AllDirectories); * * foreach (FileInfo f in files) * { * Console.WriteLine(f.Name); * //AddPgnFileToOpeningBook(f.FullName); * * //if (!CheckPgnFile(f.FullName)) * //{ * // Console.WriteLine("!!!FAIL!!!" + f.FullName); * // File.Delete(f.FullName); * // Console.ReadKey(); * //} * } * Console.ReadKey(); * } * */ private static bool CheckPgnFile(string pgnFilename) { string[] allmoves = GetAllMovesFromPgnFile(pgnFilename); Board board = new Board(); foreach (string strMove in allmoves) { try { //TODO: board.CreateMoveFromString(NotationConverter.Convert(board, strMove)); Move move = BoardUtil.CreateMoveFromString(board, strMove); board.MakeMove(move); } catch (Exception e) { Console.WriteLine(e.ToString() + "\n" + board); return(false); } // Console.WriteLine("--- " + strMove + " ---"); // Console.WriteLine(board); } return(true); }
/// <summary> /// 把Move转换为“炮二平五”中文纵列格式的棋谱文本 /// </summary> /// <param name="move">着法</param> /// <returns>中文棋谱字符串</returns> public static string NotationConvertedFromMove(Board board, Move move) { string pieceName = PieceName(move.Piece); int fileFrom = Board.File(move.From); int rankFrom = Board.Rank(move.From); int fileTo = Board.File(move.To); int rankTo = Board.Rank(move.To); string strNum = board.IsRedTurn ? RedDigitString(9 - fileFrom) : BlackDigitString(1 + fileFrom); //思路:累计在同一条纵线的上方up或下方down是否有相同棋子, //如果都有则为"中",否则根据上下判断"前后".黑棋与红旗"前后"颠倒 int up = 0; int down = 0; for (int rank = 0; rank <= 9; rank++) { if (rank == rankFrom) { continue; } if (board.pieceData[BoardUtil.Pos(fileFrom, rank)] == move.Piece) { if (rank > rankFrom) { ++down; } if (rank < rankFrom) { ++up; } } } // 前2个汉字,这种名字会产生“前炮”、“中兵”、“后马”的效果 string notation1; if (up > 0 && down > 0) { notation1 = "中"; } else if (up > 0) { notation1 = board.IsRedTurn ? "前" : notation1 = "后"; } else if (down > 0) { notation1 = board.IsRedTurn ? "后" : notation1 = "前"; } else { notation1 = pieceName + strNum; } // 后2个汉字 string notation2; if (board.IsRedTurn) { //逻辑说明:首先判断纵向y轴是否改变,负则后退,否则前进.不变则平移. int num = rankFrom - rankTo; if (num < 0) { if (IsTiltPiece(move.Piece)) { notation2 = "退" + RedDigitString(Math.Abs(9 - fileTo)); } else { notation2 = "退" + RedDigitString(Math.Abs(rankFrom - rankTo)); } } else if (num > 0) { if (IsTiltPiece(move.Piece)) { notation2 = "进" + RedDigitString(Math.Abs(9 - fileTo)); } else { notation2 = "进" + RedDigitString(Math.Abs(rankFrom - rankTo)); } } else // (num == 0) { notation2 = "平" + RedDigitString(Math.Abs(9 - fileTo)); } } else // 黑方的着法 { //逻辑说明:首先判断纵向y轴是否改变,负则后退,否则前进.不变则平移. int num = rankFrom - rankTo; if (num > 0) { if (IsTiltPiece(move.Piece)) { notation2 = "退" + BlackDigitString(Math.Abs(1 + fileTo)); } else { notation2 = "退" + BlackDigitString(Math.Abs(rankFrom - rankTo)); } } else if (num < 0) { if (IsTiltPiece(move.Piece)) { notation2 = "进" + BlackDigitString(Math.Abs(1 + fileTo)); } else { notation2 = "进" + BlackDigitString(Math.Abs(rankFrom - rankTo)); } } else //(num == 0) { notation2 = "平" + BlackDigitString(Math.Abs(1 + fileTo)); } } return(notation1 + notation2); }
/// <summary> /// 把中文纵列格式的棋谱转换成Move,例如:炮二平五-->h2e2 /// </summary> /// <param name="board">在走该着法之前的盘面情况</param> /// <param name="chNotation">中文纵列标记</param> /// <returns>Move。转换过程中出现的错误,都会抛出异常</returns> public static Move CreateMoveFromChineseNotation(Board board, string chNotation) { if (chNotation.Length != 4) { throw new Exception("着法必须是4个汉字:" + chNotation); } char first = chNotation[0]; char second = chNotation[1]; char third = chNotation[2]; char fourth = chNotation[3]; int piece = BoardUtil.PieceFromString(chNotation, board.IsRedTurn); int startPos = 0; // 先要根据前2个汉字找到棋子的初始位置,会有两大类情况,(1)车二;(2)前车 // 先处理第一种情况:类似这样的“炮二平五”,“马8进7” if ("车马炮相仕兵帅象士卒将".Contains(first)) { // 第2个汉字就可以得到起始点的纵列坐标, from 0 to 8 int startFile = ToFileNum(second); // 对于相(象)、士(仕)来说,得找到前面或后面的象、士,因为“相七退五”时,七路上可能会有2个象 // “仕六进五”也是同样的道理 if ("相仕象士".Contains(first)) { // 如果一条线上有2个象,则需要用第三个字符是“进”还是“退”来判断是移动了哪个象 if (first == '相' && second == '三' && third == '进') { startPos = Board.G0; } else if (first == '相' && second == '三' && third == '退') { startPos = Board.G4; } else if (first == '相' && second == '七' && third == '进') { startPos = Board.C0; } else if (first == '相' && second == '七' && third == '退') { startPos = Board.C4; } else if (first == '仕' && second == '四' && third == '进') { startPos = Board.F0; } else if (first == '仕' && second == '四' && third == '退') { startPos = Board.F2; } else if (first == '仕' && second == '六' && third == '进') { startPos = Board.D0; } else if (first == '仕' && second == '六' && third == '退') { startPos = Board.D2; } else if (first == '象' && second == '3' && third == '进') { startPos = Board.C9; } else if (first == '象' && second == '3' && third == '退') { startPos = Board.C5; } else if (first == '象' && second == '7' && third == '进') { startPos = Board.G9; } else if (first == '象' && second == '7' && third == '退') { startPos = Board.G5; } else if (first == '士' && second == '4' && third == '进') { startPos = Board.D9; } else if (first == '士' && second == '4' && third == '退') { startPos = Board.D7; } else if (first == '士' && second == '6' && third == '进') { startPos = Board.F9; } else if (first == '士' && second == '6' && third == '退') { startPos = Board.F7; } else { // throw new Exception("error of the position of bishop or advisor"); startPos = BoardUtil.FindPosition(board, piece, startFile); } } else { // 按理说,正规的棋谱里这里应该同一纵线上只有一个车、马、炮、兵、卒,但有些不正规的棋谱里会遇到问题 // TODO: 以后有时间的,可以多做一些容错性的判断,如果一条纵线上有2个相同类的棋子,但一个棋子无法走动时,则以另一个棋子的着法为准 startPos = BoardUtil.FindPosition(board, piece, startFile); } } else // 在这里处理第二大类情况,类似“前炮进二”这样的情况 { // 兵、卒有些特殊,有可能一列中3个兵,这样会有“中兵”或“中卒”的情况出现 // 2011年无极棋谱\华山棋谱\2011-01-09 青山豹(天罡) 和 一把家族(无极).pgn // TODO: 这里面极少可能出现的棋谱,“前1平2”的着法 if (piece == Board.RED_PAWN || piece == Board.BLACK_PAWN) { int col = BoardUtil.Find3PawnFileNum(board, piece); if (first == '中') { startPos = BoardUtil.FindMiddlePawn(board, piece, col); } else if ((first == '前' && board.IsRedTurn) || (first == '后' && !board.IsRedTurn)) { startPos = BoardUtil.FindPositionFromBlackToRed(board, piece, col); } else { startPos = BoardUtil.FindPositionFromRedToBlack(board, piece, col); } } else { // 扫描整个棋盘,找到类似“前炮”这样的棋子的位置 if ((first == '前' && board.IsRedTurn) || (first == '后' && !board.IsRedTurn)) { startPos = BoardUtil.FindPositionFromBlackToRed(board, piece); } else { startPos = BoardUtil.FindPositionFromRedToBlack(board, piece); } } } if (startPos == 0) { throw new Exception("在盘面上找不到" + first + second + "这枚棋子," + chNotation); } ///////////////////////////////////////////////////////////////////// ////////////////////////// 以后都是为了找到终点的坐标了 ///////////////////////////////////////////////////////////////////// int startPosFile = Board.File(startPos); int startPosRank = Board.Rank(startPos); // 根据第3个字符“平、进、退”分别进行相应的处理 int endFile = -1; int endRank = -1; if (third == '平') { if (!IsTiltPiece(piece)) { endFile = ToFileNum(fourth); if (endFile < 0) { throw new Exception(chNotation + "中的第4个字符错误,应该为大写数字或全角数字"); } // 对于“平”着法,起点和终点的行号坐标是一样的 endRank = startPosRank; } else //士、象、马 { throw new Exception("士象马不能有平的移动:" + chNotation); } } else if (third == '退') { if (piece == Board.RED_KNIGHT || piece == Board.BLACK_KNIGHT) { endFile = ToFileNum(fourth); if (endFile < 0) { throw new Exception(chNotation + "中的第4个字符错误,应该为大写数字或全角数字"); } int diff = Math.Abs(endFile - startPosFile); if (diff == 1) { if (!board.IsRedTurn) { endRank = startPosRank - 2; } else { endRank = startPosRank + 2; } } else if (diff == 2) { if (!board.IsRedTurn) { endRank = startPosRank - 1; } else { endRank = startPosRank + 1; } } else { throw new Exception("马的走法有误:" + chNotation); } } else if (piece == Board.RED_BISHOP || piece == Board.BLACK_BISHOP) { endFile = ToFileNum(fourth); if (endFile < 0) { throw new Exception(chNotation + "中的第4个字符错误,应该为大写数字或全角数字"); } int diff = Math.Abs(endFile - startPosFile); if (diff == 2) { if (!board.IsRedTurn) { endRank = startPosRank - 2; } else { endRank = startPosRank + 2; } } else { throw new Exception("相象的走法有误:" + chNotation); } } else if (piece == Board.RED_ADVISOR || piece == Board.BLACK_ADVISOR) { endFile = ToFileNum(fourth); if (endFile < 0) { throw new Exception(chNotation + "中的第4个字符错误,应该为大写数字或全角数字"); } int diff = Math.Abs(endFile - startPosFile); if (diff == 1) { if (!board.IsRedTurn) { endRank = startPosRank - 1; } else { endRank = startPosRank + 1; } } else { throw new Exception("仕士的走法有误:" + chNotation); } } else if ( // 炮、车、帅、兵在纵线上移动时规则都是一样的 piece == Board.RED_CANNON || piece == Board.BLACK_CANNON || piece == Board.RED_ROOK || piece == Board.BLACK_ROOK || piece == Board.RED_KING || piece == Board.BLACK_KING || piece == Board.RED_PAWN || piece == Board.BLACK_PAWN) { endFile = startPosFile; int diff = ToInt(fourth); if (diff < 0) { throw new Exception(chNotation + "中的第4个字符错误,应该为大写数字或全角数字"); } if (!board.IsRedTurn) { endRank = startPosRank - diff; } else { endRank = startPosRank + diff; } } else { throw new Exception("不认识的着法:" + chNotation); } } else // 进 { if (third != '进') { throw new Exception(chNotation + "中的第3个字符错误,应该进、退、平"); } if (piece == Board.RED_KNIGHT || piece == Board.BLACK_KNIGHT) { endFile = ToFileNum(fourth); if (endFile < 0) { throw new Exception(chNotation + "中的第4个字符错误,应该为大写数字或全角数字"); } int diff = Math.Abs(endFile - startPosFile); if (diff == 1) { if (!board.IsRedTurn) { endRank = startPosRank + 2; } else { endRank = startPosRank - 2; } } else if (diff == 2) { if (!board.IsRedTurn) { endRank = startPosRank + 1; } else { endRank = startPosRank - 1; } } else { throw new Exception("马的走法有误:" + chNotation); } } else if (piece == Board.RED_BISHOP || piece == Board.BLACK_BISHOP) { endFile = ToFileNum(fourth); if (endFile < 0) { throw new Exception(chNotation + "中的第4个字符错误,应该为大写数字或全角数字"); } int diff = Math.Abs(endFile - startPosFile); if (diff == 2) { if (!board.IsRedTurn) { endRank = startPosRank + 2; } else { endRank = startPosRank - 2; } } else { throw new Exception("相象的走法有误:" + chNotation); } } else if (piece == Board.RED_ADVISOR || piece == Board.BLACK_ADVISOR) { endFile = ToFileNum(fourth); if (endFile < 0) { throw new Exception(chNotation + "中的第4个字符错误,应该为大写数字或全角数字"); } int diff = Math.Abs(endFile - startPosFile); if (diff == 1) { if (!board.IsRedTurn) { endRank = startPosRank + 1; } else { endRank = startPosRank - 1; } } else { throw new Exception("士仕的走法有误:" + chNotation); } } else if ( piece == Board.RED_CANNON || piece == Board.BLACK_CANNON || piece == Board.RED_ROOK || piece == Board.BLACK_ROOK || piece == Board.RED_KING || piece == Board.BLACK_KING || piece == Board.RED_PAWN || piece == Board.BLACK_PAWN) { endFile = startPosFile; // !!!!! int diff = ToInt(fourth); if (diff < 0) { throw new Exception(chNotation + "中的第4个字符错误,应该为大写数字或全角数字"); } if (!board.IsRedTurn) { endRank = startPosRank + diff; } else { endRank = startPosRank - diff; } } else { throw new Exception("不认识的着法:" + chNotation); } } if (endFile < 0 || endFile >= 9 || endRank < 0 || endRank >= 10) { throw new Exception("棋子走到棋盘之外了:" + chNotation + "\n" + board); } int endPos = BoardUtil.Pos(endFile, endRank); Move move = board.CreateMove(startPos, endPos); return(move); }