Search code examples
assemblyx86-16tasmdosbox

How to set cursor position and color of a pattern in assembly language?


I'm trying to set the cursor position and color of a specific pattern in assembly; specifically, the pattern is a parallelogram (once again, haha) and its color is red. I have already tried putting only the block of code for coloring at the start of the pattern, but by putting mov ch,100h yields the result that I am not looking for, what it prints are excess characters which destroyed the shape. The problem must be involving the CH register because I've used it for printing the parallelogram. One solution that I could think of is to loop the cursor start position and coloring as it prints the characters and increments the color register along with it. However, since the parallelogram uses the said register, I might have to store it in a variable. I would like to ask for your thoughts about it. I've done this is in a procedural way, and I am rather a new one for screen processing.

This is developed using TASM/TLINK with DOSBOX as an emulator. This specific parallelogram is part of a logo that I am trying to output in assembly.

Here is an excerpt of my code:

; ============================

LPARALLEL PROC 
    
    ; CLEAR ALL REGISTERS 
    xor cx,cx
    xor bx,bx
    xor ax,ax 

    mov ah,02h
    mov bh,00h
    mov dh,9
    mov dl,41
    int 10h
    
    mov ah,09h
    mov cx,10
    mov bl,74H
    mov dx,30h
    int 10h
    
    sub INPUTPL,30h
    
    mov cl,INPUTPL
    mov bh,INPUTPL 
    mov bl,0
    
    mov DIA,bh 
    mov LBLANK,bl 
    
    LP_SPACES:
    cmp LBLANK,0
    JE PRINT_CHL
    
    mov ah,02h
    mov dl,20h
    int 21h
    dec LBLANK 
    
    JMP LP_SPACES 
    
    PRINT_CHL:
    mov ah,02h
    mov dl,04h
    int 21h
    
    mov ah,02h
    mov dl,20h
    int 21h
    
    dec DIA 
    cmp DIA,0
    JNE PRINT_CHL 
    
    lea dx,LINE 
    mov ah,09h
    int 21h
    
    inc bh 
    mov DIA,bh
    dec DIA 
    dec bh 
    
    inc bl
    mov LBLANK,bl
    loop LP_SPACES
    
    RET 
    LPARALLEL ENDP 

; ============================

Thank you to those who will take their time to answer this question, sending all good wishes <3


Solution

  • What did you expect here?

    mov ah,09h
    mov cx,10
    mov bl,74H
    mov dx,30h
    int 10h
    

    This code writes 10 RedOnWhite spaces on the screen. The mov dx,30h instruction has no effect, and the AL register happens to contain 0 and therefore BIOS outputs space characters.

    Why so convoluted?

    inc bh 
    mov DIA,bh
    dec DIA 
    dec bh 
    

    This code snippet is equivalent to just mov DIA,bh.

    A quick optimization

    dec DIA 
    cmp DIA,0
    JNE PRINT_CHL
    

    You don't need that cmp DIA,0 instruction. The dec DIA instruction by itself sets the flags that you need in order to jump conditionally.


    Since you want to color the output, it is best to use BIOS all the way. Write the colored character via the BIOS.WriteCharacterAndAttribute function 09h and have it followed by the BIOS.Teletype function 0Eh in order to advance the cursor.
    Use a subroutine so the main loop stays readable.

    ; IN (al is ASCII, ah is ColorAttribute) OUT () MOD (ax,bx,cx)
    PrintColoredCharacter:
        mov  cx, 1                 ; RepetitionCount
        mov  bh, 0                 ; DisplayPage
        mov  bl, ah                ; ColorAttribute
        mov  ah, 09h               ; BIOS.WriteCharacterAndAttribute
        int  10h
        mov  ah, 0Eh               ; BIOS.Teletype
        int  10h
        ret
    

    You don't need the memory-based variables DIA and LBLANK to write this. There are more registers available to you. Simply put the NumberOfLeadingSpaces in the SI register, and the NumberOfDiamonds in the DI register.
    For the leading spaces you probably don't care about the color. That's why I used the standard WhiteOnBlack in the below code:

        mov  al, INPUTPL           ; Byte-sized variable holding value 1 or more
        mov  ah, 0
        mov  di, ax                ; NumberOfDiamonds (dimension of the parallelogram)
        xor  si, si                ; NumberOfLeadingSpaces
    
    MORE_LINES:
        push si                    ; (1)
        push di                    ; (2)
    
        test si, si
        jz   PRINT_CHL
    LP_SPACES:
        mov  ax, 0720h             ; WhiteOnBlack space
        call PrintColoredCharacter ; -> (AX BX CX)
        dec  si
        jnz  LP_SPACES
    
    PRINT_CHL:
        mov  ax, 7404h             ; RedOnWhite diamond
        call PrintColoredCharacter ; -> (AX BX CX)
        mov  ax, 7420h             ; RedOnWhite space
        call PrintColoredCharacter ; -> (AX BX CX)
        dec  di
        jnz  PRINT_CHL 
    
        lea  dx, LINE
        mov  ah, 09h
        int  21h
        
        pop  di                    ; (2)
        pop  si                    ; (1)
        inc  si                    ; NumberOfLeadingSpaces++
        cmp  si, di
        jb   MORE_LINES
    

    The above code uses test si, si jz PRINT_CHL to determine if the number of leading spaces is zero. This could have been written like cmp si, 0 je PRINT_CHL, but that would have cost us an extra byte for encoding the immediate number.

    In order to see if a register is zero, we can or or and the register to itself and inspect the zero flag. However, the more idiomatic way is to test the register with itself. Under the hood the test instruction acts like an and instruction but one that forgets to write the destination operand.

    OR       (DEST) <- (LSRC) | (RSRC)
    AND      (DEST) <- (LSRC) & (RSRC)
    TEST     (LSRC) & (RSRC)
    

    All of these will

    • clear the overflow flag (OF) and the carry flag (CF)
    • modify the sign flag (SF), the zero flag (ZF), and the parity flag (PF)
    • leave the condition of the auxiliary flag (AF) undefined