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.





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.
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.