package javass.gui; import java.util.Map; import java.util.concurrent.BlockingQueue; import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableMap; import javafx.geometry.HPos; import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.control.Label; import javafx.scene.effect.GaussianBlur; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.BorderPane; import javafx.scene.layout.GridPane; import javafx.scene.layout.HBox; import javass.jass.Card; import javass.jass.Card.Color; import javass.jass.Card.Rank; import javass.jass.Jass; import javass.jass.PlayerId; import javass.jass.TeamId; import javafx.scene.layout.Pane; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import javafx.scene.shape.Rectangle; import javafx.scene.text.Text; import javafx.scene.text.TextAlignment; import javafx.stage.Stage; /** * Represents the graphic interface of a human player * @author Célia Houssiaux * @author Charles Beauville * */ public final class GraphicalPlayer { private static final double TRUMP_H_AND_W = 101; private static final double CARD_HEIGHT = 180; private static final double CARD_WIDTH = 120; private static final int COLUMN_TEAM = 0; private static final int COLUMN_TURNPOINTS = 1; private static final int COLUMN_TRICKPOINTS = 2; private static final int COLUMN_TOTAL_LABEL = 3; private static final int COLUMN_GAMEPOINTS = 4; private static final int CARD_ROW_SPAN = 3; private static final int CARD_COL_SPAN = 1; private static final int CARD_ROW_INDEX = 0; private static final int OWN_CARD_ROW_INDEX = 2; private static final int LEFT_CARD_COL_INDEX = 0; private static final int RIGHT_CARD_COL_INDEX = 2; private static final int MIDDLE_CARD_COL_INDEX = 1; private static final int TRUMP_ROW_INDEX = 1; private static final int TRUMP_COL_INDEX = 1; private static final double FULL_OPACITY = 1; private static final double NOT_PLAYABLE_OPACITY = 0.2; private final PlayerId ownId; private final Map playerNames; private final ScoreBean sb; private final TrickBean tb; private final HandBean hb; private final StackPane victory; private final BlockingQueue queue; private final ObservableMap cardMap = FXCollections .observableHashMap(); private final ObservableMap trumpMap = FXCollections .observableHashMap(); /** * The constructor for the graphic interface of a given player. * @param ownId the id of the player playing on the interface. * @param playerNames the map between the IDs of the players and their names. * @param sb the score bean of the game. * @param tb the trick bean of the game. * @param hb the hand bean of the game. * @param queue a queue where the card t obe played by the player is put. */ public GraphicalPlayer(PlayerId ownId, Map playerNames, ScoreBean sb, TrickBean tb, HandBean hb, BlockingQueue queue) { this.ownId = ownId; this.playerNames = playerNames; this.sb = sb; this.tb = tb; this.hb = hb; this.queue = queue; //Creates the complete pane for the game BorderPane game = new BorderPane(createTrickPane(), createScorePane(), null, createHandPane(), null); this.victory = new StackPane(game, createVictoryPanes(TeamId.TEAM_1), createVictoryPanes(TeamId.TEAM_2)); } /** * Creates the stage of the game. * @return a Stage for the game. */ public Stage createStage() { Stage stage = new Stage(); stage.setTitle("Javass - " + playerNames.get(ownId)); stage.setScene(new Scene(victory)); return stage; } private Pane createScorePane() { //Initializes the pane GridPane scorePane = new GridPane(); scorePane.setStyle( "-fx-font: 16 Optima; -fx-background-color: lightgray;-fx-padding: 5px; -fx-alignment: center;"); //Write the player names for each team in the score pane Node team1 = new Text(playerNames.get(PlayerId.PLAYER_1) + " et " + playerNames.get(PlayerId.PLAYER_3) + " : "); Node team2 = new Text(playerNames.get(PlayerId.PLAYER_2) + " et " + playerNames.get(PlayerId.PLAYER_4) + " : "); scorePane.add(team1, COLUMN_TEAM, TeamId.TEAM_1.ordinal()); scorePane.add(team2, COLUMN_TEAM, TeamId.TEAM_2.ordinal()); //Sets the scores next to each team everytime they change. for (TeamId teamId : TeamId.ALL) { Label turnPoints = new Label(); Label trickPoints = new Label(); Label scTotal = new Label(" /Total : "); Label totalPoints = new Label(); StringProperty trickPointsProperty = new SimpleStringProperty(); sb.turnPointsProperty(teamId).addListener((o, oV, nV) -> { trickPointsProperty .setValue((nV.intValue() - oV.intValue()) < 0 ? "" : "(+" + (nV.intValue() - oV.intValue()) + ")"); trickPoints.setText(trickPointsProperty.getValue()); }); trickPoints.setTextAlignment(TextAlignment.LEFT); turnPoints.textProperty() .bind(Bindings.convert(sb.turnPointsProperty(teamId))); totalPoints.textProperty() .bind(Bindings.convert(sb.totalPointsProperty(teamId))); //Add the points texts to the pane. scorePane.add(turnPoints, COLUMN_TURNPOINTS, teamId.ordinal()); scorePane.add(trickPoints, COLUMN_TRICKPOINTS, teamId.ordinal()); scorePane.add(scTotal, COLUMN_TOTAL_LABEL, teamId.ordinal()); scorePane.add(totalPoints, COLUMN_GAMEPOINTS, teamId.ordinal()); } return scorePane; } private Pane createTrickPane() { //Initializes the pane GridPane trickPane = new GridPane(); trickPane.setStyle( "-fx-background-color: whitesmoke; fx-padding: 5px; -fx-border-width: 3px 0px; -fx-border-style: solid; -fx-border-color: gray;-fx-alignment: center;"); //Associates a trump image with a trump color in the trump map, if it is empty. if(trumpMap.isEmpty()) for (Card.Color color : Color.ALL) { trumpMap.put(color, new Image("/trump_" + color.ordinal() + ".png")); } //Sets up the image of the trump color. ImageView trumpIm = new ImageView(); trumpIm.imageProperty() .bind(Bindings.valueAt(trumpMap, tb.trumpProperty())); trumpIm.setFitWidth(TRUMP_H_AND_W); trumpIm.setFitHeight(TRUMP_H_AND_W); //Adds the trump image to the pane. trickPane.add(trumpIm, TRUMP_COL_INDEX, TRUMP_ROW_INDEX); GridPane.setHalignment(trumpIm, HPos.CENTER); //Adds the played card of each player next to their name each trick. for (PlayerId pId : PlayerId.ALL) { //Sets up the halo effect on the current winning card of the trick. Rectangle halo = new Rectangle(CARD_WIDTH, CARD_HEIGHT); halo.setStyle( "-fx-arc-width: 20; -fx-arc-height: 20;-fx-fill: transparent;-fx-stroke: lightpink;-fx-stroke-width: 5;-fx-opacity: 0.5;"); halo.setEffect(new GaussianBlur(4)); halo.visibleProperty() .bind(tb.winningPlayerProperty().isEqualTo(pId).and(Bindings.createBooleanBinding(() -> !(tb.trick().values().isEmpty()), tb.trick()))); //Gets the name of the player Text name = new Text(playerNames.get(pId)); name.setStyle("-fx-font: 14 Optima;"); //Creates a pane with the card image and the halo effect. ImageView img = getImage(pId); StackPane hAndC = new StackPane(img, halo); if (pId == ownId) { //Creates a vertical box with the card pane and the name at the bottom of it. VBox ownCard = new VBox(hAndC, name); ownCard.setStyle("-fx-padding: 5px;-fx-alignment: center;"); trickPane.add(ownCard, MIDDLE_CARD_COL_INDEX, OWN_CARD_ROW_INDEX); } else { //Creates a vertical box with the card pane and the name on top of it. VBox card = new VBox(name, hAndC); card.setStyle("-fx-padding: 5px;-fx-alignment: center;"); if (pId.ordinal() == (ownId.ordinal() + 1) % PlayerId.COUNT) { trickPane.add(card, RIGHT_CARD_COL_INDEX, CARD_ROW_INDEX, CARD_COL_SPAN, CARD_ROW_SPAN); card.setAlignment(Pos.CENTER); } if (pId.ordinal() == (ownId.ordinal() + 2) % PlayerId.COUNT) trickPane.add(card, MIDDLE_CARD_COL_INDEX, CARD_ROW_INDEX); if (pId.ordinal() == (ownId.ordinal() + 3) % PlayerId.COUNT) { trickPane.add(card, LEFT_CARD_COL_INDEX, CARD_ROW_INDEX, CARD_COL_SPAN, CARD_ROW_SPAN); card.setAlignment(Pos.CENTER); } } } return trickPane; } private Pane createHandPane() { //Initialize the horizontal box for the hand of the player. HBox handPane = new HBox(); handPane.setStyle( "-fx-background-color: lightgray; -fx-spacing: 5px; -fx-padding: 5px;"); //For each card in the hand draws it if it has not been played. for (int i = 0; i < Jass.HAND_SIZE; i++) { int index = i; //Sets up the image of the card ImageView img = new ImageView(); img.imageProperty().bind( Bindings.valueAt(cardMap, Bindings.valueAt(hb.hand(), i))); img.setFitWidth(CARD_WIDTH / 2); img.setFitHeight(CARD_HEIGHT / 2); //Plays the card when it is clicked img.setOnMouseClicked(e -> { try { queue.put(hb.hand().get(index)); } catch (InterruptedException e1) { throw new Error(e1); } ; }); //Decrease the opacity of unplayable cards BooleanBinding isPlayable = Bindings.createBooleanBinding( () -> hb.playableCards().contains(hb.hand().get(index)), hb.playableCards(), hb.hand()); img.opacityProperty() .bind(Bindings.when(isPlayable).then(FULL_OPACITY).otherwise(NOT_PLAYABLE_OPACITY)); img.disableProperty().bind(isPlayable.not()); //Adds the image of the card to the pane handPane.getChildren().add(img); } return handPane; } private Pane createVictoryPanes(TeamId team) { //Creates the victory message with the winning team. Text message = new Text(); message.textProperty() .bind(Bindings.format( "%s win the game with a total of %d to %d", team.toString(), sb.totalPointsProperty(team), sb.totalPointsProperty(team.other()))); //Sets up the pane with victory message to display. BorderPane victoryPane = new BorderPane(message); victoryPane .setStyle("-fx-font: 16 Optima;-fx-background-color: white;"); victoryPane.visibleProperty() .bind(sb.winningTeamProperty().isEqualTo(team)); return victoryPane; } private ImageView getImage(PlayerId pId) { ImageView img = new ImageView(); //Associates a card image with a card in the card map, if it is empty. if (cardMap.isEmpty()) { for (Rank rank : Card.Rank.ALL) { for (Color color : Card.Color.ALL) { Card c = Card.of(color, rank); cardMap.put(c, new Image("/card_" + color.ordinal() + "_" + rank.ordinal() + "_240.png")); } } } //Finds the image at the given pId. img.imageProperty().bind( Bindings.valueAt(cardMap, Bindings.valueAt(tb.trick(), pId))); img.setFitWidth(CARD_WIDTH); img.setFitHeight(CARD_HEIGHT); return img; } }