02Credits Blog

Day35 - Implement Block Grid

Built the initial grid and block modules for Tetris Attack

2019-03-13

Project Page

Todo

Today I built the initial version of the block grid for my Tetris Attack remake. I did somethings a little weird in order to simplify what I think I will need later on.

Size

I would like my game to run on as many devices as possible. To that end I have decided to make everything to the best of my ability size independent. This means the graphics should look good at really low resolution, and really high resolution and at any aspect ration. The first step to this goal is to paramaterize the grid rendering with the required grid width and height.

I calculate the grid size by picking a percentage margin and multiplying that percentage by both the height and width and taking the larger.

  function calculateGridPosition() {
  let margin = Math.max(width * startingMargin, height * startingMargin);
  let maxWidth = width - margin;
  let maxHeight = height - margin;

I then try both axes to see which orientation the grid will fit in. Obviously I could do this a smarter way, but trial and error with two options is very reasonable and to be honest I'm too tired to do better.

  // Try Horizontal
  let gridWidth = maxWidth;
  let gridHeight = gridWidth * 2;
  if (gridHeight > maxHeight) {
    // Fallback to vertical
    gridHeight = maxHeight;
    gridWidth = gridHeight / 2;
  }

Once I arrive at the correct dimensions, I calculate some useful positioning values and return them as an object.

  let gridLeft = (width - gridWidth) / 2;
  let gridTop = (height - gridHeight) / 2 + gridHeight;
  let blockWidth = gridHeight / gridBlockHeight;
  return { gridWidth, gridHeight, gridLeft, gridTop, blockWidth };
}

Then I subscribe to the Draw event and render a bang block as a test to visualize the positioning.

  Draw.Subscribe(() => {
  let { gridWidth, gridHeight, gridLeft, gridTop, blockWidth } = calculateGridPosition();
  image(bang, gridLeft + gridWidth / 2, gridTop - gridHeight / 2, gridWidth, gridHeight);
});

Testing out the positioning by resizing the window over a variety of sizes verified that the sizing is good and fits the grid in the correct location.

Grid Stretch

The goal is for the game to render for any aspect ratio, but I optimize for the vertical phone form factor. Since the grid takes up most of the space for the case of a tall and skinny window, I declared success.

Blocks

Moving onto the block module, I kept things simple. First step was to defined the block types in an Enum-like object and build a function for picking a random block.

  const types = {
  CIRCLE: "Circle",
  TRIANGLE_UP: "TriangleUp",
  TRIANGLE_DOWN: "TriangleDown",
  DIAMOND: "Diamond",
  STAR: "Star",
  HEART: "Heart",
  BANG: "Bang"
};
function randomType() {
  let keys = Object.keys(types);
  return types[keys[Math.floor(Math.random() * keys.length)]];
}

I then created a simple Block class which assigns a random block type and stores the position for rendering. One weird bit I did was to invert the Y-axis for block positioning. My reasoning was that since blocks will get added from below, adding new blocks would require updating the y position numbers as more get pushed in. By numbering top to bottom, new blocks get increasing Y positions without needing any updates.

  export class Block {
  constructor(gridX, gridY) {
    this.type = randomType();
    this.blockLeft = gridX;
    this.blockTop = gridY;
  }
  render(gridLeft, blocksTop, gridWidth) {
    let blockWidth = gridWidth / gridBlockWidth;
    let centerX = gridLeft + blockWidth * this.blockLeft + blockWidth / 2;
    let centerY = blocksTop - blockWidth * this.blockTop + blockWidth / 2;
    image(blockImages[this.type], centerX, centerY, blockWidth, blockWidth);
  }
}

Then back on the grid side of things, I initialized the blocks in a simple starting pattern which stacks them column by column. I store them in an array to start since no block lookups are needed yet, but I suspect I will store them in a two dimensional array or map in the future.

  let blocks = [];
let maxStackHeight = 5;
for (let x = 0; x < gridBlockWidth; x++) {
  let stackHeight = Math.floor(Math.random() * 4) + maxStackHeight - 3;
  for (let y = 0; y < stackHeight; y++) {
    blocks.push(new Block(x, maxStackHeight - y));
  }
}

Finally back in the draw event I calculate the top position of the block grid and render each block!

  Draw.Subscribe(() => {
  let { gridWidth, gridHeight, gridLeft, gridTop, blockWidth } = calculateGridPosition();
  let blocksTop = gridTop - gridHeight + maxStackHeight * blockWidth;
  image(bang, gridLeft + gridWidth / 2, gridTop - gridHeight / 2, gridWidth, gridHeight);
  for (let block of blocks) {
    block.render(gridLeft, blocksTop, gridWidth);
  }
});

Which yields this image:

FinishedGrid

This work lays the foundation for the actual game mechanics. Shouldn't be too long till I start moving blocks around!

Till tomorrow,
Keith