feat: add gameplay assists and variant generator
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
import { useState } from "react";
|
||||
import type { PuzzleDefinition, VariantConstraint } from "../domain/types";
|
||||
import {
|
||||
removeKillerCagesAtCells,
|
||||
replaceOverlappingKillerCages,
|
||||
selectionTouchesKillerCage,
|
||||
} from "./constraintEditing";
|
||||
|
||||
interface ConstraintEditorProps {
|
||||
puzzle: PuzzleDefinition;
|
||||
@@ -34,7 +39,7 @@ function describeConstraint(constraint: VariantConstraint, size: number) {
|
||||
case "kropki":
|
||||
return `${constraint.kind} dot · ${cell(constraint.a)}–${cell(constraint.b)}`;
|
||||
case "xv":
|
||||
return `${constraint.total === 5 ? "V" : "X"} · ${cell(constraint.a)}–${cell(constraint.b)}`;
|
||||
return `sum ${String(constraint.total)} pair · ${cell(constraint.a)}–${cell(constraint.b)}`;
|
||||
case "inequality":
|
||||
return `${cell(constraint.lesser)} < ${cell(constraint.greater)}`;
|
||||
}
|
||||
@@ -57,6 +62,17 @@ export function ConstraintEditor({
|
||||
onChange({ ...puzzle, constraints: [...constraints, constraint] });
|
||||
const need = (count: number) => selection.length === count;
|
||||
const atLeast = (count: number) => selection.length >= count;
|
||||
const cageCellCount = Math.max(1, selection.length);
|
||||
const minimumCageSum = (cageCellCount * (cageCellCount + 1)) / 2;
|
||||
const maximumCageSum =
|
||||
(cageCellCount * (2 * puzzle.size - cageCellCount + 1)) / 2;
|
||||
const validCage =
|
||||
selection.length >= 1 &&
|
||||
selection.length <= puzzle.size &&
|
||||
Number.isInteger(cageSum) &&
|
||||
cageSum >= minimumCageSum &&
|
||||
cageSum <= maximumCageSum;
|
||||
const selectedCageExists = selectionTouchesKillerCage(constraints, selection);
|
||||
|
||||
const toggleGlobal = (
|
||||
type: "anti-knight" | "anti-king" | "non-consecutive",
|
||||
@@ -143,22 +159,46 @@ export function ConstraintEditor({
|
||||
Cage sum
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
max={puzzle.size * puzzle.size}
|
||||
min={minimumCageSum}
|
||||
max={maximumCageSum}
|
||||
value={cageSum}
|
||||
onChange={(event) => setCageSum(Number(event.target.value))}
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
disabled={!atLeast(1) || !Number.isInteger(cageSum)}
|
||||
disabled={!validCage}
|
||||
onClick={() =>
|
||||
append({ type: "killer-cage", cells: selection, sum: cageSum })
|
||||
onChange({
|
||||
...puzzle,
|
||||
constraints: replaceOverlappingKillerCages(constraints, {
|
||||
type: "killer-cage",
|
||||
cells: selection,
|
||||
sum: cageSum,
|
||||
}),
|
||||
})
|
||||
}
|
||||
>
|
||||
Add cage
|
||||
Add / replace cage
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="danger"
|
||||
disabled={!selectedCageExists}
|
||||
onClick={() =>
|
||||
onChange({
|
||||
...puzzle,
|
||||
constraints: removeKillerCagesAtCells(constraints, selection),
|
||||
})
|
||||
}
|
||||
>
|
||||
Remove selected cage
|
||||
</button>
|
||||
</div>
|
||||
<p className="muted">
|
||||
Adding replaces any cage touching the selection. Removing clears every
|
||||
cage touching a selected cell.
|
||||
</p>
|
||||
<div className="button-grid">
|
||||
<button
|
||||
type="button"
|
||||
@@ -234,7 +274,7 @@ export function ConstraintEditor({
|
||||
})
|
||||
}
|
||||
>
|
||||
V pair
|
||||
Sum 5 (XV)
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -248,7 +288,7 @@ export function ConstraintEditor({
|
||||
})
|
||||
}
|
||||
>
|
||||
X pair
|
||||
Sum 10 (XV)
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -261,7 +301,20 @@ export function ConstraintEditor({
|
||||
})
|
||||
}
|
||||
>
|
||||
First < second
|
||||
1st < 2nd (inequality)
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
disabled={!need(2)}
|
||||
onClick={() =>
|
||||
append({
|
||||
type: "inequality",
|
||||
lesser: selection[1]!,
|
||||
greater: selection[0]!,
|
||||
})
|
||||
}
|
||||
>
|
||||
1st > 2nd (inequality)
|
||||
</button>
|
||||
</div>
|
||||
<div className="inline-fields">
|
||||
@@ -374,12 +427,8 @@ export function ConstraintEditor({
|
||||
<button type="button" onClick={onCheck} disabled={busy}>
|
||||
Check definition & uniqueness
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onGenerate}
|
||||
disabled={busy || puzzle.size > 9}
|
||||
>
|
||||
Generate classic
|
||||
<button type="button" onClick={onGenerate} disabled={busy}>
|
||||
Open generator
|
||||
</button>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user