![]() |
Problem Set 10 Due on Wednesday, May 7 |
If you decide to do this problem with a partner, you are required to do all Java programming side-by-side at a single computer; you are not allowed to split up the programming tasks and work on them independently. Because of this requirement, you should be careful to choose a partner whose schedule is compatible with yours. To promote interaction, you should swap "drivers" (the person who is typing at the keyboard) at regular intervals.
It is still the case that sharing of Java code is not allowed between different teams, where a "team" is either a pair of students working together or a student working alone. As always, you may discuss any aspect of the problem in English.
lec25_miniworlds
folder in the CS111 download folder, which you are encouraged
to download and experiment with.)
The purpose of this problem set is to give you experience with data abstraction, arrays, and building an interesting program from scratch. In Task 1, you will study data abstraction in the context of simple rectangle objects presented in Lecture 24. In Task 2, you will build from scratch an animation that has many BuggleWorld-like features. As an optional open-ended extra credit challenge, you can extend your implementation from Task 2 with additional features.
All code for this assignment is available in the ps10_programs folder in the cs111 download directory on the CS fileserver.
Rect3.java
,
Rect4.java
, and
Rect5.java
from Task 1b;
GubbleWorldSprite.java
,
Gubble.java
, GubbleGroup.java
,
and any other auxiliary classes that you create
for Task 2.
ps10_programs
folder. In particular,
Rect
subfolder should
include your final versions of
Rect3.java
,
Rect4.java
, and
Rect5.java
from Task 1b.
GubbleWorld
subfolder should
include your final versions of
GubbleWorldSprite.java
,
Gubble.java
,
and GubbleGroup.java
.
If you have worked as a pair, you only need to submit one softcopy, in the drop folder of one of the partners. Be sure to indicate which drop folder you are using on the cover sheet of your hardcopy.
A key idea in CS111 is data abstraction -- the notion that "users" can successfully use objects by understanding their contracts without knowing how the contracts have been implemented by the "implementers". Contracts not only serve to simplify the programming task for users, they also give implementers the flexibility to experiment with different ways to implement the same contract.
In Lecture 24, we studied the notion of multiple implementations
of a data abstraction in the context of a simple rectangle
abstraction known as a Rect
. Conceptually,
an instance of Rect
is a rectangle positioned in
the standard Cartesian coordinate system:
Here is a sample rectangle with lower left point (23,17), width 19, and height 11:
We emphasize that the rectangles we study in this problem are not the
same as the java.awt.Rectangle
class used in
graphics applications, such as the Faces problem in Problem Set 9.
Below is a simple contract for the Rect
class that
supports two constructor methods and four instance methods.
This class could easily be extended with more methods, such as
methods for finding the perimeter or area of a rectangle, or
returning its upper left and lower right points. But we've
kept the contract small for simplicity's sake.
Rect Contract
Constructor Methods
public Rect (int llx, int lly, int width, int height);
Construct a rect from with lower left point (llx
,lly
), widthwidth
, and heightheight
.
public Rect (Point ll, Point ur);
Construct a rect with lower left pointll
and upper right pointur
.Instance Methods
public int width ();
Returns the width of this rect.
public int height ();
Returns the height of this rect.
public Point lowerLeft ();
Returns the lower left point of this rect.
public Point upperRight ();
Returns the upper right point of this rect
As an example, the rectangle depicted above could be created by either of the following constructor method invocations:
new Rect(23, 17, 19, 11);
new Rect(new Point(23, 17), new Point(42, 28));
In the remainder of this problem, you will consider five different
implementations of the Rect
contract.
In order to be able to discuss the different implementations,
we will name them Recti
, where
i
ranges between 1 and 5.
Rect1
and Rect2
that implement the contract for Rect
(modulo the change in constructor name).
In this subtask, you should draw an object diagram that shows the result of
the following sequence of statements:
Point p1 = new Point (1,2); Point p2 = new Point (9,6); Rect1 a1 = new Rect1 (p1,p2); Point ur1 = a1.upperRight(); Rect1 b1 = new Rect1 (ur1.y, ur1.x, a1.height(), a1.width()); Rect2 a2 = new Rect2 (p1,p2); Point ur2 = a2.upperRight(); Rect2 b2 = new Rect2 (ur2.y, ur2.x, a2.height(), a2.width());
Implementation of theRect1
class:import java.awt.Point; public class Rect1 { // INSTANCE VARIABLES -------------------------------- private int llx; // x coordinate of the lower left corner private int lly; // y coordinate of the lower left corner private int width; // width of the rect private int height; // height of the rect // CONSTRUCTOR METHODS ------------------------------- // Construct a rect from the lower left (x,y), width, and height. public Rect1 (int llx, int lly, int width, int height) { this.llx = llx; this.lly = lly; this.width = width; this.height = height; } // Construct a rect from the lower left and upper right points. public Rect1 (Point ll, Point ur) { this.llx = ll.x; this.lly = ll.y; this.width = ur.x - ll.x; this.height = ur.y - ll.y; } // INSTANCE METHODS ---------------------------------- // Returns the width of this rect. public int width () { return width; } // Returns the height of this rect. public int height () { return height; } // Returns the lower left point of this rect. public Point lowerLeft () { return new Point (llx, lly); } // Returns the upper right point of this rect. public Point upperRight () { return new Point (llx + width, lly + height); } }
Implementation of theRect2
class:import java.awt.Point; public class Rect2 { // INSTANCE VARIABLES -------------------------------- private Point ll; // lower left corner of rect. private Point ur; // upper right corner of rect. // CONSTRUCTOR METHODS ------------------------------- // Construct a rect from the lower left (x,y), width, and height. public Rect2 (int llx, int lly, int width, int height) { this.ll = new Point (llx, lly); this.ur = new Point (llx + width, lly + height); } // Construct a rect from the lower left and upper right points. public Rect2 (Point ll, Point ur) { this.ll = ll; this.ur = ur; } // INSTANCE METHODS ---------------------------------- // Returns the width of this rect. public int width () { return ur.x - ll.x; } // Returns the height of this rect. public int height () { return ur.y - ll.y; } // Returns the lower left point of this rect. public Point lowerLeft () { return ll; } // Returns the upper right point of this rect. public Point upperRight () { return ur; } }
Rect1
and Rect2
illustrate only two
of many ways to implement the given Rect
contract.
In this subtask, you will consider three more implementations.
Below are skeletons of classes Rect3
, Rect4
,
and Rect5
, each of which has one constructor method.
Partial Implementation of theRect3
class:public class Rect3 { // This declaration allows us to indicate all IntList operations // using the prefix "IL." private static IntList IL; public Rect3 (int llx, int lly, int width, int height) { coords = IL.prepend(llx, IL.prepend(lly + height, IL.prepend(llx + width, IL.prepend(lly, IL.empty())))); } }
Partial Implementation of theRect4
class:public class Rect4 { public Rect4 (int llx, int lly, int width, int height) { area = width * height; ul = new Point(llx, lly + height); ur = new int [2]; ur[0] = llx + width; ur[1] = lly + height; } }
For each of the above three class declarations, your goal is to flesh out the rest of the class declaration to correctly implement the
Partial Implementation of theRect5
class:public class Rect5 { public Rect5 (Point ll, Point ur) { sumsAndDiffs = new Point [2]; sumsAndDiffs[0] = new Point(ur.x + ll.x, ur.y + ll.y); sumsAndDiffs[1] = new Point(ur.x - ll.x, ur.y - ll.y); } }
Rect
contract. To do this, you will need to flesh out
the class skeleton with appropriately defined instance variables,
the other constructor method, and all instance methods.
All of these will need to be consistent with the given constructor
method (which you should not change).
Notes:
Rects
of ps10_programs
contains the implementations of Rect1
and Rect2
as well as the skeletons of Rect3
, Rect4
,
and Rect5
. You should flesh out the three skeletons.
.java
file for the class to the Rects.mcp
project. You can do this via drag-and-drop (on the Macintosh),
or via the Project:Add Files menu item (on both Macs and PCs)
RectTest
class contains code that performs
a simple test of the Rect1
and Rect2
implementations. It also contains commented out code that tests
the remaining implementations. When you flesh out an implementation,
you should uncomment the relevant testing code in
RectTest
. Compiling and executing the project file
Rects.mcp
will execute the main()
method
in the RectTest
class and display the results of
the tests in the Java Console window.
Rect3
has been configured with a declaration
that allows you to refer to any of the class methods of the
IntList
by prefixing them with "IL.
".
For instance: IL.prepend
,
IL.head
,
IL.tail
,
IL.empty
,
IL.isEmpty
.
Rect5
, consider the following. Suppose
there are two unknown numbers a and b, and you are given
two numbers c and d such that c = a + b and
d = a - b.
How can you determine the value of a given c and d?
How can you determine the value of b given c and d?
Point p1 = new Point (1,2); Point p2 = new Point (9,6); Rect3 a3 = new Rect3 (p1,p2); Point ur3 = a3.upperRight(); Rect3 b3 = new Rect3 (ur3.y, ur3.x, a3.height(), a3.width()); Rect4 a4 = new Rect4 (p1,p2); Point ur4 = a4.upperRight(); Rect4 b4 = new Rect4 (ur4.y, ur4.x, a4.height(), a4.width()); Rect5 a5 = new Rect5 (p1,p2); Point ur5 = a5.upperRight(); Rect5 b5 = new Rect5 (ur5.y, ur5.x, a5.height(), a5.width());
Gubbles are distant relatives of buggles that look like this:
Their heads are filled circles that are 25% of their body length, and their abdomens are filled circles that are 75% of their body length. They also have four legs that project outward from the center of their abdomen. A gubble can be any color.
Like buggles, gubbles live in a two-dimensional world (GubbleWorld) that is similar to BuggleWorld except for the following visual differences:
Below are pictures of some sample GubbleWorld configurations. As is apparent from the top left picture, (1) some instances of GubbleWorld do not have internal walls and (2) it is possible for two or more gubbles to occupy the same grid cell.
|
||
|
Inspired by Stephen Wolfram's book, A New Kind Of Science, gubbles have decided to experiment with cellular automata. One class of cellular automata involves two-dimensional grids whose contents change over time according to local rules that specify the next contents of a subgrid of the cells based on the current contents of that subgrid. A well-known example of this class is John Horton Conway's game of life. (Also see Mitch Resnick & Brian Silverman's active essay, Exploring Emergence.)
Gubbles are investigating their own form of cellular automata in their two-dimensional grid world. They have developed the following set of rules. In all of the rules, the term obstruction refers to either (1) a wall or (2) another gubble. When obstructions are mention as being to the left, front, or right of the gubble, this means the immediate left, immediate front, or immediate right.
Time t | Time t+1 |
|
|
Time t | Time t+1 |
|
|
|
|
Time t | Time t+1 |
|
|
|
|
Time t | Time t+1 |
|
|
|
|
|
|
Time t | Time t+1 |
|
|
Time t | Time t+1 |
|
|
|
|
It is often the case that several of the above rules will be need to explain how one configuration advances to the next. For example, all rules except for rule 5 are needed to explain how the left configuration transforms to the right configuration below. Make sure you can explain which rule controls the fate of each gubble in the left configuration.
Time t | Time t+1 |
|
|
It is worth emphasizing that in configurations with multiple gubbles, the rules are simultaneously applied to all the gubbles in a configuration. In some cases, it is impossible to explain the gubble behavior by applying the rules to the gubbbles sequentially (one by one) in any order. For example, in the following application of rule 4 seen above, the rule must be applied to both the red and blue gubbles simultaneously. If we first applied it to the red gubble, causing it to split in two, then the blue gubble would follow rule 1 and move forward instead of splitting! Similarly, if we first applied rule 4 to the blue gubble, causing it to split in two, then the red gubble would follow rule 1 and move forward instead of splitting.
Time t | Time t+1 |
|
|
Below is a an AnimationWorld
applet that shows
several examples of the above rules in action for some initial
configurations of gubbles and walls. You should play with this
applet (especially by using the Next button) to make
sure that you understand all of the rules that the gubbles
are following.
Notes on the above applet:
ps10_programs/GubbleWorld/Test
.
GubbleWorld
: This is a subclass of Sprite
that implements an entire GubbleWorld cellular automata as a single
sprite. Each call to updateState()
on a
GubbleWorld
instance causes the configuration
of the cellular automaton represented by that instance to advance
by one time step according the the rules explained above.
You must flesh out this class so that it correctly implements the
GubbleWorld
Contract.
Gubble
: This class implements the gubble creatures
that live within an instance of GubbleWorld
.
You must flesh out this class so that it correctly implements the
Gubble
Contract.
GubbleGroup
: This class implements indexed collections
of gubbles. Your implementation of GubbleWorld
should use
instances of GubbleGroup
to represent the collection of
gubbles that live in a particular configuration.
You must flesh out this class so that it correctly implements the
GubbleGroup
Contract
Notes:
ps10_programs/GubbleWorld
.
The Test
subdirectory of this directory contains a working version of the
sample GubbleWorld animations applet.
GubbleWorld
and Gubble
methods, study the code for creating the sample animation shown above:
GubbleWorldShowcase.java. Make sure you understand how this code specifies
the initial configurations of each of the six animations in the applet.
When you compile your implementation of GubbleWorld, this is the class that
is used to test your implementation.
MiniBuggleWorld
from Lecture 25 before attempting to implement GubbleWorld
.
In particular, you should understand all the details of the following
three files:
GubbleWorld
must handle sprite methods
(updateState()
, drawState()
,
and resetState()
) that do not appear in
the BuggleWorld
class. The fact that all
drawing for GubbleWorld is done by drawState()
simplifies the implementation of gubbles, which, unlike
buggles, do not need to inform the world in which they
live of changes to their state.
run()
method
in a subclass of MiniBuggleWorld
.
GubbleWorld has no run()
method; the motions
of all gubbles in the initial configuration is completely
determined by the GubbleWorld Rules in the
GubbleWorld
Contract.
These rules are implemented by the updateState
method
of the GubbleWorld
class.
MiniBuggleWorld
implementation, the collection of
buggles in the world is represented by an array
of buggles. In your GubbleWorld
implementation,
you should use instances of the GubbleGroup
class
to represent collections of gubbles.
Buggle
constructor method has zero parameters,
but the Gubble
constructor method has four parameters,
including an explicit GubbleWorld
parameter.
addGubble
(as seen in
GubbleWorldShowcase.java).
throw new MiniBuggleException(string);
throw new GubbleException(string);
GubbleWorld
as an empty grid with the dimensions
specified in the GubbleWorld
constructor method.
For this subtask, you will have to define a non-trivial
GubbleWorld
constructor and a
nontrivial drawState()
method. In order to get your
project to compile, you will also have to provide trivial
(do-nothing) "stub" definitions for some other
GubbleWorld
methods. For instance,
GubbleWorldShowcase
invokes addGubble
and addWall
, so you must have stubs for these.
And because GubbleWorld
is a subclass of Sprite
,
you must provide stub definitions for updateState
and resetState
. Test this subtask by verifying that
the correct grids are created for each of the six sample animations.
GubbleWorld
implementation to handle the addGubble
method and to
display gubbles in the initial configuration. For this subtask, you
will need to flesh out the constructor method and draw
method of the Gubble
class. You will also need an
implementation of most of the GubbleGroup
class.
(Note: as discussed in Lecture 24, there are many different ways
to implement a collection like GubbleGroup
. Choose one!)
Test this subtask by verifying that gubbles are drawn appropriately
in the initial configurations for each of the six sample animations.
At this stage, your updateState()
method should still
be empty, so running the animation will have no effect.
GubbleWorld
implementation to handle the addWall
method.
It is recommended that you represent the walls as a two-dimensional
array of booleans, though other representations are possible.
Test this subtask by verifying that walls are drawn appropriately
in the initial configurations for the Wall Mart
and
G-commerce
animations.
GubbleWorld
implementation to implement the GubbleWorld Rules
for updating configurations, as specified in the
GubbleWorld
Contract.
You should encode these rules in the updateState
method
of the GubbleWorld
class. For this subtask, you will
need to flesh out all the remaining methods of GubbleWorld
,
Gubble
, and GubbleGroup
, except for the
resetState
method of GubbleWorld
.
Test this subtask by verifying for each GubbleWorld test animation
that the buggles move according to the rules. It would be helpful
to place the applet for your implementation side-by-side with the
test applet and verify that pressing the Next button in
each applet has the same effect.
Perhaps the most challenging aspect of the GubbleWorld
implementation
is ensuring that the GubbleWorld Rules implemented in
updateState
are applied simultaneously rather than
sequentially. (See the earlier discussion on
simultaneous vs. sequential rule application.)
When implementing the rules in updateState
,
you should not change the state of
any gubbles in the current configuration, because this will affect
the obstructions detected by other gubbles in that configuration.
Instead, you should use the copy
method of the Gubble
class to create copies of any gubbles that survive to the next configuration,
and change the state of the copies. This will ensure that all gubbles in
the current configuration see a consistent view of the world.
GubbleWorld
implementation by implementing the resetState
state method.
You may find the copy
method of the GubbleGroup
class handy for this subtask.
Test this subtask by verifying for each GubbleWorld test animation that pressing
the Reset button correctly resets that world back to its
initial configuration.
GubbleWorld
implementation from Task 2 in various ways. Below are a few suggestions,
but you are welcome to design your own extensions. Feel free to talk with
Lyn or Elena about any ideas you have.
Gubble
and implement configurations that test them.
For example:
WallEater
gubbles that eat internal walls
rather than treating them as obstructions.
WallBuilder
gubbles that leave a trail of walls
behind them.
GubbleEater
gubbles that do not die when
they eat other gubbles, but instead gain energy from
such meals. A GubbleEater
with sufficient
energy can spontaneously split into two
GubbleEater
s, each with half the energy.
A GubbleEater
with insufficient energy dies.
You need to decide what happens when two
GubbleEater
s are in the same cell.
As a variant, you might consider gubbles that gain
energy from eating walls, or "food" that "grows" in
cells or is left behind by other gubbles.