Overview
The Library we use to make this game is P5.JS for website and Processing for dekstop as the platform.
Reference :
Desktop Version
If you want to run the game in the desktop (windows), you can download the game below
download linkNo Download Available!
We are very sorry, the game is still in production
Project Source
This game is open sourced in our github repo
Program Documentation
Lorem ipsum dolor, sit amet consectetur adipisicing elit. Vero, provident quos magnam fugiat possimus sint, est modi id voluptatem explicabo culpa numquam nulla. Nulla laboriosam natus ut voluptas delectus quis ducimus corporis, maiores assumenda officia?
change documentation
{ setup() function }
The setup we make using p5js :
function setup() {
getSize();
darkTheme();
const canvas = createCanvas(width, height, WEBGL);
canvas.parent(sketchContainer);
}
Note: to create a procedure, javascript use the function keyword even though it is not a function because javascript don't have the void keyword
Render.pde
Global Variable
The setup we make using processing :
String page = "MainMenu";
PFont pixelFont;
JSONObject json;
int xCenter;
int startTime, endTime;
setup()
The setup we make using processing :
void setup() {
reloadBestTime();
setupAllSound();
pixelFont = createFont("../font/PressStart2P-Regular.ttf", 50);
textFont(pixelFont);
size(1000, 800, P3D);
xCenter = width / 2;
smooth();
imageMode(CENTER);
themeSound.play();
}
clickToRenderPage()
The setup we make using processing :
void clickToRenderPage(String thePage, boolean changeTheme, float xPos, float yPos,
float objHalfWidth, float objHalfHeight) {
if ((mouseX <= xPos + objHalfWidth && mouseX >= xPos - objHalfWidth) && (mouseY <= yPos +
objHalfHeight && mouseY >= yPos - objHalfHeight)) {
page = thePage;
playTickSound();
textFont(pixelFont);
if (changeTheme) {
renderTheme();
}
}
}
mouseClicked()
The setup we make using processing :
void mouseClicked() {
switch(page) {
case "MainMenu":
mainMenuMouseClicked();
break;
case "SettingsPage":
settingsPageMouseClicked();
break;
case "PlayGame":
defaultCamera();
playGameMouseClicked();
break;
case "WinPage":
winGameMouseClicked();
break;
case "RecordPage":
recordPageMouseClicked();
break;
case "GameMode":
gameModeMouseClicked();
break;
default:
println("page does not exits");
}
}
setCamera()
The setup we make using processing :
void setCamera() {
camera(mouseX, mouseY,
(height/2.0) / tan(PI*30.0 / 180.0), width/2.0, height/2.0, 0, 0, 1, 0);
}
defaultCamera()
The setup we make using processing :
void defaultCamera() {
camera();
}
draw()
The setup we make using processing :
void draw() {
endTime = millis();
switch(page) {
case "MainMenu":
setCamera();
mainMenu();
break;
case "SettingsPage":
setCamera();
settingsPage();
break;
case "PlayGame":
defaultCamera();
playGame();
break;
case "GameMode":
setCamera();
gameMode();
break;
case "WinPage":
setCamera();
winGame();
break;
case "RecordPage":
setCamera();
recordPage();
break;
default:
println("page does not exits");
}
}
Main_Menu.pde
mainMenu()
The setup we make using processing :
void mainMenu() {
background(bgColor);
xCenter = width / 2;
displayText("MATCHUS", 50, 0, textColor, textColor, xCenter, 230);
rectMode(CENTER);
displayRect(xCenter, 270, textWidth("MATCHUS"), 10, textColor, -1);
cursor(ARROW);
displayText("PLAY", 35, 5, textColor, primaryHoverColor, xCenter, 320);
displayText("SETTINGS", 35, 5, textColor, primaryHoverColor, xCenter, 370);
displayText("BEST RECORD", 35, 5, textColor, primaryHoverColor, xCenter, 420);
displayText("EXIT", 35, 5, textColor, secondaryHoverColor, xCenter, 470);
}
clickToExitGame()
The setup we make using processing :
void clickToExitGame(float yPos) {
textSize(35);
float halfExitText = textWidth("EXIT") / 2;
if ((mouseX <= xCenter + halfExitText && mouseX >= xCenter - halfExitText) &&
(mouseY <= yPos + 15 && mouseY >= yPos - 15)) {
exit();
}
}
mainMenuMouseClicked()
The setup we make using processing :
void mainMenuMouseClicked() {
clickToRenderPage("GameMode", false, xCenter,
320, textWidth("Play") / 2, 15);
clickToRenderPage("SettingsPage", false, xCenter,
370, textWidth("Settings") / 2, 15);
clickToRenderPage("RecordPage", false, xCenter,
420, textWidth("Best Record") / 2, 15);
clickToExitGame(470);
}
Game_Mode.pde
Global Variable
The setup we make using processing :
String gameModeName;
int totalCardType;
float xPosStart, yPosStart, xRange, yRange, xLimit, yLimit, halfWidthRect, halfHeightRect;
gameMode()
The setup we make using processing :
void gameMode() {
background(bgColor);
displayText("Game Mode", 50, 0, textColor, textColor, xCenter, 230);
displayText("EASY", 35, 5, textColor, primaryHoverColor, xCenter, 300);
displayText("NORMAL", 35, 5, textColor, primaryHoverColor, xCenter, 350);
displayText("HARD", 35, 5, textColor, primaryHoverColor, xCenter, 400);
displayText("MENU", 35, 5, textColor, secondaryHoverColor, xCenter, 470);
}
gameModeMouseClicked()
The setup we make using processing :
void gameModeMouseClicked() {
setGameStart(300, "EASY");
setGameStart(350, "NORMAL");
setGameStart(400, "HARD");
clickToRenderPage("PlayGame", false, xCenter,
300, textWidth("EASY") / 2, 20);
clickToRenderPage("PlayGame", false, xCenter,
350, textWidth("NORMAL") / 2, 20);
clickToRenderPage("PlayGame", false, xCenter,
400, textWidth("HARD") / 2, 20);
clickToRenderPage("MainMenu", false, xCenter,
470, textWidth("MENU") / 2, 20);
}
setGameMode()
The setup we make using processing :
void setGameMode(int totalCard, float xPos, float yPos, float xRangeCenter, float yRangeCenter, float
xLimitRange, float yLimitRange, float halfWidth, float halfHeight) {
xPosStart = xPos;
yPosStart = yPos;
xRange = xRangeCenter;
yRange = yRangeCenter;
xLimit = xLimitRange;
yLimit = yLimitRange;
halfWidthRect = halfWidth;
halfHeightRect = halfHeight;
totalCardType = totalCard;
}
Settings.pde
settingsPage()
The setup we make using processing :
void settingsPage() {
background(bgColor);
displayText("Settings", 50, 0, textColor, textColor, xCenter, 230);
displayText("THEME:" + theme, 35, 5, textColor, primaryHoverColor, xCenter, 300);
displayText("BACK", 35, 5, textColor, secondaryHoverColor, xCenter, 350);
}
settingsPageMouseClicked()
The setup we make using processing :
void settingsPageMouseClicked() {
clickToRenderPage("SettingsPage", true, xCenter,
300, textWidth("Theme:" + theme) / 2, 15);
clickToRenderPage("MainMenu", false, xCenter,
360, textWidth("back") / 2, 15);
}
Record_Page.pde
Global Variable
The setup we make using processing :
String theme = "DARK";
int bgColor = #0d0401;
int textColor = #f2f2f2;
int primaryHoverColor = #87f49a;
int secondaryHoverColor = #fcba03;
darkTheme(), lightTheme(), colorfullTheme()
The setup we make using processing :
void darkTheme() {
theme = "DARK";
bgColor = 30;
textColor = #f2f2f2;
primaryHoverColor = #87f49a;
secondaryHoverColor = #fcba03;
}
void lightTheme() {
theme = "LIGHT";
bgColor = #d9d9d9;
textColor = #0d0401;
primaryHoverColor = #40c27a;
secondaryHoverColor = #fcba03;
}
void colorfullTheme() {
theme = "COLORFULL";
bgColor = #4195f0;
textColor = #542a91;
primaryHoverColor = #b2f007;
secondaryHoverColor = #e637c0;
}
renderTheme()
The setup we make using processing :
void renderTheme() {
switch(theme) {
case "DARK":
lightTheme();
break;
case "LIGHT":
colorfullTheme();
break;
case "COLORFULL":
darkTheme();
break;
default:
println("unknown color theme");
}
}
Play_Game.pde
Global Variable
The setup we make using processing :
boolean rotateHorizontal = true;
int win = 0;
boolean sameCard;
int rotateInterval = 10;
boolean[][] rotateStatus;
float[][] rectAngle;
int cardUp = -1;
PImage[] checkCard = new PImage[2];
int[] rowCard = new int[2];
int[] columnCard = new int[2];
boolean[][] isAbleToRotate;
boolean[][] cardFaceUp;
int[][] zPos;
playGame()
The setup we make using processing :
void playGame() {
background(bgColor);
waitToStartTheGame();
displayText("Match the card", 25, 0, textColor, textColor, xCenter, 75);
cursor(ARROW);
displayText("MENU", 25, 5, textColor, secondaryHoverColor, 75, 75);
rectMode(CENTER);
setRect();
waitToRotate();
waitAfterWin();
getTime();
}
shuffleCard()
The setup we make using processing :
void shuffleCard(PImage[][] array) {
int Cols = array.length;
int Rows = array[0].length;
for (int col = 0; col
for (int row = 0; row
int randC = int(random(Cols));
int randR = int(random(Rows));
PImage imgTemp = array[col][row];
array[col][row] = array[randC][randR];
array[randC][randR] = imgTemp;
}
}
}
setRect()
The setup we make using processing :
void setRect() {
int k = 0, l = 0;
for (float i = yPosStart; i <= yLimit; i = i + yRange) {
l = 0;
for (float j = xPosStart; j <= xLimit; j = j + xRange) {
pushMatrix();
translate(j, i, zPos[k][l]);
if (rotateHorizontal) {
rotateY(radians(rectAngle[k][l]));
} else {
rotateX(radians(rectAngle[k][l]));
}
rotateHorizontal = !rotateHorizontal;
image(cardIdentity[k][l], 0, 0, 80, 80);
translate(0, 0, -zPos[k][l]);
displayRect(0, 0, halfWidthRect * 2, halfHeightRect * 2, textColor, #ffffff);
popMatrix();
rotateRect(k, l);
l++;
}
k++;
}
}
rotateRect()
The setup we make using processing :
void rotateRect(int rowIndex, int columnIndex) {
if (rotateStatus[rowIndex][columnIndex]) {
rectAngle[rowIndex][columnIndex] += rotateInterval;
if (rectAngle[rowIndex][columnIndex] == 180 || rectAngle[rowIndex][columnIndex] == 0) {
rotateStatus[rowIndex][columnIndex] = false;
}
if (rectAngle[rowIndex][columnIndex] == 180 && !isAbleToRotate[rowIndex][columnIndex]) {
isAbleToRotate[rowIndex][columnIndex] = true;
cardUp = -1;
}
}
}
playGameMouseClicked()
The setup we make using processing :
void playGameMouseClicked() {
clickToRenderPage("MainMenu", false, 75,
75, textWidth("menu") / 2, 15);
int k = 0, l = 0;
for (float i = yPosStart; i <= yLimit; i = i + yRange) {
l = 0;
for (float j = xPosStart; j <= xLimit; j = j + xRange) {
clickToRotateRect(k, l, j, i, halfWidthRect, halfHeightRect);
l++;
}
k++;
}
}
resetCard()
The setup we make using processing :
void resetCard() {
for ( int i = 0; i < 2; i++) {
for (int j = 0; j < totalCardType; j++) {
rotateStatus[i][j] = false;
cardFaceUp[i][j] = false;
isAbleToRotate[i][j] = true;
}
}
}
setAllCardStatus()
The setup we make using processing :
void setAllCardStatus() {
rotateStatus = new boolean[2][totalCardType];
rectAngle = new float[2][totalCardType];
isAbleToRotate = new boolean[2][totalCardType];
cardFaceUp = new boolean[2][totalCardType];
zPos = new int[2][totalCardType];
for (int i = 0; i < 2; i++) {
for (int j = 0; j < totalCardType; j++) {
rotateStatus[i][j] = false;
rectAngle[i][j] = 0;
zPos[i][j] = 1;
isAbleToRotate[i][j] = false;
cardFaceUp[i][j] = true;
}
}
}
setCard()
The setup we make using processing :
void setCard() {
for (int i = 0; i < 2; i++) {
for (int j = 0; j < totalCardType; j++) {
//bgCard[i][j] = #f2f2f2;
rotateStatus[i][j] = true;
isAbleToRotate[i][j] = true;
cardFaceUp[i][j] = false;
}
}
}
changeRotateDirection()
The setup we make using processing :
void changeRotateDirection(int rowIndex, int columnIndex) {
if (rectAngle[rowIndex][columnIndex] == 0) {
rotateInterval = 10;
} else {
rotateInterval = -10;
}
}
clickToRotateRect()
The setup we make using processing :
void clickToRotateRect(int rowIndex, int columnIndex, float xPos, float yPos,
float objHalfWidth, float objHalfHeight) {
if ((mouseX <= xPos + objHalfWidth && mouseX >= xPos - objHalfWidth) &&
(mouseY <= yPos + objHalfHeight && mouseY >= yPos - objHalfHeight)) {
if (!rotateStatus[rowIndex][columnIndex] && isAbleToRotate[rowIndex][columnIndex] && cardUp != 1) {
changeRotateDirection(rowIndex, columnIndex);
rotateStatus[rowIndex][columnIndex] = true;
playFlipSound();
}
if (!cardFaceUp[rowIndex][columnIndex] && isAbleToRotate[rowIndex][columnIndex] && cardUp != 1) {
isAbleToRotate[rowIndex][columnIndex] = false;
cardFaceUp[rowIndex][columnIndex] = true;
cardUp++;
checkCard[cardUp] = cardIdentity[rowIndex][columnIndex];
rowCard[cardUp] = rowIndex;
columnCard[cardUp] = columnIndex;
if (cardUp == 1) {
if (checkCard[0] == checkCard[1]) {
for (int i = 0; i < 2; i++) {
isAbleToRotate[rowCard[i]][columnCard[i]] = false;
}
cardUp = -1;
win++;
sameCard = true;
} else {
startTimeToRotate = millis();
sameCard = false;
}
}
}
if (win == totalCardType) {
startTimeToRotate = millis();
setBestTime();
playWinSound();
if (modeBT.equals("NONE") ||
int(split(timeText, ':')[0]) < int(split(modeBT, ':')[0]) ||
int(split(timeText, ':')[1]) < int(split(modeBT, ':')[1])) {
setSaveJSONBT();
newRecord = true;
} else {
newRecord = false;
}
}
}
}
Win_Game.pde
Global Variable
The setup we make using processing :
boolean newRecord;
winGame()
The setup we make using processing :
void winGame() {
background(bgColor);
displayText("Congratulation", 50, 0, textColor, textColor, xCenter, 150);
displayText("On winning", 50, 0, textColor, textColor, xCenter, 200);
displayRecordTime();
displayText("PLAY AGAIN", 35, 5, textColor, primaryHoverColor, xCenter, 330);
displayText("MENU", 35, 5, textColor, primaryHoverColor, xCenter, 380);
}
displayRecordTime()
The setup we make using processing :
void displayRecordTime() {
if (newRecord) {
displayText("NEW RECORD TIME - " + timeText, 25, 0, textColor, textColor, xCenter, 265);
} else {
displayText("TIME - " + timeText, 25, 0, textColor, textColor, xCenter, 265);
displayText("BEST TIME - " + modeBT, 25, 0, textColor, textColor, xCenter, 450);
}
}
winGameMouseClicked()
The setup we make using processing :
void winGameMouseClicked() {
setGameStart(330, gameModeName);
clickToRenderPage("PlayGame", false, xCenter,
330, textWidth("PLAY AGAIN") / 2, 20);
clickToRenderPage("MainMenu", false, xCenter,
380, textWidth("MENU") / 2, 20);
}
Connect_JSON.pde
Global Variable
The setup we make using processing :
String modeBT;
setBestTime()
The setup we make using processing :
void setBestTime() {
switch(gameModeName) {
case "EASY":
modeBT = easyBT;
break;
case "NORMAL":
modeBT = normalBT;
break;
case "HARD":
modeBT = hardBT;
break;
default:
println("Unknown Game Mode");
}
}
setSaveJSONBT()
The setup we make using processing :
void setSaveJSONBT() {
json = new JSONObject();
switch(gameModeName) {
case "EASY":
json.setString("easy", timeText);
json.setString("normal", normalBT);
json.setString("hard", hardBT);
break;
case "NORMAL":
json.setString("easy", easyBT);
json.setString("normal", timeText);
json.setString("hard", hardBT);
break;
case "HARD":
json.setString("easy", easyBT);
json.setString("normal", normalBT);
json.setString("hard", timeText);
break;
default:
println("Unknown Game Mode");
}
saveJSONObject(json, "../data/record.json");
}
Record_Page.pde
recordPage()
The setup we make using processing :
void recordPage() {
background(bgColor);
displayText("Best Record", 50, 0, textColor, textColor, xCenter, 230);
reloadBestTime();
displayText("EASY - " + easyBT, 30, 0, textColor, textColor, xCenter, 300);
displayText("NORMAL - " + normalBT, 30, 0, textColor, textColor, xCenter, 350);
displayText("HARD - " + hardBT, 30, 0, textColor, textColor, xCenter, 400);
displayText("MENU", 35, 5, textColor, secondaryHoverColor, xCenter, 460);
}
recordPageMouseClicked()
The setup we make using processing :
void recordPageMouseClicked() {
clickToRenderPage("MainMenu", false, xCenter,
480, textWidth("menu") / 2, 20);
}
Time_Setting.pde
Global Variable
The setup we make using processing :
String easyBT, normalBT, hardBT;
int startTimeToRotate;
int minuteTime = 0;
int secondTime = 0;
String timeText;
boolean gameStart;
reloadBestTime()
The setup we make using processing :
void reloadBestTime() {
json = loadJSONObject("../data/record.json");
easyBT = json.getString("easy");
normalBT = json.getString("normal");
hardBT = json.getString("hard");
}
setGameStart()
The setup we make using processing :
void setGameStart(float yPos, String gameMode) {
textSize(35);
float halfExitText = textWidth("Play") / 2;
if ((mouseX <= xCenter + halfExitText && mouseX >= xCenter - halfExitText) &&
(mouseY <= yPos + 20 && mouseY >= yPos - 20)) {
startTime = millis();
minuteTime = 0;
secondTime = 0;
gameModeName = gameMode;
gameType();
loadAssets();
shuffleCard(cardIdentity);
setAllCardStatus();
gameStart = true;
rotateInterval = 10;
}
}
gameType()
The setup we make using processing :
void gameType() {
switch(gameModeName) {
case "EASY":
setGameMode(4, 140, 300, 240, 290, 860, 590, 100, 125);
break;
case "NORMAL":
setGameMode(5, 108, 300, 196, 246, 892, 546, 88, 113);
break;
case "HARD":
setGameMode(6, 87.5, 300, 165, 215, 912.5, 515, 77.5, 102.5);
break;
default:
println("Unknown Game Mode");
}
}
timeTicking(), timeWaitToRotateTicking()
The setup we make using processing :
int timeTicking() {
return endTime - startTime;
}
int timeWaitToRotateTicking() {
return endTime - startTimeToRotate;
}
getTime()
The setup we make using processing :
void getTime() {
int interval = 1000;
if (timeTicking() >= interval) {
secondTime++;
startTime = millis();
}
if (secondTime >= 59) {
minuteTime++;
secondTime = 0;
}
timeText = minuteTime + ":" + secondTime;
displayText(timeText, 25, 0, textColor, textColor, 915, 75);
}
waitToStartTheGame(), waitToRotate(), waitAfterWin()
The setup we make using processing :
void waitToStartTheGame() {
int interval = 3;
if (secondTime >= interval && gameStart) {
setCard();
gameStart = false;
}
}
void waitToRotate() {
int interval = 700;
if (timeWaitToRotateTicking() >= interval && cardUp == 1 && !sameCard) {
for (int i = 0; i < 2; i++) {
changeRotateDirection(rowCard[i], columnCard[i]);
rotateStatus[rowCard[i]][columnCard[i]] = true;
cardFaceUp[rowCard[i]][columnCard[i]] = false;
}
sameCard = true;
}
}
void waitAfterWin() {
int interval = 500;
if (win == totalCardType && timeWaitToRotateTicking() >= interval) {
resetCard();
page = "WinPage";
win = 0;
}
}
display_rect.pde
displayRect()
The setup we make using processing :
void displayRect(float xPos, float yPos, float widthSize, float heightSize,
int colorFill, int colorStroke) {
noFill();
noStroke();
if (colorFill != -1) {
fill(colorFill);
}
if (colorStroke != -1) {
stroke(colorStroke);
}
rect(xPos, yPos, widthSize, heightSize);
}
display_text.pde
displayText()
The setup we make using processing :
void displayText(String text, int textSize, int scaleSize, int regularColor,
int colorOnHover, float xPos, float yPos) {
textAlign(CENTER, CENTER);
textSize(textSize);
float halfTextWidth = textWidth(text) / 2;
float halfTextHeight = textSize / 2;
fill(regularColor);
if ((mouseX <= xPos + halfTextWidth &&
mouseX >= xPos - halfTextWidth) &&
(mouseY <= yPos + halfTextHeight && mouseY >= yPos - halfTextHeight)) {
textSize(textSize + scaleSize);
if (scaleSize != 0) {
cursor(HAND);
}
fill(colorOnHover);
}
text(text, xPos, yPos);
}
Play_Sound.pde
Global Variable
The setup we make using processing :
import processing.sound.*;
SoundFile tick, winSound, flip, themeSound;
setupAllSound()
The setup we make using processing :
void setupAllSound() {
tick = new SoundFile(this, "../sound/tick.mp3");
winSound = new SoundFile(this, "../sound/win.mp3");
flip = new SoundFile(this, "../sound/flip.mp3");
themeSound = new SoundFile(this, "../sound/themeSound.mp3");
}
playFlipSound(), playTickSound(), playWinSound()
The setup we make using processing :
void playFlipSound() {
flip.play();
}
void playTickSound() {
tick.play();
}
void playWinSound() {
winSound.play();
}