import javafx.application.Application; import javafx.stage.Stage; import javafx.scene.Scene; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.control.Button; import javafx.scene.image.Image; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.paint.Color; import javafx.scene.text.Font; /** * This class implements a simple card game. The user sees a card * and tries to predict whether the next card will be higher or * lower. Aces are the lowest-valued cards. If the user makes * three correct predictions, the user wins. If not, the * user loses. * * This class depends on several additional source code files: * Card.java, Hand.jave, Deck.java, and cards.png. */ public class HighLowGUI extends Application { public static void main(String[] args) { launch(args); } //--------------------------------------------------------------------- private Canvas board; // The canvas on which cards and message are drawn. private Image cardImages; // A single image contains the images of every card. private Deck deck; // A deck of cards to be used in the game. private Hand hand; // The cards that have been dealt so far. private String message; // A message drawn on the canvas, which changes // to reflect the state of the game. private boolean gameInProgress; // Set to true when a game begins and to false // when the game ends. /** * The start() method sets up the GUI and event handling. The root pane is * is a BorderPane. A canvas where cards are displayed occupies the center * position of the BorderPane. On the bottom is a HBox that holds three buttons. * ActionEvent handlers are set up to call methods defined elsewhere * in this class when the user clicks a button. */ public void start(Stage stage) { cardImages = new Image("cards.png"); board = new Canvas(4*99 + 20, 123 + 80); // space for 4 cards, with 20-pixel border // and space on the bottom for a message Button higher = new Button("Higher"); higher.setOnAction( e -> doHigher() ); Button lower = new Button("Lower"); lower.setOnAction( e -> doLower() ); Button newGame = new Button("New Game"); newGame.setOnAction( e -> doNewGame() ); HBox buttonBar = new HBox( higher, lower, newGame ); /* Improve the layout of the buttonBar. Without adjustment * the buttons will be grouped at the left end of the * HBox. My solution is to set their preferred widths * to make them all the same size and make them fill the * entire HBox. */ higher.setPrefWidth(board.getWidth()/3.0); lower.setPrefWidth(board.getWidth()/3.0); newGame.setPrefWidth(board.getWidth()/3.0); // buttonBar.setAlignment(Pos.CENTER); // Alternative layout adjustment: // group the buttons in the // center of the HBox. // higher.setMaxWidth(1000); // Alternative layout adjustment: // lower.setMaxWidth(1000); // increase the max size of the buttons // newGame.setMaxWidth(1000); // tell the HBox to let them grow. // HBox.setHgrow(higher, Priority.ALWAYS); // In this case, the buttons // HBox.setHgrow(lower, Priority.ALWAYS); // fill the HBox, but they are // HBox.setHgrow(newGame, Priority.ALWAYS); // not the same size. BorderPane root = new BorderPane(); root.setCenter(board); root.setBottom(buttonBar); doNewGame(); // set up for the first game Scene scene = new Scene(root); stage.setScene(scene); stage.setTitle("High/Low Game"); stage.setResizable(false); stage.show(); } // end start() /** * Called by an event handler when user clicks "Higher" button. * Check the user's prediction. Game ends if user guessed * wrong or if the user has made three correct predictions. */ private void doHigher() { if (gameInProgress == false) { // If the game has ended, it was an error to click "Higher", // So set up an error message and abort processing. message = "Click \"New Game\" to start a new game!"; drawBoard(); return; } hand.addCard( deck.dealCard() ); // Deal a card to the hand. int cardCt = hand.getCardCount(); Card thisCard = hand.getCard( cardCt - 1 ); // Card just dealt. Card prevCard = hand.getCard( cardCt - 2 ); // The previous card. if ( thisCard.getValue() < prevCard.getValue() ) { gameInProgress = false; message = "Too bad! You lose."; } else if ( thisCard.getValue() == prevCard.getValue() ) { gameInProgress = false; message = "Too bad! You lose on ties."; } else if ( cardCt == 4) { gameInProgress = false; message = "You win! You made three correct guesses."; } else { message = "Got it right! Try for " + cardCt + "."; } drawBoard(); } // end doHigher() /** * Called by an event handler when user clicks "Lower" button. * Check the user's prediction. Game ends if user guessed * wrong or if the user has made three correct predictions. */ private void doLower() { if (gameInProgress == false) { // If the game has ended, it was an error to click "Lower", // So set up an error message and abort processing. message = "Click \"New Game\" to start a new game!"; drawBoard(); return; } hand.addCard( deck.dealCard() ); // Deal a card to the hand. int cardCt = hand.getCardCount(); Card thisCard = hand.getCard( cardCt - 1 ); // Card just dealt. Card prevCard = hand.getCard( cardCt - 2 ); // The previous card. if ( thisCard.getValue() > prevCard.getValue() ) { gameInProgress = false; message = "Too bad! You lose."; } else if ( thisCard.getValue() == prevCard.getValue() ) { gameInProgress = false; message = "Too bad! You lose on ties."; } else if ( cardCt == 4) { gameInProgress = false; message = "You win! You made three correct guesses."; } else { message = "Got it right! Try for " + cardCt + "."; } drawBoard(); } // end doLower() /** * Called by the start() method, and called by an event handler if * the user clicks the "New Game" button. Start a new game. */ private void doNewGame() { if (gameInProgress) { // If the current game is not over, it is an error to try // to start a new game. message = "You still have to finish this game!"; drawBoard(); return; } deck = new Deck(); // Create the deck and hand to use for this game. hand = new Hand(); deck.shuffle(); hand.addCard( deck.dealCard() ); // Deal the first card into the hand. message = "Is the next card higher or lower?"; gameInProgress = true; drawBoard(); } // end doNewGame() /** * This method draws the message at the bottom of the * canvas, and it draws all of the dealt cards spread out * across the canvas. If the game is in progress, an extra * card is drawn face down representing the card to be dealt next. */ private void drawBoard() { GraphicsContext g = board.getGraphicsContext2D(); g.setFill( Color.DARKGREEN); g.fillRect(0,0,board.getWidth(),board.getHeight()); g.setFill( Color.rgb(220,255,220) ); g.setFont( Font.font(16) ); g.fillText(message,20,180); int cardCt = hand.getCardCount(); for (int i = 0; i < cardCt; i++) drawCard(g, hand.getCard(i), 20 + i * 99, 20); if (gameInProgress) drawCard(g, null, 20 + cardCt * 99, 20); } /** * Draws a card with top-left corner at (x,y). If card is null, * then a face-down card is drawn. The card images are from * the file cards.png; this program will fail without it. */ private void drawCard(GraphicsContext g, Card card, int x, int y) { int cardRow, cardCol; if (card == null) { cardRow = 4; // row and column of a face down card cardCol = 2; } else { cardRow = 3 - card.getSuit(); cardCol = card.getValue() - 1; } double sx,sy; // top left corner of source rect for card in cardImages sx = 79 * cardCol; sy = 123 * cardRow; g.drawImage( cardImages, sx,sy,79,123, x,y,79,123 ); } // end drawCard() } // end class HighLowGUI