Billiards

<< Bounce | FinalProjectsTrailIndex | DuckyLove >>

In Carom (or Spanish) Billiards there is a cue ball and 2 other balls, and no pockets. The idea is to hit three cushions with the cue ball call before hitting the last ball. This is a little like that game, but there is only one target ball and one cue ball. Try to hit three or more cushions before hitting the red ball.

This demonstrates a more complex user interface using both MouseMotionEvents and MouseEvents

Here is a Link to a working copy

You'll need these sound files:

Ball.java


import java.awt.Color;
import java.awt.Graphics;
import java.awt.geom.Rectangle2D;
public class Ball {
	private int x;
	private int y;
	private int radius;
	private int rise;
	private int run;
	private int speed;
	private Color color;
	private boolean visable; 
	public Ball(int radius){
		x=randInt(350,450);
		y=randInt(250,350);
		this.radius=radius;
		rise=randInt(1,8);
		run=randInt(-8,8);
		setVisable(false);
		setColor(Color.BLUE);
		setSpeed(1);
	}
	public int randInt(int min, int max){
		return min+(int)((max-min)*Math.random());
	}
	public void move(){
		x+=speed*run;
		y+=speed*rise;
	}
	public void bounceVertical(){
		rise*=-1;
	}
	public void bounceHorizontal(){
		run*=-1;
	}
	public void draw(Graphics g){
		if (visable){
			Color c=g.getColor();
			g.setColor(color);
			g.fillOval(x-radius, y-radius, 2*radius, 2*radius);
			g.setColor(c);
		}		
	}
	public void setVisable(boolean visable) {
		this.visable = visable;
	}
	public boolean isVisable() {
		return visable;
	}
	public Rectangle2D area(){
		return new Rectangle2D.Double(x-radius, y-radius, 2*radius, 2*radius);
	}
	public void setRise(int rise){ this.rise=rise;}
	public void setRun(int run){ this.run=run;}
	public int getRise(){return rise;}
	public int getRun(){return run;}
	public void setX(int x){ this.x=x;}
	public void setY(int y){ this.y=y;}
	public int getX(){return x;}//center x
	public int getY(){return y;}//center y
	public int getRadius(){return radius;}
	public void setColor(Color c){ this.color=c;}
	public Color getColor(){return this.color;}
	public void setSpeed(int speed) {this.speed = speed;}
	public int getSpeed() {return speed;}
}

PoolBall.java

import java.awt.Color;


public class PoolBall extends Ball 
{
	private int energy;
	public PoolBall(int radius) {
		super(radius);
		energy=0;
	}
	public PoolBall(Color color, int radius) {
		super(radius);
		energy=0;
		setColor(color);
	}
	/** a passive hit from a stick or another ball
	 * 
	 * @param angle
	 * @param force
	 */
	public void hit(double angle, double force){
		energy=(int)(0.5*force);
		setRise((int)(5*Math.sin(angle)));
		setRun((int)(5*Math.cos(angle)));
		setSpeed(3);
	}
	public void strike(PoolBall b){
		double run=b.getX()-this.getX();
		double rise=b.getY()-this.getY();
		double relationAngle =Math.atan2(rise, run);
		//double pathAngle=Math.atan2(getRise(), getRun());
		double theta=0.5*Math.PI-relationAngle;
		setRise((int)(5*Math.sin(theta)));
		setRun((int)(5*Math.cos(theta)));
		b.hit(relationAngle, energy);
	}
	public void move(){
		if (energy<=0)
			return; 
		energy=995*energy/1000;
		if (energy< 50)
			setSpeed(1);
		super.move();
	}
	public void lowerSpeed(){
		int s=getSpeed();
		s--;
		if (s>0)
			setSpeed(s);
	}
	public int getEnergy(){ return energy;}
	public void setEnergy(int energy){ this.energy=energy;}
	public boolean touches(PoolBall target) {
		return this.area().intersects(target.area());
	}
}

PoolTable.java


import java.awt.Color;
import java.awt.Graphics;
import java.awt.geom.Rectangle2D;


public class PoolTable {
	private int top;
	private int bottom;
	private int left;
	private int right;
	private Color color;
	private Color bgColor;
	public PoolTable(int left, int right, int top, int bottom){
		this.top=top;
		this.bottom=bottom;
		this.left=left;
		this.right=right;
		this.bgColor=Color.black;
		this.color=Color.GREEN;
	}
	public PoolTable(){
		this(100,700,100,400);
	}
	public void draw(Graphics g){
		Color old=g.getColor();
		g.setColor(bgColor);
		g.fillRect(0, 0, 800, 600);
		g.setColor(color);
		g.fillRect(left, top, right-left, bottom-top);
		g.setColor(old);
	}
	/**
	 * this will test if ball is close enough to a rail
	 * tell the ball to change direction
	 * and return whether or not there were changes made
	 * @param b
	 * @return
	 */
	public boolean touches(PoolBall b){
		boolean bounce=false;
		Rectangle2D r=b.area();
		double bRt=r.getWidth()+r.getX();
		double bBtm=r.getHeight()+r.getY();
		double bLt=r.getX();
		double bTop=r.getY();

		if (bRt>=right){
			b.setX(right-b.getRadius());
		 	b.setRun(-1*(int)Math.abs(b.getRun()));
		    bounce=true;
		}
		if (bLt<=left){
			b.setX(left+b.getRadius());
		    b.setRun((int)Math.abs(b.getRun()));
		    bounce=true;
		}
		if (bBtm>=bottom){
			b.setY(bottom-b.getRadius());
			b.setRise(-1*(int)Math.abs(b.getRise()));
			bounce=true;
		}
		if (bTop<=top){
			b.setY(top+b.getRadius());
			b.setRise((int)Math.abs(b.getRise()));
			bounce=true;
		}
		if (bounce)
			b.lowerSpeed();
		return bounce;
	}
}

PoolStick.java


import java.awt.Color;
import java.awt.Graphics;


public class PoolStick {
	private boolean visible;
	private Color color;
	private int[] xPoints;
	private int[] yPoints;
	private int rise;
	private int run;
	private double angle;
	private double force;
	public PoolStick(Color c){
		color=c;
		setVisible(false);
		xPoints=new int[3];
		yPoints=new int[3];
		for(int i=0;i<3;i++){
			xPoints[i]=0;
			yPoints[i]=0;
		}
		rise=0;run=0;	
	}
	public void setVisible(boolean visable) {
		this.visible = visable;
	}
	public boolean isVisible() {
		return visible;
	}
	public void setTip(int x, int y){
		xPoints[0]=x;
		yPoints[0]=y;
	}
	public void setEnd(int x, int y){
		rise = (yPoints[0]-y);
		run  = (xPoints[0]-x);
		double a=Math.atan2(run, rise);
		angle=Math.atan2(rise, run);
		force=Math.sqrt(rise*rise+run*run);
		xPoints[1]=x-(int)(15*Math.cos(a));
		yPoints[1]=y+(int)(15*Math.sin(a));
		xPoints[2]=x+(int)(15*Math.cos(a));
		yPoints[2]=y-(int)(15*Math.sin(a));
	}
	public void draw(Graphics g){
		if(visible){
			Color old=g.getColor();
			g.setColor(color);
			g.fillPolygon(xPoints, yPoints, 3);
			g.setColor(old);
		}
	}
	public int getRise(){return rise;}
	public int getRun(){return run;}
	public int getAngleDeg(){return (int)(180*angle/Math.PI);}
	public double getAngle(){return angle;}
	public double getForce(){return force;}
}

Sound.java

import java.applet.*;
import java.net.URL;
public class Sound{
	private AudioClip clip;
	public Sound(String fileName, Applet a){
		URL soundToPlay = getClass().getResource(fileName);		
		clip = a.getAudioClip(soundToPlay);
	}
	public void play(){
		clip.play();
	}
	public void loop(){
		clip.loop();
	}
}

Billiards.java

import java.applet.Applet;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import javax.swing.Timer;



@SuppressWarnings("serial")
public class Billiards extends Applet implements MouseMotionListener, MouseListener, ActionListener
{
	private PoolStick stick;
	private PoolBall cueBall, target1;

	private PoolTable table;
	Timer timer;
	int frame;
	int count;
	int score;
	Sound hit, cue, bounce;
	boolean target1Hit;
	Image virtualMem;
    Graphics gBuffer;
	public void init(){
		stick = new PoolStick(Color.red);
		cueBall = new PoolBall(Color.white, 10);
		target1 = new PoolBall(Color.red, 10);
		target1.setVisable(true);
		timer = new Timer(20, this);
		frame=0;
		count=0;
		score=0;
		target1Hit=false;
		cueBall.setVisable(true);
		this.addMouseListener(this);
		this.addMouseMotionListener(this);
		table = new PoolTable();
        virtualMem = createImage(800,600);
        gBuffer = virtualMem.getGraphics();
        hit = new Sound("hit.wav",this);
        cue =  new Sound("cue.wav", this);
        bounce = new Sound("bounce.wav",this);
	}
	public void paint(Graphics g){

		gBuffer.setColor(Color.green);
        gBuffer.fillRect(0,0,getWidth(), getHeight());

		table.draw(gBuffer);
        cueBall.draw(gBuffer);
        target1.draw(gBuffer);
		gBuffer.setColor(Color.white);
		gBuffer.drawString("Click and drag to make pool stick to hit the ball", 20, 20);
		gBuffer.drawString("Strike 3 or more cushions before hitting red Ball", 20, 445);
		gBuffer.drawString("angle="+(-1*stick.getAngleDeg())+" force="+stick.getForce(), 20, 50);
		String result="Red ball ";
		if (!target1Hit)
			result+="not ";
		gBuffer.drawString(result+"hit after "+count+" bounces", 20, 460);
		gBuffer.drawString("Score: "+score, 20, 475);

		stick.draw(gBuffer);
		//Now we send the result to the screen
        g.drawImage(virtualMem,0,0,this);
	}
	public void update(Graphics g)
    {
        paint(g); //get rid of flicker with this method
    }
	@Override
	public void mouseDragged(MouseEvent e) {
		//Needed to implement MouseMotionListener
		int x=e.getX();
		int y=e.getY();
		stick.setEnd(x, y);
		repaint();
	}

	@Override
	public void mouseMoved(MouseEvent e) {
		// Needed to implement MouseMotionListener

	}

	@Override
	public void mouseClicked(MouseEvent e) {
		// Needed to implement MouseListener

	}

	@Override
	public void mouseEntered(MouseEvent e) {
		// Needed to implement MouseListener

	}

	@Override
	public void mouseExited(MouseEvent e) {
		// Needed to implement MouseListener

	}

	@Override
	public void mousePressed(MouseEvent e) {
		// Needed to implement MouseListener
		timer.stop();
		stick.setTip(cueBall.getX(), cueBall.getY());
		stick.setEnd(e.getX(), e.getY());
		stick.setVisible(true);
		repaint();
	}

	@Override
	public void mouseReleased(MouseEvent e) {
		// Needed to implement MouseListener
		stick.setVisible(false);
		cueBall.hit(stick.getAngle(), stick.getForce());
		timer.start();
		count=0;
		target1Hit=false;
		cue.play();
		//repaint();
	}
	@Override
	public void actionPerformed(ActionEvent e) {
		// Needed to implement the Timer which generates ActionEvents
		if (e.getSource()==timer){
			frame++;
			frame%=100;		
			cueBall.move();
			if (table.touches(cueBall)&&!target1Hit){
				count++; 
				bounce.play();
			}
			if (cueBall.touches(target1)){
				cueBall.strike(target1);
				target1Hit=true;
				hit.play();
				if (count>2)
					score+=10*count;
			}
			target1.move();
			if (table.touches(target1))
				bounce.play();
		}

		repaint();
	}

}