EX45 How to Create A Map

Hi guys!:heart:
First, sorry for being unclear. It’s just a rough idea I came up while making the game, don’t know how to execute it properly.

I’m looking for a way to make a 2D imaginary map (a way to store information, not to be printed out), so whenever the user prompt North/South/West/East, the imaginary “adventurer” can walk up/down/left/right the map, then read the info inside that block, call for the specific scene inside that block.

By doing so, the story of the game doesn’t have to be linear and allows the user to move more freely. I suppose I don’t have to assign a specific scene_name on every return.

I’m thinking of using 2-d array or 2-d list, however, I’m kind of stuck right now. I’m not sure if this is the right direction to keep on researching and I’m not sure how to link a bunch of information to a specific item in list/array.

PS. I think EX45 can be really easy or really hard. If I want to make slight modifications to EX43, it can be really easy, but if I try to make something more, I feel like what I’ve learned so far is really not enough. It can get frustrated for me sometime.
I think a little bit more guidance would be helpful.:laughing::+1:

I like the idea. :slight_smile:

I’m not sure how to link a bunch of information to a specific item in list/array.

Something like this?

class GoldRoom(object):
    def enter(self):
        print("This is the gold room.")

map = [ [ GoldRoom() ] ]
    
next_room = map[0][0]
next_room.enter()

@zedshaw Since I’m new here: Is it OK to post something like this or do you reserve that right to your trusted helpers?

I don’t know about the visual programming, but implementing user directions is as simple as providing a different ‘room’ depending on user input.

For example, if input is ‘N’ return ‘Forest’, if ‘S’ return ‘Haunted_River’, if ‘E’ return ‘Graveyard’, etc… That makes the game non-linear. I included items into an inventory that I would then impact the options in different places.

It was fun, but soon made me realise how complicated a text-based adventure can get really quickly as you list of conditionals gets quite unwieldily.

Yes @CodeX, so a grid of squares, kind of like that move The Cube? Call it “The Grid”. :wink:

The code @florian has is the right start, but to make it clear what a 2d grid would look like do this first:

map = [
   [ 0, 1, 1, 0],
   [ 1, 0, 0, 1],
   [ 0, 0, 0, 0],
   [ 1, 1, 0, 0]
]

Now, imagine that’s a simple map of where monsters are that can instantly kill your player, and a player can move north, south, east, and west. If they move to a room with 1 they die. If they move to 0, they’re safe. Now, just come up with functions or objects that move the player by changing the x/y coordinates of them, using those coordinates to get the 0 or 1, and then tell the player they died.

See if that’s a good starting point then bug me for more pointers on how you could make this work. I’d start small and only figure out the movement between grid cells, then the player dying, then possibly how to clue the player in that they’re near a room that’s dangerous.

1 Like

LOL, that’s true. @gpkesley So instead of returning different things based on input, I try to link their input with functions that directly moves the character. I’m still trying to figure out a way to do so.:thinking:

Thank you so much @florian and @zedshaw. I think I have some ideas now! :heart:

Let me know what you come up with. I think I might change the game in my books to use a grid like this as the OOP style is kind of garbage and too hard.

Hi Zed, I tried to experiment on the existing code of ex43. My original idea is to pass only coordinates between functions so I can do +/- on both coordinates and make the adventurer moves through the grid. However, I got following error message:

self.start_scene = map[a][b]
TypeError: ‘type’ object is not subscriptable

It seems python does not allow me to pass two int into the function, I don’t know which part did I do wrong. Need a little help here!

Code below:

class Map(object):
    scenes = {
        'central_corridor': CentralCorridor(),
    }

    map = [
        [ 'death', 'laser_weapon_armory', 'death', 'death'],
        [ 'death', 'the_bridge', 'death', 'death'],
        [ 'death', 'central_corridor', 'death', 'death'],
        [ 'death', 'escape_pod', 'finished', 'death']
    ]

    def __init__(self, a, b):
        self.start_scene = map[a][b]

    def next_scene(self, a, b):
        val = Map.scenes.get(map[a][b])
        return val

    def opening_scene(self):
        return self.next_scene(self.start_scene)

a_map = Map(1, 2)

Thank you so much!:heart:

1 Like

Hi @CodeX

You need to do:

self.a = a. # in the init method.
self.b = b

EDIT a little correction.

Then make another method for ”start_scene”

def start_scene(self):
    self.start_scene = map[self.a][self.b]

I got it working this way.
Give it a try.
I can show you how if you need

It’s not about the two arguments.

Be careful when you refer to objects that belong to classes! You get this error because for the interpreter map is the built-in map function. It would probably work if you just changed it to self.map or Map.map, but it’s generally a good idea to avoid duplicating existing names in a language.

@ulfen69, does your start_scene function not cause the same error?

Hello @florian and @CodeX.

Here is CodeX’s code with some changes I did.

class CentralCorridor():
    print("CentralCorridor")

class Death():
    print("Death")

class Map(object):
    scenes = {
        'central_corridor': CentralCorridor(),
        'death': Death(),
    }

    map = [
        [ 'death', 'laser_weapon_armory', 'death', 'death'],
        [ 'death', 'the_bridge', 'death', 'death'],
        [ 'death', 'central_corridor', 'death', 'death'],
        [ 'death', 'escape_pod', 'finished', 'death']
    ]

    def __init__(self, a, b):

        self.a = a
        self.b = b

    def start_scene(self):
        self.start_scene = map[self.a][self.b]
        return self.start_scene

    def next_scene(self, a, b):
        val = Map.scenes.get(map[a][b])
        return val

    def opening_scene(self):
        return self.next_scene(self.start_scene)

a_map = Map(1,2)
# a_map.start_scene(0, 0)

It doesnt work as a game like this. It doesnt gives me any error.
But it print out the print statements from both classes.
So this needs more work to run as supposed.
If I find out I will tell. This was interesting. So I am happy to help if you want (@CodeX).

1 Like

That’s because start_scene doesn’t get called in this version.

Anyway, have fun making it work! :thumbsup: :slight_smile:

Hi, @ulfen69 and @florian! Thank you guys so much!:+1::+1: With your helps, I managed to make a first version of the code:

class Engine(object):

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

    def play(self):
        current_num = self.map_num.opening_num()
        last_num = self.map_num.last_num(3, 2)
        # I set ending coordinate to be (3, 2) 

        while current_num != last_num:
            print(current_num)
            action = input("> ")
            current_num = self.map_num.next_num(action)

        # be sure to print out the last number
        print(current_num)

class Map(object):

    map_num = [
       [ 00, 1, 2, 3],
       [ 10, 11, 12, 13],
       [ 20, 21, 22, 23],
       [ 30, 31, 32, 33]
    ]

    def __init__(self, a, b):
        self.a = a
        self.b = b
        self.start_num = self.map_num[self.a][self.b]

    def next_num(self, action):
        ori_a = self.a
        ori_b = self.b
        # If user input is not recognized or user goes outbound, 
        # use ori_a and ori_b to go back to current num again.

        if action == 'North':
            self.a -= 1
        elif action == 'South':
            self.a += 1
        elif action == 'West':
            self.b -= 1
        elif action == 'East':
            self.b += 1
        else:
            print("NA")
            return self.map_num[ori_a][ori_b]

        if self.a < 0 or self.a > 3 or self.b < 0 or self.b > 3:
            print:("Out of Bound")
            return self.map_num[ori_a][ori_b]

    val = self.map_num[self.a][self.b]

    return val

def opening_num(self):
    val = self.start_num
    return val

def last_num(self, a, b):
    val = self.map_num[a][b]
    return val

a_map = Map(2, 1)
a_game = Engine(a_map)
a_game.play()

The good news is it works perfectly if we let our adventurer stays within the grid.
However, if we accidentally/purposely moves the adventurer out of the grid, some weird behavior will show up:

21 (# starting coordinate)
> North
11
>North
1
> North
1
> North
31
> North
21
> North
11
> North
1
> North
Traceback (most recent call last):
File “test.py”, line 72, in
a_game.play()
File “test.py”, line 19, in play
current_num = self.map_num.next_num(action)
File “test.py”, line 56, in next_num
return self.map_num[new_a][new_b]
IndexError: list index out of range

It loops the column two times then comes to an IndexError, it doesn’t go through the code:

 if self.a < 0 or self.a > 3 or self.b < 0 or self.b > 3:
        print:("Out of Bound")
        return self.map_num[new_a][new_b]

And if I type in words (i.e “big”) other than North/South/West/East:

> big
Traceback (most recent call last):
File “test.py”, line 72, in
a_game.play()
File “test.py”, line 19, in play
current_num = self.map_num.next_num(action)
File “test.py”, line 52, in next_num
print(“NA”)
UnboundLocalError: local variable ‘print’ referenced before assignment

It does’t go through code:

else:
    print("NA")
    return self.map_num[new_a][new_b]

These two are the major problems I’m encountering right now.
Again, thanks for the helps from @florian and @ulfen69 !

1 Like

Stick it all in a try/catch block… and catch any map exceptions with a witty “you fell off the side of the flat earth!” ex message

1 Like

Oh, this is a subtle one! Love it… :slight_smile:

You have a rogue colon in the out-of-bounds if-statement. That confuses the interpreter into thinking that you want to assign something to a variable called print. Which you don’t do, so when you use print later on it says “Hey there, you still haven’t told me what print is!”.

As for why it loops through the column: You do the check for out-of-bounds coordinates, but you have already changed self.a and self.b at that point. You need to reset them, or do the check at the start of the function before you change them.

Right now they keep being decremented, and since negative indices work on lists too, the loop works until you fall off on the negative side.

The print:() is your first problem. You may also have done this:

print = ‘some value’

So at the point when you get that error Python might think you meant some variable somewhere else in the code.

@florian you are so true about the column! I print out the coordinates and it is exactly what you said! I’ll find a way to fix that, thank you so much!
PS. The rogue colon is really a silly mistake! :hear_no_evil::hear_no_evil:

@florian I got this one! Now it will hit the bound and return to the previous block! Thanks for your help!:heart::heart:

    def next_num(self, action):
        new_a = self.a
        new_b = self.b

        if action == 'North':
            new_a -= 1
        elif action == 'South':
            new_a += 1
        elif action == 'West':
            new_b -= 1
        elif action == 'East':
            new_b += 1
        else:
            print("Input Not Recognized. Please Re-Enter!")

        if new_a < 0 or new_a > 3 or new_b < 0 or new_b > 3:
            print("Out of Bound! Go Back!")
            return self.map_num[self.a][self.b]

        self.a = new_a
        self.b = new_b
        val = self.map_num[self.a][self.b]

        return val
2 Likes