I have been writing a chess engine for a little over a year and a half now (as well as lurking on the forums here); in fact, the first version of it was wiped along with everything else. I would like to release a beta version for people to download, use, and help test, however I'm afraid of some permissions that I might need. In the beginning, I used Beowulf and Crafty as good sources of information, but lately I've been looking to Crafty and Stockfish. I downloaded Ippolit and a few others, but either found the code confusing or not useful to me.
I understand that it's illegal and frowned upon to copy source code, but I'm not sure about some of the code I've copied and/or rewritten. The authors of these particular engines use these forums often, so hopefully they may see my post here.
I'm also worried about using evaluation parameters from other engines. Blackmail has quite a few evaluation parameters, but they are either zero, untuned, or ripped from other engines. I also detail Blackmail's evaluation parameters below.
Right now, with all of the zero parameters deleted, Blackmail is able to score 31% against the version of S.O.S. that is downloaded with Arena 3.0. Due to my limited processing power, I have not had the time to test against the other engines or tune some of the parameters.
Beowulf:
comm.cpp (38):
Code: Select all
//Code to disable buffering from Beowulf
//Code from Beowulf also found in MSDN: http://msdn.microsoft.com/en-us/library/ms682499(v=vs.85).aspx
/* No buffering, please... */
setbuf(stdout, NULL);
setbuf(stdin, NULL);
/* and I *really* mean it, too! */
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stdin, NULL, _IONBF, 0);
fflush(NULL);
search.cpp (108):
Code: Select all
int LMR_remaining_depth = 1; /* leave 1 full ply after reductions */
int LMR_min_reduction = 0; /* minimum reduction 1 ply */
int LMR_max_reduction = 1; /* maximum reduction 2 plies */
I copied the LMR from crafty; how many different ways can I really do this? Instead of using searchedmoves as crafty does, I use ordinary moves, so this only gets triggered after all captures are searched, and ordinary moves start being searched. There are 2 killers, so they are not reduced either. The min macro conflicted with one of the min macros from a standard .h file, so I changed it.
Code: Select all
if ( (movingpiece != PAWN) || !( ( whitetomove ? (PassedPawnCheck[from] & board->BlackPawns) == EMPTY_BITBOARD : (PassedPawnCheck[flipPosY(from)] & _byteswap_uint64(board->WhitePawns)) == EMPTY_BITBOARD ) ) ) {
extensions = cf_min(-cf_min(depthleft - 1 - LMR_remaining_depth, (ordinarymoves > 2) ? LMR_max_reduction : LMR_min_reduction), 0);
}
Stockfish:
board.h (35):
I needed the ++ operator for the PieceType for the SEE function below and some of these enums are just simply useful.
Code: Select all
enum PieceType {
EMPTY = 0,
PAWN = 1, KNIGHT = 2, BISHOP = 3, ROOK = 4, QUEEN = 5, KING = 6,
NUMPIECES = 7
};
//"Borrowed" from Stockfish
inline PieceType operator++ (PieceType& d, int) {d = PieceType(int(d) + 1); return d; }
enum Square {
A1, B1, C1, D1, E1, F1, G1, H1,
A2, B2, C2, D2, E2, F2, G2, H2,
A3, B3, C3, D3, E3, F3, G3, H3,
A4, B4, C4, D4, E4, F4, G4, H4,
A5, B5, C5, D5, E5, F5, G5, H5,
A6, B6, C6, D6, E6, F6, G6, H6,
A7, B7, C7, D7, E7, F7, G7, H7,
A8, B8, C8, D8, E8, F8, G8, H8
};
enum File { FILEA, FILEB, FILEC, FILED, FILEE, FILEF, FILEG, FILEH };
enum Rank { RANK1, RANK2, RANK3, RANK4, RANK5, RANK6, RANK7, RANK8 };
The pawn evaluation was almost literally ripped from Stockfish, although I believe I changed the order of the if statements if I remember right. I also added a few other things like pawn phalanx, but deleted those from the code posted here.
Code: Select all
bool notbackward = false, opposed = false, passed = false;
//Is this a passed pawn?
if ( (PassedPawnCheck[position] & board->BlackPawns) == EMPTY_BITBOARD ) {
pawnscore1 += CurrentPersonality->PassedPawnAdvance[phase][position];
pawnscore2 += CurrentPersonality->PassedPawnAdvance[phase+1][position];
passed = true;
notbackward = true;
} else //A pawn cannot be passed and opposed at the same time
//Is this an opposed pawn?
opposed = (PiecesInFrontOfPosition[position] & board->BlackPawns) != EMPTY_BITBOARD;
//Is this a doubled pawn (only penalized the ones in the back)?
if ( (PiecesInFrontOfPosition[position] & board->WhitePawns) != EMPTY_BITBOARD ) {
pawnscore1 -= CurrentPersonality->PawnStructureDoubled[phase][getFile(position)];
pawnscore2 -= CurrentPersonality->PawnStructureDoubled[phase+1][getFile(position)];
}
//Is this an isolated pawn?
if ( (bbFileNeighbors[getFile(position)] & board->WhitePawns) == EMPTY_BITBOARD ) {
//The check to see if this pawn is already flagged as passed is inside the isolated check
// because an isolated passed pawn still cannot be part of a pawn chain, but i don't want
// to penalize isolated passed pawns
if ( !passed) {
pawnscore1 -= CurrentPersonality->PawnStructureIsolated[opposed][phase][getFile(position)];
pawnscore2 -= CurrentPersonality->PawnStructureIsolated[opposed][phase+1][getFile(position)];
notbackward = true;
}
} else //A pawn cannot be isolated and part of a pawn chain at the same time
//Is this part of a pawn chain?
if ( (BlackPawnCaptures[position] & board->WhitePawns) != EMPTY_BITBOARD ) {
pawnscore1 += CurrentPersonality->PawnStructureChain[phase][getFile(position)];
pawnscore2 += CurrentPersonality->PawnStructureChain[phase+1][getFile(position)];
notbackward = true;
}
if ( !(notbackward) //A pawn cannot be backward if it's passed, isolated or part of a pawn chain
&& ((WhitePawnCaptures[position] & board->BlackPawns) == EMPTY_BITBOARD) ) {
BITBOARD b = WhitePawnCaptures[position];
BITBOARD allPawns = board->WhitePawns | board->BlackPawns;
while ( (b & (allPawns)) != EMPTY_BITBOARD ) {
b = b << 8;
}
if ( ((b | (b << 8)) & board->BlackPawns) != EMPTY_BITBOARD ) {
//Apply penalty for a backward pawn
pawnscore1 -= CurrentPersonality->PawnStructureBackward[opposed][phase][getFile(position)];
pawnscore2 -= CurrentPersonality->PawnStructureBackward[opposed][phase+1][getFile(position)];
}
}
Basic adaptable null reduction depth. I haven't tuned this, nor am I using fractional plies, but plan to later.
Code: Select all
int nullreduction = 3 + ( depthleft >= 5 ? depthleft / 8 : 0 );
I changed a few things from Stockfish, including early exit and not counting pinned pieces. I, however, am not currently taking en passant captures into account.
Code: Select all
int SEEScore(Board *board, int fromsquare, int tosquare)
{
PieceType capturedpiece = board->pieces[tosquare];
PieceType movingpiece = board->pieces[fromsquare];
PieceType pt;
PieceType bestknown[2] = { PAWN, PAWN };
bool whitetomove = whiteToMove(board);
//1) If the captured piece value <= the moving piece value, return a quick SEE score
if ( see_scores[capturedpiece] > see_scores[movingpiece] ) {
//Return worst case: the piece we moved gets recaptured
return see_scores[capturedpiece] - see_scores[movingpiece];
}
//2) The king cannot be recaptured, simply return the score of the captured piece
if ( capturedpiece == KING ) {
return see_scores[KING];
}
BITBOARD occupied, attackers, stmattackers, b, pieces;
//There will never be more than 32 pieces attacking a specific square
int gain[32], d = 1;
//CHANGE: Blackmail takes into account that pinned pieces cannot participate in SEE calculations UNLESS
// the destination square is a valid square for it to move to
occupied = ( board->inbetween_squares & OneShiftedBy[tosquare] ? board->AllPieces : board->AllPieces & ~board->pinned_pieces );
/*
// Handle en passant moves
if (st->epSquare == to && type_of_piece_on(from) == PAWN)
{
Square capQq = (side_to_move() == WHITE ? to - DELTA_N : to - DELTA_S);
assert(capturedType == PIECE_TYPE_NONE);
assert(type_of_piece_on(capQq) == PAWN);
// Remove the captured pawn
clear_bit(&occupied, capQq);
capturedType = PAWN;
}
*/
// Find all attackers to the destination square, with the moving piece
// removed, but possibly an X-ray attacker added behind it.
occupied = (occupied | OneShiftedBy[tosquare]) ^ OneShiftedBy[tosquare];
occupied = (occupied | OneShiftedBy[fromsquare]) ^ OneShiftedBy[fromsquare];
BITBOARD allbishops = (board->WhiteBishops | board->BlackBishops | board->WhiteQueens | board->BlackQueens);
BITBOARD allrooks = (board->WhiteRooks | board->BlackRooks | board->WhiteQueens | board->BlackQueens);
attackers = (WhitePawnCaptures[tosquare] & board->BlackPawns | BlackPawnCaptures[tosquare] & board->WhitePawns)
| KnightMoves[tosquare] & (board->WhiteKnights | board->BlackKnights)
| bishopmoves(board, tosquare) & allbishops
| rookmoves(board, tosquare) & allrooks
| KingMoves[tosquare] & (board->WhiteKing | board->BlackKing);
attackers &= occupied;
// If the opponent has no attackers we are finished
whitetomove = !whitetomove;
stmattackers = attackers & ( whitetomove ? board->WhitePieces : board->BlackPieces );
if ( stmattackers == EMPTY_BITBOARD ) {
return see_scores[capturedpiece];
}
gain[0] = see_scores[capturedpiece];
capturedpiece = movingpiece;
while ( stmattackers ) {
// Locate the least valuable attacker for the side to move. The loop
// below looks like it is potentially infinite, but it isn't. We know
// that the side to move still has at least one attacker left.
bool found = false;
pt = bestknown[whitetomove];
while ( (pt < KING) && !found ) {
switch (pt) {
case PAWN:
pieces = ( whitetomove ? board->WhitePawns : board->BlackPawns );
break;
case KNIGHT:
pieces = ( whitetomove ? board->WhiteKnights : board->BlackKnights );
break;
case BISHOP:
pieces = ( whitetomove ? board->WhiteBishops : board->BlackBishops );
break;
case ROOK:
pieces = ( whitetomove ? board->WhiteRooks : board->BlackRooks );
break;
case QUEEN:
pieces = ( whitetomove ? board->WhiteQueens : board->BlackQueens );
break;
case KING:
pieces = ( whitetomove ? board->WhiteKing : board->BlackKing );
}
if ( ( stmattackers & pieces ) != EMPTY_BITBOARD ) {
found = true;
} else {
pt ++;
}
}
//Save the lowest point, so next loop around, we can start there instead of starting at pawn again
bestknown[whitetomove] = pt;
b = stmattackers & pieces;
occupied ^= (b & (~b + 1));
attackers |= bishopmoves(board, tosquare) & allbishops | rookmoves(board, tosquare) & allrooks;
attackers &= occupied;
// Add the new entry to the swap list
gain[d] = see_scores[capturedpiece] - gain[d-1];
d++;
//CHANGE: early pruning of the SEE score
// if ( see_scores[capturedpiece] > see_scores[pt] ) {
// break;
// }
//CHANGE: early pruning of the SEE score. Q: Is this the same as my code above?
if ( cf_max(-gain[d-1], gain[d]) < 0 ) {
break;
}
// Remember the value of the capturing piece, and change the side to
// move before beginning the next iteration.
capturedpiece = pt;
whitetomove = !whitetomove;
stmattackers = attackers & ( whitetomove ? board->WhitePieces : board->BlackPieces );
// Stop before processing a king capture
if ( capturedpiece == KING && stmattackers ) {
gain[d] = see_scores[KING] - gain[d-1];
d++;
break;
}
}
// Having built the swap list, we negamax through it to find the best
// achievable score from the point of view of the side to move.
while (--d)
{
gain[d-1] = cf_min(-gain[d], gain[d-1]);
}
return gain[0];
}
Code: Select all
#define bishopmoves(x,srcpos) ( lineAttacks(x->AllPieces, &MagicMoveMaskList[srcpos][MAGICMOVE_DIAG]) | lineAttacks(x->AllPieces, &MagicMoveMaskList[srcpos][MAGICMOVE_ANTI]) )
#define rookmoves(x,srcpos) ( lineAttacks(x->AllPieces, &MagicMoveMaskList[srcpos][MAGICMOVE_FILE]) | lineAttacks(board->AllPieces, &MagicMoveMaskList[srcpos][MAGICMOVE_RANK]) )
#define queenmoves(x,srcpos) ( bishopmoves(x,srcpos) | rookmoves(x,srcpos) )
Most of the values used for evaluation are either zero, untuned, or ripped from another engine. For most of the parameters, I implemented them, ran around 1000 games or so, and tested the difference. Most changes to the evaluation were in the 20-40 elo range.
Code: Select all
* Material (100, 325, 325, 575, 1050)
* Pawn Structure (pawn structure scores are stored in a hashtable)
* - Advancement (ripped from Crafty, small adjustment to 7th rank pawns)
* - Phalanx (zero)
* - A "phalanxed" pawn has another pawn next to it.
* - Only one bonus is applied for two pawns next to each other, two bonuses are applied for
* three pawns next to each other.
* - Passed Pawn Advancement (same as standard advancement)
* - Opposed Pawns (ripped from Stockfish)
* - "opposed" pawns have an enemy pawn in front of them, regardless of how far.
* - Doubled Pawns (ripped from Stockfish)
* - "doubled" pawns have a friendly pawn in front of them, regardless of how far.
* - The pawn in front is not considered "doubled." If pawns are tripled, then there are two
* "doubled" pawns.
* - Isolated pawns (ripped from Stockfish)
* - "isolated" pawns have no friendly pawns on either side.
* - "doubled" pawns may both still be considered "isolated."
* - "passed" pawns are not counted as "isolated."
* - Pawn Chains (ripped from Stockfish)
* - "chained" pawns have another pawn behind them defending it
* - Backward Pawns (ripped from Stockfish)
* - A "backward" pawn is complicated to explain :P
* - "passed," "isolated," or "chained" pawns cannot be backward pawns.
* - Pieces which are attacked (zero)
* Penalize pieces that are pinned against the King
* - Mobility
* - Center Control
* King Development
* - King Centrality (untuned, pretty much just says to the engine "please castle")
* - Unstoppable Passed Pawn (zero)
* - A pawn is considered "unstoppable" if the opposing king cannot catch it to its queening square.
* - This is under King Development and not Pawn Structure because it depends on the position of the enemy king.
* Knights
* - Knight Pair (untuned)
* - Empty Square mobility (ripped from Stockfish)
* - Enemy King Tropism (zero)
* - Pieces which are attacked (zero)
* Bishops
* - Bishop Pair (untuned)
* - Empty Square mobility (ripped from Stockfish)
* - Enemy King Tropism (zero)
* - Pieces which are attacked (zero)
* - Good/Bad Bishops (untuned)
* Rooks
* - Rook Pair (untuned)
* - Connected Rooks (code assumes <= 2 rooks) (untuned)
* - Bonus for rooks on 7th rank (untuned)
* - Bonus for rooks on an empty file (zero)
* - Empty Square mobility (ripped from Stockfish)
* - Rooks behind passed pawns (zero)
* - Enemy King Tropism (zero)
* - Pieces which are attacked (zero)
* Queens
* - Bonus for queens on 7th rank (untuned)
* - Bonus for queens on an empty file (zero)
* - Empty Square mobility (ripped from Stockfish)
* - Queens behind passed pawns (zero)
* - Enemy King Tropism (zero)
* - Pieces which are attacked (zero)
Blackmail's code is tuned pretty well for speed; my perft runs around 70M nodes per second without a hash from the start position. Evaluation could use some work, but is still pretty fast and awaiting hand written assembly code.
Oh, and it only runs on Windows. Sorry Dr. Hyatt I'll get it working on linux when I can find a box to write it on and a few days time to write.