Refactor interactive elements
Change "Statements" to "Potential Truths". Add a new zone below the arena called "Incompatible with current worldview". When a statement conflicts with the current setup, it will be moved to this zone, and it will not be possible to drag statements from this zone.
This commit is contained in:
parent
4b1940859a
commit
81f6873c4d
1 changed files with 67 additions and 18 deletions
|
|
@ -78,39 +78,55 @@ const KnightsAndKnavesI = () => {
|
||||||
setInArena([]);
|
setInArena([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Consistency checker via brute force over assignments
|
// Consistency checker via brute force over assignments (with A's utterance semantics)
|
||||||
const isConsistent = useMemo(() => {
|
const consistentWith = (idsArr: StatementId[]) => {
|
||||||
// Try all assignments for A, B
|
const ids = new Set(idsArr);
|
||||||
const ids = new Set(inArena);
|
|
||||||
|
|
||||||
for (const aKnight of [true, false]) {
|
for (const aKnight of [true, false]) {
|
||||||
for (const bKnight of [true, false]) {
|
for (const bKnight of [true, false]) {
|
||||||
// role constraints from dragged tokens
|
// role constraints
|
||||||
if (ids.has("AK") && !aKnight) continue;
|
if (ids.has("AK") && !aKnight) continue;
|
||||||
if (ids.has("AN") && aKnight) continue;
|
if (ids.has("AN") && aKnight) continue;
|
||||||
if (ids.has("BK") && !bKnight) continue;
|
if (ids.has("BK") && !bKnight) continue;
|
||||||
if (ids.has("BN") && bKnight) continue;
|
if (ids.has("BN") && bKnight) continue;
|
||||||
if (ids.has("SNEG") && !(aKnight && bKnight)) continue;
|
if (ids.has("SNEG") && !(aKnight && bKnight)) continue;
|
||||||
|
|
||||||
// A's utterance if included: S = "At least one is a knave"
|
// Content of A's statement: S = "At least one of us is a knave."
|
||||||
|
const Scontent = (!aKnight) || (!bKnight);
|
||||||
|
|
||||||
|
// Implicit linkage: if A is declared knight/knave, his statement's content must match
|
||||||
|
if (ids.has("AK") && !Scontent) continue;
|
||||||
|
if (ids.has("AN") && Scontent) continue;
|
||||||
|
|
||||||
|
// If token S is explicitly included, also enforce truthfulness parity
|
||||||
if (ids.has("S")) {
|
if (ids.has("S")) {
|
||||||
const S = (!aKnight) || (!bKnight);
|
if (aKnight !== Scontent) continue;
|
||||||
// Knights tell truth, knaves lie
|
|
||||||
if (aKnight !== S) continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we got here, this assignment fits all dragged statements
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return inArena.length <= 1; // single or empty always treated as consistent for UX
|
return idsArr.length <= 1;
|
||||||
}, [inArena]);
|
};
|
||||||
|
|
||||||
|
const isConsistent = useMemo(() => consistentWith(inArena), [inArena]);
|
||||||
|
|
||||||
const arenaClasses = cn(
|
const arenaClasses = cn(
|
||||||
"relative rounded-lg border transition-colors min-h-[320px] grid place-items-center p-6",
|
"relative rounded-lg border transition-colors min-h-[320px] grid place-items-center p-6",
|
||||||
isConsistent ? "bg-surface border-outline" : "bg-destructive/10 border-destructive"
|
isConsistent ? "bg-surface border-outline" : "bg-destructive/10 border-destructive"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Partition remaining statements into compatible vs incompatible under current worldview
|
||||||
|
const partitions = useMemo(() => {
|
||||||
|
const remaining = STATEMENTS.filter(s => !inArena.includes(s.id));
|
||||||
|
const incompatible = remaining
|
||||||
|
.filter(s => !consistentWith([...inArena, s.id]))
|
||||||
|
.map(s => s.id);
|
||||||
|
const compatible = remaining
|
||||||
|
.filter(s => !incompatible.includes(s.id))
|
||||||
|
.map(s => s.id);
|
||||||
|
return { compatible, incompatible };
|
||||||
|
}, [inArena]);
|
||||||
|
|
||||||
function generateExplanation(idsArr: StatementId[]): string[] {
|
function generateExplanation(idsArr: StatementId[]): string[] {
|
||||||
const ids = new Set(idsArr);
|
const ids = new Set(idsArr);
|
||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
|
|
@ -173,18 +189,19 @@ const KnightsAndKnavesI = () => {
|
||||||
{/* Available statements */}
|
{/* Available statements */}
|
||||||
<Card className="lg:col-span-1">
|
<Card className="lg:col-span-1">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-xl">Statements</CardTitle>
|
<CardTitle className="text-xl">Potential Truths</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex flex-wrap gap-3">
|
<CardContent className="flex flex-wrap gap-3">
|
||||||
{available.map(s => {
|
{partitions.compatible.map(id => {
|
||||||
|
const s = STATEMENTS.find(x => x.id === id)!;
|
||||||
const meta = META[s.id];
|
const meta = META[s.id];
|
||||||
const groupClass = meta.group === "A" ? "logic-a" : meta.group === "B" ? "logic-b" : "logic-s";
|
const groupClass = meta.group === "A" ? "logic-a" : meta.group === "B" ? "logic-b" : "logic-s";
|
||||||
const styleClass = meta.neg ? "logic-dotted" : "logic-solid";
|
const styleClass = meta.neg ? "logic-dotted" : "logic-solid";
|
||||||
const classes = cn(
|
const classes = cn(
|
||||||
"cursor-move select-none rounded-md bg-surface-variant px-3 py-2 text-sm text-foreground shadow-card border-2",
|
"cursor-move select-none rounded-md bg-surface-variant px-3 py-2 text-sm text-foreground shadow-card border-2 logic-border",
|
||||||
"logic-border",
|
|
||||||
groupClass,
|
groupClass,
|
||||||
styleClass
|
styleClass,
|
||||||
|
"ring-1 ring-success/30"
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
@ -194,6 +211,7 @@ const KnightsAndKnavesI = () => {
|
||||||
className={classes}
|
className={classes}
|
||||||
aria-label={`Drag: ${s.label}`}
|
aria-label={`Drag: ${s.label}`}
|
||||||
role="button"
|
role="button"
|
||||||
|
title="Compatible with current worldview"
|
||||||
>
|
>
|
||||||
{s.label}
|
{s.label}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -267,6 +285,37 @@ const KnightsAndKnavesI = () => {
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{partitions.incompatible.length > 0 && (
|
||||||
|
<Card className="mt-4">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-xl">Incompatible with current worldview</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex flex-wrap gap-3">
|
||||||
|
{partitions.incompatible.map(id => {
|
||||||
|
const s = STATEMENTS.find(x => x.id === id)!;
|
||||||
|
const meta = META[id];
|
||||||
|
const groupClass = meta.group === "A" ? "logic-a" : meta.group === "B" ? "logic-b" : "logic-s";
|
||||||
|
const styleClass = meta.neg ? "logic-dotted" : "logic-solid";
|
||||||
|
const classes = cn(
|
||||||
|
"select-none rounded-md bg-surface-variant px-3 py-2 text-sm text-foreground shadow-card border-2 logic-border",
|
||||||
|
groupClass,
|
||||||
|
styleClass,
|
||||||
|
"opacity-60 cursor-not-allowed bg-destructive/10 ring-1 ring-destructive/40"
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={id}
|
||||||
|
className={classes}
|
||||||
|
aria-disabled="true"
|
||||||
|
title="Conflicts with current worldview"
|
||||||
|
>
|
||||||
|
{s.label}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue