ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/jsr166/jsr166/src/test/loops/Microscope.java
Revision: 1.13
Committed: Thu Jan 15 18:34:19 2015 UTC (9 years, 3 months ago) by jsr166
Branch: MAIN
Changes since 1.12: +0 -31 lines
Log Message:
delete extraneous blank lines

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