Да здравствует мыло душистое демосцена! И вам привет, дорогой читатель ;)

Начинаю цикл статей с разборами своих работ:

  1. 64b intro: radar (вы находитесь здесь)

  2. 64b nano game: snake64

С демосценой я познакомился примерно 25 лет назад (или чуть больше). Но тогда это выражалось лишь в просмотре 128–256-байтовых интро (и демок, конечно же) с изумлением а‑ля: «А что так можно было?» Думаю, у многих знакомство с этой киберкультурой начинается похожим образом :). Если вам эти слова мало о чём говорят, почитайте о демосцене скудную статью на Вики, ну и/или послушайте подкаст, а также посмотрите что люди умудряются сделать, укладываясь всего лишь, например, в 256 байт кода (справа у большинства работ есть ссылка на видео YouTube).

Полноценные интро на любимом ассемблере x86 я начал писать только 5 лет назад, в 2018 году. Именно тогда я отправил на знаменитый фестиваль Chaos Constructions (который, кстати, организаторы обещают возродить в 2024) два прода (от слова «production»): 256b intro StarLine (заняла 1-е место) и 64b intro radar (заняла 6-е место в том же compo). После этого демосцена меня засосала стала частью моей жизни, в которую время от времени я с энтузиазмом погружаюсь.

radar, 64b intro (дерганная GIF-версия, нормальную смотрите на YouTube по ссылке выше)
radar, 64b intro (дерганная GIF-версия, нормальную смотрите на YouTube по ссылке выше)

Пусть вас не смущает, что разбор я начал с работы, занявшей шестое место, а не первое. Такой выбор обусловлен тем, что её код более простой и короткий. Вместе с тем, там не так уж и мало трюков, с которыми начинающему (а может, и не только) самураю демосцены может быть интересно познакомиться. Так что не надейтесь на лёгкое расслабляющее чтиво :)

Погнали!

Давайте не будем тянуть кота за... усы, и посмотрим на код (качайте fasm 1).

; ------------------------------------------------------
; Radar 64-byte intro [main variant] (c) 2018-2019 Jin X
; ------------------------------------------------------

video_shift     =       10h     ; we use 9FFFh segment for video output instead of 0A000h
radar_radius    =       64      ; should be multiple of circles_step for the best effect
radbg_color     =       11h     ; [radar background color]
arrow_color     =       21h     ; should be more than radbg_color
circles_color   =       2       ; should be less than radbg_color
circles_step    =       10h     ; should be power of 2 for the best effect

use16
org     100h

        ; Assume: ax=bx=0 (if no cmd line params), cx=0FFh, dx=cs=ds=es=ss, si=100h, di=sp=0FFFEh (as a rule), bp=91Xh, flags=7202h or 0202h (all base flags including cf=0; if=1)

        ; Init
        mov     al,13h
        int     10h
        fild    qword [si]
        lds     ax,[bx]         ; ds=9FFFh (as a rule), ax=020CDh
        fptan                   ; st0=1=angle, st1=delta (we need about 0.02 - this order of first instructions allows to get near value)

        ; Main cycle            ; di=sp=0FFFEh, ds=9FFFh
.repeat:
        ; Fadeout
@@:     add     [di],dh
        sbb     [di],dh         ; for source [di]=0 result=0, cf=0
        inc     di
        jnz     @B              ; di=0, cf=0 (cos [0FFFFh]=0)

        ; Radar
        mov     cl,radar_radius
.next:  fld     st
        fsincos                 ; st0=cos(angle), st1=sin(angle), st2=angle, st3=delta
@@:     mov     [di],cx
        fimul   word [di]       ; cx*cos then cx*sin
        fistp   word [di]
        mov     ax,[di]
        xchg    bx,ax
        out     61h,al          ; sound
        cmc
        jc      @B              ; second pass (cos then sin)
        ; st0=angle, st1=delta
        imul    si,ax,320
        test    cl,circles_step-1 ; cf=0
        mov     dx,arrow_color + (not radbg_color)*256
        jnz     @F
        mov     dl,circles_color
@@:     mov     byte [bx+si+(160+100*320)+video_shift],dl
        loop    .next
        ; dh=not radbg_color

        fadd    st,st1          ; increase angle a tiny bit

        jmp     .repeat         ; dh=not radbg_color

;       in      al,60h
;       dec     ax
;       jnz     .repeat                 
;       ret

Здесь приведён чуть‑чуть модифицированный код (две инструкции fild + fmulp заменены на аналогичную одну fimul, в результате чего бинарь сократился до 61 байта вместо исходных 63-х).

Начну с того, что мы создаём программу в формате COM под DOS, поэтому при старте почти всегда (за исключением случаев запуска в экзотических типах/версиях DOS) регистры имеют следующие значения:

  • AX = BX = 0 (если в командной строке нет параметров с некорректным именем диска);

  • CX = 0FFh;

  • DX = CS = DS = ES = SS;

  • SI = 100h;

  • DI = SP = 0FFFEh (в некоторых экзотических случаях SP, но не DI, может быть равно 0FFFCh, но лично мне такого не встречалось);

  • BP=9??h (как правило даже 91?h, где «?» зависит от типа/версии DOS);

  • сброшены основные арифметические флаги (ZF, CF, SF, OF, PF, AF), а также DF (как после cld); флаг IF установлен (как после sti), т. е. прерывания разрешены.

Вы можете почитать об этом здесь. Сайзкодеры (люди, занимающиеся жёсткой оптимизацией кода по размеру исполняемого файла) очень часто используют эти значения.

Первое, что мы делаем — устанавливаем графический видеорежим 13h (320×200, 256 цветов), наиболее популярный в демосцене под DOS, поскольку каждый пиксель кодируется одним байтом, а весь экран (имеющий размер 64 000 байт) помещается в 64 КБ сегмент. И уже здесь мы используем наши сакральные знания о том, что на старте AH = 0, поэтому просто записываем номер видеорежима в AL и вызываем int 10h. Вжух — графический режим установлен!

Видеопамять отображается на сегмент 0A000h, поэтому для доступа к пикселям нам нужно записать это значение в какой‑то из сегментных регистров. Ну... или примерно такое значение. В смысле, примерно? Инструкция lds ax,[bx] загружает в пару регистров DS:AX значение dword по адресу BX. А что у нас по этому адресу (по адресу DS:0)? PSP, первое слово которого содержит значение 20CDh (опкод int 20h) — оно идёт лесом в AX (нам оно не нужно), а второе — сегмент за областью нашей программы. Так как COM‑программе отводится вся доступная память, то здесь будет значение 9FFFh, которое отправится в регистр DS (а в этом 16-байтовом блоке располагается инфа о том, что память закончилась... помните: «640 КБ хватит всем»?) В некоторых типах DOS такой блок отсутствует, и там записано 0A000h. Другие некоторые DOS резервируют несколько килобайт, и там может быть, скажем, 9F80h. Уф! Да, это немного опасный трюк, так как в последних 2-х случаях картинка поедет. Но он довольно популярен среди интро на 32 или 64 байта. Скажу по секрету (только никому!), интро немного заточено под DOSBox, а там всё будет ровно :). Теперь, имея в DS значение сегмента 9FFFh вместо 0A000h, нам нужно будет просто добавлять к адресу (смещению) значение 10h (video_shift), и всё будет работать так, как при DS = 0A000h. Вообще говоря, поскольку мне удалось сократить размер интро на пару байт (см. примечание после кода), и до предела в 64 байта есть целый мир аж 3 байта, мы могли бы заменить lds на пару push 0A000h + pop ds. Но не спешите радоваться. О причинах возможной печали я расскажу через абзац (и да, про fild qword [si] + fptan я тоже не забыл).

Не помню, что мотивировало меня разместить блок «Fadeout» перед основных блоком рисования «Radar», потому что сейчас, глядя на это безобразие, я не вижу особых на то поводов. Обычно в таких случаях говорят, что так сложилось по историческим причинам. Ну а раз так, то и об этом я расскажу немного позже, поскольку для объяснения даже такого небольшого куска кода нужно понимать, что (какая картинка) у нас поступает на вход цикла.

Кручу-верчу, запутать хочу

Записываем в CX радиус радара в пикселях: radar_radius = 64 (вернее, в CL, мы же знаем, что на старте CH = 0). Дублируем ST0 с помощью fld st. Стоп! А что у нас в ST0? Вернёмся в началу кода и посмотрим на инструкцию fild qword [si], которая загружает в ST0 значение qword из памяти по адресу SI = 100h. По адресу DS(CS):100h у нас начинается наш код (это же бубль-гум точка входа). А именно следующий блок:

        mov     al,13h
        int     10h
        fild    qword [si]
        lds     ax,[bx]

Эти 4 инструкции занимают ровно 8 байт (по 2 каждая), которые и загружаются в ST0. WTF? Да, бро, мы используем наш код как данные (вполне нормальная тема для сайзкодинга, привыкайте). Вот этот qword: 2CDF10CD13B0h. Отладкой выясняем, что мы загрузили число ≈ 5.599E+17. Красивое! А дальше следите за руками: после lds у нас идёт fptan (тут вы его не видите, но он есть). Который преобразует наше число в пару: 1.0 (в ST0) и ≈ 0.0294 (в ST1). It's a magic! Учите матчасть. Первым числом (1.0) будет исходный/текущий угол расположения стрелки радара (нас устроит любое значение, мы не гордые), а вторым (0.0294) — приращение угла в радианах после каждой отрисовки кадра (1.684 градуса, тоже норм). Так вот, если бы вместо lds у нас было что‑то другое, это не сработало бы, так как qword был бы другим, и значение приращения угла было бы непонятно каким. Однако британские учёные обнаружили, что если очень хочется сделать всё по науке (DS = $A000), то сделав финт ушами переставив некоторые инструкции, результат будет примерно таким же:

        fild    qword [si]
        mov     al,13h
        push    0A000h
        fptan
        int     10h
        pop     ds

Эта конструкция занимает на 2 байта больше и даёт нам значения 1.0 и ≈ 0.0337, которые нас вполне удовлетворяют (а если не видно разницы, зачем платить больше? Ну разве что в некоторых версиях DOS картинка не поедет, но тогда нужно выставить video_shift = 0). Вообще, сейчас я бы так и сделал, поскольку результат в 63 байта, как вы понимаете, не превышает 64-х байтов, но мне же нужно было познакомить вас с трюком с lds.

Как я догадался о том, что вот это всё так сработает и даст нужные значения? Ответ прост: мне очень хотелось, чтобы так случилось :))). А дальше методом экспериментов: перестановок инструкций, перебора типов (fld, fild) и размеров загружаемого значения (dword, qword, tbyte) и веря в успех, я нашёл нужную комбинацию (не просто же так fild стоит в таком странном месте). Ну и... magic, конечно же! Если вы думаете, что я шучу, то мне придётся вас разочаровать: sizecoding иногда требует и не таких экспериментов.

Отладчик Turbo Debugger, обратите внимание на область PSP (CD 20 FF 9F)
Отладчик Turbo Debugger, обратите внимание на область PSP (CD 20 FF 9F)

Ладно, вернёмся к нашему стаду коду. После дублирования ST0 у нас в стеке FPU наблюдается такой ряд чисел (от ST0 до ST2): 1.0 (angle), 1.0 (angle), 0.0294 (delta). Следующая инструкция fsincos вычисляет синус и косинус от ST0 (в первом кадре — от 1.0), записывая результат в ST1 и ST0 соответственно: 0.54 (cos), 0.841 (sin), 1.0, 0.294 (см. картинку выше). По адресу DS:DI мы записываем значение CX (радиус). Напомню, что DS = 9FFFh, DI = 0 (после цикла «Fadeout», см. ниже), а значит мы пишем в невидимую область около видеопамяти — это будет наша временная переменная (назовём её temp). Инструкцией fimul word [di] умножаем ST0 = 0.54 (косинус угла, изначально равного 1.0) на целое число temp, т. е. на CX, записывая результат обратно в ST0. Таким образом, мы получаем координату X (для первой итерации цикла CX первого кадра: round(64 * 0.54) = 35). Записываем её обратно в temp (fistp word [di]), удаляя из ST0, и оттуда загружаем в AX (да, обмен значениями с FPU возможен только через память, это печально). Меняем местами AX и BX (зачем — будет понятно позже). Записью в порт 61h (out 61h,al) выводим звук (лёгкое потрескивание... слышите?). Да, этот странный интересный звук генерирует одна 2-байтовая инструкция. Иногда её размещение в разных рандомных местах даёт вполне интересный аудиоэффект (хотя нередко получается лютый трэш, который только убивает всё впечатление от интро). Пользуйтесь, без регистрации и СМС.

Едем дальше. Инструкция cmc меняет флаг CF на противоположный. Последней инструкцией, которая влияла на этот флаг, была инструкция sbb (всё в том же «Fadeout»), которая сбросила CF в 0 (просто поверьте), дальше будет test, которая сделает то же самое. Так что, будьте уверены: на первой итерации внутреннего цикла (от первой метки @@ до jc @B) CF = 0, а после cmc CF = 1, поэтому в первом заходе jc @B осуществит прыжок к предыдущей метке @@. Это довольно распространённый трюк: организовывать так 2 или 3 итерации, меняя какой‑то флаг (иногда CF, но чаще даже PF с помощью однобайтовой инструкции inc или dec): не нужно инициализировать CX и делать loop, особенно когда CX уже используется (как раз наш случай).

Давайте посмотрим, что у нас осталось в стеке FPU: 0.841 (sin), 1.0, 0.294. Снова записываем в temp значение CX, далее умножаем его на 0.841 (уже синус угла). Получаем координату Y (для первой итерации цикла CX первого кадра: round(0.841 * 64) = 54). Записываем в temp (удаляя из ST0), затем в AX. Снова обмениваем AX и BX (успеваете?) В итоге у нас стек FPU содержит 1.0 (angle), 0.0294 (delta). Регистры: AX = X, BX = Y. Следующее выполнение cmc установит CF = 0, и jc @B не сработает.

Самое сложное позади. Можно выдохнуть, выпить чашечку чая или кофе. А пока вы греете чайник, я буду развлекать вас новыми картинками.

Пора брать холст, палитру и кисть

Палитра цветов по умолчанию для видеорежима VGA 13h
Палитра цветов по умолчанию для видеорежима VGA 13h

Палитра уже есть. Кисти рук — тоже. В качестве холста будет видеопамять. Так что, дело за малым. Но сперва несколько слов об организации этой самой видеопамяти (если вдруг кто не знает). Первый байт задаёт цвет самого левого верхнего пикселя на основании палитры, далее двигаемся вправо до конца строки (до байта с индексом 319, если считать с 0). Байт с индексом 320 — левый пиксель второй строки, 63 999-й — правый нижний. т. е. мы двигаемся слева направо и сверху вниз.

Инструкция imul si,ax,320 умножает AX (т. е. X) на 320 (кол-во точек в строке, т. е. на ширину экрана), записывая результат в SI. Далее с помощью test cl,15 мы проверяем, кратно ли текущее значение радиуса CX значению 16 (в этом случае будет ZF = 0, как и младшие 4 бита). С помощью mov dx,arrow_color + (not radbg_color)*256 заносим в DX значение 0EE21h. Здесь младший байт — цвет стрелки радара (смотрим на палитру — сине‑фиолетовый, можно было бы указать 20h — синий, — то тогда стрелка была бы более тонкой). О старшем байте пока не беспокойтесь. Инструкция jnz @F осуществляет переход к следующей метке @@, если радиус не кратен 16 (напоминаю, что mov на флаги не влияет). В противном случае меняем цвет DL на circles_color = 2 (зелёный). Здесь важно, чтобы значение arrow_color (цвет стрелки) было больше, чем radbg_color = 11h (серый цвет фона радара), а circles_color — меньше (скоро поймёте почему). И наконец, с помощью простой инструкции mov [bx+si+(160+100*320)+video_shift],dl (где 160+100*320 — координата центра экрана, которой требуется смещение video_shift, так как у нас DS = 9FFFh, а не 0A000h) мы выводим на экран кусочек нашего радара (один пиксель) :). Как говорил Иван Васильевич из известного фильма: «И всего делов‑то!» Внимательный читатель заметит подвох: «У нас AX = X, BX = Y, а мы умножаем X на 320 и добавляем Y, почему всё наоборот?» Ну перепутали мы X и Y, что это меняет? Кроме направления вращения :). Хотите, чтобы было всё по науке — перенесите xchg bx,ax выше, после первой метки @@. Завершает отрисовку инструкция loop .next, повторяющая внешний цикл с CX (радиусом) от 64 до 1. И fadd st,st1, добавляющая к углу в ST0 значение ST1 (0.0294, чуть больше градуса, напомню). У ещё одного внимательного читателя (вы оба здесь?) может возникнуть другое душное замечание: «При увеличении радиуса вращение должно происходить против часовой стрелки, но вы говорите, что обмен X и Y меняет направление, однако стрелка радара всё‑таки вращается против часовой стрелки. Вы явно что‑то не договариваете!» Всё правильно, ведь у нас ось ординат (Y) направлена сверху вниз, а не снизу вверх, как по классике. Ещё вопросы? «Минуточку, а как же Fadeout...» — «Уведите его!» Вот и славненько!

Итак, алгоритм рисования радара:

  1. FPU стек: angle, delta.

  2. radius = 64.

  3. Вычисляем синус и косинус, сохраняя угол: cos(angle), sin(angle), angle, delta.

  4. Умножаем: X = cos(a) * radius; Y = sin(a) * radius.

  5. Если radius and 15 == 0 (можно читать как radius % 16 == 0), color = circles_color, иначе color = arrow_color.

  6. Выводим точку цветом color по координатам X, Y.

  7. if (--radius > 0) goto 2.

Всё просто, не так ли? :))

Внесите занавес!

Я обещал рассказать про «Fadeout». Пацан сказал — пацан сделал.

Странная на первый взгляд конструкция add [di],dh + sbb [di],dh производит эффект «затухания» стрелки до цвета фона, оставляя белый шлейф. Работает это следующим образом: если цвет больше, чем not DH (not 0EEh = 11h = radbg_color), он уменьшается на 1, иначе он не меняется. Если посмотреть на палитру цветов, то видно, что от 21h до 11h цвета меняются от синего резко к белому и далее плавно к тёмно‑серому (стрелка — шлейф — фон).

Объясняю медленно. Сначала add добавляет к цвету значение 0EEh, а sbb, внезапно, это значение вычитает, вычитая ещё и значение флага CF. Нас интересуют исходные значения 11h и больше него (например, 12h).

  • Ситуация № 1: 11h + 0EEh = 0FFh, переноса не было, CF = 0, далее: 0FFh – 0EEh – CF(0) = 11h. Я не обманул, значение не изменилось.

  • Ситуация № 2: 12h + 0EEh = 0, произошёл перенос, CF = 1, далее: 0 – 0EEh – CF(1) = 11h. Значение уменьшилось на 1.

Теперь понимаете, почему цвет arrow_color = 21h должен быть больше, чем radbg_color = 11h (подсказка: иначе стрелка не будет гаснуть), а circles_color = 2 — меньше (подсказка: иначе кольца тоже будут гаснуть)?

Снова magic! Хотите ещё фокусов? Их есть у меня!

Весь этот балаган (начиная со второго кадра) повторяется 65 536 раз, побайтово перелопачивая весь сегмент, адресуемый регистром DS, поскольку значение DI увеличивается до изнеможения тех пор, пока не станет равным 0 (jnz @B).

Кстати, если при воспроизведении интро у вас возникает ощущение, что кто‑то впустил сюда мух, знайте: мерцающие точки в рандомных местах экрана — это результат работы этого цикла, когда значение уже увеличилось и попало на дисплей, не успев уменьшиться. Вы спросите: «Это баг или фича?» Разумеется, это специально задуманный эффект, а как же иначе?! :))

Выход без цыганочки

Стоит отдельно обратить внимание на закомментированную концовку:

;       in      al,60h
;       dec     ax
;       jnz     .repeat                 
;       ret

Это стандартная конструкция выхода из интро (к сожалению, с ней размер интро будет = 65 байтам, а это фейл). Читаем байт из порта клавиатуры 60h. Полученное значение хранит скан‑код последней нажатой клавиши. Если это значение = 1, значит мы удерживаем клавишу Esc. Значение AH = 0 (у нас радиус значительно меньше 256, так что тут без вариантов), поэтому dec ax при нажатии на Esc установит флаг ZF = 0 (как и значение AX). Ну и дальше, думаю, всё понятно. Инструкция ret осуществляет выход, поскольку при старте программы в стеке всегда лежит 0 (это даже задокументировано, в отличие от значений большинства регистров), а что по адресу CS:0, помните? Правильно, PSP, первое слово которого содержит инструкцию int 20h — завершение программы. Если по чесноку, то в данном случае затея хороша только при запуске из-под DOSBox, так как если мы пишем в сегмент 9FFFh, мы затираем системные данные с информацией о структуре памяти — это может привести к неопределённому поведению (как говорят любители и профессионалы языка C).

Выше я писал, что интро немного заточено под DOSBox. Так вот, второй момент заточки заключается в том, что в программе нет задержки, и при запуске на реальном железе радар взлетит, унеся с собой монитор (зачем вот это?) На этот счёт есть ещё один трюк (нет, не привязать монитор верёвками). Можно сделать задержку с помощью однобайтовой инструкции hlt (разместив её перед jmp .repeat), которая будет ожидать любого аппаратного прерывания. Обычно «любое аппаратное прерывание» — это прерывание от таймера (int 8), которое возникает каждые 55 миллисекунд. Если только вы или ваш кот не решите плясать на клавиатуре (в этом случае, пока клавиатура не сломается, будет возникать ещё и int 9). Однако для данного интро такая задержка слишком велика, и мне пришлось просто ограничить скорость виртуального компьютера параметром DOSBox cycles = fixed 30000 :). Это, конечно, не совсем «true», но демосцена такое допускает (хотя у особо впечатлительных от таких слов лицо может приобрести странный оттенок). А так, заменив lds на сами знаете что и добавив hlt мы получим честные 64 байта.

P. S. Для справки: значение из порта 60h в старшем бите хранит флаг удержания клавиши: если бит сброшен, значит клавиша нажата и удерживается прямо сейчас, а если установлен — отпущена (т. е. если вы нажали и отпустили клавишу Enter — скан‑код 1Ch, — вы будете постоянно читать значение 9Ch, пока не ударите по какой-нибудь другой клавише). При этом скан‑код меняют не только значимые клавиши, но и модификаторы Ctrl, Alt, Shift, Win и даже Caps Lock и пр.

Какой-то кролик из старого кинескопа
Какой-то кролик из старого кинескопа

Спасибо

...что дочитали до конца! Надеюсь, мой юмор вас не утомил, а материал оказался интересным и, что не менее важно, полезным.

Продолжить общение о сайзкодинге можно в тематическом ламповом Telegram-чате или в международном Discord-сервере (а оттуда попасть на большую сцену).

Будьте здоровы, живите богато! ;)

Комментарии (30)


  1. martyncev
    09.09.2023 18:54
    +3

    Это магия какая-то.. Первый раз я столкнулся на 64к демках от farbrausch... и.. Ну вы поняли)


    1. jin_x Автор
      09.09.2023 18:54
      +5

      farbrausch — это легенда :)


      1. igor_suhorukov
        09.09.2023 18:54
        +1

        О, да! Их fr-08 шедевр, своего времени. Так же как и Exceed - Heaven Seven.

        IMHO, самое интересное в 3D демках начинаются от 4k только, как пример elevated by Rgba & TBC!


      1. martyncev
        09.09.2023 18:54

        Честно - не слежу за демосценой, но скажите - они еще существуют? Делают?))


        1. Adam_B
          09.09.2023 18:54
          +2

          Вот буквально вчера выпустили новое https://www.pouet.net/prod.php?which=95048


          1. igor_suhorukov
            09.09.2023 18:54

            О, они теперь в браузере демки предпочитают! Нашли Продвигают свою среду визуального программирования cables.gl для WebGL. Это тот редкий случай, когда инструментарий для демок превращается в что-то массово полезное.


  1. NooneAtAll3
    09.09.2023 18:54
    -1

    А зачем нужен звук, если всё равно выводится один треск?


    1. jin_x Автор
      09.09.2023 18:54
      +7

      Разве он не органичен в данном случае?


      1. NooneAtAll3
        09.09.2023 18:54

        ограничен чем? правила соревнования требуют звук?


        вырезать эти 2 байта и всё


        1. jin_x Автор
          09.09.2023 18:54
          +8

          Не оГРаничен, а оРГаничен :))

          Правила звука не требуют, но место есть, почему бы и нет? С другой стороны, если его убрать, можно сделать выход. Или что-нибудь ещё (всё-таки 5 байт — не шуточки).


  1. x67
    09.09.2023 18:54
    +1

    Круто, реально круто!)

    А я все жду когда кто-нибудь проапгрейдит ккригер или сделает еще более живую игру и можно даже в большей размерности)


    1. Wesha
      09.09.2023 18:54
      +3

      Смысл демок в том и состоит, чтобы на публику наглядно демонстрировать, у кого пипи нейросеть больше!


  1. BugM
    09.09.2023 18:54
    +6

    fun fact: Гифка в статье весит 166кб или примерно в 2500 раз больше чем программа которой она сделана.


    1. jin_x Автор
      09.09.2023 18:54
      +8

      При этом плавность оставляет желать лучшего.

      Но это ещё цветочки. Иногда видосы 256-байтовых интро занимают сотни мегабайт, имея при этом артефакты компрессии.

      В некотором степени интро является очень хорошо сжатым самораспаковывающимся видеофайлом :)


  1. vityo
    09.09.2023 18:54
    -17

    Если хотите подрочить на маленькое, вот я когда-то написал код на питоне, который число из цифр из аргументов, пишет словами из букв, склоняет, до миллиардов. Ещё папе приходилось это когда-то на FoxPro делать для программы зарплат, а я захотел написать свое и поменьше.


    import sys
    numbers = [['одна', 'две'], ['ноль', 'один', 'два', 'три', 'четыре', 'пять', 'шесть', 'семь', 'восемь', 'девять']]
    dozens = ['двадцать', 'тридцать', 'сорок', 'пятьдесят', 'шестьдесят', 'семьдесят', 'восемьдесят', 'девяносто']
    secondDozen = ['десять', 'одиннадцать', 'двенадцать', 'тринадцать', 'четырнадцать', 'пятнадцать', 'шестнадцать', 'семнадцать', 'восемнадцать', 'девятнадцать']
    hundreds = ['сто', 'двести', 'триста', 'четыреста', 'пятьсот', 'шестьсот', 'семьсот', 'восемьсот', 'девятьсот']
    large = [['тысяча', 'тысячи', 'тысяч'], ['миллион', 'миллиона', 'миллионов'], ['миллиард', 'миллиарда', 'миллиардов']]
    o, s, l = ('', str(int(sys.argv[1])), len(str(int(sys.argv[1])))) if len(sys.argv) == 2 and len(sys.argv[1]) > 0 and sys.argv[1].isdigit() and int(sys.argv[1]) >= 0 and int(sys.argv[1]) < pow(10, 12) else exit(-1)
    d, dd = lambda i : int(s[-i]) if l >= i else 0, lambda i : int((s[-i:] if i == 2 else s[-i:-i + 2]) if l >= i else 0)
    for p in [10, 7, 4, 1]:
        dp, dp1, dp2, isP, idP = d(p), d(p + 1), d(p + 2), dd(p + 1) in range(10, 20), min(max(dd(p + 1) - 10, 0), 9)
        o += ''.join([' ' + hundreds[dp2 - 1] if dp2 != 0 else '', ' ' + dozens[dp1 - 2] if dp1 >= 2 else '', ' ' + secondDozen[idP] if isP else ''])
        o += ' ' + (numbers[0][dp - 1] if p == 4 and dp in range(1, 3) else numbers[1][dp]) if l == 1 and p == 1 or dp != 0 and not isP else ''
        pType = -1 if p == 1 or l <= p - 1 else 2 if isP or dp >= 5 else 1 if dp >= 2 else 0 if dp == 1 else 2 if dp1 >= 1 or dp2 >= 1 else -1
        o += ' ' + large[(p - 4) // 3][pType] if pType != -1 else ''
    print(o[1].upper() + o[2:])


    1. vityo
      09.09.2023 18:54
      -9

      Какие нежные и к себе принимаете. А мне тоже обидно, что на мой сленг вы видите нечто угрожающее. Статья о сжатии, даже автор говорит о неком гольфкодинге. Я и поделился своим. Это форум, или мы должны только четко-четко по делу восхвалять или опускать методы статьи?


    1. wormball
      09.09.2023 18:54
      +8

      image


    1. alan008
      09.09.2023 18:54
      +4

      Поясню, почему минусуют. Чтобы написать то, что в статье, надо обладать кучей специфических знаний и техник, за пределами даже базового ассемблера. Скажем так, тут сразу видно, что человек потратил годы, чтобы начать разбираться на таком уровне. А чтобы написать то, что в вашем примере, достаточно знаний базового питона, восьмиклассник напишет.


  1. forthuse
    09.09.2023 18:54
    +1

    Сайзкодеры (люди, занимающиеся жёсткой оптимизацией кода по размеру исполняемого файла)

    Есть даже специальные площадки для соревнований по решению задач минимальным количестве байт или символов языка включающих и ассемблер.


    К примеру:


    Здесь вывод ASCII таблицы на ассемблере занял в минимальном решении 74-е байта


    Такой ASCII таблицы:
     -------------
    0:   0 @ P ` p
    1: ! 1 A Q a q
    2: " 2 B R b r
    3: # 3 C S c s
    4: $ 4 D T d t
    5: % 5 E U e u
    6: & 6 F V f v
    7: ' 7 G W g w
    8: ( 8 H X h x
    9: ) 9 I Y i y
    A: * : J Z j z
    B: + ; K [ k {
    C: , < L \ l |
    D: - = M ] m }
    E: . > N ^ n ~
    F: / ? O _ o DEL

    Как и в представленной статье 64-е байт интро — такой полученный результат выглядит как магия.


    P.S. Возможно и набор таких задачи для решения, как "побочный" результат, позволяет отрабатывать методы эффективной транспиляции алгоритмов в разные языки и с них
    в бинарный код выполняющийся в рамхах используемого процессорного железа.


    1. jin_x Автор
      09.09.2023 18:54
      +1

      Да, сайзкодинг — это оптимизация по размеру бинарника, а гольфкодинг — по размеру исходника. Это тоже прикольная штука, я пару раз участвовал (например, тут на C).

      В демосцене есть платформы, называемые фэнтези консолями (например, TIC-80, MicroW8 и пр.) Так вот, в TIC-80 исходник не компилируется и хранится в файле в сжатом (заархивированном) виде. По сути, такой сайзкодинг отчасти правильнее назвать гольфкодингом, т.к. чем меньше исходник, тем меньше и сжатый файл. В общем случае. Хотя иногда увеличение размера исходного кода (ради того, чтобы в нём были повторяющиеся символы) может привести к уменьшению сжатого файла. Поэтому я написал "отчасти" :)


      1. forthuse
        09.09.2023 18:54
        +1

        Да, но по ссылке на площадку выше, вроде, для ассемблера и считаются полученные байт коды т.к. даже вкладка символов не активна, а для других языков, вероятно, в силу трудности такого подсчёта вкладки байтов и символов особо не различаются.


        P.S. На ассемблере реализованы и Уроки от NeHe на masm64 по OpenGL.


    1. jin_x Автор
      09.09.2023 18:54
      +1

      Сайзкодинг, кстати, как правило, имеет одну важную особенность: необходимо не просто ужать код насколько это возможно, а привести его к целевому размеру (64, 256 байт и т.д.).

      В гольфкодинге люди соревнуются в стиле "у кого меньше, тот и молодец" :)


  1. Dark_Purple
    09.09.2023 18:54

    Вжух — графический режим установлен!


  1. Moog_Prodigy
    09.09.2023 18:54
    +4

    Напомнило, как я в детстве делал "радар" из старого лампового телевизора. Какая видеокарта, какая память? Будем рулить напрямую обмотками отклоняющей системы. Схема донельзя проста, небольшой трансформатор и два конденсатора, по сути это характериограф, показывающий на 90 сдвиг фаз, в результате чего уже имеем круг (подбором емкости можно настроить именно круг а не овал на 4:3 ЭЛТ). Центральная сканирующая полоска была по сути включением-выключением отклоняющих обмоток, и ее можно было модулировать. Модуляцией и положением сканирующей полоски управлял реальный ультразвуковой радар с сельсином на оси, УЗ часть была взята за основу из книжки "юный кибернетик".

    Что только не придумаешь, чтобы не учить ненавистные уроки) Тоже в своем роде, электронная демосцена, как из г. и палок собрать нечто неочевидное и крутое.


    1. igor_suhorukov
      09.09.2023 18:54
      +1

      Аналоговое демо, тру! Цифровые демки на осцилографах есть и сейчас.

      Youscope (oscilloscope demo)


    1. igor_suhorukov
      09.09.2023 18:54
      +1

      Phosphora (Oscilloscope Demo, 1st Place @ Nova 2023 Wild Compo)


  1. ovn83
    09.09.2023 18:54

    Когда в школе учился, делал 3d куб вращающийся на Бэйсике для Спектрума.


  1. NutsUnderline
    09.09.2023 18:54

    это все очень круто, но fsincos - это уже чистерство какое то :) :)


    1. jin_x Автор
      09.09.2023 18:54

      На SSE/AVX даже синус так просто не вычислишь (особенно в упакованном формате) :)


  1. Tuxman
    09.09.2023 18:54

    В фидо в 90х была похожая uue эха, и потом файлэха с демками. Никто не называл это демосценами. Мне запомнился больше всего mars.com файлик, вроде килобайт на 4, там без каких-либо шейдеров из GPU, просто поверхность марса хорошо генерируется, и мышкой можно летать бесконечно.