Skip to content

Instantly share code, notes, and snippets.

@hugolpz
Last active August 29, 2015 14:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hugolpz/48af10320a4ab3c3c539 to your computer and use it in GitHub Desktop.
Save hugolpz/48af10320a4ab3c3c539 to your computer and use it in GitHub Desktop.
Geography game

Drag & drop geographic game.

<!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="http://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>
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment