3

I'm working on my own chess engine in C#. Actually I'm searching bugs on my move generator, but I realized that my actual chess system is too slow (even 21 minutes on perft(6)). Here is my Github repository.

I'm using a simple hierarchy for pieces and a board class that implements a list of pieces. Because of the object-oriented nature of this project, I chose to not use a multi-dimensional matrix to represent the board, given that each piece has its own position inside them. The problem is that to get a piece from the board, knowing its position, it takes O(n), where n is the number of pieces currently on the board.

In the move generator I get all possible moves assuming an empy board, and then I check them with a static class (because a piece shouldn't care about board state). I visited some sites, include the chess programming Wiki. I saw that there are many types of board representations, but in my actual state I don't know which is the best (performance and simplicity). I think this is all, I hope you will help me :)

I welcome any advice regarding my project ;) Thanks all.

Here is my board class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Chess_Engine___NOGUI
{
    class Board
    {
        public const byte BoardSize = 8;
        private Game game;
        private List<Piece> pieceList = new List<Piece>();
        private List<Piece> whitePieceList = new List<Piece>();
        private List<Piece> blackPieceList = new List<Piece>();

        public Board(Game game)
        {
            this.game = game;
        }

        public void AddPiece(Piece piece)
        {
            pieceList.Add(piece);
            switch (piece.Color)
            {
                case PieceColor.Black:
                    blackPieceList.Add(piece);
                    break;
                case PieceColor.White:
                    whitePieceList.Add(piece);
                    break;
            }
        }
        public void RemovePiece(Piece piece)
        {
            pieceList.Remove(piece);
            switch (piece.Color)
            {
                case PieceColor.Black:
                    blackPieceList.Remove(piece);
                    break;
                case PieceColor.White:
                    whitePieceList.Remove(piece);
                    break;
            }
        }

        public Square GetKingPosition(PieceColor color)
        {
            if (color == PieceColor.White)
                foreach (Piece piece in whitePieceList)
                {
                    if (piece.Type == PieceType.King)
                        return piece.square;
                }
            else
                foreach (Piece piece in blackPieceList)
                {
                    if (piece.Type == PieceType.King)
                        return piece.square;
                }

            throw new Exception("il re deve essere sempre presente");
        }
        public Piece GetPiece(Square square, PieceColor color = PieceColor.None)
        {
            switch (color)
            {
                case PieceColor.White:
                    {
                        foreach (Piece piece in whitePieceList)
                        {
                            if (piece.square == square)
                                return piece;
                        }
                        return new NullPiece(square);
                    }
                case PieceColor.Black:
                    {
                        foreach (Piece piece in blackPieceList)
                        {
                            if (piece.square == square)
                                return piece;
                        }
                        return new NullPiece(square);
                    }
                default:
                    {
                        foreach (Piece piece in pieceList)
                        {
                            if (piece.square == square)
                                return piece;
                        }
                        return new NullPiece(square);
                    }
            }
        }
        public List<Piece> GetPieceList(PieceColor color)
        {
            switch (color)
            {
                case PieceColor.Black:
                    return blackPieceList;
                case PieceColor.White:
                    return whitePieceList;
                default:
                    return pieceList;
            }
        }
        public int GetNumberOfPieces(PieceType type, PieceColor color)
        {
            int num = 0;
            foreach (Piece piece in GetPieceList(color))
            {
                if (piece.Type == type)
                    num++;
            }

            return num;
        }
        public bool IsEmpty(Square square)
        {
            if ((GetPiece(square)) is NullPiece)
            {
                return true;
            }
            return false;
        }
        public void Equip()
        {
            this.Clear();

            PieceFactory pieceFactory = new PieceFactory();
            /*PEDONI*/
            AddPiece(pieceFactory.Create(PieceType.Pawn, PieceColor.White, new Square(0, 1)));
            AddPiece(pieceFactory.Create(PieceType.Pawn, PieceColor.White, new Square(1, 1)));
            AddPiece(pieceFactory.Create(PieceType.Pawn, PieceColor.White, new Square(2, 1)));
            AddPiece(pieceFactory.Create(PieceType.Pawn, PieceColor.White, new Square(3, 1)));
            AddPiece(pieceFactory.Create(PieceType.Pawn, PieceColor.White, new Square(4, 1)));
            AddPiece(pieceFactory.Create(PieceType.Pawn, PieceColor.White, new Square(5, 1)));
            AddPiece(pieceFactory.Create(PieceType.Pawn, PieceColor.White, new Square(6, 1)));
            AddPiece(pieceFactory.Create(PieceType.Pawn, PieceColor.White, new Square(7, 1)));
            //
            AddPiece(pieceFactory.Create(PieceType.Pawn, PieceColor.Black, new Square(0, 6)));
            AddPiece(pieceFactory.Create(PieceType.Pawn, PieceColor.Black, new Square(1, 6)));
            AddPiece(pieceFactory.Create(PieceType.Pawn, PieceColor.Black, new Square(2, 6)));
            AddPiece(pieceFactory.Create(PieceType.Pawn, PieceColor.Black, new Square(3, 6)));
            AddPiece(pieceFactory.Create(PieceType.Pawn, PieceColor.Black, new Square(4, 6)));
            AddPiece(pieceFactory.Create(PieceType.Pawn, PieceColor.Black, new Square(5, 6)));
            AddPiece(pieceFactory.Create(PieceType.Pawn, PieceColor.Black, new Square(6, 6)));
            AddPiece(pieceFactory.Create(PieceType.Pawn, PieceColor.Black, new Square(7, 6)));

            /*TORRI*/
            AddPiece(pieceFactory.Create(PieceType.Rook, PieceColor.White, new Square(0, 0)));
            AddPiece(pieceFactory.Create(PieceType.Rook, PieceColor.White, new Square(7, 0)));
            //
            AddPiece(pieceFactory.Create(PieceType.Rook, PieceColor.Black, new Square(0, 7)));
            AddPiece(pieceFactory.Create(PieceType.Rook, PieceColor.Black, new Square(7, 7)));

            /*CAVALLI*/
            AddPiece(pieceFactory.Create(PieceType.Knight, PieceColor.White, new Square(1, 0)));
            AddPiece(pieceFactory.Create(PieceType.Knight, PieceColor.White, new Square(6, 0)));
            //
            AddPiece(pieceFactory.Create(PieceType.Knight, PieceColor.Black, new Square(1, 7)));
            AddPiece(pieceFactory.Create(PieceType.Knight, PieceColor.Black, new Square(6, 7)));

            /*ALFIERI*/
            AddPiece(pieceFactory.Create(PieceType.Bishop, PieceColor.White, new Square(2, 0)));
            AddPiece(pieceFactory.Create(PieceType.Bishop, PieceColor.White, new Square(5, 0)));
            //
            AddPiece(pieceFactory.Create(PieceType.Bishop, PieceColor.Black, new Square(2, 7)));
            AddPiece(pieceFactory.Create(PieceType.Bishop, PieceColor.Black, new Square(5, 7)));

            /*RE*/
            AddPiece(pieceFactory.Create(PieceType.King, PieceColor.White, new Square(4, 0)));
            //
            AddPiece(pieceFactory.Create(PieceType.King, PieceColor.Black, new Square(4, 7)));

            /*REGINE*/
            AddPiece(pieceFactory.Create(PieceType.Queen, PieceColor.White, new Square(3, 0)));
            //
            AddPiece(pieceFactory.Create(PieceType.Queen, PieceColor.Black, new Square(3, 7)));
        }
        public void Clear()
        {
            pieceList.Clear();
            whitePieceList.Clear();
            blackPieceList.Clear();
        }
        public void LoadGame(FenString fen)
        {
            this.Clear();

            foreach (Piece piece in fen.PiecePlacement)
            {
                AddPiece(piece);
            }
        }
        public void MakeMove(Move move)
        {
            if (move.IsCastling)
            {
                move.RookMoved.Move(move.RookPosition);
                if (move.IsShortCastling)
                    game.GetPlayer(move.SideMove).CanShortCastle = false;
                else
                    game.GetPlayer(move.SideMove).CanLongCastle = false;
            }
            else
            {
                if (move.HasCaptured)
                {
                    RemovePiece(move.PieceCaptured);
                }
                if (move.HasPromoted)
                {
                    RemovePiece(move.PieceMoved);
                    AddPiece(PieceFactory.CreatePiece(move.PiecePromoted, move.SideMove, move.ToSquare));
                }
            }

            // En passant target square updating

            game.EnPassantSquareStack.Push(game.EnPassantSquare); // save the current target square for the unmake-move method

            if (move.IsDoublePawnPush)
            {
                Square targetSquare;
                if (move.SideMove == PieceColor.White)
                    targetSquare = new Square(move.ToSquare.X, 2);
                else
                    targetSquare = new Square(move.ToSquare.X, 5);

                game.EnPassantSquare = targetSquare;
            }

            else if (game.EnPassantSquare != null)
            {
                game.EnPassantSquare = null;
            }

            move.PieceMoved.Move(move.ToSquare); // move piece
        }
        public void CancelMove(Move move)
        {
            if (move.IsCastling)
            {
                move.PieceMoved.Move(move.FromSquare);
                move.RookMoved.Move(move.RookMoved.startingSquare);
                if (move.IsShortCastling)
                    game.GetPlayer(move.SideMove).CanShortCastle = true;
                else
                    game.GetPlayer(move.SideMove).CanLongCastle = true;
            }
            else
            {
                if (move.HasCaptured)
                {
                    AddPiece(move.PieceCaptured);
                }
                if (move.HasPromoted)
                {
                    RemovePiece(GetPiece(move.ToSquare));
                    AddPiece(move.PieceMoved);
                }
            }

            // En passant target square updating

            game.EnPassantSquare = game.EnPassantSquareStack.Pop();

            move.PieceMoved.Move(move.FromSquare);
        }
    }
}
mpowered
  • 13,162
  • 2
  • 15
  • 18
Crybot
  • 107
  • 3
  • 8
  • 7
    Your English isn't that bad, actually! Just wanted to bolster your confidence. – BlackVegetable Jul 13 '12 at 15:52
  • Complimenti per l'inglese, non è affatto male! Finalmente un altro italiano, sei il quarto che trovo in più di un anno ;) – BlackBear Jul 13 '12 at 15:56
  • ahahh in realta` non mi sento a mio agio a parlare in inglese, penso sempre di aver scritto una stro***ta :) Translate: actually, when talking in english, I always think to say some bul***it :) – Crybot Jul 13 '12 at 16:04
  • 1
    If you don't include the code, when we lose the link to your github repository, this question becomes useless. Part of the point of Stack Overflow is to have your questions be helpful to future visitors. – George Stocker Jul 14 '12 at 16:19
  • how can I include an entire project into this thread? I can post my board class, but I don't think that 1000 lines of code would help anyone .-. – Crybot Jul 14 '12 at 20:26

2 Answers2

5

Array based representations usually have a piece list with each piece and its square, so you don't have to loop through the entire board to find a piece (edit: looking at your code, it seems you already do this?). What's much more important than board representation is how you implement the board operations. For example, testing whether the king is in check does not need to generate an entire move list; you simply have to scan outwards from the king for enemy pieces.

After having looked a bit at your code, it seems that you use a legal move generator, and that you make/unmake (and perhaps other things?) to check legality. The latter isn't necessary, depending on whether you started in check and if any pieces are pinned.

I think you know that bitboards are the standard nowadays, as it performs many piece operations setwise and gets a nice boost on 64 bit platforms. I switched from the mailbox array approach to bitboards in my own engine, and currently perft(6) is 3 seconds. Originally it was something like 30 seconds. Bitboards makes evaluation much simpler, so there's also that to consider.

Zong
  • 6,160
  • 5
  • 32
  • 46
  • thanks for the answer, so... what type of bitboard you recommend to implemet? (Magic, 0x88 ecc.ecc.) – Crybot Jul 13 '12 at 16:08
  • 0x88 is a mailbox approach. The fastest right now is magic bitboards, but I haven't implemented it myself. I use the very the simple classical approach, and have found it to be straightforward and fast enough. – Zong Jul 13 '12 at 16:10
  • thanks again :) do you know any sample project useful to study how the bitboards work? – Crybot Jul 13 '12 at 16:25
  • I'd actually advise looking at other engines; they seem divided between fast and obfuscated and clean but horribly slow. I found bitboards to simple and intuitive, and much cleaner than working with a mailbox. Just read up on bitwise operations and write a function that prints `UInt64`s as boards of 1s and 0s. Read up on the classical approach if you want a nice and simple way of generating ray attacks. – Zong Jul 13 '12 at 16:33
  • Sorry, I meant I'd advise AGAINST looking at other engines...too late to edit now. – Zong Jul 13 '12 at 17:16
3

Thanks for the link for your code, first of all, because that made this a lot easier to answer.

The issue you're having is that you iterate through all of the pieces to find a piece, which is very slow, as you mentioned.

If you want to keep the implementation similar to what you already have in place and at an object-oriented level, I would recommend that you use a Dictionary object to store the pieces. The key can be the location on the board (i.e. A4) and the value would be the piece itself. The Dictionary is a better choice in the case than the List because you can do lookups instead of having to iterate through all of the pieces.

Also in your GetNumberOfPieces method, even in your current implementation, you should use the List.Count property.

But Zong Li's answer is probably better for a chess engine specifically.

EDIT

I looked at your code a little more and I noticed you're passing around Square objects, so maybe you could implement the Dictionary as a dictionary of Square, Piece instead of coordinate, piece.

John
  • 2,653
  • 4
  • 36
  • 57
  • I think that's similar to his current approach. If you use an array and define `A1 = 0...H8 = 63`, you access pieces in the same way. – Zong Jul 13 '12 at 16:03
  • Yeah, it seems like he's deliberately using an object-oriented approach, which is fine. But as you mentioned, there are faster ways of doing this. I was just suggesting that if he were to keep it object-oriented, a Dictionary would be a better choice. – John Jul 13 '12 at 16:05
  • the GetNumberOfPieces returns a value indicating the number of a specific type of piece. Ex: GetNumberOfPieces(PieceType.Queen), it may returns 1 or even more if you promoted a pawn to queen ;) – Crybot Jul 13 '12 at 16:05