To make a usable grid for the actual Tetris blocks, I ended up making a Cell and Grid class. The cell class would keep track of the variables of each block on the grid. The Grid class would then take an x and y parameters in its constructor to make a 2d array of Cells. This helped model my layout of cells, since I could access this 2d array object inside of the grid class and use certain x and y indices to manipulate the cells. The cells were not constructed anywhere but inside of the grid class. After the grid class was constructed inside of the main program in setup(), it would loop to be shown to the UI inside of draw(). To specify, the setup() function is always ran once, while the draw() function loops infinitely unless it is directly stopped. This is helpful, since you can initiate objects once inside of setup() then show them and modify them in draw() for however long you need.
Now that the grid and cells were made, I could start designing the Block class and each specific Tetris block. Each separate Tetris piece inherited the block class and passed their color into their constructors. The specific blocks are named after the letter they represent by their shape, like ‘T’ or ‘I’. After succeeding in getting each block to be constructed and placed at the top of the grid, I worked on figuring out how to make the blocks fall and rotate. To do this, I created methods to detect if there were 'solid' cells around the 'active' cells. The solid cells were the borders and placed Tetris pieces, while the active cells were where the current Tetris block was occupying. This ended up making my code more complicated by the end of the project, but the final product still works well. A spawn zone was also added at the top of the grid, and is hidden from the player, so the blocks spawn right above the active grid. User input was also added at this stage with Processing's keyPressed() and keyReleased() functions. These helped to make it so you could get multiple inputs at once, instead of using their 'key' keyword.
Now that the blocks could be manipulated, I needed to add conditional checks to make the lines and score update when blocks were placed. I used two for loops to go through the grid array to check if all horizontal blocks in a row were solid. If so, another grid method would be called to clear that specific row, while also moving every row above it down one y position. This made it so the line would go away while also updating the other remaining solid cells. This is when I also implemented updating the score, lines, and level values inside of menu, since they could now also be properly updated. For every ten lines, the level goes up one number. Every level, the speed of the game gets faster, so the number of frames before the block drops gets lower. The score will increase based on your level and how many lines you clear at once. The next block and held block features were also implemented now. They would construct a new 4x2 grid each to display the blocks for their respective purposes. Hidden integer values were kept to keep track of these blocks outside of the grid. A helper function was defined inside of main to help randomly calculate the next block and swap the current and held block.
The final features that I added to this game were the different game states and menus, which include the start, pause, and game over screens. I made a switch case statement in the draw() loop in my main program and a integer variable called gameState to keep track of the current state. Constants were defined for the different states and user input or conditions would be used to swap them. The different states would be placed into their own methods to make readability higher for the switch case. This is why the main draw() loop is so short. Most of the games functionality comes from the statePlaying() helper function in Tetris.pde.