Solar System Explorer — Bonus 3 of 3
Solar System Explorer: Advanced Extensions
Part 3 of 3 — Adding new location types, a fuel system, distance calculation, and weather
This bonus instalment extends the Solar System Explorer with features that demonstrate more advanced OOP concepts: inheritance from a concrete class (not just an abstract base), composition, and richer polymorphic behaviour.
New Location Types
Asteroid
public class Asteroid : CelestialLocation
{
public double Size { get; set; } // diameter in km
public bool IsMiningOperation { get; set; }
public Asteroid(string name, double size, bool isMiningOperation, string funFact)
: base(name, funFact)
{
Size = size;
IsMiningOperation = isMiningOperation;
}
public override string GetInfo()
{
string miningStatus = IsMiningOperation
? "Active mining operation — watch out for flying rocks!"
: "Pristine asteroid — great for sightseeing!";
return $"{base.GetInfo()}\nSize: {Size} km diameter\n{miningStatus}";
}
public override string GetArrivalMessage()
{
string activity = IsMiningOperation
? "Mining lasers detected! Activating protective shields!"
: "Beautiful quiet rock in space. Perfect for some zero-gravity meditation.";
return $"Anchoring to asteroid {Name} with gravity hooks! {activity}";
}
public override string GetDepartureMessage()
{
return $"Detaching from {Name}. " +
(IsMiningOperation ? "Collecting those space rocks was exhausting!"
: "Mind the floating debris on the way out!");
}
}
BlackHole
public class BlackHole : CelestialLocation
{
public double Mass { get; set; } // in solar masses
public bool IsSupermassive { get; set; }
public BlackHole(string name, double mass, bool isSupermassive, string funFact)
: base(name, funFact)
{
Mass = mass;
IsSupermassive = isSupermassive;
}
public override string GetInfo()
{
string sizeCategory = IsSupermassive ? "Supermassive" : "Stellar";
return $"{base.GetInfo()}\n{sizeCategory} Black Hole\nMass: {Mass} solar masses\n" +
"WARNING: Approach within safe distance only. Time dilation in effect.";
}
public override string GetArrivalMessage()
{
if (IsSupermassive)
return $"Approaching {Name} at safe observation distance. " +
"Time dilation detected — you may age differently than expected. " +
"Please synchronize your watches upon return.";
return $"Observing stellar black hole {Name} from a very safe distance. " +
"Spaghettification risk assessed: high. Staying right here.";
}
public override string GetDepartureMessage()
{
return $"Firing escape thrusters at maximum power to leave {Name}'s gravitational influence. " +
"Note: Due to time dilation, slightly more time has passed back home than it feels like.";
}
}
Fuel System
public class EnhancedSolarSystemMap : SolarSystemMap
{
public double FuelLevel { get; private set; }
public double MaxFuel { get; private set; }
public EnhancedSolarSystemMap(double maxFuel = 1000.0) : base()
{
MaxFuel = maxFuel;
FuelLevel = maxFuel;
}
public double CalculateTravelCost(CelestialLocation from, CelestialLocation to)
{
double baseCost = 50.0;
// Planet-to-planet: cost based on distance difference
if (from is Planet fromPlanet && to is Planet toPlanet)
return baseCost + Math.Abs(fromPlanet.DistanceFromSun - toPlanet.DistanceFromSun) * 0.1;
// Moons are cheaper to reach from their parent planet
if (to is Moon moon && from is Planet planet && moon.ParentPlanet == planet.Name)
return baseCost * 0.5;
// Space stations are cheap to dock with
if (to is SpaceStation)
return baseCost * 0.3;
// Black holes: premium pricing for the experience
if (to is BlackHole)
return baseCost * 5.0;
return baseCost;
}
public double CalculateDistance(CelestialLocation from, CelestialLocation to)
{
if (from is Planet fromPlanet && to is Planet toPlanet)
return Math.Abs(fromPlanet.DistanceFromSun - toPlanet.DistanceFromSun);
return 50.0; // simplified for moons/stations
}
public void Refuel(double amount)
{
double added = Math.Min(amount, MaxFuel - FuelLevel);
FuelLevel += added;
Console.WriteLine($"Refueled +{added:F1} units. Tank: {FuelLevel:F1}/{MaxFuel:F1}");
}
public List<CelestialLocation> ShowNearbyLocations(double maxDistance = 500.0)
{
return GetAllLocations()
.Where(loc => loc != CurrentLocation && CalculateDistance(CurrentLocation, loc) <= maxDistance)
.OrderBy(loc => CalculateDistance(CurrentLocation, loc))
.ToList();
}
}
Weather System (Composition)
WeatherSystem is not a subclass of anything — it's a standalone component that could be held by the map and consulted during travel.
public class WeatherCondition
{
public string Name { get; set; }
public string Description { get; set; }
public bool AffectsTravel { get; set; }
public double FuelCostMultiplier { get; set; }
public WeatherCondition(string name, string description, bool affectsTravel, double fuelCostMultiplier = 1.0)
{
Name = name;
Description = description;
AffectsTravel = affectsTravel;
FuelCostMultiplier = fuelCostMultiplier;
}
}
public class WeatherSystem
{
private static Random random = new Random();
private static List<WeatherCondition> possibleConditions = new List<WeatherCondition>
{
new WeatherCondition("Clear", "Perfect visibility. Stars visible in all directions.", false, 1.0),
new WeatherCondition("Solar Wind", "Charged particles from the sun. Minor navigation adjustment required.", true, 1.2),
new WeatherCondition("Meteor Shower", "Beautiful but hazardous debris field. Shields up!", true, 1.5),
new WeatherCondition("Cosmic Ray Burst", "High-energy particles. Electronic systems may be affected.", true, 1.3),
new WeatherCondition("Gravitational Anomaly", "Unexpected mass detected. Navigation computers working overtime.", true, 1.8),
new WeatherCondition("Nebula Cloud", "Dense ionized gas cloud. Visibility reduced but spectacular.", true, 1.4)
};
public static WeatherCondition GetCurrentCondition()
{
return possibleConditions[random.Next(possibleConditions.Count)];
}
public static void ReportWeather()
{
WeatherCondition condition = GetCurrentCondition();
Console.WriteLine($"\n=== Space Weather Report ===");
Console.WriteLine($"Condition: {condition.Name}");
Console.WriteLine($"Description: {condition.Description}");
if (condition.AffectsTravel)
Console.WriteLine($"Travel Impact: Fuel costs x{condition.FuelCostMultiplier:F1}");
else
Console.WriteLine("Travel Impact: None");
}
}
Full Demo
class Program
{
static void Main(string[] args)
{
Console.WriteLine("=== Solar System Explorer 3.0: Advanced Edition ===\n");
WeatherSystem.ReportWeather();
EnhancedSolarSystemMap solarSystem = new EnhancedSolarSystemMap(maxFuel: 2000.0);
solarSystem.AddLocation(new Planet("Mars", 227.9, false, "Home to Olympus Mons!"));
solarSystem.AddLocation(new Planet("Saturn", 1433.5, true, "Ring system fills the Earth-Moon distance!"));
solarSystem.AddLocation(new Moon("Europa", "Jupiter", true, "Subsurface ocean detected!"));
solarSystem.AddLocation(new SpaceStation("Refuelling Depot Alpha", "Mars", 50,
"Best fuel prices in the inner solar system!"));
solarSystem.AddLocation(new Asteroid("Ceres", 940, true,
"Largest object in the asteroid belt — with miners!"));
solarSystem.AddLocation(new BlackHole("Cygnus X-1", 14.8, false,
"First black hole ever confirmed. Classic."));
Console.WriteLine("\n=== Journey Begins ===");
solarSystem.TravelTo("Refuelling Depot Alpha");
solarSystem.Refuel(500);
solarSystem.TravelTo("Saturn");
solarSystem.TravelTo("Cygnus X-1");
}
}
What These Extensions Demonstrate
Inheritance from a concrete class: EnhancedSolarSystemMap extends SolarSystemMap. This adds fuel tracking and distance calculation without rewriting the core map logic.
Composition over inheritance: WeatherSystem is not a subclass of anything. The map could hold a WeatherSystem instance and consult it during travel calculations. This keeps concerns separated without forcing an inheritance relationship.
Polymorphism still works: Asteroid and BlackHole are new subclasses of CelestialLocation. TravelTo doesn't need to know about either — it just calls GetArrivalMessage() and the right version runs.
Open/closed principle: The system is open for extension (add a new location type) and closed for modification (no changes to existing code needed to support it).
See Solar System 1 of 3 for the original procedural-vs-OOP comparison, or Solar System 2 of 3 for the notebook version.