This commit is contained in:
gpt-engineer-app[bot] 2025-12-18 08:56:57 +00:00
parent 9ab07e0421
commit 2922027ee7
3 changed files with 462 additions and 0 deletions

View file

@ -62,6 +62,7 @@ import CubeColoringPage from './pages/CubeColoringPage';
import PresentsPuzzlePage from './pages/PresentsPuzzlePage'; import PresentsPuzzlePage from './pages/PresentsPuzzlePage';
import FerrersRogersRamanujanPage from './pages/FerrersRogersRamanujanPage'; import FerrersRogersRamanujanPage from './pages/FerrersRogersRamanujanPage';
import DominoRetilingPuzzlePage from './pages/DominoRetilingPuzzlePage'; import DominoRetilingPuzzlePage from './pages/DominoRetilingPuzzlePage';
import RentDivisionPuzzlePage from './pages/RentDivisionPuzzlePage';
const queryClient = new QueryClient(); const queryClient = new QueryClient();
@ -93,6 +94,7 @@ const App = () => (
<Route path="/themes/discrete-math/uncertainty/presents-puzzle" element={<PresentsPuzzlePage />} /> <Route path="/themes/discrete-math/uncertainty/presents-puzzle" element={<PresentsPuzzlePage />} />
<Route path="/themes/discrete-math/counting/ferrers-rogers-ramanujan" element={<FerrersRogersRamanujanPage />} /> <Route path="/themes/discrete-math/counting/ferrers-rogers-ramanujan" element={<FerrersRogersRamanujanPage />} />
<Route path="/themes/social-choice" element={<SocialChoice />} /> <Route path="/themes/social-choice" element={<SocialChoice />} />
<Route path="/themes/social-choice/rent-division" element={<RentDivisionPuzzlePage />} />
<Route path="/themes/advanced-algorithms" element={<AdvancedAlgorithms />} /> <Route path="/themes/advanced-algorithms" element={<AdvancedAlgorithms />} />
<Route path="/themes/data-structures" element={<DataStructures />} /> <Route path="/themes/data-structures" element={<DataStructures />} />
<Route path="/themes/games" element={<Games />} /> <Route path="/themes/games" element={<Games />} />

View file

@ -0,0 +1,423 @@
import { useState, useMemo } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { RefreshCw } from "lucide-react";
const AGENTS = ["Alice", "Bob", "Charlie", "Dana"];
const ROOM_COLORS = [
"hsl(142, 70%, 45%)", // green
"hsl(0, 70%, 50%)", // red
"hsl(210, 70%, 50%)", // blue
"hsl(45, 80%, 50%)", // gold/yellow
];
interface RentDivisionPuzzleProps {
onComplete?: (completed: boolean) => void;
}
const RentDivisionPuzzle = ({ onComplete }: RentDivisionPuzzleProps) => {
// V-matrix: valuations[agent][room]
const [valuations, setValuations] = useState<number[][]>(() =>
Array.from({ length: 4 }, () =>
Array.from({ length: 4 }, () => Math.floor(Math.random() * 10) + 1)
)
);
// Rents for each room
const [rents, setRents] = useState<number[]>([5, 5, 5, 5]);
// Allocation: assignment[agent] = room index (-1 = unassigned)
const [assignment, setAssignment] = useState<number[]>([-1, -1, -1, -1]);
const totalRent = useMemo(() => rents.reduce((a, b) => a + b, 0), [rents]);
// Compute U-matrix: utility[agent][room] = valuation - rent
const utilities = useMemo(() => {
return valuations.map((agentVals) =>
agentVals.map((v, roomIdx) => v - rents[roomIdx])
);
}, [valuations, rents]);
// Find envy relationships
const envyInfo = useMemo(() => {
const envies: { from: number; to: number; fromUtil: number; toUtil: number }[] = [];
for (let i = 0; i < 4; i++) {
if (assignment[i] === -1) continue;
const myRoom = assignment[i];
const myUtility = utilities[i][myRoom];
for (let j = 0; j < 4; j++) {
if (i === j || assignment[j] === -1) continue;
const theirRoom = assignment[j];
const theirRoomUtility = utilities[i][theirRoom];
if (theirRoomUtility > myUtility) {
envies.push({
from: i,
to: j,
fromUtil: myUtility,
toUtil: theirRoomUtility,
});
}
}
}
return envies;
}, [assignment, utilities]);
const isEnvyFree = envyInfo.length === 0 && assignment.every((a) => a !== -1);
const allAssigned = assignment.every((a) => a !== -1);
const handleValuationChange = (agent: number, room: number, value: string) => {
const num = parseInt(value) || 0;
setValuations((prev) => {
const next = prev.map((row) => [...row]);
next[agent][room] = Math.max(0, Math.min(99, num));
return next;
});
};
const handleRentChange = (room: number, value: string) => {
const num = parseInt(value) || 0;
setRents((prev) => {
const next = [...prev];
next[room] = Math.max(0, Math.min(99, num));
return next;
});
};
const handleAssignmentChange = (agent: number, room: string) => {
const roomIdx = room === "none" ? -1 : parseInt(room);
setAssignment((prev) => {
const next = [...prev];
// If another agent has this room, unassign them
if (roomIdx !== -1) {
const existing = next.indexOf(roomIdx);
if (existing !== -1 && existing !== agent) {
next[existing] = -1;
}
}
next[agent] = roomIdx;
return next;
});
};
const randomizeValuations = () => {
setValuations(
Array.from({ length: 4 }, () =>
Array.from({ length: 4 }, () => Math.floor(Math.random() * 10) + 1)
)
);
};
const resetAll = () => {
randomizeValuations();
setRents([5, 5, 5, 5]);
setAssignment([-1, -1, -1, -1]);
};
const getAgentColor = (agentIdx: number) => {
const room = assignment[agentIdx];
return room === -1 ? "hsl(0, 0%, 60%)" : ROOM_COLORS[room];
};
return (
<div className="space-y-6">
<div className="flex gap-2 flex-wrap">
<Button variant="outline" size="sm" onClick={randomizeValuations}>
<RefreshCw className="w-4 h-4 mr-2" />
Randomize Values
</Button>
<Button variant="outline" size="sm" onClick={resetAll}>
Reset All
</Button>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Agents & Rooms Visual */}
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-lg">Agents & Rooms</CardTitle>
</CardHeader>
<CardContent>
<div className="flex gap-8 justify-center">
{/* Agents Column */}
<div className="space-y-3">
<div className="text-sm font-medium text-muted-foreground text-center mb-2">
Agents
</div>
{AGENTS.map((name, idx) => (
<div key={idx} className="flex items-center gap-2">
<div
className="w-12 h-12 rounded-lg border-2 flex items-center justify-center text-xs font-medium"
style={{
borderColor: getAgentColor(idx),
backgroundColor: `${getAgentColor(idx)}20`,
}}
>
{name.slice(0, 2)}
</div>
<Select
value={assignment[idx] === -1 ? "none" : String(assignment[idx])}
onValueChange={(v) => handleAssignmentChange(idx, v)}
>
<SelectTrigger className="w-24 h-8 text-xs">
<SelectValue placeholder="Room" />
</SelectTrigger>
<SelectContent>
<SelectItem value="none">None</SelectItem>
{[0, 1, 2, 3].map((r) => (
<SelectItem key={r} value={String(r)}>
Room {r + 1}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
))}
</div>
{/* Rooms Column */}
<div className="space-y-3">
<div className="text-sm font-medium text-muted-foreground text-center mb-2">
Rooms
</div>
{[0, 1, 2, 3].map((roomIdx) => (
<div key={roomIdx} className="flex items-center gap-2">
<div
className="w-12 h-12 rounded-lg border-2 flex items-center justify-center text-xs font-bold"
style={{
borderColor: ROOM_COLORS[roomIdx],
backgroundColor: `${ROOM_COLORS[roomIdx]}30`,
}}
>
R{roomIdx + 1}
</div>
<div className="flex items-center gap-1">
<span className="text-xs text-muted-foreground">$</span>
<Input
type="number"
value={rents[roomIdx]}
onChange={(e) => handleRentChange(roomIdx, e.target.value)}
className="w-14 h-8 text-xs text-center"
min={0}
max={99}
/>
</div>
</div>
))}
<div className="text-sm font-medium pt-2 border-t">
Total: ${totalRent}
</div>
</div>
</div>
</CardContent>
</Card>
{/* V-Matrix (Valuations) */}
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-lg">
V-Matrix <span className="text-sm font-normal text-muted-foreground">(Valuations)</span>
</CardTitle>
</CardHeader>
<CardContent>
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr>
<th className="p-1"></th>
{[1, 2, 3, 4].map((r) => (
<th
key={r}
className="p-1 text-center font-medium"
style={{ color: ROOM_COLORS[r - 1] }}
>
R{r}
</th>
))}
</tr>
</thead>
<tbody>
{AGENTS.map((name, agentIdx) => (
<tr key={agentIdx}>
<td
className="p-1 font-medium text-xs"
style={{ color: getAgentColor(agentIdx) }}
>
{name}
</td>
{[0, 1, 2, 3].map((roomIdx) => {
const isAssigned = assignment[agentIdx] === roomIdx;
return (
<td key={roomIdx} className="p-1">
<Input
type="number"
value={valuations[agentIdx][roomIdx]}
onChange={(e) =>
handleValuationChange(agentIdx, roomIdx, e.target.value)
}
className={`w-12 h-8 text-xs text-center ${
isAssigned ? "ring-2 ring-primary bg-primary/10" : ""
}`}
min={0}
max={99}
/>
</td>
);
})}
</tr>
))}
</tbody>
</table>
</div>
<p className="text-xs text-muted-foreground mt-3">
v<sub>ij</sub> = Agent i's value for Room j
</p>
</CardContent>
</Card>
{/* U-Matrix (Utilities) */}
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-lg">
U-Matrix <span className="text-sm font-normal text-muted-foreground">(Utilities)</span>
</CardTitle>
</CardHeader>
<CardContent>
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr>
<th className="p-1"></th>
{[1, 2, 3, 4].map((r) => (
<th
key={r}
className="p-1 text-center font-medium"
style={{ color: ROOM_COLORS[r - 1] }}
>
R{r}
</th>
))}
</tr>
</thead>
<tbody>
{AGENTS.map((name, agentIdx) => (
<tr key={agentIdx}>
<td
className="p-1 font-medium text-xs"
style={{ color: getAgentColor(agentIdx) }}
>
{name}
</td>
{[0, 1, 2, 3].map((roomIdx) => {
const util = utilities[agentIdx][roomIdx];
const isAssigned = assignment[agentIdx] === roomIdx;
const isEnvied = envyInfo.some(
(e) => e.from === agentIdx && assignment[e.to] === roomIdx
);
return (
<td key={roomIdx} className="p-1">
<div
className={`w-12 h-8 flex items-center justify-center text-xs font-mono rounded border ${
isAssigned
? "bg-primary/20 border-primary font-bold"
: isEnvied
? "bg-destructive/20 border-destructive text-destructive"
: "bg-muted/50 border-border"
}`}
>
{util}
</div>
</td>
);
})}
</tr>
))}
</tbody>
</table>
</div>
<p className="text-xs text-muted-foreground mt-3">
u<sub>ij</sub> = v<sub>ij</sub> r<sub>j</sub>
</p>
</CardContent>
</Card>
</div>
{/* Status / Envy Display */}
<Card className={allAssigned ? (isEnvyFree ? "border-green-500/50 bg-green-500/5" : "border-destructive/50 bg-destructive/5") : ""}>
<CardHeader className="pb-2">
<CardTitle className="text-lg">
{!allAssigned
? "Assign all agents to rooms"
: isEnvyFree
? "✓ Envy-Free Allocation!"
: "Envy Detected"}
</CardTitle>
</CardHeader>
<CardContent>
{!allAssigned ? (
<p className="text-sm text-muted-foreground">
Each agent must be assigned to exactly one room.
</p>
) : isEnvyFree ? (
<p className="text-sm text-green-600 dark:text-green-400">
No agent prefers another agent's room at the current prices. This is an envy-free allocation!
</p>
) : (
<div className="space-y-2">
{envyInfo.map((envy, idx) => (
<div
key={idx}
className="text-sm p-2 rounded bg-destructive/10 border border-destructive/20"
>
<span className="font-medium" style={{ color: getAgentColor(envy.from) }}>
{AGENTS[envy.from]}
</span>{" "}
envies{" "}
<span className="font-medium" style={{ color: getAgentColor(envy.to) }}>
{AGENTS[envy.to]}
</span>
: utility {envy.fromUtil} &lt; {envy.toUtil}
</div>
))}
</div>
)}
</CardContent>
</Card>
{/* Explanation */}
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-lg">How It Works</CardTitle>
</CardHeader>
<CardContent className="text-sm text-muted-foreground space-y-2">
<p>
<strong>Problem:</strong> n agents share n rooms and must divide a total rent R.
</p>
<p>
<strong>Goal:</strong> Find an assignment σ and rents r,...,rₙ (summing to R) such that
no agent envies anotheri.e., each agent's utility for their own room is at least as high
as for any other room.
</p>
<p>
<strong>Utility:</strong> u<sub>ij</sub> = v<sub>ij</sub> r<sub>j</sub> (value minus rent).
</p>
<p>
<strong>Envy-Free:</strong> Agent i is envy-free if u<sub>i,σ(i)</sub> u<sub>ij</sub> for all j.
</p>
</CardContent>
</Card>
</div>
);
};
export default RentDivisionPuzzle;

View file

@ -0,0 +1,37 @@
import { useState } from "react";
import RentDivisionPuzzle from "@/components/RentDivisionPuzzle";
import SocialShare from "@/components/SocialShare";
const RentDivisionPuzzlePage = () => {
const [isCompleted, setIsCompleted] = useState(false);
return (
<div
className={`min-h-screen transition-colors duration-500 p-6 ${
isCompleted ? "bg-green-50/50 dark:bg-green-950/20" : "bg-background"
}`}
>
<div className="max-w-6xl mx-auto space-y-6">
<div className="space-y-2">
<h1 className="text-3xl font-bold text-foreground">
Envy-Free Rent Division
</h1>
<p className="text-muted-foreground">
Explore fair division by assigning rooms and setting rents. Can you find an
allocation where no one envies another?
</p>
</div>
<RentDivisionPuzzle onComplete={setIsCompleted} />
<SocialShare
title="Envy-Free Rent Division - Interactive Fair Division"
description="Explore fair division by assigning rooms and setting rents to achieve envy-free allocations."
url="https://lovable.dev/themes/social-choice/rent-division"
/>
</div>
</div>
);
};
export default RentDivisionPuzzlePage;