/** Extract the PV starting from pos, using hash entries, both exact scores and bounds. */
 public string extractPV(Position pos)
 {
     string ret = "";
     pos = new Position(pos);    // To avoid modifying the input parameter
     bool first = true;
     TTEntry ent = probe(pos.historyHash());
     UndoInfo ui = new UndoInfo();
     List<ulong> hashHistory = new List<ulong>();
     bool repetition = false;
     while (ent.type != TTEntry.T_EMPTY) {
     string type = "";
     if (ent.type == TTEntry.T_LE) {
         type = "<";
     } else if (ent.type == TTEntry.T_GE) {
         type = ">";
     }
     Move m = new Move(0,0,0);
     ent.getMove(m);
     MoveGen MG = new MoveGen();
     MoveGen.MoveList moves = MG.pseudoLegalMoves(pos);
     MoveGen.RemoveIllegal(pos, moves);
     bool contains = false;
     for (int mi = 0; mi < moves.size; mi++)
         if (moves.m[mi].equals(m)) {
             contains = true;
             break;
         }
     if  (!contains)
         break;
     string moveStr = TextIO.moveTostring(pos, m, false);
     if (repetition)
         break;
     if (!first) {
         ret += " ";
     }
     ret += type + moveStr;
     pos.makeMove(m, ui);
     if (hashHistory.Contains(pos.zobristHash())) {
         repetition = true;
     }
     hashHistory.Add(pos.zobristHash());
     ent = probe(pos.historyHash());
     first = false;
     }
     return ret;
 }
Beispiel #2
0
        private static string moveTostring(Position pos, Move move, bool ulongForm, MoveGen.MoveList moves)
        {
            string ret = "";
            int wKingOrigPos = Position.getSquare(4, 0);
            int bKingOrigPos = Position.getSquare(4, 7);
            if (move.from == wKingOrigPos && pos.getPiece(wKingOrigPos) == Piece.WKING) {
            // Check white castle
            if (move.to == Position.getSquare(6, 0)) {
                    ret += ("O-O");
            } else if (move.to == Position.getSquare(2, 0)) {
                ret += ("O-O-O");
            }
            } else if (move.from == bKingOrigPos && pos.getPiece(bKingOrigPos) == Piece.BKING) {
            // Check white castle
            if (move.to == Position.getSquare(6, 7)) {
                ret += ("O-O");
            } else if (move.to == Position.getSquare(2, 7)) {
                ret += ("O-O-O");
            }
            }
            if (ret.Length == 0) {
            int p = pos.getPiece(move.from);
            ret += pieceToChar(p);
            int x1 = Position.getX(move.from);
            int y1 = Position.getY(move.from);
            int x2 = Position.getX(move.to);
            int y2 = Position.getY(move.to);
            if (ulongForm) {
                ret += ((char)(x1 + 'a'));
                ret += ((char)(y1 + '1'));
                ret += (isCapture(pos, move) ? 'x' : '-');
            } else {
                if (p == (pos.whiteMove ? Piece.WPAWN : Piece.BPAWN)) {
                    if (isCapture(pos, move)) {
                        ret += ((char)(x1 + 'a'));
                    }
                } else {
                    int numSameTarget = 0;
                    int numSameFile = 0;
                    int numSameRow = 0;
                    for (int mi = 0; mi < moves.size; mi++) {
                        Move m = moves.m[mi];
                        if (m == null)
                            break;
                        if ((pos.getPiece(m.from) == p) && (m.to == move.to)) {
                            numSameTarget++;
                            if (Position.getX(m.from) == x1)
                                numSameFile++;
                            if (Position.getY(m.from) == y1)
                                numSameRow++;
                        }
                    }
                    if (numSameTarget < 2) {
                        // No file/row info needed
                    } else if (numSameFile < 2) {
                        ret += ((char)(x1 + 'a'));   // Only file info needed
                    } else if (numSameRow < 2) {
                        ret += ((char)(y1 + '1'));   // Only row info needed
                    } else {
                        ret += ((char)(x1 + 'a'));   // File and row info needed
                        ret += ((char)(y1 + '1'));
                    }
                }
                if (isCapture(pos, move)) {
                    ret += ('x');
                }
            }
            ret += ((char)(x2 + 'a'));
            ret += ((char)(y2 + '1'));
            if (move.promoteTo != Piece.EMPTY) {
                ret += (pieceToChar(move.promoteTo));
            }
            }
            UndoInfo ui = new UndoInfo();
            if (MoveGen.givesCheck(pos, move)) {
            pos.makeMove(move, ui);
            MoveGen MG = new MoveGen();
            MoveGen.MoveList nextMoves = MG.pseudoLegalMoves(pos);
            MoveGen.RemoveIllegal(pos, nextMoves);
            if (nextMoves.size == 0) {
                ret += ('#');
            } else {
                ret += ('+');
            }
            pos.unMakeMove(move, ui);
            }

            return ret;
        }
 /**
  * Extract a list of PV moves, starting from "rootPos" and first move "m".
  */
 public List<Move> extractPVMoves(Position rootPos, Move m)
 {
     Position pos = new Position(rootPos);
     m = new Move(m);
     List<Move> ret = new List<Move>();
     UndoInfo ui = new UndoInfo();
     List<ulong> hashHistory = new List<ulong>();
     MoveGen moveGen = new MoveGen();
     while (true) {
     ret.Add(m);
     pos.makeMove(m, ui);
     if (hashHistory.Contains(pos.zobristHash())) {
         break;
     }
     hashHistory.Add(pos.zobristHash());
     TTEntry ent = probe(pos.historyHash());
     if (ent.type == TTEntry.T_EMPTY) {
         break;
     }
     m = new Move(0,0,0);
     ent.getMove(m);
     MoveGen.MoveList moves = moveGen.pseudoLegalMoves(pos);
     MoveGen.RemoveIllegal(pos, moves);
     bool contains = false;
     for (int mi = 0; mi < moves.size; mi++)
         if (moves.m[mi].equals(m)) {
             contains = true;
             break;
         }
     if  (!contains)
         break;
     }
     return ret;
 }
 /** Check if a draw claim is allowed, possibly after playing "move".
  * @param move The move that may have to be made before claiming draw.
  * @return The draw string that claims the draw, or empty string if draw claim not valid.
  */
 private string canClaimDraw(Position pos, ulong[] posHashList, int posHashListSize, Move move)
 {
     string drawStr = "";
     if (Search.canClaimDraw50(pos)) {
     drawStr = "draw 50";
     } else if (Search.canClaimDrawRep(pos, posHashList, posHashListSize, posHashListSize)) {
     drawStr = "draw rep";
     } else {
     string strMove = TextIO.moveTostring(pos, move, false);
     posHashList[posHashListSize++] = pos.zobristHash();
     UndoInfo ui = new UndoInfo();
     pos.makeMove(move, ui);
     if (Search.canClaimDraw50(pos)) {
         drawStr = "draw 50 " + strMove;
     } else if (Search.canClaimDrawRep(pos, posHashList, posHashListSize, posHashListSize)) {
         drawStr = "draw rep " + strMove;
     }
     pos.unMakeMove(move, ui);
     }
     return drawStr;
 }
Beispiel #5
0
 /**
  * Remove all illegal moves from moveList.
  * "moveList" is assumed to be a list of pseudo-legal moves.
  * This function removes the moves that don't defend from check threats.
  */
 public static void RemoveIllegal(Position pos, MoveList moveList)
 {
     int length = 0;
     UndoInfo ui = new UndoInfo();
     for (int mi = 0; mi < moveList.size; mi++) {
     Move m = moveList.m[mi];
     pos.makeMove(m, ui);
     pos.setWhiteMove(!pos.whiteMove);
     if (!inCheck(pos))
         moveList.m[length++].copyFrom(m);
     pos.setWhiteMove(!pos.whiteMove);
     pos.unMakeMove(m, ui);
     }
     moveList.size = length;
 }
Beispiel #6
0
 private bool handleDrawCmd(string drawCmd)
 {
     if (drawCmd.StartsWith("rep") || drawCmd.StartsWith("50")) {
     bool rep = drawCmd.StartsWith("rep");
     Move m = null;
     string ms = drawCmd.Substring(drawCmd.IndexOf(" ") + 1);
     if (ms.Length > 0) {
         m = TextIO.stringToMove(pos, ms);
     }
     bool valid;
     if (rep) {
         valid = false;
         List<Position> oldPositions = new List<Position>();
         Position tmpPos;
         if (m != null) {
             UndoInfo ui = new UndoInfo();
             tmpPos = new Position(pos);
             tmpPos.makeMove(m, ui);
             oldPositions.Add(tmpPos);
         }
         oldPositions.Add(pos);
         tmpPos = pos;
         for (int i = currentMove - 1; i >= 0; i--) {
             tmpPos = new Position(tmpPos);
             tmpPos.unMakeMove(moveList[i], uiInfoList[i]);
             oldPositions.Add(tmpPos);
         }
         int repetitions = 0;
         Position firstPos = oldPositions[0];
         for(int i=0; i<oldPositions.Count; i++)
         {
             Position p=oldPositions[i];
             if (p.drawRuleEquals(firstPos))
                 repetitions++;
         }
         if (repetitions >= 3) {
             valid = true;
         }
     } else {
         Position tmpPos = new Position(pos);
         if (m != null) {
             UndoInfo ui = new UndoInfo();
             tmpPos.makeMove(m, ui);
         }
         valid = tmpPos.halfMoveClock >= 100;
     }
     if (valid) {
         drawState = rep ? GameState.DRAW_REP : GameState.DRAW_50;
         drawStateMoveStr = null;
         if (m != null) {
             drawStateMoveStr = TextIO.moveTostring(pos, m, false);
         }
     } else {
         pendingDrawOffer = true;
         if (m != null) {
             processstring(ms);
         }
     }
     return true;
     } else if (drawCmd.StartsWith("offer ")) {
     pendingDrawOffer = true;
     string ms = drawCmd.Substring(drawCmd.IndexOf(" ") + 1);
     if (TextIO.stringToMove(pos, ms) != null) {
         processstring(ms);
     }
     return true;
     } else if (drawCmd=="accept") {
     if (haveDrawOffer()) {
         drawState = GameState.DRAW_AGREE;
     }
     return true;
     } else {
     return false;
     }
 }
        /** Search a position and return the best move and score. Used for test suite processing. */
        public TwoReturnValues<Move, string> searchPosition(Position pos, int maxTimeMillis)
        {
            // Create a search object
            ulong[] posHashList = new ulong[200];
            tt.nextGeneration();
            Search sc = new Search(pos, posHashList, 0, tt);

            // Determine all legal moves
            MoveGen.MoveList moves = new MoveGen().pseudoLegalMoves(pos);
            MoveGen.RemoveIllegal(pos, moves);
            sc.scoreMoveList(moves, 0);

            // Find best move using iterative deepening
            sc.timeLimit(maxTimeMillis, maxTimeMillis);
            Move bestM = sc.iterativeDeepening(moves, -1, -1, false);

            // Extract PV
            string PV = TextIO.moveTostring(pos, bestM, false) + " ";
            UndoInfo ui = new UndoInfo();
            pos.makeMove(bestM, ui);
            PV += tt.extractPV(pos);
            pos.unMakeMove(bestM, ui);

            //        tt.printStats();

            // Return best move and PV
            return new TwoReturnValues<Move, string>(bestM, PV);
        }
Beispiel #8
0
        /**
         * Update the game state according to move/command string from a player.
         * @param str The move or command to process.
         * @return True if str was understood, false otherwise.
         */
        public bool processstring(string str)
        {
            if (handleCommand(str)) {
            return true;
            }
            if (getGameState() != GameState.ALIVE) {
            return false;
            }

            Move m = TextIO.stringToMove(pos, str);
            if (m == null) {
            return false;
            }

            UndoInfo ui = new UndoInfo();
            pos.makeMove(m, ui);
            TextIO.fixupEPSquare(pos);
            while (currentMove < moveList.Count) {
            moveList.RemoveAt(currentMove);
            uiInfoList.RemoveAt(currentMove);
            drawOfferList.RemoveAt(currentMove);
            }
            moveList.Add(m);
            uiInfoList.Add(ui);
            drawOfferList.Add(pendingDrawOffer);
            pendingDrawOffer = false;
            currentMove++;
            return true;
        }
Beispiel #9
0
 static ulong perfT(MoveGen moveGen, Position pos, int depth)
 {
     if (depth == 0)
     return 1;
     ulong nodes = 0;
     MoveGen.MoveList moves = moveGen.pseudoLegalMoves(pos);
     MoveGen.RemoveIllegal(pos, moves);
     if (depth == 1) {
     int ret = moves.size;
     moveGen.returnMoveList(moves);
     return (ulong)ret;
     }
     UndoInfo ui = new UndoInfo();
     for (int mi = 0; mi < moves.size; mi++) {
     Move m = moves.m[mi];
     pos.makeMove(m, ui);
     nodes += perfT(moveGen, pos, depth - 1);
     pos.unMakeMove(m, ui);
     }
     moveGen.returnMoveList(moves);
     return nodes;
 }
Beispiel #10
0
        public string getMoveListstring(bool compressed)
        {
            string ret = "";

            // Undo all moves in move history.
            Position pos2 = new Position(pos /*this.pos*/);
            for (int i = currentMove; i > 0; i--) {
            pos2.unMakeMove(moveList[i - 1], uiInfoList[i - 1]);
            }

            // Print all moves
            string whiteMove = "";
            string blackMove = "";
            for (int i = 0; i < currentMove; i++) {
            Move move = moveList[i];
            string strMove = TextIO.moveTostring(pos2, move, false);
            if (drawOfferList[i]) {
                strMove += " (d)";
            }
            if (pos2.whiteMove) {
                whiteMove = strMove;
            } else {
                blackMove = strMove;
                if (whiteMove.Length == 0) {
                    whiteMove = "...";
                }
                if (compressed) {
                    ret += pos2.fullMoveCounter.ToString() + ". " +
                        whiteMove + " " + blackMove + " ";
                } else {
                    ret += pos2.fullMoveCounter.ToString() + ".   " +
                        whiteMove.PadRight(10) + " " + blackMove.PadRight(10) + " ";
                }
                whiteMove = "";
                blackMove = "";
            }
            UndoInfo ui = new UndoInfo();
            pos2.makeMove(move, ui);
            }
            if ((whiteMove.Length > 0) || (blackMove.Length > 0)) {
            if (whiteMove.Length == 0) {
                whiteMove = "...";
            }

                if (compressed)
                {
                    ret += pos2.fullMoveCounter.ToString() + ". " +
                        whiteMove + " " + blackMove + " ";
                }
                else
                {
                    ret += pos2.fullMoveCounter.ToString() + ".   " +
                        whiteMove.PadRight(10) + " " + blackMove.PadRight(10) + " ";
                }

            }
            string gameResult = getPGNResultstring();
            if (gameResult != "*")
            {
            ret += gameResult;
               }
            return ret;
        }
Beispiel #11
0
        public List<string> getPosHistory()
        {
            List<string> ret = new List<string>();

            Position pos2 = new Position( pos /*this.pos*/);
            for (int i = currentMove; i > 0; i--) {
            pos2.unMakeMove(moveList[i - 1], uiInfoList[i - 1]);
            }
            ret.Add(TextIO.toFEN(pos2)); // Store initial FEN

            string moves = "";
            for (int i = 0; i < moveList.Count; i++) {
            Move move = moveList[i];
            string strMove = TextIO.moveTostring(pos2, move, false);
            moves += " " + strMove;
            UndoInfo ui = new UndoInfo();
            pos2.makeMove(move, ui);
            }
            ret.Add(moves); // Store move list string
            int numUndo = moveList.Count - currentMove;
            ret.Add(((int)numUndo).ToString());
            return ret;
        }
Beispiel #12
0
        public void unMakeSEEMove(Move move, UndoInfo ui)
        {
            whiteMove = !whiteMove;
            int p = squares[move.to];
            setSEEPiece(move.from, p);
            setSEEPiece(move.to, ui.capturedPiece);
            bool wtm = whiteMove;

            // Handle castling
            int king = wtm ? Piece.WKING : Piece.BKING;
            if (p == king) {
            int k0 = move.from;
            if (move.to == k0 + 2) { // O-O
                setSEEPiece(k0 + 3, squares[k0 + 1]);
                setSEEPiece(k0 + 1, Piece.EMPTY);
            } else if (move.to == k0 - 2) { // O-O-O
                setSEEPiece(k0 - 4, squares[k0 - 1]);
                setSEEPiece(k0 - 1, Piece.EMPTY);
            }
            }

            // Handle en passant
            if (move.to == epSquare) {
            if (p == Piece.WPAWN) {
                setSEEPiece(move.to - 8, Piece.BPAWN);
            } else if (p == Piece.BPAWN) {
                setSEEPiece(move.to + 8, Piece.WPAWN);
            }
            }
        }
Beispiel #13
0
        public void unMakeMove(Move move, UndoInfo ui)
        {
            hashKey ^= whiteHashKey;
            whiteMove = !whiteMove;
            int p = squares[move.to];
            setPiece(move.from, p);
            setPiece(move.to, ui.capturedPiece);
            setCastleMask(ui.castleMask);
            setEpSquare(ui.epSquare);
            halfMoveClock = ui.halfMoveClock;
            bool wtm = whiteMove;
            if (move.promoteTo != Piece.EMPTY) {
            p = wtm ? Piece.WPAWN : Piece.BPAWN;
            setPiece(move.from, p);
            }
            if (!wtm) {
            fullMoveCounter--;
            }

            // Handle castling
            int king = wtm ? Piece.WKING : Piece.BKING;
            if (p == king) {
            int k0 = move.from;
            if (move.to == k0 + 2) { // O-O
                movePieceNotPawn(k0 + 1, k0 + 3);
            } else if (move.to == k0 - 2) { // O-O-O
                movePieceNotPawn(k0 - 1, k0 - 4);
            }
            }

            // Handle en passant
            if (move.to == epSquare) {
            if (p == Piece.WPAWN) {
                setPiece(move.to - 8, Piece.BPAWN);
            } else if (p == Piece.BPAWN) {
                setPiece(move.to + 8, Piece.WPAWN);
            }
            }
        }
Beispiel #14
0
        /**
         * Apply a move to the current position.
         * Special version that only updates enough of the state for the SEE function to be happy.
         */
        public void makeSEEMove(Move move, UndoInfo ui)
        {
            ui.capturedPiece = squares[move.to];
            bool wtm = whiteMove;

            int p = squares[move.from];
            ulong fromMask = 1UL << move.from;

            // Handle castling
            if (((pieceTypeBB[Piece.WKING] | pieceTypeBB[Piece.BKING]) & fromMask) != 0) {
            int k0 = move.from;
            if (move.to == k0 + 2) { // O-O
                setSEEPiece(k0 + 1, squares[k0 + 3]);
                setSEEPiece(k0 + 3, Piece.EMPTY);
            } else if (move.to == k0 - 2) { // O-O-O
                setSEEPiece(k0 - 1, squares[k0 - 4]);
                setSEEPiece(k0 - 4, Piece.EMPTY);
            }
            }

            // Handle en passant
            if (move.to == epSquare) {
            if (p == Piece.WPAWN) {
                setSEEPiece(move.to - 8, Piece.EMPTY);
            } else if (p == Piece.BPAWN) {
                setSEEPiece(move.to + 8, Piece.EMPTY);
            }
            }

            // Perform move
            setSEEPiece(move.from, Piece.EMPTY);
            setSEEPiece(move.to, p);
            whiteMove = !wtm;
        }
Beispiel #15
0
        /** Apply a move to the current position. */
        public void makeMove(Move move, UndoInfo ui)
        {
            ui.capturedPiece = squares[move.to];
            ui.castleMask = castleMask;
            ui.epSquare = epSquare;
            ui.halfMoveClock = halfMoveClock;
            bool wtm = whiteMove;

            int p = squares[move.from];
            int capP = squares[move.to];
            ulong fromMask = 1UL << move.from;

            int prevEpSquare = epSquare;
            setEpSquare(-1);

            if ((capP != Piece.EMPTY) || (((pieceTypeBB[Piece.WPAWN] | pieceTypeBB[Piece.BPAWN]) & fromMask) != 0)) {
            halfMoveClock = 0;

            // Handle en passant and epSquare
            if (p == Piece.WPAWN) {
                if (move.to - move.from == 2 * 8) {
                    int x = Position.getX(move.to);
                    if (    ((x > 0) && (squares[move.to - 1] == Piece.BPAWN)) ||
                            ((x < 7) && (squares[move.to + 1] == Piece.BPAWN))) {
                        setEpSquare(move.from + 8);
                    }
                } else if (move.to == prevEpSquare) {
                    setPiece(move.to - 8, Piece.EMPTY);
                }
            } else if (p == Piece.BPAWN) {
                if (move.to - move.from == -2 * 8) {
                    int x = Position.getX(move.to);
                    if (    ((x > 0) && (squares[move.to - 1] == Piece.WPAWN)) ||
                            ((x < 7) && (squares[move.to + 1] == Piece.WPAWN))) {
                        setEpSquare(move.from - 8);
                    }
                } else if (move.to == prevEpSquare) {
                    setPiece(move.to + 8, Piece.EMPTY);
                }
            }

            if (((pieceTypeBB[Piece.WKING] | pieceTypeBB[Piece.BKING]) & fromMask) != 0) {
                if (wtm) {
                    setCastleMask(castleMask & ~(1 << Position.A1_CASTLE));
                    setCastleMask(castleMask & ~(1 << Position.H1_CASTLE));
                } else {
                    setCastleMask(castleMask & ~(1 << Position.A8_CASTLE));
                    setCastleMask(castleMask & ~(1 << Position.H8_CASTLE));
                }
            }

            // Perform move
            setPiece(move.from, Piece.EMPTY);
            // Handle promotion
            if (move.promoteTo != Piece.EMPTY) {
                setPiece(move.to, move.promoteTo);
            } else {
                setPiece(move.to, p);
            }
            } else {
            halfMoveClock++;

            // Handle castling
            if (((pieceTypeBB[Piece.WKING] | pieceTypeBB[Piece.BKING]) & fromMask) != 0) {
                int k0 = move.from;
                if (move.to == k0 + 2) { // O-O
                    movePieceNotPawn(k0 + 3, k0 + 1);
                } else if (move.to == k0 - 2) { // O-O-O
                    movePieceNotPawn(k0 - 4, k0 - 1);
                }
                if (wtm) {
                    setCastleMask(castleMask & ~(1 << Position.A1_CASTLE));
                    setCastleMask(castleMask & ~(1 << Position.H1_CASTLE));
                } else {
                    setCastleMask(castleMask & ~(1 << Position.A8_CASTLE));
                    setCastleMask(castleMask & ~(1 << Position.H8_CASTLE));
                }
            }

            // Perform move
            movePieceNotPawn(move.from, move.to);
            }
            if (!wtm) {
            fullMoveCounter++;
            }

            // Update castling rights when rook moves
            if ((BitBoard.maskCorners & fromMask) != 0) {
            int rook = wtm ? Piece.WROOK : Piece.BROOK;
            if (p == rook)
                removeCastleRights(move.from);
            }
            if ((BitBoard.maskCorners & (1UL << move.to)) != 0) {
            int oRook = wtm ? Piece.BROOK : Piece.WROOK;
            if (capP == oRook)
                removeCastleRights(move.to);
            }

            hashKey ^= whiteHashKey;
            whiteMove = !wtm;
        }
Beispiel #16
0
 private void initBook(bool verbose)
 {
     bookMap = new Dictionary<ulong, List<BookEntry>>();
     long t0 = SystemHelper.currentTimeMillis();
     numBookMoves = 0;
     try {
     /* read /book.bin into buf */
     Byte[] buf = Bookbin.DATA;
     Position startPos = TextIO.readFEN(TextIO.startPosFEN);
     Position pos = new Position(startPos);
     UndoInfo ui = new UndoInfo();
     int len = buf.Length;
     for (int i = 0; i < len; i += 2) {
         int b0 = buf[i]; if (b0 < 0) b0 += 256;
         int b1 = buf[i+1]; if (b1 < 0) b1 += 256;
         int move = (b0 << 8) + b1;
         if (move == 0) {
             pos = new Position(startPos);
         } else {
             bool bad = ((move >> 15) & 1) != 0;
             int prom = (move >> 12) & 7;
             Move m = new Move(move & 63, (move >> 6) & 63,
                               promToPiece(prom, pos.whiteMove));
             if (!bad)
                 addToBook(pos, m);
             pos.makeMove(m, ui);
         }
     }
     } catch (ChessParseError ex) {
     throw new RuntimeException();
     } catch (IOException ex) {
     SystemHelper.println("Can't read opening book resource");
     throw new RuntimeException();
     }
     if (verbose) {
     long t1 = SystemHelper.currentTimeMillis();
     SystemHelper.printf("Book moves: " + numBookMoves.ToString() +
         "(parse time: " + ((t1 - t0) / 1000).ToString() + ")" );
     }
 }