Play

Support up-and-coming indie game developers by playing their games. Leave a comment to tell them what you like or what can be improved.

Learn

Our tutorials go from basic Processing through advanced libGDX.

Code

Free hosting for Processing, Java, libGDX, and JavaScript games.

Blog

Read about what we're up to, or write your own blog to keep us updated on your latest projects.

Forum

Stuck on a code problem? Just want to talk? Come say hi!

Open Source

Static Void Games is completely open-source. Want to practice your web development skill? Contribute on GitHub!

Newest games:

Recent happenings:

Kevin's latest blog: Ludum Dare 33 Postmortem

This past weekend was Ludum Dare, where participants have 48 hours to create a game around a certain theme. Participants are encouraged to write a postmortem afterwards to sum up their experience: what went well, what didn't, lessons learned, etc. This is my postmortem.

HERE is a link to my game, and HERE is a link to my Ludum Dare entry page.

To explain my game, I should first talk a little bit about Arlington Cemetery (that's not a sentence you hear every day, and no, I didn't intend a pun on the word postmortem). Arlington is a cemetery for the American military, and it dates all the way back to the Civil War. It holds around 400,000 bodies and is open to the public.

I'm not a patriotic or pro-military guy, but I live a few miles away from Arlington, so I've been there a few times. Visiting goes something like this: you walk around reading the graves, and you get little windows into the lives of the people buried in them. Maybe this guy died when he was 23 during World War 1, and then his widow lived another 60 years before she was buried next to him. Maybe this woman died in 2012, but she was younger than you. The graves become more than just a meaningless big number, and they start being stories. Each one could be a movie: would that movie be funny? Sad? Romantic? Tragic?

For example, a guy named Arthur Walk served in both world wars, then died in 1953. His wife died 3 days later. What's their story?

Thinking about just one of those stories is enough to stop you for a second, but then you look up and find yourself surrounded from one horizon to another by graves. Each one is a story. Each one is a person. And you start to feel something that I can't quite explain: partially guilty, partially complicit, partially sad. No matter what your political views are, it's a harrowing experience.

And I wanted to recreate that feeling with my game.

(I should note here that I wasn't trying to make my game fun. I wanted to make a "serious game" that felt heavy and somber, not a funny or light-hearted game at the expense of anybody.)

My plan was to visualize data from Arlington using satellite images, and I figured that somebody had probably collected data on positions of graves and information on who was buried there. I had myself a plan. Time to Ludum Dare!

The Map

My original idea was to use a single "high resolution satellite image" (whatever that is) and then figure out grave positions from that, but my googling didn't turn up any images that I could use, plus I wasn't even sure how I'd figure out the positions.

So I quickly switched gears and decided to use a piece of mapping software. I've been using the GWT Google Maps API at work, and I've been trying to get better at JavaScript, so I decided to try my hand at using the GoogleMap JavaScript API.

That turned out to be pretty simple to use. Here is all the code you need to create a google map and add a circle to it:

map = new google.maps.Map(document.getElementById('map'), {center: {lat: 38.876870, lng: -77.067524}});
var circle = new google.maps.Circle({
    strokeColor: "red",
    strokeOpacity: 1,
    strokeWeight: 1,
    fillOpacity: 0,
    map: map,
    center: {lat: element.lat, lng: element.lng},
    radius: .5
});

I wasn't very thrilled with the maximum zoom allowed by a google map: I wanted to make the view as close to "really being there" as I could, and google's maximum zoom leaves you at a bird's eye view. I tried other mapping APIs, but there maximum zooms were even worse. I then tried some kludges involving zooming in using CSS, but that just made the map really buggy. Oh well, I guess I had to live with the bird's eye view.

The Controls

Now that I had some mapping software, I had to hammer away randomly carefully develop controls that felt more like a game than a map. Step one was disabling all of the default controls, which turned out to be very easy:

map = new google.maps.Map(document.getElementById('map'), {
    center: {lat: 38.876870, lng: -77.067524},
    minZoom: 20,
    zoom: 20,
    mapTypeId: google.maps.MapTypeId.SATELLITE,
    draggable: false, 
    zoomControl: false, 
    scrollwheel: false, 
    disableDoubleClickZoom: true,
    disableDefaultUI: true,
    streetViewControl: true
});

This made it so the player couldn't just relocate the map or zoom it out even further. After locking down the map, step two was adding controls.

One of the nice things about using Google Map's JavaScript API is that I get a lot of things out of the box: I just tell the map where to display, and it handles the rest. So all I really had to do was figure out the starting latitude and longitude, store those in variables, and change those variables to change the player's position.

My first take was to use the obvious arrow keys: up goes north, left goes west, etc. I would just add or subtract a tiny amount from the latitude or longitude and recenter the map there. That worked, but it didn't feel like you were on the ground. So I decided to have the up arrow go straight, and the left and right arrows turn the direction the player was facing. Now recentering the map just required a tiny bit of trig:

function goNorth(){
    var speed = .000001;
    var deltaX = Math.cos(angle*(Math.PI/180)) * speed;
    var deltaY = Math.sin(angle*(Math.PI/180)) * speed;
    map.setCenter(new google.maps.LatLng(map.getCenter().lat()+deltaY, map.getCenter().lng()+deltaX));
}

And drawing the "player" (which is just a pointer) was a tiny bit more of trig:

function redoIcon(){

    marker.setMap(null);

    var radius = .00005;

    var leftX =  map.getCenter().lng() + Math.cos((angle-135)*(Math.PI/180)) * radius;
    var leftY = map.getCenter().lat() + Math.sin((angle-135)*(Math.PI/180)) * radius;

    var rightX =  map.getCenter().lng() + Math.cos((angle+135)*(Math.PI/180)) * radius;
    var rightY = map.getCenter().lat() + Math.sin((angle+135)*(Math.PI/180)) * radius;

    var coordinates = [
        {lat:leftY, lng: leftX},
        {lat:map.getCenter().lat(), lng: map.getCenter().lng()},
        {lat:rightY, lng: rightX}
    ];

    marker = new google.maps.Polyline({
        path:coordinates,
        strokeColor: "red",
        map:map
    });

}

One of my primary goals was to have this work on mobile, so I had to add some buttons outside of the map instead of the arrow keys. I wanted the controls to respond continuously, so you could hold down a button instead of pushing it a million times. To do that, I set up a little game loop that checked the status of which button was pressed and then did the correct thing:

function checkMovement(){
    if(upPressed){
        goNorth();
        redoIcon();
        steps++;
        if(steps == 10){
            redoCircles();
            steps = 0;
        }
    }

    if(leftPressed){
        angle++;
        if(angle > 360){
            angle = 0;
        }
        redoIcon();
    }

    if(rightPressed){
        angle--;
        if(angle < 0){
            angle = 360;
        }
        redoIcon();
    }
}

setInterval(checkMovement, 16);

The Data

All of that is fine and dandy, but it's all for nothing if I can't find the data to back it up. I just sorta assumed that somebody would have collected this data already. And that assumption turned out to be correct: **Arlington Cemetery has a web API!**

I was pretty surprised by this, but it was exactly what I was looking for. The web API allows you to put together URLs like this one:

http://public.mapper.army.mil/ANC/rest/services/ANC_External/MapServer/0/query?text=&geometry=&inSR=&relationParam=&objectIds=123456&where=1%3D1&time=&returnCountOnly=false&returnIdsOnly=false&returnGeometry=true&outSR=&outFields=*&f=pjson

Notice the 123456 part of that URL. That's the identification number of a particular grave, and visiting that url gives you data on that grave:

{
  "attributes" : {
    "OBJECTID" : 123456,
    "SDSFEATURENAME" : "Government Headstone",
    "SDSFEATUREDESCRIPTION" : "Arlington National Cemetery",
    "SURNAME" : "LITVINS",
    "INTERMENTID" : "38:5097:_:_:_"
  },
  "geometry" : {
    "x" : -8579530.4255054388,
    "y" : 4705019.6584724095
  }

Those x and y values turn out to be esri arcGIS MapPoint coordinates, so I had to convert them to latitude and longitude using code I found from this StackOverflow answer:

private static Point2D.Double toLatLng(Point2D.Double pnt)
{
    double mercatorX_lon = pnt.getX();
    double mercatorY_lat = pnt.getY();
    if (Math.abs(mercatorX_lon) < 180 && Math.abs(mercatorY_lat) < 90)
        return pnt;
    if ((Math.abs(mercatorX_lon) > 20037508.3427892) || (Math.abs(mercatorY_lat) > 20037508.3427892))
        return pnt;
    double x = mercatorX_lon;
    double y = mercatorY_lat;
    double num3 = x / 6378137.0;
    double num4 = num3 * 57.295779513082323;
    double num5 = Math.floor((double)((num4 + 180.0) / 360.0));
    double num6 = num4 - (num5 * 360.0);
    double num7 = 1.5707963267948966 - (2.0 * Math.atan(Math.exp((-1.0 * y) / 6378137.0)));
    mercatorX_lon = num6;
    mercatorY_lat = num7 * 57.295779513082323;
    return new Point2D.Double(mercatorX_lon, mercatorY_lat);
}

Now, collecting the positions for every grave in Arlington was a single for loop. I wrote a Java program that started at identification number 1 and looped to the last identification number, visiting the URL for each identification number and collecting the data for that grave. This process took about **12 hours** (that's how many graves there are!), so I left that cooking overnight.

After I had all of the grave data collected, I had to collect all of the data for the people buried in those graves. The grave data contains a INTERMENTID value, which can be used with the Arlington web API to cross-reference graves with the people buried in them. For example, I could put together this URL to find the people buried in the above grave:

http://wspublic.iss.army.mil/IssRetrieveServices.svc/search?q=locationid=38:5097:_:_:_&sortColumn=PrimaryLastName,PrimaryFirstName&sortOrder=asc&&start=0&method=IntermentsRender

That URL gives you information on the person (or people) buried in that grave:

{"BRANCHOFSERVICE":"US ARMY","COLUMN":"","COURT":"","DOB":"04\/06\/1890 00:00","DOD":"03\/14\/1959 00:00","DOI":"03\/19\/1959 00:00","GRAVE":"5097","IMAGEURL":"headstone_image_id_175469_front.jpg|http:\/\/wspublic.iss.army.mil\/AttachmentRetrieve.aspx?AttachmentKey=254324,headstone_image_id_175469_back.jpg|http:\/\/wspublic.iss.army.mil\/AttachmentRetrieve.aspx?AttachmentKey=254326","ISS_ID":175469,"LOCATIONID":"38:5097:_:_:_","NICHE":"","PRIMARYFIRSTNAME":"ADAM","PRIMARYLASTNAME":"LITVINS","PRIMARYMIDDLENAME":"","RELATIONSHIP":"Unspecified","SECTION":"38","SUFFIX":""}

Now came another for loop: I wrote another Java program that looped over every grave I had already collected, got its intermentID, then used the web API to get all of the people with a matching locationID. That process took another 18 hours!

After I had collected all of the grave and person data, I wrote a third Java program that combined them into a single JSON array of people and their grave positions, and saved that to a file. This is the file that my program would actually use.

Loading the Data

The data file I created was JSON, so loading it in JavaScript was just a matter of using ajax to get the file, then using the parse() function to create an array of objects, then looping over those objects and adding them to a data structure to use when checking against the player's position:

function loadData(){
    var xmlhttp=new XMLHttpRequest();
    xmlhttp.onreadystatechange=function(){
        if (xmlhttp.readyState==4 && xmlhttp.status==200){
            var graves = JSON.parse(xmlhttp.responseText);

            for(var i = 0; i < graves.length; i++){
                graves[i].x = graves[i].lng;
                graves[i].y = graves[i].lat;
                graves[i].width = 0;
                graves[i].height = 0;
                allGraves.push(graves[i]);
            }   
        }
    };
    xmlhttp.open("GET","data.json",true);
    xmlhttp.send();
}

This worked, but the data file was about 250 MB, so it took a couple minutes to load when you started the game. No player wants to wait that long, so I had to load the data in smaller pieces.

To split the data up, I created a fourth Java program that organized the data into 10 files instead of 1. This program split the positions up into "rings" by checking their distance from the player's starting position. That way, the first file would contain the graves closest to the player's starting position, the second file would contain the graves in the second-closest ring, etc.

int[] counts = new int[20];
boolean[] added = new boolean[20];

FileWriter[] fw = new FileWriter[20];
for(int i = 0; i < fw.length; i++){
    fw[i] = new FileWriter("C:\\Users\\Kevin\\Desktop\\TheTour\\data_" + i + ".json");
    fw[i].write("[");
}

outer: for(String graveNumber : gravesMap.keySet()){
    JSONObject grave = gravesMap.get(graveNumber);

    double graveX = grave.getDouble("lng");
    double graveY = grave.getDouble("lat");

    for(int i = 1; i < 20; i++){
        if(Point2D.distance(centerX, centerY, graveX, graveY) < i*ringDistance){

            if(grave.has("people")){
                counts[i-1]+= grave.getJSONArray("people").length();
            }

            if(added[i-1]){
                fw[i-1].write(",\n");
            }
            added[i-1] = true;
            fw[i-1].write(grave.toString());
            continue outer;
        }
    }

    if(added[19]){
        fw[19].write(",\n");
    }
    added[19] = true;

    fw[19].write(grave.toString());

    if(grave.has("people")){
        counts[19]+= grave.getJSONArray("people").length();
    }
}

for(int i = 0; i < fw.length; i++){
    fw[i].write("]");
    fw[i].flush();
    fw[i].close();
}

Now that I had the data split up into multiple files, I used a recursive ajax call to load the files one at a time:

function loadData(loadingIndex){

    var xmlhttp=new XMLHttpRequest();
    xmlhttp.onreadystatechange=function(){
        if (xmlhttp.readyState==4 && xmlhttp.status==200){
            var graves = JSON.parse(xmlhttp.responseText);
            for(var i = 0; i < graves.length; i++){
                graves[i].x = graves[i].lng;
                graves[i].y = graves[i].lat;
                graves[i].width = 0;
                graves[i].height = 0;
                tree.insert(graves[i]);
                allGraves.push(graves[i]);
            }
            if(loadingIndex < 11){
                loadData(loadingIndex+1);
            }
        }
    };
    xmlhttp.open("GET","data/data_" + loadingIndex + ".json",true);
    xmlhttp.send();
}

This worked out, because now the graves closest to the player's start position loaded much faster. And as the player moved around, the graves further away were being loaded in the background.

Quadtree of Graves (would make a sweet band name)

My game's algorithm went something like this:

  • Determine the player's latitude and longitude based on what keys or buttons they were pressing.
  • Center the map at that position.
  • Find graves near that location and display information about the people buried in them.

That last step worried me, because I knew that iterating over 400,000 graves and checking their positions against the player's position every time the player moved would bog down my game too much. I thought about different ways to store the data that would make accessing it faster.

I decided to just use a Quadtree, specifically this JavaScript quadtree implementation. A quadtree is good at telling you which positions are **not** near you, so it cut back on the number of checks I had to do when figuring out which graves were near the player.

This turned out to be overkill, since simply looping over them ended up being surprisingly fast, but oh well, at least I can say I've used a quadtree now.

Visualizing the Data

I had all of the pieces put together: loading the data, getting the player's position based on user input, getting the graves near that position, and getting the data on the people in those graves. The last part was visualizing those people.

I created a function that took a person and return an html component that showed that person's information:

function createPersonDiv(person){
    var personDiv = document.createElement("div");
    personDiv.className = "personDiv";

    var nameP = document.createElement("p");
    nameP.appendChild(document.createTextNode(nameCase(person.firstName) + " " + nameCase(person.middleName) + " " + nameCase(person.lastName) + " " + person.suffix));
    personDiv.appendChild(nameP);

    var branchP = document.createElement("p");
    branchP.appendChild(document.createTextNode(person.branchOfService));
    personDiv.appendChild(branchP);

    if("Unspecified" != person.relationship){
        var relationshipP = document.createElement("p");
        relationshipP.appendChild(document.createTextNode(person.relationship));
        personDiv.appendChild(relationshipP);
    }

    if(person.dob != null){
        var dobP = document.createElement("p");
        dobP.appendChild(document.createTextNode("Born: " + person.dob.substring(0, 10)));
        personDiv.appendChild(dobP);
    }

    if(person.dod != null){
        var dodP = document.createElement("p");
        dodP.appendChild(document.createTextNode("Died: " + person.dod.substring(0, 10)));
        personDiv.appendChild(dodP);
    }

    if(person.doi != null){
        var doiP = document.createElement("p");
        doiP.appendChild(document.createTextNode("Interment: " + person.doi.substring(0, 10)));
        personDiv.appendChild(doiP);
    }

    if(person.imageUrl != null){
        for(var si = 0; si < person.imageUrl.split(",").length; si++){

            var imageLink = document.createElement("a");
            imageLink.href = person.imageUrl.split(",")[si].split("|")[1];


            var imageImg = document.createElement("img");
            imageImg.className = "tombstoneImage";
            imageImg.src = person.imageUrl.split(",")[si].split("|")[1];
            imageLink.appendChild(imageImg);

            personDiv.appendChild(imageLink);
        }
    }

    return personDiv;
}

Whenever the player moved, I just called this function with the people buried nearby.

Putting it all together, it looks like this:

What went well?

I came in with a goal of visualizing the graves from Arlington, and I think I achieved that goal. My end result is very close to what I was picturing in my head before I started.

I'm pretty pleased with how well everything came together: there weren't very many moments of pounding my fists against the keyboard. The hardest part was remembering which directions latitude and longitude represented (I ended up in Antarctica many times), and for the most part development went pretty smoothly.

I'm particularly proud of my solution for splitting up the data. I realized pretty late in the game that loading the whole thing wasn't going to work, which gave me a split-second of panic. But my solution was pretty simple and low-tech, and more importantly- it worked. Even more importantly, "recursive ajax call" just sounds cool.

Best of all, a few people seem to "get it" and are leaving comments like:

This is great. Chilling and pointed interpretation of the theme. I like it as a proof of concept, and I think there's lots to explore here. I hope you continue with this project.

This is really powerful. I can't say I enjoy it, but it really nailed the theme.

An interesting and original idea. I'm not so much into data visualization but I think bringing this to the jam is amazing and surprising. 5* for innovation :-)

Leaving the theme up to the "player" was the right thing to do. There's a lot to take from this if you give yourself some time to think it over. I'd expect anyone who did so would likely find something different than the next person.

What didn't?

One of my primary goals for this was for it to work on mobile devices. and that fueled a bunch of decisions I made. However, that failed miserably: the data (even split up into multiple files) seems to be too much for a mobile browser, so it crashes. Even if it didn't crash, the user interface is nowhere near as smooth as desktop, so the buttons aren't very responsive. It's basically unplayable on mobile, which is a bummer.

It's not very fun. I never intended it to be a ball pit, but I wanted it to be enthralling, not boring. One of the things I tell people when rating their games is that they should make their players feel fast- just moving around in the world should be interesting. I failed to follow my own advice here. I did that on purpose- I wanted the player's speed to be about the same as actually walking around in real life, but that feels painfully slow when looking at it from a bird's eye view. If you could just zoom to the other side of the cemetery, that's defeating the whole point. In fact, the player's speed is closer to a car, but it still feels too slow. I'm not sure how to get the right balance between "slow enough to take it all in" and "interesting to use".

It's not a game. I knew this wasn't going to be Mario, but this is Ludum Dare after all, so I probably should have worked a little harder to add goals or something. I considered adding something like a scavenger hunt, maybe taking a tour or finding famous graves:

It's not very personal. I wanted to really invoke the feeling of "these graves are people, these people have stories", and I think I partially achieved that. But it still feels a little more like data than it does stories about people. I thought about adding a "google me" link to each person. But it's amazing how much is just lost to history. Now that everybody is on facebook, it's easy to forget how little data is available on people who were around before the internet.

I didn't explain it very well. I wanted to leave it up to the player- some people might feel pride, some people might be horrified, etc. But by not explaining at all, I'm afraid that most people just won't "get" it at all, move around for a couple seconds, and then hit the back button. I think a lot of people won't even know what they're looking at. I guess I should have made more of a statement instead of leaving the player with no information.

Wrap it up

Overall, I'm pleased with the results, and if nothing else, this is a good start to a more polished final product. That's the whole point of Ludum Dare, so in that way I succeeded.

The Dare

I thought it would be interesting to list every single thing I googled (you can find your search history here) throughout Ludum Dare, in order. Gulp, here goes:

arlington cemetery grave information by lat long
arlington cemetery get grave information by lat long
arlington cemetery get grave information
arlington cemetery grave information
arlington cemetery grave listing
list of people buried in arlington cemetery
arlington cemetery grave list
arlington cemetery LOCATIONID lat long
arlington cemetery LOCATIONID
arlington cemetery api LOCATIONID
arlington cemetery api
lat long -8579114.7468382735, 4703637.7785233567
arlington cemetery grave id location
java json
200000 seconds
200000 seconds to hours
java filewriter append
esriGeometryPoint to lat long
convert esriGeometryPoint to lat long
web mercator
web mercator viewer
esriGeometryPoint spatial reference
WGS_1984_Web_Mercator_Auxiliary_Sphere to lat long
convert esriMeters to lat long
latitude longitude map
google maps
java json
java read text from url
google maps javascript max zoom
arlington cemetery REALPROPERTYSITEUNIQUEID
json java
json validator
30000 seconds to minutes
30000 seconds to hours
javascript map api
15000/300000
javascript map api
html css zoom
css scale
arlington national cemetery satellite view
arlington national cemetery satellite view
arlington national cemetery high resolution satellite images
google maps api increase max zoom
google maps api maximum zoom level
google maps api go beyond maximum zoom level
google maps css zoom
css scale
javascript set css of element
css transform-origin
google maps api disable user interaction
javascript hold in button
javascript mouse down
google maps api set center
efficient storage of lat lng points
javascript listen for arrow keys
googlemaps api marker
googlemaps api marker
googlemaps api marker icons
google.maps.SymbolPath
google.maps.SymbolPath
google maps rotation
javascript max memory
windows 8 prevent restart after update
windows 8 find when computer will restart after update
your pc will automatically restart today if you don't restart now
javascript quad tree

java read text from file
Exception in thread "main" java.nio.charset.MalformedInputException: Input length = 1
eclipse increase memory
eclipse increase memory run configuration
eclipse vm arguments xmx
java bufferedreader file
arthur walk 04/11/1895
elsie walk 11/29/1892
Arthur Richard Walk
javascript ajax
javascript json
amazon s3 access-control-allow-origin
javascript json
googlemaps api point
latitude longitude map
(-77.051964)-(-77.082348)
38.867942-38.891929
javascript array
javascript create element
css div attachment
javascript get element by class
javascript get element by id
javascript clear element children
javascript check if undefined
javascript string to lower case
javascript element add class
doi
doi date of internment
doi date of interment
date of interment
define: interment
javascript string split
headstone_image_id_
javascript create img element
javascript create a href
css overflow scroll
Lloyd Patterson us army 11/24/1934
Lloyd Patterson us army died 11/24/1934
Lloyd Patterson us army died 11/24/1934 arlington
walter thomas us army 06/19/1933
walter thomas us army 06/19/1933 arlington
walter thomas 06/19/1933 arlington
javascript cos
javascript to radians
topdown person sprite
topdown person sprite
javascript google map polygons
javascript quadtree
javascript onkeyup
javascript setinterval

javascript button onmousedown
javascript function
javascript onmousedown
javascript inline function
ajax maximum length
latitude longitude map
(-77.082348) + 0.030384
(38.891929) + 0.030384
(38.891929) + 023987
(38.891929) + .023987
(38.891929) - .023987
javascript quadtree
(-77.082348) + 0.030384
(38.867942) + 0.023987
2.5 gigs / 10
2.5 gigs / 100
250 mb /10
how much memory can a website use?
json validator
javascript array append all
javascript google map rectangle
google.maps.LatLngBounds
arlington cemerery
javascript android onmousedown
html favicon
google maps javascript street view
Advertisement