Midterm: MEMO game

Edit: I added a sound effect when flipping a card.

After some thinking, I decided to create a MEMO game for my midterm, a classic game with the goal to find matching pairs of cards. I though this would be a relatively easy way to incorporate different images into my project but it turned out more difficult than I expected as a lot of functionality is needed. This was challenging but thanks to support from Aaron, Jack, and various contributors to online forums, I managed to create a final product that I am happy with despite there being room for improvement and functionality I decided not to include in favor of healthy sleeping habits.

I first created a wireframe of the design I intended in Processing.
During the process, there were some scary moments.
I tested functionalities in different ways with print() functions and by placing simpler objects before placing colors for my cards.
First success: Images placed at the right locations.
All images added and placed.

When two clicked cards match, they remain face up and the player can try to find more matching pairs. They can also click the redo button which I incorporated into the title to return to the first screen and restart the game.

MEMO game screen recording

Intrigued? You can download the game here and play yourself.

Here is the code for the main tab:

//Game screens: 0: Initial Screen, 1: Game Screen

//for timer
int time;
int wait = 1000;

int gameScreen = 0;

PImage redo;
PImage abudhabi;
PImage berlin;
PImage clock;
PImage paris;
PImage people;
PImage stairs;
PImage tram;
PImage water;

Card[] cards;

IntList types; //to shuffle 16 cards of 8 different types
int showing; //to count how many cards are showing

int rows = 4;
int columns = 4;
int cardsize = 150;
int innerpadding = 15;
int outermargin = 20;
int cardstotal = (rows * columns);

void setup() {
  size(685, 855); //window size

  //load images
  redo = loadImage("redo.png");
  abudhabi = loadImage("abudhabi.jpg");
  berlin = loadImage("berlin.jpg");
  clock = loadImage("clock.jpg");
  paris = loadImage("paris.jpg");
  people = loadImage("people.jpg");
  stairs = loadImage("stairs.jpg");
  tram = loadImage("tram.jpg");
  water = loadImage("water.jpg");

  //add types of cards to IntList
  types = new IntList();
  types.append(0);
  types.append(0);
  types.append(1);
  types.append(1);
  types.append(2);
  types.append(2);
  types.append(3);
  types.append(3);
  types.append(4);
  types.append(4);
  types.append(5);
  types.append(5);
  types.append(6);
  types.append(6);
  types.append(7);
  types.append(7);
  types.shuffle();

  //create cards
  cards = new Card [16];

  int cardsinrow = 0;
  int cardsincolumn = 0;

  showing = 0; //keep track of how many cards are face up
  for (int i = 0; i < cards.length; i++) {
    cards[i] = new Card((outermargin + (cardsinrow * (cardsize + innerpadding))), (outermargin + (cardsincolumn * (cardsize + innerpadding))), types.get(i), i);
    cardsinrow++;
    if (cardsinrow >= columns) { // "Line break" for the cards
      cardsinrow = 0;
      cardsincolumn++;
    }
    //println(types.get(i));
  }
}

void draw() {
  if (gameScreen == 0) {
    initScreen(); //code in "initScreen" tab
  }

  if (gameScreen == 1) {
    //memoScreen(); //ideally simplify here and move code to "memoScreen" tab
    background(255);
    noStroke();
    //memo title
    pushMatrix();
    rectMode(CORNER);
    fill(0);
    rect(outermargin+innerpadding*0+cardsize*0, outermargin+innerpadding*0+cardsize*0, cardsize, cardsize);
    rect(outermargin+innerpadding*1+cardsize*1, outermargin+innerpadding*0+cardsize*0, cardsize, cardsize);
    rect(outermargin+innerpadding*2+cardsize*2, outermargin+innerpadding*0+cardsize*0, cardsize, cardsize);
    fill(255);
    triangle(20, 20, 170, 20, 95, 80);
    rect(outermargin+innerpadding+cardsize+60, outermargin+40, 150-60, 20);
    rect(outermargin+innerpadding+cardsize+60, outermargin+cardsize-40-20, 150-60, 20);
    triangle(20+cardsize*2+innerpadding*2, 20, 170+cardsize*2+innerpadding*2, 20, 95+cardsize*2+innerpadding*2, 80);
    ellipseMode(CORNER);
    fill(0);
    ellipse(width-outermargin-cardsize, 20, cardsize, cardsize);
    popMatrix();

    //memo cards
    pushMatrix();
    imageMode(CORNER);
    translate(0, outermargin+cardsize); //to place cards below title
    //to store type and id of card that is flipped
    int[] type = {-1, -1};
    int[] ids = {-1, -1};
    int index = 0;

    for (int i=0; i < cards.length; i++) {
      cards[i].display();

      if (cards[i].show && !cards[i].matched) {
        type[index] = cards[i].type;
        ids[index] = cards[i].id;
        index++;
        //println(cards[i].id+": "+cards[i].type); //functionality testing
      }
    }
    if (type[0] != -1 && type[1] != -1) {
      if (type[0]==type[1]) {
        cards[ids[0]].matched = true;
        cards[ids[1]].matched = true;
        showing = 0;
      } else {
        if (millis() - time >= wait) {
          println("time passed");//functionality testing
          cards[ids[0]].show = false;
          cards[ids[1]].show = false;
          time = millis();//update the stored time
        }
        showing = 0;
      }
    }
    popMatrix();

    //redo symbol up right. when clicked on memoScreen return to initScreen.
    imageMode(CENTER);
    image(redo, 594, 94, 75, 75);
    //when redo image clicked, return to initScreen and reset
    if (mouseX >= width-outermargin-cardsize && mouseX <= width-outermargin && mouseY >= outermargin && mouseY <= outermargin+cardsize) {
      ellipseMode(CORNER);
      fill(0, 0, 0, 50);
      ellipse(width-outermargin-cardsize, 20, cardsize, cardsize);
      if (mousePressed == true) {
        gameScreen = 0;
        types.shuffle();
        for (int i=0; i<cards.length; i++) {
          cards[i].type = types.get(i);
          cards[i].matched = false;
          cards[i].show = false;
        }
      } else {
        return;
      }
    }
  }
}

void mousePressed() {
  if (gameScreen == 1) {
    for ( int t=0; t < cards.length; t++) {
      cards[t].mouseClick();
      time = millis();
    }
  }
}

Card class:

public class Card {

  int card1id, card2id, card1type, card2type;

  int cardposx;
  int cardposy;
  int type; //
  int id;
  boolean show;
  boolean pickCard;
  boolean matched;

  Card(int cardposx_, int cardposy_, int type_, int id_) { //add images
    cardposx = cardposx_;
    cardposy = cardposy_;
    type = type_; //to later assign images to different types
    id = id_; //will use t for now
    show = false; //meaning card does not show the image
    pickCard = true; //meaning card is able to be picked
    matched = false; //meaning card has not matched with another card yet
  }

  void display() {
    // ln(cardposx, cardposy, type, id); //testing
    if (!pickCard) { //card cannot be picked
      return;
    }
    if (!show) { //display blue rect as backside if cards do not show
      fill(0, 0, 255, 190); //card color slightly transparent to become fully blue when mouse hovers over
      if ( mouseOverCard() && showing < 2) {
        fill(0, 0, 255, 255); //stronger blue w/o transparency when mouse hovers over card
      }
      rect(cardposx, cardposy, cardsize, cardsize);
    } else {
      if (type == 0) { //change id to type after testing
        image(abudhabi, cardposx, cardposy, cardsize, cardsize);
      } else if (type == 1) {
        image(berlin, cardposx, cardposy, cardsize, cardsize);
      } else if (type == 2) {
        image(clock, cardposx, cardposy, cardsize, cardsize);
      } else if (type == 3) {
        image(paris, cardposx, cardposy, cardsize, cardsize);
      } else if (type == 4) {
        image(people, cardposx, cardposy, cardsize, cardsize);
      } else if (type == 5) {
        image(stairs, cardposx, cardposy, cardsize, cardsize);
      } else if (type == 6) { 
        image(tram, cardposx, cardposy, cardsize, cardsize);
      } else if (type == 7) {
        image(water, cardposx, cardposy, cardsize, cardsize);
      }
    }
  }

  boolean mouseOverCard() { //to determine when mouse hovers over a card
    //println(cardposx, cardposy, type, id); //testing
    if ( !pickCard ) {
      return( false );
    }
    return( mouseX >= cardposx && mouseX < cardposx+cardsize && mouseY >= cardposy+outermargin+cardsize && mouseY < cardposy+cardsize+outermargin+cardsize ); //need to add +outermargin+cardsize to Y location to account for translation of location under title
  }
  void mouseClick() {
    //time = millis();//store the current time
    //println(time);
    if ( !pickCard ) {
      return;
    } //nothing happens if card cannot be clicked
    if ( mouseOverCard() && !show && showing < 2) { // no more than two cards can be picked. only cards that do not show image already can be clicked //add && showing < 2
      show = !show;
      showing++;
      //println(showing); //functionality testing
    }
    if ( showing == 16) { //change this code
      gameScreen = 2;
    }
  }
  boolean sameType() { //check whether picked cards match meaning they have the same type
    return true; //just for testing
  }
}

First screen:

void initScreen() {
  // code of initial screen
  pushMatrix();
  background(0);

  //memo title
  fill(255);
  rectMode(CORNER);
  rect(outermargin+innerpadding*0+cardsize*0, outermargin+innerpadding*0+cardsize*0, cardsize, cardsize);
  rect(outermargin+innerpadding*1+cardsize*1, outermargin+innerpadding*0+cardsize*0, cardsize, cardsize);
  rect(outermargin+innerpadding*2+cardsize*2, outermargin+innerpadding*0+cardsize*0, cardsize, cardsize);
  ellipseMode(CORNER);
  ellipse(width-outermargin-cardsize, 20, cardsize, cardsize);
  fill(0);
  triangle(20, 20, 170, 20, 95, 80);
  rect(outermargin+innerpadding+cardsize+60, outermargin+40, 150-60, 20);
  rect(outermargin+innerpadding+cardsize+60, outermargin+cardsize-40-20, 150-60, 20);
  triangle(20+cardsize*2+innerpadding*2, 20, 170+cardsize*2+innerpadding*2, 20, 95+cardsize*2+innerpadding*2, 80);

  //game instructions
  textAlign(CENTER);
  textSize(50);
  fill(255);
  text("Find the matching pairs.", width/2, height/2-100);

  //example rects & "Play" button
  rectMode(CENTER);
  fill(0, 0, 255, 190);
  rect(width/2+50, height/2, 50, 50);
  rect(width/2-50, height/2, 50, 50);
  if (gameScreen == 0 && mouseX >= 270 && mouseX <= 420 && mouseY >= 408 && mouseY <= 458) {
    fill(0, 0, 255);
    rect(width/2+50, height/2, 50, 50);
    rect(width/2-50, height/2, 50, 50);
    if (mousePressed == true) { //mouseX >= 270 && mouseX <= 420 && mouseY >= 408 && mouseY <= 458 &&
      gameScreen = 1;
    }
  }
  fill(255);
  textSize(30);
  text("PL", width/2-50, height/2+13);
  text("AY", width/2+50, height/2+13);
  fill(0, 0, 255);
  text("=", width/2, height/2+13);


  //game instructions continued
  fill(255);
  textSize(20);
  text("Click to flip a card. Click another card.", width/2, height/2+100);
  text("Do that again and again.", width/2, height/2+135);
  text("Eventually you'll find all pairs.", width/2, height/2+170);
  text("Inshallah.", width/2, height/2+205);
  text("Ready? Click P L  A Y.", width/2, height/2+275);
  popMatrix();
}

Find an overview of my media sources here.

 

Leave a Reply