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([]);
|
||||
}
|
||||
|
||||
// Consistency checker via brute force over assignments
|
||||
const isConsistent = useMemo(() => {
|
||||
// Try all assignments for A, B
|
||||
const ids = new Set(inArena);
|
||||
|
||||
// Consistency checker via brute force over assignments (with A's utterance semantics)
|
||||
const consistentWith = (idsArr: StatementId[]) => {
|
||||
const ids = new Set(idsArr);
|
||||
for (const aKnight 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("AN") && aKnight) continue;
|
||||
if (ids.has("BK") && !bKnight) continue;
|
||||
if (ids.has("BN") && 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")) {
|
||||
const S = (!aKnight) || (!bKnight);
|
||||
// Knights tell truth, knaves lie
|
||||
if (aKnight !== S) continue;
|
||||
if (aKnight !== Scontent) continue;
|
||||
}
|
||||
|
||||
// If we got here, this assignment fits all dragged statements
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return inArena.length <= 1; // single or empty always treated as consistent for UX
|
||||
}, [inArena]);
|
||||
return idsArr.length <= 1;
|
||||
};
|
||||
|
||||
const isConsistent = useMemo(() => consistentWith(inArena), [inArena]);
|
||||
|
||||
const arenaClasses = cn(
|
||||
"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"
|
||||
);
|
||||
|
||||
// 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[] {
|
||||
const ids = new Set(idsArr);
|
||||
const lines: string[] = [];
|
||||
|
|
@ -173,18 +189,19 @@ const KnightsAndKnavesI = () => {
|
|||
{/* Available statements */}
|
||||
<Card className="lg:col-span-1">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xl">Statements</CardTitle>
|
||||
<CardTitle className="text-xl">Potential Truths</CardTitle>
|
||||
</CardHeader>
|
||||
<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 groupClass = meta.group === "A" ? "logic-a" : meta.group === "B" ? "logic-b" : "logic-s";
|
||||
const styleClass = meta.neg ? "logic-dotted" : "logic-solid";
|
||||
const classes = cn(
|
||||
"cursor-move select-none rounded-md bg-surface-variant px-3 py-2 text-sm text-foreground shadow-card border-2",
|
||||
"logic-border",
|
||||
"cursor-move select-none rounded-md bg-surface-variant px-3 py-2 text-sm text-foreground shadow-card border-2 logic-border",
|
||||
groupClass,
|
||||
styleClass
|
||||
styleClass,
|
||||
"ring-1 ring-success/30"
|
||||
);
|
||||
return (
|
||||
<div
|
||||
|
|
@ -194,6 +211,7 @@ const KnightsAndKnavesI = () => {
|
|||
className={classes}
|
||||
aria-label={`Drag: ${s.label}`}
|
||||
role="button"
|
||||
title="Compatible with current worldview"
|
||||
>
|
||||
{s.label}
|
||||
</div>
|
||||
|
|
@ -267,6 +285,37 @@ const KnightsAndKnavesI = () => {
|
|||
</ul>
|
||||
</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>
|
||||
</section>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue