COMMENT#
                335 Byte Plasma
                ~~~~~~~~~~~~~~~
                                        Chris Softley, 9/2/97
                                        (chris@softnco.demon.co.uk)
   Draws a plasma in screen mode 13h, then palette cycles it until
   a key is pressed.
   Requires a 386 or above  & VGA - theres no detection.
   To reduce the requirement to a 286, change the 3 byte conditional jumps
   to x_loop and y_ loop to 5-byte equivalent code sequences, making
   this program 339 bytes.

   Size can be reduced by not setting the palette until cycling begins,
   not checking for vertical retrace, making assumptions about registers
   on startup, etc...

   Enjoy...

#
        ideal
        MODEL tiny
        CODESEG
        ORG 0100h
        P386

; Some constants. You can play with 'S_GRAIN' in the range 8 to 32.
; Powers of two, though, please :)
S_GRAIN     = 32
S_LASTGRAIN = 64 ; S_GRAIN * 2
S_GRAIN_O_8 = 4  ; S_GRAIN / 8

MACRO SETALC ; Set AL to Carry Flag - Opcode undocumented, but works on
  DB 0D6H    ; all processors to date, even non-intel ones like my Cyrix.
ENDM         ; Sets all bits of AL to the carry flag.

Start:
; Set screen mode
        mov ax,13h
        int 10h
; Zero the palette
        mov di,offset Palette
        SETALC  ; CF=0 so AL = 0 and AH = 0 from int 10 so AX = 0
        push ax ; Push 0
        mov cx,288 ; count of words to fill
        mov bx,di  ; bx = offset Palette
        push cx    ; Push count of words in palette
        push di    ; Push offset Palette
        rep stosw  ; Fill palette with 0s

; Set necessary bytes in palette buffer
        mov ah,3Fh ; AH = 63, AL = 0, BX = offset palette
PalLoop:
        mov [byte bx],ah        ;Set palette values
        mov [word bx+193],ax
        mov [byte bx+2],al
        mov [word bx+384],ax
        add bx,3                ;Advance pointers
        add ax,0ff01h           ; Inc AH, dec AL, Will carry whilst AH !=0
        jc short PalLoop

; Duplicate palette for cycling later
        pop si
        pop cx
        rep movsw

; Set initial palette
        call setpal

; Seed R.N.G.
        pop es           ; es = 0
        mov ax,[es:46ch] ; Read low word of timer ticks since midnight as seed.
        push 0a000h
        mov [di],ax
        pop es           ; ES = video data segment

; Main Loop...
        mov si,S_LASTGRAIN      ; SI = 'LastGrain'
iter_loop:                      ; Loop for passes
        sub dx,dx               ; DX = 'read-y'
        mov [y],dx
  y_loop:                       ; Loop for 'y' coord
          mov ax,[y]
          sub ax,si
          cmp ax,dx
          jne short skip_s_ready
            mov dx,[y]          ; snap DX to point on next-size-up grid
    skip_s_ready:
          sub cx,cx             ; CX = 'read-x'
          mov [x],cx
    x_loop:                     ; Loop for 'x' coord
            mov ax,[x]
            sub ax,si
            cmp ax,cx
            jne short skip_s_readx
              mov cx,[x]        ; snap CX to point on next-size-up grid
    skip_s_readx:
            sub ax,ax
            push cx
            push dx
            call GetPixel   ; al = screen(readx,ready)
            mov di,ax
            mov bx,si
            add bx,cx
            push bx
            push dx
            call GetPixel   ; al = screen(readx+lastgrain,ready)
            add di,ax
            mov bx,cx
            sub bx,si
            push bx
            push dx
            call GetPixel   ; al = screen(readx-lastgrain,ready)
            add di,ax
            mov bx,si
            add bx,dx
            push cx
            push bx
            call GetPixel   ; al = screen(readx,ready+lastgrain)
            add di,ax
            mov bx,si
            sub bx,dx
            js short skip   ; Check if y-coord off screen.
            push cx
            push bx
            call GetPixel   ; al = screen(readx,ready-lastgrain)
skip:
            add ax,di   ; ax = sum of all above points.
            push dx     ; save dx
            CWD         ; dx = 0 (ax not negative)
            mov bx,5
            div bx      ; Get average colour of points read
            mov di,ax   ; save in di
            imul ax,[RandNum],9421   ; 9421 is prime.
            inc ax
            mov [RandNum],ax          ; Save random number
            CBW                       ; Only want a byte random number
            imul ax,S_GRAIN_O_8       ; Multiply up as reqd
org $-1
grain_o_8 db S_GRAIN_O_8             ; Multiplier is stored as part of instr.
            pop dx      ; get dx back
            add ax,di   ; ax = random no + ave colour
            jg short non_neg  ; Check range of colour...
             mov al,1
non_neg:
            cmp ax,194
            jle short non_sat
             mov al,194
non_sat:
           imul bx,[y],320      ; set the pixel
           add bx,[x]
           mov [es:bx],al
          mov ax,[grain]
          add [x],ax        ; Move x on
          cmp [word x],320  ; Still in range?
          jb near x_loop    ;   - Yes: Loop back
        add [y],ax          ; No... Move y on
        cmp [byte y],200    ; Still in range?
        jb near y_loop      ;   - Yes: Loop back
      cmp si,1              ; Was LastGrain <=1 in the last pass?
      jbe short initAnRot   ; Yes - we've drawn it, go and cycle the palette
        mov ax,S_GRAIN      ; No - mov ax,grain
 org $-2
 grain dw S_GRAIN           ; 'Grain' stored as part of the instruction
       mov si,ax            ; lastgrain=grain
       cmp al,1
       jbe short ctu_i_l    ; if grain <= 1 keep going
       shr [byte grain],1   ; else half it
       shr al,4
       mov [grain_o_8],al   ; and set grain_o_8
ctu_i_l:
        jmp near iter_loop  ; Loop back & do another pass.


; Cycle the palette until a key is pressed...
initAnRot:
            mov si,offset Palette
Rotate:     mov ah,1                ;Check for any key
            int 16h
            jnz short Done
            mov dx,03DAh      ; Wait for next vertical retrace to start
VRTloop1:   in al,dx
            test al,8
            jnz short VRTloop1
VRTloop2:   in al,dx
            test al,8
            jz short VRTloop2
            call setpal                ; Set the palette starting at [si]
            sub si,573                 ; Setpal adds 576 to si, so this has
                                       ; the effect of adding 3.
            cmp si,offset palette +576 ;Check for wrap-around
            jne short Rotate           ;If not, loop back...
             jmp short initAnRot       ;If so, go reinitialise si and loop.

; End the program
Done:       mov ax,3                ;Restore text mode
            int 10h
            ret                     ;Return. (return address on stack points
                                    ;         to the int 20h in the PSP)

; Set the palette.
; Write 576 bytes of palette data out, pointed to by si.
setpal:     mov dx,03c8h
            mov al,1       ; Start at colour 1
            out dx,al
            inc dx
            mov cx,576
            rep outsb
            ret



; Get the colour of a pixel.
; Requires x then y to have been pushed first.
; returns colour in al.
GetPixel:
            mov bp,sp           ; reference operands on stack
            imul bx,[bp+2],320  ; calculate start of line
            add bx,[bp+4]       ; calculate complete address
            mov al,[es:bx]      ; read pixel
            ret 4               ; forget operands & return



; Data...

palette db 1152 dup(?)          ; Palette space. 2 palette spaces, each wit
                                ; room enough for 194 3-byte colour entries.
RandNum dw ?                    ; Current random number seed. Needs to follow
                                ; straight after palette for when it is seeded.
y dw ?                          ; Current x-position
x dw ?                          ; Current y-position

; 335 bytes.

END Start
