Take the 2-minute tour ×
Programming Puzzles & Code Golf Stack Exchange is a question and answer site for programming puzzle enthusiasts and code golfers. It's 100% free, no registration required.

Bounty is over, thephpdeveloper wins with Conway's Game of Life

The web platform today is advancing at a rapid rate. Features like CSS3 animations, transforms, drop shadows and gradients, <canvas>, <audio> and <video> tags, SVG, WebGL, and many more mean that you can do far more in the browser, and in far less code, than ever before. Of course, many developers don't get to use those new features, because the sites and applications they work on need to be backwards compatible with ancient, moth-eaten browsers like IE6.

So, what happens if you take the harness off? Allow yourself to use any and all new features that you like? Live a little, go crazy, use weird bleeding edge features that only 1% of your users will be able to take advantage of?

Of course, with unlimited resources and the ability to talk to a server, you can do all manner of things—load megabytes of code and libraries and videos, and so on—but challenges aren't very interesting without constraints. The major constraint for this contest is: what can you do in a single, self-contained, 4k data: URI? Self-contained means it must not refer to any external resources, connect to any servers using WebSockets or XHR, or anything of the sort. If you want to embed resources like PNGs or MP3s, feel free to include data URIs within your data URI, or come up with some other clever way of embedding sub-resources. 4k means 4096 bytes, properly URI-encoded, ASCII text (you may use a base64 encoded data URI if you choose, to avoid URI encoding, but usually URI encoded text will be smaller than base64 for plain text).

To provide inspiration, the theme of the contest is StackOverflow memes. Create a unicorn-jousting game, a Jon Skeet fact generator, a freehand-circle based drawing program, or anything to do with one of the popular StackOverflow & meta.so memes.

I would encourage entries that are interactive in some way; not just a simple animation or static image, they should respond to user input, whether through events, CSS hover, scrolling, browser window resizing, or any other way you can think of. This is not a hard requirement, though; great demos that are not interactive will be considered, though interactivity would be preferred.

Your entry must run in at least one public release of at least one of the 5 major browsers (IE, Firefox, Chrome, Safari, Opera). Only mainline releases (not builds from branches or builds that require patches), with no special configuration settings, plugins, or anything else that does not come with the stock browser are allowed. Nightly builds, betas, and release candidates are fine. Please specify in your entry which browsers you've tested your entry with. There are no limitations on what technologies you may use within those constraints; you may do a pure SVG animation, a pure CSS animation, something in JavaScript using WebGL, or heck, even something that uses XML and XSLT if that strikes your fancy. If you can cram it into a valid data URI, with no external dependencies, and get a browser to run it, it's fair game.

To add to the competition here, on Monday, March 21, I will open a bounty on this question. How can I afford a bounty when I have only 101 rep? Well, all rep I gain from upvotes to this question between now and Monday will go into the bounty (up to the limit of 500 allowed for a single bounty; it would be pretty hard for me to hit that limit, though, given the rep cap). Entries will be accepted for 6 days after that; all entries must be in at least 24 hours before the bounty expires, to give me time to check them all out and evaluate them. At that point, I will accept the highest voted answer, and give the bounty to my favorite answer (which may or may not be the same as the highest voted). My criteria for awarding the bounty will include beauty, fun, clever technique, interesting use of new features, interactivity, and size. People should vote based on any criteria they want, as long as the entries meet the rules.

Here are some sources of inspiration, to get you started:

  • Chrome Experiments, a collection of demonstrations of the modern web platform
  • Mozilla Hacks, a blog about the modern web platform with many demos of new features in Firefox 4
  • JS1k, a contest for 1k JavaScript demos
  • 10k Apart, a webapp in 10k contest
  • gl64k, a demo contest currently running for 64k WebGL demos
  • Shader Toy, a set of demos of what you can do with WebGL shaders

Format for entries:

Name of entry

data:text/html,Your%20data%20URI

Works in Firefox 4 RC, Chrome 10, and Opera 11

Description of your entry; what it does, why it's neat, what clever techniques you used.

<script>
  // code in expanded form to more easily see how it works
</script>

Any credits for inspirations, any code you might have borrowed from, etc.

(StackExchange appears not to accept data URIs in links, so you'll need to embed it directly in a <pre> tag)

share|improve this question
    
@Joey I linked to a thread on SO memes for reference‌​, for anyone not familiar. Here are some of the more popular ones to get you started: unicorns, waffles, freehand circles (that is, circles or other diagrams drawn freehand in MS Paint or similar applications, commonly used for highlighting some sort of user interface bug), Jon Skeet and Chuck Norris-style "facts" about him. –  Brian Campbell Mar 18 '11 at 20:19
    
By the way, entries do not need to not need to be about StackOverflow memes in general; just pick one meme, like unicorns. In fact, I was thinking of making the theme be unicorns, but decided to open it up a little by allowing any StackOverflow meme. And heck, if you have a cool demo that doesn't fit the theme, submit it anyhow. The theme is there mostly to provide inspiration, not to restrict what you do. –  Brian Campbell Mar 19 '11 at 2:56
    
I have got an idea, but I think it will take 6 to 8 weeks to implement, could you extend the deadline a bit? –  eBusiness Mar 20 '11 at 18:55
    
@eBusiness Ha ha! No, unlike Stack Overflow, there are actually deadlines here. –  Brian Campbell Mar 20 '11 at 20:14
    
more entries please? –  mauris Mar 28 '11 at 5:07
show 1 more comment

3 Answers 3

up vote 29 down vote accepted
+100

SO meme: Everything is a meme

Everything is a meme. enough said.

Conway's Game of Life, HTML5+CSS3+JS,

3,543 3,561 3,555 bytes

data:text/html,<!DOCTYPE%20html>%0A<html><head><title>Conway's%20Game%20of%20Life%20by%20Sam-Mauris%20Yong</title><style>body{margin:20px;padding:0;font:12px/1.6%20tahoma,sen-serif;}.clr{clear:both}#ftr{padding:10px;border-top:1px%20solid%20#DDD;margin-top:10px}input[type="submit"],input[type="button"],a.btn,a.btn:visited{color:#999;-moz-border-radius:5px;-webkit-border-radius:5px;border:1px%20solid%20#EEE;color:#333;cursor:pointer;padding:4px%208px;text-decoration:none;border:1px%20solid%20#EEE;background-color:#DDD}input[type="submit"]:hover,input[type="button"]:hover,a.btn:hover{background-color:#666;border:1px%20solid%20#EEE;color:#EEE;text-shadow:#000%201px%201px%200}a.btn.%20a.btn:visited{padding:5px%208px}input[type="submit"]:focus,input[type="button"]:focus,a.btn:focus{outline:none;border:1px%20solid%20#099}</style></head><body><h1>Conway's%20Game%20of%20Life</h1><canvas%20id="c"%20width="800"%20height="600"></canvas><div%20class="clr"></div><div%20style="margin-top:5px;"><input%20type="button"%20value="Start"%20id="ctrlStart"><input%20type="button"%20value="Stop"%20id="ctrlStop"><input%20type="button"%20value="Next"%20id="ctrlNext"></div><div%20id="ftr"><i>App%20requires%20awesome%20browsers%20supporting%20HTML5.</i><br>Written%20by%20@<a%20href="http://twitter.com/thephpdeveloper">thephpdeveloper</a>%20aka%20Sam-Mauris%20Yong.</div><script>eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return%20r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new%20RegExp('\\b'+e(c)+'\\b','g'),k[c]);return%20p}('7%20$(b){j%20Q.R(b)}7%20p(b){j%20S(b)}5%20k=$("c");5%20a=k.T("U");5%209={x:V,y:W};5%20s=10;5%206=t%20B(9.x);5%20X=Y;5%20i=-1;l(i++<6.h-1){6[i]=t%20B(9.y)}7%20C(b,c){a.D="Z";a.E(b*s,c*s,s,s);a.F="11";a.G(b*s,c*s,s,s)}7%20u(b){5%20c=t%2012();4(g(b)=="H"){4(g(b.h)!="I"){5%20c=[]}13(5%20d%2014%20b){4(g(b[d])=="H"){c[d]=u(b[d])}v{4(g(b[d])=="15"){c[d]=b[d]}v{4(g(b[d])=="16"){c[d]=b[d]}v{4(g(b[d])=="17"){((b[d]==m)?c[d]=m:c[d]=n)}}}}}}j%20c}7%208(b){j%20g(b)=="I"||!b?n:m}7%20J(b,d){5%20c=0;4(b>0){4(8(6[b-1][d])){c++}4(d>0){4(8(6[b-1][d-1])){c++}}4(d<9.y-1){4(8(6[b-1][d+1])){c++}}}4(b<9.x-1){4(8(6[b+1][d])){c++}4(d>0){4(8(6[b+1][d-1])){c++}}4(d<9.y-1){4(8(6[b+1][d+1])){c++}}}4(d>0){4(8(6[b][d-1])){c++}}4(d<9.y-1){4(8(6[b][d+1])){c++}}j%20c}7%20K(){5%20d=u(6);5%20c=0;l(c<6.h){5%20f=0;l(f<6[c].h){5%20b=6[c][f];5%20e=J(c,f);4(e<2||e>3){d[c][f]=n}4(e==3){d[c][f]=m}f++}c++}6=d}7%20o(){a.D="#18";a.E(0,0,p(k.19),p(k.1a));5%20b=0;l(b<6.h){5%20c=0;l(c<6[b].h){a.F="#1b";a.G(b*s,c*s,s,s);4(6[b][c]){C(b,c)}c++}b++}}5%20q;7%20w(){K();o()}7%20L(){q=M.1c(w,1d)}7%20N(){q=M.1e(q)}7%20z(c){4(r){5%20b=O.P((c.1f-p(k.1g))/s);5%20d=O.P((c.1h-p(k.1i))/s);6[b][d]=!6[b][d];o()}}5%20r=n;o();k.1j=7(b){r=m};k.1k=7(b){z(b)};k.1l=7(b){z(b);r=n};$("1m").A=N;$("1n").A=w;$("1o").A=L;',62,87,'||||if|var|space|function|evalbool|max|||||||typeof|length||return||while|true|false|draw||time_var|dodrawing||new|deepObjCopy|else|next|||editorDraw|onclick|Array|drawCell|fillStyle|fillRect|strokeStyle|strokeRect|object|undefined|countAliveNeighbours|update|start|window|stop|Math|floor|document|getElementById|parseInt|getContext|2d|80|60|lastSpace|null|black||white|Object|for|in|string|number|boolean|fff|width|height|ccc|setInterval|250|clearInterval|pageX|offsetLeft|pageY|offsetTop|onmousedown|onmousemove|onmouseup|ctrlStop|ctrlNext|ctrlStart'.split('|'),0,{}))</script></body></html>

This is Conway's Game of Life written myself for HTML5 with canvas and CSS3. I wrote it for fun during the period of 10K Apart competition but I did not submit this for the competition.

Base64 encoded version spans over 4.61KB of data, whereas the original version is ~3543 bytes.

To compress the size: Javascript code minified by YUI online compressor, then by Dean Edwards' packer. CSS Code minified by YUI online compressor. Uses jQuery library from Google API Library. Valid HTML5 and CSS3 (experimental version of the w3 validator).

To play:

  • Black box represents a live cell, white represents dead cell.
  • Click on a box to mark a live cell, click again to mark it dead.
  • Press <Start> to run the simulation, <Stop> to pause, and <Next> to show the next step
  • Runs awesome on Internet Explorer 9, Firefox 4 (and Firefox 3 as well), Safari 5, and Google Chrome.

The human-readable (robots shall die) version:

<!DOCTYPE html>
<html>
    <head>
        <title>Conway's Game of Life by Sam-Mauris Yong</title>
        <style>
            body{
                margin:20px;
                padding:0;
                font:12px/1.6 tahoma,sen-serif;
            }
            .clr{
                clear:both
            }
            #ftr{
                padding:10px;
                border-top:1px solid #DDD;
                margin-top:10px
            }
            input[type="submit"],input[type="button"],a.btn,a.btn:visited{
                color:#999;
                -moz-border-radius:5px;
                -webkit-border-radius:5px;
                border:1px solid #EEE;
                color:#333;
                cursor:pointer;
                padding:4px 8px;
                text-decoration:none;
                border:1px solid #EEE;
                background-color:#DDD
            }            
            input[type="submit"]:hover,input[type="button"]:hover,a.btn:hover{
                background-color:#666;
                border:1px solid #EEE;
                color:#EEE;
                text-shadow:#000 1px 1px 0
            }
            a.btn,a.btn:visited{
                padding:5px 8px
            }
            input[type="submit"]:focus,input[type="button"]:focus,a.btn:focus{
                outline:none;
                border:1px solid #099
            }
        </style>
    </head>
    <body>
        <h1>Conway's Game of Life</h1>
    <canvas id="c" width="800" height="600"></canvas>
    <div class="clr"></div><div style="margin-top:5px;">
        <input type="button" value="Start" id="ctrlStart">
        <input type="button" value="Stop" id="ctrlStop">
        <input type="button" value="Next" id="ctrlNext">
    </div>
    <div id="ftr">
        <i>App requires awesome browsers supporting HTML5.</i>
        <br>
        Written by @<a href="http://twitter.com/thephpdeveloper">thephpdeveloper</a> aka Sam-Mauris Yong.
    </div>
    <script>
        function $(i){
            return document.getElementById(i);
        }
        function p(v){
            return parseInt(v);
        }
        var k = $("c");
        var a = k.getContext('2d');

        var max = {
            x: 80,
            y: 60
        };

        var s = 10;
        var space = new Array(max.x);
        var lastSpace = null;
        var i = -1;
        while(i++ < space.length - 1){
            space[i]= new Array(max.y);
        }

        function drawCell(x,y){
            a.fillStyle = "black";
            a.fillRect(x * s, y * s, s, s);
            a.strokeStyle = "white";
            a.strokeRect(x * s, y * s, s, s);
        }

        function deepObjCopy (dupeObj) {
            var retObj = new Object();
            if (typeof(dupeObj) == 'object') {
                if (typeof(dupeObj.length) != 'undefined')
                    var retObj = [];
                for (var objInd in dupeObj) {
                    if (typeof(dupeObj[objInd]) == 'object') {
                        retObj[objInd] = deepObjCopy(dupeObj[objInd]);
                    } else if (typeof(dupeObj[objInd]) == 'string') {
                        retObj[objInd] = dupeObj[objInd];
                    } else if (typeof(dupeObj[objInd]) == 'number') {
                        retObj[objInd] = dupeObj[objInd];
                    } else if (typeof(dupeObj[objInd]) == 'boolean') {
                        ((dupeObj[objInd] == true) ? retObj[objInd] = true : retObj[objInd] = false);
                    }
                }
            }
            return retObj;
        }

        function evalbool(v){
            return typeof(v) == 'undefined' || !v ? false : true;
        }

        function countAliveNeighbours(x,y){
            var l = 0;
            // left side
            if(x > 0){
                if(evalbool(space[x-1][y])){
                    l++;
                }
                if(y > 0){
                    if(evalbool(space[x-1][y-1])){
                        l++;
                    }
                }
                if(y < max.y-1){
                    if(evalbool(space[x-1][y+1])){
                        l++;
                    }
                }
            } // left side

            // right side
            if(x < max.x - 1){
                if(evalbool(space[x+1][y])){
                    l++;
                }
                if(y > 0){
                    if(evalbool(space[x+1][y-1])){
                        l++;
                    }
                }
                if(y < max.y-1){
                    if(evalbool(space[x+1][y+1])){
                        l++;
                    }
                }
            }
            // right side

            if(y > 0){
                if(evalbool(space[x][y-1])){
                    l++;
                }
            }

            if(y < max.y-1){
                if(evalbool(space[x][y+1])){
                    l++;
                }
            }

            return l;
        }

        function update(){
            var t = deepObjCopy(space);
            var x = 0;
            while(x < space.length){
                var y = 0;
                while(y < space[x].length){
                    var cell = space[x][y];
                    var nalive = countAliveNeighbours(x,y)

                    if(nalive < 2 || nalive > 3){
                        t[x][y] = false;
                    }
                    if(nalive==3){
                        t[x][y] = true;
                    }

                    y++;
                }
                x++;
            }
            space = t;
        }

        function draw(){
            a.fillStyle = "#fff";
            a.fillRect(0, 0, p(k.width), p(k.height));
            var x = 0;
            while(x < space.length){
                var y = 0;
                while(y < space[x].length){
                    a.strokeStyle = "#ccc";
                    a.strokeRect(x*s, y*s, s,s);
                    if(space[x][y]){
                        drawCell(x, y);
                    }
                    y++;
                }
                x++;
            }
        }

        var time_var;

        function next(){
            update();
            draw();
        }

        function start(){
            time_var = window.setInterval(
            next, 250);
        }

        function stop(){
            time_var = window.clearInterval(time_var);
        }
        function editorDraw(e){
            if(dodrawing){
                var x = Math.floor((e.pageX-p(k.offsetLeft))/s);
                var y = Math.floor((e.pageY-p(k.offsetTop))/s);
                space[x][y] = !space[x][y];
                draw();
            }
        }
        var dodrawing = false;

        draw();
        k.onmousedown = function(e){
            dodrawing=true;
        }
        k.onmousemove = function(e){
            editorDraw(e);
        }
        k.onmouseup = function(e){
            editorDraw(e);
            dodrawing=false;
        }
        $("ctrlStop").onclick = stop;
        $("ctrlNext").onclick = next;
        $("ctrlStart").onclick = start;
    </script>
</body>
</html>

A class in history:

  1. Modified to remove dependency from jQuery as well as URI-encoding all spaces. Improved code in many ways (that I can't remember of).
  2. Fixed bug in calculation of alive neighbours and refactored some code to reduce size.
share|improve this answer
1  
This is very cool, but isn't the jQuery library an external dependency? –  Gareth Mar 22 '11 at 9:16
    
@Gareth ahh just saw it. dang. –  mauris Mar 22 '11 at 9:53
    
Hosted: ebusiness.hopto.org/gol.htm –  eBusiness Mar 22 '11 at 13:09
2  
You don't require jQuery for much in here; you could probably replace it with raw DOM access without expanding your code too much, possibly at the expense of IE compat (but canvas isn't compatible with older IE either, so you wouldn't be losing much). If that expands it so you need to cut it down a little more to fit, you have some descriptive text you could remove, as well as some /> sequences that you can replace with > since you're not writing XHTML. Also, remember to properly URI-encode your result; while browsers might accept spaces in URIs, they aren't technically valid. –  Brian Campbell Mar 22 '11 at 13:14
4  
A few more things you could use to save space, if you wind up needing some. There's no actual need for <html>, <head>, and <body> tags (nor their closing tags). They're implicit in HTML, and will be added in the appropriate places by the browser. –  Brian Campbell Mar 22 '11 at 13:19
show 5 more comments

Sorry to dig up an old thread, but I saw this challenge on the side bar and I just couldn't resist. I don't mind that the challenge is over really, it was just fun coming up with something.

Maybe we could have another round?

Anyway, my submission:

Edit

Sorry to dig this up again, but it was bothering me for ages that I couldn't get this under 1KB. Now I've done it!

Interactive, Shaded Cube:

960 987 1082 1156 1182 1667 1587 bytes!, HTML+CSS3+JS

data:text/html,<script>X='position:absolute;',S=Math,l=S.sin,V=S.cos,D='style',$='getElementsByTagName',E=H=G=(L=K=99)/2,q=-G,j=1e4,Y=',';function _(p,r,D){A=[],B=l(r),C=V(r);for(z=6;z--;)v=z*3,A.unshift({x:l(D)*(B*p[v+1]+C*p[v+2])+V(D)*p[v]+K,y:C*p[v+1]-B*p[v+2]+K});return A}function R(a,b,c){F=x[v++],a=N[a],b=N[b],c=N[c];F.setAttribute(D,X+'-webkit-transform:matrix('+(a.x-b.x)/L+Y+(a.y-b.y)/L+Y+(c.x-b.x)/L+Y+(c.y-b.y)/L+Y+b.x+Y+b.y+');opacity:'+(((b.y-a.y)/(b.x-a.x)-(c.y-a.y)/(c.x-a.x)<0)^(a.x<b.x^a.x>c.x)));F[$]('b')[0][D].background='rgb(0,'+(d(a,c)+d(a,b))+',0)';return R}function d(P,O){W=P.x-O.x,Q=P.y-O.y;return S.sqrt(W*W+Q*Q)|0}onload=function(){P=document;for(o=6;o--;)P.body.appendChild(P.createElement('P')).innerHTML='<b '+D+'="'+X+'width:99;height:99"></b>';x=P[$]('p');onmousemove=function(e){J=e.pageX-K;U=e.pageY-K};setInterval(function(){N=_([q,q,q,G,G,q,q,G,q,q,q,G,G,q,G,G,G,G],E+=J/j,H+=U/j);R(2,0,3)(5,1,2)(0,2,1)(4,3,0)(3,4,5)(1,5,4)})}</script>

Move your mouse.

Works in Chrome (18.something, but should work on most recent ones).

I golfed this pretty well, I saved a few characters using a trick I thought was pretty cool: Say you have the following:

function g(x){alert("hello "+x+"!")}
g("dave");g("martin");g("alice");g("rose");g("bob");g("helen");g("jo");

characters can be saved by returning the function within itself and doing the following:

function g(x){alert("hello "+x+"!");return g}
g("dave")("martin")("alice")("rose")("bob")("helen")("jo");

Savings depends on how many function calls you have. This is probably better for obfuscation rather than golfing though.

I also saved a characters by replacing 1000 with 1e4, giving the Math class, and some of its functions, aliases. Using variables for repeated strings (quite hard to find some of these saves). Also, I had the word style in my code a few times; some of them were strings and others were identifiers like element.style.whatever. Assigning the string to a variable (D='style) allowed me to replace the strings with D and to replace the identifiers like so element[D].whatever.

Latest Edit: sorry to bring up an old comp', but some ideas how to shorten this just came to me!

share|improve this answer
    
Nice, thanks for creating an entry! This is pretty cool. I like your golfing techniques, too. –  Brian Campbell Mar 16 '12 at 14:06
    
Nice, but you have plenty of space left, you should add more to it :) How about a Rubik simulator? –  aditsu Mar 6 '13 at 17:51
    
onmousemove could be modified: onmousemove=function(e){U=K-e.pageX;J=e.pageY-K};. This way cube will roll towards the mouse pointer (more intuitive). –  Victor Jan 15 at 20:00
    
If you are happy with ECMAScript 6 (it only works in Firefox (SpiderMonkey), as far as I know), then function g(x){alert("hello "+x+"!");return g} g("dave")("martin")("alice")("rose")("bob")("helen")("jo"); can become g=x=>alert("hello "+x+"!"),g;g("dave")("martin")("alice")("rose")("bob")("helen")("jo"); –  toothbrush Feb 26 at 13:45
add comment

JavaScript 489 chars

This plays Guess a Number game.

data:text/html,<!DOCTYPE%20html><html><body><h1>Guess the number between 0 and 100</h1><p id="p">good luck</p><form><input id="i" type="text"></input></form><br><button onclick="h()">Try</button><script>var r=Math.round(100*Math.random());function h(){var a=document.getElementById("i").value;var anum="/(^\d+$)/";var res="is not a number!";if (!isNaN(a)){if(a<r) res="higher";else if(r<a) res="lower";else res="you win";}document.getElementById("p").innerHTML=res;}</script></body></html>

I worked it out with this code:

<!DOCTYPE html>
<html>
<head>
</head>
<body onload="g()">
<p id="p"></p>

<script>
f=function(x){
var y=x.replace(" ","%20");
window.location.assign("data:text/html,"+y);
}
g=function(){
var a="<!DOCTYPE html><html><body><h1>Guess the number between 0 and 100</h1><p id=\"p\">good luck</p>"+
"<form><input id=\"i\" type=\"text\"><\/input></form>"
+"<br><button onclick=\"h()\">Try<\/button><script>"
+document.getElementById("s").innerHTML+
"<\/script><\/body><\/html>";
f(a);
}
</script>
<script id="s">
var r=Math.round(100*Math.random());
function h(){
var a=document.getElementById("i").value;
var anum="/(^\d+$)/";
var res="is not a number!";
if (!isNaN(a)){
if(a<r) res="higher";
else if(r<a) res="lower";
else res="you win";
}
document.getElementById("p").innerHTML=res;
}
</script>

</body>
</html>
share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.