ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/jsr166/jsr166/src/test/loops/Microscope.java
Revision: 1.3
Committed: Mon Sep 27 19:15:15 2010 UTC (13 years, 7 months ago) by jsr166
Branch: MAIN
Changes since 1.2: +2 -4 lines
Log Message:
use blessed declaration modifier order

File Contents

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