Our game is close to being complete -- or at least complete
enough that it can be fun to play. All that remains is removing full
rows (since filling rows is the object of Tetris) and keeping score.
This function will iterate a variable, oldRow, from the bottom to the top of the board (that is, from rows-1 to 0). It will also start another variable, newRow, at the bottom of the board. Each oldRow that is not full is copied to the newRow, which is then decremented by one. Each oldRow that is full is not copied, and the fullRows counter is incremented. At the end, the rows on top are cleared (set to emptyColor) as necessary
Note that there is a very common mistake here: instead
of copying a row element-by-element, you might simply assign the oldRow into the newRow as such:
board[newRow] = board[oldRow] # WRONG!!!
While it is possible to make this approach work (and it can even be more efficient than the approach described above), it is almost universally a source of bugs and confusion. You will likely end up with two or more rows in your board referencing the same list (that is, they will be "aliases" rather than "copies")! This leads to very confusing situations, but now you know to expect it and to avoid it!
Now that we are removing rows, we should keep score.
To do that, we will introduce a canvas data value "score" (which is set to
zero in the init function). In our removeFullRows function, we will
increment the score by the square of total number of full rows
removed in order to reward removing
multiple lines at once. And now that we are keeping score, we should
display the score, which involves adding a function drawScore, which is
called by redrawAll.