Школа создателей компьютерных игр

BannerDrive.ru
[Главная] [С чего начать?] [Дистанционное обучение] [Статьи] [Обучалки] [Книги] [Софт] [Форум] [Ссылки] [О сайте]

Лабиринт часть 2



Подпишитесь на рассылку "Создание компьютерных игр"

Рассылки Subscribe.Ru
Создание компьютерных игр

Рассылка выходит раз в месяц.


Понравился сайт? Узнайте, как помочь сайту.


Рекомендуемые книги


Андре Ламот.
Программирование трехмерных игр для Windows. Советы профессионала по трехмерной графике и растеризации


Андре Ламот.
Программирование игр для Windows. Советы профессионала


Проголосуйте за сайт в рейтинге GameTop!
(нажмите на кнопку рейтинга)

GameTop - рейтинг игровых ресурсов. Портал Rolemancer (www.rolemancer.ru)

Не забывайте, что результаты рейтинга обновляются раз в неделю. Пожалуйста, голосуйте почаще!


Статистика посещаемости

Rambler's Top100

Лабиринт | Все обучалки раздела | Стеки

Описание

Расширяется концепция, рассмотренная в обучалке "Лабиринт". Добавляется загрузка лабиринта из файла, точка выхода из лабиринта, и "монстры".

Скачать обучалку (Visual C++ 6)

Работа программы

Исходный код

Файл main.c
// Done by TheTutor -- 10/26/01
// Перевод © 2004 Евгений Казеко
// www.gamecoder.kazeko.com
// evgeniy@kazeko.com

/*      Это расширенная обучалка на основе идеи "лабиринт". На самом деле вы можете назвать
      ее игрой! Конечно, до чего-либо вроде NetHack еще далеко, но это начало в верном
      направлении.

      Итак, что же такое делает эта обучалка, чего не делала базовая обучалка "Лабиринт"?
      Вот список:

      1) Добавилась возможность загрузки "лабиринтов" из файла.
      2) Лабиринт можно завершить. Просто дойдите до "Е" и вы выиграли игру.
      3) Теперь в игре есть монстры, случайно двигающиеся по лабиринту. Коснитесь
            одного из них и игра окончится.

      Эта обучалка отличается от других в том, что ее структура является "хорошим
      примером структуры для игры". Нельзя сказать что эта структура идеальна или
      даже действительно хороша, но она определенно выше среднего. Я упоминаю некоторые
      сложности в конце этого файла. И все же, если вы задумаетесь над тем "Как же мне
      следует структурировать мою программу для игры, которую я собираюсь написать?", вы
      сможете использовать эту обучалку как пример.
*/

#include "maze.h"
#include <conio.h>; // Для использования _kbhit()

int main()
{ 
      if(InitGame() == false)
            return 1;

      // Пока игра НЕ окончена
      while(isGameOver() == false)
      {
            UpdatePlayer();
            UpdateMonsters();
            DrawScreen();

            CheckGameState();
      }

      FreeMem(); // Освобождаем память

      // Это просто говорит "пока клавиша НЕ нажата" продолжать выполнение цикла 
      // (ничего не делать). Мы ставим это сюда, чтобы игра не закрывалась неожиданно
      // после ее окончания (т.е. вам нужно будет нажать клавишу чтобы закрыть окно).
      while(!_kbhit())
            ;

      return 0;
}

/*      Игра окончена... почти

      Теперь, когда вы посмотрели весь код (вы ведь просмотрели код, верно?), я поговорю
      о некоторых вещах, которые могут быть улучшены.

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

      1)      Если вы захотите изменить ее, вы будете должны передать ее как указатель.
      2)      Когда вы передаете ее как указатель, это получается быстрее. Помните, что когда вы
            передаете переменную (любую) в функцию, функция делает локальную копию этой переменной.
            Поэтому если нам нужно передать в функцию GAMEDATA, функция должна будет сделать
            локальные копии всех переменных структуры GAMEDATA. Но если мы передадим ее как указатель,
            все что будет передаваться в функцию - это 4 байта информации (указатель на GAMEDATA, 
            так как функция не будет делать локальные копии переменных, потому что указатель "указывает"
            на конкретную переменную).
            А что если вы не хотите менять переменную, но случайно меняете? Есть ли способ передать
            указатель, чтобы при этом переменная не менялась? Ответ - да! Просто поставьте впереди
            имени переменной (в объявлении функции) ключевое слово "const".

            Чтобы привязать все это к нашей обучалке - Если было бы нужно переписать функцию
            LoadMap(), ее объявление (расположенное в maze.h), выглядело бы вот так:


            void LoadMap(GAMEDATA *gamedata); 
            
            Функция взяла бы указатель на GAMEDATA, таким образом мы могли бы изменить переменную
            в функции LoadMap() и сохранить эти изменения при возврате из функции.

      Еще одна вещь, которую можно улучшить в игре состоит в следующем:
      Если вы приглядитесь, вы увидите, что когда ваш символ двигается, монстры двигаются с той
      же скоростью. Но если вы остановитесь, монстры немного замедляются. Может быть это и
      необязательно плохая вещь (возможно так вам понравится). Но в любом случае, это не так уж
      и сложно исправить, поэтому я оставляю это вам в качестве упражнения.
*/

/*
|  TheTutor  
|  thetutor@gametutorials.com 
|  © 2000-2002 GameTutorials 
*/
// Перевод © 2004 Евгений Казеко
// www.gamecoder.kazeko.com
// evgeniy@kazeko.com

Файл maze.h
#ifndef MAZE_H
#define MAZE_H

// Перевод © 2004 Евгений Казеко
// www.gamecoder.kazeko.com
// evgeniy@kazeko.com

#include <windows.h> // Для консольных функций
#include <stdio.h>

#define MAX_MONSTERS 10
#define WALL '#'    // Символ ASCII представляющий собой стену

// Направления, в которых может двигаться персонаж
#define NORTH 0
#define SOUTH 1
#define WEST 2
#define EAST 3
#define MAX_DIR 4 // Наибольшее число направлений, в которых можед двигаться персонаж

// Насколько далеко из левого верхнего угла окна (0,0) мы хотим сместиться
// при рисовании на экране.
#define INDENT_X 5
#define INDENT_Y 5

#define MAP_NAME "Map.txt"  
     // Имя нашего текстового файла с "картой лабиринта"
     // *Замечание* Maze.txt записывается следующим образом:
     // '0' представляет собой открытое место
     // '1' представляет собой стену

// В языке С нет типа данных "bool", поэтому мы определим наш собственный тип.
// Нам также нужно определить "true" и "false"
typedef unsigned char bool;
#define true 1
#define false 0


// Сперва нужно определить структуру для персонажей (игрока и монстров)
typedef struct _ENTITY
{
      int xPos; // координата X положения ENTITY
      int yPos; // координата Y положения ENTITY
 
      unsigned char dispChar; // Символ ASCII для ENTITY
      unsigned char color;    // Цвет и фон для символа "dispChar" ENTITY
} ENTITY;

// Эта структура будет хранить все наши игровые данные
typedef struct _GAMEDATA
{
      CHAR_INFO *screenBuff; // Это буфер (массив), который мы выделим для хранения всех
                                     // "данных о лабиринте", которые мы будем отображать на экран

      HANDLE outputHandle; // Это ссылка на то, где мы хотим отображать экранный буфер (экран)
      HANDLE inputHandle;  // Это ссылка на то, откуда мы хотим получать ввод пользователя (т.е. клавиатура)

      SMALL_RECT rect; // Определяет прямоугольную область на ЭКРАНЕ, где мы хотим рисовать

      // Ширина и высота нашего лабиринта
      int width;
      int height;

      // Координаты (x,y) "конечного положения" лабиринта.
      // Когда игрок касается этого места, он победил.
      int xEndPos;
      int yEndPos;

      bool gameover; // Принимает значение true, когда игра окончена, или игрок нажал клавишу "Esc"
} GAMEDATA;

// Функции инициализируют все переменные, нужные нам для игры. 
// Возвращают true при успешной инициализации, false при неудачной
bool InitGame();
bool InitHandles(); // Инициализирует ссылки
void InitPlayer();  // Иницализирует игрока
void InitMonsters(); // Инициализирует монстров

// Загружает карту (определенную MAP_NAME) в GAMEDATA
// Возращает true при успешной загрузке, false при неудаче
bool LoadMap();


// Эта функция берет ENTITY (игрока или монстра) и направление, в котором они ХОТЯТ двигаться.
// Если они могут двигаться в этом направлении (т.е. в этом направлении нет стены WALL),
// функция возвращает FALSE, в другом случае возвращает TRUE (они НЕ могут двигаться
// в указанном направлении, произошло столкновение).
bool Collide(ENTITY entity, char dir);

void UpdatePlayer();   // Если игрок двигался, обновляет его положение
void UpdateMonsters(); // Обновляет положение монстров при их перемещениях

void moveUp(ENTITY *entity);    // Двигает персонажа (игрока или монстра) на одну позицию вверх
void moveDown(ENTITY *entity);  // Двигает персонажа (игрока или монстра) на одну позицию вниз
void moveLeft(ENTITY *entity);  // Двигает персонажа (игрока или монстра) на одну позицию влево
void moveRight(ENTITY *entity); // Двигает персонажа (игрока или монстра) на одну позицию вправо

void DrawScreen(); // Рисует лабиринт и персонажей на экран

void CheckGameState(); // Определяет состояние игры - если игрок погиб или выиграл, 
                                 // функция устанавливает признак конца игры и печатает соответствующее сообщение
                                 

bool isGameOver(); // Возвращает true если игра окончена (или игрок вышел)
                           // В другом случае возвращает false

void FreeMem(); // Освобождает память, выделенную нашей игрой, когда игра заканчивается.

// Перевод © 2004 Евгений Казеко
// www.gamecoder.kazeko.com
// evgeniy@kazeko.com

#endif

Файл maze.c
#include "maze.h"

// Перевод © 2004 Евгений Казеко
// www.gamecoder.kazeko.com
// evgeniy@kazeko.com

// Здесь мы делаем глобальный (по отношению к этому .с файлу) экземпляр GAMEDATA

GAMEDATA gamedata;
ENTITY player;
ENTITY monster[MAX_MONSTERS];


// Эта функция загружает/создает все необходимые данные для игры.
// Если случается ошибка, функция возвращает false, в другом случае мы возвращаем true.
bool InitGame()
{
      // Сбрасываем генератор случайных чисел
      srand(GetTickCount());

      // Инициализируем устройства ввода и вывода
      if(InitHandles() == false)
            return false;

      // Загружаем и инициализируем карту
      if(LoadMap() == false)
            return false;

      InitPlayer();      // Инициализируем игрока
      InitMonsters(); // Инициализируем монстров
      
      // Инициализируем "прямоугольник", определяя где мы хотим рисовать в нашем окне
      gamedata.rect.Left = INDENT_X;
      gamedata.rect.Top = INDENT_Y;
      gamedata.rect.Right = INDENT_X + (gamedata.width - 1);
      gamedata.rect.Bottom = INDENT_Y + (gamedata.height - 1);

      while(1)
      {
            // Случайно определяем положение выхода из лабиринта
            gamedata.xEndPos = rand()%gamedata.width;
            gamedata.yEndPos = rand()%gamedata.height;
      
            // Если это место было открыто (т.е. не WALL), мы можем выйти из цикла
            if(gamedata.screenBuff[gamedata.xEndPos + gamedata.yEndPos * gamedata.width].Char.AsciiChar == ' ')
                  break;
      }

      DrawScreen(); // Рисуем весь экран (один раз)
            
      return true;  // Мы загрузились :)
}

// Инициализируем наши стандартные устройства ввода и вывода
bool InitHandles()
{
      // Получаем ссылку на стандартное устройство вывода
      gamedata.outputHandle = GetStdHandle(STD_OUTPUT_HANDLE);

            // Проверка на ошибки - Если мы не сможем получить ссылку на устройство вывода,
            // мы не сможем рисовать на экран, поэтому мы выходим.
            if(gamedata.outputHandle == NULL)
                  return false;

      // Получаем ссылку на стандартное устройство ввода
      gamedata.inputHandle = GetStdHandle(STD_INPUT_HANDLE);

            // Проверка на ошибки - Если мы не сможем получить ссылку на устройство ввода,
            // мы не сможем получать данные с клавиатуры, поэтому мы выходим.
            if(gamedata.inputHandle == NULL)
                  return false;

      return true; // Устройства инициализированы
}


// Инициализируем игрока
void InitPlayer()
{
      int index; // Используется для индексации массива "screenBuff"

      player.dispChar = (char)1; // Это код ASCII для улыбающегося лица
      player.color = BACKGROUND_BLUE | FOREGROUND_RED; // Игрок будет красный с синим фоном
                                                                             // (так же как и пол)

      while(1)
      {
            // Устанавливаем игрока на случайное место в лабиринте
            player.xPos = rand()%gamedata.width;
            player.yPos = rand()%gamedata.height;

            // Вычисляем индекс (т.е. положение в "screenBuff", где будет игрок,
            // предполагая, что это не стена).
            index = player.xPos + player.yPos * gamedata.width;

            // Если это место, которое мы случайно сгенерировали, открыто (т.е. это не стена)
            if(gamedata.screenBuff[index].Char.AsciiChar == ' ')
            {
                  // Ставим на него игрока
                  gamedata.screenBuff[index].Char.AsciiChar = player.dispChar;
                  gamedata.screenBuff[index].Attributes = player.color;
                        break; // и выходим из цикла
            }
      } // конец while(1)
}

// Инициализируем монстров
void InitMonsters()
{
      int index = 0; // Индекс в массиве монстров

      int scrBufIndex = 0; // Индекс массива "screenBuff"

      // Пока мы не установим все начальные позиции монстров
      while(index != MAX_MONSTERS)
      {
            // Устанавливаем символ для монстров
            // Устанавливаем цвет светло-зеленый (синий фон как и у пола)
            monster[index].dispChar = (char)235;
            monster[index].color = BACKGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY;

            // Определяем случайное положение монстра в лабиринте
            monster[index].xPos = rand()%gamedata.width;
            monster[index].yPos = rand()%gamedata.height;

            // Определяем индекс в экранном буфере
            scrBufIndex = monster[index].xPos + monster[index].yPos * gamedata.width;

            // Если случайно сгенерированое положение свободно (т.е. не стена),
            // то мы готовы установить следующего монстра
            if(gamedata.screenBuff[scrBufIndex].Char.AsciiChar == ' ')
            {
                  // Заполняем экранный буфер монстром
                  gamedata.screenBuff[scrBufIndex].Char.AsciiChar = monster[index].dispChar;
                  gamedata.screenBuff[scrBufIndex].Attributes = monster[index].color;
                  
                  index++; // Увеличиваем индекс, переходя к следующему монстру
            }
      } // конец while(index != MAX_MONSTERS)
} // конец InitMonsters()

// Эта функция загружает данные карты, определенной MAP_NAME (в maze.h), в структуру "gamedata"
bool LoadMap()
{
      FILE *file_ptr = NULL; // Указатель на файл карты
      
      // Эти переменные будут использоваться для индексирования screenBuff
      // *Помните*, хотя "screenBuff" и является одномерным массивом, мы
      // будем работать с ним, как с двумерным

      int x = 0;
      int y = 0;

      char temp; // Это будет временный символ - в нем будет храниться текущий символ, 
                     // прочитанный из файла
                    

      file_ptr = fopen(MAP_NAME,"r"); // Открываем файл с картой для чтения

            // Проверка на ошибку
            if(file_ptr == NULL) // Если мы не смогли открыть файл карты, возвращаем false
                  return false; 

      // Первая строка файла с картой содержит два целых числа.
      // Первое число это ширина карты
      // Второе число это высота карты

      fscanf(file_ptr,"%d",&(gamedata.width));  // Читаем ширину карты
      fscanf(file_ptr,"%d",&(gamedata.height)); // Читаем высоту карты

      fgetc(file_ptr); // Пропускаем "символ конца строки" (т.е. '\n')

      // Теперь мы можем создать массив CHAR_INFO, достаточно большой, чтобы сохранить карту
      gamedata.screenBuff = (CHAR_INFO*)malloc(sizeof(CHAR_INFO) * gamedata.width * gamedata.height);
      
            // Проверка на ошибку - нужно убедиться, что мы смогли выделить память
            if(gamedata.screenBuff == NULL)
            {
                  fclose(file_ptr); // Закрываем открытый файл
                        return false;
            }

      while(!feof(file_ptr)) // Продолжаем пока не достигнем конца файла с картой
      {
            temp = (char)fgetc(file_ptr); // Читаем следующий символ (char) в файле

            switch(temp)
            {
                  case '0': // Мы прочитали "открытое место" лабиринта

                        // Заполняем текущее положение в экранном буфере символом пробела (' ')
                        // синего цвета
                        gamedata.screenBuff[x + y * gamedata.width].Char.AsciiChar = ' ';
                        gamedata.screenBuff[x + y * gamedata.width].Attributes = BACKGROUND_BLUE;
                              break;

                  case '1':  // Мы прочитали "стену" лабиринта

                        // Заполняем текущее положение в экранном буфере символом, 
                        // определенным переменной WALL, и задаем ему зеленый цвет на синем фоне
                        gamedata.screenBuff[x + y * gamedata.width].Char.AsciiChar = WALL;
                        gamedata.screenBuff[x + y * gamedata.width].Attributes = BACKGROUND_BLUE | FOREGROUND_GREEN;
                              break;            
            }

            /*      А теперь довольно хитрое выражение, поэтому я объясню его.
                  Если мы прочитали "0" или "1", мы должны увеличить х, в другом случае - нет.
                  Поэтому в нашем выражении if мы используем логическое И (&&). Сперва мы
                  проверяем равна ли "temp" значениям '0' или '1' - если нет, то выражение if
                  закончено (т.е. х не будет увеличен). Если temp равно '0' или '1', будет
                  выполнена правая сторона И (&&) (и следовательно х будет увеличен).
                  И уже ЗАТЕМ, если х равен gamedata.width, будет выполнен код между {}.            */

            if((temp == '0' || temp == '1') && (++x == gamedata.width))
            {
                  x = 0;
                  y++; // Нам нужно увеличить y, чтобы мы "перешли на следующую строку" экранного буфера.
            }
      } // конец while 

      // Если мы добрались досюда, значит карта успешно загрузилась.
    // И теперь, все что нужно сделать - это закрыть файл, который мы открыли и вернуть true
   fclose(file_ptr);
            return true; // Все в порядке :)
} // конец bool LoadMap()


// Возвращает true, если "entity" не может двигаться в направлении, определенном "dir"
// В другом случае возвращает false ("entity" может двигаться в направлении "dir")
bool Collide(ENTITY entity, char dir)
{
      int tempX = entity.xPos; // Получаем координату "xPos" структуры entity
      int tempY = entity.yPos; // Получаем координату "yPos" структуры entity

      switch(dir)
      {
            case NORTH:

                  tempY--; // Двигаем tempY вверх на единицу, и таким образом, проверяем место
                               // к северу от "entity"

                  // Если место к северу от entity это стена
                  if(gamedata.screenBuff[tempX + tempY * gamedata.width].Char.AsciiChar == WALL)
                        return true;  // НЕ МОЖЕМ двигаться на север
                  else
                        return false; // МОЖЕМ двигаться на север

            case SOUTH:

                  tempY++; // Двигаем tempY вниз на единицу, проверяя место к югу от "entity"

                  // Если место к югу от entity это стена
                  if(gamedata.screenBuff[tempX + tempY * gamedata.width].Char.AsciiChar == WALL)
                        return true;  // НЕ МОЖЕМ двигаться на юг
                  else
                        return false; // МОЖЕМ двигаться на юг

            case WEST:

                  tempX--; // Двигаем tempX влево на единицу, проверяя место к западу от "entity"

                  // Если место к западу от entity это стена
                  if(gamedata.screenBuff[tempX + tempY * gamedata.width].Char.AsciiChar == WALL)
                        return true;  // НЕ МОЖЕМ двигаться на запад
                  else
                        return false; // МОЖЕМ двигаться на запад

            case EAST:

                  tempX++; // Двигаем tempX вправо на единицу, проверяя место к востоку от "entity"

                  // Если место к востоку от entity это стена
                  if(gamedata.screenBuff[tempX + tempY * gamedata.width].Char.AsciiChar == WALL)
                        return true;  // НЕ МОЖЕМ двигаться на восток
                  else
                        return false; // МОЖЕМ двигаться на восток

            // Мы можем двигаться ТОЛЬКО на север, юг, восток или запад, поэтому, если
            // передается какое-либо другое направление, просто возвращаем false
            default:
                  return false;

      } // конец switch(dir)
} // конец bool Collide(ENTITY entity, char dir)


// Обновляет положение игрока и записывает его в "screenBuff"
void UpdatePlayer()
{
      INPUT_RECORD inputRecord; // Мы будем заполнять эту структуру информацией
                                            // о пользовательском вводе (какую клавишу нажал игрок)

      DWORD eventsReadIn; // Эта переменная будет хранить число прочитанных событий
                                  // (оно нам не нужно, но мы должны передать его в функцию)

      // Сбрасываем буфер ввода (т.е. удалаем сохраненные в нем нажатия клавиш)
      FlushConsoleInputBuffer(gamedata.inputHandle);

      /*      А что же делает эта строка?
            Когда мы вызываем функцию ReadConsoleInput(), идущую несколькими строками ниже,
            она ЖДЕТ ввода с клавиатуры, поэтому если мы просто вызовем эту функцию,
            наша игра будет делать что-либо (т.е. монстры будут двигаться) только тогда, 
            когда будет нажата клавиша на клавиатуре. Поэтому, то, что делает функция ниже,
            это отслеживает буфер ввода в течении 200 милисекунд. Если обнаруживается какой-либо
            ввод, функция возвращает значение, отличное от WAIT_TIMEOUT. Если проходят 200 
            миллисекунд, и нет никакого ввода (т.е. игрок не двигается), функция WaitForSingleObject()
            возвращает значение WAIT_TIMEOUT, и мы просто выходим из нашей функции.
      */

      if(WAIT_TIMEOUT == WaitForSingleObject(gamedata.inputHandle,200))
            return;

      /*      Это функция, которая будет заполнять "inputRecord". Параметры:
            gamedata.inputHandle - ссылка на буфер ввода (т.е. клавиатуру)
            &inputRecord - адрес структуры типа INPUT_RECORD, которая будет заполняться "данными".
            1 - число структур INPUT_RECORD, которое следует прочитать
            &eventsReadIn - адрес переменной типа DWORD, содержит число успешно прочитанных
            переменных INPUT_RECORD.
      */
      ReadConsoleInput(gamedata.inputHandle, &inputRecord, 1, &eventsReadIn);

      // Если клавиша была нажата, а не отжата (bKeyDown позволяет избежать двойных перемещений для NT)
      if(inputRecord.EventType == KEY_EVENT && inputRecord.Event.KeyEvent.bKeyDown)
      {
            // В зависимости от кода прочитанной клавиши выполним действия
            switch(inputRecord.Event.KeyEvent.wVirtualKeyCode)
            {
                  case VK_UP: // Если была нажата стрелка вверх

                        // Если игрок НЕ столкнется со стеной при движении на север
                        if(Collide(player,NORTH) == false)
                              moveUp(&player); // Перемещаем игрока на север

                        break;

                  case VK_DOWN: // Если была нажата стрелка вниз

                        // Если игрок НЕ столкнется со стеной при движении на юг
                        if(Collide(player,SOUTH) == false)
                              moveDown(&player); // Перемещаем игрока на юг

                        break;

                  case VK_LEFT: // Если была нажата стрелка влево

                        // Если игрок НЕ столкнется со стеной при движении на запад
                        if(Collide(player,WEST) == false)
                              moveLeft(&player); // Перемещаем игрока на запад

                        break;                        

                  case VK_RIGHT: // Если была нажата стрелка вправо

                        // Если игрок НЕ столкнется со стеной при движении на восток
                        if(Collide(player,EAST) == false)
                              moveRight(&player); // Перемещаем игрока на восток

                        break;

                  case VK_ESCAPE:

                        gamedata.gameover = true;
                              break;

            } // конец switch(inputRecord.Event.KeyEvent.wVirtualKeyCode)
      } // конец if(inputRecord.EventType == KEY_EVENT)
} // конец UpdatePlayer()


// Мы будем перемещать монстров в случайном направлении
void UpdateMonsters()
{
      char dir; // Эта переменная будет хранить случайное направление движения
      int i = 0;

      for(; i < MAX_MONSTERS; i++)
      {
            dir = rand()%MAX_DIR;

            // Если мы МОЖЕМ двигаться в данном направлении
            if(Collide(monster[i],dir) == false)
            {
                  switch(dir)
                  {
                        case NORTH: 
                              moveUp(&(monster[i]));
                                    break;

                        case SOUTH:
                              moveDown(&(monster[i]));
                                    break;

                        case EAST:
                              moveRight(&(monster[i]));
                                    break;

                        case WEST: 
                              moveLeft(&(monster[i]));
                                    break;
                  
                  } // конец switch(dir)
            } // конец if(Collide(monster[i],dir) == false)
      } // конец for(; i < MAX_MONSTERS; i++)
} // конец void UpdateMonsters()


// Перемещаем ENTITY (игрок или монстр) на одну позицию на север
void moveUp(ENTITY *entity)
{
      // Индекс "screenBuff", где персонаж находится в настоящее время.
      int index = entity->xPos + entity->yPos * gamedata.width;

      // Устанавливаем на место, где находится персонаж, "символ пола"
      gamedata.screenBuff[index].Char.AsciiChar = ' ';
      gamedata.screenBuff[index].Attributes = BACKGROUND_BLUE;

      // Перемещаем персонажа вверх на одну позицию
      entity->yPos--;

      // Определяем новый индекс места, где стоит персонаж
      index = entity->xPos + entity->yPos * gamedata.width;

      // Рисуем персонажа там где он теперь стоит
      gamedata.screenBuff[index].Char.AsciiChar = entity->dispChar;
      gamedata.screenBuff[index].Attributes = entity->color;
}


// Перемещаем ENTITY (игрок или монстр) на одну позицию на юг
void moveDown(ENTITY *entity)
{
      // Индекс "screenBuff", где персонаж находится в настоящее время.
      int index = entity->xPos + entity->yPos * gamedata.width;

      // Устанавливаем на место, где находится персонаж, "символ пола"
      gamedata.screenBuff[index].Char.AsciiChar = ' ';
      gamedata.screenBuff[index].Attributes = BACKGROUND_BLUE;

      // Перемещаем персонажа вниз на одну позицию
      entity->yPos++;

      // Определяем новый индекс места, где стоит персонаж
      index = entity->xPos + entity->yPos * gamedata.width;

      // Рисуем персонажа там где он теперь стоит
      gamedata.screenBuff[index].Char.AsciiChar = entity->dispChar;
      gamedata.screenBuff[index].Attributes = entity->color;
}


// Перемещаем ENTITY (игрок или монстр) на одну позицию на запад
void moveLeft(ENTITY *entity)
{
      // Индекс "screenBuff", где персонаж находится в настоящее время.
      int index = entity->xPos + entity->yPos * gamedata.width;

      // Устанавливаем на место, где находится персонаж, "символ пола"
      gamedata.screenBuff[index].Char.AsciiChar = ' ';
      gamedata.screenBuff[index].Attributes = BACKGROUND_BLUE;

      // Перемещаем персонажа влево на одну позицию
      entity->xPos--;

      // Определяем новый индекс места, где стоит персонаж
      index = entity->xPos + entity->yPos * gamedata.width;

      // Рисуем персонажа там где он теперь стоит
      gamedata.screenBuff[index].Char.AsciiChar = entity->dispChar;
      gamedata.screenBuff[index].Attributes = entity->color;
}


// Перемещаем ENTITY (игрок или монстр) на одну позицию на восток
void moveRight(ENTITY *entity)
{
      // Индекс "screenBuff", где персонаж находится в настоящее время.
      int index = entity->xPos + entity->yPos * gamedata.width;

      // Устанавливаем на место, где находится персонаж, "символ пола"
      gamedata.screenBuff[index].Char.AsciiChar = ' ';
      gamedata.screenBuff[index].Attributes = BACKGROUND_BLUE;

      // Перемещаем персонажа вправо на одну позицию
      entity->xPos++;

      // Определяем новый индекс места, где стоит персонаж
      index = entity->xPos + entity->yPos * gamedata.width;

      // Рисуем персонажа там где он теперь стоит
      gamedata.screenBuff[index].Char.AsciiChar = entity->dispChar;
      gamedata.screenBuff[index].Attributes = entity->color;
}


// Рисуем лабиринт на экран
void DrawScreen()
{
      COORD buffSize = {gamedata.width, gamedata.height}; // Здесь хранится ширина, а затем
                                                                                  // высота "screenBuff" (порядок ВАЖЕН)
                                                                                        
      COORD buffCoord = {0,0}; // Левый верхний угол места в "screenBuff", откуда мы хотим начать рисовать
                                         
      // Индекс массива "screenBuff", где находится символ "конец лабиринта"
      // Мы рисуем его каждый раз, и поэтому, даже если на этом месте находится монстр,
      // символ ВСЕГДА виден.
      int index = gamedata.xEndPos + gamedata.yEndPos * gamedata.width;
      
      gamedata.screenBuff[index].Char.AsciiChar = 'E';
      gamedata.screenBuff[index].Attributes = BACKGROUND_BLUE | FOREGROUND_RED;

      /*      Это функция, которая рисует наш экранный буфер на экран. Параметры:
            gamedata.outputHandle - ссылка на буфер вывода (экран)
            gamedata.screenBuff - указатель на экранный буфер, который содержит данные
                                            (массив структур CHAR_INFO) для вывода на экран.
            buffSize - это размер буфера, хранимый как (колонка, ряд), и тогда
                           колонка * ряд равняется числу элементов массива (в screenBuff)
            buffCoord - определяет левый верхний угол в screenBuff, откуда следует
                              начинать рисовать.
            &(gamedata.rect) - адрес (указатель на) переменной(ую) SMALL_RECT, которая
                                       определяет прямоугольную область, которую мы хотим выводить
                                       в наше окно.
      */

      WriteConsoleOutput(gamedata.outputHandle,gamedata.screenBuff,buffSize,
                                 buffCoord,&(gamedata.rect));
}


// Функция проверяет состояние игры - Если вы дотронулись до монстра, или до "Е" (символ
// выхода), изменяется состояние игры (игра окончена) и печатается сообщение (победили вы
// или проиграли)
void CheckGameState()
{
      int i = 0;

      // Сперва проверяем, не столкнулись ли вы с одним из монстров
      for(; i < MAX_MONSTERS; i++)
      {
            // Если они на том же месте - вы проиграли
            if((monster[i].xPos == player.xPos) && (monster[i].yPos == player.yPos))
            {
                  printf("You Lose!!!!!!!\n");
                  gamedata.gameover = true;
                        return;
            }
      }

      // Если вы на месте, где находится выход (буква 'E')
      if((gamedata.xEndPos == player.xPos) && (gamedata.yEndPos == player.yPos))
      {
            // Вы победили
            printf("Winner and still champion!\n");
            gamedata.gameover = true;
      }
} // конец void CheckGameState()


// Возвращает true, если игра окончена, или false в другом случае
bool isGameOver() { return gamedata.gameover; }


// Освобождаем память, выделенную игрой
void FreeMem()
{
      // Если мы выделили память под экранный буфер, освобождаем ее
      if(gamedata.screenBuff)
            free(gamedata.screenBuff);

      gamedata.screenBuff = NULL;

      FlushConsoleInputBuffer(gamedata.inputHandle); // Удаляем нажатия клавиш, которые
                                                                           // могут быть сохранены в буфере клавиатуры
}

// Перевод © 2004 Евгений Казеко
// www.gamecoder.kazeko.com
// evgeniy@kazeko.com

Файл Map.txt
60 15
111111111111111111111111111111111111111111111111111111111111
101111100000001101111100000000001100000000001101111100000001
101101111011101101101100011101101101111011101101101100011101
100000000011101100000000011101100000000011101100000000011101
101111111111101101111101111101101111111111101101111111111101
101000001101100001000001101101101000001101100001000001101101
101011101101101101011101101101101011101101100001011101101101
101011101101100001011101101101101011101101100001011101101101
101111101101101101111101101101101111101101101101111101101101
101111101101101101111101101101101111101101101101111101101101
100000001100000000000001100001100000001100000000000001100001
111111001101111011111001101111111111001101111011111001101111
111000001101111011000001101110011000001101111011000001101111
100000000000011000000000000000000000000000011000000000000011
111111111111111111111111111111111111111111111111111111111111

Скачать обучалку (Visual C++ 6)

Лабиринт | Все обучалки раздела | Стеки

[Главная] [С чего начать?] [Дистанционное обучение] [Статьи] [Обучалки] [Книги] [Софт] [Форум] [Ссылки] [О сайте]

Copyright © 2003-2005 Евгений Казеко. Все права защищены. E-mail: evgeniy@kazeko.com

озеленение петербург озеленение петербург озеленение петербург , ноутбуки харьков , Доска бесплатных объявлений - сдам квартиру в одессе. Недвижимость все виды услуг.