ConnectGenerator
Static public entry point. Namespace SimpleConnect.
| Member | Returns | Notes |
|---|---|---|
Generate(settings) | ConnectPuzzleData | Instant, always solvable. Solution count stays Unknown. |
GenerateVerified(settings) | ConnectPuzzleData | Solvable + uniqueness-checked; hunts for a unique board and stamps IsUnique / SolutionCount / Difficulty. |
GenerateVerifiedAsync(settings) | Task<ConnectPuzzleData> | Background thread; await resumes on the main thread. |
GenerateBatch(settings, count) | List<ConnectPuzzleData> | A pack of count verified puzzles, deterministic seeds. |
GenerateBatchAsync(settings, count, progress) | Task<List<ConnectPuzzleData>> | Background batch; IProgress<int> reports 1..count. |
ConnectRules
Static, stateless play rules. A pipe for pair i is the path drawn from its A
endpoint: pipes[i] = [A, …]. Pass the full set of pipes.
| Member | Returns | Notes |
|---|---|---|
IsLegalStep(puzzle, pipes, pairId, next, out reason) | bool | In bounds, not a wall, adjacent to the line's head, no crossing another pair; Strict lines also forbid self-touch. |
IsSolved(puzzle, pipes, out reason) | bool | Every pair joined end-to-end, no overlaps, board covered when the fill rule requires it. |
Validate(puzzle, out error) | bool | Is the puzzle data well-formed (importers / editors). |
IsLegalStep and IsSolved each have a no-reason overload.
Solver
SolutionVerdict v = Solver.Verify(puzzle, nodeBudget: 400_000, budgetSeconds: 2.5);
// v.Count -> SolutionCount.One / Multiple / Unknown
// v.Verified -> proven (not cut off by a budget)
// v.IsUnique -> Count == One && Verified
// v.Found -> distinct solutions found (capped at 2)
// v.Nodes -> search nodes expanded (the difficulty signal)
Dispatches on the fill rule (strict / loose), topology-aware for square & hex, and bounded by node and wall-clock budgets — a call can never hang.
ConnectPuzzleData
The serializable puzzle unit (stored inside the ScriptableObjects).
| Field | Type | Meaning |
|---|---|---|
Width / Height | int | Grid size. |
Shape / Fill | enum | Square/Hex · CoverAll (fill) / Connect (Strict lines). |
Seed | string | The seed that produced it. |
Pairs | List<EndpointPair> | The pairs to connect; each carries its own path. |
Walls | List<Cell> | Blocked cells the paths may never enter. |
Bridges | List<Cell> | Reserved — square-only crossing cells; not produced by the current generator (empty). |
IsUnique / SolutionCount | bool / enum | The solver's verdict. |
Difficulty | int | 1–5 stars; 0 = unrated. |
Plus computed helpers: CellCount, PairCount, WallCount,
PlayableCount (grid − walls), IsCoverAll, TotalPathLength,
BridgeCount / HasBridges.
EndpointPair
One link to connect. No color is stored — only an Id; your
renderer maps that to a color, pipe, or number.
| Member | Type | Meaning |
|---|---|---|
Id | int | Stable index of this pair. Map it to a color/icon in your renderer. |
A / B | Cell | The two endpoints to join. |
Path | List<Cell> | The intended solution route, A…B inclusive. |
Length | int | Cells in Path. |
IsEndpoint(cell) | bool | True if cell is A or B. |
NetworkGenerator
Static public entry point for the network (rotate) engine. Namespace
SimpleConnect. Mirrors ConnectGenerator's five entry points,
driven by NetworkGenerationSettings (see
every setting).
| Member | Returns | Notes |
|---|---|---|
Generate(settings) | NetworkPuzzleData | Instant single roll, always self-solvable. Verdict stays Unknown; with fountains, check FountainsSatisfied() yourself. |
GenerateVerified(settings) | NetworkPuzzleData | Retries until a board is verified unique and every fountain pin is satisfied; stamps IsUnique / SolutionCount / Difficulty. With loops or a dead-end cap it prefers unique boards and falls back honestly. |
GenerateVerifiedAsync(settings) | Task<NetworkPuzzleData> | Background thread; await resumes on the main thread. |
GenerateBatch(settings, count) | List<NetworkPuzzleData> | A pack of count verified networks, deterministic seeds. |
GenerateBatchAsync(settings, count, progress) | Task<List<NetworkPuzzleData>> | Background batch; IProgress<int> reports 1..count. |
Constants: MinSize 2, MaxSize 30,
DefaultBranchiness 0.35. NetworkGenerationSettings.Clone() /
Clamp() behave like their pair-engine counterparts; the generator clamps a
private copy, so your settings object is never mutated.
NetworkPuzzleData
The serializable network unit. The solved network is stored as one connection mask per cell — bits N=0, E=1, S=2, W=3 (walls are 0). The rotate presentation scrambles each tile's rotation; the shape of each tile is fixed.
| Field | Type | Meaning |
|---|---|---|
Width / Height | int | Grid size (square only). |
Seed | string | The seed that produced it. |
Masks | int[] | Per-cell connection mask, row-major (y*Width + x). |
Walls | List<Cell> | Blocked cells (holes) the network weaves around. |
Fountains | List<Fountain> | Required-degree pins carried with the board (empty = none). |
IsUnique / SolutionCount | bool / enum | The solver's verdict (networks always verify: One or Multiple). |
Difficulty | int | 1–5 stars; 0 = unrated. |
Helpers: MaskAt(x,y) / MaskAt(cell),
DegreeAt(x,y) (0 for walls, else 1–4), TryGetFountain(cell, out f),
FountainsSatisfied(), BuildWallGrid(), plus
CellCount / WallCount / PlayableCount /
FountainCount, Index(x,y), InBounds(x,y).
Fountain
A required-degree pin: a cell the solved network must meet with an exact number of pipe connections.
| Member | Type | Meaning |
|---|---|---|
Cell | Cell | The pinned cell. |
RequiredDegree | int | Exact connection count 1–4, or Fountain.RandomDegree (0) = the generator rolls a junction degree (2–4) per board and stamps the resolved value. |
Fountain(x, y, degree) / Fountain(cell, degree) | ctor | Degree is clamped to 0–4. |
FountainsSatisfied() treats a raw RandomDegree pin
as satisfied by any junction (degree ≥ 2); generated boards always carry concrete resolved
degrees. A statically impossible pin (out of bounds, more pipes than the cell has
neighbours, conflicting duplicates) makes the board report unsatisfied — honestly, in a
single generation attempt.
NetworkSolver & helpers
bool unique = NetworkSolver.IsUnique(puzzle); // exactly one rotation solution?
long count = NetworkSolver.CountSolutions(puzzle, cap: 2); // early-out at the cap
int stars = NetworkDifficulty.Stars(puzzle); // 1..5 logic-difficulty rating
A solution is a rotation assignment where every edge is reciprocated, nothing points off-board or into a wall, and the network is one connected piece. Validated against a brute-force oracle, including boards with loops.
PipeMask is the shared direction-mask helper:
N/E/S/W bit constants, DX/DY deltas,
Bit(d), Opp(d), RotateCW(mask),
Degree(mask). The demo's rotate brains
(SimpleConnect.Demo.NetworkGame: Load / Rotate /
AutoSolve / Solved) are built on it — read them for a working
reference.
Enums & structs
| Type | Values / shape |
|---|---|
GridShape | Square · Hex |
FillRule | CoverAll (fill the board) · Connect (Strict lines) |
SolutionCount | Unknown · One · Multiple |
Cell | int X, Y grid coordinate |
SolutionVerdict | Count, Verified, Found, Nodes, IsUnique |
Difficulty.Stars(verdict) | int 1–5 from the verdict's node count; 0 if not verified-unique |
ScriptableObjects
| Type | Purpose |
|---|---|
ConnectPuzzle | One ConnectPuzzleData as an asset. .Puzzle reads it. |
ConnectPuzzlePack | Many puzzles (a level set). .Puzzles reads them; .Get(i), .Count. |
IPuzzleProvider | Implemented by both — AppendTo(list); a single list can mix packs and puzzles. |
NetworkPuzzle | One NetworkPuzzleData as an asset. .Puzzle reads it; facts (Width, IsUnique, Seed…) readable without touching it. |
NetworkPuzzlePack | Many networks. .Puzzles, .Get(i), .Count, .PackName. |
INetworkProvider | Implemented by both network assets — AppendTo(list), same mixing pattern. |
Create assets via the Connect Generator / Network Generator windows (Save Current / Save Pack).
Reading a saved puzzle
Assign a pack or puzzle in the inspector, then read the ConnectPuzzleData and
walk its pairs. This is everything you need to draw a board and know the solution:
using SimpleConnect;
public sealed class MyGame : MonoBehaviour
{
[SerializeField] ConnectPuzzlePack pack; // drag a pack asset here
[SerializeField] ConnectPuzzle single; // or a single puzzle
void Start()
{
// pick a puzzle: from the pack (by index / foreach) or the single asset
ConnectPuzzleData p = single.Puzzle; // or pack.Get(0), or foreach pack.Puzzles
int w = p.Width, h = p.Height;
bool fillBoard = p.IsCoverAll; // false = Strict lines (gaps allowed)
// each pair: its Id (map to YOUR color/icon), the two endpoints, and the route
foreach (EndpointPair pair in p.Pairs)
{
int id = pair.Id; // 0,1,2… -> your palette
Cell a = pair.A, b = pair.B; // endpoints to place
foreach (Cell c in pair.Path) // intended solution, A..B inclusive
DrawLineCell(c.X, c.Y, id);
}
// blocked cells
foreach (Cell wall in p.Walls)
DrawWall(wall.X, wall.Y);
// verdict / rating
bool unique = p.IsUnique; // solver-proven single solution
int stars = p.Difficulty; // 1..5, 0 if unrated
}
void DrawLineCell(int x, int y, int pairId) { /* your renderer */ }
void DrawWall(int x, int y) { }
}
Id is the only identity. Map Id → palette / pipe / number in your renderer,
exactly like the demo's LevelRenderer3D does.Reading a saved network
[SerializeField] NetworkPuzzlePack networkPack; // drag a network pack here
NetworkPuzzleData n = networkPack.Get(0);
for (int y = 0; y < n.Height; y++)
for (int x = 0; x < n.Width; x++)
{
int mask = n.MaskAt(x, y); // N/E/S/W bits of the SOLVED tile; 0 = wall
int arms = n.DegreeAt(x, y); // 1 = endpoint .. 4 = cross
}
foreach (Fountain f in n.Fountains)
Mark(f.Cell, f.RequiredDegree); // pinned cells, if the board has any
To make it playable, scramble each tile's rotation and let the player turn
them back — that's exactly what the demo's NetworkGame does
(Load(puzzle, seed) scrambles, Rotate(cell) is a tap,
Solved flips when every edge is mutual and the network is one piece).
Helpers & advanced
Cell
| Member | Notes |
|---|---|
X, Y | Column / row (int). |
ManhattanTo(other) | Grid (Manhattan) distance between two cells. |
IsAdjacentTo(other) | Orthogonal (square) adjacency. For hex, use a topology (below). |
Topology — for custom hex movement / layout
Building your own hex input or renderer? Use the topology instead of hard-coding neighbors — it's square/hex correct:
| Member | Notes |
|---|---|
Topology.For(shape) | An IGridTopology for Square or Hex. |
Topology.MaxNeighbors | 6 — size your neighbor buffer with this constant. |
IGridTopology.Neighbors(cell, w, h, buffer) | Fills buffer with in-bounds neighbors; returns the count. |
IGridTopology.AreAdjacent(a, b) | Adjacency under that topology. |
Other convenience
| Member | Notes |
|---|---|
GenerationSettings.Clone() / Clamp() | Field-for-field copy / coerce to valid ranges (returns this). |
ConnectPuzzle.Label / Shape / Fill / Width / Height / PairCount / IsUnique / SolutionCount / Difficulty / Seed | Read puzzle facts without touching .Puzzle (handy for list UI). |
ConnectPuzzlePack.PackName / Count / Get(i) | Pack name, size, and indexed access. |
Generator.Generate (unverified — prefer ConnectGenerator),
Difficulty.Stars (rate a verdict yourself),
QualityMetrics / ObstacleMetrics (generation ranking helpers),
and Rng (deterministic RNG: NextInt / NextDouble / Shuffle).