// - "Talk to me like I'm a 3 year old!" Programming Lessons -
//
// $Author: Ben Humphrey DigiBen@gametutorials.com
//
// $Program: ConsoleMaze
//
// $Description: Creates a maze that you can walk around and collide with
//
// $Date: 6/30/01
//
// Перевод © 2004 Евгений Казеко
// www.gamecoder.kazeko.com
// evgeniy@kazeko.com
#include <windows.h>
#define SCREEN_WIDTH 20 // Это ширина нашего окна
#define SCREEN_HEIGHT 15 // Это высота нашего окна
#define START_X 0 // Это начальная позиция X нашего символа
#define START_Y 7 // Это начальная позиция Y нашего символа
#define PLAYER 1 // Определяем символ игрока (улыбающееся лицо)
#define WALL '#' // Определяем символ - стену
// Это заголовок функции рисования лабиринта (мы делаем это, для того чтобы мы могли определить функцию
// внутри функции main())
void DrawMaze(CHAR_INFO screenBuffer[], int playerX, int playerY);
// Это заголовок функции, двигающей символ по экрану
BOOL MovePlayer(CHAR_INFO screenBuffer[], COORD *playerPos);
void main()
{
// Это наш экранный буфер, в котором будут храниться все данные о лабиринте, который мы хотим
// отобразить на экран.
CHAR_INFO screenBuffer[SCREEN_WIDTH * SCREEN_HEIGHT] = {0};
// Координаты ширины и высоты окна, которое мы отображаем
COORD bufferSize = {SCREEN_WIDTH , SCREEN_HEIGHT};
// Положение игрока
COORD playerPos = {START_X, START_Y};
// Эти переменные используются для работы с одномерным массивом как с двумерным
int x = 0, y = 0;
// Сбрасываем генератор случайных чисел, чтобы у нас не появлялся тот же самый лабиринт.
srand( GetTickCount() );
/* Ниже мы создаем наш лабиринт из случайно устанавливаемых знаков #.
Мы просто заполняем данными экранный буфер, но пока еще НЕ выводим их на экран.
Быстрее рисовать все сразу, а не использовать WriteConsoleOutputCharacter().
*/
// Заполняем колонки (значение y) буфера
for(y = 0; y < bufferSize.Y; y++)
{
// Мы начинаем с х = 2. Это для того, чтобы у нас было место для свободного
// передвижения перед лабиринтом.
// Заполняем ряды (значение х) буфера
for(x = 2; x < bufferSize.X; x++)
{
// Заполняем текущий индекс массива символом стены и цветом.
// Если наше случайное число равно 2, то рисуем стену лабиринта.
// (На мой взгляд, здесь допущена ошибка и стена рисуется при числе равном 0 - прим.перев.)
if(!(rand() % 3))
{
screenBuffer[x + y * SCREEN_WIDTH].Char.AsciiChar = WALL;
screenBuffer[x + y * SCREEN_WIDTH].Attributes = FOREGROUND_GREEN;
}
}
}
/* Рисуем игрока в его текущей позиции вместе с лабиринтом.
Функция берет экранный буфер и положение игрока.
Здесь отображается лабиринт с начальным положением игрока. */
DrawMaze(screenBuffer, playerPos.X, playerPos.Y);
// Далее идет наш главный цикл, который проверяет, передвигали ли мы игрока, и если да, то
// перерисовывает экран
while(1)
{
// Мы передаем положение игрока, потому что мы изменим его при передвижении.
// Если мы передвигаемся, то функция MovePlayer() возвращает TRUE, и нам нужно перерисовать экран
if(MovePlayer(screenBuffer, &playerPos))
{
// Рисуем обновленное положение игрока внутри лабиринта
DrawMaze(screenBuffer, playerPos.X, playerPos.Y);
}
}
} // Конец программы
//////////////////////////// DRAW MAZE ////////////////////////////////
// Эта функция рисует наш буфер на экране, вместе с игроком
void DrawMaze(CHAR_INFO screenBuffer[], int playerX, int playerY)
{
// Это наш прямоугольник, который содержит координаты, по которым мы рисуем.
SMALL_RECT drawRect = {0, 0, SCREEN_WIDTH - 1, SCREEN_HEIGHT - 1};
// Это координаты ширины и высоты окна, которое мы отображаем.
COORD bufferSize = {SCREEN_WIDTH , SCREEN_HEIGHT};
// Указывает левый верхний угол из которого мы рисуем
COORD zeroZero = {0, 0};
// Буфер вывода
HANDLE hOutput;
// Присваиваем буфер вывода соответствующей переменной
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
// Рисуем игрока в положении, которое было передано
screenBuffer[playerX + playerY * SCREEN_WIDTH].Char.AsciiChar = PLAYER;
screenBuffer[playerX + playerY * SCREEN_WIDTH].Attributes = FOREGROUND_RED;
// Выводим буфер на экран со скоростью молнии.
WriteConsoleOutput(hOutput, screenBuffer, bufferSize, zeroZero, &drawRect);
}
//////////////////////////////// MOVE PLAYER /////////////////////////////////
// Эта функция проверяет ввод с клавиатуры, изменяет положение игрока и стирает его
// предыдущее положение, если игрок двигался.
BOOL MovePlayer(CHAR_INFO screenBuffer[], COORD *playerPos)
{
// Это структура, содержащая информацию о буфере ввода (о том, что делает пользователь)
INPUT_RECORD InputRecord;
// Эта переменная сохраняет предыдущее положение игрока,
// чтобы мы могли стереть его после передвижения.
COORD oldPosition = *playerPos;
// Мы создаем переменную типа DWORD для совместимости с ReadConsoleInput().
// (Содержит данные о количестве произошедших событий).
DWORD Events=0;
// Используется для опроса клавиатуры
HANDLE hInput;
// Содержит данные о том, двигался ли игрок или нет
BOOL bPlayerMoved = FALSE;
// Определяет нажали ли мы клавишу, а не отжали.
int bKeyDown = 0;
// Инициализируем буфер ввода.
hInput = GetStdHandle(STD_INPUT_HANDLE);
// Читаем ввод пользователя, сохраняя информацию в структуре InputRecord.
ReadConsoleInput(hInput, &InputRecord, 1, &Events);
/* Теперь мы проверяем, было ли событие клавиатуры.
Для Windows 95 и 98 нам не нужно это, но на системах, основанных на NT, консольные
функции обрабатываются по-другому. В Windows 95 и 98 нажатие регистрируется только
когда клавиша нажимается вниз, в то время как системы NT регистрируют нажатие и
отжатие клавиши. Это заставляет игрока двигаться каждый раз на 2 позиции, поэтому
нам нужно убедиться, что мы двигаем игрока только при нажатии клавиши. Мы используем
переменную bKeyDown, которая содержится в структуре InputRecord, убеждаясь, что она
равна значению "истинно", когда нажата клавиша. Обратите внимание на "&& bKeyDown".
Так мы указываем, что если была нажата клавиша, мы хотим войти в условное выражение
только если это было нажатие вниз.
*/
bKeyDown = InputRecord.Event.KeyEvent.bKeyDown;
if(InputRecord.EventType == KEY_EVENT && bKeyDown)
{
// Проверяем, нажата ли стрелка вправо
if(InputRecord.Event.KeyEvent.wVirtualKeyCode == VK_RIGHT)
{
// Проверяем, не выходит ли новое положение игрока за границы нашего буфера
if(playerPos->X < (SCREEN_WIDTH - 1) )
{
// Увеличиваем координату Х, потому что мы двигаемся вправо.
playerPos->X++;
// Устанавливаем истинное значение переменной, показывающей, двигался ли игрок.
bPlayerMoved = TRUE;
}
}
// Проверяем, нажата ли стрелка влево
else if(InputRecord.Event.KeyEvent.wVirtualKeyCode == VK_LEFT)
{
if(playerPos->X > 0)
{
playerPos->X--;
bPlayerMoved = TRUE;
}
}
// Проверяем, нажата ли стрелка вверх
else if(InputRecord.Event.KeyEvent.wVirtualKeyCode == VK_UP)
{
if(playerPos->Y > 0)
{
playerPos->Y--;
bPlayerMoved = TRUE;
}
}
// Проверяем, нажата ли стрелка вниз
else if(InputRecord.Event.KeyEvent.wVirtualKeyCode == VK_DOWN)
{
if(playerPos->Y < (SCREEN_HEIGHT - 1) )
{
playerPos->Y++;
bPlayerMoved = TRUE;
}
}
// Если нажата ESCAPE
else if(InputRecord.Event.KeyEvent.wVirtualKeyCode == VK_ESCAPE)
{
exit(0); // Выход из программы
}
}
// Очищаем буфер ввода
FlushConsoleInputBuffer(hInput);
// Если игрок передвинулся
if(bPlayerMoved)
{
/* Проверяем, было ли столкновение со стеной. Если наше новое положение совпадает
со стеной, мы устанавливаем положение игрока обратно на его прежнее место.
В другом случае, если это не стена, мы затираем старое положение пробелом. */
// Если мы не столкнулись со стеной, стираем старое положение.
if(screenBuffer[playerPos->X + playerPos->Y * SCREEN_WIDTH].Char.AsciiChar != WALL)
{
// Стираем старое положение игрока пробелом.
screenBuffer[oldPosition.X + oldPosition.Y * SCREEN_WIDTH].Char.AsciiChar = ' ';
screenBuffer[oldPosition.X + oldPosition.Y * SCREEN_WIDTH].Attributes = 0;
}
else // Если было столкновение
{
// Устанавливаем положение игрока обратно на прежнее место
*playerPos = oldPosition;
}
return TRUE; // Возвращаем TRUE, говоря, что нам нужно перерисовать экран.
}
return FALSE; // Мы не передвигались, поэтому возвращаем FALSE.
}
/* *Примечание*
Эта обучалка позволяет вам сделать лабиринт и перемещать по нему символ.
Теперь вы действительно можете создать классную игру! Это пожалуй наиболее простая
форма обработки столкновений. Все, что вы делаете, это проверяете, столкнулся ли
игрок со стеной, отслеживая положение X и Y в массиве экранного буфера. Если в этом
месте есть символ стены, тогда мы не позволяем игроку двигаться туда. Чтобы сделать
это, используется всего лишь одна строка кода.
Если вы все еще не понимаете, почему мы используем операторы -> и *, то следует сказать,
что это использование указателей. Когда мы используем указатель, нам необходимо сделать
"обратную ссылку" на указатель, чтобы получить значение, а не адрес в памяти.
В противном случае, если мы не будем использовать * и ->, то мы получим число, такое как
0x00AFF3. Но нам нужно значение по этому адресу, а не сам адрес. Надеюсь, что теперь вам
стало понятно.
*/
// © 2001 GameTutorials
// Перевод © 2004 Евгений Казеко
// www.gamecoder.kazeko.com
// evgeniy@kazeko.com
|