Advertisement

Python - pygame - How to efficiently select sprites for different rects in randomly generated map

Started by April 01, 2020 06:54 PM
3 comments, last by Alberth 4 years, 7 months ago

Hi!

So I'm making a randomly generated dungeon and I have a sprite sheet with different walls in it (horizontal, vertical, corners, 3 way split walls etc). I'm trying to blit all my map elements properly, and I currently do this, by scanning through the whole game map, which is made of 80x60 block objects, some passable others are not. The script decides what each passable block bordering non-passable block should be blitted as. For each non-passable block it collects the data from the 8 bordering blocks and based on which of those are passable and which aren't the script decides which wall sprite is added.

My problem is that I can only do this checking by doing an exhaustive “if - then ; elif - then” etc logical list for each possible configuration, which is very leghtly and convoluted to say the least.

Can anyone offer an easier solution for this?

Use bits!

That may too fast for you, so let's take a step at a time. Use an 8-tuple (top, topright, right, bottomright … topleft) where each value is 1 if associated neighbour is passable, else 0. So you get values like

c = (0, 1, 1, 0, 1, 0, 0, 1)
d
# Bit number i has decimal value 2**i, thus bit 0 has value 2**0 = 1, bit 1 has value 2**1 = 2, etc
# In code:
>>> for i in range(8):
. . . print("bit {} has decimal value 2**{} = {}".format(i, i, 2**i))
bit 0 has decimal value 2**0 = 1
bit 1 has decimal value 2**1 = 2
bit 2 has decimal value 2**2 = 4
bit 3 has decimal value 2**3 = 8
bit 4 has decimal value 2**4 = 16
bit 5 has decimal value 2**5 = 32
bit 6 has decimal value 2**6 = 64
bit 7 has decimal value 2**7 = 128

The simplest form to convert to a number is now

bit_values = (1, 2, 4, 8, 16, 32, 64, 128)
number = sum(vc * bitval for vc, bitval in zip(c, bit_values))

And this is a number from 0 to 255, one for each combination of the 8 passable / non-passable neighbours. You can make a list of 256 results, and use the computed number to index in that result list.

There is a simple pattern in how the bits toggle, let's do that for 3 bits (bits 0, 1, and 2):

>>> for i in range(8):
...   print("{:03b}".format(i))
...
000
001
010
011
100
101
110
111

Bit 0 is at the far right, you see it toggles each next line. bit 1 is in the middle, it toggles every 2 lines. bit 2 is at the left, it toggles every 4 lines.

This pattern continues. Bit 7 toggles every 128 lines (so you first gey 128 lines where bit 7 is 0, and then 128 lines where bit 7 is 1, total of 256 lines). Within 128 lines, bit 6 toggles after 64 lines. Within 64 lines, bit 5 toggles after 32 lines, and so on. So it is very predictable at which index in the list a wall is passable or not.

Instead of 8 directions, you may want to start with 4. That gives you only 16 options which is a lot more manageable to understand. Once you understand it, scaling up to 8 is easy.

Advertisement

@Alberth Wow, what a great solution! It took me a few reads to get the idea, but I'll give it a try. Thanks a lot!

Basically, what happens is that you let go of seeing integers as values, and instead see them as a collection of 0/1 bits. Python has operators &, |, ^, <<, and >> to work with collections of bits. The bits view is also why ‘bin’, ‘oct’, and ‘hex’ exist in Python. A decimal value is what everybody is used to, but bin/oct/hex are way simpler to use if you want to see the underlying bit patterns.

Apparently there is even a few wiki pages about it: https://wiki.python.org/moin/BitwiseOperators

This topic is closed to new replies.

Advertisement