Phaser AE

Rope untangle rules

Pro packv3.0.0-32DPhysics

The rules of rope-untangling puzzles, topology first. Ropes run between plugs in sockets; the player moves plugs to free sockets until nothing crosses, or until every rope is home. The rules decide at once, generated levels are always solvable, and physical ropes add the feel without ever holding a move up.

This is a Pro pack. Upgrade to Phaser Pro to use it in your games.See Phaser Pro →

Released October 14, 2026 · updated October 20, 2026

Rope untangle rules: a screenshot of its demo
A note about these graphics

Expansion packs don't come with graphics. This screenshot is one way of drawing what the pack does, made for its demo: none of the sprites, tiles or backgrounds in it are in the pack.

That's by design. A pack is a game's logic, mechanics and effects, and it works with whatever art you give it. Some packs draw effects, backdrops or 3D scenes of their own, but none carries a game's sprites.

So the look is yours. Make the art on phaser.io, bring your own, or let your agent draw it in code: the same pack can look like anything.

The pack

ropeUntangle

function ropeUntangle(): Pack<{ ropeUntangle: RopeUntangleApi }>

The rope-untangle pack: game.ropeUntangle, the rules, the generator and the physical ropes.

Helpers

createUntangle

function createUntangle(level: UntangleLevel): UntangleGame

Make a game of a level. Throws, listing every problem, on a level validateUntangleLevel refuses.

createUntangleFeel

function createUntangleFeel(game: UntangleGame, options: UntangleFeelOptions = {}): UntangleFeel

Make the physical ropes of a game. They follow the game's state: moves, undo, redo and restarts.

generateUntangle

function generateUntangle(options: UntangleGenerateOptions = {}): UntangleLevel

Make a level. Throws when the layout cannot hold the ropes, or the scramble cannot reach the crossings asked for.

searchUntangle

function searchUntangle(level: UntangleLevel, from?: Readonly<UntangleState>, options: { maxStates?: number } = {}): UntangleMove[] | null

Search for moves that win a level from a state (default its start): best first, by crossings and unmet targets plus half the moves so far. Returns the moves, an empty list when the state is already won, or null when no win is found within maxStates states (default 20,000).

segmentsCross

function segmentsCross(ax: number, ay: number, bx: number, by: number, cx: number, cy: number, dx: number, dy: number): { x: number; y: number } | null

Whether two segments cross: properly, or overlapping along a line. Touching at an end does not count.

validateUntangleLevel

function validateUntangleLevel(level: UntangleLevel): string[]

Check a level and say what is wrong with it, as plain sentences; empty when it can be played.

Types

RopeUntangleApi

interface RopeUntangleApi

What the rope-untangle pack adds to a game: game.ropeUntangle.

  • create: typeof createUntangle — Make a game of a level.
  • generate: typeof generateUntangle — Make a level by scrambling a solved board.
  • validate: typeof validateUntangleLevel — Check a level; empty when it can be played.
  • search: typeof searchUntangle — Search for a win from a state.
  • feel: typeof createUntangleFeel — Physical ropes that follow a game's plugs, for feel.

UntangleCrossing

interface UntangleCrossing

Where two ropes cross, and which lies over.

  • over: string — The rope on top.
  • under: string — The rope beneath.
  • x: number — Where, across.
  • y: number — Where, down.

UntangleEnd

type UntangleEnd = { socket: string } | { anchor: { x: number; y: number } }

A rope's end: a plug in a socket, which the player moves, or a fixed anchor (a hanging rope's top).

UntangleEvent

type UntangleEvent = | { kind: 'move'; move: UntangleMove; crossings: number } | { kind: 'clear'; rope: string } | { kind: 'won'; moves: number; stars: number } | { kind: 'lost' }

What happened, for the presenter.

UntangleFeel

interface UntangleFeel

The physical ropes of an untangle game.

  • readonly world: RopeWorld — The rope world, for drawing or colliders.
  • hold(rope: string, end: 0 | 1, point: { x: number; y: number }): void — A plug in the hand: from now on that end follows point (call again as the hand moves).
  • release(): void — Let go: the plug goes back to wherever the rules have it (its new socket, after a move).
  • readonly held: { rope: string; end: 0 | 1 } | null — The plug in the hand, or null.
  • step(dt: number): void — Advance the ropes by dt seconds.
  • view(): { id: string; colour?: string; points: number[]; layer: number; fade: number; ends: [{ x: number; y: number }, { x: number; y: number }] }[] — Each rope to draw, bottom first: its polyline [x0, y0, z0, x1, …] (one run), its layer, and how far it has faded out (0 on the board, rising to 1 as a cleared rope leaves).

UntangleFeelOptions

interface UntangleFeelOptions

How the ropes feel.

  • gravity?: number — Gravity, units per second squared, down the board. Default 0: a board seen from above, where ropes lie flat. Give about 900 for hanging ropes seen from the side.
  • slack?: number — Rope length beyond the distance between its ends, as a fraction. Default 0.08 hanging. From above the default is -0.03: a stretchy cord a little shorter than the gap, so its own tension keeps it straight (with no gravity, a slack rope keeps whatever waves it is given).
  • stretch?: number — Stretch compliance (see physics-rope): 0 inextensible. Default 3e-4 from above, a stretchy cord; 0 hanging.
  • radius?: number — The ropes' radius. Default 6.
  • damping?: number — How quickly ropes stop moving, per second. Default 6 from above, 1 hanging.
  • rope?: Omit<RopeWorldOptions, 'gravity' | 'damping' | 'flat'> — More rope-world options (step, iterations).

UntangleGame

interface UntangleGame

A game of one level.

  • readonly level: Readonly<UntangleLevel> — The level, as played (a copy).
  • readonly rules: Readonly<Required<Omit<UntangleRules, 'moves'>>> & { readonly moves: number | null } — The rules in force: the level's, with the defaults filled in.
  • readonly state: Readonly<UntangleState> — The state, live.
  • crossings(): UntangleCrossing[] — Every crossing now.
  • refuse(move: UntangleMove): string | null — Why a move cannot be made, or null when it can.
  • move(move: UntangleMove): { ok: true } | { ok: false; reason: string } — Make a move. Returns whether it was made, and why not.
  • free(): string[] — The sockets no plug is in.
  • moves(): UntangleMove[] — Every move that can be made now.
  • undo(): boolean — Undo the last move. False when there is none.
  • redo(): boolean — Redo an undone move. False when there is none.
  • readonly canUndo: boolean — Whether there is a move to undo.
  • readonly canRedo: boolean — Whether there is a move to redo.
  • restart(): void — Start the level again.
  • hint(maxStates?: number): UntangleMove | null — A move towards the win, or null when none is found within maxStates states (default 20,000).
  • stars(): number — Stars for the moves made: 3 at or under par, 2 within half again, else 1.
  • drain(): UntangleEvent[] — What happened since the last drain, oldest first.
  • view(): UntangleView — Everything to draw.
  • snapshot(): UntangleSave — The game as plain JSON: the moves, replayed on restore.
  • restore(save: UntangleSave): void — Resume a snapshot of this level. Throws on another level's, and the game is left as it was.

UntangleGenerateOptions

interface UntangleGenerateOptions

How to make a level.

  • seed?: number — The seed. The same options and seed give the same level. Default 0.
  • id?: string — An id for the level. Default untangle-<seed>.
  • width?: number — The board's width. Default 600.
  • height?: number — The board's height. Default 800.
  • layout?: UntangleLayout — Where the sockets go. Default a 4 × 5 grid.
  • ropes?: number — How many ropes. Default 5.
  • crossings?: number — The fewest crossings to scramble to. Default the rope count.
  • scramble?: number — The fewest scrambling moves. Default the rope count.
  • rules?: UntangleRules — The rule variations. The targets and both wins colour each rope's home sockets.
  • colours?: ReadonlyArray<string> — The rope colours, in order (repeated when there are more ropes).
  • minimum?: number — The fewest moves the level must need: the best solution the search finds must be at least this long. Default 0. This, with the crossings, is the difficulty; the rope count is not.
  • search?: number — How many states the search may try for a shorter solution (or any, when undoing the scramble is blocked). Default 4,000.

UntangleLayout

type UntangleLayout = /** A grid of holes, a peg board: `cols` across, `rows` down. */ | { kind: 'grid'; cols: number; rows: number } /** A ring of holes. */ | { kind: 'ring'; count: number } /** * Hanging ropes: one anchor per rope along the top, and `slots` sockets in each of `rows` rows below (more slots * than ropes leaves room to move). */ | { kind: 'hanging'; slots: number; rows?: number } /** Your own sockets. */ | { kind: 'sockets'; sockets: ReadonlyArray<{ x: number; y: number }> }

Where the sockets go.

UntangleLevel

interface UntangleLevel

A level.

  • id?: string — Its id, which saves carry.
  • name?: string — A name to show.
  • width: number — The board's width, for presenters.
  • height: number — The board's height, for presenters.
  • sockets: UntangleSocket[] — The sockets.
  • ropes: UntangleRope[] — The ropes, where they start.
  • order?: string[] — Which ropes lie over which at the start, bottom first. Default the order of ropes.
  • rules?: UntangleRules — The rule variations.
  • par?: number — Moves for three stars. Default none: every win is three stars.
  • solution?: UntangleMove[] — A known solution, from the generator.

UntangleMove

interface UntangleMove

A move: take one end of a rope (0 or 1) to a free socket.

  • rope: string — The rope.
  • end: 0 | 1 — Which end.
  • to: string — The free socket it goes to.

UntangleRope

interface UntangleRope

A rope.

  • id: string — Its id, unique on the board.
  • colour?: string — Its colour, for drawing and for the targets win.
  • ends: [UntangleEnd, UntangleEnd] — Its two ends. At least one must be a plug.

UntangleRules

interface UntangleRules

The rule variations.

  • win?: 'untangled' | 'targets' | 'both' — When the level is won: 'untangled' (the default) when no rope crosses another, 'targets' when every plug sits in a socket of its rope's colour, 'both' when both hold.
  • lock?: boolean — A rope with another lying over it is pinned: its plugs cannot move. Default false.
  • clear?: boolean — A rope that crosses nothing after a move leaves the board, freeing its sockets. Default false.
  • moves?: number — The most moves allowed; the level is lost when they run out. Default no limit.

UntangleSave

interface UntangleSave

A saved game.

  • format: 'phaser-ae-rope-untangle' — Always 'phaser-ae-rope-untangle'.
  • version: 1 — The save format's version, 1.
  • level: string — The level's id ('' for a level without one).
  • history: ReturnType<History<UntangleState, UntangleMove>['snapshot']> — The moves, as the kernel's history saves them.

UntangleSocket

interface UntangleSocket

A socket a plug can sit in.

  • id: string — Its id, unique on the board.
  • x: number — Where it is, across.
  • y: number — Where it is, down.
  • colour?: string — The colour a rope must reach here, for the targets win. Default none.

UntangleState

interface UntangleState

The game's state: plain JSON.

  • ends: Record<string, [UntangleEnd, UntangleEnd]> — Each rope's ends now.
  • order: string[] — Which rope lies over which, bottom first.
  • cleared: string[] — The ropes that have left the board (the clear rule).
  • moves: number — Moves made.
  • phase: 'playing' | 'won' | 'lost' — 'playing', then 'won', or 'lost' when the moves run out.

UntangleView

interface UntangleView

Everything a presenter draws.

  • sockets: { id: string; x: number; y: number; colour?: string; rope: string | null; end: 0 | 1 | null }[] — Each socket, its colour, and which rope's plug is in it.
  • ropes: { id: string; colour?: string; a: { x: number; y: number }; b: { x: number; y: number }; layer: number; plugs: [boolean, boolean]; tangled: boolean; locked: boolean; onTarget: [boolean, boolean]; }[] — Each rope still on the board, bottom first: its ends, its layer, whether it crosses anything, whether it is pinned (the lock rule), and whether each end is on its colour.
  • crossings: UntangleCrossing[] — Every crossing.
  • cleared: string[] — The ropes that have left the board.