Во второй части я показал, как js‑движок qb выводит на экран инвентарь английского вопроса и правила его построения: по зонам карточек ученик видит, из каких структур состоит вопрос и из какой зоны берётся каждый элемент. Однако вопрос в qb ученик строит от русской опоры, и тип вопроса уже задан ею: «Кто преподавал химию и физику…?» сам сообщает, что это вопрос к подлежащему. Утвердительного английского предложения, из которого вопрос получается, ученик в qb не видит, а значит, не видит и отношения между предложением и вопросом. Он не видит, к какому члену предложения ставится вопрос, что вопросительное слово замещает этот член целиком, вместе с зависимыми словами, и что остальное предложение остаётся на месте: меняется только порядок слов, а при необходимости добавляется вспомогательный глагол. Нет в qb и ответа на вопрос.

Эти два пробела закрывает js‑движок qt. В нём ученику дано английское предложение, разрезанное на карточки по членам предложения, и красным выделено то, что замещает вопросительное слово. Ученик берёт карточку целиком, меняет красное на вопросительное слово, остальные карточки ставит как есть и получает вопрос. Затем он даёт на этот вопрос краткий ответ и возвращает в нём то, что заменил вопросительным словом. Так qt закрепляет навык, которого в qb нет совсем, — навык построения кратких ответов. Краткий ответ на вопрос к подлежащему состоит из подлежащего и вспомогательного глагола, который несёт время, число и отрицание: Who examined the ships in his group carefully? — The admiral did. Ответ на вопрос к предложной группе сохраняет предлог: From the deck of whose ship…? — From the deck of his ship.

Вместе два js‑движка выводят на экран грамматику английского вопроса с двух сторон. qb показывает, из чего вопрос строится: инвентарь и правила. qt показывает, из чего вопрос получается: члены утвердительного предложения, замену члена вопросительным словом и в кратком ответе обратную замену.

Какими обходными путями ученик подменяет построение вопроса к члену предложения

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

  1. Куцый вопрос. Ученик выбрасывает часть предложения и пишет What did the judge do? вместо вопроса, в котором стоят все слова предложения.

  2. Замена не того куска. Ученик заменяет вопросительным словом одно слово и отрывает его от группы, к которой оно относится, или, наоборот, захватывает лишнее.

  3. Подсмотренный ответ. Ученик ищет верный вопрос в самом упражнении.

  4. Перебор вариантов. Ученик меняет вопрос наугад, пока упражнение его не примет.

Как эти пути закрыты в упражнении и какие из них остаются открытыми, видно по тому, что ученик видит на экране и что он делает.

Как устроено упражнение Question Transformer и как в нём закрыты обходные пути

Когда ученик открывает страницу Question Transformer и выбирает набор, он видит предложение, разрезанное на карточки, одна из которых выделена красным, два поля — для вопроса и для краткого ответа — и под ними четыре постоянные зоны: вопросительные слова, вспомогательные и модальные глаголы с not, местоимения по разрядам и предлоги. Вопрос ученик набирает с клавиатуры или вставляет в поле карточки предложения и карточки из зон.

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

По этой рамке закрыты три первых обходных пути:

  • куцый вопрос не принимается, потому что ключ содержит все слова предложения, а проверка требует полного совпадения с ключом;

  • замена не того куска не принимается, потому что вопросительное слово замещает ровно выделенное красным, а карточка неделима;

  • подсмотреть ответ негде: js‑движок не показывает ни верный вопрос, ни верный ответ, а Skip только откладывает задание.

Четвёртый путь, перебор вариантов, js‑движок не закрывает: каждое изменение он проверяет и сразу засчитывает совпадение, а штрафа за перебор нет.

Трудность, которую ученик не может преодолеть сам, снята подсказкой. Она лежит в спойлере, и открывает её сам ученик: в ней названы тип вопроса и формула, по которой он строится, а там, где алгоритм ученика расходится с английским, — примечание с правилом. С формулой задание сводится к подстановке, поэтому подсказка — помощь, которую ученик включает сам, так же как замедление записи в js‑движке dub из первой части.

Как предложение, поля ввода и проверка устроены в коде js‑движка qt

Всё, что ученик видит на экране и делает с карточками, реализовано в js‑движке qt. Как и qb, упражнение состоит из HTML‑страницы, XML‑наборов заданий и самого js‑движка — JS‑файла qt‑script.js, написанного с использованием jQuery, и CSS‑файла qt‑styles.css. Наборы лежат в папке qt_data/, и список, из которого ученик выбирает набор, js‑движок строит по реестру qt_data/index.json, так же как в qb.

Как составитель набора описывает предложение и вопросы к его членам в XML

Предложение задаётся один раз, а вопросов к нему столько, сколько в нём членов, к которым составитель ставит вопрос. Каждый вопрос — отдельное задание.

<sentence chunks="The admiral|examined|the ships in his group|carefully">
	<q target="The admiral" qtype="subject">
		<answer>Who examined the ships in his group carefully</answer>
		<short>The admiral did</short>
	</q>
	<q target="carefully" qtype="special">
		<answer>How did the admiral examine the ships in his group</answer>
		<short>Carefully</short>
	</q>
</sentence>

В атрибуте chunks через | перечислены карточки, то есть члены предложения со всеми зависимыми словами: in his group стоит в одной карточке с the ships, потому что относится к этому существительному. Атрибут target — слова, которые замещает вопросительное слово, <answer> — вопрос, <short> — краткий ответ; верные варианты в обоих тегах перечисляются через |. Функция readXML() превращает каждый <q> в отдельный шаг упражнения, и все шаги одного предложения получают одни и те же карточки:

$(xml).find('sentences sentence').each(function() {
	var $s = $(this);
	var chunks = $s.attr('chunks').split('|');
	$s.children('q').each(function() {
		var $q = $(this);
		STEPS.push({
			chunks: chunks,
			target: $q.attr('target'),
			chunk: $q.attr('chunk') || '',
			answer: $q.children('answer').text().trim(),
			short: $q.children('short').text().trim(),
			qtype: ($q.attr('qtype') || '').toLowerCase()
			// а также type, hint и note для подсказки
		});
	});
});

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

Почему в js‑движке qt нет зоны Words и что лежит в постоянных зонах

В qb слова конкретного вопроса лежат в зоне Words. В qt эта зона не нужна: словами служат карточки самого предложения. Постоянные зоны заданы в js‑движке так же, как в qb, и от набора не зависят. Отличие одно: в зоне местоимений qt есть ещё there.

var ZONE3 = [
	{label: 'subject', words: ['I','you','he','she','it','we','they']},
	{label: 'object', words: ['me','him','her','us','them']},
	// demonstrative, possessive, absolute possessive, reflexive
	{label: 'there', words: ['there']}
];

Как ученик набирает вопрос с клавиатуры, щелчком по карточке и перетаскиванием

Из карточек предложения и зон ученик составляет вопрос в обычном текстовом поле. Щелчок по карточке или по слову из зоны вставляет его в активное поле, в то место, где стоит курсор, а пробелы вокруг вставки js‑движок расставляет сам:

function insertAtCursor(inputId, text) {
	var el = document.getElementById('qt-input-' + inputId);
	if (el.readOnly) return;
	var start = el.selectionStart;
	var end = el.selectionEnd;
	var before = el.value.substring(0, start);
	var after = el.value.substring(end);
	var spaceBefore = (before.length > 0 && before[before.length - 1] !== ' ') ? ' ' : '';
	var spaceAfter = (after.length > 0 && after[0] !== ' ') ? ' ' : '';
	el.value = before + spaceBefore + text + spaceAfter + after;
	// курсор встаёт за вставкой, затем запускается проверка
	scheduleAutoCheck();
}

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

Как js‑движок проверяет вопрос и краткий ответ в два этапа

Кнопки «Проверить» в упражнении нет. Через 400 мс после последнего изменения в поле функция doAutoCheck() сравнивает набранное со всеми вариантами ключа. Функция norm() приводит текст к нижнему регистру, отбрасывает всё, кроме букв, цифр, апострофа и пробелов, и схлопывает пробелы, поэтому ни регистр, ни знаки препинания на проверку не влияют:

function norm(s) {
	return s.toLowerCase().replace(/[^a-z0-9'’\s]/g, '').replace(/’/g, "'")
		.replace(/\s+/g, ' ').trim();
}

function matchesAny(studentText, answerField) {
	var sNorm = norm(studentText);
	var variants = answerField.split('|');
	for (var i = 0; i < variants.length; i++) {
		if (sNorm === norm(variants[i])) return true;
	}
	return false;
}

Задание проходит два этапа. На первом js‑движок сравнивает поле вопроса с <answer>. Если вопрос совпал, js‑движок ставит заглавную букву и вопросительный знак, закрывает поле для правки и переводит курсор в поле ответа. На втором этапе он сравнивает поле ответа с <short>, и только совпавший краткий ответ засчитывает задание: ответ получает точку, пара «вопрос — ответ» уходит в банк ответов, и js‑движок переходит к следующему заданию. Поэтому задание нельзя закрыть одним вопросом: краткий ответ ученик строит каждый раз. Если набранное не совпало ни с одним вариантом, js‑движок ничего не сообщает и ждёт следующего изменения.

Что показывает блок «Тип вопроса» и почему выбор типа не влияет на результат

В спойлере с подсказкой есть блок «Тип вопроса». В нём ученик может выбрать, какой вопрос он строит: к подлежащему, специальный или общий, — и получить объяснение, а при ошибке — объяснение с правилами. Тип js‑движок берёт из атрибута qtype. Выбор типа на результат не влияет: засчитывают задание только совпавшие вопрос и краткий ответ.

Как Skip сохраняет черновик обоих полей

Skip откладывает задание, и функция skipStep() сохраняет в результатах всё набранное в обоих полях и этап, на котором ученик остановился:

results[curStep] = {
	status: 'skip',
	// q и a — черновик для банка ответов
	savedQ: $q.value,
	savedA: $a.value,
	savedPhase: phase
};

Функция nextStep() ищет следующее несданное задание, а дойдя до конца списка, начинает с начала. Когда отложенное задание возвращается в следующем цикле, js‑движок восстанавливает черновик: если вопрос уже был принят, ученик сразу продолжает с краткого ответа. Прогресс хранится в localStorage под составным ключом из пути страницы и имени набора, как в qb.

Общий принцип, на котором построены js‑движки e‑repa.ru

Карточки‑члены предложения в js‑движке qt выводятся из того же принципа, что зоны в qb и визуальная структура синтагмы в dub. Js‑движок переводит правила и инвентарь из абстрактно‑понятийной формы в наглядно‑действенную, и это позволяет ученику совершать конкретные действия: назначать каждому элементу функцию, его структурную роль. В qt такое действие — замена члена предложения вопросительным словом: ученик берёт карточку целиком, решает, каким вопросительным словом заменить красное и куда поставить эту карточку в вопросе, а в кратком ответе возвращает красное на место вопросительного слова.

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

  1. Получить границы, то есть телесность и структурность. Член предложения становится карточкой, а граница карточки показывает, где кончается группа, которую замещает вопросительное слово: the ships in his group — одна карточка, и замещается она целиком.

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

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

  4. Повторяться до автоматизма. К одному предложению ученик ставит вопросы ко всем выделенным членам по очереди и каждый раз даёт краткий ответ, закрепляя последовательность решений. Из внешнего действия вырастает внутреннее, и опора становится ненужной.