View Javadoc

1   /**********************************************
2    * Copyright (C) 2009 Lukas Laag
3    * This file is part of lib-gwt-svg-chess.
4    * 
5    * libgwtsvg-chess is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    * 
10   * libgwtsvg-chess is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   * 
15   * You should have received a copy of the GNU General Public License
16   * along with libgwtsvg-chess.  If not, see http://www.gnu.org/licenses/
17   **********************************************/
18  package org.vectomatic.svg.chess;
19  
20  import java.util.Collections;
21  import java.util.HashMap;
22  import java.util.HashSet;
23  import java.util.Map;
24  import java.util.Set;
25  
26  import org.vectomatic.dom.svg.OMSVGAnimatedString;
27  import org.vectomatic.dom.svg.OMSVGDocument;
28  import org.vectomatic.dom.svg.OMSVGGElement;
29  import org.vectomatic.dom.svg.OMSVGMatrix;
30  import org.vectomatic.dom.svg.OMSVGPoint;
31  import org.vectomatic.dom.svg.OMSVGRectElement;
32  import org.vectomatic.dom.svg.OMSVGSVGElement;
33  import org.vectomatic.dom.svg.OMSVGUseElement;
34  
35  import com.alonsoruibal.chess.Board;
36  import com.alonsoruibal.chess.Move;
37  import com.alonsoruibal.chess.bitboard.BitboardUtils;
38  import com.alonsoruibal.chess.movegen.LegalMoveGenerator;
39  import com.alonsoruibal.chess.movegen.MoveGenerator;
40  import com.google.gwt.core.client.GWT;
41  import com.google.gwt.core.client.Scheduler;
42  import com.google.gwt.dom.client.Style.Cursor;
43  import com.google.gwt.event.dom.client.MouseDownEvent;
44  import com.google.gwt.event.dom.client.MouseDownHandler;
45  import com.google.gwt.event.dom.client.MouseEvent;
46  import com.google.gwt.event.dom.client.MouseMoveEvent;
47  import com.google.gwt.event.dom.client.MouseMoveHandler;
48  import com.google.gwt.event.dom.client.MouseUpEvent;
49  import com.google.gwt.event.dom.client.MouseUpHandler;
50  import com.google.gwt.user.client.Command;
51  
52  /**
53   * Class to update the SVG chess board
54   * Representations used for the chessboard:
55   * <dl>
56   * <dt>index</dt><dd>int 0 ... 63</dd>
57   * <dt>algebraic</dt><dd>string: a1 ... h8</dd>
58   * <dt>square</dt><dd>long: (1 bit per square)</dd>
59   * <dt>coords</dt><dd>0 &lt;= x &lt;= 7 ; 0 &lt;= y &lt= 7</dd>
60   * </dl>
61   * @author Lukas Laag (laaglu@gmail.com)
62   */
63  public class ChessBoard implements MouseDownHandler, MouseUpHandler, MouseMoveHandler {
64  
65  	private ChessCss css;
66  	private OMSVGSVGElement svgElt;
67  	private OMSVGGElement boardElt;
68  	private OMSVGDocument boardDoc;
69  	private OMSVGUseElement targetPiece;
70  	private int sqWidth;
71  	private int sqHeight;
72  
73  	private Board board;
74  	private MoveGenerator legalMoveGenerator;
75  	private enum BoardMode {
76  		SRC_MODE,
77  		DEST_MODE
78  	};
79  	
80  	/**
81  	 * The current move number
82  	 */
83  	private int moveNumber;
84  	/**
85  	 * Returns the legal destination index
86  	 * from a source index;
87  	 */
88  	private Map<Integer, Set<Integer>> srcToDestIndex;
89  	/**
90  	 * Source index of the current move
91  	 */
92  	private int srcIndex;
93  	/**
94  	 * Destination index of the current move
95  	 */
96  	private int destIndex;
97  	/**
98  	 * Coordinates of the mousedown origin point
99  	 */
100 	private OMSVGPoint mouseDownCoords;
101 	/**
102 	 * Current UI mode: select either source or dest index
103 	 */
104 	private BoardMode mode;
105 	/**
106 	 * Maps algebraic coordinates to board squares
107 	 */
108 	private Map<String, OMSVGRectElement> algebraicToRects;
109 	/**
110 	 * Maps algebraic coordinates to chess pieces
111 	 */
112 	private Map<String, OMSVGUseElement> algebraicToPieces;
113 
114 	private Main main;
115 	
116 	public ChessBoard(Board board, OMSVGSVGElement svgElt, Main main) {
117 		this.board = board;
118 		this.svgElt = svgElt;
119 		this.main = main;
120 		this.boardDoc = (OMSVGDocument) svgElt.getOwnerDocument();
121 		this.boardElt = (OMSVGGElement) boardDoc.getElementById("board");
122 		this.css = Resources.INSTANCE.getCss();
123 		this.srcToDestIndex = new HashMap<Integer, Set<Integer>>();
124 		this.mode = BoardMode.SRC_MODE;
125 		this.srcIndex = -1;
126 		this.destIndex = -1;
127 		this.algebraicToRects = new HashMap<String, OMSVGRectElement>();
128 		for (int i = 0; i < 8; i++) {
129 			for (int j = 0; j < 8; j++) {
130 				int index = j + 8 * i;
131 				
132 				String squareId = BitboardUtils.index2Algebraic(index);
133 				OMSVGRectElement squareElt = (OMSVGRectElement) boardDoc.getElementById(squareId);
134 				algebraicToRects.put(squareId, squareElt);
135 			}
136 		}
137 		OMSVGRectElement sqElement = algebraicToRects.get("a1");
138 		this.sqWidth = (int)sqElement.getWidth().getBaseVal().getValue();
139 		this.sqHeight = (int)sqElement.getHeight().getBaseVal().getValue();
140 		this.algebraicToPieces = new HashMap<String, OMSVGUseElement>(); 
141 
142 		// Legal moves logic
143 		legalMoveGenerator = new LegalMoveGenerator();
144 		moveNumber = -1;
145 		update(false);
146 		
147 		// Wire events
148 		boardElt.addMouseMoveHandler(this);
149 		boardElt.addMouseUpHandler(this);
150 	}
151 	
152 	/**
153 	 * Adds a new piece to the chessboard. Pieces are represented
154 	 * by svg &lt;use&gt; elements
155 	 * @param piece
156 	 * The piece to add
157 	 * @param algebraic
158 	 * The position
159 	 */
160 	public void addPiece(char piece, String algebraic) {
161 		if (piece != '.') {
162 			OMSVGRectElement squareElt = (OMSVGRectElement) boardDoc.getElementById(algebraic);
163 			OMSVGUseElement useElt = boardDoc.createSVGUseElement();
164 			useElt.getX().getBaseVal().setValue(squareElt.getX().getBaseVal().getValue());
165 			useElt.getY().getBaseVal().setValue(squareElt.getY().getBaseVal().getValue());
166 			useElt.getWidth().getBaseVal().setValue(sqWidth);
167 			useElt.getHeight().getBaseVal().setValue(sqHeight);
168 			useElt.getHref().setBaseVal("#" + Character.toString(piece));
169 			useElt.getStyle().setCursor(Cursor.MOVE);
170 			useElt.addMouseDownHandler(this);
171 			useElt.addMouseUpHandler(this);
172 			boardElt.appendChild(useElt);
173 			algebraicToPieces.put(algebraic, useElt);
174 		}
175 	}
176 	
177 	/**
178 	 * Removes a piece from the chessboard at the specified position
179 	 * @param algebraic
180 	 * The position
181 	 */
182 	public void removePiece(String algebraic) {
183 		OMSVGUseElement useElt = algebraicToPieces.remove(algebraic);
184 		if (useElt != null) {
185 			boardElt.removeChild(useElt);
186 		}
187 	}
188 	
189 	/**
190 	 * Returns the piece at the specified position
191 	 * @param algebraic
192 	 * The position
193 	 * @return
194 	 */
195 	public char getPiece(String algebraic) {
196 		OMSVGUseElement useElt = algebraicToPieces.get(algebraic);
197 		if (useElt != null) {
198 			OMSVGAnimatedString href = useElt.getHref();
199 			if (href != null) {
200 				String baseVal = href.getBaseVal();
201 				if (baseVal != null) {
202 					return baseVal.charAt(1);
203 				}
204 			}
205 		}
206 		return '.';
207 	}
208 	
209 	/**
210 	 * Update the chessboard
211 	 * @param force
212 	 * Force the recomputation of possible moves
213 	 */
214 	public void update(boolean force) {
215 		// If the move number has changed, update the possible
216 		// legal moves
217 		if (force || board.getMoveNumber() != moveNumber) {
218 			srcToDestIndex.clear();
219 			moveNumber = board.getMoveNumber();
220 			int[] moves = new int[64];
221 			int moveCount = legalMoveGenerator.generateMoves(board, moves, 0);
222 			for (int i = 0; i < moveCount; i++) {
223 				int srcIndex = Move.getFromIndex(moves[i]);
224 				Set<Integer> destIndices = srcToDestIndex.get(srcIndex);
225 				if (destIndices == null) {
226 					destIndices = new HashSet<Integer>();
227 					srcToDestIndex.put(srcIndex, destIndices);
228 				}
229 				destIndices.add(Move.getToIndex(moves[i]));
230 			}
231 		}
232 		
233 		Set<Integer> destIndices = srcToDestIndex.containsKey(srcIndex) ? srcToDestIndex.get(srcIndex) : Collections.<Integer>emptySet();
234 		for (int i = 0; i < 8; i++) {
235 			for (int j = 0; j < 8; j++) {
236 				int index = j + 8 * i;
237 				
238 				String squareId = BitboardUtils.index2Algebraic(index);
239 				OMSVGRectElement squareElt = algebraicToRects.get(squareId);
240 				
241 				// Change the colors of the squares to highlight possible moves
242 				String className = ((j + i ) % 2) == 0 ? css.whiteSquare() : css.blackSquare();
243 				if (destIndices.contains(index)) {
244 					className = css.blueSquare();
245 				}
246 				if (mode == BoardMode.DEST_MODE && index == destIndex) {
247 					className = destIndices.contains(index) ? css.greenSquare() : css.redSquare();
248 				}
249 				if (index == srcIndex) {
250 					className = css.yellowSquare();
251 				}
252 				if (!className.equals(squareElt.getClassName().getBaseVal())) {
253 					squareElt.setClassNameBaseVal(className);
254 					//GWT.log("Setting: " + className, null);
255 				}
256 
257 				// Update the piece on this square, if any
258 				char piece = board.getPieceAt(BitboardUtils.index2Square((byte)index));
259 				if (getPiece(squareId) != piece) {
260 					removePiece(squareId);
261 					addPiece(piece, squareId);
262 				}
263 			}
264 		}
265 	}
266 
267 	@Override
268 	public void onMouseDown(MouseDownEvent event) {
269 //		GWT.log("onMouseDown(" + toString(event) + "))", null);
270 		onMouseDown_(event);
271 		event.stopPropagation();
272 		event.preventDefault();
273 	}
274 	
275 	private void onMouseDown_(MouseEvent<?> event) {
276 		String algebraic = getAlgebraic(event);
277 		if (targetPiece == null) {
278 			targetPiece = algebraicToPieces.get(algebraic);
279 			if (targetPiece != null) {
280 				this.destIndex = BitboardUtils.algebraic2Index(algebraic);
281 				mode = BoardMode.DEST_MODE;
282 				mouseDownCoords = getLocalCoordinates(event);
283 				update(false);
284 			}
285 		} else {
286 			int index = algebraic != null ? BitboardUtils.algebraic2Index(algebraic) : -1;
287 			if (index == srcIndex) {
288 				targetPiece = null;
289 			}
290 		}
291 		event.stopPropagation();
292 		event.preventDefault();
293 	}
294 
295 	@Override
296 	public void onMouseUp(MouseUpEvent event) {
297 //		GWT.log("onMouseUp(" + toString(event) + "))", null);
298 
299 		if (targetPiece != null) {
300 			mode = BoardMode.SRC_MODE;
301 			Set<Integer> destIndices = srcToDestIndex.containsKey(srcIndex) ? srcToDestIndex.get(srcIndex) : Collections.<Integer>emptySet();
302 			if (destIndices.contains(destIndex)) {
303 				final int move = Move.getFromString(board, BitboardUtils.index2Algebraic(srcIndex) + BitboardUtils.index2Algebraic(destIndex));
304 				board.doMove(move);
305 				GWT.log("newItem(" + board.getMoveNumber() +  ")", null);
306 				main.addMove();
307 				Scheduler.get().scheduleDeferred(new Command() {
308 					@Override
309 					public void execute() {
310 						main.nextMove();
311 					}
312 					
313 				});
314 			} else {
315 				targetPiece.getX().getBaseVal().setValue(getX(srcIndex));
316 				targetPiece.getY().getBaseVal().setValue(getY(srcIndex));
317 			}
318 			targetPiece = null;
319 			update(false);
320 		} else {
321 			onMouseDown_(event);
322 		}
323 		event.stopPropagation();
324 		event.preventDefault();
325 	}
326 
327 	@Override
328 	public void onMouseMove(MouseMoveEvent event) {
329 		String algebraic = getAlgebraic(event);
330 		//GWT.log("onMouseMove(" + algebraic + "))", null);
331 		int index = algebraic != null ? BitboardUtils.algebraic2Index(algebraic) : -1;
332 		if (mode == BoardMode.SRC_MODE) {
333 			if (srcIndex != index) {
334 				srcIndex = index;
335 				update(false);
336 			}
337 		} else {
338 			// Compute the delta from the mousedown point.
339 			OMSVGPoint p = getLocalCoordinates(event);
340 			targetPiece.getX().getBaseVal().setValue(getX(srcIndex) + p.getX() - mouseDownCoords.getX());
341 			targetPiece.getY().getBaseVal().setValue(getY(srcIndex) + p.getY() - mouseDownCoords.getY());
342 			if (destIndex != index) {
343 				destIndex = index;
344 				update(false);
345 			}
346 		}
347 		event.stopPropagation();
348 		event.preventDefault();
349 	}
350 	
351 	public OMSVGPoint getLocalCoordinates(MouseEvent<?> e) {
352 		OMSVGPoint p = svgElt.createSVGPoint(e.getClientX(), e.getClientY());
353 		OMSVGMatrix m = boardElt.getScreenCTM().inverse();
354 		return p.matrixTransform(m);
355 	}
356 
357 	public int getX(int index) {
358 		return sqWidth * (7 - (index % 8));
359 	}
360 	public int getY(int index) {
361 		return sqHeight * (7 - (index / 8));
362 	}
363 
364 	/**
365 	 * Returns the algebraic corresponding to a mouse event, or null if
366 	 * there is no square
367 	 * @param event
368 	 * The mouse event
369 	 * @return
370 	 * The algebraic corresponding to a mouse event
371 	 */
372 	public String getAlgebraic(MouseEvent<?> event) {
373 
374 		OMSVGPoint p = getLocalCoordinates(event);
375 		int x = (int)(p.getX() / sqWidth);
376 		int y = (int)(p.getY() / sqHeight);
377 		if (x >= 0 && x <= 7 && y >= 0 && y <= 7) {
378 			char algebraic[] = new char[2];
379 			algebraic[0] = (char)('a' + x);
380 			algebraic[1] = (char)('8' - y);
381 			return new String(algebraic);
382 		}
383 		return null;
384 	}
385 //
386 //	public String toString(MouseEvent e) {
387 //		String width = svgElt.getStyle().getWidth();
388 //		float r = (Integer.parseInt(width.substring(0, width.length() - 2 /* 2 == "px".length() */))) / svgElt.getBBox().getWidth();
389 //		StringBuffer buffer = new StringBuffer();
390 //		buffer.append(" e=");
391 //		buffer.append(e.getRelativeElement());
392 //		buffer.append(" t=");
393 //		buffer.append(e.getNativeEvent().getEventTarget());
394 //		buffer.append(" cet=");
395 //		buffer.append(e.getNativeEvent().getCurrentEventTarget());
396 //		buffer.append(" cx=");
397 //		buffer.append(e.getClientX());
398 //		buffer.append(" cy=");
399 //		buffer.append(e.getClientY());
400 //		buffer.append(" scx=");
401 //		buffer.append((int)((e.getClientX())/ r));
402 //		buffer.append(" scy=");
403 //		buffer.append((int)((e.getClientY()) / r));
404 //		buffer.append(" rx=");
405 //		buffer.append(e.getRelativeX(svgElt.getElement()));
406 //		buffer.append(" ry=");
407 //		buffer.append(e.getRelativeY(svgElt.getElement()));
408 //		buffer.append(" x=");
409 //		buffer.append(e.getX());
410 //		buffer.append(" y=");
411 //		buffer.append(e.getY());
412 //		buffer.append(" sx=");
413 //		buffer.append(e.getScreenX());
414 //		buffer.append(" sy=");
415 //		buffer.append(e.getScreenY());
416 //		return buffer.toString();
417 //	}
418 //	private void log(String s) {
419 //		Text t = (Text) DOM.getElementById("title").getFirstChild();
420 //		t.setData(s);
421 //	}
422 }