294 lines
12 KiB
NASM
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 |