Open In App

Create a Crossword Puzzle Game using React-Native

Last Updated : 23 Jul, 2025
Comments
Improve
Suggest changes
Like Article
Like
Report

A Crossword Puzzle is a traditional word power game. The user needs to guess words based on the hints provided. So here, we will create a basic crossword puzzle app in React Native.

The crossword puzzle app contains four buttons, namely Generate, Verify, Reset, and Solve. The Generate button will create a new crossword puzzle, the Verify button will check the correctness of the solved puzzle, the Reset button will empty all the fields entered in the puzzle, and the Solve button will solve the puzzle.

To give you a better idea of what we’re going to create, let’s watch a demo video:

Demo Video

Playground

Note: This Section is to interact with the app which you are going to build.

Prerequisites

Approach to create a Crossword Puzzle Game

Our code generates a random crossword puzzle. Here I have created our crossword puzzle with two levels. Each level has 4 words. These words need to be filled in the cells. Hints are given for each word. The user must guess the word and fill it into the cells based on the hint. I have used a list named crosswordData to store the words and hints. You can add more words to increase the levels in the game. Finally, the app provides good crossword puzzles to improve your vocabulary.

Step-by-Step Implementation

Step 1: Create a React Native Project

Now, create a project with the following command.

npx create-expo-app app-name --template

Note: Replace the app-name with your app name for example : react-native-demo-app

Next, you might be asked to choose a template. Select one based on your preference as shown in the image below. I am selecting the blank template because it will generate a minimal app that is as clean as an empty canvas in JavaScript.

It completes the project creation and displays a message: "Your Project is ready!" as shown in the image below.

Now go into your project folder, i.e., react-native-demo

cd app-name

Project Structure:

Step 2: Run  Application

Start the server by using the following command.

npx expo start

Then, the application will display a QR code.

  • For the Android users,
    • For the Android Emulator, press " a" as mentioned in the image below.
    • For the Physical Device, download the " Expo Go " app from the Play Store. Open the app, and you will see a button labeled " Scan QR Code. " Click that button and scan the QR code; it will automatically build the Android app on your device.
  • For iOS users, simply scan the QR code using the Camera app.
  • If you're using a web browser, it will provide a local host link that you can use as mentioned in the image below.

Step 3: Start Coding

Example: Add the following code in CrosswordGrid.js and App.js.

App.js
//App.js

import React from "react";
import { View, StyleSheet } from "react-native";
import CrosswordGrid from "./CrosswordGrid";

const App = () => {
	// levels can be added here in the crosswordData
	const crosswordData = [
		[
			{
				answer: "TIGER",
				hint: "The powerful predator roams the jungle",
				startx: 4,
				starty: 1,
				orientation: "down",
				position: 1,
			},
			{
				answer: "EAGLE",
				hint: "A majestic bird known for its keen eyesight",
				startx: 4,
				starty: 4,
				orientation: "across",
				position: 2,
			},
			{
				answer: "ITALIC",
				hint: "It's a style of typeface characterized by slanted letters",
				startx: 7,
				starty: 1,
				orientation: "down",
				position: 3,
			},
			{
				answer: "INFINITE",
				hint:"It describes something boundless or limitless in extent or quantity",
				startx: 1,
				starty: 2,
				orientation: "across",
				position: 4,
			},
		],
		[
			{
				answer: "QUIVER",
				hint: "To shake or tremble slightly, often with rapid movements",
				startx: 1,
				starty: 4,
				orientation: "across",
				position: 1,
			},
			{
				answer: "TWIRL",
				hint: "To spin or rotate quickly",
				startx: 3,
				starty: 2,
				orientation: "down",
				position: 2,
			},
			{
				answer: "GAZE",
				hint: "To look steadily and intently at something, 
						often implying concentration or contemplation",
				startx: 5,
				starty: 1,
				orientation: "down",
				position: 3,
			},
			{
				answer: "FLUTE",
				hint: "A musical instrument with a high-pitched sound",
				startx: 2,
				starty: 6,
				orientation: "across",
				position: 4,
			},
		],
	];

	return (
		<View style={styles.container}>
			<CrosswordGrid crosswordData={crosswordData} />
		</View>
	);
};

const styles = StyleSheet.create({
	container: {
		flex: 1,
		justifyContent: "center",
		alignItems: "center",
	},
});

export default App;
CrosswordGrid.js
//CrosswordGrid.js

import React, { useState, useEffect } from 'react';
import { View, TextInput, StyleSheet, Text, Button } from 'react-native';

let level = 0;

const generateInitialGrid = (crosswordData) => {
	const initialGrid = Array(7).fill(0).map(() => Array(8).fill('X'));
	crosswordData[level].forEach(({ answer, startx, starty, orientation }) => {
		let x = startx - 1;
		let y = starty - 1;

		for (let i = 0; i < answer.length; i++) {
			if (orientation === 'across') {
				initialGrid[y][x + i] = '';
			} else if (orientation === 'down') {
				initialGrid[y + i][x] = '';
			}
		}
	});
	return initialGrid;
};

const generateAnswerGrid = (crosswordData) => {
	const answerGrid = Array(7).fill(0).map(() => Array(8).fill('X'));
	crosswordData[level].forEach(({ answer, startx, starty, orientation }) => {
		let x = startx - 1;
		let y = starty - 1;

		for (let i = 0; i < answer.length; i++) {
			if (orientation === 'across') {
				answerGrid[y][x + i] = answer[i];
			} else if (orientation === 'down') {
				answerGrid[y + i][x] = answer[i];
			}
		}
	});
	return answerGrid;
};


const CrosswordGrid = ({ crosswordData }) => {
	const [grid, setGrid] = useState(generateInitialGrid(crosswordData));

	useEffect(() => {
		setGrid(generateInitialGrid(crosswordData));
	}, [crosswordData]);

	const handleInputChange = (row, col, text) => {
		const newGrid = [...grid];
		newGrid[row][col] = text.toUpperCase();
		setGrid(newGrid);
	};

	const handleGenerate = () => {
		level = (level + 1) % 2;
		setGrid(generateInitialGrid(crosswordData));
	};

	const handleVerify = () => {
		const answerGrid = generateAnswerGrid(crosswordData);
		const isCorrect = JSON.stringify(grid) === JSON.stringify(answerGrid);
		if (isCorrect) {
			alert('Congratulations! Your crossword is correct.');
		} else {
			alert('Incorrect. Please try again.');
		}
	};

	const handleReset = () => {
		setGrid(generateInitialGrid(crosswordData));
	};

	const handleSolve = () => {
		const answerGrid = generateAnswerGrid(crosswordData);
		setGrid(answerGrid);
	};

	const renderGrid = () => (
		<View>
			{grid.map((row, rowIndex) => (
				<View key={rowIndex} style={styles.row}>
					{row.map((cell, colIndex) => (
						<View key={colIndex} style={styles.cellContainer}>
							{crosswordData[level].map((entry) => {
								const { startx, starty, position } = entry;
								if (rowIndex + 1 === starty && colIndex + 1 === startx) {
									return (
										<Text key={`digit-${position}`} 
											style={styles.smallDigit}>
											{position}
										</Text>
									);
								}
								return null;
							})}
							<TextInput
								style={[styles.cell, 
								grid[rowIndex][colIndex] ==='X' ? styles.staticCell:null]}
								value={cell}
								editable={grid[rowIndex][colIndex] !== 'X'}
								onChangeText={(text) =>
									handleInputChange(rowIndex,colIndex, text)
								}
								maxLength={1}
							/>
						</View>
					))}
				</View>
			))}
		</View>
	);

	const renderQuestions = () => {
		const questions = { across: [], down: [] };

		crosswordData[level].forEach(({ hint, orientation, position }) => {
			const questionText = `${position}. ${hint}`;
			questions[orientation].push(
				<Text key={`question-${position}`} style={styles.questionText}>
					{questionText}
				</Text>
			);
		});

		return (
			<View>
				<View style={styles.headingContainer}>
					<Text style={styles.headingText}>Across</Text>
				</View>
				<View style={styles.questionsContainer}>
					{questions.across.map((question, index) => (
						<View key={`across-question-container-${index}`}>
							{question}
						</View>
					))}
				</View>
				<View style={styles.headingContainer}>
					<Text style={styles.headingText}>Down</Text>
				</View>
				<View style={styles.questionsContainer}>
					{questions.down.map((question, index) => (
						<View key={`down-question-container-${index}`}>
							{question}
						</View>
					))}
				</View>
			</View>
		);
	};


	return (
		<View style={styles.container}>
			{renderQuestions()}
			{renderGrid()}
			<View style={styles.buttonContainer}>
				<Button color={'#228B22'} 
						title="Generate"
						onPress={handleGenerate} 
						style={styles.button} />
				<View style={styles.gap} />
				<Button color={'#228B22'} 
						title="Verify"
						onPress={handleVerify} 
						style={styles.button} />
				<View style={styles.gap} />
				<Button color={'#228B22'} 
						title="Reset"
						onPress={handleReset} 
						style={styles.button} />
				<View style={styles.gap} />
				<Button color={'#228B22'} 
						title="Solve"
						onPress={handleSolve} 
						style={styles.button} />
			</View>
		</View>
	);
};

const styles = StyleSheet.create({
	container: {
		flex: 1,
		justifyContent: 'center',
		alignItems: 'center',
	},
	row: {
		flexDirection: 'row',
	},
	cellContainer: {
		position: 'relative',
	},
	cell: {
		borderWidth: 1,
		margin: 1,
		borderColor: '#228B22',
		width: 30,
		height: 30,
		textAlign: 'center',
	},
	staticCell: {
		borderColor: 'transparent',
		color: 'white',
	},
	smallDigit: {
		position: 'absolute',
		top: 2,
		left: 2,
		fontSize: 10,
		fontWeight: 'bold',
	},
	questionsContainer: {
		justifyContent: 'space-between',
		marginBottom: 10,
		padding: 10,
	},
	questionText: {
		fontSize: 16,
		fontStyle: 'italic',
	},
	headingContainer: {
		marginTop: 10,
		marginBottom: 5,
	},
	headingText: {
		fontSize: 18,
		fontWeight: 'bold',
		color: '#228B22',
		textAlign: 'center',
	},
	buttonContainer: {
		flexDirection: 'row',
		justifyContent: 'space-around',
		marginTop: 20,
		marginHorizontal: 10,
	},
	button: {
		flex: 1, // Ensure equal width for both buttons
	},
	gap: {
		width: 10, // Adjust the width as needed for the desired gap
	},
});

export default CrosswordGrid

Output:


Similar Reads