DungeonSmash

The game screen appears here if your browser supports the Canvas API.

Attribution

Make your own procedurally generated dungeons, pages 32-38, by Mac Bowley.

Licensed under Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported.

Original Python code


# Importing modules
import secrets # Secrets is a crypto grade randomness library - in my opinion better than random for this.
import math # For the math
from collections import namedtuple
from pygame import Rect

WIDTH = 1024
HEIGHT = 768

# Map variables
TILE_SIZE = 32 # Dimensions of the tiles - change if neccessary
TILE_ACROSS = int(WIDTH/TILE_SIZE) # Use the previous variables to set a tile width
TILE_DOWN = int(HEIGHT/TILE_SIZE) # and tile height

MAX_ROOM_SIZE = 7
MIN_ROOM_SIZE = 3
ROOM_PADDING = 3

MAP_BORDER = 1

NUMBER_OF_ROOMS = 12

LOOT_ROOMS = 2

MAX_ENEMIES = NUMBER_OF_ROOMS*2

Room = namedtuple('Room', ['width', 'height', 'pos_x', 'pos_y'])

def generate_room():
    rm_width = MIN_ROOM_SIZE + secrets.randbelow(MAX_ROOM_SIZE - MIN_ROOM_SIZE + 1)
    rm_height = MIN_ROOM_SIZE + secrets.randbelow(MAX_ROOM_SIZE - MIN_ROOM_SIZE + 1)

    rm_pos_x = secrets.randbelow(TILE_ACROSS - rm_width)
    if rm_pos_x < MAP_BORDER:
        rm_pos_x = MAP_BORDER
    rm_pos_y = secrets.randbelow(TILE_DOWN - rm_height)
    if rm_pos_y < MAP_BORDER:
        rm_pos_y = MAP_BORDER

    return Room(rm_width, rm_height, rm_pos_x, rm_pos_y)

def create_rooms():
    Rooms = []
    for i in range(NUMBER_OF_ROOMS):
        intersect = True
        while intersect:
            intersect = False
            rm = generate_room()
            for other in Rooms:
                room = other[0]
                padded_pos_x = rm.pos_x - 1
                padded_pos_y = rm.pos_y - 1
                padded_width = rm.width + 3
                padded_height = rm.height + 3
                rm_1 = Rect((room.pos_x*32, room.pos_y*32), (room.width*32, room.height*32))
                rm_2 = Rect((padded_pos_x*32, padded_pos_y*32), (padded_width*32, padded_height*32))
                if rm_1.colliderect(rm_2):
                    intersect = True
        Rooms.append([rm, {"NORTH": None, "SOUTH": None, "EAST": None, "WEST": None}])
    return Rooms

def create_corridors(rm, Rooms):
    candidates = {"NORTH": None, "SOUTH": None, "EAST": None, "WEST": None}
    for other in Rooms:
        if other[0] != rm[0]:
            current_room = rm[0]
            other_room = other[0]
            left_marker = max(current_room.pos_x, other_room.pos_x)
            right_marker = min(current_room.pos_x + current_room.width, other_room.pos_x + other_room.width)
            horizontal_overlap = list(range(left_marker, right_marker))
            if len(horizontal_overlap) > 0:
                vertical_corridors(candidates, other, rm, horizontal_overlap)
            top_marker = max(current_room.pos_y, other_room.pos_y)
            bottom_marker = min(current_room.pos_y + current_room.height, other_room.pos_y + other_room.height)
            vertical_overlap = list(range(top_marker, bottom_marker))
            if len(vertical_overlap) > 0:
                horizontal_corridors(candidates, other, rm, vertical_overlap)
    return candidates


def vertical_corridors(candidates, other, rm, horizontal_overlap):
    current_room = rm[0]
    current_connections = rm[1]
    other_room = other[0]
    other_connections = other[1]
    if current_room.pos_y > other_room.pos_y and other_connections["SOUTH"] == None and current_connections["NORTH"] != 0:
        connector = candidates["NORTH"]
        if connector == None:
            candidates["NORTH"] = (other, horizontal_overlap)
            other_connections["SOUTH"] = 0
        else:
            if other_room.pos_y + other_room.height > connector[0][0].pos_y + connector[0][0].height:
                connector[0][1]["SOUTH"] = None
                candidates["NORTH"] = (other, horizontal_overlap)
                other_connections["SOUTH"] = 0
    if current_room.pos_y < other_room.pos_y and other_connections["NORTH"] == None and current_connections["SOUTH"] != 0:
        connector = candidates["SOUTH"]
        if connector == None:
            candidates["SOUTH"] = (other, horizontal_overlap)
            other_connections["NORTH"] = 0
        else:
            if other_room.pos_y < connector[0][0].pos_y:
                connector[0][1]["NORTH"] = None
                candidates["SOUTH"] = (other, horizontal_overlap)
                other_connections["NORTH"] = 0

def horizontal_corridors(candidates, other, rm, vertical_overlap):
    current_room = rm[0]
    current_connections = rm[1]
    other_room = other[0]
    other_connections = other[1]
    if current_room.pos_x > other_room.pos_x and other_connections["EAST"] == None and current_connections["WEST"] != 0:
        connector = candidates["WEST"]
        if connector == None:
            candidates["WEST"] = (other, vertical_overlap)
            other_connections["EAST"] = 0
        else:
            if other_room.pos_x < connector[0][0].pos_x:
                connector[0][1]["EAST"] = None
                candidates["WEST"] = (other, vertical_overlap)
                other_connections["EAST"] = 0
    if current_room.pos_x < other_room.pos_x and other_connections["WEST"] == None and current_connections["EAST"] != 0:
        connector = candidates["EAST"]
        if connector == None:
            candidates["EAST"] = (other, vertical_overlap)
            other_connections["WEST"] = 0
        else:
            if other_room.pos_x + other_room.width < connector[0][0].pos_x + connector[0][0].width:
                connector[0][1]["WEST"] = None
                candidates["EAST"] = (other, vertical_overlap)
                other_connections["WEST"] = 0

def find_position(Room, Map):
    x_pos = Room.pos_x + secrets.randbelow(Room.width - 1)
    y_pos = Room.pos_y + secrets.randbelow(Room.height - 1)
    while Map[x_pos][y_pos] != 1:
        x_pos = Room.pos_x + secrets.randbelow(Room.width)
        y_pos = Room.pos_y + secrets.randbelow(Room.height)
    return x_pos, y_pos

def placeEnemies(Rooms, Map):
    for i in range(NUMBER_OF_ROOMS - LOOT_ROOMS):
        room = secrets.choice(Rooms)
        amount_of_enemies = secrets.randbelow(4)
        for enemy in range(amount_of_enemies):
            x_pos, y_pos = find_position(room[0], Map)
            Map[x_pos][y_pos] = 4
        chest_x_pos, chest_y_pos = find_position(room[0], Map)
        Map[chest_x_pos][chest_y_pos] = 6
        Rooms.remove(room)
    return Map

def placeBoss(boss_room, Map):
    x_pos = boss_room.pos_x + (boss_room.width//2)
    y_pos = boss_room.pos_y + (boss_room.height//2)

    Map[x_pos][y_pos] = 5

    boss_enemies = secrets.randbelow(4) + 1

    for henchman in range(boss_enemies):
        hench_x, hench_y = find_position(boss_room, Map)
        Map[hench_x][hench_y] = 4

    for chest in range(2):
        chest_x_pos, chest_y_pos = find_position(boss_room, Map)
        Map[chest_x_pos][chest_y_pos] = 6

    return Map

def placePlayer(room, Map):
    player_x, player_y = find_position(room, Map)
    Map[player_x][player_y] = 7
    return Map

def populateRooms(Rooms, Map):
    biggestArea = 0
    biggestRoom = ""
    smallestArea = math.inf
    smallestRoom = ""

    for rm in Rooms:
        area = rm[0].width * rm[0].height
        if area >= biggestArea:
            biggestArea = area
            biggestRoom = rm
        if area <= smallestArea:
            smallestArea = area
            smallestRoom = rm
    rooms_to_populate = Rooms
    rooms_to_populate.remove(biggestRoom)
    rooms_to_populate.remove(smallestRoom)

    Map = placeEnemies(rooms_to_populate, Map)

    boss_room = biggestRoom[0]
    Map = placeBoss(boss_room, Map)

    player_spawn = smallestRoom[0]
    Map = placePlayer(player_spawn, Map)
    return Map

def create_map():
    Map = []
    for x in range(TILE_ACROSS):
        row = []
        for y in range(TILE_DOWN):
            row.append(0)
        Map.append(row)
    Rooms = create_rooms()
    for rm in Rooms:
        for x in range(rm[0].width):
            for y in range(rm[0].height):
                Map[rm[0].pos_x+x][rm[0].pos_y+y] = 1
        rm[1] = create_corridors(rm, Rooms)
        #Create the corridors on the map array
        for key, value in rm[1].items():
            if len(rm[1].items()) > 1:
                skip = secrets.randbelow(100) # Chance to skip drawing this corridor
            else:
                skip = 100
            if value is not None and value is not 0 and skip > 20:
                dir = [0, 0]
                start_pos = [rm[0].pos_x, rm[0].pos_y]
                end_pos = [value[0][0].pos_x, value[0][0].pos_y]
                mid_overlap = value[1][len(value[1])//2]
                if key == "NORTH":
                    dir[1] = -1
                    start_pos[0] = mid_overlap
                    start_pos[1] -= 1
                    end_pos[0] = mid_overlap
                    end_pos[1] += value[0][0].height
                    tile = 3
                elif key == "SOUTH":
                    dir[1] = 1
                    start_pos[0] = mid_overlap
                    start_pos[1] += rm[0].height
                    end_pos[0] = mid_overlap
                    end_pos[1] -= 1
                    tile = 3
                elif key == "EAST":
                    dir[0] = 1
                    start_pos[0] += rm[0].width
                    start_pos[1] = mid_overlap
                    end_pos[0] -= 1
                    end_pos[1] = mid_overlap
                    tile = 2
                elif key == "WEST":
                    dir[0] = -1
                    start_pos[0] -= 1
                    start_pos[1] = mid_overlap
                    end_pos[0] += value[0][0].width
                    end_pos[1] = mid_overlap
                    tile = 2
                Map[start_pos[0]][start_pos[1]] = tile
                Map[end_pos[0]][end_pos[1]] = tile
                distance = (start_pos[0] - end_pos[0]) + (start_pos[1] - end_pos[1])
                next_pos = start_pos
                for i in range(abs(distance)):
                    next_pos = [next_pos[0] + dir[0], next_pos[1] + dir[1]]
                    if Map[next_pos[0]][next_pos[1]] == 0:
                        Map[next_pos[0]][next_pos[1]] = tile
    Map = populateRooms(Rooms, Map)
    return Map


from Map_Generator import create_map
from tiles import *
from collections import namedtuple

## Dimensions of the tiles - change if neccessary
TILE_SIZE = 32

## Window Parameters // Viewport
SCREEN_WIDTH = 10
SCREEN_HEIGHT = 8

## Pygame Zero width and height for the game
WIDTH = SCREEN_WIDTH * TILE_SIZE
HEIGHT = SCREEN_HEIGHT * TILE_SIZE

## Set up world co-ordinates
## Top left corner of the screen
screen_pos = [0, 0]
## Player world co-ordinates
playerTile = [0, 0]

## Create the player Actor
player = Actor(player_image)

## Create the player variables
class Player:
    def __init__(self, actor, health, inventory):
        self.Actor = actor
        self.Health = health
        self.Inventory = inventory


    def Attack(self):
        global playerTile, Map
        for y in range(-1, 2):
            for x in range(-1, 2):
                check_x = playerTile[0] + x
                check_y = playerTile[1] + y
                if Map[check_x][check_y] == 4 or Map[check_x][check_y] == 5:
                    Map[check_x][check_y] = 1
                    break

player_object = Player(player, 100, {})


## Generate the first Map
Map = create_map()
## Search the Map for spawn positions
for x in range(len(Map)):
    for y in range(len(Map[0])):
        if Map[x][y] == 7:
            playerTile = [x, y]
            Map[x][y] = 1

def camera_follow():
    global screen_pos
    screen_pos[0] = playerTile[0] - (SCREEN_WIDTH//2)
    screen_pos[1] = playerTile[1] - (SCREEN_HEIGHT//2)

    if screen_pos[0] < 0:
        screen_pos[0] = 0
    elif screen_pos[0] + SCREEN_WIDTH > len(Map):
        screen_pos[0] = len(Map)-SCREEN_WIDTH

    if screen_pos[1] < 0:
        screen_pos[1] = 0
    elif screen_pos[1] + SCREEN_HEIGHT > len(Map[0]):
        screen_pos[1] = len(Map[0])-SCREEN_HEIGHT

camera_follow()

def on_key_down(key):
    global playerTile
    if key == key.A :
        player_object.Attack()
    new_pos = [playerTile[0], playerTile[1]]
    if key == key.RIGHT:
        new_pos[0] += 1
    if key == key.LEFT:
        new_pos[0] -= 1
    if key == key.UP:
        new_pos[1] -= 1
    if key == key.DOWN:
        new_pos[1] += 1
    if movement_collision(new_pos) == True:
        playerTile[0] = new_pos[0]
        playerTile[1] = new_pos[1]
    camera_follow()

def movement_collision(position):
    global Map
    tile = Map[position[0]][position[1]]
    if tile == 1 or tile == 2 or tile == 3 or tile == 4:
        return True
    elif tile == 0:
        return False
    elif tile == 6:
        Map[position[0]][position[1]] = 1
        print("Gold!")

def draw_level():
    global player
    for i in range(SCREEN_WIDTH):
        for j in range(SCREEN_HEIGHT):
            x = screen_pos[0] + i
            y = screen_pos[1] + j
            if Map[x][y] == 0:
                screen.blit(dirt_tile, (i*32, j*32))
            elif Map[x][y] == 1:
                screen.blit(room_tile, (i*32, j*32))
            elif Map[x][y] == 2:
                screen.blit(south_corridor, (i*32, j*32))
            elif Map[x][y] == 3:
                screen.blit(north_corridor, (i*32, j*32))
            elif Map[x][y] == 4:
                screen.blit(room_tile, (i*32, j*32))
                screen.blit(enemy_basic, (i*32, j*32))
            elif Map[x][y] == 5:
                screen.blit(room_tile, (i*32, j*32))
                screen.blit(enemy_boss, (i*32, j*32))
            elif Map[x][y] == 6:
                screen.blit(room_tile, (i*32, j*32))
                screen.blit(chest, (i*32, j*32))
            elif Map[x][y] == 7:
                screen.blit(room_tile, (i*32, j*32))

            if x == playerTile[0] and y == playerTile[1]:
                player.x = i*32+player.width//2
                player.y = j*32+player.height//2

def draw():
    draw_level()
    player.draw()