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) {
}
}
}
}
/*-
* 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;
}
}
/*-
* 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);
}
/*-
* 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);
}
}
/*-
* 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);
}
}
/*-
* 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);
}
}
/*-
* 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);
}
}
/*-
* 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);
}
}
/*-
* 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;
}
}
java.awt.event.KeyEvent
.