protected virtual IEnumerator PlayMoveRoutine(AIMove m, float cursorDelay) { var b = grid.Get(m.x, m.y); showCursor = true; CreateCursor(0, b, true); yield return(new WaitForSeconds(cursorDelay)); if (grid.CanMove(b.block.x, b.block.y)) { grid.Move(b.block, m.direction); } else { // Can't move? // Maybe it's due to optimisation... // Try to move the neighbor var neighbor = grid.Get(m.x + m.direction, m.y); if (neighbor != null) { grid.Move(neighbor.block, -1 * m.direction); } } }
public virtual int WeightDanger(AIMove m, Vector2 highestBlock, WeightData w) { const float DANGER_HEIGHT = 0.77f; // Combos avoid danger. // So doing combos is always nice, better than throwing blocks away if (w.combosCount > 0) { return(w.combosCount * Values.danger); } int weight = 0; //int width = m.gridAfterMove.GetUpperBound(0) + 1; int height = m.gridAfterMove.GetUpperBound(1) + 1; int dangerHeightThreshold = (int)(height * DANGER_HEIGHT); if (highestBlock.y > dangerHeightThreshold) { if (highestBlock.y > GetHighest(m.gridAfterMove).y) { weight += Values.danger; } } return(weight); }
public virtual int WeightGeneral(AIMove m, int depth) { // Weight is slightly impacted by the number of moves required to get to the combo // SHOULD BE NEGATIVE. GOING DEEPER IS NOT BETTER. // We could weight anything else here return(depth * Values.depth); }
private void PrepareMoveToPlay(AIMove move) { nothingToDoCount = 0; PlayMove = true; var path = GetPathFromChildren(move); foreach (var m in path) { movesToPlay.Enqueue(m); } }
private List <AIMove> GetPathFromChildren(AIMove child) { List <AIMove> path = new List <AIMove>() { child }; AIMove current = child.parent; while (current != null) { path.Insert(0, current); current = current.parent; } return(path); }
/// <summary> /// Apply the move to the simplified grid and returns the new grid (with uncleared combos) /// </summary> public sbyte[,] GetGridWithMove(sbyte[,] baseGrid, AIMove move) { // Copy array sbyte[,] gridCopy = (sbyte[, ])baseGrid.Clone(); int x1 = move.x; int x2 = move.x + move.direction; int yBase = move.y; // Swap sbyte a = baseGrid[x1, yBase]; sbyte b = baseGrid[x2, yBase]; gridCopy[x1, yBase] = b; gridCopy[x2, yBase] = a; // Do the fall checks from bot to top int pxStart = x1 < x2 ? x1 : x2; int pxMax = x1 > x2 ? x1 : x2; gridCopy = ForceFall(gridCopy, pxStart, pxMax); return(gridCopy); }
/// <summary> /// Compute a weight on the move /// </summary> public virtual WeightData WeightMove(AIMove m, int width, int height, int depth) { var w = new WeightData(); blocksInComboIndex = 0; currentComboBlocksIndex = 0; if (m.gridAfterMove == null) { Log.Error("AI - Missing grid for move!"); return(w); } int weight = WeightGeneral(m, depth); // Compute combos sbyte currentBlock = 0; int currentCombo = 0; void CommitCommbo() { weight += WeightCombo(currentCombo, currentBlock, m.gridAfterMove, width, height); w.combosCount++; // Transfer current list to general list for (int i = 0; i < currentComboBlocksIndex; i++) { if (blocksInComboIndex < blocksInCombo.Length) { blocksInCombo[blocksInComboIndex] = currentComboBlocks[i]; blocksInComboIndex++; } } } var highestBlock = Vector2.one * -1; // Vertical for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { sbyte block = m.gridAfterMove[x, y]; if (block == 99) { continue; } // Not in a combo if (currentBlock == 0) { if (block > 0) { currentBlock = block; currentCombo = 1; // Same than a list.Add(new Vector) but better when 10 000 elements! currentComboBlocks[currentComboBlocksIndex].x = x; currentComboBlocks[currentComboBlocksIndex].y = y; currentComboBlocksIndex++; } else { currentBlock = 0; currentCombo = 0; currentComboBlocksIndex = 0; } } else { // Blocks are falling if (block == 0) { // Skip continue; } // Check for an identical block if (currentBlock == block) { currentCombo++; currentComboBlocks[currentComboBlocksIndex].x = x; currentComboBlocks[currentComboBlocksIndex].y = y; currentComboBlocksIndex++; } // Combo breaker else { if (currentCombo >= 3) { CommitCommbo(); } // Reset currentCombo = 1; currentBlock = block; currentComboBlocks[currentComboBlocksIndex].x = x; currentComboBlocks[currentComboBlocksIndex].y = y; currentComboBlocksIndex++; } } if (block > 0) { if (y > highestBlock.y) { highestBlock.x = x; highestBlock.y = y; } } } // y if (currentCombo >= 3) { CommitCommbo(); } // Reset currentBlock = 0; currentCombo = 0; currentComboBlocksIndex = 0; // list.Clear } // x // Horizontal for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { sbyte block = m.gridAfterMove[x, y]; if (block == 99) { continue; } // Not in a combo if (currentBlock == 0) { if (block > 0) { currentBlock = block; currentCombo = 1; currentComboBlocks[currentComboBlocksIndex].x = x; currentComboBlocks[currentComboBlocksIndex].y = y; currentComboBlocksIndex++; } else { currentBlock = 0; currentCombo = 0; currentComboBlocksIndex = 0; } } else { // Check for an identical block if (currentBlock == block) { currentCombo++; currentComboBlocks[currentComboBlocksIndex].x = x; currentComboBlocks[currentComboBlocksIndex].y = y; currentComboBlocksIndex++; } // Combo breaker else { if (currentCombo >= 3) { CommitCommbo(); } // Reset currentCombo = 1; currentBlock = block; currentComboBlocks[currentComboBlocksIndex].x = x; currentComboBlocks[currentComboBlocksIndex].y = y; currentComboBlocksIndex++; } } } // x if (currentCombo >= 3) { CommitCommbo(); } // Reset currentBlock = 0; currentCombo = 0; currentComboBlocksIndex = 0; } // y // Check if the game over is near weight += WeightDanger(m, highestBlock, w); w.total = weight; w.blocksInCombo = blocksInCombo.Take(blocksInComboIndex).ToArray(); return(w); }
/// <summary> /// Get all possible moves from the simplified grid /// </summary> public List <AIMove> GetMoves(int level, sbyte[,] g, int minX, int maxX, AIMove parent) { // Explore the grid! int width = g.GetUpperBound(0) + 1; int height = g.GetUpperBound(1) + 1; // Find the moves var moves = new List <AIMove>(); int currentCount = 0; int limit = settings.movesLimits.Length > level ? settings.movesLimits[level] : int.MaxValue; // Look left only on the first step. Then right only. int start = minX + 1; for (int x = start; x < maxX; x++) { // Be sure we're in the grid if (x < 0 || x >= width) { continue; } for (int y = 0; y < height; y++) { int block = g[x, y]; if (block >= 0 && block < 99) // Not empty & not garbage { // Check left if (x > 0 && x == start) { if (block != 0 || g[x - 1, y] != 0) { if (parent == null || (parent.x != (x - 1) && parent.y != y && parent.direction != -1)) { moves.Add(new AIMove(x, y, -1, parent)); currentCount++; } } } // Check right if (x < width - 1) { if (block != 0 || g[x + 1, y] != 0) { if (parent == null || (parent.x != (x + 1) && parent.y != y && parent.direction != 1)) { moves.Add(new AIMove(x, y, 1, parent)); currentCount++; } } } } if (level > 0 && currentCount > limit) { return(moves); } } } // First depth = randomize please if (level == 0 && limit > 0) { return(moves.OrderBy(m => Random.Range(0f, 1f)).Take(limit).ToList()); } return(moves); }
/// <summary> /// Compute a list of possible moves from a given grid /// </summary> /// <param name="level">Depth</param> /// <param name="n">Branch index</param> /// <param name="g"></param> /// <param name="minX">Left bound</param> /// <param name="maxX">Right bound</param> /// <param name="parent"></param> /// <returns></returns> public List <AIMove> GetWeightedMoves(int level, int n, sbyte[,] g, int minX, int maxX, AIMove parent) { int width = g.GetUpperBound(0) + 1; int height = g.GetUpperBound(1) + 1; var moves = GetMoves(level, g, minX, maxX, parent); if (moves.Count > 0) { foreach (var m in moves) { m.gridAfterMove = GetGridWithMove(g, m); var weightData = heuristic.WeightMove(m, width, height, level); m.weight = weightData.total + (parent?.weight ?? 0); m.depthLevel = level; m.comboCount = weightData.combosCount + (parent?.comboCount ?? 0); m.gridAfterMove = RemoveCombos(m.gridAfterMove, weightData.blocksInCombo); } } return(moves); }
protected virtual void PlayMove(AIMove m, float cursorDelay = 0.25f) { StartCoroutine(PlayMoveRoutine(m, cursorDelay)); }