Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix(FifteenPuzzle): guarantee solvable boards; add optional difficulty tiers - #196

Open
borgr wants to merge 3 commits into
TextArena:mainfrom
borgr:fifteenpuzzle-solvable
Open

fix(FifteenPuzzle): guarantee solvable boards; add optional difficulty tiers#196
borgr wants to merge 3 commits into
TextArena:mainfrom
borgr:fifteenpuzzle-solvable

Conversation

@borgr

@borgr borgr commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

Problem: FifteenPuzzleEnv._generate_board shuffles all 16 tiles with a uniform random.shuffle. Only half of the permutations of a sliding puzzle are actually solvable, so ~50% of FifteenPuzzle-v0 episodes are impossible to solve — the agent can never reach the goal state and always runs out to the turn limit. Measured on main: 9983/20000 ≈ 49.9% solvable.

Fix: after shuffling, check solvability via the standard inversion / blank-row parity rule; if the layout is unsolvable, swap two non-blank tiles. A single transposition flips the inversion parity (and doesn't move the blank), which converts an unsolvable board into a solvable one. O(1), no distribution bias beyond the parity correction, and the goal state / tile set are unchanged.

Verification (20,000 generated boards):

  • unsolvable: 0 (was ~50%)
  • trivially already-solved: 0
  • tile integrity (1–15 + one blank) preserved on every board
  • ta.make("FifteenPuzzle-v0")resetstep still works end-to-end

Additive difficulty tiers

Layered on top of the solvability fix, this PR also adds an optional difficulty argument ("easy" / "medium" / "hard") to FifteenPuzzleEnv, and registers FifteenPuzzle-v0-easy, FifteenPuzzle-v0-medium, and FifteenPuzzle-v0-hard.

The difficulty-tier idea comes from @MadBonzz in #169. This reimplements it additively so it composes with the solvable-board generator and doesn't change any existing behaviour:

  • Non-breaking. The constructor keeps its max_turns argument; difficulty defaults to None, which is the existing full-shuffle-then-repair path. FifteenPuzzle-v0 is registered exactly as before.
  • Solvable by construction. When a difficulty is set, the board is scrambled by a bounded number of legal blank slides from the solved layout (easy 1–5, medium 6–10, hard 11–20), so it is always solvable and its distance from solved scales with the tier — no parity repair needed. The scramble avoids immediately undoing its previous slide so the moves don't cancel out.
  • Invalid difficulty values raise ValueError.

Verification (300 boards per tier + default):

  • solvable: 300/300 for every tier and the default path
  • mean misplaced tiles scales with difficulty: easy ≈ 3.9, medium ≈ 7.7, hard ≈ 10.5, default (full random) ≈ 15.0
  • ta.make(...)resetstep works end-to-end for the base id and all three variants
  • FifteenPuzzleEnv(difficulty="…invalid…") raises ValueError

The core solvability change is unchanged from the original scope; the difficulty tiers are purely additive on top of it (new optional kwarg + new registered ids, no change to the prompt, rendering, wrappers, or the existing FifteenPuzzle-v0 registration).

_generate_board did a uniform random.shuffle of all 16 tiles, but only
half of the permutations of a sliding puzzle are solvable, so roughly 50%
of games were impossible to solve (the agent could never reach the goal
and always hit the turn limit).

Detect the unsolvable case via inversion/blank-row parity and, when it
occurs, swap two non-blank tiles to flip the inversion parity and restore
solvability. Verified over 20k boards: 0 unsolvable, 0 trivially-solved,
tile integrity preserved.
Add an optional `difficulty` argument (easy/medium/hard) to
FifteenPuzzleEnv. When set, the board is scrambled a bounded number of
legal slides from the solved layout, so its distance from solved scales
with the tier and the result is solvable by construction. The default
(difficulty=None) is unchanged: a full random shuffle repaired to a
solvable layout, keeping FifteenPuzzle-v0 exactly as before.

Register FifteenPuzzle-v0-easy/-medium/-hard alongside the base id.

The difficulty-tier idea comes from @MadBonzz (TextArena#169); this reimplements
it additively so it composes with the solvable-board generator without
changing the existing constructor or registration.
@borgr borgr changed the title fix(FifteenPuzzle): guarantee generated boards are solvable fix(FifteenPuzzle): guarantee solvable boards; add optional difficulty tiers Aug 10, 2026
The typing import list carried Union, which is not referenced anywhere
in the module.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant