Thursday, May 15, 2014

Android Blackjack: Adapting Old Java Code

Android Piecing Together BlackJack

Android Piecing Together BlackJack

Introduction

Now that the Eclipse Android IDE is up and running it's time to start adapting the old BlackJack code for use under Android. I am only a beginning Android programmer and I myself am using this opportunity to become better acquainted with the Android IDE and the Graphics features of the Android library. In the "Hello World" version of BlackJack the Android tools built everything for us. Now will be the time to add explicitly the Android widgets we need to get the job done

Programs are Activities

Android creates a MainActivity class. This is the main program for the application. It is the Android equivalent to the public static void main() routine in Java. Activities have a "lifecycle" and one of the methods that gets created is 'onCreate'. This is where you put some initialization code for your application. Anything else you want the application you must create your own handler methods in the ActivityMain.

Android did that

Looking over the Android generated code you see that Android used a TextView object placed on the screen to show the words "Hello World". We are going to use that element to do our own bidding. We are also going to add some more TextView elements to display other necessary output.

TextView + EditView + Buttons = input and output

The first step in recreating BlackJack on Android is to simulate the text based game. When we previously created a text based game for regular Java it helped to establish the classes for the underlying BlackJack logic and the classes that would become the playing cards on the screen. There is only one problem Android doesn't have stdin and stdout like Java on a traditional computer. So the first step is to use some simple elements in Android to simulate a textbased display.

TextView

This object displays text on the android screen. The Hello World program has one TextView. You can find it under the fragmentmain.xml file and you can manipulate in a graphical mode or by directly editing the xml of the file itself. Take a look at the properties on the right of the graphical editor display. There is a field called Text and it has a strange string representation: '@string/helloword'. Android places all strings in a strings.xml file. This is to centralize the strings so that other people can grab the file and put translations to languages other than English. In this manner your program can be easily translated into other languages. This file can be directly accessed in the res/values directory under the main project.

If you don't want to deal with the Android formalism of strings. You can just type a straight string in it will work. The IDE will spit out some complaints but it will still all run under the simulator.

The TextView widget is found under the Form Widgets heading in the Graphical layout tool. Just drag and drop to place another widget on the screen.

EditView

EditView widgets are used for user input. There are different types depending on whether you are inputting numbers or text. You can find these in the Text Fields section of the graphical layout.

Buttons

Most of these elements are quite intuitive we use them all the time. The Button widget is no exception. There are 2 properties we need to set or use to make them completely functional. First like a TextView there is a Text property that gets displayed inside the button. Second Buttons have an onClick attribute. You create a method in your Activity that is public and has a single parameter of class View. Then in the attributes for OnClick you place the name of your method. When the button is pressed the method will be called.

A first rough cut at a UI

Need an EditText for bet, bankroll. Need a button for deal, hit, stand, new game. Need some TextViews to label the EditText widgets. Using the graphical tools this is what the UI will look like:

Relative Layout

The key to placement using the graphical interface is that all the elements must be specified either relative to eachother or to the parent screen. This allows you to place elements where you want and have them display similarly on different sized devices. I threw those elements up used the text property to create a string for each of the display names you see. Just use the '…' button to the right of property:

  • click on this it opens a dialog box

  • Select new String

  • provide at least 2 values the 'R' field name and the value of the

string itself. 'R' is a generated class that interfaces all of the externalized elements (from the .xml files) and gives us access inside of Java to the info. R contains an ID for every widget placed and every property so that we get programmatic access to those values. The function of R for our purposes will become more apparent when we start some actual coding.

Combine the BlackJack code

The previous BlackJack program was strung together from scratch and functionality added as we thought of it. get closer to the final classes that will be used in the Android application.

  • CardDeck
  • BlackJackStrategy
  • Hand
  • PlayingCard

I cut and paste code from the last BlackJack game designed for regular Java. I deleted or commented the calls to the original Java graphics. Then I took the BlackJack class and threaded that code into the MainActivity. The method calls add are the following

  • onGo
  • onHit
  • onStand
  • handleDealer
  • onCreate

Hand changes

I added a new display method that takes a TextView parameter and will display the text representation of a Blackjack hand.

Button Handling

The buttons New, Hit, and Stand need to invoke code when they are pressed. The routines onGo, onHit, and onStand handle each of these button presses respectively. I took code out of the original BlackJack class and moved it to the appropriate routines. These routines all take a View parameter and the name of each routine gets placed in the Button's onClick property.

Conclusion

This may seem like a quick bit of work. What you didn't see was the week or two of part-time work getting Eclipse on my Mac, getting the right Android SDK, Getting the right Android plug-in. I also did another program before I started in on the BlackJack program. So if you're trying Android development for the first time, don't be discouraged if it seems to be taking up a ton of your time. It get's better as you gain experience (like everything in life).

If you've done everything correctly you should have the following screens when you run the App in the emulator:

Before Starting a Hand

This is a picture after I entered a bet and bankroll but before pressing New.

After Starting a Hand

This is after pressing the New Button. See that the Text representation of the Hands appear.

The Code

The MainActivity class gets generated with the project. You should cut and paste code from the MainActivity class below as opposed to replacing entirely. The other classes you can add right in to the source tree.

MainActivity.java

package com.nasty.blackjack;

import android.app.Activity;
import android.app.ActionBar;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.TextView;
import android.os.Build;

public class MainActivity extends Activity {
    private Hand player;
    private Hand dealer;
    private CardDeck card;
    private BlackJackStrategy strategy;
    private int bet;
    private int bankroll;

        @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();
                }
        this.strategy = new BlackJackStrategy();
        this.card = new CardDeck(this.strategy);
        // Before adding graphic elements shuffle the deck of cards
        this.card.shuffle();


        }

        @Override
        public boolean onCreateOptionsMenu(Menu menu) {

                // Inflate the menu; this adds items to the action bar if it is present.
                getMenuInflater().inflate(R.menu.main, menu);
                return true;
        }

        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
                // Handle action bar item clicks here. The action bar will
                // automatically handle clicks on the Home/Up button, so long
                // as you specify a parent activity in AndroidManifest.xml.
                int id = item.getItemId();
                if (id == R.id.action_settings) {
                        return true;
                }
                return super.onOptionsItemSelected(item);
        }

        /**
         * onGo - handles the start of the game after the fo button is pressed
         */
    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);

        // 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);
    }
        /**
         * onHit - handles the hit button being pressed
         */
        public void onHit(View view) {
                TextView mPlayer = (TextView)findViewById(R.id.textView6);

        this.player.deal(this.card);
        player.display(mPlayer);
        int scr = this.player.scoreHand();
        if (scr >= 21) {
            // Player has gone bust display a message and activate continue button
            // or Player has 21 and will win if dealer has <21 or bust
            this.handleDealer(scr);
        }
        }

        /**
         * onStand - handles the rest of the game after player stands
         */
        public void onStand(View view) {
        // Player is done getting cards time to do the dealer stuff
        this.handleDealer(this.player.scoreHand());
        }

        /**
         * handleDealer
         * This is the code that gets executed after the player 
         * stands his hand.
         */
    public void handleDealer(int pscr) {
        int dscr = 0;
        TextView mDealer = (TextView)findViewById(R.id.textView5);
        String msg = "Player score: " + pscr;
        // All cards must be displayed which reset the flag and fixes a 
        // bug where after the cards are shuffled some player cards get
        // the displayBack field set from previously used dealer cards
        dealer.displayAll();
        dealer.display(mDealer);

        if (pscr > 21) {
            msg = msg + " BUST, LOSER!!!";
            bankroll -= bet;
        } else {
            for (dscr = dealer.scoreHand();
                    dscr < 17;
                    dscr = dealer.scoreHand()) {
                this.dealer.deal(card);
                dealer.display(mDealer);
            }

            // Code to score the game and settle the bet will go here
            if (pscr > dscr) {
                msg = msg + " Dealer score: "+ dscr+" ... You Win!!!";
                bankroll = bankroll + bet;
            } else if (dscr > 21) {
                msg = msg + " Dealer score: "+ dscr+": ... You Win!! Dealer has BUSTED";
                bankroll += bet;
            } else if (pscr == dscr) {
                msg = msg + " Dealer score: "+ dscr+" ... PUSH";
            } else {
                msg = msg + " Dealer score: "+ dscr+" ... LOSER!!!";
                bankroll -= bet;
            }
        }
        // Here should display a status message as to how 
        // the game was scored
        // update display of bankroll
    }

        /**
         * A placeholder fragment containing a simple view.
         */
        public static class PlaceholderFragment extends Fragment {

                public PlaceholderFragment() {
                }

                @Override
                public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                Bundle savedInstanceState) {
                        View rootView = inflater.inflate(R.layout.fragment_main, container,
                                        false);
                        return rootView;
                }
        }

}

CardDeck.java

package com.nasty.blackjack;

public class CardDeck {
    PlayingCard deck[] = new PlayingCard[52];
    int deckidx = 0; // the location of the next card to be dealt
    public CardDeck(BlackJackStrategy strategy) {
        for (int i = 0; i < 52; i++) {
            deck[i] = new PlayingCard(i, strategy.getCardScore(i));
        }

    }

    void shuffle() {
        int rindex;
        PlayingCard swap;

        for (int i = 0; i < 52; i++) {
            // each time through the loop there are less numbers to randomize
            // 52 - i to be exact. But then everything from 0 to i-1 has already
            // been selected at random so add i to the random number to get the
            // appropriate index
            rindex = (int) ((Math.random() * ((double) (52 - i))) + i);
            swap = deck[i];
            deck[i] = deck[rindex];
            deck[rindex] = swap;
        }
        this.deckidx = 0;
    }

    void display() {
        int card_val;
        int card_suit;
        for (int i = 0; i < 52; i++) {
            System.out.print(this.deck[i].getCardText() + " ");
        }
        System.out.println();
    }

    public PlayingCard deal() {
        if (this.deckidx >= 52) {
            deckidx = 0;
            this.shuffle();
        }            
        return this.deck[this.deckidx++];
    }
}

Hand.java

package com.nasty.blackjack;

import java.util.ArrayList;

import android.widget.TextView;

public class Hand {
    ArrayList<PlayingCard> cards = new ArrayList<PlayingCard>();
//  ArrayList<PlayingCard> sorted = new ArrayList<PlayingCard>();

  private String displayName = "change in constructor";
  private BlackJackStrategy strategy = null;   

  /**
   * Default constructor that assumes BlackJackStrategy and labels itself "Hand"
   */
  public Hand()
  {
      this("Hand", new BlackJackStrategy());
  }

  /**
   * Assumes BlackJack Strategy but provides for a text name for the hand
   * @param displayName the title of the hand
   */
  public Hand(String displayName)
  {
      this(displayName, new BlackJackStrategy());
  }

  /**
   * The most general constructor where you can supply a strategy for a 
   * BlackJack strategy (this may be extended to provide for other games)
   * @param displayName the title of the hand
   * @param strategy BlackJack strategy being used
   */
  public Hand(String displayName, BlackJackStrategy strategy)
  {
      this.displayName = displayName;
      this.strategy = strategy;
//      this.setLayout(new FlowLayout());
//      Border blackline = BorderFactory.createLineBorder(Color.black);
//      TitledBorder hTitle = BorderFactory.createTitledBorder(blackline, this.displayName);
//      this.setBorder(hTitle);

//      this.cs = new GridBagConstraints();
/*      if ("Player".equals(this.displayName)) {
          this.cs.gridx = 0;
          this.cs.gridy = 2;
          this.cs.gridwidth = 3;            
      } else {
          this.cs.gridx = 0;
          this.cs.gridy = 1;
          this.cs.gridwidth = 3;                        
      }
      */
  }


  /**
   * Given a CardDeck it deals a card adding to the Hand ArrayList and to 
   * the JPanel list of displayable objects
   * @param deck CardDeck to deal from
   */
  public void deal(CardDeck deck)
  {
     PlayingCard c = deck.deal();
     this.cards.add(c);
     //this.add(c);       
  }

  public void dealHoleCard(CardDeck deck)
  {
      PlayingCard c = deck.deal();
      c.setDisplayBack(true);
      this.cards.add(c);
      //this.add(c);
  }

  public void displayAll()
  {
      for (PlayingCard c: this.cards) {
          if (c.getDisplayBack()) {
              c.setDisplayBack(false);
          }
      }
  }

  /**
   * sort hand is necessary for scoring a blackjack hand to account for 
   * aces being one or 11. 
   * @return ArrayList of PlayingCard in score ascending order
   */
  public ArrayList<PlayingCard> sortHand() {
      // sort hand make Aces 11 for sorting purposes
      // Use the insertion sort code and modify it by using the card_value
      // method as the way to sort

      ArrayList<PlayingCard> sorted = (ArrayList<PlayingCard>) this.cards.clone();

      // The clone statement does a deep copy of the array list
      // Just to prove it I print out the sorted field individually
      PlayingCard tmp;
      for (int i = 1; i < sorted.size(); i++) {
          for (int j = i;
                  j > 0
                  && sorted.get(j).card_value() < sorted.get(j-1).card_value();
                  j--) {
              // swap values

              tmp = sorted.get(j);
              sorted.set(j, sorted.get(j - 1));
              sorted.set(j - 1, tmp);
          }
      }
      return sorted;
  }

  /**
   * Call through to the BlackJack strategy to score the Hand
   * @return int value of score
   */
  public int scoreHand() {
      return this.strategy.getScore(this);
  }

  /** 
   * text based display to change android TextView text
   */
  public void display(TextView t) {
  //    System.out.println(this.displayName);
          String msg = new String("");
      for (PlayingCard i : cards) {
          if (i.getDisplayBack()) {
                  msg = new String(msg + "X" +" ");
          }
          else {
              msg = new String(msg + i.getCardText() + " ");
          }
      }
      // set the text view text passed in
      t.setText(msg);
  }

  /** 
   * text based display routine for testing purposes
   */
  public void display() {
      System.out.println(this.displayName);
      for (PlayingCard i : cards) {
          System.out.print(i.getCardText() + " ");
      }
      System.out.println();
  }

  public void discard()
  {
      // remove each card from the List of displayed items
//      for (PlayingCard c : this.cards) {
//          this.remove(c);
//      }

      // throw the current cards into the garbage by creating a new list
      this.cards = new ArrayList<PlayingCard>();
  }

}

BlackJackStrategy.java

package com.nasty.blackjack;

import java.util.ArrayList;

public class BlackJackStrategy {
    private int card_score[] = {11, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10};
    public final static int RANK = 13;

    BlackJackStrategy() {
        super();
    }

    /**
     * Take an internal int cardno representation and return it's blackjack 
     * value
     * @param cardno an integer between 0 and 51 that represents a specific card
     * @return the blackjack scoring value of the card
     */
    public int getCardScore(int cardno) {
        return this.card_score[cardno%RANK];
    }

    /**
     * determine the score of a blackjack hand, sort the hand then figure out
     * which Aces should be 1 or 11.
     * @param h the Hand class passed in
     * @return returns the score of the hand
     */
    public int getScore(Hand h) {
        ArrayList<PlayingCard> sorted = h.sortHand();
        int score = 0;
        for (PlayingCard i : sorted) {
            // Need to test each card to see if it is an ACE
            // If an ACE and score is < 11 use 11 as ACE value otherwise use 1
            // I haven't done a proof but I believe that checking the score
            // less than 11 is sufficient to handle ACEs and you won't bust a
            // hand incorrectly
            if (i.card_value() == 11) {
                // Processing ACE
                if (score >= 11) {
                    // ACEs must all be 1
                    score += 1;
                }
                else {
                    score += 11;
                }
            }
            else {
                // Just add card_value it's not an ACE
                score += i.card_value();
            }
        }
        return score;
    }
}

PlayingCard.java

package com.nasty.blackjack;

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

}

Author: Nasty Old Dog

Validate XHTML 1.0

No comments:

Post a Comment