CPSC 124 | Introduction to Programming | Spring 2024 |
The bundling of subroutines and variables into modules known as objects is at the heart of object-oriented programming. Lab 8 focused on using already-written classes; in this lab you'll gain experience with writing classes of your own.
Labs and projects are for practice and learning. While it can be very productive to work on problems with your peers, it is also easy to underestimate how much you yourself understand and can do in such situations — so often something looks easy when someone else does it! With this in mind, you should always make the first attempt on a problem or programming task yourself using only the resources provided for the course (textbook, slides and examples posted on the schedule page, other resources linked on the course webpages). After that point, you are encouraged to come to office hours and/or go to Teaching Fellows. You may not collaboratively write solutions or code, and you may not copy solutions or code written by others, even if you contributed ideas.
You can discuss specific exercises with other students in general terms — such as how you might get started on that problem, or how or when to use various programming constructs, or strategies for debugging — but how to use a particular programming construct to solve a specific problem or debugging a particular program should only be discussed in office hours or with the Teaching Fellows.
API documentation for the provided classes can be found below:
As in lab 8, we're now working with programs that involve more than one file. Each .java file needs to be compiled in order for the program to run. There are several options for compiling multiple files:
separate javac MyClass.java commands for each file
listing multiple filenames on the same commandline: javac MyClass1.java MyClass2.java ...
utilizing wildcards: e.g.
Java will attempt to compile dependencies as needed — if you compile your main program, for example, and it involves other classes which have been updated since they were last compiled, those classes will be recompiled automatically when you compile the main program. Sometimes, though, Java can't figure out all the dependencies and you can get confusing results due to a mix of old and new versions, so it is recommended that you instead use one of the options above to explicitly compile everything needed.
For all exercises:
Unless otherwise indicated in the problem, if specific output is shown to indicate what your program should do, be sure your program's output matches what is shown exactly.
Employ good programming style, including choosing descriptive variable names and autoformatting your code. Use blank lines and comments to group and label statements that together perform a distinct task.
Write Javadoc-style comments including Include your name and a description of the class for each class, whether it is for constructing objects or containing a main program
Write Javadoc-style comments for each method, subroutine, and function. Also identify and check preconditions, throwing an IllegalArgumentException if a precondition is violated.
Continue to write pseudocode (and to practice stepwise refinement in the development of your pseudocode) before writing Java. Also continue to practice incremental development — test as you go (using placeholder variables and subroutines as needed) rather than writing the whole program before you try to compile or run it for the first time.
You have been provided with a file War.java (one of the files you copied) which implements the card game War. (This is similar to the BasicWar example from class, but includes all of the rules from the game instead of a simplified version.) To try the program out, compile War.java and run it.
Imagine that we want to modify the program so that each player has a name (other than "player 1" and "player 2"). In addition, we want to keep track of how many times each player wins a war during the game. You could do this by introducing four new variables: name1 and name2 for the names and numwins1 and numwins2 for the number of wins. But this gets to be a lot of variables, and there's a lot of duplication of effort because the same things are done for each player.
A better solution is to create a new type Player to bundle together everything that belongs to a player — name, hand, and number of wars won. The main program will then only need two variables to keep track of everything to do with the players: player1 and player2.
Write a class Player with the elements described below.
Player should have the following instance variables:
It should have a constructor which takes the player's name as a parameter and initializes the name instance variable accordingly. The constructor should also create a new empty hand for the player's hand, and should initialize the number of wars won to 0.
It should have the following methods:
a getter getName which returns the player's name
a getter getNumWarsWon which returns the number of wars won
a getter getNumCards which returns the number of cards in the player's hand
a getter hasCards which returns true if the player has cards in her hand and false otherwise
a dealCards method which takes a deck and a number of cards as parameters, and deals the specified number of cards from the deck into the player's hand
a playCard method which gets the first card from the player's hand, removes the card from the hand, and returns the card
a warWon method which takes the cards won in a war (a Hand) as a parameter, moves those cards to the player's hand in a random order, and increments the number of times the player has won a war
To see how to move cards from one hand to another in a random order, locate the section in War.java with the comment "player 1 gets the cards in random order". That loop handles moving the cards from played to hand1 in a random order — copy that code to warWon and modify the variable names so that it moves cards from the Hand parameter to the player's hand instead of from played to hand1.
Modify the provided main program in War.java to instead use your new Player class:
Replace the creation of the two Hand objects with the creation of two Player objects. (Choose any names you want for the players. It is OK to hardcode these names — you don't need to prompt the user to enter names.) Also rename the variables hand1 and hand2 to something more appropriate for variables storing players instead of hands.
Update the code to use methods of Player where appropriate. At the very least, you'll need to change anywhere hand1 or hand2 was used so that the proper method is called on one of the Player objects. In some cases, a whole chunk of code may need to be replaced e.g. the two loops that deal out 26 cards to each player can be replaced by two calls to Player's dealCards method.
Change the places "player 1" or "player 2" is printed to instead print the player's name (retrieved from the appropriate Player object).
At the end of the game, also print the number of wars each player won.
Pong is one of the classic computer games. Dating back to 1972, it was one of the earliest arcade games and the first with widespread success. Pong simulates a game of table tennis — each player has a paddle and they try to hit the ball back and forth.
In the remaining exercises, you will implement a version of Pong.
You have been provided with two parts of this program: a main program in Pong.java and a class Bouncer.java which contains some functions for determining when the ball hits a paddle or wall.
Note: This program makes use of JavaFX, so remember that you need to compile with jfxc instead of javac and run with jfx instead of java.
To see how your program should work:
Compile (jfxc) the provided files Bouncer.java and Pong.java.
Run (jfx) Pong. Use the 'a' and 'z' keys to move the red paddle and the 'k' and 'm' keys to move the blue paddle. When a player misses the ball, the ball's position is reset to a random spot in the middle of the game board. The game continues indefinitely, so close the window when you get bored.
There is occasionally some odd bouncing behavior with the ball and paddle. Getting bouncing exactly right is tricky, so as long as your program generally works (i.e. the oddness is no more frequent than the demo), you're OK.
You should also notice that some messages are printed out to your terminal window when you run the program:
*** using demo PongTable *** *** using demo PongBall *** *** using demo PongPaddle *** *** using demo PongPaddle ***
You'll be writing these classes in the exercises, but .class files were provided so that you can see how the program should work and so you can test your program as you go. The messages are printed to remind you that you are using provided versions of the classes instead of your own; they will go away as you compile your own versions.
Note: it is very important that you name your classes and methods as directed, and that you have the right header for each method. (i.e. the right types of parameters in the right order, and the right return type) If you don't, the provided code will not work! (You'll get compiler errors and possibly program crashes with NoSuchMethodError exceptions. If you get error messages you can't figure out, ask!)
Another note: You should include the lines
import javafx.scene.canvas.*; import javafx.scene.paint.*; import javafx.scene.input.*;
at the very beginning (before the public class part) of each class that you write. If you forget them, you will get "symbol not found" compiler errors. (VSCode may also "helpfully" add a different import, java.awt.Color, which will cause more compiler problems. If you notice that import has appeared in any of your files, delete it.)
The PongPaddle class describes the paddles used in the Pong game.
Write a new class PongPaddle as described below.
PongPaddle should have instance variables for the following things:
All of these values should be doubles except for the color, which should be of type Color.
There should be a constructor which takes the paddle's position (x and y coordinates), dimensions (width and height), and color (type Color) as parameters. The instance variables should be initialized accordingly.
PongPaddle should also have the following methods:
Getters getTop, getBottom, getLeft, and getRight which return the y coordinate of the top of the paddle, the y coordinate of the bottom of the paddle, the x coordinate of the left of the paddle, and the x coordinate of the right of the paddle, respectively. Note that you will need to calculate these values from the instance variables. Also remember that "top" is higher on the screen than "bottom". (What does that mean about the y coordinates?)
moveUp and moveDown, which take an amount (in pixels) by which the paddle should be moved as a parameter, and move the paddle up or down on the screen by that amount. Note that these methods do not redraw the paddle — they only change the variables storing the paddle's position.
draw, which takes a GraphicsContext as a parameter and draws the paddle as a filled rectangle in its color. The paddle should be drawn in its current position. (Note that the instance variables store the position of the center of the paddle, so you will have to adjust accordingly when you draw the shape.)
When you are done, compile PongPaddle and then run the main program (Pong). If all is well, the program should work just like the demo version (check that the paddles are drawn in the right spot and that pressing the appropriate keys moves them correctly) and the "*** using demo PongPaddle ***" messages should have gone away.
The PongBall class describes the ball used in the Pong game.
Write a new class PongBall as described below.
PongBall should have instance variables for the following things:
All of these values should be doubles except for the color, which should be of type Color.
There should be a constructor which takes the ball's position (x and y coordinates), radius, velocity (horizontal and vertical), and color (type Color) as parameters and initializes the instance variables accordingly.
PongBall should also have the following methods:
Getters getX, getY, getRadius, getXVelocity, and getYVelocity which return the ball's x coordinate, y coordinate, radius, horizontal velocity, and vertical velocity, respectively.
Setter setPosition which takes new x and y coordinates as parameters, and which sets the ball's position accordingly.
move, which updates the ball's current position. The two velocities describe how much the ball moves in each direction in one time unit. Update the ball's position by adding the x velocity to the x coordinate of the position and the y velocity to the y coordinate of the position. Note that negative velocities aren't a problem (they just mean the ball is travelling in the opposite direction) and don't need to be handled in a special way.
bounceX and bounceY, which update the ball's velocity in response to a bounce off of a vertical or horizontal wall, respectively. This just means that the appropriate velocity is reversed i.e. multiply the horizontal velocity by -1 in bounceX and the vertical velocity by -1 in bounceY.
draw, which takes a GraphicsContext as a parameter and draws the ball as a filled circle in its color. (Note that the ball's instance variables store the position of the ball's center, so you will have to adjust accordingly when you draw the shape.)
When you are done, compile PongBall and then run the main program (Pong). If all is well, the program should work just like the demo version (check that the ball is drawn in the right spot and that it moves and bounces correctly) and the "*** using demo PongBall ***" message should have gone away.
The PongTable class coordinates all of the elements of the game.
Write a new class PongTable as described below.
PongTable should have instance variables for the following things:
Note that the ball and paddles will be objects (you just wrote those classes...), so choose the types accordingly. The width and height of the playing area should be doubles.
There should be a constructor which takes the width and the height of the playing area as parameters, and initializes those instance variables accordingly. The other instance variables will need to be initialized with new objects of the appropriate types: pass the right things to the constructors for those classes so that the ball has a radius of 4 and starts in the middle of the playing area (choose your own smallish values for the velocity), and the paddles have dimensions 10x50 and start in the middle of their respective sides of the playing area. You can choose your own colors, as long as the paddles and ball are all different colors.
PongTable should also have the following methods:
A method draw which takes a GraphicsContext as a parameter and draws the ball and both paddles. This is just a matter of calling the appropriate methods on those objects.
A method step which does one step of the animation that makes the game go. "One step of the animation" means moving the ball a small amount and then dealing with the repercussions of that. In particular, your method should (in order):
(Note that step does not update the display.)
For moving the ball, you just need to call the appropriate method on the appropriate object.
To handle bouncing, the Bouncer class provides some subroutines to determine if the ball has hit a paddle or wall. (Look at the API documentation to figure out how to use the class.) Note that these subroutines are static — Bouncer is a class whose purpose is to collect a bunch of subroutines rather than to create objects. This means that you do not need to create a Bouncer object in order to call the subroutines, but you will need to write Bouncer. before the subroutine name. Bouncer also provides constants Bouncer.NONE, Bouncer.LEFT, Bouncer.RIGHT, Bouncer.TOP, Bouncer.BOTTOM indicating which side of the paddle or wall has been hit.
Use the getPaddleSideHit and getWallHit functions in Bouncer to determine what, if anything, has been hit. If the ball has hit any side of a paddle or the top or bottom wall, you will need to use the appropriate PongBall method to tell the ball to bounce in the correct direction. If the ball has hit the left or right walls, you will need to use the appropriate PongBall method to reset the ball's position — the ball's x coordinate should be set to the middle of the playing area and the y coordinate to a random value within the playing area.
Note: you should not call getPaddleSideHit more than once per paddle or getWallHit more than once. Instead, store the return value in a variable and use that variable as often as you like. (This is because of some details about how these functions are implemented in order to somewhat properly deal with bouncing.)
A method handleKey which takes a key (type KeyCode) as a parameter, and carries out the action specified by that key. There are four cases here: KeyCode.A (the 'a' key) should move player 1's paddle up, KeyCode.Z (the 'z' key) moves player 1's paddle down, KeyCode.K (the 'k' key) moves player 2's paddle up, KeyCode.M (the 'm' key) moves player 2's paddle down. A paddle should move by a small amount each time it moves, and you should be careful not to move a paddle above the top or below the bottom of the playing area. (Start with 4 pixels, and adjust bigger or smaller as needed to make the game challenging but playable.) Use the appropriate PongPaddle methods to actually manipulate the paddle. The overall structure of this will be similar to handling the arrow keys in Minesweeper.
When you are done, compile PongTable and then run the main program (Pong). If all is well, the program should work just like the demo version (check that everything is drawn in the right spot, the ball moves and bounces correctly, and the paddles move correctly) and the "*** using demo PongTable ***" message should have gone away.
Don't forget to hand in your work when you are done!
As in lab 2 (and all labs), grading will be based on both functionality and style. Style means following the good programming style conventions outlined in lab 2, as well as producing reasonably compact and elegant solutions. "Reasonably compact and elegant" means that your program is not significantly longer or more complex than it needs to be. In particular, you must use loops when loops are appropriate — achieving repetition by copying-and-pasting a bunch of code will not earn credit.