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.