Thanks to some spare time, I had the opportunity to retake the speccy programming, so here is my last creation: a clone of the Depthcharge arcade game. Nothing fancy, but at least I could have some fun developing for ours old but reliable Spectrums.
Game mechanic is simple: move your ship left and right and drop charges to destroy submarines, while avoiding their missiles. Number of submarines and missiles can be chosen at each game start.
The file attached contains:
- .TAP Spanish version
- .TAP English version
- Source code for both versions
- A 'readme.txt' file
Hope you enjoy and as always, any comments are welcome. Cheers!
FUNCTION t() as uLong
asm
DI
LD DE,(23674)
LD D,0
LD HL,(23672)
EI
end asm
end function
Which saves a lot of maths and division. If you want to divide by 50 to get whole seconds, you can do it outside the function - but generally, I found t() returning clock ticks and working with them as 50 times larger meant I have more precision on where I launch events. (Instead of at 1s, 2s, 3s, I go 50 frames, 100 frames, 150 frames - and then if I want to I can tweak that to 40,80,120 much more easily).
I notice you are actually using it in the line:
a = t1 / 30 * PI
So to get t you do a lot of stack work, then divide by 50 - and then divide by 30. I think I'd do it as frames and divide by 150 in one go when setting a, which is probably shorter than running the full division twice.
Anyway. Just my thougts. The game is quite fun Never enough depth charges.
11-18-2018, 12:48 AM (This post was last modified: 01-05-2021, 01:32 PM by boriel.
Edit Reason: Fix URL
)
britlion Wrote:Very nice little fun thing. Good job.
By the way, my sneaky fast t() function is:
Code:
FUNCTION t() as uLong
asm
DI
LD DE,(23674)
LD D,0
LD HL,(23672)
EI
end asm
end function
Which saves a lot of maths and division. If you want to divide by 50 to get whole seconds, you can do it outside the function - but generally, I found t() returning clock ticks and working with them as 50 times larger meant I have more precision on where I launch events. (Instead of at 1s, 2s, 3s, I go 50 frames, 100 frames, 150 frames - and then if I want to I can tweak that to 40,80,120 much more easily).
I notice you are actually using it in the line:
a = t1 / 30 * PI
So to get t you do a lot of stack work, then divide by 50 - and then divide by 30. I think I'd do it as frames and divide by 150 in one go when setting a, which is probably shorter than running the full division twice.
Anyway. Just my thougts. The game is quite fun Never enough depth charges.
Many thanks! I'm not fond on ASM so any input is very welcome; at least I can compare your code with mine and learn something new
Besides, I updated the game with a few improvements: sonar is now draw OK , added a minor visual effect on it, load screen and intro music. And the inlays, shared with my other Escape game.
Spanish inlay: https://drive.google.com/open?id=1aHkpNZ...Cfx5sgDVvJ
English inlay: https://drive.google.com/open?id=1TTmyl3...31KLPwTItz
Cheers.
EDIT: just tried your ASM t() function and it's so fast I can now "play" with the sonar and add more effects
Anyway, just to try to understand a bit of ASM with your function:
DI -> disables interruptions, so the program won't interfere with the function
LD DE,(23674) -> load the value of the 23674 address (part of the Spectrum frames system variable) in the DE register (one of the Spectrum 8+8 -pair- bit registers)
LD D,0 -> load 0 into D register (?)
LD HL,(23672) -> load the value of the 23674 address (part of the Spectrum frames system variable) in the HL register (one of the Spectrum 8+8 -pair- bit registers)
EI -> enables interruptions before existing function
But the function, what returns? The sum of all values? And why load a zero value into D? Sorry if they are basic ASM questions, but my knowledge of ASM and the Z80 are almost none.
boriel Wrote:Note: You can directly do that in ZXBasic (replacing the ASM function) with:
Code:
t = PEEK(Ulong, 23672) BAND 0xFFFFFF
It won't be perhaps as fast as the ASM. Can you check it?
Tested and it's way faster than ASM; in fact, I can't control the speed of the sonar pointer. With the ASM function, I can do that changing the 360 in a = t1 / 360 * PI: REM a is the seconds pointer in radians line.
This is how the sonar subroutine is defined right now:
Code:
REM Rutina del sonar
SUB sonar()
REM sonido del sonar
segundos = segundos + 1
if segundos = 15 then
beep 0.07,45 : beep 0.13,30
segundos = 0
poke 23164+(RND*3),135
poke 23196+(RND*3),135
poke 23228+(RND*3),135
end if
REM grafico del sonar
FUNCTION t() as uLong
asm
DI
LD DE,(23674)
LD D,0
LD HL,(23672)
EI
end asm
end function
DIM t1 AS FLOAT
t1 = t()
a = t1 / 360 * PI: REM a is the seconds pointer in radians
sx = 20 * SIN a : LET sy = 20 * COS a
PLOT 237, 28: DRAW sx, sy
if sx2 <> sx then
over 1
PLOT 237, 28: DRAW sx2, sy2
sx2 = sx : sy2 = sy
over 0
CIRCLE 236, 28, 18
CIRCLE 236, 28, 13
CIRCLE 236, 28, 8
plot 236,9 : draw 0,38
plot 217,28 : draw 38,0
end if
if sx > 19.9 or sx < -19.9 or (sx > 0 and sx < 3) then
resetsonar = resetsonar + 1
if resetsonar = 1 then
for y = 18 to 23
print at y,27; " ";
next y
CIRCLE 236, 28, 18
CIRCLE 236, 28, 13
CIRCLE 236, 28, 8
plot 236,9 : draw 0,38
plot 217,28 : draw 38,0
end if
else
resetsonar = 0
end if
How the heck is the compiled code faster than the ASM one? *blink*
I thought the ASM one was pretty optimal - I can't see how to do it faster. I must check that. Are you sure it's working correctly (if too fast) with it?
The ASM one does:
DI - disable interrupts. Just for a moment, it stops the clock. This means it can't tick mid measure. Technically it will lose a frame very very _very_ rarely, but in practice nobody would notice. It's a safety device to make all the bytes consistent while we read it.
LD DE, (23674) - The clock is at 23672,23763,23674. This loads D up with (23675) and E up with (23674). The contents of 23675 are redundant - it's a 3 byte clock, so we clear D with
LD D,0
LD HL, (23672) loads H with (23673) and L with (23672).
EI - Put interrupts back. We've got our numbers.
end ASM
Back to normal.
So all we've done is copy the clock bytes into DEHL (well, 0+E+H+L)
Since the function returns a uLong, which is 4 bytes in DEHL, it returns this number.
Boriel's peek method is technically not interrupt safe, by the way, though the probability of an interrupt hitting right in the middle of those small set of instructions is fairly low. That said, it's going to happen eventually.
britlion Wrote:How the jeck is the compiled cosde faster than the ASM on? *blink*
You are right, my mistake :-S I really wanted to say I didn't know to implement/control the code provided by boriel as opposite as yours, what works "as is", without any modification.