View Javadoc
1   /**************************************************************************
2    Copyright 2005 Webstersmalley
3   
4    Licensed under the Apache License, Version 2.0 (the "License");
5    you may not use this file except in compliance with the License.
6    You may obtain a copy of the License at
7   
8    http://www.apache.org/licenses/LICENSE-2.0
9   
10   Unless required by applicable law or agreed to in writing, software
11   distributed under the License is distributed on an "AS IS" BASIS,
12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   See the License for the specific language governing permissions and
14   limitations under the License.
15   *************************************************************************/
16  /*
17   * Created on 23-Aug-2005
18   */
19  package com.webstersmalley.chessweb.model;
20  
21  import java.util.Collection;
22  import java.util.HashMap;
23  import java.util.HashSet;
24  import java.util.Iterator;
25  import java.util.Map;
26  import java.util.Set;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  
31  import com.webstersmalley.chessweb.game.PlayableGame;
32  
33  /***
34   * Represents the current state of the chessboard.
35   * 
36   * @author Matthew Smalley
37   */
38  public class Board {
39      
40      /*** Logger for this class and subclasses. */
41      private final Log logger = LogFactory.getLog(getClass());
42  
43      /*** Width of the board. */
44      private int width;
45  
46      /*** Height of the board. */
47      private int height;
48  
49      /*** Squares map. String and Squares pairs. */
50      private Map squares = new HashMap();
51  
52      /*** The moves history. */
53      private MoveHistory moveHistory = new MoveHistory();
54  
55      /*** Whose turn is it? */
56      private Team nextTurn;
57  
58      /*** Whether WHITE is checked. */
59      private boolean whiteChecked;
60      
61      /*** Whether BLACK is checked. */
62      private boolean blackChecked;
63      
64      /***
65       * Sets up the Square objects.
66       */
67      protected final void setup() {
68          for (int i = 0; i < width; i++) {
69              for (int j = 0; j < height; j++) {
70                  Position position = new Position(i, j);
71                  squares.put(position.toString(), new Square(position));
72              }
73          }
74      }
75  
76      /***
77       * Accessor for the height variable.
78       * 
79       * @return the height
80       */
81      public final int getHeight() {
82          return height;
83      }
84  
85      /***
86       * Mutator for the height variable.
87       * 
88       * @param height
89       *            the new height.
90       */
91      public final void setHeight(final int height) {
92          this.height = height;
93      }
94  
95      /***
96       * Accessor for the width variable.
97       * 
98       * @return the width
99       */
100     public final int getWidth() {
101         return width;
102     }
103 
104     /***
105      * Mutator for the width variable.
106      * 
107      * @param width
108      *            the new width.
109      */
110     public final void setWidth(final int width) {
111         this.width = width;
112     }
113 
114     /***
115      * Returns the square at the given position.
116      * 
117      * @param pos
118      *            the position
119      * @return the square
120      */
121     private Square getSquareAt(final Position pos) {
122         logger.debug("Looking up the Square at: " + pos);
123         logger.debug("It's: " + squares.get(pos.toString()));
124         return (Square) squares.get(pos.toString());
125     }
126 
127     /***
128      * Return the piece at a given position.
129      * 
130      * @param pos
131      *            the Position.
132      * @return the piece.
133      */
134     public final Piece getAt(final Position pos) {
135         return getSquareAt(pos).getPiece();
136     }
137 
138     /***
139      * Return the piece at a given location.
140      * 
141      * @param file
142      *            the file coordinate
143      * @param rank
144      *            the rank coordinate
145      * @return the piece.
146      */
147     public final Piece getAt(final String file, final int rank) {
148         Position pos = new Position();
149         pos.setFile(file);
150         pos.setRank(rank);
151         return getAt(pos);
152     }
153 
154     /***
155      * Perform the actual move. At this point, it is assumed all validation has
156      * been done (and passed!)
157      * 
158      * @param move
159      *            the move.
160      */
161     private void doMove(final Move move) {
162         place(move.getDestination(), getAt(move.getSource()));
163         place(move.getSource(), null);
164         moveHistory.addMove(move);
165     }
166 
167     /***
168      * Changes whose turn it is.
169      */
170     private void advanceMove() {
171         if (nextTurn == Team.WHITE) {
172             nextTurn = Team.BLACK;
173         } else {
174             nextTurn = Team.WHITE;
175         }
176     }
177 
178     /***
179      * Move a piece from one position to another.
180      * 
181      * @param move
182      *            the move
183      * @throws InvalidMoveException
184      *             if the move is invalid
185      */
186     public final void movePiece(final Move move) throws InvalidMoveException {
187         logger.debug("moving: " + move);
188         Piece piece = getAt(move.getSource());
189         if (piece == null) {
190             throw new InvalidMoveException(
191                     "Error: there is no piece at position: " 
192                     + move.getSource());
193         }
194         if (piece.getTeam() != nextTurn) {
195             throw new InvalidMoveException("Error: it is " + nextTurn
196                 + "'s turn");
197         }
198 
199         piece.validateMove(move, this);
200 
201         // Now that we know we can move it...
202         if (move.isCastling()) {
203             if (piece.equals(Piece.WHITE_KING)) {
204                 if (move.getDestination().getAlgebraicNotation().equals("c1")) {
205                     doMove(move);
206                     doMove(new Move(new Position("a1"), new Position("d1")));
207                 } else {
208                     doMove(move);
209                     doMove(new Move(new Position("h1"), new Position("f1")));
210                 }
211             } else {
212                 if (move.getDestination().getAlgebraicNotation().equals("c8")) {
213                     doMove(move);
214                     doMove(new Move(new Position("a8"), new Position("d8")));
215                 } else {
216                     doMove(move);
217                     doMove(new Move(new Position("h8"), new Position("f8")));
218                 }
219             }
220         } else {
221             doMove(move);
222         }
223         advanceMove();
224         findChecked();
225     }
226     
227     /***
228      * Return all squares containing a given team's pieces.
229      * @param team the team
230      * @return Collection the squares
231      */
232     private Collection getTeamSquares(final Team team) {
233         Set set = new HashSet();
234         for (Iterator it = squares.values().iterator(); it.hasNext();) {
235             Square square = (Square) it.next();
236             if (square.hasPiece() && square.getPiece().getTeam() == team) {
237                 set.add(square);
238             }
239         }
240         return set;
241     }
242     
243     /***
244      * Find the square of the first occurence of 
245      * piece in the board state.
246      * @param piece the piece
247      * @return the square (or null if it is not found)
248      */
249     private Square findSquare(final Piece piece) {
250         for (Iterator it = squares.values().iterator(); it.hasNext();) {
251             Square square = (Square) it.next();
252             if (square.getPiece() == piece) {
253                 return square;
254             }
255         }
256         return null;
257     }
258     
259     /***
260      * Find out if a given king is checked.
261      * @param ourKing the king to check.
262      * @param enemyTeam the enemy to check against.
263      * @return whether the king is checked
264      */
265     private boolean findChecked(final Piece ourKing, final Team enemyTeam) {
266         Square kingSquare = findSquare(ourKing);
267         Collection enemySquares = getTeamSquares(enemyTeam);
268         for (Iterator it = enemySquares.iterator(); it.hasNext();) {
269             Square enemySquare = (Square) it.next();
270             Move move = new Move(enemySquare.getPosition(),
271                     kingSquare.getPosition());
272             try {
273                 enemySquare.getPiece().validateMove(move, this);
274                 return true;
275             } catch (InvalidMoveException e) {
276                 logger.debug("This piece cannot take the king");
277             }
278         }
279         return false;
280     }
281     
282     /***
283      * Find out whether black or white is checked.
284      */
285     private void findChecked() {
286         setWhiteChecked(findChecked(Piece.WHITE_KING, Team.BLACK));
287         setBlackChecked(findChecked(Piece.BLACK_KING, Team.WHITE));
288     }
289 
290     /***
291      * Place a piece at a position. Private method, as this should be accessed
292      * via movePiece instead.
293      * 
294      * @param pos
295      *            the position
296      * @param piece
297      *            the piece
298      */
299     public final void place(final Position pos, final Piece piece) {
300         Square square = getSquareAt(pos);
301         square.setPiece(piece);
302     }
303 
304     /***
305      * Returns the collection of all Squares.
306      * 
307      * @return the collection of all Squares.
308      */
309     public final Collection getSquares() {
310         return squares.values();
311     }
312 
313     /***
314      * Checks whether a given position is on the board.
315      * 
316      * @param position
317      *            the position
318      * @return whether it is on the board or not.
319      */
320     public final boolean isOnBoard(final Position position) {
321         return (position.getRank() > 0 && position.getRank() <= width
322             && position.getFileNumber() > 0 
323             && position.getFileNumber() <= height);
324     }
325 
326     /***
327      * @return Returns the moveHistory.
328      */
329     public final MoveHistory getMoveHistory() {
330         return moveHistory;
331     }
332 
333     /***
334      * @param nextTurn
335      *            The nextTurn to set.
336      */
337     public final void setNextTurn(final Team nextTurn) {
338         this.nextTurn = nextTurn;
339     }
340 
341     /***
342      * @return Returns the nextTurn.
343      */
344     public final Team getNextTurn() {
345         return nextTurn;
346     }
347 
348     /***
349      * @return Returns the blackChecked.
350      */
351     public final boolean isBlackChecked() {
352         return blackChecked;
353     }
354 
355     /***
356      * @param blackChecked The blackChecked to set.
357      */
358     public final void setBlackChecked(final boolean blackChecked) {
359         this.blackChecked = blackChecked;
360     }
361 
362     /***
363      * @return Returns the whiteChecked.
364      */
365     public final boolean isWhiteChecked() {
366         return whiteChecked;
367     }
368 
369     /***
370      * @param whiteChecked The whiteChecked to set.
371      */
372     public final void setWhiteChecked(final boolean whiteChecked) {
373         this.whiteChecked = whiteChecked;
374     }
375     
376     /***
377      * Returns a message indicating if either team is checked.
378      * @return the checked message.
379      */
380     public final String getChecked() {
381         StringBuffer sb = new StringBuffer();
382         if (whiteChecked) {
383             sb.append("White is checked\n");
384         }
385         if (blackChecked) {
386             sb.append("Black is checked\n");
387         }
388         return sb.toString();
389     }
390 }