Fix graph layout and edge labels

The graph now occupies the full screen width, and approximately one-third of the edges are left without constraints to improve spacing and reduce visual clutter.
This commit is contained in:
gpt-engineer-app[bot] 2025-08-10 07:12:03 +00:00
parent 9b07ffe9d9
commit 48c3f7a97a

View file

@ -76,16 +76,26 @@ const DogsBunnyPuzzle: React.FC<DogsBunnyPuzzleProps> = ({
const conditionPrefixes = ['SOMEBODY AT', 'NOBODY AT']; const conditionPrefixes = ['SOMEBODY AT', 'NOBODY AT'];
// Connect all locations with conditional edges // Connect all locations with conditional edges
let edgeCount = 0;
const totalPossibleEdges = locations.length * (locations.length - 1);
const freeEdgePercentage = 0.33; // About 1/3 of edges will be free
for (let i = 0; i < locations.length; i++) { for (let i = 0; i < locations.length; i++) {
for (let j = i + 1; j < locations.length; j++) { for (let j = i + 1; j < locations.length; j++) {
// Decide if this edge should be free (no conditions)
const shouldBeFree = edgeCount < totalPossibleEdges * freeEdgePercentage && rng() < 0.5;
let conditions: string[] = [];
if (!shouldBeFree) {
const numConditions = diff === 'easy' ? 1 : diff === 'medium' ? 2 : 3; const numConditions = diff === 'easy' ? 1 : diff === 'medium' ? 2 : 3;
const conditions: string[] = [];
for (let k = 0; k < numConditions; k++) { for (let k = 0; k < numConditions; k++) {
const conditionType = locationTypes[Math.floor(rng() * locationTypes.length)]; const conditionType = locationTypes[Math.floor(rng() * locationTypes.length)];
const prefix = conditionPrefixes[Math.floor(rng() * conditionPrefixes.length)]; const prefix = conditionPrefixes[Math.floor(rng() * conditionPrefixes.length)];
conditions.push(`${prefix} ${conditionType.toUpperCase()}`); conditions.push(`${prefix} ${conditionType.toUpperCase()}`);
} }
}
edges.push({ edges.push({
from: locations[i].id, from: locations[i].id,
@ -98,6 +108,8 @@ const DogsBunnyPuzzle: React.FC<DogsBunnyPuzzleProps> = ({
to: locations[i].id, to: locations[i].id,
conditions: conditions conditions: conditions
}); });
edgeCount += 2;
} }
} }
@ -339,13 +351,11 @@ const DogsBunnyPuzzle: React.FC<DogsBunnyPuzzleProps> = ({
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="flex flex-col lg:flex-row gap-6"> {/* Puzzle Area - Full Width */}
{/* Puzzle Area */}
<div className="flex-1">
<div <div
id="puzzle-area" id="puzzle-area"
className="relative bg-muted/10 rounded-lg p-4 min-h-[600px] w-full" className="relative bg-muted/10 rounded-lg p-6 min-h-[700px] w-full mb-6"
style={{ height: '600px' }} style={{ height: '700px' }}
> >
{/* Edges */} {/* Edges */}
<svg className="absolute inset-0 w-full h-full pointer-events-none"> <svg className="absolute inset-0 w-full h-full pointer-events-none">
@ -369,35 +379,36 @@ const DogsBunnyPuzzle: React.FC<DogsBunnyPuzzleProps> = ({
strokeWidth={isValid ? "3" : "1"} strokeWidth={isValid ? "3" : "1"}
strokeDasharray={isValid ? "none" : "5,5"} strokeDasharray={isValid ? "none" : "5,5"}
/> />
{/* Condition labels - simplified to show first condition only */} {/* Condition labels - only show if edge has conditions */}
{edge.conditions.length > 0 && (
<g> <g>
{edge.conditions.length === 1 ? ( {edge.conditions.length === 1 ? (
// Single condition // Single condition
<circle <circle
cx={(fromLoc.x + toLoc.x) / 2 + 20} cx={(fromLoc.x + toLoc.x) / 2 + 30}
cy={(fromLoc.y + toLoc.y) / 2 + 20} cy={(fromLoc.y + toLoc.y) / 2 + 30}
r="12" r="14"
fill="hsl(var(--background))" fill="hsl(var(--background))"
stroke={edge.conditions[0].includes('NOBODY') ? "#ef4444" : "hsl(var(--border))"} stroke={edge.conditions[0].includes('NOBODY') ? "#ef4444" : "hsl(var(--border))"}
strokeWidth="2" strokeWidth="2"
/> />
) : ( ) : (
// Multiple conditions - show as rounded rectangle with dots // Multiple conditions - show as rounded rectangle
<rect <rect
x={(fromLoc.x + toLoc.x) / 2 + 20 - 15} x={(fromLoc.x + toLoc.x) / 2 + 30 - 16}
y={(fromLoc.y + toLoc.y) / 2 + 8} y={(fromLoc.y + toLoc.y) / 2 + 16}
width="30" width="32"
height="24" height="28"
rx="12" rx="14"
fill="hsl(var(--background))" fill="hsl(var(--background))"
stroke={edge.conditions.some(c => c.includes('NOBODY')) ? "#ef4444" : "hsl(var(--border))"} stroke={edge.conditions.some(c => c.includes('NOBODY')) ? "#ef4444" : "hsl(var(--border))"}
strokeWidth="2" strokeWidth="2"
/> />
)} )}
<text <text
x={(fromLoc.x + toLoc.x) / 2 + 20} x={(fromLoc.x + toLoc.x) / 2 + 30}
y={(fromLoc.y + toLoc.y) / 2 + 25} y={(fromLoc.y + toLoc.y) / 2 + 36}
fontSize="14" fontSize="16"
textAnchor="middle" textAnchor="middle"
className="pointer-events-none" className="pointer-events-none"
> >
@ -405,9 +416,9 @@ const DogsBunnyPuzzle: React.FC<DogsBunnyPuzzleProps> = ({
</text> </text>
{edge.conditions.length > 1 && ( {edge.conditions.length > 1 && (
<text <text
x={(fromLoc.x + toLoc.x) / 2 + 32} x={(fromLoc.x + toLoc.x) / 2 + 42}
y={(fromLoc.y + toLoc.y) / 2 + 16} y={(fromLoc.y + toLoc.y) / 2 + 22}
fontSize="8" fontSize="10"
textAnchor="middle" textAnchor="middle"
className="pointer-events-none fill-muted-foreground" className="pointer-events-none fill-muted-foreground"
> >
@ -416,6 +427,7 @@ const DogsBunnyPuzzle: React.FC<DogsBunnyPuzzleProps> = ({
)} )}
<title>{edge.conditions.map(formatCondition).join(' AND ')}</title> <title>{edge.conditions.map(formatCondition).join(' AND ')}</title>
</g> </g>
)}
</g> </g>
); );
})} })}
@ -429,7 +441,7 @@ const DogsBunnyPuzzle: React.FC<DogsBunnyPuzzleProps> = ({
return ( return (
<div <div
key={location.id} key={location.id}
className={`absolute w-10 h-10 rounded-full border-2 flex items-center justify-center cursor-pointer transition-all ${ className={`absolute w-12 h-12 rounded-full border-2 flex items-center justify-center cursor-pointer transition-all ${
isValidMove isValidMove
? 'border-primary bg-primary/20 scale-110' ? 'border-primary bg-primary/20 scale-110'
: 'border-border bg-background hover:bg-muted' : 'border-border bg-background hover:bg-muted'
@ -440,21 +452,21 @@ const DogsBunnyPuzzle: React.FC<DogsBunnyPuzzleProps> = ({
}} }}
onClick={() => isValidMove && handleMove(location.id)} onClick={() => isValidMove && handleMove(location.id)}
> >
<span className="text-lg"> <span className="text-xl">
{getLocationIcon(location.type)} {getLocationIcon(location.type)}
</span> </span>
{/* Characters at this location */} {/* Characters at this location */}
{charactersHere.map((char, index) => ( {charactersHere.map((char, index) => (
<div <div
key={char.id} key={char.id}
className={`absolute w-6 h-6 rounded-full border flex items-center justify-center cursor-pointer transition-all ${ className={`absolute w-7 h-7 rounded-full border flex items-center justify-center cursor-pointer transition-all ${
selectedCharacter === char.id selectedCharacter === char.id
? 'border-primary bg-primary text-primary-foreground scale-110' ? 'border-primary bg-primary text-primary-foreground scale-110'
: 'border-border bg-background hover:bg-muted' : 'border-border bg-background hover:bg-muted'
}`} }`}
style={{ style={{
right: -8 - (index * 12), right: -9 - (index * 14),
top: -8 - (index * 12), top: -9 - (index * 14),
}} }}
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
@ -470,10 +482,10 @@ const DogsBunnyPuzzle: React.FC<DogsBunnyPuzzleProps> = ({
); );
})} })}
</div> </div>
</div>
{/* Instructions & Controls */} {/* Instructions & Controls - Below the graph */}
<div className="w-full lg:w-80 space-y-4"> <div className="flex flex-col lg:flex-row gap-6">
<div className="flex-1 space-y-4">
<div className="space-y-2"> <div className="space-y-2">
<h3 className="font-semibold">How to Play:</h3> <h3 className="font-semibold">How to Play:</h3>
<ul className="text-sm text-muted-foreground space-y-1"> <ul className="text-sm text-muted-foreground space-y-1">
@ -498,9 +510,9 @@ const DogsBunnyPuzzle: React.FC<DogsBunnyPuzzleProps> = ({
</div> </div>
</div> </div>
<Separator /> </div>
<div className="space-y-2"> <div className="lg:w-80 space-y-2">
<Button onClick={resetPuzzle} variant="outline" className="w-full"> <Button onClick={resetPuzzle} variant="outline" className="w-full">
<RotateCcw className="h-4 w-4 mr-2" /> <RotateCcw className="h-4 w-4 mr-2" />
Reset Reset
@ -519,7 +531,6 @@ const DogsBunnyPuzzle: React.FC<DogsBunnyPuzzleProps> = ({
</Button> </Button>
</div> </div>
</div> </div>
</div>
</CardContent> </CardContent>
</Card> </Card>
</div> </div>