# 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:

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: https://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)`

## Final program

In the next post, I’ll show how to create layers in a more efficient way.

This entry was posted in Tutorial and tagged , . Bookmark the permalink.

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

1. Małgorzata Serżysko-Sosnowksa says:

Hi! I am following your tutorial and I have a question concerning @property. Why can’t we just create a new variable worldWidth and assign to it int(self.worldSize.x). Like:
self.worldWidth = int(self.worldSize.x)

I am new to Pyhon and would appreciate an explanation.
And thank you so much for sharing your experience via this tutorial. It is really helpful. 🙂

• phylyp says:

You can create a new attribute worldWidth and assign any value inside or outside the class.

However, you can’t control who is updating it. For instance, a user (e.g. someone outside the class) could change it:

```class GameState: def __init__(self): self.worldSize = Vector2(1,2) self.worldWidth = int(self.worldSize.x) self.worldHeight = int(self.worldSize.y)```

``` ```

```state = GameState() state.worldWidth = 3 # => state.worldSize.x is 1 and state.worldWidth is 3, which is inconsistent```

With a property, the user can’t change the value of worldWidth, so state.worldWidth always equals int(self.worldSize.x).

If you want to only change the width of the world, you can use a setter:

```class GameState: def __init__(self): self.worldSize = Vector2(1,2)```

``` @property def worldWidth(self): return int(self.worldSize.x) @worldWidth.setter def worldWidth(self, width): self.worldSize.x = width ```

```state = GameState state.worldWidth = 3 # => state.worldSize.x is 3, state.worldWidth returns 3```

properties and setters can “fake” an attribute and let you full control of what happens.

I hope this answer helps 🙂

• Małgorzata Serżysko-Sosnowksa says:

I think I get it. So the @property prevents accidental overwrite by the further part of the code. Thank you very much for the explanation 🙂