my problem is that I have a 2D array like this:
[["#", "#", "#", "#", "#", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "#", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "#", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "#", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "#", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "#", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "#", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "#", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "#", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "#", "#", "#", "#", "#", "#"]]
the player starts in the to right (represented as "X") and the goal is to get to the door ("[") . I've already made the game and player movement, but I'm trying to make a level generator so that I don't have to manually make levels, Ive already made the level gen, I just need an algorithm to check whether or not the level is possible to play, (sometimes the door isn't reachable)
i tried to make my own (quite janky) pathfinding algorithm and it really just didn't work.
How do I go about making such a function, to check the levels for playability?
code for the game below:
import sys
import tty
import termios
import os
import random
#instalize base variables for the game
levelnum = 1
op = 1
atk = 1
hp = 20
ehp = 5
XX = 0
XY = 0
keytf = False
level = [["#", "#", "#", "#", "|", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "|", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "|", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "|", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "#", "#", "#", "#", "#", "["],
["#", "#", "#", "#", "#", "#", "#", "#", "#", "["],
["#", "#", "#", "#", "|", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "|", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "|", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "|", "#", "#", "#", "#", "#"]]
#key getting function
def get_key(): # get the key pressed
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(fd)
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
#print level function
def printlevel(): # Print the current level
level[XY][XX] = "X"
print("\033[H", end="") # Clear the terminal
for i in range(10):
print(" ".join(level[i]))
print("Stats:")
print("HP: " + str(hp))
print("ATK: " + str(atk))
print("Enemy HP: " + str(ehp))
#level storage
def newlevel(levelnum):
global level
if levelnum == 1:
level = [["#", "#", "#", "#", "|", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "|", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "|", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "|", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "#", "#", "#", "#", "#", "["],
["#", "#", "#", "#", "#", "#", "#", "#", "#", "["],
["#", "#", "#", "#", "|", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "|", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "|", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "|", "#", "#", "#", "#", "#"]]
elif levelnum == 2:
level = [["#", "#", "#", "#", "|", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "|", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "|", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "|", "|", "#", "#", "#", "#"],
["#", "#", "#", "#", "#", "e", "#", "#", "#", "["],
["#", "#", "#", "#", "|", "|", "#", "#", "#", "["],
["#", "#", "#", "#", "|", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "|", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "|", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "|", "#", "#", "#", "#", "#"]]
elif levelnum == 3:
level = [["#", "|", "#", "e", "#", "|", "#", "e", "#", "#"],
["#", "|", "#", "|", "#", "|", "#", "|", "#", "#"],
["#", "|", "#", "|", "#", "|", "#", "|", "#", "#"],
["#", "|", "#", "|", "#", "|", "#", "|", "#", "#"],
["H", "|", "H", "|", "H", "|", "H", "|", "#", "["],
["#", "|", "#", "|", "#", "|", "#", "|", "#", "["],
["#", "|", "#", "|", "#", "|", "#", "|", "#", "#"],
["#", "|", "#", "|", "#", "|", "#", "|", "#", "#"],
["#", "|", "#", "|", "#", "|", "#", "|", "#", "#"],
["#", "e", "#", "|", "#", "e", "#", "|", "#", "#"]]
elif levelnum == 4:
level = [["#", "#", "#", "#", "|", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "|", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "|", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "|", "|", "#", "#", "#", "#"],
["#", "#", "#", "#", "#", "D", "#", "#", "#", "["],
["#", "|", "|", "|", "|", "|", "#", "#", "#", "["],
["#", "#", "#", "|", "|", "#", "#", "#", "#", "#"],
["|", "|", "#", "|", "|", "#", "#", "#", "#", "#"],
["|", "K", "#", "|", "|", "#", "#", "#", "#", "#"],
["|", "|", "|", "|", "|", "#", "#", "#", "#", "#"]]
#check your next move
def movechecker(move, op):
global XY, XX, levelnum, ehp, atk, hp, keytf
level[XY][XX] = "#"
if move == "[":
XX = 0
XY = 0
levelnum += 1
newlevel(levelnum)
elif move == "e" and ehp >= 1:
ehp -= atk
hp -= 1
elif move == "H":
hp += 10
moveit(op)
elif move == "K":
keytf = True
moveit(op)
elif move == "D" and keytf == True:
moveit(op)
elif ehp < 1:
moveit(op)
ehp = 5
else:
moveit(op)
printlevel()
#move the player
def moveit(op):
global XY, XX
if op == 1: # Move right
XX += 1
elif op == 2: # Move left
XX -= 1
elif op == 3: # Move up
XY -= 1
elif op == 4: # Move down
XY += 1
level[XY][XX] = "X"
printlevel()
printlevel()
#main game loop
while True: # Main game loop
key = get_key()
if key == "d" and XX < 9 and level[XY][XX+1] != "|":
move = level[XY][XX+1]
op = 1
movechecker(move, op)
elif key == "a" and XX > 0 and level[XY][XX-1] != "|":
move = level[XY][XX-1]
op = 2
movechecker(move, op)
elif key == "w" and XY > 0 and level[XY-1][XX] != "|":
move = level[XY-1][XX]
op = 3
movechecker(move, op)
elif key == "s" and XY < 9 and level[XY+1][XX] != "|":
move = level[XY+1][XX]
op = 4
movechecker(move, op)
level gen:
import random
level = [["#", "#", "#", "#", "#", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "#", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "#", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "#", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "#", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "#", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "#", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "#", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "#", "#", "#", "#", "#", "#"],
["#", "#", "#", "#", "#", "#", "#", "#", "#", "#"]]
def getwall():
if random.randint(1, 100) < 35:
return "|"
return "#"
def genlevel():
for i in range (10):
for j in range (10):
level [i][j] = getwall()
level [0][0] = " "
level [9][9] = "["
Your approach is to generate a random grid, and then verify whether it is one where you can reach the exit from the entry point. I suppose if this is not possible, you will generate another random grid, and continue like that until you have found a valid one.
I would suggest to approach this differently. You can enhance the generation part to always generate a grid that has a solution. I would also suggest to avoid cycles, which also means you'd avoid trivial "spaces" that have no walls, like this:
# #
# #
You could use one of the algorithms suggested on Wikipedia - Maze generation algorithm, such as the depth-first traversal, using an explicit stack:
from random import choice, randrange
WALL = "|"
FREE = " " # A temporary value. Will not occur in a fully generated level
REACHABLE = "#"
ENTRY = "[" # The single cell where the player starts
EXIT = "]" # The target cell that the player should reach
def gen_level(size):
size |= 1 # ensure it is odd
# start with all cells surrounded by walls
level = [
[
(WALL, FREE)[(x & y) % 2]
for x in range(size)
]
for y in range(size)
]
stack = [(1, 1)]
level[1][1] = REACHABLE
while stack:
x1, y1 = stack[-1]
try:
x2, y2 = choice([
(x2, y2)
for dx, dy in ((-2, 0), (2, 0), (0, -2), (0, 2))
for x2, y2 in ((x1 + dx, y1 + dy), )
if 0 <= x2 < size and 0 <= y2 < size and level[y2][x2] == FREE
])
level[y2][x2] = level[(y1+y2)//2][(x1+x2)//2] = REACHABLE
stack.append((x2, y2))
except IndexError:
stack.pop()
level[1 + randrange(size//2) * 2][0] = ENTRY
level[1 + randrange(size//2) * 2][-1] = EXIT
return level
Here is how you would call the above function:
level = gen_level(20)
for line in level:
print(*line)
And here is one possible output that this produces:
| | | | | | | | | | | | | | | | | | | | |
| # # # # # # # | # # # # # # # # # | # |
| | | | | | | # | | | # | | | | | # | # |
| # | # # # | # # # | # # # | # # # | # |
| # | # | # | | | # | | | # | # | | | # |
| # # # | # # # | # # # | # | # # # | # |
| # | | | # | | | | | # | # | | | # | # |
| # # # | # # # # # # # | # # # | # # # ]
| | | # | | | | | | | | | # | # | | | # |
[ # | # # # # # | # # # # # | # # # | # |
| # | | | | | # | # | | | | | | | # | # |
| # # # # # | # | # # # # # # # | # | # |
| # | | | # | # | | | | | | | # | # | | |
| # # # | # # # | # # # # # | # | # # # |
| | | # | | | | | # | | | # | | | | | # |
| # | # | # # # | # # # | # # # # # # # |
| # | # | # | # | | | # | | | | | | | # |
| # | # # # | # # # | # # # | # | # # # |
| # | | | | | | | # | | | # | # | # | | |
| # # # # # # # # # # # # # | # # # # # |
| | | | | | | | | | | | | | | | | | | | |
Note that the actual height/width is odd (not 20, but 21): this is a consequence of the choice to have all (x, y) reachable that have odd x and odd y.
Unrelated, but the output is a bit more "readable" when you use block-characters for walls, and a very light character (like a dot) for the reachable cells, like using:
WALL = "█"
REACHABLE = "·"
...and then format the output as follows:
for line in level:
print(" ".join(line).replace(WALL + " " + WALL, WALL * 3)
.replace(WALL + " " + WALL, WALL * 3))
Then you get an output like this:
█████████████████████████████████████████
█ · · · █ · · · · · · · · · · · · · █ · █
█████ · █████████ · █████████ · █ · █ · █
█ · █ · █ · · · █ · █ · · · █ · █ · · · █
█ · █ · █ · █ · █ · █████ · █ · █████████
█ · █ · · · █ · █ · · · · · █ · █ · · · █
█ · █████████ · █████████ · █ · █ · █ · █
[ · · · · · █ · █ · · · █ · █ · · · █ · █
█ · █████████ · █ · █ · █████████████ · █
█ · · · · · █ · · · █ · █ · · · · · · · █
█ · █████ · █████████ · █ · █████████████
█ · █ · · · █ · · · · · █ · █ · · · · · █
█████ · █████ · █████████ · █ · █████ · █
█ · · · █ · · · █ · · · · · █ · █ · █ · █
█ · █████ · █████ · █████████ · █ · █ · █
█ · █ · · · █ · · · · · · · · · · · █ · █
█ · █ · █████ · █████████████████ · █ · █
█ · █ · █ · · · · · · · █ · · · █ · █ · █
█ · █ · █████████████████ · █ · █████ · █
█ · · · · · · · · · · · · · █ · · · · · ]
█████████████████████████████████████████