В честь недели 30-строчных игрушек на JavaScript решил показать своё некогда творение — ханойская башня с различными уровнями сложности и анимацией. Итак, встречайте:

image

image
image

На самом деле я был лукавым и 30-и строк кода здесь нет, поскольку помимо JS кода использовались еще html-разметка и css-стили, а это при привидении в однострочный вид в любом случае займет еще одну строку (на деле это еще +30строк html и +122строки css).

Да и изначально JavaScript код умещался тоже в 120 строк кода, поэтому заголовок должен выглядеть немного иначе:
«Из 120 в 30»

Но после получаса раздумий, еще часа уговоров совести сделать ужасное и, в конечном итоге, минут 20 говнокодинга оптимизации кода был получен результат. Кому не терпится, то вот ссылка на jsfidle.

Особенности:

  • Несколько уровней сложности;
  • Оповещение о неправильном ходе;
  • Минимальная, но приятная анимация;
  • Сделано без абсолютного позиционирования и canvas. Если посмотреть на другие реализации этой игры на js, то многие очень любят позиционировать блоки с помощью абсолютных значений. Но не Я. Я не люблю абсолютного позиционирования, поэтому Я просто повернул игровую область на 180 градусов и использовал стандартное блочное позиционирование;
  • Использованные библиотеки — нет таких;
  • Нарисована в стиле минималистический модерн с пастельными(!) цветами.


Код

оптимизированный JavaScript:

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;
        }});}


JavaScript до оптимизации
window.onload = function() {
    document.querySelector(".o-easy").onclick = function(){hanoiInit(3)};
    document.querySelector(".o-medium").onclick = function(){hanoiInit(5)};
    document.querySelector(".o-hard").onclick = function(){hanoiInit(7)};
}
function hanoiInit(dif){
    "use strict";
    var colors = ["#82E9FF", "#BB8BFF","#FF695C","#FFF560","#80E845","#00FFB7","#E84018"];
    var difficulty = parseInt(dif);
    var  discs = {};
    var winornot={"1":false, "2":false,"3":true};

    (function generateCols(){
        document.querySelector(".o-easy").onclick = function(){};
        document.querySelector(".o-medium").onclick = function(){};
        document.querySelector(".o-hard").onclick = function(){};
        document.querySelector(".ch-dif").style.top = -400+"px";
        var i;
        var 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 addClass(o, c){
        var re = new RegExp("(^|\\s)" + c + "(\\s|$)", "g");if (re.test(o.className)) return;
        o.className = (o.className + " " + c).replace(/\s+/g, " ").replace(/(^ | $)/g, "");
    }

    function removeClass(o, c){
        var re = new RegExp("(^|\\s)" + c + "(\\s|$)", "g");
        o.className = o.className.replace(re, "$1").replace(/\s+/g, " ").replace(/(^ | $)/g, "");
    }

    function hasClass(o,c) {
        var regexp =  new RegExp("(^|\\s)" + c + "(\\s|$)", "g");
        return  regexp.test(o.className);
    }

    function redAlert(){
     var hh = document.querySelector(".hh-cont");
        hh.className += " alert";
        setTimeout(function(){removeClass(hh, "alert");},200);
    }

    function Disk(r){
        var that = this;
        this.r=r;
        this.node = createDisc(r);

        function createDisc(l){
            console.log(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[0].returnWidth>that.node.returnWidth){
                    where.appendChild(that.node);
                }else{
                    redAlert();
                }
            }
            removeClass(that.node, "active");
        }
    }

    addEvent(document.querySelector(".hh-cont"), "click", function(e){
        var target = e.srcElement || e.target;
        if(hasClass(target,"hh-disc")) target = target.parentNode;
        var nodeId;
        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);
            checkWinOrNot();
        }
    });

    function checkWinOrNot(){
        if(!winornot["1"]){
            if(document.querySelector(".l-first").children.length==difficulty){
                winornot["1"] = true;
            }
        }
        if(!winornot["2"]){
            if(document.querySelector(".l-second").children.length==difficulty){
                winornot["2"] = true;
            }
        }
        if( winornot["1"] && winornot["2"] ){
            document.querySelector(".win").style.top=0;
        }
    }
}



Заключение

Создатель гоночка на JavaScript (30 строк кода) пользователь agegorin написал:
Ребята, занимайтесь нормальным программированием.

Я не соглашусь с ним. Но соглашусь с его благодарностями к тем, кто начал эту неделю ненормального программирования и кто её поддержал. И от себя пожелаю:
Ребята, будьте ненормальными программистами!