1 /**
2  * The chess framework library.
3  * More information is available at http://www.jinchess.com/.
4  * Copyright (C) 2002 Alexander Maryanovsky.
5  * All rights reserved.
6  *
7  * The chess framework library is free software; you can redistribute
8  * it and/or modify it under the terms of the GNU Lesser General Public License
9  * as published by the Free Software Foundation; either version 2 of the
10  * License, or (at your option) any later version.
11  *
12  * The chess framework library is distributed in the hope that it will
13  * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with the chess framework library; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  */
21 
22 package free.chess;
23 
24 import free.chess.event.*;
25 import javax.swing.event.ChangeEvent;
26 import javax.swing.event.ChangeListener;
27 import javax.swing.event.EventListenerList;
28 import java.util.StringTokenizer;
29 import free.util.Utilities;
30 
31 
32 /**
33  * Represents a position in one of the chess wild variants. The Position class
34  * itself is generally variant independent, but it does make some assumptions,
35  * such as the board being a 8x8 container of pieces, there being two players
36  * and other things common to most chess variants.
37  * <B>IMPORTANT:</B> This class is not thread safe.
38  */
39 
40 public final class Position{
41 
42 
43 
44   /**
45    * The WildVariant of this Position.
46    */
47 
48   private final WildVariant variant;
49 
50 
51 
52 
53   /**
54    * A matrix to keep references to the pieces.
55    */
56 
57   private final Piece [][] pieces = new Piece[8][8];
58 
59 
60 
61 
62 
63   /**
64    * The Modifier of this Position.
65    */
66 
67   private final Modifier modifier;
68 
69 
70 
71 
72 
73   /**
74    * The Player whose turn it currently is in this Position.
75    */
76 
77   private Player currentPlayer;
78 
79 
80 
81 
82   /**
83    * A FEN representation of the position.
84    */
85 
86   private String positionFEN;
87 
88 
89 
90 
91 
92   /**
93    * Only one ChangeEvent is needed per model instance since the
94    * event's only (read-only) state is the source property.  The source
95    * of events generated here is always "this".
96    */
97 
98   protected transient ChangeEvent changeEvent = null;
99 
100 
101 
102 
103   /**
104    * The listeners waiting for model changes.
105    */
106 
107   protected EventListenerList listenerList = new EventListenerList();
108 
109 
110 
111 
112   /**
113    * Creates a new Position with the regular WildVariant (normal chess).
114    *
115    * @see #init()
116    */
117 
Position()118   public Position(){
119     this(Chess.getInstance());
120   }
121 
122 
123 
124 
125 
126   /**
127    * Creates a new Position with the given WildVariant.
128    */
129 
Position(WildVariant variant)130   public Position(WildVariant variant){
131     this.variant = variant;
132     this.modifier = new Modifier(this);
133     init();
134   }
135 
136 
137 
138 
139   /**
140    * Creates a new Position which is exactly like the given Position.
141    */
142 
Position(Position source)143   public Position(Position source){
144     this.variant = source.variant;
145     this.modifier = new Modifier(this);
146     copyFrom(source);
147   }
148 
149 
150 
151 
152   /**
153    * Returns the WildVariant of this Position.
154    */
155 
getVariant()156   public WildVariant getVariant(){
157     return variant;
158   }
159 
160 
161 
162 
163   /**
164    * Returns the Piece at the given Square.
165    *
166    * @param square The location of the piece to return.
167    */
168 
getPieceAt(Square square)169   public Piece getPieceAt(Square square){
170     return getPieceAt(square.getFile(),square.getRank());
171   }
172 
173 
174 
175 
176 
177   /**
178    * Returns the piece at the square with the given file and rank.
179    */
180 
getPieceAt(int file, int rank)181   public Piece getPieceAt(int file, int rank){
182     return pieces[file][rank];
183   }
184 
185 
186 
187 
188   /**
189    * Returns the piece at the square specified by the given string, as if by
190    * {@link Square#parseSquare(String)}.
191    */
192 
getPieceAt(String square)193   public Piece getPieceAt(String square){
194     return getPieceAt(Square.parseSquare(square));
195   }
196 
197 
198 
199 
200 
201   /**
202    * Puts the given piece at the given square, replacing the piece that was
203    * there before.
204    *
205    * @param piece The piece to put.
206    * @param square The square where to put the piece.
207    */
208 
setPieceAt(Piece piece, Square square)209   public void setPieceAt(Piece piece, Square square){
210     setPieceAtImpl(piece, square);
211     fireStateChanged();
212   }
213 
214 
215 
216 
217 
218   /**
219    * Puts the given piece at the given square, replacing the piece that was
220    * there before.
221    *
222    * @param piece The piece to put.
223    * @param square The square where to put the piece.
224    */
225 
setPieceAt(Piece piece, String square)226   public void setPieceAt(Piece piece, String square){
227     setPieceAt(piece, Square.parseSquare(square));
228   }
229 
230 
231 
232 
233   /**
234    * Returns the player whose turn it is in this position, the "current"
235    * player.
236    */
237 
getCurrentPlayer()238   public Player getCurrentPlayer(){
239     return currentPlayer;
240   }
241 
242 
243 
244 
245   /**
246    * Sets the current player in this position to be the given player.
247    *
248    * @param player The player whose turn it is next.
249    */
250 
setCurrentPlayer(Player player)251   public void setCurrentPlayer(Player player){
252     setCurrentPlayerImpl(player);
253     fireStateChanged();
254   }
255 
256 
257 
258 
259   /**
260    * Sets this Position to represent the position represented by
261    * the given string. The string should represent a position by specifying
262    * 64 characters indicating what occupies (-PNBRQKpnbrqk) each square, in
263    * lexigraphic order (a8, b8, ..., h1). The player to move will be
264    * the player with the white pieces.
265    *
266    * @param pos The string representing the position.
267    *
268    * @throws PositionFormatException if the given string is not in the
269    * expected format.
270    */
271 
setLexigraphic(String pos)272   public void setLexigraphic(String pos) throws PositionFormatException{
273     if (pos.length() < 64)
274       throw new PositionFormatException("Less than 64 letters in the string: " + pos);
275 
276     int i = 0;
277     try{
278       for (int rank = 7; rank >= 0; rank--){
279         for (int file = 0; file < 8; file++){
280           setPieceAtImpl(variant.parsePiece("" + pos.charAt(i++)), Square.getInstance(file, rank));
281         }
282       }
283     } catch (IllegalArgumentException e){
284         throw new PositionFormatException(e);
285       }
286 
287     setCurrentPlayerImpl(Player.WHITE_PLAYER);
288 
289     fireStateChanged();
290   }
291 
292 
293 
294 
295 
296   /**
297    * Returns a string representing the position by 64 characters indicating what
298    * occupies (obtained by calling toShortColorString() on each piece, '-' for
299    * no piece) each square, in lexigraphic order (a8, b8, ..., h1).
300    */
301 
getLexigraphic()302   public String getLexigraphic(){
303     StringBuffer buf = new StringBuffer(64);
304     for (int rank=7; rank>=0; rank--)
305       for (int file=0; file<8; file++){
306         Piece piece = getPieceAt(Square.getInstance(file, rank));
307         if (piece==null)
308           buf.append("-");
309         else
310           buf.append(piece.toShortColorString());
311       }
312 
313     return buf.toString();
314   }
315 
316 
317 
318 
319 
320 
321   /**
322    * Sets this Position to represent the position described by the given string
323    * in FEN format. The FEN format is described at
324    * <A HREF="http://www.very-best.de/pgn-spec.htm#16.1">http://www.very-best.de/pgn-spec.htm#16.1</A>.
325    * The characters describing pieces aren't limited to the chess set (like in
326    * FEN), but are determined by the WildVariant of this position.
327    *
328    * @throws PositionFormatException if the given string is not in the expected
329    * format.
330    */
331 
setFEN(String fen)332   public void setFEN(String fen) throws PositionFormatException{
333     StringTokenizer fenTokenizer = new StringTokenizer(fen, " ");
334     if (fenTokenizer.countTokens() != 6)
335       throw new PositionFormatException("Wrong amount of fields");
336 
337     String pos = fenTokenizer.nextToken();
338     StringTokenizer ranks = new StringTokenizer(pos,"/");
339     if (ranks.countTokens() != 8)
340       throw new PositionFormatException("Wrong amount of ranks");
341 
342     for (int rank = 7; rank >= 0; rank--){
343       String rankString = ranks.nextToken();
344       int file = 0;
345       for (int i = 0; i < rankString.length(); i++){
346         if (file > 7)
347           throw new PositionFormatException("Rank " + rank + " extends beyond the board");
348 
349         char c = rankString.charAt(i);
350         if (Character.isDigit(c)){
351           int emptyFiles = Character.digit(c, 10);
352           while (emptyFiles-- > 0){
353             setPieceAtImpl(null, Square.getInstance(file, rank));
354             file++;
355           }
356         }
357         else{
358           try{
359             setPieceAtImpl(variant.parsePiece(String.valueOf(c)), Square.getInstance(file, rank));
360             file++;
361           } catch (IllegalArgumentException e){
362               throw new PositionFormatException(e);
363             }
364         }
365       }
366       if (file != 8)
367         throw new PositionFormatException("Rank " + rank + " is a few files short");
368     }
369 
370     String colorToMove = fenTokenizer.nextToken();
371     if (colorToMove.length() != 1)
372       throw new PositionFormatException("Wrong amount of characters in active color indicator: " + colorToMove);
373     if (colorToMove.equals("w"))
374       setCurrentPlayerImpl(Player.WHITE_PLAYER);
375     else if (colorToMove.equals("b"))
376       setCurrentPlayerImpl(Player.BLACK_PLAYER);
377     else
378       throw new PositionFormatException("Wrong active color indicator: " + colorToMove);
379 
380     this.positionFEN = fen;
381   }
382 
383 
384 
385 
386 
387   /**
388    * Returns the FEN representation of this Position. May return
389    * <code>null</code> if the current position wasn't set via the setFEN method.
390    * Note that as soon as the position is changed after setFEN was called, this
391    * method will return <code>null</code>.
392    */
393 
getFEN()394   public String getFEN(){
395     return positionFEN;
396   }
397 
398 
399 
400 
401 
402 
403   /**
404    * Sets this Position to the initial position.
405    */
406 
init()407   public final void init(){
408     variant.init(this);
409   }
410 
411 
412 
413 
414   /**
415    * Clears this position of any pieces. The current player is set to the
416    * player with the White pieces.
417    */
418 
clear()419   public void clear(){
420     for (int file=0;file<8;file++)
421       for (int rank=0;rank<8;rank++)
422         setPieceAtImpl(null,Square.getInstance(file,rank));
423     setCurrentPlayerImpl(Player.WHITE_PLAYER);
424 
425     fireStateChanged();
426   }
427 
428 
429 
430 
431   /**
432    * Makes the given Move on this position. This method first fires a MoveEvent
433    * and then a ChangeEvent.
434    *
435    * @param move The move to make.
436    *
437    * @throws IllegalArgumentException if the given Move is incompatible with
438    * the wild variant of this Position.
439    */
440 
makeMove(Move move)441   public void makeMove(Move move){
442     variant.makeMove(move, this, modifier);
443     fireMoveMade(move);
444     fireStateChanged();
445   }
446 
447 
448 
449 
450   /**
451    * Makes this position a copy of the given position by setting it to
452    * the same state. The WildVariants of the Positions must match.
453    *
454    * @param position The position to copy.
455    */
456 
copyFrom(Position position)457   public void copyFrom(Position position){
458     if (!variant.equals(position.variant))
459       throw new IllegalArgumentException("The WildVariants of the positions don't match");
460 
461     for (int file = 0; file < pieces.length; file++){
462       for (int rank = 0; rank < pieces[file].length; rank++){
463         pieces[file][rank] = position.pieces[file][rank];
464       }
465     }
466 
467     setCurrentPlayerImpl(position.getCurrentPlayer());
468 
469     this.positionFEN = position.positionFEN;
470 
471     fireStateChanged();
472   }
473 
474 
475 
476 
477   /**
478    * Puts the given piece at the given square, replacing the piece that was
479    * there before. The difference between this and the setPieceAt(Piece,Square)
480    * method is that this method does not fire a ChangeEvent. It's useful when
481    * you want to do a series of changes and only then fire a ChangeEvent.
482    *
483    * @param piece The piece to put.
484    * @param square The square where to put the piece.
485    *
486    * @see #setPieceAt(Piece, Square);
487    */
488 
setPieceAtImpl(Piece piece, Square square)489   private void setPieceAtImpl(Piece piece, Square square){
490     pieces[square.getFile()][square.getRank()] = piece;
491     positionFEN = null;
492   }
493 
494 
495 
496 
497   /**
498    * Sets the current player in this position to be the given player.
499    * The difference between this and the setCurrentPlayer(Player)
500    * method is that this method does not fire a ChangeEvent. It's useful
501    * when you want to do a series of changes and only then fire a ChangeEvent.
502    *
503    * @param player The player whose turn it is next.
504    */
505 
setCurrentPlayerImpl(Player player)506   private void setCurrentPlayerImpl(Player player){
507     this.currentPlayer = player;
508     positionFEN = null;
509   }
510 
511 
512 
513 
514   /**
515    * Adds a ChangeListener.  The change listeners are run each
516    * time the Position changes.
517    *
518    * @param l the ChangeListener to add
519    * @see #removeChangeListener
520    */
521 
addChangeListener(ChangeListener l)522   public void addChangeListener(ChangeListener l) {
523     listenerList.add(ChangeListener.class, l);
524   }
525 
526 
527 
528 
529   /**
530    * Removes a ChangeListener.
531    *
532    * @param l the ChangeListener to remove
533    * @see #addChangeListener
534    */
535 
removeChangeListener(ChangeListener l)536   public void removeChangeListener(ChangeListener l) {
537     listenerList.remove(ChangeListener.class, l);
538   }
539 
540 
541 
542 
543   /**
544    * Run each ChangeListeners stateChanged() method.
545    */
546 
fireStateChanged()547   protected void fireStateChanged(){
548     Object [] listeners = listenerList.getListenerList();
549     for (int i = listeners.length-2; i>=0; i-=2 ){
550       if (listeners[i] == ChangeListener.class){
551         if (changeEvent == null){
552           changeEvent = new ChangeEvent(this);
553         }
554         ((ChangeListener)listeners[i+1]).stateChanged(changeEvent);
555       }
556     }
557   }
558 
559 
560 
561 
562   /**
563    * Adds a MoveListener.  The change listeners are run each
564    * time a move is made in this position.
565    *
566    * @param l the ChangeListener to add
567    * @see #removeChangeListener
568    */
569 
addMoveListener(MoveListener l)570   public void addMoveListener(MoveListener l) {
571     listenerList.add(MoveListener.class, l);
572   }
573 
574 
575 
576   /**
577    * Removes a MoveListener.
578    *
579    * @param l the ChangeListener to remove
580    * @see #addChangeListener
581    */
582 
removeMoveListener(MoveListener l)583   public void removeMoveListener(MoveListener l) {
584     listenerList.remove(MoveListener.class, l);
585   }
586 
587 
588 
589 
590   /**
591    * Run each MoveListeners moveMade() method.
592    *
593    * @param move The Move that was made.
594    */
595 
fireMoveMade(Move move)596   protected void fireMoveMade(Move move){
597     MoveEvent evt = new MoveEvent(this,move);
598     Object [] listeners = listenerList.getListenerList();
599     for (int i = listeners.length-2; i>=0; i-=2 ){
600       if (listeners[i] == MoveListener.class){
601         ((MoveListener)listeners[i+1]).moveMade(evt);
602       }
603     }
604   }
605 
606 
607 
608 
609   /**
610    * Returns a textual representation of the board.
611    */
612 
toString()613   public String toString(){
614     if (getCurrentPlayer().isWhite())
615       return "White to move in "+getLexigraphic();
616     else
617       return "Black to move in "+getLexigraphic();
618   }
619 
620 
621 
622 
623   /**
624    * Returns true iff the specified <code>Position</code> is the same as this
625    * one.
626    */
627 
equals(Position pos)628   public boolean equals(Position pos){
629     if (!variant.equals(pos.variant))
630       return false;
631 
632     if (!currentPlayer.equals(pos.currentPlayer))
633       return false;
634 
635     for (int file = 0; file < pieces.length; file++)
636       for (int rank = 0; rank < pieces[file].length; rank++)
637         if (!Utilities.areEqual(pieces[file][rank], pos.pieces[file][rank]))
638           return false;
639 
640     return true;
641   }
642 
643 
644 
645 
646   /**
647    * Returns true iff the specified object is a <code>Position</code> and
648    * represents the same position as this one.
649    */
650 
equals(Object obj)651   public boolean equals(Object obj){
652     if (!(obj instanceof Position))
653       return false;
654 
655     return equals((Position)obj);
656   }
657 
658 
659 
660 
661   /**
662    * Returns the hashcode of this position.
663    */
664 
hashCode()665   public int hashCode(){
666     int result = 17;
667     result = 37*result + variant.hashCode();
668     result = 37*result + currentPlayer.hashCode();
669     for (int file = 0; file < pieces.length; file++)
670       for (int rank = 0; rank < pieces[file].length; rank++){
671         Piece piece = pieces[file][rank];
672         int c = (piece == null) ? 0 : piece.hashCode();
673         result = 37*result + c;
674       }
675 
676     return result;
677   }
678 
679 
680 
681 
682   /**
683    * Instances of this class is allowed to make modifications to a Position
684    * without triggering the Position instance to fire any events. It should only
685    * be used by WildVariant implementations for making multi-step atomic changes
686    * which don't trigger firing many events to a Position and the firing an event
687    * indicating the change is done.
688    */
689 
690   public static final class Modifier{
691 
692 
693 
694     /**
695      * The Position we're modifying. Don't eliminate this by making the Modifier
696      * class nonstatic because it causes Jikes to create invalid bytecode.
697      * See http://www-124.ibm.com/pipermail/jikes-dev/2000-November/001585.html
698      */
699 
700     private final Position position;
701 
702 
703 
704     /**
705      * Creates a new PositionModifier with the given Position.
706      */
707 
Modifier(Position position)708     private Modifier(Position position){
709       this.position = position;
710     }
711 
712 
713 
714     /**
715      * Puts the given piece at the given Square.
716      */
717 
setPieceAt(Piece piece, Square square)718     public void setPieceAt(Piece piece, Square square){
719       position.setPieceAtImpl(piece, square);
720     }
721 
722 
723 
724     /**
725      * Sets the current player.
726      */
727 
setCurrentPlayer(Player player)728     public void setCurrentPlayer(Player player){
729       position.setCurrentPlayerImpl(player);
730     }
731 
732 
733   }
734 
735 
736 }
737