8
\$\begingroup\$

Over the last couple of months I've been working on a zork-style text based adventure game to kind of crash-course teach myself Python. I've gotten to the point in it where I'm fairly proud of what I've made so far, although it's not complete I think that it's ready enough to post on here.

    world = {}  # Creates World Dict which stores Rooms


class Items:  # Creates template for all items
    def __init__(self, name, info, weight):
        self.name = name
        self.info = info
        self.weight = weight


class DoorKeys(Items):  # Allows Door Keys to be stored as a separate subclass from Items, function the same as Items
    def __init__(self, name, info, weight):
        super().__init__(name, info, weight)


class Weapon(Items):  # Creates Weapon subclass of Items class
    def __init__(self, name, info, damage, speed, weight):
        super().__init__(name, info, weight)
        self.damage = damage
        self.speed = speed


# This section contains all Items in game
Sword = Weapon("Sword", "A sharp looking sword. Good for fighting goblins!", 7, 5, 5)
Knife = Weapon("Knife", "A wicked looking knife, seems sharp!", 5, 7, 3)
Stick = Weapon("Stick", "You could probably hit someone with this stick if you needed to", 2, 3, 3)
Rusty_Key = DoorKeys("Rusty_Key", "A key! I wonder what it opens.", .01)
Ornate_Key = DoorKeys("Ornate_Key", "An ornate key with an engraving of a small cottage on one side", .01)
Moonstone = Items("Moonstone", "A smooth white stone that seems to radiate soft white light", .05)
Flower = Items("Flower", "A beautiful wildflower", .001)


class Room:  # Creates template for all rooms

    def __init__(self, name, description, exits, actions, roominv, roomkey, lock):  # Runs every time a new room is created
        self.name = name
        self.description = description
        self.exits = exits
        self.actions = actions
        self.roominv = roominv
        self.roomkey = roomkey
        self.lock = lock


# This section contains all Rooms in game
world['introd'] = Room('introd', "You are in a forest, you can hear wildlife all around you. There seems to be a clearing in the distance.", {'n': "clearing"}, {"Search the ground", "Go North"}, {'Sword': Sword}, None, False)

world['clearing'] = Room('clearing', "You are in a clearing surrounded by forest. Sunlight is streaming in, illuminating a bright white flower in the center of the clearing. \
To the South is the way you entered the forest. A well worn path goes to the East. In the distance a harp can be heard.", {'s' or 'south' or 'go south': "introd", 'e' or 'east' or 'go east': "forest path"}, {"Take flower", "Go south", "Go East"}, {'Flower': Flower}, None, False)

world['forest path'] = Room('forest path', "You begin walking down a well beaten path. The sounds of the forest surround you. Ahead you can see a fork in the road branching to the South and East.\
You can smell smoke coming from the South, and can hear a stream to the East", {'s' or 'south' or 'go south': "cottage", 'e' or 'east' or 'go east': "stream", 'w' or 'west' or 'go west': "clearing"}, {"Go South", "Go East", "Go West"}, {'Stick': Stick}, None, False)

world['stream'] = Room('stream', "You come upon a relaxing stream at the edge of the woods. It looks like there is something shiny in the water. To your South is a rickety looking shack, \
to your West is the forest path you came down", {'s' or 'south' or 'go south': "shack", 'w' or 'west' or 'go west': "forest path"}, {"Go South", "Go West"}, {'Rusty_Key': Rusty_Key}, None, False)

world['shack'] = Room('shack', "In front of you is a shack, possibly used as an outpost for hunting. It looks dilapidated.", {'s' or 'south' or 'go south': "inside shack", 'n' or 'north' or 'go north': "stream"}, {"Go South", "Go North"}, None, None, False)

world['inside shack'] = Room('inside shack', "The inside of the shack is dirty. Bits of ragged fur are scattered about the floor and on a table against the back wall.\
A sharp looking knife is on the table. There is an ornate key hanging on the wall by a string.", {'n' or 'north' or 'go north': "shack"}, {"Go North", "Take Knife", "Take Key"}, {'Knife': Knife, 'Ornate_Key': Ornate_Key}, Rusty_Key, True)

world['cottage'] = Room('cottage', "A quaint cottage sits in the middle of a small clearing, smoke drifting lazily from the chimney.", {'n' or 'north' or 'go north': "forest path"}, {"Go north"}, {'Moonstone': Moonstone}, Ornate_Key, True)


class Player:  # The Player class stores all attributes of the player character, and allows the player to interact with the game

    def __init__(self, name, health, bag, room_name):
        self.name = name
        self.health = health
        self.bag = bag
        self.room = world[room_name]

        def travel(self, direction):  # The travel function takes 2 args, the 'direction' arg is provided by player command
            if direction not in self.room.exits.keys():  # Checks if the direction arg does not correspond to an exit of the current room
                print("You can't go that way!")  # If direction does not match up to a possible exit, this string is displayed to the player
            else:  # If direction DOES match up to a possible exit
                new_room_name = self.room.exits[direction]  # Direction arg is used as key to self.room.exits dict; Variable named new_room_name is created and set to value that corresponds to key
                print("moving to", new_room_name)  # Prints "moving to" whatever room you are moving to
            if world[new_room_name].lock is True and world[new_room_name].roomkey not in self.bag:  # Checks to see if the room you are attempting to move to's lock status is set to true, then checks to see if player inventory does not contain the key that opens that lock
                print('The door is locked! You need the right key!')  # if player inventory does NOT contain the key, this message is displayed to the player
            else:  # Otherwise
                world[new_room_name].lock = False  # change the room you are attempting to move to's lock status to False
                self.room = world[new_room_name]  # and set the current room equal to the room that you are attempting to move to
                print(self.room.description)  # Then print a description of the current room
                print(self.room.actions)  # and possible actions in that room; printing on separate lines because it looks nicer.

        def addToInventory(item):  # Creates ability to pick up items
            self.bag.append(self.room.roominv[key])  # adds specific item to player inventory from current room's roominv, key provided by player command
            del self.room.roominv[key]  # Removes the item that you picked up from the roominv

        command = '  '
        while command != "":
            command = input('>>> ')
            if command in self.room.exits:
                travel(self, command)
            elif command == 'look':
                print(self.room.description)
                for item in list(self.room.exits.keys()):
                    print('Exits', item)
            elif command == '':
                print('You have to say what it is you want to do!')
                command = '#'
            elif command == 'search':
                for item in list(self.room.roominv.keys()):
                    print("you find a", item)
                if not self.room.roominv:
                    print("You don't find anything")
            elif command.split()[0] == 'Take':
                for key in list(self.room.roominv.keys()):
                    if self.room.roominv[key].name == command.split()[1]:
                        addToInventory(key)
            elif command == 'Inventory':
                for item in list(self.bag):
                    print("Your bag contains:", item.name)
            else:
                print('Invalid command')


player = Player("Jeff", 100, [], 'introd')

here is an example game

>>> look
You are in a forest, you can hear wildlife all around you. There seems to be a clearing in the distance.
Exits n
>>> search
you find a Sword
>>> Take Sword
>>> Inventory
Your bag contains: Sword
>>> n
moving to clearing
You are in a clearing surrounded by forest. Sunlight is streaming in, illuminating a bright white flower in the center of the clearing. To the South is the way you entered the forest. A well worn path goes to the East. In the distance a harp can be heard.
{'Go East', 'Go south', 'Take flower'}
>>> Take Flower
>>> Inventory
Your bag contains: Sword
Your bag contains: Flower
>>> look
You are in a clearing surrounded by forest. Sunlight is streaming in, illuminating a bright white flower in the center of the clearing. To the South is the way you entered the forest. A well worn path goes to the East. In the distance a harp can be heard.
Exits s
Exits e
>>> e
moving to forest path
You begin walking down a well beaten path. The sounds of the forest surround you. Ahead you can see a fork in the road branching to the South and East.You can smell smoke coming from the South, and can hear a stream to the East
{'Go East', 'Go West', 'Go South'}
>>> e
moving to stream
You come upon a relaxing stream at the edge of the woods. It looks like there is something shiny in the water. To your South is a rickety looking shack, to your West is the forest path you came down
{'Go West', 'Go South'}
>>> search
you find a Rusty_Key
>>> Take Rusty_Key
>>> 

I would love any advice or critiques about my code, I'm currently working on implementing NLTK to allow it to understand more vague/complex commands (i.e. "Go north" instead of only understanding "n").

I'm very new to coding in general and python so i wanted to check and see if this architecture/structure makes sense for building a game like this.

Ideally i'd like this to be an engine that I could use to make different text-games of different genre's.

\$\endgroup\$
8
\$\begingroup\$
  1. There's a very serious architectural problem with your code. There's essentially no separation of concerns. The logic of the game is intertwined with user interactions and the game configuration. It makes the code:

    • essentially untestable. It's impossible to write decent unit-tests for a method if the only visible effect is something being printed to the screen (and, possibly, a change of the internal state of an object).

    • hard to modify. You can't change just the logic or just the user interaction part. You'll have to update both.

    • very hard to read. Too many things are going on at the same time.

  2. You need to separate the logic of the game from all other things. Create a bunch of small, reusable, testable methods. For instance, you need to check if the player can go to a specific room. Good. Make it a method. Name it properly (for instance, can_go(self, direction)). Write unit tests for it. Identify the next small reusable component. Create a method for it. Keep doing it until the code becomes testable and readable.

  3. One class should have one well-defined responsibility. Your Player class does too much. It stores everything, including the location, items and so on. Move things to where they belong. Create a Location class that stores the current position and processes all movements. Create a Bag class that represents a bag of items and handles all operations for items (like finding a new one or using an existing one). A rule of thumb: you should be able to describe what instances of the class represent without using the word and. The same goes for methods.

  4. Create separate functions or classes for displaying the information to the user and reading her commands. It has nothing to do with the logic of the game. Keep the concerns separated.

  5. Using expressions like world[new_room_name].lock is True is a terrible practice. If something is a bool, treat as such. It's just world[new_room_name].lock.

  6. Get rid of useless comments. They shouldn't tell what the code does. Ideally, the code should be self-documenting. Comments # Removes the item that you picked up from the room or # Creates World Dict which stores Rooms are far worse than no comments whatsoever. They're litter. Burn them.

  7. It would be nice to load the game config (rooms, items and so on) from a file instead of having them hard-coded in your application. It'll make your app more flexible and readable (those long declarations occupy a lot of space and clutter the code). At least move them to a separate .py config file. Don't mix the logic with them.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.