This commit is contained in:
gpt-engineer-app[bot] 2025-12-08 09:37:58 +00:00
parent 5671af101c
commit 3b26796cd0

View file

@ -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>