public void testIteratorOverEmptyCollection() { sizableArray<move> foo = new sizableArray<move>(5); if (foo.Length != 0) throw new Exception("Iterator length not zero while iterating over an empty collection"); foreach (move thisMove in foo) throw new Exception("Iterator returned something while iterating over an empty collection"); }
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 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 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 static void testListsAreOfSameMoves(List<move> expectedmoves, sizableArray<move> actualMoves) { Assert.AreEqual(expectedmoves.Count, actualMoves.Length, "Incorrect amount of moves"); foreach (move thisPossibleMove in actualMoves) { for (int i = 0; i < expectedmoves.Count; i++) { move thisExpectedMove = expectedmoves[i]; if ((thisExpectedMove.srcPos.isSameSquareAs(thisPossibleMove.srcPos) && (thisExpectedMove.dstPos.isSameSquareAs(thisPossibleMove.dstPos)))) { expectedmoves.Remove(thisExpectedMove); break; } } } Assert.AreEqual(0, expectedmoves.Count, "Unexpected move found"); }
public override lineAndScore findBestMove() { sizableArray<move> movesToConsider = getMoves(colToMove); // Filter out any moves in to check sizableArray<move> movesNotIntoCheck = new sizableArray<move>(movesToConsider.Length); pieceColour movingCol = colToMove; foreach (move consideredMove in movesToConsider) { doMove(consideredMove); if (!isPlayerInCheck(movingCol)) movesNotIntoCheck.Add(consideredMove); undoMove(consideredMove); } // and then select a random move. int rndNum = rnd.Next(movesNotIntoCheck.Length); move randomMove = movesNotIntoCheck[rndNum]; return new lineAndScore(new move[] { randomMove }, 0, null); }
public void testIterator() { sizableArray<move> foo = new sizableArray<move>(5); move move1 = new move(new square(0,0), new square(1,1)); move move2 = new move(new square(1,1), new square(2,2)); foo.Add(move1); foo.Add(move2); bool move1seen = false; bool move2seen = false; foreach (move iterated in foo) { Debug.WriteLine("Iterated object " + iterated); if (iterated == move1) { if (move1seen) throw new Exception("Move one iterated twice"); move1seen = true; } else if (iterated == move2) { if (move2seen) throw new Exception("Move two iterated twice"); move2seen = true; } else if (iterated == null) throw new Exception("Iterator returned null"); else throw new Exception("Iterator returned something crazy"); } if (!move1seen || !move2seen) throw new Exception("Iterator did not iterate over all elements"); }
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; }
private void prioritizeMoves(sizableArray<move> movesToConsider, int depthLeft) { // Bring any moves which are 'probably good' to the top of our list. Hold an array of bools, and set // each one which corresponds to a good move, so that we can avoid moving anything twice. bool[] movesReordered = new bool[movesToConsider.Length]; int[] reorderedMovesToConsider = new int[movesToConsider.Length]; int reorderedCount = 0; int n = 0; if (_killerStore != null) { // If one of these moves caused a cutoff last time, consider that move first. foreach (move consideredMove in movesToConsider) { if (_killerStore.contains(consideredMove, depthLeft)) { movesReordered[n] = true; reorderedMovesToConsider[reorderedCount++] = n; } n++; } } // Consider any capturing or pawn promotions first, too n = 0; foreach (move thisMove in movesToConsider) { if (thisMove.isCapture || // captures are good. thisMove.isPawnPromotion) // pawn promotions are good. { if (movesReordered[n] == false) { movesReordered[n] = true; reorderedMovesToConsider[reorderedCount++] = n; } } n++; } // Swap any good moves such that they are at the top int swapCount = 0; for (int i = 0; i < reorderedCount; i++) movesToConsider.bringToPosition(reorderedMovesToConsider[i], swapCount++); }
public void add(int x, int y) { // This is slightly complicated by the fact that any new piece could block other pieces // from threatening squares. Because of this, we keep per-piece 'threat lists' containing // the a list of squares threatened by the given piece, and if a piece is placed in to a // square threatened by another piece, we then re-evaluate the threatening pieces. // Find squares which threaten the square we are placing in to, and stash them in another // list sizableArray<int> piecesToRecalc = new sizableArray<int>(piecesWhichThreatenSquare[x, y].Count); piecesToRecalc.AddRange( piecesWhichThreatenSquare[x, y] ); // Now, add the new pieces threatened squares sizableArray<square> potentialDestSquares = _parentBoard[x, y].getCoveredSquares(_parentBoard); // Since our threat map is always stored from white's viewpoint, we should add or subtract // depending if threatened pieces are white or black. int mapAddition = _parentBoard[x, y].colour == pieceColour.white ? 1 : -1; // Now cycle through each move and apply them to our map, and to the piece. //_parentBoard[x, y].coveredSquares.Clear(); foreach (square potentialDstSq in potentialDestSquares) { // Add the threatened squares to our threat map this[potentialDstSq.position] += mapAddition; speedySquareList piecesWhichThreatenThisSq = piecesWhichThreatenSquare[potentialDstSq.position.x, potentialDstSq.position.y]; // update our list of pieces threatening each square piecesWhichThreatenThisSq[squarePos.flatten(x, y)] = true; // and our list of squares covered by piece _parentBoard[x, y].coveredSquares[potentialDstSq.position.flatten()] = true; } // and then recalculate pieces that need it. To save time, we don't re-evaluate everything- // we just remove extra squares based on the piece. foreach (int toRecalcPos in piecesToRecalc) { square toRecalc = _parentBoard[toRecalcPos]; int toRecalcAddition = toRecalc.colour == pieceColour.white ? 1 : -1; // Knights can never be blocked from accessing squares. if (toRecalc.type == pieceType.knight) continue; //Pawns can always access their two attack squares. if (toRecalc.type == pieceType.pawn) continue; // Remove covered squares which are on the other 'side' of us - for example, if a rook // is to our left, remove squares to our right from it. int offX = toRecalc.position.x - x; int offY = toRecalc.position.y - y; int sx; if (offX < 0) sx = +1; else if (offX > 0) sx = -1; else sx = 0; int sy; if (offY < 0) sy = +1; else if (offY > 0) sy = -1; else sy = 0; // Look right up to the edge for all pieces apart from the king, which can look only // one square in each direction. int limitx = DoktorChessAIBoard.sizeX; int limity = DoktorChessAIBoard.sizeY; if (toRecalc.type == pieceType.king) { limitx = toRecalc.position.x + (sx * 2); limity = toRecalc.position.x + (sy * 2); if (limitx > DoktorChessAIBoard.sizeX) limitx = DoktorChessAIBoard.sizeX; if (limity > DoktorChessAIBoard.sizeY) limity = DoktorChessAIBoard.sizeY; } //Debug.WriteLine( toRecalc.type + toRecalc.position + ":"); int removex = x + sx; int removey = y + sy; while(removex >= 0 && removex < limitx && removey >= 0 && removey < limity ) { squarePos toRemoveSqPos = new squarePos(removex, removey); this[toRemoveSqPos] -= toRecalcAddition; speedySquareList piecesWhichThreatenThisSq = piecesWhichThreatenSquare[removex, removey]; piecesWhichThreatenThisSq[toRecalc.position] = false; toRecalc.coveredSquares[ toRemoveSqPos ] = false; //Debug.WriteLine("Removed now-blocked " + pos); // we can threaten one piece, but no farther. if (_parentBoard[removex, removey].type != pieceType.none) break; removex += sx; removey += sy; } } sanityCheck(); }
public void remove(square toRemove) { int posDirection = toRemove.colour == pieceColour.white ? 1 : -1; // Remove the actual piece, and what it threatens. foreach (int threatenedSquareFlat in toRemove.coveredSquares ) { squarePos threatenedSquare = squarePos.unflatten(threatenedSquareFlat); this[threatenedSquare] -= posDirection; // The removed piece no longer threatens this square. piecesWhichThreatenSquare[threatenedSquare.x, threatenedSquare.y][toRemove.position] = false; } toRemove.coveredSquares.Clear(); // and now force a re-evaluation of things that threatened this square. sizableArray<int> piecesToRecalc = new sizableArray<int>(piecesWhichThreatenSquare[toRemove.position.x, toRemove.position.y].Count); piecesToRecalc.AddRange(piecesWhichThreatenSquare[toRemove.position.x, toRemove.position.y]); foreach (int toRecalcFlat in piecesToRecalc) { square toRecalc = _parentBoard[squarePos.unflatten(toRecalcFlat)]; // Knights can never be blocked from accessing squares. if (toRecalc.type == pieceType.knight) continue; //Pawns can always access their two attack squares. if (toRecalc.type == pieceType.pawn) continue; int toRecalcAddition = toRecalc.colour == pieceColour.white ? 1 : -1; // Remove covered squares which are on the other 'side' of us - for example, if a rook // is to our left, remove squares to our right from it. int offX = toRecalc.position.x - toRemove.position.x; int offY = toRecalc.position.y - toRemove.position.y; int sx; if (offX < 0) sx = +1; else if (offX > 0) sx = -1; else sx = 0; int sy; if (offY < 0) sy = +1; else if (offY > 0) sy = -1; else sy = 0; // Look right up to the edge for all pieces apart from the king, which can look only // one square in each direction. int limitx = DoktorChessAIBoard.sizeX; int limity = DoktorChessAIBoard.sizeY; if (toRecalc.type == pieceType.king) { limitx = toRecalc.position.x + (2 * sx); limity = toRecalc.position.x + (2 * sx); if (limitx > DoktorChessAIBoard.sizeX) limitx = DoktorChessAIBoard.sizeX; if (limity > DoktorChessAIBoard.sizeY) limity = DoktorChessAIBoard.sizeY; } //Debug.WriteLine(toRecalc.type + " @ " + toRecalc.position + ":"); int removex = toRemove.position.x + sx; int removey = toRemove.position.y + sy; while(removex >= 0 && removex < limitx && removey >= 0 && removey < limity ) { squarePos pos = new squarePos(removex, removey); this[pos] += toRecalcAddition; piecesWhichThreatenSquare[removex, removey][toRecalc.position] = true; toRecalc.coveredSquares[pos.flatten()] = true; //Debug.WriteLine("Added discovered " + pos); // we can threaten one piece, but no farther. if (_parentBoard[removex, removey].type != pieceType.none) break; removex += sx; removey += sy; } } sanityCheck(); }
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; }
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> /// 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; }
private void addPawnMovesToSquare(sizableArray<move> moveList, square src, square dst) { // If we are moving in to the end row, we should promote. Handle this. if (dst.position.y == (colour == pieceColour.white ? 7 : 0) ) { // OK. Promotions it is. pieceType[] promotionOptions = new[] { pieceType.queen, pieceType.rook, pieceType.knight, pieceType.bishop }; foreach (pieceType promotionOption in promotionOptions) { moveList.Add(new move(src, dst, promotionOption)); } } else { moveList.Add(new move(src, dst)); } }
/// <summary> /// Return a collecetion of all moves one player may be able to make. /// Note that moves in to check will also be returned. /// </summary> /// <param name="toMoveColour">Side to examine</param> /// <returns></returns> public sizableArray<move> getMoves(pieceColour toMoveColour) { List<square> occupiedSquares = getPiecesForColour(toMoveColour); // Generously guess the size of this array sizableArray<move> possibleMoves = new sizableArray<move>(occupiedSquares.Count * 50); // Add all moves from all pieces foreach (square occupiedSquare in occupiedSquares) possibleMoves.AddRange(occupiedSquare.getPossibleMoves(this)); return possibleMoves; }