Complicating code in C#

Discussion of chess software programming and technical issues.

Moderators: hgm, Rebel, chrisw

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 »

maksimKorzh wrote: Tue Dec 08, 2020 8:36 pm Marcel, I always enjoy reading your code snippets))) It's like if I was reading my own code but if I had a better habits)
Thanks :) Our coding styles are, essentially, very similar. Just don't use global variables anymore (as you have a video about it, you already know how to do this in C; at least one possible way), and keep some better habits and discipline with regard to variable and constant names. You can also switch to Rust. It has a default coding style (rustfmt) built in, and if you don't keep to it, it will nag you until you do :D
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 »

My experience is that I always regret it when I did not use an interface.

Use interface for hiding representation and polymorphism.

Now I have this:

Code: Select all

  public struct BitBoardCoord : ICoord
    {
        public int Value
        {
            get;
            private set;
        }

        public BitBoardCoord(ulong bit)
        {
            if (bit == 0) Value = -1;
            Value = (int)Math.Log(bit, 2);
        }

        public BitBoardCoord(coord coord)
        {
            Value = (int)coord;
        }
              
        public override string ToString()
        {
            return ((coord)Value).ToString();            
        }
    }
    
    public interface ICoord
    {
        int Value
        {
            get;
        }
    }
    
Last edited by Henk on Thu Dec 10, 2020 6:01 pm, edited 2 times in total.
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: Thu Dec 10, 2020 4:19 pm Or maybe a struct with one property Value. Still not efficient.
Why do you need coordinates anyway? Chess engines don't use coordinates internally, most of the time. Square numbers from 0 to and including 63 is more efficient. If you need coordinates as a reference (for example E1 and G1 for castling, that sort of stuff) just define a few constants with the correct square number.

I have this struct, which just collects a bunch of constants under one name:

Code: Select all

pub struct Squares;
impl Squares {
    // White side squares that are important for castling
    pub const A1: Square = 0;
    pub const B1: Square = 1;
    pub const C1: Square = 2;
    pub const D1: Square = 3;
    pub const E1: Square = 4;
    pub const F1: Square = 5;
    pub const G1: Square = 6;
    pub const H1: Square = 7;

    // Black side squares that are important for castling
    pub const A8: Square = 56;
    pub const B8: Square = 57;
    pub const C8: Square = 58;
    pub const D8: Square = 59;
    pub const E8: Square = 60;
    pub const F8: Square = 61;
    pub const G8: Square = 62;
    pub const H8: Square = 63;

    // White EP-squares start/end
    pub const A3: Square = 16;
    pub const H3: Square = 23;

    // Black EP-squares start/end
    pub const A6: Square = 40;
    pub const H6: Square = 47;
}
And then, in my FEN-reader, for example:

Code: Select all

const EP_SQUARES_WHITE: RangeInclusive<Square> = Squares::A3..=Squares::H3;
const EP_SQUARES_BLACK: RangeInclusive<Square> = Squares::A6..=Squares::H6;
So I can say things like (pseudocode): "if square in EP_SQUARES_WHITE { .... }"

You are making things much more difficult for yourself than you need to. If you'd want to, the only things you need in a chess engine are strings, characters, and some ints. (And maybe some derivative types such as arrays of ints, and the like.)
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 »

mvanthoor wrote: Thu Dec 10, 2020 5:54 pm
Henk wrote: Thu Dec 10, 2020 4:19 pm Or maybe a struct with one property Value. Still not efficient.
Why do you need coordinates anyway? Chess engines don't use coordinates internally, most of the time. Square numbers from 0 to and including 63 is more efficient. If you need coordinates as a reference (for example E1 and G1 for castling, that sort of stuff) just define a few constants with the correct square number.

I have this struct, which just collects a bunch of constants under one name:

Code: Select all

pub struct Squares;
impl Squares {
    // White side squares that are important for castling
    pub const A1: Square = 0;
    pub const B1: Square = 1;
    pub const C1: Square = 2;
    pub const D1: Square = 3;
    pub const E1: Square = 4;
    pub const F1: Square = 5;
    pub const G1: Square = 6;
    pub const H1: Square = 7;

    // Black side squares that are important for castling
    pub const A8: Square = 56;
    pub const B8: Square = 57;
    pub const C8: Square = 58;
    pub const D8: Square = 59;
    pub const E8: Square = 60;
    pub const F8: Square = 61;
    pub const G8: Square = 62;
    pub const H8: Square = 63;

    // White EP-squares start/end
    pub const A3: Square = 16;
    pub const H3: Square = 23;

    // Black EP-squares start/end
    pub const A6: Square = 40;
    pub const H6: Square = 47;
}
And then, in my FEN-reader, for example:

Code: Select all

const EP_SQUARES_WHITE: RangeInclusive<Square> = Squares::A3..=Squares::H3;
const EP_SQUARES_BLACK: RangeInclusive<Square> = Squares::A6..=Squares::H6;
So I can say things like (pseudocode): "if square in EP_SQUARES_WHITE { .... }"

You are making things much more difficult for yourself than you need to. If you'd want to, the only things you need in a chess engine are strings, characters, and some ints. (And maybe some derivative types such as arrays of ints, and the like.)
When someone says I want to play capablanca chess or three dimensional chess or whatever idiotic chess variation then you want to keep thinks similar to reduce complexity.
Henk
Posts: 7216
Joined: Mon May 27, 2013 10:31 am

Re: Complicating code in C#

Post by Henk »

I think enum below is terrible code as well. Mixing pieceColor with pieceKind.

Code: Select all

 
public enum PieceType2 { whitePawn, whiteKnight, whiteBishop, whiteRook,  whiteQueen, whiteKing, blackPawn, blackKnight, blackBishop, blackRook, blackQueen, blackKing, none }
 
Maybe better use a struct
Piece {
int Color;
int Kind;
}

or something.
Joost Buijs
Posts: 1563
Joined: Thu Jul 16, 2009 10:47 am
Location: Almere, The Netherlands

Re: Complicating code in C#

Post by Joost Buijs »

Henk wrote: Fri Dec 11, 2020 11:16 am I think enum below is terrible code as well. Mixing pieceColor with pieceKind.

Code: Select all

 
public enum PieceType2 { whitePawn, whiteKnight, whiteBishop, whiteRook,  whiteQueen, whiteKing, blackPawn, blackKnight, blackBishop, blackRook, blackQueen, blackKing, none }
 
Maybe better use a struct
Piece {
int Color;
int Kind;
}

or something.
There is noting wrong with using an enum, if you insist on writing inefficient code use the struct.

I use the lowest bit for color information, like:

Code: Select all

enum PieceType { White, Black, whitePawn, blackPawn, whiteKnight, blackKnight, etc. };
Henk
Posts: 7216
Joined: Mon May 27, 2013 10:31 am

Re: Complicating code in C#

Post by Henk »

Yes its efficiency. Or maybe use a number and do an extra lookup to get the properties of the piece.

enum won't work when N players play a game with M different pieces where N, M large.
Henk
Posts: 7216
Joined: Mon May 27, 2013 10:31 am

Re: Complicating code in C#

Post by Henk »

Henk wrote: Fri Dec 11, 2020 12:06 pm Yes its efficiency.
enum won't work when N players play a game with M different pieces where N, M large.
So again end up with an int or a subrange of int to make it generic.
Joost Buijs
Posts: 1563
Joined: Thu Jul 16, 2009 10:47 am
Location: Almere, The Netherlands

Re: Complicating code in C#

Post by Joost Buijs »

Henk wrote: Fri Dec 11, 2020 12:44 pm
Henk wrote: Fri Dec 11, 2020 12:06 pm Yes its efficiency.
enum won't work when N players play a game with M different pieces where N, M large.
So again end up with an int or a subrange of int to make it generic.
I really don't understand what you are trying to achieve, why complicate matters so much, it is all a piece of cake.
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 »

One way to control code cmplexity is having a certain mindset. In my case...

"Do I really NEED to use this feture in the programming language to write this piece of code?"

That means, for exmple: I *could* build an interface for my board implementation, but as I don't intend to provide multiple board representations, I don't write one. I DO provide multiple communication protocols (uci and xboard), so I DO write an interface for my Comm module. The engine can either attach the uci or xboard module when it starts. I have no interface for Search (alpha/beta only), but if I decide to add MCTS, I will add the interface as well and make bot the A/B-search and MCTS-search conform to it, so my engine can load either at startup.

"Do I really NEED to use a library for this?"

Yes, I could use a library for some things in my engine, but if I don't have to, I don't. When do I use a library?
- If the library provides better/faster functionality than the standard library (crossbeam-channel vs. STD channels in Rust)
- If I am not confident that I can write my own code better or more robust (such as a good random number generator)
- If the library provides functionality that makes my code shorter / cleaner / easier to read (if-chain, in Rust)
- If writing the code myself is a lot of work, but not interesting for me (probing end game table bases)
- If the library makes it easy to do something that would be a lot of work to do well (the command line parser CLAP, for example)

One other criterion for me to include a library into my engine is that it either:
- Is very well known and very well supported (such as CLAP, rand, crossbeam-channel)
OR
- Is very easy to get rid of and/or replace should I want to (if-chain; even though I don't see that changing anytime soon, as long as Rust's macro parser doesn't change)

This keeps the used programming language features and third-party code in your engine to a bare minimum. That means you have more control over the code that ends up in your engine, and you won't get into the 'crap this library is outdated and not updated anymore' scenario quite as often (if ever). All of this makes your code less complex, and more self-sufficient.

Everything else with regard to complexity is something you either avoid, or design into your engine yourself.
Author of Rustic, an engine written in Rust.
Releases | Code | Docs | Progress | CCRL