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