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:

  1. Repetitive code — We need separate loops to find each location type, and this gets unmanageable as we add more types.
  2. Parameter overload — Look at all those parameters in TravelTo. It's like trying to pack an entire rocket in your carry-on.
  3. State management — Tracking current location and its type separately is error-prone. What if they get out of sync?
  4. Separation of data and logic — The travel logic is completely separated from the location data.
  5. 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:

  1. Clean abstractionCelestialLocation defines the common interface for all locations.
  2. Polymorphism — Each location type implements its own arrival and departure messages.
  3. Encapsulation — Travel logic lives in SolarSystemMap, not scattered across the program.
  4. State management — Current location is an object reference; type/object mismatch is impossible.
  5. Extensibility — Adding a new location type doesn't require changing TravelTo.
  6. 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.

Built with LogoFlowershow