diff --git a/src/components/KnightsAndKnavesI.tsx b/src/components/KnightsAndKnavesI.tsx index 41882cd..e7e5153 100644 --- a/src/components/KnightsAndKnavesI.tsx +++ b/src/components/KnightsAndKnavesI.tsx @@ -15,7 +15,7 @@ type StatementId = typeof STATEMENTS[number]["id"]; const KnightsAndKnavesI = () => { const [inArena, setInArena] = useState([]); - + const [showExplain, setShowExplain] = useState(false); // SEO basics useEffect(() => { const title = "Knights and Knaves – I (Logic Puzzle)"; @@ -100,6 +100,54 @@ const KnightsAndKnavesI = () => { 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 A’s 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, A’s 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 (
@@ -145,10 +193,18 @@ const KnightsAndKnavesI = () => {

{isConsistent ? "Consistent" : "Incompatible set"}

- +
+ {!isConsistent && ( + + )} + +
{inArena.length === 0 ? ( @@ -179,6 +235,16 @@ const KnightsAndKnavesI = () => { )} + {showExplain && !isConsistent && ( +
+

Why inconsistent?

+
    + {explanation.map((line, i) => ( +
  • {line}
  • + ))} +
+
+ )}