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 trial

Python Python Collections (Retired) Dungeon Game Building the Game: Part 2

Just Another Dungeon Game - Review/Tips Appreciated.

Posting my code for the dungeon game.

Interested to see if there are any glaring issues in the way I'm writing code as this is my first time learning programming. Anything obvious that I could do more efficiently, etc? Any tips or recommendations is much appreciated!

Thank you Kenneth Love for a great course so far, 2 courses into the Learn Python track and really digging it.

#-----------------------------------------------------------------------
# IMPORT
#-----------------------------------------------------------------------

import random


#-----------------------------------------------------------------------
# FUNCTIONS
#-----------------------------------------------------------------------

def set_map_size():
  try:
    size = int(input("> "))
    if size > 2 and size < 21:
      return(size)
    else:
      print("\nPlease choose an integer between 3 and 20")
      return(set_map_size()) 
  except:
    print("\nPlease choose an integer between 3 and 20")
    return(set_map_size())

def generate_map_grid(size):
  new_map = []
  for y in range(size):
    for x in range(size):
      new_map.append((y,x))
  return(new_map)

def generate_locations():
  door_loc = random.choice(map_grid)

  monster_loc = random.choice(map_grid)
  while monster_loc == door_loc:
    monster_loc = random.choice(map_grid)

  player_loc = random.choice(map_grid)
  while player_loc == door_loc or player_loc == monster_loc:
    player_loc = random.choice(map_grid)

  return(door_loc, monster_loc, player_loc)

def generate_map_dict(map_grid):
  map_dict = {}
  for location in map_grid:
    if location == door_loc:
      map_dict[location] = 'door'
    elif location == monster_loc:
      map_dict[location] = 'monster'
    elif location == player_loc:
      map_dict[location] = 'player'
    else:
      map_dict[location] = 'empty'
  return(map_dict)

def render_live_map():
  x_count = 0
  print('')
  for location in map_grid:

    if map_dict[location] == 'empty':
      print ("[ ]", end='')         
    elif map_dict[location] == 'breadcrumb':
      print("[~]", end='')
    elif map_dict[location] == 'monster':
      if playing_game == False:
        print("[X]", end='')
      else:
        print("[ ]", end='')
    elif map_dict[location] == 'door':
      if playing_game == False:
        print("[@]", end='')
      else:
        print("[ ]", end='')
    elif map_dict[location] == 'player':
      print("[P]", end='')

    if x_count == map_size - 1:
      print("\n", end='')
      x_count = 0
    else:
      x_count += 1

def move_player(direction, current_player_loc):
  player_y, player_x = current_player_loc
  moved = True
  if direction == "L" and not player_x == 0:
    player_x -= 1
  elif direction == "R" and not player_x == map_size - 1:
    player_x += 1
  elif direction == "U" and not player_y == 0:
    player_y -= 1
  elif direction == "D" and not player_y == map_size -1:
    player_y += 1
  else:
    print("\n*** You can't move that way, you're at the edge of the map.")
    moved = False

  new_player_loc = player_y, player_x
  return(new_player_loc, current_player_loc, moved)

def show_legend():
  print("\nLEGEND:\n[ ] = room\n[P] = player\n[~] = breadcrumb\n[@] = door (hidden until you win, lose, or quit)\n[X] = monster (hidden until you win, lose, or quit)")

def show_help():
  print("\nHELP:\n- Move by typing L, R, U, or D.\n- Also type map, legend, help, or quit.")

#-----------------------------------------------------------------------
# START GAME: GENERATE MAP GRID, SET LOCATIONS, GENERATE LIVE MAP DICT
#-----------------------------------------------------------------------

print("\nSET MAP SIZE\n(3 = 3x3, 4 = 4x4, etc)\nStart with something small like 3 or 4. Min is 3, max is 20.")
map_size = set_map_size()
map_grid = generate_map_grid(map_size)
door_loc, monster_loc, player_loc = generate_locations()
map_dict = generate_map_dict(map_grid)
print("\n{}*{} map successfuly generated.".format(map_size,map_size))

show_legend()
show_help()

print("\nARE YOU READY TO ENTER THE DUNGEON?\nType Y if yes, or anything else to quit.")
if input("> ").upper() == 'Y':
  playing_game = True
  render_live_map()
else:
  playing_game = False
  print("\nGood bye.")

#-----------------------------------------------------------------------
# MAIN GAME LOOP
#-----------------------------------------------------------------------

while playing_game == True:

  move = input("\n> ").upper()
  render_map = True

  if move == "QUIT":
    playing_game = False
    quit = True
    render_live_map()
    print("\nOK. You quit. Bye...")
  elif move == "HELP":
    show_help()
    continue    
  elif move == "LEGEND":
    show_legend()
    continue
  elif move == "MAP":
    render_live_map()
    continue
  elif move == "L" or move == "R" or move == "U" or move == "D":

    player_loc, old_player_loc, moved = move_player(move, player_loc)

    if moved == True:
      map_dict[old_player_loc] = 'breadcrumb'
      map_dict[player_loc] = 'player'
    else:
      render_map = False

    if player_loc == door_loc:
      playing_game = False
      map_dict[player_loc] = 'door' #to show location of door during final map render instead of P
      render_live_map()
      print("\nYOU FOUND THE DOOR! YOU WIN!")
    elif player_loc == monster_loc:
      playing_game = False
      map_dict[player_loc] = 'monster' #to show location of monster during final map render instead of P
      render_live_map()
      print("\nGAME OVER. THE MONSTER GOT YOU.")
    elif render_map == True:
      render_live_map()

  else:
    print("\n*** Invalid selection. Type help to see commands.")
    continue

#-----------------------------------------------------------------------
# PLAY AGAIN?
#-----------------------------------------------------------------------

  if playing_game == False and not quit == True:
    print("\nPlay Again?\nType Y if yes, or anything else to quit.")
    if input("> ").upper() == "Y":
      playing_game = True
      print("\nSET MAP SIZE\n(3 = 3x3, 4 = 4x4, etc)\nMin is 3, max is 20.")
      map_size = set_map_size()
      map_grid = generate_map_grid(map_size)
      door_loc, monster_loc, player_loc = generate_locations()
      map_dict = generate_map_dict(map_grid)
      print("\n{}*{} map successfuly generated.".format(map_size,map_size))
      render_live_map()
    else:
      print("Ok, bye.")

Wow!! That is so COOL! Thank you, it brings a tear to my eye..

1 Answer

Works great!

You could do a few things to clean up the code (like using the PEP 8 style guide for Python coding), and shorten/combine some things:

  1. Use a list comprehension to generate the map grid and return in one line:

    ### before ###
    def generate_map_grid(size):
      new_map = []
      for y in range(size):
        for x in range(size):
          new_map.append((y,x))
      return(new_map)
    
    ### after ###
    def generate_map_grid(size):
      return [(y,x) for y in range(size) for x in range(size)]
    
  2. Shorten all the conditional checks that are comparing to booleans, by changing == False to use not before the variable, and removing == True:

    ### before ###
    if map_dict[location] == 'empty':
          print ("[ ]", end='')
        elif map_dict[location] == 'breadcrumb':
          print("[~]", end='')
        elif map_dict[location] == 'monster':
          if playing_game == False:
            print("[X]", end='')
          else:
            print("[ ]", end='')
        elif map_dict[location] == 'door':
          if playing_game == False:
            print("[@]", end='')
          else:
            print("[ ]", end='')
        elif map_dict[location] == 'player':
          print("[P]", end='')
    
    ### after ###
    if map_dict[location] == 'empty':
          print ("[ ]", end='')
        elif map_dict[location] == 'breadcrumb':
          print("[~]", end='')
        elif map_dict[location] == 'monster':
          if not playing_game:
            print("[X]", end='')
          else:
            print("[ ]", end='')
        elif map_dict[location] == 'door':
          if not playing_game:
            print("[@]", end='')
          else:
            print("[ ]", end='')
        elif map_dict[location] == 'player':
          print("[P]", end='')
    
    ### before ###
    if playing_game == False and not quit == True:
    
    ### after ###
    if not playing_game and not quit:
    
    ### before ###
    while playing_game == True:
    
    ### after ###
    while playing_game:
    
    ### before ###
    if moved == True:
          map_dict[old_player_loc] = 'breadcrumb'
          map_dict[player_loc] = 'player'
        else:
          render_map = False
    
        if player_loc == door_loc:
          playing_game = False
          map_dict[player_loc] = 'door' #to show location of door during final map render instead of P
          render_live_map()
          print("\nYOU FOUND THE DOOR! YOU WIN!")
        elif player_loc == monster_loc:
          playing_game = False
          map_dict[player_loc] = 'monster' #to show location of monster during final map render instead of P
          render_live_map()
          print("\nGAME OVER. THE MONSTER GOT YOU.")
        elif render_map == True:
          render_live_map()
    
    ### after ###
    if moved:
          map_dict[old_player_loc] = 'breadcrumb'
          map_dict[player_loc] = 'player'
        else:
          render_map = False
    
        if player_loc == door_loc:
          playing_game = False
          map_dict[player_loc] = 'door' #to show location of door during final map render instead of P
          render_live_map()
          print("\nYOU FOUND THE DOOR! YOU WIN!")
        elif player_loc == monster_loc:
          playing_game = False
          map_dict[player_loc] = 'monster' #to show location of monster during final map render instead of P
          render_live_map()
          print("\nGAME OVER. THE MONSTER GOT YOU.")
        elif render_map:
          render_live_map()
    

    If you don't like the look of that code, because you feel it makes it less clear, then maybe you could rename the variables to suit:

    ### from ###
    if moved:
          map_dict[old_player_loc] = 'breadcrumb'
          map_dict[player_loc] = 'player'
        else:
          render_map = False
    
    ### to ###
    if has_moved:
          map_dict[old_player_loc] = 'breadcrumb'
          map_dict[player_loc] = 'player'
        else:
          should_render_map = False
    
  3. In your main game loop, you're using continue within the if/elif/else conditional statements. You don't need that, since it will exit the if/elif/else after it completes the matching block, and loop again once it reaches the end of the while block:

    ### before ###
    while playing_game:
    
      move = input("\n> ").upper()
      render_map = True
    
      if move == "QUIT":
        playing_game = False
        quit = True
        render_live_map()
        print("\nOK. You quit. Bye...")
      elif move == "HELP":
        show_help()
        continue
      elif move == "LEGEND":
        show_legend()
        continue
      elif move == "MAP":
        render_live_map()
        continue
      elif move == "L" or move == "R" or move == "U" or move == "D":
    
        player_loc, old_player_loc, moved = move_player(move, player_loc)
    
        if moved:
          map_dict[old_player_loc] = 'breadcrumb'
          map_dict[player_loc] = 'player'
        else:
          render_map = False
    
        if player_loc == door_loc:
          playing_game = False
          map_dict[player_loc] = 'door' #to show location of door during final map render instead of P
          render_live_map()
          print("\nYOU FOUND THE DOOR! YOU WIN!")
        elif player_loc == monster_loc:
          playing_game = False
          map_dict[player_loc] = 'monster' #to show location of monster during final map render instead of P
          render_live_map()
          print("\nGAME OVER. THE MONSTER GOT YOU.")
        elif render_map:
          render_live_map()
    
      else:
        print("\n*** Invalid selection. Type help to see commands.")
        continue
    
    ### after ###
    while playing_game:
    
      move = input("\n> ").upper()
      render_map = True
    
      if move == "QUIT":
        playing_game = False
        quit = True
        render_live_map()
        print("\nOK. You quit. Bye...")
      elif move == "HELP":
        show_help()
      elif move == "LEGEND":
        show_legend()
      elif move == "MAP":
        render_live_map()
      elif move == "L" or move == "R" or move == "U" or move == "D":
    
        player_loc, old_player_loc, moved = move_player(move, player_loc)
    
        if moved:
          map_dict[old_player_loc] = 'breadcrumb'
          map_dict[player_loc] = 'player'
        else:
          render_map = False
    
        if player_loc == door_loc:
          playing_game = False
          map_dict[player_loc] = 'door' #to show location of door during final map render instead of P
          render_live_map()
          print("\nYOU FOUND THE DOOR! YOU WIN!")
        elif player_loc == monster_loc:
          playing_game = False
          map_dict[player_loc] = 'monster' #to show location of monster during final map render instead of P
          render_live_map()
          print("\nGAME OVER. THE MONSTER GOT YOU.")
        elif render_map:
          render_live_map()
    
      else:
        print("\n*** Invalid selection. Type help to see commands.")
    
  4. There's a few places where you're using print just before you use input. You could just combine the two and only use the input, to save another function call. If you're worried about the argument to input being too large, move the prompt message to a variable, which would also make it easier to change later:

    ### before ###
    print("\nARE YOU READY TO ENTER THE DUNGEON?\nType Y if yes, or anything else to quit.")
    if input("> ").upper() == 'Y':
    
    ### after ###
    if input("\nARE YOU READY TO ENTER THE DUNGEON?\nType Y if yes, or anything else to quit.\n> ").upper() == 'Y':
    
    ### or ###
    ready_message = "\nARE YOU READY TO ENTER THE DUNGEON?\nType Y if yes, or anything else to quit.\n> "
    if input(ready_message).upper() == 'Y':
    
  5. If you're printing the same variable twice using the format function, you can use the index twice in the string you're formatting:

    ### before ###
    print("\n{}*{} map successfuly generated.".format(map_size,map_size))
    
    ### after ###
    print("\n{0}*{0} map successfully generated.".format(map_size))
    
  6. It looks like you repeat a lot of code when setting up a new game. Maybe move that to a new function? It'd mean changing a whole lot of other stuff so that you're passing in parameters to your other functions for things like the map_grid and map_dict. This would be a much bigger change of course.

  7. It seems a bit strange to me to have the grid as a list and then a dict to hold information about particular locations within that grid and what is in those places. Why not have either a nested list with the inner lists containing the coordinates and what is in that cell, or use a dict within each cell, with, say keys x and y for the coordinates and occupant or something to describe what is there. Then you would just always access the one list.

  8. You could change to a class-based system, which I think is covered in another Python course.

the greatest comment i've ever seen in treehouse forum (Y) well done bro great suggestion

Thanks Alfred E Bence! It was a good experience/practice to review someone else's code and think about how I might have done things differently.

Treehouse really has a great community, and that's the main thing that keeps bringing me back here from any other web development learning solution.