Yatzy.java

Denna kod är public domain. Om ni hittar fel eller vill ändra något i koden blir jag jätteglad om ni skickar dessa ändringar till jesper [at] fantasi [punkt] se.


/*
 * You may play this game and more on http://www.fantasi.se/
 *
 * You will also need the following images:
 * dot.gif, dice.gif, hold.gif, newgame.gif, roll.gif, hiscore.gif
 *
 * To get the HiScore list working, you will need the Perl script yatzy.pl
 * This script is called as a cgi and must be on a web server supporting
 * cgi's. To play the game on a local host, the methods loadHiScore() and
 * saveHiScore() need some rewriting to access a local file instead.
 */

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.io.*;
import java.net.*;
import java.util.Date;
import java.text.DateFormat;

public class Yatzy extends JApplet implements ActionListener
{
   public static final long serialVersionUID = 0;
   private CardLayout layout;
   private JButton hi2game, addName, game2hi, newGame;
   private JLabel points;
   private JTextField name;
   private JLabel hiName[] = new JLabel[15];
   private JLabel score[] = new JLabel[15];
   private JLabel date[] = new JLabel[15];
   private YatzyDice t[] = new YatzyDice[5];
   private Thread tr[] = new Thread[5];
   private JLabel txt[] = new JLabel[18];
   private JButton but[] = new JButton[15];
   private boolean change[] = new boolean[18];
   private Font plain = new Font("SansSerif", Font.PLAIN, 12);
   private Font bold = new Font("SansSerif", Font.BOLD, 12);
   private Container gameContainer;

   protected int turns = 0;
   protected JButton roll;

   public Yatzy()
   {
      /* Tell the applet to ignore the system event queue to avoid
       * error messages in JDK 1.1 and JFC 1.1
       */
      getRootPane().putClientProperty("defeatSystemEventQueueCheck", Boolean.TRUE);
   }

   public void init()
   {
      gameContainer = getContentPane();
      gameContainer.setBackground(Color.white);

      /*
       * The game view layout
       */

      /* We need to catch exceptions thrown by URL creation */
      try {
         newGame = new JButton(new ImageIcon(new URL(getCodeBase(), "newgame.gif")));
         newGame.setBorderPainted(false);
         newGame.setMargin(new Insets(0, 0, 0, 0));
         newGame.setBackground(Color.white);
         newGame.addActionListener(this);
         roll = new JButton(new ImageIcon(new URL(getCodeBase(), "roll.gif")));
         roll.setBorderPainted(false);
         roll.setMargin(new Insets(0, 0, 0, 0));
         roll.setBackground(Color.white);
         roll.addActionListener(this);
         game2hi = new JButton(new ImageIcon(new URL(getCodeBase(), "hiscore.gif")));
         game2hi.setBorderPainted(false);
         game2hi.setMargin(new Insets(0, 0, 0, 0));
         game2hi.setBackground(Color.white);
         game2hi.addActionListener(this);
      }
      catch (IOException e) {
         System.out.println("Error while loading graphics! " + e);
      }

      /* Create five dices and five threads to run them */
      for (int i = 0; i < 5; i++) {
         t[i] = new YatzyDice(this);
         tr[i] = new Thread(t[i]);
      }

      JPanel scoreBoard = whitePanel();
      GridBagLayout gb = new GridBagLayout();
      GridBagConstraints gbc = new GridBagConstraints();
      gbc.fill = GridBagConstraints.BOTH;
      scoreBoard.setLayout(gb);
      addYatzyLine(scoreBoard, gb, gbc, "Ones", 0);
      addYatzyLine(scoreBoard, gb, gbc, "Deuces", 1);
      addYatzyLine(scoreBoard, gb, gbc, "Threes", 2);
      addYatzyLine(scoreBoard, gb, gbc, "Fours", 3);
      addYatzyLine(scoreBoard, gb, gbc, "Fives", 4);
      addYatzyLine(scoreBoard, gb, gbc, "Sixes", 5);
      addYatzyLine(scoreBoard, gb, gbc, "Sum", 15);
      addYatzyLine(scoreBoard, gb, gbc, "Bonus", 16);
      addYatzyLine(scoreBoard, gb, gbc, "One Pair", 8);
      addYatzyLine(scoreBoard, gb, gbc, "Two Pairs", 9);
      addYatzyLine(scoreBoard, gb, gbc, "Three of a kind", 10);
      addYatzyLine(scoreBoard, gb, gbc, "Four of a kind", 11);
      addYatzyLine(scoreBoard, gb, gbc, "Full house", 12);
      addYatzyLine(scoreBoard, gb, gbc, "Small Straight", 13);
      addYatzyLine(scoreBoard, gb, gbc, "Large Straight", 14);
      addYatzyLine(scoreBoard, gb, gbc, "Chans", 6);
      addYatzyLine(scoreBoard, gb, gbc, "Yatzy", 7);
      addYatzyLine(scoreBoard, gb, gbc, "Total", 17);

      JPanel control = whitePanel();
      control.setLayout(new GridLayout(9, 1));
      for (int i = 0; i < 5; i++) {
         control.add(t[i]);
         t[i].setEnabled(false);
      }
      control.add(whitePanel());
      control.add(roll);
      control.add(game2hi);
      control.add(newGame);

      JPanel gameView = whitePanel();
      gameView.setLayout(new BorderLayout());
      gameView.add("Center",scoreBoard);
      gameView.add("East",control);

      /*
       * The hiscore layout
       */

      hi2game = new JButton("Back to the game");
      hi2game.addActionListener(this);

      JPanel names = whitePanel();
      names.setLayout(new GridLayout(18, 1));
      names.add(new JLabel("Name:"));
      for (int n = 0; n < 15; n++) {
         hiName[n] = new JLabel("-");
         names.add(hiName[n]);
      }
      names.add(whitePanel());
      names.add(hi2game);

      JPanel scores = whitePanel();
      scores.setLayout(new GridLayout(18, 1));
      scores.add(new JLabel("Score:"));
      for (int i = 0; i < 15; i++) {
         score[i] = new JLabel("0");
         scores.add(score[i]);
      }
      scores.add(whitePanel());
      scores.add(whitePanel());

      JPanel dates = whitePanel();
      dates.setLayout(new GridLayout(18, 1));
      dates.add(new JLabel("Date:"));
      for (int i = 0; i < 15; i++) {
         date[i] = new JLabel("-");
         dates.add(date[i]);
      }
      dates.add(whitePanel());
      dates.add(whitePanel());

      JPanel hiscoreView = whitePanel();
      hiscoreView.setLayout(new BorderLayout());
      hiscoreView.add("West",scores);
      hiscoreView.add("Center",names);
      hiscoreView.add("East",dates);


      /*
       * The 'enter name' layout
       */

      name = new JTextField(25); 
      addName = new JButton("Save Hiscore");
      addName.addActionListener(this);
      points = new JLabel("0", JLabel.CENTER);

      JPanel nameView = whitePanel();
      nameView.setLayout(new GridLayout(17, 1));
      nameView.add(whitePanel());
      nameView.add(whitePanel());
      nameView.add(whitePanel());
      nameView.add(whitePanel());
      nameView.add(new JLabel("Congratulations! You've entered the Hall of fame!",
                              JLabel.CENTER));
      nameView.add(whitePanel());
      nameView.add(new JLabel("Enter your name:", JLabel.CENTER));
      nameView.add(name);
      nameView.add(whitePanel());
      nameView.add(whitePanel());
      nameView.add(new JLabel("Your total score:", JLabel.CENTER));
      nameView.add(points);
      nameView.add(whitePanel());
      nameView.add(addName);
      nameView.add(whitePanel());
      nameView.add(whitePanel());
      nameView.add(whitePanel());

      /*
       * Put them together in a CardLayout
       */

      layout = new CardLayout();

      gameContainer.setLayout(layout);
      gameContainer.add(gameView, "game");
      gameContainer.add(hiscoreView, "hisc");
      gameContainer.add(nameView, "name");
   }

   private JPanel whitePanel()
   {
      JPanel p = new JPanel();
      p.setOpaque(false);
      return p;
   }

   private void addYatzyLine(JPanel p, GridBagLayout gb,
                             GridBagConstraints gbc, String title, int n)
   {
      gbc.gridwidth = GridBagConstraints.RELATIVE;
      if (n < 15) {
         but[n] = new JButton(title);
         gb.setConstraints(but[n], gbc);
         but[n].setBackground(Color.white);
         but[n].setEnabled(false);
         but[n].addActionListener(this);
         p.add(but[n]);
      }
      else {
         JLabel l = new JLabel(title);
         gb.setConstraints(l, gbc);
         l.setBackground(Color.white);
         p.add(l);
      }

      gbc.gridwidth = GridBagConstraints.REMAINDER;
      gbc.weightx = 2.0;
      txt[n] = new JLabel("-", JLabel.CENTER);
      gb.setConstraints(txt[n], gbc);
      txt[n].setBackground(Color.white);
      p.add(txt[n]);
      change[n] = true;
   }

   /*
    * Handles all the buttons in the game
    */
   public void actionPerformed(ActionEvent e)
   {
      // 'Back to the game' from the HiScore list
      if (e.getSource() == hi2game) {
         layout.show(gameContainer, "game");
         return;
      }

      // 'Save HiScore' from Hall of fame
      if (e.getSource() == addName) {
         saveHiScore();
         loadHiScore();
         layout.show(gameContainer, "hisc");
         return;
      }

      // Roll the dices
      if (e.getSource() == roll) {
         roll.setEnabled(false);
         turns++;
         for (int i = 0; i < 5; i++) {
            tr[i] = new Thread(t[i]);
            tr[i].start();
         }
         return;
      }

      // New game
      if (e.getSource() == newGame) {
         for (int i = 0; i < 5; i++)
            if (tr[i].isAlive())
               t[i].stop();
         diceCleanup();
         clearScore();
         roll.setEnabled(true);
         return;
      }

      // Show the HiScore
      if (e.getSource() == game2hi) {
         loadHiScore();
         layout.show(gameContainer, "hisc");
         return;
      }

      // Place score during game
      for (int i = 0; i < 15; i++) {
         if (e.getSource() == but[i])
         {
            change[i] = false;
            txt[i].setFont(plain);
            diceCleanup();

            for (i = 0; i < 15; i++) {
               but[i].setEnabled(false);
               if (change[i]) {
                  txt[i].setText("-");
                  txt[i].setFont(plain);
               }
            }
            txt[15].setText(getSum());
            txt[16].setText(getBon());
            txt[17].setText(getTot());

            boolean cont = false;
            for (int j = 0; j < 15; j++)
               cont = cont || change[j];

            if (!cont) {
               roll.setEnabled(false);
               String tot = getTot();
               loadHiScore();
               if (str2int(tot) > str2int(score[14].getText())) {
                  points.setText(tot);
                  layout.show(gameContainer, "name");
               }
            }
            else
               roll.setEnabled(true);
            return;
         }
      }
   }


   /*
    * Reset dices
    */
   private void diceCleanup()
   {
      turns = 0;
      for (int i = 0; i < 5; i++)
         t[i].cleanup();
   }


   /*
    * Calculate and place current result. This is the heart of the
    * game.  Here the dices values are turned into scores. It might
    * look a bit confusing but it's acually quite fast...
    */
   protected void checkScore()
   {
      /* Both arrays below are indexed with the value of a dice (1 - 6) 
       * of course arrays are indexed from zero, which means that we use
       * value - 1 as index.
       */
      int     re[] = new int[6];
      boolean ex[] = new boolean[6];
      int     n;

      /* Init each variable to -1 to mark that we have not found any
       * pair etc yet
       */
      int     pair1 = -1, pair2 = -1, thok = -1, fook = -1, fiok = -1;
      boolean bigstr = false, smallstr = false;

      /* First, step through the five dices and count how many of
       * each value there are.
       */
      for (int i = 0; i < 5; i++)
         re[t[i].getValue() - 1]++;

      /* For each value, set the existance flag if the value was
       * represented on any dice.
       */
      for (int i = 0; i < 6; i++) {
         if (re[i] > 0)
            ex[i] = true;
         else
            ex[i] = false;
      }

      /* Step through values, start from the highest. If we find
       * more than four of any value, stop immediately.
       */
      for (n = 5; (n > -1) && (re[n] < 4); n--) {
         if (re[n] > 2)
            thok = n; /* Found three of a kind */
         if (re[n] > 1) {
            /* We found a pair. If this is the first pair set
             * pair1 to this value otherwise set pair 2.
             */
            if (pair1 == -1)
               pair1 = n;
            else
               pair2 = n;
         }
      }

      /* If n reached -1 in the previous for loop, we did not find
       * the same value on more than at most three dices. Otherwise,
       * set the four of a kind and perhaps even the five of a kind
       * variable to what value the dices had.
       */
      if (n != -1) {
         pair1 = n; thok = n; fook = n;
         if (re[n] == 5)
            fiok = n;
      }
      /* So n did reach -1.. Then we might have a straight,
       * check the corresponding flags.
       */
      else if (ex[0] && ex[1] && ex[2] && ex[3] && ex[4])
         smallstr = true;
      else if (ex[1] && ex[2] && ex[3] && ex[4] && ex[5])
         bigstr = true;

      /* Display score in the first six fields */
      for (int i = 0; i < 6; i++)
         if (change[i]) {
            but[i].setEnabled(true);
            txt[i].setText(re[i] * (i + 1) + "");
            txt[i].setFont(bold);
         }

      /* Set zeros in the rest */
      for (int i = 6; i < 15; i++)
         if (change[i]) {
            but[i].setEnabled(true);
            txt[i].setText("0");
            txt[i].setFont(bold);
         }

      if (pair1 != -1) {
         /* We found a pair! */

         if (change[8])
            txt[8].setText((Math.max(pair1,pair2) + 1) * 2 + "");

         if (pair2 != -1) {
            /* We found another pair! */
            if (change[9])
               txt[9].setText(pair1 * 2 + pair2 * 2 + 4 + "");

            if (thok != -1) {
               /* We have three of a kind, that means we also
                * have a full house!
                */
               if (change[10])
                  txt[10].setText(thok * 3 + 3 + "");

               if ((thok == pair1) && change[12]) 
                  txt[12].setText(thok * 3 + pair2 * 2 + 5 + "");
               else if (change[12])
                  txt[12].setText(thok * 3 + pair1 * 2 + 5 + "");
            }
         }
         else if (thok != -1) {
            /* We did not have two pairs, but we might still have
             * three, four and five of a kind.
             */
            if (change[10])
               txt[10].setText(thok * 3 + 3 + "");

            if (fook != -1) {
               if (change[11])
                  txt[11].setText(fook * 4 + 4 + "");

               if ((fiok != -1) && change[7]) 
                  txt[7].setText("50");             
            }
         }
      }
      /* No pair? Then we might have a straight. */
      else if (smallstr && change[13])
         txt[13].setText("15");
      else if (bigstr && change[14])
         txt[14].setText("20");

      /* Set the sum field */
      if (change[6])
         txt[6].setText(t[0].getValue() + t[1].getValue() +
                        t[2].getValue() +
                        t[3].getValue() + t[4].getValue() + "");
   }

   /*
    * Clear the scoreboard before a new game
    */
   private void clearScore()
   {
      for (int i = 0; i < 15; i++)
         but[i].setEnabled(false);

      for (int i = 0; i < 18; i++) {
         change[i] = true;
         txt[i].setText("-");
         txt[i].setFont(plain);
      }
   }

   /*
    * Converts string numbers to ints and '-' to zero
    */
   private int str2int(String str)
   {
      if (str != "-")
         return Integer.parseInt(str);
      else
         return 0;
   }

   /*
    * Calculates the upper sum
    */
   private String getSum()
   {
      int sum = 0;

      for (int i = 0; i < 6; i++)
         sum = sum + str2int(txt[i].getText());
      return sum + "";
   }

   /*
    * Check for bonus
    */
   private String getBon()
   {
      if (str2int(txt[15].getText()) >= 63)
         return "50";
      else
         return "0";
   }

   /*
    * Calculate the total score
    */
   private String getTot()
   {
      int tot = 0;

      for (int i = 6; i < 17; i++)
         tot = tot + str2int(txt[i].getText());
      return tot + "";
   }

    /*
     * Read the HiScore from a file
     */
   private void loadHiScore()
   {
      try {
         URL url = new URL(this.getCodeBase(), "Yatzy.hi");
         BufferedReader indata =
            new BufferedReader(new InputStreamReader(url.openStream()));

         for (int i = 0; i < 15; i++) {
            hiName[i].setText(indata.readLine());
            score[i].setText(indata.readLine());
            date[i].setText(indata.readLine());
         }
         indata.close();
      }
      catch (IOException e) {
         for (int i = 0; i < 15; i++) {
            hiName[i].setText("Yatzy");
            score[i].setText("0");
            date[i].setText("someday");
         }
      }
   }

   /*
    * Send score to the HiScore CGI.
    * The Perl CGI is also available from http://www.fantasi.se/jesper
    */
   private void saveHiScore()
   {
      try {
         Date d = new Date();
         DateFormat df = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
         URL url = new URL("http://url.to.server/yatzy.pl?" +
                           "name=" + name.getText() + "&score=" + getTot() +
                           "&date=" + df.format(d));
         BufferedReader indata =
            new BufferedReader(new InputStreamReader(url.openStream()));
         String tmp = indata.readLine();
         System.out.println(tmp);
      }
      catch (RuntimeException e) {
         System.err.println(e.getMessage());
         return;
      }
      catch (MalformedURLException e) {
         System.err.println(e.getMessage());
         return;
      }
      catch (IOException e) {
         System.err.println(e.getMessage());
         return;
      }
   }
}

/*
 * A threaded dice
 */
class YatzyDice extends JPanel implements Runnable
{
   public static final long serialVersionUID = 0;
   private int       value;
   private boolean   lock;
   private boolean   stopped;
   private Yatzy     theGame;

   // Should be ImageIcon...
   private Image imgDice, imgDot, imgHold;

   YatzyDice( Yatzy pan )
   {
      theGame = pan;
      value   = -1;
      lock    = false;
      imgDice = theGame.getImage(theGame.getCodeBase(), "dice.gif");
      imgDot  = theGame.getImage(theGame.getCodeBase(), "dot.gif");
      imgHold = theGame.getImage(theGame.getCodeBase(), "hold.gif");
      setBackground(Color.white);
      addMouseListener(new DiceMouseListener());
   }

   /*
    * Make the dice look all new and shiny
    */
   public void cleanup()
   {
      setEnabled(true);
      lock = false;
      value = -1;
      repaint();
   }

   /*
    * Let the dice roll for a while before giving final result
    */
   public void roll()
   {
      for(int i = 0; i < 20; i++) {
         if (stopped)
            return;

         if (!lock) {
            value = (int)(Math.random() * 6) + 1;
            repaint();
         }
         try {
            Thread.sleep(70);
         }
         catch (InterruptedException e) {
            System.err.println(e.getMessage());
         }
      }

      if (theGame.turns >= 3) {
         lock = true;
         repaint();
      }
      else
         theGame.roll.setEnabled(true);
   }

   /*
    * Do you feel lucky?
    */
   public void run()
   {
      stopped = false;
      setEnabled(false);
      roll();
      if (!stopped) {
         theGame.checkScore();
         if (theGame.turns < 3)
            setEnabled(true);
      }
   }

   /*
    * Some small get and set methods...
    */
   public void stop()        { stopped = true; }
   public int getValue()     { return value;   }
   public boolean isLocked() { return lock;    }

   /*
    * Listen for those tiny squeek noises only a mouse can make
    */
   class DiceMouseListener extends MouseAdapter
   {
      public void mousePressed( MouseEvent e )
      {
         if (lock && (theGame.turns < 3))
            lock = false;
         else if (!lock && (value != -1))
            lock = true;

         repaint();
      }
   }

   /*
    * A not entirely stupid way of drawing a dice accually
    */
   public void paintComponent(Graphics g)
   {
      super.paintComponent(g);
      if (lock)
         g.drawImage(imgHold, 52, 18, this);
      else {
         g.setColor(Color.white);
         g.fillRect(52, 18, 30, 14);
      }

      g.drawImage(imgDice, 0, 0, this);

      if (value != -1) {
         if ((value % 2) == 1)
            g.drawImage(imgDot, 21, 21, this);
         if (value >= 2) {
            g.drawImage(imgDot, 31, 11, this);
            g.drawImage(imgDot, 11, 31, this);
            if (value >= 4) {
               g.drawImage(imgDot, 11, 11, this);
               g.drawImage(imgDot, 31, 31, this);
               if (value == 6) {
                  g.drawImage(imgDot, 11, 21, this);
                  g.drawImage(imgDot, 31, 21, this);
               }
            }
         }
      }
   }
}