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:
parent
9b07ffe9d9
commit
48c3f7a97a
1 changed files with 98 additions and 87 deletions
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue