3D Chess 2K18

The place for codemasters or beginners to talk about programming any language for the Spectrum.
User avatar
arkannoyed
Manic Miner
Posts: 435
Joined: Mon Feb 05, 2018 9:56 am
Location: Northamptonshire

Re: 3D Chess 2K18

Post by arkannoyed »

Part 4: Build Me A Thing?!

Ok, so moving on, now its onto the second routine, the one that actually builds the Chess Pieces.

As mentioned earlier, E holds the current piece. To decide whether we needed to come to this routine a zero check was done. As it didn't hold 00 then here we are!

So next, we need to take the result of the additional test on BIT 0 to see if we're going to make a White or Black piece.

Code: Select all

chess_2019:
           sbc a,a                           ;set B/W component according to BIT 0 of E
           ld c,a                            ;set B/W component in C, Black=FF, White=00
C now holds 00 for White and FF for Black.

As you might be able to see in pictures posted earlier on in this thread, the pieces start a few lines higher than the square bottom. So here we give it 4 lines of buffering to raise the start of the piece up a bit.

Code: Select all

blanking:
           ld b,04h
           ld a,d                            ;test current line no.
                                             ;D just holds a number used for line check and masking + counter correction.
           cp b                              ;if 00-03 then RET, as we've not reached printing
           ret c                             ;of the base yet.
The reason for using B as the value to check against is that it is used twice later on in the routine for different purposes.

If the D line counter holds 00-03 then we just return to the main routine.
User avatar
arkannoyed
Manic Miner
Posts: 435
Joined: Mon Feb 05, 2018 9:56 am
Location: Northamptonshire

Re: 3D Chess 2K18

Post by arkannoyed »

Part 4a: Jump Vectors

Now it gets rather complex, so hopefully you've been paying attention!

The next routine checks the data. The pieces are represented by the values 01-06, so to move around the data stream we use control codes 01-06 followed simply by a Lo byte address within the data to jump to.

Fortunately none of the pure data includes values from 00-07 for this is handy.

These vectors allow for all the pieces to start printing from the same point, but then follow their own paths, and in some cases converge again later on.

The routine to decode all of this isn't complicated, but as IX is used for the data addressing, and the fact that we need to save HL- the screen address to print to on the stack, we do a nifty swap to allow HL to do the addressing stuff, therefore saving a couple of bytes.

Code: Select all


           push ix                           ;swap indexing the graphics data from IX to HL
           ex (sp),hl                        ;swap SCR address onto stack and IX from stack into HL
get_byte:
           ld a,(hl)                         ;get data byte in A
           inc l                             ;next byte in data
           ld d,(hl)                         ;get D to use later or as the jump vector byte

           cp 07h                            ;test if A<07 indicating the target piece
           jr nc,test_7                      ;if +07 then continue processing data/std line
switch1:
           inc l                             ;
           cp e                              ;test against E, current piece
           jr nz,get_byte                    ;if not matching, then get next byte
switch2:
           ld l,d                            ;match found therefore we need to alter the addressing in HL using
           jr get_byte                       ;the second byte, now held in D, then repeat getting data
test_7:
           ld e,l                            ;adjust IX to match HL
           ld ixl,e
Upon exiting the routine, IX is restored to the correct point, leaving HL now free to be used as the Left hand side line buffer.
User avatar
Ast A. Moore
Rick Dangerous
Posts: 2640
Joined: Mon Nov 13, 2017 3:16 pm

Re: 3D Chess 2K18

Post by Ast A. Moore »

arkannoyed wrote: Wed Apr 17, 2019 9:33 am does this all read ok, and make some sense?
You’re doing great. Keep going.
Every man should plant a tree, build a house, and write a ZX Spectrum game.

Author of A Yankee in Iraq, a 50 fps shoot-’em-up—the first game to utilize the floating bus on the +2A/+3,
and zasm Z80 Assembler syntax highlighter.
User avatar
arkannoyed
Manic Miner
Posts: 435
Joined: Mon Feb 05, 2018 9:56 am
Location: Northamptonshire

Re: 3D Chess 2K18

Post by arkannoyed »

Part 4b: Where Now?

Next, with the data byte in A we need to decide which sort of decoding we need to do

Code: Select all

add a,a                           ;test BIT 7 of A

jr c,data_line                    ;if type '1' line then jump to Data_Line builder
If BIT 7 =0 then we're going to build a Standard line type, so called because it follows a simple pattern and uses length and a simple data infill along with the B/W component (held in C) to make the line.

If BIT 7 =1 then it will be one of 4 possible Data Lines, for which we need to jump to a separate routine for.
Last edited by arkannoyed on Wed Apr 17, 2019 10:48 am, edited 1 time in total.
User avatar
arkannoyed
Manic Miner
Posts: 435
Joined: Mon Feb 05, 2018 9:56 am
Location: Northamptonshire

Re: 3D Chess 2K18

Post by arkannoyed »

Part 5: Standard Lines

If Bit 7 was a '0' then we will have arrived here. Here is where we build the standard Line type which will be in HL & DE

the format of A will now be as follows;

Bits 7-4 will be 4 bits of data, anything you like as long as its the White version of the Data. This will become clearer soon.
Bits 3-0 are the counter which determines the length of the line. This value is currently -4, which gets added (remember B from a few posts earlier?)

So, given the byte A=A4 for instance (would've started as 52h before the BIT 7 test shift)
This contains the Data 0101 and the counter 0100 (04)
Upon correction the counter will become 08, meaning we perform 8 shifts (4 on each buffer Left and Right)

Assuming this will be a White piece, The line would look like this in the data buffers before the shift cycle;

HL=00000000 10101100
DE=00000000 00001100

This will form the line on screen when its printed;

10000000 000010101

Code: Select all

standard_line:
           ld l,a                            ;save source in L
           and 0f0h                          ;mask Data part upper 4 bits
           ld d,a                            ;save in D
           xor l                             ;inverse mask - 0Fh to get counter
           add a,b                           ;ADD +04h - same as  SUB -0FCh
           ld b,a                            ;counter into B
           ld a,c                            ;B/W component into A
           or 0ch                            ;overlay add-in outer bits
           and 0fch                          ;mask 0FCh
           ld e,a                            ;into E
           xor d                             ;apply Data
           ld l,a                            ;into L
           ld d,c                            ;B/W component into D
           ld h,c                            ;B/W component into H
           inc c                             ;test C (00-01=W/FF-00=B)
           jr nz,st_lp1                      ;skip over swap if Black
st_lp0:
           ex de,hl                          ;swap buffers Left-Right
st_lp1:
           add hl,hl                         ;propagate the unwanted bits out of HL
           djnz st_lp0                       ;loop

           ld a,h                            ;replace BIT 7 of H
           rla                               ;with the last BIT carried
           rrca                              ;out of D

           jr bit4_res+1                     ;jump to printing line -1 (ld h,a)
After the data is built and prior to the shift cycle, a test of C (the Black or White component) is performed. This swaps the sides that the register pairs relate to depending on which colour piece we're making. A simple way of reversing the shading from one side to the other.

Basically the lines are made at their maximum length, then squashed to the required length.

Clear as mud eh?

Finally, the last bit to be shifted out gets put into the other side at BIT 7. This allows the Data infill to cross a maximum of 1 bit into the opposite side. The longest line possible would be 26 bits, the shortest, 8 bits.
User avatar
arkannoyed
Manic Miner
Posts: 435
Joined: Mon Feb 05, 2018 9:56 am
Location: Northamptonshire

Re: 3D Chess 2K18

Post by arkannoyed »

I don't want to go too far ahead if anything is too baffling, otherwise some of the concepts that follow might appear totally bonkers!?
User avatar
djnzx48
Manic Miner
Posts: 729
Joined: Wed Dec 06, 2017 2:13 am
Location: New Zealand

Re: 3D Chess 2K18

Post by djnzx48 »

It looks good to me, but I think I'd need a month's supply of wet towels to understand the whole thing properly!
User avatar
arkannoyed
Manic Miner
Posts: 435
Joined: Mon Feb 05, 2018 9:56 am
Location: Northamptonshire

Re: 3D Chess 2K18

Post by arkannoyed »

Part 6: The Data Lines

If we've arrived here then BIT 7 must have been set to '1' indicating that we've encountered 1 of 4 line types.

First we need to do a bit check to see which of 2 branches to take;

Code: Select all

data_line:
           add a,a
           jr c,long_line
So if BIT 7 is set then we're going to jump to the complex Long Line decoder, if '0' then its one of the other 3 types.

Code: Select all

mini_ln_tst:
           cp 3eh
           jr c,bit5_test
           inc ixl
This checks whether its a 1 byte 'Mini Line' which will be a symmetrical 8 bit max length line or whether its one of the 2 byte line types, in which case an INC of IX is needed.

Next E and L are assumed to be empty, as the line will be less than 16 bits long so those 2 registers are set to 00h.

Then a test is performed to decide where to go next;

Code: Select all

bit5_test:
           ld e,00h                          ;set the outer bytes to 00
           ld l,e                            ;L=00

           add a,a
           jr nc,bit4_test-1
And if the BIT was a '1' then the Right side data in D has the remains of A added to it to create the Left side data that goes into the H register.

Code: Select all

bit5_set:
           add a,d
           jr bit4_res+1
However, if the BIT was a '0' then we go here;

Code: Select all

bit4_test:
           jr nc,bit4_res                    ;if bit ='0' then the data for BWRL is in the 4 bits remaining in A
bit4_set:
           rrca                              ;re-use bit to fill BIT 7, creating a left orientated mask
           and c                             ;mask with C BW byte to create 5 bit overlay mask of ccccc000
           xor d                             ;create the Black or White Left and Right buffer bytes
bit4_res:
           ld d,a                            ;load Right buffer
           ld h,a                            ;load Left buffer
This actually performs an RLCA as a consequence of the previous instruction being the jump vector at the end of the Long line routine (07h). This checks BIT 7 again. If its '0' then we've propagated the 'Mini Line' data to the correct position and can just put it straight into H and D. If its a '1' then we recycle that bit back to create a 5 BIT mask and use C (the Black / White component) to create a version of the data in D to create a Black or White version of the line, then placed into D and H

Apologies, that was really hard to explain in terms I even managed to follow.
d2010
Dizzy
Posts: 79
Joined: Tue Mar 26, 2019 9:19 am

Re: 3D Chess 2K18

Post by d2010 »

VIEWport music
https://youtu.be/RdQ_unzeFy4
:x
arkannoyed wrote: Fri Feb 15, 2019 3:17 pm I am interested to know if the source makes an sense to anyone perhaps more familiar with source code listings?

I generally comment mine extremely badly or not at all. I'm probably not alone in the fact that when you write something, it reads just fine to you without lengthy explanations. I do appreciate however, that the older I get, the more helpful the comments are when I revisit some past projects.

If any parts do need further explanation, then I will oblige of course.
User avatar
arkannoyed
Manic Miner
Posts: 435
Joined: Mon Feb 05, 2018 9:56 am
Location: Northamptonshire

Re: 3D Chess 2K18

Post by arkannoyed »

Part 6a: Long Lines

I have to confess I've never been a fan of this Data type. Its a 5 byte data format, allowing for complex line configurations. There are ways of reducing the size, but the decoding gets horrific!

Basically, the outer 3 bits on any lines that are at the maximum 22 bits long are the same whether Black or White, though different for each side. These 6 bits are the lower 6 bits of the first data byte and are split and shifted to the correct place as the top 3 bits of E and L

Code: Select all

long_line:
           ld l,a                            ;remainder of A into L - 6 bits
           and 0e0h                          ;mask upper 3 bits E0h
           ld e,a                            ;into E
The following 4 bytes contain the Left and right White and Black data. Using that handy value in B=04h from earlier, we use that to loop through the routine rather wastefully, as twice would do, but it allows the addressing to work correctly by doing the right number of INC IXL iterations.

The 3 bits placed in L earlier get shifted left using ADD HL,HL, though care has to be taken as this is actually 1 shift too many. The effect loses 1 pixel in 2 places, but its very difficult to spot. This might be corrected at some point. Alternatively, we could just DEC B to do 1 less loop. Adds a byte though :(

Code: Select all

lllp_00:
           ld a,(ix+00h)                     ;get White data
           and c                             ;mask with B/W component
           xor (ix+0feh)                     ;apply Black data switch
           ld d,h                            ;roll result from H into D. Pass 1 is null
           add hl,hl                         ;propagate 3 bits in L
           ld h,a                            ;B/W result into A
           inc ixl                           ;next
           djnz lllp_00                      ;loop x 4
           jr print_line                     ;second byte here is 07=RLCA
Last edited by arkannoyed on Wed Apr 17, 2019 12:04 pm, edited 1 time in total.
User avatar
arkannoyed
Manic Miner
Posts: 435
Joined: Mon Feb 05, 2018 9:56 am
Location: Northamptonshire

Re: 3D Chess 2K18

Post by arkannoyed »

d2010 wrote: Wed Apr 17, 2019 11:20 am VIEWport music
https://youtu.be/RdQ_unzeFy4
:x
arkannoyed wrote: Fri Feb 15, 2019 3:17 pm I am interested to know if the source makes an sense to anyone perhaps more familiar with source code listings?

I generally comment mine extremely badly or not at all. I'm probably not alone in the fact that when you write something, it reads just fine to you without lengthy explanations. I do appreciate however, that the older I get, the more helpful the comments are when I revisit some past projects.

If any parts do need further explanation, then I will oblige of course.
I think I feel rather queezy now! Eugh!
User avatar
arkannoyed
Manic Miner
Posts: 435
Joined: Mon Feb 05, 2018 9:56 am
Location: Northamptonshire

Re: 3D Chess 2K18

Post by arkannoyed »

Part 7: Print the Line

So, all of the construction of the line is now done and its there in HL and DE in a form that the following routines can interpret and print in the correct form.

First, we retrieve the Screen address to print to and swap it with the Left hand side data, so a single instruction handles that;

Code: Select all

print_line:
           ex (sp),hl                        ;put HL Left buffer on stack and retrieve screen address
           ld b,l                            ;save SCReen address LO byte
We also save the position of HL at the centre for doing the Left side in B

The masking is done by the Least Significant Bit (LSB) being detected and inverted (reset). In the case of the Right sided data, its already in the correct direction to print quite easily and calculate a mask on the fly as follows, whilst also checking whether we're doing 1 or 2 bytes.

Code: Select all

right:
           ld a,e                            ;test if E=00
           or a                              ;
           jr z,r_yes                        ;if E>00 then just copy D to screen.
r_no:
           ld (hl),d                         ;put full byte on screen, no need to mask
           inc l                             ;next L to far right byte
           ld d,e                            ;swap E into D so the routine that follows masks E instead
r_yes:
           ld a,d                            ;mask
           dec d                             ;and
           and d                             ;print D
           ld e,a                            ;
           xor d                             ;
           and (hl)                          ;merge with SCReen contents
           or e                              ;
           ld (hl),a                         ;place on SCReen

           ld l,b                            ;restore SCReen address LO byte
The Left side data is residing on the stack, so we get it into DE. The problem is that its been created in reverse form, backwards. As we're at the centre of the line though and the first bit to place is at BIT 7 of H, then we just work our way left one bit at a time until we reach the last and then invert it and return after again restoring the screen address to the centre.

Code: Select all

get_lhs:
           pop de                            ;LHS data retrieve from stack
           ld c,80h                          ;left bit mask set +1 right so that DEC L happens
left:
           rlc c                             ;next left bit
           jr nc,left_ovr
           dec l
left_ovr:
           ex de,hl                          ;swap data into HL
           add hl,hl                         ;propagate MSB
           ex de,hl                          ;swap back to SCReen in HL, data in DE
           sbc a,a                           ;apply carry
           xor (hl)                          ;XOR screen contents
           and c                             ;mask current bit
           xor (hl)                          ;XOR screen again to write the bit
           ld (hl),a                         ;place byte
           ld a,d                            ;check if data finished
           or e                              ;
           jr nz,left                        ;loop if not
left_end:
           ld a,(hl)                         ;change the last bit to 0
           xor c                             ;
           ld (hl),a                         ;

           ld l,b                            ;restore SCReen address
           ret                               ;
And that Ladies and Gentlemen is it! I could post the Graphics data next, though thats kind of pointless, so maybe just a link to the file or the asm. Any preferences? Any questions?
User avatar
arkannoyed
Manic Miner
Posts: 435
Joined: Mon Feb 05, 2018 9:56 am
Location: Northamptonshire

Re: 3D Chess 2K18

Post by arkannoyed »

I need a lie down! :?
User avatar
arkannoyed
Manic Miner
Posts: 435
Joined: Mon Feb 05, 2018 9:56 am
Location: Northamptonshire

Re: 3D Chess 2K18

Post by arkannoyed »

I knew this process would be useful!

I've just corrected the problem with losing bits in the Long Lines routine by changing at what point L gets its data by placing the instruction LD L,A at the start;

Code: Select all

data_line:
           ld l,a                            ;put data into L in case of Long Line so we don't lose any bits on the 4 shift cycle
           add a,a
           jr c,long_line
mini_ln_tst:
           cp 3eh
           jr c,bit5_test
           inc ixl
bit5_test:
           ld e,00h                          ;set the outer bytes to 00
           ld l,e                            ;L=00

           add a,a
           jr nc,bit4_test-1
bit5_set:
           add a,d
           jr bit4_res+1
long_line:
           and 0e0h                          ;mask upper 3 bits E0h
           ld e,a                            ;into E
lllp_00:
           ld a,(ix+00h)                     ;get White data
           and c                             ;mask with B/W component
           xor (ix+0feh)                     ;apply Black data switch
           ld d,h                            ;roll result from H into D. Pass 1 is null
           add hl,hl                         ;propagate 3 bits in L
           ld h,a                            ;B/W result into A
           inc ixl                           ;next
           djnz lllp_00                      ;loop x 4
           jr print_line                     ;second byte here is 07=RLCA
bit4_test:
           jr nc,bit4_res                    ;if bit ='0' then the data for BWRL is in the 4 bits remaining in A
bit4_set:
           rrca                              ;re-use bit to fill BIT 7, creating a left orientated mask
           and c                             ;mask with C BW byte to create 5 bit overlay mask of ccccc000
           xor d                             ;create the Black or White Left and Right buffer bytes
bit4_res:
           ld d,a                            ;load Right buffer
           ld h,a                            ;load Left buffer
User avatar
arkannoyed
Manic Miner
Posts: 435
Joined: Mon Feb 05, 2018 9:56 am
Location: Northamptonshire

Re: 3D Chess 2K18

Post by arkannoyed »

User avatar
arkannoyed
Manic Miner
Posts: 435
Joined: Mon Feb 05, 2018 9:56 am
Location: Northamptonshire

Re: 3D Chess 2K18

Post by arkannoyed »

Lastly, I need to send thanks to Einar, as he wrote the original initialisation routine that sets the board at the game start. It's only had very minor changes applied since as far as I can remember. The routine is totally separate to the board build/ display one and is 40 bytes. It does have a bug, in that it swaps the White King and Queens places with one another. So far, I haven't dedicated much time to trying to correct it or come up with an alternative, though I'm sure it can be done in fewer bytes.

Code: Select all

init_board:
           ld de,0ffc0h
           ld b,d
           ld c,d
           ld hl,data_17
ib_loop1:
           ld a,(hl)
           dec a
           ld (bc),a
           ldi
           jr nz,ib_loop1
           dec e
           ld b,20h
ib_loop2:
           ld (de),a
           inc e
           djnz ib_loop2

           ret                   ;23 bytes
data_17:
           db 05h,07h,09h,0bh,0dh,09h,07h,05h
           db 03h,03h,03h,03h,03h,03h,03h,03h,01h
User avatar
Einar Saukas
Bugaboo
Posts: 3070
Joined: Wed Nov 15, 2017 2:48 pm

Re: 3D Chess 2K18

Post by Einar Saukas »

arkannoyed wrote: Wed Apr 17, 2019 1:21 pmLastly, I need to send thanks to Einar, as he wrote the original initialisation routine that sets the board at the game start.
You are welcome!

arkannoyed wrote: Wed Apr 17, 2019 1:21 pmIt does have a bug, in that it swaps the White King and Queens places with one another.
Ops!!!
User avatar
arkannoyed
Manic Miner
Posts: 435
Joined: Mon Feb 05, 2018 9:56 am
Location: Northamptonshire

Re: 3D Chess 2K18

Post by arkannoyed »

Nevermind!

I do have a working version I knocked together in 41 bytes, but ideally I'd love 38, as theres 38 bytes spare between the end of the routine and the board matrix. However chances are I'll find somewhere to save a byte in the code at some point anyway in which case something bigger should fit.! :D
User avatar
arkannoyed
Manic Miner
Posts: 435
Joined: Mon Feb 05, 2018 9:56 am
Location: Northamptonshire

Re: 3D Chess 2K18

Post by arkannoyed »

Ch-ch-ch-ch Changes!

I suppose I'll have to do the whole damn post again! Grrrrrr!

Only 1 byte improvement actually with a change to the square calculating routine allowing a saving in the routine that sets the counter for piece height.
Now down to 484 bytes.
User avatar
arkannoyed
Manic Miner
Posts: 435
Joined: Mon Feb 05, 2018 9:56 am
Location: Northamptonshire

Re: 3D Chess 2K18

Post by arkannoyed »

Oh bugger, there goes another one!

Saved another byte in the same routine, so now 483 bytes

by changing the routine to this;

Code: Select all

get_height:
           ld e,c                         ;DE points to the current square
           ld a,(de)                      ;get the piece we need in A
           ld b,a                         ;save temp in B
           rra                            ;/2
           ld e,a                         ;DE points to the heights table
           ld a,(de)                      ;get the piece height
           ld e,b                         ;E now holds the current piece
           ld b,a                         ;B=height
           ld ix,0fe44h                   ;IX = Graphics data entry address
Or this, does the same thing;

Code: Select all

get_height:
           ld e,c                         ;DE points to the current square
           ld a,(de)                      ;get the piece we need in A
           rra                            ;/2
           ld e,a                         ;DE points to the heights table
           ld a,(de)                      ;get the piece height
           rl e                           ;put back th B/W bit into E
           ld b,a                         ;B=height
User avatar
arkannoyed
Manic Miner
Posts: 435
Joined: Mon Feb 05, 2018 9:56 am
Location: Northamptonshire

Re: 3D Chess 2K18

Post by arkannoyed »

And there is the possibly to save another byte by changing the first byte of the data to 96 instead of 9E so it can act as the jump vector for the loop.

Have to watch that though in case I save more bytes. I think I'll leave it for now.

Both routines now weigh in agonisingly at 257 bytes!
User avatar
arkannoyed
Manic Miner
Posts: 435
Joined: Mon Feb 05, 2018 9:56 am
Location: Northamptonshire

Re: 3D Chess 2K18

Post by arkannoyed »

And another byte saved.

Silly me, never noticed that the first byte of both the Standard line and data line routines was LD L,A. So now that can be placed before we enter either of those.

Now 482 bytes
d2010
Dizzy
Posts: 79
Joined: Tue Mar 26, 2019 9:19 am

Re: 3D Chess 2K18

Post by d2010 »

arkannoyed wrote: Thu Apr 18, 2019 12:45 pm And another byte saved.
Silly me, never noticed that the first byte of both the Standard line and data line routines was LD L,A. So now that can be placed before we enter either of those.
Now 482 bytes
You make animations with demos.Why the pieces don't- move in table 8x8?
https://youtu.be/3XMqizx-8RA
Please subscribe , please you click on like-on-youtube for more-fun.
:arrow:
User avatar
arkannoyed
Manic Miner
Posts: 435
Joined: Mon Feb 05, 2018 9:56 am
Location: Northamptonshire

Re: 3D Chess 2K18

Post by arkannoyed »

Substance abuse is a strange pastime!! :lol:
User avatar
arkannoyed
Manic Miner
Posts: 435
Joined: Mon Feb 05, 2018 9:56 am
Location: Northamptonshire

Re: 3D Chess 2K18

Post by arkannoyed »

Not had a great deal of time to dedicate to this lately, but a couple of quick changes I spotted were possible this afternoon.

The first is 3 bytes saved in the Graphics data, where they can be shared elsewhere with just the addition of an extra jump vector (2 bytes)

The second I've been puzzling over for a very long time, and now I've finally figured out the maths of it. Instead of the traditional next line up routine, I've replaced it and shared the correction part from the squares calculating routine with calculating the next line up on screen.

All bafflingly complicated, however, its saved 3 bytes, and also allowed me to move the (still not completely fixed) board initialising routine to just before the board data matrix. Now there are precisely ZERO free bytes wasted inbetween.

The main routine and data is now an impressive 479 bytes!

Thats actually only 7 bytes away from the ENTIRE project fitting snugly into 512 bytes (if you include the 40 byte initialise routine)

Thankfully there seems to be no drop in speed at all.
Post Reply