2
\$\begingroup\$

I'm looking for feedback on my implementation of Item() objects in my beginner project (work in progress) or indeed any other feedback you feel is appropriate. I'm sure what I've managed is very primitive, but I've alot on atm and my brain is creaking(enjoying though!).

The plan is for the items to be an integral part of the game (checking attributes of found items, using and consuming etc) so I'd like to get the code right early on.

Would it be better to use a dictionary to store player and location items instead of a list?

In various sections of the program for example I use:

item_names = [item.name for item in self.items]

or similar to allow user to pick. Then a for loop to update the inventory such as:

for item in self.items:
            if item.name == remove_item:
                self.items.remove(item)
                location.location_items.append(item)
                print("Item removed.")

Full code(working):

# Adventure game
# need location add-item function (and remove)
# use dicts for items or lists?

#from adventureqmodule import *
import random
import time

def question(question, boolean = False, options = ["yes", "no"]):
    """
    If boolean, returns True or False. Otherwise returns a string
    regardless of type in options
    """
    options = ["yes", "no"] if boolean else [str(option) for option in options]
    while True:
        print(question)
        print("\nOptions: {}".format(", ".join(options)))
        response = input("Input: ").lower()
        if boolean and response in ["y", "n"] + options:
            return response[0] == "y"
        if response in options:
            return response
        else:
            print("That isn't a valid response.")



class Item(object):
    """An item in a fictional world"""

    items = {"bottled water" : {"health" : 1, "damage" : 0},
                 "spam" : {"health" : 1, "damage" : 0},
                 "first aid kit" : {"health" : 3, "damage" : 0},
                 "axe handle" : {"health" : 0, "damage" : 3},
                 "metal bar" : {"health" : 0, "damage" : 4}}

    def __init__(self):
        self.name = random.choice(list(self.items.keys()))
        self.health = self.items[self.name]["health"]
        self.damage = self.items[self.name]["damage"]

    @classmethod
    def generate(self):
        return Item()


class Character(object):
    def __init__(self, name, health):
        self.name = name
        self.health = health

    def __str__(self):
        rep = "Name: " + self.name
        rep += "\nHealth: " + str(self.health)
        return rep

    @property
    def dead(self):
        return self.health <= 0

    def take_damage(self, damage):
        self.health -= damage

    def attack(self):
        NotImplementedError


class Enemy(Character):
    """An enemy in a fictional world"""
    def __init__(self):
        self.name = random.choice(["Wild Cat", "Troll", "Boar"])
        self.health = random.randrange(5, 9)
        self.damage = random.randrange(1, 3)

    @classmethod
    def generate(self):
        return Enemy()

    def attack(self, other):
        print("The {} attacks and deals {} damage!".format(self.name, \
                                                           self.damage))
        other.take_damage(self.damage)



class Player(Character):
    """A player in an adventure game"""

    def __init__(self, name, health = 10):
        super(Player, self).__init__(name, health)
        self.items = [Item.generate()]
        self.strikes = {"headbutt" : 4, "kick" : 3, "punch" :
                        random.randrange(4)}
        self.inventory_max = 1


    def __str__(self):
        rep = super(Player, self).__str__()
        return rep


    def inventory(self, location):
        commands = ["attributes", "remove", "add", "exit"]
        item_names = [item.name for item in self.items]
        if self.items:
            print("Your inventory is: {}".format(", ".join(item_names)))
        else:
            print("Your inventory is empty.")
            print(", ".join(item_names))

        inventory_action = question("", options = commands)
        if inventory_action == "remove":
            if not self.items:
                print("\nYour inventory is empty.")
            else:
                self.remove_item(location)
        if inventory_action == "add":
            self.add_item(location)
        if inventory_action == "attributes":
            print("Not yet implemented")
            pass


    def remove_item(self, location):
        item_names = [item.name for item in self.items]
        remove_item = question("Which item?", options = item_names)
        for item in self.items:
            if item.name == remove_item:
                self.items.remove(item)
                location.location_items.append(item)
                print("Item removed.")
            break


    def add_item(self, location):
        location_item_names = [item.name for item in location.location_items]
        if location.location_searched:
            add_item = question("Which item?", options = location_item_names)
            for item in location.location_items:
                if item.name == add_item:
                    self.items.append(item)
                    location.location_items.remove(item)
                    print("Item added.")
                break
        else:
            print("There are no items around to add.")


    def attack(self, other):
        print("You have {} health.".format(self.health))
        strike = question("What type of strike do you use?", options = list(\
            self.strikes.keys()))
        strike_damage = self.strikes.get(strike)
        other.take_damage(strike_damage)
        print("You attack the {} with a {} and deal {} damage!".format\
              (other.name, strike, strike_damage))


class Location(object):

    LOCATION_DATA = {"meadow" : {"description" : "\nYou are standing in a \
meadow. To the north is a forest.", "exits" : ["forest", "coast"]},
                 "forest" : {"description" : "\nYou are in a forest. To the \
south is a meadow.", "exits" : ["meadow", "coast"]},
                     "coast" : {"description" : "\nYou are standing on a golden\
beach.", "exits" : ["meadow", "forest"]}}

    def __init__(self, key):
        self.description = self.LOCATION_DATA[key]["description"]
        self.name = str(key)
        self.exits = self.LOCATION_DATA[key]["exits"]
        self.location_items = [Item.generate(), Item.generate()]
        self.enemy = Enemy.generate()
        self.location_searched = False


class Meadow(Location):
    pass

class Forest(Location):
    pass


class Game(object):

    def __init__(self, player, location_map):
        self.player = player
        self.location_map = location_map
        self.commands = ["look", "move", "inventory", "search"]

    def handle_commands(self, command, location, player):
        enemy = location.enemy
        if command == "look" and enemy.dead:
            print(location.description)
            print("You catch a glimpse of a corpse. It's the slain {}."\
                  .format(enemy.name))
        elif command == "look" and not enemy.dead:
            print(location.description)
            print("Wait, you see a {} in the near distance!".format(enemy.name))
            fight = question("Attack the {}?".format(enemy.name),\
                             boolean = True)
            if fight:
                self.combat(player, enemy)
            else:
                print("They'll probably still be around later.")

        if command == "inventory":
            player.inventory(location)
        if command == "search":
            location_item_names = [item.name for item in location.\
                                   location_items]
            location.location_searched = True
            if location.location_items:
                print("You find: {}.".format(", ".join(location_item_names)))
            else:
                print("You find nothing.")

    def combat(self, player, enemy):
        print("The {} sees you coming...".format(enemy.name))
        attacker, defender = enemy, player
        while True:
            print(player)
            print(enemy)
            attacker.attack(defender)
            if defender.dead:
                break
            attacker, defender = defender, attacker
        insert = "killed" if enemy.dead else "were killed by"
        print("You {} the {}!".format(insert, enemy.name))


    def play(self):
        current_location = self.location_map.next_location("meadow")
        while not self.player.dead:
            command = question("", options = self.commands)
            if command == "move":
                next_location_name = question("Where to?", options = \
                                              current_location.exits)
                current_location = self.location_map.next_location\
                                   (next_location_name)
                self.player.found_items = []
            else:    
                self.handle_commands(command, current_location, self.player)
        print("\nGame Over\n")

class Map(object):
    """Map of location"""
    LOCATIONS = {"meadow" : Location("meadow"),
                 "forest" : Location("forest")}

    def __init__(self):
        #self.location_start = location_start
        pass

    def next_location(self, location):
        return self.LOCATIONS.get(location)

    #def start_location(self):
        #return self.next_location(self.location_start)

def main():
    print("\t\tA Countryside Adventure")
    name = input("\n\nWelcome adventurer! What might be your name? ").title()
    print("\nI wish you luck on your adventures, {}!".format(name))
    print("And so the adventure begins....")
    time.sleep(2)

    a_map = Map()
    player = Player(name)
    game = Game(player, a_map)
    game.play()
    print("Player name: {}".format(player.name))
    print("Played a game.")


again = True
while again:
    main()
    again = question("Do you want to play again?", boolean = True)

input("\n\nPress the enter key to exit..")
\$\endgroup\$
2
\$\begingroup\$

I would change code of this type:

class Enemy(Character):
    """An enemy in a fictional world"""
    def __init__(self):
        self.name = random.choice(["Wild Cat", "Troll", "Boar"])
        self.health = random.randrange(5, 9)
        self.damage = random.randrange(1, 3)

    @classmethod
    def generate(self):
        return Enemy()

To be like this:

class Enemy(Character):
    """An enemy in a fictional world"""
    def __init__(self, name, health, damage):
        self.name = name
        self.health = health
        self.damage = damage

    @classmethod
    def generate(cls, health_range=(5, 9), damage_range=(1, 3)):
        name = random.choice(["Wild Cat", "Troll", "Boar"])
        health = random.randrange(*health_range)
        damage = random.randrange(*damage_range)
        return Enemy(name, health, damage)

This way you can, if you want to, just create a specific enemy (like Enemy("Ginourmous Spider", sys.maxint, 1000) in a specific quest). You can also easily have a progression of difficulty, by having multiple levels in a dungeon, with eg.

enemy_health = [(5, 9), (6, 10), (7, 12)]
enemy_damage = [(1, 3), (1, 4), (2, 6)]
no_enemies = [5, 10, 20]
....
dungeon_level = 3
health_range = enemy_health[dungeon_level - 1]
damage_range = enemy_damage[dungeon_level - 1]
n_enemies = no_enemies[dungeon_level - 1]
enemies = [Enemy.generate(health_range, damage_range) for _ in range(n_enemies)]

Or you might implement a database system for enemies/items and could then just write a different generate method:

    @classmethod
    def generate_from_file(cls, in_file):
        with open(in_file) as enemies:
            for enemy in enemies:
                yield Enemy(*enemy.split(",")

with the file looking like

Boar,100,10
Small Spider,120,7
...

(This is not perfect, but I hope you get my meaning).

The same change can be made for Item.

Note that the convention for classmethods seems to be to call the first variable cls (for class), instead of self, but this is just a label in the end.

Character.attack(self) should have the signature Character.attack(self, other), unless you want to play a game of "stop hitting yourself, stop hitting yourself".

\$\endgroup\$
  • \$\begingroup\$ Yay for the hattrick! Did not realize all three quetion were from the same person ;) \$\endgroup\$ – Graipher Aug 19 '16 at 14:16
0
\$\begingroup\$

I've done the following to simplify choosing from a list of objects (still think there must be a better way however), takes out plenty of repetition:

def which_item(self, object_list):
    """
    Takes an object list and produces string list of object names so user
    can select an object. Returns selected object.
    """
    name_list = [item.name for item in object_list]
    item_name = question("Which item?", options = name_list)
    for item in object_list:
        if item.name == item_name:
            return item

This should have immediately come to mind.

\$\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.