JMC: Player Aids - Part 5

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

Player Aids

Abstract strategy games are not actually my forte. My twin weaknesses are thinking several moves ahead and keeping track of a lot of hidden information, so I’m probably not going to be a Grandmaster at Japanese Military Chess. What I can do, however, is paper over some of this with a nice friendly user interface.

Gathering Intelligence

Mysterious Enemy Pieces

Staring at the board you see an array of enemy pieces, identical apart from the grid references which identify their starting positions. Any one of them could be a landmine or an elite combat unit. You must take your move, but with fingers poised over the keyboard you ponder, move what and to where?

At the beginning of a game you don’t know anything about the identity of your opponent’s pieces. However, over the course of play their movements and the outcomes of various battles will reveal some information about them. In a physical game, it is up to the player to either commit this to memory or take notes, but our computer implementation can offer an automated aid to memory.

Using some simple rules, we can keep track for each piece, which of the 16 different types have been eliminated based on observed play. There are three sources of information for this logic:

  • Start Position – Not all piece types can be placed in any position. Banners cannot be placed on the back row and mines cannot block the bridge entrances at the front.
  • Movement – Whenever a piece moves, the possibility of it being a mine or a banner is eliminated as those pieces are not mobile. Likewise, if a piece moves more than one space then that eliminates the majority of piece types which don’t have this capability.
  • Combat – When a piece attacks another or is itself attacked, the outcome of the battle is resolved and displayed. Working backwards from the table of combat outcomes, this eliminates piece types which could not have produced that outcome.

Note that when it comes to combat, eliminating potential piece types from one player’s pieces depends on knowing the types of the other player’s. The resulting intelligence gathered is therefore from the perspective of that other player. Hence, you can only view the intelligence gathered on your opponent’s pieces. Viewing it on your own would leak information about your opponent’s outside the rules of the game.

As there are 16 different types of piece, we can store the information for a piece in the bits of a 16-bit word. A 1-bit if the piece type is possible and a 0-bit if it has been eliminated. The flags words for each piece are stored in two arrays, V_piece_flags_lo and V_piece_flags_hi, containing the low and high bytes respectively. This arrangement is more efficient for the 6502 to address than storing each pair of bytes together, as it avoids needing to shift the index to calculate an address.

The flags arrays are initialised by the new_game routine in the menu module to reflect the placement restrictions. Both the random and manual setup now use these flags to determine legal placement rather than their own bespoke checks. Actually, prior to this, the random placement routine conservatively never placed mines on the front row even if there wasn’t a bridge entrance as it ran before the bridge data was loaded. This loading has been moved earlier so that the V_map_bridge_up/down tables can be used to find the bridge entrances and hence determine the restriction on placing mines.

The majority of pieces will have their flags word initialised to 0xFFFF. During play, bits will be cleared as new information is revealed. The current intelligence on an opposing piece can be viewed by pressing ’I’ or F1. The display uses the same layout as the selection tray from the setup phrase and reuses a few of the routines. Piece types which have been eliminated are displayed in inverted monochrome.

Moving Hypothetically

Intelligence on a confirmed Plane

When a player moves a piece the resolve_move routine first updates the piece’s flags based on how it has moved. The same routines which were used to mark the legal moves of a piece before moving are now repurposed to test whether a move falls within the footprint of different types of piece.

Excluding the plane for the moment, the ground moving pieces can be arranged into three tiers of mobility. Only the engineer, cavalry, and tank can move more than one space in each direction. Specifically, the cavalry and tank are in the middle tier as they can move 2 spaces forward, and the engineer is in the top tier as it can move an unlimited amount in any direction.

By classifying which tier of footprint a move falls into, we can determine which piece types are possibilities. The process starts by clearing the marks array and then, for each tier, marking the piece type indicated by the ground_move_table_archetype array. The mark_piece_type_movement routine allows marking the current board position using an arbitrary piece type.

Each tier is a superset of the previous one, so the marks array doesn’t need to be cleared again. After each round of marking, we check if the target position has been marked and if so exit loop. Otherwise, the loop finishes once all the tiers have been tested.

The ground_move_table_hi/lo arrays contain the necessary masks to eliminate pieces for each tier.

Tier Index Archetypal Piece High Mask Low Mask
0 Spy %00111011 %11111111
1 Cavalry %00000010 %00000110
2 Engineer %00000000 %00000010
3 %00000000 %00000000

The plane’s movement footprint has to be handled separately because it doesn’t fit into this hierarchy. It isn’t a superset of any other tier because it’s neither restricted by nor able to take advantage of bridges. The bit representing the plane in the high mask is set afterwards based on an independent test of the plane’s footprint.

Finally, the mask of possible pieces is stored in Z_piece_flags_hi/lo, and the update_piece_flags routine takes the logical-and of the current piece flags and the mask and stores the results back in the V_piece_flags_hi/lo arrays.

Table-driven Combat

If a piece is moved onto a space occupied by one of the other player’s pieces then combat will ensue. The resolve_move routine will first determine the outcome and then also update both pieces’ flags accordingly. This routine originally used a series of branching tests to decide which outcome occurred: attacker wins, defender wins, or a pyrrhic1 victory for both of them. Numerically higher piece types usually defeat lower ones, notwithstanding checks for all the special cases.

However, it made sense to switch to a table-driven system which can be used both for resolving the outcome and updating the flags. Performing the flags update one bit at a time with branching decisions would be very slow, but we can update them in parallel using bitwise logic driven by a table.

The combat_table_hi/lo arrays allow looking up, given the type of the attacking piece as an index, the types of piece which it can defeat in the form of a flags word. Some additional logic is still required to handle the cases where both pieces are destroyed (mines and most pairs two of identical type), but most combat outcomes are a simple table lookup.

Index High Flags Low Flags Comments
0 %10100000 %00000000 Spy can defeat a General, but not another Spy
1 %11000000 %00000011 Engineer can defeat mines
2 %10000000 %00000111
3 %10000000 %00001111
4 %10000000 %00011111
5 %10000000 %00111111
6 %10000000 %01111111
7 %10000000 %11111111
8 %10000001 %11111111
9 %10000011 %11111111
10 %11000111 %11111111 Plane can defeat Mines
11 %10001111 %11111111
12 %10011111 %11111111
13 %10111111 %11111110 General is defeated by a Spy
14 %11111011 %11111101 Mines are defeated by Engineers and Planes
15 %10000000 %00000000 Undefended Banners are defeated by everything.

The resolve_move routine loads the piece flags from the table according to the attacking piece type. We then need to check the bit according to the defending piece type. As the 6502 doesn’t have a barrel shifter, we do that using a second table stored in bit_table_lo and bit_table_hi. Each entry in this table has a single bit set and hence we can obtain a mask corresponding to the defending piece type.

By taking the logical-and of the flags from combat_table_hi/lo and bit_table_hi/lo arrays, we can test if the bit is set and the attacker won. However, pyrrhic outcomes have to be checked for separately:

  • If the bit is set, the victory is pyrrhic if the attacking and defending pieces have the same type. The spy is only piece with its own bit clear as it is defeated by itself.
  • If the bit is clear, the outcome is nonetheless pyrrhic if the defending piece is a mine. The engineer and plane have their mine bits set as they can defeat a mine without being destroyed themselves.

Serious Bit Twiddling

Intelligence on a 6+ piece

Once the outcome of a battle has been determined as above, the same combat table can be used to produce masks of possible piece types for both participants:

Combat Outcome Mask for Attacking Piece Mask for Defending Piece
Attacker table[def] ^ $efff table[att] ^ (1 << att)
Defender table[def] ^ (1 << def) table[att] ^ $efff
Pyrrhic ismine(def) ? table[def] : (1 << def) (table[att] & $c000) ^ $4000 ^ (1 << att)

The flags mask for each piece should2 be derived from the combat table entry of its opponent’s type. This is because the piece flags represent intelligence gathered from the perspective of the other player. Or, in other words, players can only use the known types of their own pieces to infer the possible types of their opponent’s. In the case of a simple victory or defeat:

  • For the flags mask of the victorious piece, we use the combat table entry of its defeated opponent. The bits must be inverted so that 0-bits representing defeat become 1-bits representing a possible type. However, the bit representing the banner type is not inverted, as combat can never eliminate the banner piece due to its special behaviour.
  • For the flags mask of the defeated piece, we use the combat table entry of its victorious opponent. The 1-bits representing victory are reinterpreted as representing a possible type. However, the combat table is encoded so that piece types with pyrrhic victories against the same type also have a bit set. The bit representing the opponent’s piece type is therefore inverted to eliminate this special-case from the set of possibilities.

In the case of a pyrrhic outcome, it’s a bit more complicated:

  • For the flags mask of the attacking piece, the defender’s combat table entry is used only if the defending piece is a mine. Otherwise, however, the attacker must have the same type as the defender, so the mask should only have that single bit set.
  • For the flags mask of the defending piece, the attacker’s combat table entry is used only for the banner and mine bits. The mine bit must be inverted so that 0-bits representing pyrrhic victories become 1-bits representing a possible type. In addition to this, the single bit representing the attacker’s type must also be set to allow for the possibility that the pyrrhic victory was caused by both pieces being the same type.

Once again, the masks are stored in Z_piece_flags_hi/lo and the update_piece_flags routine is called in turn to update the piece flags arrays and eliminate any piece types not set in the mask.

Visiting the Graveyard

Graveyard

When a piece loses a battle, it is removed from the board. The information about its existence, including any intelligence collected, is no longer accessible from the board. However, intelligence about a defeated piece can still shine some light on the identities of those still remaining in play. To avail the player of this information, we must enter the graveyard.

The game now stores a list of defeated pieces in the array V_dead_list and keeps a count of its length in Z_game_dead. Whenever a piece is defeated during play, its piece ID is appended to this list. Space is getting a bit tight in the page I reserved for board data so this overlaps with the V_piece_quantities array, which is only used during the setup phase.

The list is made visible to the player by addition of a “graveyard” mode. By pressing ‘G’ or F2, the game switches between the main board and view of the defeated pieces. Displaying the graveyard shares a lot of code with the existing main board view. Access to the arrays holding the board is relatively abstracted so that only a few routines then need to check the mode flag and alter their behaviour accordingly.

The decode_board_position routine normally detects the special headquarters spaces which cover two grid cells and the corresponding break in the linear numbering of positions due the invalid positions. The facilitates drawing the headquarters centred and adjusting the cursor navigation to skip over the invalid spaces. In graveyard mode, this routine is overridden to return nothing. Hence, the pieces appear in a completely regular grid and can be navigated as such.

The load_board_piece routine normally reads the ID and type of a piece from the V_board_pieces and V_piece_types arrays and sets up some variables in zero page. In graveyard mode, it instead reads from the V_dead_list array or returns an invalid piece if the position is beyond the limit given by Z_game_dead. This causes the graveyard board to fill up with defeated pieces in columns growing downwards.

The draw_board_piece routine also checks the flag to switch the symbol it draws for an empty space between a square on the main board to a crucifix in the graveyard. This provides another visual indicator of which mode the player is currently in. I had to rearrange some of the bridge sprites to make enough space to put the crucifix on the same page as the square. This allows switching between them just by changing the least significant byte.

Finally, the routines which selects pieces checks the flag to prevent any attempt being made to move pieces while graveyard mode is active.

The Animation of Battle

Combat animation

The animation of moving pieces was one of the first things I developed, long before the game was in any way playable. However, there was still one thing missing. When one piece moves on top of another to attack it, the battle is underwhelmingly resolved in a single frame when the screen suddenly displays the victor or a blank space if it was a pyrrhic victory.

To make these encounters more dramatic and visually interesting, I wanted some graphical indication of combat taking place to be displayed before the result was shown. My first thought was some kind of explosion, but how to draw it? I had thus far squeezed all the game’s artwork into just three pages of RAM with only a few bytes to spare. Decent hand-drawn explosions would easily push me over into needing another page.

Some kind of programmatic effect was needed then. I toyed with the idea of some kind of pixel automata or randomly generated pattern, but both of these seemed like they would require a lot of extra code. The way that pixels are packed into a byte makes it tricky to read and modify them individually.

It occurred to me then that, since a byte contains a strip of four horizontal pixels in MODE 4, you could produce effects consisting of repeating vertical stripes using relatively simple byte level processing. I decided that blending together the graphics of the two pieces using vertical stripes would be a fitting way of showing that they were engaged in battle.

This operation requires two buffers to hold images of the pieces involved. Fortunately, we already have the V_front_buffer and V_back_buffer used to hold the piece being drawn and the background it’s moving over, and these can be repurposed. The sprite drawing routine always targets the front buffer, so we draw the defending piece first and then copy it into the back buffer using the existing routine. The attacking piece can then be drawn into the front buffer.

I added a new graphics routine, blend_back_to_front in gfxlib_host.asm , which combines the bytes of the two buffers into the front one according to a bit mask stored in V_blend_mask. The mask adheres to the same pixel format as the display. A 1-bit in the mask causes it to take the corresponding bit value from each byte in the front buffer and a 0-bit to take it from the back buffer. Since high and low bits of the colour index are split across the high and low nibbles, the nibbles must be identical to select whole pixels and not mix the colours between them.

Piece A Piece B Frame 1 Frame 2 Frame 3 Frame 4
Piece A Piece B Frame 1 Frame 2 Frame 3 Frame 4

The mask is initially set to %00110011, which produces 2 pixel wide stripes taken from alternating buffers. The blending effect can be animated by changing the mask over time, which we will do by rotating it. Since the high and low nibbles are identical, rotating the byte is the same as rotating the nibbles. Hence, it will go through 4 distinct states before returning to the initial one. As this pattern changes, different portions of each piece become visible, creating the impression of them “fighting” before revealing the final outcome.

The 6502 has rotate instructions, but these go through the carry flag whereas we need to move bits from one end of the byte to the other immediately. This is easily accomplished by performing a left shift which moves the most-significant bit into the carry flag and then an add with carry of immediate zero to put the value back into the least-significant bit.

Graphics routines are special because they always run on the host processor even if the main program is running on the Tube parasite. Fortunately, there were still 31 bytes of space available in one of the existing graphics library code pages. Hence, an additional user OSWORD and some marshalling code to transport the call across the wire was also needed in gfxlib_tube_host.asm and gfxlib_tube_parasite.asm .

Action Replay

Previously, in a hot-seat game, players would never actually see their opponent’s pieces move, only the board state afterwards. The display of a piece moving was intertwined with the game logic that moved it, so if you missed it then it was gone. This has now been refactored so that you can watch the piece movement animation as many times as you like. In addition to displaying it at the start of a turn, pressing ‘P’ or F3 will trigger it whenever.

I added 5 new zero-page variables to hold the details of the last move: the source position and piece ID, the destination position and piece ID (if any), and the ID of the victorious piece (if any). These are included in the range which is zeroed by new_game, and which is saved to and loaded from save games. The resolve_move routine sets up these variables alongside updating the piece flags and the dead list but no longer modifies the board itself.

The new move_last_piece routine is now responsible for displaying animation. It uses the saved information to always reset the board to its pre-move state in case this is not the first time, then updates it to the post-move state once the animation is complete.

Memory Madness

To alleviate the ever lowering memory headroom, I’ve refactored the _APP overlay into three new separate overlays. The code for the setup (_SETP) and play (_PLAY) phases is now loaded at the same address on demand, giving each module more space to work when. The common code (_CMN) between them has now moved into a district memory area and remains permanently resident.

Furthermore, I’ve set aside 1 KB of memory out of the application range for a new set of overlays I’m calling plugins. Plugins contain code which is specific to different versus modes such as hot-seat now and computer and Econet opponents in the future.

Address Range Size Function Overlays
$2000-$26FF 1.75 KB Module _MENU, _SETP, _PLAY
$2700-$2AFF 1.00 KB Plugin _2H, _2R
$2B00-$30FF 1.50 KB Common _CMN
$3100-$37FF 1.75 KB Graphics _GH, _GTH, _GTP

A benefit of this change is that these common routines are now accessible to the menu module. As foreshadowed in a previous part of this series, swap_sides is a great utility routine that we could employ when setting up the board.

To recap, swap_sides transforms all the board positions and piece IDs in the game state so that the players swap which side of the board they’re on. This can simplify a lot of code by assuming that the current player is always on the bottom side and hence having to deal with fewer conditions.

Previously, in the menu module, the code which performs a random setup used a large table which described how to place pieces for both players. This has been replaced by a simpler routine which only places on the bottom side and is called twice. Likewise, there is a new routine in the menu module which initialises the piece flags. It only needs to check for restrictions from the perspective of the bottom side and then can be called twice.

Random Demo

After all the above, I felt a bit underwhelmed that you could still only play the game hot-seat. I’m certainly short of willing opponents ready to sit at my Beeb, and you probably are too. My plan for the next part in the series is to implement a real computer opponent with some degree of “intelligence”. This will be challenging, but is there something simpler we could do right now?

We already have the routines to determine if a move is legal. We already have a random number generator. Are you thinking what I’m thinking Pinky?

Volià! The Random opponent is now selectable from the Versus item in the New Game menu. Using the new Player item you can choose to play as red or blue, or select a special demo mode where both sides are played by the random code. This is a great way to get to and test the game over screen with less effort. The implementation in play_random.asm is quite brief.

Download JMC 0.2.0 disk image

Play JMC 0.2.0 with JsBeeb (Single Processor)

Play JMC 0.2.0 with JsBeeb (Second Processor)

Browse source code of JMC 0.2.0


  1. Both pieces are removed from play. ↩︎

  2. The combat table is mostly symmetric so sometimes I’ve found it can be hard to keep straight in my head which way round it should be. ↩︎