Skip to content

Commit c1d10d5

Browse files
committed
Remove portal puzzles from puzzle screen
1 parent d31e589 commit c1d10d5

1 file changed

Lines changed: 44 additions & 128 deletions

File tree

src/screens/PuzzlesScreen.tsx

Lines changed: 44 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,11 @@ import {
66
PUZZLES, filterPuzzles, puzzleDifficulty,
77
type Puzzle, type Difficulty
88
} from "../puzzles/puzzles";
9-
import {
10-
PORTAL_PUZZLES, filterPortalPuzzles,
11-
type PortalPuzzle
12-
} from "../puzzles/portal-puzzles";
13-
import { parseUci, parseSquare, type Square } from "../engine/board";
9+
import { parseUci } from "../engine/board";
1410
import { gameResult } from "../engine/rules";
1511

1612
type Status = "solving" | "wrong" | "solved";
1713
type MateFilter = "all" | 1 | 2 | 3;
18-
type Mode = "standard" | "portal";
19-
20-
/** Parse extended UCI: "e2e4" or "e2e4@d8" (portal teleport). */
21-
function parsePortalUci(uci: string): {
22-
from: Square; to: Square; portalTo?: Square;
23-
promotion?: "Q" | "R" | "B" | "N";
24-
} {
25-
const atIdx = uci.indexOf("@");
26-
if (atIdx !== -1) {
27-
const base = uci.slice(0, atIdx);
28-
const land = uci.slice(atIdx + 1);
29-
return {
30-
from: parseSquare(base.slice(0, 2)),
31-
to: parseSquare(base.slice(2, 4)),
32-
portalTo: parseSquare(land.slice(0, 2)),
33-
};
34-
}
35-
const u = parseUci(uci);
36-
return { from: u.from, to: u.to, promotion: u.promotion as "Q" | "R" | "B" | "N" | undefined };
37-
}
3814

3915
const DIFF_LABEL: Record<Difficulty | "all", string> = {
4016
all: "All",
@@ -46,10 +22,8 @@ const DIFF_LABEL: Record<Difficulty | "all", string> = {
4622

4723
export function PuzzlesScreen() {
4824
const { loadPosition, state, tryMove, activeProfile, recordPuzzleSolved, recordPuzzleAttempt } = useGame();
49-
const [mode, setMode] = useState<Mode>("standard");
5025
const [difficulty, setDifficulty] = useState<Difficulty | "all">("beginner");
5126
const [mateIn, setMateIn] = useState<MateFilter>("all");
52-
const [portalMateIn, setPortalMateIn] = useState<1 | 2 | "all">("all");
5327
const [newOnly, setNewOnly] = useState(true);
5428
const [index, setIndex] = useState(0);
5529
const [status, setStatus] = useState<Status>("solving");
@@ -73,20 +47,13 @@ export function PuzzlesScreen() {
7347
return unsolved.length > 0 ? unsolved : basePool;
7448
}, [basePool, newOnly, solvedIds]);
7549

76-
const basePortalPool = useMemo(
77-
() => filterPortalPuzzles({ mateIn: portalMateIn }),
78-
[portalMateIn]
79-
);
80-
const portalPool = useMemo(() => {
81-
if (!newOnly) return basePortalPool;
82-
const unsolved = basePortalPool.filter((p) => !solvedIds.has(p.id));
83-
return unsolved.length > 0 ? unsolved : basePortalPool;
84-
}, [basePortalPool, newOnly, solvedIds]);
85-
86-
const pool: (Puzzle | PortalPuzzle)[] = mode === "portal" ? portalPool : stdPool;
50+
const pool: Puzzle[] = stdPool;
8751
const puzzle = pool[index];
8852
const sideToMate = useMemo(() => (puzzle ? puzzle.setup().turn : "w"), [puzzle?.id]);
89-
const totalSolved = activeProfile?.stats.puzzlesSolved ?? 0;
53+
const totalSolved = useMemo(
54+
() => PUZZLES.reduce((n, p) => n + (solvedIds.has(p.id) ? 1 : 0), 0),
55+
[solvedIds]
56+
);
9057

9158
const diffCounts = useMemo(() => {
9259
const out: Record<Difficulty | "all", { solved: number; total: number }> = {
@@ -118,7 +85,7 @@ export function PuzzlesScreen() {
11885
return out;
11986
}, [solvedIds, difficulty]);
12087

121-
useEffect(() => { setIndex(0); }, [mateIn, difficulty, newOnly, mode, portalMateIn]);
88+
useEffect(() => { setIndex(0); }, [mateIn, difficulty, newOnly]);
12289

12390
useEffect(() => {
12491
if (!puzzle) return;
@@ -139,20 +106,11 @@ export function PuzzlesScreen() {
139106
const plyIndex = moved - 1;
140107
const expected = puzzle.moves[plyIndex];
141108
const last = state.history[state.history.length - 1];
142-
const exp = parsePortalUci(expected);
109+
const exp = parseUci(expected);
143110
let ok =
144111
last.from.file === exp.from.file && last.from.rank === exp.from.rank &&
145112
last.to.file === exp.to.file && last.to.rank === exp.to.rank &&
146113
(!exp.promotion || last.promotion === exp.promotion);
147-
if (ok && exp.portalTo) {
148-
ok = !!last.isPortalEntry &&
149-
!!last.portalTo &&
150-
last.portalTo.file === exp.portalTo.file &&
151-
last.portalTo.rank === exp.portalTo.rank;
152-
} else if (ok && !exp.portalTo && last.isPortalEntry) {
153-
// Expected a non-portal move, but the user made a portal move.
154-
ok = false;
155-
}
156114

157115
// Accept alternative winning moves: if the user has just checkmated,
158116
// count the puzzle as solved even when it differs from the scripted line.
@@ -184,9 +142,9 @@ export function PuzzlesScreen() {
184142
}
185143

186144
setPlayedPlies(moved);
187-
const reply = parsePortalUci(puzzle.moves[nextIdx]);
145+
const reply = parseUci(puzzle.moves[nextIdx]);
188146
const t = setTimeout(() => {
189-
tryMove(reply.from, reply.to, reply.promotion, reply.portalTo);
147+
tryMove(reply.from, reply.to, reply.promotion as "Q" | "R" | "B" | "N" | undefined);
190148
setPlayedPlies((x) => x + 1);
191149
}, 300);
192150
return () => clearTimeout(t);
@@ -215,11 +173,9 @@ export function PuzzlesScreen() {
215173
return <div className="puzzle-banner">You play {side}. Mate in {puzzle.mateIn()} — find the forced win.{done}</div>;
216174
}, [status, puzzle, alreadySolved, sideToMate]);
217175

218-
const allSolvedHere = mode === "portal"
219-
? basePortalPool.length > 0 && basePortalPool.every((p) => solvedIds.has(p.id))
220-
: basePool.length > 0 && basePool.every((p) => solvedIds.has(p.id));
176+
const allSolvedHere = basePool.length > 0 && basePool.every((p) => solvedIds.has(p.id));
221177

222-
const totalCount = mode === "portal" ? PORTAL_PUZZLES.length : PUZZLES.length;
178+
const totalCount = PUZZLES.length;
223179

224180
return (
225181
<div className="screen">
@@ -230,78 +186,39 @@ export function PuzzlesScreen() {
230186
<h2>🧩 Puzzles</h2>
231187

232188
<div className="puzzle-tabs">
233-
<span className="tabs-label">Mode</span>
234-
<button
235-
className={mode === "standard" ? "pill active" : "pill"}
236-
onClick={() => setMode("standard")}
237-
>Standard</button>
238-
<button
239-
className={mode === "portal" ? "pill active" : "pill"}
240-
onClick={() => setMode("portal")}
241-
>🌀 Portal</button>
189+
<span className="tabs-label">Level</span>
190+
{(["beginner", "easy", "medium", "hard", "all"] as const).map((d) => {
191+
const c = diffCounts[d];
192+
return (
193+
<button key={d}
194+
className={difficulty === d ? "pill active" : "pill"}
195+
onClick={() => setDifficulty(d)}
196+
>{DIFF_LABEL[d]}<span className="count">{c.solved}/{c.total}</span></button>
197+
);
198+
})}
242199
</div>
243200

244-
{mode === "standard" && (
245-
<div className="puzzle-tabs">
246-
<span className="tabs-label">Level</span>
247-
{(["beginner", "easy", "medium", "hard", "all"] as const).map((d) => {
248-
const c = diffCounts[d];
249-
return (
250-
<button key={d}
251-
className={difficulty === d ? "pill active" : "pill"}
252-
onClick={() => setDifficulty(d)}
253-
>{DIFF_LABEL[d]}<span className="count">{c.solved}/{c.total}</span></button>
254-
);
255-
})}
256-
</div>
257-
)}
258-
259-
{mode === "standard" ? (
260-
<div className="puzzle-tabs">
261-
<span className="tabs-label">Type</span>
262-
{([
263-
{ key: "all" as MateFilter, label: "All" },
264-
{ key: 1 as MateFilter, label: "M1" },
265-
{ key: 2 as MateFilter, label: "M2" },
266-
{ key: 3 as MateFilter, label: "M3" }
267-
]).map((it) => {
268-
const c = mateCounts[it.key];
269-
return (
270-
<button key={String(it.key)}
271-
className={mateIn === it.key ? "pill active" : "pill"}
272-
onClick={() => setMateIn(it.key)}
273-
>{it.label}<span className="count">{c.solved}/{c.total}</span></button>
274-
);
275-
})}
276-
<label className="pill toggle" style={{ cursor: "pointer", marginLeft: "auto" }}>
277-
<input type="checkbox" checked={newOnly} onChange={(e) => setNewOnly(e.target.checked)} />
278-
New only
279-
</label>
280-
</div>
281-
) : (
282-
<div className="puzzle-tabs">
283-
<span className="tabs-label">Type</span>
284-
{(["all", 1, 2] as const).map((it) => {
285-
const count = basePortalPool.length === 0 && it !== portalMateIn
286-
? PORTAL_PUZZLES.filter((p) => it === "all" || p.mateIn() === it).length
287-
: 0;
288-
const total = PORTAL_PUZZLES.filter((p) => it === "all" || p.mateIn() === it).length;
289-
const solved = PORTAL_PUZZLES.filter((p) =>
290-
(it === "all" || p.mateIn() === it) && solvedIds.has(p.id)
291-
).length;
292-
return (
293-
<button key={String(it)}
294-
className={portalMateIn === it ? "pill active" : "pill"}
295-
onClick={() => setPortalMateIn(it)}
296-
>{it === "all" ? "All" : `M${it}`}<span className="count">{solved}/{total || count}</span></button>
297-
);
298-
})}
299-
<label className="pill toggle" style={{ cursor: "pointer", marginLeft: "auto" }}>
300-
<input type="checkbox" checked={newOnly} onChange={(e) => setNewOnly(e.target.checked)} />
301-
New only
302-
</label>
303-
</div>
304-
)}
201+
<div className="puzzle-tabs">
202+
<span className="tabs-label">Type</span>
203+
{([
204+
{ key: "all" as MateFilter, label: "All" },
205+
{ key: 1 as MateFilter, label: "M1" },
206+
{ key: 2 as MateFilter, label: "M2" },
207+
{ key: 3 as MateFilter, label: "M3" }
208+
]).map((it) => {
209+
const c = mateCounts[it.key];
210+
return (
211+
<button key={String(it.key)}
212+
className={mateIn === it.key ? "pill active" : "pill"}
213+
onClick={() => setMateIn(it.key)}
214+
>{it.label}<span className="count">{c.solved}/{c.total}</span></button>
215+
);
216+
})}
217+
<label className="pill toggle" style={{ cursor: "pointer", marginLeft: "auto" }}>
218+
<input type="checkbox" checked={newOnly} onChange={(e) => setNewOnly(e.target.checked)} />
219+
New only
220+
</label>
221+
</div>
305222

306223
{allSolvedHere && newOnly && (
307224
<p className="hint" style={{ margin: "4px 0 8px" }}>
@@ -316,8 +233,7 @@ export function PuzzlesScreen() {
316233
{banner}
317234
<div className="puzzle-meta">
318235
<span>Puzzle {index + 1} / {pool.length}</span>
319-
{"rating" in puzzle && puzzle.rating !== undefined && <span className="pill">{puzzle.rating}</span>}
320-
{mode === "portal" && <span className="pill">🌀 Portal</span>}
236+
{puzzle.rating !== undefined && <span className="pill">{puzzle.rating}</span>}
321237
{alreadySolved && <span className="pill solved">✓ Solved</span>}
322238
{entry && entry.attempts > 0 && <span className="pill">Tries: {entry.attempts}</span>}
323239
</div>

0 commit comments

Comments
 (0)