Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

Python

Dungeon game - OOP basics

Hello everyone!

I wanted to solidify my knowledge of the Python basics and OOP course by making the dungeon game that was suggested as a project after completing the memory game. Please have a look and review my code, if you have an alternate or easier way of coding something, please let me know, it's very appreciated.

Thanks also to @mel Rumsey who suggested I should use a linter or pep8 checker before I post my code to make sure it's clean and follow syntax rules of python coding.

import random
import os


class Game:

    def __init__(self, game_size):
        self.game_size = game_size
        self.game_field = []
        self.debug_mode = False

        for x in range(game_size):
            for y in range(game_size):
                self.game_field.append((x, y))

        self.door = random.choice(self.game_field)
        self.door_visible = False

    def cls(self):
        os.system('clear')

    def draw_grid(self, player, monster):
        if monster.position == self.door:
            self.door = random.choice(self.game_field)
        grid_ceiling = " " + ("_ " * int(self.game_size))
        print(grid_ceiling, end="\n")
        for cell in self.game_field:
            x, y = cell
            if (x, y) == player.position and player.visible:
                fill = player
            elif (x, y) == monster.position and monster.visible:
                fill = monster
            elif (x, y) == self.door and self.door_visible:
                fill = "D"
            else:
                fill = "_"
            if y == int(self.game_size) - 1:
                print(f"|{fill}|", end="\n")
            else:
                print(f"|{fill}", end="")

    def show_help(self):
        self.cls()
        print("To move, please write any direction that is"
              "avaiable in the 'available moves' list")
        print("To enter debug mode, write: 'debug'")
        print("To show this help: write 'help'")
        input()

    def check_win(self, player, monster):
        if player == monster:
            monster.visible = True
            return "lose"
        elif player.position == self.door:
            self.door_visible = True
            return "win"
        else:
            return "continue"


class Player:

    def __init__(self, game_field, position):
        self.position = position
        self.symbol = "X"
        self.visible = True

    def __str__(self):
        return str(self.symbol)

    def __eq__(self, other):
        return self.position == other.position

    def show_moves(self, grid_size):
        move_options = ["UP", "DOWN", "LEFT", "RIGHT"]
        x, y = self.position

        if x == 0:
            move_options.remove("UP")
        if y == 0:
            move_options.remove("LEFT")
        if x == grid_size - 1:
            move_options.remove("DOWN")
        if y == grid_size - 1:
            move_options.remove("RIGHT")

        return move_options

    def move_player(self, direction, game_field):
        x, y = self.position

        if direction == "UP":
            x -= 1
        elif direction == "DOWN":
            x += 1
        elif direction == "LEFT":
            y -= 1
        elif direction == "RIGHT":
            y += 1

        if (x, y) not in game_field:
            raise ValueError

        else:
            self.position = x, y


class Monster:

    def __init__(self, game_field, position):
        self.position = position
        self.symbol = "@"
        self.visible = False

    def __str__(self):
        return str(self.symbol)


if __name__ == '__main__':

    program = True

    while program:
        field_size = input("Welcome to the Dungeon game, please" 
                           "select difficulty: 'easy', 'medium' or 'hard'")
        difficulty = {"easy": 4, "medium": 5, "hard": 6}
        game = Game(difficulty[field_size])
        # Making sure all positions are unique
        pos_door, pos_monster, pos_player = random.sample(game.game_field, 3)
        player = Player(game.game_field, pos_player)
        monster = Monster(game.game_field, pos_monster)
        game.door = pos_door
        game_loop = True

        while game_loop:
            game.cls()
            game.draw_grid(player, monster)
            available_moves = player.show_moves(difficulty[field_size])
            print("\nAvailable Moves: ", ", ".join(available_moves))
            move = input().upper()
            if move == "HELP":
                game.show_help()
            elif move == "DEBUG":
                monster.visible = True
                game.door_visible = True
                game.debug_mode = True
            else:
                try:
                    player.move_player(move, game.game_field)
                except ValueError:
                    print("Move is out of bounds")
                    input()
                else:
                    if game.check_win(player, monster) == "win":
                        game.cls()
                        player.visible = False
                        game.draw_grid(player, monster)
                        print("You won the game!")
                        break
                    elif game.check_win(player, monster) == "lose":
                        game.cls()
                        player.visible = False
                        game.draw_grid(player, monster)
                        print("You lost the game, GAME OVER!")
                        break
                    else:
                        continue

        retry = input("Play again? Y/N    ").upper()
        if retry == "Y" or retry == "YES":
            game.cls()
            continue
        else:
            print("Okay, thanks for playing, bye!")
            program = False

1 Answer

Hi Isak Landström ! This is awesome. I won on my first try! (Granted, it was on easy 😜 lol lost on hard of course haha)

The code looks excellent. You have some good error handling for when a player enters a direction that is out of bounds. I would suggest also adding some error handling to the inputs especially to the main menu. The game crashes if anything other than 'easy', 'medium', or 'hard' is entered.


So, one thing I noticed is that the clear command doesn't work for players who are on Windows. Anytime it tries to clear there is an error in the console that says

'clear' is not recognized as an internal or external command,
operable program or batch file.

This is because clear is not a command for windows. So, a way to fix this so that it works for anyone, regardless of their os is by adding an if/else statement in the system method:

def clear():
    os.system('cls' if os.name == 'nt' else 'clear')

Windows, oddly returns 'nt' as the os.name, so it will type cls and clear the console if it's windows... else it will run clear which will work for Mac.


Your OOP looks solid! Nice job creating this game with different difficulties, everything runs pretty smoothly. Also, that pep8 compliant code you have there is beautiful! ;)

Hi and thank you once again for a great reply.

Yeah, there might have been one or two places where I could have handled the error the user might have generated (god damn users...!). Did you notice my debug method though? It was very useful when I wrote the code cause i could for example see that when I sometimes got the same position for door as monster, which made it so I couldn't win/lose the game. I fixed this error by making sure I got 3 unique samples with the random.sample function. Before this I asigned the position in the init method and used random.choice but that sometimes like I mentioned above gave the same position for either door or monster, so random.sample was the solution for me here.

I have seen the clear and 'cls' before but this time I forgot to add it :). What I am curious about though is the syntax in the clear method:

def clear():
    os.system('cls' if os.name == 'nt' else 'clear')

For me this way of using the 'if' keyword deviates from what I'm normally have been seeing. Here 'if' is preceeded by 'cls' and furthermore there is no ':' operator that indicates that the if clause body starts. Maybe this is just some pythonic way of writing an if/else clause?

Cheers, Isak