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/1
HTTP/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:=0
HTTP/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/1
HTTP/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
t1
andt2
.- call them
(r1, phi1, theta1)
and(r2, phi2, theta2)
- call them
- We want to solve for
vrock
andr0
. 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 + r0
andr2 = vrock × t2 + r0
- therefore,
v = (r1 - r2) ÷ (t1 - t2)
- therefore,
r0 = r1 - vrock × t1
orr0 = 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φ × t1
orbφ = 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θ × t1
orbθ = phi2 - mθ × t2
- we know that
- We need to compute direction in which to fire the slug; its
phi
andtheta
angles- we're given the slug's
vslug
velocity - we know that at collision time
tcollide
, thatrrock
andrslug
will be the same:rslug = rrock = rcollide
- we know
rcollide = vrock × tcollide + r0
andrslug, 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:
game
which contains the game core dataapi
which contains views used by the API
The major tables are:
game.rocks
which contains all rocks for a given game (incl. those that have collided with a slug or the earth)game.slugs
which contains all slugs for a given game (incl. those that have collided with a rock)game.collisions
which contains all collisions between all rocks and all slugs (rocks × slugs); thecollision
column represents the state of the collisiongame.hits
which 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 (ornull
if 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 (ornull
if there was a hit)miss
, an enum field with the reason for the miss —r
didn't match,theta
didn't match, and/orphi
didn't match (ornull
if there was a hit)
The game.collisions
and game.hits
tables are populated by triggers.
- upon insert/update to
game.rocks
orgame.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.rocks
whch contains all rocks (incl. those that have collided) with positions and other derived fields computedapi.slugs
whch contains all slugs (incl. those that have collided) with positions and other derived fields computedapi.all_neos
which contains a union of all rocks and slugs (incl. those that have collided)api.neos
which contains a union of all active rocks and slugs (not incl. those that have collided)api.collisions
which contains all collisions (whether hits or missed) inner-joined nicely withgame.rocks
andgame.slugs
to include display namesapi.hits
which contains all collisions (whether hits or missed) inner-joined nicely withgame.rocks
andgame.slugs
to 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.