Часть пятая. Что то с памятью...Оглавление
Что то с памятью моей стало...
В
прошлой части мы научились выводить в консоли различные сообщения. Теперь настало время не только читать с консольного окна но и принимать различные сообщения от внешних устройств (пока что научимся принимать сообщения от устройств ввода/вывода).
Итак давайте зададимся целью создать игру. Простенькую консольную игрушку вроде арканоида или сокобана.
Для некоторых эти игры вызывают ностальгическое чувство утери прекрасного времени нашей молодости.
Ээээххх не будем о грустном, приступим к теоретическим вопросам.
Во первых графику в консоли мы можем реализовать только текстовыми символами(нет, возможна и GDI графика, но это уже будет не чистая консоль) Вот коды символов консоли для наших целей
Код: "CODE"
0 1 2 3 4 5 6 7 8 9 A B C D E F
B ░ ▒ ▓ │ ┤ ╡ ╢ ╖ ╕ ╣ ║ ╗ ╝ ╜ ╛ ┐
C └ ┴ ┬ ├ ─ ┼ ╞ ╟ ╚ ╔ ╩ ╦ ╠ ═ ╬ ╧
D ╨ ╤ ╥ ╙ ╘ ╒ ╓ ╫ ╪ ┘ ┌ █ ▄ ▌ ▐ ▀
Из этого ясно, что символу ▓ будет соответствовать код
0B2X, а символу ╟ соответственно
0С7XВ консольных приложениях есть два пути взаимодействия с пользователем. Высокоуровневый ввод/вывод и низкоуровневый.
С высокоуровневыми конструкциями
WriteStr() и
ReadString() мы уже знакомы по
предыдущим статьям,
так что давайте рассмотрим низкоуровневые конструкции и особенности Оберон компиляторов обрабатывать данные.
Как положено в таких разработки, для приёма сообщений от операционной системы нам понадобится две вещи.
Первая - это цикл (не вечный) и вторая - это сам приёмник информации (чаще всего это функция).
Выглядеть всё наше хозяйство будет вот так
Код: "OBERON"
REPEAT
W.GetNumberOfConsoleInputEvents(C.hIn,r);
C.ReadConsoleData(CD);
UNTIL (CD.KeyCode=W.VK_ESCAPE) OR Exit;
Цикл мы предусмотрительно сделали с выходом по условию (забегая вперёд - или по клавише
Esc или по внутреннему какому то условию)
Я не привожу здесь описаний и код всего взаимодействия, моя цель объяснить особенности наших внутренних
обёрток для API операционной системы.
Отвлекусь от темы и объясню, что такое так называемая
обёртка в программировании.
Посмотрим на следующую функцию
Код: "OBERON"
PROCEDURE WriteStr* (str: String);
VAR
n,i,c:Integer;
BEGIN
n:=Length(str);
W.WriteFile(hOut, str, n, NIL, NIL);
END WriteStr;
В нутри её мы замечаем
WinApi функцию
WriteFile.
Так вот - обёртка это удобное представление некоих действий.
В данном случае, что бы постоянно не вычислять длину строки и не вызывать
WinApi функцию вывода в консоль
мы просто написали свою и используем для вывода строк функцию
WriteStr с единственным параметром -
строкаТак же и библиотеки в языках высокого уровня - это всего лишь
обёртки для API функций ОС.
Ну суд да дело, а кашу надо есть.
Функция
WinApi -
GetNumberOfConsoleInputEvents всего лишь ожидает от операционной системы сообщения (ну зачем нам гонять цикл не по делу) и затем передаёт управление далее программе.
Второй функцией мы видим
C.ReadConsoleData(CD);.
Так как в секции импорта модуль консоли у нас определена как
CКод: "OBERON"
IMPORT
SYSTEM, C:=Console, W:=Windows;
То и функция будет в этом модуле.
Открываем наш файл
Console.Mod и... ничего вменяемого кроме копирайта не находим.
Однако после многочасовых поисков в любом текстовом редакторе натыкаемся на уже известную по имени функцию
ReadConsoleData которая как мы понимаем является обёрткой для какой то функции
ReadConsoleInputX, таким же макаром исследуем модуль
Windows.Mod исходя из
W:=Windows в секции импорта и находим её описание а так же описание похожей функции
Код: "OBERON"
ReadConsoleInput- : PROCEDURE [WINAPI] (hConsoleInput: HANDLE; VAR lpBuffer: INPUT_RECORD;
nLength: LONGINT; VAR lpNumberOfEventsRead: LONGINT): BOOL;
ReadConsoleInputX- : PROCEDURE [WINAPI] (hConsoleInput: HANDLE; VAR lpBuffer: INPUT_RECORD_X;
nLength: LONGINT; VAR lpNumberOfEventsRead: LONGINT): BOOL;
Что за дела? Спросите вы. Почему функция
ReadConsoleInput это WinApi функция которая есть в описании а
ReadConsoleInputХ это какая то новая функция с изменнённым типом lpBuffer:
INPUT_RECORD_X.
Сейчас я вам всё объясню.
Посмотрим на апишную структуру
INPUT_RECORDКод: "OBERON"
INPUT_RECORD* = RECORD [NOTAG]
EventType*: INTEGER;
Event*: RECORD [union]
KeyEvent* : KEY_EVENT_RECORD;
MouseEvent* : MOUSE_EVENT_RECORD;
WindowBufferSizeEvent* : WINDOW_BUFFER_SIZE_RECORD;
MenuEvent* : MENU_EVENT_RECORD;
FocusEvent* : FOCUS_EVENT_RECORD;
END;
END;
Поле
Event определено как UNION а значит это изменяемое поле и значение его будет зависить от
EventTypeТо есть если
EventType равен нулю то запонится поле
KeyEvent, если двум то заполнится поле
WindowBufferSizeEvent одно но!!!
ПОЛЯ UNION НЕ ПОДДЕРЖИВАЮТСЯ В ETH Oberon КОМПИЛЯТОРЕ!!!И что же делать? Придётся создать структуру похожую, но с массивом данных, в которые и придут данные из Api функции, а потом уже по этим данным заполнить свою структуру для удобства.
Код: "OBERON"
INPUT_RECORD_X* = RECORD [NOTAG]
EventType*: INTEGER;
Event*: RECORD [NOTAG]
Data*: ARRAY 32 OF CHAR;
END;
END;
Data*:
ARRAY 32
OF CHAR;
Вот в этот массив имеющий длинну самой большой структуры
INPUT_RECORD мы и записываем наши данные.
После этого обрабатываем их
обёрткой ReadConsoleData и заполняем уже нашу структуру
CONSOLE_DATAКод: "OBERON"
CONSOLE_DATA* = RECORD
EventType* : LONGINT;
KeyDown* : LONGINT;
KeyChar* : CHAR;
KeyCode* : INTEGER;
MouseX* : INTEGER;
MouseY* : INTEGER;
MouseBState* : SET;
MouseCState* : SET;
MouseEState* : SET;
END;
Она включает в себя коды нажатых клавиш, координаты мышки а так же статусы сообщений (нажата/отжата кнопка, нажата правая/средняя/левая кнопка мыши)
Ну вот пожалуй и всё. Основные моменты я описал в данной статье, а частные вы можете посмотреть во вложении.
Посмотреть скриншоты игры и саму игру можно скачать тут -
Скриншоты и описание.
Засим консольный цикл считаю законченым, далее мы перейдем уже к проработке GUI приложений, OpenGL библиотеке и так далее.