import java.applet.Applet;
import java.awt.AWTEvent;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.util.Random;

/**
 * @author alexis reigel - mail@koffeinfrei.org, 
 * @date octobre 2005
 *    
 * Simple mastermind implementation
 * source code released under gpl v2
 * all rights to "mastermind" held by hasbro (http://www.hasbro.com)
 * all rights to "java" held by sun microsystems (http://www.sun.com) 
 */
public class Mastermind extends Applet{
  // mmhh, enums would be so much nicer
  /* code pegs */
  private static final int BLUE = 0;
  private static final int RED = 1;
  private static final int GREEN = 2;
  private static final int YELLOW = 3;
  private static final int ORANGE = 4;
  private static final int MAGENTA = 5;
  
  /* key pegs */
  private static final int WHITE = 0;
  private static final int BLACK = 1;
  
  private static final int NONE = -1;
  
  private static final int NUM_COLORS = 6;
  private static final int NUM_ROUNDS = 8;
  
  /* sizes */
  private static final int HEIGHT_FIELD = 40;
  private static final int WIDTH_CODE_FIELD = 160;
  private static final int WIDTH_KEY_FIELD = 40;
  private static final int HEIGHT_BOARD 
    = HEIGHT_FIELD * NUM_ROUNDS;
  private static final int WIDTH_BOARD 
    = WIDTH_CODE_FIELD + WIDTH_KEY_FIELD;
  private static final int SIZE_HOLE_CODE = 14;
  private static final int SIZE_HOLE_KEY = 8;
  private static final int SIZE_PEG_CODE = 18;
  private static final int SIZE_PEG_KEY = 14;
  private static final int SPACER = 14;
  private static final int HEIGHT_BUTTON = 20;
  private static final int WIDTH_BUTTON = 80;
  
  private int[][] m_pegs_key_set;
  private int[][] m_pegs_code_set;
  
  private Color[] m_pegs;
  private boolean[] m_pegs_selected;
  private Point[] m_peg_selection_loc;
  private Point m_button_loc;
  
  private int m_current_round;
  
  private int m_current_drag;
  private int m_mouse_x;
  private int m_mouse_y;
  
  private int[] m_result_set;
  
  private boolean m_playing;
  private boolean m_won;
  
  
  public void init(){
    m_pegs = new Color[NUM_COLORS];
    m_pegs[BLUE] = Color.blue;
    m_pegs[RED] = Color.red;
    m_pegs[GREEN] = Color.green;
    m_pegs[YELLOW] = Color.yellow;
    m_pegs[ORANGE] = Color.orange;
    m_pegs[MAGENTA] = Color.magenta;
    m_pegs_selected = new boolean[NUM_COLORS];
    m_pegs_selected[BLUE] = true;
    m_pegs_selected[RED] = true;
    m_pegs_selected[GREEN] = true;
    m_pegs_selected[YELLOW] = true;
    m_pegs_selected[ORANGE] = false;
    m_pegs_selected[MAGENTA] = false;
    
    m_pegs_key_set = new int[NUM_ROUNDS][4];
    m_pegs_code_set = new int[NUM_ROUNDS][4];
    
    m_peg_selection_loc = new Point[NUM_COLORS];
    
    reset();
    
    m_playing = false;
    
    setBackground(Color.white);
    this.enableEvents(AWTEvent.MOUSE_EVENT_MASK |
      AWTEvent.MOUSE_MOTION_EVENT_MASK);
    this.requestFocus();
  }
  
  public void reset(){
    /* reset key set */ 
    for (int i = 0; i < m_pegs_key_set.length; ++i)
      for (int j = 0; j < m_pegs_key_set[0].length; ++j)
        m_pegs_key_set[i][j] = NONE;
    
    /* reset code set */
    for (int i = 0; i < m_pegs_code_set.length; ++i)
      for (int j = 0; j < m_pegs_code_set[0].length; ++j)
        m_pegs_code_set[i][j] = NONE;
    
    m_current_round = 0;
    m_current_drag = -1; 
    m_won = false;
        
    makeResultSet();
  }
  
  /* count number of selected colors */
  private int numSelectedColors(){
    int num_selected = 0;
    for (int i = 0; i < NUM_COLORS; ++i) 
      if (m_pegs_selected[i]) ++num_selected;
    return num_selected;
  }
  
  private void drawCodeOval(Graphics g, int round, int pos, int size){
    g.fillOval( 
      (2 * pos+1) * WIDTH_CODE_FIELD / NUM_ROUNDS - size / 2, 
      HEIGHT_FIELD / 2  + (NUM_ROUNDS - round - 1) * HEIGHT_FIELD - size / 2, 
      size, 
      size 
    );
  }
  
  private void drawKeyOval(Graphics g, int round, int pos, int size){
    g.fillOval( 
      WIDTH_CODE_FIELD + WIDTH_KEY_FIELD / 4 - size / 2 + pos%2 * WIDTH_KEY_FIELD / 2 /*4*/ /*SIZE_HOLE + WIDTH_KEY_FIELD / 8- SIZE_HOLE / 4*/, 
      (NUM_ROUNDS - round - 1) * HEIGHT_FIELD + HEIGHT_FIELD / 4 - size / 2 + (pos > 1? WIDTH_KEY_FIELD / 2 /*4*/ /*SIZE_HOLE*/: 0) /*+ WIDTH_KEY_FIELD / 8 - SIZE_HOLE / 4*/, 
      size, 
      size
    );
  }
  
//  private void drawCodeHighlight(Graphics g, int round, int pos, int size){
//    g.setColor(Color.white);
//    g.fillOval(
//      (2 * pos+1) * WIDTH_CODE_FIELD / NUM_ROUNDS - size / 2 +size/4, 
//      HEIGHT_FIELD / 2  + (NUM_ROUNDS - round - 1) * HEIGHT_FIELD - size / 2 + size/4, 
//      size / 3, 
//      size / 3/*,
//      -135, 
//      180 */
//    );
//  }
  
  private void paintDrag(Graphics g){
    g.setColor(m_pegs[m_current_drag]);
    g.fillOval(
      m_mouse_x - SIZE_PEG_CODE / 2,
      m_mouse_y - SIZE_PEG_CODE / 2,
      SIZE_PEG_CODE,
      SIZE_PEG_CODE
    );
  }
  
  public void paint(Graphics g) {
    /* if all pegs are set, get result */
    if (m_playing){
      boolean all_set = true;
      for (int i = 0; i < 4; ++i){
        if (m_pegs_code_set[m_current_round][i] == NONE)
          all_set = false;
      }
      if (all_set){
        evalResult();
      }
    }
    
    /* paint lines (= number of rounds) */
    for (int i = NUM_ROUNDS - 1; i >= 0 ; --i){
      g.setColor(new Color(217, 194, 104));
      g.fill3DRect(0, (NUM_ROUNDS - i -1) * HEIGHT_FIELD, WIDTH_CODE_FIELD, HEIGHT_FIELD, true);
      g.fill3DRect(WIDTH_CODE_FIELD, (NUM_ROUNDS - i -1) * HEIGHT_FIELD, WIDTH_KEY_FIELD, HEIGHT_FIELD, true);
      
      /* paint code holes/pegs */
      for (int j = 0; j < 4; ++j){
        int size;
        /* empty hole */
        if (m_pegs_code_set[i][j] == NONE){
          g.setColor(Color.white);
          size = SIZE_HOLE_CODE;
          
          g.drawArc(
            (2 * j+1) * WIDTH_CODE_FIELD / NUM_ROUNDS - size / 2, 
            HEIGHT_FIELD / 2  + (NUM_ROUNDS - i - 1) * HEIGHT_FIELD - size / 2, 
            size, 
            size,
            -135, 
            180 
          );
          /*
          g.drawOval( 
            1+(2 * j+1) * WIDTH_CODE_FIELD / NUM_ROUNDS - size / 2, 
            1+HEIGHT_FIELD / 2  + i * HEIGHT_FIELD - size / 2, 
            size, 
            size 
          );
          */
          g.setColor(Color.black);
          drawCodeOval(g, i, j, size);
        }
        /* pegged hole */
        else{
          g.setColor( m_pegs[ m_pegs_code_set[i][j] ] );
          drawCodeOval(g, i, j, SIZE_PEG_CODE);
//          drawCodeHighlight(g, i, j, SIZE_PEG_CODE);
        }
        
        
//        g.fillOval( 
//          (2 * j+1) * WIDTH_CODE_FIELD / NUM_ROUNDS - size / 2, 
//          HEIGHT_FIELD / 2  + i * HEIGHT_FIELD - size / 2, 
//          size, 
//          size 
//        );
        
        /* paint key holes/pegs */
        for (int k = 0; k < 4; ++k){
          /* empty hole */
          if (m_pegs_key_set[i][k] == NONE){
            g.setColor(Color.white);
            size = SIZE_HOLE_KEY;
            
            g.drawArc( 
              WIDTH_CODE_FIELD + WIDTH_KEY_FIELD / 4 - size / 2 + k%2 * WIDTH_KEY_FIELD / 2 /*4*/ /*SIZE_HOLE + WIDTH_KEY_FIELD / 8- SIZE_HOLE / 4*/, 
              (NUM_ROUNDS - i - 1) * HEIGHT_FIELD + HEIGHT_FIELD / 4 - size / 2 + (k > 1? WIDTH_KEY_FIELD / 2 /*4*/ /*SIZE_HOLE*/: 0) /*+ WIDTH_KEY_FIELD / 8 - SIZE_HOLE / 4*/, 
              size, 
              size,
              -135,
              180
            );
            g.setColor(Color.black);
          }
          /* pegged hole */
          else{
            if (m_pegs_key_set[i][k] == BLACK)
              g.setColor( Color.black );
            else if (m_pegs_key_set[i][k] == WHITE)
              g.setColor( Color.white );
            size = SIZE_PEG_KEY;
          }
          drawKeyOval(g, i, k, size);
//          g.fillOval( 
//            WIDTH_CODE_FIELD + WIDTH_KEY_FIELD / 4 - size / 2 + k%2 * WIDTH_KEY_FIELD / 2 /*4*/ /*SIZE_HOLE + WIDTH_KEY_FIELD / 8- SIZE_HOLE / 4*/, 
//            i * HEIGHT_FIELD + HEIGHT_FIELD / 4 - size / 2 + (k > 1? WIDTH_KEY_FIELD / 2 /*4*/ /*SIZE_HOLE*/: 0) /*+ WIDTH_KEY_FIELD / 8 - SIZE_HOLE / 4*/, 
//            size, 
//            size
//          );
        }
      }
    }
    
    /* paint color selection */
    int num_colors = (m_playing)? numSelectedColors() : NUM_COLORS;
    int count = 0;
    for (int i = 0; i < NUM_COLORS; ++i){
      if (!m_playing) count = i;
      
      /* store locations */
      m_peg_selection_loc[i] = new Point(
        (2 * count+1) * WIDTH_BOARD  / num_colors / 2 - SIZE_PEG_CODE / 2,
      HEIGHT_BOARD + SPACER
      );
      
      if (m_pegs_selected[i]){
        g.setColor(Color.black);
        g.fillOval( 
          m_peg_selection_loc[i].x - 1, 
          m_peg_selection_loc[i].y -1, 
          SIZE_PEG_CODE + 2, 
          SIZE_PEG_CODE + 2
        );  
      }
      
      if (!m_playing || m_pegs_selected[i]){
        g.setColor( m_pegs[i] );
        g.fillOval( 
          m_peg_selection_loc[i].x, 
          m_peg_selection_loc[i].y, 
          SIZE_PEG_CODE, 
          SIZE_PEG_CODE 
        );
      }
      
      if (m_playing && m_pegs_selected[i]) ++count;
    }
    
    /* paint start / stop button */
    if (m_button_loc == null)
      m_button_loc = new Point(WIDTH_BOARD / 2 - WIDTH_BUTTON / 2, m_peg_selection_loc[0].y + SIZE_PEG_CODE + SPACER);
    g.setColor(Color.lightGray);
    g.fill3DRect(
      m_button_loc.x,//m_peg_selection_loc[0].x, // take coordinates from color selection above
      m_button_loc.y, 
      WIDTH_BUTTON, 
      HEIGHT_BUTTON, 
      true
    );
    g.setColor(Color.black);
    /*g.draw3DRect(
      m_peg_selection_loc[0].x, // take coordinates from color selection above
      m_peg_selection_loc[0].y + SIZE_PEG_CODE + SPACER, 
      WIDTH_BUTTON, 
      HEIGHT_BUTTON, 
      true
    );*/
    String disp;
    if (m_playing) disp = "end game";
    else disp = "new game";
    g.setFont(new Font("sans-serif", Font.BOLD, 12));
    g.drawString(disp, WIDTH_BOARD / 2 - WIDTH_BUTTON / 2 + 10, m_peg_selection_loc[0].y + SIZE_PEG_CODE + 2 * SPACER);
    
    /* print lost/won */
    if (m_current_round == NUM_ROUNDS && !m_won){
      g.drawString("Sorry. Correct code is:", m_peg_selection_loc[0].x, m_peg_selection_loc[0].y + SIZE_PEG_CODE + 4 * SPACER);
      for (int i = 0; i < 4; ++i){
        g.setColor(m_pegs[ m_result_set[i] ]);
        drawCodeOval(g, -3, i, SIZE_PEG_CODE);
      }
    }
    else if (m_won){
      g.drawString("Well done!", m_peg_selection_loc[0].x, m_peg_selection_loc[0].y + SIZE_PEG_CODE + 4 * SPACER);
    }
    
    /* paing drag */
    if (m_current_drag > -1){
      paintDrag(g);
    }

  }
  
  /* result */ 
  private void makeResultSet(){
    m_result_set = new int[4];
    Random rand = new Random();
    for (int i = 0; i < 4; ++i){
      int num;
      while ( !m_pegs_selected[ num = rand.nextInt(NUM_COLORS) ] ){}
      m_result_set[i] = num;
    }
  }
  
  private void evalResult(){
    int last_key_peg = 0;
    boolean[] code_is_black = new boolean[4];
    boolean[] code_is_white_result = new boolean[4];
    boolean[] code_is_white_guess = new boolean[4];
    for (int i = 0; i < 4; ++i){
      if (m_result_set[i] == m_pegs_code_set[m_current_round][i]){
        m_pegs_key_set[m_current_round][last_key_peg++] = BLACK;
        code_is_black[i] = true;
      }
    }
    // all black -> done
    if (last_key_peg == 4){
      m_playing = false;
      m_won = true;
    }
    // search whites
    else{
      for (int i = 0; i < 4; ++i){
        for (int j = 0; j < 4; ++j){
          if ( !code_is_black[i] && !code_is_black[j]
            && !code_is_white_guess[i] && !code_is_white_result[j] 
            && m_result_set[j] == m_pegs_code_set[m_current_round][i]
          ){
            m_pegs_key_set[m_current_round][last_key_peg++] = WHITE;
            code_is_white_guess[i] = true;
            code_is_white_result[j] = true;
          }
        }
      }
    }
    ++m_current_round;
    
    if (m_current_round == NUM_ROUNDS && !m_won)
      m_playing = false;
  }
  
  /* mouse events */
  public void processMouseEvent(MouseEvent e) {
    /* select/deselect color */
    if (e.getID() == MouseEvent.MOUSE_CLICKED){
      /* button clicked */
      if (e.getX() >= m_button_loc.x
        && e.getX() <= m_button_loc.x + WIDTH_BUTTON
        && e.getY() >= m_button_loc.y
        && e.getY() <= m_button_loc.y + HEIGHT_BUTTON
      ){
        m_playing = !m_playing;
        reset();
        repaint();
      }
      
      int num_selected = numSelectedColors();
        
      for (int i = 0; i < NUM_COLORS; ++i){
        if ( !m_playing 
          && e.getX() >= m_peg_selection_loc[i].x
          && e.getX() <= m_peg_selection_loc[i].x + SIZE_PEG_CODE
          && e.getY() >= m_peg_selection_loc[i].y
          && e.getY() <= m_peg_selection_loc[i].y + SIZE_PEG_CODE
        ){

          if (!m_pegs_selected[i] || num_selected > 2) // min 2 must be selected
            m_pegs_selected[i] = !m_pegs_selected[i];
          repaint();
        }
      }
    }
    /* peg taken to drag */
    else if (e.getID() == MouseEvent.MOUSE_PRESSED){
      for(int i = 0; i < NUM_COLORS; ++i){
        if ( m_playing && m_pegs_selected[i]
          && e.getX() >= m_peg_selection_loc[i].x
          && e.getX() <= m_peg_selection_loc[i].x + SIZE_PEG_CODE
          && e.getY() >= m_peg_selection_loc[i].y
          && e.getY() <= m_peg_selection_loc[i].y + SIZE_PEG_CODE
        ){
          m_current_drag = i;
        }
      }
    }
    /* peg released */
    else if (m_current_drag > -1 && e.getID() == MouseEvent.MOUSE_RELEASED){
      for (int i = 0; i < 4; ++i){
        if ( i * WIDTH_CODE_FIELD / 4 <= e.getX()
          && (i + 1) * WIDTH_CODE_FIELD / 4 >= e.getX()
          && NUM_ROUNDS * HEIGHT_FIELD - m_current_round * HEIGHT_FIELD >= e.getY()
          && NUM_ROUNDS * HEIGHT_FIELD - (m_current_round + 1) * HEIGHT_FIELD <= e.getY()
        ){
          m_pegs_code_set[m_current_round][i] = m_current_drag;
        }
      }
      m_current_drag = -1;
      repaint();
    }
  }
  
  public void processMouseMotionEvent(MouseEvent e) {
    if(e.getID() == MouseEvent.MOUSE_DRAGGED) {
        if (m_current_drag > -1){
          m_mouse_x = e.getX();
          m_mouse_y = e.getY();
          repaint();
        }
    }
  }
}
