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 [boardSize, setBoardSize] = useState(8);
|
||||
const [dominoes, setDominoes] = useState<Domino[]>([]);
|
||||
const [selectedDomino, setSelectedDomino] = useState<number | null>(null);
|
||||
const [showInstructions, setShowInstructions] = useState(false);
|
||||
|
|
@ -24,24 +25,24 @@ const DominoRetilingPuzzle: React.FC<DominoRetilingPuzzleProps> = ({ showSocialS
|
|||
const [showHint, setShowHint] = useState(false);
|
||||
const [hoverPlacement, setHoverPlacement] = useState<{ row: number; col: number; isHorizontal: boolean } | null>(null);
|
||||
|
||||
const BOARD_ROWS = 8;
|
||||
const BOARD_COLS = 9; // 8 + 1 extra square on top row
|
||||
const BOARD_ROWS = boardSize;
|
||||
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 newDominoes: Domino[] = [];
|
||||
const occupied = new Set<string>();
|
||||
let dominoId = 0;
|
||||
|
||||
// Fill the 8x8 board with random dominoes
|
||||
for (let row = 0; row < 8; row++) {
|
||||
for (let col = 0; col < 8; col++) {
|
||||
// Fill the n×n board with random dominoes
|
||||
for (let row = 0; row < boardSize; row++) {
|
||||
for (let col = 0; col < boardSize; col++) {
|
||||
const key = `${row},${col}`;
|
||||
if (occupied.has(key)) continue;
|
||||
|
||||
// Randomly choose horizontal or vertical
|
||||
const canHorizontal = col + 1 < 8 && !occupied.has(`${row},${col + 1}`);
|
||||
const canVertical = row + 1 < 8 && !occupied.has(`${row + 1},${col}`);
|
||||
const canHorizontal = col + 1 < boardSize && !occupied.has(`${row},${col + 1}`);
|
||||
const canVertical = row + 1 < boardSize && !occupied.has(`${row + 1},${col}`);
|
||||
|
||||
if (!canHorizontal && !canVertical) continue;
|
||||
|
||||
|
|
@ -72,7 +73,7 @@ const DominoRetilingPuzzle: React.FC<DominoRetilingPuzzleProps> = ({ showSocialS
|
|||
setSelectedDomino(null);
|
||||
setMoveCount(0);
|
||||
setHoverPlacement(null);
|
||||
}, []);
|
||||
}, [boardSize]);
|
||||
|
||||
useEffect(() => {
|
||||
generateRandomTiling();
|
||||
|
|
@ -94,11 +95,11 @@ const DominoRetilingPuzzle: React.FC<DominoRetilingPuzzleProps> = ({ showSocialS
|
|||
}, [dominoes]);
|
||||
|
||||
// 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) 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
|
||||
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';
|
||||
};
|
||||
|
||||
const cellSize = Math.min(50, 400 / boardSize);
|
||||
|
||||
// Render the board
|
||||
const renderBoard = () => {
|
||||
const cells: JSX.Element[] = [];
|
||||
const occupied = getOccupiedCells(selectedDomino ?? undefined);
|
||||
|
||||
// Render cells
|
||||
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++) {
|
||||
const isExtraSquare = row === 0 && col === 8;
|
||||
const isExtraSquare = row === 0 && col === boardSize;
|
||||
|
||||
const cellStyle: React.CSSProperties = {
|
||||
position: 'absolute',
|
||||
left: col * 50,
|
||||
top: row * 50,
|
||||
width: 50,
|
||||
height: 50,
|
||||
left: col * cellSize,
|
||||
top: row * cellSize,
|
||||
width: cellSize,
|
||||
height: cellSize,
|
||||
};
|
||||
|
||||
cells.push(
|
||||
<div
|
||||
key={`cell-${row}-${col}`}
|
||||
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' : ''}`}
|
||||
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)}
|
||||
onMouseLeave={() => setHoverPlacement(null)}
|
||||
onClick={() => {
|
||||
|
|
@ -209,23 +218,23 @@ const DominoRetilingPuzzle: React.FC<DominoRetilingPuzzleProps> = ({ showSocialS
|
|||
dominoes.forEach(d => {
|
||||
if (d.id === selectedDomino) return; // Hide selected domino
|
||||
|
||||
const width = d.isHorizontal ? 96 : 46;
|
||||
const height = d.isHorizontal ? 46 : 96;
|
||||
const width = d.isHorizontal ? cellSize * 2 - 4 : cellSize - 4;
|
||||
const height = d.isHorizontal ? cellSize - 4 : cellSize * 2 - 4;
|
||||
|
||||
cells.push(
|
||||
<div
|
||||
key={`domino-${d.id}`}
|
||||
className={`absolute rounded border-2 cursor-pointer transition-all z-20 ${getDominoColor(d.isHorizontal)} hover:brightness-110`}
|
||||
style={{
|
||||
left: d.col * 50 + 2,
|
||||
top: d.row * 50 + 2,
|
||||
left: d.col * cellSize + 2,
|
||||
top: d.row * cellSize + 2,
|
||||
width,
|
||||
height,
|
||||
}}
|
||||
onClick={() => handleDominoClick(d.id)}
|
||||
>
|
||||
<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>
|
||||
);
|
||||
|
|
@ -233,23 +242,23 @@ const DominoRetilingPuzzle: React.FC<DominoRetilingPuzzleProps> = ({ showSocialS
|
|||
|
||||
// Render hover preview domino
|
||||
if (hoverPlacement && selectedDomino !== null) {
|
||||
const width = hoverPlacement.isHorizontal ? 96 : 46;
|
||||
const height = hoverPlacement.isHorizontal ? 46 : 96;
|
||||
const width = hoverPlacement.isHorizontal ? cellSize * 2 - 4 : cellSize - 4;
|
||||
const height = hoverPlacement.isHorizontal ? cellSize - 4 : cellSize * 2 - 4;
|
||||
|
||||
cells.push(
|
||||
<div
|
||||
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"
|
||||
style={{
|
||||
left: hoverPlacement.col * 50 + 2,
|
||||
top: hoverPlacement.row * 50 + 2,
|
||||
left: hoverPlacement.col * cellSize + 2,
|
||||
top: hoverPlacement.row * cellSize + 2,
|
||||
width,
|
||||
height,
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
);
|
||||
|
|
@ -261,6 +270,8 @@ const DominoRetilingPuzzle: React.FC<DominoRetilingPuzzleProps> = ({ showSocialS
|
|||
const horizontalCount = dominoes.filter(d => d.isHorizontal).length;
|
||||
const verticalCount = dominoes.filter(d => !d.isHorizontal).length;
|
||||
|
||||
const numDominoes = (boardSize * boardSize) / 2;
|
||||
|
||||
return (
|
||||
<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">
|
||||
|
|
@ -275,11 +286,23 @@ const DominoRetilingPuzzle: React.FC<DominoRetilingPuzzleProps> = ({ showSocialS
|
|||
<CardContent>
|
||||
<div className="text-center space-y-4">
|
||||
<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.
|
||||
</p>
|
||||
|
||||
<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">
|
||||
<Shuffle className="w-4 h-4 mr-2" />
|
||||
New Puzzle
|
||||
|
|
@ -339,11 +362,11 @@ const DominoRetilingPuzzle: React.FC<DominoRetilingPuzzleProps> = ({ showSocialS
|
|||
<CardContent className="pt-0">
|
||||
<div className="space-y-4">
|
||||
<ul className="list-disc list-inside space-y-2 text-sm">
|
||||
<li>Click on a domino to select it (turns yellow)</li>
|
||||
<li>Valid placement positions will appear as dashed outlines</li>
|
||||
<li>Click on a valid position to move the domino there</li>
|
||||
<li>Click on a domino to pick it up (it disappears from the board)</li>
|
||||
<li>Hover over empty cells to see where you can place it</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>Goal: Make all 32 dominoes horizontal</li>
|
||||
<li>Goal: Make all {numDominoes} dominoes horizontal</li>
|
||||
</ul>
|
||||
|
||||
<div className="flex items-center gap-4 text-sm text-muted-foreground">
|
||||
|
|
@ -372,8 +395,8 @@ const DominoRetilingPuzzle: React.FC<DominoRetilingPuzzleProps> = ({ showSocialS
|
|||
<div
|
||||
className="relative border-2 border-foreground/30 bg-background"
|
||||
style={{
|
||||
width: BOARD_COLS * 50,
|
||||
height: BOARD_ROWS * 50,
|
||||
width: BOARD_COLS * cellSize,
|
||||
height: BOARD_ROWS * cellSize,
|
||||
}}
|
||||
>
|
||||
{renderBoard()}
|
||||
|
|
@ -381,7 +404,7 @@ const DominoRetilingPuzzle: React.FC<DominoRetilingPuzzleProps> = ({ showSocialS
|
|||
</div>
|
||||
{selectedDomino !== null && (
|
||||
<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>
|
||||
)}
|
||||
</CardContent>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue