Search code examples
assemblytasm

Correctly display the message output using one variable while retaining their position


Good day, dear programmers,

We are assigned by our teacher to create a program countdown from 5 - 0.
Most of my classmate use many variables to display the output. It does work by coding it like below the given code.

The problem is the code looks horrible and creates too many additional LOC.

The problem is how can I shorten my code?

I don't like to use many variables for that and call each variable one by one and call the cursor position too many times. I am only posting a part of the code because the code is too long.

This is my code

csrPos macro x1:REQ, y1:REQ
       mov ah, 02h
       mov bh, 0
       mov dl, x1
       mov dh, y1
       int 10h
       endm

prnstr macro msg
    mov ah, 9
    mov dx, offset msg
    int 21h
    endm

.model small
.stack 100h
.data
       ; Five message with light color green
       msgFive0 db 27, "[2;32;40m$"
       msgFive1 db "ÛÛÛÛÛ$"
       msgFive2 db "ÛÛ   $"
       msgFive3 db "ÛÛ   $"
       msgFive4 db "ÛÛÛÛ $"
       msgFive5 db "   ÛÛ$"
       msgFive6 db "   ÛÛ$"
       msgFive7 db "ÛÛ ÛÛ$"
       msgFive8 db "ÛÛ ÛÛ$"
       msgFive9 db " ÛÛÛ $"
.code
       mov ax, @data
       mov ds, ax

       csrPos 38, 7
       prnstr msgfive0
       csrPos 38, 8
       prnstr msgfive1
       csrPos 38, 9
       prnstr msgfive2
       csrPos 38, 10
       prnstr msgfive3
       csrPos 38, 11
       prnstr msgfive4
       csrPos 38, 12
       prnstr msgfive5
       csrPos 38, 13
       prnstr msgfive6
       csrPos 38, 14
       prnstr msgfive7
       csrPos 38, 15
       prnstr msgfive8
       csrPos 38, 16
       prnstr msgfive9

       mov ah, 4ch
       int 21h

END

Output:
This is the output of that program when I run it. It already looks good as our teacher told us to make. Five output

What I want to do is like this.

.data
       ; Five message with light color green
       msgFive db 27, "[2;32;40m$"
                db "ÛÛÛÛÛ$"
                db "ÛÛ   $"
                db "ÛÛ   $"
                db "ÛÛÛÛ $"
                db "   ÛÛ$"
                db "   ÛÛ$"
                db "ÛÛ ÛÛ$"
                db "ÛÛ ÛÛ$"
                db " ÛÛÛ $"


.code
       mov ax, @data
       mov ds, ax

       csrPos 38, 7
       prnstr msgfive

       mov ah, 4ch
       int 21h
END

How can I do it while retaining the output using that code?

Sorry for my bad English. I am using TASM assembler


Solution

  • Well, your general idea is sort of good, but you don't explain what problem you hit to not implement it.

    Seems to me one of the PITA will be that macro csrPos 38, 7 (I really hate macros, especially in code of people just learning assembly, so take this as a bit biased, but really... I will try to explain also in factual way, so you can decide yourself).

    That does instantly move output position for next output, but then you print the character as series of multiple output, so you need to adjust position between... but that proposed code prnstr msgfive has no simple idea where the previous csrPos macro did set it up. You can solve that inside prnstr by using BIOS service to actually read the current position back before print, print the chars, adjust position, and do csrPos ... that idea should immediately feel like "code smell" (although if you have no better idea, it often helps to write it at least in "wrong" way, so you can see yourself resulting source, and think about it again with the new experience, that often helps to find out better solution).

    So what you probably want is something like prnstr msgfive, 38, 7, letting the prnstr to handle the position itself, and removing the csrPos 38, 7 from main part of code... that will save you also 1LOC, also it removes that information responsibility clash, where prnstr was not aware where it should print it.

    But then I would sacrifice few LOC of source to avoid macro completely and rather do something like (example, also feel free to pick different way of providing arguments, like different registers or even stack based calling convention, if you wish):

        ...
        mov  dx,38 + 7*256        ; position[38,7]
        mov  si,OFFSET char_five  ; memory address of font data
        mov  bl,10                ; light green color
        call print_char           ; call subroutine to output the character
        ...
    

    Now the print_char subroutine, doing all the heavy-lifting, has all important information, so it can decide itself, when/how it will modify output position, and the task of displaying some character is now only 4 LOC in the main block, which is IMO reasonable.


    How you can get good ideas how to shorten your code .. well, if you take a look on your original code (in disassembly view in debugger, ideally), you will see series of the same instructions, repeating over and over, just with different immediate values. That's always the sign, that you can move those immediate data into some kind of data structure, and write the code only once, using some kind of "loop" around to repeat it with multiple data.

    So you should look for essential parts of your code, which is distinct from others, and keeps repeating - if it's like 3+ times, it's probably worth to convert it into subroutine.

    Also you should often try to look for immediate data, which can be actually calculated - like if you want to print prime numbers from 2 to 1000, you can either put the full list as text into source (and it will be fastest solution!), or you can program it as loop from integer 2 to 1000, testing each if it is a prime, and output it (smaller binary for the price of computation).

    Some data are often simpler to calculate, then to write manually in the source (for example "cursor position for block of lines starting at column 38), and the performance drop is often negligible (like in your current task, you should have 1 sec delay, so whether you display the digit in 10 microseconds or 15 microseconds doesn't matter that much).

    Other ASM-related ways of saving some LOCs or to make it easier to maintain:

    • if you know your subroutine will have to put position into dh and dl, and you are free to use your own custom calling convention, then there's no reason to not pass the argument directly in dx, so the subroutine doesn't need to copy/move the argument values into dh:dl itself.

    • use subroutines instead of macros, it makes easier to review the source for others and it's easier for debugging, as what you see in debugger is lot more similar, what you wrote into source. When you debug macros, you see them in debugger in the injected form, i.e. it doesn't resemble the source at all, which makes it more confusing (once you will hit the special situation, where using macro makes sense, you will know it, but at this moment your macros are rather like just subroutines and should be written like that). (also when seasoned programmer is reviewing your code, they know what mov ax,10h does, but they have no idea what csrPos does, because that's not x86 instruction, so they have to switch back and forth between the code they are reviewing and macro definition, because they will not memorize it instantly, but they must every time consider every instruction hidden in the macro, as those may have unwanted side effects to your other code, so they can't just be like "ok, that does just change position, no need to bother with instructions" ... also you yourself will be confused, when you will check that source after few months)

    You can check my answer of other somewhat-similar question here TASM cursor position to see particular way of implementing similar print_char subroutine, using loops and data definitions to make the code more "general", suitable for printing "any" character. It makes the subroutine itself lot more complex than your pos, print, pos, print, pos, print, ... sequence, but you have to only write it once, debug only hundred times, and then you can use it for all characters. Also it's more fun, even if it takes longer. And you can brag you are more sophisticated programmer than somebody using that tedious manual sequence of writing everything... ;) :D