diff --git a/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java b/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java index 97a974a9..3584fbab 100644 --- a/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java +++ b/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java @@ -1,20 +1,17 @@ package mock.app; +import mock.model.RaceLogic; import network.Messages.Enums.XMLMessageType; import network.Messages.LatestMessages; import network.Messages.XMLMessage; -import org.mockito.Mock; import visualiser.gameController.ControllerServer; import java.io.DataOutputStream; import java.io.IOException; -import java.lang.reflect.Array; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.List; import java.util.concurrent.ArrayBlockingQueue; /** @@ -43,6 +40,10 @@ public class ConnectionAcceptor implements Runnable { private short boatXMLSequenceNumber; //regatta xml sequence number private short regattaXMLSequenceNumber; + //controller server + private ControllerServer controllerServer; + // + private RaceLogic rl = null; /** * Connection Acceptor Constructor @@ -65,6 +66,11 @@ public class ConnectionAcceptor implements Runnable { return serverPort; } + + public void setRace(RaceLogic rl){ + this.rl = rl; + } + /** * Run the Acceptor */ @@ -76,7 +82,7 @@ public class ConnectionAcceptor implements Runnable { Socket mockSocket = serverSocket.accept(); DataOutputStream outToVisualiser = new DataOutputStream(mockSocket.getOutputStream()); MockOutput mockOutput = new MockOutput(latestMessages, outToVisualiser); - ControllerServer controllerServer = new ControllerServer(mockSocket); + this.controllerServer = new ControllerServer(mockSocket, rl); new Thread(mockOutput).start(); new Thread(controllerServer).start(); mockOutputList.add(mockOutput); diff --git a/racevisionGame/src/main/java/mock/app/Event.java b/racevisionGame/src/main/java/mock/app/Event.java index b4b0586c..617ad584 100644 --- a/racevisionGame/src/main/java/mock/app/Event.java +++ b/racevisionGame/src/main/java/mock/app/Event.java @@ -94,6 +94,8 @@ public class Event { //Create and start race. RaceLogic newRace = new RaceLogic(new MockRace(boatDataSource, raceDataSource, regattaDataSource, this.latestMessages, this.boatPolars, Constants.RaceTimeScale), this.latestMessages); + mockOutput.setRace(newRace); + new Thread(newRace).start(); } diff --git a/racevisionGame/src/main/java/mock/model/MockBoat.java b/racevisionGame/src/main/java/mock/model/MockBoat.java index 104fa264..a2f74dd7 100644 --- a/racevisionGame/src/main/java/mock/model/MockBoat.java +++ b/racevisionGame/src/main/java/mock/model/MockBoat.java @@ -25,7 +25,7 @@ public class MockBoat extends Boat { /** * Stores whether the boat is on autoVMG or not */ - private boolean autoVMG = true; + private boolean autoVMG = false; diff --git a/racevisionGame/src/main/java/mock/model/RaceLogic.java b/racevisionGame/src/main/java/mock/model/RaceLogic.java index 9e810761..adc0fe37 100644 --- a/racevisionGame/src/main/java/mock/model/RaceLogic.java +++ b/racevisionGame/src/main/java/mock/model/RaceLogic.java @@ -1,12 +1,20 @@ package mock.model; import javafx.animation.AnimationTimer; +import mock.model.commandFactory.Command; +import mock.model.commandFactory.CommandFactory; +import mock.model.commandFactory.CompositeCommand; +import network.Messages.Enums.BoatActionEnum; import network.Messages.Enums.BoatStatusEnum; import network.Messages.Enums.RaceStatusEnum; import network.Messages.LatestMessages; -import shared.model.Race; +import visualiser.gameController.ControllerServer; -public class RaceLogic implements Runnable { +import java.util.Observable; +import java.util.Observer; +import java.util.Stack; + +public class RaceLogic implements Observer, Runnable { /** * State of current race modified by this object */ @@ -16,6 +24,8 @@ public class RaceLogic implements Runnable { */ private RaceServer server; + private CompositeCommand commands; + /** * Initialises race loop with state and server message queue * @param race state of race to modify @@ -24,6 +34,7 @@ public class RaceLogic implements Runnable { public RaceLogic(MockRace race, LatestMessages messages) { this.race = race; this.server = new RaceServer(race, messages); + this.commands = new CompositeCommand(); } /** @@ -123,7 +134,7 @@ public class RaceLogic implements Runnable { //If it is still racing, update its position. if (boat.getStatus() == BoatStatusEnum.RACING) { - + commands.execute(); race.updatePosition(boat, framePeriod, race.getRaceClock().getDurationMilli()); } @@ -173,4 +184,13 @@ public class RaceLogic implements Runnable { iters++; } }; + + @Override + public void update(Observable o, Object arg) { + ControllerServer server = (ControllerServer)o; + BoatActionEnum action = server.getAction(); + MockBoat boat = race.getBoats().get(0); + + commands.addCommand(CommandFactory.createCommand(race, boat, action)); + } } diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java b/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java index 6e87f11b..fba06cb5 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java @@ -19,6 +19,8 @@ public class CommandFactory { switch(action) { case AUTO_PILOT: return new VMGCommand(race, boat); case TACK_GYBE: return new TackGybeCommand(race, boat); + case UPWIND: return new WindCommand(race, boat, true); + case DOWNWIND: return new WindCommand(race, boat, false); default: return null; // TODO - please please have discussion over what to default to } } diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/CompositeCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/CompositeCommand.java new file mode 100644 index 00000000..74c5e95b --- /dev/null +++ b/racevisionGame/src/main/java/mock/model/commandFactory/CompositeCommand.java @@ -0,0 +1,24 @@ +package mock.model.commandFactory; + +import java.util.ArrayDeque; +import java.util.Queue; + +/** + * Wraps multiple commands into a composite to execute queued commands during a frame. + */ +public class CompositeCommand implements Command { + private Queue commands; + + public CompositeCommand() { + this.commands = new ArrayDeque<>(); + } + + public void addCommand(Command command) { + commands.add(command); + } + + @Override + public void execute() { + while(!commands.isEmpty()) commands.remove().execute(); + } +} diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java index 150a1da8..8dcf1c48 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java @@ -18,6 +18,9 @@ public class TackGybeCommand implements Command { //The refactoring of MockRace will require changes to be made @Override public void execute() { + + boat.setAutoVMG(false); + /*VMG newVMG = boat.getPolars().calculateVMG( race.getWindDirection(), race.getWindSpeed(), diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java index 64cc6a9f..1a1eeda5 100644 --- a/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java +++ b/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java @@ -18,6 +18,7 @@ public class VMGCommand implements Command { //The refactoring of MockRace will require changes to be made @Override public void execute() { + boat.setAutoVMG(true); /*VMG newVMG = boat.getPolars().calculateVMG( race.getWindDirection(), race.getWindSpeed(), diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java new file mode 100644 index 00000000..530bf5bc --- /dev/null +++ b/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java @@ -0,0 +1,37 @@ +package mock.model.commandFactory; + +import mock.model.MockBoat; +import mock.model.MockRace; +import shared.model.Bearing; + +/** + * Created by connortaylorbrown on 4/08/17. + */ +public class WindCommand implements Command { + private MockRace race; + private MockBoat boat; + private int direction; + + public WindCommand(MockRace race, MockBoat boat, boolean upwind) { + this.race = race; + this.boat = boat; + this.direction = upwind? -1 : 1; + } + + @Override + public void execute() { + + boat.setAutoVMG(false); + + double wind = race.getWindDirection().degrees(); + double heading = boat.getBearing().degrees(); + + double offset = 3.0; + + offset *= direction; + double headWindDelta = wind - heading; + if ((headWindDelta < 0) || (headWindDelta > 180)) offset *= -1; + + boat.setBearing(Bearing.fromDegrees(heading + offset)); + } +} diff --git a/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java b/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java index fb6a257b..66e98d89 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java +++ b/racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java @@ -1,19 +1,19 @@ package visualiser.gameController; +import mock.model.RaceLogic; import network.BinaryMessageDecoder; import network.MessageDecoders.BoatActionDecoder; import network.Messages.Enums.BoatActionEnum; -import visualiser.gameController.Keys.ControlKey; -import visualiser.gameController.Keys.KeyFactory; import java.io.DataInputStream; import java.io.IOException; import java.net.Socket; +import java.util.Observable; /** * Service for dispatching key press data to race from client */ -public class ControllerServer implements Runnable { +public class ControllerServer extends Observable implements Runnable { /** * Socket to client */ @@ -22,13 +22,23 @@ public class ControllerServer implements Runnable { * Wrapper for input from client */ private DataInputStream inputStream; + /** + * Last received boat action + */ + private BoatActionEnum action; + /** + * + */ + private RaceLogic rc; /** * Initialise server-side controller with live client socket * @param socket to client */ - public ControllerServer(Socket socket) { + public ControllerServer(Socket socket, RaceLogic rc) { this.socket = socket; + this.rc = rc; + this.addObserver(rc); try { this.inputStream = new DataInputStream(this.socket.getInputStream()); } catch (IOException e) { @@ -36,6 +46,10 @@ public class ControllerServer implements Runnable { } } + public BoatActionEnum getAction() { + return action; + } + /** * Wait for controller key input from client and loop. */ @@ -48,8 +62,11 @@ public class ControllerServer implements Runnable { inputStream.read(message); BinaryMessageDecoder encodedMessage = new BinaryMessageDecoder(message); BoatActionDecoder boatActionDecoder = new BoatActionDecoder(encodedMessage.getMessageBody()); - BoatActionEnum decodedMessage = boatActionDecoder.getBoatAction(); - System.out.println("Received key: " + decodedMessage); + action = boatActionDecoder.getBoatAction(); + + // Notify observers of most recent action + this.notifyObservers(); + this.setChanged(); } } catch (IOException e) { e.printStackTrace(); diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/KeyFactory.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/KeyFactory.java index ef1368f0..be95abd3 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/Keys/KeyFactory.java +++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/KeyFactory.java @@ -27,8 +27,8 @@ public class KeyFactory { keyState.put("SPACE", new VMGKey("VMG")); keyState.put("SHIFT", new SailsToggleKey("Toggle Sails")); keyState.put("ENTER", new TackGybeKey("Tack/Gybe")); - keyState.put("PAGE_UP", new UpWindKey("Upwind")); - keyState.put("PAGE_DOWN", new DownWindKey("Downwind")); + keyState.put("UP", new UpWindKey("Upwind")); + keyState.put("DOWN", new DownWindKey("Downwind")); } /** diff --git a/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java b/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java new file mode 100644 index 00000000..e5d147d9 --- /dev/null +++ b/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java @@ -0,0 +1,58 @@ +package mock.model.commandFactory; + +import mock.model.MockBoat; +import mock.model.MockRace; +import network.Messages.Enums.BoatActionEnum; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import shared.model.Bearing; +import shared.model.Boat; +import shared.model.Race; +import visualiser.model.VisualiserRace; + +import static org.mockito.Mockito.when; +import static org.testng.Assert.*; +import static org.mockito.Mockito.mock; + +/** + * Created by connortaylorbrown on 4/08/17. + */ +public class WindCommandTest { + private MockRace race; + private MockBoat boat; + private Command upwind; + private Command downwind; + private double initial; + + private double offset = 3.0; + + @Before + public void setUp() { + race = mock(MockRace.class); + boat = new MockBoat(0, "Bob", "NZ", null); + + when(race.getWindDirection()).thenReturn(Bearing.fromDegrees(0.0)); + boat.setBearing(Bearing.fromDegrees(45.0)); + + upwind = CommandFactory.createCommand(race, boat, BoatActionEnum.UPWIND); + downwind = CommandFactory.createCommand(race, boat, BoatActionEnum.DOWNWIND); + + initial = boat.getBearing().degrees(); + } + + /** + * Ensure the difference between initial and final angle is 3 degrees + */ + @Test + public void upwindCommandDecreasesAngle() { + upwind.execute(); + assertEquals(initial - boat.getBearing().degrees(), -offset, 1e-5); + } + + @Test + public void downwindCommandIncreasesAngle() { + downwind.execute(); + assertEquals(initial - boat.getBearing().degrees(), offset, 1e-5); + } +} \ No newline at end of file