From 2922027ee7627f961cdb25be38dcf1b811ae985a Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Thu, 18 Dec 2025 08:56:57 +0000 Subject: [PATCH] Changes --- src/App.tsx | 2 + src/components/RentDivisionPuzzle.tsx | 423 ++++++++++++++++++++++++++ src/pages/RentDivisionPuzzlePage.tsx | 37 +++ 3 files changed, 462 insertions(+) create mode 100644 src/components/RentDivisionPuzzle.tsx create mode 100644 src/pages/RentDivisionPuzzlePage.tsx diff --git a/src/App.tsx b/src/App.tsx index 0c4261a..9ca5682 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -62,6 +62,7 @@ import CubeColoringPage from './pages/CubeColoringPage'; import PresentsPuzzlePage from './pages/PresentsPuzzlePage'; import FerrersRogersRamanujanPage from './pages/FerrersRogersRamanujanPage'; import DominoRetilingPuzzlePage from './pages/DominoRetilingPuzzlePage'; +import RentDivisionPuzzlePage from './pages/RentDivisionPuzzlePage'; const queryClient = new QueryClient(); @@ -93,6 +94,7 @@ const App = () => ( } /> } /> } /> + } /> } /> } /> } /> diff --git a/src/components/RentDivisionPuzzle.tsx b/src/components/RentDivisionPuzzle.tsx new file mode 100644 index 0000000..8981580 --- /dev/null +++ b/src/components/RentDivisionPuzzle.tsx @@ -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(() => + Array.from({ length: 4 }, () => + Array.from({ length: 4 }, () => Math.floor(Math.random() * 10) + 1) + ) + ); + + // Rents for each room + const [rents, setRents] = useState([5, 5, 5, 5]); + + // Allocation: assignment[agent] = room index (-1 = unassigned) + const [assignment, setAssignment] = useState([-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 ( +
+
+ + +
+ +
+ {/* Agents & Rooms Visual */} + + + Agents & Rooms + + +
+ {/* Agents Column */} +
+
+ Agents +
+ {AGENTS.map((name, idx) => ( +
+
+ {name.slice(0, 2)} +
+ +
+ ))} +
+ + {/* Rooms Column */} +
+
+ Rooms +
+ {[0, 1, 2, 3].map((roomIdx) => ( +
+
+ R{roomIdx + 1} +
+
+ $ + handleRentChange(roomIdx, e.target.value)} + className="w-14 h-8 text-xs text-center" + min={0} + max={99} + /> +
+
+ ))} +
+ Total: ${totalRent} +
+
+
+
+
+ + {/* V-Matrix (Valuations) */} + + + + V-Matrix (Valuations) + + + +
+ + + + + {[1, 2, 3, 4].map((r) => ( + + ))} + + + + {AGENTS.map((name, agentIdx) => ( + + + {[0, 1, 2, 3].map((roomIdx) => { + const isAssigned = assignment[agentIdx] === roomIdx; + return ( + + ); + })} + + ))} + +
+ R{r} +
+ {name} + + + 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} + /> +
+
+

+ vij = Agent i's value for Room j +

+
+
+ + {/* U-Matrix (Utilities) */} + + + + U-Matrix (Utilities) + + + +
+ + + + + {[1, 2, 3, 4].map((r) => ( + + ))} + + + + {AGENTS.map((name, agentIdx) => ( + + + {[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 ( + + ); + })} + + ))} + +
+ R{r} +
+ {name} + +
+ {util} +
+
+
+

+ uij = vij − rj +

+
+
+
+ + {/* Status / Envy Display */} + + + + {!allAssigned + ? "Assign all agents to rooms" + : isEnvyFree + ? "✓ Envy-Free Allocation!" + : "Envy Detected"} + + + + {!allAssigned ? ( +

+ Each agent must be assigned to exactly one room. +

+ ) : isEnvyFree ? ( +

+ No agent prefers another agent's room at the current prices. This is an envy-free allocation! +

+ ) : ( +
+ {envyInfo.map((envy, idx) => ( +
+ + {AGENTS[envy.from]} + {" "} + envies{" "} + + {AGENTS[envy.to]} + + : utility {envy.fromUtil} < {envy.toUtil} +
+ ))} +
+ )} +
+
+ + {/* Explanation */} + + + How It Works + + +

+ Problem: n agents share n rooms and must divide a total rent R. +

+

+ Goal: Find an assignment σ and rents r₁,...,rₙ (summing to R) such that + no agent envies another—i.e., each agent's utility for their own room is at least as high + as for any other room. +

+

+ Utility: uij = vij − rj (value minus rent). +

+

+ Envy-Free: Agent i is envy-free if ui,σ(i) ≥ uij for all j. +

+
+
+
+ ); +}; + +export default RentDivisionPuzzle; diff --git a/src/pages/RentDivisionPuzzlePage.tsx b/src/pages/RentDivisionPuzzlePage.tsx new file mode 100644 index 0000000..63dad7f --- /dev/null +++ b/src/pages/RentDivisionPuzzlePage.tsx @@ -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 ( +
+
+
+

+ Envy-Free Rent Division +

+

+ Explore fair division by assigning rooms and setting rents. Can you find an + allocation where no one envies another? +

+
+ + + + +
+
+ ); +}; + +export default RentDivisionPuzzlePage;