Фантазии о Вселенной и мой личный сайт
Страница запроса

Звездное небо

model tiny
codeseg
startupcode

    mov     ax, 0x0013
    int     0x10            ; Видеорежим 320x200x256
    push    0xA000
    pop     es              ; Сегмент ES = 0xA000 видеобуфера
    pop     cx              ; CX=0, SP=0
    inc     bx              ; BX = любое число для генератора
R:  xadd    bx, dx          ; Генератор псевдослучайного числа
    mov     ax, bx
    and     al, 0x0F
    or      al, 0x10        ; Цвет AL = [16 до 31] шкала серого
    and     ah, ah
    cmovne  ax, sp          ; Если AH <> 0, AX = SP [=0]
    stosb                   ; Нарисовать звезду
    loop    R               ; Повторить
    int     0x16            ; Ожидание нажатия клавиши
    int     0x20            ; Выход из программы
Суть программы в том, что она выдает на экран звездное небо, используя стандартный видеорежим BIOS, 320x200x256. Для установления этого видеорежима нужно вызвать шестнадцатое прерывание с параметрами AH = 0, AL = видеорежим, в данное случае AL = 19 = 0x13 [hex]

После чего в регистр ES записывается значение 0xA000, которое есть сегмент, указывающий на видеопамять. Инструкция "POP CX" в стандарных com-программах будет означать то, что в CX обязательно окажется 0, ибо при инициализации система непременно записывает в стек 16-ти разрядный 0. Инструкция "INC BX" сменяет на единицу регистр BX... По сути, это нужно лишь для того, чтобы запустить генератор чисел для построения звездного неба. Если в регистрах BX и DX одновременно окажутся 0, программа выдаст лишь черный экран.

Мнемоника "XADD BX, DX" означает следующее:

  • Вначале BX и DX обмениваются местами [XCHG BX, DX]
  • После чего BX складывается с DX: BX = BX + DX [ADD BX, DX]
Допустим, что в BX = 1, DX = 0, тогда:
BX = 1, DX = 0: BX = 0, DX = 1: BX = 1
BX = 1, DX = 1: BX = 1, DX = 1: BX = 2
BX = 2, DX = 1: BX = 1, DX = 2: BX = 3
BX = 3, DX = 2: BX = 2, DX = 3: BX = 5
...
Получается ряд чисел, последовательно хранящихся в регистре BX:
1, 1, 2, 3, 5, 8, 13, 21, 34, 55...
Который является ничем иным, как "Числами Фибоначчи". Эффект псевдослучайных чисел получается тогда, когда есть некий "ограничитель", в данном случае ограничителем является разрядность регистров BX и DX. Если попробовать сложить два предыдущих числа Фибоначчи, например, 28657 и 46368, не превышающих допустимого предела значении 16-ти битных регистров (макс.знач. = 65535) получился число 75025. Если откинуть старший, 17-й бит, который, в данном случае, просто перейдет в флаг процессора Carry Flag = 1, получится число 9489. Можно допустить, это число получилось "непредсказуемо", и оно теперь будет новой "базой генерации". Сложив его с предыдущим, 46368, начнется новый цикл чисел. Постоянно находясь в замкнутом цикле, числа, "крутясь, как белка в колесе", будут очень похожи на случайные. Хотя это не так... Почему? Например, можно предположить, что последовательность
1 2 4 7 9 4 3 2
1 5 6 1 2 4 7 9
4 3 2 1 5 6 1 2
4 7 9 4 3 2 1 5
6 1 2 4 7 9 4 3
случайна. Теперь разложим в другом порядке:
1 2 4 7 9 4 3 2 1 5 6
1 2 4 7 9 4 3 2 1 5 6
1 2 4 7 9 4 3 2 1 5 6
1 2 4 7 9 4 3 . . . .

Теперь же последовательность далеко не случайна. Мы видим, что здесь есть легко проглядывающийся цикл. Так же и в генераторе "случайных чисел" с помощью чисел Фибоначчи, где действительно случайными можно называть первые 24 числа, а остальными назвать произодными от них. Два фактора - постоянно меняющаяся база генерации в 65536x65536 значении и раскладка в 320 столбцов дает необходимый элемент случайности, достаточный для генерации звездного неба. Лирическое отступление: вот как я пристроил знаменитые числа Фибоначчи! :) Как называется, нашел им новое применение среди огромного числа его бывших применений

Дальше все просто. В регистр AX переписывается "случайное" значение BX, "отрезаются" лишние биты, оставляя только первые 4, а потом "дописывая" в 5-й бит единицу. Таким образом, в AL получается число в диапазоне от 16 до 31, что соответствует 16-ти градациям серого цвета из стандартной палитры цветов. Потом проверяется, есть ли 0 в регистре AH. Дело в том, что при генерации числа регистр BX редко оказывается со значением, меньшим, чем 256. Потому, если в регистре такое значение, а это говорит о том, что в старшем его байте BH = 0, то в точке под номером DI [регистр, в котором записана текущая позиция на экране] будет "звезда".

Инструкция CMOVNE AX, SP работает только начиная с 686 модели процессора. Она работает следующим образом:

- Если флаг ZF (Zero Flag) = 1, то инструкция не выполняется
- Иначе AX = SP

Стоит заметить, что SP = 0, так как при запуске этот регистр всегда равен 0xFFFE. "Вытолкнув" нуль в начале программы, в CX, программа, таким образом, "подняла" регистр SP на 2 байта вверх, т.е. 0xFFFE + 0x0002 = 0x0000. Так что, если AH <> 0, что бывает довольно часто, в ZF будет 0 тоже довольно часто. А это говорит о том, что следующая точка чаще окажется нулем, чем некоторым значением из шкалы серого.

Инструкция STOSB записывает значение регистра AL по адресу ES:DI и увеличивает значение DI на единицу. Первоначальное значение DI не имеет значения, ибо оно все равно за 65536 раз увеличения на единицу примет все возможные свои значения. Инструкция LOOP производит повтор блока генерации звездного неба ровно 65536 раз.

INT 0x16 - это системное BIOS-прерывание, обслуживающее клавиатуру. Чтобы установить в ожидание приема символа, необходимо, чтобы в регистре AH = 0, что произойдет в любом случае, потому что если в AH будет не 0, то выполнится CMOVNE и запишет в AX = 0, и в AH тоже будет 0. Если же в AH = 0, то в AL = [16..31], но это значения не имеет. Команда INT 0x20 завершает выполнение COM-файла, причем только COM, а не EXE.