import java.text.DecimalFormat;

import java.applet.Applet;

import java.awt.Graphics;
import java.awt.Color;
import java.awt.Component;
import java.awt.Canvas;
import java.awt.Panel;
import java.awt.GridLayout;
import java.awt.Dimension;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.Point;
import java.awt.Button;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
import java.awt.Container;
import java.awt.Insets;
import java.awt.Choice;
import java.awt.Label;
import java.awt.TextArea;
import java.awt.Font;
import java.awt.event.ItemListener;
import java.awt.event.ItemEvent;

public class Mastermind extends Applet {

  static final int STATE_NONE = 0;
  static final int STATE_INITED = 1;
  static final int STATE_PLAYING = 2;
  static final int STATE_DONE = 3;
  static final int STATE_GIVENUP = 4;
  static final int STATE_STOPPED = 5;

  static final int SPOT_SCRATCHPAD = -1;
  static final int SPOT_SECRET = -2;

  static final int COLOR_NONE = -1;

  final static int spotSize = 20;
  final static int smallSpotSize = (int) (Mastermind.spotSize * 0.3);
  final static int smallSpotMargin =
    (Mastermind.spotSize - Mastermind.smallSpotSize) / 2;

  static Color pieceColors[] = new Color[] {
    Color.black, Color.white,
      new Color(0x99, 0x33, 00), // brown
      Color.yellow,
      Color.red,
      Color.blue, Color.orange, Color.green };

  static final Color backColor = Color.gray;

  static Color normalButtonColor;

  MouseListener mouseListener;

  Button giveUp = new Button("Give Up"),
    startAgain = new Button("Restart"),
    guess = new Button("Guess");

  int currentRow;

  GridBagLayout gbl = new GridBagLayout();
  GridBagConstraints gbc = new GridBagConstraints();

  int state = Mastermind.STATE_NONE;

  int secretCode[] = new int[5];
  int secretColors[] = new int[5];
  BoardSpot secret[] = new BoardSpot[5];
  int scratchpadColors[] = new int[10];
  Spot scratchpad[] = new BoardSpot[10];
  int playColors[][] = new int[12][5];
  BoardSpot play[][] = new BoardSpot[12][5];
  Score scores[] = new Score[12];
  PaletteSpot paletteSpots[] = new PaletteSpot[8];

  AbortableLooper allRowsChecker;

  int cheatState;
  Choice cheatChoice;

  TextArea commentary;
  LazyTextArea commentaryShow;

  int guessStatus;
  GuessStatus guessStatusBulb;

  Spot dummySpot;
  Spot sourceSpot;

  public void init() {
    if (this.state != STATE_NONE) return;
    this.gbc.insets = new Insets(1, 1, 1, 1);
    this.setLayout(this.gbl);
    this.mouseListener = new MouseAdapter() {
      public void mouseClicked(MouseEvent me) {
        if (Mastermind.this.state == Mastermind.STATE_PLAYING)
          Mastermind.this.mouseClicked(me);
      }
      public void mouseEntered(MouseEvent me) {
        if (Mastermind.this.state == Mastermind.STATE_PLAYING)
          Mastermind.this.mapComponent(me.getComponent()).
            setFocused(true);
      }
      public void mouseExited(MouseEvent me) {
        if (Mastermind.this.state == Mastermind.STATE_PLAYING)
          Mastermind.this.mapComponent(me.getComponent()).
            setFocused(false);
      }
    };
    ActionListener buttonListener = new ActionListener() {
      public void actionPerformed(ActionEvent ae) {
        Mastermind.this.actionPerformed(ae);
      }
    };
    this.setBackground(Color.lightGray);
    this.gbc.gridx = 0;
    this.gbc.gridy = 1;
    this.gbc.insets.left = this.gbc.insets.right = 10;
    this.addGridBagContents(this.startAgain);
    this.gbc.gridy = 7;
    this.gbc.insets.left = this.gbc.insets.right = 10;
    this.addGridBagContents(this.guess);
    Mastermind.normalButtonColor= this.guess.getBackground();
    this.gbc.gridy = 10;
    this.gbc.insets.left = this.gbc.insets.right = 10;
    this.addGridBagContents(this.giveUp);
    this.initPalette();
    this.initScratchPad();
    this.initBoard();
    this.initCheatChoice();
    this.gbc.gridx = 11;
    this.gbc.gridy = 0;
    this.gbc.insets.left = 15;
    this.addGridBagContents(new Label("Guess:"));
    this.guessStatusBulb = new GuessStatus();
    this.gbc.anchor = GridBagConstraints.WEST;
    this.addGridBagContents(this.guessStatusBulb);
    this.commentary =
      new TextArea("Welcome to Mastermind\n" +
                   "By Rujith de Silva (rujith2@yahoo.com)\n" +
                   "2001-03-06\n\n",
                   20, 50);
    this.commentary.setFont(new Font("Dialog", Font.PLAIN, 10));
    this.commentary.setEditable(false);
    this.commentaryShow = new LazyTextArea(this.commentary);
    this.gbc.gridx = 9;
    this.gbc.gridy = 2;
    this.gbc.gridheight = GridBagConstraints.REMAINDER;
    this.gbc.gridwidth = GridBagConstraints.REMAINDER;
    this.gbc.anchor = GridBagConstraints.NORTHWEST;
    this.addGridBagContents(this.commentary);
    this.dummySpot = new DummySpot();
    this.addMouseListener(Mastermind.this.mouseListener); // XXX
    this.guess.addActionListener(buttonListener);
    this.startAgain.addActionListener(buttonListener);
    this.giveUp.addActionListener(buttonListener);

    // Create loopers
    this.allRowsChecker = new AbortableLooper() {
      public void run() throws AbortedException {
        Mastermind.this.checkAllRows();
      }
    };
    new Thread(new Runnable() {
      public void run() { Mastermind.this.analyzeProgress();
      }}).start();

    this.validate();
    this.state = Mastermind.STATE_INITED;
  }

  public void start() {
    // if (this.state == Mastermind.STATE_NONE) this.init();
    this.allRowsChecker.start();
    this.currentRow = 0;
    int i, j;
    for (i = 0; i < 12; ++i) {
      this.setRowEnabled(i, false);
      for (j = 0; j < 5; ++j) {
        this.play[i][j].reset();
      }
      this.scores[i].setResult(0, 0);
    }

    for (i = 0; i < 10; ++i) { this.scratchpad[i].setColor(-1); }

    for (i = 0; i < 5; ++i) {
      this.secretCode[i] = (int) (8.0 * Math.random());
      this.secret[i].setColor(-1);
    }
    for (i = 0; i < 8; ++i) {
      this.paletteSpots[i].reset();
    }

    this.giveUp.setEnabled(true);
    this.setRowEnabled(0, true);
    this.checkRow();
    this.commentaryShow.setLazy(this.cheatState < 2);
    this.commentaryShow.appendText("Start:  32768\n");
    this.state = Mastermind.STATE_PLAYING;
  }

  public void stop() {
    this.state = Mastermind.STATE_STOPPED;
    this.allRowsChecker.stop();
    this.commentaryShow.appendText("---------------\n");
  }

  void actionPerformed(ActionEvent ae) {
    String command = ae.getActionCommand();
    if ("Guess".equals(command)) { this.doGuess(); }
    else if ("Restart".equals(command)) { this.restart(); }
    else if ("Give Up".equals(command)) { this.giveUp(); }
  }

  void restart() {
    this.setRowEnabled(this.currentRow, false);
    this.commentaryShow.setLazy(false);
    this.commentaryShow.appendText("---------------\n");
    if (this.cheatState > 0) {
      --this.cheatState;
      this.cheatChoice.select(this.cheatState);
    }
    synchronized (this.rowChanged) {
      this.analyzableRow = -1;
      this.rescanGame = true;
      this.rowChanged.notify();
    }
    this.start();
  }

  void doGuess() {
    this.setSourceSpot(null);
    this.setRowEnabled(this.currentRow, false);
    int result = Scorer.getScore(this.secretCode,
                                 this.playColors[this.currentRow]);
    int black = result / 8;
    int white = result % 8;
    this.scores[this.currentRow].setResult(black, white);
    synchronized (this.rowChanged) {
      this.analyzableRow = this.currentRow;
      this.rowChanged.notify();
    }
    if (black != 5) {
      ++this.currentRow;
      this.setRowEnabled(this.currentRow, true);
    } else {
      this.state = Mastermind.STATE_DONE;
      this.showSecret();
    }
    this.checkRow();
    synchronized (this.rowChanged) { this.rowChanged.notify(); }
  }

  void giveUp() {
    this.setSourceSpot(null);
    this.state = Mastermind.STATE_GIVENUP;
    this.setRowEnabled(this.currentRow, false);
    this.showSecret();
  }

  void showSecret() {
    for (int i = 0; i < 5; ++i) {
      this.secret[i].forceSetColor(this.secretCode[i]);
    }
    this.guess.setEnabled(false);
    this.giveUp.setEnabled(false);
    this.commentaryShow.setLazy(false);
  }

  int guessColor = 0;
  static Color guessColors[] = new Color[] {
    Color.red, Color.green, Color.black };

  void mouseClicked(MouseEvent me) {
    // this.guessColor++;
    // this.guessColor %= 3;
    // this.guess.setForeground(Mastermind.guessColors[this.guessColor]);

    Spot target = this.mapComponent(me.getComponent());

    if (this.sourceSpot == target) return;

    if (this.sourceSpot == null || ! target.canSetColor()) {
      if (target.getColor() != COLOR_NONE) {
        this.setSourceSpot(target);
      }
      else
        this.setSourceSpot(null);
    } else {
      // Source was set previously, now target was pressed, so attempt
      // to do the move.
        int colorToMove = this.sourceSpot.grabColor();
        if (colorToMove == COLOR_NONE) {
          this.setSourceSpot(target);
        } else {
          int colorToMoveBack = target.setColor(colorToMove);
          if (colorToMoveBack != COLOR_NONE)
            this.sourceSpot.setColor(colorToMoveBack);
          this.setSourceSpot(null);
        }
    }
  }

  void setSourceSpot(Spot spot) {
    if (this.sourceSpot == spot) return;
    if (this.sourceSpot != null) {
      this.sourceSpot.setSelected(false);
    }
    if (spot != null) spot.setSelected(true);
    this.sourceSpot = spot;
  }

  Spot mapComponent(Component component) {
    if (component == null) return null;
    if (component == this) return this.dummySpot;
    return (Spot) component;
  }

  final static Dimension spotDimension =
  new Dimension(Mastermind.spotSize, Mastermind.spotSize);

  interface Spot {
    int getColor();
    boolean canSetColor();
    int setColor(int c);
    int grabColor();
    void setSelected(boolean selected);
    void setFocused(boolean focused);
  }

  class DummySpot implements Spot {
    public int getColor() { return COLOR_NONE; }
    public boolean canSetColor() { return true; }
    public int setColor(int c) { return COLOR_NONE; }
    public int grabColor() { return COLOR_NONE; }
    public void setSelected(boolean selected) { }
    public void setFocused(boolean focused) { }
  }

  abstract class AbstractSpot extends Canvas implements Spot {
    boolean focused = false, selected = false;

    AbstractSpot() {
      // this.forceSetColor(color);
      this.addMouseListener(Mastermind.this.mouseListener);
    }

    public Dimension getMinimumSize() {
      return this.getPreferredSize();
    }

    public Dimension getPreferredSize() {
      return Mastermind.spotDimension;
    }

    public abstract boolean canSetColor();
    public abstract int setColor(int c);
    public abstract void forceSetColor(int c);
    public abstract int grabColor();

    abstract public int getColor();

    public void setFocused(boolean focused) {
      if (this.focused == focused) return;
      this.focused = focused;
      this.repaint();
    }

    public void setSelected(boolean selected) {
      if (this.selected == selected) return;
      this.selected = selected;
      this.repaint();
    }

    void reset() {
      this.selected = false;
      this.focused = false;
      this.repaint();
    }
  }

  class BoardSpot extends AbstractSpot {
    int row, column;
    boolean playEnabled = false;

    BoardSpot(int row, int column) {
      this.row = row;
      this.column = column;
      this.forceSetColor(-1);
    }

    public void forceSetColor(int c) {
      switch (this.row) {
      case SPOT_SCRATCHPAD:
        Mastermind.this.scratchpadColors[this.column] = c;
        break;
      case SPOT_SECRET:
        Mastermind.this.secretColors[this.column] = c;
        break;
      default:
        Mastermind.this.playColors[this.row][this.column] = c;
        break;
      }
      this.repaint();
    }

    public int getColor() {
      switch (this.row) {
      case SPOT_SCRATCHPAD:
        return Mastermind.this.scratchpadColors[this.column];
      case SPOT_SECRET:
        return Mastermind.this.secretColors[this.column];
      default:
        return Mastermind.this.playColors[this.row][this.column];
      }
    }

    public int grabColor() {
      int t = this.getColor();
      if (t != COLOR_NONE) {
        if (this.playEnabled) {
          this.forceSetColor(COLOR_NONE);
           Mastermind.this.checkRow();
         }
         this.selected = false;
         this.repaint();
       }
       return t;
     }

     public boolean isPlayEnabled() { return this.playEnabled; }

     public void paint(Graphics g) {
       int thisColor = this.getColor();
       Color c = (thisColor == Mastermind.COLOR_NONE) ?
          Mastermind.backColor :
         Mastermind.pieceColors[thisColor];
       g.setColor(c);
       boolean showSmall = (thisColor == Mastermind.COLOR_NONE &&
                            ! this.playEnabled);
       int margin = (showSmall ? Mastermind.smallSpotMargin : 0);
       int size = (showSmall ? Mastermind.smallSpotSize :
                   Mastermind.spotSize);
       if (! showSmall) {
         boolean expand = this.selected ||
           (this.focused && (thisColor != COLOR_NONE ||
                             Mastermind.this.sourceSpot != null));
         if (! expand) {
           margin += 1;
           size -= 2;
         }
       }
       g.fillOval(margin, margin, size, size);
       if (this.selected) {
         g.setColor(Mastermind.getBorderColor(c));
         g.drawOval(margin, margin, size - 1, size - 1);
       }
       if (this.row == Mastermind.SPOT_SECRET &&
           Mastermind.this.state != Mastermind.STATE_DONE &&
           Mastermind.this.state != Mastermind.STATE_GIVENUP) {
         g.setColor(Color.black);
         g.drawLine(1, 1, Mastermind.spotSize - 2,
                    Mastermind.spotSize - 2);
         g.drawLine(1, Mastermind.spotSize - 2, Mastermind.spotSize -
                    2, 1);
       }
     }

     public boolean canSetColor() { return this.playEnabled; }

     public int setColor(int c) {
       if (! this.playEnabled && this.row >= 0)
         return COLOR_NONE;
       int oldColor = this.getColor();
       this.forceSetColor(c);
       if (this.row >= 0) Mastermind.this.checkRow();
       this.repaint();
       if (this.playEnabled) return oldColor;
       return COLOR_NONE;
     }

     void setPlayEnabled(boolean playEnabled) {
       if (this.playEnabled != playEnabled) {
         this.playEnabled = playEnabled;
         this.repaint();
       }
     }

     void reset() {
       this.forceSetColor(COLOR_NONE);
       super.reset();
     }
   }

   static final Dimension scoreDimension = new Dimension(50, 8);

   class Score extends Canvas {
     int black = 0, white = 0;

     public Dimension getMinimumSize() {
       return this.getPreferredSize();
     }

     public Dimension getPreferredSize() {
       return Mastermind.scoreDimension;
     }

     void setResult(int black, int white) {
       this.black = black;
       this.white = white;
       this.repaint();
     }

     public void paint(Graphics g) {
       int peg = 0;
       for (int i = 0; i < this.black; ++i) {
         this.paintPeg(g, peg++, true);
       }
       for (int i = 0; i < this.white; ++i) {
         this.paintPeg(g, peg++, false);
       }
       g.setColor(Color.black);
       for (; peg < 5; ++peg) {
         g.drawOval(peg * 10 + 4, 3, 2, 2);
       }
     }

     void paintPeg(Graphics g, int peg, boolean isBlack) {
       g.setColor(isBlack ? Color.black : Color.white);
       g.fillOval(peg * 10 + 1, 0, 8, 8);
     }
   }

   void initBoard() {
     int i, j;
     this.gbc.gridx = 3;
     this.gbc.gridy = 0;
     for (i = 0; i < 5; ++i) {
       BoardSpot spot = new BoardSpot(SPOT_SECRET, i);
       this.gbc.insets.top = this.gbc.insets.bottom = 10;
       this.addGridBagContents(spot);
       this.secret[i] = spot;
     }

     i = 12;
     while (i-- > 0) {
       for (j = 0; j < 5; ++j) {
         BoardSpot spot = new BoardSpot(i, j);
         this.gbc.gridx = 3 + j;
         this.gbc.gridy = 12 - i;
         this.gbc.insets.top = this.gbc.insets.bottom = 3;
         this.addGridBagContents(spot);
         this.play[i][j] = spot;
       }
       Score score = new Score();
       this.scores[i] = score;
       // this.gbc.gridwidth = GridBagConstraints.REMAINDER;
       this.gbc.gridx = 8;
       this.gbc.gridy = 12 - i;
       this.gbc.insets.left = this.gbc.insets.right = 10;
       this.addGridBagContents(score);
     }
   }

   void initCheatChoice() {
     this.gbc.gridx = 9;
     this.gbc.gridy = 0;
     this.addGridBagContents(new Label("Cheat:"));
     final Choice choice = new Choice();
     choice.addItem("Not at all");
     choice.addItem("A little bit");
     choice.addItem("Somewhat");
     choice.addItem("A lot");
     this.gbc.gridx = 10;
     this.gbc.gridy = 0;
     // this.gbc.weightx = 1.0;
     this.gbc.anchor = GridBagConstraints.WEST;
     this.gbc.insets.right = 15;
     this.addGridBagContents(choice);
     ItemListener il = new ItemListener() {
       public void itemStateChanged(ItemEvent ie) {
         Mastermind.this.cheatStateChanged
           (choice.getSelectedIndex());
       }
     };
     this.cheatChoice = choice;
     choice.addItemListener(il);
   }

   void cheatStateChanged(int cheatState) {
     if (this.cheatState == cheatState) return;
     this.cheatState = cheatState;
     if (cheatState == 0)
       this.setGuessStatus(1);
     else
       this.allRowsChecker.abort();
     this.commentaryShow.setLazy(cheatState < 2);
   }

   void setRowEnabled(int row, boolean enabled) {
     if (row < 0 || row >= 12) return;
     for (int i = 0; i < 5; ++i) {
       this.play[row][i].setPlayEnabled(enabled);
     }
   }

   void checkRow() {
     if (this.state != Mastermind.STATE_PLAYING) {
       this.guess.setEnabled(false);
       this.setGuessStatus(1);
       return;
     }
     boolean allSet = true;
     int thisRow[] = this.playColors[currentRow];
     for (int i = 0; i < 5; ++i) {
       if (thisRow[i] < 0) {
         allSet = false;
         break;
       }
     }
     this.guess.setEnabled(allSet);
     if (this.cheatState >= 1) this.allRowsChecker.abort();
   }

   void initScratchPad() {
     for (int i = 0; i < 10; ++i) {
       this.gbc.gridx = 1 + ((int) i / 5);
       this.gbc.gridy = 2 + (i % 5);
       this.gbc.insets.left = 3;
       this.gbc.insets.right = ((i >= 5) ? 10 : 3);
       BoardSpot temp = new BoardSpot(SPOT_SCRATCHPAD, i);
       temp.setPlayEnabled(true);
       this.scratchpad[i] = temp;
       this.addGridBagContents(temp);
     }
   }

   void initPalette() {
     for (int i = 0; i < 8; ++i) {
       this.gbc.gridx = 1 + ((int) i/4);
       this.gbc.gridy = (i % 4) + 8;
       this.gbc.insets.left = this.gbc.insets.right = 3;
       PaletteSpot paletteSpot = new PaletteSpot(i);
       this.paletteSpots[i] = paletteSpot;
       this.addGridBagContents(paletteSpot);
     }
   }

   class PaletteSpot extends AbstractSpot {
     int color;

     PaletteSpot(int color) {
       this.forceSetColor(color);
     }

     public void forceSetColor(int c) { this.color = c; }
     public int getColor() { return this.color; }

     public void paint(Graphics g) {
       Color thisColor = Mastermind.pieceColors[this.color];
       g.setColor(thisColor);
       int margin = (this.selected || this.focused) ? 0 : 1;
       g.fillRect(margin, margin, 20 - 2 * margin, 20 - 2 * margin);
       if (this.selected) {
         g.setColor(Mastermind.getBorderColor(thisColor));
         g.drawRect(0, 0, Mastermind.spotSize - 1, Mastermind.spotSize - 1);
       }
     }

     public int grabColor() {
       return this.color;
     }

     public boolean canSetColor() { return false; }

     public int setColor(int color) {
       return COLOR_NONE;
     }
   }

   static final Dimension guessStatusDimension =
     new Dimension (25, 25);
   static final Color guessStatusColors[] =
     new Color[] { Color.red, Color.yellow, Color.green };

  static final int guessShapes[][][][] =
     new int[][][][]
     { /* Wrong guess */
       { { { 4,  20, 20, 5 },
       { 11, 16, 14, 8 } },
       { { 9, 16, 13, 8 },
         { 21, 6, 5, 20 } } },
         /* Question mark */
       { { {  7, 12, 17, 11,  11, 13, 13, 20,  20, 14, 11, 5 },
           { 12,  7, 10, 13,  18, 18, 15, 11,   8,  4,  4, 9 } },
         { { 12, 10, 12, 15 },
           { 18, 21, 23, 21 } } },
         /* Right guess */
         { {
       {  5, 13, 19, 16, 12, 5 },
         { 17, 20,  5,  5, 17, 14 } } }
     };

   class GuessStatus extends Canvas {
     int status = 1;

     public Dimension getMinimumSize() {
       return this.getPreferredSize();
     }

     public Dimension getPreferredSize() {
       return Mastermind.guessStatusDimension;
     }

     public void paint(Graphics g) {
       g.setColor(Mastermind.guessStatusColors[this.status]);
       g.fillOval(0, 0, 25, 25);
       g.setColor(Color.black);
       int lines[][][] = Mastermind.guessShapes[this.status];
       for (int i = 0; i < lines.length; ++i) {
         g.fillPolygon( lines[i][0], lines[i][1],
                      lines[i][0].length );
       }
     }

     void setStatus(int status) {
       this.status = status;
       this.repaint();
     }
   }

   void setGuessStatus(int s) {
     if (this.guessStatus == s) return;
     this.guessStatusBulb.setStatus(s);
     this.guessStatus = s;
   }

   void addGridBagContents(Component child) {
     this.gbl.setConstraints(child, this.gbc);
     this.add(child);
     this.gbc.gridx = this.gbc.gridy = GridBagConstraints.RELATIVE;
     this.gbc.gridwidth = this.gbc.gridheight = 1;
     this.gbc.fill = GridBagConstraints.NONE;
     this.gbc.ipadx = this.gbc.ipady = 0;
     Insets insets = this.gbc.insets;
     insets.top = insets.bottom = insets.right = insets.left = 1;
     this.gbc.anchor = GridBagConstraints.CENTER;
     this.gbc.weightx = this.gbc.weighty = 0.0;
   }

   public static Color getBorderColor(Color spot) {
     if (spot == Color.black || spot == Color.blue ||
         spot == Mastermind.pieceColors[2])
       return Color.white;
     return Color.black;
   }
  
  /**
   * Check all the rows and update the guess status.  It is running
   * continuously in a loop.
   */
  final RowGenerator guessScanner = new RowGenerator();

  void checkAllRows() throws AbortedException {
    this.setGuessStatus(1); // Set safe value while finding correct
                            // value.

     if (this.cheatState < 1 || this.currentRow < 0) return;

     int row = this.currentRow;

     boolean empty = true;
     boolean full = true;

     for (int i = 0; i < 5; ++i) {
       if (this.playColors[row][i] == COLOR_NONE)
         full = false;
       else
         empty = false;
     }

     /* If current row not full, mark it only at cheat level 3. */
     if (this.cheatState < 3 && ! full) return;

     /* If row is completely empty, immediately mark the guess as
        green.  This is merely a performance optimization.  Otherwise
        the code will merely try all possible guesses, duly find some,
        and mark the guess as green after a lot of calculations. */
     if (empty) { this.setGuessStatus(2); return; }

     this.guessScanner.init(this.playColors[row]);
     int poss[];

  ALLGUESSES:
     while ((poss = this.guessScanner.getNext()) != null) {
       for (int prevRow = 0; prevRow < row; ++prevRow) {
         this.allRowsChecker.hasBeenAborted();
         if (! checkRows(prevRow, poss)) {
           continue ALLGUESSES;
         }
       }
       this.setGuessStatus(2);
       return;
     }
     this.setGuessStatus(0);
   }

   boolean checkRows(int row1, int rowColors[]) {
     int i, j;
     int result = Scorer.getScore(this.playColors[row1],
                                  rowColors);
     int black = result / 8;
     int white = result % 8;

     return (black == this.scores[row1].black &&
             white == this.scores[row1].white);
   }

   Object rowChanged = new Object();
   int analyzableRow = -1;
   boolean rescanGame = false;
   int codePossibilities = 32768;

   void analyzeProgress() {
     boolean doRow = false;
     int rowDone = -1;
     while (true) {
       doRow = false;
       synchronized (this.rowChanged) {
         while (! this.rescanGame && this.analyzableRow == rowDone) {
           try { this.rowChanged.wait(); }
           catch (InterruptedException ie) { }
         }
         if (this.rescanGame) {
           this.rescanGame = false;
           this.codePossibilities = 32768;
           rowDone = -1;
         }
         if (this.analyzableRow > rowDone) doRow = true;
       }
       if (doRow) {
         ++rowDone;
         this.analyzeRow(rowDone);
         this.analyzeErrors(rowDone);
         this.commentaryShow.appendText("\n");
      }
    }
  }

  final RowGenerator rowAnalyzer = new RowGenerator();

  static final DecimalFormat rowFormatter = new
  DecimalFormat("####0");

  static final DecimalFormat factorFormatter =
  new DecimalFormat("####0.0");

  void analyzeRow(int row) {
    this.rowAnalyzer.init();
    int count = 0;
    int poss[];
    while ((poss = this.rowAnalyzer.getNext()) != null) {
      if (this.analyzePriorRows(poss, row))
        ++count;
    }
    double factor = ((double) this.codePossibilities) / count;
    this.codePossibilities = count;
    String show = "Row " + rowFormatter.format(row) +
      (row < 10 ? ":  " : ": ")
         + count + " (" + factorFormatter.format(factor) + ")";
    this.commentaryShow.appendText(show);
    // this.commentaryShow.appendText("Analyzed row " + row + "\n");
  }

  boolean analyzePriorRows(int poss[], int row) {
    for (int scanRow = 0; scanRow <= row; ++scanRow) {
      int res = Scorer.getScore(poss, this.playColors[scanRow]);
      if (res / 8 != this.scores[scanRow].black ||
          res % 8 != this.scores[scanRow].white)
        return false;
    }
    return true;
  }

  void analyzeErrors(int row) {
    if (countColors(this.playColors[row]) <= 2) return;
    int prevRow;
    String bad = null;
    int countBad = 0;
    for (prevRow = 0; prevRow < row; ++prevRow) {
      if (! checkRows(prevRow, this.playColors[row])) {
        if (bad == null) bad = Integer.toString(prevRow);
        else bad += ", " + prevRow;
        ++countBad;
      }
    }
    if (bad == null) return;
    this.commentaryShow.appendText(" violates row" +
                                   (countBad > 1 ? "s " : " ") + bad);
  }

  static int countColors(int row[]) {
    int count = 0, i, col;
    for (i = 0; i < 8; ++i) {
      for (col = 0; col < 5; ++col) {
        if (row[col] == i) {
          ++count;
          break;
        }
      }
    }
    return count;
  }
}

