<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>AI Game</title>
    <style>
        body {
            display: flex;
            justify-content: space-between;
            align-items: center;
            height: 100vh;
            margin: 0;
            background-color: #f0f0f0;
        }
        #gameArea {
            position: relative;
            width: 600px;
            height: 400px;
            border: 2px solid #000;
            overflow: hidden;
            background-color: #fff;
        }
        .ai {
            position: absolute;
            width: 20px;
            height: 20px;
            background-color: blue;
        }
        .ball {
            position: absolute;
            width: 20px;
            height: 20px;
            background-color: green;
            border-radius: 50%;
        }
        #log {
            width: 200px;
            height: 400px;
            border: 1px solid #000;
            background-color: #fff;
            overflow-y: auto;
            padding: 10px;
        }
    </style>
</head>
<body>

<div id="gameArea">
    <div class="ai" id="ai"></div>
    <div class="ball" id="ball"></div>
</div>
<div id="log"></div>

<script src="https://cdn.jsdelivr.net/npm/@pytorch/pytorch@latest/dist/pytorch.min.js"></script>
<script>
    const ai = document.getElementById('ai');
    const ball = document.getElementById('ball');
    const log = document.getElementById('log');
    const gameArea = document.getElementById('gameArea');

    let aiPosition = { x: 0, y: 0 };
    let ballPosition = { x: Math.random() * (gameArea.clientWidth - 20), y: Math.random() * (gameArea.clientHeight - 20) };
    let generation = 0;

    function updatePositions() {
        ai.style.left = aiPosition.x + 'px';
        ai.style.top = aiPosition.y + 'px';
        ball.style.left = ballPosition.x + 'px';
        ball.style.top = ballPosition.y + 'px';
    }

    function moveAI() {
        // Simple AI movement logic (to be replaced with JS-PyTorch logic)
        if (aiPosition.x < ballPosition.x) aiPosition.x += 2;
        if (aiPosition.x > ballPosition.x) aiPosition.x -= 2;
        if (aiPosition.y < ballPosition.y) aiPosition.y += 2;
        if (aiPosition.y > ballPosition.y) aiPosition.y -= 2;

        // Check for wall collision
        if (aiPosition.x < 0 || aiPosition.x > gameArea.clientWidth - 20 || 
            aiPosition.y < 0 || aiPosition.y > gameArea.clientHeight - 20) {
            log.innerHTML += `Поколение ${generation}: Столкновение со стеной!<br>`;
            generation++;
            resetAI();
        }

        // Check for ball collection
        if (Math.abs(aiPosition.x - ballPosition.x) < 20 && Math.abs(aiPosition.y - ballPosition.y) < 20) {
            log.innerHTML += `Поколение ${generation}: Мяч собран!<br>`;
            resetBall();
        }

        updatePositions();
    }

    function resetAI() {
        aiPosition = { x: 0, y: 0 }; // Reset AI position
    }

    function resetBall() {
        ballPosition = { x: Math.random() * (gameArea.clientWidth - 20), y: Math.random() * (gameArea.clientHeight - 20) };
    }

    setInterval(moveAI, 100); // Move AI every 100ms
    updatePositions();
</script>

</body>
</html>
Explanation:
HTML Structure: The game area is defined with a blue square (AI) and a green circle (ball). The log area on the left displays the AI's actions.
CSS Styles: Basic styles are applied to position the elements and give them colors.
JavaScript Logic:
The AI moves towards the ball using simple logic.
If the AI hits the wall, it logs the event and resets its position.
If the AI collects the ball, it logs that event and resets the ball's position.
Next Steps:
Integrate JS-PyTorch for more advanced AI behavior.
Implement a learning algorithm that allows the AI to adapt based on previous generations.
Enhance the game mechanics and visuals as needed.
Feel free to modify and expand upon this code to suit your needs! If you have any questions or need further assistance, just let me know!

У меня есть такой код.
Сделай мне игру на HTML, где будет синий квадратик (ИИ) и предмет который будет искать ИИ - зелёный мячик. Необходимо чтобы с каждым новым поколением ИИ обучалось, поколения меняется, как только ИИ коснётся стенки. Прошлые результаты куда-то записывает, где находятся стенки и старается к ним не приближаться зная, что там нет мячика. Результаты логов сохраняет себе в память и выводит мне на экран слева. Необходимо использовать библиотеку JS-PyTorch это библиотека на JavaScript, похожая на PyTorch, с поддержкой графического ускорения (GPU). Синтаксис библиотеки близок к синтаксису PyTorch.

Прочитай так же это:

Введение
Меня всегда интересовало программирование шахматных движков и все различные алгоритмы поиска, используемые в шахматных движках, но у меня никогда не было простого примера, на который я мог бы посмотреть и понять основные идеи. Там всегда были тысячи строк с хитростями, оптимизациями и умными приёмами, которые мне приходилось изучать, чтобы хотя бы начать понимать общую структуру таких программ. Затем я прошёл вводный курс по искусственному интеллекту в школе, где мне пришлось выполнить несколько интригующих проектов по искусственному интеллекту с использованием хорошо известного фреймворка AIMA (на котором в общих чертах основан исходный код этого проекта), и в конце концов я понял, насколько всё просто, и туман недопонимания рассеялся. Одним из проектов, которые мне было поручено выполнить в этом классе, была реализация игры «Тигры и козы» — не такой известной индийской настольной игры, как шахматы, но с очень похожей и гораздо более простой структурой и набором правил. Немного подумав, я решил реализовать игру на JavaScript и HTML, чтобы она была простой и независимой от платформы. Цель этой статьи — предоставить кодовую базу для понимания и сравнения различных алгоритмов состязательного поиска, таких как Minimax и Alpha-Beta, а также для понимания общей структуры таких программ ИИ с использованием минимального объёма знаний о конкретной платформе или фреймворке.

Предыстория
«Тигры и козы» — индийская настольная игра для двух игроков, в которую играют на специальной доске, изображённой ниже:

Изображение 1

Один игрок должен загнать коз во двор, по одной козе за раз, и не может перемещать коз, пока они все не окажутся во дворе. Другой игрок, играющий за тигров, может ловить коз, перепрыгивая через них. Тигры могут перемещаться только на один квадрат в каждом направлении (как и козы) и могут перепрыгивать только через один квадрат. Их цель — поймать всех коз. Цель коз — окружить тигров, чтобы они больше не могли двигаться. Я также добавил правило, согласно которому, если определенная позиция выпадает более двух раз, игра заканчивается вничью; чтобы избежать повторений. Чтобы перемещать фигуры, пользователь должен щелкнуть по фигуре, а затем по целевому квадрату. Я рекомендую загрузить исходный код и запустить игру несколько раз, чтобы разобраться в игре, прежде чем переходить к следующему разделу.

Использование кода
В основе этих типов состязательных игр лежит объект «Состояние игры» — сущность, которая представляет текущее состояние игры, то есть расположение фигур или то, чья очередь ходить. Я представил текущее состояние игры с помощью класса GameState() в файле TigersAndGoats.js, как показано ниже:

JavaScript
функция GameState() {
это.SideToPlay = 1; //0 == Тигры, 1 == Козы то же, что и в AgentIndex
this.Внешние размеры = 15;
//массив из 23 элементов, соответствующих 23 клеткам доски.
// Каждая клетка либо пуста, либо на ней изображены коза или тигр.
это.CurrentPosition = ['T', 'E', 'E', 'T', 'T', 'E', 'E', 'E',
'E', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'E'];
это.Хэш = 0; // Компактное представление состояния игры. Хэш - это 51-битное двоичное число.
это.Результат = -1; // 0 == Победа "Тигров", 1 == Победа "Козлов", 2 == Ничья, -1 == безрезультатно
}
Возможно, это не самое эффективное представление, но оно самое простое.

При первой загрузке игры (функция window.onload() ) создаётся новый объект GameState и сохраняется как текущее состояние игры. Затем вызывается функция UpdateUserInterface() в файле UIUtility.js, которая проверяет текущее состояние игры и соответствующим образом обновляет игровое поле и элементы пользовательского интерфейса.

Как только мы представим текущее состояние игры, мы сможем получить список доступных действий или ходов, которые игрок может совершить в этом состоянии.

JavaScript
var state = новое игровое состояние();
var actions = state.getLegalActions();
Всякий раз, когда пользователь делает ход, вызывается ProcessUserInput() функция, которая определяет, входит ли действие пользователя в список допустимых действий, и если да, то выполняет ход, генерируя следующее состояние и обновляя пользовательский интерфейс.

var newState = oldState.generateSuccessor(действие, история);
Но когда наступит очередь компьютера двигаться, будет вызвана функция computerPlay(). Она порождает новый веб-воркер, который в JavaScript является эквивалентом потока, и затем воркер запускает agent.getAction(), которая выполняет поиск и продолжает возвращать наилучшее действие по мере продвижения вглубь дерева поиска.

Изображение 2

Алгоритмы
Я реализовал в основном 5 различных алгоритмов поиска: минимакс, экспоненциальный минимакс, альфа-бета, скаут и MTD(f)[2]. Все эти алгоритмы работают в рамках итеративного углубления. Например, вот часть getAction() функции агента минимакс:

JavaScript
var глубина = 1;
var результат;
while (true) {
результат = минимакс(состояние игры, глубина, this.Индекс);
//результат = оценка, действие, глубина, открытые узлы
postMessage([результат[0], результат[1], глубина, открытые узлы]);
глубина++;
}
О поиске в нулевом окне
Алгоритм альфа-бета-отсечения повышает эффективность по сравнению с минимаксом за счёт поиска только тех узлов в дереве поиска, которые могут изменить итоговый результат в верхней части дерева. Другими словами, отсекаются те ветви, которые не влияют на результат поиска. Для этого каждому узлу в дереве поиска рекурсивно назначается окно поиска (которое сначала равно [-Inf, +Inf]). Сначала самый верхний узел максимизатора в верхней части дерева поиска сообщает своим дочерним узлам минимизатора, что он ищет значение между -Inf и +Inf. Затем по мере поиска некоторые значения будут подниматься по дереву поиска, и максимизатору в верхней части будет присвоен временный максимум. После этого максимизатор будет интересоваться только значениями выше временного максимума, поэтому он задаст своим дочерним узлам нижнюю границу в окне стремления: [временный максимум, +Inf]. Дети будут знать, что максимизатор не заинтересован в значениях ниже нижней границы, поэтому всякий раз, когда они получают от своих детей значение ниже нижней границы, потому что они являются минимизаторами и знают, что всё, что они в конечном итоге вернут, будет ниже или равно этому значению, они немедленно возвращают его, чтобы завершить поиск по этой ветке дерева (обрезать ветку и быть проигнорированными родительским максимизатором!). То же самое касается минимизаторов и того, как они назначают верхние границы для своих детей-максимизаторов. По мере того, как алгоритм углубляется в дерево поиска, окно поиска становится всё меньше, и в результате происходит больше отсечений, и чем больше мы отсекаем, тем меньше узлов мы посещаем и тем быстрее работает наш алгоритм поиска.

Алгоритм «Скаут» работает даже быстрее, чем «Альфа-бета», выполняя поиск с нулевым или пустым окном для большинства узлов. Поиск с нулевым окном похож на проверку. Допустим, максимизатор в верхней части дерева хочет узнать, может ли результат поиска быть выше текущего временного максимума. Если нет, то нет смысла продолжать поиск. В этом случае максимизатор должен завершить поиск, немедленно вернуть свой временный максимум и сэкономить немного времени! Этот тест выполняется путём установки ширины окна поиска в ноль. Например, если текущий временный максимум максимизатора равен 40, он может установить окно поиска в [40, 40] и сообщить своим дочерним элементам, что его не интересует значение ниже 40, поэтому он немедленно вернёт его, а если есть значение выше 40, он немедленно вернёт его, потому что ему всё равно, какое это значение, он просто хочет знать, существует ли оно!

Алгоритм «Скаут» выполняет полный альфа-бета-поиск только для первого ребёнка, а затем тестирует других детей, чтобы понять, могут ли они справиться лучше! Поскольку эти тесты выполняются с окнами нулевой ширины, отсечений будет больше, и они выполняются намного быстрее, чем обычный альфа-бета-поиск. В результате, если порядок ходов правильный, то есть первое предположение алгоритма «Скаут» является лучшим предположением, «Скаут» будет работать намного быстрее, чем альфа-бета-поиск.

Один из способов получить хороший порядок ходов в рамках итеративного углубления — сохранять результаты предыдущих поисков с меньшей глубиной в таблице транспонирования, а затем, когда мы доходим до одного и того же состояния игры в дереве поиска, обращаться к таблице транспонирования и сначала рассматривать ранее известные лучшие ходы.

MTD(f) работает даже быстрее, чем Scout, угадывая счёт, а затем выполняя поиск в нулевом окне с самого начала в верхней части дерева поиска! По сути, он делает предположение, затем выполняет проверку, чтобы убедиться, что предположение было верным, а затем соответствующим образом корректирует предположение. В моих тестах MTD(f) в начальной позиции работал хуже, чем Scout с памятью, но в среднем при большом наборе тестовых позиций MTD(f) должен быть примерно на 10% быстрее, чем Scout[3].

Некоторые результаты
Чтобы сравнить производительность, я запустил все алгоритмы в начальной позиции на своём ноутбуке Core i3 с тактовой частотой 2,5 ГГц и ограничением глубины в 6. Вот результаты:

Ожидаемый максимум Минимаксный Альфа-Бета
Альфа-Бета

с памятью

Разведчик

без памяти

Разведчик

с памятью

MTD(f)
Достигнутая Максимальная Глубина 6 6 6 6 6 6 6
Время (мс) 52,219 51,301 1,585 577 1,015 559 648
Общее Количество Расширенных узлов 3,769,060 3,769,060 111,450 27,842 68,421 24,253 31,204
Посещенные Листья 2,997,798 2,997,798 58,835 12,202 41,177 10,681 12,874
Оценка 52.3 49 49 49 49 49 49
Экшен -1,-1,1 -1,-1,7 -1,-1,7 -1,-1,7 -1,-1,7 -1,-1,7 -1,-1,7

Достопримечательности
Хэш - таблица

Хэш-значение каждого нового состояния игры генерируется при вызове функции gameState.generateSuccessor и сохраняется в этом новом состоянии игры:

JavaScript
состояние.Хэш = 0;
для (var i = 0; i < состояние.currentPosition.длина; i++) {
если (состояние.currentPosition[i] == 'T')
состояние.Хэш += 2;
else if (состояние.Текущее положение[i] == 'G')
государство.Hash += 1;
state.Hash *= 4;
}
state.Hash *= 4;
state.Hash += state.OutsideGoats;
state.Hash *= 2;
state.Hash += state.SideToPlay;
Этот алгоритм генерирует идеальный хэш, исключающий возможность коллизий хэшей. Поэтому хэш-значение можно использовать, например, чтобы проверить, встречались ли два игровых состояния ранее в массиве истории.

Ещё одно применение хэша — в таблице транспонирования. На каждой итерации этих алгоритмов поиска с использованием памяти алгоритм сохраняет результат поиска в хэш-таблице.

JavaScript
if (TTresult == null || (TTresult != null && TTresult[2] < глубина))
Таблица транспозиции.вставить(состояние.Хэш, [действие, оценка, глубина, a, b]);
Затем в начале каждой итерации алгоритм проверяет, не выполнялся ли ранее аналогичный поиск с большей глубиной, и если да, то возвращает результат из таблицы транспонирования для экономии времени. Если нет, то он всё равно может использовать эту информацию для упорядочивания ходов.

C#
Сжиматься ▲
var TTresult = transpositionTable.find(state.Hash);
if (TTresult != null) {
if (TTresult[1] >= глубина) {
if (TTresult[2] == TTresult[3]) {
history.pop();
return [TTresult[2], TTresult[0]];
}
if (TTresult[2] > b) {
history.pop();
return [TTresult[2], TTresult[0]];
}
if (TTresult[3] < a) {
history.pop();
return [TTresult[3], TTresult[0]];
}
a = Math.max(a, TTresult[2]);
b = Math.min(b, TTresult[3]);
}
for (var i = 0; i < actions.длина; i++) {
if (действия[i].сравнить(TTresult[0])) {
действия.сращивание(i, 1);
действия.сращивание (0, 0, TTresult[0]);
перерыв;
}
}
}
Теперь, после нескольких секунд поиска, в хэш-таблице будут миллионы элементов. Если бы хэш-таблица была просто списком элементов и каждый раз, когда мы хотели бы найти элемент в хэш-таблице, нам приходилось бы просматривать миллионы элементов, то производительность алгоритмов поиска с использованием памяти существенно снизилась бы. Поэтому операции вставки и поиска в хэш-таблице должны выполняться быстрее, чем O(n). Для этого я реализовал пользовательскую хэш-таблицу с операциями вставки и поиска, выполняемыми за постоянное время (O(1)). В частности, на практике, независимо от количества элементов в хэш-таблице, операции вставки и поиска (нахождения) займут всего 7 шагов.

Поскольку хэш-ключ всегда представляет собой 51-битное двоичное число, мы можем создать дерево следующим образом:

Изображение 3

C#
Хэш-дерево.прототип.find = функция(ключ) {
поиск по дереву функций (глубина, ключ, узел) {
var nextKey = ключ & 0xff;
если (глубина == 1 && узел.Узлы[ключ] != null) {
возвращает узел.Узлы[ключ].Данные;
}
else if (узел.Узлы[следующая клавиша] != null) {
key = key - (key & 0xff);
return treeSearch(глубина - 1, ключ / 256, node.Nodes[следующий_ключ]);
}
return null;
}
return treeSearch(7, ключ, это);
}
Таким образом, время выполнения операций вставки и поиска будет зависеть только от длины хеш-ключа, которая является постоянной (51 бит).

Для запуска программы
Если вы используете Google Chrome, вам следует запускать его с флагом --allow-file-access-from-files. Это связано с ограничениями безопасности Chrome

И это:

This is a quick and easy tutorial for making a simple AI game in HTML5: The famous Ping Pong Game. I made this game in a course in the university, and I would like to share with you my simplified code.

Let’s walk through game.js. You can also download and play here: pingpong.

1 – Request Animation Frame
window.requestAnimFrame = (function(callback) {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback) { window.setTimeout(callback, 1000 / 60); };
});
All credits go to Paul Irish. This function requests a new animation frame each time interval according to the browser (cross-browser). I recommend that you check his website and read the explanation.
In short, without this function the game will animate slowly and poorly.
More info at: Mozilla Developer Network.

2 – Creating the canvas
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.width = 550;
canvas.height = 400;
document.body.appendChild(canvas);
The first thing we need to do is create a canvas element. This can be done either in HTML or in JavaScript. Here I did it in JavaScript. When we have the element we get a reference to its context, which we use to issue drawing commands. Then we set its dimensions, and add it to document so it’ll appear in the page. more on canvas

3 – Defining the graphics
// Background image
var bgReady = false;
var bgImage = new Image();
bgImage.onload = function () {
bgReady = true;
};
bgImage.src = "images/background.png";
Every game needs some graphics, so let’s add up some images. This is done here in the most simple way, that’s why it’s just an Image and not a class.

We do this for all of the graphics, we need: background, paddles and ball.

bgReady is used so we can draw the image when it’s safe, because drawing it before it’s fully loaded will throw an error. (the same goes to paddleReady and ballReady).

4 – Game Objects
var player_paddle = {
speed: 5, //movement in pixels per second
score: 0,
x: 480,
y: 100
}

var ai_paddle = {
speed: 2, //this is the AI trick
score: 0,
x: 10,
y: 100
}

var ball = {
x: canvas.width / 2,
y: canvas.height / 2,
speed_X: 3,
speed_Y: 3
}
Now we define some variables we’ll need to use later.
player_paddleis the paddle that the user is going to control, it has speed which is how fast it’ll move in pixels per second, score which is the score of the player and x and y that are the initial position of the paddle.
ai_paddle is the same thing used for the computer.
Lastly, ball has x and y like before with the addition of speed on both the x-axis and the y-axis, if we don’t do this the ball won’t move diagonally.

The Trick
As you can notice, the speed of the ai_paddle is less than the speed of the ball which itself is less than the speed of the player_paddle. This is the AI Trick. The computer knows the exact position of the ball so it can hit it back. But if this is so, the user won’t win. And what kind of game does not allow winning? That is why the speed of the computer’s paddle must be lower than the one of the ball and of the player_paddle, so it may seem that the computer can’t hit the ball but the truth is that the computer cannot follow up with the ball.

5 – Player Input
// Handle keyboard controls
var keysDown = {};

addEventListener("keydown", function (e) {
keysDown[e.keyCode] = true;
}, false);

addEventListener("keyup", function (e) {
delete keysDown[e.keyCode];
}, false);
Here we are storing the user input in a variable keysDown for later use in the game. keysDown stores any event’s keyCode (the keys, here it’s up and down). If a key code is in the object, the user is currently pressing that key.

6 – New Round
// Reset the game when any of the two paddles score
var reset = function () {
ball.x = canvas.width / 2;
ball.y = canvas.height / 2;
}
The reset function is called to begin a new round (serve). It places the ball in the center of the screen.

7 – Update Game
var update = function (modifier) {
This is the update function and is called every single interval execution. In this function we are moving the objects.

The modifier argument is a time-based number based on 1. Because every object is moving by pixels per second, if one second has passed, the player_paddle‘s speed will be multiplied by 1 which means that it will have moved 5 pixels in that second. The same goes to the ai_paddle and the ball. This function gets called so rapidly that the modifier value will typically be very low, but using this pattern will ensure that the objects will move the same speed no matter how fast (or slowly) the script is running. Let’s check the movement of the objects:

//ball movement -----------------------
ball.x = ball.x + ball.speed_X;
ball.y = ball.y + ball.speed_Y;

if (ball.x >= canvas.width) { //ai_paddle scored
ai_paddle.score++;
reset()
}

if (ball.x <= 0) { //player_paddle scored player_paddle.score++; reset() } if (ball.y >= canvas.height - ballImage.height
|| ball.y <= 0) {
ball.speed_Y = -1 * ball.speed_Y;
}
The ball is always moving according to its speed which is programmed as speed_X and speed_X. (So the ball could also move diagonally). If the ball ball hits the canvas’s right edge the computer wins one point and reciprocally if it hits the left edge the player wins one point, and in both cases the round is reset.
Now, we want to limit the ball inside the canvas that is why if the ball hit the upper or lower edge of the canvas it will move in the opposite side according to the y-axis. ball.speed_Y = -1 * ball.speed_Y; . We put canvas.height - ballImage.height because we are checking when the bottom of the ball is hitting the canvas not the top (check Note below).

//player_paddle movement -------------
if (38 in keysDown) { // Player holding UP
if (player_paddle.y > 0)
player_paddle.y -= player_paddle.speed;
}

if (40 in keysDown) { // Player holding DOWN
if (player_paddle.y < canvas.height - paddleImage.height)
player_paddle.y += player_paddle.speed;
}
The player is moving according to the user’s input, so if the user hit UP (which has the keycode of 38) the player_paddle moves up.

//ai_paddle movement -----------------
//following the ball
if (ai_paddle.y < ball.y) {
if (ai_paddle.y < canvas.height - paddleImage.height) ai_paddle.y += ai_paddle.speed; } else { if (ai_paddle.y > 0)
ai_paddle.y -= ai_paddle.speed;
}
The computer is always following the ball, so its x and y are changing according to the ball’s x and y.

In both cases (player_paddle and ai_paddle) we are limiting the paddle inside the canvas. The paddles can’t go up more than the beginning of the canvas (player_paddle.y > 0), and can’t go down more than the end of the canvas. But if we put player_paddle.y < canvas.heigh whole paddle will pass the canvas and stop when the exact top of it hit the canvas’ height. So we write player_paddle.y < canvas.height - paddleImage.height, to get the height of the whole object, in this case it is an image.

Note:
untitled
Collision
// collision
if (
ball.x < player_paddle.x + paddleImage.width && ball.x + ballImage.width > player_paddle.x &&
ball.y < player_paddle.y + paddleImage.height && ballImage.height + ball.y > player_paddle.y
||
ball.x < ai_paddle.x + paddleImage.width && ball.x + ballImage.width > ai_paddle.x &&
ball.y < ai_paddle.y + paddleImage.height && ballImage.height + ball.y > ai_paddle.y) {
ball.speed_X = -1 * ball.speed_X;
}
};
This part is where the ball hit any of the two paddles. If it does the ball will move in the opposite way according to the x-axis. To understand more collision of two 2D objects I recommend checking this link.

8 – Draw Everything
var draw = function () {
if (bgReady) {
ctx.drawImage(bgImage, 0, 0);
}

if (paddleReady) {
ctx.drawImage(paddleImage, player_paddle.x, player_paddle.y);
ctx.drawImage(paddleImage, ai_paddle.x, ai_paddle.y);
}

if (ballReady) {
ctx.drawImage(ballImage, ball.x, ball.y);
}

// Score
ctx.fillStyle = "rgb(250, 250, 250)";
ctx.font = "30px AR HERMANN";
ctx.textAlign = "left";
ctx.textBaseline = "top";
ctx.fillText("AI: " + ai_paddle.score + "\t\tPlayer: " + player_paddle.score,
20,
10);
};
Here we are drawing everything to the screen. First we take the background image and draw it to the canvas. Repeat for the paddles and ball. Note that the order is important, as any image drawn to the canvas will draw over the pixels under it.

Next we change some properties on the context related to how to draw the font, and we make a call to fillText to display the score. And we are done drawing.

9 – The main game loop
var main = function () {
var now = Date.now();
var delta = now - then;

update(delta / 1000);
draw();

then = now;

// Request to do this again
requestAnimationFrame(main);
};
The main game loop is what controls the flow of the game. First we want to get the current timestamp so we can calculate how many milliseconds have passed since the last interval (the delta). We get the modifier to send to update by dividing by 1000 (the number of milliseconds in one second). Then we call draw and record the timestamp.

10 – Play Game
var then = Date.now();
reset();
main();
We are almost done! First we set our timestamp (with the variable then) . Then we call reset to start a new round, finally we call the main game loop.

So that’s it! I hope that my code was understandable and that you can benefit from this. Happy Coding!

Сделай мне  игру исходя из того, что ты прочитал