Crumple a Collapse-like game framework

edited December 6 in Share Your Work

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.

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

Sign In or Register to comment.