Документация Engee
Notebook

Игра «Камень-ножницы-бумага»

В этом примере рассмотрен код веб-приложения для игры «Камень-ножницы-бумага» с графическим интерфейсом, реализованный при помощи HTML и JavaScript. Давайте разберем его по частям.

HTML-структура

Код начинается со стандартной HTML-разметки:

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Камень-ножницы-бумага</title>

Стили (CSS)

В разделе <style> определены несколько CSS-правил:

#reset-btn:hover {
	transform: scale(1.2);
	transition: transform 0.2s;
}
.choice-btn:hover:not(:disabled) {
	transform: scale(1.1);
	transition: transform 0.2s;
}
.choice-btn:disabled {
	opacity: 0.5;
	cursor: not-allowed;
}

Эти стили добавляют:

  • анимацию увеличения кнопки сброса при наведении;
  • анимацию увеличения кнопок выбора при наведении (если они не отключены);
  • стиль для отключенных кнопок выбора (полупрозрачные с курсором «не разрешено»).

Основная структура страницы

Тело документа содержит контейнер с игрой:

<div style="background: #f0f0f0; font-family: Arial, sans-serif; max-width: 400px; margin: 0 auto; padding: 20px; border-radius: 20px; position: relative;">

Внутри него несколько секций.

  1. Счетчик очков:
<div style="display: flex; justify-content: center; align-items: center; gap: 20px; margin-bottom: 10px;">
	<div style="background: white; padding: 15px; border-radius: 10px; text-align: center; font-size: 18px; flex-grow: 1;">
		<div style="display: flex; justify-content: space-around; align-items: center;">
			<div style="display: flex; align-items: center; gap: 5px;">
				<div style="font-weight: bold;">ВЫ:</div>
				<div id="player-score" style="font-size: 24px;">0️⃣</div>
			</div>
			<div style="font-weight: bold; font-size: 20px;">VS</div>
			<div style="display: flex; align-items: center; gap: 5px;">
				<div style="font-weight: bold;">Робот:</div>
				<div id="computer-score" style="font-size: 24px;">0️⃣</div>
			</div>
		</div>
		<div id="result-text"></div>
	</div>
	<button onclick="resetGame()" id="reset-btn" style="background: none; border: none; cursor: pointer; font-size: 24px; margin-left: 10px;">🔄</button>
</div>
  1. Область выбора игрока и компьютера:
<div style="display: flex; justify-content: space-between; margin-bottom: 20px; height: 100px; align-items: center;">
    <div style="text-align: center; width: 50%; position: relative;">
        <div style="position: relative; display: inline-block; vertical-align: middle; line-height: 0;">
            <div id="player-choice" style="font-size: 80px;">🤠</div>
            <div id="player-mini-choice" style="..."></div>
        </div>
    </div>
    <div style="text-align: center; width: 50%; position: relative;">
        <div style="position: relative; display: inline-block; vertical-align: middle; line-height: 0;">
            <div id="computer-choice" style="font-size: 80px;">🤖</div>
            <div id="computer-mini-choice" style="..."></div>
        </div>
    </div>
</div>
  1. Кнопки выбора:
<div style="text-align: center; margin-bottom: 15px;">
	<h3 style="margin-bottom: 10px; color: #444;">Ваш выбор:</h3>
	<div style="display: flex; justify-content: center; gap: 10px; margin-bottom: 15px;">
		<button onclick="makeChoice(0)" class="choice-btn" style="..."></button>
		<button onclick="makeChoice(1)" class="choice-btn" style="..."></button>
		<button onclick="makeChoice(2)" class="choice-btn" style="...">✌️</button>
	</div>
</div>

JavaScript-логика

Инициализация переменных

let playerScore = 0;
let computerScore = 0;
let gameActive = true;

const choices = ['✊', '✋', '✌️'];
const beats = {
	'✊': '✌️',
	'✋': '✊',
	'✌️': '✋'
};

Вспомогательные функции

  1. updateScoreEmoji обновляет счет с помощью эмодзи:
function updateScoreEmoji(scoreElement, score) {
	const emojiScores = ['0️⃣', '1️⃣', '2️⃣', '3️⃣'];
	scoreElement.textContent = emojiScores[score] || score;
}
  1. animateComputerChoice анимирует выбор компьютера:
function animateComputerChoice(finalChoice, callback) {
	let iterations = 0;
	const animationInterval = setInterval(() => {
		document.getElementById('computer-choice').textContent = choices[iterations % 3];
		iterations++;
		if (iterations > 10) {
			clearInterval(animationInterval);
			document.getElementById('computer-choice').textContent = '🤖';
			document.getElementById('computer-mini-choice').textContent = finalChoice;
			if (callback) callback();
		}
	}, 100);
}

Основные функции игры

  1. makeChoice – обработчик выбора игрока:
function makeChoice(choiceIdx) {
	if (!gameActive) {
		alert('Игра окончена. Нажмите 🔄 для новой.');
		return;
	}
	const playerEmoji = choices[choiceIdx];
	const computerEmoji = choices[Math.floor(Math.random() * 3)];

	// Обновляем интерфейс
	document.getElementById('player-mini-choice').textContent = playerEmoji;
	document.getElementById('player-choice').textContent = '🤠';
	document.querySelectorAll('.choice-btn').forEach(btn => btn.disabled = true);
	// Определяем победителя
	function getWinner(player, computer) {
		if (player === computer) return 'draw';
		return beats[player] === computer ? 'player' : 'computer';
	}
	const winner = getWinner(playerEmoji, computerEmoji);

	// Обновляем счет
	if (winner === 'player') {
		playerScore++;
	} else if (winner === 'computer') {
		computerScore++;
	}
	// Проверяем конец игры
	if (playerScore === 3 || computerScore === 3) {
		gameActive = false;
	}

	// Анимируем выбор компьютера и обновляем интерфейс
	animateComputerChoice(computerEmoji, () => {
		updateScoreEmoji(document.getElementById('player-score'), playerScore);
		updateScoreEmoji(document.getElementById('computer-score'), computerScore);

		// Показываем результат
		let resultText = '';
		if (!gameActive) {
			if (playerScore === 3) {
				resultText = '🏆 Победил игрок!';
			} else {
				resultText = '🤖 Победил робот!';
			}
		} else {
			if (winner === 'draw') {
				resultText = 'Ничья!';
			} else if (winner === 'player') {
				resultText = 'Вы выиграли этот раунд!';
			} else {
				resultText = 'Робот выиграл в этом раунде!';
			}
		}
		document.getElementById('result-text').textContent = resultText;

		// Включаем/выключаем кнопки в зависимости от состояния игры
		document.querySelectorAll('.choice-btn').forEach(btn => {
			btn.disabled = !gameActive;
		});
	});
}
  1. resetGame – сброс игры:
function resetGame() {
	playerScore = 0;
	computerScore = 0;
	gameActive = true;
	updateScoreEmoji(document.getElementById('player-score'), playerScore);
	updateScoreEmoji(document.getElementById('computer-score'), computerScore);
	document.getElementById('player-choice').textContent = '🤠';
	document.getElementById('computer-choice').textContent = '🤖';
	document.getElementById('player-mini-choice').textContent = '';
	document.getElementById('computer-mini-choice').textContent = '';
	document.getElementById('result-text').textContent = '';
	document.querySelectorAll('.choice-btn').forEach(btn => btn.disabled = false);
}

Этот код представляет собой законченное приложение с интерфейсом и всеми необходимыми функциями для игры «Камень-ножницы-бумага».

Теперь перейдём к подключению нашего приложения к Engee. Самый простой вариант – вызвать и отобразить HTML-файл напрямую в .ngscript.

In [ ]:
@time display(MIME("text/html"), read("$(@__DIR__)/GUI.html", String))
Камень-Ножницы-Бумага
ВЫ:
0️⃣
VS
Робот:
0️⃣
🤠
🤖

Ваш выбор:

  0.396500 seconds (51.99 k allocations: 3.394 MiB, 99.28% compilation time)

Либо мы можем поднять сервер при помощи GenieFramework, вызвав файл App.jl

using GenieFramework

route("/") do
    html(read(joinpath(@__DIR__, "GUI.html"), String))
end
In [ ]:
Pkg.add("GenieFramework")
using GenieFramework
In [ ]:
# Запускаем приложение и получаем URL
app_url = string(engee.genie.start(string(@__DIR__,"/App.jl")))
# Извлекаем URL с помощью регулярного выражения
url_match = match(r"'(https?://[^']+)'", app_url)
url = url_match[1]  # Получаем URL из первой группы захвата
Out[0]:
"https://engee.com/prod/user/demo54365636-yurev/genie/App/"

По результатам запуска сервера мы можем подключиться к серверу по URL и отобразить приложение в iframe, не покидая Engee.

iframe (Inline Frame) — это HTML-элемент, который позволяет встраивать другой HTML-документ или веб-страницу внутрь текущей страницы. Это создает «окно» внутри страницы, где может отображаться независимый контент.

html_content = """<iframe src="$url" width="750" height="500" style="border: none;"></iframe>"""

display(MIME("text/html"), html_content)

Вывод

Код игры «Камень-ножницы-бумага» представляет собой законченное веб-приложение. Анализируя способы вызова и их особенности, можно сделать следующие наблюдения.

  1. Прямой вывод HTML:

    • простая демонстрация без серверной части;
    • работает в локальном контексте Engee;
    • ограниченная интеграция с другими компонентами.
  2. Через GenieFramework:

    • запускает полноценный веб-сервер,
    • позволяет встраивать игру в iframe,
    • подходит для интеграции в более крупные проекты,
    • требует больше времени на запуск,
    • обеспечивает изоляцию контекста выполнения.