Quiz Applet

by Jim Chapman
Home| Where we live | Jim| Isabel| James| Edward]

// Quiz Applet
// Copyright (C) 2000 Jim Chapman
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of version 2 (and no other version)
// of the GNU General Public License

// 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.

// If you require a copy of the GNU General Public License write to
// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
// Boston, MA  02111-1307, USA.
//
// Jim Chapman may be reached at: jim_chapman@turnip.demon.co.uk
//

import java.awt.Color;
import java.util.Random;
import com.objectspace.jgl.util.Randomizer;
import java.awt.Button;
import java.awt.Label;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.applet.Applet;
import java.text.NumberFormat;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import com.borland.jbcl.util.Timer;
import com.borland.jbcl.util.TimerClient;

class QuizTimerClient implements TimerClient {
  public QuizTimerClient(QuizApplet a) {
    myApplet=a;
  }
  public void timerEvent(int id) {
    myApplet.timerAlarm();
  }
  QuizApplet myApplet;
}

class Capital {
  public Capital(String c1, String c2) {
    capital=c1;
    country=c2;
  }
  public String capital;
  public String country;
}

public class QuizApplet extends Applet {
  // GUI Components:
  Button buttonStart = new Button("Start");
  Label labelQuestion = new Label("");
  Label labelAnswer = new Label("",Label.CENTER);
  Button[] answerButtons=new Button[6];
  Label labelProgress = new Label("");
  Label labelScore = new Label("HIGH SCORE");

  // Member variables for utility classes.
  QuizTimerClient myTimerClient=new QuizTimerClient(this);
  Timer myTimer=new Timer();
  Randomizer myRandom=new Randomizer();

  // Member variables for maintaining state
  int READY_TO_START =1;
  int WAITING_TO_ASK = 2;
  int WAITING_FOR_ANSWER = 3;
  int DISPLAYING_ANSWER=4;

  int currentState=0;

  int correctButton=0;
  int playersAnswer=0;
  int correctCapital=-1;

  int questions=0;
  int correct=0;
  int score=0;
  int highScore=0;

  // Member variable for managing time delays.
  long whenTimerStarted;

  //Get a parameter value
  public String getParameter(String key, String def) {
    return       (getParameter(key) != null ? getParameter(key) : def);
  }

  //Construct the applet
  public QuizApplet() {
    answerButtons[1] = new Button("1");
    answerButtons[2] = new Button("2");
    answerButtons[3] = new Button("3");
    answerButtons[4] = new Button("4");
    answerButtons[5] = new Button("5");
  }

  //Initialize the applet
  public void init() {
    try  {
      jbInit();
    }
    catch(Exception e)  {
      e.printStackTrace();
    }

    // Put the FSM into its start state.
    enterState(READY_TO_START);
  }
  //Start the applet
  public void start() {
  }

  //Stop the applet
  public void stop() {
  }

  //Destroy the applet
  public void destroy() {
  }

  //Get Applet information
  public String getAppletInfo() {
    return "Quiz Applet";
  }

  //Get parameter info
  public String[][] getParameterInfo() {
    return null;
  }

  //Component initialization
  private void jbInit() throws Exception {

    setFont(new java.awt.Font("Monospaced", 0, 12));
    setLayout(new GridLayout(0,1));
    buttonStart.setLabel("Start");
    buttonStart.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        startButtonPress();
      }
    });

    for(int i=1; i<=5; i++)
    {
      answerButtons[i].setSize(new Dimension(75, 30));
      answerButtons[i].setLabel("");
    }
    answerButtons[1].addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        answerButtonPress(1);
      }
    });
    answerButtons[2].addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        answerButtonPress(2);
      }
    });
    answerButtons[3].addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        answerButtonPress(3);
      }
    });
    answerButtons[4].addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        answerButtonPress(4);
      }
    });
    answerButtons[5].addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        answerButtonPress(5);
      }
    });

    labelScore.setText("");

    add(buttonStart);
    add(labelQuestion);
    add(labelAnswer);
    for(int i=1; i<=5; i++)
    {
      add(answerButtons[i]);
    }
    add(labelProgress);
    add(labelScore);
  }

  void answerButtonPress(int i) {
    stopTimer();
    questions++;
    playersAnswer=i;
    if(playersAnswer==correctButton) {
      correct++;
      long timeElapsed= (System.currentTimeMillis()-whenTimerStarted);
      // If the player answers in under 2 seconds, s/he gets 100 points.  If they answer after
      // more than 2 seconds but less than 4 seconds, the score is 0 for 4 seconds, 100 for 2
      // seconds, and interpolated in the obvious way between these values.
      if (timeElapsed<2000)
      {
        score+=100;
      }
      else
      {
        score+=(4000-timeElapsed)/20;
      }
    }
    enterState(DISPLAYING_ANSWER);
  }

  void startButtonPress() {
    if(currentState==READY_TO_START) {
      questions=0;
      correct=0;
      score=0;
      buttonStart.disable();
      startTimer(1000);
      enterState(WAITING_TO_ASK);
      capitalsAlreadyAsked=new boolean[capitals.length];
      for(int i=0; i<capitals.length; i++)
      {
        capitalsAlreadyAsked[i]=false;
      }
    }
    else {
      stopTimer();
      enterState(READY_TO_START);
    }
  }

  void timerAlarm() {
    if(currentState==WAITING_TO_ASK)
    {
      stopTimer();
      if(questions>=20) {
        if(score>highScore) {highScore=score;}
        enterState(READY_TO_START);
      }
      else {  // questions<20
        startTimer(100);
        enterState(WAITING_FOR_ANSWER);
      }
    }
    else if(currentState==WAITING_FOR_ANSWER)
    {
      // Set progress label to a row of N stars where N is 20 if we have 2000-4000 milliseconds
      // remaining, and N thereafter declines at a rate of one star per 100 milliseconds.
      long timeElapsed=System.currentTimeMillis()-whenTimerStarted;
      int progress=(int)(4000-timeElapsed)/20; // in range 0-200.
      if(progress>100) {progress=100;}
      String progressStr="";
      for(int i=0; i<progress/5; i++)
      {
        progressStr+="*";
      }
      labelProgress.setText(progressStr);

      if(timeElapsed>=4000) // If the player didn't answer within 4 seconds, s/he loses.
      {
        stopTimer();
        questions++;
        enterState(DISPLAYING_ANSWER);
      }
    }
  }

  void enterState(int newState) {
    currentState=newState;
    if(newState==READY_TO_START)
    {
      buttonStart.enable();
      buttonStart.setLabel("Start");
      labelAnswer.setText("");
      labelQuestion.setText("");
      labelProgress.setText("");
      for(int i=1; i<=5; i++)
      {
        answerButtons[i].disable();
        answerButtons[i].setLabel("");
        answerButtons[i].setBackground(buttonStart.getBackground()); // i.e. return it to how buttons
                                                                     // usually look.
      }

      NumberFormat nf = NumberFormat.getInstance();
      labelScore.setText("HIGH SCORE:"+nf.format(highScore));
    }
    else if (newState==WAITING_TO_ASK)
    {
      // Nothing to do.  Leave the screen displaying whatever it was showing previously,
      // and wait for a timer alarm to move us into the READY_TO_START state.
    }
    else if (newState==WAITING_FOR_ANSWER)
    {
      buttonStart.enable();
      labelAnswer.setText("");
      buttonStart.setLabel("Quit");
      labelProgress.setText("********************");

      playersAnswer=0; // The player hasn't given an answer yet.  If s/he does, this will get
                       // set to a valid button number.

      // Decide which button will be the correct answer this round;
      correctButton=myRandom.getInt(1,5);

      for(int i=1; i<=5; i++)
      {
        answerButtons[i].enable();
        answerButtons[i].setBackground(Color.lightGray);
        // Now pick a random number j, such that no button numbered less than i
        // alread has a label equal to capitals[j].capital, and such that
        // capitalsAlreadyAsked[j]==false (i.e. this capital has not already been asked).
        boolean needToPickAgain;
        int j;
        do {
          needToPickAgain=false;
          j=myRandom.getInt(0,capitals.length-1);

          if(capitalsAlreadyAsked[j])
          {
            needToPickAgain=true;
          }
          else
          {
            for(int k=1; k<i; k++)
            {
              if(answerButtons[k].getLabel()==capitals[j].capital)
              {
                needToPickAgain=true;
                break;
              }
            }
          }
        } while(needToPickAgain);
        answerButtons[i].setLabel(capitals[j].capital);
        if(i==correctButton) {
          correctCapital=j;
          capitalsAlreadyAsked[j]=true;
        }
      }

      labelQuestion.setText("What is the capital of "+capitals[correctCapital].country+"?");

      NumberFormat nf = NumberFormat.getInstance();
      labelScore.setText("SCORE: "+nf.format(score)
       + "(" + nf.format(correct)
       + "/" + nf.format(questions) +" Correct)");
    }
    else if (newState==DISPLAYING_ANSWER)
    {
      buttonStart.enable();
      buttonStart.setLabel("Quit");
      for(int i=1; i<=5; i++) {
        answerButtons[i].disable();
      }

      // Indicate the player's answer and the correct answer.
      if(playersAnswer!=0)
      {
        answerButtons[playersAnswer].setBackground(Color.red);
      }
      if(correctButton!=0)
      {
        answerButtons[correctButton].setBackground(Color.green);
      }
      if(correctCapital!=-1)
      {
        labelQuestion.setText("The capital of "+capitals[correctCapital].country+" is:");
        labelAnswer.setText(capitals[correctCapital].capital);
      }
      NumberFormat nf = NumberFormat.getInstance();
      labelScore.setText("SCORE: "+nf.format(score)
       + "(" + nf.format(correct)
       + "/" + nf.format(questions) +" Correct)");

      // If the player got the right answer, show it to him/her for one second.  If it s/he
      // got it wrong, show it for four seconds, to increase the sense of shame and sorrow.
      if (playersAnswer==correctButton)
      {
        startTimer(1000);
      }
      else
      {
        startTimer(4000);
      }
      enterState(WAITING_TO_ASK);
    }
  }

  public void startTimer(int milliseconds) {
    myTimer.startTimer(myTimerClient, 1, milliseconds, true);
    whenTimerStarted=System.currentTimeMillis();
  }

  public void stopTimer() {
    myTimer.stopTimer(myTimerClient, 1);
  }

  Capital[] capitals={
    new Capital("Kabul", "AFGHANISTAN"),
    new Capital("Algiers", "ALGERIA"),
    new Capital("Pago Pago", "AMERICAN SAMOA"),
    new Capital("Andorra la Vella", "ANDORRA"),
    new Capital("Luanda", "ANGOLA"),
    new Capital("The Valley", "ANGUILLA"),
    new Capital("St John's", "ANIGUA & BARBUDA"),
    new Capital("Buenos Aires", "ARGENTINA"),
    new Capital("Yerevan", "ARMENIA"),
    new Capital("Oranjestad", "ARUBA"),
    new Capital("Canberra", "AUSTRALIA"),
    new Capital("Vienna", "AUSTRIA"),
    new Capital("Baku", "AZERBAIJAN"),
    new Capital("Nassau", "BAHAMAS"),
    new Capital("Manama", "BAHRAIN"),
    new Capital("Dhaka", "BANGLADESH"),
    new Capital("Bridgetown", "BARBADOS"),
    new Capital("Brussels", "BELGIUM"),
    new Capital("Belmopan", "BELIZE"),
    new Capital("Minsk", "BELORUSSIA"),
    new Capital("Porto Novo", "BENIN"),
    new Capital("Hamilton", "BERMUDA"),
    new Capital("Thimpu", "BHUTAN"),
    new Capital("La Paz", "BOLIVIA"),
    new Capital("Sarajevo", "BOSNIA-HERZEGOVINA"),
    new Capital("Gaborone", "BOTSWANA"),
    new Capital("Brasilia", "BRAZIL"),
    new Capital("Bandar Seri Begawan", "BRUNEI"),
    new Capital("Sofia", "BULGARIA"),
    new Capital("Ouagadougou", "BURKINA"),
    new Capital("Rangoon", "BURMA"),
    new Capital("Bujumbura", "BURUNDI"),
    new Capital("Phnom Penh", "CAMBODIA"),
    new Capital("Yaounde", "CAMEROON"),
    new Capital("Ottawa", "CANADA"),
    new Capital("Praia", "CAPE VERDE"),
    new Capital("George Town", "CAYMAN ISLAND"),
    new Capital("Bangui", "CENTRAL AFRICAN REPUBLIC"),
    new Capital("N'Djamena", "CHAD"),
    new Capital("Santiago", "CHILE"),
    new Capital("Beijing", "CHINA"),
    new Capital("Bogota", "COLOMBIA"),
    new Capital("Moroni", "COMOROS"),
    new Capital("Brazzaville", "CONGO"),
    new Capital("Avarua on Rarotonga", "COOK ISLANDS"),
    new Capital("San Jose", "COSTA RICA"),
    new Capital("Zagreb", "CROATIA"),
    new Capital("Havana", "CUBA"),
    new Capital("Nicosia", "CYPRUS"),
    new Capital("Prague", "CZECH REPUBLIC"),
    new Capital("Copenhagen", "DENMARK"),
    new Capital("Djibouti", "DJIBOUTI"),
    new Capital("Roseau", "DOMINICA"),
    new Capital("Santo Domingo", "DOMINICAN REPUBLIC"),
    new Capital("Quito", "ECUADOR"),
    new Capital("Cairo", "EGYPT"),
    new Capital("San Salvador", "EL SALVADOR"),
    new Capital("Malabo", "EQUATORIAL GUINEA"),
    new Capital("Tallinn", "ESTONIA"),
    new Capital("Addis Ababa", "ETHIOPIA"),
    new Capital("Torshavn", "FAEROE ISLANDS"),
    new Capital("Stanley", "FALKLAND ISLANDS"),
    new Capital("Suva", "FIJI"),
    new Capital("Helsinki", "FINLAND"),
    new Capital("Paris", "FRANCE"),
    new Capital("Cayenne", "FRENCH GUIANA"),
    new Capital("Papeete", "FRENCH POLYNESIA"),
    new Capital("Libreville", "GABON"),
    new Capital("Tbilisi", "GEORGIA"),
    new Capital("Berlin", "GERMANY"),
    new Capital("Accra", "GHANA"),
    new Capital("Athens", "GREECE"),
    new Capital("Nuuk", "GREENLAND"),
    new Capital("St George's", "GRENADA"),
    new Capital("Basse-Terre", "GUADELOUPE"),
    new Capital("Agana", "GUAM"),
    new Capital("Guatemala City", "GUATEMALA"),
    new Capital("Conakry", "GUINEA"),
    new Capital("Bissau", "GUINEA-BISSAU"),
    new Capital("Georgetown", "GUYANA"),
    new Capital("Port-au-Prince", "HAITI"),
    new Capital("Tegucigalpa", "HONDURAS"),
    new Capital("Budapest", "HUNGARY"),
    new Capital("Reykjavik", "ICELAND"),
    new Capital("New Delhi", "INDIA"),
    new Capital("Jakarta", "INDONESIA"),
    new Capital("Tehran", "IRAN"),
    new Capital("Baghdad", "IRAQ"),
    new Capital("Dublin", "IRELAND"),
    new Capital("Jerusalem", "ISRAEL"),
    new Capital("Rome", "ITALY"),
    new Capital("Yamoussoukro", "IVORY COAST"),
    new Capital("Kingston", "JAMAICA"),
    new Capital("Tokyo", "JAPAN"),
    new Capital("Amman", "JORDAN"),
    new Capital("Alma-Ata", "KAZAKHSTAN"),
    new Capital("Nairobi", "KENYA"),
    new Capital("Bairiki", "KIRIBATI"),
    new Capital("Kuwait", "KUWAIT"),
    new Capital("Bishkek", "KYRGYZSTAN"),
    new Capital("Vientiane", "LAOS"),
    new Capital("Riga", "LATVIA"),
    new Capital("Beirut", "LEBANON"),
    new Capital("Maseru", "LESOTHO"),
    new Capital("Monrovia", "LIBERIA"),
    new Capital("Tripoli", "LIBYA"),
    new Capital("Vaduz", "LIECHTENSTEIN"),
    new Capital("Vilnius", "LITHUANIA"),
    new Capital("Luxembourg", "LUXEMBOURG"),
    new Capital("Skopje", "MACEDONIA"),
    new Capital("Antananarivo", "MADAGASCAR"),
    new Capital("Funchal", "MADEIRA"),
    new Capital("Lilongwe", "MALAWI"),
    new Capital("Kuala Lumpur", "MALAYSIA"),
    new Capital("Male", "MALDIVES"),
    new Capital("Bamako", "MALI"),
    new Capital("Valletta", "MALTA"),
    new Capital("Majuro", "MARSHALL ISLANDS"),
    new Capital("Fort-de-France", "MARTINIQUE"),
    new Capital("Nouakchott", "MAURITANIA"),
    new Capital("Port Louis", "MAURITIUS"),
    new Capital("Dzaoudzi", "MAYOTTE"),
    new Capital("Mexico City", "MEXICO"),
    new Capital("Kolonia", "MICRONESIA"),
    new Capital("Kishinev", "MOLDAVIA"),
    new Capital("Monaco-ville", "MONACO"),
    new Capital("Ulan Bator", "MONGOLIA"),
    new Capital("Plymouth", "MONTSERRAT"),
    new Capital("Rabat", "MOROCCO"),
    new Capital("Maputo", "MOZAMBIQUE"),
    new Capital("Windhoek", "NAMIBIA"),
    new Capital("Yaren", "NAURU"),
    new Capital("Kathmandu", "NEPAL"),
    new Capital("Willemstad", "NETHERLANDS ANTILLES"),
    new Capital("Noumea", "NEW CALEDONIA"),
    new Capital("Wellington", "NEW ZEALAND"),
    new Capital("Managua", "NICARAGUA"),
    new Capital("Niamey", "NIGER"),
    new Capital("Abuja", "NIGERIA"),
    new Capital("Alofi", "NIUE"),
    new Capital("Pyongyang", "NORTH KOREA"),
    new Capital("Saipan", "NORTHERN MARIANA ISLANDS"),
    new Capital("Oslo", "NORWAY"),
    new Capital("Muscat", "OMAN"),
    new Capital("Islamabad", "PAKISTAN"),
    new Capital("Koror", "PALAU"),
    new Capital("Panama City", "PANAMA"),
    new Capital("Port Moresby", "PAPUA NEW GUINEA"),
    new Capital("Asuncion", "PARAGUAY"),
    new Capital("Lima", "PERU"),
    new Capital("Manila", "PHILIPPINES"),
    new Capital("Warsaw", "POLAND"),
    new Capital("Lisbon", "PORTUGAL"),
    new Capital("San Juan", "PUERTO RICO"),
    new Capital("Doha", "QATAR"),
    new Capital("Saint-Denis", "REUNION"),
    new Capital("Bucharest", "ROMANIA"),
    new Capital("Moscow", "RUSSIAN FEDERATION"),
    new Capital("Kigali", "RWANDA"),
    new Capital("San Marino", "SAN MARINO"),
    new Capital("Sao Tome", "SAO TOME AND PRINCIPE"),
    new Capital("Riyadh", "SAUDI ARABIA"),
    new Capital("Dakar", "SENEGAL"),
    new Capital("Victoria", "SEYCHELLES"),
    new Capital("Freetown", "SIERRA LEONE"),
    new Capital("Singapore City", "SINGAPORE"),
    new Capital("Bratislava", "SLOVAKIA"),
    new Capital("Ljubljana", "SLOVENIA"),
    new Capital("Honiara", "SOLOMON ISLANDS"),
    new Capital("Mogadishu", "SOMALIA"),
    new Capital("Cape Town", "SOUTH AFRICA"),
    new Capital("Seoul", "SOUTH KOREA"),
    new Capital("Madrid", "SPAIN"),
    new Capital("Colombo", "SRI LANKA"),
    new Capital("Jamestown", "ST HELENA"),
    new Capital("Basseterre", "ST KITTS-NEVIS"),
    new Capital("Castries", "ST LUCIA"),
    new Capital("Saint-Pierre", "ST PIERRE AND MIQUELON"),
    new Capital("Kingstown", "ST VINCENT AND THE GRENADINES"),
    new Capital("Khartoum", "SUDAN"),
    new Capital("Paramaribo", "SURINAM"),
    new Capital("Mbabane", "SWAZILAND"),
    new Capital("Stockholm", "SWEDEN"),
    new Capital("Berne", "SWITZERLAND"),
    new Capital("Damascus", "SYRIA"),
    new Capital("Taipei", "TAIWAN"),
    new Capital("Dushanbe", "TAJIKSTAN"),
    new Capital("Dodoma", "TANZANIA"),
    new Capital("Bangkok", "THAILAND"),
    new Capital("Banjul", "THE GAMBIA"),
    new Capital("Amsterdam", "THE NETHERLANDS"),
    new Capital("Lome", "TOGO"),
    new Capital("Nuku'alofa", "TONGA"),
    new Capital("Port of Spain", "TRINIDAD AND TOBAGO"),
    new Capital("Tunis", "TUNISIA"),
    new Capital("Ankara", "TURKEY"),
    new Capital("Cockburn Town", "TURKS AND CAICOS ISLANDS"),
    new Capital("Funafuti", "TUVALU"),
    new Capital("Kampala", "UGANDA"),
    new Capital("Kiev", "UKRAINE"),
    new Capital("Abu Dhabi", "UNITED ARAB EMIRATES"),
    new Capital("London", "UNITED KINGDOM"),
    new Capital("Washington, D.C.", "UNITED STATES OF AMERICA"),
    new Capital("Montevideo", "URAGUAY"),
    new Capital("Tashkent", "UZBEKISTAN"),
    new Capital("Port-Vila", "VANATU"),
    new Capital("Caracas", "VENEZUELA"),
    new Capital("Hanoi", "VIETNAM"),
    new Capital("Road Town", "VIRGIN ISLANDS (U.K.)"),
    new Capital("Charlotte Amalie", "VIRGIN ISLANDS (U.S.A.)"),
    new Capital("Mata-Utu", "WALLIS AND FUTUNA ISLANDS"),
    new Capital("Apia", "WESTERN SAMOA"),
    new Capital("San'a", "YEMEN"),
    new Capital("Belgrade", "YUGOSLAVIA"),
    new Capital("Kinshasa", "ZAIRE"),
    new Capital("Lusaka", "ZAMBIA"),
    new Capital("Harare", "ZIMBABWE")
  };

  boolean[] capitalsAlreadyAsked;
}



home@jim-chapman.net