void PrivateStrategyEvaluate ( STBoard board, STPiece piece, ref double rating ) { rating = 0.0; if (false == piece.IsValid( )) { return; } // The board was given to us with the piece already committed // to the board cells, so we can now collapse any completed // (fully-occupied) rows. board.CollapseAnyCompletedRows( ); // Note that this evaluation of pile height is AFTER collapsing // any completed rows. int pileHeight = 0; pileHeight = board.GetPileMaxHeight( ); // This simplistic strategy only punishes the maximum // height of the pile. rating = ((-1.0) * (double)pileHeight); }
private double PrivateStrategy ( bool flagCalledFromParentPly, // True if called from a parent level STBoard board, STPiece piece, ref int bestRotationDelta, // 0 or {0,1,2,3} ref int bestTranslationDelta // 0 or {...,-2,-1,0,1,2,...} ) { if (false == piece.IsValid( )) { return(0.0); } int currentBestTranslationDelta = 0; int currentBestRotationDelta = 0; double currentBestMerit = (-1.0e20); int currentBestPriority = 0; int trialTranslationDelta = 0; int trialRotationDelta = 0; double trialMerit = 0.0; int trialPriority = 0; int maxOrientations = 0; bool moveAcceptable = false; int count = 0; STBoard tempBoard = new STBoard(); STPiece tempPiece = new STPiece(); maxOrientations = STPiece.GetMaximumOrientationsOfShape(piece.GetShape()); for ( trialRotationDelta = 0; trialRotationDelta < maxOrientations; trialRotationDelta++ ) { // Make temporary copy of piece, and rotate the copy. tempPiece.CopyFrom(piece); for (count = 0; count < trialRotationDelta; count++) { tempPiece.Rotate(); } // Determine the translation limits for this rotated piece. bool moveIsPossible = false; int minDeltaX = 0; int maxDeltaX = 0; board.DetermineAccessibleTranslationsForPieceOrientation ( tempPiece, ref moveIsPossible, // false==NONE POSSIBLE ref minDeltaX, // Left limit ref maxDeltaX // Right limit ); // Consider all allowed translations for the current rotation. if (true == moveIsPossible) { for ( trialTranslationDelta = minDeltaX; trialTranslationDelta <= maxDeltaX; trialTranslationDelta++ ) { // Evaluate this move // Copy piece to temp and rotate and translate tempPiece.CopyFrom(piece); for (count = 0; count < trialRotationDelta; count++) { tempPiece.Rotate( ); } tempPiece.Translate(trialTranslationDelta, 0); moveAcceptable = board.DetermineIfPieceIsWithinBoardAndDoesNotOverlapOccupiedCells ( tempPiece ); if (true == moveAcceptable) { // Since the piece can be (not necessarily GET) at the goal // horizontal translation and orientation, it's worth trying // out a drop and evaluating the move. tempBoard.CopyFrom(board); tempBoard.FullDropAndCommitPieceToBoard(tempPiece); trialPriority = 0; if (true == flagCalledFromParentPly) { // UNUSED: int rowsEliminated = 0; // UNUSED: rowsEliminated = tempBoard.CollapseAnyCompletedRows(); double weightTotalShadowedHoles = (-0.65); double weightPileHeightWeightedCells = (-0.10); double weightSumOfWellHeights = (-0.20); trialMerit = (weightTotalShadowedHoles) * (double)(tempBoard.GetTotalShadowedHoles( )); trialMerit += (weightPileHeightWeightedCells) * (double)(tempBoard.GetPileHeightWeightedCells( )); trialMerit += (weightSumOfWellHeights) * (double)(tempBoard.GetSumOfWellHeights( )); } else { double weightRowElimination = (0.30); double weightTotalOccupiedCells = (-0.00); double weightTotalShadowedHoles = (-0.65); double weightPileHeightWeightedCells = (-0.10); double weightSumOfWellHeights = (-0.20); int rowsEliminated = 0; rowsEliminated = tempBoard.CollapseAnyCompletedRows(); // Single Ply (No next piece) // Averages around 1310 rows in 10 games, with a min of 445 and a max of 3710. trialMerit = (weightRowElimination) * (double)(rowsEliminated); trialMerit += (weightTotalOccupiedCells) * (double)(tempBoard.GetTotalOccupiedCells( )); trialMerit += (weightTotalShadowedHoles) * (double)(tempBoard.GetTotalShadowedHoles( )); trialMerit += (weightPileHeightWeightedCells) * (double)(tempBoard.GetPileHeightWeightedCells( )); trialMerit += (weightSumOfWellHeights) * (double)(tempBoard.GetSumOfWellHeights( )); } // If this move is better than any move considered before, // or if this move is equally ranked but has a higher priority, // then update this to be our best move. if ( (trialMerit > currentBestMerit) || ((trialMerit == currentBestMerit) && (trialPriority > currentBestPriority)) ) { currentBestPriority = trialPriority; currentBestMerit = trialMerit; currentBestTranslationDelta = trialTranslationDelta; currentBestRotationDelta = trialRotationDelta; } } } } } // Commit to this move bestTranslationDelta = currentBestTranslationDelta; bestRotationDelta = currentBestRotationDelta; return(currentBestMerit); }
// The following evaluation function was adapted from Pascal code submitted by: // Pierre Dellacherie (France). (E-mail : [email protected]) // // This amazing one-piece algorithm completes an average of roughly 600 000 // rows, and often attains 2 000 000 or 2 500 000 rows. However, the algorithm // sometimes completes as few as 15 000 rows. I am fairly certain that this // is NOT due to statistically abnormal patterns in the falling piece sequence. // // Pierre Dellacherie corresponded with me via e-mail to help me with the // conversion of his Pascal code to C++. // // WARNING: // If there is a single board and piece combination with the highest // 'rating' value, it is the best combination. However, among // board and piece combinations with EQUAL 'rating' values, // the highest 'priority' value wins. // // So, the complete rating is: { rating, priority }. void PrivateStrategyEvaluate ( STBoard board, STPiece piece, ref double rating, ref int priority ) { rating = 0.0; priority = 0; if (false == piece.IsValid()) { return; } int boardWidth = 0; int boardHeight = 0; boardWidth = board.GetWidth(); boardHeight = board.GetHeight(); int pieceMinX = 0; int pieceMinY = 0; int pieceMaxX = 0; int pieceMaxY = 0; piece.GetTranslatedBoundingRectangle (ref pieceMinX, ref pieceMinY, ref pieceMaxX, ref pieceMaxY); // Landing Height (vertical midpoint) double landingHeight = 0.0; landingHeight = 0.5 * (double)(pieceMinY + pieceMaxY); int completedRows = 0; completedRows = board.GetTotalCompletedRows(); int erodedPieceCellsMetric = 0; if (completedRows > 0) { // Count piece cells eroded by completed rows before doing collapse on pile. int pieceCellsEliminated = 0; pieceCellsEliminated = board.CountPieceCellsEliminated(piece); // Now it's okay to collapse completed rows board.CollapseAnyCompletedRows(); // Weight eroded cells by completed rows erodedPieceCellsMetric = (completedRows * pieceCellsEliminated); } // Note that this evaluation of pile height is AFTER collapsing // any completed rows. int pileHeight = 0; pileHeight = board.GetPileMaxHeight(); // Each empty row (above pile height) has two (2) "transitions" // (We could call ref_Board.GetTransitionCountForRow( y ) for // these unoccupied rows, but this is an optimization.) int boardRowTransitions = 0; boardRowTransitions = 2 * (boardHeight - pileHeight); // Only go up to the pile height, and later we'll account for the // remaining rows transitions (2 per empty row). int y = 0; for (y = 1; y <= pileHeight; y++) { boardRowTransitions += (board.GetTransitionCountForRow(y)); } int boardColumnTransitions = 0; int boardBuriedHoles = 0; int boardWells = 0; int x = 0; for (x = 1; x <= boardWidth; x++) { boardColumnTransitions += board.GetTransitionCountForColumn(x); boardBuriedHoles += board.GetBuriedHolesForColumn(x); boardWells += board.GetAllWellsForColumn(x); } // Final Rating rating = (0.0); rating += ((-1.0) * (landingHeight)); rating += ((1.0) * ((double)(erodedPieceCellsMetric))); rating += ((-1.0) * ((double)(boardRowTransitions))); rating += ((-1.0) * ((double)(boardColumnTransitions))); rating += ((-4.0) * ((double)(boardBuriedHoles))); rating += ((-1.0) * ((double)(boardWells))); // EXPLANATION: // [1] Punish landing height // [2] Reward eroded piece cells // [3] Punish row transitions // [4] Punish column transitions // [5] Punish buried holes (cellars) // [6] Punish wells #if DEBUGGING_PRINT_STATEMENTS STEngine.GetConsole().AddLine ( " D:" + (21.0 - landingHeight) + " R:" + erodedPieceCellsMetric + " RC:" + (-boardRowTransitions) + " CC:" + (-boardColumnTransitions) + " H:" + (-4 * boardBuriedHoles) + " W:" + (-boardWells) ); #endif // PRIORITY: // Priority is further differentiation between possible moves. // We further rate moves accoding to the following: // * Reward deviation from center of board // * Reward pieces to the left of center of the board // * Punish rotation // Priority is less important than the rating, but among equal // ratings we select the option with the greatest priority. // In principle we could simply factor priority in to the rating, // as long as the priority was less significant than the smallest // variations in rating, but for large board widths (>100), the // risk of loss of precision in the lowest bits of the rating // is too much to tolerate. So, this priority is stored in a // separate variable. int absoluteDistanceX = 0; absoluteDistanceX = (piece.GetX() - board.GetPieceSpawnX()); if (absoluteDistanceX < 0) { absoluteDistanceX = (-(absoluteDistanceX)); } priority = 0; priority += (100 * absoluteDistanceX); if (piece.GetX() < board.GetPieceSpawnX()) { priority += 10; } priority -= (piece.GetOrientation( ) - 1); }