# The Bitwise Challenge Post-Mortem, Part 4

This is part 4 of the post-mortem I’m doing for the Bitwise Challenge, in which I made a Tetris-like game called Loftis as an Addon in WildStar in four hours. Here are links to parts onetwo, and three.

Okay, so by the end of part 3 we had written the logic for creating new blocks, testing whether a position is valid, rotating blocks, and moving blocks side to side as they rise through the field.

If when a block is rising it cannot move into the position above, it is time to place it. The code for placing a block is straightforward:

``` local tGame = self.tGame
local tBlock = self.tGame.tBlock
for y = 1,4 do
for x = 1,4 do
if tBlock[y][x] then
tGame.arField[tGame.iRow + y - 1][tGame.iColumn + x - 1] = true
local tPixie = tGame.arPixies[tGame.iRow + y - 1][tGame.iColumn + x - 1]
local nLeft = (tGame.iColumn + x - 2) * 32
local nTop = -128 + (tGame.iRow + y) * 32 - 32

tPixie.loc.nOffsets = {nLeft, nTop, nLeft + 32, nTop + 32}
tGame.arPixies[tGame.iRow + y - 1][tGame.iColumn + x - 1] = tPixie
end
end
end
self.tGame.tBlock = nil```

Aaaaaaaack! More magic numbers. Bad Bitwise. I’m not going to talk too much about the Pixies API calls here because that is WildStar-specific, and these post-mortems are more about the logic behind Loftis. All that we are really doing here is setting the tGame.arField[y][x] to true for any corresponding true value in our rising block. After doing that we reset the tGame.tBlock member to nil. We’ll see why later.

After placing a new block, we check to see if we have completed any rows. This is done in two steps. First, we make a list of all rows in which all 10 blocks are filled.

``` -- check for scoring
nRowCount = 0
arRowsToRemove = {}
for y = 4,21 do
local nBlocks = 0
for x = 1,10 do
if tGame.arField[y][x] then
nBlocks = nBlocks + 1
end
end

if nBlocks == 10 then
nRowCount = nRowCount + 1
arRowsToRemove[nRowCount] = y
end
end```

Note: the arRowsToRemove variable should be local! I’m really not sure why it wasn’t flagged as local; might have been an edit or just an oversight. Also note that we start our checking at row 4 because rows 1-3 are dummy rows that are always on. But we only go to 21! Another bug! That should be 24. Whenever we find a row that is completely on, we store its index and continue.

After compiling our list of completed rows, we remove them in reverse order, so as to preserve the correct indices of preceding rows.

``` if nRowCount == 0 then
return
end

local nScore = 50
for i = nRowCount,1,-1 do
nScore = nScore * 2
self:RemoveRow(arRowsToRemove[i])
end

self.tGame.nScore = self.tGame.nScore + nScore```

We also double the score for each row and add that temporary value to the game score. Since we started at 50, the scores for completing 1-4 rows will be 100, 200, 400, 800.

Removing a row is simple:

``` for y = nRow, 27 do
for x = 1,10 do
tGame.arField[y][x] = tGame.arField[y + 1][x]
end
end

for x = 1,10 do
tGame.arField[28][x] = false
end```

The magic numbers… they burn. You can hopefully see why removing the rows in reverse order is necessary here. Note that this method of counting the rows and then calling RemoveRow for each of the rows is really really inefficient. Because of the way I do this, if I were to remove the first 4 rows due to a particularly well placed I-piece, nearly the entirely field is moved 4 entire times. There are two reasons I did it this way: one, it was very very fast to write it this way. Two, even though it is about as inefficient as possible, it’s still plenty fast enough to get all the work done without a hiccup. Again, in a production environment or with a larger dataset, we wouldn’t do it this way.

Along those same lines, if you look at the code dealing with pixies surrounding the removal code, you can see that I destroy all the blocks in the field and re-create them with every single row removal. Extremely inefficient! I started to do this in a better way but by this point I was really sweating the clock and ended up just hacking this code because I knew performance wasn’t a concern.

That brings us to the last part of this Addon, the update function. Here the function is called OnTimer, because I implemented through the use of an Apollo timer. Normally I would respond every frame and calculate how much time had elapsed for the simulation. Here, however, I just decided to set a 20 Hz timer and always assume that 50 ms had elapsed. Again, a coding speed decision. The result of this is that if WildStar slows down to less than 20 FPS, Loftis will slow down, making it easier to play.

Here’s the OnTimer function:

```function Loftis:OnTimer()
if self.tGame == nil then
return
end
local tGame = self.tGame
if tGame.bGameOver then
return
end
if tGame.tBlock == nil then
-- we need to make a new block and put it in the world
-- if we can't place the block, game over man
local iBlock = math.random(1, 7)
local tBlock = InitBlock(karBlocks[iBlock])
local nRotate = math.random(1, 4)
for i = 1,nRotate do
tBlock = RotateBlockRight(tBlock)
end
if not self:IsPositionValid(tBlock, 24, 5) then
self:DoGameOver()
end
tGame.tBlock = tBlock
tGame.iColumn = 4
tGame.iRow = 24
tGame.fProgress = 0
return
end

tGame.fProgress = tGame.fProgress + 0.10
if tGame.fProgress >= 1.0 then
if tGame.iRow > 1 and self:IsPositionValid(tGame.tBlock, tGame.iRow - 1, tGame.iColumn) then
tGame.fProgress = 0
tGame.iRow = tGame.iRow - 1
else
self:PlaceBlock()
return
end
end

-- Update Block pixies
self:UpdateBlockPixies()
end```

Now you can see why I initialized the tGame.tBlock member to nil when starting a new game, and why we reset it to nil when it is placed. Every update, we check to see if it is nil, and if it is, we create a new one from the 7 variations, rotate it 1-4 times, and then place it if we can, or call DoGameOver if we cannot.

Rising is controlled through the fProgress member, as noted previously. Here’s another magic number, 0.10, which determines how fast the blocks are rising. This equates to one block every half a second. If we had used an elapsed time model, we would have used a constant for speed (2.0) and multiplied it by elapsed time. Whenever fProgress gets to 1, we check to see if the block can keep rising and reset progress to 0, or place the block.

Finally, we call UpdateBlockPixies to modify the sprites that are used to draw the block. I won’t go into much detail there (again, WildStar specific) but the method I used does allow for “smooth movement” of the blocks as they rise, using fProgress to determine the relative y-value of the sprites drawn.

That’s pretty much it for Loftis. I’ve gone over all the major logic in the game and hopefully showed my thought process as I wrote the game. Not bad for a 4-hour challenge, though your mileage may vary. Obviously there were bugs that I found as I did this post-mortem, and there might be others as well. If you are in the WildStar beta, please feel free to install Loftis, modify it, etc. I myself will be updating a version with a little more polish and a few less bugs eventually, probably next week, depending on the weather here.

If you are not in the WildStar beta, you can probably follow along with the Apollo API calls and get a pretty good feel for what they do based on their names. I hope to see you in the game eventually!

Bitwise out.

## Author: Wiesman

Husband, father, video game developer, liberal, and perpetual Underdog.

## 2 thoughts on “The Bitwise Challenge Post-Mortem, Part 4”

1. Just finished reading all four parts, nice post. I’d love more post like this.