Getting boat locations from race simulator & bug fixes

- Boat locations that are generated by the simulator are sent to the client as they happen
- Fixed heading and lat/lon encoding
- Fixed a bug where the header wasn't included in the sent byte stream
- Fixed the format of data as it's sent to the client.
- Data is now sent using a channel
- Removed tests that don't work with channels

Tags: #story[829]
This commit is contained in:
Michael Rausch
2017-04-29 19:38:21 +12:00
parent 8a04a0e5b7
commit 3e97f016d5
16 changed files with 330 additions and 354 deletions
+168 -94
View File
@@ -1,29 +1,44 @@
package seng302.server;
import seng302.server.messages.*;
import seng302.server.simulator.Boat;
import seng302.server.simulator.Simulator;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.*;
public class ServerThread implements Runnable{
public class ServerThread implements Runnable, Observer {
private Thread runner;
private StreamingServerSocket server;
private long startTime;
boolean raceStarted = false;
private List<Boat> boats;
private Simulator raceSimulator;
private final int HEARTBEAT_PERIOD = 5000;
private final int RACE_STATUS_PERIOD = 1000;
private final int RACE_STATUS_PERIOD = 1000/2;
private final int RACE_START_STATUS_PERIOD = 1000/2;
private final int BOAT_LOCATION_PERIOD = 1000/5;
private final int PORT_NUMBER = 8085;
private final int TIME_TILL_RACE_START = 10;
private static final int LOG_LEVEL = 1;
public ServerThread(String threadName){
runner = new Thread(this, threadName);
System.out.println("Spawning Server Thread");
serverLog("Spawning Server", 0);
raceSimulator = new Simulator(BOAT_LOCATION_PERIOD);
boats = raceSimulator.getBoats();
runner.start();
}
public static void serverLog(String message, int logLevel){
if(logLevel <= LOG_LEVEL){
System.out.println("[SERVER] " + message);
}
}
/**
* Creates and returns an XML Message from the file specified
* @param fileName The source XML file
@@ -38,6 +53,9 @@ public class ServerThread implements Runnable{
} catch (IOException e) {
e.printStackTrace();
}
catch (NullPointerException e){
return null;
}
if (fileContents != null){
return new XMLMessage(fileContents, type, server.getSequenceNumber());
@@ -47,56 +65,113 @@ public class ServerThread implements Runnable{
}
/**
* @return A sample race status message
* @return Get a race status message for the current race
*/
public Message getTestRaceStatusMessage(){
BoatSubMessage boat1 = new BoatSubMessage(1, BoatStatus.PRESTART, 0, 0, 0,
10000, 10000);
public Message getRaceStatusMessage(){
List<BoatSubMessage> boatSubMessages = new ArrayList<BoatSubMessage>();
BoatStatus boatStatus;
RaceStatus raceStatus;
BoatSubMessage boat2 = new BoatSubMessage(2, BoatStatus.PRESTART, 0, 0, 0,
10000, 10000);
if (raceStarted){
boatStatus = BoatStatus.RACING;
raceStatus = RaceStatus.STARTED;
}
else{
boatStatus = BoatStatus.PRESTART;
raceStatus = RaceStatus.PRESTART;
}
BoatSubMessage boat3 = new BoatSubMessage(3, BoatStatus.PRESTART, 0, 0, 0,
10000, 10000);
for (Boat b : boats){
BoatSubMessage m = new BoatSubMessage(b.getSourceID(), boatStatus, b.getLastPassedCorner().getSeqID(), 0, 0, 0, 0);
boatSubMessages.add(m);
}
List<BoatSubMessage> boats = new ArrayList<BoatSubMessage>();
boats.add(boat1);
boats.add(boat2);
boats.add(boat3);
return new RaceStatusMessage(1, RaceStatus.PRESTART, 1000, WindDirection.EAST,
100, 3, RaceType.MATCH_RACE, 1, boats);
return new RaceStatusMessage(1, raceStatus, startTime, WindDirection.EAST,
100, boats.size(), RaceType.MATCH_RACE, 1, boatSubMessages);
}
/**
* @return A list of sample boat location messages
* Starts an instance of the race simulator
*/
public List<Message> getTestBoatLocationMessages(){
List<Message> messages = new ArrayList<>();
messages.add(new BoatLocationMessage(1, 1, 100, 200, 231, 23));
messages.add(new BoatLocationMessage(2, 2, 400, 300, 211, 13));
return messages;
private void startRaceSim(){
serverLog("Starting Race Simulator", 0);
raceSimulator.addObserver(this);
new Thread(raceSimulator).start();
}
/**
* Starts sending heartbeat messages to the client
*/
private void startSendingHeartbeats(){
serverLog("Sending Heartbeats", 0);
Timer t = new Timer();
public void run() {
t.schedule(new TimerTask() {
@Override
public void run() {
Message heartbeat = new Heartbeat(server.getSequenceNumber());
try {
server.send(heartbeat);
} catch (IOException e) {
System.out.print("");
}
}
}, 0, HEARTBEAT_PERIOD);
}
/**
* Start sending race start status messages until race starts
*/
private void startSendingRaceStartStatusMessages(){
Timer t = new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
Message raceStartStatusMessage = new RaceStartStatusMessage(server.getSequenceNumber(), startTime , 1,
RaceStartNotificationType.SET_RACE_START_TIME);
try {
if (startTime < System.currentTimeMillis()/1000 && !raceStarted){
startRaceSim();
raceStarted = true;
serverLog("Race Started", 0);
}
else{
server.send(raceStartStatusMessage);
}
} catch (IOException e) {
System.out.print("");
}
}
}, 0, RACE_START_STATUS_PERIOD);
}
private void startSendingRaceStatusMessages(){
serverLog("Sending Race Status Messages", 0);
Timer t = new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
Message statusMessage = getRaceStatusMessage();
try {
server.send(statusMessage);
} catch (IOException e) {
System.out.print("");
}
}
}, 100, RACE_STATUS_PERIOD);
}
/**
* Sends the race, boat, and regatta XML files to the client
*/
private void sendXml(){
try{
server = new StreamingServerSocket(PORT_NUMBER);
}
catch (IOException e){
System.err.println("Failed to bind socket: " + e.getMessage());
}
server.start();
try {
// Load and send race XML data
Message raceData = getXmlMessage("/server_config/race.xml", XMLMessageSubType.RACE);
Message boatData = getXmlMessage("/server_config/boats.xml", XMLMessageSubType.BOAT);
Message regatta = getXmlMessage("/server_config/regatta.xml", XMLMessageSubType.REGATTA);
Message raceData = getXmlMessage("server_config/race.xml", XMLMessageSubType.RACE);
Message boatData = getXmlMessage("server_config/boats.xml", XMLMessageSubType.BOAT);
Message regatta = getXmlMessage("server_config/regatta.xml", XMLMessageSubType.REGATTA);
if (raceData != null){
server.send(raceData);
@@ -106,57 +181,56 @@ public class ServerThread implements Runnable{
server.send(boatData);
}
if (regatta != null){
server.send(regatta);
if (regatta != null){
server.send(regatta);
}
// Timer to send the heartbeat
Timer t = new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
Message hb = new Heartbeat(server.getSequenceNumber());
try {
server.send(hb);
} catch (IOException e) {
System.out.print("");
}
}
}, 0, HEARTBEAT_PERIOD);
// Timer to send the race status messages
Timer t1 = new Timer();
t1.schedule(new TimerTask() {
@Override
public void run() {
Message statusMessage = getTestRaceStatusMessage();
try {
server.send(statusMessage);
} catch (IOException e) {
System.out.print("");
}
}
}, 100, RACE_STATUS_PERIOD);
t1.schedule(new TimerTask() {
@Override
public void run() {
List<Message> messages = getTestBoatLocationMessages();
for (Message m : messages){
try {
server.send(m);
} catch (IOException e) {
System.out.print("");
}
}
}
}, 100, BOAT_LOCATION_PERIOD);
} catch (IOException e) {
System.err.println(e.getMessage());
serverLog("Couldn't send an XML Message: " + e.getMessage(), 0);
}
}
public void run() {
try{
server = new StreamingServerSocket(PORT_NUMBER);
}
catch (IOException e){
serverLog("Failed to bind socket: " + e.getMessage(), 0);
}
startTime = (System.currentTimeMillis()/1000) + TIME_TILL_RACE_START;
// Wait for client to connect
server.start();
startSendingHeartbeats();
sendXml();
startSendingRaceStartStatusMessages();
startSendingRaceStatusMessages();
}
/**
* Send a boat location message when they are updated by the simulator
* @param o .
* @param arg .
*/
@Override
public void update(Observable o, Object arg) {
// Only send if server started
if(!server.isStarted()){
return;
}
for (Boat b : ((Simulator) o).getBoats()){
try {
Message m = new BoatLocationMessage(b.getSourceID(), 1, b.getLat(),
b.getLng(), b.getHeadingCorner().getBearingToNextCorner(),
((long) b.getSpeed()));
server.send(m);
} catch (IOException e) {
serverLog("Couldn't send a boat status message", 1);
}
}
}
}
@@ -4,35 +4,44 @@ import seng302.server.messages.Message;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.channels.Channels;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
import java.util.ArrayList;
import java.util.List;
class StreamingServerSocket {
private java.net.ServerSocket socket;
private Socket client;
private ServerSocketChannel socket;
private SocketChannel client;
private List<Socket> clients;
private short seqNum;
private boolean isServerStarted;
StreamingServerSocket(int port) throws IOException{
socket = new java.net.ServerSocket(port);
socket = ServerSocketChannel.open();
socket.socket().bind(new InetSocketAddress("localhost", port));
clients = new ArrayList<>();
socket.setSoTimeout(10000);
//socket.setSoTimeout(10000);
seqNum = 0;
isServerStarted = false;
}
void start(){
System.out.println("Listening For Connections");
ServerThread.serverLog("Listening For Connections",0);
try {
client = socket.accept();
} catch (IOException e) {
e.getMessage();
}
if (client == null){
if (client.socket() == null){
start();
}
else{
System.out.println("client connected from " + client.getInetAddress());
isServerStarted = true;
ServerThread.serverLog("client connected from " + client.socket().getInetAddress(),0);
}
}
@@ -41,8 +50,9 @@ class StreamingServerSocket {
return;
}
DataOutputStream outputStream = new DataOutputStream(client.getOutputStream());
message.send(outputStream);
//DataOutputStream outputStream = new DataOutputStream(client.getOutputStream());
//System.out.println(client);
message.send(client);
seqNum++;
}
@@ -50,4 +60,8 @@ class StreamingServerSocket {
public short getSequenceNumber(){
return seqNum;
}
public boolean isStarted(){
return isServerStarted;
}
}
@@ -2,6 +2,9 @@ package seng302.server.messages;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
public class BoatLocationMessage extends Message {
private final int MESSAGE_SIZE = 56;
@@ -11,10 +14,10 @@ public class BoatLocationMessage extends Message {
private long sourceId;
private long sequenceNum;
private DeviceType deviceType;
private long latitude;
private long longitude;
private double latitude;
private double longitude;
private long altitude;
private long heading;
private Double heading;
private long pitch;
private long roll;
private long boatSpeed;
@@ -38,13 +41,13 @@ public class BoatLocationMessage extends Message {
* @param heading The boats heading
* @param boatSpeed The boats speed
*/
public BoatLocationMessage(int sourceId, int sequenceNum, long latitude, long longitude, long heading, long boatSpeed){
public BoatLocationMessage(int sourceId, int sequenceNum, double latitude, double longitude, double heading, long boatSpeed){
messageVersionNumber = 1;
time = System.currentTimeMillis() / 1000L;
this.sourceId = sourceId;
this.sequenceNum = sequenceNum;
this.deviceType = DeviceType.RACING_YACHT;
this.latitude = -latitude;
this.latitude = latitude;
this.longitude = longitude;
this.altitude = 0;
this.heading = heading;
@@ -126,19 +129,23 @@ public class BoatLocationMessage extends Message {
@Override
public void send(DataOutputStream outputStream) {
public void send(SocketChannel outputStream) throws IOException{
allocateBuffer();
writeHeaderToBuffer();
heading = (heading + 180.0) % 360.0;
long headingToSend = (long)((heading/360.0)*65535.0);
putByte((byte) messageVersionNumber);
putInt((int) time, 6);
putInt(time, 6);
putInt((int) sourceId, 4);
putUnsignedInt((int) sequenceNum, 4);
putByte((byte) deviceType.getCode());
putInt((int) latitude, 4);
putInt((int) longitude, 4);
putInt((int) latLonToBinaryPackedLong(latitude), 4);
putInt((int) latLonToBinaryPackedLong(longitude), 4);
putInt((int) altitude, 4);
putUnsignedInt((int) heading, 2);
putInt(headingToSend, 2);
putInt((int) pitch, 2);
putInt((int) roll, 2);
putUnsignedInt((int) boatSpeed, 2);
@@ -153,12 +160,9 @@ public class BoatLocationMessage extends Message {
putUnsignedInt((int) currentSet, 2);
putInt((int) rudderAngle, 2);
writeCRC();
try {
outputStream.write(getBuffer().array());
} catch (IOException e) {
System.out.print("");
}
rewind();
outputStream.write(getBuffer());
}
}
@@ -1,6 +1,9 @@
package seng302.server.messages;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Collections;
public class Header {
// From API spec
@@ -12,6 +15,8 @@ public class Header {
private int sourceId;
private short messageLength;
private static final int MESSAGE_LEN = 15;
private ByteBuffer buff;
private int buffPos;
/**
* Message Header from section 3.2 of the AC35 Streaming
@@ -25,38 +30,34 @@ public class Header {
this.sourceId = sourceId;
this.messageLength = messageLength;
timeStamp = (int) (System.currentTimeMillis() / 1000L);
buff = ByteBuffer.allocate(MESSAGE_LEN);
buffPos = 0;
}
private void putInBuffer(byte[] bytes, long val){
byte[] tmp = bytes.clone();
Message.reverse(tmp);
buff.put(tmp);
buffPos += tmp.length;
buff.position(buffPos);
}
/**
* @return a ByteBuffer containing the message header
*/
public ByteBuffer getByteBuffer(){
ByteBuffer buff = ByteBuffer.allocate(15);
putInBuffer(ByteBuffer.allocate(1).put((byte)syncByte1).array(), syncByte1);
// Sync Byte 1, 1 byte
buff.put(ByteBuffer.allocate(1).put((byte)syncByte1).array());
buff.position(1);
putInBuffer(ByteBuffer.allocate(1).put((byte)syncByte2).array(), syncByte2);
// Sync Byte 2, 1 byte
buff.put(ByteBuffer.allocate(1).put((byte)syncByte2).array());
buff.position(2);
putInBuffer(ByteBuffer.allocate(1).put((byte)messageType.getCode()).array(), messageType.getCode());
// Message Type, 1 byte
buff.put(ByteBuffer.allocate(1).put((byte)messageType.getCode()).array());
buff.position(3);
putInBuffer(Message.intToByteArray(timeStamp, 6), timeStamp);
// Timestamp, 6 bytes
int x = ((int) Integer.toUnsignedLong(6));
buff.put(ByteBuffer.allocate(6).putInt(timeStamp).array());
buff.position(9);
putInBuffer(Message.intToByteArray(sourceId, 4), sourceId);
// Source ID, 4 bytes
buff.put(ByteBuffer.allocate(4).putInt(sourceId).array());
buff.position(13);
// Message Length, 2 bytes
buff.put(ByteBuffer.allocate(2).putShort(messageLength).array());
buff.position(15);
putInBuffer(Message.intToByteArray(messageLength, 2), messageLength);
return buff;
}
@@ -3,6 +3,9 @@ package seng302.server.messages;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
import java.util.zip.CRC32;
public class Heartbeat extends Message {
@@ -23,7 +26,7 @@ public class Heartbeat extends Message {
}
@Override
public void send(DataOutputStream outputStream) {
public void send(SocketChannel outputStream) throws IOException {
setHeader(new Header(MessageType.HEARTBEAT, 0x01, (short) getSize()));
allocateBuffer();
@@ -33,10 +36,6 @@ public class Heartbeat extends Message {
writeCRC();
try {
outputStream.write(getBuffer().array());
} catch (IOException e) {
e.printStackTrace();
}
outputStream.write(getBuffer());
}
}
@@ -2,6 +2,9 @@ package seng302.server.messages;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
public class MarkRoundingMessage extends Message{
private final long MESSAGE_VERSION_NUMBER = 1;
@@ -38,7 +41,7 @@ public class MarkRoundingMessage extends Message{
}
@Override
public void send(DataOutputStream outputStream) {
public void send(SocketChannel outputStream) throws IOException {
allocateBuffer();
writeHeaderToBuffer();
@@ -53,10 +56,6 @@ public class MarkRoundingMessage extends Message{
writeCRC();
try {
outputStream.write(getBuffer().array());
} catch (IOException e) {
e.printStackTrace();
}
outputStream.write(getBuffer());
}
}
@@ -1,7 +1,13 @@
package seng302.server.messages;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.reflect.Array;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.zip.CRC32;
public abstract class Message {
@@ -33,13 +39,14 @@ public abstract class Message {
/**
* Send the message as through the outputStream
*/
public abstract void send(DataOutputStream outputStream);
public abstract void send(SocketChannel outputStream) throws IOException;
/**
* Allocate byte buffer to correct size
*/
void allocateBuffer(){
buffer = ByteBuffer.allocate(Header.getSize() + getSize() + CRC_SIZE);
buffer.order(ByteOrder.LITTLE_ENDIAN);
bufferPosition = 0;
}
@@ -47,7 +54,7 @@ public abstract class Message {
* Write the set header to the byte buffer
*/
void writeHeaderToBuffer(){
buffer.put(getHeader().getByteBuffer());
buffer.put(getHeader().getByteBuffer().array());
bufferPosition += Header.getSize();
buffer.position(bufferPosition);
}
@@ -89,12 +96,15 @@ public abstract class Message {
}
else if (size < 4){
// Use short
buffer.put(ByteBuffer.allocate(size).putShort((short) (val & 0xffff)).array());
byte[] tmp = Message.intToByteArray(val, size); //ByteBuffer.allocate(size).putShort((short) (val & 0xffff)).array();
reverse(tmp);
buffer.put(tmp);
moveBufferPositionBy(size);
}
else{
// Use int
buffer.put(ByteBuffer.allocate(size).putInt((int) (val & 0xffffffffL)).array());
byte[] tmp = Message.intToByteArray(val, size);
reverse(tmp);
moveBufferPositionBy(size);
}
}
@@ -104,12 +114,16 @@ public abstract class Message {
* @param val The integer value to add
* @param size The size of the integer to be added to the buffer
*/
void putInt(int val, int size){
void putInt(long val, int size){
if (size < 4){
buffer.put(ByteBuffer.allocate(size).putShort((short) val).array());
byte[] tmp = Message.intToByteArray(val, size);
reverse(tmp);
buffer.put(tmp);
}
else{
buffer.put(ByteBuffer.allocate(size).putInt((short) val).array());
byte[] tmp = Message.intToByteArray(val, size);
reverse(tmp);
buffer.put(tmp);
}
moveBufferPositionBy(size);
}
@@ -141,7 +155,9 @@ public abstract class Message {
crc = new CRC32();
buffer.position(0);
crc.update(buffer.array());
byte[] data = Arrays.copyOfRange(buffer.array(), 0, buffer.array().length-CRC_SIZE);
crc.update(data);
buffer.position(bufferPosition);
putInt((int) crc.getValue(), CRC_SIZE);
@@ -154,4 +170,42 @@ public abstract class Message {
return buffer;
}
/**
* Rewind the buffer to the beginning
*/
void rewind(){
buffer.flip();
}
/**
* Convert an integer to an array of bytes
* @param val The value to add
* @param len The width of the integer in the buffer
* @return
*/
public static byte[] intToByteArray(long val, int len){
int index = 0;
byte[] data = new byte[len];
for (int i = 0; i < len; i++){
data[len - index - 1] = (byte) ((val >>> (8 * index)));
index++;
}
return data;
}
/**
* Reverse an array of bytes
* @param data The byte[] to reverse
*/
public static void reverse(byte[] data) {
for (int left = 0, right = data.length - 1; left < right; left++, right--) {
byte temp = data[left];
data[left] = data[right];
data[right] = temp;
}
}
}
@@ -9,7 +9,7 @@ public enum MessageType {
RACE_STATUS(12),
DISPLAY_TEXT_MESSAGE(20),
XML_MESSAGE(26),
RACE_START_STATUS(27),
RACE_START_STATUS(20),
YACHT_EVENT_CODE(29),
YACHT_ACTION_CODE(31),
CHATTER_TEXT(36),
@@ -2,6 +2,9 @@ package seng302.server.messages;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
public class RaceStartStatusMessage extends Message {
private final int MESSAGE_SIZE = 20;
@@ -37,7 +40,7 @@ public class RaceStartStatusMessage extends Message {
}
@Override
public void send(DataOutputStream outputStream) {
public void send(SocketChannel outputStream) throws IOException {
allocateBuffer();
writeHeaderToBuffer();
@@ -50,10 +53,6 @@ public class RaceStartStatusMessage extends Message {
writeCRC();
try {
outputStream.write(getBuffer().array());
} catch (IOException e) {
e.printStackTrace();
}
outputStream.write(getBuffer());
}
}
@@ -3,6 +3,9 @@ package seng302.server.messages;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
import java.util.List;
import java.util.zip.CRC32;
@@ -63,7 +66,7 @@ public class RaceStatusMessage extends Message{
* @param outputStream The output stream to send the message
*/
@Override
public void send(DataOutputStream outputStream) {
public void send(SocketChannel outputStream) throws IOException {
allocateBuffer();
writeHeaderToBuffer();
@@ -83,11 +86,6 @@ public class RaceStatusMessage extends Message{
writeCRC();
// Send
try {
outputStream.write(getBuffer().array());
} catch (IOException e) {
e.printStackTrace();
}
outputStream.write(getBuffer());
}
}
@@ -3,6 +3,9 @@ package seng302.server.messages;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
import java.util.zip.CRC32;
public class XMLMessage extends Message{
@@ -45,7 +48,7 @@ public class XMLMessage extends Message{
* Send this message as a stream of bytes
* @param outputStream The output stream to send the message
*/
public void send(DataOutputStream outputStream) {
public void send(SocketChannel outputStream) throws IOException {
allocateBuffer();
writeHeaderToBuffer();
@@ -60,11 +63,6 @@ public class XMLMessage extends Message{
writeCRC();
// Send
try {
outputStream.write(getBuffer().array());
} catch (IOException e) {
e.printStackTrace();
}
outputStream.write(getBuffer());
}
}
@@ -47,7 +47,7 @@ public class Simulator extends Observable implements Runnable {
for (Boat boat : boats) {
numOfFinishedBoats += moveBoat(boat, lapse);
}
System.out.println(boats.get(0));
//System.out.println(boats.get(0));
setChanged();
notifyObservers(boats);
@@ -121,4 +121,8 @@ public class Simulator extends Observable implements Runnable {
}
}
public List<Boat> getBoats(){
return boats;
}
}