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

# User Rev Content
1 dl 1.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     }