Feat: Adds sounds to game #327
+14
-8
@@ -5,6 +5,7 @@ import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController.ChatKey;
|
|||||||
import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatModel;
|
import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatModel;
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatType;
|
import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatType;
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.client.chat.Message;
|
import ch.unibas.dmi.dbis.cs108.casono.client.chat.Message;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.ui.sound.SoundManager;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -70,7 +71,11 @@ public class ChatBoxController {
|
|||||||
ChatModel globalChatModel = new ChatModel(global, username, -1, null);
|
ChatModel globalChatModel = new ChatModel(global, username, -1, null);
|
||||||
chatController.getChatModelMap().put(new ChatController.ChatKey(global), globalChatModel);
|
chatController.getChatModelMap().put(new ChatController.ChatKey(global), globalChatModel);
|
||||||
addChatTab("GLOBAL", globalChatModel, global);
|
addChatTab("GLOBAL", globalChatModel, global);
|
||||||
addWhisperChatButton.setOnAction(_ -> addWhisperChatButton.show());
|
addWhisperChatButton.setOnAction(
|
||||||
|
_ -> {
|
||||||
|
SoundManager.getInstance().playButtonClick();
|
||||||
|
addWhisperChatButton.show();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -90,14 +95,13 @@ public class ChatBoxController {
|
|||||||
MenuItem menuItem = new MenuItem(targetUserName);
|
MenuItem menuItem = new MenuItem(targetUserName);
|
||||||
addWhisperChatButton.getItems().add(menuItem);
|
addWhisperChatButton.getItems().add(menuItem);
|
||||||
menuItem.setOnAction(
|
menuItem.setOnAction(
|
||||||
_ ->
|
_ -> {
|
||||||
|
SoundManager.getInstance().playButtonClick();
|
||||||
addWhisperChat(
|
addWhisperChat(
|
||||||
targetUserName,
|
targetUserName,
|
||||||
new ChatModel(
|
new ChatModel(
|
||||||
ChatType.WHISPER,
|
ChatType.WHISPER, username, -1, targetUserName));
|
||||||
username,
|
});
|
||||||
-1,
|
|
||||||
targetUserName)));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,14 +128,16 @@ public class ChatBoxController {
|
|||||||
if (oldUsername.equals(item.getText())) {
|
if (oldUsername.equals(item.getText())) {
|
||||||
item.setText(newUsername);
|
item.setText(newUsername);
|
||||||
item.setOnAction(
|
item.setOnAction(
|
||||||
_ ->
|
_ -> {
|
||||||
|
SoundManager.getInstance().playButtonClick();
|
||||||
addWhisperChat(
|
addWhisperChat(
|
||||||
newUsername,
|
newUsername,
|
||||||
new ChatModel(
|
new ChatModel(
|
||||||
ChatType.WHISPER,
|
ChatType.WHISPER,
|
||||||
username,
|
username,
|
||||||
-1,
|
-1,
|
||||||
newUsername)));
|
newUsername));
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui;
|
|||||||
import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController;
|
import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController;
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatModel;
|
import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatModel;
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.client.chat.Message;
|
import ch.unibas.dmi.dbis.cs108.casono.client.chat.Message;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.ui.sound.SoundManager;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
@@ -76,6 +77,7 @@ public class ChatViewController implements Initializable {
|
|||||||
* The input field is cleared after sending.
|
* The input field is cleared after sending.
|
||||||
*/
|
*/
|
||||||
public void sendMessage() {
|
public void sendMessage() {
|
||||||
|
SoundManager.getInstance().playButtonClick();
|
||||||
String message = inputField.getText().trim();
|
String message = inputField.getText().trim();
|
||||||
if (!message.isEmpty()) {
|
if (!message.isEmpty()) {
|
||||||
inputField.clear();
|
inputField.clear();
|
||||||
|
|||||||
+48
@@ -15,6 +15,7 @@ import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents.Noteboo
|
|||||||
import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents.PlayerStatusController;
|
import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents.PlayerStatusController;
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents.SettingsController;
|
import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents.SettingsController;
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents.TaskbarController;
|
import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents.TaskbarController;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.ui.sound.SoundManager;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@@ -74,6 +75,7 @@ public class CasinoGameController {
|
|||||||
private Image dealerImage;
|
private Image dealerImage;
|
||||||
private javafx.animation.Timeline timeline;
|
private javafx.animation.Timeline timeline;
|
||||||
private boolean gameStarted = false;
|
private boolean gameStarted = false;
|
||||||
|
private boolean firstCardUpdate = true;
|
||||||
private java.util.List<String> lastCommunityKeys = java.util.List.of();
|
private java.util.List<String> lastCommunityKeys = java.util.List.of();
|
||||||
private java.util.List<String> lastMyCardKeys = java.util.List.of();
|
private java.util.List<String> lastMyCardKeys = java.util.List.of();
|
||||||
private int lastPot = Integer.MIN_VALUE;
|
private int lastPot = Integer.MIN_VALUE;
|
||||||
@@ -198,6 +200,38 @@ public class CasinoGameController {
|
|||||||
return suit + ":" + value;
|
return suit + ":" + value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count how many new non-null card keys were added when transitioning from old keys to new
|
||||||
|
* keys.
|
||||||
|
*
|
||||||
|
* @param oldKeys The previous list of card keys
|
||||||
|
* @param newKeys The new list of card keys
|
||||||
|
* @return The count of new (non-null) cards that were revealed
|
||||||
|
*/
|
||||||
|
private int countNewCards(java.util.List<String> oldKeys, java.util.List<String> newKeys) {
|
||||||
|
int count = 0;
|
||||||
|
int minSize = Math.min(oldKeys.size(), newKeys.size());
|
||||||
|
|
||||||
|
// Count cards that changed from "null" to actual card
|
||||||
|
for (int i = 0; i < minSize; i++) {
|
||||||
|
String oldKey = oldKeys.get(i);
|
||||||
|
String newKey = newKeys.get(i);
|
||||||
|
if ("null".equals(oldKey) && !"null".equals(newKey)) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count any new cards beyond the previous size (if applicable)
|
||||||
|
for (int i = minSize; i < newKeys.size(); i++) {
|
||||||
|
String newKey = newKeys.get(i);
|
||||||
|
if (!"null".equals(newKey)) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.max(1, count); // At least play one sound if cards changed
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a list of unique keys for a list of cards.
|
* Generate a list of unique keys for a list of cards.
|
||||||
*
|
*
|
||||||
@@ -641,14 +675,28 @@ public class CasinoGameController {
|
|||||||
|
|
||||||
var newCommunityKeys = cardKeys(community, TOTAL_SLOTS);
|
var newCommunityKeys = cardKeys(community, TOTAL_SLOTS);
|
||||||
if (!newCommunityKeys.equals(lastCommunityKeys)) {
|
if (!newCommunityKeys.equals(lastCommunityKeys)) {
|
||||||
|
int newCardCount = countNewCards(lastCommunityKeys, newCommunityKeys);
|
||||||
lastCommunityKeys = newCommunityKeys;
|
lastCommunityKeys = newCommunityKeys;
|
||||||
renderCommunityCards(community);
|
renderCommunityCards(community);
|
||||||
|
if (!firstCardUpdate) {
|
||||||
|
SoundManager.getInstance().playCardRevealMultiple(newCardCount);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var newMyCardKeys = cardKeys(myCards, PLAYER_SLOTS);
|
var newMyCardKeys = cardKeys(myCards, PLAYER_SLOTS);
|
||||||
if (!newMyCardKeys.equals(lastMyCardKeys)) {
|
if (!newMyCardKeys.equals(lastMyCardKeys)) {
|
||||||
|
int newCardCount = countNewCards(lastMyCardKeys, newMyCardKeys);
|
||||||
lastMyCardKeys = newMyCardKeys;
|
lastMyCardKeys = newMyCardKeys;
|
||||||
renderPlayerCards(myCards);
|
renderPlayerCards(myCards);
|
||||||
|
// Don't play sound on first card update (initial animation)
|
||||||
|
if (!firstCardUpdate) {
|
||||||
|
SoundManager.getInstance().playCardRevealMultiple(newCardCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// After first update, enable sounds for subsequent card reveals
|
||||||
|
if (firstCardUpdate) {
|
||||||
|
firstCardUpdate = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+8
@@ -8,6 +8,7 @@ import ch.unibas.dmi.dbis.cs108.casono.client.game.PlayerId;
|
|||||||
import ch.unibas.dmi.dbis.cs108.casono.client.game.PlayerState;
|
import ch.unibas.dmi.dbis.cs108.casono.client.game.PlayerState;
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.client.network.LobbyClient;
|
import ch.unibas.dmi.dbis.cs108.casono.client.network.LobbyClient;
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui.Casinomainui;
|
import ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui.Casinomainui;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.ui.sound.SoundManager;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -1538,18 +1539,21 @@ public class TaskbarController {
|
|||||||
*/
|
*/
|
||||||
@FXML
|
@FXML
|
||||||
private void onInputSubmittedAction() {
|
private void onInputSubmittedAction() {
|
||||||
|
SoundManager.getInstance().playButtonClick();
|
||||||
processBet();
|
processBet();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Called when the Call button is clicked. */
|
/** Called when the Call button is clicked. */
|
||||||
@FXML
|
@FXML
|
||||||
private void onInputPlayerCall() {
|
private void onInputPlayerCall() {
|
||||||
|
SoundManager.getInstance().playButtonClick();
|
||||||
submitAction(ActionType.CALL_BUTTON);
|
submitAction(ActionType.CALL_BUTTON);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Called when the Fold button is clicked. */
|
/** Called when the Fold button is clicked. */
|
||||||
@FXML
|
@FXML
|
||||||
private void onInputPlayerFold() {
|
private void onInputPlayerFold() {
|
||||||
|
SoundManager.getInstance().playButtonClick();
|
||||||
|
|
||||||
if (gameService == null) {
|
if (gameService == null) {
|
||||||
LOGGER.error("GameService not initialized");
|
LOGGER.error("GameService not initialized");
|
||||||
@@ -1573,6 +1577,7 @@ public class TaskbarController {
|
|||||||
/** Called when the Raise button is clicked. */
|
/** Called when the Raise button is clicked. */
|
||||||
@FXML
|
@FXML
|
||||||
private void onInputPlayerRaise() {
|
private void onInputPlayerRaise() {
|
||||||
|
SoundManager.getInstance().playButtonClick();
|
||||||
submitPresetInputAndProcess("raise");
|
submitPresetInputAndProcess("raise");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1614,6 +1619,7 @@ public class TaskbarController {
|
|||||||
*/
|
*/
|
||||||
@FXML
|
@FXML
|
||||||
private void onExitButtonClick() {
|
private void onExitButtonClick() {
|
||||||
|
SoundManager.getInstance().playButtonClick();
|
||||||
javafx.application.Platform.runLater(
|
javafx.application.Platform.runLater(
|
||||||
() -> {
|
() -> {
|
||||||
// Close game stage
|
// Close game stage
|
||||||
@@ -1739,6 +1745,7 @@ public class TaskbarController {
|
|||||||
*/
|
*/
|
||||||
@FXML
|
@FXML
|
||||||
private void onBrowserButtonClick() {
|
private void onBrowserButtonClick() {
|
||||||
|
SoundManager.getInstance().playButtonClick();
|
||||||
try {
|
try {
|
||||||
Path path =
|
Path path =
|
||||||
Paths.get(
|
Paths.get(
|
||||||
@@ -1798,6 +1805,7 @@ public class TaskbarController {
|
|||||||
/** Opens the highscore popup window from the taskbar. */
|
/** Opens the highscore popup window from the taskbar. */
|
||||||
@FXML
|
@FXML
|
||||||
private void onHighscoreButtonClick() {
|
private void onHighscoreButtonClick() {
|
||||||
|
SoundManager.getInstance().playButtonClick();
|
||||||
try {
|
try {
|
||||||
var shared = ClientApp.getSharedClientService();
|
var shared = ClientApp.getSharedClientService();
|
||||||
if (shared == null || shared.isOffline()) {
|
if (shared == null || shared.isOffline()) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui;
|
package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui;
|
||||||
|
|
||||||
/** Main UI application for Casono. Loads the main FXML layout and sets up the stage. */
|
/** Main UI application for Casono. Loads the main FXML layout and sets up the stage. */
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.ui.sound.SoundManager;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
import javafx.fxml.FXMLLoader;
|
import javafx.fxml.FXMLLoader;
|
||||||
@@ -30,6 +31,9 @@ public class Casinomainui extends Application {
|
|||||||
* @throws IOException If loading the FXML fails.
|
* @throws IOException If loading the FXML fails.
|
||||||
*/
|
*/
|
||||||
public void start(Stage stage) throws IOException {
|
public void start(Stage stage) throws IOException {
|
||||||
|
// Pre-load sounds to avoid delays on first play
|
||||||
|
SoundManager.getInstance().preloadSounds();
|
||||||
|
|
||||||
// If the launcher passed an address argument (ip:port), expose it as
|
// If the launcher passed an address argument (ip:port), expose it as
|
||||||
// system properties so controllers can read it without embedding defaults.
|
// system properties so controllers can read it without embedding defaults.
|
||||||
var raw = getParameters().getRaw();
|
var raw = getParameters().getRaw();
|
||||||
|
|||||||
+4
@@ -5,6 +5,7 @@ import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController;
|
|||||||
import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService;
|
import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService;
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.client.network.LobbyClient;
|
import ch.unibas.dmi.dbis.cs108.casono.client.network.LobbyClient;
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents.HighscoreViewController;
|
import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents.HighscoreViewController;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.ui.sound.SoundManager;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
@@ -170,6 +171,7 @@ public class CasinomainuiController {
|
|||||||
/** Handles the login button action. Validates input and calls LobbyClient.login(). */
|
/** Handles the login button action. Validates input and calls LobbyClient.login(). */
|
||||||
@FXML
|
@FXML
|
||||||
public void handleLoginButton() {
|
public void handleLoginButton() {
|
||||||
|
SoundManager.getInstance().playButtonClick();
|
||||||
String username = usernameField == null ? null : usernameField.getText();
|
String username = usernameField == null ? null : usernameField.getText();
|
||||||
if (username == null || username.isBlank()) {
|
if (username == null || username.isBlank()) {
|
||||||
showAlert("Please enter a username.");
|
showAlert("Please enter a username.");
|
||||||
@@ -259,6 +261,7 @@ public class CasinomainuiController {
|
|||||||
/** Handles the exit button action to close the application. */
|
/** Handles the exit button action to close the application. */
|
||||||
@FXML
|
@FXML
|
||||||
public void handleexitbutton() {
|
public void handleexitbutton() {
|
||||||
|
SoundManager.getInstance().playButtonClick();
|
||||||
if (chatController != null) {
|
if (chatController != null) {
|
||||||
chatController.shutdown();
|
chatController.shutdown();
|
||||||
}
|
}
|
||||||
@@ -272,6 +275,7 @@ public class CasinomainuiController {
|
|||||||
*/
|
*/
|
||||||
@FXML
|
@FXML
|
||||||
public void handleCreateLobbyButton() {
|
public void handleCreateLobbyButton() {
|
||||||
|
SoundManager.getInstance().playButtonClick();
|
||||||
if (translationManager.isFull()) {
|
if (translationManager.isFull()) {
|
||||||
LOGGER.warn("Grid is full! No more lobbies available.");
|
LOGGER.warn("Grid is full! No more lobbies available.");
|
||||||
return;
|
return;
|
||||||
|
|||||||
+2
@@ -6,6 +6,7 @@ import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService;
|
|||||||
import ch.unibas.dmi.dbis.cs108.casono.client.network.LobbyClient;
|
import ch.unibas.dmi.dbis.cs108.casono.client.network.LobbyClient;
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.CasinoGameUI;
|
import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.CasinoGameUI;
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.RequestParameter;
|
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.RequestParameter;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.ui.sound.SoundManager;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -364,6 +365,7 @@ public class LobbyButtonGridManager {
|
|||||||
|
|
||||||
btn.setOnAction(
|
btn.setOnAction(
|
||||||
e -> {
|
e -> {
|
||||||
|
SoundManager.getInstance().playButtonClick();
|
||||||
Integer targetLobbyId = translationManager.getLobbyIdForButton(buttonId);
|
Integer targetLobbyId = translationManager.getLobbyIdForButton(buttonId);
|
||||||
|
|
||||||
if (targetLobbyId != null) {
|
if (targetLobbyId != null) {
|
||||||
|
|||||||
@@ -0,0 +1,182 @@
|
|||||||
|
package ch.unibas.dmi.dbis.cs108.casono.ui.sound;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import javafx.animation.PauseTransition;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.event.ActionEvent;
|
||||||
|
import javafx.scene.media.AudioClip;
|
||||||
|
import javafx.util.Duration;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages sound effects for the Casono game. Provides methods to play sounds for card reveals and
|
||||||
|
* button clicks.
|
||||||
|
*/
|
||||||
|
public class SoundManager {
|
||||||
|
private static final Logger LOGGER = LogManager.getLogger(SoundManager.class);
|
||||||
|
private static final SoundManager INSTANCE = new SoundManager();
|
||||||
|
private final Map<String, AudioClip> soundCache = new HashMap<>();
|
||||||
|
private static final float MIN_VOLUME = 0.0f;
|
||||||
|
private static final float MAX_VOLUME = 1.0f;
|
||||||
|
private static final float DEFAULT_VOLUME = 0.5f;
|
||||||
|
private float volume = DEFAULT_VOLUME;
|
||||||
|
private static final long CARD_REVEAL_STAGGER_MS = 120; // Delay between card reveal sounds
|
||||||
|
|
||||||
|
private SoundManager() {}
|
||||||
|
|
||||||
|
/** Returns the singleton instance of SoundManager. */
|
||||||
|
public static SoundManager getInstance() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pre-loads critical sounds into cache to avoid delays on first play. Should be called at
|
||||||
|
* application startup.
|
||||||
|
*/
|
||||||
|
public void preloadSounds() {
|
||||||
|
try {
|
||||||
|
getOrLoadSound("button-click");
|
||||||
|
getOrLoadSound("card-reveal");
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.error("Error preloading sounds", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Plays a button click sound. */
|
||||||
|
public void playButtonClick() {
|
||||||
|
playSound("button-click");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Plays a card reveal sound (for dealt or community cards). */
|
||||||
|
public void playCardReveal() {
|
||||||
|
playSound("card-reveal");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plays multiple card reveal sounds in sequence with slight stagger/overlap. Used when multiple
|
||||||
|
* cards are revealed at once (e.g., Flop reveals 3 cards, Turn reveals 1 card). Sounds play
|
||||||
|
* with a slight delay between them to simulate a person revealing cards one by one.
|
||||||
|
*
|
||||||
|
* @param count The number of cards being revealed. Each card gets its own sound with stagger.
|
||||||
|
*/
|
||||||
|
public void playCardRevealMultiple(int count) {
|
||||||
|
if (count <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (count == 1) {
|
||||||
|
playCardReveal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
final int cardIndex = i;
|
||||||
|
long delayMs = cardIndex * CARD_REVEAL_STAGGER_MS;
|
||||||
|
|
||||||
|
PauseTransition pause = new PauseTransition(Duration.millis(delayMs));
|
||||||
|
pause.setOnFinished(this::onCardRevealDelayFinished);
|
||||||
|
pause.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onCardRevealDelayFinished(ActionEvent event) {
|
||||||
|
Platform.runLater(this::playCardRevealSafely);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void playCardRevealSafely() {
|
||||||
|
try {
|
||||||
|
playCardReveal();
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.error("Error playing card reveal sound", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Plays a card shuffle sound. */
|
||||||
|
public void playCardShuffle() {
|
||||||
|
playSound("card-shuffle");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Plays a pot/chip sound. */
|
||||||
|
public void playChip() {
|
||||||
|
playSound("chip");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plays a generic sound by key. The sound file should exist at: /sounds/{category}/{key}.mp3 or
|
||||||
|
* .wav
|
||||||
|
*/
|
||||||
|
public void playSound(String soundKey) {
|
||||||
|
try {
|
||||||
|
AudioClip audioClip = getOrLoadSound(soundKey);
|
||||||
|
if (audioClip != null) {
|
||||||
|
audioClip.setVolume(volume);
|
||||||
|
audioClip.play();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.error("Error playing sound: {}", soundKey, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets or loads a sound from cache. Categorizes sounds automatically based on key prefix. */
|
||||||
|
private AudioClip getOrLoadSound(String soundKey) {
|
||||||
|
if (soundCache.containsKey(soundKey)) {
|
||||||
|
return soundCache.get(soundKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
String category = categorizeSound(soundKey);
|
||||||
|
String resourcePath = String.format("/sounds/%s/%s.wav", category, soundKey);
|
||||||
|
|
||||||
|
try {
|
||||||
|
URL soundUrl = getClass().getResource(resourcePath);
|
||||||
|
if (soundUrl != null) {
|
||||||
|
AudioClip clip = new AudioClip(soundUrl.toString());
|
||||||
|
soundCache.put(soundKey, clip);
|
||||||
|
return clip;
|
||||||
|
} else {
|
||||||
|
LOGGER.warn("Sound resource not found: {}", resourcePath);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.error("Failed to load sound: {}", resourcePath, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Categorizes a sound based on its key prefix. */
|
||||||
|
private String categorizeSound(String soundKey) {
|
||||||
|
if (soundKey.startsWith("button")) {
|
||||||
|
return "buttons";
|
||||||
|
} else if (soundKey.startsWith("card")) {
|
||||||
|
return "cards";
|
||||||
|
} else {
|
||||||
|
return "game";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the volume for sound effects (0.0 - 1.0). */
|
||||||
|
public void setVolume(float volume) {
|
||||||
|
this.volume = Math.max(MIN_VOLUME, Math.min(MAX_VOLUME, volume));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets the current volume level. */
|
||||||
|
public float getVolume() {
|
||||||
|
return volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Clears the sound cache. */
|
||||||
|
public void clearCache() {
|
||||||
|
soundCache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Mutes all sounds by setting volume to 0. */
|
||||||
|
public void mute() {
|
||||||
|
setVolume(0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Unmutes sounds by restoring to default volume. */
|
||||||
|
public void unmute() {
|
||||||
|
setVolume(DEFAULT_VOLUME);
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user