View: Multi-Page Single Page

ConnectGenerator

Static public entry point. Namespace SimpleConnect.

MemberReturnsNotes
Generate(settings)ConnectPuzzleDataInstant, always solvable. Solution count stays Unknown.
GenerateVerified(settings)ConnectPuzzleDataSolvable + 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.

MemberReturnsNotes
IsLegalStep(puzzle, pipes, pairId, next, out reason)boolIn 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)boolEvery pair joined end-to-end, no overlaps, board covered when the fill rule requires it.
Validate(puzzle, out error)boolIs 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).

FieldTypeMeaning
Width / HeightintGrid size.
Shape / FillenumSquare/Hex · CoverAll (fill) / Connect (Strict lines).
SeedstringThe seed that produced it.
PairsList<EndpointPair>The pairs to connect; each carries its own path.
WallsList<Cell>Blocked cells the paths may never enter.
BridgesList<Cell>Reserved — square-only crossing cells; not produced by the current generator (empty).
IsUnique / SolutionCountbool / enumThe solver's verdict.
Difficultyint1–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.

MemberTypeMeaning
IdintStable index of this pair. Map it to a color/icon in your renderer.
A / BCellThe two endpoints to join.
PathList<Cell>The intended solution route, AB inclusive.
LengthintCells in Path.
IsEndpoint(cell)boolTrue 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).

MemberReturnsNotes
Generate(settings)NetworkPuzzleDataInstant single roll, always self-solvable. Verdict stays Unknown; with fountains, check FountainsSatisfied() yourself.
GenerateVerified(settings)NetworkPuzzleDataRetries 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.

FieldTypeMeaning
Width / HeightintGrid size (square only).
SeedstringThe seed that produced it.
Masksint[]Per-cell connection mask, row-major (y*Width + x).
WallsList<Cell>Blocked cells (holes) the network weaves around.
FountainsList<Fountain>Required-degree pins carried with the board (empty = none).
IsUnique / SolutionCountbool / enumThe solver's verdict (networks always verify: One or Multiple).
Difficultyint1–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.

MemberTypeMeaning
CellCellThe pinned cell.
RequiredDegreeintExact 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)ctorDegree 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

TypeValues / shape
GridShapeSquare · Hex
FillRuleCoverAll (fill the board) · Connect (Strict lines)
SolutionCountUnknown · One · Multiple
Cellint X, Y grid coordinate
SolutionVerdictCount, Verified, Found, Nodes, IsUnique
Difficulty.Stars(verdict)int 1–5 from the verdict's node count; 0 if not verified-unique

ScriptableObjects

TypePurpose
ConnectPuzzleOne ConnectPuzzleData as an asset. .Puzzle reads it.
ConnectPuzzlePackMany puzzles (a level set). .Puzzles reads them; .Get(i), .Count.
IPuzzleProviderImplemented by both — AppendTo(list); a single list can mix packs and puzzles.
NetworkPuzzleOne NetworkPuzzleData as an asset. .Puzzle reads it; facts (Width, IsUnique, Seed…) readable without touching it.
NetworkPuzzlePackMany networks. .Puzzles, .Get(i), .Count, .PackName.
INetworkProviderImplemented 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) { }
}
Color is yoursThe data never stores a color — pair 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

MemberNotes
X, YColumn / 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:

MemberNotes
Topology.For(shape)An IGridTopology for Square or Hex.
Topology.MaxNeighbors6 — 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

MemberNotes
GenerationSettings.Clone() / Clamp()Field-for-field copy / coerce to valid ranges (returns this).
ConnectPuzzle.Label / Shape / Fill / Width / Height / PairCount / IsUnique / SolutionCount / Difficulty / SeedRead puzzle facts without touching .Puzzle (handy for list UI).
ConnectPuzzlePack.PackName / Count / Get(i)Pack name, size, and indexed access.
Advanced / low-level — also public, but most projects won't need them: Generator.Generate (unverified — prefer ConnectGenerator), Difficulty.Stars (rate a verdict yourself), QualityMetrics / ObstacleMetrics (generation ranking helpers), and Rng (deterministic RNG: NextInt / NextDouble / Shuffle).