ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/jsr166/jsr166/src/test/loops/Microscope.java
Revision: 1.17
Committed: Thu Sep 15 06:45:56 2016 UTC (7 years, 7 months ago) by jsr166
Branch: MAIN
CVS Tags: HEAD
Changes since 1.16: +2 -2 lines
Log Message:
whitespace

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