programming-examples/assembly/Multi-function fractal demonstration program which results in 255 byte program.asm
2019-11-15 12:59:38 +01:00

294 lines
12 KiB
NASM

; teeny program displays the Mandelbrot set.
;
; Home Up PgUp
; Left Right correspond to 8 obvious directions
; End Dn PgDn
;
.model TINY
;JUMPS ; without this, see caveat under 8086 above
NONE = 00h ; use this for no features
PRINTZOOM = 01h ; printout and beep features
MODECHANGE = 02h ; support video mode change?
SPEED = 04h ; use 386 instructions for speed
STARTCOORDS = 08h ; use starting coordinates (instead of 0,0)
HIRES = 10h ; use hi resolution (single mode version only)
; choose the desired features from the feature list above, and OR them
; all together as shown below:
FEATURES = PRINTZOOM OR MODECHANGE OR STARTCOORDS OR SPEED OR HIRES
if (FEATURES AND SPEED)
.386
endif
ifdef (FEATURES AND HIRES)
VIDMODE = 12h ; use mode 12h
PIXWIDTH = 640 ; ... which is 640x480
PIXHEIGHT = 480
else
VIDMODE = 13h ; use mode 13h
PIXWIDTH = 320 ; ... which is 320x200
PIXHEIGHT = 200
endif
TEXTMODE = 3 ; our exit video mode (80x25 color text mode)
ZOOMLIMIT = 13 ; can change to up to 13 for extended zoom in
VIDEO_INT = 10h ; BIOS video services interrupt
WRITE_PIXEL = 0Ch ; write pixel video service
WRITE_CHAR = 0eh ; write char in TTY mode video service
CHANGE_MODE = 00h ; change mode video service
KEYBD_INT = 16h ; BIOS keyboard services interrupt
; ASCII codes
EXTENDED = 000h ; no ASCII code for extended key codes
BELL = 007h ; the ASCII bell char to make a beep
CR = 00dh ; a carriage return character
ESCAPE = 01bh ; the escape key
PLUS = 02bh ; ASCII code for '+' key
V_KEY = 'v' ; ASCII code for video mode switch
; keyboard scan codes
MINUS = 04ah ; scan code for gray '-' key
; feel free to experiment with the following constants:
DELTA = 100 ; the unit of pan movement in pixels
THRESHOLD = 4 ; must be in the range of (0,255)
STARTSCALE = 7 ; a number from 0 to ZOOMLIMIT, inclusive
STARTX =-DELTA ; to the right by 1 delta unit (STARTCOORDS feature)
STARTY =-DELTA ; down by 1 delta unit (STARTCOORDS feature)
CHAR_COLOR = 0fh ; white on black background (for PRINTZOOM feature)
.code
org 100h
;****************************************************************************
;
; Here's the main routine, and it's a bit convoluted.
;
;****************************************************************************
Start proc
ife (FEATURES AND MODECHANGE)
mov ax,VIDMODE
int VIDEO_INT
endif
if (FEATURES AND STARTCOORDS)
mov bp,STARTX
mov di,STARTY
else
xor bp,bp ; zero initial X offset
xor di,di ; initial Y offset is identical
endif
if (FEATURES AND MODECHANGE)
mov si,offset VidTbl; point to default video table
jmp @@ChgMode
video STRUC
ScrnMode dw ? ; the mode number for BIOS' purposes
ScrnWidth dw ? ; pixel width of screen minus one
ScrnHeight dw ? ; full height of screen in pixels
NextMode dw ? ; pointer to next video structure
video ENDS
VidTbl video <54h, 800-1, 600, ($ + 2)> ; highest res
video <13h, 320-1, 200, ($ + 2)> ; lowest res
video <12h, 640-1, 480, offset VidTbl> ; next to lowest res
else
jmp @@Render ; leap right in there and draw
endif
@@TryPlus:
cmp al,PLUS ; Q: gray + key?
mov al,[scale] ; get the scale factor in al now
jnz @@TryMinus ; N: maybe it's something else
dec al ; Y: it's plus so zoom out
js @@beep ; if AL<0, balk - can't zoom that far
sar bp,1 ; adjust offsets for new scale so
sar di,1 ; we stay in the same place
jmp @@AdjustScale
@@TryMinus:
cmp ah,MINUS ; Q: gray - key?
jnz @@ReadKey ; N: it's not a valid key
inc al ; Y: zoom in
cmp al,ZOOMLIMIT ; Q: have we zoomed too far?
ja @@beep ; Y: yes, so just beep and don't adjust
sal bp,1 ; adjust offsets for new scale so
sal di,1 ; we stay in the same place
@@AdjustScale:
mov [scale],al ; update the scale value
@@Render:
if (FEATURES AND PRINTZOOM)
mov al,'0'+ZOOMLIMIT; maximum printable character
sub al,[scale] ; invert the sense
call PrintChar ; show the character
mov al,CR ; print a carriage return (no line feed -
call PrintChar ; we don't want to advance to next line)
endif
;****************************************************************************
; Draw
; This routine is the fractal drawing engine. It has been
; optimized for size, sacrificing speed.
;
;****************************************************************************
if (FEATURES AND MODECHANGE)
mov cx,(video ptr [si]).ScrnHeight
push si ; we do this because it's very slow
; if we read the Width from memory
; every inner loop iteration
mov si,(video ptr [si]).ScrnWidth
else
mov cx, PIXHEIGHT ; height of screen in pixels
endif
sub di,cx ; adjust our Y offset
@@CalcRow:
push cx ; save the row pointer on the stack
if (FEATURES AND MODECHANGE)
mov cx,si ; fetch the screen width
else
mov cx, PIXWIDTH-1 ; width of screen in pixels
endif
sub bp,cx ;
@@CalcPixel:
push cx ; save the column counter on stack
xor cx, cx ; clear out color loop counter
xor bx, bx ; zero i coefficient
xor dx, dx ; zero j coefficient
@@CycleColors:
push dx ; save j value for later
mov ax, bx ; ax = i
sub ax, dx ; ax = i - j
add dx, bx ; dx = i + j
stc ; one additional shift, please
call Shifty ; ax = ((i+j)*(i-j)) shifted right
pop dx ; retrieve our saved value for j
add ax,bp ; account for base offset...
cmp ah,THRESHOLD ; Q: is i > THRESHOLD * 256?
xchg bx,ax ; now swap new i with old i
jg @@draw ; Y: draw this pixel
clc ; no additional shifts here, please
call Shifty ; now dx:ax = old i * j
xchg dx,ax ;
add dx,di ; account for base offset...
inc cl ; increment color
jnz @@CycleColors ; keep going until we're done
@@draw:
xchg ax, cx ; mov color into al
pop cx ; retrieve our column counter
pop dx ; fetch row (column already in cx)
push dx ; must leave a copy on the stack
xor bx,bx ; write to video page zero
mov ah,WRITE_PIXEL ; write pixel command
int VIDEO_INT ; video BIOS call
inc bp ; adjust our X base value
loop @@CalcPixel ; keep going until we've done a line
inc di ; adjust our Y base value
pop cx ; keep going until we've done 'em all
loop @@CalcRow ; more rows?
if (FEATURES AND MODECHANGE)
pop si ; restore vid ptr if we use one
endif
@@beep:
if (FEATURES AND PRINTZOOM)
mov al,BELL ;
call PrintChar ;
else
mov ax,((WRITE_CHAR SHL 8) OR BELL) ; make a beep
int VIDEO_INT ; (bx=0 -- any video page, any char attr)
endif
@@ReadKey:
xor ax,ax ; fetch a keystroke
int KEYBD_INT ; keyboard request
cmp al,ESCAPE ; Q: does the user want to exit?
jz @@exit ; Y: do so immediately
if (FEATURES AND MODECHANGE)
cmp al,V_KEY ; request for video mode change?
jnz @@TestExt ; if not, go on
@@ChgMode:
mov si,(video PTR [si]).NextMode ; change pointers
mov ax,(video PTR [si]).ScrnMode ; load new video mode
int VIDEO_INT ; change modes
jmp @@Render ; draw new screen
@@TestExt:
endif
cmp al,EXTENDED ; Q: is it an extended key code?
jnz @@TryPlus ; N: it's not so see if it's '+'
@@ArrowKey:
inc ah ; increment it to make indexing easier
add ah,ah ; multiply by two
mov bl,6 ; fix template (bh is already zero)
and bl,ah ; now bx contains address of delta
if (FEATURES AND MODECHANGE)
push si ; save video ptr if we're using one
endif
mov si,offset Deltas; fetch the delta value
add bp,[bx+si] ; add it to the X offset
shr ah,2 ; now look at the Y value of keystroke
mov bl,6 ; turn it into a table offset
and bl,ah ; do it now
sub di,[bx+si] ; and subtract from Y offset
if (FEATURES AND MODECHANGE)
pop si ; restore video ptr if we're using one
endif
jmp @@Render ; go draw this thing.
@@exit:
mov ax,TEXTMODE ; back to normal now
int VIDEO_INT ; change modes
ret ; and exit via old style
Start endp
Deltas dw +DELTA,0,-DELTA,0 ; handy table for calculating
; changes in X and Y offsets
;****************************************************************************
; Shifty
;
; This routine multiplies AX by DX and shifts the result (in
; DX:AX) to the right by scale bits (or scale+1 bits if CY is
; set). The resulting value is left in AX. DX is destroyed.
;
;****************************************************************************
Shifty proc near
push cx ; save middle bits (i*i - j*j)
db 0b1h ; code for mov cl,immed8
scale db STARTSCALE
adc cl,0 ; adjust per CY flag
imul dx ; do the multiply
if (@Cpu AND 8) ; is is a 386 or better?
xchg ax,dx ;
shl eax,16 ; put hi part in hi 16 bits
xchg ax,dx
shr eax,cl ;
else
@@Rotate:
rcr dx,1 ;
rcr ax,1 ;
loop @@Rotate ; ch is always zero so this is OK
endif
pop cx ;
ret ;
Shifty endp
if (FEATURES AND PRINTZOOM)
;****************************************************************************
; PrintChar
;
; This simple subroutine prints a single character (in AL) to the
; screen using a BIOS call. AH and BX are destroyed.
;
;****************************************************************************
PrintChar proc
mov ah,WRITE_CHAR ; write a character in TTY mode
mov bx,CHAR_COLOR AND 07fh ; use page 0 (bh), non-xor color (bl)
int VIDEO_INT ; do it up
ret
PrintChar endp
endif
end Start