2
\$\begingroup\$

Based upon feedback received in this post I've amended my game and would very much appreciate further feedback on it's current state(work in progress) and any suggestions on what I could implement in the future.

An @classmethod was introduced in Enemy() and these are completely new to me so a confirmation that all is correct with that would be very nice.

Code:

# A Countryside Adventure

#from adventureqmodule import *
from sys import exit
import random

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("Options: {}".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 Character(object):
    """A character in a fictional world"""

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

    def __str__(self):
        rep = self.name + " has health: " + str(self.health)
        return rep

    def attack(self, other):
        raise NotImplementedError

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



class Player(Character):
    """A player in a fictional world"""

    def __init__(self, name, health = 10):
        super(Player, self).__init__(name, health)
        self.inventory = ["axe handle", "first aid kit"]
        self.inventory_max = 2
        self.strikes = {"headbutt" : 4 , "kick" : 3, "punch"\
                        : random.randrange(1, 4)}

    def __str__(self):
        rep = super(Player, self).__str__()
        rep += "\nInventory: " + str(self.inventory)
        return rep


    def find_items(self, location_items):
        print("On the ground you see: {}".format(", ".join(location_items)))
        print("Your inventory (max {} items) is: {}".format\
              (self.inventory_max, self.inventory))
        pick_up = True
        while pick_up:
            pick_up = question("Pick anything up?", boolean = True)
            if not pick_up:
                break
            item = input("What? ")

            if item in location_items:
                self.add_inventory(item, location_items)
            else:
                print("There is no such item here.")


    def add_inventory(self, item, location_items):
        if len(self.inventory) < self.inventory_max:
            self.inventory.append(item)
            location_items.remove(item)
            print("You picked up: {}".format(item))
            print("Your inventory is now: {}".format(self.inventory))

        elif len(self.inventory) == self.inventory_max:
            remove = question("Inventory full. Remove an item?", boolean = True)
            if remove:
                self.remove_inventory(location_items)
                location_items.remove(item)
                self.inventory.append(item)
            print("You picked up: {}".format(item))
            print("Your inventory is now: {}".format(self.inventory))


    def remove_inventory(self, location_items):
        remove_item = None
        while remove_item not in self.inventory:
            print("You inventory is: {}".format(self.inventory))
            remove_item = input("Which item do you want to remove? ")
            if remove_item not in self.inventory:
                print("Sorry, that isn't in your inventory.")
        location_items.append(remove_item)
        self.inventory.remove(remove_item)


    def attack(self, other):
        strike = question("\nStrike the {}?".format(other.name), boolean = True)
        if strike:
            strike_type = input("What type of strike(headbutt, kick, punch)? ")\
                          .lower()
            if strike_type in self.strikes:
                print("You got the {} with a {} and dealt {} damage!".format(\
                    other.name, strike_type, self.strikes.get(strike_type)))
                other.take_damage(self.strikes.get(strike_type))
            else:
                print("Oops. You missed!")
        else:
            print("You wuss!")



class Enemy(Character):
    """An enemy is a fictional world"""

    def __init__(self, name, health = 7, damage = 2):
        super(Enemy, self).__init__(name, health)
        self.damage = damage

    def attack(self, other):
        other.take_damage(self.damage)
        print("\nThe {} attacks and deals you {} damage!".format(self.name,\
                                                           self.damage))
    @classmethod
    def generate(cls):
        names = ["Boar", "Troll", "Wild cat"]
        enemy = Enemy(random.choice(names))
        return enemy

    def die(self):
        print("You have defeated the {}!".format(self.name))



class Location(object):
    """A location in an adventure game"""
    def __init__(self):
        self.enemy = Enemy.generate()

    def enter(self):
        NotImplementedError

    def battle(self, player, enemy):
        """Player and an enemy battle"""
        print("A {} suddenly appears!".format(enemy.name))
        fight = question("\nFight or run?", options = ["fight", "run"])
        if fight == "fight":
            while player.health and enemy.health > 0:
                #print("\n")
                enemy.attack(player)
                if player.health <= 0:
                    break
                print(player)
                print(enemy)
                player.attack(enemy)

            if enemy.health <= 0:
                print("You have defeated the {}!".format(enemy.name))
                enemy.dead = True
                #enemy.die()
            elif player.health <= 0:
                print("You are killed by the {}!".format(enemy.name))
                player.dead = True

        else:
            print("You escaped unharmed. But he shall be waiting should you \
venture")
            print("to these parts again!")



class Meadow(Location):
    """A meadow in an adventure game"""

    def __init__(self): 
        super(Meadow, self).__init__()
        self.location_items = ["bottled water"] 

    def enter(self, player):
        #enemy  = self.generate_enemy()
        print(player)
        print("\nYou are in a meadow.")
        if self.location_items:
            player.find_items(self.location_items)
        if not self.enemy.dead:
            self.battle(player, self.enemy)
            if player.dead:
                return "dead"
        else:
            print("The slain body of the {} lies slumped pathetically on the\
 ground.".format(self.enemy.name))

        print("\nTo your left is a beach, to your right a forest.")
        left_right = question("Which way do you go? ", options = ["left",\
                                                                  "right"])

        if left_right == "left":
            return "beach"
        else:
            return "forest"


class Beach(Location):
    """A beach location in an adventure game"""

    def __init__(self): 
        super(Beach, self).__init__()
        self.location_items = ["chocolate bar"] 

    def enter(self, player):
        print(player)
        print("\nYou are at the beach.")
        if self.location_items:
            player.find_items(self.location_items)
        print("location items after adjustment: {}".format(self.location_items))
        if not self.enemy.dead:
            self.battle(player, self.enemy)
            if player.dead:
                return "dead"
        else:
            print("You pass the lifeless body of the {}.".format(self.enemy.\
                                                                 name))
        print("\nTo your left is a meadow, to your right a forest.")
        left_right = question("Which way do you go? ", options = ["left",\
                                                                  "right"])

        if left_right == "left":
            return "meadow"
        else:
            return "forest"


class Forest(Location):
    """A forest in an adventure game"""

    def __init__(self): 
        super(Forest, self).__init__()
        self.location_items = ["crossbow"] 

    def enter(self, player):
        print(player)
        print("\nYou are in the forest.")
        if self.location_items:
            player.find_items(self.location_items)
        print("location items after adjustment: {}".format(self.location_items))
        if not self.enemy.dead:
            self.battle(player, self.enemy)
            if player.dead:
                return "death"
        else:
            print("You smile as you pass the corpse of the {}.".format(\
                self.enemy.name))

        print("\nTo your left is meadow, to your right a beach.")
        left_right = question("Which way do you go?", options = ["left",\
                                                                 "right"])
        if left_right == "left":
            return "meadow"
        else:
            return "beach"



class Game(object):
    """A fictional countryside adventure"""

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

    def play(self):
        current_location = self.location_map.start_location()
        while True:
            next_location_name = current_location.enter(self.\
                                                        location_map.player)
            if next_location_name == "dead":
                print("Game Over")
                break
            current_location = self.location_map.next_location\
                               (next_location_name)


class Map(object):

    LOCATIONS = {"meadow" : Meadow(),
                 "beach" : Beach(),
                 "forest" : Forest()}

    def __init__(self, location, player):
        self.location = location
        self.player = player

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

    def start_location(self):
        return self.next_location(self.location)


def main():
    print("\n\n\t\tCountryside adventure")
    name = input("\n\nWelcome adventurer! What might be your name? ").title()
    print("\nI wish you the very best of luck, {}!.\n".format(name))

    player = Player(name)
    a_map = Map("meadow", player)
    game = Game(a_map)
    game.play()

play = True
while play:
    main()
    play = question("Do you want to play again?", boolean = True)
\$\endgroup\$
2
\$\begingroup\$

I would use another nice python decorator, @property to make the check for dead easier:

class Character(object):
    ...
    @property
    def dead(self):
        return self.health <= 0
...
class Location(object):
    ...
    def battle(self, player, enemy):
        ...
        attacker, defender = enemy, player
        while True:
            attacker.attack(defender)
            if defender.dead:
                break
            print(defender)
            attacker, defender = defender, attacker
        action = "are killed by" if player.dead else "have defeated"
        print("You {} the {.name}!".format(action, enemy))

This means you can access player.dead like an attribute and on every access he will actually call player.dead() to check the health of the player. I also swapped around the checks, because if the player is dead it does not matter whether the enemy is also dead!

EDIT: I now transformed the while loop into an infinite loop of an attacker attacking a defender where the two roles are swapped around, starting with enemy attacking player. I also made the printing more generic, the two strings were quite similar.

\$\endgroup\$
  • \$\begingroup\$ Thanks! Very cool. I want get this type of efficiency into my code. \$\endgroup\$ – Michael Johnson Aug 15 '16 at 15:05
  • \$\begingroup\$ Are there any benefits to making dead a property and not just using a function call to player.dead()? The only difference I see at the moment is that you can omit the (). \$\endgroup\$ – nluigi Aug 15 '16 at 16:05
  • 1
    \$\begingroup\$ I think that the primary benefit is that you don't have to have the character.health <= 0 check in multiple places in the codebase - the DRY (Don't Repeat Yourself) principle. \$\endgroup\$ – silleknarf Aug 15 '16 at 16:47
  • \$\begingroup\$ What @silleknarf said. Also if you ever want to change the check for deadness, it is now in one place. Maybe a magical item keeps your soul in this plane even if your health is below zero? Just add a check for that in dead, instead of everywhere where you now check the health. \$\endgroup\$ – Graipher Aug 16 '16 at 9:07

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.