Thinking in Objects: Methods and Encapsulation
The goal of this lab is to work together to refactor the given code to make better use of the existing classes by giving them more methods that use instance variables that are hidden from the main class.
Additionally, you will make a simple "test case" to practice working with Timelines.
Getting Started
For this lab exercise, you must work together with someone else in the class (for example your partner for the Game project):
- One person should fork the original lab_frogger repository into their own account to provide a place for the group to edit, commit, and push changes
- On the resulting project web page, go to
Settings -> Membersto add your partner so both people in the group can access the same repository - Search for your partner's name and give them a
Maintainerrole in the project and chooseAdd to Project - Both students should clone the one forked repository to their individual machines so they can work on their own separate copies
- Import the project into IntelliJ (
New -> Project From Existing Sources)using the folder you just created (and just accept the defaults for all the options by selectingNext)
- Once the project window appears, you will need to do a few more steps to get it to run:
- Select
File -> Project Structureand then selectLibrarieswithin the dialog that appears - Right click on the correct library for your platform (i.e., one of
linux,mac, orwin) and selectAdd to Modules - Click
OKin the dialog that appears andOKagain to close the Project Settings dialog - Click to expand the folders
src -> froggerto find the Java classGame - Right click on this class to run it by selecting
Run Game.main()
- Select
- Run the program to verify that your Java installation is working.
If there are compilation errors or it does not run, then the project is not correctly configured.
Discussion
Use Gitlab's markdown format to record the group's answers in the file called DISCUSSION.md that is included with the example code. IntelliJ includes a markdown editor that provides a preview or here is a web-based editor that provides similar functionality and can be shared like Google Docs.
Start by reading over the code to see if you have any questions about how it works. Currently, one rectangle moves back and forth across the screen to block the player's "frog" from moving from the bottom of the screen to the top. Your ultimate goal is to make any number of blocks moving back and forth across the screen at random speeds.
To begin discussing the code's design, spend some time evaluating the code using the following questions as a guide:
- What are some reasons to prefer having the
Rectanglecontained as an instance variable in theBlockorMoverclasses? What are some reasons to prefer having themextendRectangle? - Based on the names of variables and what the
Gameclass is doing with theRectangleit gets from callinggetShape(), what methods does it make sense for theMoverandBlockclasses to have? - Given those methods, what constants and variables does it make sense to hide from the
Gameclass (i.e., extract into one of the other classes)? - Given those variables, what parameters should the constructor for the
MoverandBlockclasses take? - Do you think it makes sense to make any of the methods (current or proposed)
privateinstead ofpublic?
Refactor the Code
As a reminder, try to practice using the suggested workflow to integrate new versions of the code with the other person's copy of the code regularly — start with each person making their own separate branch.
Each person should choose a class (either Mover or Block) in which to create the new methods you discussed (without changing the signature of the existing methods except perhaps the constructor), then merge the code (this should produce no conflicts since the Game class should not be refactored yet), then work together to refactor the Game class.
You should be able to do most of this simply by moving the existing code to the new classes and then calling the new methods in place of the old code with the appropriate parameters. In the current design, the method getShape() is a necessary evil because it needs to be passed to OpenJFX methods that require it, but the Game class is accessing it far too often and thus doing too much work that an active object could be doing to share the "code intelligence". In fact, the ultimate goal is that, when completely refactored, the Game class would not call any methods directly on the Rectangle class (e.g., getX(), setX(), setFill(), etc.). Instead those calls should be done in either the Mover or Block classes (so, for example, we could change the Rectangle to an ImageView without changing the Game class code). Thus you will know you are "done" when the only calls to getShape() within the Game class are in the add() calls within the setupGame() method and all the work previously done is encapsulated in well named methods that are called on the appropriate object.
Revisit the discussion quesitons above to see if refactoring the code has changed what you think about the answers you gave, especially the methods you thought each class should have and what methods should be public or private. Add your new thoughts to the discussion file.
Adding a Feature
Update the code to add two new features:
- Create a number of
Blockobjects along the left side of the screen (i.e., x-coordinate of 0 and y-coordinate based on their order of creation), whose color is random and whose height is determined by the number to create so they are spread evenly from top to bottom. The number of blocks to create should be controlled by a constant in theGameclass so it is possible to change only one number in your code and change the complexity of the game (i.e., number of blocks, and their height). Note, to be playable, theMover's height should also probably be proportional to the height of the blocks. - Detect when the player has won the game by reaching the top of the screen (y-coordinate of 0), then pause the game, remove all the blocks and display a congratulatory message. Note, to maintain the goals set in the refactoring section, you will need to add a new method to the
Moverclass to do this part.
Each person should choose a feature to work on in their branch, then merge the code.You may make any changes to the code you think are warranted (functional, organizational, or creating new classes) and feel free to discuss the changes with each other (either for better understanding OpenJFX or to avoid conflicts), and it should be possible to make these changes without creating a merge conflict.
Testing
To prepare for testing your game project, write the framework for testing your code in this lab together.
The actual "test" will simply be to change the color of all the blocks a given number of times in an animation that lasts one second when a numeric key is pressed. To do this, you will pause() the current Timeline (stored as myAnimation), then create a new Timeline object that will last a fixed number of steps instead of an INDEFINITE number. The KeyFrame object for this new Timeline will will call the flash() method instead of the step() method. After the new Timeline's animation ends, you can either restart the original Timeline by pressing the SPACE key or look up how to do it automatically using the method setOnFinished().
To think more about the role of testing in games, answer these discussion questions:
- For this game, list some specific situations that would be valuable to test?
- Pick one or two and describe how you would setup the test to be most effective?
- For the Breakout game, list some specific situations that would be valuable to test?
- Pick one or two and describe how you would setup the test to be most effective?
Submission
At the end of class, use Gitlab's Merge Request to submit your group's answers to the discussion questions above and refactored code to the original organization repository. Make sure the NetIDs of everyone in the group are in the title of your Merge Request and in the markdown file.