Pull to refresh

Морской бой на JavaScript

Поддался пороку тренду недели и решил написать крошечный морской бой в 30 строк кода. Вот он: ровно 30 строк JavaScript, 2 строки HTML и 6 — CSS. Сжать можно было и гораздо сильнее, в текущем виде читабельность практически не пострадала (за исключением инициализирующих массивов).



Морской бой на JSFiddle.

Сделал на div-ах. Можно было использовать и canvas, но для данной задачи это было бы чересчур, т.к. никакой анимации и сложных фигур не предусматривалось. К тому же, к div-ам удобнее биндить клики и выбирать их селекторами (что было важно для оптимизации, как станет ясно в дальнейшем).


Разбор исходника



Исходник на JSFiddle.

Развернуть исходник
JavaScript:
(function(w, h) {
    var p1map = ['~ss~~~~s~~','~~~~~~~~~~','~~~~s~~~~s','~s~~~~s~~s','~s~~~~s~~s','~s~~~~~~~~','~~~~~~~~s~','~~~~ss~~~~','~s~~~~~~~~','~~~~ssss~~'];
    var p2map = ['~~~s~~~~ss','~s~s~~~~~~','~~~s~~~~~~','~~~s~~~s~~','~~~~~~~s~~','~s~~s~~s~~','~s~~~~~~~~','~s~s~~~~~~','~~~~~ss~~~','ss~~~~~~s~'];
    var p1 = document.querySelector('#p1'), p2 = document.querySelector('#p2');
    for (i=0;i<w;i++) for (j=0;j<h;j++) {
        div1 = document.createElement('div');
        div1.id = i+'_'+j, div1.className = p1map[i][j] == 's' ? 's' : 'w';
        p1.appendChild(div1);
        div2 = document.createElement('div');
        div2.className = p2map[i][j] == 's' ? 's' : 'w';
        div2.onclick = function () { if (fire(this)) backfire(); };
        p2.appendChild(div2);
     }
    function fire(el) {
        if (el.className == 'd' || el.className == 'm') return false;
        el.className = el.className == 's' ? 'd' : 'm';
        if (document.querySelectorAll('#p2 .s').length === 0) {
            alert('You have won!'); 
            return false;
        }
        if (el.className == 'm') return true;
    }
    function backfire() {
        for (i=w*h;i>0;i--) {
            var targets = document.querySelectorAll('#p1 .s, #p1 .w');
            if (targets.length === 0 || fire(targets[Math.floor(Math.random() * targets.length)])) break;
        }
        if (document.querySelectorAll('#p1 .s').length === 0) alert('You have lost!');
    }
})(10, 10);

CSS:
#p1 .s { background: #222; }
#p2 .s, .w { background: skyblue; }
.d { background: red;}
.m { background: gray; }
#p1 div, #p2 div { width: 20px; height: 20px; float: left; }
#p1, #p2 { width: 200px; height: 200 px; float: left; margin: 10px; }

HTML:
<div id="p1"></div>
<div id="p2"></div>



Карты для обоих игроков задаются через массивы p1map и p2map. Нагляднее, конечно, рисовать корабли, когда массивы выстроены в двух измерениях, но мы ведь экономим строки. Никаких ограничений на установку кораблей нет, полагаемся на честность игрока (как было во времена этой игры в школьных тетрадках).

Компьютерный противник ужасно примитивен: рандомно бомбит квадраты, не учитывая ситуацию на игровом поле. Но это, на мой взгляд, немного уравновешивается тем, что мы не знаем, «ранен» корабль или «убит», и вынуждены делать больше выстрелов, чтобы разведать обстановку. Оба эти решения позволили упростить и тем самым сэкономить код.

В альфа-версии боя для хода компьютера использовался цикл for с количеством итераций по количеству клеток на игровом поле. Если за эти итерации не удавалось рандомно обнаружить нетронутую клетку, ход компьютера не выполнялся. Изъян, мягко говоря.

    for (i=w*h;i>0;i--) {
        var r1 = Math.floor(Math.random() * w), r2 = Math.floor(Math.random() * h);
        el = document.getElementById(r1+'_'+r2);
        if (el.className != 'd' && el.className != 'm') if (fire(el)) break;
    }


Полез было вдохновляться замечательным постом про уникальные случайные числа на Stack Overflow, но решение оказалось куда более простым: с помощью querySelectorAll получать список «необстрелянных» клеток и выбирать цель уже среди них. Здесь возник и потенциал для увеличения сложности без намёка на AI: отсекать некоторую часть пустых клеток из выборки, чтобы CPU чаще попадал в корабли.

Также, думаю, не было особого смысла (кроме экономии строк) в передаче высоты и ширины игрового поля через параметры в главную функцию — всё равно без изменения карт игра в таком случае не заработает.

Принимаю предложения по улучшению. Можно ли было впихнуть в этот объем кода различение подбитых и потопленных кораблей, простенький AI, генератор или редактор карт, звуки взрывов?
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.