private static IList <int> CompleteConsecutiveness( Board board, IEnumerable <Consecutiveness> incompleteMoves) { var cells = board.Cells; var boardDimensions = board.Dimensions; var moves = new List <int>(); void collect(CellCoordinates coordinates) { if (CellCoordinates.IsInside(coordinates, boardDimensions)) { var cellAt = CellCoordinates.ToCellAt(coordinates, boardDimensions); if (cells[cellAt] == CellOwner.None) { moves.Add(cellAt); } } } var prioritized = incompleteMoves .OrderByDescending(consecutiveness => consecutiveness.CellsAt.Count); foreach (var consecutiveness in prioritized) { // single moves cannot be completed in one turn if (consecutiveness.CellsAt.Count < 2) { continue; } var first = CellCoordinates.FromCellAt(consecutiveness.CellsAt.First(), boardDimensions); var second = CellCoordinates.FromCellAt(consecutiveness.CellsAt.Skip(1).First(), boardDimensions); var last = CellCoordinates.FromCellAt(consecutiveness.CellsAt.Last(), boardDimensions); var stepSpan = second - first; collect(first - stepSpan); collect(last + stepSpan); } return(moves); }
private static IList <int> FindWinningMoves( Board board, IEnumerable <Consecutiveness> incompleteMoves, Predicate <CellOwner> cellOwnerPredicate) { var dimensions = board.Dimensions; var cells = board.Cells; var filteredIncompleteMoves = incompleteMoves.Where( (consecutiveness) => cellOwnerPredicate(cells[consecutiveness.CellsAt.First()])); // When there are moves, which could complete a winning consecutiveness on the board, // we should preferably take it. var completingMoves = CompleteConsecutiveness(board, filteredIncompleteMoves); if (completingMoves.Count > 0) { return(Cells.TakeAnyCell(completingMoves)); } // Gaps between taken cells could also complete a winning consecutiveness var gaps = new List <Gap>(); var smallestSpread = int.MaxValue; for (int i = 0; i < cells.Count; i++) { // Skip cells which are not set (we find the gaps otherwise) // and skip cells which are not of interest for now var leftCellOwner = cells[i]; if (leftCellOwner == CellOwner.None || !cellOwnerPredicate(leftCellOwner)) { continue; } // Bubble upwards and find a complementing cell of the same cellOwner var leftCoordinates = CellCoordinates.FromCellAt(i, dimensions); for (int j = i + 1; j < cells.Count; j++) { var rightCellOwner = cells[j]; if (rightCellOwner != leftCellOwner) { continue; } // When the half distance between them is an integer (or distance from the middle to both // sides is equal), we have found a potential gap. var distance = j - i; var halfDistance = distance / 2; if (halfDistance + halfDistance != distance) { continue; } // The middle cell as gap must be claimable (only concerns boards bigger than 3x3) var middleCellAt = i + halfDistance; var middleCellOwner = cells[middleCellAt]; if (middleCellOwner != CellOwner.None) { continue; } // Now we need to determine the spread between left cell and middle cell in terms of // board coordinates. This is actually only a priorization help for boards bigger than 3x3. // On 3x3 we will always have a spread of 1. var middleCoordinates = CellCoordinates.FromCellAt(middleCellAt, dimensions); var spread = (middleCoordinates - leftCoordinates).Spread(); smallestSpread = Math.Min(smallestSpread, spread); gaps.Add(new Gap(middleCellAt, spread)); } } // Gaps with smaller spread will be filled first var gapsWithSmallestSpread = gaps.Where((gap) => gap.Spread == smallestSpread) .Select((gap) => gap.CellAt); return(Cells.TakeAnyCell(gapsWithSmallestSpread.ToList())); }