# Python 3 text-based RPG (to be upgraded to Pygame)

I've been working on a text-based RPG for a while now on-and-off. After a long hiatus, I've come back to the project.

My goal as of right now is to port the game from it's current Print-output version to a version that works in a Pygame window before I add anything else.

Now it's worth mentioning that I'm a complete noob to programming. This is my first project. I'm still pretty uncertain on the proper ways of separating logic/general code architecture.

This being said, here is my code.

Game File (FlubbosMagicForest.py):

from gameworld import *

def main():

player = Player("Jeff", 100)
bag = Bag([])
location = Location('introd')

command = '  '
while command != "":
command = input('>>> ')
if command in location.room.exits:
location.travel(command, bag)
elif command == 'look':
location.room_desc()
elif command == '':
print('You have to say what it is you want to do!')
command = '#'
elif command == 'search':
location.search_room()
elif command.split()[0] == 'Take':
location.check_take(command.split()[1], bag, location)
elif command == 'Inventory':
bag.check_inv()
else:
print('Invalid command')

if __name__ == '__main__':
main()


gameworld.py:

from gameitems import *

class Room:

def __init__(self, name, description, exits, actions, roominv, roomkey, lock):
self.name = name
self.description = description
self.exits = exits
self.actions = actions
self.roominv = roominv
self.roomkey = roomkey
self.lock = lock

class Player:

def __init__(self, name, health):
self.name = name
self.health = health

class Location:

def __init__(self, room):
self.room = world[room]

def travel(self, direction, bag):
if direction not in self.room.exits.keys():
self.no_exit()
else:
self.set_new_room_name(direction, bag)

def set_new_room_name(self, direction, bag):
new_room_name = self.room.exits[direction]
print("moving to", new_room_name)
self.key_check(new_room_name, bag)

def key_check(self, new_room_name, bag):
if world[new_room_name].lock and world[new_room_name].roomkey not in bag.inventory:
self.no_key()
else:
world[new_room_name].lock = False
self.set_room(new_room_name)
self.room_desc()

def set_room(self, new_room_name):
self.room = world[new_room_name]

def no_exit(self):
print("You can't go that way!")

def no_key(self):
print('The door is locked! You need the right key!')

def room_desc(self):
print(self.room.description)
print(self.room.actions)

def search_room(self):
if self.room.roominv:
for item in list(self.room.roominv.keys()):
print("you find a", item)
else:
print("You don't find anything")

def none_here(self, key):
print("You can't find a", key)

def check_take(self, key, bag, location):
if self.room.roominv and key in self.room.roominv:
print('you take the', key)
else:
self.none_here(key)

class Bag():

def __init__(self, inventory):
self.inventory = inventory

def add_to_inv(self, key, location):
self.inventory.append(location.room.roominv[key])
del location.room.roominv[key]

def check_inv(self):
for item in list(self.inventory):
print("Your bag contains:", item.name)

world = {}

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': "introd", 'e': "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': "cottage", 'e': "stream", 'w': "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': "shack", 'w': "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': "inside shack", 'n': "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': "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': "forest path"}, {"Go north"}, None, None, False)

world['inside cottage'] = Room('inside cottage', "The inside of the cottage is warm and cozy. It reeks like death.", {'n': 'outside cottage'}, None, {'Moonstone': Moonstone}, Ornate_Key, True)


gameitems.py:

class Items:
def __init__(self, name, info, weight):
self.name = name
self.info = info
self.weight = weight

class DoorKeys(Items):
def __init__(self, name, info, weight):
super().__init__(name, info, weight)

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

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)


Please let me know if anything is blaringly wrong, I'm sure there is. If anyone has any tips on restructuring the code to prepare it for a Pygame adaptation, that would be much appreciated.

main program:

while command != "":


could be

while command:


but you have to initialize command to non-empty. I'd do:

while True:


then at the end:

    if not command:
break


gameworld.py:

in Locations.travel, don't use in xxx.keys(). It doesn't matter much in python 3, but python 2 makes that a list, whereas the simplest & universal way is:

if direction not in self.room.exits:  # no .keys(), dicts support "in"


In Locations.check_take, don't check if the dict is empty, it's cumbersome:

if self.room.roominv and key in self.room.roominv:


should just be:

if key in self.room.roominv:


In Locations.check_inv:

for item in list(self.inventory):


why forcing iteration on self.inventory? Just do:

for item in self.inventory:


I'd like to add that your initialization of world is clumsy & error prone. You should loop on the items of a dictionary/list of dictionaries contained in a json configuration file instead, so anyone (even not a python coder) can improve the game map.

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)


(and the other entries) could be loaded from a dict containing the key (introd), the description, and the dictionary of directions & contents. This is more complex because you're using an object like Sword as key (so you'd need some kind of evaluation/lookup table to create the object, not a big deal). Think about it.

gameitems.py:

not much, except that this code is redundant:

class DoorKeys(Items):
def __init__(self, name, info, weight):
super().__init__(name, info, weight)


could just be:

class DoorKeys(Items):
pass


at this point, since you're not adding anything specific. So DoorKeys object is probably redundant as well unless you're planning to add specific stuff.

I feel like utilizing classes for some parts but disregarding them for others is throwing the entire thing off.

The massive if/elif/else block in the command file could easily be a list of predefined Command() instances, and a step further, referenced inside of a Commandlist() instance. It might take a little bit more time to code all of the class definitions, but everything would be stored more neatly, and would be easier to reference. As a side note, cmd2 works pretty well for all text-based, has an easy way to define commands, and could be used to test that things are functioning properly.

It would also be fairly easy, and far more organized, to define a World class with various attributes.

class World():
def __init__(self, rooms, start_id = 1, num_of_rooms = 3):
self.rooms = _rooms
self.id = _id
self.num_of_rooms = _num_of_rooms


You could easily add other attributes to World if needed, and it helps to set up for what @Jean mentioned, which is feeding in data from json files. Surprisingly easy to do, and while the files aren't the prettiest format, it's still much more appealing than scrolling through a wall of code to change a specific room's name because it was hardcoded into a file.

This opens up the possibility for using many other class instances to hold all of your data and storing them within a World() instance for easier access. All you would need is a method to load the data as a dictionary and unpack it to the arguments of an object instance. Note, I'm using pkg_resources with this, so you would need to import pkg_resources or just the specific re

def _build_room(self, id):
jsontext = resource_string(__name__, 'data/dungeon{}/room{}.json'.format(self._id, id))
d['id'] = id
room = R.Room(**d)
return room


Of course you would also need a method which utilizes build_room() to create a dictionary of rooms for World.rooms, but that's as easy as...

def _update_rooms(self):
d = {}
for i in range(self._start_id, self._num_of_rooms + 1):
try:
d[i] = self._build_room(i)
except FileNotFoundError:
print("File not found.  Please check to make sure it exists")
self._rooms = d


This same principle could be used to create a container for all items in a game, an inventory system, characters, and many other possibilities.

Note that I'm not advocating this as the best way, or even a good way. I'm still a beginner and there are many concepts I'm still fuzzy on. This is just the conclusion I reached from all of my various reading of things other people who seem to actually know what they're talking about have typed out. Also, I'm no help down the road towards GUI, and I have no idea if any of this would muddle an approach towards it or ease it.

I'm actually working on something very similar to this. If you're interested, you can find it all here.

• Hrm...now I'm curious about the downvote. I didn't think anything in my post was inherently bad, as I was mostly just saying that it would make sense to make everything OOP instead of just half of the code. Maybe my examples weren't the best...but whatever. – Noved Jun 16 '18 at 16:27