Поддался пороку тренду недели и решил написать крошечный морской бой в 30 строк кода. Вот он: ровно 30 строк JavaScript, 2 строки HTML и 6 — CSS. Сжать можно было и гораздо сильнее, в текущем виде читабельность практически не пострадала (за исключением инициализирующих массивов).
Морской бой на JSFiddle.
Сделал на div-ах. Можно было использовать и canvas, но для данной задачи это было бы чересчур, т.к. никакой анимации и сложных фигур не предусматривалось. К тому же, к div-ам удобнее биндить клики и выбирать их селекторами (что было важно для оптимизации, как станет ясно в дальнейшем).
Исходник на JSFiddle.
Карты для обоих игроков задаются через массивы
Компьютерный противник ужасно примитивен: рандомно бомбит квадраты, не учитывая ситуацию на игровом поле. Но это, на мой взгляд, немного уравновешивается тем, что мы не знаем, «ранен» корабль или «убит», и вынуждены делать больше выстрелов, чтобы разведать обстановку. Оба эти решения позволили упростить и тем самым сэкономить код.
В альфа-версии боя для хода компьютера использовался цикл
Полез было вдохновляться замечательным постом про уникальные случайные числа на Stack Overflow, но решение оказалось куда более простым: с помощью
Также, думаю, не было особого смысла (кроме экономии строк) в передаче высоты и ширины игрового поля через параметры в главную функцию — всё равно без изменения карт игра в таком случае не заработает.
Принимаю предложения по улучшению. Можно ли было впихнуть в этот объем кода различение подбитых и потопленных кораблей, простенький AI, генератор или редактор карт, звуки взрывов?
Морской бой на JSFiddle.
Сделал на div-ах. Можно было использовать и canvas, но для данной задачи это было бы чересчур, т.к. никакой анимации и сложных фигур не предусматривалось. К тому же, к div-ам удобнее биндить клики и выбирать их селекторами (что было важно для оптимизации, как станет ясно в дальнейшем).
Разбор исходника
Исходник на JSFiddle.
Развернуть исходник
JavaScript:
CSS:
HTML:
(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, генератор или редактор карт, звуки взрывов?