When I build things for the web, I prefer using vanilla JavaScript as much as possible. However, for more complex projects, I also like to use Reef.js. The Brush Ninja animated gif maker is built with Reef.js, as are the other apps on Brush Ninja. I also use Reef js on my personal website admin panel.
In this blog post, I’ll show you how I used Reef.js to create a country flag game.
What is Reef.js?
Reef.js is a lightweight alternative to React that utilizes vanilla JavaScript. It doesn’t require any complex build process and allows you to work with normal JavaScript commands. With Reef.js, you can easily store data and update html when that data changes.
Getting Started
Since I wanted to create a new game for Brush Ninja, I decided to write a tutorial on how I made it using Reef.js. As I mentioned in my last post, my plan was to develop a multiplication tables game, but I have since decided to change it to a country flags game.
In this game, players are presented with a country flag along with three country options. Their task is to identify the country corresponding to the given flag.
The Game HTML
The html for this project is simple. I just need a div to insert the game template into. I also need Reef.js and some CSS to make the game look nice. I am using ElementalCSS – my own CSS library.
<script src="https://cdn.jsdelivr.net/npm/reefjs@12/dist/reef.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/BinaryMoon/ElementalCSS/dist/elementalcss.min.css" />
<div id="game"></div>
Gathering Data
To start off, we need a list of countries and their respective country codes. To accomplish this task efficiently, I asked ChatGPT (an AI language model) to provide me with a JavaScript object of code and name pairs.
const country_codes = {"ad": "Andorra","ae": "United Arab Emirates","af": "Afghanistan","ag": "Antigua and Barbuda","ai": "Anguilla","al": "Albania","am": "Armenia","ao": "Angola","aq": "Antarctica","ar": "Argentina","as": "American Samoa","at": "Austria","au": "Australia","aw": "Aruba","ax": "Åland Islands","az": "Azerbaijan","ba": "Bosnia and Herzegovina","bb": "Barbados","bd": "Bangladesh","be": "Belgium","bf": "Burkina Faso","bg": "Bulgaria","bh": "Bahrain","bi": "Burundi","bj": "Benin","bl": "Saint Barthélemy","bm": "Bermuda","bn": "Brunei Darussalam","bo": "Bolivia (Plurinational State of)","bq": "Bonaire, Sint Eustatius and Saba","br": "Brazil","bs": "Bahamas","bt": "Bhutan","bv": "Bouvet Island","bw": "Botswana","by": "Belarus","bz": "Belize","ca": "Canada","cc": "Cocos (Keeling) Islands","cd": "Congo (Democratic Republic of the)","cf": "Central African Republic","cg": "Congo","ch": "Switzerland","ci": "Côte d'Ivoire","ck": "Cook Islands","cl": "Chile","cm": "Cameroon","cn": "China","co": "Colombia","cr": "Costa Rica","cu": "Cuba","cv": "Cabo Verde","cw": "Curaçao","cx": "Christmas Island","cy": "Cyprus","cz": "Czechia","de": "Germany","dj": "Djibouti","dk": "Denmark","dm": "Dominica","do": "Dominican Republic","dz": "Algeria","ec": "Ecuador","ee": "Estonia","eg": "Egypt","eh": "Western Sahara","er": "Eritrea","es": "Spain","et": "Ethiopia","fi": "Finland","fj": "Fiji","fk": "Falkland Islands (Malvinas)","fm": "Micronesia (Federated States of)","fo": "Faroe Islands","fr": "France","ga": "Gabon","gb": "United Kingdom of Great Britain and Northern Ireland","gd": "Grenada","ge": "Georgia","gf": "French Guiana","gg": "Guernsey","gh": "Ghana","gi": "Gibraltar","gl": "Greenland","gm": "Gambia","gn": "Guinea","gp": "Guadeloupe","gq": "Equatorial Guinea","gr": "Greece","gs": "South Georgia and the South Sandwich Islands","gt": "Guatemala","gu": "Guam","gw": "Guinea-Bissau","gy": "Guyana","hk": "Hong Kong","hm": "Heard Island and McDonald Islands","hn": "Honduras","hr": "Croatia","ht": "Haiti","hu": "Hungary","id": "Indonesia","ie": "Ireland","il": "Israel","im": "Isle of Man","in": "India","io": "British Indian Ocean Territory","iq": "Iraq","ir": "Iran (Islamic Republic of)","is": "Iceland","it": "Italy","je": "Jersey","jm": "Jamaica","jo": "Jordan","jp": "Japan","ke": "Kenya","kg": "Kyrgyzstan","kh": "Cambodia","ki": "Kiribati","km": "Comoros","kn": "Saint Kitts and Nevis","kp": "Korea (Democratic People's Republic of)","kr": "Korea (Republic of)","kw": "Kuwait","ky": "Cayman Islands","kz": "Kazakhstan","la": "Lao People's Democratic Republic","lb": "Lebanon","lc": "Saint Lucia","li": "Liechtenstein","lk": "Sri Lanka","lr": "Liberia","ls": "Lesotho","lt": "Lithuania","lu": "Luxembourg","lv": "Latvia","ly": "Libya","ma": "Morocco","mc": "Monaco","md": "Moldova (Republic of)","me": "Montenegro","mf": "Saint Martin (French part)","mg": "Madagascar","mh": "Marshall Islands","mk": "North Macedonia","ml": "Mali","mm": "Myanmar","mn": "Mongolia","mo": "Macao","mp": "Northern Mariana Islands","mq": "Martinique","mr": "Mauritania","ms": "Montserrat","mt": "Malta","mu": "Mauritius","mv": "Maldives","mw": "Malawi","mx": "Mexico","my": "Malaysia","mz": "Mozambique","na": "Namibia","nc": "New Caledonia","ne": "Niger","nf": "Norfolk Island","ng": "Nigeria","ni": "Nicaragua","nl": "Netherlands","no": "Norway","np": "Nepal","nr": "Nauru","nu": "Niue","nz": "New Zealand","om": "Oman","pa": "Panama","pe": "Peru","pf": "French Polynesia","pg": "Papua New Guinea","ph": "Philippines","pk": "Pakistan","pl": "Poland","pm": "Saint Pierre and Miquelon","pn": "Pitcairn","pr": "Puerto Rico","ps": "Palestine, State of","pt": "Portugal","pw": "Palau","py": "Paraguay","qa": "Qatar","re": "Réunion","ro": "Romania","rs": "Serbia","ru": "Russian Federation","rw": "Rwanda","sa": "Saudi Arabia","sb": "Solomon Islands","sc": "Seychelles","sd": "Sudan","se": "Sweden","sg": "Singapore","sh": "Saint Helena, Ascension and Tristan da Cunha","si": "Slovenia","sj": "Svalbard and Jan Mayen","sk": "Slovakia","sl": "Sierra Leone","sm": "San Marino","sn": "Senegal","so": "Somalia","sr": "Suriname","ss": "South Sudan","st": "Sao Tome and Principe","sv": "El Salvador","sx": "Sint Maarten (Dutch part)","sy": "Syrian Arab Republic","sz": "Eswatini","tc": "Turks and Caicos Islands","td": "Chad","tf": "French Southern Territories","tg": "Togo","th": "Thailand","tj": "Tajikistan","tk": "Tokelau","tl": "Timor-Leste","tm": "Turkmenistan","tn": "Tunisia","to": "Tonga","tr": "Turkey","tt": "Trinidad and Tobago","tv": "Tuvalu","tw": "Taiwan (Province of China)","tz": "Tanzania, United Republic of","ua": "Ukraine","ug": "Uganda","us": "United States of America","uy": "Uruguay","uz": "Uzbekistan","va": "Holy See","vc": "Saint Vincent and the Grenadines","ve": "Venezuela (Bolivarian Republic of)","vg": "Virgin Islands (British)","vi": "Virgin Islands (U.S.)","vn": "Viet Nam","vu": "Vanuatu","wf": "Wallis and Futuna","ws": "Samoa","ye": "Yemen","yt": "Mayotte","za": "South Africa","zm": "Zambia","zw": "Zimbabwe"};
Picking Random Countries
Next up, we need a function that allows us to select random countries from our list. This function will come in handy when presenting users with different flags and corresponding options.
const randomCountry = function () {
const keys = Object.keys(country_codes);
const randomId = Math.floor(Math.random() * keys.length);
return {
code: keys[randomId],
name: country_codes[keys[randomId]],
};
};
Adding Interactivity
Now that we have all the setup in place, it’s time to add some interactivity to our country flag game. Reef.js makes this process incredibly straightforward.
First we get some properties from the Reef object and set our default data values.
let { store, component } = reef;
// Create a reactive data store.
let data = store({
flag: '', // The flag to display (and the correct answer).
answers: [], // The possible answers (including the correct answer).
correct: 0, // How many questions have been answered correctly.
asked: 0, // How many questions have been asked.
});
Next we need a template to display the flag and the possible answers. We can use the data
object to access the values we set earlier. I am making use of the FlagCDN api to get the flag images. I am also using template literals (backticks) to make the code more readable and avoid combining lots of strings.
When I publish the finished game on Brush Ninja I will self host the images to avoid any issues with the FlagCDN service.
const template = function() {
const buttonClass='bg-white p2 display-block m-y3 b-br1'; // Store the button classes in a const to save repeating them.
return `
<div class="flex gap5 f6 t-tt-u f-fw900 m-b5">
<span>Asked: ${data.asked}</span>
<span>Correct: ${data.correct}</span>
</div>
<strong>Which country has this flag?</strong>
<img
src="https://flagcdn.com/112x84/${data.flag.code}.png"
width="112"
height="84"
>
<a href="" data-value="${data.answers[0].code}" class="${buttonClass}">${data.answers[0].name}</a>
<a href="" data-value="${data.answers[1].code}" class="${buttonClass}">${data.answers[1].name}</a>
<a href="" data-value="${data.answers[2].code}" class="${buttonClass}">${data.answers[2].name}</a>
`;
}
component( '#game', template );
Within the template we can access the reef object properties by looking at the properties of the data object we setup previously.
We make use of the component object to render the template into the div with id #game
.
Pick a question
Next we need a function that picks a question and sets all the properties. This function will be called when the game starts, and when the user clicks on an answer.
const nextQuestion = function() {
data.asked ++; // Number of questions asked.
const correct = randomCountry(); // Pick a random country.
data.flag = correct; // Set the flag to display.
// Pick three possible answers (including the correct one) and shuffle them.
data.answers = [
correct,
randomCountry(),
randomCountry()
].sort( () => 0.5 - Math.random() );
};
// Load in a question.
nextQuestion();
I am using a simple shuffle function to randomize the order of the answers. This function is taken from Stack Overflow. This method is not good for large arrays and does not make properly random results, but for this game it is good enough.
Pick an answer
Finally we need to add click events so that the user can select an answer. We can use a click event listener for this. When the user clicks on an answer we check if it is correct, and then load in the next question.
I add the event listener to the entire game container. This captures all events within the container, which is useful as we are adding and changing the answers dynamically.
// Add an event listener to the entire game div.
document.querySelector( '#game' ).addEventListener(
'click',
(e) => {
// See if there's an answer. This will be empty if anything other than a button is clicked.
const answer = e.target.getAttribute('data-value');
if ( answer ) {
// Stop the link from working.
e.preventDefault();
// Is the answer correct?
if ( answer === data.flag.code ) {
// Yes, add a point.
data.correct++;
}
// Load the next question.
nextQuestion();
}
}
);
The finished Country Flag Game
Next steps
This game is a good start, but there are a few things that could be improved. For example, it would be nice to have a timer so that the user has to answer within a certain time limit. It would also be good to track what the right and wrong answers are and display them at the end so the player can learn where they went wrong.
I will add all these things and more to my version on Brush Ninja before I publish it.
Reef.js
Reef.js is a fantastic tool for creating interactive web applications without the need for complex build processes or additional frameworks like React. By incorporating Reef.js into your projects, you can leverage its lightweight nature and vanilla JavaScript approach to build dynamic and engaging experiences for your users.
FAQs
What is Reef.js?
Reef.js is a lightweight alternative to React that utilizes vanilla JavaScript. It doesn’t require any complex build process and allows you to work with normal JavaScript commands. With Reef.js, you can easily store data and update HTML when that data changes.
How do I get started with Reef.js?
To get started with Reef.js, you can include the Reef.js library in your HTML file using a script tag. Then, you can create a reactive data store and define templates to display data. The blog post provides a detailed example of setting up a simple game using Reef.js.
How do I add interactivity to my Reef.js application?
To add interactivity to your Reef.js application, you can define a data store with default values and create templates that display the data. You can use event listeners to capture user interactions and update the data accordingly. The blog post demonstrates this process in the context of the Country Flag Game.
Where can I find more information about Reef.js and its capabilities?
For more information about Reef.js and its capabilities, you can visit the official Reef.js website at reefjs.com. The website provides documentation and resources to help you get started with Reef.js.
Was it good/ useful/ a load of old rubbish? Let me know on Mastodon, or BlueSky (or Twitter X if you must).
Link to this page
Thanks for reading. I'd really appreciate it if you'd link to this page if you mention it in your newsletter or on your blog.