In: Computer Science
Purpose
For this programming project, you will create an
application that draws fractal-like shapes using recursion. The
class that creates the graphics window for the display of the
shapes is given to you (see below). What you have to do is create a
set of classes for the three shapes that are displayed in the
graphics window. Two of the shapes must be a Sierpinski triangle
and an H-shape. The third shape is your choice, though it must be a
fractal-like shape. There is also the possibility of earning a few
extra-credit points (up to 3 points) by allowing some deformation
of the shapes while moving a slider on the graphics
window.
FractalDisplay class (code:
http://flepeint.ccfaculty.org/java143/hw3/FractalDisplay.java)
The FractalDisplay class is given and requires very
minimal coding on your part. Instantiating the FractalDisplay class
displays a graphics window, which features 3 radio buttons for the
selection of the shapes, two buttons to add or remove a level to
the shapes, and a slider that when moved should deform the shapes
in an interesting manner. The slider feature is not required and
should only be attempted for extra-credit once the rest of the
assignment is completed.
The only code you will write in FractalDisplay is to
call the constructors of the three concrete shape classes of this
project, namely the SierpinskiTriangle, HShape, and MyShape
classes. Look for the comment //TODO. Only 3 lines of code are
needed, one for each of the calls of the constructors of the three
concrete shape classes.
Drawing of the shapes
For this programming project, three shapes are
required. Two of them must be a Sierpinski triangle and an H shape.
The third one is your choice, though it must be a fractal-like
shape. You can get some ideas by consulting this page.
The Sierpinski triangle starts with one triangle. We
call this level 1 of the triangle. At level 2, 3 triangles are
added inside of the first triangle. At level 3, 3 more triangles
are added inside of the three level 2 triangles. Mathematically,
the process goes on forever. For our display, this is of course not
possible since we are limited by the size of single pixel on the
computer screen. We will limit the level to a maximum value of 10
for the Sierpinski triangle.
Level 1: 1 triangleLevel 2: 1 + 1 * 3 = 4 trianglesLevel 3: 1 + 1 *
3 + 3 * 3 = 13 triangles
The H-shape follows the same pattern as the Sierpinski
triangle.
Initially, at level 1, there is one H. At level 2, 7
H's are drawn within the seven squares making up the original H.
And at level 3, 7 more H's are added in the 7 seven H's of level 2.
As for the Sierpinski triangle, we need to limit the level to a
maximum value, which will be 5 for the H shape.
Level 1: 1 HLevel 2: the shape has 1 + 1 * 7 = 8 H's (but only the
last level is drawn since the level 1 H would hide the smaller H
shapes)Level 3: the shape has 1 + 1 * 7 + 7 * 7 = 57 H's (though
only the level 2 H's are displayed)
Organization of the shape classes
Since the 3 shapes that are displayed have many
features in common (e.g. all of them may add a level or remove a
level), we want to use inheritance to avoid code repetition. To do
so, we start by listing all of the common methods in a Shape
interface. As an aside, notice that the code of FractalDisplay
knows only about the Shape type except for the actual constructor
calls of the concrete shape classes. This is of course the
advantage of using inheritance. It creates a low coupling between
the FractalDisplay class and the concrete shape classes, that is
the code of FractalDisplay doesn't rely heavily on the
implementation of the Shape interface by the concrete shape
classes.
The Shape interface will contain the following
methods
void draw(Graphics g)Draws this shapeboolean addLevel()Adds a level
to this shape. Returns true if the operation was successful or
false if the maximum level has been reached.boolean
removeLevel()Removes a level from this shape. Returns true if the
operation was successful or false if the shape was at level 1.int
countShapes()Returns the total number of shapes of this shape (e.g.
57 for the H shape at level 2).void update(int value)Modifies this
shape in an interesting way given a value in the range 0-100. This
method is only required for the extra-credit part.
When thinking about the implementation of the Shape
interface by the concrete shape classes, you will notice many
similarities between all of the implementations. For that reason,
it is a good idea to create an abstract class AbstractShape that
implements Shape and contains the code that is common to all three
concrete shape classes. By inheriting AbstractShape, the concrete
shape classes get the methods implemented in AbstractShape and just
need to add their own specific code (e.g. the implementation of the
draw method). This is leading us to the following class
hierarchy:
As just mentioned, AbstractShape will contain the
fields and methods that can be used by all of the concrete shape
classes. For instance, all shapes have a level, a maximum level
value, a color, and may have children shapes (e.g. any Sierpinski
triangle may have 3 inner triangles, and any H may have 7 inner
H's). A possible design of AbstractShape would list fields to store
that information. For instance:
public abstract class AbstractShape implements Shape
{
protected int level;
protected int maxLevel;
protected AbstractShape[] children;
protected Color color;
// The fields may be initialized by the AbstractShape constructor
with the values
// received from the super() calls in the constructors of the
concrete shape classes.
// For instance, the SierpinskiTriangle constructor may call the
AbstractShape constructor with
// the a max level value of 10 and a children array length of
3
// Alternatively the fields may be initialized in the concrete
class constructors (they are visible by
// the concrete classes since they are declared
protected)
What precisely goes in the AbstractShape class depends
on your implementation design, though I strongly recommend that you
follow the guidelines given in the next section. In any case, make
sure that you avoid any code repetition among your implementations
of the concrete classes. It is possible to have all of the methods
of the Shape interface implemented by AbstractShape except for the
draw method (and possibly the update method if you attempt the
extra credit part).
Implementation details
The details outlined below use the HShape and
SierpinskiTriangle classes as illustrations. However, they should
be readily applicable to the fractal shape that you select as your
third shape (coded in the MyShape class).
addLevel() method (must use
recursion)
As mentioned in the previous section, it is possible
to implement the addLevel method in the AbstractShape class. The
only addition to a concrete class can just be a method to create
the actual child shapes.
To model the Sierpinski triangle, we can think of the
triangles at level n as being the children of the triangle at level
n-1. For instance, for a Sierpinski triangle with three levels, the
level 1 triangle has three children which are the three level 2
triangles. Each of the three level 2 triangle has also three
children on level 3, for a total of nine triangles. And the nine
triangles at level 3 have no children since they are on the last
level.
For the H shape, a division can be thought of creating
a covering of 7 smaller H's for every H making up the shape. That
is we can think of an HShape has having 7 children.
Even though the construction of the Sierpinski
triangle and the H are different, we can use the same approach for
both and write some of the code in their common AbstractShape base
class.
In the AbstractShape class, add an array of
AbstractShape objects to store the AbstractShape objects that are
the child shapes. For example, the array will be of length 3 for a
Sierpinski triangle and of length 7 for an H shape. The array may
be initialized in the constructor of AbstractShape if the
AbstractShape constructor takes the array length as a parameter, or
directly in the constructor of the concrete classes if you make the
visibility of the array protected. The elements of the children
array are null for an initial fractal. When level 2 is created, the
array is filled with actual shapes. For level 3, it is the arrays
of the shapes that are added at level 2 that are filled,
etc.
In the AbstractShape class, implement the addLevel()
method to add a level to a shape. It will do so by initializing the
elements of the array of children for the last current level. In
the FractalDisplay class, you can check that the method is called
whenever the "Add level" button is clicked. Since the
FractalDisplay class has only a reference to the top level shape,
the method will iterate to get to the last level of the shape. Do
so by using recursion (don't use any loop, except to loop over the
elements of the array of children in a current level.) The base
case of the method will be attained when the array of children is
empty. Fill the array of children by calling a method (e.g.
createChildren()) declared abstract in AbstractShape and
implemented in the concrete shape classes. Dynamic binding will
automatically select the correct implementation! Of course, the
array should be filled only if the maximum level has not been
reached. Return a boolean to tell it if a new level could be added.
The boolean value is relayed to the FractalDisplay class to tell it
if the operation was successful. If a new level could not be added,
then the FractalDisplay displays a message box saying that no new
level can be added.
removeLevel() method (must use
recursion)
In the AbstractShape class, implement removeLevel() to
remove a level from a shape. If a shape has n levels, the last
level may be removed by setting to null the elements of the array
of children at level n-1. This method is called by the
DisplayFractal class whenever the button "Remove level" is clicked.
Contrary to the addLevel method, removeLevel won't iterate to the
last level of the selected shape. It will iterate to the level
preceding the last level. This is because to remove the last level,
the method needs to set to null the elements of the array of
children that refer to that last level. As for addLevel, iterate by
using recursion (that is don't use any loop, except to loop over
the elements of the array of children in a current level.) If the
shape that is displayed is at level 1, a level can't be removed. In
that case, removeLevel returns false. The boolean value will be
passed on to the display so that it can display a message box if a
level could not be removed.
countShapes() method (must use
recursion)
In the AbstractShape class, implement countShapes() to
count the total number of shapes in a shape. If there is only one
shape (level 1), the count is 1. For a shape with two levels, the
count is 1 + number of shapes at level 2, that is 4 triangles for a
Sierpinski triangle with two levels, or 8 H's for an H with two
levels. For a shape with three levels, the counts are 13 for a
Sierpinski triangle and 57 for an H.
As for addLevel and removeLevel, your implementation
must use recursion except to loop over the children shapes in a
current level.
The countShapes() method is called from the
FractalDisplay class whenever the mouse is right clicked on a
shape.
draw() method (must use recursion)
This is the method that must be implemented in each
concrete class (SierpinskiTriangle, HShape and MyShape). The method
takes a Graphics object that can draw almost anything. Check the
documentation of the Graphics class in the java documentation
pages. For example, the Sierpinski triangle may be drawn by
invoking the drawPolygon method.
As for all of the other methods discussed in this
section, use recursion to implement the method except to loop over
the array of children.
Extra-credit (up to 3 points)
Implement the update() method that takes an integer in
the 0-100 range and modifies the display of the current shape as a
function of the value of the integer. The value is changed by
moving a slider on the display window. Initially the value given by
the slider is set at 50. As an illustration, here is an example
with the Sierpinski triangle
Working code implemented in Java and appropriate comments provided for better understanding.
Here I am attaching code for all files:
AbstractShape.java:
import java.awt.Color;
import java.awt.Graphics;
import java.util.Random;
public abstract class AbstractShape implements Shape {
protected AbstractShape[] children; // the array of
children shapes
protected int level, width, height, drawStartX,
drawStartY; // level, width and height of the graphics frame,
origin starting at (0,0)
protected final int maxLevel;
// max level determined by each shape
class
protected Color color;
// the color the shape will
be drawn in
protected double sliderVal = 1.0; // slider value that
skews the shapes
public static int count;
// the number of shapes in
the display
private static Random rand = new Random();
/**
* AbstractShape child constructor shared by the shape
subclasses
*
* @param drawStartX
* x-coordinate origin
* @param drawStartY
*
y-coordinate origin
* @param width
* width of the graphics space
* @param height
*
height of the graphics space
* @param maxlevel
* maximum level of recursion
* @param level
* current level of recursion
*
*/
protected AbstractShape(int drawStartX, int
drawStartY, int width, int height, int maxLevel, int level) {
this(maxLevel,level);
this.drawStartX = drawStartX;
this.drawStartY = drawStartY;
this.width = width;
this.height = height;
}
/**
* AbstractShape constructor shared by the shape
subclasses
*
* @param maxlevel
* maximum level of recursion
* @param level
* current level of recursion
* @param color
* color of the shape
*
*/
protected AbstractShape(int maxLevel, int level)
{
this.maxLevel = maxLevel;
this.level = level;
this.color = new
Color(rand.nextInt());
}
/**
* Draws the current state of the recursive image
*
* @param g
* the Graphics context on which to draw
*/
@Override
public void draw(Graphics g) {
g.setColor(color);
if (children == null) {
drawBaseShape(g);
} else {
for
(AbstractShape child : children) {
child.draw(g);
}
}
}
/**
* Draw the base shape
*/
protected abstract void drawBaseShape(Graphics g);
/**
* Adds a level of recursive shapes
*
* @return boolean
*
Whether or not a level was added
*/
@Override
public boolean addLevel() {
if (level == maxLevel) {
return
false;
} else if (children != null)
{
for (Shape child
: children) {
if (!child.addLevel()) {
return false;
}
}
} else {
createChildren();
}
count = countShapes();
return true;
}
/**
* Reverses the recursive drawing of the shape and
removes level
*
* @return boolean
*
Whether or not a level was removed
*/
@Override
public boolean removeLevel() {
if (children == null) {
return
false;
} else if (children[0].children ==
null) {
children =
null;
} else {
for
(AbstractShape child : children) {
child.removeLevel();
}
}
count = countShapes();
return true;
}
/**
* Recursive algorithm to count the number of
shapes
*
* @return numOfShapes
*
The number of shapes below this shape and this
shape
*/
@Override
public int countShapes() {
if (children != null) {
int numOfShapes
= 0;
for
(AbstractShape child : children) {
numOfShapes += child.countShapes();
}
return
numOfShapes;
} else {
return 1;
}
}
/**
* Send shape count to FractalDisplay
*
* @return count
*
The number of shapes
*/
public static int getCount() {
return count;
}
/**
* Updates values for the slider
*
* @param value
*
The new slider value
*/
@Override
public void update(int value) {
sliderVal = value / 50.0;
int depth = findDepth();
children = null;
createChildrenAtDepth(depth);
}
/**
* @param depth
*
how deep to make the tree
*/
private void createChildrenAtDepth(int depth) {
if (depth > 1) {
createChildren();
depth--;
for
(AbstractShape child : children) {
child.createChildrenAtDepth(depth);
}
}
}
/**
* @return int depth
*
the depth of the tree
*/
private int findDepth() {
if (children == null) {
return 1;
} else {
return 1 +
children[0].findDepth();
}
}
/**
* Create a new set of children.
*/
protected abstract void createChildren();
}
FractalDisplay.java
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButton;
import javax.swing.JSlider;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
/**
* Construct a window to display the fractal shapes. This class is
almost
* complete. Add your own code only in the places marked with the
comment TODO
*
*/
public class FractalDisplay extends JPanel implements MouseListener, ActionListener, ChangeListener {
// Width of the inner panel
public static final int WIDTH = 800;
// Height of the inner panel
public static final int HEIGHT = 800;
// Possible shapes
private int which;
public static final int SIERPINSKI_TRIANGLE = 0;
public static final int H_SHAPE = 1;
public static final int MY_SHAPE = 2;
private JFrame frame;
// Other elements in the window
// Radio buttons (with their titles)
private String[] titles;
private JRadioButton[] radioButtons;
// The button to add/remove levels
private JButton addLevel, removeLevel;
// Slider to change the display (extra-credit
feature)
private JSlider slider;
// Current shape
private Shape shape;
// Use a popup menu to display information
// the total number of shapes in the current
shape
// -> triggered by a right click of the the
mouse.
private JPopupMenu popup;
private JLabel popupLabel;
private JLabel label;
/**
* Constructs a FractalDisplay to display fractal
shapes
*/
public FractalDisplay() {
// Use a windows look and feel (if
available)
try {
UIManager.LookAndFeelInfo[] lfinfo =
UIManager.getInstalledLookAndFeels();
UIManager.setLookAndFeel(lfinfo[4].getClassName());
} catch (Exception e) {/* ignore
any problem */
}
// Radio buttons
titles = new String[] { "Sierpinski
Triangle", "HShape", "My shape" };
radioButtons = new
JRadioButton[titles.length];
// Only one radio button can be
selected at a time
ButtonGroup buttonGroup = new
ButtonGroup();
for (int i = 0; i <
radioButtons.length; i++) {
radioButtons[i]
= new JRadioButton(titles[i]);
buttonGroup.add(radioButtons[i]);
radioButtons[i].addActionListener(this);
}
// Button to add or remove
levels to the shape
addLevel = new JButton("Add
level");
addLevel.addActionListener(this);
removeLevel = new JButton("Remove
level");
removeLevel.addActionListener(this);
// Slider to change the
shape
slider = new JSlider(0, 100,
50);
slider.addChangeListener(this);
// Place the components in the
frame
JPanel contentPane = new
JPanel();
contentPane.setLayout(new
BorderLayout());
// at the bottom (SOUTH)
JPanel southPanel = new JPanel(new
GridLayout(3, 1));
JPanel southPanelFirstRow = new
JPanel();
for (int i = 0; i <
radioButtons.length; i++)
southPanelFirstRow.add(radioButtons[i]);
southPanel.add(southPanelFirstRow);
JPanel southPanelSecondRow = new
JPanel();
southPanelSecondRow.add(addLevel);
southPanelSecondRow.add(removeLevel);
southPanel.add(southPanelSecondRow);
contentPane.add(southPanel,
BorderLayout.SOUTH);
JPanel southPanelThirdRow = new
JPanel();
southPanelThirdRow.add(slider);
southPanel.add(southPanelThirdRow);
setBackground(Color.WHITE);
contentPane.add(this,
BorderLayout.CENTER);
label = new JLabel("Number of
shapes: ");
label.setFont(new Font("Courier",
Font.BOLD, 32));
JPanel northPanel = new
JPanel();
northPanel.setBackground(Color.WHITE);
northPanel.add(label);
contentPane.add(northPanel,
BorderLayout.NORTH);
// Get ready to listen to mouse
clicks
addMouseListener(this);
// Put everything in a
frame
frame = new JFrame("Recursive
graphics");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(contentPane);
// Show it
frame.setSize(WIDTH, HEIGHT);
frame.setVisible(true);
// Resize it with the actual
size
Insets insets =
frame.getInsets();
int width = WIDTH + insets.left +
insets.right;
int height = HEIGHT + insets.top +
insets.bottom + (int) (southPanel.getPreferredSize().getHeight()) +
(int) (northPanel.getPreferredSize().getHeight());
frame.setSize(width, height);
frame.setResizable(false);
frame.setVisible(true);
// popup menu
popup = new JPopupMenu();
popupLabel = new JLabel("",
SwingConstants.CENTER);
popupLabel.setFont(new
Font("Courier", Font.BOLD, 32));
popup.add(popupLabel);
}
/**
* Handles the button clicks
*
* @param e the ActionEvent generated by the
click
*/
public void actionPerformed(ActionEvent e) {
if (e.getSource().getClass() ==
JRadioButton.class) {
for (int i = 0;
i < radioButtons.length; i++) {
if (e.getSource() == radioButtons[i]) {
which = i;
// reset the slider to its
default value
slider.setValue(50);
break;
}
}
switch (which)
{
case
SIERPINSKI_TRIANGLE:
shape = new SierpinksiTriangle(WIDTH,
HEIGHT);
shape.update(slider.getValue());
break;
case
H_SHAPE:
shape = new HShape(WIDTH, HEIGHT);
break;
case
MY_SHAPE:
shape = new MyShape(WIDTH, HEIGHT);
break;
}
} else if (e.getSource() ==
addLevel) {
// Don't do
anything if there is no display
if (shape !=
null) {
boolean success = shape.addLevel();
label.setText("Number of Shapes = " +
AbstractShape.getCount());
if (!success) {
JOptionPane.showMessageDialog(this, "Can't add another level",
"Message",
JOptionPane.WARNING_MESSAGE);
}
}
} else if (e.getSource() ==
removeLevel) {
// Don't do
anything if there is no display
if (shape !=
null) {
boolean success = shape.removeLevel();
label.setText("Number of Shapes = " +
AbstractShape.getCount());
if (!success) {
JOptionPane.showMessageDialog(this, "Can't remove another level",
"Message",
JOptionPane.WARNING_MESSAGE);
}
}
} else {
// unknown
source
return;
}
// display the new drawing
repaint();
}
/**
* Paints the shape
*/
public void paintComponent(Graphics gfx) {
super.paintComponent(gfx);
// If there is nothing to display,
stop here
if (shape != null) {
// Use some
graphics2D features (smooth edges)
Graphics2D g =
(Graphics2D) gfx;
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
shape.draw(g);
}
}
/**
* Different platforms might have different ways to
trigger a popup menu: check
* all possibilities
*/
public void mousePressed(MouseEvent e) {
checkPopup(e);
}
public void mouseClicked(MouseEvent e) {
checkPopup(e);
}
public void mouseReleased(MouseEvent e) {
checkPopup(e);
}
/**
* Displays a message box giving the total number
shapes in the current shape
*/
private void checkPopup(MouseEvent e) {
// Do it only if we have a request
for a pop up menu, aka right click...
if (!e.isPopupTrigger()) {
return;
}
// Display the information about
the shape currently painted
if (shape != null) {
int count =
shape.countShapes();
popupLabel.setText("total number of shapes = " + count);
popup.show(e.getComponent(), e.getX(), e.getY());
}
}
@Override
public void stateChanged(ChangeEvent e) {
if (shape != null) {
shape.update(slider.getValue());
repaint();
}
}
public void mouseExited(MouseEvent e) {
}
public void mouseEntered(MouseEvent e) {
}
/**
* Starts the application
*/
public static void main(String[] args) {
new FractalDisplay();
}
}
Shape.java:
import java.awt.Graphics;
public interface Shape {
/**
* Draws the current state of the recursive image
*
* @param g
* the Graphics context on which to draw
*/
void draw(Graphics g);
/**
* Adds a level of recursive shapes
*
* @return boolean
*
Whether or not a level was added
*/
public boolean addLevel();
/**
* Reverses the recursive drawing of the shape and
removes level
*
* @return boolean
*
Whether or not a level was removed
*/
public boolean removeLevel();
/**
* Recursive algorithm to count the number of
shapes
*
* @return numOfShapes
*
The number of shapes below this shape and this
shape
*/
public int countShapes();
/**
* Updates values for the slider
*
* @param value
*
The new slider value
*/
public void update(int value);
}
HShape.java:
import java.awt.Color;
import java.awt.Graphics;
import java.util.Random;
public class HShape extends AbstractShape {
protected static final int maxLevel = 5;
/**
* Create a new HShape.
* This constructor is used when creating the root
HShape object.
*
* @param width
* The width of the
display.
* @param height
* The height of the
display.
*/
public HShape(int width, int height) {
this(0,0,width,height,1);
}
/**
* Create a new HShape.
* This constructor is used for creating
children.
*
* @param drawStartX
* x-coordinate origin
* @param drawStartY
*
y-coordinate origin
* @param width
* width of the graphics space
* @param height
*
height of the graphics space
* @param level
* The depth of this shape in relation to the
root.
*/
public HShape(int drawStartX, int drawStartY, int
width, int height, int level) {
super(drawStartX, drawStartY,
width, height, maxLevel, level);
}
/**
* Create a new set of children.
*/
@Override
protected void createChildren() {
this.children = new
AbstractShape[7];
int newLevel = level + 1;
int childWidth = (int)
Math.round(width / 3.0);
int childHeight = (int)
Math.round(height / 3.0);
int childNumber = 0;
for (int row = 0; row < 3;
row++) {
for (int col =
0; col < 3; col++) {
if (col == 1 && row != 1) {
continue;
}
// make child
children[childNumber] = new HShape(childWidth *
col + drawStartX, childHeight * row + drawStartY,
childWidth, childHeight, newLevel);
childNumber++;
}
}
}
/**
* Draw the base shape
*/
protected void drawBaseShape(Graphics g) {
g.fillRect(drawStartX, drawStartY,
width / 3, height);
g.fillRect(drawStartX + width / 3,
drawStartY + height / 3, width / 3, height / 3);
g.fillRect(drawStartX + width / 3 *
2, drawStartY, width / 3, height);
}
}
MyShape.java:
import java.awt.Color;
import java.awt.Graphics;
import java.util.Random;
public class MyShape extends AbstractShape {
protected static final int maxLevel = 7;
/**
* Construct a new MyShape.
* This constructor is for the initial root
shape.
*
* @param width
* The width of the
display.
* @param height
* The height of the
display.
*/
protected MyShape(int width, int height) {
this(0, 0, width, height, 1);
}
/**
* Construct a new MyShape.
* This constructor is used for creating
children.
*
* @param drawStartX
* x-coordinate origin
* @param drawStartY
*
y-coordinate origin
* @param width
* width of the graphics space
* @param height
*
height of the graphics space
* @param level
* The depth of this shape in relation to the
root.
*
*/
protected MyShape(int drawStartX, int drawStartY, int
width, int height, int level) {
super(drawStartX, drawStartY,
width, height, maxLevel, level);
}
/**
* Create a new set of children.
*/
@Override
protected void createChildren() {
this.children = new
AbstractShape[8];
// nearly identical to the
HShape...
int newLevel = level + 1;
int childWidth = (int)
Math.round(width / 3.0);
int childHeight = (int)
Math.round(height / 3.0);
int childNumber = 0;
for (int row = 0; row < 3;
row++) {
for (int col =
0; col < 3; col++) {
if (col == 1 && row == 1) {
continue;
}
// make child
children[childNumber] = new MyShape(childWidth *
col + drawStartX, childHeight * row + drawStartY,
childWidth, childHeight, newLevel);
childNumber++;
}
}
}
/**
* Draw the base shape
*/
@Override
protected void drawBaseShape(Graphics g) {
g.fillRect(drawStartX, drawStartY,
width, height);
}
}
SierpinksiTriangle.java:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Polygon;
import java.util.Random;
public class SierpinksiTriangle extends AbstractShape {
protected static final int maxLevel = 10; // The max
depth of the tree from the root.
private int[] xPoints = new int[4]; // array of
integers representing the x values of the triangles vertices.
private int[] yPoints = new int[4]; // array of
integers representing the y values of the triangles vertices.
/**
* Construct the initial SierpinksiTriangle
shape.
*
* @param width
* The initial display
width
* @param height
* The initial display
height
*/
protected SierpinksiTriangle(int width, int height)
{
// Height - 1 to prevents the line
from being drawn outside the box;
// it seems the display box has a
height of 800 but only displays [0,799]
// I'm not concerned with loosing
the last pixel of the right side, so width was not adjusted.
this(
new int[] { 0,
width / 2, width, 0 },
// xPoints
new int[] {
height-1, 0, height-1, height-1 }, // yPoints
1.0,
// slider value
1
// starting level
);
}
/**
* Construct a new SierpinksiTriangle.
*
* This constructor is used for creating
children.
*
* @param xPoints
* array of integers
representing the x values of the triangles vertices.
* @param yPoints
* array of integers
representing the y values of the triangles vertices.
* @param sliderVal
* The value of the
slider
* @param level
* The depth of this
shape in relation to the root.
*
*/
protected SierpinksiTriangle(int[] xPoints, int[]
yPoints,double sliderVal, int level) {
super(maxLevel, level);
this.xPoints = xPoints;
this.yPoints = yPoints;
this.sliderVal = sliderVal;
}
/**
* Create a new set of children.
*/
@Override
protected void createChildren() {
this.children = new
AbstractShape[3];
int newLevel = level + 1;
int[] sharedX = new int[]
{
(int) ((xPoints[1] - xPoints[0]) / 2.0 *
sliderVal) + xPoints[0],
(int) ((xPoints[2] - xPoints[1]) / 2.0 *
sliderVal) + xPoints[1],
(int) (xPoints[2] + ((xPoints[0] - xPoints[2]) /
2.0 * sliderVal)) };
int[] sharedY = new int[]
{
(int) (yPoints[0] - ((yPoints[0] - yPoints[1]) /
2.0 * sliderVal)),
(int) (yPoints[1] + ((yPoints[2] - yPoints[1]) /
2.0 * sliderVal)),
(int) (yPoints[2] + ((yPoints[0] - yPoints[2]) /
2.0 * sliderVal)) };
children[0] = new
SierpinksiTriangle(new int[] { xPoints[0], sharedX[0], sharedX[2],
xPoints[3] },
new int[] { yPoints[0],
sharedY[0], sharedY[2], yPoints[3] },
sliderVal, newLevel);
children[1] = new
SierpinksiTriangle(new int[] { sharedX[0], xPoints[1], sharedX[1],
sharedX[0] },
new int[] { sharedY[0],
yPoints[1], sharedY[1], sharedY[0] },
sliderVal, newLevel);
children[2] = new
SierpinksiTriangle(new int[] { sharedX[2], sharedX[1], xPoints[2],
sharedX[2] },
new int[] { sharedY[2],
sharedY[1], yPoints[2], sharedY[2] },
sliderVal, newLevel);
}
/**
* Draw the base shape
*/
@Override
protected void drawBaseShape(Graphics g) {
g.drawPolyline(xPoints, yPoints,
4);
}
}
Sample Output Screenshots: