Welcome to the Treehouse Community
Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.
Looking to learn something new?
Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.
Start your free trialKrasimir Georgiev
19,146 PointsUpdated Dungeon Game
Hey guys so here is an updated version of the dungeon game.
Sadly I had to implement classes so it steps out of the Python Collections course, but I hope you like it nevertheless.
Game features:
- Monsters which are fightable
- Map generation
- Armor and Weapons which help you even the odds against the monsters
- 4 difficulties
Suggestions on how to improve the code or the game are very welcome.
EDIT: Fixed style and changed the way types and rarity is chosen in item.py
dungeon_game.py
import random
from combat import Combat
from monster import *
from character import Character
from item import *
from monster_spawner import MonsterSpawner
from item_spawner import ItemSpawner
from map import Map
def choose_difficulty():
print("Difficulties: \n Easy, Normal, Hard, Legendary")
difficulty_choice = input("On which difficulty would you like to play?")
difficulty_choice = difficulty_choice.lower()
if difficulty_choice == "easy":
difficulty = 1
elif difficulty_choice == "normal":
difficulty = 2
elif difficulty_choice == "hard":
difficulty = 3
elif difficulty_choice == "legendary":
difficulty = 4
return difficulty
def spawn_monster_guarding_item(monster_spawner, item_spawner):
location = map.get_location()
monster_spawner.spawn_monster(location)
item_spawner.spawn_item(location)
def show_help():
print("Type QUIT to quit,CHEAT to see other entities' position,\n" +
"STATS to see your stats,The direction you would like to" +
" go in to move\nand HELP to see this message")
game_over = False
cheat_activated = False
play_level = True
go_to_next_level = False
exit_immediatly = False
show_help()
player = Character()
difficulty = choose_difficulty()
max_level = difficulty * 5 - 1
print("You wake up in a dark maze.")
level = 0
counter = 0
while True:
if level >= max_level:
print("Congratulations! You managed to escape the maze!")
break
if exit_immediatly:
break
if game_over:
replay = input("Would you like to play again?")
replay = replay.lower()
if replay == "yes" or replay == "y":
player = Character()
gameOver = False
monster_spawner.monster_list = []
item_spawner.item_list = []
level = 0
counter = 0
else:
break
monster_spawner = MonsterSpawner(level, difficulty)
item_spawner = ItemSpawner(level, difficulty)
if go_to_next_level:
counter = 0
level += 1
go_to_next_level = False
monster_spawner.monster_list = []
item_spawner.item_list = []
map_rows = 3 + round(level * 0.49)
map_collumns = 3 + round(level * 0.49)
map = Map()
map.generate_map(map_rows, map_collumns)
player.pos = map.get_location()
player.pos_x, player.pos_y = player.pos
door_pos = map.get_location()
print("Starting level {}".format(level + 1))
spawn_monster_guarding_item(monster_spawner, item_spawner)
while play_level:
if counter == 22 - level:
spawn_monster_guarding_item(monster_spawner, item_spawner)
counter = 0
if cheat_activated:
print("Player position {}".format(player.pos))
print("Door position {}".format(door_pos))
for monster in monster_spawner.monster_list:
print("Monster position {}".format(monster.pos))
for item in item_spawner.item_list:
print("Item position {}".format(item.pos))
player.heal()
map.draw_map(player.pos)
direction_dict = map.collision_detection(player.pos)
direction = input("Where would you like to go?")
direction = direction.lower()
if direction_dict["S"] and (direction == "s" or direction == "south" or direction == "d" or direction == "down"):
player.pos_y += 1
elif direction_dict["N"] and (direction == "n" or direction == "north" or direction == "u" or direction == "up"):
player.pos_y -= 1
elif direction_dict["E"] and (direction == "e" or direction == "east" or direction == "r" or direction == "right"):
player.pos_x += 1
elif direction_dict["W"] and (direction == "w" or direction == "west" or direction == "l" or direction == "left"):
player.pos_x -= 1
elif direction == "cheat":
cheat_activated = True
print("Cheating activated")
continue
elif direction == "stats":
player.show_stats()
continue
elif direction == "help":
show_help()
continue
elif direction == "quit":
exit_immediatly = True
break
else:
print("Command not recognized and/or you aren't allowed to go there.")
continue
player.pos = player.pos_x, player.pos_y
counter += 1
for monster in monster_spawner.monster_list:
if player.pos == monster.pos:
print("You have encountered a {}".format(monster))
battle = Combat()
while player.hit_points > 0 and monster.hit_points > 0:
battle.weapon_attack(player, monster)
battle.weapon_attack(monster, player)
if player.hit_points <= 0:
print("You have been defeated by {}".format(monster))
game_over = True
break
elif monster.hit_points <= 0:
player.experience += monster.experience
print("Player has defeated {}, earning {} experience".format(monster, monster.experience))
monster_spawner.monster_list.remove(monster)
if game_over:
break
for item in item_spawner.item_list:
if player.pos == item.pos:
print("You have found a:\n{}".format(item))
get_item = input("Would you like to take it?")
get_item = get_item.lower()
if get_item == 'y' or get_item == 'yes':
print("You took a {} {}".format(item.type, item.name))
if item.__class__.__name__ == "Armor":
player.set_armor(item)
else:
player.set_weapon(item)
item_spawner.item_list.remove(item)
player.level_up()
if player.pos == door_pos:
print("You managed to get to the door!")
go_to_next_level = True
break
print("Thank you for playing! I hope you come back soon!")
character.py
import random
from map import Map
from item import *
class Character:
level = 1
level_up_experience = 3
experience = 0
hit_points = 10
max_hit_points = 10
heal_rate = 1
def __init__(self, **kwargs):
self.weapon = Weapon(0, 5, "stick")
self.armor = Armor(1, "robes")
for key, value in kwargs.items():
setattr(self, key, value)
def display_stats(self):
print("Player stats: \n Level: {}, Experience: {}, {}".format(self.level, self.experience, self.max_hit_points))
def __str__(self):
return "Player"
def show_stats(self):
print("Player Level: {}\nExperience: {}\nHealth: {}\nHeal Rate: {}\nArmor: {}\nWeapon: {}".format(self.level, self.experience, self.hit_points, self.heal_rate, self.armor, self.weapon))
def level_up(self):
if self.experience >= self.level_up_experience:
constant = 0.335 - (self.level * 0.005)
self.level_up_experience = round((self.level / constant) * (self.level / constant))
self.max_hit_points += 4
self.level += 1
if self.level % 3 == 0:
self.heal_rate += 1
print("You leveled up to level {}.Next level requires {} experience".format(self.level, self.level_up_experience))
def set_weapon(self, weapon):
self.weapon = weapon
def set_armor(self, armor):
self.armor = armor
def heal(self):
if self.hit_points < self.max_hit_points:
self.hit_points += self.heal_rate
while self.hit_points > self.max_hit_points:
self.hit_points -= 1
combat.py
import random
class Combat:
def weapon_attack(self, attacking_entity, defending_entity):
experience_difference = attacking_entity.experience - defending_entity.experience
roll = random.randint(0, 100)
roll += experience_difference
damage = attacking_entity.weapon.attack() - defending_entity.armor.defense
if damage < 0:
damage = 0
if roll <= 10:
counter_damage = defending_entity.weapon.attack() - attacking_entity.armor.defense
if counter_damage <= 0:
counter_damage = 1
attacking_entity.hit_points -= counter_damage
print("{} has attacked {} but {} has countered {}'s attack, dealing {} damage".format(attacking_entity, defending_entity, defending_entity, attacking_entity, counter_damage))
elif roll <= 30:
print("{} has attacked {} but {} has dodged {}'s attack, evading {} damage".format(attacking_entity, defending_entity, defending_entity, attacking_entity, damage))
else:
defending_entity.hit_points -= damage
print("{} has attacked {}, dealing {} damage".format(attacking_entity, defending_entity, damage))
item.py
import random
RARITY = ['Common', 'Uncommon', 'Rare', 'Epic', 'Legendary']
WEAPON_TYPES = ['Training', 'Stone', 'Iron', 'Steel', 'Silver', 'Dragonbone']
ARMOR_TYPES = ['Cloth', 'Leather', 'Iron', 'Steel', 'Silver', 'Dragonbone']
class Item:
def set_item_attr(self, **kwargs):
rarity_chance = random.randint(0, 100)
rarity_counter = 0
self.power = 0
for rarity_type in RARITY:
if rarity_chance <= round((100 - 10 * rarity_counter) * (rarity_counter + 1) / len(RARITY) + 10 * rarity_counter):
break
else:
self.power += 5
rarity_counter += 1
self.type = RARITY[rarity_counter]
for (key, value) in kwargs.items():
setattr(self, key, value)
def __str__(self):
if self.__class__.__name__ == "Armor":
stats = "Defense: {}".format(self.defense)
else:
stats = "Attack range: {} - {}".format(self.min_attack, self.max_attack)
return "{} {}\n {} ".format(self.type, self.name, stats)
class Weapon(Item):
def __init__(self, min_attack, max_attack, name, **kwargs):
self.set_item_attr(**kwargs)
self.name = name
self.min_attack = min_attack
self.max_attack = max_attack
combined_attack = self.min_attack + self.max_attack
type_counter = 0
for weapon_type in WEAPON_TYPES:
if combined_attack <= (100 * (type_counter + 1)) / len(WEAPON_TYPES):
break
else:
type_counter += 1
try:
type = WEAPON_TYPES[type_counter]
except:
type = WEAPON_TYPES[type_counter - 1]
self.type += " {}".format(type)
def attack(self):
attack = random.randint(self.min_attack, self.max_attack)
return attack
class Armor(Item):
def __init__(self, defense, name, **kwargs):
self.set_item_attr(**kwargs)
self.name = name
self.defense = defense
type_counter = 0
for armor in ARMOR_TYPES:
if defense <= (50 * (type_counter + 1)) / len(ARMOR_TYPES):
break
else:
type_counter += 1
try:
type = ARMOR_TYPES[type_counter]
except:
type = ARMOR_TYPES[type_counter - 1]
self.type += " {}".format(type)
item_spawner.py
from item import *
WEAPON_NAMES = ['Dagger', 'Sword', 'Greatsword', 'Mace', 'Axe', 'Battle Axe', 'Katana']
ARMOR_NAMES = ["Hunter's Armor", 'Griffin Armor', 'Armor',
'Dragonslayer Armor', 'Guard Outfit', 'Kingsguard Armor', 'High Priest Outfit']
class ItemSpawner:
item_list = []
def __init__(self, level, difficulty):
self.level = level
self.difficulty = difficulty
def spawn_item(self, location):
spawn_chance = random.randint(0, 100)
if spawn_chance < 50:
armor = round((random.randint(0, 5) * (5 - self.difficulty)) + self.level)
item = Armor(armor, random.choice(ARMOR_NAMES))
item.defense = item.defense + round(item.power / self.difficulty)
else:
min_attack = round(random.randint(0, 5) * (5 - self.difficulty) + self.level)
max_attack = round(random.randint(5, 10) * (5 - self.difficulty) + self.level)
item = Weapon(min_attack, max_attack, random.choice(WEAPON_NAMES))
item.min_attack = item.min_attack + round(item.power / self.difficulty)
item.max_attack = item.max_attack + round(item.power / self.difficulty)
item.pos = location
self.item_list.append(item)
map.py
import random
class Map:
available_space = []
game_map = []
map_borders = ()
def generate_map(self, rows, collumns):
self.game_map = []
self.available_space = []
self.map_borders = rows - 1, collumns - 1
for x in range(0, rows):
for y in range(0, collumns):
self.game_map.append((x, y))
self.available_space.append((x, y))
def get_location(self):
map_space = random.choice(self.available_space)
map_idx = self.available_space.index(map_space)
game_map_idx = self.game_map.index(map_space)
location = self.game_map[game_map_idx]
del self.available_space[map_idx]
return location
def collision_detection(self, entity_pos):
dict_of_directions = {}
map_x, map_y = self.map_borders
shadow_pos_x, shadow_pos_y = entity_pos
shadow_pos_y -= 1
if shadow_pos_y <= map_y and shadow_pos_y >= 0:
dict_of_directions['N'] = True
else:
dict_of_directions['N'] = False
shadow_pos_y += 2
if shadow_pos_y <= map_y and shadow_pos_y >= 0:
dict_of_directions['S'] = True
else:
dict_of_directions['S'] = False
shadow_pos_x += 1
if shadow_pos_x <= map_x and shadow_pos_x >= 0:
dict_of_directions['E'] = True
else:
dict_of_directions['E'] = False
shadow_pos_x -= 2
if shadow_pos_x <= map_y and shadow_pos_x >= 0:
dict_of_directions['W'] = True
else:
dict_of_directions['W'] = False
return dict_of_directions
def draw_map(self, player_pos):
player_pos_x, player_pos_y = player_pos
map_x, map_y = self.map_borders
cell = ""
counter = -1
for y in range(-1, map_y + 1):
counter += 1
if counter != 0:
print(" " + "_ " * (map_x + 1))
print(cell)
cell = ""
for x in range(0, map_x + 1):
if player_pos == (x, y + 1):
cell += "|{}|".format("X")
else:
cell += "|{}|".format("_")
monster.py
import random
from map import Map
from item import *
class Monster:
min_hit_points = 2
max_hit_points = 5
min_experience = 1
max_experience = 5
def choose_weapon(self):
weapon_choice = random.randint(0, 10)
if weapon_choice <= 4:
self.weapon = Weapon(1, 2, "claws")
elif weapon_choice <= 7:
self.weapon = Weapon(2, 4, "wooden club")
else:
self.weapon = Weapon(3, 6, "sword")
def __str__(self):
return self.__class__.__name__
def __init__(self, difficulty, level, **kwargs):
self.armor = Armor(0, "light armor")
self.choose_weapon()
self.hit_points = random.randint(self.min_hit_points,
self.max_hit_points)
self.hit_points = round(self.hit_points * difficulty + level)
self.experience = random.randint(self.min_experience,
self.max_experience)
self.experience = round(self.experience * (difficulty / 2) + level)
self.experience = round(self.experience * (difficulty / 2))
self.weapon.min_attack = round(self.weapon.min_attack * (difficulty / 2) + level)
self.weapon.max_attack = round(self.weapon.max_attack * (difficulty / 2) + level)
for (key, value) in kwargs.items():
setattr(self, key, value)
class Goblin(Monster):
min_hit_points = 2
max_hit_points = 3
min_experience = 1
max_experience = 3
def choose_weapon(self):
weapon_choice = random.randint(0, 10)
if weapon_choice <= 6:
self.weapon = Weapon(1, 2, "knife")
else:
self.weapon = Weapon(2, 4, "cleaver")
class Troll(Monster):
min_hit_points = 5
max_hit_points = 10
min_experience = 2
max_experience = 6
def choose_weapon(self):
weapon_choice = random.randint(0, 10)
if weapon_choice <= 4:
self.weapon = Weapon(1, 2, "tree branch")
elif weapon_choice <= 7:
self.weapon = Weapon(3, 5, "wooden club")
else:
self.weapon = Weapon(5, 10, "iron mace")
class Ghoul(Monster):
min_hit_points = 2
max_hit_points = 4
min_experience = 1
max_experience = 1
def choose_weapon(self):
self.weapon = Weapon(1, 2, "claws")
class Giant(Monster):
min_hit_points = 4
max_hit_points = 8
min_experience = 1
max_experience = 5
def choose_weapon(self):
weapon_choice = random.randint(0, 10)
if weapon_choice <= 6:
self.weapon = Weapon(1, 2, "tree branch")
else:
self.weapon = Weapon(3, 5, "wooden club")
monster_spawner.py
from monster import *
import random
class MonsterSpawner:
monster_list = []
def __init__(self, level, difficulty):
self.level = level
self.difficulty = difficulty
def spawn_monster(self, location):
min_spawn_chance = self.difficulty * 5 + self.level
spawn_chance = random.randint(min_spawn_chance, 100)
if spawn_chance <= 30:
monster = Goblin(self.difficulty, self.level)
elif spawn_chance <= 49:
monster = Ghoul(self.difficulty, self.level)
elif spawn_chance <= 69:
monster = Monster(self.difficulty, self.level)
elif spawn_chance <= 89:
monster = Giant(self.difficulty, self.level)
else:
monster = Troll(self.difficulty, self.level)
monster.pos = location
self.monster_list.append(monster)
Krasimir Georgiev
19,146 PointsThanks!
1 Answer
Kenneth Love
Treehouse Guest TeacherWow, that's a lot of code! Couple of style points:
- Don't use
camelCase
orStuddlyCaps
names for functions. Functions should always be written insnake_case
. Same for variable names.StuddlyCaps
is only for classes andcamelCase
isn't for anything in Python. - Make sure there are two blank lines before your function and class definitions. Class methods should have a single blank line before them.
- Classes that don't inherit from a parent class shouldn't have parentheses on them.
- Make sure your spacing around commas, equals, etc is consistent.
All this and more is in the Write Better Python course which you probably haven't gotten to, so no harm, no foul.
You probably want to be careful with the magic index numbers in your Armor
and other places. They'll make it harder to add or remove items without changing the output of your script, possibly unpredictably.
Krasimir Georgiev
19,146 PointsThank you so much for your review Kenneth!
Keith Whatling
17,759 PointsKeith Whatling
17,759 PointsThat is fantastic!