Teaches: automation, JSON, REST APIs, Python, curl, httpie.
The earth is in danger!
We've detected multiple near earth objects (‘NEO’s) on a collision course with the earth. But we're not defenseless. Surrounding the earth we've launched multiple orbital defense platforms. It is your job to control these platforms and defend earth.
There are multiple objects (‘rocks’) spiralling toward the earth. Your orbital defense platforms (‘ODP’s) have telescopes and railguns. The telescopes can image the sky one ‘octant’ at a time. The railguns can fire ‘slugs’ to shoot down the ‘rocks.’
- the earth is a point mass located at
(0, 0, 0)in(x, y, z)space - orbital defense platforms are located at
(0, 0, 0)in(x, y, z)space- they orbit quickly enough that they can fire in any direction at any time
- they orbit quickly enough that they can image the sky in any direction at any time
- each rock moves on a simple trajectory
- these trajectories are ballistic and inertialess (no thrust)
- these trajectories will spiral around the earth until impact
- they are unaffected by gravitational forces
- the trajectories are modelled by linear equations in spherical coördinates
- each slug moves on a linear (straight line) trajectory from the earth
- slugs can be aimed in any direction
- these trajectories are ballistic and inertialess (no thrust)
- slugs cannot turn; they move in straight lines only
- the trajectories are modelled by linear equations in spherical coördinates
- slugs collide only with other rocks
- slugs cannot collide with other slugs, and rocks cannot collide with other rocks
- if the rock has a mass of 1, then collision with a slug vaporizes both the rock and slug immediately
- if the rock has a mass greater than 1, then collision with a slug vaporizes the slug and rock but a rock fragment with smaller mass will be created
The orbital defense platforms can be controlled via JSON REST APIs. There are two endpoints.
The server can be found at: http://neocrisis.xyz
# install https://httpie.org/
$ http get http://neocrisis.xyz/help/
$ http get http://neocrisis.xyz/telescope/help/
$ http get http://neocrisis.xyz/railgun/help/
Instructions for how to request or send info to the server is below.
| Verb | endpoint URL | params | description |
|---|---|---|---|
| GET | /info |
gives information about the orbital weapon station | |
| GET | /telescope/<int:octant> |
octant from [1, 8] |
images the specified octant (Ⅰ-Ⅷ) of the night sky and returns NEOs it sees |
| POST | /railgun |
name, string (optional)target, string phi, numbertheta, numberfired, string (optional) |
fires a slug named name intending to hit target at the specified angles theta and phi, optionally specifying the future fired time at which to fire the slug as HH:MM:SS (for precise timing purposes) |
The /telescope endpoint returns a JSON structure that looks like:
{ "objects": [ obj, … ] }
Each obj is a map with the following fields:
| field | description |
|---|---|
| type | the tye of object: rock or slug |
| name | the name of the object |
| mass | the mass of the object |
| fired | when the rock was fired at the earth or when the slug was fired into space |
| pos | the spherical coördinates of the object at observation time |
| cpos | the Caresian coördinates of the object at observation time |
| obs_time | the observation time |
| octant | the octant (Ⅰ, Ⅱ, Ⅲ, Ⅳ, Ⅴ, Ⅵ, Ⅶ, Ⅷ) as an integer in which the object was spotted |
| age | the age of the object (how long it has been around since fired) in seconds |
Use curl or http (https://github.com/jakubroztocil/httpie/) to experiment with this API. Examples:
Take an image of octant one (Ⅰ) to search for objects.
$ http GET http://neocrisis.xyz/telescope/1HTTP/1.0 200 OK
Content-Length: 20
Content-Type: application/json
Date: Wed, 4 Jul 2018 09:00:00 EST
Server: Werkzeug/0.14.1 Python/3.6.6
{
"objects": [
{
"age": 3600,
"cpos": {
"x": 0.0,
"y": 0.0,
"z": 100.0,
},
"fired": "2018-07-04T08:00:00.000000-04:00",
"mass": 2,
"name": "99942 apophis",
"obs_time": "2018-07-04T09:00:00.000000-04:00",
"octant": 1,
"pos": {
"phi": 0.0,
"r": 100.0,
"theta": 0.0
},
"type": "rock"
}
]
}The above output shows one Near Earth Object: an asteroid (note the type ‘rock’) named ‘99942 apophis.’ It's on a direct course to hit the earth, unlike the real 99942 Apophis awhich has only a 2.7% chance of hitting earth on April 13, 2029 (https://en.wikipedia.org/wiki/99942_Apophis)
Next, we can fire a railgun slug at 99942 apophis. To better keep track of this new object, we can name it. We can name it anything we like. We'll name it ‘100 @ apophis’ to indicate what we intended to aim at.
$ http POST http://neocrisis.xyz/railgun name='100 @ apophis' theta:=0 phi:=0HTTP/1.0 200 OK
Content-Length: 75
Content-Type: application/json
Date: Wed, 4 Jul 2018 09:00:01 EST
Server: Werkzeug/0.14.1 Python/3.6.6
{
"slug": {
"name": "100 @ apophis",
"phi": 0.0,
"theta": 0.0
}
}Image octant one (Ⅰ) again to see both the rock and the railgun slug we fired at it.
$ http GET http://neocrisis.xyz/telescope/1HTTP/1.0 200 OK
Content-Length: 20
Content-Type: application/json
Date: Wed, 4 Jul 2018 09:00:05 EST
Server: Werkzeug/0.14.1 Python/3.6.6
{
"objects": [
{
"age": 3600,
"cpos": {
"x": 0.0,
"y": 0.0,
"z": 95.0,
},
"fired": "2018-07-04T08:00:00.000000-04:00",
"mass": 2,
"name": "99942 apophis",
"obs_time": "2018-07-04T09:00:05.000000-04:00",
"octant": 1,
"pos": {
"phi": 0.0,
"r": 95.0,
"theta": 0.0
},
"type": "rock"
},
{
"age": 4,
"cpos": {
"x": 0.0,
"y": 0.0,
"z": 4.0,
},
"fired": "2018-07-04T09:00:01.000000-04:00",
"mass": 1,
"name": "100 @ apophis",
"obs_time": "2018-07-04T09:00:05.000000-04:00",
"octant": 1,
"pos": {
"phi": 0.0,
"r": 4.0,
"theta": 0.0
},
"type": "slug"
}
]
}See docs/trajectories.pdf for more information.
The position of slugs and rocks can be described in terms of Cartesian
coördinates (x, y, z) or spherical coördinates (r, θ, φ).
| Cartesian coördinate | measures | range & units | directional convention |
|---|---|---|---|
x |
distance | [0, ∞) meters | from the earth's core toward the 0° Prime Meridian (Greenwich, UK) |
y |
distance | [0, ∞) meters | from the earth's core toward the 90° meridian (Memphis, TN) |
z |
distance | [0, ∞) meters | from the earth's core toward 0° latitutde (North Pole) |
Note that our convention is left-handed, as opposed to the traditional right-handed coordniate system seen the graphics below. This is because our positive y direction is west, instead of east; in other words, their +y is our -y.
| spherical coördinate | measures | range & units | directional convention |
|---|---|---|---|
r ‘radius’ |
distance | [0, ∞) meters | from the earth's core toward outer space |
θ ‘theta’, ‘azimuth’ |
angle | [0, 2π] radians | east-to-west (longitudinally) from the 0° Prime Meridian (Greenwich, UK) toward the 90° meridian (Memphis, TN) |
φ ‘phi’, ‘inclination’ |
angle | [0, π] radians | north-to-south (latitudinally) from the North Pole to the South Pole |
| number | roman numeral | x-sign |
y-sign |
z-sign |
|---|---|---|---|---|
| 1 | I | + |
+ |
+ |
| 2 | II | - |
+ |
+ |
| 3 | III | - |
- |
+ |
| 4 | IV | + |
- |
+ |
| 5 | V | + |
+ |
- |
| 6 | VI | - |
+ |
- |
| 7 | VII | - |
- |
- |
| 8 | VIII | + |
- |
- |
| NEO | params | r | phi | theta |
|---|---|---|---|---|
| slugs | v velocityphi fixed at fire timetheta fixed at fire time |
r = v × t |
phi |
theta |
| rocks | v velocityr₀ initial radiusmφ phi-slopebφ phi-interceptmθ theta-slopebθ theta-intercept |
r = v × t + r₀ |
phi = mφ × t + bφ |
theta = mθ × t + bθ |
The position of an object is determined only by its parameters and t, time.
Hints:
- the above equations are independent of each other: r, phi, and theta do not affect each other.
- for the rocks, each equation has only two unknowns; therefore, you only need to take two measurements to determine the unknowns
To compute the trajectory of a rock, take two measurements of its position in spherical coördinates (r, θ, φ).
- Two measurements are taken at
t1andt2.- call them
(r1, phi1, theta1)and(r2, phi2, theta2)
- call them
- We want to solve for
vrockandr0. Note: unlike a real asteroid, these asteroids' distances to the earth vary linearly. You need only basic algebra to determine a firing solution. No calculus is needed. They do not accelerate: their velocities are constant at all times.- we know that
r1 = vrock × t1 + r0andr2 = vrock × t2 + r0 - therefore,
v = (r1 - r2) ÷ (t1 - t2) - therefore,
r0 = r1 - vrock × t1orr0 = r2 - vrock × t2 - Remember, velocity is a vector (signed) quantity! Since the rock is moving TOWARDS the origin (in other words,
rrock(t)is becoming smaller over time), its velocity will be negative
- we know that
- We also want to solve for
mφandbφ.- we know that
phi1 = mφ × t1 + bφandphi2 = mφ × t2 + bφ - therefore,
mφ = (phi1 - phi2) ÷ (t1 - t2) - therefore,
bφ = phi1 - mφ × t1orbφ = phi2 - mφ × t2
- we know that
- Finally, we want to solve for
mθandbθ.- we know that
theta1 = mθ × t1 + bθandtheta2 = mθ × t2 + bθ - therefore,
mθ = (phi1 - phi2) ÷ (t1 - t2) - therefore,
bθ = phi1 - mθ × t1orbθ = phi2 - mθ × t2
- we know that
- We need to compute direction in which to fire the slug; its
phiandthetaangles- we're given the slug's
vslugvelocity - we know that at collision time
tcollide, thatrrockandrslugwill be the same:rslug = rrock = rcollide - we know
rcollide = vrock × tcollide + r0andrslug, collide = vslug × tcollide - therefore,
tcollide = r0 ÷ (vrock - vslug) - knowing the collision time, we can determine the firing angles
- therefore,
phicollide = mφ × tcollide + bφandthetacollide = m0 × tcollide + b0
- we're given the slug's
Altogether:
# solve for v and r0
v = (r1 - r2) ÷ (t1 - t2)
r0 = r1 - v × t1
r0 = r2 - v × t2
# solve for mφ and bφ
mφ = (phi1 - phi2) ÷ (t1 - t2)
bφ = phi1 - mφ × t1, bφ = phi2 - mφ × t2
# solve for mθ and bθ
mθ = (phi1 - phi2) ÷ (t1 - t2)
bθ = phi1 - mθ × t1, bθ = phi2 - mθ × t2
# solve for tcollide
tcollide = r0 ÷ (vrock - vslug)
# solve for phicollide and thetacollide
phicollide = mφ × tcollide + bφ
thetacollide = m0 × tcollide + b0
The game engine lives entirely in the data model of a Postgres database.
- the data model is at engine/model.sql
- sample (test) data can be found at engine/data.sql
- simple smoke tests (using the sample data) can be found at engine/checks.sql
- the data model is made bitemporal via engine/bitemporal
There are two schemas:
gamewhich contains the game core dataapiwhich contains views used by the API
The major tables are:
game.rockswhich contains all rocks for a given game (incl. those that have collided with a slug or the earth)game.slugswhich contains all slugs for a given game (incl. those that have collided with a rock)game.collisionswhich contains all collisions between all rocks and all slugs (rocks × slugs); thecollisioncolumn represents the state of the collisiongame.hitswhich contains all of the hits (every computed hit of a slug and a rock)
The collision composite type represents the result of a collision computation. Its fields include:
t, the time at when the collision would have occurred (ornullif no collision was possible)pos, the position at which the collision would have occured (ornull)mdiff, the difference in position between the slug and rock if there was a miss (ornullif there was a hit)miss, an enum field with the reason for the miss —rdidn't match,thetadidn't match, and/orphididn't match (ornullif there was a hit)
The game.collisions and game.hits tables are populated by triggers.
- upon insert/update to
game.rocksorgame.slugs, recompute all collisions and insert/update ingame.collisions - upon insert/update/delete to
game.collisions, recompute all hits and delete/insert ingame.hits - upon insert to
game.hits, compute and rock fragments and insert intogame.rocks(potentially “cascading” triggers)
The major views in api are:
api.rockswhch contains all rocks (incl. those that have collided) with positions and other derived fields computedapi.slugswhch contains all slugs (incl. those that have collided) with positions and other derived fields computedapi.all_neoswhich contains a union of all rocks and slugs (incl. those that have collided)api.neoswhich contains a union of all active rocks and slugs (not incl. those that have collided)api.collisionswhich contains all collisions (whether hits or missed) inner-joined nicely withgame.rocksandgame.slugsto include display namesapi.hitswhich contains all collisions (whether hits or missed) inner-joined nicely withgame.rocksandgame.slugsto include display names
The REST API is a single-file flask (https://github.com/pallets/flask) app.
You can find it at api/api.py.
You can find some sample scripts in examples/.
- examples/observer.py images the night sky in a loop and reports what it finds
- examples/autofire.py is a sample automated defense system that scans and automatically fires at every object it sees
neocrisis is a collaborative coding game created by the folks behind NYC
Python! It has been played at the following events:
- "Collaborative Code Night: NEO Crisis" (Aug 2, 2018)
- "Collaborative Code Night: NEO Crisis" (Mar 14, 2019)
Submit a PR to list any events where you've played NEO Crisis.