1 /*
2 * piece.c
3 *
4 * Rules for all pieces.
5 */
6 /*
7
8 3Dc, a game of 3-Dimensional Chess
9 Copyright (C) 1995 Paul Hicks
10
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.
15
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.
20
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.
24
25 E-Mail: paulh@euristix.ie
26 */
27
28 #include <stdlib.h>
29 #include "machine.h"
30 #include "3Dc.h"
31
32 #define PARAMS (Piece *, File, Rank, Level)
33
34 Local INLINE Boolean
35 KingMayMove PARAMS,
36 QueenMayMove PARAMS,
37 BishopMayMove PARAMS,
38 KnightMayMove PARAMS,
39 RookMayMove PARAMS,
40 PrinceMayMove PARAMS,
41 PrincessMayMove PARAMS,
42 AbbeyMayMove PARAMS,
43 CannonMayMove PARAMS,
44 GalleyMayMove PARAMS,
45 PawnMayMove PARAMS;
46
47 #undef PARAMS
48
49 /* This function interprets the result of TraverseDir(piece...) */
50 Global Boolean
IsMoveLegal(const Piece * piece,const Piece * dest)51 IsMoveLegal(const Piece *piece, const Piece *dest)
52 {
53 if (dest == SQUARE_EMPTY)
54 return TRUE;
55 if (dest == SQUARE_INVALID)
56 {
57 n3DcErr = E3DcSIMPLE;
58 return FALSE;
59 }
60 else if ( piece->bwSide == dest->bwSide )
61 {
62 n3DcErr = E3DcBLOCK;
63 return FALSE;
64 }
65
66 return TRUE;
67 }
68
69 Global Piece *
PieceNew(const Title nType,const File x,const Rank y,const Level z,const Colour col)70 PieceNew(const Title nType,
71 const File x, const Rank y, const Level z,
72 const Colour col)
73 {
74 Piece *piece;
75
76 piece = (Piece *)malloc(sizeof(Piece));
77
78 if (!piece)
79 return NULL;
80
81 piece->xyzPos.xFile = x;
82 piece->xyzPos.yRank = y;
83 piece->xyzPos.zLevel = z;
84
85 piece->bwSide = col;
86 piece->nName = nType;
87 piece->bVisible = TRUE;
88 piece->bHasMoved = FALSE;
89
90 return piece;
91 }
92
93 Global void
PieceDelete(Piece * piece)94 PieceDelete(Piece *piece)
95 {
96 if (Board[piece->xyzPos.zLevel][piece->xyzPos.yRank][piece->xyzPos.xFile] ==
97 piece)
98 Board[piece->xyzPos.zLevel][piece->xyzPos.yRank][piece->xyzPos.xFile] =
99 NULL;
100
101 /* We don't need to remove the piece from the muster, as pieceDelete
102 * is only called when restarting, at which time init3Dc is also called,
103 * thereby overwriting Muster's reference to the piece. The only reason
104 * for removing the piece from Board above is so that the board may be
105 * redrawn cleanly.
106 */
107
108 free(piece);
109 piece = NULL;
110 }
111
112 Global Boolean
PieceMayMove(Piece * piece,const File xNew,const Rank yNew,const Level zNew)113 PieceMayMove(Piece *piece,
114 const File xNew, const Rank yNew, const Level zNew)
115 {
116 Boolean retval;
117
118 if (!piece || !piece->bVisible)
119 {
120 n3DcErr = E3DcINVIS;
121 return FALSE;
122 }
123
124 /* Do bits which are the same for all pieces first */
125 if (xNew == piece->xyzPos.xFile &&
126 yNew == piece->xyzPos.yRank &&
127 zNew == piece->xyzPos.zLevel)
128 {
129 n3DcErr = E3DcSIMPLE;
130 return FALSE;
131 }
132
133 if ((Board[zNew][yNew][xNew] != NULL) &&
134 (Board[zNew][yNew][xNew]->bVisible == TRUE) &&
135 (Board[zNew][yNew][xNew]->bwSide == piece->bwSide))
136 {
137 n3DcErr = E3DcBLOCK;
138 return FALSE; /* Can't take a piece on your team */
139 }
140
141 switch (piece->nName)
142 {
143 case king:
144 retval = KingMayMove(piece, xNew, yNew, zNew);
145 break;
146 case queen:
147 retval = QueenMayMove(piece, xNew, yNew, zNew);
148 break;
149 case bishop:
150 retval = BishopMayMove(piece, xNew, yNew, zNew);
151 break;
152 case knight:
153 retval = KnightMayMove(piece, xNew, yNew, zNew);
154 break;
155 case rook:
156 retval = RookMayMove(piece, xNew, yNew, zNew);
157 break;
158 case prince:
159 retval = PrinceMayMove(piece, xNew, yNew, zNew);
160 break;
161 case princess:
162 retval = PrincessMayMove(piece, xNew, yNew, zNew);
163 break;
164 case abbey:
165 retval = AbbeyMayMove(piece, xNew, yNew, zNew);
166 break;
167 case cannon:
168 retval = CannonMayMove(piece, xNew, yNew, zNew);
169 break;
170 case galley:
171 retval = GalleyMayMove(piece, xNew, yNew, zNew);
172 break;
173 case pawn:
174 retval = PawnMayMove(piece, xNew, yNew, zNew);
175 break;
176 default:
177 retval = FALSE;
178 n3DcErr = E3DcSIMPLE;
179 }
180
181 if ( retval != FALSE )
182 {
183 if ( FakeMoveAndIsKingChecked(piece, xNew, yNew, zNew) == TRUE )
184 {
185 n3DcErr = E3DcCHECK;
186 return FALSE;
187 }
188 }
189
190 return retval;
191 }
192
193 /*
194 * Execute the move
195 */
196 Global Boolean
PieceMove(Piece * piece,const File xNew,const Rank yNew,const Level zNew)197 PieceMove(Piece *piece,
198 const File xNew, const Rank yNew, const Level zNew)
199 {
200 Move thisMove;
201 Boolean moveType; /* Not quite Boolean... */
202
203 if (!(moveType = PieceMayMove(piece, xNew, yNew, zNew)))
204 return FALSE;
205
206 /*
207 * Keep record of move
208 */
209 thisMove.xyzBefore.xFile = piece->xyzPos.xFile;
210 thisMove.xyzBefore.yRank = piece->xyzPos.yRank;
211 thisMove.xyzBefore.zLevel = piece->xyzPos.zLevel;
212 thisMove.xyzAfter.xFile = xNew;
213 thisMove.xyzAfter.yRank = yNew;
214 thisMove.xyzAfter.zLevel = zNew;
215
216 if (moveType == EnPASSANT)
217 {
218 thisMove.nHadMoved = EnPASSANT;
219 thisMove.pVictim = Board[zNew][yNew + (piece->bwSide == WHITE ?
220 -1 : 1)][xNew];
221 }
222 else if (moveType == CASTLE)
223 {
224 thisMove.nHadMoved = CASTLE;
225 thisMove.pVictim = Board[1][yNew][xNew < ((FILES-1)/2) ? 0 : FILES-1];
226 }
227 else if (moveType == PROMOTE)
228 {
229 thisMove.nHadMoved = PROMOTE;
230 thisMove.pVictim = Board[zNew][yNew][xNew];
231 }
232 else
233 {
234 thisMove.nHadMoved = piece->bHasMoved;
235 thisMove.pVictim = Board[zNew][yNew][xNew];
236 }
237
238 StackPush(MoveStack, &thisMove);
239
240 piece->bHasMoved = TRUE;
241 PieceDisplay(piece, FALSE);
242 Board[piece->xyzPos.zLevel][piece->xyzPos.yRank][piece->xyzPos.xFile] = NULL;
243
244 if (Board[zNew][yNew][xNew]) /* Kill victim */
245 {
246 PieceDisplay(Board[zNew][yNew][xNew], FALSE);
247 Board[zNew][yNew][xNew]->bVisible = FALSE;
248 Board[zNew][yNew][xNew] = NULL;
249 }
250
251 Board[zNew][yNew][xNew] = piece;
252 piece->xyzPos.xFile = xNew;
253 piece->xyzPos.yRank = yNew;
254 piece->xyzPos.zLevel = zNew;
255 PieceDisplay(piece, TRUE);
256
257 /* Now move any special pieces */
258 if (moveType == CASTLE)
259 {
260 int xRookSrc, xRookDest;
261
262 /* If xNew on right of board then move to left
263 * else move to right */
264 if (xNew > (FILES/2))
265 {
266 xRookSrc = FILES -1;
267 xRookDest = xNew -1;
268 }
269 else
270 {
271 xRookSrc = 0;
272 xRookDest = xNew +1;
273 }
274
275 PieceDisplay(Board[1][yNew][xRookSrc], FALSE);
276
277 Board[1][yNew][xRookDest] = Board[1][yNew][xRookSrc];
278 Board[1][yNew][xRookSrc] = NULL;
279
280 (Board[1][yNew][xRookDest])->xyzPos.xFile = xRookDest;
281 (Board[1][yNew][xRookDest])->bHasMoved = TRUE;
282
283 PieceDisplay(Board[1][yNew][xRookDest], TRUE);
284 }
285 else if (moveType == EnPASSANT)
286 {
287 int yPawnSrc;
288
289 /* If yNew is forward of half-way then victim is back one
290 * else it is forward one */
291 yPawnSrc = (yNew > (RANKS/2) ? yNew-1 : yNew+1);
292 PieceDisplay(Board[zNew][yPawnSrc][xNew], FALSE);
293 Board[zNew][yPawnSrc][xNew]->bVisible = FALSE;
294 Board[zNew][yPawnSrc][xNew] = NULL;
295 }
296
297 #if 0
298 /* I think that this code is obsolete.. */
299 /* Check that the king isn't in check */
300 if (IsKingChecked( piece->bwSide ))
301 {
302 /* Oops, this move puts the king in check;
303 * it's illegal, so undo it */
304 PieceUndo();
305 n3DcErr = E3DcCHECK;
306 return FALSE;
307 }
308 #endif
309
310 /* If this bit is up with EnPASSANT and CASTLE, then the
311 * promotion dialog pops up even though the promotion is
312 * illegal. A promotion doesn't affect whether or not
313 * the opponent checks your king (even if you promote to
314 * cannon or something, you're still in the same place as
315 * the dissappearing pawn..) so it works out better all
316 * around if we just do it here. */
317 if (moveType == PROMOTE)
318 {
319 PieceDisplay(piece, FALSE);
320 PiecePromote(piece); /* This function asks for promotion type, etc. */
321 }
322
323 return TRUE;
324 }
325
326 /*
327 * Undo the move
328 */
329 Global Boolean
PieceUndo(void)330 PieceUndo(void)
331 {
332 Move *move;
333 Colour bwMoved, bwTaken;
334 Coord src, dest;
335
336 move = StackPop(MoveStack);
337 if (move == NULL)
338 return FALSE;
339
340 src = move->xyzAfter;
341 dest = move->xyzBefore;
342
343 bwMoved = Board[src.zLevel][src.yRank][src.xFile]->bwSide;
344 bwTaken = (bwMoved == WHITE ? BLACK : WHITE);
345
346 /* Clear the "moved-to" square */
347 PieceDisplay(Board[src.zLevel][src.yRank][src.xFile], FALSE);
348
349 /* Move the "moved" piece back */
350 Board[dest.zLevel][dest.yRank][dest.xFile] =
351 Board[src.zLevel][src.yRank][src.xFile];
352 (Board[dest.zLevel][dest.yRank][dest.xFile])->xyzPos = dest;
353
354 Board[src.zLevel][src.yRank][src.xFile] = NULL;
355
356 switch (move->nHadMoved)
357 {
358 case PROMOTE:
359 /* This piece was promoted from a pawn: demote it */
360 Board[dest.zLevel][dest.yRank][dest.xFile]->nName = pawn;
361 (Board[dest.zLevel][dest.yRank][dest.xFile])->bHasMoved = TRUE;
362 break;
363
364 case CASTLE:
365 {
366 int xRookSrc, xRookDest; /* xRookDest is beside edge */
367
368 /* The move undone was a castle */
369 /* The king is back in the right place; now
370 * fix the rook */
371 if (src.xFile < dest.xFile)
372 {
373 /* Castled to a smaller-id square (Queen's side for white,
374 * King's side for black) */
375 xRookSrc = dest.xFile -1;
376 xRookDest = 0;
377 }
378 else
379 {
380 /* Castled to a larger-id square (Queen's side for black,
381 * King's side for white) */
382 xRookSrc = dest.xFile +1;
383 xRookDest = FILES -1;
384 }
385
386 PieceDisplay(Board[1][dest.yRank][xRookSrc], FALSE);
387 Board[1][dest.yRank][xRookDest] = Board[1][dest.yRank][xRookSrc];
388 Board[1][dest.yRank][xRookSrc] = NULL;
389 (Board[1][dest.yRank][xRookDest])->xyzPos.xFile = xRookDest;
390 (Board[1][dest.yRank][xRookDest])->xyzPos.yRank = dest.yRank;
391 (Board[1][dest.yRank][xRookDest])->xyzPos.zLevel = 1;
392 PieceDisplay(Board[1][dest.yRank][xRookDest], TRUE);
393
394 /* And finally---reset the bHasMoved flags */
395 (Board[1][dest.yRank][dest.xFile])->bHasMoved = FALSE;
396 (Board[1][dest.yRank][xRookDest])->bHasMoved = FALSE;
397 }
398 break;
399
400 case EnPASSANT:
401 FallThrough();
402
403 default:
404 (Board[dest.zLevel][dest.yRank][dest.xFile])->bHasMoved =
405 move->nHadMoved;
406 }
407
408 /* Draw the piece in its original space */
409 PieceDisplay(Board[dest.zLevel][dest.yRank][dest.xFile], TRUE);
410
411 /* Put any taken piece back */
412 if (move->pVictim)
413 {
414 Coord srcPos;
415
416 /* Don't use src as the victim's square, as it could have
417 * been en passant */
418 srcPos = move->pVictim->xyzPos;
419
420 Board[srcPos.zLevel][srcPos.yRank][srcPos.xFile] = move->pVictim;
421 Board[srcPos.zLevel][srcPos.yRank][srcPos.xFile]->bVisible = TRUE;
422
423 PieceDisplay(Board[srcPos.zLevel][srcPos.yRank][srcPos.xFile], TRUE);
424 }
425
426 free(move);
427
428 return TRUE;
429 }
430
431 /*
432 * Here down are the specific piece-movement functions
433 *
434 * These all assume that piece is of the correct type.
435 * No check is made and things get very odd if this assumption
436 * is contradicted, so be careful.
437 */
438
439 Local INLINE Boolean
KingMayMove(Piece * piece,const File xNew,const Rank yNew,const Level zNew)440 KingMayMove(Piece *piece,
441 const File xNew, const Rank yNew, const Level zNew)
442 {
443 File xDiff, xCur, xInc;
444 Rank yDiff;
445 Level zDiff;
446
447 xDiff = xNew - piece->xyzPos.xFile;
448 yDiff = yNew - piece->xyzPos.yRank;
449 zDiff = zNew - piece->xyzPos.zLevel;
450
451 xDiff = ABS(xDiff);
452 yDiff = ABS(yDiff);
453 zDiff = ABS(zDiff);
454
455 /* Not allowed move more than 1 except when castling */
456 if ( (piece->bHasMoved && (xDiff > 2)) ||
457 (yDiff > 1) || (zDiff > 1) )
458 {
459 n3DcErr = E3DcDIST;
460 return FALSE;
461 }
462
463 /*
464 * At this stage, we have determined that, given an empty board,
465 * the move is legal. Now take other pieces into account.
466 */
467 if (FakeMoveAndIsKingChecked( piece, xNew, yNew, zNew) ||
468 ( (xDiff == 2) &&
469 FakeMoveAndIsKingChecked( piece, (xNew + piece->xyzPos.xFile)/2,
470 yNew, zNew ) ))
471 {
472 n3DcErr = E3DcCHECK;
473 return FALSE;
474 }
475
476 if (xDiff == 2)
477 { /* Castling */
478 File xRook;
479
480 if (yDiff || zDiff)
481 {
482 n3DcErr = E3DcSIMPLE;
483 return FALSE;
484 }
485
486 /*
487 * Determine x-pos of castling rook
488 */
489 if (xNew > piece->xyzPos.xFile)
490 xRook = FILES-1;
491 else
492 xRook = 0;
493
494 if (piece->bHasMoved ||
495 Board[1][yNew][xRook]->bHasMoved)
496 {
497 n3DcErr = E3DcMOVED;
498 return FALSE;
499 }
500 else if (!Board[1][yNew][xRook])
501 {
502 n3DcErr = E3DcSIMPLE;
503 return FALSE;
504 }
505
506 xInc = ( xRook == 0 ) ? -1 : 1 ;
507
508 for (xCur = piece->xyzPos.xFile + xInc; xCur != xRook; xCur += xInc)
509 { /* Is the castle blocked? */
510 if (Board[1][yNew][xCur])
511 {
512 n3DcErr = E3DcBLOCK;
513 return FALSE;
514 }
515 }
516
517 return CASTLE;
518 }
519
520 return TRUE;
521 }
522
523 Local INLINE Boolean
QueenMayMove(Piece * piece,const File xNew,const Rank yNew,const Level zNew)524 QueenMayMove(Piece *piece,
525 const File xNew, const Rank yNew, const Level zNew)
526 {
527 File xDiff;
528 Rank yDiff;
529 Level zDiff;
530 Piece
531 *pDestSquare;
532
533 xDiff = xNew - piece->xyzPos.xFile;
534 yDiff = yNew - piece->xyzPos.yRank;
535 zDiff = zNew - piece->xyzPos.zLevel;
536
537 if ((xDiff && yDiff && (ABS(xDiff) != ABS(yDiff))) ||
538 (xDiff && zDiff && (ABS(xDiff) != ABS(zDiff))) ||
539 (yDiff && zDiff && (ABS(yDiff) != ABS(zDiff))))
540 {
541 n3DcErr = E3DcSIMPLE;
542 return False;
543 }
544
545 /*
546 * At this stage, we have determined that, given an empty board,
547 * the move is legal. Now take other pieces into account.
548 */
549 pDestSquare = TraverseDir(piece, xDiff, yDiff, zDiff,
550 MAX(ABS(xDiff), MAX(ABS(yDiff), ABS(zDiff))));
551 return IsMoveLegal(piece, pDestSquare);
552 }
553
554 Local INLINE Boolean
BishopMayMove(Piece * piece,const File xNew,const Rank yNew,const Level zNew)555 BishopMayMove(Piece *piece,
556 const File xNew, const Rank yNew, const Level zNew)
557 {
558 File xDiff;
559 Rank yDiff;
560 Level zDiff;
561 Piece *pDestSquare;
562
563 xDiff = xNew - piece->xyzPos.xFile;
564 yDiff = yNew - piece->xyzPos.yRank;
565 zDiff = zNew - piece->xyzPos.zLevel;
566
567 if (!DIAG3D(xDiff, yDiff, zDiff))
568 {
569 n3DcErr = E3DcSIMPLE;
570 return FALSE;
571 }
572
573 /*
574 * At this stage, we have determined that, given an empty board,
575 * the move is legal. Now take other pieces into account.
576 */
577 pDestSquare = TraverseDir(piece, xDiff, yDiff, zDiff,
578 MAX(ABS(xDiff), ABS(yDiff)));
579 return IsMoveLegal(piece, pDestSquare);
580 }
581
582 Local INLINE Boolean
KnightMayMove(Piece * piece,const File xNew,const Rank yNew,const Level zNew)583 KnightMayMove(Piece *piece,
584 const File xNew, const Rank yNew, const Level zNew)
585 {
586 File xDiff;
587 Rank yDiff;
588
589 if (zNew != piece->xyzPos.zLevel)
590 {
591 n3DcErr = E3DcLEVEL;
592 return FALSE; /* Knights may not change level */
593 }
594
595 xDiff = xNew - piece->xyzPos.xFile;
596 yDiff = yNew - piece->xyzPos.yRank;
597
598 xDiff = ABS(xDiff);
599 yDiff = ABS(yDiff);
600
601 if ((xDiff == 0) ||
602 (yDiff == 0) ||
603 ((xDiff + yDiff) != 3))
604 return FALSE;
605
606 return TRUE;
607 }
608
609 Local INLINE Boolean
RookMayMove(Piece * piece,const File xNew,const Rank yNew,const Level zNew)610 RookMayMove(Piece *piece,
611 const File xNew, const Rank yNew, const Level zNew)
612 {
613 File xDiff;
614 Rank yDiff;
615 Level zDiff;
616 Piece *pDestSquare;
617
618 xDiff = xNew - piece->xyzPos.xFile;
619 yDiff = yNew - piece->xyzPos.yRank;
620 zDiff = zNew - piece->xyzPos.zLevel;
621
622 if (!HORZ3D(xDiff, yDiff, zDiff))
623 {
624 n3DcErr = E3DcSIMPLE;
625 return FALSE;
626 }
627
628 /*
629 * At this stage, we have determined that, given an empty board,
630 * the move is legal. Now take other pieces into account.
631 */
632 pDestSquare = TraverseDir(piece, xDiff, yDiff, zDiff,
633 MAX(ABS(xDiff), MAX(ABS(yDiff), ABS(zDiff))));
634 return IsMoveLegal(piece, pDestSquare);
635 }
636
637 Local INLINE Boolean
PrinceMayMove(Piece * piece,const File xNew,const Rank yNew,const Level zNew)638 PrinceMayMove(Piece *piece,
639 const File xNew, const Rank yNew, const Level zNew)
640 {
641 File xDiff;
642 Rank yDiff;
643
644 if (zNew != piece->xyzPos.zLevel)
645 {
646 n3DcErr = E3DcLEVEL;
647 return FALSE; /* Princes may not change level */
648 }
649
650 xDiff = xNew - piece->xyzPos.xFile;
651 yDiff = yNew - piece->xyzPos.yRank;
652
653 xDiff = ABS(xDiff);
654 yDiff = ABS(yDiff);
655
656 if (xDiff > 1 || yDiff > 1) /* Not allowed move more than 1 */
657 {
658 n3DcErr = E3DcDIST;
659 return FALSE;
660 }
661
662 return TRUE;
663 }
664
665 Local INLINE Boolean
PrincessMayMove(Piece * piece,const File xNew,const Rank yNew,const Level zNew)666 PrincessMayMove(Piece *piece,
667 const File xNew, const Rank yNew, const Level zNew)
668 {
669 File xDiff;
670 Rank yDiff;
671 Piece * pDestSquare;
672
673 if (zNew != piece->xyzPos.zLevel)
674 {
675 n3DcErr = E3DcLEVEL;
676 return FALSE; /* Princesses may not change level */
677 }
678
679 xDiff = xNew - piece->xyzPos.xFile;
680 yDiff = yNew - piece->xyzPos.yRank;
681
682 if (xDiff && yDiff && (ABS(xDiff) != ABS(yDiff)))
683 {
684 n3DcErr = E3DcSIMPLE;
685 return FALSE;
686 }
687
688 /*
689 * At this stage, we have determined that, given an empty board,
690 * the move is legal. Now take other pieces into account.
691 */
692 pDestSquare = TraverseDir(piece, xDiff, yDiff, 0,
693 MAX(ABS(xDiff), ABS(yDiff)));
694 return IsMoveLegal(piece, pDestSquare);
695 }
696
697 Local INLINE Boolean
AbbeyMayMove(Piece * piece,const File xNew,const Rank yNew,const Level zNew)698 AbbeyMayMove(Piece *piece,
699 const File xNew, const Rank yNew, const Level zNew)
700 {
701 File xDiff;
702 Rank yDiff;
703 Piece *pDestSquare;
704
705 if (zNew != piece->xyzPos.zLevel)
706 {
707 n3DcErr = E3DcLEVEL;
708 return FALSE; /* Abbies may not change level */
709 }
710
711 xDiff = xNew - piece->xyzPos.xFile;
712 yDiff = yNew - piece->xyzPos.yRank;
713
714 if (!DIAG(xDiff, yDiff))
715 {
716 n3DcErr = E3DcSIMPLE;
717 return FALSE;
718 }
719
720 /*
721 * At this stage, we have determined that, given an empty board,
722 * the move is legal. Now take other pieces into account.
723 */
724 pDestSquare = TraverseDir(piece, xDiff, yDiff, 0,
725 MAX(ABS(xDiff), ABS(yDiff)));
726 return IsMoveLegal(piece, pDestSquare);
727 }
728
729 Local INLINE Boolean
CannonMayMove(Piece * piece,const File xNew,const Rank yNew,const Level zNew)730 CannonMayMove(Piece *piece,
731 const File xNew, const Rank yNew, const Level zNew)
732 {
733 File xDiff;
734 Rank yDiff;
735 Level zDiff;
736
737 xDiff = xNew - piece->xyzPos.xFile;
738 yDiff = yNew - piece->xyzPos.yRank;
739 zDiff = zNew - piece->xyzPos.zLevel;
740
741 xDiff = ABS(xDiff);
742 yDiff = ABS(yDiff);
743 zDiff = ABS(zDiff);
744
745 if (((xDiff + yDiff + zDiff) != 6) ||
746 ((xDiff != 3) && (yDiff != 3)) ||
747 ((xDiff != 2) && (yDiff != 2) && (zDiff != 2)))
748 {
749 n3DcErr = E3DcSIMPLE;
750 return FALSE;
751 }
752
753 return TRUE;
754 }
755
756 Local INLINE Boolean
GalleyMayMove(Piece * piece,const File xNew,const Rank yNew,const Level zNew)757 GalleyMayMove(Piece *piece,
758 const File xNew, const Rank yNew, const Level zNew)
759 {
760 File xDiff;
761 Rank yDiff;
762 Piece *pDestSquare;
763
764 if (zNew != piece->xyzPos.zLevel)
765 {
766 n3DcErr = E3DcLEVEL;
767 return FALSE; /* Gallies may not change level */
768 }
769
770 xDiff = xNew - piece->xyzPos.xFile;
771 yDiff = yNew - piece->xyzPos.yRank;
772
773 if (!HORZ(xDiff, yDiff))
774 {
775 n3DcErr = E3DcSIMPLE;
776 return FALSE;
777 }
778
779 /*
780 * At this stage, we have determined that, given an empty board,
781 * the move is legal. Now take other pieces into account.
782 */
783 pDestSquare = TraverseDir(piece, xDiff, yDiff, 0,
784 MAX(ABS(xDiff), ABS(yDiff)));
785 return IsMoveLegal(piece, pDestSquare);
786 }
787
788 Local INLINE Boolean
PawnMayMove(Piece * piece,const File xNew,const Rank yNew,const Level zNew)789 PawnMayMove(Piece *piece,
790 const File xNew, const Rank yNew, const Level zNew)
791 {
792 File xDiff;
793 Rank yDiff, yInc;
794
795 if (zNew != piece->xyzPos.zLevel)
796 {
797 n3DcErr = E3DcLEVEL;
798 return FALSE; /* Pawns may not change level */
799 }
800
801 xDiff = xNew - piece->xyzPos.xFile;
802 yInc = yDiff = yNew - piece->xyzPos.yRank;
803
804 xDiff = ABS(xDiff);
805 yDiff = ABS(yDiff);
806
807 /*
808 * Pawns must move at least 1 forward
809 */
810 if ((yDiff == 0) ||
811 ((yInc < 0) && (piece->bwSide == WHITE)) ||
812 ((yInc > 0) && (piece->bwSide == BLACK))) /* Moving backwards */
813 {
814 n3DcErr = E3DcSIMPLE;
815 return FALSE;
816 }
817
818 /* Check the definitely-illegal moves first.. */
819 if (xDiff > 1 ||
820 (xDiff == 1 && yDiff != 1))
821 {
822 n3DcErr = E3DcSIMPLE;
823 return FALSE;
824 }
825
826 /*
827 * It is difficult to cater for 'en passant' in the middle of a
828 * conditional. So, against all convention laid out in other
829 * rules functions, I am checking a move and returning true if it
830 * is valid, rather than returning FALSE if it is invalid.
831 */
832 #if 0
833 /*
834 * TODO:
835 * Only allow en passant taking of pawns that moved two spaces
836 * forward in one go (in the previous move only?)
837 * Each piece must have an identifier; either its memory location
838 * or its offset into the Muster. That way this can be used as the
839 * 4th line of this conditional.
840 */
841 ( StackPeek(MoveStack, 1)->nId == Board[zNew][yNew - yInc][xNew]->nId &&
842 !(StackPeek(MoveStack, 1)->nHadMoved) &&
843 Board[zNew][yNew - yInc][xNew]->bHasMoved /* Moved only once */
844 )
845 #endif /* 0 */
846 if (xDiff == 1 && yDiff == 1 && !Board[zNew][yNew][xNew])
847 { /* En passant? */
848 if (Board[zNew][yNew - yInc][xNew] && /* 'Takable' piece */
849 Board[zNew][yNew - yInc][xNew]->nName == pawn && /* Is pawn */
850 Board[zNew][yNew - yInc][xNew]->bwSide != piece->bwSide && /* Is enemy */
851 1) /* Dummy line to reduce no. of changes */
852 {
853 return EnPASSANT;
854 }
855 else
856 {
857 n3DcErr = E3DcSIMPLE;
858 return FALSE;
859 }
860 }
861
862 /*
863 * Pawns can not move forward under these conditions:
864 * They move more than 2
865 * They move more than 1 and they have already moved
866 * They attempt to take any piece (catered for in next conditional)
867 */
868 if (yDiff > 2 || /* Move too far */
869 (piece->bHasMoved && yDiff == 2)) /* Move too far */
870 {
871 n3DcErr = E3DcDIST;
872 return FALSE;
873 }
874
875 /*
876 * Pawns may not take anything under these conditions:
877 * They do not move diagonally forward one space
878 * The victim is an ally
879 */
880 if (Board[zNew][yNew][xNew] && /* Taking something */
881 (!(xDiff == 1 && yDiff == 1) || /* Not moving diagonally */
882 Board[zNew][yNew][xNew]->bwSide == piece->bwSide))
883 {
884 n3DcErr = E3DcSIMPLE;
885 return FALSE;
886 }
887
888 /* Check for possible promotion */
889 if ((yNew == FILES-1 && piece->bwSide == WHITE) ||
890 (yNew == 0 && piece->bwSide == BLACK))
891 return PROMOTE;
892
893 return TRUE;
894 }
895