1

I made a fully functional chess game in Unity using C#. Now i want to add AI, for the chess engine i went with Stockfish. I got the engine inside the game but it does nothing because it cant communicate with the board.

To communicate i need to make a FEN string per row, starting on the top left, the FEN string looks like this:

rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1

lower case are black pieces, upper case white pieces, the numbers are black spaces, w means white turn next, KQkq means that castling in available, - means en passant is available and 0 1 number of moves.

Does anyone know of a tutorial, or tips to create and manipulate strings to make the FEN string?

I will paste the code i have done so far towards the Stockfish Process, i haven't done anything related to the FEN string because i don't really know how to start it.

Any links or tips are welcome

void RunProcess()
{
    ProcessStartInfo startInfo = new ProcessStartInfo();
    startInfo.UseShellExecute = false;
    startInfo.RedirectStandardInput = true;
    startInfo.RedirectStandardOutput = true;
    startInfo.RedirectStandardError = false;
    startInfo.CreateNoWindow = true;
    startInfo.FileName = Application.streamingAssetsPath + "/stockfish_9_x64.exe";

    Process process = new Process();
    process.StartInfo = startInfo;
    process.Start();

    string output;

    process.StandardInput.WriteLine("uci");
    process.StandardInput.WriteLine("isready");
    process.StandardInput.WriteLine("position fen rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
    process.StandardInput.WriteLine("go");
    process.StandardInput.WriteLine("stop");
    process.StandardInput.WriteLine("quit");

    do
    {
        output = process.StandardOutput.ReadLine();
    } while (!output.Contains("move"));

    UnityEngine.Debug.Log(output);
}

void OnMouseDown()
{
    RunProcess();
}
manlio
  • 18,345
  • 14
  • 76
  • 126
  • https://github.com/jpbruyere/Chess is a stockfish client, but I don't use FEN string, I use position commands instead. – j-p Jan 31 '19 at 17:09
  • What are you having trouble with? Constructing a string, determining which pieces are where on the board in order to construct a string? Something else? – Colin Young Jan 31 '19 at 17:49
  • @ColinYoung Constructing the string. i dont know how to start it. I thought on making a switch case per piece, to assign a char to each piece. But i dont know if that would work or not. – Alvaro alamillo vargas Feb 01 '19 at 10:29

2 Answers2

2

Just to get the basic piece, you could do something like (note: not tested):

public enum ChessPieces
{
    King, Queen, Rook, // ... etc. 
}

public class ChessPiece : MonoBehavior
{
    public string FenId { get; }

    private readonly Dictionary<ChessPiece, string> FenIds = {
        { ChessPieces.King, "K" },
        { ChessPieces.Queen, "Q" },
        // ... etc.
    };

    // assuming you create the set of pieces programatically, use this constructor
    public ChessPiece(ChessPiece piece, ChessColor color)
    {
        FenId = color == ChessColor.Black 
            ? FenIds[piece].ToLower() 
            : FenIds[piece].ToUpper();
    }
}

Then, assuming you are storing your board in an array of rows, to dump the layout into a string I'd probably override ToString on my ChessBoard class (also not tested):

// somewhere in your code set the board up
_chessBoard.Rows.Add(new [] {
    new ChessPiece(ChessPieces.Rook, ChessColor.Black),
    new ChessPiece(ChessPieces.Knight, ChessColor.Black),
    // ... etc.
    })
_chessBoard.Rows.Add(new [] { /* next row ... */ });
// ... etc.

// to create your output, put this into the override of ToString:
var output = ""; // should be StringBuilder, but for clarity and since this isn't likely performance limiting...
var rowIndex = 0;
foreach (var row in _chessBoard.Rows)
{
    rowIndex++;
    var blankSpaces = 0;

    foreach(var piece in row)
    {
        if (piece == null) 
        {
            blankSpaces++;
        }
        else
        {
            output += blankSpaces == 0 
                ? piece.FenId
                : string.Format("{0}{1}", blankspaces, piece.FenId);
            blankSpaces = 0;
        }

        if (blankSpaces > 0)
        {
            output += blankSpaces;
        }
    }

    if (rowIndex != 8)
    {
        output += "/";
    }
}

At this point you've got your basic layout in a string and you should have the basic idea for adding the other FEN fields.

I should note that I've selected a collection of arrays for storing your board. That probably isn't the most efficient storage mechanism (i.e. in the best case you're storing 50% empty values, which will only increase as the game progresses), but since we're only talking about 64 items total we're probably okay on memory.

Colin Young
  • 3,018
  • 1
  • 22
  • 46
0
public void LoadFEN(string fen)
{
    //Removes all pieces
    foreach (Piece piece in pieces)
    {
        Destroy(piece.gameObject);
    }

    pieces = new List<Piece>();
    AddSquareCoordinates(); // Add "local" coordinates to all squares

    #region FENStuff
    int xPos = 0;
    int yPos = 7;
    string[] fenChunks = fen.Split(' '); //Fen parts separated
    for (int x = 0; x < fenChunks[0].Length; x++)
    {
        switch (fenChunks[0][x])
        {
            case 'K':
                PlacePiece(PieceType.King, xPos, yPos, -1);
                break;
            case 'k':
                PlacePiece(PieceType.King, xPos, yPos, 1);
                break;
            case 'Q':
                PlacePiece(PieceType.Queen, xPos, yPos, -1);
                break;
            case 'q':
                PlacePiece(PieceType.Queen, xPos, yPos, 1);
                break;
            case 'R':
                PlacePiece(PieceType.Rook, xPos, yPos, -1);
                break;
            case 'r':
                PlacePiece(PieceType.Rook, xPos, yPos, 1);
                break;
            case 'N':
                PlacePiece(PieceType.Knight, xPos, yPos, -1);
                break;
            case 'n':
                PlacePiece(PieceType.Knight, xPos, yPos, 1);
                break;
            case 'B':
                PlacePiece(PieceType.Bishop, xPos, yPos, -1);
                break;
            case 'b':
                PlacePiece(PieceType.Bishop, xPos, yPos, 1);
                break;
            case 'P':
                PlacePiece(PieceType.Pawn, xPos, yPos, -1);
                break;
            case 'p':
                PlacePiece(PieceType.Pawn, xPos, yPos, 1);
                break;
        }

        if (char.IsDigit(fenChunks[0][x]))
        {
            xPos += (int)char.GetNumericValue(fen[x]);
        }
        else
            xPos += 1;

        if (fenChunks[0][x] == '/')
        {
            yPos -= 1;
            xPos = 0;
        }
    }

    SetStartPiecesCoor(); // Update all piece's coordinate
    AddCastleRooks(); // Add rooks to the king piece
    PawnFirstSquareAdjust(); //Checks if the pawns have already moved

    curTurn = fenChunks[1] == "w" ? -1 : 1;

    //fen cadtling priviledges code
    Piece kingWhite = GetKingPiece(-1);
    Piece kingBlack = GetKingPiece(1);
    bool castleWhiteKing = true, castleWhiteQueen = true, castleBlackKing = true, castleBlackQueen = true;
    for(int i = 0; i < fenChunks[2].Length; i++)
    {
        switch(fenChunks[2][i])
        {
            case 'K':
                castleWhiteKing = false;
                break;
            case 'Q':
                castleWhiteQueen = false;
                break;
            case 'k':
                castleBlackKing = false;
                break;
            case 'q':
                castleBlackQueen = false;
                break;
        }
    }

    kingWhite.started = castleWhiteKing && castleWhiteQueen;
    if(kingWhite.castlingRooks[0] != null)
        kingWhite.castlingRooks[0].started = castleWhiteKing;
    if(kingWhite.castlingRooks[1] != null)
        kingWhite.castlingRooks[1].started = castleWhiteQueen;

    kingBlack.started = castleBlackKing && castleBlackQueen;
    if (kingBlack.castlingRooks[1] != null)
        kingBlack.castlingRooks[0].started = castleBlackKing;
    if (kingBlack.castlingRooks[1] != null)
        kingBlack.castlingRooks[1].started = castleBlackQueen;

    if (fenChunks[3] != "-")
    {
        string coordinate = fenChunks[3];
        string row = coordinate[1] == '3' ? "4" : "5";
        coordinate = coordinate[0] + row;
        GetSquareFromLetterCoordinate(coordinate).holdingPiece.enPassantAvailable = true;            
    }

    halfMoveClock = Convert.ToInt32(fenChunks[4]);
    fullMoveClock = Convert.ToInt32(fenChunks[5]);

    #endregion
    UpdateGameTheme(curTheme);
}

public void ExportFEN()
{
    int freeCellCount = 0;
    fen = "";
    for (int y = 7; y > -1; y--)
    {
        for (int x = 0; x < 8; x++)
        {
            Piece piece = GetSquareFromCoordinate(new Vector2Int(x, y)).holdingPiece;
            if (piece == null)
            {
                freeCellCount += 1;
            }
            else
            {
                if (freeCellCount != 0)
                {
                    fen += freeCellCount.ToString();
                    freeCellCount = 0;
                }
                if (piece.pieceType == PieceType.King)
                {
                    if (piece.team == -1)
                        fen += "K";
                    else
                        fen += "k";
                }
                else if (piece.pieceType == PieceType.Queen)
                {
                    if (piece.team == -1)
                        fen += "Q";
                    else
                        fen += "q";
                }
                else if (piece.pieceType == PieceType.Rook)
                {
                    if (piece.team == -1)
                        fen += "R";
                    else
                        fen += "r";
                }
                else if (piece.pieceType == PieceType.Bishop)
                {
                    if (piece.team == -1)
                        fen += "B";
                    else
                        fen += "b";
                }
                else if (piece.pieceType == PieceType.Knight)
                {
                    if (piece.team == -1)
                        fen += "N";
                    else
                        fen += "n";
                }
                else if (piece.pieceType == PieceType.Pawn)
                {
                    if (piece.team == -1)
                        fen += "P";
                    else
                        fen += "p";
                }

            }
        }
        if (freeCellCount != 0)
        {
            fen += freeCellCount.ToString();
        }
        freeCellCount = 0;
        if (y != 0)
            fen += '/';
    }

    fen += " ";
    string turnChar = curTurn == -1 ? "w" : "b";
    fen += turnChar + " ";

    Piece kingWhite = GetKingPiece(-1);
    Piece kingBlack = GetKingPiece(1);

    if (!kingWhite.started)
    {
        if (kingWhite.castlingRooks[0] != null && !kingWhite.castlingRooks[0].started)
            fen += "K";
        if (kingWhite.castlingRooks[1] != null && !kingWhite.castlingRooks[1].started)
            fen += "Q";
    }
    if (!kingBlack.started)
    {
        if (kingBlack.castlingRooks[0] != null && !kingBlack.castlingRooks[0].started)
            fen += "k";
        if (kingBlack.castlingRooks[1] != null && !kingBlack.castlingRooks[1].started)
            fen += "q";
    }
    fen += " ";

    fen += enPassantSquare + " ";

    fen += halfMoveClock.ToString() + " " + fullMoveClock.ToString();
}

private void PlacePiece(PieceType type, int xCoord, int yCoord, int team)
{
    Square square = GetSquareFromCoordinate(new Vector2Int(xCoord, yCoord));
    GameObject pieceObj;
    Piece piece;
    int prefabIndex = -1;
    switch (type)
    {
        case PieceType.King:
            prefabIndex = 0;
            break;
        case PieceType.Queen:
            prefabIndex = 1;
            break;
        case PieceType.Rook:
            prefabIndex = 2;
            break;
        case PieceType.Knight:
            prefabIndex = 3;
            break;
        case PieceType.Bishop:
            prefabIndex = 4;
            break;
        case PieceType.Pawn:
            prefabIndex = 5;
            break;
    }

    pieceObj = Instantiate(piecePrefabs[prefabIndex], pieceParent.transform);
    pieceObj.transform.position = square.transform.position;

    piece = pieceObj.GetComponent<Piece>();

    piece.team = team;
    piece.curSquare = square;
    piece.board = this;
    pieces.Add(piece);
}

private void AddCastleRooks()
{
    foreach (Piece piece in pieces)
    {

        if (piece.pieceType == PieceType.King)
        {
            if (piece.team == -1)
            {
                Piece rook1 = GetSquareFromCoordinate(new Vector2Int(7, 0)).holdingPiece;
                if (rook1 != null && rook1.pieceType == PieceType.Rook)
                    piece.castlingRooks.Add(rook1);
                else piece.castlingRooks.Add(null);

                Piece rook2 = GetSquareFromCoordinate(new Vector2Int(0, 0)).holdingPiece;
                if (rook2 != null && rook1.pieceType == PieceType.Rook)
                    piece.castlingRooks.Add(rook2);
                else piece.castlingRooks.Add(null);

            }
            else
            {
                Piece rook1 = GetSquareFromCoordinate(new Vector2Int(7, 7)).holdingPiece;
                if (rook1 != null && rook1.pieceType == PieceType.Rook)
                    piece.castlingRooks.Add(rook1);
                else piece.castlingRooks.Add(null);


                Piece rook2 = GetSquareFromCoordinate(new Vector2Int(0, 7)).holdingPiece;
                if (rook2 != null && rook1.pieceType == PieceType.Rook)
                    piece.castlingRooks.Add(rook2);
                else piece.castlingRooks.Add(null);

            }
        }
    }
}

private void PawnFirstSquareAdjust()
{
    int startRank;

    foreach (Piece piece in pieces)
    {
        startRank = piece.team == -1 ? 1 : 6;

        if (piece.pieceType == PieceType.Pawn)
        {
            if (piece.curSquare.coor.y != startRank)
            {
                piece.started = true;
            }
        }
    }
}

I was also developing my chess app and I know it might be late. But hope this helps.

I have PieceType as an enum. I guess you can figure out the variables.

Also I reset the moveClocks in the piece Move() function.