-
Notifications
You must be signed in to change notification settings - Fork 17
Storing Levels as Strings
In this appendix a method to use text strings to define and store levels is demonstrated.
In the part 1-06 levels are 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 tools 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): Care should be taken 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 the level data (I've put several spaces and a comment here to illustrate the point, but they are not present in the actual code). Depending on the way it's parsed, 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.
Lua doesn't allow to iterate over contents of the string directly, but it has built-in capabilities for pattern matching and regular expressions, which can be used instead.
In this case, gmatch
string method is a good choice:
for a given string and a regexp pattern, it allows to iterate over each substring in the string that
matches the pattern. Typically it is used in constructions with the for
-loop, such as
for pattern_match in some_string:gmatch( pattern ) do
print( pattern_match )
end
To extract a single line from a multiline string, a '(.-)\n'
pattern can be used.
Dot '.'
matches 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 the new line character. Literally '(.-)\n'
reads as "match shortest sequence of arbitrary characters of lengths zero or more with newline at the end". So, effectively, '(.-)'
matches everything from the beginning of the line to the nearest end-of-line character, which is precisely a single line of a multiline string. To iterate over each character of the extracted line, a call to gmatch('.')
is enough: the dot '.'
matches single arbitrary character.
The iteration should be performed in the bricks.construct_level
function, where depending on the
extracted symbol certain actions are performed:
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
local col_index = 0
for bricktype in row:gmatch('.') do --(*2)
col_index = col_index + 1
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): Iteration over each line of the string representing level.
An extracted line is assigned to the row
variable.
(*2): Iteration over each character of a single line.
(*3): Dispatch on type of the extracted character.
Feedback is crucial to improve the tutorial!
Let me know if you have any questions, critique, suggestions or just any other ideas.
Chapter 1: Prototype
- The Ball, The Brick, The Platform
- Game Objects as Lua Tables
- Bricks and Walls
- Detecting Collisions
- Resolving Collisions
- Levels
Appendix A: Storing Levels as Strings
Appendix B: Optimized Collision Detection (draft)
Chapter 2: General Code Structure
- Splitting Code into Several Files
- Loading Levels from Files
- Straightforward Gamestates
- Advanced Gamestates
- Basic Tiles
- Different Brick Types
- Basic Sound
- 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
- Improved Ball Rebounds
- Ball Launch From Platform (Two Objects Moving Together)
- Mouse Controls
- Spawning Bonuses
- Bonus Effects
- Glue Bonus
- Add New Ball Bonus
- Life and Next Level Bonuses
- Random Bonuses
- Menu Buttons
- Wall Tiles
- Side Panel
- Score
- Fonts
- More Sounds
- Final Screen
- Packaging
Appendix D: GUI Layouts
Appendix E: Love-release and Love.js
Beyond Programming: