На Хабре неоднократно появлялись статьи с описанием приложения из известного фильма «The Matrix». Как известно, все они принадлежат так называемому классу View Matrix, позволяют визуализировать состояние мира в котором проживают главные герои фильма. Но в отличие от предыдущий версий данных программ для ЭВМ, я хочу рассказать о визуализаторе матрицы первой версии (о которой в фильме упоминал герой с именем Морфеус в первой части трилогии).
Дорогие друзья, эта статья посвящена всем кто помнит что такое Dos, видео режим 80x25 и прочие «замечательные мелочи» олд скульного разработчика приложений. А так же кому интересно с этим познакомиться.
Далее следует предыстория этого рассказа. В начале двухтысячных годов, в мою бытность студентом, появился у меня замечательный девайс (карманный персональный компьютер): Compaq iPAQ H3800 с установленной операционной системой Microsoft Windows Pocket PC 2002. А так же имелись в наличии длинные, темные вечера зимних каникул в деревне, не имея при себе ни интернета, ни персонального компьютера в данном месте. А только сей девайс и большое желание студента размять мозговые клетки не банальными кросс/сканвордами а чем то поинтереснее. Признаюсь, перед отправлением в деревню на это устройство мною был установлен эмулятор операционной системы Dos под названием PocketDOS, а так же взята с собой в те времена являющаяся моей «настольной книгой», это замечательное произведение автора Зубкова С.В., под названием: «Assembler для Dos, Windows и UNIX». Плюс мне пригодились: стилус и стандартный блокнот операционной системы Windwos Pocket PC.
(На данный момент конечно же будет уместней использовать TASM в эмуляторе DosBox для настольных PC).
Впрочем, результаты недавнего опроса «На каком языке вы бы написали Матрицу?» подтверждают мои догадки о том, что визуализировать матрицу лучше на том же языке программирования что и её писать.
Итак, начнем же разгадывать кроссворд «The Matrix» на языке Assembler, под процессор i80186, в операционной системе Dos с помощью компилятор и линковщика Turbo Assembler (далее TASM) от компании Borland, на устройстве iPAQ H3800.
Как известно процессор i80186 является 16 битным, соответственно регистры данного процессора имеют размер 16 бит (например регистр аккумулятор AX) и делятся на две части по 8 бит (AH — старшая часть и AL — младшая). Так же нам понадобятся прерывания BIOS: 10h и 16h, которые будут управлять видеорежимом и клавиатурой соответственно. И прерывание Dos под номером 21h, необходимое нам в данном случае для вывода строки на дисплей и корректного завершения программы.
Чтобы отображать матрицу нам достаточно небольшого объема оперативной памяти, поэтому мы будем использовать формат исполняемого файла – .com и модель памяти tiny. Как написано у классиков ассемблера в данном случае установим размер стека равным 100h.
.model tiny ; модель памяти крохотного размера
.186 ;для процессора i80186
.code ; начало единственной секции, секции кода
org 100h ; размер стека
Далее следует память в которой мы объявляем переменные и константы для нашего кода, и сразу перепрыгиваем эту часть памяти чтобы не выполнять её:
main:
jmp start
; описание некоторых констант говорящих о размере экрана вывода матрицы
W equ 1Fh
H equ 18h
OFFS equ 04h
cells db W + OFFS dup(0)
c_n db W + OFFS dup(0)
bsymb db 0
_sleep dw 1fffh ; задержка вывода каждого символа матрицы на экране
the_matrix db "T H E M A T R I X" ; да это она, надпись матрица
the_matrix_len equ $ - the_matrix ; длина текста , символ $ говорит о текущем положении строки кода
msg_end db 0ah, 0dh, "Follow the white rabbit.", 0ah, 0dh, "$"
timer dw 04ffh ; время отображения названия программы
Итак, мы добрались до момента когда нужно указать видео режим дисплея нашей ЭВМ в котором мы будем выводить символы матрицы. Но сначала сохраним текущий режим в стек чтобы в конце к нему вернуться.
mov ax, 0f00h ; считать текущий видеорежим
int 10h
push ax ; результат сохраним в стек
Выставляем видеорежим: текст CGA,EGA 80x25:
mov ax, 0003h
int 10h
Далее идет просто огромный кусок кода в котором происходят невероятные магические преобразования:
- Определение позиции символа на экране который необходимо затереть
- Процедура перемещения курсора и затирание символа
- Определение позиции вывода символа на экран
- Генерация символа из таблицы ASCII
- Вывод его с определенным цветом
- Цикл с задержкой между выводом следующего символа
- Определение истекшего значения таймера вывода надписи «T H E M A T R I X»
- Вывод надписи «T H E M A T R I X»
main_loop:
imul dx, 4E35h
inc dx
push dx
and dh, W
add dh, OFFS
shr dx, 08h
mov bx, dx
lea di, cells
add byte ptr [di+ bx], 1
mov dh, byte ptr [di+ bx]
cmp dh, H
jne next1
mov byte ptr [di+ bx], 0
next1:
cmp dh, 0
je draw1
dec dh ; номер строки
mov bh, 00h
mov ah, 02h ; установить позицию курсора
int 10h
mov ah, 09h ; писать символ/атрибут в текущей позиции курсора
mov al, bsymb ; записываемый символ
mov bx, 0002h ; номер видео страницы, цвет зеленый
mov cx, 01h ;счетчик (сколько экземпляров символа записать)
int 10h
draw1:
inc dh
mov bh, 00h
mov ah, 02h
int 10h
mov ah, 09h ; писать символ/атрибут в текущей позиции курсора
pop dx
imul dx, 4E35h
inc dx
push dx
mov bsymb, dh ; сгенерированный символ
mov al, bsymb
mov bx, 000Ah
mov cx, 01h
int 10h
;...
pop dx
imul dx, 4E35h
inc dx
push dx
and dh, W
add dh, OFFS
shr dx, 08h
mov bx, dx
lea di, c_n
add byte ptr [di+ bx], 01h
mov dh, byte ptr [di+ bx]
cmp dh, H + 01h
jne next2
mov byte ptr [di+ bx], 00h
next2:
dec dh
mov bh, 00h
mov ah, 2
int 10h
mov ah, 09h ; писать символ/атрибут в текущей позиции курсора
mov al, 08h ; номер символа
mov bh, 00h ; номер страницы
mov bl, 00h ; видео атрибут (текст) или цвет (графика)
mov cx, 0001h ; один символ
int 10h
; sleep cycle
mov cx, _sleep
loop2:
nop
loop loop2
;draw message
cmp timer, 0
je no_msg
dec timer
mov ax, 1300h ; писать строку
mov ch, 00h
mov cl, the_matrix_len
mov bh, 00h ; номер страницы
mov bl, 20h ; цвет
mov dl, 0Bh ; позиция (колонка)
mov dh, 0Bh ; позиция (строка)
lea bp, the_matrix ; выводимая строка
int 10h
inc dh
И, наконец, нам необходимо получить сигнал о том что мы больше не хотим смотреть на матрицу и уже можем проследовать за белым кроликом. Для этого мы используем проверку нажатия клавиши клавиатуры и если символ нажат не был, то продолжаем вывод символов матрицы:
mov ah, 01h
int 16h
pop dx
jz loop1
jmp end_prog
loop1:
jmp main_loop
Символ нажат и мы закрываем программу, но сначала восстановим предыдущий видеорежим:
end_prog:
pop ax ; восстановить видеорежим из стека
mov ah, 00h
mov bh, 00h
int 10h
Следуем за белым кроликом:
mov ah, 09h ; выдать строку на дисплей
lea dx, msg_end ; адрес строки, заканчивающейся символом '$' (ASCII 24H)
int 21h
Заканчиваем программу корректно:
mov ah, 4ch
mov al, 00h ; код выхода
int 21h
end main
Если слепить все эти кусочки кода вместе, то скомпилировать и слинковать можно с помощью команд:
tasm /t /z matrix
tlink /t matrix
В итоге вот что мы имеем:
- Вывод вертикально скатывающихся символов зеленым цветом
- Вывод надписи «The Matrix» по центру с исчезновением через некоторое время
- Ожидание символа с клавиатуры для прерывания вывода и завершения ропграммы
- Скомпилированную версию приложения размером 357 байт
Автор: AndreySu