Development

image

Getting Started

If you have never done any development for Dysruption and would like a step-by-step guide on how to launch development and production builds of the game, click here.

Known Issues

Dependencies

  • Docker – used for local development and for packaging releases

  • Node – used for the game server, the game client, and AWS dependencies

  • AWS CLI – used mainly by the AWS CDK, but also for AWS authentication

  • AWS CDK – used to manage the AWS resources used by the system

  • Git – access to project source code

  • Angular – used by the Front End (client)

  • Angular CLI – used to generate new front end components and run the development Angular server

  • QGIS – used to modify the geospatial database features

Docker login for command line access to stored Dysruption Docker images:

docker login registry.gitlab.nps.edu/moves/dysruption:latest

Create and Publish a New Release

Edit Version Strings

From the develop branch, modify the following files with the new version string:

  • shared/app-version.ts

  • package.json

  • client/package.json

  • shared/package.json

Edit cdk/config.txt

Add a unique identifier tag in this file. To be used with the naming of all the CDK generated resources. (e.g. “DEMO”)

Install Packages

Root Folder:

npm run init

CDK Folder:

npm install

Deploy CDK

cd cdk

cdk deploy --outputs-file ../server/cdk-outputs.json ProdStack

Create Git Release

  • Commit and push all above version file changes (not the config.txt)

  • From GitLab Deployments->New Release

    • Create new Tag (e.g. 0.10.0)

    • Add release title (e.g. Version 0.10.0)

    • Select a Milestone if applicable

    • Click Create Release

Data Development

This is the list of properties that are being used either on the client or server of the game that must be in the GIS data or Flow Solver models. The actual property names used in the data can have any name and be mapped to specific accessors using the Infrastructure Configuration objects.

By Data Model

Node (GIS Point Feature)

  • id: The unique id number for this Node so connected Arcs can find it

  • name: String to display to user about which Node this is

  • gridX: The x coordinate to move this Node in schematic view

  • gridY: The y coordinate to move this Node in schematic view

Arc (GIS Line Feature)

  • from: The fid of the Node this Arc is coming from

  • to: The fid of the Node this Arc is going to

  • active: Whether the Arc is enabled or disabled

Properties shared by Nodes and Arcs

  • visible: The visibility of a feature to players, 0: nowhere, 1: all views, 2: schematic only, 3: map only

  • invulnerable: Property specifying a feature that may not be attacked during a game, 1: invulnerable, 0: vulnerable

  • render_type: The icon to display for the feature

By Use Case

Creating a Game model instance

  • Node

    • id: Read by Arcs to find the Arc’s from and `to

    • name: Read by Arcs to name themselves if not given a name in GIS

    • gridx: Read by Arcs connected to this Node to determine their grid coordinates

    • gridy: Read by Arcs connected to this Node to determine their grid coordinates

  • Arc

    • from: Used to find the Node this Arc starts at to determine grid coordinates

    • to: Used to find the Node this Arc ends at to determine grid coordinates

    • active: Initial state set based on game mode - this requirement will be removed here when scenario editors can set initial property overrides for different games/modes

In order to allow customizing the infrastructures used in Dysruption scenarios without requiring a new deployment of Dysruption, configuration objects can be added to the Dysruption DynamoDB Table to define each one. A configuration object must have the following properties defined:

Configuration File

Many of the display properties for infrastructure components can be modified in the Configuration file:

dysruption/cdk/lib/configuration-construct.ts

Infrastructure Configuration

  • objectKey: Always set to INFRASTRUCTURE_TYPE

  • id: The unique identifier for which type of infrastructure this represents (e.g. fuel)

  • displayName: The name of the infrastructure shown to users in the Scenario Editor

  • mapObjectConfiguration: A Map Object Configuration (as defined below) for every single type of map object (Nodes and Arcs) with the following required accesors:

    • gisId: referencing a GIS property for the GIS ID of the map object

    • name: referencing a GIS property for the human readable name of the map object

    • visibility: referencing a GIS property for which views this object should show up in

      • 0: This object is invisible

      • 1: This object shows in all views

      • 2: This object only shows in the Schematic View

      • 3: This object only shows in the Map View

    • vulnerable: Whether this object is vulnerable to being disabled

  • nodeConfiguration: A Map Object Configuration for Node map objects (points) with the following required accesors:

    • gridX: referencing a GIS property for what x value this node will have in the schematic view

    • gridY: referencing a GIS property for what y value this node will have in the schematic view

  • arcConfiguration: A Map Object Configuration for Arc map objects (lines) with the following required accesors:

    • from: referencing a GIS property for the Node’s GIS id of where this arc starts from

    • to: referencing a GIS property for the Node’s GIS id of where this arc goes to

    • isActive: referencing a GIS property for determining if this Arc is active/enabled or not

  • flowSolverAddress: the AWS ARN address for where the AWS state machine this infrastructure uses during a simulation step

In the Dysruption system, Nodes on the map will merge the mapObjectConfiguration and nodeConfiguration objects to define what gets displayed and what actions a user can take on Nodes. Likewise, Arcs on the map will merge the mapObjectConfiguration and arcConfiguration objects.

Map Object Configuration

This object is a map between GIS, the Dysruption system, and the flow solver this infrastructure uses. It has the following properties:

  • accessors: The list of Accessors (see below) that define default values and how the Dysruption system can access GIS/Flow Solver properties

  • actions: The list of Actions (see below) a user can perform in each game mode

    • ‘0’ is Break It Bad, ‘1’ is Fix It Fast, and ‘2’ is Save Our System

  • tooltipDisplay: What Display Properties (see below) will be displayed as tooltips when a user mouses over a map object

  • mapDisplay: What Display Properties will be shown as labels on top of a map object at all times

  • detailDisplay: What Display Properties will be displayed in the inspector panel when a map object is selected

  • styleModifiers: What Style Modifiers (see below) will be applied to objects of this type

Accessors

This is a map of Properties that either have to be defined for the Dysruption system use (see above configuration objects) or to add default values for properties the flow solver uses that aren’t defined in GIS. If the accessor is one Dysruption requires, the key in the map is listed above (e.g. name for mapObjectConfiguration), otherwise the key must be unique, but has no other requirement.

Example default accessor definition for mapObjectConfiguration:

accessors: {
  gisId: { key: 'fid', customizable: false },
  name: { key: 'name', customizable: false },
  failureProbabilityOffset: { key: 'failure_probability_offset', defaultValue: 0 },
  temperature: { key: 'temperature_property', defaultValue: 72 }
}

Actions

This is the list of actions you can perform in each game mode. The actions object is a map with three keys: 0 (Break It Bad), 1 (Fix It Fast), and 2 (Save Our System). If more game modes get added, more keys will be needed in this map. Each key’s value is a list of actions for that mode.

Each action object in this list has the following properties:

  • type: A unique identifier for what type of action this is, must be unique within a game mode’s list of actions

  • displayName: The name of the action users will see in game

  • className: Space separate attributes to determine the icon and other styling of this action in game

    • Examples include dys-icon target, dys-icon wrench, dys-icon swap

  • conditions: The list of Conditions (see below) that must all be true for this action to be available for the user to perform on an object (defaults to no conditions)

  • modifications: The list of Property (see below) changes that will be applied when this action is performed

    • property: The Property whose value will be set

    • value: The value the Property will be set to

  • cost: How much of a user’s budget will be used when this action is performed (defaults to 500)

Example default actions for arcObjectConfiguration:

actions: {
  "0": [
    { type: 'disable', displayName: 'Attack', className: 'dys-icon target', 
      conditions: [
        { __type: 'not-equal-condition', property: { key: 'xhat' }, value: 1 }
      ], 
      modifications: [
        { property: { key: 'xhat' }, value: 1 }
      ],
      cost: 0
    }
  ],
  "1": [
    { type: 'enable', displayName: 'Enable', className: 'dys-icon wrench', 
      conditions: [
        { __type: 'not-equal-condition', property: { key: 'xhat' }, value: 0 }
      ], 
      modifications: [
        { property: { key: 'xhat' }, value: 0 }
      ],
      cost: 0
    }
  ],
  "2": [
    { type: 'repair', displayName: 'Repair', className: 'dys-icon wrench', 
      conditions: [
        { __type: 'not-equal-condition', property: { key: 'xhat' }, value: 0 },
        { __type: 'not-equal-condition', property: { key: 'fci' }, value: 100 }
      ],
      modifications: [
        { property: { key: 'xhat' }, value: 0 },
        { property: { key: 'fci' }, value: 97.0 },
        { property: { key: "skip_fci_turns" }, value: 1 }
      ],
      cost: 250 },
    { type: 'replace', displayName: 'Replace', className: 'dys-icon swap', 
      modifications: [
        { property: { key: 'xhat' }, value: 0 }, 
        { property: { key: 'fci' }, value: 100 },
        { property: { key: "skip_fci_turns" }, value: 1 }
      ],
      cost: 500 },
    { type: 'maintain', displayName: "Maintain", className: 'dys-icon checklist', 
      conditions: [
        { __type: 'not-equal-condition', property: { key: 'xhat' }, value: 1 },
      ], 
      modifications: [
        { property: { key: "skip_fci_turns" }, value: 1 }
      ],
      cost: 50
    }
  ],
}

Display Properties

Display properties all have the following properties to fill out:

  • displayName: The name that will be displayed in front of the value (defaults to undefined)

  • conditions: The list of Conditions necessary that must all be true to show this property (defaults to no conditions)

  • property: The actual Property being displayed

  • graphable: Whether this property is graphable or not (defaults to true)

Examples:

{ displayName: 'Supply', conditions: [{ __type: 'greater-than-condition', property: { key: 'supply' }, value: 0 }], property: { key: 'supply' } },
{ displayName: 'Demand', conditions: [{ __type: 'less-than-equal-condition', property: { key: 'supply' }, value: 0 }], property: { __type: 'absolute-property', key: 'supply' } }
{ property: { key: 'name' } }

Style Modifiers

These objects allow setting various flags to modify the visuals of the map object in game. These include setting the color theme for the infrastructure, the icons to use, how animations are determined, among other things.

Style modifiers have the following properties:

  • className: What flag to add to the element if all conditions are true

  • conditions: The list of Conditions (see below) that must all be true for this flag to be set (defaults to no conditions)

Examples:

// Styles for infrastructure type
{ className: type },
// Styles for on/off
{
  className: 'on',
  conditions: [
    { __type: 'equal-condition', property: { key: 'shortfall' }, value: 0 },
  ]
},
{
  className: 'off',
  conditions: [
    { __type: 'greater-than-condition', property: { key: 'shortfall' }, value: 0 },
  ]
},
// Styles for supply/demand
{
  className: 'supply',
  conditions: [
    { __type: 'greater-than-condition', property: { key: 'supply' }, value: 0 },
  ]
},
{
  className: 'demand',
  conditions: [
    { __type: 'less-than-equal-condition', property: { key: 'supply' }, value: 0 },
  ]
}

Property

This object is a map between the Dysruption system and the GIS/Flow Solver data. Based on the type of Property, the GIS value may be modified. All Property types have the following fields:

  • key: The GIS/Flow Solver property we are getting a value from or setting a value to

  • defaultValue: The default value we want to set in case the property does not exist in GIS on an object

  • customizable: Whether this property can be customized in the scenario editor or not

AbsoluteProperty

When the value is read, its absolute value will be given used instead.

Add the following fields to your configuration Property object:

  • __type: absolute-property

DifferenceProperty

When the value is read, it shows a value that’s the difference between two GIS/Flow Solver properties.

Add the following fields to your configuration Property object:

  • __type: difference-property

  • otherKey: The key of the GIS/Flow Solver property to subtract from the key property

InvertedProperty

When the value is read, it shows the opposite value (really only useful for values of 0 or 1).

Add the following fields to your configuration Property object:

  • __type: inverted-property

Conditions

These classes use Properties (see above) to check if they meet certain conditions with the current value.

GreaterThanCondition

This condition is true if the Property’s current value is higher than the Condition’s value.

This condition has the following properties:

  • __type: greater-than-condition

  • property: The Property to get the current value from

  • value: The value to compare the Property value against

Example: { __type: 'greater-than-condition', property: { key: 'supply' }, value: 0 }

GreaterThanEqualToCondition

This condition is true if the Property’s current value is higher than or equal to the Condition’s value.

This condition has the following properties:

  • __type: greater-than-equal-condition

  • property: The Property to get the current value from

  • value: The value to compare the Property value against

Example: { __type: 'greater-than-equal-condition', property: { key: 'fci' }, value: 50 }

LessThanCondition

This condition is true if the Property’s current value is lower than the Condition’s value.

This condition has the following properties:

  • __type: less-than-condition

  • property: The Property to get the current value from

  • value: The value to compare the Property value against

Example: { __type: 'less-than-condition', property: { key: 'supply' }, value: 1 }

LessThanEqualToCondition

This condition is true if the Property’s current value is lower than or equal to the Condition’s value.

This condition has the following properties:

  • __type: less-than-equal-condition

  • property: The Property to get the current value from

  • value: The value to compare the Property value against

Example: { __type: 'less-than-equal-condition', property: { key: 'fci' }, value: 100 }

EqualCondition

This condition is true if the Property’s current value is equal to the Condition’s value.

This condition has the following properties:

  • __type: equal-condition

  • property: The Property to get the current value from

  • value: The value to compare the Property value against

Example: { __type: 'equal-condition', property: { key: 'supply' }, value: 0 }

NotEqualCondition

This condition is true if the Property’s current value is not equal to the Condition’s value.

This condition has the following properties:

  • __type: not-equal-condition

  • property: The Property to get the current value from

  • value: The value to compare the Property value against

Example: { __type: 'not-equal-condition', property: { key: 'xhat' }, value: 1 }

Full Example

Here is what one of the default infrastructure type configuration objects look like:

{
  "objectType": "INFRASTRUCTURE_TYPE",
  "id": "fuel",
  "displayName": "Fuel",
  "mapObjectConfiguration": {
    "accessors": {
      "gisId": {
        "key": "fid",
        "customizable": false
      },
      "name": {
        "key": "name",
        "customizable": false
      },
      "failureProbabilityOffset": {
        "key": "failure_probability_offset",
        "defaultValue": 0
      },
      "temperature": {
        "key": "temperature_property",
        "defaultValue": 72
      }
    },
    "actions": {
      "0": [],
      "1": [],
      "2": []
    },
    "detailDisplay": [
      {
        "displayName": "Name",
        "property": {
          "key": "name"
        },
        "graphable": false
      },
      {
        "displayName": "Temperature",
        "property": {
          "key": "temperature_property"
        }
      }
    ],
    "mapDisplay": [],
    "styleModifiers": [],
    "tooltipDisplay": []
  },
  "nodeConfiguration": {
    "accessors": {
      "gridX": {
        "key": "gridx",
        "customizable": false
      },
      "gridY": {
        "key": "gridy",
        "customizable": false
      },
      "penalty": {
        "key": "penalty",
        "defaultValue": 10
      }
    },
    "actions": {
      "0": [],
      "1": [],
      "2": []
    },
    "detailDisplay": [
      {
        "displayName": "Supply",
        "property": {
          "key": "supply"
        },
        "conditions": [
          {
            "__type": "greater-than-condition",
            "property": {
              "key": "supply"
            },
            "value": 0
          }
        ]
      },
      {
        "displayName": "Demand",
        "property": {
          "__type": "absolute-property",
          "key": "supply"
        },
        "conditions": [
          {
            "__type": "less-than-equal-condition",
            "property": {
              "key": "supply"
            },
            "value": 0
          }
        ]
      },
      {
        "displayName": "Shortfall",
        "property": {
          "key": "shortfall"
        }
      },
      {
        "displayName": "Penalty",
        "property": {
          "key": "penalty"
        },
        "graphable": false
      }
    ],
    "mapDisplay": [
      {
        "property": {
          "key": "name"
        }
      }
    ],
    "styleModifiers": [
      {
        "className": "fuel"
      },
      {
        "className": "on",
        "conditions": [
          {
            "__type": "equal-condition",
            "property": {
              "key": "shortfall"
            },
            "value": 0
          }
        ]
      },
      {
        "className": "off",
        "conditions": [
          {
            "__type": "greater-than-condition",
            "property": {
              "key": "shortfall"
            },
            "value": 0
          }
        ]
      },
      {
        "className": "supply",
        "conditions": [
          {
            "__type": "greater-than-condition",
            "property": {
              "key": "supply"
            },
            "value": 0
          }
        ]
      },
      {
        "className": "demand",
        "conditions": [
          {
            "__type": "less-than-equal-condition",
            "property": {
              "key": "supply"
            },
            "value": 0
          }
        ]
      }
    ],
    "tooltipDisplay": [
      {
        "displayName": "Supply",
        "property": {
          "key": "supply"
        },
        "conditions": [
          {
            "__type": "greater-than-condition",
            "property": {
              "key": "supply"
            },
            "value": 0
          }
        ]
      },
      {
        "displayName": "Demand",
        "property": {
          "__type": "absolute-property",
          "key": "supply"
        },
        "conditions": [
          {
            "__type": "less-than-equal-condition",
            "property": {
              "key": "supply"
            },
            "value": 0
          }
        ]
      }
    ]
  },
  "arcConfiguration": {
    "accessors": {
      "from": {
        "key": "from",
        "customizable": false
      },
      "to": {
        "key": "to",
        "customizable": false
      },
      "isActive": {
        "__type": "inverted-property",
        "key": "xhat"
      }
    },
    "actions": {
      "0": [
        {
          "type": "disable",
          "displayName": "Attack",
          "className": "dys-icon target",
          "conditions": [
            {
              "__type": "not-equal-condition",
              "property": {
                "key": "xhat"
              },
              "value": 1
            }
          ],
          "modifications": [
            {
              "property": {
                "key": "xhat"
              },
              "value": 1
            }
          ],
          "cost": 0
        }
      ],
      "1": [
        {
          "type": "enable",
          "displayName": "Enable",
          "className": "dys-icon wrench",
          "conditions": [
            {
              "__type": "not-equal-condition",
              "property": {
                "key": "xhat"
              },
              "value": 0
            }
          ],
          "modifications": [
            {
              "property": {
                "key": "xhat"
              },
              "value": 0
            }
          ],
          "cost": 0
        }
      ],
      "2": [
        {
          "type": "repair",
          "displayName": "Repair",
          "className": "dys-icon wrench",
          "conditions": [
            {
              "__type": "not-equal-condition",
              "property": {
                "key": "xhat"
              },
              "value": 0
            },
            {
              "__type": "not-equal-condition",
              "property": {
                "key": "fci"
              },
              "value": 100
            }
          ],
          "modifications": [
            {
              "property": {
                "key": "xhat"
              },
              "value": 0
            },
            {
              "property": {
                "key": "fci"
              },
              "value": 97
            },
            {
              "property": {
                "key": "skip_fci_turns"
              },
              "value": 1
            }
          ],
          "cost": 250
        },
        {
          "type": "replace",
          "displayName": "Replace",
          "className": "dys-icon swap",
          "modifications": [
            {
              "property": {
                "key": "xhat"
              },
              "value": 0
            },
            {
              "property": {
                "key": "fci"
              },
              "value": 100
            },
            {
              "property": {
                "key": "skip_fci_turns"
              },
              "value": 1
            }
          ],
          "cost": 500
        },
        {
          "type": "maintain",
          "displayName": "Maintain",
          "className": "dys-icon checklist",
          "conditions": [
            {
              "__type": "not-equal-condition",
              "property": {
                "key": "xhat"
              },
              "value": 1
            }
          ],
          "modifications": [
            {
              "property": {
                "key": "skip_fci_turns"
              },
              "value": 1
            }
          ],
          "cost": 50
        }
      ]
    },
    "detailDisplay": [
      {
        "displayName": "Flow",
        "property": {
          "__type": "absolute-property",
          "key": "flow"
        }
      },
      {
        "displayName": "FCI",
        "property": {
          "key": "fci"
        }
      }
    ],
    "mapDisplay": [],
    "styleModifiers": [
      {
        "className": "fuel"
      },
      {
        "className": "on",
        "conditions": [
          {
            "__type": "equal-condition",
            "property": {
              "key": "xhat"
            },
            "value": 0
          }
        ]
      },
      {
        "className": "off",
        "conditions": [
          {
            "__type": "equal-condition",
            "property": {
              "key": "xhat"
            },
            "value": 1
          }
        ]
      },
      {
        "className": "forward",
        "conditions": [
          {
            "__type": "less-than-condition",
            "property": {
              "key": "flow"
            },
            "value": 0
          }
        ]
      },
      {
        "className": "reverse",
        "conditions": [
          {
            "__type": "greater-than-condition",
            "property": {
              "key": "flow"
            },
            "value": 0
          }
        ]
      },
      {
        "className": "stopped",
        "conditions": [
          {
            "__type": "equal-condition",
            "property": {
              "key": "flow"
            },
            "value": 0
          }
        ]
      },
      {
        "className": "darkness-fade-low",
        "conditions": [
          {
            "__type": "greater-than-equal-condition",
            "property": {
              "key": "fci"
            },
            "value": 0
          },
          {
            "__type": "less-than-condition",
            "property": {
              "key": "fci"
            },
            "value": 25
          }
        ]
      },
      {
        "className": "darkness-fade-mid",
        "conditions": [
          {
            "__type": "greater-than-equal-condition",
            "property": {
              "key": "fci"
            },
            "value": 25
          },
          {
            "__type": "less-than-condition",
            "property": {
              "key": "fci"
            },
            "value": 50
          }
        ]
      },
      {
        "className": "darkness-fade-high",
        "conditions": [
          {
            "__type": "greater-than-equal-condition",
            "property": {
              "key": "fci"
            },
            "value": 50
          },
          {
            "__type": "less-than-condition",
            "property": {
              "key": "fci"
            },
            "value": 75
          }
        ]
      },
      {
        "className": "darkness-fade-max",
        "conditions": [
          {
            "__type": "greater-than-equal-condition",
            "property": {
              "key": "fci"
            },
            "value": 75
          }
        ]
      }
    ],
    "tooltipDisplay": [
      {
        "displayName": "Flow",
        "property": {
          "__type": "absolute-property",
          "key": "flow"
        }
      },
      {
        "displayName": "FCI",
        "property": {
          "key": "fci"
        }
      }
    ]
  },
  "flowSolverAddress": "arn:aws:states:us-west-1:252182408753:stateMachine:APIStateMachineauthor1F103352-9YHjOS78OMcW"
}

Building Images for GitLab

To build an image that will be pushed to the Dysruption Gitlab Registry

docker build registry.gitlab.nps.edu/moves/dysruption:<version> .

Or to re-tag an existing image

docker tag <existing image> registry.gitlab.nps.edu/moves/dysruption:<version>

For example, docker tag dysruption:latest registry.gitlab.nps.edu/moves/dysruption:1.0.0

To build an image with multi-platform support

First, create and use a custom Docker Builder called “mybuilder”. This only needs to happen once.

docker buildx create --name mybuilder --driver docker-container --bootstrap --use

Then, create, tag, and push to the Gitlab Container Repository in one swell foop:

docker buildx build --platform linux/amd64,linux/arm64 -t <image>:<tab> --push .

For example, to build an image for the QGIS Server for both AMD64 and ARM64 and push to the Gitlab repository:

docker buildx build --platform linux/amd64,linux/arm64 -t registry.gitlab.nps.edu/moves/dysruption/qgis_server:latest --push .

Note: The Gitlab Container Registry web page will not display this image correctly in the UI, even though it is stored and accessed correctly.

Login to Docker before pulling or pushing images to the Gitlab repository

docker login registry.gitlab.nps.edu/moves/dysruption:<version>

Push the tagged image to Gitlab (requires Docker Login)

docker push registry.gitlab.nps.edu/moves/dysruption:<version>

For example, docker push registry.gitlab.nps.edu/moves/dysruption:1.0.0

Pull an image from Gitlab (requires Docker Login)

docker pull registry.gitlab.nps.edu/moves/dysruption:<version>