Building a React Drag-and-Drop Puzzle Game

JavaScriptJavaScriptBeginner
Practice Now

Introduction

In this project, we will create a drag-and-drop puzzle game using React. This is an excellent project for beginners to learn about React components, state management, and handling user interactions. By the end of this project, you will have a functional puzzle game.

👀 Preview

Preview

🎯 Tasks

In this project, you will learn:

  • How to set up a new React application
  • How to create the main puzzle game component
  • How to manage state and set up the puzzle image
  • How to display puzzle pieces on the screen
  • How to implement drag and drop functionality
  • How to incorporate the PuzzleGame component into the main application file
  • How to add CSS to style the puzzle

🏆 Achievements

After completing this project, you will be able to:

  • Set up a React application and create a component
  • Manage state and handle user interactions
  • Implement drag and drop functionality
  • Incorporate styling to make the puzzle visually appealing

Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL css(("`CSS`")) -.-> css/BasicConceptsGroup(["`Basic Concepts`"]) css(("`CSS`")) -.-> css/BasicStylingGroup(["`Basic Styling`"]) css(("`CSS`")) -.-> css/CoreLayoutGroup(["`Core Layout`"]) css(("`CSS`")) -.-> css/AdvancedLayoutGroup(["`Advanced Layout`"]) css(("`CSS`")) -.-> css/IntermediateStylingGroup(["`Intermediate Styling`"]) css(("`CSS`")) -.-> css/ResponsiveandAdaptiveDesignGroup(["`Responsive and Adaptive Design`"]) css(("`CSS`")) -.-> css/CSSPreprocessorsGroup(["`CSS Preprocessors`"]) css(("`CSS`")) -.-> css/CodingStandardsandBestPracticesGroup(["`Coding Standards and Best Practices`"]) javascript(("`JavaScript`")) -.-> javascript/BasicConceptsGroup(["`Basic Concepts`"]) javascript(("`JavaScript`")) -.-> javascript/AdvancedConceptsGroup(["`Advanced Concepts`"]) react(("`React`")) -.-> react/FundamentalsGroup(["`Fundamentals`"]) react(("`React`")) -.-> react/AdvancedConceptsGroup(["`Advanced Concepts`"]) react(("`React`")) -.-> react/StateManagementGroup(["`State Management`"]) react(("`React`")) -.-> react/StylingGroup(["`Styling`"]) css/BasicConceptsGroup -.-> css/selectors("`Selectors`") css/BasicStylingGroup -.-> css/colors("`Colors`") css/BasicStylingGroup -.-> css/fonts("`Fonts`") css/BasicStylingGroup -.-> css/text_styling("`Text Styling`") css/CoreLayoutGroup -.-> css/box_model("`Box Model`") css/CoreLayoutGroup -.-> css/margin_and_padding("`Margin and Padding`") css/CoreLayoutGroup -.-> css/borders("`Borders`") css/CoreLayoutGroup -.-> css/width_and_height("`Width and Height`") css/CoreLayoutGroup -.-> css/display_property("`Display Property`") css/CoreLayoutGroup -.-> css/positioning("`Positioning`") css/AdvancedLayoutGroup -.-> css/flexbox("`Flexbox`") css/AdvancedLayoutGroup -.-> css/grid_layout("`Grid Layout`") css/IntermediateStylingGroup -.-> css/backgrounds("`Backgrounds`") css/ResponsiveandAdaptiveDesignGroup -.-> css/mobile_first_design("`Mobile First Design`") css/CSSPreprocessorsGroup -.-> css/nesting("`Nesting`") css/CSSPreprocessorsGroup -.-> css/import_and_extend("`Import and Extend`") css/CodingStandardsandBestPracticesGroup -.-> css/comments("`Comments`") javascript/BasicConceptsGroup -.-> javascript/variables("`Variables`") javascript/BasicConceptsGroup -.-> javascript/data_types("`Data Types`") javascript/BasicConceptsGroup -.-> javascript/arith_ops("`Arithmetic Operators`") javascript/BasicConceptsGroup -.-> javascript/comp_ops("`Comparison Operators`") javascript/BasicConceptsGroup -.-> javascript/loops("`Loops`") javascript/BasicConceptsGroup -.-> javascript/functions("`Functions`") javascript/BasicConceptsGroup -.-> javascript/array_methods("`Array Methods`") javascript/AdvancedConceptsGroup -.-> javascript/higher_funcs("`Higher-Order Functions`") javascript/AdvancedConceptsGroup -.-> javascript/spread_rest("`Spread and Rest Operators`") javascript/AdvancedConceptsGroup -.-> javascript/template_lit("`Template Literals`") javascript/AdvancedConceptsGroup -.-> javascript/es6("`ES6 Features`") react/FundamentalsGroup -.-> react/jsx("`JSX`") react/FundamentalsGroup -.-> react/components_props("`Components and Props`") react/FundamentalsGroup -.-> react/event_handling("`Handling Events`") react/FundamentalsGroup -.-> react/list_keys("`Lists and Keys`") react/AdvancedConceptsGroup -.-> react/hooks("`React Hooks`") react/StateManagementGroup -.-> react/use_state_reducer("`Using useState and useReducer`") react/StylingGroup -.-> react/css_in_react("`CSS in React`") subgraph Lab Skills css/selectors -.-> lab-298945{{"`Building a React Drag-and-Drop Puzzle Game`"}} css/colors -.-> lab-298945{{"`Building a React Drag-and-Drop Puzzle Game`"}} css/fonts -.-> lab-298945{{"`Building a React Drag-and-Drop Puzzle Game`"}} css/text_styling -.-> lab-298945{{"`Building a React Drag-and-Drop Puzzle Game`"}} css/box_model -.-> lab-298945{{"`Building a React Drag-and-Drop Puzzle Game`"}} css/margin_and_padding -.-> lab-298945{{"`Building a React Drag-and-Drop Puzzle Game`"}} css/borders -.-> lab-298945{{"`Building a React Drag-and-Drop Puzzle Game`"}} css/width_and_height -.-> lab-298945{{"`Building a React Drag-and-Drop Puzzle Game`"}} css/display_property -.-> lab-298945{{"`Building a React Drag-and-Drop Puzzle Game`"}} css/positioning -.-> lab-298945{{"`Building a React Drag-and-Drop Puzzle Game`"}} css/flexbox -.-> lab-298945{{"`Building a React Drag-and-Drop Puzzle Game`"}} css/grid_layout -.-> lab-298945{{"`Building a React Drag-and-Drop Puzzle Game`"}} css/backgrounds -.-> lab-298945{{"`Building a React Drag-and-Drop Puzzle Game`"}} css/mobile_first_design -.-> lab-298945{{"`Building a React Drag-and-Drop Puzzle Game`"}} css/nesting -.-> lab-298945{{"`Building a React Drag-and-Drop Puzzle Game`"}} css/import_and_extend -.-> lab-298945{{"`Building a React Drag-and-Drop Puzzle Game`"}} css/comments -.-> lab-298945{{"`Building a React Drag-and-Drop Puzzle Game`"}} javascript/variables -.-> lab-298945{{"`Building a React Drag-and-Drop Puzzle Game`"}} javascript/data_types -.-> lab-298945{{"`Building a React Drag-and-Drop Puzzle Game`"}} javascript/arith_ops -.-> lab-298945{{"`Building a React Drag-and-Drop Puzzle Game`"}} javascript/comp_ops -.-> lab-298945{{"`Building a React Drag-and-Drop Puzzle Game`"}} javascript/loops -.-> lab-298945{{"`Building a React Drag-and-Drop Puzzle Game`"}} javascript/functions -.-> lab-298945{{"`Building a React Drag-and-Drop Puzzle Game`"}} javascript/array_methods -.-> lab-298945{{"`Building a React Drag-and-Drop Puzzle Game`"}} javascript/higher_funcs -.-> lab-298945{{"`Building a React Drag-and-Drop Puzzle Game`"}} javascript/spread_rest -.-> lab-298945{{"`Building a React Drag-and-Drop Puzzle Game`"}} javascript/template_lit -.-> lab-298945{{"`Building a React Drag-and-Drop Puzzle Game`"}} javascript/es6 -.-> lab-298945{{"`Building a React Drag-and-Drop Puzzle Game`"}} react/jsx -.-> lab-298945{{"`Building a React Drag-and-Drop Puzzle Game`"}} react/components_props -.-> lab-298945{{"`Building a React Drag-and-Drop Puzzle Game`"}} react/event_handling -.-> lab-298945{{"`Building a React Drag-and-Drop Puzzle Game`"}} react/list_keys -.-> lab-298945{{"`Building a React Drag-and-Drop Puzzle Game`"}} react/hooks -.-> lab-298945{{"`Building a React Drag-and-Drop Puzzle Game`"}} react/use_state_reducer -.-> lab-298945{{"`Building a React Drag-and-Drop Puzzle Game`"}} react/css_in_react -.-> lab-298945{{"`Building a React Drag-and-Drop Puzzle Game`"}} end

Project Setup

Objective: The foundation of our puzzle-game is a new React application.

  • Open your terminal.

  • Navigate to your project directory:

    cd puzzle-game
  • Install project dependencies:

    npm install

Create PuzzleGame Component

Objective: Set up the main puzzle game component.

  • Inside the src directory, create a folder named components.
  • Within components, create a new file named PuzzleGame.js.
  • Add the following basic structure to PuzzleGame.js:
// src/components/PuzzleGame.js
// Importing React and useState for managing component state
import React, { useState, useEffect } from "react";

// Defining the PuzzleGame component
const PuzzleGame = () => {
  // This component will handle the game logic and UI
  return (
    <div className="game-container">
      {/* This is where the puzzle will be rendered */}
    </div>
  );
};

// Exporting the component for use in other files
export default PuzzleGame;

Implement State and Image Setup

Objective: Set up the state to manage puzzle pieces and define the puzzle image.

  • In PuzzleGame.js, import useState from React.
  • Define a state variable to keep track of the puzzle pieces' positions.
  • Define the URL of the puzzle image (place an image named corgi.png in the public folder).
// src/components/PuzzleGame.js

// ...other imports
const PuzzleGame = () => {
  // Define the image URL and initial positions of puzzle pieces
  const imgUrl = "corgi.png"; // Make sure this image exists in the public folder
  const [positions, setPositions] = useState([...Array(16).keys()]);

  useEffect(() => {
    // Shuffle the positions for the initial puzzle setup
    setPositions((prevPositions) => {
      const newPos = [...prevPositions];
      newPos.sort(() => Math.random() - 0.5);
      return newPos;
    });
  }, []);

  // Component's return statement
  return (
    <div className="game-container">
      {/* The UI of the puzzle will be added here in later steps */}
    </div>
  );
};

// Exporting PuzzleGame component
export default PuzzleGame;

Render Puzzle Pieces

Objective: Display the puzzle pieces on the screen.

  • Map over the positions state to render individual puzzle pieces.
  • Each piece should display a portion of the image.
// src/components/PuzzleGame.js
const PuzzleGame = () => {
  // ...previous code

  return (
    <div className="game-container">
      <div className="reference-image">
        <img src={imgUrl} alt="Reference Image" />
      </div>
      <div className="puzzle-container">
        {positions.map((pos, index) => {
          const x = (pos % 4) * 100;
          const y = Math.floor(pos / 4) * 100;
          return (
            <div
              key={index}
              className="puzzle-piece"
              style={{
                backgroundImage: `url('${imgUrl}')`,
                backgroundPosition: `-${x}px -${y}px`
              }}
            />
          );
        })}
      </div>
    </div>
  );
};

Add Drag and Drop Functionality

Objective: Implement the logic for dragging and dropping puzzle pieces.

  • Add handlers for drag start, drag over, and drop events.
  • Implement the logic to swap puzzle pieces when dropped.
// src/components/PuzzleGame.js

const PuzzleGame = () => {
  // ...previous code

  // Handling the start of a drag event
  const handleDragStart = (e, position) => {
    e.dataTransfer.setData("text/plain", position);
  };

  // Handling the drop event
  const handleDrop = (e, position) => {
    e.preventDefault();
    const originalPosition = e.dataTransfer.getData("text");
    // Add logic here to swap positions of puzzle pieces
    setPositions((prevPositions) => {
      const newPos = [...prevPositions];
      [newPos[originalPosition], newPos[position]] = [
        newPos[position],
        newPos[originalPosition]
      ];
      return newPos;
    });
  };

  // Allowing the drop action by preventing default behavior
  const handleDragOver = (e) => {
    e.preventDefault();
  };

  // Render puzzle pieces with added drag and drop handlers
};
// src/components/PuzzleGame.js
const PuzzleGame = () => {
  // ...previous code

  return (
    <div className="game-container">
      <div className="reference-image">
        <img src={imgUrl} alt="Reference Image" />
      </div>
      <div className="puzzle-container">
        {positions.map((pos, index) => {
          const x = (pos % 4) * 100;
          const y = Math.floor(pos / 4) * 100;
          return (
            <div
              key={index}
              className="puzzle-piece"
              draggable
              onDragStart={(e) => handleDragStart(e, index)}
              onDrop={(e) => handleDrop(e, index)}
              onDragOver={handleDragOver}
              style={{
                backgroundImage: `url('${imgUrl}')`,
                backgroundPosition: `-${x}px -${y}px`
              }}
            />
          );
        })}
      </div>
    </div>
  );
};

Updating the App Component

Objective: Incorporate the PuzzleGame component into the main application file.

  • Open src/App.js.
  • Render the PuzzleGame component.
// src/App.js

import React from "react";
import "./App.css";
import PuzzleGame from "./components/PuzzleGame";

// App component that renders the PuzzleGame component
function App() {
  return (
    <div className="App">
      <PuzzleGame />
    </div>
  );
}

export default App;

Styling the Puzzle

Objective: Add CSS to make the puzzle visually appealing.

  • Open src/App.css.
  • Add styles for .game-container and .puzzle-piece to layout the puzzle correctly.
body {
  font-family: "Arial", sans-serif;
  text-align: center;
  padding: 20px;
  background: #f0f0f0;
}

.game-container {
  display: flex;
  justify-content: center;
  align-items: flex-start;
  gap: 20px;
}

.reference-image {
  display: flex;
  align-items: center;
  justify-content: center;
  border: 3px solid #aaa9a9;
  /* Add a border for consistency */
  padding: 10px;
  /* Add some padding around the image */
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
  /* Add shadow for depth */
}

.reference-image img {
  display: block;
  /* Remove any default inline spacing */
  max-width: 200px;
}

.puzzle-container {
  width: 400px;
  height: 400px;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 2px;
  border: 5px solid #aaa9a9;
  box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
  /* Add a larger shadow for a 3D effect */
  background: #ddd;
  /* Light background for the gaps */
}

.puzzle-piece {
  width: 100%;
  height: 100%;
  background-size: 400px 400px;
  cursor: grab;
  box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.3);
  /* Add inset shadow for each piece */
}

Running the Application

Objective: Launch the application to see the completed puzzle game.

  • In your project directory, run:

    npm start
  • The application should open in your web browser, displaying the puzzle game.

Summary

Congratulations! You've successfully built a drag-and-drop puzzle game in React. This project covered setting up a React project, creating components, managing state, handling user interactions, and applying basic styles. You can now experiment with adding more features like a timer, score, or different levels to enhance the game.

Other JavaScript Tutorials you may like