Short fallout style text game (Merged from multiple topics)

The place for codemasters or beginners to talk about programming any language for the Spectrum.
User avatar
ketmar
Manic Miner
Posts: 732
Joined: Tue Jun 16, 2020 5:25 pm
Location: Ukraine

Re: Reading a 16 bit value at (IX+4)

Post by ketmar »

ParadigmShifter wrote: Sat Sep 30, 2023 7:01 pm You can't do

ex de, ix

or

ex de, iy

though.
yeah, it's really sad. understandable from the hardware point, but sad.
User avatar
ParadigmShifter
Manic Miner
Posts: 780
Joined: Sat Sep 09, 2023 4:55 am

Re: Reading a 16 bit value at (IX+4)

Post by ParadigmShifter »

It just "flips the wiring" round (it doesn't really do that, but that is the effect) so when you access DE it uses HL instead and vice versa :) It doesn't actually exchange any bits in registers or swap anything, just which one responds to instructions, which is why it is so fast. Clever.

Shadow register exchange works the same way which is why that's only 4T as well.
Wall_Axe
Manic Miner
Posts: 501
Joined: Mon Nov 13, 2017 11:13 pm

Re: Reading a 16 bit value at (IX+4)

Post by Wall_Axe »

ok ill give some more detail:
I have 'rooms' with three string each to describe the room.

I want to store pointers to the rooms in an array. So I can do random access to them.
(this is not relevant to the original post though)

So I wanted the first string at the beginning of the room struct, so no math needed to access it.
Then I wanted to print the next string , which is 32 bytes plus the address of the room, (which is the same as the address of the first string)

What I'm doing is putting the address of the room into DE and then adding 32 to it. But I wondered if there is a more official or better way of doing it with IX?
User avatar
MatGubbins
Dynamite Dan
Posts: 1239
Joined: Mon Nov 13, 2017 11:45 am
Location: Kent, UK

Re: Reading a 16 bit value at (IX+4)

Post by MatGubbins »

If each of your room arrays are stored the same format

byte 0 - start X
byte 1 - start Y
byte 2 - length X
byte 3 - length Y
byte 4 - roomtext
and so on

then you can set up with your assembler code to look like this

Code: Select all

startX     EQU  0    ; this will be IX+0
startY     EQU  1    ;              IX+1 
lengthX    EQU  2    ;              IX+2
lengthY    EQU  3    ;              IX+3
RoomText   EQU  4    ;              IX+4
and so on

(depending on your assembler you might need a space or comma or = between the EQU and the value)

LD IX,roomplace
then all you have to do to read or write is
LD A,(IX+startX) ; this will be the same as LD A,(IX+0)
or
LD (IX+lengthX),A ; this will be the same as LD (IX+2),A

this will make your life a whole lot easier to read and follow your own code.
Wall_Axe
Manic Miner
Posts: 501
Joined: Mon Nov 13, 2017 11:13 pm

Re: Reading a 16 bit value at (IX+4)

Post by Wall_Axe »

thanks, I just had another look at my code: here is a better explanation.

at the label
stringtoprint:

is the address of the string to print. My string printing function reads it.

So I need to get the memory address of the room +32 and save that address to stringtoprint.

Just wondered the best way of doing that?

IX+32 wouldnt work cos it only does bytes and not 16 bit memory values. Unless you knew how to do it using high and low bytes somehow.

On a related note, how would I add an 8bit value in A to DE?
User avatar
TMD2003
Rick Dangerous
Posts: 2047
Joined: Fri Apr 10, 2020 9:23 am
Location: Airstrip One
Contact:

Re: Reading a 16 bit value at (IX+4)

Post by TMD2003 »

ParadigmShifter wrote: Sat Sep 30, 2023 5:14 pm Learning how to cope with not very many registers is worth learning even as a beginner (like the OP seems to be) though I think.
Training exercise: translate your code to work on a C64.

"Wait... what... where are all the registers? A, X, Y, that's your lot?"
Spectribution: Dr. Jim's Sinclair computing pages.
Features my own programs, modified type-ins, RZXs, character sets & UDGs, and QL type-ins... so far!
sn3j
Manic Miner
Posts: 523
Joined: Mon Oct 31, 2022 12:29 am
Location: Germany

Re: Reading a 16 bit value at (IX+4)

Post by sn3j »

Wall_Axe wrote: Sun Oct 01, 2023 12:52 pm On a related note, how would I add an 8bit value in A to DE?
add e
ld e, a
jr nc 1
inc d
POKE 23614,10: STOP      1..0 hold, SS/m/n colors, b/spc toggle
Wall_Axe
Manic Miner
Posts: 501
Joined: Mon Nov 13, 2017 11:13 pm

Re: Reading a 16 bit value at (IX+4)

Post by Wall_Axe »

sn3j wrote: Sun Oct 01, 2023 3:24 pm add e
ld e, a
jr nc 1
inc d
Thanks, quite a complicated thing to do in itself :shock:
Wall_Axe
Manic Miner
Posts: 501
Joined: Mon Nov 13, 2017 11:13 pm

Re: Reading a 16 bit value at (IX+4)

Post by Wall_Axe »

jr nc 1 seems to be not compiling
User avatar
Stefan
Manic Miner
Posts: 823
Joined: Mon Nov 13, 2017 9:51 pm
Location: Belgium
Contact:

Re: Reading a 16 bit value at (IX+4)

Post by Stefan »

Wall_Axe wrote: Sun Oct 01, 2023 4:23 pm jr nc 1 seems to be not compiling
https://clrhome.org/table/#jr%20nc

You're missing a comma.
User avatar
ketmar
Manic Miner
Posts: 732
Joined: Tue Jun 16, 2020 5:25 pm
Location: Ukraine

Re: Reading a 16 bit value at (IX+4)

Post by ketmar »

16-bit math with 8-bit registers is quite easy. just imagine doing it in binary, with the good old method they toight at school.

Code: Select all

00000001 10101010 = DE
         10000000 = A
---------------------

now, E+A is:
1 00101010 = E
so, we used 8-bit operation, but got a 9-bit result. that "extra" bit is in "carry" processor flag now. it is called "carry" exactly because it holds the bit which we should "carry on" for the 2nd addition part.

to perform the second part (modifying D), we need to do:
D+0+carry_flag.
because by the textbook rule, all "missing" left-hand digits are assumed to be zeroes.

there are plenty of ways for doing that. prolly the most eaiser to understand is this:

Code: Select all

add a,e
ld  e,a
; here we done with E, and carry holds our "extra bit"
; now finish it! with D ;-)
ld  a,d
adc a,0
ld  d,a
here, "ADC" does: "A + n + carry_flag" (hence the "C").

now, as we have our working code, we can optimise it. as we basically need to simply increment D when carry flag is set, we can do it slightly faster:

Code: Select all

add a,e
ld  e,a
; now, jump over the increment if carry flag is not set ("NC" means "no carry" here)
jr  nc,skip_me
inc d
skip_me:
but this introduces a label, and all labels must be unique. i.e. you cannot simply copy-paste that code where you need it. so let's use a little trick:

Code: Select all

add a,e
ld  e,a
; now, jump over the increment if carry flag is not set
jr  nc,$+3
inc d
here, special asm command "$" always holds the address of the first byte of the instruction we're currently assembling. i.e. here it is the address of the first byte of "JR" instruction. "JR" is 2 bytes long, and "INC" is 1 byte, so we need to jump 3 bytes forward.

now we have code that doesn't need any labels, and easy to copy-and-paste around.

just don't overuse such "$" hacks. it is almost always better to introduce a new label, it helps to keep your code more readable and maintainable.
Last edited by ketmar on Sun Oct 01, 2023 5:39 pm, edited 1 time in total.
User avatar
ketmar
Manic Miner
Posts: 732
Joined: Tue Jun 16, 2020 5:25 pm
Location: Ukraine

Re: Reading a 16 bit value at (IX+4)

Post by ketmar »

TMD2003 wrote: Sun Oct 01, 2023 2:02 pm Training exercise: translate your code to work on a C64.

"Wait... what... where are all the registers? A, X, Y, that's your lot?"
but hey, we have that handy Page Zero as a register bank instead! ;-)
sn3j
Manic Miner
Posts: 523
Joined: Mon Oct 31, 2022 12:29 am
Location: Germany

Re: Reading a 16 bit value at (IX+4)

Post by sn3j »

Commas... pfff
Wall_Axe wrote: Sun Oct 01, 2023 3:59 pm Thanks, quite a complicated thing to do in itself :shock:
It's a bit like you did it in school back then, you add two digits and the carry, if there is one, goes into the next column.
POKE 23614,10: STOP      1..0 hold, SS/m/n colors, b/spc toggle
AndyC
Dynamite Dan
Posts: 1429
Joined: Mon Nov 13, 2017 5:12 am

Re: Reading a 16 bit value at (IX+4)

Post by AndyC »

One of the most common tricks in Z80 is page aligning your data. This means putting things on 256 byte boundaries where possible and also being cunning to take advantage of alignment even when you can't.

For example if you "room" structure is always 128 bytes and room 0 is page aligned, then you can add 32 to the address of it by doing (assuming the address is in DE

LD A,E
ADD A, 32
LD E, A

On the face of it, that shouldn't be right, but since you know adding 32 can't overflow from the low byte (because every address would be a multiple of 128) it doesn't matter.

If you have very small structures and around 256 elements, you can even benefit by rearranging the structures entirely around page alignment. So store (page aligned) all the first bytes of the structs first, then the second bytes (again page aligned) etc

Now reading the first three bytes of element 57 of a structure becomes something like:

LD H, structure_high_address
LD L, (57)
LD A,(HL)
INC H
LD E,(HL)
INC H
LD D,(HL)
User avatar
Joefish
Rick Dangerous
Posts: 2071
Joined: Tue Nov 14, 2017 10:26 am

Re: Reading a 16 bit value at (IX+4)

Post by Joefish »

SkoolKid wrote: Sat Sep 30, 2023 2:20 pm The simplest way would be something like this:

Code: Select all

LD L,(IX+4)
LD H,(IX+5)
I was always too scared to give that a go! :lol: Wasn't sure what would end up where.
User avatar
ketmar
Manic Miner
Posts: 732
Joined: Tue Jun 16, 2020 5:25 pm
Location: Ukraine

Re: Reading a 16 bit value at (IX+4)

Post by ketmar »

Joefish wrote: Sun Oct 01, 2023 10:33 pm I was always too scared to give that a go! :lol: Wasn't sure what would end up where.
schematic reasons, again. ;-) i was struggling to remember it too until i switched to think about what schematics will do, not how i write an emulation of the instruction. ;-) schematics can replace only one operand, and "(HL)" takes precedence. i mean, "(HL)" is distinct from "HL", or "H", or "L", they are completely different things. hardware can flip either "HL" (hence we have "LD A,IXH"), or "(HL)", but not both. and "(HL)" wins. if you remember octal instruction encodings, it can be thought like this: "register with the higher code always wins". smth like that.
Wall_Axe
Manic Miner
Posts: 501
Joined: Mon Nov 13, 2017 11:13 pm

Re: Short fallout style text game

Post by Wall_Axe »

I tried six different languages:basic,boriel's basic,Z88DK,OBERON etc. and all of them had faults like using too much memory, only using arrays or the data would overwrite the code, causing crashes.

I'm now using assembly and trying to use function calls as much as possible. To make it readable.

One interesting thing is that if you want to use pointers the first one is accessed like this:

LD HL,(playercharactername)

only one line and very simple, it loads the address that is stored at the memory address.

but if you have a pointer to a pointer you need completely different code to access the information at the second one:

LD E,(HL)
INC HL
LD D,(HL)
LD H,D
LD L,E

So I replaced all code to access information that is pointed to, with a function call:
readAddressIntoHL
LD E,(HL)
INC HL
LD D,(HL)
LD H,D
LD L,E
ret

so now the code is readable.
User avatar
ParadigmShifter
Manic Miner
Posts: 780
Joined: Sat Sep 09, 2023 4:55 am

Re: Short fallout style text game

Post by ParadigmShifter »

Tip 1: it's not very long code, use a macro instead, a CALL costs 3 bytes, macro is not much longer (it's 4 bytes long so costs an extra byte but saves a CALL and a RET execution time = 27T states (microjiffies)).

Tip 2: this is probably better (shorter, faster), trashes A though. But DE is not trashed (bonus!).Usually A is the register you don't mind trashing since it is used for all maths operations.

Code: Select all

; HL <- (HL)
; original HL is trashed
; A is trashed
readAddressIntoHL:
    LD A, (HL)
    INC HL
    LD H, (HL)
    LD L, A
    RET
Macro version (sjasmplus, not sure if it will work with other assemblers), and you can pick which register to trash instead of A (do not use H or L of course). Macro argument "reg" is the register you don't mind trashing.

Code: Select all

    MACRO LDHL_CONTENTSHL reg
    LD reg, (HL)
    INC HL
    LD H, (HL)
    LD L, reg
    ENDM
If you know beforehand HL is aligned to a 2 byte boundary (actually, as long as L isn't #FF) you can do this

Code: Select all

    MACRO LDHL_CONTENTSHL_ALIGN2 reg
    LD reg, (HL)
    INC L ; L only overflows if it was #FF (255)
    LD H, (HL)
    LD L, reg
    ENDM
saving 2 (TWO!) microjiffies. Don't check whether HL is aligned to a 2 byte address (low bit is 0) or if L is 255 at runtime though, that would be silly - use the safe version if you are unsure ;)

Note: macros which you don't use take up no space, unlike functions you never call.

Macro usage:

Code: Select all

    LD HL, #6942 ; example
    LDHL_CONTENTSHL a
    ; HL now contains (#6942), A is trashed
So you don't need a call. It's also easy to wrap a macro in a function later if space consumed by macro expansion gets worrying

Code: Select all

readAddressIntoHL:
    LDHL_CONTENTSHL a
    RET
User avatar
ParadigmShifter
Manic Miner
Posts: 780
Joined: Sat Sep 09, 2023 4:55 am

Re: Short fallout style text game

Post by ParadigmShifter »

Sometimes you want to just dereference HL into another register pair, and your original code was nearly there, but then it trashed the original pointer

Something like this DE <- (HL), HL -> HL+1

LD E, (HL)
INC HL
LD D, (HL)

then you can either DEC HL again if you want HL to be the same as what it started out as, or if you are about to read more stuff from the next address you can INC HL.

EDIT: Always get high and low byte order mixed up! Fixed

You often end up flipping between using DE and HL a lot and you want to look at EX DE, HL for that by the way.

If you want to do this

(DE) <- (HL)

have a look at LDI and LDIR (both of which trash BC, BC is used for the count in LDIR though).

LD DE, dest
LD HL, src
LDI
LDI
; (dest) now has (src); HL, DE incremented by 2; BC is decremented by 2

To copy 1357 bytes from src to dest (e.g. copying a string or other data)

LD DE, dest
LD HL, src
LD BC, 1357 ; example
LDIR

If the regions overlap, look at LDDR and go backwards ;)
firelord
Manic Miner
Posts: 574
Joined: Wed Nov 03, 2021 10:57 am
Location: Greece - Thessaloniki

Re: Short fallout style text game

Post by firelord »

Wall_Axe wrote: Mon Aug 07, 2023 2:22 pm
Spoiler
One interesting thing about fallout is playing as a stupid,lucky or charismatic character and seeing the differences in what happens.

I thought that this is simple enough for a zx spectrum basic game.
It could be:
A text adventure
have simple combat
Have choosable dialogue options that change depending on your stats.

To make it something that I won't abandon I was thinking of only having two situations in the game.
A situation is a quest basically.
It could be a locked door or a child in a well.

I wanted to crowdsource ideas.

To keep it a manageable project I was thinking of only three options:
Physically strong
Intelligent
Or.
Charismatic

Character
The two situations could be a base to explore and a group of ruffians to deal with outside the base.
Unless someone has better ideas.

The base would only consist of three or four rooms.
I know this post is a bit late -since you already decided to go the Assembly way...
These simple specs fit the capabilities of Lantern adventure system. While testing it for my CSSCGC games I tried more-or-less all the ideas you mentioned. It uses a nice GUI and uses a C-like language for events. You can also add some limited graphics in the 48k version (in form of SCR files) .
Wall_Axe
Manic Miner
Posts: 501
Joined: Mon Nov 13, 2017 11:13 pm

Re: Short fallout style text game

Post by Wall_Axe »

thanks for the suggestions, derefencing without trashing DE is good. I'll check out lantern for a laff, always like to investigate these things.
User avatar
ParadigmShifter
Manic Miner
Posts: 780
Joined: Sat Sep 09, 2023 4:55 am

Re: Short fallout style text game

Post by ParadigmShifter »

I have a kind of hierarchy for most cases of which registers I can trash and which I can't.

A register - always assume it can be trashed. It's used for nearly everything. If you need to keep it around and have a spare register, copy A to that register and copy it back once you are done with what you are doing. I hardly ever PUSH AF or POP AF though (I use EX AF, AF' most of the time when I was going to do that and don't have another register to save it to - getting more advanced there though. EX AF, AF' is fast and you really don't need to push the flags register very often so it's a waste usually). EDIT: I think what I most use POP AF for is popping something off the stack I pushed earlier but don't need to remember anymore, i.e. cleanup the stack but preserve current values of BC, DE and HL ;) That's because INC SP is kind of useless and you have to do it twice to throw a value away (would be miles better if it added 2 to SP instead of 1 as a special case just for that register!).

C register - I usually use this for outer loop counters and as such it is usually pushed on entry to a loop along with B, so I can normally trash this/use it as a temp register (say to copy A to) inbetween.

B register - normally the inner loop counter because of DJNZ. Usually pushed on entry to the loop and popped before the DJNZ so is often available like C.

Typical inner/outer loop code, DE, HL and A still available (and BC can be used as temp registers if you push them).

Code: Select all

    LD C, outerloopctr
    
.outerloop
    LD B, innerloopctr

.innerloop
    PUSH BC ; if you want to do other stuff with B or C
    ; do loopy stuff
    POP BC
    DJNZ .innerloop
    ; stuff you do after innerloop has finished before looping back
    DEC C
    JR NZ, .outerloop
DE - usually used as a second pointer since EX DE, HL is a thing. If you don't need to use DE as a second pointer, use them both as general purpose registers.

HL - usually needs to be saved since it is the most useful 16 bit register (only one you can do an add with).

Not counting IX, IY here since I don't use them very often if I can help it. I tend to use EXX a lot to avoid PUSH/POP but that's getting a bit more advanced. I've got my current source file open - it's around 2000 lines of code before the data section starts (including blank lines though) - number of times I use the index registers: 0

I use a ton of macros but if the code bloat gets too much I will turn them into calls as I explained how to do earlier.
Wall_Axe
Manic Miner
Posts: 501
Joined: Mon Nov 13, 2017 11:13 pm

Re: Short fallout style text game

Post by Wall_Axe »

thanks, i wouldnt mind getting into macros


its interesting to see the spectrum doing something lightning fast:
Image
User avatar
ParadigmShifter
Manic Miner
Posts: 780
Joined: Sat Sep 09, 2023 4:55 am

Re: Short fallout style text game

Post by ParadigmShifter »

If you don't wait for the VBlank (i.e. don't do a halt) you can go a lot faster than that, but it may flicker.

This is what I was doing yesterday, had to slow the animation down to 1/4 speed since it was too fast at 50fps. I'm only drawing what changes each frame though.

Blue border is when it has finished drawing everything (so no flicker since it hasn't reached top of the screen yet). White border is twiddling thumbs doing a HALT

Image

More action packed example lol

Image
Wall_Axe
Manic Miner
Posts: 501
Joined: Mon Nov 13, 2017 11:13 pm

Re: Short fallout style text game

Post by Wall_Axe »

nice, i was playing columns 3 on the megadrive for PS4 a few days ago.

I had to make a function to display a number from 0 to 255, it was torturous but got it done. Had to create the divide number function as well :)

I realise I am reinventing the wheel, but I like to know whats going on.


Now I can print strings and numbers thats it really, the technical stuff is almost done. I just need input.

Then I can create functions that do high level stuff like print a room description. Then I'll hopefully be coding in mostly english.

Image

Im not using halt so thats as fast as I can get it to go. Im coding for ease and size, not speed though. SO im using loads of function calls, not unrolling loops etc.
Post Reply