package netgame.fivecarddraw; import javafx.application.Platform; import javafx.stage.Stage; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.ButtonType; import javafx.scene.control.TextField; import javafx.scene.control.Alert; import javafx.scene.layout.BorderPane; import javafx.scene.layout.VBox; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.paint.Color; import javafx.scene.image.Image; import javafx.scene.text.Font; import javafx.scene.text.FontWeight; import javafx.geometry.Insets; import javafx.event.ActionEvent; import javafx.event.EventHandler; import netgame.common.*; import java.io.IOException; import java.util.Optional; /** * A window for one player in a two-player networked game of * Five Card Draw Poker. The window is created by the main * program, netgame.fivecarddraw.Main. *
When a PokerWindow is opened, it establishes a * a connection to a PokerHub at a specified host and port. * Once two players have connected to the hub, the hub will * manage a game of poker between the two players. Each * player is given $1000 to start. One player acts a * "dealer", and the dealer clicks a "DEAL" button to start * the game. (The role of dealer alternates between players.) * After the cards are dealt, there is a first round of * betting. The non-dealer player bets first. When either * player matches the other's bet, the betting round ends and * each player has a chance to discard some cards and draw * new ones. Then there is a second round of betting. When * that round ends, the players' hands are compared, and the * winner is announced. (Players also have the choice of * folding, instead of betting, which will end the game * immediately.) Note that the player's amount of money * can become negative, and nothing is done when that * happens. The game continues until one or the other * player quits. The other player is notified so that * the second player's program can also be terminated. *
For details of the communication protocol that
* is used between the PokerWindows and the PokerHub,
* see the comments on the PokerHub class.
*/
public class PokerWindow extends Stage {
private PokerClient connection; // Handles communication with the PokerHub; used to send messages to the hub.
private PokerGameState state; // Represents the state of the game, as seen by this player. The state is
// received as a message from the hub whenever the state changes. This
// variable changes only in the newState() method.
private boolean[] discard; // When the player is discarding cards, this array tells which cards the
// player wants to discard. discard[i] is true if the player is discarding
// the i-th card in the hand.
private PokerCard[] opponentHand; // The opponent's hand. This variable is null during the playing of a
// hand. It becomes non-null if the opponent's hand is sent to this
// player at the end of one hand of poker.
private Canvas canvas; // The canvas where the game is displayed, defined by the inner class, Display.
private Image cardImages; // An image holding pictures of all the cards. The Image is loaded
// as a resource by the PokerWindow constructor from a resource file
// "netgame/fivecarddraw/cards.png." (The program will be non-functional
// if that resource file is not there.)
private Button dealButton; // User interface components shown along the right side of the window.
private Button drawButton;
private Button betButton;
private Button callButton;
private Button passButton;
private Button foldButton;
private Button quitButton;
private TextField betInput;
private String message = ""; // text that is displayed on the canvas
private String messageFromServer = "";
private String money = "", opponentsMoney = "", pot = "";
/**
* The constructor sets up the window and makes it visible on the screen.
* It starts a thread that will open a connection to a PokerHub.
* The window will become operational when the game starts, or it will be closed
* and the program terminated if the connection attempt fails.
* @param hubHostName the host name or IP address where the PokerHub is listening.
* @param hubPort the port number where the PokerHub is listening.
*/
public PokerWindow(final String hubHostName, final int hubPort) {
cardImages = new Image("netgame/fivecarddraw/cards.png");
messageFromServer = "WAITING FOR CONNECTION";
canvas = new Canvas(550,575);
drawBoard();
canvas.setOnMousePressed( evt -> doClick(evt.getX(),evt.getY()) );
betInput = new TextField();
betInput.setEditable(false);
betInput.setPrefColumnCount(5);
VBox.setMargin(betInput,new Insets(0,10,0,15));
VBox controls = new VBox();
EventHandler Note that this method is called from a separate thread, not
* from the GUI event thread. In order to avoid synchronization
* issues, this method uses Platform.runLater() to carry
* out its task in the GUI event thread.
*/
protected void messageReceived(final Object message) {
Platform.runLater( () -> {
if (message instanceof PokerGameState)
newState( (PokerGameState)message );
else if (message instanceof String) {
messageFromServer = (String)message ;
drawBoard();
}
else if (message instanceof PokerCard[]) {
opponentHand = (PokerCard[])message;
drawBoard();
}
});
}
/**
* This method is called when the hub shuts down. That is a signal
* that the opposing player has quit the game. The user is informed
* of this, and the program is terminated.
*/
protected void serverShutdown(String message) {
Platform.runLater( () -> {
showMessage("Your opponent has quit.\nThe game is over.");
System.exit(0);
});
}
} // end nested class PokerClient
private static Font font16 = Font.font(16); //fonts for use in drawBoard
private static Font font24 = Font.font(24);
private static Font font38 = Font.font(38);
private void drawBoard() {
GraphicsContext g = canvas.getGraphicsContext2D();
g.setFill(Color.BEIGE);
g.fillRect(0,0,canvas.getWidth(), canvas.getHeight());
g.setStroke(Color.DARKRED);
g.setLineWidth(8);
g.strokeRect(0,0,canvas.getWidth(),canvas.getHeight());
g.setFill(Color.GREEN);
g.setFont(font24);
g.fillText(opponentsMoney,100,40);
g.fillText(money,100,550);
g.setFont(font38);
g.fillText(pot,150,300);
g.setFill(Color.DARKRED);
g.setFont(font16);
g.fillText(message,30,355);
g.fillText(messageFromServer,30,230);
if (state == null) {
// Still waiting for connections. Don't draw anything.
return;
}
if (state.hand == null) {
// This happens only while waiting for the first hand to be dealt.
// Draw outlines of the card locations for this player's hand.
g.setStroke(Color.DARKRED);
g.setLineWidth(2);
for (int x = 25; x < 500; x += 105)
g.strokeRect(x,380,80,124);
}
else {
// Draw the cards in this player's hand.
for (int i = 0; i < 5; i++) {
if (discard != null && discard[i])
drawCard(g,null,25+i*105,380);
else
drawCard(g,state.hand[i],25+i*105,380);
}
}
if (state.hand == null) {
// This happens only while waiting for the first hand to be dealt.
// Draw outlines of the card locations for the opponent's hand.
g.setStroke(Color.DARKRED);
g.setLineWidth(2);
for (int x = 25; x < 500; x += 105)
g.strokeRect(x,70,80,124);
}
else if (opponentHand == null) {
// The opponent's hand exists but is unknown. Draw it as face-down cards.
for (int i = 0; i < 5; i++)
drawCard(g,null,25+i*105,70);
}
else {
// The opponent's hand is known. Draw the cards.
for (int i = 0; i < 5; i++)
drawCard(g,opponentHand[i],25+i*105,70);
}
}
private void showMessage(String message) {
Alert alert = new Alert(Alert.AlertType.INFORMATION, message);
alert.showAndWait();
}
/**
* A class to define the action event handler that responds when the user clicks
* a button. The button that was clicked is given by evt.getSource(). Note that
* once an action is taken, the buttons that were enabled are disabled,
* to prevent the user from generating extra messages while the hub is
* processing the user's action.
*/
public void doAction(ActionEvent evt) {
Object src = evt.getSource();
if (src == quitButton) { // end the program
doQuit();
}
else if (src == dealButton) {
// send "deal" as a message to the hub, which will start the next hand of the game
dealButton.setDisable(true);
connection.send("deal");
}
else if (src == foldButton) {
// send "fold" as a message to the hub, which will end the game because this user folded
foldButton.setDisable(true);
betButton.setDisable(true);
passButton.setDisable(true);
callButton.setDisable(true);
betInput.setEditable(false);
betInput.setText("");
connection.send("fold");
}
else if (src == passButton) {
// send the integer 0 as a message, indicating that the user places no bet;
// this is only possible for the first bet in a betting round
foldButton.setDisable(true);
betButton.setDisable(true);
passButton.setDisable(true);
callButton.setDisable(true);
betInput.setEditable(false);
betInput.setText("");
connection.send(Integer.valueOf(0));
}
else if (src == callButton) {
// send an integer equal to the minimum possible bet as a message to the hub;
// this means "see" in the first betting round and "call" in the second, and
// it will end the current betting round.
foldButton.setDisable(true);
betButton.setDisable(true);
passButton.setDisable(true);
callButton.setDisable(true);
betInput.setEditable(false);
betInput.setText("");
connection.send(Integer.valueOf(state.amountToSee));
}
else if (src == drawButton) {
// Send the list of cards that the user wants to discard as a message to
// the hub. The cards are recorded in the discard array.
int ct = 0;
for (int i = 0; i < 5; i++) { // Count the number of discarded cards.
if (discard[i])
ct++;
}
if (ct == 0) {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION,
"Are you sure you want to draw NO cards?\n"
+"If not, click 'No', and select\n"
+ "the cards that you want to discard.",
ButtonType.NO, ButtonType.YES);
Optional