256 байт intro «Springs» для компьютера Vectrex
Решение написать что-нибудь для Chaos Constructions пришло, как водится, довольно внезапно (в первую очередь потому, что до последнего момента не было очевидно, состоится он в этом году или нет). Так что, времени написать что-то большее, чем работу для конкурса Tiny intro (256 байт для любой платформы) уже не оставалось. Это же определило и выбор платформы, так как под Vectrex я уже писал пару лет назад и проще было вспомнить ассемблер 6809, чем изучать следующий.
Чем (среди прочего) мне нравится демосцена, так это тем что, приступая к работе, понятия не имеешь, что в итоге получишь. Среди нескольких идей, что именно написать, конкретно вот этой не было точно. Две были отброшены потому, что изображение на эмуляторе и реальном Vectrex'e слишком уж отличалось — после каждой сборки заливать всё это в эмулятор ПЗУ и перетыкать его в Vectrex чтобы посмотреть, что получилось — нереально.
Третью идею я было начал реализовывать, но уже в процессе увидел, что сделать такое красиво в 256 байт — слишком сложно. Но, в процессе что-то там переглючило и напомнило пружину. Вот эту идею я, в итоге, и развил:
Не буду здесь описывать как программировать под Vectrex, поскольку уже подробно делал это в своей статье. Упомяну только, что принцип вывода изображения у этого компьютера радикально отличается от большинства остальных — он векторный. Причём, с некоторыми оговорками можно отображать не только прямые линии, но и кривые (чем, собственно, я и воспользовался).
Важным признаком хорошей демо является, на мой взгляд, использование преимуществ именно той платформы, под которую пишется работа. Сильные стороны есть почти у всех компьютеров — на одних это аппаратный скроллинг, на других поддержка большого количества спрайтов, на третьих — мощный процессор. В данном случае оказалось возможным нарисовать «витки» пружины не при помощи вычислений координат каждой точки кривой, как это пришлось бы делать на других компьютерах, а аппаратным способом: при нарастающем напряжении на ЦАП канала Y (вертикаль), через короткие равные промежутки времени менять напряжение на ЦАП канала X (горизонталь), в результате чего получалась кривая, похожая на синусоиду. Причём, для каждой последующей «пружины» амплитуда отличалась и, соответственно, они имеют разную ширину («диаметр»).
Очевидно, пружины должны сжиматься, иначе какие это пружины? «Сжатие» обеспечивается изменением напряжения по вертикали. Говоря точнее, изменением разницы между напряжением в точке, где луч начинает отрисовку пружины (внизу) и точкой, где пружина заканчивается (вверху).
Однако, простым увеличением и уменьшением этой разницы здесь не отделаешься — для пружин в такой ситуации естественна разная скорость сжатия взависимости от текущей растянутости. И вот тут уже без вычислений не обойтись… Понятно, что процессор 6809 команды для вычисления синусов не имеет, а табличка синусов для 256 байтной интро — слишком большая роскошь. К счастью, покопавшись в исходниках ПЗУ Vectrex, я обнаружил там по адресу $fc6d табличку, отлично подходящую для такого случая:
Единственное, что нужно было сделать — привести значения к нужному масштабу, разделив на 4 двумя сдвигами вправо:
В результате, пружина сжимается довольно естественно, а заодно и поднимается/опускается по тому же закону — с ускорением и замедлением.
Теперь следующая проблема — кому интересна единственная пружина, прыгающая на одном месте? Так ни компо не выиграешь, ни лайков не наберёшь — пружины должны прыгать одна за другой.
С горизонтальным перемещением, допустим, всё просто. Достаточно равномерно менять напряжение в канале X ЦАП и всё произойдёт само — уехав за экран, где произойдёт переполнение (за -127), пружина автоматически появится с другой стороны экрана (+127). Сложнее дело обстоит с синхронностью прыжков — в живой природе пружины никогда не идут в ногу, у них всегда есть некоторая рассинхронизация в прыжках.
Поскольку состояние пружины в любой момент времени полностью определяется выбранным из таблицы синусов значением, логично хранить это значение отдельно для каждой из пружин. Выбираем адрес в ОЗУ, где будем это дело хранить. Но, предварительно, нужно эту табличку проинициализировать начальными значениями. И не любыми, чтобы в шествии был некоторый порядок. Резервируем несколько байт в ПЗУ, а потом копируем в ОЗУ:
Как легко видеть, всю эту радость очень легко полностью выкинуть, просто найдя в BIOS адрес, где идут три подходящих байта подряд. Просто изначально я не думал, что обойдусь тремя пружинами, а потом убирать это уже не было времени, да и смысла (всё и так влезло в 256 байт).
Табличка синусов небольшая, поэтому если на каждую итерацию стандартного цикла Vectrex «по кадрам» брать следующее значение, пружины будут скакать очень быстро. Поэтому, берём из таблички следующее значение не каждую итерацию (но рисуем-то конечно в каждую, иначе будет мерцать):
Ещё для красоты добавим на кончик пружины яркую светящуюся точку — перед тем, как выключить луч, слегка задерживаем его там:
Внизу рисуем линию «земли» (к слову, из-за того, что рисование прямой происходит через BIOS, приходится задавать максимальный масштаб. В случае с кривой масштаб задавать не надо, т.к. мы её рисуем полностью «вручную»):
Много места также отнимает надпись внизу. Так что, я решил ограничиться сокращённым названием и указанием размера. Впрочем, была и другая причина. Дело в том, что из-за, во многом, аналоговой природы схем Vectrex'a, его конкретным экземплярам присущи всякие искажения. Конкретно в моём это выражается в двух вещах:
1. При выводе строк процедурами BIOS (там с помощью пунктирных векторов имитируется растровые символы) при увеличении длины строки она начинает наклоняться, так что где-то после 10 символа уже трудно что-то разобрать.
2. С уменьшением высоты, пружины начинает перекашивать влево. Это не «так задумано» — это проблема конкретного экземпляра Vectrex'a. Я попросил владельца двух других машин заснять, как выглядит intro у него. Легко заметить, что разница существенная:
Хотя звук в 256 байтных intro встречается довольно редко, если такая возможность есть — грех ей не воспользоваться.
Чем хорош BIOS Vectrex — в нём припасена не только простая подпрограмма для проигрывания примитивных мелодий, но и несколько примеров самих мелодий (по десятку-другому байт на каждую). Для большой демо это конечно нельзя использовать, но для 256 байт intro — отличная «вишенка на торте»:
Надо сказать, что в итоге код получился далёким от совершенства, за что мне немного стыдно учитывая, что ассемблер 6809 очень мощный и приятный, так что не получится сослаться на него, как на причину (как это можно было бы сделать в случае, скажем, с моей предыдущей работой под Videopac, у которого был весьма неприятный для программирования Intel 8048). Полагаю, что имея опыт в программировании под 6809, можно было бы достаточно легко уменьшить нынешние 255 байт до 180-200.
Большое количество регистров, в том числе 16-разрядных, очень экономит нервы и байты. При этом, однако, любопытно, что увеличение/умненьшение регистра на единицу есть только для a,b и d. А вот для индексных (и u) подрузамевается, что при нормальном программированиии они увеличиваются на единицу в рамках команд типа lda ,u+
Даже add не работает с этими регистрами. Впрочем, есть выход. Можно увеличивать и уменьшать их на любое количество байт командами вида leau N,u
Чем (среди прочего) мне нравится демосцена, так это тем что, приступая к работе, понятия не имеешь, что в итоге получишь. Среди нескольких идей, что именно написать, конкретно вот этой не было точно. Две были отброшены потому, что изображение на эмуляторе и реальном Vectrex'e слишком уж отличалось — после каждой сборки заливать всё это в эмулятор ПЗУ и перетыкать его в Vectrex чтобы посмотреть, что получилось — нереально.
Третью идею я было начал реализовывать, но уже в процессе увидел, что сделать такое красиво в 256 байт — слишком сложно. Но, в процессе что-то там переглючило и напомнило пружину. Вот эту идею я, в итоге, и развил:
Не буду здесь описывать как программировать под Vectrex, поскольку уже подробно делал это в своей статье. Упомяну только, что принцип вывода изображения у этого компьютера радикально отличается от большинства остальных — он векторный. Причём, с некоторыми оговорками можно отображать не только прямые линии, но и кривые (чем, собственно, я и воспользовался).
Важным признаком хорошей демо является, на мой взгляд, использование преимуществ именно той платформы, под которую пишется работа. Сильные стороны есть почти у всех компьютеров — на одних это аппаратный скроллинг, на других поддержка большого количества спрайтов, на третьих — мощный процессор. В данном случае оказалось возможным нарисовать «витки» пружины не при помощи вычислений координат каждой точки кривой, как это пришлось бы делать на других компьютерах, а аппаратным способом: при нарастающем напряжении на ЦАП канала Y (вертикаль), через короткие равные промежутки времени менять напряжение на ЦАП канала X (горизонталь), в результате чего получалась кривая, похожая на синусоиду. Причём, для каждой последующей «пружины» амплитуда отличалась и, соответственно, они имеют разную ширину («диаметр»).
Очевидно, пружины должны сжиматься, иначе какие это пружины? «Сжатие» обеспечивается изменением напряжения по вертикали. Говоря точнее, изменением разницы между напряжением в точке, где луч начинает отрисовку пружины (внизу) и точкой, где пружина заканчивается (вверху).
Однако, простым увеличением и уменьшением этой разницы здесь не отделаешься — для пружин в такой ситуации естественна разная скорость сжатия взависимости от текущей растянутости. И вот тут уже без вычислений не обойтись… Понятно, что процессор 6809 команды для вычисления синусов не имеет, а табличка синусов для 256 байтной интро — слишком большая роскошь. К счастью, покопавшись в исходниках ПЗУ Vectrex, я обнаружил там по адресу $fc6d табличку, отлично подходящую для такого случая:
RTRIGS FCB 0,25,50,74 SINE TABLE- 16 ANGLES/QUADRANT
FCB 98,121,142,162 VALUES REPRESENT SINE*256
FCB 181,198,213,226
FCB 237,245,251,255
FCB 255,255,251,245
FCB 237,226,213,198
FCB 181,162,142,121
FCB 98,74,50,25
Единственное, что нужно было сделать — привести значения к нужному масштабу, разделив на 4 двумя сдвигами вправо:
lsra
lsra
В результате, пружина сжимается довольно естественно, а заодно и поднимается/опускается по тому же закону — с ускорением и замедлением.
Теперь следующая проблема — кому интересна единственная пружина, прыгающая на одном месте? Так ни компо не выиграешь, ни лайков не наберёшь — пружины должны прыгать одна за другой.
С горизонтальным перемещением, допустим, всё просто. Достаточно равномерно менять напряжение в канале X ЦАП и всё произойдёт само — уехав за экран, где произойдёт переполнение (за -127), пружина автоматически появится с другой стороны экрана (+127). Сложнее дело обстоит с синхронностью прыжков — в живой природе пружины никогда не идут в ногу, у них всегда есть некоторая рассинхронизация в прыжках.
Поскольку состояние пружины в любой момент времени полностью определяется выбранным из таблицы синусов значением, логично хранить это значение отдельно для каждой из пружин. Выбираем адрес в ОЗУ, где будем это дело хранить. Но, предварительно, нужно эту табличку проинициализировать начальными значениями. И не любыми, чтобы в шествии был некоторый порядок. Резервируем несколько байт в ПЗУ, а потом копируем в ОЗУ:
springs equ $C890 ; index in sine table for each spring
...
ldu #springstmp
ldx #springs
lda #(3*3)
jsr Move_Mem_a ; A - byte count, U - source, X - destination
...
springstmp:
db 15, 20, 25
Как легко видеть, всю эту радость очень легко полностью выкинуть, просто найдя в BIOS адрес, где идут три подходящих байта подряд. Просто изначально я не думал, что обойдусь тремя пружинами, а потом убирать это уже не было времени, да и смысла (всё и так влезло в 256 байт).
Табличка синусов небольшая, поэтому если на каждую итерацию стандартного цикла Vectrex «по кадрам» брать следующее значение, пружины будут скакать очень быстро. Поэтому, берём из таблички следующее значение не каждую итерацию (но рисуем-то конечно в каждую, иначе будет мерцать):
ldb ,x ; загружаем в b индекс в таблице синусов для очередной кривой (по адресу, находящемуся в регистре x)
...
lda frames_c
bita #$03
bne skipinc ; пропуск
incb ; следующий индекс в таблице синусов
skipinc:
stb ,x+ ; сохраняем индекс из b обратно, откуда взяли. И заодно увеличиваем адрес в x на единицу
Ещё для красоты добавим на кончик пружины яркую светящуюся точку — перед тем, как выключить луч, слегка задерживаем его там:
ldb #$40 ; end dot brightness (20-30 is ok for release)
repeat_dot:
decb
bne repeat_dot
clr <VIA_shift_reg ; Blank beam in VIA shift register
Внизу рисуем линию «земли» (к слову, из-за того, что рисование прямой происходит через BIOS, приходится задавать максимальный масштаб. В случае с кривой масштаб задавать не надо, т.к. мы её рисуем полностью «вручную»):
lda #$ff ; максимальный масштаб
sta <VIA_t1_cnt_lo
ldd #(-60*256+(-54)) ; Y,X. Перемещаем выключенный луч в начальную точку
jsr Moveto_d
ldd #(0*256+(127)) ; Y,X Перемещаем включенный луч до конечной
jsr Draw_Line_d
Много места также отнимает надпись внизу. Так что, я решил ограничиться сокращённым названием и указанием размера. Впрочем, была и другая причина. Дело в том, что из-за, во многом, аналоговой природы схем Vectrex'a, его конкретным экземплярам присущи всякие искажения. Конкретно в моём это выражается в двух вещах:
1. При выводе строк процедурами BIOS (там с помощью пунктирных векторов имитируется растровые символы) при увеличении длины строки она начинает наклоняться, так что где-то после 10 символа уже трудно что-то разобрать.
2. С уменьшением высоты, пружины начинает перекашивать влево. Это не «так задумано» — это проблема конкретного экземпляра Vectrex'a. Я попросил владельца двух других машин заснять, как выглядит intro у него. Легко заметить, что разница существенная:
Хотя звук в 256 байтных intro встречается довольно редко, если такая возможность есть — грех ей не воспользоваться.
Чем хорош BIOS Vectrex — в нём припасена не только простая подпрограмма для проигрывания примитивных мелодий, но и несколько примеров самих мелодий (по десятку-другому байт на каждую). Для большой демо это конечно нельзя использовать, но для 256 байт intro — отличная «вишенка на торте»:
ldu #$fe38
jsr Init_Music_chk ; Initialize the music
...
; внутри цикла, регулярно:
jsr Do_Sound
Надо сказать, что в итоге код получился далёким от совершенства, за что мне немного стыдно учитывая, что ассемблер 6809 очень мощный и приятный, так что не получится сослаться на него, как на причину (как это можно было бы сделать в случае, скажем, с моей предыдущей работой под Videopac, у которого был весьма неприятный для программирования Intel 8048). Полагаю, что имея опыт в программировании под 6809, можно было бы достаточно легко уменьшить нынешние 255 байт до 180-200.
Большое количество регистров, в том числе 16-разрядных, очень экономит нервы и байты. При этом, однако, любопытно, что увеличение/умненьшение регистра на единицу есть только для a,b и d. А вот для индексных (и u) подрузамевается, что при нормальном программированиии они увеличиваются на единицу в рамках команд типа lda ,u+
Даже add не работает с этими регистрами. Впрочем, есть выход. Можно увеличивать и уменьшать их на любое количество байт командами вида leau N,u
- Исходник: https://github.com/petersobolev/springs
- Бинарник: http://enlight.ru/files/springs.zip
- На pouet.net: http://www.pouet.net/prod.php?which=71620
- Другие мои работы под разные древние платформы: http://enlight.ru/roi/
7 комментариев
— офигенно сказано!
Отличная интра, отличная статья! Правда, пружины начинают сжиматься ещё в воздухе, до соприкосновения с поверхностью, как будто предчувствуют и сжимаются от страха :) Но это, конечно, уже детали!
Фрог, спасибо тебе!
Опечатка?