Skip to content

Commit a0fb10f

Browse files
committed
ws: first commit
1 parent 7f7f115 commit a0fb10f

File tree

7 files changed

+217
-47
lines changed

7 files changed

+217
-47
lines changed

src/ch/epfl/chacun/GameState.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public enum Action {
6565

6666
Objects.requireNonNull(players);
6767
players = List.copyOf(players);
68-
Preconditions.checkArgument(players.size() >= MIN_PLAYER_COUNT);
68+
//Preconditions.checkArgument(players.size() >= MIN_PLAYER_COUNT);
6969

7070
Preconditions.checkArgument(tileToPlace == null ^ nextAction == Action.PLACE_TILE);
7171
}
@@ -446,4 +446,8 @@ private GameState withFinalPointsCounted() {
446446
return new GameState(players, tileDecks, null, newBoard, Action.END_GAME, newMessageBoard);
447447
}
448448

449+
public GameState withPlayers(List<PlayerColor> players) {
450+
return new GameState(players, tileDecks, tileToPlace, board, nextAction, messageBoard);
451+
}
452+
449453
}

src/ch/epfl/chacun/TextMakerFr.java

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package ch.epfl.chacun;
22

3+
import javafx.beans.value.ObservableValue;
4+
35
import java.util.List;
46
import java.util.Map;
57
import java.util.Set;
@@ -30,10 +32,10 @@ public String getFrenchName() {
3032
Animal.Kind.TIGER, "tigre"
3133
);
3234

33-
private final Map<PlayerColor, String> names;
35+
private final ObservableValue<Map<PlayerColor, String>> namesO;
3436

35-
public TextMakerFr(Map<PlayerColor, String> names) {
36-
this.names = Map.copyOf(names);
37+
public TextMakerFr(ObservableValue<Map<PlayerColor, String>> namesO) {
38+
this.namesO = namesO;
3739
}
3840

3941
private String pluralizeGameItems(GameItem item, int count) {
@@ -101,7 +103,7 @@ private String animalsToString(Map<Animal.Kind, Integer> animals) {
101103

102104
@Override
103105
public String playerName(PlayerColor playerColor) {
104-
return names.getOrDefault(playerColor, null);
106+
return namesO.getValue().getOrDefault(playerColor, null);
105107
}
106108

107109
@Override

src/ch/epfl/chacun/gui/ActionsUI.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ private ActionsUI() {
4242
* inserts a new action in the field
4343
* @return a node containing the last actions of the game state and a field where one may insert a new action
4444
*/
45-
public static Node create(ObservableValue<List<String>> actionsO, Consumer<String> handler) {
45+
public static Node create(
46+
ObservableValue<List<String>> actionsO,
47+
Consumer<String> handler,
48+
ObservableValue<Boolean> isOwnerCurrentPlayerO
49+
) {
4650
HBox hbox = new HBox();
4751
hbox.setId("actions");
4852
hbox.getStylesheets().add("/actions.css");
@@ -60,6 +64,7 @@ public static Node create(ObservableValue<List<String>> actionsO, Consumer<Strin
6064
handler.accept(textField.getText());
6165
textField.clear();
6266
});
67+
textField.disableProperty().bind(isOwnerCurrentPlayerO.map(v -> !v));
6368

6469
hbox.getChildren().addAll(text, textField);
6570
return hbox;

src/ch/epfl/chacun/gui/BoardUI.java

+1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ public static Node create(
7171
ObservableValue<Rotation> rotationO,
7272
ObservableValue<Set<Occupant>> occupantsO,
7373
ObservableValue<Set<Integer>> highlightedTilesO,
74+
ObservableValue<Boolean> isOwnerCurrentPlayerO,
7475

7576
Consumer<Rotation> rotationConsumer,
7677
Consumer<Pos> posConsumer,

src/ch/epfl/chacun/gui/Main.java

+52-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package ch.epfl.chacun.gui;
22

33
import ch.epfl.chacun.*;
4+
import ch.epfl.chacun.net.WSClient;
45
import javafx.application.Application;
56
import javafx.beans.property.ObjectProperty;
67
import javafx.beans.property.SimpleObjectProperty;
@@ -52,9 +53,26 @@ private TileDecks getShuffledTileDecks(Long seed) {
5253
);
5354
}
5455

56+
private Map<PlayerColor, String> getPlayersMap(String playerNames) {
57+
String[] names = playerNames.split(",");
58+
Map<PlayerColor, String> playersMap = new TreeMap<>();
59+
for (int i = 0; i < names.length; i++) {
60+
playersMap.put(PlayerColor.ALL.get(i), names[i]);
61+
}
62+
return playersMap;
63+
}
64+
5565
@Override
5666
public void start(Stage primaryStage) {
5767

68+
String gameName = "androzGame";
69+
String mySuperName = "Androz" + new Random().nextInt(1000);
70+
WSClient wsClient = new WSClient(
71+
gameName,
72+
mySuperName
73+
);
74+
75+
5876
Parameters parameters = getParameters();
5977
Map<String, String> namedParameters = parameters.getNamed();
6078

@@ -65,17 +83,24 @@ public void start(Stage primaryStage) {
6583
int playersSize = players.size();
6684
Preconditions.checkArgument(playersSize >= 2 && playersSize <= 5);
6785

68-
List<PlayerColor> playerColors = PlayerColor.ALL.subList(0, playersSize);
69-
Map<PlayerColor, String> playersNames = new TreeMap<>();
70-
for (int i = 0; i < playersSize; i++) {
71-
playersNames.put(playerColors.get(i), players.get(i));
72-
}
86+
SimpleObjectProperty<Map<PlayerColor, String>> playerNamesO = new SimpleObjectProperty<>(new TreeMap<>());
87+
ObservableValue<List<PlayerColor>> playerColorsO = playerNamesO.map(map -> new ArrayList<>(map.keySet()));
88+
playerNamesO.setValue(getPlayersMap(mySuperName));
7389

74-
TextMaker textMaker = new TextMakerFr(playersNames);
90+
TextMaker textMaker = new TextMakerFr(playerNamesO);
7591

76-
GameState gameState = GameState.initial(playerColors, tileDecks, textMaker);
92+
GameState gameState = GameState.initial(playerColorsO.getValue(), tileDecks, textMaker);
7793
SimpleObjectProperty<GameState> gameStateO = new SimpleObjectProperty<>(gameState);
7894

95+
wsClient.setOnGamePlayerJoin(newPlayerNames -> {
96+
playerNamesO.setValue(getPlayersMap(newPlayerNames));
97+
gameStateO.setValue(gameStateO.getValue().withPlayers(playerColorsO.getValue()));
98+
});
99+
wsClient.setOnGameJoinAccept(newPlayerNames -> {
100+
playerNamesO.setValue(getPlayersMap(newPlayerNames));
101+
gameStateO.setValue(gameStateO.getValue().withPlayers(playerColorsO.getValue()));
102+
});
103+
79104
ObservableValue<List<MessageBoard.Message>> observableMessagesO = gameStateO.map(
80105
gState -> gState.messageBoard().messages()
81106
);
@@ -120,10 +145,25 @@ public void start(Stage primaryStage) {
120145
if (newSt != null) saveState(newSt, gameStateO, actionsO);
121146
};
122147

123-
Node playersNode = PlayersUI.create(gameStateO, new TextMakerFr(playersNames));
148+
ObservableValue<String> ownerPlayerColorO = playerNamesO.map(playerName -> {
149+
PlayerColor owner = null;
150+
for (Map.Entry<PlayerColor, String> entry : playerName.entrySet()) {
151+
if (entry.getValue().equals(mySuperName)) {
152+
owner = entry.getKey();
153+
break;
154+
}
155+
}
156+
return owner.toString();
157+
});
158+
159+
ObservableValue<Boolean> isOwnerCurrentPlayerO = gameStateO.map(
160+
gState -> gState.currentPlayer() == PlayerColor.valueOf(ownerPlayerColorO.getValue())
161+
);
162+
163+
Node playersNode = PlayersUI.create(gameStateO, new TextMakerFr(playerNamesO));
124164
Node messagesNode = MessageBoardUI.create(observableMessagesO, highlightedTilesO);
125165
Node decksNode = DecksUI.create(tileToPlaceO, leftNormalTilesO, leftMenhirTilesO, textToDisplayO, onOccupantClick);
126-
Node actionsNode = ActionsUI.create(actionsO, onEnteredAction);
166+
Node actionsNode = ActionsUI.create(actionsO, onEnteredAction, isOwnerCurrentPlayerO);
127167

128168
SimpleObjectProperty<Rotation> nextRotationO = new SimpleObjectProperty<>(Rotation.NONE);
129169
Consumer<Rotation> onRotationClick = r -> {
@@ -152,7 +192,7 @@ public void start(Stage primaryStage) {
152192
});
153193

154194
Node boardNode = BoardUI.create(
155-
Board.REACH, gameStateO, nextRotationO, visibleOccupants, highlightedTilesO,
195+
Board.REACH, gameStateO, nextRotationO, visibleOccupants, highlightedTilesO, isOwnerCurrentPlayerO,
156196
// consumers
157197
onRotationClick, onPosClick, onOccupantClick
158198
);
@@ -181,5 +221,7 @@ public void start(Stage primaryStage) {
181221

182222
gameStateO.setValue(gameStateO.getValue().withStartingTilePlaced());
183223

224+
wsClient.connect();
225+
184226
}
185227
}

src/ch/epfl/chacun/gui/PlayersUI.java

+47-31
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import java.util.ArrayList;
1515
import java.util.List;
1616
import java.util.Map;
17+
import java.util.function.Function;
1718

1819
/**
1920
* This class represents the graphical representation
@@ -43,7 +44,10 @@ private PlayersUI() {
4344
* @return a graphical node containing the occupants of the game with their names, their colours,
4445
* their points and available occupants, with the current player highlighted
4546
*/
46-
public static Node create(ObservableValue<GameState> gameStateO, TextMaker textMaker) {
47+
public static Node create(
48+
ObservableValue<GameState> gameStateO,
49+
TextMaker textMaker
50+
) {
4751

4852
ObservableValue<Map<PlayerColor, Integer>> pointsO = gameStateO.map(gState -> gState.messageBoard().points());
4953

@@ -52,45 +56,57 @@ public static Node create(ObservableValue<GameState> gameStateO, TextMaker textM
5256
vBox.getStylesheets().add("players.css");
5357
vBox.setId("players");
5458

55-
// for each player color, create a text flow
56-
PlayerColor.ALL.forEach(playerColor -> {
57-
String name = textMaker.playerName(playerColor);
58-
if (name == null) return;
59+
ObservableValue<List<PlayerColor>> players = gameStateO.map(GameState::players);
60+
61+
Function<List<PlayerColor>, Boolean> addPlayersNodes = (newPlayers) -> {
62+
newPlayers
63+
.stream()
64+
.skip(vBox.getChildren().size())
65+
.forEach(playerColor -> {
66+
String name = textMaker.playerName(playerColor);
67+
68+
if (name == null) return;
69+
70+
TextFlow textFlow = new TextFlow();
71+
textFlow.getStyleClass().add("player");
5972

60-
TextFlow textFlow = new TextFlow();
61-
textFlow.getStyleClass().add("player");
73+
// we update here the current player
74+
ObservableValue<Boolean> isCurrentPlayer = gameStateO.map(gState -> gState.currentPlayer() == playerColor);
75+
isCurrentPlayer.addListener((_, _, newValue) -> {
76+
if (newValue) textFlow.getStyleClass().add("current");
77+
else textFlow.getStyleClass().remove("current");
78+
});
79+
// todo check null --> currentplayer should trigger listener
80+
if (gameStateO.getValue().currentPlayer() == playerColor)
81+
textFlow.getStyleClass().add("current");
6282

63-
// we update here the current player
64-
ObservableValue<Boolean> isCurrentPlayer = gameStateO.map(gState -> gState.currentPlayer() == playerColor);
65-
isCurrentPlayer.addListener((_, _, newValue) -> {
66-
if (newValue) textFlow.getStyleClass().add("current");
67-
else textFlow.getStyleClass().remove("current");
68-
});
69-
// todo check null --> currentplayer should trigger listener
70-
if (gameStateO.getValue().currentPlayer() == playerColor) textFlow.getStyleClass().add("current");
83+
Circle circle = new Circle(5);
84+
circle.setFill(ColorMap.fillColor(playerColor));
7185

72-
Circle circle = new Circle(5);
73-
circle.setFill(ColorMap.fillColor(playerColor));
86+
ObservableValue<String> pointsTextO = pointsO.map(points -> STR." \{name} : \{textMaker.points(points.getOrDefault(playerColor, 0))}\n");
87+
ObservableValue<Map<Occupant.Kind, Integer>> occupantsO = gameStateO
88+
.map(gState -> Map.of(
89+
Occupant.Kind.PAWN, gState.freeOccupantsCount(playerColor, Occupant.Kind.PAWN),
90+
Occupant.Kind.HUT, gState.freeOccupantsCount(playerColor, Occupant.Kind.HUT)
91+
));
7492

75-
ObservableValue<String> pointsTextO = pointsO.map(points -> STR." \{name} : \{textMaker.points(points.getOrDefault(playerColor, 0))}\n");
76-
ObservableValue<Map<Occupant.Kind, Integer>> occupantsO = gameStateO
77-
.map(gState -> Map.of(
78-
Occupant.Kind.PAWN, gState.freeOccupantsCount(playerColor, Occupant.Kind.PAWN),
79-
Occupant.Kind.HUT, gState.freeOccupantsCount(playerColor, Occupant.Kind.HUT)
80-
));
93+
Text pointsText = new Text();
94+
pointsText.textProperty().bind(pointsTextO);
8195

82-
Text pointsText = new Text();
83-
pointsText.textProperty().bind(pointsTextO);
96+
textFlow.getChildren().addAll(circle, pointsText);
8497

85-
textFlow.getChildren().addAll(circle, pointsText);
98+
textFlow.getChildren().addAll(getOccupants(playerColor, Occupant.Kind.HUT, occupantsO));
99+
textFlow.getChildren().add(new Text(" "));
100+
textFlow.getChildren().addAll(getOccupants(playerColor, Occupant.Kind.PAWN, occupantsO));
86101

87-
textFlow.getChildren().addAll(getOccupants(playerColor, Occupant.Kind.HUT, occupantsO));
88-
textFlow.getChildren().add(new Text(" "));
89-
textFlow.getChildren().addAll(getOccupants(playerColor, Occupant.Kind.PAWN, occupantsO));
102+
vBox.getChildren().add(textFlow);
90103

91-
vBox.getChildren().add(textFlow);
104+
});
105+
return true;
106+
};
92107

93-
});
108+
players.addListener((_, _, newPlayers) -> addPlayersNodes.apply(newPlayers));
109+
addPlayersNodes.apply(players.getValue());
94110

95111
return vBox;
96112
}

src/ch/epfl/chacun/net/WSClient.java

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package ch.epfl.chacun.net;
2+
3+
import ch.epfl.chacun.Preconditions;
4+
import javafx.application.Platform;
5+
6+
import java.net.URI;
7+
import java.net.http.HttpClient;
8+
import java.net.http.WebSocket;
9+
import java.util.concurrent.CompletionStage;
10+
import java.util.function.Consumer;
11+
12+
public final class WSClient implements WebSocket.Listener {
13+
14+
private final String gameName;
15+
private final String username;
16+
private WebSocket ws;
17+
18+
private boolean connected = false;
19+
20+
private Consumer<String> onGameJoinAccept;
21+
private Consumer<String> onGamePlayerJoin;
22+
23+
public WSClient(String gameName, String username) {
24+
this.gameName = gameName;
25+
this.username = username;
26+
27+
this.onGameJoinAccept = (data) -> {};
28+
this.onGamePlayerJoin = (data) -> {};
29+
}
30+
31+
private static String connectURI(String gameName, String username) {
32+
return STR."wss://cs108-chacun-multiplayer.sys.polysource.ch/?gameName=\{gameName}&username=\{username}";
33+
}
34+
35+
private void acknowledgePing() {
36+
Preconditions.checkArgument(connected);
37+
ws.sendText("PONG", true);
38+
}
39+
40+
private void handleMessage(String message) {
41+
String[] messageParts = message.split("\\.");
42+
String action = messageParts[0];
43+
String data = messageParts[1];
44+
switch (action) {
45+
case "GAMEJOIN_ACCEPT":
46+
onGameJoinAccept.accept(data);
47+
break;
48+
case "GAMEJOIN_NEWCOMER":
49+
onGamePlayerJoin.accept(data);
50+
break;
51+
case "PING":
52+
acknowledgePing();
53+
break;
54+
}
55+
}
56+
57+
public void setOnGameJoinAccept(Consumer<String> onGameJoinAccept) {
58+
this.onGameJoinAccept = onGameJoinAccept;
59+
}
60+
61+
public void setOnGamePlayerJoin(Consumer<String> onGamePlayerJoin) {
62+
this.onGamePlayerJoin = onGamePlayerJoin;
63+
}
64+
65+
public void connect() {
66+
Preconditions.checkArgument(!connected);
67+
ws = HttpClient
68+
.newHttpClient()
69+
.newWebSocketBuilder()
70+
.buildAsync(URI.create(WSClient.connectURI(gameName, username)), this)
71+
.join();
72+
}
73+
74+
@Override
75+
public CompletionStage<?> onText(WebSocket webSocket, CharSequence data, boolean last) {
76+
Platform.runLater(() -> handleMessage(data.toString()));
77+
return WebSocket.Listener.super.onText(webSocket, data, last);
78+
}
79+
80+
@Override
81+
public void onOpen(WebSocket webSocket) {
82+
connected = true;
83+
WebSocket.Listener.super.onOpen(webSocket);
84+
}
85+
86+
@Override
87+
public CompletionStage<?> onClose(WebSocket webSocket, int statusCode, String reason) {
88+
connected = false;
89+
throw new IllegalStateException("Unexpected WS connection closed");
90+
}
91+
92+
public void sendAction(String message) {
93+
ws.sendText(message, true);
94+
}
95+
96+
public void dispatchGameStarted() {
97+
ws.sendText("gameStarted", true);
98+
}
99+
100+
}

0 commit comments

Comments
 (0)