/// <summary> /// Modifies this board in-place by shifting those cards that can be shifted or merged in the specified direction. /// </summary> /// <param name="newCardCells">The possible locations for a new card will be added to this array.</param> /// <returns>Whether anything was able to be shifted.</returns> public unsafe bool ShiftInPlace(ShiftDirection dir, IntVector2D *newCardCells) { ulong oldBoard = _board; switch (dir) { case ShiftDirection.Left: ShiftLeft(newCardCells); break; case ShiftDirection.Right: ShiftRight(newCardCells); break; case ShiftDirection.Up: ShiftUp(newCardCells); break; case ShiftDirection.Down: ShiftDown(newCardCells); break; default: throw new NotSupportedException("Unknown ShiftDirection '" + dir + "'."); } return(oldBoard != _board); }
/// <summary> /// Shifts this board in-place down. /// </summary> private unsafe void ShiftDown(IntVector2D *newCardCells) { { ulong prevBoard = _board; ulong cell1Index = (_board & MASK_0_3) >> SHIFT_0_3; ulong cell2Index = (_board & MASK_0_2) >> SHIFT_0_2; ulong arrayLookup = cell2Index | (cell1Index << 4); cell1Index = DEST_SHIFT_RESULTS[arrayLookup]; cell2Index = SOURCE_SHIFT_RESULTS[arrayLookup]; ulong cell3Index = (_board & MASK_0_1) >> SHIFT_0_1; arrayLookup = cell3Index | (cell2Index << 4); cell2Index = DEST_SHIFT_RESULTS[arrayLookup]; cell3Index = SOURCE_SHIFT_RESULTS[arrayLookup]; ulong cell4Index = (_board & MASK_0_0) >> SHIFT_0_0; arrayLookup = cell4Index | (cell3Index << 4); cell3Index = DEST_SHIFT_RESULTS[arrayLookup]; cell4Index = SOURCE_SHIFT_RESULTS[arrayLookup]; _board = (_board & ~(MASK_0_3 | MASK_0_2 | MASK_0_1 | MASK_0_0)) | (cell1Index << SHIFT_0_3) | (cell2Index << SHIFT_0_2) | (cell3Index << SHIFT_0_1) | (cell4Index << SHIFT_0_0); if (prevBoard != _board) { newCardCells[0] = new IntVector2D(0, 0); } else { newCardCells[0] = new IntVector2D(-1, -1); } } { ulong prevBoard = _board; ulong cell1Index = (_board & MASK_1_3) >> SHIFT_1_3; ulong cell2Index = (_board & MASK_1_2) >> SHIFT_1_2; ulong arrayLookup = cell2Index | (cell1Index << 4); cell1Index = DEST_SHIFT_RESULTS[arrayLookup]; cell2Index = SOURCE_SHIFT_RESULTS[arrayLookup]; ulong cell3Index = (_board & MASK_1_1) >> SHIFT_1_1; arrayLookup = cell3Index | (cell2Index << 4); cell2Index = DEST_SHIFT_RESULTS[arrayLookup]; cell3Index = SOURCE_SHIFT_RESULTS[arrayLookup]; ulong cell4Index = (_board & MASK_1_0) >> SHIFT_1_0; arrayLookup = cell4Index | (cell3Index << 4); cell3Index = DEST_SHIFT_RESULTS[arrayLookup]; cell4Index = SOURCE_SHIFT_RESULTS[arrayLookup]; _board = (_board & ~(MASK_1_3 | MASK_1_2 | MASK_1_1 | MASK_1_0)) | (cell1Index << SHIFT_1_3) | (cell2Index << SHIFT_1_2) | (cell3Index << SHIFT_1_1) | (cell4Index << SHIFT_1_0); if (prevBoard != _board) { newCardCells[1] = new IntVector2D(1, 0); } else { newCardCells[1] = new IntVector2D(-1, -1); } } { ulong prevBoard = _board; ulong cell1Index = (_board & MASK_2_3) >> SHIFT_2_3; ulong cell2Index = (_board & MASK_2_2) >> SHIFT_2_2; ulong arrayLookup = cell2Index | (cell1Index << 4); cell1Index = DEST_SHIFT_RESULTS[arrayLookup]; cell2Index = SOURCE_SHIFT_RESULTS[arrayLookup]; ulong cell3Index = (_board & MASK_2_1) >> SHIFT_2_1; arrayLookup = cell3Index | (cell2Index << 4); cell2Index = DEST_SHIFT_RESULTS[arrayLookup]; cell3Index = SOURCE_SHIFT_RESULTS[arrayLookup]; ulong cell4Index = (_board & MASK_2_0) >> SHIFT_2_0; arrayLookup = cell4Index | (cell3Index << 4); cell3Index = DEST_SHIFT_RESULTS[arrayLookup]; cell4Index = SOURCE_SHIFT_RESULTS[arrayLookup]; _board = (_board & ~(MASK_2_3 | MASK_2_2 | MASK_2_1 | MASK_2_0)) | (cell1Index << SHIFT_2_3) | (cell2Index << SHIFT_2_2) | (cell3Index << SHIFT_2_1) | (cell4Index << SHIFT_2_0); if (prevBoard != _board) { newCardCells[2] = new IntVector2D(2, 0); } else { newCardCells[2] = new IntVector2D(-1, -1); } } { ulong prevBoard = _board; ulong cell1Index = (_board & MASK_3_3) >> SHIFT_3_3; ulong cell2Index = (_board & MASK_3_2) >> SHIFT_3_2; ulong arrayLookup = cell2Index | (cell1Index << 4); cell1Index = DEST_SHIFT_RESULTS[arrayLookup]; cell2Index = SOURCE_SHIFT_RESULTS[arrayLookup]; ulong cell3Index = (_board & MASK_3_1) >> SHIFT_3_1; arrayLookup = cell3Index | (cell2Index << 4); cell2Index = DEST_SHIFT_RESULTS[arrayLookup]; cell3Index = SOURCE_SHIFT_RESULTS[arrayLookup]; ulong cell4Index = (_board & MASK_3_0) >> SHIFT_3_0; arrayLookup = cell4Index | (cell3Index << 4); cell3Index = DEST_SHIFT_RESULTS[arrayLookup]; cell4Index = SOURCE_SHIFT_RESULTS[arrayLookup]; _board = (_board & ~(MASK_3_3 | MASK_3_2 | MASK_3_1 | MASK_3_0)) | (cell1Index << SHIFT_3_3) | (cell2Index << SHIFT_3_2) | (cell3Index << SHIFT_3_1) | (cell4Index << SHIFT_3_0); if (prevBoard != _board) { newCardCells[3] = new IntVector2D(3, 0); } else { newCardCells[3] = new IntVector2D(-1, -1); } } }
/// <summary> /// Returns whether the specified ShiftDirection is valid for the specified game. /// </summary> private unsafe static bool TestShiftDirection(FastBoard board, ShiftDirection dir) { IntVector2D *newCardCells = stackalloc IntVector2D[4]; return(board.ShiftInPlace(dir, newCardCells)); }
/// <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); } }