A Java 21 character/crew management system for the Coriolis: The Great Dark tabletop RPG.
darkforge.databasepackage — first relational persistence layer, built on JDBC + SQLite (jdbc:sqlite:), persisting the crew roster across three related tables. Complements (does not replace) the existingdarkforge.persistenceserialization.DatabaseManager— owns the singlejdbc:sqlite:darkforge.dbConnection, setsPRAGMA foreign_keys = ON, and runs a hybrid bootstrap (verify tables viasqlite_master, create fromschema.sqlif missing)ExplorerDao/CrewDao/CrewMembershipDao— all DML throughPreparedStatement(inserts useRETURN_GENERATED_KEYS); provide the three required reads (ordered select, multi-table join, aggregation)- Row/result records —
ExplorerRow,CrewRow,RosterEntry,CrewStat
- 3-table schema (
schema.sql) —crew·explorer·crew_membership, with full integrity constraints: PK + FK + NOT NULL + UNIQUE + CHECK, plusON DELETE CASCADEon both foreign keys- Crew roles stored as enum-constant tokens (
DELVER/SCOUT/BURROWER/GUARD/ARCHAEOLOGIST) with aCHECKlist; round-trip viaCrewRole.valueOf(...) - Professions stored as raw display name (no
CHECK); validated on reload byExplorerFactory - Six attributes (STR, AGL, LOG, PER, INS, EMP)
CHECK-constrained to 2–6; derived stats (Health/Hope/Heart) computed in Java, not stored
- Crew roles stored as enum-constant tokens (
FacadeDatabase— new subfacade wired byFacadeDarkforge.initialize()(same rhythm asFacadeConcurrency), exposed viadatabaseAccess(); DB connection closed on exit- CLI option
[15] Crew Database— prompt-driven create-explorer / create-crew / assign-member / view-roster / crew-stats; nothing pre-seeded, all data entered at runtime DatabaseException extends DarkForgeException— uniform error type for connection / SQL / constraint failures (mirrorsConcurrencyException)- JUnit 5 database tests — DAO round-trip, constraint enforcement (FK / composite PK / role + attribute
CHECK/ UNIQUE),ON DELETE CASCADE, and query correctness, each against a temp file DB orjdbc:sqlite::memory: Mainbanner bumped v5.0 → v6.0
- Java 21+
lib/sqlite-jdbc-3.45.x.jar(Xerial SQLite JDBC driver) (NEW)lib/slf4j-api-2.0.13.jar(required by sqlite-jdbc 3.43.2.0+ for its internal logging) (NEW)lib/slf4j-nop-2.0.13.jar(no-op SLF4J provider; silences the startup "No SLF4J providers" warning) (NEW)lib/json-20240303.jar(org.json)lib/gson-2.10.1.jar(Google Gson)lib/junit-platform-console-standalone-1.10.2.jar(testing)
Compile
javac -cp "lib/*" -d out \
src/darkforge/*.javaRun CLI
java -cp "out:lib/*" darkforge.MainRun GUI
java -cp "out:lib/*" darkforge.gui.DarkforgeGuiRun serialization benchmark
java -cp "out:lib/*" darkforge.persistence.SerializationBenchmarkRun dice probability simulator (CLI option [14])
java -cp "out:lib/*" darkforge.Main
# then choose [14] Dice Probability SimulatorRun crew database (CLI option [15])
java -cp "out:lib/*" darkforge.Main
# then choose [15] Crew Database
# tables auto-create in darkforge.db on first runRun tests
java -jar lib/junit-platform-* -cp out --scan-classpathsrc/darkforge/
├── collection/ # Inventory, generics
├── concurrency/ # ExecutorService, Callable, AtomicLong, SwingWorker adapter
├── cli/ # CLI menus (option [15] added)
├── crew/ # Crew, Vehicle, analytics
├── data/ # GameDataProvider, catalog
├── database/ # JDBC + SQLite: DatabaseManager, DAOs, row records (NEW)
├── display/ # Displayable, formatters
├── exception/ # DarkForgeException, ConcurrencyException, DatabaseException (NEW)
├── facade/ # Facade pattern (FacadeDatabase added)
├── gui/ # Swing GUI (DiceSimulatorPanel, background tasks)
├── model/ # GameEntity hierarchy
└── persistence/ # Serialization
src/resources/ # JSON catalog files + schema.sql (NEW)
test/darkforge/ # JUnit 5 test suite (database tests added)
Hybrid composition + interfaces.
Explorer implements InventoryHolder<CharacterItem> and Equippable<Weapon>.
Vehicle implements InventoryHolder<CargoItem> and Equippable<VehicleModule>.
Facade pattern provides simplified access via FacadeDarkforge → FacadeCrew / FacadeCatalog / FacadeConcurrency / FacadeDatabase.
DatabaseManagerowns the singlejdbc:sqlite:darkforge.dbConnectionfor the lifetime of the application; setsPRAGMA foreign_keys = ON(SQLite enforces FKs per-connection only when set) and is closed viaFacadeDatabase.shutdown()fromMain'sfinallyblock, alongside the concurrency shutdown.- Schema management is hybrid:
schema.sqlships as a classpath resource and runs withCREATE TABLE IF NOT EXISTS, so re-running against an existing.dbis a no-op. This is reproducible for graders and demonstrates JDBC DDL. - The relational↔OO mapping is isomorphic:
Explorer↔explorerrows,Crew↔crewrows, and crew roles ↔crew_membership.role. A 3-table join mirrors the lecture's researcher/journal/publication example. - All DML flows through
PreparedStatement; inserts return generated keys. The three required reads are an orderedSELECT, a multi-tableJOIN, and aGROUP BYaggregation (COUNT/AVG).
DiceSimulatorowns a fixed-sizeExecutorService(one thread per available core) for the lifetime of the application; shut down viaFacadeConcurrency.shutdown()fromMain'sfinallyblock.- Roll batches are submitted as
Callable<BatchResult>viainvokeAll; each task owns its own seededjava.util.Randomso no RNG is shared across threads. - Aggregation uses
AtomicLongper counter, hit once per batch (not once per roll), keeping contention bounded by batch count. - GUI off-EDT work goes through
BackgroundTask<T>(aSwingWorker<T, Integer>adapter);done()dispatches result/error back to the EDT.
Dice mechanics follow Coriolis: The Great Dark exactly: each 6 is a success, and pushing re-rolls any die not showing a 6 or a 1. Sixes lock as successes; ones lock as banes (Hope damage on Base dice, gear damage on Gear dice). The convergence test asserts a 3-die pool reaches the analytic targets within ±1%: unpushed ≈ 42.1% (