A React component for solving crossword puzzles, supporting the iPuz format.
- Interactive crossword grid with keyboard and mouse support
- Mobile-friendly with virtual keyboard
- Smart navigation that automatically moves to the next empty cell
- Support for checking answers and revealing solutions
- Timer functionality
- Responsive design that works on all devices
- Support for iPuz format puzzles
The crossword solver features intelligent navigation that helps you move through the puzzle efficiently:
-
Filling Empty Cells:
- When you fill in the last empty cell of an answer, the cursor automatically advances to the next clue
- If you fill in a cell but there are still other empty cells in the answer, the cursor moves to the next cell in the current answer
-
Replacing Letters:
- When you replace a letter in the middle of an answer, you stay in the current answer
- If you replace a letter in the last cell and the answer becomes complete, the cursor advances to the next clue
-
End of Answer Behavior:
- If you reach the end of an incomplete answer, the cursor cycles back to the first empty cell in that answer
- If you reach the end of a complete answer, the cursor advances to the next clue
-
Orientation Changes:
- When you complete the last answer in one orientation (across/down), the cursor automatically switches to the other orientation
- The cursor always moves to the first empty cell of the next answer
This navigation system ensures a smooth solving experience by automatically guiding you to the next cell that needs attention.
npm install react-xword
import React from 'react';
import CrosswordSolver from 'react-xword';
import { IPuzPuzzle } from 'react-xword/types';
const puzzle: IPuzPuzzle = {
version: "http://ipuz.org/v1",
kind: ["http://ipuz.org/crossword#1"],
dimensions: {
width: 5,
height: 5
},
puzzle: [
[{ cell: 1 }, { cell: 2 }, { cell: 3 }, { cell: 4 }, { cell: 5 }],
[{ cell: 6 }, null, null, null, { cell: 7 }],
[{ cell: 8 }, null, null, null, { cell: 9 }],
[{ cell: 10 }, null, null, null, { cell: 11 }],
[{ cell: 12 }, { cell: 13 }, { cell: 14 }, { cell: 15 }, { cell: 16 }]
],
clues: {
Across: [
{ number: 1, clue: "First across clue" },
{ number: 6, clue: "Second across clue" },
{ number: 8, clue: "Third across clue" },
{ number: 10, clue: "Fourth across clue" },
{ number: 12, clue: "Fifth across clue" }
],
Down: [
{ number: 1, clue: "First down clue" },
{ number: 2, clue: "Second down clue" },
{ number: 3, clue: "Third down clue" },
{ number: 4, clue: "Fourth down clue" },
{ number: 5, clue: "Fifth down clue" }
]
},
solution: {
grid: [
["H", "E", "L", "L", "O"],
["W", "", "", "", "O"],
["R", "", "", "", "R"],
["L", "", "", "", "L"],
["D", "W", "O", "R", "D"]
]
},
metadata: {
title: "Example Crossword",
author: "Crossword Creator",
date: "2024-03-20"
}
};
const App: React.FC = () => {
return <CrosswordSolver ipuzData={puzzle} />;
};
export default App;
The component accepts puzzles in the iPuz format. The IPuzPuzzle
type is defined as follows:
interface IPuzDimensions {
width: number;
height: number;
}
interface IPuzCell {
cell?: number;
style?: {
shapebg?: string;
};
value?: string;
}
type IPuzGrid = (IPuzCell | string | null)[][];
interface IPuzClue {
number: number;
clue: string;
answer?: string;
format?: string;
}
interface IPuzClues {
Across: IPuzClue[];
Down: IPuzClue[];
}
interface IPuzMetadata {
title?: string;
author?: string;
editor?: string;
copyright?: string;
publisher?: string;
date?: string;
notes?: string;
}
interface IPuzPuzzle {
version: string;
kind: string[];
dimensions: IPuzDimensions;
puzzle: IPuzGrid;
clues: IPuzClues;
solution?: string[][];
metadata?: IPuzMetadata;
}
Prop | Type | Description |
---|---|---|
ipuzData |
IPuzPuzzle |
The puzzle data in IPuz format. |
onComplete |
(completionTime: number) => void |
Called when the puzzle is solved and the success modal is about to be shown. Receives the completion time in seconds. |
leftNavElements |
React.ReactNode |
Elements to display in the left side of the actions bar. |
onStart |
() => void |
Called when the user starts the puzzle (dismisses the splash modal). |
isComplete |
boolean |
If true, the puzzle is shown as completed and locked. |
- onComplete: Fires when the puzzle is solved and the success modal is about to be shown. Receives the completion time in seconds.
<CrosswordSolver ipuzData={puzzle} />
<CrosswordSolver
ipuzData={puzzle}
onComplete={(time) => console.log(`Completed in ${time} seconds!`)}
/>
<CrosswordSolver
ipuzData={puzzle}
leftNavElements={
<div className="custom-nav">
<button onClick={() => console.log('Custom action')}>
Custom Button
</button>
<span>Custom Text</span>
</div>
}
/>
<CrosswordSolver
ipuzData={puzzle}
onStart={() => console.log('Puzzle started!')}
/>
<CrosswordSolver
ipuzData={puzzle}
isComplete={true}
/>
The component uses CSS modules for styling. You can override the default styles by importing the CSS file and modifying the classes:
.solver-container {
/* Your custom styles */
}
.solver-grid {
/* Your custom styles */
}
/* ... other classes ... */
MIT