ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/jsr166/jsr166/src/test/loops/Microscope.java
Revision: 1.1
Committed: Sun Sep 19 12:55:37 2010 UTC (13 years, 7 months ago) by dl
Branch: MAIN
Log Message:
Add and update FJ and Queue tests

File Contents

# Content
1 /*
2 * Written by Doug Lea with assistance from members of JCP JSR-166
3 * Expert Group and released to the public domain, as explained at
4 * http://creativecommons.org/licenses/publicdomain
5 */
6
7 import java.awt.*;
8 import javax.swing.*;
9 import java.util.*;
10 import java.awt.event.*;
11 import javax.swing.event.*;
12 import java.util.concurrent.*;
13
14
15 /**
16 * Microscope implements a version of the 7th Guest
17 * game found looking in the Microscope in the laboratory.
18 * See <a href="http://gee.cs.oswego.edu/dl/applets/micro.html">
19 * Microscope</a> version for instructions.
20 * <p>
21 * The code has been mangled beyond recognition
22 * as a test of ForkJoin
23 **/
24
25 public class Microscope extends JPanel {
26
27 static final CountDownLatch cd = new CountDownLatch(1);
28 /*
29 * If true, the move finder uses a repeatable evaluation
30 * strategy, so all self-play games at same level have same outcome.
31 * This is useful for testing purposes, but much less fun to watch.
32 */
33
34 static boolean DETERMINISTIC = false;
35
36 static boolean headless = false;
37
38 // Command-line parameters
39
40 static int nprocs;
41 static int lookAheads = 4;
42 static boolean autostart = true;
43 static ForkJoinPool group;
44 static long startTime;
45
46 public static void main(String[] args) throws Exception {
47 nprocs = 0;
48 try {
49 if (args.length > 0) {
50 nprocs = Integer.parseInt(args[0]);
51 autostart = false;
52 if (args.length > 1) {
53 autostart = true;
54 lookAheads = Integer.parseInt(args[1]);
55 DETERMINISTIC = true;
56 }
57 }
58 }
59 catch (Exception e) {
60 System.out.println("Usage: java Microscope <threads> [<level>]");
61 return;
62 }
63 group = nprocs == 0 ? new ForkJoinPool() : new ForkJoinPool(nprocs);
64 startTime = System.currentTimeMillis();
65 System.out.print("Score: ");
66 oneRun();
67 cd.await();
68 long now = System.currentTimeMillis();
69 System.out.println();
70 System.out.println("time: " + (now - startTime) + "ms");
71 System.out.println(group.toString());
72 System.exit(0);
73 }
74
75 static void onExit() {
76 long now = System.currentTimeMillis();
77 System.out.println("time: " + (now - startTime) + "ms");
78 System.out.println(group.toString());
79 System.exit(0);
80 }
81
82 static void oneRun() {
83 if (java.awt.GraphicsEnvironment.isHeadless()) {
84 headless = true;
85 Microscope t = new Microscope();
86 t.init();
87 }
88 else {
89 JFrame frame = new JFrame();
90 frame.addWindowListener(new WindowAdapter() {
91 public void windowClosing(WindowEvent e) {onExit();}});
92
93 Microscope t = new Microscope();
94 frame.setSize(new Dimension(400, 400));
95 frame.getContentPane().add(t);
96 // frame.pack();
97 frame.setVisible(true);
98 t.init();
99 }
100 }
101
102 // representations:
103
104 Board board = new Board(); // The current board representation
105
106 synchronized Board getBoard() { return board; }
107 synchronized void setBoard(Board b) {
108 board = b;
109 System.out.print(b.score(player) + " ");
110 if (boardPanel != null)
111 boardPanel.repaint();
112 }
113
114 Player player = Player.Blue; // current player (BLUE, GREEN)
115
116 synchronized Player getPlayer() { return player; }
117 synchronized void setPlayer(Player p) { player = p; }
118
119
120 final AutoMover auto; // The move finder.
121 final User user; // Mover for user moves
122 Mover mover = null; // the current Mover (always == auto or user or null)
123
124 synchronized Mover getMover() { return mover; }
125 synchronized void setMover(Mover m) { mover = m; }
126 synchronized boolean isMoving() { return mover != null; }
127
128 Vector<Move> history = new Vector<Move>(); // List of completed moves;
129
130 boolean demoMode = true;
131 synchronized boolean getDemoMode() { return demoMode; }
132 synchronized void setDemoMode(boolean b) { demoMode = b; }
133 synchronized boolean toggleDemoMode() { return demoMode = !demoMode; }
134
135 final BoardPanel boardPanel;
136
137 JLabel scoreLabel;
138 JButton autoButton;
139 JButton undoButton;
140 JButton modeButton;
141 JSlider levelSlider;
142
143 public Microscope() {
144 if (headless) {
145 boardPanel = null;
146 auto = new AutoMover(this);
147 user = new User(this);
148 return;
149 }
150 boardPanel = new BoardPanel();
151 scoreLabel = new JLabel("Score: 0");
152 autoButton = new JButton(" Start ");
153 undoButton = new JButton("Undo");
154 modeButton = new JButton("Demo mode");
155 levelSlider = new JSlider(JSlider.VERTICAL, 2, 6, lookAheads);
156
157 auto = new AutoMover(this);
158 user = new User(this);
159
160 JPanel topPanel = new JPanel();
161 autoButton.addActionListener(new ActionListener() {
162 public void actionPerformed(ActionEvent e) {
163 if (!isMoving()) {
164 startMover(auto);
165 autoButton.setText("Cancel");
166 }
167 else {
168 stopMover();
169 if (getDemoMode())
170 autoButton.setText(" Start ");
171 else
172 autoButton.setText(" Find ");
173 }
174 }});
175
176 modeButton.addActionListener(new ActionListener() {
177 public synchronized void actionPerformed(ActionEvent e) {
178 toggleDemoMode();
179 updateStatus();
180
181 }});
182
183 undoButton.addActionListener(new ActionListener() {
184 public void actionPerformed(ActionEvent e) {
185 undo();
186 }});
187
188 levelSlider.addChangeListener(new ChangeListener() {
189 public void stateChanged(ChangeEvent e) {
190 setLevel(((JSlider)(e.getSource())).getValue());
191 }});
192
193 // Dimension labDim = new Dimension(40, 16);
194 // Dimension labDim = new Dimension(72, 24);
195 // scoreLabel.setMinimumSize(labDim);
196 // scoreLabel.setPreferredSize(labDim);
197
198
199 topPanel.add(autoButton);
200 topPanel.add(modeButton);
201 topPanel.add(undoButton);
202 topPanel.add(scoreLabel);
203 add(topPanel);
204
205
206 levelSlider.setLabelTable(levelSlider.createStandardLabels(1));
207 levelSlider.setPaintLabels(true);
208
209 JPanel botPanel = new JPanel();
210
211 botPanel.add(boardPanel);
212 JPanel sliderPanel = new JPanel();
213 sliderPanel.setLayout(new BoxLayout(sliderPanel, BoxLayout.Y_AXIS));
214 sliderPanel.add(levelSlider);
215 sliderPanel.add(new JLabel("Level"));
216
217 botPanel.add(sliderPanel);
218
219 add(botPanel);
220 }
221
222 void initializeBoard() {
223 board.reset();
224 board.occupy(Player.Blue, 0, 0);
225 board.occupy(Player.Blue, Board.RANKS-1, Board.RANKS-1);
226 board.occupy(Player.Green, 0, Board.RANKS-1);
227 board.occupy(Player.Green, Board.RANKS-1, 0);
228 setPlayer(Player.Blue);
229 if (boardPanel != null)
230 boardPanel.repaint();
231 }
232
233 public void init() {
234 initializeBoard();
235 if (autostart) {
236 startMover(auto);
237 }
238 }
239
240
241 synchronized void setLevel(int l) {
242 lookAheads = l;
243 if (lookAheads <= 1) lookAheads = 2;
244 }
245
246 public int level () { return Microscope.lookAheads; }
247
248
249 // process a move (called only from mover)
250
251 public void move(Move m, Mover mvr) {
252 if (mvr != mover ||
253 m == null ||
254 (mvr == user && !m.isLegal())) {
255 setMover(null);
256 if (mvr == auto && autostart) {
257 cd.countDown();
258 return; // System.exit(0);
259 }
260 }
261 else {
262 m.commit();
263 setBoard(m.board());
264 setPlayer(m.player().opponent());
265
266 history.addElement(m);
267
268 if (mvr == auto &&
269 getDemoMode() &&
270 !m.isPass()) {
271 if (getBoard().gameOver()) {
272 if (autostart) {
273 cd.countDown();
274 return; // System.exit(0);
275 }
276 else
277 setMover(null);
278 }
279 else {
280 auto.startTurn(new Board(getBoard()), getPlayer());
281 }
282 }
283 else {
284 setMover(null);
285 }
286 }
287 }
288
289 // start up a Mover
290 void startMover(Mover m) {
291 Mover mvr = getMover();
292 if (mvr == null) {
293 setMover(m);
294 m.startTurn(new Board(getBoard()), player);
295 }
296 }
297
298 // stop current Mover
299 void stopMover() {
300 Mover mvr = getMover();
301 if (mvr != null) {
302 setMover(null);
303 mvr.cancel();
304 }
305 }
306
307
308 // handle Undo button
309 synchronized void undo() {
310 if (mover == null) {
311 if (history.size() > 1) {
312 history.removeElementAt(history.size()-1);
313 Move m = history.lastElement();
314 setPlayer(m.player().opponent());
315 setBoard(m.board());
316 }
317 else if (history.size() == 1) {
318 history.removeAllElements();
319 initializeBoard();
320 }
321 }
322 }
323
324 // handle click on tile
325 void userMove(int row, int col) {
326 startMover(user);
327 user.choose(row, col);
328 }
329
330 void updateStatus() { // normally called from board update
331 Player p = getPlayer();
332 int s = getBoard().score(p);
333 if (scoreLabel == null) {
334 System.out.print(s + " ");
335 return;
336 }
337
338 scoreLabel.setForeground(displayColor(p));
339 scoreLabel.setText("Score: " + s);
340
341 if (getDemoMode())
342 modeButton.setText("Demo mode");
343 else {
344 if (getPlayer().isBlue())
345 modeButton.setText("Blue turn");
346 else
347 modeButton.setText("Green turn");
348 }
349
350 }
351
352
353 static final int CELL_SIZE = 40; // size of a tile/cell
354
355 static final Color paleGreen = new Color(152, 251, 152);
356 static final Color darkGreen = new Color(60, 179, 113);
357
358 static final Color possibleMoveColor = Color.yellow;
359
360
361 public static Color displayColor(Player pl) {
362 if (pl.isBlue()) return Color.blue;
363 else if (pl.isGreen()) return darkGreen;
364 else return Color.white;
365 }
366
367 public static Color lightDisplayColor(Player pl) {
368 if (pl.isBlue()) return Color.cyan;
369 else if (pl.isGreen()) return paleGreen;
370 else return Color.gray;
371 }
372
373
374 class BoardPanel extends Canvas implements MouseListener {
375
376 BoardPanel() {
377 setSize(new Dimension(Board.RANKS * CELL_SIZE + 5,
378 Board.RANKS * CELL_SIZE + 5));
379 addMouseListener(BoardPanel.this);
380 }
381
382 public void paint(Graphics g) {
383
384 Board b = getBoard();
385 Player p = getPlayer();
386
387 // the cells
388 for (int row = 0; row < Board.RANKS; row++) {
389 for (int col = 0; col < Board.RANKS; col++) {
390
391 // Highlight selected tile and legal destinations
392 if (user.placing()) {
393 if (user.hasMovedFrom(row, col))
394 g.setColor(lightDisplayColor(p));
395 else if (user.canMoveTo(row, col))
396 g.setColor(possibleMoveColor);
397 else
398 g.setColor(displayColor(b.occupant(row, col)));
399 }
400
401 else
402 g.setColor(displayColor(b.occupant(row, col)));
403
404 // tiles are just filled rectangles
405 g.fillRect(row * CELL_SIZE, col * CELL_SIZE, CELL_SIZE, CELL_SIZE);
406 }
407 }
408
409 // the grid over the cells
410 g.setColor(Color.black);
411 for ( int i = 0; i <= Board.RANKS; i++) {
412 g.drawLine(0, i * CELL_SIZE, Board.RANKS * CELL_SIZE, i * CELL_SIZE);
413 g.drawLine(i * CELL_SIZE, 0, i * CELL_SIZE, Board.RANKS * CELL_SIZE);
414 }
415
416 updateStatus();
417 }
418
419 public void mouseReleased(MouseEvent evt) {
420
421 int x = evt.getX();
422 int y = evt.getY();
423
424 int row = x / CELL_SIZE;
425 int col = y / CELL_SIZE;
426
427 if (Board.inBounds(row, col)) { // cell selection
428 userMove(row, col);
429 repaint();
430 }
431 }
432
433 public void mouseClicked(MouseEvent e) {}
434 public void mousePressed(MouseEvent e) {}
435 public void mouseEntered(MouseEvent e) {}
436 public void mouseExited(MouseEvent e) {}
437 }
438
439 /**
440 * Player is just a glorified enumeration
441 **/
442
443 static final class Player {
444
445 public static final int EMPTY = 0;
446 public static final int BLUE = 1;
447 public static final int GREEN = 2;
448 public static final int ILLEGAL_PLAYER_VALUE = 3;
449
450 public static final Player Empty = new Player(EMPTY);
451 public static final Player Blue = new Player(BLUE);
452 public static final Player Green = new Player(GREEN);
453 public static final Player Illegal = new Player(ILLEGAL_PLAYER_VALUE);
454
455 /* private */ int code_;
456
457 public Player(int code) { code_ = code; }
458 public Player(Player p) { code_ = p.code_; }
459
460 public boolean same(Player p) { return code_ == p.code_; }
461
462 public boolean isEmpty() { return code_ == EMPTY; }
463 public boolean isBlue() { return code_ == BLUE; }
464 public boolean isGreen() { return code_ == GREEN; }
465 public boolean isLegal() { return code_ <= GREEN; }
466
467 public Player opponent() {
468 if (code_ == GREEN) return Blue;
469 else if (code_ == BLUE) return Green;
470 else return Illegal;
471 }
472
473 }
474
475 /**
476 * Board configurations are represented by bit vectors.
477 * Since there are only 49 cells, the bits can be held in `longs',
478 * one for each player.
479 * <p>
480 * Boards are not immutable, but are never passed around across
481 * threads (instead new ones are constructed), so don't
482 * need any synch.
483 **/
484
485 static final class Board {
486
487 /*
488 First, some Constants and utilities that might as well be here
489 */
490
491 public static final int RANKS = 7;
492 public static final int CELLS = RANKS * RANKS;
493
494 static final long FULL = (1L << CELLS) - 1;
495
496 // The finder uses a spare bit to remember whose move it is.
497 static final long BLUEBIT = (1L << CELLS);
498
499 // Bits representing the adjacent cells for every position
500 static final long[] adjacentMasks = new long[CELLS];
501
502 // bit pattern associated with each tile
503 static final long[] cellBits = new long[CELLS];
504
505 // locations of all cells reachable by a jump for every position
506 static final byte[][] jumpDestinations = new byte[CELLS][];
507
508 // initialize tables
509 static {
510 byte[] dests = new byte[CELLS];
511 for (int j = 0; j < RANKS; ++j) {
512 for (int i = 0; i < RANKS; ++i) {
513 int k = i + j * RANKS;
514 long nmask = 0;
515 int jumpCount = 0;
516 for (int c = j-2; c <= j+2; ++c) {
517 for (int r = i-2; r <= i+2; ++r) {
518 if (c >= 0 && c < RANKS &&
519 r >= 0 && r < RANKS) {
520 int cellIndex = r + c * RANKS;
521 if (r == i-2 || r == i+2 || c == j-2 || c == j+2) {
522 dests[jumpCount++] = (byte)cellIndex;
523 }
524 else if (!(r == i && c == j)) {
525 nmask |= 1L << cellIndex;
526 }
527 }
528 }
529 }
530 adjacentMasks[k] = nmask;
531 cellBits[k] = 1L << k;
532 jumpDestinations[k] = new byte[jumpCount];
533 for (int l = 0; l < jumpCount; ++l)
534 jumpDestinations[k][l] = dests[l];
535
536 }
537 }
538
539 }
540
541
542 public static boolean inBounds(int row, int col) {
543 return (0 <= row) && (row < RANKS) && (0 <= col) && (col < RANKS);
544 }
545
546 // The representation
547
548 long blue_; // bit vector; true if occupied by blue
549 long green_; // same for green;
550
551 // constructors and intializers:
552
553 public Board() { blue_ = 0L; green_ = 0L; }
554 public Board(Board b) { blue_ = b.blue_; green_ = b.green_; }
555 public Board(long b, long g) { blue_ = b; green_ = g; }
556
557 public void copyState(Board b) {
558 blue_ = b.blue_;
559 green_ = b.green_;
560 }
561
562 void reset() {
563 blue_ = 0L; green_ = 0L;
564 }
565
566 long getBlue() { return blue_; }
567 long getGreen() { return green_; }
568
569
570 public Player occupant(int row, int col) {
571 if ((0 <= row) && (row < RANKS) && (0 <= col) && (col < RANKS)) {
572 long m = 1L << (row + col * RANKS);
573 if ((blue_ & m) != 0L) return Player.Blue;
574 else if ((green_ &m) != 0L) return Player.Green;
575 else return Player.Empty;
576 }
577 else
578 return Player.Illegal;
579 }
580
581
582 // place a tile without taking opponent tiles
583
584 public void occupy(Player player, int row, int col) {
585 long m = 1L << (row + col * RANKS);
586 long nm = ~m;
587 if (player.code_ == Player.BLUE) {
588 blue_ |= m;
589 green_ &= nm;
590 }
591 else if (player.code_ == Player.GREEN) {
592 blue_ &= nm;
593 green_ |= m;
594 }
595 else {
596 blue_ &= nm;
597 green_ &= nm;
598 }
599 }
600
601 public void unoccupy(int row, int col) {
602 long nm = ~(1L << (row + col * RANKS));
603 blue_ &= nm;
604 green_ &= nm;
605 }
606
607
608
609 // place a tile, taking all adjacent tiles of opponent
610
611 public void take(Player player, int row, int col) {
612 int k = (row + col * RANKS);
613 long dest = 1L << k;
614 long nbrMask = adjacentMasks[k];
615 long sourceBlue = blue_;
616 long sourceGreen = green_;
617 if (player.code_ == Player.BLUE) {
618 blue_ = sourceBlue | dest | (sourceGreen & nbrMask);
619 green_ = sourceGreen & ~(sourceGreen & nbrMask);
620 }
621 else {
622 blue_ = sourceBlue & ~(sourceBlue & nbrMask);
623 green_ = sourceGreen | dest | (sourceBlue & nbrMask);
624 }
625 }
626
627 public boolean gameOver() {
628 return
629 (((blue_ | green_) & FULL) == FULL) ||
630 ((blue_ & ~BLUEBIT) == 0) ||
631 ((green_ & ~BLUEBIT) == 0);
632 }
633
634
635 public int score(Player player) {
636 if (player.isBlue()) {
637 return score(blue_, green_);
638 }
639 else {
640 return score(green_, blue_);
641 }
642 }
643
644 static int score(long b, long g) {
645
646 // much faster by splitting into ints
647 // and using clever shift-based bit counter
648
649 int lb = (int)(b & ((1L << 32) - 1));
650 int hb = ((int)(b >>> 32)) & ((1 << (CELLS - 32)) - 1);
651
652 lb -= (0xaaaaaaaa & lb) >>> 1;
653 lb = (lb & 0x33333333) + ((lb >>> 2) & 0x33333333);
654 lb = lb + (lb >>> 4) & 0x0f0f0f0f;
655 lb += lb >>> 8;
656 lb += lb >>> 16;
657
658 hb -= (0xaaaaaaaa & hb) >>> 1;
659 hb = (hb & 0x33333333) + ((hb >>> 2) & 0x33333333);
660 hb = hb + (hb >>> 4) & 0x0f0f0f0f;
661 hb += hb >>> 8;
662 hb += hb >>> 16;
663
664 hb = ((lb + hb) & 0xff);
665
666 int lg = (int)(g & ((1L << 32) - 1));
667 int hg = ((int)(g >>> 32)) & ((1 << (CELLS - 32)) - 1);
668
669 lg -= (0xaaaaaaaa & lg) >>> 1;
670 lg = (lg & 0x33333333) + ((lg >>> 2) & 0x33333333);
671 lg = lg + (lg >>> 4) & 0x0f0f0f0f;
672 lg += lg >>> 8;
673 lg += lg >>> 16;
674
675 hg -= (0xaaaaaaaa & hg) >>> 1;
676 hg = (hg & 0x33333333) + ((hg >>> 2) & 0x33333333);
677 hg = hg + (hg >>> 4) & 0x0f0f0f0f;
678 hg += hg >>> 8;
679 hg += hg >>> 16;
680
681 return hb - ((lg + hg) & 0xff);
682 }
683
684
685
686 static int slowscore(long b, long g) {
687 int score = 0;
688 for (int l = 0; l < CELLS; ++l) {
689 score += (int)(b & 1);
690 b >>>= 1;
691 score -= (int)(g & 1);
692 g >>>= 1;
693 }
694 return score;
695 }
696
697
698 }
699
700 /**
701 * Moves represent transitions across Board states
702 **/
703
704
705 static final class Move {
706
707 static final int NO_VALUE = -1; // row/col value if not yet set
708 static final int PASS_VALUE = -2; // special value for pass moves
709
710 // utilities for classifying moves
711
712 public static boolean twoFrom(int a, int b) {
713 return (a - b == 2) || (b - a == 2);
714 }
715
716 public static boolean withinTwo(int a, int b) {
717 int diff = a - b; return -2 <= diff && diff <= 2;
718 }
719
720 // representations
721
722 int fromRow;
723 int fromCol;
724
725 int toRow;
726 int toCol;
727
728 Player player_;
729 Board board_;
730
731 boolean committed = false; // true if board reflects move
732
733 // constructors and intializers
734
735 public Move(Player turn, Board board) {
736 fromRow = NO_VALUE; fromCol = NO_VALUE;
737 toRow = NO_VALUE; toCol = NO_VALUE;
738 player_ = turn;
739 board_ = board;
740 }
741
742 public Move(Player turn, Board board, boolean isCommitted) {
743 fromRow = NO_VALUE; fromCol = NO_VALUE;
744 toRow = NO_VALUE; toCol = NO_VALUE;
745 player_ = turn;
746 board_ = board;
747 committed = isCommitted;
748 }
749
750 synchronized void reset() {
751 fromRow = NO_VALUE;
752 fromCol = NO_VALUE;
753 toRow = NO_VALUE;
754 toCol = NO_VALUE;
755 }
756
757 // setters:
758
759 synchronized void player(Player p) { player_ = p; }
760 synchronized void board(Board b) { board_ = b; }
761 synchronized void from(int sr, int sc) { fromRow = sr; fromCol = sc; }
762 synchronized void to(int dr, int dc) { toRow = dr; toCol = dc; }
763
764 // accessors:
765
766 synchronized boolean isFrom(int r, int c) {
767 return fromRow== r && fromCol == c;
768 }
769 synchronized boolean isTo(int r, int c) {
770 return toRow == r && toCol == c;
771 }
772 synchronized Board board() {
773 return board_;
774 }
775 synchronized Player player() {
776 return player_;
777 }
778
779
780 // status checks:
781
782 synchronized boolean isPass() { // is this a `pass' move?
783 return (toRow == PASS_VALUE || fromRow == PASS_VALUE);
784 }
785
786 synchronized boolean isJump() {
787 return
788 (fromRow - toRow == 2) || (toRow - fromRow == 2) ||
789 (fromCol - toCol == 2) || (toCol - fromCol == 2);
790 }
791
792 synchronized boolean hasFrom() { // is from set?
793 return fromRow != NO_VALUE && fromCol != NO_VALUE;
794 }
795
796 synchronized boolean hasTo() { // is to set?
797 return toRow != NO_VALUE && toCol != NO_VALUE;
798 }
799
800
801 synchronized boolean possibleTo(int r, int c) { // is (r, c) a legal `to'?
802 return hasFrom() &&
803 withinTwo(fromRow, r) &&
804 withinTwo(fromCol, c) &&
805 board_.occupant(r, c).isEmpty();
806 }
807
808 synchronized boolean isLegal() {
809 if (isPass())
810 return true;
811 else if (!board_.occupant(toRow, toCol).isEmpty())
812 return false;
813 else if (!board_.occupant(fromRow, fromCol).same(player_))
814 return false;
815 else if (!(withinTwo(fromRow, toRow) && withinTwo(fromCol, toCol)))
816 return false;
817 else
818 return true;
819 }
820
821 synchronized void commit() { // update board to reflect move
822 if (!committed) {
823 committed = true;
824 if (isLegal() && !isPass()) {
825 if (isJump()) board_.occupy(Player.Empty, fromRow, fromCol);
826 board_.take(player_, toRow, toCol);
827 }
828 }
829 }
830
831 }
832
833 /**
834 * Mover is an abstract class to simplify code dealing with
835 * either user moves or auto moves.
836 **/
837
838
839 static abstract class Mover {
840
841 // caller for move callbacks
842 protected Microscope game;
843
844 protected Mover(Microscope ap) { game = ap; }
845
846 // start a turn as player on given board
847 public abstract void startTurn(Board b, Player p);
848
849 // cancel current partial move
850 public abstract void cancel();
851
852 // return true if move not yet ready
853 public abstract boolean placing();
854
855 }
856
857 /**
858 * User builds moves via instructions/clicks by users
859 **/
860
861 static class User extends Mover {
862
863 private Move current;
864
865 public User(Microscope ap) { super(ap); current = null; }
866
867 public synchronized void startTurn(Board b, Player p) {
868 current = new Move(p, b);
869 }
870
871 public boolean placing() {
872 return current != null && current.hasFrom() && !current.hasTo();
873 }
874
875 public synchronized void cancel() {
876 if (current != null) {
877 current.reset();
878 current = null;
879 }
880 }
881
882 public synchronized void choose(int row, int col) {
883 if (current != null) {
884 if (row == Move.PASS_VALUE) {
885 current.from(row, col);
886 game.move(current, this);
887 current = null;
888 }
889 else if (!current.hasFrom()) {
890 if (current.board().occupant(row, col).same(current.player())) {
891 current.from(row, col);
892 }
893 }
894 else {
895 current.to(row, col);
896 game.move(current, this);
897 current = null;
898 }
899 }
900 }
901
902 public synchronized boolean canMoveTo(int row, int col) {
903 return placing() && current.possibleTo(row, col);
904 }
905
906 public synchronized boolean hasMovedFrom(int row, int col) {
907 return current != null && current.isFrom(row, col);
908 }
909
910 }
911
912
913 /**
914 * AutoMover constructs Finders that compute actual moves
915 **/
916
917 static class AutoMover extends Mover {
918
919 boolean cancelled = false;
920 RootFinder currentFinder = null;
921
922 public AutoMover(Microscope ap) {
923 super(ap);
924 }
925
926
927 public synchronized boolean placing() {
928 return currentFinder != null;
929 }
930
931 synchronized void stopPlacing() {
932 currentFinder = null;
933 }
934
935
936 public synchronized void cancel() {
937 if (placing()) {
938 currentFinder.cancel(false);
939 stopPlacing();
940 }
941 }
942
943 public synchronized void startTurn(Board board, Player player) {
944 if (!placing()) {
945 currentFinder = new RootFinder(board, player,
946 Microscope.lookAheads, this);
947 group.execute(currentFinder);
948 }
949 }
950
951 synchronized void relay(Move move) { // relay callback from finder
952 if (placing()) {
953 stopPlacing();
954 game.move(move, this);
955 }
956 }
957
958 }
959
960
961 /**
962 * Implements a classic all-possible-move search algorith using
963 * ForkJoinTasks. The move finder is not all that smart. Among
964 * other possible improvements, it could keep a cache of explored
965 * moves and avoid repeating them. This would likely speed it up
966 * since most expansions are duplicates of others. It could also
967 * be changed to prune moves, although this is unlikely to work
968 * well without better partial evaluation functions.
969 **/
970
971 static class Finder extends RecursiveAction {
972
973 static final int NOMOVE = Integer.MIN_VALUE;
974 static final int LOSE = NOMOVE+1;
975 static final int WIN = -LOSE;
976
977 final long ours; // bits for our tiles
978 final long theirs; // bits for opponent tiles
979 final int level; // current number of lookAheads
980 final Finder next; // Each Finder is placed in a linked list by parent
981
982 // Assigned once; must be volatile since accessed by parents
983 volatile int bestScore;
984
985 Finder(long ours, long theirs, int level, Finder next) {
986 this.ours = ours;
987 this.theirs = theirs;
988 this.level = level;
989 this.next = next;
990 }
991
992 protected final void compute() {
993
994 // Handle sure wins and losses here
995 if ((ours & ~Board.BLUEBIT) == 0)
996 bestScore = LOSE;
997
998 else if ((theirs & ~Board.BLUEBIT) == 0)
999 bestScore = WIN;
1000
1001 else if (((ours | theirs) & Board.FULL) == Board.FULL) {
1002 int score = Board.score(ours, theirs);
1003 if (score > 0) bestScore = WIN;
1004 else if (score < 0) bestScore = LOSE;
1005 else bestScore = 0;
1006 }
1007
1008 else
1009 search();
1010 }
1011
1012
1013 final void search() {
1014 int best = NOMOVE; // For direct evaluation when level == 1
1015 Finder forked = null; // list of forked subtasks when level > 1
1016
1017 long open = ~(ours | theirs); // currently empty cells
1018 long here = 1; // travserse through bits
1019
1020 for (int k = 0; k < Board.CELLS; ++k, here <<= 1) {
1021 if ((here & ours) != 0) {
1022 /*
1023 * Step through possible destinations to find
1024 * jumps for this tile
1025 */
1026 byte[] dests = Board.jumpDestinations[k];
1027 for (int j = 0; j < dests.length; ++j) {
1028 byte d = dests[j];
1029 long dest = 1L << d;
1030
1031 if ( (dest & open) != 0) {
1032 long adjacent = Board.adjacentMasks[d];
1033 long nTheirs = theirs & ~adjacent;
1034 long nOurs = (ours & ~here) | dest | (theirs & adjacent);
1035
1036 if (level > 1)
1037 (forked =
1038 new Finder(nTheirs, nOurs, level-1, forked)).fork();
1039 else {
1040 int sc = Board.score(nOurs, nTheirs);
1041 if (sc > best) best = sc;
1042 }
1043 }
1044 }
1045 }
1046 else if ((here & open) != 0) {
1047 /*
1048 * If this cell is open, and is within 1 of one of
1049 * our tiles, it can be taken in some copy move.
1050 * It doesn't matter which of the adjacent cells
1051 * is considered to be source of copy move
1052 */
1053 long adjacent = Board.adjacentMasks[k];
1054 if ((ours & adjacent) != 0) {
1055 long nTheirs = theirs & ~adjacent;
1056 long nOurs = ours | here | (theirs & adjacent);
1057 if (level > 1)
1058 (forked = new Finder(nTheirs, nOurs, level-1, forked)).fork();
1059 else {
1060 int sc = Board.score(nOurs, nTheirs);
1061 if (sc > best) best = sc;
1062 }
1063 }
1064 }
1065 }
1066
1067 if (level > 1)
1068 collect(forked);
1069 else
1070 bestScore = best;
1071 }
1072
1073 /**
1074 * Join all subtasks and evaluate moves. Default is sub-finder version.
1075 * Overridden in RootFinder
1076 **/
1077
1078 void collect(Finder forked) {
1079 int best = NOMOVE;
1080 while (forked != null) {
1081 while (!forked.isDone()) {
1082 // interleave joins with status checks
1083 if (isDone()) {
1084 cancelAll(forked);
1085 return;
1086 }
1087 else {
1088 ForkJoinTask<?> t = pollTask();
1089 if (t != null)
1090 t.quietlyInvoke();
1091 }
1092 }
1093 int score = -forked.bestScore; // negate opponent score
1094 if (score > best) {
1095 best = score;
1096 if (score >= WIN) {
1097 cancelAll(forked.next);
1098 break;
1099 }
1100 }
1101 forked = forked.next;
1102 }
1103
1104 bestScore = best;
1105 }
1106
1107 /**
1108 * Cancel all forked subtasks in list
1109 **/
1110
1111 void cancelAll(Finder forked) {
1112 while (forked != null) {
1113 forked.cancel(false);
1114 forked = forked.next;
1115 }
1116 }
1117
1118 }
1119
1120 /**
1121 * Root Finder class -- wait out other finders and issue callback to game.
1122 **/
1123
1124 static class RootFinder extends Finder {
1125 final AutoMover automover;
1126 final Player player;
1127 RootFinder(Board board, Player p, int level, AutoMover automover) {
1128 super( (p.isBlue()? (board.getBlue()| Board.BLUEBIT) : board.getGreen()),
1129 (p.isBlue()? board.getGreen() : (board.getBlue()| Board.BLUEBIT)),
1130 level,
1131 null);
1132
1133 this.player = p;
1134 this.automover = automover;
1135 }
1136
1137
1138 /**
1139 * This differs from default version by recording
1140 * and calling back with best move
1141 **/
1142 void collect(Finder forked) {
1143 int best = NOMOVE;
1144 Finder bestFinder = null;
1145 while (forked != null) {
1146 while (!forked.isDone()) {
1147 if (isDone()) {
1148 cancelAll(forked);
1149 return;
1150 }
1151 else {
1152 ForkJoinTask<?> t = pollTask();
1153 if (t != null)
1154 t.quietlyInvoke();
1155 }
1156 }
1157 int score = -forked.bestScore; // negate opponent score
1158 if (bestFinder == null || score > best) {
1159 best = score;
1160 bestFinder = forked;
1161 if (score >= WIN) {
1162 cancelAll(forked.next);
1163 break;
1164 }
1165 }
1166
1167 // Just for fun, introduce a little randomness via hashcodes
1168 else if (score == best &&
1169 !Microscope.DETERMINISTIC &&
1170 (System.identityHashCode(forked) >
1171 System.identityHashCode(bestFinder))) {
1172 bestFinder = forked;
1173 }
1174 forked = forked.next;
1175 }
1176
1177 Move move = null;
1178 if (bestFinder != null) {
1179 /*
1180 Even though accessed here,
1181 the ours and theirs vars of Finders do not
1182 need to be volatile because they are immutably
1183 established in constructors.
1184 */
1185
1186 long nextOurs = bestFinder.theirs;
1187 long nextTheirs = bestFinder.ours;
1188 long blue = (player.isBlue())? nextOurs : nextTheirs;
1189 long green = (player.isBlue())? nextTheirs: nextOurs;
1190 move = new Move(player, new Board(blue, green), true);
1191 }
1192 automover.relay(move);
1193 }
1194 }
1195
1196
1197 }