//------------------------------------------------------------------------------
// Java Applet Binger
// Copyright (C) 2001 by Alexander Adam
// No Warranty of any kind.
//------------------------------------------------------------------------------

import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import javax.swing.*;
import java.net.URL;
import java.net.MalformedURLException;

/** 
  * Java Applet Binger<br>
  * <b>No Warranty of any kind.<b>
  * @author Alexander Adam<br>Copyright (C) 2001
  * @version 1.02
  */
public class Binger extends Applet implements Runnable {

  /*
   * Constants
   */
  private final String sNewGame_en = "Start Game";
  private final String sNewGame_de = "Spiel Start";
  private final String sEndScore_en = "Game Over: Score ";
  private final String sEndScore_de = "Spiel Ende: Punkte ";
  private final String sSound_en = "Sound";
  private final String sSound_de = "Ton";
  private final String sSpeed_en = "Speed";
  private final String sSpeed_de = "Geschwindigkeit";
  private final String sStartLevel_en = "Start level";
  private final String sStartLevel_de = "Beginn in Runde";
  private final String sOptions_en = "Options";
  private final String sOptions_de = "Einstellungen";

  private final Color[] colors = { Color.red, Color.blue, Color.yellow, Color.green};

  private final int red = 0;
  private final int blue = 1;
  private final int yellow = 2;
  private final int green = 3;

  private final int numButtons = 4;
  private final int numAudioClips = 4;
  private final int numImages = 5;

  private final int optionsFontSize = 10;
  private final int centerLabelNorthFontSize = 14;
  private final int newGameButtonFontSize = 18;

  private final int minApplause = 10; // minimum Score for final Applause
  private final int maxApplause = 3;  // applause repeats

  private final int maxSpeed = 200;
  private final int maxStartLevel = 20;

  // for sleep() ...
  private final int pauseFlash       = 2;
  private final int pauseAfterBlink  = 6;
  private final int pauseBeforeBlink = 10;
  private final int pauseAfterAll    = 10;

  // Language ( Locale (Country) )
  private String lang;
  private String lang_de;

  // Game ( Thread )
  private Thread game;
  private boolean gameOver = false;

  // Button table
  private Button[] buttons = new Button[numButtons];

  // Sounds
  private AudioClip audioBeep;
  private AudioClip[] dials = new AudioClip[numAudioClips];

  // Color List
  private int colorListCounter = 0;
  private Vector colorList = new Vector();

  // Button Counter for ButtonListener
  private int buttonCounter = 0;
  private ButtonListener buttonListener = new ButtonListener();

  // Game Panel
  private Panel panel = new Panel();
  private Color panelBackground = Color.white;

  // New Game Button
  private Button newGameButton = new Button();

  // Inner ImageApplet
  private Panel centerPanel = new Panel();
  private Label centerLabelNorth = new Label();
  private ImageApplet imageApplet = new ImageApplet();
  private Image image;
  private Image[] images = new Image[numImages];

  // Options
  private Checkbox soundOption = new Checkbox();
  private Scrollbar speedScroller;
  private Label speedValueLabel;
  private int speed = 7 * maxSpeed / 10;          // Start with 70%
  private int startLevel = 1;                     // Start with Level 1
  private Scrollbar startLevelScroller;
  private Label startLevelValueLabel;

  // Other Flags
  private boolean startManual = false;
  private boolean soundOn = true;

  /**
    * This method is called when the <b>Applet</b> is first <b>initialized</b>
    */
  public void init()
  {
    // Get Locale
    lang = Locale.getDefault().getLanguage();
    lang_de = Locale.GERMAN.getLanguage();

    // Set Big Font for newGameButton
    Font font = getFont();

    if ( font != null ) {
      font = new Font( font.getName(), Font.BOLD, newGameButtonFontSize );
    }
    if ( font != null ) {
      newGameButton.setFont( font );
    }

    if ( lang.equals(lang_de) ) {
      newGameButton.setLabel(sNewGame_de);
    } else {
      newGameButton.setLabel(sNewGame_en);
    }

    // getImages
    for ( int im = 0; im < numImages; im++ ) {
      String imFile = "Binger"+(im+1)+".jpg";
      images[im] = getImage(getCodeBase(),imFile);
    }

    image = images[0];

    // getAudioClips
    for ( int iac = 0; iac < numAudioClips; iac++ ) {
      String iacFile = "dial"+(iac)+".au";
      dials[iac] = getAudioClip(getCodeBase(),iacFile);
    }

    audioBeep = getAudioClip(getCodeBase(),"beep1.au");

    // Set buttons ...
    buttons[red] = new Button();
    buttons[blue] = new Button();
    buttons[yellow] = new Button();
    buttons[green] = new Button();

    buttons[red].setBackground( colors[red] );
    buttons[blue].setBackground( colors[blue] );
    buttons[yellow].setBackground( colors[yellow] );
    buttons[green].setBackground( colors[green] );

    buttons[red].addActionListener(buttonListener);
    buttons[blue].addActionListener(buttonListener);
    buttons[yellow].addActionListener(buttonListener);
    buttons[green].addActionListener(buttonListener);

    // Set Binger Layout
    setLayout(new BorderLayout());

    // Set CenterPanel with image
    if ( font != null ) {
      font = new Font( font.getName(), Font.BOLD, centerLabelNorthFontSize );
    }
    if ( font != null ) {
      centerLabelNorth.setFont( font );
    }
    centerLabelNorth.setAlignment(Label.CENTER);
    centerPanel.setLayout(new BorderLayout());
    centerPanel.add("Center", imageApplet );
    centerPanel.add("North", centerLabelNorth );

    // Create inner (Center) panel
    GridBagLayout gridbag = new GridBagLayout();
    GridBagConstraints constraints = new GridBagConstraints();
    panel.setLayout(gridbag);

    constraints.fill = GridBagConstraints.BOTH;
    constraints.insets = new Insets(2,2,2,2);
    constraints.ipadx = 1;
    constraints.ipady = 1;

    buildConstraints( constraints, 1, 0, 1, 1, 1, 1 );
    gridbag.setConstraints(buttons[red], constraints);
    buildConstraints( constraints, 0, 1, 1, 1, 1, 1 );
    gridbag.setConstraints(buttons[green], constraints);
    buildConstraints( constraints, 1, 1, 1, 1, 1, 1 );
    gridbag.setConstraints(centerPanel, constraints);
    buildConstraints( constraints, 2, 1, 1, 1, 1, 1 );
    gridbag.setConstraints(buttons[blue], constraints);
    buildConstraints( constraints, 1, 2, 1, 1, 1, 1 );
    gridbag.setConstraints(buttons[yellow], constraints);

    panel.add(buttons[red]);
    panel.add(buttons[blue]);
    panel.add(buttons[yellow]);
    panel.add(buttons[green]);
    panel.add(centerPanel);
    panel.setBackground(panelBackground);

    // Setup newGame Button
    newGameButton.addActionListener( new NewGameListener() );

    // Setup Options Panel
    Panel optionsPanel = new Panel();
    Label optionsLabel = new Label();

    if ( font != null ) {
      font = new Font( font.getName(), Font.BOLD, optionsFontSize );
    }
    if ( font != null ) {
      optionsPanel.setFont( font );
    }

    if ( lang.equals(lang_de) ) {    
      optionsLabel.setText(sOptions_de);
    } else {
      optionsLabel.setText(sOptions_en);
    }

    // Sound Option
    soundOption.setState(soundOn);
    soundOption.addItemListener( new SoundListener() );
    if ( lang.equals(lang_de) ) {
      soundOption.setLabel(sSound_de);
    } else {
      soundOption.setLabel(sSound_en);
    }

    // Speed Option from 1 (slowest) to maxSpeed (fastest)
    Label speedLabel = new Label();
    if ( lang.equals(lang_de) ) {    
      speedLabel.setText(sSpeed_de);
    } else {
      speedLabel.setText(sSpeed_en);
    }
    speedScroller = new Scrollbar( Scrollbar.HORIZONTAL, 1, 10, 1, maxSpeed + 10 );
    speedScroller.setValue(speed);
    speedScroller.addAdjustmentListener(new SpeedListener() );
    speedValueLabel = new Label( (100*speed/maxSpeed)+"%" );

    // Speed Option from 1 (slowest) to maxSpeed (fastest)
    Label startLevelLabel = new Label();
    if ( lang.equals(lang_de) ) {    
      startLevelLabel.setText(sStartLevel_de);
    } else {
      startLevelLabel.setText(sStartLevel_en);
    }
    startLevelScroller = new Scrollbar( Scrollbar.HORIZONTAL, 1, 1, 1, maxStartLevel + 1 );
    startLevelScroller.setValue(startLevel);
    startLevelScroller.addAdjustmentListener(new StartLevelListener() );
    startLevelValueLabel = new Label( ""+startLevel );

    optionsPanel.setLayout( new FlowLayout(FlowLayout.LEFT));
    optionsPanel.add(optionsLabel);
    optionsPanel.add(soundOption);
    optionsPanel.add(speedLabel);
    optionsPanel.add(speedScroller);
    optionsPanel.add(speedValueLabel);
    optionsPanel.add(startLevelLabel);
    optionsPanel.add(startLevelScroller);
    optionsPanel.add(startLevelValueLabel);

    // Setup Binger main panel
    add("North", optionsPanel );
    add("Center", panel );
    add("South", newGameButton );
  }

  /**
    * This method is called when the <b>Applet</b> should <b>start</b><br>
    * Here we use it to start the game Thread
    */
  public void start()
  {
    if ( game == null && startManual ) {

      newGameButton.setEnabled(false);
      startLevelScroller.setEnabled(false);

      startManual = false;

      game = new Thread(this);
      game.start();
    }
  }

  /**
   * This method is called when the <b>Applet</b> should <b>stop</b>
   */
  public void stop()
  {
    game = null;
  }

  /**
   * This method is called when the <b>game Thread</b> calls <b>start</b><br>
   * Here is the main implementation of the game
   */
  public void run()
  {
    Thread currentThread = Thread.currentThread();
    Random qqq = new Random();

    colorList.removeAllElements();

    // If startLevel > 1, pre-generate a sequence
    for (colorListCounter=0;colorListCounter<startLevel-1;colorListCounter++) {
      colorList.addElement( new Integer( java.lang.Math.abs(qqq.nextInt())%numButtons ) );
    }

    gameOver = false;

    while ( game == currentThread ) {
      // Clear ??? 
      centerLabelNorth.setText("");

      // Generate next random color for colorList
      colorList.addElement( new Integer( java.lang.Math.abs(qqq.nextInt())%numButtons ) );
      colorListCounter++;

      // Prepare Buttons for showColorList
      panel.setBackground(panelBackground);
      buttons[red].setBackground( colors[red].darker() );
      buttons[blue].setBackground( colors[blue].darker() );
      buttons[yellow].setBackground( colors[yellow].darker() );
      buttons[green].setBackground( colors[green].darker() );
      buttons[red].setEnabled(false);
      buttons[blue].setEnabled(false);
      buttons[yellow].setEnabled(false);
      buttons[green].setEnabled(false);

      // Show List ( Buttons from List will blink )
      showColorList( currentThread, false );

      // After List is shown ...
      // Reset buttonCounter
      buttonCounter = 0;

      // Re-Aktivate Buttons
      // panel.setBackground(panelBackground.darker());
      buttons[red].setEnabled(true);
      buttons[blue].setEnabled(true);
      buttons[yellow].setEnabled(true);
      buttons[green].setEnabled(true);

      boolean question = true;
      // Wait ... ButtonListener will handle Button Events
      while ( game == currentThread && buttonCounter < colorListCounter ) {
        try {
          Thread.sleep(pauseAfterAll*(1+maxSpeed-speed));
          if ( question ) {
            centerLabelNorth.setText("? ? ?");
            question = false;
          } else {
            centerLabelNorth.setText("");
            question = true;
          }
        } catch ( InterruptedException e ) {
        }
      }

      // Okay, so far so good
      // Give some applause ...
      if ( game == currentThread || colorListCounter > minApplause ) {
        for ( int applauses = 0; applauses < maxApplause; applauses++ ) {
          for ( int im = 0; im < numImages; im++ ) {
            image = images[im];
            imageApplet.repaint();
            try {
              Thread.sleep(1+maxSpeed-speed);
            } catch ( InterruptedException e ) {
            }
          }
          image = images[0];
          imageApplet.repaint();
        }
      }
    }

    gameOver = true;
  }

  /**
   * Blink Color List
   */
  private void showColorList( Thread currentThread, boolean fast ) 
  {
    int sleepC = (1+maxSpeed-speed);
    int listOffset = 0;
    if (fast) {
      sleepC = sleepC / 2;
    }

    centerLabelNorth.setText("("+colorListCounter+")");

    while ( game == currentThread && listOffset < colorListCounter ) {
      // Map colorTable Entry
      int c = ((Integer)colorList.elementAt(listOffset)).intValue();

      // Pause
      try {
        Thread.sleep(sleepC*pauseBeforeBlink);
      } catch ( InterruptedException e ) {
      }

      // Make button Color for item from colorList darker
      buttons[ c ].setBackground( colors[ c ] );

      // Pause
      if ( soundOn ) {
        dials[c].play();
      }
      try {
        Thread.sleep(sleepC*pauseAfterBlink);
      } catch ( InterruptedException e ) {
      }

      // Reset button Color for item from colorList
      buttons[ c ].setBackground( colors[ c ].darker() );
      listOffset++;
    }
  }

  /**
    * ActionListener for newGame Button
    */
  private class NewGameListener implements ActionListener {

    public void actionPerformed(ActionEvent evt) {
      startManual = true;
      if ( soundOn ) {
        audioBeep.play();
      }
      start();
    }
  }

  /**
    * ActionListener for GameSound
    */
  private class SoundListener implements ItemListener {

    public void itemStateChanged(ItemEvent evt) {
      Checkbox source = (Checkbox)evt.getSource();
      soundOn = source.getState();
      audioBeep.play();
    }
  }

  /**
    * ActionListener for GameSpeed
    */
  private class SpeedListener implements AdjustmentListener {

    public void adjustmentValueChanged(AdjustmentEvent evt) {
      Scrollbar source = (Scrollbar)evt.getSource();
      speed = source.getValue();
      speedValueLabel.setText( (100*speed/maxSpeed)+"%" );
    }
  }

  /**
    * ActionListener for StartLevel
    */
  private class StartLevelListener implements AdjustmentListener {

    public void adjustmentValueChanged(AdjustmentEvent evt) {
      Scrollbar source = (Scrollbar)evt.getSource();
      startLevel = source.getValue();
      startLevelValueLabel.setText( ""+startLevel );
    }
  }

  /**
    * ActionListener for Buttons
    */
  private class ButtonListener implements ActionListener {

    public void actionPerformed(ActionEvent evt) {
      Object source = evt.getSource();

      if ( buttonCounter < colorListCounter ) {
        int c = ((Integer)colorList.elementAt(buttonCounter)).intValue();
        if ( source == buttons[ c ] ) {
          // Okay, correct button was pressed
          buttonCounter++;
          // Flash Button for Confirmation
          Button button = (Button)source;
          Color bg = button.getBackground();
          button.setBackground(bg.brighter());
          if ( soundOn ) {
            dials[c].play();
          } 
          try {
            Thread.sleep(pauseFlash*(1+maxSpeed-speed));

          } catch ( InterruptedException ex ) {
          }
          button.setBackground(bg);
        } else {
          // Button was incorrect
          // Disable Buttons
          buttons[red].setEnabled(false);
          buttons[blue].setEnabled(false);
          buttons[yellow].setEnabled(false);
          buttons[green].setEnabled(false);

          if ( soundOn ) {
            audioBeep.play();
          }

          // Show agian correct Sequence
          showColorList( game, true );

          buttons[red].setBackground( colors[red] );
          buttons[blue].setBackground( colors[blue] );
          buttons[yellow].setBackground( colors[yellow] );
          buttons[green].setBackground( colors[green] );

          if ( soundOn ) {
            audioBeep.play();
          }

          stop();

          // Wait till stop was successful ( and game is over )
          while ( !gameOver ) {
            try {
              Thread.sleep(1+maxSpeed-speed);
            } catch ( InterruptedException ex ) {
            }
          }

          if ( lang.equals(lang_de) ) {
            centerLabelNorth.setText(sEndScore_de+(colorListCounter-1));
          } else {
            centerLabelNorth.setText(sEndScore_en+(colorListCounter-1));
          }

          newGameButton.setEnabled(true);
          startLevelScroller.setEnabled(true);
        }
      }
    }
  }

  /**
   * Helper function for GridBagConstraints
   */
  private void buildConstraints( GridBagConstraints gbc, 
                                 int gx, int gy, int gw, int gh, int wx, int wy ) {
    gbc.gridx = gx;
    gbc.gridy = gy;
    gbc.gridwidth = gw;
    gbc.gridheight = gh;
    gbc.weightx = wx;
    gbc.weighty = wy;
  }

  /**
    * This class implements an inner Applet, to display Images
    */
  private class ImageApplet extends Applet {

    public void init() {
    }

    public void paint( Graphics screen ) {
      if ( image != null ) {
        Dimension size = imageApplet.getSize();
        int width = size.width;
        int height = size.height;
        int imageWidth = image.getWidth(this);
        int imageHeight = image.getHeight(this);

        if ( imageWidth > width ) {
          imageWidth = width;
        }
        if ( imageHeight > height ) {
          imageHeight = height;
        }
        screen.drawImage( image, (width-imageWidth)/2, (height-imageHeight)/2, imageWidth, imageHeight, this );
      }
    }
  }
}

