Skip to main content

Step 2 - Flipping Cards: Click Detection

Let's make cards flip when clicked and manage the flipping logic!

Understanding Flip Logic

When clicking cards:

  1. Detect which card was clicked
  2. Flip it face-up (if allowed)
  3. Track how many cards are currently flipped
  4. Only allow 2 cards flipped at once
  5. Later we'll check if they match

Step 1: Add Click Tracking Variables

Add variables to track flipped cards:

let cols = 4;
let rows = 4;
let cardWidth;
let cardHeight;
let cards = [];
let flippedCards = []; // Track currently flipped cards

Breaking it down

  • flippedCards = [] - Array to hold references to cards currently face-up
  • Will contain 0, 1, or 2 cards
  • When 2 cards are flipped, we'll check if they match

Step 2: Detect Card Clicks

Add mousePressed() function:

function mousePressed() {
// Loop through all cards
for (let card of cards) {
// Check if click is inside this card
if (mouseX > card.x && mouseX < card.x + cardWidth &&
mouseY > card.y && mouseY < card.y + cardHeight) {
console.log("Clicked card with value:", card.value);
}
}
}

Breaking it down

  • mousePressed() - Called once when mouse is clicked
  • Loop through all cards to find which was clicked
  • Boundary check:
    • mouseX > card.x - Click is to the right of card's left edge
    • mouseX < card.x + cardWidth - Click is to the left of card's right edge
    • mouseY > card.y - Click is below card's top edge
    • mouseY < card.y + cardHeight - Click is above card's bottom edge
    • All four must be true = click is inside card
  • Console logs which card was clicked

Test it: Click cards - console should show their values!

Step 3: Flip Cards on Click

Now actually flip the card:

function mousePressed() {
for (let card of cards) {
if (mouseX > card.x && mouseX < card.x + cardWidth &&
mouseY > card.y && mouseY < card.y + cardHeight) {

// Don't flip if already matched
if (card.matched) {
continue;
}

// Don't flip if already flipped
if (card.flipped) {
continue;
}

// Flip the card
card.flipped = true;
flippedCards.push(card);
console.log("Flipped cards:", flippedCards.length);
}
}
}

Breaking it down

  • if (card.matched) continue; - Skip matched cards (can't flip them again)
  • if (card.flipped) continue; - Skip already flipped cards
  • card.flipped = true - Set card to face-up
  • flippedCards.push(card) - Add to array of flipped cards
  • Console shows how many cards are currently flipped

Test it: Click cards - they should flip face-up!

Step 4: Limit to Two Cards

Only allow two cards to be flipped at once:

function mousePressed() {
// Don't flip more cards if 2 are already flipped
if (flippedCards.length >= 2) {
return;
}

for (let card of cards) {
if (mouseX > card.x && mouseX < card.x + cardWidth &&
mouseY > card.y && mouseY < card.y + cardHeight) {

if (card.matched) {
continue;
}

if (card.flipped) {
continue;
}

card.flipped = true;
flippedCards.push(card);
console.log("Flipped cards:", flippedCards.length);
}
}
}

Breaking it down

  • if (flippedCards.length >= 2) return; - Check at start of function
  • If 2 cards are already flipped, exit function immediately
  • Player can't flip more cards until we handle the current pair
  • This prevents flipping 3+ cards at once

Test it: You can only flip 2 cards, then clicks don't work!

Step 5: Add Temporary Match Checking

Let's check if two flipped cards match:

function mousePressed() {
if (flippedCards.length >= 2) {
return;
}

for (let card of cards) {
if (mouseX > card.x && mouseX < card.x + cardWidth &&
mouseY > card.y && mouseY < card.y + cardHeight) {

if (card.matched) {
continue;
}

if (card.flipped) {
continue;
}

card.flipped = true;
flippedCards.push(card);

// Check for match when 2 cards are flipped
if (flippedCards.length === 2) {
checkMatch();
}
}
}
}

function checkMatch() {
let card1 = flippedCards[0];
let card2 = flippedCards[1];

if (card1.value === card2.value) {
console.log("Match!");
} else {
console.log("No match!");
}
}

Breaking it down

  • if (flippedCards.length === 2) - When second card is flipped
  • checkMatch() - New function to compare cards
  • let card1 = flippedCards[0] - First flipped card
  • let card2 = flippedCards[1] - Second flipped card
  • if (card1.value === card2.value) - Compare their values
  • Console shows if they match

Test it: Flip 2 cards - console says if they match!

Step 6: Handle Matches

When cards match, mark them as matched:

function checkMatch() {
let card1 = flippedCards[0];
let card2 = flippedCards[1];

if (card1.value === card2.value) {
// Cards match!
card1.matched = true;
card2.matched = true;
console.log("Match! Cards marked as matched.");

// Clear flipped cards array
flippedCards = [];
} else {
console.log("No match!");
}
}

Breaking it down

  • When match is found:
    • card1.matched = true - Mark first card as matched
    • card2.matched = true - Mark second card as matched
    • Matched cards stay face-up (remember our draw code checks card.matched)
    • flippedCards = [] - Clear the array so we can flip more cards
  • Player can now flip next pair

Test it: Find matching pairs - they stay face-up!

Step 7: Flip Non-Matching Cards Back

Cards that don't match should flip back after a delay:

let cols = 4;
let rows = 4;
let cardWidth;
let cardHeight;
let cards = [];
let flippedCards = [];
let canClick = true; // Prevent clicking during delay

Update checkMatch():

function checkMatch() {
let card1 = flippedCards[0];
let card2 = flippedCards[1];

if (card1.value === card2.value) {
card1.matched = true;
card2.matched = true;
flippedCards = [];
} else {
// No match - flip back after delay
canClick = false; // Prevent clicking during delay

setTimeout(function() {
card1.flipped = false;
card2.flipped = false;
flippedCards = [];
canClick = true; // Allow clicking again
}, 1000); // 1 second delay
}
}

Update mousePressed() to check canClick:

function mousePressed() {
// Don't allow clicks during delay
if (!canClick) {
return;
}

if (flippedCards.length >= 2) {
return;
}

// ... rest of code stays the same ...
}

Breaking it down

  • canClick = true - Variable to control when clicks are allowed
  • When cards don't match:
    • canClick = false - Disable clicking
    • setTimeout(function() {...}, 1000) - Wait 1 second (1000 milliseconds)
    • Inside setTimeout:
      • card1.flipped = false - Flip first card back
      • card2.flipped = false - Flip second card back
      • flippedCards = [] - Clear array
      • canClick = true - Re-enable clicking
  • if (!canClick) return; - Exit mousePressed if clicking disabled
  • Player sees cards for 1 second before they flip back

Test it: Click non-matching cards - they flip back after 1 second!

Step 8: Add Visual Feedback for Hover

Show which card you're about to click:

function draw() {
background(50);

for (let card of cards) {
// Check if mouse is over this card
let hovering = false;
if (canClick && !card.flipped && !card.matched) {
if (mouseX > card.x && mouseX < card.x + cardWidth &&
mouseY > card.y && mouseY < card.y + cardHeight) {
hovering = true;
}
}

if (card.flipped || card.matched) {
// Face-up card
if (hovering) {
fill(220, 120, 120); // Brighter if hovering
} else {
fill(200, 100, 100);
}
stroke(255);
strokeWeight(3);
rect(card.x, card.y, cardWidth, cardHeight, 10);

fill(255);
noStroke();
textSize(40);
textAlign(CENTER, CENTER);
text(card.value, card.x + cardWidth / 2, card.y + cardHeight / 2);
} else {
// Face-down card
if (hovering) {
fill(120, 170, 220); // Brighter blue if hovering
} else {
fill(100, 150, 200);
}
stroke(255);
strokeWeight(3);
rect(card.x, card.y, cardWidth, cardHeight, 10);

stroke(80, 120, 160);
strokeWeight(2);
line(card.x + 10, card.y + 10,
card.x + cardWidth - 10, card.y + cardHeight - 10);
line(card.x + cardWidth - 10, card.y + 10,
card.x + 10, card.y + cardHeight - 10);
}
}
}

Breaking it down

  • let hovering = false - Track if mouse is over this card
  • Check for hover:
    • Only if clicking is allowed (canClick)
    • Only if card is not flipped or matched
    • Check if mouse is inside card boundaries
  • If hovering, use brighter color
    • Face-down: fill(120, 170, 220) instead of fill(100, 150, 200)
    • Face-up: fill(220, 120, 120) instead of fill(200, 100, 100)
  • Gives clear feedback about which card will be clicked

Test it: Move mouse over cards - they brighten!

Step 9: Change Cursor on Hover

Make cursor change to pointer:

function draw() {
background(50);

let anyHovering = false;

for (let card of cards) {
let hovering = false;
if (canClick && !card.flipped && !card.matched) {
if (mouseX > card.x && mouseX < card.x + cardWidth &&
mouseY > card.y && mouseY < card.y + cardHeight) {
hovering = true;
anyHovering = true;
}
}

// ... rest of card drawing code ...
}

// Set cursor based on whether hovering any card
if (anyHovering) {
cursor(HAND);
} else {
cursor(ARROW);
}
}

Breaking it down

  • let anyHovering = false - Track if hovering ANY card
  • Set to true when hovering any clickable card
  • cursor(HAND) - Show pointing hand cursor
  • cursor(ARROW) - Normal arrow cursor
  • Clear feedback that cards are clickable

Complete Step 2 Code

Here's everything together:

let cols = 4;
let rows = 4;
let cardWidth;
let cardHeight;
let cards = [];
let flippedCards = [];
let canClick = true;

function setup() {
createCanvas(600, 600);

cardWidth = width / cols - 20;
cardHeight = height / rows - 20;

let values = [1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8];
values = shuffle(values);

let index = 0;
for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
let card = {
value: values[index],
x: i * (cardWidth + 20) + 10,
y: j * (cardHeight + 20) + 10,
flipped: false,
matched: false
};
cards.push(card);
index++;
}
}
}

function draw() {
background(50);

let anyHovering = false;

for (let card of cards) {
let hovering = false;
if (canClick && !card.flipped && !card.matched) {
if (mouseX > card.x && mouseX < card.x + cardWidth &&
mouseY > card.y && mouseY < card.y + cardHeight) {
hovering = true;
anyHovering = true;
}
}

if (card.flipped || card.matched) {
if (hovering) {
fill(220, 120, 120);
} else {
fill(200, 100, 100);
}
stroke(255);
strokeWeight(3);
rect(card.x, card.y, cardWidth, cardHeight, 10);

fill(255);
noStroke();
textSize(40);
textAlign(CENTER, CENTER);
text(card.value, card.x + cardWidth / 2, card.y + cardHeight / 2);
} else {
if (hovering) {
fill(120, 170, 220);
} else {
fill(100, 150, 200);
}
stroke(255);
strokeWeight(3);
rect(card.x, card.y, cardWidth, cardHeight, 10);

stroke(80, 120, 160);
strokeWeight(2);
line(card.x + 10, card.y + 10,
card.x + cardWidth - 10, card.y + cardHeight - 10);
line(card.x + cardWidth - 10, card.y + 10,
card.x + 10, card.y + cardHeight - 10);
}
}

if (anyHovering) {
cursor(HAND);
} else {
cursor(ARROW);
}
}

function mousePressed() {
if (!canClick) {
return;
}

if (flippedCards.length >= 2) {
return;
}

for (let card of cards) {
if (mouseX > card.x && mouseX < card.x + cardWidth &&
mouseY > card.y && mouseY < card.y + cardHeight) {

if (card.matched) {
continue;
}

if (card.flipped) {
continue;
}

card.flipped = true;
flippedCards.push(card);

if (flippedCards.length === 2) {
checkMatch();
}
}
}
}

function checkMatch() {
let card1 = flippedCards[0];
let card2 = flippedCards[1];

if (card1.value === card2.value) {
card1.matched = true;
card2.matched = true;
flippedCards = [];
} else {
canClick = false;

setTimeout(function() {
card1.flipped = false;
card2.flipped = false;
flippedCards = [];
canClick = true;
}, 1000);
}
}

What You Learned

In this step, you:

  • ✅ Detected clicks on specific cards
  • ✅ Flipped cards face-up when clicked
  • ✅ Limited flipping to 2 cards at once
  • ✅ Checked if two cards match
  • ✅ Kept matched cards face-up
  • ✅ Flipped non-matching cards back with delay
  • ✅ Prevented clicking during flip-back delay
  • ✅ Added hover effects for better UX
  • ✅ Changed cursor to show clickable cards
  • ✅ Learned about setTimeout for delays

What's Next?

The game is playable! But we're missing some key features: tracking moves, detecting when the game is won, and ability to restart. Next, we'll add these finishing touches.

Coming up: Move counter, win detection, and restart!


Next Step: Step 3 - Complete Code: Win Detection and Polish