Merge branch 'develop' into 1250_SendingGameObjects

# Conflicts:
#	src/main/java/seng302/gameServer/GameState.java
#	src/main/java/seng302/gameServer/MainServerThread.java
#	src/main/java/seng302/visualiser/GameView.java
#	src/main/java/seng302/visualiser/controllers/RaceViewController.java
This commit is contained in:
William Muir
2017-09-09 14:02:12 +12:00
42 changed files with 1036 additions and 279 deletions
@@ -17,6 +17,7 @@ import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import seng302.gameServer.messages.BoatAction;
import seng302.gameServer.messages.BoatStatus;
import seng302.gameServer.messages.ChatterMessage;
import seng302.gameServer.messages.CustomizeRequestType;
import seng302.gameServer.messages.MarkRoundingMessage;
import seng302.gameServer.messages.MarkType;
@@ -45,7 +46,6 @@ public class GameState implements Runnable {
@FunctionalInterface
interface NewMessageListener {
void notify(Message message);
}
@@ -70,6 +70,7 @@ public class GameState implements Runnable {
private static Long previousUpdateTime;
public static Double windDirection;
private static Double windSpeed;
private static Double speedMultiplier = 1d;
private static Boolean customizationFlag; // dirty flag to tell if a player has customized their boat.
@@ -99,7 +100,7 @@ public class GameState implements Runnable {
players = new ArrayList<>();
GameState.hostIpAddress = hostIpAddress;
customizationFlag = false;
speedMultiplier = 1.0;
currentStage = GameStages.LOBBYING;
isRaceStarted = false;
//set this when game stage changes to prerace
@@ -431,7 +432,7 @@ public class GameState implements Runnable {
private void updateVelocity(ServerYacht yacht) {
Double trueWindAngle = Math.abs(windDirection - yacht.getHeading());
Double boatSpeedInKnots = PolarTable.getBoatSpeed(getWindSpeedKnots(), trueWindAngle);
Double maxBoatSpeed = GeoUtility.knotsToMMS(boatSpeedInKnots);
Double maxBoatSpeed = GeoUtility.knotsToMMS(boatSpeedInKnots) * speedMultiplier;
if (yacht.getPowerUp() != null) {
if (yacht.getPowerUp().equals(TokenType.BOOST)) {
maxBoatSpeed *= 2;
@@ -756,6 +757,35 @@ public class GameState implements Runnable {
}
public static void processChatter(ChatterMessage chatterMessage, boolean isHost) {
String chatterText = chatterMessage.getMessage();
String[] words = chatterText.split("\\s+");
if (words.length > 2 && isHost) {
switch (words[2].trim()) {
case ">speed":
try {
setSpeedMultiplier(Double.valueOf(words[3]));
notifyMessageListeners(new ChatterMessage(
chatterMessage.getMessage_type(),
"SERVER: Speed modifier set to x" + words[3]
));
} catch (Exception e) {
Logger logger = LoggerFactory.getLogger(GameState.class);
logger.error("cannot parse >speed value");
}
return;
case ">finish":
notifyMessageListeners(new ChatterMessage(
chatterMessage.getMessage_type(),
"SERVER: Game will now finish"
));
endRace();
return;
}
}
notifyMessageListeners(chatterMessage);
}
public static void addMessageEventListener(NewMessageListener listener) {
markListeners.add(listener);
}
@@ -772,4 +802,16 @@ public class GameState implements Runnable {
customizationFlag = false;
}
public static void endRace () {
yachts.forEach((id, yacht) -> yacht.setBoatStatus(BoatStatus.FINISHED));
currentStage = GameStages.FINISHED;
}
public static void setSpeedMultiplier (double multiplier) {
speedMultiplier = multiplier;
}
public static double getSpeedMultiplier () {
return speedMultiplier;
}
}
@@ -4,6 +4,8 @@ import java.io.IOException;
import java.util.Stack;
import java.util.Timer;
import java.util.TimerTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.model.Player;
import seng302.gameServer.messages.Heartbeat;
import seng302.gameServer.messages.Message;
@@ -14,6 +16,9 @@ import seng302.gameServer.messages.Message;
* cannot be sent to a player
*/
public class HeartbeatThread implements Runnable {
private Logger logger = LoggerFactory.getLogger(HeartbeatThread.class);
private final int HEARTBEAT_PERIOD = 200;
private ClientConnectionDelegate delegate;
private Integer seqNum;
@@ -44,20 +49,23 @@ public class HeartbeatThread implements Runnable {
* The delegate is notified if a player has disconnected
*/
private void sendHeartbeatToAllPlayers(){
Message heartbeat = new Heartbeat(seqNum);
for (Player player : GameState.getPlayers()){
if (!player.getSocket().isConnected()) {
playerLostConnection(player);
}
try {
player.getSocket().getOutputStream().write(heartbeat.getBuffer());
} catch (IOException e) {
playerLostConnection(player);
try {
Message heartbeat = new Heartbeat(seqNum);
for (Player player : GameState.getPlayers()) {
if (!player.getSocket().isConnected()) {
playerLostConnection(player);
}
try {
player.getSocket().getOutputStream().write(heartbeat.getBuffer());
} catch (IOException e) {
playerLostConnection(player);
}
}
updateDelegate();
seqNum++;
} catch (NullPointerException ne) {
logger.debug("Socket closed between checking for connection and sending heartbeat");
}
updateDelegate();
seqNum++;
}
/**
@@ -85,17 +85,20 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
//FINISHED
else if (GameState.getCurrentStage() == GameStages.FINISHED) {
terminate();
broadcastMessage(makeRaceStatusMessage());
try {
Thread.sleep(1000); //Hackish fix to make sure all threads have sent closing RaceStatus
terminate();
} catch (InterruptedException ie) {
serverLog("Thread interrupted while waiting to terminate clients", 1);
}
}
}
// TODO: 14/07/17 wmu16 - Send out disconnect packet to clients
try {
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
serverToClientThread.terminate();
}
serverSocket.close();
return;
} catch (IOException e) {
System.out.println("IO error in server thread handler upon closing socket");
}
@@ -199,6 +202,9 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
@Override
public void clientConnected(ServerToClientThread serverToClientThread) {
logger.debug("Player Connected From " + serverToClientThread.getThread().getName(), 0);
if (serverToClientThreads.size() == 0) { //Sets first client as host.
serverToClientThread.setAsHost();
}
serverToClientThreads.add(serverToClientThread);
serverToClientThread.addConnectionListener(this::sendSetupMessages);
serverToClientThread.addDisconnectListener(this::clientDisconnected);
@@ -2,6 +2,7 @@ package seng302.gameServer;
import java.util.Arrays;
import seng302.gameServer.messages.BoatAction;
import seng302.gameServer.messages.ChatterMessage;
import seng302.gameServer.messages.ClientType;
import seng302.gameServer.messages.CustomizeRequestType;
import seng302.gameServer.messages.Message;
@@ -28,5 +29,18 @@ public class ServerPacketParser {
long type = Message.bytesToLong(Arrays.copyOfRange(payload, 4, 5));
return CustomizeRequestType.getRequestType((int) type);
}
public static ChatterMessage extractChatterText(byte[] payload) {
return new ChatterMessage(
payload[1], new String(Arrays.copyOfRange(payload, 3, payload.length))
);
}
public static ChatterMessage extractChatterText(StreamPacket packet) {
byte[] payload = packet.getPayload();
return new ChatterMessage(
payload[1], new String(Arrays.copyOfRange(payload, 3, payload.length))
);
}
}
@@ -22,6 +22,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.messages.BoatAction;
import seng302.gameServer.messages.BoatLocationMessage;
import seng302.gameServer.messages.ChatterMessage;
import seng302.gameServer.messages.ClientType;
import seng302.gameServer.messages.CustomizeRequestType;
import seng302.gameServer.messages.Message;
@@ -75,6 +76,7 @@ public class ServerToClientThread implements Runnable {
private ClientType clientType;
private Boolean isRegistered = false;
private Boolean isHost = false;
private XMLGenerator xmlGenerator;
@@ -199,7 +201,12 @@ public class ServerToClientThread implements Runnable {
completeRegistration(requestedType);
break;
case CHATTER_TEXT:
ChatterMessage chatterMessage = ServerPacketParser
.extractChatterText(
new StreamPacket(type, payloadLength, timeStamp, payload));
GameState.processChatter(chatterMessage, isHost);
break;
case RACE_CUSTOMIZATION_REQUEST:
Long sourceID = Message
.bytesToLong(Arrays.copyOfRange(payload, 0, 3));
@@ -313,4 +320,41 @@ public class ServerToClientThread implements Runnable {
public void addDisconnectListener(DisconnectListener disconnectListener) {
this.disconnectListener = disconnectListener;
}
public void setAsHost() {
isHost = true;
}
private void checkChatterForCommands(ChatterMessage chatterMessage) {
// String chatterText = new String(
// Arrays.copyOfRange(chatterPayload, 3, 3 + chatterPayload.length)
// );
// String[] words = chatterText.split("\\s+");
// if (words.length > 2 && isHost) {
// switch (words[2].trim()) {
// case ">speed":
// try {
// GameState.setSpeedMultiplier(Double.valueOf(words[3]));
// GameState.broadcastChatter(new ChatterMessage(
// Byte.toUnsignedInt(chatterPayload[1]),
// "SERVER: Speed modifier set to x" + words[3]
// ));
// } catch (Exception e) {
// logger.error("cannot parse >speed value");
// }
// return;
// case ">finish":
// GameState.broadcastChatter(new ChatterMessage(
// chatterPayload[1],
// "SERVER: Game will now finish"
// ));
// GameState.endRace();
// return;
// }
// }
// GameState.broadcastChatter(
// ServerPacketParser.extractChatterText(chatterPayload)
// );
}
}
@@ -11,9 +11,11 @@ public class ChatterMessage extends Message {
private int message_size = 21;
private String message;
public ChatterMessage(int message_type, int message_size, String message) {
public ChatterMessage(int message_type, String message) {
byte[] byteMessage = message.getBytes();
this.message_type = message_type;
this.message_size = message_size;
this.message_size = byteMessage.length;
this.message = message;
setHeader(new Header(MessageType.CHATTER_TEXT, 1, (short) getSize()));
@@ -23,7 +25,7 @@ public class ChatterMessage extends Message {
putByte((byte) MESSAGE_VERSION_NUMBER);
putInt(message_type, 1);
putInt(message_size, 1);
putBytes(message.getBytes());
putBytes(byteMessage);
writeCRC();
rewind();
@@ -34,5 +36,11 @@ public class ChatterMessage extends Message {
return MESSAGE_SIZE + message_size;
}
public String getMessage() {
return message;
}
public int getMessage_type() {
return message_type;
}
}
@@ -15,6 +15,7 @@ import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import seng302.model.stream.parser.RaceStartData;
import seng302.model.stream.parser.RaceStatusData;
import seng302.utilities.Sounds;
/**
* Class for storing race data that does not relate to specific vessels or marks such as time or wind.
@@ -34,6 +35,7 @@ public class RaceState {
private long serverSystemTime;
private long expectedStartTime;
private boolean isRaceStarted = false;
private boolean gunFired = false;
long timeTillStart;
private ObservableList<ClientYacht> playerPositions;
private List<ClientYacht> collisions = new ArrayList<>();
@@ -64,6 +66,10 @@ public class RaceState {
if (raceTime < 0) {
return "-" + DATE_TIME_FORMAT.format(-1 * (raceTime - 1000));
} else {
if (!gunFired) {
gunFired = true;
Sounds.playCapGunSound();
}
return DATE_TIME_FORMAT.format(serverSystemTime - expectedStartTime);
}
}
@@ -57,7 +57,7 @@ public class RaceStatusData {
* Returns the data for boats collected form race status packets.
*
* @return A list of boat data. Boat data is in the form
* [boatID, estTimeToNextMark, estTimeToFinish, legNumber].
* [boatID, estTimeToNextMark, estTimeToFinish, legNumber, status].
*/
public List<long[]> getBoatData () {
return boatData;
+163
View File
@@ -0,0 +1,163 @@
package seng302.utilities;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
/**
* Static class for playing sounds throughout the program
*
* Created by kre39 on 28/08/17.
*/
public class Sounds {
private static MediaPlayer musicPlayer;
private static MediaPlayer soundEffect;
private static MediaPlayer soundPlayer;
private static boolean musicMuted = false;
private static boolean soundEffectsMuted = false;
public static void stopMusic() {
if (musicPlayer != null) {
musicPlayer.stop();
}
}
public static void setMutes() {
if (soundPlayer != null) {
soundPlayer.setMute(soundEffectsMuted);
}
if (soundEffect != null) {
soundEffect.setMute(soundEffectsMuted);
}
if (musicPlayer != null) {
musicPlayer.setMute(musicMuted);
}
}
public static void stopSoundEffects() {
if (soundEffect != null) {
soundEffect.stop();
}
}
public static void toggleMuteMusic() {
musicMuted = !musicMuted;
if (musicPlayer != null) {
musicPlayer.setMute(musicMuted);
}
}
public static void toggleMuteEffects() {
soundEffectsMuted = !soundEffectsMuted;
if (soundPlayer != null) {
soundPlayer.setMute(soundEffectsMuted);
}
if (soundEffect != null) {
soundEffect.setMute(soundEffectsMuted);
}
}
public static boolean isMusicMuted() {
return musicMuted;
}
public static boolean isSoundEffectsMuted() {
return soundEffectsMuted;
}
public static void playRaceMusic() {
// Media menuMusic = new Media(Sounds.class.getClassLoader().getResource("sounds/Chill-house-music-loop-116-bpm.wav").toString());
Media raceMusic = new Media(Sounds.class.getClassLoader().getResource("sounds/Music-loop-120-bpm.mp3").toString());
musicPlayer = new MediaPlayer(raceMusic);
musicPlayer.setCycleCount(MediaPlayer.INDEFINITE);
musicPlayer.play();
raceMusic = new Media(Sounds.class.getClassLoader().getResource("sounds/Sounds-of-the-ocean.mp3").toString());
soundEffect = new MediaPlayer(raceMusic);
soundEffect.setCycleCount(MediaPlayer.INDEFINITE);
// soundEffect.setVolume(0.3);
soundEffect.play();
musicPlayer.setMute(musicMuted);
soundEffect.setMute(soundEffectsMuted);
}
public static void playMenuMusic() {
Media menuMusic = new Media(
Sounds.class.getClassLoader().getResource("sounds/Elevator-music.mp3").toString());
musicPlayer = new MediaPlayer(menuMusic);
musicPlayer.setCycleCount(MediaPlayer.INDEFINITE);
musicPlayer.play();
}
public static void playFinishMusic() {
Media finishMusic = new Media(Sounds.class.getClassLoader().getResource("sounds/Happy-birthday-song.mp3").toString());
musicPlayer = new MediaPlayer(finishMusic);
musicPlayer.setCycleCount(MediaPlayer.INDEFINITE);
musicPlayer.play();
musicPlayer.setMute(musicMuted);
}
public static void playButtonClick() {
if (!soundEffectsMuted) {
Media buttonClick = new Media(
Sounds.class.getClassLoader().getResource("sounds/Button-click-sound.mp3")
.toString());
soundPlayer = new MediaPlayer(buttonClick);
soundPlayer.play();
soundPlayer.setMute(soundEffectsMuted);
}
}
public static void playFinishSound() {
if (!soundEffectsMuted) {
Media finishSound = new Media(
Sounds.class.getClassLoader().getResource("sounds/Sms-notification.mp3")
.toString());
soundPlayer = new MediaPlayer(finishSound);
soundPlayer.play();
}
}
public static void playMarkRoundingSound() {
if (!soundEffectsMuted) {
Media markRoundingSound = new Media(
Sounds.class.getClassLoader().getResource("sounds/sms-tone.mp3").toString());
soundPlayer = new MediaPlayer(markRoundingSound);
soundPlayer.play();
}
}
public static void playCapGunSound() {
if (!soundEffectsMuted) {
Media gunSound = new Media(
Sounds.class.getClassLoader().getResource("sounds/Gunshot-sound.mp3").toString());
soundPlayer = new MediaPlayer(gunSound);
soundPlayer.play();
}
}
public static void playCrashSound() {
if (!soundEffectsMuted) {
Media crashSound = new Media(
Sounds.class.getClassLoader().getResource("sounds/Large-metal-door-slam.mp3")
.toString());
soundPlayer = new MediaPlayer(crashSound);
soundPlayer.play();
}
}
public static void playHoverSound() {
if (!soundEffectsMuted) {
Media hoverSound = new Media(Sounds.class.getClassLoader().getResource("sounds/sound-over.wav").toString());
soundPlayer = new MediaPlayer(hoverSound);
soundPlayer.play();
}
}
}
@@ -2,9 +2,11 @@ package seng302.utilities;
import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javafx.util.Pair;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
@@ -62,31 +64,10 @@ public class StreamParser {
long windDir = bytesToLong(Arrays.copyOfRange(payload, 18, 20));
long rawWindSpeed = bytesToLong(Arrays.copyOfRange(payload, 20, 22));
// DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
// currentTime = format.format((new Date(currentTime)))
RaceStatusData data = new RaceStatusData(
windDir, rawWindSpeed, raceStatus, currentTime, expectedStartTime
);
// long timeTillStart =
// ((new Date(expectedStartTime)).getTime() - (new Date(currentTime)).getTime()) / 1000;
//
// if (timeTillStart > 0) {
// timeSinceStart = timeTillStart;
// } else {
// if (raceStatus == 4 || raceStatus == 8) {
// raceFinished = true;
// raceStarted = false;
// } else if (!raceStarted) {
// raceStarted = true;
// raceFinished = false;
// }
// timeSinceStart = timeTillStart;
// }
//
//
int noBoats = payload[22];
int raceType = payload[23];
long boatID, estTimeAtNextMark, estTimeAtFinish;
@@ -106,24 +87,6 @@ public class StreamParser {
return data;
}
// private static void setBoatLegPosition(Yacht updatingBoat, Integer leg){
// Integer placing = 1;
// if (leg != updatingBoat.getLegNumber() && (raceStarted || raceFinished)) {
// for (Yacht boat : boats.values()) {
// if (boat.getLegNumber() != null && leg <= boat.getLegNumber()){
// placing += 1;
// }
// }
// updatingBoat.setPlacing(placing.toString());
// updatingBoat.setLegNumber(leg);
// boatsPos.putIfAbsent(placing, updatingBoat);
// boatsPos.replace(placing, updatingBoat);
// } else if(updatingBoat.getLegNumber() == null){
// updatingBoat.setPlacing("1");
// updatingBoat.setLegNumber(leg);
// }
// }
/**
* Parses and returns the text from a StreamPacket containing text data for display.
*
@@ -255,15 +218,15 @@ public class StreamParser {
* @return Chatter text message as a string. Returns null if the packet is not of type
* CHATTER_TEXT.
*/
public static String extractChatterText(StreamPacket packet) {
public static Pair<Integer, String> extractChatterText(StreamPacket packet) {
if (packet.getType() != PacketType.CHATTER_TEXT) {
return null;
}
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
int messageType = payload[1];
int length = payload[2];
return new String(Arrays.copyOfRange(payload, 3, 3 + length));
int length = (int) bytesToLong(new byte[]{payload[2]});
return new Pair<>(messageType, new String(Arrays.copyOfRange(payload, 3, 3 + length)));
}
/**
@@ -392,26 +355,6 @@ public class StreamParser {
};
}
public static void extractBoatAction(StreamPacket packet) {
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
long actionType = bytesToLong(Arrays.copyOfRange(payload, 0, 1));
if (actionType == 1) {
System.out.println("VMG");
} else if (actionType == 2) {
System.out.println("SAILS IN");
} else if (actionType == 3) {
System.out.println("SAILS OUT");
} else if (actionType == 4) {
System.out.println("TACK/GYBE");
} else if (actionType == 5) {
System.out.println("UPWIND");
} else if (actionType == 6) {
System.out.println("DOWNWIND");
}
}
/**
* takes an array of up to 7 bytes and returns a positive long constructed from the input bytes
*
@@ -18,6 +18,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.messages.BoatAction;
import seng302.gameServer.messages.BoatActionMessage;
import seng302.gameServer.messages.ChatterMessage;
import seng302.gameServer.messages.ClientType;
import seng302.gameServer.messages.CustomizeRequestMessage;
import seng302.gameServer.messages.CustomizeRequestType;
@@ -281,9 +282,17 @@ public class ClientToServerThread implements Runnable {
* @param message The given message type.
*/
private void sendBoatActionMessage(BoatActionMessage message) {
sendByteBuffer(message.getBuffer());
}
public void sendChatterMessage(String message) {
sendByteBuffer(new ChatterMessage(clientId, message).getBuffer());
}
private void sendByteBuffer(byte[] bytes) {
if (clientId != -1) {
try {
os.write(message.getBuffer());
os.write(bytes);
} catch (IOException e) {
logger.warn("IOException on attempting to sendBoatAction from Client");
notifyDisconnectListeners("Cannot communicate with server");
@@ -292,7 +301,7 @@ public class ClientToServerThread implements Runnable {
}
}
private void closeSocket() {
public void closeSocket() {
try {
socket.close();
socketOpen = false;
@@ -1,8 +1,11 @@
package seng302.visualiser;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Date;
import java.util.Map;
import java.util.TimeZone;
import javafx.application.Platform;
@@ -12,8 +15,10 @@ import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Pane;
import javafx.util.Pair;
import seng302.gameServer.GameState;
import seng302.gameServer.MainServerThread;
import seng302.gameServer.messages.BoatAction;
@@ -28,6 +33,7 @@ import seng302.model.stream.parser.RaceStatusData;
import seng302.model.stream.parser.YachtEventData;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.model.stream.xml.parser.RegattaXMLData;
import seng302.utilities.Sounds;
import seng302.utilities.StreamParser;
import seng302.utilities.XMLParser;
import seng302.visualiser.controllers.FinishScreenViewController;
@@ -53,6 +59,8 @@ public class GameClient {
private RaceState raceState = new RaceState();
private LobbyController lobbyController;
private ArrayList<ClientYacht> finishedBoats = new ArrayList<>();
private ObservableList<String> clientLobbyList = FXCollections.observableArrayList();
/**
@@ -74,7 +82,6 @@ public class GameClient {
startClientToServerThread(ipAddress, portNumber);
socketThread.addDisconnectionListener((cause) -> {
showConnectionError(cause);
tearDownConnection();
Platform.runLater(this::loadStartScreen);
});
socketThread.addStreamObserver(this::parsePackets);
@@ -92,10 +99,7 @@ public class GameClient {
lobbyController.setCourseName("");
}
lobbyController.addCloseListener((exitCause) -> {
this.tearDownConnection();
this.loadStartScreen();
});
lobbyController.addCloseListener((exitCause) -> this.loadStartScreen());
this.lobbyController = lobbyController;
} catch (IOException ioe) {
showConnectionError("Unable to find server");
@@ -113,7 +117,6 @@ public class GameClient {
try {
startClientToServerThread(ipAddress, portNumber);
socketThread.addDisconnectionListener((cause) -> {
this.tearDownConnection();
Platform.runLater(this::loadStartScreen);
});
LobbyController lobbyController = loadLobby();
@@ -134,7 +137,8 @@ public class GameClient {
lobbyController.disableReadyButton();
server.startGame();
} else if (exitCause == CloseStatus.LEAVE) {
tearDownConnection();
server.terminate();
server = null;
loadStartScreen();
}
});
@@ -145,20 +149,11 @@ public class GameClient {
}
}
private void tearDownConnection() {
socketThread.setSocketToClose();
if (server != null) {
server.terminate();
server = null;
}
}
private void loadStartScreen() {
// socketThread.setSocketToClose();
// if (server != null) {
// server.terminate();
// server = null;
// }
if (socketThread != null) {
socketThread.setSocketToClose();
}
FXMLLoader fxmlLoader = new FXMLLoader(
getClass().getResource("/views/StartScreenView.fxml"));
try {
@@ -207,9 +202,19 @@ public class GameClient {
raceView = fxmlLoader.getController();
ClientYacht player = allBoatsMap.get(socketThread.getClientId());
raceView.loadRace(allBoatsMap, courseData, raceState, player);
raceView.getSendPressedProperty().addListener((obs, old, isPressed) -> {
if (isPressed) {
formatAndSendChatMessage(raceView.readChatInput());
}
});
}
private void loadFinishScreenView() {
Sounds.stopMusic();
Sounds.stopSoundEffects();
Sounds.playFinishMusic();
FXMLLoader fxmlLoader = loadFXMLToHolder("/views/FinishScreenView.fxml");
FinishScreenViewController controller = fxmlLoader.getController();
controller.setFinishers(raceState.getPlayerPositions());
@@ -293,6 +298,14 @@ public class GameClient {
case YACHT_EVENT_CODE:
showCollisionAlert(StreamParser.extractYachtEventCode(packet));
break;
case CHATTER_TEXT:
Pair<Integer, String> playerIdMessagePair = StreamParser
.extractChatterText(packet);
raceView.updateChatHistory(
allBoatsMap.get(playerIdMessagePair.getKey()).getColour(),
playerIdMessagePair.getValue()
);
}
}
}
@@ -347,6 +360,9 @@ public class GameClient {
for (ClientYacht yacht : allBoatsMap.values()) {
if (yacht.getBoatStatus() != BoatStatus.FINISHED.getCode()) {
raceFinished = false;
} else if (!finishedBoats.contains(yacht)) {
finishedBoats.add(yacht);
Sounds.playFinishSound();
}
}
@@ -362,6 +378,7 @@ public class GameClient {
}
if (raceFinished) {
Sounds.playFinishSound();
close();
loadFinishScreenView();
}
@@ -385,6 +402,12 @@ public class GameClient {
* @param e The key event triggering this call
*/
private void keyPressed(KeyEvent e) {
if (raceView.isChatInputFocused()) {
if (e.getCode() == KeyCode.ENTER) {
formatAndSendChatMessage(raceView.readChatInput());
}
return;
}
switch (e.getCode()) {
case SPACE: // align with vmg
socketThread.sendBoatAction(BoatAction.VMG); break;
@@ -393,12 +416,16 @@ public class GameClient {
case PAGE_DOWN: // downwind
socketThread.sendBoatAction(BoatAction.DOWNWIND); break;
case ENTER: // tack/gybe
// if chat box is active take whatever is in there and send it to server
socketThread.sendBoatAction(BoatAction.TACK_GYBE); break;
}
}
private void keyReleased(KeyEvent e) {
if (raceView.isChatInputFocused()) {
return;
}
switch (e.getCode()) {
//TODO 12/07/17 Determine the sail state and send the appropriate packet (eg. if sails are in, send a sail out packet)
case SHIFT: // sails in/sails out
@@ -421,6 +448,7 @@ public class GameClient {
private void showCollisionAlert(YachtEventData yachtEventData) {
// 33 is the agreed code to show collision
if (yachtEventData.getEventId() == 33) {
Sounds.playCrashSound();
raceState.storeCollision(
allBoatsMap.get(
yachtEventData.getSubjectId().intValue()
@@ -428,4 +456,19 @@ public class GameClient {
);
}
}
private void formatAndSendChatMessage(String rawChat) {
if (rawChat.length() > 0) {
socketThread.sendChatterMessage(
new SimpleDateFormat("[HH:mm:ss] ").format(new Date()) +
allBoatsMap.get(socketThread.getClientId()).getShortName() + ": " + rawChat
);
}
}
public ClientToServerThread getSocketThread() {
return socketThread;
}
}
+30 -12
View File
@@ -35,6 +35,7 @@ import seng302.model.mark.Corner;
import seng302.model.mark.Mark;
import seng302.model.token.Token;
import seng302.utilities.GeoUtility;
import seng302.utilities.Sounds;
import seng302.visualiser.fxObjects.AnnotationBox;
import seng302.visualiser.fxObjects.BoatObject;
import seng302.visualiser.fxObjects.CourseBoundary;
@@ -63,6 +64,7 @@ public class GameView extends Pane {
private double metersPerPixelX, metersPerPixelY;
final double SCALE_DELTA = 1.1;
private boolean isZoom = false;
private Text fpsDisplay = new Text();
private Polygon raceBorder = new CourseBoundary();
@@ -101,7 +103,7 @@ public class GameView extends Pane {
private void zoomOut() {
scaleFactor = 0.1;
if (this.getScaleX() > 0.5) {
if (this.isZoom && this.getScaleX() > 0.5) {
this.setScaleX(this.getScaleX() - scaleFactor);
this.setScaleY(this.getScaleY() - scaleFactor);
}
@@ -109,7 +111,7 @@ public class GameView extends Pane {
private void zoomIn() {
scaleFactor = 0.10;
if (this.getScaleX() < 2.5) {
if (this.isZoom && this.getScaleX() < 2.5) {
this.setScaleX(this.getScaleX() + scaleFactor);
this.setScaleY(this.getScaleY() + scaleFactor);
}
@@ -143,6 +145,13 @@ public class GameView extends Pane {
gameObjects.add(markers);
gameObjects.add(tokens);
initializeTimer();
this.sceneProperty().addListener(((observable, oldValue, scene) -> {
if (scene != null) {
setupZoom();
} else {
disableZoom();
}
}));
}
private void initializeTimer() {
@@ -462,17 +471,25 @@ public class GameView extends Pane {
/**
* Enables zoom. Has to be called after this is added to a scene.
*/
public void enableZoom () {
if (this.getScene() != null) {
this.getScene().addEventHandler(KeyEvent.KEY_PRESSED, (event) -> {
if (event.getCode() == KeyCode.Z) {
zoomIn();
} else if (event.getCode() == KeyCode.X) {
zoomOut();
}
});
}
private void setupZoom() {
this.getScene().addEventHandler(KeyEvent.KEY_PRESSED, (event) -> {
if (event.getCode() == KeyCode.Z) {
zoomIn();
} else if (event.getCode() == KeyCode.X) {
zoomOut();
}
});
enableZoom();
}
public void enableZoom() {
isZoom = true;
}
public void disableZoom() {
isZoom = false;
}
/**
* Rescales the race to the size of the window.
*
@@ -809,6 +826,7 @@ public class GameView extends Pane {
//Only show arrows for this and next leg.
CompoundMark nextMark = null;
if (legNumber < course.size() - 1) {
Sounds.playMarkRoundingSound();
nextMark = course.get(legNumber);
for (Mark mark : nextMark.getMarks()) {
markerObjects.get(mark).showNextEnterArrow();
@@ -7,6 +7,7 @@ import javafx.scene.control.TextField;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import seng302.gameServer.messages.CustomizeRequestType;
import seng302.utilities.Sounds;
import seng302.visualiser.ClientToServerThread;
public class CustomizationController {
@@ -34,7 +35,8 @@ public class CustomizationController {
@FXML
public void submitCustomization() {
System.out.println("Attempting to send");
Sounds.playButtonClick();
// System.out.println("Attempting to send");
socketThread.sendCustomizationRequest(CustomizeRequestType.NAME, nameField.getText().getBytes());
// TODO: 16/08/17 ajm412: Turn colors into byte array.
Color color = boatColorPicker.getValue();
@@ -15,10 +15,12 @@ import javafx.fxml.Initializable;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import seng302.model.ClientYacht;
import seng302.utilities.Sounds;
public class FinishScreenViewController implements Initializable {
@@ -85,6 +87,11 @@ public class FinishScreenViewController implements Initializable {
}
public void switchToStartScreenView() {
Sounds.playButtonClick();
setContentPane("/views/StartScreenView.fxml");
}
public void playButtonHoverSound(MouseEvent mouseEvent) {
Sounds.playHoverSound();
}
}
@@ -16,6 +16,7 @@ import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.stage.Stage;
@@ -23,6 +24,7 @@ import seng302.gameServer.GameStages;
import seng302.gameServer.GameState;
import seng302.model.Colors;
import seng302.model.RaceState;
import seng302.utilities.Sounds;
import seng302.visualiser.ClientToServerThread;
/**
@@ -31,6 +33,10 @@ import seng302.visualiser.ClientToServerThread;
*/
public class LobbyController {
public void playButtonHoverSound(MouseEvent mouseEvent) {
Sounds.playHoverSound();
}
public enum CloseStatus {
LEAVE,
READY
@@ -153,6 +159,7 @@ public class LobbyController {
@FXML
public void customize() {
Sounds.playButtonClick();
Parent root;
try {
FXMLLoader fxmlLoader = new FXMLLoader(LobbyController.class.getResource("/views/customizeView.fxml"));
@@ -184,6 +191,7 @@ public class LobbyController {
@FXML
public void leaveLobbyButtonPressed() {
Sounds.playButtonClick();
// TODO: 10/07/17 wmu16 - Finish function!
GameState.setCurrentStage(GameStages.CANCELLED);
// TODO: 20/07/17 wmu16 - Implement some way of terminating the game
@@ -193,6 +201,7 @@ public class LobbyController {
@FXML
public void readyButtonPressed() {
Sounds.playButtonClick();
GameState.setCurrentStage(GameStages.PRE_RACE);
// Do countdown logic here
@@ -9,6 +9,7 @@ import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
@@ -25,6 +26,7 @@ import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Slider;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
@@ -42,18 +44,30 @@ import seng302.model.mark.CompoundMark;
import seng302.model.mark.Mark;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.model.token.Token;
import seng302.utilities.Sounds;
import seng302.visualiser.GameView;
import seng302.visualiser.controllers.annotations.Annotation;
import seng302.visualiser.controllers.annotations.ImportantAnnotationController;
import seng302.visualiser.controllers.annotations.ImportantAnnotationDelegate;
import seng302.visualiser.controllers.annotations.ImportantAnnotationsState;
import seng302.visualiser.fxObjects.BoatObject;
import seng302.visualiser.fxObjects.ChatHistory;
/**
* Controller class that manages the display of a race
*/
public class RaceViewController extends Thread implements ImportantAnnotationDelegate {
private final int CHAT_LIMIT = 128;
@FXML
private Pane basePane;
@FXML
private Button chatSend;
@FXML
private Pane chatHistoryHolder;
@FXML
private TextField chatInput;
@FXML
private LineChart<String, Double> raceSparkLine;
@FXML
@@ -86,26 +100,51 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
private GameView gameView;
private RaceState raceState;
private ChatHistory chatHistory;
private Timeline timerTimeline;
private Timer timer = new Timer();
private List<Series<String, Double>> sparkLineData = new ArrayList<>();
private ImportantAnnotationsState importantAnnotations;
private ObservableList<ClientYacht> selectionComboBoxList = FXCollections.observableArrayList();
public void initialize() {
Sounds.stopMusic();
Sounds.playRaceMusic();
// Load a default important annotation state
importantAnnotations = new ImportantAnnotationsState();
//Formatting the y axis of the sparkline
// raceSparkLine.getYAxis().setRotate(180);
// raceSparkLine.getYAxis().setTickLabelRotation(180);
// raceSparkLine.getYAxis().setTranslateX(-5);
raceSparkLine.getYAxis().setRotate(180);
raceSparkLine.getYAxis().setTickLabelRotation(180);
raceSparkLine.getYAxis().setTranslateX(-5);
raceSparkLine.visibleProperty().setValue(false);
raceSparkLine.getYAxis().setAutoRanging(false);
sparklineYAxis.setTickMarkVisible(false);
positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString());
selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView());
chatInput.lengthProperty().addListener((obs, oldLen, newLen) -> {
if (newLen.intValue() > CHAT_LIMIT) {
chatInput.setText(chatInput.getText().substring(0, CHAT_LIMIT));
}
});
chatHistory = new ChatHistory();
chatHistoryHolder.getChildren().addAll(chatHistory);
chatHistory.prefWidthProperty().bind(
chatHistoryHolder.widthProperty()
);
chatHistory.prefHeightProperty().bind(
chatHistoryHolder.heightProperty()
);
// chatHistory.setFitToWidth(true);
// chatHistory.setFitToHeight(true);
// chatHistory.textProperty().addListener((obs, oldValue, newValue) -> {
// chatHistory.setScrollTop(Double.MAX_VALUE);
// });
contentAnchorPane.setOnMouseClicked((event) ->
contentAnchorPane.requestFocus()
);
}
public void loadRace (
@@ -117,12 +156,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
this.markers = raceData.getCompoundMarks();
this.raceState = raceState;
initializeUpdateTimer();
initialiseFPSCheckBox();
initialiseAnnotationSlider();
initialiseBoatSelectionComboBox();
initialiseSparkLine();
raceState.getPlayerPositions().addListener((ListChangeListener<ClientYacht>) c -> {
while (c.next()) {
if (c.wasPermutated()) {
@@ -138,11 +171,10 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
Platform.runLater(() -> contentAnchorPane.getChildren().add(0, gameView));
gameView.setBoats(new ArrayList<>(participants.values()));
gameView.updateBorder(raceData.getCourseLimit());
gameView.updateTokens(raceData.getTokens());
gameView.updateTokens(raceData.getTokens());
gameView.updateCourse(
new ArrayList<>(raceData.getCompoundMarks().values()), raceData.getMarkSequence()
);
gameView.enableZoom();
);
gameView.setBoatAsPlayer(player);
gameView.startRace();
@@ -157,6 +189,20 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
updateWindDirection(raceState.windDirectionProperty().doubleValue());
updateWindSpeed(raceState.getWindSpeed());
gameView.setWindDir(raceState.windDirectionProperty().doubleValue());
chatInput.focusedProperty().addListener((obs, oldValue, newValue) -> {
if (newValue) {
gameView.disableZoom();
} else {
gameView.enableZoom();
}
});
Platform.runLater(() -> {
initializeUpdateTimer();
initialiseFPSCheckBox();
initialiseAnnotationSlider();
initialiseBoatSelectionComboBox();
initialiseSparkLine();
});
}
/**
@@ -307,13 +353,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
)
);
}
// XYChart.Series<String, Double> positionData = sparkLineData.get(yacht.getSourceID());
// positionData.getData().add(
// new XYChart.Data<>(
// Integer.toString(legNumber),
// 1.0 + participants.size() - yacht.getPlacing()
// )
// );
}
@@ -535,10 +574,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
* for the combobox to take action upon selection
*/
private void initialiseBoatSelectionComboBox() {
yachtSelectionComboBox.setItems(
FXCollections.observableArrayList(participants.values())
);
//Null check is if the listener is fired but nothing selected
selectionComboBoxList.setAll(participants.values());
yachtSelectionComboBox.setItems(selectionComboBoxList);
yachtSelectionComboBox.valueProperty().addListener((obs, lastSelection, selectedBoat) -> {
if (selectedBoat != null) {
gameView.selectBoat(selectedBoat);
@@ -625,4 +662,24 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
gameView.updateBorder(raceData.getCourseLimit());
gameView.updateTokens(raceData.getTokens());
}
public ReadOnlyBooleanProperty getSendPressedProperty() {
return chatSend.pressedProperty();
}
public boolean isChatInputFocused() {
return chatInput.focusedProperty().getValue();
}
public String readChatInput() {
String chat = chatInput.getText();
chatInput.clear();
basePane.requestFocus();
return chat;
}
public void updateChatHistory(Paint playerColour, String newMessage) {
Platform.runLater(() -> chatHistory.addMessage(playerColour, newMessage));
}
}
@@ -6,12 +6,16 @@ import java.net.NetworkInterface;
import java.net.URL;
import java.util.Enumeration;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TextField;
import javafx.scene.control.ToggleButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import seng302.gameServer.GameState;
import seng302.utilities.Sounds;
import seng302.visualiser.GameClient;
/**
@@ -20,114 +24,56 @@ import seng302.visualiser.GameClient;
*/
public class StartScreenController implements Initializable {
@FXML
private ToggleButton muteMusicButton;
@FXML
private ToggleButton muteSoundsButton;
@FXML
private TextField ipTextField;
@FXML
private TextField portTextField;
@FXML
private GridPane startScreen2;
@FXML
private AnchorPane holder;
GameClient gameClient;
private GameClient gameClient;
public void initialize(URL url, ResourceBundle resourceBundle) {
Sounds.stopMusic();
Sounds.stopSoundEffects();
Sounds.playMenuMusic();
if (Sounds.isMusicMuted()) {
muteMusicButton.setText("UnMute Music");
} else {
muteMusicButton.setText("Mute Music");
}
if (Sounds.isSoundEffectsMuted()) {
muteSoundsButton.setText("UnMute Sounds");
} else {
muteSoundsButton.setText("Mute Sounds");
}
Sounds.setMutes();
// gameClient = new GameClient(holder);
}
//
// /**
// * Loads the fxml content into the parent pane
// * @param jfxUrl
// * @return the controller of the fxml
// */
// private Object setContentPane(String jfxUrl) {
// try {
// AnchorPane contentPane = (AnchorPane) startScreen2.getParent();
// contentPane.getChildren().removeAll();
// contentPane.getChildren().clear();
// contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
// FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(jfxUrl));
// contentPane.getChildren().addAll((Pane) fxmlLoader.load());
//
// return fxmlLoader.getController();
// } catch (IOException e) {
// e.printStackTrace();
// }
// return null;
// }
/**
* ATTEMPTS TO:
* Sets up a new game state with your IP address as designated as the host.
* Starts a thread to listen for incoming connections.
* Starts a client to server thread and connects to own ip.
* Switches to the lobby screen
* Creates an instance of GameClient and runs it as a host.
*/
@FXML
public void hostButtonPressed() {
// new GameState(getLocalHostIp());
Sounds.playButtonClick();
gameClient = new GameClient(holder);
gameClient.runAsHost(getLocalHostIp(), 4942);
// try {
//// String ipAddress = InetAddress.getLocalHost().getHostAddress();
//// new GameState(ipAddress);
//// new MainServerThread();
//// ClientToServerThread clientToServerThread = new ClientToServerThread("localhost", 4950);
//// controller.setClientToServerThread(clientToServerThread);
// // get the lobby controller so that we can pass the game server thread to it
// new GameState(getLocalHostIp());
// MainServerThread mainServerThread = new MainServerThread();
//// ClientState.setHost(true);
// // host will connect and handshake to itself after setting up the server
// // TODO: 24/07/17 wmu16 - Make port number some static global type constant?
//// ClientToServerThread clientToServerThread = new ClientToServerThread(ClientState.getHostIp(), 4942);
//// ClientState.setConnectedToHost(true);
//// controller.setClientToServerThread(clientToServerThread);
// LobbyController lobbyController = (LobbyController) setContentPane("/views/LobbyView.fxml");
// lobbyController.setMainServerThread(mainServerThread);
// } catch (Exception e) {
// Alert alert = new Alert(AlertType.ERROR);
// alert.setHeaderText("Cannot host");
// alert.setContentText("Oops, failed to host, try to restart.");
// alert.showAndWait();
// e.printStackTrace();
// }
}
/**
* ATTEMPTS TO:
* Connect to an ip address and port using the ip and port specified on start screen.
* Starts a Client To Server Thread to maintain connection to host.
* Switch view to lobby view.
* Creates an instance of GameClient and runs it has a client.
*/
@FXML
public void connectButtonPressed() {
// TODO: 10/07/17 wmu16 - Finish function
Sounds.playButtonClick();
gameClient = new GameClient(holder);
gameClient.runAsClient(ipTextField.getText().trim().toLowerCase(), 4942);
// try {
// String ipAddress = ipTextField.getText().trim().toLowerCase();
// Integer port = Integer.valueOf(portTextField.getText().trim());
//
//// ClientToServerThread clientToServerThread = new ClientToServerThread(ipAddress, port);
//// ClientState.setHost(false);
//// ClientState.setConnectedToHost(true);
//
//// controller.setClientToServerThread(clientToServerThread);
//// setContentPane("/views/LobbyView.fxml");
// } catch (Exception e) {
// Alert alert = new Alert(AlertType.ERROR);
// alert.setHeaderText("Cannot reach the host");
// alert.setContentText("Please check your host IP address.");
// alert.showAndWait();
// }
}
// public void setController(Controller controller) {
// this.controller = controller;
// }
/**
* Gets the local host ip address and sets this ip to ClientState.
@@ -162,7 +108,30 @@ public class StartScreenController implements Initializable {
if (ipAddress == null) {
System.out.println("[HOST] Cannot obtain local host ip address.");
}
// ClientState.setHostIp(ipAddress);
return ipAddress;
}
public void toggleMusic(ActionEvent actionEvent) {
Sounds.toggleMuteMusic();
Sounds.playButtonClick();
if (Sounds.isMusicMuted()) {
muteMusicButton.setText("UnMute Music");
} else {
muteMusicButton.setText("Mute Music");
}
}
public void toggleSounds(ActionEvent actionEvent) {
Sounds.toggleMuteEffects();
Sounds.playButtonClick();
if (Sounds.isSoundEffectsMuted()) {
muteSoundsButton.setText("UnMute Sounds");
} else {
muteSoundsButton.setText("Mute Sounds");
}
}
public void playButtonHoverSound(MouseEvent mouseEvent) {
Sounds.playHoverSound();
}
}
@@ -0,0 +1,75 @@
package seng302.visualiser.fxObjects;
import java.util.Arrays;
import javafx.collections.ListChangeListener;
import javafx.scene.Node;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Background;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
/**
* Extension of a ScrollPane that contains a TextFlow. Has an addMessage() function to parse and
* display chatter text.
*/
public class ChatHistory extends ScrollPane {
private TextFlow textFlow = new TextFlow();
public ChatHistory() {
this.setContent(textFlow);
this.setFitToWidth(true);
this.setFitToHeight(true);
this.setMaxHeight(Double.MAX_VALUE);
this.setMaxWidth(Double.MAX_VALUE);
this.setVbarPolicy(ScrollBarPolicy.AS_NEEDED);
this.setHbarPolicy(ScrollBarPolicy.NEVER);
this.lookup(".scroll-pane").setStyle("-fx-background: rgba(255, 255, 255, 0.1); -fx-background-color: rgba(255, 255, 255, 0.1);");
this.textFlow.setStyle(
"-fx-background: rgba(255, 255, 255, 0.1); -fx-background-color: rgba(255, 255, 255, 0.1);"
);
//This makes the window auto scroll.
textFlow.getChildren().addListener((ListChangeListener<Node>) c ->
this.setVvalue(1.0)
);
//This just makes it so that the ChatHistory is on focus it passes it off to the parent.
this.parentProperty().addListener((obs, old, parent) ->
this.focusedProperty().addListener((obsVal, oldVal, onFocus) -> {
if (onFocus) {
parent.requestFocus();
}
})
);
}
/**
* Adds a message to chat history. Messages should be either of the form:
* "[HH:MM:ss] player_name: message_text" or
* "SERVER: message_text"
* @param colour The colour of the user sending the message
* @param Text The chatter text message to be displayed
*/
public void addMessage (Paint colour, String Text) {
String[] words = Text.split(":");
if (words[0].trim().equals("SERVER")) {
Text text = new Text(Text + "\n\n");
text.setStyle("-fx-font-weight: bolder");
textFlow.getChildren().add(text);
} else {
Text timePlayer = new Text(
String.join(":", Arrays.copyOfRange(words, 0, 3)) + ":"
);
timePlayer.setStyle("-fx-font-weight: bold");
timePlayer.setFill(colour);
Text message = new Text(
String.join(":", Arrays.copyOfRange(words, 3, words.length)) + "\n\n"
);
message.wrappingWidthProperty().bind(this.widthProperty());
textFlow.getChildren().addAll(timePlayer, message);
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,5 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
@@ -10,6 +15,7 @@
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.text.Font?>
<GridPane fx:id="finishScreenGridPane" maxHeight="837.0" maxWidth="837.0" minHeight="837.0" minWidth="837.0" nodeOrientation="LEFT_TO_RIGHT" prefHeight="837.0" prefWidth="837.0" style="-fx-background-color: #2C2c36;" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.visualiser.controllers.FinishScreenViewController">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
@@ -42,6 +48,6 @@
<Insets bottom="50.0" />
</GridPane.margin>
</TableView>
<Button mnemonicParsing="false" onAction="#switchToStartScreenView" styleClass="blue-ui-btn" text="Return to Start Screen" GridPane.halignment="CENTER" GridPane.rowIndex="3" GridPane.valignment="TOP" />
<Button mnemonicParsing="false" onAction="#switchToStartScreenView" onMouseEntered="#playButtonHoverSound" styleClass="blue-ui-btn" text="Return to Start Screen" GridPane.halignment="CENTER" GridPane.rowIndex="3" GridPane.valignment="TOP" />
</children>
</GridPane>
+3 -3
View File
@@ -37,9 +37,9 @@
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Button fx:id="readyButton" focusTraversable="false" mnemonicParsing="false" onAction="#readyButtonPressed" prefWidth="101.0" text="Ready" GridPane.halignment="CENTER" />
<Button focusTraversable="false" mnemonicParsing="false" onAction="#leaveLobbyButtonPressed" text="Leave Lobby" GridPane.columnIndex="2" GridPane.halignment="CENTER" />
<Button fx:id="customizeButton" focusTraversable="false" mnemonicParsing="false" onAction="#customize" text="Customization" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.valignment="CENTER" />
<Button fx:id="readyButton" focusTraversable="false" mnemonicParsing="false" onAction="#readyButtonPressed" onMouseEntered="#playButtonHoverSound" prefWidth="101.0" text="Ready" GridPane.halignment="CENTER" />
<Button focusTraversable="false" mnemonicParsing="false" onAction="#leaveLobbyButtonPressed" onMouseEntered="#playButtonHoverSound" text="Leave Lobby" GridPane.columnIndex="2" GridPane.halignment="CENTER" />
<Button fx:id="customizeButton" focusTraversable="false" mnemonicParsing="false" onAction="#customize" onMouseEntered="#playButtonHoverSound" text="Customization" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.valignment="CENTER" />
</children>
</GridPane>
<GridPane GridPane.rowIndex="1">
+26 -35
View File
@@ -6,50 +6,41 @@
<?import javafx.scene.layout.*?>
<?import javafx.scene.shape.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.chart.CategoryAxis?>
<?import javafx.scene.chart.LineChart?>
<?import javafx.scene.chart.NumberAxis?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Slider?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Circle?>
<?import javafx.scene.text.Font?>
<?import javafx.scene.text.Text?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="998.0" prefWidth="1530.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.visualiser.controllers.RaceViewController">
<children>
<AnchorPane layoutX="322.0" layoutY="130.0" prefHeight="998.0" prefWidth="1281.0"
style="-fx-background-color: skyblue;" AnchorPane.bottomAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<AnchorPane fx:id="basePane" layoutX="322.0" layoutY="130.0" prefHeight="998.0" prefWidth="1281.0" style="-fx-background-color: skyblue;" AnchorPane.bottomAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<GridPane prefHeight="998.0" prefWidth="1281.0" AnchorPane.bottomAnchor="0.0"
AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0"
AnchorPane.topAnchor="0.0">
<GridPane prefHeight="998.0" prefWidth="1281.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="630.0" minWidth="10.0"
prefWidth="68.0"/>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="1213.0" minWidth="10.0"
prefWidth="1213.0"/>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="630.0" minWidth="10.0" prefWidth="68.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="1213.0" minWidth="10.0" prefWidth="1213.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="489.0" minHeight="1.0" prefHeight="24.0"
vgrow="SOMETIMES"/>
<RowConstraints maxHeight="997.0" minHeight="10.0" prefHeight="974.0"
vgrow="SOMETIMES"/>
<RowConstraints maxHeight="489.0" minHeight="1.0" prefHeight="24.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="997.0" minHeight="10.0" prefHeight="974.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<AnchorPane fx:id="contentAnchorPane" prefHeight="200.0" prefWidth="200.0"
GridPane.columnSpan="2" GridPane.rowSpan="2"/>
<Text fx:id="fpsDisplay" strokeType="OUTSIDE" strokeWidth="0.0" text="60 FPS"
GridPane.halignment="CENTER" GridPane.valignment="CENTER"/>
<AnchorPane fx:id="contentAnchorPane" prefHeight="200.0" prefWidth="200.0" GridPane.columnSpan="2" GridPane.rowSpan="2">
<children>
<AnchorPane layoutX="799.0" layoutY="702.0" prefHeight="214.0" prefWidth="468.0">
<children>
<VBox prefHeight="214.0" prefWidth="468.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Pane fx:id="chatHistoryHolder" maxHeight="9.9999999999E10" maxWidth="1.7976931348623157E308" prefHeight="9.9999999999E10" />
<HBox VBox.vgrow="NEVER">
<children>
<TextField fx:id="chatInput" focusTraversable="false" prefHeight="25.0" HBox.hgrow="ALWAYS" />
<Button fx:id="chatSend" focusTraversable="false" mnemonicParsing="false" text="Send" />
</children>
</HBox>
</children>
</VBox>
</children>
</AnchorPane>
</children>
</AnchorPane>
<Text fx:id="fpsDisplay" strokeType="OUTSIDE" strokeWidth="0.0" text="60 FPS" GridPane.halignment="CENTER" GridPane.valignment="CENTER" />
</children>
</GridPane>
</children>
+24 -5
View File
@@ -20,15 +20,13 @@
<children>
<GridPane fx:id="startScreen2" layoutX="365.0" layoutY="285.0" nodeOrientation="LEFT_TO_RIGHT" prefWidth="800.0" style="-fx-background-color: #2C2c36;">
<children>
<Label alignment="CENTER" text="Party Parrots at Sea" textFill="WHITE"
GridPane.columnSpan="2147483647" GridPane.halignment="CENTER" GridPane.rowIndex="1"
GridPane.valignment="BOTTOM">
<Label alignment="CENTER" text="Party Parrots at Sea" textFill="WHITE" GridPane.columnSpan="2147483647" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="BOTTOM">
<font>
<Font size="40.0" />
</font>
</Label>
<Button mnemonicParsing="false" onAction="#hostButtonPressed" prefHeight="25.0" prefWidth="175.0" text="Host" GridPane.columnSpan="2147483647" GridPane.halignment="CENTER" GridPane.rowIndex="2" />
<Button mnemonicParsing="false" onAction="#connectButtonPressed" prefHeight="25.0" prefWidth="147.0" text="Connect" GridPane.columnIndex="1" GridPane.rowIndex="4">
<Button mnemonicParsing="false" onAction="#hostButtonPressed" onMouseEntered="#playButtonHoverSound" prefHeight="25.0" prefWidth="175.0" text="Host" GridPane.columnSpan="2147483647" GridPane.halignment="CENTER" GridPane.rowIndex="2" />
<Button mnemonicParsing="false" onAction="#connectButtonPressed" onMouseEntered="#playButtonHoverSound" prefHeight="25.0" prefWidth="147.0" text="Connect" GridPane.columnIndex="1" GridPane.rowIndex="4">
<GridPane.margin>
<Insets left="5.0" right="5.0" />
</GridPane.margin>
@@ -51,6 +49,27 @@
<Insets left="5.0" right="5.0" />
</GridPane.margin>
</TextField>
<GridPane GridPane.columnSpan="2" GridPane.rowIndex="5">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<ToggleButton fx:id="muteMusicButton" mnemonicParsing="false" onAction="#toggleMusic" onMouseEntered="#playButtonHoverSound" prefWidth="130.0" text="Mute Music" GridPane.halignment="RIGHT">
<GridPane.margin>
<Insets left="5.0" right="5.0" />
</GridPane.margin>
</ToggleButton>
<ToggleButton fx:id="muteSoundsButton" mnemonicParsing="false" onAction="#toggleSounds" onMouseEntered="#playButtonHoverSound" prefWidth="130.0" text="Mute Sounds" GridPane.columnIndex="1">
<GridPane.margin>
<Insets left="5.0" right="5.0" />
</GridPane.margin>
</ToggleButton>
</children>
</GridPane>
</children>
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="442.0" />
+5
View File
@@ -0,0 +1,5 @@
Feature: SendChat
Scenario: User send chat to another client
Given There are two games running
When the first client has sent the message "Hello world"
Then the other client should receive the message "Hello world"
@@ -0,0 +1,247 @@
package seng302.gameServer.server;
import java.io.IOException;
import org.junit.Assert;
import org.junit.Test;
import seng302.gameServer.GameState;
import seng302.gameServer.MainServerThread;
import seng302.gameServer.messages.BoatStatus;
import seng302.model.stream.packets.StreamPacket;
import seng302.model.stream.parser.RaceStatusData;
import seng302.utilities.StreamParser;
import seng302.visualiser.ClientToServerThread;
/**
* Created by cir27 on 3/09/17.
*/
public class ChatCommandsTest {
private boolean dcSent = false;
private ClientToServerThread client;
private ClientToServerThread host;
private MainServerThread mst;
@Test
public void sendFinishAsHost () {
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
try {
dcSent = false;
new GameState("localhost");
mst = new MainServerThread();
host = new ClientToServerThread("localhost", 4942);
host.addStreamObserver(() -> {
while (host.getPacketQueue().peek() != null) {
StreamPacket packet = host.getPacketQueue().poll();
switch (packet.getType()) {
case RACE_STATUS:
RaceStatusData rsd = StreamParser.extractRaceStatus(packet);
if (rsd.getBoatData().get(0)[4] == BoatStatus.FINISHED.getCode()) {
mst.terminate();
Assert.assertTrue(dcSent);
}
break;
default:
break;
}
}
});
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
mst.startGame();
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
host.sendChatterMessage("[time_prefix] <name_prefix> >finish");
dcSent = true;
try {
Thread.sleep(2000);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
host = null;
client = null;
mst = null;
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
@Test
public void sendSpeedAsHostValid () {
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
new GameState("localhost");
mst = new MainServerThread();
host = null;
try {
host = new ClientToServerThread("localhost", 4942);
} catch (IOException ioe) {
ioe.printStackTrace();
}
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
mst.startGame();
host.sendChatterMessage("[time_prefix] <name_prefix> >speed 5.0");
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
Assert.assertEquals(5.0, GameState.getSpeedMultiplier(), 0.00001);
mst.terminate();
try {
Thread.sleep(2000);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
host = null;
client = null;
mst = null;
}
@Test
public void sendSpeedAsHostInvalid () {
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
new GameState("localhost");
mst = new MainServerThread();
host = null;
try {
host = new ClientToServerThread("localhost", 4942);
} catch (IOException ioe) {
ioe.printStackTrace();
}
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
mst.startGame();
host.sendChatterMessage("[time_prefix] <name_prefix> >speed fdgdgdfg");
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
mst.terminate();
Assert.assertEquals(1.0, GameState.getSpeedMultiplier(), 0.00001);
try {
Thread.sleep(2000);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
@Test
public void sendCommandAsClient () {
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
mst = new MainServerThread();
try {
host = new ClientToServerThread("localhost", 4942);
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
client = new ClientToServerThread("localhost", 4942);
} catch (IOException ioe) {
ioe.printStackTrace();
}
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
mst.startGame();
try {
Thread.sleep(200);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
client.sendChatterMessage("[time_prefix] <name_prefix> >speed 5.0");
try {
Thread.sleep(200);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
Assert.assertEquals(1.0, GameState.getSpeedMultiplier(), 0.00001);
mst.terminate();
host.setSocketToClose();
client.setSocketToClose();
try {
Thread.sleep(2000);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
@Test
public void receiveFinishedAsClient () {
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
new GameState("localhost");
dcSent = false;
mst = new MainServerThread();
host = null;
try {
host = new ClientToServerThread("localhost", 4942);
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
client = new ClientToServerThread("localhost", 4942);
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
client.addStreamObserver(() -> {
while (client.getPacketQueue().peek() != null) {
StreamPacket packet = client.getPacketQueue().poll();
switch (packet.getType()) {
case RACE_STATUS:
RaceStatusData rsd = StreamParser.extractRaceStatus(packet);
if (rsd.getBoatData().get(0)[4] == BoatStatus.FINISHED.getCode()) {
mst.terminate();
Assert.assertTrue(dcSent);
}
break;
default:
break;
}
}
});
} catch (IOException ioe) {
ioe.printStackTrace();
}
host.sendChatterMessage("[time_prefix] <name_prefix> >finish");
dcSent = true;
}
}
+12 -8
View File
@@ -2,7 +2,10 @@ package seng302.models;
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import javafx.util.Pair;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -21,6 +24,7 @@ public class YachtTest {
@BeforeClass
public static void setUp() {
new GameState("localhost");
y1 = new ServerYacht("Yacht", 1, "Y1", "Y1", "Yacht 1", "C1");
gs = new GameState("localhost");
}
@@ -52,7 +56,7 @@ public class YachtTest {
Double upwind = PolarTable.getOptimalUpwindVMG(windSpeed).keySet().iterator().next();
Double downwind = PolarTable.getOptimalDownwindVMG(windSpeed).keySet().iterator().next();
HashMap<Double, Double> values = new HashMap<>();
List<Pair<Double, Double>> values = new ArrayList<>();
upwind = (double) Math.floorMod(upwind.longValue() + windDirection.longValue(), 360L);
Double upwindRight = upwind;
@@ -61,19 +65,19 @@ public class YachtTest {
Double downwindRight = downwind;
Double downwindLeft = 360 - downwindRight;
values.put(190d, upwindRight);
values.put(170d, upwindLeft);
values.put(10d, downwindLeft);
values.put(350d, downwindRight);
values.add(new Pair<>(190d, upwindRight));
values.add(new Pair<>(170d, upwindLeft));
values.add(new Pair<>(10d, downwindLeft));
values.add(new Pair<>(350d, downwindRight));
for (Double begin : values.keySet()) {
y1.setHeading(begin);
for (Pair<Double, Double> beginEndPair : values) {
y1.setHeading(beginEndPair.getKey());
y1.turnToVMG();
for (int i = 0; i < 200; i++) {
y1.runAutoPilot();
}
y1.disableAutoPilot();
assertEquals(values.get(begin), y1.getHeading(), 5.0);
assertEquals(beginEndPair.getValue(), y1.getHeading(), 5.0);
}
}
+59
View File
@@ -0,0 +1,59 @@
package steps;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;
import javafx.util.Pair;
import org.junit.Assert;
import seng302.gameServer.MainServerThread;
import seng302.model.stream.packets.StreamPacket;
import seng302.utilities.StreamParser;
import seng302.visualiser.ClientToServerThread;
/**
* Cucumber test for sending chat messages
* Created by kre39 on 7/08/17.
*/
public class SendChatSteps {
private ClientToServerThread client;
private ClientToServerThread host;
private MainServerThread mst;
@Given("^There are two games running$")
public void the_are_two_games_running() throws Throwable {
mst = new MainServerThread();
host = new ClientToServerThread("localhost", 4942);
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
client = new ClientToServerThread("localhost", 4942);
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
mst.startGame();
Thread.sleep(200);
}
@When("^the first client has sent the message \"([^\"]*)\"$")
public void the_user_has_pressed_sends_the_message_in_a_text_box(String arg1) throws Throwable {
client.sendChatterMessage("[time_prefix] <name_prefix> " + arg1);
}
@Then("^the other client should receive the message \"([^\"]*)\"$")
public void the_other_client_should_receive_the_message(String arg1) throws Throwable {
Object[] packets = host.getPacketQueue().toArray();
Pair<Integer, String> message = StreamParser.extractChatterText((StreamPacket) packets[packets.length - 1]);
Assert.assertEquals("[time_prefix] <name_prefix> " + arg1, message.getValue());
mst.terminate();
host.setSocketToClose();
client.setSocketToClose();
}
}
+3
View File
@@ -13,6 +13,7 @@ import seng302.model.ServerYacht;
import seng302.visualiser.ClientToServerThread;
/**
* Cucumber test for toggling sail
* Created by kre39 on 7/08/17.
*/
public class ToggleSailSteps {
@@ -49,5 +50,7 @@ public class ToggleSailSteps {
} else {
Assert.assertFalse(yacht.getSailIn());
}
mst.terminate();
client.closeSocket();
}
}