; starfish.asm ; Andy Goth ; Written 7 September 2021 ; http://andy.junkdrome.org/devel/starfish/ ; ; Cute display hack, written for the Function 2021 256-byte competition. ; https://2021.function.hu/ ; Special thanks to: ; - Attila Szabo, for encouraging me to join ; - Tony Ha, for rearranging and eliminating some code ; - Michael Kubel, for helping me use SI-based pointers ; - All of the above, for good conversation during the development process ; ; This code is written for Borland Turbo Assembler 3.1 and MS-DOS. I ; developed it on an AST Advantage! Pro 486DX/33 running MS-DOS 5.0. Most ; of the writing was done in the MS-DOS Editor 1.1, and the early debugging ; was done in Borland Turbo Debugger 3.1. IDEAL ; Needed for syntax regularization. MODEL TINY ; Needed for *.COM output. P386N ; Needed for P387 (next line). P387 ; Needed for fsin. DOSSEG CODESEG ORG 100h ; Needed for *.COM output. _main: ; Initialization. ----------------------------------------------------------- ; At the program start, assume VGA 80x25 color text mode is selected, as if ; by mov ax,0003h;int 10h. Furthermore, assume AX=BX=CH=0 and CL=0ffh. See ; https://web.archive.org/web/20170129102219/http://pferrie.host22.com/misc/lowlevel12.htm ; which documents not only the initial register values but also how they got ; that way. Another initial condition is an empty keyboard buffer, so there's ; no need to flush it. Credit goes to Tony Ha for pointing this out to me. ; **OPTIONAL** ; Borland Turbo Debugger "helpfully" zeroes out various registers that DOS ; itself does not. However, Borland fails to reinitialize the FPU when ; restarting the program. Thus, these lines are necessary for the sake of ; debugging and can be taken out in the final release. mov cl,0ffh ; Get back to the DOS initial value. finit ; {}. ; Initialize pointer through which memory variables are accessed. mov si,OFFSET @vars ; Set base address. ; Hide cursor. The initial value of BH is 0, which corresponds to page 0. mov ah,02h ; Set cursor position. mov dx,0c800h ; Move cursor offscreen. int 10h ; Do it. ; Construct font. The initial value of AL is 0, which is used as the bit ; pattern for character 0. Each character's bit pattern is simply the ; number of the character, e.g., character 1 has the rightmost pixel set. mov di,OFFSET @buf ; Store into font buffer. @initFontBuffer: stosb ; Store first scan line. stosb ; Store second scan line. inc al ; Advance to next pattern. jnz @initFontBuffer ; Continue populating font buffer. ; Load font. The initial BL is 0, which is used as the font block number. mov ax,1110h ; Load custom font. lea bp,[buf] ; Pointer to font data, ES is already set. inc cx ; Character count. Initial CX=255, we want 256. xor dx,dx ; Start at character zero. mov bh,02h ; 2 scan lines per character. int 10h ; Do it. ; Load constants into FPU. These will remain for the entire execution. fld [starfish] ; {f}. fld [toBam] ; {b,f}. fld [aspect] ; {s,b,f}. ; Initialize ES register. Using push/pop saves one byte versus mov. push 0b800h ; Text display segment. pop es ; Load into ES. ; Main loop. ---------------------------------------------------------------- ; This part of the program repeats until a key is pressed. ; The main loop repeats for each frame. @mainLoop: mov [y],100 ; Start at y=100. xor di,di ; Start drawing at address b800:0000. ; The row loop repeats for each new row. @rowLoop: mov [x],-320 ; Start at x=-320. fild [y] ; {y,s,b,f}. fld st(0) ; {y,y,s,b,f}. fmul st(1),st(0) ; {y,y^2,s,b,f}. ; The character loop repeats for each character, i.e. each 8-pixel group. @charLoop: mov dx,8000h ; DH=80h=bit mask, DL=0=character bits. ; The pixel loop repeats for each pixel in the character. For each pixel, ; compute one of the two spirals being displayed. The hypotenuse and angle ; of the rightmost pixel will be left over afterward and will feed into the ; calculations for the starfish effect and the second spiral, which are ; displayed at lower (1/8) horizontal resolution than the first spiral. @pixelLoop: fld st(0) ; {y,y,y^2,s,b,f}. fild [x] ; {x,y,y,y^2,s,b,f}. fmul st(0),st(4) ; {x*s,y,y,y^2,s,b,f}. fld st(0) ; {x*s,x*s,y,y,y^2,s,b,f}. fmul st(0),st(0) ; {(x*s)^2,x*s,y,y,y^2,s,b,f}. fadd st(0),st(4) ; {(x*s)^2+y^2,x*s,y,y,y^2,s,b,f}. fsqrt ; {h=sqrt((x*s)^2+y^2),x*s,y,y,y^2,s,b,f}. fistp [h] ; {x*s,y,y,y^2,s,b,f}. fpatan ; {atan2(y,x*s),y,y^2,s,b,f}. fmul st(0),st(4) ; {a=atan2(y,x*s)*b,y,y^2,s,b,f}. fistp [a] ; {y,y^2,s,b,f}. mov al,[BYTE PTR h] ; AL=h add al,[BYTE PTR j] ; AL=h+j. add al,[BYTE PTR a] ; AL=h+j+a. test al,8 ; Check 8 bit of h+j+a. jz @skipBit ; If bit is set, skip setting pixel. or dl,dh ; Otherwise set pixel in character. @skipBit: inc [x] ; Increment X coordinate. shr dh,1 ; Shift bit mask. jnz @pixelLoop ; Loop over all eight bits of the character. ; Per-character math. fild [j] ; {j,y,y^2,s,b,f}. fild [a] ; {a,j,y,y^2,s,b,f}. fsubr st(0),st(1) ; {j-a,j,y,y^2,s,b,f}. fmul st(0),st(6) ; {(j-a)*f,j,y,y^2,s,b,f}. fsin ; {sin((j-a)*f),j,y,y^2,s,b,f}. fimul [c10] ; {sin((j-a)*f)*10,j,y,y^2,s,b,f}. fiadd [c10] ; {sin((j-a)*f)*10+10,j,y,y^2,s,b,f}. faddp ; {sin((j-a)*f)*10+10+j,y,y^2,s,b,f}. fiadd [h] ; {sin((j-a)*f)*10+10+j+h,y,y^2,s,b,f}. fistp [buf] ; {y,y^2,s,b,f}. ; Determine the color. mov al,[BYTE PTR buf] ; AL=sin((j-a)*f)*10+10+j+h. mov bl,5dh ; Try bright magenta on dim magenta background. test al,18h ; Check if AL%64 is 0-7 or 32-39. jz @colorEnd ; If so, use magenta. mov bl,4ch ; Else, try bright blue on dim blue background. test al,20h ; Check if AL%64 is 8-31. jz @colorEnd ; If so, use blue. mov bl,19h ; Else use bright red on dim blue background. @colorEnd: ; Determine the color intensity. mov al,[BYTE PTR h] ; AL=h. sub al,[BYTE PTR j] ; AL=h-j. sub al,[BYTE PTR a] ; AL=h-j-a. test al,8 ; Check 8 bit of h-j-a. mov ah,bl ; Provisionally use the color decided above. mov al,dl ; Use the spiral bit pattern decided far above. jz @bright ; If bit 8 is not set, use bright colors. and ah,07h ; Else use a black background and dim foreground. @bright: stosw ; Store the pixels and colors, and update DI. ; End of character. cmp [x],320 ; Check for end of row. jl @charLoop ; If not, process the next character. ; End of row. Get rid of the row-specific FPU values and return the FPU ; state back to when it contained only constants. Do this by abusing fcompp ; which compares the top two values (don't care) and pops them both (needed). ; The alternative is two ffree and fincstp instructions, so this saves six ; bytes. After that, go down one row. fcompp ; {s,b,f}. dec [y] ; Advance to the next row. cmp [y],-100 ; Check if there are more rows. jg @rowLoop ; If so, process the next row. ; End of frame. inc [j] ; Increment frame counter. mov ah,01h ; Check for keystrokes. int 16h ; Do it. jz @mainLoop ; If no keystroke, continue running. ; Rude shutdown. This leaves the screen scrambled, so type "MODE 80" to fix. ; ret ; Just exit. ; **OPTIONAL** ; Graceful shutdown code that restores the screen and keyboard. mov ax,0003h ; Select VGA 80x25 text mode. int 10h ; Do it. mov ax,0c00h ; Flush keyboard buffer. int 21h ; Do it. mov ax,4c00h ; Exit program. int 21h ; Do it. ; Initialized data. --------------------------------------------------------- @vars: @aspect DD 0.3703704 ; Constant 10/27. @spiral DD 57.29578 ; Constant 180/pi. @toBam DD 81.48733 ; Constant 256/pi (180/pi * 64/45). @starfish DD 0.06205616 ; Constant pi*8/405 (pi/36 * 32/45). @10 DW 10 ; Constant 10. ; Uninitialized data. ------------------------------------------------------- @j DW ? ; Frame count, doesn't matter how it starts. @y DW ? ; Y coordinate, 100 (top) through -99 (bottom). @x DW ? ; X coordinate, -320 (left) through 319 (right). @a DW ? ; Angle in degrees, ranges -180 through 180. @h DW ? ; Hypotenuse in pixels, nonnegative. @buf DW 256 DUP (?) ; Font and miscellaneous temporary buffer. ; SI-relative variable locations. ------------------------------------------- aspect EQU DWORD PTR si + @aspect - @vars spiral EQU DWORD PTR si + @spiral - @vars toBam EQU DWORD PTR si + @toBam - @vars starfish EQU DWORD PTR si + @starfish - @vars c10 EQU WORD PTR si + @10 - @vars j EQU WORD PTR si + @j - @vars y EQU WORD PTR si + @y - @vars x EQU WORD PTR si + @x - @vars a EQU WORD PTR si + @a - @vars h EQU WORD PTR si + @h - @vars buf EQU WORD PTR si + @buf - @vars END _main