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
?
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
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.
(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()
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.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.