У тебя есть бриф небольшой игры. Теперь задача — самому превратить его правила в работающую партию. В этом практикуме нет последовательности строк, которую достаточно перепечатать: ты выбираешь данные, связываешь действие с правилом и проверяешь последствия.

Результат — запускаемый прототип: игрок понимает цель, делает действие, видит результат, доходит до конца и может начать заново. Прототип проверяет основную механику; для него достаточно текста, кнопок и простых фигур. Звук, сохранение и новая графика нужны только тогда, когда без них не работает твой бриф.

Что понадобится

Возьми свой документ из практикума о брифе. Под рукой должны быть правила, три этапа и список того, что не входит в первую версию. Можно обращаться к документации и к своей ранее решённой небольшой задаче. Это не проверка памяти на синтаксис; важно самому связать части программы и объяснить свои решения.

Если в брифе остались вопросы вроде «когда засчитывается доставка» или «достаточно ли суммы без обязательной карты», реши их до реализации. Иначе невозможно отличить ошибку программы от правила, с которым ты ещё не определился.

Исходники урока

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

Скачать ZIP

Проверь заготовку проекта

Скопируй starter/ в отдельную рабочую папку. Открой её index.html и нажми «Проверить JavaScript». Должно измениться сообщение. Во встроенном окне ниже происходит только эта проверка: готовой игровой логики в нём нет.

Открыть заготовку в отдельной вкладке.

В папке три обычных файла: разметка index.html, оформление style.css и game.js с техническим обработчиком. Атрибут defer даёт скрипту выполняться после разбора HTML. querySelector находит элемент по ID, а addEventListener подключает действие к событию. Эти команды ты уже использовал с кнопками кликера.

const statusText = document.querySelector("#status");
const checkButton = document.querySelector("#check-button");

function handleCheck() {
  statusText.textContent = "JavaScript подключён. Теперь можно собирать своё правило.";
}

checkButton.addEventListener("click", handleCheck);

Это весь готовый JavaScript заготовки. Комментарии TODO в файле отмечают работу, которую предстоит сделать. После проверки техническую кнопку и её обработчик можно удалить. Дальше ты меняешь разметку под свой бриф: создаёшь карточки, добавляешь Canvas или кнопки сигнала. Готовое оформление можно использовать; воспроизводить его по памяти не требуется.

Заготовка открывается напрямую из папки: в ней нет модулей, загрузки данных и внешних ресурсов. Если добавляешь возможности, которым нужен HTTP, используй уже изученный локальный сервер. В любом случае распакуй ZIP целиком и сохраняй относительные пути между файлами.

Собери одно действие: данные → ввод → правило → отображение → конец

Открой work-plan.md. На каждом этапе запиши ожидаемый результат и проверь его, прежде чем расширять программу. Пока одна посылка не доставляется правильно, пять посылок не помогут найти ошибку. Пока одно нажатие карты срабатывает дважды, рано разбирать все исходы головоломки.

1. Опиши данные

Раздели постоянные настройки и состояние партии. Затем выпиши начальные значения и значения после одного допустимого действия. Для доставки это может быть пустой груз, а после подбора — один ID в грузе. Для карточек — нулевая сумма и пустой список выбранных карт, затем сумма первой карты и один выбранный ID. Для реакции — ожидание сигнала, затем разрешённый ответ.

Выбери только данные своего проекта. Массив нужен для набора предметов или карт; объект — чтобы держать связанные свойства. Не добавляй координаты карточной игре, если её элементы уже располагает HTML. Если берёшь объект-шаблон начального состояния, проверь, что изменяемые массивы новой партии не остаются общей ссылкой на массивы старой.

Проверка: покажи начальные данные в Console или интерфейсе. Назови, какое значение изменится при первом действии и какое обязано остаться прежним. До обработчиков это можно проверить вручную отдельным присваиванием, затем убрать временную проверку.

2. Обработай одно действие игрока

Выбери одно событие: нажатие карточки, движение персонажа или ответ на сигнал. Временно выведи запись из обработчика через console.log. Одно действие должно дать одну запись. Для удерживаемого движения проверь изменения состояния клавиши, а не добавляй новое событие на каждый кадр.

Обычная HTML-кнопка уже работает с мышью и клавиатурой. Для Canvas используй изученный ввод и не забудь очистку удержания при потере фокуса. Если переносишь несколько вспомогательных функций из своего старого проекта, перечисли их в журнале и убедись, что можешь объяснить каждую. Не переноси целую старую игру вместе с ненужными правилами.

Проверка: обработчик подключён один раз. Он не создаётся в render, при рестарте или внутри игрового цикла. Непонятное двойное действие нужно исправить сейчас.

3. Реализуй правило

Для одного действия выпиши цепочку: можно ли принять его → что меняется → что проверяется после изменения. Начни с запрета. Например, при полном грузе посылка не должна исчезнуть, а уже выбранная карта не должна второй раз увеличить сумму. Ранний return подходит для выхода до изменения данных.

После допустимого действия измени состояние один раз. Не прибавляй результат одновременно в обработчике и функции рисования. Если есть несколько обязательных условий, уточни, когда проверяются все сразу. Сумма 10 после двух карт не выигрывает игру, где нужны ровно три.

Проверка: прогони разрешённый и запрещённый случаи. Запиши значения до и после. Если получилась ошибка, оставь этот короткий сценарий для повторной проверки — он полезнее фразы «иногда не работает».

4. Покажи последствия

Теперь свяжи данные с видимым результатом. Игроку нужны цель, текущий прогресс и понятная причина отказа. «Несу: 2 из 2» объясняет заполненный груз; «Нужен ключ» объясняет неподходящий набор. Одного изменения цвета недостаточно, если из него нельзя понять правило.

Пусть отображение читает состояние. Для HTML обновляй подписи и доступность элементов, для Canvas очищай сцену и рисуй текущее положение. Повторный вызов render() с теми же данными не должен добавлять очки, выбирать карту или начинать новый таймер.

Проверка: сравни данные в Console и экран после одного действия. Если данные верны, а экран нет, ищи ошибку в отображении, не переписывай уже проверенное правило.

5. Добавь конец и полный рестарт

Реализуй условия окончания из брифа и запрети дальнейшие игровые действия после конца. Затем перечисли всё изменяемое: не только счёт, но и выбранные ID, груз, флаги, номер попытки и недоступные кнопки. Рестарт должен восстановить ту же ситуацию, что свежий запуск.

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

Проверка: дойди до конца, попробуй ещё одно действие, перезапусти и снова сыграй. Повтори три раза. Число кнопок и обработчиков не растёт, старый результат не возвращается. Сохрани рабочую версию в Git или отдельной папке и подпиши, что именно проверил.

Самостоятельная работа: собери партию по своему брифу

Всё основное задание — твоя реализация. Пройди три этапа из брифа, используя порядок выше внутри каждого. Начни с одного предмета, карты или попытки, затем расширь до оговорённого набора. В attempt-log.md записывай затруднение, свою гипотезу и наблюдение после проверки.

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

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

Если застрял: с чего начать поиск ошибки

Дерево целиком находится в diagnostic-tree.md. Начни с наблюдения и двигайся по одной ветке.

Что видишьСледующая проверка
Страница не запускаетсяРаспакуй архив, проверь расширение index.html и соседние файлы; открой Console
В Console красная ошибкаНачни с первой ошибки и указанной строки, обнови страницу после исправления
Кнопка молчит, ошибок нетПоставь console.log в обработчик. Нет записи — проверь ID, подключение скрипта и событие
Обработчик вызывается, но данные не меняютсяПосмотри начальные значения и условие запрета перед изменением
Данные меняются, экран прежнийПроверь вызов отображения и элементы, куда выводятся значения
Одно действие учтено дваждыПроверь подключение game.js и все места регистрации событий
Победа срабатывает слишком раноВыпиши все обязательные условия и порядок их проверки
После рестарта ошибка возвращаетсяСравни каждое поле состояния со свежим запуском; проверь старые таймеры, цикл и ввод, если они есть

Проверяй рядом с границей: меньше порога, ровно порог, больше порога; предпоследний и последний ход; повторное действие после конца. Не меняй правило и интерфейс одновременно, пока не понял, где результат впервые становится неверным.

Подсказка 1: не знаю, с чего начать правило

Запиши конкретные данные до действия и ожидаемые после. Например: «груз содержит два ID, игрок касается третьей посылки, груз и посылка не меняются». Это уже один запрет, который можно проверить отдельно.

Подсказка 2: путаются исходные и текущие данные

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

Подсказка 3: одна часть работает, а целая партия — нет

Нарисуй цепочку «ввод → запрет → изменение данных → проверка конца → отображение». Для каждого звена найди одну функцию или участок своего кода. Найди отсутствующее звено либо место, где одно действие проходит цепочку два раза. Подсказки отдельно для доставки, карточек и сигнала есть в hints.md.

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

Как проверить результат и свою самостоятельность

Пройди prototype-checklist.md. У игры должна быть инструкция, принятое и запрещённое действие, видимый результат, достижимый конец и повторный запуск. Проверь поражение, если оно есть в брифе. Проверь управление на заявленном устройстве. В Console не должно оставаться необъяснённых ошибок.

Затем без открытого образца объясни, где хранятся данные, какое условие запрещает действие, что завершает партию и почему рестарт восстанавливает всё. Выбери одно небольшое изменение собственного правила, предскажи результат и проверь его. Если можешь лишь повторить готовый код, вернись к этой части и попробуй небольшое изменение, обращаясь к документации.

УровеньНаблюдаемый результат
0Пока не могу объяснить, как эта часть работает
1Повторяю по готовому коду
2Изменяю и объясняю с небольшой подсказкой
3Решаю новую ограниченную задачу с документацией, без готовой реализации

Отметь отдельно данные и сброс, правило, ввод, отображение и отладку. Не складывай уровни в общий балл: хорошее оформление не компенсирует непонятое условие победы. Сохрани файлы игры, бриф и журнал решений. Честно укажи, какая помощь понадобилась и что осталось непроверенным. Работающий прототип ещё не доказывает, что игра нравится другим людям.

Один законченный пример для разбора

Открывай его после своей попытки. Это самостоятельный небольшой пример «Собери ровно 10», а не единственная допустимая игра. Он соединяет разные карты, сумму и ограниченное число выборов. Обязательного ключа из предложенного брифа в нём нет: это условие остаётся отдельной задачей.

Я попробовал сам — посмотреть пример и разбор

Выбери ровно три разные карты из 1–6. Сумма 10 после третьей карты даёт победу; превышение 10 — немедленное поражение; меньшая сумма после трёх карт — тоже поражение. 4 + 6 после двух карт ещё не победа.

Открыть пример в отдельной вкладке. Его файлы лежат в solutions/reference-game/, а объяснение — в solutions/reference-review.md, после рабочих документов просмотрщика.

Проверь 1 → 3 → 6, затем 1 → 2 → 3 и 6 → 5. Сравни одну часть со своей работой: сначала запрет, потом изменение данных, затем отображение. handleCard проверяет повтор по выбранному ID, меняет сумму и число выборов, определяет исход и вызывает render. resetGame создаёт новое состояние; кнопки и обработчики создаются только при загрузке.

Если не знакома запись selectedIds.includes(card.id), она проверяет, встречается ли ID в массиве, и возвращает true или false. Подробности есть в справке MDN об includes. Остальные команды отображения объяснены в файле разбора.

Если взял весь образец, не записывай его как самостоятельную реализацию. Закрой ответ и добавь обязательный ключ на карте 3: 1 + 4 + 5 теперь проигрывает, а 1 + 3 + 6 выигрывает. Обнови инструкцию и сброс, объясни, где проверяется новое условие. Готового кода этой вариации в комплекте нет.