Levels - noooway/love2d_arkanoid_tutorial GitHub Wiki

The next step is to implement different levels.

All levels-related information will be held at the levels table. The actual sequence of levels will be stored in the levels.sequence array.

local levels = {}
.....
levels.sequence = {}

I represent each level as a 2d-table. Inner tables correspond to the rows of the bricks. In the future, the numbers will indicate the brick types; currently they only show whether the brick is present on certain position or not.

levels.sequence[1] = {
   { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
   { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
   { 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1 },
   { 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1 },
   { 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0 },
   { 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0 },
   { 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0 },
   { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
}

levels.sequence[2] = {
   { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
   { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
   { 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1 },
   { 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0 },
   { 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0 },
   { 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0 },
   { 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1 },
   { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
}

Since there are several levels, it is necessary to maintain the current level number:

levels.current_level = 1

Changes are necessary in bricks.construct_level function, to make it receive one of the levels.sequence[.....] tables with bricks arrangement.

function bricks.construct_level( level_bricks_arrangement )
   bricks.no_more_bricks = false
   for row_index, row in ipairs( level_bricks_arrangement ) do         --(*1)
      for col_index, bricktype in ipairs( row ) do
         if bricktype ~= 0 then                                        --(*2)
            local new_brick_position_x = bricks.top_left_position_x +
               ( col_index - 1 ) *
               ( bricks.brick_width + bricks.horizontal_distance )
            local new_brick_position_y = bricks.top_left_position_y +
               ( row_index - 1 ) *
               ( bricks.brick_height + bricks.vertical_distance )
            local new_brick = bricks.new_brick( new_brick_position_x,
                                                new_brick_position_y )
            table.insert( bricks.current_level_bricks, new_brick )
         end
      end
   end
end

(*1): Instead of the fixed number of rows and columns, the iteration goes over the elements of the level_bricks_arrangement.
(*2): The brick is created if it's type is nonzero.

When there are no more bricks left, switch to the next level happens. The number of remaining bricks is monitored by bricks.update function. If there are none, a function to switch to the next level could be called. However, instead of that, I raise the bricks.no_more_bricks flag, which is checked each update cycle

function bricks.update( dt )
   if #bricks.current_level_bricks == 0 then    --(*1)
      bricks.no_more_bricks = true
   else
      for _, brick in pairs( bricks.current_level_bricks ) do
         bricks.update_brick( brick )
      end      
   end
end

function love.update( dt )
   .....
   levels.switch_to_next_level( bricks )
end

(*1): The number of remaining bricks is equivalent to the length of the bricks.current_level_bricks table.

If it is on, then the current_level is updated, the new set of bricks is constructed and the ball is repositioned.

function levels.switch_to_next_level( bricks )
   if bricks.no_more_bricks then
      .....
         levels.current_level = levels.current_level + 1
         bricks.construct_level( levels.sequence[levels.current_level] )
         ball.reposition()
      .....
      end
   end
end

function ball.reposition()
   ball.position_x = 200
   ball.position_y = 500   
end

It is possible to directly call the levels.switch_to_next_level in the bricks.update instead of rising the bricks.no_more_bricks flag. However, the flag will be more convenient later, when gamestates are introduced.

It is also necessary to do something special when there are no more levels, i.e. when the game is finished. For now, displaying a simple congratulations message should be sufficient.

The total number of levels equals to the length of the levels.sequence array. It is possible to check whether the levels.current_level is the last one by comparing it with the #levels.sequence. When there are no more levels left, switch_to_next_level function raises the levels.gamefinished flag.

function levels.switch_to_next_level( bricks )
   if bricks.no_more_bricks then
      if levels.current_level < #levels.sequence then       --(*1)
         levels.current_level = levels.current_level + 1                  --(*2)
         bricks.construct_level( levels.sequence[levels.current_level] ) 
         ball.reposition()                                                
      else
         levels.gamefinished = true                                       --(*3)
      end
   end
end

(*1): checks, whether the levels.current_level is the last one.
(*2): if not, update the level counter, construct new level and reposition the ball.
(*3): otherwise signal that the game is finished.

When levels.gamefinished is on, the message is displayed

function love.draw()
   .....
   walls.draw()
   if levels.gamefinished then
      love.graphics.printf( "Congratulations!\n" ..
			       "You have finished the game!",
			    300, 250, 200, "center" )
   end
end

Through this tutorial I've chosen to represent levels as 2d Lua tables. In the appendix A a method to store them as text strings is demonstrated.