0

I'm writing a program which plays Tic Tac Toe and has various versions of ComputerPlayer, such as the RandomPlayer and THandPlayer:

class RandomPlayer(ComputerPlayer):
    def __init__(self, mark):
        super(RandomPlayer, self).__init__(mark=mark)

    def get_move(self, board):
        moves = board.available_moves()
        if moves:   # If "moves" is not an empty list (as it would be if cat's game were reached)
            return moves[np.random.choice(len(moves))]    # Apply random select to the index, as otherwise it will be seen as a 2D array

class THandPlayer(ComputerPlayer):
    def __init__(self, mark):
        super(THandPlayer, self).__init__(mark=mark)

    def get_move(self, board):
        moves = board.available_moves()
        if moves:   # If "moves" is not an empty list (as it would be if cat's game were reached)
            for move in moves:
                if board.get_next_board(move, self.mark).winner() == self.mark:                         # Make winning move (if possible)
                    return move
                elif board.get_next_board(move, self.opponent_mark).winner() == self.opponent_mark:     # Block opponent's winning move
                    return move
            else:
                # return moves[np.random.choice(len(moves))]        # This is a repetition of the code in RandomPlayer and is not DRY
                randomplayer = RandomPlayer(mark=self.mark)
                return randomplayer.get_move(board)
                # return RandomPlayer.get_move(board)         # This returns an error as "get_move" is an instance method

The THandPlayer also selects moves at random if no winning move can be made or an opponent's winning move blocked. Right now I am doing this by creating an instance of RandomPlayer and calling get_move on it. This could be made more succinct, however, if get_move could be made such that it can be interpreted both as a class method and an instance method. Is this possible?

EDIT

To simplify the question, suppose we have two classes, RandomPlayer and OtherPlayer, both which have an instance method get_move:

import numpy as np

class RandomPlayer:
    def get_move(self, arr):
        return np.random.choice(arr)

class OtherPlayer:
    def get_move(self, arr):
        if max(arr) > 5:
            return max(arr)
        else:
            randomplayer=RandomPlayer()
            return randomplayer.get_move(arr)

arr = np.arange(4)

otherplayer = OtherPlayer()
print otherplayer.get_move(arr)

Is it possible to use RandomPlayer's get_move method in OtherPlayer without creating an instance of RandomPlayer?

1
  • 5
    Instance methods have access to an instance (self), class or static methods do not. Either your method needs self or it does not. If it requires self, then it cannot be a class method or static. If it does not need self, then just make it a static/class method, period. Commented Aug 15, 2016 at 15:13

4 Answers 4

2

It sounds like you're looking for a staticmethod, which has access to neither cls nor self but can be accessed via either:

>>> class Foo:
...     @staticmethod
...     def bar():
...         print('baz')
...         
>>> Foo.bar()
baz
>>> Foo().bar()
baz
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks, I had not noticed at first that you had omitted the self when defining the static method.
1

(This is an alternative to my other answer, using a different abstraction.)

A random move isn't something associated with a player as much as it is something associated with a board; it's like board.available_moves, but returns a single move instead of all moves.

 class Board(...):
     # Given how often this is called by or before
     # random_move(), it would be smart to implement
     # some kind of caching so that the available
     # moves don't have to be recalcuated for the same board
     # state every time it is called.
     def available_moves(self):
         ...

     def random_move(self):
         moves = self.available_moves()
         if moves:
             return moves[np.random.choice(len(moves))]

class RandomPlayer(ComputerPlayer):

    def get_move(self, board):
        return board.random_move()

class THandPlayer(ComputerPlayer):
    def get_move(self, board):
        moves = board.available_moves()
        if moves:
            for move in moves:
                if board.get_next_board(move, self.mark).winner() == self.mark:
                    return move
                elif board.get_next_board(move, self.opponent_mark).winner() == self.opponent_mark:
                    return move
            else:
                return board.random_move()

Comments

1

A random move is a specific type of move; put a method which generates one in ComputerPlayer; then both RandomPlayer and THandPlayer can call it as necessary.

class ComputerPlayer(...):
    @staticmethod
    def choose_random_move(moves):
        if moves:
            return moves[np.random.choice(len(moves))]


class RandomPlayer(ComputerPlayer):    
    def get_move(self, board):
        moves = board.available_moves()
        if moves:
            return self.choose_random_move(moves)


class THandPlayer(ComputerPlayer):

    def get_move(self, board):
        moves = board.available_moves()
        for move in moves:
            for mark in [self.mark, self.opponent_mark]:
                if board.get_next_board(move, mark).winner() == mark:
                    return move
        else:
            return self.choose_random_move(moves)

Some extra notes:

  • If your __init__ method doesn't do anything except call super and pass along the exact same arguments, don't implement it; just let the inherited method be called directly.

  • The two checks for a winner can be refactored.

  • choose_random_move doesn't necessarily need to be a static method; you can keep it as an instance method with a default implementation that ignores any player-specific information in choosing a move. Derived classes can override the method if they like.

2 Comments

Regarding your last point, you actually do want to make the move that would allow the opponent to win, as it blocks the square that the opponent could otherwise make its winning move in. (move here is a tuple representing a coordinate on the board that the player places its own mark on; I admit there may be some context missing from the question).
True; depending on the rules of the game, it could also be possible to block the move without actually making that exact move. But, strategy isn't really relevant to the question of making a random move, so I'll delete that discussion from the answer.
0

For the sake of completeness, here is my implementation of the solution suggested by deceze, in which I also followed chepner's suggestion to refactor the two Boolean statements:

class RandomPlayer(ComputerPlayer):
    def __init__(self, mark):
        super(RandomPlayer, self).__init__(mark=mark)

    @staticmethod
    def get_move(board):
        moves = board.available_moves()
        if moves:   # If "moves" is not an empty list (as it would be if cat's game were reached)
            return moves[np.random.choice(len(moves))]    # Apply random selection to the index, as otherwise it will be seen as a 2D array

class THandPlayer(ComputerPlayer):
    def __init__(self, mark):
        super(THandPlayer, self).__init__(mark=mark)

    def get_move(self, board):
        moves = board.available_moves()
        if moves:
            for move in moves:
                if THandPlayer.next_move_winner(board, move, self.mark):
                    return move
                elif THandPlayer.next_move_winner(board, move, self.opponent_mark):
                    return move
            else:
                return RandomPlayer.get_move(board)         

    @staticmethod
    def next_move_winner(board, move, mark):
        return board.get_next_board(move, mark).winner() == mark

A static method is used both to default to the random player and to refactor the Boolean statements.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.