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

Вид материалаДокументы

Содержание


Таблица 12.3. 16-разрядные значения счетчика для наиболее употребительных частот таймера.
Подобный материал:
1   ...   23   24   25   26   27   28   29   30   ...   37


Доступ к микросхеме таймера осуществляется через порты 40h-43h. Как видите, мы можем использовать только счетчики 0 и 3. Связываться со счетчиком 1 нам определенно не стоит! Счетчик 0 уже используется DOS для системных часов. Так почему бы нам не использовать его, перепрограммировав на нужную частоту?

Это вполне можно сделать. И этот метод не вызовет неполадок в компьютере. Единственный недостаток такого подхода состоит в том, что изменится системное время. Мы можем обойти это, сохранив время, за которое изменяется значение частоты таймера и по ходу игры отслеживать время с учетом этого изменения. Затем, перед окончанием работы программы, следует восстановить прежнее значение частоты и изменить системное время. Для сохранения и восстановления системного времени можно использовать функции Си.

Изменение частоты таймера

Давайте поговорим о том, как изменять частоту таймера. Таймер генерирует прерывания следующим образом: заданное значение регистра счетчика таймера уменьшается на единицу каждый раз, когда приходит импульс от генератора. Когда значение счетчика становится равным нулю, таймер генерирует прерывание, после чего восстанавливает исходное значение счетчика. Затем процесс повторяется вновь. Счетчик 0 генерирует прерывания 18.2 раза в секунду или через каждые 55.4 миллисекунды. Это время задается следующим образом: на микросхему таймера подаются импульсы с частотой 1.19318МГц. В случае системного таймера в регистр помещается число 65536. Если взять 1.19318 и разделить па 65536 получится 18.206 циклов в секунду или 54.925 миллисекунд. Как видите, создатели персонального компьютера просто использовали макси­мально возможный делитель, который может поместиться в 16 битов: 65535. (На самом деле таймер «тикает» на один раз больше — вот откуда я взял число 65536).

Таким образом, все что нам следует сделать, это вычислить 16-разрядное число, при делении на которое 1.19318МГц даст нам нужную частоту. Для вашего удобства я рассчитал подходящие значения для наиболее часто встречающихся частот. Взгляните на таблицу 12.3

^ Таблица 12.3. 16-разрядные значения счетчика для наиболее употребительных частот таймера.

Шестандцатиричное

Значение

Десятичное


Значение

Частота


4DAEh

19886

60Hz

965C

39722

30Hz

E90B

59659

20Hz

Таким образом, нам следует перепрограммировать счетчик 0 таким образом, чтобы он генерировал прерывания с более подходящей для нашей игры частотой - так, чтобы наша игра могла контролировать события чаще чем 18.2 раза в секунду.

Давайте посмотрим, каким образом можно задать соответствующее значение счетчика. Нас интересуют порты ввода/вывода 40h и 43h (все, о чем мы будем говорить, относится к ним): счетчик 0 и соответствующий управляющий регистр. Значения битов управляющего регистра, показаны в таблице 12.4.



Из таблицы 12.4 видно, что в нашем распоряжении много параметров. Впрочем, нам сейчас все они не нужны, нам надо всего-навсего изменить начальное значение счетчика 0. При этом значения других битов должны быть следующими:
  • Режим работы - 2, генератор частоты;
  • Метод подсчета - двоичный;
  • Запись регистра счетчика осуществляется при помощи операции «Прочитать/записать младший байт, а затем старший байт счетчика».

Таким образом, все что мы должны сделать, это записать управляющее слово в порт 43h, а затем выполнить две операции записи в порт 40h. В ходе первой операции записи мы установим значение младшего байта нового значения счетчика, а при второй — значение старшего байта. В том, где какие байты аппаратные средства разберутся сами, поэтому вам об этом волноваться не надо. В Листинге 12.5 приведен текст функции, программирующей таймер.

Листинг 12.5. Перепрограммируем системные часы (OUTATIME.C).

// ВКЛЮЧАЕМЫЕ ФАЙЛЫ /////////////////////////////////////

#include

#include

// ОПРЕДЕЛЕНИЯ //////////////////////////////////////////

#define CONTROL_8253 0х43 // управляющий регистр 6253

#define CONTROL_WORD 0хЗС // управляющее слово, задающее режим 2,

// двоичный подсчет, запись

// младший/старший байт

#define COUNTERED 0х40 // счетчик 0

#define TIMER_60HZ 0x4DAE // 60Гц

#define TIMER_30HZ 0x965C // 30Гц

#define TIMER_20HZ 0xE90B // 20Гц

#define TIMER_18HZ 0xFFFF // 18.2Гц (стандартная частота)

// МАКРОСЫ //////////////////////////////////////////////

#define LOW_BYTE(n) (n & 0x00ff)

#define HI_BYTE(n) ((n>>8) & 0x00ff}

// ФУНКЦИИ //////////////////////////////////////////////

void Change Time(unsigned int new count)

{

// послать управляющее слово, задающее режим 2, двоичный подсчет,

// запись младший/старший'байт


_outp(CONTROL_8253, CONTROL_WORD);

// теперь запишем младший значащий байт в регистр счетчика _outp(COUNTER_0,LOW_BYTE(new_count));

//я теперь запишем старший байт в регистр счетчика

_outp(COUNTER_0,HI_BYTE(new_count) ) ;

} // конец Change_Time

// ОСНОВНАЯ ПРОГРАММА ///////////////////////////////////

main()

{

// перепрограммирование таймера с частоты 18.2Гц на частоту 60Гц

Change_Time (TIMER_60HZ);

} // конец,функции main

После окончания этой программы системный таймер будет работать слишком быстро для внутренних часов DOS. Для того чтобы это поправить, вы можете:
  • Перезагрузить компьютер;
  • Запустить программу еще раз, изменив константу, передаваемую функции Change_Time (), на TIMER_18HZ.

Собираем все вместе

Итак, мы рассмотрели основы работы с прерываниями, многозадачность и некоторые приемы программирования, которые несколько облегчают реализацию архитектуры компьютерной игры. Причем представленная здесь коллекция приемов программирования далеко не самая полная и лучшая. Мы просто обсудили эти вопросы для того, чтобы вы смогли представить себе различные способы организации игры. Впрочем, вскоре вы, наверняка, придумаете и свои Методы и трюки. Главная цель этой главы состоит в том, чтобы дать вам некую Справную точку и направление для ваших размышлений. Теперь я хочу просуммировать все полученные вами знания и разобрать несколько примеров применения рассмотренных нами методов. После этого для окончательного закрепления материала я приведу еще два примера программ.

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

Управление задачами, зависящими от времени и событий, осуществляется за счет подпрограмм обработки тех прерываний, которые происходят по наступлению определенного момента времени или в результате какого-либо события. В этом смысле очень полезным оказывается прерывание системного таймера 0х1С. Мы можем поместить адрес нашего обработчика в таблицу векторов по адресу, соответствующему прерыванию, которое генерируется при каждом системном «тике» (то есть 18.2 раза в секунду). Это гарантирует нам, что независимо от загруженности системы, наша подпрограмма обработки прерываний будет вызываться всегда с неизменной частотой. Кроме того, мы знаем, что системный таймер можно запрограммировать и на другую частоту, например, 20Гц, 30Гц, 60Гц и так далее. Нам это может понадобиться, если мы собираемся использовать прерывание для чего-то, что требует более частого выполнения.

И, наконец, мы узнали, как использовать прерывания для изменения глобальных переменных, являющихся, по сути, сообщениями, на которые могут реагировать другие функции той же программы. Это может пригодиться в тех случаях, когда в игре надо выполнять критичные по времени исполнения операции, синхронизацию событий и тому подобные задачи.

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

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

Обсуждение всех этих вопросов призвано помочь вам осознать, как писать программы, работающие в «реальном режиме времени». Иными словами, как организовать выполнение нужных действий таким образом, чтобы создать впечатление одновременности происходящего. В компьютерных играх без этого не обойтись.

Напоследок мы узнали, как перепрограммировать системные часы, реализованные микросхемой таймера 8253. Мы узнали, что в счетчике 0 хранится 16-разрядное значение, которое рассматривается как делитель поступающего сигнала с частотой 1.19318МГц. Результирующий сигнал используется для генерации «тиков», своего рода пульса, который инициирует прерывание по времени. Это прерывание может быть перехвачено с помощью вектора прерывания 0х1С. В ста процентах игр этот таймер перепрограммируется на более подходящую частоту вроде 30Гц или 60Гц. Таким образом, работа программы синхронизируется по новой базовой частоте. Помимо этого, входящая в состав игры подсистема вывода звуковых эффектов и подсистема ввода/вывода реа лизуются в виде фоновых задач, которые реализованы в виде процедур обслуживания таймерного прерывания 0х1С. Благодаря этому ввод/вывод и музыка исполняются с неизменной скоростью даже тогда, когда сама игра несколько замедляется вследствие выполнения вычислений или сложных графических эффектов.

Писать эту главу было непросто, поскольку в ней мне пришлось изложить вам очень много новых вещей. На самом деле, следует иметь в виду, что только опыт поможет вам разобраться с миллионами тонкостей, которые приходится учитывать при программировании компьютерных игр. Тем не менее, перед вами уже открылись новые возможности. Сейчас вы готовы мыслить по-новому и это Уже неплохо.

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

Пример обработчика прерывания № 1 - Там полно звезд...

Чтобы продемонстрировать вам, насколько полезными могут оказаться многозадачность и прерывания, я написал в виде обработчика прерывания программу, которая рисует трехмерное звездное небо. Как вам уже известно, при каждом приращении счетчика внутреннего таймера, генерируется прерывание. При помощи вектора 0х1С я сопоставил этому прерыванию свою процедуру обслуживания, которая и создает трехмерное звездное небо. Поскольку обращение к моему обработчику происходит 18.2 раза в секунду, значит и картина звездного неба обновляется с этой же частотой. Нечто подобное мы могли бы использовать при написании игры, работающей на одном экране (игры типа «перестреляй их всех»). Наше звездное небо будет изменяться независимо от того, что будет происходить на переднем плане.

Обратите внимание на то, что процедура обслуживания прерывания сделана автономной. Это означает, что она инициализирует свои данные при первом обращении к ней (в этот момент она создает базу данных с информацией о звездах), а при последующих вызовах просто видоизменяет картинку.

И напоследок еще одно маленькое замечание: завершая программу, вы можете оставить ее резидентной в памяти, нажав клавишу Е. Сделав это, вы увидите, что звездное небо присутствует в строке приглашения DOS, что выглядит несколько странно! Причина такого поведения программы кроется в том, что когда вы завершаете ее нажатием клавиши Е, она не восстанавливает прежний обработчик прерывания. Компьютер продолжает вызывать по прерыванию от таймера наш обработчик. Вы наверняка даже и представить себе не могли, что это сработает! И, тем не менее, завершаясь, программа оставляет в оперативной памяти большую часть своего кода неизменным, в результате чего обработчику прерывания удается пережить окончание работы породившей его программы. Если вы попытаетесь запустить еще какую-нибудь программу, компьютер, скорее всего, «зависнет». Поэтому для написания настоящих резидентных программ такой метод применять не стоит. (На самом деле для этих целей предназначена специальная функция DOS, которая называется dos keep, но сейчас мы не будем подробно рассматривать резидентные программы. Мы просто случайно создали одну из них). Если система «зависнет», вам придется перезагрузиться.

Текст программы, изображающей трехмерное звездное небо, представлен в Листинге 12.6.

Листинг 12.6. Трехмерное звездное небо (STARS.C).

// ВКЛЮЧАЕМЫЕ ФАЙЛЫ /////////////////////////////////////

#include

#include

#include

#include

#include

#include

// ОПРЕДЕЛЕНИЯ //////////////////////////////////////////

#define TIME_KEEPER_INT 0x1C

#define NUM_STARS 50

// структуры ////////////////////////////////////////////

typedef struct star_typ

int x,y; // координаты звезды

int vel; // проекция скорости звезды на ось Х

int color; // цвет звезды

} star, *star_ptr;

// ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ /////////////////////////////////////////

void (_interrupt _far *01d_Isr)(void);

// хранит старый обработчик прерывания

unsigned char far *video_buffer = (char far *)0xA0000000L;

// указатель на видеобуфер

int star_first=1; // флаг первого вызова автономной функции

// звездного неба

star starstNUM_STARS]; //звездное небо

// функции ///////////////////////////////////////////////////////

void Plot_Pixel_Fast(int x,int y,unsigned char color)

{

// эта функция рисует точку заданного цвета несколько быстрее,

// чем обычно за счет применения операции сдвига вместо операции

// умножения

// используем известный факт, что 320*у = 256*у + 64*у = у<<8 + у<<6

video_buffer[((у<<8) + (у<<6)) + х] = color;

} // конец Plot Pixel_Fast

/////////////////////////////////////////////////////////////

void _interrupt _far Star_Int(void)

{

// эта функция создает иллюзию трехмерного звездного неба,

// наблюдаемого из иллюминатора космического корабля Enterprise

// Замечание: эту функцию следует исполнять не реже одного раза

// в 55.4мс, иначе не избежать перезагрузки!

int index;

// проверка, надо ли нам инициализировать звездное поле

// (то есть первый ли раз вызывается функция)

if (star_first)

{

// сброс флага первого вызова

star_first=0;

// инициализация всех звезд

for(index=0; index
{

// инициализация цвета, скорости и координаты каждой звезды

stars[index].х = rand()%320;

stars[index].у = rand()%180;

// выбор плоскости для звезды switch(rand()%3)

{ case 0:

// плоскость 1 - наиболее удаленная плоскость

{

// установка скорости и цвета звезды

stars[index].vel = 2;

stars[index].color = 8;

} break;

case 1: // плоскость 2 - плоскость, расположенная

// посередине

{

stars[index].vel = 4;

stars [index] .color =7;

} break;

case 2: // плоскость 3 -самая ближняя плоскость

{

stars[index].vel = 6;

stars[index].color = 15;

} break;

} // конец оператора switch

} // конец цикла

} // конец оператора if

else

{ // не первый вызов функции, поэтому производим рутинные

// действия: стирание, перемещение, рисование

for (index=0; index
{ // стирание

Plot_Pixel_Fast(stars[index].х,stars[index]-у,0);

// перемещение

if ((stars[index].x+=stars[index].vel) >=320 ) stars[index].х = 0;

// рисование

Plot_Pixel_Fast(stars[index],x,stars[index],y, stars[index].color);

} // конец цикла

} // конец else

} // конец Star_Int

// ОСНОВНАЯ ПРОГРАММА ///////////////////////////////////

void main(void)

{ int num1, num2,с ;

_setvideomode(_MRES256COLOR) ;

// установка обработчика прерывания

Old_Isr = _dos_getvect(TIME_KEEPER_INT) ;

_dos_setvect(TIME_KEEPER_INT, Star_Int);

// ожидание нажатия клавиши пользователем

_settextposition(23,0);

printf("Hit Q - to quit.");

printf("\nHit E - to see something wonderful...");

// чтение символа

с = getch();

// хочет ли пользователь рискнуть?

if (c=='e')

{

printf("\nLook stars in DOS, how can this be ?") ;

exit(0);

// выход без восстановления старого обработчика прерывания

} // конец оператора if

/ восстановление старого обработчика прерывания

_dos_setvect(TIME_KEEPER_INT, 01d_Isr);

_setvideomode(_DEFAULTMODE) ;

}// конец функции main

Пример обработчика прерывания № 2 - Ловим нажатия клавиш!

Как мы узнали в третьей главе, «Основы работы с устройствами ввода», мы можем использовать BIOS для чтения нажатия клавиши путем проверки значения скан-кода. Это отличный способ, и он вполне применим для большинства случаев, но что делать, если вы хотите одновременно нажать две клавиши? (Примером такой ситуации может служить момент, когда вы одновременно нажимаете клавиши «стрелка вверх» или «стрелка вниз» и «стрелка вправо» чтобы изменить направление движения на диагональное). Единственный способ обработать подобную ситуацию в программе требует более тонкой работы с клавиатурой. BIOS по сравнению со стандартными функциями Си просто дает нам еще один уровень управления, но если мы хотим добиться необходимой для профессионально написанных компьютерных игр функциональности, нам следует глубже, глубже и еще глубже разобраться с клавиатурой.

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

При нажатии клавиши клавиатурой посылается код нажатия, а при отпускании — соответствующий код отпускания. Эти коды похожи на уже известный вам скан-код. Различие состоит в том, что при отпускании клавиши, каким бы ни было значение кода нажатия, к нему всегда прибавляется 128.

Чтобы наша программа смогла взять контроль за клавиатурой на себя, мы выгрузим обработчик клавиатуры операционной системы DOS и установим наш собственный драйвер. Он будет получать коды клавиш независимо от того, что произошло — нажатие или отпускание, а затем сохранять эту информацию в глобальной переменной, к которой имеет доступ наша Си-программа. При таком подходе функция нашей программы сможет использовать текущее значение этой переменной для выяснения того, какие клавиши в данный момент нажаты, а какие отпущены. Разобравшись что к чему, функция сведет эту информацию в таблицу. Клавиатурное прерывание имеет номер 0х09. Все, что нам требуется сделать, это написать и установить соответствующую процедуру обработки данного прерывания.

Прежде чем мы этим займемся, вспомним адреса клавиатурных портов ввода/вывода и их функции. Собственно клавиатурный порт ввода/вывода находится по адресу 60h, а регистр, управляющий клавиатурой — по адресу 61lh Эти порты находятся на микросхеме PIA (Peripheral Interface Adapter). Кроме всего прочего, нам еще потребуется выполнить определенные процедуры перед вызовом нашего обработчика прерывания и после его завершения. Общий порядок всех действий таков:

  1. Войти в процедуру обслуживания прерывания. Это происходит при каяждом нажатии клавиши.
  2. Прочитать из порта ввода/вывода 60h код клавиши и поместить его в глобальную переменную для последующей обработки программой или обновления содержимого таблицы, в которой хранится информация о нажатых клавишах.
  3. Прочитать содержимое управляющего регистра из порта ввода/вывода 61h и выполнить над ним логическую операцию OR с числом 82h.
  4. Записать полученный результат в порт регистра управления 61h.
  5. Выполнить над содержимым управляющего регистра логическую операцию AND с числом 7Fh. Это сбрасывает состояние клавиатуры, давая ей понять что нажатие на клавишу обработано и мы готовы к считыванию информации о нажатии других клавиш.
  6. Сбросить состояние контроллера прерываний 8259. (Без этого можно и обойтись, однако лучше подстраховаться). Для этого следует записать в порт 20h число 20h. Забавное совпадение, не правда ли?
  7. Выйти из обработчика прерывания.

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

Листинг 12.7. Киберточка (CYBER.C)._____________________

// ВКЛЮЧАЕМЫЕ ФАЙЛЫ///////////////////////////////////////

#include

#include

#include

#include

#include

#include

// ОПРЕДЕЛЕНИЯ //////////////////////////////////////////

#define KEYBOARD_INT 0х09

#define KEY_BUFFER 0х60

#define KEY_CONTROL 0х61

#define INT_CONTROL 0х20

// коды нажатия и отпускания для клавиш со стрелками

#define MAKE_RIGHT 77

#define MAKE_LEFT 75

#define MAKE_UP 72

#define MAKE_DOWN 80

#define BREAK__RIGHT 205

#define BREAK_LEFT 203

#define BREAK_UP 200

#define BREAK_DOWN 208

// индексы в таблице состояния клавиш со стрелками

#define INDEX_UP 0

#define INDEX_DOWN 1

#define INDEX_RIGHT 2

#define INDEX_LEFT 3

// ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ ////////////////////////////////

void (_interrupt _far *01d_Isr)(void);

// хранит старый обработчик прерывания

unsigned char far *video_buffer = (char far *)0xA0000000L;

// указатель на видеобуфер

int raw_key; // необработанные данные от клавиатуры

int key_table[4] = {0,0,0,0};

// таблица состояний клавиш со стрелками

// ФУНКЦИИ //////////////////////////////////////////////

void Plot_Pixel_Fast(int x,int y,unsigned char color)

{

// эта функция рисует точку заданного цвета несколько быстрее,

// чем обычно за счет применения операции сдвига вместо операции

// умножения

// используем известный факт, что 320*у = 256*у + 64*у = у<<8 + у<<6

video_buffer[((y<<8) + (у<<6) ) + х] = color;

} // конец Plot_Pixel_Fast

/////////////////////////////////////////////

void Fun_Back(void)

{

int index;

// несомненно запоминающийся рисунок фона

_setcolor(1) ;

_rectangle(_GFILLINTERIOR, 0,0,320,200);

_setcolor{15) ;

for (index=0; index<10; index++)

{

_moveto(16+index*32,0);

_lineto(16+index*32,199);

} // конец цикла

for (index=0; index<10; index++)

{

_moveto(0,10+index*20) ;

_lineto(319,10+index*20);

} // конец цикла

} // конец Fun Back ///////////////////////////////////////

void _interrupt _far New_Key_Int(void)

{

// я в настроении немного попрограммировать на ассемблере!

_asm{

sti ; разрешаем прерывания

in al,KEY BUFFER ; получаем нажатую клавишу

xor ah,ah ; обнуляем старшие 8 бит регистра АХ

mov raw_key, ax ; сохраняем код клавиши

in al,KEY_CONTROL ; читаем управляющий регистр

or al,82h ; устанавливаем необходимые биты для сброса FF

out KEY_CONTROL,al ; посылаем новые данные в управляющий регистр

and al,7fh

out KEY_CONTROL,al ; завершаем сброс

mov al,20h

out INT CONTROL,al; завершаем прерывание

} // конец ассемблерной вставки

// теперь вернемся К Си, чтобы изменить данные

// в таблице состояния клавиш со стрелками

// обработка нажатой клавиши и изменение таблицы

switch(raw_key)

{

case MAKE_UP: // нажатие стрелки вверх

{

key_table[INDEX_UP] = 1;

} break;

case MAKE_DOWN: // нажатие стрелки вниз

{

key_table[INDEX_DOWN] = 1;

) break;

case MAKE_RIGHT: // нажатие' стрелки вправо

{

key_table[INDEX_RIGHT] = 1;

} break;

case MAKE_LEFT: // нажатие стрелки влево

{

key__table[INDEX_LEFT] = 1;

} break;

case BREAK_UP: // отпускание стрелки вверх

{

key_table[INDEX_UP] = 0;

} break;

case BREAK DOWN: // отпускание стрелки вниз

{

key_table[INDEX_DOWN] = 0;

} break;

case BREAK_RIGHT; // отпускание стрелки вправо

{

key_table[INDEX_RIGHT] = 0;

} break;

case BREAK_LEFT: // отпускание стрелки влево

{

key_table[INDEX_LEFT] = 0;

} break;

default: break;

} // конец оператора switch

} // конец New_Key_Int

// ОСНОВНАЯ ПРОГРАММА ////////////////////////////////

void main(void)

{

int dопе=0, x=160, y=100;// флаг выхода и координаты точки

//установка видеорежима 320x200x256

_setvideomode(_MRES256COLOR) ;

Fun_Back(); // оригинальная картинка, не так ли?

printf("\nPress ESC to Exit.");

// установка нового обработчика прерывания

Old_Isr = _dos_getvect(KEYBOARD_INT) ;

_dos_setvect(KEYBOARD_INT, New_Key_Int);

// основной цикл

while(!done)

{

_settextposition(24,2) ;

printf("raw key=%d ",raw_key);

// смотрим в таблицу и перемещаем маленькую точку

if (key_table[INDEX_RIGHT]) х++;

if (key_table[INDEX_LEFT]) х--;

if (key_table[INDEX_UP]) y--;

if (key_table[INDEX_DOWN]) y++;

// рисуем киберточку

Plot_Pixel_Fast(x,y,10);

// Наша клавиша завершения. Значение кода нажатия ESC равно 1

if (raw_key==1) done=l;

} // конец while

// восстановление старого обработчика прерывания

_dos_setvect(KEYBOARD_INT, Old_Isr) ;

_setvideomode (_DEFAULTMODE) ;

} // конец функции main

Уфф... Вот и все, ребята!

Заключение

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

Мы научились писать обработчики прерываний на языке Си и регистрировать их в таблице векторов прерываний. Кроме того, мы узнали много полезного о том, как при помощи автономных функций и функций ответа можно организовать на персональном компьютере синхронизацию программы и обработку наступления определенных событий.

И, в конце концов, мы использовали полученные знания Для написания нескольких программ, которые брали на себя управление компьютером - одна из этих программ даже не пожелала вернуть управление обратно!