Saturday, May 24, 2014

Android BlackJack: Getting Individual Cards

Android BlackJack: Getting Individual Cards

Android BlackJack: Getting Individual Cards

Introduction

Previously code was added in to the MainActivity class to diplay the full Bitmap for the card pictures. Now we want to get at one of the individual cards in that Bitmap and display it. This will ultimately become the code needed to display an entire hand as the game progresses. The Bitmap class has some methods that are similar to what was used in regular Java. Let's adapt the classes used in the regular Java program to take advantage of the new Android classes and methods

CardImageFactory

CardImageFactory was responsible for getting individual card images out of the larger bitmap picture of an entire deck of cards. Under Android the Bitmap class is used to manipulate these pictures. Looking at the code for Bitmaps from the Android documentation site, the Bitmap class has methods similar to the ImageBuffer class in standard Java. If we rewrite CardImageFactory class to use the appropriate Android classes then the PlayingCard class can have a Bitmap based method to return the bitmap of the particular card we want displayed. The code is fairly straight forward and changes were made to the initCardImageFactory method and the makeCardIcon method.

CardImageFactory

public class CardImageFactory {
           private static final int SUIT_COUNT = 4;
           private static final int RANK_COUNT = 13;
           private  static Bitmap fullDeckImg = null;
           public static   int width = 0;
           public static   int height = 0;

            /**
            * Initialized the .png image in memory and sets up width and height 
            * readying size parameters to cut the image up into icons. This also
            * isolates IO exceptions to this method so the individual calls 
            * do not have to have exception handling.
            * 
            * @param pathToDeck the http address of the full card image file
            * @throws MalformedURLException
            * @throws IOException 
            */
           public static void initCardImageFactory(Resources res, int resId) {
              if (fullDeckImg == null) {
                  final BitmapFactory.Options options = new BitmapFactory.Options();
                  // options.inJustDecodeBounds = true;
                  fullDeckImg = BitmapFactory.decodeResource(res, resId, options);
                  height = options.outHeight;
                  width = options.outWidth;

                  // width = fullDeckImg.getWidth();
                  // height = fullDeckImg.getHeight();
              }
           }

           /**
            * For the structure of the current Black Jack program this returns 
            * individual card images instead of a full list. This allows you to 
            * develop a class hierarchy where the image is stored at the individual
            * PlayingCard object level.
            * 
            * @param rank card face value number 0 - 12
            * @param suit suits 0 - 4
            * @return 
            */
           public static Bitmap makeCardIcon(int rank, int suit) {
                    int x = (rank * width) / PlayingCard.RANK;
                    int y = (suit * height) / PlayingCard.SUIT;
                    int w = width / PlayingCard.RANK;
                    int h = height / PlayingCard.SUIT;
                    //return fullDeckImg.getSubimage(x, y, w, h);
//                  BufferedImage cardImg = fullDeckImg.getSubimage(x, y, w, h)
                    return Bitmap.createBitmap(fullDeckImg, x, y, w, h);
           } 

}

Similar to our previous use of the Android Bitmap routines (used to display a full deck of cards) there needs to be a 'Resources' instance passed in. There also needs to be the resource ID of the main bitmap that contains the pictures of each of the cards. Since there isn't any resizing of the Bitmap going on (just a piece of the bitmap is needed) we don't set the 'inJustDecodeBounds' option. BitmapFactory.decodeResource(res, resId) pulls in the full Bitmap. This gets stored under the static field fullDeckImg. The options field get populated with the width and the height of the full bitmap. These are stored in the CardImageFactory class for later use.

Now most of the code for the makeCardIcon method can be used the same way as in standard Java. Just calculate the starting pixel address and the length and width of the individual card and use the Bitmap.createBitmap method to generate a Bitmap of the individual card.

PlayingCard

The PlayingCard class is where the actual display method is located. It is at the level of this class that we have access to the data need to calculate the card suit and face value. The getCardBitmap method is all that was added. The display card back methods have been commented out as they still have the standard Java way of doing things. This will need to be fixed when we are ready to display a real game.

public class PlayingCard {
    public static final int RANK=13;
    public static final int SUIT=4;
    private int cardno;
    private int score = 0;
    private boolean diplayBack = false;

    private String faceVal = "A23456789TJQK";
    private String suit = "CSHD";

    public PlayingCard(int cardno, int score)
    {
        this.cardno = cardno;
        this.score = score;
        this.diplayBack = false;
       // this.setIcon(CardImageFactory.makeCardIcon(cardno%RANK, cardno%SUIT));
    }

    /**
     * Get the card scoring value for blackjack face cards = 10 ace = 11 
     * all other cards equal their face value. Aces can equal 1 at times and 
     * is handled elsewhere
     * @return 
     */
    public int card_value()
    {
        return this.score;
    }

    /**
     * convert cardno into text string of face value and suit
     * @return 
     */
    public String getCardText() {
        int card_val = this.cardno % 13;
        int card_suit = this.cardno % 4;
        return this.faceVal.substring(card_val, card_val + 1)
                + this.suit.substring(card_suit, card_suit + 1);
    }

    public boolean getDisplayBack() {
        return this.diplayBack;
    }

    public void setDisplayBack(boolean disp) {
        this.diplayBack = disp;
/*        if (this.diplayBack) {
            this.setIcon(new ImageIcon(System.getProperty("user.home")+"/Downloads/classic-cards/b1fv.png"));
        }
        else {
            this.setIcon(CardImageFactory.makeCardIcon(cardno%RANK, cardno%SUIT));
        }
        */
    }

    public Bitmap getCardBitmap() {
        return CardImageFactory.makeCardIcon(cardno%RANK, cardno%SUIT);
    }
}

Lifecycle issues

There is more to the Activity life cycle than just the onCreate method (more details here1,2). The onCreate happens before all the resources are created for the main resource object R. Luckily there is an onStart() method. Overriding this method in MainActivity allows the new graphic elements (the card pictures) to be displayed properly on the very first screen the user will see.

I moved the setup of the dealer and player hands into the onStart routine and added code to initialize getting the full card bitmap. I also added the display of the full deck into this method. Now the deck will be displayed when the program starts instead of having to see the little green robot.

onStart method and onCreate changes

@Override
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (savedInstanceState == null) {
                getFragmentManager().beginTransaction()
                                .add(R.id.container, new PlaceholderFragment()).commit();
        }
}

@Override
protected void onStart() {
        super.onStart();
this.strategy = new BlackJackStrategy();
this.card = new CardDeck(this.strategy);
// Before adding graphic elements shuffle the deck of cards
this.card.shuffle();

ImageView mImageView1 = (ImageView)findViewById(R.id.imageView1);

mImageView1.setImageBitmap(
            decodeSampledBitmapFromResource(getResources(), R.raw.classic_playing_cards, 100, 100));
CardImageFactory.initCardImageFactory(getResources(), R.raw.classic_playing_cards);

}

Display a Card in the onGo Method

ImageView is the easiest way to get a picture placed on the tablet screen. As with previous additions to the display just use the graphical placement tool under Eclipse under the Fragment.xml file. The ImageView instances placed under the dealer and player hands display their text. These ImageView object were set initially to display the little green robot.

Screen additions

onGo Method changes

Now the onGo method needs to add in code to access the 2 new ImageView objects placed on the screen. Then once the objects have been initialized they can be made to receive the individual card Bitmap. The following are the changes made to the onGo method to display a card from each hand. In this case the second card of the hand.

    public void onGo(View view) {
        TextView mPlayer = (TextView)findViewById(R.id.textView6);
        TextView mDealer = (TextView)findViewById(R.id.textView5);
        EditText mBet = (EditText)findViewById(R.id.editText1);
        EditText mBkroll = (EditText)findViewById(R.id.editText2);
//      ImageView mImageView = new ImageView(this.getBaseContext());
//        ImageView mImageView1 = (ImageView)findViewById(R.id.imageView1);
        ImageView mImageView2 = (ImageView)findViewById(R.id.imageView2);
        ImageView mImageView3 = (ImageView)findViewById(R.id.imageView3);

//        mImageView1.setImageBitmap(
//                  decodeSampledBitmapFromResource(getResources(), R.raw.classic_playing_cards, 100, 100));
//        CardImageFactory.initCardImageFactory(getResources(), R.raw.classic_playing_cards);

        // Get bet and bankroll from the EditText widgets
        this.bet = Integer.parseInt(mBet.getText().toString());
        this.bankroll = Integer.parseInt(mBkroll.getText().toString());

        player = new Hand("Player");
        dealer = new Hand("Dealer");

        // Deal cards
        player.deal(this.card);
        dealer.dealHoleCard(this.card);
        player.deal(this.card);
        dealer.deal(this.card);

        // Need a clear display because display appends
        player.display(mPlayer);
        dealer.display(mDealer);
        mImageView2.setImageBitmap(dealer.cards.get(1).getCardBitmap());
        mImageView3.setImageBitmap(player.cards.get(1).getCardBitmap());
    }

Screen Before onGo Executed

Screen After onGo Executed

Clicking the 'New' button causes the onGo method to be executed. If the changes were made correctly this is what you should see (the individual cards may differ secondary to the differences in the underlying random number generator).

Conclusion

There it is individual cards can now be displayed. In the next incantation of this program the following enhancements will be made:

  • Change the display so that the dealer and player hands can take up the whole width of the screen if necessary
  • Get rid of the display of the whole deck. It was just there as an experiment to learn how to setup a Bitmap for display
  • Add in display of the back of the card for the hole card of the dealer
  • Figure out how to erase the cards from the screen in preparation for a new game.
  • Package up for deployment on an actual android device

Author: Nasty Old Dog

Validate XHTML 1.0

No comments:

Post a Comment