CS 112 - Introduction to Computer Science II
Homework #9

Due: At the beginning of class 27.

NOTE: This work is to be done in pairs.  I strongly recommend the practice of Pair Programming described simply here.  Although team members are permitted to divide work, each team member should be able to informally present all work of his/her teammate.

JavaFX Interactive GridPane for Birds of a Feather Game

In this exercise, you will build upon the previous specification for the Birds of a Feather CardPane.java in order to build an interactive Birds of a Feather game GridPane called CardGridPane.java.  Your implementation will be tested with my CardPane class, so it's important that you do not presume anything beyond the ability to construct a CardPane with a StringProperty. Let me emphasize up front: Do not rely upon fields, getters, or any additional methods you may have created in your CardPane.  All interaction with each CardPane should be through the StringProperty 2D array you are given in the CardGridPane.java constructor, from which you'll get each StringProperty used to construct each CardPane.  This allows a clean, easy interaction with your CardPane objects.

You (and graders) will test your CardGridPane implementation with given file CardGridPaneTest.java which should not be altered.  This JavaFX Application first prompts you for a positive integer seed for generating a shuffled card deal, creates a 4-by-4 2D array of StringProperty values for the dealt cards, and constructs a CardGridPane with that StringProperty[][] argument.  So CardGridPane extends GridPane and has as its only required method a constructor:

	public CardGridPane(StringProperty[][] cardGrid) {
		...
	}

Here is a screenshot of the CardGridPaneTest with seed 1:

It is possible (but not required) to implement all code for this assignment (except for private fields) within your constructor.  (My implementation with comments is 55 lines of code.)  My recommendation is to approach this work in stages.

Populating your grid layout with CardPanes

First, you will create previous assignment CardPane objects in four rows and four columns that are constructed with the corresponding StringProperty objects as you are given in the constructor parameter.  These are added to the correct rows and columns within your CardGridPane (which extends GridPane).

Then add a margin around each card so that they maintain a separation of at least a few pixels.  For this, you'll want to find examples of the use of setMargin and the class Insets.

Enable mouse dragging

Next, I recommend adding the ability to detect mouse drag events from CardPane to CardPane.  Before adding a move behavior, it is beneficial to identify a smaller "stepping stone" along the way.  For now, your object is to get your CardGridPane to print the intended move.  For example, if I press the mouse on the 5C above, drag to the 5D above, and release the mouse, I'd like my program to print the intended move source row, source column, destination row, and destination column.  In this example, I might have my code print:

1 2 -> 3 2

This helps me see that I'm getting the necessary information before I attach a behavior to such events.  (You will comment out extra printing before assignment submission.)  Here is what you need to know in order to get and print this row/column information:

Make legal moves

Once you're sure you're detecting correct user moves, you can comment out your print statement and make the intended move if it is legal.

A move is legal if:

For illegal moves, nothing changes.  For a legal move, the appropriate StringProperty objects should be changed so that the destination CardPane StringProperty is set to the String of the source CardPane StringProperty, and then the source CardPane StringProperty is set to the empty String.  Now, your CardPaneGrid should enact legal Birds of a Feather moves.

Make a stack of legal moves

Now that we can make moves, it would be handy to be able to undo such moves.  For this, we can create one or more stacks that hold move information.  With each legal move, we need to push (into one or more stacks) the minimum following information: source row, source column, destination row, destination column, and card covered by the move.  With such information, a command from the user to undo will have us pop our stack(s) and have sufficient information to undo the previous move.

About creating undo stack(s): There are many ways to do this, so your undo stack(s) design is up to you.  You'll need one or more private Stack fields for your CardGridPane object.  One could put all information into a String, have a single Stack<String> and read one's undo information from a popped String using a Scanner.  One could create two separate stacks, one for int arrays to hold grid position information (e.g. {r1, c1, r2, c2}) and another for card Strings.  One could define an inner class to hold all such information in fields of your inner class object.  The choice is up to you.

First, add such code to push information into the stack(s) with each legal move, and temporarily print your undo stack(s) information until you can see that all necessary information is in place.

Detect undo key events

Before processing undo key events (Control-'z' or 'u'), you'll want to make sure you're detecting such events correctly, so your goal for this stage is to print "undo" every time a user types Control-'z' or 'u'.

We want to add a KeyListener to detect key presses in our pane's Scene object.  However, a call to getScene when the constructor is first run will yield a null value.  (The object hasn't been added to a Scene yet!) 

However, we can listen for changes to the CardGrid's scene property to detect when it is no longer null, and set the handler for the Scene's OnKeyPressed Event then:

		sceneProperty().addListener((o, oldVal, newVal) -> { 
			// setup undo listener
			if (getScene() != null) {
				getScene().setOnKeyPressed(ev -> {
					// Add keyboard listener for Control-z / u undo event using lambda expression
...

If the key pressed is neither Control-'z' or 'u', ignore it. When Control-'z' or 'u' is pressed, print "undo".  Make sure you are detecting undo key pressed before proceeding, and make sure you're not causing multiple undo events for each key press.

Undo moves

Now, when Control-'z' or 'u' is pressed, check to see if your undo stack(s) are empty.  If so, ignore the undo command as there's no move to undo. However, if your stack(s) are not empty, pop the stack(s) and undo the move indicated by setting the source CardPane StringProperty to the destination CardPane StringProperty and then setting the destination StringProperty back to the card covered by the move.

Once you have this working and have removed extraneous test printing, you are done!  Observe how we have followed part of the Agile software development methodology by practicing incremental development.   I'll often make the analogy that this is like locating stepping stones for crossing a river.  Rather than create a large mess of code without testing, we proceed in modest stages and build success upon success.  Not only is this psychologically satisfying, but it makes debugging easier if we are testing as we go and are assured of some parts working when seeking the source of a problem.

Notes:

There are many ways to accomplish this task. The submission system will collect your CardPane.java only and will not offer feedback.  These will be graded manually according to the following grading rubric:

Rubric: (20 points total)