Игра Морской бой на JavaScript. Выстрел игрока.
Вступление.
Очередная статья из цикла «Игра Морской бой на чистом JavaScript». В этой статье мы рассмотрим:
- Визуальное отображение клеток, по которым стрелять нет смысла.
- Выстрел игрока.
- Обработка результата выстрела.
- Визуальное отображение попадания и промаха.
Прежде чем приступать к написанию алгоритма выстрела, необходимо вспомнить несколько массивов, о которых писалось в статье Игра Морской бой на JavaScript. Рандомная расстановка кораблей.
- matrix
-
это двумерный массив (матрица), в которой содержится информация о местоположении кораблей эскадры. Кроме этого, в матрицу записываются реультаты выстрела (промах или попадание) и места, где кораблей точно не может быть, исходя из правил игры. Изначально, данный массив при создании заполняется нулями, которые соответствуют пустой клетке.
- 0 — пустая клетка;
- 1 — палуба корабля;
- 2 — палубы корабля не может быть по условиям игры (только для экземпляра human);
- 3 — промах;
- 4 — попадание.
Двумерный массив
matrix
используется, в первую очередь, для визуального отображения хода игры.
Во время игры используются два экземпляра массиваmatrix
:
human.matrix
— соответствует игровому полю игрока;
computer.matrix
— соответствует игровому полю компьютера. - squadron
-
объект эскадры, в котором храниться полная информация по каждому кораблю. Данные этого объекта используются для:
— определения корабля, в который было попадание;
— уничтожен данный корабль или только повреждён (ранен);
— количество уничтоженных (потопленных) кораблей в эскадре.
Также хочу напомнить, что весь алгоритм игры «Морской бой», реализован в классе Controller
. Подробнее о классе Controller
и инициализации игры «Морской бой», читайте в статье Игра Морской бой на JavaScript. Редактирование положения корабля и начало игры.
Игра «Морской бой». Блокировка клетки игрового поля и установка маркера.
Прежде чем рассматривать реализацию выстрела игрока, необходимо разобраться с блокировкой клеток игрового поля и установкой маркера на них. Напомню, таким образом отмечаются клетки игрового поля, в которых, согласно правилам игры, не могут располагаться корабли — это соседние с кораблём клетки, как по вертикали и горизонтали, так и по диагонали. У вас может возникнуть вопрос, а зачем их помечать? Это позволяет лучше оценить возможное расположение кораблей противника и облегчает выбор координат для следующего выстрела.
Маркер устанавливается кликом правой кнопки мыши по клетке игрового поля компьютера, при этом клетка не должна содержать отметки попадания или промаха. Снимается маркер повторным кликом правой кнопкой.
Для этой цели, мы в функции init
зарегистрировали обработчик события contextmenu
:
1 2 3 4 5 |
// установка и снятие отметки на клетку, где однозначно не может быть // палубы корабля противника computerfield.addEventListener('contextmenu', this.setUselessCell.bind(this)); |
Алгоритм по установке и снятию маркера на клетке игрового поля не сложен. Давайте рассмотрим его:
-
1
Используя функцию
transformCoordsInMatrix
, переводим координаты точки клика правой кнопкой мыши, в координаты матрицы.Запомните, это очень важно.
Координаты точки клика отсчитываются относительно верхнего левого угла игрового поля противника. -
2
Получаем коллекцию всех объектов маркеров заблокированных клеток.
-
3
Перебираем полученную коллекцию, сравнивая координаты иконок с координатами клика.
-
4
Если координаты ни одной иконки не совпадут, значит клетка, по которой был сделан клик, пустая. Устанавливаем на неё маркер пустой клетки.
-
5
Если координаты совпадут, значит этой клетке уже установлена какая-то иконка. В зависимости от типа иконки или удаляем её, или кратковременно подсвечиваем красным цветом.
Теперь напишем JS-код, реализующий данный алгоритм. Для этого мы создадим функцию setUselessCell
и запишем её в конец класса Controller
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
setUselessCell(e) { e.preventDefault(); // проверяем нажатие правой кнопки мыши и флага, блокирующего // действия игрока if (e.which != 3 || compShot) return; // преобразуем координаты клика относительно документа в координаты матрицы const coords = this.transformCoordsInMatrix(e, computer); // проверяем наличие иконок по полученным координатам // если иконка присутствует, то, в зависимости от типа, удаляем её или // кратковременно подсвечиваем красным цветом const check = this.checkUselessCell(coords); // если по данным координатам иконки отсутствуют, устанавливаем маркер // пустой клетки if (check) { this.showIcons(this.opponent, coords, 'shaded-cell'); } } |
Как мы видим, функция setUselessCell
использует три новые функции — transformCoordsInMatrix
, checkUselessCell
, showIcons
. Эти функции будут использоваться и в дальнейшем, при формировании выстрела. Поэтому рассмотрим их подробнее.
Преобразование координат относительно документа в координаты матрицы.
Функция transformCoordsInMatrix
принимает два параметра:
- event
- Событие сгенерированное пользователем. В список свойств данного события, входят
pageX
иpageY
, которые содержат координаты клика в пикселях по оси'X'
и оси'Y'
относительно всего документа. - self
- Экземпляр игрового поля, относительно которого будут преобразовываться координаты клика. Содержит координаты сторон игрового поля —
fieldTop
иfieldLeft
.
Прежде чем приступим к написанию JS-кода, внимательно изучите приведённый ниже рисунок, чтобы понять принцип преобразования координат.
Где:
- e.pageX
- координата точки клика по оси Х относительно документа;
- fieldLeft
- координата левой границы игрового поля по оси Х;
- e.pageX — fieldLeft
- координата точки клика по оси Х относительно левой границы игрового поля.
По оси ‘Y’ — аналогично.
Полученные координаты точки клика переводим в координаты двумерного массива (матрицы). Для этого разность координат необходимо поделить на ширину стороны клетки, которая равна размеру палубы корабля. Размер палубы является значением статического свойства (константы) SHIP_SIDE
класса Field
. Полученное значение округляем с недостатком или просто отбрасываем от него дробную часть, что одно и то же. Оставшаяся целая часть и есть координата двумерного массива.
1 2 3 4 5 6 7 |
transformCoordsInMatrix(e, self) { const x = Math.trunc((e.pageY - self.fieldTop) / Field.SHIP_SIDE); const y = Math.trunc((e.pageX - self.fieldLeft) / Field.SHIP_SIDE); return [x, y]; } |
Проверка наличия маркеров по вычисленным координатам.
Проверка наличия маркеров заведомо пустых клеток по координатам клика левой или правой кнопкой мыши осуществляется в функции checkUselessCell
. Функция принимает единственный параметр — координаты клика.
Теперь рассмотрим особенность в работе функции.
Функция checkUselessCell
используется не только для установки / снятия маркера пустой клетки, но и при выстреле игрока, для проверки отсутствия маркера по координатам выстрела. Причём, её поведение зависит от того, какая функция её вызвала. Вот при определении имени вызвавшей функции и начинаются сложности.
Первое, что приходит на ум — использовать свойство caller
.
1 2 3 |
const f = this.checkUselessCell.caller.name; |
К сожалению, выдаётся такая ошибка:
Uncaught TypeError: ‘caller’, ‘callee’, and ‘arguments’ properties may not be accessed on strict mode functions or the arguments objects for calls to them
В версии ES6/ES2015 и более поздних версиях режим use strict включается принудительно, независимо от того, прописано это в коде или нет.
Придётся идти более длинным и неочевидным путём, используя нестандартное свойство stack
объекта Error
. Это свойство возвращает трассировку стека вызываемых функций в порядке их выполнения. Парсим полученный результат и получаем имя функции, вызвавшей checkUselessCell
.
1 2 3 |
const f = (new Error()).stack.split('\n')[2].trim().split(' ')[1]; |
В зависимости от полученного имени функции, или удаляем маркер или окрашиваем его в красный цвет на 0.5 сек.
Хочу сказать, что выстрел по установленному маркеру возможен только при выстреле игрока. Компьютеру выстрелить по заблокированной клетке не даст алгоритм, управляющий его игрой.
Полный код функции checkUselessCell
с комментариями:
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 |
checkUselessCell(coords) { // данная строчка кода используется при установке маркера игроком // если значение матрицы по полученным координатам отлично от нуля, // считаем, что в этом месте уже установлена некая иконка if (computer.matrix[coords[0]][coords[1]] > 1) return false; // получаем коллекцию маркеров на игровом поле противника const icons = this.opponent.field.querySelectorAll('.shaded-cell'); if (icons.length == 0) return true; for (let icon of icons) { // получаем координаты иконки и сравниваем их с аргументом функции const [x, y] = Controller.getCoordsIcon(icon); if (coords[0] == x && coords[1] == y) { // если координаты иконки и координаты полученные в аргументе совпали, // проверяем, какая функция вызвала функцию checkUselessCell const f = (new Error()).stack.split('\n')[2].trim().split(' ')[1]; if (f == 'Controller.setUselessCell') { // удаляем маркер пустой клетки icon.remove(); } else { // на 0.5s окрашиваем маркер в красный цвет icon.classList.add('shaded-cell_red'); setTimeout(() => { icon.classList.remove('shaded-cell_red') }, 500); } return false; } } return true; } |
Отображение иконок на игровом поле.
Напомню, что за отображение иконок отвечает функция showIcons
, которая в качестве аргумента принимает три параметра:
- экземпляр игрового поля, на котором будет размещена иконка;
- координаты иконки в виде координат матрицы;
- класс CSS, определяющий тип иконки.
Алгоритм работы функции очень простой. С помощью встроенного метода createElement
создаётся элемент SPAN
, которому прописываются полученный класс CSS, а стилям left
и top
соответствующие координаты. После этого SPAN
переносится на игровое поле. Иконки промаха и попадания выводятся с задержкой в 400 мс.
JS-код функции showIcons
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
showIcons(opponent, [x, y], iconClass) { // экземпляр игрового поля на котором будет размещена иконка const field = opponent.field; // небольшая задержка при формировании иконок промаха и попадания if (iconClass === 'dot' || iconClass === 'red-cross') { setTimeout(() => fn(), 400); } else { fn(); } function fn() { // создание элемента и добавление ему класса и стилей const span = document.createElement('span'); span.className = `icon-field ${iconClass}`; span.style.cssText = `left:${y * Field.SHIP_SIDE}px; top:${x * Field.SHIP_SIDE}px;`; // размещаем иконку на игровом поле field.appendChild(span); } } |
Игра «Морской бой». Получение координат выстрела и его визуализация.
Как я писал ранее в предыдущей статье, в функции init
устанавливается обработчик события, который при клике левой кнопкой мыши по игровому полю компьютера запускает функцию makeShot
:
1 2 3 |
computerfield.addEventListener('click', this.makeShot.bind(this)); |
Аргументом функции makeShot
является событие, генерируемое игроком, при клике по игровому полю компьютера. По наличию данного аргумента можно определить — кто стреляет. Если выстрел был компьютера, то e === undefined
. На данном этапе, мы рассмотрим работу функции применительно к выстрелу игрока. Небольшие дополнения к JS-коду, относящиеся к выстрелу компьютера мы рассмотрим позже, в следующей статье.
Итак, e !== undefined
, т. е. стреляет игрок. При этом необходимо убедиться, что клик сделан левой кнопкой мыши и не установлен флаг compShot
, сигнализирующий о том, что в данный момент запущен алгоритм выстрела компьютера. Добавим для этого в код функции проверку следующего условия:
1 2 3 |
if (e.which != 1 || compShot) return; |
Если условие не выполнилось, то, используя ранее рассмотренную функцию transformCoordsInMatrix
, преобразуем координаты клика в координаты (индексы) матрицы. Используя полученные координаты, проверяем, что в месте клика не установлен маркер однозначно пустой клетки или иконки промаха и попадания. Для этого вызовем функцию checkUselessCell
. Если функция возвращает true
, запускаем механизм визуализации взрыва по координатам выстрела.
Реализация взрыва достаточно простая и основана на css-анимации.
C помощью функции showIcons
создаём иконку взрыва. Добавляем иконке класс active
, который реализует css-анимацию. Через 430 ms удаляем иконку с игрового поля.
Весь этот алгоритм реализуем в функции showExplosion()
:
1 2 3 4 5 6 7 8 |
showExplosion(x, y) { this.showIcons(this.opponent, [x, y], 'explosion'); const explosion = this.opponent.field.querySelector('.explosion'); explosion.classList.add('active'); setTimeout(() => explosion.remove(), 430); } |
Добавим вызов функции makeShot
. Теперь, на данном этапе функция makeShot
выглядит следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
makeShot(e) { let x, y; if (e !== undefined) { if (e.which != 1 || compShot) return; ([x, y] = this.transformCoordsInMatrix(e, this.opponent)); // проверяем наличие иконки 'shaded-cell' по полученым координатам const check = this.checkUselessCell([x, y]); if (!check) return; } else { // получаем координаты для выстрела компьютера // рассмотрим в следующей статье ... } // показываем и удаляем иконку выстрела this.showExplosion(x, y); } |
Игра «Морской бой». Обработка результатов выстрела.
В данном разделе мы с вами рассмотрим JS-код, который обрабатывает информацию, полученную из двумерного массива matrix
и, в зависимости от результата выстрела (промах, попадание, повторный обстрел одних и тех же координат), отображает игровой процесс на экране монитора.
Этот JS-код обрабатывает как выстрел игрока, так и, с небольшими дополнениями, выстрел компьютера. Поэтому постараемся разобрать его как можно подробнее.
Для вызова тех или иных функции, в зависимости от полученного значения, будем использовать конструкцию switch
. Аргументом для данной конструкции будет полученное из матрицы значение v
. В конец функции makeShot
запишем следующий 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 |
makeShot(e) { let x, y; if (e !== undefined) { if (e.which != 1 || compShot) return; ([x, y] = this.transformCoordsInMatrix(e, this.opponent)); // проверяем наличие иконки 'shaded-cell' по полученым координатам const check = this.checkUselessCell([x, y]); if (!check) return; } else { // получаем координаты для выстрела компьютера ([x, y] = this.getCoordsForShot()); } // показываем и удаляем иконку выстрела this.showExplosion(x, y); const v = this.opponent.matrix[x][y]; switch(v) { case 0: // промах this.miss(x, y); break; case 1: // попадание this.hit(x, y); break; case 3: // повторный обстрел case 4: Controller.showServiceText('По этим координатам вы уже стреляли!'); break; } } |
Теперь рассмотрим подробнее JS-код обработки промаха и попадания.
Игра «Морской бой». Промах и продолжение игры.
Рассмотрим алгоритм обработки промаха:
-
1
Визуально отображаем промах.
-
2
Записываем в матрицу (двумерный массив) экземпляра
computer
по этим координатам значение3
.
Напоминаю, что мы рассмотриваем выстрел игрока. В случае выстрела компьютера запись будет происходит в двумерный массив экземпляраhuman
. -
3
Выводим сообщение о промахе.
-
4
Определяем, чей выстрел будет следующим.
-
5
Если следующий выстрел делает компьютер, то устанавливаем флаг
compShot
вtrue
, чтобы в это время игрок не мог производить никаких действий. В противном случае — устанавливаем флаг в состояниеfalse
.
Данный алгоритм реализован в функции miss
. В качестве параметров функция принимает координаты выстрела преобразованные в координаты (индексы) двумерного массива matrix
.
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 |
miss(x, y) { let text = ''; // устанавливаем иконку промаха и записываем промах в матрицу this.showIcons(this.opponent, [x, y], 'dot'); this.opponent.matrix[x][y] = 3; // определяем статус игроков if (this.player === human) { text = 'Вы промахнулись. Стреляет компьютер.'; this.player = computer; this.opponent = human; compShot = true; setTimeout(() => this.makeShot(), 2000); } else { text = 'Компьютер промахнулся. Ваш выстрел.'; this.player = human; this.opponent = computer; compShot = false; // код, относящийся к промаху компьютера, будет рассмотрен в следующей статье ... } setTimeout(() => Controller.showServiceText(text), 400); } |
Игра «Морской бой». Попадание в корабль противника.
Алгоритм обработки результата попадания в корабль противника:
-
1
Визуально отображаем попадание и записываем в двумерный массив
matrix
экземпляра противника по этим координатам значение4
. -
2
Выводим сообщение о попадании.
-
3
Перебираем объект эскадры
squadron
экземпляра противника, в котором храниться информация о каждом корабле. По координатам выстрела определяем корабль в который произошло попадание. -
4
Увеличиваем на единицу счётчик попаданий по данному кораблю.
-
5
Если количество попаданий равно количеству палуб у корабля, то удаляем корабль из объекта эскадры.
-
6
Проверяем, все ли корабли противника потоплены. В зависимости от результата или продолжаем (делаем следующий выстрел) или заканчиваем игру, объявляя о победе.
Данный алгоритм реализован в функции hits
. В качестве параметров функция принимает координаты выстрела преобразованные в координаты (индексы) двумерного массива matrix
.
Первые два пункта алгоритма подробно рассматривать не будем, т. к. его JS-код практически полностью совпадает с JS-кодом обработки промаха.
1 2 3 4 5 6 7 8 9 10 11 |
hit(x, y) { let text = ''; // устанавливаем иконку попадания и записываем попадание в матрицу this.showIcons(this.opponent, [x, y], 'red-cross'); this.opponent.matrix[x][y] = 4; // выводим текст, зависящий от стреляющего text = (this.player === human) ? 'Поздравляем! Вы попали. Ваш выстрел.' : 'Компьютер попал в ваш корабль. Выстрел компьютера'; setTimeout(() => Controller.showServiceText(text), 400); } |
Игра «Морской бой». Поиск корабля, в который произошло попадание.
Как я писал ранее, для поиска корабля, в который произошло попадание, необходимо перебрать объект эскадры squadron
. Каждым элементом данного объекта является экземпляр объекта корабля, созданный с помощью конструктора Ships
.
Более подробно об объекте корабля изложено в описании конструктора кораблей —
Ships
в статье «Игра Морской бой на JavaScript. Рандомная расстановка кораблей.»
На данном этапе нам понадобятся только три свойства объекта корабля:
- decks — количество палуб;
- hits — счётчик попаданий;
- arrDecks — массив с координатами каждой палубы корабля.
В процессе поиска, нужно перебрать массив arrDecks
каждого корабля эскадры, сравнивая координаты палуб с координатами выстрела. В случае совпадения, увеличиваем счётчик попадания объекта найденного корабля на единицу и сравниваем значение счётчика с количеством палуб. Если эти значения равны, то удаляем корабль из объекта эскадры.
Допишем в конец функции hits
следующий JS-код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
outerloop: for (let name in this.opponent.squadron) { const dataShip = this.opponent.squadron[name]; for (let value of dataShip.arrDecks) { // перебираем координаты палуб и сравниваем с координатами попадания // если координаты не совпадают, переходим к следующей итерации if (value[0] != x || value[1] != y) continue; dataShip.hits++; // если попаданий меньше количества палуб, прерываем перебор координат палуб if (dataShip.hits < dataShip.arrDecks.length) break outerloop; // код, относящийся к выстрелу компьютера, будет рассмотрен в следующей статье if (this.opponent === human) { ... } // если количество попаданий в корабль равно количеству палуб, // удаляем данный корабль из объекта эскадры delete this.opponent.squadron[name]; break outerloop; } } |
Чтобы избежать вложенных условных операторов if
, в приведённом JS-коде использована директива continue
.
Для того, чтобы исключить ненужные итерации внутреннего и внешнего цикла, используется оператор break
с меткой outerloop
. Такое использование оператора break
позволяет выйти сразу и из внутреннего и из внешнего циклов.
Игра «Морской бой». Окончание игры.
Принимать решение о продолжении боя или его окончании мы будем на основании длины массива ключей объекта squadron
— если его длина равна нулю, значит все корабли эскадры противника уничтожены. В противном случае, морской бой продолжается и игрок может совершить новый выстрел. Хочу сразу отметить, что большинство JS-кода относится к выстрелу компьютера. Подробно это будет рассмотрено в следующей статье.
Рассмотрим подробнее окончание игры при выстреле игрока. При окончании игры необходимо совершить всего два действия:
- вывести текстовое сообщение поздравления с выигрышем;
- показать кнопку продолжения игры.
Напишем JS-код, который реализует описанный функционал. Разместим его в конце функции hits
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
if (Object.keys(this.opponent.squadron).length == 0) { // код, относящийся к выстрелу компьютера, будет рассмотрен в следующей статье if (this.opponent === human) { ... } else { text = 'Поздравляем! Вы выиграли!'; } Controller.showServiceText(text); // показываем кнопку продолжения игры buttonNewGame.hidden = false; // код, относящийся к выстрелу компьютера, будет рассмотрен в следующей статье } else if (this.opponent === human) { ... } |
1 2 3 |
Object.keys(this.opponent.squadron).length |
У объектов отсутствует встроенное свойство length
, определяющее их размер. С помощью метода Object.keys
получим массив ключей объекта, а далее вычислим длину этого массива.
Теперь полный JS-код функции hit
выглядит следующим образом:
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 |
hit(x, y) { let text = ''; // устанавливаем иконку попадания и записываем попадание в матрицу this.showIcons(this.opponent, [x, y], 'red-cross'); this.opponent.matrix[x][y] = 4; // выводим текст, зависящий от стреляющего text = (this.player === human) ? 'Поздравляем! Вы попали. Ваш выстрел.' : 'Компьютер попал в ваш корабль. Выстрел компьютера'; setTimeout(() => Controller.showServiceText(text), 400); // перебираем корабли эскадры противника outerloop: for (let name in this.opponent.squadron) { const dataShip = this.opponent.squadron[name]; for (let value of dataShip.arrDecks) { // перебираем координаты палуб и сравниваем с координатами попадания // если координаты не совпадают, переходим к следующей итерации if (value[0] != x || value[1] != y) continue; dataShip.hits++; if (dataShip.hits < dataShip.arrDecks.length) break outerloop; // код, относящийся к выстрелу компьютера, будет рассмотрен в следующей статье if (this.opponent === human) { ... } // если количество попаданий в корабль равно количеству палуб, // удаляем данный корабль из массива эскадры delete this.opponent.squadron[name]; break outerloop; } } // все корабли эскадры уничтожены if (Object.keys(this.opponent.squadron).length == 0) { // код, относящийся к выстрелу компьютера, будет рассмотрен в следующей статье if (this.opponent === human) { ... } else { text = 'Поздравляем! Вы выиграли!'; } Controller.showServiceText(text); // показываем кнопку продолжения игры buttonNewGame.hidden = false; } } |
Игра «Морской бой». Выстрел по ранее обстрелянным координатам.
Если игрок выстрелил по клетке, которую обстрелял ранее и там зафиксировано попадание или промах, то игроку выдаётся предупреждение о повторном обстреле данных координат.
Напомню код конструкции switch
в функции makeShot
:
1 2 3 4 5 6 |
case 3: // повторный обстрел case 4: Controller.showServiceText('По этим координатам вы уже стреляли!'); break; |
На этом мы закончим рассматривать выстрел игрока.
В следующей статье мы рассмотрим выстрел компьютера, напишем функционал, который будет оценивать игровую обстановку и вести обстрел эскадры игрока по оптимальному алгоритму, анализировать результаты выстрела и учитывать эти результаты при выборе координат для следующего выстрела.
Комментарии
-
Комментарии должны содержать вопросы и дополнения по статье, ответы на вопросы других пользователей.
Комментарии содержащие обсуждение политики, будут безжалостно удаляться. -
Для удобства чтения Вашего кода, не забываейте его форматировать. Вы его можете подсветить код с помощью тега
<pre>
:
—<pre class="lang:xhtml">
- HTML;
—<pre class="lang:css">
- CSS;
—<pre class="lang:javascript">
- JavaScript. - Если что-то не понятно в статье, постарайтесь указать более конкретно, что именно не понятно.
-
-
В данный момент пишу статью «Выстрел компьютера». Параллельно дополняю и улучшаю ИИ компьютера. Думаю, что ещё пару недель у меня это займёт.
Скорее всего, это будет последняя статья из цикла «Морской бой».
-
-
Привет, я насчёт всего этого бурелома с new Error().stack….. А что, если функции checkUselessCell добавить ещё один параметр и назвать его, скажем, isShaded? И вызывать её из разных функций соответственно либо true, либо false? Вроде работает.
-
Решил я пристроить себе на сайт эту удвивтельную реализацию, но вот незадача… не работает перетаскивание корабликов. Связано это с тем, что изначально поле у меня скрыто и выезжает из за угла по клику со сменой right:-100%. А вот все остальное работает. Может надо как-то переиницилизировать координаты? Поддержите js советом
Привет, сколько всего частей планируешь. И когда следующая ?