Merge branch 'story77' into 'master'

Story77



See merge request !44
main
Fan-Wu Yang 8 years ago
commit e4a8bd08e7

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>team-7</artifactId>
<groupId>seng302</groupId>
<version>2.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<name>matchBrowser</name>
<artifactId>matchBrowser</artifactId>
<version>2.0</version>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-all -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>seng302</groupId>
<artifactId>racevisionGame</artifactId>
<version>2.0</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</version>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>app.Main</Main-Class>
<X-Compile-Source-JDK>${maven.compiler.source}</X-Compile-Source-JDK>
<X-Compile-Target-JDK>${maven.compiler.target}</X-Compile-Target-JDK>
</manifestEntries>
</transformer>
</transformers>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,12 @@
package app;
import networkInterface.NetworkInterface;
/**
* Used when starting the matchmaking browser
*/
public class Main {
public static void main(String[] args) {
NetworkInterface networkInterface = new NetworkInterface();
}
}

@ -0,0 +1,37 @@
package model;
public class ClientAddress {
private String ip;
private int port;
public ClientAddress(String ip, int port) {
this.ip = ip;
this.port = port;
}
public String getIp() {
return ip;
}
public int getPort() {
return port;
}
@Override
public boolean equals(Object o) {
return o != null && o instanceof ClientAddress && hashCode() == o.hashCode();
}
@Override
public int hashCode() {
int result = ip != null ? ip.hashCode() : 0;
result *= 31;
return result;
}
@Override
public String toString() {
return "{ip='" + ip + '\'' +
", port=" + port+"}";
}
}

@ -0,0 +1,32 @@
package model;
import network.Messages.HostGame;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* Holds a table object that stores current games
*/
public class MatchTable {
private HashMap<ClientAddress, HostGame> matchTable;
public MatchTable() {
this.matchTable = new HashMap<>();
}
public void addEntry(ClientAddress address, HostGame newEntry) {
this.matchTable.put(address, newEntry);
}
public HashMap<ClientAddress, HostGame> getMatchTable() {
return matchTable;
}
@Override
public String toString() {
return "MatchTable=" + matchTable;
}
}

@ -0,0 +1,37 @@
package model;
/**
* Used to create a key made of an ip and port.
*/
public class TableKey {
private final String ip;
private final int port;
public TableKey(String ip, int port) {
this.ip = ip;
this.port = port;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof TableKey)) return false;
TableKey key = (TableKey) o;
return ip.equals(key.ip) && port == key.port;
}
@Override
public int hashCode() {
int result = port;
result = 31 * result + ip.hashCode();
return result;
}
@Override
public String toString() {
return "[ip='" + ip + '\'' +
", port=" + port +
']';
}
}

@ -0,0 +1,132 @@
package networkInterface;
import model.ClientAddress;
import model.MatchTable;
import network.BinaryMessageDecoder;
import network.Exceptions.InvalidMessageException;
import network.MessageDecoders.HostGameMessageDecoder;
import network.MessageDecoders.HostedGamesRequestDecoder;
import network.MessageEncoders.HostedGamesRequestEncoder;
import network.Messages.Enums.MessageType;
import network.Messages.HostGame;
import network.Messages.HostGamesRequest;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.time.LocalDateTime;
import java.util.*;
/**
* Holds the output for the network for
*/
public class NetworkInterface {
private Timer scheduler;
private DatagramSocket serverSocket;
private byte[] receiveData = new byte[1024];
private Set<ClientAddress> clientsAddresses;
private MatchTable matchTable;
public NetworkInterface(){
this.clientsAddresses = new HashSet<>();
this.matchTable = new MatchTable();
this.scheduler = new Timer(true);
try {
this.serverSocket = new DatagramSocket(3779);
startBroadcast(5000);
scheduleFlush(70000);
this.run();
} catch (IOException e) {
System.err.println("Error listening on port: " + this.serverSocket.getLocalPort() + ".");
System.exit(-1);
}
}
/**
* Broadcasts match table to clients at a requested interval
* @param period interval to broadcast table
*/
private void startBroadcast(int period) {
scheduler.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
List<HostGame> games = new ArrayList<>();
for(Map.Entry<ClientAddress, HostGame> tableEntry: matchTable.getMatchTable().entrySet()) {
HostGame game = tableEntry.getValue();
if(game != null) {
games.add(game);
}
}
HostedGamesRequestEncoder encoder = new HostedGamesRequestEncoder();
try {
byte[] message = encoder.encode(new HostGamesRequest(games));
for(ClientAddress address: clientsAddresses) {
serverSocket.send(new DatagramPacket(message, message.length, InetAddress.getByName(address.getIp()), 4941));
}
} catch (InvalidMessageException | IOException e) {
e.printStackTrace();
}
}
}, period, period);
}
/**
* Flushes the match table at a requested interval
* @param period interval to flush table
*/
private void scheduleFlush(int period) {
scheduler.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
matchTable.getMatchTable().clear();
}
}, period, period);
}
private void run() throws IOException{
while(true) {
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
serverSocket.receive(receivePacket);
BinaryMessageDecoder messageDecoder = new BinaryMessageDecoder(receivePacket.getData());
switch (MessageType.fromByte(messageDecoder.getHeaderMessageType())){
case HOST_GAME:
//decode and update table
HostGameMessageDecoder decoder = new HostGameMessageDecoder();
HostGame newKnownGame;
try{
newKnownGame = (HostGame) decoder.decode(messageDecoder.getMessageBody());
newKnownGame.setIp(receivePacket.getAddress().getHostAddress());
this.matchTable.addEntry(new ClientAddress(receivePacket.getAddress().getHostAddress(), receivePacket.getPort()), newKnownGame);
}catch (InvalidMessageException e){
System.out.println(e);
System.err.println("Message received that is not a hostedGame packet");
}
break;
case HOSTED_GAMES_REQUEST:
//update known clients
HostedGamesRequestDecoder decoder2 = new HostedGamesRequestDecoder();
HostGamesRequest newKnownGames;
try{
newKnownGames = (HostGamesRequest) decoder2.decode(messageDecoder.getMessageBody());
if (newKnownGames.getKnownGames().size() == 0){
//this is just an alert message with no content
clientsAddresses.add(new ClientAddress(receivePacket.getAddress().getHostAddress(), receivePacket.getPort()));
}
}catch (InvalidMessageException e){
System.out.println(e);
System.err.println("Message received that is not a hostedGamesRequest packet");
}
break;
}
}
}
}

@ -0,0 +1,29 @@
package model;
import network.Messages.Enums.RaceStatusEnum;
import network.Messages.HostGame;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import static org.junit.Assert.assertEquals;
public class MatchTableTest {
private MatchTable testTable;
@Before
public void setUp() {
testTable = new MatchTable();
}
@Test
public void testTable() {
HostGame entry = new HostGame("127.0.0.1", 4942, (byte)1, (byte)1, RaceStatusEnum.PRESTART, (byte)6, (byte)1);
testTable.addEntry(new ClientAddress("127.0.0.1", 3779), entry);
assertEquals(testTable.getMatchTable().get(new ClientAddress("127.0.0.1", 4942)), entry);
}
}

@ -10,6 +10,7 @@
<modules> <modules>
<module>racevisionGame</module> <module>racevisionGame</module>
<module>dedicatedServer</module> <module>dedicatedServer</module>
<module>matchBrowser</module>
</modules> </modules>
<url>https://eng-git.canterbury.ac.nz/SENG302-2016/team-7</url> <url>https://eng-git.canterbury.ac.nz/SENG302-2016/team-7</url>

@ -10,6 +10,8 @@ import mock.model.commandFactory.CompositeCommand;
import mock.model.wind.ShiftingWindGenerator; import mock.model.wind.ShiftingWindGenerator;
import mock.model.wind.WindGenerator; import mock.model.wind.WindGenerator;
import mock.xml.RaceXMLCreator; import mock.xml.RaceXMLCreator;
import network.Messages.Enums.RaceStatusEnum;
import network.Messages.HostGame;
import network.Messages.LatestMessages; import network.Messages.LatestMessages;
import shared.dataInput.*; import shared.dataInput.*;
import shared.enums.XMLFileType; import shared.enums.XMLFileType;
@ -20,7 +22,8 @@ import shared.exceptions.XMLReaderException;
import shared.model.Bearing; import shared.model.Bearing;
import shared.model.Constants; import shared.model.Constants;
import java.io.*; import java.io.IOException;
import java.net.Inet4Address;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
@ -105,6 +108,8 @@ public class Event {
boatsXMLFile = "mock/mockXML/boatsSinglePlayer.xml"; boatsXMLFile = "mock/mockXML/boatsSinglePlayer.xml";
} }
double windAngle = 300;
//Read XML files. //Read XML files.
try { try {
//this.raceXML = RaceXMLCreator.alterRaceToWind(raceXMLFile, 90); //this.raceXML = RaceXMLCreator.alterRaceToWind(raceXMLFile, 90);
@ -113,7 +118,7 @@ public class Event {
//this.raceXML = Event.setRaceXMLAtCurrentTimeToNow(XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8), 1000, 5000); //this.raceXML = Event.setRaceXMLAtCurrentTimeToNow(XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8), 1000, 5000);
this.raceXML = RaceXMLCreator.alterRaceToWind(this.raceXML, XMLFileType.Contents, -1, true); this.raceXML = RaceXMLCreator.alterRaceToWind(this.raceXML, XMLFileType.Contents, -1, true);
} else { } else {
this.raceXML = RaceXMLCreator.alterRaceToWind(this.raceXML, XMLFileType.Contents, 300, false); this.raceXML = RaceXMLCreator.alterRaceToWind(this.raceXML, XMLFileType.Contents, windAngle, false);
} }
this.boatXML = XMLReader.readXMLFileToString(boatsXMLFile, StandardCharsets.UTF_8); this.boatXML = XMLReader.readXMLFileToString(boatsXMLFile, StandardCharsets.UTF_8);
@ -146,7 +151,7 @@ public class Event {
this.latestMessages = new LatestMessages(); this.latestMessages = new LatestMessages();
WindGenerator windGenerator = new ShiftingWindGenerator( WindGenerator windGenerator = new ShiftingWindGenerator(
Bearing.fromDegrees(225), Bearing.fromDegrees(windAngle),
12 12
); );
@ -225,4 +230,14 @@ public class Event {
return raceXML; return raceXML;
} }
/**
* Creates the needed data type for a network packet
* @return hostGame Ac35DataType
* @throws IOException Inet4Address issue
*/
public HostGame getHostedGameData() throws IOException{
String ip = Inet4Address.getLocalHost().getHostAddress();
return new HostGame(ip, 3779,(byte) 1,(byte) 1, RaceStatusEnum.PRESTART, (byte) 1, (byte) 1);
}
} }

@ -60,7 +60,6 @@ public class MockOutput implements RunnableWithFramePeriod {
try { try {
Thread.sleep(500); Thread.sleep(500);
} catch (InterruptedException e) { } catch (InterruptedException e) {
//If we get interrupted, exit the function. //If we get interrupted, exit the function.
Logger.getGlobal().log(Level.WARNING, "MockOutput.run().sleep(waitForXMLs) was interrupted on thread: " + Thread.currentThread(), e); Logger.getGlobal().log(Level.WARNING, "MockOutput.run().sleep(waitForXMLs) was interrupted on thread: " + Thread.currentThread(), e);
@ -72,7 +71,6 @@ public class MockOutput implements RunnableWithFramePeriod {
} }
} }
long previousFrameTime = System.currentTimeMillis(); long previousFrameTime = System.currentTimeMillis();

@ -129,6 +129,7 @@ public class MockRace extends RaceState {
this.boats.add(mockBoat); this.boats.add(mockBoat);
getRaceDataSource().incrementSequenceNumber(); getRaceDataSource().incrementSequenceNumber();
} }
/** /**

@ -80,7 +80,6 @@ public class RaceServer {
updateXMLFiles(); updateXMLFiles();
} }
/** /**
* Checks if the race/boat/regatta data sources have changed, and if they have, update their xml representations. * Checks if the race/boat/regatta data sources have changed, and if they have, update their xml representations.
*/ */

@ -0,0 +1,64 @@
package network.MessageDecoders;
import network.Exceptions.InvalidMessageException;
import network.Messages.AC35Data;
import network.Messages.CourseWinds;
import network.Messages.Enums.RaceStatusEnum;
import network.Messages.HostGame;
import network.Messages.RaceStatus;
import java.util.Arrays;
import static network.Utils.ByteConverter.bytesToInt;
import static network.Utils.ByteConverter.bytesToLong;
public class HostGameMessageDecoder implements MessageDecoder {
/**
* The encoded message.
*/
private byte[] encodedMessage;
/**
* The decoded message.
*/
private HostGame message;
/**
* Constructor
*/
public HostGameMessageDecoder() {
}
@Override
public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException {
this.encodedMessage = encodedMessage;
try{
byte ipPart1 = encodedMessage[0];
byte ipPart2 = encodedMessage[1];
byte ipPart3 = encodedMessage[2];
byte ipPart4 = encodedMessage[3];
String ipString = bytesToLong(ipPart1) + "." + bytesToLong(ipPart2) + "." + bytesToLong(ipPart3) + "." + bytesToLong(ipPart4);
// System.out.println(ipString);
int port = bytesToInt(Arrays.copyOfRange(encodedMessage, 4, 8));
byte map = encodedMessage[8];
byte speed = encodedMessage[9];
byte status = encodedMessage[10];
byte requiredNumPlayers = encodedMessage[11];
byte currentNumPlayers = encodedMessage[12];
message = new HostGame(ipString, port, map,
speed, RaceStatusEnum.fromByte(status),
requiredNumPlayers, currentNumPlayers);
return message;
} catch (Exception e) {
throw new InvalidMessageException("Could not decode Host game message.", e);
}
}
}

@ -0,0 +1,35 @@
package network.MessageDecoders;
import network.Exceptions.InvalidMessageException;
import network.Messages.AC35Data;
import network.Messages.HostGame;
import network.Messages.HostGamesRequest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static network.Utils.ByteConverter.bytesToInt;
public class HostedGamesRequestDecoder implements MessageDecoder{
@Override
public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException {
try{
int numberOfGames = bytesToInt(Arrays.copyOfRange(encodedMessage, 0, 4));
HostGameMessageDecoder lineDecoder = new HostGameMessageDecoder();
List<HostGame> knownGames = new ArrayList<>();
int byteIndex = 4;
for (int i = 0; i < numberOfGames; i++){
knownGames.add((HostGame) lineDecoder.decode(Arrays.copyOfRange(encodedMessage, byteIndex, byteIndex+13)));
byteIndex += 13;
}
return new HostGamesRequest(knownGames);
} catch (Exception e) {
e.printStackTrace();
throw new InvalidMessageException("Could not decode Host game message.", e);
}
}
}

@ -0,0 +1,52 @@
package network.MessageEncoders;
import network.Exceptions.InvalidMessageException;
import network.Messages.AC35Data;
import network.Messages.HostGame;
import java.nio.ByteBuffer;
import static network.Utils.ByteConverter.intToBytes;
public class HostGameMessageEncoder implements MessageEncoder{
/**
* Constructor
*/
public HostGameMessageEncoder() {
}
@Override
public byte[] encode(AC35Data message) throws InvalidMessageException {
try{
//Downcast
HostGame hostGame = (HostGame) message;
ByteBuffer hostGameMessage = ByteBuffer.allocate(13);
ByteBuffer ipBytes = ByteBuffer.allocate(4);
String ip = hostGame.getIp();
String[] ipValues = ip.split("\\.");
for(String value:ipValues){
ipBytes.put(intToBytes(Integer.parseInt(value), 1)[0]);
}
byte raceStatus = hostGame.getStatus().getValue();
hostGameMessage.put(ipBytes.array());
hostGameMessage.put(intToBytes(hostGame.getPort()));
hostGameMessage.put(hostGame.getMap());
hostGameMessage.put(hostGame.getSpeed());
hostGameMessage.put(raceStatus);
hostGameMessage.put(hostGame.getRequiredNumPlayers());
hostGameMessage.put(hostGame.getCurrentNumPlayers());
// System.out.println(hostGameMessage.array()[4]);
return hostGameMessage.array();
} catch (Exception e) {
throw new InvalidMessageException("Could not encode Host game message.", e);
}
}
}

@ -0,0 +1,43 @@
package network.MessageEncoders;
import network.Exceptions.InvalidMessageException;
import network.Messages.AC35Data;
import network.Messages.HostGame;
import network.Messages.HostGamesRequest;
import java.nio.ByteBuffer;
import static network.Utils.ByteConverter.intToBytes;
public class HostedGamesRequestEncoder implements MessageEncoder{
/**
* Constructor
*/
public HostedGamesRequestEncoder() {
}
@Override
public byte[] encode(AC35Data message) throws InvalidMessageException {
try{
//Downcast
HostGamesRequest hostGamesRequest = (HostGamesRequest) message;
int numGames = hostGamesRequest.getKnownGames().size();
ByteBuffer hostedGamesRequestMessage = ByteBuffer.allocate(4+13*numGames);
hostedGamesRequestMessage.put(intToBytes(numGames));
HostGameMessageEncoder lineEncoder = new HostGameMessageEncoder();
for (HostGame line: hostGamesRequest.getKnownGames()) {
hostedGamesRequestMessage.put(lineEncoder.encode(line));
}
return hostedGamesRequestMessage.array();
}catch(Exception e){
throw new InvalidMessageException("Could not encode Host game message.", e);
}
}
}

@ -39,6 +39,10 @@ public enum MessageType {
*/ */
ASSIGN_PLAYER_BOAT(121), ASSIGN_PLAYER_BOAT(121),
HOST_GAME(108),
HOSTED_GAMES_REQUEST(109),
NOTAMESSAGE(0); NOTAMESSAGE(0);

@ -0,0 +1,89 @@
package network.Messages;
import network.Messages.Enums.MessageType;
import network.Messages.Enums.RaceStatusEnum;
import network.Utils.ByteConverter;
import java.nio.ByteBuffer;
import static network.Utils.ByteConverter.intToBytes;
public class HostGame extends AC35Data {
private String ip;
private int port;
private byte map;
private byte speed;
private RaceStatusEnum status;
private byte requiredNumPlayers;
private byte currentNumPlayers;
public HostGame(String ip, int port, byte map, byte speed,
RaceStatusEnum status, byte requiredNumPlayers,
byte currentNumPlayers) {
super(MessageType.HOST_GAME);
this.ip = ip;
this.port = port;
this.map = map;
this.speed = speed;
this.status = status;
this.requiredNumPlayers = requiredNumPlayers;
this.currentNumPlayers = currentNumPlayers;
}
/**
* @return the ip of host
*/
public String getIp() {
return ip;
}
/**
* @return the port of host
*/
public int getPort() {
return port;
}
/**
* @return the map index
*/
public byte getMap() {
return map;
}
/**
* @return the speed value of game
*/
public byte getSpeed() {
return speed;
}
/**
* @return the status of race
*/
public RaceStatusEnum getStatus() {
return status;
}
/**
* @return required number of players
*/
public byte getRequiredNumPlayers() {
return requiredNumPlayers;
}
/**
* @return current number of players
*/
public byte getCurrentNumPlayers() {
return currentNumPlayers;
}
public void setIp(String ip) {
this.ip = ip;
}
}

@ -0,0 +1,23 @@
package network.Messages;
import network.Messages.Enums.MessageType;
import java.util.List;
public class HostGamesRequest extends AC35Data{
private List<HostGame> knownGames;
/**
* Constructor
* @param knownGames games known by sender
*/
public HostGamesRequest(List knownGames) {
super(MessageType.HOSTED_GAMES_REQUEST);
this.knownGames = knownGames;
}
public List<HostGame> getKnownGames() {
return knownGames;
}
}

@ -9,7 +9,11 @@ import javafx.scene.image.ImageView;
import mock.app.Event; import mock.app.Event;
import mock.exceptions.EventConstructionException; import mock.exceptions.EventConstructionException;
import visualiser.app.App; import visualiser.app.App;
import visualiser.app.MatchBrowserSingleton;
import visualiser.network.MatchBrowserInterface;
import java.io.IOException; import java.io.IOException;
import java.net.DatagramSocket;
import java.net.Socket; import java.net.Socket;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -24,11 +28,17 @@ public class HostGameController extends Controller {
private @FXML ImageView mapImage; private @FXML ImageView mapImage;
private ArrayList<Image> listOfMaps; private ArrayList<Image> listOfMaps;
private int currentMapIndex = 0; private int currentMapIndex = 0;
private DatagramSocket udpSocket;
private MatchBrowserInterface matchBrowserInterface;
public void initialize() { public void initialize() {
loadMaps(); loadMaps();
this.udpSocket = MatchBrowserSingleton.getInstance().getUdpSocket();
this.matchBrowserInterface = MatchBrowserSingleton.getInstance().getMatchBrowserInterface();
} }
/** /**
* Loads in the list of playable maps to be selected from. * Loads in the list of playable maps to be selected from.
*/ */
@ -55,6 +65,8 @@ public class HostGameController extends Controller {
App.game = new Event(false, currentMapIndex); App.game = new Event(false, currentMapIndex);
App.gameType = currentMapIndex; App.gameType = currentMapIndex;
connectSocket("localhost", 4942); connectSocket("localhost", 4942);
alertMatchBrowser();
} catch (EventConstructionException e) { } catch (EventConstructionException e) {
Logger.getGlobal().log(Level.SEVERE, "Could not create Event.", e); Logger.getGlobal().log(Level.SEVERE, "Could not create Event.", e);
throw new RuntimeException(e); throw new RuntimeException(e);
@ -63,19 +75,32 @@ public class HostGameController extends Controller {
} }
} }
/**
* Sends info to the match browser so clients can see it
*/
public void alertMatchBrowser(){
try{
matchBrowserInterface.startSendingHostData(App.game.getHostedGameData(), udpSocket);
}catch (IOException e){
System.err.println("failed to send out hosted game info");
}
}
/** /**
* Connect to a socket * Connect to a socket
* @param address address of the server * @param address address of the server
* @param port port that the server is run off * @param port port that the server is run off
* @throws IOException socket error
*/ */
public void connectSocket(String address, int port) throws IOException { public void connectSocket(String address, int port) throws IOException {
Socket socket = new Socket(address, port); Socket socket = new Socket(address, port);
RaceStartController rsc = (RaceStartController)loadScene("raceStart.fxml"); InGameLobbyController iglc = (InGameLobbyController)loadScene("gameLobby.fxml");
rsc.enterLobby(socket, true); iglc.enterGameLobby(socket, true);
} }
/** /**
* Menu button pressed. Prompt alert then return to menu * Menu button pressed. Prompt alert then return to menu
* @throws IOException socket error
*/ */
public void menuBtnPressed() throws Exception { public void menuBtnPressed() throws Exception {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION); Alert alert = new Alert(Alert.AlertType.CONFIRMATION);

@ -0,0 +1,363 @@
package visualiser.Controllers;
import com.interactivemesh.jfx.importer.stl.StlMeshImporter;
import javafx.animation.AnimationTimer;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Label;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.scene.shape.MeshView;
import mock.app.Event;
import network.Messages.Enums.RaceStatusEnum;
import network.Messages.Enums.RequestToJoinEnum;
import visualiser.app.App;
import visualiser.gameController.ControllerClient;
import visualiser.layout.SeaSurface;
import visualiser.layout.Subject3D;
import visualiser.layout.View3D;
import visualiser.model.VisualiserBoat;
import visualiser.model.VisualiserRaceEvent;
import visualiser.model.VisualiserRaceState;
import java.io.IOException;
import java.net.Socket;
import java.net.URL;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Controller for Hosting a game.
*/
public class InGameLobbyController extends Controller {
@FXML
private ImageView imageView;
@FXML
GridPane playerContainer;
@FXML
private Label playerLabel;
@FXML
private Label playerLabel2;
@FXML
private Label playerLabel3;
@FXML
private Label playerLabel4;
@FXML
private Label playerLabel5;
@FXML
private Label playerLabel6;
@FXML
private Label countdownLabel;
@FXML
private AnchorPane countdownTenPane;
@FXML
private Label countdownTenText;
private Event game;
private View3D playerBoat;
private VisualiserRaceEvent visualiserRaceEvent;
private boolean isHost;
private ControllerClient controllerClient;
private ArrayList<Label> allPlayerLabels;
private ObservableList<Subject3D> subjects = FXCollections.observableArrayList();
private StlMeshImporter importer;
private PopulatePlayers lobbyUpdateListener;
public void initialize() {
allPlayerLabels = new ArrayList(Arrays.asList(playerLabel, playerLabel2,
playerLabel3,
playerLabel4,
playerLabel5,
playerLabel6));
URL asset = HostGameController.class.getClassLoader().getResource("assets/V1.2 Complete Boat.stl");
importer = new StlMeshImporter();
importer.read(asset);
lobbyUpdateListener = new PopulatePlayers();
}
private void resetLobby(){
for (Label label: allPlayerLabels){
label.setText("No Player");
}
List<Node> nodeCopy = new ArrayList(playerContainer.getChildren());
for (Node node: nodeCopy){
if (node instanceof View3D){
playerContainer.getChildren().remove(node);
}
}
}
public class PopulatePlayers implements ListChangeListener {
@Override
public void onChanged(Change change) {
Platform.runLater(
() -> {
while (change.next()){
if (change.wasAdded() || change.wasRemoved() || change.wasUpdated()){
try{
resetLobby();
int count = 0;
int row = 0;
for (VisualiserBoat boat :visualiserRaceEvent.getVisualiserRaceState().getBoats()) {
View3D playerBoatToSet = new View3D();
playerBoatToSet.setItems(subjects);
playerContainer.add(playerBoatToSet, (count % 3) , row);
playerContainer.setMargin(playerBoatToSet, new Insets(10, 10, 10, 10));
SeaSurface sea = new SeaSurface(750, 200, 250, 0, 210);
subjects.add(sea.getSurface());
MeshView mesh = new MeshView(importer.getImport());
Subject3D subject = new Subject3D(mesh);
subjects.add(subject);
playerBoatToSet.setDistance(50);
playerBoatToSet.setYaw(45);
playerBoatToSet.setPitch(20);
AnimationTimer rotate = new AnimationTimer() {
@Override
public void handle(long now) {
subject.setHeading(subject.getHeading().getAngle() + 0.1);
}
};
rotate.start();
allPlayerLabels.get(count).setText("Player: " + boat.getSourceID());
allPlayerLabels.get(count).toFront();
count += 1;
if (count > 2){
row = 1;
}
}
}
catch(ConcurrentModificationException e){
}
}
}
}
);
}
}
/*
private void populatePlayers(ListChangeListener.Change change){
}*/
/**
* Starts the race.
*/
private void startRace() {
//Initialises the race clock.
initialiseRaceClock(this.visualiserRaceEvent.getVisualiserRaceState());
//Starts the race countdown timer.
countdownTimer();
}
/**
* Initialises the race clock/timer labels for the start time, current time, and remaining time.
* @param visualiserRace The race to get data from.
*/
private void initialiseRaceClock(VisualiserRaceState visualiserRace) {
//Remaining time.
initialiseRaceClockDuration(visualiserRace);
}
/**
* Initialises the race duration label.
* @param visualiserRace The race to get data from.
*/
private void initialiseRaceClockDuration(VisualiserRaceState visualiserRace) {
visualiserRace.getRaceClock().durationProperty().addListener((observable, oldValue, newValue) -> {
Platform.runLater(() -> {
countdownLabel.setText(newValue);
});
});
}
/**
* Countdown timer until race starts.
*/
private void countdownTimer() {
new AnimationTimer() {
@Override
public void handle(long arg0) {
//Get the current race status.
RaceStatusEnum raceStatus = visualiserRaceEvent.getVisualiserRaceState().getRaceStatusEnum();
//Try catch for getting interval times
try {
long interval = ChronoUnit.MILLIS.between(visualiserRaceEvent.getVisualiserRaceState().getRaceClock().getCurrentTime(), visualiserRaceEvent.getVisualiserRaceState().getRaceClock().getStartingTime());
if(interval<=10000){
countdownLabel.setVisible(false);
countdownTenPane.setVisible(true);
countdownTenText.setVisible(true);
}
countdownText(interval);
} catch (Exception e){
}
//If the race has reached the preparatory phase, or has started...
if (raceStatus == RaceStatusEnum.PREPARATORY || raceStatus == RaceStatusEnum.STARTED) {
//Stop this timer.
stop();
//Hide this, and display the race controller.
try {
visualiserRaceEvent.getVisualiserRaceState().getBoats().removeListener(lobbyUpdateListener);
RaceViewController rvc = (RaceViewController)
loadScene("raceView.fxml");
rvc.startRace(visualiserRaceEvent, controllerClient,
isHost);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}.start();
}
public void enterGameLobby(Socket socket, boolean isHost){
try {
this.visualiserRaceEvent = new VisualiserRaceEvent(socket, RequestToJoinEnum.PARTICIPANT);
this.isHost = isHost;
this.controllerClient = visualiserRaceEvent.getControllerClient();
this.visualiserRaceEvent.getVisualiserRaceState().getBoats().addListener(this.lobbyUpdateListener);
startRace();
} catch (IOException e) {
//TODO should probably let this propagate, so that we only enter this scene if everything works
Logger.getGlobal().log(Level.WARNING, "Could not connect to server.", e);
}
}
/**
* Menu button pressed. Prompt alert then return to menu
* @throws IOException socket erro
*/
public void menuBtnPressed() throws IOException {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle("Quitting race");
alert.setContentText("Do you wish to quit the race?");
alert.setHeaderText("You are about to quit the race");
Optional<ButtonType> result = alert.showAndWait();
if(result.get() == ButtonType.OK){
visualiserRaceEvent.terminate();
try{
if(isHost) {
App.game.endEvent();
}
loadScene("title.fxml");
}catch (IOException ignore){};
}
}
/**
* Start button pressed. Currently only prints out start
*/
public void startBtnPressed(){
//System.out.println("Should start the race. This button is only visible for the host");
}
public void joinSpecPressed(){
//System.out.println("Spectator list pressed. Joining spectators");
}
public void joinRacePressed(){
//System.out.println("Empty race user pane pressed. Joining racers");
}
/**
* Countdown interval checker that updates the countdown text
* @param interval Countdown interval to check
*/
private void countdownText(long interval){
//Do nothing if 5 seconds or less to go
if (interval <=5000){
countdownTenPane.setVisible(false);
//countdownTenText.setText("5");
return;
}
//6 seconds left. Display 1 second for countdown
if (interval <=6000){
countdownTenText.setText("1");
return;
}
//7 seconds left. Display 2 seconds for countdown
if (interval <=7000){
countdownTenText.setText("2");
return;
}
//8 seconds left. Display 3 seconds for countdown
if (interval <=8000){
countdownTenText.setText("3");
return;
}
//9 seconds left. Display 4 seconds for countdown
if (interval <=9000){
countdownTenText.setText("4");
return;
}
//10 seconds left. Display 5 seconds for countdown
if (interval <=10000){
countdownTenText.setText("5");
return;
}
}
}

@ -208,6 +208,7 @@ public class KeyBindingsController extends Controller {
loadNotification("One or more key bindings are missing. " + loadNotification("One or more key bindings are missing. " +
"Failed to save.", true); "Failed to save.", true);
} }
((Stage)btnCancel.getScene().getWindow()).close();
} }
/** /**

@ -7,9 +7,17 @@ import javafx.scene.control.Button;
import javafx.scene.control.TableColumn; import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView; import javafx.scene.control.TableView;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
import network.Messages.HostGame;
import visualiser.app.MatchBrowserSingleton;
import visualiser.model.RaceConnection; import visualiser.model.RaceConnection;
import visualiser.network.MatchBrowserLobbyInterface;
import java.io.IOException; import java.io.IOException;
import java.net.DatagramSocket;
import java.net.Socket; import java.net.Socket;
import java.net.SocketException;
import java.util.Observable;
import java.util.Observer;
/** /**
* Controller for the Lobby for entering games * Controller for the Lobby for entering games
@ -22,11 +30,19 @@ public class LobbyController extends Controller {
private @FXML Button joinGameBtn; private @FXML Button joinGameBtn;
private @FXML TextField addressFld; private @FXML TextField addressFld;
private @FXML TextField portFld; private @FXML TextField portFld;
private ObservableList<RaceConnection> connections; private ObservableList<RaceConnection> connections;
private ObservableList<RaceConnection> customConnections;
//the socket for match browser
private DatagramSocket udpSocket;
private MatchBrowserLobbyInterface matchBrowserLobbyInterface;
public void initialize() { public void initialize() {
// set up the connection table // set up the connection table
connections = FXCollections.observableArrayList(); connections = FXCollections.observableArrayList();
customConnections = FXCollections.observableArrayList();
//connections.add(new RaceConnection("localhost", 4942, "Local Game")); //connections.add(new RaceConnection("localhost", 4942, "Local Game"));
lobbyTable.setItems(connections); lobbyTable.setItems(connections);
@ -42,12 +58,16 @@ public class LobbyController extends Controller {
} }
}); });
joinGameBtn.setDisable(true); joinGameBtn.setDisable(true);
this.udpSocket = MatchBrowserSingleton.getInstance().getUdpSocket();
receiveMatchData();
} }
/** /**
* Refreshes the connections in the lobby * Refreshes the connections in the lobby
*/ */
public void refreshBtnPressed(){ public void refreshBtnPressed(){
addServerGames();
for(RaceConnection connection: connections) { for(RaceConnection connection: connections) {
connection.check(); connection.check();
} }
@ -62,15 +82,17 @@ public class LobbyController extends Controller {
/** /**
* Connect to a connection. * Connect to a connection.
* @throws IOException socket error
*/ */
public void connectSocket() throws IOException { public void connectSocket() throws IOException {
RaceConnection connection = lobbyTable.getSelectionModel().getSelectedItem(); RaceConnection connection = lobbyTable.getSelectionModel().getSelectedItem();
Socket socket = new Socket(connection.getHostname(), connection.getPort()); Socket socket = new Socket(connection.getHostname(), connection.getPort());
RaceStartController rsc = (RaceStartController)loadScene("raceStart.fxml"); InGameLobbyController iglc = (InGameLobbyController)loadScene("gameLobby.fxml");
rsc.enterLobby(socket, false); iglc.enterGameLobby(socket, false);
} }
public void menuBtnPressed() throws IOException { public void menuBtnPressed() throws IOException {
matchBrowserLobbyInterface.closeSocket();
loadScene("title.fxml"); loadScene("title.fxml");
} }
@ -82,7 +104,8 @@ public class LobbyController extends Controller {
String portString = portFld.getText(); String portString = portFld.getText();
try { try {
int port = Integer.parseInt(portString); int port = Integer.parseInt(portString);
connections.add(new RaceConnection(hostName, port, "Boat Game")); customConnections.add(new RaceConnection(hostName, port, "Boat Game"));
connections.addAll(customConnections);
addressFld.clear(); addressFld.clear();
portFld.clear(); portFld.clear();
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
@ -90,4 +113,30 @@ public class LobbyController extends Controller {
} }
} }
} public void receiveMatchData(){
matchBrowserLobbyInterface = new MatchBrowserLobbyInterface();
try {
matchBrowserLobbyInterface.startReceivingHostData(new DatagramSocket(4941));
Observer o = new Observer() {
@Override
public void update(Observable o, Object arg) {
refreshBtnPressed();
}
};
matchBrowserLobbyInterface.addObserver(o);
} catch (SocketException e) {
System.err.println("Socket 4941 in use");
}
}
/**
* Adds the games received from the server
*/
private void addServerGames() {
connections.clear();
connections.addAll(customConnections);
for (HostGame game : matchBrowserLobbyInterface.getGames()) {
connections.add(new RaceConnection(game.getIp(), 4942, "Boat Game"));
}
}
}

@ -540,7 +540,7 @@ public class RaceViewController extends Controller {
//Set next key to press as the zoom-out key //Set next key to press as the zoom-out key
searchMapForKey("Zoom Out"); searchMapForKey("Zoom Out");
//Update tutorial text //Update tutorial text
tutorialText.setText("Nice! You will also be able to zoom into boats and marks by clicking them. To zoom out press " + keyToPress + "."); tutorialText.setText("Nice! You will also be able to zoom into boats and marks by clicking them. \nTo zoom out press " + keyToPress + ".");
updateTutorialState(); updateTutorialState();
break; break;
case ZOOMOUT: case ZOOMOUT:

@ -38,6 +38,7 @@ public class TitleController extends Controller {
/** /**
* To be implemented at a later date- will open the next scene displaying * To be implemented at a later date- will open the next scene displaying
* games a player can join. Place holder method for now! * games a player can join. Place holder method for now!
* @throws IOException socket error
*/ */
public void joinAGame() throws IOException { public void joinAGame() throws IOException {
loadScene("lobby.fxml"); loadScene("lobby.fxml");

@ -0,0 +1,37 @@
package visualiser.app;
import visualiser.network.MatchBrowserInterface;
import java.io.IOException;
import java.net.DatagramSocket;
public class MatchBrowserSingleton {
private DatagramSocket udpSocket;
private MatchBrowserInterface matchBrowserInterface;
private static MatchBrowserSingleton instance = null;
public MatchBrowserSingleton() {
this.matchBrowserInterface = new MatchBrowserInterface();
try{
this.udpSocket = matchBrowserInterface.setupMatchBrowserConnection();
}catch (IOException e){
System.err.println("Error in setting up connection with match browser");
}
}
public static MatchBrowserSingleton getInstance() {
if (instance == null){
instance = new MatchBrowserSingleton();
}
return instance;
}
public DatagramSocket getUdpSocket() {
return udpSocket;
}
public MatchBrowserInterface getMatchBrowserInterface() {
return matchBrowserInterface;
}
}

@ -0,0 +1,124 @@
package visualiser.layout;
import javafx.scene.shape.TriangleMesh;
import java.util.ArrayList;
import java.util.List;
/**
* 3D plane
*/
public class Plane3D extends TriangleMesh{
/**
* Length is up down, and width is left right. Drawn on the x-y plane with z kept at 0.
* @param width width of the plane
* @param length length of the plane
* @param subdivisionsWidth number of divisions along the width of the plane
* @param subdivisionsLength number of division along the length of the plane
*/
public Plane3D(float width, float length, int subdivisionsWidth, int subdivisionsLength){
//add texture points and vertex points
float subWidth = width / (float) subdivisionsWidth;
float subLength = length / (float) subdivisionsLength;
ArrayList<Float> pointsList = new ArrayList<>();
ArrayList<Float> textureCoord = new ArrayList<>();
float startW = -width/2;
float startL = -length/2;
for (float l = 0; l <= length; l += subLength) {
for (float w = 0; w <= width; w += subWidth){
//add points
pointsList.add(w + startW);
pointsList.add(l + startL);
pointsList.add(0f);
//addTexture coords
textureCoord.add(1 - w/width);
textureCoord.add(1 - l/length);
}
}
this.getPoints().setAll(copyListToArray(pointsList));
this.getTexCoords().setAll(copyListToArray(textureCoord));
//connect points to make faces
ArrayList<Integer> faces = new ArrayList<>();
int listSize = pointsList.size()/3;
int divsInRow = subdivisionsWidth + 1;
for (int i = 0; i < listSize; i++){
int row = i/divsInRow;
if (row < 1){
continue;
}
boolean notFirstCol = (i) % divsInRow != 0;
boolean notLastCol = (i + 1) % divsInRow != 0;
if (notFirstCol){
faces.add(i);
faces.add(i);
// printPointAtIndex(i);
faces.add(i - divsInRow);
faces.add(i - divsInRow);
// printPointAtIndex(i - divsInRow);
faces.add(i - 1);
faces.add(i - 1);
// printPointAtIndex(i-1);
}
if (notLastCol) {
faces.add(i - divsInRow + 1);
faces.add(i - divsInRow + 1);
// printPointAtIndex(i - divsInRow + 1);
faces.add(i - divsInRow);
faces.add(i - divsInRow);
// printPointAtIndex(i - divsInRow);
faces.add(i);
faces.add(i);
// printPointAtIndex(i);
}
}
this.getFaces().setAll(copyListToIntArray(faces));
}
/**
* Testing function to see if the points are correct
* @param index index that the points correspond to (remember 3 is a point)
*/
private void printPointAtIndex(int index){
int i = index * 3;
float x = this.getPoints().get(i);
float y = this.getPoints().get(i + 1);
float z = this.getPoints().get(i + 2);
}
/**
* copies the list to a float array because java List.toArray isn't working
* @param list list to copy
* @return array
*/
private static float[] copyListToArray(List<Float> list){
float[] res = new float[list.size()];
for (int i = 0; i < list.size(); i++){
res[i] = list.get(i);
}
return res;
}
/**
* copies the list to an integer array because java List.toArray isn't working
* @param list list to copy
* @return array
*/
private static int[] copyListToIntArray(List<Integer> list){
int[] res = new int[list.size()];
for (int i = 0; i < list.size(); i++){
res[i] = list.get(i);
}
return res;
}
}

@ -0,0 +1,136 @@
package visualiser.layout;
import javafx.scene.image.Image;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.MeshView;
import visualiser.utils.PerlinNoiseGenerator;
/**
* Creates a SeaSurface
*/
public class SeaSurface {
private float[][] noiseArray;
private Subject3D surface;
/**
* Sea Surface Constructor
* @param size size of the sea surface (has to be square for simplicity's sake)
* @param freq frequency the perlin noise is to be generated at
* @param x offset that the sea should be set at position-wise
* @param y offset that the sea should be set at position-wise
* @param z offset that the sea should be set at position-wise
*/
public SeaSurface(int size, double freq, double x, double y, double z){
noiseArray = PerlinNoiseGenerator.createNoise(size, freq);
createSurface();
surface.setZ(z);
surface.setY(y);
surface.setX(x);
}
/**
* Creates the sea surface
*/
private void createSurface(){
Image diffuseMap = createImage(noiseArray.length, noiseArray);
PhongMaterial material = new PhongMaterial();
material.setDiffuseMap(diffuseMap);
//material.setSpecularColor(Color.WHITE);
Plane3D seaPlane = new Plane3D(noiseArray.length, noiseArray.length, 10, 10);
MeshView seaSurface = new MeshView(seaPlane);
// Box seaSurface = new Box(noiseArray.length, 0.1, noiseArray.length);
seaSurface.setMaterial(material);
seaSurface.setMouseTransparent(true);
seaSurface.toFront();
//seaSurface.setRotationAxis(new Point3D(1, 0, 0));
//seaSurface.setRotate(90);
surface = new Subject3D(seaSurface);
}
/**
* Create texture for uv mapping
* @param size size of the image to make
* @param noise array of noise
* @return image that is created
*/
private Image createImage(double size, float[][] noise) {
int width = (int) size;
int height = (int) size;
WritableImage wr = new WritableImage(width, height);
PixelWriter pw = wr.getPixelWriter();
//interpolate colours based on noise
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
float value = noise[x][y];
double gray = normalizeValue(value, -.5, .5, 0., 1.);
gray = clamp(gray, 0, 1);
//values to interpolate on
Color brightBlue = new Color(0.06, 0.5, .78, 1);
Color lightBlue = new Color(0.15, 0.68, .88, 1);
Color lighterBlue = new Color(0.28, 0.73, .91, 1);
Color colour = Color.WHITE.interpolate(brightBlue, gray).interpolate(lighterBlue, gray).interpolate(lightBlue, gray);
pw.setColor(x, y, colour);
}
}
return wr;
}
/**
* Nomalises the values so that the colours are correct
* @param value value to normalise
* @param min current min
* @param max current max
* @param newMin new min
* @param newMax new max
* @return returns normalised value
*/
private static double normalizeValue(double value, double min, double max, double newMin, double newMax) {
return (value - min) * (newMax - newMin) / (max - min) + newMin;
}
/**
* clamps a value between a min and max
* @param value value to clamp
* @param min minimum value it can be
* @param max maximum value it can be
* @return result after clamp
*/
private static double clamp(double value, double min, double max) {
if (Double.compare(value, min) < 0)
return min;
if (Double.compare(value, max) > 0)
return max;
return value;
}
/**
* Get surface
* @return the surface so it can be drawn
*/
public Subject3D getSurface(){
return surface;
}
}

@ -257,4 +257,4 @@ public class View3D extends Pane {
public void setPitch(double pitch) { public void setPitch(double pitch) {
this.pitch.setAngle(-pitch); this.pitch.setAngle(-pitch);
} }
} }

@ -112,8 +112,8 @@ public class VisualiserRaceEvent {
* Terminates the server connection and race service. * Terminates the server connection and race service.
*/ */
public void terminate() { public void terminate() {
this.serverConnectionThread.interrupt();
this.visualiserRaceServiceThread.interrupt(); this.visualiserRaceServiceThread.interrupt();
this.serverConnectionThread.interrupt();
serverConnection.terminate();
} }
} }

@ -0,0 +1,34 @@
package visualiser.network;
import network.Messages.AC35Data;
import shared.model.RunnableWithFramePeriod;
import java.io.IOException;
import java.net.DatagramSocket;
public class MatchBrowserClientRunnable implements RunnableWithFramePeriod {
private MatchBrowserLobbyInterface matchBrowserLobbyInterface;
private DatagramSocket socket;
public MatchBrowserClientRunnable(MatchBrowserLobbyInterface matchBrowserInterface, DatagramSocket socket) {
this.matchBrowserLobbyInterface = matchBrowserInterface;
this.socket = socket;
}
@Override
public void run(){
long previousFrameTime = System.currentTimeMillis();
while (!Thread.interrupted()) {
try{
matchBrowserLobbyInterface.receiveGameInfo(socket);
}catch (IOException e){
System.err.println("HostGameMessage could not be received");
}
long currentFrameTime = System.currentTimeMillis();
waitForFramePeriod(previousFrameTime, currentFrameTime, 10000);
previousFrameTime = currentFrameTime;
}
}
}

@ -0,0 +1,40 @@
package visualiser.network;
import network.Exceptions.InvalidMessageException;
import network.Messages.AC35Data;
import network.Messages.HostGame;
import shared.model.RunnableWithFramePeriod;
import java.io.IOException;
import java.net.DatagramSocket;
public class MatchBrowserHostRunnable implements RunnableWithFramePeriod {
private MatchBrowserInterface matchBrowserInterface;
private DatagramSocket socket;
private AC35Data gameInfo;
public MatchBrowserHostRunnable(MatchBrowserInterface matchBrowserInterface, DatagramSocket socket, AC35Data gameInfo) {
this.matchBrowserInterface = matchBrowserInterface;
this.socket = socket;
this.gameInfo = gameInfo;
}
@Override
public void run(){
long previousFrameTime = System.currentTimeMillis();
while (!Thread.interrupted()) {
try{
matchBrowserInterface.sendOutGameInfo(gameInfo, socket);
}catch (IOException e){
System.err.println("HostGameMessage could not be sent");
}
long currentFrameTime = System.currentTimeMillis();
waitForFramePeriod(previousFrameTime, currentFrameTime, 10000);
previousFrameTime = currentFrameTime;
}
}
}

@ -0,0 +1,94 @@
package visualiser.network;
import network.BinaryMessageEncoder;
import network.Exceptions.InvalidMessageException;
import network.MessageEncoders.HostGameMessageEncoder;
import network.MessageEncoders.HostedGamesRequestEncoder;
import network.Messages.AC35Data;
import network.Messages.Enums.MessageType;
import network.Messages.HostGamesRequest;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
/**
* UDP interface for the matchBrowser to send out hosted game info and get in other hosts info
*/
public class MatchBrowserInterface {
//ip of the server
private InetAddress IPAddress;
//port server is hosted on
private int port;
public MatchBrowserInterface() {
//TODO change to ip of cloud server hosting this
try {
this.IPAddress = InetAddress.getLocalHost();//InetAddress.getByName("132.181.13.223");
} catch (UnknownHostException e) {
e.printStackTrace();
}
this.port = 3779;
}
/**
* Used by host to send out race information to the server
* @param gameInfo the hostGame info for message
* @param socket the udp socket assigned on startup
* @throws IOException socket error
*/
protected void sendOutGameInfo(AC35Data gameInfo, DatagramSocket socket) throws IOException{
byte[] fullMessageToSend;
try{
HostGameMessageEncoder encoder = new HostGameMessageEncoder();
byte[] message = encoder.encode(gameInfo);
BinaryMessageEncoder messageEncoder = new BinaryMessageEncoder(MessageType.HOST_GAME
,System.currentTimeMillis(), 1,(short) 13 ,message);
fullMessageToSend = messageEncoder.getFullMessage();
DatagramPacket sendPacket = new DatagramPacket(fullMessageToSend, fullMessageToSend.length, IPAddress, port);
socket.send(sendPacket);
}catch (InvalidMessageException e){
System.err.println("HostGameMessage could not be encoded");
}
}
/**
* start to send these messages on repeat until game stopped
* @param gameInfo hostgame data
* @param socket socket to send to
*/
public void startSendingHostData(AC35Data gameInfo, DatagramSocket socket){
MatchBrowserHostRunnable hostRunnable = new MatchBrowserHostRunnable(this, socket, gameInfo);
Thread hostRunnableThread = new Thread(hostRunnable, "Socket: " + socket.toString());
hostRunnableThread.start();
}
/**
* Used by a client to setup a connection with the match browser server
* @return the socket created for this connection
* @throws IOException socket error
*/
public DatagramSocket setupMatchBrowserConnection() throws IOException{
DatagramSocket clientSocket = new DatagramSocket();
//creates and empty hostedGamesRequest packet that can be sent to the server
//this lets the server check the udp fields for ip and port to know that this client exists
try{
HostedGamesRequestEncoder encoder = new HostedGamesRequestEncoder();
byte[] message = encoder.encode(new HostGamesRequest(new ArrayList()));
BinaryMessageEncoder messageEncoder = new BinaryMessageEncoder(MessageType.HOSTED_GAMES_REQUEST
,System.currentTimeMillis(), 1,(short) 13 ,message);
DatagramPacket sendPacket = new DatagramPacket(messageEncoder.getFullMessage(), messageEncoder.getFullMessage().length, IPAddress, port);
clientSocket.send(sendPacket);
}catch (InvalidMessageException e){
System.err.println("HostedGamesRequestMessage could not be encoded");
}
return clientSocket;
}
}

@ -0,0 +1,76 @@
package visualiser.network;
import network.Exceptions.InvalidMessageException;
import network.MessageDecoders.HostedGamesRequestDecoder;
import network.Messages.HostGame;
import network.Messages.HostGamesRequest;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
/**
* Used to receive lobby information from server
*/
public class MatchBrowserLobbyInterface extends Observable {
private DatagramSocket socket;
private MatchBrowserClientRunnable clientRunnable;
private List<HostGame> games = new ArrayList<>();
private Thread clientRunnableThread;
public MatchBrowserLobbyInterface() {
}
/**
* start receiving game info
* @param socket to receive from
*/
public void startReceivingHostData(DatagramSocket socket) {
this.socket = socket;
clientRunnable = new MatchBrowserClientRunnable(this, socket);
clientRunnableThread = new Thread(clientRunnable, "Socket: " + socket.toString());
clientRunnableThread.start();
}
/**
* Used by client to received race information from the server
* @param socket socket to read from
* @throws IOException socket error
*/
protected void receiveGameInfo(DatagramSocket socket) throws IOException {
byte[] data = new byte[64];
DatagramPacket receivedPacket = new DatagramPacket(data, 64);
socket.receive(receivedPacket);
HostedGamesRequestDecoder hostedGamesRequestDecoder = new HostedGamesRequestDecoder();
try {
HostGamesRequest message = (HostGamesRequest) hostedGamesRequestDecoder.decode(data);
games = message.getKnownGames();
// System.out.println(games.get(0).getIp());
setChanged();
notifyObservers();
} catch (InvalidMessageException e) {
System.err.println("HostedGamesRequestMessage could not be decoded");
}
}
/**
* Gets the host games
* @return games to be returned in list
*/
public List<HostGame> getGames() {
return games;
}
/**
* Used to close the socket and runnable once out of the lobby
*/
public void closeSocket() {
clientRunnableThread.interrupt();
this.socket.close();
}
}

@ -466,7 +466,15 @@ public class ServerConnection implements RunnableWithFramePeriod {
this.visualiserRaceControllerThread.interrupt(); this.visualiserRaceControllerThread.interrupt();
} }
if (this.socket != null) {
try {
this.socket.getInputStream().close();
this.socket.getOutputStream().close();
this.socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//TODO input controller? //TODO input controller?

@ -0,0 +1,88 @@
package visualiser.utils;
/**
* Perlin Noise Generator
*/
public class PerlinNoiseGenerator {
/**
* Create an array of the given size with values of perlin noise
* @param size size of array that you wish to create
* @param freq frequency that the noise is to be generated at.
* @return noise generated
*/
public static float[][] createNoise( int size, double freq) {
float[][] noiseArray = new float[(int) size][(int) size];
for (int x = 0; x < size; x++) {
for (int y = 0; y < size; y++) {
double frequency = freq / (double) size;
double noise = ImprovedNoise.noise(x * frequency, y * frequency, 0);
noiseArray[x][y] = (float) noise;
}
}
return noiseArray;
}
/**
* Perlin noise generator
*
* // JAVA REFERENCE IMPLEMENTATION OF IMPROVED NOISE - COPYRIGHT 2002 KEN PERLIN.
* // http://mrl.nyu.edu/~perlin/paper445.pdf
* // http://mrl.nyu.edu/~perlin/noise/
*/
public final static class ImprovedNoise {
static public double noise(double x, double y, double z) {
int X = (int)Math.floor(x) & 255, // FIND UNIT CUBE THAT
Y = (int)Math.floor(y) & 255, // CONTAINS POINT.
Z = (int)Math.floor(z) & 255;
x -= Math.floor(x); // FIND RELATIVE X,Y,Z
y -= Math.floor(y); // OF POINT IN CUBE.
z -= Math.floor(z);
double u = fade(x), // COMPUTE FADE CURVES
v = fade(y), // FOR EACH OF X,Y,Z.
w = fade(z);
int A = p[X ]+Y, AA = p[A]+Z, AB = p[A+1]+Z, // HASH COORDINATES OF
B = p[X+1]+Y, BA = p[B]+Z, BB = p[B+1]+Z; // THE 8 CUBE CORNERS,
return lerp(w, lerp(v, lerp(u, grad(p[AA ], x , y , z ), // AND ADD
grad(p[BA ], x-1, y , z )), // BLENDED
lerp(u, grad(p[AB ], x , y-1, z ), // RESULTS
grad(p[BB ], x-1, y-1, z ))),// FROM 8
lerp(v, lerp(u, grad(p[AA+1], x , y , z-1 ), // CORNERS
grad(p[BA+1], x-1, y , z-1 )), // OF CUBE
lerp(u, grad(p[AB+1], x , y-1, z-1 ),
grad(p[BB+1], x-1, y-1, z-1 ))));
}
static double fade(double t) { return t * t * t * (t * (t * 6 - 15) + 10); }
static double lerp(double t, double a, double b) { return a + t * (b - a); }
static double grad(int hash, double x, double y, double z) {
int h = hash & 15; // CONVERT LO 4 BITS OF HASH CODE
double u = h<8 ? x : y, // INTO 12 GRADIENT DIRECTIONS.
v = h<4 ? y : h==12||h==14 ? x : z;
return ((h&1) == 0 ? u : -u) + ((h&2) == 0 ? v : -v);
}
static final int p[] = new int[512], permutation[] = { 151,160,137,91,90,15,
131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180
};
static { for (int i=0; i < 256 ; i++) p[256+i] = p[i] = permutation[i]; }
}
}

@ -0,0 +1,112 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.image.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.SplitPane?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.text.Font?>
<AnchorPane fx:id="gameLobbyWrapper" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="780.0" style="-fx-background-color: rgba(100, 100, 100, 0.2);" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.InGameLobbyController">
<children>
<GridPane style="-fx-background-color: rgba(0, 0, 0, 0.3);" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Button mnemonicParsing="false" onAction="#menuBtnPressed" text="Quit" GridPane.rowIndex="2">
<GridPane.margin>
<Insets left="20.0" />
</GridPane.margin>
</Button>
<Label fx:id="countdownLabel" alignment="CENTER" contentDisplay="CENTER" textFill="WHITE" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.rowIndex="2">
<font>
<Font size="16.0" />
</font>
</Label>
<GridPane fx:id="playerContainer" GridPane.columnSpan="3" GridPane.rowIndex="1">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<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 minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label fx:id="playerLabel" text="No Player" GridPane.halignment="CENTER" GridPane.valignment="BOTTOM">
<GridPane.margin>
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />
</GridPane.margin>
</Label>
<Label fx:id="playerLabel4" text="No Player" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="BOTTOM">
<GridPane.margin>
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />
</GridPane.margin>
</Label>
<Label fx:id="playerLabel2" text="No Player" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.valignment="BOTTOM">
<GridPane.margin>
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />
</GridPane.margin>
</Label>
<Label fx:id="playerLabel3" text="No Player" GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.valignment="BOTTOM">
<GridPane.margin>
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />
</GridPane.margin>
</Label>
<Label fx:id="playerLabel5" text="No Player" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="BOTTOM">
<GridPane.margin>
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />
</GridPane.margin>
</Label>
<Label fx:id="playerLabel6" text="No Player" GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="BOTTOM">
<GridPane.margin>
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />
</GridPane.margin>
</Label>
</children>
</GridPane>
<Label text="Get Ready For The Next Race" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.valignment="CENTER">
<font>
<Font name="Cabin-Bold" size="17.0" />
</font>
</Label>
</children>
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="80.0" minHeight="80.0" prefHeight="80.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="1.7976931348623157E308" minHeight="250.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="80.0" minHeight="80.0" prefHeight="80.0" vgrow="SOMETIMES" />
</rowConstraints>
</GridPane>
<AnchorPane fx:id="countdownTenPane" prefHeight="200.0" prefWidth="200.0" visible="false" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Label fx:id="countdownTenText" alignment="CENTER" text="COUNTDOWN" textFill="#ee0000" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<font>
<Font name="System Bold" size="41.0" />
</font>
</Label>
<Label fx:id="preRacelabel" alignment="CENTER" text="Entering Race In:" textFill="RED" AnchorPane.bottomAnchor="80.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<font>
<Font name="System Bold" size="18.0" />
</font>
</Label>
</children>
</AnchorPane>
</children>
</AnchorPane>

@ -0,0 +1,36 @@
package network.MessageDecoders;
import network.MessageEncoders.HostGameMessageEncoder;
import network.Messages.AC35Data;
import network.Messages.Enums.RaceStatusEnum;
import network.Messages.HostGame;
import org.junit.Assert;
import org.junit.Test;
public class HostGameMessageDecoderTest {
@Test
public void hostGameMessageDecoderTest() throws Exception {
HostGame testHost = new HostGame("127.0.0.1", 3779, (byte) 1, (byte) 2, RaceStatusEnum.PRESTART, (byte) 6, (byte) 2);
HostGameMessageEncoder encoder = new HostGameMessageEncoder();
byte[] encodedMessage = encoder.encode(testHost);
HostGameMessageDecoder decoder = new HostGameMessageDecoder();
HostGame decodedTest = (HostGame) decoder.decode(encodedMessage);
compareHostGameMessage(testHost, decodedTest);
}
public static void compareHostGameMessage(HostGame original, HostGame decoded) {
Assert.assertEquals(original.getIp(), decoded.getIp());
Assert.assertEquals(original.getPort(), decoded.getPort());
Assert.assertEquals(original.getSpeed(), decoded.getSpeed());
Assert.assertEquals(original.getStatus(), decoded.getStatus());
Assert.assertEquals(original.getRequiredNumPlayers(), decoded.getRequiredNumPlayers());
Assert.assertEquals(original.getCurrentNumPlayers(), decoded.getCurrentNumPlayers());
}
}

@ -0,0 +1,38 @@
package network.MessageDecoders;
import network.MessageEncoders.HostGameMessageEncoder;
import network.MessageEncoders.HostedGamesRequestEncoder;
import network.Messages.Enums.RaceStatusEnum;
import network.Messages.HostGame;
import network.Messages.HostGamesRequest;
import org.junit.Assert;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
public class HostedGamesRequestDecoderTest {
@Test
public void hostGamesRequestMessageDecoderTest() throws Exception {
HostGame testHostGame1 = new HostGame("127.0.0.1", 3779, (byte) 1, (byte) 2, RaceStatusEnum.PRESTART, (byte) 6, (byte) 2);
HostGame testHostGame2 = new HostGame("127.0.0.1", 3780, (byte) 1, (byte) 2, RaceStatusEnum.PRESTART, (byte) 6, (byte) 2);
List knownGames = Arrays.asList(testHostGame1, testHostGame2);
HostedGamesRequestEncoder encoder = new HostedGamesRequestEncoder();
byte[] encodedMessage = encoder.encode(new HostGamesRequest(knownGames));
HostedGamesRequestDecoder decoder = new HostedGamesRequestDecoder();
HostGamesRequest decodedTest = (HostGamesRequest) decoder.decode(encodedMessage);
compareHostGamesRequestMessage(knownGames, decodedTest.getKnownGames());
}
public static void compareHostGamesRequestMessage(List<HostGame> original, List<HostGame> decoded) {
Assert.assertEquals(original.get(0).getIp(), decoded.get(0).getIp());
Assert.assertEquals(original.get(1).getPort(), decoded.get(1).getPort());
}
}
Loading…
Cancel
Save