public void doVerifyThreeFoldRepetition(baseBoard ourBoard) { // Play some useless knight moves. Once the same board situation has occured three // times, the game is a draw. for (int n = 0; n < 8; n++) { Assert.AreEqual(gameStatus.inProgress, ourBoard.getGameStatus(pieceColour.white), "Game declared drawn at move " + n.ToString()); switch (n % 4) { case 0: // Play Nc3 ourBoard.doMove(new move(ourBoard[1, 0], ourBoard[2, 2])); break; case 1: // Play Nc6 ourBoard.doMove(new move(ourBoard[1, 7], ourBoard[2, 5])); break; case 2: // And move knights back again. // Nb1 ourBoard.doMove(new move(ourBoard[2, 2], ourBoard[1, 0])); break; case 3: // nb8 ourBoard.doMove(new move(ourBoard[2, 5], ourBoard[1, 7])); break; default: throw new ArgumentException(); } } Assert.AreEqual(gameStatus.drawn, ourBoard.getGameStatus(pieceColour.white)); }
private bool canCastle(baseBoard theBoard, bool kingSide) { // We can castle under specific circumstances: // * Our king has not moved // * A rook is on the same rank as the king and has not moved // * The squares between the king and the rook are empty // * The first two spaces between the king and the rook are not threatened by anything (the rook can move through 'check' but the king cannot) // * The partnering rook has not been flagged as uncastlable // If we are castling kingside, examine row 7 - otherwise, row 0. square potentialRookSquare = theBoard[ kingSide ? 7 : 0 , position.y]; if (movedCount == 0 // King has not moved && potentialRookSquare.type == pieceType.rook // Rook is present && potentialRookSquare.movedCount == 0 // Rook has not moved && potentialRookSquare.excludeFromCastling == false ) { // Verify that king spaces are free int startx; int limitx; if (kingSide) { startx = position.x + 1; limitx = potentialRookSquare.position.x - 1; } else { limitx = position.x - 1; startx = potentialRookSquare.position.x + 1; } for (int n = startx ; n < limitx + 1; n++) { square perhapsEmpty = theBoard[ n, position.y ]; if (perhapsEmpty.type != pieceType.none) { // Intermediate squares are occupied - castling is impossible. return false; } } // Verify that two spaces next to the king are not threatened by the opposing side for (int n = 1; n < 3; n++) { square perhapsThreatened = theBoard[ kingSide ? position.right(n) : position.left(n)]; if ( theBoard.isThreatenedHint(perhapsThreatened, colour) ) { // Intermediate squares are threatened - castling is impossible. return false; } } return true; } return false; }
public override sizableArray<square> getCoveredSquares(baseBoard onThis) { sizableArray<square> toRet = new sizableArray<square>(20); getSquaresCoveredForVector(toRet, onThis, vectorDirection.left); getSquaresCoveredForVector(toRet, onThis, vectorDirection.down); getSquaresCoveredForVector(toRet, onThis, vectorDirection.right); getSquaresCoveredForVector(toRet, onThis, vectorDirection.up); return toRet; }
public override sizableArray<move> getPossibleMoves(baseBoard onThis) { sizableArray<move> toRet = new sizableArray<move>(20); getMovesForVector(toRet, onThis, vectorDirection.left); getMovesForVector(toRet, onThis, vectorDirection.down); getMovesForVector(toRet, onThis, vectorDirection.right); getMovesForVector(toRet, onThis, vectorDirection.up); return toRet; }
public static Table makeTable(baseBoard board) { try { Table htmlTable = new Table(); htmlTable.CellPadding = 0; htmlTable.CellSpacing = 0; for (int y = DoktorChessAIBoard.sizeY - 1; y > -1; y--) { TableRow newRow = new TableRow(); for (int x = 0; x < DoktorChessAIBoard.sizeX; x++) { TableCell newCell = new TableCell(); // Fill with our chess piece image if there's a piece there pieceType pieceType = board.getPieceType(x, y); if (pieceType != pieceType.none) { Image pieceImage = new Image(); newCell.Controls.Add(pieceImage); pieceImage.AlternateText = board.getPieceString(x, y); pieceImage.ImageUrl = getImageForPiece(pieceType, board.getPieceColour(x, y)); pieceImage.CssClass = "piece"; } // Get our nice checkerboard pattern via CSS. Set all of our cells to // have the boardSquare CSS, too, so that jQuery can funkerise them. newCell.CssClass = "boardSquare"; int offx = (y%2 == 1) ? x : x + 1; // offset by 1 on every other row if ((offx%2 == 0)) newCell.CssClass += " boardSquareOdd"; else newCell.CssClass += " boardSquareEven"; newCell.Attributes["x"] = x.ToString(); newCell.Attributes["y"] = y.ToString(); newRow.Cells.Add(newCell); } htmlTable.Rows.Add(newRow); } return htmlTable; } catch (Exception) { return new Table(); } }
public override sizableArray<square> getCoveredSquares(baseBoard parentBoard) { sizableArray<square> toRet = new sizableArray<square>(8); foreach (squarePosOffset potentialSquare in potentialSquares) { int posX = position.x + potentialSquare.x; int posY = position.y + potentialSquare.y; if (IsOnBoard(potentialSquare.x, potentialSquare.y)) toRet.Add( parentBoard[posX, posY] ); } return toRet; }
public lineAndScore findBestMoveWithTimeout(baseBoard boardToMove, int timeout) { findBestMoveBoard = boardToMove; Thread findThread = new Thread(findBestMoveWithTimeoutThread); evnt = new AutoResetEvent(false); evnt.Reset(); findThread.Start(); if (evnt.WaitOne(timeout, false)) { if (ex != null) throw ex; return bestLine; } findThread.Abort(); throw new Exception("Player timeout"); }
private void doMove(baseBoard boardToMove) { lineAndScore bestMove = boardToMove.findBestMove(); Console.WriteLine(string.Format("Best line for {0}: {1}", toPlay, bestMove.ToString(moveStringStyle.chessNotation))); Console.WriteLine(bestMove.ToString()); //Console.WriteLine("{0} boards scored in {1} ms, {2}/sec. {3} ms in board scoring.", myBoard.stats.boardsScored, myBoard.stats.totalSearchTime, myBoard.stats.scoredPerSecond, myBoard.stats.boardScoreTime ); //Console.Write(String.Format("{0},", myBoard.stats.boardsScored)); //Console.Write(boardToMove.coverLevel.ToString()); move bestFirstMove = bestMove.line[0]; p1.board.doMove(bestFirstMove); p2.board.doMove(bestFirstMove); Console.WriteLine(boardToMove.ToString()); moves.Add(bestFirstMove); }
public override sizableArray<move> getPossibleMoves(baseBoard onThis) { sizableArray<move> possibleMoves = new sizableArray<move>(potentialSquares.Length + 2); findFreeOrCapturableIfOnBoard(possibleMoves, onThis, potentialSquares); if (canCastle(onThis, true)) { // Castling kingside is possible. It is represented as a two-space move by the king. possibleMoves.Add(new move(this, onThis[position.right(2)])); } if (canCastle(onThis, false)) { // Likewise, queenside. possibleMoves.Add(new move(this, onThis[position.left(2)])); } return possibleMoves; }
public override sizableArray<square> getCoveredSquares(baseBoard parentBoard) { sizableArray<square> toRet = new sizableArray<square>(2); int direction = (colour == pieceColour.white) ? 1 : -1; if ((position.y + direction < baseBoard.sizeY) && (position.y + direction > -1)) { // Check the two diagonals if (position.x > 0) { toRet.Add( parentBoard[position.up(direction).leftOne()] ); } if (position.x < baseBoard.sizeX - 1) { toRet.Add( parentBoard[position.up(direction).rightOne()] ); } } return toRet; }
public virtual sizableArray<move> getPossibleMoves(baseBoard onThis) { // Empty squares can't move, silly. return new sizableArray<move>(0); }
public override sizableArray<move> getPossibleMoves(baseBoard onThis) { return findFreeOrCapturableIfOnBoard(null, onThis, potentialSquares); }
private void gameFinished(baseBoard board) { if (OnGameFinished != null) OnGameFinished.Invoke(this, board); }
private void gameThread() { // Make a new AppDomain for each player. appDomainWhite = createAppDomain(white.assemblyPath, "Tournament game AppDomain (white player)"); appDomainBlack = createAppDomain(black.assemblyPath, "Tournament game AppDomain (black player)"); colToMove = pieceColour.white; // Make new players try { gameBoardWhite = white.makeNewBoard(appDomainWhite); } catch (Exception e) { throw new playerException(white, pieceColour.white, "While attempting to make board: ", e); } try { gameBoardBlack = black.makeNewBoard(appDomainBlack); } catch (Exception e) { throw new playerException(black, pieceColour.black, "While attempting to make board: ", e); } // Fill in our board HTML TextWriter ourTextWriter = new StringWriter(); HtmlTextWriter ourHtmlWriter = new HtmlTextWriter(ourTextWriter); utils.makeTableAndEscapeContents(gameBoardWhite).RenderControl(ourHtmlWriter); boardRepresentation = ourTextWriter.ToString(); // Initialise player times (ms) white.timeLeft = black.timeLeft = 5*60*1000; isRunning = true; while (true) { // Do some quick sanity checks to ensure that both AIs have consistant views of // the situation if (gameBoardWhite.colToMove != colToMove) throw new playerException(white, pieceColour.white, "Player has incorrect the 'next player' value"); if (gameBoardBlack.colToMove != colToMove) throw new playerException(black, pieceColour.black, "Player has incorrect the 'next player' value"); // Okay, checks are ok, so lets play a move! baseBoard boardToMove = colToMove == pieceColour.white ? gameBoardWhite : gameBoardBlack; contender player = colToMove == pieceColour.white ? white : black; int timeLeft = colToMove == pieceColour.white ? white.timeLeft : black.timeLeft; move bestMove; try { moveWithTimeout mwo = new moveWithTimeout(); boardToMove.timeLeftMS = timeLeft; lineAndScore bestLine = mwo.findBestMoveWithTimeout(boardToMove, timeLeft); bestMove = bestLine.line[0]; moveList.Add(bestMove); } catch (Exception e) { throw new playerException(player, colToMove, "During move search: ", e); } // Now play the move on both boards foreach (baseBoard thisContener in new[] { gameBoardBlack, gameBoardWhite }) { try { thisContener.doMove(bestMove); } catch (Exception e) { contender culprit = thisContener == gameBoardBlack ? black : white; pieceColour culpritCol = thisContener == gameBoardBlack ? pieceColour.black : pieceColour.white; throw new playerException(culprit, culpritCol, "While playing move as " + culpritCol + ": ", e); } } pieceColour colJustMoved = colToMove; colToMove = colToMove == pieceColour.white ? pieceColour.black : pieceColour.white; // Extract a graphical representation of the board so we don't need to query it // while the game is running TextWriter ourTextWriter2 = new StringWriter(); HtmlTextWriter ourHtmlWriter2 = new HtmlTextWriter(ourTextWriter2); utils.makeTableAndEscapeContents(gameBoardWhite).RenderControl(ourHtmlWriter2); boardRepresentation = ourTextWriter2.ToString(); // Check that game is still in progrss gameStatus statusWhite; gameStatus statusBlack; try { statusWhite = gameBoardWhite.getGameStatus(colJustMoved); } catch (Exception e) { throw new playerException(white, pieceColour.white, "While evaluating game", e); } try { statusBlack = gameBoardBlack.getGameStatus(colJustMoved); } catch (Exception e) { throw new playerException(black, pieceColour.black, "While evaluating game ", e); } if (statusBlack != statusWhite) { // This could be white or black's fault - we can't tell for sure here. throw new playerException(white, pieceColour.white, "While evaluating game ", new Exception("White and Black disagree on game status")); } if (statusWhite != gameStatus.inProgress) { // OK, the game is over! if (statusWhite == gameStatus.drawn) { isDraw = true; } else { isDraw = false; winningSide = colJustMoved; } break; } } isFinished = true; isRunning = false; gameFinished(gameBoardWhite); }
private bool canEnPassantTo(square adjacent, baseBoard theBoard) { // We can capture via en passant if: // * An enemy pawn is on our right/left // * The enemy pawn has moved only once // * The enemy pawn moved last move // * The enemy pawn (and us) are on the 4th/5th rank (according to player colour) // * The space behind the enemy pawn is empty. if ( adjacent.containsPieceNotOfColour(colour) // Is an enemy piece && adjacent.type == pieceType.pawn // Is an enemy pawn && adjacent.movedCount == 1 // Has moved only once && adjacent.moveNumbers.Peek() == theBoard.moveCount-1 // Moved last move && adjacent.position.y == (colour == pieceColour.white ? 4 : 3) // is on start row && theBoard[ adjacent.position.up(colour == pieceColour.white ? 1 : -1) ].type == pieceType.none ) return true; return false; }
public ChessPlayer(baseBoard newBoard) { board = newBoard; }
private void gameFinished(tournamentGame recentlyfinished, baseBoard fromWhitesView) { // Change the score of each player if (recentlyfinished.isErrored) { if (recentlyfinished.white.isErrored) { recentlyfinished.white.errorCount += 1; recentlyfinished.black.score += 1.0f; recentlyfinished.black.wins++; } if (recentlyfinished.black.isErrored) { recentlyfinished.black.errorCount += 1; recentlyfinished.white.score += 1.0f; recentlyfinished.white.wins++; } } else if (recentlyfinished.isDraw) { recentlyfinished.white.score += 0.5f; recentlyfinished.black.score += 0.5f; recentlyfinished.white.draws++; recentlyfinished.black.draws++; } else { if (recentlyfinished.winningSide == pieceColour.white) { recentlyfinished.white.score += 1.0f; recentlyfinished.white.wins++; recentlyfinished.black.losses++; } else { recentlyfinished.black.score += 1.0f; recentlyfinished.black.wins++; recentlyfinished.white.losses++; } } // Make the historic game info playedGame gameInfo = new playedGame(); gameInfo.isDraw = recentlyfinished.isDraw; gameInfo.isErrored = recentlyfinished.white.isErrored; gameInfo.erroredSide = recentlyfinished.erroredSide; gameInfo.errorMessage = recentlyfinished.white.errorMessage; gameInfo.opponentID = recentlyfinished.black.ID; gameInfo.opponentTypeName = recentlyfinished.black.typeName; gameInfo.exception = recentlyfinished.white.exception; gameInfo.moveList = recentlyfinished.moveList; gameInfo.didWin = recentlyfinished.winningSide == pieceColour.white; gameInfo.col = pieceColour.white; gameInfo.board = fromWhitesView; recentlyfinished.white.gamesPlayed.Add(gameInfo); playedGame gameInfoBlack = new playedGame(); gameInfoBlack.isDraw = recentlyfinished.isDraw; gameInfoBlack.isErrored = recentlyfinished.black.isErrored; gameInfoBlack.erroredSide = recentlyfinished.erroredSide; gameInfoBlack.errorMessage = recentlyfinished.black.errorMessage; gameInfoBlack.exception = recentlyfinished.black.exception; gameInfoBlack.opponentID = recentlyfinished.white.ID; gameInfoBlack.opponentTypeName = recentlyfinished.white.typeName; gameInfoBlack.moveList = recentlyfinished.moveList; gameInfoBlack.didWin = recentlyfinished.winningSide == pieceColour.black; gameInfoBlack.col = pieceColour.black; gameInfoBlack.board = fromWhitesView; recentlyfinished.black.gamesPlayed.Add(gameInfoBlack); // Start the next! lock (gameQueue) { tournamentGame[] pendingGames = gameQueue.Where(x => !x.isRunning && !x.isFinished).ToArray(); if (pendingGames.Length > 0) { _currentGame = pendingGames[0]; pendingGames[0].startInNewThread(); } else { _currentGame = null; } } }
/// <summary> /// Is this move legal according to the rules of chess? /// </summary> /// <param name="ourBoard">Board to examine</param> /// <returns></returns> public bool isLegal(baseBoard ourBoard) { if (_srcSquare.type == pieceType.none) return false; // Is the move possible according to the piece? sizableArray<move> possibleMovesWithMovingPiece = _srcSquare.getPossibleMoves(ourBoard); return possibleMovesWithMovingPiece.Any( possibleMove => possibleMove.srcPos.isSameSquareAs(srcPos) && possibleMove.dstPos.isSameSquareAs(dstPos) ); }
public static move fromJSON(string JSON, baseBoard parentBoard) { minimalMove json = new JavaScriptSerializer().Deserialize<minimalMove>(JSON); // Don't forget that the board is inverted, as we show it to the user json.srcSquarePos = new squarePos(json.srcSquarePos.x, 7 - json.srcSquarePos.y); json.dstSquarePos = new squarePos(json.dstSquarePos.x, 7 - json.dstSquarePos.y); move toRet = new move( parentBoard[json.srcSquarePos], parentBoard[json.dstSquarePos]); return toRet; }
/// <summary> /// Find moves in a given direction, including captures /// </summary> /// <param name="addTo"></param> /// <param name="onThis">The board to move on</param> /// <param name="dir">The vectorDirection to move in</param> /// <returns>A List<move> of moves</returns> public sizableArray<move> getMovesForVector(sizableArray<move> addTo, baseBoard onThis, vectorDirection dir) { if (addTo == null) addTo = new sizableArray<move>(8); loopConfig lcfg = new loopConfig(position, dir); int x = lcfg.startX; int y = lcfg.startY; while ((x != lcfg.finishX) && (y != lcfg.finishY)) { squarePos sqPos = new squarePos(x, y); // If the square is empty, we can move to it.. if (onThis[sqPos].type == pieceType.none) { addTo.Add(new move(onThis[position], onThis[sqPos])); } else { if (onThis[sqPos].colour != colour ) { // the square is occupied by an enemy piece. we can move to it, // but no further. addTo.Add(new move(onThis[position], onThis[sqPos])); } break; } x += lcfg.directionX; y += lcfg.directionY; } return addTo; }
public virtual sizableArray<square> getCoveredSquares(baseBoard parentBoard) { return new sizableArray<square>(0); }
public override sizableArray<move> getPossibleMoves(baseBoard onThis) { sizableArray<move> toRet = new sizableArray<move>(40); int direction; if (colour == pieceColour.white) direction = +1; else direction = -1; // We can capture upward diagonally, if immediate diagonal upward squares // contain an enemy piece. if ((position.y + direction < baseBoard.sizeY) && (position.y + direction > -1) ) { // Check for en passant. En passant can never cause a promotion. if (position.x > 0) { square adjacentLeft = onThis[position.leftOne()]; if (canEnPassantTo(adjacentLeft, onThis)) toRet.Add(new move(onThis[position], onThis[position.up(direction).leftOne()], adjacentLeft)); } if (position.x < baseBoard.sizeX - 1) { square adjacentRight = onThis[position.rightOne()]; if (canEnPassantTo(adjacentRight, onThis)) toRet.Add(new move(onThis[position], onThis[position.up(direction).rightOne()], adjacentRight)); } // And we can move forward two if we haven't moved this piece yet, and both // squares are empty. It is assumed that this can't cause a promotion, so explicitly // prevent this move from moving in to the back row. if (movedCount == 0) { if (position.y + (direction * 2) < baseBoard.sizeY && position.y + (direction * 2) > -1) { // check back row if (position.y != (colour == pieceColour.white ? 7 : 0)) { if (onThis[position.up(direction * 2)].type == pieceType.none && onThis[position.up(direction * 1)].type == pieceType.none) toRet.Add(new move(onThis[position], onThis[position.up(direction*2)])); } } } // All of the other moves could cause a promotion, so call addPawnMovesToSquare so that // promoting moves are added. // Check the two diagonals if (position.x > 0) { if (onThis[position.up(direction).leftOne()].containsPieceNotOfColour(colour)) addPawnMovesToSquare(toRet, onThis[position], onThis[position.up(direction).leftOne()]); } if (position.x < baseBoard.sizeX - 1) { if (onThis[position.up(direction).rightOne()].containsPieceNotOfColour(colour)) addPawnMovesToSquare(toRet, onThis[position], onThis[position.up(direction).rightOne()]); } // We can move forward one if that square is empty. if (onThis[position.up(direction)].type == pieceType.none) { if (onThis[position.up(direction)].type == pieceType.none) addPawnMovesToSquare(toRet, onThis[position], onThis[position.up(direction)]); } } return toRet; }
public rookSquare findCastlingRook(baseBoard theBoard) { if (!isACastling) throw new Exception("Asked to find castling rook of a move not a castle"); if (dstPos.x > srcPos.x) return (rookSquare) theBoard[7, dstPos.y]; else if (srcPos.x > dstPos.x) return (rookSquare) theBoard[0, dstPos.y]; throw new Exception("Malformed castling"); }
protected sizableArray<move> findFreeOrCapturableIfOnBoard(sizableArray<move> returnArray, baseBoard onThis, squarePosOffset[] potentialSquareOffsets) { if (returnArray == null) returnArray = new sizableArray<move>(potentialSquareOffsets.Length); foreach (squarePosOffset potentialSquareOffset in potentialSquareOffsets) { if (!IsOnBoard(potentialSquareOffset.x, potentialSquareOffset.y)) continue; square destSquare = onThis[potentialSquareOffset.x + position.x, potentialSquareOffset.y + position.y]; if (destSquare.type == pieceType.none) { // Square is free. returnArray.Add(new move(this, destSquare)); } else { if (destSquare.colour != this.colour) { // We can capture. returnArray.Add(new move(this, destSquare)); } } } return returnArray; }
/// <summary> /// Add any extra data to the move so it makes sense /// </summary> /// <param name="ourBoard"></param> /// <returns>null if move is illegal</returns> public move sanitize(baseBoard ourBoard) { sizableArray<move> possibleMovesWithMovingPiece = _srcSquare.getPossibleMoves(ourBoard); IEnumerable<move> casted = possibleMovesWithMovingPiece.Cast<move>(); return casted.FirstOrDefault(a => a.srcPos.isSameSquareAs(srcPos) && a.dstPos.isSameSquareAs(dstPos)); }
protected void getSquaresCoveredForVector(sizableArray<square> addTo, baseBoard onThis, vectorDirection dir) { if (addTo == null) addTo = new sizableArray<square>(8); loopConfig lcfg = new loopConfig(position, dir); int x = lcfg.startX; int y = lcfg.startY; while ((x != lcfg.finishX) && (y != lcfg.finishY)) { squarePos sqPos = new squarePos(x, y); // If the square is empty, we can move to it.. if (onThis[sqPos].type == pieceType.none) { addTo.Add( onThis[sqPos] ); } else { // the square is occupied by some piece. We are covering it, but we cannot go any further. addTo.Add( onThis[sqPos] ); break; } x += lcfg.directionX; y += lcfg.directionY; } return; }