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

Made the monster Follow You! My variation on the grid based game.

I decided to make my own little spin on the grid based python game you create in the course "Python Collections"

Before I even watched the videos on making the game, I embarked on making it myself. I figured by the cute little animation depicting the game that you wanted us to make the monster follow the player. Turns out that wasn't the case, but I got it to work anyways :P

Here it is! Let me know if you have any suggestions, I know the coding work is probably a bit sloppy and there is, I'm sure, many improvements to be made. But! I hope you enjoy it nonetheless

In case you're wondering how I got the monster to follow the player, to pick the best move that gets it closest to the player, it was actually rather simple. What I did was take the distance between lines formula, which is sqrt((x2^2 - x1^2) + (y2^2 - y1^2)) of each possible move (up, down, left, and right) so long as they don't go outside the grid, and the monster just picked the move that resulted in the smallest number. Turns out, that's all it takes to get a monster to chase you!

import random

def create_moves(player):
    plyr = player
    removeable = (-1, -1)
    while True:
        up = ((plyr[0] - 1), plyr[1])
        down = ((plyr[0] + 1), plyr[1])
        left = (plyr[0], (plyr[1] - 1))
        right = (plyr[0], (plyr[1] + 1))
        move_list = [up, down, left, right]

        if move_list[1][0] > 4:
            move_list[1] = removeable
        if move_list[3][1] > 4:
            move_list[3] = removeable
        if move_list[0][0] < 0:
            move_list[0] = removeable
        if move_list[2][1] < 0:
            move_list[2] = removeable
        break

    try:
        choice_list = ["1 = Up {}".format(up), "2 = Down {}".format(down), "3 = Left {}".format(left),
                         "4 = Right {}".format(right)]
        for index, moves in enumerate(move_list):
            if move_list[index] == removeable:
                choice_list[index] = ""
        updated_list = list()
        for items in choice_list:
            if items == "":
                continue
            else:
                updated_list.append(items)

        print(*updated_list, sep='\n')
        user_choice = input("\nChoice: ")
        user_choice = int(user_choice)
        if user_choice > 4 or user_choice < 1:
            int("bacon") #Used to throw an exception to avoid 
                         #users entering in letters
        if user_choice == 1:
            return up
        elif user_choice == 2:
            return down
        elif user_choice == 3:
            return left
        elif user_choice == 4:
            return right
    except:
        print("Choice must be a number and it must be between 1 and 4")
        create_moves(plyr)

def create_monster_moves(mon):
    removeable = (-1, -1)
    while True:
        m_up = ((mon[0] - 1), mon[1])
        m_down = ((mon[0] + 1), mon[1])
        m_left = (mon[0], (mon[1] - 1))
        m_right = (mon[0], (mon[1] + 1))
        m_move_list = [m_up, m_down, m_left, m_right]

        if m_move_list[1][0] > 4:
            m_move_list[1] = removeable
        if m_move_list[3][1] > 4:
            m_move_list[3] = removeable
        if m_move_list[0][0] < 0:
            m_move_list[0] = removeable
        if m_move_list[2][1] < 0:
            m_move_list[2] = removeable

        return m_move_list

def move_monster(mon, plyr):
    removeable = (-1, -1)
    m_move_list = create_monster_moves(mon)
    best_move_num = 100.0
    best_move_tup = (0,0)
    while True:
        for monster_moves in m_move_list:
            if(monster_moves == removeable):
                continue
            else:
                move = ((plyr[0] - monster_moves[0])**(2) + (plyr[1] -
                                                   monster_moves[1])**(2))**(.5)

            if move < best_move_num:
                best_move_num = move
                best_move_tup = monster_moves
        break

    return best_move_tup

def draw_map(plyr, mon):
    print(" _ _ _ _ _")
    tile = "|{}"
    for idx, cell in enumerate(CELLS):
        if idx in [0, 1, 2, 3, 5, 6, 7, 8, 10, 11, 12, 13, 15, 16, 17, 18, 20, 21, 22, 23]:
            if cell == plyr:
                print(tile.format('X'), end='')
            elif cell == mon:
                print(tile.format('M'), end='')
            else:
                print(tile.format('_'), end='')
        else:
            if cell == plyr:
                print(tile.format("X|"))
            elif cell == mon:
                print(tile.format("M|"))
            else:
                print(tile.format("_|"))

CELLS = [(0,0), (0,1), (0,2), (0,3), (0,4),
         (1,0), (1,1), (1,2), (1,3), (1,4),
         (2,0), (2,1), (2,2), (2,3), (2,4),
         (3,0), (3,1), (3,2), (3,3), (3,4),
         (4,0), (4,1), (4,2), (4,3), (4,4)]

monster = 0
player = 0,0
door = 0

user_choice = 0

#Sets each object a cell. If anyone has the same cell, it starts over
while True:
    monster = random.choice(CELLS)
    player = random.choice(CELLS)
    door = random.choice(CELLS)

    if monster == door or monster == player or door == player:
        continue
    else:
        break
        #If it's else, none of them were equal

while True:
    print("\nYou are in cell {}".format(player))

    draw_map(player, monster)

    player = create_moves(player)
    if player == door:
        print("You win! You made it to the door!")
        break
    elif player == monster:
        print("You got eaten by the monster :( Game over")
        break
    monster = move_monster(monster, player)

    draw_map(player, monster)

    if player == monster:
        print("You got eaten by the monster :( Game over")
        break

2 Answers

John Hill
seal-mask
.a{fill-rule:evenodd;}techdegree seal-36
John Hill
Front End Web Development Techdegree Graduate 35,236 Points

Dude…the gameplay is definitely heightened by the chasing monster! I'm playing it right now! Couple things: when I'm next to a wall, and even though the option is removed, I can still select that option and go off the grid. And it'd be cool to have a 'Play Again?' option at the end of a game. It also shows the updated map twice after I select a move.

I'm not deep enough yet to know how to improve the code or I'd let you know if I saw anything! Here are some conceptual ideas though: being able to select diff map sizes (more monsters on larger maps?); when the monster gets the player, have an option to battle it(!) in a turn-based style fight or a dice roll; maybe the user could land on a power-up that would show where the door is. Maybe too much, but I'm digging it!

Question though: why is the removable tuple set to (-1,-1)? Also, how is it that you can call mon[0] and mon[1]? I don't a a 'mon' list…does it trace back to removable somehow??

Those are some really good idea, I mean like really good ideas. I will definitely work on them when I get a chance, I will try and implement as much of them as I can. Some great practice if I could get them all implemented into the game.

As for your first question, I have the variable removeable set to (-1, -1) just as a way to set and test the list if any of the moves go into the negatives. Essentially, if a move results in anything greater than 4 or smaller than 0, it's off the map, and the move in that index of the list is replaced with the tuple removeable which is (-1, -1). This way, I can have a loop that looks through the entire list and omits any of the indexes that are equal to the variable removeable which is (-1,. -1). Basically, it's just my way of figuring and testing for the moves that are invalid.

As for your second question: In the method create_monster_moves(mon): the passed in variable mon is a reference of the monster variable which is a tuple containing the position of the monster. When the method is called, you have to pass in the monster variable and therefore you can reference indexes of the tuple by doing mon[0] to get the first part (the row) or mon[1] to get the second part of the tuple (the column).

Oh, and I knew about being able to go off the grid. I am meaning to fix that since it's kind of sloppy to leave it in, but I hadn't been able to get around to it/I forgot it was there. So, thanks for the reminder! I will try and fix that while I try and add those other features and ideas

Brandon Wall
Brandon Wall
5,512 Points

Dude this is awesome, I'm going to look at it a little harder and play around with it myself. Your distance between lines formula is a bit over my head since I never got far in the maths, but one thing I did see that was interesting was your line.

if user_choice > 4 or user_choice < 1:
            int("bacon") #Used to throw an exception to avoid 
                         #users entering in letters

Instead of doing it like that, which is clever, you can use the keyword raise, like this:

if user_choice > 4 or user_choice <1:
    raise ValueError #You can also just use raise by itself. Check out help('raise') in the Python interpreter

raise is a great trick I learned recently, when used by itself it re-raises the last exception that was active in the current scope. If no exception is active in the current scope, A RuntimeError exception is raised indicating that this is an error.

John Hill
seal-mask
.a{fill-rule:evenodd;}techdegree seal-36
John Hill
Front End Web Development Techdegree Graduate 35,236 Points

"raise" the roof for this tip!! (I'm….SO….SORRY!!)

Distance formula [ (x2 - x1)^2 + (y2 - y1)^2 = c^2 ] is just an iteration of the Pythagorean Theorem [ a^2 + b^2 = c^2 ] to find a side of a triangle, where x2 - x1 = a and y2 - y1 = b.

distance

So if y2 is 12 and y1 is 5, the distance from y2 to y1 (and the length of that particular side of the triangle) is 7. Now you can plug that new number 7 into the Pythag-Theorem. If you know the side lengths initially, you can omit the distance formula and just use the Pythag-Theorem. But if you have only coordinates to work with, you'd do the one extra step of subtracting the coordinate values to find the side lengths.

These all say the same thing:

a^2 + b^2 = c^2

(x2 - x1)^2 + (y2 - y1)^2 = c^2

sqrt( (x2 - x1)^2 + (y2 - y1)^2 ) = c

**note: the pic uses the variable 'd' instead of 'c', but it's the same biz-nass

Huh, I had never heard of raise before, but I will definitely keep that in mind. A much more pythonic way to cause an exception that's for sure haha!

Oh, and thanks for the compliment :) Glad you like the program!