3D Chess 2K18
Re: 3D Chess 2K18
Extremely impressive what can be done in such a small amount of memory just goes to show how much is wasted within a PC.
- arkannoyed
- Manic Miner
- Posts: 436
- Joined: Mon Feb 05, 2018 9:56 am
- Location: Northamptonshire
Re: 3D Chess 2K18
I know, I’m trying my best to comment the source, then explanations will make sense. Maybe tomorrow!dfzx wrote: ↑Sun Apr 14, 2019 1:27 pmI'd ask that maybe you'd add another sentence or two explaining, at a high level, what you spotted and how you optimised it. And how you spotted it, if there's a pattern or something which others might learn from.arkannoyed wrote: ↑Fri Apr 12, 2019 4:48 pm Probably uninteresting to many here, and I agree that I should really be concentrating on developing the GUI and game stuff, however, I've saved a further 2 bytes on the main display code, making it now 490 bytes
I'm sure its familiar to many, that sometimes when you're staring at the same piece of code and you mind isn't in gear, its nice to escape by faffing with something else instead!
Not that I mean to be impudent, demanding what you should tell us. But you're clearly an expert on optimising Z80 code, and I for one, as a beginner/intermediate level Z80 coder, could learn a huge amount from you if you'd ever care to share it.
- Einar Saukas
- Bugaboo
- Posts: 3099
- Joined: Wed Nov 15, 2017 2:48 pm
Re: 3D Chess 2K18
At the risk of going further off topic - Yeah, I had that when it came out. Quite an achievement, but nothing on the order of Quake as a game. And besides, that was four times bigger at 128k (compressed, with procedurally generated textures, levels and sounds).
It was more a jab at the idiocy of the zack4mac's assessment of the state of PC software.
- arkannoyed
- Manic Miner
- Posts: 436
- Joined: Mon Feb 05, 2018 9:56 am
- Location: Northamptonshire
Re: 3D Chess 2K18
Back on topic!
I’ve had a rather successful morning. I’ve been studiously tidying up source in order to try and make it suitable to post in snippets with explanations. Like a sort of mini series on what bits do what I suppose. Anyway, I have a few inspired moments, and I’ve only gone and saved 4 more bytes!! So the main code is now 486 bytes
Which is comprised of 260 code and 226 data
The initialise board routine had to sacrifice a byte as it was sharing the first byte (01 from ld BC) of the code as the terminating byte for its data. So that’s now back at 40 bytes. However, there’s a brand new method of calculating the screen address sort of cumulatively, whereas before it was done from a base address each time. This is obviously faster, though not noticeably. A few other tweeks here and there and voila!
Nearly there with tidying up source, even with the new bits, though it’s probably harder to understand now!!
I’ve had a rather successful morning. I’ve been studiously tidying up source in order to try and make it suitable to post in snippets with explanations. Like a sort of mini series on what bits do what I suppose. Anyway, I have a few inspired moments, and I’ve only gone and saved 4 more bytes!! So the main code is now 486 bytes
Which is comprised of 260 code and 226 data
The initialise board routine had to sacrifice a byte as it was sharing the first byte (01 from ld BC) of the code as the terminating byte for its data. So that’s now back at 40 bytes. However, there’s a brand new method of calculating the screen address sort of cumulatively, whereas before it was done from a base address each time. This is obviously faster, though not noticeably. A few other tweeks here and there and voila!
Nearly there with tidying up source, even with the new bits, though it’s probably harder to understand now!!
- arkannoyed
- Manic Miner
- Posts: 436
- Joined: Mon Feb 05, 2018 9:56 am
- Location: Northamptonshire
Re: 3D Chess 2K18
.....and another byte saved!! That’s over 1% size reduction today alone!!
Amazing the things that happen when you’re in the right frame of mind, and perhaps caffeine fuelled!!
Now 485 bytes
Amazing the things that happen when you’re in the right frame of mind, and perhaps caffeine fuelled!!
Now 485 bytes
- arkannoyed
- Manic Miner
- Posts: 436
- Joined: Mon Feb 05, 2018 9:56 am
- Location: Northamptonshire
Re: 3D Chess 2K18
Part 1: The Graphics format
Before I start to try and explain the decoding and how the board is displayed, its important to cover the format that the graphics data takes.
The data is quite simple, but incorporates control codes along the way that decide when to jump to different parts of the data stream according to which piece is currently being printed.
The data entry point is the same for all, as the bases are all common.
The pieces are numbered as follows;
2/3 = Pawns
4/5 = Rooks
6/7 = Knights
8/9 = Bishops
A/B = Queens
C/D = Kings
Obviously these are Hex values. 00 = empty square.
What happens when we perform a shirt right on the piece number is that we get the following;
1 = Pawn
2 = Rook
3 = Knight
4 = Bishop
5 = Queen
6 = King
BIT 0 was indication whether it was a White (0) or Black (1) piece.
Accessing the graphics data is identical whether its a Black or white piece, but the difference in appearance is all down to that BIT 0 being used to alter the data.
There are two separate routines used to print the board. One handles the screen addressing, and searching through the board, along with printing the board squares (only Black are printed to save time)
The other routine is then called if a piece occupies the square. This builds the specific line of the piece and prints it. It also handles all of the graphics data addressing and handling the control codes within it.
The Graphics Data
There are 5 distinct types to graphics data for the build routine to handle. It was a long task to find a good balance between compact graphics data and the procramming to decode it.
1. Standard lines.
For all lines, the data is built in the HL and DE register pairs.
Standard lines are 1 byte long, and indicated by bit 7 being set to 0.
There are 4 bits of data which according to that Black or White bit (BIT 0) are either left as is (White) or inverted (Black)
The rest of the line is created using that colour bit to fill in.
The lower 3 bits of the data byte hold a counter to create the correct line length.
Depending upon whether its a Black or White line, the registers are swapped (EX DE,HL) before printing which swaps the shading (the 4 bits of data) to either side.
2. Mini lines.
These are also 1 byte of data where the lower 4 bits create a symmetrical line which is the same whether Black or white
3. BWL-BWR lines.
These lines consist of 2 bytes of data where the second byte is the Right side data and the lower 5 bits of the 1st byte are XOR'd onto it to create the Left side data. Same line for Black or White.
4. BLR-WLR lines.
These are again 2 bytes of data. They create a version of each other where the colour bit is XOR'ed to alter the data.
5. Long lines.
These are 5 byte lines which allow for complex lines where Black and White are often very different to one another (see Knights for example). The 1st bytes lower 6 bits hold bits 8-10 for each side. The lines can be a maximum of 22 bits long.
The 4 bytes folowing are arranged as follows
1 = Black data Right
2 = Black data Left
3 = White data Right
4 = White data Left
The Black data has the colour byte (FF=Black, 00=White) applied (AND) as a mask. Then the White data is XOR'ed onto it. This might seem odd, but in programming terms, it gives a simple (ish) cycle to get which data we need and keep the addressing simple. The Black data needs to be the XOR'ed result with White. Mindbending to hand encode I can confirm!
Black and White pieces are both the same size, so the same data is used and just changed according to some simple transforms to give the correct final resulting piece.
Originally the sprites and board were designed circa 1994-95. The data at that time was in the form of simple masked sprites at a size of 4000 bytes. The board again was a straight bitmap of 6144 bytes. Then there was a print routine and tables of all the piece print addresses and sizes and graphics data address too. All in it was about 11k.
Now nearly 25 years later its 485 bytes!
I'm not really sure what drove me to want to squash the size so dramatically, but its become quite a pleasing excercise in both encoding and programming, where my Z80 skills have really had to improve a lot.
Part 2 will examine the first part of the routine that handles creating the screen address of the squares and setting up the piece height for the print loop.
Hopefully as I go through the various parts, the process might become clearer to some. It has become far simpler as the routine has developed fortunately, and is now a very solid reliable thing. No nasty registers (IY, R, I etc) or any use of EXX that might interfere with things. Its also quite simple to add-in extra things as in what will need to be there to create the functionality of the GUI in the eventual playable entity.
Before I start to try and explain the decoding and how the board is displayed, its important to cover the format that the graphics data takes.
The data is quite simple, but incorporates control codes along the way that decide when to jump to different parts of the data stream according to which piece is currently being printed.
The data entry point is the same for all, as the bases are all common.
The pieces are numbered as follows;
2/3 = Pawns
4/5 = Rooks
6/7 = Knights
8/9 = Bishops
A/B = Queens
C/D = Kings
Obviously these are Hex values. 00 = empty square.
What happens when we perform a shirt right on the piece number is that we get the following;
1 = Pawn
2 = Rook
3 = Knight
4 = Bishop
5 = Queen
6 = King
BIT 0 was indication whether it was a White (0) or Black (1) piece.
Accessing the graphics data is identical whether its a Black or white piece, but the difference in appearance is all down to that BIT 0 being used to alter the data.
There are two separate routines used to print the board. One handles the screen addressing, and searching through the board, along with printing the board squares (only Black are printed to save time)
The other routine is then called if a piece occupies the square. This builds the specific line of the piece and prints it. It also handles all of the graphics data addressing and handling the control codes within it.
The Graphics Data
There are 5 distinct types to graphics data for the build routine to handle. It was a long task to find a good balance between compact graphics data and the procramming to decode it.
1. Standard lines.
For all lines, the data is built in the HL and DE register pairs.
Standard lines are 1 byte long, and indicated by bit 7 being set to 0.
There are 4 bits of data which according to that Black or White bit (BIT 0) are either left as is (White) or inverted (Black)
The rest of the line is created using that colour bit to fill in.
The lower 3 bits of the data byte hold a counter to create the correct line length.
Depending upon whether its a Black or White line, the registers are swapped (EX DE,HL) before printing which swaps the shading (the 4 bits of data) to either side.
2. Mini lines.
These are also 1 byte of data where the lower 4 bits create a symmetrical line which is the same whether Black or white
3. BWL-BWR lines.
These lines consist of 2 bytes of data where the second byte is the Right side data and the lower 5 bits of the 1st byte are XOR'd onto it to create the Left side data. Same line for Black or White.
4. BLR-WLR lines.
These are again 2 bytes of data. They create a version of each other where the colour bit is XOR'ed to alter the data.
5. Long lines.
These are 5 byte lines which allow for complex lines where Black and White are often very different to one another (see Knights for example). The 1st bytes lower 6 bits hold bits 8-10 for each side. The lines can be a maximum of 22 bits long.
The 4 bytes folowing are arranged as follows
1 = Black data Right
2 = Black data Left
3 = White data Right
4 = White data Left
The Black data has the colour byte (FF=Black, 00=White) applied (AND) as a mask. Then the White data is XOR'ed onto it. This might seem odd, but in programming terms, it gives a simple (ish) cycle to get which data we need and keep the addressing simple. The Black data needs to be the XOR'ed result with White. Mindbending to hand encode I can confirm!
Black and White pieces are both the same size, so the same data is used and just changed according to some simple transforms to give the correct final resulting piece.
Originally the sprites and board were designed circa 1994-95. The data at that time was in the form of simple masked sprites at a size of 4000 bytes. The board again was a straight bitmap of 6144 bytes. Then there was a print routine and tables of all the piece print addresses and sizes and graphics data address too. All in it was about 11k.
Now nearly 25 years later its 485 bytes!
I'm not really sure what drove me to want to squash the size so dramatically, but its become quite a pleasing excercise in both encoding and programming, where my Z80 skills have really had to improve a lot.
Part 2 will examine the first part of the routine that handles creating the screen address of the squares and setting up the piece height for the print loop.
Hopefully as I go through the various parts, the process might become clearer to some. It has become far simpler as the routine has developed fortunately, and is now a very solid reliable thing. No nasty registers (IY, R, I etc) or any use of EXX that might interfere with things. Its also quite simple to add-in extra things as in what will need to be there to create the functionality of the GUI in the eventual playable entity.
- arkannoyed
- Manic Miner
- Posts: 436
- Joined: Mon Feb 05, 2018 9:56 am
- Location: Northamptonshire
Re: 3D Chess 2K18
Part 2: The Beginning
So, swiftly on to the second part, and now for some actual code!!
This is the start of the routine, and a few parameters are set up.
The Board is stored as a simple 64 byte array at address FFC0h (65472). For easy end checking, when the end is reached, the lo byte address counter will reach 00.
The Screen address is calculated from a base value, then just incremented with each pass, taking into account when we need to reset it to the start of a row.
+0022 to go to the next square
+ff30 to reset to next row, square 0
then after the calculation we save that value on the stack for the next pass and correct it to give a valid screen address.
This involves 3 shifts left of the H register, done in a loop by setting the top 3 bits previously to '110' and checking for no Carry to end. simple!
Within the routine, B is set to FF for addressing the board. D is the line counter, used in a few places later in the routine.
HL now points to the bottom line of the square. The squares are all based upon 4 bytes width, and we're pointing to the 3rd byte along (second from the right).
So, swiftly on to the second part, and now for some actual code!!
This is the start of the routine, and a few parameters are set up.
The Board is stored as a simple 64 byte array at address FFC0h (65472). For easy end checking, when the end is reached, the lo byte address counter will reach 00.
Code: Select all
chess_build:
ld hl,0c9c0h ;initial value to calculate squares SCReen addr.
ld c,l
main_loop:
ld a,c ;current square into A
and 07h ;check row
ld de,0ff30h ;DE=next row add value
ld b,d ;B=FF
jr z,ovr_main ;if were still on the same row
ld de,0022h ;then add to go to the next square
ovr_main:
add hl,de ;add
push hl ;save cumulative square address
scr_crt:
sla h ;correct to create SCReen address
jr c,scr_crt ;loop 3 times
;square 00 address = 4810h
+0022 to go to the next square
+ff30 to reset to next row, square 0
then after the calculation we save that value on the stack for the next pass and correct it to give a valid screen address.
This involves 3 shifts left of the H register, done in a loop by setting the top 3 bits previously to '110' and checking for no Carry to end. simple!
Within the routine, B is set to FF for addressing the board. D is the line counter, used in a few places later in the routine.
HL now points to the bottom line of the square. The squares are all based upon 4 bytes width, and we're pointing to the 3rd byte along (second from the right).
- arkannoyed
- Manic Miner
- Posts: 436
- Joined: Mon Feb 05, 2018 9:56 am
- Location: Northamptonshire
Re: 3D Chess 2K18
Part 2a:How High?
Next up is the little routine that checks which piece is occupying the current square and sets its height into the counter B. We also initialise IX with the entry point for the Graphics data.
The table of height vectors is stored at FF00h, so by altering the Lo byte to be the piece code, we can easily find the number we need.
The piece occupying the square is also placed into E for use a little later on in the routine, where the main check takes place to either leave the square empty or print a piece on it.
Next up is the little routine that checks which piece is occupying the current square and sets its height into the counter B. We also initialise IX with the entry point for the Graphics data.
Code: Select all
get_height:
ld d,c ;save C
ld a,(bc) ;A=piece occupying the current square
ld e,a ;into E for use later on
rra ;/2 to get rid of BIT 0 (B/W bit)
ld c,a ;adjust address to point to piece heights table
ld a,(bc) ;get height
ld c,d ;restore C
ld d,b ;D=FF reset line counter
ld b,a ;B=height
ld ix,0fe44h ;IX = Graphics data entry address
The piece occupying the square is also placed into E for use a little later on in the routine, where the main check takes place to either leave the square empty or print a piece on it.
- arkannoyed
- Manic Miner
- Posts: 436
- Joined: Mon Feb 05, 2018 9:56 am
- Location: Northamptonshire
Re: 3D Chess 2K18
Part 3: The Piece Print Loop
Now, with all the important parameters set we enter the printing loop for the square/ piece. The square of the board and the piece occupying it (if any) are printed within the same loop so that it saves time.
After and INC D (the current line counter) we save BC and DE to the stack. Now its time to look at whether we need to print the current square. If its a Black square, then yes, if White, then we can skip this process entirely. This is done by checking the Parity of bits 1 and 3 in the current square number (00-3f). If the Parity is even, i.e. after an AND %00001001 we get either 0 bits set or 2 bits set then the square is White and with JP PE we can skip over the entire process. To actually reverse (or rotate 90 degrees) the board we can actually just use JP PO instead, which will prove to be useful with added functionality in the eventual game.
Next is a simple check to see if the line counter (D) shows that we've finished printing the square. So if its +11h then again we can skip this routine.
Note: main_call of course being the next majorly important part that follows
Now, with all the important parameters set we enter the printing loop for the square/ piece. The square of the board and the piece occupying it (if any) are printed within the same loop so that it saves time.
After and INC D (the current line counter) we save BC and DE to the stack. Now its time to look at whether we need to print the current square. If its a Black square, then yes, if White, then we can skip this process entirely. This is done by checking the Parity of bits 1 and 3 in the current square number (00-3f). If the Parity is even, i.e. after an AND %00001001 we get either 0 bits set or 2 bits set then the square is White and with JP PE we can skip over the entire process. To actually reverse (or rotate 90 degrees) the board we can actually just use JP PO instead, which will prove to be useful with added functionality in the eventual game.
Next is a simple check to see if the line counter (D) shows that we've finished printing the square. So if its +11h then again we can skip this routine.
Code: Select all
get_len:
inc d ;inc current line number
push de ;line number (D) and piece (E) onto stack
push bc ;height counter (B) and current square (C) onto stack
current_sq:
ld a,c ;C=current square
and 09h ;
rotation:
jp pe,main_call ;if white square, jump to main call
;change to JP PO to effectively rotate the board
;90 degrees.
calc_line:
ld a,d ;D=current line
sub 11h ;are we above the height of the board square?
jr nc,main_call ;if so, skip drawing the line part of the square
- arkannoyed
- Manic Miner
- Posts: 436
- Joined: Mon Feb 05, 2018 9:56 am
- Location: Northamptonshire
Re: 3D Chess 2K18
Part 3a: You're So Square
Next, if we're going to print a Black square, comes the important algorithm that calculates the length of the line we need to draw. This is based upon a calculation performed using the current line counter D only.
The Black squares are 17 lines high and have line lengths centered as follows;
2, 6, 10, 14, 18, 22, 26, 30, 32, 30, 26, 22, 18, 14, 10, 6, 2
The first step is to calculate the half length of the line so that from the centre we can move Left bit by bit to find the start position to begin setting the bits.
B is set as the counter to find the offset position left, which happens as follows;
Now we can simply double the line length counter and start filling in those bits!
Simple!
Next, if we're going to print a Black square, comes the important algorithm that calculates the length of the line we need to draw. This is based upon a calculation performed using the current line counter D only.
The Black squares are 17 lines high and have line lengths centered as follows;
2, 6, 10, 14, 18, 22, 26, 30, 32, 30, 26, 22, 18, 14, 10, 6, 2
The first step is to calculate the half length of the line so that from the centre we can move Left bit by bit to find the start position to begin setting the bits.
Code: Select all
adc a,d ;add back 1 + D (line number)
jr nc,ovr_e ;if we don't go above 00 then don't DEC or INVERT
jr z,skp1 ;
dec a
skp1:
cpl ;
ovr_e:
and 0fh ;
inc a ;
ld b,a ;line length /2 in B
Code: Select all
ld c,80h ;C=bit mask
add_line:
push hl ;save HL position
addl_lp1:
rlc c ;next bit left
jr nc,addl_ovr1 ;
dec l ;update HL if theres a carry
addl_ovr1:
djnz addl_lp1 ;loop
Code: Select all
draw_line:
add a,a ;x2 to get line length
ld b,a ;into B again
addl_lp2:
ld a,(hl) ;
or c ;
ld (hl),a ;merge bit with screen content
rrc c
jr nc,addl_ovr2
inc l
addl_ovr2:
djnz addl_lp2 ;
pop hl ;restore HL
- arkannoyed
- Manic Miner
- Posts: 436
- Joined: Mon Feb 05, 2018 9:56 am
- Location: Northamptonshire
Re: 3D Chess 2K18
Part 3b: Is Anybody There?
So now we come to the all important check to determine whether there is a piece occupying the square we're currently concentrating on.
Remember the E holds that value. So if E=00 then its an empty square. Anything else and we won't get a zero result, so something must be there.
Using an SRA E does 2 things in 1 basically. In the first instance it determines whether to CALL the piece building routine. If that CALL happens, then the bit that was shifted out of BIT 0 was the Black or White determining bit, which will set the overlay byte.
So now we come to the all important check to determine whether there is a piece occupying the square we're currently concentrating on.
Remember the E holds that value. So if E=00 then its an empty square. Anything else and we won't get a zero result, so something must be there.
Code: Select all
main_call:
sra e ;test E - piece no to see if a piece occupies the square.
call nz,chess_2019 ;call main build routine if a piece occupies the square
- arkannoyed
- Manic Miner
- Posts: 436
- Joined: Mon Feb 05, 2018 9:56 am
- Location: Northamptonshire
Re: 3D Chess 2K18
Part 3c: Going up and Loops
The remaining part of the routine after restoring BC and DE just now calculates the next line up for HL in Screen terms, which is fairly standard stuff
Then we just perform a DJNZ loop according to what height the piece (or empty square) is.
Finally, the next board address calculator value is restored and the next square address is INC'd.
So that first routine took just 112 bytes. If you were to run it alone with an empty board matrix you get....a chess board.
The remaining part of the routine after restoring BC and DE just now calculates the next line up for HL in Screen terms, which is fairly standard stuff
Code: Select all
pop bc ;B=height C=current square
pop de
nxt_line_up:
ld a,h ;fairly standard stuff
dec h ;I'm sure we're all
and 07h ;familiar with!
jr nz,piece_loop ;
ld a,l ;
add a,0e0h ;
ld l,a ;
sbc a,a ;
and 08h ;
add a,h ;
ld h,a ;
Code: Select all
piece_loop:
djnz get_len ;inner loop for pieces
Code: Select all
end_check:
pop hl ;restore SCReen calc value
inc c ;next square
jr nz,main_loop ;repeat if needed (64 squares)
ret ;ret
- arkannoyed
- Manic Miner
- Posts: 436
- Joined: Mon Feb 05, 2018 9:56 am
- Location: Northamptonshire
Re: 3D Chess 2K18
Purely as a matter of interest, does this all read ok, and make some sense? Obviously I understand what everything does without explanation, so I may have missed some vital information without realising. If its going ok though, then I'll move on to the rather more complicated piece printing routine.
- arkannoyed
- Manic Miner
- Posts: 436
- Joined: Mon Feb 05, 2018 9:56 am
- Location: Northamptonshire
Re: 3D Chess 2K18
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.
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.
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.
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
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.
If the D line counter holds 00-03 then we just return to the main routine.
- arkannoyed
- Manic Miner
- Posts: 436
- Joined: Mon Feb 05, 2018 9:56 am
- Location: Northamptonshire
Re: 3D Chess 2K18
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.
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.
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
- Ast A. Moore
- Rick Dangerous
- Posts: 2641
- Joined: Mon Nov 13, 2017 3:16 pm
Re: 3D Chess 2K18
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.
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.
- arkannoyed
- Manic Miner
- Posts: 436
- Joined: Mon Feb 05, 2018 9:56 am
- Location: Northamptonshire
Re: 3D Chess 2K18
Part 4b: Where Now?
Next, with the data byte in A we need to decide which sort of decoding we need to do
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.
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 =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.
- arkannoyed
- Manic Miner
- Posts: 436
- Joined: Mon Feb 05, 2018 9:56 am
- Location: Northamptonshire
Re: 3D Chess 2K18
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
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.
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)
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.
- arkannoyed
- Manic Miner
- Posts: 436
- Joined: Mon Feb 05, 2018 9:56 am
- Location: Northamptonshire
Re: 3D Chess 2K18
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!?
Re: 3D Chess 2K18
It looks good to me, but I think I'd need a month's supply of wet towels to understand the whole thing properly!
- arkannoyed
- Manic Miner
- Posts: 436
- Joined: Mon Feb 05, 2018 9:56 am
- Location: Northamptonshire
Re: 3D Chess 2K18
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;
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.
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;
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.
However, if the BIT was a '0' then we go here;
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.
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
Code: Select all
mini_ln_tst:
cp 3eh
jr c,bit5_test
inc ixl
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
Code: Select all
bit5_set:
add a,d
jr bit4_res+1
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
Apologies, that was really hard to explain in terms I even managed to follow.
Re: 3D Chess 2K18
VIEWport music
https://youtu.be/RdQ_unzeFy4
https://youtu.be/RdQ_unzeFy4
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.
- arkannoyed
- Manic Miner
- Posts: 436
- Joined: Mon Feb 05, 2018 9:56 am
- Location: Northamptonshire
Re: 3D Chess 2K18
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
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
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 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.