Merge branch 'story63' into 'master'

63. [C, M] I'd like to be able to make small adjustments to the heading of my boat, as Gemma, either upwind or downwind.

Note: each keypress should change the boat's heading by a small amount (3 degrees?) Pressing the 'upwind' key will turn the boat's head towards the wind by that small amount, while the 'downwind' key will do the opposite. Continuing to press the downwind key when the boat is already heading straight downwind, or upwind when heading directly upwind, achieves nothing.

Acceptance criteria:

- The player boat turns towards wind a small amount for each press of the 'upwind' key.
- The player boat turns away from the wind by a small amount for each press of the 'downwind' key.
- If the boat heading is directly into wind after a series of 'upwind' keypresses, the heading will continue to change in the same direction for one more keypress.
- Substitute 'downwind' for 'upwind' for the downwind case.


See merge request !21
main
Fraser Cope 8 years ago
commit 7b382e48ac

@ -1,20 +1,17 @@
package mock.app; package mock.app;
import mock.model.RaceLogic;
import network.Messages.Enums.XMLMessageType; import network.Messages.Enums.XMLMessageType;
import network.Messages.LatestMessages; import network.Messages.LatestMessages;
import network.Messages.XMLMessage; import network.Messages.XMLMessage;
import org.mockito.Mock;
import visualiser.gameController.ControllerServer; import visualiser.gameController.ControllerServer;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Array;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ArrayBlockingQueue;
/** /**
@ -43,6 +40,10 @@ public class ConnectionAcceptor implements Runnable {
private short boatXMLSequenceNumber; private short boatXMLSequenceNumber;
//regatta xml sequence number //regatta xml sequence number
private short regattaXMLSequenceNumber; private short regattaXMLSequenceNumber;
//controller server
private ControllerServer controllerServer;
//
private RaceLogic rl = null;
/** /**
* Connection Acceptor Constructor * Connection Acceptor Constructor
@ -65,6 +66,11 @@ public class ConnectionAcceptor implements Runnable {
return serverPort; return serverPort;
} }
public void setRace(RaceLogic rl){
this.rl = rl;
}
/** /**
* Run the Acceptor * Run the Acceptor
*/ */
@ -76,7 +82,7 @@ public class ConnectionAcceptor implements Runnable {
Socket mockSocket = serverSocket.accept(); Socket mockSocket = serverSocket.accept();
DataOutputStream outToVisualiser = new DataOutputStream(mockSocket.getOutputStream()); DataOutputStream outToVisualiser = new DataOutputStream(mockSocket.getOutputStream());
MockOutput mockOutput = new MockOutput(latestMessages, outToVisualiser); MockOutput mockOutput = new MockOutput(latestMessages, outToVisualiser);
ControllerServer controllerServer = new ControllerServer(mockSocket); this.controllerServer = new ControllerServer(mockSocket, rl);
new Thread(mockOutput).start(); new Thread(mockOutput).start();
new Thread(controllerServer).start(); new Thread(controllerServer).start();
mockOutputList.add(mockOutput); mockOutputList.add(mockOutput);

@ -94,6 +94,8 @@ public class Event {
//Create and start race. //Create and start race.
RaceLogic newRace = new RaceLogic(new MockRace(boatDataSource, raceDataSource, regattaDataSource, this.latestMessages, this.boatPolars, Constants.RaceTimeScale), this.latestMessages); RaceLogic newRace = new RaceLogic(new MockRace(boatDataSource, raceDataSource, regattaDataSource, this.latestMessages, this.boatPolars, Constants.RaceTimeScale), this.latestMessages);
mockOutput.setRace(newRace);
new Thread(newRace).start(); new Thread(newRace).start();
} }

@ -25,7 +25,7 @@ public class MockBoat extends Boat {
/** /**
* Stores whether the boat is on autoVMG or not * Stores whether the boat is on autoVMG or not
*/ */
private boolean autoVMG = true; private boolean autoVMG = false;

@ -1,12 +1,20 @@
package mock.model; package mock.model;
import javafx.animation.AnimationTimer; 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.BoatStatusEnum;
import network.Messages.Enums.RaceStatusEnum; import network.Messages.Enums.RaceStatusEnum;
import network.Messages.LatestMessages; 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 * State of current race modified by this object
*/ */
@ -16,6 +24,8 @@ public class RaceLogic implements Runnable {
*/ */
private RaceServer server; private RaceServer server;
private CompositeCommand commands;
/** /**
* Initialises race loop with state and server message queue * Initialises race loop with state and server message queue
* @param race state of race to modify * @param race state of race to modify
@ -24,6 +34,7 @@ public class RaceLogic implements Runnable {
public RaceLogic(MockRace race, LatestMessages messages) { public RaceLogic(MockRace race, LatestMessages messages) {
this.race = race; this.race = race;
this.server = new RaceServer(race, messages); 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 it is still racing, update its position.
if (boat.getStatus() == BoatStatusEnum.RACING) { if (boat.getStatus() == BoatStatusEnum.RACING) {
commands.execute();
race.updatePosition(boat, framePeriod, race.getRaceClock().getDurationMilli()); race.updatePosition(boat, framePeriod, race.getRaceClock().getDurationMilli());
} }
@ -173,4 +184,13 @@ public class RaceLogic implements Runnable {
iters++; 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));
}
} }

@ -19,6 +19,8 @@ public class CommandFactory {
switch(action) { switch(action) {
case AUTO_PILOT: return new VMGCommand(race, boat); case AUTO_PILOT: return new VMGCommand(race, boat);
case TACK_GYBE: return new TackGybeCommand(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 default: return null; // TODO - please please have discussion over what to default to
} }
} }

@ -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<Command> 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();
}
}

@ -18,6 +18,9 @@ public class TackGybeCommand implements Command {
//The refactoring of MockRace will require changes to be made //The refactoring of MockRace will require changes to be made
@Override @Override
public void execute() { public void execute() {
boat.setAutoVMG(false);
/*VMG newVMG = boat.getPolars().calculateVMG( /*VMG newVMG = boat.getPolars().calculateVMG(
race.getWindDirection(), race.getWindDirection(),
race.getWindSpeed(), race.getWindSpeed(),

@ -18,6 +18,7 @@ public class VMGCommand implements Command {
//The refactoring of MockRace will require changes to be made //The refactoring of MockRace will require changes to be made
@Override @Override
public void execute() { public void execute() {
boat.setAutoVMG(true);
/*VMG newVMG = boat.getPolars().calculateVMG( /*VMG newVMG = boat.getPolars().calculateVMG(
race.getWindDirection(), race.getWindDirection(),
race.getWindSpeed(), race.getWindSpeed(),

@ -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));
}
}

@ -1,19 +1,19 @@
package visualiser.gameController; package visualiser.gameController;
import mock.model.RaceLogic;
import network.BinaryMessageDecoder; import network.BinaryMessageDecoder;
import network.MessageDecoders.BoatActionDecoder; import network.MessageDecoders.BoatActionDecoder;
import network.Messages.Enums.BoatActionEnum; import network.Messages.Enums.BoatActionEnum;
import visualiser.gameController.Keys.ControlKey;
import visualiser.gameController.Keys.KeyFactory;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.IOException; import java.io.IOException;
import java.net.Socket; import java.net.Socket;
import java.util.Observable;
/** /**
* Service for dispatching key press data to race from client * 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 * Socket to client
*/ */
@ -22,13 +22,23 @@ public class ControllerServer implements Runnable {
* Wrapper for input from client * Wrapper for input from client
*/ */
private DataInputStream inputStream; private DataInputStream inputStream;
/**
* Last received boat action
*/
private BoatActionEnum action;
/**
*
*/
private RaceLogic rc;
/** /**
* Initialise server-side controller with live client socket * Initialise server-side controller with live client socket
* @param socket to client * @param socket to client
*/ */
public ControllerServer(Socket socket) { public ControllerServer(Socket socket, RaceLogic rc) {
this.socket = socket; this.socket = socket;
this.rc = rc;
this.addObserver(rc);
try { try {
this.inputStream = new DataInputStream(this.socket.getInputStream()); this.inputStream = new DataInputStream(this.socket.getInputStream());
} catch (IOException e) { } 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. * Wait for controller key input from client and loop.
*/ */
@ -48,8 +62,11 @@ public class ControllerServer implements Runnable {
inputStream.read(message); inputStream.read(message);
BinaryMessageDecoder encodedMessage = new BinaryMessageDecoder(message); BinaryMessageDecoder encodedMessage = new BinaryMessageDecoder(message);
BoatActionDecoder boatActionDecoder = new BoatActionDecoder(encodedMessage.getMessageBody()); BoatActionDecoder boatActionDecoder = new BoatActionDecoder(encodedMessage.getMessageBody());
BoatActionEnum decodedMessage = boatActionDecoder.getBoatAction(); action = boatActionDecoder.getBoatAction();
System.out.println("Received key: " + decodedMessage);
// Notify observers of most recent action
this.notifyObservers();
this.setChanged();
} }
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();

@ -27,8 +27,8 @@ public class KeyFactory {
keyState.put("SPACE", new VMGKey("VMG")); keyState.put("SPACE", new VMGKey("VMG"));
keyState.put("SHIFT", new SailsToggleKey("Toggle Sails")); keyState.put("SHIFT", new SailsToggleKey("Toggle Sails"));
keyState.put("ENTER", new TackGybeKey("Tack/Gybe")); keyState.put("ENTER", new TackGybeKey("Tack/Gybe"));
keyState.put("PAGE_UP", new UpWindKey("Upwind")); keyState.put("UP", new UpWindKey("Upwind"));
keyState.put("PAGE_DOWN", new DownWindKey("Downwind")); keyState.put("DOWN", new DownWindKey("Downwind"));
} }
/** /**

@ -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);
}
}
Loading…
Cancel
Save