Computer Science 252
Problem Solving with Java

Spring 2016, The College of Saint Rose

FruitNinja BlueJ Project

Click here to download a BlueJ project for FruitNinja.


FruitNinja Source Code

The Java source code for FruitNinja is below. Click on a file name to download it.


FruitNinja.java

import objectdraw.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.Random;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;

/*
 * Example FruitNinja: an example final project that implements a simplified version
 * of the "Fruit Ninja" game.  Each time the user presses the button to launch
 * more flying fruit, an active object is created that in turn creates a series of
 * flying fruit.  The player attempts to drag the mouse over each flying fruit object
 * to earn a point.  If they do not do so before it falls off the bottom of the canvas,
 * a miss is recorded.  When 3 misses are recorded, the game ends.
 *
 * Jim Teresco, The College of Saint Rose, CSC 252, Spring 2016
 *
 * $Id: FruitNinja.java 2915 2016-03-24 02:23:40Z terescoj $
 */

public class FruitNinja extends WindowController implements ActionListener {

    // how many fruits can you miss before the game ends?
    private static final int MAX_MISSES = 3;
    
    // instance variables to keep track of scoring
    private JLabel scoreLabel;
    private int score, misses;
    
    // the game keeps a list of FlyingFruit objects currently in the air
    private ArrayList<FlyingFruit> fruitList;

    public void begin() {
        setSize(600, 600);

        Container contentPane = getContentPane();

        // A JPanel, in which we will group together our JButton
        // and JLabel for the score
        JPanel southPanel = new JPanel();

        JButton nextButton = new JButton("Next Round");
        southPanel.add(nextButton);

        scoreLabel = new JLabel("Score: 0, Misses: 0");
        southPanel.add(scoreLabel);

        // listen for button presses
        nextButton.addActionListener(this);

        // get our panel into the pane
        contentPane.add(southPanel, BorderLayout.SOUTH);

        contentPane.validate();

        // arraylist of FlyingFruit objects
        fruitList = new ArrayList<FlyingFruit>();
    }

    // handle button press, only one button so it's simple
    public void actionPerformed(ActionEvent e) {

        if (misses < MAX_MISSES) {
            // the FruitLauncher is responsible for launching a series of
            // FlyingFruit objects
            new FruitLauncher(this, canvas);
        }
    }

    // method to inform the game that there's a new FlyingFruit
    // in the air to check in our drag
    public synchronized void newFruit(FlyingFruit f) {

        fruitList.add(f);
    }

    // method to inform the game that a piece of fruit is done, either
    // because the fruit was hit or it went off the canvas, as indicated 
    // by the second parameter
    public synchronized void fruitDone(FlyingFruit f, boolean hit) {

        if (!hit) {
            if (misses < MAX_MISSES) {
                misses++;
                scoreLabel.setText("Score: " + score + ", Misses: " + misses);            
            }
            if (misses == MAX_MISSES) {
                new FilledRect(0, 0, canvas.getWidth(), canvas.getHeight(), canvas).setColor(Color.red);
                Text gameOver = new Text("Game Over!", 0, 0, canvas);
                gameOver.setColor(Color.white);
                gameOver.setFontSize(20);
                gameOver.move(canvas.getWidth()/2 - gameOver.getWidth()/2,
                    canvas.getHeight()/2 - gameOver.getHeight()/2);
            }                
        }
        fruitList.remove(f);
    }

    // the only mouse event we care about is onMouseDrag, we simply loop over
    // all of the FlyingFruit objects currently in play and see if any of them
    // contain the point
    public void onMouseDrag(Location point) {

        if (misses < MAX_MISSES) {
            for (FlyingFruit f : fruitList) {

                if (f.contains(point)) {
                    f.hit();
                    score++;
                }
            }
            scoreLabel.setText("Score: " + score + ", Misses: " + misses);
        }
    }
}

FruitLauncher.java


/**
 * Active object to create a series of flying fruits for 
 * Fruit Ninja game
 * 
 * @author Jim Teresco
 */
import objectdraw.*;
import java.awt.*;
import java.util.Random;

public class FruitLauncher extends ActiveObject {

    // we'll create between 3 and 7 FlyingFruits per FruitLauncher
    private static final int MIN_FRUITS = 3;
    private static final int MAX_FRUITS = 7;
    // in ms:
    private static final int AVERAGE_INTERVAL = 500;
    
    // just some information we need to remember from the parameters
    // to the constructor that need to be used in the run method, where
    // they are passed to the FlyingFruit constructor
    private DrawingCanvas c;
    private FruitNinja game;
    
    // constructor remembers a few bits of information then activates
    public FruitLauncher(FruitNinja game, DrawingCanvas c) {
        
        this.c = c;
        this.game = game;
        
        start();
    }
    
    // the life our our launcher is to decide how many to launch, then launch them
    public void run() {
        Random r = new Random();
        int numFruits = r.nextInt(MAX_FRUITS - MIN_FRUITS) + MIN_FRUITS;
        
        for (int fruitNum = 0; fruitNum < numFruits; fruitNum++) {
            new FlyingFruit(game, c);
            pause(AVERAGE_INTERVAL);
        }
    }
}

FlyingFruit.java


/**
 * One flying piece of fruit for the Fruit Ninja game.
 * 
 * @author Jim Teresco 
 */
import objectdraw.*;
import java.awt.*;
import java.util.Random;

public class FlyingFruit extends ActiveObject {
    
    // lots of speeds and sizes
    private static final double MIN_XSPEED = -2;
    private static final double MAX_XSPEED = 2;
    private static final double MIN_YSPEED = -6;
    private static final double MAX_YSPEED = -10;
    private static final double FRUIT_SIZE = 40;
    private static final int SHRAPNEL_COUNT = 4;
    
    private static final double GRAVITY = 0.1;
    
    private static final int PAUSE_TIME = 33;
    
    // the actual graphical object that's flying
    private FilledOval fruit;
    
    // our speeds
    private double xSpeed, ySpeed;
    
    // need to remember the canvas
    private DrawingCanvas c;
    
    // reference to the game's WindowController which will be used
    // to report when new instances are created and when instances
    // fall off the bottom of the canvas
    private FruitNinja game;
    
    // have we hit this one yet?
    private boolean hit = false;
    
    // the constructor is responsible for remembering some information,
    // choosing a starting position and speed, and telling the game
    // that it exists.
    public FlyingFruit(FruitNinja game, DrawingCanvas c) {
        
        this.game = game;
        this.c = c;
        
        Random r = new Random();
        
        // random x position within middle "half" of canvas
        double xPos = r.nextDouble()*c.getWidth()/2 + c.getWidth()/4 - FRUIT_SIZE/2;
        
        // our actual fruit object
        fruit = new FilledOval(xPos, c.getHeight(), FRUIT_SIZE, FRUIT_SIZE, c);
        fruit.setColor(new Color(r.nextInt(200), r.nextInt(200), r.nextInt(200)));
        
        // calculate initial speed
        xSpeed = r.nextDouble()*(MAX_XSPEED - MIN_XSPEED) + MIN_XSPEED;
        ySpeed = r.nextDouble()*(MAX_YSPEED - MIN_YSPEED) + MIN_YSPEED;
        
        // tell the game that we have a new FlyingFruit
        game.newFruit(this);
        
        start();
    }
    
    // method to see if the fruit object here contains a point
    public boolean contains(Location point) {
        
        return fruit.contains(point);
    }

    // method to call when this fruit object has been hit
    public void hit() {
        
        hit = true;
    }
    
    // the life of a FlyingFruit
    public void run() {

        boolean done = false;
        
        while (!done) {
            
            // move it
            fruit.move(xSpeed, ySpeed);
            // acceleration due to gravity
            ySpeed += GRAVITY;
            // wait a little
            pause(PAUSE_TIME);
            
            // see if we've gone off the bottom
            if (ySpeed > 0 && fruit.getY() > c.getHeight()) {
                done = true;
            }
            
            // if we've been hit, explode!
            if (hit) {
                done = true;
                // start the shrapnel here
                Color color = fruit.getColor();
                double x = fruit.getX() + FRUIT_SIZE/2;
                double y = fruit.getY() + FRUIT_SIZE/2;
                for (int i = 0; i < SHRAPNEL_COUNT; i++) {
                    new Shrapnel(x, y, xSpeed, ySpeed, color, c);
                }
            }
        }
        
        // inform the game that this object is done, hit value indicates
        // hit or miss so game can update status
        game.fruitDone(this, hit);
        
        fruit.removeFromCanvas();
    }
}

Shrapnel.java


/**
 * Shrapnel of a hit fruit object, just a straightforward
 * animation of an object subject to acceleration due to
 * gravity with no other real interactions.
 * 
 * @author Jim Teresco
 */
import objectdraw.*;
import java.awt.*;
import java.util.Random;

public class Shrapnel extends ActiveObject {
    
    // animation parameters
    private static final double MAX_SPEED_CHANGE = 2;
    private static final double SHRAPNEL_SIZE = 20;
    private static final int PAUSE_TIME = 33;
    private static final double GRAVITY = 0.1;
    
    // instance variables for the actual graphical object, and its
    // animation speeds
    private FilledOval s;
    private double xSpeed, ySpeed;
    
    // we'll need this (or just its height) to decide when we've gone
    // off the bottom - using the canvas itself means things are still ok
    // if the canvas gets resized.
    private DrawingCanvas c;
    private static Random r = new Random();
    
    // construct our shrapnel
    public Shrapnel(double x, double y, double xSpeed, double ySpeed, 
                    Color color, DrawingCanvas c) {

        this.c = c;
        
        s = new FilledOval(x - SHRAPNEL_SIZE/2, y - SHRAPNEL_SIZE/2, SHRAPNEL_SIZE,
                           SHRAPNEL_SIZE, c);
        s.setColor(color);
        
        // choose a random speed similar to current speed
        this.xSpeed = xSpeed + r.nextDouble()*2*MAX_SPEED_CHANGE - MAX_SPEED_CHANGE;
        this.ySpeed = ySpeed + r.nextDouble()*2*MAX_SPEED_CHANGE - MAX_SPEED_CHANGE;
        
        start();
    }

    // the simple life of fruit shrapnel
    public void run() {
        
        while (s.getY() < c.getHeight()) {
           
            s.move(xSpeed, ySpeed);
            pause(PAUSE_TIME);
            ySpeed += GRAVITY;
        }
        s.removeFromCanvas();
        
    }
}