Add explanation for inconsistency

Add an "Explain" button next to "Reset" in the Knights and Knaves puzzle. When clicked, this button displays an explanation for any inconsistencies found in the arena below it.
This commit is contained in:
gpt-engineer-app[bot] 2025-08-08 06:55:24 +00:00
parent 954110c6a2
commit 44dd1497f6

View file

@ -15,7 +15,7 @@ type StatementId = typeof STATEMENTS[number]["id"];
const KnightsAndKnavesI = () => { const KnightsAndKnavesI = () => {
const [inArena, setInArena] = useState<StatementId[]>([]); const [inArena, setInArena] = useState<StatementId[]>([]);
const [showExplain, setShowExplain] = useState(false);
// SEO basics // SEO basics
useEffect(() => { useEffect(() => {
const title = "Knights and Knaves I (Logic Puzzle)"; const title = "Knights and Knaves I (Logic Puzzle)";
@ -100,6 +100,54 @@ const KnightsAndKnavesI = () => {
isConsistent ? "bg-surface border-outline" : "bg-destructive/10 border-destructive" isConsistent ? "bg-surface border-outline" : "bg-destructive/10 border-destructive"
); );
function generateExplanation(idsArr: StatementId[]): string[] {
const ids = new Set(idsArr);
const lines: string[] = [];
// Direct role contradictions
if (ids.has("AK") && ids.has("AN")) lines.push("A cannot be both a knight and a knave.");
if (ids.has("BK") && ids.has("BN")) lines.push("B cannot be both a knight and a knave.");
// A's utterance linkage
if (ids.has("S")) {
const forcedA = ids.has("AK") ? true : ids.has("AN") ? false : null;
const forcedB = ids.has("BK") ? true : ids.has("BN") ? false : null;
if (forcedA === true && forcedB === true) {
lines.push("If A and B are both knights, then As statement “at least one of us is a knave” is false; but a knight cannot assert a falsehood.");
}
if (forcedA === false) {
lines.push("If A is a knave, his statement must be false. “At least one of us is a knave” being false means neither is a knave (both are knights), contradicting “A is a knave” and any claim that B is a knave.");
}
if (forcedB === true && forcedA === null) {
lines.push("B is a knight. If A were a knight, his statement would be false; if A were a knave, his statement would be true. Either way, As truthfulness would not match his statement, so no assignment works.");
}
// Enumerate remaining candidate assignments for clarity
if (lines.length === 0) {
const aVals = forcedA === null ? [true, false] : [forcedA];
const bVals = forcedB === null ? [true, false] : [forcedB];
for (const aKnight of aVals) {
for (const bKnight of bVals) {
const content = (!aKnight) || (!bKnight);
const ok = (aKnight === content);
if (!ok) {
const contentTruth = content ? "true" : "false";
lines.push(`If A is ${aKnight ? "a knight" : "a knave"} and B is ${bKnight ? "a knight" : "a knave"}, then the content “at least one of us is a knave” is ${contentTruth}, which a ${aKnight ? "knight must tell" : "knave must not tell"}.`);
}
}
}
}
}
if (lines.length === 0) lines.push("The selected set cannot be satisfied by any assignment of A and B.");
return lines;
}
const explanation = useMemo(() => (isConsistent ? [] : generateExplanation(inArena)), [inArena, isConsistent]);
useEffect(() => { if (isConsistent) setShowExplain(false); }, [isConsistent]);
return ( return (
<section className="space-y-6"> <section className="space-y-6">
<header className="space-y-2"> <header className="space-y-2">
@ -145,10 +193,18 @@ const KnightsAndKnavesI = () => {
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-4">
<p className={cn("text-sm font-medium", isConsistent ? "text-success" : "text-destructive")} <p className={cn("text-sm font-medium", isConsistent ? "text-success" : "text-destructive")}
>{isConsistent ? "Consistent" : "Incompatible set"}</p> >{isConsistent ? "Consistent" : "Incompatible set"}</p>
<button <div className="flex items-center gap-3">
onClick={reset} {!isConsistent && (
className="text-muted-foreground hover:text-foreground text-sm" <button
>Reset</button> onClick={() => setShowExplain((v) => !v)}
className="text-destructive hover:text-destructive/80 text-sm"
>Explain</button>
)}
<button
onClick={reset}
className="text-muted-foreground hover:text-foreground text-sm"
>Reset</button>
</div>
</div> </div>
{inArena.length === 0 ? ( {inArena.length === 0 ? (
@ -179,6 +235,16 @@ const KnightsAndKnavesI = () => {
)} )}
</div> </div>
</div> </div>
{showExplain && !isConsistent && (
<div className="mt-4 rounded-md border border-outline bg-surface p-4">
<h2 className="text-sm font-semibold mb-2">Why inconsistent?</h2>
<ul className="list-disc pl-5 space-y-1 text-sm text-foreground">
{explanation.map((line, i) => (
<li key={i}>{line}</li>
))}
</ul>
</div>
)}
</div> </div>
</div> </div>
</section> </section>