 ## Discover Python and Patterns (17): 2D arrays

We still have no background in our game: I add one in this post using 2D arrays.

This post is part of the Discover Python and Patterns series

## 2D arrays

We have already seen lists in Python, for instance, the creation of a list of three numbers:

``list = [ 1,2,3 ]``

If you remember well, an item of a list can be anything, including a list:

``array2d = [ [1,2,3], [4,5,6] ]``

Using this syntax, I created a 2D array with two rows of three items:

1 2 3
4 5 6

You can access each row of the array with one pair of brackets:

``````print(array2d) # [1,2,3]
print(array2d) # [4,5,6]``````

You can use a second pair of brackets to access of the numbers of the 2D array:

``````print(array2d) # 2
print(array2d) # 5``````

More generally, you can access (or change) any item with the syntax `array2d[y][x]`, where `x` and `y` are the coordinates in the array.

Resizing a 2D array is not a simple task; most of the time, we don't need to change the size of 2D arrays. For dynamic 2D arrays, we need to create or use a dedicated Python library, but this is another story.

You can iterate through 2D arrays using a double `for`statement:

``````for y in range(2):
for x in range(3):
print(array2d[y][x]," ",end='')
print()``````

The `end=''` argument in the `print()` call means that we don't want the printing of a line return. It leads to the following result:

`````` 1  2  3
4  5  6``````

Using a 2D array, I wish to add a background to our game:

I also created a tileset using the sprites found here: zintoki.itch.io/ground-shaker, created by zintoki.

## Create the 2D array

In the constructor of the `GameState` class, I create a new attribute `ground` with the coordinates of tiles in the tileset. For instance, `Vector2(5,1)` are the coordinates of the green grass tile:

``````self.ground = [
[ Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(6,2), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(5,1)],
[ Vector2(5,1), Vector2(5,1), Vector2(7,1), Vector2(5,1), Vector2(5,1), Vector2(6,2), Vector2(7,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(6,1), Vector2(5,1), Vector2(5,1), Vector2(6,4), Vector2(7,2), Vector2(7,2)],
[ Vector2(5,1), Vector2(6,1), Vector2(5,1), Vector2(5,1), Vector2(6,1), Vector2(6,2), Vector2(5,1), Vector2(6,1), Vector2(6,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(6,2), Vector2(6,1), Vector2(5,1)],
[ Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(6,1), Vector2(6,2), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(6,2), Vector2(5,1), Vector2(7,1)],
[ Vector2(5,1), Vector2(7,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(6,5), Vector2(7,2), Vector2(7,2), Vector2(7,2), Vector2(7,2), Vector2(7,2), Vector2(7,2), Vector2(7,2), Vector2(8,5), Vector2(5,1), Vector2(5,1)],
[ Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(6,1), Vector2(6,2), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(6,2), Vector2(5,1), Vector2(7,1)],
[ Vector2(6,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(6,2), Vector2(5,1), Vector2(5,1), Vector2(7,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(6,2), Vector2(7,1), Vector2(5,1)],
[ Vector2(5,1), Vector2(5,1), Vector2(6,4), Vector2(7,2), Vector2(7,2), Vector2(8,4), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(6,2), Vector2(5,1), Vector2(5,1)],
[ Vector2(5,1), Vector2(5,1), Vector2(6,2), Vector2(5,1), Vector2(5,1), Vector2(7,1), Vector2(5,1), Vector2(5,1), Vector2(6,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(7,4), Vector2(7,2), Vector2(7,2)],
[ Vector2(5,1), Vector2(5,1), Vector2(6,2), Vector2(6,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(5,1), Vector2(5,1)]
]``````

Creating such a 2D array directly with Python code is not handy; I'll show later how to create them with software like Tiled.

## Render the tiles in the 2D array

I first create a new method `renderGround()` in the `UserInterface` class dedicated to the rendering of a single tile. It works as before (like in the `renderUnit()` method):

``````def renderGround(self,position,tile):
# Location on screen
spritePoint = position.elementwise()*self.cellSize

# Texture
texturePoint = tile.elementwise()*self.cellSize
textureRect = Rect(int(texturePoint.x), int(texturePoint.y), int(self.cellSize.x),int(self.cellSize.y))
self.window.blit(self.groundTexture,spritePoint,textureRect)``````

In the `render()` method of the `UserInterface` class, I add the rendering of all tiles of the 2D array using a double `for`statement (before the rendering of the unit!):

``````for y in range(int(self.gameState.worldSize.y)):
for x in range(int(self.gameState.worldSize.x)):
self.renderGround(Vector2(x,y),self.gameState.ground[y][x])`````````

## Python properties

In several locations in the program, there are expressions a little long. I want to show you here a feature of the Python language to reduce them a bit, and thus get a program easier to read.

For example, the expression to get the integer value of the width of the world is the following one:

``int(self.gameState.worldSize.x)``

We can shorter it in the following way:

``self.gameState.worldWidth``

A first solution to get this result is to create a new attribute `worldWidth` in the `GameState` class. We can initialize it with the int value of the `x` member of the `worldSize`attribute. If the world's width never changes, everything is fine.

However, this first solution is dangerous. After several months or years of improvements in our game, we could forget this trick, and forget to update `worldWidth` when `worldSize` changes (and vice-versa). It will lead to unexpected behavior and a difficult bug to solve.

The Python language offers a second solution: the properties. In a class, you can create a method that works as an attribute:

``````class GameState():

...

@property
def worldWidth(self):
return int(self.worldSize.x)

...``````

The `worldWidth()` method returns the value of the `x` member of the `worldSize` attribute converted to an `int`.

Pay attention to the line above: there is a `@property`. This kind of line is called an annotation. It tells that the following method is an "accessor" or a "getter". It should have no arguments except `self`, and must return a value. Then, we can use it as if it was an attribute (to get the value, to change it, it is another syntax).

The advantage of this solution compared to the first one is that, if we update the `worldSize` attribute, then the `worldWidth` property (e.g., like a "fake attribute") is also updated.

Using this syntax, I add two new properties `worldWidth` and `worldHeight` in the `GameState` class:

``````class GameState():

...

@property
def worldWidth(self):
return int(self.worldSize.x)

@property
def worldHeight(self):
return int(self.worldSize.y)

...``````

Similarly, I add two properties in the `UserInterface` class for the cell width and height:

``````class UserInterface ():

...

@property
def cellWidth(self):
return int(self.cellSize.x)

@property
def cellHeight(self):
return int(self.cellSize.y)

...``````

These properties increase the readability in several locations in the program; for instance, the computation of the tile coordinates in the texture goes from:

``textureRect = Rect(int(texturePoint.x), int(texturePoint.y), int(self.cellSize.x),int(self.cellSize.y))``

to:

``textureRect = Rect(int(texturePoint.x), int(texturePoint.y), self.cellWidth, self.cellHeight)``