Overview
Simple Connect generates grid puzzles whose solution links every pair of matching endpoints with paths that never cross. By default the paths fill the whole board; an optional Strict lines rule keeps each path from running beside itself. It verifies them too — a solver reports whether a board has exactly one solution. Square & hex grids, deterministic from a seed, stored as ScriptableObjects. Pairs carry an Id, not a color. Pair boards drive two play styles: draw the lines, or play them as scrambled rotate-to-fix pipe tiles.
The second engine generates networks (square-only): one connected pipe
system — straights, corners, Ts, crosses — the player rotates back together, proven unique
by rotation. Shape it with branchiness and junction-bias
dials, weld loops, cap dead ends down to zero for sealed
plumbing, and pin fountain cells that must be fed by an exact number of
pipes (2/3/4, or ? = rolled per board).
Requirements
- Unity 6000.3+.
- Core (generation / solving / rules / data / ScriptableObjects): no package dependencies, any render pipeline.
- Demo: Input System + TextMeshPro; Built-in pipeline (URP/HDRP users convert the demo materials).
Quick start
No code
Pairs: open Window ▸ Living Failure ▸ Simple Connect ▸ Connect Generator,
pick Shape / size / pairs / coverage (and optionally Strict lines), click
Generate, then Save Current or Save Pack.
Networks: open … ▸ Network Generator, dial in Branchiness /
Junction bias (optionally Allow loops / Limit dead ends), click cells in the
Fountains foldout to pin their pipe count
(·→2→3→4→?) or set
Random fountains, Generate, Save.
Code
using SimpleConnect;
var settings = new GenerationSettings {
Width = 7, Height = 7, Shape = GridShape.Hex,
PairCount = 0, TargetCoverage = 0.9f, RequireUnique = true,
// Fill defaults to CoverAll; set Fill = FillRule.Connect for Strict lines.
};
ConnectPuzzleData puzzle = ConnectGenerator.GenerateVerified(settings);
List<ConnectPuzzleData> pack = ConnectGenerator.GenerateBatch(settings, 10);
// the network engine mirrors the same entry points
var net = new NetworkGenerationSettings {
Width = 9, Height = 9, Coverage = 0.9f,
Branchiness = 0.5f, DegreeBias = 0.4f, RandomFountains = 2,
// AllowLoops = true, MaxDeadEnds = 0 (sealed plumbing)
};
net.Fountains.Add(new Fountain(4, 4, 4)); // pin the centre: exactly 4 pipes in
NetworkPuzzleData network = NetworkGenerator.GenerateVerified(net);
// network.IsUnique, network.Difficulty, network.FountainsSatisfied()
Playing
// pipes[i] = the path drawn for pair i, from its A endpoint
bool ok = ConnectRules.IsLegalStep(puzzle, pipes, pairId, nextCell);
bool won = ConnectRules.IsSolved(puzzle, pipes);
Generating puzzles
The Generate window: Batch count, Clear on generate, Presets / Reset, Require unique, and Save Current / Save Pack.
By default the lines fill the board and may run beside themselves; Strict lines forbids self-touch (clean routes, some cells left empty). Square is 4-neighbor, Hex is pointy-top 6-neighbor (odd-r); both are topology-aware. Leftover cells become walls on purpose — that's what forces a unique solution. Generation is deterministic per seed — share the seed instead of the data.
Saved assets get inspector tools: a solution-count + difficulty badge plus Verify (re-check; Verify All reports any changed verdicts) and Re-roll (regenerate a slot until it verifies, then replace it; Re-roll Unverified fixes a whole pack at once). Both are undoable.
The Network Generator
Branchiness shapes the network (0 = corridors, 1 = bushy); Junction bias pumps T/cross density up to the structural ceiling (~⅓ of cells). Allow loops welds redundant routes; Limit dead ends seals the degree-1 tips — 0 = fully closed plumbing. Most looped/sealed boards still verify unique; the rest are honestly stamped Multiple (generation prefers unique ones).
Fountains: click cells in the window's Fountains foldout to pin an exact
pipe count — the cycle is capped by what a cell can physically take (corner 2, edge 3).
? pins get a rolled 2–4 per board; Random fountains scatters
N seed-deterministic pins. Pins are satisfied during growth and gate verification.
Pinned cells show a blue frame in previews with an "all satisfied" row; orange
dots are ordinary dead-end terminals, not fountains.
For runtime generation behind a loading screen:
var levels = await ConnectGenerator.GenerateBatchAsync(settings, 20,
new System.Progress<int>(done => bar.value = done / 20f));
var nets = await NetworkGenerator.GenerateBatchAsync(netSettings, 20,
new System.Progress<int>(done => bar.value = done / 20f));
Settings
| Field | Range | Meaning |
|---|---|---|
Width / Height | 3–20 | Grid size. |
Shape | Square / Hex | 4- or 6-neighbor. |
Strict lines (Fill) | off / on | Fill the board vs no-self-touch, gaps allowed. |
TargetCoverage | 0.4–1.0 | How full the board is; rest are walls. |
PairCount | 0…cells/2 | Pairs to place; 0 = auto. |
MinPairLength | 2… | Reject trivially short lines. |
MinSeparation | 0…W+H | Min gap between a pair's endpoints; 0 = auto. |
RequireUnique | bool | Verify a single-solution board. |
TargetDifficulty | 0–5 | Roll until this star rating (0 = rank by quality). |
Seed | string | Empty = random; reproduces the board. |
UniquenessNodeBudget / UniquenessSeconds | 1k–5M / 0.2–20 | Solver node + time budgets. |
Network settings (NetworkGenerationSettings)
| Field | Range | Meaning |
|---|---|---|
Width / Height | 2–30 | Grid size (square only). |
Coverage | 0.4–1.0 | Network coverage; rest are walls/holes. |
Branchiness | 0–1 | Corridors ↔ bushy (default 0.35). |
DegreeBias | 0–1 | Junction appetite (default 0 = off). |
AllowLoops / LoopCount | bool / 0–99 | Weld cycles; 0 = auto (~1 per 20 cells). |
MaxDeadEnds | -1 / 0–99 | -1 = off; N = seal tips down to N; 0 = fully sealed. |
Fountains / RandomFountains | list / 0–12 | Authored pins; extra random pins per board. |
MaxAttempts | 1–500 | Boards tried for a verified, fountain-satisfied one (default 40). |
Seed | string | Empty = random; reproduces the board. |
API reference
ConnectGenerator
| Member | Returns |
|---|---|
Generate(settings) | ConnectPuzzleData (unverified) |
GenerateVerified(settings) | ConnectPuzzleData (uniqueness-checked) |
GenerateVerifiedAsync(settings) | Task<ConnectPuzzleData> |
GenerateBatch(settings, count) | List<ConnectPuzzleData> |
GenerateBatchAsync(settings, count, progress) | Task<List<ConnectPuzzleData>> |
ConnectRules
| Member | Notes |
|---|---|
IsLegalStep(puzzle, pipes, pairId, next) | In bounds, not a wall, adjacent to the head, no crossing; Strict forbids self-touch. |
IsSolved(puzzle, pipes) | Every pair joined, no overlaps, board covered when required. |
Validate(puzzle, out error) | Well-formed data → bool. |
Solver
Solver.Verify(puzzle) → SolutionVerdict (Count,
Verified, IsUnique, Found, Nodes). Bounded by
node + time budgets.
NetworkGenerator
Mirrors ConnectGenerator's five entry points
(Generate / GenerateVerified[Async] /
GenerateBatch[Async]) over NetworkGenerationSettings →
NetworkPuzzleData. GenerateVerified retries until a board is
unique and every fountain is satisfied; raw Generate is one roll
(check FountainsSatisfied() yourself). Settings objects are never mutated.
NetworkSolver & data
NetworkSolver.IsUnique(p) / CountSolutions(p, cap) — a solution
is a rotation assignment with every edge reciprocated, nothing off-board or into a wall,
and one connected network (validated against a brute-force oracle, loops included).
NetworkDifficulty.Stars(p) rates 1–5.
NetworkPuzzleData stores a per-cell connection mask
(bits N=0 E=1 S=2 W=3, row-major; walls 0) — read with MaskAt /
DegreeAt; PipeMask has the bit helpers
(Bit / Opp / RotateCW / Degree).
Fountain = Cell + RequiredDegree (1–4, or
Fountain.RandomDegree = 0 for ?).
Types
ConnectPuzzleData (size, shape, fill, pairs, walls, verdict, difficulty) ·
EndpointPair (Id, A, B, Path) ·
GenerationSettings · Cell (X, Y) ·
GridShape · FillRule · SolutionCount ·
ConnectPuzzle / ConnectPuzzlePack (both IPuzzleProvider) ·
NetworkPuzzle / NetworkPuzzlePack (both INetworkProvider).
Reading a saved puzzle
Assign a ConnectPuzzle or ConnectPuzzlePack, then walk the data:
ConnectPuzzleData p = single.Puzzle; // or pack.Get(0) / foreach pack.Puzzles
foreach (EndpointPair pair in p.Pairs) {
int id = pair.Id; // map to YOUR color/pipe/number
Cell a = pair.A, b = pair.B; // endpoints
foreach (Cell c in pair.Path) Draw(c.X, c.Y, id); // intended route A..B
}
foreach (Cell wall in p.Walls) DrawWall(wall.X, wall.Y);
bool unique = p.IsUnique; int stars = p.Difficulty; // verdict / rating
// networks: masks straight off the data
NetworkPuzzleData n = networkPack.Get(0);
int mask = n.MaskAt(x, y); // solved tile's N/E/S/W bits; 0 = wall
int arms = n.DegreeAt(x, y); // 1 endpoint .. 4 cross
foreach (Fountain f in n.Fountains) Mark(f.Cell, f.RequiredDegree);
Id — never a color. Map it in your renderer.
For rotate play, scramble each tile's rotation and let the player turn them back — the
demo's NetworkGame shows how.The demo
The demo is a ready-to-play scene in Assets/SimpleConnect/Scenes/ with
all three modes — open it and press Play. The landing menu
picks Connect (then Color or Pipes style, Square / Hex, Strict lines) or
Network (square-only, straight to source), then Random
(generated, with a progress bar) or Puzzle Packs.
Draw controls: Square WASD/arrows · Hex QE AD ZC · drag to draw · Tab/1–9 pick the active pair · Backspace undo · R reset · H hide legend · F auto-solve. Rotate modes: click a tile to rotate · R fresh board · F auto-solve.
Skin it by dropping your tile prefabs on LevelRenderer3D (empty slots fall back
to primitives); color comes from the pair Id. The rotate boards use the included pipe meshes
(slots on RotateRenderer3D / NetworkRenderer3D); the Network
board's generation dials sit on its NetworkController, and its
Fountain Markers toggle (off by default) highlights pinned cells.
One-click builders to rebuild it live in Editor/Tools but are kept off the
menu — uncomment a builder's [MenuItem] line to bring it back under
… ▸ Demo Setup (Create Demo (all modes) builds boards, HUD,
and menu in one click).
Troubleshooting
- "Not verified" — solver hit its budget; the board still plays. Use smaller grids / Require Unique / a higher budget.
- Can't draw onto an empty cell — Strict lines blocks running beside your own line (allowed with Strict off), and you can only extend the active pair's head. Strict mode also leaves some cells empty on purpose.
- Switch pairs — Tab / Shift+Tab or 1–9; or click an endpoint / existing line.
- Magenta materials — Built-in shader in a URP/HDRP project; run Unity's material converter on the demo materials.
- No sound — add an AudioListener to the camera.
- Compile errors — install Input System + TextMeshPro; import TMP essentials.
- Mouse won't draw — hold and drag over adjacent cells.
- Hex keys — Q E A D Z C, not WASD (no straight-up neighbor on hex).
- F does nothing — it auto-solves; ignored on a solved level; only R cancels mid-playback.
- Network says "Multiple" — loops / dead-end sealing can rarely produce multi-solution boards; generation prefers unique and stamps honestly. Raise
MaxAttemptsor use Re-roll ▸ Multiple-solution. - Fountains "NOT satisfied" — the pin is statically impossible (corner caps at 2, edge at 3, conflicting duplicates) or the attempt budget ran out; spread pins or raise
MaxAttempts. - Unexpected fountains / blue discs — pins and the Random fountains slider persist between sessions (watch the warning above Generate); the demo's discs come from
NetworkRenderer3D's Fountain Markers toggle, off by default.
About
Simple Connect v1.0, by Living Failure. Verify don't hope; no color in the data; no bloat; pipeline-agnostic; readable example code. Also from Living Failure: Simple Hamiltonian, a cover-all / single-path generator. Support: livingfailuregames@gmail.com. Distributed under the Unity Asset Store EULA.