Perlessence
Blog
Set Game in JS with YUI
I grew up on a game called Set. For those of you too lazy to click, it's a pattern recognition card game. You start by laying out 12 cards. Each card has symbols with four attributes: shape (squiggles, pills, diamonds), color (red, purple, green), fill (solid, empty, semi-filled), and count (1, 2 or 3). The goal of the game is to make the most Sets of three cards. Here's where it gets tricky. Three cards constitute a set if, for each attribute, all the cards are either all the same or all different. See the end of the post for examples.
I used several elements of YUI to build this game. The Dom and Event utility classes were used for basic element manipulation and for attaching event listeners. I used the Button class for creating the playing cards. Each card on the playing field is a Button instance of type checkbox, since a card can toggle between selected and not. I used CSS in combination with the classes that YUI attaches to each button on certain events to change the border of the card to indicate it is selected. I used the focus property (and its yui-button-focus class) to highlight a button in the Find Third method.
The code went through many iterations, in a sort of agile development way. I only wrote code as I needed it, but I also did think ahead in the architecture and planned out the methods I knew I'd need. I found that it was easier--at least on such a small project--to code for functionality first and then go back and optimize as I learned what was and wasn't being reused. My rule of thumb, seconded by dormando in his Ops Mantras post, is that the second time I find myself writing the same/similar code, it's time to refactor it out.
The images for the cards were stolen (for testing purposes only!) from the Set official site and are
still named according to their convention, which is a simple integer.gif (e.g., 81.gif). At first I have
an array of objects hardcoded to the attribute values of each image
this.cards[81] = {shape: 'squiggle', color : 'green', ... } but I found that I could
dynamically create this array because of the way the cards were numbered. Rather than store each property
as a string, I store an integer that maps to a global attributes array (e.g. shape : 0 corresponds to
a squiggle) and translate for the few times that I need to show pretty text. Long-term, I plan on
renaming the images to match the index values of the corresponding attributes (e.g., 3012.gif would
be shape 3, color 0, count 1, fill 2). And even more long-term, it would be fun to play with PHP's
image functions to dynamically generate the images.
The biggest challenge was actually finishing this project. If you view the JS source, you'll see I still have features I want to implement, but the game is fully functional with a couple of little addons. It was a nice challenge in algorithm design; as that's not my strength, I don't doubt it can be optimized further. Still, I'm happy with the way it turned out and hope you have fun with it.
Play game and view source code
Valid set:
- Color : same (all red)
- Shape : different (one pill, one diamond, one squiggle)
- Fill : different (one empty, one solid, one semi-filled)
- Count : different (one, two, three shapes)
![]()
![]()
![]()
Invalid set
- Color : FAIL (two are green, one is purple)
- Shape : FAIL (two are diamonds, one is a pill)
- Fill : FAIL (two are empty, one is semi-filled)
- Count : same (all two shapes)
![]()
![]()
![]()
Fun with JS: Element.parentTag
Here at Gaia we have a swapSubmit function that allows you to style submit buttons by using JavaScript to change the submits to buttons and storing their value in a hidden field. It's one of the standard techniques for handling this particular problem. The problem is that it requires the form to have an ID so it can identify all the button within. Now, I know that according to web standards the form should have an ID anyway, but I've never been fond of having markup that isn't used. So as a purely theoretical exercise I decided to write a function to get around that.
The function uses recursive calls and element.parentNode to find the first parent of the calling element that is of type 'tag'. For example, I could do an onclick on a button with this function to find the form that contains the button. I wrote the function as an extension to the Element class so it can be attached to anything.
Here's how you could make a form button get the form it is in. This example would alert 'foo'.
<form id="foo">
<button name="finder" type="button" onclick="alert(this.parentTag('form').id);">Find my form!</button>
</form>
I also got bored and decided to add a second parameter to the function to find not the first matching parent element, but the topmost one in the document. I don't really see much use for it, but it was easy to add in. It wouldn't be difficult at all to add functionality to find the Nth parent up from the calling element either.
<div id="foo">
<div id="bar">
<div id="baz">
<div id="quux">
<a href="#" onclick="alert(this.parentTag('div'))">I print 'quux'</a>
<a href="#" onclick="alert(this.parentTag('div', true))">I print 'foo'</a>
</div>
</div>
</div>
</div>
Functional version of the above code (changed divs to fieldsets to stay within the example code):
Here's the actual function. Feel free to use it.
Element.prototype.parentTag = function(tag, find_top) {
var found_node; // Holds the current found node
// Checks for matching tags
var nodeMatch = function(tag, node) {
return (node.tagName.toLowerCase() == tag.toLowerCase());
}
var bubbleTagMatch = function(tag, cur_node) {
parent = cur_node.parentNode;
// If we've reached the top of the tree, get out of here
if (parent == document) {
return found_node;
}
if (find_top) {
if (nodeMatch(tag,parent)) {
found_node = parent;
}
return bubbleTagMatch(tag, parent);
}
else {
return (nodeMatch(tag, parent)) ? parent : bubbleTagMatch(tag, parent);
}
}
// Start the recursion with the calling tag
return bubbleTagMatch(tag, this);
}