Skip to content

Latest commit

History

17 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

SpacePlusPlus (space++)

Overview

SpacePlusPlus is a terminal-based space exploration and trading game. Players take on the role of a Captain, managing a crew (via skills), a ship, and a growing inventory of valuable commodities. The goal is to amass 100,000 credits through strategic trading and survival before your contract cycles expire.

Core Game Mechanics

  • Trading & Economy: The heart of the game lies in trading across a procedurally generated Universe. System locations, connections, and base market volatility are generated from a specific game seed. Players buy low in one system and jump to another to sell high. The trade skill allows players to manipulate market prices, providing a significant advantage in profit margins.
  • Transit & Risk: Traveling between star systems consumes fuel and one cycle of time. During transit, the GameEngine triggers random events (e.g., Pirate Ambushes or Asteroid Fields) that test the player's skills and ship integrity.
  • Ship Management: Players must balance fuel, hull integrity, and cargo capacity. Upgrading firepower or repairing the hull is essential for survival, but requires careful credit management. Every ship class also features a unique, manually activated trait.
  • Skill Progression: As players survive events and complete tasks, they gain experience. Every 100 XP triggers a skill level-up in a rotation (Navigation, Combat, Trade), providing permanent buffs to gameplay mechanics.

UML Class Diagram

classDiagram
class Skills {
<<struct>>
+int navigation
+int combat
+int trade
}
class InventoryItem {
<<struct>>
+string name
+int costBasis
}
class StarSystem {
<<struct>>
+string name
+int x
+int y
+int distance
+Market localMarket
}
class Captain {
-string _name
-long _credits
-int _experience
-Skills _skills
-vector~InventoryItem~ _inventory
+Captain()
+setName(string name)
+gainCredits(long credits)
+spendCredits(long credits) bool
+gainExperience(int amount) string
+setSkills(const Skills& skills)
+setInventory(const vector~InventoryItem~& inv)
+addItem(string name, int costBasis)
+removeItem(string name) bool
+getName() string
+getCredits() long
+getExperience() int
+getSkills() Skills
+getInventoryItems() vector~InventoryItem~
+getInventory() vector~string~
}
Captain *-- Skills : contains
Captain *-- InventoryItem : contains
class Ship {
<<abstract>>
#string _name
#int _hull
#int _maxHull
#int _fuel
#int _fuelCapacity
#int _cargoCapacity
#int _firepower
#float _riskMultiplier
#float _baseRiskMultiplier
+Ship(string, int, int, int, int, float)
+setName(string name)
+takeDamage(int damage) bool
+consumeFuel(int amount) bool
+refuel(int amount)
+repairHull(int amount)
+upgradeFirepower(int amount)
+setHull(int hull)
+setFuel(int fuel)
+setCargoCapacity(int capacity)
+setFirepower(int firepower)
+resetRiskMultiplier()
+applyUniqueTrait(Captain& player) string
+getName() string
+getHull() int
+getMaxHull() int
+getFuel() int
+getFuelCapacity() int
+getCargoCapacity() int
+getFirepower() int
+getRiskMultiplier() float
}
Ship <|-- LightFighter
class LightFighter {
+LightFighter()
+applyUniqueTrait(Captain& player) string
}
Ship <|-- HeavyFreighter
class HeavyFreighter {
+HeavyFreighter()
+applyUniqueTrait(Captain& player) string
}
Ship <|-- Smuggler
class Smuggler {
+Smuggler()
+applyUniqueTrait(Captain& player) string
}
Ship <|-- Scavenger
class Scavenger {
+Scavenger()
+applyUniqueTrait(Captain& player) string
}
class Event {
<<abstract>>
+~Event()
+getChoices(const Captain&, const Ship&) vector~string~
+trigger(Captain& captain, Ship& ship, int choice) string
}
Event <|-- PirateAmbush
class PirateAmbush {
+getChoices(const Captain&, const Ship&) vector~string~
+trigger(Captain& captain, Ship& ship, int choice) string
}
Event <|-- AsteroidField
class AsteroidField {
+getChoices(const Captain&, const Ship&) vector~string~
+trigger(Captain& captain, Ship& ship, int choice) string
}
Event <|-- SolarFlare
class SolarFlare {
+trigger(Captain& captain, Ship& ship, int choice) string
}
Event <|-- SpacePolicePatrol
class SpacePolicePatrol {
+getChoices(const Captain&, const Ship&) vector~string~
+trigger(Captain& captain, Ship& ship, int choice) string
}
Event <|-- DerelictSalvage
class DerelictSalvage {
+trigger(Captain& captain, Ship& ship, int choice) string
}
Event <|-- CometIntercept
class CometIntercept {
+getChoices(const Captain&, const Ship&) vector~string~
+trigger(Captain& captain, Ship& ship, int choice) string
}
Event <|-- DistressBeacon
class DistressBeacon {
+getChoices(const Captain&, const Ship&) vector~string~
+trigger(Captain& captain, Ship& ship, int choice) string
}
class Market {
-map~string, int~ _buyPrices
-map~string, int~ _sellPrices
+Market()
+generatePrices(unsigned int seed, float volatility)
+getBuyPrice(string itemName) int
+getSellPrice(string itemName) int
+getAvailableItems() map~string, int~
}
class Universe {
-unsigned int _currentSeed
-mt19937 _rngEngine
-map~string, StarSystem~ _galaxyMap
-map~string, vector~string~~ _connections
+generateGalaxy(unsigned int seed, float volatility)
+getNearbySystems(string currentSystemName) vector~StarSystem~
+getSystem(string name) StarSystem
+getRandomSystemName() string
}
Universe *-- StarSystem : contains
StarSystem *-- Market : contains
class UIManager {
-TerminalFrame _tf
-int VISUAL_START
-int VISUAL_END
-int LOG_START
-int LOG_END
-int MENU_START
-int MENU_END
+UIManager()
+clearScreen() const
+showHowToPlay()
+printMarketTable(const Market&, int) const
+printAsciiArt(string) const
+promptForName() string
+promptForSaveSlot() int
+showMessage(string)
+showMainMenu() int
+showDifficultyMenu() int
+showShipSelection() int
+showMarket(Market, Captain&, Ship&) string
+showEventChoices(vector~string~) int
+showSpaceportMenu(StarSystem, Captain&, const Ship&, int) int
+selectDestination(vector~StarSystem~) StarSystem
}
class SaveManager {
-string getFilenameForSlot(int slotID) const
+saveGame(int, const Captain&, const Ship&, unsigned int, int, int, string) bool
+loadGame(int, Captain&, unique_ptr~Ship~, unsigned int&, int&, int&, string&) bool
+doesSaveExist(int) bool
}
class runtime_error {
<<std>>
}
class GameOverException {
<<exception>>
}
runtime_error <|-- GameOverException
GameOverException <|-- HullBreachException
class HullBreachException {
}
GameOverException <|-- StrandedException
class StrandedException {
}
GameOverException <|-- NoCyclesLeftException
class NoCyclesLeftException {
}
GameOverException <|-- VictoryException
class VictoryException {
}
class GameEngine {
-unsigned int _currentSeed
-Captain _player
-unique_ptr~Ship~ _currentShip
-Universe _universe
-UIManager _ui
-SaveManager _saveSys
-GameState _currentState
-Difficulty _currentDifficulty
-RunConfig _currentConfig
-bool _isPlaying
-StarSystem _currentLocation
-StarSystem _currentDestination
-int _cyclesRemaining
-unique_ptr~Event~ _pendingEvent
-string _pendingArtType
-handleMainMenu()
-handleSpaceport()
-handleTransitPhase()
-handleEventState()
-pickRandomEvent()
-applyDifficultySettings()
-applyItemEffect(string)
public:
+GameEngine()
+run()
+startNewGame(Difficulty, int)
+loadGame()
}
GameEngine *-- Captain : manages
GameEngine *-- Ship : manages
GameEngine *-- Universe : manages
GameEngine *-- UIManager : manages
GameEngine *-- SaveManager : manages
GameEngine ..> Event : uses
UIManager *-- TerminalFrame : contains
class TerminalFrame {
-struct termios _originalTermios
-int _width
-int _height
+TerminalFrame(int width, int height)
+~TerminalFrame()
+enableRawMode()
+disableRawMode()
+clearScreen() const
+moveCursor(int x, int y) const
+hideCursor() const
+showCursor() const
+drawBorder() const
+printAt(int x, int y, const string& text) const
+getInput() Key
}
Loading

Getting Started

To run this game on macOS or Linux, follow these steps:

  1. Clone the repository:

    git clone [https://github.com/achulzhanov/spaceplusplus](https://github.com/achulzhanov/spaceplusplus)
    cd spaceplusplus
  2. Compile the game:

    clang++ -std=c++17 -I include src/*.cpp -o spaceplusplus
  3. Run the game: First, resize your terminal window to 100x30.

    ./spaceplusplus

Note: This application requires a POSIX-compliant terminal (macOS/Linux) due to the use of termios.h for raw mode input handling.


Description of the Relation Among Classes

Inheritance Relationships ("Is-A")

  • The Ship Hierarchy: The LightFighter, HeavyFreighter, Smuggler, and Scavenger classes are derived from the abstract Ship base class using public inheritance. This is because every specific ship "is a" spaceship, sharing common attributes (hull, fuel, firepower) and mechanics (takeDamage(), refuel()). By making applyUniqueTrait() a pure virtual function in the base class, the design enforces polymorphism. This allows the GameEngine to manage a single std::unique_ptr<Ship> and uniformly interact with any ship type at runtime without needing to know its exact derived class.
  • The Event Hierarchy:PirateAmbush, AsteroidField, SpacePolicePatrol, and CometIntercept are derived from the abstract Event base class. Deriving both negative hazards and positive "opportunity" events from a single base class allows the engine to randomly select and execute any event's polymorphic trigger() function seamlessly during the transit cycle.
  • The Exception Hierarchy:HullBreachException and StrandedException are derived from the custom GameOverException (which inherits from <stdexcept>). This hierarchy allows the game loop to throw specific errors based on gameplay failures and catch them dynamically to trigger the correct Game Over UI.

Composition Relationships ("Has-A")

  • The GameEngine Hub: The GameEngine class utilizes object composition to act as the central controller of the program. It "has a" Captain, a Universe, a UIManager, and a SaveManager. Instead of inheriting from these classes, GameEngine instantiates them as member variables to orchestrate the state machine and pass data between them safely.
  • The Universe Structure: The Universe class "has a" collection of StarSystem structs, and each StarSystem "has a" Market. This nested composition ensures that procedural generation perfectly links deterministic market prices to specific geographical locations in the game world.
  • The UIManager Composition: The UIManager "has a" TerminalFrame to handle low-level terminal operations.

Dependency Relationships ("Uses-A")

  • The Ephemeral Event: The GameEngine does not own an Event object permanently. Instead, it "uses" one. During the transit loop, the engine temporarily spawns a derived Event, passes the Captain and Ship by reference into the trigger() method to apply effects or prompt the user for a decision, and then immediately destroys the event.

Use Cases

Use Case 1: Start Game and Plot Course

  1. The program boots up and UIManager prints the Main Menu.
  2. The user selects the option to start a "New Career", selects a difficulty, and chooses from four distinct ship classes.
  3. The program instantiates a new Captain object, prompts the user for a name, and allocates starting credits and the chosen Ship.
  4. The Universe generates a procedural galaxy map based on a random seed.
  5. UIManager displays a "How To Play" tutorial overlay to introduce core mechanics.
  6. The program transitions to the Spaceport phase, where UIManager displays the ship's current stats, cargo hold, and nearby star systems.
  7. The user selects a destination from the list.
  8. The program calculates the required fuel and decrements the _cyclesRemaining variable based on the destination's distance, transitioning the game into the Transit Phase.

Use Case 2: Surviving a Transit Cycle

  1. The GameEngine begins the transit sequence.
  2. The program attempts to call consumeFuel() on the Ship. If the required fuel exceeds current reserves, a StrandedException is thrown, caught by the engine, and the Game Over screen is displayed.
  3. The engine generates a random number, modified by the ship's _riskMultiplier, to determine if a deep-space encounter occurs.
  4. If triggered, a polymorphic Event (e.g., CometIntercept or PirateAmbush) executes its trigger() function. The engine may pause the loop to prompt the user for a risk/reward decision, or perform an automatic stat-check combining the Captain's skills and the Ship's firepower to determine success or failure. Certain events allow the player to utilize their specific Ship's unique trait to bypass danger.
  5. If the hull drops to zero during combat or an environmental hazard, a HullBreachException is thrown, caught by the engine, and the Game Over screen is displayed.
  6. UIManager paginates and prints the event's outcome (e.g., loot salvaged or damage taken) and draws an updated ASCII progress bar for the transit cycle.
  7. Once the event resolves, the ship successfully arrives at the new Spaceport.

Use Case 3: Trading, Upgrading, and Saving Progress

  1. The player arrives at a new StarSystem.
  2. The user selects the "Marketplace" option from the Spaceport Operations menu.
  3. UIManager accesses the local Market object and prints a formatted table of current buy/sell prices, actively modified by the player's Trade skill.
  4. The user selects items to sell; the program removes the items from the Captain's inventory and increases their credits.
  5. The user navigates back to the Spaceport menu and spends credits to safely mutate the ship's state (e.g., calling repairHull() or refuel()), or manually triggers their ship's unique trait (e.g., Jury-Rig).
  6. The user selects the "Save & Quit" option.
  7. The program passes the Captain data, Ship status, current Universe seed, and difficulty settings to the SaveManager.
  8. SaveManager executes File I/O operations, writing the data to save_slot_X.dat.
  9. The program displays a success message and safely terminates execution.

MVC Implementation

The game is architected using the Model-View-Controller (MVC) design pattern to cleanly separate data, presentation, and gameplay logic.

1. The Model (Data & State)

The Model encapsulates the pure data of the game. These classes do not know how they are drawn to the screen or how the user interacts with them; they simply hold state and expose methods to safely mutate that state.

  • Captain: Manages the player's name, credits, experience, and inventory logic.
  • Ship (and subclasses): Tracks fuel, hull integrity, cargo capacity, and firepower.
  • Universe & StarSystem: Holds the procedurally generated map, coordinates, and star system names.
  • Market: Stores the deterministic buy and sell prices for commodities in a specific system.
  • Skills & InventoryItem: Simple structs holding attributes.

2. The View (Presentation Layer)

The View is entirely responsible for what the user sees and how raw input is captured. It contains no gameplay logic or math.

  • UIManager: The primary view class. It handles layout logic, draws ASCII art, paginates text logs, formats the marketplace table, and translates raw key presses into actionable menu choices.
  • TerminalFrame: A low-level wrapper for POSIX termios.h. It is responsible for raw terminal manipulation, including hiding the cursor, moving the draw position via ANSI escape codes, and reading unbuffered keyboard input.

3. The Controller (Logic & Coordination)

The Controller acts as the brain. It listens for user choices via the View, decides how those choices impact the game, mutates the Model, and then tells the View what to display next.

  • GameEngine: The central controller. It manages the core loop and state machine (e.g., switching from Spaceport to Transit). If the user chooses to "Buy Fuel" via the UIManager, the GameEngine checks the Captain's credits, deducts them, and calls refuel() on the Ship.
  • Event (and subclasses): Encapsulates specific encounter logic. They act as sub-controllers that temporarily take over during the transit phase, mutating the Ship or Captain models based on simulated dice rolls.
  • SaveManager: Handles serialization. It translates the current state of the Model into a flat file on the disk and can rebuild the Model from that file upon loading.

About

An object-oriented, terminal-based space trading game written in C++. Features procedural star system generation, polymorphic ship classes, and real-time ASCII rendering.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages