Small unfinished game implementing the Wave Function Collapse algorithm
Quantum mechanics introduces this concept when a wave function reduces from several to a single eigenstate caused by interaction with the outside world.
However, in this particular case, Wave Function Collapse allows for procedurally generated images based on a single or multiple smaller images given as input, where patterns within them will be analysed to generate something of the same theme, but variated.
This is especially useful for level design in video games, the hassle of manually designing levels for games makes using WFC better in a way. Not always though. Anyway, this was my attempt at it.
A map is generated from a set of tiles and rules using the socket technique where each side of the tile has one or multiple connectors, each identified by a number or letter.
The connectors determine which tiles can stick themselves to other tiles, a tile can only connect to another when both sockets of the sides that collide are equivalent.
There's also a player you can move around (just a square for now) as well as an enemy (still a work-in-progress). Collision between the player and the map is done with pixel colour analysis: checking that pixels' colour around the player corresponds to a colour you can walk on. It's not amazing, it may change, or maybe not.
All tiles are contained within the /src
folder along with a random script that I wrote with the Pillow library to take the 6 tiles I originally had, rotate and save them every 90° clockwise (stops at 270°). Like that, I had rotated versions of the tiles (yes, that could've been implemented efficiently in the WFC script without having to create millions of images, but that's for another time).
To regenerate the map, you obviously have to close and re-open the script. Won't be for long though (if all goes to plan).
For now, rules have been manually entered to establish the different sockets between tiles. Tiles with the least entropy have to be collapsed first. The tile values declared below as a dictionary contain an ID that corresponds to the image (tile texture) it is pointing to and the socket for each side of the tile defined in a clockwise manner starting from the top [TOP, RIGHT, BOTTOM, LEFT].
self.tile_values = {
0: [0, 0, 0, 0], 1: [1, 0, 1, 0], 2: [0, 0, 1, 0], 3: [1, 1, 1, 1],
4: [0, 1, 1, 0], 5: [0, 1, 1, 1], 6: [0, 0, 0, 0], 7: [0, 0, 0, 0],
8: [0, 0, 0, 0], 9: [0, 1, 0, 1], 10: [1, 0, 1, 0], 11: [0, 1, 0, 1],
12: [0, 1, 0, 0], 13: [1, 0, 0, 0], 14: [0, 0, 0, 1], 15: [1, 1, 1, 1],
16: [1, 1, 1, 1], 17: [1, 1, 1, 1], 18: [1, 1, 0, 0], 19: [1, 0, 0, 1],
20: [0, 0, 1, 1], 21: [1, 1, 1, 0], 22: [1, 1, 0, 1], 23: [1, 0, 1, 1],
24: [0, 0, 0, 0], 25: [0, 1, 0, 1], 26: [1, 0, 1, 0], 27: [0, 1, 0, 1],
28: [1, 0, 1, 0]
}
This is essentially how the sockets work.
Any entity's position after the map generation has finished is based on the presence of one specific tile (ID 0: "SAFE TILE" in the code). The function:
find_ideal_spot_for_entity()
-is responsible for finding that position by ultimately going through each cell and finding all those that have the SAFE TILE and then choosing randomly from that list as the starting point for that player. In terms of collisions between the player and the tiles, the colours of the pixels from the center of the player on all four sides (because it's a square) are analysed. If it picks out a specific GREEN (0, 182, 0, 255) colour, you can freely walk on it as you please. Any other colours in a certain direction will prevent the player from moving in that direction. The function below is responsible for all of that.
def update(self):
"""Update player movement."""
if self.up:
if self.map.window.instance.get_at((self.x + int(self.size / 2), self.y - 2)) == self.map.SAFE_TILE_COLOUR and self.y > 10:
self.y -= self.speed
if self.down:
if self.map.window.instance.get_at((self.x + int(self.size / 2), self.y + self.size + 2)) == self.map.SAFE_TILE_COLOUR and self.y < self.map.window.get_height() - 20:
self.y += self.speed
if self.left:
if self.map.window.instance.get_at((self.x - 2, self.y + int(self.size / 2))) == self.map.SAFE_TILE_COLOUR and self.x > 10:
self.x -= self.speed
if self.right:
if self.map.window.instance.get_at((self.x + self.size + 2, self.y + int(self.size / 2))) == self.map.SAFE_TILE_COLOUR and self.x < self.map.window.get_width() - 20:
self.x += self.speed
The dimensions of the grid and the size of the tiles can be altered with the GRID_SIZE and IMAGE_DIM variables.
self.IMAGE_DIM = 48
self.GRID_SIZE = 15
- [Arrow Keys] -> Move player
A "requirements" text file is provided within the repository.
To install the necessary library(ies) to run the script:
1- Open CMD/Git Bash/Terminal
2- Change the current directory to the project path (cd path\to\project)
3- Install the library(ies) from the "requirements.txt" file with: pip install -r requirements.txt
Developed by: SammygoodTunes
Credit to (for the explanatory videos): The Coding Train, Martin Donald
Library(ies) used: Pygame 2.2.0, Pillow 8.4.0
Version: 1.0.1