Εκτενές παράδειγμα

Διομήδης Σπινέλλης
Τμήμα Διοικητικής Επιστήμης και Τεχνολογίας
Οικονομικό Πανεπιστήμιο Αθηνών
dds@aueb.gr

Γιορτινή κάρτα

Ολοκληρωμένο παράδειγμα

Διάγραμμα των κλάσεων

XMas card class diagram

XmasCard

package gr.aueb.xmascard;

import java.awt.Rectangle;

/**
 * The Christmas Card program main class.
 *
 * @author Giorgos Gousios, Diomidis Spinellis
 * @depend - - - gr.aueb.xmascard.DrawPanel
 * @depend - <instantiate> - gr.aueb.xmascard.MidiPlayer
 * @depend - - - gr.aueb.xmascard.Tree
 * @depend - - - gr.aueb.xmascard.PointSnowFlake
 * @depend - - - gr.aueb.xmascard.SlashSnowFlake
 */
public class XmasCard {

    /** Number of trees */
    private static final int numTrees = 30;
    /** Number of snowflakes */
    private static final int numSnowFlakes = 1500;
    /** Minimum tree width. */
    private static final int treeWidth = 30;
    /** Minimum tree height. */
    private static final int treeHeight = 100;
    /** Additional variation to tree height and width */
    private static final int treeWobble = 100;
    /** Song to play. */
    private static String musicFile = "jbelrock.mid";

    public static void main(String[] args) {

        // Create a window and the canvas to draw onto.
        DrawPanel d = new DrawPanel();

        // Create randomly-positioned trees.
        for (int i = 0; i < numTrees; i++) {
            Rectangle treeBox = new Rectangle(
		(int)(Math.random() * DrawPanel.WIDTH),
                (int)(Math.random() * DrawPanel.HEIGHT),
                treeWidth + (int)(Math.random() * treeWobble),
                treeHeight + (int)(Math.random() * treeWobble));

            Tree t = new Tree(d.getCanvas(), treeBox);
            d.addDrawObject(t);
        }

	// Start playing music
	MidiPlayer m = new MidiPlayer(musicFile);

        // Create the snowflakes.
        for (int i = 0; i < numSnowFlakes; i++) {
	    switch (i % 6) {
	    case 0:
	    case 1:
                d.addDrawObject(new PointSnowFlake(d.getCanvas(), '.', 15));
		break;
	    case 2:
                d.addDrawObject(new PointSnowFlake(d.getCanvas(), 'o', 10));
		break;
	    case 3:
                d.addDrawObject(new PointSnowFlake(d.getCanvas(), '*', 5));
		break;
	    case 4:
	    case 5:
                d.addDrawObject(new SlashSnowFlake(d.getCanvas()));
		break;
	    }
	    try {
		// Allow existing snowflakes to fall a bit, before adding more
		Thread.sleep(100);
	    } catch (InterruptedException e) {
	    }
        }
    }
}

DrawPanel


/*-
 * Copyright 2005-2018 Diomidis Spinellis
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

package gr.aueb.card;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Vector;
import javax.swing.JFrame;
import javax.swing.JPanel;

/**
 * The program's main window.
 * Extends JFrame to display the window where the
 * trees and snow are drawn. Implements the {@link java.lang.Runnable Runnable}
 * interface so as to create a thread that repeatedly calls the
 * {@link gr.aueb.card.Drawable#draw() draw}method.
 *
 * @author Giorgos Gousios, Diomidis Spinellis
 * @opt nodefillcolor lightblue
 * @assoc 1 drawablePanel 1 DrawablePanel
 */
public class DrawPanel extends JFrame implements Runnable {

    /** The window's width. */
    public static final int WIDTH = 1024;
    /** The window's height. */
    public static final int HEIGHT = 768;

    /** The window's background color (blue). */
    public static final Color BACKGROUND_COLOR = new Color(0, 153, 204);

    /* A table that holds the objects to be drawn */
    private Vector<Drawable> drawObjects = null;

    /* The drawing thread (not serializable) */
    private transient Thread thread;

    /* The canvas to draw onto */
    private DrawablePanel drawablePanel = null;

    /** Serial number of persistant  data.
     * Required, because JFrame implements serializable.
     */
    static final long serialVersionUID = 1L;

    /**
     * Constructor to initialize an object with the minimal required state.
     * The constructor is private, as the full initialization is done
     * in the getInstance method.
     */
    private DrawPanel() {
        super("Holiday Card");
    }

    /**
     * Field initialization based on a constructed instance
     */
    private void initialize() {
        drawObjects = new Vector<Drawable>();
        initializeGraphics();
        initializeThread();
    }

    /**
     * Initialize, display the window, and start the animation.
     * The code here is separate from the constructor in order to
     * avoid the resulting "this escape".
     */
    public static DrawPanel getInstance() {
        DrawPanel instance = new DrawPanel();
        instance.initialize();
        return instance;
    }

    /** Initialize the main window. */
    private void initializeGraphics() {
        // Make our window look nice
        JFrame.setDefaultLookAndFeelDecorated(true);

        // Create our drawing canvas
        drawablePanel = new DrawablePanel(this);
        drawablePanel.setBackground(BACKGROUND_COLOR);
        drawablePanel.setPreferredSize(new Dimension(WIDTH, HEIGHT));
        setContentPane(drawablePanel);

        // Handle termination
        setDefaultCloseOperation(
                javax.swing.WindowConstants.DISPOSE_ON_CLOSE);

        // Exit when the window is closed
        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });

        // Our size
        setSize(WIDTH, HEIGHT);

        // Force the parent window to expand the canvas to all available space
        pack();

        //Display the window
        setVisible(true);
    }

    /** Start the execution of the drawing thread. */
    private void initializeThread() {
        if (thread == null) {
            thread = new Thread(this);
            thread.setPriority(Thread.MIN_PRIORITY);
            thread.start();
        }
    }

    /** Add a component to be drawn. */
    public void addDrawObject(Drawable drawObject) {
        drawObjects.add(drawObject);
    }

    /** Return a copy of the component list to be drawn */
    public Vector<Drawable> getDrawables() {
        return new Vector<Drawable>(drawObjects);
    }

    /**
     * The method to be executed by the running thread. Executes the
     * {@link DrawablePanel#repaint()}method periodically.
     */
    public void run() {
        Thread me = Thread.currentThread();

        // Allow termination by setting thread to null
        while (thread == me) {
            // tell drawablePanel to repaint its contents
            drawablePanel.repaint();
            try {
                Thread.sleep(250);
            } catch (InterruptedException e) {
            }
        }
        thread = null;
    }

    /**
     * Get the canvas's drawing panel
     *
     * @return javax.swing.JPanel
     */
    public JPanel getCanvas(){
        return drawablePanel;
    }
}

Drawable


/*-
 * Copyright 2005-2018 Diomidis Spinellis
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

package gr.aueb.card;

import javax.swing.JPanel;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;

/**
 * An abstract representation of a self-drawable object.
 *
 * @author Giorgos Gousios, Diomidis Spinellis
 */
public abstract class Drawable {

    /**
     * The canvas to draw the object onto
     */
    protected Graphics2D canvas;

    /**
     * The canvas's bounds
     */
    protected Rectangle bounds;

    /**
     * Create drawable item
     *
     * @param panel The panel to draw the object onto
     */
    public Drawable(JPanel panel) {
        bounds = panel.getBounds();
        canvas = (Graphics2D)panel.getGraphics();
    }

    /**
     * Draws the object onto the canvas
     *
     */
    public abstract void draw(Graphics g);
}

DrawablePanel


/*-
 * Copyright 2005-2018 Diomidis Spinellis
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

package gr.aueb.card;

import java.awt.Color;
import java.awt.Graphics;
import java.util.Vector;

import javax.swing.JPanel;


/**
 * The Holiday Card program main class.
 *
 * @author Georgios Zouganelis
 * Draw components from this object to reduce flickering.
 */
public class DrawablePanel extends JPanel {

    /** The DrawPanel this DrawablePanel is attached to **/
    private DrawPanel controller = null;

    /** Serial number of persistent  data.
     * Required, because JPanel implements serializable.
     */
    private static final long serialVersionUID = 1L;

    /**
     * Constructor to initialize the DrawablePanel with it's controller
     *
     */
    public DrawablePanel(DrawPanel panel) {
        controller = panel;
    }

    /**
     * Perform all drawing operations
     * By overriding the JPanel method and initiating all the drawing
     * from this place we take advantage of JPanel's double-buffering
     * capability.
     */
    @Override
    public void paintComponent(Graphics g){
        super.paintComponent(g);
        setBackground(DrawPanel.BACKGROUND_COLOR);

        // Ask our controller for a copy of items to draw
        Vector<Drawable> toPaint = controller.getDrawables();
        for (Drawable d : toPaint)
            d.draw(g);
    }
}

Tree


/*-
 * Copyright 2005-2018 Diomidis Spinellis
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

package gr.aueb.card;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Polygon;
import java.awt.Rectangle;
import javax.swing.JPanel;

/**
 * A self-drawable tree. Uses a box to specify the tree's bounds (the dimensions
 * constructor parameter). The trunk is placed in the middle of the bottom side
 * of the box, having a width equal to the 8% of the total width of the tree and
 * a height equal to the 20% of the total height of the bounding box. The main
 * body is represented as an isosceles triangle with a height of 80% of the
 * height of the bounding box.
 *
 * @author Giorgos Gousios, Diomidis Spinellis
 * @opt nodefillcolor green
 */
public class Tree extends Drawable {

    /** Tree trunk width as % of the bounding rectangle width */
    private final double TRUNK_WIDTH_FACTOR = 0.08;
    /** Tree trunk height as % of the bounding rectangle height */
    private final double TRUNK_HEIGHT_FACTOR = 0.2;
    /** Tree body height as % of the bounding rectangle height */
    private final double BODY_HEIGHT_FACTOR = 0.8;
    /** Trunk's color (RGB) */
    private final Color BROWN = new Color(204, 102, 0);
    /** Body's color (RGB) */
    private final Color GREEN = new Color(0, 254, 0);
    /** Tree balls' color (RGB) */
    private final Color RED = new Color(250, 0, 0);

    /** The tree's bounding rectangle */
    private Rectangle dimensions;

    /**
     * Creates a tree from the specified bounding box
     *
     * @param panel The panel to draw the object onto
     * @param dimensions The bounding box dimensions.
     */
    public Tree(JPanel panel, Rectangle dimensions) {
    super(panel);
	this.dimensions = dimensions;
    }

    /**
     * Draws the tree.
     *
     * @param g The Graphics object on which we will paint
     */
    @Override
    public void draw(Graphics g) {
	drawTrunk(g);
	drawBody(g);
    }

    /**
     * Draws the trunk. For details on how the lengths are calculated
     *
     * @param g The Graphics object on which we will paint
     * @see gr.aueb.Tree the class description.
     */
    private void drawTrunk(Graphics g) {
	/* Calculate the trunk rectangle first */
	Rectangle r = new Rectangle();

	r.x = (int) (dimensions.x + (dimensions.width
                    - dimensions.width * TRUNK_WIDTH_FACTOR) / 2);
	r.y = (int) (dimensions.y + dimensions.height * BODY_HEIGHT_FACTOR);
	r.width = (int) (dimensions.width * TRUNK_WIDTH_FACTOR);
	r.height = (int) (dimensions.height * TRUNK_HEIGHT_FACTOR);

	/* Draw it! */
	g.drawRect(r.x, r.y, r.width, r.height);

	/* Fill it with brown color */
	Color c = g.getColor();
	g.setColor(BROWN);
	g.fillRect(r.x, r.y, r.width, r.height);
	g.setColor(c); //Revert paint color to default
    }

    /**
     * Draws the body. For details on how the lengths are calculated
     *
     * @param g The Graphics object on which we will paint
     * @see gr.aueb.Tree the class description.
     */
    private void drawBody(Graphics g) {
        /* Create the polygon (triangle) to draw */
        Polygon p = new Polygon();
        p.addPoint(dimensions.x + dimensions.width / 2, dimensions.y);
        p.addPoint(dimensions.x,
                (int) (dimensions.y + dimensions.height * BODY_HEIGHT_FACTOR));
        p.addPoint(dimensions.x + dimensions.width,
                (int) (dimensions.y + dimensions.height * BODY_HEIGHT_FACTOR));
        /* Draw the body */
        g.drawPolygon(p);

        /* Fill it with green color */
        Color c = g.getColor();
        g.setColor(GREEN);
        g.fillPolygon(p);
        g.setColor(c); // Revert paint color to default

        /* Set Ornaments to the body. */
        drawTreeOrnaments(g);
    }

    /**
     * Draws the ornaments of the tree.
     * @param g The Graphics object on which we will paint
     * @param x The Abscissa of the part of the body to draw the ornament
     * @param y The Ordinate of the part of the body to draw the ornament
     */
    private void addTreeOrnament(Graphics g, int x, int y) {
        /* Draw Tree Ornament. */
        g.drawOval(x, y, 10, 10);
        /* Set color to Red. */
        g.setColor(RED);
        /* Fill Tree Ornament with color. */
        g.fillOval(x, y, 10, 10);
    }

    /**
     * Calls addTreeOrnament for specific locations on
     * the tree body.
     * @param g The Graphics object on which we will paint
     */
    private void drawTreeOrnaments(Graphics g) {
        /* yAxis of the body. */
        int yAxis = (int) (dimensions.y + dimensions.height * BODY_HEIGHT_FACTOR);
        /* Add ornament to down left. */
        addTreeOrnament(g, dimensions.x - 2, yAxis - 2);
        /* Add ornament to down right. */
        addTreeOrnament(g, dimensions.x + dimensions.width - 1, yAxis - 1);
        /* Add ornament to up left. */
        addTreeOrnament(g, dimensions.x + dimensions.width / 2 - 5 - 20, yAxis - 25);
        /* Add ornament to up right. */
        addTreeOrnament(g, dimensions.x + dimensions.width / 2 - 5 + 20,  yAxis - 25);
        /* Add ornament to middle. */
        addTreeOrnament(g,dimensions.x + dimensions.width / 2 - 5, yAxis - 65);
    }
}

SnowFlake


/*-
 * Copyright 2005-2018 Diomidis Spinellis
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

package gr.aueb.card;

import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.Graphics;
import javax.swing.JPanel;

/**
 * A self-drawable 'snowflake' represented by a character. The move pattern and
 * character to be displayed is determined by subclasses.
 *
 * @author Giorgos Gousios, Diomidis Spinellis
 * @opt nodefillcolor white
 */
public abstract class SnowFlake extends Drawable {

    /** The snowflake's background color. */
    private static final Color WHITE = new Color(255, 255, 255);

    /**
     * The 'x' current coordinate of the snowflake.
     */
    protected int coordX;

    /**
     * The 'y' current coordinate of the snowflake.
     */
    protected int coordY;

    /**
     * The character to be displayed as a snowflake
     */
    protected char displayChar;

    /**
     * Create a snowflake represented by a point-like character.
     *
     * @param panel The panel to draw the object onto
     */
    public SnowFlake(JPanel panel) {
        super(panel);
        coordX = (int) (bounds.width * Math.random()) + bounds.x;
        coordY = 0;
    }

    /**
     * Draw the snowflake and wrap around.
     *
     * @param g The Graphics object on which we will paint
     */
    @Override
    public void draw(Graphics g) {
        // Go back to the top when hitting the bottom
        if (coordY >= bounds.height + bounds.y)
            coordY = 0;

        // Draw the character in white
        g.setColor(WHITE);
        g.drawString((Character.valueOf(displayChar)).toString(),
        coordX, coordY);
    }
}

SlashSnowFlake


/*-
 * Copyright 2005-2018 Diomidis Spinellis
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

package gr.aueb.card;

import java.awt.Graphics;
import javax.swing.JPanel;

/**
 * A class that animates a slash on a canvas.
 *
 * @author Giorgos Gousios, Diomidis Spinellis
 * @opt nodefillcolor white
 */
public class SlashSnowFlake extends SnowFlake {

    /**
     * Create a snowflake represented by a slash.
     *
     * @param panel The panel to draw the object onto
     */
    public SlashSnowFlake(JPanel panel) {
        super(panel);
        displayChar = '/';
    }

    /**
     * Display the slash on the drawing canvas. The slash alternates between
     * forward slash and backslash depending on the current 'y' coordinate.
     *
     * @param g The Graphics object on which we will paint
     */
    @Override
    public void draw(Graphics g) {

        /* / on even lines, \ on odd lines */
        displayChar = ((coordY % 2) == 0) ? '/' : '\\';

        /* Move by 0 to 10 pixels down*/
        coordY += (int) (Math.random() * 10);

        // Draw it through the superclass
        super.draw(g);
    }
}

PointSnowFlake


/*-
 * Copyright 2005-2018 Diomidis Spinellis
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

package gr.aueb.card;

import java.awt.Graphics;
import javax.swing.JPanel;

/**
 * A class that animates a point-like character on a canvas.
 * The character can be e.g. a . or a * or an o.
 *
 * @author Giorgos Gousios, Diomidis Spinellis
 * @opt nodefillcolor white
 */
public class PointSnowFlake extends SnowFlake {

    /** The wieght of the snowflake. */
    int weight;

    /**
     * Create a snowflake represented by a point-like character.
     *
     * @param panel The panel to draw the object onto
     * @param c The character to draw
     * @param w The snowflake's weight
     */
    public PointSnowFlake(JPanel panel, char c, int w) {
        super(panel);
        displayChar = c;
        weight = w;
    }

    /**
     * Display the star onto the canvas. The star changes its 'x' coordinate,
     * depending on the 'y' coordinate.
     *
     * @param g The Graphics object on which we will paint
     */
    @Override
    public void draw(Graphics g) {

        // Move the snowflake left and right
        switch (coordY % 3) {
        case 1:
            coordX = coordX - 5;
            break;
        case 2:
            coordX = coordX + 5;
            break;
        default:
            break;
        }

        // Move down, based on the weight
        coordY += (int)(Math.random() * weight);

        // Draw it through the superclass
        super.draw(g);
    }
}

MidiPlayer


/*-
 * Copyright 2005-2018 Diomidis Spinellis
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

package gr.aueb.card;

import javax.sound.midi.*;
import java.io.InputStream;
import java.io.IOException;

/**
 * Play the specified MIDI file
 * Note:
 * For this to work you must ensure that the computer's mixer
 * is configured to play the software synhtesizer output.
 *
 * @author Diomidis Spinellis
 */
public class MidiPlayer {
    /** The sequencer we are using to play the MIDI data. */
    static Sequencer sequencer = null;

    /** Constructor for playing the specified file. */
    MidiPlayer(String file) {
	playFile(file);
    }

    /** Play the specified file. */
    public void playFile(String file) {
        InputStream midiFile = getClass().getResourceAsStream(file);
        try {
	    if (sequencer == null)
		sequencer = MidiSystem.getSequencer();
	    else
		end();
	    sequencer.setSequence(MidiSystem.getSequence(midiFile));
            sequencer.open();
            sequencer.start();
        } catch(MidiUnavailableException e) {
            System.err.println("Midi device unavailable:" + e);
        } catch(InvalidMidiDataException e) {
            System.err.println("Invalid MIDI data:" + e);
        } catch(IOException e) {
            System.err.println("I/O error:" + e);
        }
    }

    /** Return true if the music is still playing. */
    public boolean isPlaying() {
	return sequencer.isRunning();
    }

    /* Stop playing. */
    public void end() {
            sequencer.stop();
            sequencer.close();
	    sequencer = null;
    }
}

Τεκμηρίωση

Τα σχόλια μέσα στον κώδικα επιτρέπουν την αυτόματη δημιουργία της τεκμηρίωσης, με τη χρήση του προγράμματος javadoc.
Τεκμηρίωση σε μορφή XML

Προαιρετική άσκηση

Με βάση τον κώδικα της κάρτας υλοποιήστε ένα παιγνίδι (π.χ. απόφυγε το χιόνι, ή μάζεψε τις μπάλες, ή πιάσε τον τάρανδο, κ.λπ.).

Πληροφορίες για την άσκηση

Περιεχόμενα