3
\$\begingroup\$

I am developing a text-based RPG with a randomly generated world space. I have built a light engine to produce a world for me and fill it with 'world objects' like trees, mountains, villages, and the like. The world can be generated either through a fully automatic method, or a "manual" method, that allows for more specific placement of world objects.

My code might be a bit sloppy, since I'm rather new to Python programming and the world of OOP. That being said, the following code provides the complete WorldSpace class, but a rather bare-bones version on the Player class -- a version that just specifies some basic variables to show how I will be structuring the real Player class.

I believe all of this code to be in working condition. My apologies if any of you get errors.

from random import randint, choice, randrange

Player class:

class Player(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.baseStats = {'health': 1, 'stamina': 0, 'mana': 0, 'equip load': 0,
        'level': 1}

        self.descStats = {'name': None, 'race': None, 'class': None,
        'description': None}

        self.baseSkills = {'strength': 0, 'intelligence': 0, 'defense': 0,
        'speech': 0, 'endurance': 0, 'faith': 0, 'luck': 0}

        self.inventory = {'weapons': [], 'armor sets': [], 'rings': [],
        'miracles': [], 'spells': [], 'gold': None, 'keys': None}

        self.equipment = {'weapon': None, 'armor set': None, 'rings': []}

    def moveNorth(self):
        self.x -= 1

    def moveSouth(self):
        self.x += 1

    def moveEast(self):
        self.y += 1

    def moveWest(self):
        self.y -= 1

    def movement(self, world):
        movement_inp = raw_input("""
W. | Move North
S. | Move South
D. | Move East
A. | Move West
> """).lower()
        if movement_inp == 'w':
            self.moveNorth()

        elif movement_inp == 's':
            self.moveSouth()

        elif movement_inp == 'd':
            self.moveEast()

        elif movement_inp == 'a':
            self.moveWest()

        else:
            print 'Not a valid command'
            self.movement(world)

hero = Player(1, 2)

World class:

class WorldSpace(object):
    world_object_list = ['forest', 'mountain', 'ocean', 'other']

    def __init__(self, row, col, world_array):
        self.row = row  # Number of lists to be created.
        self.col = col  # Number of indexes inside of each row.
        self.world_array = world_array

    def genSingle_Char(self, char, count):
        # 'genSingle_Char' - Generate single character world objects.
        # Adds a number of random, specific characters to the world array.
        original_count = count
        while count != 0:
            row = randrange(len(self.world_array))
            column = randrange(len(self.world_array[0]))
            if self.world_array[row][column] == ' ':
                self.world_array[row][column] = char
                count -= 1

        print 'Generated {} {}\'s...'.format(original_count, char)

    def genLine_WObj(self, a, b, row_val, char, count):
        # 'genLine_WObj' - Generate Line(like) world object.
        # Used to place lines of psuedo-random width and length onto the world map.
        while count != 0:
            row_col_dict = {row_val: (a, b)}
            for row in row_col_dict.keys():
                startPos, endPos = row_col_dict[row]

                for i in range(startPos, endPos):
                    self.world_array[row][i] = char

            b += choice([0, 0, 1])
            a += choice([0, 0, 0, 1])

            row_val -= 1
            count -= 1

        print 'Generated psuedo-randomized line with world object `{}`'.format(char)

    def genShiftBlock(self, a, b, row_val, char, count):
        while count != 0:
            row_col_dict = {row_val: (a, b)}
            for row in row_col_dict.keys():
                startPos, endPos = row_col_dict[row]

                for i in range(startPos, endPos):
                    self.world_array[row][i] = char

                a += choice([0, 1, -1])
                b += choice([0, 1, -1])
                row_val -= 1
                count -= 1

        print 'Generated shift block(`~` | oceans).'

    def printPlayerMap(self):
        print('\n'.join([''.join(['{:1}'.format(item) for item in row])
                for row in self.world_array]))

    @classmethod
    def generateWorld(cls, autogen):
        print 'Starting world generation...'
        print
        if autogen is True:
            row_amt = 35
            col_amt = 75

        else:
            print 'Recommended row amt: 35, recommended col amt: 75.'
            print
            row_amt = input('Please specify row length of world array.: ')
            col_amt = input('Please specify column length of world array.: ')
            if (row_amt or col_amt) == 0:
                print 'Invalid world values!'
                self.generateWorld(False)

        world_array = [[' ']*col_amt for _ in xrange(row_amt)]
        print 'Created world...'
        return WorldSpace(row_amt, col_amt, world_array)

    def autoModifyWorld(self):
        print 'Now automatically modifying the world space.'
        # Forests:
        count = randint(1, 4)
        while count != 0:
            a_val_rand = choice([randint(10, 20), randint(45, 50)])
            b_val_rand = a_val_rand + randint(5, 10)
            row_val_rand = randint(10, 30)
            count_val_rand = randint(4, 8)
            self.genLine_WObj(a_val_rand, b_val_rand, row_val_rand, 't',
            count_val_rand)
            count -= 1

        print 'Created forests...'

        # Mountains:
        a_val_rand = choice([randint(22, 23), randint(45, 46)])
        b_val_rand = a_val_rand + 1
        row_val_rand = randint(15, 34)
        count_val_rand = randint(15, 20)
        self.genLine_WObj(a_val_rand, b_val_rand, row_val_rand, 'm',
        count_val_rand)

        print 'Created mountains...'

        # Oceans:
        a_val_rand = choice([randint(5, 20), randint(20, 50)])
        b_val_rand = a_val_rand + 10
        row_val_rand = 34
        count_val_rand = randint(5, 15)
        self.genShiftBlock(a_val_rand, b_val_rand, row_val_rand, '~',
        count_val_rand)

        print 'Created oceans...'

        # Other:
        randCount = randint(2, 4)
        while randCount != 0:
            char_val_rand = choice(['D', 'V', '$']) # 'Dungeons', 'Village', 'Loot' icons.
            count_val_rand = randint(1, 4)
            self.genSingle_Char(char_val_rand, count_val_rand)
            randCount -= 1

        print 'Created random characters...'
        print 'Automatically modified world...'

    def manualModifyWorld(self):
        # The value 'world_object_type' is represented by
        # 'forest', 'mountain', 'ocean', or 'other'.
        for item in self.world_object_list:
            print item
        world_object_type = raw_input(
        ('What world object would you like to implement?: ').lower())
        a_val = input('Please specify value [a] of row_col_dict.: ')
        b_val = input('Please specify value [b] of row_col_dict: ')
        row_val = input('Please specify first row to be modified.: ')
        char_val = raw_input('Please specify char used to modify array.: ')
        count_val = input('Please specify number of times to repeat action: ')
        if world_object_type == 'forest':
            self.genLine_WObj(a_val, b_val, row_val, char_val, count_val)

        elif world_object_type == 'mountain':
            self.genLine_WObj(a_val, b_val, row_val, char_val, count_val)

        elif world_object_type == 'ocean':
            self.generateLineBlock(a_val, b_val, row_val, char_val, count_val)

        elif world_object_type == 'other':
            # 'other' is used to add single characters to the map.
            char_val = raw_input('Please specify char used to modify array.: ')
            count_val = input('Please specify times to repeat action: ')
            self.genSingle_Char(char_val, count_val)

        else:
            print 'Not a valid world object type!'

        repeat = input("""Would you like to reuse this function or continue?
1/ Yes, 2/ No.: """)
        print
        if repeat == 1:
            self.manualModifyWorld(False)

        elif repeat == 2:
            print 'Manually modified world...'
            pass

        while repeat not in [1, 2]:
            print 'Not a valid input.'
            repeat = input("""Would you like to reuse this function or continue?
    1/ Yes, 2/ No.: """)
            print

def main(player):
    print """
|[~~Welcome to the whiteWall world creator interface~~]|
"""
    auto = input('Would you like to automate this process?: 1/ Yes, 2/ No: ')
    if auto == 1:
        world = WorldSpace.generateWorld(True)
        world.autoModifyWorld()

    else:
        print """If you have already started a game, enter [1]. Otherwise, it is extremely
    important that you enter [2], so that a world space can be generated."""
        world_gen_inp = input("""
1. | Boot game?
2. | Generate new world?
> """)
        if world_gen_inp == 2:
            world = WorldSpace.generateWorld(False)
            print
            world.manualModifyWorld()

        else:
            pass

    print 'Now booting game...'
    # This section is a temporary solution.
    # In final version, this will loop to the main game loop.
    while player.baseStats['health'] != 0:
        print '##PLAYER MAP##'
        current_tile = world.world_array[player.x][player.y]
        world.world_array[player.x][player.y] = 'P'
        world.printPlayerMap()
        world.world_array[player.x][player.y] = current_tile
        print
        player.movement(world)

main(hero)

I'm looking for ways to cut down my code, or improve it in any way:

  1. Creating a map of roughly 2000 x 2000 (or some other large amount) and only printing small sections of the maybe 30 or so tiles around the player at a time, based on their location on the map, to give the impression of a field of view and a nearly endless world.

  2. If a player crosses a water ~, or a mountain m tile, have them return to their previous position, to give the impression they cannot walk on certain tiles.

\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

Small suggestion, instead of:

if movement_inp == 'w':
    self.moveNorth()
elif movement_inp == 's':
    self.moveSouth()
elif movement_inp == 'd':
    self.moveEast()
elif movement_inp == 'a':
    self.moveWest()
else:
    print 'Not a valid command'
    self.movement(world)

Use:

movement = dict(w=self.moveNorth, s=self.moveSouth, d=self.moveEast, a=self.moveWest)
....
try:
    movement[movement_inp]()
except KeyError:
    print 'Not a valid command'
    self.movement()

Or better:

def __init__(self, x=0, y=0):
    self.directions = dict(w=(0,1), s=(0,-1), d=(1,0), a=(-1,0))
    ...

def movement(self):
    while True:
        movement_inp = raw_input("""
W. | Move North
S. | Move South
D. | Move East
A. | Move West
> """).lower()
        try:
            direction = self.directions[movement_inp]
        except KeyError:
            print 'Not a valid command'
        else:
            self.x, self.y = self.x + direction[0], self.y + direction[1]
            break

Which gets completely rid of the (very similar) moveX functions.

I also see no point in having world as a parameter when you don't do any checks with it.

While it is nice to abstract the skills part away a bit into different categories, it might be more advantageous to have them in the namespace directly. This way you could do print "Strength: {.strength}".format(player) or player.strength += 5 on level up.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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