Move a caracter around the screen using arrow keys...(ASSEMBLY)

Started by IndioDoido, June 01, 2007, 08:16:39 PM

Previous topic - Next topic

IndioDoido

hi!

i'm trying to create a simple pacman like game, and i'm starting to make a simple caracter move around the screen using the arrow keys.

I made a asm code to make a '*' move to the right when the right arrow key is pressed, so far so good, but it keeps "creating" '*' caracters, and draw's a '*' line. And i would like just for one '*' to move right.

Can anyone help me?

here's my code:

mov_right:
mov ax,0b800h
mov es,ax

mov al,0h
mov ah,'*'
mov es:[bx],ah
inc bx
inc bx
jmp ciclo

MichaelW

To make it appear that the "*" is moving, for each move you must erase the current "*", update the screen buffer address, and then draw the new "*". Another problem with your code is that END is a MASM reserved word, and you are using it as a label. Also, Interrupt 21h Function 8 will wait for a key press, so there is no need to check the keyboard status. This is actually a 16-bit DOS program and I will be moving it to the 16-bit DOS Programming forum before very long.

eschew obfuscation

IndioDoido

hey MichaelW, tnx for the reply.

i'v change the code so that the last caracter can be erased:


mov_right:
mov ah,'*'
inc bx
mov es:[bx],ah
mov ah,00h
dec bx
mov es:[bx],ah
inc bx
jmp ciclo


i can move around the screen, it's not that realistic, but ok...i think it will do.
But the problem is that now the '*' appears alternately with a green box. Example, while pressing a arrow key: * [greenbox] * [greenbox] *

why is that?

MichaelW

The code does not display anything for me under Windows 2000. The problem with the green boxes is probably the result of the character code being written into the display buffer attribute bytes. Unless you are trying to change the default display colors (white on black), you should skip over the attribute bytes, always at odd addresses, and write the character only into the character bytes, always at even addresses. In case this is not clear, the display buffer is effectively laid out like this, starting at offset 0, with C = character byte and A = attribute byte:

CACACACA...

After each buffer update I would leave the buffer address set to the last character written. Then after each press of an arrow key, write a space character (ASCII code 32) to that address to erase the current character, then update the buffer address, and write the new character.
eschew obfuscation

IndioDoido

hey again MichaelW

the caracter motion is working nicely now :D

now i have another question, i've loaded a .txt file with the labyrinth and now i want the caracter to stop when he finds a wall.
how can i do that?


Ex.:
CMP BX, '#'  ??
je outofhere

mov al,'<'
inc bx
mov es:[bx],al
mov al,00h
dec bx
mov es:[bx],al
inc bx
jmp ciclo

MichaelW

You could check for the wall character in the next character cell in the direction of motion, before you erase the last character. One efficient way to do this would be to put the address adjustment value (+2, -2, +160, -160) in an index register, and use the value to check for the wall character, and update bx. You are accessing the display buffer with an indirect memory operand, in this case [bx]. See Indirect Memory Operands here. So the instruction that checks the wall character in the next character cell in the direction of motion could be something like this:

cmp [bx+di], wallchar

And to update bx, instead of adding or subtracting a constant, you could just add the value in the index register to bx.
eschew obfuscation

IndioDoido

THANKS ALOT!!!!

I now have a little nice pacman running around without passing through the labyrinth  :cheekygreen:

The first stage is over ;)
Now i'mg going to work on the fantom, i only need one chasing pacman, and it's not inteligent.
All i need is a fantom moving arownd randomly.

In our source files for this project, we have this random number generator code:

CalcAleat proc near

sub sp,2
push bp
mov bp,sp
push ax
push cx
push dx
mov ax,[bp+4]
mov [bp+2],ax

mov ah,00h
int 1ah

add dx,ultimo_num_aleat
add cx,dx
mov ax,65521
push dx
mul cx
pop dx
xchg dl,dh
add dx,32749
add dx,ax

mov ultimo_num_aleat,dx

mov [BP+4],dx

pop dx
pop cx
pop ax
pop bp
ret
CalcAleat endp


But i have no ideia, on how to use it  :eek
I was thinking on using a similar code as the one i used to move pacman, but instead, i would get the random number from the above code and send it to [bx]:

mov al,0Fh
mov ah,'%'
mov es:[bx],ah
mov es:[bx+1],al


Will this work? If so, how can i accomplish it?

MichaelW

The CalcAleat procedure leaves a 16-bit number in ultimo_num_aleat. The number appears to be at least somewhat random, but the procedure is a mess. Among other problems it modifies SP without preserving it, then moves the return address on the stack to compensate. As it is, the following instructions can be removed without affecting the generated number.

sub sp,2
push bp
mov bp,sp
mov ax,[bp+4]
mov [bp+2],ax
mov [BP+4],dx
pop bp

I'm not sure how you want the "fantom" to move. If you want it to move smoothly then I think you will need to move it one character cell at time, in a random direction, or in a direction that changes randomly after some time interval or number of moves. If you have 4 possible directions, you could use the value of the lower 2 bits of the random number to select the direction. If you just want it to appear in random locations, then you could scale the random number to get it into an acceptable range and then clear bit0 of the result to get a character address. Either way you will need to limit the rate at which it moves, I think either by synchronizing the moves with user key presses, or with a timer.
eschew obfuscation

IndioDoido

hi MichaelW

What i really want is to move the ghost in a direction that changes randomly after some time interval. But the ghost has to move around alone, so it seems i will need to use a timer, and not the keys  :(

Can you tell me how to use the timer to move the ghost?

MichaelW

The first method that occurs to me is to do everything in a big main loop. Because the main loop would need to run continuously you would need to check the keyboard status and ensure that a keystroke was available before calling any input function that would otherwise wait for a key press. The main loop would move pacman according to the user keystrokes, monitor the system timer ticks, and move the ghost by one character cell position each time some number of timer ticks had elapsed, with the number determining the speed of motion. The ghost's direction of motion could be changed after some fixed number of timer ticks, or after some random number of timer ticks. The direction of motion could be selected at random, or according to some predefined algorithm. The count of timer ticks since midnight is stored in the BIOS data area located at segment address 40h, as 32-bit value at offset 6Ch. The timer ticks occur ~18.2 times per second. Handling the timer rollover at midnight would complicate the timing code somewhat, because you could not depend on the count always increasing, but not handling the rollover would cause the program to malfunction if it happened to be running when the rollover occurred. There are other methods of getting at and/or using the system timer tick, but this method is by far the easiest.
eschew obfuscation

IndioDoido

ok. Let me see if i understand...
When i press a key to move pacman, i will also set a movement for the ghost?

I'm using a labyrinth from a text file, how can i start the ghost movement and pacman's?
Bellow is part of the code to start the pacman's motion (only to the right), and count the dot's. How can i also start the ghost's movement?  :red


start:
mov cx,478 ;numer of dot's (.) on the labyrinth
mov bx,0
add bx,650 ;pacman start possition on the labyrinth
mov ah,'<'
mov es:[bx],ah

ciclo:
        mov   ah,0bh           
        int   21h
        cmp   al,0ffh           
        je    seeBuffer         
        jmp   ciclo

seeBuffer:
        mov   ah,08h           
        int   21h           
        cmp   al,0             
        jne   keyboard     
        mov   ah,08h           
        int   21h

keyboard:
cmp al, 4dh
je mov_right

jne ciclo

cout_right:
dec cx
cmp cx,0
je win
jmp cont_right

mov_right:
mov al, es:[bx+2]
cmp al,46 ;compare position with the dot's (.)
je count_right

cont_right:
mov al, es:[bx+2]
cmp al, 35 ;labyrinth wall
je  ciclo

mov al,0Fh
mov ah,' '
mov es:[bx],ah
mov es:[bx+1],al
add bx,2
mov ah,'<'
mov es:[bx],ah
mov es:[bx+1],al

jmp ciclo


You also mentioned "BIOS data area located at segment address 40h, as 32-bit value at offset 6Ch", how can i use this for the ghost random movement?

Sorry about all these questions, but i'm really new to ASM and need to put the game working and understand it.  ::)

MichaelW

This is an example of what I was describing, except I decided to use the BIOS Return Clock Tick Count function (Interrupt 1Ah, function 0) to get the timer tick count because doing so is a little easier.

.model small
.stack
.data
    cntlo dw 0
    cnthi dw 0
.code
.startup

    ;===================================================
    ; Initialize labyrinth, pacman, and ghost, etc here.
    ;===================================================

    ;----------------------------------------------------------
    ; Initialize timer. Number of ticks determines ghost speed.
    ;----------------------------------------------------------

    mov ah, 0
    int 1ah           ; current tick count returned in CX:DX
    add dx, 8         ; add 8 ticks, ~440ms
    adc cx, 0         ; add in carry from low word
    mov cntlo, dx     ; save low word of terminal count
    mov cnthi, cx     ; save high word of terminal count

  mainLoop:

    mov ah, 0bh
    int 21h           ; check for key waiting
    cmp al, 0ffh      ; return will be FFh if key waiting
    je  @F            ; jump if key waiting
    jmp noKey
  @@:
    ;-----------------------------------------
    ; Exit if the user pressed the Escape key.
    ;-----------------------------------------

    mov ah, 8
    int 21h           ; get the key
    cmp al, 1bh       ; check for Escape
    jne @F
    jmp exit
  @@:

    ;=======================================
    ; Key is in AL. Update pacman, etc here.
    ;=======================================

  noKey:

    ;-------------
    ; Check timer.
    ;-------------

    mov ah, 0
    int 1ah           ; current tick count returned in CX:DX
    cmp al, 0         ; check midnight rollover flag
    jne timeOut       ; must restart timer if midnight passed
    cmp cx, cnthi     ; compare high word
    jne @F            ; no timeout
    cmp dx, cntlo     ; compare low word
    jnb timeOut
  @@:
    jmp mainLoop      ; continue looping

  timeOut:

    ;----------------------------------------------
    ; Timer has timed out, reset it for next cycle.
    ;----------------------------------------------

    add dx, 8         ; add 8 ticks, ~440ms
    adc cx, 0         ; add in carry from low word
    mov cntlo, dx     ; save low word of terminal count
    mov cnthi, cx     ; save high word of terminal count

    ;===================
    ; Update ghost here.
    ;===================

    mov dl, "+"       ; display pacifier at each timout
    mov ah, 2
    int 21h

  noTimeOut:

    jmp mainLoop      ; continue looping

  exit:

.exit
end
eschew obfuscation

IndioDoido

hi.

i don't now how i can use this code... :'(
the display pacifier for test only moves right in a certain amount of time, but how can i make the (.) move around in diferent directions?

what's supposed to happen when a key is pressed?

About the error, what's wrong about this:
"The main loop needs to get a key only once per loop. "

MichaelW

The pacifier was just something that I displayed so I could verify that the timer was working. I left all of the details of moving and displaying the ghost up to you, just as I left all of the pacman and labyrinth details up to you. For the ghost, I would select a starting position and display it before I entered the main loop. In the main loop, each time the timer timed out, and after the timer had been reset for the next cycle, I would calculate a new position for the ghost and move it to that position. So you would be moving pacman in response to an arrow key being pressed, and moving the ghost in response to the timer timing out.

I did not see the error in the code until after I had posted, and at the time I was not able to fix it. The problem was that any key read by the code at the bottom of the loop would be discarded. The only way the code at the top of the loop could receive an arrow key would be if the key were pressed after the code at the bottom of the loop had checked for and/or read a key, and before the code at the top of the loop checked for a key. I have now fixed the code. Take note that I used conditional jumps only for short distances, because for such jumps the target must lie within +127 and -128 bytes of the instruction following the jump. For the jumps were I didn't know how much code you might be adding, I used unconditional jumps that have a maximum distance of +32767 and -32768. The assembler will return an error if you exceed the maximum jump distance.

eschew obfuscation