Advertisement

Sharing My Random Map Generator

Started by March 22, 2018 01:16 AM
2 comments, last by RidiculousName 6 years, 5 months ago

I've made a very simple map generator as a demo for combat encounters in my game. I plan to have the trees and rocks be cover, while the player's bandits ambush a caravan on the road in the middle. Please, let me know what you think. Criticism is welcome.


"""
Generates a basic map for combat
"""

__Author__ = "RidiculousName"
__date__ = "3/21/18"


import pygame as pg
import copy
import random


def createMap(width, height, trees, rocks, roadWidth):
    """
    creates a combat map

    :param width: int; 20-60
    width of map in squares
    :param height: int; 20-60
    height of map in squares
    :param trees: int; 0 to (width*height)//5
    # of trees in map
    :param rocks: int; 0 to (width*height)//5
    # of rocks in map
    :param roadWidth: int; 0-10
    width of road in map
    (if value=0, will not have a road)
    :return:
    tuple matrix of map
    """
    # variable declarations
    mapMatrix = []
    rowList = [0] * width
    treeLocations = []
    rockLocations = []
    colIndex = random.randint(0, height)
    rowIndex = random.randint(0, width)


    #error checking
    if trees > (width * height) // 3:
        print("ERROR: TOO MANY TREES")
        return 0
    elif rocks > (width * height) // 3:
        print("ERROR: TOO MANY ROCKS")
        return 0

    # create a blank map full of grass
    for i in range(height):
        row = copy.copy(rowList)
        mapMatrix.append(row)

    # add trees
    for i in range(trees):
        while (rowIndex, colIndex) in treeLocations:
            colIndex = random.randint(0, height - 1)
            rowIndex = random.randint(0, width - 1)

        mapMatrix[rowIndex][colIndex] = 1
        treeLocations.append((rowIndex, colIndex))

    # add rocks
    for i in range(rocks):
        while (rowIndex, colIndex) in treeLocations \
        or (rowIndex, colIndex) in rockLocations:
            colIndex = random.randint(0, height - 1)
            rowIndex = random.randint(0, width - 1)

        mapMatrix[rowIndex][colIndex] = 2
        rockLocations.append((rowIndex, colIndex))

    # add the road
    if roadWidth > 0:
        ct = int(roadWidth // 2)
        road = int(height // 2)
        while ct > 0:
            mapMatrix[road + ct] = [3] * width
            mapMatrix[road - ct] = [3] * width
            ct -= 1
        mapMatrix[road] = [3] * width

    # convert to tuple
    for i in range(height):
        mapMatrix[i] = tuple(mapMatrix[i])

    # return
    return tuple(mapMatrix)

def showMap(screen, mapMatrix):
    """

    :param screen: pygame screen object
    images are blitted to this

    :param mapMatrix: list matrix
    contains the map matrix

    :return: none
    """

    # variable declarations
    height = pg.display.Info().current_h
    width  = pg.display.Info().current_w
    x_pos  = 0
    y_pos  = 0
    grass = pg.image.load("grass.png").convert()
    tree  = pg.image.load("tree.png").convert()
    rock  = pg.image.load("rock.png").convert()
    road  = pg.image.load("road.png").convert()

    for i in range(len(mapMatrix)):
        for j in range(len(mapMatrix[i])):
            if mapMatrix[i][j] == 0:
                screen.blit(grass, [x_pos, y_pos])
            elif mapMatrix[i][j] == 1:
                screen.blit(tree, [x_pos, y_pos])
            elif mapMatrix[i][j] == 2:
                screen.blit(rock, [x_pos, y_pos])
            elif mapMatrix[i][j] == 3:
                screen.blit(road, [x_pos, y_pos])
            x_pos += 16
        y_pos += 16
        x_pos = 0


def main():
    """
    calls functions to allow the game to run
    """
    # variable declarations
    done = False

    # initialize pygame
    pg.init()

    # make screen object
    size = (1600, 900)
    screen = pg.display.set_mode(size)

    # set window caption
    pg.display.set_caption("Bandit King")

    #manages FPS
    clock = pg.time.Clock()

    #creates map
    mapMatrix = createMap(30, 30, 140, 20, 2)

    while not done:
        # --- main event loop
        for event in pg.event.get():
            if event.type == pg.QUIT:
                done = True

        # --- game logic

        # --- drawing code
        showMap(screen, mapMatrix)

        # --- update screen
        pg.display.flip()

        # --- limit to 60 FPS
        clock.tick(60)

    #print("height: ", pg.display.Info().current_h, "width: ", pg.display.Info().current_w)
    pg.quit()




if __name__ == "__main__":
    main()

image.thumb.png.0ea4310a5a61221abd418b3186aff318.png


"""
Generates a basic map for combat
"""

__Author__ = "RidiculousName"
__date__ = "3/21/18"


import pygame as pg
import copy
import random


def createMap(width, height, trees, rocks, roadWidth):
    """
    creates a combat map

    :param width: int; 20-60
    width of map in squares
    :param height: int; 20-60
    height of map in squares
    :param trees: int; 0 to (width*height)//5
    # of trees in map
    :param rocks: int; 0 to (width*height)//5
    # of rocks in map
    :param roadWidth: int; 0-10
    width of road in map
    (if value=0, will not have a road)
    :return:
    tuple matrix of map
    """

# If you indent your text a bit it's simpler to read:
    """
    :param width: int; 20-60
        width of map in squares
    :param height: int; 20-60
        height of map in squares
    :param trees: int; 0 to (width*height)//5
        # of trees in map
    :param rocks: int; 0 to (width*height)//5
        # of rocks in map
    :param roadWidth: int; 0-10
        width of road in map
        (if value=0, will not have a road)
    :return:
        tuple matrix of map
    """

# If you also add empty lines, it's even better. I also made it full sentences.
    """
    :param width: int; 20-60
        Width of the map in squares.

    :param height: int; 20-60
        Height of the map in squares.

    :param trees: int; 0 to 1/5th of the map size.
        Number of trees in the map.

    :param rocks: int; 0 to 1/5th of the map size.
        Number of rocks in the map.

    :param roadWidth: int; 0-10
        Width of road in map, use 0 to skip making a road.


    :return:
        tuple matrix of map
    """

    # variable declarations
    mapMatrix = []
    rowList = [0] * width
    treeLocations = []
    rockLocations = []
    colIndex = random.randint(0, height)
    rowIndex = random.randint(0, width)


    #error checking
    if trees > (width * height) // 3:
        print("ERROR: TOO MANY TREES")
        return 0
    elif rocks > (width * height) // 3:
        print("ERROR: TOO MANY ROCKS")
        return 0

# So your documentation is lying, it's actually 1/3rd of the map. Introduce a
# constant for it, and use it both here and in the documentation, so it's
# always consistent.

    TREE_FRACTION = 0.33

    """
    ...
    :param trees: int; 0 upto TREE_FRACTION of the map.
        Number of trees in the map.
    ...
    """
    ...
    if trees > width * height * TREE_FRACTION:
        ...


    # create a blank map full of grass
    for i in range(height):
        row = copy.copy(rowList)
        mapMatrix.append(row)

    # add trees
    for i in range(trees):
        while (rowIndex, colIndex) in treeLocations:
            colIndex = random.randint(0, height - 1)
            rowIndex = random.randint(0, width - 1)

        mapMatrix[rowIndex][colIndex] = 1
        treeLocations.append((rowIndex, colIndex))

# After you planted a tree, the "while (rowIndex, colIndex) in treeLocations" will
# immediately hold for the next tree that you want to plant. The body of the
# while will then find a new coordinate, so nothing bad will happen. However,
# since you already know this will happen, why not avoid it completely?

    for i in range(trees):
        # Initialize the colIndex and rowIndex once before you test (rowIndex, colIndex).
        #
        # This makes the initialization higher up in the code also obsolete.
        # Finally, by doing it in this way, it is easier to understand what
        # code belongs together, and if you want to move some code to another
        # place, it's more likely you'll get it right the first time.
        colIndex = random.randint(0, height - 1)
        rowIndex = random.randint(0, width - 1)
        while (rowIndex, colIndex) in treeLocations:
            colIndex = random.randint(0, height - 1)
            rowIndex = random.randint(0, width - 1)

        mapMatrix[rowIndex][colIndex] = 1
        treeLocations.append((rowIndex, colIndex))

# A second problem is that treeLocations is a list, which is very slow to
# search if it has many entries. It is generally a good idea to avoid searching
# in a list. Searching for a dict key or in a set is much faster.

    filled = set()
    for i in range(trees):
        colIndex = random.randint(0, height - 1)
        rowIndex = random.randint(0, width - 1)
        while (rowIndex, colIndex) in filled: # Inspect the set
            colIndex = random.randint(0, height - 1)
            rowIndex = random.randint(0, width - 1)

        mapMatrix[rowIndex][colIndex] = 1
        treeLocations.append((rowIndex, colIndex))
        filled.add((rowIndex, colIndex)) # Add it to the set as well.

# Alternatively, why not use the mapMatrix for checking?
# I left treeLocations in now, but it could be removed entirely, as you don't
# seem to use it elsewhere.

    for i in range(trees):
        colIndex = random.randint(0, height - 1)
        rowIndex = random.randint(0, width - 1)
        while mapMatrix[rowIndex][colIndex] == 1:
            colIndex = random.randint(0, height - 1)
            rowIndex = random.randint(0, width - 1)

        mapMatrix[rowIndex][colIndex] = 1
        treeLocations.append((rowIndex, colIndex))

    # add rocks
    for i in range(rocks):
        while (rowIndex, colIndex) in treeLocations \
        or (rowIndex, colIndex) in rockLocations:
# Instead of having a separate rockLocations, you could use 'filled' again
# (just fill it further), or use mapMatrix again:
#
#       while mapMatrix[rowIndex][colIndex\ in (1, 2):
#
            colIndex = random.randint(0, height - 1)
            rowIndex = random.randint(0, width - 1)

        mapMatrix[rowIndex][colIndex] = 2
        rockLocations.append((rowIndex, colIndex))

    # add the road
    if roadWidth > 0:
        ct = int(roadWidth // 2)
        road = int(height // 2)
# "//" already converts to integer, no need to do it a second time with "int(..)"

        while ct > 0:
            mapMatrix[road + ct] = [3] * width
            mapMatrix[road - ct] = [3] * width
            ct -= 1
        mapMatrix[road] = [3] * width

# Instead of starting at the center, you can also start at an edge of the road:
        road = height // 2 - roadWidth // 2
        ct = 0
        while ct < roadWidth:
            mapMatrix[road + ct] = [3] * width
            ct = ct + 1

# Not sure why you do this conversion to tuples below, you can keep the lists just as easy.

    # convert to tuple
    for i in range(height):
        mapMatrix[i] = tuple(mapMatrix[i])

    # return
    return tuple(mapMatrix)

def showMap(screen, mapMatrix):
    """

    :param screen: pygame screen object
    images are blitted to this

    :param mapMatrix: list matrix
    contains the map matrix

    :return: none
    """

# mapMatrix is actually a tuple of tuples, rather than a list.

# When you don't return a value, the ':return:' entry is normally left out.
# Note that not returning a value is different from "return None" even though
# Python gives the same value in both cases. The reason is that often None has
# a special value in these cases, which is then described in the comment, for
# example ":return: Number of birds found in the map, or None if there are no trees."


    # variable declarations
    height = pg.display.Info().current_h
    width  = pg.display.Info().current_w
    x_pos  = 0
    y_pos  = 0
    grass = pg.image.load("grass.png").convert()
    tree  = pg.image.load("tree.png").convert()
    rock  = pg.image.load("rock.png").convert()
    road  = pg.image.load("road.png").convert()

    for i in range(len(mapMatrix)):
        for j in range(len(mapMatrix[i])):
            if mapMatrix[i][j] == 0:
                screen.blit(grass, [x_pos, y_pos])
            elif mapMatrix[i][j] == 1:
                screen.blit(tree, [x_pos, y_pos])
            elif mapMatrix[i][j] == 2:
                screen.blit(rock, [x_pos, y_pos])
            elif mapMatrix[i][j] == 3:
                screen.blit(road, [x_pos, y_pos])
            x_pos += 16
        y_pos += 16
        x_pos = 0
# You use row and column elsewhere, and here you use i, j, x, and y. It's less
# confusing if you use the same name for the same thing everywhere.
#
# i and xpos are directly coupled, and j and ypos are directly coupled, you
# need only one set of coordinates here (for example, replace 'xpos' by 'i *
# 16', and 'ypos' by j * 16'. Alternatively, compute the second set from the
# first set (see below what I mean).
#
# Each "if" computes "mapMatrix[i][j]". You can also compute that once and
# store it.
    for i in range(len(mapMatrix)):
        x_pos = i * 16
        for j in range(len(mapMatrix[i])):
            y_pos = j * 16

            mapVal = mapMatrix[i][j]
            if mapVal == 0:
                image = grass
            elif mapVal == 1:
                image = tree
            elif mapVal == 2:
                image = rock
            elif mapVal == 3:
                image = road

            screen.blit(image, [x_pos, y_pos])

# One step further, the 'if' checks for a constant number, and then selects an image.
# You can do that in a dict as well:

    inages = {0: grass,
              1: tree,
              2: rock,
              3: road, }

    for i in range(len(mapMatrix)):
        x_pos = i * 16
        for j in range(len(mapMatrix[i])):
            y_pos = j * 16

            mapVal = mapMatrix[i][j]
            image = images[mapVal]
            screen.blit(image, [x_pos, y_pos])

# And if you take out all the intermediates (not sure you want to do this):

    inages = {0: grass, 1: tree, 2: rock, 3: road}

    for i in range(len(mapMatrix)):
        for j in range(len(mapMatrix[i])):
            screen.blit(images[mapMatrix[i][j]],
                        [i * 16, j * 16])

def main():
    """
    calls functions to allow the game to run
    """
    # variable declarations
    done = False

    # initialize pygame
    pg.init()

    # make screen object
    size = (1600, 900)
    screen = pg.display.set_mode(size)

    # set window caption
    pg.display.set_caption("Bandit King")

# This comment just states what the code also says. Such comments is not
# useful. (You can assume the reader can read the code, or he/she wouldn't read
# your program.)
# In general, don't tell in comment what the code is doing (the code says that
# already). Instead, explain why, or explain the overall aim.


    #manages FPS
    clock = pg.time.Clock()

# This comment is useful, the code tells me you're reading the clock, but the
# code doesn't tell me why you are reading the clock. The comment does however.

    #creates map
    mapMatrix = createMap(30, 30, 140, 20, 2)

    while not done:
        # --- main event loop
        for event in pg.event.get():
            if event.type == pg.QUIT:
                done = True

        # --- game logic

# This comment states overall aim ("game logic is coming here"), so that's
# fine. The 3 comments below don't say much, you can just delete them, as the
# code says all already.

        # --- drawing code
        showMap(screen, mapMatrix)

        # --- update screen
        pg.display.flip()

        # --- limit to 60 FPS
        clock.tick(60)

# Try to avoid hard-coding magic numbers. Instead, make a constant for it

    FPS = 60
    clock = ...
    ...
    while not done:
        ...
        clock.tick(FPS)

# The number now has a name, which has meaning to the reader. Also, changing it
# to another value is much simpler now, as it's obvious which number to change.
#
# Look for other numbers that you have.


    #print("height: ", pg.display.Info().current_h, "width: ", pg.display.Info().current_w)
    pg.quit()




if __name__ == "__main__":
    main()

 

Just inserted comments and code in your program, while keeping your code nearby for comparison.

Hope it is useful for you.

 

 

Advertisement

Thank you! I'll take a look through it right now.

This topic is closed to new replies.

Advertisement