Sunday, June 16, 2013

AP Computer Science: The Summer After

AP Computer Science: The Summer After

AP Computer Science: The Summer After

Introduction

It is now almost summer (2013) and for those of you who have completed the AP Computer Science course you may be wondering: How do you maintain (and/or improve) your computer skills? At least this was the question from one student I know in a similar situation and I thought it was a good question to address.

Program For Yourself

The first thing you must do is program for yourself. The Blackjack series of programs from this blog was my attempt to show new students that the subject matter of your programs need not be seemingly useless programs. You should be able to dream up any program that may interest you. If your not sure what interests you then I suggest start with some simple things from your math classes. Programming is an excellent way to reinforce your mathematic abilities.

A Summer After Sylabus

Numerical Integration - Trapezoidal Rule

In calculus you learned to differentiate and integrate various functions. You should have learned that some functions (eg. 1/(ln x)) do not have an integral that can be expressed as a formula. That's where the Trapezoidal Rule comes in to play. By just knowing the original function you can estimate the area under its curve (ie. find an estimate of the definite integral) by dividing the area up into tiny trapezoids and summing up their area. The smaller the width of the trapezoids the more accurate the estimate.

  1. Find the definition of the Trapezoidal Rule in your calculus book or Wikipedia
  2. Create a program that implements the trapezoidal rule.
  3. test the program on the function f(x) = x2 and compare trapezoidal answer to the actual integral (1/3)x3.
  4. If you book has an example of a function that does not have a solvable integral test your program on that function and see that it provides the same answers
  5. Investigate the use of System.out.format to make the output line up nicely
  6. Try implementing Simpson's Rule after you have the Trapezoidal rule working

Big Project

You have this nice integrator function working let's get it ready to share with other people. You could package it as a desktop application but these days everyone wants to access things on the web. There are 2 ways to do this but both need a web server to deliver the program.

  • Applet a java program that is downloaded via the web and run by the web browser
  • Servlet a java program that runs in a Web Server and serves web pages to a browser

Servlet first

To create a servlet you are going to need a web server program. For Java servlets there are a couple available I prefer Apache Tomcat just because I learned that first. Some people like Jetty. The servlet program will be written the same way no matter which you choose. The installation of that servlet will vary depending on which one you use.

For this project the following steps should be taken:

  1. Google the Servlet servers and pick one of your liking
  2. Download and install the server (tomcat project page: http://tomcat.apache.org/)
  3. Get it up and running. Under Tomcat if it's running properly then you should be able to see the admin page when you put http://localhost:8080 in your browser.
  4. Both Eclipse and Netbeans IDE will set up a Servlet project. Google a HelloWorld example under each and get it to compile in your IDE.
  5. Figure out how to get the project to create a .war file so the app can be deployed
  6. Look at the Tomcat User documents and find out where to place the .war file
  7. Once the .war file has been placed you should see it appear in the tomcat admin page.
  8. Go to the servlet in your browser at http://localhost:8080/<app-dir-name>
  9. Create a new project for a servlet and adapt your integration code to run as a servlet
  10. Install the project under tomcat and verify that the code comes up properly

Improving the Integration Servlet

  • create another Servlet that will accept the following user input
    • a - the starting point for the definite integral
    • b - the end point
    • step - the step size
  • Make sure to add a submit button the submit button should call the integration servlet and provide the parameters
  • modify your Servlet to look for the parameters from the new input servlet
  • To make this really amazing can you figure out a way to allow the user to input a function and the servlet compiles it into java code and integrates it. I believe this is possible but I have never done it.

Final Big Project for the Summer

I have 2 suggestions for a final project for the summer.

  • Learn a new language
    • The key to being a good Software Developer is to learn new computer languages quickly. The best way to learn quickly is to dive in and just start learning a new progrmamming language. Then repeat the process, add another and another language to your repertoire. Each successive language learned will be assimilated faster.
    • LISP/Scheme
    • PERL
    • Ruby
    • C/C++
    • J
    • Javascript
    • Prolog
  • Find the Harvard CS50 lecture videos and watch them. The course goes over C, Javascript, and some other languages while teaching introductory principles of Computer Science

Conclusion

That's it! No code at this point. It's time for those that have taken AP Computer Science to start learning to read online documentation and getting things up and running. If you have any problems you should be able to post a comment to the blog I would be glad to give some pointers.

What I will do in the next article will be to create an interface for the function class. Then use that interface to create an Applet that integrates the function. The Applet should then be a specification for how the Servlet project should work from a functional perspective.

Author: Nasty Old Dog

Validate XHTML 1.0

Friday, May 31, 2013

Java Swing BlackJack: The Final Touches

Graphical BlackJack: Adding Game Logic

Graphical BlackJack: Adding Game Logic

Introduction

In the original text based version of this game there was a considerable amount of game logic. The game would proceed in the following fashion:

  1. Game Start Up
  2. Player Enters Bet
  3. Initial Hands for Player and Dealer are displayed
  4. Player decides to Hit or Stand (This is looping code the player can Hit until the hand Busts or the Player presses Stand or the Hand = 21)
  5. The Dealer hand his played using the rule dealer must hit 16 or under
  6. The Hands are scored the closest to 21 without going over wins. Ties mean the bet money is returned and player starts a new hand
  7. Go back to step 2.
  8. Player quits by pressing the Windows red "X" button

Most of the above logic can be cut and pasted into the actionPerformed method with little revision. There will some Button handling things to add.

  • When the game starts the Hit and Stand buttons are disabled and the Bet button is enabled
  • After the Bet is placed the Bet button is disabled and the Hit and Stand buttons are enabled
  • When the stand button is pressed or the hand is bust or at 21 and before the dealer logic is started the Hit and Stand buttons are disabled.
  • After the winner is determined and it's time for a new round the Bet button needs to be enabled again.

JTextField problems

There is the issue (it's been ignored for a while) that the JTextField where the user enters the Bet amount needs to be enlarged. Currently it is appearing so small that when the user clicks in it to type something nothing appears. A simple call to the JTextField's setPreferredSize method placed in the BlackJack run method before the JTextField gets added for diplay will fix this:

  • betField.setPreferredSize(new Dimension(75,25));

Ouput

Start Up

  • Bet button active
    • nothing to be done the default state for a created JButton is Enabled = true
  • hit and stand inactive
    • add hitButton.setEnabled(false) and same for standButton
    • This is placed in run method after the buttons are created
  • Enter JTextField value and press bet
    • deactivate bet button
    • activate hit and stand
    • if no value entered in JTextField or non-numeric entered clear JTextField and leave bet button active and essentially do nothing until the user enters something meaningful (a try-catch block is used to capture this)
    • These changes are added to the actionPerformed method (check if "bet" action command has come in)

Code Changes to BlackJack

public void actionPerformed(ActionEvent ae) {
    if ("hit".equals(ae.getActionCommand())) {
        // Hit button press add a card to the player hand then redisplay frame
        this.player.deal(this.card);

        // Once the card is added you need to revalidate the frame and repaint
        frame.validate();
        frame.repaint();
    }
    if ("bet".equals(ae.getActionCommand())) {
        String betStr = betField.getText();
        try {
            this.bet = Integer.parseInt(betStr);
        } 
        catch (Exception ex) {
            // set bet = 0 and reset the JTextField to empty the box of bad text
            // return (do nothing user entered invalid text)
            // At some point need to add a Message display to window to 
            // Inform the user of problems
            betField.setText("");
            bet = 0;
            return;
        }
        // Bet is an int so go ahead and deactivate Bet Button enable hit and stand
        betButton.setEnabled(false);
        hitButton.setEnabled(true);
        standButton.setEnabled(true);
        frame.validate();
        frame.repaint();
    }
}

/**
 * run method that does all the work of this class
 */
@Override
public void run() {
    Container contentPane = frame.getContentPane();


    // Set the layout manager for the JFrame using the contentPaine
    FlowLayout layout = new FlowLayout();
    contentPane.setLayout(layout);

    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setPreferredSize(new Dimension(300,500));

    // Need a label for the JTextField
    JLabel betLabel = new JLabel("Bet Amount:");

    // set the different Action Command Strings
    betButton.setActionCommand("bet");
    hitButton.setActionCommand("hit");
    standButton.setActionCommand("stand");

    // set this BlackJack class as the handler for each of the buttons
    betButton.addActionListener(this);
    hitButton.addActionListener(this);
    standButton.addActionListener(this);

    // Disable Hit and Stand buttons
    hitButton.setEnabled(false);
    standButton.setEnabled(false);

    // Set size of the bet JTextField 
    betField.setPreferredSize(new Dimension(75,25));

Output

Here is the start up screen notice the hit and stand buttons are greyed out. The user has also entered "abc" for the bet amount BlackJackStartInactiveButton.png

After pressing the Bet button on the bad bet text the JTextField is reset and the bet Button remains active with the other buttons inactive. BlackJackAfterBadBet.png

Entering a number in the bet field and pressing the Bet button, the Bet button becomes inactivated, and the hit and stand buttons are now active .img/BlackJackAfterGoodBet.png

There are still a couple of issues lingering with this user input. The bet field is an int type. It can take on negative numbers which would mean that the user would get paid if they lose the hand. This can be handled by taking the absolute value of the entry or resetting and forcing the user to reenter.

The player can also enter 0 as a valid bet. So the second way above for handling a bad bet is probably the easiest. Just test for input less than or equal to 0 and reset if it is.

One more problem that is not dealt with is the fact that a bet is not checked against the bankroll. Losing all your money means your done playing in Vegas it may be a good thing to do here. But this problem will be deferred until later.

The following code added after the exception handling block (ie. try-catch) should fix the problems

// Check if bet is less than or equal to 0 and reset if it is
if (bet <= 0) {
    betField.setText("");
    bet = 0;
    return;
}

Add in Game Logic

While weaving in the game logic it became apparent that there is a need to have a message display and a continue button. Otherwise when the hands are scored the program will cause the hands to disappear (or at least that is the functionality you want) and the Player won't know what the status of the hand is and feel the program is cheating.

There also needs to be a screen area set aside to display the bankroll. That is how the Player determines how well they are doing. The JLabel will suffice for displaying text. Two more will be added one for an informational display and one for the Bankroll. We may want to place the bankroll JLabel inside a JPanel so it can have a titled border.

What was changed?

  • Added fields into BlackJack class for the continue button and the extra labels
  • Set the dimensions of the player hand and the dealer hand. This helps in maintaining the window at a constant size when there are no cards in the hand yet
  • Added a handleDealer method to do the game logic for the dealer's hand and scoring for determining win or lose
  • Added a resetFrame method to start a new round with no cards in the hands
    • remove all the swing objects from frame with a call to the ContentPane.removeAll method
    • then add back all the elements just like what happens at start up
  • worked the logic into actionPerformed
    • when to activate continue button and deactivate the others
    • The buttons keep the state of the game by being activated or deactivated
  • Moved some of the elements around to prepare for using a better Layout Manager than FlowLayout in the future
  • Got rid of all the text output method calls. Everything happens on the screen

Code for BlackJack

/*
 * BlackJack a simple implementation upgrade for graphical card display
 */
package blackjack;

import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.net.MalformedURLException;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

/**
 * This class controls the game logic of this simple implementation of 
 * Black Jack. A more profession version would allow for multiplayer games
 * via a game server so you could play your friends on the internet. 
 * Some improvements would be:
 * <p>
 *    Adding insurance (players could insure bets against a dealer having blackjack) <p>
 *    Increase the payout for a blackjack hand to 1.5 x the original bet <p>
 *    Double Down <p>
 *    Splitting pairs <p>
 * 
 * @author Nasty Old Dog
 */
public class BlackJack implements ActionListener, Runnable {

    int bankroll = 1000;
    int bet = 0;
    CardDeck card;
    Hand player;
    Hand dealer;
    BlackJackStrategy strategy = new BlackJackStrategy();
    JFrame frame = new JFrame("BlackJack");
    JButton hitButton = new JButton("Hit");
    JButton standButton = new JButton("Stand");
    JButton betButton = new JButton("Bet");
    JButton continueButton = new JButton("Continue");
    JLabel bankLabel = new JLabel("Bankroll: " + bankroll);
    JLabel infoLabel = new JLabel("Welcome to BlackJack");
    JTextField betField = new JTextField();
    // Need a label for the JTextField
    JLabel betLabel = new JLabel("Bet Amount:");

    public BlackJack() {
        this.card = new CardDeck(this.strategy);
    }

    /** 
     * handles the Dealer hand playing logic
     */
    public void handleDealer(int pscr) {
        int dscr = 0;
        String msg = "Player score: " + pscr + "   Dealer: " + dscr;
        if (pscr > 21) {
            msg = msg + ": BUST LOSER!!!";
            bankroll -= bet;
        } else {
            for (dscr = dealer.scoreHand();
                    dscr < 17;
                    dscr = dealer.scoreHand()) {
                this.dealer.deal(card);
            }

            // Code to score the game and settle the bet will go here
            if (pscr > dscr) {
                msg = msg + ": You Win!!!";
                bankroll = bankroll + bet;
            } else if (dscr > 21) {
                msg = msg + ": You Win!! Dealer has BUSTED";
                bankroll += bet;
            } else if (pscr == dscr) {
                msg = msg + ": PUSH";
            } else {
                msg = msg + ": LOSER!!!";
                bankroll -= bet;
            }
        }

        // Print message about who won. Then wait for continue button to be 
        // pressed so user can have time to look at cards and verify the 
        // situattion
        infoLabel.setText(msg);
        betButton.setEnabled(false);
        hitButton.setEnabled(false);
        standButton.setEnabled(false);
        continueButton.setEnabled(true);
        frame.validate();
        frame.repaint();
    }

    /**
     * Handles all the graphics user input
     * @param ae ActionEvent generated by JButtons in the frame
     */
    @Override
    public void actionPerformed(ActionEvent ae) {
        if ("hit".equals(ae.getActionCommand())) {
            // Hit button press add a card to the player hand then redisplay frame
            this.player.deal(this.card);

            // Once the card is added you need to revalidate the frame and repaint
            frame.validate();
            frame.repaint();
            int scr = this.player.scoreHand();
            if (scr > 21) {
                // Player has gone bust display a message and activate continue button
                bankroll -= bet;
                this.handleDealer(scr);
            }
            if (scr == 21) {
                // Player has 21 and will win if dealer has <21 or bust
                this.handleDealer(scr);

            }
            // Otherwise do nothing player decides whether to hit or stand
        }
        if ("stand".equals(ae.getActionCommand())) {
            // Player is done getting cards time to do the dealer stuff
            this.handleDealer(this.player.scoreHand());
            betButton.setEnabled(false);
            standButton.setEnabled(false);
            betButton.setEnabled(false);
            continueButton.setEnabled(true);

        }
        if ("bet".equals(ae.getActionCommand())) {
            String betStr = betField.getText();
            try {
                this.bet = Integer.parseInt(betStr);
            } catch (Exception ex) {
                // set bet = 0 and reset the JTextField to empty the box of bad text
                // return (do nothing user entered invalid text)
                // At some point need to add a Message display to window to 
                // Inform the user of problems
                betField.setText("");
                bet = 0;
                return;
            }

            // Check if bet is less than or equal to 0 and reset if it is
            if (bet <= 0) {
                betField.setText("");
                bet = 0;
                return;
            }

            // Bet is an int so go ahead and deactivate Bet Button enable hit and stand
            betButton.setEnabled(false);
            hitButton.setEnabled(true);
            standButton.setEnabled(true);
            player.deal(this.card);
            dealer.deal(this.card);
            player.deal(this.card);
            dealer.deal(this.card);
            frame.validate();
            frame.repaint();
        }
        if ("continue".equals(ae.getActionCommand())) {
            betButton.setEnabled(true);
            hitButton.setEnabled(false);
            standButton.setEnabled(false);
            continueButton.setEnabled(false);
            bankLabel.setText("Bankroll: " + bankroll);
            infoLabel.setText("Welcome to BlackJack, Place your bet!");
            betField.setText("");
            bet = 0;
            player = new Hand("Player");
            dealer = new Hand("Dealer");
            this.resetFrame();
        }
    }

    /**
     * This reset the entire frame and reloads all the displayable objects in 
     * the proper order
     */
    public void resetFrame() {
        frame.getContentPane().removeAll();
        player.setPreferredSize(new Dimension(500,150));
        dealer.setPreferredSize(new Dimension(500,150));

        frame.add(infoLabel);
        frame.add(dealer);
        frame.add(player);
        frame.add(hitButton);
        frame.add(standButton);
        frame.add(continueButton);
        frame.add(betLabel);
        frame.add(betField);
        frame.add(betButton);
        frame.add(bankLabel);
        frame.validate();
        frame.repaint();
    }

    /**
     * run method that does all the work of this class
     */
    @Override
    public void run() {
        Container contentPane = frame.getContentPane();


        // Set the layout manager for the JFrame using the contentPaine
        FlowLayout layout = new FlowLayout();
        contentPane.setLayout(layout);

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setPreferredSize(new Dimension(550, 450));


        // set the different Action Command Strings
        betButton.setActionCommand("bet");
        hitButton.setActionCommand("hit");
        standButton.setActionCommand("stand");
        continueButton.setActionCommand("continue");

        // set this BlackJack class as the handler for each of the buttons
        betButton.addActionListener(this);
        hitButton.addActionListener(this);
        standButton.addActionListener(this);
        continueButton.addActionListener(this);

        // Disable Hit and Stand buttons
        hitButton.setEnabled(false);
        standButton.setEnabled(false);
        continueButton.setEnabled(false);

        // Set size of the bet JTextField 
        betField.setPreferredSize(new Dimension(75, 25));

        // Set up some hands
        player = new Hand("Player");
        dealer = new Hand("Dealer");

        // Set a preferred size for the Hand JPanel
        player.setPreferredSize(new Dimension(500,150));
        dealer.setPreferredSize(new Dimension(500,150));

        // Add all the objects into the fram so they can be displayed
        frame.add(dealer);
        frame.add(player);
        frame.add(hitButton);
        frame.add(standButton);
        frame.add(betLabel);
        frame.add(betField);
        frame.add(betButton);
        frame.add(bankLabel);
        frame.add(infoLabel);
        frame.add(continueButton);
        frame.pack();
        frame.setVisible(true);
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        try {
            CardImageFactory.initCardImageFactory("http://www.jfitz.com/cards/classic-playing-cards.png");
            BlackJack game = new BlackJack();

            SwingUtilities.invokeLater(game);
        } catch (MalformedURLException ex) {
            System.out.println(ex.getMessage());
        } catch (IOException ex) {
            System.out.println(ex.getMessage());
        }
    }
}

There were a bunch of changes and code has been woven in all over the place. The coding happened fast and furious, rather than upset the train of thought I plowed through it. Hopefully the reader can match up code to functionality on the screen.

Output at Startup

The start up screen now looks like this:

BlackJackFixedHandDim.png

Output after round played

After User finishes a round then the Continue Button is active:

BlackJackAtContinueActive.png

Conclusion

This is almost complete. It now plays a game of BlackJack but there are some small things missing. There are also some Graphics design issues that need to be addressed

  • BlackJack logic
    • Need to shuffle the cards.
    • Need to track how many cards have been used and reshuffle when the CardDeck is low
  • Graphics fixes
    • Need a better Layout Manager that fixes the objects displayed in a more fixed fashion
    • The message label should be at the top of the screen where it will be noticed more
    • The continue button should go along side the Hit and Stand Buttons
    • The bankroll display should be enhanced to be in a JPanel with a title border and the amount inside the JPanel as a JLabel
    • It would be nice to have the bankroll display and bet amount controls on the right hand side of the screen. Maybe when the Layout Manager is fixed this will be easy to do.

Author: Nasty Old Dog

Validate XHTML 1.0

Wednesday, May 22, 2013

BlackJack and GridBagLayout

BlackJack and GridBagLayout

BlackJack and GridBagLayout

Introduction

In a flurry of hacking the Graphics Based BlackJack program was thrown together. However the Graphics are inadequate in a number of ways, the biggest of which is the choice of Layout Manager. The FlowLayout moves objects around depending on the area of the JFrame after a mouse resize operation. It's functional for testing game logic but not worthy to be considered a real prototype for a graphical BlackJack program.

The designers of the Java Swing API think so as well. Not necessarily of this BlackJack program in particularly, but they had the foresight to provide different LayoutManagers and make them general enough to handle a variety of graphic object placement issues. The Java Tutorials are again used to investigate the functionality of one of the more versatile Layout Managers: the GridBagLayout manager (see the References for a link).

Essential Elements of GridBagLayout

The functionality is similar to creating a table in a word processor or using a spreadsheet. A grid is defined implicitly and through the use of constraint objects the graphics objects are place in the grid. Implicitly in this sense means that the Swing GridBagLayout manager class infers the grid size from the grid coordinates provided in the constraint objects. Then using an overloaded "add" method to the ContentPane of the JFrame object both the graphics object and it's corresponding constraints object are added to the JFrame ContentPane.

The changes required to the BlackJack class will be to add a new contraint object definition with each object to be displayed. The pseudo code will take the following form:

Object definition and initialization
Constraint definition 
contentPane.add(SwingObject, Constraints);
// repeat the above for each object to be placed in the GridBagLayout

Grid Design

The following block diagram shows the arrangement of the Swing objects and shows the grid coordinates for each object:

From the above diagram the grid coordinates of where the objects are placed in the JFrame relative to each other are established:

Object Coordinates (gridx,gridy) Span
Message 0,0 3 boxes
Bankroll 3,0 1 box
Dealer Hand 0,1 3 boxes
betLabel 3,1 1 box
Player Hand 0,2 3 boxes
betTextField 3,2 1 box
hitButton 0,3 1 box
standButton 1,3 1 box
continueButton 2,3 1 box
betButton 3,3 1 box

GridBag Constraints

The constraints needed to improve the placement of the BlackJack Swing Objects are as follows

  • For Grid placement
    • GridBagConstraint.gridx specifies the column of the grid
    • GridBagConstraint.gridy specifies the row of the grid
  • For Grid Span (ie. how manch grid boxes the object takes
    • GridBagConstraint.gridwidth specifies the number of columns the object uses
    • GridBagConstraint.gridheight specifies the number of rows the object uses

The objects are still somewhat tied together in the fact that if they share a row that has a taller object the other objects may not get displayed evenly. This is a detail best saved for when objects are actually placed and then investigate what constraints may be necessary to fix the display of the objects

First Cut at GridBag Code

The following must be exchanged for where the Objects are added to the frame content pane:

// Set Constraints of the displayed objects
// Add all the objects into the fram so they can be displayed
GridBagConstraints constraints = new GridBagConstraints();
constraints.gridx = 0;
constraints.gridy = 0;
constraints.gridwidth = 3;
frame.add(infoLabel, constraints);

constraints = new GridBagConstraints();
constraints.gridx = 3;
constraints.gridy = 0;
constraints.gridwidth = 1;
frame.add(bankLabel, constraints);

constraints = new GridBagConstraints();
constraints.gridx = 0;
constraints.gridy = 1;
constraints.gridwidth = 3;
frame.add(dealer, constraints);

constraints = new GridBagConstraints();
constraints.gridx = 3;
constraints.gridy = 1;
constraints.gridwidth = 1;
frame.add(betLabel, constraints);

constraints = new GridBagConstraints();
constraints.gridx = 0;
constraints.gridy = 2;
constraints.gridwidth = 3;
frame.add(player, constraints);

constraints = new GridBagConstraints();
constraints.gridx = 3;
constraints.gridy = 2;
constraints.gridwidth = 1;
frame.add(betField, constraints);

constraints = new GridBagConstraints();
constraints.gridx = 0;
constraints.gridy = 3;
constraints.gridwidth = 1;
frame.add(hitButton, constraints);

constraints = new GridBagConstraints();
constraints.gridx = 1;
constraints.gridy = 3;
constraints.gridwidth = 1;
frame.add(standButton, constraints);

constraints = new GridBagConstraints();
constraints.gridx = 2;
constraints.gridy = 3;
constraints.gridwidth = 1;
frame.add(continueButton, constraints);

constraints = new GridBagConstraints();
constraints.gridx = 3;
constraints.gridy = 3;
constraints.gridwidth = 1;
frame.add(betButton, constraints);

frame.pack();
frame.setVisible(true);

The above code when added and run displays the objects in a nasty shrunken form it turns out the window is not sized properly and needs to be larger. Changing the frame preferredSize field will make the objects display in their new relative positions at the size they were in the previous version of the program.

Output 1

The following line of code should be changed near the beginning of the "run" method:

  • From
    • frame.setPreferredSize(new Dimension(550, 450));
  • To
    • frame.setPreferredSize(new Dimension(650, 550));

Now the larger frame displays all the elements as expected.

Output 2

Add Constraints to the resetFrame Method

Since resetFrame and the run method go through the same Objects and Constraints the above code using constraints was moved out of the "run" method and into a method called setConstraints.

/** 
 * This method centralizes the constraints for object placement in a 
 * GridBagLayout manager. It is used at start-up and to reset the 
 * game window after each round
 */
public void setConstraints()
{
            // Set Constraints of the displayed objects
    // Add all the objects into the fram so they can be displayed
    GridBagConstraints constraints = new GridBagConstraints();
    constraints.gridx = 0;
    constraints.gridy = 0;
    constraints.gridwidth = 3;
    frame.add(infoLabel, constraints);

    constraints = new GridBagConstraints();
    constraints.gridx = 3;
    constraints.gridy = 0;
    constraints.gridwidth = 1;
    frame.add(bankLabel, constraints);

    constraints = new GridBagConstraints();
    constraints.gridx = 0;
    constraints.gridy = 1;
    constraints.gridwidth = 3;
    frame.add(dealer, constraints);

    constraints = new GridBagConstraints();
    constraints.gridx = 3;
    constraints.gridy = 1;
    constraints.gridwidth = 1;
    frame.add(betLabel, constraints);

    constraints = new GridBagConstraints();
    constraints.gridx = 0;
    constraints.gridy = 2;
    constraints.gridwidth = 3;
    frame.add(player, constraints);

    constraints = new GridBagConstraints();
    constraints.gridx = 3;
    constraints.gridy = 2;
    constraints.gridwidth = 1;
    frame.add(betField, constraints);

    constraints = new GridBagConstraints();
    constraints.gridx = 0;
    constraints.gridy = 3;
    constraints.gridwidth = 1;
    frame.add(hitButton, constraints);

    constraints = new GridBagConstraints();
    constraints.gridx = 1;
    constraints.gridy = 3;
    constraints.gridwidth = 1;
    frame.add(standButton, constraints);

    constraints = new GridBagConstraints();
    constraints.gridx = 2;
    constraints.gridy = 3;
    constraints.gridwidth = 1;
    frame.add(continueButton, constraints);

    constraints = new GridBagConstraints();
    constraints.gridx = 3;
    constraints.gridy = 3;
    constraints.gridwidth = 1;
    frame.add(betButton, constraints);

}
/**
 * This reset the entire frame and reloads all the displayable objects in 
 * the proper order
 */
public void resetFrame() {
    frame.getContentPane().removeAll();
    player.setPreferredSize(new Dimension(500,150));
    dealer.setPreferredSize(new Dimension(500,150));

    // Set Constraints of the displayed objects
    // Add all the objects into the fram so they can be displayed
    this.setConstraints();

    frame.validate();
    frame.repaint();
}

/**
 * run method that does all the work of this class
 */
@Override
public void run() {
    Container contentPane = frame.getContentPane();


    // Set the layout manager for the JFrame using the contentPaine
    //FlowLayout layout = new FlowLayout();
    GridBagLayout layout = new GridBagLayout();
    contentPane.setLayout(layout);

    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setPreferredSize(new Dimension(650, 550));


    // set the different Action Command Strings
    betButton.setActionCommand("bet");
    hitButton.setActionCommand("hit");
    standButton.setActionCommand("stand");
    continueButton.setActionCommand("continue");

    // set this BlackJack class as the handler for each of the buttons
    betButton.addActionListener(this);
    hitButton.addActionListener(this);
    standButton.addActionListener(this);
    continueButton.addActionListener(this);

    // Disable Hit and Stand buttons
    hitButton.setEnabled(false);
    standButton.setEnabled(false);
    continueButton.setEnabled(false);

    // Set size of the bet JTextField 
    betField.setPreferredSize(new Dimension(75, 25));

    // Set up some hands
    player = new Hand("Player");
    dealer = new Hand("Dealer");

    // Set a preferred size for the Hand JPanel
    player.setPreferredSize(new Dimension(500,150));
    dealer.setPreferredSize(new Dimension(500,150));

    // Set Constraints of the displayed objects
    // Add all the objects into the fram so they can be displayed
    this.setConstraints();

    frame.pack();
    frame.setVisible(true);
}

Conclusion

The new GridBagLayout manager keeps the game elements stationary on the screen during play. Previous changes to field size could have an effect on the FlowLayout manager and cause the objects to move around. This is annoying to the User of the program. The spacing of the elements under GridBagLayot is adequate and the game has a more professional feel to it from the FlowLayout manager.

In a perfect world the betLabel would share the space with betTextField. They go together so they should be near each other. The bankroll display looks a little out of place even with the info message at the top of the window. There are also shuffling issues to deal with. The testing so far has been done without shuffling the deck. The CardDeck class can also run out of cards as no arrangement has been made to reshuffle when the deck is low on cards.

One bug has been discovered during testing. The first hand is a push and the bank roll remains at 1000 in the subsequent round. But the next hand is a loser if Stand is pressed. The program takes out double the amount when the new Bankroll is displayed at the start of the next round.

References

  1. The Java Tutorials: How to Use GridBagLayout http://docs.oracle.com/javase/tutorial/uiswing/layout/gridbag.html

Author: Nasty Old Dog

Validate XHTML 1.0

Sunday, May 19, 2013

Graphical BlackJack Game Logic

Graphical BlackJack: Adding Game Logic

Graphical BlackJack: Adding Game Logic

Introduction

In the original text based version of this game there was a considerable amount of game logic. The game would proceed in the following fashion:

  1. Game Start Up
  2. Player Enters Bet
  3. Initial Hands for Player and Dealer are displayed
  4. Player decides to Hit or Stand (This is looping code the player can Hit until the hand Busts or the Player presses Stand or the Hand = 21)
  5. The Dealer hand his played using the rule dealer must hit 16 or under
  6. The Hands are scored the closest to 21 without going over wins. Ties mean the bet money is returned and player starts a new hand
  7. Go back to step 2.
  8. Player quits by pressing the Windows red "X" button

Most of the above logic can be cut and pasted into the actionPerformed method with little revision. There will some Button handling things to add.

  • When the game starts the Hit and Stand buttons are disabled and the Bet button is enabled
  • After the Bet is placed the Bet button is disabled and the Hit and Stand buttons are enabled
  • When the stand button is pressed or the hand is bust or at 21 and before the dealer logic is started the Hit and Stand buttons are disabled.
  • After the winner is determined and it's time for a new round the Bet button needs to be enabled again.

JTextField problems

There is the issue (it's been ignored for a while) that the JTextField where the user enters the Bet amount needs to be enlarged. Currently it is appearing so small that when the user clicks in it to type something nothing appears. A simple call to the JTextField's setPreferredSize method placed in the BlackJack run method before the JTextField gets added for diplay will fix this:

  • betField.setPreferredSize(new Dimension(75,25));

Ouput

Start Up

  • Bet button active
    • nothing to be done the default state for a created JButton is Enabled = true
  • hit and stand inactive
    • add hitButton.setEnabled(false) and same for standButton
    • This is placed in run method after the buttons are created
  • Enter JTextField value and press bet
    • deactivate bet button
    • activate hit and stand
    • if no value entered in JTextField or non-numeric entered clear JTextField and leave bet button active and essentially do nothing until the user enters something meaningful (a try-catch block is used to capture this)
    • These changes are added to the actionPerformed method (check if "bet" action command has come in)

Code Changes to BlackJack

public void actionPerformed(ActionEvent ae) {
    if ("hit".equals(ae.getActionCommand())) {
        // Hit button press add a card to the player hand then redisplay frame
        this.player.deal(this.card);

        // Once the card is added you need to revalidate the frame and repaint
        frame.validate();
        frame.repaint();
    }
    if ("bet".equals(ae.getActionCommand())) {
        String betStr = betField.getText();
        try {
            this.bet = Integer.parseInt(betStr);
        } 
        catch (Exception ex) {
            // set bet = 0 and reset the JTextField to empty the box of bad text
            // return (do nothing user entered invalid text)
            // At some point need to add a Message display to window to 
            // Inform the user of problems
            betField.setText("");
            bet = 0;
            return;
        }
        // Bet is an int so go ahead and deactivate Bet Button enable hit and stand
        betButton.setEnabled(false);
        hitButton.setEnabled(true);
        standButton.setEnabled(true);
        frame.validate();
        frame.repaint();
    }
}

/**
 * run method that does all the work of this class
 */
@Override
public void run() {
    Container contentPane = frame.getContentPane();


    // Set the layout manager for the JFrame using the contentPaine
    FlowLayout layout = new FlowLayout();
    contentPane.setLayout(layout);

    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setPreferredSize(new Dimension(300,500));

    // Need a label for the JTextField
    JLabel betLabel = new JLabel("Bet Amount:");

    // set the different Action Command Strings
    betButton.setActionCommand("bet");
    hitButton.setActionCommand("hit");
    standButton.setActionCommand("stand");

    // set this BlackJack class as the handler for each of the buttons
    betButton.addActionListener(this);
    hitButton.addActionListener(this);
    standButton.addActionListener(this);

    // Disable Hit and Stand buttons
    hitButton.setEnabled(false);
    standButton.setEnabled(false);

    // Set size of the bet JTextField 
    betField.setPreferredSize(new Dimension(75,25));

Output

Here is the start up screen notice the hit and stand buttons are greyed out. The user has also entered "abc" for the bet amount

After pressing the Bet button on the bad bet text the JTextField is reset and the bet Button remains active with the other buttons inactive.

Entering a number in the bet field and pressing the Bet button, the Bet button becomes inactivated, and the hit and stand buttons are now active

There are still a couple of issues lingering with this user input. The bet field is an int type. It can take on negative numbers which would mean that the user would get paid if they lose the hand. This can be handled by taking the absolute value of the entry or resetting and forcing the user to reenter.

The player can also enter 0 as a valid bet. So the second way above for handling a bad bet is probably the easiest. Just test for input less than or equal to 0 and reset if it is.

One more problem that is not dealt with is the fact that a bet is not checked against the bankroll. Losing all your money means your done playing in Vegas it may be a good thing to do here. But this problem will be deferred until later.

The following code added after the exception handling block (ie. try-catch) should fix the problems

// Check if bet is less than or equal to 0 and reset if it is
if (bet <= 0) {
    betField.setText("");
    bet = 0;
    return;
}

Add in Game Logic

While weaving in the game logic it became apparent that there is a need to have a message display and a continue button. Otherwise when the hands are scored the program will cause the hands to disappear (or at least that is the functionality you want) and the Player won't know what the status of the hand is and feel the program is cheating.

There also needs to be a screen area set aside to display the bankroll. That is how the Player determines how well they are doing. The JLabel will suffice for displaying text. Two more will be added one for an informational display and one for the Bankroll. We may want to place the bankroll JLabel inside a JPanel so it can have a titled border.

What was changed?

  • Added fields into BlackJack class for the continue button and the extra labels
  • Set the dimensions of the player hand and the dealer hand. This helps in maintaining the window at a constant size when there are no cards in the hand yet
  • Added a handleDealer method to do the game logic for the dealer's hand and scoring for determining win or lose
  • Added a resetFrame method to start a new round with no cards in the hands
    • remove all the swing objects from frame with a call to the ContentPane.removeAll method
    • then add back all the elements just like what happens at start up
  • worked the logic into actionPerformed
    • when to activate continue button and deactivate the others
    • The buttons keep the state of the game by being activated or deactivated
  • Moved some of the elements around to prepare for using a better Layout Manager than FlowLayout in the future
  • Got rid of all the text output method calls. Everything happens on the screen

Code for BlackJack

/*
 * BlackJack a simple implementation upgrade for graphical card display
 */
package blackjack;

import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.net.MalformedURLException;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

/**
 * This class controls the game logic of this simple implementation of 
 * Black Jack. A more profession version would allow for multiplayer games
 * via a game server so you could play your friends on the internet. 
 * Some improvements would be:
 * <p>
 *    Adding insurance (players could insure bets against a dealer having blackjack) <p>
 *    Increase the payout for a blackjack hand to 1.5 x the original bet <p>
 *    Double Down <p>
 *    Splitting pairs <p>
 * 
 * @author Nasty Old Dog
 */
public class BlackJack implements ActionListener, Runnable {

    int bankroll = 1000;
    int bet = 0;
    CardDeck card;
    Hand player;
    Hand dealer;
    BlackJackStrategy strategy = new BlackJackStrategy();
    JFrame frame = new JFrame("BlackJack");
    JButton hitButton = new JButton("Hit");
    JButton standButton = new JButton("Stand");
    JButton betButton = new JButton("Bet");
    JButton continueButton = new JButton("Continue");
    JLabel bankLabel = new JLabel("Bankroll: " + bankroll);
    JLabel infoLabel = new JLabel("Welcome to BlackJack");
    JTextField betField = new JTextField();
    // Need a label for the JTextField
    JLabel betLabel = new JLabel("Bet Amount:");

    public BlackJack() {
        this.card = new CardDeck(this.strategy);
    }

    /** 
     * handles the Dealer hand playing logic
     */
    public void handleDealer(int pscr) {
        int dscr = 0;
        String msg = "Player score: " + pscr + "   Dealer: " + dscr;
        if (pscr > 21) {
            msg = msg + ": BUST LOSER!!!";
            bankroll -= bet;
        } else {
            for (dscr = dealer.scoreHand();
                    dscr < 17;
                    dscr = dealer.scoreHand()) {
                this.dealer.deal(card);
            }

            // Code to score the game and settle the bet will go here
            if (pscr > dscr) {
                msg = msg + ": You Win!!!";
                bankroll = bankroll + bet;
            } else if (dscr > 21) {
                msg = msg + ": You Win!! Dealer has BUSTED";
                bankroll += bet;
            } else if (pscr == dscr) {
                msg = msg + ": PUSH";
            } else {
                msg = msg + ": LOSER!!!";
                bankroll -= bet;
            }
        }

        // Print message about who won. Then wait for continue button to be 
        // pressed so user can have time to look at cards and verify the 
        // situattion
        infoLabel.setText(msg);
        betButton.setEnabled(false);
        hitButton.setEnabled(false);
        standButton.setEnabled(false);
        continueButton.setEnabled(true);
        frame.validate();
        frame.repaint();
    }

    /**
     * Handles all the graphics user input
     * @param ae ActionEvent generated by JButtons in the frame
     */
    @Override
    public void actionPerformed(ActionEvent ae) {
        if ("hit".equals(ae.getActionCommand())) {
            // Hit button press add a card to the player hand then redisplay frame
            this.player.deal(this.card);

            // Once the card is added you need to revalidate the frame and repaint
            frame.validate();
            frame.repaint();
            int scr = this.player.scoreHand();
            if (scr > 21) {
                // Player has gone bust display a message and activate continue button
                bankroll -= bet;
                this.handleDealer(scr);
            }
            if (scr == 21) {
                // Player has 21 and will win if dealer has <21 or bust
                this.handleDealer(scr);

            }
            // Otherwise do nothing player decides whether to hit or stand
        }
        if ("stand".equals(ae.getActionCommand())) {
            // Player is done getting cards time to do the dealer stuff
            this.handleDealer(this.player.scoreHand());
            betButton.setEnabled(false);
            standButton.setEnabled(false);
            betButton.setEnabled(false);
            continueButton.setEnabled(true);

        }
        if ("bet".equals(ae.getActionCommand())) {
            String betStr = betField.getText();
            try {
                this.bet = Integer.parseInt(betStr);
            } catch (Exception ex) {
                // set bet = 0 and reset the JTextField to empty the box of bad text
                // return (do nothing user entered invalid text)
                // At some point need to add a Message display to window to 
                // Inform the user of problems
                betField.setText("");
                bet = 0;
                return;
            }

            // Check if bet is less than or equal to 0 and reset if it is
            if (bet <= 0) {
                betField.setText("");
                bet = 0;
                return;
            }

            // Bet is an int so go ahead and deactivate Bet Button enable hit and stand
            betButton.setEnabled(false);
            hitButton.setEnabled(true);
            standButton.setEnabled(true);
            player.deal(this.card);
            dealer.deal(this.card);
            player.deal(this.card);
            dealer.deal(this.card);
            frame.validate();
            frame.repaint();
        }
        if ("continue".equals(ae.getActionCommand())) {
            betButton.setEnabled(true);
            hitButton.setEnabled(false);
            standButton.setEnabled(false);
            continueButton.setEnabled(false);
            bankLabel.setText("Bankroll: " + bankroll);
            infoLabel.setText("Welcome to BlackJack, Place your bet!");
            betField.setText("");
            bet = 0;
            player = new Hand("Player");
            dealer = new Hand("Dealer");
            this.resetFrame();
        }
    }

    /**
     * This reset the entire frame and reloads all the displayable objects in 
     * the proper order
     */
    public void resetFrame() {
        frame.getContentPane().removeAll();
        player.setPreferredSize(new Dimension(500,150));
        dealer.setPreferredSize(new Dimension(500,150));

        frame.add(infoLabel);
        frame.add(dealer);
        frame.add(player);
        frame.add(hitButton);
        frame.add(standButton);
        frame.add(continueButton);
        frame.add(betLabel);
        frame.add(betField);
        frame.add(betButton);
        frame.add(bankLabel);
        frame.validate();
        frame.repaint();
    }

    /**
     * run method that does all the work of this class
     */
    @Override
    public void run() {
        Container contentPane = frame.getContentPane();


        // Set the layout manager for the JFrame using the contentPaine
        FlowLayout layout = new FlowLayout();
        contentPane.setLayout(layout);

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setPreferredSize(new Dimension(550, 450));


        // set the different Action Command Strings
        betButton.setActionCommand("bet");
        hitButton.setActionCommand("hit");
        standButton.setActionCommand("stand");
        continueButton.setActionCommand("continue");

        // set this BlackJack class as the handler for each of the buttons
        betButton.addActionListener(this);
        hitButton.addActionListener(this);
        standButton.addActionListener(this);
        continueButton.addActionListener(this);

        // Disable Hit and Stand buttons
        hitButton.setEnabled(false);
        standButton.setEnabled(false);
        continueButton.setEnabled(false);

        // Set size of the bet JTextField 
        betField.setPreferredSize(new Dimension(75, 25));

        // Set up some hands
        player = new Hand("Player");
        dealer = new Hand("Dealer");

        // Set a preferred size for the Hand JPanel
        player.setPreferredSize(new Dimension(500,150));
        dealer.setPreferredSize(new Dimension(500,150));

        // Add all the objects into the fram so they can be displayed
        frame.add(dealer);
        frame.add(player);
        frame.add(hitButton);
        frame.add(standButton);
        frame.add(betLabel);
        frame.add(betField);
        frame.add(betButton);
        frame.add(bankLabel);
        frame.add(infoLabel);
        frame.add(continueButton);
        frame.pack();
        frame.setVisible(true);
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        try {
            CardImageFactory.initCardImageFactory("http://www.jfitz.com/cards/classic-playing-cards.png");
            BlackJack game = new BlackJack();

            SwingUtilities.invokeLater(game);
        } catch (MalformedURLException ex) {
            System.out.println(ex.getMessage());
        } catch (IOException ex) {
            System.out.println(ex.getMessage());
        }
    }
}

There were a bunch of changes and code has been woven in all over the place. The coding happened fast and furious, rather than upset the train of thought I plowed through it. Hopefully the reader can match up code to functionality on the screen.

Output at Startup

The start up screen now looks like this:

Output after round played

After User finishes a round then the Continue Button is active:

Conclusion

This is almost complete. It now plays a game of BlackJack but there are some small things missing. There are also some Graphics design issues that need to be addressed

  • BlackJack logic
    • Need to shuffle the cards.
    • Need to track how many cards have been used and reshuffle when the CardDeck is low
  • Graphics fixes
    • Need a better Layout Manager that fixes the objects displayed in a more fixed fashion
    • The message label should be at the top of the screen where it will be noticed more
    • The continue button should go along side the Hit and Stand Buttons
    • The bankroll display should be enhanced to be in a JPanel with a title border and the amount inside the JPanel as a JLabel
    • It would be nice to have the bankroll display and bet amount controls on the right hand side of the screen. Maybe when the Layout Manager is fixed this will be easy to do.

Author: Nasty Old Dog

Validate XHTML 1.0