diff --git a/assignments/nbproject/project.properties b/assignments/nbproject/project.properties index 58458faeae7e33494078e93d20fe58c9ab4e9521..07acdd81b4036da91bc4dfdfba023dd979b93e8c 100644 --- a/assignments/nbproject/project.properties +++ b/assignments/nbproject/project.properties @@ -1,135 +1,135 @@ -annotation.processing.enabled=true -annotation.processing.enabled.in.editor=false -annotation.processing.processors.list= -annotation.processing.run.all.processors=true -annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output -application.desc=Student assignments performed as part of NPS course Networked Graphics MV3500. This course is an introduction to network communications in simulation applications. Topics include an introduction to the TCP/IP protocol stack; TCP/IP socket communications, including TCP, UDP, and multicast; and protocol design issues, with emphasis on Distributed Interactive Simulation (DIS) Protocol and High Level Architecture (HLA). Course emphasis is on creation and testing of network programming network code and web-browser applications. -application.homepage=https://gitlab.nps.edu/Savage/NetworkedGraphicsMV3500/tree/master/assignments -application.splash=../..\\NetworkedGraphicsMV3500\\documentation\\images\\OpenDisSurferDude.png -application.title=NPS Networked Graphics MV3500 assignments -application.vendor=Don Brutzman -auxiliary.org-netbeans-spi-editor-hints-projects.perProjectHintSettingsFile=nbproject/cfg_hints.xml -build.classes.dir=${build.dir}/classes -build.classes.excludes=**/*.java,**/*.form -# This directory is removed when the project is cleaned: -build.dir=build -build.generated.dir=${build.dir}/generated -build.generated.sources.dir=${build.dir}/generated-sources -# Only compile against the classpath explicitly listed here: -build.sysclasspath=ignore -build.test.classes.dir=${build.dir}/test/classes -build.test.results.dir=${build.dir}/test/results -# Uncomment to specify the preferred debugger connection transport: -#debug.transport=dt_socket -debug.classpath=\ - ${run.classpath} -debug.modulepath=\ - ${run.modulepath} -debug.test.classpath=\ - ${run.test.classpath} -debug.test.modulepath=\ - ${run.test.modulepath} -# Files in build.classes.dir which should be excluded from distribution jar -# Avoid compilation or inclusion of student project depending on mutex libraries only available in JDK8 -# https://stackoverflow.com/questions/27906896/exclude-package-from-build-but-not-from-view-in-netbeans-8 -excludes=**/MV3500Cohort2019JulySeptember/projects/BrennenstuhlKnobelochMcCann/** -dist.archive.excludes=**/MV3500Cohort2019JulySeptember/projects/BrennenstuhlKnobelochMcCann/** - -# This directory is removed when the project is cleaned: -dist.dir=dist -dist.jar=${dist.dir}/Networked_Graphics_MV3500_assignments.jar -dist.javadoc.dir=${dist.dir}/javadoc -endorsed.classpath= -file.reference.dis-enums-1.3.jar=../lib/dis-enums-1.3.jar -file.reference.opendis7-full.jar=../lib/opendis7-full.jar -file.reference.open-dis_4.16.jar=../lib/open-dis_4.16.jar -file.reference.simkit-doc.zip=../lib/simkit-doc.zip -file.reference.simkit-src.zip=../lib/simkit-src.zip -file.reference.simkit.jar=../lib/simkit.jar -#file.reference.opendis7-enumerations-classes.jar=../lib/opendis7-enumerations-classes.jar -#file.reference.opendis7-pdus-classes.jar=../lib/opendis7-pdus-classes.jar -includes=** -jar.archive.disabled=${jnlp.enabled} -jar.compress=false -jar.index=${jnlp.enabled} -javac.classpath=\ - ${file.reference.opendis7-full.jar}:\ - ${file.reference.dis-enums-1.3.jar}:\ - ${file.reference.open-dis_4.16.jar}:\ - ${file.reference.simkit-doc.zip}:\ - ${file.reference.simkit-src.zip}:\ - ${file.reference.simkit.jar} -# ${file.reference.opendis7-enumerations-classes.jar}:\ -# ${file.reference.opendis7-pdus-classes.jar}:\ - -# Space-separated list of extra javac options -javac.compilerargs=-Xlint:deprecation -Xlint:unchecked -javac.deprecation=false -javac.external.vm=true -javac.modulepath= -javac.processormodulepath= -javac.processorpath=\ - ${javac.classpath} -javac.source=22 -javac.target=22 -javac.test.classpath=\ - ${javac.classpath}:\ - ${build.classes.dir} -javac.test.modulepath=\ - ${javac.modulepath} -javac.test.processorpath=\ - ${javac.test.classpath} -javadoc.additionalparam=-header "NPS Networked Graphics MV3500 Assignments" -javadoc.author=true -javadoc.encoding=${source.encoding} -javadoc.html5=false -javadoc.noindex=false -javadoc.nonavbar=false -javadoc.notree=false -javadoc.private=false -#javadoc.reference.opendis7-enumerations-classes.jar=../lib/opendis7-enumerations-javadoc.jar -javadoc.splitindex=true -javadoc.use=true -javadoc.version=false -javadoc.windowtitle=MV3500 Assignments -jlink.launcher=false -jlink.launcher.name=Networked_Graphics_MV3500_assignments -jnlp.codebase.type=no.codebase -jnlp.descriptor=application -jnlp.enabled=false -jnlp.mixed.code=default -jnlp.offline-allowed=false -jnlp.signed=false -jnlp.signing= -jnlp.signing.alias= -jnlp.signing.keystore= -# Optional override of default Application-Library-Allowable-Codebase attribute identifying the locations where your signed RIA is expected to be found. -manifest.custom.application.library.allowable.codebase= -# Optional override of default Caller-Allowable-Codebase attribute identifying the domains from which JavaScript code can make calls to your RIA without security prompts. -manifest.custom.caller.allowable.codebase= -# Optional override of default Codebase manifest attribute, use to prevent RIAs from being repurposed -manifest.custom.codebase= -# Optional override of default Permissions manifest attribute (supported values: sandbox, all-permissions) -manifest.custom.permissions= -meta.inf.dir=${src.dir}/META-INF -mkdist.disabled=false -platform.active=default_platform -project.licensePath=../license.txt -run.classpath=\ - ${javac.classpath}:\ - ${build.classes.dir} -# Space-separated list of JVM arguments used when running the project. -# You may also define separate properties like run-sys-prop.name=value instead of -Dname=value. -# To set system properties for unit tests define test-sys-prop.name=value: -run.jvmargs= -run.modulepath=\ - ${javac.modulepath} -run.test.classpath=\ - ${javac.test.classpath}:\ - ${build.test.classes.dir} -run.test.modulepath=\ - ${javac.test.modulepath} -source.encoding=UTF-8 -#source.reference.opendis7-enumerations-classes.jar=../lib/opendis7-enumerations-source.jar -src.dir=src -test.src.dir=test +annotation.processing.enabled=true +annotation.processing.enabled.in.editor=false +annotation.processing.processors.list= +annotation.processing.run.all.processors=true +annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output +application.desc=Student assignments performed as part of NPS course Networked Graphics MV3500. This course is an introduction to network communications in simulation applications. Topics include an introduction to the TCP/IP protocol stack; TCP/IP socket communications, including TCP, UDP, and multicast; and protocol design issues, with emphasis on Distributed Interactive Simulation (DIS) Protocol and High Level Architecture (HLA). Course emphasis is on creation and testing of network programming network code and web-browser applications. +application.homepage=https://gitlab.nps.edu/Savage/NetworkedGraphicsMV3500/tree/master/assignments +application.splash=../..\\NetworkedGraphicsMV3500\\documentation\\images\\OpenDisSurferDude.png +application.title=NPS Networked Graphics MV3500 assignments +application.vendor=Don Brutzman +auxiliary.org-netbeans-spi-editor-hints-projects.perProjectHintSettingsFile=nbproject/cfg_hints.xml +build.classes.dir=${build.dir}/classes +build.classes.excludes=**/*.java,**/*.form +# This directory is removed when the project is cleaned: +build.dir=build +build.generated.dir=${build.dir}/generated +build.generated.sources.dir=${build.dir}/generated-sources +# Only compile against the classpath explicitly listed here: +build.sysclasspath=ignore +build.test.classes.dir=${build.dir}/test/classes +build.test.results.dir=${build.dir}/test/results +# Uncomment to specify the preferred debugger connection transport: +#debug.transport=dt_socket +debug.classpath=\ + ${run.classpath} +debug.modulepath=\ + ${run.modulepath} +debug.test.classpath=\ + ${run.test.classpath} +debug.test.modulepath=\ + ${run.test.modulepath} +# Files in build.classes.dir which should be excluded from distribution jar +# Avoid compilation or inclusion of student project depending on mutex libraries only available in JDK8 +# https://stackoverflow.com/questions/27906896/exclude-package-from-build-but-not-from-view-in-netbeans-8 +excludes=**/MV3500Cohort2019JulySeptember/projects/BrennenstuhlKnobelochMcCann/** +dist.archive.excludes=**/MV3500Cohort2019JulySeptember/projects/BrennenstuhlKnobelochMcCann/** + +# This directory is removed when the project is cleaned: +dist.dir=dist +dist.jar=${dist.dir}/Networked_Graphics_MV3500_assignments.jar +dist.javadoc.dir=${dist.dir}/javadoc +endorsed.classpath= +file.reference.dis-enums-1.3.jar=../lib/dis-enums-1.3.jar +file.reference.opendis7-full.jar=../lib/opendis7-full.jar +file.reference.open-dis_4.16.jar=../lib/open-dis_4.16.jar +file.reference.simkit-doc.zip=../lib/simkit-doc.zip +file.reference.simkit-src.zip=../lib/simkit-src.zip +file.reference.simkit.jar=../lib/simkit.jar +#file.reference.opendis7-enumerations-classes.jar=../lib/opendis7-enumerations-classes.jar +#file.reference.opendis7-pdus-classes.jar=../lib/opendis7-pdus-classes.jar +includes=** +jar.archive.disabled=${jnlp.enabled} +jar.compress=false +jar.index=${jnlp.enabled} +javac.classpath=\ + ${file.reference.opendis7-full.jar}:\ + ${file.reference.dis-enums-1.3.jar}:\ + ${file.reference.open-dis_4.16.jar}:\ + ${file.reference.simkit-doc.zip}:\ + ${file.reference.simkit-src.zip}:\ + ${file.reference.simkit.jar} +# ${file.reference.opendis7-enumerations-classes.jar}:\ +# ${file.reference.opendis7-pdus-classes.jar}:\ + +# Space-separated list of extra javac options +javac.compilerargs=-Xlint:deprecation -Xlint:unchecked +javac.deprecation=false +javac.external.vm=true +javac.modulepath= +javac.processormodulepath= +javac.processorpath=\ + ${javac.classpath} +javac.source=20 +javac.target=20 +javac.test.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir} +javac.test.modulepath=\ + ${javac.modulepath} +javac.test.processorpath=\ + ${javac.test.classpath} +javadoc.additionalparam=-header "NPS Networked Graphics MV3500 Assignments" +javadoc.author=true +javadoc.encoding=${source.encoding} +javadoc.html5=false +javadoc.noindex=false +javadoc.nonavbar=false +javadoc.notree=false +javadoc.private=false +#javadoc.reference.opendis7-enumerations-classes.jar=../lib/opendis7-enumerations-javadoc.jar +javadoc.splitindex=true +javadoc.use=true +javadoc.version=false +javadoc.windowtitle=MV3500 Assignments +jlink.launcher=false +jlink.launcher.name=Networked_Graphics_MV3500_assignments +jnlp.codebase.type=no.codebase +jnlp.descriptor=application +jnlp.enabled=false +jnlp.mixed.code=default +jnlp.offline-allowed=false +jnlp.signed=false +jnlp.signing= +jnlp.signing.alias= +jnlp.signing.keystore= +# Optional override of default Application-Library-Allowable-Codebase attribute identifying the locations where your signed RIA is expected to be found. +manifest.custom.application.library.allowable.codebase= +# Optional override of default Caller-Allowable-Codebase attribute identifying the domains from which JavaScript code can make calls to your RIA without security prompts. +manifest.custom.caller.allowable.codebase= +# Optional override of default Codebase manifest attribute, use to prevent RIAs from being repurposed +manifest.custom.codebase= +# Optional override of default Permissions manifest attribute (supported values: sandbox, all-permissions) +manifest.custom.permissions= +meta.inf.dir=${src.dir}/META-INF +mkdist.disabled=false +platform.active=default_platform +project.licensePath=../license.txt +run.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir} +# Space-separated list of JVM arguments used when running the project. +# You may also define separate properties like run-sys-prop.name=value instead of -Dname=value. +# To set system properties for unit tests define test-sys-prop.name=value: +run.jvmargs= +run.modulepath=\ + ${javac.modulepath} +run.test.classpath=\ + ${javac.test.classpath}:\ + ${build.test.classes.dir} +run.test.modulepath=\ + ${javac.test.modulepath} +source.encoding=UTF-8 +#source.reference.opendis7-enumerations-classes.jar=../lib/opendis7-enumerations-source.jar +src.dir=src +test.src.dir=test diff --git a/assignments/src/MV3500Cohort2024JulySeptember/homework3/Schnitzler/README.md b/assignments/src/MV3500Cohort2024JulySeptember/homework3/Schnitzler/README.md new file mode 100644 index 0000000000000000000000000000000000000000..7d4497eba49fb1f84703d5d52a046afda8c3019b --- /dev/null +++ b/assignments/src/MV3500Cohort2024JulySeptember/homework3/Schnitzler/README.md @@ -0,0 +1,44 @@ +## Homework 3: Example Simulation Recording using OpenDIS Network Streams + +<!-- Viewable at https://gitlab.nps.edu/Savage/NetworkedGraphicsMV3500/-/blob/master/assignments/src/MV3500Cohort2024JulySeptember/homework3/README.md --> + +### Description + +Using the [OpenDIS ExampleSimulationProgram](../../../../../examples/src/OpenDis7Examples/ExampleSimulationProgram.java) and implement a simple DES. +In the DES a SimpleMover and a SimplePathMoverManager are used. The SimpleMover is moving along a given path. +The SimpleMover is modified in this way, that every time if the movement is changed an ESPDU is sent via DisChannel, but at least every 5 seconds with the current position. + +### Assignment + +1. Adapt the functionality for [OpenDIS ExampleSimulationProgram](../../../../examples/src/OpenDis7Examples/ExampleSimulationProgram.java), modifying provided code +2. Experiment with the enumeration values that set up each entity and PDU. What works for you? What makes sense for your future work? +3. Adapt or replace the UML diagrams to describe what you have going on. +4. Record, save and replay your result stream using [PduRecorder](https://savage.nps.edu/opendis7-java/javadoc/edu/nps/moves/dis7/utilities/stream/PduRecorder.html) or [Wireshark](https://www.wireshark.org) + * see local [assignments/src/pduLog](../../../pduLog) subdirectory for latest opendis log files + * Coming soon, we will also (again have) [X3D-Edit](https://savage.nps.edu/X3D-Edit) for DIS stream recording/replay +5. Observe good-practice conventions in the [assignments README](../../../README.md) and [current-course README](../README.md) instructions. + +This assignment presents a Problem Prototyping opportunity. +While some minimal functionality is expected, the general outline of +a networking problem and proposed solution holds great interest. +Think of it as warmup preparation for your future work. + +This is also a freeplay opportunity. +You have the option to pick one or more of the provided course example programs +and adapt the source to demonstrate a new client-server handshake protocol of interest. + +Be sure to provide a rationale that justifies why the networking choices you made +(TCP/UDP, unicast/multicast, etc.) are the best for the problem you are addressing. + +You may find that the prior [homework2 README](../homework2/README.md) still provides +helpful details on what specific deliverables are expected in each homework assignment. + +Team efforts are encouraged, though if you choose a team approach be sure to justify why. +This is a good warmup prior to final projects. Have fun with Java networking! + +### Prior Assignment, August 2019 + +In 2019, students worked together on a single project to check wireless multicast connectivity recently deployed on NPS campus. + +See their experimental results in the [NPS Multicast Connectivity Report](../../MV3500Cohort2019JulySeptember/homework3). + diff --git a/assignments/src/MV3500Cohort2024JulySeptember/homework3/Schnitzler/SchnitzlerSimulationProgram.java b/assignments/src/MV3500Cohort2024JulySeptember/homework3/Schnitzler/SchnitzlerSimulationProgram.java new file mode 100644 index 0000000000000000000000000000000000000000..cfb786ec308e46b7c8a27ed269a6dbf8c45b88fc --- /dev/null +++ b/assignments/src/MV3500Cohort2024JulySeptember/homework3/Schnitzler/SchnitzlerSimulationProgram.java @@ -0,0 +1,357 @@ +/** + * Copyright (c) 2008-2023, MOVES Institute, Naval Postgraduate School (NPS). All rights reserved. + * This work is provided under a BSD open-source license, see project license.html or license.txt + * @author brutzman@nps.edu for the original dis part + * @author simonschnitzler for the simulation part + */ +package MV3500Cohort2024JulySeptember.homework3.Schnitzler; + +import edu.nps.moves.dis7.pdus.*; +import edu.nps.moves.dis7.utilities.DisChannel; +import edu.nps.moves.dis7.utilities.PduFactory; +import java.awt.geom.Point2D; +import java.time.LocalDateTime; +import java.util.logging.Level; +import java.util.logging.Logger; +import simkit.Schedule; +import simkit.util.SimplePropertyDumper; + +/** The purpose of this inheritable class is to provide an easily modifiable + * example simulation program that includes DIS-capable entities performing + * tasks of interest, and then reporting activity via PDUs to the network. + * Default program initialization includes PDU recording turned on by default. + * @see <a href="https://gitlab.nps.edu/Savage/NetworkedGraphicsMV3500/-/blob/master/examples/src/OpenDis7Examples/ExampleSimulationProgramLog.txt" target="_blank">ExampleSimulationProgramLog.txt</a> + * @see <a href="https://gitlab.nps.edu/Savage/NetworkedGraphicsMV3500/-/blob/master/examples/src/OpenDis7Examples/ExampleSimulationProgramPduCaptureLog.dislog" target="_blank">ExampleSimulationProgramPduCaptureLog.dislog</a> + * @see <a href="https://gitlab.nps.edu/Savage/NetworkedGraphicsMV3500/-/blob/master/examples/src/OpenDis7Examples/ExampleSimulationProgramFlowDiagram.pdf" target="_blank">ExampleSimulationProgramFlowDiagram.pdf</a> + * @see <a href="https://gitlab.nps.edu/Savage/NetworkedGraphicsMV3500/-/blob/master/examples/src/OpenDis7Examples/ExampleSimulationProgramWireshark.png" target="_blank">ExampleSimulationProgramWireshark.png</a> + * @see <a href="https://gitlab.nps.edu/Savage/NetworkedGraphicsMV3500/-/blob/master/examples/src/OpenDis7Examples/ExampleSimulationProgramSequenceDiagram.png" target="_blank">ExampleSimulationProgramSequenceDiagram.png</a> + */ +public class SchnitzlerSimulationProgram +{ + /* **************************** infrastructure code, modification is seldom needed ************************* */ + + private String descriptor = this.getClass().getSimpleName(); + /** DIS channel defined by network address/port combination includes multiple utility capabilities */ + protected DisChannel disChannel; + /** Factory object used to create new PDU instances */ + protected PduFactory pduFactory; + + /** seconds per loop for real-time or simulation execution */ + private double simulationTimeStepDuration = 1.0; // seconds TODO encapsulate + /** initial simulation time in seconds */ + double simulationTimeInitial = 0.0; + /** current simulation time in seconds */ + double simulationTimeSeconds = simulationTimeInitial; + /** Maximum number of simulation loops */ + int MAX_LOOP_COUNT = 20; + + String narrativeMessage1 = new String(); + String narrativeMessage2 = new String(); + String narrativeMessage3 = new String(); + + /** EntityID settings for entity 1 */ + protected EntityID entityID_1 = new EntityID(); + /** EntityID settings for entity 2 */ + protected EntityID entityID_2 = new EntityID(); + /** ESPDU for entity 1 */ + protected EntityStatePdu entityStatePdu_1; + /** ESPDU for entity 2 */ + protected EntityStatePdu entityStatePdu_2; + /** FirePdu for entity 1 first weapon (if any) */ + protected FirePdu firePdu_1a; + /** FirePdu for entity 1 second weapon (if any) */ + protected FirePdu firePdu_1b; + /** MunitionDescriptor for these weapons */ + protected MunitionDescriptor munitionDescriptor1; + + + // hey programmer, what other state do you want? this is a good place to declare it... + + /** + * Constructor to create an instance of this class. + * Design goal: additional built-in initialization conveniences can go here + * to keep your efforts focused on the runSimulation() method. + */ + // base constructor is not invoked automatically by other constructors + // https://stackoverflow.com/questions/581873/best-way-to-handle-multiple-constructors-in-java + public SchnitzlerSimulationProgram() + { + initialize(); + } + /** + * Constructor to create an instance of this class. + * @param newDescriptor describes this program, useful for logging and debugging + */ + public SchnitzlerSimulationProgram(String newDescriptor) + { + descriptor = newDescriptor; + initialize(); + } + /** + * Utility Constructor that allows your example simulation program to override default network address and port + * @param address network address to use + * @param port corresponding network port to use + */ + public SchnitzlerSimulationProgram(String address, int port) + { + disChannel.setNetworkAddress (address); + disChannel.setNetworkPort (port); + disChannel.setVerboseComments (true); // TODO rename library method to disambiguate CommentPDU + // TODO still seems really chatty... add silent mode? + disChannel.setVerboseDisNetworkInterface(true); // Default false + disChannel.setVerbosePduRecorder (true); // default false + initialize(); + } + + /** Initialize channel setup for OpenDis7 and report a test PDU + * @see initializeDisChannel + * @see initializeSimulationEntities */ + private void initialize() + { + initializeDisChannel(); // must come first, uses PduFactory + + //initializeSimulationEntities(); // set unchanging parameters + + disChannel.join(); // TODO further functionality expected + + String timeStepMessage = "Simulation timestep duration " + getSimulationTimeStepDuration() + " seconds"; + disChannel.sendCommentPdu(simulationTimeSeconds, DisChannel.COMMENTPDU_SIMULATION_TIMESTEP, timeStepMessage); + // additional constructor initialization can go here + } + + /** Initialize channel setup for OpenDis7 and report a test PDU */ + private void initializeDisChannel() + { + if (disChannel == null) + disChannel = new DisChannel(); + else + { + disChannel.printlnTRACE ("*** warning, duplicate invocation of initializeDisChannel() ignored"); + return; + } + pduFactory = disChannel.getPduFactory(); + disChannel.setDescriptor(this.getClass().getSimpleName()); // ExampleSimulationProgram might be a superclass + disChannel.setUpNetworkInterface(); + disChannel.printlnTRACE ("just checking: disChannel.getNetworkAddress()=" + disChannel.getNetworkAddress() + + ", getNetworkPort()=" + disChannel.getNetworkPort()); + disChannel.getDisNetworkInterface().setVerbose(true); // sending and receipt + disChannel.printlnTRACE ("just checking: hasVerboseSending()=" + disChannel.getDisNetworkInterface().hasVerboseSending() + + ", hasVerboseReceipt()=" + disChannel.getDisNetworkInterface().hasVerboseReceipt()); + disChannel.getPduRecorder().setVerbose(true); + + // TODO confirm whether recorder is explicitly started by programmer (or not) + +// disChannel.sendCommentPdu(VariableRecordType.OTHER, "DisThreadedNetworkInterface.initializeDisChannel() complete"); // hello channel, debug + } + + /** + * Set up the DES. A single SimpleMoverOpenDis7 starting at (0.0,0.0) + * with a maximum speed of 30.0 and the following + * waypoints:<br>(20.0,100.0),<br>(80.0,10.0),<br>(140.0,150.0) + */ + private void initializeSimulation(){ + Point2D startingPoint = new Point2D.Double(0.0,0.0); + double maxSpeed = 30.0; + SimpleMoverOpenDis7 simpleMover = new SimpleMoverOpenDis7(startingPoint, maxSpeed, disChannel); + simpleMover.setName("Fred"); + + Point2D[] path = new Point2D[]{new Point2D.Double(20.0,100.0), + new Point2D.Double(80.0,10.0),new Point2D.Double(140.0,150.0)}; + + SimplePathMoverManagerOpenDis7 simplePathMoverManager = new SimplePathMoverManagerOpenDis7(path,true); + + simplePathMoverManager.addSimEventListener(simpleMover); + simpleMover.addSimEventListener(simplePathMoverManager); + + SimplePropertyDumper simplePropertyDumper = new SimplePropertyDumper(); + simplePathMoverManager.addPropertyChangeListener(simplePropertyDumper); + simpleMover.addPropertyChangeListener(simplePropertyDumper); + simplePropertyDumper.setDumpSource(true); + + System.out.println(simpleMover); + System.out.println(simplePathMoverManager); + + Schedule.setVerbose(true); + Schedule.reset(); + Schedule.setPauseAfterEachEvent(true); + + } + + /** + * This runSimulationLoops() method is for you, a customizable programmer-modifiable + * code block for defining and running a new simulation of interest. + * + * Welcome! Other parts of this program handle bookkeeping and plumbing tasks so that + * you can focus on your model entities and activities. + * Expandable support includes DIS EntityStatePdu, FirePdu and CommentPdu all available for + * modification and sending in a simulation loop. + * Continuous improvement efforts seek to make this program as easy and straightforward + * as possible for DIS simulationists to use and adapt. + * All of the other methods are setup, teardown and configuration that you may find + * interesting, even helpful, but don't really have to worry about. + */ + @SuppressWarnings("SleepWhileInLoop") // yes we might do that + public void runSimulationLoops () + { + try + { + int simulationLoopCount = 0; + boolean simulationComplete = false; // sentinel variable as termination condition, are we done yet? + + // TODO reset Clock Time for today's date and timestamp to zero, providing consistent outputs for each simulation run + String timeMessage = "Simulation time " + simulationTimeSeconds + " at LocalDateTime " + LocalDateTime.now(); + disChannel.sendCommentPdu(simulationTimeSeconds, DisChannel.COMMENTPDU_TIME, timeMessage); + // TODO replace enumeration with disChannel.COMMENTPDU_TIME + // TODO fix VariableRecordType.TIME_AMP_DATE_VALID + + // =================================================================================================== + // loop the simulation while allowed, programmer can set additional conditions to break out and finish + + initializeSimulation(); + + while (simulationLoopCount < MAX_LOOP_COUNT) // are we done yet? + { + simulationLoopCount++; + // good practice: increment loop counter as first action in that loop + + + // ============================================================================================= + // * your own simulation code starts here! ***************************************************** + // ============================================================================================= + + double simTime = Schedule.getSimTime(); + while(simTime < simulationLoopCount * getSimulationTimeStepDuration()){ + Schedule.startSimulation(); + simTime = Schedule.getSimTime(); + } + + + // make your reports: narrative code for CommentPdu here (set all to empty strings to avoid sending) + narrativeMessage1 = "MV3500 ExampleSimulationProgram"; + narrativeMessage2 = "runSimulation() loop " + simulationLoopCount; + narrativeMessage3 = ""; // intentionally blank for testing + + // your loop termination condition goes here + if (simulationLoopCount >= MAX_LOOP_COUNT) // for example + { + simulationComplete = true; + } + // ============================================================================================= + // * your own simulation code is finished here! ************************************************ + // ============================================================================================= + + // staying synchronized with timestep: wait duration for elapsed time in this loop + // Thread.sleep needs a (long) parameter for milliseconds, which are clumsy to use sometimes + Thread.sleep((long)(getSimulationTimeStepDuration() * 1000)); // units of seconds * (1000 msec/sec) = milliseconds + System.out.println ("... [Pausing for " + getSimulationTimeStepDuration() + " seconds]"); + + + // =============================== + // current loop now finished, check whether to terminate if simulation complete, otherwise continue + if (simulationComplete || (simulationLoopCount > 10000)) // for example; including fail-safe condition is good + { + System.out.println ("... [loop termination condition met, simulationComplete=" + simulationComplete + "]"); // ", final loopCount=" + loopCount + + System.out.flush(); + break; + } + simulationTimeSeconds += getSimulationTimeStepDuration(); // good practice: increment simulationTime as lastst action in that loop + + } // end of simulation loop, continue until done + // ===================================================================================================// ===================================================================================================// ===================================================================================================// =================================================================================================== + + narrativeMessage2 = "runSimulation() completed successfully"; // all done, so tell everyone else on the channel + // TODO better javadoc needs to be autogenerated for VariableRecordType enumerations + disChannel.sendCommentPdu(DisChannel.COMMENTPDU_NARRATIVE, narrativeMessage1, narrativeMessage2, narrativeMessage3); + System.out.println ("... [final=completion CommentPdu successfully sent for simulation]"); + +// disChannel.getPduRecorder(). TODO record XML as well + disChannel.leave(); // embedded SimulationManager is expected to send appropriate PDUs for entity, application shutdown + } + catch (InterruptedException iex) // handle any exception that your code might choose to provoke! + { + Logger.getLogger(SchnitzlerSimulationProgram.class.getSimpleName()).log(Level.SEVERE, null, iex); + } + + } + + /** + * Initial execution via main() method: handle args array of command-line initialization (CLI) arguments here + * @param args command-line parameters: network address and port + */ + protected void handleArguments (String[] args) + { + // initial execution: handle args array of initialization arguments here + if (args.length == 2) + { + if ((args[0] != null) && !args[0].isEmpty()) + thisProgram.disChannel.setNetworkAddress(args[0]); + if ((args[1] != null) && !args[1].isEmpty()) + thisProgram.disChannel.setNetworkPort(Integer.parseInt(args[1])); + } + else if (args.length != 0) + { + System.err.println("Usage: " + thisProgram.getClass().getSimpleName() + " [address port]"); + System.exit(-1); + } + } + + /** + * Get simple descriptor (such as parent class name) for this network interface, used in trace statements + * @return simple descriptor name + */ + public String getDescriptor() { + return descriptor; + } + + /** + * Set new simple descriptor (such as parent class name) for this network interface, used in trace statements + * @param newDescriptor simple descriptor name for this interface + */ + public void setDescriptor(String newDescriptor) { + if (newDescriptor == null) + newDescriptor = ""; + this.descriptor = newDescriptor; + } + + /** + * parameter accessor method + * @return the simulationTimeStepDuration in seconds + */ + public double getSimulationTimeStepDuration() { + return simulationTimeStepDuration; + } + + /** + * parameter accessor method + * @param timeStepDurationSeconds the simulationTimeStepDuration in seconds to set + */ + public void setSimulationTimeStepDuration(double timeStepDurationSeconds) { + this.simulationTimeStepDuration = timeStepDurationSeconds; + } + + /** Locally instantiable copy of program, can be subclassed. */ + protected static SchnitzlerSimulationProgram thisProgram; + + /** + * Main method is first executed when a program instance is loaded. + * @see <a href="https://docs.oracle.com/javase/tutorial/getStarted/application/index.html" target="_blank">Java Tutorials: A Closer Look at the "Hello World!" Application</a> + * @param args command-line parameters: network address and port. + * Command-line arguments are an array of optional String parameters that are passed from execution environment during invocation + */ + public static void main(String[] args) + { + thisProgram = new SchnitzlerSimulationProgram("test constructor"); // create instance of self within static main() method + + thisProgram.disChannel.printlnTRACE("main() started..."); + + thisProgram.handleArguments(args); // process any command-line invocation arguments + + thisProgram.runSimulationLoops(); // ... your simulation execution code goes in there ... + + thisProgram.disChannel.tearDownNetworkInterface(); // make sure no processes are left lingering + + thisProgram.disChannel.printlnTRACE("complete."); // report successful completion + + System.exit(0); // ensure all threads and sockets released + } +} diff --git a/assignments/src/MV3500Cohort2024JulySeptember/homework3/Schnitzler/SimpleMoverOpenDis7.java b/assignments/src/MV3500Cohort2024JulySeptember/homework3/Schnitzler/SimpleMoverOpenDis7.java new file mode 100644 index 0000000000000000000000000000000000000000..7b5a9f439a5b4f8b83201a230057eada3fe4dd6b --- /dev/null +++ b/assignments/src/MV3500Cohort2024JulySeptember/homework3/Schnitzler/SimpleMoverOpenDis7.java @@ -0,0 +1,403 @@ +package MV3500Cohort2024JulySeptember.homework3.Schnitzler; + +import edu.nps.moves.dis7.entities.deu.platform.land.Leopard2A7PLUS; +import edu.nps.moves.dis7.enumerations.ForceID; +import edu.nps.moves.dis7.pdus.EntityID; +import edu.nps.moves.dis7.pdus.EntityStatePdu; +import edu.nps.moves.dis7.pdus.Vector3Float; +import edu.nps.moves.dis7.utilities.DisChannel; +import edu.nps.moves.dis7.utilities.PduFactory; +import java.awt.geom.Point2D; +import static java.lang.Double.NaN; +import simkit.Schedule; +import simkit.SimEntityBase; + +/** + * Represent a unit that moves with constant velocity. Its responsibilities are + * to maintain its state and respond to events directing it to perform only + * maneuver, specifically to move to a given waypoint at its maximum speed. + * + * @author simonschnitzler + */ +public class SimpleMoverOpenDis7 extends SimEntityBase { + + /** max time between two ESPDUs */ + public static final double HEARTBEATTIME = 5.0; + + /** 2D Vector with NaN as values */ + public static final Point2D NaP = new Point2D.Double(NaN, NaN); + + private Point2D initialLocation; + private double maxSpeed; + + /** Dis extension */ + private DisChannel disChannel; + + /** last stop location of the simpleMover */ + protected Point2D lastStopLocation; + /** current velocity of the mover */ + protected Point2D velocity; + /** current destination of the simpleMover */ + protected Point2D destination; + /** start move time of the simpleMover */ + protected double startMoveTime; + + /** EntityID settings for simpleMover */ + private EntityID entityID = new EntityID(); + + /** ESPDU for simpleMover */ + private EntityStatePdu entityStatePdu; + + /** PduFactory */ + private PduFactory pduFactory; + + + + + /** + * Instantiate lastStopLocation, velocity, and destination as NaP each + */ + public SimpleMoverOpenDis7() { + lastStopLocation = (Point2D) NaP.clone(); + velocity = (Point2D) NaP.clone(); + destination = (Point2D) NaP.clone(); + } + + /** + * Instantiate a new SimpleMover with the given initialLocation and + * maxSpeed. + * + * @param initialLocation Given initial location of this mover + * @param maxSpeed Given maximum speed of mover + */ + public SimpleMoverOpenDis7(Point2D initialLocation, double maxSpeed) { + this(); + setInitialLocation(initialLocation); + setMaxSpeed(maxSpeed); + } + + /** + * Instantiate a new SimpleMover with the given initialLocation and + * maxSpeed. + * + * @param initialLocation Given initial location of this mover + * @param maxSpeed Given maximum speed of mover + * @param disChannel Given DisChannel to use for send ESPDU + */ + public SimpleMoverOpenDis7(Point2D initialLocation, double maxSpeed, DisChannel disChannel) { + this(initialLocation, maxSpeed); + setDisChannel(disChannel); + initializeEntity(); + } + + /** + * Initialize simulation entity for the simpleMover. + */ + private void initializeEntity(){ + if (pduFactory == null) + pduFactory = disChannel.getPduFactory(); + entityStatePdu = pduFactory.makeEntityStatePdu(); + + // Your model setup: define participants. who's who in this zoo? + // Assuming you keep track of entity objects... here is some support for for Entity 1. + + // PDU objects are already declared and instances created, so now set their values. + // who is who in our big zoo, sufficient for global participation if we need it + entityID.setSiteID(1).setApplicationID(2).setEntityID(3);// made-up example ID; + disChannel.addEntity(entityID); + + + entityStatePdu.setEntityID(entityID); + entityStatePdu.setForceId(ForceID.FRIENDLY); + entityStatePdu.setEntityType(new Leopard2A7PLUS()); + entityStatePdu.setMarking("Mover #1"); + entityStatePdu.getMarkingString(); // use Netbeans Debug breakpoint here to check left justified... + } + + private void sendESPDU(){ + Point2D currentLocation = getCurrentLocation(); + entityStatePdu.getEntityLocation().setX(currentLocation.getX()); + entityStatePdu.getEntityLocation().setY(currentLocation.getY()); + entityStatePdu.getEntityLocation().setZ(0.0); + Point2D currentVelocity = getVelocity(); + Vector3Float currentVelocityFloat = new Vector3Float(); + currentVelocityFloat.setX((float)currentVelocity.getX()); + currentVelocityFloat.setY((float)currentVelocity.getY()); + currentVelocityFloat.setZ(0.0f); + entityStatePdu.setEntityLinearVelocity(currentVelocityFloat); + entityStatePdu.setTimestampSeconds(Schedule.getSimTime()); + + disChannel.sendSinglePdu(entityStatePdu); + } + + /** + * Set lastStopLocation to initialLocation<br> + * set velocity to (0,0)<br> + * set destination to NaP<br> + * set startMoveTime to the current SimTime + */ + @Override + public void reset() { + super.reset(); + lastStopLocation.setLocation(getInitialLocation()); + velocity.setLocation(0.0, 0.0); + destination.setLocation(NaP); + startMoveTime = Schedule.getSimTime(); + } + + /** + * Fire property changes for all state variables with the initial values. + */ + public void doRun() { + firePropertyChange("lastStopLocation", getLastStopLocation()); + firePropertyChange("velocity", getVelocity()); + firePropertyChange("destination", getDestination()); + firePropertyChange("startMoveTime", getStartMoveTime()); + + sendESPDU(); + waitDelay("HeartBeat", HEARTBEATTIME, this); + } + + /** + * If no changes happen, a heartbeat is send. + * + * @param mover which is meant to send a heartbeat + */ + public void doHeartBeat(SimpleMoverOpenDis7 mover){ + sendESPDU(); + + waitDelay("HeartBeat", HEARTBEATTIME, mover); + } + + /** + * set destination to the given destination and firePropertyChange<br> + * schedule StartMove(this) if the distance to the destination is greater + * than 0.0 + * + * @param destination the new destination for the mover + */ + public void doMoveTo(Point2D destination) { + Point2D oldDestination = getDestination(); + this.destination.setLocation(destination); + firePropertyChange("destination", oldDestination, getDestination()); + + if (lastStopLocation.distance(destination) > 0.0) { + waitDelay("StartMove", 0.0, this); + } + } + + /** + * Compute the distance to the destination <br> + * compute the moveTime to destination <br> + * compute velocity with maxSpeed and direction to the destination <br> + * set startMoveTime to the current SimTime <br> + * Schedule an EndMove event with the given mover as parameter and the delay + * of the computed moveTime + * + * @param mover instance which starts its move + */ + public void doStartMove(SimpleMoverOpenDis7 mover) { + double distance = lastStopLocation.distance(destination); + double moveTime = distance / getMaxSpeed(); + + Point2D oldVelocity = getVelocity(); + this.velocity.setLocation((destination.getX() - lastStopLocation.getX()) / moveTime, + (destination.getY() - lastStopLocation.getY()) / moveTime); + firePropertyChange("velocity", oldVelocity, getVelocity()); + + double oldStartMoveTime = getStartMoveTime(); + startMoveTime = Schedule.getSimTime(); + firePropertyChange("startMoveTime", oldStartMoveTime, getStartMoveTime()); + + waitDelay("EndMove", moveTime, mover); + + interrupt("HeartBeat", mover); + sendESPDU(); + waitDelay("HeartBeat", HEARTBEATTIME, mover); + + } + + /** + * Set lastStopLocation to the destination<br> + * set velocity to 0.0<br> + * set destination to NaP + * + * @param mover instance which ends its move + */ + public void doEndMove(SimpleMoverOpenDis7 mover) { + Point2D oldLastStopLocation = getLastStopLocation(); + lastStopLocation.setLocation(getDestination()); + firePropertyChange("lastStopLocation", oldLastStopLocation, getLastStopLocation()); + + Point2D oldVelocity = getVelocity(); + velocity.setLocation(0.0, 0.0); + firePropertyChange("velocity", oldVelocity, getVelocity()); + + Point2D oldDestination = getDestination(); + destination.setLocation(NaP); + firePropertyChange("destination", oldDestination, getDestination()); + + interrupt("HeartBeat", mover); + sendESPDU(); + waitDelay("HeartBeat", HEARTBEATTIME, mover); + } + + /** + * Schedule an OrderStop event with this mover instance as parameter and a + * delay of 0.0 + */ + public void doOrderStop() { + waitDelay("Stop", 0.0, this); + } + + /** + * Set lastStopLocation to the currentLocation<br> + * set startMoveTime to the current SimTime<br> + * set velocity to 0.0<br> + * cancel the EndMove event for the given mover instance + * + * @param mover instance which stops its move + */ + public void doStop(SimpleMoverOpenDis7 mover){ + Point2D oldLastStopLocation = getLastStopLocation(); + lastStopLocation.setLocation(getCurrentLocation()); + firePropertyChange("lastStopLocation", oldLastStopLocation, getLastStopLocation()); + + double oldStartMoveTime = getStartMoveTime(); + startMoveTime = Schedule.getSimTime(); + firePropertyChange("startMoveTime", oldStartMoveTime, getStartMoveTime()); + + Point2D oldVelocity = getVelocity(); + velocity.setLocation(0.0, 0.0); + firePropertyChange("velocity", oldVelocity, getVelocity()); + + interrupt("EndMove",mover); + + interrupt("HeartBeat", mover); + sendESPDU(); + waitDelay("HeartBeat", HEARTBEATTIME, mover); + } + + /** + * Return current location. + * + * @return this mover's current location based on linear equation of motion + */ + public Point2D getCurrentLocation() { + double elapsedTime = Schedule.getSimTime() - getStartMoveTime(); + return new Point2D.Double(lastStopLocation.getX() + elapsedTime * velocity.getX(), + lastStopLocation.getY() + elapsedTime * velocity.getY()); + } + + /** + * Return initial location. + * @return initialLocation + */ + public Point2D getInitialLocation() { + return (Point2D) initialLocation.clone(); + } + + /** + * Set initial Location + * + * @param initialLocation initial location of the mover. + */ + public void setInitialLocation(Point2D initialLocation) { + this.initialLocation = (Point2D) initialLocation.clone(); + } + + /** + * Get max speed. + * + * @return maxSpeed of the mover. + */ + public double getMaxSpeed() { + return maxSpeed; + } + + /** + * Set max speed + * + * @param maxSpeed of the mover. + * @throws IllegalArgumentException if the speed is negative. + */ + public void setMaxSpeed(double maxSpeed) { + if (maxSpeed < 0.0) { + throw new IllegalArgumentException("maxSpeed must be >= 0.0: " + maxSpeed); + } + this.maxSpeed = maxSpeed; + } + + /** + * Get last stop location. + * + * @return lastStopLocation of the mover. + */ + public Point2D getLastStopLocation() { + return (Point2D) lastStopLocation.clone(); + } + + /** + * Get velocity. + * + * @return velocity of the mover. + */ + public Point2D getVelocity() { + return (Point2D) velocity.clone(); + } + + /** + * Get destination. + * + * @return destination of the mover. + */ + public Point2D getDestination() { + return (Point2D) destination.clone(); + } + + /** + * Get start move time. + * + * @return startMoveTime time since the last StartMove event. + */ + public double getStartMoveTime() { + return startMoveTime; + } + + /** + * Get dis channel. + * + * @return disChannel of the simulation. + */ + public DisChannel getDisChannel() { + return disChannel; + } + + + /** + * Set DisChannel. + * + * @param disChannel of the simulation. + * @throws IllegalArgumentException if the disChannel is null. + */ + public void setDisChannel(DisChannel disChannel) { + if (disChannel == null) { + throw new IllegalArgumentException("disChannel is: " + disChannel); + } + this.disChannel = disChannel; + } + + /** + * Override normal toString() to return current location and velocity + * + * @return Name (current location, current velocity) + */ + @Override + public String toString() { + Point2D currentLocation = getCurrentLocation(); + return String.format("%s (%,.3f, %,.3f) [%,.3f, %,.3f]", getName(), + currentLocation.getX(), currentLocation.getY(), + velocity.getX(), velocity.getY()); + } + +} diff --git a/assignments/src/MV3500Cohort2024JulySeptember/homework3/Schnitzler/SimplePathMoverManagerOpenDis7.java b/assignments/src/MV3500Cohort2024JulySeptember/homework3/Schnitzler/SimplePathMoverManagerOpenDis7.java new file mode 100644 index 0000000000000000000000000000000000000000..3860affbdfed615d1fdd0ae130276e1edfa77332 --- /dev/null +++ b/assignments/src/MV3500Cohort2024JulySeptember/homework3/Schnitzler/SimplePathMoverManagerOpenDis7.java @@ -0,0 +1,175 @@ +package MV3500Cohort2024JulySeptember.homework3.Schnitzler; + +import java.awt.geom.Point2D; +import simkit.SimEntityBase; + +/** + * Represent a move manager. Each instance of SimplePathMoverManager will + * control a single SimpleMover, directing it along a pre-determined list of + * waypoints (Point2D instances). This component has only one state variable + * next. + * + * @author simonschnitzler + */ +public class SimplePathMoverManagerOpenDis7 extends SimEntityBase { + + private Point2D[] path; + private boolean startOnRun; + + /** next waypoint index */ + protected int nextWaypointIndex; + + /** + * Zero-argument constructor + */ + public SimplePathMoverManagerOpenDis7() { + } + + /** + * Instantiate with given parameters + * + * @param path given path + * @param startOnRun if startOnRun is true, start from Run event + */ + public SimplePathMoverManagerOpenDis7(Point2D[] path, boolean startOnRun) { + this(); + setPath(path); + setStartOnRun(startOnRun); + } + + /** + * Set nextWaypointIndex to the initial value 0; + */ + @Override + public void reset() { + super.reset(); + nextWaypointIndex = 0; + } + + /** + * Fire property change for the state variable nextWaypointIndex<br> + * If startOnRun is true schedule a MoveTo event with the waypoint at index + * nextWaypointIndex as parameter and with a delay of 0.0. + */ + public void doRun() { + firePropertyChange("nextWaypointIndex", getNextWaypointIndex()); + + if (isStartOnRun()) { + waitDelay("MoveTo", 0.0, getPath(getNextWaypointIndex())); + } + } + + /** + * Does nothing - meant to be "heard" by another component + * + * @param waypoint which is the next destination + */ + public void doMoveTo(Point2D waypoint) { + } + + /** + * Add 1 to the nextWaypointIndex and fire a property change<br> + * If the new nextWaypointIndex is less than the length of the path, then + * schedule a MoveTo event with a delay of 0.0 and the waypoint at the index + * of the new nextWaypointIndex as parameter.<br> + * If the new nextWaypointIndex is equal to the length of the path, then + * schedule a Stop event with a delay of 0.0 + * + * @param mover which ends its move + */ + public void doEndMove(SimpleMoverOpenDis7 mover) { + int oldNextWaypointIndex = getNextWaypointIndex(); + nextWaypointIndex += 1; + firePropertyChange("nextWaypointIndex", oldNextWaypointIndex, getNextWaypointIndex()); + + if (getNextWaypointIndex() < path.length) { + waitDelay("MoveTo", 0.0, getPath(getNextWaypointIndex())); + } + if (getNextWaypointIndex() == path.length) { + waitDelay("Stop", 0.0); + } + } + + /** + * Schedule an OrderStop event with a delay of 0.0 + */ + public void doStop(){ + waitDelay("OrderStop",0.0); + } + + /** + * Does nothing - meant to be "heard" by another component + */ + public void doOrderStop(){ + } + + /** + * Get path. + * @return the path + */ + public Point2D[] getPath() { + return path.clone(); + } + + /** + * Get waypoint at given index of the path. + * + * @param index of the waypoint + * @return the waypoint of the path at the given index + */ + public Point2D getPath(int index) { + return (Point2D) path[index].clone(); + } + + /** + * Set the path. + * + * @param path the path to set + * @throws IllegalArgumentException if the path length is not greater than 0 + */ + public void setPath(Point2D[] path) { + if (path.length > 0) { + this.path = path.clone(); + } else { + throw new IllegalArgumentException("path must be at least lengt 1: " + path.length); + } + } + + /** + * Set the waypoint of the path at a given index. + * + * @param index of the waypoint to set + * @param path the waypoint of the path to set at the given index + */ + public void setPath(int index, Point2D path) { + this.path[index] = (Point2D) path.clone(); + } + + /** + * Get startOnRun Boolean. + * + * @return startOnRun + */ + public boolean isStartOnRun() { + return startOnRun; + } + + /** + * Set startOnRunBoolean. + * + * @param startOnRun boolean if should start from the beginning. + */ + public void setStartOnRun(boolean startOnRun) { + this.startOnRun = startOnRun; + } + + /** + * Get next waypoint index. + * + * @return nextWaypointIndex + */ + public int getNextWaypointIndex() { + return nextWaypointIndex; + } + +} diff --git a/assignments/src/MV3500Cohort2024JulySeptember/homework3/Schnitzler/package-info.java b/assignments/src/MV3500Cohort2024JulySeptember/homework3/Schnitzler/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..308438e0b4c67ec9201b01020d7a30f345aed88f --- /dev/null +++ b/assignments/src/MV3500Cohort2024JulySeptember/homework3/Schnitzler/package-info.java @@ -0,0 +1,10 @@ +/** + * Final project assignments supporting the NPS MOVES MV3500 Networked Graphics course. + * + * @see <a href="https://gitlab.nps.edu/Savage/NetworkedGraphicsMV3500/-/tree/master/assignments" target="_blank">networkedGraphicsMV3500 assignments</a> + * @see java.lang.Package + * @see <a href="https://stackoverflow.com/questions/22095487/why-is-package-info-java-useful" target="_blank">StackOverflow: why-is-package-info-java-useful</a> + * @see <a href="https://stackoverflow.com/questions/624422/how-do-i-document-packages-in-java" target="_blank">StackOverflow: how-do-i-document-packages-in-java</a> + */ + +package MV3500Cohort2024JulySeptember.homework3.Schnitzler;