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 org.vectomatic.dom.svg.OMSVGSVGElement;
21  import org.vectomatic.dom.svg.utils.OMSVGParser;
22  
23  import com.alonsoruibal.chess.Board;
24  import com.alonsoruibal.chess.Config;
25  import com.alonsoruibal.chess.Move;
26  import com.alonsoruibal.chess.search.SearchEngine;
27  import com.alonsoruibal.chess.search.SearchObserver;
28  import com.alonsoruibal.chess.search.SearchParameters;
29  import com.alonsoruibal.chess.search.SearchStatusInfo;
30  import com.google.gwt.core.client.EntryPoint;
31  import com.google.gwt.core.client.GWT;
32  import com.google.gwt.core.client.Scheduler;
33  import com.google.gwt.core.client.Scheduler.ScheduledCommand;
34  import com.google.gwt.dom.client.StyleInjector;
35  import com.google.gwt.event.dom.client.ChangeEvent;
36  import com.google.gwt.event.dom.client.ClickEvent;
37  import com.google.gwt.uibinder.client.UiBinder;
38  import com.google.gwt.uibinder.client.UiFactory;
39  import com.google.gwt.uibinder.client.UiField;
40  import com.google.gwt.uibinder.client.UiHandler;
41  import com.google.gwt.user.client.Element;
42  import com.google.gwt.user.client.Window;
43  import com.google.gwt.user.client.ui.Button;
44  import com.google.gwt.user.client.ui.DecoratedPopupPanel;
45  import com.google.gwt.user.client.ui.DialogBox;
46  import com.google.gwt.user.client.ui.DisclosurePanel;
47  import com.google.gwt.user.client.ui.HTML;
48  import com.google.gwt.user.client.ui.HorizontalPanel;
49  import com.google.gwt.user.client.ui.Image;
50  import com.google.gwt.user.client.ui.Label;
51  import com.google.gwt.user.client.ui.ListBox;
52  import com.google.gwt.user.client.ui.RootLayoutPanel;
53  import com.google.gwt.user.client.ui.SplitLayoutPanel;
54  import com.google.gwt.user.client.ui.TextArea;
55  import com.google.gwt.widgetideas.client.HSliderBar;
56  import com.google.gwt.widgetideas.client.SliderBar;
57  import com.google.gwt.widgetideas.client.SliderBar.LabelFormatter;
58  import com.google.gwt.widgetideas.client.SliderListenerAdapter;
59  
60  /**
61   * Main class. Instantiates the UI and runs the game loop
62   */
63  public class Main implements EntryPoint, SearchObserver {
64  	interface MainBinder extends UiBinder<SplitLayoutPanel, Main> {
65  	}
66  	private static MainBinder mainBinder = GWT.create(MainBinder.class);
67  
68  	@UiField(provided=true)
69  	ChessConstants constants = ChessConstants.INSTANCE;
70  	@UiField(provided=true)
71  	ChessCss style = Resources.INSTANCE.getCss();
72  
73  	@UiField
74  	HTML boardContainer;
75  	@UiField
76  	Button restartButton;
77  	@UiField
78  	Button fenButton;
79  	@UiField
80  	Button undoButton;
81  	@UiField
82  	Button redoButton;
83  
84  	@UiField
85  	DisclosurePanel advancedPanel;
86  	
87  	@UiField
88  	Label modeLabel;
89  	@UiField
90  	Label reflectionLabel;
91  	@UiField
92  	Label fenLabel;
93  	@UiField
94  	Label currentPlayerLabel;
95  	@UiField
96  	Label historyLabel;
97  	
98  	@UiField
99  	ListBox modeListBox;
100 	@UiField
101 	HSliderBar reflectionSlider;
102 	@UiField
103 	TextArea fenArea;
104 	@UiField
105 	TextArea historyArea;
106 	@UiField
107 	Label currentPlayerValueLabel;
108 	@UiField
109 	HTML about;
110 	
111 	DialogBox confirmBox;
112 
113 	/**
114 	 * The Carballo engine
115 	 */
116 	SearchEngine engine;
117 	/**
118 	 * The Carballo board
119 	 */
120 	Board board;
121 	/**
122 	 * The SVG chess board
123 	 */
124 	ChessBoard chessboard;
125 	/**
126 	 * A &lt;div&gt; element to contain the SVG root element
127 	 */
128 	private Element boardDiv;
129 	/**
130 	 * The root SVG element
131 	 */
132 	private OMSVGSVGElement boardElt;
133 	/**
134 	 * Array of preset Carballo move times in ms 
135 	 */
136 	private int[] moveTimes = new int[] {3000, 10000, 30000, 60000, 180000, 600000};
137 	/**
138 	 * Index of the currently selected move time in the array
139 	 */
140 	private int moveTimeIndex;
141 	/**
142 	 * To handle integration of undo/redo with the browser back/forward button, if supported
143 	 */
144 	private HistoryManager historyManager;
145 	/**
146 	 * Last move number
147 	 */
148 	int lastMoveNumber;
149 	/**
150 	 * First move number
151 	 */
152 	int firstMoveNumber;
153 
154 	/**
155 	 * UiBinder factory method to instantiate HSliderBar 
156 	 * @return
157 	 */
158 	@UiFactory
159 	HSliderBar makeHSliderBar() {
160 		final String[] labels = {
161 			ChessConstants.INSTANCE.mt3s(),
162 			ChessConstants.INSTANCE.mt10s(),
163 			ChessConstants.INSTANCE.mt30s(),
164 			ChessConstants.INSTANCE.mt1m(),
165 			ChessConstants.INSTANCE.mt3m(),
166 			ChessConstants.INSTANCE.mt10m()
167 		};
168 		HSliderBar sliderBar = new HSliderBar(0, 5, new LabelFormatter() {
169 		    protected String formatLabel(SliderBar slider, double value) {
170 		    	return labels[(int)value];
171 		    }			
172 		});
173 		sliderBar.setStepSize(1);
174 		sliderBar.setNumTicks(5);
175 		sliderBar.setNumLabels(5);
176 		sliderBar.setCurrentValue(0);
177 		sliderBar.addSliderListener(new SliderListenerAdapter() {
178 			@Override
179 			public void onValueChanged(SliderBar slider, double curValue) {
180 				moveTimeIndex = (int)curValue;
181 			}			
182 		});
183 		return sliderBar;
184 	}
185 	
186 	public int getHeight() {
187 		return (Window.getClientHeight() - 150);
188 	}
189 	
190 	  
191 	/**
192 	 * GWT entry point
193 	 */
194 	public void onModuleLoad() {
195 		final DecoratedPopupPanel initBox = new DecoratedPopupPanel();
196 		HorizontalPanel hpanel = new HorizontalPanel();
197 		hpanel.add(new Image(Resources.INSTANCE.getWaitImage()));
198 		hpanel.add(new Label(ChessConstants.INSTANCE.waitMessage()));
199 		initBox.add(hpanel);
200 		initBox.center();
201 		initBox.show();
202 		Scheduler.get().scheduleDeferred(new ScheduledCommand() {	
203 			@Override
204 			public void execute() {
205 				// Inject CSS in the document headers
206 				StyleInjector.inject(Resources.INSTANCE.getCss().getText());
207 				
208 				// Create a Carballo chess engine
209 				Config config = new Config();
210 				config.setTranspositionTableSize(2);
211 				config.setBook(new JSONBook());
212 				engine = new SearchEngine(config);
213 				engine.setObserver(Main.this);
214 				board = engine.getBoard();
215 		
216 				// Instantiate UI
217 				SplitLayoutPanel binderPanel = mainBinder.createAndBindUi(Main.this);
218 				confirmBox = ConfirmBox.createConfirmBox(Main.this);
219 				advancedPanel.getHeaderTextAccessor().setText(constants.advanced());
220 				
221 				modeListBox.addItem(ChessMode.whitesVsComputer.getDescription(), ChessMode.whitesVsComputer.name());
222 				modeListBox.addItem(ChessMode.blacksVsComputer.getDescription(), ChessMode.blacksVsComputer.name());
223 				modeListBox.addItem(ChessMode.whitesVsBlacks.getDescription(), ChessMode.whitesVsBlacks.name());
224 				modeListBox.addItem(ChessMode.computerVsComputer.getDescription(), ChessMode.computerVsComputer.name());
225 				modeListBox.setSelectedIndex(0);
226 				
227 				about.setHTML(ChessConstants.INSTANCE.about());
228 				RootLayoutPanel.get().add(binderPanel);
229 				
230 				// Parse the SVG chessboard and insert it in the HTML UI
231 				// Note that the elements must be imported in the UI since they come from another XML document
232 				boardDiv = boardContainer.getElement();
233 				boardElt = OMSVGParser.parse(Resources.INSTANCE.getBoard().getText());
234 				boardDiv.appendChild(boardElt.getElement());
235 		
236 				// Create the SVG chessboard. Use a temporary chessboard
237 				// until the engine has been initialized
238 				chessboard = new ChessBoard(board, boardElt, Main.this);
239 		
240 				// Add undo-redo support through the browser back/forward buttons
241 				historyManager = GWT.create(HistoryManager.class);
242 				historyManager.initialize(Main.this);
243 		
244 				moveTimeIndex = 0;
245 				restart();
246 				initBox.hide();
247 			}
248 		});
249 	}
250 
251 	/**
252 	 * Refresh the non SVG elements of the UI (list of moves, current player, FEN) 
253 	 */
254 	private void updateUI() {
255 		StringBuffer buffer = new StringBuffer();
256 		int count = board.getMoveNumber();
257 		for (int i = firstMoveNumber; i < count; i++) {
258 			String move = board.getSanMove(i);
259 			assert move != null;
260 			if (i > 0) {
261 				buffer.append("\n");
262 			}
263 			buffer.append((i + 1) + ". " + move);
264 		}
265 		historyArea.setVisibleLines(count == 0 ? 1 : count);
266 		historyArea.setText(buffer.toString());
267 		fenArea.setText(board.getFen());
268 		currentPlayerValueLabel.setText(board.getTurn() ? ChessConstants.INSTANCE.white() : ChessConstants.INSTANCE.black());
269 		int moveNumber = board.getMoveNumber();
270 		
271 		int firstPossibleMove = firstMoveNumber;
272 		int lastPossibleMove = lastMoveNumber;
273 		switch(getMode()) {
274 			case whitesVsBlacks:
275 				break;
276 			case whitesVsComputer:
277 				if (firstMoveNumber % 2 == 1) {
278 					firstPossibleMove++;
279 				}
280 				if (lastPossibleMove % 2 == 1) {
281 					lastPossibleMove--;
282 				}
283 				break;
284 			case blacksVsComputer:
285 				if (firstMoveNumber % 2 == 0) {
286 					firstPossibleMove++;
287 				}
288 				if (lastPossibleMove % 2 == 0) {
289 					lastPossibleMove--;
290 				}
291 				break;
292 			case computerVsComputer:
293 				firstPossibleMove = Integer.MAX_VALUE;
294 				lastPossibleMove = Integer.MIN_VALUE;
295 				break;
296 		}
297 		undoButton.setEnabled(moveNumber > firstPossibleMove);
298 		redoButton.setEnabled(moveNumber < lastPossibleMove);
299 	}
300 	ChessMode getMode() {
301 		return ChessMode.valueOf(modeListBox.getValue(modeListBox.getSelectedIndex()));
302 	}
303 	
304 	/**
305 	 * Invoked to make the game advance to the next move
306 	 */
307 	public void nextMove() {
308 		updateUI();
309 		switch (board.isEndGame()) {
310 			case 1 :
311 				Window.alert(ChessConstants.INSTANCE.whitesWin());
312 				restart();
313 				break;
314 			case -1:
315 				Window.alert(ChessConstants.INSTANCE.blacksWin());
316 				restart();
317 				break;
318 			case 99:
319 				Window.alert(ChessConstants.INSTANCE.draw());
320 				restart();
321 				break;
322 			default:
323 				switch(getMode()) {
324 					case whitesVsBlacks:
325 						break;
326 					case whitesVsComputer:
327 						if (!board.getTurn()) {
328 							computerMove();
329 						}
330 						break;
331 					case blacksVsComputer:
332 						if (board.getTurn()) {
333 							computerMove();
334 						}
335 						break;
336 					case computerVsComputer:
337 						computerMove();
338 						break;
339 				}
340 				break;
341 		}
342 
343 	}
344 	
345 	/**
346 	 * Invoked to make the computer play the next move
347 	 */
348 	private void computerMove() {
349 		Scheduler.get().scheduleDeferred(new ScheduledCommand() {
350 			@Override
351 			public void execute() {
352 				engine.go(SearchParameters.get(moveTimes[moveTimeIndex]));
353 			}
354 			
355 		});
356 	}
357 
358 	/**
359 	 * Invoked by the carballo engine when the search is done
360 	 */
361 	public void bestMove(int bestMove, int ponder) {
362 		GWT.log("Main.bestMove(" + Move.toStringExt(bestMove) + ", " + Move.toStringExt(ponder) + ")", null);
363 		board.doMove(bestMove);
364 		addMove();
365 		chessboard.update(false);
366 		nextMove();
367 	}
368 
369 	/**
370 	 * Unused carballo chess engine event handler
371 	 */
372 	public void info(SearchStatusInfo info) {
373 		GWT.log("Main.info(" + info + ")", null);
374 	}
375 	
376 	/**
377 	 * Start a new game
378 	 */
379 	public void restart() {
380 		chessboard.update(true);
381 		lastMoveNumber = 0;
382 		historyManager.setMove(0);
383 		board.startPosition();
384 		firstMoveNumber = board.getMoveNumber();
385 		chessboard.update(true);
386 		nextMove();
387 	}
388 	
389 	@UiHandler("fenButton")
390 	public void updateFen(ClickEvent event) {
391 		GWT.log("Main.updateFen(" + fenArea.getText() + ")", null);
392 		historyManager.setMove(0);
393 		board.setFen(fenArea.getText());
394 		lastMoveNumber = firstMoveNumber = board.getMoveNumber();
395 		chessboard.update(true);
396 		nextMove();
397 	}
398 
399 	@UiHandler("modeListBox")
400 	public void modeChange(ChangeEvent event) {
401 		GWT.log("Main.modeChange(" + modeListBox.getSelectedIndex() + ")", null);
402 		nextMove();
403 	}
404 	
405 	@UiHandler("restartButton")
406 	public void confirmRestart(ClickEvent event) {
407 		GWT.log("Main.confirmRestart()", null);
408         confirmBox.center();
409         confirmBox.show();
410     }
411 
412 	@UiHandler("undoButton")
413 	public void undo(ClickEvent event) {
414 		GWT.log("Main.undo()", null);
415 		int moveNumber = board.getMoveNumber();
416 		switch(getMode()) {
417 			case whitesVsBlacks:
418 				setMove(moveNumber - 1);
419 				break;
420 			case whitesVsComputer:
421 			case blacksVsComputer:
422 				setMove(moveNumber - 2);
423 				break;
424 			case computerVsComputer:
425 				break;
426 		}
427 		nextMove();
428    }
429 
430 	@UiHandler("redoButton")
431 	public void redo(ClickEvent event) {
432 		GWT.log("Main.redo()", null);
433 		int moveNumber = board.getMoveNumber();
434 		switch(getMode()) {
435 			case whitesVsBlacks:
436 				setMove(moveNumber + 1);
437 				break;
438 			case whitesVsComputer:
439 			case blacksVsComputer:
440 				setMove(moveNumber + 2);
441 				break;
442 			case computerVsComputer:
443 				break;
444 		}
445 		nextMove();
446     }
447 	
448 	/**
449 	 * Add an event to the browser undo/redo stack
450 	 */
451 	public void addMove() {
452 		historyManager.addMove();
453 		lastMoveNumber = board.getMoveNumber();
454 	}
455 
456 	private void setMove(int moveNumber) {
457 		historyManager.setMove(moveNumber);
458 		board.undoMove(moveNumber);
459 		chessboard.update(true);
460 	}
461 }