Hardcore JavaScript or Power of 30 lines

Hey there! About one year ago on HackerNews and Reddit was submitted amazing example of code - Spreadsheet program in under 30 lines of JavaScript by Ondřej Žára (also author of Rot.js - ROguelike Toolkit). It's inspired Russian developers community to do something like this. So, on the Habrahabr (largest collective blog about technology in Europe) appeared trend named a "Week of 30 lines".

I thought that it can be interesting for others and collect most popular examples - 10 tiny games on JavaScript. Not all of them are really written in 30 lines, but I am sure, that many of you could find something helpful.

Tag

Demo on JSFiddle.

The basic principle is a model that contains an array of numbers from 0 to 15 and rules of its changes. When building the model there are creates a random order of numbers, which is adjusted if it's impossible to solve. To display the model using 16 div that are updated with each step.

var fifteen = {  
  Move: {up: -4, left: -1, down: 4, right: 1},
  order: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15].sort(function() { return Math.random()-.5; }).concat(0),
  hole: 15,
  isCompleted: function() { return !this.order.some(function(item, i) { return item > 0 && item-1 !== i; }); },
  go: function(move) {
    var index = this.hole + move;
    if (!this.order[index]) return false;
    if (move == fifteen.Move.left || move == fifteen.Move.right)
      if (Math.floor(this.hole/4) !== Math.floor(index/4)) return false;
    this.swap(index, this.hole);
    this.hole = index;
    return true; },
  swap: function(i1, i2) { var t = this.order[i1]; this.order[i1] = this.order[i2]; this.order[i2] = t; },
  solvable: function(a) {
    for (var kDisorder = 0, i = 1, len = a.length-1; i < len; i++)
      for (var j = i-1; j >= 0; j--) if (a[j] > a[i]) kDisorder++;
    return !(kDisorder % 2); } };
if (!fifteen.solvable(fifteen.order)) fifteen.swap(0, 1);  
var box = document.body.appendChild(document.createElement('div'));  
for (var i = 0; i < 16; i++) box.appendChild(document.createElement('div'));  
window.addEventListener('keydown', function(e) {  
  if (fifteen.go(fifteen.Move[{39: 'left', 37: 'right', 40: 'up', 38: 'down'}[e.keyCode]])) {
    draw(); if (fifteen.isCompleted()) {
      box.style.backgroundColor = "gold";
      window.removeEventListener('keydown', arguments.callee); } }});
draw();  
function draw() {  
  for (var i = 0, tile; tile = box.childNodes[i], i < 16; i++) {
    tile.textContent = fifteen.order[i]; tile.style.visibility = fifteen.order[i]? 'visible' : 'hidden'; } };

Arkanoid (30 lines of code)

Demo on JSFiddle.

In General, the result is the most authentic to the original game.

  • The angle of reflection of the ball depends (slightly) on the side of the racket it fell. When it's falling to the left part then ball flies off to the left. When it hits to the right part, then to the right. In both cases, the projectile is reflected at a slightly larger angle than when it strikes form the middle. If it hits to center, then ball flies off at a 45° angle and its direction on the x-axis doesn't changes.

  • The scoring (of course).

  • Given three lives. However, if the ball gets to the bottom face the game won't reseted and ball just bounces off and taken away one life.

Among the shortcomings: the ball sometimes behaves inappropriately and breaks too many bricks.

(function (fld, pF, px, py, dx, dy, lifes, score) {
  var cycle = setInterval(function () {
    var bx = pF(ball.style.left = pF(ball.style.left) + dx + 'px'),
      by = pF(ball.style.top = pF(ball.style.top) + dy + 'px'),
      row = ((by - 30) / 14) | 0, col = (bx / 32) | 0;

    if (bx < 0 && dx < 0 || bx >= 314 && dx > 0) dx *= -1;
    if (bx + 6 >= px && bx + 6 <= px + 64 && by >= 259 && by <= 264) {
      dy *= -1;
      if (bx + 6 <= px + 21) dx = -6;
      else if (bx + 6 >= px + 43) dx = 6;
      else if (Math.abs(dx) == 6) dx = (dx * 2 / 3) | 0;
    }
    if (by < 0) dy *= -1;
    if (by >= 288 && !--lifes) clearInterval(cycle), alert('Game over!');
    if (by >= 288 && lifes) dy *= -1, lifesNode.innerHTML = lifes;
    if (by >= 18 && by <= 100 && fld[row * 10 + col].className != 'removed') {
      dy *= -1, fld[row * 10 + col].className = 'removed';
      if (dx < 0 && ((bx | 0) % 32 < 10 || (bx | 0) % 32 > 22)) dx *= -1;
      if (dx > 0 && (((bx + 12) | 0) % 32 < 10 || ((bx + 12) | 0) % 32 > 22)) dx *= -1;
      scoreNode.innerHTML = ++score;
      if (score == 50) clearInterval(cycle), alert('Victory!');
    }
  }, 1000 / 60);

  document.addEventListener('mousemove', function (e) {
    px = (e.pageX > 40) ? ((e.pageX < 290) ? e.pageX - 40 : 256) : 0;
    paddle.style.left = px + 'px';
  }, false);
}(field.children, parseFloat, 129, 270, -4, -4, 3, 0));
How works collision detection

The area with brick is bounded from above and from below, therefore to check the collision makes sense only when the projectile in this area. The height and width of the bricks is determined. So, taking the coordinates of the ball, we'll be able to determine the number of brick in which he hits. If there are X and Y coordinates and the width and height of the bricks equal to width and height, then taking the integer part from the particular of X / width and Y / height, we get exactly the column number and row col and row of the current target. Knowing this we can calculate the number of this item: number = row * rowLength + col.

Sokoban (36 lines of code)

Demo on JSFiddle.

If you don't know what is a Sokoban - Wiki.

(function() {
var levelData = ["  wwwww ","www   w ","w b w ww","w w  p w","w    w w","wwbwp  w"," wy  www"," wwwww  "], level = [[], [] ,[] ,[] ,[] ,[] ,[] ,[]];  
var x, y, dx, dy, cell, fwdCell, fwd2cell, field = document.getElementById('field');  
for (var n = 0; n < levelData.length; n++)  
    for (var m = 0; m < levelData[n].length; m++) {
        level[n].push(div = document.createElement('div'));
        div.className = levelData[n][m] == ' ' ? 's' : levelData[n][m];
        field.appendChild(div); 
        if (levelData[n][m] == 'y')  x = m, y = n;
    }
window.addEventListener('keydown', function(e) {  
    if (e.keyCode == 37) dx = -1, dy = 0;
    else if (e.keyCode == 39) dx = 1, dy = 0;
    else if (e.keyCode == 38) dx = 0, dy = -1;
    else if (e.keyCode == 40) dx = 0, dy = 1;
    else return;
    if ((fwdCell = level[y + dy][x + dx]).className == 'w') return;
    var cell = level[y][x];

    if (fwdCell.className == 'b' || fwdCell.className == 'a') {
        var fwd2cell = level[y + dy + dy][x + dx + dx];
        if (fwd2cell.className == 'w' || fwd2cell.className == 'b' || fwd2cell.className == 'a')
            return;
        fwd2cell.className = fwd2cell.className == 'p' ? 'a' : 'b';
        fwdCell.className = fwdCell.className == 'a' ? 'p' : 's';
    }
    if (fwdCell.className == 'w') return;
    cell.className = cell.className == 'Y' ? 'p' : 's';
    fwdCell.className = fwdCell.className == 'p' ? 'Y' : 'y';
    x += dx; y += dy;
    for (var n = 0; n < level.length; n++) 
        for (var m = 0; m < level[n].length; m++) 
            if (level[n][m].className == 'b') return;
    alert('You win!');
});
})();

About the code I can say just that level map contains in levelData array where w - wall, b - box, s or white space - empty place and y - player.

Tetris (36 lines of code)

Demo on JSFiddle.

  • All shapes of Tetris
  • Keyboard control
  • UP — figurines spinning in a clockwise direction
  • DOWN — accelerate falling
  • The falling speed increases gradually
  • Points are counted
var fs = "1111:01|01|01|01*011|110:010|011|001*110|011:001|011|010*111|010:01|11|01:010|111:10|11|10*11|11*010|010|011:111|100:11|01|01:001|111*01|01|11:100|111:11|10|10:111|001", now = [3,0], pos = [4,0];  
var gP = function(x,y) { return document.querySelector('[data-y="'+y+'"] [data-x="'+x+'"]'); };  
var draw = function(ch, cls) {  
    var f = fs.split('*')[now[0]].split(':')[now[1]].split('|').map(function(a){return a.split('')});
    for(var y=0; y<f.length; y++)
        for(var x=0; x<f[y].length; x++)
            if(f[y][x]=='1') {
                if(x+pos[0]+ch[0]>9||x+pos[0]+ch[0]<0||y+pos[1]+ch[1]>19||gP(x+pos[0]+ch[0],y+pos[1]+ch[1]).classList.contains('on')) return false;
                gP(x+pos[0]+ch[0], y+pos[1]+ch[1]).classList.add(cls!==undefined?cls:'now');
            }
    pos = [pos[0]+ch[0], pos[1]+ch[1]];
}
var deDraw = function(){ if(document.querySelectorAll('.now').length>0) deDraw(document.querySelector('.now').classList.remove('now')); }  
var check = function(){  
    for(var i=0; i<20; i++)
        if(document.querySelectorAll('[data-y="'+i+'"] .brick.on').length == 10) 
            return check(roll(i), document.querySelector('#result').innerHTML=Math.floor(document.querySelector('#result').innerHTML)+10);
};
var roll = function(ln){ if(false !== (document.querySelector('[data-y="'+ln+'"]').innerHTML = document.querySelector('[data-y="'+(ln-1)+'"]').innerHTML) && ln>1) roll(ln-1); };  
window.addEventListener('keydown', kdf = function(e){  
    if(e.keyCode==38&&false!==(now[1]=((prv=now[1])+1)%fs.split('*')[now[0]].split(':').length) && false===draw([0,0], undefined, deDraw())) draw([0,0],undefined, deDraw(), now=[now[0],prv]);
    if((e.keyCode==39||e.keyCode==37)&&false===draw([e.keyCode==39?1:-1,0],undefined,deDraw())) draw([0,0],undefined,deDraw());
    if(e.keyCode == 40)
        if(false === draw([0,1], undefined, deDraw())) {
            if(draw([0,0], 'on', deDraw())||true) check();
            if(false === draw([0,0], undefined, now = [Math.floor(Math.random()*fs.split('*').length),0], pos = [4,0])) { 
                toV=-1; 
                alert('Your score: '+document.querySelector('#result').innerHTML); 
            }
        }
});
toF = function() {  
    kdf({keyCode:40});
    setTimeout(function(){if(toV>=0)toF();}, toV=toV>0?toV-0.5:toV);
}
toF(toV = 500);  

The principle of working:

  • All figures are stored in the variable fs="1111:01/01/01/01*011/110:010/011/001*..." as a string. To get an array of figures — do the split('*'). Each figure contains from 1 ("square") to 4 (for L, G and "pyramids") states for possibility of rotation. Each state are separated by a colon. Correspondingly to obtain a single state — split(':'). Let's say got a "pyramid" — "010/111", here we do split('|') and then get a finite two-dimensional array for one state of one figure. 0 - empty space (no need to draw), 1 - need to draw.

  • All movements of figures are doing by two functions — "erase figure" and "try to build". At any movement to the left or to the right, or down, or even rotation firstly erase the current displayed figure, then try to build a figure at the new location.

  • Movements made from the keyboard. The timer just calls the function that handles the keyboard's event. On each call to the timeout — the time before the call is reduced.

  • If you cannot move figure down — then it ran into the previous figure, or the bottom. In this case, check whether there are no filled lines and draw a new figure at the top. If drawing of new figure failed — game over!

Racing (29 lines of code)

Demo on JSFiddle.

(function(elid, width, height, speed, strength){
    var canvas = document.querySelector(elid),
            ctx = canvas.getContext("2d"),
            pos = 0, blocks = [];
    canvas.width = width; canvas.height = height;
    ctx.fillStyle = "black";
    var game = setInterval(function(){
        if( Math.random() < strength) blocks.push([Math.random()*(width-10),-10]);
        ctx.clearRect(0,0,width,height);
        ctx.fillRect(pos,height-50,10,40);
        for(var i = 0; i < blocks.length; i++){
            ctx.fillRect(blocks[i][0],blocks[i][1],10,10);
            if( blocks[i][1] > height - 60 && blocks[i][1] < height - 10 && Math.abs( pos - blocks[i][0]) < 10 ){
                clearInterval(game);
                alert("Game over. You have " + Math.floor(1000 * strength) + " points.");
            }
            if( blocks[i][1] > height - 5 ){
                blocks.splice( i, 1);
                i--;
            } else {
                blocks[i][1] += 5;
            }
        }
        strength += 0.001;
    },speed);
    document.addEventListener('mousemove', function (e) {
        pos = (e.pageX > 0) ? ((e.pageX < width) ? e.pageX : width-10) : 0;
    }, false);
})("#canvas",400,300,33,0.05);

Roguelike/RPG (30 lines of code)

Demo on JSFiddle.

About the game

You play as a brave knight who must save the Princess from the dragon. On the way to the dragon's cave with the knight does adventure like battles with monsters and visits to small villages. After battle with monsters, the hero gets the gold and experience. Gold can be spent for treatment and purchase healing potions in the shops that are on the way. Life experiences can raise the level of the hero, which increases his attack and defense. The aim of the game is comprehensively prepare for a showdown with the dragon, and then defeat him and free the Princess.

(function(elid, wi, he, exp, pot, gld, hp, lvl, cur_e, e_sz){
    var hit = function(){ evs[cur_e][8]-=lvl*4+6; if(evs[cur_e][8]<=0) {
        alert("Monster defeated and you got "+evs[cur_e][10]+"EXP and $"+evs[cur_e][11]);
        exp+=evs[cur_e][10];gld+=evs[cur_e][11]; while(exp>=lvl*10){exp-=lvl*10;lvl++;alert("Level Up!")}
        cur_e++; if(cur_e==e_sz) alert("Victory!") } else {
        hp-=Math.max(0,evs[cur_e][9]-lvl*2-1); if(hp<=0) alert("Game Over!") } }
    var use = function(){ if (pot>0){pot--;hp+=10;if(hp>100)hp=100} }
    var nxt = function(){ cur_e++ }, battle = ["Attack","Use Potion +10HP","Skip Battle",hit,use,nxt],
    canvas=document.querySelector(elid), ctx=canvas.getContext("2d"), evs=[], e_tp=[
    ["Shop", "Wizard provides his services", "Buy Potion $20", "Full Heal $100", "Leave",
     function(){ if(gld>=20){gld-=20;pot++} }, function(){ if(gld>=100){gld-=100;hp=100} }, nxt ],
    ["Skeleton", "A terrible skeleton on your way"].concat(battle,70,15,25,100),
    ["Goblin", "Green goblin wants to get your money"].concat(battle,50,10,15,70),
    ["Slime", "What the strange jelly monster?"].concat(battle,20,6,7,30),
    ["Dragon", "Omg! It is evil Dragon!","Attack","Use Potion +10HP","-",hit,use,,300,25,100,1000 ] ],
        q=e_tp.length-1; canvas.width=wi; canvas.height=he; for (var i=0;i<e_sz-1;i++)
     evs.push( e_tp[Math.floor(Math.random()*q)].slice(0) ); evs.push( e_tp[q].slice(0) );
    var game = setInterval(function(){ ctx.clearRect(0,0,wi,he);
        ctx.fillText("NanoRPG in 30 lines of JavaScript by ripatti",10,15);
        ctx.fillText("LVL "+lvl+"  HP "+hp+"/100  EXP "+exp+"/"+lvl*10+"  ATK "+(lvl*4+6)+
         "  DEF "+(lvl*2+1)+"  Gold $"+gld+"  Potions "+pot,10,30);
        for (var i=0;i<e_sz;i++) ctx.fillText((i==e_sz-1||i<=cur_e)?evs[i][0]:"??",i*50+15,70);
        ctx.fillText("@",cur_e*50+25,60); ctx.fillText(evs[cur_e][1],20,100);
        if (evs[cur_e].length>8) ctx.fillText("Enemy HP "+evs[cur_e][8],250,100);
        for (var i=0;i<3;i++) { ctx.strokeRect(i*120+5,120,110,20);
            ctx.fillText(evs[cur_e][i+2],i*120+10,133); } }, 100);
    document.addEventListener('click', function(e){ for (var i=0;i<3;i++)
        if (i*120+5<=e.pageX && e.pageX<i*120+115 && 120<=e.pageY && e.pageY<140)
            if (hp>0) evs[cur_e][i+5]() }, false);
})("#canvas",365,150,0,3,100,100,1,0,7);
Improving to 60 lines (4kb)

The game has become cyclical and more complicated. Added the following:

  • Arena, where you can train, to exchange money for the experience
  • The forge, where you can enhance your sword and armor
  • +2 kind of enemy units — now a total of 5 species

Small changes in generation of the worlds:

  • Some monsters and buildings appears only if you beat the game several times
  • One of the two locations in front of the castle dragon is sure to be a store

The new version you can find here.

Minesweeper (34 lines of code)

Demo on JSFiddle.

(function (count,fileds) {
    function cl(id){x=document.getElementById(id);return x?(x.className.indexOf('bomb')!=-1?1:0):0;}
    var bombs=0;
    for(i=0;i<fileds;i++){
        r=document.createElement('div');
        if(Math.random()*fileds<count){r.className='bomb close',document.getElementById('text').innerHTML=(++bombs)+' bomb\'s';}
        else r.className='close';
        r.id=Math.floor(i/10)+'_'+i%10;
        document.body.appendChild(r);
    }
    for(o=0;o<fileds;o++){
        i=Math.floor(o/10),j=o%10,num=0,obj=document.getElementById(i+'_'+j);
        for(k=0;k<9;k++)num+=cl((i-(Math.floor(k/3)-1))+'_'+(j-(k%3-1)));
        obj.innerHTML=num==0?'&nbsp;':num;
        obj.onclick=function(){mix=this.id.split('_'),open(mix[0],mix[1]);}
        obj.oncontextmenu=function(){this.className=this.className.indexOf('flag')!=-1?this.className.replace(/ flag/,''):this.className+' flag';return false;}
    }
    function open(i,j){
        dom=document.getElementById(i+'_'+j);
        if(!dom||dom.className.indexOf('close')==-1)return;
        if(dom.className.indexOf('bomb')!=-1){
            divs=document.getElementsByTagName('div');
            for(i=0;i<divs.length;i++)divs[i].className=divs[i].className.indexOf('bomb')!=-1?'bomb':'';
            alert('You lose!');
        }
        else {
            dom.className='';
            var elems = document.getElementsByTagName('div'),len=0;
            for (ki in elems)if(elems[ki].className&&elems[ki].className.indexOf('close')!=-1)len++;
            if(len<=bombs)alert('You win!');
        }
        if(dom.innerHTML=='&nbsp;')for(var ks=0;ks<9;ks++)open(i-((Math.floor(ks/3)-1)),j-(((ks%3)-1)));
    }
}(10,100));

Snakes (30 lines of code)

Demo on JSFiddle.

The final code isn't for aesthetes and perfectionists 😀. And this 30 lines doesn't include code which generates grid.

(function(width, height, length, current, dx, dy, x, y, hasFood, newEl){     

document.body.onkeydown = function(e){  
    dx = (e.keyCode - 38) % 2, dy = (e.keyCode - 39) % 2;
};

var timer = setInterval(function () {  
    x = (x + dx) < 0 ? width - 1 : (x + dx) % width; 
    y = (y + dy) < 0 ? height - 1 : (y + dy) % height;
    newEl = document.getElementsByClassName(y + '_' + x)[0]
    if(newEl.className.indexOf('s') > 0) {
        clearInterval(timer), alert('Game Over! Score: ' + length)
    };
    if(newEl.className.indexOf('f') > 0) {
        newEl.className = newEl.className.replace(' f', ''), length++, hasFood = false;
    }
    newEl.className += ' s', newEl.setAttribute('data-n', current++);

    for(var i = 0, min = Infinity, item, items = document.getElementsByClassName('s'), len = items.length; i < len && len > length; i++)
        if(+items[i].getAttribute('data-n') < min)
            min = +items[i].getAttribute('data-n'), item = items[i];

    if(!!item) item.className = item.className.replace(' s', '');

    for(var fItem, fX, fY; !hasFood; fX = Math.round(Math.random() * 10 % width), fY = Math.round(Math.random() * 10 % height))
        if(!!fX && !!fY && document.getElementsByClassName(fY + '_' + fX)[0].className.indexOf('s') < 0)
            hasFood = true, document.getElementsByClassName(fY + '_' + fX)[0].className += ' f';
}, 1000);

})(10, 10, 5, 1, 1, 0, 0, 0, false, null);

A few words about "algorithms":

  • For saving a size of code, it's doesn't contain array which must be mapping on UI. It's "Dom Addictive" algorithm, which works directly with DOM. The cells of the snake are marked with an s class and food cells with class f. The presence of these classes determines an action with a snake or with food.

  • Each new cell of the snake is marked with data-n attribute which is equal to the number++ (constantly increasing).

  • Looking for div's with the class s (snake square) and find the cell with the minimum value of data-n and do it the usual (deleted/moved the tail of the snake).

Tic-Tac-Toe (30 lines of code)

Demo on JSFiddle.

var t = new Array(9);

function ai() {  
  var id = Math.floor(Math.random() * 9);
  t[id] ? ai() : move(id, 'ai');
}

function checkEnd() {  
  if (t[0]=='ai' && t[1]=='ai' && t[2]=='ai' || t[0]=='player' && t[1]=='player' && t[2]=='player')  return true;
  if (t[3]=='ai' && t[4]=='ai' && t[5]=='ai' || t[3]=='player' && t[4]=='player' && t[5]=='player')  return true;
  if (t[6]=='ai' && t[7]=='ai' && t[8]=='ai' || t[6]=='player' && t[7]=='player' && t[8]=='player')  return true;
  if (t[0]=='ai' && t[3]=='ai' && t[6]=='ai' || t[0]=='player' && t[3]=='player' && t[6]=='player')  return true;
  if (t[1]=='ai' && t[4]=='ai' && t[7]=='ai' || t[1]=='player' && t[4]=='player' && t[7]=='player')  return true;
  if (t[2]=='ai' && t[5]=='ai' && t[8]=='ai' || t[2]=='player' && t[5]=='player' && t[8]=='player')  return true;
  if (t[0]=='ai' && t[4]=='ai' && t[8]=='ai' || t[0]=='player' && t[4]=='player' && t[8]=='player')  return true;
  if (t[2]=='ai' && t[4]=='ai' && t[6]=='ai' || t[2]=='player' && t[4]=='player' && t[6]=='player')  return true;
  if(t[0] && t[1] && t[2] && t[3] && t[4] && t[5] && t[6] && t[7] && t[8]) return true;
}

function move(id, role) {  
  if(t[id]) return false;
  t[id] = role;
  document.getElementById(id).className = 'cell ' + role;
  !checkEnd() ? (role == 'player') ? ai() : null : reset()
}

function reset() {  
  alert("Gave over!");
  location.reload();
}

Tower of Hanoi (30 lines of code)

Demo on JSFiddle.

function hanoiInit(difficulty){"use strict";  
    var addClass = function (o,c){o.className = (o.className + " " + c).replace(/\s+/g, " ").replace(/(^ | $)/g, "");}
    var removeClass = function (o,c){o.className = o.className.replace(new RegExp("(^|\\s)" + c + "(\\s|$)", "g"), "$1").replace(/\s+/g, " ").replace(/(^ | $)/g, "");}
    var hasClass = function (o,c) {return  (new RegExp("(^|\\s)" + c + "(\\s|$)", "g")).test(o.className);}
    var redAlert = function (){document.querySelector(".hh-cont").className += " alert";setTimeout(function(){removeClass(document.querySelector(".hh-cont"), "alert");},200);}
    var colors = ["#82E9FF", "#BB8BFF","#FF695C","#FFF560","#80E845","#00FFB7","#E84018"], discs = {}, winornot={"1":false, "2":false,"3":true};
    (function generateCols(){document.querySelector(".ch-dif").style.top = -400+"px"; var i, firstCol = document.querySelectorAll(".l-third")[0];
        for(i=0; i<difficulty; i++){discs[i]=new Disk(256-(256/difficulty)*i);discs[i].insert(firstCol, i);}}());
    function addEvent(elem, evType, fn) {if (elem.addEventListener) elem.addEventListener(evType, fn, false);
        else if (elem.attachEvent) elem.attachEvent('on' + evType, fn); else elem['on' + evType] = fn;}
    function Disk(r){var that = this; this.r=r, this.node = createDisc(r);
        function createDisc(l){var nElem = document.createElement('div');
            nElem.innerHTML ='', nElem.style.width = r+"px", nElem.style.backgroundColor = colors.pop(), nElem.className = "hh-disc", nElem.returnWidth = parseInt(r);
            return nElem;}
        this.insert = function(where, i){if(i || i===0) that.node.elemId=i;
            if(where.children.length===0) where.appendChild(that.node);
            else if(where.children[where.children.length-1].returnWidth>that.node.returnWidth) where.appendChild(that.node);
            else document.querySelector(".hh-cont").className += " alert";
            setTimeout(function(){removeClass(document.querySelector(".hh-cont"), "alert");},200);
            removeClass(that.node, "active");}}
    addEvent(document.querySelector(".hh-cont"), "click", function(e){
        var target = e.srcElement || e.target, nodeId;
        if(hasClass(target,"hh-disc")) target = target.parentNode;
        if(!document.querySelector(".active")) { if(target.children.length===0) return false;addClass(target.children[target.children.length-1], "active");
        }else{nodeId = document.querySelector(".active").elemId;
            discs[nodeId].insert(target);
            if(!winornot["1"]&&document.querySelector(".l-first").children.length==difficulty) winornot["1"] = true;
            if(!winornot["2"]&&document.querySelector(".l-second").children.length==difficulty) winornot["2"] = true;
            if( winornot["1"] && winornot["2"] ) document.querySelector(".win").style.top=0;
        }});}

Also you might be interested in: