;******************************************************;
;* PLASMA.ASM -- Simple plasma display, with rotating *;
;* palette.  Requires VGA, 186.                       *;
;******************************************************;

.Model Tiny
.186
.Code
Org 100h

Prog        Proc

            mov ax,13h              ;Set video mode 13h
            int 10h

            mov di,offset Palette   ;Zero the palette
            xor al,al
            mov cx,576
            rep stosw

            mov ax,3F00h            ;AL = 0, AH = 63, BX = 0
            xor bx,bx

PalLoop:    mov Palette[bx],ah      ;Set palette values
            mov Palette[bx+2],al
            mov word ptr Palette[bx+193],ax
            mov word ptr Palette[bx+384],ax

            add bx,3                ;Advance pointers
            inc al
            dec ah                  ;Loop back
            jns PalLoop

            xor bx,bx               ;Set initial palette
            call setpal

            push 40h                ;ES = 40h
            pop es
            mov ax,es:[6Ch]         ;Seed RNG
            mov bx,es:[6Eh]
            mov RandNum[0],ax
            mov RandNum[2],bx

            push 0A000h             ;ES = 0A000h
            pop es

            call rand192            ;Set corner pixels to random colors
            mov es:[0],al
            call rand192
            mov es:[319],al
            call rand192
            mov es:[63360],al
            call rand192
            mov es:[63999],al

            push 199                ;Draw plasma
            push 319
            push 0
            push 0
            call Plasma

            push cs                 ;ES = CS
            pop es

            mov si,offset Palette   ;Double palette for rotation
            mov di,offset Palette+576
            mov cx,576
            rep movsb

            xor bx,bx               ;BX = 0

Rotate:     mov ah,1                ;Check for Escape key
            int 16h
            jnz Done

            mov dx,03DAh            ;Status port

VRTloop1:   in al,dx                ;Wait for next retrace
            test al,8
            jnz VRTloop1
VRTloop2:   in al,dx
            test al,8
            jz VRTloop2

            call setpal             ;Set palette

            add bx,3                ;Advance palette
            cmp bx,576              ;Check for wrap
            jne Rotate              ;Loop back...
            xor bx,bx
            jmp Rotate

Done:       mov ax,3                ;Set text mode
            int 10h
            ret                     ;Return

rand192:    mov ax,192              ;Get random number 1-192
            call Rand
            inc ax
            ret

setpal:     mov dx,03C8h            ;Index port
            mov al,1                ;Set register 1
            out dx,al
            inc dx                  ;Data port
            lea si,Palette[bx]      ;Send palette
            mov cx,576
            rep outsb
            ret                     ;Return

Prog        EndP

;**************************** Plasma -- Recursive plasma procedure

Plasma      Proc

            push bp                 ;Set up stack frame
            mov bp,sp

            pusha                   ;Save all registers

            mov ax,[bp+4]           ;Get values
            mov bx,[bp+6]
            mov cx,[bp+8]
            mov dx,[bp+10]

            mov si,cx               ;x2 - x1 < 2, done
            sub si,ax
            cmp si,2
            jnb P_Cont
            jmp P_Done

P_Cont:     mov si,ax               ;x = (x1 + x2) / 2
            add si,cx
            sar si,1
            mov di,bx               ;y = (y1 + y2) / 2
            add di,dx
            sar di,1

            pusha                   ;Save registers

            push bx                 ;Adjust top side
            push si
            push bx
            push cx
            push bx
            push ax
            call Adjust
            push dx                 ;Adjust bottom side
            push si
            push dx
            push cx
            push dx
            push ax
            call Adjust
            push di                 ;Adjust left side
            push ax
            push dx
            push ax
            push bx
            push ax
            call Adjust
            push di                 ;Adjust right side
            push cx
            push dx
            push cx
            push bx
            push cx
            call Adjust

            push si                 ;Center pixel on, recurse
            push di
            call GetPixel
            test al,al
            jnz P_Recurse

            xor ah,ah               ;Zero AH
            push [bp+4]             ;DX = sum of corners
            push [bp+6]
            call GetPixel
            mov dx,ax
            push [bp+8]
            push [bp+6]
            call GetPixel
            add dx,ax
            push [bp+4]
            push [bp+10]
            call GetPixel
            add dx,ax
            push [bp+8]
            push [bp+10]
            call GetPixel
            add dx,ax

            shr dx,2                ;DX = average of corners
            mov al,dl
            push si                 ;Set center pixel
            push di
            call SetPixel

P_Recurse:  popa                    ;Restore registers
            push di                 ;Plasma x1, y1, x, y
            push si
            push bx
            push ax
            call Plasma
            push di                 ;Plasma x, y1, x2, y
            push cx
            push bx
            push si
            call Plasma
            push dx                 ;Plasma x, y, x2, y2
            push cx
            push di
            push si
            call Plasma
            push dx                 ;Plasma x1, y, x, y2
            push si
            push di
            push ax
            call Plasma

P_Done:     popa                    ;Restore registers
            pop bp                  ;Delete stack frame
            ret 8                   ;Return, pop args

Plasma      EndP

;**************************** Adjust -- Random adjust pixel midpoint

Adjust      Proc

            push bp                 ;Set up stack frame
            mov bp,sp

            pusha                   ;Save all registers

            mov si,[bp+12]          ;Get values
            mov di,[bp+14]

            push si                 ;Check pixel
            push di
            call GetPixel
            test al,al              ;Already on, done
            jnz A_Done

            mov ax,[bp+8]           ;BX = |x2 - x1| + |y2 - y1|
            sub ax,[bp+4]
            mov bx,[bp+10]
            sub bx,[bp+6]
            test ax,ax              ;get absolute values...
            jge $+4
            neg ax
            test bx,bx
            jge $+4
            neg bx
            add bx,ax

            imul ax,bx,2            ;AX = BX * 2 + 1
            inc ax

            mov bx,ax               ;Get random number
            call Rand               ;positive and negative
            sar bx,1
            sub ax,bx
            xchg dx,ax              ;in DX

            push [bp+4]             ;AX = average of ends + DX
            push [bp+6]
            call GetPixel
            push ax
            push [bp+8]
            push [bp+10]
            call GetPixel
            pop bx
            xor ah,ah
            add al,bl
            adc ah,0
            sar ax,1
            add ax,dx

            test ax,ax              ;Make sure it's in range
            jg $+5
            mov ax,1
            cmp ax,192
            jle $+5
            mov ax,192

            push si                 ;Set center pixel
            push di
            call SetPixel

A_Done:     popa                    ;Restore registers
            pop bp                  ;Delete stack frame
            ret 12                  ;Return, pop args

Adjust      EndP

;**************************** Rand -- Random number generator

Rand        Proc

            push bx                ; This RNG routine was
            push cx                ;taken from the TurboC
            push dx                ;runtime library, but
            push ax                ;it is a big kludge.
            mov ax,RandNum[0]      ; I do not know how 
            mov dx,4E35h           ;it really works.
            mul dx          
            mov cx,dx       
            mov bx,ax       
            imul ax,RandNum[0],015Ah
            add cx,ax
            imul ax,RandNum[2],4E35h
            add ax,cx
            inc bx
            mov RandNum[0],bx
            mov RandNum[2],ax
            add ax,ax
            and bl,1
            or al,bl
            pop bx
            xor dx,dx
            div bx
            mov ax,dx
            pop dx
            pop cx
            pop bx
            ret

Rand        EndP

;**************************** GetPixel -- Read a pixel

GetPixel    Proc

            push bp
            mov bp,sp
            imul bx,[bp+4],320
            add bx,[bp+6]
            mov al,es:[bx]
            pop bp
            ret 4

GetPixel    EndP

;**************************** SetPixel -- Write a pixel

SetPixel    Proc

            push bp
            mov bp,sp
            imul bx,[bp+4],320
            add bx,[bp+6]
            mov es:[bx],al
            pop bp
            ret 4

SetPixel    EndP

;**************************** DATA section

RandNum     dw ?,?

Palette     db 1152 dup(?)

End Prog
