Added Ex. 10
added client hosts a server added javadoc comments
This commit is contained in:
@@ -14,9 +14,12 @@
|
|||||||
import java.util.prefs.Preferences;
|
import java.util.prefs.Preferences;
|
||||||
|
|
||||||
import static pp.util.PreferencesUtils.getPreferences;
|
import static pp.util.PreferencesUtils.getPreferences;
|
||||||
|
import static pp.util.Util.loadSound;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An application state that plays sounds.
|
||||||
|
*/
|
||||||
public class GameMusic extends AbstractAppState implements GameEventListener {
|
public class GameMusic extends AbstractAppState implements GameEventListener {
|
||||||
private static final Logger LOGGER = System.getLogger(GameMusic.class.getName());
|
|
||||||
private static final Preferences PREFERENCES = getPreferences(GameMusic.class);
|
private static final Preferences PREFERENCES = getPreferences(GameMusic.class);
|
||||||
private static final String ENABLED_PREF = "enabled";
|
private static final String ENABLED_PREF = "enabled";
|
||||||
private static final String VOLUME_PREF = "volume";
|
private static final String VOLUME_PREF = "volume";
|
||||||
@@ -25,16 +28,30 @@ public class GameMusic extends AbstractAppState implements GameEventListener {
|
|||||||
|
|
||||||
private AudioNode backgroundMusic;
|
private AudioNode backgroundMusic;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the music is enabled in the user preferences.
|
||||||
|
*
|
||||||
|
* @return true if music is enabled, false otherwise
|
||||||
|
*/
|
||||||
public static boolean enabledInPreferences(){
|
public static boolean enabledInPreferences(){
|
||||||
return PREFERENCES.getBoolean(ENABLED_PREF, true);
|
return PREFERENCES.getBoolean(ENABLED_PREF, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the music volume level stored in the user preferences.
|
||||||
|
*
|
||||||
|
* @return the volume level as a float (default is 0.5f)
|
||||||
|
*/
|
||||||
public static float volumeInPreferences(){
|
public static float volumeInPreferences(){
|
||||||
return PREFERENCES.getFloat(VOLUME_PREF, 0.5f);
|
return PREFERENCES.getFloat(VOLUME_PREF, 0.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the game music system
|
||||||
|
*
|
||||||
|
* @param stateManager the state manager of the game
|
||||||
|
* @param app the main application
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void initialize(AppStateManager stateManager, Application app) {
|
public void initialize(AppStateManager stateManager, Application app) {
|
||||||
super.initialize(stateManager, app);
|
super.initialize(stateManager, app);
|
||||||
@@ -43,19 +60,12 @@ public void initialize(AppStateManager stateManager, Application app) {
|
|||||||
if (isEnabled()) playMusic();
|
if (isEnabled()) playMusic();
|
||||||
}
|
}
|
||||||
|
|
||||||
private AudioNode loadSound(Application app, String name) {
|
/**
|
||||||
try {
|
* Sets the enabled state of this AppState.
|
||||||
final AudioNode sound = new AudioNode(app.getAssetManager(), name, AudioData.DataType.Buffer);
|
* Overrides {@link com.jme3.app.state.AbstractAppState#setEnabled(boolean)}
|
||||||
sound.setLooping(false);
|
*
|
||||||
sound.setPositional(false);
|
* @param enabled {@code true} to enable the AppState, {@code false} to disable it.
|
||||||
return sound;
|
*/
|
||||||
}
|
|
||||||
catch (AssetLoadException | AssetNotFoundException ex) {
|
|
||||||
LOGGER.log(Level.ERROR, ex.getMessage(), ex);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setEnabled(boolean enabled) {
|
public void setEnabled(boolean enabled) {
|
||||||
if(enabled && !isEnabled()){
|
if(enabled && !isEnabled()){
|
||||||
@@ -68,6 +78,9 @@ else if(!enabled && isEnabled()){
|
|||||||
PREFERENCES.putBoolean(ENABLED_PREF, enabled);
|
PREFERENCES.putBoolean(ENABLED_PREF, enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plays the background music.
|
||||||
|
*/
|
||||||
public void playMusic(){
|
public void playMusic(){
|
||||||
if (backgroundMusic != null){
|
if (backgroundMusic != null){
|
||||||
backgroundMusic.play();
|
backgroundMusic.play();
|
||||||
@@ -75,7 +88,7 @@ public void playMusic(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops background music
|
* Stops background music.
|
||||||
*/
|
*/
|
||||||
public void stopMusic(){
|
public void stopMusic(){
|
||||||
if (backgroundMusic != null){
|
if (backgroundMusic != null){
|
||||||
@@ -83,6 +96,11 @@ public void stopMusic(){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the volume of the background music and saves the volume setting in user preferences.
|
||||||
|
*
|
||||||
|
* @param volume the volume level to set (0.0f to 1.0f)
|
||||||
|
*/
|
||||||
public void setMusicVolume(float volume){
|
public void setMusicVolume(float volume){
|
||||||
if(backgroundMusic != null){
|
if(backgroundMusic != null){
|
||||||
backgroundMusic.setVolume(volume);
|
backgroundMusic.setVolume(volume);
|
||||||
|
|||||||
@@ -10,9 +10,6 @@
|
|||||||
import com.jme3.app.Application;
|
import com.jme3.app.Application;
|
||||||
import com.jme3.app.state.AbstractAppState;
|
import com.jme3.app.state.AbstractAppState;
|
||||||
import com.jme3.app.state.AppStateManager;
|
import com.jme3.app.state.AppStateManager;
|
||||||
import com.jme3.asset.AssetLoadException;
|
|
||||||
import com.jme3.asset.AssetNotFoundException;
|
|
||||||
import com.jme3.audio.AudioData;
|
|
||||||
import com.jme3.audio.AudioNode;
|
import com.jme3.audio.AudioNode;
|
||||||
import pp.battleship.notification.GameEventListener;
|
import pp.battleship.notification.GameEventListener;
|
||||||
import pp.battleship.notification.SoundEvent;
|
import pp.battleship.notification.SoundEvent;
|
||||||
@@ -22,6 +19,7 @@
|
|||||||
import java.util.prefs.Preferences;
|
import java.util.prefs.Preferences;
|
||||||
|
|
||||||
import static pp.util.PreferencesUtils.getPreferences;
|
import static pp.util.PreferencesUtils.getPreferences;
|
||||||
|
import static pp.util.Util.loadSound;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An application state that plays sounds.
|
* An application state that plays sounds.
|
||||||
@@ -81,26 +79,6 @@ public void initialize(AppStateManager stateManager, Application app) {
|
|||||||
explosionSound = loadSound(app, "Sound/Effects/explosion.wav"); //NON-NLS
|
explosionSound = loadSound(app, "Sound/Effects/explosion.wav"); //NON-NLS
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads a sound from the specified file.
|
|
||||||
*
|
|
||||||
* @param app The application
|
|
||||||
* @param name The name of the sound file.
|
|
||||||
* @return The loaded AudioNode.
|
|
||||||
*/
|
|
||||||
private AudioNode loadSound(Application app, String name) {
|
|
||||||
try {
|
|
||||||
final AudioNode sound = new AudioNode(app.getAssetManager(), name, AudioData.DataType.Buffer);
|
|
||||||
sound.setLooping(false);
|
|
||||||
sound.setPositional(false);
|
|
||||||
return sound;
|
|
||||||
}
|
|
||||||
catch (AssetLoadException | AssetNotFoundException ex) {
|
|
||||||
LOGGER.log(Level.ERROR, ex.getMessage(), ex);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plays the splash sound effect.
|
* Plays the splash sound effect.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -7,10 +7,12 @@
|
|||||||
|
|
||||||
package pp.battleship.client;
|
package pp.battleship.client;
|
||||||
|
|
||||||
|
import com.simsilica.lemur.Checkbox;
|
||||||
import com.simsilica.lemur.Container;
|
import com.simsilica.lemur.Container;
|
||||||
import com.simsilica.lemur.Label;
|
import com.simsilica.lemur.Label;
|
||||||
import com.simsilica.lemur.TextField;
|
import com.simsilica.lemur.TextField;
|
||||||
import com.simsilica.lemur.component.SpringGridLayout;
|
import com.simsilica.lemur.component.SpringGridLayout;
|
||||||
|
import pp.battleship.client.clienthost.BattleshipServerClient;
|
||||||
import pp.dialog.Dialog;
|
import pp.dialog.Dialog;
|
||||||
import pp.dialog.DialogBuilder;
|
import pp.dialog.DialogBuilder;
|
||||||
import pp.dialog.SimpleDialog;
|
import pp.dialog.SimpleDialog;
|
||||||
@@ -36,7 +38,10 @@ class NetworkDialog extends SimpleDialog {
|
|||||||
private String hostname;
|
private String hostname;
|
||||||
private int portNumber;
|
private int portNumber;
|
||||||
private Future<Object> connectionFuture;
|
private Future<Object> connectionFuture;
|
||||||
|
private Future<Object> serverFuture;
|
||||||
private Dialog progressDialog;
|
private Dialog progressDialog;
|
||||||
|
private BattleshipServerClient server;
|
||||||
|
private final Checkbox clientHostCheckbox;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new NetworkDialog.
|
* Constructs a new NetworkDialog.
|
||||||
@@ -56,7 +61,8 @@ class NetworkDialog extends SimpleDialog {
|
|||||||
input.addChild(host, 1);
|
input.addChild(host, 1);
|
||||||
input.addChild(new Label(lookup("port.number") + ": "));
|
input.addChild(new Label(lookup("port.number") + ": "));
|
||||||
input.addChild(port, 1);
|
input.addChild(port, 1);
|
||||||
|
clientHostCheckbox = new Checkbox("Host Server");
|
||||||
|
input.addChild(clientHostCheckbox);
|
||||||
DialogBuilder.simple(app.getDialogManager())
|
DialogBuilder.simple(app.getDialogManager())
|
||||||
.setTitle(lookup("server.dialog"))
|
.setTitle(lookup("server.dialog"))
|
||||||
.setExtension(d -> d.addChild(input))
|
.setExtension(d -> d.addChild(input))
|
||||||
@@ -77,11 +83,22 @@ private void connect() {
|
|||||||
hostname = host.getText().trim().isEmpty() ? LOCALHOST : host.getText();
|
hostname = host.getText().trim().isEmpty() ? LOCALHOST : host.getText();
|
||||||
portNumber = Integer.parseInt(port.getText());
|
portNumber = Integer.parseInt(port.getText());
|
||||||
openProgressDialog();
|
openProgressDialog();
|
||||||
|
|
||||||
|
if (clientHostCheckbox.isChecked()){
|
||||||
|
serverFuture = network.getApp().getExecutor().submit(this::initServer);
|
||||||
|
|
||||||
|
while (server == null || !server.isReady()){
|
||||||
|
Thread.sleep(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
connectionFuture = network.getApp().getExecutor().submit(this::initNetwork);
|
connectionFuture = network.getApp().getExecutor().submit(this::initNetwork);
|
||||||
}
|
}
|
||||||
catch (NumberFormatException e) {
|
catch (NumberFormatException e) {
|
||||||
network.getApp().errorDialog(lookup("port.must.be.integer"));
|
network.getApp().errorDialog(lookup("port.must.be.integer"));
|
||||||
}
|
}
|
||||||
|
catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -109,6 +126,22 @@ private Object initNetwork() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to initialize the server hosted by the client.
|
||||||
|
*
|
||||||
|
* @throws RuntimeException If an error occurs when starting the server.
|
||||||
|
*/
|
||||||
|
private Object initServer(){
|
||||||
|
try {
|
||||||
|
server = new BattleshipServerClient();
|
||||||
|
server.run();
|
||||||
|
return null;
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.log(Level.ERROR, "Error while starting server", e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is called by {@linkplain pp.dialog.DialogManager#update(float)} for periodically
|
* This method is called by {@linkplain pp.dialog.DialogManager#update(float)} for periodically
|
||||||
* updating this dialog. T
|
* updating this dialog. T
|
||||||
@@ -127,6 +160,17 @@ public void update(float delta) {
|
|||||||
LOGGER.log(Level.WARNING, "Interrupted!", e); //NON-NLS
|
LOGGER.log(Level.WARNING, "Interrupted!", e); //NON-NLS
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (serverFuture != null && serverFuture.isDone()) {
|
||||||
|
try {
|
||||||
|
serverFuture.get();
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
LOGGER.log(Level.ERROR, "Failed to start server", e.getCause());
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LOGGER.log(Level.WARNING, "Server thread was interrupted", e);
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,10 +2,21 @@
|
|||||||
|
|
||||||
import com.simsilica.lemur.Slider;
|
import com.simsilica.lemur.Slider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a volume slider for controlling the background music volume in the Battleship game.
|
||||||
|
* This class extends the {@link Slider} class and interfaces with the {@link GameMusic} instance
|
||||||
|
* to adjust the volume settings based on user input.
|
||||||
|
*/
|
||||||
public class VolumeSlider extends Slider {
|
public class VolumeSlider extends Slider {
|
||||||
private GameMusic gameMusic;
|
private final GameMusic gameMusic;
|
||||||
private float volume;
|
private float volume;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new VolumeSlider instance and initializes it with the current volume level
|
||||||
|
* from the game music preferences.
|
||||||
|
*
|
||||||
|
* @param gameMusic the instance of {@link GameMusic} to control music volume
|
||||||
|
*/
|
||||||
public VolumeSlider(GameMusic gameMusic){
|
public VolumeSlider(GameMusic gameMusic){
|
||||||
super();
|
super();
|
||||||
this.gameMusic = gameMusic;
|
this.gameMusic = gameMusic;
|
||||||
@@ -13,6 +24,11 @@ public VolumeSlider(GameMusic gameMusic){
|
|||||||
getModel().setPercent(volume);
|
getModel().setPercent(volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the volume setting based on the current slider position.
|
||||||
|
* If the slider's percent value has changed, it updates the music volume
|
||||||
|
* in the associated {@link GameMusic} instance.
|
||||||
|
*/
|
||||||
public void update(){
|
public void update(){
|
||||||
if(getModel().getPercent() != volume){
|
if(getModel().getPercent() != volume){
|
||||||
this.volume = (float) getModel().getPercent();
|
this.volume = (float) getModel().getPercent();
|
||||||
|
|||||||
@@ -0,0 +1,202 @@
|
|||||||
|
////////////////////////////////////////
|
||||||
|
// Programming project code
|
||||||
|
// UniBw M, 2022, 2023, 2024
|
||||||
|
// www.unibw.de/inf2
|
||||||
|
// (c) Mark Minas (mark.minas@unibw.de)
|
||||||
|
////////////////////////////////////////
|
||||||
|
|
||||||
|
package pp.battleship.client.clienthost;
|
||||||
|
|
||||||
|
import com.jme3.network.ConnectionListener;
|
||||||
|
import com.jme3.network.HostedConnection;
|
||||||
|
import com.jme3.network.Message;
|
||||||
|
import com.jme3.network.MessageListener;
|
||||||
|
import com.jme3.network.Network;
|
||||||
|
import com.jme3.network.Server;
|
||||||
|
import com.jme3.network.serializing.Serializer;
|
||||||
|
import pp.battleship.BattleshipConfig;
|
||||||
|
import pp.battleship.game.server.Player;
|
||||||
|
import pp.battleship.game.server.ServerGameLogic;
|
||||||
|
import pp.battleship.game.server.ServerSender;
|
||||||
|
import pp.battleship.message.client.ClientMessage;
|
||||||
|
import pp.battleship.message.client.MapMessage;
|
||||||
|
import pp.battleship.message.client.ShootMessage;
|
||||||
|
import pp.battleship.message.server.EffectMessage;
|
||||||
|
import pp.battleship.message.server.GameDetails;
|
||||||
|
import pp.battleship.message.server.ServerMessage;
|
||||||
|
import pp.battleship.message.server.StartBattleMessage;
|
||||||
|
import pp.battleship.model.Battleship;
|
||||||
|
import pp.battleship.model.IntPoint;
|
||||||
|
import pp.battleship.model.Shot;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.System.Logger;
|
||||||
|
import java.lang.System.Logger.Level;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
import java.util.logging.LogManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Server implementing the visitor pattern as MessageReceiver for ClientMessages
|
||||||
|
*/
|
||||||
|
public class BattleshipServerClient implements MessageListener<HostedConnection>, ConnectionListener, ServerSender {
|
||||||
|
private static final Logger LOGGER = System.getLogger(BattleshipServerClient.class.getName());
|
||||||
|
private static final File CONFIG_FILE = new File("server.properties");
|
||||||
|
|
||||||
|
private final BattleshipConfig config = new BattleshipConfig();
|
||||||
|
private Server myServer;
|
||||||
|
private final ServerGameLogic logic;
|
||||||
|
private final BlockingQueue<ReceivedMessageClient> pendingMessages = new LinkedBlockingQueue<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
// Configure logging
|
||||||
|
LogManager manager = LogManager.getLogManager();
|
||||||
|
try {
|
||||||
|
manager.readConfiguration(new FileInputStream("logging.properties"));
|
||||||
|
LOGGER.log(Level.INFO, "Successfully read logging properties"); //NON-NLS
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
LOGGER.log(Level.INFO, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the server and reads the configuration from the specified file.
|
||||||
|
* Initializes the game logic and sets up logging.
|
||||||
|
*/
|
||||||
|
public BattleshipServerClient() {
|
||||||
|
config.readFromIfExists(CONFIG_FILE);
|
||||||
|
LOGGER.log(Level.INFO, "Configuration: {0}", config); //NON-NLS
|
||||||
|
logic = new ServerGameLogic(this, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the server is ready.
|
||||||
|
*
|
||||||
|
* @return true if the server is running, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean isReady() {
|
||||||
|
return myServer != null && myServer.isRunning();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the server and continuously processes incoming messages.
|
||||||
|
*/
|
||||||
|
public void run() {
|
||||||
|
startServer();
|
||||||
|
while (true)
|
||||||
|
processNextMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the server by creating a network server on the specified port.
|
||||||
|
*/
|
||||||
|
private void startServer() {
|
||||||
|
try {
|
||||||
|
LOGGER.log(Level.INFO, "Starting server..."); //NON-NLS
|
||||||
|
myServer = Network.createServer(config.getPort());
|
||||||
|
initializeSerializables();
|
||||||
|
myServer.start();
|
||||||
|
registerListeners();
|
||||||
|
LOGGER.log(Level.INFO, "Server started: {0}", myServer.isRunning()); //NON-NLS
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
LOGGER.log(Level.ERROR, "Couldn't start server: {0}", e.getMessage()); //NON-NLS
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the next message in the queue.
|
||||||
|
*/
|
||||||
|
private void processNextMessage() {
|
||||||
|
try {
|
||||||
|
pendingMessages.take().process(logic);
|
||||||
|
}
|
||||||
|
catch (InterruptedException ex) {
|
||||||
|
LOGGER.log(Level.INFO, "Interrupted while waiting for messages"); //NON-NLS
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers all serializable message classes for network transmission.
|
||||||
|
*/
|
||||||
|
private void initializeSerializables() {
|
||||||
|
Serializer.registerClass(GameDetails.class);
|
||||||
|
Serializer.registerClass(StartBattleMessage.class);
|
||||||
|
Serializer.registerClass(MapMessage.class);
|
||||||
|
Serializer.registerClass(ShootMessage.class);
|
||||||
|
Serializer.registerClass(EffectMessage.class);
|
||||||
|
Serializer.registerClass(Battleship.class);
|
||||||
|
Serializer.registerClass(IntPoint.class);
|
||||||
|
Serializer.registerClass(Shot.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the message and connection listeners for the server.
|
||||||
|
*/
|
||||||
|
private void registerListeners() {
|
||||||
|
myServer.addMessageListener(this, MapMessage.class);
|
||||||
|
myServer.addMessageListener(this, ShootMessage.class);
|
||||||
|
myServer.addConnectionListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void messageReceived(HostedConnection source, Message message) {
|
||||||
|
LOGGER.log(Level.INFO, "message received from {0}: {1}", source.getId(), message); //NON-NLS
|
||||||
|
if (message instanceof ClientMessage clientMessage)
|
||||||
|
pendingMessages.add(new ReceivedMessageClient(clientMessage, source.getId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void connectionAdded(Server server, HostedConnection hostedConnection) {
|
||||||
|
LOGGER.log(Level.INFO, "new connection {0}", hostedConnection); //NON-NLS
|
||||||
|
logic.addPlayer(hostedConnection.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void connectionRemoved(Server server, HostedConnection hostedConnection) {
|
||||||
|
LOGGER.log(Level.INFO, "connection closed: {0}", hostedConnection); //NON-NLS
|
||||||
|
final Player player = logic.getPlayerById(hostedConnection.getId());
|
||||||
|
if (player == null)
|
||||||
|
LOGGER.log(Level.INFO, "closed connection does not belong to an active player"); //NON-NLS
|
||||||
|
else { //NON-NLS
|
||||||
|
LOGGER.log(Level.INFO, "closed connection belongs to {0}", player); //NON-NLS
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shuts down the server and closes all active connections.
|
||||||
|
*
|
||||||
|
* @param exitValue the exit code to terminate the program with
|
||||||
|
*/
|
||||||
|
private void exit(int exitValue) { //NON-NLS
|
||||||
|
LOGGER.log(Level.INFO, "close request"); //NON-NLS
|
||||||
|
if (myServer != null)
|
||||||
|
for (HostedConnection client : myServer.getConnections()) //NON-NLS
|
||||||
|
if (client != null) client.close("Game over"); //NON-NLS
|
||||||
|
System.exit(exitValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the specified message to the specified connection.
|
||||||
|
*
|
||||||
|
* @param id the connection id
|
||||||
|
* @param message the message
|
||||||
|
*/
|
||||||
|
public void send(int id, ServerMessage message) {
|
||||||
|
if (myServer == null || !myServer.isRunning()) {
|
||||||
|
LOGGER.log(Level.ERROR, "no server running when trying to send {0}", message); //NON-NLS
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final HostedConnection connection = myServer.getConnection(id);
|
||||||
|
if (connection != null)
|
||||||
|
connection.send(message);
|
||||||
|
else
|
||||||
|
LOGGER.log(Level.ERROR, "there is no connection with id={0}", id); //NON-NLS
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
////////////////////////////////////////
|
||||||
|
// Programming project code
|
||||||
|
// UniBw M, 2022, 2023, 2024
|
||||||
|
// www.unibw.de/inf2
|
||||||
|
// (c) Mark Minas (mark.minas@unibw.de)
|
||||||
|
////////////////////////////////////////
|
||||||
|
|
||||||
|
package pp.battleship.client.clienthost;
|
||||||
|
|
||||||
|
import pp.battleship.message.client.ClientInterpreter;
|
||||||
|
import pp.battleship.message.client.ClientMessage;
|
||||||
|
|
||||||
|
record ReceivedMessageClient(ClientMessage message, int from) {
|
||||||
|
void process(ClientInterpreter interpreter) {
|
||||||
|
message.accept(interpreter, from);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
import com.jme3.math.Vector3f;
|
import com.jme3.math.Vector3f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum representing different ship models for the Battleship game.
|
||||||
|
* Each ship model has a corresponding 3D model path, scale, translation, color texture, and optional bump texture.
|
||||||
|
*/
|
||||||
public enum ShipModel {
|
public enum ShipModel {
|
||||||
SHIP1("Models/Ships/1/ship1.j3o",0.15f, new Vector3f(0f, 0f, 0f), "Models/Ships/1/ship1_color.png", null),
|
SHIP1("Models/Ships/1/ship1.j3o",0.15f, new Vector3f(0f, 0f, 0f), "Models/Ships/1/ship1_color.png", null),
|
||||||
SHIP2("Models/Ships/2/ship2.j3o",0.03f, new Vector3f(0f, 0.2f, 0f), "Models/Ships/2/ship2.jpg", null),
|
SHIP2("Models/Ships/2/ship2.j3o",0.03f, new Vector3f(0f, 0.2f, 0f), "Models/Ships/2/ship2.jpg", null),
|
||||||
@@ -14,6 +18,15 @@ public enum ShipModel {
|
|||||||
private final String colorPath;
|
private final String colorPath;
|
||||||
private final String bumpPath;
|
private final String bumpPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new ShipModel with the specified parameters.
|
||||||
|
*
|
||||||
|
* @param modelPath the path to the 3D model of the ship
|
||||||
|
* @param modelScale the scale factor to be applied to the model
|
||||||
|
* @param translation the translation to be applied to the model
|
||||||
|
* @param colorPath the path to the color texture of the model
|
||||||
|
* @param bumpPath the optional path to the bump texture of the model (may be null)
|
||||||
|
*/
|
||||||
ShipModel(String modelPath, float modelScale, Vector3f translation, String colorPath, String bumpPath){
|
ShipModel(String modelPath, float modelScale, Vector3f translation, String colorPath, String bumpPath){
|
||||||
this.modelPath = modelPath;
|
this.modelPath = modelPath;
|
||||||
this.modelScale = modelScale;
|
this.modelScale = modelScale;
|
||||||
@@ -22,22 +35,47 @@ public enum ShipModel {
|
|||||||
this.bumpPath = bumpPath;
|
this.bumpPath = bumpPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the path to the bump texture of the ship model.
|
||||||
|
*
|
||||||
|
* @return the bump texture path, or null if no bump texture is defined
|
||||||
|
*/
|
||||||
public String getBumpPath() {
|
public String getBumpPath() {
|
||||||
return bumpPath;
|
return bumpPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the path to the color texture of the ship model.
|
||||||
|
*
|
||||||
|
* @return the color texture path
|
||||||
|
*/
|
||||||
public String getColorPath() {
|
public String getColorPath() {
|
||||||
return colorPath;
|
return colorPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the scale factor of the ship model.
|
||||||
|
*
|
||||||
|
* @return the scale factor
|
||||||
|
*/
|
||||||
public float getScale() {
|
public float getScale() {
|
||||||
return modelScale;
|
return modelScale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the path to the 3D model of the ship.
|
||||||
|
*
|
||||||
|
* @return the model path
|
||||||
|
*/
|
||||||
public String getPath() {
|
public String getPath() {
|
||||||
return modelPath;
|
return modelPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the translation to be applied to the ship model.
|
||||||
|
*
|
||||||
|
* @return the translation vector
|
||||||
|
*/
|
||||||
public Vector3f getTranslation() {
|
public Vector3f getTranslation() {
|
||||||
return translation;
|
return translation;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -155,7 +155,6 @@ public void received(MapMessage msg, int from) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the placement of battleships on the map.
|
* Validates the placement of battleships on the map.
|
||||||
*
|
|
||||||
* Ensures that:
|
* Ensures that:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>The number of ships matches the configuration.</li>
|
* <li>The number of ships matches the configuration.</li>
|
||||||
|
|||||||
@@ -52,10 +52,14 @@ public ShipMapDTO(ShipMap map) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the current ship map fits the game details provided.
|
* Checks if the ship map is compatible with the provided game details.
|
||||||
*
|
*
|
||||||
* @param details the game details to be matched against
|
* This method verifies that the ship map's dimensions match the game details,
|
||||||
* @return true if the ship map fits the game details, false otherwise
|
* that the total number of ships is correct, and that the ship placements
|
||||||
|
* do not overlap and stay within the map boundaries.
|
||||||
|
*
|
||||||
|
* @param details the game details containing required dimensions and ship configurations
|
||||||
|
* @return true if the ship map fits the game details; false otherwise
|
||||||
*/
|
*/
|
||||||
public boolean fits(GameDetails details) {
|
public boolean fits(GameDetails details) {
|
||||||
if (width != details.getWidth() || height != details.getHeight())
|
if (width != details.getWidth() || height != details.getHeight())
|
||||||
|
|||||||
@@ -7,6 +7,14 @@
|
|||||||
|
|
||||||
package pp.util;
|
package pp.util;
|
||||||
|
|
||||||
|
import com.jme3.app.Application;
|
||||||
|
import com.jme3.asset.AssetLoadException;
|
||||||
|
import com.jme3.asset.AssetNotFoundException;
|
||||||
|
import com.jme3.audio.AudioData;
|
||||||
|
import com.jme3.audio.AudioNode;
|
||||||
|
|
||||||
|
import java.lang.System.Logger;
|
||||||
|
import java.lang.System.Logger.Level;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@@ -17,6 +25,8 @@
|
|||||||
* A class with auxiliary functions.
|
* A class with auxiliary functions.
|
||||||
*/
|
*/
|
||||||
public class Util {
|
public class Util {
|
||||||
|
private static final Logger LOGGER = System.getLogger(Util.class.getName());
|
||||||
|
|
||||||
private Util() { /* do not instantiate */ }
|
private Util() { /* do not instantiate */ }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -87,4 +97,24 @@ public static <T, E extends T> Set<T> add(Set<T> set, E element) {
|
|||||||
newSet.add(element);
|
newSet.add(element);
|
||||||
return newSet;
|
return newSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a sound from the specified file.
|
||||||
|
*
|
||||||
|
* @param app The application
|
||||||
|
* @param name The name of the sound file.
|
||||||
|
* @return The loaded AudioNode.
|
||||||
|
*/
|
||||||
|
public static AudioNode loadSound(Application app, String name) {
|
||||||
|
try {
|
||||||
|
final AudioNode sound = new AudioNode(app.getAssetManager(), name, AudioData.DataType.Buffer);
|
||||||
|
sound.setLooping(false);
|
||||||
|
sound.setPositional(false);
|
||||||
|
return sound;
|
||||||
|
}
|
||||||
|
catch (AssetLoadException | AssetNotFoundException ex) {
|
||||||
|
LOGGER.log(Level.ERROR, ex.getMessage(), ex);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user