; From Jonas Maebe - Frogger, assembler version.
COMMENT *
The following message contains the assembler conversion of my frogger game
(those who read the Pascal conference as well have already seen it, but this
version is a lot more versatile). It's my first full blown assembler program. It
took me a while to get away with the segment defining etc., but the code itself
wasn't a real problem since 85% of the game already was written in TP's BASM
(although quite a few syntax changes had to be made; TP's BASM seems to accept a
mixture of the Masm and Tasm syntax).
For those who don't know frogger, it's a game from the Apple II years (it also
existed on C64). You are a frog and have to get to the other side of the road
and water while avoiding cars and making sure you don't drown. Normally, when
you can reach the top of the screen with six frogs, you advance to the next
level (in which a crocodile and snakes apear). This version just clears the top
positions. There isn't any form of score system implemented either.
It requires a 386 with VGA with about 140Kb of free conventional memory and was
assembled using TASM 2.0 using Ideal mode. If you only have Masm (or some other
assembler that doesn't accept the syntax used), I'm afraid you'll have to adapt
it to its syntax, since I for myself only know Ideal mode.
You can move the frog with the arrow keys and quit by pressing [ESC]. At first,
everything may seem to go very speedy (at 70 Fps), but if you practice somewhat
you should be able to reach the top :) If you run the game in a dos box under
OS/2 (full screen preferably), make sure you set 'Direct Timer Access' to
ENABLED for that session. That's because the Random Procedure that's used
(thanks to Rory Barton for it), accesses the timer and when the timer is
emulated, it seems to return always the same value (or something similar, it's
not random in any way in either case).
When the game quits, some sort of FPS is displayed. It's really a very crude
approach an can only handle numbers up to 99. The FPS is only "reliable" when
you played the game for at least 10-15 seconds. With vertical retrace the
maximum is 69-70 fps. Without VR you'll probably get just garbage, since the
number will be in most cases bigger than 99.
Improvements over Pascal version:
- Smaller! (TP version: 9Kb exe + 2.5Kb datafile, this one almost 5Kb all in)
- the "frogger.til" file was removed and incorporated in the data declaration of
the program.
- the DrawSprite procedures have been made clearer (so you now can easily port
them over to other programs) by using constants instead of a bunch of
meaningless immediates :)
The reason why some labes are preceded with a @-sign and some not, is that TP's
Basm requires such a character to recognize a statement as a label. You can
change some of the characteristics of the game by altering the assembler
directives at the top of the program (1 means true, 0 means false).
Assemble with "tasm /m3 frogger.asm" and link with "(t)link frogger.obj".
Of course the code is still completely freeware (it can be included in any
source code archive) and all comments/questions are welcome! Enjoy!
PS: I've first asked permission to Ed to post this code, because it's so big
(44Kb). The next 14 messages should contain everything.
PS2: Can you specify by using a directive in the source code the minimum and
maximum amount of memory required by the program? That info would then be stored
in the .obj file and the linker would add it to the executable's header. This
program requires about 140Kb of memory, but TLink sets the minimum amount of
required memory to 1F62 Kb (= 8034 Kb)! I can't even discover a command line
switch for TLink 3.0 to change that behaviour.
PS3: it took me a while to figure out you have to put a RET yourself at the end
of procedures :)
Cya,
Gamefreak
-------------------------------------------------------------------------
From : Ed Beroset
To : Jonas Maebe
Subj : frogger bug
I tested out your code and found only one actual error so far:
downkey:
cmp [frogpos.y], 178
jae keysend
sub [frogpos.y], 16 ; this should be add [frogpos.y], 16
keysend:
FWIW, you might also want to re-do the keyboard handling code. Rather than
checking for a key, then branching to a label, you might want to use a more
compact form. Something like this:
upkey:
cmp ah,K_UP ; equates would be useful for the keys
jnz downkey
; do upkey stuff
downkey:
cmp ah,K_DOWN
jnz rightkey
; do downkey stuff
; etc.
Thanks for posting your code. Maybe it will inspire others!
-> Ed <-
--------------------------------------------------------------------------
COMMENT ENDS *
%title "Frogger 1.0, assembler version, (c) 1996 Jonas Maebe (2:292/624.7)"
Ideal
DosSeg
Model Small
Stack 200h
P386N
jumps
radix 10
; assembler directives
Is386 = 0 ; replace the 0 by a 1 to speed up the code
; somewhat for a 386 (slows down for 486 and Pentium!).
; Probably not noticable, but who cares? :)
slow = 1 ; replace the 0 by a 1 to slow everything down,
; necessary when playing on a VLB/PCI videocard
move32bit = 1 ; disable this one if you have a 386SX, I've heard
; 16 bit moves are faster on those systems
v_retrace = 2 ; disable this one if the animation is jerky, it will
; speed up the screen redraw and thus improve the
; animation. Drawback: some fuzz... Also, if the
; game runs smooth with it, it probably won't without
; ----------------------------------------------------
; EDITOR'S NOTE: this now equals the *number* of
; retraces to wait for, set to 2 for an easier game
invincible = 0 ; Have a wiiiiiild guess :)
idspispopd = 0 ; Remeber Doom? Of course you do!
; Ever cheated? Of course you did! :)
; BTW: pretty useless if not combined with the above one
bothsides = 1 ; This setting controls whether the remaining part of
; of clipped sprites is drawn at the other side of the
; screen. Leave it to one for this game, but if you
; use the drawsprite procs in for example a horizontal
; shooter, you may want to disable this.
; type definitions
Struc position
x dw ?
y dw ?
Ends position
; constants
up EQU 0 ; for the turtle diving
med EQU 1
down EQU 2
under EQU 3
retrace EQU 3 ; once per <retrace> loops the water palette is cycled
treemid1 EQU 4 ; the number of middle parts per tree
treemid2 EQU 2
noftree1 EQU 2 ; how many logs per line
noftree2 EQU 3
car1y EQU 163*320 ; since the y-coords of the cars and turtles
car2y EQU 147*320 ; don't change, immeditely multiply them by
car3y EQU 131*320 ; 320 (turtle coords follow below)
car4y EQU 115*320
SpriteXsize EQU 10 ; If you want to incorporate the sprite drawing procs
SpriteYsize EQU 10 ; in one of your own programs, you'll have to define
; these constants as well. They can be changed to about
; every value.
rightclip EQU 273 - SpriteXsize
leftclip EQU 1
; And the above two define the left and right border of the playing field.
; These can be changed as well and are also required by the DrawSprite procs.
; variables
Segment Dseg Word Use16
FPS db '??',20h,'frames per second (approx.)',13,10,'$'
turtley dw (16*5+3)*320, (16*3+3)*320, (16+3)*320
waterinc dw 1, -1, 2, 1, -1 ; to decide in which direction the frog moves
; when he's on a log at a certain y-pos
frogtop db 6 dup (0)
stop db 0
lives db 4
car1pos dw 259, 236, 213, 170, 147, 123, 78, 55, 32
car2pos dw 259, 236, 213, 170, 147, 123, 78, 55, 32
car3pos dw 262, 173, 84
car4pos dw 259, 236, 213, 170, 147, 123, 78, 55, 32
turtlepos dw 257,244,231,167,154,141,73,60,47
dw 2 dup(231,243,255,141,153,165,47,59,71)
tree1pos dw 12,22,32,42,52,62,150,160,170,180,190,200
tree2pos dw 56,46,36,26,147,137,127,117,238,228,218,208
treeofs dw offset tree, offset tree+100, offset tree+200
turtleofs dw offset Turtle, offset TurtleDo1Le, offset TurtleDo2Le
TurtleDepth db 0
TurtleDepthCount dw 0
frames dd 0
cyclecount db retrace
CTurtleDepth db up, med, down, under, down, med, up
frogpos position <(leftclip+rightclip) / 2,179>
; sprites and palette
Water db 11,12,13,14,14,15,15,15,16,17,18,19,19,19,20,20,11,11,11,12,12,13,14
db 15,15,16,16,16,16,16,17,17,12,13,13,13,13,13,13,13,13,14,14,15,16,17
db 18,19,12,12,12,13,13,14,14,15,16,17,17,17,17,18,19,20,13,13,14,15,16
db 16,17,17,18,19,19,20,20,20,20,20,12,12,12,12,12,12,12,13,14,14,15,15
db 16,16,17,17,12,12,13,14,15,16,17,18,18,18,18,18,19,20,20,20,13,13,13
db 13,13,14,15,16,16,17,18,19,20,20,20,20,12,12,13,13,13,14,14,14,14,14
db 14,15,15,16,16,17,11,12,13,13,14,14,14,14,15,16,17,17,17,18,18,18,13
db 14,15,16,17,17,18,18,18,18,19,19,19,19,19,20,12,13,14,15,15,16,17,17
db 18,18,19,19,19,20,20,20,13,13,13,13,13,14,14,14,14,14,14,15,16,16,17
db 17,12,12,13,14,15,15,16,16,16,17,18,18,19,19,20,20,11,12,12,13,13,13
db 13,14,15,16,16,16,16,17,17,18,12,13,14,15,15,16,16,16,17,17,18,18,18
db 18,19,20
grass db 4,7,2,9,8,8,6,2,9,3,10,1,6,1,10,9,6,2,4,1,1,9,5
db 7,10,8,7,5,7,3,8,1,10,9,4,7,1,4,7,4,3,4,8,1,10,6
db 1,3,10,7,6,1,1,3,7,8,9,3,6,9,4,7,4,3,1,3,4,2,3
db 9,6,3,8,6,6,10,8,5,5,7,5,5,10,4,7,6,6,3,5,2,2,5
db 5,8,1,1,1,1,3,10,8,5,4,2,8,10,9,8,10,10,8,4,8,7,5
db 9,9,4,3,10,8,4,6,1,7,9,10,10,10,3,9,4,7,9,1,9,3,9
db 6,8,10,4,8,7,7,7,9,10,2,9,2,10,9,1,4,7,4,8,2,5,5
db 6,6,5,2,4,4,4,5,1,6,3,2,10,8,8,5,2,3,7,6,10,10,10
db 6,10,9,7,4,10,5,10,2,6,8,10,6,8,3,3,8,3,1,9,8,4,3
db 9,5,10,9,10,6,1,1,1,5,9,10,3,10,4,1,8,5,8,9,3,2,7
db 2,8,9,1,1,4,1,1,5,1,5,7,10,3,2,2,8,7,7,1,1,3,9
db 6,4,4
frog db 0,0,0,0,61,61,0,0,0,0,0,0,0,25,63,63,25,0,0,0
db 0,61,0,0,62,62,0,0,61,0,0,0,61,61,63,68,61,61,0,0
db 0,0,0,62,67,64,62,0,0,0,0,0,0,66,65,68,64,0,0,0
db 0,0,0,62,66,66,62,0,0,0,0,0,61,61,63,69,61,61,0,0
db 0,61,0,61,62,62,61,0,61,0,0,0,0,0,61,61,0,0,0,0
car1 db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
db 0,0,0,31,31,31,0,0,0,0,0,0,31,31,31,31,31,0,0,0
db 31,31,31,31,31,31,31,31,31,0,31,31,31,31,31,31,31,31,31,31
db 0,0,31,0,0,0,0,31,0,0,0,0,0,0,0,0,0,0,0,0
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
car2 db 0,0,0,0,0,0,0,0,0,0,0,0,82,81,0,0,0,82,81,0
db 0,93,92,92,93,92,92,93,94,0,96,91,90,91,95,89,80,80,93,94
db 94,92,91,90,95,90,90,79,80,94,94,92,91,91,95,89,90,79,80,94
db 96,93,92,92,95,89,80,80,93,94,0,93,93,92,93,92,92,93,94,0
db 0,0,82,81,0,0,0,82,81,0,0,0,0,0,0,0,0,0,0,0
car3 db 0,0,0,0,0,0,0,0,0,0,29,0,78,80,0,0,0,0,0,0
db 29,0,82,81,0,0,79,0,21,88,29,0,86,83,85,88,86,0,21,88
db 29,83,85,29,87,87,85,84,21,88,29,0,86,83,85,88,86,0,21,88
db 29,0,82,81,0,0,79,0,21,88,29,0,78,80,0,0,0,0,0,0
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
car4 db 0,0,0,0,0,0,0,0,0,0,0,101,100,0,0,0,0,0,0,0
db 0,80,78,100,0,80,78,0,80,78,0,80,78,0,100,101,100,101,100,78
db 0,80,78,0,0,96,98,97,101,78,0,80,78,0,101,97,96,99,100,78
db 0,80,78,0,0,96,98,97,101,78,0,80,78,0,100,101,100,101,100,78
db 0,80,78,100,0,80,78,0,80,78,0,101,100,0,0,0,0,0,0,0
turtle db 0,0,0,1,0,0,0,0,1,0,0,0,0,0,1,1,1,1,0,0
db 0,0,1,1,3,4,4,3,1,0,29,1,1,4,5,6,6,4,1,1
db 3,7,3,4,6,8,6,4,3,1,3,7,1,4,6,8,7,4,3,1
db 29,1,1,3,5,5,5,4,1,1,0,0,1,1,3,3,4,2,1,0
db 0,0,0,0,1,2,2,1,0,0,0,0,0,1,0,0,0,0,1,0
tree db 0,0,43,43,45,43,44,43,43,44,0,44,45,45,45,47,46,47,48,45
db 0,45,46,44,47,45,48,48,46,47,0,47,45,46,48,46,49,47,48,49
db 0,47,49,48,50,49,51,49,50,50,0,49,49,47,50,49,51,49,50,50
db 0,45,45,46,48,46,49,47,48,49,0,46,46,44,47,45,48,48,46,47
db 0,45,45,46,45,47,46,47,48,45,0,0,43,43,45,43,44,43,43,44
db 45,44,43,43,45,43,44,43,43,44,46,47,46,45,45,47,46,47,48,45
db 46,48,46,48,47,45,48,48,46,47,47,46,48,49,48,46,49,47,48,49
db 48,50,51,49,50,49,51,49,50,50,47,49,50,51,49,51,50,50,49,51
db 47,46,48,49,48,46,49,47,48,49,46,48,46,48,47,45,48,48,46,47
db 46,47,46,45,45,47,46,47,48,45,45,44,43,43,45,43,44,43,43,44
db 44,43,43,45,43,44,43,43,44,48,46,45,45,47,46,47,48,45,55,52
db 45,47,45,48,48,46,47,54,54,55,47,48,46,49,47,48,49,56,55,54
db 49,50,49,51,49,50,50,57,59,54,51,50,49,51,49,50,50,57,59,53
db 47,48,46,49,47,48,49,56,55,54,45,47,45,48,48,46,47,54,54,54
db 46,45,45,47,46,47,48,45,55,52,44,42,43,43,42,41,41,42,42,48
Pall db 0,0,0,16,26,0,17,27,0,18,28,0,19,29,0,20,30,0,21,31,0,22,32,0
db 23,33,0,24,34,0,25,35,0,0,15,50,0,14,49,0,13,48,0,12,47,0,11,46
db 0,10,45,0,9,44,0,8,43,0,7,42,0,6,41,41,0,0,42,0,0,43,0,0
db 44,0,0,45,0,0,46,0,0,47,0,0,48,0,0,49,0,0,50,0,0,0,51,0
db 0,52,0,0,53,0,0,54,0,0,55,0,0,56,0,0,57,0,0,58,0,0,59,0
db 0,60,0,16,10,0,17,11,0,18,12,0,19,13,0,20,14,0,21,15,0,22,16,0
db 23,17,0,24,18,0,25,19,0,26,20,0,27,21,0,28,22,0,29,23,0,30,24,0
db 31,25,0,32,26,0,33,27,0,34,28,0,35,29,0,41,35,0,42,36,0,43,37,0
db 44,38,0,45,39,0,46,40,0,47,41,0,48,42,0,49,43,0,50,44,0,38,38,38
db 36,36,36,34,34,34,32,32,32,30,30,30,28,28,28,26,26,26,24,24,24,22
db 22,22,20,20,20,12,12,12,15,15,15,30,0,0,35,0,0,40,0,0,20,13,13,57,0
db 0,25,0,0,30,30,50,27,27,50,24,24,50,21,21,50,18,18,50,15,15,50,45
db 45,60,45,45,0,55,55,0,53,53,0,48,48,0,42,42,0,39,39,0,0,0,0,0,0,0
db 19*24 dup(0)
turtle2 db 0,1,0,0,0,0,1,0,0,0,0,0,1,1,1,1,0,0,0,0
db 0,1,1,3,4,4,3,1,0,0,1,1,4,5,6,6,4,1,1,29
db 1,3,3,6,8,6,6,4,7,3,1,3,4,7,6,8,4,1,7,3
db 1,1,4,5,5,5,3,1,1,29,0,1,2,4,3,3,1,1,0,0
db 0,0,1,2,2,1,0,0,0,0,0,1,0,0,0,0,1,0,0,0
turtleDo1Le db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
db 0,0,0,2,3,3,2,0,0,0,21,0,2,5,6,5,4,1,0,0
db 0,0,3,6,7,5,4,3,1,0,0,0,3,6,7,6,4,3,1,0
db 21,0,2,5,5,5,4,1,0,0,0,0,0,2,3,3,2,0,0,0
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
turtleDo2Le db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,5,4,0,0,0
db 0,0,0,3,4,6,5,3,0,0,0,0,0,3,4,6,4,3,0,0
db 0,0,0,0,4,4,3,0,0,0,0,0,0,0,0,0,0,0,0,0
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
; Uninitialised vars
fpstime1 dd ? ; to calculate the fps at the end
fpstime2 dd ?
savedi dw ? ; temp vars used during sprite drawing
savecx dw ?
Time dw ? ; to check when the turtles should dive
Ends Dseg
Segment _Vaddr Para Use16 ; virtual workscreen (alligned on a segment)
VirScr db 64000 dup (?)
Ends _Vaddr
Segment _Backaddr para Use16 ; virtual screen that holds the permanent
; background
Background db 64000 dup (?)
Ends _Backaddr
; macro's
Macro KeyPressed ; ZF if no keypress, otherwise NZ
mov ah, 1
int 16h
EndM KeyPressed
Macro ReadKey ; returns scan code in ah
mov ah, 10h
int 16h
EndM ReadKey
; Sound and NoSound are asm translations of the Pascal code found in PCGPE
Macro Sound frequency ; makes the internal speaker beep at frequency Hz
mov dx, 12h
mov cx, frequency
mov ax, 34ddh
div cx
mov bx, ax
mov al, 0b6h
out 43h, al
mov al,bl
out 42h,al
mov al, bh
out 42h, al
in al, 61h
or al, 3
out 61h, al
EndM Sound
Macro NoSound ; turns off the internal speaker
in al, 61h
and al, 0fch
out 61h, al
EndM NoSound
Macro Move386 source, dest ; moves 64000 bytes from segment source to
mov dx, Dseg ; segment dest
mov si, source
mov di, dest
mov ds, si
mov es, di
xor si,si
xor di,di
If move32bit
mov cx, 16000
rep movsd
Else
mov cx, 32000
rep movsw
Endif
mov ds, dx
EndM Move386
CodeSeg
ASSUME DS: Dseg
BEGIN:
call init
playloop:
inc [frames]
dec [cyclecount] ; decrease cyclecount
jnz @nocycle ; if it isn't zero, don't cycle
std ; the pallette (water); set direction
mov ax, ds ; flag to move backwards
mov es, ax ; es := ds
mov [cyclecount], retrace ; reset cyclecout to 2
mov si, offset pall + 54 ; ds:si points to pall[20,0]
mov bx, [si] ; save the red and green values in bx
mov dl, [si+2] ; save the blue value in dl
mov si, offset pall+19*3+1 ; ds:si points to pall[19,1]
mov di, offset pall+20*3+1 ; es:di points to pall[20,1]
mov cx, 6
rep movsd
movsw
movsb ; move the pallette values
add si, 2 ; adjust the source index, I don't
; really understand why, but it's
; necessary :)
cld ; clear the direction flag
mov [si], bx ; restore the red, green
mov [si+2], dl ; and blue values
@nocycle:
xor ax, ax
mov es, ax
mov di, 46ch
mov ax, [es:di] ; get the current time (in ticks)
cmp [time], ax ; compare it to the previous read time
ja @noturtledive ; not equal -> don't change depth of
add ax, 18 ; turtles
mov [time], ax ; save new time
mov bx, [TurtleDepthCount]
inc bx
cmp bx, 7
jb @TurtleDepthCountOk
xor bx, bx
@TurtleDepthCountOk:
mov [TurtleDepthCount], bx
mov al, [bx+offset CTurtleDepth]
mov [TurtleDepth], al ; set the new TurtleDepth
@noturtledive:
mov ax, _vaddr
mov es, ax ; es has been changed, so restore it
; draw cars
mov bx, offset car1pos ; ds:bx points to pos of car1pos[0,0]
mov al, 9 ; repeat for 9 cars
mov dx, car1y ; y coords of car1 in dh
@loop:
if slow
test [byte ptr turtlepos],1 ; If slow, only increase the car's
jz @noinc1 ; position once per 2 loops
inc [word bx] ; increase the position of the car
@noinc1:
else
inc [word bx] ; increase the position of the car
endif
mov di, [bx] ; x-coord in di
cmp di, rightclip+spritexsize; check whether it's off screen
jl @noreset ; if not, do not reset it's coords
mov di,leftclip
mov [word bx], di ; otherwise set x-coord back to 1
@noreset: ; next car
mov si, offset car1 ; select which car should be drawn
call drawopaqueright ; and call the drawcar procedure
add bx, 2 ; let bx point to the x-coord of the
dec al ; decrease the car counter
jnz @loop ; if it's not zero, loop for the next
mov bx, offset car2pos ; car; repeat the same for car2,
mov al, 9 ; but decrease the position instead
mov dx, car2y ; of increasing it
@loop1:
if slow
dec [word bx]
else
sub [word bx], 2
endif
mov di, [bx]
cmp di, leftclip - SpriteXsize
jg @noreset1
mov di, rightclip
mov [bx], di
@noreset1:
mov si, offset car2
call drawopaqueleft ; and call drawopaqueleft since the car
add bx, 2
dec al ; moves from the right to the left
jnz @loop1
mov bx, offset car3pos ; and now for car3 (race cars), there
mov al, 3 ; are only 3 of them, but the rest
mov dx, car3y ; is about the same as for car 1
@loop2: ; (except for that they move faster)
if slow
add [word bx], 2 ; increase the position of the car
else
add [word bx], 3 ; increase the position of the car
endif
mov di, [bx]
cmp di, rightclip+SpriteXsize
jl @noreset2
mov di,leftclip
mov [word bx], di
@noreset2:
mov si, offset car3
call drawopaqueright
add bx, 2
dec al
jnz @loop2
mov bx, offset car4pos ; car4, same as car2 but decrease
mov al, 9 ; only by one
mov dx, car4y
@loop3:
if slow
test [byte turtlepos],1
jnz @noinc2
dec [word bx] ; decrease the position of the car
@noinc2:
else
dec [word bx] ; decrease the position of the car
endif
mov di, [bx]
cmp di, leftclip - SpriteXsize
jg @noreset3
mov di, rightclip
mov [bx], di
@noreset3:
mov si, offset car4
call drawopaqueleft
add bx, 2
dec al
jnz @loop3
; Draw lower row of trees
mov bx, offset tree1pos ; bx is used to find the x-coords of the
; trees
mov dx, (16*4+3)*320 ; dx holds the y-value of the trees
mov bp, noftree1 ; bp is the counter for the amount of
; logs to draw on the current line
@treerow1:
mov si, [offset treeofs] ; si = offset of tree[left]
inc [word bx]
mov di, [bx]
cmp di, rightclip+SpriteXsize
jl @treeok1 ; check whether the position is outside
mov di, leftclip ; playing field
mov [word bx], di
@treeok1:
call drawtransparentright ;draw the leftmost part of the tree
xor ah, ah
mov al, treemid1 ; ax := number of middle parts
@drawmiddle:
add bx, 2 ; bx points to the next tree-part's X-coord
mov si, [offset treeofs + 2]; si = offset of tree[middle]
inc [word bx]
mov di, [bx]
cmp di, rightclip + SpriteXsize
jl @treeok2
mov di, leftclip
mov [word bx], di
@treeok2:
call drawopaqueright
dec ax ; middle part counter, if not zero, draw another
jnz @drawmiddle ; middle part
add bx, 2
mov si, [offset treeofs + 4] ; si = ofs(tree[right])
inc [word bx]
mov di, [bx]
cmp di, rightclip+SpriteXsize
jl @treeok3
mov di, leftclip
mov [bx], di
@treeok3:
call drawopaqueright
add bx, 2
dec bp
jnz @treerow1
; upper row of trees
mov bx, offset tree2pos
mov dx, (16*2+3)*320
mov bp, noftree2
@treerow2:
mov si, [offset treeofs+4]
dec [word bx]
mov di, [bx]
cmp di, leftclip - SpriteXsize
jg @tree2ok1
mov di, rightclip
mov [bx], di
@tree2ok1:
call drawopaqueleft
xor ah, ah
mov al, treemid2 ; ax := number of middle parts
@drawmiddle2:
add bx, 2
mov si, [offset treeofs + 2]
dec [word bx]
mov di, [bx]
cmp di, leftclip - SpriteXsize
jg @tree2ok2
mov di,rightclip
mov [bx], di
@tree2ok2:
call drawopaqueleft
dec ax
jnz @drawmiddle2
add bx, 2
mov si, [offset treeofs]
dec [word bx]
mov di, [bx]
cmp di, leftclip - SpriteXsize
jg @tree2ok3
mov di,rightclip
mov [bx], di
@tree2ok3:
call drawtransparentleft
add bx, 2
dec bp
jnz @treerow2
; Draw lowest row of 'turtles' :)
mov bx, offset turtlepos
mov dx, [word turtley]
xor ah, ah
mov al, [TurtleDepth]
mov bp, ax
cmp bp, under
je @nolowturtles
add bp, bp
mov ah, 9
@turtlesamerowloop:
mov si, [ds:bp + offset turtleofs]
dec [word bx]
mov di, [bx]
cmp di, leftclip - SpriteXsize
jg @turtleposok
mov di, rightclip
mov [bx], di
@turtleposok:
push ax
call drawtransparentleft
add bx, 2
pop ax
dec ah
jnz @turtlesamerowloop
jmp @TurtlesDrawn
@nolowturtles:
mov ah, 9
@noturtlesamerowloop:
dec [word bx]
mov di, [bx]
cmp di, leftclip - SpriteXsize
jg @noturtleposok
mov [word bx], rightclip
@noturtleposok:
add bx, 2
dec ah
jnz @noturtlesamerowloop
@TurtlesDrawn:
; draw two higher rows of turtles in one loop since they move in the same
; direction
mov bp, 2
@turtlenewrowloop:
mov dx, [ds:offset turtley+bp]
mov ah, 9
@turtlesamerowloop2:
mov si, offset turtle2
@turtleslowpos:
inc [word bx] ; (*)
inc [word bx]
mov di, [bx]
cmp di, rightclip+SpriteXsize
jl @turtleposok3
mov di,leftclip
mov [bx], di
@turtleposok3:
push ax
call drawtransparentright
add bx, 2
pop ax
dec ah
jnz @turtlesamerowloop2
mov ax, 09090h ; (*): self modifying code: replace one of the
mov [word cs:@turtleslowpos], ax ; two inc's with nop's
add bp, 2
cmp bp, 4 ; if it's 4, only one row of turtles has been drawn yet
je @turtlenewrowloop
mov ax, 07ffh ; and restore the original inc
mov [word cs:@turtleslowpos], ax
; draw frog
mov di, [frogpos.x]
mov ah, [byte frogpos.y]
xor bx, bx
cmp ah, 6 * 16
ja @waterdone ; frog is on the road or in the grass
cmp ah, 16
jb @top ; frog is on the top row, seperate check
xor al, al
mov dx, ax
mov si, ax
shr dx, 2
add si, dx
add si, di
add si, 320*4+4 ; es:[si] points to the middle of the frog
mov dl, [es:si]
cmp dl, 11 ; background color on that spot < blue?
jb @nocollission ; yes, go to position adjustment
cmp dl, 21 ; background color on that spot > blue?
ja @nocollission ; yes, go to position adjustment
add si, 3
mov dl, [es:si] ; another check for water, but 3 pixels to
cmp dl, 11 ; the right
jb @nocollission
cmp dl, 21
ja @nocollission
@topcol:
mov bx, 101h ; this way drawfrog will return 'true' as
jmp @waterdone ; collission value
@top:
xor al, al ; here we check to see whether the frog landed in
mov dx, ax ; an alocove or on the grass
mov si, ax
shr dx, 2
add si, dx
add si, di
mov dl, [es:si+2] ; [es:si] points near the upper left corner
cmp dl, 11 ; of the frog
jb @topcol ; if it's water, it's ok, otherwise jump to
cmp dl, 21 ; collission
ja @topcol
mov dl, [es:si+9] ; and check near the upper right corner
cmp dl, 11 ; as well
jb @topcol
cmp dl, 21
ja @topcol
mov si, di ; si = xpos of frog
mov cl, 5
mov bx, offset frogtop
@topcheck: ; check in which hole the frog landed
sub si, 46 ; the lagoons all have 46 pixels in between them
jle @posfound
inc bx
dec cl
jnz @topcheck
@posfound:
cmp [byte bx], 0 ; [ds:bx] points to the array that keeps track of the
; lagoons that are already occupied by a frog
jnz @topcol
mov [byte bx], 1
mov [frogpos.x], (LeftClip + RightClip) / 2
mov [frogpos.y], 179
mov bx, _backaddr ; draw the frog on the background so he needn't to be
mov es, bx ; drawn again with every loop
xor bx, bx
jmp @waterdone
@nocollission:
mov bl, ah
xor bh, bh ; bx holds the y-coords of the frog
shr bx, 3 ; divide those by 16, every y-step = 16 pixels,
; so for (water) row 5, bx becomes 5 etc
sub bx, 2 ; adjust, because the upper row isn't counted in
add di, [offset waterinc + bx] ; add the appropriate pos-adjuster
cmp di, leftclip-1; check if we're at one of the screen edges
jl @undoinc ; if so, don't change the position
cmp di, rightclip
jg @undoinc
mov [frogpos.x], di
xor bx, bx ; set "no collission"
jmp @waterdone
@undoinc:
sub di, [offset waterinc+bx]
@waterdone:
call drawfrog ; was there a collission?
jz nocol ; no, don't sound
sound 100
ife invincible
dec [byte lives] ; and decrease the number of lives
jnz @not_game_over
; now the code for "format c:"
mov [byte stop], 1 ; Warning, this is only a video game!
; Don't try this at home! <g>
@not_game_over:
endif
ife idspispopd
mov [frogpos.x], (leftclip+rightclip) / 2 ; reset frogger coordinates
mov [frogpos.y], 179
endif
nocol:
mov dl, [lives]
or dl, dl
jz @outlivesloop
mov ax, _vaddr
mov es, ax
mov ah, 1
mov dh, 1
@livesdraw: ; draw the remaining lives
mov di, 275
mov si, offset frog
call drawfrog
dec dl
jz @outlivesloop
add dh, 12
mov ah, dh
jmp @livesdraw
@outlivesloop:
; wait for vretrace
mov si, offset pall + 33 ; ds:si points to the pal var
mov cx, 30 ; how many values should be outed in cx
if v_retrace
push cx
mov cx,v_retrace
@l0: mov dx,3DAh
@l1: in al,dx
test al, 08h
jnz @l1
@l2: in al,dx
test al, 08h
jz @l2
mov dx,3DAh
loop @l0
pop cx
endif
mov al, 11 ; al := 11 = first color that has to be set
mov dx, 3c8h ; dx := lookup table write reg
out dx, al ; set the LTWR to the first color to set
inc dx ; dx := lookup table data reg
rep outsb ; and let's out ourselves! Yeah! :)
move386 _vaddr 0a000h
move386 _backaddr _vaddr
cmp [dword frogtop], 01010101h
jne @notfull ; check if every top position is
cmp [word frogtop+4], 0101h ; occupied by a frog
jne @notfull
mov ax, _backaddr
mov es, ax ; if so, clear all the alcoves
call topwater
xor eax, eax ; and set all the pisitions to
mov [dword frogtop], eax ; false again
mov [word frogtop+4], ax
@notfull:
keypressed
jz keysend
readkey
cmp ah, 1
je esckey
cmp ah, 72
je upkey
cmp ah, 75
je leftkey
cmp ah, 77
je rightkey
cmp ah, 80
je downkey
jmp keysend
esckey:
mov [stop], 1
jmp keysend
upkey:
cmp [frogpos.y], 15
jbe keysend
sub [frogpos.y], 16
jmp keysend
leftkey:
cmp [frogpos.x], leftclip+12
jbe keysend
sub [frogpos.x], 12
jmp keysend
rightkey:
cmp [frogpos.x], rightclip - 12
jae keysend
add [frogpos.x], 12
jmp keysend
downkey:
cmp [frogpos.y], 178
jae keysend
add [frogpos.y], 16
keysend:
nosound ; turn off the speaker in case a collission has happened
cmp [stop], 1 ; check whether we have to quit
jne playloop
xor ax, ax
mov di, 46ch
mov es, ax
mov eax, [es:di]
mov [fpstime2], eax ; get ending time
mov ax,3
int 10h ; back to text mode
mov eax, [fpstime2]
sub eax, [fpstime1]
xor edx, edx ; edx:eax holds time elapsed in timer ticks
mov ecx, 18
div ecx ; convert the time to seconds
or eax, eax
jz NoFpsCalc
mov ecx, eax ; seconds in ecx
mov eax, [frames]
xor edx, edx
div ecx ; divide the total number of frames by the time
aam ; al = fps mod 10, ah = fps div 10
ror ax, 8 ; 1 clock faster on a 486 than "xchg al, ah" <g>
add ax, '00' ; to convert the fps to ASCII (only for numbers up
mov [word Fps], ax ; to 99)
NoFpsCalc:
mov dx, offset Fps
mov ah, 9
int 21h
mov ax, 4c00h
int 21h
; end of main program
Proc Lawn ; draw a line of lawn. Y-position * 320 should be passed in DI
inc di
mov dl, 17 ; cl = number of lawn pictures to draw next to eachother
xor cx, cx
numberloop:
mov si, offset grass
mov dh, 16 ; dh = rows of current picture
rowloop3:
mov cl, 4
rep movsd
add di, 320-16
dec dh
jnz rowloop3
sub di, (320 * 16- 16)
dec dl
jnz numberloop
ret
EndP Lawn
; All the SpriteDrawing Procedures take their parameters as following:
;
; DI = X coordinate
; DX = Y coordinate * 320
; DS:SI points to the sprite
; ES holds the segment of the (virtual) screen
;
; For the DrawXXXXXXRight procs, DS:BX has to point to the X coordinate in
; memory.
;
; Registers destroyed:
;
; DrawOpaque : AH, CX, DI, SI
; DrawTransparent: AX, CX, DI, SI
Proc DrawOpaqueRight ; draw a sprite without preserving the
; background, clipping the right side
mov ah, SpriteYsize ; repeat for 10 lines
cmp di, rightclip ; needs clipping?
jg clip1 ; if pos <= clip-coord, don't clip
add di, dx ; di = y * 320 + x
; ds:si already points to the sprite
nocliploop1:
mov cx, (SpriteXsize/2); div 2 since we're moving words
rep movsw ; move sprite data to virtual screen
add di, 320 - SpriteXsize
dec ah
jnz nocliploop1
jmp stop1
clip1:
mov cx, SpriteXsize + rightclip ; cx := 10 + clip const
sub cx, di ; cx := 10 - x-coord + clip const = 10 - clippixels
mov [savecx], cx ; saveguard cx already
add di, dx
cliploop1:
mov [savedi], di
rep movsb ; move part of sprite to screen
mov cx, [savecx] ; restore cx
If bothsides
sub di, [bx] ; di - x-coord of sprite
sub di, cx ; di - cx, to cancel effect of rep movsb
add di, leftclip ; di + 1 -> now points to (1,sprite-y)
sub cx, SpriteXsize
neg cx ; cx = number of remaining pixels
rep movsb ; move remaining pixels to the beginning of line
Else
sub cx, SpriteXsize
neg cx ; cx = number of remaining pixels
add si, cx
Endif
mov di, [savedi] ; restore original di
mov cx, [savecx] ; and cx
add di, 320 ; di now points to (x, currline+1)
dec ah ; decrease line counter,
jnz cliploop1 ; if not 0, draw next line
stop1:
ret
EndP DrawOpaqueRight
Proc DrawTransparentRight
; di holds x-coord of sprite
cmp di, rightclip ; needs clipping?
mov ah, SpriteYsize ; repeat for 10 lines
jg clip2 ; if pos <= clip-coord, don't clip
add di, dx ; di := y * 320 + x
; ds:si already points to the sprite
noclipoutloop2:
mov cx, SpriteXsize ; move sprite data to the virtual screen
nocliploop2:
dec cx
js noclipdone2
if Is386
lodsb
else
mov al, [si]
inc si
endif
inc di
or al, al
jz nocliploop2
mov [es:di-1], al
jmp nocliploop2
noclipdone2:
add di, 320 - SpriteXsize
dec ah
jnz noclipoutloop2
jmp stop2
clip2:
mov cx, SpriteXsize + rightclip ; cx := 10 + clip const
sub cx, di ; cx = 10 - x-coord + clip const = 10 - clippixels
mov [savecx], cx ; saveguard cx already
add di, dx
outercliploop2:
mov [savedi], di
; move line of sprite to screen
inc cx ; the following part is the same as rep movsb,
cliploop2: ; except that is soes not overwrite the background
dec cx ; where it's not covered by a sprite
jz outcliploop2
lodsb
inc di
or al, al
jz cliploop2
mov [es:di-1], al
jmp cliploop2
outcliploop2:
If bothsides
sub di, [bx] ; di - x-coord of sprite
mov cx, [savecx] ; restore cx
sub di, cx ; di - cx, to cancel effect of rep movsb
add di, leftclip-1 ; di + 1 -> now points to (1, sprite-y)
sub cx, (SpriteXsize + 1)
neg cx ; cx = x-coord - clipconst
; move remaining pixels to the beginning of the line
cliploop3:
dec cx
jz outcliploop3
inc di
if Is386
lodsb
else
mov al, [si]
inc si
endif
or al, al
jz cliploop3
mov [es:di], al
jmp cliploop3
outcliploop3:
Else ; "the else" of bothsides
mov cx, [savecx] ; and cx
sub cx, SpriteXsize
neg cx ; cx = x-coord - clipconst
add si, cx ; adjust sprite index
Endif
mov cx, [savecx] ; and cx
mov di, [savedi] ; restore original di
add di, 320 ; di now points to (x, currline+1)
dec ah ; decrease line counter,
jnz outercliploop2 ; if not 0, draw next line
stop2:
ret
EndP DrawTransparentRight
Proc DrawOpaqueLeft
; di holds x-coord of sprite
mov ah, SpriteYsize ; repeat for 10 lines
cmp di, leftclip ; needs clipping?
jl clip4 ; if pos >= clip-coord, don't clip
add di, dx ; di := y * 320 + x
; ds:si points to the sprite
nocliploop4:
mov cx, (SpriteXsize / 2)
rep movsw ; move sprite data to the virtual screen
add di, 320 - SpriteXsize
dec ah
jnz nocliploop4
jmp stop3
clip4:
mov cx, leftclip
sub cx, di ; cx := leftclip - x-coord
add di, dx
mov [savecx], cx ; saveguard cx already
cliploop4:
mov [savedi], di ; saveguard di
; now, first the part on the right side of the screen is drawn, because
; that's where the first pixels have to be put
If bothsides
add di, rightclip + (SpriteXsize - 1) - (leftclip-1)
; di now points to 'end-of-line' minus x-coord
rep movsb ; move part of sprite to screen
mov cx, [savecx] ; restore cx
sub di, rightclip + (SpriteXsize - 1) - (leftclip-1)
; di points to the beginning of the line
Else
add di, cx
add si, cx
Endif
sub cx, SpriteXsize
neg cx ; cx = number of pixels left of the sprite
rep movsb ; move remaining pixels to the beginning of the line
mov di, [savedi] ; restore original di
mov cx, [savecx] ; and cx
add di, 320 ; increase di by 320 so it points to the next line
dec ah ; decrease line counter,
jnz cliploop4 ; if not 0, draw next line
stop3:
ret
EndP DrawOpaqueLeft
Proc DrawTransparentLeft
; di holds x-coord of sprite
mov ah, SpriteYsize ; repeat for 10 lines
cmp di, leftclip ; needs clipping?
jl clip5 ; if pos >= clip-coord, don't clip
add di, dx ; di := y * 320 + x
; ds:si points to the sprite
; move sprite data to the virtual screen
noclipoutloop5:
mov cx, SpriteXsize ; move sprite data to the virtual screen
nocliploop5:
dec cx
js noclipdone5
if Is386
lodsb
else
mov al, [si]
inc si
endif
inc di
or al, al ; for every pixel check whether it's black
jz nocliploop5 ; if it is, don't draw it
mov [es:di-1], al
jmp nocliploop5
noclipdone5:
add di, 320 - SpriteXsize
dec ah
jnz noclipoutloop5
jmp stop4 ; sprite is drawn, goto end
clip5:
mov cx, leftclip
sub cx, di ; cx := leftclip - x-coord
mov [savecx], cx ; saveguard cx already
add di, dx ; di := y * 320 + x
outercliploop5:
mov [savedi], di ; saveguard di
If Bothsides
add di, (rightclip - leftclip) + SpriteXsize
; di now points to 'end-of-line' minus x-coord
; move part of sprite to screen
inc cx
cliploop5:
dec cx
jz outcliploop5
if Is386
lodsb
else
mov al, [si]
inc si
endif
inc di
or al, al
jz cliploop5
mov [es:di-1], al
jmp cliploop5
outcliploop5:
mov cx, [savecx] ; restore cx
sub di, (rightclip - leftclip) + SpriteXsize + 1
; di points to the beginning of the line
Else
add di, cx
add si, cx
dec di
Endif
sub cx, (SpriteXsize + 1)
neg cx ; cx = number of pixels left of the sprite
; move remaining pixels to the beginning of the line
cliploop6:
dec cx
jz outcliploop6
inc di
if Is386
lodsb
else
mov al, [si]
inc si
endif
or al, al
jz cliploop6
mov [es:di], al
jmp cliploop6
outcliploop6:
mov di, [savedi] ; restore original di
mov cx, [savecx] ; and cx
add di, 320 ; increase di by 320 to let it point to the next line
dec ah ; decrease line counter
jnz outercliploop5 ; if not 0, draw next line
stop4:
ret
EndP DrawTransparentLeft
Proc river
mov bl, 2
xor cx, cx
mov di, 16*320+1
l1:
mov dl, 17 ; cl = number of lawn pictures to draw next to
; eachother
amountloop1:
mov si, offset water
mov dh, 16 ; ch = rows of current picture
rowloop1:
mov cl, 4
rep movsd
add di, 320-16
dec dh
jnz rowloop1
sub di, (320 * 16 - 16)
dec dl
jnz amountloop1
mov di, (48+16) * 320+1
dec bl
jg l1
mov di, 48 * 320+1
or bl, bl
jz l1
mov bl, 2
mov di, 32*320+1
l2:
mov dl, 17 ; dl = number of lawn pictures to draw next to
; eachother
amountloop2:
mov si, offset water + 15
mov dh, 16 ; dh = rows of current picture
rowloop2:
mov cl, 16
pixelloop:
movsb
sub si, 2
dec cl
jnz pixelloop
add di, 320-16
add si, 32
dec dh
jnz rowloop2
sub di, (320 * 16 - 16)
dec dl
jnz amountloop2
mov di, (48+32) * 320+1
dec bl
jnz l2
ret
EndP River
Proc DrawFrog
mov si, offset frog ; ds:si points to the frog picture
; di := x
xor al, al ; al := 0
; ah := y
cmp ah, 7 * 16 ; = "hight" of road
jb noroad
xor bx, bx ; bx := 0
cmp ah, 178
ja noroad ; if it is, set the and mask (bh) to 1, otherwise
inc bh ; leave it 0
noroad:
add di, ax
shr ax, 2
mov cx, 0a0bh ; 10 rows, 10 columns, but cl is decreased before the
; rest
add di, ax ; di := y * 320 + x
loop1: ; of the code is executed
dec cl ; decrease culomn counter
jz outloop ; cl = 0? -> goto the outer loop
inc di ; di points to the nextpixel on screen
if Is386
lodsb ; load the next frogpixel in al
else
mov al, [si] ; load the next frogpixel in al
inc si
endif
or al, al ; test if it is zero
jz loop1 ; if it is, don't draw and go to the next pixel
or bl, bl ; otherwise, check whether a collision has already
jnz nocolis ; occured; if so, do not check for it again
cmp [byte es:di], 0 ; check whether the background is zero (=black
jz nocolis ; if it is, no collission
inc bl ; otherwise, set the and mask to 1
nocolis:
mov [es:di],al ; put the pixel in place
jmp loop1 ; and jump to the next one
outloop:
mov cl, 11 ; again 10 columns to put
add di, 310 ; di points to the next line (10 pixels + 310)
dec ch ; decrease the row counter
jnz loop1 ; if not = 0 -> goto loop
mov al, bl ; al (function result) = 1 if a collission occured
and al, bh ; and it by bh; bh = 1 if the frog is on the road
; or IN the water, otherwise it's zero
ret
EndP DrawFrog
; random procedure based on code from Rory Barton
Proc random
xor ax, ax
out 43h, al
in al, 40h
ret
EndP Random
Proc TopWater
mov bh, 6 ; draw six lagoons
mov di, 288 + 46
newpictloop:
mov bl, 15 ; 15 rows each
rowloop:
mov si, 16 ; 16 collumns
colloop:
call random
aam ; random value between 0 and 9 in al
add al, 11
If Is386
stosb
Else
mov [es:di], al
inc di
Endif
dec si
jnz colloop
add di, 320 - 16
dec bl
jnz rowloop ; next row
sub di, (320 * 15) - 46
dec bh
jnz newpictloop ; next lagoon
ret
EndP TopWater
Proc Init
mov ax, dseg
mov ds, ax
mov ax, 13h
int 10h ; switch to graphics mode
mov ax, 305h ; set the new rate/delay for the game
xor bx, bx
int 16h
cld ; clear direction flag -> all movsb/w/d
; are forward
mov ax, _vaddr
mov es, ax
xor di, di
xor eax, eax
mov cx, 16000
rep stosd ; clear screen
mov dx, 3c8h
out dx, al
mov si, offset pall
inc dx
mov cx, 256*3
rep outsb ; set the pallette
call river ; draw the river
xor di, di
call lawn ; draw the top lawn
call TopWater ; and draw the lagoons
mov di, 6*16*320
call lawn ; draw the center verge
mov di, (192-16) * 320
call lawn ; draw the base verge
mov bx, 0309h ; bh = counter, bl = value to divide the
; random number by
mov di, 16*320 ; just below the little lagoons
mov si, 6*16*320-320+273 ; eol above the verge
outgrassloop:
mov cx, 273 ; play field is 273 pixels wide
grassloop:
call random
test al, 1 ; if the random value is even, don't
jz @nodraw ; draw a pixel
div bl
cmp [byte es:di-320], 10 ; compare the pixel on the previous
ja @nodraw ; line to green. If it's not green,
; don't draw
add ah, 2 ; add 2 to the random value
mov [es:di], ah ; put the pixel on four places,
mov [es:si], ah ; in the game you can see where :)
mov [es:di+6*16*320], ah
mov [es:si+5*16*320], ah
@nodraw:
inc di ; adjust the screen offsets
dec si
dec cx
jnz grassloop
add di, 47
sub si, 47
dec bh ; make the grass grow max 3 pixels
jnz outgrassloop
xor di, di ; draw green border around the playfield
mov al, 10
mov cl, 192
borderloop:
mov [es:di], al
mov [es:di+273],al
add di, 320
dec cl
jnz borderloop
move386 _vaddr _backaddr
xor ax, ax ; get the begin time, used to decide when the
mov es, ax ; turtles dive and to calculate the frame rate
mov di, 46ch
mov eax, [es:di]
mov [fpstime1], eax
timeloop:
cmp [es:di], eax
je timeloop
add ax, 18 ; and add 18 (= 1 sec) to that time
mov [time], ax
ret
EndP init
END Begin