Changes
This commit is contained in:
parent
5671af101c
commit
3b26796cd0
1 changed files with 62 additions and 39 deletions
|
|
@ -17,6 +17,7 @@ interface DominoRetilingPuzzleProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const DominoRetilingPuzzle: React.FC<DominoRetilingPuzzleProps> = ({ showSocialShare = true }) => {
|
const DominoRetilingPuzzle: React.FC<DominoRetilingPuzzleProps> = ({ showSocialShare = true }) => {
|
||||||
|
const [boardSize, setBoardSize] = useState(8);
|
||||||
const [dominoes, setDominoes] = useState<Domino[]>([]);
|
const [dominoes, setDominoes] = useState<Domino[]>([]);
|
||||||
const [selectedDomino, setSelectedDomino] = useState<number | null>(null);
|
const [selectedDomino, setSelectedDomino] = useState<number | null>(null);
|
||||||
const [showInstructions, setShowInstructions] = useState(false);
|
const [showInstructions, setShowInstructions] = useState(false);
|
||||||
|
|
@ -24,24 +25,24 @@ const DominoRetilingPuzzle: React.FC<DominoRetilingPuzzleProps> = ({ showSocialS
|
||||||
const [showHint, setShowHint] = useState(false);
|
const [showHint, setShowHint] = useState(false);
|
||||||
const [hoverPlacement, setHoverPlacement] = useState<{ row: number; col: number; isHorizontal: boolean } | null>(null);
|
const [hoverPlacement, setHoverPlacement] = useState<{ row: number; col: number; isHorizontal: boolean } | null>(null);
|
||||||
|
|
||||||
const BOARD_ROWS = 8;
|
const BOARD_ROWS = boardSize;
|
||||||
const BOARD_COLS = 9; // 8 + 1 extra square on top row
|
const BOARD_COLS = boardSize + 1; // n + 1 extra square on top row
|
||||||
|
|
||||||
// Generate a random valid tiling of the 8x8 board
|
// Generate a random valid tiling of the n×n board
|
||||||
const generateRandomTiling = useCallback(() => {
|
const generateRandomTiling = useCallback(() => {
|
||||||
const newDominoes: Domino[] = [];
|
const newDominoes: Domino[] = [];
|
||||||
const occupied = new Set<string>();
|
const occupied = new Set<string>();
|
||||||
let dominoId = 0;
|
let dominoId = 0;
|
||||||
|
|
||||||
// Fill the 8x8 board with random dominoes
|
// Fill the n×n board with random dominoes
|
||||||
for (let row = 0; row < 8; row++) {
|
for (let row = 0; row < boardSize; row++) {
|
||||||
for (let col = 0; col < 8; col++) {
|
for (let col = 0; col < boardSize; col++) {
|
||||||
const key = `${row},${col}`;
|
const key = `${row},${col}`;
|
||||||
if (occupied.has(key)) continue;
|
if (occupied.has(key)) continue;
|
||||||
|
|
||||||
// Randomly choose horizontal or vertical
|
// Randomly choose horizontal or vertical
|
||||||
const canHorizontal = col + 1 < 8 && !occupied.has(`${row},${col + 1}`);
|
const canHorizontal = col + 1 < boardSize && !occupied.has(`${row},${col + 1}`);
|
||||||
const canVertical = row + 1 < 8 && !occupied.has(`${row + 1},${col}`);
|
const canVertical = row + 1 < boardSize && !occupied.has(`${row + 1},${col}`);
|
||||||
|
|
||||||
if (!canHorizontal && !canVertical) continue;
|
if (!canHorizontal && !canVertical) continue;
|
||||||
|
|
||||||
|
|
@ -72,7 +73,7 @@ const DominoRetilingPuzzle: React.FC<DominoRetilingPuzzleProps> = ({ showSocialS
|
||||||
setSelectedDomino(null);
|
setSelectedDomino(null);
|
||||||
setMoveCount(0);
|
setMoveCount(0);
|
||||||
setHoverPlacement(null);
|
setHoverPlacement(null);
|
||||||
}, []);
|
}, [boardSize]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
generateRandomTiling();
|
generateRandomTiling();
|
||||||
|
|
@ -94,11 +95,11 @@ const DominoRetilingPuzzle: React.FC<DominoRetilingPuzzleProps> = ({ showSocialS
|
||||||
}, [dominoes]);
|
}, [dominoes]);
|
||||||
|
|
||||||
// Check if a position is valid for the augmented board
|
// Check if a position is valid for the augmented board
|
||||||
const isValidCell = (row: number, col: number): boolean => {
|
const isValidCell = useCallback((row: number, col: number): boolean => {
|
||||||
if (row < 0 || row >= BOARD_ROWS) return false;
|
if (row < 0 || row >= BOARD_ROWS) return false;
|
||||||
if (row === 0) return col >= 0 && col < BOARD_COLS;
|
if (row === 0) return col >= 0 && col < BOARD_COLS;
|
||||||
return col >= 0 && col < 8;
|
return col >= 0 && col < boardSize;
|
||||||
};
|
}, [BOARD_ROWS, BOARD_COLS, boardSize]);
|
||||||
|
|
||||||
// Check if we can place a domino at a position
|
// Check if we can place a domino at a position
|
||||||
const canPlaceDomino = useCallback((row: number, col: number, isHorizontal: boolean, excludeDominoId: number): boolean => {
|
const canPlaceDomino = useCallback((row: number, col: number, isHorizontal: boolean, excludeDominoId: number): boolean => {
|
||||||
|
|
@ -167,32 +168,40 @@ const DominoRetilingPuzzle: React.FC<DominoRetilingPuzzleProps> = ({ showSocialS
|
||||||
return 'bg-rose-400 border-rose-600';
|
return 'bg-rose-400 border-rose-600';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const cellSize = Math.min(50, 400 / boardSize);
|
||||||
|
|
||||||
// Render the board
|
// Render the board
|
||||||
const renderBoard = () => {
|
const renderBoard = () => {
|
||||||
const cells: JSX.Element[] = [];
|
const cells: JSX.Element[] = [];
|
||||||
const occupied = getOccupiedCells(selectedDomino ?? undefined);
|
|
||||||
|
|
||||||
// Render cells
|
// Render cells
|
||||||
for (let row = 0; row < BOARD_ROWS; row++) {
|
for (let row = 0; row < BOARD_ROWS; row++) {
|
||||||
const maxCol = row === 0 ? BOARD_COLS : 8;
|
const maxCol = row === 0 ? BOARD_COLS : boardSize;
|
||||||
for (let col = 0; col < maxCol; col++) {
|
for (let col = 0; col < maxCol; col++) {
|
||||||
const isExtraSquare = row === 0 && col === 8;
|
const isExtraSquare = row === 0 && col === boardSize;
|
||||||
|
|
||||||
const cellStyle: React.CSSProperties = {
|
const cellStyle: React.CSSProperties = {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
left: col * 50,
|
left: col * cellSize,
|
||||||
top: row * 50,
|
top: row * cellSize,
|
||||||
width: 50,
|
width: cellSize,
|
||||||
height: 50,
|
height: cellSize,
|
||||||
};
|
};
|
||||||
|
|
||||||
cells.push(
|
cells.push(
|
||||||
<div
|
<div
|
||||||
key={`cell-${row}-${col}`}
|
key={`cell-${row}-${col}`}
|
||||||
className={`border border-border/50 ${
|
className={`border border-border/50 ${
|
||||||
isExtraSquare ? 'bg-primary/20' : (row + col) % 2 === 0 ? 'bg-muted/30' : 'bg-muted/60'
|
isExtraSquare
|
||||||
|
? 'bg-blue-500/40'
|
||||||
|
: (row + col) % 2 === 0 ? 'bg-muted/30' : 'bg-muted/60'
|
||||||
} ${selectedDomino !== null ? 'cursor-pointer' : ''}`}
|
} ${selectedDomino !== null ? 'cursor-pointer' : ''}`}
|
||||||
style={cellStyle}
|
style={{
|
||||||
|
...cellStyle,
|
||||||
|
...(isExtraSquare ? {
|
||||||
|
backgroundImage: 'repeating-linear-gradient(45deg, transparent, transparent 4px, rgba(59, 130, 246, 0.3) 4px, rgba(59, 130, 246, 0.3) 8px)',
|
||||||
|
} : {})
|
||||||
|
}}
|
||||||
onMouseEnter={() => handleCellHover(row, col)}
|
onMouseEnter={() => handleCellHover(row, col)}
|
||||||
onMouseLeave={() => setHoverPlacement(null)}
|
onMouseLeave={() => setHoverPlacement(null)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
@ -209,23 +218,23 @@ const DominoRetilingPuzzle: React.FC<DominoRetilingPuzzleProps> = ({ showSocialS
|
||||||
dominoes.forEach(d => {
|
dominoes.forEach(d => {
|
||||||
if (d.id === selectedDomino) return; // Hide selected domino
|
if (d.id === selectedDomino) return; // Hide selected domino
|
||||||
|
|
||||||
const width = d.isHorizontal ? 96 : 46;
|
const width = d.isHorizontal ? cellSize * 2 - 4 : cellSize - 4;
|
||||||
const height = d.isHorizontal ? 46 : 96;
|
const height = d.isHorizontal ? cellSize - 4 : cellSize * 2 - 4;
|
||||||
|
|
||||||
cells.push(
|
cells.push(
|
||||||
<div
|
<div
|
||||||
key={`domino-${d.id}`}
|
key={`domino-${d.id}`}
|
||||||
className={`absolute rounded border-2 cursor-pointer transition-all z-20 ${getDominoColor(d.isHorizontal)} hover:brightness-110`}
|
className={`absolute rounded border-2 cursor-pointer transition-all z-20 ${getDominoColor(d.isHorizontal)} hover:brightness-110`}
|
||||||
style={{
|
style={{
|
||||||
left: d.col * 50 + 2,
|
left: d.col * cellSize + 2,
|
||||||
top: d.row * 50 + 2,
|
top: d.row * cellSize + 2,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
}}
|
}}
|
||||||
onClick={() => handleDominoClick(d.id)}
|
onClick={() => handleDominoClick(d.id)}
|
||||||
>
|
>
|
||||||
<div className="absolute inset-0 flex items-center justify-center">
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
<div className={`w-3 h-3 rounded-full ${d.isHorizontal ? 'bg-emerald-700' : 'bg-rose-700'}`} />
|
<div className={`w-2 h-2 rounded-full ${d.isHorizontal ? 'bg-emerald-700' : 'bg-rose-700'}`} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -233,23 +242,23 @@ const DominoRetilingPuzzle: React.FC<DominoRetilingPuzzleProps> = ({ showSocialS
|
||||||
|
|
||||||
// Render hover preview domino
|
// Render hover preview domino
|
||||||
if (hoverPlacement && selectedDomino !== null) {
|
if (hoverPlacement && selectedDomino !== null) {
|
||||||
const width = hoverPlacement.isHorizontal ? 96 : 46;
|
const width = hoverPlacement.isHorizontal ? cellSize * 2 - 4 : cellSize - 4;
|
||||||
const height = hoverPlacement.isHorizontal ? 46 : 96;
|
const height = hoverPlacement.isHorizontal ? cellSize - 4 : cellSize * 2 - 4;
|
||||||
|
|
||||||
cells.push(
|
cells.push(
|
||||||
<div
|
<div
|
||||||
key="hover-preview"
|
key="hover-preview"
|
||||||
className="absolute rounded border-2 cursor-pointer z-30 bg-amber-400 border-amber-600 opacity-80 hover:opacity-100 transition-opacity"
|
className="absolute rounded border-2 cursor-pointer z-30 bg-amber-400 border-amber-600 opacity-80 hover:opacity-100 transition-opacity"
|
||||||
style={{
|
style={{
|
||||||
left: hoverPlacement.col * 50 + 2,
|
left: hoverPlacement.col * cellSize + 2,
|
||||||
top: hoverPlacement.row * 50 + 2,
|
top: hoverPlacement.row * cellSize + 2,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
pointerEvents: 'none',
|
pointerEvents: 'none',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="absolute inset-0 flex items-center justify-center">
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
<div className="w-3 h-3 rounded-full bg-amber-700" />
|
<div className="w-2 h-2 rounded-full bg-amber-700" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -261,6 +270,8 @@ const DominoRetilingPuzzle: React.FC<DominoRetilingPuzzleProps> = ({ showSocialS
|
||||||
const horizontalCount = dominoes.filter(d => d.isHorizontal).length;
|
const horizontalCount = dominoes.filter(d => d.isHorizontal).length;
|
||||||
const verticalCount = dominoes.filter(d => !d.isHorizontal).length;
|
const verticalCount = dominoes.filter(d => !d.isHorizontal).length;
|
||||||
|
|
||||||
|
const numDominoes = (boardSize * boardSize) / 2;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-4xl mx-auto p-6 space-y-6">
|
<div className="max-w-4xl mx-auto p-6 space-y-6">
|
||||||
<Card className="bg-gradient-to-r from-primary/10 to-secondary/10 border-primary/20">
|
<Card className="bg-gradient-to-r from-primary/10 to-secondary/10 border-primary/20">
|
||||||
|
|
@ -275,11 +286,23 @@ const DominoRetilingPuzzle: React.FC<DominoRetilingPuzzleProps> = ({ showSocialS
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="text-center space-y-4">
|
<div className="text-center space-y-4">
|
||||||
<p className="text-lg text-muted-foreground">
|
<p className="text-lg text-muted-foreground">
|
||||||
An 8×8 board is tiled with 32 dominoes. An extra square is added to the top-right.
|
A {boardSize}×{boardSize} board is tiled with {numDominoes} dominoes. An extra square is added to the top-right.
|
||||||
Move dominoes one at a time to make all dominoes horizontal.
|
Move dominoes one at a time to make all dominoes horizontal.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-wrap justify-center gap-4 items-center">
|
<div className="flex flex-wrap justify-center gap-4 items-center">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<label className="text-sm font-medium">Board Size: {boardSize}×{boardSize}</label>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="2"
|
||||||
|
max="12"
|
||||||
|
step="2"
|
||||||
|
value={boardSize}
|
||||||
|
onChange={(e) => setBoardSize(parseInt(e.target.value))}
|
||||||
|
className="w-24 h-2 bg-muted rounded-lg appearance-none cursor-pointer"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<Button onClick={generateRandomTiling} variant="outline" size="sm">
|
<Button onClick={generateRandomTiling} variant="outline" size="sm">
|
||||||
<Shuffle className="w-4 h-4 mr-2" />
|
<Shuffle className="w-4 h-4 mr-2" />
|
||||||
New Puzzle
|
New Puzzle
|
||||||
|
|
@ -339,11 +362,11 @@ const DominoRetilingPuzzle: React.FC<DominoRetilingPuzzleProps> = ({ showSocialS
|
||||||
<CardContent className="pt-0">
|
<CardContent className="pt-0">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<ul className="list-disc list-inside space-y-2 text-sm">
|
<ul className="list-disc list-inside space-y-2 text-sm">
|
||||||
<li>Click on a domino to select it (turns yellow)</li>
|
<li>Click on a domino to pick it up (it disappears from the board)</li>
|
||||||
<li>Valid placement positions will appear as dashed outlines</li>
|
<li>Hover over empty cells to see where you can place it</li>
|
||||||
<li>Click on a valid position to move the domino there</li>
|
<li>Click to place the domino in the highlighted position</li>
|
||||||
<li>You can only move a domino if there are two adjacent empty squares to receive it</li>
|
<li>You can only move a domino if there are two adjacent empty squares to receive it</li>
|
||||||
<li>Goal: Make all 32 dominoes horizontal</li>
|
<li>Goal: Make all {numDominoes} dominoes horizontal</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div className="flex items-center gap-4 text-sm text-muted-foreground">
|
<div className="flex items-center gap-4 text-sm text-muted-foreground">
|
||||||
|
|
@ -372,8 +395,8 @@ const DominoRetilingPuzzle: React.FC<DominoRetilingPuzzleProps> = ({ showSocialS
|
||||||
<div
|
<div
|
||||||
className="relative border-2 border-foreground/30 bg-background"
|
className="relative border-2 border-foreground/30 bg-background"
|
||||||
style={{
|
style={{
|
||||||
width: BOARD_COLS * 50,
|
width: BOARD_COLS * cellSize,
|
||||||
height: BOARD_ROWS * 50,
|
height: BOARD_ROWS * cellSize,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{renderBoard()}
|
{renderBoard()}
|
||||||
|
|
@ -381,7 +404,7 @@ const DominoRetilingPuzzle: React.FC<DominoRetilingPuzzleProps> = ({ showSocialS
|
||||||
</div>
|
</div>
|
||||||
{selectedDomino !== null && (
|
{selectedDomino !== null && (
|
||||||
<p className="text-center text-sm text-muted-foreground mt-4">
|
<p className="text-center text-sm text-muted-foreground mt-4">
|
||||||
Domino selected. Click on a valid placement (dashed outline) to move it, or click the domino again to deselect.
|
Domino picked up. Hover over empty cells to preview placement, then click to place.
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue