yeah, it's really sad. understandable from the hardware point, but sad.
Short fallout style text game (Merged from multiple topics)
Re: Reading a 16 bit value at (IX+4)
- ParadigmShifter
- Dynamite Dan
- Posts: 1048
- Joined: Sat Sep 09, 2023 4:55 am
Re: Reading a 16 bit value at (IX+4)
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.
Shadow register exchange works the same way which is why that's only 4T as well.
Re: Reading a 16 bit value at (IX+4)
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?
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?
- MatGubbins
- Dynamite Dan
- Posts: 1242
- Joined: Mon Nov 13, 2017 11:45 am
- Location: Kent, UK
Re: Reading a 16 bit value at (IX+4)
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
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.
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
(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.
Re: Reading a 16 bit value at (IX+4)
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?
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?
Re: Reading a 16 bit value at (IX+4)
Training exercise: translate your code to work on a C64.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.
"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!
Features my own programs, modified type-ins, RZXs, character sets & UDGs, and QL type-ins... so far!
Re: Reading a 16 bit value at (IX+4)
add e
ld e, a
jr nc 1
inc d
POKE 23614,10: STOP 1..0 hold, SS/m/n colors, b/spc toggle
Re: Reading a 16 bit value at (IX+4)
jr nc 1 seems to be not compiling
Re: Reading a 16 bit value at (IX+4)
Re: Reading a 16 bit value at (IX+4)
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.
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:
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:
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:
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.
Code: Select all
00000001 10101010 = DE
10000000 = A
---------------------
now, E+A is:
1 00101010 = E
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
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:
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
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.
Re: Reading a 16 bit value at (IX+4)
but hey, we have that handy Page Zero as a register bank instead! ;-)
Re: Reading a 16 bit value at (IX+4)
Commas... pfff
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
Re: Reading a 16 bit value at (IX+4)
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)
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)
Re: Reading a 16 bit value at (IX+4)
I was always too scared to give that a go! Wasn't sure what would end up where.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)
Re: Reading a 16 bit value at (IX+4)
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.
Re: Short fallout style text game
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.
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.
- ParadigmShifter
- Dynamite Dan
- Posts: 1048
- Joined: Sat Sep 09, 2023 4:55 am
Re: Short fallout style text game
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.
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.
If you know beforehand HL is aligned to a 2 byte boundary (actually, as long as L isn't #FF) you can do this
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:
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
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
Code: Select all
MACRO LDHL_CONTENTSHL reg
LD reg, (HL)
INC HL
LD H, (HL)
LD L, reg
ENDM
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
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
Code: Select all
readAddressIntoHL:
LDHL_CONTENTSHL a
RET
- ParadigmShifter
- Dynamite Dan
- Posts: 1048
- Joined: Sat Sep 09, 2023 4:55 am
Re: Short fallout style text game
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
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
Re: Short fallout style text game
I know this post is a bit late -since you already decided to go the Assembly way...Wall_Axe wrote: ↑Mon Aug 07, 2023 2:22 pmSpoiler
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.
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) .
Re: Short fallout style text game
thanks for the suggestions, derefencing without trashing DE is good. I'll check out lantern for a laff, always like to investigate these things.
- ParadigmShifter
- Dynamite Dan
- Posts: 1048
- Joined: Sat Sep 09, 2023 4:55 am
Re: Short fallout style text game
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).
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.
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
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.
- ParadigmShifter
- Dynamite Dan
- Posts: 1048
- Joined: Sat Sep 09, 2023 4:55 am
Re: Short fallout style text game
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
More action packed example lol
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
More action packed example lol
Re: Short fallout style text game
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.
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.
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.
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.