Движки e-repa.ru. Часть 2. qb: конструктор вопросов
- вторник, 10 ноября 2026 г. в 00:00:10
В первой части я показал, как js-движок dub помогает ученику преодолеть трудность восприятия английской речи на слух: js-движок переводит структуру синтагмы из слухового канала в визуальный. Другая трудность, с которой сталкивается русскоязычный ученик, — английская грамматика, в частности построение вопросительного предложения. Помимо особенностей самого английского вопроса — инверсии, наличия вспомогательного глагола, недопустимости разрыва определения и определяемого слова — ученику трудно различать типы вопросов: вопрос к подлежащему, специальный, общий, разделительный и т. д. Поэтому вопросительное предложение складывается для ученика в сложный для усвоения комплекс. Чтобы помочь ученику видеть структуры, входящие в построение вопросительного предложения, я разработал js-движок qb.

Структуры, из которых строится английский вопрос, — вопросительное слово, вспомогательный глагол, подлежащее, предлог, смысловой глагол и остальные слова вопроса, — ученик видит на экране как отдельные зоны карточек: Wh-words, Auxiliaries, Pronouns & Nouns, Prepositions и Words. Собирая вопрос, он всякий раз решает, из какой зоны брать карточку и нужна ли ему эта зона вообще. Для специального вопроса он берёт вопросительное слово, затем вспомогательный глагол и подлежащее: Where do you live? Для общего вопроса карточек из зоны Wh-words он не берёт и начинает со вспомогательного глагола: Do you live here? Вопрос к подлежащему строится без вспомогательного глагола do, и после вопросительного слова сразу идёт смысловой глагол: Who lives here? А если вопросительное слово относится к существительному, разорвать их нельзя, и ученик ставит существительное из зоны Words сразу за вопросительным словом, до вспомогательного глагола: What tiles is she buying? Так типы вопросов, которые в учебнике даны списком правил, ученик различает по тому, какие зоны участвуют в сборке и в каком порядке.
Что даёт такое зонирование? Когда в задании сказано «постройте вопрос» и опоры нет, ученик должен опираться на правила построения вопроса и на инвентарь, то есть на то, какие бывают вопросительные слова, вспомогательные глаголы, местоимения и предлоги. Всё это есть у него только в голове, в абстрактно-понятийной форме: это заученные формулировки и парадигмы, которые нельзя увидеть и взять. Зонирование переводит правила и инвентарь в наглядно-действенную форму. Состав зон — это инвентарь, выложенный на экран, а то, какие зоны участвуют в сборке и в каком порядке, — это правила построения вопроса. Это такая же внешняя опора, как визуальная структура синтагмы в js-движке dub, и со временем ученик научается обходиться без неё.
Как зонирование помогает усвоить сложный грамматический комплекс? Комплекс, который в учебнике дан списком правил, а в задании «постройте вопрос» не дан вовсе, ученик должен удержать в голове целиком, и целое при этом рассыпается. Зонирование раскладывает его на функциональные структурные части и показывает их: ученик видит, из каких структур состоит вопрос и из какой парадигмы берётся каждый элемент. Но он не только видит, он ещё и действует руками: берёт карточку из зоны, ставит её в линию на её функциональное место, переставляет, убирает. В результате правило построения вопроса проходит через моторику: собирая вопросы одного типа, ученик раз за разом повторяет одно и то же движение — в специальном вопросе от зоны вопросительных слов к зоне вспомогательных глаголов и дальше к подлежащему, в общем вопросе сразу от вспомогательного глагола к подлежащему. Зрительный образ структуры и моторный образ действия складываются вместе, и комплекс усваивается как действие, а не как знание о действии.
Всё, что ученик видит на экране и делает руками, реализовано в js-движке qb. Как и в js-движке dub, упражнение состоит из трёх частей: HTML-страницы, XML-файла с заданиями и самого js-движка, то есть JS-файла qb-script.js, написанного с использованием jQuery, и CSS-файла qb-styles.css. Разница в том, что в dub одна страница — это одно упражнение с одной записью, а в qb на одной странице собрано много наборов заданий. Все они хранятся в папке qb_data/, и когда ученик открывает страницу Question Drill — Конструктор, js-движок читает реестр qb_data/index.json и строит по нему список наборов, из которого ученик выбирает один.
Наборы различаются заданиями, а инвентарь вопросительного предложения — вопросительные слова, вспомогательные глаголы, местоимения и предлоги — во всех заданиях и во всех наборах один и тот же. Поэтому задан он не в XML отдельного набора, а в самом js-движке, в виде постоянных массивов:
var ZONE1 = [ ['who','whom','what','which','whose'], ['when','where','why','how'], ['many','much','long','kind'] ]; var ZONE2 = [ {words: ['do','does','did']}, {words: ['am','is','are','was','were']}, {words: ['be','being','been']}, {words: ['have','has','had']}, {words: ['can','will','shall','may','must','need']}, {words: ['could','would','should','might']}, {words: ['not']} ];
Точно так же заданы местоимения (ZONE3_PERMANENT) и предлоги (ZONE5). У этого решения два следствия. Во-первых, составитель набора не может случайно убрать из зоны нужную форму или забыть её, потому что зоны от набора не зависят. Во-вторых, ученик всегда видит в зоне Auxiliaries все формы вспомогательных и модальных глаголов, а не только ту, которая входит в ответ, и поэтому вспомогательный глагол ему приходится выбирать по времени и лицу, а не брать единственную карточку. Зону предлогов js-движок достраивает на странице сам, функцией buildPrepositionZone(), поэтому зона, добавленная позже остальных, сразу появилась во всех наборах без правки их XML.
Раз инвентарь задан в js-движке, составителю набора остаётся написать в XML только само задание: русскую опору, варианты английского ответа и обманку.
<q> <task>01. Кто преподавал химию и физику в школе для мальчиков и девочек?</task> <answer>Who taught chemistry and physics at a school for boys and girls|Who taught chemistry and physics in a school for boys and girls</answer> <vx>teaching</vx> </q>
В <answer> через | перечислены все верные варианты ответа: здесь ученик вправе собрать и at a school, и in a school, и js-движок примет оба варианта. В <vx> стоит обманка, то есть форма того же глагола, которая в этой позиции неверна: teaching рядом с taught. Зону Words составитель набора не размечает, её js-движок строит сам, функцией buildScrambleWords():
q.answer.split('|').forEach(function(variant) { variant.trim().split(/\s+/).forEach(function(w) { if (w && !hardcoded[norm(w)]) add(w); }); }); q.vx.forEach(add);
Функция берёт слова всех вариантов ответа, отбрасывает те, что уже есть в постоянных зонах, добавляет обманку, убирает повторы и перемешивает оставшееся по Фишеру — Йетсу. В результате в зоне Words остаются только слова конкретного вопроса и обманка, а служебные слова — вопросительное слово, вспомогательный глагол, местоимение, предлог — ученик берёт из постоянных зон, где должен их выбрать.
Выбирать ученику приходится и в зоне Words. Если бы карточка при переносе на линию исчезала из зоны, ученик мог бы не выбирать, а раскладывать: по числу оставшихся карточек он видел бы, сколько слов ещё нужно, и последнюю карточку ставил бы на последнее место, не думая. Поэтому карточка остаётся в зоне, а на линию js-движок ставит её копию:
card.addEventListener('click', function() { if (!answered) addCardToEnd(word, zone); });
Одна карточка the годится для любого числа вхождений, и вычислить длину ответа по карточкам нельзя. Кроме клика, карточку можно перетащить в нужное место линии, и тогда js-движок показывает, куда она встанет: соседние карточки раздвигаются, и ученик видит позицию вставки ещё до того, как отпустит карточку. Сборочная линия переносится на несколько строк, поэтому функция findSlotIndex() сначала выбирает строку по вертикали, а затем место в строке по горизонтали.
Каждое изменение сборки js-движок проверяет сам, кнопки «Проверить» в упражнении нет: через 400 мс после последнего изменения функция doAutoCheck() сравнивает сборку слово за словом с каждым вариантом ответа:
var variants = q.answer.split('|'); var matched = false; for (var vi = 0; vi < variants.length; vi++) { var eNorm = variants[vi].trim().split(/\s+/).map(norm); if (sNorm.length !== eNorm.length) continue; var ok = true; for (var i = 0; i < sNorm.length; i++) { if (sNorm[i] !== eNorm[i]) { ok = false; break; } } if (ok) { matched = true; break; } }
Перед сравнением функция norm() приводит слова к нижнему регистру и отбрасывает всё, кроме букв, цифр и апострофа, поэтому ни регистр первой карточки, ни знак препинания на проверку не влияют. Если сборка совпала с любым из вариантов, задание засчитано, линия подсвечивается зелёным, и чуть больше чем через секунду js-движок переходит к следующему заданию.
Проверка засчитывает только полное совпадение с вариантом ответа, а артикль, притяжательное местоимение или предлог часто нельзя вывести из русской опоры: «мальчик по имени Дик» по-английски может быть и a boy, и the boy, и по русскому тексту не понять, какой вариант стоит в ключе. Если из-за этого задание не засчитывается, оно превращается в угадывание, а угадывание и есть тот обходной путь, который упражнение должно закрыть. Поэтому рядом со Skip стоит кнопка с лампочкой, и загорается она ровно тогда, когда всё, что составляет цель дрилла, — вопросительное слово, вспомогательный глагол, порядок слов, форма глагола — в сборке уже верно, а расходятся только «мелкие» слова: артикли, притяжательные местоимения и предлоги. Функция stripSoft() убирает эти слова из сборки и из каждого варианта ответа, а findNearMatch() ищет вариант, у которого остаток совпал со сборкой:
var sCore = stripSoft(sNorm).join(' '); q.answer.split('|').forEach(function(variant) { var words = variant.trim().split(/\s+/); var vNorm = words.map(norm); if (stripSoft(vNorm).join(' ') !== sCore) return; var mk = lcsMarks(sNorm, vNorm); var dist = sNorm.length + vNorm.length - 2 * mk.len; if (!best || dist < best.dist) best = {words: words, marks: mk, dist: dist}; });
Функция lcsMarks() строит наибольшую общую подпоследовательность так же, как побуквенная проверка в js-движке dub, только по словам, а не по буквам. По ней js-движок обводит красным карточки ученика, которые лишние или стоят не на своём месте, и выделяет жирным в ключе слова, которых в сборке не хватает. Ученик, нажав лампочку, видит ближайший вариант ключа, и дальше возможны два исхода. Если он исправил сборку и она совпала с ключом, задание засчитано. Если посмотрел и не исправил, задание не засчитано, и дальше он нажимает Skip. Для наборов, где цель дрилла — сами артикли, предлоги или притяжательные местоимения, у <questions> ставится атрибут strict: он исключает названный класс из «мелких» слов, и лампочка на ошибках в них уже не загорается.
Skip, которым ученик откладывает несданное задание, и циклы устроены так же, как в js-движке dub: функция nextCur() после каждого задания ищет следующее несданное, а дойдя до конца списка, начинает с начала, поэтому отложенное задание возвращается к ученику в следующем цикле, пока он не соберёт его верно. Прогресс хранится в localStorage, но ключ здесь не путь страницы, как в dub, а составной: pathname + '::' + имя набора. Поэтому прогресс каждого набора хранится отдельно, и, начав второй набор, ученик не теряет работу над первым.
Зоны в js-движке qb и визуальная структура синтагмы в js-движке dub выводятся из одного принципа, общего для всех js-движков e-repa.ru. Js-движок переводит правила и инвентарь из абстрактно-понятийной формы в наглядно-действенную, и это позволяет ученику совершать конкретные действия: назначать каждому элементу инвентаря функцию, его структурную роль. В qb ученик берёт карточку из зоны, ставит её в линию на функциональное место, переставляет, убирает; в dub он вписывает каждое расслышанное слово в своё поле в строке синтагмы.
В абстрактно-понятийной форме такое действие невозможно, потому что в ней нет ни предмета, ни места. Правило и парадигма в голове — это знание о структуре: заученная формулировка и список форм. Элемент в нём не отделён от других, а грамматика существует целиком и расплывчато. Чтобы назначить элементу функцию, нужны отдельный элемент, который можно взять, и место в структуре, куда его можно поставить. Ученик, у которого правило есть только в голове, может его вспомнить, но совершить с ним действие не может. Наглядно-действенная форма позволяет абстрактно-понятийной форме:
Получить границы, то есть телесность и структурность. В голове грамматика построения вопроса существует целиком и расплывчато: ученик знает, что в вопросе есть вспомогательный глагол, подлежащее и вопросительное слово, но не отделяет их друг от друга. На экране каждая часть структуры становится карточкой, то есть отдельным предметом, который можно взять, и у структуры появляется устройство: видно, из каких частей она состоит и где функциональное место каждой.
Превратиться в решение. Поставить карточку «куда-нибудь» нельзя: место определяется тем, что слово делает в вопросе, и, чтобы поставить карточку, ученик каждый раз определяет функцию слова. Знание о структуре («в вопросе есть вспомогательный глагол») становится действием («этот глагол стоит здесь, потому что…»).
Стать видимой самому ученику. Пока рассуждение было в голове, проверить его было нельзя. Теперь оно лежит перед глазами в виде расположения карточек: ошибка перестаёт быть смутным «что-то не так» и становится конкретной неверной позицией, которую можно найти и исправить, переставив карточку.
Повторяться до автоматизма. Действие с карточками ученик выполняет много раз и закрепляет последовательность решений: кто спрашивает, какой это тип вопроса, где вспомогательный глагол. Из внешнего действия вырастает внутреннее, и опора становится ненужной.