View Javadoc
1   /******************************************************************************
2    * GameManager.java - Manage all of the moves that happen in a single game
3    * $Id$
4    * 
5    * BuckoFIBS - Backgammon by BuckoSoft
6    * Copyright© 2009,2010 - Dick Balaska - BuckoSoft, Corp.
7    * 
8    * $Log$
9    * Revision 1.29  2013/09/12 06:37:01  dick
10   * Doubles dispatches gameEvents.
11   *
12   * Revision 1.28  2011/09/23 02:58:39  dick
13   * Rework a little bit when position gets updated. (and still needs work).
14   *
15   * Revision 1.27  2011/07/16 02:33:09  dick
16   * Add YouDouble.
17   *
18   * Revision 1.26  2011/07/04 03:43:07  dick
19   * Add FirstMove handling.
20   *
21   * Revision 1.25  2011/06/18 19:29:48  dick
22   * Work on resigning and YouCantMove.
23   *
24   * Revision 1.24  2011/06/14 19:18:07  dick
25   * Add YouCantMove handling.
26   * Bearing off only affects You.  Others will get a move message.
27   *
28   * Revision 1.23  2011/06/13 04:42:41  dick
29   * Handle FIBS_PlayerWantsToResign.
30   *
31   * Revision 1.22  2011/06/10 20:41:17  dick
32   * Add Undo to sendGameEvent().
33   * queueGameEvent() tweakage.
34   *
35   * Revision 1.21  2011/06/05 06:56:41  dick
36   * Handle AcceptsAndWins
37   *
38   * Revision 1.20  2011/06/02 19:11:20  dick
39   * Deal with resign messages.
40   *
41   * Revision 1.19  2011/05/23 05:55:42  dick
42   * Add youMoved() which removes any pending "YourMove" from the queue.
43   *
44   * Revision 1.18  2011/05/23 01:11:17  dick
45   * Add playPaused() skeleton.
46   *
47   * Revision 1.17  2011/05/22 05:09:45  dick
48   * Add AcceptReject double.
49   * Fix game opening sequence.
50   *
51   * Revision 1.16  2011/05/21 20:18:29  dick
52   * Sorting dice works.
53   *
54   * Revision 1.15  2011/05/21 04:59:56  dick
55   * Work on GUI integration.
56   * Add the preStartList for events that can't go out before the first board.
57   *
58   * Revision 1.14  2011/05/18 05:55:18  dick
59   * Nice watching, Still needs work for playing.
60   *
61   * Revision 1.13  2011/05/17 23:11:26  dick
62   * Working on passing replay commands to the animator.
63   *
64   * Revision 1.12  2011/05/16 14:18:29  dick
65   * Add support for the CantMove event.
66   *
67   * Revision 1.11  2011/05/16 11:33:58  dick
68   * Add FIBS_PlayerRolls.
69   *
70   * Revision 1.10  2011/05/15 18:17:15  dick
71   * Working on why the last GameMove is empty, instead of bearing off the last checkers.
72   *
73   * Revision 1.9  2011/05/15 02:19:12  dick
74   * GameLine becomes GameBoard.
75   * Rearrange a whole bunch of packages.
76   *
77   * Revision 1.8  2011/05/14 04:43:01  dick
78   * I needed to deal with some lightweight Boards.
79   * So finally make the primary document Document
80   * and demote just the Board handling to a domain object.
81   *
82   * Revision 1.7  2011/05/13 18:24:06  dick
83   * Working on turning off animation, which should skip move events
84   * and just display boards.
85   *
86   * Revision 1.6  2011/05/13 14:29:02  dick
87   * Work with GameEvents, not Lines.
88   *
89   * Revision 1.5  2011/05/11 22:22:42  dick
90   * Working on animating moves.
91   *
92   * Revision 1.4  2011/05/10 16:08:20  dick
93   * Fix the javadoc pointers to the source code.
94   *
95   * Revision 1.3  2010/03/03 13:12:21  inim
96   * Replaced (c) sign in comment mangled by CVS default encoding back to UTF-8
97   *
98   * Revision 1.2  2010/03/03 12:19:49  inim
99   * Moved source to UTF8 encoding from CP1252 encoding. To this end all source files' (c) message was updated to "Copyright© 2009,2010 - Dick Balaska - BuckoSoft, Corp.". This replaces the (c) sign to UTF8, and adds the new year 2010.
100  *
101  * Revision 1.1  2010/02/04 05:57:53  inim
102  * Mavenized project folder layout
103  *
104  * Revision 1.7  2009/02/23 09:44:54  dick
105  * Javadoc.
106  *
107  * Revision 1.6  2009/02/14 13:13:21  dick
108  * BuckoFIBS is released under the GNU license.
109  * Javadoc.
110  *
111  * Revision 1.5  2009/02/11 09:01:20  dick
112  * Don't initialize your turn if its not your turn or you're not playing.
113  *
114  * Revision 1.4  2009/01/31 08:53:08  dick
115  * We're moving checkers, not dice.
116  *
117  * Revision 1.3  2009/01/31 08:48:23  dick
118  * Line contains info about and GameManager initializes YourMove.
119  *
120  * Revision 1.2  2009/01/31 06:08:11  dick
121  * React to the replay buttons being pressed.
122  *
123  * Revision 1.1  2009/01/28 22:26:52  dick
124  * Skeleton GameManager.
125  */
126 
127 /* 
128  * This program is free software: you can redistribute it and/or modify
129  * it under the terms of the GNU General Public License as published by
130  * the Free Software Foundation, either version 3 of the License, or
131  * (at your option) any later version.
132  *
133  * This program is distributed in the hope that it will be useful,
134  * but WITHOUT ANY WARRANTY; without even the implied warranty of
135  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
136  * GNU General Public License for more details.
137  *
138  * You should have received a copy of the GNU General Public License
139  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
140  *
141  * The Original Code is BuckoFIBS, <http://www.buckosoft.com/BuckoFIBS/>.
142  * The Initial Developer of the Original Code is Dick Balaska and BuckoSoft, Corp.
143  * 
144  */
145 package com.buckosoft.fibs.BuckoFIBS;
146 
147 import java.util.LinkedList;
148 
149 import org.slf4j.Logger;
150 import org.slf4j.LoggerFactory;
151 
152 import com.buckosoft.fibs.BuckoFIBS.AudioManager.Cue;
153 import com.buckosoft.fibs.BuckoFIBS.gui.MainDialog;
154 import com.buckosoft.fibs.BuckoFIBS.gui.MainDialogI;
155 import com.buckosoft.fibs.BuckoFIBS.gui.boardTab.ReplayToolbarI;
156 import com.buckosoft.fibs.domain.Board;
157 import com.buckosoft.fibs.domain.CookieString;
158 import com.buckosoft.fibs.domain.gameEvent.GameEvent;
159 import com.buckosoft.fibs.domain.gameEvent.GameEventAcceptAndWin;
160 import com.buckosoft.fibs.domain.gameEvent.GameEventBoard;
161 import com.buckosoft.fibs.domain.gameEvent.GameEventCantMove;
162 import com.buckosoft.fibs.domain.gameEvent.GameEventDouble;
163 import com.buckosoft.fibs.domain.gameEvent.GameEventFirstMove;
164 import com.buckosoft.fibs.domain.gameEvent.GameEventFirstRoll;
165 import com.buckosoft.fibs.domain.gameEvent.GameEventMove;
166 import com.buckosoft.fibs.domain.gameEvent.GameEventPleaseAcceptOrRejectDouble;
167 import com.buckosoft.fibs.domain.gameEvent.GameEventPleaseAcceptOrRejectResign;
168 import com.buckosoft.fibs.domain.gameEvent.GameEventPleaseMove;
169 import com.buckosoft.fibs.domain.gameEvent.GameEventPleaseRollOrDouble;
170 import com.buckosoft.fibs.domain.gameEvent.GameEventRejectResign;
171 import com.buckosoft.fibs.domain.gameEvent.GameEventResign;
172 import com.buckosoft.fibs.domain.gameEvent.GameEventRoll;
173 import com.buckosoft.fibs.domain.gameEvent.GameEventYouDouble;
174 import com.buckosoft.fibs.domain.gameEvent.GameEvent.Life;
175 import com.buckosoft.fibs.domain.gameEvent.GameEvent.Type;
176 import com.buckosoft.fibs.net.FIBSMessages;
177 
178 /** Track each turn in a game. 
179  * Provides the ability to go back and forth through the game moves list.
180  * We receive game events from CommandDispatcher and send them to AnimationManager.
181  * @author Dick Balaska
182  * @since 2009/01/28
183  * @version $Revision$ <br> $Date$
184  * @see <a href="http://cvs.buckosoft.com/Projects/BuckoFIBS/BuckoFIBS/src/main/java/com/buckosoft/fibs/BuckoFIBS/GameManager.java">cvs GameManager.java</a>
185  */
186 public class GameManager implements FIBSMessages {
187 	private	final static boolean DEBUG = true;
188 	private	final static boolean DEBUGsend = true;
189 	private	final static boolean DEBUGpos = true;
190 	private	final static boolean DEBUGJibsBoard = true;
191 	
192 	private Logger logger = LoggerFactory.getLogger(getClass());
193 
194 	private int 				position;
195 	private	ReplayToolbarI		replayToolbar;
196 	private	MainDialogI			mainDialog;
197 	private	BFProperties		bfProps;
198 
199 	private final static int Undo		= -2;
200 	private final static int Backward	= -1;
201 	private final static int Start		=  0;
202 	private final static int Forward	=  1;
203 
204 	private LinkedList<GameEvent>	preStartList = new LinkedList<GameEvent>();
205 	//	private	boolean					resumingMatch = false;
206 
207 	/** Construct a GameManager */
208 	public GameManager() {
209 	}
210 
211 	/** Set the reference to the ReplayToolbar so that we can turn on and off his buttons
212 	 * @param replayToolbar The replayToolbar
213 	 */
214 	public void setReplayToolbar(ReplayToolbarI replayToolbar) {
215 		this.replayToolbar = replayToolbar;
216 	}
217 
218 	/** Set the reference to the instance of MainDialog
219 	 * @param mainDialog The MainDialog that is running
220 	 */
221 	public	void setMainDialog(MainDialog mainDialog) {
222 		this.mainDialog = mainDialog;
223 	}
224 
225 	/** Set the reference to the properties
226 	 * @param bfProperties
227 	 */
228 	public void setProperties(BFProperties bfProperties) {
229 		this.bfProps = bfProperties;
230 	}
231 
232 	/** Reset a game to have no moves stored.
233 	 * @param resumeMatch true if we are resuming a match
234 	 */
235 	public void reset(boolean resumeMatch) {
236 //		this.resumingMatch = resumeMatch;
237 		gameEvents.clear();
238 		preStartList.clear();
239 		position = 0;
240 		if (DEBUGpos)
241 			logger.info("reset: pos = " + this.position);
242 		updateToolbarButtons();
243 	}
244 
245 	/** Fibs says it is our turn
246 	 */
247 	public	void onRollOrDouble() {
248 		queueGameEvent(new GameEventPleaseRollOrDouble());
249 	}
250 
251 	/** Player has taken his turn, moved his checkers and picked up the dice.
252 	 */
253 	public void youMoved() {
254 		this.mainDialog.playSound(Cue.PickUpDice);
255 		this.mainDialog.updateBoard();
256 		//this.mainDialog.youCantMove();		// you can't move because your turn is over
257 /*		Iterator<GameEvent> iter = gameEvents.iterator();
258 		while (iter.hasNext()) {
259 			GameEvent ge = iter.next();
260 			if (ge.getType() == Type.PleaseMove) {
261 				iter.remove();
262 				break;
263 			}
264 		}
265 */	}
266 
267 	/** Add a FIBS game event message to our game 
268 	 * @param cs The Cookie and String from {@link ClientReceiveParser}
269 	 */
270 	public void addEvent(CookieString cs) {
271 		if (DEBUG)
272 			logger.info("addEvent: " + cs.getCookie() + " " + cs.getString());
273 		switch (cs.getCookie()) {
274 		case FIBS_Board:
275 			addBoardLine(cs.getString());
276 			break;
277 		case FIBS_PlayerMoves:
278 			addMove(cs.getString());
279 			break;
280 		case FIBS_BearingOff:
281 			addBearingOff(cs.getString());
282 			break;
283 		case FIBS_YouRoll:
284 		case FIBS_PlayerRolls:
285 			addRoll(cs.getString());
286 			break;
287 		case FIBS_CantMove:
288 			addCantMove(cs.getString());
289 			break;
290 		case FIBS_YouCantMove:
291 			addYouCantMove(cs.getString());
292 			break;
293 		case FIBS_PleaseMove:
294 			addPleaseMove(cs.getString());
295 			break;
296 		case FIBS_YourTurnToMove:
297 			addYourTurnToMove(cs.getString());
298 			break;
299 		case FIBS_MakesFirstMove:
300 			addFirstMove(cs.getString());
301 			break;
302 		case FIBS_FirstRoll:
303 			addFirstRoll(cs.getString());
304 			break;
305 		case FIBS_StartingNewGame:
306 			addStartNewGame(cs.getString());
307 			break;
308 		case FIBS_NewMatchAck2:
309 			addPlayerJoinsYou(cs.getString());
310 			break;
311 		case FIBS_Turn:
312 			addResumeYourTurn(cs.getString());
313 			break;
314 		case FIBS_AcceptRejectDouble:
315 			addAcceptRejectDouble(cs.getString());
316 			break;
317 		case FIBS_PlayerWantsToResign:
318 		case FIBS_WatchResign:
319 		case FIBS_YouResign:
320 			addResign(cs.getString());
321 			break;
322 		case FIBS_ResignRefused:
323 			addResignRefused(cs.getString());
324 			break;
325 		case FIBS_AcceptWins:
326 			addAcceptsAndWins(cs.getString());
327 			break;
328 		case FIBS_YouDouble:
329 			addYouDouble(cs.getString());
330 			break;
331 		case FIBS_Doubles:
332 			addOtherDoubles(cs.getString());
333 			break;
334 		default:
335 			logger.warn("Unhandled event: (" + cs.getCookie() + ") "+ cs.getString());
336 			break;
337 		}
338 	}
339 
340 	/** Starting a new game against Player. "<code>Starting a new game with dickbalaska.</code>"
341 	 * This resets the GameManager and generates a fake board, just so we have something to work against
342 	 *  until a real board arrives.
343 	 * @param s The string to parse. 
344 	 */
345 	private void addStartNewGame(String s) {
346 		reset(false);
347 		GameEventBoard gb = new GameEventBoard();
348 		Board b = gb.getBoard();
349 		b.setStartPositions();
350 		b.setPlayerName(Board.O, "You");
351 		String ss[] = s.split(" ");
352 		b.setPlayerName(Board.X, ss[5]);
353 		if (DEBUG)
354 			logger.info("addStartNewGame: opponent=" + ss[5]);
355 		queueGameEvent(gb);
356 	}
357 	/** Handle "<code>** Player dickbalaska has joined you for a 5 point match.</code>"
358 	 * This is the same as addStartNewGame() except the opponent joined your invite.
359 	 * @param s The String to parse
360 	 */
361 	private void addPlayerJoinsYou(String s) {
362 		reset(false);
363 		GameEventBoard gb = new GameEventBoard();
364 		Board b = gb.getBoard();
365 		b.setStartPositions();
366 		b.setPlayerName(Board.O, "You");
367 		String ss[] = s.split(" ");
368 		b.setPlayerName(Board.X, ss[2]);
369 		if (DEBUG)
370 			logger.info("addPlayerJoinsYou: opponent=" + ss[2]);
371 		queueGameEvent(gb);
372 	}
373 	/** Resume a match, and it's this player's turn upcoming
374 	 * @param s "<code>turn: Bucko.</code>"
375 	 */
376 	private void addResumeYourTurn(String s) {
377 		GameEventRoll gr = new GameEventRoll();
378 		String[] ss = s.split(" ");
379 		gr.setPlayerName(ss[1].substring(0, ss[1].length()-1));
380 		this.preStartList.add(gr);			// let the board fill out the values...
381 		if (DEBUG)
382 			logger.info("addResumeYourTurn: addPreStart: " + gr.toString());
383 		if (gr.getPlayerName().equals(this.mainDialog.getDocument().getName())) {
384 			GameEventPleaseMove gpm = new GameEventPleaseMove();
385 			gpm.setCheckersToMove(2);
386 			gpm.setDice(gr.getDice());
387 			this.preStartList.add(gpm);
388 			if (DEBUG)
389 				logger.info("addResumeYourTurn: addPreStart: " + gpm.toString());
390 		}
391 
392 	}
393 	/** Add this board line to the game log
394 	 * @param s The board to add
395 	 */
396 	private void addBoardLine(String s) {
397 		GameEventBoard gb = new GameEventBoard(s);
398 		queueGameEvent(gb);
399 		if (!this.preStartList.isEmpty()) {
400 			Board b = gb.getBoard();
401 			if (DEBUGJibsBoard)
402 				this.mainDialog.writeGameMessageln("board:" + b.getPlayerName()[0] + ":" + b.getPlayerName()[1]);
403 
404 			b.setSortDice(this.bfProps.isHighDieLeft());
405 			this.mainDialog.getDocument().setBoard(b);
406 			for (GameEvent ge : this.preStartList) {
407 				if (DEBUG)
408 					logger.info("Sending preStart: " + ge.toString());
409 				if (ge.getType() == Type.Roll) {
410 					GameEventRoll gr = (GameEventRoll)ge;
411 					if (gr.getDice()[0] == 0) {		// Do we need the dice roll from the board?
412 						if (this.bfProps.isHighDieLeft())
413 						gr.getDice()[0] = b.getDice()[Board.O][0];
414 						gr.getDice()[1] = b.getDice()[Board.O][1];
415 					}
416 				}
417 				queueGameEvent(ge);
418 			}
419 //			this.resumingMatch = false;
420 			this.preStartList.clear();
421 		}
422 	}
423 	private void addMove(String s) {
424 		GameEventMove gm = new GameEventMove();
425 		gm.parse(s);
426 		gm.setWho(this.mainDialog.getDocument().getBoard().getXOFromName(gm.getPlayerName()));
427 		gm.setDirection(this.mainDialog.getDocument().getBoard().getDirectionFromName(gm.getPlayerName()));
428 		if (DEBUG)
429 			logger.info("addMove: " + s);
430 		queueGameEvent(gm);
431 	}
432 	/** Bearing off is just a move that comes in funny.
433 	 * It is only important for You.  Opponents and watchers also get a move, so we'll use that.
434 	 * @param s 'Bearing off: 6 o 2 o '
435 	 */
436 	private void addBearingOff(String s) {
437 		GameEventRoll gr = (GameEventRoll)getLastOf(Type.Roll);
438 		if (!gr.getPlayerName().equals("You"))
439 			return;
440 		GameEventMove gm = new GameEventMove();
441 		gm.parseBearingOff(s);
442 		gm.setWho(Board.O);
443 		if (DEBUG)
444 			logger.info("addBearingOff: " + s);
445 		queueGameEvent(gm);
446 		
447 	}
448 	private	void addRoll(String s) {
449 		GameEventRoll gr = new GameEventRoll();
450 		gr.parse(s);
451 		gr.setWho(this.mainDialog.getDocument().getBoard().getXOFromName(gr.getPlayerName()));
452 		if (DEBUG) {
453 			logger.info("addRoll: " + s + " who:" + gr.getWho());
454 		}
455 		queueGameEvent(gr);
456 	}
457 	private	void addFirstRoll(String s) {
458 		GameEventFirstRoll gfr = new GameEventFirstRoll();
459 		gfr.parse(s);
460 		//gfr.setWho(this.mainDialog.getDocument().getBoard().getXOFromName(gfr.getPlayerName()));
461 		if (DEBUG) {
462 			logger.info("addRoll: " + s + " who:" + gfr.getWho());
463 		}
464 		queueGameEvent(gfr);
465 	}
466 	private void addYouCantMove(String unused) {
467 		GameEventCantMove gcm = new GameEventCantMove();
468 		gcm.setWho(Board.O);
469 		if (DEBUG)
470 			logger.info("addYouCantMove: " + " who:" + gcm.getWho());
471 		queueGameEvent(gcm);
472 		
473 	}
474 	private	void addCantMove(String s) {
475 		GameEventCantMove gcm = new GameEventCantMove();
476 		gcm.parse(s);
477 		gcm.setWho(this.mainDialog.getDocument().getBoard().getXOFromName(gcm.getPlayerName()));
478 		if (DEBUG)
479 			logger.info("addCantMove: " + s + " who:" + gcm.getWho());
480 		queueGameEvent(gcm);
481 	}
482 	/** Called when the current event is your move.
483 	 * @param checkersToMove The number of checkers you have to move.
484 	 */
485 	private void addPleaseMove(String s) {
486 		GameEventPleaseMove gpm = new GameEventPleaseMove();
487 		gpm.parse(s);
488 		GameEventRoll gr = (GameEventRoll)getLastOf(Type.Roll);
489 		gpm.setDice(gr.getDice());
490 		if (DEBUG)
491 			logger.info("addPleaseMove: toMove=" + gpm.getCheckersToMove());
492 		queueGameEvent(gpm);
493 	}
494 	/** Called if You are taking the first turn of the game. "<code>It's your turn to move.</code>"
495 	 * @param unused
496 	 */
497 	private void addYourTurnToMove(String s) {
498 		addFirstMove(s);
499 		GameEventPleaseMove gpm = new GameEventPleaseMove();
500 		//gpm.parse(s);
501 		gpm.setCheckersToMove(2);
502 		GameEventRoll gr = (GameEventRoll)getLastOf(Type.Roll);
503 		if (gr == null)
504 			gr = (GameEventRoll)getLastOf(Type.FirstRoll);
505 		gpm.setDice(gr.getDice());
506 		if (DEBUG)
507 			logger.info("addYourTurnToMove: toMove=" + gpm.getCheckersToMove());
508 		queueGameEvent(gpm);
509 	}
510 	private void addFirstMove(String s) {
511 		GameEventFirstMove gefm = new GameEventFirstMove();
512 		GameEventFirstRoll gefr = (GameEventFirstRoll)getLastOf(Type.FirstRoll);
513 		GameEventBoard geb = (GameEventBoard)getLastOf(Type.Board);
514 		gefm.parse(s, gefr, geb.getBoard());
515 		if (DEBUG)
516 			logger.info("addFirstMove:");
517 		queueGameEvent(gefm);
518 		
519 	}
520 	private void addResign(String s) {
521 		GameEventResign ger = new GameEventResign();
522 		GameEventBoard geb = (GameEventBoard)getLastOf(Type.Board);
523 		Board b = geb.getBoard();
524 		ger.parse(s, b);
525 		
526 		if (DEBUG)
527 			logger.info("addResign: s=" + s);
528 		queueGameEvent(ger);
529 		GameEventPleaseAcceptOrRejectResign geparr = new GameEventPleaseAcceptOrRejectResign();
530 		geparr.setWho(ger.getWho());
531 		geparr.setResigningPoints(ger.getResigningPoints());
532 		queueGameEvent(geparr);
533 	}
534 	private void addResignRefused(String unused) {
535 		GameEventPleaseAcceptOrRejectResign geparr =
536 			(GameEventPleaseAcceptOrRejectResign)getLastOf(Type.PleaseAcceptOrRejectResign);
537 		if (geparr == null)
538 			return;			// not enough info to properly work from this (player is most likely starting to watch in mid-game).
539 		GameEventRejectResign gerr = new GameEventRejectResign();
540 		gerr.setWho(geparr.getWho() == Board.X ? Board.O : Board.X);
541 		GameEventPleaseAcceptOrRejectResign gepaorr = (GameEventPleaseAcceptOrRejectResign)getLastOf(Type.PleaseAcceptOrRejectResign);
542 		gerr.setResigningPoints(gepaorr.getResigningPoints());
543 		queueGameEvent(gerr);
544 	}
545 	private void addAcceptsAndWins(String s) {
546 		GameEventAcceptAndWin geaaw = new GameEventAcceptAndWin();
547 		GameEventBoard geb = (GameEventBoard)getLastOf(Type.Board);
548 		Board b = geb.getBoard();
549 		geaaw.parse(s, b);
550 		queueGameEvent(geaaw);
551 	}
552 	private void addYouDouble(String s) {
553 		GameEventYouDouble geyd = new GameEventYouDouble();
554 		GameEventBoard geb = (GameEventBoard)getLastOf(Type.Board);
555 		Board b = geb.getBoard();
556 		b.setWasDoubled(b.getXO());
557 		geyd.setCubeBefore(b.getCube());
558 		geyd.setWhoDoubled(b.getXO());
559 		logger.info("who = " + geb.getBoard().getXOAsText());
560 		logger.info("geyd.whoDoubled = " + geyd.getWhoDoubled());
561 		queueGameEvent(geyd);
562 		GameEventPleaseAcceptOrRejectDouble gepard = new GameEventPleaseAcceptOrRejectDouble();
563 		gepard.setGuiEvent(false);
564 		gepard.setWhoDoubled(geyd.getWhoDoubled());
565 		logger.info("gepard.setWhoDoubled = " + gepard.getWhoDoubled());
566 		queueGameEvent(gepard);
567 
568 	}
569 
570 	private void addOtherDoubles(String s) {
571 		GameEventDouble ged = new GameEventDouble();
572 		GameEventBoard geb = (GameEventBoard)getLastOf(Type.Board);
573 		Board b = geb.getBoard();
574 		ged.parse(s, b);
575 		b.setWasDoubled(ged.getWhoDoubled());
576 		if (DEBUG)
577 			logger.info("addOtherDoubles: s=" + s);
578 		queueGameEvent(ged);
579 		GameEventPleaseAcceptOrRejectDouble gepard = new GameEventPleaseAcceptOrRejectDouble();
580 		gepard.setGuiEvent(false);
581 		gepard.setWhoDoubled(ged.getWhoDoubled());
582 		queueGameEvent(gepard);
583 	}
584 	private void addAcceptRejectDouble(String s) {
585 		GameEventDouble ged = new GameEventDouble();
586 		GameEventBoard geb = (GameEventBoard)getLastOf(Type.Board);
587 		Board b = geb.getBoard();
588 		ged.parse(s, b);
589 		b.setWasDoubled(ged.getWhoDoubled());
590 
591 		if (DEBUG)
592 			logger.info("addDoubles to You: s=" + s);
593 		queueGameEvent(ged);
594 		GameEventPleaseAcceptOrRejectDouble gepard = new GameEventPleaseAcceptOrRejectDouble();
595 		//gepard.setWho(Board.X);
596 		if (DEBUG)
597 			logger.info("addAcceptRejectDouble: add PleaseAcceptRejectDouble");
598 		queueGameEvent(gepard);
599 	}
600 
601 	///////////////////////////////////////////////////////////////////////////
602 	/** Go to the first event in the game log
603 	 */
604 	public void goFirst() {
605 		this.position = 1;
606 		if (DEBUGpos)
607 			logger.info("goFirst: pos = " + this.position);
608 		sendGameEvent(Start);
609 		updateToolbarButtons();
610 	}
611 
612 	/** Go to the previous event in the game log
613 	 */
614 	public void goPrev() {
615 		if (this.position > 1)
616 			this.position--;
617 		if (DEBUGpos)
618 			logger.info("goPrev: pos = " + this.position);
619 		sendGameEvent(Backward);
620 		updateToolbarButtons();
621 	}
622 
623 	/** Go to the next event in the game log
624 	 */
625 	public void goNext() {
626 		if (this.position < this.gameEvents.size())
627 			this.position++;
628 		if (DEBUGpos)
629 			logger.info("goNext: pos = " + this.position);
630 		sendGameEvent(Forward);
631 		updateToolbarButtons();
632 	}
633 
634 	/** Go to the last event in the game log
635 	 */
636 	public void goLast() {
637 		this.position = this.gameEvents.size();
638 		if (DEBUGpos)
639 			logger.info("goLast: pos = " + this.position);
640 		sendGameEvent(Forward);
641 		updateToolbarButtons();
642 	}
643 
644 	/** Undo a partial move, start your turn over.
645 	 */
646 	public void goUndo() {
647 		sendGameEvent(Undo);
648 	}
649 
650 	/** Play or Paused pressed */
651 	public void goPlayPaused() {
652 		
653 	}
654 
655 	///////////////////////////////////////////////////////////////////////////
656 	///////////////////////////////////////////////////////////////////////////
657 	/** Send the current Event to the display.
658 	 * If animation is off, then skip animation events by moving to prev/next.
659 	 * Synchronized because the timer thread can enter here
660 	 * @param dir The direction to skip to, if need be. {@link #Forward} or {@link #Backward}
661 	 */
662 	private	synchronized void sendGameEvent(int dir) {
663 		//int localpos = this.position;
664 		if (DEBUGsend || DEBUGpos)
665 			logger.info("sendGameEvent: position=" + this.position + " size=" + this.gameEvents.size());
666 		if (dir == Undo) {
667 			// We assume it is a legit undo.
668 			// We are going to look for board-rolls-board and rewind to that first board.
669 			boolean lastBoardWasSeen = false;
670 			GameEvent ge = null;
671 			int	k;
672 			for (k = this.gameEvents.size()-1; k >= 0; k--) {
673 				ge = this.gameEvents.get(k);
674 				if (ge.getType() == Type.Board) {
675 					if (lastBoardWasSeen) {
676 						break;
677 					}
678 					lastBoardWasSeen = true;
679 				}
680 				ge = null;
681 			}
682 			if (ge != null) {
683 				for (position = k; position < this.gameEvents.size(); position++) {
684 					ge = this.gameEvents.get(position);
685 					this.mainDialog.moveEvent(ge);
686 				}
687 			}
688 		}
689 		if (dir == Forward && this.replayToolbar.isPlayPause()) {
690 			
691 		}
692 		while (true) {
693 			GameEvent ge = this.gameEvents.get(this.position-1);
694 			if (DEBUGsend)
695 				logger.debug("send: loop top: pos=" + this.position + " ge=" + ge.getType().toString());
696 			if (ge.getType() == Type.Board) {
697 //				GameBoard gb = (GameBoard)ge;
698 //				Board board = this.mainDialog.getDocument().getBoard();
699 //				if (board.isYouPlaying() && board.isYourMove() && position == gameEvents.size())
700 //					this.mainDialog.yourMove(gb.getCheckersToMove());
701 				if (DEBUGsend) {
702 					logger.debug("sendToAnimate a: pos=" + position + " size=" + this.gameEvents.size());
703 					logger.debug("sendToAnimtate a:  " + ge.getType().toString());
704 				}
705 				this.mainDialog.moveEvent(ge);
706 				if ((bfProps.isAnimateMoves() && this.position == gameEvents.size())
707 						|| dir == Start) {
708 					return;
709 				}
710 				if (this.position >= gameEvents.size()) {	// unless it is the last one in the list
711 					if (DEBUGsend || DEBUGpos)
712 						logger.info("send: End of list: Don't bump pos " + this.position + " after sending board");
713 					updateToolbarButtons();
714 					return;
715 				}
716 				this.position += dir;
717 				if (DEBUGsend || DEBUGpos)
718 					logger.info("send: Set pos to " + this.position + " after sending board");
719 				updateToolbarButtons();
720 			} else {
721 				if (bfProps.isAnimateMoves()) {
722 					if (DEBUGsend) {
723 						logger.info("sendToAnimate b: pos=" + position + " size=" + this.gameEvents.size());
724 						logger.info("sendToAnimate b:  " + ge.getType().toString());
725 					}
726 					this.mainDialog.moveEvent(ge);
727 					updateToolbarButtons();
728 					return;				
729 				}
730 				if (dir == Forward) {				// going forward?
731 					if (this.position < gameEvents.size()) {
732 						this.position++;								// try to skip ahead
733 					}
734 					if (DEBUGsend || DEBUGpos)
735 						logger.info("send: skip position to " + this.position + " gameEvents.size=" + gameEvents.size());
736 					if (this.position >= gameEvents.size()) {	// unless it is the last one in the list
737 						if (DEBUGsend || DEBUGpos) {
738 							logger.info("sendToAnimate c: pos=" + position + " size=" + this.gameEvents.size());
739 							logger.info("sendToAnimate c:  " + ge.getType().toString());
740 						}
741 						this.mainDialog.moveEvent(ge);
742 						//this.position = localpos;
743 						if (DEBUGsend || DEBUGpos)
744 							logger.info("send: pos = " + this.position + " and send nothing");
745 						updateToolbarButtons();
746 						return;
747 					}
748 					if (DEBUGsend)
749 						logger.info("send: Skipping this event");
750 					continue;
751 				}
752 				this.position--;
753 				if (this.position < 1) {
754 					this.position = 1;
755 					if (DEBUGsend) {
756 						logger.info("sendToAnimate d: pos=" + position + " size=" + this.gameEvents.size());
757 						logger.info("sendToAnimate d:  " + ge.getType().toString());
758 					}
759 					this.mainDialog.moveEvent(ge);
760 					updateToolbarButtons();
761 					return;
762 				}
763 			}
764 		}
765 	}
766 
767 	private LinkedList<GameEvent>	gameEvents = new LinkedList<GameEvent>();
768 
769 	/** All write access to the gameEvents list is via this function.
770 	 * @param gameEvent The event to queue at the end.
771 	 */
772 	private	void queueGameEvent(GameEvent gameEvent) {
773 		if (DEBUG)
774 			logger.debug("queueGameEvent: a position=" + this.position + " size=" + this.gameEvents.size());
775 		if (!gameEvents.isEmpty()) {
776 			GameEvent old = gameEvents.getLast();
777 			if (old.getLife() == Life.Transient) {
778 				if (this.position == gameEvents.size())
779 					this.position--;
780 				gameEvents.removeLast();
781 				if (DEBUG) {
782 					logger.debug("queueGameEvent: b position=" + this.position + " size=" + this.gameEvents.size());
783 					logger.debug("queueGameEvent: b remove old=" +  old.getType().toString());
784 				}
785 //				if (gameEvent.getType() == Type.Board) {
786 //					GameEventBoard gb = (GameEventBoard)gameEvent;
787 //					gb.setPostEvent(old);
788 //					if (DEBUG)
789 //						logger.info("queueGameEvent: set post=" + old.getType().toString());
790 //				}
791 			}
792 		}
793 		gameEvents.add(gameEvent);
794 		if (DEBUG)
795 			logger.debug("queueGameEvent: queued: " + gameEvent.toString());
796 		if (DEBUG)
797 			logger.debug("queueGameEvent: c position=" + this.position + " size=" + this.gameEvents.size());
798 		boolean shouldMove = this.replayToolbar.isPlayPause();
799 		if (shouldMove) {
800 			this.position++;
801 			sendGameEvent(Forward);
802 		} else {
803 			updateToolbarButtons();
804 		}
805 	}
806 
807 	private	GameEvent	getLastOf(Type eventType) {
808 		GameEvent ret = null;
809 		for (GameEvent ge : gameEvents) {
810 			if (ge.getType() == eventType)
811 				ret = ge;
812 		}
813 		return(ret);
814 	}
815 
816 	private	void updateToolbarButtons() {
817 		this.replayToolbar.getJButtonFirst().setEnabled(position > 1);
818 		this.replayToolbar.getJButtonBack().setEnabled( position > 1);
819 		this.replayToolbar.getJButtonNext().setEnabled( position < gameEvents.size());
820 		this.replayToolbar.getJButtonEnd().setEnabled(  position < gameEvents.size());
821 	}
822 	
823 	// Timer goes here
824 }