Description:
For my midterm project, I made a mutant version of Match Three game, including elements of Tetris. The patterns of the cubes come from Mahjong, a traditional Chinese tile-based chess game. The player needs to match 3 or more cubes with the same patterns and make combos to eliminate the cubes, and the goal is to eliminate the highlighted cube. I designed a help window, so when you press “H”, you can see some of the combos allowed in this game. There are some more kinds of combos, and I intend to leave the part for the player to figure out.
In the game, the player uses “s”, “a”, “d” to move the cube, and can press “enter” to start, “p” to pause the game, “h” for help and “esc” to exit the game. The players can press “enter” to restart the game once they clear or they lose the game.
I inserted some sound effects, like when the cube falls into the place or the player successfully makes a combo, also when the player wins or loses the game.
There is also one more mechanism that as the score accumulates, the cubes will fall down at a higher speed.
Mahjong
Challenges:
It is challenging to design the mechanism for the game and also to figure out the logic part of the codes. During this week, I focused on the UI of the game. And using images instead of colors is harder than I thought. I had to modify pretty many codes to replace all the colors with images.
And for the sound part, I had to pay attention to where to put the sound.play code so that it won’t loop over and over. It also takes me a lot of time designing the UI and locating the texts and images.
But the whole process of making the game from scratch is interesting. I learned a lot from this midterm assignment!
import processing.sound.*; SoundFile same_sound; SoundFile win_sound; SoundFile fit_sound; SoundFile over_sound; int w = 10; int h = 10; int q = 40; int dt; int currentTime; PImage[] cubes; PImage bg; PImage bg1; PImage win; PImage over; Grid grid; Piece piece; Piece nextPiece; Pieces pieces; Score score; int level = 1; int nbLines = 0; int txtSize = 20; int textColor = 0; int x1; Boolean gameOver = false; Boolean gameOn = false; Boolean gamePause = false; Boolean gameWin = false; Boolean help = false; void setup() { size(600, 480, P2D); textSize(30); cubes = new PImage[4]; cubes[0] = loadImage("dong.png"); cubes[1] = loadImage("nan.png"); cubes[2] = loadImage("xi.png"); cubes[3] = loadImage("bei.png"); bg = loadImage("bg.png"); bg1 = loadImage("bg1.png"); win = loadImage("win.jpeg"); over = loadImage("over.png"); same_sound = new SoundFile(this, "same.wav"); win_sound = new SoundFile(this, "win.wav"); fit_sound = new SoundFile(this, "fit.wav"); over_sound = new SoundFile(this, "over.wav"); } void initialize() { nbLines = 0; dt = 1000; currentTime = millis(); score = new Score(); grid = new Grid(); pieces = new Pieces(); piece = new Piece(-2); nextPiece = new Piece(-2); grid.generate(); score = new Score(); level = 1; x1 = int(random(0, w)); while (grid.isFree(x1,h-1)) { x1 = int(random(0, w)); } } void draw() { background(0); image(bg, 0, 0, 600, 480); if (grid != null) { grid.drawGrid(); int now = millis(); if (gameOn) { if (now - currentTime > dt) { currentTime = now; piece.oneStepDown(); } } fill(#F8FC08); PFont f = createFont("", 20); textFont(f); text("P - PAUSE", 30, 360); text("H - HELP", 30, 400); PFont pixel = createFont("ARCADECLASSIC.TTF", 40); textFont(pixel); piece.display(false); score.display(); fill(#DE9F16); text("Goal: ", 30, 200); if (grid.cells[x1][h-1] != -1) { image(cubes[grid.cells[x1][h-1]], 50, 220, 40, 40); } stroke(#F8FC08); strokeWeight(5); line(50, 220, 90, 220); line(50, 220, 50, 260); line(90, 220, 90, 260); line(50, 260, 90, 260); } if (gameOver) { noStroke(); background(255); fill(0); over.resize(600, 200); image(over, 0, 100); text("Press 'ENTER' to restart.", 50, 400); over_sound.play(); noLoop(); } if (!gameOn) { image(bg1, 0, 0, 600, 480); noStroke(); fill(#A04C13); PFont f = createFont("", 30); textFont(f); text("MOVE LEFT", 70, 140); text("MOVE RIGHT", 70, 180); text("MOVE DOWN", 70, 220); text("PAUSE", 70, 260); text("HELP", 465, 390); text("EXIT", 500, 430); PFont pixel = createFont("ARCADECLASSIC.TTF", 40); textFont(pixel); text("A", 25, 140); text("D", 25, 180); text("S", 25, 220); text("P", 25, 260); text("H", 420, 390); text("ESC", 420, 430); fill(0); stroke(0); strokeWeight(3); line(20, 140, 50, 140); line(20, 115, 50, 115); line(20, 115, 20, 140); line(50, 115, 50, 140); line(20, 155, 50, 155); line(20, 180, 50, 180); line(20, 155, 20, 180); line(50, 155, 50, 180); line(20, 195, 50, 195); line(20, 220, 50, 220); line(20, 195, 20, 220); line(50, 195, 50, 220); line(20, 235, 50, 235); line(20, 260, 50, 260); line(20, 235, 20, 260); line(50, 235, 50, 260); line(415, 390, 445, 390); line(415, 365, 445, 365); line(415, 365, 415, 390); line(445, 365, 445, 390); line(415, 430, 490, 430); line(415, 405, 490, 405); line(415, 405, 415, 430); line(490, 405, 490, 430); } if (gameWin) { noStroke(); image(win, 0, 0, 600, 480); fill(#E36917); text("Press 'ENTER' to restart.", 30, 460); win_sound.amp(0.5); win_sound.play(); noLoop(); } if (help) { fill(#EAD0E4); rect(0, 0, 600, 480); fill(#E36917); PFont f = createFont("", 30); textFont(f); text("Match 3 or more cubes with", 40, 100); text("the same pattern to eliminate.", 40, 140); image(cubes[3], 60, 180, 50, 50); image(cubes[3], 110, 180, 50, 50); image(cubes[3], 160, 180, 50, 50); image(cubes[2], 280, 180, 50, 50); image(cubes[2], 330, 180, 50, 50); image(cubes[2], 380, 180, 50, 50); image(cubes[2], 430, 180, 50, 50); image(cubes[2], 480, 180, 50, 50); image(cubes[1], 110, 250, 50, 50); image(cubes[1], 110, 300, 50, 50); image(cubes[1], 110, 350, 50, 50); image(cubes[0], 280, 250, 50, 50); image(cubes[0], 330, 250, 50, 50); image(cubes[0], 380, 250, 50, 50); image(cubes[0], 380, 300, 50, 50); image(cubes[0], 380, 350, 50, 50); } } void goToNextPiece() { piece = new Piece(nextPiece.kind); nextPiece = new Piece(-2); } void goToNextLevel() { level = 1 + int(nbLines / 100); dt *= .98; } void keyPressed() { if (gameOn) { piece.inputKey(keyCode); } if (keyCode == 80) { if (gameOn) { gamePause = !gamePause; if (gamePause) { fill(0, 60); rect(width/2 - 220, height/2 - 50, 450, 100, 20); fill(255); textSize(25); PFont f = createFont("", 30); textFont(f); stroke(0); text("Press 'P' to restart the game", width/2 - 200, height/2); noLoop(); } else if (!gamePause){ loop(); } } } else if (key == ENTER) { if (!gameOn || gameWin || gameOver) { initialize(); gameWin = false; gameOver = false; gameOn = true; loop(); } } if (keyCode == 72) { help = !help; if (gameOn) { gamePause = true; } } }
class Piece { final int[] colors = {0, 1, 2, 3}; final int[][] pos; int x = int(w/2); int y = 0; int kind; int c; Piece(int k) { if (k == -2) { kind = int(random(0, 4)); } else { kind = k; } c = colors[kind]; pos = pieces.pos[kind]; } void display(Boolean still) { stroke(250); //fill(c); pushMatrix(); if (!still) { translate(160, 40); translate(x*q, y*q); } image(cubes[c], pos[0][0] * q, pos[0][1] * q, 40, 40); popMatrix(); } // returns true if the piece can go one step down void oneStepDown() { y += 1; if(!grid.pieceFits()){ piece.y -= 1; grid.addPieceToGrid(); } } // try to go one step left void oneStepLeft() { x --; } // try to go one step right void oneStepRight() { x ++; } void goToBottom() { grid.setToBottom(); } void inputKey(int k) { switch(k) { case 65: x --; if(grid.pieceFits()){ }else { x++; } break; case 68: x ++; if(grid.pieceFits()){ }else{ x--; } break; case 83: oneStepDown(); break; } } } class Pieces { int[][][] pos = new int [4][1][2]; Pieces() { pos[0][0][0] = -1; pos[0][0][1] = 0; pos[1][0][0] = -1; pos[1][0][1] = 0; pos[2][0][0] = -1; pos[2][0][1] = 0; pos[3][0][0] = -1; pos[3][0][1] = 0; } }
class Grid { int [][] cells = new int[w][h]; Grid() { for (int i = 0; i < w; i ++) { for (int j = 0; j < h; j ++) { cells[i][j] = -1; } } } Boolean isFree(int x, int y) { if (x > -1 && x < w && y > -1 && y < h) { return cells[x][y] == -1; } else if (y < 0) { return true; } return false; } Boolean pieceFits() { int x = piece.x; int y = piece.y; int[][] pos = piece.pos; Boolean pieceOneStepDownOk = true; int tmpx = pos[0][0]+x; int tmpy = pos[0][1]+y; if (tmpy >= h || !isFree(tmpx, tmpy)) { fit_sound.play(); pieceOneStepDownOk = false; } return pieceOneStepDownOk; } void addPieceToGrid() { int x = piece.x; int y = piece.y; int[][] pos = piece.pos; if(pos[0][1]+y >= 0){ cells[pos[0][0]+x][pos[0][1]+y] = piece.c; }else{ gameOn = false; gameOver = true; return; } checkSame(); goToNextPiece(); checkSame(); drawGrid(); checkGame(); } void checkSame() { int nb = 0; int mark_x = 0; int mark_y = 0; boolean same = false; // check horizontal for (int j = 0; j < h; j ++) { for (int i = 0; i < w; i++) { int c1 = cells[i][j]; if ((i+2 < w) && (!isFree(i, j))) { if ((cells[i+1][j] == c1) && (cells[i+2][j] == c1)) { int mark_num = 2; mark_x = i; mark_y = j; if ((i+3 < w) && (cells[i+3][j] == c1)) { mark_num = 3; } if ((i+4 < w) && (cells[i+4][j] == c1)) { mark_num = 4; } nb = nb + mark_num - 1; same_sound.play(); for (int k = mark_y; k > 0; k--) { for (int m = mark_x; m <= mark_x + mark_num; m++) { if (m==x1 && j == h-1) { cells[x1][h-1] = -1; checkGame(); } if ((j+2 < h) && (cells[m][j+1] == c1) && (cells[m][j+2] == c1)) { cells[m][j+1] = cells[m][j]; cells[m][j+2] = cells[m][j+1]; checkGame(); } cells[m][k] = cells[m][k-1]; cells[m][0] = -1; checkGame(); continue; } } } } if ((j+2 < h) && (!isFree(i, j))) { if ((cells[i][j+1] == c1) && (cells[i][j+2] == c1)) { same = true; mark_x = i; mark_y = j; if (same) { nb++; same_sound.play(); for (int k = mark_y; k < mark_y + 3; k++) { try{ cells[mark_x][k] = -1; checkGame(); } catch(Exception e) { } } for (int k = mark_y; k > 2; k--){ cells[mark_x][k] = cells[mark_x][k-2]; checkGame(); } } } } } } checkGame(); deleteLines(nb); } Boolean checkWin() { if (isFree(x1, h-1)) { return true; } return false; } void deleteLines(int nb) { nbLines += nb; if (int(score.points / 100) > level) { goToNextLevel(); } score.addLinePoints(nb); } void setToBottom() { int j = 0; for (j = 0; j < h; j ++) { if (!pieceFits()) { break; } else { piece.y++; } } piece.y--; delay(1500); addPieceToGrid(); } void drawGrid() { stroke(150); pushMatrix(); translate(160, 40); line(-10, 0, -10, h*q); for (int i = 0; i < w; i ++) { for (int j = 0; j < h; j ++) { if (cells[i][j] != -1) { image(cubes[cells[i][j]], i * q, j * q, 40, 40); } } } pick(x1, h-1); popMatrix(); } void generate() { for (int i = 0; i < w; i ++) { for (int j = h * 1/2 ; j < h ; j ++) { int kind = int(random(0, 4)); cells[i][j] = kind; image(cubes[kind], i * q, j * q, 40, 40); } checkSame(); } checkSame(); } void checkGame() { for (int i=0; i<w; i++) { if (!isFree(i, 0)) { gameOver = true; } for (int j=0; j<h; j++) { if (!isFree(i, j) && isFree(i,j+1)) { cells[i][j] = -1; } } } if (checkWin()) { gameWin = true; } } void pick(int x, int y) { stroke(#F8FC08); strokeWeight(5); if (cells[x][y] != -1) { image(cubes[cells[x][y]], x * q, y * q, 40, 40); } line(x*q, y*q, (x+1)*q, y*q); line(x*q, y*q, x*q, (y+1)*q); line((x+1)*q, y*q, (x+1)*q, (y+1)*q); line(x*q, (y+1)*q, (x+1)*q, (y+1)*q); } }
class Score { int points = 0; void addLinePoints(int nb) { points += level * nb * 10; } void display() { pushMatrix(); translate(30, 60); //score fill(#DE9F16); text("score: ", 0, 0); fill(230, 230, 12); text(""+formatPoint(points), 0, txtSize + 10); popMatrix(); } String formatPoint(int p) { String txt = ""; int qq = int(p/1000000); if (qq > 0) { txt += qq + ","; p -= qq * 1000000; } qq = int(p/1000); if (txt != "") { if (qq == 0) { txt += "000"; } else if (qq < 10) { txt += "00"; } else if (qq < 100) { txt += "0"; } } if (qq > 0) { txt += qq; p -= qq * 1000; } if (txt != "") { txt += ","; } if (txt != "") { if (p == 0) { txt += "000"; } else if (p < 10) { txt += "00" + p; } else if (p < 100) { txt += "0" + p; } else { txt += p; } } else { txt += p; } return txt; } }