CoolFace
Apppublic

AIConsultant/Tic-Tac-Toe-RL

sourceHugging Faceupdated 3y agoView on Hugging Face
1likes
objects.py291 linesDownload Raw Back to root
1import numpy as np2import pickle3 4BOARD_ROWS = 35BOARD_COLS = 36 7 8class State:9    def __init__(self, p1, p2):10        self.board = np.zeros((BOARD_ROWS, BOARD_COLS))11        self.p1 = p112        self.p2 = p213        self.isEnd = False14        self.boardHash = None15        # init p1 plays first16        self.playerSymbol = 117 18    # get unique hash of current board state19    def getHash(self):20        self.boardHash = str(self.board.reshape(BOARD_COLS * BOARD_ROWS))21        return self.boardHash22 23    def winner(self):24        # row25        for i in range(BOARD_ROWS):26            if sum(self.board[i, :]) == 3:27                self.isEnd = True28                return 129            if sum(self.board[i, :]) == -3:30                self.isEnd = True31                return -132        # col33        for i in range(BOARD_COLS):34            if sum(self.board[:, i]) == 3:35                self.isEnd = True36                return 137            if sum(self.board[:, i]) == -3:38                self.isEnd = True39                return -140        # diagonal41        diag_sum1 = sum([self.board[i, i] for i in range(BOARD_COLS)])42        diag_sum2 = sum([self.board[i, BOARD_COLS - i - 1] for i in range(BOARD_COLS)])43        diag_sum = max(abs(diag_sum1), abs(diag_sum2))44        if diag_sum == 3:45            self.isEnd = True46            if diag_sum1 == 3 or diag_sum2 == 3:47                return 148            else:49                return -150 51        # tie52        # no available positions53        if len(self.availablePositions()) == 0:54            self.isEnd = True55            return 056        # not end57        self.isEnd = False58        return None59 60    def availablePositions(self):61        positions = []62        for i in range(BOARD_ROWS):63            for j in range(BOARD_COLS):64                if self.board[i, j] == 0:65                    positions.append((i, j))  # need to be tuple66        return positions67 68    def updateState(self, position):69        self.board[position] = self.playerSymbol70        # switch to another player71        self.playerSymbol = -1 if self.playerSymbol == 1 else 172 73    # only when game ends74    def giveReward(self):75        result = self.winner()76        # backpropagate reward77        if result == 1:78            self.p1.feedReward(1)79            self.p2.feedReward(0)80        elif result == -1:81            self.p1.feedReward(0)82            self.p2.feedReward(1)83        else:84            self.p1.feedReward(0.1)85            self.p2.feedReward(0.5)86 87    # board reset88    def reset(self):89        self.board = np.zeros((BOARD_ROWS, BOARD_COLS))90        self.boardHash = None91        self.isEnd = False92        self.playerSymbol = 193 94    def playwithbot(self, rounds=100):95        for i in range(rounds):96            if i % 1000 == 0:97                print("Rounds {}".format(i))98            while not self.isEnd:99                # Player 1100                positions = self.availablePositions()101                p1_action = self.p1.chooseAction(positions, self.board, self.playerSymbol)102                # take action and upate board state103                self.updateState(p1_action)104                board_hash = self.getHash()105                self.p1.addState(board_hash)106                # check board status if it is end107 108                win = self.winner()109                if win is not None:110                    # self.showBoard()111                    # ended with p1 either win or draw112                    self.giveReward()113                    self.p1.reset()114                    self.p2.reset()115                    self.reset()116                    break117 118                else:119                    # Player 2120                    positions = self.availablePositions()121                    p2_action = self.p2.chooseAction(positions, self.board, self.playerSymbol)122                    self.updateState(p2_action)123                    board_hash = self.getHash()124                    self.p2.addState(board_hash)125 126                    win = self.winner()127                    if win is not None:128                        # self.showBoard()129                        # ended with p2 either win or draw130                        self.giveReward()131                        self.p1.reset()132                        self.p2.reset()133                        self.reset()134                        break135 136    # play with human137    def playwithhuman(self):138        while not self.isEnd:139            # Player 1140            positions = self.availablePositions()141            p1_action = self.p1.chooseAction(positions, self.board, self.playerSymbol)142            # take action and upate board state143            self.updateState(p1_action)144            self.showBoard()145            # check board status if it is end146            win = self.winner()147            if win is not None:148                if win == 1:149                    print(self.p1.name, "wins!")150                else:151                    print("tie!")152                self.reset()153                break154 155            else:156                # Player 2157                positions = self.availablePositions()158                p2_action = self.p2.chooseAction(positions)159 160                self.updateState(p2_action)161                self.showBoard()162                win = self.winner()163                if win is not None:164                    if win == -1:165                        print(self.p2.name, "wins!")166                    else:167                        print("tie!")168                    self.reset()169                    break170 171    def showBoard(self):172        # p1: x  p2: o173        for i in range(0, BOARD_ROWS):174            print('-------------')175            out = '| '176            for j in range(0, BOARD_COLS):177                if self.board[i, j] == 1:178                    token = 'x'179                if self.board[i, j] == -1:180                    token = 'o'181                if self.board[i, j] == 0:182                    token = ' '183                out += token + ' | '184            print(out)185        print('-------------')186 187 188class Player:189    def __init__(self, name, exp_rate=0.3):190        self.name = name191        self.states = []  # record all positions taken192        self.lr = 0.2193        self.exp_rate = exp_rate194        self.decay_gamma = 0.9195        self.states_value = {}  # state -> value196        self.loadPolicy('policy_' + str(self.name))  # Load the pre-trained policy197 198    def getHash(self, board):199        boardHash = str(board.reshape(BOARD_COLS * BOARD_ROWS))200        return boardHash201 202    def chooseAction(self, positions, current_board, symbol):203        if np.random.uniform(0, 1) <= self.exp_rate:204            # take random action205            idx = np.random.choice(len(positions))206            action = positions[idx]207        else:208            value_max = -999209            for p in positions:210                next_board = current_board.copy()211                next_board[p] = symbol212                next_boardHash = self.getHash(next_board)213                value = 0 if self.states_value.get(next_boardHash) is None else self.states_value.get(next_boardHash)214                # print("value", value)215                if value >= value_max:216                    value_max = value217                    action = p218        # print("{} takes action {}".format(self.name, action))219        return action220 221    # append a hash state222    def addState(self, state):223        self.states.append(state)224 225    # at the end of game, backpropagate and update states value226    def feedReward(self, reward):227        for st in reversed(self.states):228            if self.states_value.get(st) is None:229                self.states_value[st] = 0230            self.states_value[st] += self.lr * (self.decay_gamma * reward - self.states_value[st])231            reward = self.states_value[st]232 233    def reset(self):234        self.states = []235 236    def savePolicy(self):237        fw = open('policy_' + str(self.name), 'wb')238        pickle.dump(self.states_value, fw)239        fw.close()240 241    def loadPolicy(self, file):242        fr = open(file, 'rb')243        self.states_value = pickle.load(fr)244        fr.close()245 246 247class HumanPlayer:248    def __init__(self, name):249        self.name = name250 251    def chooseAction(self, positions):252        while True:253            row = int(input("Input your action row:"))254            col = int(input("Input your action col:"))255            action = (row, col)256            if action in positions:257                return action258 259    # append a hash state260    def addState(self, state):261        pass262 263    # at the end of game, backpropagate and update states value264    def feedReward(self, reward):265        pass266 267    def reset(self):268        pass269 270 271if __name__ == "__main__":272    # training273    p1 = Player("p1")274    p2 = Player("p2")275 276    st = State(p1, p2)277    print("training...")278    st.playwithbot(200000)279 280    p1.savePolicy()281    p2.savePolicy()282 283    # # play with human284    # p1 = Player("computer", exp_rate=0)285    # p1.loadPolicy("policy_p1")286 287    # p2 = HumanPlayer("human")288 289    # st = State(p1, p2)290    # st.playwithhuman()291