Игра Морской бой на JavaScript. Выстрел игрока.
Вступление.
Очередная статья из цикла «Игра Морской бой на чистом JavaScript». В этой статье мы рассмотрим:
- Визуальное отображение клеток, по которым стрелять нет смысла.
- Выстрел игрока.
- Обработка результата выстрела.
- Визуальное отображение попадания и промаха.
Прежде чем приступать к написанию алгоритма выстрела, необходимо вспомнить несколько массивов, о которых писалось в статье Игра Морской бой на JavaScript. Рандомная расстановка кораблей.
-
1
matrix
— это двумерный массив (матрица), в которой содержится информация о местоположении кораблей эскадры. Кроме этого, в матрицу записываются реультаты выстрела (промах или попадание) и места, где кораблей точно не может быть, исходя из правил игры. Изначально, данный массив при создании заполняется нулями, которые соотвествуют пустой клетке.0
— пустая клетка;1
— палуба корабля;2
— палубы корабля не может быть по условиям игры;3
— промах;4
— попадание.
Двумерный массив
matrix
используется, в первую очередь, для визуального отображения хода игры.
Во время игры используются два экземпляра массиваmatrix
:user.matrix
— соответствует игровому полю игрока;comp.matrix
— соответствует игровому полю компьютера.
-
2
squadron
— массив эскадры, в котором храниться полная информация по каждому кораблю. Данные этого массива используются для:- — определения корабля, в которого было попадание;
- — уничтожен данный корабль или только повреждён (ранен);
- — количество уничтоженных (потопленных) кораблей в эскадре.
Кроме массивов, хочу напомнить о том, что весь алгоритм игры «Морской бой», реализован в модуле Controller
, содержащем объект battle
. В этот объект с помощью простой литеральной нотации записаны все функции определяющие алгоритм игры и определён ряд свойств — глобальных, в области видимости модуля, переменных.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
var Controller = (function() { var player, enemy, self, coords, text, srvText = getElement('text_btm'), tm = 0; var battle = { // инициализация игры init: function() { ... } }; return ({ battle: battle, init: battle.init }); })(); |
Подробнее о модуле Controller
и инициализации игры «Морской бой», читайте в статье Игра Морской бой на JavaScript. Редактирование положения корабля и начало игры.
Игра «Морской бой». Блокировка клетки игрового поля и установка маркера.
Прежде чем рассматривать реализацию выстрела игрока, необходимо разобраться с блокировкой клеток игрового поля и установкой маркера на них. Напомню, таким образом отмечаются клетки игрового поля, в которых, согласно правилам игры, не могут располагаться корабли — это соседние с кораблём клетки, как по вертикали и горизонтали, так и по диагонали. У вас может возникнуть вопрос, а зачем их помечать? Это позволяет лучше оценить возможное расположение кораблей противника и облегчает выбор координат для следующего выстрела.

Маркер устанавливается кликом правой кнопки мыши по клетке игрового поля компьютера, при этом клетка не должна содержать отметки попадания или промаха. Снимается маркер также — кликом правой кнопкой.
Для этой цели, мы в функции battle.init
зарегистрировали обработчик события contextmenu
:
1 2 3 4 5 |
// установка и снятие отметки на клетку, где однозначно не может быть // палубы корабля противника compfield.addEventListener('contextmenu', self.setEmptyCell); |
Алгоритм по установке и снятию маркера на клетке игрового поля не сложен. Давайте рассмотрим его:
-
1
Используя функцию
transformCoordinates
, переводим координаты точки клика правой кнопкой мыши, в координаты матрицы.Запомните, это очень важно.
Координаты точки клика отсчитываются относительно верхнего левого угла игрового поля противника. -
2
Получаем коллекцию всех объектов иконок, которые в данный момент расположены на игровом поле противника — иконки попаданий, промахов и заблокированных клеток.
-
3
Перебираем полученную коллекцию, сравнивая координаты иконок с координатами клика.
-
4
Если координаты ни одной иконки не совпадут, значит клетка, по которой был сделан клик, пустая. Устанавливаем на неё заштрихованный маркер и в матрицу игрового поля компьютера по полученным координатам записываем значение ‘2’, что соответствует заблокированной клетке.
-
5
Если координаты совпадут и в этой клетке уже установлен заштрихованный маркер, то удаляем его, а в матрицу игрового поля компьютера по этим координатам записываем значение ‘0’, что соответствует пустой клетке.
Теперь напишем JS-код, реализующий данный алгоритм. Для этого мы создадим две функции — setEmptyCell
и checkCell
. Данные функции будут методами объекта battle
. Запишем их в конец JS-кода этого объекта в простой литеральной нотации.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
var Controller = (function() { var player, enemy, self, coords, text, srvText = getElement('text_btm'), tm = 0; var battle = { // инициализация игры init: function() { ... }, setEmptyCell: function(e) { // если клик сделан не правой кнопкой мыши, прекращаем работу функции if (e.which != 3) return false; // блокируем действие браузера по умолчанию - появление // контекстного меню e.preventDefault(); // преобразуем относительные координаты клика в координаты // двумерного массива (матрицы игрового поля) coords = self.transformCoordinates(e, comp); // прежде чем штриховать клетку, необходимо проверить пустая ли клетка // если там уже есть маркер блокировки, то удалить его, // если попадание или промах, то никаких действий не производим var ch = self.checkCell(); // если в выбранной клетке уже установлена какая-то иконка, то // ch = false, в противном случае ch = true if (ch) { // заштриховываем выбранную клетку self.showIcons(enemy, coords, 'shaded-cell'); // записываем в матрице игрового поля компьютера 2, // значение заблокированной клетки comp.matrix[coords.x][coords.y] = 2; } }, checkCell: function() { // получаем коллекцию всех иконок, установленных на игровом поле компьютера var icons = enemy.field.querySelectorAll('.icon-field'), flag = true; // перебираем полученную коллекцию иконок [].forEach.call(icons, function(el) { // преобразуем относительные координаты иконок в координаты матрицы var x = el.style.top.slice(0, -2) / comp.shipSide, y = el.style.left.slice(0, -2) / comp.shipSide; // сравниваем координаты иконок с координатами клика if (coords.x == x && coords.y == y) { // проверяем, какая иконка установлена, используя класс, отвечающий // за вывод заштрихованной иконки класс 'shaded-cell' var isShaded = el.classList.contains('shaded-cell'); // если клетка, по которой кликнули, уже заштрихована, то if (isShaded) { // удаляем эту иконку el.parentNode.removeChild(el); // записываем в матрице игрового поля компьютера 0, // значение пустой клетки comp.matrix[coords.x][coords.y] = 0; } flag = false; } }); return flag; } }; return ({ battle: battle, init: battle.init }); })(); |
Игра «Морской бой». Получение координат выстрела игрока.
Как я писал ранее в предыдущей статье, в функции battle.init
устанавливается обработчик события, который при клике по игровому полю компьютера запускает функцию shoot
:
1 2 3 4 |
// обработка выстрела игрока compfield.addEventListener('click', self.shoot); |
Добавим к объекту battle
метод shoot
. Запись, как говорилось ранее, будем осуществлять в простой литеральной нотации:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
var Controller = (function() { var player, enemy, self, coords, text, srvText = getElement('text_btm'), tm = 0; var battle = { // инициализация игры init: function() { ... }, // визуальное отображения заблокированных клеток setEmptyCell: function(e) { ... }, checkCell: function(coords) { ... }, // обработка выстрела shoot: function(e) { ... } }; return ({ battle: battle, init: battle.init }); })(); |
На данном этапе, мы рассмотрим работу функции shoot
применительно к выстрелу игрока. Небольшие дополнения к JS-коду, относящиеся к выстрелу компьютера мы рассмотрим позже, в следующей статье, когда будем разбираться с искусственным интеллектом противника.
Аргументом функции shoot
является событие, генерируемое игроком, при клике по игровому полю компьютера. По наличию данного аргумента можно определить — кто стреляет. Если выстрел был компьютера, то e === undefined
В список свойств данного события, входят pageX
и pageY
, которые содержат координаты клика в пикселях по оси 'X'
и оси 'Y'
относительно всего документа. Зная координаты сторон игрового поля компьютера (они являются свойствами экземпляра comp.Field
— fieldX
и fieldY
), получаем координаты клика уже относительно рамки игрового поля.
Теперь мы можем преобразовать полученные координаты в координаты матрицы и извлечь значение матрицы в клетке игрового поля, по которой был сделан клик. Если значение равно 0 — значит это пустая клетка и игрок промахнулся, а если значение равно 1 — в данной клетке находится палуба корабля и было попадание.
Вот краткий алгоритм обработки выстрела игрока. Забегая вперёд, скажу, что алгоритм выстрела компьютера точно такой же, только координаты клика выдаёт искусственный интеллект, который мы с вами ещё создадим. А пока напишем JS-код, реализующий приведённый выше алгоритм:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
shoot: function(e) { // e !== undefined - значит выстрел производит игрок if (e !== undefined) { // если клик сделан не левой кнопкой мыши, прекращаем работу функции if (e.which != 1) return false; // преобразуем координаты выстрела в координаты матрицы coords = self.transformCoordinates(e, enemy); } else { // работу данных функций подробно рассмотрим в следующей статье coords = (comp.shootMatrixAround.length) ? self.getCoordinatesShotAround() : self.getCoordinatesShot(); } // значение матрицы по полученным координатам var val = enemy.matrix[coords.x][coords.y]; } |
Теперь рассмотрим подробнее функцию transformCoordinates
, которая преобразует пиксельные координаты точки клика в координаты матрицы, хранящей информацию о расположении кораблей противника.
Прежде чем приступим к написанию JS-кода, внимательно изучите приведённый ниже рисунок, чтобы понять принцип преобразования координат.

Где:
e.pageX
— координата точки клика по оси Х относительно документа;comp.fieldX
— координата левой границы игрового поля компьютера по оси Х;e.pageX - comp.fieldX
— координата точки клика по оси Х относительно левой границы игрового поля компьютера.
По оси ‘Y’ — аналогично.
Полученные координаты точки клика переводим в координаты двумерного массива (матрицы). Для этого разность координат необходимо поделить на ширину стороны клетки, которая равна размеру палубы корабля. Размер палубы является значением свойства shipSide
конструктора Field
. Полученное значение округляем с недостатком или просто отбрасываем от него дробную часть, что одно и то же. Оставшаяся целая часть и есть координата двумерного массива.
Аргументами функции является событие и переменная enemy
, которая в данный момент равна 'comp'
.
Всё что мы изложили выше, можно описать двумя простыми формулами. Добавим в самый конец объекта battle
метод transformCoordinates
:
1 2 3 4 5 6 7 8 9 10 11 |
transformCoordinates: function(e, enemy) { // создадим объект, в который запишем полученные координаты матрицы var obj = {}; // вычисляем ячейку двумерного массива,которая соответствует // координатам выстрела obj.x = Math.trunc((e.pageY - enemy.fieldX) / enemy.shipSide), obj.y = Math.trunc((e.pageX - enemy.fieldY) / enemy.shipSide); return obj; } |
Для получения целого используем метод Math.trunc
, который отбрасывает десятичную запятую и все цифры после неё. К сожалению, IE данный метод не поддерживает и нужно использовать полифилл.
1 2 3 4 5 6 7 8 |
if (!Math.trunc) { Math.trunc = function(v) { v = +v; return (v - v % 1) || (!isFinite(v) || v === 0 ? v : v < 0 ? -0 : 0); }; } |
Настало время перейти к самой сложной части — обработке полученного из матрицы значения.
Игра «Морской бой». Обработка результатов выстрела.
В данном разделе мы с вами рассмотрим JS-код, который обрабатывает информацию, полученную из двумерного массива matrix
и, в зависимости от результата выстрела (промах, попадание, повторный обстрел одних и тех же координат), отображает игровой процесс на экране монитора.
Этот JS-код обрабатывает как выстрел игрока, так и, с небольшими дополнениями, выстрел компьютера. Поэтому постараемся разобрать его как можно подробнее.
Ещё раз вспомним, какие значения могут храниться в матрице и чему они соответствуют:
0
— пустая клетка;1
— палуба корабля;2
— клетка, отмеченная игроком;3
— зафиксирован промах;4
— зафиксировано попадание.
Для вызова тех или иных функции, в зависимости от полученного значения, будем использовать конструкцию switch
. Аргументом для данной конструкции будет полученное из матрицы значение val
. В конец функции shoot
запишем следующий JS-код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
switch(val) { // промах case 0: break; // попадание case 1: break; // отмеченная координата case 2: break; // ранее обстрелянная координата case 3: case 4: break; } |
Игра «Морской бой». Промах.
Рассмотрим алгоритм обработки промаха:
-
1
Визуально отображаем промах и записываем в матрицу по этим координатам значение
3
. -
2
Выводим сообщение о промахе.
-
3
Определяем, чей выстрел будет следующим.
-
4
Если следующий выстрел делает компьютер, то снимаем пользовательские обработчики событий
click
иcontextmenu
, чтобы в это время игрок не мог производить никаких действий. В противном случае — устанавливаем эти обработчики событий.
Напоминаю, что в данный момент мы рассматриваем выстрел игрока, поэтому обработчики событийclick
иcontextmenu
будут удалены.
Прежде чем приступим к написанию JS-кода, давайте рассмотрим новую вспомогательную функцию showIcons
, которая будет отвечать за визуальное отображение результа выстрела, а также отображать клетки, в которых гарантированно не может быть расположен корабль. Данная функция универсальная и будет использоваться, как при выстреле игрока, так и при выстреле компьютера.
Функция showIcons
будет использовать три аргумента:
-
enemy
— указывает, кто на момент выстрела является противником; -
coords
— координаты выстрела в системе координат матрицы; -
класс таблицы стилей (
'dot'
,'red-cross'
,'shaded-cell'
), который выводит точку, крест или заштрихованную ячейку соответственно.
Функция showIcons
является методом объекта battle
, поэтому запишем её в конец данного объекта:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
showIcons: function(enemy, coords, iconClass) { // создаём элемент DIV var div = document.createElement('div'); // присваиваем вновь созданному элементу два класса // в зависимости от аргумента iconClass, фоновым рисунком у DIV'а будет или точка, // или красный крест, или заштрихованная клетка div.className = 'icon-field ' + iconClass; // задаём смещение созданного элемента, при этом преобразуем координаты из матричных // в относительные (относительно игрового поля противника) // для этого значение координаты в сетке матрицы умножаем на ширину клетки // игрового поля, которая, как вы помните равна 33px div.style.cssText = 'left:' + (coords.y * enemy.shipSide) + 'px; top:' + (coords.x * enemy.shipSide) + 'px;'; // устанавливаем созданный элемент в игровом поле противника enemy.field.appendChild(div); } |
Теперь настало время написать JavaScript, который реализует алгоритм промаха при выстреле:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
case 0: // устанавливаем иконку промаха и записываем промах в матрицу self.showIcons(enemy, coords, 'dot'); enemy.matrix[coords.x][coords.y] = 3; // выводим сообщение о промахе в нижней части экрана text = (player === user) ? 'Вы промахнулись. Стреляет компьютер.' : 'Компьютер промахнулся. Ваш выстрел.'; self.showServiseText(text); // определяем, чей выстрел следующий player = (player === user) ? comp : user; enemy = (player === user) ? comp : user; if (player == comp) { // снимаем обработчики событий для пользователя compfield.removeEventListener('click', self.shoot); compfield.removeEventListener('contextmenu', self.setEmptyCell); // запускаем функцию shoot для выстрела компьютера setTimeout(function() { return self.shoot(); }, 1000); } else { // устанавливаем обработчики событий для пользователя compfield.addEventListener('click', self.shoot); compfield.addEventListener('contextmenu', self.setEmptyCell); } break; |
Игра «Морской бой». Попадание в корабль противника.
Алгоритм обработка результата попадания в корабль противника:
-
1
Визуально отображаем попадание и записываем в матрицу по этим координатам значение
4
-
2
Перебираем массив эскадры
squadron
, в котором храниться информация о каждом корабле противника, и определяем корабль в который произошло попадание. После этого вносим изменения в данные массива и выводим сообщение о попадании. -
3
Проверяем, все ли корабли противника потоплены. В зависимости от результата или продолжаем игру (делаем следующий выстрел), или заканчиваем, объявляя о победе игрока.
Первый пункт алгоритма подробно рассматривать не будем, т. к. его JS-код практически полностью совпадает с JS-кодом обработки промаха.
1 2 3 4 5 6 7 8 9 10 11 |
case 1: // записываем в матрицу значение '4', которое соответствует попаданию enemy.matrix[coords.x][coords.y] = 4; // отображаем иконку попадания self.showIcons(enemy, coords, 'red-cross'); // выводим сообщение о попадании в нижней части экрана text = (player === user) ? 'Поздравляем! Вы попали. Ваш выстрел.' : 'Компьютер попал в ваш корабль. Выстрел компьютера'; self.showServiseText(text); break; |
Игра «Морской бой». Поиск корабля, в который произошло попадание.
Как я писал ранее, для поиска корабля, в который произошло попадание, необходимо перебрать массив эскадры squadron
. Каждым элементом данного массива является экземпляр объекта корабля, созданный с помощью конструктора Ships
.
Более подробно об объекте корабля изложено в описании конструктора кораблей —
Ships
в статье «Игра Морской бой на JavaScript. Рандомная расстановка кораблей.»
На данном этапе нам понадобятся только три свойства объекта корабля:
decks
— количество палуб;hits
— счётчик попаданий;matrix
— массив с координатами каждой палубы корабля.
В процессе поиска, нужно перебрать массив matrix
каждого корабля эскадры, сравнивая координаты палуб с координатами выстрела. В случае совпадения, увеличиваем счётчик попадания (hits
) найденного корабля на единицу и сравниваем значение счётчика с количеством палуб. Если эти значения равны, то удаляем корабль из массива эскадры.
Теперь блок case 1
будет выглядеть следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
case 1: // записываем в матрицу значение '4', которое соответствует попаданию enemy.matrix[coords.x][coords.y] = 4; // отображаем иконку попадания self.showIcons(enemy, coords, 'red-cross'); // выводим сообщение о попадании в нижней части экрана text = (player === user) ? 'Поздравляем! Вы попали. Ваш выстрел.' : 'Компьютер попал в ваш корабль. Выстрел компьютера'; self.showServiseText(text); // перебор массива начнём с конца, для получения корректных значений // при возможном удалении его элементов for (var i = enemy.squadron.length - 1; i >= 0; i--) { var warship = enemy.squadron[i], // полная информация о корабле arrayDescks = warship.matrix; // массив с координатами палуб // перебираем координаты палуб корабля for (var j = 0, length = arrayDescks.length; j < length; j++) { // сравниваем координаты палубы с координатами выстрела if (arrayDescks[j][0] == coords.x && arrayDescks[j][1] == coords.y) { // если координаты одной из палуб совпали с координатами выстрела // увеличиваем счётчик попаданий warship.hits++; // если кол-во попаданий в корабль становится равным кол-ву палуб // считаем этот корабль уничтоженным и удаляем его из эскадры if (warship.hits == warship.decks) { enemy.squadron.splice(i, 1); } // выходим из цикла, т.к. палуба найдена break; } } } break; |
Игра «Морской бой». Продолжение боя и окончание игры.
Принимать решение о продолжении боя или его окончании мы будем на основании длины массива squadron
— если его длина равна нулю, значит все корабли эскадры противника уничтожены. В противном случае, морской бой продолжается и игрок может совершить новый выстрел. Хочу сразу отметить, что большинство JS-кода относится к выстрелу компьютера. Подробно это будет рассмотрено в следующей статье.
Рассмотрим подробнее окончание игры при выстреле игрока.
При окончании игры необходимо совершить всего два действия:
- вывести текстовое сообщение поздравления с выигрышем;
-
снять обработчики событий пользователя:
—click
— выстрел;
—contextmenu
— пометка клеток на игровом поле.
Напишем JS-код, который реализует описанный функционал. Разместим его в конце блока case 1
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
// все корабли эскадры противника уничтожены, игра закончена if (enemy.squadron.length == 0) { text = (player === user) ? 'Поздравляем! Вы выиграли.' : 'К сожалению, вы проиграли.'; srvText.innerHTML = text; // победа игрока if (player == user) { // удаляем обработчики событий для пользователя compfield.removeEventListener('click', self.shoot); compfield.removeEventListener('contextmenu', self.setEmptyCell); // победа компьютера } else { // показываем оставшиеся корабли компьютера // подробно рассмотрим в следующей статье ..... } // бой продолжается } else { // следующий выстрел компьютера if (player === comp) { // подготовка к выстрелу и сам выстрел // подробно рассмотрим в следующей статье ..... } } |
Итак, мы подробно изучили работу JavaScript, обрабатывающего попадание при выстреле, как игрока, так и компьютера. Отдельные моменты, касающиеся только компьютера, будут рассмотренны в следующей статье.
Теперь настало время разобрать, что же произойдёт, если игрок попытается совершить выстрел по заблокированной клетке, отмеченной маркером.
Игра «Морской бой». Выстрел по заблокированной клетке.
Если игрок сделает выстрел по заблокированной клетке, не смотря на установленный маркер, то ему будет выдано предупреждение о необходимости снять блокировку, а маркер на 0.5 сек подсветится красным цветом.
JavaScript здесь несложный, поэтому отдельно описывать его не буду — достаточно будет комментариев в самом коде.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
case 2: // выводим предупреждение text = 'Снимите блокировку с этих координат!'; self.showServiseText(text); // получаем коллекцию всех объектов маркеров блокированных клеток var icons = enemy.field.querySelectorAll('.shaded-cell'); // перебираем полученную коллекцию иконок [].forEach.call(icons, function(el) { // преобразуем относительные координаты иконок в координаты матрицы var x = el.style.top.slice(0, -2) / comp.shipSide, y = el.style.left.slice(0, -2) / comp.shipSide; // сравниваем координаты иконок с координатами клика if (coords.x == x && coords.y == y) { // в случае совпадения координат добавляем маркеру класс, // подсвечивающий его в красный цвет el.classList.add('shaded-cell_red'); // через 0.5 сек этот класс удаляем setTimeout(function() { el.classList.remove('shaded-cell_red'); }, 500); } }); break; |
Хочу сказать, что такая ситуация возможна только при выстреле игрока. Компьютеру выстрелить по заблокированной или ранее обстрелянной клетке не даст его искусственный интеллект.
Игра «Морской бой». Выстрел по ранее обстрелянным координатам.
Если игрок выстрелил по клетке, которую обстрелял ранее и там зафиксировано попадание или промах, то игроку выдаётся предупреждение о повторном обстреле данных координат.
Добавим в конструкцию switch
следующий JS-код:
1 2 3 4 5 6 7 |
case 3: case 4: text = 'По этим координатам вы уже стреляли!'; self.showServiseText(text); break; |
На этом мы закончим рассматривать выстрел игрока.
В следующей статье мы рассмотрим выстрел компьютера, напишем примитивный искусственный интеллект, который будет оценивать игровую обстановку и вести обстрел эскадры игрока по оптимальному алгоритму, анализировать результаты выстрела и учитывать эти результаты при выборе координат для следующего выстрела.
Комментарии
-
Комментарии должны содержать вопросы и дополнения по статье, ответы на вопросы других пользователей.
Комментарии содержащие обсуждение политики, будут безжалостно удаляться. -
Для удобства чтения Вашего кода, не забываейте его форматировать. Вы его можете подсветить код с помощью тега
<pre>
:
—<pre class="lang:xhtml">
- HTML;
—<pre class="lang:css">
- CSS;
—<pre class="lang:javascript">
- JavaScript. - Если что-то не понятно в статье, постарайтесь указать более конкретно, что именно не понятно.
-
-
В данный момент пишу статью «Выстрел компьютера». Параллельно дополняю и улучшаю ИИ компьютера. Думаю, что ещё пару недель у меня это займёт.
Скорее всего, это будет последняя статья из цикла «Морской бой».
-
Привет, сколько всего частей планируешь. И когда следующая ?