;
;  This program implements a simple one player game on the IBM PC.
;  It requires the color/graphics adapter (although it operates only
;  in character mode, and could be run on a monochrome monitor with
;  a suitable change in the screen attributes).
;
;  The general picture is a blue screen with green walls on the top
;  and on the two sides. On the bottom line is a paddle which consists
;  of a red strip, five character positions long which can be moved
;  from side to side using the left and right cursor keys.
;
;  On pressing the space bar, a "ball" (which is a yellow happy face
;  on a blue background) is released and travels around bouncing
;  of the walls and paddle until it is missed and falls through the
;  bottom. A game is five balls after which control returns to DOS.
;
;  Start execution here, do an initial jump past the data
;
      JMP   MAIN          ; start execution
;
;  Definitions of colors to be used
;
WALLC      EQU   22H          ; wall is all green
FIELDC     EQU   1FH          ; playing field is white on blue
PADLC      EQU   44H          ; paddle is all red
BALLC      EQU   1EH          ; ball is yellow on blue background
NORMC      EQU   07H          ; grey on black (for return to DOS)
;
;  Data areas, first the position and direction of the ball. If no ball is
;  in play, then BROW is zero and the other fields are irrelevant. If
;  the ball is in play, then BROW, BCOL give its current position, and
;  BVDIR, BHDIR give its direction (one of four possibilities since the
;  ball always moves in a diagnonal direction.
;
BROW      DB    ?          ; current line of ball (0 = no ball)
BCOL      DB    ?          ; current char of ball
BVDIR     DB    ?          ; vertical direction (-1=up, +1=down)
BHDIR     DB    ?          ; horiz direction (-1=left, +1=right)
;
;  The following location gives the current paddle position. It is in the
;  range 3-76, since it gives the position of the middle of the paddle.
;  We start out with the paddle in the middle of the screen
;
PADPOS     DB    40          ; current paddle position
PADMN      EQU   3          ; minimum position
PADMX      EQU   76          ; maximum position
;
;  Control of keyboard motion. For the first three movements in a given
;  direction, the paddle moves one square at a time, for the next three
;  movements in the same direction, it moves two sqares at a time. For
;  subsequent movements in the same direction, it moves three squares.
;
;  The location KMOVE is -3,-2,-1,0,+1,+2,+3 showing how far and in
;  which direction the paddle was moved last time. A negative value
;  means that the ball is moving left, positive means it is moving right
;  and zero is used as the initila value when a new ball is launched.
;
KMOVE      DB    ?          ; current direction of paddle
KCTR       DB    ?          ; count movements in that direction
;
;  Count balls in play, and score (count of bounces off paddle)
;
BCOUNT      DB    0          ; number of balls played
SCORE       DW    0          ; score so far
;
;  Initial notes of funeral march (used when ball is missed)
;  See TUNE routine for definition of the format of this list
;
FUNR      DW    74,262,6,0
          DW    53,262,6,0
          DW    14,262,6,0
          DW    74,262,6,0
          DW    53,311,6,0
          DW    14,294,6,0
          DW    53,294,6,0
          DW    14,262,6,0
          DW    53,262,6,0
          DW    14,247,6,0
          DW    74,262,6,0
          DW    0
;
;  Beep data for ball bouncing (see NOTE routine)
;
BEEPL      EQU   1000          ; frequency for beep off left wall
BEEPR      EQU   1500          ; frequency for beep off right wall
BEEPT      EQU   750          ; frequency for beep off top
BEEPP      EQU   1250          ; frequency for beep off paddle
BDUR       EQU   7          ; length of bounce beeps (0.07 secs)
;
;  Here is the main program. We use a top down approach in which
;  the functions are distributed to lower level modules. The first step
;  is to initialize the screen (playing field and wall display).
;
MAIN      PROC
      CALL  INIT          ; initialize screen
;
;  Loop to play 5 balls
;
MAIN1:INC   BCOUNT          ; bump count of balls played
      CALL  PLAY          ; play one ball
      CMP   BCOUNT,5      ; check five balls played
      JNE   MAIN1          ; loop back for another if not
;
;  Reset screen to normal, and return to DOS
;
      MOV   DX,0          ; reset screen to normal color
      MOV   CX,25*80
      MOV   BL,NORMC
      MOV   AL,' '
      CALL  PAINT
      INT   20H          ; then return to DOS
MAIN      ENDP
;
;  Initialization subroutine. Make playing field and walls
;
INIT      PROC
      PUSH  AX          ; save registers
      PUSH  BX
      PUSH  CX
      PUSH  DX
      MOV   DX,0          ; home cursor
      MOV   CX,25*80      ; clear screen to all blue
      MOV   BL,FIELDC     ; set proper color
      MOV   AL,' '
      CALL  PAINT          ; paint playing field
      MOV   BL,WALLC      ; set color for wall
      MOV   CX,80          ; paint top wall
      CALL  PAINT
;
;  Loop to paint side walls
;
INIT1:ADD   DH,1          ; bump to next line
      MOV   DL,0          ; set left column
      MOV   CX,1          ; one wall piece on left
      CALL  PAINT
      MOV   DL,79          ; one wall piece on right
      CALL  PAINT
      CMP   DH,24          ; loop till all set
      JNE   INIT1
;
;  Walls are complete, initialize ball and score counts and we are done
;
      CALL  UTEXT          ; initialize text messages
      POP   DX          ; restore registers
      POP   CX
      POP   BX
      POP   AX
      RET               ; then return to caller
INIT      ENDP
;
;  Main controlling routine for playing one ball
;
PLAY      PROC
      PUSH  AX          ; save registers
      PUSH  BX
      PUSH  CX
      PUSH  DX
      MOV   KMOVE,0          ; set no paddle movement yet
      MOV   KCTR,0
      CALL  DISPD          ; display initial position
      MOV   DH,25          ; set cursor off
      MOV   DL,0
      CALL  SETPOS
;
;  Wait for ball to be launched (any key pressed)
;
PLAY1:MOV   AH,1          ; check for key pressed?
      INT   16H
      JZ    PLAY1          ; loop if not
      MOV   AH,0          ; else read key
      INT   16H
      CMP   AL,' '        ; was it space bar?
      JNE   PLAY1          ; if not, ignore key
;
;  Here we put the ball into play
;
      MOV   AX,BEEPP      ; set beep off paddle
      MOV   DX,BDUR
      CALL  NOTE
      MOV   BROW,23          ; ball is on bottom line, just above paddle
      MOV   AL,PADPOS
      MOV   BCOL,AL
      MOV   BVDIR,-1      ; going up
      MOV   BHDIR,+1      ; to the right
;
;  Loop through ball and paddle positions as play proceeds
;
PLAY2:      CALL  NEWP          ; update paddle position
      CALL  DISPD          ; display paddle in current position
      MOV   DH,BROW          ; point to ball position
      MOV   DL,BCOL
      MOV   AL,2          ; ball is ASCII 2 (happy face)
      MOV   BL,BALLC      ; set proper color
      MOV   CX,1
      CALL  PAINT          ; display ball at current position
      CALL  DELAY          ; wait for a moment
      MOV   AL,' '        ; clear old ball position
      MOV   BL,FIELDC
      CALL  PAINT
      CALL  UTEXT          ; update text messages
      CALL  NEWP          ; set new paddle position
      CALL  NEWB          ; set new ball position and direction
      CMP   BROW,0          ; ball still in play?
      JNZ   PLAY2          ; loop if so
;
;  Ball is out of play, we are done
;
      POP   DX          ; restore registers
      POP   CX
      POP   BX
      POP   AX
      RET               ; return to caller
PLAY      ENDP
;
;  Routine to display paddle. We paint the paddle first, then the
;  spaces on either side. Things are done in this order to avoid turning
;  off the paddle completely, which might cause some flicker.
;
DISPD      PROC
      PUSH  AX          ; save registers
      PUSH  BX
      PUSH  CX
      PUSH  DX
;
;  Paint the paddle itself
;
      MOV   DH,24          ; set left position of paddle
      MOV   DL,PADPOS
      SUB   DL,2
      MOV   CX,5          ; paint five squares for paddle
      MOV   BL,PADLC
      MOV   AL,' '
      CALL  PAINT          ; display paddle
;
;  Paint space to right of paddle
;
      ADD   DL,5          ; point to space on right
      MOV   CX,79          ; space on right is 79
      SUB   CL,DL          ;      minus starting pos of space
      MOV   BL,FIELDC     ; set proper color for space
      MOV   AL,' '
      CALL  PAINT          ; paint spaces on the right
;
;  Paint space to left of paddle
;
      MOV   DL,1          ; point to space on left
      MOV   CL,PADPOS     ; space on left is paddle position
      SUB   CL,3          ;      minus 3 for start of space
      CALL  PAINT          ; paint space on the left
;
;  Paddle display is complete
;
      POP   DX          ; restore registers
      POP   CX
      POP   BX
      POP   AX
      RET               ; return
DISPD      ENDP
;
;  Routine to set new paddle position
;
NEWP      PROC
      PUSH  AX          ; save registers
;
;  See if left or right cursor key was pressed (ignore any other)
;
      MOV   AH,1          ; any key pressed?
      INT   16H
      JZ    NEWP9          ; exit if not
      MOV   AH,0          ; else read the key
      INT   16H
      CMP   AH,4DH          ; check right arrow
      JE    NEWP3          ; jump if right arrow
      CMP   AH,4BH          ; check left arrow
      JNE   NEWP9          ; if neither, ignore key
;
;  Here for left arrow
;
      CMP   KMOVE,0          ; test current position
      JL    NEWP1          ; jump if left (negative)
      MOV   KMOVE,0          ; else set no movement now
      JMP   NEWP2          ; and merge to shift speed
;
;  We were already moving left
;
NEWP1:CMP   KCTR,3          ; already 3 moves this direction?
      JB    NEWP6          ; jump if not, no speed change
      CMP   KMOVE,-3      ; already moving at max speed?
      JE    NEWP6          ; if so, no change in speed
;
;  Here we are increasing left movement speed
;
NEWP2:DEC   KMOVE          ; set new movement speed
      MOV   KCTR,0          ; and reset counter
      JMP   NEWP6          ; jump to set new paddle position
;
;  Here for right arrow
;
NEWP3:CMP   KMOVE,0          ; test current direction
      JG    NEWP4          ; jump if right (positive)
      MOV   KMOVE,0          ; else set no movement now
      JMP   NEWP5          ; and merge to shift speed
;
;  We were already moving right
;
NEWP4:CMP   KCTR,3          ; already 3 moves this direction?
      JB    NEWP6          ; jump if not, no speed change
      CMP   KMOVE,+3      ; already moving at max speed?
      JE    NEWP6          ; if so, no change in speed
;
;  Here we are increasing right movement speed
;
NEWP5:INC   KMOVE          ; set new movement speed
      MOV   KCTR,0          ; and reset counter
;
;  Merge here to set new paddle position
;
NEWP6:INC   KCTR          ; bump count of moves
      MOV   AL,PADPOS     ; compute new position
      ADD   AL,KMOVE      ; with new movement
      CMP   AL,PADMN      ; reset to min if lower
      JGE   NEWP7
      MOV   AL,PADMN
NEWP7:CMP   AL,PADMX      ; reset to max if higher
      JLE   NEWP8
      MOV   AL,PADMX
NEWP8:MOV   PADPOS,AL     ; store new paddle pos
      CALL  DISPD          ; display new position
;
;  Exit with new position set if cursor key
;
NEWP9:POP   AX          ; restore registers
      RET               ; return to caller
NEWP      ENDP
;
;  Routine to set new ball position, first deal with vertical position
;
NEWB      PROC
      PUSH  AX          ; save registers
      PUSH  BX
      PUSH  DX
      PUSH  SI
      MOV   BL,BROW          ; get current line
      ADD   BL,BVDIR      ; adjust by adding direction to get new line
      JNZ   NEWB1          ; jump if not on top wall
;
;  If we bounce off top wall, reset direction to down
;
      MOV   AX,BEEPT      ; set beep off top
      MOV   DX,BDUR
      CALL  NOTE
      MOV   BL,1          ; reset position to top of play area
      MOV   BVDIR,+1      ; reset direction to down
;
;  Now deal with horizontal position
;
NEWB1:MOV   BROW,BL          ; first store new vertical position
      MOV   BL,BCOL          ; get current char
      ADD   BL,BHDIR      ; add horizontal direction to get new char
      JNZ   NEWB2          ; jump if not at left wall
;
;  If we are at left wall, reset horizontal direction for bounce
;
      MOV   AX,BEEPL      ; set beep off left
      MOV   DX,BDUR
      CALL  NOTE
      MOV   BL,1          ; reset position to left of play area
      MOV   BHDIR,+1      ; reset direction to right
;
;  If at right wall, reset direction for bounce
;
NEWB2:CMP   BL,79          ; jump if not at right wall
      JNE   NEWB3
      MOV   BL,78          ; else reset position to right of play area
      MOV   BHDIR,-1      ; reset direction to left
      MOV   AX,BEEPR      ; set beep off right
      MOV   DX,BDUR
      CALL  NOTE
;
;  Here we have new position, check for paddle bounce
;
NEWB3:MOV   BCOL,BL          ; store new character position
      CMP   BROW,24          ; on bottom line?
      JNE   NEWB8          ; jump if not, all set
;
;  Check for case of bouncing off paddle (ball is on bottom line).
;  First we see if the ball is hitting the center of the paddle.
;
      MOV   BH,PADPOS     ; get paddle position
      MOV   BL,BCOL          ; get horizontal position of ball
      CMP   BL,BH          ; is ball hitting center of paddle?
      JE    NEWB4          ; jump if so
;
;  Test to see if ball is hitting either of the paddle squares on the left
;
      DEC   BH          ; point to square to left of center
      CMP   BL,BH          ; did ball hit this square?
      JE    NEWB5          ; jump if so
      DEC   BH          ; point to leftmost paddle square
      CMP   BL,BH          ; did ball hit this square?
      JE    NEWB5          ; jump if so
;
;  Test to see if ball is hitting either of the paddle squares on the right
;
      ADD   BH,3          ; point to square to right of center
      CMP   BL,BH          ; did ball hit this square?
      JE    NEWB6          ; jump if so
      INC   BH          ; point to rightmost paddle square
      CMP   BL,BH          ; did ball hit this square?
      JE    NEWB6          ; jump if so
;
;  This is where ball has missed the paddle and goes out of play
;
      MOV   BROW,0          ; indicate ball no longer in play
      MOV   DH,25          ; set cursor off screen
      MOV   DL,0
      CALL  SETPOS
      LEA   SI,FUNR          ; play funeral march
      CALL  TUNE
      JMP   NEWB8          ; exit
;
;  Ball hit middle of paddle, keep horizontal direction
;
NEWB4:MOV   BROW,23          ; reset line to bottom of play area
      JMP   NEWB7          ; exit
;
;  Ball hit left of paddle, reset direction to left
;
NEWB5:MOV   BROW,23          ; reset line to bottom of play area
      MOV   BHDIR,-1      ; reset direction to left
      JMP   NEWB7          ; exit
;
;  Ball hit right of paddle, reset direction to right
;
NEWB6:MOV   BROW,23          ; reset line to bottom of play area
      MOV   BHDIR,+1      ; reset direction to right
;
;  Merge here for ball bouncing off paddle, horizontal direction set
;
NEWB7:MOV   AX,BEEPP      ; set beep off paddle
      MOV   DX,BDUR
      CALL  NOTE
      MOV   BVDIR,-1      ; reset direction to up
      INC   SCORE          ; bump score
;
;  Exit here after setting new ball position and direction
;
NEWB8:POP   SI          ; restore registers
      POP   DX
      POP   BX
      POP   AX
      RET               ; return
NEWB      ENDP
;
;  Routine to update text messages (ball count and score)
;
UTEXT      PROC
      PUSH  AX          ; save registers
      PUSH  DX
;
;  Output score
;
      MOV   DX,0204H      ; set position for score
      CALL  SETPOS
         MOV   AL,'S'        ; set message SCORE:
      CALL  WCHAR
         MOV   AL,'C'
      CALL  WCHAR
         MOV   AL,'O'
      CALL  WCHAR
         MOV   AL,'R'
      CALL  WCHAR
         MOV   AL,'E'
      CALL  WCHAR
         MOV   AL,':'
      CALL  WCHAR
         MOV   AL,' '
      CALL  WCHAR
      MOV   AX,SCORE      ; output score
      CALL  UTXTI
;
;  Output ball count
;
      MOV   DL,69          ; set position for ball count
      CALL  SETPOS
         MOV   AL,'B'        ; set message BALL:
      CALL  WCHAR
         MOV   AL,'A'
      CALL  WCHAR
         MOV   AL,'L'
      CALL  WCHAR
         MOV   AL,'L'
      CALL  WCHAR
         MOV   AL,':'
      CALL  WCHAR
         MOV   AL,' '
      CALL  WCHAR
;
;  Complete output of ball count
;
      MOV   AL,BCOUNT     ; get ball count
      OR    AL,'0'        ;   in ASCII
      CALL  WCHAR          ; write ball count
      POP   DX          ; restore registers
      POP   AX
      RET               ; return to caller
UTEXT      ENDP
;
;  Routine to output integer value from AX
;
UTXTI      PROC
      PUSH  CX          ; save registers
      PUSH  DX
      MOV   CX,10          ; divide input argument by 10
      SUB   DX,DX
      DIV   CX
;
;  We have the number / 10 in AX, and number mod 10 in DX. If the
;  value in AX is non-zero, we make a recursive call to output it
;
      OR    AX,AX          ; test non-zero quotient
      JZ    UTX2          ; jump if zero
      CALL  UTXTI          ; else output upper digits
;
;  Here we output the remainder from the division as the last digit
;
UTX2: MOV   AL,DL          ; remainder to AL
      OR    AL,'0'        ; convert to ASCII
      CALL  WCHAR          ; write last digit
      POP   DX          ; restore registers
      POP   CX
      RET               ; return to caller
UTXTI      ENDP
;
;  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!
;
DELAY      PROC
      PUSH  AX          ; save registers
      PUSH  DX
      MOV   DH,25          ; get cursor off screen area
      MOV   DL,0          ;      (cleaner appearence)
      CALL  SETPOS
      SUB   AX,AX          ; zero frequency for rest
      MOV   DX,4          ; delay of 0.04 secs is reasonable
      CALL  NOTE          ; execute delay
      POP   DX          ; restore registers
      POP   AX
      RET               ; return to caller
DELAY      ENDP
;
;  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).
;
TUNE      PROC
      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
TUNE      ENDP
;
;  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)
;
NOTE      PROC
      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
      OUT   CTRL,AL          ; rewrite control port
      POP   SI          ; restore registers
      POP   DX
      POP   CX
      POP   BX
      POP   AX
      RET               ; return to caller
NOTE      ENDP
;
;  Routine to read count, returns current timer 2 count in AX
;
RCTR      PROC
      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
RCTR      ENDP
;
;  Routine to set cursor position
;
;      (DH,DL)          Cursor line, column
;      CALL  SETPOS
;
SETPOS      PROC
      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
SETPOS      ENDP
;
;  Routine to paint characters on screen
;
;      (DH,DL)          Line, column of start of area to paint
;      (CX)               Number of characters to paint
;      (BL)               Required color
;      (AL)               Character code
;      CALL  PAINT
;
PAINT      PROC
      PUSH  AX          ; save registers
      PUSH  BX
      CMP   CX,0          ; skip if no chars to paint
      JE    PAINT1
      CALL  SETPOS          ; set cursor position
      MOV   BH,0          ; write the characters
      MOV   AH,9
      INT   10H
;
;  Here with characters painted
;
PAINT1:  POP   BX          ; restore registers
      POP   AX
      RET               ; return to caller
PAINT      ENDP
;
;  Routine to write one character to screen
;
;      (AL)               Character to write
;      CALL  WCHAR
;
WCHAR      PROC
      PUSH  AX          ; save registers
      PUSH  BX
      PUSH  BP
      SUB   BH,BH          ; set page zero
      MOV   AH,14          ; set code for write teletype
      INT   10H          ; write character
      POP   BP          ; restore registers
      POP   BX
      POP   AX
      RET               ; return to caller
WCHAR      ENDP
;
      END