Chris Pollett >
Old Classes >
PIC20B

   ( Print View )

Enrollment info

Course Info: Homework Assignments: Practice Exams: PIC:
                            












HW3 Solutions Page

Return to homework page.


//
// Filenname: Slot.java
//
// Purpose: contains classes to implement a simple slot machine
//          application which uses Threads to stop the reels of the
//          machine after they are set in motion.
//
//

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;


//
// Interface Name: ReelParts
//
// Purpose: This interface contains all the constant used for drawing the
//          slot machine. It is also implemented by Reel and ReelPanel.
//          We do this so a Reel can synchronize against either a Reel or a
//          ReelPanel.
//

interface ReelParts
{
        static String reelImageName = "reel.jpg"; // image used to draw reel
	static int BAR = 0, APPLE = 1; //enum of shapes on reel
	static int APPLEOFFSET = 120, BAROFFSET = 50; // where shapes are on image
	static int REELHT = 160, REELWTH = 290; //height and width of all reels
                                                //put together
	static int LEFTX = 0, TOPY = 0; //coordinates for three reels
	static int CENTERX = 90, RIGHTX = 180;
	static int INDICATORX=0, INDICATORY=60, INDICATORWTH=5; //coordinates
                                                                //of indicator
	static int BTNMSGHT = 40; //combined height of buttons and message label
}

//
// Class Name: Slot
//
// Purpose: main class for this application
//

public class Slot extends JFrame {

	ReelPanel rp;
	JPanel bp;
	JLabel msgLabel;

	//
	// Method Name: Slot
	//
	// Purpose: constructor. Sets up the message label the slot machine
	//          reels and the buttons for spin/inserting a coin.

	public Slot()
	{
		super("Slot Machine");
		Container c = getContentPane();
		c.setLayout(new BorderLayout());

		setBackground(Color.white);

		msgLabel = new JLabel(" "); // a message beginning with space means
                                            // we still have to insert a coin.
		c.add(msgLabel, BorderLayout.NORTH);

	        Image img = (new ImageIcon(ReelParts.reelImageName)).getImage();

		rp = new ReelPanel(img, msgLabel); //panel with slot machine reels.
		c.add(rp, BorderLayout.CENTER);

		bp = ButtonPanel(); //set up buttons
		c.add(bp, BorderLayout.SOUTH);

		setSize(ReelParts.REELWTH,ReelParts.REELHT+
                                          ReelParts.BTNMSGHT);
		show();
	}

	//
	// Method Name: ButtonPanel
	//
	// Purpose: sets up spin and insert coins buttons and their listeners
	//

	public JPanel ButtonPanel()
	{

		bp = new JPanel();
		bp.setLayout(new GridLayout(1,2));
		JButton spin = new JButton("Spin");
		spin.addActionListener(
			new ActionListener(){
			  public void actionPerformed(ActionEvent e)
			  {
				if(msgLabel.getText().startsWith(" "))
				{
				    msgLabel.setText(
                                               " Please insert coin");
				}
				else
				{
				    msgLabel.setText(" ");
				    rp.spin();
				}
			  }
			}
                	);
		bp.add(spin,0);

		JButton insertCoin = new JButton("Insert Coin");
		insertCoin.addActionListener(
			new ActionListener(){
			  public void actionPerformed(ActionEvent e)
			  {
				if(msgLabel.getText().startsWith(" "))
				{
                                      msgLabel.setText("Spin Away");
				}
				else
				{
                                      msgLabel.setText(
                                       "Return.Max 1 Coin.Please Spin.");
				}
			  }
			}
                	);
		bp.add(insertCoin,1);
		return bp;
	}


	//
	// Method Name: main
	//
	// Purpose: instantiates a Slot object and sets up a window listener for our
	//          frame

	public static void main (String args[])
	{

		Slot app = new Slot();

		app.addWindowListener( new WindowAdapter() {
			public void windowClosing(WindowEvent e)
			{
				System.exit(0);
			}

                });
	}
}

//
// Class Name: ReelPanel
//
// Purpose: panel on which the reels of the slot machine are drawn.
//          This class manages these reels and their animation.

class ReelPanel extends JPanel implements ReelParts
{
	private Reel r1, r2, r3; // three reels on slot machine
	private JLabel msgLabel; // so can say if user won on a given spin
	private int amountPaid; // how much user has been paid so far
	private Timer animationTimer; //timer used to animate reels.
	private Thread payThread; //used to stop animationTimer and (if need be)
                                  //pay user
	private boolean payout; //flag used to determine if we pay the user this spin.

	//
	// Method Name: ReelPanel
	//
	// Purpose: constructor. Stes up three Reel, animationTimer, and payoutThread
	//

	ReelPanel(Image img, JLabel mLabel)
	{

		msgLabel=mLabel;

		amountPaid=0;	//at start haven't paid user anything.

		r1 = new Reel(img,this, RIGHTX, TOPY, REELWTH, REELHT);

		r2 = new Reel(img,r1, CENTERX, TOPY, RIGHTX, REELHT);

		r3 = new Reel(img,r2, LEFTX, TOPY, CENTERX, REELHT);

		initPayoutThread();

		setBackground(Color.white);

		setSize(getPreferredSize());

	}


	//
	// Method Name: initPayoutThread
	//
	// Purpose: sets up the Timer to control reel animation and
        //          sets up a Thread to stop this animation after
	//          the reels have stopped spinning and (if need be)
        //          pay the user.
        //

	public void initPayoutThread()
	{
		animationTimer = new Timer(50, new ActionListener(){
			public void actionPerformed(ActionEvent e)
			{
				move();
				repaint();
			}
		});

		payThread = new Thread(new Runnable(){
			public void run()
			{
				while(true)
				{
                                   synchronized(r3)
				   {
					try
					{
                               			   r3.wait();
					}
					catch(InterruptedException e)
					{
					   e.printStackTrace();
					}
				    }
				    animationTimer.stop();
				    if(payout) {
					try{
					  pay();
					}
					catch (TiltException e)
				        {
					  System.err.println("Machine out of money");
				          System.exit(-1);
					}
				    }
				}
			}
		});
		payThread.start();
	}


	//
	// Method Name: move
	//
	// Purpose: updates the animation for each of the reels.
	//

	public void move()
	{
		r1.move();
		r2.move();
		r3.move();
	}

	//
	// Method Name: paintComponent
	//
	// Purpose: draws the slot machine reels and the indicator
        //          bar
	//

	public void paintComponent( Graphics g)
	{
		super.paintComponent(g);
		r1.draw(g, this);
		r2.draw(g, this);
		r3.draw(g, this);

		g.setColor(Color.yellow);
		g.fillRect(INDICATORX, INDICATORY, REELWTH, INDICATORWTH);
	}

	//
	// Method Name: pay
	//
	// Purpose: Tell user they won and handle total payout so far.
	//          If this is more than 2 dollars throw a TiltException.

	public void pay() throws TiltException
	{
		msgLabel.setText(" You won $1.00.");
		amountPaid +=1;
		if( amountPaid >2) throw new TiltException();
	}

	//
	// Method Name: spin
	//
	// Purpose: selects the final value for each reel then spins
	//          the three reels as in the description of the homework
        //          for a while until they stop on these values.

	public synchronized void spin()
	{
		payout=false;
		int tmp=0;
		int r=0;

		if(Math.random()<=.2) // win at least 20 percent of the time
		{
		    r3.start(BAR);
		    r2.start(BAR);
		    r1.start(BAR);
		    payout =true;
		}
		else //otherwise randomly choose a symbol
		{
		    tmp=r=(int)(Math.random()+.5);
		    r3.start(r);
		    tmp+=(r=(int)(Math.random()+.5));
		    r2.start(r);
		    tmp+=(r=(int)(Math.random()+.5));
		    r1.start(r);
		    if(tmp % 3 == 0) payout = true;
		}
		animationTimer.start();
		notify();
	}

	//
	// Method Name: getPreferredSize
	//
	// Purpose: returns the best size for drawing the slot machine reel
	//

	public Dimension getPreferredSize()
	{
		return new Dimension(REELWTH,REELHT);
	}

}


//
// Class Name: Reel
//
// Purpose: this class is used to manage one reel on a slot machine
//


class Reel implements ReelParts
{

	private int dx1, dy1, dx2, dy2;//location to draw reel to
	private Image img; // image used to draw reel
	private ReelParts rr; // object this Reel's stop Thread waits on
	private int offset, animSpeed; // offset controls what part of
                            //of img to draw and animSpeed is the number of
                            //pixels through the animation we go / ActionEvent
	private Thread reelStopper; //thread used to stop Reel spinning
	private boolean stopReel = false; //used to tell move() whether to stop
                                          //moving the Reel.
	private int fruitOffset; // where in img the fruit we are supposed to
                                 // stop on lives.

	//
	// Method Name: Reel
	//
	// Purpose: constructor of the class. image is the
        //          the image used to draw the reel. rightReel
        //          is the object this Reel's reelStopper Thread waits on.
	//          The destxi and destyi specify where to draw reel.

	Reel(Image image, ReelParts rightReel,
                                 int destx1, int desty1, int destx2, int desty2 )
	{

		img = image;
		rr = rightReel;

                dx1=destx1; dy1=desty1; dx2=destx2; dy2=desty2;

		offset=(int)(Math.random()*30);
		animSpeed= 15 + (int)(Math.random()*5);

		reelStopper = new Thread(new Runnable(){
			public void run()
			{
				while(true)
				{
				   synchronized(rr)
				   {
					try
					{
                               			   rr.wait();
					}
					catch(InterruptedException e)
					{
					   e.printStackTrace();
					}
				    }
                                    try{
				       Thread.sleep(1000+(int)(Math.random()*1000));
                                    }
				    catch(InterruptedException e)
				    {
                                       e.printStackTrace();
				    }

				    stopReel = true;


				}
			}
		});
		reelStopper.start();
	}

	//
	// Method Name: move()
	//
	// Purpose: If Reel should still be spun advance it by animSpeed.
	//          Otherwise, notify object waiting on this Reel.

	public void move()
	{


		if(stopReel && offset <= fruitOffset &&
                            fruitOffset <=offset+animSpeed)
		{

                   synchronized(Reel.this)
                    {      Reel.this.notify(); }
		}
		else
		{
		   offset +=animSpeed;
		   if(offset>160) offset=0;
		}
	}

	//
	// Method Name: draw
	//
	// Purpose: draws the reel with the Graphics g and ImageObsever ob
        //          passed to it.
	//

	public void draw(Graphics g, Component ob)
	{
	     g.drawImage(img, dx1, dy1, dx2, dy2, 0, 0+offset,
                                               70, REELHT+offset, ob);
	}

	//
	// Method Name: start
	//
	// Purpose: starts the reel spinning, sets up the reels
        //          final resting position.
	//
	public void start(int reelFruit)
	{
		if(reelFruit == APPLE) fruitOffset = APPLEOFFSET;

		if(reelFruit == BAR) fruitOffset = BAROFFSET;

		stopReel = false;
	}

}

//
// Class Name: TiltException
//
// Purpose: the kind of Exception thrown when the slot machine runs out of money
//

class TiltException extends Exception
{
	TiltException()
	{
		super("TiltException");
	}
}