3D Chess 2K18

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

Re: 3D Chess 2K18

Post by arkannoyed » Mon Apr 15, 2019 1:46 pm

Back on topic! :D

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!!
0 x

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

Re: 3D Chess 2K18

Post by arkannoyed » Mon Apr 15, 2019 2:25 pm

.....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
0 x

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

Re: 3D Chess 2K18

Post by arkannoyed » Tue Apr 16, 2019 9:54 am

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.
2 x

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

Re: 3D Chess 2K18

Post by arkannoyed » Tue Apr 16, 2019 10:41 am

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.

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
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).
2 x

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

Re: 3D Chess 2K18

Post by arkannoyed » Wed Apr 17, 2019 7:56 am

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.

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 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.
1 x

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

Re: 3D Chess 2K18

Post by arkannoyed » Wed Apr 17, 2019 8:08 am

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.

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
Note: main_call of course being the next majorly important part that follows
0 x

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

Re: 3D Chess 2K18

Post by arkannoyed » Wed Apr 17, 2019 8:18 am

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.

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
B is set as the counter to find the offset position left, which happens as follows;

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
Now we can simply double the line length counter and start filling in those bits!

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
Simple!
0 x

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

Re: 3D Chess 2K18

Post by arkannoyed » Wed Apr 17, 2019 8:24 am

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.

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
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.
0 x

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

Re: 3D Chess 2K18

Post by arkannoyed » Wed Apr 17, 2019 8:30 am

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

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                         ;
Then we just perform a DJNZ loop according to what height the piece (or empty square) is.

Code: Select all

piece_loop:
           djnz get_len                   ;inner loop for pieces
Finally, the next board address calculator value is restored and the next square address is INC'd.

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
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.
0 x

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

Re: 3D Chess 2K18

Post by arkannoyed » Wed Apr 17, 2019 8:33 am

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.
0 x

Post Reply