Простейшие процедуры работы с клавиатурой в языке Borland Pascal находятся в модуле crt, однако они не очень подходят для игровых программ. Эти процедуры ориентированы на консольный ввод, или, другими словами, на функционирование клавиатуры в режиме пишущей машинки, когда представляет интерес только нажатие на клавишу, а не ее отпускание. Впрочем, в автоматической пишущей машинке реализован автоповтор: при длительном удерживании клавиши генерируется последовательность одинаковых символов, а при отпускании это прекращается. Однако знаки печатаются неравномерно по времени: после первого следует примерно полусекундный интервал, а затем частота повторения возрастает более чем на порядок. Значит, если подобным образом управлять движением персонажа, то он сначала сделает один маленький шажок, на миг замрет и лишь затем продолжит движение. Понятно, что для динамических игр такая неравномерность движения неприемлема. Поэтому давайте разберемся, как работает клавиатура ПК. Прикладные программы обращаются к ней не напрямую, а через драйвер, аналогично взаимодействию с мышью. Но поскольку последние бывают различных типов, то и протоколы обмена с ними также многообразны, что не позволяет действовать в обход драйвера. Аппаратный же интерфейс клавиатуры всегда одинаков, и когда стандартный драйвер не устраивает, то всегда можно написать собственный и не задумываться о совместимости. Драйвер состоит из двух взаимно независимых частей, т. е. они никогда не вызывают друг друга и работают асинхронно. Между собой эти части взаимодействуют только через буфер, где хранятся числа, соответствующие нескольким последним нажатым клавишам.

Первая часть — вызываемый нажатием клавиши обработчик аппаратного прерывания клавиатуры, который выясняет ее номер, находит соответствующий номер ASCII-символа и помещает их оба (номера) в буфер. Именно этот обработчик прерывания отслеживает кодовую страницу при переключении раскладок клавиатуры и реагирует на клавиши-модификаторы , и . Вторая часть помогает проверить содержание буфера и из него извлечь (если, конечно, там что-либо находится) номера символа и клавиши. Именно с этой частью и работают процедуры KeyPressed и ReadKey.

Обработчик прерывания отслеживает, как правило, лишь нажатия на клавиши и игнорирует их отпускания. Однако есть и исключения, — если бы не контролировались отпускания клавиш-модификаторов, то они просто не выполняли бы свои функции. Информация о состоянии клавиш , и имеется как раз в нужном нам виде: в области данных BIOS существуют поля, отдельные биты которых описывают клавиши-модификаторы: нажатой соответствует «1», отпущенной — «0». Значит, в любой момент можно узнать, нажата клавиша или отпущена. Эта область занимает два байта по адресам 0040:0017h и 0040:0018h, но, к сожалению, так можно определить лишь состояние клавиш-модификаторов, а информация о наиболее интересных в нашем случае курсорных «стрелках» и «пробеле» отсутствует. Поэтому пойдем другим путем — напишем собственный драйвер клавиатуры, удовлетворяющий всем нашим требованиям.

Вы уже, наверное, догадались, что клавиатура сообщает своему обработчику и о нажатии, и об отпускании клавиш, иначе не работали бы клавиши-модификаторы. При нажатии на клавишу контроллер клавиатуры сообщает ее номер, а при отпускании — этот же номер, но с установленным старшим битом, т. е. увеличенный на 128. Давайте поступим так: заведем массив логических (boolean) переменных, где каждая ячейка будет соответствовать определенной клавише и хранить информацию о том, нажата она или отпущена. Обработчик прерывания просто запишет в нужную ячейку значение TRUE, когда клавиша нажата, и FALSE — когда отпущена. Вторая часть драйвера, обеспечивающая взаимодействие с прикладной программой, при этом не понадобится — достаточно будет непосредственно проверять содержимое нужных ячеек массива.

Написать обработчик клавиатурного прерывания — задача более сложная, чем может показаться на первый взгляд. Дело в том, что первая клавиатура IBM PC содержала лишь 83 клавиши, у IBM PC AT имелось на одну больше, а сейчас стандартом стала 101-клавишная расширенная клавиатура. Правда, зачастую к ней добавляют еще несколько клавиш, играющих определенную роль в Windows. Кроме того, и разнообразные эргономичные клавиатуры, предназначенные для ноутбуков, нередко довольно сильно отличаются от стандарта, а ведь с точки зрения и прикладных программ, и драйверов клавиатуры они должны быть идентичны. Поэтому при нумерации клавиш получилась довольно запутанная система. Так, прямые аналоги клавиш IBM PC шифруются единственным кодом, а остальные — последовательностью кодов. Одни клавиши, например , выдают такую последовательность кодов, какую должен был набрать пользователь IBM PC XT, нажимая несколько клавиш, другие предваряются управляющим кодом $E0. Зачастую оба эти подхода сочетаются.

В нашу задачу не входит написание драйвера, «различающего» все клавиши расширенной клавиатуры (хотя это вполне реально), для нас главное — чтобы он обеспечивал необходимый минимум функций. Так, мы не будем стремиться к тому, чтобы различались клавиши управления курсором в «курсорном» блоке и на цифровой клавиатуре, а позаботимся лишь о возможности различать правые и левые и , так как их расположение (и размер) делает их удобными для использования в играх. Чтобы облегчить работу с таблицей клавиш (булевым массивом), припишем правым и особые (нестандартные) коды и поместим последние в свободной области.

«Сердце» модуля (см. листинг) — обработчик аппаратного прерывания клавиатуры NewInt. Он вызывается нажатием на клавишу, читает из порта ее номер, сообщает контроллеру, что байт принят, анализирует этот байт и вносит изменения в таблицу Key, содержащую признаки нажатия для всех клавиш (даже с определенным запасом) и вынесенную в интерфейсную часть модуля. По завершении всех действий обработчик сообщает контроллеру прерываний, что работа закончена. Если контроллер должен передать обработчику последовательность байтов, то для каждого из них происходит аппаратное прерывание, и потому обработчик должен запоминать переданные ему управляющие коды с помощью переменной E0pressed. Таким образом отслеживаются нажатия на правые и . Если потребуется различать нажатия клавиш «курсорного» и цифрового блоков, то это делается так же, поскольку коды клавиш «курсорного» блока предваряются управляющим кодом $E0. Размер массива Key выбирается таким, чтобы не требовалось дополнительных проверок драйвера и чтобы никакие нестандартные или вновь введенные коды клавиш не могли испортить ячеек памяти за его пределами.

Описанный обработчик прерывания не вызывает старый, и потому нет опасности переполнения клавиатурного буфера, а также не работают никакие стандартные функции, будь то текстовый ввод, обращение к функциям ReadKey и KeyPressed или прерывание отлаживаемой программы. Более того, если вы работаете в среде DOS, а не в DOS-сессии Windows (система Windows обработает «сообщения» клавиатуры еще до того, как передаст их в DOS-сессию), то даже можете безбоязненно нажимать + + — перезагрузки не произойдет.

Если же вам нужно ввести строку или символ стандартными средствами, то вместо собственного обработчика подключите стандартный с помощью процедуры SetStandardInt. После ввода с применением процедуры SetMyInt снова замените обработчик на собственный. Ни в коем случае не следует пользоваться стандартными процедурами и функциями консольного ввода при переопределенном обработчике прерываний — это верный способ «подвесить» ПК. Ведь стандартные функции пытаются прочесть символ из буфера, а там ничего появиться не может, так как стандартный обработчик отключен.

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

При восстановлении стандартного обработчика предусмотрено обнуление всех флагов в области переменных BIOS, соответствующих клавишам-модификаторам. Если этого не сделать, то могут появиться трудно обнаруживаемые ошибки, например, если запустить программу из среды Borland Pascal нажатием +, то по завершении работы клавиатура может начать «странно» функционировать — так, как будто вы нажали и удерживаете клавишу . Это происходит потому, что переопределение обработчика произошло раньше, чем вы отпустили , и старый обработчик «запомнил» именно такое состояние.

Окончание в следующем номере.

1289