JMC: Play Time - Part 4

This is part 4 in a series about implementing Japanese Military Chess for the BBC Micro. See part 1 here .

Play Time

Fetch your best friend and gather round the BBC Micro. It’s time to get into the hot-seat and take turns playing Japanese Military Chess! Before we start though, let’s delve into the rules of the game in a bit more detail:

Rules of the Game

Photograph of a Japanese rules sheet

The objective of Japanese Military Chess is to capture your opponent’s headquarters with a sufficiently high ranking piece, or failing that, to eliminate enough of their pieces so that they are unable to move on their turn and hence lose. This is achieved by taking turns to move a piece, either into an empty space or to attack one of your opponent’s pieces. Attacks are resolved based on the types of the pieces involved, with one or both of the pieces being removed from play depending on the result.

Icon Name Quantity
Spy Spy 1
Engineer Engineer 2
Cavalry Cavalry 1
Junior Officer #1 Junior Officer #2 Junior Officer #3 Junior Officers 2 each
Senior Officer #4 Senior Officer #5 Senior Officer #6 Senior Officers 1 each
Tank Tank 2
Plane Plane 2
Flag Officer #7 Flag Officer #8 Flag Officers 1 each
General General 1
Mine Mine 2
Banner Banner 1

There are 16 different types of piece and players have 1 or 2 of each depending on the type, giving 23 pieces in total. At the beginning of the game, each player sets up their pieces by arranging them onto the 23 spaces on their side of the board. Crucially, a player can see positions but not the types of their opponent’s pieces. When one piece attacks another then a third-party, the computer in our case, is required to inspect the piece types and determine the outcome without revealing any other information to the players.

The game board is roughly a 6 by 8 grid of spaces divided equally into two 6 by 4 halves, one for each player. Where the two halves meet is notionally a river and there are two or four bridges for crossing it depending on the configuration being played. The one deviation from a regular grid is that each player’s half has a single double-sized space at the far end denoting their headquarters.

Most pieces can be placed anywhere during setup, but there are restrictions on the two immobile types of piece. The banner piece must be placed in front of another piece and so it cannot be placed on the back row of spaces. The landmine pieces cannot be placed next to any of the bridge entrances on the front row of spaces.

Once the setup is complete, players take it in turns to move one piece at a time until the game ends. Aside from the banner and the mines which are immobile, all pieces can move at least one space in any of the cardinal directions. Some pieces have better movement abilities. The cavalry and tank pieces can move two spaces forwards, but not over another piece. The engineer can move an unlimited number of spaces in any cardinal direction, but again not over another piece. The plane can move an unlimited number of spaces forwards or backwards, including over other pieces. The plane also ignores bridges and can cross the river at any point.

Attacker ⚔
Spy Engineer Cavalry Junior Officer #1 Junior Officer #2 Junior Officer #3 Senior Officer #4 Senior Officer #5 Senior Officer #6 Tank Plane Flag Officer #7 Flag Officer #8 General
Defender ⛨Spy
Engineer
Cavalry
Junior Officer #1
Junior Officer #2
Junior Officer #3
Junior Officer #3
Senior Officer #4
Senior Officer #6
Tank
Plane
Flag Officer #7
Flag Officer #8
General
Mine
Banner??????????????

The pieces are largely arranged in strength order. A stronger piece defeats weaker pieces, and two pieces of equal strength mutually destroy each other. There are some exceptions however. Mines always self-destruct when attacked and only the engineer and the plane can survive attacking them. The spy, which is otherwise the weakest piece, can defeat the general, which is otherwise the strongest. A spy attacking another spy loses the attack, rather than both being destroyed as with other types of piece.

The only remaining special case is the banner piece. The banner is immobile. When attacked, it behaves as if it was the same type as the piece immediately behind it provided that piece belongs to the same player. If the space behind it is empty or contains a piece belonging to the opposing player then the banner is always defeated.

If a player moves one of their flag officers or general onto their opponent’s headquarters, they win the game. Alternatively, if a player cannot take any legal moves on their turn then their opponent wins.

Build Your Own Army

Tray of Piece Types

The first thing we need is a mechanism for players to set up their pieces on the board. The previous demo already contained many of the necessary fundamentals for drawing the board, drawing the tray1 of available pieces on the side, and highlighting them both with a cursor. The principal missing ingredient was suitable controls with which to do the placement.

To make this work, we need to keep track of how many pieces are remaining to place. The array V_piece_quantities contains this information indexed by the piece type. An additional quantity bitmap is drawn next to each piece in the tray to show this to the player.

When a player tries to place a piece, if the quantity is sufficient, the piece is written into the board arrays. The remaining quantity is decremented and if an earlier placed piece was removed to make way then its quantity is incremented back up.

The board itself is stored as two 48 byte arrays. The first array, V_board_pieces, contains an element for every valid position on the board plus two invalid positions as explained later. The values in this array are either 0xFF (bit 7 set) if the space is empty or an ID number which identifies the piece occupying the space. The ID numbers are used as an index into a second array, V_piece_types, which contains the type of each piece.

The piece ID numbers are assigned so that they’re equal to each piece’s starting position index. Hence, which player a piece belongs to can be determined by which side of the board it started on. During placement, as all pieces are at their starting positions, we can simply use the same position index into both arrays when manipulating the board. During play once pieces start to move, we will need to lookup the piece ID in the first array in order to access the piece types stored in the second for a given position.

76543210
Empty flag Unused X coord Y coord
Side flag

The board is approximately a 6 by 8 grid of spaces, notwithstanding that the two headquarters spaces are double-sized. The Y coordinate occupies the least significant 3 bits of the position index. The X coordinate occupies the next 3 bits. As the Y coordinate values use all 8 possible values of a 3 bit field and the X coordinate does not, storing it in the least significant bits reduces gaps in the indexing.

Of particular note, bit 2 (the side flag) in a position index is part of the Y coordinate and indicates which player’s side of the board the space is located in. By extension, bit 2 in a piece ID indicates which player that piece belongs to.

The headquarters spaces occupy the space of two grid cells. Hence, the actual data is stored in the left-hand position and the right-hand position is deemed invalid. Handling this special case is a consideration throughout all the board handling code.

Taking Turns

For several reasons, it makes sense to design the game so that the current player’s headquarters always appears at the bottom of the screen with their pieces advancing upwards. This means that piece type icons, which except for the end game reveal are only shown for the current player, can point forwards without needing different versions for the top and bottom sides.

Graphics aside however, the most compelling reason is to simplify the game logic. It means that i) the current player’s home spaces can be identified by their having the side flag (bit 2) set in their position index, and ii) the current player’s pieces can be identified by their having the side flag set in their piece IDs. Hard-coding this, rather than comparing against a variable or having to branch into different code depending on the side of the board, saves instructions and clock cycles in many different routines.

Having made this commitment, we need a routine which can swap the positions and IDs of all the pieces on the board between the two sides so that both players can take turns. More specifically, we need to virtually rotate the board by 180 degrees so that we’re sitting on the other side of it. Fortunately, this isn’t as difficult as it sounds.

A 180 degree rotation like this is equivalent to flipping the board both horizontally and vertically. Even better, this flipping can be accomplished using a single subtraction. We need to subtract the Y coordinate in bits 0-2 of the position index from the maximum Y value, 7, in order to flip vertically. Subtracting a value in the range 0 to 7 from 7 will never underflow carry into the next most significant bit (bit 3). Hence, we can in parallel also subtract the X coordinate in the next 3 bits from the maximum X value, 5, in order to flip horizontally.

The only caveat to this simple operation is the two invalid spaces created by the double-sized headquarters. The headquarters is stored in the left grid cell and the one to the right of it is invalid. If we naively rotate the board then these positions will be swapped when rotated. Hence, the swap_sides routine in board_main.asm checks for these positions and swaps the columns back by flipping the last significant bit of the X coordinate.

Finally, the red and blue player colours are swapped in the palette so that the pieces display correctly with their inverted status.

The Heat of Battle

Moving on to the main phase of the game post setup, we need to be able to move the pieces and to determine whether a given move is legal. Even better, for the benefit of the player, we would like to enumerate the legal moves for display.

The user interface allows the player to select a piece for moving. At this point, the game determines the legal destinations for that piece and marks them in a position indexed array, V_board_marks. The board rendering code then uses this information to draw a little sprite on top of each marked space for the benefit of the player.

Before marking, the array is first cleared back to zero. The piece’s maximum movement in each direction is read from a table according to its type. Hence, a routine starts from the piece’s position and walks away from it in different directions marking as appropriate.

There are separate routines in board_marks.asm , mark_left_right and mark_up_down, which handle marking along the X and Y axes. The X axis routine is simpler as it only has to deal with the boundary condition and stepping over the invalid space next to each headquarters. Movement along the Y axis is more complex because it has to deal with both the irregular shape of the headquarters and the different bridge configurations. It uses two lookup tables, V_map_bridge_up and V_map_bridge_down. For each position on the board, these tables indicate the position index of the adjacent space in the up or down direction:

76543210
Blocked flag Double flag X coord Y coord

If there is no adjacent space because movement is blocked by the river or the edge of the board then the blocked flag (bit 7) is set. If there are two adjacent spaces because we are leaving the headquarters or crossing a bridge then the position of the left space is encoded and the double flag (bit 6) indicates that we can also move into the space to the right of it.

There is a separate routine again, mark_column, for handling the plane’s movement as it alone ignores the bridges and can fly over the river and other pieces.

Once the player selects a legal destination to move to, the piece animation code developed earlier in this series is invoked to show the piece moving. Afterwards, the outcome of the movement needs to be resolved by comparing the types of the pieces involved. This is done by a routine which checks for special cases and then falls back to a numeric comparison of the piece type in the general case. The moving piece is removed from its original position by writing 0xFF into the V_board_pieces array. If successful, its piece ID is written into the new position. If unsuccessful, it vanishes.

Finally, once the player finishes their turn, we need to check if they have won the game. There are two win conditions. The first is met if a sufficiently high ranking piece has occupied the opponent’s headquarters space and this is fairly straightforward to check. The second is met if the other player is unable to move on their turn.

This latter case usually happens when a player has no mobile pieces left, but it can conceivably happen if they have boxed in one of their mobile pieces with immobile ones during setup. We must therefore iterate through all the opposing pieces and use the same marking code as above to determine if they have any legal moves. If the array of marks is still all zero at the end of the process then the second win condition is invoked.

First Stab: Save Games

Screenshot of the save game screen in JMC 0.1.0

As I started to develop more of the game, I ran into the problem that it was taking a long time to get through setting up the board in order to test things like piece movement let alone win detection. The logical solution seemed to be the ability to load and save games.

I implemented a little text editing widget in the menu for entering file names. It would be better to have a browser to select files for loading, but this will suffice for now.

There are two types of game data which need loading and saving. There are game state variables stored in zero page RAM, while the larger board arrays are stored in language workspace RAM. It would be nice to write some kind of file header and maybe a checksum too, but that can be added later.

The menu module now assumes responsibility for initialising this game data before passing control to the main application. Loading a save game versus starting a new game is therefore simply a matter of where the data comes from.

The game already uses OSFILE $FF to load code and data from disk. Its counterpart, OSFILE $00, similarly supports saving data to a file. However, it’s limited to transferring a single memory range directly to or from a named file, and as above the game state is not stored in a contiguous range. I considered copying the zero page variables to form a contiguous area, but it seemed rather ugly and inflexible. So, I decided instead to bite the bullet and use proper file handles and multiple read/write calls instead of OSFILE.

The OSFIND entry point provides for opening and closing files handles. OSGBPB then allows you to read or write a range of bytes to or from a handle. At present, there is a great deal of symmetry between these two operations. The load_save routine in menu_disk.asm implements both loading and saving, making the same MOS calls with only a couple of parameters varied according to a lookup table.

Emergency Break

One of the trickier things about file I/O is error handling. While OSFIND $Bx (open) and OSGBPB do define ways of returning errors to the caller, this is only used in some circumstances (e.g. illegal filename, read past end of file). Other errors (e.g. disk error) are signalled instead by breaking, which normally aborts the running program.

If we want to provide a friendly user interface in the face of I/O errors, we need to install a break handler by overwriting BRKV. Actually, it was an oversight of mine to have not already installed a break handler as this is a precondition for using the language workspace RAM. The loader now installs a trivial break handler which simply loops indefinitely. This will be triggered if any errors occur while loading game components and cause the game to hang while the “Loading…” message is displayed.

While loading or saving a game, this trivial handler is temporarily replaced by a special one. It start by printing the details of any error and pausing for the user to press a key. However, having literally broken the flow of control we can’t trivially return to the subroutine in which the error occurred. We have to reset the stack2, restore the original break handler, and then carefully reenter the right place in the menu code so that the user can try again.

Second Stab: Random Numbers

Once I had finally implemented the save game system and got it working, I realised that it wasn’t actually as useful for solving my testing problems as I had imagined. Extracting saved games from the disk images and reinserting them into new builds of the game proved to be fiddly. Plus, I had to regenerate the saved games whenever I made any changes breaking changes to the game’s in-memory representation.

My next solution was to implement a mode where the game sets up the board for you by placing the pieces randomly. For the random number generator, I adopted Brad Smith’s 32-bit LFSR-based PRNG for the 6502 , requiring only minor adaptation for the syntax of BeebAsm. I didn’t think performance was likely to be an issue so I used the smallest version which takes ~200 cycles per output byte.

Before we can use the generator however, we first need to seed it otherwise it will produce the same setup every time the game is run. A common approach, if you have one, is to use the time on the real-time clock as a seed, but only later models in the BBC Micro range like the Master were so equipped out of the box. What the MOS does provide is a count of how many hundredths of a second have elapsed since the machine was reset via OSWORD $01 . You might imagine that on a real system, for example, variation in the exact time required to operate the drive mechanism will cause this value to vary. Emulators are not so generous in providing entropy, but there is one thing we can universally rely on to provide some unpredictability we can harvest for our seed, the user.

It takes 4 key presses to navigate the new game menu, enable random setup, and start the game. Even the most precise human cannot fail to vary the timestamp of each key press. Hence, I sample the timer at the top of the menu loop and seed the generator. The two least significant bytes are used plus for extra measure, I also count the number of iterations of a busy loop before the timer value changes to incorporate some sub-centisecond variation.

Each byte is EORed3 into the start of the shift register and shifted along by running the generator. We must carefully check that this never results in the seed becoming entirely zero as it will only output zeros in this state.

According to Plan

The actual placement of the pieces is performed by the random_setup routine in menu_setup.asm . This works by expanding the piece quantities table into separate array which lists the types of the 23 pieces to be placed. The routine iterates through all the current player’s home spaces and randomly picks an element from the array to place. Once placed, that element is removed by moving the last element of the array into that position and reducing the length by one.

The random number generator produces an 8-bit output value each time it is called. We only need values in the range 0 to 22, so this is masked down to 5-bits. If the output number is higher than the index of the last element then we loop and get a new value. This method of obtaining a random number below a certain limit is unbiased, but I was worried that it might be prohibitively slow when the list became short. However, my fears proved to be unfounded.

Likewise, if the piece isn’t legal to place in the current position then loop and pick another one. This requires some consideration of the order in which we fill the spaces so as to avoid being left with only pieces that aren’t legal to place in the remaining spaces. Iterating through the spaces by their position index would finish on the front row, which potentially has a prohibition placing mines, or on the back row, where placing the banner is prohibited.

Hence, with these constraints, the random setup routine uses a table-driven plan to control what can be placed where and in what order it’s done. It reads successive bytes from the table and interprets them in one of two ways:

76543210
Place 0 Unused X coord Y coord
Control 1 Unused End flag Reset flag Side bit Allow banner Allow mines

Bytes with the control flag (bit 7) set configure the routine for subsequent placements. The routine uses an array of flags to check whether a piece type is legal to place. The allow flags (bits 0 and 1) are used to adjust the flags for placing mines and the banner. If the reset flag (bit 3) is set then the list of pieces to place is initialised for the player indicated by bit 2. Finally, if the end flag (bit 4) is set then it marks the end of the table.

Bytes without the control flag set indicate a position index for placement. The plan is arranged so the first and last rows are placed first with the relevant restrictions, and then the middle rows are placed afterwards where there are no restrictions and we can guarantee it won’t run out of legal options.

Note that this routine places all the pieces for both players one after another. If the swap_sides routine was available we could half the size of the plan plus make other simplifications and just run through it twice to handle both sides. The swapping routine isn’t currently available in the menu module where the setup takes place, but it’s something to consider when, as I think is likely to be needed, the code is further broken up into loadable units.

Third Stab: Debug Mode

Random setup was a boon to testing, but sometimes I wanted something a little bit more flexible. Selectable as an alternative to hot-seat in the New Game menu, enter Debug Mode:

Debug Mode disables several of the normal game rules checks. It allows you to complete the setup without placing all your pieces. It allows you to make illegal moves or an illegal number of moves. It also allows you to see the types of the opposing player’s pieces for the ultimate in debugging convenience.

Demo in the Hot-seat

Screenshot of piece movement in JMC 0.1.0

I set my goal a little high in aiming for a playable game this time, both in the time it’s taken to implement it and doubly so in the time it’s taken to write it up. This post is roughly 50% longer than its predecessor! Still, here we are in the end:

The demo is now fully playable for two players hot-seat4. The controls are hopefully not too difficult to intuit (feedback welcome), and a legend of the available keys is printed on the side of each screen.

I’m quite pleased with the way the setup controls work. You can keep on pressing P or the space bar while moving the board cursor and the piece type cursor will automatically advance as the quantity of each type is depleted. Alternatively, you can press S or Return to switch between the board and moving the piece type cursor directly.

Once you’ve finished setting up (if you didn’t pick a random one), press E or F1 to end your turn. Likewise, once you’ve moved a piece during the game, press E or F1. In hot-seat mode, there’s an interstitial screen between turns to protect the secrecy of your pieces while changing over.

For the next part in the series I will aim to write and code less ;-). It will focus on some enhancements to the user interface.

Download JMC 0.1.0 disk image

Play JMC 0.1.0 with JsBeeb (Single Processor)

Play JMC 0.1.0 with JsBeeb (Second Processor)

Browse source code of JMC 0.1.0

⇨ Continue to part 5


  1. I wanted to call this a palette of pieces, but I’ve adopted the tray nomenclature to avoid confusion with the BBC Micro’s colour palette. ↩︎

  2. Otherwise a sufficient number of breaks would cause the stack to overflow. ↩︎

  3. Exclusive OR. Old school without a capital X. ↩︎

  4. Two players taking turns on a single computer. ↩︎