Due to unusually high levels of website traffic you will be presenting with regular Cloudflare checks for the time being.

Changing all attributes

The place for codemasters or beginners to talk about programming any language for the Spectrum.
Post Reply
dfzx
Manic Miner
Posts: 704
Joined: Mon Nov 13, 2017 6:55 pm
Location: New Forest, UK
Contact:

Changing all attributes

Post by dfzx »

Another "let's get the Specchums to do my work for me disguised as a programming challenge" post! :lol:

What's the quickest way to change all attributes of a given value to another given value? So if I'm given (PAPER_RED|INK_WHITE) and I want all the character cells with that attribute value changed to (PAPER_WHITE|INK_BLUE), what's the most efficient way to do it (timewise)?
Derek Fountain, author of the ZX Spectrum C Programmer's Getting Started Guide and various open source games, hardware and other projects, including an IF1 and ZX Microdrive emulator.
edjones
Drutt
Posts: 33
Joined: Fri Feb 28, 2020 1:42 pm

Re: Changing all attributes

Post by edjones »

Most likely a loop of PUSH opcodes. I'll leave the actual code to you! :lol:
User avatar
bobs
Microbot
Posts: 107
Joined: Thu Dec 28, 2017 8:26 am
Location: UK
Contact:

Re: Changing all attributes

Post by bobs »

I’ll bite… A 256 high-byte aligned look-up table, where each low byte is an attribute value, and the value the attribute to change it to. Then run over the whole attribute buffer converting each one in turn.
Ralf
Rick Dangerous
Posts: 2307
Joined: Mon Nov 13, 2017 11:59 am
Location: Poland

Re: Changing all attributes

Post by Ralf »

I'm not reallly convinced about tricks with stack and table

Here is some very straighforward code, I'm writing without testing:

Code: Select all

LD HL,22528
LD DE, 256*checkedValue + newValue
LD B,0

Loop:
LD A,(HL)
CP D         ;is checked value
JP NZ,Loop2

LD (HL),E ;load new value
Loop2:
INC L      ;next byte
DJNZ Loop
And repeat it 3 times for 3 screen segments.

Now show me a faster version :)
User avatar
MatGubbins
Dynamite Dan
Posts: 1241
Joined: Mon Nov 13, 2017 11:45 am
Location: Kent, UK

Re: Changing all attributes

Post by MatGubbins »

D is the attribute that you want to change
E is the new attribute colour

Code: Select all

        LD   D,1        ; look for this value
        LD   E,2        ; Change to this 
        LD   HL,22528   ; start of attribs
again
        LD   A,(HL)     ; grab the value in HL
        CP   D          ; is this what I want?
        JR   NZ,skip    ; no, do JR
        LD   (HL),E     ; yes, change it
skip
        INC  HL         ; move along
        LD   A,H        ; read value of H
        CP   91         ; is it in the printer buffer?
        JR   NZ,again   ; no, loop
        RET             ; yes, done
 
catmeows
Manic Miner
Posts: 720
Joined: Tue May 28, 2019 12:02 pm
Location: Prague

Re: Changing all attributes

Post by catmeows »

Ralf wrote: Sun Apr 03, 2022 10:56 pm I'm not reallly convinced about tricks with stack and table

Code: Select all

  ld hl, attr_third
  ld d, table
loop:
  ld e, (hl)
  ld a, (de)
  ld (hl), a
  inc l
  djnz loop
Proud owner of Didaktik M
User avatar
Bubu
Manic Miner
Posts: 542
Joined: Fri Apr 02, 2021 8:24 pm
Location: Spain

Re: Changing all attributes

Post by Bubu »

Ralf wrote: Sun Apr 03, 2022 10:56 pm I'm not reallly convinced about tricks with stack and table

Here is some very straighforward code, I'm writing without testing:

Code: Select all

LD HL,22528
LD DE, 256*checkedValue + newValue
LD B,0

Loop:
LD A,(HL)
CP D         ;is checked value
JP NZ,Loop2

LD (HL),E ;load new value
Loop2:
INC L      ;next byte
DJNZ Loop
And repeat it 3 times for 3 screen segments.

Now show me a faster version :)

Let's go:

Code: Select all

ld hl, 22528
ld bc, 32*24
ld de, 256*checkedValue + newValue
loop:
ld a, d
cpir
ld (hl), e
ld a, b
or c
jp nz, loop
If something works, don't touch it !!!! at all !!!
catmeows
Manic Miner
Posts: 720
Joined: Tue May 28, 2019 12:02 pm
Location: Prague

Re: Changing all attributes

Post by catmeows »

Bubu wrote: Mon Apr 04, 2022 1:57 am
Let's go:

Code: Select all

ld hl, 22528
ld bc, 32*24
ld de, 256*checkedValue + newValue
loop:
ld a, d
cpir
ld (hl), e
ld a, b
or c
jp nz, loop
Nice one. I'm not very familiar with CPIR behaviour but I believe P/V flag could be used to control loop .

Code: Select all

loop:
cpir
ld (hl), e
jp po, loop
Proud owner of Didaktik M
dfzx
Manic Miner
Posts: 704
Joined: Mon Nov 13, 2017 6:55 pm
Location: New Forest, UK
Contact:

Re: Changing all attributes

Post by dfzx »

Thanks once again for the advice, chaps. :)

I don't get the PUSH or table approaches at all, but I can study and learn from the fast loops which have been presented.
Derek Fountain, author of the ZX Spectrum C Programmer's Getting Started Guide and various open source games, hardware and other projects, including an IF1 and ZX Microdrive emulator.
User avatar
TMD2003
Rick Dangerous
Posts: 2047
Joined: Fri Apr 10, 2020 9:23 am
Location: Airstrip One
Contact:

Re: Changing all attributes

Post by TMD2003 »

Bubu wrote: Mon Apr 04, 2022 1:57 am

Code: Select all

ld hl, 22528
ld bc, 32*24
ld de, 256*checkedValue + newValue
loop:
ld a, d
cpir
ld (hl), e
ld a, b
or c
jp nz, loop
Image

HOWEVER: I tried this, and it replaced every attribute one square immediately to the right of those I'd checked with what I specified to replace. Add in a DEC HL immediately after the CPIR, and... it works but is still not quite perfect, in that it stops replacing with four squares still unreplaced on row 21. (My BASIC test program only filled the PRINT rows rather than the INPUT area as well.)

I was using JR instead of JP in the last line. I changed it back to JP and it made no difference, though there was only one attribute left unreplaced.

But still, it's almost top banana! While ploughing through the Machine Code Tutor again, the chapters on block instructions are very vague about the non-LD instructions, so although I thought CPIR (and CPDR) might be useful, it was still hard to see how. This has at least given a concrete example that I can work with.

The IN and OUT block instructions are still a total mystery...
Spectribution: Dr. Jim's Sinclair computing pages.
Features my own programs, modified type-ins, RZXs, character sets & UDGs, and QL type-ins... so far!
Ralf
Rick Dangerous
Posts: 2307
Joined: Mon Nov 13, 2017 11:59 am
Location: Poland

Re: Changing all attributes

Post by Ralf »

Code: Select all

  ld hl, attr_third
  ld d, table
loop:
  ld e, (hl)
  ld a, (de)
  ld (hl), a
  inc l
  djnz loop
I get it now.
The lookup table approach is very counter-intuitive but yes, it actually may be the fastest.

Explanation for anybody who needs it:
We don't check if we should change a byte or not. We change every byte.
Or it would better to say we "change" every byte.

Because we have a table (actually a block of bytes) which looks like

Code: Select all

address       value
32768 + 0     0
32768 + 1     1
32768 + 2     2
32768 + 3     155
32768 + 4     4
So we read an attribute from memory.
Then we use it as lower byte of table address.
Then we read a value from table address and write it to the memory

in 99% of cases we "replace" the value with the same value :)
In one single case we replace our value with new value (3 to 155 in my example)

And it is actually faster then checking each time if we should replace the value or not :o
User avatar
TMD2003
Rick Dangerous
Posts: 2047
Joined: Fri Apr 10, 2020 9:23 am
Location: Airstrip One
Contact:

Re: Changing all attributes

Post by TMD2003 »

I've worked out what's going wrong with my fix to Bubu's CPIR routine. Every time I have to DEC HL so that the new attributes are dropped in the right place, it reduces the number of squares that the BC loop will check by one. A slow (for machine code!) solution is to put the loop right back to the initial line so that it starts again at the first square and CPIRs the entire attribute area until it finds nothing:

Code: Select all

loop: ld hl, 22528
ld bc, 32*24
ld de, 256*checkedValue + newValue	; I used 199 here so that flash/bright/paper/ink 0 is replaced with fl1/br1/paper0/ink7
ld a, d
cpir
[b]dec hl[/b]	; this had to be inserted to get the attribute in the right place
ld (hl), e
ld a, b
or c
[b]jr nz, loop[/b]	; no idea why Bubu used JP here...!
On closer inspection I found Bubu's idea was to start the CPIR process again from the point where it found the last attribute to compare with, so where HL had to be dropped by one to compensate for CPIR landing one byte further on from where it should be, so BC also needs to be corrected, nudged back up by one. After some hair-pulling, I worked out that the INC BC has to come before the CPIR, so that when the CPIR process is finished, BC=0 if there are no attributes left to check - I was putting it after the DEC HL, in which case BC always equals at least 1 and it gets stuck in a loop.

I wrote a (not entirely small!) routine to fill the entire screen (INPUT area as well) with * characters, no flash or bright, black ink, and a random paper colour. To make sure the adjustments worked perfectly, I made sure (23,31) - the last byte of the attributes - was black on black so that the CPIR instruction would pick up on it (at least, given the value I'd put in DE). So now, this version of Bubu's code will do the job, put the new attributes where they need to be, and scan and correct the entire attribute area without touching the next byte:

Code: Select all

ld hl, 22528
ld bc, 32*24
ld de, 256*checkedValue + newValue
loop:
ld a, d
[b]inc bc[/b]
cpir
[b]dec hl[/b]
ld (hl), e
ld a, b
or c
[b]jr nz, loop[/b]
And to vary it from BASIC:
POKE startaddress+7,CheckedValue
POKE startaddress+8,NewValue

Now the Triumphant Baby can have his day!
Spectribution: Dr. Jim's Sinclair computing pages.
Features my own programs, modified type-ins, RZXs, character sets & UDGs, and QL type-ins... so far!
User avatar
Lethargeek
Manic Miner
Posts: 759
Joined: Wed Dec 11, 2019 6:47 am

Re: Changing all attributes

Post by Lethargeek »

Ralf wrote: Mon Apr 04, 2022 12:48 pm

Code: Select all

  ld hl, attr_third
  ld d, table
loop:
  ld e, (hl)
  ld a, (de)
  ld (hl), a
  inc l
  djnz loop
I get it now.
The lookup table approach is very counter-intuitive but yes, it actually may be the fastest.
also it may be unrolled to make it faster BUT
it's always 25 clocks per byte (without djnz)
however

Code: Select all

	...
	cp (hl)
	jrnz _skip
	ld (hl),c
_skip	inc l
	...
is 25/23 clocks per byte without a table

...but of course using tables you can change more than one value in one go
AndyC
Dynamite Dan
Posts: 1448
Joined: Mon Nov 13, 2017 5:12 am

Re: Changing all attributes

Post by AndyC »

Rather than worrying about fudging BC around the CPIR, could you not just do:

DEC HL
LD (HL), E
INC HL
User avatar
TMD2003
Rick Dangerous
Posts: 2047
Joined: Fri Apr 10, 2020 9:23 am
Location: Airstrip One
Contact:

Re: Changing all attributes

Post by TMD2003 »

As it turns out, that works as well. (And it'll be faster, but imperceptibly so...)
Spectribution: Dr. Jim's Sinclair computing pages.
Features my own programs, modified type-ins, RZXs, character sets & UDGs, and QL type-ins... so far!
AndyC
Dynamite Dan
Posts: 1448
Joined: Mon Nov 13, 2017 5:12 am

Re: Changing all attributes

Post by AndyC »

Splitting the screen into thirds may also help, since then you might be able to get away with 8-bit adjustments, though I haven't thought through all the logic there so it might get tricky at the boundaries.
User avatar
Bubu
Manic Miner
Posts: 542
Joined: Fri Apr 02, 2021 8:24 pm
Location: Spain

Re: Changing all attributes

Post by Bubu »

TMD2003 wrote: Mon Apr 04, 2022 2:48 pm
jr nz, loop ; no idea why Bubu used JP here...!
I think JP y faster than JR:

JP Z : 10 clock cycles
JR Z : 12 clock cycles (only if condition fails, then 7 clock cycles)
If something works, don't touch it !!!! at all !!!
User avatar
MatGubbins
Dynamite Dan
Posts: 1241
Joined: Mon Nov 13, 2017 11:45 am
Location: Kent, UK

Re: Changing all attributes

Post by MatGubbins »

Please correct me if I've made a huge mistake here but when the CPIR BC counter hits zero it will continue with the next instruction of placing E into (HL) and that will be address 23296, the first byte of the printer buffer.

Code: Select all

  
        LD   A,0     ; 
        LD   (23296),A   ; set to zero for testing
        LD   D,64  ; look for this
        LD   E,249 ; set to this value
        LD   HL,22528
        LD   BC,32*24
alloop  LD   A,D
        INC  BC
        CPIR
        DEC  HL
        LD   (HL),E
        LD   A,B
        OR   C
        JR   NZ,alloop
        RET   
Once the code has been run and returned to basic, do PRINT PEEK 23296 and it should be 249.


This next snip of code removes the INC BC command and adds in the INC HL after LD (HL),E
This will now place a colour in 23295 (the last byte of the attributes)

Code: Select all

        LD   A,0
        LD   (23296),A   ; set to zero for testing

        LD   D,64  ; look for this
        LD   E,249 ; set to this value
        LD   HL,22528
        LD   BC,32*24
alloop  LD   A,D
       ; INC  BC
        CPIR
        DEC  HL
        LD   (HL),E
        INC  HL
        LD   A,B
        OR   C
        JR   NZ,alloop
        RET
PRINT PEEK 23296 should be zero.

Are the routines flawed or is it me doing something wrong?
User avatar
Turtle_Quality
Manic Miner
Posts: 508
Joined: Fri Dec 07, 2018 10:19 pm

Re: Changing all attributes

Post by Turtle_Quality »

Yes the routine is flawed
catmeows wrote: Mon Apr 04, 2022 2:08 am I'm not very familiar with CPIR behaviour but I believe P/V flag could be used to control loop .

Code: Select all

loop:
cpir
ld (hl), e
jp po, loop
The point @catmeows made was overlooked in the latest versions, there needs to be a check of the P/V flag to see why the the CPIR finished, either because a match was found or because the BC counter reached zero
Definition of loop : see loop
User avatar
MatGubbins
Dynamite Dan
Posts: 1241
Joined: Mon Nov 13, 2017 11:45 am
Location: Kent, UK

Re: Changing all attributes

Post by MatGubbins »

I did wonder what the PO part did but couldn't get it working properly. A jiggle of the placement and by using the RET PO command and adding 1 to the BC counter then it should now work properly.

Code: Select all

        LD   A,64      ; look for this
        LD   E,249     ; set to this value
        LD   HL,22528  ; start address
        LD   BC,768+1  ; bytes to count <--- note the +1 here
alloop  CPIR           ; if (HL) = A then flag it up
                           ; auto INC HL
                           ; auto DEC BC
                           ; if flag is true then exit CPIR
                           ; if BC is 0 then exit CPIR
        RET  PO        ; if BC is 0 then it has hit the printer buffer, RET
             ; JP PO,done   ; or use this
                       ; else
        DEC  HL        ; shuffle back a byte
        LD   (HL),E    ; put the value in
        INC  HL        ; shuffle forward
        JP   alloop    ; loop
done
If it still fails then I'm going back to the simple routines :)
User avatar
Bedazzle
Manic Miner
Posts: 309
Joined: Sun Mar 24, 2019 9:03 am

Re: Changing all attributes

Post by Bedazzle »

MatGubbins wrote: Tue Apr 05, 2022 1:07 am Please correct me if I've made a huge mistake here but when the CPIR BC counter hits zero it will continue with the next instruction of placing E into (HL) and that will be address 23296, the first byte of the printer buffer.
You can change CPIR to CPDR, so it will run until ROM.
User avatar
Turtle_Quality
Manic Miner
Posts: 508
Joined: Fri Dec 07, 2018 10:19 pm

Re: Changing all attributes

Post by Turtle_Quality »

Bedazzle wrote: Tue Apr 05, 2022 9:42 pm You can change CPIR to CPDR, so it will run until ROM.
Neat solution, certainly quicker than checking every time why the CPIR finished
Definition of loop : see loop
Post Reply