You are on page 1of 40

/*

 * 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);
   }
}

You might also like