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
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
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 }