Skip to content

Commit 5c84969

Browse files
committed
init: first commit for extension
1 parent de23260 commit 5c84969

15 files changed

+607
-78
lines changed

resources/sounds/gained_points.mp3

20.4 KB
Binary file not shown.

resources/sounds/menhir_closed.mp3

66.9 KB
Binary file not shown.

resources/sounds/placed_tile.mp3

12 KB
Binary file not shown.

src/ch/epfl/chacun/GameState.java

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

3+
import ch.epfl.chacun.audio.SoundManager;
4+
35
import java.util.*;
46
import java.util.stream.Collectors;
57
import java.util.stream.Stream;
@@ -13,6 +15,7 @@
1315
* @param board the board of the game, where the tiles can be placed
1416
* @param nextAction the next action to be performed
1517
* @param messageBoard the message board of the game, containing the messages generated during the game
18+
* @param nextSound the sound to play when the game state is updated
1619
* @author Valerio De Santis (373247)
1720
* @author Simon Lefort (371918)
1821
*/
@@ -22,7 +25,8 @@ public record GameState(
2225
Tile tileToPlace,
2326
Board board,
2427
Action nextAction,
25-
MessageBoard messageBoard
28+
MessageBoard messageBoard,
29+
SoundManager.Sound nextSound
2630
) {
2731

2832
/**
@@ -52,9 +56,11 @@ public enum Action {
5256
* @param nextAction the next action to be performed, must not be null
5357
* @param messageBoard the message board of the game,
5458
* containing the messages generated during the game, must not be null
59+
* @param nextSound the sound to play when the game state is updated, can be null
5560
* @throws NullPointerException if any of the arguments is null, except tileToPlace
5661
* @throws IllegalArgumentException if there are not enough players or if the tileToPlace is null and
57-
* the next action is to place a tile, or if the tileToPlace is not null and the next action is not to place a tile.
62+
* the next action is to place a tile, or if the tileToPlace is not null and the
63+
* next action is not to place a tile.
5864
*/
5965
public GameState {
6066

@@ -65,11 +71,36 @@ public enum Action {
6571

6672
Objects.requireNonNull(players);
6773
players = List.copyOf(players);
68-
Preconditions.checkArgument(players.size() >= MIN_PLAYER_COUNT);
6974

7075
Preconditions.checkArgument(tileToPlace == null ^ nextAction == Action.PLACE_TILE);
7176
}
7277

78+
/**
79+
* Constructs a new game state, with no tile to place, an empty board, and the start game action.
80+
*
81+
* @param players the ordered list of the game players, must contain at least 2 players and not be null
82+
* @param tileDecks the tile decks of the game, containing the cards to place, must not be null
83+
* @param tileToPlace the tile to place on the board, may be null if no tile is to be placed
84+
* @param board the board of the game, where the tiles can be placed, must not be null
85+
* @param nextAction the next action to be performed, must not be null
86+
* @param messageBoard the message board of the game,
87+
* containing the messages generated during the game, must not be null
88+
* @throws NullPointerException if any of the arguments is null, except tileToPlace
89+
* @throws IllegalArgumentException if there are not enough players or if the tileToPlace is null and
90+
* the next action is to place a tile, or if the tileToPlace is not null and the
91+
* next action is not to place a tile.
92+
*/
93+
public GameState(
94+
List<PlayerColor> players,
95+
TileDecks tileDecks,
96+
Tile tileToPlace,
97+
Board board,
98+
Action nextAction,
99+
MessageBoard messageBoard
100+
) {
101+
this(players, tileDecks, tileToPlace, board, nextAction, messageBoard, SoundManager.Sound.SILENT);
102+
}
103+
73104
/**
74105
* Constructs the initial game state, with no tile to place, an empty board, and the start game action.
75106
*
@@ -301,8 +332,10 @@ public GameState withPlacedTile(PlacedTile tile) {
301332
}
302333
}
303334

304-
return new GameState(players, tileDecks, null, newBoard, Action.OCCUPY_TILE, newMessageBoard)
305-
.withTurnFinishedIfOccupationImpossible();
335+
return new GameState(
336+
players, tileDecks, null, newBoard, Action.OCCUPY_TILE,
337+
newMessageBoard, SoundManager.Sound.PLACED_TILE
338+
).withTurnFinishedIfOccupationImpossible();
306339
}
307340

308341
/**
@@ -334,6 +367,8 @@ private GameState withTurnFinished() {
334367
Preconditions.checkArgument(board.lastPlacedTile() != null);
335368

336369
MessageBoard newMessageBoard = messageBoard;
370+
Map<PlayerColor, Integer> currentPoints = messageBoard.points();
371+
337372
// the tile has already been added to the board previously
338373
Board newBoard = board;
339374
TileDecks newTileDecks = tileDecks;
@@ -376,9 +411,13 @@ private GameState withTurnFinished() {
376411
if (newTileDecks.deckSize(Tile.Kind.NORMAL) > 0) {
377412
List<PlayerColor> newPlayers = new LinkedList<>(players);
378413
Collections.rotate(newPlayers, -1);
414+
415+
SoundManager.Sound nextSound = newMessageBoard.points().equals(currentPoints)
416+
? nextSound() : SoundManager.Sound.GAINED_POINTS;
417+
379418
return new GameState(newPlayers, newTileDecks.withTopTileDrawn(Tile.Kind.NORMAL),
380419
newTileDecks.topTile(Tile.Kind.NORMAL),
381-
newBoard, Action.PLACE_TILE, newMessageBoard
420+
newBoard, Action.PLACE_TILE, newMessageBoard, nextSound
382421
);
383422
} else {
384423
return new GameState(players, newTileDecks, null, newBoard, Action.END_GAME, newMessageBoard)
@@ -444,4 +483,34 @@ private GameState withFinalPointsCounted() {
444483
return new GameState(players, tileDecks, null, newBoard, Action.END_GAME, newMessageBoard);
445484
}
446485

486+
/**
487+
* Returns a new game state with the given players list as new players
488+
*
489+
* @param players the new players list
490+
* @return a new game state with the given players list as new players
491+
*/
492+
public GameState withPlayers(List<PlayerColor> players) {
493+
return new GameState(players, tileDecks, tileToPlace, board, nextAction, messageBoard);
494+
}
495+
496+
/**
497+
* Returns a new game state with the given chat message added to the message board
498+
*
499+
* @param message the chat message to add
500+
* @return a new game state with the given chat message added to the message board
501+
*/
502+
public GameState withGameChatMessage(String message) {
503+
return new GameState(players, tileDecks, tileToPlace, board, nextAction, messageBoard.withGameChatMessage(message));
504+
}
505+
506+
/**
507+
* Returns a new game state with the given chat message added to the message board
508+
*
509+
* @param textMaker the text maker to generate the message
510+
* @return a new game state with the given chat message added to the message board
511+
*/
512+
public GameState withTextMaker(TextMaker textMaker) {
513+
return new GameState(players, tileDecks, tileToPlace, board, nextAction, messageBoard.withTextMaker(textMaker));
514+
}
515+
447516
}

src/ch/epfl/chacun/MessageBoard.java

+23
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,29 @@ public MessageBoard withWinners(Set<PlayerColor> winners, int points) {
340340
);
341341
}
342342

343+
/**
344+
* Returns a new message board with the given text maker
345+
* @param textMaker the text maker to use
346+
* @return a new message board with the given text maker
347+
*/
348+
public MessageBoard withTextMaker(TextMaker textMaker) {
349+
return new MessageBoard(textMaker, messages);
350+
}
351+
352+
/**
353+
* Returns a new message board with the given message added
354+
* @param message the message to add
355+
* @return a new message board with the given message added
356+
*/
357+
public MessageBoard withGameChatMessage(String message) {
358+
return withNewMessage(
359+
message,
360+
0,
361+
Set.of(),
362+
Set.of()
363+
);
364+
}
365+
343366
/**
344367
* Represents a message on the message board.
345368
*

src/ch/epfl/chacun/TextMaker.java

+7
Original file line numberDiff line numberDiff line change
@@ -147,4 +147,11 @@ public interface TextMaker {
147147
* @return le texte en question
148148
*/
149149
String clickToUnoccupy();
150+
151+
/**
152+
* Retourne un texte demandant au joueur actuel de rentrer un message dans le chat.
153+
*
154+
* @return le texte en question
155+
*/
156+
String enterChatMessage();
150157
}

src/ch/epfl/chacun/TextMakerFr.java

+5
Original file line numberDiff line numberDiff line change
@@ -294,4 +294,9 @@ public String clickToOccupy() {
294294
public String clickToUnoccupy() {
295295
return "Cliquez sur le pion que vous désirez reprendre, ou ici pour ne pas en reprendre.";
296296
}
297+
298+
@Override
299+
public String enterChatMessage() {
300+
return "Entrez un message...";
301+
}
297302
}
+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package ch.epfl.chacun.audio;
2+
3+
import javafx.scene.media.Media;
4+
import javafx.scene.media.MediaPlayer;
5+
6+
import java.net.URL;
7+
8+
/**
9+
* This class is responsible for playing sounds in the game
10+
*
11+
* @author Valerio De Santis (373247)
12+
* @author Simon Lefort (371918)
13+
*/
14+
public final class SoundManager {
15+
16+
private static MediaPlayer mediaPlayer;
17+
18+
/**
19+
* Plays a sound (high priority, stops the current sound if any)
20+
*
21+
* @param sound the sound to play
22+
*/
23+
public void play(Sound sound) {
24+
25+
if (sound == Sound.SILENT) {
26+
mediaPlayer.stop();
27+
return;
28+
}
29+
30+
String name = STR."/sounds/\{sound.toString().toLowerCase()}.mp3";
31+
URL url = getClass().getResource(name);
32+
assert url != null;
33+
Media media = new Media(url.toString());
34+
35+
mediaPlayer = new MediaPlayer(media);
36+
mediaPlayer.setAutoPlay(true);
37+
}
38+
39+
/**
40+
* The sounds of our game, ordered by priority in concurrent occasions
41+
*/
42+
public enum Sound {
43+
PLACED_TILE,
44+
GAINED_POINTS,
45+
MENHIR_CLOSED,
46+
SILENT
47+
}
48+
49+
50+
}

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

+7-1
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,14 @@ private ActionsUI() {
4242
* @param actionsO the observable list of all actions since the start of the game
4343
* @param handler an event handler whose method has to be applied when the player
4444
* inserts a new action in the field
45+
* @param isLocalPlayerCurrentPlayerO the observable value of a boolean that is true if the local player is the current player
4546
* @return a node containing the last actions of the game state and a field where one may insert a new action
4647
*/
47-
public static Node create(ObservableValue<List<String>> actionsO, Consumer<String> handler) {
48+
public static Node create(
49+
ObservableValue<List<String>> actionsO,
50+
Consumer<String> handler,
51+
ObservableValue<Boolean> isLocalPlayerCurrentPlayerO
52+
) {
4853

4954
Text text = new Text();
5055
text.textProperty().bind(actionsO.map(ActionsUI::actionsTextRepresentation));
@@ -59,6 +64,7 @@ public static Node create(ObservableValue<List<String>> actionsO, Consumer<Strin
5964
handler.accept(textField.getText());
6065
textField.clear();
6166
});
67+
textField.disableProperty().bind(isLocalPlayerCurrentPlayerO.map(b -> !b));
6268

6369
HBox hbox = new HBox(text, textField);
6470
hbox.setId("actions");

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

+41-32
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,16 @@ private BoardUI() {
5555
* for a certain message when the player hovers over it. It also handles some graphical effects to
5656
* render the positions where a tile can be placed in the next turn and the mouse interactions.
5757
*
58-
* @param reach the reach of the board (the distance from the center to the borders),
59-
* the board will be a square of size (2*range+1)²
60-
* @param gameStateO the observable value of the current game state
61-
* @param rotationO the observable value of the current rotation of the tile to be placed
62-
* @param occupantsO the observable value of the set containing the occupants on the board to show
63-
* @param highlightedTilesO the observable value of the set containing the tiles to highlight
64-
* @param rotationConsumer the consumer that will be called when the player rotates the tile to be placed (right click)
65-
* @param posConsumer the consumer that will be called when the player places the tile (left click)
66-
* @param occupantConsumer the consumer that will be called when the player clicks on an occupant
58+
* @param reach the reach of the board (the distance from the center to the borders),
59+
* the board will be a square of size (2*range+1)²
60+
* @param gameStateO the observable value of the current game state
61+
* @param rotationO the observable value of the current rotation of the tile to be placed
62+
* @param occupantsO the observable value of the set containing the occupants on the board to show
63+
* @param highlightedTilesO the observable value of the set containing the tiles to highlight
64+
* @param isLocalPlayerCurrentPlayerO the observable value of the boolean that is true if the local player is the current player
65+
* @param rotationConsumer the consumer that will be called when the player rotates the tile to be placed (right click)
66+
* @param posConsumer the consumer that will be called when the player places the tile (left click)
67+
* @param occupantConsumer the consumer that will be called when the player clicks on an occupant
6768
* @return a graphical node representing the board of the game
6869
*/
6970
public static Node create(
@@ -72,6 +73,7 @@ public static Node create(
7273
ObservableValue<Rotation> rotationO,
7374
ObservableValue<Set<Occupant>> occupantsO,
7475
ObservableValue<Set<Integer>> highlightedTilesO,
76+
ObservableValue<Boolean> isLocalPlayerCurrentPlayerO,
7577

7678
Consumer<Rotation> rotationConsumer,
7779
Consumer<Pos> posConsumer,
@@ -151,7 +153,10 @@ public static Node create(
151153
if (isAlreadyPlaced) return new CellData(placedTile,
152154
darkVeilEnabledO.getValue() ? Color.BLACK : Color.TRANSPARENT);
153155
// else, if the tile is not placed yet, nor in the fringe, display it as an empty image
154-
if (!isInFringeO.getValue()) return new CellData(Color.TRANSPARENT);
156+
if (
157+
!isInFringeO.getValue()
158+
|| !isLocalPlayerCurrentPlayerO.getValue()
159+
) return new CellData(Color.TRANSPARENT);
155160

156161
GameState currentGameState = gameStateO.getValue();
157162
PlayerColor currentPlayer = currentGameState.currentPlayer();
@@ -168,10 +173,11 @@ public static Node create(
168173
}
169174
// finally, if the tile is in the fringe but the mouse is not on it,
170175
// we display it with a veil of the current player's color
176+
assert currentPlayer != null;
171177
return new CellData(ColorMap.fillColor(currentPlayer));
172178
// these arguments are the sensibility of the code,
173179
// every time one of them changes, the code is re-executed
174-
}, isInFringeO, group.hoverProperty(), rotationO, darkVeilEnabledO, placedTileO);
180+
}, isInFringeO, group.hoverProperty(), rotationO, darkVeilEnabledO, placedTileO, isLocalPlayerCurrentPlayerO);
175181

176182
// we bind the graphical properties of the group to the cell data's values
177183
group.rotateProperty().bind(cellDataO.map(cellData -> cellData.tileRotation().degreesCW()));
@@ -184,27 +190,30 @@ public static Node create(
184190
// when a tile is placed, we add the animals and the occupants on it
185191
placedTileO.addListener((_, oldPlacedTile, placedTile) -> {
186192
// if the tile is already placed or is not yet, we do not have to add the animals and the occupants
187-
if (oldPlacedTile != null || placedTile == null) return;
188-
189-
double negatedTileRotation = placedTile.rotation().negated().degreesCW();
190-
191-
// handle "jeton d'annulation", a marker that signals that an animal is cancelled
192-
List<Node> cancelledAnimalsNodes =
193-
placedTile.meadowZones().stream()
194-
.flatMap(meadow -> meadow.animals().stream())
195-
.map(animal -> getCancelledAnimalNode(animal, cancelledAnimalsO, negatedTileRotation))
196-
.toList();
197-
group.getChildren().addAll(cancelledAnimalsNodes);
198-
// here we handle the graphical representation of the occupants
199-
List<Node> potentialOccupantsNodes = placedTile.potentialOccupants()
200-
.stream()
201-
.map(occupant -> getOccupantNode(
202-
placedTile.placer(), occupant,
203-
occupantsO, occupantConsumer, negatedTileRotation
204-
))
205-
.toList();
206-
207-
group.getChildren().addAll(potentialOccupantsNodes);
193+
if (oldPlacedTile == null && placedTile != null) {
194+
double negatedTileRotation = placedTile.rotation().negated().degreesCW();
195+
196+
// handle "jeton d'annulation", a marker that signals that an animal is cancelled
197+
List<Node> cancelledAnimalsNodes =
198+
placedTile.meadowZones().stream()
199+
.flatMap(meadow -> meadow.animals().stream())
200+
.map(animal -> getCancelledAnimalNode(animal, cancelledAnimalsO, negatedTileRotation))
201+
.toList();
202+
group.getChildren().addAll(cancelledAnimalsNodes);
203+
// here we handle the graphical representation of the occupants
204+
List<Node> potentialOccupantsNodes = placedTile.potentialOccupants()
205+
.stream()
206+
.map(occupant -> getOccupantNode(
207+
placedTile.placer(), occupant,
208+
occupantsO, occupantConsumer, negatedTileRotation
209+
))
210+
.toList();
211+
212+
group.getChildren().addAll(potentialOccupantsNodes);
213+
} else if (oldPlacedTile != null && placedTile == null) { // rollback!
214+
group.getChildren().stream().skip(1).forEach(group.getChildren()::remove);
215+
}
216+
208217
});
209218
}
210219
}

0 commit comments

Comments
 (0)