Solar System Explorer — OOP 1 of 3
Solar System Explorer: A Whimsical Journey Through OOP
Part 1 of 3 — Managing Solar System Locations with Basic Data Structures, then rebuilding with classes
Part 1: The Procedural Approach
Let's start by creating a simple solar system explorer using basic C# data structures — lists and dictionaries — and see where it breaks down.
using System;
using System.Collections.Generic;
class Program
{
static void Main(string[] args)
{
// Create lists of locations by type
List<Dictionary<string, object>> planets = new List<Dictionary<string, object>>
{
new Dictionary<string, object>
{
{"name", "Mars"},
{"distanceFromSun", 227.9},
{"hasRings", false},
{"funFact", "Home to Olympus Mons, the tallest mountain in the solar system!"}
},
new Dictionary<string, object>
{
{"name", "Saturn"},
{"distanceFromSun", 1433.5},
{"hasRings", true},
{"funFact", "Its rings would make a fabulous hula hoop for a giant!"}
}
};
List<Dictionary<string, object>> moons = new List<Dictionary<string, object>>
{
new Dictionary<string, object>
{
{"name", "Europa"},
{"parentPlanet", "Jupiter"},
{"hasWater", true},
{"funFact", "Has an ocean under its icy crust where space fish might be doing the backstroke!"}
},
new Dictionary<string, object>
{
{"name", "Titan"},
{"parentPlanet", "Saturn"},
{"hasWater", false},
{"funFact", "Has lakes of liquid methane! Not recommended for skinny dipping."}
}
};
List<Dictionary<string, object>> spaceStations = new List<Dictionary<string, object>>
{
new Dictionary<string, object>
{
{"name", "ISS"},
{"orbitingBody", "Earth"},
{"crewCapacity", 7},
{"funFact", "Astronauts see 16 sunrises every day! Talk about needing coffee."}
},
new Dictionary<string, object>
{
{"name", "Lunar Gateway"},
{"orbitingBody", "Moon"},
{"crewCapacity", 4},
{"funFact", "Future lunar outpost where astronauts will practice their moon walks!"}
}
};
// Track current location
Dictionary<string, object> currentLocation = planets[0]; // Start on Mars
string currentLocationType = "planet";
Console.WriteLine("Welcome to the Solar System Explorer 1.0!");
Console.WriteLine($"You are currently on {currentLocation["name"]}, a {currentLocationType}.");
Console.WriteLine($"Fun fact: {currentLocation["funFact"]}");
TravelTo("Titan", currentLocation, currentLocationType, planets, moons, spaceStations);
}
static void TravelTo(string destinationName, Dictionary<string, object> currentLocation, string currentLocationType,
List<Dictionary<string, object>> planets, List<Dictionary<string, object>> moons,
List<Dictionary<string, object>> spaceStations)
{
Dictionary<string, object> destination = null;
string destinationType = null;
foreach (var planet in planets)
{
if ((string)planet["name"] == destinationName)
{
destination = planet;
destinationType = "planet";
break;
}
}
if (destination == null)
{
foreach (var moon in moons)
{
if ((string)moon["name"] == destinationName)
{
destination = moon;
destinationType = "moon";
break;
}
}
}
if (destination == null)
{
foreach (var station in spaceStations)
{
if ((string)station["name"] == destinationName)
{
destination = station;
destinationType = "space station";
break;
}
}
}
if (destination == null)
{
Console.WriteLine($"Oh no! {destinationName} couldn't be found on any star chart!");
return;
}
Console.WriteLine($"\nFiring up the warp drives! Zooooom!");
Console.WriteLine($"Traveling from {currentLocation["name"]} to {destination["name"]}...");
if (currentLocationType == "planet" && destinationType == "moon")
Console.WriteLine("Planet to moon travel: Reducing thrusters as we approach this smaller celestial body!");
else if (currentLocationType == "moon" && destinationType == "planet")
Console.WriteLine("Moon to planet travel: Increasing gravity compensators for landing on this massive orb!");
else if (destinationType == "space station")
Console.WriteLine("Approaching space station: Remember to use the docking protocols and not the parking brake!");
Console.WriteLine($"Arrival complete at {destination["name"]}, a {destinationType}.");
Console.WriteLine($"Fun fact: {destination["funFact"]}");
}
}
Limitations of the Procedural Approach
This works, but it has real problems:
- Repetitive code — We need separate loops to find each location type, and this gets unmanageable as we add more types.
- Parameter overload — Look at all those parameters in
TravelTo. It's like trying to pack an entire rocket in your carry-on. - State management — Tracking current location and its type separately is error-prone. What if they get out of sync?
- Separation of data and logic — The travel logic is completely separated from the location data.
- No type safety — We're using strings to track types, which is about as reliable as delivering pizza to Jupiter.
Let's fix all of this with OOP.
Part 2: A Class-Based Solar System Explorer
using System;
// Base class for all space locations
public abstract class CelestialLocation
{
public string Name { get; set; }
public string FunFact { get; set; }
public CelestialLocation(string name, string funFact)
{
Name = name;
FunFact = funFact;
}
public virtual string GetInfo()
{
return $"{Name}: {FunFact}";
}
// Each location type provides its own messaging
public abstract string GetArrivalMessage();
public abstract string GetDepartureMessage();
}
public class Planet : CelestialLocation
{
public double DistanceFromSun { get; set; } // millions of km
public bool HasRings { get; set; }
public Planet(string name, double distanceFromSun, bool hasRings, string funFact)
: base(name, funFact)
{
DistanceFromSun = distanceFromSun;
HasRings = hasRings;
}
public override string GetInfo()
{
string ringsInfo = HasRings ? "Has magnificent rings!" : "No rings, how ordinary.";
return $"{base.GetInfo()}\nDistance from Sun: {DistanceFromSun} million km\n{ringsInfo}";
}
public override string GetArrivalMessage()
{
return $"Touchdown on planet {Name}! Gravity stabilizers engaged. " +
$"{(HasRings ? "Wow, those rings look even better up close!" : "Clear skies for landing!")}";
}
public override string GetDepartureMessage()
{
return $"Blasting off from {Name}'s gravitational pull! Engaging warp speed in 3...2...1...";
}
}
public class Moon : CelestialLocation
{
public string ParentPlanet { get; set; }
public bool HasWater { get; set; }
public Moon(string name, string parentPlanet, bool hasWater, string funFact)
: base(name, funFact)
{
ParentPlanet = parentPlanet;
HasWater = hasWater;
}
public override string GetInfo()
{
string waterInfo = HasWater
? "Contains water - potential for space swimming!"
: "No water detected - bring your own beverages.";
return $"{base.GetInfo()}\nOrbits: {ParentPlanet}\n{waterInfo}";
}
public override string GetArrivalMessage()
{
return $"Soft landing on {Name}, a moon of {ParentPlanet}! " +
"Reduced gravity detected - watch your step or you might accidentally do a backflip!";
}
public override string GetDepartureMessage()
{
return $"Gently pushing off from {Name}'s surface. The views of {ParentPlanet} from here are spectacular!";
}
}
public class SpaceStation : CelestialLocation
{
public string OrbitingBody { get; set; }
public int CrewCapacity { get; set; }
public SpaceStation(string name, string orbitingBody, int crewCapacity, string funFact)
: base(name, funFact)
{
OrbitingBody = orbitingBody;
CrewCapacity = crewCapacity;
}
public override string GetInfo()
{
return $"{base.GetInfo()}\nOrbits: {OrbitingBody}\nCrew Capacity: {CrewCapacity} space explorers";
}
public override string GetArrivalMessage()
{
return $"*Mechanical docking sounds* Connected to {Name} space station! " +
"Please wait for the airlock to pressurize.";
}
public override string GetDepartureMessage()
{
return $"Disconnecting docking clamps from {Name}. Don't forget to wave to the crew!";
}
}
Now the SolarSystemMap class that manages everything:
using System;
using System.Collections.Generic;
using System.Linq;
public class SolarSystemMap
{
private List<CelestialLocation> locations;
public CelestialLocation CurrentLocation { get; private set; }
public SolarSystemMap()
{
locations = new List<CelestialLocation>();
}
public void AddLocation(CelestialLocation location)
{
locations.Add(location);
if (locations.Count == 1)
{
CurrentLocation = location;
Console.WriteLine($"Starting our adventure at {location.Name}!");
Console.WriteLine(location.GetArrivalMessage());
Console.WriteLine($"Fun Fact: {location.FunFact}");
}
}
public void DisplayAllLocations()
{
Console.WriteLine("\n Solar System Map: All Known Locations");
var planets = locations.OfType<Planet>().ToList();
var moons = locations.OfType<Moon>().ToList();
var stations = locations.OfType<SpaceStation>().ToList();
Console.WriteLine($"\nPlanets ({planets.Count}):");
foreach (var planet in planets)
{
string current = planet == CurrentLocation ? " [YOU ARE HERE]" : "";
Console.WriteLine($" - {planet.Name} ({planet.DistanceFromSun} million km from Sun){current}");
}
Console.WriteLine($"\nMoons ({moons.Count}):");
foreach (var moon in moons)
{
string current = moon == CurrentLocation ? " [YOU ARE HERE]" : "";
Console.WriteLine($" - {moon.Name} (Orbits: {moon.ParentPlanet}){current}");
}
Console.WriteLine($"\nSpace Stations ({stations.Count}):");
foreach (var station in stations)
{
string current = station == CurrentLocation ? " [YOU ARE HERE]" : "";
Console.WriteLine($" - {station.Name} (Orbits: {station.OrbitingBody}, Crew: {station.CrewCapacity}){current}");
}
}
public bool TravelTo(string destinationName)
{
CelestialLocation destination = locations.FirstOrDefault(loc => loc.Name == destinationName);
if (destination == null)
{
Console.WriteLine($"Error: {destinationName} not found in our star charts!");
return false;
}
if (destination == CurrentLocation)
{
Console.WriteLine($"You're already at {destinationName}! Save fuel for interstellar donut runs.");
return false;
}
Console.WriteLine("\n=== COMMENCING TRAVEL SEQUENCE ===");
Console.WriteLine(CurrentLocation.GetDepartureMessage());
Console.WriteLine($"\nTRAVELING from {CurrentLocation.Name} to {destination.Name}");
Console.WriteLine("\n" + destination.GetArrivalMessage());
Console.WriteLine($"Fun Fact: {destination.FunFact}");
CurrentLocation = destination;
return true;
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Welcome to Solar System Explorer 2.0!");
SolarSystemMap solarSystem = new SolarSystemMap();
solarSystem.AddLocation(new Planet("Mars", 227.9, false,
"Home to Olympus Mons, the tallest mountain in the solar system!"));
solarSystem.AddLocation(new Planet("Saturn", 1433.5, true,
"Its rings would make a fabulous hula hoop for a giant!"));
solarSystem.AddLocation(new Planet("Earth", 149.6, false,
"The only planet known to have chocolate cake. Coincidence? I think not."));
solarSystem.AddLocation(new Moon("Europa", "Jupiter", true,
"Has an ocean under its icy crust where space fish might be doing the backstroke!"));
solarSystem.AddLocation(new Moon("Titan", "Saturn", false,
"Has lakes of liquid methane! Not recommended for skinny dipping."));
solarSystem.AddLocation(new Moon("Luna", "Earth", false,
"Earth's moon is simply called 'the Moon', which shows a severe lack of creativity."));
solarSystem.AddLocation(new SpaceStation("ISS", "Earth", 7,
"Astronauts see 16 sunrises every day! Talk about needing coffee."));
solarSystem.AddLocation(new SpaceStation("Lunar Gateway", "Moon", 4,
"Future lunar outpost where astronauts will practice their moon walks!"));
solarSystem.DisplayAllLocations();
// Interactive travel
bool exploring = true;
while (exploring)
{
Console.WriteLine("\n=== TRAVEL MENU ===");
Console.WriteLine($"You are currently at: {solarSystem.CurrentLocation.Name}");
Console.WriteLine("Where would you like to go? (type a name, 'map', or 'exit')");
string input = Console.ReadLine();
if (input.ToLower() == "exit")
{
exploring = false;
Console.WriteLine("Thank you for exploring the solar system!");
}
else if (input.ToLower() == "map")
{
solarSystem.DisplayAllLocations();
}
else
{
solarSystem.TravelTo(input);
}
}
}
}
Part 3: Benefits of the Class-Based Approach
Comparing the two implementations:
- Clean abstraction —
CelestialLocationdefines the common interface for all locations. - Polymorphism — Each location type implements its own arrival and departure messages.
- Encapsulation — Travel logic lives in
SolarSystemMap, not scattered across the program. - State management — Current location is an object reference; type/object mismatch is impossible.
- Extensibility — Adding a new location type doesn't require changing
TravelTo. - Richer behaviours — Each location can have specialised behaviours without cluttering the main program.
Part 4: Taking It Further
Some directions to extend:
// Adding a travel cost system
public double CalculateTravelCost(CelestialLocation from, CelestialLocation to)
{
double baseCost = 100;
if (from is Planet fromPlanet && to is Planet toPlanet)
return baseCost * Math.Abs(fromPlanet.DistanceFromSun - toPlanet.DistanceFromSun) / 50;
if (to is Moon moon)
return baseCost + (moon.HasWater ? 50 : 20); // Water moons are tourist attractions!
return baseCost;
}
// Adding a new location type
public class Asteroid : CelestialLocation
{
public double Size { get; set; }
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 GetArrivalMessage()
{
return $"Anchoring to asteroid {Name} with gravity hooks! Remember to wear magnetic boots.";
}
public override string GetDepartureMessage()
{
return $"Detaching from {Name}. Mind the floating debris!";
}
}
Adding a new type requires no changes to SolarSystemMap.TravelTo — it just works. That's the real payoff of the OOP approach.
Continue to Solar System 2 of 3 for the notebook version, or Solar System Bonus 3 of 3 for advanced extensions including a fuel system and weather.