Tháng ba bất chợt một ngày trắng tinh hoa sưa về đây… Hà Nội, Chủ Nhật 26/02/2023…

Ta vội bước trên phố phường Hà Noi
Bỗng gặp hàng sưa trắng một mau hoa
Trắng cả góc trời níu bư ớc chan qua
Lưu luyến qúa chùm hoa sưa lặng lẽ

Hương hoa không nồng nàn như hoa sữa
Khong sắc màu tim biếc cánh bằng lăng
Khong vàng thẫm một màu hoa điệp lan
Mà nhẹ nhàng нет duyên sưa trắng muốt…

1. Представьте

1.1. Обзор

Каро — простая игра, в которую мы все играли в детстве. Мы можем играть в игры в двух режимах: человек против человека или, более продвинутый, человек против компьютера. Однако, мы делаем это красиво, мы можем создать два компьютера, и они могут играть с ними. В статье я буду разрабатывать игру с тремя режимами:

- Человек против человека

- Человек против компьютера

- Компьютер против компьютера

1.2. Представьте игру с каро

Гомоку существовало в Японии еще до Реставрации Мэйдзи (1868 г.).\[7\] Название «гомоку» происходит от японского языка, в котором оно упоминается как гомокунарабэ (五目並べ). Го означает пять, моку — слово, противоположное фигурам, а нарабе — построение. Игра популярна в Китае, где она называется Wuziqi (五子棋).\[8\] Wu (五 wǔ) означает пять, zi (子 zǐ) означает «кусок», а qi (棋 qí) относится к категории настольных игр в Китайский язык. Игра также популярна в Корее, где она называется омок (오목 \[五目\]), что имеет ту же структуру и происхождение, что и японское название. В девятнадцатом веке игра была представлена ​​​​в Британии, где она была известна как Go Bang, что, как говорят, является искажением японского слова goban, которое само было адаптировано из китайского k'i pan (qí pan) «го-доска». ”

1.3. Правила игры

Фристайл-гомоку не имеет ограничений ни для одного из игроков и позволяет игроку выиграть, создав линию из пяти или более камней, при этом каждый игрок поочередно кладет по одному камню за раз.

Давно известно, что черные (игрок, делающий первый ход) имеют преимущество, еще до того, как Л. Виктор Эллис доказал, что черные могут добиться победы (см. Ниже). Рэндзю пытается смягчить этот дисбаланс с помощью дополнительных правил, направленных на уменьшение преимущества черных первого игрока. В нее играют на доске 15 × 15 с правилами «три-три», «четыре-четыре» и надчеркиваниями, применяемыми только к черным.

- Правило трех и трех запрещает ход, который одновременно образует два открытых ряда по три камня (ряды, не заблокированные камнем противника с обоих концов).

- Правило четырех и четырех запрещает ход который одновременно образует два ряда из четырех камней (открытых или нет).

- Верхние линии не позволяют игроку выиграть, если он образует линию из 6 или более камней. Рэндзю также использует различные правила открытия турниров, такие как Soosõrv-8, текущий международный стандарт.

В Каро (также называемом гомоку +, популярным среди вьетнамцев) победитель должен иметь верхнюю линию или непрерывный ряд из пяти камней, которые не заблокированы ни с одного конца (верхние линии не подпадают под это правило). Это делает игру более сбалансированной и дает белым больше возможностей для защиты.

2. Структура игрового проекта

css: это папка, в которой сохраняются все файлы CSS: style.html, украшающие страницы и т. д.

изображения: это это папка, в которой сохраняются все изображения игры: фон, X, O,…

js: это папка, в которой сохраняются все файлы JavaScript: обрабатывать и обрабатывать все функции игры.

caro.html, home.html: Интерфейс игры.

3. Разработка игры

3.1. Интерфейс игры

home.html : домашняя страница игры Caro.

‹!DOCTYPE html›
‹html lang="en"›

‹ссылка rel="таблица стилей" href=""../common/css/home.css"›
‹ссылка rel="таблица стилей" href="css/caro.css"›

‹head›
‹meta charset="UTF-8›
‹title›Домашняя игра крестики-нолики‹/title›
‹/head›
‹body›
‹div›
‹div class="options"›
‹label for="list-type-play"›‹/label›‹select id="list-type-play" class=" скрыть-option option”›
‹option selected=”selected” disabled="disabled" value=””›Select type play‹/option›
‹option value=”2-players”›2 игрока ‹/option›
‹option value="игрок-компьютер"›Player и компьютер‹/option›
‹option value="компьютер-компьютер"›Компьютер и компьютер‹/option›
‹/выбрать›
‹/дел›

‹div class="options" style="margin-top: 30px"›
‹label for="list-row"›‹/label›‹select id="list-row" class="hide-option option”›
‹option selected="selected" disabled="disabled" value=""›Выберите количество строк‹/option›
‹option value="10›10‹/option›
‹значение опции=”20›20‹/опция›
‹значение опции=”30›30‹/опция›
‹значение опции=”40›40‹/опция›
‹значение опции=”50›50‹/опция›
‹значение опции=”60›60‹/опция›
‹/выбрать›
‹/div›

‹div class="options" style="margin-top: 30px"›
‹label for="list-column"›‹/label›‹select id="list-column" class="hide-option option”›
‹option selected="selected" disabled="disabled" value=""›Выберите количество столбцов‹/option›
‹option value="10›10‹/option›
‹значение опции=”20›20‹/опция›
‹значение опции=”30›30‹/опция›
‹значение опции=”40›40‹/опция›
‹значение опции=”50›50‹/опция›
‹значение опции=”60›60‹/опция›
‹/выбрать›
‹/div›
‹/ раздел ›

‹div class="button" id="button" onclick="handleLetGo()"›Поехали!‹/div›
‹/body›
‹/html›

‹тип скрипта="text/javascript" src="js/caro-home.js"›‹/script›

caro.html: основной интерфейс игры.

‹!DOCTYPE html›
‹html lang="en"›

‹link rel=”stylesheet” href=”css/caro.css”›
‹head›
‹meta charset=”utf-8”›
‹meta name=”viewport” content ="width=device-width, initial-scale=1.0'›
‹title›Game caro‹/title›
‹link href="https://fonts.googleapis.com/css? family=Инди+Цветок' rel=»таблица стилей»›
‹/head›
‹body›

‹h1›Игра в игру X-O‹/h1›
‹table id="table_game"›
‹/table›

‹/тело›
‹/html›

‹тип скрипта=”текст/javascript” src=”js/constants.js”›‹/script›
‹тип скрипта=”текст/javascript” src="js/caro-main.js"›‹/ сценарий›

caro.css: мы можем добавить стиль ко всем элементам HTML: цвет страницы, фон, шрифт и размер слов.

body {
background-color: rgb(32, 32, 32);
background-image: url(“https://janschreiber.github.io/img2/black-chalk.jpg' );
цвет: rgb(230, 230, 230);
выравнивание текста: по центру;
семейство шрифтов: 'Indie Flower', 'Comic Sans', курсив;
> размер шрифта: 0,7 em;
}
h1 {
высота строки: 1 em;
нижнее поле: 0;
нижний отступ: 5 пикселей;< br /> размер шрифта: 2,8 em;
вес шрифта: полужирный;
}
h2 {
размер шрифта: 1,3 em;
вес шрифта: полужирный;
отступ: 0;
поле: 0;

}
h3 {
размер шрифта: 1.1em;
оформление текста: подчеркивание;
стиль оформления текста: пунктир;
заполнение: 0;
margin: 10px 0 2px 0;
}
table {
margin: 2% auto;
border-collapse: Collapse;
}
# table_game {
позиция: относительная;
размер шрифта: 120 пикселей;
поле: 1% авто;
граница-коллапс: свернуть;
}
. td_game {
граница: 4 пикселя сплошная rgb(230, 230, 230);
ширина: 90 пикселей;
высота: 90 пикселей;
отступ: 0;
выравнивание по вертикали. : по середине;
выравнивание текста: по центру;
}
.fixed {
ширина: 90 пикселей;
высота: 90 пикселей;
высота строки: 90 пикселей. ;
отображение: блок;
переполнение: скрыто;
курсор: указатель;
}
.td_list {
text-align: center;
> размер шрифта: 1,3 em;
вес шрифта: жирный;
}
.th_list {
размер шрифта: 1,3 em;
вес шрифта: полужирный ;
text-align: center;
text-decoration: underline;
}
#restart {
font-size: 3em;
width: 1em ;
высота: 0,9em;
курсор: указатель;
поле: 0 авто;
переполнение: скрыто;
}
.x {
> цвет: темно-лососевый;
положение: относительное;
верх: -8 пикселей;
размер шрифта: 1,2 em;
курсор: по умолчанию;
}
.o {
цвет: аквамарин;
положение: относительное;
верх: -7px;
размер шрифта: 1.0em;
курсор: по умолчанию;
> }

/* модальный фон */
.modal {
display: none;
position: fixed;
z-index: 1;
left: 0;
> сверху: 0;
ширина: 100%;
высота: 100%;
переполнение: авто; /* при необходимости включить прокрутку */
background-color: black; /* резервный цвет */
background-color: rgba(0, 0, 0, 0.6);
}

/* модальное содержимое */
.modal-content {
background-color: rgb(240, 240, 240);
color: rgb(32, 32, 32);
> размер шрифта: 2em;
вес шрифта: полужирный;
/* 16 % сверху и по центру */
поле: 16 % авто;
отступ: 20 пикселей;
рамка: 2 пикселя сплошного черного цвета;
радиус границы: 10 пикселей;
ширина: 380 пикселей;
максимальная ширина: 80 %;
}
.modal -content p {
margin: 0;
padding: 0;
}

/* кнопка закрытия модального диалога */
.close {
color: rgb(170, 170, 170);
float: right;
position: relative;
> вверху: -25px;
справа: -10px;
размер шрифта: 34px;
вес шрифта: полужирный;
}
.close:hover,< br /> .close: focus {
цвет: черный;
оформление текста: нет;
курсор: указатель;
}

.win-color {
background-color: rgb(240, 240, 240);
}

Потому что в интерфейсе игры есть только HTML и CSS. Это не сложно и не сложно, поэтому давайте прочитаем и разберемся! 😄😄😄 Ниже результат

3.2. Управление всеми функциями игры

caro-home.js: это файл, который обрабатывает все функции и события на главной странице. На главной странице мы будем обрабатывать только те события, которые происходят, когда игроки нажимают кнопку **Поехали**. Мы добавим три параметра: режим игры, сумму строк и сумму столбцов.

function handleLetGo() {
let typePlay = document.getElementById("list-type-play").value;
let rows = document.getElementById("list-row").value;
let rows = document.getElementById("list-row").value;
> let columns = document.getElementById("list-column").value;

if (typePlay === "" || rows === "" || columns === "") {
alert("Vui lòng chọn kiểu chơi");
return
}
window.location.href = "/game-development/games/caro/caro.html?type=" + typePlay + "&rows=" + rows + "&columns=" + columns;
}

caro-main.js: это файл, который обрабатывает все основные события игры. На данный момент есть много событий и функций, которые мы будем развивать. Давайте развивать игру вместе со мной! 😁😁😁

Конечно! В первый раз игра запустит переменные, события, музыку и изображения… **функция init** получит значение домашней страницы для режима игры, сумму строк и сумму столбцов. На основе этой информации мы создадим игровую матрицу с *суммой строк* *суммой столбцов*. Матрица игры сохранит состояние игры и отрисует его по тегу table.

пусть tableXO = document.getElementById («table_game»);
tableXO.innerHTML = tableContent

Инициализация полной функции исходного кода

function init() {
player = X;
matrixGame = [];
typeGame = TWO_PLAYER;
const urlParams = new URLSearchParams(window.location.search);
> let rows = urlParams.get («строки»);
let columns = urlParams.get («столбцы»);

if (строки === "" || столбцы === "" || (urlParams.get("тип") !== TWO_PLAYER && urlParams.get("тип") !== КОМПЬЮТЕР && urlParams.get(" type") !== COMPUTER_COMPUTER)) {
window.location.href = "/game-development/games/caro/home.html";
}

typeGame = urlParams.get («тип»)

// Таблица данных
let tableXO = document.getElementById(“table_game”);
let tableContent = “”;

for (let row = 0; row ‹ rows; row++) {
let arr = [];
let rowHTML = “‹tr›”;
for (let col = 0; col ‹ столбцы; col++) {
arr.push("");
rowHTML += `‹td class="td_game"›‹div id="` + row.toString() + "-" + col.toString() + `” onclick="handleClick(this.id)" class="fixed"›‹/div›‹/td›`
}
rowHTML += “‹/tr› ”;

tableContent += rowHTML;
matrixGame.push(arr);
}

tableXO.innerHTML = tableContent
}

window.addEventListener("загрузка", (событие) =› {
init();
});

Обработка проверки состояний ничьей и выигрыша в игре довольно проста:

- Игра состояния рисования: мы будем перебирать все элементы в матрице игры (строки x столбцы). Если все элементы обозначены знаком « », то эта локация не имеет хода. И все элементы! = “” = Игра ничья.

- Игра состояния победы: мы проверим горизонталь, вертикаль, правую диагональ и левую диагональ. Если за один ход их больше 5, игрок или компьютер выиграют игру.

Исходный код:

Рисовать

function checkDraw() {
for (let i = 0; i ‹ matrixGame.length; i++) {
for (let j = 0; j ‹ matrixGame[0].length; j++) {
for (let j = 0; j ‹ matrixGame[0].length; j++) {
/> if (matrixGame[i][j] === "") {
return false
}
}
}

вернуть истину
}

Выиграть

function getHorizontal(x, y, player) {
let count = 1;
for (let i = 1; i ‹ 5; i++) {
if (y + i ‹ matrixGame[0 ].length && matrixGame[x][y + i] === player) {
count++;
} else {
break
}
}

for (пусть i = 1; i ‹ 5; i++) {
if (y — i ›= 0 && y — i ‹ matrixGame[0].length && matrixGame[x][y — i] === игрок) {
count++;
} else {
break
}
}

число возвратов;
}

function getVertical(x, y, player) {
let count = 1;
for (let i = 1; i ‹ 5; i++) {
if (x + i ‹ matrixGame.length && matrixGame[x + i][y] === player) {
count++;
} else {
break
}
}

for (пусть i = 1; i ‹ 5; i++) {
if (x — i ›= 0 && x — i ‹ matrixGame.length && matrixGame[x — i][y] === player) {
count++;
} else {
break
}
}

число возвратов;
}

function getRightDiagonal(x, y, player) {
let count = 1;
for (let i = 1; i ‹ 5; i++) {
if (x — i ›= 0 && x — i ‹ matrixGame.length && y + i ‹ matrixGame[0].length && matrixGame[x — i][y + i] === player) {
count++;
} else {< br /> перерыв
}
}

for (пусть i = 1; i ‹ 5; i++) {
if (x + i ‹ matrixGame.length && y — i ›= 0 && y — i ‹ matrixGame[0].length && matrixGame[x + i][y — i] === player) {
count++;
} else {
break
}
}

число возвратов;
}

function getLeftDiagonal(x, y, player) {
let count = 1;
for (let i = 1; i ‹ 5; i++) {
if (x — i ›= 0 && x — i ‹ matrixGame.length && y — i ›= 0 && y — i ‹ matrixGame[0].length && matrixGame[x — i][y — i] === player) {
count++;< br /> } еще {
перерыв
}
}

for (пусть i = 1; i ‹ 5; i++) {
if (x + i ‹ matrixGame.length && y + i ‹ matrixGame[0].length && matrixGame[x + i][y + i] === player) {
count++;
} else {
break
}
}

число возвратов;
}

function checkWin(points) {
return getHorizontal(Number(points[0]), Number(points[1]), player) ›= 5
|| getVertical(Number(points[0]), Number(points[1]), player) ›= 5
|| getRightDiagonal(Number(points[0]), Number(points[1]), player) ›= 5
|| getLeftDiagonal(Number(points[0]), Number(points[1]), player) ›= 5
}

3.3. Режим игры человек и человек

Мы обработали все игровые функции в версии 3.2, включая инициализацию игр, проверку розыгрышей и победу в игре. В версии 3.3 мы будем обрабатывать событие, когда игрок или компьютер делает ход на игровой матрице.

- Если функция **processClick** возвращает значение «выигрыш», то мы уведомим сообщение с содержанием «X/O является победителем» и снова запустим игру.

- Если функция **processClick** возвращает значение «draw», когда мы отправим сообщение с содержанием «Draw» и снова начнем игру.

function handleClick(id) {
switch (processClick(id)) {
case WIN:
setTimeout(function () {
alert("Player: " + player + " is победитель»);

// сброс игры
init();
}, 100);
break;
case DRAW:
setTimeout(function () {
alert( "Рисовать");

// сброс игры
init();
}, 100);
break;
}
}

function processClick: обработка события, когда игрок или компьютер играет ход в режиме игры «человек против человека». Мы выполним следующие шаги:

- Шаг 1: Если это место в игровой матрице имело значение «X» или «O», то мы вернем void.

- Шаг 2: Если игрок X, тогда установите значение место в матрице игры на «x» и нарисуйте X в игровом интерфейсе `html document.getElementById(id).innerHTML = XText;`

- Шаг 3: Если игрок O, тогда установите установите значение местоположения в матрице игры в «o» и нарисуйте X в интерфейсе игры `html document.getElementById(id).innerHTML = OText;`

— Шаг 4: Проверка ничья или выигрыш. Если это ничья, верните «ничья», а если это выигрыш, верните «выигрыш».

- Шаг 5: Поменяйте местами ход `javascript player = player === X ? О : Х;`

function processClick(id) {
let points = id.split("-");

switch (typeGame) {
case TWO_PLAYER:

if (matrixGame[Number(points[0])][Number(points[1])] === X || matrixGame[Number(points[0])][Number(points[1])] === O ) {
возврат
}

if (player === X) {
matrixGame[Number(points[0])][Number(points[1])] = X;
document.getElementById(id).innerHTML = XText;
}

if (player === O) {
matrixGame[Number(points[0])][Number(points[1])] = O;
document.getElementById(id).innerHTML = OText;
}

if (checkWin(points)) {
return WIN;
}

// проверка отрисовки
if (checkDraw()) {
return DRAW;
}

игрок = игрок === X ? O : X;
break;
case COMPUTER:
// исходный код для обработки воспроизведения с помощью компьютера
}
}

3.4. Режим игры человек и компьютер

Алгоритмов довольно много: минимакс, подъем в гору,… Они используются для выбора оптимальных поворотов. Пример с минимаксным алгоритмом:

Минимакс — это правило принятия решений, используемое в искусственном интеллекте, теории принятия решений, теории игр, статистике и философии для сведения к минимуму возможных потерь в наихудшем (максимальном) сценарии. Когда речь идет о прибыли, это называется «максимин» — максимизировать минимальную прибыль. Первоначально сформулированная для теории игр с нулевой суммой для нескольких игроков, охватывающая как случаи, когда игроки делают поочередные ходы, так и те, когда они делают одновременные ходы, она также была распространена на более сложные игры и на общее принятие решений в условиях неопределенности.

Здесь мы упростим игру и, основываясь на предыдущем опыте игрока в игре, определим стратегию игрока для победы.

Справочный документ:

См. эту статью: [Л. Victor Allis, HJ van den Herik, MPH Huntjens, 1993. Go-Moku and Threat-Space Search](https://web.archive.org/web/20140411074912/http://chalmersgomoku.googlecode.com/files/ allis1994.pdf)

Поехали 😘😘😘

Мы создадим 2 константы: MAP_SCORE_COMPUTER*, MAP_POINT_HUMAN.

MAP_SCORE_COMPUTER: мы проверим, есть ли у него 5 в том же ходе => победа => Счет этого хода - Бесконечность. Если у него есть 4 в том же ходе => счет = 2000 очков. Если у него есть 3 в том же ходе => счет = 500 очков. Если у него 2 в тот же ход => оценка = 300 очков. Если у него есть 1 в том же ходе => оценка = 100 очков. Эта оценка представляет собой атаку.

MAP_POINT_HUMAN: если у него есть 4 в том же ходу =› оценка = 999999 очков. Если у него есть 3 в том же ходе => оценка = 1000 очков. Если у него 2 в тот же ход => оценка = 400 очков. Если у него есть 1 в том же ходе => оценка = 10 очков. Если он имеет ноль в том же ходе => оценка = 0 очков. Эта оценка представляет собой защиту.

const MAP_SCORE_COMPUTER = новая карта([
[5, бесконечность], [4, 2000], [3, 500], [2, 300], [1, 100]
])
const MAP_POINT_HUMAN = новая карта([
[4, 999999], [3, 1000], [2, 400], [1, 10], [0, 0]
])

функция getPointsComputer: функция получит местоположение, которое является очередью компьютера.

- Шаг 1: maxScore – максимально возможная оценка, которую может получить компьютер. listScorePoint – это массив местоположений компьютера, которые можно отметить, а pointsComputer – это массив максимально возможных баллов компьютера.

 – Шаг 2. Мы прокрутите игровую матрицу и проверьте ее. Если значение местоположения равно «», то мы получим самый высокий один и тот же поворот по горизонтали, вертикали, правой диагонали и левой диагонали. Счет за ход = MAP_SCORE_COMPUTER[наивысший результат за ход] + MAP_POINT_HUMAN[наивысший результат за ход]

 – Шаг 3. Мы пройдемся по игровой матрице, имеет равный счет maxScore = вставить элемент в pointsComputer

- Шаг 4: Поскольку в каждом состоянии игры будет несколько ходов с одинаковым maxScore: Чтобы сделать его более справедливым, мы назначим точки случайным образом.

function getPointsComputer() {
let maxScore = -Infinity
let pointsComputer = []
let listScorePoint = []
for (let i = 0; i ‹ matrixGame.length; i++ ) {
for (let j = 0; j ‹ matrixGame[0].length; j++) {
if (matrixGame[i][j] === "") {
let score = MAP_SCORE_COMPUTER.get(Math.max(getHorizontal(i, j, O),getVertical(i,j,O),getRightDiagonal(i,j,O),getLeftDiagonal(i,j,O))) +
MAP_POINT_HUMAN.get(Math.max(getHorizontal(i, j, X),getVertical(i,j,X),getRightDiagonal(i,j,X),getLeftDiagonal(i,j,X)) — 1)< br /> if (maxScore ‹= оценка) {
maxScore = оценка
listScorePoint.push({
«оценка»: оценка,
«балл»: [i,j] ,
})
}
}
}
}

// получить максимальную оценку списка
for (константный элемент listScorePoint) {
if (element.score === maxScore) {
pointsComputer.push(element.point)
}
}
return pointsComputer[Math.floor(Math.random()*pointsComputer.length)]
}

3.5. Компьютер в игровом режиме и компьютер

Режим игры прикольный 😄😄😄 В режиме игры мы заменим ход человека другим компьютером. Мы по-прежнему используем функцию getPointsComputer для получения местоположения поворота.

async function ComputerAndComputer(sumPoints) {
for (let i = 0; i ‹ sumPoints; i++) {
await delay(100);
// компьютер A
let pointsComputerA = getPointsComputer()
matrixGame[pointsComputerA[0]][pointsComputerA[1]] = X;
document.getElementById(pointsComputerA[0].toString() + «-» + pointsComputerA[1].toString ()).innerHTML = XText;

// проверка выигрыша
if (checkWin(pointsComputerA)) {
return WIN;
}

// проверка отрисовки
if (checkDraw()) {
return DRAW;
}

игрок = игрок === X ? О: Х;

await delay(100);
// компьютер B
let pointsComputerB = getPointsComputer()
matrixGame[pointsComputerB[0]][pointsComputerB[1]] = O;
document. getElementById(pointsComputerB[0].toString() + «-» + pointsComputerB[1].toString()).innerHTML = OText;

// проверка выигрыша
if (checkWin(pointsComputerB)) {
return WIN;
}

// проверка отрисовки
if (checkDraw()) {
return DRAW;
}

игрок = игрок === X ? О : Х;
}
}

Мы проверим, является ли режим игры «компьютер и компьютер» в событии загрузки, а затем вызовем функцию «компьютер и компьютер», чтобы два компьютера играли с ними.

Примечание: пусть sumPoints = matrixGame.length x matrixGame[0].length — это сумма максимальных ходов. Если он достигает максимума за ход, то состояние игры — ничья.

window.addEventListener («загрузка», (событие) => {
init();

if(typeGame === COMPUTER_COMPUTER) {
let sumPoints = matrixGame.length * matrixGame[0].length
ComputerAndComputer(sumPoints).then(state =› {
switch (state) {
case WIN:
setTimeout(function () {
alert("Игрок: " + player + " — победитель");

// сброс игры
init();
location.reload();
}, 100);
break;
case DRAW:
setTimeout( function () {
alert("Ничья");

// сброс игры
init();
location.reload();
}, 100);
break;
}
})

});

4. Заключение

Вот некоторые из полученных изображений и видео. ^^

‹center›‹iframe width=»560’ height=»315’ src=»https://www.youtube.com/embed/jm5ieVDNo8g'›‹/iframe›‹/center›

* Сайт игры: https://nguyenvantuan2391996.github.io/game-development/games/caro/home.html

* Исходный код: https://github.com/nguyenvantuan2391996/ разработка игр/дерево/мастер/игры/каро

Ссылка: https://tuannguyenhust.hashnode.dev/