.MODEL	SMALL

;-----------------------------------------------------------------------;
; This file contains various procedures that write to the screen:	;
;									;
; INIT_WRITE_CHAR		Initializes this module			;
; WRITE_CHAR			Writes a character on the screen	;
; WRITE_STRING			Writes a string to the screen		;
; WRITE_HEX			Writes a two-digit hex number		;
; WRITE_HEX_DIGIT		Writes a single hexadecimal digit	;
; WRITE_DECIMAL			Writes a decimal number on the screen	;
; WRITE_PATTERN			Writes a pattern on the screen		;
; WRITE_CHAR_N_TIMES		Writes N copies of one character	;
; WRITE_ATTRIBUTE_N_TIMES	Writes just the attribute to the screen	;
;-----------------------------------------------------------------------;

.DATA
	PUBLIC	SCREEN_PTR
	PUBLIC	SCREEN_X, SCREEN_Y
SCREEN_SEG	DW	0B800h		;Segment of the screen buffer
SCREEN_PTR	DW	0		;Offset into screen memory of cursor
SCREEN_X	DB	0		;Position of the screen cursor
SCREEN_Y	DB	0


.CODE

	PUBLIC	WRITE_STRING
;-----------------------------------------------------------------------;
; This procedure writes a string of characters to the screen.  The	;
; string must end with		DB	0				;
;									;
; On entry:	DS:DX	Address of the string				;
;									;
; Uses:		WRITE_CHAR						;
;-----------------------------------------------------------------------;
WRITE_STRING	PROC
	PUSH	AX
	PUSH	DX
	PUSH	SI
	PUSHF				;Save direction flag
	CLD				;Set direction for increment (forward)
	MOV	SI,DX			;Place address into SI for LODSB
STRING_LOOP:
	LODSB				;Get a character into the AL register
	OR	AL,AL			;Have we found the 0 yet?
	JZ	END_OF_STRING		;Yes, we are done with the string
	MOV	DL,AL			;No, write character
	CALL	WRITE_CHAR
	JMP	STRING_LOOP
END_OF_STRING:
	POPF				;Restore direction flag
	POP	SI
	POP	DX
	POP	AX
	RET
WRITE_STRING	ENDP


	PUBLIC	WRITE_HEX
;-----------------------------------------------------------------------;
; This procedure converts the byte in the DL register to hex and writes	;
; the two hex digits at the current cursor position.			;
;									;
; On entry:	DL	Byte to convert to hex.				;
;									;
; Uses:		WRITE_HEX_DIGIT						;
;-----------------------------------------------------------------------;
WRITE_HEX	PROC			;Entry point
	PUSH	CX			;Save registers used in this procedure
	PUSH	DX
	MOV	DH,DL			;Make a copy of byte
	MOV	CX,4			;Get the upper nibble in DL
	SHR	DL,CL
	CALL	WRITE_HEX_DIGIT		;Display first hex digit
	MOV	DL,DH			;Get lower nibble into DL
	AND	DL,0Fh			;Remove the upper nibble
	CALL	WRITE_HEX_DIGIT		;Display second hex digit
	POP	DX
	POP	CX
	RET
WRITE_HEX	ENDP

	PUBLIC	WRITE_HEX_DIGIT
;-----------------------------------------------------------------------;
; This procedure converts the lower 4 bits of DL to a hex digit and	;
; writes it to the screen.						;
;									;
; On entry:	DL	Lower 4 bits contain number to be printed	;
;			in hex.						;
;									;
; Uses:		WRITE_CHAR						;
;-----------------------------------------------------------------------;
WRITE_HEX_DIGIT		PROC
	PUSH	DX			;Save registers used
	CMP	DL,10			;Is this nibble <10?
	JAE	HEX_LETTER		;No, convert to a letter
	ADD	DL,"0"			;Yes, convert to a digit
	JMP	Short WRITE_DIGIT	;Now write this character
HEX_LETTER:
	ADD	DL,"A"-10		;Convert to hex letter
WRITE_DIGIT:
	CALL	WRITE_CHAR		;Display the letter on the screen
	POP	DX			;Restore old value of DX
	RET
WRITE_HEX_DIGIT		ENDP

	PUBLIC	INIT_WRITE_CHAR
;-----------------------------------------------------------------------;
; You need to call this procedure before you call WRITE_CHAR since	;
; WRITE_CHAR uses information set by this procedure.			;
;									;
; Writes:	SCREEN_SEG						;
;-----------------------------------------------------------------------;
INIT_WRITE_CHAR		PROC
	PUSH	AX
	PUSH	BX
	MOV	BX,0B800h		;Set for color graphics display
	INT	11h			;Get equipment information
	AND	AL,30h			;Keep just the video display type
	CMP	AL,30h			;Is this a monochrome display adapter?
	JNE	SET_BASE		;No, it's color, so use B800
	MOV	BX,0B800h		;Yes, it's monochrome, so use B000
SET_BASE:
	MOV	SCREEN_SEG,BX		;Save the screen segment
	POP	BX
	POP	AX
	RET
INIT_WRITE_CHAR		ENDP

	PUBLIC	WRITE_CHAR
	EXTRN	CURSOR_RIGHT:PROC
;-----------------------------------------------------------------------;
; This procedure outputs a character to the screen by writing directly	;
; into screen memory, so that characters such as the backspace are	;
; treated as any other characters and are displayed.			;
;									;
; This procedure must do a bit of work to update the cursor position.	;
;									;
; On entry:	DL	Byte to print on screen.			;
;									;
; Uses:		CURSOR_RIGHT						;
; Reads:	SCREEN_SEG, SCREEN_PTR					;
;-----------------------------------------------------------------------;
WRITE_CHAR	PROC
	PUSH	AX
	PUSH	BX
	PUSH	DX
	PUSH	ES

	MOV	AX,SCREEN_SEG		;Get segment for screen memory
	MOV	ES,AX			;Point ES to screen memory
	MOV	BX,SCREEN_PTR		;Pointer to character in screen memory

	MOV	DH,7			;Use the normal attribute
	MOV	ES:[BX],DX		;Write character/attribute to screen
	CALL	CURSOR_RIGHT		;Now move to next cursor position

	POP	ES
	POP	DX
	POP	BX
	POP	AX
	RET
WRITE_CHAR	ENDP

	PUBLIC	WRITE_DECIMAL
;-----------------------------------------------------------------------;
; This procedure writes a 16-bit, unsigned number in decimal notation.	;
;									;
; On entry:	DX	N : 16-bit, unsigned number.			;
;									;
; Uses:		WRITE_HEX_DIGIT						;
;-----------------------------------------------------------------------;
WRITE_DECIMAL	PROC
	PUSH	AX			;Save registers used here
	PUSH	CX
	PUSH	DX
	PUSH	SI
	MOV	AX,DX
	MOV	SI,10			;Will divide by 10 using SI
	XOR	CX,CX			;Count of digits placed on stack
NON_ZERO:
	XOR	DX,DX			;Set upper word of N to 0
	DIV	SI			;Calculate N/10 and (N mod 10)
	PUSH	DX			;Push one digit onto the stack
	INC	CX			;One more digit added
	OR	AX,AX			;N = 0 yet?
	JNE	NON_ZERO		;Nope, continue
WRITE_DIGIT_LOOP:
	POP	DX			;Get the digits in reverse order
	CALL	WRITE_HEX_DIGIT
	LOOP	WRITE_DIGIT_LOOP
END_DECIMAL:
	POP	SI
	POP	DX
	POP	CX
	POP	AX
	RET
WRITE_DECIMAL	ENDP

	PUBLIC	WRITE_CHAR_N_TIMES
;-----------------------------------------------------------------------;
; This procedure writes more than one copy of a character		;
;									;
; On entry:	DL	Character code					;
;		CX	Number of times to write the character		;
;									;
; Uses:		WRITE_CHAR						;
;-----------------------------------------------------------------------;
WRITE_CHAR_N_TIMES	PROC
	PUSH	CX
N_TIMES:
	CALL	WRITE_CHAR
	LOOP	N_TIMES
	POP	CX
	RET
WRITE_CHAR_N_TIMES	ENDP


	PUBLIC	WRITE_ATTRIBUTE_N_TIMES
	EXTRN	CURSOR_RIGHT:PROC
;-----------------------------------------------------------------------;
; This procedure sets the attribute for N characters, starting at the	;
; current cursor position.						;
;									;
;	CX	Number of characters to set attribute for		;
;	DL	New attribute for characters				;
;									;
; Uses:		CURSOR_RIGHT						;
; Reads:	SCREEN_SEG, SCREEN_PTR					;
;-----------------------------------------------------------------------;
WRITE_ATTRIBUTE_N_TIMES		PROC
	PUSH	AX
	PUSH	CX
	PUSH	DI
	PUSH	ES

	MOV	AX,SCREEN_SEG		;Set ES to point to screen segment
	MOV	ES,AX
	MOV	DI,SCREEN_PTR		;Character under cursor
	INC	DI			;Point to the attribute under cursor
	MOV	AL,DL			;Put attribute into AL
ATTR_LOOP:
	STOSB				;Save one attribute
	INC	DI			;Move to next attribute
	INC	SCREEN_X		;Move to next column
	LOOP	ATTR_LOOP		;Write N attributes

	DEC	DI			;Point to start of next character
	MOV	SCREEN_PTR,DI		;Remember where we are

	POP	ES
	POP	DI
	POP	CX
	POP	AX
	RET
WRITE_ATTRIBUTE_N_TIMES	ENDP


	PUBLIC	WRITE_PATTERN
;-----------------------------------------------------------------------;
; This procedure writes a line to the screen, based on data in the	;
; form									;
;									;
;	DB	{character, number of times to write character}, 0	;
; Where {x} means that x can be repeated any number of times		;
;									;
; On entry:	DS:DX	Address of the pattern to draw			;
;									;
; Uses:		WRITE_CHAR_N_TIMES					;
;-----------------------------------------------------------------------;
WRITE_PATTERN	PROC
	PUSH	AX
	PUSH	CX
	PUSH	DX
	PUSH	SI
	PUSHF				;Save the direction flag
	CLD				;Set direction flag for increment
	MOV	SI,DX			;Move offset into SI register for LODSB
PATTERN_LOOP:
	LODSB				;Get character data into AL
	OR	AL,AL			;Is it the end of data (0h)?
	JZ	END_PATTERN		;Yes, return
	MOV	DL,AL			;No, set up to write character N times
	LODSB				;Get the repeat count into AL
	MOV	CL,AL			;And put in CX for WRITE_CHAR_N_TIMES
	XOR	CH,CH			;Zero upper byte of CX
	CALL	WRITE_CHAR_N_TIMES
	JMP	PATTERN_LOOP
END_PATTERN:
	POPF				;Restore direction flag
	POP	SI
	POP	DX
	POP	CX
	POP	AX
	RET
WRITE_PATTERN	ENDP

	END
