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..")
asked Aug 19, 2016 at 9:59
\$\endgroup\$

2 Answers 2

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".

answered Aug 19, 2016 at 10:37
\$\endgroup\$
1
  • \$\begingroup\$ Yay for the hattrick! Did not realize all three quetion were from the same person ;) \$\endgroup\$ Commented Aug 19, 2016 at 14:16
1
\$\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.

answered Aug 19, 2016 at 14:12
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.