Complicating code in C#

Discussion of chess software programming and technical issues.

Moderators: hgm, Rebel, chrisw

Henk
Posts: 7216
Joined: Mon May 27, 2013 10:31 am

Complicating code in C#

Post by Henk »

Before this:

Code: Select all

   public interface IPieceSort
    {
        int Value { get; }
    }
I used

Code: Select all

       public enum Sort { whitePawn, whiteKnight, whiteBishop, whiteRook,  whiteQueen, whiteKing, blackPawn, blackKnight, blackBishop, blackRook, blackQueen, blackKing, none }
But Sort is a concrete type which should be avoided. At least in an interface. Maybe better use int but that is too generic and giving no information.

Now I need an extra class to make it work.

Code: Select all

 class PredefinedPieceSortValues
    {
        public IPieceSort[] pieceSortValues;

        private static object syncRoot = new Object();
        private static PredefinedPieceSortValues instance;

        public static PredefinedPieceSortValues PieceSortInstance
        {
            get
            {
                if (instance == null)
                {
                    lock (syncRoot)
                    {
                        if (instance == null)
                            instance = new PredefinedPieceSortValues();
                    }
                }

                return instance;
            }
        }


        private PredefinedPieceSortValues()
        {
            const int NUMBER_OF_SORT_VALUES = (int)none;
            pieceSortValues = new ChessPieceSort[NUMBER_OF_SORT_VALUES];

            for (var i = whitePawn; i <= blackKing; i++)
            {
                pieceSortValues[(int)i] = new ChessPieceSort(i);
            }

        }

        public IPieceSort PieceSort(ChessPiece.Sort pieceSort)
        {
            return pieceSortValues[(int)pieceSort];

        }
 }

Get the impression making it overcomplicated. But don't see a better solution yet.
User avatar
mvanthoor
Posts: 1784
Joined: Wed Jul 03, 2019 4:42 pm
Location: Netherlands
Full name: Marcel Vanthoor

Re: Complicating code in C#

Post by mvanthoor »

What are you trying to do? Why would you need an interface for Sort? I assume you're not going to regularly replace that method... so why use an interface? The only place in my engine where I use the Rust-equivalent of an interface is for the communication module, because I have two modules.

Maybe, if I should implement MCTS, there'll also be another search module, and then I'll put an interface on top of that as well, but not for something like Sort. That's just a function within the search module that never needs to be called from outside.

A chess engine has these parts:

- Communication (UCI / XBoard)
- Board representation
- Search
- Move generator
- Evaluation
- And the "Engine" module to control everything

Depending on the design of your engine, any of them can be a function, a passive or an active object, and any can have an interface if you want to have multiple implementations... or none can have an interface.

In my engine, the modules Engine, Communication and Search are active (threaded) objects / structs that receive and send messages. Board and MoveGenerator are passive objects / structs that just sit there and provide functionality that can be called. "Board" only does one thing: it keeps the current game state, and updates it when a move is made/unmade (and provide functions to query the game state.) MoveGenerator only does one thing: generate pseudo-legal moves for the Board you put in it. (Split by captures/promotions/quiet, etc, depending on which MoveType you want.)

Evaluation is just a function for now. It may never become an object; or it may become a passive object with a public "evaluate" function that takes a Board (and maybe a MoveGenerator, depending on how I decide to implement mobility checking), and a bunch of private member functions so I don't get one huge evaluation function.

Of all those objects, only "Communication" has an interface, because I have two communication modules.

So yes, I do think you're making it too complicated.
Author of Rustic, an engine written in Rust.
Releases | Code | Docs | Progress | CCRL
User avatar
mvanthoor
Posts: 1784
Joined: Wed Jul 03, 2019 4:42 pm
Location: Netherlands
Full name: Marcel Vanthoor

Re: Complicating code in C#

Post by mvanthoor »

PS: with regard to implementing MVV-LVA sorting/move picking, this is my current implementation:

Code: Select all

// Move sorting routines.

use super::Search;
use crate::{defs::NrOf, movegen::defs::MoveList};

// MVV_VLA[victim][attacker]
pub const MVV_LVA: [[u8; NrOf::PIECE_TYPES + 1]; NrOf::PIECE_TYPES + 1] = [
    [0, 0, 0, 0, 0, 0, 0],       // victim K, attacker K, Q, R, B, N, P, None
    [50, 51, 52, 53, 54, 55, 0], // victim Q, attacker K, Q, R, B, N, P, None
    [40, 41, 42, 43, 44, 45, 0], // victim R, attacker K, Q, R, B, N, P, None
    [30, 31, 32, 33, 34, 35, 0], // victim B, attacker K, Q, R, B, N, P, None
    [20, 21, 22, 23, 24, 25, 0], // victim K, attacker K, Q, R, B, N, P, None
    [10, 11, 12, 13, 14, 15, 0], // victim P, attacker K, Q, R, B, N, P, None
    [0, 0, 0, 0, 0, 0, 0],       // victim None, attacker K, Q, R, B, N, P, None
];

impl Search {
    pub fn score_moves(ml: &mut MoveList) {
        for i in 0..ml.len() {
            let m = ml.get_mut_move(i);
            let value = MVV_LVA[m.captured()][m.piece()];
            m.add_score(value);
        }
    }

    pub fn pick_move(ml: &mut MoveList, index: u8) {
        for i in (index + 1)..ml.len() {
            if ml.get_move(i).score() > ml.get_move(index).score() {
                ml.swap(i as usize, index as usize);
            }
        }
    }
}
And that's everything there is.
Author of Rustic, an engine written in Rust.
Releases | Code | Docs | Progress | CCRL
Henk
Posts: 7216
Joined: Mon May 27, 2013 10:31 am

Re: Complicating code in C#

Post by Henk »

If I would add an elephant then code should not break for fide chess which does not allow an elephant stepping on the chessboard
Sven
Posts: 4052
Joined: Thu May 15, 2008 9:57 pm
Location: Berlin, Germany
Full name: Sven Schüle

Re: Complicating code in C#

Post by Sven »

Henk wrote: Mon Dec 07, 2020 4:39 pm If I would add an elephant then code should not break for fide chess which does not allow an elephant stepping on the chessboard
I think one problem might be your choice of names: "Sort" is usually understood as something completely different from what you actually mean. Many people associate it with "ordering". What you mean is more like "piece type".

Apart from that, I suggest not to use an interface for a concept that is nothing more than an enumeration type, i.e. a collection of constants. You won't need it, even in an engine that allows elephants on a chess board. Instead, you might have different enumeration types for different variants, and different implementations of board, move generator, and evaluator. The latter three are candidates for using an interface, and within each variant-specific implementation you will use the corresponding enumeration type for the "piece type". I don't see any need for a piece type interface. If you provide enough abstraction in the interfaces of board, move generator, evaluator, then the search (which might remain independent from the concrete variant) will not access piece types directly.
Sven Schüle (engine author: Jumbo, KnockOut, Surprise)
User avatar
mvanthoor
Posts: 1784
Joined: Wed Jul 03, 2019 4:42 pm
Location: Netherlands
Full name: Marcel Vanthoor

Re: Complicating code in C#

Post by mvanthoor »

Henk wrote: Mon Dec 07, 2020 4:39 pm If I would add an elephant then code should not break for fide chess which does not allow an elephant stepping on the chessboard
Yes, of course; you could also build in the capability to handle dice rolling and Dungeons and Dragons stats calculation into the engine without breaking FIDE Chess.

The point is that you first need to determine what you want to build. If you want to build a chess engine for FIDE chess, you only need:

64 squares
6 pieces: KQRBNP
FIDE rules/move generation
960 rules (if you want to support that)

If you are making provisions for custom board sizes, extra pieces, alternative movements and rules, you're building a multi-variant engine, and that's a lot more work and more complicated than 'just' building an engine for FIDE and 960 chess.
Author of Rustic, an engine written in Rust.
Releases | Code | Docs | Progress | CCRL
User avatar
mvanthoor
Posts: 1784
Joined: Wed Jul 03, 2019 4:42 pm
Location: Netherlands
Full name: Marcel Vanthoor

Re: Complicating code in C#

Post by mvanthoor »

Sven wrote: Tue Dec 08, 2020 12:43 am
Henk wrote: Mon Dec 07, 2020 4:39 pm If I would add an elephant then code should not break for fide chess which does not allow an elephant stepping on the chessboard
I think one problem might be your choice of names: "Sort" is usually understood as something completely different from what you actually mean. Many people associate it with "ordering". What you mean is more like "piece type"
Possibly a wrong translation from Dutch:

"Wat voor soort dier is dat?" -> "What sort of animal is that?" -> Sort

Analogue: "Sort of piece" (but "sort of" is not used in English when you already know what something is and what type it is.)

"Soort"and "Sort of... / Sort" are false friends here.
Author of Rustic, an engine written in Rust.
Releases | Code | Docs | Progress | CCRL
Henk
Posts: 7216
Joined: Mon May 27, 2013 10:31 am

Re: Complicating code in C#

Post by Henk »

Now I have a method in the interface of a chessboard.

Code: Select all

ICoordSet GetSquares(IPieceType pieceType);
It is used by evaluation for instance. I could use ICoordSet GetSquares(IPieceColor, IPieceKind) instead
but IPieceColor and IPieceKind are enumerations too.

I don't think it is possible to make GetSquares private.
For instance in evaluation I might count the distance between elephants.

Looks like I should introduce IChessPiece or IPiece again or maybe call it IActor.
Henk
Posts: 7216
Joined: Mon May 27, 2013 10:31 am

Re: Complicating code in C#

Post by Henk »

mvanthoor wrote: Mon Dec 07, 2020 3:31 pm What are you trying to do? Why would you need an interface for Sort? I assume you're not going to regularly replace that method... so why use an interface? The only place in my engine where I use the Rust-equivalent of an interface is for the communication module, because I have two modules.

Maybe, if I should implement MCTS, there'll also be another search module, and then I'll put an interface on top of that as well, but not for something like Sort. That's just a function within the search module that never needs to be called from outside.

A chess engine has these parts:

- Communication (UCI / XBoard)
- Board representation
- Search
- Move generator
- Evaluation
- And the "Engine" module to control everything

Depending on the design of your engine, any of them can be a function, a passive or an active object, and any can have an interface if you want to have multiple implementations... or none can have an interface.

In my engine, the modules Engine, Communication and Search are active (threaded) objects / structs that receive and send messages. Board and MoveGenerator are passive objects / structs that just sit there and provide functionality that can be called. "Board" only does one thing: it keeps the current game state, and updates it when a move is made/unmade (and provide functions to query the game state.) MoveGenerator only does one thing: generate pseudo-legal moves for the Board you put in it. (Split by captures/promotions/quiet, etc, depending on which MoveType you want.)

Evaluation is just a function for now. It may never become an object; or it may become a passive object with a public "evaluate" function that takes a Board (and maybe a MoveGenerator, depending on how I decide to implement mobility checking), and a bunch of private member functions so I don't get one huge evaluation function.

Of all those objects, only "Communication" has an interface, because I have two communication modules.

So yes, I do think you're making it too complicated.
I tried Monte Carlo Tree Search. I still think it was a waste of time. Storing nodes costs too much memory.
Or maybe do some pruning.
JohnWoe
Posts: 491
Joined: Sat Mar 02, 2013 11:31 pm

Re: Complicating code in C#

Post by JohnWoe »

Henk wrote: Mon Dec 07, 2020 12:58 pm Before this:

Code: Select all

   public interface IPieceSort
    {
        int Value { get; }
    }
I used

Code: Select all

       public enum Sort { whitePawn, whiteKnight, whiteBishop, whiteRook,  whiteQueen, whiteKing, blackPawn, blackKnight, blackBishop, blackRook, blackQueen, blackKing, none }
But Sort is a concrete type which should be avoided. At least in an interface. Maybe better use int but that is too generic and giving no information.

Now I need an extra class to make it work.

Code: Select all

 class PredefinedPieceSortValues
    {
        public IPieceSort[] pieceSortValues;

        private static object syncRoot = new Object();
        private static PredefinedPieceSortValues instance;

        public static PredefinedPieceSortValues PieceSortInstance
        {
            get
            {
                if (instance == null)
                {
                    lock (syncRoot)
                    {
                        if (instance == null)
                            instance = new PredefinedPieceSortValues();
                    }
                }

                return instance;
            }
        }


        private PredefinedPieceSortValues()
        {
            const int NUMBER_OF_SORT_VALUES = (int)none;
            pieceSortValues = new ChessPieceSort[NUMBER_OF_SORT_VALUES];

            for (var i = whitePawn; i <= blackKing; i++)
            {
                pieceSortValues[(int)i] = new ChessPieceSort(i);
            }

        }

        public IPieceSort PieceSort(ChessPiece.Sort pieceSort)
        {
            return pieceSortValues[(int)pieceSort];

        }
 }

Get the impression making it overcomplicated. But don't see a better solution yet.
Without looking any further. I count 6 indentation levels. After 3 you are generally kinda fucked. :D

It's code smell.