;           BOY RUNNING
;                         by
;                               Nathan Hull
;  This program demonstrates the use of a redefined character set
;  to perform simple animation.  In this case the animated figure
;  is of a boy running.  The data for the boy consists of 4 frames of data,
;  each of which is 4 by 4 characters.  Each character is represented on
;  an 8 by 8 bit grid, and can thus be encoded in 8 bytes of information.
;  In the following table, each db has 8 bytes of information, and, so, is
;  exactly one ascii character long.  There are a total of 64 ascii characters
;  coded, for a total of 512 bytes in the table.  Since there are 8 pixels per
;  byte, this represents 4096 pixels coded.
	      jmp main
;frame 1 data
boyd          db  8 dup(0)                           ;ascii 80h (upper left)
	      db  8 dup(0)                           ;ascii 81h
	      db  0,1,7,31,4 dup(63)                 ;ascii 82h
	      db  120,252,244,240,240,240,176,176    ;ascii 83h
	      db  8 dup (0)                          ;ascii 84h
	      db  0,1,3,7,14,14,12,1                 ;ascii 85h
	      db  31,207,231,247,127,126,254,254     ;ascii 86h
	      db  248,184,200,240,128,0,8,120        ;ascii 87h
	      db  0,1,3,7,15,14,12,0                 ;ascii 88h
	      db  7,207,207,223,255,255,239,195      ;ascii 89h
	      db  254,254,255,247,227,128,128,128    ;ascii 8ah
	      db  120,240,192,128,4 dup(0)           ;ascii 8bh
	      db  8 dup(0)                           ;ascii 8ch
	      db  3,3,1,1,4 dup(0)                   ;ascii 8dh
	      db  131,135,207,254,252,240,96,0       ;ascii 8eh
	      db  8 dup(0)                           ;ascii 8fh
;frame 2 data
	      db  8 dup(0)                           ;ascii 90h
	      db  8 dup(0)                           ;ascii 91h
	      db  5 dup(0),3,15,31                   ;ascii 92h
	      db  3 dup (0),124,2 dup(254),252,248   ;ascii 93h
	      db  8 dup(0)                           ;ascii 94h
	      db  3 dup(0),1,3,7,6,6                 ;ascii 95h
	      db  63,63,31,159,207,231,255,127       ;ascii 96h
	      db  248,248,216,216,252,220,228,248    ;ascii 97h
	      db  8 dup(0)                           ;ascii 98h
	      db  6,15,63,127,255,255,127,127        ;ascii 99h
	      db  254,3 dup(252),253,223,143,128     ;ascii 9ah
	      db  0,0,96,224,224,192,128,0           ;ascii 9bh
	      db  0,1,1,5 dup(0)                     ;ascii 9ch
	      db  255,239,207,30,127,255,252,0       ;ascii 9dh
	      db  192,192,0,0,128,0,0,0              ;ascii 9eh
	      db  8 dup(0)                           ;ascii 9fh
;frame 3 data
	      db  8 dup(0)                           ;ascii a0h
	      db  8 dup(0)                           ;ascii a1h
	      db  0,0,3,7,4 dup(15)                  ;ascii a2h
	      db  0,126,3 dup(255),252,236,236       ;ascii a3h
	      db  8 dup(0)                           ;ascii a4h
	      db  7 dup(0),1                         ;ascii a5h
	      db  15,7,1,1,7,63,255,255              ;ascii a6h
	      db  254,238,242,252,224,128,0,0        ;ascii a7h
	      db  7 dup(0),1                         ;ascii a8h
	      db  1,3,7,15,31,63,255,252             ;ascii a9h
	      db  255,254,255,255,252,255,255,252    ;ascii aah
	      db  0,0,128,128,0,128,0,0              ;ascii abh
	      db  3,15,30,30,28,30,30,0              ;ascii ach
	      db  192,7 dup(0)                       ;ascii adh
	      db  62,14,6 dup(0)                     ;ascii aeh
	      db  8 dup(0)                           ;ascii afh
;frame 4 data
	      db  8 dup(0)                           ;ascii b0h
	      db  7 dup(0),13                        ;ascii b1h
	      db  1,7,4 dup(15),7,199                ;ascii b2h
	      db  254,255,255,253,252,236,236,254    ;ascii b3h
	      db  5 dup(0),63,254,254                ;ascii b4h
	      db  31,30,3 dup(0),7,31,63             ;ascii b5h
	      db  227,121,63,63,254,3 dup(255)       ;ascii b6h
	      db  238,242,252,0,3,135,255,252        ;ascii b7h
	      db  252,63,63,31,4 dup(0)              ;ascii b8h
	      db  4 dup(255),4 dup(0)                ;ascii b9h
	      db  240,224,192,240,248,63,31,15       ;ascii bah
	      db  0,0,96,224,224,192,192,128         ;ascii bbh
	      db  8 dup(0)                           ;ascii bch
	      db  8 dup(0)                           ;ascii bdh
	      db  6,7 dup(0)                         ;ascii beh
	      db  8 dup(0)                           ;ascii bfh
ascii     db     ?
frame     db     ?
speed     dw     6
;  First we set mode to 320 by 200  & clears the screen
main: mov        ah,0                                ;set mode
	  mov    al,4                                ;320 x 200 color
	  int    10h                                 ;screen interrupt
;  Here we set the color pallete  & background color
	  mov    ah,11                 ;set color
	  mov    bh,1                  ;set palette
	  mov    bl,0                  ;choose green, red, yellow
	  int    10h
	  mov    ah,11                 ;set color
	  mov    bh,0                  ;set background
	  mov    bl,1                  ;choose blue
	  int    10h
;  Set the interupt table to point to our data in table boyd
	  mov    al,1fh                ;interupt vector number
	  mov    dx,offset boyd        ;boy data
	  mov    ah,25h                ;set int vector table function
	  int    21h                   ;dos function call

	  mov   ax, 0
	  int   33h                     ;start mouse
	  mov   ax, 1                   ;show mouse for debugging
	  int   33h
;  Since we are using medium resolution graphics, this means we have 40 columns
;  and 25 rows.
;  Draw the boy in the in the uppper right hand corner of the screen
frame1:   mov    ascii,80h             ;first char of frame one
	  mov    frame,1
startfr:  sub    dx,dx                 ;home cursor
drawchar: mov    ah,2                  ;set cursor
	  mov    bh,0
	  int    10h
	  mov    ah,9                  ;draw character
	  mov    cx,1                  ;1 char
	  mov    bl,03h                ;draw boy in yellow on blue
	  mov    al,ascii
	  int    10h
	  inc    ascii                 ;next ascii character
	  inc    dl                    ;next column
	  cmp    dl,3                  ;boy is 4 chars wide
	  jna    drawchar
	  mov    dl,0                  ;first column
	  inc    dh                    ;down a row
	  cmp    dh,3                  ;boy is 4 chars tall
	  jna    drawchar
;  Just finished drawing one frame
	  call   howfast
	  call   delay                 ;see frame
	  inc    frame                 ;go to next frame
	  cmp    frame,4
	  jna    startfr
;  Check to see if user has hit a key as a signal to stop - read keyboard
	  mov    ah,0bh                ;check keyboard
	  int    21h                   ;dos function call
	  cmp    al,0ffh               ;loop if key is not hit
	  jne    frame1
;  Else key was hit, set mode to text and return to dos
	  mov    ah,0                  ;set mode
	  mov    al,3                  ;80 x 25 color text
	  int    10h
	  int    20h                   ;back to dos
howfast:  push  ax
	  push  bx
	  push  cx
	  push  dx
	  mov   ax, 3
	  int   33h                     ;get horz mouse position
	  cmp   cx, 400
	  ja    faster
	  cmp   cx, 200
	  ja    outahere
	  cmp   speed, 500                ;don't race it too slowly!!
	  jae   outahere
	  add   speed, 1
	  jmp   outahere
faster:   cmp   speed, 1 
	  jbe   outahere                ;not too fast!!  
	  sub   speed, 1
outahere: pop   dx
	  pop   cx
	  pop   bx
	  pop   ax

;  Routine to kill time while a specific frame is on the screen
;  Delay routine, if we had no delay, then things would move so rapidly
;  we could not tell what was going on, and could not move the paddle
;  fast enough to keep up with the play!
      PUSH  AX             ; save registers
      PUSH  DX
      MOV   DH,25          ; get cursor off screen area
      MOV   DL,0           ;      (cleaner appearence)
      SUB   AX,AX          ; zero frequency for rest
      MOV   DX,speed           ; delay of 0.06 secs is reasonable
      CALL  NOTE           ; execute delay
      POP   DX             ; restore registers
      POP   AX
      RET                  ; return to caller
;  Routine to play tune on speaker
;      (SI)               Pointer to tune list
;      CALL TUNE
;  The tune list is a series of word pairs. The first word is the
;  duration in units of 1/100th of a second, and the second word
;  is the frequency. An entry with zero duration ends the tune list
;  and a zero frequency is a rest (period of silence).
      PUSH  AX          ; save registers
      PUSH  DX
;  Loop through notes of the tune
TUN1: LODSW                ; load duration
      OR    AX,AX          ; test zero duration ending the list
      JZ    TUN2           ; if so, end of tune
;  Play next note
      XCHG  AX,DX          ; put duration in DX
      LODSW                ; load frequency
      CALL  NOTE           ; play the note
      JMP   TUN1           ; and loop back
;  Here at end of tune
TUN2: POP   DX             ; restore registers
      POP   AX
      RET                  ; return to caller
;  Routine to play note on speaker
;      (AX)           Frequency in Hz (32 - 32000)
;      (DX)           Duration in units of 1/100 second
;      CALL  NOTE
;  Note: a frequency of zero, means rest (silence) for the indicated
;  time, allowing this routine to be used simply as a timing delay.
;  Definitions for timer gate control
CTRL      EQU   61H           ; timer gate control port
TIMR      EQU   00000001B     ; bit to turn timer on
SPKR      EQU   00000010B     ; bit to turn speaker on
;  Definitions of input/output ports to access timer chip
TCTL      EQU   043H          ; port for timer control
TCTR      EQU   042H          ; port for timer count values
;  Definitions of timer control values (to send to control port)
TSQW      EQU   10110110B     ; timer 2, 2 bytes, sq wave, binary
LATCH     EQU   10000000B     ; latch timer 2
;  Define 32 bit value used to set timer frequency
FRHI      EQU   0012H          ; timer frequency high (1193180 / 256)
FRLO      EQU   34DCH          ; timer low (1193180 mod 256)
      PUSH  AX          ; save registers
      PUSH  BX
      PUSH  CX
      PUSH  DX
      PUSH  SI
      MOV   BX,AX          ; save frequency in BX
      MOV   CX,DX          ; save duration in CX
;  We handle the rest (silence) case by using an arbitrary frequency to
;  program the clock so that the normal approach for getting the right
;  delay functions, but we will leave the speaker off in this case.
      MOV   SI,BX          ; copy frequency to BX
      OR    BX,BX          ; test zero frequency (rest)
      JNZ   NOT1           ; jump if not
      MOV   BX,256         ; else reset to arbitrary non-zero
;  Initialize timer and set desired frequency
NOT1: MOV   AL,TSQW          ; set timer 2 in square wave mode
      OUT   TCTL,AL
      MOV   DX,FRHI          ; set DX:AX = 1193180 decimal
      MOV   AX,FRLO          ;      = clock frequency
      DIV   BX               ; divide by desired frequency
      OUT   TCTR,AL          ; output low order of divisor
      MOV   AL,AH            ; output high order of divisor
      OUT   TCTR,AL
;  Turn the timer on, and also the speaker (unless frequency 0 = rest)
      IN    AL,CTRL          ; read current contents of control port
      OR    AL,TIMR          ; turn timer on
      OR    SI,SI            ; test zero frequency
      JZ    NOT2             ; skip if so (leave speaker off)
      OR    AL,SPKR          ; else turn speaker on as well
;  Compute number of clock cycles required at this frequency
NOT2: OUT   CTRL,AL          ; rewrite control port
      XCHG  AX,BX            ; frequency to AX
      MUL   CX               ; frequency times secs/100 to DX:AX
      MOV   CX,100           ; divide by 100 to get number of beats
      DIV   CX
      SHL   AX,1             ; times 2 because two clocks/beat
      XCHG  AX,CX            ; count of clock cycles to CX
;  Loop through clock cycles
NOT3:      CALL  RCTR          ; read initial count
;  Loop to wait for clock count to get reset. The count goes from the
;  value we set down to 0, and then is reset back to the set value
NOT4: MOV   DX,AX          ; save previous count in DX
      CALL  RCTR           ; read count again
      CMP   AX,DX          ; compare new count : old count
      JB    NOT4           ; loop if new count is lower
      LOOP  NOT3           ; else reset, count down cycles
;  Wait is complete, so turn off clock and return
      IN    AL,CTRL           ; read current contents of port
      AND   AL,0FFH-TIMR-SPKR ; reset timer/speaker control bits
; note that the above statement is an equation
      OUT   CTRL,AL           ; rewrite control port
      POP   SI                ; restore registers
      POP   DX
      POP   CX
      POP   BX
      POP   AX
      RET               ; return to caller
;  Routine to read count, returns current timer 2 count in AX
      MOV   AL,LATCH         ; latch the counter
      OUT   TCTL,AL          ; latch counter
      IN    AL,TCTR          ; read lsb of count
      MOV   AH,AL
      IN    AL,TCTR          ; read msb of count
      XCHG  AH,AL            ; count is in AX
      RET                    ; return to caller
;  Routine to set cursor position
;      (DH,DL)          Cursor line, column
      PUSH  AX            ; save registers
      PUSH  BX
      MOV   BH,0          ; set cursor
      MOV   AH,2
      INT   10H
      POP   BX            ; restore registers
      POP   AX
      RET                 ; return to caller