jQuery Flip Puzzle Game

CSSCSSBeginner
Practice Now

Introduction

This project, the "jQuery Flip Puzzle Game," provides a hands-on experience in web development, focusing on JavaScript, jQuery, and Bootstrap. It involves object-oriented programming in JavaScript and addresses the common issue of "this" binding in the context of web development. The game is implemented using jQuery and Bootstrap 3 for the user interface. While familiarity with Bootstrap is helpful, the project's core logic is built with jQuery and JavaScript.

In the game, players start with a grid of orange blocks. Each block has one orange side and one blue side. When a player clicks on a block, its color flips, and the adjacent blocks' colors also change. The goal is to turn all blocks blue to complete the game.

👀 Preview

jQuery Flip Puzzle Game

🎯 Tasks

In this project, you will learn:

  • How to implement object-oriented programming in JavaScript and address the "this" binding issue.
  • How to build the core game logic using jQuery and JavaScript.
  • How to create an interactive puzzle game where players flip block colors to win.

🏆 Achievements

After completing this project, you will be able to:

  • Apply object-oriented programming principles in JavaScript.
  • Handle "this" binding in JavaScript for event handling and object methods.
  • Develop interactive web games using jQuery.
  • Utilize Bootstrap 3 to create visually appealing and user-friendly interfaces.

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/IntermediateStylingGroup(["`Intermediate Styling`"]) css(("`CSS`")) -.-> css/CSSPreprocessorsGroup(["`CSS Preprocessors`"]) css(("`CSS`")) -.-> css/CodingStandardsandBestPracticesGroup(["`Coding Standards and Best Practices`"]) html(("`HTML`")) -.-> html/BasicStructureGroup(["`Basic Structure`"]) html(("`HTML`")) -.-> html/TextContentandFormattingGroup(["`Text Content and Formatting`"]) html(("`HTML`")) -.-> html/LayoutandSectioningGroup(["`Layout and Sectioning`"]) html(("`HTML`")) -.-> html/FormsandInputGroup(["`Forms and Input`"]) html(("`HTML`")) -.-> html/AdvancedElementsGroup(["`Advanced Elements`"]) javascript(("`JavaScript`")) -.-> javascript/BasicConceptsGroup(["`Basic Concepts`"]) javascript(("`JavaScript`")) -.-> javascript/AdvancedConceptsGroup(["`Advanced Concepts`"]) javascript(("`JavaScript`")) -.-> javascript/ToolsandEnvironmentGroup(["`Tools and Environment`"]) javascript(("`JavaScript`")) -.-> javascript/SecurityGroup(["`Security`"]) jquery(("`jQuery`")) -.-> jquery/EventHandlingGroup(["`Event Handling`"]) 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/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/IntermediateStylingGroup -.-> css/backgrounds("`Backgrounds`") css/CSSPreprocessorsGroup -.-> css/nesting("`Nesting`") css/CodingStandardsandBestPracticesGroup -.-> css/comments("`Comments`") html/BasicStructureGroup -.-> html/basic_elems("`Basic Elements`") html/BasicStructureGroup -.-> html/charset("`Character Encoding`") html/BasicStructureGroup -.-> html/lang_decl("`Language Declaration`") html/BasicStructureGroup -.-> html/head_elems("`Head Elements`") html/TextContentandFormattingGroup -.-> html/text_head("`Text and Headings`") html/TextContentandFormattingGroup -.-> html/para_br("`Paragraphs and Line Breaks`") html/LayoutandSectioningGroup -.-> html/access_cons("`Accessibility Considerations`") html/LayoutandSectioningGroup -.-> html/doc_flow("`Document Flow Understanding`") html/FormsandInputGroup -.-> html/forms("`Form Elements`") html/AdvancedElementsGroup -.-> html/custom_attr("`Custom Data Attributes`") html/AdvancedElementsGroup -.-> html/adv_access("`Accessibility for Advanced Elements`") 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/cond_stmts("`Conditional Statements`") javascript/BasicConceptsGroup -.-> javascript/loops("`Loops`") javascript/BasicConceptsGroup -.-> javascript/functions("`Functions`") javascript/BasicConceptsGroup -.-> javascript/str_manip("`String Manipulation`") javascript/BasicConceptsGroup -.-> javascript/array_methods("`Array Methods`") javascript/BasicConceptsGroup -.-> javascript/obj_manip("`Object Manipulation`") javascript/AdvancedConceptsGroup -.-> javascript/async_prog("`Asynchronous Programming`") javascript/AdvancedConceptsGroup -.-> javascript/closures("`Closures`") javascript/AdvancedConceptsGroup -.-> javascript/proto_inherit("`Prototypes and Inheritance`") javascript/ToolsandEnvironmentGroup -.-> javascript/bom("`Browser Object Model`") javascript/SecurityGroup -.-> javascript/web_sec("`Web Security Basics`") jquery/EventHandlingGroup -.-> jquery/event_methods("`Event Methods`") subgraph Lab Skills css/selectors -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} css/colors -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} css/fonts -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} css/text_styling -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} css/margin_and_padding -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} css/borders -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} css/width_and_height -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} css/display_property -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} css/positioning -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} css/backgrounds -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} css/nesting -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} css/comments -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} html/basic_elems -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} html/charset -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} html/lang_decl -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} html/head_elems -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} html/text_head -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} html/para_br -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} html/access_cons -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} html/doc_flow -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} html/forms -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} html/custom_attr -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} html/adv_access -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} javascript/variables -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} javascript/data_types -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} javascript/arith_ops -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} javascript/comp_ops -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} javascript/cond_stmts -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} javascript/loops -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} javascript/functions -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} javascript/str_manip -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} javascript/array_methods -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} javascript/obj_manip -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} javascript/async_prog -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} javascript/closures -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} javascript/proto_inherit -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} javascript/bom -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} javascript/web_sec -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} jquery/event_methods -.-> lab-298990{{"`jQuery Flip Puzzle Game`"}} end

Basic HTML Structure

According to the preview image, a preliminary webpage layout is created. In Bootstrap, there is a Modal dialog similar to the alert pop-up window, and the style of Modal is more aesthetic in comparison.

We need to include the CSS styles from Bootstrap, our custom CSS styles, jQuery file, Bootstrap JavaScript file, and the game's main JavaScript file into the project. Write the following code in the head tag of the HTML page:

<head>
  <meta charset="utf-8" />
  <title>Blue Puzzle</title>
  <!-- Include Bootstrap CSS -->
  <link
    rel="stylesheet"
    href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css"
  />
  <!-- Include custom CSS -->
  <link rel="stylesheet" href="style.css" />

  <!-- Include jQuery and Bootstrap JavaScript -->
  <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"></script>
  <!-- Include game main JavaScript -->
  <script src="game.js"></script>
</head>

Game Area Setup

To set up the game area, including the buttons for game reset and other functions, insert the following code into the body tag:

<div class="container">
  <div class="heading">
    <h1 class="title">jQuery Flip Puzzle Game</h1>
    <div class="scoresContainer">
      <!-- Display current game level -->
      <div class="currLevel">Current level: <b>1</b></div>
    </div>
  </div>
  <div class="aboveGame">
    <!-- Game buttons -->
    <a
      class="instruct btn btn-primary"
      data-toggle="modal"
      data-target="#instructions"
      >Game Instructions</a
    >
    <a
      class="newgame btn btn-primary"
      data-toggle="modal"
      data-target="#newGame"
      >Restart</a
    >
    <a
      class="reset btn btn-primary"
      data-toggle="modal"
      data-target="#restartLevel"
      >Reset Level</a
    >
  </div>
  <div class="board">
    <!-- Game area -->
    <div class="gamerow">
      <div class="gamesquare coord0q0"></div>
    </div>
  </div>
</div>

Edit the content of the popup corresponding to the "Edit Game Instructions" button. Write the following code below the game area code:

<!-- Gameplay Modal -->
<div
  class="modal fade"
  id="instructions"
  tabindex="-1"
  role="dialog"
  aria-hidden="true"
>
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h4 class="modal-title">Gameplay</h4>
      </div>
      <div class="modal-body">
        <p>How to win: Make all puzzle pieces turn blue.</p>
        <p>
          Gameplay: Each square has one orange side and one blue side. When you
          click on a square, its color will flip, and the colors of the squares
          adjacent to it will also flip.
        </p>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-primary" data-dismiss="modal">
          Start Game
        </button>
      </div>
    </div>
  </div>
</div>

Edit the content of the modal corresponding to the "Restart" button. Write the following code below the game logic code:

<!-- New Game modal -->
<div
  class="modal fade"
  id="newGame"
  tabindex="-1"
  role="dialog"
  aria-hidden="true"
>
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h4 class="modal-title">Restart</h4>
      </div>
      <div class="modal-body">
        <p>Are you sure you want to restart?</p>
      </div>
      <div class="modal-footer">
        <button
          type="button"
          class="btn btn-primary"
          id="newGameConfirm"
          data-dismiss="modal"
        >
          Start Game
        </button>
      </div>
    </div>
  </div>
</div>

Modify the content of the popup corresponding to the "Reset Level" button. Add the following code below the "Restart Level" code:

<!-- Reset Level Confirmation Modal -->
<div
  class="modal fade"
  id="restartLevel"
  tabindex="-1"
  role="dialog"
  aria-hidden="true"
>
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h4 class="modal-title">Reset Level Confirmation</h4>
      </div>
      <div class="modal-body">
        <p>Are you sure you want to reset the level?</p>
      </div>
      <div class="modal-footer">
        <button
          type="button"
          class="btn btn-primary"
          id="resetLevelConfirm"
          data-dismiss="modal"
        >
          Reset
        </button>
      </div>
    </div>
  </div>
</div>

CSS Styles

The CSS styles of the game are relatively simple. The code in style.css is shown below:

.container {
  width: 600px;
  margin: 0 auto;
}

/* Game level */
.scoresContainer {
  float: right;
  text-align: right;
  font-size: 18px;
}

/* Game buttons */
.aboveGame:after {
  display: block;
  margin: 20px 0;
  content: "";
  clear: both;
}

/* Game area */
.board {
  position: absolute;
  background-color: #5f5f5f;
  border-radius: 4px;
}

.gamesquare {
  float: left;
  margin-right: 15px;
  border-radius: 3px;
}

Setting up the game scene

The tiles in the game panel are stored as a two-dimensional array, where each element has a value of 0 (set to orange) or 1 (set to blue). Initially, all values are set to 0. When a tile is clicked, its color is flipped, and the coordinates of the adjacent tiles are determined to flip their colors as well, while also changing the values in the two-dimensional array. The game is completed when all values in the array become 1 (i.e., all tiles are blue).

Main logic:

  1. The relationship between the game scene size, difficulty level, and the size and number of tiles.

  2. Clicking on a tile and changing the state of the adjacent tiles.

  3. Checking if all tile states are 1.

function SetStyle() {}
SetStyle.prototype.setGridSize = function (level) {
  var margin = this.getMargin(level);
  var res = ($(".container").width() - margin * level) / level;

  // Set the size and spacing of the tiles
  $(".gamesquare").css("margin-right", margin);
  $(".gamesquare").css("width", res);
  $(".gamesquare").css("height", res);

  // Set the height, right margin, and bottom margin of each row
  $(".gamerow").css("height", res);
  $(".gamerow").css("margin-right", margin * -1);
  $(".gamerow").css("margin-bottom", margin);

  // Set the padding of the game area
  $(".board").css("padding", margin);
  $(".board").css("padding-bottom", 0);
};
SetStyle.prototype.getMargin = function (level) {
  if (level <= 6) return 15;
  if (level > 15) return 5;
  return 20 - level;
};

Create Game constructor

// Game constructor
function Game() {
  // Game level
  this.level = 1;
  // Create objects for controlling the game
  this.gb;
  this.sh = new SetStyle();
}

The Game constructor is meant to create instances of a game with properties to track the game's state and methods to handle game functionalities like starting the game, updating its state, and rendering visuals. However, the actual logic for these functionalities (inside start, update, and render methods) is currently not provided, and placeholders are in place for future implementation.

Setting Game Details

Set the game details in the prototype methods of the Game class:

// Prototype method of the Game class, controlling the specific game logic, make sure to reset the 'this' reference
Game.prototype = {
  processClick: function (w, h) {
    this.gb.processClick(w, h);
    this.updateCounts();
    if (this.gb.isGameWin()) {
      this.gameEnd();
    }
  },
  // Start the game
  beginGame: function () {
    this.setupLevel();
  },
  // Game end
  gameEnd: function () {
    this.level++;
    this.resetGame();
  },
  // Reset the game, redirect 'this' using bind
  resetGame: function () {
    $("#levelDescriptor").html("Enter Level " + this.level);
    setTimeout(
      function () {
        this.setupLevel(); // When 'this' is not reset, it refers to the window object
      }.bind(this),
      500
    ); // Use bind to redirect 'this' from window to the instance
  },
  // Set the difficulty level
  setupLevel: function () {
    this.gb = new GameBoard(this.level, this.level);
    $(".board").html(""); // Clear game board
    this.gb.populate(); // Reset all tiles to orange color
    this.gb.renderBoard(); // Render game board and create tiles
    this.sh.setGridSize(this.level); // Control tile size in game area
    this.updateCounts(); // Update current level display
    this.applyBindings(); // Flip the colors of tiles around the clicked tile
  },
  // Update current level display
  updateCounts: function () {
    $(".currLevel").html("Current Level: <b>" + this.level + "</b>");
  },
  applyBindings: function () {
    var that = this; // Save 'this' as a variable before the DOM event callback for easy reference
    $(".gamesquare").click(function () {
      // Get the position of the clicked tile
      var cname = $(this).attr("class").split(" ")[1];
      var coord = cname.substring(5).split("q");
      var height = parseInt(coord[1]);
      var width = parseInt(coord[0]);
      that.processClick(width, height);
    });
  },
  onNewGameClick: function () {
    this.level = 1;
    this.setupLevel();
  }
};

This code extends the functionality of the Game constructor by adding prototype methods. These methods define the primary game logic and interactions.

Setting the coordinates of the blocks

This code defines a constructor function named GameBoard that is used to create game board objects.

// xPos, yPos are the coordinates of the block
function GameBoard(xPos, yPos) {
  // Game board
  // Tile coordinates
  this.high = yPos - 1; // Index starts from 0
  this.wide = xPos - 1; // Index starts from 0
  this.count = 0;
  // The horizontal coordinate is wide, and the vertical coordinate is high
  //    [0][0] |  [0][1]
  //  - - - - - - - - - - - -
  //    [1][0] |  |[1][1]

  // Create a two-dimensional array of tiles
  this.board = new Array(xPos);
  for (var i = 0; i <= this.wide; i++) {
    this.board[i] = new Array(yPos);
  }
}

Setting Game Rules

The code snippet you've provided extends the functionality of the GameBoard constructor by adding prototype methods which define the core game rules and rendering logic.

// Implementation of game rules
GameBoard.prototype = {
  renderBoard: function () {
    var htmlString = ""; // Structure of game squares
    for (var j = 0; j <= this.high; j++) {
      htmlString += "<div class='gamerow'>";
      for (var i = 0; i <= this.wide; i++) {
        htmlString += "<div class='gamesquare coord" + i + "q" + j + "'></div>";
      }
      htmlString += "</div>";
    }
    $(".board").html(htmlString);

    for (var i = 0; i <= this.wide; i++) {
      for (var j = 0; j <= this.high; j++) {
        this.processClickView(i, j);
      }
    }
  },
  processClick: function (w, h) {
    //
    // Flip the colors of the blocks surrounding the clicked block
    //

    // Find the surrounding blocks that need to be flipped
    var lowx = w - 1;
    var highx = w + 1;
    var lowy = h - 1;
    var highy = h + 1;

    // Check if the clicked block is an edge block
    if (w == 0) lowx = 0;
    if (w == this.wide) highx = this.wide;
    if (h == 0) lowy = 0;
    if (h == this.high) highy = this.high;

    // Flip the vertically adjacent blocks of the clicked block
    for (var i = lowy; i <= highy; i++) {
      if (this.board[w][i] == 0) {
        this.board[w][i] = 1;
        this.count++;
      } else {
        this.board[w][i] = 0;
        this.count--;
      }
      this.processClickView(w, i);
    }

    // Flip the horizontally adjacent blocks of the clicked block
    for (var i = lowx; i <= highx; i++) {
      if (i == w) continue;
      if (this.board[i][h] == 0) {
        this.board[i][h] = 1;
        this.count++;
      } else {
        this.board[i][h] = 0;
        this.count--;
      }
      this.processClickView(i, h);
    }
  },
  // Flip the color of a block
  processClickView: function (w, h) {
    var coord = ".coord" + w + "q" + h;
    if (this.board[w][h] == 0) {
      $(coord).css("background-color", "#e8BB39");
    } else {
      $(coord).css("background-color", "#6060e0");
    }
  },

  // Reset all blocks to orange color
  populate: function () {
    for (var i = 0; i <= this.wide; i++) {
      for (var j = 0; j <= this.high; j++) {
        this.board[i][j] = 0;
      }
    }
  },

  // Game win condition
  isGameWin: function () {
    return this.count == (this.wide + 1) * (this.high + 1);
  }
};

Initializing the game

This code sets up the game when the document is ready. It initializes the game, starts the first level, and also sets up event listeners for resetting the current level or starting a new game.

// Initialize the game
$(document).ready(function () {
  // Create the game
  var game = new Game();
  // Begin the game
  game.beginGame();

  // Reset the level tiles
  $("#resetLevelConfirm").click(function () {
    game.setupLevel();
  });

  // Start a new game
  $("#newGameConfirm").click(function () {
    game.onNewGameClick();
  });
});

Running the App

  • Open index.html in a web browser.
    open web
  • The effect of the page is as follows:
    Image description

Summary

From the development of this game, we have encountered:

  • Object-oriented methods in JavaScript
  • JavaScript internal this reference and redirection
  • How to manipulate the DOM with jQuery
  • Matrix relationship issues

We do not actually need to implement this logic, but we need to learn the mindset. To solve a problem, we need to analyze the problem first and clarify the logical relationships involved. That is the key to problem-solving.

Other CSS Tutorials you may like