Skip to content

Storing Levels as Strings

noooway edited this page May 15, 2017 · 53 revisions

In this appendix I want to demonstrate how to use text strings to define and store levels.

So far levels were represented by 2d Lua tables. This is acceptable for small maps, but quickly becomes inconvenient as level size and complexity grows. A widely used technique is to store levels as text strings. This can be a good solution for medium-sized maps, where tables are already inefficient and specialized libraries such as Tiled are overkill.

For Arkanoid, it is natural to represent a level as a multiline string. Each line corresponds to a row of bricks, and each character in this line - to specific position. To define multiline strings, Lua provides special syntax - double square brackets. Let's agree that the hash symbol '#' denotes a brick, and the space ' ' or the underscore '_' denote empty space. That way it is possible to use underscores to visually indicate size of the map.

levels.sequence = {}
levels.sequence[1] = [[  --(*1)
___________

# # ### # #
# # #   # #
### ##   # 
# # #    # 
# # ###  # 
___________
]]

levels.sequence[2] = [[
___________

##  # # ###
# # # # #  
###  #  ## 
# #  #  #  
###  #  ###
___________
]]

(*1): Be careful not to put any whitespaces or any other characters after opening brackets, because Lua will treat it as an additional line that will be added to your level data (I've put several spaces and a comment here to illustrate the point, but it is not present in the actual code). Depending on the way you parse it, it may or may not cause errors.

To construct levels from such data, it is necessary to iterate over each line and each character of each line. In this case, simple regexp matching is sufficient. Lua has built-in capabilities for pattern matching and regular expressions. In our case we, gmatch method is a good choice: for a given string and a regexp pattern, it allows to iterate over each substring in the string that mathes the pattern. Typically it is used in constructions with for-loop, such as

for pattern_match in some_string:gmatch( pattern ) do
   print( pattern_match )
end

It is possible to split original multiline string into substrings separated by newline characters '\n'. Each such string defines a row. Next, it is necessary to iterate over each character in the row and do some action depending on the symbol we found.

function bricks.construct_level( level_bricks_arrangement )
   bricks.no_more_bricks = false
   local row_index = 0
   for row in level_bricks_arrangement:gmatch( '(.-)\n' ) do          --(*1)
      row_index = row_index + 1
      print( "row", row_index, row )
      local col_index = 0
      for bricktype in row:gmatch('.') do                             --(*2)
         col_index = col_index + 1
         print( "col", col_index, bricktype )
         if bricktype == '#' then                                     --(*3)
            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): dot '.' means arbitrary character, minus '-' means "match the previous character or class of characters zero or more times, as few times as possible", brackets '()' denote a group and '\n' stands for new line character. '(.-)\n' matches shortest sequence of arbitrary characters of lengths zero or more followed by newline. Basically, '(.-)' matches everything from the beginning of the line to the nearest end-of-line character. That value will be assigned to row variable.
(*2): row:gmatch('.') iterates over each character of the string row.
(*3): each character in the row is analyzed and depending on it's type certain actions are performed.

    Home
    Acknowledgements
    Todo

Chapter 1: Prototype

  1. The Ball, The Brick, The Platform
  2. Game Objects as Lua Tables
  3. Bricks and Walls
  4. Detecting Collisions
  5. Resolving Collisions
  6. Levels

    Appendix A: Storing Levels as Strings
    Appendix B: Optimized Collision Detection (draft)

Chapter 2: General Code Structure

  1. Splitting Code into Several Files
  2. Loading Levels from Files
  3. Straightforward Gamestates
  4. Advanced Gamestates
  5. Basic Tiles
  6. Different Brick Types
  7. Basic Sound
  8. Game Over

    Appendix C: Stricter Modules (draft)
    Appendix D-1: Intro to Classes (draft)
    Appendix D-2: Chapter 2 Using Classes.

Chapter 3 (deprecated): Details

  1. Improved Ball Rebounds
  2. Ball Launch From Platform (Two Objects Moving Together)
  3. Mouse Controls
  4. Spawning Bonuses
  5. Bonus Effects
  6. Glue Bonus
  7. Add New Ball Bonus
  8. Life and Next Level Bonuses
  9. Random Bonuses
  10. Menu Buttons
  11. Wall Tiles
  12. Side Panel
  13. Score
  14. Fonts
  15. More Sounds
  16. Final Screen
  17. Packaging

    Appendix D: GUI Layouts
    Appendix E: Love-release and Love.js

Beyond Programming:

  1. Game Design
  2. Minimal Marketing (draft)
  3. Finding a Team (draft)

Archive

Clone this wiki locally