// A player's danger zone must be calculated from a World and a blockWorld public DangerZone(int playerNum, World world, BlockWorld blockWorld) { // Init danger array dangerZoneArray = new float[World.BlocksWidth, dangerZoneHeight]; // Init to zero for (int i = 0; i < World.BlocksWidth; i++) { for (int j = 0; j < dangerZoneHeight; j++) { dangerZoneArray[i, j] = 0.0f; } } // Store block world as a copy for modification this.blockWorld = blockWorld.Clone(); // Uses exact filtering to compute a belief distribution for player position // and weapon type/ammo sourceBeliefs = new Dictionary <IJCoords, ProjectileSourceBelief>(); computeSourceBeliefs(playerNum, world); // For each source belief computed by exact filtering, run the trajectory runTrajectories(); }
// Runs trajectories of projectile source beliefs void runTrajectories() { // Iterate through each possibility foreach (KeyValuePair <IJCoords, ProjectileSourceBelief> entry in sourceBeliefs) { ProjectileSourceBelief sourceBelief = entry.Value; int sourceI = entry.Key.I; int sourceJ = entry.Key.J; int sourceAmmo = sourceBelief.Ammo; float sourceProbability = sourceBelief.Probability; // Iterate through each weapon type for each possibility foreach (WeaponType weaponType in sourceBelief.PossibleWeapons) { if (weaponType == WeaponType.Lightning) { // Add danger to all above for lightning - easy for (int j = -World.FloorLevelJ; j < World.BlocksHeight; j++) { if (j < sourceJ) { addDanger(sourceI, j, sourceProbability * LightningDangerWeight); } } } else { // Recursively add values to the danger zone 2D array // Use same blockworld for both directions since paths won't cross BlockWorld newBlockWorld = blockWorld.Clone(); addDangerToBlockAndNeighbors(sourceI, sourceJ, weaponType, sourceProbability, sourceAmmo, true, newBlockWorld); addDangerToBlockAndNeighbors(sourceI, sourceJ, weaponType, sourceProbability, sourceAmmo, false, newBlockWorld); } } } }
// Outputs the optimal path public Path ComputeBestPath(BlockWorld blockWorld) { HeapPriorityQueue <Path> frontier = new HeapPriorityQueue <Path>(MaxNodes); HashSet <BlockWorld> explored = new HashSet <BlockWorld>(); // Initial state, path BlockWorld initialState = blockWorld; Path initialPath = new Path(0, new List <BlockWorld>() { initialState }); // Add the initial path to the frontier frontier.Enqueue(initialPath, 0 + Heuristic(initialState)); // Find paths int expansions = 0; while (frontier.Count > 0) { Path path = frontier.Dequeue(); BlockWorld lastWorld = path.Last(); // Return the no path if (expansions > MaxExpansions) { return(null); } // Check goal if (GoalFunction(lastWorld)) { return(path); } // Mark as explored explored.Add(lastWorld); // Iterate over possible actions List <BlockWorldAction> possibleActions = lastWorld.ApplicableActions(); foreach (BlockWorldAction action in possibleActions) { // Try the action on a cloned block world BlockWorld newWorld = lastWorld.Clone(); newWorld.Advance(action); // Check if explored already bool alreadyExplored = false; foreach (BlockWorld exploredWorld in explored) { if (exploredWorld.PropertiesEqual(newWorld)) { alreadyExplored = true; break; } } if (!alreadyExplored) { // Extend path Path newPath = path.ExtendedPath(newWorld, CostFunction(newWorld)); // Add to frontier frontier.Enqueue(newPath, Heuristic(newWorld) + newPath.Cost); } } expansions++; } // No solution exists return(null); }
// Recursively add danger to the 2D array void addDangerToBlockAndNeighbors(int i, int j, WeaponType type, float probability, int ammo, bool facingRight, BlockWorld blockWorld, bool isFalling = false) { // Base cases if (probability < epsilon) { return; } if (ammo == 0) { return; // Master mode has negative ammo - do not return } // Blow out ground if there is any at the current position. If there is if (blockWorld.CheckGroundByIndex(i, j)) { // Another base case for immutable ground bool immutable = blockWorld.CheckGroundImmutableByIndex(i, j); if (immutable) { return; // End of the line } // Blow out ground otherwise blockWorld.SetGroundByIndex(i, j, false); addDangerToBlockAndNeighbors(i, j, type, probability * groundBlowoutFactor, ammo - 1, facingRight, blockWorld); return; } else { int normalized = facingRight ? -1 : 1; switch (type) { case WeaponType.Rockets: { addDanger(i, j, probability * RocketsDangerWeight); addDangerToBlockAndNeighbors(i + normalized, j, type, probability, ammo, facingRight, blockWorld); break; } case WeaponType.Bombs: { addDanger(i, j, probability * BombsDangerWeight); addDangerToBlockAndNeighbors(i, j + 1, type, probability, ammo, facingRight, blockWorld); break; } case WeaponType.Minions: { addDanger(i, j, probability * MinionsDangerWeight); bool groundDown = blockWorld.CheckGroundByIndex(i, j + 1); bool groundRight = blockWorld.CheckGroundByIndex(i + normalized, j); bool goRight = false; bool goDown = false; // Do natural motion - right first unless hasn't fallen if (!groundRight && !groundDown) { if (isFalling && blockWorld.CheckGroundByIndex(i + normalized, j + 1)) { goRight = true; } else { goDown = true; } } else if (!groundRight && groundDown) { goRight = true; } else if (groundRight && !groundDown) { goDown = true; } else { // Block worlds diverge, so make a new one BlockWorld newBlockWorld = blockWorld.Clone(); // Go both directions addDangerToBlockAndNeighbors(i + normalized, j, type, probability / 2.0f, ammo, facingRight, blockWorld, false); addDangerToBlockAndNeighbors(i, j + 1, type, probability / 2.0f, ammo, facingRight, newBlockWorld, true); } // Do sole motions if (goRight) { addDangerToBlockAndNeighbors(i + normalized, j, type, probability, ammo, facingRight, blockWorld, false); return; } if (goDown) { addDangerToBlockAndNeighbors(i, j + 1, type, probability, ammo, facingRight, blockWorld, true); return; } break; } } } }