Ducky Love
<< Billiards | FinalProjectsTrailIndex | MakeMaze >>


Click here for a working demonstration
This shooter game is here to demonstrate how to write your code so the sound and graphics files can be read when placed in a .jar file
The applet is then embedded in a web page with something like this:
duckyLove.html
<html>
<head>
<title> Ducky Love Applet</title>
</head>
<body>
<h1>Ducky Love Applet</h1>
<hr>
<applet
code="DuckyLove.class"
archive="duckyLove.jar"
width=800
height=600
>
If you see this, your browser is ignoring this awesome Java Applet!
</applet>
<hr>
<h4><a href="http://apcs.mathorama.com/index.php?n=Main.DuckyLove"> Source Code Here</a></h4>
</body>
</html>
Heart.java
import java.awt.Graphics;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
public class Heart {
private BufferedImage img;
private int x,y,w,h, deltaX, deltaY;
public Heart(double angle, BufferedImage img){
this.img=img;
w=200;
h=200;
x=400;
y=450;
deltaX=(int)Math.round(10*Math.cos(angle));
deltaY=(int)Math.round(10*Math.sin(angle));
}
public boolean touches(Rectangle2D object){
return object.intersects(x, y, w, h);
}
public void move(){
w=95*w/100;
h=95*h/100;
x+=deltaX;
y-=deltaY;
}
public void draw(Graphics g){
g.drawImage(img, x-w/2, y-h/2, x+w/2, y+h/2, 0, 0, img.getWidth(), img.getHeight(), null);
}
public int getY(){
return y;
}
public Rectangle2D area(){
return new Rectangle2D.Double(x-w/2, y-h/2, w, h);
}
}
Target.java
import java.awt.Graphics;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
public class Target
{
private BufferedImage img;
private int x,y,w,h, deltaX;
public Target(BufferedImage img){
this.img=img;
x=400;
y=25;
w=75;
h=70;
deltaX=randomInt(3,8);
}
public boolean touches(Rectangle2D object){
if(deltaX>0)
return object.intersects(x-w, y, w, h);
return object.intersects(x, y, w, h);
}
public int randomInt(int min, int max){
return min+(int)(max*Math.random());
}
public void move(){
x+=deltaX;
if (x+w>800){
deltaX=-1*randomInt(3,8);
x=800-w;
}
if (x<0){
deltaX=randomInt(3,8);
x=w;
}
}
public void draw(Graphics g){
int rightSide=x-w;
if (deltaX<0)
rightSide=x+w;
g.drawImage(img, x, y, rightSide, y+h, 0, 0, img.getWidth(), img.getHeight(), null);
}
public int getSpeed(){
return Math.abs(deltaX);
}
}
Crossbow.java
import java.awt.Color;
import java.awt.Graphics;
public class Crossbow {
private int[] xPoints, yPoints;
int x;
public Crossbow(){
x=400;
xPoints=new int[3];
yPoints=new int[3];
xPoints[0]=400;xPoints[1]=x-20;xPoints[2]=x+20;
yPoints[0]=450;yPoints[1]=600;yPoints[2]=600;
}
public void setX(int x){
if (x>=0 && x<=800)
this.x=x;
xPoints[1]=x-20;
xPoints[2]=x+20;
}
public void draw(Graphics g){
g.setColor(new Color (200, 20, 200));
g.fillPolygon(xPoints, yPoints, 3);
}
public double getAngle(){
return Math.atan2(600-200, 400-x);
}
}
DuckyLove.java (Application version)
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.*;
import java.awt.image.*;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import javax.imageio.ImageIO;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
/**
* Ducky Love Application
*
* A more peaceful approach to the
* "Shooting Gallery" Game, here we
* send hearts to our "rubber ducky"
*
*
* Based on my Applet version from long ago
*
* @author Chris Thiel, ofmCap
* @version May 6, 2026
*/
public class DuckyLove extends JPanel implements ActionListener,MouseMotionListener, MouseListener
{
private Crossbow bow;
private Target target;
private ArrayList<Heart> hearts;
private int score, hits, miss;
private Timer timer;
private File bellFile;
private Clip bell;
private BufferedImage background;
private BufferedImage heartImg, duckImg;
private Font font;
//private Image virtualMem;
//private Graphics gBuffer;
public static int WIDTH=800;
public static int HEIGHT=620;
public DuckyLove(){
timer = new Timer(10,this);
score = 0;
hits = 0;
miss = 0;
bow=new Crossbow();
background=loadImage("background.gif");
heartImg = loadImage("heart.png");
duckImg = loadImage("ducky.gif");
target = new Target(duckImg);
//We load the large files once to save time,
//getting the resource from the jar file
hearts = new ArrayList<Heart>();
timer.start();
this.addMouseListener(this);
this.addMouseMotionListener(this);
font = new Font("Helvetica", Font.BOLD, 18);
}
public BufferedImage loadImage(String imageFileName)
{
URL url = getClass().getResource(imageFileName);
if (url == null)
throw new RuntimeException("cannot find file: " + imageFileName);
try
{
return ImageIO.read(url);
}
catch(IOException e)
{
throw new RuntimeException("unable to read from file: " + imageFileName);
}
}
public void paintComponent(Graphics g){
super.paintComponent(g);
if (background!=null){
g.drawImage(background, 0, 0, WIDTH, HEIGHT, null);
} else {
g.setColor(Color.WHITE);
g.fillRect(0, 0, WIDTH, HEIGHT);
g.setColor(Color.RED);
g.drawString("background image not found", 20, 60);
}
bow.draw(g);
target.draw(g);
for(Heart h:hearts)
h.draw(g);
g.setColor(Color.black);
g.setFont(font);
g.drawString("Hits: "+hits, 715, 560);
g.drawString("Misses: "+miss, 690, 585);
int percent=0;
if (hits+miss!=0)
percent=100*hits/(hits+miss);
g.drawString("Score: "+score+" ("+percent+"%)", 20, 20);
g.drawString("Angle: "+Math.round(180*bow.getAngle()/Math.PI), 20, 580);
}
public void update(Graphics g)
{
paint(g); //get rid of Windows flicker with this method
}
@Override
public void mouseClicked(MouseEvent arg0) {}
@Override
public void mouseEntered(MouseEvent arg0) {}
@Override
public void mouseExited(MouseEvent arg0) {}
@Override
public void mousePressed(MouseEvent arg0) {}
@Override
public void mouseReleased(MouseEvent arg0) {
// fire a heart
hearts.add(new Heart(bow.getAngle(), heartImg));
}
@Override
public void mouseDragged(MouseEvent e) {}
@Override
public void mouseMoved(MouseEvent e) {
int x=e.getX();
bow.setX(x);
repaint();
}
@Override
public void actionPerformed(ActionEvent arg0) {
target.move();
for(Heart h:hearts)
h.move();
int i=0;
while (i<hearts.size()){
Heart h=hearts.get(i);
//check for hit
boolean hit=target.touches(h.area());
if (hit){
score+=10*target.getSpeed();
hits++;
playBell();
hearts.remove(h);
}
if (h.getY()<-50){
score--;
miss++;
hearts.remove(h);
}else{
i++;
}
}
repaint();
}
public void playBell() {
File bellFile = new File("bell.wav");
try {
bell = AudioSystem.getClip();
bell.open(AudioSystem.getAudioInputStream(bellFile));
bell.start();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
DuckyLove app = new DuckyLove();
JFrame window = new JFrame("Ducky Love Game");
window.setSize(WIDTH, HEIGHT);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.getContentPane().add(app);
window.getContentPane().addMouseMotionListener(app);
window.setVisible(true);
}
}
DuckyLove.java (Original Applet version not supported by current web browsers)
import java.applet.Applet;
import java.applet.AudioClip;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import javax.imageio.ImageIO;
import javax.swing.Timer;
@SuppressWarnings("serial")
public class DuckyLove extends Applet implements ActionListener,MouseMotionListener, MouseListener
{
private Crossbow bow;
private Target target;
private ArrayList<Heart> hearts;
private int score, hits, miss;
private Timer timer;
private AudioClip bell;
private Image background;
private BufferedImage heartImg, duckImg;
private Font font;
private Image virtualMem;
private Graphics gBuffer;
public void init(){
timer = new Timer(10,this);
score = 0;
hits = 0;
miss = 0;
bow=new Crossbow();
//We load the large files once to save time,
//getting the resource from the jar file
background = getImage( getClass().getResource("/background.gif"));
try {
heartImg = ImageIO.read( getClass().getResource("/heart.png"));
duckImg = ImageIO.read( getClass().getResource("/ducky.gif"));
} catch (IOException e) {
e.printStackTrace();
}
target=new Target(duckImg);
hearts = new ArrayList<Heart>();
timer.start();
this.addMouseListener(this);
this.addMouseMotionListener(this);
bell = getAudioClip(getClass().getResource("/bell.wav"));
bell.play();
virtualMem = createImage(getWidth(),getHeight());
gBuffer = virtualMem.getGraphics();
font = new Font("Helvetica", Font.BOLD, 18);
}
public void paint(Graphics g){
gBuffer.drawImage(background, 0, 0, null);
bow.draw(gBuffer);
target.draw(gBuffer);
for(Heart h:hearts)
h.draw(gBuffer);
gBuffer.setColor(Color.black);
gBuffer.setFont(font);
gBuffer.drawString("Hits: "+hits, 715, 560);
gBuffer.drawString("Misses: "+miss, 690, 585);
int percent=0;
if (hits+miss!=0)
percent=100*hits/(hits+miss);
gBuffer.drawString("Score: "+score+" ("+percent+"%)", 20, 20);
gBuffer.drawString("Angle: "+Math.round(180*bow.getAngle()/Math.PI), 20, 580);
g.drawImage(virtualMem,0,0,this);
}
public void update(Graphics g)
{
paint(g); //get rid of flicker with this method
}
@Override
public void mouseClicked(MouseEvent arg0) {}
@Override
public void mouseEntered(MouseEvent arg0) {}
@Override
public void mouseExited(MouseEvent arg0) {}
@Override
public void mousePressed(MouseEvent arg0) {}
@Override
public void mouseReleased(MouseEvent arg0) {
// fire a heart
hearts.add(new Heart(bow.getAngle(), heartImg));
}
@Override
public void mouseDragged(MouseEvent e) {}
@Override
public void mouseMoved(MouseEvent e) {
int x=e.getX();
bow.setX(x);
repaint();
}
@Override
public void actionPerformed(ActionEvent arg0) {
target.move();
for(Heart h:hearts)
h.move();
int i=0;
while (i<hearts.size()){
Heart h=hearts.get(i);
//check for hit
boolean hit=target.touches(h.area());
if (hit){
score+=10*target.getSpeed();
hits++;
bell.play();
hearts.remove(h);
}
if (h.getY()<-50){
score--;
miss++;
hearts.remove(h);
}else{
i++;
}
}
repaint();
}
}
