News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests
NB: Posting URL's See here: Posted URL Change

Main Menu

Asteroids: Here we go

Started by sewardsb, November 21, 2013, 08:26:26 AM

Previous topic - Next topic

sewardsb

Using strictly MASM and Irvine32, I have been faced with a relatively tough challenge as a beginner in assembly. And that is the game Asteroids. I would like to open this up as a Q&A thread as a I move along through the process of creating the game! I will obviously provide my in-depth knowledge and logic behind each step that festers me, possibly resulting in a question here on this forum. I have played the game numerous times and have looked at higher-level code. Please keep in mind before leaving feedback that I will be using a 200x100 DOS console window and ONLY instructions/syntax from the Irvine32 book. I will label each question, as they come, on individual posts  :biggrin:

As for the actual code, I am hesitant whether or not I should be posting all of it (as students should be learning initially without these kind of resources), so I will post code solely based on the question.

And lastly, your feedback is greatly appreciated  :t

dedndave

if you have examined the Irvine32 library, you have undoubtedly noticed a lack of graphics-oriented functions
that's because console-mode graphics was more-or-less a 16-bit toy

so, unless these asteroids are to be made of text characters, you are fighting an uphill battle from the very beginning

sewardsb

I do understand that, and it is using only ASCII characters. I am almost done with all of the ship rotation and movement, but I do have a quick question in the meantime :bgrin:

Question 1: The ship has a "center" point of type COORD that is used to render all 8 directions (N,NE,E,SE..) of the ship based off of the user input (w,a,d,spacebar). I also have an array of type COORD that stores the currently rendered points (that I would like to use for collision detection later on). All of the current directions i have are working fine (tested the "center" point coords each move), but I have to push and pop the "center" COORD each time I add pts to the array, or else the center XY will become 0. Is it true that only one COORD struct can be used, unless you save the other one (like i did with push and pop)?

dedndave

inside a PROC, you can push EBX, ESI, EDI and use them to store across calls
MyFunc PROC USES EBX ESI EDI

;use EBX, ESI, EDI freely - API functions will not alter them

    ret

MyFunc ENDP

or
MyFunc PROC

    push    ebx
    push    esi
    push    edi

;use EBX, ESI, EDI freely - API functions will not alter them

    pop     edi
    pop     esi
    pop     ebx
    ret

MyFunc ENDP

EBP may be used the same way, if the PROC has no LOCAL's or parameters

sewardsb

Yes so I have learned about uses, but I can't find anywhere regarding my question on the COORD struct and whether or not only one local var can be initialized. It seems to be the case, considering any writes I do into the COORD array sets the other COORD "center" variable to 0,0.. But the good news so far is that I have successfully coded all of the rotations and movement, now onto the shooting.

dedndave

as i recall, COORD structures contain 2 WORD-sized members
but, without seeing the code, i can't really say what's going on

sewardsb

Code in regards to Question 1: Note that I am just posting the appropriate data and procedure. I know that the procedure doesn't have to store the vessel coordinates into 'vesselXY', but I think that storing them while rendering the ship would be more efficient for collision detection afterwards.

.data
ALIGN WORD ;aligns vesselXY to a word boundary to match the data type
vesselXY COORD 13 DUP(<?,?>)         ;struct array of X&Y points ([00,02],[04,06]..)
ALIGN WORD
vCenter COORD <100,50> ;center of ship, used to calculate rotations
vesselLen = ($-vesselXY)/4;         ;vessel length (each coordinate has an offset of 4)
vesselDir db '0'                 ;vessel direction: can be set to: 0=N, 1=NE, 2=E, SE, S, SW, W NW
count db 0 ;temp label loop count

leftX db 0         ;temp storage for current left-wing X coord
leftY db 0         ;temp storage for current left-wing Y coord
rightX db 0 ;temp storage for current right-wing X coord
rightY db 0 ;temp storage for current right-wing Y coord

.code
RenderVesselNorth proc uses esi ;draws/stores the vessel north coords into 'vesselXY' given center coord 'vCenter'
Call ClearRegisters
Call clrscr
mov al, '*'         ;set asterik for WriteChar

push vCenter.X ;since vCenter is somehow being set to 0, push now then pop at end to get back
push vCenter.Y

drawVessel_lbl:
CMP count, 0
je apex_lbl

CMP count, 8
jg removeExtraCoords_lbl

TEST count, 1
jnz leftWing_lbl ;if odd count, draw/store current left-wing coord
jz rightWing_lbl ;if even count, draw/store current right-wing coord

leftWing_lbl:
dec leftX ;x=x-1, y=y+1
inc leftY
mov dl, leftX ;move coords to dl,dh to move to current coord location
mov dh, leftY
Call StoreAndRenderCoordinates ;stores/displays the calculated coord [dl,dh]
jmp next_lbl

rightWing_lbl:
inc rightX ;+1 to rightX and leave leftY the same, as we use it below
mov dl, rightX ;store current X pt, Y pt already stored from leftWing_lbl
Call GoToXY
Call WriteChar
mov ecx, 0
movzx cx, dl
mov (COORD PTR vesselXY[esi]).X, cx
mov (COORD PTR vesselXY[esi]).Y, bx
jmp next_lbl

apex_lbl: ;store apex coord from vCenter
mov cx, vCenter.X ;store vCenter (has X,Y center coord)
mov bx, vCenter.Y

sub bx, 4 ;set Y pt -4 from vCenter coord
mov (COORD PTR vesselXY).X, cx ;now store cx,bx in vessel apex coord (first coord)
mov (COORD PTR vesselXY).Y, bx
mov dl, cl ;prepare apex coord into dl, dh
mov dh, bl
Call GoToXY
Call WriteChar ;display apex asterisk

mov leftX, dl ;prepare coords
mov rightX, dl
mov leftY, dh ;Y pt will be the same for both, so only use leftY

jmp next_lbl

removeExtraCoords_lbl: ;removes the extra coords only needed for diagnol ships
               mov (COORD PTR vesselXY[esi]).X, 0
               mov (COORD PTR vesselXY[esi]).Y, 0

next_lbl:
add esi, TYPE COORD ;go to next ship coords
inc count ;increment lbl count
CMP count, vesselLen ;if count < shipLen
jl drawVessel_lbl

pop vCenter.Y
pop vCenter.X

Call ShowCoordinatesOnRender ;show current vCenter coords when rendered vessel (for testing: vCenter is available because pop is above)

mov count, 0 ;reset count
mov vesselDir, 0 ;set direction = north(0)
ret
RenderVesselNorth endp

qWord

There is no need to use typecasts for the array vesselXY, because it is already correctly typed. Therefore you could write:
mov vesselXY[esi].X, ...
Also the TYPE-operator is not needed:
add esi, COORD
; someone may prefer this variant because of readability
add esi,SIZEOF COORD
MREAL macros - when you need floating point arithmetic while assembling!

dedndave

there are a few things i see   :P

but, to answer the main question....
the reason the center coordinate is being overwritten is....
vesselXY COORD 13 DUP(<?,?>)         ;struct array of X&Y points ([00,02],[04,06]..)
ALIGN WORD
vCenter COORD <100,50> ;center of ship, used to calculate rotations
vesselLen = ($-vesselXY)/4;         ;vessel length (each coordinate has an offset of 4)

you have included the vCenter member in the vesselLen calculation
also - not good to pop an ALIGN in there
even though it works, it has the potential of causing a bad calculation

sewardsb

#9
dedndave, thank you! Can't believe I didn't see that  :icon_eek:

And qword, thanks for the feedback! I will remove the unnecessary type casts.

Question 2: How can I store multiple large strings and ASCII characters? Working on the menu, I can't store rather large strings in the db type, and it's not allowing them to be stored in larger types (also tried EQU). I am able to do ASCII art with multiple db labels, so maybe I'll just have to do the same for paragraphs of text..

sewardsb

Question 3: Just completed implementation on shooting (w/ N max bullets), but calling clear screen every single time so that it only shows the current position of all bullets and then re-rendering the ship based on direction is causing 'blinking' of the screen. Is there a way to remove the char from the screen without having to call clear screen? If so, I can get the previous coord pt of that bullet and remove it from the screen.

Answer: (shouldn't have even asked this question  :icon_eek:)

RemovePreviousLaserBeam proc uses eax ebx
mov dh, al
mov dl, bl ;store previous pts to go to
Call GoToXY ;remove laser from previous spot
mov al, 0
Call WriteChar
ret
RemovePreviousLaserBeam endp

dedndave

you can print a space   :P
if there is "background" behind it, store it in a "z-buffer", then show the bullet
when you want to remove the bullet, get the background from the z-buffer and display that

in this one, i position the cursor, display a space, and position the cursor again
you can modify that idea however you like
http://masm32.com/board/index.php?topic=2609.msg27752#msg27752

sewardsb

Question 4: I just found out that I cannot use conditional IF/WHILE statements in my project. I converted most of my code to use CMP and JMP instructions successfully, but there is one procedure that I cannot get identical results with. It's either a performance difference, or the conversion from nested IF statements to JMP was inaccurate. I have been beating myself up over this for a while now.

The procedure is called RenderLaserBeams. It goes through all the laser-beam elements inside the array of type LASER and increments them based on their direction that was initially set when fired. It also includes logic to 'erase' (sets it to 0,0) the laser-beam once it gets to a wall-boundary, and detects whether it was the current shot fired or an old shot (if current shot fired, we start that laser-beam's coordinates farther out than the older ones, to start at the ship apex).

Could someone take a look at the procedures, and let me know if there's any differences in code logic from the IF one to the JMP one? I've attached both procedures. The IF procedure has the desired results  :icon_confused:

jj2007

Quote from: sewardsb on November 27, 2013, 03:40:40 AM
Question 4: I just found out that I cannot use conditional IF/WHILE statements in my project.

Why that? It's 32-bit code, and everybody (well, with the exception of Dave :P) uses
.if eax
  nop
.elseif ecx
  nop
.endif
.While ecx
  .Break .if !Carry?
.Endw
.Repeat
  dec ecx
.Until Sign?

etc etc ...

In addition, you have an incredibly versatile Switch macro that can make C programmers really envious:

include \masm32\include\masm32rt.inc
.code
start:
  mov eax, 17      ; try any combination you like,..
  mov edx, 77
  mov ecx, 16      ; put 17 to see another case
  mov ebx, 17

  Switch eax
  Case 1
     print "eax is one", 13, 10
  Case ecx .. edx
     print "eax is between edx and ecx", 13, 10
  Case 10 .. 15
     print "eax is between 10 and 15", 13, 10
  Case ebx
     print "eax is the same as ebx", 13, 10
  Case 16, 18, 20
     print "eax is sixteen or eighteen or twenty", 13, 10
  Default
     print str$(eax), " is nowhere in the ranges above", 13, 10
  Endsw

  exit
end start

dedndave

the easy way......

assemble the .IF version
disassemble the resulting EXE using \masm32\bin\dumpbin.exe