N.E.G. 1.0 released

Discussion of chess software programming and technical issues.

Moderators: hgm, chrisw, Rebel

User avatar
Roland Chastain
Posts: 659
Joined: Sat Jun 08, 2013 10:07 am
Location: France
Full name: Roland Chastain

Re: N.E.G. 1.0 released

Post by Roland Chastain »

hgm wrote: Sun Dec 29, 2019 11:45 am N.E.G. 0.3 was actually a slightly modified version of my regular alpha-beta engine Joker, which was closed source. I haven't worked on that since 2008, and I am not even sure I do have sources, as this is many computers ago.

The Ram 2.0 source is below. It should be linked with the MersenneTwister random generator.
Thank you very much, for N.E.G 1.3 and for Ram 2.0.

After some research I was able to compile Ram (under Linux). :)

Here are the results of a little tournament that I have just made.

Code: Select all

Rank Name                Elo    +    - games score oppo. draws 
   1 Iota 0.1            633  138  103    48   96%   168    8% 
   2 Moustique 0.3       393  123  113    24   69%   238   13% 
   3 Alouette 0.0.9      309  114  108    24   63%   238   25% 
   4 NEG 1.3             287   81   79    48   54%   254   17% 
   5 Ram 2.0              32   79   85    48   19%   318   33% 
   6 Arminius 2017 RND     0   80   89    48   16%   326   27% 
Qui trop embrasse mal étreint.
User avatar
flok
Posts: 499
Joined: Tue Jul 03, 2018 10:19 am
Full name: Folkert van Heusden

Re: N.E.G. 1.0 released

Post by flok »

Roland Chastain wrote: Sun Dec 29, 2019 4:19 pm Here are the results of a little tournament that I have just made.

Code: Select all

Rank Name                Elo    +    - games score oppo. draws 
   1 Iota 0.1            633  138  103    48   96%   168    8% 
   2 Moustique 0.3       393  123  113    24   69%   238   13% 
   3 Alouette 0.0.9      309  114  108    24   63%   238   25% 
   4 NEG 1.3             287   81   79    48   54%   254   17% 
   5 Ram 2.0              32   79   85    48   19%   318   33% 
   6 Arminius 2017 RND     0   80   89    48   16%   326   27% 
can you include POS (https://vanheusden.com/pos/)? Am curious how it scores with the others.
User avatar
Roland Chastain
Posts: 659
Joined: Sat Jun 08, 2013 10:07 am
Location: France
Full name: Roland Chastain

Re: N.E.G. 1.0 released

Post by Roland Chastain »

flok wrote: Sun Dec 29, 2019 8:46 pm can you include POS (https://vanheusden.com/pos/)? Am curious how it scores with the others.
With pleasure. Unfortunately, I can't get the Linux version to work. :?

I will give it a try tomorrow under Windows.
Qui trop embrasse mal étreint.
User avatar
Roland Chastain
Posts: 659
Joined: Sat Jun 08, 2013 10:07 am
Location: France
Full name: Roland Chastain

Re: N.E.G. 1.0 released

Post by Roland Chastain »

With CuteChess POS doesn't seem to work. I tried with WinBoard. It works, but unfortunately POS always loses on time. The time control was 40/4. What I don't understand is that Iota 0.1 always loses on time too, which doesn't happen with CuteChess, even with shorter time control. :|

My Windows computer is not very powerful. At times, all the applications run in slow motion, I don't know why.

I still saw that N.E.G. 0.3 is stronger than later versions.
Qui trop embrasse mal étreint.
Guenther
Posts: 4718
Joined: Wed Oct 01, 2008 6:33 am
Location: Regensburg, Germany
Full name: Guenther Simon

Re: N.E.G. 1.0 released

Post by Guenther »

Roland Chastain wrote: Mon Dec 30, 2019 10:44 pm With CuteChess POS doesn't seem to work. I tried with WinBoard. It works, but unfortunately POS always loses on time. The time control was 40/4. What I don't understand is that Iota 0.1 always loses on time too, which doesn't happen with CuteChess, even with shorter time control. :|

My Windows computer is not very powerful. At times, all the applications run in slow motion, I don't know why.

I still saw that N.E.G. 0.3 is stronger than later versions.
Just in case, you know that POS needs cmd args?

Code: Select all

POS_120 --io-mode xboard" /fd=C:\Engines\WBJAVA\POS_120
https://rwbc-chess.de

[Trolls n'existent pas...]
User avatar
Roland Chastain
Posts: 659
Joined: Sat Jun 08, 2013 10:07 am
Location: France
Full name: Roland Chastain

Re: N.E.G. 1.0 released

Post by Roland Chastain »

Guenther wrote: Tue Dec 31, 2019 8:37 am Just in case, you know that POS needs cmd args?

Code: Select all

POS_120 --io-mode xboard" /fd=C:\Engines\WBJAVA\POS_120
Thank you for the information.
Qui trop embrasse mal étreint.
ydebilloez
Posts: 173
Joined: Tue Jun 27, 2017 11:01 pm
Location: Lubumbashi
Full name: Yves De Billoëz

Re: N.E.G. 1.0 released

Post by ydebilloez »

Under Arena 3.9 linux 64 bit, it does not work.
Yves De Billoëz @ macchess belofte chess
Once owner of a Mephisto I, II, challenger, ... chess computer.
unserializable
Posts: 64
Joined: Sat Oct 24, 2020 6:39 pm
Full name: Taimo Peelo

Re: N.E.G. 1.0 released

Post by unserializable »

Adam Hair wrote: Sun Dec 29, 2019 3:03 pm Hi H.G.
The executable found on the download page is misnamed NEG1_2 though a text editor shows that it refers to itself as 1.3. Also, the web site still uses the name "N.E.G. 1.2".
Hey H.G.M! Mismatch of version numbers that Adam mentioned is still true. N.E.G. 1.3 (as it is called in source code) was fantastic first opponent when developing Monchester 1.0 -- fast and somewhat challenging, while still random enough to create situations for discovering bugs when promoting on square a1 (0) :)

N.E.G. source code I made compile under Linux by creating Makefile that added missing headers:

Code: Select all

(printf "#include <stdlib.h>\n#include <string.h>\n" ; cat NEG.c) | $(CC) -x c - -o NEG
In the games played, "Niet Erg Goed" 1.3. made occasional illegal moves resulting in its forfeit, from square where it does not have a piece, while in check and where check removing move would be e.p. capture. Sample games below:

[pgn]
[Event "Computer Chess Game"]
[Date "2020.09.21"]
[Round "16"]
[White "monchester in progress"]
[Black "N.E.G. 1.3"]
[Result "1-0"]
[TimeControl "40/300"]

1. Nc3 Nf6 {-0,07/2 0,1} 2. e3 e5 {-0,15/2 0,1} 3. Rb1 Bd6 {-1,02/2 0,1} 4.
Ra1 Nc6 {-1,11/2 0,1} 5. Rb1 b6 {-0,99/2 0,1} 6. Ra1 h6 {-1,02/2 0,1} 7.
Rb1 Bb7 {-0,93/2 0,1} 8. Ra1 Rg8 {-0,87/2 0,1} 9. Rb1 Qe7 {-1,06/2 0,1} 10.
Ra1 g5 {-0,99/2 0,1} 11. Rb1 a5 {-1,07/2 0,1} 12. Ra1 Ra7 {-1,10/2 0,1} 13.
Nb5 Ra8 {-1,09/2 0,1} 14. c4 Bb4 {-0,92/2 0,1} 15. Nxc7+ Kf8 {-0,16/2 0,1}
16. Nxa8 Bxa8 {-1,97/2 0,1} 17. Rb1 d6 {-2,03/2 0,1} 18. Ra1 Bb7
{-1,76/2 0,1} 19. Rb1 e4 {-1,94/2 0,1} 20. Ra1 Qd8 {-1,80/2 0,1} 21. Rb1
Nd7 {-1,96/2 0,1} 22. Ra1 Ke8 {-2,05/2 0,1} 23. Rb1 Nc5 {-1,83/2 0,1} 24.
a3 Bxd2+ {+0,48/2 0,1} 25. Bxd2 Nd3+ {-1,14/2 0,1} 26. Bxd3 exd3
{+5,08/2 0,1} 27. Bc3 d2+ {+0,74/2 0,1} 28. Qxd2 Ke7 {+0,06/2 0,1} 29. Qd5
a4 {-0,09/2 0,1} 30. Qe4+ Kd7 {-3,37/2 0,1} 31. c5 bxc5 {-0,85/2 0,1} 32.
Qg4+ Ke7 {-0,99/2 0,1} 33. Qe4+ Kf8 {-3,09/2 0,1} 34. Rd1 h5 {-0,98/2 0,1}
35. Ra1 Qd7 {-1,20/2 0,1} 36. Qf3 h4 {+1,01/2 0,1} 37. Rd1 Ke8
{-0,56/2 0,1} 38. Ra1 Qc7 {-0,59/2 0,1} 39. Qg4 Kf8 {-0,41/2 0,1} 40. Rd1
Qd8 {-0,40/2 0,1} 41. Ra1 Ne5 {-0,48/2 0,1} 42. Bxe5 dxe5 {-0,58/2 0,1} 43.
Rd1 Qa5+ {-0,56/2 0,1} 44. Ke2 Ba6+ {-0,68/2 0,1} 45. Kf3 Qb6 {-0,50/2 0,1}
46. Qxa4 Qxb2 {-0,37/2 0,1} 47. Qxa6 Kg7 {-8,64/2 0,1} 48. Rf1 Qc2
{-8,60/2 0,1} 49. Ra1 Qf5+ {-9,65/2 0,1} 50. Ke2 f6 {-0,43/2 0,1} 51. Qa7+
Kh6 {-9,57/2 0,1} 52. Qxc5 Qg4+ {-0,42/2 0,1} 53. Kf1 Rc8 {-0,37/2 0,1} 54.
f3 Qd7 {-0,63/2 0,1} 55. Qb4 Qd3+ {-0,40/2 0,1} 56. Kf2 Rc2+ {-0,09/2 0,1}
57. Ne2 Qxe2+ {-0,05/2 0,1} 58. Kg1 Qxe3+ {-0,02/2 0,1} 59. Kf1 Rf2+
{-0,02/2 0,1} 60. Kg1 Kh7 {-2,90/2 0,1} 61. Qe7+ Kg6 {-0,24/2 0,1} 62. Qe8+
Kg7 {-0,13/2 0,1} 63. Qe7+ Kh6 {+0,00/2 0,1} 64. Qxf6+ Kh5 {-0,25/2 0,1}
65. g4+
{Xboard: Forfeit due to invalid move: h6h5 (h6h5 via `0, `0) res=24} 1-0
[/pgn]

[pgn]
[Event "Computer Chess Game"]
[Date "2020.10.01"]
[Round "22"]
[White "N.E.G. 1.3"]
[Black "monchester in progress"]
[Result "0-1"]
[TimeControl "120+1"]

1. Nc3 {+0,00/2} Nc6 {+0,00/5 0,3} 2. Nf3 {+0,23/2 0,1} Nf6 {+0,00/5 0,4}
3. g3 {-0,12/2 0,1} d5 {+1,00/5 0,6} 4. a4 {-0,07/2 0,1} d4 {+1,00/5 1,1}
5. Nb5 {+0,00/2 0,1} Bf5 {+8,00/5 1,3} 6. h4 {-0,01/2 0,1} Bg6
{+8,00/5 2,2} 7. c3 {+0,11/2 0,1} a6 {+8,00/5 2,0} 8. Nbxd4 {-0,09/2 0,1}
Nxd4 {+8,00/5 2,1} 9. cxd4 {+0,01/2 0,1} Qd7 {+7,00/5 1,1} 10. Qb3
{+0,02/2 0,1} Be4 {+8,00/5 4} 11. Ne5 {-0,06/2 0,1} Qxd4 {+8,00/5 6} 12.
Rg1 {+4,97/2 0,1} Qxe5 {+10,00/5 8} 13. e3 {-1,51/2 0,1} Bc6 {+10,00/5 6}
14. Bc4 {-1,53/2 0,1} Qc5 {+10,00/5 6} 15. Kf1 {-4,92/2 0,1} Bd5
{+16,00/5 6} 16. Bxd5 {-1,48/2 0,1} Nxd5 {+10,00/5 4} 17. Qxb7
{-0,09/2 0,1} Rd8 {+9,00/5 2,9} 18. Qxa6 {+0,06/2 0,1} Qc2 {+15,00/5 2,8}
19. Qb5+ {-0,10/2 0,1} c6 {+15,00/5 0,2} 20. Qb7 {+0,06/2 0,1} Qd1+
{+24,00/5 2,6} 21. Kg2 {-0,16/2 0,1} Qc2 {+15,00/5 2,8} 22. h5
{-4,85/2 0,1} Qe4+ {+15,00/5 2,9} 23. Kh2 {-0,09/2 0,1} Qc2 {+15,00/5 3}
24. f3 {+0,00/2 0,1} Nxe3 {+16,00/5 2,5} 25. Qb6 {-1,91/2 0,1} Nd5
{+16,00/5 4} 26. Qb7 {-0,04/2 0,1} Qc4 {+16,00/5 2,5} 27. Kg2 {+0,07/2 0,1}
Qc5 {+16,00/5 3} 28. Rf1 {+0,03/2 0,1} Qc4 {+16,00/5 2,9} 29. a5
{-0,06/2 0,1} Qe2+ {+33,00/5 3} 30. Kh1 {-0,13/2 0,1} Qxf1+ {+42,00/5 2,8}
31. Kh2 {-0,22/2 0,1} Qf2+ {+42,00/5 2,3} 32. Kh3 {+0,04/2 0,1} Qf1+
{+42,00/5 2,2} 33. Kg4 {-0,02/2 0,1} Qc4+ {+42,00/5 2,3} 34. Kf5
{-0,97/2 0,1} e6+ {+84,00/5 4} 35. Ke5 {-0,04/2 0,1} f6+ {+848,00/5 6} 36.
Kxe6 {+1,12/2 0,1} Nc7+ {+83,00/5 6} 37. Kf5 {-0,20/2 0,1} Qe6+
{+83,00/5 6} 38. Kf4 {-0,01/2 0,1} g5+ {+847,00/5 4}
{Xboard: Forfeit due to invalid move: f5f4 (f5f4 via `0, `0) res=24} 0-1

[/pgn]
Monchester 1.0, chess engine playing at scholastic level: https://github.com/unserializable/monchester ("Daddy, it is gonna take your horsie!")
Tickle Monchester at: https://lichess.org/@/monchester
User avatar
maksimKorzh
Posts: 775
Joined: Sat Sep 08, 2018 5:37 pm
Location: Ukraine
Full name: Maksim Korzh

Re: N.E.G. 1.0 released

Post by maksimKorzh »

hgm wrote: Sun Dec 28, 2014 2:59 pm The following version (N.E.G. 1.1) will always print the 'q' suffix on promotion moves:

Code: Select all

#include <stdio.h>

#ifdef WIN32 
#    include <windows.h>
#else
#    include <sys/time.h>
#    include <sys/times.h>
#    include <unistd.h>
     int GetTickCount()
     {	struct timeval t;
	gettimeofday(&t, NULL);
	return t.tv_sec*1000 + t.tv_usec/1000;
     }
#endif

#define WHITE 8
#define BLACK 16
#define COLOR (WHITE|BLACK)


typedef void Func(int stm, int from, int to, void *closure);
typedef int Board[128];

int value[8] = { 0, 100, 100, 10000, 325, 350, 500, 950 };
int firstDir[] = { 0, 0, 27, 4, 18, 8, 13, 4 };
int steps[] = { -16, -15, -17, 0, 1, -1, 16, -16, 15, -15, 17, -17, 0, 1, -1, 16, -16, 0, 18, 31, 33, 14, -18, -31, -33, -14, 0, 16, 15, 17, 0 };
Board PST = {
 0, 2, 4, 6, 6, 4, 2, 0,   0,0,0,0,0,0,0,0,
 2, 8,10,12,12,10, 8, 2,   0,0,0,0,0,0,0,0,
 6,12,16,18,18,16,12, 6,   0,0,0,0,0,0,0,0,
 8,14,18,20,20,18,14, 8,   0,0,0,0,0,0,0,0,
 8,14,18,20,20,18,14, 8,   0,0,0,0,0,0,0,0,
 6,12,16,18,18,16,12, 6,   0,0,0,0,0,0,0,0,
 2, 8,10,12,12,10, 8, 2,   0,0,0,0,0,0,0,0,
 0, 2, 4, 6, 6, 4, 2, 0,   0,0,0,0,0,0,0,0
};


//              "abcdefghijklmnopqrstuvwxyz"
char pieces[] = ".5........3..4.176........";
Board board, attacks[2], lva[2];
int bestScore, bestFrom, bestTo, lastMover, lastChecker, randomize, post;

void
MoveGen (Board board, int stm, Func proc, void *cl)
{
  int from, to, piece, victim, type, dir, step;
  for(from=0; from<128; from = from + 9 & ~8) {
    piece = board[from];
    if(piece & stm) {
      type = piece & 7;
      dir = firstDir[type];
      while((step = steps[dir++])) {
        to = from;
        do {
          to += step;
          if(to & 0x88) break;
          victim = board[to];
          (*proc)(piece & COLOR, from, to, cl);
          victim += type < 5;
          if(!(to - from & 7) && type < 3 && (type == 1 ? to > 79 : to < 48)) victim--;
        } while(!victim);
      }
    }
  }
}

int InCheck (int stm)
{
  int k, xstm = COLOR - stm;
  for(k=0; k<128; k++) if( board[k] == stm + 3 ) {
    int dir=4, step;
    int f = (stm == WHITE ? -16 : 16); // forward
    int p = (stm == WHITE ? 2 : 1);
    if(!(k + f + 1 & 0x88) && board[k + f + 1] == xstm + p) return k + f + 2;
    if(!(k + f - 1 & 0x88) && board[k + f - 1] == xstm + p) return k + f;
    while((step = steps[dir])) {
	int from = k + steps[dir + 14];
	if(!(from & 0x88) && board[from] == xstm + 4) return from + 1;
	from = k + step;
	if(!(from & 0x88) && board[from] == xstm + 3) return from + 1;
	from = k;
	while(!((from += step) & 0x88)) if(board[from]) { // occupied
	    if(dir < 8 && (board[from] & COLOR + 6) == xstm + 6) return from + 1; // R or Q and orthogonal
	    if(dir > 7 && (board[from] & COLOR + 5) == xstm + 5) return from + 1; // B or Q and diagonal
	    break;
	}
	dir++;
    }
    break;
  }
  return 0;
}

void
Count (int stm, int from, int to, void *cl)
{
  int s = (stm == BLACK);
  if(!(to - from & 7) && (board[from] & 7) < 3) return; // ignore Pawn non-captures
  attacks[s][to]++;
  if(lva[s][to] > value[board[from] & 7]) lva[s][to] = value[board[from] & 7];
}

void
Score (int stm, int from, int to, void *cl)
{
  int score = PST[to] - PST[from];
  int piece = board[from];
  int victim = board[to];
  int myVal = value[piece & 7];
  int hisVal = value[victim & 7];
  int push = (piece & 7) < 3 && to - from & 7;// Pawn non-capture
  int s = (stm == BLACK);
  int check;
  if((piece & 7) < 3 && !(to - from & 7) != !victim) return; // weed out illegal pawn modes
  if((piece & 7) == 3) score -= score;        // keep King out of center
  else if(myVal > 400) score = 0;             // no centralization for R, Q
  if((piece & ~7) == (victim & ~7)) return;   // self capture
  board[from] = 0; board[to] = piece;
  if(from != lastChecker && InCheck(COLOR - stm)) score += 50; // bonus for checking with new piece
  check = InCheck(stm);                       // in check after move?
  board[to] = victim; board[from] = piece;
  if(check) return;                           // illegal
  score += ((rand()>>8 & 31) - 16)*randomize; // randomize
  if(from == lastMover) score -= 10;          // discourage moving same piece twice
  if(hisVal && hisVal < 400) score += PST[to];// centralization bonus of victim
  score += hisVal;                            // captured piece
  if(attacks[!s][to]) {                       // to-square was attacked
    if(attacks[s][to] - 1 + push < attacks[!s][to] ) score -= myVal; else // not sufficiently protected
    if(myVal > lva[!s][to]) score += lva[!s][to] - myVal; // or protected, but more valuable
  }
  if((piece & 7) != 3 && attacks[!s][from]) { // from-square was attacked (and not King)
    if(attacks[s][from] < attacks[!s][from] ) score += myVal; else // not sufficiently protected
    if(myVal > lva[!s][from]) score -= lva[!s][from] - myVal; // or protected, but more valuable
  }
  if((piece & 7) == 1 && to < 48) score += 50;
  if((piece & 7) == 2 && to > 79) score += 50;

  if(score > bestScore) bestScore = score, bestFrom = from, bestTo = to; // remember best move
  if(post) printf("2 %d 0 1 %c%d%c%d\n", score, (from&7) + 'a', 8 - (from >> 4), (to&7) + 'a', 8 - (to >> 4));
}

int
Setup (char *fen)
{
  char c;
  int i;
  for(i=0; i<128; i++) board[i] = 0;
  i = 0;
  while((c = *fen++)) {
    if(c == 'p') board[i++] = BLACK + 2; else
    if(c >= '0' && c <= '9') i += c - '0'; else
    if(c >= 'a' && c <= 'z') board[i++] = BLACK + pieces[c - 'a'] - '0'; else
    if(c >= 'A' && c <= 'Z') board[i++] = WHITE + pieces[c - 'A'] - '0'; else
    if(c == '/') i = (i | 15) + 1; else break;
  }
  for(i=0; i<128; i = i + 9 & ~8) printf(i&7 ? " %2d" : "\n# %2d", board[i]); printf("\n");
  return (*fen == 'w' ? WHITE : BLACK);
}

int
main ()
{
  int stm = WHITE, engineSide = 0;
  char line[256], command[20];
  srand(GetTickCount());
  while(1) {
    int i, c;
    if(stm == engineSide) {
	char *promo = "";
	for(i=0; i<128; i++) lva[0][i] = lva[1][i] = 30000, attacks[0][i] = attacks[1][i] = 0;
	MoveGen(board, COLOR, &Count, NULL);
	bestScore = -30000;
	MoveGen(board, stm, &Score, NULL);
	board[bestTo] = board[bestFrom]; board[bestFrom] = 0; stm ^= COLOR;
	if((board[bestTo] & 7) < 3 && (stm == BLACK ? bestTo < 16 : bestTo > 111)) board[bestTo] |= 7, promo = "q"; // always promote to Q
	lastMover = bestTo;
	lastChecker = InCheck(stm) ? bestTo : -1 ;
	printf("move %c%d%c%d%s\n", (bestFrom&7) + 'a', 8 - (bestFrom >> 4), (bestTo&7) + 'a', 8 - (bestTo >> 4), promo);
    }
    fflush(stdout); i = 0;
    while((line[i++] = c = getchar()) != '\n') if(c == EOF) printf("# EOF\n"), exit(1); line[i] = '\0';
    if(*line == '\n') continue;
    sscanf(line, "%s", command);
    printf("# command: %s\n", command);
    if(!strcmp(command, "usermove")) {
	int from, to; char c, d, promo, ep;
	sscanf(line, "usermove %c%d%c%d%c", &c, &from, &d, &to, &promo);
	from = (8 - from)*16 + c - 'a'; to = (8 - to)*16 + d - 'a';
	if((board[from] & 7) == 3 && to - from == 2) board[from + 1] = board[to + 1], board[to + 1] = 0; // K-side castling
	if((board[from] & 7) == 3 && from - to == 2) board[from - 1] = board[to - 2], board[to - 2] = 0; // Q-side castling
	ep = ((board[from] & 7) < 3 && !board[to]); // recognize e.p. capture
	board[to] = board[from]; if(ep) board[from & 0x70 | to & 7] = 0; board[from] = 0;
	if(promo == 'q') board[to] = board[to] | 7; // promote
	stm ^= COLOR;	
    }
    else if(!strcmp(command, "protover")) printf("feature myname=\"N.E.G. 1.1\" setboard=1 usermove=1 analyze=0 colors=0 sigint=0 sigterm=0 done=1\n");
    else if(!strcmp(command, "new"))      stm = Setup("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"), randomize = 0, engineSide = BLACK;
    else if(!strcmp(command, "go"))       engineSide = stm;
    else if(!strcmp(command, "result"))   engineSide = 0;
    else if(!strcmp(command, "force"))    engineSide = 0;
    else if(!strcmp(command, "setboard")) stm = Setup(line+9);
    else if(!strcmp(command, "random"))   randomize = !randomize;
    else if(!strcmp(command, "post"))     post = 1;
    else if(!strcmp(command, "nopost"))   post = 0;
    else if(!strcmp(command, "quit"))     break;
  }
  return 0;
}
Hi HGM

What a fantastic idea to share the source code within a post!
I have a couple of questions regarding implementation.

The move generator code reminds me microMax:
1. 0x88 board representation (but PST are separate instead of being on the right part of unused board squares)
2. Three nested loops in movegen
3. Faking captures for non-sliding pieces

But what I can't figure out is whether it supports castling, in microMax there was a rocket science level line that was handling
double pawn push and castling (for you've explained that the nature of these two actions is same in essence). Here in NEG I can see line:

Code: Select all

if(!(to - from & 7) && type < 3 && (type == 1 ? to > 79 : to < 48)) victim--;
It seems to my code monkey's eyes to be responsible for double pawn push and castling but castling part is much simpler compared to microMax.
I assume that happens because here king capture is not allowed (hence InCheck function)

So the question is - is it full FIDE rules (apart from promotions and probably 50 rule/3 fold repetitions)?

Also I would like to ask for your permission:
Can I make a tutorial series on NEG's movegen? (MicroMax is too complicated because of movegen is embedded into a search, beta cutoffs on king capture and other unusual stuff like remembering best from square to order first in the next iteration of IID)

I'm also now playing with MCTS algorithm and looking for some light weight movegen for this purpose (my bitboard based BBC is huge and I just don't want to use it for that purpose), so can I please use your NEG's movegen in my project?

THANKS IN ADVANCE!

P.S. You can't ever imagine how happy I am with obtaining this source code! It's a GOLD for me! Thank you HGM!
User avatar
hgm
Posts: 28205
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: N.E.G. 1.0 released

Post by hgm »

maksimKorzh wrote: Wed Nov 11, 2020 1:51 amThe move generator code reminds me microMax:
1. 0x88 board representation (but PST are separate instead of being on the right part of unused board squares)
2. Three nested loops in movegen
3. Faking captures for non-sliding pieces
Nearly all my engines have a move generator like that. Sometimes the outer loop does not scan the board, but a piece list. Sometimes the table of start indexes in the array of board steps is not only indexed by piece type but also by from-square (making it a pise-square table for moves, rather than scores, to have location-dependent moving, as in Xiangqi).
But what I can't figure out is whether it supports castling, in microMax there was a rocket science level line that was handling
double pawn push and castling (for you've explained that the nature of these two actions is same in essence). Here in NEG I can see line:

Code: Select all

if(!(to - from & 7) && type < 3 && (type == 1 ? to > 79 : to < 48)) victim--;
It seems to my code monkey's eyes to be responsible for double pawn push and castling but castling part is much simpler compared to microMax.
I assume that happens because here king capture is not allowed (hence InCheck function)
Indeed, that is the double-push code, which undoes the kludge of faking a victim for leapers if the Pawn is still on 3rd rank. This is in the move generator. Castling and e.p. capture are only implemented for input moves; they are not in the move generator, and N.E.G. would never play them byitself. In the code for handling the "usermove" command the castling is done through:

Code: Select all

        if((board[from] & 7) == 3 && to - from == 2) board[from + 1] = board[to + 1], board[to + 1] = 0; // K-side castling
        if((board[from] & 7) == 3 && from - to == 2) board[from - 1] = board[to - 2], board[to - 2] = 0; // Q-side castling
This just recognizes sideway double steps by the King, and then moves the corresponding Rook in addition to the normal move performance.
So the question is - is it full FIDE rules (apart from promotions and probably 50 rule/3 fold repetitions)?
The move generator isn't; it doesn't generate castling or e.p. capture. To generate castlings according to the rules you would have to keep track of piece virginity. Micro-Max does this through an extra bit in the piece encodings on the board, but you could also keep track of a castling rights word (containing 1 bit per type of castling), and a board-size array that for each square tabulates which castling rights are destroyed when that square is used as from- or to-square. (Actually you only have to keep track of whether it is used as to-square, but then you would have to test whether it is non-empty to test if the corresponding castling is allowed.)
Also I would like to ask for your permission:
Can I make a tutorial series on NEG's movegen? (MicroMax is too complicated because of movegen is embedded into a search, beta cutoffs on king capture and other unusual stuff like remembering best from square to order first in the next iteration of IID)
Of course you can; N.E.G. can be considered public domain. There are also some unusual things in the N.E.G. move generator, though: it also generates 'pseudo-captures', i.e. capture of friendly pieces. So the 'consumer' of the moves (the callback) has to test whether it actually gets a pseudo-legal move, or just a 'protection', depending on what it wants to do (select a move for playing, or just count attackers and protectors).

Perhaps the move generator of KingSlayer / simple (line 841-932 in the latest commit) would be more suitable for your purpose. It also uses the three nested loops, similar to micro-Max, but is decoupled from search, and just puts the generated moves in a list. It also contains a few quirks (such as marking pieces protected by 'pseudo-captures' on a separate board, and a way to abort move generation when an 'off-scale' capture (such as that of a King) is found, and remember piece mobility, but you could easily delete the corresponding code sections from it without compromising the overall structure.
I'm also now playing with MCTS algorithm and looking for some light weight movegen for this purpose (my bitboard based BBC is huge and I just don't want to use it for that purpose), so can I please use your NEG's movegen in my project?

THANKS IN ADVANCE!

P.S. You can't ever imagine how happy I am with obtaining this source code! It's a GOLD for me! Thank you HGM!