Your first step in building Plaisante is a Card class. For the game, we will create one Card object for every card in a standard 52-card poker deck. Each Card object is responsible for:
storing information about its card properties e.g., the queen of hearts
knowing how to visually draw the card it represents
storing and updating its position on the screen
We’ll implement these functionalities one at a time.
Setting up the Card class¶
Let’s begin by setting up the data the Card class holds through the instance variables and how new Card objects are created through the constructor. Open the starter code for the Card class, which shows the constants and the instance variables you will use in your implementation.
[Task] Complete the instance variables and constructor¶
Your instance variables hold all of the data about a card. You’ll declare the following four, following the TODOs in the starter file. Make sure to declare them as private.
suitandrank, bothStringvalues: what each card represents. The suit is one of “hearts”, “diamonds”, “clubs”, or “spades”, and the rank is one of “2” through “10”, “J”, “Q”, “K”, or “A”.value, anint: what the card is worth when we score it. Aces are high in this game, so “A” is 14, “K” is 13, “Q” is 12, and “J” is 11. Number cards are worth their number.isRed, aboolean: whether the rank and suit get printed in red or black. Hearts and diamonds are red, clubs and spades are black.
The other four are already declared for you in the starter file:
xandy, bothfloatvalues: where the card is on the screen right now.targetXandtargetY, bothfloatvalues: where the card is heading. When a card is dealt, it starts on the deck and is animated into the playing area. These values ensure that the animation is smooth.
Next, complete the constructor. The Card() constructor takes a rank, a suit, and a starting position, setting up all of the instance variables as follows:
Store
suitandrankdirectly from the parameters.Set
xandyfrom the parameters too.Set
targetXandtargetYto the same starting position as x and y.Set
valueusing thegetCardValue(String rank)helper method, which is provided for you in the starter file.Set
isRedtotruewhen the suit is hearts or diamonds, andfalseotherwise.
[Task] Getters and setters¶
We declare instance variables private to ensure that nothing outside the Card class can modify or read the data each card represents, unless we purposely allow access by writing getter and setter methods. Getter methods return the value of an instance variable, and setter methods change the value of an instance variable. We’ve provided two getters in the starter file already:
public int getWidth() {
return CARD_WIDTH;
}
public int getHeight() {
return CARD_HEIGHT;
}The starter file also has the rest of the getters and setters written as incomplete methods for you to fill in. For example, here is getX():
public float getX() {
// TODO your code here
return 0f;
}The return 0f; line is a placeholder that lets the file compile, so replace it with your own return statement. Using the two provided getters as your model, complete getX(), getY(), getSuit(), getRank(), and getValue() so that each returns its instance variable. Then, complete the remaining three methods:
setPosition(float x, float y)sets the card’s current position on the screen.setTargetPosition(float x, float y)sets the card’s target position on the screen for animation.toString()returns a short printable label for the card, in the form “[rank] of [suit]” e.g. “A of hearts”.
Click here to check your answer
c.getValue() returns 12. The rank is "Q", and queens are worth 12 in this game. Take a look at the logic in the getCardValue(String rank) helper method to see how this is calculated.
c.toString() returns "Q of spades". The format is the rank, then the word “of”, then the suit.
[Task] Test your Card class¶
As your programs grow in size and complexity, it becomes increasingly important to test your code early and often before you add more features. We refer to this as the “code-a-little, test-a-little” approach. Let’s take some time here to practice this approach by writing some tests for the methods you just implemented. As you did in PA 1, you’ll write your checks inside the static testCode() method provided at the bottom of the starter file, which main() calls for you. Recall the three steps most tests follow:
Set up a small, predictable input.
Run your code on the input by calling the method you’re testing.
Check for correctness by comparing the actual output to the expected output.
Like before, you can do this by writing if statements to compare the actual output to the expected output:
Card card = new Card("Q", "hearts", 0, 0);
if (!card.getRank().equals("Q")) {
System.out.println("Failed test for getRank: expected Q but got " + card.getRank());
}Write code that tests the following (you can use the “Check your understanding” example above as a guide):
Card values. Build a face card (e.g., “Q”) and check that
getValue()gives the correct value. Do the same for a number card like “7”.toString(). Build a card you know and check the label it produces. Make sure to compareStringvalues with.equals(), not==.
You can run your tests by clicking the play button for the Card.java file.
Drawing a Card object¶
Next, you’ll put together the visual elements of the card, which includes: 1) the card’s white background and 2) the rank and suit symbols.
Order of operations for Processing drawing¶
In PA 1, ToneMatrix extended PApplet, which meant the class was the Processing application itself. That’s why you could call fill() and rect() on their own, as they were already part of the application.
The Card class is different. A card isn’t a window, it’s an object that gets drawn into a Processing application window. So the Processing application gets passed to the card as a parameter named app, and every drawing call has to say which application it’s talking to. We provide one such PApplet application as CardMain.java.
// the app variable refers to the Processing application
app.fill(255);To draw the card, we call the render(app) method, which is provided for you in the starter file:
public void render(PApplet app) {
// Ensures that the text is centered on the card
app.textAlign(PApplet.CENTER, PApplet.CENTER);
drawBackground(app);
drawRankAndSuit(app);
}Notice the order of the two calls to drawBackground() and drawRankAndSuit(). The white card background has to go down first, or it would paint over the rank and the suit instead of sitting behind them. Your job is to implement these two methods.
Here are the Processing drawing methods you’ll need. You’ve seen fill(int rgb) and rect() before in PA 1, and the rest are new. Notice that all of these methods are called on the app parameter, which is the Processing application that the card is being drawn in:
app.fill(int rgb): sets the color that the next shape will be filled with to a shade of grey.rgbis a single number from 0 to 255, where 0 is black and 255 is white.app.fill(int r, int g, int b): the same idea but as any color shade rather than only shades of gray. The three numbers are how much red, green, and blue to mix, with 0 being none of that color and 255 being the most saturated version of that color.[1] For example, pure red isapp.fill(255, 0, 0), while black isapp.fill(0, 0, 0)and white isapp.fill(255, 255, 255). To get a pure purple, you would useapp.fill(255, 0, 255), which mixes pure red, no green, and pure blue together. If you’re wondering what color a particular RGB combination will generate, you can test out different combinations at this website.app.stroke(int rgb): sets the color of the outline of the next shape, on the same 0 to 255 greyscale.app.strokeWeight(int weight): sets how thick that outline is, in pixels.app.rect(float x, float y, float width, float height, float radius): draws a rectangle with its top-left corner at(x, y). The last argument rounds off the corners, which is what gives a playing card its shape instead of a rectangle with sharp corners.app.textSize(int size): sets how large the next text will be drawn, in pixels.app.text(String words, float x, float y): draws some text at(x, y), in the currentfill()color and at the currenttextSize().
[Task] Drawing exercise on card coordinates¶
A card’s x and y are its top-left corner. Everything printed on the card gets placed relative to that corner, as an offset to that top-left x-y coordinate. This allows the card to be animated across the screen without the drawing code changing at all, as the offset stays the same while the card’s position changes.
We provide constants that state the card is CARD_WIDTH (100) pixels wide and CARD_HEIGHT (140) pixels tall. The three things we print (the rank twice, and the suit symbol once) land at these offsets from the corner:
| What | Text size | Offset from the card’s (x,y) corner |
|---|---|---|
| rank, upper left | 24 | (x + 20, y + 20) |
| rank, lower right | 24 | (x + CARD_WIDTH - 20, y + CARD_HEIGHT - 20) |
| suit symbol, middle | 48 | (x + (CARD_WIDTH / 2), y + (CARD_HEIGHT / 2)) |
Note that because render() sets textAlign(CENTER, CENTER), when we call app.text(String words, float x, float y) our text will automatically be centered both horizontally and vertically around the coordinate we pass in. This is in contrast to methods like rect() that by default treat the coordinate passed in as the upper left corner of our shape. So when you draw this on paper, mark a dot at the offset and center the character on the dot. Use the figure below as a guide.

The anatomy of a card. The card’s own position is its top-left corner, and everything printed on it is placed as an offset from there. The dots mark where each piece of text is anchored, and because the text is center-aligned, each dot sits at the middle of its character.
Click here to check your work
The corners are at
(50, 50),(150, 50),(50, 190), and(150, 190). The card runs 100 pixels right and 140 pixels down from its position.The rank anchors are at
(70, 70)and(130, 170), which is(50 + 20, 50 + 20)and(50 + 80, 50 + 120).The suit anchor is at
(100, 120), the center of the card.
[Task] Complete drawBackground()¶
Now that we have the card’s visual elements drawn by hand, let’s translate them into code.
The drawBackground() method draws the blank card as a white rounded rectangle with a thin black outline. The starter file lists the steps as TODOs, in the order they need to happen:
private void drawBackground(PApplet app) {
// TODO set the fill to white
// TODO set the stroke to black
// TODO set the stroke weight to 1
// TODO draw the rectangle with the given x, y, width, height, and corner radius
}Each TODO is one line of code. The values you’ll need are:
White is brightness
255, and black is brightness0.The rectangle is drawn at the card’s
xandy, and isCARD_WIDTHwide andCARD_HEIGHTtall.The corner radius is
10.
The first three TODOs are needed because of the Processing drawing order and style settings we discussed above: we set every style this method depends on at the beginning of the method. If we don’t, we risk carrying over unrelated style settings from the most recent drawing.
The only piece of syntax that’s new is the fifth argument to rect():
// a 60 by 40 rectangle at (x,y) positions at (10, 10), with corners rounded by 10 pixels
app.rect(10, 10, 60, 40, 10);[Task] Complete drawRankAndSuit()¶
Next, we’ll implement the text printing on the card. Matching the drawn diagram, the rank is printed in two opposite corners with a text size of 24 and the suit symbol is printed in the middle with a text size of 48. All of the text is in the same color, red for hearts and diamonds and black for clubs and spades based on the card’s isRed boolean. As before, the starter file lists the steps as TODOs:
private void drawRankAndSuit(PApplet app) {
// TODO set the fill color to be red or black depending on isRed
// TODO set the text size to 24
// TODO draw the rank in the upper left corner
// TODO draw the rank in the lower right corner
// TODO set the text size to 48
// TODO draw the suit symbol in the middle
}Visually testing your Card implementation¶
Once you’ve implemented the drawing methods, you can visually test them by running CardMain.java. Open the file and click the play button in VSCodium. If your implementation is working correctly, you should see an Ace of Spades rendered in the window, with a grid overlayed on top:

Animating a Card¶
Next, let’s implement the animation logic for our Card class.
[Task] update() method¶
Recall from PA 1 that a Processing application’s draw() method runs repeatedly, repainting the whole window each time. This repeated redrawing is what makes animation possible: nothing on screen actually moves, we redraw it somewhere slightly different, over and over, fast enough that our eyes read it as motion.
So a card animates by nudging itself a little closer to its target position each call to draw() through the update() method:
public void draw() {
// update the card position to be closer to its target position
card.update();
// then draw the card
card.render(this);
}Specifically, with each call to update(), we want the card to move 10% of the gap between where it currently is (x,y) and where it’s going (targetX, targetY).
Processing provides functionality for this through the PApplet.lerp(start, stop, amount) method[2], which returns a float that is the interpolated value between start and stop based on the amount parameter. Specifically, the three parameters are:
start: the starting positionstop: the target positionamount: the fraction of the way fromstarttostopto move. Anamountof 0 returnsstart, 1 returns backstop, and0.1fgets you 10% of the way there.
Continually moving 10% of the remaining distance to the target position allows a card to “glide” smoothly to its position and settle, instead of moving at a flat speed:
A demo animation of the completed game dealing cards from the deck.
One way to picture this: a card at heading for has a gap of 100 pixels, and if we’re moving 10% of that gap it updates to . Then, on the next update the gap between and is 90 pixels, so . And so on: the next update lands at . The gap shrinks every frame, so the steps shrink along with it. However, there’s a catch: closing 10% of the gap over and over never quite gets you exactly to the target number. The solution is to check once we’re within a pixel of the target, and “snap” the card to the exact target position:

Putting this together, update() has two parts:
Set the card’s position to the lerp of
xtowardtargetX, and the lerp ofytowardtargetY, both by 10%.If the card is now within 1 pixel of its target on both axes, set its
(x,y)position to exactly(targetX, targetY).
To check if two values are close to each other, we can use the Math.abs() method. For example the below code checks if the difference between a and b is less than 1:
if (Math.abs(a - b) < 1) {
System.out.println("a and b are within 1 pixel of each other");
}Below update() in the starter file are two methods that are provided for you, which the game will use in Part 2:
coordinatesContain(float x, float y)returnstrueif the pixel(x, y)falls inside the card’s rectangle,falseotherwise.atTarget()returnstrueif the card’sxandyare equal totargetXandtargetY,falseotherwise.
Demo your implementation with CardMain.java¶
Once you’ve implemented update(), run the CardMain.java file again. In addition to rendering the card as before, clicking in the window will update the card’s target position, causing it to animate smoothly to be centered at that new position:
Also feel free to change the card’s rank and suit in the setup() method to check that different cards draw the way you expect.
When your tests pass and your card animates correctly, you’ve finished Part 1. Congratulations! In Part 2, we’ll build the hand evaluation logic that makes the game work.
Part 1 task checklist¶
The checkpoint autograder will grade your implementation of:
the
Cardconstructoryour getters and setters
your
testCode()test cases
It will also provide feedback (but no points) on your implementation of:
the
drawBackground()methodthe
drawRankAndSuit()methodthe
update()method
This is the RGB color model, the same one behind the greyscale numbers you used in PA 1. A single number like
fill(200)is actually shorthand for an equal mix of all three e.g.fill(200, 200, 200). This equal mix comes out as some form of greyscale.“Lerp” is short for linear interpolation, which is the general idea of picking a value partway between two others. It shows up all over graphics and animation, usually as a way to make something move or fade smoothly.