We are about to switch to a new forum software. Until then we have removed the registration on this forum.
I was reminded of this game style and decided to make (most of) one.
I am adding it here hoping it will be useful or entertaining to someone.
Currently has no goal. Left click to remove 3 or more of same color and shape.
Right click to remove a single shape. Doing so will reduce your score by the greater of 10 points or 10 percent.
Edit* There is a flaw in this example. I think I will leave it in as a "homework trap".
int N = 11; // number of tiles in each direction
// tilesX, tilesY, left, top, boardwidth, boardheight
Board game = new Board(N,N, 80,40, 640,480);
float SCORE_COST = 0.1;
int MIN_COST = 10;
int MINIMUM_CONNECTED = 3;
int[][] blocks = new int[N][N];
int[][] grabmap = new int[N][N];
TileStack stax = new TileStack(N*N);
void setup()
{
size(800, 600, P2D);
noSmooth();
ellipseMode(CORNER);
noFill();
noStroke();
frameRate(60);
}
void mouseClicked()
{
boolean isright = (mouseButton == RIGHT);
game.click(mouseX, mouseY, isright);
}
void draw()
{
background(0);
game.update();
game.display(mouseX, mouseY);
fill(255);
text("Score: " + game.getScore(), 10, height - 30);
}
class Tile
{
final int RECTANGLE = 0;
final int CIRCLE = 1;
final int BROKEN = 2;
// Choose the number of color and shape combinations for balance
// Too many and it is more difficult to find matching sets (possibly add more tiles)
// Too few, and there is no challenge
int[] COLS = {
color(0, 255, 0),
color(0, 0, 255),
color(255, 0, 0),
color(255, 255, 0)
};
int[] DRAW_SHAPES = { RECTANGLE, CIRCLE };
float TICK_STEP = 0.1;
int col = 0;
int type = 0;
float size = 1.0;
boolean isempty = true;
boolean isbroken = true;
Tile()
{
int t = (int)random(DRAW_SHAPES.length);
this.type = DRAW_SHAPES[t];
t = (int)random(COLS.length);
this.col = COLS[t];
this.isempty = false;
this.isbroken = false;
this.size = 1.0;
}
void tick()
{
this.size -= TICK_STEP;
if (this.size < TICK_STEP) { this.isempty = true; }
}
void display(int locx, int locy, int stepx, int stepy)
{
if (this.isBroken()) { this.tick(); }
fill(this.col);
switch(this.type)
{
case CIRCLE:
ellipse(locx, locy, stepx*this.size, stepy*this.size);
break;
case RECTANGLE:
rect(locx, locy, stepx*this.size, stepy*this.size);
break;
default:
break;
}
}
int getType() { return this.type; }
int getCol() { return this.col; }
// begin animation
void crack() { this.isbroken = true; }
boolean isBroken() { return this.isbroken; }
boolean isEmpty() { return this.isempty; }
boolean compare(Tile b) {
return (b.getType() == this.getType()) &&
(b.getCol() == this.getCol());
}
}
class Board
{
int w = 1;
int h = 1;
int top = 0;
int left = 0;
int bw = 640;
int bh = 480;
int score = 0;
Tile[][] parts;
// Recursion
// https://en.wikipedia.org/wiki/Flood_fill
void tileSelection(int x, int y, Tile ref)
{
if (x >= N) { return; } // boundary check
if (x < 0) { return; }
if (y >= N) { return; }
if (y < 0) { return; }
if (grabmap[x][y] != 0) { return; } // already checked?
if ( !parts[x][y].compare(ref) ) { return; } // match?
stax.push( parts[x][y] );
grabmap[x][y] = 1;
tileSelection(x+1, y, ref); // continue by checking neighbors
tileSelection(x-1, y, ref);
tileSelection(x, y+1, ref);
tileSelection(x, y-1, ref);
}
Board(int wid, int hi, int L, int T, int szx, int szy)
{
this.top = T;
this.left = L;
this.bw = szx;
this.bh = szy;
this.w = wid;
this.h = hi;
this.score = 0;
parts = new Tile[wid][hi];
for (int x=0; x<this.w; x++)
{
for (int y=0; y<this.h; y++)
{
parts[x][y] = new Tile();
}
}
}
int getScore() { return this.score; }
void update()
{
// Start from bottom up and fill in as needed
for (int y=(this.h-1); y>=0; y--)
{
for (int x=0; x<this.w; x++)
{
if (0 != y)
{
if (parts[x][y].isEmpty())
{
Tile t = parts[x][y];
parts[x][y] = parts[x][y-1];
parts[x][y-1] = t;
}
}
else
{
if (parts[x][y].isEmpty())
{
parts[x][y] = new Tile();
}
}
}
}
}
void click(int x, int y, boolean forceShatter)
{
// get x,y indecies
// remove adjacent blocks if possible
int stepx = this.bw / this.w;
int stepy = this.bh / this.h;
int localx = (x - this.left) / stepx;
int localy = (y - this.top) / stepy;
boolean inbounds = true;
if ((localx >= this.w) || (localx < 0))
{
inbounds = false;
}
if ((localy >= this.h) || (localy < 0))
{
inbounds = false;
}
if (inbounds)
{
if (!forceShatter)
{
stroke(0, 255, 0);
noFill();
stax.clear();
for (int gx=0; gx<N; gx++)
{
for (int gy=0; gy<N; gy++)
{
grabmap[gx][gy] = 0;
}
}
tileSelection(localx, localy, parts[localx][localy]);
if (stax.pileSize() >= MINIMUM_CONNECTED)
{
this.score += stax.pileSize();
while (stax.hasContent())
{
stax.pop().crack();
}
}
}
else
{
parts[localx][localy].crack();
int docked = (int)(this.score * SCORE_COST);
this.score -= max(docked, 10);
if (this.score < 0)
{
this.score = 0;
// here would be a good place to end the game
}
}
}
}
void display(int mx, int my)
{
noStroke();
int stepx = this.bw / this.w;
int stepy = this.bh / this.h;
for (int x=0; x<this.w; x++)
{
for (int y=0; y<this.h; y++)
{
Tile selected = parts[x][y];
if (!selected.isEmpty())
{
int locx = this.left + x*stepx;
int locy = this.top + y*stepy;
selected.display(locx, locy, stepx, stepy);
}
}
}
int localx = (mx - this.left) / stepx;
int localy = (my - this.top) / stepy;
boolean inbounds = true;
if ((localx >= this.w) || (localx < 0))
{
inbounds = false;
}
if ((localy >= this.h) || (localy < 0))
{
inbounds = false;
}
if (inbounds)
{
stroke(255);
noFill();
rect(localx*stepx+this.left, localy*stepy+this.top, stepx, stepy);
}
}
}
class TileStack
{
Tile[] st;
int pointer = 0;
TileStack(int size)
{
this.st = new Tile[size];
}
void clear()
{
this.pointer = 0;
this.st[this.pointer] = null;
}
boolean hasContent()
{
return this.pointer > 0;
}
int pileSize()
{
if (this.pointer > 0)
{
return this.pointer;
}
else
{
return 0;
}
}
boolean push(Tile t)
{
if (this.pointer < this.st.length)
{
st[this.pointer] = t;
this.pointer++;
return true;
}
else
{
return false;
}
}
Tile pop()
{
this.pointer--;
if (this.pointer < 0)
{
this.pointer = -1;
return null;
}
else
{
return this.st[this.pointer];
}
}
}
Comments
Very cool example -- thank you for sharing this!