0

I am coding a simple maze game. I've created a Player class with a move method -- but whenever I want to check if the move is in the available paths list, it does run the move method and changes my character position.

class Player :

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

    def move (self, direction):
        if direction == "right":    self.position[0] += 1
        elif direction == "left":   self.position[0] -= 1
        elif direction == "up":     self.position[1] -= 1
        elif direction == "down":   self.position[1] += 1
        return self.position

maze_paths = [[0,0], [1,0]]
mac = Player([0,0])
direction = 'left'

if mac.move(direction) in maze_paths:
    print('ok')
else:
    print('No way!')

print('Final position is: %r' % (mac.position))

The output I expect is:

No way!
Final position is: [0, 0]

...because the move left shouldn't be allowed with the maze as it's defined. However, what we have instead is:

No way!
Final position is: [-1, 0]

...with the move happening even though it shouldn't be allowed.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
Toto Briac
  • 908
  • 9
  • 29
  • 4
    Because your `move` method is designed to change the player's position. What you need is a separate `can_move` method. – kaya3 Dec 17 '19 at 20:33
  • Note that in general, a good Stack Overflow question should have only the shortest possible code that can be used to reproduce and test fixes for a specific and narrow bug. Don't give us your whole program; cut it down to the shortest thing that can be run without changes to demonstrate your problem. And that problem itself should be explicitly shown -- as in, "I expect X, but get Y with traceback Z", with all three of X, Y and Z in the text. See the [mre] Help Center page for more details. – Charles Duffy Dec 17 '19 at 20:39
  • @kaya3 is correct. Your test (if mac.move(direction) is changing your state. One day you may like to learn about "functional" programming, which is very strict about functions which don't change the state of the computer. It will probably change the way you code even in Python. In the meantime, you need to either make a test of the proposed move which doesn't actually do it, or add a new function that reverses a move, by creating a new state while keeping the old one; if you don't want to make the move, you just throw away the new state. – Tim Richardson Dec 17 '19 at 20:41
  • @TimRichardson, ...I've edited this into a true [mcve] (see how the code is *actually runnable*, and both expected and actual outputs are provided); it now has my support. – Charles Duffy Dec 17 '19 at 21:41

2 Answers2

3

As @TimRichardson suggested in the comments, the act of calculating state should be split out from the act of changing state. Functional programming languages are built to make this easy, but Python isn't one -- so you need to jump through some hoops.

Consider splitting move into two methods, like the calculate_move and do_move shown below:

class Player:
    def calculate_move(self, direction):
        """Calculate where we would be if we moved in a direction"""
        emulated_position = self.position[:]  # make a copy
        if direction == "right":    emulated_position[0] += 1
        elif direction == "left":   emulated_position[0] -= 1
        elif direction == "up":     emulated_position[1] -= 1
        elif direction == "down":   emulated_position[1] += 1
        return emulated_position

    def do_move(self, direction):
        """Actually move in the given direction"""
        self.position = self.calculate_move(direction)

maze_paths = [[0,0], [1,0]]
mac = Player([0,0])
direction = 'left'

if mac.calculate_move(direction) in maze_paths:
    mac.do_move(direction)
    print('ok')
else:
    print('No way!')

print('Final position is: %r' % mac.position)

...which properly emits as output:

No way!
Final position is: [0, 0]
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • Works great. But why do I have to use slice() to copy self.position ? – Toto Briac Dec 18 '19 at 10:20
  • @TotoBriac, because otherwise, changing `emulated_position` would also change `self.position`, since `foo = bar` (with mutable types) makes `foo` *a reference to the same object as `bar`* in Python, rather than a separate object with the same initial value. – Charles Duffy Dec 18 '19 at 13:34
  • @TotoBriac, ...see also https://stackoverflow.com/questions/29785084/changing-one-list-unexpectedly-changes-another-too -- note that you'd avoid that issue by using tuples (which are immutable) instead of lists. (There are other advantages to that too; `if my_list in list_of_lists` has to read `list_of_lists` start-to-end, whereas because tuples are immutable they can be indexed, so you can make a *set* of tuples, such that checking if another tuple is inside that set happens in constant time; `if my_tuple in tuple_set` is much, *much* faster when the set is large). – Charles Duffy Dec 18 '19 at 13:35
0

Create a meningful separate validate function like. Did this on my phone.

def validateMove(direction):

      if direction == "right":
             return self.position[0] + 1 in self.maze_paths
      if direction == "down":
             return self.position[1] + 1 in self.maze_paths
      if direction == "left":
             return self.position[0] - 1 in self.maze_paths
      if direction =="up":
             return self.position[1] - 1 in self.maze_paths
      return false
grimur82
  • 149
  • 2
  • 10