I'm new to assembly and I've just finished this guessing game that I wanted to make to improve my skills in this language. It runs in a Unix environment and does the following:
- generates a random number between 0 and 255,
- the user inputs a guess belonging to that range,
- the program converts it into an integer,
- the program then emits a feedback based on whether the input is too low, high or right,
- the program gets back to the first step if it's not the right number else it's the end.
I haven't noticed any bugs yet; it seems to work properly. There's a few things I would like to address in the future; tell me what I should add to this list after you've taken a look at the code below:
- the code is not made to be robust: it is very error-prone if the user puts in the wrong type of data and/or too many chars,
- the switch or match part, no matter what you call it, of the guess function feels a bit weird to me; there's a lot of unnecessary assignment to the registers and I do the same things multiple times (like doing the comparison)
- the range 0-255 is a bit weird, often it's 1-100 but it's not unplayable: the worst case scenario is 1+log_2(256) = 9 moves in worst case dichotomic search; also it avoids further manipulation of the random number which could easily cause errors during development.
It's written using AT&T syntax.
main.s
.section .data
.equ SUCCESS, 0
.equ EXIT, 1
.equ SYS_CALL, 0x80
.section .text
.globl _start
_start:
call random
pushl %eax # Save random on the stack as arg
call guess
addl $4, %esp
movl $SUCCESS, %ebx
movl $EXIT, %eax
int $SYS_CALL
random.s
.section .data
.equ GET_RAND, 355
.equ GET_RAND_FLAGS, 0
.equ SYS_CALL, 0x80
.section .bss
.equ RAND_SIZE, 1
.lcomm RAND, RAND_SIZE
.section .text
# Output between 0 and 255
.globl random
.type random, @function
random:
pushl %ebp
movl %esp, %ebp
movl $GET_RAND, %eax
movl $RAND, %ebx
movl $RAND_SIZE, %ecx
movl $GET_RAND_FLAGS, %edx
int $SYS_CALL
movl RAND, %eax
movl $0, RAND # clear buffer
movl %ebp, %esp
popl %ebp
ret
guess.s
.section .data
MSG_RIGHT: .ascii "That's the right number!\n"
MSG_LOWER: .ascii "Lower!\n"
MSG_HIGHER: .ascii "Higher!\n"
.equ LEN_RIGHT, 25
.equ LEN_LOWER, 7
.equ LEN_HIGHER, 8
.equ READ, 3
.equ WRITE, 4
.equ STDIN, 0
.equ STDOUT, 1
.equ SYS_CALL, 0x80
.equ CHAR_NEW_LINE, 10
.equ CHAR_ZERO, 48
.section .bss
.equ BUFFER_SIZE, 4 # not much size needed, just 4 bytes would suffice
# (from "0" to "255" + '\n')
.lcomm BUFFER_DATA, BUFFER_SIZE
.section .text
# INPUT: the secret number
#
# iteratively guess the number and get a feedback
# until win
#
# -4(%ebp) -> user integer input
# -8(%ebp) -> winning flag ($1 = win)
.globl guess
.type guess, @function
guess:
pushl %ebp
movl %esp, %ebp
movl $0, -8(%ebp) # clear flag
get_input:
# clear input
movl $0, -4(%ebp)
movl $READ, %eax
movl $STDIN, %ebx
movl $BUFFER_DATA, %ecx
movl $BUFFER_SIZE, %edx
int $SYS_CALL
# convert string into number
atoi:
movl $0, %ecx # current digit
movl $0, %edx # current result
movl $BUFFER_DATA, %ebx # pointer into %ebx
atoi_next:
cmpb $CHAR_NEW_LINE, (%ebx) # is the char '\n'?
je atoi_end
# update the current result
imull $10, %edx # %edx *= 10
movb (%ebx), %cl # %ecx = char (putting in %ecx before hand to prevent overflow)
subb $CHAR_ZERO, %cl # %ecx -= '0'
addl %ecx, %edx
movb $0, (%ebx) # clear behind myself
incl %ebx # move the pointer
jmp atoi_next
atoi_end:
movl $0, (%ebx) # clear again
movl %edx, -4(%ebp) # save int input as loc var
movl 8(%ebp), %ebx # put random into %ebx
# case input == random
movl $1, -8(%ebp) # right flag
movl $MSG_RIGHT, %ecx
movl $LEN_RIGHT, %edx
cmpl %ebx, -4(%ebp)
je write
# case input < random
movl $0, -8(%ebp) # wrong flag
movl $MSG_HIGHER, %ecx
movl $LEN_HIGHER, %edx
cmpl %ebx, -4(%ebp)
jl write
# case input > random
movl $0, -8(%ebp) # wrong flag
movl $MSG_LOWER, %ecx
movl $LEN_LOWER, %edx
cmpl %ebx, -4(%ebp)
jg write
write:
movl $WRITE, %eax
movl $STDOUT, %ebx
int $SYS_CALL
cmpl $0, -8(%ebp) # is the flag false?
je get_input
movl %ebp, %esp
popl %ebp
ret
and to compile:
build.sh
(execute it in the same directory as main.s, random.s and guess.s)
#build set of assembly files together
#MODIFY BEGIN - /!\ filenames without extensions
main="main" # entry-point
out="out" # output executable
dependencies=("guess" "random") # dependencies of the project
#MODIFY STOP
as --32 $main.s -o $main.o
obj_files="$main.o"
for dep in ${dependencies[@]}; do
as --32 $dep.s -o $dep.o
obj_files+=" $dep.o"
done
ld -m elf_i386 $obj_files -o $out
rm $main.o
for dep in ${dependencies[@]}; do
rm $dep.o
done
Then you can just run ./out