import { useState, useCallback } from "react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { RefreshCw, RotateCcw, ExternalLink, ArrowLeft } from "lucide-react"; import { toast } from "sonner"; import SocialShare from "@/components/SocialShare"; import { useSearchParams, useNavigate } from "react-router-dom"; type Piece = 'white' | 'black' | null; type Position = { row: number; col: number; }; interface KnightsPuzzleProps { showSocialShare?: boolean; } const KnightsPuzzle = ({ showSocialShare = false }: KnightsPuzzleProps) => { const [searchParams] = useSearchParams(); const navigate = useNavigate(); const isFromPuzzles = searchParams.get('from') === 'puzzles'; // Initial state: white knights at top corners, black knights at bottom corners const initialBoard: Piece[][] = [['white', null, 'white'], [null, null, null], ['black', null, 'black']]; // Target state: black knights at top corners, white knights at bottom corners const targetBoard: Piece[][] = [['black', null, 'black'], [null, null, null], ['white', null, 'white']]; const [board, setBoard] = useState(initialBoard); const [selectedSquare, setSelectedSquare] = useState(null); const [moveCount, setMoveCount] = useState(0); const [isComplete, setIsComplete] = useState(false); const [moveHistory, setMoveHistory] = useState([]); // Knight move offsets const knightMoves = [[-2, -1], [-2, 1], [-1, -2], [-1, 2], [1, -2], [1, 2], [2, -1], [2, 1]]; const isValidPosition = (row: number, col: number): boolean => { return row >= 0 && row < 3 && col >= 0 && col < 3; }; const getValidMoves = (fromRow: number, fromCol: number): Position[] => { const validMoves: Position[] = []; knightMoves.forEach(([dRow, dCol]) => { const newRow = fromRow + dRow; const newCol = fromCol + dCol; if (isValidPosition(newRow, newCol) && board[newRow][newCol] === null) { validMoves.push({ row: newRow, col: newCol }); } }); return validMoves; }; const isValidMove = (from: Position, to: Position): boolean => { const rowDiff = Math.abs(from.row - to.row); const colDiff = Math.abs(from.col - to.col); return rowDiff === 2 && colDiff === 1 || rowDiff === 1 && colDiff === 2; }; const checkWinCondition = (currentBoard: Piece[][]): boolean => { for (let row = 0; row < 3; row++) { for (let col = 0; col < 3; col++) { if (currentBoard[row][col] !== targetBoard[row][col]) { return false; } } } return true; }; const handleSquareClick = (row: number, col: number) => { const piece = board[row][col]; if (selectedSquare) { const from = selectedSquare; const to = { row, col }; // If clicking on the same square, deselect if (from.row === to.row && from.col === to.col) { setSelectedSquare(null); return; } // If clicking on an empty square and it's a valid knight move if (piece === null && isValidMove(from, to)) { const newBoard = board.map(r => [...r]); const movingPiece = newBoard[from.row][from.col]; newBoard[from.row][from.col] = null; newBoard[to.row][to.col] = movingPiece; setBoard(newBoard); setMoveCount(moveCount + 1); setMoveHistory([...moveHistory, [from, to]]); setSelectedSquare(null); // Check win condition if (checkWinCondition(newBoard)) { setIsComplete(true); toast.success(`Puzzle solved in ${moveCount + 1} moves!`); } } else if (piece !== null) { // Select a different piece setSelectedSquare({ row, col }); } else { setSelectedSquare(null); } } else if (piece !== null) { // Select a piece setSelectedSquare({ row, col }); } }; const resetPuzzle = () => { setBoard(initialBoard); setSelectedSquare(null); setMoveCount(0); setIsComplete(false); setMoveHistory([]); }; const undoLastMove = () => { if (moveHistory.length === 0) return; const lastMove = moveHistory[moveHistory.length - 1]; const [from, to] = lastMove; const newBoard = board.map(r => [...r]); const movingPiece = newBoard[to.row][to.col]; newBoard[to.row][to.col] = null; newBoard[from.row][from.col] = movingPiece; setBoard(newBoard); setMoveCount(moveCount - 1); setMoveHistory(moveHistory.slice(0, -1)); setSelectedSquare(null); setIsComplete(false); }; const getSquareClass = (row: number, col: number): string => { const baseClass = "w-16 h-16 border-2 border-outline flex items-center justify-center cursor-pointer transition-all duration-200"; const isSelected = selectedSquare?.row === row && selectedSquare?.col === col; const validMoves = selectedSquare ? getValidMoves(selectedSquare.row, selectedSquare.col) : []; const isValidMoveTarget = validMoves.some(move => move.row === row && move.col === col); let bgClass = (row + col) % 2 === 0 ? "bg-surface" : "bg-surface-variant"; if (isSelected) { bgClass = "bg-accent"; } else if (isValidMoveTarget) { bgClass = "bg-accent/30 ring-2 ring-accent"; } return `${baseClass} ${bgClass}`; }; const renderPiece = (piece: Piece) => { if (piece === 'white') { return ; } else if (piece === 'black') { return ; } return null; }; return
{/* Back to Puzzles button - only show when coming from puzzles */} {isFromPuzzles &&
} {/* Green: Title and Description - Centered */}

Knights Exchange Puzzle

Can the black and white knights swap places using legal chess moves? Move knights by clicking on them, then clicking on a valid destination.

{/* Blue and Orange: Moves (left) and Status (right) on same line */}
Moves: {moveCount}
{isComplete ? "🎉 Puzzle Solved!" : "🎯 In Progress"}
{/* Interactive and Goal State side by side */}
{/* Left: Interactive game */}
{board.map((row, rowIndex) => row.map((piece, colIndex) =>
handleSquareClick(rowIndex, colIndex)}> {renderPiece(piece)}
))}
{/* Controls */}
{/* Right: Goal state */}
{targetBoard.map((row, rowIndex) => row.map((piece, colIndex) =>
{renderPiece(piece)}
))}
{/* Goal label */}

Goal State

{/* Purple: Initial and Target positions side by side */}
{/* Red: Game rules */}
  • Click on a knight to select it, valid moves will be highlighted. Click on a highlighted square to move the knight to said square (knights move in an L-shape, like in chess).
  • Goal: Swap white and black knights
{/* Attribution */}

Puzzle source:{" "} @neuwirthe.bsky.social

{/* Social Share Section */} {showSocialShare && }
; }; export default KnightsPuzzle;