Page 3 of 4

Re: N.E.G. 1.0 released

Posted: Sun Dec 29, 2019 3:19 pm
by Roland Chastain
hgm wrote:
Sun Dec 29, 2019 10: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% 

Re: N.E.G. 1.0 released

Posted: Sun Dec 29, 2019 7:46 pm
by flok
Roland Chastain wrote:
Sun Dec 29, 2019 3: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.

Re: N.E.G. 1.0 released

Posted: Mon Dec 30, 2019 1:30 am
by Roland Chastain
flok wrote:
Sun Dec 29, 2019 7: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.

Re: N.E.G. 1.0 released

Posted: Mon Dec 30, 2019 9:44 pm
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.

Re: N.E.G. 1.0 released

Posted: Tue Dec 31, 2019 7:37 am
by Guenther
Roland Chastain wrote:
Mon Dec 30, 2019 9: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

Re: N.E.G. 1.0 released

Posted: Tue Dec 31, 2019 7:42 am
by Roland Chastain
Guenther wrote:
Tue Dec 31, 2019 7: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.

Re: N.E.G. 1.0 released

Posted: Mon Jan 13, 2020 12:35 pm
by ydebilloez
Under Arena 3.9 linux 64 bit, it does not work.

Re: N.E.G. 1.0 released

Posted: Sun Nov 08, 2020 7:17 pm
by unserializable
Adam Hair wrote:
Sun Dec 29, 2019 2: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:




Re: N.E.G. 1.0 released

Posted: Wed Nov 11, 2020 12:51 am
by maksimKorzh
hgm wrote:
Sun Dec 28, 2014 1: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&#40;)
     &#123;	struct timeval t;
	gettimeofday&#40;&t, NULL&#41;;
	return t.tv_sec*1000 + t.tv_usec/1000;
     &#125;
#endif

#define WHITE 8
#define BLACK 16
#define COLOR &#40;WHITE|BLACK&#41;


typedef void Func&#40;int stm, int from, int to, void *closure&#41;;
typedef int Board&#91;128&#93;;

int value&#91;8&#93; = &#123; 0, 100, 100, 10000, 325, 350, 500, 950 &#125;;
int firstDir&#91;&#93; = &#123; 0, 0, 27, 4, 18, 8, 13, 4 &#125;;
int steps&#91;&#93; = &#123; -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 &#125;;
Board PST = &#123;
 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
&#125;;


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

void
MoveGen &#40;Board board, int stm, Func proc, void *cl&#41;
&#123;
  int from, to, piece, victim, type, dir, step;
  for&#40;from=0; from<128; from = from + 9 & ~8&#41; &#123;
    piece = board&#91;from&#93;;
    if&#40;piece & stm&#41; &#123;
      type = piece & 7;
      dir = firstDir&#91;type&#93;;
      while&#40;&#40;step = steps&#91;dir++&#93;)) &#123;
        to = from;
        do &#123;
          to += step;
          if&#40;to & 0x88&#41; break;
          victim = board&#91;to&#93;;
          (*proc&#41;&#40;piece & COLOR, from, to, cl&#41;;
          victim += type < 5;
          if&#40;!&#40;to - from & 7&#41; && type < 3 && &#40;type == 1 ? to > 79 &#58; to < 48&#41;) victim--;
        &#125; while&#40;!victim&#41;;
      &#125;
    &#125;
  &#125;
&#125;

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

void
Count &#40;int stm, int from, int to, void *cl&#41;
&#123;
  int s = &#40;stm == BLACK&#41;;
  if&#40;!&#40;to - from & 7&#41; && &#40;board&#91;from&#93; & 7&#41; < 3&#41; return; // ignore Pawn non-captures
  attacks&#91;s&#93;&#91;to&#93;++;
  if&#40;lva&#91;s&#93;&#91;to&#93; > value&#91;board&#91;from&#93; & 7&#93;) lva&#91;s&#93;&#91;to&#93; = value&#91;board&#91;from&#93; & 7&#93;;
&#125;

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

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

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

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

Re: N.E.G. 1.0 released

Posted: Wed Nov 11, 2020 9:20 am
by hgm
maksimKorzh wrote:
Wed Nov 11, 2020 12:51 am
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
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!