6: Inheritance & Algorithms
We introduce our first complex algorithms and start to pull back the scaffolding a bit. Students will get their first real experience designing aggregate objects with encapsulated properties.
Learning Targets
I can secure my class variables with encapsulation.
I can create a constructor to initialize an object.
I can override a parent method.
I can build unit tests for all methods in an aggregate object.
Inheritance
Keep it DRY and OOP-y
DRY stands for Don't Repeat Yourself. So far, that's meant that if you need to repeat a bit of code, you make a loop instead of sloppily copying and pasting the same commands a few times. Now we're seeing that OOP, or Object-Oriented Programming, has was to build classes off of one another. That means we can avoid repeating (aka DRY code) by using inheritance.
Abstract Classes
Polymorphism Practice
Polymorphism is my favorite CS term because it sounds so much fancier than it really is (sort of a theme here). In class we'll build an abstract class called Shape with some encaspulated instance variables like length
and width
, abstract methods like .area()
. Then we'll inherit from those methods when we create types of shapes like Circle, Rectangle, Triangle and so on. Let's look closer at why this is an example of inheritance and polymorphism.
Why this is smart, object-oriented programming
Creating a parent class called Shape
that all your shapes will inherit means that the common properties and functions can live in one place. If new features or changes needed to happen across all the elements in your app, you would have a logical place to check.
But why do we declare abstract methods?
Let's take a look at a shortened Shape
class:
class Shape {
// a single example encaspulated instance variable
private float length;
// accessor method
public float length(){
// using this. is not required but helpful when writing
return this.length;
// mutator method
public void length(float length){
// now using this. is required to tell the local and instance var apart
this.length = length;
// example abstract method. Each shape will need to make or "implement"
pubilc abstract float area();
}
All the encapsulation shown between lines 3 - 14 should be familiar by now. Seek additional practice including your teacher's office hours if not. The cool new thing here is line 17. Because the parent is declaring this method, all its children (i.e class Circle extends Shape {
)will have to build their own area method. The Circle
class will return 3.14 * (length * length);
for its area()
implementation.
So how exactly is this business "polymorphic?"
Because all of my app's shapes will have a common parent, I can create a collection of Shape
like:
ArrayList<Shape> myShapes = new ArrayList<>();
That allows me to to loop through the whole collection and report out their areas:
for (Shape x : myShapes) {
System.out.println("This shape's area is: " + x.area() + " cm2");
}
Multiple Inheritance?
What if I wanted to build a huge space game with thousands of types of ships. I'd make rules for cargo ships and warships. There may be times when I want multiple inheritances to be applied. Well there are some limits here in Java. You can have multiple vertical levels, like Ship --> Warship --> Battleship but a single ship can't inherit from two places simultaneously. Instead, there are Interfaces.
Elevens
Please download the guide to this old project from CollegeBoard.
Elevens 1
Let's create a simple Card
object and test its encapsulated properties.
/**
* This is a class that tests the Card class.
*/
public class CardTester {
/**
* The main method in this class checks the Card operations for consistency.
* @param args is not used.
*/
public static void main(String[] args) {
/* *** TO BE IMPLEMENTED IN ACTIVITY 1 *** */
}
}
/**
* Card.java
*
* <code>Card</code> represents a playing card.
*/
public class Card {
/**
* String value that holds the suit of the card
*/
private String suit;
/**
* String value that holds the rank of the card
*/
private String rank;
/**
* int value that holds the point value.
*/
private int pointValue;
/**
* Creates a new <code>Card</code> instance.
*
* @param cardRank a <code>String</code> value
* containing the rank of the card
* @param cardSuit a <code>String</code> value
* containing the suit of the card
* @param cardPointValue an <code>int</code> value
* containing the point value of the card
*/
public Card(String cardRank, String cardSuit, int cardPointValue) {
/* *** TO BE IMPLEMENTED IN ACTIVITY 1 *** */
}
/**
* Accesses this <code>Card's</code> suit.
* @return this <code>Card's</code> suit.
*/
public String suit() {
/* *** TO BE IMPLEMENTED IN ACTIVITY 1 *** */
}
/**
* Accesses this <code>Card's</code> rank.
* @return this <code>Card's</code> rank.
*/
public String rank() {
/* *** TO BE IMPLEMENTED IN ACTIVITY 1 *** */
}
/**
* Accesses this <code>Card's</code> point value.
* @return this <code>Card's</code> point value.
*/
public int pointValue() {
/* *** TO BE IMPLEMENTED IN ACTIVITY 1 *** */
}
/** Compare this card with the argument.
* @param otherCard the other card to compare to this
* @return true if the rank, suit, and point value of this card
* are equal to those of the argument;
* false otherwise.
*/
public boolean matches(Card otherCard) {
/* *** TO BE IMPLEMENTED IN ACTIVITY 1 *** */
}
/**
* Converts the rank, suit, and point value into a string in the format
* "[Rank] of [Suit] (point value = [PointValue])".
* This provides a useful way of printing the contents
* of a <code>Deck</code> in an easily readable format or performing
* other similar functions.
*
* @return a <code>String</code> containing the rank, suit,
* and point value of the card.
*/
@Override
public String toString() {
/* *** TO BE IMPLEMENTED IN ACTIVITY 1 *** */
}
}
import java.util.List;
import java.util.ArrayList;
/**
* The ThirteensBoard class represents the board in a game of Thirteens.
*/
public class ThirteensBoard extends Board {
/**
* The size (number of cards) on the board.
*/
private static final int BOARD_SIZE = 10;
/**
* The ranks of the cards for this game to be sent to the deck.
*/
private static final String[] RANKS =
{"ace", "2", "3", "4", "5", "6", "7", "8", "9", "10", "jack", "queen", "king"};
/**
* The suits of the cards for this game to be sent to the deck.
*/
private static final String[] SUITS =
{"spades", "hearts", "diamonds", "clubs"};
/**
* The values of the cards for this game to be sent to the deck.
*/
private static final int[] POINT_VALUES =
{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0};
/**
* Flag used to control debugging print statements.
*/
private static final boolean I_AM_DEBUGGING = false;
/**
* Creates a new <code>ThirteensBoard</code> instance.
*/
public ThirteensBoard() {
super(BOARD_SIZE, RANKS, SUITS, POINT_VALUES);
}
/**
* Determines if the selected cards form a valid group for removal.
* In Thirteens, the legal groups are (1) a pair of non-face cards
* whose values add to 13, and (2) a king.
* @param selectedCards the list of the indices of the selected cards.
* @return true if the selected cards form a valid group for removal;
* false otherwise.
*/
@Override
public boolean isLegal(List<Integer> selectedCards) {
if (selectedCards.size() == 1) {
return findKing(selectedCards).size() > 0;
} else if (selectedCards.size() == 2) {
return findPairSum13(selectedCards).size() > 0;
} else {
return false;
}
}
/**
* Determine if there are any legal plays left on the board.
* In Thirteens, there is a legal play if the board contains
* (1) a pair of non-face cards whose values add to 13, or (2) a king.
* @return true if there is a legal play left on the board;
* false otherwise.
*/
@Override
public boolean anotherPlayIsPossible() {
List<Integer> cIndexes = cardIndexes();
return findPairSum13(cIndexes).size() > 0
|| findKing(cIndexes).size() > 0;
}
/**
* Look for an 13-pair in the selected cards.
* @param selectedCards selects a subset of this board. It is list
* of indexes into this board that are searched
* to find an 13-pair.
* @return a list of the indexes of an 13-pair, if an 13-pair was found;
* an empty list, if an 13-pair was not found.
*/
private List<Integer> findPairSum13(List<Integer> selectedCards) {
List<Integer> foundIndexes = new ArrayList<Integer>();
for (int sk1 = 0; sk1 < selectedCards.size(); sk1++) {
int k1 = selectedCards.get(sk1).intValue();
for (int sk2 = sk1 + 1; sk2 < selectedCards.size(); sk2++) {
int k2 = selectedCards.get(sk2).intValue();
if (cardAt(k1).pointValue() + cardAt(k2).pointValue() == 13) {
foundIndexes.add(new Integer(k1));
foundIndexes.add(new Integer(k2));
return foundIndexes;
}
}
}
return foundIndexes;
}
/**
* Look for a king in the selected cards.
* @param selectedCards selects a subset of this board. It is list
* of indexes into this board that are searched
* to find a king.
* @return a list of the index of a king, if a king was found;
* an empty list, if a king was not found.
*/
private List<Integer> findKing(List<Integer> selectedCards) {
List<Integer> foundIndexes = new ArrayList<Integer>();
for (Integer kObj : selectedCards) {
int k = kObj.intValue();
if (cardAt(k).rank().equals("king")) {
foundIndexes.add(new Integer(k));
return foundIndexes;
}
}
return foundIndexes;
}
/**
* Looks for a legal play on the board. If one is found, it plays it.
* @return true if a legal play was found (and made); false othewise.
*/
public boolean playIfPossible() {
return playPairSum13IfPossible() || playKingIfPossible();
}
/**
* Looks for a pair of non-face cards whose values sum to 13.
* If found, replace them with the next two cards in the deck.
* The simulation of this game uses this method.
* @return true if an 13-pair play was found (and made); false othewise.
*/
private boolean playPairSum13IfPossible() {
List<Integer> foundIndexes = cardIndexes();
List<Integer> cardsToReplace = findPairSum13(foundIndexes);
if (cardsToReplace.size() > 0) {
replaceSelectedCards(cardsToReplace);
if (I_AM_DEBUGGING) {
System.out.println("13-Pair removed.\n");
}
return true;
} else {
return false;
}
}
/**
* Looks for a King.
* If found, replace it with the next card in the deck.
* The simulation of this game uses this method.
* @return true if a king play was found (and made); false othewise.
*/
private boolean playKingIfPossible() {
List<Integer> foundIndexes = cardIndexes();
List<Integer> cardsToReplace = findKing(foundIndexes);
if (cardsToReplace.size() > 0) {
replaceSelectedCards(cardsToReplace);
if (I_AM_DEBUGGING) {
System.out.println("King removed.\n");
}
return true;
} else {
return false;
}
}
}
import java.util.List;
import java.util.ArrayList;
/**
* The ElevensBoard class represents the board in a game of Elevens.
*/
public class ElevensBoard extends Board {
/**
* The size (number of cards) on the board.
*/
private static final int BOARD_SIZE = 9;
/**
* The ranks of the cards for this game to be sent to the deck.
*/
private static final String[] RANKS =
{"ace", "2", "3", "4", "5", "6", "7", "8", "9", "10", "jack", "queen", "king"};
/**
* The suits of the cards for this game to be sent to the deck.
*/
private static final String[] SUITS =
{"spades", "hearts", "diamonds", "clubs"};
/**
* The values of the cards for this game to be sent to the deck.
*/
private static final int[] POINT_VALUES =
{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 0, 0};
/**
* Flag used to control debugging print statements.
*/
private static final boolean I_AM_DEBUGGING = true;
/**
* Creates a new <code>ElevensBoard</code> instance.
*/
public ElevensBoard() {
super(BOARD_SIZE, RANKS, SUITS, POINT_VALUES);
}
/**
* Determines if the selected cards form a valid group for removal.
* In Elevens, the legal groups are (1) a pair of non-face cards
* whose values add to 11, and (2) a group of three cards consisting of
* a jack, a queen, and a king in some order.
* @param selectedCards the list of the indices of the selected cards.
* @return true if the selected cards form a valid group for removal;
* false otherwise.
*/
@Override
public boolean isLegal(List<Integer> selectedCards) {
if (selectedCards.size() == 2) {
return findPairSum11(selectedCards).size() > 0;
} else if (selectedCards.size() == 3) {
return findJQK(selectedCards).size() > 0;
} else {
return false;
}
}
/**
* Determine if there are any legal plays left on the board.
* In Elevens, there is a legal play if the board contains
* (1) a pair of non-face cards whose values add to 11, or (2) a group
* of three cards consisting of a jack, a queen, and a king in some order.
* @return true if there is a legal play left on the board;
* false otherwise.
*/
@Override
public boolean anotherPlayIsPossible() {
List<Integer> cIndexes = cardIndexes();
return findPairSum11(cIndexes).size() > 0
|| findJQK(cIndexes).size() > 0;
}
/**
* Look for an 11-pair in the selected cards.
* @param selectedCards selects a subset of this board. It is list
* of indexes into this board that are searched
* to find an 11-pair.
* @return a list of the indexes of an 11-pair, if an 11-pair was found;
* an empty list, if an 11-pair was not found.
*/
private List<Integer> findPairSum11(List<Integer> selectedCards) {
List<Integer> foundIndexes = new ArrayList<Integer>();
for (int sk1 = 0; sk1 < selectedCards.size(); sk1++) {
int k1 = selectedCards.get(sk1).intValue();
for (int sk2 = sk1 + 1; sk2 < selectedCards.size(); sk2++) {
int k2 = selectedCards.get(sk2).intValue();
if (cardAt(k1).pointValue() + cardAt(k2).pointValue() == 11) {
foundIndexes.add(new Integer(k1));
foundIndexes.add(new Integer(k2));
return foundIndexes;
}
}
}
return foundIndexes;
}
/**
* Look for a JQK in the selected cards.
* @param selectedCards selects a subset of this board. It is list
* of indexes into this board that are searched
* to find a JQK group.
* @return a list of the indexes of a JQK, if a JQK was found;
* an empty list, if a JQK was not found.
*/
private List<Integer> findJQK(List<Integer> selectedCards) {
List<Integer> foundIndexes = new ArrayList<Integer>();
int jackIndex = -1;
int queenIndex = -1;
int kingIndex = -1;
for (Integer kObj : selectedCards) {
int k = kObj.intValue();
if (cardAt(k).rank().equals("jack")) {
jackIndex = k;
} else if (cardAt(k).rank().equals("queen")) {
queenIndex = k;
} else if (cardAt(k).rank().equals("king")) {
kingIndex = k;
}
}
if (jackIndex != -1 && queenIndex != -1 && kingIndex != -1) {
foundIndexes.add(new Integer(jackIndex));
foundIndexes.add(new Integer(queenIndex));
foundIndexes.add(new Integer(kingIndex));
}
return foundIndexes;
}
/**
* Looks for a legal play on the board. If one is found, it plays it.
* @return true if a legal play was found (and made); false othewise.
*/
public boolean playIfPossible() {
return playPairSum11IfPossible() || playJQKIfPossible();
}
/**
* Looks for a pair of non-face cards whose values sum to 11.
* If found, replace them with the next two cards in the deck.
* The simulation of this game uses this method.
* @return true if an 11-pair play was found (and made); false othewise.
*/
private boolean playPairSum11IfPossible() {
List<Integer> foundIndexes = cardIndexes();
List<Integer> cardsToReplace = findPairSum11(foundIndexes);
if (cardsToReplace.size() > 0) {
replaceSelectedCards(cardsToReplace);
if (I_AM_DEBUGGING) {
System.out.println("11-Pair removed.\n");
}
return true;
} else {
return false;
}
}
/**
* Looks for a group of three face cards JQK.
* If found, replace them with the next three cards in the deck.
* The simulation of this game uses this method.
* @return true if a JQK play was found (and made); false othewise.
*/
private boolean playJQKIfPossible() {
List<Integer> foundIndexes = cardIndexes();
List<Integer> cardsToReplace = findJQK(foundIndexes);
if (cardsToReplace.size() > 0) {
replaceSelectedCards(cardsToReplace);
if (I_AM_DEBUGGING) {
System.out.println("JQK-Triplet removed.\n");
}
return true;
} else {
return false;
}
}
}
import java.awt.Point;
import java.awt.Graphics;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Color;
import java.awt.Toolkit;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.ImageIcon;
import java.net.URL;
import java.util.List;
import java.util.ArrayList;
/**
* This class provides a GUI for solitaire games related to Elevens.
*/
public class CardGameGUI extends JFrame implements ActionListener {
/** Height of the game frame. */
private static final int DEFAULT_HEIGHT = 302;
/** Width of the game frame. */
private static final int DEFAULT_WIDTH = 800;
/** Width of a card. */
private static final int CARD_WIDTH = 73;
/** Height of a card. */
private static final int CARD_HEIGHT = 97;
/** Row (y coord) of the upper left corner of the first card. */
private static final int LAYOUT_TOP = 30;
/** Column (x coord) of the upper left corner of the first card. */
private static final int LAYOUT_LEFT = 30;
/** Distance between the upper left x coords of
* two horizonally adjacent cards. */
private static final int LAYOUT_WIDTH_INC = 100;
/** Distance between the upper left y coords of
* two vertically adjacent cards. */
private static final int LAYOUT_HEIGHT_INC = 125;
/** y coord of the "Replace" button. */
private static final int BUTTON_TOP = 30;
/** x coord of the "Replace" button. */
private static final int BUTTON_LEFT = 570;
/** Distance between the tops of the "Replace" and "Restart" buttons. */
private static final int BUTTON_HEIGHT_INC = 50;
/** y coord of the "n undealt cards remain" label. */
private static final int LABEL_TOP = 160;
/** x coord of the "n undealt cards remain" label. */
private static final int LABEL_LEFT = 540;
/** Distance between the tops of the "n undealt cards" and
* the "You lose/win" labels. */
private static final int LABEL_HEIGHT_INC = 35;
/** The board (Board subclass). */
private Board board;
/** The main panel containing the game components. */
private JPanel panel;
/** The Replace button. */
private JButton replaceButton;
/** The Restart button. */
private JButton restartButton;
/** The "number of undealt cards remain" message. */
private JLabel statusMsg;
/** The "you've won n out of m games" message. */
private JLabel totalsMsg;
/** The card displays. */
private JLabel[] displayCards;
/** The win message. */
private JLabel winMsg;
/** The loss message. */
private JLabel lossMsg;
/** The coordinates of the card displays. */
private Point[] cardCoords;
/** kth element is true iff the user has selected card #k. */
private boolean[] selections;
/** The number of games won. */
private int totalWins;
/** The number of games played. */
private int totalGames;
/**
* Initialize the GUI.
* @param gameBoard is a <code>Board</code> subclass.
*/
public CardGameGUI(Board gameBoard) {
board = gameBoard;
totalWins = 0;
totalGames = 0;
// Initialize cardCoords using 5 cards per row
cardCoords = new Point[board.size()];
int x = LAYOUT_LEFT;
int y = LAYOUT_TOP;
for (int i = 0; i < cardCoords.length; i++) {
cardCoords[i] = new Point(x, y);
if (i % 5 == 4) {
x = LAYOUT_LEFT;
y += LAYOUT_HEIGHT_INC;
} else {
x += LAYOUT_WIDTH_INC;
}
}
selections = new boolean[board.size()];
initDisplay();
setDefaultCloseOperation(EXIT_ON_CLOSE);
repaint();
}
/**
* Run the game.
*/
public void displayGame() {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
setVisible(true);
}
});
}
/**
* Draw the display (cards and messages).
*/
public void repaint() {
for (int k = 0; k < board.size(); k++) {
String cardImageFileName =
imageFileName(board.cardAt(k), selections[k]);
URL imageURL = getClass().getResource(cardImageFileName);
if (imageURL != null) {
ImageIcon icon = new ImageIcon(imageURL);
displayCards[k].setIcon(icon);
displayCards[k].setVisible(true);
} else {
throw new RuntimeException(
"Card image not found: \"" + cardImageFileName + "\"");
}
}
statusMsg.setText(board.deckSize()
+ " undealt cards remain.");
statusMsg.setVisible(true);
totalsMsg.setText("You've won " + totalWins
+ " out of " + totalGames + " games.");
totalsMsg.setVisible(true);
pack();
panel.repaint();
}
/**
* Initialize the display.
*/
private void initDisplay() {
panel = new JPanel() {
public void paintComponent(Graphics g) {
super.paintComponent(g);
}
};
// If board object's class name follows the standard format
// of ...Board or ...board, use the prefix for the JFrame title
String className = board.getClass().getSimpleName();
int classNameLen = className.length();
int boardLen = "Board".length();
String boardStr = className.substring(classNameLen - boardLen);
if (boardStr.equals("Board") || boardStr.equals("board")) {
int titleLength = classNameLen - boardLen;
setTitle(className.substring(0, titleLength));
}
// Calculate number of rows of cards (5 cards per row)
// and adjust JFrame height if necessary
int numCardRows = (board.size() + 4) / 5;
int height = DEFAULT_HEIGHT;
if (numCardRows > 2) {
height += (numCardRows - 2) * LAYOUT_HEIGHT_INC;
}
this.setSize(new Dimension(DEFAULT_WIDTH, height));
panel.setLayout(null);
panel.setPreferredSize(
new Dimension(DEFAULT_WIDTH - 20, height - 20));
displayCards = new JLabel[board.size()];
for (int k = 0; k < board.size(); k++) {
displayCards[k] = new JLabel();
panel.add(displayCards[k]);
displayCards[k].setBounds(cardCoords[k].x, cardCoords[k].y,
CARD_WIDTH, CARD_HEIGHT);
displayCards[k].addMouseListener(new MyMouseListener());
selections[k] = false;
}
replaceButton = new JButton();
replaceButton.setText("Replace");
panel.add(replaceButton);
replaceButton.setBounds(BUTTON_LEFT, BUTTON_TOP, 100, 30);
replaceButton.addActionListener(this);
restartButton = new JButton();
restartButton.setText("Restart");
panel.add(restartButton);
restartButton.setBounds(BUTTON_LEFT, BUTTON_TOP + BUTTON_HEIGHT_INC,
100, 30);
restartButton.addActionListener(this);
statusMsg = new JLabel(
board.deckSize() + " undealt cards remain.");
panel.add(statusMsg);
statusMsg.setBounds(LABEL_LEFT, LABEL_TOP, 250, 30);
winMsg = new JLabel();
winMsg.setBounds(LABEL_LEFT, LABEL_TOP + LABEL_HEIGHT_INC, 200, 30);
winMsg.setFont(new Font("SansSerif", Font.BOLD, 25));
winMsg.setForeground(Color.GREEN);
winMsg.setText("You win!");
panel.add(winMsg);
winMsg.setVisible(false);
lossMsg = new JLabel();
lossMsg.setBounds(LABEL_LEFT, LABEL_TOP + LABEL_HEIGHT_INC, 200, 30);
lossMsg.setFont(new Font("SanSerif", Font.BOLD, 25));
lossMsg.setForeground(Color.RED);
lossMsg.setText("Sorry, you lose.");
panel.add(lossMsg);
lossMsg.setVisible(false);
totalsMsg = new JLabel("You've won " + totalWins
+ " out of " + totalGames + " games.");
totalsMsg.setBounds(LABEL_LEFT, LABEL_TOP + 2 * LABEL_HEIGHT_INC,
250, 30);
panel.add(totalsMsg);
if (!board.anotherPlayIsPossible()) {
signalLoss();
}
pack();
getContentPane().add(panel);
getRootPane().setDefaultButton(replaceButton);
panel.setVisible(true);
}
/**
* Deal with the user clicking on something other than a button or a card.
*/
private void signalError() {
Toolkit t = panel.getToolkit();
t.beep();
}
/**
* Returns the image that corresponds to the input card.
* Image names have the format "[Rank][Suit].GIF" or "[Rank][Suit]S.GIF",
* for example "aceclubs.GIF" or "8heartsS.GIF". The "S" indicates that
* the card is selected.
*
* @param c Card to get the image for
* @param isSelected flag that indicates if the card is selected
* @return String representation of the image
*/
private String imageFileName(Card c, boolean isSelected) {
String str = "cards/";
if (c == null) {
return "cards/back1.GIF";
}
str += c.rank() + c.suit();
if (isSelected) {
str += "S";
}
str += ".GIF";
return str;
}
/**
* Respond to a button click (on either the "Replace" button
* or the "Restart" button).
* @param e the button click action event
*/
public void actionPerformed(ActionEvent e) {
if (e.getSource().equals(replaceButton)) {
// Gather all the selected cards.
List<Integer> selection = new ArrayList<Integer>();
for (int k = 0; k < board.size(); k++) {
if (selections[k]) {
selection.add(new Integer(k));
}
}
// Make sure that the selected cards represent a legal replacement.
if (!board.isLegal(selection)) {
signalError();
return;
}
for (int k = 0; k < board.size(); k++) {
selections[k] = false;
}
// Do the replace.
board.replaceSelectedCards(selection);
if (board.isEmpty()) {
signalWin();
} else if (!board.anotherPlayIsPossible()) {
signalLoss();
}
repaint();
} else if (e.getSource().equals(restartButton)) {
board.newGame();
getRootPane().setDefaultButton(replaceButton);
winMsg.setVisible(false);
lossMsg.setVisible(false);
if (!board.anotherPlayIsPossible()) {
signalLoss();
lossMsg.setVisible(true);
}
for (int i = 0; i < selections.length; i++) {
selections[i] = false;
}
repaint();
} else {
signalError();
return;
}
}
/**
* Display a win.
*/
private void signalWin() {
getRootPane().setDefaultButton(restartButton);
winMsg.setVisible(true);
totalWins++;
totalGames++;
}
/**
* Display a loss.
*/
private void signalLoss() {
getRootPane().setDefaultButton(restartButton);
lossMsg.setVisible(true);
totalGames++;
}
/**
* Receives and handles mouse clicks. Other mouse events are ignored.
*/
private class MyMouseListener implements MouseListener {
/**
* Handle a mouse click on a card by toggling its "selected" property.
* Each card is represented as a label.
* @param e the mouse event.
*/
public void mouseClicked(MouseEvent e) {
for (int k = 0; k < board.size(); k++) {
if (e.getSource().equals(displayCards[k])
&& board.cardAt(k) != null) {
selections[k] = !selections[k];
repaint();
return;
}
}
signalError();
}
/**
* Ignore a mouse exited event.
* @param e the mouse event.
*/
public void mouseExited(MouseEvent e) {
}
/**
* Ignore a mouse released event.
* @param e the mouse event.
*/
public void mouseReleased(MouseEvent e) {
}
/**
* Ignore a mouse entered event.
* @param e the mouse event.
*/
public void mouseEntered(MouseEvent e) {
}
/**
* Ignore a mouse pressed event.
* @param e the mouse event.
*/
public void mousePressed(MouseEvent e) {
}
}
}
Elevens 2
Now we'll build our aggregate object, the Deck
.
/**
* This is a class that tests the Deck class.
*/
public class DeckTester {
/**
* The main method in this class checks the Deck operations for consistency.
* @param args is not used.
*/
public static void main(String[] args) {
/* *** TO BE IMPLEMENTED IN ACTIVITY 2 *** */
}
}
import java.util.List;
import java.util.ArrayList;
/**
* The Deck class represents a shuffled deck of cards.
* It provides several operations including
* initialize, shuffle, deal, and check if empty.
*/
public class Deck {
/**
* cards contains all the cards in the deck.
*/
private List<Card> cards;
/**
* size is the number of not-yet-dealt cards.
* Cards are dealt from the top (highest index) down.
* The next card to be dealt is at size - 1.
*/
private int size;
/**
* Creates a new <code>Deck</code> instance.<BR>
* It pairs each element of ranks with each element of suits,
* and produces one of the corresponding card.
* @param ranks is an array containing all of the card ranks.
* @param suits is an array containing all of the card suits.
* @param values is an array containing all of the card point values.
*/
public Deck(String[] ranks, String[] suits, int[] values) {
cards = new ArrayList<Card>();
for (int j = 0; j < ranks.length; j++) {
for (String suitString : suits){
cards.add(new Card(ranks[j], suitString, values[j]));
}
}
/**
* Determines if this deck is empty (no undealt cards).
* @return true if this deck is empty, false otherwise.
*/
public boolean isEmpty() {
/* *** TO BE IMPLEMENTED IN ACTIVITY 2 *** */
}
/**
* Accesses the number of undealt cards in this deck.
* @return the number of undealt cards in this deck.
*/
public int size() {
/* *** TO BE IMPLEMENTED IN ACTIVITY 2 *** */
}
/**
* Randomly permute the given collection of cards
* and reset the size to represent the entire deck.
*/
public void shuffle() {
/* *** TO BE IMPLEMENTED IN ACTIVITY 4 *** */
}
/**
* Deals a card from this deck.
* @return the card just dealt, or null if all the cards have been
* previously dealt.
*/
public Card deal() {
/* *** TO BE IMPLEMENTED IN ACTIVITY 2 *** */
// IS EMPTY if so return null
size--;
Card c = cards.get(size);
return c;
}
/**
* Generates and returns a string representation of this deck.
* @return a string representation of this deck.
*/
@Override
public String toString() {
String rtn = "size = " + size + "\nUndealt cards: \n";
for (int k = size - 1; k >= 0; k--) {
rtn = rtn + cards.get(k);
if (k != 0) {
rtn = rtn + ", ";
}
if ((size - k) % 2 == 0) {
// Insert carriage returns so entire deck is visible on console.
rtn = rtn + "\n";
}
}
rtn = rtn + "\nDealt cards: \n";
for (int k = cards.size() - 1; k >= size; k--) {
rtn = rtn + cards.get(k);
if (k != size) {
rtn = rtn + ", ";
}
if ((k - cards.size()) % 2 == 0) {
// Insert carriage returns so entire deck is visible on console.
rtn = rtn + "\n";
}
}
rtn = rtn + "\n";
return rtn;
}
}
Elevens 3: Shuffling Algorithm
This algorithm uses a lot of structures we'll see when we have to start sorting items rather than shuffling. The problem with a perfect shuffle is that after 4 passes, all the cards are back in the original order.
/**
* This class provides a convenient way to test shuffling methods.
*/
public class Shuffler {
/**
* The number of consecutive shuffle steps to be performed in each call
* to each sorting procedure.
*/
private static final int SHUFFLE_COUNT = 1;
/**
* Tests shuffling methods.
* @param args is not used.
*/
public static void main(String[] args) {
System.out.println("Results of " + SHUFFLE_COUNT +
" consecutive perfect shuffles:");
int[] values1 = {0, 1, 2, 3};
for (int j = 1; j <= SHUFFLE_COUNT; j++) {
perfectShuffle(values1);
System.out.print(" " + j + ":");
for (int k = 0; k < values1.length; k++) {
System.out.print(" " + values1[k]);
}
System.out.println();
}
System.out.println();
System.out.println("Results of " + SHUFFLE_COUNT +
" consecutive efficient selection shuffles:");
int[] values2 = {0, 1, 2, 3};
for (int j = 1; j <= SHUFFLE_COUNT; j++) {
selectionShuffle(values2);
System.out.print(" " + j + ":");
for (int k = 0; k < values2.length; k++) {
System.out.print(" " + values2[k]);
}
System.out.println();
}
System.out.println();
}
/**
* Apply a "perfect shuffle" to the argument.
* The perfect shuffle algorithm splits the deck in half, then interleaves
* the cards in one half with the cards in the other.
* @param values is an array of integers simulating cards to be shuffled.
*/
public static void perfectShuffle(int[] values) {
/* *** TO BE IMPLEMENTED IN ACTIVITY 3 *** */
}
/**
* Apply an "efficient selection shuffle" to the argument.
* The selection shuffle algorithm conceptually maintains two sequences
* of cards: the selected cards (initially empty) and the not-yet-selected
* cards (initially the entire deck). It repeatedly does the following until
* all cards have been selected: randomly remove a card from those not yet
* selected and add it to the selected cards.
* An efficient version of this algorithm makes use of arrays to avoid
* searching for an as-yet-unselected card.
* @param values is an array of integers simulating cards to be shuffled.
*/
public static void selectionShuffle(int[] values) {
/* *** TO BE IMPLEMENTED IN ACTIVITY 3 *** */
}
}
Elevens 4-6
In Activity 4, we'll add the shuffler method to the Deck
class. It's the same process but we'll adapt using arrays to ArrayLists. We'll just skip activities 5 and 6 outright as we'll cover that material elsewhere. In case you're curious, the one cool thing we skip over for now is assert statements in Java:
Elevens 7
import java.util.List;
import java.util.ArrayList;
/**
* This class represents a Board that can be used in a collection
* of solitaire games similar to Elevens. The variants differ in
* card removal and the board size.
*/
public abstract class Board {
/**
* The cards on this board.
*/
private Card[] cards;
/**
* The deck of cards being used to play the current game.
*/
private Deck deck;
/**
* Flag used to control debugging print statements.
*/
private static final boolean I_AM_DEBUGGING = false;
/**
* Creates a new <code>Board</code> instance.
* @param size the number of cards in the board
* @param ranks the names of the card ranks needed to create the deck
* @param suits the names of the card suits needed to create the deck
* @param pointValues the integer values of the cards needed to create
* the deck
*/
public Board(int size, String[] ranks, String[] suits, int[] pointValues) {
cards = new Card[size];
deck = new Deck(ranks, suits, pointValues);
if (I_AM_DEBUGGING) {
System.out.println(deck);
System.out.println("----------");
}
dealMyCards();
}
/**
* Start a new game by shuffling the deck and
* dealing some cards to this board.
*/
public void newGame() {
deck.shuffle();
dealMyCards();
}
/**
* Accesses the size of the board.
* Note that this is not the number of cards it contains,
* which will be smaller near the end of a winning game.
* @return the size of the board
*/
public int size() {
return cards.length;
}
/**
* Determines if the board is empty (has no cards).
* @return true if this board is empty; false otherwise.
*/
public boolean isEmpty() {
for (int k = 0; k < cards.length; k++) {
if (cards[k] != null) {
return false;
}
}
return true;
}
/**
* Deal a card to the kth position in this board.
* If the deck is empty, the kth card is set to null.
* @param k the index of the card to be dealt.
*/
public void deal(int k) {
cards[k] = deck.deal();
}
/**
* Accesses the deck's size.
* @return the number of undealt cards left in the deck.
*/
public int deckSize() {
return deck.size();
}
/**
* Accesses a card on the board.
* @return the card at position k on the board.
* @param k is the board position of the card to return.
*/
public Card cardAt(int k) {
return cards[k];
}
/**
* Replaces selected cards on the board by dealing new cards.
* @param selectedCards is a list of the indices of the
* cards to be replaced.
*/
public void replaceSelectedCards(List<Integer> selectedCards) {
for (Integer k : selectedCards) {
deal(k.intValue());
}
}
/**
* Gets the indexes of the actual (non-null) cards on the board.
*
* @return a List that contains the locations (indexes)
* of the non-null entries on the board.
*/
public List<Integer> cardIndexes() {
List<Integer> selected = new ArrayList<Integer>();
for (int k = 0; k < cards.length; k++) {
if (cards[k] != null) {
selected.add(Integer.valueOf(k));
}
}
return selected;
}
/**
* Generates and returns a string representation of this board.
* @return the string version of this board.
*/
public String toString() {
String s = "";
for (int k = 0; k < cards.length; k++) {
s = s + k + ": " + cards[k] + "\n";
}
return s;
}
/**
* Determine whether or not the game has been won,
* i.e. neither the board nor the deck has any more cards.
* @return true when the current game has been won;
* false otherwise.
*/
public boolean gameIsWon() {
if (deck.isEmpty()) {
for (Card c : cards) {
if (c != null) {
return false;
}
}
return true;
}
return false;
}
/**
* Method to be completed by the concrete class that determines
* if the selected cards form a valid group for removal.
* @param selectedCards the list of the indices of the selected cards.
* @return true if the selected cards form a valid group for removal;
* false otherwise.
*/
public abstract boolean isLegal(List<Integer> selectedCards);
/**
* Method to be completed by the concrete class that determines
* if there are any legal plays left on the board.
* @return true if there is a legal play left on the board;
* false otherwise.
*/
public abstract boolean anotherPlayIsPossible();
/**
* Deal cards to this board to start the game.
*/
private void dealMyCards() {
for (int k = 0; k < cards.length; k++) {
cards[k] = deck.deal();
}
}
}
import java.util.List;
import java.util.ArrayList;
/**
* The ElevensBoard class represents the board in a game of Elevens.
*/
public class ElevensBoard extends Board {
/**
* The size (number of cards) on the board.
*/
private static final int BOARD_SIZE = 9;
/**
* The ranks of the cards for this game to be sent to the deck.
*/
private static final String[] RANKS =
{"ace", "2", "3", "4", "5", "6", "7", "8", "9", "10", "jack", "queen", "king"};
/**
* The suits of the cards for this game to be sent to the deck.
*/
private static final String[] SUITS =
{"spades", "hearts", "diamonds", "clubs"};
/**
* The values of the cards for this game to be sent to the deck.
*/
private static final int[] POINT_VALUES =
{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 0, 0};
/**
* Flag used to control debugging print statements.
*/
private static final boolean I_AM_DEBUGGING = true;
/**
* Creates a new <code>ElevensBoard</code> instance.
*/
public ElevensBoard() {
super(BOARD_SIZE, RANKS, SUITS, POINT_VALUES);
}
/**
* Determines if the selected cards form a valid group for removal.
* In Elevens, the legal groups are (1) a pair of non-face cards
* whose values add to 11, and (2) a group of three cards consisting of
* a jack, a queen, and a king in some order.
* @param selectedCards the list of the indices of the selected cards.
* @return true if the selected cards form a valid group for removal;
* false otherwise.
*/
@Override
public boolean isLegal(List<Integer> selectedCards) {
if (selectedCards.size() == 2) {
return findPairSum11(selectedCards).size() > 0;
} else if (selectedCards.size() == 3) {
return findJQK(selectedCards).size() > 0;
} else {
return false;
}
}
/**
* Determine if there are any legal plays left on the board.
* In Elevens, there is a legal play if the board contains
* (1) a pair of non-face cards whose values add to 11, or (2) a group
* of three cards consisting of a jack, a queen, and a king in some order.
* @return true if there is a legal play left on the board;
* false otherwise.
*/
@Override
public boolean anotherPlayIsPossible() {
List<Integer> cIndexes = cardIndexes();
return findPairSum11(cIndexes).size() > 0
|| findJQK(cIndexes).size() > 0;
}
/**
* Look for an 11-pair in the selected cards.
* @param selectedCards selects a subset of this board. It is list
* of indexes into this board that are searched
* to find an 11-pair.
* @return a list of the indexes of an 11-pair, if an 11-pair was found;
* an empty list, if an 11-pair was not found.
*/
private List<Integer> findPairSum11(List<Integer> selectedCards) {
List<Integer> foundIndexes = new ArrayList<Integer>();
for (int sk1 = 0; sk1 < selectedCards.size(); sk1++) {
int k1 = selectedCards.get(sk1).intValue();
for (int sk2 = sk1 + 1; sk2 < selectedCards.size(); sk2++) {
int k2 = selectedCards.get(sk2).intValue();
if (cardAt(k1).pointValue() + cardAt(k2).pointValue() == 11) {
foundIndexes.add(Integer.valueOf(k1));
foundIndexes.add(Integer.valueOf(k2));
return foundIndexes;
}
}
}
return foundIndexes;
}
/**
* Look for a JQK in the selected cards.
* @param selectedCards selects a subset of this board. It is list
* of indexes into this board that are searched
* to find a JQK group.
* @return a list of the indexes of a JQK, if a JQK was found;
* an empty list, if a JQK was not found.
*/
private List<Integer> findJQK(List<Integer> selectedCards) {
List<Integer> foundIndexes = new ArrayList<Integer>();
int jackIndex = -1;
int queenIndex = -1;
int kingIndex = -1;
for (Integer kObj : selectedCards) {
int k = kObj.intValue();
if (cardAt(k).rank().equals("jack")) {
jackIndex = k;
} else if (cardAt(k).rank().equals("queen")) {
queenIndex = k;
} else if (cardAt(k).rank().equals("king")) {
kingIndex = k;
}
}
if (jackIndex != -1 && queenIndex != -1 && kingIndex != -1) {
foundIndexes.add(Integer.valueOf(jackIndex));
foundIndexes.add(Integer.valueOf(queenIndex));
foundIndexes.add(Integer.valueOf(kingIndex));
}
return foundIndexes;
}
/**
* Looks for a legal play on the board. If one is found, it plays it.
* @return true if a legal play was found (and made); false othewise.
*/
public boolean playIfPossible() {
return playPairSum11IfPossible() || playJQKIfPossible();
}
/**
* Looks for a pair of non-face cards whose values sum to 11.
* If found, replace them with the next two cards in the deck.
* The simulation of this game uses this method.
* @return true if an 11-pair play was found (and made); false othewise.
*/
private boolean playPairSum11IfPossible() {
List<Integer> foundIndexes = cardIndexes();
List<Integer> cardsToReplace = findPairSum11(foundIndexes);
if (cardsToReplace.size() > 0) {
replaceSelectedCards(cardsToReplace);
if (I_AM_DEBUGGING) {
System.out.println("11-Pair removed.\n");
}
return true;
} else {
return false;
}
}
/**
* Looks for a group of three face cards JQK.
* If found, replace them with the next three cards in the deck.
* The simulation of this game uses this method.
* @return true if a JQK play was found (and made); false othewise.
*/
private boolean playJQKIfPossible() {
List<Integer> foundIndexes = cardIndexes();
List<Integer> cardsToReplace = findJQK(foundIndexes);
if (cardsToReplace.size() > 0) {
replaceSelectedCards(cardsToReplace);
if (I_AM_DEBUGGING) {
System.out.println("JQK-Triplet removed.\n");
}
return true;
} else {
return false;
}
}
}
Elevens 9
/**
* This is a class that plays the GUI version of the Elevens game.
* See accompanying documents for a description of how Elevens is played.
*/
public class ElevensGUIRunner {
/**
* Plays the GUI version of Elevens.
* @param args is not used.
*/
public static void main(String[] args) {
Board board = new ElevensBoard();
CardGameGUI gui = new CardGameGUI(board);
gui.displayGame();
}
}
import java.awt.Point;
import java.awt.Graphics;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Color;
import java.awt.Toolkit;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.ImageIcon;
import java.net.URL;
import java.util.List;
import java.util.ArrayList;
/**
* This class provides a GUI for solitaire games related to Elevens.
*/
public class CardGameGUI extends JFrame implements ActionListener {
/** Height of the game frame. */
private static final int DEFAULT_HEIGHT = 302;
/** Width of the game frame. */
private static final int DEFAULT_WIDTH = 800;
/** Width of a card. */
private static final int CARD_WIDTH = 73;
/** Height of a card. */
private static final int CARD_HEIGHT = 97;
/** Row (y coord) of the upper left corner of the first card. */
private static final int LAYOUT_TOP = 30;
/** Column (x coord) of the upper left corner of the first card. */
private static final int LAYOUT_LEFT = 30;
/** Distance between the upper left x coords of
* two horizonally adjacent cards. */
private static final int LAYOUT_WIDTH_INC = 100;
/** Distance between the upper left y coords of
* two vertically adjacent cards. */
private static final int LAYOUT_HEIGHT_INC = 125;
/** y coord of the "Replace" button. */
private static final int BUTTON_TOP = 30;
/** x coord of the "Replace" button. */
private static final int BUTTON_LEFT = 570;
/** Distance between the tops of the "Replace" and "Restart" buttons. */
private static final int BUTTON_HEIGHT_INC = 50;
/** y coord of the "n undealt cards remain" label. */
private static final int LABEL_TOP = 160;
/** x coord of the "n undealt cards remain" label. */
private static final int LABEL_LEFT = 540;
/** Distance between the tops of the "n undealt cards" and
* the "You lose/win" labels. */
private static final int LABEL_HEIGHT_INC = 35;
/** The board (Board subclass). */
private Board board;
/** The main panel containing the game components. */
private JPanel panel;
/** The Replace button. */
private JButton replaceButton;
/** The Restart button. */
private JButton restartButton;
/** The "number of undealt cards remain" message. */
private JLabel statusMsg;
/** The "you've won n out of m games" message. */
private JLabel totalsMsg;
/** The card displays. */
private JLabel[] displayCards;
/** The win message. */
private JLabel winMsg;
/** The loss message. */
private JLabel lossMsg;
/** The coordinates of the card displays. */
private Point[] cardCoords;
/** kth element is true iff the user has selected card #k. */
private boolean[] selections;
/** The number of games won. */
private int totalWins;
/** The number of games played. */
private int totalGames;
/**
* Initialize the GUI.
* @param gameBoard is a <code>Board</code> subclass.
*/
public CardGameGUI(Board gameBoard) {
board = gameBoard;
totalWins = 0;
totalGames = 0;
// Initialize cardCoords using 5 cards per row
cardCoords = new Point[board.size()];
int x = LAYOUT_LEFT;
int y = LAYOUT_TOP;
for (int i = 0; i < cardCoords.length; i++) {
cardCoords[i] = new Point(x, y);
if (i % 5 == 4) {
x = LAYOUT_LEFT;
y += LAYOUT_HEIGHT_INC;
} else {
x += LAYOUT_WIDTH_INC;
}
}
selections = new boolean[board.size()];
initDisplay();
setDefaultCloseOperation(EXIT_ON_CLOSE);
repaint();
}
/**
* Run the game.
*/
public void displayGame() {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
setVisible(true);
}
});
}
/**
* Draw the display (cards and messages).
*/
public void repaint() {
for (int k = 0; k < board.size(); k++) {
String cardImageFileName =
imageFileName(board.cardAt(k), selections[k]);
URL imageURL = getClass().getResource(cardImageFileName);
if (imageURL != null) {
ImageIcon icon = new ImageIcon(imageURL);
displayCards[k].setIcon(icon);
displayCards[k].setVisible(true);
} else {
throw new RuntimeException(
"Card image not found: \"" + cardImageFileName + "\"");
}
}
statusMsg.setText(board.deckSize()
+ " undealt cards remain.");
statusMsg.setVisible(true);
totalsMsg.setText("You've won " + totalWins
+ " out of " + totalGames + " games.");
totalsMsg.setVisible(true);
pack();
panel.repaint();
}
/**
* Initialize the display.
*/
private void initDisplay() {
panel = new JPanel() {
public void paintComponent(Graphics g) {
super.paintComponent(g);
}
};
// If board object's class name follows the standard format
// of ...Board or ...board, use the prefix for the JFrame title
String className = board.getClass().getSimpleName();
int classNameLen = className.length();
int boardLen = "Board".length();
String boardStr = className.substring(classNameLen - boardLen);
if (boardStr.equals("Board") || boardStr.equals("board")) {
int titleLength = classNameLen - boardLen;
setTitle(className.substring(0, titleLength));
}
// Calculate number of rows of cards (5 cards per row)
// and adjust JFrame height if necessary
int numCardRows = (board.size() + 4) / 5;
int height = DEFAULT_HEIGHT;
if (numCardRows > 2) {
height += (numCardRows - 2) * LAYOUT_HEIGHT_INC;
}
this.setSize(new Dimension(DEFAULT_WIDTH, height));
panel.setLayout(null);
panel.setPreferredSize(
new Dimension(DEFAULT_WIDTH - 20, height - 20));
displayCards = new JLabel[board.size()];
for (int k = 0; k < board.size(); k++) {
displayCards[k] = new JLabel();
panel.add(displayCards[k]);
displayCards[k].setBounds(cardCoords[k].x, cardCoords[k].y,
CARD_WIDTH, CARD_HEIGHT);
displayCards[k].addMouseListener(new MyMouseListener());
selections[k] = false;
}
replaceButton = new JButton();
replaceButton.setText("Replace");
panel.add(replaceButton);
replaceButton.setBounds(BUTTON_LEFT, BUTTON_TOP, 100, 30);
replaceButton.addActionListener(this);
restartButton = new JButton();
restartButton.setText("Restart");
panel.add(restartButton);
restartButton.setBounds(BUTTON_LEFT, BUTTON_TOP + BUTTON_HEIGHT_INC,
100, 30);
restartButton.addActionListener(this);
statusMsg = new JLabel(
board.deckSize() + " undealt cards remain.");
panel.add(statusMsg);
statusMsg.setBounds(LABEL_LEFT, LABEL_TOP, 250, 30);
winMsg = new JLabel();
winMsg.setBounds(LABEL_LEFT, LABEL_TOP + LABEL_HEIGHT_INC, 200, 30);
winMsg.setFont(new Font("SansSerif", Font.BOLD, 25));
winMsg.setForeground(Color.GREEN);
winMsg.setText("You win!");
panel.add(winMsg);
winMsg.setVisible(false);
lossMsg = new JLabel();
lossMsg.setBounds(LABEL_LEFT, LABEL_TOP + LABEL_HEIGHT_INC, 200, 30);
lossMsg.setFont(new Font("SanSerif", Font.BOLD, 25));
lossMsg.setForeground(Color.RED);
lossMsg.setText("Sorry, you lose.");
panel.add(lossMsg);
lossMsg.setVisible(false);
totalsMsg = new JLabel("You've won " + totalWins
+ " out of " + totalGames + " games.");
totalsMsg.setBounds(LABEL_LEFT, LABEL_TOP + 2 * LABEL_HEIGHT_INC,
250, 30);
panel.add(totalsMsg);
if (!board.anotherPlayIsPossible()) {
signalLoss();
}
pack();
getContentPane().add(panel);
getRootPane().setDefaultButton(replaceButton);
panel.setVisible(true);
}
/**
* Deal with the user clicking on something other than a button or a card.
*/
private void signalError() {
Toolkit t = panel.getToolkit();
t.beep();
}
/**
* Returns the image that corresponds to the input card.
* Image names have the format "[Rank][Suit].GIF" or "[Rank][Suit]S.GIF",
* for example "aceclubs.GIF" or "8heartsS.GIF". The "S" indicates that
* the card is selected.
*
* @param c Card to get the image for
* @param isSelected flag that indicates if the card is selected
* @return String representation of the image
*/
private String imageFileName(Card c, boolean isSelected) {
String str = "cards/";
if (c == null) {
return "cards/back1.GIF";
}
str += c.rank() + c.suit();
if (isSelected) {
str += "S";
}
str += ".GIF";
return str;
}
/**
* Respond to a button click (on either the "Replace" button
* or the "Restart" button).
* @param e the button click action event
*/
public void actionPerformed(ActionEvent e) {
if (e.getSource().equals(replaceButton)) {
// Gather all the selected cards.
List<Integer> selection = new ArrayList<Integer>();
for (int k = 0; k < board.size(); k++) {
if (selections[k]) {
selection.add(new Integer(k));
}
}
// Make sure that the selected cards represent a legal replacement.
if (!board.isLegal(selection)) {
signalError();
return;
}
for (int k = 0; k < board.size(); k++) {
selections[k] = false;
}
// Do the replace.
board.replaceSelectedCards(selection);
if (board.isEmpty()) {
signalWin();
} else if (!board.anotherPlayIsPossible()) {
signalLoss();
}
repaint();
} else if (e.getSource().equals(restartButton)) {
board.newGame();
getRootPane().setDefaultButton(replaceButton);
winMsg.setVisible(false);
lossMsg.setVisible(false);
if (!board.anotherPlayIsPossible()) {
signalLoss();
lossMsg.setVisible(true);
}
for (int i = 0; i < selections.length; i++) {
selections[i] = false;
}
repaint();
} else {
signalError();
return;
}
}
/**
* Display a win.
*/
private void signalWin() {
getRootPane().setDefaultButton(restartButton);
winMsg.setVisible(true);
totalWins++;
totalGames++;
}
/**
* Display a loss.
*/
private void signalLoss() {
getRootPane().setDefaultButton(restartButton);
lossMsg.setVisible(true);
totalGames++;
}
/**
* Receives and handles mouse clicks. Other mouse events are ignored.
*/
private class MyMouseListener implements MouseListener {
/**
* Handle a mouse click on a card by toggling its "selected" property.
* Each card is represented as a label.
* @param e the mouse event.
*/
public void mouseClicked(MouseEvent e) {
for (int k = 0; k < board.size(); k++) {
if (e.getSource().equals(displayCards[k])
&& board.cardAt(k) != null) {
selections[k] = !selections[k];
repaint();
return;
}
}
signalError();
}
/**
* Ignore a mouse exited event.
* @param e the mouse event.
*/
public void mouseExited(MouseEvent e) {
}
/**
* Ignore a mouse released event.
* @param e the mouse event.
*/
public void mouseReleased(MouseEvent e) {
}
/**
* Ignore a mouse entered event.
* @param e the mouse event.
*/
public void mouseEntered(MouseEvent e) {
}
/**
* Ignore a mouse pressed event.
* @param e the mouse event.
*/
public void mousePressed(MouseEvent e) {
}
}
}
You will need to add this folder to your codebase.
Concepts to Review
Loops
Searching / counting while traversing an array and an ArrayList
Traverse to find max and mins:
public static int findLargest(int[] nums){
Interact with an ArrayList
public static void makeLowerCase(ArrayList<String> commands){
Keep count
public static int countNames(ArrayList<String> names, String target){
Shuffle methods:
public static void selectShuffle(int[] nums){
public static void perfectShuffle(Card[] cards){
Nested loops
Find common elements between two collections:
public static ArrayList<String> findCommonNames(String[] roster1, String[] roster2){
Classes
Encapsulation with accessor and mutator methods
Implementing an abstract class (with encapsulation)
OOP: When should you move properties and methods to an abstract layer?
Name three properties or methods that would be suitable for a
Human
class and three that would go on an abstractMammal
class.
How do you declare an abstract class and an abstract method?
Declare an abstract class called
Athlete
, give it private properties ofname
,number
,position
, andis_active
.Make a constructor.
Make accessor methods for each property.
How do you declare a class to inherit the properties from an abstract class?
Create classes called
SoccerAthlete
andBasketballAthlete
that both inherit fromAthlete
.Give each class a
toString
method that returns the String, "My name is {name} and I'm a {position} in soccer. I'm #{number}." Only if they're active. If they're inactive, the returning String should read, "I used to be play basketball as a {position}."
How can you take advantage of polymorphism?
Create a runner class with a main method that creates an array of
Athlete
s. Loop through them and have each one introduce themselves.
Challenge
Submit a completed NumSet class:
import java.util.ArrayList;
public class NumSet {
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
/*
ROUND 1 tests
*/
// Create a random int array of a given length, low and high end of range
int[] randArray = randArray(15, 0, 100);
// Create a random Integer ArrayList of given length, low and high range
ArrayList<Integer> randArrL = randArrL(8, 5, 50);
// How many similar elements are in a given array and ArrayList
System.out.print("There are this many similar elements: ");
System.out.println(compareNums(randArray, randArrL));
// printPretty takes an int array and prints it out nicely
printPretty(randArray);
// printPretty takes an Integer ArrayList and prints it out nicely
printPretty(randArrL);
/*
ROUND 2 tests
*/
// shuffle randomizes an int array (then calls printPretty)
shuffle(randArray);
// shuffle randomizes an Integer ArrayList (then calls printPretty)
shuffle(randArrL);
// divide all numbers by two
divByTwo(randArray);
divByTwo(randArrL);
//sumArray
sumArray(randArray);
sumArray(randArrL);
}
/*
ROUND 1 code
*/
// TODO: randArray
// TODO: randArrL
// TODO: compareNums
// TODO: prettyPretty (overloaded)
/*
ROUND 2 code
*/
// TODO: shuffle array
// TODO: shuffle ArrayList
// TODO: divByTwo (overloaded)
// TODO: sumArray (overloaded)
}
Studying for the Test
Your Elevens test will be a semi-randomized selection of multiple choice questions all taken from the same sources used for our Do Nows. There will be one free response question similar to the homework. Both of these parts of the test reflect the two portions of the AP test. Therefore, the best way to study would be to review AP practice problems from our relevant topics:
Redo homework assignments and previous drills
This site's Chapters 3-9: https://runestone.academy/runestone/books/published/apcsareview/index.html
Take a practice test: http://ice.cc.gatech.edu/apexam_final/index.jsp
Do some Codingbat: https://codingbat.com/java/AP-1
Last updated