// - "Talk to me like I'm a 3 year old!" Programming Lessons -
//
// $Author: Ben Humphrey digiben@gametutorials.com
//
// $Program: ConsoleIntro
//
// $Description: Using the arrow keys we move a character around
//
// $Date: 6/19/00
//
// Перевод © 2004 Евгений Казеко
// www.gamecoder.kazeko.com
// evgeniy@kazeko.com
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
void main()
{
HANDLE hInput, hOutput;
INPUT_RECORD InputRecord;
COORD CursorPosition;
DWORD Events=0;
/* В этой программе появляются новые типы переменных.
Как можно объяснить HANDLE? Думайте о HANDLE как о чем-либо, что не содержит ни числа, ни
символов, но является экземпляром чего-либо. Просто представьте, что HANDLE это способ
получить доступ к чему-либо. В данном случае, мы используем его для доступа к буферу ввода.
Буфер ввода это поток данных, который записывает действия клавиатуры или мыши.
То есть, если я нажму клавишу или щелкну мышью, это действие сохранится в буфере ввода.
Мы будем использовать функции консольного ввода. Это работает в основном для Windows, и я не
знаю, как это будет работать на других системах. В качестве имени переменной я выбрал "hInput",
потому что это переменная типа HANDLE. Обычно перед именем переменной ставят первую букву типа.
Это называется "Венгерская нотация". Много лет назад кто-то в Microsoft разработал конвенцию
по написанию программного кода, которой придерживаются все в Microsoft для улучшения читаемости
программы. Этот человек был венгром, отсюда и название. Еще раз скажу, что вы можете называть
переменные так, как вы хотите. Лучше всего давать легко читаемые названия.
Итак, у нас есть переменные для ввода и вывода. Далее, мы используем еще один тип переменных,
INPUT_RECORD. Такие переменные содержат информацию о буфере ввода, в основном
о событиях мыши и клавиатуры. Это структуры. Структура это переменная, в которой содержатся
другие переменные.
Я объясню это на примере COORD, которые тоже являются структурами. COORD используется для
координат. Вспомните математику. Координаты это X и Y, верно? (по крайней мере для 2D). Итак,
COORD это структура, содержащая две переменные, X и Y. Со структурой проще будет работать,
поэтому мы не используем просто "int x" и "int y".
Последняя переменная, DWORD, такая же как int, но она всегда 32-битная. Размер int зависит
от компьютера. Когда мы используем DWORD, мы всегда уверены в ее размере. В основном, DWORD
используется для работы с функциями - некоторые из тех, что мы будем вызывать ниже, требуют
переменные типа DWORD.
*/
hInput = GetStdHandle(STD_INPUT_HANDLE);
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
/* Мы вызываем функцию GetStdHandle() (что значит Get Standard Handle). В эту функцию
передается STD_INPUT_HANDLE. Если мы хотим получить доступ к буферу вывода, мы передаем
STD_OUTPUT_HANDLE. STD_INPUT_HANDLE это просто число. Оно определено в каком-то
заголовочном файле. Возможно, определение выглядит так:
#define STD_INPUT_HANDLE 0x0fff2ac00. Или что-то вроде этого, что-нибудь
с 0x и шестнадцатеричным числом.
Функция возвращает ссылку на буфер ввода или вывода, в зависимости от переданого в функцию
параметра. Теперь в переменной "hInput" содержится доступ к буферу ввода.
*/
SetConsoleMode(hInput, ENABLE_PROCESSED_INPUT);
/* Мы передаем этот доступ в функцию, которая устанавливает консольный режим.
Напомню, что термин "консоль" применим к окну DOS. Оно называется "консольное приложение".
Итак, в целом, мы устанавливаем режим окна, в котором будет весь печатаемый нами текст.
ENABLE_PROCESSED_INPUT это еще одна величина, определенная директивой #define, которая
дает этой функции знать, что следует разрешить завершение приложения клавишами CONTROL-C.
Иногда это очень удобно. Мы могли бы записать сюда же ENABLE_MOUSE_INPUT, но мы отложим
это до следующей обучалки.
*/
CursorPosition.X = 0;
CursorPosition.Y = 0;
/* Так мы присваиваем значения переменным структуры - используя точку.
В Visual Studio 6.0, когда вы набираете "CursorPosition" и затем точку, вот так:
"CursorPosition.", должно появиться маленькое окно, которое показывает, какие
переменные есть в структуре. Так как мы используем COORD, у нас будут только X и Y.
В данном случае мы устанавливаем значения переменных X и Y структуры "CursorPosition"
равные нулю. Это значит, что когда мы вызовем функцию, устанавливающую
положение курсора, курсор будет в верхнем левом углу экрана.
Кстати, в большинстве операционных систем верхний левый угол всегда (0,0).
*/
/* Теперь, когда у нас есть доступ к вводу и мы установили режим,
позволяющий выходить клавишами CONTROL-C, начнем проверку ввода.
Создадим бесконечный цикл, чтобы мы смогли проверять ввод с клавиатуры.
Помните, while(1) всегда истинно, поэтому цикл никогда не прервется,
если только мы не закроем окно или не нажмем CONTROL-C */
while(1)
{
SetConsoleCursorPosition(hOutput, CursorPosition);
/* Мы вызываем эту функцию, чтобы установить положение курсора.
Выше мы присвоили переменной "CursorPosition" значение (0,0).
Поэтому все, что нам нужно сделать - это передать "CursorPosition".
Первый параметр должен быть типа HANDLE и указывать на буфер вывода.
Второй параметр должен быть типа COORD.
Теперь, после того как курсор установлен на (X = 0,Y = 0), где он находится в любом случае
при запуске программы, мы хотим проверить ввод. Поэтому мы вызываем ReadConsoleInput().
Параметры включают в себя: (Input HANDLE, INPUT_RECORD, количество читаемых
событий и переменную хранящую информацию о том, сколько событий произошло)
Мы передаем сделаную выше ссылку на буфер ввода, затем нашу переменную InputRecord со знаком "&",
потому что функции нужно изменять ее значение. Затем мы передаем количество событий, равное 1
(нам нужно отслеживать только одно событие). И затем мы передаем переменную, изменяемую функцией,
(также со знаком &) в которую записывается число найденных функцией событий. События означают -
щелкнули ли мы мышью И нажали клавишу, или подвинули ли мы мышь И щелкнули ей и т.д.
Нам не нужна последняя переменная, так как мы отслеживаем только одно событие (как мы указали).
*/
ReadConsoleInput(hInput, &InputRecord, 1, &Events);
/* Теперь у нас есть инфорамция, сохраненная в "InputRecord". Функция ReadConsoleInput() проверила
были ли входные данные, и если да, то она сохранила информацию в нашей структуре "InputRecord".
Так как это структура, мы должны использовать точку для доступа к переменным внутри структуры.
В структуре InputRecord есть еще две структуры. Нам нужна структура "Event", в которой содержится
5 структур. Нас интересуют "KeyEvent" и "MouseEvent". Остальные относятся к событиям окна и меню.
(Еще раз, когда вы ставите точку после каждого имени структуры, появляется окно, в котором
показывается, что входит в каждую структуру).
Итак, нам нужна "KeyEvent", внутри которой много полезной информации. Мы можем проверить, была
ли нажата клавиша, определить введенный символ, узнать, нажала ли клавиша control или узнать
значение VirtualKeyCode.
Небольшой урок по клавиатуре. У каждой клавиши на клавиатуре есть собственное число. Это число
называется VirtualKeyCode. Где-то в заголовочных файлах есть определение для каждой клавиши,
такое как VK_RIGHT. Итак, VK_RIGHT это код для правой стрелки. Для левой стрелки это VK_LEFT.
Есть также VK_UP и VK_DOWN.
Итак, ниже мы проверяем, является ли код, записанный в нашу InputRecord, кодом правой клавиши.
*/
if(InputRecord.Event.KeyEvent.wVirtualKeyCode == VK_RIGHT)
{
// Если была нажата правая стрелка
// Увеличиваем координату Y положения курсора, смещая его на строку вниз.
CursorPosition.Y++;
// Увеличиваем координату X курсора, сдвигая его вправо.
CursorPosition.X = CursorPosition.X + 1;
/* Как видите, я увеличил X и Y по-разному, для того чтобы вы знали оба способа.
Способ ++ используется для увеличения только на 1. */
printf("Go Right!\n"); // Печатаем направление движения
}
// Проверяем, была ли нажата левая стрелка
else if(InputRecord.Event.KeyEvent.wVirtualKeyCode == VK_LEFT)
{
// Уменьшаем координату Х на 1, сдвигая курсор влево.
CursorPosition.X--;
// Увеличиваем координату Y на 1, сдвигая курсор вниз.
CursorPosition.Y = CursorPosition.Y + 1;
// И снова, я использовал 2 различных метода увеличения и уменьшения.
printf("Go Left!\n"); // Печатаем направление движения
}
// Теперь мы проверяем, был ли нажат пробел. VK_SPACE это код для пробела.
else if(InputRecord.Event.KeyEvent.wVirtualKeyCode == VK_SPACE)
{
// Сбрасываем координату X на 0
CursorPosition.X = 0;
// Сбрасываем координату Y на 0
CursorPosition.Y = 0;
// Теперь курсор в левом верхнем углу экрана.
printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
// Мы делаем это, чтобы убрать текст с экрана.
}
// Здесь мы проверяем, является ли координата Х больше 80 или меньше 0.
// Обычно консольное окно имеет размер (80, 25). Поэтому, если Х = 80, значит мы достигли конца окна.
if(CursorPosition.X > 80 || CursorPosition.X < 0)
{
CursorPosition.X = 0; // Сбрасываем координату в ноль (налево до конца).
printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
}
// Проверяем, не вышел ли курсор за нижнюю границу
if(CursorPosition.Y > 24)
{
CursorPosition.Y = 0; // Сбрасываем координату в ноль
printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
}
FlushConsoleInputBuffer(hInput); // Это все равно что "rewind()" для scanf().
// Функция очищает буфер ввода.
}
} // Конец программы
// © 2001 GameTutorials
// Перевод © 2004 Евгений Казеко
// www.gamecoder.kazeko.com
// evgeniy@kazeko.com
|