block by hugolpz 48af10320a4ab3c3c539

Geography game

Full Screen

Drag & drop geographic game.

index.html

<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="//code.jquery.com/jquery-2.1.0.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.4.13/d3.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/topojson/1.1.0/topojson.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3-geo-projection/0.2.9/d3.geo.projection.min.js"></script>
	
<script src="../js/wikiatlas.js"></script>
	<style>svg { border: 1px solid #BBB; }
#hook {
  margin:10px 10%;
  padding:20px;
  border:2px solid #d0d0d0;
  border-radius: 5px;
  height:100%;
  clear:both;
}
#score{
  float:right;
  padding:4px 0;
  font-weight:bold;
}
#info{
  float:left;
  font-weight:bold;
  padding:4px 0;
  width:80%;
}
#top{
  margin:10px 10%;
}
.country{
  display:none;
  fill:#555;
  stroke:#555;
}
.country-back{
  fill:#ddd;
  stroke:#fff;
}
.correct {
  fill:#61aa61;
  fill-opacity: 0.6;
}
.error{
  fill:#B10000;
  fill-opacity: 0.6;
}
.fill {
  fill: #fff;
}
.graticule {
  fill: none;
  stroke: #777;
  stroke-width: .5px;
  stroke-opacity: .5;
}
#top .start{
  position: absolute;
  top: 43%;
  left: 45%;
  background: #fff;
  width: auto;
  padding: 4px 10px;
  text-align: center;
  font-size: 140%;
}
#options{
  margin-bottom:10px;
}
</style>
Check out the source or go <a href="//techslides.com/geography-game-with-d3/">back to article</a>.</p>

    <div id="options">
      <label>Country Borders?</label>
      <input type="checkbox" id="borders" name="borders" />
    </div>

    <div id="info" class="start">Loading...</div>
    <div id="score"></div>
    <div class="clear"></div>
  </div>

  <div id="hook"></div>

<script>
/* ****************************************************** */
/* INIT************************************************** */
var width = document.getElementById('hook').offsetWidth-60;
var height = width / 2;

var topo,all,tries=0,score=0;

var projection = d3.geo.equirectangular().translate([0, 0]).scale(width / 2 / Math.PI);

var path = d3.geo.path().projection(projection);

var graticule = d3.geo.graticule();

/* ****************************************************** */
/* SVG ************************************************** */
var svg = d3.select("#hook").append("svg")
      .attr("width", width)
      .attr("height", height);

var outterg = svg.append("g")
	.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

var g = outterg.append("g").attr("id", "innerg");

  g.append("defs").append("path")
      .datum({type: "Sphere"})
      .attr("id", "sphere")
      .attr("d", path);
 
  g.append("use")
      .attr("class", "stroke")
      .attr("xlink:href", "#sphere");
 
  g.append("use")
      .attr("class", "fill")
      .attr("xlink:href", "#sphere");

  g.append("path")
      .datum(graticule)
      .attr("class", "graticule")
      .attr("d", path);

/* ****************************************************** */
/* GIS data injection *********************************** */
  d3.json("./world-geogame-110m.json", function(error, world) {

    topo = topojson.feature(world, world.objects.countries).features;
    var neighbors = topojson.neighbors(world.objects.countries.geometries);

    //filter and place borders into topo
    topo = topo.filter(function(f,i){ 
      if(path.area(f)>100){
        var b = neighbors[i];
        var t = 'Nearby...  ';
        if(b.length>0){
          b.forEach(function(g){
            t = t + topo[g].properties.name+', ';
          });
          f.properties.borders = t.substr(0,t.length-2);
        } else {
          f.properties.borders = 'No nearby countries';
        }
        return f;
      }
    });

    //lets shuffle topo so that it shows different countries on each reload
    d3.shuffle(topo);

    var country = d3.select("#innerg").selectAll(".country").data(topo);

    //ofsets (useless !)
    var offsetL = document.getElementById('hook').offsetLeft+30;
   var offsetT =document.getElementById('hook').offsetTop-30;

    d3.select("#innerg").insert("path")  
      .datum(topojson.feature(world, world.objects.countries))
      .attr("class", "country-back")  
      .attr("d", path); 

    country.enter().insert("path")
      .attr("class", "country")
      .attr("d", path)
      .attr("id", function(d,i) { return 'b'+d.id; })
      .attr("title", function(d,i) { return d.properties.id; })
      .style("stroke", "#111")
      .attr("transform", function(d,i) { 
        var cx = Number(-1*path.centroid(d)[0]);
        var cy = Number(-1*path.centroid(d)[1]);
        var coord = [cx,cy];
        return "translate(" + coord + ")"
      })
      .attr("x", function(d,i) { 
        var cx = Number(-1*path.centroid(d)[0]);
        return cx;
      })  
      .attr("y", function(d,i) { 
        var cy = Number(-1*path.centroid(d)[1]);
        return cy;
      })     
      .call(drag);
   
    //all used to keep track
    all = d3.selectAll(".country")[0];
    d3.select("#info").text("World Map");

    //start
    go();

  });



/* ****************************************************** */
/* GAME FUNCTION **************************************** */
/* Start ************************************************ */
function go(){
  d3.select("#info").attr("class","start");
  var missed = topo.length - all.length - score;
  d3.select("#score").text("Score: "+score+"/"+topo.length+" | Missed: "+missed);

  if(all.length>0){
    var obj = d3.select(all.shift());
    obj.style("display","block");
    d3.select("#info").text(obj.attr("title"));
    window.setTimeout(function(){ 
      d3.select("#info").attr("class","");
    }, 1500);
  } else {
    d3.select("#info").text("All Done. Restart to try again."); 
    d3.select(".country-back").transition().duration(1500).style("fill", function(){
      return "#666";
    });
  }
}
	
/* Borders or not *************************************** */
d3.select("#borders").on("click",function(){
  if(this.checked){ d3.select(".country-back").style("stroke","#ddd"); } 
  else { d3.select(".country-back").style("stroke","#fff"); }
});
	
/* ****************************************************** */
/* DRAG & DROP ****************************************** */
var drag = d3.behavior.drag()
    .on("drag", dragmove)
    .on("dragstart", dragstart)
    .on("dragend", dragend);

function dragstart(d) {
  //d3.event.sourceEvent.stopPropagation(); // silence other listeners
  d.x = Number(d3.select(this).attr("x"));
  d.y = Number(d3.select(this).attr("y"));
}

function dragmove(d) {
  //only if its new country
  if(d3.select('#b'+d.id).attr("class") == "country"){
    d.x += d3.event.dx;
    d.y += d3.event.dy;
    d3.select(this).attr("transform", function(d,i){
      return "translate(" + [ d.x,d.y ] + ")"
    });
  }
}

function dragend(d) {
  //only if its new country
  if(d3.select('#b'+d.id).attr("class") == "country"){

    //snap to position if you are 10 pixels or closer
    if(d.x > -10 && d.x < 10 && d.y > -10 && d.y < 10 ){
		tries = 0; score++;
		d3.select(this)
			.attr("class","correct")
			.attr("transform", function(d,i){ return "translate([0,0])"; });
		go(); // go next

    } else { tries++; /* wrong ********************** */      
      if(tries == 1){ // move shape back to Null Island.
        d3.select("#info").text(d.properties.borders.split(",").slice(0,3));
        //put back in the middle!
        var cx = Number(-1*path.centroid(d)[0]);
        var cy = Number(-1*path.centroid(d)[1]);
        var coord = [cx,cy];
        d3.select(this).transition().duration(500).attr("transform", function(d,i){
          return "translate(" + coord + ")"
        });
		  
      } else { //wrong again, move element to correct position and mark red!
        d3.select(this).transition().duration(500).attr("class","error").attr("transform", function(d,i){
          return "translate([0,0])";
        });
        tries = 0;
		go();//go to next!
	  }     
    }
  }
}
</script>
</body>
</html>