Just another LEGAL movegen

Discussion of chess software programming and technical issues.

Moderator: Ras

User avatar
vittyvirus
Posts: 646
Joined: Wed Jun 18, 2014 2:30 pm
Full name: Fahad Syed

Just another LEGAL movegen

Post by vittyvirus »

Sorry for this *useless* post, but I couldn't resist posting this. Yaka's movegen was rewritten yet again, and this time with legal move generation. Have a look:

Code: Select all

=======================
 Starting Benchmarking 
=======================
; Start position
FEN: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 
#0. b1a3: 4856835
...manually truncated...
#19. h2h4: 5385554
Took 2827 milliseconds
Total Nodes: 119060324
Nodes/sec: 42115431

; Kiwipete
FEN: r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1
#0. f3d3: 164583144
#1. f3e3: 189120807
#2. f3g3: 198078522
#3. f3h3: 210100865
#4. f3f4: 181938761
#5. f3g4: 189789456
#6. f3f5: 226135507
#7. f3h5: 197839051
#8. f3f6: 146338070
#9. a1b1: 160413321
#10. a1c1: 159720218
#11. a1d1: 149265033
#12. h1f1: 154273720
#13. h1g1: 166086672
#14. d2c1: 158801466
#15. d2e3: 184114087
#16. d2f4: 165805784
#17. d2g5: 177883051
#18. d2h6: 161319567
#19. e2d1: 131348645
#20. e2f1: 174218453
#21. e2d3: 167737155
#22. e2c4: 170094798
#23. e2b5: 158033152
#24. e2a6: 130642863
#25. c3b1: 165673862
#26. c3d1: 165415976
#27. c3a4: 191260040
#28. c3b5: 166970874
#29. e5d3: 140737072
#30. e5c4: 145182844
#31. e5g4: 144264874
#32. e5c6: 169836097
#33. e5g6: 165477768
#34. e5d7: 193856446
#35. e5f7: 176070755
#36. g2h3: 158328615
#37. d5e6: 203255191
#38. a2a3: 197413067
#39. b2b3: 153953689
#40. g2g3: 141076301
#41. d5d6: 151133066
#42. a2a4: 183872225
#43. g2g4: 135208177
#44. e1d1: 148612404
#45. e1f1: 139601450
#46. e1g1C: 172063416
#47. e1c1C: 148701308
Took 182589 milliseconds
Total Nodes: 8031647685
Nodes/sec: 43987576

FEN: 8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 1
#0. b4b1: 1160678
...manually truncated...
#13. a5a6: 968724
Took 437 milliseconds
Total Nodes: 11030083
Nodes/sec: 25240464

FEN: r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1
#0. f1f2: 123078367
...manually truncated...
#5. g1h1: 149335005
Took 12823 milliseconds
Total Nodes: 706045033
Nodes/sec: 55060830

FEN: rnbqkb1r/pp1p1ppp/2p5/4P3/2B5/8/PPP1NnPP/RNBQK2R w KQkq - 0 6
#0. d1d2: 61274029
...manually truncated...
#41. e1g1C: 57347753
Took 57960 milliseconds
Total Nodes: 2362704901
Nodes/sec: 40764404
Stockfish is about 3 times faster than this, and qperft is about 2.7 times faster. I use completely legal move generation, and like Texel 1.01, in case of a move of pinned piece, I simply make and unmake the move to check if the position is legal. I'm not extremely worried about speed, but I plan to add pinned piece detection scheme after it plays its first game ;)
Henk
Posts: 7251
Joined: Mon May 27, 2013 10:31 am

Re: Just another LEGAL movegen

Post by Henk »

At least legal moves generation is much simpler than pseudo legal move generation. Today and yesterday I got trouble with solving most simple mate in N problems. So I am going to use legal moves generation only. Less trouble the better.
mvk
Posts: 589
Joined: Tue Jun 04, 2013 10:15 pm

Re: Just another LEGAL movegen

Post by mvk »

vittyvirus wrote:Sorry for this *useless* post, but I couldn't resist posting this. Yaka's movegen was rewritten yet again, and this time with legal move generation. Have a look:
Congratulations! The biggest trap next is to spend time on making it faster. Being within a factor 3 is great already, but most important is to have peace of mind that this part is correct, so you can focus on the evaluation and SEE+search first.

Have you noted that your generator is relatively fast in the more complex position? In my program, it is the opposite...

[d] r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq -

Code: Select all

Rookie> perft 6
6 119060324 (1.637 s)
Rookie> r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 
Rookie> perft 6
6 8031647685 (131.041 s)
Rookie> 8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 
Rookie> perft 6
6 11030083 (0.174 s)
Rookie> r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 
Rookie> perft 6
6 706045033 (12.700 s)
Rookie> rnbqkb1r/pp1p1ppp/2p5/4P3/2B5/8/PPP1NnPP/RNBQK2R w KQkq -
Rookie> perft 6
6 2362704901 (34.642 s)
[Account deleted]
User avatar
vittyvirus
Posts: 646
Joined: Wed Jun 18, 2014 2:30 pm
Full name: Fahad Syed

Re: Just another LEGAL movegen

Post by vittyvirus »

mvk wrote:
vittyvirus wrote:Sorry for this *useless* post, but I couldn't resist posting this. Yaka's movegen was rewritten yet again, and this time with legal move generation. Have a look:
Congratulations! The biggest trap next is to spend time on making it faster. Being within a factor 3 is great already, but most important is to have peace of mind that this part is correct, so you can focus on the evaluation and SEE+search first.

Have you noted that your generator is relatively fast in the more complex position? In my program, it is the opposite...

[d] r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq -

Code: Select all

Rookie> perft 6
6 119060324 (1.637 s)
Rookie> r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 
Rookie> perft 6
6 8031647685 (131.041 s)
Rookie> 8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 
Rookie> perft 6
6 11030083 (0.174 s)
Rookie> r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 
Rookie> perft 6
6 706045033 (12.700 s)
Rookie> rnbqkb1r/pp1p1ppp/2p5/4P3/2B5/8/PPP1NnPP/RNBQK2R w KQkq -
Rookie> perft 6
6 2362704901 (34.642 s)
I see many more speedups in my movegen, especially when it comes to removing illegal moves. I guess my movegen removes illegal moves quickly when in check or in case of pinned pieces. Here's the code:

Code: Select all

template <bool in_check>
  inline void remove_illegal(Position& pos)
  {
    square_t ksq = pos.king_square();
    Bitboard katk = AttackGen::bishop_attacks(ksq, pos.all_pieces())
      | AttackGen::rook_attacks(ksq, pos.all_pieces());
    // bishop_attackes() and rook_attackes() of a square automatically masks
    // potential king and pawn attacks.
    if (in_check)
      katk |= AttackGen::attacks_by<Piece::KNIGHT>(ksq);
    size_t len = 0;
    GameLine gl;
    bool is_legal;
    for (size_t i = 0; i < size_; ++i) {
      Move &m(move_stack[i]);
      if ((m.get_from_sq() != ksq) && ((katk & Bitboards::SquareMask[in_check ?
        m.get_to_sq() : m.get_from_sq()]) == 0) && (m.get_type() != Move::ENPASSANT))
        is_legal = !in_check;
      else {
        pos.make_move(m, gl);
        is_legal = pos.is_legal();
        pos.unmake_move(m, gl);
      }
      if (is_legal)
        move_stack[len++] = m;
    }
    size_ = len;
  }
make_move() is quite expensive, so I guess the obvious speed up would be to use a special make_move() function that simply updates the Bitboards to check whether the other king is attacked (the position is illegal).
zullil
Posts: 6442
Joined: Tue Jan 09, 2007 12:31 am
Location: PA USA
Full name: Louis Zulli

Re: Just another LEGAL movegen

Post by zullil »

vittyvirus wrote: I see many more speedups in my movegen, especially when it comes to removing illegal moves. I guess my movegen removes illegal moves quickly when in check or in case of pinned pieces. Here's the code:

Code: Select all

template <bool in_check>
  inline void remove_illegal(Position& pos)
  {
    square_t ksq = pos.king_square();
    Bitboard katk = AttackGen::bishop_attacks(ksq, pos.all_pieces())
      | AttackGen::rook_attacks(ksq, pos.all_pieces());
    // bishop_attackes() and rook_attackes() of a square automatically masks
    // potential king and pawn attacks.
    if (in_check)
      katk |= AttackGen::attacks_by<Piece::KNIGHT>(ksq);
    size_t len = 0;
    GameLine gl;
    bool is_legal;
    for (size_t i = 0; i < size_; ++i) {
      Move &m(move_stack[i]);
      if ((m.get_from_sq() != ksq) && ((katk & Bitboards::SquareMask[in_check ?
        m.get_to_sq() : m.get_from_sq()]) == 0) && (m.get_type() != Move::ENPASSANT))
        is_legal = !in_check;
      else {
        pos.make_move(m, gl);
        is_legal = pos.is_legal();
        pos.unmake_move(m, gl);
      }
      if (is_legal)
        move_stack[len++] = m;
    }
    size_ = len;
  }
make_move() is quite expensive, so I guess the obvious speed up would be to use a special make_move() function that simply updates the Bitboards to check whether the other king is attacked (the position is illegal).
If you are calling make-move and then checking for legality, is this really a legal move generator at all, in the usual sense of the term?

BTW, what is the CPU? And I assume you are bulk-counting at leaf nodes?
User avatar
vittyvirus
Posts: 646
Joined: Wed Jun 18, 2014 2:30 pm
Full name: Fahad Syed

Re: Just another LEGAL movegen

Post by vittyvirus »

zullil wrote:
vittyvirus wrote: I see many more speedups in my movegen, especially when it comes to removing illegal moves. I guess my movegen removes illegal moves quickly when in check or in case of pinned pieces. Here's the code:

Code: Select all

template <bool in_check>
  inline void remove_illegal(Position& pos)
  {
    square_t ksq = pos.king_square();
    Bitboard katk = AttackGen::bishop_attacks(ksq, pos.all_pieces())
      | AttackGen::rook_attacks(ksq, pos.all_pieces());
    // bishop_attackes() and rook_attackes() of a square automatically masks
    // potential king and pawn attacks.
    if (in_check)
      katk |= AttackGen::attacks_by<Piece::KNIGHT>(ksq);
    size_t len = 0;
    GameLine gl;
    bool is_legal;
    for (size_t i = 0; i < size_; ++i) {
      Move &m(move_stack[i]);
      if ((m.get_from_sq() != ksq) && ((katk & Bitboards::SquareMask[in_check ?
        m.get_to_sq() : m.get_from_sq()]) == 0) && (m.get_type() != Move::ENPASSANT))
        is_legal = !in_check;
      else {
        pos.make_move(m, gl);
        is_legal = pos.is_legal();
        pos.unmake_move(m, gl);
      }
      if (is_legal)
        move_stack[len++] = m;
    }
    size_ = len;
  }
make_move() is quite expensive, so I guess the obvious speed up would be to use a special make_move() function that simply updates the Bitboards to check whether the other king is attacked (the position is illegal).
If you are calling make-move and then checking for legality, is this really a legal move generator at all, in the usual sense of the term?

BTW, what is the CPU? And I assume you are bulk-counting at leaf nodes?
Hi,
I call make_move(), and then if the king is attacked, the position is illegal and the move is removed. This allows perft termination at depth == 1. I'm using a very poor Intel Core i5-2430M @ 2.4 GHz with 64-bit compile, -Ofast optimization, with g++ 4.8.1 and Windows 8 64-bit.

Code: Select all

Stockfish 010215 64 POPCNT by Tord Romstad, Marco Costalba and Joona Kiiski
perft 6

Position: 1/1
a2a3: 4463267
b2b3: 5310358
c2c3: 5417640
d2d3: 8073082
e2e3: 9726018
f2f3: 4404141
g2g3: 5346260
h2h3: 4463070
a2a4: 5363555
b2b4: 5293555
c2c4: 5866666
d2d4: 8879566
e2e4: 9771632
f2f4: 4890429
g2g4: 5239875
h2h4: 5385554
b1a3: 4856835
b1c3: 5708064
g1f3: 5723523
g1h3: 4877234

===========================
Total time (ms) : 943
Nodes searched  : 119060324
Nodes/second    : 126256971
zullil
Posts: 6442
Joined: Tue Jan 09, 2007 12:31 am
Location: PA USA
Full name: Louis Zulli

Re: Just another LEGAL movegen

Post by zullil »

vittyvirus wrote:
zullil wrote:
vittyvirus wrote: I see many more speedups in my movegen, especially when it comes to removing illegal moves. I guess my movegen removes illegal moves quickly when in check or in case of pinned pieces. Here's the code:

Code: Select all

template <bool in_check>
  inline void remove_illegal(Position& pos)
  {
    square_t ksq = pos.king_square();
    Bitboard katk = AttackGen::bishop_attacks(ksq, pos.all_pieces())
      | AttackGen::rook_attacks(ksq, pos.all_pieces());
    // bishop_attackes() and rook_attackes() of a square automatically masks
    // potential king and pawn attacks.
    if (in_check)
      katk |= AttackGen::attacks_by<Piece::KNIGHT>(ksq);
    size_t len = 0;
    GameLine gl;
    bool is_legal;
    for (size_t i = 0; i < size_; ++i) {
      Move &m(move_stack[i]);
      if ((m.get_from_sq() != ksq) && ((katk & Bitboards::SquareMask[in_check ?
        m.get_to_sq() : m.get_from_sq()]) == 0) && (m.get_type() != Move::ENPASSANT))
        is_legal = !in_check;
      else {
        pos.make_move(m, gl);
        is_legal = pos.is_legal();
        pos.unmake_move(m, gl);
      }
      if (is_legal)
        move_stack[len++] = m;
    }
    size_ = len;
  }
make_move() is quite expensive, so I guess the obvious speed up would be to use a special make_move() function that simply updates the Bitboards to check whether the other king is attacked (the position is illegal).
If you are calling make-move and then checking for legality, is this really a legal move generator at all, in the usual sense of the term?

BTW, what is the CPU? And I assume you are bulk-counting at leaf nodes?
Hi,
I call make_move(), and then if the king is attacked, the position is illegal and the move is removed. This allows perft termination at depth == 1.
Yes, I can read the code. I guess to me what you're doing is not "legal move generation" but rather "pseudo-legal move generation with filtering". Not sure I understand the advantage of removing the illegal moves during generation, rather than later ...
User avatar
vittyvirus
Posts: 646
Joined: Wed Jun 18, 2014 2:30 pm
Full name: Fahad Syed

Re: Just another LEGAL movegen

Post by vittyvirus »

zullil wrote:
vittyvirus wrote:
zullil wrote:
vittyvirus wrote: I see many more speedups in my movegen, especially when it comes to removing illegal moves. I guess my movegen removes illegal moves quickly when in check or in case of pinned pieces. Here's the code:

Code: Select all

template <bool in_check>
  inline void remove_illegal(Position& pos)
  {
    square_t ksq = pos.king_square();
    Bitboard katk = AttackGen::bishop_attacks(ksq, pos.all_pieces())
      | AttackGen::rook_attacks(ksq, pos.all_pieces());
    // bishop_attackes() and rook_attackes() of a square automatically masks
    // potential king and pawn attacks.
    if (in_check)
      katk |= AttackGen::attacks_by<Piece::KNIGHT>(ksq);
    size_t len = 0;
    GameLine gl;
    bool is_legal;
    for (size_t i = 0; i < size_; ++i) {
      Move &m(move_stack[i]);
      if ((m.get_from_sq() != ksq) && ((katk & Bitboards::SquareMask[in_check ?
        m.get_to_sq() : m.get_from_sq()]) == 0) && (m.get_type() != Move::ENPASSANT))
        is_legal = !in_check;
      else {
        pos.make_move(m, gl);
        is_legal = pos.is_legal();
        pos.unmake_move(m, gl);
      }
      if (is_legal)
        move_stack[len++] = m;
    }
    size_ = len;
  }
make_move() is quite expensive, so I guess the obvious speed up would be to use a special make_move() function that simply updates the Bitboards to check whether the other king is attacked (the position is illegal).
If you are calling make-move and then checking for legality, is this really a legal move generator at all, in the usual sense of the term?

BTW, what is the CPU? And I assume you are bulk-counting at leaf nodes?
Hi,
I call make_move(), and then if the king is attacked, the position is illegal and the move is removed. This allows perft termination at depth == 1.
Yes, I can read the code. I guess to me what you're doing is not "legal move generation" but rather "pseudo-legal move generation with filtering". Not sure I understand the advantage of removing the illegal moves during generation, rather than later ...
Actually, the advantage is that you only need to check the possibly pinned piece moves are legal or not... You don't have to check whether each and every move is legal or not.
joels
Posts: 1
Joined: Wed Jun 25, 2014 6:13 am

Re: Just another LEGAL movegen

Post by joels »

mvk wrote:Congratulations! The biggest trap next is to spend time on making it faster.
Words of wisdom. When I got started, I got stuck in this trap and ended up throwing out my 0x88 board in favor of bitboards, all because I wanted faster perft.

In retrospect, switching to a bitboard representation was a good thing... but even after that, I found myself trying to get perft even faster.

Had to stop and remind myself of my original goal.
User avatar
vittyvirus
Posts: 646
Joined: Wed Jun 18, 2014 2:30 pm
Full name: Fahad Syed

Re: Just another LEGAL movegen

Post by vittyvirus »

joels wrote:
mvk wrote:Congratulations! The biggest trap next is to spend time on making it faster.
Words of wisdom. When I got started, I got stuck in this trap and ended up throwing out my 0x88 board in favor of bitboards, all because I wanted faster perft.

In retrospect, switching to a bitboard representation was a good thing... but even after that, I found myself trying to get perft even faster.

Had to stop and remind myself of my original goal.
I foucus on clean, concise, and easy to understand code more than speed. My make_move() is only ~140 lines of code, and unmake_move() around 80 lines. My whole movegen stuff (not including initialization code for magic bitboards) is ~300 lines of code. In my first engine, all this used to be 2000+ lines.
Although I'd forget about small efficiencies almost all the time, I wouldn't compromise on speed when it gets, say, 5% down. After Yaka plays its first game, I _will_ try to speed up its perft...