diff --git a/gui/GraphicalPlayer.java b/gui/GraphicalPlayer.java new file mode 100644 index 0000000..c9417b6 --- /dev/null +++ b/gui/GraphicalPlayer.java @@ -0,0 +1,314 @@ +package ch.epfl.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 ch.epfl.javass.jass.Card; +import ch.epfl.javass.jass.Card.Color; +import ch.epfl.javass.jass.Card.Rank; +import ch.epfl.javass.jass.Jass; +import ch.epfl.javass.jass.PlayerId; +import ch.epfl.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; + } +} \ No newline at end of file diff --git a/gui/GraphicalPlayerAdapter.java b/gui/GraphicalPlayerAdapter.java new file mode 100644 index 0000000..4030aae --- /dev/null +++ b/gui/GraphicalPlayerAdapter.java @@ -0,0 +1,110 @@ +package ch.epfl.javass.gui; + +import java.util.Map; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + +import javafx.application.Platform; + +import ch.epfl.javass.jass.Card; +import ch.epfl.javass.jass.CardSet; +import ch.epfl.javass.jass.Player; +import ch.epfl.javass.jass.PlayerId; +import ch.epfl.javass.jass.Score; +import ch.epfl.javass.jass.TeamId; +import ch.epfl.javass.jass.Trick; +import ch.epfl.javass.jass.TurnState; +import ch.epfl.javass.jass.Card.Color; + +/** + * A {@link Player} that has a graphical interface. + * @author Charles BEAUVILLE + * @author Celia HOUSSIAUX + * + */ +public final class GraphicalPlayerAdapter implements Player { + + private final ScoreBean scoreBean; + private final HandBean handBean; + private final TrickBean trickBean; + private GraphicalPlayer graphicalPlayer; + private final BlockingQueue queue; + + /** + * Constructor of a {@link Player} that has a graphical interface. + */ + public GraphicalPlayerAdapter() { + scoreBean = new ScoreBean(); + handBean = new HandBean(); + trickBean = new TrickBean(); + + queue = new ArrayBlockingQueue(1); + } + + @Override + public Card cardToPlay(TurnState state, CardSet hand) { + + Platform.runLater(() -> { + handBean.setPlayableCards(state.trick().playableCards(hand)); + }); + + Card c; + try { + c = queue.take(); + Platform.runLater(() -> { + handBean.setPlayableCards(CardSet.EMPTY); + }); + return c; + } catch (InterruptedException e) { + throw new Error(e); + } + } + + @Override + public void setPlayers(PlayerId ownId, Map playerNames) { + graphicalPlayer = new GraphicalPlayer(ownId, playerNames, scoreBean, + trickBean, handBean, queue); + Platform.runLater(() -> { + graphicalPlayer.createStage().show(); + }); + } + + @Override + public void setTrump(Color trump) { + Platform.runLater(() -> { + trickBean.setTrump(trump); + }); + } + + @Override + public void setWinningTeam(TeamId winningTeam) { + Platform.runLater(() -> { + scoreBean.setWinningTeam(winningTeam); + }); + } + + @Override + public void updateHand(CardSet newHand) { + Platform.runLater(() -> { + handBean.setHand(newHand); + }); + } + + @Override + public void updateScore(Score score) { + Platform.runLater(() -> { + for (TeamId id : TeamId.ALL) { + scoreBean.setTurnPoints(id, score.turnPoints(id)); + scoreBean.setGamePoints(id, score.gamePoints(id)); + scoreBean.setTotalPoints(id, score.totalPoints(id)); + } + }); + } + + @Override + public void updateTrick(Trick newTrick) { + Platform.runLater(() -> { + trickBean.setTrick(newTrick); + }); + } +} diff --git a/gui/ScoreBean.java b/gui/ScoreBean.java new file mode 100644 index 0000000..da968a2 --- /dev/null +++ b/gui/ScoreBean.java @@ -0,0 +1,117 @@ +package ch.epfl.javass.gui; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import ch.epfl.javass.jass.TeamId; +import javafx.beans.property.*; + +/** + * A bean containing the score properties. + * @author Charles BEAUVILLE + * @author Celia HOUSSIAUX + * + */ +public final class ScoreBean { + + private final SimpleIntegerProperty turnPointsTeam1Property = new SimpleIntegerProperty(); + private final SimpleIntegerProperty turnPointsTeam2Property = new SimpleIntegerProperty(); + private final SimpleIntegerProperty gamePointsTeam1Property = new SimpleIntegerProperty(); + private final SimpleIntegerProperty gamePointsTeam2Property = new SimpleIntegerProperty(); + private final SimpleIntegerProperty totalPointsTeam1Property = new SimpleIntegerProperty(); + private final SimpleIntegerProperty totalPointsTeam2Property = new SimpleIntegerProperty(); + private final SimpleObjectProperty winningTeamProperty = new SimpleObjectProperty(); + private final Map turnPointsPropertyMap; + { + Map temp = new HashMap(); + temp.put(TeamId.TEAM_1, turnPointsTeam1Property); + temp.put(TeamId.TEAM_2, turnPointsTeam2Property); + turnPointsPropertyMap = Collections.unmodifiableMap(temp); + } + private final Map gamePointsPropertyMap; + { + Map temp = new HashMap(); + temp.put(TeamId.TEAM_1, gamePointsTeam1Property); + temp.put(TeamId.TEAM_2, gamePointsTeam2Property); + gamePointsPropertyMap = Collections.unmodifiableMap(temp); + } + private final Map totalPointsPropertyMap; + { + Map temp = new HashMap(); + temp.put(TeamId.TEAM_1, totalPointsTeam1Property); + temp.put(TeamId.TEAM_2, totalPointsTeam2Property); + totalPointsPropertyMap = Collections.unmodifiableMap(temp); + } + + /** + * Gives the property of the turn points of a given team. + * @param team the TeamId of the team to get the turn points from. + * @return a ReadOnlyIntegerProperty the turn points property of the given team. + */ + public ReadOnlyIntegerProperty turnPointsProperty(TeamId team) { + return turnPointsPropertyMap.get(team); + } + + /** + * Sets the turn points property of a given team to a given number. + * @param team a teamId the team to change the turn points. + * @param newTurnPoints an int the bew number of points of the team. + */ + public void setTurnPoints(TeamId team, int newTurnPoints) { + turnPointsPropertyMap.get(team).set(newTurnPoints); + } + + /** + * Gives the property of the game points of a given team. + * @param team the TeamId of the team to get the game points from. + * @return a ReadOnlyIntegerProperty the game points property of the given team. + */ + public ReadOnlyIntegerProperty gamePointsProperty(TeamId team) { + return gamePointsPropertyMap.get(team); + } + + /** + * Sets the game points property of a given team to a given number. + * @param team a teamId the team to change the game points. + * @param newGamePoints an int the new number of points of the team. + */ + public void setGamePoints(TeamId team, int newGamePoints) { + gamePointsPropertyMap.get(team).set(newGamePoints); + } + + /** + * Gives the property of the total points of a given team. + * @param team the TeamId of the team to get the total points from. + * @return a ReadOnlyIntegerProperty the total points property of the given team. + */ + public ReadOnlyIntegerProperty totalPointsProperty(TeamId team) { + return totalPointsPropertyMap.get(team); + } + + /** + * Sets the total points property of a given team to a given number. + * @param team a teamId the team to change the total points. + * @param newTotalPoints an int the new number of points of the team. + */ + public void setTotalPoints(TeamId team, int newTotalPoints) { + totalPointsPropertyMap.get(team).set(newTotalPoints); + } + + /** + * Gives the property of the winning team. + * @return a ReadOnlyObjectProperty the winning team property of the game. + */ + public ReadOnlyObjectProperty winningTeamProperty() { + return winningTeamProperty; + } + + /** + * Sets the winning team property to the given team. + * @param winningTeam the team that won the game. + */ + public void setWinningTeam(TeamId winningTeam) { + winningTeamProperty.set(winningTeam); + } + +} diff --git a/gui/TrickBean.java b/gui/TrickBean.java new file mode 100644 index 0000000..889ba04 --- /dev/null +++ b/gui/TrickBean.java @@ -0,0 +1,68 @@ +package ch.epfl.javass.gui; + +import ch.epfl.javass.jass.Card; +import ch.epfl.javass.jass.Card.Color; +import ch.epfl.javass.jass.PlayerId; +import ch.epfl.javass.jass.Trick; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableMap; + +/** + * A bean containing the trick properties. + * @author Charles BEAUVILLE + * @author Celia HOUSSIAUX + * + */ +public final class TrickBean { + + private final SimpleObjectProperty trumpProperty = new SimpleObjectProperty(); + private final SimpleObjectProperty winningPlayerProperty = new SimpleObjectProperty(); + private final ObservableMap trickMap = FXCollections.observableHashMap(); + + /** + * Gives the trump property of the trick. + * @return a ReadOnlyObjectProperty of a the trump color. + */ + public ReadOnlyObjectProperty trumpProperty(){ + return trumpProperty; + } + + /** + * Sets the trump property to the given color. + * @param trump a color the trump color to be set. + */ + public void setTrump(Color trump){ + trumpProperty.set(trump); + } + + /** + * Gives an observable map of cards associated by player IDs. + * @return an observable map of cards associated by player IDs. + */ + public ObservableMap trick(){ + return FXCollections.unmodifiableObservableMap(trickMap); + } + + /** + * Set the trick property to the given trick. + * @param newTrick a trick to change the trick property. + */ + public void setTrick(Trick newTrick) { + trickMap.clear(); + + for (int i = 0; i < newTrick.size(); i++) + trickMap.put(newTrick.player(i), newTrick.card(i)); + + winningPlayerProperty.set(newTrick.winningPlayer()); + } + + /** + * Gives the property of the winning player of the trick. + * @return ReadOnlyObjectProperty the winning player property. + */ + public ReadOnlyObjectProperty winningPlayerProperty(){ + return winningPlayerProperty; + } +}