This past week I presented the design review for our team. Overall the presentation went well and everyone else seems to be doing well in their respective projects.
The design review report is longer than expected and is taking some time to put together, even with the work we already did for the presentation. Nonetheless, it should be finished by Monday.
Aside from that, the project itself is going well. This week I added line clearing and holding logic.
Line clearing turned out to be an interesting problem, because of the mechanics of how the lines need to be cleared. In the set of 20 rows, it is possible to clear any of 1, 2, 3, or 4 rows at a time. These rows are not necessarily adjacent. This makes the problem somewhat more difficult since you cannot simply shift non-full rows into the space underneath.
Here I took advantage of our board’s clock rate to implement a relatively long operation to “line clear”. First, there is a detection for full rows in the playfield. This is simply done by looping through the columns of each row and skipping rows with BLANK or GHOST tiles. Then, all full rows are replaced by empty rows. This means that any full row is guaranteed to only be full for 1 cycle, which is important for bookkeeping. Then, all blank rows are swapped with the row above it. How exactly this logic is written is important. For example,
for (int i = 1; i < PLAYFIELD_ROWS; i++) begin
if (lines_empty[i]) begin
locked_state[i] <= locked_state[i – 1];
locked_state[i – 1] <= ‘{PLAYFIELD_COLS{BLANK}};
end
end
This causes issues if two rows clear simultaneously. Let’s say both row 19 and row 18 are “full” and then are cleared, so they are both “empty”. Then, with loop unrolling, row 18 swaps with row 17 and row 19 swaps with row 18. However, since the loop unrolling is in-order, row 18 is loaded with a blank, row 19 is loaded with row 18 (which is blank) and row 17 is now also blank. This then propagates up and then wipes the entire playfield. Oops.
So clearly, we want to have any tiles being shifted down to not be overwritten by tiles that are being swapped up. Then, we can split the loop.
for (int i = 1; i < PLAYFIELD_ROWS; i++) begin
if (lines_empty[i]) begin
locked_state[i – 1] <= ‘{PLAYFIELD_COLS{BLANK}};
end
end
for (int i = 1; i < PLAYFIELD_ROWS; i++) begin
if (lines_empty[i]) begin
locked_state[i] <= locked_state[i – 1];
end
end
And then line-clearing works! Each row being full for exactly 1 cycle means I can keep track of lines cleared by continuously summing the total number of lines full. This means there’s no additional bookkeeping overhead for line clearing which is quite nice.
Hold logic is a lot more straightforward, it is just an FSM that tracks if holding is valid or not as well as deciding if there is a current “hold” piece which indicates if the new piece needs to be loaded from the Seven Bag or if it should be swapped with the current hold piece.
I had originally wanted to also get a graphical component working this week, the lines sent area. In most modern implementations, the area underneath the hold piece box is used to show a bar that indicates how many lines are about to get sent to your opponent. This will likely be added next week or potentially over spring break. Unfortunately, the Design Review Report is taking longer than expected (and we didn’t factor it into our Gantt Chart) so this results in some small setbacks. Nonetheless, I am slightly behind schedule, but spring break is right around the corner, so I have no qualms about catching up.
Next week I’ll be working on detecting T-spins using the 3 corner method and immobile heuristics. I also wish to implement combos, that is detecting lines cleared back-to-back as this also affects the number of lines being sent to the opponent.