Игра Tombstone
Мы начали с того, что вообще ничего не знали о VGA-карте. Сейчас мы знаем очень много. Поэтому я думаю, что было бы лучше, если бы вы написали маленькую двухмерную игру для закрепления пройденного материала. Чтобы вам в этом помочь, я написал демонстрацию и назвал ее Tombstone.
В этой демонстрации маленький ковбой ходит по городу с различной скоростью. У вас есть все инструменты, чтобы «дать» ему пистолет и "научить" стрелять. В PCX-файле на дискете вы найдете для этого все необходимые картинки. Вся программа за исключением функции Set_Mode() дана в Листинге 5.16. Прилинкуйте Set_Mode(), когда будете создавать исполняемый файл.
Листинг 5.16. Tombstone (TOMB.С).
// ВКЛЮЧАЕМЫЕ
ФАЙЛЫ
/////////////////////////////////////////////
#include <io.h>
#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <dos.h>
#include <bios.h>
#include <fcntl.h>
#include <memory.h>
#include <math.h>
#include <string.h>
// ОПРЕДЕЛЕНИЯ //////////////////////////////////////////////////
#define ROM_CHAR_SET_SEG
0xF000 // сегмент описания символов в ПЗУ
#define ROM_CHAR_SET_OFF
0xFA6E // смещение, соответствующее
// описанию первого символа
#define VGA256 0х13
#define TEXT_MODE 0х03
#define PALETTE_MASK ОхЗC6
#define PALETTE_REGISTER_RD 0x3C7
#define PALETTE_REGISTER_WR 0x3C8
#define PALETTE_DATA 0x3C9
#define SCREEN_WIDTH (unsigned int)320
#define SCREEN_HEIGHT (unsigned int)200
#define CHAR_WIDTH 8
#define CHAR_HEIGHT 8
#define SPRITE_MIDTH 24
#define SPRITE_HEIGHT 24
#define MAX_SPRITE_FRAMES 16
#define SPRITE_DEAD 0
#define sprite_alive 1
#define SPRITE_DYING 2
// СТРУКТУРЫ
ДАННЫХ ////////////////////////////////////////
typedef struct RGB_color_typ
{
unsigned char red; // красная составляющая цвета (0-63)
unsigned char green; // зеленая составляющая цвета (0-63)
unsigned char blue; // синяя составляющая цвета (0-63)
} RGB_color, *RGB_color_ptr;
typedef struct pcx_header_typ
{ char manufacturer;
char version;
char encoding;
char bits_per_pixel;
int x,y;
int width,height;
int horz_res;
int vert_res;
char ega_palette[46];
char reserved;
char num_color_planes;
int bytes_per_line;
int palette_type;
char padding[58];
} pcx_header, *pcx_header_ptr;
typedef struct pcx_picture_typ
{
pcx_header header;
RGB_color palette[256];
char far *buffer;
} pcx_picture, *pcx_picture_ptr;
typedef struct sprite_typ
{
int x,y; // текущая позиция спрайта
int x_old,y_old; // предыдущая позиция спрайта
int width,height; // размеры спрайта
int anim_clock; // время анимации
int anim_speed; // скорость анимации
int motion_speed; // скорость движения
int motion_clock; // время движения
char far *frames[MAX_SPRITE__FRAMES]; // массив указателей
// на образы
int curr_frame; // отображаемый фрейм
int num_frames; // общее число фреймов
int state; // статус спрайта
char far *background; // фон под спрайтом
}sprite, *sprite_ptr;
// ВНЕШНИЕ ФУНКЦИИ /////////////////////////////////
extern Set_Mode(int mode);
// ПРОТОТИПЫ ///////////////////////////////////////
void Set_Palette_Register(int index, RGB_color_ptr color);
void Plot_Pixel_Fast(int x,int y,unsigned char color);
void PCX_Init(pcx_picture *image);
void PCX_Delete(pcx_picture *image);
void PCX_Load(char *filename, pcx_picture_ptr image, int enable_palette) ;
void PCX_Show_Buffer(pcx_picture_ptr image);
// ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ //////////////////////////////
unsigned char far *video_buffer = (char far *) 0xA0000000L;
unsigned int far *video_buffer_w = (int far *)0xA0000000L;
unsigned char far *rom_char_set = (char far *)0xF000FA6EL;
// ФУНКЦИИ ////////////////////////////////////////////
void Blit_Char(int xc,int yc,char c,int color) {
// эта функция отображает символ на экране
// используя описание
// символов размером 8х8 точек, хранящееся в ПЗУ
int offset,х,у;
unsigned char data;
char far *work_char;
unsigned char bit_mask = 0х80;
// вычисляем смещение описания символа в ПЗУ
work_char = rom_charset + с * CHAR_HEIGHT;
// вычисляем смещение символа в видеобуфере
offset = (ус << 8} + (ус << 6) + хс;
for (у=0; y<CHAR_HEIGHT; y++)
{
// сбросить битовую маску
bit_mask = 0х80;
for (x=0; x<CHAR_WIDTH; x++)
{ // если бит равен 1, рисуем пиксель
if ((*work_char & bit_mask))
video_buffer[offset+x] = color;
// сдвигаем
маску
bit_mask = (bit_mask>>1);
} // конец отрисовки строки
// переходим к следующей строке
offset += SCREEN_WIDTH;
work_char++;
} // конец рисования
} // конец
функции
//////////////////////////////////////////////////////////
void Blit_String(int x,int y,int color, char *string)
{ // функция отображает на экране передаваемую строку символов
// Расстояние между символами строки постоянно
// Для отображения символов вызывается функция blit_char
int index;
for (index=0; string[index]!=0; index++)
{
Blit_Char(x+(index<<3) ,y, string[index],color);
} // конец цикла for
} // конец функции
///////////////////////////////////////////////////////
void Delay(int t)
{
float x = 1;
while(t—>0)
x=cos(x);
} // конец функции
///////////////////////////////////////////////////////
void Set_Palette_Register(int index, RGB_color_ptr color) {
// функция устанавливает элемент таблицы цветов задаваемый
// параметром index. Значения компонент цвета задаются полями
// структуры color.
// указываем VGA-карте, что мы будем изменять регистр палитры
_outp(PALETTE_MASK,Oxff);
// указываем номер изменяемого регистра
_outp(PALETTE_REGISTER_WR, index);
// теперь меняем значения компонентов. Каждый раз используется
// один и тот же порт
_outp(PALETTE_DATA,color->red);
_outp(PALETTE_DATA,color->green);
_outp(PALETTE_DATA,color->blue);
} // конец функции
///////////////////////////////////////////////////////
void PCX_Init(pcx_picture_ptr image)
{
// функция выделяет память для загрузки PCX-файла
if ((image->buffer = (char far *)malloc (SCREEN_WIDTH * SCREEN_HEIGHT +1)));
printf("\ncouldn't allocate screen buffer");
} // конец функции
///////////////////////////////////////////////////////
void Plot_Pixel_Fast(int x,int y,unsigned char color)
{
// функция отображает на экране точку заданного цвета
// вместо умножения используется сдвиг
// пользуемся тем, что 320*у=256*у+64*у=у<<8+у<<6
video_buffer[ ((у<<8) + (у<<6)) + х] = color;
} // конец функции ///////////////////////////////////////////////////////////////////////////
void PCX_Delete(pcx_picture_ptr image)
{ // функция освобождает память, выделенную для загрузки PCX-файла
_ffree (image->buffer);
} // конец
функции
//////////////////////////////////////////////////////////
void PCX_Load(char *filename, pcx_picture_ptr image, int enable_palette}
{ // функция загружает PCX-файл и заполняет поля структуры
// pcx_picture, включая изображение (после декомпрессии),
// заголовок и палитру
FILE *fp;
int num_bytes,index;
long count;
unsigned char data;
char far *temp_buffer;
// открыть
файл
fp = fopen(filename,"rb");
// загрузить
заголовок
temp_buffer = (char far *)image;
for (index=0; index<128; index++)
{
temp_buffer[index] = getc(fp);
} // конец цикла for
// загрузить данные и декодировать их в буфере
count=0;
while(count<=SCREEN_WIDTH * SCREEN_HEIGHT)
{ // получить первую часть данных
data = getc(fp);
// это RLE?
if (data>=192 && data<=255)
{ // сколько байт сжато?
num_bytes = data-192;
// получить значение цвета для сжатых данных
data = getc(fp);
// заполняем буфер полученным цветом
while(num_bytes-->0)
{
image->buffer[count++] = data;
} // конец цикла while
} // конец обработки сжатых данных
else
{
// поместить значение цвета в очередную позицию
image->buffer[count++] = data;
} // конец обработки несжатых данных
} // конец цикла while
// перейти в позицию, не доходя 768 байт от конца файла
fseek(fp,-768L,SEEK_END) ;
// загрузить
палигру
for (index=0; index<256; index++)
{
// красная
составляющая
image->palette[index].red = (getc(fp) >> 2);
// зеленая
составляющая
image->palette[index].green = (getc(fp) >> 2);
// синяя
составляющая
image->palette[index].blue = (getc(fp) >> 2) ;
} // конец
цикла for
fclose(fp);
// если установлен флаг enable_palette, установить новую палитру
if (enable_palette)
{
for (index=0; index<256; index++)
{
Set_Palette_Register(index,
(RGB_color_ptr)&image->palette[index]);
} // конец цикла for
} // конец установки палитры
} // конец
функции
//////////////////////////////////////////
void PCX_Show_Buffer (pcx_picture_ptr image)
{ // функция копирует буфер, содержащий изображение из PCX-файла,
// в видеопамять
_fmemcpy(char far *)video_buffer,
(char far *) image->buffer,SCREEN_WIDTH*SCREEN__HEIGHT);
} // конец
функции
////////////////////////////////////////////////////
void Sprite_Init(sprite_ptr sprite, int x,int y, int ac, int as,int mc,int ms)
{
// функция инициализирует спрайт
int index;
sprite->x = x;
sprite->y = у;
sprite->x_old = x;
sprite->y_old = у;
sprite->width = SPRITE_WIDTH;
sprite->height = SPRITE_HEIGHT;
sprite->anim_clock = ac;
sprite->anim_speed = as;
sprite->motion_clock = me;
sprite->motion_speed = ms;
sprite->curr frame = 0;
sprite->state = SPRITE_DEAD;
sprite->num_frames = 0;
sprite->background = (char far *)malloc (SPRITE_WIDTH * SPRITE_HEIGHT+1);
// устанавливаем все указатели в значение NULL
for (index=0; index<MAX_SPRITE_FRAMES; index++) sprite->frames[index] = NULL;
} // конец
функции
////////////////////////////////////////////////////////
void Sprite_Delete(sprite_ptr sprite)
{ // функция освобождает всю связанную со спрайтом память
int index;
_ffree(sprite->background) ;
// освобождаем память, выделенную под кадры анимации
for (index=0; index<MAX_SPRITE_FRAMES; index++) _ffree(sprite->frames(index]);
} // конец
функции
///////////////////////////////////////////////////////
void PCX_Grap_Bitmap (pcx_picture_ptr image, sprite_ptr sprite, int sprite_frame, int grab_x, int grab_y)
{
// функция выделяет один кадр из буфера PCX-файла.
// Предполагается, что изображение размером 320х200 пикселей
// в действительности представляет собой массив 12х8 изображений
// каждое размерностью по 24х24 пикселя
int x_off,y_off, x,y, index;
char far *sprite_data;
//вначале выделяем память для размещения спрайта
sprite->frames[sprite_frame] = (char far *)malloc (SPRITE_WIDTH * SPRITE_HEIGHT);
// создаем альтернативный указатель для ускорения доступа
sprite_data = sprite->frames[sprite_frame];
// загружаем битовый образ в выделенную область памяти
// вначале вычисляем, какое из изображений копировать.
// Помните: в действительности PCX-файл представляет собой массив
// из отдельных элементов размером 24х24 пикселя.
// Индекс (0,0) соответствует левому верхнему изображению,
// (11,7) - правому нижнему.
x_off
= 25 * grab_x + 1;
y_off = 25 * grab_y + 1;
// вычисляем начальный адрес
y_off
= y_off * 320;
for (y=0; y<SPRITE__HEIGHT; y++)
{
for (x=0; x<SPRITE_WrETH; x++)
{
// берем очередной байт и помещаем его в текущую позицию буфера
sprite_data[y*24 + х] = image->buffer [y_off + х_off + х];
} // конец копирования строки
// переходим к следующей строке y_off+=320;
} // конец копирования
// увеличиваем счетчик кадров sprite->num_frames++;
} // конец функции
//////////////////////////////////////
void Behind_Sprite(sprite_ptr sprite)
{
// функция сохраняет содержимое видеопамяти в той области,
// куда будет выведен спрайта
char far *work back;
int work_offset=0,offset,y;
// создаем альтернативный указатель для ускорения доступа
work_back = sprite->background;
// вычисляем смещение в видеопамяти
offset = (sprite->y << 6) + (sprite->y << 6) + sprite->x;
for (y=0; y<SPRITE_HEIGHT; y++)
{
// копируем очередную строку видеопамяти в буфер
_fmemcpy((char far *)&work_back[work_offset], (char far *)&video_buffer[offset], SPRITE_WIDTH);
// переходим к следующей строке
offset += SCREEN_WIDTH;
work_offset += SPRITE_WIDTH;
} // конец цикла for
} // конец функции
///////////////////////////////////////////
void Erase_Sprite(sprite_ptr sprite)
{ // функция восстанавливает фон, сохраненный перед выводом спрайта
char far *work_back;
int work_offset=0,offset,y;
// создаем альтернативный указатель для ускорения доступа
work_back = sprite->background;
// вычисляем смещение в видеобуфере
offset = (sprite->y_old << 8) + (sprite->y_old << 6} + sprite->x_old;
for (y=0; y<SPRITE_HEIGHT; y++)
{
// копируем в видеопамять очередную строку буфера
_fmemcpy((char far *)&video_buffer [offset],(char far *)&work_back[work_offset], SPRITE_WIDTH);
// перейти к следующей строке
offset += SCREEN_WIDTH;
work_offset += SPRITE_WIDTH;
} // конец цикла for
} // конец функции
///////////////////////////////////////////////////////
void Draw_Sprite(sprite_ptr sprite)
{
// функция, рисующая спрайт на экране вместо умножения
// используется
сдвиг
char far *work sprite;
int work_offset=0,offset,x,y;
unsigned char data;
work sprite = sprite->frames[sprite->curr_frame];
// вычислить смещение спрайта в видеобуфере
offset = (sprite->y << 8) + (sprite->y << 6) + sprite->x;
for (y=0; y<SPRITE_HEIGHT;y++)
{
for (x=0; X<SPRITE_WIDTH; X++)
{
// проверяем, не является ли пиксель "прозрачным" (с кодом 0),
// если
нет - рисуем
if ((data=work_sprite[work_offset+x]))
video_buffer[offset+x] = data;
} // конец вывода строки
// перейти к следующей строке в видеобуфере и буфере спрайта
offset += SCREEN_WIDTH;
work_offset += SPRITE_WIDTH;
} //конец цикла no Y
} // конец функции
// ОСНОВНАЯ ПРОГРАММА /////////////////////////////////////////
void main(void)
{
long index,redraw;
RGB_color color;
int frame_dir = 1;
pcx_picture town, cowboys;
sprite cowboy;
// установить видеорежим 320х200х256
Set_Mode(VGA256);
// установить глобальные указатели на область памяти экрана
Set_Screen_Pointers();
// загрузить
фон
PCX_Init((pcx_picture_ptr)&town) ;
PCX_Load( "town. pox", (pcx_picture_ptr)&town, 1) ;
PCX_Show_Buffer((pcx_picture_ptr)&town);
PCX_Delete((pcx_picture_ptr)&town);
// вывести на экран заставку игры
Blit_String(128, 24,50, "TOMBSTONE");
// загрузить
образы
PCX_Init((pcx_picture_ptr)&cowboys) ;
PCX_Load("cowboys.pcx", (pcx_picture_ptr) &cowboys,0) ;
// извлечь все образы из PCX-файла
Sprite_Init((sprite_ptr)&cowboy,SPRITE_WIDTH,100,0,7,0,3) ;
PCX_Grap_Bitmap ((pcx_picture_ptr) &cowboys, (sprite_ptr) &cowboy, 0, 0, 0);
PCX Grap_Bitmap( (pcx_picture_ptr) &cowboys,
(sprite_ptr)&cowboy,1,1,0) ;
PCX_Grap_Bitmap((pcx_picture_ptr)&cowboys,
(sprite_ptr)&cowboy,2,2, 0) ;
PCX_Grap_Bitmap ((pcx_picture_ptr)&cowboys,
(sprite_ptr)&cowboy,3,1,0) ;
// очистить
память, выделенную
для загрузки PCX-файла PCX_Delete((pcx_picture_ptr)&cowboys);
Behind_Sprite((sprite_ptr)&cowboy);
Draw_Sprite ((sprite_ptr) &cowboy);
// главный
цикл
cowboy.state = SPRITE_ALIVE;
while
(!kbhit()) {
redraw = 0; // используется как флаг необходимости перерисовки
if (cowboy.state==SPRITE_ALIVE)
{
//не пора ли менять кадр?
if (++cowboy.anim_clock > cowboy.anim_speed)
{
// сбрасываем счетчик времени
cowboy.anim_clock
= 0;
if (++cowboy.curr_frame >= cowboy. num_frames)
{
cowboy.curr_frame = 0;
} // конец обработки достижения последнего кадра
redraw=1;
} // конец смены кадра
// проверяем не пора ли передвигать спрайт
if (++cowboy.motion_clock > cowboy.motion_speed)
{
// переустановить счетчик движения
cowboy.motion clock
=0;
// сохранить старую позицию
cowboy.x_old == cowboy.x;
redraw =1;
//передвинуть спрайт
if (++cowboy.x >= SCREEN_WIDTH-2*SPRITE_WIDTH)
{
Erase_Sprite((sprite_ptr)&cowboy);
cowboy.state = SPRITE_DEAD;
redraw = 0;
} // конец обработки достижения последнего кадра
} // конец обработки движения спрайта
} // конец обработки ситуации "ковбой жив"
else
{ // пытаемся "оживить" ковбоя
if (rand()%100 == 0 )
{
cowboy.state = SPRITE_ALIVE;
cowboy.x = SPRITE_WIDTH;
cowboy.curr frame = 0;
cowboy.anim.speed = 3 + rand()%6;
cowboy.motion_speed = 1 + rand()%3;
cowboy.anim_clock = 0;
cowboy.motion_clock = 0;
Behind_Sprite{(sprite_ptr)&cowboy);
}
} // конец процесса "оживления"
// теперь состояние спрайта изменено
if (redraw)
{
// удалить спрайт в старой позиции
Erase_Sprite((sprite_ptr)&cowboy) ;
// сохранить фон в новой позиции
Behind_Sprite((sprite_ptr)&cowboy) ;
// нарисовать спрайт в новой позиции
Draw_Sprite((sprite_ptr)&cowboy);
// обновить старые координаты
cowboy.x_old = cowboy.x;
cowboy.у_old = cowboy.у;
} // конец перерисовки
Delay(1000);
} // конец главного игрового цикла
for(index=0; index <300000;index++,Plot_Pixel_Fast(rand()%320,
rand()%200,0));
//Перейти обратно в текстовый режим
Set_Mode(TEXT_MODE);
}//Конец функции main