/// <summary> /// Returns the possible bonus cards for the specified board. /// </summary> public static void GetPossibleBonusCardIndexes(ulong maxCardIndex, ref ByteList12 cardIndexes) { byte maxBonusCardIndex = (byte)(maxCardIndex - 3); for(byte cardIndex = 4; cardIndex <= maxBonusCardIndex; cardIndex++) cardIndexes.Add(cardIndex); }
/// <summary> /// Returns the quality value for shifting the specified board in the specified direction. /// Returns null if shifting in that direction is not possible. /// </summary> private unsafe float?EvaluateMoveForBoard(FastBoard board, FastDeck deck, ulong knownNextCardIndex, ShiftDirection dir, int recursionsLeft, ref long movesEvaluated) { FastBoard shiftedBoard = board; IntVector2D *newCardCells = stackalloc IntVector2D[4]; if (shiftedBoard.ShiftInPlace(dir, newCardCells)) { float totalQuality = 0; float totalWeight = 0; if (knownNextCardIndex == ulong.MaxValue) // Special value for bonus card. { ByteList12 indexes = new ByteList12(); Game.GetPossibleBonusCardIndexes(board.GetMaxCardIndex(), ref indexes); for (int i = 0; i < indexes.Count; i++) { ulong cardIndex = indexes.Items[i]; for (int j = 0; j < 4; j++) { IntVector2D cell = newCardCells[j]; if (cell.X < 0) { continue; } FastBoard newBoard = shiftedBoard; newBoard.SetCardIndex(cell, cardIndex); float quality; if (recursionsLeft == 0 || GetBestMoveForBoard(newBoard, deck, 0, recursionsLeft - 1, out quality, ref movesEvaluated) == null) { quality = _evaluator(newBoard); movesEvaluated++; } totalQuality += quality; totalWeight += 1; } } } else if (knownNextCardIndex > 0) { FastDeck newDeck = deck; newDeck.Remove(knownNextCardIndex); for (int i = 0; i < 4; i++) { IntVector2D cell = newCardCells[i]; if (cell.X < 0) { continue; } FastBoard newBoard = shiftedBoard; newBoard.SetCardIndex(cell, knownNextCardIndex); float quality; if (recursionsLeft == 0 || GetBestMoveForBoard(newBoard, newDeck, 0, recursionsLeft - 1, out quality, ref movesEvaluated) == null) { quality = _evaluator(newBoard); movesEvaluated++; } totalQuality += quality; totalWeight += 1; } } else if (_moveSearchDepth - recursionsLeft - 1 < _cardCountDepth) { if (deck.Ones > 0) { FastDeck newDeck = deck; newDeck.RemoveOne(); for (int i = 0; i < 4; i++) { IntVector2D cell = newCardCells[i]; if (cell.X < 0) { continue; } FastBoard newBoard = shiftedBoard; newBoard.SetCardIndex(cell, 1); float quality; if (recursionsLeft == 0 || GetBestMoveForBoard(newBoard, newDeck, 0, recursionsLeft - 1, out quality, ref movesEvaluated) == null) { quality = _evaluator(newBoard); movesEvaluated++; } totalQuality += quality * deck.Ones; totalWeight += deck.Ones; } } if (deck.Twos > 0) { FastDeck newDeck = deck; newDeck.RemoveTwo(); for (int i = 0; i < 4; i++) { IntVector2D cell = newCardCells[i]; if (cell.X < 0) { continue; } FastBoard newBoard = shiftedBoard; newBoard.SetCardIndex(cell, 2); float quality; if (recursionsLeft == 0 || GetBestMoveForBoard(newBoard, newDeck, 0, recursionsLeft - 1, out quality, ref movesEvaluated) == null) { quality = _evaluator(newBoard); movesEvaluated++; } totalQuality += quality * deck.Twos; totalWeight += deck.Twos; } } if (deck.Threes > 0) { FastDeck newDeck = deck; newDeck.RemoveThree(); for (int i = 0; i < 4; i++) { IntVector2D cell = newCardCells[i]; if (cell.X < 0) { continue; } FastBoard newBoard = shiftedBoard; newBoard.SetCardIndex(cell, 3); float quality; if (recursionsLeft == 0 || GetBestMoveForBoard(newBoard, newDeck, 0, recursionsLeft - 1, out quality, ref movesEvaluated) == null) { quality = _evaluator(newBoard); movesEvaluated++; } totalQuality += quality * deck.Threes; totalWeight += deck.Threes; } } // Note that we're not taking the chance of getting a bonus card into consideration. That would be way too expensive at not much benefit. } else { float quality; if (recursionsLeft == 0 || GetBestMoveForBoard(shiftedBoard, deck, 0, recursionsLeft - 1, out quality, ref movesEvaluated) == null) { quality = _evaluator(shiftedBoard); movesEvaluated++; } totalQuality += quality; totalWeight += 1; } return(totalQuality / totalWeight); } else { return(null); } }