Компьютер уже выбирает ход из массива. Теперь добавим три кнопки игрока и правило определения победителя. Сначала заработает один раунд, затем счёт, матч до трёх побед и полный перезапуск. Проверим правила на всех девяти сочетаниях, чтобы не пропустить ошибку из-за случайного выбора ходов.
Попробуй сыграть матч и начать новый. Здесь мы шаг за шагом собираем игру с тремя кнопками и счётом.
Продолжаем игру из прошлого урока
Нужен урок о массивах и случайном выборе. Папка start этого комплекта содержит те же файлы, что его final: список moves, функция chooseComputerMove и кнопка выбора компьютера. Продолжай свою папку или возьми start; эксперимент с репликами оставь отдельно.
Распакуй ZIP целиком. У каждого этапа своя папка: index.html, style.css и game.js лежат рядом. Открывай HTML двойным щелчком; после правки сохраняй файл и обновляй браузер. Установка, аккаунт и сервер для этого примера не нужны. Инструкция в просмотрщике объясняет версии; solutions/ оставь до самостоятельной попытки.
Версии: step-01 — один раунд; step-02 — счёт без окончания; step-03 — матч до трёх побед; final — новая партия. Каждый этап можно запускать. Внимательно следи за файлом и местом вставки, чтобы не оставить старый обработчик удалённой кнопки.
Игрок выбирает ход
В index.html поменяй заголовок h1 и название вкладки title на «Камень, ножницы, бумага». Удали старый абзац с computer-move и кнопку choose-button. На их место добавь:
<div class="choices">
<button id="rock-button" type="button">Камень</button>
<button id="scissors-button" type="button">Ножницы</button>
<button id="paper-button" type="button">Бумага</button>
</div>
<div class="stats">
<p>Твой ход: <strong id="player-move">—</strong></p>
<p>Ход компьютера: <strong id="computer-move">—</strong></p>
</div>
<p id="round-result" class="result" role="status">Выбери ход.</p>
Здесь снова только обычные кнопки. Их разные id позволяют подключить три понятных действия. computer-move остался, но его элемент заменён вместе с разметкой — на странице не должно быть двух одинаковых id. Пустое состояние показываем знаком «—», пока игрок ещё не ходил.
В game.js сохрани массив moves и функцию chooseComputerMove из предыдущего занятия. Удали старые render, handleComputerChoice, chooseButton, его регистрацию и последний вызов render();. Вместо прежнего состояния и поиска элементов добавь сразу после массива:
let playerMove = '—';
let computerMove = '—';
let roundResult = '';
const playerMoveElement = document.getElementById('player-move');
const computerMoveElement = document.getElementById('computer-move');
const resultElement = document.getElementById('round-result');
const rockButton = document.getElementById('rock-button');
const scissorsButton = document.getElementById('scissors-button');
const paperButton = document.getElementById('paper-button');
roundResult пока пустая строка. Дальше договоримся хранить в ней короткий код результата: draw — ничья, player — победил игрок, computer — победил компьютер. Код правила не будет зависеть от текста сообщения на экране.
Переводим словесное правило в функцию
До написания кода сформулируй правило. Одинаковые ходы дают ничью. Игрок выигрывает в трёх случаях: камень против ножниц, ножницы против бумаги, бумага против камня. Для остальных допустимых сочетаний выигрывает компьютер.
После chooseComputerMove добавь функцию:
function determineResult(playerChoice, computerChoice) {
if (playerChoice === computerChoice) {
return 'draw';
}
if (playerChoice === 'камень' && computerChoice === 'ножницы') {
return 'player';
}
if (playerChoice === 'ножницы' && computerChoice === 'бумага') {
return 'player';
}
if (playerChoice === 'бумага' && computerChoice === 'камень') {
return 'player';
}
return 'computer';
}
Два параметра — два входных хода. Первый if обрабатывает сразу три ничьи и завершает вызов через return. Затем каждое сочетание победы описано отдельным понятным условием; && требует совпадения обоих ходов. Если ни одно не подошло, возвращаем computer.
В каждый аргумент передаём одно из трёх слов из moves, без изменений в написании. Она не проверяет произвольный текст пользователя: его здесь нет, потому что ход игрока задают наши кнопки, а ход компьютера берётся из массива. При добавлении четвёртого жеста одних данных уже не хватит — придётся определить новые правила.
Все девять сочетаний
Не пытайся проверить правило только случайными партиями. Из трёх вариантов каждого участника получается девять пар. В таблице результат указан с точки зрения игрока:
| Игрок ↓ / компьютер → | Камень | Ножницы | Бумага |
|---|---|---|---|
| Камень | draw | player | computer |
| Ножницы | computer | draw | player |
| Бумага | player | computer | draw |
Сохрани game.js, обнови страницу и выполни эти вызовы в Console. Они проверяют правило отдельно от кнопок, не меняя состояние игры:
console.log(determineResult('камень', 'камень')); // draw
console.log(determineResult('камень', 'ножницы')); // player
console.log(determineResult('камень', 'бумага')); // computer
console.log(determineResult('ножницы', 'камень')); // computer
console.log(determineResult('ножницы', 'ножницы')); // draw
console.log(determineResult('ножницы', 'бумага')); // player
console.log(determineResult('бумага', 'камень')); // player
console.log(determineResult('бумага', 'ножницы')); // computer
console.log(determineResult('бумага', 'бумага')); // draw
Сверь каждую строку с таблицей. Если не совпала одна пара, сначала проверь именно её условие и порядок аргументов. Не исправляй случайный выбор ради ошибки сравнения. Здесь всего девять сочетаний, поэтому каждое можно проверить отдельным вызовом.
Проводим и показываем один раунд
После determineResult добавь отображение и игровое действие:
function render() {
playerMoveElement.textContent = playerMove;
computerMoveElement.textContent = computerMove;
if (roundResult === '') {
resultElement.textContent = 'Выбери ход.';
} else if (roundResult === 'draw') {
resultElement.textContent = 'Ничья в раунде.';
} else if (roundResult === 'player') {
resultElement.textContent = 'Ты выиграл раунд.';
} else {
resultElement.textContent = 'Компьютер выиграл раунд.';
}
}
function playRound(playerChoice) {
playerMove = playerChoice;
computerMove = chooseComputerMove();
roundResult = determineResult(playerMove, computerMove);
render();
}
Вызов playRound сначала запоминает ход игрока, затем один раз выбирает компьютерный. Только после этого определяет результат по этим двум значениям и вызывает render. Функция render лишь переводит код результата в сообщение; повторная отрисовка не должна проводить новый раунд.
Внизу файла добавь именованные обработчики и однократную регистрацию:
function handleRock() {
playRound('камень');
}
function handleScissors() {
playRound('ножницы');
}
function handlePaper() {
playRound('бумага');
}
rockButton.addEventListener('click', handleRock);
scissorsButton.addEventListener('click', handleScissors);
paperButton.addEventListener('click', handlePaper);
render();
Обработчик передаётся браузеру без скобок, а уже внутри него вызывается playRound с конкретным аргументом. Так обработчик кнопки передаёт выбранный ход в функцию раунда. Повторяющиеся три короткие регистрации сейчас помогают видеть связь каждой кнопки с действием; обобщать их заранее не требуется.
Проверка step-01: при загрузке «Выбери ход», после нажатия видны оба хода и соответствующее сообщение. Проверь каждую кнопку и одну активацию через Tab/Enter. На следующем нажатии начинается новый раунд. Счёта и матча пока нет.
Начисляем очко победителю
В index.html после сообщения round-result добавь:
<p>Счёт: ты <strong id="player-score">0</strong> : <strong id="computer-score">0</strong> компьютер</p>
После остальных переменных состояния добавь два числа, а рядом с поиском элементов — две ссылки:
let playerScore = 0;
let computerScore = 0;
const playerScoreElement = document.getElementById('player-score');
const computerScoreElement = document.getElementById('computer-score');
Внутри playRound после присваивания roundResult и перед render добавь:
if (roundResult === 'player') {
playerScore = playerScore + 1;
} else if (roundResult === 'computer') {
computerScore = computerScore + 1;
}
У ничьей нет ветки начисления, поэтому оба числа остаются прежними. Не начисляй очко в render: тогда каждый вывод или изменение текста превращались бы в ещё одну победу. В конце render, перед закрывающей скобкой, добавь:
playerScoreElement.textContent = playerScore;
computerScoreElement.textContent = computerScore;
Это step-02: счёт накапливается, но верхней границы пока нет. Проверь хотя бы одну ничью: после неё счёт не растёт. Чтобы отделить проблему правила от проблемы отображения, сравни playerScore и computerScore в Console с надписями. Перезагрузка страницы пока обнуляет их.
Матч заканчивается на трёх победах
После счёта в HTML добавь <p id="match-status">Матч до 3 побед.</p>. В JS добавь к состоянию и ссылкам:
let matchOver = false;
const targetScore = 3;
const matchStatusElement = document.getElementById('match-status');
В самое начало playRound поставь защиту. После начисления очков, перед render, проверь цель:
// В начале playRound:
if (matchOver) {
return;
}
// После начисления очка, перед render:
if (playerScore >= targetScore || computerScore >= targetScore) {
matchOver = true;
}
Это две разные вставки в одну функцию. Ничья может увеличить число сыгранных раундов, но не приближает счёт к трём. Поэтому матч не обязательно длится три раунда. Проверка через || завершает его при достижении цели любым участником. Защита стоит до случайного выбора и всех изменений: лишнее действие после окончания не должно даже менять показанные ходы.
В конец render добавь:
if (matchOver) {
if (playerScore >= targetScore) {
matchStatusElement.textContent = 'Ты выиграл матч!';
} else {
matchStatusElement.textContent = 'Компьютер выиграл матч.';
}
} else {
matchStatusElement.textContent = 'Матч до ' + targetScore + ' побед.';
}
rockButton.disabled = matchOver;
scissorsButton.disabled = matchOver;
paperButton.disabled = matchOver;
Вложенный if проверяется только внутри завершённого матча: сначала выясняем, закончилась ли игра, затем кто достиг цели. Это те же условия и фигурные скобки, только одна проверка расположена внутри другой. Проверки выполняются в том порядке, в котором мы их вложили.
Проверка step-03: при 2 очках матч продолжается, при 3 — заканчивается. Сброс пока через обновление страницы. После результата попробуй playRound('камень') в Console: ни счёт, ни последние ходы не должны измениться. Кнопки disabled подсказывают запрет, а ранний return обеспечивает его.
Добавляем кнопку нового матча
Добавь после match-status в HTML:
<div class="actions">
<button id="reset-button" class="secondary" type="button">Новый матч</button>
</div>
Рядом с другими ссылками в JS найди reset-button, ниже игровых обработчиков добавь сброс, а его регистрацию поставь вместе с остальными перед последним render:
const resetButton = document.getElementById('reset-button');
function handleReset() {
playerMove = '—';
computerMove = '—';
roundResult = '';
playerScore = 0;
computerScore = 0;
matchOver = false;
render();
}
resetButton.addEventListener('click', handleReset);
Это final. Сбрасываем не только цифры: старый код результата и последние ходы тоже относятся к состоянию. Флаг matchOver снова false, поэтому render разблокирует три кнопки. Массив moves и цель targetScore не менялись в течение матча, их восстанавливать не нужно. Обработчики при сбросе повторно не регистрируем.
| Сценарий | Ожидаемый результат |
|---|---|
| Свежий старт | 0:0, ходы «—», кнопки доступны |
| Ничья | Счёт прежний, оба хода видны |
| Победный раунд при счёте 2 у участника | Счёт становится 3, матч завершён |
| Повторное действие после окончания | Данные не меняются |
| Новый матч после победы или посреди игры | Полное начальное состояние |
| Пять быстрых сбросов, затем один ход | Ровно один раунд; не пять начислений |
Самостоятельно: матч из пяти раундов
Сохрани базовый final, затем в отдельной копии измени условие окончания: матч длится ровно пять раундов, включая ничьи. Выигрывает тот, у кого больше очков. Если очки равны, весь матч заканчивается вничью. До пятого раунда игра продолжается даже при трёх победах подряд. Новый матч должен обнулить и счётчик раундов.
Теперь нужно отдельно считать раунды: победы и ничьи вместе. Сначала запиши, какие данные понадобятся и где они меняются. Предскажи результат на четвёртом и пятом раундах. Проверь, что шестой вызов не меняет состояние, а пять ничейных раундов завершают матч со счётом 0:0.
Подсказка 1: отдели раунд от победы
Два существующих счётчика считают победы. Потребуется ещё одно число для всех раундов. Увеличивай его один раз после получения результата, независимо от ничьей.
Подсказка 2: конец и победитель — разные проверки
Сравни число сыгранных раундов с пределом 5 для окончания. После окончания отдельно сравни playerScore и computerScore. Если ни один не больше другого, результат матча — ничья. Новый счётчик также добавь в handleReset.
Решение после попытки
Запустить вариант на пять раундов. Исходники ответа и разбор находятся в solutions скачанного архива. Сначала сравни своё правило завершения, затем сброс. Если воспользовался готовой функцией, повтори другой ограниченный вариант без неё и объясни каждое изменение.
Проверь готовую игру
Ты можешь прочитать функцию и объяснить правило своими словами, проверяешь все девять пар, различаешь результат раунда и матча, меняешь условие завершения и проверяешь границы. Покажи несколько новых матчей: сброс не оставляет старые ходы или очки. Сохрани рабочий результат и свою вариацию в Git, как в предыдущем модуле.
Для продолжения сохрани базовый final — матч до трёх побед. Расширенная игра в «Поиграть» остаётся отдельным дополнением.