4
* An implementation of computer intelligence (you wot?) for 3Dc.
8
3Dc, a game of 3-Dimensional Chess
9
Copyright (C) 1995,1996 Paul Hicks
11
This program is free software; you can redistribute it and/or modify
12
it under the terms of the GNU General Public License as published by
13
the Free Software Foundation; either version 2 of the License, or
14
(at your option) any later version.
16
This program is distributed in the hope that it will be useful,
17
but WITHOUT ANY WARRANTY; without even the implied warranty of
18
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
GNU General Public License for more details.
21
You should have received a copy of the GNU General Public License
22
along with this program; if not, write to the Free Software
23
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25
E-Mail: paulh@euristix.ie
39
#define MAX(a,b) ((a) < (b) ? (b) : (a))
40
#define MIN(a,b) ((a) > (b) ? (b) : (a))
44
Local struct stackList
46
stack *stacks[BEST_STACKS];
47
int ratings[BEST_STACKS];
49
{ NULL, NULL, NULL, NULL, NULL },
50
{ INT_MIN, INT_MIN, INT_MIN, INT_MIN, INT_MIN }
53
Local int values[ TITLES ] =
55
26, 42, 22, 10, 25, /* Royalty */
56
45, 21, 12, 24, 15, /* Nobility */
62
PushMove(Move *move, int value)
66
if ( value == INT_MIN )
69
for ( i = 0; (bestMoves.stacks [i] != NULL) &&
70
(bestMoves.ratings[i] > value) &&
75
if ( i == BEST_STACKS )
81
if ( bestMoves.ratings[i] == value )
83
StackPush( bestMoves.stacks[i], move );
92
/* Get rid of the lowest level (if it falls off the end) */
93
if (bestMoves.stacks[j] != NULL)
94
StackDelete( bestMoves.stacks[j] );
96
/* Move all the lower levels down */
99
bestMoves.stacks [j] = bestMoves.stacks [j-1];
100
bestMoves.ratings[j] = bestMoves.ratings[j-1];
103
/* Create the new stack */
104
bestMoves.stacks[i] = StackNew();
105
StackPush( bestMoves.stacks[i], move );
106
bestMoves.ratings[i] = value;
112
/* This function circumvents the problem of negative ratings
113
* by adding to all ratings enough to make the smallest rating == 0 */
119
for ( i = (BEST_STACKS -1); (bestMoves.stacks[i] == NULL) && (i >= 0); --i )
122
if ((i < 0) || (bestMoves.ratings[i] >= 0))
125
add = -(bestMoves.ratings[i]);
128
bestMoves.ratings[i] += add;
136
int stacks, randStack, randMove, randBase, randNum;
138
if ( bestMoves.stacks[0] == NULL )
142
* This algorithm works by generating a number randBase on which to base a
143
* call to random(): think of it as a die with randBase sides. randBase
144
* it dependant on the rating of the moves at the current level and
145
* on the level itself. It may be that the number of moves at the current
146
* level will also be a factor in later version. (A "level" is all moves
147
* with equal rating. Only the top BEST_STACKS levels are remembered).
148
* Then the generated random number is reverse engineered into a level.
149
* Finally a purely random choice of move within that level is made.
151
if ( bestMoves.ratings[0] == INT_MAX )
153
/* If this move wins the game then do it unconditionally */
159
for ( stacks = 0, randBase = 0;
160
( bestMoves.stacks[stacks] != NULL ) &&
161
( stacks < BEST_STACKS ) ;
163
randBase += ((BEST_STACKS-stacks) * bestMoves.ratings[stacks]);
165
randNum = random()%randBase;
167
for ( randStack = stacks-1;
168
(randStack > 0) && (randNum > 0); --randStack )
170
randNum -= (( BEST_STACKS - randStack ) *
171
bestMoves.ratings[randStack] );
174
randMove = random()%bestMoves.stacks[randStack]->nSize;
175
D( printf( "Choosing move %i from a stack of size %i\n",
176
randMove+1, bestMoves.stacks[randStack]->nSize ) );
179
ret = StackPeek( bestMoves.stacks[randStack], randMove );
180
CHECK((ret->xyzBefore.zLevel != 3) && (ret->xyzAfter.zLevel != 3));
182
/* This defeats the opaqueness of stack.c but it's easy */
183
/* It just clears the one move we've just made from the move stack */
185
/* If the chosen move was the top of the stack.. */
188
/* Don't free the move: it's "ret" and still in use! */
189
StackPop( bestMoves.stacks[randStack] );
191
/* If there is only the one move then we remove the stack */
192
if ( bestMoves.stacks[randStack]->nSize == 0 )
194
StackDelete( bestMoves.stacks[randStack] );
196
while ( ((randStack+1) < BEST_STACKS) &&
197
(bestMoves.stacks[randStack+1] != NULL) )
199
bestMoves.stacks [randStack] = bestMoves.stacks [randStack+1];
200
bestMoves.ratings[randStack] = bestMoves.ratings[randStack+1];
204
bestMoves.stacks[randStack] = NULL;
205
bestMoves.ratings[randStack] = INT_MIN;
208
/* We used a second or later move */
212
struct stack_el *el, *temp;
214
el = bestMoves.stacks[randStack]->top;
216
for (i = 1; (i < randMove) && (el->below != NULL); ++i)
221
if (!CHECK((el->below != NULL) && (el->below->mvt == ret)))
223
/* Hopefully this can never happen */
228
el->below = temp->below;
229
bestMoves.stacks[randStack]->nSize--;
238
RateMove( Move *move, Colour bwSide )
243
Piece *moving, *storing;
244
bwEnemy = ( bwSide == WHITE ) ? BLACK : WHITE;
246
/* Rate taking king */
247
if (move->pVictim == Muster[ bwEnemy ][ MusterIdx( king, 0 )])
250
/* Fake the move for simple lookahead */
251
moving = Board[ move->xyzBefore.zLevel ]
252
[ move->xyzBefore.yRank ]
253
[ move->xyzBefore.xFile ];
254
storing = Board[ move->xyzAfter.zLevel ]
255
[ move->xyzAfter.yRank ]
256
[ move->xyzAfter.xFile ];
258
if (!CHECK( moving != NULL ))
261
/* Rate saving king */
262
/* It might be more efficient to put the IsKingChecked() call
263
* inside the move faked below but that would mean nasty code
264
* duplication re: finding king coords and so on. This code
265
* is easier to maintain. */
266
if ( FakeMoveAndIsKingChecked( Muster[ bwSide ][ MusterIdx( king, 0 )],
267
move->xyzAfter.xFile,
268
move->xyzAfter.yRank,
269
move->xyzAfter.zLevel ))
273
Board[ move->xyzBefore.zLevel ]
274
[ move->xyzBefore.yRank ]
275
[ move->xyzBefore.xFile ] = NULL;
276
Board[ move->xyzAfter.zLevel ]
277
[ move->xyzAfter.yRank ]
278
[ move->xyzAfter.xFile ] = moving;
279
if ( storing != NULL )
280
storing->bVisible = FALSE;
283
xyzPos = (Muster[ bwEnemy ][ MusterIdx( king, 0 )])->xyzPos;
284
if ( SquareThreatened( bwSide,
285
xyzPos.xFile, xyzPos.yRank, xyzPos.zLevel) != NULL )
286
rating += values[ king ];
288
/* Rate danger: if there's a chance of being captured in the new pos. */
289
xyzPos = move->xyzAfter;
290
if ( SquareThreatened( bwEnemy,
291
xyzPos.xFile, xyzPos.yRank, xyzPos.zLevel) != NULL )
292
rating -= (values[ moving->nName ] /2);
295
Board[ move->xyzBefore.zLevel ]
296
[ move->xyzBefore.yRank ]
297
[ move->xyzBefore.xFile ] = moving;
298
Board[ move->xyzAfter.zLevel ]
299
[ move->xyzAfter.yRank ]
300
[ move->xyzAfter.xFile ] = storing;
301
if ( storing != NULL )
302
storing->bVisible = TRUE;
305
if (( move->pVictim != NULL ) &&
306
( move->pVictim->bwSide == bwEnemy ))
310
rating += values[ move->pVictim->nName ];
312
/* Rate evasion: if there's a chance of any friendly piece
313
* being captured in the old pos but not in the new (this isn't
314
* an authorative check and needs to be enhanced). */
315
for ( i = 0; i < PIECES; ++i )
317
if ( Muster[ bwSide ][ i ]->bVisible &&
318
SquareThreatened( bwEnemy,
319
Muster[ bwSide ][ i ]->xyzPos.xFile,
320
Muster[ bwSide ][ i ]->xyzPos.yRank,
321
Muster[ bwSide ][ i ]->xyzPos.zLevel ) ==
323
rating += (values[ Muster[ bwSide ][ i ]->nName ] /2);
327
/* Rate special moves */
328
/* En passant and castling not yet possible for computer */
329
if (( move->pVictim != NULL ) &&
330
( move->pVictim->bwSide == bwSide))
331
rating += 10; /* Castling */
332
if (( move->xyzAfter.yRank == (bwSide == WHITE ? RANKS-1 : 0) ) &&
333
( moving->nName == pawn ))
334
rating += values[queen]; /* Promotion */
335
/* Note the horrible magic numbers below */
336
if ( (ABS( move->xyzAfter.yRank - move->xyzBefore.yRank ) == 2) &&
337
( moving->nName == pawn ))
338
rating += 1; /* Two-forward by pawn: the computer doesn't
339
* usually like opening its attack routes otherwise */
341
/* Rate position; distance forward (should be proximity to
342
* enemy king except for pawns */
343
if ( bwSide == WHITE )
344
rating += move->xyzAfter.yRank - move->xyzBefore.yRank;
346
rating += 7 - move->xyzAfter.yRank + move->xyzBefore.yRank;
352
GenMove(const Colour bwSide, Move **ret)
354
Local int pieceIdx = 0;
358
/* First clear out any old moves */
363
for ( i = 0; (bestMoves.stacks[i] != NULL) && (i < BEST_STACKS); ++i)
365
StackDelete(bestMoves.stacks[i]);
366
bestMoves.stacks[i] = NULL;
367
bestMoves.ratings[i] = INT_MIN;
371
if ( Muster[bwSide][pieceIdx]->bVisible )
373
moves = FindAllMoves( Muster[bwSide][pieceIdx] );
380
while ( (thisMove = StackPop( moves )) != NULL )
382
PushMove( thisMove, RateMove( thisMove, bwSide ) );
385
StackDelete( moves );
389
if (++pieceIdx == PIECES)
401
/* This tries again for a move in case the last one failed for some reason */
403
GenAltMove(const Colour bwSide, Move **ret)
409
/* Creates a stack of legal moves that this piece can take in this go */
411
FindAllMoves(Piece *piece)
418
#define CURX (piece->xyzPos.xFile)
419
#define CURY (piece->xyzPos.yRank)
420
#define CURZ (piece->xyzPos.zLevel)
426
bwEnemy = ((piece->bwSide == WHITE) ? BLACK : WHITE);
427
move.xyzBefore = piece->xyzPos;
429
if (piece->nName == knight)
431
for (y = MAX(0, CURY -2); y < MIN(RANKS, CURY +3); y++)
435
for (x = MAX(0, CURX -2); x < MIN(FILES, CURX +3); x++)
440
if (ABS(CURX-x) == ABS(CURY-y))
443
if ((Board[CURZ][y][x] == NULL) ||
444
(Board[CURZ][y][x]->bwSide == bwEnemy))
446
move.xyzAfter.xFile = x;
447
move.xyzAfter.yRank = y;
448
move.xyzAfter.zLevel = CURZ;
449
move.pVictim = Board[CURZ][y][x];
450
move.nHadMoved = piece->bHasMoved;
452
if (!FakeMoveAndIsKingChecked( piece, x, y, CURZ ))
453
StackPush(moves, &move);
454
} /* End valid move */
458
else if (piece->nName == cannon)
460
for (z = 0; z < LEVELS; z++)
465
for (y = MAX( 0, CURY -3 ); y < MIN( RANKS, CURY +4 ); y++)
470
for (x = MAX( 0, CURX -3 ); x < MIN( FILES, CURX +4 ); x++)
475
if ((ABS(CURX-x) == ABS(CURY-y)) ||
476
(ABS(CURX-x) == ABS(CURZ-z)) ||
477
(ABS(CURY-y) == ABS(CURZ-z)))
480
if ((Board[z][y][x] == NULL) ||
481
(Board[z][y][x]->bwSide == bwEnemy))
483
move.xyzAfter.xFile = x;
484
move.xyzAfter.yRank = y;
485
move.xyzAfter.zLevel = z;
486
move.pVictim = Board[z][y][x];
487
move.nHadMoved = piece->bHasMoved;
489
if (!FakeMoveAndIsKingChecked( piece, x, y, z ))
490
StackPush(moves, &move);
491
} /* End valid move */
496
else if (piece->nName == pawn) /* Don't bother searching for en passant */
498
y = ((piece->bwSide == WHITE) ? 1 : -1);
500
for (x = MAX(0, CURX-1); x < MIN(FILES, CURX+2); ++x)
502
/* Due to the complexity of the conditional this time,
503
* I've opted for aborting when illegal instead of
504
* proceeding when legal. */
505
if ((x == CURX) && (Board[CURZ][CURY+y][CURX] != NULL))
509
((Board[CURZ][CURY + y][x] == NULL) ||
510
((Board[CURZ][CURY + y][x])->bwSide != bwEnemy)) )
513
move.xyzAfter.xFile = x;
514
move.xyzAfter.yRank = CURY + y;
515
move.xyzAfter.zLevel = CURZ;
516
move.pVictim = Board[CURZ][CURY + y][x];
517
move.nHadMoved = piece->bHasMoved;
519
if (!FakeMoveAndIsKingChecked(piece, x, CURY+y, CURZ))
520
StackPush(moves, &move);
522
/* This next conditional is for the two-forward move:
523
* it only happens when the previous attempt was the one-forward
524
* move and makes assumptions based on that fact. */
525
if ( (x==CURX) && (piece->bHasMoved == FALSE) &&
526
(Board[CURZ][CURY + y+y][CURX] == NULL) )
528
move.xyzAfter.yRank += y;
529
if (!FakeMoveAndIsKingChecked(piece, CURX, CURY+y+y, CURZ))
530
StackPush(moves, &move);
540
* The king and prince can only move one square;
541
* all others can move MAX(FILES,RANKS)-1.
542
* For a regular board, this is 7. (Not 8: If you moved 8
543
* in any direction you would be off the edge of the board)
545
if ((piece->nName == king) ||
546
(piece->nName == prince))
549
dist = MAX(FILES, RANKS) -1;
551
for (z = -1; z <= 1; ++z)
554
* Cater for pieces that can't change level.
556
if (((piece->nName == prince) ||
557
(piece->nName == princess) ||
558
(piece->nName == abbey) ||
559
(piece->nName == galley)) &&
563
for (y = -1; y <= 1; ++y)
565
for (x = -1; x <= 1; ++x)
567
if ((x==0) && (y==0) && (z==0))
571
* Cater for the pieces that can only move
572
* horizontally/vertically.
574
if (((piece->nName == rook) ||
575
(piece->nName == galley)) &&
579
* Cater for the pieces that can only move
582
else if (((piece->nName == bishop) ||
583
(piece->nName == abbey)) &&
587
for (d = 1; d <= dist; ++d)
589
pEncountered = TraverseDir(piece, x, y, z, d);
590
if (IsMoveLegal(piece, pEncountered))
592
move.xyzAfter = pEncountered->xyzPos;
593
move.pVictim = pEncountered;
594
move.nHadMoved = piece->bHasMoved;
596
/* Check for putting own king in check */
597
if (!FakeMoveAndIsKingChecked(piece,
598
pEncountered->xyzPos.xFile,
599
pEncountered->xyzPos.yRank,
600
pEncountered->xyzPos.zLevel))
601
StackPush(moves, &move);
604
if (pEncountered != SQUARE_EMPTY)
605
break; /* No point on continuing in this direction if
606
* we've hit a piece or the edge of the board.. */