package mock.app; import mock.dataInput.PolarParser; import mock.exceptions.EventConstructionException; import mock.model.MockRace; import mock.model.Polars; import mock.model.RaceLogic; import mock.model.SourceIdAllocator; import mock.model.commandFactory.CompositeCommand; import mock.model.wind.ShiftingWindGenerator; import mock.model.wind.WindGenerator; import network.Messages.LatestMessages; import shared.dataInput.*; import shared.enums.XMLFileType; import shared.exceptions.InvalidBoatDataException; import shared.exceptions.InvalidRaceDataException; import shared.exceptions.InvalidRegattaDataException; import shared.exceptions.XMLReaderException; import shared.model.Bearing; import shared.model.Constants; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; /** * A Race Event, this holds all of the race's information as well as handling the connection to its clients. */ public class Event { /** * Contents of the various xml files. */ private String raceXML; private String regattaXML; private String boatXML; private XMLFileType xmlFileType; private Polars boatPolars; /** * Data sources containing data from the xml files. */ RaceDataSource raceDataSource; BoatDataSource boatDataSource; RegattaDataSource regattaDataSource; private ConnectionAcceptor connectionAcceptor; private LatestMessages latestMessages; private CompositeCommand compositeCommand; /** * This is used to allocate source IDs. */ private SourceIdAllocator sourceIdAllocator; private Thread raceThread; private Thread connectionThread; private int mapIndex; /** * Constructs an event, using various XML files. * @param singlePlayer Whether or not to create a single player event. * @throws EventConstructionException Thrown if we cannot create an Event for any reason. */ public Event(boolean singlePlayer, int mapIndex) throws EventConstructionException { this.mapIndex = mapIndex; String raceXMLFile; String boatsXMLFile = "mock/mockXML/boatTest.xml"; String regattaXMLFile = "mock/mockXML/regattaTest.xml"; switch (mapIndex){ case 0:raceXMLFile = "mock/mockXML/raceSixPlayers.xml"; break; case 1:raceXMLFile = "mock/mockXML/oMapLayout.xml"; break; case 2: raceXMLFile = "mock/mockXML/iMapLayout.xml"; break; case 3: raceXMLFile = "mock/mockXML/mMapLayout.xml"; break; case 4: raceXMLFile = "mock/mockXML/raceTutorial.xml"; boatsXMLFile = "mock/mockXML/boatTutorial.xml"; regattaXMLFile = "mock/mockXML/regattaTutorial.xml"; break; default: raceXMLFile = "mock/mockXML/raceSixPlayers.xml"; } if (singlePlayer) { raceXMLFile = "mock/mockXML/raceSinglePlayer.xml"; boatsXMLFile = "mock/mockXML/boatsSinglePlayer.xml"; } //Read XML files. try { //this.raceXML = RaceXMLCreator.alterRaceToWind(raceXMLFile, 90); this.raceXML = XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8); this.raceXML = Event.setRaceXMLAtCurrentTimeToNow(XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8)); if(mapIndex==4){ this.raceXML = Event.setRaceXMLAtCurrentTimeToNow(XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8), 1000, 5000); } this.boatXML = XMLReader.readXMLFileToString(boatsXMLFile, StandardCharsets.UTF_8); this.regattaXML = XMLReader.readXMLFileToString(regattaXMLFile, StandardCharsets.UTF_8); } catch (XMLReaderException e) { throw new EventConstructionException("Could not read XML files.", e); } this.xmlFileType = XMLFileType.Contents; this.boatPolars = PolarParser.parse("mock/polars/acc_polars.csv"); PolarParser.parseNewPolars("mock/polars/acc_polars.csv"); //Parse the XML files into data sources. try { this.raceDataSource = new RaceXMLReader(this.raceXML, this.xmlFileType); this.boatDataSource = new BoatXMLReader(this.boatXML, this.xmlFileType); this.regattaDataSource = new RegattaXMLReader(this.regattaXML, this.xmlFileType); } catch (XMLReaderException | InvalidRaceDataException | InvalidRegattaDataException | InvalidBoatDataException e) { throw new EventConstructionException("Could not parse XML files.", e); } this.compositeCommand = new CompositeCommand(); this.latestMessages = new LatestMessages(); WindGenerator windGenerator = new ShiftingWindGenerator( Bearing.fromDegrees(225), 12 ); RaceLogic newRace = new RaceLogic( new MockRace( boatDataSource, raceDataSource, regattaDataSource, this.boatPolars, Constants.RaceTimeScale, windGenerator ), this.latestMessages, this.compositeCommand); this.raceThread = new Thread(newRace, "Event.Start()->RaceLogic thread"); raceThread.start(); //Create connection acceptor. this.sourceIdAllocator = new SourceIdAllocator(newRace.getRace()); try { this.connectionAcceptor = new ConnectionAcceptor(latestMessages, compositeCommand, sourceIdAllocator, newRace); } catch (IOException e) { throw new EventConstructionException("Could not create ConnectionAcceptor.", e); } this.connectionThread = new Thread(connectionAcceptor, "Event.Start()->ConnectionAcceptor thread"); connectionThread.start(); } public void endEvent() throws IOException { this.connectionThread.interrupt(); this.connectionAcceptor.closeConnection(); this.raceThread.interrupt(); } //TODO remove this after demo on 18th august! /** * Sets the xml description of the race to show the race was created now, and starts in 4 minutes * @param raceXML The race.xml contents. * @return String containing edited xml */ public static String setRaceXMLAtCurrentTimeToNow(String raceXML) { return setRaceXMLAtCurrentTimeToNow(raceXML, Constants.RacePreStartTime, Constants.RacePreparatoryTime); } public static String setRaceXMLAtCurrentTimeToNow(String raceXML, long racePreStartTime, long racePreparatoryTime){ //The start time is current time + 4 minutes. prestart is 3 minutes, and we add another minute. long millisecondsToAdd = racePreStartTime + racePreparatoryTime; long secondsToAdd = millisecondsToAdd / 1000; //Scale the time using our time scalar. secondsToAdd = secondsToAdd / Constants.RaceTimeScale; DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ"); ZonedDateTime creationTime = ZonedDateTime.now(); raceXML = raceXML.replace("RACE_CREATION_TIME", dateFormat.format(creationTime)); raceXML = raceXML.replace("RACE_START_TIME", dateFormat.format(creationTime.plusSeconds(secondsToAdd))); return raceXML; } }