Chapter 5

Global Definitions

Board Representation

The first variables in defs.c contain the information that describes the current chess position. Together, they form the basic state of the board that the rest of the engine works with.

int board[128];
int king_square[2] = {E1, E8};
int side = WHITE;
int enpassant = NONE;
int castle = 15;

The most important variable is board. ABC uses a 128-element array because the engine represents the chessboard using the 0x88 board representation. Only 64 of those 128 positions correspond to actual chessboard squares; the remaining positions make it possible to detect off-board moves very efficiently.

Each valid entry in board contains a value representing the piece occupying that square. An empty square contains the EMPTY value, while pieces such as WP, WN, BR, or BQ identify the piece and its color.

The king_square array stores the current square of each king. Its two elements correspond to WHITE and BLACK, so the initial values E1 and E8 place the kings on their normal starting squares.

Keeping the king locations separately means the engine does not have to search through the entire board every time it needs to know where a king is. This is especially important when checking whether a king is in check or whether a generated move is legal.

The side variable stores whose turn it is to move. It starts as WHITE, because White makes the first move in the standard starting position. During play, this value switches between WHITE and BLACK after each move.

The enpassant variable stores the square where an en passant capture is currently possible. At the beginning of a game there is no such square, so it is initialized to NONE. When a pawn makes a two-square advance, the engine can update this variable so that the opponent can recognize the special capture on the following move.

Piece Encoding

Although the piece definitions themselves are located in abc.h rather than defs.c, this is a good place to explain why the pieces are encoded the way they are, since many of the lookup tables in defs.c depend on this representation.

enum types {
    NONE = -1,
    EMPTY,
    KING,
    PAWN,
    KNIGHT,
    BISHOP,
    ROOK,
    QUEEN
};

enum pieces {
    WK = 1, WP, WN, WB, WR, WQ,
    BK = 9, BP, BN, BB, BR, BQ
};

The piece values are not arbitrary. They are arranged so that a single integer contains both the piece type and the piece color.

White pieces: 1 - 6
Black pieces: 9 - 14

The lower three bits store the piece type:

piece & 7

This operation extracts values from 1 to 6, corresponding to:

1 = KING
2 = PAWN
3 = KNIGHT
4 = BISHOP
5 = ROOK
6 = QUEEN

For example:

WP = 2      0010
WN = 3      0011
WR = 5      0101

BP = 10     1010
BN = 11     1011
BR = 13     1101
10 & 7 = 2   → pawn
13 & 7 = 5   → rook

The fourth bit stores the color information:

piece >> 3

This produces:

White → 0
Black → 1

For example:

5 >> 3   = 0   → white
13 >> 3  = 1   → black

This encoding allows many operations to be expressed with very compact code. Instead of maintaining separate arrays for white and black pieces or writing long chains of comparisons, ABC can determine the type and color of a piece using simple bit operations.

This becomes particularly useful in move generation. A move generator often needs to ask questions such as:

What type of piece is this?
Which side does it belong to?
Which movement rules apply?

With this encoding, those answers can be obtained directly from a single integer value. This leads to concise and elegant move-generation code, which is one of the design goals of ABC.

The practical use of this representation will become more apparent later when we examine move generation.

Encoding Castling Rights

The castle variable stores all four castling rights inside a single integer using bit flags. The four rights are defined by the castling enum:

enum castling {
    WKC = 1,
    WQC = 2,
    BKC = 4,
    BQC = 8
};

Each value represents a different bit of the integer. Written in binary, the four flags are:

WKC = 1  = 0001
WQC = 2  = 0010
BKC = 4  = 0100
BQC = 8  = 1000

The names describe the four possible rights: White kingside, White queenside, Black kingside, and Black queenside. Because each flag uses a different bit, several rights can be stored in castle at the same time.

At the beginning of a normal game, castle is initialized to 15:

castle = 15;
            

In binary, 15 is 1111, so all four castling rights are enabled:

1111
||||
|||+-- WKC = 1
||+--- WQC = 2
|+---- BKC = 4
+----- BQC = 8

This is equivalent to combining the four flags:

WKC | WQC | BKC | BQC
  1 |   2 |   4 |   8 = 15

The important idea is that each bit can be examined independently. If the WKC bit is set, White still has the right to castle kingside. If it is not set, that right has been lost. The same applies to the other three flags.

ABC also defines a convenient mapping between a side and its two castling flags:

int castling_side[2][2] = {{1, 2}, {4, 8}};

The first dimension represents the side, while the second represents the castling direction. For White, the two values are 1 and 2, corresponding to WKC and WQC. For Black, they are 4 and 8, corresponding to BKC and BQC.

castling_side[WHITE][0] = WKC = 1
castling_side[WHITE][1] = WQC = 2

castling_side[BLACK][0] = BKC = 4
castling_side[BLACK][1] = BQC = 8

This allows the engine to refer to the appropriate castling rights through the side and direction instead of having to handle all four possibilities separately.

The second important piece of castling data is the castling_rights array:

int castling_rights[128] = {
     7, 15, 15, 15,  3, 15, 15, 11,  NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE,
    15, 15, 15, 15, 15, 15, 15, 15,  NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE,
    15, 15, 15, 15, 15, 15, 15, 15,  NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE,
    15, 15, 15, 15, 15, 15, 15, 15,  NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE,
    15, 15, 15, 15, 15, 15, 15, 15,  NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE,
    15, 15, 15, 15, 15, 15, 15, 15,  NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE,
    15, 15, 15, 15, 15, 15, 15, 15,  NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE,
    13, 15, 15, 15, 12, 15, 15, 14,  NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE
};

This table associates each square of the 0x88 board with a castling-rights mask. Most squares have the value 15, meaning that moving a piece from that square does not remove any castling right. The special values occur on the king and rook starting squares, because moving one of those pieces can permanently remove a castling right.

For example, the starting squares on the first rank have the important values:

A1 = 13
E1 = 3
H1 = 14

A8 = 7
E8 = 12
H8 = 11

These numbers are themselves combinations of the same four castling bits. For example, E1 = 3 is binary 0011, which contains both White castling flags. This makes sense because moving the White king from E1 removes both kingside and queenside castling rights.

Likewise, H1 = 14 is 1110. The WKC bit is cleared, while the other three rights remain available. Moving the rook from H1 therefore removes only White's kingside castling right.

The same principle applies to A1, E8, A8, and H8. Each of these squares has a mask designed to remove exactly the castling rights affected by moving a king or rook from that starting square.

The table therefore gives ABC a compact way to update castling rights whenever a move involves a particular square. Instead of writing separate rules for every king and rook starting square, the engine can use the mask associated with the square and combine it with the current castle value.

The complete use of these masks, including how castling moves are generated and how the rights are updated when moves are made, will appear later when we examine move generation and move making.

Algebraic Square Names

The square_to_coords array associates every index of the 128-element 0x88 board with a coordinate string:

char *square_to_coords[] = {
    "a8", "b8", "c8", "d8", "e8", "f8", "g8", "h8", "i8", ...
};

The first eight entries correspond to the first rank of the 0x88 representation, the next eight to the next rank, and so on. The extra entries such as i8, j8, and beyond are not real chessboard squares. They exist because the 0x88 representation uses sixteen positions per row, even though only the first eight are valid chess squares.

This means the array index can be used directly to obtain a printable coordinate. For example, the internal square value for E1 can be used as an index into square_to_coords to obtain the string "e1".

This is particularly useful when ABC needs to convert its internal representation into text, such as when printing a move or communicating a move through the UCI protocol.

Converting FEN Characters to Pieces

The char_pieces array performs the opposite kind of translation. FEN represents pieces using characters such as P for a white pawn and q for a black queen. ABC represents those pieces internally using the piece constants defined in abc.h.

int char_pieces[] = {
    ['P'] = WP, ['N'] = WN, ['B'] = WB, ['R'] = WR, ['Q'] = WQ, ['K'] = WK,
    ['p'] = BP, ['n'] = BN, ['b'] = BB, ['r'] = BR, ['q'] = BQ, ['k'] = BK
};

This is a designated-initializer array. Instead of assigning values sequentially, each entry is associated directly with the character that indexes it. Therefore, char_pieces['P'] gives WP, while char_pieces['q'] gives BQ.

This makes FEN parsing simple. When the engine encounters a piece character in a FEN string, it can use that character directly as an index and obtain the corresponding internal piece value.

Promoted Pieces

The promoted_pieces array provides another lookup table, this time for converting a promoted piece value into its corresponding piece character:

int promoted_pieces[] = {
    [WQ] = 'q', [WR] = 'r', [WB] = 'b', [WN] = 'n',
    [BQ] = 'q', [BR] = 'r', [BB] = 'b', [BN] = 'n'
};

The array is used specifically for promotion information. When a pawn promotes, the engine needs to know whether the resulting piece is a queen, rook, bishop, or knight so that the move can be represented correctly.

Notice that both white and black promoted pieces map to the same lowercase character. The color is already known from the piece value itself, while this table only needs to identify the type of promoted piece.

The values therefore provide a compact conversion from the engine's internal piece representation to the character used when describing a promotion.

ASCII Piece Representation

Finally, ascii_pieces provides characters for displaying pieces on the console:

char ascii_pieces[] = ".KPNBRQ--kpnbrq";

The index into this string corresponds to the piece value used internally by ABC. The first character, ., represents an empty square. The uppercase characters represent the white pieces, while the lowercase characters represent the black pieces.

. K P N B R Q - - k p n b r q
0 1 2 3 4 5 6 7 8 9 A B C D E

The two hyphens occupy the unused piece values between the white and black piece ranges. This keeps the character at each position aligned with the numerical piece constant.

These lookup tables illustrate an important pattern used throughout ABC: the engine can keep its internal representation compact and efficient while using small tables whenever it needs to translate those values into a human-readable form. The board uses integer indices and piece codes internally, while functions that communicate with the outside world can quickly convert those values into coordinates, FEN characters, promotion characters, or ASCII board pieces.

Piece Movement Offsets

With the board representation and piece values defined, ABC also needs to know how each type of piece can move. Instead of writing the movement directions separately inside the move-generation code, the engine stores them in a lookup table called move_offsets.

int move_offsets[7][8] = {
    { 0 }, { -16, -1, 16, 1, -17, -15, 17, 15 },
    { 0 }, { -33, -31, 33, 31, -18, -14, 18, 14 },
    { -17, -15, 17, 15}, {-16, -1, 16, 1 },
    { -16, -1, 16, 1, -17, -15, 17, 15 }
};

These numbers are offsets in the 0x88 board representation. Moving from one square to another does not require calculating a new coordinate; the engine can simply add an offset to the current square index.

For example, on a 0x88 board, moving one square vertically changes the index by 16, while moving one square horizontally changes it by 1. Diagonal movement uses combinations such as 17 and 15.

-16   up
 16   down
 -1   left
  1   right
-17   up-left
-15   up-right
 17   down-right
 15   down-left

The table contains one row for each internal piece type used by ABC. Pawns are skipped, while sliding and leaping pieces receive the offsets appropriate for their movement patterns.

The knight, for example, uses offsets such as -33, -31, 33, 31, -18, -14, 18, and 14. These represent the eight possible destinations of a knight from a given square.

Bishops use the four diagonal offsets, while rooks use the four horizontal and vertical offsets. Queens combine both sets, giving them all eight directions. The exact way these offsets are used to walk through the board will appear later when we examine move generation.

Number of Movement Directions

The companion offset_length array tells the engine how many offsets are valid for each piece:

int offset_length[7] = { 0, 8, 0, 8, 4, 4, 8 };

This is important because the rows of move_offsets all have room for eight values, but not every piece uses all eight. A bishop needs only four directions, a rook needs four, while a knight and queen use eight.

The two arrays therefore work together. move_offsets provides the possible movement directions, while offset_length tells the engine how many of those entries it should examine for the current piece.

This is another example of ABC keeping its rules in compact data tables. The move-generation code does not need a separate hard-coded list of directions for every piece. It can look up the appropriate offsets and the number of valid offsets, then apply the same general mechanism to different piece types.

Pawn Ranks

Pawns are different from the other pieces because their movement depends on their position on the board. In particular, a pawn has a starting rank where it can make its initial two-square advance, and a promoting rank where it can be transformed into another piece.

int pawn_starting_rank[] = {0x60, 0x10};
int pawn_promoting_rank[] = {0x00, 0x70};

These arrays store the relevant 0x88 board boundaries for each side. The first element corresponds to WHITE, while the second corresponds to BLACK.

For the starting ranks, White's value is 0x60 and Black's is 0x10. In the 0x88 representation, these values identify the ranks from which each side's pawns begin. This allows the move-generation code to determine whether a pawn is still on its starting rank and can therefore consider its initial two-square move.

An important property of the 0x88 representation is that an entire rank occupies exactly 16 consecutive array positions. The lower four bits represent the file, while the upper bits identify the rank:

a8 = 0x00    h8 = 0x07
a7 = 0x10    h7 = 0x17
a6 = 0x20    h6 = 0x27
...
a2 = 0x60    h2 = 0x67
a1 = 0x70    h1 = 0x77

This makes a rank especially easy to identify. The first square of a rank is always aligned to a multiple of 0x10, and every valid chess square on that rank lies between that value and seven positions later.

This is why 0x60 represents White's second rank and 0x10 represents Black's seventh rank. They are the starting positions of the two pawn armies, and the rest of each rank can be reached simply by adding the file offset from 0 through 7.

The same property makes the promotion ranks equally convenient:

WHITE = 0x00    a8
BLACK = 0x70    a1

White pawns promote when they reach the eighth rank, whose first square is 0x00. Black pawns promote when they reach the first rank, whose first square is 0x70. Because an entire rank occupies one 16-position block, the engine can identify these ranks using a single boundary value rather than storing all eight squares individually.

Keeping these values in arrays indexed by side lets the engine use the same general logic for both colors. Instead of hard-coding separate rank checks for White and Black, the appropriate boundary can be selected using the current side.

These small tables provide the move generator with the positional information it needs for the two special stages of a pawn's journey: its initial move and its promotion. The actual use of these values will appear later when we examine pawn move generation and promotion handling.

Random State and Piece Keys

ABC also reserves a small amount of global data for its random-number state and piece keys:

int random_state = 12345;

int piece_keys[7][128];

The random_state variable provides the initial state for the engine's pseudo-random number generation. The piece_keys table stores a separate key for each piece type and board square.

These keys are used to represent board positions with compact numerical values. The table therefore has dimensions for both the piece type and its square on the 0x88 board.

The complete purpose of these keys, and how ABC uses them to build and maintain its position hashes, will be explained later when we examine hashing and 3-fold repetition detection.

Nodes Searched

ABC keeps a counter for the number of search nodes examined during a search:

long nodes = 0;

The nodes variable is incremented as the engine searches positions. It provides a simple measure of how much work the search has performed and is also useful when reporting search information such as the number of nodes searched during a negamax search.

The counter is reset when a new search begins and grows throughout the search. Unlike an evaluation score, it does not describe the quality of a position; it simply measures the amount of search work performed.

Most Valuable Victim, Least Valuable Attacker

When ABC generates moves, it does not search every move in an arbitrary order. The order in which moves are searched has a major effect on the efficiency of alpha-beta search. If strong moves are examined first, the search can discover good bounds earlier and prune more of the remaining tree.

One of the simplest move-ordering techniques used by ABC is MVV-LVA, which stands for Most Valuable Victim, Least Valuable Attacker.

int mvv_lva[15][15] = {
     0,   0,   0,   0,   0,   0,   0,  0,  0,  0,   0,   0,   0,   0,   0,
     0, 600, 100, 200, 300, 400, 500,  0,  0,  600, 100, 200, 300, 400, 500,
     0, 605, 105, 205, 305, 405, 505,  0,  0,  605, 105, 205, 305, 405, 505,
     0, 604, 104, 204, 304, 404, 504,  0,  0,  604, 104, 204, 304, 404, 504,
     0, 603, 103, 203, 303, 403, 503,  0,  0,  603, 103, 203, 303, 403, 503,
     0, 602, 102, 202, 302, 402, 502,  0, 0, 602, 102, 202, 302, 402, 502,
     0, 601, 101, 201, 301, 401, 501,  0,  0,  601, 101, 201, 301, 401, 501,
     0,   0,   0,   0,   0,   0,   0,  0,  0,    0,   0,   0,   0,   0,   0,
     0,   0,   0,   0,   0,   0,   0,  0,   0,    0,   0,   0,   0,   0,   0,
     0, 600, 100, 200, 300, 400, 500,  0,  0,  600, 100, 200, 300, 400, 500,
     0, 605, 105, 205, 305, 405, 505,  0,  0,  605, 105, 205, 305, 405, 505,
     0, 604, 104, 204, 304, 404, 504,  0,  0, 604, 104, 204, 304, 404, 504,
     0, 603, 103, 203, 303, 403, 503,  0,  0, 603, 103, 203, 303, 403, 503,
     0, 602, 102, 202, 302, 402, 502,  0,  0, 602, 102, 202, 302, 402, 502,
     0, 601, 101, 201, 301, 401, 501,  0,  0, 601, 101, 201, 301, 401, 501
};

The table is indexed by two piece values: the attacking piece and the captured piece. The resulting number is a score that can be used to rank captures during move ordering.

The basic idea is simple: capturing a more valuable piece should generally be considered more promising, while capturing it with a less valuable attacker is especially attractive. For example, a pawn capturing a queen should be searched before a queen capturing a pawn.

The numbers in the table are deliberately arranged so that the value of the captured piece has the largest influence. The basic capture values are:

pawn   = 100
knight = 200
bishop = 300
rook   = 400
queen  = 500
king   = 600

The attacker then provides a small adjustment. A lower-valued attacking piece receives a slightly higher score than a higher-valued attacking piece. This produces the least valuable attacker part of MVV-LVA.

For example, consider two captures of a queen:

pawn captures queen   → 600 + 5 = 605
knight captures queen  → 600 + 4 = 604
bishop captures queen  → 600 + 3 = 603
rook captures queen    → 600 + 2 = 602
queen captures queen   → 600 + 1 = 601

Therefore, all of these captures target the same valuable victim, but the pawn capture receives the highest score because the pawn is the least valuable attacker.

The same pattern appears for every other victim. Capturing a rook starts with 400, capturing a bishop starts with 300, and so on. The small attacker bonus then determines the ordering between captures of the same victim.

The table contains 15 × 15 entries because ABC's piece representation provides enough numerical values for the empty square, the white pieces, unused values, and the black pieces. The duplicated-looking sections correspond to the white and black piece ranges. The entries that do not represent meaningful captures are simply 0.

The important point is that MVV-LVA does not determine whether a capture is good or bad. It is only a move-ordering heuristic. A pawn taking a queen may receive a very high MVV-LVA score, but the resulting position still has to be searched to determine whether the capture is actually sound.

Its real value comes from alpha-beta search. By trying promising captures first, ABC has a better chance of finding strong moves early and producing cutoffs that prevent the engine from searching unnecessary branches.

The table therefore acts as a small precomputed scoring system for one of the simplest and most useful move-ordering heuristics in a chess engine.

Move Ordering Heuristics

After MVV-LVA, ABC uses two more pieces of global data to improve move ordering: killer moves and history moves.

The killer_moves table stores moves that caused a beta cutoff during the search:

int killer_moves[2][64];

A beta cutoff occurs when the engine discovers that a move is already good enough to exceed the current search window. Once this happens, there is no reason to examine the remaining moves in that position.

The important observation is that a move that produces a cutoff in one position can sometimes be a strong candidate in other positions during the search, even when it is not a capture. ABC therefore remembers these moves and can try them earlier when ordering moves.

The first dimension provides two killer-move slots. This allows ABC to remember two useful cutoff moves for a given search position. The second dimension represents the maximum search ply that ABC supports.

Killer moves are therefore not chosen because of the material value of a capture. Unlike MVV-LVA, they are based on what actually happened during the search: a move caused a cutoff, so ABC gives it a higher priority when ordering moves later.

The second table is used for the history heuristic:

int history_moves[15][128];

While killer moves remember a small number of specific moves, the history table records a broader history of how successful moves have been during the search. Its dimensions correspond to the engine's piece values and the 128 entries of the 0x88 board representation.

This allows ABC to associate a history score with a particular piece moving to a particular square. When a quiet move repeatedly proves useful during the search, its history score can become higher. When ABC later encounters the same piece-to-square move, that accumulated score can be used to give the move a higher position in the move list.

The history heuristic therefore provides a broader form of memory than killer moves. Killer moves answer the question “Which moves recently caused cutoffs?”, while history scores answer something closer to “Which piece-to-square moves have generally been successful during the search?”

Together, these two tables complement MVV-LVA. Captures can be ordered using information about the attacking and captured pieces, while quiet moves can be prioritized using information gathered from previous parts of the search.

The complete way ABC combines killer moves, history scores, MVV-LVA, and the other move-ordering information will appear later when we examine move ordering.

Principal Variation

ABC also stores the principal variation found during the search:

int pv_table[64][64];
int pv_length[64];

The value 64 represents the maximum search ply that ABC supports. For each ply, pv_table stores the sequence of moves that currently forms the best line found by the search, while pv_length records how many moves are present in that line.

The principal variation is the line of play the engine considers best from the current position. ABC uses this information to display the search result and its predicted sequence of moves through the UCI interface.

The details of how the principal variation is built and updated during alpha-beta search will appear later when we examine the search algorithm.

Position Repetitions

ABC also keeps a table for positions that have occurred during the current game:

int repetition_table[1000];
int repetition_index;

The repetition_table stores the position hashes encountered during the game, while repetition_index keeps track of the current position in that table.

ABC uses this information to detect repeated positions and determine whether the threefold repetition rule can apply.

Half-Move Counter

ABC also keeps track of the current search ply:

int ply = 0;

The ply variable represents the current depth within the search tree. It starts at 0 at the root position and increases as the engine searches deeper into subsequent positions.

ABC uses this value to access data associated with the current search level, such as principal variation information and move-ordering heuristics.

Game Phase and Material Scores

ABC also defines thresholds and material values used to distinguish different stages of the game:

int opening_phase_score = 6192;
int endgame_phase_score = 518;

int material_score[2][15] = {
    0, 12000, 82, 337, 365, 477, 1025, 0, 0, -12000, -82, -337, -365, -477, -1025,
    0, 12000, 94, 281, 297, 512,  936, 0, 0, -12000, -94, -281, -297, -512,  -936
};

The opening_phase_score and endgame_phase_score values define the boundaries used by ABC to classify a position as an opening, middlegame, or endgame.

The material_score table contains separate piece values for the opening and endgame. This allows ABC to give pieces slightly different values depending on the stage of the game. For example, a pawn is worth 82 in the opening but 94 in the endgame.

The table also contains negative values for the black pieces, allowing the same material system to represent both sides with a single set of values.

The material weights, together with the positional scores introduced next, are based on PeSTO's Evaluation Function from the Chess Programming Wiki.

Positional Scores

In addition to material values, ABC assigns different scores to pieces depending on the square they occupy. These positional scores are stored separately for the opening and endgame:

int positional_score[2][7][128] = {
    { /* Opening square values */ },
    { /* Endgame square values */ }
};

The first dimension selects the game phase, the second selects the piece type, and the third corresponds to the 128 squares of the 0x88 board. This gives ABC a separate positional value for each piece on each valid board square.

For example, the opening table contains values for the king, knight, bishop, rook, queen, and pawn. A piece can therefore receive a bonus or penalty depending on where it is placed, rather than being evaluated only by its material value.

Because the tables contain separate opening and endgame values, ABC can evaluate the same position differently depending on the stage of the game. A square that is useful for a piece during the opening may not have the same strategic value in the endgame.

Mirroring the Board for Black

The positional tables are stored from White's perspective. To use the same tables for Black, ABC defines a mirror_score lookup table:

int mirror_score[128] = {
    A1, B1, C1, D1, E1, F1, G1, H1,    NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE,
    A2, B2, C2, D2, E2, F2, G2, H2,    NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE,
    A3, B3, C3, D3, E3, F3, G3, H3,    NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE,
    A4, B4, C4, D4, E4, F4, G4, H4,    NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE,
    A5, B5, C5, D5, E5, F5, G5, H5,    NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE,
    A6, B6, C6, D6, E6, F6, G6, H6,    NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE,
    A7, B7, C7, D7, E7, F7, G7, H7,    NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE,
    A8, B8, C8, D8, E8, F8, G8, H8,    NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE
};

This table maps each square to the corresponding square from the opposite side's perspective. For example, A8 maps to A1, E8 maps to E1, and A2 maps to A7.

This allows ABC to reuse the same positional tables for both colors. Instead of maintaining completely separate square-value tables for White and Black, the engine can mirror Black's square before looking up its positional score.

UCI Time Control and Search State

The final group of variables in defs.c stores information received through the UCI protocol and keeps track of the engine's time-controlled search:

int quit = 0;
int movestogo = 30;
int movetime = -1;
int time = -1;
int inc = 0;
int starttime = 0;
int stoptime = 0;
int timeset = 0;
int stopped = 0;

The quit flag is set when the GUI sends the UCI quit command, allowing the engine to terminate its main loop.

movestogo, movetime, time, and inc store the time-control information supplied by the GUI. They represent the number of moves remaining, a fixed amount of time for the current move, the available clock time, and the time increment respectively.

The starttime and stoptime variables hold the calculated start and deadline times for a search. The timeset flag indicates whether a time limit has actually been established.

Finally, stopped indicates that the current search should stop. Together, these variables allow ABC to receive UCI time controls, calculate when a search must end, and communicate that information to the search code.

The complete handling of UCI commands and time-controlled searching will appear later when we examine UCI communication and search control.