/* "Dots" -- A simple Java applet-based puzzle game
   Copyright (c) 1999 Aaron Hertzmann
   http://www.mrl.nyu.edu/hertzmann
   
   
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

The GNU Public license is available at www.fsf.org.




Notes:


*  For instructions on how to play and other information, please visit
   http://www.mrl.nyu.edu/hertzmann/dots

*  This was written in 1996, with Java 1.0.  I haven't made any attempt to
   clean it up for distribution, but it's not too complicated.

*  To compile this program with a command-line javac compiler, just type:
   % javac Dots.java

*/


import java.awt.*;
import java.applet.*;
import java.util.Random;
import java.util.Vector;

public class Dots extends Applet {
    public void init() {
	setLayout(new BorderLayout());
	Label messageLabel = new Label("\"Dots\" by Aaron Hertzmann");
	add("North",messageLabel);
	DrawPanel dp = new DrawPanel(messageLabel);
	add("Center", dp);
	add("South",new DrawControls(dp));
    }

    public boolean handleEvent(Event e) {
	switch (e.id) {
	  case Event.WINDOW_DESTROY:
	    System.exit(0);
	    return true;
	  default:
	    return false;
	}
    }

    public static void main(String args[]) {
	Frame f = new Frame("Dots");
	Dots Dots = new Dots();
	Dots.init();
	Dots.start();

	f.add("Center", Dots);
	f.resize(400, 400);
	f.show();
    }
}

class DrawPanel extends Panel {
    Board board;
    Checkbox answerBox;

    public DrawPanel(Label ml) 
      {
	setBackground(Color.white);
	answerBox = new Checkbox("Answer");
	board = new Board(ml,answerBox);
    }

    public boolean handleEvent(Event e) {
	switch (e.id) {
	case Event.MOUSE_DOWN:
	  board.mouseDown(e,getGraphics());
	  return false;
	case Event.WINDOW_DESTROY:
	    System.exit(0);
	  return true;
	default:
	  return false;
	}
    }

    public void paint(Graphics g) 
      {
	Rectangle r = bounds();
	board.screenWidth = r.width;
	board.screenHeight = r.height;
	board.drawBoard(g);
	board.fillBoard(g);
      }

    void newBoard()
      {
	Graphics g = getGraphics();
	Rectangle r = bounds();
	g.clearRect(0,0,r.width,r.height);
	board.newBoard();
	board.drawBoard(g);
      }

    void newSize(int size)
      {
	board.width = board.height = size;
	newBoard();
      }

    void newProb(int p)
      {
	board.probability = p;
	newBoard();
      }

    void erase()
      {
	board.erase(getGraphics());
      }

    void answer(boolean b)
      {
	board.answer = b;
	repaint();
      }

    void setBW(boolean bw)
      {
	board.blackAndWhite = bw;
	repaint();
      }
  }

class DrawControls extends Panel {
    DrawPanel target;
    
    public DrawControls(DrawPanel target) {
	this.target = target;
	
	setLayout(new FlowLayout());
	setBackground(Color.lightGray);

	add(new Button("New"));
	add(new Button("Erase"));
	add(target.answerBox);

	Choice menu = new Choice();
	menu.addItem("2x2");
	menu.addItem("5x5");
	menu.addItem("10x10");
	menu.addItem("12x12");
	menu.addItem("15x15");
	menu.addItem("20x20");
	menu.setBackground(Color.lightGray);
	menu.select("10x10");
	add(menu);

	Choice p = new Choice();
	p.addItem("25%");
	p.addItem("40%");
	p.addItem("45%");
	p.addItem("50%");
	p.addItem("55%");
	p.addItem("60%");
	p.select("50%");
	add(p);

	add(new Button("Custom"));

	add(new Checkbox("B&W"));

    }

    public void paint(Graphics g) {
	Rectangle r = bounds();

	g.setColor(Color.lightGray);
	g.draw3DRect(0, 0, r.width, r.height, false);
    }

    public boolean action(Event e, Object arg) {
      if (e.target instanceof Button)
	{
	  String choice = (String)arg;

	  if (arg.equals("New"))
	    target.newBoard();
	  else 
	    if (arg.equals("Erase"))
	      target.erase();
	    else
	      if (choice.equals("Custom"))
		{
		  CustomFrame cf = new CustomFrame(target);
		}
	}
      else
      if (e.target instanceof Choice) 
	{
	  String choice = (String)arg;
	  
	  if (choice.equals("2x2"))
	    target.newSize(2);
	    else
	      if (choice.equals("5x5"))
		target.newSize(5);
	    else
	      if (choice.equals("10x10"))
		target.newSize(10);
	      else
		if (choice.equals("15x15"))
		  target.newSize(15);
	    else
	      if (choice.equals("12x12"))
		target.newSize(12);
	    else
	      if (choice.equals("20x20"))
		target.newSize(20);
	    else
	      if (choice.equals("25%"))
		target.newProb(25);
	    else
	      if (choice.equals("40%"))
		target.newProb(40);
	    else
	      if (choice.equals("45%"))
		target.newProb(45);
	    else
	      if (choice.equals("50%"))
		target.newProb(50);
	    else
	      if(choice.equals("55%"))
		target.newProb(55);
	      else if (choice.equals("60%"))
		target.newProb(60);
	  } 
      else
	if (e.target instanceof Checkbox)
	  {
	    String choice = ((Checkbox)e.target).getLabel();
	    boolean b = ((Boolean)e.arg).booleanValue();

	    if (choice.equals("Answer"))
	      {
		target.answer(b);
	      }
	    else
	      if (choice.equals("B&W"))
		target.setBW(b);
	  }
      
      return true;
  }
}


    
class Board
{
  // bit fields
  static final int DOT = 1;   // really a dot
  static final int GUESS = 2;  // user thinks there's a dot
  static final int EMPTY_GUESS = 4; // user thinks it's empty

  int screenWidth = 400;   // screen size
  int screenHeight = 400;  // screen size
  int leftMargin = 5;
  int topMargin = 5;
  int boxWidth;
  int boxHeight;

  int[][] board;
  boolean[] correctColumns;  // which columns are correctly marked
  boolean[] correctRows;
  int numColsCorrect;
  int numRowsCorrect;

  int[][] columns;   // lists of numbers along the top
  int[][] rows;      // lists of numbers along the side

  int width;         // number of boxes in the board's width
  int height;

  int bHeight;       // number of boxes in the width, including the side #'s
  int bWidth;

  int probability;   // probability*100 that a box has a dot in it 

  Random rand;

  boolean blackAndWhite = false;
  boolean answer = false;

  Label messageLabel;
  Checkbox answerBox;

  Board(Label ml,Checkbox ab)
    {
      width = 10;
      height = 10;
      probability = 50;
      
      rand = new Random();

      messageLabel = ml;

      answerBox = ab;

      newBoard();
    }

  void message(String text)
    {
      messageLabel.setText(text);
    }

  void resetAnswerBox()
    {
      answer = false;
      answerBox.setState(false);
    }

  void newBoard()
    {
      resetAnswerBox();
      board = new int[width][height];
      correctColumns = new boolean[width];
      correctRows = new boolean[height];

      int sum;
      numColsCorrect = 0;
      numRowsCorrect = 0;

      for(int i=0;i<width;i++)
	{
	  sum = 0;

	  for(int j=0;j<height;j++)
	    {
	      board[i][j] = (rand.nextFloat()*100 < probability ? DOT : 0);
	      if (board[i][j] == DOT)
		sum++;
	    }

	  correctColumns[i] = (sum == 0);	    

	  if (sum == 0)
	    numColsCorrect ++;
	}

      if (numColsCorrect == width)
	message("Empty board!");

      columns = new int[width][];
      rows = new int[height][];

      bHeight = 0;
      bWidth = 0;

      for(int i=0;i<width;i++)
	{
	  columns[i] = computeColumn(i);
	  if (columns[i].length > bHeight)
	    bHeight = columns[i].length;
	}
      for(int i=0;i<height;i++)
	{
	  rows[i] = computeRow(i);
	  if (rows[i].length > bWidth)
	    bWidth = rows[i].length;
	}

      for(int i=0;i<height;i++)
	{
	  correctRows[i] = checkRow(i);
	  if (correctRows[i])
	    numRowsCorrect ++;
	}
    }

  int[] computeColumn(int x)
    {
      Vector nums = new Vector();

      boolean lastNum = false;
      int count = 0;

      for(int i=0;i<height;i++)
	{
	  if (lastNum && (board[x][i] & DOT) == DOT)
	    {
	      count ++;
	      continue;
	    }

	  if (lastNum)
	    {
	      nums.addElement(new Integer(count));
	      count = 0;
	      lastNum = false;
	      continue;
	    }

	  if((board[x][i] & DOT) == DOT)
	    {
	      count = 1;
	      lastNum = true;
	      continue;
	    }
	}

      if (lastNum)
	nums.addElement(new Integer(count));
      
      int[] n = new int[nums.size()];

      for(int i=0;i<nums.size();i++)
	n[i] = ((Integer)nums.elementAt(i)).intValue();

      return n;
    }  

  int[] computeRow(int y)
    {
      Vector nums = new Vector();

      boolean lastNum = false;
      int count = 0;

      for(int i=0;i<width;i++)
	{
	  if (lastNum && (board[i][y] & DOT)==DOT)
	    {
	      count ++;
	      continue;
	    }

	  if (lastNum)
	    {
	      nums.addElement(new Integer(count));
	      count = 0;
	      lastNum = false;
	      continue;
	    }

	  if((board[i][y] & DOT) == DOT)
	    {
	      count = 1;
	      lastNum = true;
	      continue;
	    }
	}

      if (lastNum)
	nums.addElement(new Integer(count));

      int[] n = new int[nums.size()];

      for(int i=0;i<nums.size();i++)
	n[i] = ((Integer)nums.elementAt(i)).intValue();

      return n;
    }  

  void drawBoard(Graphics g)
    {
      FontMetrics fm = g.getFontMetrics();
      int ascent2 = fm.getAscent()/2;

      boxWidth = (screenWidth-leftMargin*2)/(bWidth+width);
      boxHeight = (screenHeight-topMargin*2)/(bHeight+height);
      
      int top = boxHeight * bHeight+topMargin;
      int left = boxWidth * bWidth+leftMargin;
      int right =(bWidth+width)*boxWidth+leftMargin;
      int bottom = (bHeight+height)*boxHeight+topMargin;

      int i,j;

      for(i=0;i<bWidth;i++)
	g.drawLine(i*boxWidth+leftMargin,top,
		   i*boxWidth+leftMargin,bottom);
      for(;i<=bWidth+width;i++)
	g.drawLine(i*boxWidth+leftMargin,topMargin,
		   i*boxWidth+leftMargin,bottom);

      for(i=0;i<bHeight;i++)
	g.drawLine(left,i*boxHeight+topMargin,
		   right,i*boxHeight+topMargin);
      for(;i<=bHeight+height;i++)
	g.drawLine(leftMargin,i*boxHeight+topMargin,
		   right,i*boxHeight+topMargin);

      int l;

      int bw2 = boxWidth/2;
      int bh2 = boxHeight/2;

      for(i=0;i<width;i++)
	{
	  l = columns[i].length;

	  for(j=0;j<l;j++)
	    {
	      String s = (new Integer(columns[i][l-j-1])).toString();
	      
	      int width = fm.stringWidth(s);

	      g.drawString(s,(bWidth+i)*boxWidth+leftMargin+bw2-width/2,
			   (bHeight-j-1)*boxHeight+topMargin+bh2+ascent2);
	    }
	}

      for(i=0;i<height;i++)
	{
	  l = rows[i].length;

	  for(j=0;j<l;j++)
	    {
	      String s = (new Integer(rows[i][l-j-1])).toString();
	      
	      int width = fm.stringWidth(s);

	      g.drawString(s,(bWidth-j-1)*boxWidth+leftMargin+bw2-width/2,
			   (bHeight+i)*boxHeight+topMargin+bh2+ascent2);
	    }
	}
    }

  void drawSquare(Graphics g,int x,int y)
    {
      if (x < 0 && y < 0 && x >= width && y >= height)
	return;
      
      int left = leftMargin+(bWidth+x)*boxWidth;
      int top = topMargin+(bHeight+y)*boxHeight;

      Color drawColor;

      if ((board[x][y] & GUESS) == GUESS)
	drawColor = Color.black;
      else
	{
	  if ((board[x][y] & EMPTY_GUESS) == EMPTY_GUESS)
	    {
	      drawColor = (blackAndWhite ? Color.white : Color.orange);
	    }
	  else
	    drawColor = Color.white;
	}

      g.setColor(drawColor);
      g.fillRect(left+1,top+1,boxWidth-1,boxHeight-1);

      if (blackAndWhite && ((board[x][y] & EMPTY_GUESS) == EMPTY_GUESS))
	{
	  g.setColor(Color.black);
	  g.drawLine(left+1,top+1,left+boxWidth,top+boxHeight);
	  g.drawLine(left+1,top+boxHeight,left+boxWidth,top+1);
	}	  

      if (answer && (board[x][y] & DOT) == DOT)
	drawAnswerSquare(g,x,y);
    }

  void mouseDown(Event e,Graphics g)
    {
      int left = leftMargin+bWidth*boxWidth;
      int top = topMargin+bHeight*boxHeight;
      int right = leftMargin+(bWidth+width+1)*boxWidth;
      int bottom = topMargin+(bHeight+height+1)*boxHeight;

      int cx = (e.x-left)/boxWidth;
      int cy = (e.y-top)/boxHeight;

      if (cx < 0 || cy < 0 || cx >= width || cy >= height)
	return;

      // right mouse button
      if ((e.modifiers & Event.META_MASK) == Event.META_MASK)
	{
	 board[cx][cy] &= ~GUESS;
	 if ((board[cx][cy] & EMPTY_GUESS) == EMPTY_GUESS)
	   board[cx][cy] &= ~EMPTY_GUESS;
	 else
	   board[cx][cy] |= EMPTY_GUESS;
	}
      else
	{
	 board[cx][cy] &= ~EMPTY_GUESS;
	 if ((board[cx][cy] & GUESS) == GUESS)
	   board[cx][cy] &= ~GUESS;
	 else
	   board[cx][cy] |= GUESS;
	}

      drawSquare(g,cx,cy);

      boolean newVal = checkColumn(cx);

      if (correctColumns[cx] && !newVal)
	numColsCorrect --;
      else
	if (!correctColumns[cx] && newVal)
	  numColsCorrect ++;

      correctColumns[cx] = newVal;
      
      newVal = checkRow(cy);
      
      if (correctRows[cy] && !newVal)
	numRowsCorrect --;
      else 
	if (!correctRows[cy] && newVal)
	  numRowsCorrect ++;

      correctRows[cy] = newVal;

      if (numColsCorrect == width && numRowsCorrect == height)
	message("Correct!");
      else
	message("");

/*
      String m = (numColsCorrect == width && numRowsCorrect == height ?
		  "Correct " : "");

		  m+= (numColsCorrect+" "+numRowsCorrect);

      for(int i=0;i<width;i++)
	m+= (correctColumns[i] ? "T":"F");

      m+= " ";
      
      for(int i=0;i<width;i++)
	m+= (correctRows[i] ? "T":"F");

      message(m);
*/
    }
	
  boolean checkColumn(int x)
    {
      boolean lastNum = false;
      int count = 0;
      int n = 0;

      for(int y=0;y<height;y++)
	{
	  if (lastNum && (board[x][y] & GUESS) == GUESS)
	    {
	      count ++;
	      continue;
	    }

	  if (lastNum)
	    {
	      if (n >= columns[x].length || count != columns[x][n])
		return false;

	      count = 0;
	      lastNum = false;
	      n++;
	      continue;
	    }

	  if((board[x][y] & GUESS) == GUESS)
	    {
	      count = 1;
	      lastNum = true;
	      continue;
	    }
	}

      if (lastNum && (n >= columns[x].length || count != columns[x][n]))
	return false;

      if (lastNum)
	n++;

      return (n == columns[x].length);
    }

  boolean checkRow(int y)
    {
      boolean lastNum = false;
      int count = 0;
      int n = 0;

      for(int x=0;x<width;x++)
	{
	  if (lastNum && (board[x][y] & GUESS) == GUESS)
	    {
	      count ++;
	      continue;
	    }

	  if (lastNum)
	    {
	      if (n >= rows[y].length || count != rows[y][n])
		return false;

	      count = 0;
	      lastNum = false;
	      n++;
	      continue;
	    }

	  if((board[x][y] & GUESS) == GUESS)
	    {
	      count = 1;
	      lastNum = true;
	      continue;
	    }
	}

      if (lastNum && (n >= rows[y].length || count != rows[y][n]))
	return false;
      
      if (lastNum)
	n++;

      return (n == rows[y].length);
    }

  void erase(Graphics g)
    {
      resetAnswerBox();

      for(int x=0;x<width;x++)
	for(int y=0;y<height;y++)
	  {
	    board[x][y] &= ~(GUESS | EMPTY_GUESS);
	    drawSquare(g,x,y);
	  }
    }

  void drawAnswerSquare(Graphics g,int x,int y)
    {
      if (x < 0 && y < 0 && x >= width && y >= height)
	return;
      
      int left = leftMargin+(bWidth+x)*boxWidth;
      int top = topMargin+(bHeight+y)*boxHeight;

      if (blackAndWhite)
	g.setColor(Color.white);
      else
	g.setColor(Color.blue);

      g.fillRect(left+boxWidth/3,top+boxWidth/3,boxWidth/3,boxHeight/3);

      if (blackAndWhite)
	{
	  g.setColor(Color.black);
	  g.drawRect(left+boxWidth/3,top+boxWidth/3,boxWidth/3,boxHeight/3);
	}
    }

  void fillBoard(Graphics g)
    {
      for(int x=0;x<width;x++)
	for(int y=0;y<height;y++)
	  drawSquare(g,x,y);
    }

}

class CustomFrame extends Frame
{
  DrawPanel drawPanel;
  TextField rowTF, columnTF, probTF;
  Board b;

  CustomFrame(DrawPanel dp)
    {
      super("Custom Board");

      drawPanel = dp;
      b = dp.board;

      setLayout(new BorderLayout());

      Panel p = new Panel();

      p.setLayout(new FlowLayout());

      p.add(new Label("Rows:"));

      rowTF = new TextField(4);

      p.add(rowTF);
      rowTF.setText(String.valueOf(b.height));

      p.add(new Label("Columns:"));

      columnTF = new TextField(4);
      p.add(columnTF);
      columnTF.setText(String.valueOf(b.width));

      add("North",p);

      p = new Panel();
      p.setLayout(new FlowLayout());

      p.add(new Label("Dot probability:"));
      probTF = new TextField(4);
      probTF.setText(String.valueOf(b.probability));
      p.add(probTF);
      add("Center",p);

      p = new Panel();

      p.setLayout(new FlowLayout());
      p.add(new Button("Set"));
      p.add(new Button("Done"));
      add("South",p);

      pack();
      show();
    }

  public boolean action(Event e,Object arg)
    {
      if (e.target instanceof Button)
	{
	  if (((String)e.arg).equals("Set"))
	    {
	      b.width = Integer.parseInt(columnTF.getText());

	      b.height = Integer.parseInt(rowTF.getText());

	      b.probability = Integer.parseInt(probTF.getText());

	      if (b.width < 1)
		b.width = 1;

	      if (b.height < 1)
		b.height = 1;

	      drawPanel.newBoard();
	    }
	  else
	  if (((String)e.arg).equals("Done"))
	    {
	      hide();
	      dispose();
	    }
	  
	  return true;
	}

      return false;
    }
}
