/// <summary> /// Calculates cells Cost for specified player. /// </summary> /// <param name="klopPlayer">The klop player.</param> /// <param name="callback">Callback for evaluation result.</param> public void EvaluateCells(IKlopPlayer klopPlayer, Action<IKlopCell, double> callback) { var toRemove = new HashSet<Tuple<IKlopPlayer, IKlopCell>>(); foreach (IKlopCell cell in _klopModel.Cells) { var cacheKey = new Tuple<IKlopPlayer, IKlopCell>(klopPlayer, cell); if (!CellValueCache.ContainsKey(cacheKey)) continue; var cachedCell = CellValueCache[cacheKey]; if (cachedCell.Item2 == cell.State) continue; // Remove cell and adjanced cells from cache (do not remove immediately, needed for adjanced cells check) toRemove.Add(cacheKey); toRemove.AddRange(_klopModel.GetNeighborCells(cell).Select(c => new Tuple<IKlopPlayer, IKlopCell>(klopPlayer, c))); } CellValueCache.RemoveRange(toRemove); foreach (IKlopCell cell in _klopModel.Cells) { var cacheKey = new Tuple<IKlopPlayer, IKlopCell>(klopPlayer, cell); double cost; if (CellValueCache.ContainsKey(cacheKey)) { cost = CellValueCache[cacheKey].Item1; //Debug.Assert(f.Cost == GetCellCost(cell, klopPlayer)); // Cache integrity test } else { cost = GetCellCost(cell, klopPlayer); CellValueCache[cacheKey] = new Tuple<double, ECellState>(cost, cell.State); } callback(cell, cost); } }
/// <summary> /// Generates the starting pattern: returns next position for the initial standoff. /// </summary> /// <param name="model">The model.</param> /// <param name="player">The player.</param> /// <param name="enemyDistanceFunc">The enemy distance function.</param> public static IKlopCell GenerateStartingPattern(this IKlopModel model, IKlopPlayer player, Func <IKlopCell, double> enemyDistanceFunc) { if (model == null || player == null || enemyDistanceFunc == null) { throw new ArgumentNullException(); } // TODO: Target sometimes falls behing enemy cells, and, however, target cell is not close to enemy, the path is. // TODO: "Safe path"?? "Safe evaluator".. or SafePathFinder. How to build safe cells map fast? return(model.Cells .Where(c => { if (c.X < 1 || c.Y < 1 || c.X >= model.FieldWidth - 2 || c.Y >= model.FieldHeight - 2) { return false; } if (model.GetNeighborCells(c).Any(cc => cc.Owner != null)) { return false; } var dx1 = Math.Abs(c.X - player.BasePosX); var dy1 = Math.Abs(c.Y - player.BasePosY); return dx1 > 1 && dy1 > 1 && ((dx1 * dx1 + dy1 * dy1) < (Math.Pow(model.FieldHeight, 2) + Math.Pow(model.FieldWidth, 2)) / 3) && (enemyDistanceFunc(c) > model.TurnLength / 1.7); }).Random() ?? model.Cells.Where(c => c.Owner == null).Random()); }
/// <summary> /// Evaluates the cells for specified player. /// </summary> /// <param name="klopPlayer">The klop player.</param> private void EvaluateCells(IKlopPlayer klopPlayer) { _cellEvaluator.EvaluateCells(klopPlayer, (cell, cost) => { var node = _field[cell.X, cell.Y]; node.Reset(); node.Cost = cost; }); }
/// <summary> /// Gets the cell cost. /// </summary> /// <param name="cell">The cell.</param> /// <param name="klopPlayer">The klop player.</param> /// <returns></returns> private double GetCellCost(IKlopCell cell, IKlopPlayer klopPlayer) { if (cell.Owner == klopPlayer) { return 0; // Zero cost for owned cell } if (cell.State == ECellState.Dead) { return TurnBlockedCost; // Can't move into own dead cell or base cell } //TODO: Additive cost! E.g. near own clop + near enemy clop!! if (cell.Owner != null && cell.State == ECellState.Alive) { if (IsCellNearBase(cell, klopPlayer)) { return TurnEatOwnbaseCost; } if (_klopModel.Players.Where(p => p != klopPlayer).Any(enemy => IsCellNearBase(cell, enemy))) { return TurnEatEnemyBaseCost; } return TurnEatCost; } if (IsCellNearBase(cell, klopPlayer)) { return TurnNearOwnBaseCost; } var neighbors = _klopModel.GetNeighborCells(cell).ToArray(); if (neighbors.Any(c => c.State == ECellState.Base)) { return TurnNearEnemyBaseCost; } var enemyCount = neighbors.Count(c => c.Owner != null && c.Owner != klopPlayer); if (enemyCount > 0) { //return TurnNearEnemyEmptyCost*(1 + (double) enemyCount/2); // Turn near enemy klop costs a bit more. return TurnEmptyCost + TurnNearEnemyEmptyCostAddition*enemyCount; } var neighborCount = neighbors.Count(c => c.Owner != null); return TurnEmptyCost*(1 + (double) neighborCount/2); // Default - turn into empty cell. }
/// <summary> /// Finds the path between two nodes. /// </summary> public IEnumerable <Node> FindPath(Node startNode, Node finishNode, IKlopPlayer klopPlayer, bool inverted, bool skipEvaluate = false) { // Init field if (!skipEvaluate) { EvaluateCells(klopPlayer); } // Get result var lastNode = _aStar.FindPath(startNode, finishNode, GetDistance, GetNodeByCoordinates, inverted); while (lastNode != null) { yield return(lastNode); lastNode = lastNode.Parent; } }
private static bool IsCellNearBase(IKlopCell cell, IKlopPlayer baseOwner) { return Math.Max(Math.Abs(cell.X - baseOwner.BasePosX), Math.Abs(cell.Y - baseOwner.BasePosY)) == 1; }
/// <summary> /// Finds the path betweed specified nodes for specified player. /// </summary> public List <IKlopCell> FindPath(int startX, int startY, int finishX, int finishY, IKlopPlayer klopPlayer, bool inverted = false) { return(FindPath(GetNodeByCoordinates(startX, startY), GetNodeByCoordinates(finishX, finishY), klopPlayer, inverted) .Select(n => _klopModel[n.X, n.Y]).Where(c => c.Owner != klopPlayer).ToList()); }
/// <summary> /// Determines whether specified player is defeated and cannot longer make moves. /// </summary> public bool IsPlayerDefeated(IKlopPlayer player) { return(_defeatedPlayers.Contains(player)); }
/// <summary> /// Finds the most important cell: cell which most of all affects total path cost. /// </summary> /// <returns>Tuple of most important cell and path cost difference.</returns> private Tuple <IKlopCell, double> FindMostImportantCell(int startX, int startY, int finishX, int finishY, IKlopPlayer klopPlayer) { var startN = _pathFinder.GetNodeByCoordinates(startX, startY); var finishN = _pathFinder.GetNodeByCoordinates(finishX, finishY); var initialCost = _pathFinder.FindPath(startN, finishN, klopPlayer, false).Sum(n => n.Cost); double maxCost = 0; Node resultNode = null; foreach ( var node in Model.Cells.Where(c => c.Available && c.Owner == klopPlayer && c.State == ECellState.Alive).Select( c => _pathFinder.GetNodeByCoordinates(c.X, c.Y))) { var oldCost = node.Cost; node.Cost = KlopCellEvaluator.TurnBlockedCost; var cost = _pathFinder.FindPath(startN, finishN, klopPlayer, false, true).Sum(n => n.Cost); if (cost > maxCost) { maxCost = cost; resultNode = node; } node.Cost = oldCost; } return(resultNode == null ? null : new Tuple <IKlopCell, double>(Model[resultNode.X, resultNode.Y], maxCost - initialCost)); }