Code a Magic Card Trick with JavaScript and Node.js

Have you ever wanted to create your own interactive magic trick? With some JavaScript knowledge and Node.js, it‘s easier than you might think! In this tutorial, we‘ll code a classic card trick from scratch and learn valuable web development skills along the way.

The trick we‘ll be creating works like this:

  1. The magician (the web app in this case) displays a grid of playing card images face-down.
  2. The spectator (user) mentally selects a card and clicks it to turn it face-up, remembering their card.
  3. The spectator turns their card face-down again and clicks the "Magic" button.
  4. All the cards are shuffled around, then turned face-up, revealing that the spectator‘s original card has vanished!
  5. The spectator clicks the "Reveal" button and their vanished card reappears in a new location.

It seems like magic, but of course there‘s a logical explanation happening behind the scenes in code. Let‘s dive in and learn how to create this trick ourselves!

Setting Up the Development Environment

Before we can start coding, we need to set up a Node.js development environment. If you don‘t already have Node and npm (node package manager) installed, you can download an installer for your operating system from the official Node.js website.

Once you have Node and npm ready, create a new project folder and initialize it with a package.json file by running:

mkdir magic-cards
cd magic-cards
npm init -y

We‘ll be using the Express web framework to build our card trick app. We‘ll also use MongoDB to store some data during the trick. Install these dependencies with:

npm install express mongodb

Now create a new file called server.js. This is where we‘ll write the main code for our Express server.

Creating the Express Server

At the top of server.js, require the necessary modules and initialize an Express app:

const express = require(‘express‘);
const mongodb = require(‘mongodb‘);

const app = express();

We need to set up the app to use EJS (Embedded JavaScript) as its view engine. EJS lets us embed JavaScript code in HTML templates. Create a folder called views and configure Express:

app.set(‘view engine‘, ‘ejs‘);
app.set(‘views‘, ‘./views‘);

Let‘s define the first route for the app‘s home page:

app.get(‘/‘, (req, res) => {
  res.render(‘index‘);
});

This tells Express to render an EJS template called index.ejs when a user visits the root URL. Create this file inside the views folder:

<!DOCTYPE html>
<html>
  <head>
    <title>Magic Card Trick</title>
  </head>
  <body>

    <p>Click the button below to start.</p>
    <form action="/trick" method="POST">
      <button type="submit">Begin Trick</button>
    </form>
  </body>
</html>

Here we have a simple HTML page with a form that submits a POST request to /trick when the button is clicked. This will initiate the card trick.

To actually start the server and listen for requests, add this code at the bottom of server.js:

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server listening on port ${PORT}`);  
});

We‘re using the PORT environment variable if defined (as it would be on hosts like Heroku) or defaulting to 3000 for local development. Run the app with:

node server.js

Now if you visit http://localhost:3000 in your browser, you should see the welcoming message and begin button.

Generating the Playing Cards Grid

Next, let‘s create the /trick route and have it display the initial grid of face-down cards:

app.post(‘/trick‘, (req, res) => {  
  const cards = generateCards();
  res.render(‘trick‘, { cards: cards });
});

The generateCards function will create an array of 16 objects representing the playing cards in a 4×4 grid. Each object will have an id, image URL, and faceUp property. We‘ll use images of the freeCodeCamp.org Programmer Playing Cards, which have a nice programming theme.

Here‘s the code for generateCards:

function generateCards() {
  const cards = [];
  const suits = [‘hearts‘, ‘diamonds‘, ‘clubs‘, ‘spades‘];

  for (let i = 0; i < 4; i++) {
    for (let j = 0; j < 4; j++) {
      const value = j + 1; 
      const card = {
        id: i * 4 + j,
        image: `/img/${value}_of_${suits[i]}.png`,
        faceUp: false
      };
      cards.push(card);
    }
  }

  return cards;
}

The nested loops generate the 16 cards, assigning each an id from 0-15, an image URL based on its suit and value, and a faceUp property initially set to false.

The /img/ path assumes you have the card images stored in a folder called public/img. Make sure to download the images or replace with your own.

Now create the trick.ejs template that will render the cards in a grid:

<!DOCTYPE html>
<html>
  <head>
    <title>Magic Card Trick</title>
    <style>
      .card {
        width: 100px;
        height: 150px;
        margin: 10px;
        cursor: pointer;
      }
      .flipped {
        transform: rotateY(180deg);
      }
    </style>
  </head>
  <body>


    <div id="cards">
      <% for (let card of cards) { %>
        <img 
          src="<%= card.faceUp ? card.image : ‘/img/back.png‘ %>" 
          onclick="flipCard(this, <%= card.id %>)"
          class="card <%= card.faceUp ? ‘flipped‘ : ‘‘ %>"
        >  
      <% } %>
    </div>

    <button id="magic" onclick="doMagic()" disabled>Magic</button>
    <button id="reveal" onclick="reveal()" disabled>Reveal</button>

    <script>
      let selectedCard = null;

      function flipCard(img, id) {
        img.classList.toggle(‘flipped‘);
        img.src = img.classList.contains(‘flipped‘) 
          ? ‘<%= cards[id].image %>‘
          : ‘/img/back.png‘;

        selectedCard = selectedCard === id ? null : id;
        document.getElementById(‘magic‘).disabled = !selectedCard;
      }

      function doMagic() {
        fetch(‘/magic‘, {
          method: ‘POST‘,
          headers: { ‘Content-Type‘: ‘application/json‘ },
          body: JSON.stringify({ selectedCard: selectedCard })  
        })
          .then(response => response.text())
          .then(html => {
            document.body.innerHTML = html;
            document.getElementById(‘reveal‘).disabled = false;  
          });
      }

      function reveal() {
        fetch(‘/reveal‘)
          .then(response => response.text())
          .then(html => {
            document.body.innerHTML = html;
          });  
      }
    </script>

  </body>
</html>

There‘s a lot going on here, so let‘s break it down:

  • We loop through the cards array passed in from the route and render each as an tag. The src attribute is conditionally set to either the card‘s image or the back image based on its faceUp property.

  • Each has an onclick handler that calls the flipCard function, toggling the flipped CSS class and updating the image src. It also keeps track of the currently selected card‘s id.

  • The Magic button is initially disabled until a card is selected. Its onclick handler calls doMagic which sends a POST request to /magic with the selected card id in the body.

  • The Reveal button is also initially disabled. Its onclick calls reveal which sends a GET to /reveal.

The /magic and /reveal routes are where the real trick happens, so let‘s implement those next.

Performing the "Magic"

Here‘s the code for the /magic route:

app.post(‘/magic‘, (req, res) => {
  const selectedCard = parseInt(req.body.selectedCard); 
  let cards = generateCards();
  let magicIndex = null;

  cards = cards.map((card, i) => {
    if (i === selectedCard) {
      // Remove the selected card
      return null;
    } else if (!magicIndex && card.value === 1) {
      // Replace the first card that has a value of 1 (Ace)
      magicIndex = i;
      return { ...card, value: cards[selectedCard].value };
    } else {
      return card;
    }
  }).filter(card => card);

  // Fisher-Yates shuffle algorithm
  for (let i = cards.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [cards[i], cards[j]] = [cards[j], cards[i]];
  }

  cards = cards.map(card => ({ ...card, faceUp: true }));

  req.app.locals.magicIndex = magicIndex;

  res.render(‘magic‘, { cards: cards });  
});

This is where the magic happens! Here‘s how it works:

  1. Extract the selectedCard id from the request body.
  2. Generate a new array of cards.
  3. Map over the cards, removing the selected card and replacing the first Ace card with a card whose value matches the selected card. This is what creates the illusion that the selected card has "vanished".
  4. Shuffle the cards using the Fisher-Yates algorithm.
  5. Set all the cards to faceUp: true.
  6. Store the index of the "magic" card (the one that replaced the Ace) in res.app.locals so we can access it in the /reveal route.
  7. Render the magic.ejs template with the updated cards.

The magic.ejs template is very similar to trick.ejs, just without the card flipping functionality and with all cards faceUp.

Now for the final step, revealing the vanished card:

app.get(‘/reveal‘, (req, res) => {
  let cards = generateCards();
  const magicIndex = req.app.locals.magicIndex;  

  cards[magicIndex] = {
    id: magicIndex, 
    value: 1,
    image: ‘/img/fcc_ace_of_spades.png‘,
    faceUp: true
  };

  res.render(‘reveal‘, { cards: cards });
});

Here we generate one more array of cards. We retrieve the stored magicIndex and use it to replace that card with the final reveal card (the Ace of Spades in this case, but you could use any card you want).

Finally, we render the reveal.ejs template, which again is very similar to the previous templates. The only difference is it displays a message indicating the selected card has been found.

Deploying the App

We now have a fully functioning magic card trick app! The last step is to deploy it so others can try it out. One easy option is to use Heroku.

First, create a Procfile in your project root with the following line:

web: node server.js

This tells Heroku how to start your app. Commit the Procfile and push your code to a new GitHub repository.

On Heroku, create a new app and link it to your GitHub repo. Enable automatic deploys so that the app will rebuild whenever you push new changes.

Click the "Deploy" button, and once the build finishes, you should be able to access your card trick app at https://your-app-name.herokuapp.com!

Next Steps

Congratulations, you‘ve just coded your own interactive magic card trick using JavaScript and Node.js! This project covered a variety of web development concepts, including:

  • Setting up a Node.js/Express app
  • Generating dynamic data
  • Implementing interactivity with event listeners
  • Posting and fetching data with HTTP requests
  • Rendering views with template engines
  • Deploying a Node.js app to a host

You can expand on this trick in many ways. Some ideas:

  • Add more advanced card animations/transitions with CSS
  • Enhance the visuals with a front-end framework like React or Vue
  • Allow users to customize the cards or grid size
  • Implement multiple tricks or difficulty levels

The beauty of magic tricks is that they can be endlessly customized. Think of a story you want to tell or illusion you want to create, and see if you can replicate it in code.

I hope this tutorial has sparked your imagination and shown you the magical possibilities of combining art and code. Now go forth and create your own delightful deceptions!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *