; 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