I wanted to write a game for a while, so today I decided to create a geography game with D3.js. The idea is to let the player drag and drop territories into their appropriate place on the world map. The map is rendered using the Robinson projection and only the top 100 territories are chosen according to land area. These 100 “countries” are shuffled on each refresh and upon a successful drop, the land changes color to green. If the player makes a mistake, a hint is provided: all the countries that border the selected country. The player has one more try to drag the land into the right place or the land turns red, animates on its own to the right place, and the next territory is loaded. The goal is to get a perfect score and learn where every country is located on a map. Let’s look at the code.
I started with a normal setup and Natural Earth vector data. In order to keep the map responsive to screen size, I am calculating the browser window and adjusting the width and height on page load. I have used TopoJSON and the neighbors function allows me to quickly figure out all the bordering countries of each region. I will be using that data for hints and the path.area function allows me to calculate the land area of each territory and eliminate all the small countries as they would be difficult to drag and drop. Next, I shuffle the territories so that the game has random selection on each page refresh.
I draw the background world using original TopoJSON data and then loop through the 100 selected and randomized countries to draw them on the map in the middle. I place them in the middle of the map by using the path.centroid function and adjusting the coordinates in the translate property. They are all hidden using CSS and then we start the game with the go function which first updates the current score and then shows the first country that can be dragged and dropped. The go function has a conditional that checks if all the countries have been selected and at that point the game is over, a message is shown, and the map animates to a darker color.
For drag and drop, I am using all three events: dragstart, drag, and dragend. On dragstart, I update the object x and y attributes to make sure that dragging starts from the middle of the map. The drag function just updates the position of the element according to d3.event or mouse change in position. Drag and dragend functions also check the class of the object to make sure that dropped territories do not respond to drag events. The rest of the logic lives in the dragend function which calculates the final dragged position and decides if the element was dragged to the right location. If the country is within a 10 pixels range, we move the element to the pixel perfect spot, update the color to green via CSS class, update the score, and trigger the go function that loads the next country.
If the country was dropped in the wrong spot, we first check if this is the first attempt and provide a textual hint: bordering territories. If this was the second attempt, we animate the country to its proper place and color it red via a CSS class. At this point, we change the value of tries or attempts back to 0 and trigger the go function again, which either loads up the next country or shows the “end of game” message and color animation.
Check out this game here. What do you think?