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']; 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++) {
const numConditions = diff === 'easy' ? 1 : diff === 'medium' ? 2 : 3; // Decide if this edge should be free (no conditions)
const conditions: string[] = []; const shouldBeFree = edgeCount < totalPossibleEdges * freeEdgePercentage && rng() < 0.5;
for (let k = 0; k < numConditions; k++) { let conditions: string[] = [];
const conditionType = locationTypes[Math.floor(rng() * locationTypes.length)];
const prefix = conditionPrefixes[Math.floor(rng() * conditionPrefixes.length)]; if (!shouldBeFree) {
conditions.push(`${prefix} ${conditionType.toUpperCase()}`); 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({ edges.push({
@ -98,6 +108,8 @@ const DogsBunnyPuzzle: React.FC<DogsBunnyPuzzleProps> = ({
to: locations[i].id, to: locations[i].id,
conditions: conditions conditions: conditions
}); });
edgeCount += 2;
} }
} }
@ -339,14 +351,12 @@ 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
<div className="flex-1"> id="puzzle-area"
<div className="relative bg-muted/10 rounded-lg p-6 min-h-[700px] w-full mb-6"
id="puzzle-area" style={{ height: '700px' }}
className="relative bg-muted/10 rounded-lg p-4 min-h-[600px] w-full" >
style={{ height: '600px' }}
>
{/* 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">
{puzzleState.edges.map((edge, index) => { {puzzleState.edges.map((edge, index) => {
@ -369,53 +379,55 @@ 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 */}
<g> {edge.conditions.length > 0 && (
{edge.conditions.length === 1 ? ( <g>
// Single condition {edge.conditions.length === 1 ? (
<circle // Single condition
cx={(fromLoc.x + toLoc.x) / 2 + 20} <circle
cy={(fromLoc.y + toLoc.y) / 2 + 20} cx={(fromLoc.x + toLoc.x) / 2 + 30}
r="12" cy={(fromLoc.y + toLoc.y) / 2 + 30}
fill="hsl(var(--background))" r="14"
stroke={edge.conditions[0].includes('NOBODY') ? "#ef4444" : "hsl(var(--border))"} fill="hsl(var(--background))"
strokeWidth="2" stroke={edge.conditions[0].includes('NOBODY') ? "#ef4444" : "hsl(var(--border))"}
/> strokeWidth="2"
) : ( />
// Multiple conditions - show as rounded rectangle with dots ) : (
<rect // Multiple conditions - show as rounded rectangle
x={(fromLoc.x + toLoc.x) / 2 + 20 - 15} <rect
y={(fromLoc.y + toLoc.y) / 2 + 8} x={(fromLoc.x + toLoc.x) / 2 + 30 - 16}
width="30" y={(fromLoc.y + toLoc.y) / 2 + 16}
height="24" width="32"
rx="12" height="28"
fill="hsl(var(--background))" rx="14"
stroke={edge.conditions.some(c => c.includes('NOBODY')) ? "#ef4444" : "hsl(var(--border))"} fill="hsl(var(--background))"
strokeWidth="2" 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 && (
<text <text
x={(fromLoc.x + toLoc.x) / 2 + 32} x={(fromLoc.x + toLoc.x) / 2 + 30}
y={(fromLoc.y + toLoc.y) / 2 + 16} y={(fromLoc.y + toLoc.y) / 2 + 36}
fontSize="8" fontSize="16"
textAnchor="middle" textAnchor="middle"
className="pointer-events-none fill-muted-foreground" className="pointer-events-none"
> >
+{edge.conditions.length - 1} {getConditionIcon(edge.conditions[0])}
</text> </text>
)} {edge.conditions.length > 1 && (
<title>{edge.conditions.map(formatCondition).join(' AND ')}</title> <text
</g> 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> </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();
@ -469,11 +481,11 @@ const DogsBunnyPuzzle: React.FC<DogsBunnyPuzzleProps> = ({
</div> </div>
); );
})} })}
</div> </div>
</div>
{/* Instructions & Controls - Below the graph */}
{/* Instructions & Controls */} <div className="flex flex-col lg:flex-row gap-6">
<div className="w-full lg:w-80 space-y-4"> <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,26 +510,25 @@ 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
</Button> </Button>
<Button onClick={generateNewPuzzle} variant="outline" className="w-full"> <Button onClick={generateNewPuzzle} variant="outline" className="w-full">
<Shuffle className="h-4 w-4 mr-2" /> <Shuffle className="h-4 w-4 mr-2" />
New Puzzle New Puzzle
</Button> </Button>
<Button onClick={sharePuzzle} variant="outline" className="w-full"> <Button onClick={sharePuzzle} variant="outline" className="w-full">
<Share2 className="h-4 w-4 mr-2" /> <Share2 className="h-4 w-4 mr-2" />
Share Share
</Button> </Button>
<Button onClick={screenshotPuzzle} variant="outline" className="w-full"> <Button onClick={screenshotPuzzle} variant="outline" className="w-full">
<Download className="h-4 w-4 mr-2" /> <Download className="h-4 w-4 mr-2" />
Screenshot Screenshot
</Button> </Button>
</div>
</div> </div>
</div> </div>
</CardContent> </CardContent>