4
\$\begingroup\$

We have been given this project and I was wondering if someone could make my algorithm better using TASM and DOSBox.

This is what I have done so far:

ideal
model small
stack 10244
dataseg
msg1 db 'Enter Word: $'
msg2 db 'Enter Hint: $'
msg3 db 'Enter Guess(Press . to end guess)($'
wrnmsg db 'Wrong!$'
a3msg db 'Number of letters: $'
a4msg db 'First two letters: $'
a5msg db 'Last letter: $'
a6msg db 'Second to the last letter: $'
fail db ' Enter guess again($'
secret db 21 dup('')
hint db 101 dup('')
guess db 21 dup('')
endss db '): $'
win db 'That is correct! You win!$'
lose db 'Im sorry you lose$'
cnt dw 0
codeseg
PROC compare
mov bx, 0
mov di, 0

loop1:
    mov al, [secret+di]
    mov ah, [guess+di]

    cmp ah, al
    jne no
    cmp al, '$'
    je endi
no:
    mov bx, 1
endi:
    ret
ENDP

PROC main
mov ax, @data
mov ds, ax
mov es, ax

lea dx, [msg1]
mov ah, 9
int 21h

mov bx, 0
scan:
    mov ah, 1
    int 21h
    cmp al, '.'
    je com
    inc [cnt]
    mov [secret+bx], al
    inc bx
    jmp scan
com:
    mov [secret+bx], '$'

mov dx, 10
mov ah, 2
int 21h

lea dx, [msg2]
mov ah, 9
int 21h

lea dx, [hint]
mov ah, 10
int 21h

mov dx, 10
mov ah, 2
int 21h

lea dx, [msg3]
mov ah, 9
int 21h

lea dx, [hint]
mov ah, 9
int 21h

lea dx, [endss]
mov ah, 9
int 21h

mov cx, 0
loop2:
    inc cx
    mov bx, 0
    scan2:
        mov ah, 1
        int 21h
        cmp al, '.'
        je com2
        mov [guess+bx], al
        inc bx
        jmp scan2
    com2:
        mov [guess+bx], '$'

        mov dx, 10
        mov ah, 2
        int 21h

    call compare
    cmp bx, 0
    je right

    lea dx, [wrnmsg]
    mov ah, 9
    int 21h

    cmp cx, 3
    je three
    cmp cx, 4
    je four
    cmp cx, 5
    je five
    cmp cx, 6
    je six
    cmp cx, 7
    je fail

    man:

    mov dx, 10
    mov ah, 2
    int 21h

    lea dx, [fail]
    mov ah, 9
    int 21h

    lea dx, [hint]
    mov ah, 9
    int 21h

    lea dx, [endss]
    mov ah, 9
    int 21h

    jmp loop2

right:
    lea dx [win]
    mov ah, 9
    int 21h

    jmp end1

fail:
    lea dx, [lose]
    mov ah, 9
    int 21h

    jmp end1

three:
    lea dx, [a3msg]
    mov ah, 9
    int 21h

    lea dx, [cnt]
    mov ah, 2
    int 21h
    jmp man
four:
    lea dx, [a4msg]
    mov ah, 9
    int 21h

    mov di, 0
    mov si, 1
    mov dh, [secret+di]
    mov ah, 2
    int 21h
    mov dh, [secret+si]
    int 21h

    jmp man
five:
    lea dx, [a5msg]
    mov ah, 9
    int 21h

    mov di, [cnt]
    mov dl, [secret+di]
    mov ah, 2
    int 21h

    jmp man
six:
    lea dx, [a6msg]
    mov ah, 9
    int 21h

    mov di, [cnt]
    dec di
    mov ah, 2
    int 21h

    jmp man
end1:
    mov ah, 4ch
    int 21h
ENDP
END main

What was required of us was a program that will simulate a guessing game where the first player will input the word to be guessed by the second player including a hint such as what category and other description. (Description can have a maximum of 100 characters).

The second player will be given 6 chances to guess. If in the third try, the 2nd player still cannot guess the word, hints will be given for the succeeding unsuccessful guesses as cited below:

  • Third unsuccessful try – Number of letters will be displayed on screen
  • Fourth unsuccessful try – First two letters will be revealed
  • Fifth unsuccessful try – Last letter will be revealed
  • Sixth unsuccessful try- Second to the last letter will be revealed
\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

You have a bit of code duplication that could be extracted and consolidated. But first with some error fixes (at least the code did not compile on my TASM).

Compilation issues

right:
    lea dx [win] ; <-- missing comma between dx and [win]

fail is defined twice. First as a string (fail db ' Enter guess again($') and the second time as a label. I would rename the first one to failmsg and use it in

lea dx, [failmsg] ; <-- instead of fail before

With those fixes the code compiles now. As for the code review I would start with

DOS functions

mov ah,09
int 21h

which is just Display string. So let's call it like that and make sure a requirement that when calling it we have dx

PROC display_string
   mov ah,09
   int 21h
   ret
ENDP

and instead of

lea dx, [a3msg]
mov ah, 9
int 21h

just do

lea dx, [a3msg]
call display_string

The same goes for other DOS functions

PROC get_char
   mov ah,01
   int 21h
   ret
ENDP

PROC put_char
   mov ah,02
   int 21h
   ret
ENDP

Using those instead of bare int-ing will make your code more readable. Having those it's clear what you try to do with this code

High-level functions

mov dx, 10
call put_char

but in DOS it should be 2 characters: 13,10 so it's better to do like this:

PROC new_line
  mov dx,13
  call put_char
  mov dx,10
  call put_char
  ret
ENDP

Buffered input

I don't know why you chose buffered input as your method of entering the hint, but in case of this command the data has a specific format.

hint db 100,?,101 dup(0)

The actual string starts on index 2 and before that you have max string length, and bytes read. Also there's no $ at the end. You have to put it there.

xor bx,bx
mov bl, [hint+1]
mov [hint+bx+2], '$'

So printing the hint should look like this:

lea dx, [hint+2]
call display_string

Printing the count

This is obviously wrong

lea dx, [cnt]
call put_char

as in the cnt will be the number of characters not the ascii representation of that number. If we could restrict ourselves to maximum 9 characters that would be easy. Just add 48(dec) to the cnt and you get the value. Since your secret is limited to 21 characters we could go with a simple method for printing.

PROC print_number
    push bx
    push dx
    mov bx,10
    xor dx,dx
    div bx
    mov bx,dx
    cmp ax,0
    je second
    mov dx,ax
    add dx,48
    call put_char
second:
    mov dx,bx
    add dx,48
    call put_char
    pop dx
    pop bx
    ret
ENDP

What we do here is just divide the number in ax by 10 and print the values stored in ax (result - only if >0) and the dx - reminder.

Printing the hints

On fourth attempt you are putting the character to be printed in dh. It should be in dl. You don't also have to use different registers so just:

mov dl, [secret]
call put_char
mov dl, [secret+1]
call put_char

To print the last char you need to keep in might that the last is '$' so just one before the last

mov di, [cnt]
mov dl, [secret+di-1]
call put_char

The same issue when you try to print the 6th hint. Also that you did not retrieve the char at all.

mov di, [cnt]
sub di,2
mov dl, [secret+di-1]
call put_char

Compare

Your compare method is kinda strange. You do not iterate at all and you don't use any opcodes that would do that for you. It works only when you have one-char secret. Not so cool :)

I would write it like this

PROC compare
  push cx
  lea si, [secret]
  lea di, [guess]
  rep cmpsb
  pop cx
  ret
ENDP

and change how to check for equality

call compare
je right

Other things that might be good to fix

You have the same loop for scanning the characters. Why not enclose it as another proc and just use in those two places? You can make assumption that for example di will point to the buffer that has to be filled. Also in this scanning you could handle for example backspace character and delete the characters.

PROC read_input
  push cx
  mov bx, 0
  mov cx, 0
scan:
  call get_char
  cmp al, '.'
  je com
  inc cx
  mov [di+bx], al
  inc bx
  jmp scan
com:
  mov [di+bx], '$'
  mov ax,cx
  pop cx
  ret
ENDP

And use it

lea di,[secret]
call read_input
mov [cnt],ax

and similarly in the second place.

Also some checks for input length might be good to do.

You should also comment more your code. I strongly believe the code should be self documented but not on such low level as asm :) So comment more.

GIST

\$\endgroup\$
3
  • \$\begingroup\$ cmp reg, 0 => test reg, reg. mov reg, 0 => xor reg, reg. Your proposed compare method is invalid, since you don't actually set cx. Furthermore, you should be using either repe/repz or repne/repnz with cmpsb, not just rep. You also don't need lea there; a simple mov would work just fine to get the address of the secret and guess variables. And you probably shouldn't be assuming the state of the direction flag; procedures that use string instructions should explicitly CLD or STD. \$\endgroup\$
    – Cody Gray
    Commented May 26, 2017 at 12:21
  • \$\begingroup\$ in general you are true with the cmp/test, and mov/xor but those are only shorter opcodes and I wouldn't go with that if I don't need to reduce the size as much as possible. As for the compare I'm pretty sure I've tested it and worked so maybe I've missed something when writing the answer - need to check the code again; as for the direction flag - agreed. Should be explicitly set. \$\endgroup\$ Commented May 26, 2017 at 20:46
  • \$\begingroup\$ They are shorter opcodes, which makes them faster. There is never a disadvantage. It should just be the way you write the code in the first place, like standard idioms. Your assembler probably converts the simple rep prefix into repe, then. Some of them do this, trying to be helpful. \$\endgroup\$
    – Cody Gray
    Commented May 28, 2017 at 8:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.