Разработка заданий для модуля LightPT
Начальные действия
Чтобы задания проверялись автоматически, необходимо создать Урок с заданиями. Урок представляет собой папку, в которой находятся .pas-файлы - заготовки с формулировками заданий, а также специальный файл lightpt.dat. Этот файл может быть пустым или содержать название урока. Именно этот файл является маркером того, что все задания в этой папки будут проходить через невидимую проверяющую систем
Итак, создадим папку Lesson1 и в ней файл lightpt.dat, в котором напишем одну строчку в кодировке Windows 1251: Урок-1. Задания на массивы
Создание задания
Теперь создадим в этой папке первый файл с заданием ArrTask1.pas со следующим содержимым:
Заметим, что в заготовке задания уже массив заполнен и выведен. Позже рассмотрим ситуацию решения с нуля.
Создание модуля проверки заданий
Теперь создадим модуль, который будет проверять все выполненные задания в уроке (в олимпиадном программировании такие подпрограммы называются чекерами). Для этого в той же папке создадим файл Tasks.pas со следующим содержимым (важно использовать именно это имя файла):
Вот полный текст проверяющего модуля Tasks:
unit Tasks; uses LightPT; procedure CheckTaskT(name: string); begin ClearOutputListFromSpaces; case name of 'ArrTask1': begin end; end; end; initialization CheckTask := CheckTaskT; end.
Здесь ClearOutputListFromSpaces надо писать всегда - оно удаляет пробелы из вывода, которые добавляет при выводе Print, a.Print и ему подобные.
Теперь в ветви 'ArrTask1' оператора case напишем чекер, проверяющий выполнение задания ArrTask1.
Написание чекера
Нужно понимать, что чекер состоит из следующих основных компонент:
- Проверка типов, введенных пользователем (процедура CheckData)
- Считывание ввода пользователя в локальные переменные
- Решение задания в чекере и получение ответа
- Проверка соответствия ответа тому, который вывел пользователь (процедура CheckOutput)
Рассмотрим каждый из этих пунктов по-отдельности
Проверка типов, введенных пользователем
Проверка типов, введенных пользователем, осуществляется с помощью процедуры CheckData. Простейшая форма вызова CheckData имеет вид:
var n := 10; CheckData(Input := |cInt| * n);
var n := 10; CheckData(|cInt| * n);
Это говорит о том, что мы проверяем, что пользователь ввёл 10 целых чисел (каждое целое число задаётся здесь маркером типа cInt).
Какие ещё варианты задания входных данных могут быть? Их всего пять:
cInt, cRe, cStr, cBool, cChar
Они должны быть объединены в массив типов тем или иным способом, например, с помощью операций сложения массивов и умножения массива на число. Например, выражение
|cInt| * n
задаёт массив из n целых типов, а выражение
|cStr| + |cInt| * n + |cRe| * 2
задаёт массив из одного строкового типа, после которого идёт n целых типов и 2 вещественных.
Если маркер типа умножается на число, его необязательно окаймлять в ||, например:
|cStr| + cInt * n + cRe * 2
Считывание ввода пользователя
var n := 10; CheckData(cInt * n);
Это значит, что для решения задания ученик вводит n целых чисел тем или иным способом.
Теперь чтобы проверить задание, мы должны те n значений, которые ввёл ученик, считать во внутренние переменные. Мы это сделаем таким способом:
var a := IntArr(n);
Функция IntArr(n) считывает следующие n данных, которые ввёл пользователь, в массив, рассматривая их как целые. Таким образом, она возвращает массив из n целых.
Какие еще функции можно использовать для считывания ввода пользователя?
Int Re Boo Chr Str
которые считывают один элемент данных и продвигаются вперёд, пары
Int2 Re2
которые возвращают кортежи (пар другого типа нет поскольку это - редкость), а также массивы одного типа из n элементов:
IntArr(n) ReArr(n) BooArr(n) ChrArr(n) StrArr(n)
Решение задания в чекере и получение ответа
Теперь когда мы считали все данные, которые ввёл пользователь, в переменные, решим задачу так, как должен решать ученик. В данном случае это просто:
var cnt := a.Count(x -> x mod 2 = 0);
Проверка ответа
Теперь необходимо понять, что в заготовке задания мы уже вывели сгенерированный случайный массив и нам лишь надо вывести найденное значение cnt. Для этого воспользуемся процедурой CheckOutput:
CheckOutput(a,cnt);
Всё, текст проверки полностью готов:
procedure CheckTaskT(name: string); begin case name of 'ArrTask1': begin var n := 10; CheckData(Input := cInt * n + |cRe|); // проверка типов var a := IntArr(n); // считывание ввода var cnt := a.Count(x -> x mod 2 = 0); // решение задания CheckOutput(a,cnt); // проверка ответа end; end; end;
Как видим, он совсем простой - состоит из 5 строк.
Как это выглядит со стороны ученика
Ученик, решая ArrTask1, вначале просто запустит заготовку и увидит
Далее он попытается решить неправильно:
Добавление тестов (не дописано)
К этому может добавляться вызов процедуры генерации тестов. Дело в том, что пользователь может попытаться обмануть систему проверки LightPT и ввести данные, на которых задача решается просто.