View Javadoc
1   /******************************************************************************
2    * AnimateManager.java - Manage displaying game moves as they happen
3    * $Id$
4    * 
5    * BuckoFIBS - Backgammon by BuckoSoft
6    * Copyright© 2011 - Dick Balaska - BuckoSoft, Corp.
7    * 
8    * $Log$
9    * Revision 1.9  2013/09/12 06:42:28  dick
10   * Animating the double cube.
11   *
12   * Revision 1.8  2013/09/10 00:22:54  dick
13   * Spline2D and Flasher move MoveSpline2D and MoveFlasher
14   *
15   * Revision 1.7  2011/07/16 03:52:55  dick
16   * Add YouDouble
17   *
18   * Revision 1.6  2011/07/04 03:43:53  dick
19   * Add FirstRoll handling.
20   *
21   * Revision 1.5  2011/06/18 19:32:58  dick
22   * Set the resigning points in the AcceptAndWin object.
23   *
24   * Revision 1.4  2011/06/05 06:55:43  dick
25   * Handle AcceptAndWin.
26   *
27   * Revision 1.3  2011/06/02 19:17:48  dick
28   * If the last event on the queue is removed and it isGui(), then keep it special off the queue,
29   * so that BoardGui knows what kind of gui event to deal with (but we don't run the queue).
30   *
31   * Deal with Resign events.
32   *
33   * Revision 1.2  2011/05/23 05:59:12  dick
34   * AnimateManager is responsible for playing game sounds as they come up.
35   *
36   * Revision 1.1  2011/05/22 22:56:08  dick
37   * c.b.f.B.g.boardTab.board becomes c.b.f.B.g.boardTab.boardPane .
38   *
39   * Revision 1.13  2011/05/22 05:23:28  dick
40   * Add "Please" handling, RollOrDouble and AcceptRejectDouble.
41   * All GameEvent objects are named starting with GameEvent.
42   *
43   * Revision 1.12  2011/05/21 20:18:30  dick
44   * Sorting dice works.
45   *
46   * Revision 1.11  2011/05/21 06:09:12  dick
47   * Put the dice back in the board in the correct place.
48   *
49   * Revision 1.10  2011/05/21 05:06:35  dick
50   * Handle the PleaseMove event.
51   *
52   * Revision 1.9  2011/05/18 05:54:43  dick
53   * Animating uses a clone of the board so that when we muck with it,
54   * we don't mess up the game boards (in case we replay them).
55   *
56   * Revision 1.8  2011/05/17 22:52:38  dick
57   * extraCheckers is only used for the drawing, and should not be used in calculations; it is not at the same time as the calcs.
58   *
59   * Revision 1.7  2011/05/16 21:25:32  dick
60   * Handle Home and Bar during Move.
61   *
62   * Revision 1.6  2011/05/16 17:03:34  dick
63   * Erase the dice after a CantMove.
64   *
65   * Revision 1.5  2011/05/16 15:59:01  dick
66   * Add finalize().
67   * Clear dice from the board during the roll animation.
68   *
69   * Revision 1.4  2011/05/16 14:17:58  dick
70   * Add support for Type.CantMove.
71   *
72   * Revision 1.3  2011/05/16 11:40:57  dick
73   * All access for the event list goes through only one synchronized function.
74   *
75   * Revision 1.2  2011/05/15 04:37:30  dick
76   * All access to eventList is through one synchronized function.
77   * Calculate the checkers positions from lastBoard thru the moves on the queue.
78   *
79   * Revision 1.1  2011/05/15 02:17:54  dick
80   * Move the AnimateEvents to their own package.
81   *
82   * Revision 1.4  2011/05/14 04:43:01  dick
83   * I needed to deal with some lightweight Boards.
84   * So finally make the primary document Document
85   * and demote just the Board handling to a domain object.
86   *
87   * Revision 1.3  2011/05/14 00:12:21  dick
88   * Redraw the board after erasing extraCheckers.
89   *
90   * Revision 1.2  2011/05/13 18:25:28  dick
91   * Debugging animation.  Still needs help with the Y checker position.
92   * Also need to do bar/home positions.
93   *
94   * Revision 1.1  2011/05/13 14:28:13  dick
95   * Manage displaying game moves as they happen
96   *
97   */
98  
99  /* 
100  * This program is free software: you can redistribute it and/or modify
101  * it under the terms of the GNU General Public License as published by
102  * the Free Software Foundation, either version 3 of the License, or
103  * (at your option) any later version.
104  *
105  * This program is distributed in the hope that it will be useful,
106  * but WITHOUT ANY WARRANTY; without even the implied warranty of
107  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
108  * GNU General Public License for more details.
109  *
110  * You should have received a copy of the GNU General Public License
111  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
112  *
113  * The Original Code is BuckoFIBS, <http://www.buckosoft.com/BuckoFIBS/>.
114  * The Initial Developer of the Original Code is Dick Balaska and BuckoSoft, Corp.
115  * 
116  */
117 package com.buckosoft.fibs.BuckoFIBS.gui.boardTab.boardPane;
118 
119 import java.util.Iterator;
120 import java.util.LinkedList;
121 import java.util.Timer;
122 import java.util.TimerTask;
123 
124 import javax.swing.ImageIcon;
125 
126 import org.slf4j.Logger;
127 import org.slf4j.LoggerFactory;
128 
129 import com.buckosoft.fibs.BuckoFIBS.AudioManager;
130 import com.buckosoft.fibs.BuckoFIBS.BFProperties;
131 import com.buckosoft.fibs.BuckoFIBS.gui.boardTab.BoardGui;
132 import com.buckosoft.fibs.BuckoFIBS.gui.boardTab.boardPane.animateType.DoubleSpline2D;
133 import com.buckosoft.fibs.BuckoFIBS.gui.boardTab.boardPane.animateType.MoveFlasher;
134 import com.buckosoft.fibs.BuckoFIBS.gui.boardTab.boardPane.animateType.MoveSpline2D;
135 import com.buckosoft.fibs.domain.Board;
136 import com.buckosoft.fibs.domain.gameEvent.GameEvent;
137 import com.buckosoft.fibs.domain.gameEvent.GameEvent.Type;
138 import com.buckosoft.fibs.domain.gameEvent.GameEventAcceptAndWin;
139 import com.buckosoft.fibs.domain.gameEvent.GameEventBoard;
140 import com.buckosoft.fibs.domain.gameEvent.GameEventCantMove;
141 import com.buckosoft.fibs.domain.gameEvent.GameEventDouble;
142 import com.buckosoft.fibs.domain.gameEvent.GameEventFirstRoll;
143 import com.buckosoft.fibs.domain.gameEvent.GameEventMove;
144 import com.buckosoft.fibs.domain.gameEvent.GameEventPleaseAcceptOrRejectDouble;
145 import com.buckosoft.fibs.domain.gameEvent.GameEventPleaseAcceptOrRejectResign;
146 import com.buckosoft.fibs.domain.gameEvent.GameEventPleaseMove;
147 import com.buckosoft.fibs.domain.gameEvent.GameEventPleaseRollOrDouble;
148 import com.buckosoft.fibs.domain.gameEvent.GameEventRejectResign;
149 import com.buckosoft.fibs.domain.gameEvent.GameEventResign;
150 import com.buckosoft.fibs.domain.gameEvent.GameEventRoll;
151 
152 /** Manage animating game events
153  * @author Dick Balaska
154  * @since 2011/05/12
155  * @version $Revision$ <br> $Date$
156  * @see <a href="http://cvs.buckosoft.com/Projects/BuckoFIBS/BuckoFIBS/src/main/java/com/buckosoft/fibs/BuckoFIBS/gui/boardTab/boardPane/AnimateManager.java">cvs AnimateManager.java</a>
157  */
158 public class AnimateManager {
159 	private	final static boolean DEBUG = true;
160 	private	final static boolean DEBUGqueue = false;
161 	private	final static boolean DEBUGmove = false;
162 	
163     private Logger logger = LoggerFactory.getLogger(getClass());
164 	private	BoardPane		boardPane;
165 	private	BoardGui		boardGui;
166 	private	BFProperties	bfProps;
167 	private	ImageIcon		resignIcon = null;
168 	private	ImageIcon		acceptIcon = null;
169 
170 	/** The last Board processed by the TimerTask. */
171 	private	Board			lastBoard;
172 
173 	/** 4 checkers and 4 positions and 4 zero spares for in process compound moves.
174 	 * These are the translucent checkers in transit between Boards. 
175 	 * These are pairs; 0/1 = point/offset */
176 	protected	int[]		extraCheckers = new int[12];
177 	/** The number of used checkers in the extraCheckers array */
178 	protected	int			extraCheckersIndex = 0;
179 	/** Who's extra Checkers (are in home) */
180 	protected	int			extraCheckersWho = 0;
181 
182 	/** Default constructor */
183 	public AnimateManager() {}
184 
185 	/** Shut down the timer queue on cleanup. 
186 	 * Failing to do so causes the app to keep running...
187 	 */
188 	@Override
189 	public void finalize() {
190 		accessEventList(EventListCommand.removeAll, null, null);
191 		if (moveTimer != null)
192 			moveTimer.cancel();
193 		if (moveTimerTask != null)
194 			moveTimerTask.cancel();
195 		moveTimer = null;
196 		moveTimerTask = null;		
197 	}
198 
199 	/** Set the back reference to the boardGui for notifications 
200 	 * @param boardPane The BoardPane
201 	 */
202 	public void setBoardPane(BoardPane boardPane) {
203 		this.boardGui = (BoardGui)boardPane;
204 		this.boardPane = boardPane;		// yeah they are the same object, but logically separate.
205 	}
206 
207 	/** Set the reference to the properties
208 	 * @param bfProperties
209 	 */
210 	public void setProperties(BFProperties bfProperties) {
211 		this.bfProps = bfProperties;
212 	}
213 
214 	/** Return the first {@link AnimateEvent} in the queue. 
215 	 * Does not remove the element.
216 	 * @return The head of the queue or null if the queue is empty.
217 	 */
218 	public AnimateEvent	getHeadEvent() {
219 		return(accessEventList(EventListCommand.head, null, null));
220 	}
221 
222 	/** Queue this animation event for display.<br>
223 	 * Note that if !props.isAnimateMoves() then we just discard the moves 
224 	 *    except for Board, which is important.
225 	 *    
226 	 * @param gameEvent The event to queue and eventually display
227 	 */
228 	public void addEvent(GameEvent gameEvent) {
229 		if (DEBUGqueue || DEBUG)
230 			logger.info("addEvent: " + gameEvent.getType().toString() + ":: " + gameEvent.toString());
231 		if (gameEvent.getType() == Type.Board)
232 			addBoard((GameEventBoard)gameEvent);
233 		else {
234 			if (!bfProps.isAnimateMoves())
235 				return;
236 			switch (gameEvent.getType()) {
237 				case Move:
238 					addMove((GameEventMove)gameEvent);
239 					break;
240 				case Roll:
241 					addRoll((GameEventRoll)gameEvent);
242 					break;
243 				case CantMove:
244 					addCantMove((GameEventCantMove)gameEvent);
245 					break;
246 				case PleaseMove:
247 					addPleaseMove((GameEventPleaseMove)gameEvent);
248 					break;
249 				case PleaseRollOrDouble:
250 					addPleaseRollOrDouble((GameEventPleaseRollOrDouble)gameEvent);
251 					break;
252 				case PleaseAcceptOrRejectDouble:
253 					addPleaseAcceptOrRejectDouble((GameEventPleaseAcceptOrRejectDouble)gameEvent);
254 					break;
255 				case Resign:
256 					addResign((GameEventResign)gameEvent);
257 					break;
258 				case PleaseAcceptOrRejectResign:
259 					addPleaseAcceptOrRejectResign((GameEventPleaseAcceptOrRejectResign)gameEvent);
260 					break;
261 				case RejectResign:
262 					addRejectResign((GameEventRejectResign)gameEvent);
263 					break;
264 				case AcceptAndWin:
265 					addAcceptAndWin((GameEventAcceptAndWin)gameEvent);
266 					break;
267 				case FirstRoll:
268 					addFirstRoll((GameEventFirstRoll)gameEvent);
269 					break;
270 				case Double:
271 					addDouble((GameEventDouble)gameEvent);
272 					break;
273 				//case OpponentDouble:
274 				//	addPleaseAcceptOrRejectDouble((GameEventPleaseAcceptOrRejectDouble)gameEvent);
275 				//	break;
276 				default:
277 					logger.warn("unhandled addEvent: " + gameEvent.getType().toString() + ":: " + gameEvent.toString());
278 			}
279 		}
280 	}
281 
282 	// put the home and bar values at the end of the point array for convienence
283 	private final static int paHOME = 26;
284 	private final static int paBAR = 28;
285 	private	int[] pointArray = new int[paBAR+2];
286 	
287 	private void addBoard(GameEventBoard gameBoard) {
288 //		this.board = boardPane.board;
289 		AnimateEventBoard aeb = new AnimateEventBoard();
290 		aeb.setBoard(gameBoard.getBoard().clone());
291 		queueEvent(aeb);
292 		if (gameBoard.getPostEvent() != null)
293 			addEvent(gameBoard.getPostEvent());
294 	}
295 	private	void addCantMove(GameEventCantMove gameCantMove) {
296 		AnimateEventCantMove acm = new AnimateEventCantMove();
297 		acm.setWho(gameCantMove.getWho());
298 		queueEvent(acm);
299 	}
300 	private	void addRoll(GameEventRoll gameRoll) {
301 		AnimateEventDiceRoll aedr = new AnimateEventDiceRoll();
302 		aedr.setWho(gameRoll.getWho());
303 		int[] dice = gameRoll.getDice();
304 		maybeSortDice(dice);
305 		aedr.dice[0] = dice[0];
306 		aedr.dice[1] = dice[1];
307 		if (DEBUG)
308 			logger.info("addRoll: dice=" + dice[0] + "-" + dice[1] + " aedr=" + aedr.dice[0] + "-" + aedr.dice[1]);
309 		queueEvent(aedr);
310 	}
311 	private void addFirstRoll(GameEventFirstRoll gameFirstRoll) {
312 		AnimateEventFirstRoll aefr = new AnimateEventFirstRoll();
313 		aefr.setWhiteDie(gameFirstRoll.getDice()[0]);
314 		aefr.setBlackDie(gameFirstRoll.getDice()[1]);
315 		aefr.setWhitePlayer(gameFirstRoll.getPlayerNames()[0]);
316 		aefr.setBlackPlayer(gameFirstRoll.getPlayerNames()[1]);
317 		queueEvent(aefr);
318 	}
319 	private	void addPleaseMove(GameEventPleaseMove gamePleaseMove) {
320 		AnimateEventPleaseMove aepm = new AnimateEventPleaseMove();
321 		aepm.setCheckersToMove(gamePleaseMove.getCheckersToMove());
322 		aepm.setDice(gamePleaseMove.getDice());
323 		maybeSortDice(aepm.getDice());
324 		queueEvent(aepm);
325 	}
326 	private void addPleaseRollOrDouble(GameEventPleaseRollOrDouble unused) {
327 		AnimateEventPleaseRollOrDouble aeprd = new AnimateEventPleaseRollOrDouble();
328 		queueEvent(aeprd);
329 	}
330 	private void addPleaseAcceptOrRejectDouble(GameEventPleaseAcceptOrRejectDouble gepaord) {
331 		// XXX
332 		AnimateEventPleaseAcceptOrRejectDouble aepard = new AnimateEventPleaseAcceptOrRejectDouble();
333 		aepard.setGui(gepaord.isGuiEvent());
334 		aepard.setWhoDoubled(gepaord.getWhoDoubled());
335 		queueEvent(aepard);
336 	}
337 	private void addResign(GameEventResign gameEventResign) {
338 		if (resignIcon == null)
339 			resignIcon = new ImageIcon(getClass().getResource("/g/resign_flag-366x353.png"));
340 		AnimateEventResign aer = new AnimateEventResign();
341 		aer.setIcon(resignIcon);
342 		aer.setWho(gameEventResign.getWho());
343 		aer.setResigningPoints(gameEventResign.getResigningPoints());
344 		queueEvent(aer);
345 	}
346 	private void addPleaseAcceptOrRejectResign(GameEventPleaseAcceptOrRejectResign gameEventPleaseAcceptRejectResign) {
347 		AnimateEventPleaseAcceptOrRejectResign aeparr = new AnimateEventPleaseAcceptOrRejectResign();
348 		if (resignIcon == null)
349 			resignIcon = new ImageIcon(getClass().getResource("/g/resign_flag-366x353.png"));
350 		aeparr.setIcon(resignIcon);
351 		aeparr.setWho(gameEventPleaseAcceptRejectResign.getWho());
352 		aeparr.setResigningPoints(gameEventPleaseAcceptRejectResign.getResigningPoints());
353 		queueEvent(aeparr);
354 	}
355 	private void addRejectResign(GameEventRejectResign gameEventRejectResign) {
356 		AnimateEventRejectResign aerr = new AnimateEventRejectResign();
357 		if (resignIcon == null)
358 			resignIcon = new ImageIcon(getClass().getResource("/g/resign_flag-366x353.png"));
359 		aerr.setIcon(resignIcon);
360 		aerr.setWho(gameEventRejectResign.getWho() == Board.X ? Board.O : Board.X);
361 		aerr.setResigningPoints(gameEventRejectResign.getResigningPoints());
362 		queueEvent(aerr);
363 	}
364 	private void addAcceptAndWin(GameEventAcceptAndWin gameEventAcceptAndWin) {
365 		AnimateEventAcceptAndWin aeaaw = new AnimateEventAcceptAndWin();
366 		if (acceptIcon == null)
367 			acceptIcon = new ImageIcon(getClass().getResource("/g/green-check-mark-217x250.png"));
368 		aeaaw.setIcon(acceptIcon);
369 		aeaaw.setWho(gameEventAcceptAndWin.getWho() == Board.X ? Board.O : Board.X);
370 		aeaaw.setResigningPoints(gameEventAcceptAndWin.getResigningPoints());
371 		queueEvent(aeaaw);
372 	}
373 	// XXX
374 	private void addDouble(GameEventDouble gameDouble) {
375 		AnimateEventDouble aed = createAnimateEventDouble();
376 		aed.setBoardPane(boardPane);
377 		aed.setCubeBefore(gameDouble.getCubeBefore());
378 		aed.setWhoDoubled(gameDouble.getWhoDoubled());
379 		queueEvent(aed);
380 	}
381 
382 	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
383 	/** A little helper class for keeping track of moving checkers
384 	 * @author dick
385 	 */
386 	private class PointOffset {
387 		PointOffset(int point, int offset) {
388 			this.point = point; this.offset = offset;
389 		}
390 		public int	point;
391 		public int	offset;
392 	}
393 
394 	private void addMove(GameEventMove gameMove) {
395 		int[] m = gameMove.getMoves();
396 		accessEventList(EventListCommand.calcCheckers, null, pointArray);
397 		if (DEBUGmove)
398 			logger.info("who=" + gameMove.getWho() 
399 					+ " bar=" + pointArray[paBAR+gameMove.getWho()] + " home=" + pointArray[paHOME+gameMove.getWho()]);
400 
401 		LinkedList<PointOffset>	movingCheckers = new LinkedList<PointOffset>();
402 		Iterator<PointOffset> iter;
403 
404 		for (int i=0; i<m.length; i+=2) {
405 			AnimateEventMove am = createAnimateEventMove();
406 			am.setBoardPane(boardPane);
407 			am.setWho(gameMove.getWho());
408 			int startPoint = m[i];
409 			int startOffset = Math.abs(pointArray[startPoint]);
410 			startOffset--;
411 			iter = movingCheckers.iterator();
412 			while (iter.hasNext()) {
413 				PointOffset po = iter.next();
414 				if (po.point == startPoint) {
415 					startOffset = po.offset;
416 					iter.remove();
417 				}
418 			}
419 			if (m[i] == Board.Bar) {
420 				startOffset = pointArray[paBAR+gameMove.getWho()]-1;
421 				pointArray[paBAR+gameMove.getWho()]--;
422 			} else {
423 				pointArray[m[i]] -= gameMove.getDepth();
424 			}
425 			am.setStartPointAndOffset(m[i], startOffset);
426 			int endPoint = m[i+1];
427 			int endOffset = Math.abs(pointArray[m[i+1]]);
428 			for (PointOffset po : movingCheckers) {
429 				if (po.point == endPoint) {
430 					endOffset = po.offset+1;
431 					break;
432 				}
433 			}
434 			if (m[i+1] == Board.Home) {
435 				endOffset = pointArray[paHOME+gameMove.getWho()];
436 				pointArray[paHOME+gameMove.getWho()]++;
437 			} else
438 				pointArray[m[i+1]] += gameMove.getDepth();
439 			am.setEndPointAndOffset(endPoint, endOffset);
440 			movingCheckers.addFirst(new PointOffset(endPoint, endOffset));
441 			if (DEBUGmove) {
442 				logger.info("addMove: checkers on start point[" + m[i] + "]=" + pointArray[m[i]]);
443 				logger.info("addMove: checkers on end   point[" + m[i+1] + "]=" + pointArray[m[i+1]]);
444 				logger.info("addMove: start=" + am.pointNumberStart + "/" + am.checkerNumberStart + " end=" 
445 						+ am.pointNumberEnd + "/" + am.checkerNumberEnd);
446 			}
447 			am.calculate();
448 			queueEvent(am);
449 		}
450 	}
451 
452 	private void maybeSortDice(int[] dice) {
453 		if (this.bfProps.isHighDieLeft() && dice[0] < dice[1]) {
454 			int x;
455 			x = dice[0];
456 			dice[0] = dice[1];
457 			dice[1] = x;
458 		}
459 	}
460 
461 	/** Get a new AnimateMove object depending on the user's preference 
462 	 * of which kind to use.
463 	 * @return The new AnimateEventMove
464 	 */
465 	private	AnimateEventMove createAnimateEventMove() {
466 		AnimateEventMove am = null;
467 		switch (bfProps.getAnimateType()) {
468 		case AnimateEventMove.ANIMATE_FLASHER:
469 			am = new MoveFlasher();
470 			break;
471 		case AnimateEventMove.ANIMATE_SPLINE2D:
472 			am = new MoveSpline2D();
473 			break;
474 		}
475 		return(am);
476 	}
477 
478 	/** Get a new AnimateMove object depending on the user's preference 
479 	 * of which kind to use.
480 	 * @return The new AnimateEventMove
481 	 */
482 	private	AnimateEventDouble createAnimateEventDouble() {
483 		AnimateEventDouble ad = null;
484 		switch (bfProps.getAnimateType()) {
485 		case AnimateEventMove.ANIMATE_FLASHER:
486 // XXX:		ad = new DoubleFlasher();
487 			ad = new DoubleSpline2D();	// XXX: Temp!
488 			break;
489 		case AnimateEventMove.ANIMATE_SPLINE2D:
490 			ad = new DoubleSpline2D();
491 			break;
492 		}
493 		return(ad);
494 	}
495 
496 	/** Queue this event for execution.  If the queue isn't running, then start it
497 	 * @param am The Event to queue
498 	 */
499 	private void queueEvent(AnimateEvent am) {
500 		//boolean start = accessEventList(EventListCommand.head, null, null) == null;
501 		accessEventList(EventListCommand.add, am, null);
502 		if (moveTimer == null)
503 			startMoveList();
504 	}
505 	
506 ///////////////////////////////////////////////////////////////////////////////
507 ///////////////////////////////////////////////////////////////////////////////
508 ///////////////////////////////////////////////////////////////////////////////
509 ///////////////////////////////////////////////////////////////////////////////
510 	private LinkedList<AnimateEvent>	_private_eventList = new LinkedList<AnimateEvent>();
511 	private	AnimateEvent		guiEvent = null;
512 	private	Timer				moveTimer = null;
513 	private	MoveTimerTask		moveTimerTask = null;
514 	
515 	/** Commands handled by the synchronized list function below */
516 	private enum EventListCommand {
517 		head,
518 		add,
519 		remove,
520 		calcCheckers,
521 		size,
522 		removeAll
523 	}
524 	/** provide access to the cross-thread eventList.
525 	 * We perform several functions, based on {@link EventListCommand}<br>
526 	 * head = return the first element of the list.<br>
527 	 * add = queue this AnimateEvent at the end of the list.<br>
528 	 * remove = remove the head element from the list.<br>
529 	 * calcCheckers = fill out the derived "current" checkerPositions by walking the list.<br>
530 	 * size = fill out checkerPositions[0] with the size of the list.<br>
531 	 * removeAll = empty the list.
532 	 * 
533 	 * @param command The {@link EventListCommand} to execute on the _private_eventList
534 	 * @param ae The AnimateEvent to queue
535 	 * @param checkerPositions The checkerPositions to fill out
536 	 * @return Some commands return the head of the list, most return null.
537 	 */
538 	synchronized private AnimateEvent accessEventList(EventListCommand command, AnimateEvent ae, int[] checkerPositions) {
539 		switch (command) {
540 		case remove:
541 			if (_private_eventList.isEmpty())
542 				return(null);
543 			ae = _private_eventList.removeFirst();
544 			if (_private_eventList.size() == 0 && ae.isGui())
545 				guiEvent = ae;
546 			return(ae);
547 		case add:
548 			_private_eventList.add(ae);
549 			guiEvent = null;
550 			return(null);
551 		case head:
552 			if (guiEvent != null)
553 				return(guiEvent);
554 			if (_private_eventList.isEmpty())
555 				return(null);
556 			return(_private_eventList.getFirst());
557 		case calcCheckers:
558 			int	i;
559 			if (lastBoard != null) {
560 				for (i=0; i<25; i++)		// set some default positions
561 					checkerPositions[i] = lastBoard.getPoints()[i];
562 				checkerPositions[paHOME] = lastBoard.getHome()[0]; 
563 				checkerPositions[paHOME+1] = lastBoard.getHome()[1]; 
564 				checkerPositions[paBAR] = lastBoard.getBar()[0]; 
565 				checkerPositions[paBAR+1] = lastBoard.getBar()[1]; 
566 				if (DEBUG && false) {
567 					logger.info("calcCheckers: home:" + checkerPositions[paHOME] + "-" + checkerPositions[paHOME+1]
568 					                        + " bar:" + checkerPositions[paBAR] + "-" + checkerPositions[paBAR+1]);
569 				}
570 			}
571 			for (AnimateEvent e : _private_eventList) {
572 				switch (e.getType()) {
573 				case Board:
574 					AnimateEventBoard aeb = (AnimateEventBoard)e;
575 					Board b = aeb.getBoard();
576 					int[] p = b.getPoints();
577 					for (i=0; i<25; i++)
578 						checkerPositions[i] = p[i];
579 					checkerPositions[Board.Bar] = b.getBar()[aeb.getWho()]; 
580 					checkerPositions[Board.Home] = b.getHome()[aeb.getWho()];
581 					if (DEBUG && false)
582 						logger.info("calcCheckers: home=" + checkerPositions[Board.Home]
583 						             + " from " + b.getHome()[0] + "-" + b.getHome()[1]);
584 					break;
585 				case Move:
586 					AnimateEventMove aem = (AnimateEventMove)e;
587 					if (checkerPositions[aem.pointNumberStart] < 0) {
588 						checkerPositions[aem.pointNumberStart]++;
589 						checkerPositions[aem.pointNumberEnd]--;
590 					} else {
591 						checkerPositions[aem.pointNumberStart]++;
592 						checkerPositions[aem.pointNumberEnd]--;					
593 					}
594 				}
595 			}
596 			return(null);
597 		case size:
598 			checkerPositions[0] = _private_eventList.size();
599 			return(null);
600 		case removeAll:
601 			_private_eventList.clear();
602 			return(null);
603 		}
604 		logger.error("unhandled accessEventList command: " + command.toString());
605 		return(null);
606 	}
607 ///////////////////////////////////////////////////////////////////////////////
608 ///////////////////////////////////////////////////////////////////////////////
609 ///////////////////////////////////////////////////////////////////////////////
610 ///////////////////////////////////////////////////////////////////////////////
611 	
612 	private	int		timerIncrement = 50;
613 
614 	private void startMoveList() {
615 		moveTimer = new Timer();
616 		moveTimerTask = new MoveTimerTask();
617 		moveTimer.schedule(moveTimerTask, 0, timerIncrement);
618 	}
619 
620 	private class MoveTimerTask extends TimerTask {
621 		private	double	offset = 0.0;
622 		private	int[]	queueLength = new int[1];
623 		private	double	timerIncrementD = (double)timerIncrement/1000.0;
624 
625 		@Override
626 		public void run() {
627 			
628 			AnimateEvent ae = accessEventList(EventListCommand.head, null, null);
629 			if (ae != null) {
630 				if (DEBUGqueue)
631 					logger.info("mtt: process Event: " + ae.getType().toString() + " " + ae.toString());
632 				// Figure out our time increment based on queueLength
633 				//timerIncrementD = (double)timerIncrement/1000.0;
634 				timerIncrementD = (double)timerIncrement/(double)ae.getDuration();
635 				accessEventList(EventListCommand.size, null, queueLength);
636 				if (queueLength[0] > 7)
637 					timerIncrementD *= 2;
638 				if (queueLength[0] > 10)
639 					timerIncrementD *= 2;
640 				if (queueLength[0] > 12)
641 					timerIncrementD *= 1.2;
642 				
643 				// Is this a board?  Special handling
644 				if (ae.getType() == AnimateEvent.Type.Board) {
645 					AnimateEventBoard aeb = (AnimateEventBoard)ae;
646 					lastBoard = aeb.getBoard();
647 					boardPane.setBoard(lastBoard);
648 					accessEventList(EventListCommand.remove, null, null);
649 					if (DEBUGqueue)
650 						logger.info("mtt: remove EventBoard: " + aeb.getType().toString());
651 					offset = 0.0;
652 					for (int i=0; i<extraCheckers.length; i++)
653 						extraCheckers[i] = 0;
654 					extraCheckersIndex = 0;
655 					boardPane.updateBoardTab();
656 				} else {
657 					ae.setOffset(offset);
658 					switch (ae.getType()) {
659 					case Move:
660 						AnimateEventMove am = (AnimateEventMove)ae; 
661 						if (offset == 0.0) {
662 							extraCheckersWho = am.getWho();
663 							boolean extraFound = false;
664 							for (int i=0; i<extraCheckersIndex; i+=2) {
665 								if (extraCheckers[i] == am.pointNumberStart) {
666 									extraFound = true;
667 									for (int j=i; j<extraCheckersIndex; j+=2) {
668 										extraCheckers[j] = extraCheckers[j+2];
669 										extraCheckers[j+1] = extraCheckers[j+3];
670 									}
671 									extraCheckersIndex -= 2;
672 									break;
673 								}
674 							}
675 							if (!extraFound && lastBoard != null) {
676 								if (lastBoard.getPoints()[am.pointNumberStart] < 0)
677 									lastBoard.getPoints()[am.pointNumberStart]++;
678 								else
679 									lastBoard.getPoints()[am.pointNumberStart]--;
680 								if (am.pointNumberStart == Board.Bar)
681 									lastBoard.getBar()[ae.getWho()]--;
682 							}
683 	
684 						}
685 						break;
686 					case Double:
687 						AnimateEventDouble aed = (AnimateEventDouble)ae; 
688 						if (offset == 0.0) {
689 							aed.calculate(lastBoard);
690 						}
691 						break;
692 					case Roll:
693 						if (lastBoard != null) {			// clear out any dice in the board during roll
694 							int[][] d = lastBoard.getDice();
695 							d[Board.X][0] = 0;
696 							d[Board.X][1] = 0;
697 							d[Board.O][0] = 0;
698 							d[Board.O][1] = 0;
699 						}
700 						break;
701 					case PleaseMove:
702 						AnimateEventPleaseMove aepm = (AnimateEventPleaseMove) ae;
703 						if (lastBoard != null) {
704 							int[][] d = lastBoard.getDice();
705 							d[Board.O][0] = aepm.getDice()[0]; 
706 							d[Board.O][1] = aepm.getDice()[1];
707 							boardGui.yourMove(aepm.getCheckersToMove());
708 							boardGui.playSound(AudioManager.Cue.YourTurn);
709 							if (DEBUG)
710 								logger.info("******DING YOUR TURN dice=" + d[Board.O][0] + "-" + d[Board.O][1]
711 								             + " who=" + aepm.getWho() + " offset=" + aepm.offset);
712 						}
713 						offset = 1.0;
714 						break;
715 					case PleaseRollOrDouble:
716 						if (lastBoard != null) {
717 							lastBoard.setYourTurnToRollOrDouble(true);
718 							boardGui.playSound(AudioManager.Cue.RollOrDouble);
719 						}
720 						offset = 1.0;
721 						break;
722 					case PleaseAcceptOrRejectDouble:
723 						if (lastBoard != null) {
724 							lastBoard.setAcceptDeclineDouble();
725 							boardGui.playSound(AudioManager.Cue.Doubled);
726 						}
727 						offset = 1.0;
728 						break;
729 					case PleaseAcceptOrRejectResign:
730 						AnimateEventPleaseAcceptOrRejectResign ar = (AnimateEventPleaseAcceptOrRejectResign)ae; 
731 						if (lastBoard != null) {
732 							if (ar.getWho() != Board.O)
733 								lastBoard.setAcceptDeclineResign(true);
734 							lastBoard.setWhoIsResigning(ar.getWho());
735 							lastBoard.setResigningPoints(ar.getResigningPoints());
736 							boardGui.playSound(AudioManager.Cue.Doubled);
737 						}
738 						offset = 1.0;
739 						break;
740 					case RejectResign:
741 						if (lastBoard != null) {
742 							lastBoard.setAcceptDeclineResign(false);
743 						}
744 						break;
745 					case FirstRoll:
746 						AnimateEventFirstRoll aefr = (AnimateEventFirstRoll)ae;
747 						if (lastBoard == null) {
748 							lastBoard = new Board();
749 							lastBoard.setPlayerName(Board.O, aefr.getWhitePlayer());
750 							lastBoard.setPlayerName(Board.X, aefr.getBlackPlayer());
751 						}
752 						break;
753 					}
754 					/////////////////////////////////////////////////////////////////
755 					// Repaint the board and update
756 					boardPane.repaint();
757 					offset += timerIncrementD;
758 					//logger.info("event:" + ae + " "+ ae.getType().toString() + " offset=" + offset + " timerD=" + timerIncrementD);
759 
760 					// If this event is terminating, maybe special handling.
761 					if (offset > 1.0) {
762 						switch (ae.getType()) {
763 						case Move:
764 							AnimateEventMove am = (AnimateEventMove)ae; 
765 							extraCheckers[extraCheckersIndex++] = am.pointNumberEnd;
766 							extraCheckers[extraCheckersIndex++] = am.checkerNumberEnd;
767 							if (lastBoard != null) {			// clear out any dice in the board after move
768 								int[][] d = lastBoard.getDice();
769 								d[Board.X][0] = 0;
770 								d[Board.X][1] = 0;
771 								d[Board.O][0] = 0;
772 								d[Board.O][1] = 0;
773 							}
774 							break;
775 						case Roll:
776 							AnimateEventDiceRoll ar = (AnimateEventDiceRoll)ae;
777 							if (lastBoard != null) {			// restore the dice on the board so we don't flash the screen
778 								int[][] d = lastBoard.getDice();
779 								if (ar.getWho() == Board.X) {
780 									d[Board.X][0] = ar.dice[0];
781 									d[Board.X][1] = ar.dice[1];
782 								} else {
783 									d[Board.O][0] = ar.dice[0];
784 									d[Board.O][1] = ar.dice[1];
785 								}
786 							}
787 							break;
788 						case CantMove:
789 							if (lastBoard != null) {			// clear out any dice in the board after cantmove
790 								int[][] d = lastBoard.getDice();
791 								d[Board.X][0] = 0;
792 								d[Board.X][1] = 0;
793 								d[Board.O][0] = 0;
794 								d[Board.O][1] = 0;
795 							}
796 							break;
797 						}
798 						AnimateEvent aer = accessEventList(EventListCommand.remove, null, null);
799 						offset = 0.0;
800 						if (DEBUGqueue)
801 							logger.info("mtt: remove Event: " + aer.getType().toString());
802 					}
803 				}
804 			}
805 
806 			// if there are no events in the list, then shut down the timer.
807 			int[] i = new int[1];
808 			accessEventList(EventListCommand.size, null, i);
809 			if (i[0] == 0) {
810 				if (DEBUGqueue)
811 					logger.info("mtt: terminate timer");
812 				if (moveTimer != null)
813 					moveTimer.cancel();
814 				if (moveTimerTask != null)
815 					moveTimerTask.cancel();
816 				moveTimer = null;
817 				moveTimerTask = null;
818 				return;
819 			}
820 		}
821 	}
822 }