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