Простейшие процедуры работы с клавиатурой в языке Borland Pascal находятся в модуле crt, однако они не очень подходят для игровых программ. Эти процедуры ориентированы на консольный ввод, или, другими словами, на функционирование клавиатуры в режиме пишущей машинки, когда представляет интерес только нажатие на клавишу, а не ее отпускание. Впрочем, в автоматической пишущей машинке реализован автоповтор: при длительном удерживании клавиши генерируется последовательность одинаковых символов, а при отпускании это прекращается. Однако знаки печатаются неравномерно по времени: после первого следует примерно полусекундный интервал, а затем частота повторения возрастает более чем на порядок. Значит, если подобным образом управлять движением персонажа, то он сначала сделает один маленький шажок, на миг замрет и лишь затем продолжит движение. Понятно, что для динамических игр такая неравномерность движения неприемлема. Поэтому давайте разберемся, как работает клавиатура ПК. Прикладные программы обращаются к ней не напрямую, а через драйвер, аналогично взаимодействию с мышью. Но поскольку последние бывают различных типов, то и протоколы обмена с ними также многообразны, что не позволяет действовать в обход драйвера. Аппаратный же интерфейс клавиатуры всегда одинаков, и когда стандартный драйвер не устраивает, то всегда можно написать собственный и не задумываться о совместимости. Драйвер состоит из двух взаимно независимых частей, т. е. они никогда не вызывают друг друга и работают асинхронно. Между собой эти части взаимодействуют только через буфер, где хранятся числа, соответствующие нескольким последним нажатым клавишам.
Первая часть — вызываемый нажатием клавиши обработчик аппаратного прерывания клавиатуры, который выясняет ее номер, находит соответствующий номер ASCII-символа и помещает их оба (номера) в буфер. Именно этот обработчик прерывания отслеживает кодовую страницу при переключении раскладок клавиатуры и реагирует на клавиши-модификаторы
Обработчик прерывания отслеживает, как правило, лишь нажатия на клавиши и игнорирует их отпускания. Однако есть и исключения, — если бы не контролировались отпускания клавиш-модификаторов, то они просто не выполняли бы свои функции. Информация о состоянии клавиш
Вы уже, наверное, догадались, что клавиатура сообщает своему обработчику и о нажатии, и об отпускании клавиш, иначе не работали бы клавиши-модификаторы. При нажатии на клавишу контроллер клавиатуры сообщает ее номер, а при отпускании — этот же номер, но с установленным старшим битом, т. е. увеличенный на 128. Давайте поступим так: заведем массив логических (boolean) переменных, где каждая ячейка будет соответствовать определенной клавише и хранить информацию о том, нажата она или отпущена. Обработчик прерывания просто запишет в нужную ячейку значение TRUE, когда клавиша нажата, и FALSE — когда отпущена. Вторая часть драйвера, обеспечивающая взаимодействие с прикладной программой, при этом не понадобится — достаточно будет непосредственно проверять содержимое нужных ячеек массива.
Написать обработчик клавиатурного прерывания — задача более сложная, чем может показаться на первый взгляд. Дело в том, что первая клавиатура IBM PC содержала лишь 83 клавиши, у IBM PC AT имелось на одну больше, а сейчас стандартом стала 101-клавишная расширенная клавиатура. Правда, зачастую к ней добавляют еще несколько клавиш, играющих определенную роль в Windows. Кроме того, и разнообразные эргономичные клавиатуры, предназначенные для ноутбуков, нередко довольно сильно отличаются от стандарта, а ведь с точки зрения и прикладных программ, и драйверов клавиатуры они должны быть идентичны. Поэтому при нумерации клавиш получилась довольно запутанная система. Так, прямые аналоги клавиш IBM PC шифруются единственным кодом, а остальные — последовательностью кодов. Одни клавиши, например
В нашу задачу не входит написание драйвера, «различающего» все клавиши расширенной клавиатуры (хотя это вполне реально), для нас главное — чтобы он обеспечивал необходимый минимум функций. Так, мы не будем стремиться к тому, чтобы различались клавиши управления курсором в «курсорном» блоке и на цифровой клавиатуре, а позаботимся лишь о возможности различать правые и левые
«Сердце» модуля (см. листинг) — обработчик аппаратного прерывания клавиатуры NewInt. Он вызывается нажатием на клавишу, читает из порта ее номер, сообщает контроллеру, что байт принят, анализирует этот байт и вносит изменения в таблицу Key, содержащую признаки нажатия для всех клавиш (даже с определенным запасом) и вынесенную в интерфейсную часть модуля. По завершении всех действий обработчик сообщает контроллеру прерываний, что работа закончена. Если контроллер должен передать обработчику последовательность байтов, то для каждого из них происходит аппаратное прерывание, и потому обработчик должен запоминать переданные ему управляющие коды с помощью переменной E0pressed. Таким образом отслеживаются нажатия на правые
Описанный обработчик прерывания не вызывает старый, и потому нет опасности переполнения клавиатурного буфера, а также не работают никакие стандартные функции, будь то текстовый ввод, обращение к функциям ReadKey и KeyPressed или прерывание отлаживаемой программы. Более того, если вы работаете в среде DOS, а не в DOS-сессии Windows (система Windows обработает «сообщения» клавиатуры еще до того, как передаст их в DOS-сессию), то даже можете безбоязненно нажимать — перезагрузки не произойдет.
Если же вам нужно ввести строку или символ стандартными средствами, то вместо собственного обработчика подключите стандартный с помощью процедуры SetStandardInt. После ввода с применением процедуры SetMyInt снова замените обработчик на собственный. Ни в коем случае не следует пользоваться стандартными процедурами и функциями консольного ввода при переопределенном обработчике прерываний — это верный способ «подвесить» ПК. Ведь стандартные функции пытаются прочесть символ из буфера, а там ничего появиться не может, так как стандартный обработчик отключен.
Новый обработчик подключается в блоке инициализации модуля, а отключается — в процедуре MyExit, вызываемой при любом завершении программы, в том числе и аварийном. Программа должна восстанавливать все перехваченные вектора прерываний даже в том случае, когда закрывается по ошибке, поэтому обычным вызовом SetStandardInt из основной программы здесь не обойтись — иначе система может зависнуть.
При восстановлении стандартного обработчика предусмотрено обнуление всех флагов в области переменных BIOS, соответствующих клавишам-модификаторам. Если этого не сделать, то могут появиться трудно обнаруживаемые ошибки, например, если запустить программу из среды Borland Pascal нажатием
Окончание в следующем номере.