N.E.G. 1.0 released

Discussion of chess software programming and technical issues.

Moderators: hgm, chrisw, Rebel

User avatar
hgm
Posts: 28185
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

N.E.G. 1.0 released

Post by hgm »

Just for fun I made a small improvement to my non-searching Chess engine N.E.G.: it had a nasty tendency to draw games where it had reduced the opponent to a bare King, because when there is nothing left to capture it prefers checks, and chasing the bare King with a Queen (which often was the only active piece) of course leads to nothing.

So in this new version it only gets the bonus for checking with a new piece. This usually is enough to get a bare King checkmated.

I also made it WB v2, supporting setboard, and Linux-compatible. This is the source code:

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) {
	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 == WHITE ? bestTo < 16 : bestTo > 111)) board[bestTo] |= 7; // always promote to Q
	lastMover = bestTo;
	lastChecker = InCheck(stm) ? bestTo : -1 ;
	printf("move %c%d%c%d\n", (bestFrom&7) + 'a', 8 - (bestFrom >> 4), (bestTo&7) + 'a', 8 - (bestTo >> 4));
    }
    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.0\" 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;
}
User avatar
hgm
Posts: 28185
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 »

Oops, I already discovered a bug. The 'always promote to Q' line has the colors reversed, and should have read:

Code: Select all

	if((board[bestTo] & 7) < 3 && (stm == BLACK ? bestTo < 16 : bestTo > 111)) board[bestTo] |= 7; // always promote to Q
As it was, it would never promote its own Pawns, but just leave them as dead wood on the last rank.

Fairy-Max with a depth limit of 1 ply scores about 72% against N.E.G. (measured over 100 games). This doesn't give a proper indication of the strength difference, however, as most points of N.E.G. are due to draws, being stalemated in a totally lost position. (At 1 ply Fairy-Max can see checkmate, due to the check-extension + King capture in QS, but not stalemate which does not start with check.)

Nevertheless, N.E.G. was able to win 8 games out of 100.
Adam Hair
Posts: 3226
Joined: Wed May 06, 2009 10:31 pm
Location: Fuquay-Varina, North Carolina

Re: N.E.G. 1.0 released

Post by Adam Hair »

I wonder how it would do against Usurpator. I have a Pentium IV sitting around that I may load with Winboard and use to run that match.
Roger Brown
Posts: 782
Joined: Wed Mar 08, 2006 9:22 pm

Re: N.E.G. 1.0 released

Post by Roger Brown »

hgm wrote:Just for fun I made a small improvement to my non-searching Chess engine N.E.G.: it had a nasty tendency to draw games where it had reduced the opponent to a bare King, because when there is nothing left to capture it prefers checks, and chasing the bare King with a Queen (which often was the only active piece) of course leads to nothing.

So in this new version it only gets the bonus for checking with a new piece. This usually is enough to get a bare King checkmated.

I also made it WB v2, supporting setboard, and Linux-compatible. This is the source code:

Code: Select all

SNIP
Hello H.G.,

I went to your site but the the N.E.G. version available is 0.3d.exe.

Where is version 1.0 available?

Later.
Sven
Posts: 4052
Joined: Thu May 15, 2008 9:57 pm
Location: Berlin, Germany
Full name: Sven Schüle

Re: N.E.G. 1.0 released

Post by Sven »

hgm wrote:Oops, I already discovered a bug. The 'always promote to Q' line has the colors reversed, and should have read:

Code: Select all

	if((board[bestTo] & 7) < 3 && (stm == BLACK ? bestTo < 16 : bestTo > 111)) board[bestTo] |= 7; // always promote to Q
As it was, it would never promote its own Pawns, but just leave them as dead wood on the last rank.

Fairy-Max with a depth limit of 1 ply scores about 72% against N.E.G. (measured over 100 games). This doesn't give a proper indication of the strength difference, however, as most points of N.E.G. are due to draws, being stalemated in a totally lost position. (At 1 ply Fairy-Max can see checkmate, due to the check-extension + King capture in QS, but not stalemate which does not start with check.)

Nevertheless, N.E.G. was able to win 8 games out of 100.
Isn't the command[20] array in main() too small for a "setboard" command?
mar
Posts: 2598
Joined: Fri Nov 26, 2010 2:00 pm
Location: Czech Republic
Full name: Martin Sedlak

Re: N.E.G. 1.0 released

Post by mar »

Sven Schüle wrote:Isn't the command[20] array in main() too small for a "setboard" command?
setboard is only 9 chars (including zero terminator). line is 256 bytes, should be enough.
Sven
Posts: 4052
Joined: Thu May 15, 2008 9:57 pm
Location: Berlin, Germany
Full name: Sven Schüle

Re: N.E.G. 1.0 released

Post by Sven »

mar wrote:
Sven Schüle wrote:Isn't the command[20] array in main() too small for a "setboard" command?
setboard is only 9 chars (including zero terminator). line is 256 bytes, should be enough.
Agreed. The sscanf(line, "%s", command) confused me but actually it stores the first word ending at the first whitespace in 'command' (the special handling of whitespace characters in (s)scanf looks inconsistent to me but it is as it is ...). I never use functions from the scanf family in my life, and now once again I see why.
flok

Re: N.E.G. 1.0 released

Post by flok »

I just got an error from cutechess-cli (got cc-c to work eventually) that neg played an invalid move:

Code: Select all

[Event "?"] 
[Site "?"] 
[Date "2014.12.27"] 
[Round "2"] 
[White "neg"] 
[Black "600f"] 
[Result "0-1"] 
[ECO "A00"] 
[Opening "Dunst (Sleipner, Heinrichsen) Opening"] 
[PlyCount "128"] 
[TimeControl "50/90+1"] 
 
1. Nc3 {+0.14/2 0s} d5 {1.5s} 2. Nf3 {+0.14/2 0s} Be6 {1.5s} 3. d4 {0.00/2 0s} 
Bg4 {1.5s} 4. Be3 {0.00/2 0s} Bxf3 {1.6s} 5. gxf3 {0.00/2 0s} c6 {1.6s} 
6. a4 {0.00/2 0s} h5 {1.6s} 7. b4 {0.00/2 0s} Qd7 {1.7s} 8. h4 {0.00/2 0s} 
f5 {1.7s} 9. Bg2 {0.00/2 0s} Qc8 {1.8s} 10. f4 {0.00/2 0s} Kd8 {1.8s} 
11. Bf3 {0.00/2 0s} Rh7 {1.9s} 12. a5 {0.00/2 0s} Nf6 {1.9s} 13. Rb1 {0.00/2 0s} 
Rh6 {2.0s} 14. Qc1 {0.00/2 0s} a6 {2.1s} 15. Ra1 {0.00/2 0s} e6 {2.2s} 
16. b5 {0.00/2 0s} axb5 {2.2s} 17. Rb1 {0.00/2 0s} Rxa5 {2.3s} 
18. Qd1 {0.00/2 0s} Ra3 {2.5s} 19. Nxb5 {0.00/2 0s} cxb5 {2.6s} 
20. Rxb5 {0.00/2 0s} Rxe3 {2.8s} 21. fxe3 {0.00/2 0s} Rh7 {3.0s} 
22. Ra5 {0.00/2 0s} Ne8 {3.2s} 23. Qc1 {0.00/2 0s} Nc7 {3.6s} 24. c3 {0.00/2 0s} 
b6 {4.0s} 25. Ra4 {0.00/2 0s} b5 {4.1s} 26. Ra2 {0.00/2 0s} e5 {1.1s} 
27. fxe5 {0.00/2 0s} Ke8 {1.1s} 28. Rb2 {0.00/2 0s} Qb7 {1.1s} 
29. Qd1 {0.00/2 0s} Kf7 {1.1s} 30. Rc2 {0.00/2 0s} Kg6 {1.1s} 
31. Rg1+ {0.00/2 0s} Kf7 {1.1s} 32. Rd2 {-4.02/2 0s} Nc6 {1.1s} 
33. Qc1 {-3.42/2 0s} Nxe5 {1.1s} 34. dxe5 {-3.42/2 0s} f4 {1.1s} 
35. exf4 {-3.42/2 0s} Ba3 {1.1s} 36. Qxa3 {-3.42/2 0s} Qb6 {1.1s} 
37. Rh1 {+1.58/2 0s} Qc6 {1.1s} 38. e3 {-0.10/2 0s} g6 {1.1s} 
39. Qb3 {0.00/2 0s} Qb7 {1.1s} 40. Bxd5+ {0.00/2 0s} Nxd5 {1.1s} 
41. Qxd5+ {0.00/2 0s} Qxd5 {1.1s} 42. Rxd5 {+5.00/2 0s} b4 {1.1s} 
43. cxb4 {0.00/2 0s} Ke6 {1.1s} 44. Rd6+ {0.00/2 0s} Ke7 {1.1s} 
45. Rxg6 {0.00/2 0s} Kf7 {1.1s} 46. Rf6+ {0.00/2 0s} Ke7 {1.1s} 
47. e4 {0.00/2 0s} Rh8 {1.1s} 48. Rg6 {0.00/2 0s} Rh7 {1.1s} 49. b5 {0.00/2 0s} 
Rh6 {1.1s} 50. Rxh6 {0.00/2 0s} Kf7 {1.1s} 51. Rxh5 {0.00/2 0s} Kg6 {2.1s} 
52. Rg5+ {0.00/2 0s} Kf7 {2.0s} 53. b6 {0.00/2 0s} Ke6 {2.0s} 
54. Rg6+ {0.00/2 0s} Kf7 {2.0s} 55. Rh6 {0.00/2 0s} Kg7 {2.0s} 
56. Rf6 {0.00/2 0s} Kh7 {2.0s} 57. e6 {0.00/2 0s} Kg7 {2.0s} 
58. Rf7+ {0.00/2 0s} Kh6 {2.0s} 59. b7 {0.00/2 0s} Kg6 {2.0s} 
60. f5+ {0.00/2 0s} Kh6 {2.0s} 61. Rf6+ {0.00/2 0s} Kg7 {2.0s} 
62. Rg6+ {0.00/2 0s} Kh7 {1.9s} 63. f6 {0.00/2 0s} Kxg6 {1.9s} 
64. Rg1+ {0.00/2 0s} Kxf6 {1.9s, White makes an illegal move: b7b8} 0-1 
User avatar
hgm
Posts: 28185
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 »

Oh, I guess cutechess-cli does not assume Q as default promotion choice. I will fix that later today.
User avatar
hgm
Posts: 28185
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 »

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;
}