Professional Documents
Culture Documents
* Module 6 Programming Assignment: Build Card Game Phase 3 Authors: Andrew
* Bundy, Jason Contreras, Brian James, Robert Meis Due: 12/3/19 Rules
* (displayed in dialog box at start of game): Rule 1: You can play a card that
* is one value higher or lower than the card on the stack. Rule 2: The stacks
* begin with no cards. Any card may be played on an empty stack. Rule 3: Click
* on the card you want to play. Rule 4: The stacks you can play your card on
* will be highlighted. The rest will not. Rule 5: Click on the highlighted
* stack you wish to play your card on. Rule 6: Jokers can be played on any
* stack. Any card can be played on a Joker. Rule 7: If you can't play a card on
* any stack, click the Pass Turn button. Rule 8: The computer passes if it
* can't play a card on any stack. Rule 9: If both human and computer pass their
* turn, a new card is dealt onto each stack. Rule 10: The game ends when the
* dealing deck is empty. Whoever passes the fewest times wins. Phase 1 MVC
* Formatting: Robert Meis, Andrew Bundy Modified previous week's assignment to
* fit the Model‐View‐Controller pattern. Phase 2 Timer: Jason Contreras, Andrew
* Bundy, Brian James A timer that runs independently of the GUI. Phase 3 GUI:
* Andrew Bundy, Robert Meis, Brian James The interface for the Build Card Game
* to be played. Phase 3 Gameplay: Andrew Bundy, Brian James, Jason Contreras
* The underlying logic for the Build Card Game. Phase 3 Computer Strategy:
* Andrew Bundy CardGameFramework: Professor Cecil Allows for variable gameplay;
* holds the deck and hands for the game.
*/
package assignment6;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JToggleButton;
import javax.swing.border.Border;
import assignment6.Card.Suit;
public class Assignment6
{
public static void main(String[] args)
{
// Initialize the game. Start playing.
Controller.initializer();
}
}
/*
* The Model class holds the data necessary to play a card game. It keeps this
* data separate from View, with Controller acting as a bridge between the two
* classes. It also houses the Timer class.
*/
class Model
{
public static int NUM_CARDS_PER_HAND = 7;
public static int NUM_PLAYERS = 2;
private static Icon[][] iconCards = new ImageIcon[14][4];
private static Icon iconBack;
public static boolean iconsLoaded = false;
/**
* loadCardIcons loads all of the .gif files that hold the image icon of each
* card (standard 52 cards, 4 Jokers, 1 card back). It will load the icons
* only once.
*/
public static void loadCardIcons()
{
if (!iconsLoaded)
{
for (int suit = 0; suit < 4; suit++)
{
for (int card = 0; card < 14; card++)
{
iconCards[card][suit] = new ImageIcon(
"images/" + turnIntIntoCardValue(card)
+ turnIntIntoCardSuit(suit) + ".gif");
}
}
iconBack = new ImageIcon("images/BK.gif");
// Sets to true so if statement will no longer trigger. Will only load
// entire array once.
iconsLoaded = true;
}
}
// Accessor for a specific card icon.
public static Icon getIcon(Card card)
{
loadCardIcons();
return iconCards[valueAsInt(card)][suitAsInt(card)];
}
// Accessor for the icon of the card back.
public static Icon getBackCardIcon()
{
return iconBack;
}
/**
* turnIntIntoCardValue receives an integer and uses the Card class's
* valueRank method to determine the corresponding card value (0 for lowest,
* 13 for highest). It then returns that value as a char. If the integer is
* out of range, the value 'M' is returned to denote an invalid parameter.
*
* @param intValue The integer to convert to a card value.
* @return The char value of the card.
*/
private static char turnIntIntoCardValue(int intValue)
{
char finalValue;
char[] cardValue = Card.valueRanks(); // Holds all values of cards.
// Determine the corresponding card value.
if (intValue >= 0 && intValue < 14)
{
finalValue = cardValue[intValue];
}
// Return bad value if integer is out of range.
else
{
finalValue = 'M';
}
return finalValue;
}
/**
* turnIntoIntoCardSuit receives an integer and converts it to a card suit
* value.
*
* @param suit The integer to be converted to a suit.
* @return String containing the corresponding suit value.
*/
private static String turnIntIntoCardSuit(int suit)
{
String finalSuit = null;
String[] allSuits =
{ "C", "D", "H", "S" };
if (suit >= 0 && suit < 4)
{
finalSuit = allSuits[suit];
} else
{
finalSuit = allSuits[0];
}
return finalSuit;
}
/**
* suitAsInt converts a suit value into an integer to locate the card's suit
* value in the iconCards array.
*
* @param card Card whose suit will be converted to integer.
* @return Integer index location of the card's suit.
*/
private static int suitAsInt(Card card)
{
// Loop through Suit values to find match. Return index of Suit as int.
for (int i = 0; i < 4; i++)
{
if (card.getSuit() == Card.Suit.values()[i])
{
return i;
}
}
return 0;
}
/**
* valueAsInt converts a card value into an integer to locate the card's
* value in the iconCards array.
*
* @param card Card whose value will be converted to integer.
* @return Integer index location of the card's value.
*/
private static int valueAsInt(Card card)
{
char cardValue = card.getValue();
char[] valueArr = Card.valueRanks();
for (int cardArrIndex = 0; cardArrIndex < 14; cardArrIndex++)
{
if (valueArr[cardArrIndex] == cardValue)
{
return cardArrIndex;
}
}
return 0;
}
/*
* The Timer class creates a new thread to allow a timer count up while other
* threads continue working, in this case, the GUI. It can be started and
* stopped by a toggle button.
*/
static class Timer extends Thread
{
private int seconds;
private int minutes;
private int hours;
private boolean isRunning = true;
private boolean run = true;
// Default constructor.
public Timer()
{
seconds = 0;
minutes = 0;
hours = 0;
View.timerLabel.setText("00:00:00");
}
@Override
/**
* run starts a separate thread that increments a timer. It pauses for 1
* second between incrementing to ensure an accurate count.
*/
public void run()
{
while (run)
{
timerIncrementing();
View.timerLabel.setText(toString());
doNothing(1000);
}
}
/**
* timerIncrementing increases the time to be displayed on the timer. When
* this method is called during run, there is a 1 second delay to ensure
* the timer is incrementing properly.
*/
private void timerIncrementing()
{
if (getIsRunning())
{
if (seconds == 59)
{
seconds = 0;
if (minutes == 59)
{
minutes = 0;
if (hours == 99)
{
hours = 0;
} else
{
hours++;
}
} else
{
minutes++;
}
} else
{
seconds++;
}
}
}
/**
* doNothing calls the Thread.sleep method to pause the thread for a set
* number of milliseconds.
*
* @param milliseconds Number of milliseconds to pause for.
*/
public void doNothing(int milliseconds)
{
try
{
Thread.sleep(milliseconds);
} catch (InterruptedException e)
{
System.exit(0);
}
}
@Override
/**
* @return The time as a string.
*/
public String toString()
{
String currentTime = "";
currentTime = getHours() + ":" + getMinutes() + ":" + getSeconds();
return currentTime;
}
// Accessors.
public int getSeconds()
{
return seconds;
}
public int getMinutes()
{
return minutes;
}
public int getHours()
{
return hours;
}
public boolean getIsRunning()
{
return isRunning;
}
// Mutator.
public boolean setIsRunning(boolean isRunning)
{
this.isRunning = isRunning;
return isRunning;
}
}
}
/*
* The View class displays the elements of the card game in as a GUI. It sets up
* the card table and houses JComponents that will be updated by Controller.
*/
class View extends JFrame
{
int MAX_CARDS_PER_HAND = 56;
int MAX_PLAYERS = 2;
private int numCardsPerHand;
private int numPlayers;
public static JPanel pnlComputerHand, pnlHumanHand, pnlPlayArea;
public static JLabel timerLabel = new JLabel();
public static JToggleButton timerButton = new JToggleButton();
public static JPanel[] pnlPlayAreaArr = new JPanel[3];
public static JButton[] stackCards = new JButton[3];
public static JLabel[] stackLabels = new JLabel[3];
public static JButton pass = new JButton();
public static JLabel computerScore = new JLabel("", JLabel.CENTER);
public static JLabel humanScore = new JLabel("", JLabel.CENTER);
public static JLabel[] computerLabels = new JLabel[Model.NUM_CARDS_PER_HAND];
public static JButton[] humanButtons = new JButton[Model.NUM_CARDS_PER_HAND];
public static JLabel[] playedCardLabels = new JLabel[Model.NUM_PLAYERS];
public static JLabel[] playLabelText = new JLabel[Model.NUM_PLAYERS];
public static JLabel endResult = new JLabel("", JLabel.CENTER);
// Default constructor.
public View()
{
super();
setTitle("Card Table");
setSize(1024, 800);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
//
pnlComputerHand = new JPanel(); // Create JPanels
setPreferredSize(new Dimension(1150, 216));
Border cBorder = BorderFactory.createTitledBorder("Computer Player");
pnlComputerHand.setBorder(cBorder);
GridBagConstraints constraints = new GridBagConstraints();
JLabel computerPlayerLabel = new JLabel();
pnlComputerHand.add(computerPlayerLabel);
pnlComputerHand.setBackground(Color.WHITE);
pnlComputerHand.setLayout(new GridBagLayout());
//
pnlPlayArea = new JPanel();
setPreferredSize(new Dimension(1150, 218));
Border pBorder = BorderFactory.createTitledBorder("Playing Area");
pnlPlayArea.setBorder(pBorder);
constraints.insets = new Insets(5, 5, 5, 5);
constraints.weighty = 0.0;
constraints.weightx = 0.0;
constraints.gridx = 0;
constraints.gridy = 0;
JLabel playAreaLabel = new JLabel();
pnlPlayArea.add(playAreaLabel, constraints);
pnlPlayArea.setBackground(Color.WHITE);
pnlPlayArea.setLayout(new GridBagLayout());
//
pnlHumanHand = new JPanel();
setPreferredSize(new Dimension(1150, 216));
Border hBorder = BorderFactory.createTitledBorder("Human Player");
pnlHumanHand.setBorder(hBorder);
JLabel humanPlayerLabel = new JLabel();
pnlHumanHand.add(humanPlayerLabel);
pnlHumanHand.setBackground(Color.WHITE);
pnlHumanHand.setLayout(new GridBagLayout());
add(pnlComputerHand, BorderLayout.NORTH);
add(pnlPlayArea, BorderLayout.CENTER);
add(pnlHumanHand, BorderLayout.SOUTH);
}
// Constructor #1.
public View(String title, int numCardsPerHand, int numPlayers)
{
this();
setTitle(title);
this.numCardsPerHand = numCardsPerHand;
this.numPlayers = numPlayers;
}
// Accessors.
public int getNumCardsPerHand()
{
return numCardsPerHand;
}
public int getNumPlayers()
{
return numPlayers;
}
/**
* setUpTable sets up the card table for Build game. It creates areas to hold
* the computer's cards, the player's cards, and the playing field, which
* will show the results of each round.
*
* @param cardGame Holds player/computer hands.
*/
public static void setUpTable(CardGameFramework cardGame)
{
// Establish main frame in which program will run.
View myView = new View("Build Game", Model.NUM_CARDS_PER_HAND,
Model.NUM_PLAYERS);
myView.setLocationRelativeTo(null);
myView.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
GridBagConstraints constraints = new GridBagConstraints();
// Load all card icons.
Model.loadCardIcons();
// Instantiate and add icons for all cards in computer/player hand.
for (int i = 0; i < Model.NUM_CARDS_PER_HAND; i++)
{
humanButtons[i] = new JButton(
Model.getIcon(cardGame.getHand(1).inspectCard(i)));
computerLabels[i] = new JLabel(Model.getBackCardIcon());
// Adds action listener to button. Passes low card game to be played
// on button click.
humanButtons[i].addActionListener(
new Controller.CardListener(humanButtons[i], cardGame));
myView.pnlComputerHand.add(computerLabels[i]);
humanButtons[i].setBackground(Color.WHITE);
humanButtons[i].setOpaque(true);
humanButtons[i].setBorderPainted(false);
myView.pnlHumanHand.add(humanButtons[i]);
}
// Instantiate labels for the playing field.
for (int i = 0; i < Model.NUM_PLAYERS; i++)
{
playedCardLabels[i] = new JLabel();
}
// Set play field.
// Instantiate panels to insert into pnlPlayArea.
for (int i = 0; i < 3; i++)
{
pnlPlayAreaArr[i] = new JPanel();
pnlPlayAreaArr[i].setBackground(Color.WHITE);
pnlPlayAreaArr[i].setLayout(new GridBagLayout());
pnlPlayArea.add(pnlPlayAreaArr[i]);
}
stackLabels[0] = new JLabel("Stack One", JLabel.CENTER);
stackLabels[1] = new JLabel("Stack Two", JLabel.CENTER);
stackLabels[2] = new JLabel("Stack Three", JLabel.CENTER);
// Set stack buttons to show back of cards to start.
for (int i = 0; i < 3; i++)
{
stackCards[i] = new JButton(Model.getBackCardIcon());
stackCards[i]
.addActionListener(new Controller.StackListener(i, cardGame));
stackCards[i].setBackground(Color.WHITE);
stackCards[i].setOpaque(true);
stackCards[i].setBorderPainted(false);
stackCards[i].setEnabled(false);
}
// Add stacks and stack text.
constraints.anchor = GridBagConstraints.CENTER;
constraints.insets = new Insets(5, 5, 5, 100);
constraints.gridx = 0;
constraints.gridy = 0;
myView.pnlPlayAreaArr[1].add(stackCards[0], constraints);
constraints.insets = new Insets(5, 5, 5, 100);
constraints.gridx = 0;
constraints.gridy = 1;
myView.pnlPlayAreaArr[1].add(stackLabels[0], constraints);
constraints.insets = new Insets(0, 0, 0, 0);
constraints.gridx = 2;
constraints.gridy = 0;
myView.pnlPlayAreaArr[1].add(stackCards[1], constraints);
constraints.insets = new Insets(0, 50, 0, 50);
constraints.gridx = 2;
constraints.gridy = 1;
myView.pnlPlayAreaArr[1].add(stackLabels[1], constraints);
constraints.anchor = GridBagConstraints.CENTER;
constraints.insets = new Insets(5, 100, 5, 5);
constraints.gridx = 3;
constraints.gridy = 0;
myView.pnlPlayAreaArr[1].add(stackCards[2], constraints);
constraints.insets = new Insets(5, 100, 5, 5);
constraints.gridx = 3;
constraints.gridy = 1;
myView.pnlPlayAreaArr[1].add(stackLabels[2], constraints);
// Add JButton to skip player round.
constraints.insets = new Insets(50, 0, 0, 0);
constraints.gridx = 2;
constraints.gridy = 4;
pass.setText("Pass turn");
pass.addActionListener(new Controller.PassListener(cardGame));
myView.pnlPlayArea.add(pass, constraints);
// Adds human and computer scores.
constraints.insets = new Insets(0, 0, 5, 50);
constraints.gridx = 0;
constraints.gridy = 0;
myView.pnlPlayAreaArr[0].add(computerScore, constraints);
timerButton.addActionListener(new Controller.TimerListener());
constraints.insets = new Insets(5, 0, 0, 50);
constraints.gridx = 0;
constraints.gridy = 1;
myView.pnlPlayAreaArr[0].add(humanScore, constraints);
// Add timer button and label.
constraints.insets = new Insets(0, 50, 0, 50);
constraints.gridx = 0;
constraints.gridy = 0;
myView.pnlPlayAreaArr[2].add(timerLabel, constraints);
timerButton.addActionListener(new Controller.TimerListener());
constraints.insets = new Insets(0, 50, 0, 50);
constraints.gridx = 0;
constraints.gridy = 1;
timerButton.setPreferredSize(new Dimension(62, 30));
myView.pnlPlayAreaArr[2].add(timerButton, constraints);
// Start game with labels.
computerScore.setText("Computer passes: 0");
humanScore.setText("Human passes: 0");
// Add end result label.
constraints.anchor = GridBagConstraints.CENTER;
constraints.insets = new Insets(5, 5, 5, 30);
myView.pnlPlayAreaArr[1].add(View.endResult, constraints);
View.endResult.setFont(new Font("Arial", Font.PLAIN, 20));
// Show labels for stacks.
for (int i = 0; i < 3; i++)
{
stackLabels[i].setVisible(true);
}
// Show everything to the user.
myView.setVisible(true);
}
}
/*
* The Controller class allows for a user to play the Build Card Game. It acts
* as an interface between the Model and View classes, which house the data and
* GUI components respectively. It controls the data flow between the two
* classes and updates the game as it is played. It houses inner classes for
* button listeners for the card buttons, stack buttons, and pass button.
*/
class Controller
{
public static final int NUM_STACKS = 3;
private static Deck[] stacks = new Deck[3];
private static boolean[] playableStacks = new boolean[NUM_STACKS];
private static int playerPass, computerPass;
private static boolean playerPassed;
private static boolean computerPassed;
/**
* initializer sets up the card table and basic rules for the Build Card
* game. It also displays the Build Card game rules in a window before the
* first turn is played.
*/
public static void initializer()
{
int numPacksPerDeck = 1;
int numJokersPerPack = 2;
int numUnusedCardsPerPack = 0;
Card[] unusedCardsPerPack = null;
// Set number of times the players pass to 0.
playerPass = 0;
computerPass = 0;
// Start with all pass checks variables set to false for testing.
resetPassChecks();
// Instantiate stacks.
for (int i = 0; i < NUM_STACKS; i++)
{
stacks[i] = new Deck(numPacksPerDeck);
stacks[i].init(numPacksPerDeck);
}
// Empty decks to make stacks empty.
for (int i = 0; i < NUM_STACKS; i++)
{
while (stacks[i].getNumCards() > 0)
{
stacks[i].dealCard();
}
}
// Set up deck and basic game rules.
// TODO: This line goes over 80 char when formatted, manually format it
// before creating the text file to make sure it's under 80.
CardGameFramework buildCardGame = new CardGameFramework(numPacksPerDeck,
numJokersPerPack, numUnusedCardsPerPack, unusedCardsPerPack,
Model.NUM_PLAYERS, Model.NUM_CARDS_PER_HAND);
// Deal the initial cards to the players' hands.
buildCardGame.deal();
// Sort both hands.
buildCardGame.sortHands();
// Set up card table with labels and panels.
View.setUpTable(buildCardGame);
// Create and display dialog window with the rules.
String rulesMessage = "Rule 1: You can play a card that is one value "
+ "higher or lower than the card on the stack.\n"
+ "Rule 2: The stacks begin with no cards. Any card may be played on "
+ "an empty stack.\n" + "Rule 3: Click on the card you want to play.\n"
+ "Rule 4: The stacks you can play your card on will be highlighted."
+ " The rest will not.\n"
+ "Rule 5: Click on the highlighted stack you wish to play your card "
+ "on.\n"
+ "Rule 6: Jokers can be played on any stack. Any card can be played "
+ "on a Joker.\n"
+ "Rule 7: If you can't play a card on any stack, click the Pass Turn "
+ "button.\n"
+ "Rule 8: The computer passes if it can't play a card on any stack.\n"
+ "Rule 9: If both human and computer pass their turn, a new card is "
+ "dealt onto each stack.\n"
+ "Rule 10: The game ends when the dealing deck is empty. Whoever "
+ "passes the fewest times wins.";
JOptionPane.showMessageDialog(View.pnlPlayArea, rulesMessage);
}
/**
* playerTurn evaluates a card that a player selects in the GUI. The card is
* evaluated to see if it can be played on any of the stacks. If the stack is
* playable, it is enabled to allow the user to place their card on it.
*
* @param pCard The player's card.
*/
public static void playerTurn(Card pCard)
{
// Determine which stacks card can be played on.
determinePlayableStacks(pCard);
// Shows which stacks can be played on.
for (int i = 0; i < NUM_STACKS; i++)
{
if (playableStacks[i])
{
View.stackCards[i].setEnabled(true);
}
}
}
/**
* computerTurn simulates the computer's turn. It plays the first card it can
* on the first available stack. If the computer has a Joker, it will save it
* unless it cannot play any other card. The computer will always try to play
* the Joker on a stack that has a value identical to another stack if one
* exists, or the middle stack if all stacks have different values.
*
* @param computer The cards in the computer's hand.
* @return Whether the computer was able to play a card or not.
*/
public static boolean computerTurn(Hand computer)
{
boolean hasJoker = false; // Whether or not the computer has a Joker.
int jokerIndex = ‐1; // Holds index of Joker.
// Determine which stacks the card can be played on.
for (int i = 0; i < computer.getNumCards(); i++)
{
// Save Jokers.
if (computer.inspectCard(i).getValue() == 'X')
{
jokerIndex = i;
hasJoker = true;
} else
{
determinePlayableStacks(computer.inspectCard(i));
// Search stacks to see if card can be played on it.
for (int j = 0; j < NUM_STACKS; j++)
{
// Play card if stack is valid.
if (playableStacks[j])
{
if (!(addToStack(computer.playCard(i), j)))
{
System.exit(1);
} else
{
return true;
}
}
}
}
}
// If computer has a Joker, plays it on a stack that has a value
// identical to another stack.
if (hasJoker)
{
for (int k = 0; k < NUM_STACKS; k++)
{
Card stackCard1 = stacks[k].inspectCard(stacks[k].getTopCard());
for (int m = 0; m < NUM_STACKS; m++)
{
Card stackCard2 = stacks[m].inspectCard(stacks[m].getTopCard());
// Check if stack card is equal to another stack card.
if (k != m && stackCard1.equals(stackCard2))
{
if (!(addToStack(computer.playCard(jokerIndex), k)))
{
System.exit(1);
} else
{
return true;
}
}
}
}
// If no stacks are identical, play on the middle stack.
if (!(addToStack(computer.playCard(jokerIndex), 1)))
{
System.exit(1);
} else
{
return true;
}
}
return false;
}
/**
* determinePlayableStacks takes a card parameter and determines which stacks
* it can be played on. A card can be played on a stack if its value is 1
* value higher or lower than the card value on the top of the stack. Jokers
* can be played on any card and any card can be played on Jokers.
*
* @param card The card to be checked against the stacks.
*/
public static void determinePlayableStacks(Card card)
{
// Value index of each stack's top card.
int[] stackTopCardVal = new int[NUM_STACKS];
for (int i = 0; i < NUM_STACKS; i++)
{
stackTopCardVal[i] = ‐1;
}
char[] valuesArr = Card.valueRanks();
int playableCardIndx = ‐1; // Holds value index of parameter card.
// Reset all stacks before testing which are valid.
resetPlayableStacks();
// If a stack is empty, it's playable to any card.
for (int i = 0; i < NUM_STACKS; i++)
{
if (stacks[i].getNumCards() == 0)
{
playableStacks[i] = true;
}
}
// Get the value indexes of each of the stacks' top card.
for (int i = 0; i < NUM_STACKS; i++)
{
for (int j = 0; j < 14; j++)
{
Card stackCard = stacks[i].inspectCard(stacks[i].getTopCard());
if (stackCard.getValue() == valuesArr[j])
{
stackTopCardVal[i] = j;
}
}
}
// Gets the value index of the card.
for (int j = 0; j < 14; j++)
{
if (card.getValue() == valuesArr[j])
{
playableCardIndx = j;
}
}
// Determine which stacks the card can be played on.
for (int i = 0; i < NUM_STACKS; i++)
{
if (playableCardIndx == (stackTopCardVal[i] ‐ 1)
|| playableCardIndx == (stackTopCardVal[i] + 1)
|| playableCardIndx == 0 || stackTopCardVal[i] == 0)
{
playableStacks[i] = true;
}
}
}
/**
* beginNextRound sets up the next round. If the players played a card, they
* are dealt a new one. Both hands are sorted, and the human's hand is
* updated to show the contents of their current hand. Then the number of
* passes for both players is updated. If both the human and the computer
* passed, then a new card is dealt onto each stack.
*
* @param game Holds human/computer's hands.
*/
public static void beginNextRound(CardGameFramework game)
{
// Add card to each stack if both player and computer passed and cards
// remain in the deck.
if (playerPassed && computerPassed)
{
for (int i = 0; i < NUM_STACKS; i++)
{
// End game if no cards remain to be dealt.
if (game.getNumCardsRemainingInDeck() > 0)
{
if (!(addToStack(game.getCardFromDeck(), i)))
{
System.exit(1);
}
}
}
}
// Deal cards into the hands if card was played and cards remain in deck.
if (game.getHand(0).getNumCards() < 7
&& game.getNumCardsRemainingInDeck() > 0)
{
game.takeCard(0);
}
if (game.getHand(1).getNumCards() < 7
&& game.getNumCardsRemainingInDeck() > 0)
{
game.takeCard(1);
}
// Sort hands.
game.sortHands();
// Set icons to reflect new hand.
for (int i = 0; i < game.getHand(1).getNumCards(); i++)
{
View.humanButtons[i]
.setIcon(Model.getIcon(game.getHand(1).inspectCard(i)));
}
// Show previous round score.
String humScore = "Human passes: " + playerPass;
String compScore = "Computer passes: " + computerPass;
View.humanScore.setText(humScore);
View.computerScore.setText(compScore);
// End game if no cards remain in the deck.
if (game.getNumCardsRemainingInDeck() == 0)
{
endGame();
}
// Reset all pass counters.
resetPassChecks();
}
/**
* addToStack adds a card to a particular stack. If the card is invalid, the
* method will not add the card to the stack and returns false.
*
* @param card Card to be added to the stack.
* @param stackIndex Stack card will be added to.
* @return Whether or not card was valid and added.
*/
public static boolean addToStack(Card card, int stackIndex)
{
if (card.getErrorFlag())
{
return false;
} else
{
// Add card to stack, update stack button icon.
Card cardToAdd = new Card(card);
stacks[stackIndex].addCard(cardToAdd);
View.stackCards[stackIndex].setIcon(Model.getIcon(cardToAdd));
return true;
}
}
/**
* resetPlayableStacks resets the stacks for testing. All stacks start as
* false, and will be set to true if a card is able to be played on them. The
* stack buttons will also be disabled.
*/
public static void resetPlayableStacks()
{
for (int i = 0; i < NUM_STACKS; i++)
{
playableStacks[i] = false;
View.stackCards[i].setEnabled(false);
}
}
/**
* resetPassChecks resets the variables associated with tracking if both
* players skipped in the same turn.
*/
public static void resetPassChecks()
{
playerPassed = false;
computerPassed = false;
}
/**
* endGame finishes the game once there are no cards left in the deck or both
* players passed their turns 5 times in a row. It determines a winner by
* counting who passed their turn more. Once a winner is decided, the play
* field is cleared (except for the timer) and a message shows who won the
* game.
*/
public static void endGame()
{
// Remove cards and player labels from play field.
for (int i = 0; i < NUM_STACKS; i++)
{
View.stackCards[i].setVisible(false);
View.stackLabels[i].setVisible(false);
}
// Remove pass turn button from playing field.
View.pass.setVisible(false);
// Determine who won the game. Display the result.
if (playerPass < computerPass)
{
View.endResult
.setText("You terminated the machine! Take that, Skynet.");
} else if (playerPass > computerPass)
{
View.endResult
.setText("Humans will never triumph. Bow before me, mortal.");
} else
{
View.endResult.setText("A tie. It seems we are equals...for now.");
}
}
/*
* The CardListener class is added as an action listener to the player's
* buttons that represent their cards. It plays a turn of the game each time
* the player clicks on one of their buttons.
*/
static class CardListener implements ActionListener
{
private JButton playedButton; // Button the user clicked.
private CardGameFramework cardGame; // Holds hand/deck info, methods.
private static Card playerCard;
// Constructor #1.
public CardListener(JButton button, CardGameFramework game)
{
playedButton = button;
cardGame = game;
}
/**
* actionPerformed activates on button click. It determines whether or not
* the selected card can be played on one of the three stacks in the play
* field. If the card can be played, the stacks that are valid will be
* active and allow the player to click on them.
*
* @param e A click event.
*/
@Override
public void actionPerformed(ActionEvent e)
{
Icon buttonIcon = playedButton.getIcon(); // Icon of pressed button.
int cardIndex = ‐1; // Holds index of card that matches icon.
// Find index in player's hand where clicked card is.
for (int i = 0; i < cardGame.getHand(1).getNumCards(); i++)
{
if (buttonIcon == Model.getIcon(cardGame.getHand(1).inspectCard(i)))
{
cardIndex = i;
}
}
// Exit if the card isn't found.
if (cardIndex < 0)
{
View.endResult.setText("Error. Card not found.");
System.exit(1);
}
// Set card to pass to next phase of game.
playerCard = cardGame.getHand(1).inspectCard(cardIndex);
// Player tries to play their card on a deck.
Controller.playerTurn(playerCard);
}
// Accessor for player card.
public static Card getPlayerCard()
{
return playerCard;
}
}// end CardListner class
/*
* The StackListener class is an action listener for the three stacks in the
* playing field. It allows the user to place a card on the stack.
*/
static class StackListener implements ActionListener
{
private int stackIndex;
private CardGameFramework game;
private Card cardToAdd;
public StackListener(int index, CardGameFramework game)
{
stackIndex = index;
this.game = game;
}
/**
* actionPerformed plays through the human player's turn by adding their
* selected card to the stack and updating the button icon to display that
* card. Once the human has played, then the computer takes its turn.
*
* @param e A click event.
*/
@Override
public void actionPerformed(ActionEvent e)
{
cardToAdd = CardListener.getPlayerCard();
// Adds card to stack and updates stack icon.
if (!(addToStack(cardToAdd, stackIndex)))
{
System.exit(1);
}
// Play the card from the hand to remove it.
for (int i = 0; i < Model.NUM_CARDS_PER_HAND; i++)
{
if (game.getHand(1).inspectCard(i).equals(cardToAdd))
{
game.playCard(1, i);
}
}
// If computer cannot play a card, it passes.
if (!(Controller.computerTurn(game.getHand(0))))
{
computerPass++;
computerPassed = true;
}
Controller.beginNextRound(game);
}
}
/*
* The PassListener class is an action listener for the pass button. It skips
* the player's turn when clicked.
*/
static class PassListener implements ActionListener
{
private CardGameFramework game; // Holds instance of computer's hand.
// Default constructor.
public PassListener(CardGameFramework game)
{
this.game = game;
}
/**
* actionPerformed skips the player's turn. It increments the number of
* times the human player has passed, and then lets the computer take its
* turn.
*
* @param e A click event.
*/
@Override
public void actionPerformed(ActionEvent e)
{
// Skip the player's turn.
playerPass++;
playerPassed = true;
// If computer cannot play a card, it passes.
if (!(Controller.computerTurn(game.getHand(0))))
{
computerPass++;
computerPassed = true;
}
Controller.beginNextRound(game);
}
}
/*
* The TimerListener class is an action listener for the timer button. When
* the timer button is toggled, it will stop and start the timer.
*/
static class TimerListener implements ActionListener
{
private Model.Timer timerThread = new Model.Timer();
// Default Constructor.
public TimerListener()
{
View.timerButton.setText("Stop");
timerThread.start();
}
/**
* actionPerformed determines whether or not the timer button is toggled.
* If it is toggled, the timer is stopped. If it isn't toggled, the timer
* will resume. The button text updates on each click.
*
* @param e A click event.
*/
@Override
public void actionPerformed(ActionEvent e)
{
if (View.timerButton.isSelected())
{
View.timerButton.setText("Start");
timerThread.setIsRunning(false);
} else
{
View.timerButton.setText("Stop");
timerThread.setIsRunning(true);
}
}
}
}
// begin CardGameFramework class
class CardGameFramework
{
private static final int MAX_PLAYERS = 50;
private int numPlayers;
private int numPacks; // # standard 52‐card packs per deck
// ignoring jokers or unused cards
private int numJokersPerPack; // if 2 per pack & 3 packs per deck, get 6
private int numUnusedCardsPerPack; // # cards removed from each pack
private int numCardsPerHand; // # cards to deal each player
private Deck deck; // holds the initial full deck and gets
// smaller (usually) during play
private Hand[] hand; // one Hand for each player
private Card[] unusedCardsPerPack; // an array holding the cards not used
// in the game. e.g. pinochle does not
// use cards 2‐8 of any suit
public CardGameFramework(int numPacks, int numJokersPerPack,
int numUnusedCardsPerPack, Card[] unusedCardsPerPack, int numPlayers,
int numCardsPerHand)
{
int k;
// filter bad values
if (numPacks < 1 || numPacks > 6)
{
numPacks = 1;
}
if (numJokersPerPack < 0 || numJokersPerPack > 4)
{
numJokersPerPack = 0;
}
if (numUnusedCardsPerPack < 0 || numUnusedCardsPerPack > 50)
{
// card
numUnusedCardsPerPack = 0;
}
if (numPlayers < 1 || numPlayers > MAX_PLAYERS)
{
numPlayers = 4;
}
// one of many ways to assure at least one full deal to all players
if (numCardsPerHand < 1 || numCardsPerHand > numPacks
* (52 ‐ numUnusedCardsPerPack) / numPlayers)
{
numCardsPerHand = numPacks * (52 ‐ numUnusedCardsPerPack) / numPlayers;
}
// allocate
this.unusedCardsPerPack = new Card[numUnusedCardsPerPack];
this.hand = new Hand[numPlayers];
for (k = 0; k < numPlayers; k++)
{
this.hand[k] = new Hand();
}
deck = new Deck(numPacks);
// assign to members
this.numPacks = numPacks;
this.numJokersPerPack = numJokersPerPack;
this.numUnusedCardsPerPack = numUnusedCardsPerPack;
this.numPlayers = numPlayers;
this.numCardsPerHand = numCardsPerHand;
for (k = 0; k < numUnusedCardsPerPack; k++)
{
this.unusedCardsPerPack[k] = unusedCardsPerPack[k];
}
// prepare deck and shuffle
newGame();
}
// constructor overload/default for game like bridge
public CardGameFramework()
{
this(1, 0, 0, null, 4, 13);
}
public Hand getHand(int k)
{
// hands start from 0 like arrays
// on error return automatic empty hand
if (k < 0 || k >= numPlayers)
{
return new Hand();
}
return hand[k];
}
public Card getCardFromDeck()
{
return deck.dealCard();
}
public int getNumCardsRemainingInDeck()
{
return deck.getNumCards();
}
private void newGame()
{
int k, j;
// clear the hands
for (k = 0; k < numPlayers; k++)
{
hand[k].resetHand();
}
// restock the deck
deck.init(numPacks);
// remove unused cards from the deck
for (k = 0; k < numPacks; k++)
{
for (j = 0; j < numUnusedCardsPerPack; j++)
{
deck.removeCard(unusedCardsPerPack[j]);
}
}
// add jokers
for (k = 0; k < numPacks; k++)
{
for (j = 0; j < numJokersPerPack; j++)
{
deck.addCard(new Card('X', Card.Suit.values()[j]));
}
}
// shuffle the cards
deck.shuffle();
}
public boolean deal()
{
// returns false if not enough cards, but deals what it can
int k, j;
boolean enoughCards;
// clear all hands
for (j = 0; j < numPlayers; j++)
{
hand[j].resetHand();
}
enoughCards = true;
for (k = 0; k < numCardsPerHand && enoughCards; k++)
{
for (j = 0; j < numPlayers; j++)
{
if (deck.getNumCards() > 0)
{
hand[j].takeCard(deck.dealCard());
} else
{
enoughCards = false;
break;
}
}
}
return enoughCards;
}
public void sortHands()
{
int k;
for (k = 0; k < numPlayers; k++)
{
hand[k].sort();
}
}
public Card playCard(int playerIndex, int cardIndex)
{
// returns bad card if either argument is bad
if (playerIndex < 0 || playerIndex > numPlayers ‐ 1 || cardIndex < 0
|| cardIndex > numCardsPerHand ‐ 1)
{
// Creates a card that does not work
return new Card('M', Card.Suit.SPADES);
}
// return the card played
return hand[playerIndex].playCard(cardIndex);
}
public boolean takeCard(int playerIndex)
{
// returns false if either argument is bad
if (playerIndex < 0 || playerIndex > numPlayers ‐ 1)
{
return false;
}
// Are there enough Cards?
if (deck.getNumCards() <= 0)
{
return false;
}
return hand[playerIndex].takeCard(deck.dealCard());
}
}
/*
* The Card class stores a char value which represents the value of a card
* (e.g., 'A', '2', '3'), as well as an Enum value which represents the suit
* (e.g., CLUBS, DIAMONDS). It contains accessors and a mutator, and functions
* to convert the card values to a String for output to the user. It also
* contains get and set functions, and the set function will test for valid
* input. The card class is utilized by both the Hand and Deck classes to
* instantiate Card objects.
*/
class Card
{
private char value;
private Suit suit;
private boolean errorFlag = false;
public enum Suit
{
CLUBS, DIAMONDS, HEARTS, SPADES;
}
// Default constructor initializes values to 'A' and 'SPADES'.
public Card()
{
set('A', Suit.SPADES);
}
// Constructor with all parameters.
public Card(char value, Suit suit)
{
set(value, suit);
}
// Copy constructor.
public Card(Card card)
{
set(card.getValue(), card.getSuit());
}
// Accessor for value.
public char getValue()
{
return value;
}
// Accessor for enum variable suit, returns suit enum.
public Suit getSuit()
{
return suit;
}
// Accessor for errorFlag, returns boolean value of errorFlag.
public boolean getErrorFlag()
{
return errorFlag;
}
/**
* toString takes a char value representing the card value (2, 3, T etc.) and
* converts it to a string. It does the same with a suit value, and then
* concatenates them. It then returns the concatenated string as cardString.
* Returns error message if errorFlag is set.
*
* @return String with the card value and suit.
*/
@Override
public String toString()
{
if (errorFlag == true)
{
return "Invalid Input";
}
String charToString = new Character(value).toString();
String enumToString = suit.toString();
// Concatenate value and suit.
String cardString = charToString + " of " + enumToString;
return cardString;
}
/**
* isValid tests for validity for a char value ('A', '2', '3', etc.) if the
* input is valid, it returns true; if invalid, it returns false. This method
* takes both a char and enum Suit value, but doesn't check Suit.
*
* @param value Card value to check for validity.
* @param suit Card suit to check for validity.
* @return Whether card is valid or not.
*/
private boolean isValid(char value, Suit suit)
{
Character compareChar = new Character(value);
if (compareChar.equals('X') || compareChar.equals('A')
|| compareChar.equals('2') || compareChar.equals('3')
|| compareChar.equals('4') || compareChar.equals('5')
|| compareChar.equals('6') || compareChar.equals('7')
|| compareChar.equals('8') || compareChar.equals('9')
|| compareChar.equals('T') || compareChar.equals('J')
|| compareChar.equals('Q') || compareChar.equals('K'))
{
return true;
}
return false;
}
/**
* set is a mutator for the char variables value and suit. It takes both a
* char value and an enum Suit value and tests both for validity using the
* method isValid, and sets the values if values are valid, and returns true,
* while setting errorFlag to false. If they are not valid, it sets the
* errorFlag to true, and returns false.
*
* @param value To set value field.
* @param suit To set suit field.
* @return Whether parameters were valid or not.
*/
public boolean set(char value, Suit suit)
{
if (isValid(value, suit))
{
this.value = value;
this.suit = suit;
errorFlag = false;
return true;
}
errorFlag = true;
return false;
}
/**
* equals compares the values of the calling object and the Card class object
* passed into the method and returns true if all the values are equal, and
* false otherwise.
*
* @param card Card object to compare against this card's fields.
* @return Whether or not the cards have the same values.
*/
public boolean equals(Card card)
{
// Convert char values to strings to allow use of equals() method.
String charToStringThis = new Character(value).toString();
String charToStringInput = new Character(card.value).toString();
if (charToStringThis.equals(charToStringInput) && suit.equals(card.suit)
&& errorFlag == card.errorFlag)
{
return true;
}
return false;
}
/**
* valueRanks creates a char array that holds the values of all cards from
* lowest to highest, with Joker as the lowest and King as the highest.
*
* @return Char array holding the values in order from lowest to highest.
*/
public static char[] valueRanks()
{
char[] valArr = new char[14]; // Array to hold all values.
// Sets values in array based on their ranking.
valArr[0] = 'X';
valArr[1] = 'A';
valArr[2] = '2';
valArr[3] = '3';
valArr[4] = '4';
valArr[5] = '5';
valArr[6] = '6';
valArr[7] = '7';
valArr[8] = '8';
valArr[9] = '9';
valArr[10] = 'T';
valArr[11] = 'J';
valArr[12] = 'Q';
valArr[13] = 'K';
return valArr;
}
/**
* arraySort uses a bubble sort to place an array of cards in order from
* lowest to highest. It uses the private helper function shouldSwap to
* determine whether or not to swap two adjacent cards.
*
* @param cardArr Card array to be sorted.
* @param arraySize Size of card array.
*/
public static void arraySort(Card[] cardArr, int arraySize)
{
Card temp = null; // To hold value for swapping.
// Goes through array until all values are sorted.
for (int i = 0; i < arraySize ‐ 1; i++)
{
// Scans array to determine whether adjacent values should be swapped.
for (int j = 0; j < arraySize ‐ i ‐ 1; j++)
{
// Determines if cards should be swapped, then swaps when necessary.
if (shouldSwap(cardArr[j], cardArr[j + 1]))
{
temp = new Card(cardArr[j + 1]);
cardArr[j + 1].set(cardArr[j].getValue(), cardArr[j].getSuit());
cardArr[j].set(temp.getValue(), temp.getSuit());
temp = null;
}
}
}
}
/**
* shouldSwap determines which value between two cards is lowest. If the
* second card has a lower value than the first, the two cards should be
* swapped. Private helper function for arraySort.
*
* @param card1 First card to test.
* @param card2 Second card to test.
* @return Whether or not to swap the two cards.
*/
private static boolean shouldSwap(Card card1, Card card2)
{
char[] valuesArr = valueRanks(); // Holds card value rank, lowest‐highest.
int cardValue1 = ‐1, cardValue2 = ‐1; // Values of two cards to compare.
// Finds index of card values in ranked array.
for (int i = 0; i < 14; i++)
{
if (card1.getValue() == valuesArr[i])
{
cardValue1 = i;
}
if (card2.getValue() == valuesArr[i])
{
cardValue2 = i;
}
}
// If the value of the second card is smaller than the first, then they
// should be swapped.
if (cardValue2 < cardValue1)
{
return true;
} else
{
return false;
}
}
}
/*
* The Hand class allows the user to hold up to 50 Card objects in a Card array.
* It includes a variable to check how many cards are in the hand, as well as a
* constant to limit the number of cards in the array. Its methods allow it to
* reset the hand so there are no cards, add a card to the array, play the top
* card in the hand, display the entire hand, and view a specific card in the
* hand, in addition to the usual accessor methods. When a Hand object is
* created, it starts with no cards in the hand. Only through taking a valid
* Card object from a client source will it be able to add new cards to the
* array. The hand can be decreased or emptied using either the playCard or
* resetHand methods.
*/
class Hand
{
private Card[] myCards; // Holds array of cards that represent a hand.
private int numCards; // Holds the number of cards in a hand.
public static final int MAX_CARDS = 50; // Prevents over‐large hands.
// Default constructor creates an empty hand with a max limit of 50.
public Hand()
{
numCards = 0;
myCards = new Card[MAX_CARDS];
}
// Accessor for numCards.
public int getNumCards()
{
return numCards;
}
/**
* resetHand removes all cards from the hand by resetting the number of cards
* to 0.
*/
public void resetHand()
{
numCards = 0;
}
/**
* takeCard adds the card to the top of the array if it is valid, then
* increments the number of cards in the array.
*
* @param card The card to be added to array.
* @return Whether parameter card was added or not.
*/
public boolean takeCard(Card card)
{
// Checks for error flag before adding card to hand.
if (card.getErrorFlag())
{
return false;
} else
{
// Creates new card and sets value to parameter card.
if (numCards < MAX_CARDS)
{
Card addCard = new Card(card.getValue(), card.getSuit());
// Adds card to array at empty index. Increases number of cards.
myCards[numCards] = addCard;
numCards++;
return true;
} else
{
return false;
}
}
}
/**
* playCard allows a player to select a specific card in their hand and play
* it. When the card is played, it is removed from the hand and the number of
* cards is decremented.
*
* @param cardIndex Index of the card to be played.
* @return Card to be played or card with error flag set if index is invalid.
*/
public Card playCard(int cardIndex)
{
// Returns invalid card if there are no cards left in the hand.
if (numCards < 1)
{
return new Card('M', Suit.DIAMONDS);
}
// Sets card to be played.
Card card = myCards[cardIndex];
numCards‐‐;
// Shifts hand to "remove" card.
for (int i = cardIndex; i < numCards; i++)
{
myCards[i] = myCards[i + 1];
}
myCards[numCards] = null;
return card;
}
/**
* Converts the contents of the entire hand into a string to be returned.
*
* Uses newlines to keep output within console window, with commas to clearly
* distinguish between cards. Additional formatting handled by client.
*
* @return String containing the contents of the entire hand.
*/
@Override
public String toString()
{
String wholeHand = ""; // To hold contents of hand.
// Adds card values to the string.
for (int i = 0; i < numCards; i++)
{
if (i == numCards ‐ 1)
{
wholeHand += myCards[i].toString();
}
// Formats hand to keep whole string from printing on a single line.
else
{
// Adds newline, except at beginning.
if (i % 5 == 0 && i != 0)
{
wholeHand += "\n";
}
wholeHand += myCards[i].toString() + ", ";
}
}
return wholeHand;
}
/**
* Accessor for an individual card. Checks if index is valid, then returns
* either a card with an error flag or the requested card.
*
* @param k Index of card to access.
* @return Card user requested or card w/ error flag.
*/
public Card inspectCard(int k)
{
// Returns card with error flag set if index is invalid.
if (k < 0 || k >= numCards)
{
return new Card('M', Suit.DIAMONDS);
} else
{
return new Card(myCards[k]);
}
}
// Sort the hand by calling the arraySort() method in the Card class.
public void sort()
{
Card.arraySort(myCards, numCards);
}
}
/*
* The Deck class will create two decks of cards. It creates a master copy deck
* and a working copy deck. When a new deck is copied from the 56‐card master
* deck, the copied deck can then be shuffled. The new deck can also be dealt to
* a hand. No cards are lost in the shuffle. A copy of the master may have
* between one and six packs of cards (MAX_CARDS = 312), but not more. If one
* master deck has already been created, then another master deck will not be
* created.
*/
class Deck
{
public static final int MAX_CARDS = 336; // Set maximum size (6×56 cards).
private static Card[] masterPack; // The unchanging pack of card objects.
private Card[] cards; // The deck that we create using the master.
private int topCard; // The index of the top card of the array.
public static int numPacks; // The number of packs to be added to deck.
private int numCards; // Number of cards remaining in the deck.
// Default constructor.
public Deck()
{
this(1); // If number of packs is not specified, default to one pack.
}
// Constructor #1. Sets topCard/numCards to a 52‐card deck, not including
// Jokers.
public Deck(int numPacks)
{
Deck.numPacks = numPacks;
topCard = numPacks * 52 ‐ 1;
numCards = numPacks * 52;
allocateMasterPack(); // Generate the Master Pack.
}
// Accessor for numCards.
public int getNumCards()
{
return numCards;
}
// Accessor for topCard.
public int getTopCard()
{
return topCard;
}
/**
* init will instantiate a Deck object and also find out the array index of
* the top card on the deck. The number of packs is multiplied by 56 and the
* result is the number of cards in the new deck. The integer value of the
* top card is set by using the number of cards minus 1.
*
* @param numPacks Number of packs to be included in the deck.
*/
public void init(int numPacks)
{
numCards = 52 * numPacks;
topCard = numCards ‐ 1;
// Exit if deck has too many cards.
if (numCards > MAX_CARDS)
{
System.exit(1);
} else
{
cards = new Card[numPacks * 56];
int i = 0; // Index for cards array.
// Loops through card and master pack arrays to initialize card array.
while (i < numCards)
{
// Gets all 52 cards from master pack and copies them into cards
// array. Does not include Jokers.
for (int j = 0; j < 52; j++)
{
cards[i] = new Card();
cards[i].set(masterPack[j].getValue(), masterPack[j].getSuit());
i++; // Next index in card array.
}
}
}
}
/**
* shuffle will shuffle the deck using a random number generator. It will
* shuffle the deck even if some cards have already been played.
*/
public void shuffle()
{
Card tempCard; // Holds card for swap.
int randCard; // Holds random number to be used as an index.
Random randPick = new Random(); // Holds random array index.
// Swap the top card with the randomly selected card.
while (topCard != 0)
{
randCard = randPick.nextInt(topCard + 1);
tempCard = cards[topCard];
cards[topCard] = cards[randCard];
cards[randCard] = tempCard;
topCard = topCard ‐ 1;
}
topCard = numCards ‐ 1; // Reset the top card index position.
}
/**
* dealCard returns and removes the card in the top occupied position of
* cards[]. It also makes sure there are still cards available.
*
* @return Card to be dealt or card with error flag set if no cards remain.
*/
public Card dealCard()
{
// Return card with error flag when no cards remain.
if (topCard < 0)
{
return new Card('M', Suit.DIAMONDS);
// Set card to be dealt, decrement the deck.
} else
{
Card dealtCard = cards[topCard];
topCard ‐= 1;
numCards ‐= 1;
return dealtCard;
}
}
/**
* inspectCard is an accessor for an individual card. If the parameter index
* is invalid, it returns a card with the error flag set to true. card with
* errorFlag = true if k is bad.
*
* @param k Index of card to be accessed.
* @return Card at index or card with error flag set on invalid index.
*/
public Card inspectCard(int k)
{
// Returns card with error flag set if index is invalid.
if (k < 0 || k >= numPacks * 56)
{
return new Card('M', Suit.DIAMONDS);
} else
{
return new Card(cards[k]);
}
}
/**
* allocateMasterPack creates a single master deck that all other decks will
* be instantiated from. The master deck will not be instantiated more than
* one time while the program runs. The master deck holds the 52 cards found
* in a standard deck, plus leave the last 4 elements uninitialized to hold
* any Jokers that might be added later.
*/
private static void allocateMasterPack()
{
// Return if masterPack already exists.
if (masterPack != null)
{
return;
}
masterPack = new Card[56]; // Instantiate a new Card object array.
// Sets 13 cards values in each of the 4 suits.
for (int deckSize = 0; deckSize < masterPack.length; deckSize++)
{
masterPack[deckSize] = new Card();
// Set the suit of the card.
Suit suit;
if (deckSize % 4 == 0)
{
suit = Suit.SPADES;
} else if (deckSize % 4 == 1)
{
suit = Suit.DIAMONDS;
} else if (deckSize % 4 == 2)
{
suit = Suit.HEARTS;
} else
{
suit = Suit.CLUBS;
}
// Set the value of card.
int switchValue = deckSize % 13;
switch (switchValue)
{
case 0:
masterPack[deckSize].set('A', suit);
break;
case 1:
masterPack[deckSize].set('2', suit);
break;
case 2:
masterPack[deckSize].set('3', suit);
break;
case 3:
masterPack[deckSize].set('4', suit);
break;
case 4:
masterPack[deckSize].set('5', suit);
break;
case 5:
masterPack[deckSize].set('6', suit);
break;
case 6:
masterPack[deckSize].set('7', suit);
break;
case 7:
masterPack[deckSize].set('8', suit);
break;
case 8:
masterPack[deckSize].set('9', suit);
break;
case 9:
masterPack[deckSize].set('T', suit);
break;
case 10:
masterPack[deckSize].set('J', suit);
break;
case 11:
masterPack[deckSize].set('Q', suit);
break;
case 12:
masterPack[deckSize].set('K', suit);
break;
}
}
}
/**
* addCard will add a card to the top of the deck. If there are too many
* instances of the card in the deck (more versions of the card than number
* of decks), then the method returns false.
*
* @param card Card to be added to the top of the deck.
* @return Whether or not card was added.
*/
public boolean addCard(Card card)
{
int numOfCardCopies = 0; // Number of copies of a single card in the deck.
// Checks that there are not too many copies of the card to be added.
for (int i = 0; i < numCards; i++)
{
if (cards[i].equals(card))
{
numOfCardCopies++;
}
}
// Will not add card if there are too many copies or deck is full.
if (numOfCardCopies >= numPacks || numCards >= numPacks * 56)
{
return false;
}
// Add a copy of the card to the top of the deck.
numCards++;
topCard++;
cards[topCard] = new Card(card);
return true;
}
/**
* removeCard will remove a designated card from the deck. If the card is
* found in the deck, it is replaced by the current top card.
*
* @param card Card to be removed.
* @return Whether or not the card was removed.
*/
public boolean removeCard(Card card)
{
// Checks to see if the card is in the deck. If it is, replace it with
// the top card.
for (int i = 0; i < numCards; i++)
{
if (cards[i].equals(card))
{
cards[i] = cards[topCard];
// Decrease deck size.
numCards‐‐;
topCard‐‐;
return true;
}
}
return false;
}
/**
* sort will sort all of the cards in the deck so they are ordered in value
* from lowest to highest, with Joker as the lowest (or Ace if there are no
* Jokers), and King as the highest.
*/
public void sort()
{
Card.arraySort(cards, numCards);
}
}