Array Comparisons (or Bulls and Cows)

When someone asked for help with a 'bulls and cows game' and showed what they were trying to do, I realised that the obvious way to set up such a game would involve two arrays and different types of compares. This was nothing like the approach that the person asking for help was taking (which didn't use arrays and did use lots of obsolete JavaScript commands only needed if you want to support Netscape 3). The one thing in the criteria they specified that was interesting was that the four digit number the computer selects that you have to try to guess is not allowed to have repeating digits. This reduces the number of possible combinations in half and means we need to use code that ensures the four entries are unique.

Let's take a look at how I went about creating the game so that you can see the sort of approach that I take to problems like this one.

The most obvious place to start is in generating the computer answer that the person is trying to guess. We need four single digits with no repeats. The obvious solution to this is to set up an array of the ten digits, shuffle thenm and then grab four. We start with an array shuffle routine. Here's the one I wrote many years ago that I still haven't been able to improve on.

Array.prototype.shuffle = function() {
var r=[],c = this.slice(0);
while (c.length) r.push(c.splice(Math.random() * c.length, 1)[0]);
return r;

To have the computer establish a four digit answer for us to try to guess that doesn't have any repeats we create a function to return an array of four randomly shuffled digits. I didn't have such a routine in my collection but it is trivial to write one.

var computer = function() {
return [0,1,2,3,4,5,6,7,8,9].shuffle().slice(6);

Just as a reminder the 6 in the slice command tells it to skip the first six entries and then copy the rest. As we start with ten values skipping the first six means we get four returned.

Now the problem requires two different comparisons. We need one to check for the same digit in the same position and one that simply checks if the digit appears anywhere in the other array. Now these are relatively straightforward array comparisons - one that returns the values that match between two arrays and one that finds the intersection of the two arrays. Were we to allow the same digits to be repeated then we'd need a different method instead of the intersect one.

Array.prototype.matches = function(c) {return this.filter(function(a,b) {return a === c[b];});};
Array.prototype.intersect = function(c) {
var n = {};
this.forEach(function(i) {c.forEach(function(j) {if(i===j) n[i] = '';});},this);

The matches method returns all of the entries from our first array that have the same value in the same position in the second array (what the game calls bulls) while the intersection method finds all the entries that appear in both arrays and returns them without any duplications. The test for duplicates converts the values returned to strings but as we are only interested in the number of entries returned that doesn't matter in this case. The number of cows is the number of entries in the intersection less the number of exact matches.

The next step is to create a web page so we can read in the four digit guess that the player makes. This involved writing the most new code of the entire exercise but most of it is actually HTML. Much of the JavaScript while unique to this particular page can easily be copied from examples and simply expanded to cover what we need by combining the examples together. In fact we will not even need much in the way of JavaScript validation as modern HTML allows us to define that each of the four inputs is required and must contain a single digit number. The only validation left to test via the JavaScript is that none of the four numbers is a repeat of any of the others and a simple regular expression takes care of that.

<!DOCTYPE html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
input {width:15px;};
<title>Bulls and Cows</title>
<input type="text" required pattern="^\d$">
<input type="text" required pattern="^\d$">
<input type="text" required pattern="^\d$">
<input type="text" required pattern="^\d$">
<button type="text">Guess</button>
<p id="result"></p>
(function() {
'use strict';
var b = document.getElementsByTagName('button')[0],
res = document.getElementById('result');
b.addEventListener('click', function(e) {
var ck, player = [];
[]'input'), function(el) { player.push(+el.value);
ck = player.join('');
if(ck.match(/(\d)\1+/g)) res.innerHTML = 'No repeats allowed';
else res.innerHTML = '';
}, false);

The next step is to combine the above code together. This will give us a basic working game. We can then build on additional features as required once we have the game working.

As you can see, the JavaScript to generate the basic functionality of this game is not all that much and about half of it is defining three Array methods that any JavaScript programmer ought to have in their collection of script samples anyway.

From here we can look at adding further features such as counting the number of unsuccessful guesses made, providing a way to reset the game without having to reload the page and perhaps having the cursor jump automatically from one field to the next instead of the player having to tab but I'll leave adding the extra features to you.

Perhaps the most noticeable thing about this code when compared to the code the person was asking for help with is that even with all the HTML this code is still only a fraction of the length of their non-functional JavaScript. The biggest problem with the approach they were trying to use and the reason they couldn't get things to work was they were trying to treat the values as four digit numbers instead of as an array of four one digit numbers. That by itself requires dramatically more code to do the equivalent of the two array compare functions.


This article written by Stephen Chapman, Felgall Pty Ltd.

go to top

FaceBook Follow
Twitter Follow