/// <Summary> /// Provides a list of objects within a certain distance from a position, /// ordered by distance. /// /// Copied from StarMap.cs (should probably make this a utility) /// </Summary> /// <param name="position">Starting Point for the search.</param> /// <returns>A list of Fleet and Star objects.</returns> private List <Mappable> FindNearObjects(NovaPoint position) { List <Mappable> nearObjects = new List <Mappable>(); foreach (FleetIntel report in clientData.EmpireState.FleetReports.Values) { if (!report.IsStarbase) { if (PointUtilities.IsNear(report.Position, position)) { nearObjects.Add(report); } } } foreach (StarIntel report in clientData.EmpireState.StarReports.Values) { if (PointUtilities.IsNear(report.Position, position)) { nearObjects.Add(report); } } // nearObjects.Sort(ItemSorter); return(nearObjects); }
/// <summary> /// Attempt an attack. /// </summary> /// <param name="allAttacks">A list of WeaponDetails representing a round of attacks.</param> private bool ProcessAttack(WeaponDetails attack) { // First, check that the target stack we originally identified has not // been destroyed (actually, the stack still exists at this point but // it may have no ship tokens left). In which case, don't bother trying to // fire this weapon system (we'll wait until the next battle clock // "tick" and re-target then). if (attack.TargetStack == null || attack.TargetStack.IsDestroyed) { return(false); } if (attack.SourceStack == null || attack.SourceStack.IsDestroyed) { // Report.Error("attacking stack no longer exists"); return(false); } // If the target stack is not within the range of this weapon system // then there is no point in trying to fire it. if (PointUtilities.Distance(attack.SourceStack.Position, attack.TargetStack.Position) > attack.Weapon.Range) { return(false); } // Target is valid; execute attack. ExecuteAttack(attack); return(true); }
/// <summary> /// Check if the fleet hits the minefiled. /// </summary> /// <remarks> /// The probability of hitting a mine is 0.3% per light year traveled for each /// warp over the safe speed. /// TODO (priority 3) - reference required. /// /// Example: A fleet traveling at Warp 9 has a 1.5% chance per light year /// traveled in a turn. Traveling 10 light years through the Minefield that /// turn, the fleet has a 10.5% chance of triggering a mine. /// </remarks> /// <param name="fleet">The moving fleet.</param> /// <param name="minefield">The minefield being traversed.</param> /// <returns>true if the minefield is hit.</returns> private bool CheckForHit(Fleet fleet, Minefield minefield) { // Calculate how long we are going to be in the Minefield. This is the // lesser of the distance to the next waypoint and the radius of the // field. NovaPoint currentPosition = fleet.Position; Waypoint targetWaypoint = fleet.Waypoints[0]; NovaPoint targetPosition = targetWaypoint.Position; double travelDistance = PointUtilities.Distance(currentPosition, targetPosition); if (minefield.Radius > (int)travelDistance) { travelDistance = minefield.Radius; } double speeding = fleet.Speed - minefield.SafeSpeed; double probability = (0.03 * travelDistance * speeding) * 100; double dice = random.Next(0, 100); if (dice < probability) { return(true); } return(false); }
private Point GetLookAheadLocation(Point currentLocation, Direction direction) { return(direction.Id switch { 'U' => PointUtilities.MoveUp(currentLocation), 'R' => PointUtilities.MoveRight(currentLocation), 'D' => PointUtilities.MoveDown(currentLocation), 'L' => PointUtilities.MoveLeft(currentLocation), _ => throw new ArgumentOutOfRangeException(nameof(direction), direction, "Unexpected direction") });
public StarIntel Scout(List <StarIntel> excludedStars) { bool missionAccepted = false; // Find a star to scout StarIntel starToScout = CloesestStar(this.fleet, excludedStars); if (starToScout != null) { // Do we need fuel first? double fuelRequired = 0.0; Fleet nearestFuel = ClosestFuel(fleet); if (!fleet.CanRefuel) { // Can not make fuel, so how much fuel is required to scout and then refuel? if (nearestFuel != null) { int bestWarp = 6; // FIXME (priority 4) - what speed to scout at? double bestSpeed = bestWarp * bestWarp; double speedSquared = bestSpeed * bestSpeed; double fuelConsumption = fleet.FuelConsumption(bestWarp, clientState.EmpireState.Race); double distanceSquared = PointUtilities.DistanceSquare(fleet.Position, starToScout.Position); // to the stars distanceSquared += PointUtilities.DistanceSquare(starToScout.Position, nearestFuel.Position); // and back to fuel (minimum) double time = Math.Sqrt(distanceSquared / speedSquared); fuelRequired = time * fuelConsumption; } else { // OMG there is no fuel! - just keep scouting then? } } if (fleet.FuelAvailable > fuelRequired) { // Fuel is no problem SendFleet(starToScout, fleet, new NoTask()); missionAccepted = true; } else { // Refuel before scouting further SendFleet(nearestFuel, fleet, new NoTask()); } } if (missionAccepted) { return(starToScout); } else { return(null); } }
public void TestIsNearDoesNotChangeParams() { NovaPoint a = new NovaPoint(1, 2); NovaPoint b = new NovaPoint(3, 4); bool testNear = PointUtilities.IsNear(a, b); Assert.IsTrue(a.X == 1); Assert.IsTrue(a.Y == 2); Assert.IsTrue(b.X == 3); Assert.IsTrue(b.Y == 4); }
public void Test5PositionStacks() { battleEngine.PositionStacks(zoneStacks); double distance = 0; Fleet stackA = zoneStacks[0] as Fleet; Fleet stackB = zoneStacks[1] as Fleet; distance = PointUtilities.Distance(stackA.Position, stackB.Position); Assert.Greater((int)distance, 3); // changed from Global.MaxRange -1 to 3 due to reduction in battle board size from Ken's 20x20 to Stars! 10x10 - Dan 07 Jul 11 }
/// <summary> /// Determine if a fleet is within a Minefield. The fleet is inside the /// circle if the distance between the field and the center of the field is /// less than the radius of the field. /// </summary> private bool IsInField(Fleet fleet, Minefield minefield) { // If we are travelling at a "safe" speed we can just pretend we are // not in a Minefield. if (fleet.Speed <= minefield.SafeSpeed) { return(false); } double distance = PointUtilities.Distance(fleet.Position, minefield.Position); if (distance < minefield.Radius) { return(true); } return(false); }
/// <summary> /// Return the closest refuelling point. /// </summary> /// <param name="fleet">The fleet looking for fuel.</param> /// <returns>The closest fleet that can refuel (normally a star base).</returns> private Fleet ClosestFuel(Fleet customer) { if (customer == null) { return(null); } // initialise the list of fuel stations, if null. if (FuelStations == null) { FuelStations = new FleetList(); foreach (Fleet pump in clientState.EmpireState.OwnedFleets.Values) { if (pump.CanRefuel) { FuelStations.Add(pump); } } } // if there are still no fuel stations, bug out if (FuelStations.Count == 0) { return(null); } Fleet closestFuelSoFar = null; double minRefulerDistance = double.MaxValue; foreach (Fleet pump in FuelStations.Values) { double distSquare = PointUtilities.DistanceSquare(pump.Position, customer.Position); if (distSquare < minRefulerDistance) { minRefulerDistance = distSquare; closestFuelSoFar = pump; } } return(closestFuelSoFar); }
public void Test6DoBattle() { Fleet stackA = zoneStacks[0] as Fleet; Fleet stackB = zoneStacks[1] as Fleet; List <Fleet> battlingFleets = new List <Fleet>(); battlingFleets.Add(stackA); battlingFleets.Add(stackB); double distanceS, distanceE; distanceS = PointUtilities.Distance(stackA.Position, stackB.Position); battleEngine.DoBattle(zoneStacks); distanceE = PointUtilities.Distance(stackA.Position, stackB.Position); // Check that the fleets have moved towards each other. Assert.Greater(distanceS, distanceE); }
/// <Summary> /// If there is another waypoint before the selected one, display the fuel, /// time, etc. required for this leg. /// </Summary> /// <param name="index">Index of the waypoint to display.</param> private void DisplayLegDetails(int index) { Waypoint thisWaypoint = selectedFleet.Waypoints[index]; WaypointTasks.Text = thisWaypoint.Task.Name; if (selectedFleet.Waypoints.Count == 1) { thisWaypoint.WarpFactor = 0; } selectedFleet.Waypoints[index] = thisWaypoint; warpFactor.Value = thisWaypoint.WarpFactor; warpText.Text = "Warp " + thisWaypoint.WarpFactor; if (index > 0 && thisWaypoint.WarpFactor > 0) { Waypoint from = selectedFleet.Waypoints[index - 1]; Waypoint to = selectedFleet.Waypoints[index]; double distance = PointUtilities.Distance(from.Position, to.Position); double time = distance / (to.WarpFactor * to.WarpFactor); double fuelUsed = selectedFleet.FuelConsumption(to.WarpFactor, empireState.Race) * time; legDistance.Text = string.Format("{0}", distance.ToString("f1")); legFuel.Text = string.Format("{0}", fuelUsed.ToString("f1")); legTime.Text = string.Format("{0}", time.ToString("f1")); } else { legDistance.Text = "0"; legFuel.Text = "0"; legTime.Text = "0"; } Waypoint previous = null; double fuelRequired = 0; // Sum up the total fuel required for all waypoints in the current // route (as long as there is more than one waypoint). foreach (Waypoint waypoint in selectedFleet.Waypoints) { if (previous != null && waypoint.WarpFactor > 0) { double distance = PointUtilities.Distance(waypoint.Position, previous.Position); int warp = waypoint.WarpFactor; double speed = warp * warp; double travelTime = distance / speed; fuelRequired += selectedFleet.FuelConsumption(warp, empireState.Race) * travelTime; } previous = waypoint; } System.Drawing.Color color = fuelRequired > selectedFleet.FuelAvailable ? System.Drawing.Color.Red : System.Drawing.Color.Black; routeFuelUse.ForeColor = color; label3.ForeColor = color; label5.ForeColor = color; routeFuelUse.Text = fuelRequired.ToString("f1"); }
/// <Summary> /// Build a list of all Minefields that are visible to the player. /// </Summary> /// <remarks> /// This consists /// of: /// /// (1) Minefields owned by the player /// (2) Minefiels within the range of scanners on ships owned by the player /// (3) Minefields within the range of scanners on planets owned by the player /// </remarks> private void DetermineVisibleMinefields() { List <Fleet> playersFleets = new List <Fleet>(); foreach (Fleet fleet in clientState.EmpireState.OwnedFleets.Values) { if (fleet.Owner == clientState.EmpireState.Id) { playersFleets.Add(fleet); } } // ------------------------------------------------------------------- // (1) First the easy one. Minefields owned by the player. // ------------------------------------------------------------------- foreach (Minefield minefield in this.turnData.AllMinefields.Values) { if (minefield.Owner == clientState.EmpireState.Id) { this.visibleMinefields[minefield.Key] = minefield; } } // ------------------------------------------------------------------- // (2) Not so easy. Minefields within the scanning range of the // player's ships. // ------------------------------------------------------------------- foreach (Fleet fleet in playersFleets) { foreach (Minefield minefield in this.turnData.AllMinefields.Values) { bool isIn = PointUtilities.CirclesOverlap( fleet.Position, minefield.Position, fleet.ScanRange, minefield.Radius); if (isIn == true) { this.visibleMinefields[minefield.Key] = minefield; } } } // ------------------------------------------------------------------- // (3) Now that we know how to deal with ship scanners planet scanners // are just the same. // ------------------------------------------------------------------- foreach (Minefield minefield in turnData.AllMinefields.Values) { foreach (Star report in clientState.EmpireState.OwnedStars.Values) { if (report.Owner == clientState.EmpireState.Id) { bool isIn = PointUtilities.CirclesOverlap( report.Position, minefield.Position, report.ScanRange, minefield.Radius); if (isIn == true) { this.visibleMinefields[minefield.Key] = minefield; } } } } }
/// ---------------------------------------------------------------------------- /// <Summary> /// Populate the display. We use unbound population because some of the fields /// need a little logic to decode (we don't just have a bunch of strings). /// </Summary> /// <param name="sender">The source of the event.</param> /// <param name="e">A <see cref="EventArgs"/> that contains the event data.</param> /// ---------------------------------------------------------------------------- private void OnLoad(object sender, EventArgs e) { const int NumColumns = 11; this.fleetGridView.Columns[6].Name = "Cargo"; this.fleetGridView.AutoSize = true; foreach (Fleet fleet in empireState.OwnedFleets.Values) { if (fleet.Owner == empireState.Id) { if (fleet.Type == ItemType.Starbase) { continue; } string location; if (fleet.InOrbit != null) { location = fleet.InOrbit.Name; } else { location = "Space at " + fleet.Position.ToString(); } string destination = "-"; string eta = "-"; string task = "-"; if (fleet.Waypoints.Count > 1) { Waypoint waypoint = fleet.Waypoints[1]; destination = waypoint.Destination; if (!(waypoint.Task is NoTask)) { task = waypoint.Task.Name; } double distance = PointUtilities.Distance( waypoint.Position, fleet.Position); double speed = waypoint.WarpFactor * waypoint.WarpFactor; double time = distance / speed; eta = time.ToString("F1"); } Cargo cargo = fleet.Cargo; StringBuilder cargoText = new StringBuilder(); cargoText.AppendFormat( "{0} {1} {2} {3}", cargo.Ironium, cargo.Boranium, cargo.Germanium, cargo.ColonistsInKilotons); int i = 0; string[] row = new string[NumColumns]; row[i++] = fleet.Name; row[i++] = location; row[i++] = destination; row[i++] = eta; row[i++] = task; row[i++] = fleet.FuelAvailable.ToString("f1"); row[i++] = cargoText.ToString(); row[i++] = fleet.Composition.Count.ToString(System.Globalization.CultureInfo.InvariantCulture); row[i++] = "-"; row[i++] = fleet.BattlePlan; row[i++] = fleet.Mass.ToString(System.Globalization.CultureInfo.InvariantCulture); this.fleetGridView.Rows.Add(row); } } this.fleetGridView.AutoResizeColumns(); }
/// <summary> /// Move stacks towards their targets (if any). Record each movement in the /// battle report. /// </summary> /// <param name="battlingStacks">All stacks in the battle.</param> public void MoveStacks(List <Stack> battlingStacks) { // Movement in Squares per Round // Round // Movement 1 2 3 4 5 6 7 8 // 1/2 1 0 1 0 1 0 1 0 // 3/4 1 1 0 1 1 1 0 1 // 1 1 1 1 1 1 1 1 1 // 1 1/4 2 1 1 1 2 1 1 1 // 1 1/2 2 1 2 1 2 1 2 1 // 1 3/4 2 2 1 2 2 2 1 2 // 2 2 2 2 2 2 2 2 2 // 2 1/4 3 2 2 2 3 2 2 2 // 2 1/2 3 2 3 2 3 2 3 2 // repeats for rounds 9 - 16 // In Stars! each round breaks movement into 3 phases. // Phase 1: All stacks that can move 3 squares this round get to move 1 square. // Phase 2: All stacks that can move 2 or more squares this round get to move 1 square. // Phase 3: All stacks that can move this round get to move 1 square. // TODO (priority 3) - verify that a ship should be able to move 1 square per phase if it has 3 move points, or is it limited to 1 per turn? for (var phase = 1; phase <= movementPhasesPerRound; phase++) { // TODO (priority 5) - Move in order of ship mass, juggle by 15% foreach (Stack stack in battlingStacks) { if (stack.Target != null & !stack.IsStarbase) { NovaPoint from = stack.Position; NovaPoint to = stack.Target.Position; int movesThisRound = 1; // FIXME (priority 6) - kludge until I implement the above table if (stack.BattleSpeed <= 0.5) { movesThisRound = movementTable[0, battleRound % 8]; } else if (stack.BattleSpeed <= 0.75) { movesThisRound = movementTable[1, battleRound % 8]; } else if (stack.BattleSpeed <= 1.0) { movesThisRound = movementTable[2, battleRound % 8]; } else if (stack.BattleSpeed <= 1.25) { movesThisRound = movementTable[3, battleRound % 8]; } else if (stack.BattleSpeed <= 1.5) { movesThisRound = movementTable[4, battleRound % 8]; } else if (stack.BattleSpeed <= 1.75) { movesThisRound = movementTable[5, battleRound % 8]; } else if (stack.BattleSpeed <= 2.0) { movesThisRound = movementTable[6, battleRound % 8]; } else if (stack.BattleSpeed <= 2.25) { movesThisRound = movementTable[7, battleRound % 8]; } else { // stack.BattleSpeed > 2.25 movesThisRound = movementTable[8, battleRound % 8]; } bool moveThisPhase = true; switch (phase) { case 1: { moveThisPhase = movesThisRound == 3; break; } case 2: { moveThisPhase = movesThisRound >= 2; break; } case 3: { moveThisPhase = movesThisRound >= 1; break; } } // stack can move only after accumulating at least 1 move point, and after doing so expends that 1 move point if (moveThisPhase) { stack.Position = PointUtilities.BattleMoveTo(from, to); // Update the battle report with these movements. BattleStepMovement report = new BattleStepMovement(); report.StackKey = stack.Key; report.Position = stack.Position; battle.Steps.Add(report); } } // TODO (priority 7) - shouldn't stacks without targets flee the battle if their strategy says to do so? they're sitting ducks now! } } }
private void Scan(EmpireData empire) { foreach (Mappable scanner in empire.IterateAllMappables()) { int scanRange = 0; int penScanRange = 0; //Do some self scanning (Update reports) and set ranges.. if (scanner is Star) { scanRange = (scanner as Star).ScanRange; penScanRange = (scanner as Star).ScanRange; // TODO:(priority 6) Planetary Pen-Scan not implemented yet. empire.StarReports[scanner.Name].Update(scanner as Star, ScanLevel.Owned, serverState.TurnYear); } else { scanRange = (scanner as Fleet).ScanRange; penScanRange = (scanner as Fleet).PenScanRange; empire.FleetReports[scanner.Key].Update(scanner as Fleet, ScanLevel.Owned, empire.TurnYear); } // Scan everything foreach (Mappable scanned in serverState.IterateAllMappables()) { // ...That isn't ours! if (scanned.Owner == empire.Id) { continue; } ScanLevel scanLevel = ScanLevel.None; double range = 0; range = PointUtilities.Distance(scanner.Position, scanned.Position); if (scanned is Star) { Star star = scanned as Star; // There are two ways to get information from a Star: // 1. In orbit with a fleet, // 2. Not in orbit with a Pen Scan (fleet or star). // Non penetrating distance scans won't tell anything about it. if ((scanner is Fleet) && range == 0) { scanLevel = (scanner as Fleet).CanScan ? ScanLevel.InDeepScan : ScanLevel.InPlace; } else // scanner is Star or non orbiting Fleet { scanLevel = (range <= penScanRange) ? ScanLevel.InDeepScan : ScanLevel.None; } // Dont update if we didn't scan to allow report to age. if (scanLevel == ScanLevel.None) { continue; } if (empire.StarReports.ContainsKey(scanned.Name)) { empire.StarReports[scanned.Name].Update((scanned as Star), scanLevel, serverState.TurnYear); } else { empire.StarReports.Add(scanned.Name, (scanned as Star).GenerateReport(scanLevel, serverState.TurnYear)); } } else // scanned is Fleet { // Fleets are simple as scan levels (PenScan for example) won't affect them. We only // care for non penetrating distance scans. if (range > scanRange) { continue; } // Check if we have a record of this design(s). foreach (ShipToken token in (scanned as Fleet).Composition.Values) { if (empire.EmpireReports[scanned.Owner].Designs.ContainsKey(token.Design.Key)) { continue; } // If not, add just the empty Hull. ShipDesign newDesign = new ShipDesign(token.Design); newDesign.Key = token.Design.Key; newDesign.ClearAllocated(); empire.EmpireReports[scanned.Owner].Designs.Add(newDesign.Key, newDesign); } if (!empire.FleetReports.ContainsKey(scanned.Key)) { empire.FleetReports.Add(scanned.Key, (scanned as Fleet).GenerateReport(ScanLevel.InScan, serverState.TurnYear)); } else { empire.FleetReports[scanned.Key].Update((scanned as Fleet), ScanLevel.InScan, serverState.TurnYear); } } } } }
private IReadOnlyList <string> GetRoute(IReadOnlyDictionary <Point, char> view) { const char scaffold = '#'; var route = new List <string>(); var currentDirection = Direction.Up; var currentLocation = view.Single(pixel => pixel.Value == '^').Key; var keepGoing = true; var length = 0; while (keepGoing) { var lookAheadLocation = GetLookAheadLocation(currentLocation, currentDirection); view.TryGetValue(lookAheadLocation, out var lookAheadPixel); if (lookAheadPixel == scaffold) { // Keep going in same direction length++; currentLocation = currentDirection.Id switch { 'U' => PointUtilities.MoveUp(currentLocation), 'R' => PointUtilities.MoveRight(currentLocation), 'D' => PointUtilities.MoveDown(currentLocation), 'L' => PointUtilities.MoveLeft(currentLocation), _ => throw new ArgumentOutOfRangeException() }; } else { lookAheadLocation = GetLookAheadLocation(currentLocation, currentDirection.TurnRight); view.TryGetValue(lookAheadLocation, out lookAheadPixel); if (lookAheadPixel == scaffold) { // Scaffold on the right if (length > 0) { route.Add(length.ToString()); } route.Add(Direction.Right.Id.ToString()); currentDirection = currentDirection.TurnRight; length = 0; } else { lookAheadLocation = GetLookAheadLocation(currentLocation, currentDirection.TurnLeft); view.TryGetValue(lookAheadLocation, out lookAheadPixel); if (lookAheadPixel == scaffold) { // Scaffold on the left if (length > 0) { route.Add(length.ToString()); } route.Add(Direction.Left.Id.ToString()); currentDirection = currentDirection.TurnLeft; length = 0; } else { // Scaffold only behind so must have reached the end if (length > 0) { route.Add(length.ToString()); } keepGoing = false; } } } } return(route); }