CPSC 124, Winter 1998
Sample Answers to Lab 8


This page contains sample answers to some of the exercises from Lab #8 in CPSC 124: Introductory Programming, Winter 1998. See the information page for that course for more information.


Exercise 1: Here is a modification of the original CardsApplet.java file that uses a two-dimensional array of cards. Additions and modifications to the original file are shown in blue.

        /*
           This applet lets the user play a simple card game.
           The playing board has five rows and five columns
           Each space in this grid can hold a card.  The user
           sees one card from the deck at a time, and places
           it on the board by clicking on one of the empty
           spaces on the board.  The goal is to make good
           poker hands in each row and column.  (Note that
           the applet size MUST be 450 by 330 for this to
           work properly.)
        */


        import java.awt.*;
        import java.applet.Applet;

        public class TenWayPokerSolitaire extends Applet {

           Image cardImages;   // This Image will contain pictures of all 52 cards.
                               // It is loaded from a file "smallcards.gif" which
                               // must be in the same directory as the applet class file.

           Deck deck;     // A deck of playing cards to be used in the game.

           Card nextCard; // The card most recently dealt from the deck.

           Card[][] board;     // Cards on the playing board.  (Where no card
                               // is played, there is a null.)

           int cardsPlayed;    // Number of cards that have been placed on the
                               // board. when this becomes 25, the game is over.

           boolean gameOver;   // This is set to false when a game begins and
                               // to true when it ends.  (The appearance of the
                               // applet and the legal actions are different,
                               // depending on whether the game is over or not.) 

           Font bigFont;  // a large-size font.


           public void init() {
                 // Called by system when the applet is create;
                 // initialize instance variables and start a game.
              setBackground(Color.lightGray);
              bigFont = new Font("Helvetica",Font.ITALIC,24);
              cardImages = getImage(getCodeBase(), "smallcards.gif");
              deck = new Deck();
              board = new Card[5][5];
              startGame();
           }


           void startGame() {
                  // Begin a new game.
              deck.shuffle();
              nextCard = deck.dealNextCard();
              for (int row = 0; row < 5; row++)
                 for (int col = 0; col < 5; col++)
                    board[row][col] = null;
              cardsPlayed = 0;
              gameOver = false;
           }


           void playCard(int row, int col) {
                  // This is called when the user clicks on on the
                  // playing board.  The parameters are numbers
                  // between 0 adnd 4 that tell which position in
                  // the grid was clicked on.
                  //   This method places the next card on the
                  // playing board at the specified position and
                  // deals a new card.
              if (board[row][col] == null) {
                 board[row][col] = nextCard;
                 Graphics g = getGraphics();
                 putCard(g, nextCard, 10 + 50*col, 5 + 65*row);
                 nextCard = deck.dealNextCard();
                 putCard(g, nextCard, 330, 155);
                 g.dispose();
                 cardsPlayed++;
                 if (cardsPlayed == 25) {
                    gameOver = true;
                    repaint(270,100,150,180);
                 }
              }
           }


           public void paint(Graphics g) {  // repaint the applet

              for (int row = 0; row < 5; row++)
                for (int col = 0; col < 5; col++) {
                   putCard(g, board[row][col], 10 + 50*col, 5+65*row);
                }

              g.setColor(Color.white);
              g.fillRect(260,5,180,320);
              g.setColor(Color.black);
              g.drawRect(260,5,179,319);
              g.setColor(Color.blue);

              g.drawString("Try to place the cards",270,20);
              g.drawString("to make good Poker hands",270,35);
              g.drawString("across each row and down",270,50);
              g.drawString("each column.  Just click",270,65);
              g.drawString("on the blank spot where",270,80);
              g.drawString("you want to put the card.",270,95);

              g.setColor(Color.lightGray);
              g.fillRoundRect(280,255,140,40,20,20);
              g.setColor(Color.red);
              g.drawRoundRect(280,255,140,40,20,20);
              g.drawString("Click to Restart",315,280);

              if (gameOver) {
                 g.setFont(bigFont);
                 g.setColor(Color.red);
                 g.drawString("Game Over",285,180);
              }
              else {
                 g.setColor(Color.blue);
                 g.drawString("Next card:",270,140);
                 putCard(g, nextCard, 330, 155);
              }

           }


           public boolean mouseDown(Event evt, int x, int y) {
                 // If user clicks in the "restart" area, start
                 // a new game.  If the user clicks on the playing
                 // board area, call playCard().
              if (x > 280 && x < 420 && y > 255 && y < 295) { // in restart area
                 startGame();
                 repaint();
              }
              else {
                 int col = (x-5) / 50;  // user clicked on the playing board
                 int row = (y-3) / 65;  //    if 0 <= row <= 4 and 0 <= col <= 4.
                 if (row >= 0 && row < 5 && col >= 0 & col < 5)
                    playCard(row,col);
              }
              return true;
           }


           void putCard(Graphics g, Card c, int x, int y) {
                  // Draws the specified card with its upper left-hand
                  // corner at (x,y).  A card occupies a 40-by-60
                  // rectangle.  This method will only work with the
                  // card images in the file smallcards.gif.  If that
                  // file is not available, it won't draw anything.
              if (c == null)
                 putBlank(g,x,y);  // to be safe, if c is null just draw a blank
              else {
                Graphics clipped = g.create(x,y,40,60);
                clipped.drawImage(cardImages,-40*c.getValue(),-60*c.getSuit(),this);
              }
           }


           void putBlank(Graphics g, int x, int y) {
                  // Draws a white rectangle with a blue border,   The rectangle
                  // is the same size as a Card.  Its upper left corner is at (x,y).
              g.setColor(Color.white);
              g.fillRect(x,y,40,60);
              g.setColor(Color.blue);
              g.drawRect(x,y,39,59);
           }

        } // end TenWayPokerSolitaire

Exercise 2: To support "kaleidascopic" drawing, the doDrawLine() in the original KSCanvas.java file just has to be changed to:

           void doDrawLine(int x1, int y1, int x2, int y2) {

                     // User has dragged mouse from (x1,y1) to (x2,y2).  If kaleidascopic
                     // is false, this just draws a line from (x1,y1) to (x2,y2).  If
                     // kaleidascopic is false, it draws that line plus all its horizontal,
                     // vertical, and diagonal reflections.  (Each line is drawn by a
                     // call to basicPutLine(), which is defined below.)

              basicPutLine(x1,y1,x2,y2);  // Always draw the line from (x1,y1) to (x2,y2).

              if (kaleidascopic) { // Draw the other seven reflected lines

                 basicPutLine(width-x1,y1,width-x2,y2);     // Draw horizontal/vertical reflections
                 basicPutLine(x1,height-y1,x2,height-y2);
                 basicPutLine(width-x1,height-y1,width-x2,height-y2);

                 int a1 = ( y1 * width ) / height;    // Endpoints of diagonally reflected line
                 int b1 = ( x1 * height ) / width;
                 int a2 = ( y2 * width ) / height;
                 int b2 = ( x2 * height ) / width;

                 basicPutLine(a1,b1,a2,b2);                   // Draw diagonally reflected line
                 basicPutLine(width-a1,b1,width-a2,b2);       //    and its horizontal/vertical
                 basicPutLine(a1,height-b1,a2,height-b2);     //    reflections.
                 basicPutLine(width-a1,height-b1,width-a2,height-b2);

              }  // end if

           }  // end of doDrawline()

Exercise 3: There were two errors in the original PrimeFinder.java file. First of all, the line

      console.putln("The " + NUMBER_TO_FIND + "-th prime is " + primes[NUMBER_TO_FIND]);

should read

      console.putln("The " + NUMBER_TO_FIND + "-th prime is " + primes[NUMBER_TO_FIND - 1]);

Using the number NUMBER_TO_FIND as an index for the array, primes, causes an array-index-out-of-bounds error. The legal range of indices for this array is from 0 to NUMBER_TO_FIND - 1. The intention was to print out the last item in the array, which is the NUMBER_TO_FIND-th prime. But the last item in the array is at index NUMBER_TO_FIND - 1, not at index NUMBER_TO_FIND.

The second error is in the isPrime() method. The line that reads

      while (p < primeCt && primes[p] < S) {

should read

      while (p < primeCt && primes[p] <= S) {

The smallest divisor of N can be anything up to and including Math.sqrt(N). For example, the non-prime number N = 49, is divisible by 7, which is the square root of 49. However, the original version of isPrime() failed to test whether 49 is divisible by 7, and so it ended up counting 49 as prime. It makes the same mistake for any number that is the square of a prime. The fixed version makes the correct check.

When the fixed version is run with NUMBER_TO_FIND = 100000, it finds that 1,299,709 is the 100,000-th prime number.

The exercise asks you to explain how you went about debugging the program. This is a somewhat individual matter. I would start by putting some output statements in the program. It would soon be clear that the program runs up to the point just before the offending console.putln statement, but that it never gets past this statement. The assumption, then, is that something in the statement caused the program to crash. I would examine the statement and hopefully realize that there is an index-out-of-bounds error. Any time you see an array reference, you should be aware of this possibility. (If you run the program in the debugger in Developer Studio, it will tell you that the program crashed because of an index-out-of-bounds error.)

Once the first error is fixed, the program runs. However, as stated in the lab, it gives incorrect results. When you look at the output, you can see that it counts 4 as being a prime, even though it is in fact divisible by 2. You have to figure out why the program thinks 4 is prime. The place to look is in the isPrime() method, where primality is checked. Running the program in the debugger and stepping through the method when N = 4 can be useful. You could also put a line such as

      console.putln("Checking " + N + " for divisibility by " + primes[p]);

into the while loop in the isPrime() method. This will soon tell you that 4 is never checked for divisibility by 2. (You could add a "console.getln();" statement to make the program stop and wait for you to press return after printing out the message. Or you could set NUMBER_TO_FIND equal to 3 or 4 so you don't get too much output.


David Eck, 9 March 1998